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 
35 typedef union
36 {
37 	HEADER	qb1;
38 	char	qb2[PACKETSZ];
39 } querybuf;
40 
41 extern int h_errno;
42 extern char *malloc();
43 extern char *optarg;
44 extern int optind;
45 
46 char *lookup();
47 
48 main(argc, argv)
49 int argc;
50 char **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  */
84 readfile(infile)
85 FILE *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  */
136 entry(node, domain, domainlen)
137 char *node;
138 char *domain;
139 char *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  */
207 int
208 valhost(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 
327 struct entry {
328     struct entry *next;
329     char *node;
330     char *domain;
331 };
332 struct entry *firstentry;
333 
334 /*
335  * Find any remembered information about "node"
336  */
337 char *lookup(node)
338 char *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  */
355 remember(node, domain)
356 char *node;
357 char *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  */
398 finish()
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