NetBSD/usr.sbin/sendmail/contrib/bitdomain.c
1996-01-22 12:50:24 +00:00

410 lines
8.7 KiB
C

/*
* By John G. Myers, jgm+@cmu.edu
* Version 1.2
*
* Process a BITNET "internet.listing" file, producing output
* suitable for input to makemap.
*
* The input file can be obtained via anonymous FTP to bitnic.educom.edu.
* Change directory to "netinfo" and get the file internet.listing
* The file is updated monthly.
*
* Feed the output of this program to "makemap hash /etc/bitdomain.db"
* to create the table used by the "FEATURE(bitdomain)" config file macro.
* If your sendmail does not have the db library compiled in, you can instead
* use "makemap dbm /etc/bitdomain" and
* "FEATURE(bitdomain,`dbm -o /etc/bitdomain')"
*
* The bitdomain table should be rebuilt monthly.
*/
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>
#include <ctype.h>
#include <string.h>
/* don't use sizeof because sizeof(long) is different on 64-bit machines */
#define SHORTSIZE 2 /* size of a short (really, must be 2) */
#define LONGSIZE 4 /* size of a long (really, must be 4) */
typedef union
{
HEADER qb1;
char qb2[PACKETSZ];
} querybuf;
extern int h_errno;
extern char *malloc();
extern char *optarg;
extern int optind;
char *lookup();
main(argc, argv)
int argc;
char **argv;
{
int opt;
while ((opt = getopt(argc, argv, "o:")) != EOF) {
switch (opt) {
case 'o':
if (!freopen(optarg, "w", stdout)) {
perror(optarg);
exit(1);
}
break;
default:
fprintf(stderr, "usage: %s [-o outfile] [internet.listing]\n",
argv[0]);
exit(1);
}
}
if (optind < argc) {
if (!freopen(argv[optind], "r", stdin)) {
perror(argv[optind]);
exit(1);
}
}
readfile(stdin);
finish();
exit(0);
}
/*
* Parse and process an input file
*/
readfile(infile)
FILE *infile;
{
int skippingheader = 1;
char buf[1024], *node, *hostname, *p;
while (fgets(buf, sizeof(buf), infile)) {
for (p = buf; *p && isspace(*p); p++);
if (!*p) {
skippingheader = 0;
continue;
}
if (skippingheader) continue;
node = p;
for (; *p && !isspace(*p); p++) {
if (isupper(*p)) *p = tolower(*p);
}
if (!*p) {
fprintf(stderr, "%-8s: no domain name in input file\n", node);
continue;
}
*p++ = '\0';
for (; *p && isspace(*p); p++) ;
if (!*p) {
fprintf(stderr, "%-8s no domain name in input file\n", node);
continue;
}
hostname = p;
for (; *p && !isspace(*p); p++) {
if (isupper(*p)) *p = tolower(*p);
}
*p = '\0';
/* Chop off any trailing .bitnet */
if (strlen(hostname) > 7 &&
!strcmp(hostname+strlen(hostname)-7, ".bitnet")) {
hostname[strlen(hostname)-7] = '\0';
}
entry(node, hostname, sizeof(buf)-(hostname - buf));
}
}
/*
* Process a single entry in the input file.
* The entry tells us that "node" expands to "domain".
* "domain" can either be a domain name or a bitnet node name
* The buffer pointed to by "domain" may be overwritten--it
* is of size "domainlen".
*/
entry(node, domain, domainlen)
char *node;
char *domain;
char *domainlen;
{
char *otherdomain, *p, *err;
/* See if we have any remembered information about this node */
otherdomain = lookup(node);
if (otherdomain && strchr(otherdomain, '.')) {
/* We already have a domain for this node */
if (!strchr(domain, '.')) {
/*
* This entry is an Eric Thomas FOO.BITNET kludge.
* He doesn't want LISTSERV to do transitive closures, so we
* do them instead. Give the the domain expansion for "node"
* (which is in "otherdomian") to FOO (which is in "domain")
* if "domain" doesn't have a domain expansion already.
*/
p = lookup(domain);
if (!p || !strchr(p, '.')) remember(domain, otherdomain);
}
}
else {
if (!strchr(domain, '.') || valhost(domain, domainlen)) {
remember(node, domain);
if (otherdomain) {
/*
* We previously mapped the node "node" to the node
* "otherdomain". If "otherdomain" doesn't already
* have a domain expansion, give it the expansion "domain".
*/
p = lookup(otherdomain);
if (!p || !strchr(p, '.')) remember(otherdomain, domain);
}
}
else {
switch (h_errno) {
case HOST_NOT_FOUND:
err = "not registered in DNS";
break;
case TRY_AGAIN:
err = "temporary DNS lookup failure";
break;
case NO_RECOVERY:
err = "non-recoverable nameserver error";
break;
case NO_DATA:
err = "registered in DNS, but not mailable";
break;
default:
err = "unknown nameserver error";
break;
}
fprintf(stderr, "%-8s %s %s\n", node, domain, err);
}
}
}
/*
* Validate whether the mail domain "host" is registered in the DNS.
* If "host" is a CNAME, it is expanded in-place if the expansion fits
* into the buffer of size "hbsize". Returns nonzero if it is, zero
* if it is not. A BIND error code is left in h_errno.
*/
int
valhost(host, hbsize)
char *host;
int hbsize;
{
register u_char *eom, *ap;
register int n;
HEADER *hp;
querybuf answer;
int ancount, qdcount;
int ret;
int type;
int qtype;
char nbuf[1024];
if ((_res.options & RES_INIT) == 0 && res_init() == -1)
return (0);
_res.options &= ~(RES_DNSRCH|RES_DEFNAMES);
_res.retrans = 30;
_res.retry = 10;
qtype = T_ANY;
for (;;) {
h_errno = NO_DATA;
ret = res_querydomain(host, "", C_IN, qtype,
&answer, sizeof(answer));
if (ret <= 0)
{
if (errno == ECONNREFUSED || h_errno == TRY_AGAIN)
{
/* the name server seems to be down */
h_errno = TRY_AGAIN;
return 0;
}
if (h_errno != HOST_NOT_FOUND)
{
/* might have another type of interest */
if (qtype == T_ANY)
{
qtype = T_A;
continue;
}
else if (qtype == T_A)
{
qtype = T_MX;
continue;
}
}
/* otherwise, no record */
return 0;
}
/*
** This might be a bogus match. Search for A, MX, or
** CNAME records.
*/
hp = (HEADER *) &answer;
ap = (u_char *) &answer + sizeof(HEADER);
eom = (u_char *) &answer + ret;
/* skip question part of response -- we know what we asked */
for (qdcount = ntohs(hp->qdcount); qdcount--; ap += ret + QFIXEDSZ)
{
if ((ret = dn_skipname(ap, eom)) < 0)
{
return 0; /* ???XXX??? */
}
}
for (ancount = ntohs(hp->ancount); --ancount >= 0 && ap < eom; ap += n)
{
n = dn_expand((u_char *) &answer, eom, ap,
(u_char *) nbuf, sizeof nbuf);
if (n < 0)
break;
ap += n;
GETSHORT(type, ap);
ap += SHORTSIZE + LONGSIZE;
GETSHORT(n, ap);
switch (type)
{
case T_MX:
case T_A:
return 1;
case T_CNAME:
/* value points at name */
if ((ret = dn_expand((u_char *)&answer,
eom, ap, (u_char *)nbuf, sizeof(nbuf))) < 0)
break;
if (strlen(nbuf) < hbsize) {
(void)strcpy(host, nbuf);
}
return 1;
default:
/* not a record of interest */
continue;
}
}
/*
** If this was a T_ANY query, we may have the info but
** need an explicit query. Try T_A, then T_MX.
*/
if (qtype == T_ANY)
qtype = T_A;
else if (qtype == T_A)
qtype = T_MX;
else
return 0;
}
}
struct entry {
struct entry *next;
char *node;
char *domain;
};
struct entry *firstentry;
/*
* Find any remembered information about "node"
*/
char *lookup(node)
char *node;
{
struct entry *p;
for (p = firstentry; p; p = p->next) {
if (!strcmp(node, p->node)) {
return p->domain;
}
}
return 0;
}
/*
* Mark the node "node" as equivalent to "domain". "domain" can either
* be a bitnet node or a domain name--if it is the latter, the mapping
* will be written to stdout.
*/
remember(node, domain)
char *node;
char *domain;
{
struct entry *p;
if (strchr(domain, '.')) {
fprintf(stdout, "%-8s %s\n", node, domain);
}
for (p = firstentry; p; p = p->next) {
if (!strcmp(node, p->node)) {
p->domain = malloc(strlen(domain)+1);
if (!p->domain) {
goto outofmemory;
}
strcpy(p->domain, domain);
return;
}
}
p = (struct entry *)malloc(sizeof(struct entry));
if (!p) goto outofmemory;
p->next = firstentry;
firstentry = p;
p->node = malloc(strlen(node)+1);
p->domain = malloc(strlen(domain)+1);
if (!p->node || !p->domain) goto outofmemory;
strcpy(p->node, node);
strcpy(p->domain, domain);
return;
outofmemory:
fprintf(stderr, "Out of memory\n");
exit(1);
}
/*
* Walk through the database, looking for any cases where we know
* node FOO is equivalent to node BAR and node BAR has a domain name.
* For those cases, give FOO the same domain name as BAR.
*/
finish()
{
struct entry *p;
char *domain;
for (p = firstentry; p; p = p->next) {
if (!strchr(p->domain, '.') && (domain = lookup(p->domain))) {
remember(p->node, domain);
}
}
}