1/*
2 * By John G. Myers, jgm+@cmu.edu
3 * Version 1.2
4 *
5 * Process a BITNET "internet.listing" file, producing output
6 * suitable for input to makemap.
7 *
8 * The input file can be obtained via anonymous FTP to bitnic.educom.edu.
9 * Change directory to "netinfo" and get the file internet.listing
10 * The file is updated monthly.
11 *
12 * Feed the output of this program to "makemap hash /etc/mail/bitdomain.db"
13 * to create the table used by the "FEATURE(bitdomain)" config file macro.
14 * If your sendmail does not have the db library compiled in, you can instead
15 * use "makemap dbm /etc/mail/bitdomain" and
16 * "FEATURE(bitdomain,`dbm -o /etc/mail/bitdomain')"
17 *
18 * The bitdomain table should be rebuilt monthly.
19 */
20
21#include <stdio.h>
22#include <errno.h>
23#include <sys/types.h>
24#include <netinet/in.h>
25#include <arpa/nameser.h>
26#include <resolv.h>
27#include <netdb.h>
28#include <ctype.h>
29#include <string.h>
30
31/* don't use sizeof because sizeof(long) is different on 64-bit machines */
32#define SHORTSIZE	2	/* size of a short (really, must be 2) */
33#define LONGSIZE	4	/* size of a long (really, must be 4) */
34
35typedef union
36{
37	HEADER	qb1;
38	char	qb2[PACKETSZ];
39} querybuf;
40
41extern int h_errno;
42extern char *malloc();
43extern char *optarg;
44extern int optind;
45
46char *lookup();
47
48main(argc, argv)
49int argc;
50char **argv;
51{
52    int opt;
53
54    while ((opt = getopt(argc, argv, "o:")) != -1) {
55	switch (opt) {
56	case 'o':
57	    if (!freopen(optarg, "w", stdout)) {
58		perror(optarg);
59		exit(1);
60	    }
61	    break;
62
63	default:
64	    fprintf(stderr, "usage: %s [-o outfile] [internet.listing]\n",
65		    argv[0]);
66	    exit(1);
67	}
68    }
69
70    if (optind < argc) {
71	if (!freopen(argv[optind], "r", stdin)) {
72	    perror(argv[optind]);
73	    exit(1);
74	}
75    }
76    readfile(stdin);
77    finish();
78    exit(0);
79}
80
81/*
82 * Parse and process an input file
83 */
84readfile(infile)
85FILE *infile;
86{
87    int skippingheader = 1;
88    char buf[1024], *node, *hostname, *p;
89
90    while (fgets(buf, sizeof(buf), infile)) {
91	for (p = buf; *p && isspace(*p); p++);
92	if (!*p) {
93	    skippingheader = 0;
94	    continue;
95	}
96	if (skippingheader) continue;
97
98	node = p;
99	for (; *p && !isspace(*p); p++) {
100	    if (isupper(*p)) *p = tolower(*p);
101	}
102	if (!*p) {
103	    fprintf(stderr, "%-8s: no domain name in input file\n", node);
104	    continue;
105	}
106	*p++ = '\0';
107
108	for (; *p && isspace(*p); p++) ;
109	if (!*p) {
110	    fprintf(stderr, "%-8s no domain name in input file\n", node);
111	    continue;
112	}
113
114	hostname = p;
115	for (; *p && !isspace(*p); p++) {
116	    if (isupper(*p)) *p = tolower(*p);
117	}
118	*p = '\0';
119
120	/* Chop off any trailing .bitnet */
121	if (strlen(hostname) > 7 &&
122	    !strcmp(hostname+strlen(hostname)-7, ".bitnet")) {
123	    hostname[strlen(hostname)-7] = '\0';
124	}
125	entry(node, hostname, sizeof(buf)-(hostname - buf));
126    }
127}
128
129/*
130 * Process a single entry in the input file.
131 * The entry tells us that "node" expands to "domain".
132 * "domain" can either be a domain name or a bitnet node name
133 * The buffer pointed to by "domain" may be overwritten--it
134 * is of size "domainlen".
135 */
136entry(node, domain, domainlen)
137char *node;
138char *domain;
139char *domainlen;
140{
141    char *otherdomain, *p, *err;
142
143    /* See if we have any remembered information about this node */
144    otherdomain = lookup(node);
145
146    if (otherdomain && strchr(otherdomain, '.')) {
147	/* We already have a domain for this node */
148	if (!strchr(domain, '.')) {
149	    /*
150	     * This entry is an Eric Thomas FOO.BITNET kludge.
151	     * He doesn't want LISTSERV to do transitive closures, so we
152	     * do them instead.  Give the the domain expansion for "node"
153	     * (which is in "otherdomian") to FOO (which is in "domain")
154	     * if "domain" doesn't have a domain expansion already.
155	     */
156	    p = lookup(domain);
157	    if (!p || !strchr(p, '.')) remember(domain, otherdomain);
158	}
159    }
160    else {
161	if (!strchr(domain, '.') || valhost(domain, domainlen)) {
162	    remember(node, domain);
163	    if (otherdomain) {
164		/*
165		 * We previously mapped the node "node" to the node
166		 * "otherdomain".  If "otherdomain" doesn't already
167		 * have a domain expansion, give it the expansion "domain".
168		 */
169		p = lookup(otherdomain);
170		if (!p || !strchr(p, '.')) remember(otherdomain, domain);
171	    }
172	}
173	else {
174	    switch (h_errno) {
175	    case HOST_NOT_FOUND:
176		err = "not registered in DNS";
177		break;
178
179	    case TRY_AGAIN:
180		err = "temporary DNS lookup failure";
181		break;
182
183	    case NO_RECOVERY:
184		err = "non-recoverable nameserver error";
185		break;
186
187	    case NO_DATA:
188		err = "registered in DNS, but not mailable";
189		break;
190
191	    default:
192		err = "unknown nameserver error";
193		break;
194	    }
195
196	    fprintf(stderr, "%-8s %s %s\n", node, domain, err);
197	}
198    }
199}
200
201/*
202 * Validate whether the mail domain "host" is registered in the DNS.
203 * If "host" is a CNAME, it is expanded in-place if the expansion fits
204 * into the buffer of size "hbsize".  Returns nonzero if it is, zero
205 * if it is not.  A BIND error code is left in h_errno.
206 */
207int
208valhost(host, hbsize)
209	char *host;
210	int hbsize;
211{
212	register u_char *eom, *ap;
213	register int n;
214	HEADER *hp;
215	querybuf answer;
216	int ancount, qdcount;
217	int ret;
218	int type;
219	int qtype;
220	char nbuf[1024];
221
222	if ((_res.options & RES_INIT) == 0 && res_init() == -1)
223		return (0);
224
225	_res.options &= ~(RES_DNSRCH|RES_DEFNAMES);
226	_res.retrans = 30;
227	_res.retry = 10;
228
229	qtype = T_ANY;
230
231	for (;;) {
232		h_errno = NO_DATA;
233		ret = res_querydomain(host, "", C_IN, qtype,
234				      &answer, sizeof(answer));
235		if (ret <= 0)
236		{
237			if (errno == ECONNREFUSED || h_errno == TRY_AGAIN)
238			{
239				/* the name server seems to be down */
240				h_errno = TRY_AGAIN;
241				return 0;
242			}
243
244			if (h_errno != HOST_NOT_FOUND)
245			{
246				/* might have another type of interest */
247				if (qtype == T_ANY)
248				{
249					qtype = T_A;
250					continue;
251				}
252				else if (qtype == T_A)
253				{
254					qtype = T_MX;
255					continue;
256				}
257			}
258
259			/* otherwise, no record */
260			return 0;
261		}
262
263		/*
264		**  This might be a bogus match.  Search for A, MX, or
265		**  CNAME records.
266		*/
267
268		hp = (HEADER *) &answer;
269		ap = (u_char *) &answer + sizeof(HEADER);
270		eom = (u_char *) &answer + ret;
271
272		/* skip question part of response -- we know what we asked */
273		for (qdcount = ntohs(hp->qdcount); qdcount--; ap += ret + QFIXEDSZ)
274		{
275			if ((ret = dn_skipname(ap, eom)) < 0)
276			{
277				return 0;		/* ???XXX??? */
278			}
279		}
280
281		for (ancount = ntohs(hp->ancount); --ancount >= 0 && ap < eom; ap += n)
282		{
283			n = dn_expand((u_char *) &answer, eom, ap,
284				      (u_char *) nbuf, sizeof nbuf);
285			if (n < 0)
286				break;
287			ap += n;
288			GETSHORT(type, ap);
289			ap += SHORTSIZE + LONGSIZE;
290			GETSHORT(n, ap);
291			switch (type)
292			{
293			  case T_MX:
294			  case T_A:
295				return 1;
296
297			  case T_CNAME:
298				/* value points at name */
299				if ((ret = dn_expand((u_char *)&answer,
300				    eom, ap, (u_char *)nbuf, sizeof(nbuf))) < 0)
301					break;
302				if (strlen(nbuf) < hbsize) {
303				    (void)strcpy(host, nbuf);
304				}
305				return 1;
306
307			  default:
308				/* not a record of interest */
309				continue;
310			}
311		}
312
313		/*
314		**  If this was a T_ANY query, we may have the info but
315		**  need an explicit query.  Try T_A, then T_MX.
316		*/
317
318		if (qtype == T_ANY)
319			qtype = T_A;
320		else if (qtype == T_A)
321			qtype = T_MX;
322		else
323			return 0;
324	}
325}
326
327struct entry {
328    struct entry *next;
329    char *node;
330    char *domain;
331};
332struct entry *firstentry;
333
334/*
335 * Find any remembered information about "node"
336 */
337char *lookup(node)
338char *node;
339{
340    struct entry *p;
341
342    for (p = firstentry; p; p = p->next) {
343	if (!strcmp(node, p->node)) {
344	    return p->domain;
345	}
346    }
347    return 0;
348}
349
350/*
351 * Mark the node "node" as equivalent to "domain".  "domain" can either
352 * be a bitnet node or a domain name--if it is the latter, the mapping
353 * will be written to stdout.
354 */
355remember(node, domain)
356char *node;
357char *domain;
358{
359    struct entry *p;
360
361    if (strchr(domain, '.')) {
362	fprintf(stdout, "%-8s %s\n", node, domain);
363    }
364
365    for (p = firstentry; p; p = p->next) {
366	if (!strcmp(node, p->node)) {
367	    p->domain = malloc(strlen(domain)+1);
368	    if (!p->domain) {
369		goto outofmemory;
370	    }
371	    strcpy(p->domain, domain);
372	    return;
373	}
374    }
375
376    p = (struct entry *)malloc(sizeof(struct entry));
377    if (!p) goto outofmemory;
378
379    p->next = firstentry;
380    firstentry = p;
381    p->node = malloc(strlen(node)+1);
382    p->domain = malloc(strlen(domain)+1);
383    if (!p->node || !p->domain) goto outofmemory;
384    strcpy(p->node, node);
385    strcpy(p->domain, domain);
386    return;
387
388  outofmemory:
389    fprintf(stderr, "Out of memory\n");
390    exit(1);
391}
392
393/*
394 * Walk through the database, looking for any cases where we know
395 * node FOO is equivalent to node BAR and node BAR has a domain name.
396 * For those cases, give FOO the same domain name as BAR.
397 */
398finish()
399{
400    struct entry *p;
401    char *domain;
402
403    for (p = firstentry; p; p = p->next) {
404	if (!strchr(p->domain, '.') && (domain = lookup(p->domain))) {
405	    remember(p->node, domain);
406	}
407    }
408}
409
410