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