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
main(argc,argv)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 */
readfile(infile)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 */
entry(node,domain,domainlen)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
valhost(host,hbsize)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 */
lookup(node)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 */
remember(node,domain)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 */
finish()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