/* * mx_client_verify() -- subroutine for ZMailer smtpserver * * By Matti Aarnio 1997-1999,2002-2003 */ #include "smtpserver.h" #include "zresolv.h" extern int use_ipv6; static int dnsmxlookup __((const char*, int, int, int)); extern int debug; static char * txt_buf = NULL; static int dnsmxlookup(host, depth, mxmode, qtype) const char *host; int depth; int mxmode; int qtype; { HEADER *hp; msgdata *eom, *cp; int qlen, n, i, qdcount, ancount, nscount, arcount, maxpref, class; u_short type; int saw_cname = 0, had_mx_record = 0; int ttl; struct addrinfo req, *ai; #define MAXMX 128 char *mx[MAXMX]; int mxtype[MAXMX]; int mxcount; querybuf qbuf, answer; msgdata buf[8192], realname[8192]; if (depth == 0) h_errno = 0; if (depth > 3) return -EX_NOHOST; if (debug) { printf("000- dnsmxlookup('%s', depth=%d mxmode=%d qtype=%s)\n", host, depth, mxmode, ((qtype == T_TXT) ? "T_TXT" : ((qtype == T_MX) ? "T_MX" : "other"))); } qlen = res_mkquery(QUERY, host, C_IN, qtype, NULL, 0, NULL, (void*)&qbuf, sizeof qbuf); if (qlen < 0) { if (debug) printf("000- res_mkquery failed\n"); return -EX_SOFTWARE; } n = res_send((void*)&qbuf, qlen, (void*)&answer, sizeof answer); if (n < 0) { return -EX_TEMPFAIL; } eom = (msgdata *)&answer + n; /* * find first satisfactory answer */ hp = (HEADER *) &answer; qdcount = ntohs(hp->qdcount); ancount = ntohs(hp->ancount); nscount = ntohs(hp->nscount); arcount = ntohs(hp->arcount); if (debug) printf("000- len=%d rcode=%d qdcount=%d ancount=%d nscount=%d arcount=%d TC=%d\n", n, hp->rcode, qdcount, ancount, nscount, arcount, hp->tc); if (hp->rcode != NOERROR || ancount == 0) { switch (hp->rcode) { case NXDOMAIN: /* Non-authoritative iff response from cache. * Old BINDs used to return non-auth NXDOMAINs * due to a bug; if that is the case by you, * change to return EX_TEMPFAIL iff hp->aa == 0. */ return -EX_NOHOST; case SERVFAIL: return -EX_TEMPFAIL; case NOERROR: goto perhaps_address_record; case FORMERR: case NOTIMP: case REFUSED: return -EX_NOPERM; } return -EX_UNAVAILABLE; } cp = (msgdata *)&answer + sizeof(HEADER); for (; qdcount > 0; --qdcount) { #if defined(BIND_VER) && (BIND_VER >= 473) cp += dn_skipname(cp, eom) + QFIXEDSZ; #else /* !defined(BIND_VER) || (BIND_VER < 473) */ cp += dn_skip(cp) + QFIXEDSZ; #endif /* defined(BIND_VER) && (BIND_VER >= 473) */ } realname[0] = '\0'; maxpref = 70000; mxcount = 0; while (--ancount >= 0 && cp < eom) { n = dn_expand((msgdata *)&answer, eom, cp, (void*)buf, sizeof buf); if (n < 0) break; cp += n; NS_GET16(type, cp); /* type */ NS_GET16(class, cp); /* class */ NS_GET32(ttl, cp); /* ttl */ NS_GET16(n, cp); /* dlen */ if (cp + n > eom) { /* BAD data.. */ break; } if (type == T_CNAME) { cp += dn_expand((msgdata *)&answer, eom, cp, (void*)realname, sizeof realname); saw_cname = 1; continue; } if (type != qtype) { /* Not looked for .. */ cp += n; continue; } if (type == T_MX) { cp += 2; /* MX preference value */ n = dn_expand((msgdata *)&answer, eom, cp, (void*)buf, sizeof buf); if (n < 0) break; cp += n; if (debug) printf("000 MX[%d] = '%s'\n", mxcount, buf); if (mxcount < MAXMX) { mx[mxcount] = strdup((const char *)buf); if (!mx[mxcount]) break; /* Out of memory ?? */ mxtype[mxcount] = 0; ++mxcount; } /* If too many MXes, just skip the rest.. */ had_mx_record = 1; continue; } /* ===== END OF MX DATA PROCESING ========= */ if (type == T_TXT) { int len = (*cp) & 0xFF; /* 0..255 chars */ /* Mal-formed inputs are possible overflowing the buffer.. */ if (len > (eom - cp)) len = (eom - cp); if (len > n - 1) len = n - 1; if (txt_buf != NULL) free(txt_buf); txt_buf = emalloc(len+1); ++cp; memcpy(txt_buf, cp, len); txt_buf[len] = '\0'; for (i = 0; i < mxcount; ++i) { if (mx[i]) free(mx[i]); mx[i] = NULL; } return 1; /* OK! */ } /* If reached here, skip the data tail */ /* In theory could be an abort even.. */ cp += n; } /* ===== END OF DNS ANSWER PROCESSING ======= */ if (ancount > 0) { /* Sigh, waste of time :-( */ for (i = 0; i < mxcount; ++i) if (mx[i]) free(mx[i]); return -EX_SOFTWARE; } if (qtype == T_MX && !mxmode && had_mx_record) { /* Accept if found ANYTHING! */ if (debug) printf("000- ... accepted!\n"); for (i = 0; i < mxcount; ++i) if (mx[i]) free(mx[i]); return 1; } /* Now skip the AUTHORITY SECTION data */ while (nscount > 0 && cp < eom) { #if defined(BIND_VER) && (BIND_VER >= 473) n = dn_skipname(cp, eom); #else /* !defined(BIND_VER) || (BIND_VER < 473) */ n = dn_skip(cp); #endif /* defined(BIND_VER) && (BIND_VER >= 473) */ if (n < 0) break; cp += n; if (cp+10 > eom) { cp = eom; break; } NS_GET16(type, cp); /* type - short */ NS_GET16(class, cp); /* class - short */ cp += NS_INT32SZ; /* ttl - long */ NS_GET16(n, cp); /* dlen - short */ cp += n; /* We simply skip this data.. */ if (cp <= eom) --nscount; } if (debug) printf("000- nscount=%d (== 0 ?) arcount=%d cpaa && nscount == 0 && arcount > 0 && cp < eom) { n = dn_expand((msgdata *)&answer, eom, cp, (void*)buf, sizeof buf); if (n < 0) { cp = eom; break; } cp += n; if (cp+10 > eom) { cp = eom; break; } NS_GET16(type, cp); /* type - short */ NS_GET16(class, cp); /* class - short */ cp += NS_INT32SZ; /* ttl - long */ NS_GET16(n, cp); /* dlen - short */ if (cp + n > eom) { cp = eom; break; } if (class != C_IN) { cp += n; --nscount; continue; } /* Ok, we have Type IN data in the ADDITIONAL SECTION */ /* A and AAAA are known here! */ if (type == T_A #if defined(AF_INET6) && defined(INET6) || (type == T_AAAA) #endif ) { Usockaddr usa; /* Pick the address data */ for (i = 0; i < mxcount; ++i) { /* Is this known (wanted) name ?? */ if (strcasecmp((const char *)buf, mx[i]) == 0) { /* YES! */ mxtype[i] |= (type == T_A) ? 1 : 2 ; /* bitflag: 1 or 2 */ /* We do have a wanted name! */ /* build addrinfo block, pick addresses */ /* WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! This assumes intimate knowledge of the system implementation of the ``struct addrinfo'' ! */ memset(&usa, 0, sizeof(usa)); switch (type) { #if defined(AF_INET6) && defined(INET6) case T_AAAA: usa.v6.sin6_family = PF_INET6; memcpy(&usa.v6.sin6_addr, cp, 16); break; #endif case T_A: usa.v4.sin_family = PF_INET; memcpy(&usa.v4.sin_addr, cp, 4); break; default: break; } #if 1 if (debug) { if (usa.v4.sin_family == AF_INET) { inet_ntop(AF_INET, (void*) & usa.v4.sin_addr, (char *)buf, sizeof(buf)); printf("000- matching %s AR address IPv4:[%s]\n", mx[i], buf); } #if defined(AF_INET6) && defined(INET6) else if (usa.v6.sin6_family == AF_INET6) { inet_ntop(AF_INET6, (void*) & usa.v6.sin6_addr, buf, sizeof(buf)); printf("000- matching %s AR address IPv6:[%s]\n", mx[i], buf); } #endif else printf("000- matching unknown %s AR address family address; AF=%d\n", mx[i], usa.v4.sin_family); } #endif i = matchmyaddress( &usa ); if (i == 1) { if (debug) printf("000- AR ADDRESS MATCH!\n"); for (i = 0; i < mxcount; ++i) if (mx[i]) free(mx[i]); return 1; /* Found a match! */ } else if (debug) printf("000- AR matchmyaddress() yields: %d\n", i); break; /* Name matched, no need to spin more here.. */ } /* Matched name! */ } /* Name matching loop */ } /* type = T_A or T_AAAA */ cp += n; --arcount; } /* Additional data collected! */ /* Now scan thru all MXes, if there are cases WITHOUT A or AAAA records, look them up here. */ for (n = 0; n < mxcount; ++n) { struct addrinfo *ai2; int k = 0, rc; memset(&req, 0, sizeof(req)); switch(mxtype[n]) { case 0: /* no addresses seen! */ req.ai_family = 0; /* Both OK (IPv4/IPv6) */ /* Definitely ask for it! */ break; case 1: /* T_A only seen */ #if defined(AF_INET6) && defined(INET6) if (use_ipv6) req.ai_family = AF_INET6; else continue; /* AF_INET address already seen, skip it.. */ #else continue; /* Skip it! */ #endif break; case 2: /* T_AAAA only seen */ req.ai_family = AF_INET; break; case 3: /* T_A and T_AAAA seen */ default: /* BUG! */ continue; /* No need for any lookup, if this is not 0..2 */ break; } req.ai_socktype = SOCK_STREAM; req.ai_protocol = IPPROTO_TCP; req.ai_flags = AI_CANONNAME; /* ai_family set above. */ ai = NULL; /* This resolves CNAME, it should not happen in case of MX server, though.. */ #ifdef HAVE__GETADDRINFO_ i = _getaddrinfo_(mx[n], "0", &req, &ai, debug ? stdout : NULL); #else i = getaddrinfo(mx[n], "0", &req, &ai); #endif if (debug) printf("000- getaddrinfo('%s','0') -> r=%d, ai=%p\n",mx[n],i,ai); if (i != 0) continue; /* Well well.. spurious! */ if (!mxmode) /* Accept if found ANYTHING! */ { if (debug) printf("000- ... accepted!\n"); freeaddrinfo(ai); for (i = 0; i < mxcount; ++i) if (mx[i]) free(mx[i]); return 1; } for ( ai2 = ai ; ai2 != NULL; ai2 = ai2->ai_next) { ++k; #if 1 if (debug) { Usockaddr * usa = (Usockaddr *) ai2->ai_addr; char buf[60]; if (usa->v4.sin_family == AF_INET) { inet_ntop(AF_INET, (void*) & usa->v4.sin_addr, buf, sizeof(buf)); printf("000- matching %s address IPv4:[%s]\n", mx[n], buf); } #if defined(AF_INET6) && defined(INET6) else if (usa->v6.sin6_family == AF_INET6) { inet_ntop(AF_INET6, (void*) & usa->v6.sin6_addr, buf, sizeof(buf)); printf("000- matching %s address IPv6:[%s]\n", mx[n], buf); } #endif else printf("000- matching %s unknown address family address; AF=%d\n", mx[n], usa->v4.sin_family); } #endif rc = matchmyaddress((Usockaddr *)ai2->ai_addr); if (rc == 1) { if (debug) printf("000- ADDRESS MATCH!\n"); freeaddrinfo(ai); for (i = 0; i < mxcount; ++i) if (mx[i]) free(mx[i]); return 1; /* Found a match! */ } else if (debug) printf("000- matchmyaddress() yields: %d\n", rc); } if (debug) printf("000- No address match among %d address!\n", k); freeaddrinfo(ai); } for (i = 0; i < mxcount; ++i) if (mx[i]) free(mx[i]); /* Didn't find any, but saw CNAME ? Recurse with the real name */ if (saw_cname) return dnsmxlookup((void *)realname, depth+1, mxmode, qtype); if (had_mx_record && mxmode) return 2; /* We have SOME date, but no match on ourselves! */ perhaps_address_record: if (qtype == T_MX) { /* No MX, perhaps A ? */ memset(&req, 0, sizeof(req)); req.ai_socktype = SOCK_STREAM; req.ai_protocol = IPPROTO_TCP; req.ai_flags = AI_CANONNAME; req.ai_family = PF_INET; ai = NULL; /* This resolves CNAME, it should not happen in case of MX server, though.. */ #ifdef HAVE__GETADDRINFO_ if (debug) printf("000- perhaps A?\n"); i = _getaddrinfo_((const char*)host, "0", &req, &ai, debug ? stdout : NULL); #else i = getaddrinfo((const char*)host, "0", &req, &ai); #endif if (debug) printf("000- getaddrinfo('%s','0') (PF_INET) -> r=%d (%s), ai=%p\n",host,i,gai_strerror(i),ai); #if defined(AF_INET6) && defined(INET6) if (use_ipv6) { /* Want, but not have AAAA, ask for it. */ int n2; struct addrinfo *ai2 = NULL; memset(&req, 0, sizeof(req)); req.ai_socktype = SOCK_STREAM; req.ai_protocol = IPPROTO_TCP; req.ai_flags = AI_CANONNAME; req.ai_family = PF_INET6; /* This resolves CNAME, it should not happen in case of MX server, though.. */ #ifdef HAVE__GETADDRINFO_ n2 = _getaddrinfo_((const char *)host, "0", &req, &ai2, debug ? stdout : NULL); #else n2 = getaddrinfo((const char *)host, "0", &req, &ai2); #endif if (debug) printf("000- getaddrinfo('%s','0') (PF_INET6) -> r=%d (%s), ai=%p\n",host,n2,gai_strerror(n2),ai2); if (i != 0 && n2 == 0) { /* IPv6 address, no IPv4 (or error..) */ i = n2; ai = ai2; ai2 = NULL; } if (ai2 && ai) { /* BOTH ?! Catenate them! */ struct addrinfo **aip; aip = &ai->ai_next; while (*aip) aip = &((*aip)->ai_next); *aip = ai2; } } #endif if (i) return i; i = matchmyaddresses(ai); #if 1 /* With this we can refuse to accept any message with source domain pointing back to loopback ! */ if (i == 2) { /* Loopback ! */ freeaddrinfo(ai); return 0; } #endif if (i == 0 && mxmode) { freeaddrinfo(ai); return 2; /* Didn't find our local address in client-MX-mode */ } freeaddrinfo(ai); return 1; /* Found any address, or in client-MX-mode, a local address! */ } if (mxmode) return 2; /* Not found, had no MX data either */ return 0; /* Not found! */ } /* For SOFT errors, return -102, for hard errors, -2. For 'we are MX', return 0. For (retmode == '+'), and without MX, return 1. */ int mx_client_verify(retmode, domain, alen) int retmode, alen; const char *domain; { char hbuf[2000]; int rc; if (alen >= sizeof(hbuf)-2) alen = sizeof(hbuf)-2; strncpy(hbuf, domain, alen); hbuf[alen] = 0; /* Chop off the trailers from the name */ rc = dnsmxlookup(hbuf, 0, 1, T_MX); if (rc == 1) return 0; /* Found! */ if (rc == -EX_TEMPFAIL) { return -104; } if (retmode == '+') { if (rc == -EX_NOHOST || rc == -EX_UNAVAILABLE) return -2; /* Definitely hard errors */ if (rc == 2) return -103; return -102; /* Soft error */ } if (rc == 2) return -3; return -2; /* Reject */ } int sender_dns_verify(retmode, domain, alen) int retmode, alen; const char *domain; { char hbuf[500]; int rc; if (alen >= sizeof(hbuf)-2) alen = sizeof(hbuf)-2; strncpy(hbuf, domain, alen); hbuf[alen] = 0; /* Chop off the trailers from the name */ rc = dnsmxlookup(hbuf, 0, 0, T_MX); if (debug) printf("000- dnsmxlookup() did yield: %d, retmode='%c'\n", rc,retmode); if (rc == 1) return 0; /* Found! */ if (rc == -EX_TEMPFAIL) { return -104; } if (retmode == '+') { if (rc == -EX_NOHOST || rc == -EX_UNAVAILABLE || rc == EAI_NONAME) return -2; /* Definitely hard errors */ if (rc == 2) return -103; return -102; /* Soft error */ } if (rc == 2) return -3; return -2; /* Reject */ } int client_dns_verify(retmode, domain, alen) int retmode, alen; const char *domain; { return sender_dns_verify(retmode, domain, alen); } int rbl_dns_test(ipaf, ipaddr, rbldomain, msgp) const int ipaf; const u_char *ipaddr; char *rbldomain; char **msgp; { char hbuf[2000], *s, *suf; int hspc; struct hostent *hp; int has_ok = 0; if (ipaf == P_K_IPv4) { sprintf(hbuf, "%d.%d.%d.%d.", ipaddr[3], ipaddr[2], ipaddr[1], ipaddr[0]); } else { /* Ok, the other variant is IPv6 ... */ int i; for (i = 15; i >= 0; --i) { sprintf(hbuf + ((15-i) << 2), "%x.%x.", ipaddr[i] & 0x0F, (ipaddr[i] >> 4) & 0x0F); } strcpy(hbuf+64,"ip6."); /* Fixed length of hex nybbles */ } suf = hbuf + strlen(hbuf); hspc = sizeof(hbuf) - strlen(hbuf) - 2; while (*rbldomain) { /* "rbldomain" is possibly a COLON-demarked set of domain names: rbl.maps.vix.com:dul.maps.vix.com which isn't so easy to read, but ... */ /* The 2000 char buffer should be way oversized for this routine's needs.. And it is managerial input at the policy database, which has the "unpredictable" size... */ s = strchr(rbldomain, ':'); if (s) *s = 0; if (strcmp(rbldomain,"+") == 0) strncpy (suf, "rbl.maps.vix.com", hspc); else strncpy (suf, rbldomain, hspc); suf[hspc] = '\0'; if (s) { *s = ':'; rbldomain = s+1; } else { rbldomain += strlen(rbldomain); } /* Add explicite DOT into the tail of the lookup object. That way the lookup should never use resolver's SEARCH suffix set. */ s = suf + strlen(suf) - 1; if (*s != '.') { *(++s) = '.'; *(++s) = 0; } if (debug) printf("000- looking up DNS A object: %s\n", hbuf); hp = gethostbyname(hbuf); if (hp != NULL) { /* XX: Should verify that the named object has A record: 127.0.0.2 D'uh.. alternate dataset has A record: 127.0.0.3 */ char abuf[30]; inet_ntop(AF_INET, (void*) hp->h_addr, abuf, sizeof(abuf)); type(NULL,0,NULL, "Looked up DNS A object: %s -> %s", hbuf, abuf); if (strncmp("127.0.0.",abuf,8) != 0) { has_ok = 1; continue; /* Isn't 127.0.0.* */ } #if 0 if (strcmp("127.0.0.4",abuf) == 0) { /* ORBS NETBLOCK */ if (has_ok) continue; } #endif /* Ok, then lookup for the TXT entry too! */ if (debug) printf("000- looking up DNS TXT object: %s\n", hbuf); if (dnsmxlookup(hbuf, 0, 0, T_TXT) == 1) { if (*msgp != NULL) free(*msgp); *msgp = strdup(txt_buf); s = *msgp; if (s) { for ( ;*s; ++s) { int c = ((*s) & 0xFF); /* Characters not printable in ISO-8859-* are masked with space. */ if (c < ' ' || (c >= 127 && c < 128+32) || c == 255) *s = ' '; } } type(NULL,0,NULL,"Found DNS TXT object: %s\n", *msgp ? *msgp : ""); } return -1; } /* Didn't find A record */ type(NULL,0,NULL, "Didn't find DNS A object: %s", hbuf); } return 0; }