/* Copyright (c) 2003-2006 by Juliusz Chroboczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "polipo.h" #ifndef NO_STANDARD_RESOLVER #ifndef NO_FANCY_RESOLVER int dnsUseGethostbyname = 1; #else const int dnsUseGethostbyname = 3; #endif #else #ifndef NO_FANCY_RESOLVER const int dnsUseGethostbyname = 0; #else #error use no resolver at all? #endif #endif #ifndef NO_FANCY_RESOLVER AtomPtr dnsNameServer = NULL; int dnsMaxTimeout = 60; int dnsNameServerPort = 53; #endif #ifndef NO_STANDARD_RESOLVER int dnsGethostbynameTtl = 1200; #endif int dnsNegativeTtl = 120; #ifdef HAVE_IPv6 int dnsQueryIPv6 = 2; #else const int dnsQueryIPv6 = 0; #endif typedef struct _DnsQuery { unsigned id; AtomPtr name; ObjectPtr object; AtomPtr inet4, inet6; time_t ttl4, ttl6; time_t time; int timeout; TimeEventHandlerPtr timeout_handler; struct _DnsQuery *next; } DnsQueryRec, *DnsQueryPtr; union { struct sockaddr sa; struct sockaddr_in sin; #ifdef HAVE_IPv6 struct sockaddr_in6 sin6; #endif } nameserverAddress_storage; #ifndef NO_FANCY_RESOLVER static AtomPtr atomLocalhost, atomLocalhostDot; #define nameserverAddress nameserverAddress_storage.sa static DnsQueryPtr inFlightDnsQueries; static DnsQueryPtr inFlightDnsQueriesLast; #endif static int really_do_gethostbyname(AtomPtr name, ObjectPtr object); static int really_do_dns(AtomPtr name, ObjectPtr object); #ifndef NO_FANCY_RESOLVER static int stringToLabels(char *buf, int offset, int n, char *string); static int labelsToString(char *buf, int offset, int n, char *d, int m, int *j_return); static int dnsBuildQuery(int id, char *buf, int offset, int n, AtomPtr name, int af); static int dnsReplyHandler(int abort, FdEventHandlerPtr event); static int dnsReplyId(char *buf, int offset, int n, int *id_return); static int dnsDecodeReply(char *buf, int offset, int n, int *id_return, AtomPtr *name_return, AtomPtr *value_return, int *af_return, unsigned *ttl_return); static int dnsHandler(int status, ConditionHandlerPtr chandler); static int dnsGethostbynameFallback(int id, AtomPtr message); static int sendQuery(DnsQueryPtr query); static int idSeed; #endif #if !defined(NO_FANCY_RESOLVER) && !defined(WIN32) static int parseResolvConf(char *filename) { FILE *f; char buf[512]; char *p, *q; int n; AtomPtr nameserver = NULL; f = fopen(filename, "r"); if(f == NULL) { do_log_error(L_ERROR, errno, "DNS: couldn't open %s", filename); return 0; } while(1) { p = fgets(buf, 512, f); if(p == NULL) break; n = strlen(buf); if(buf[n - 1] != '\n') { int c; do_log(L_WARN, "DNS: overly long line in %s -- skipping.\n", filename); do { c = fgetc(f); if(c == EOF) break; } while(c != '\n'); if(c == EOF) break; } while(*p == ' ' || *p == '\t') p++; if(strcasecmp_n("nameserver", p, 10) != 0) continue; p += 10; while(*p == ' ' || *p == '\t') p++; q = p; while(*q == '.' || *q == ':' || digit(*q) || letter(*q)) q++; if(*q != ' ' && *q != '\t' && *q != '\r' && *q != '\n') { do_log(L_WARN, "DNS: couldn't parse line in %s -- skipping.\n", filename); continue; } nameserver = internAtomLowerN(p, q - p); break; } fclose(f); if(nameserver) { dnsNameServer = nameserver; return 1; } else { return 0; } } #endif void preinitDns() { #ifdef HAVE_IPv6 int fd; #endif assert(sizeof(struct in_addr) == 4); #ifdef HAVE_IPv6 assert(sizeof(struct in6_addr) == 16); #endif #ifndef NO_STANDARD_RESOLVER CONFIG_VARIABLE(dnsGethostbynameTtl, CONFIG_TIME, "TTL for gethostbyname addresses."); #endif #ifdef HAVE_IPv6 fd = socket(PF_INET6, SOCK_STREAM, 0); if(fd < 0) { if(errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT) { dnsQueryIPv6 = 0; } else { do_log_error(L_WARN, errno, "DNS: couldn't create socket"); } } else { close(fd); } #endif #ifndef NO_FANCY_RESOLVER #ifndef WIN32 parseResolvConf("/etc/resolv.conf"); #endif if(dnsNameServer == NULL || dnsNameServer->string[0] == '\0') dnsNameServer = internAtom("127.0.0.1"); CONFIG_VARIABLE(dnsMaxTimeout, CONFIG_TIME, "Max timeout for DNS queries."); CONFIG_VARIABLE(dnsNegativeTtl, CONFIG_TIME, "TTL for negative DNS replies with no TTL."); CONFIG_VARIABLE(dnsNameServer, CONFIG_ATOM_LOWER, "The name server to use."); CONFIG_VARIABLE(dnsNameServerPort, CONFIG_INT, "The name server port to use."); #ifndef NO_STANDARD_RESOLVER CONFIG_VARIABLE(dnsUseGethostbyname, CONFIG_TETRASTATE, "Use the system resolver."); #endif #endif #ifdef HAVE_IPv6 CONFIG_VARIABLE(dnsQueryIPv6, CONFIG_TETRASTATE, "Query for IPv6 addresses."); #endif } void initDns() { #ifndef NO_FANCY_RESOLVER int rc; struct timeval t; struct sockaddr_in *sin = (struct sockaddr_in*)&nameserverAddress; #ifdef HAVE_IPv6 struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&nameserverAddress; #endif atomLocalhost = internAtom("localhost"); atomLocalhostDot = internAtom("localhost."); inFlightDnsQueries = NULL; inFlightDnsQueriesLast = NULL; gettimeofday(&t, NULL); idSeed = t.tv_usec & 0xFFFF; sin->sin_family = AF_INET; sin->sin_port = htons(dnsNameServerPort); rc = inet_aton(dnsNameServer->string, &sin->sin_addr); #ifdef HAVE_IPv6 if(rc != 1) { sin6->sin6_family = AF_INET6; sin6->sin6_port = htons(dnsNameServerPort); rc = inet_pton(AF_INET6, dnsNameServer->string, &sin6->sin6_addr); } #endif if(rc != 1) { do_log(L_ERROR, "DNS: couldn't parse name server %s.\n", dnsNameServer->string); exit(1); } #endif } int do_gethostbyname(char *origname, int count, int (*handler)(int, GethostbynameRequestPtr), void *data) { ObjectPtr object; int n = strlen(origname); AtomPtr name; GethostbynameRequestRec request; int done, rc; memset(&request, 0, sizeof(request)); request.name = NULL; request.addr = NULL; request.error_message = NULL; request.count = count; request.handler = handler; request.data = data; if(n <= 0 || n > 131) { if(n <= 0) { request.error_message = internAtom("empty name"); do_log(L_ERROR, "Empty DNS name.\n"); done = handler(-EINVAL, &request); } else { request.error_message = internAtom("name too long"); do_log(L_ERROR, "DNS name too long.\n"); done = handler(-ENAMETOOLONG, &request); } assert(done); releaseAtom(request.error_message); return 1; } if(origname[n - 1] == '.') n--; name = internAtomLowerN(origname, n); if(name == NULL) { request.error_message = internAtom("couldn't allocate name"); do_log(L_ERROR, "Couldn't allocate DNS name.\n"); done = handler(-ENOMEM, &request); assert(done); releaseAtom(request.error_message); return 1; } request.name = name; request.addr = NULL; request.error_message = NULL; request.count = count; request.object = NULL; request.handler = handler; request.data = data; object = findObject(OBJECT_DNS, name->string, name->length); if(object == NULL || objectMustRevalidate(object, NULL)) { if(object) { privatiseObject(object, 0); releaseObject(object); } object = makeObject(OBJECT_DNS, name->string, name->length, 1, 0, NULL, NULL); if(object == NULL) { request.error_message = internAtom("Couldn't allocate object"); do_log(L_ERROR, "Couldn't allocate DNS object.\n"); done = handler(-ENOMEM, &request); assert(done); releaseAtom(name); releaseAtom(request.error_message); return 1; } } if((object->flags & (OBJECT_INITIAL | OBJECT_INPROGRESS)) == OBJECT_INITIAL) { if(dnsUseGethostbyname >= 3) rc = really_do_gethostbyname(name, object); else rc = really_do_dns(name, object); if(rc < 0) { assert(!(object->flags & (OBJECT_INITIAL | OBJECT_INPROGRESS))); goto fail; } } if(dnsUseGethostbyname >= 3) assert(!(object->flags & OBJECT_INITIAL)); #ifndef NO_FANCY_RESOLVER if(object->flags & OBJECT_INITIAL) { ConditionHandlerPtr chandler; assert(object->flags & OBJECT_INPROGRESS); request.object = object; chandler = conditionWait(&object->condition, dnsHandler, sizeof(request), &request); if(chandler == NULL) goto fail; return 1; } #endif if(object->headers && object->headers->length > 0) { if(object->headers->string[0] == DNS_A) assert(((object->headers->length - 1) % sizeof(HostAddressRec)) == 0); else assert(object->headers->string[0] == DNS_CNAME); request.addr = retainAtom(object->headers); } else if(object->message) { request.error_message = retainAtom(object->message); } releaseObject(object); if(request.addr && request.addr->length > 0) done = handler(1, &request); else done = handler(-EDNS_HOST_NOT_FOUND, &request); assert(done); releaseAtom(request.addr); request.addr = NULL; releaseAtom(request.name); request.name = NULL; releaseAtom(request.error_message); request.error_message = NULL; return 1; fail: releaseNotifyObject(object); done = handler(-errno, &request); assert(done); releaseAtom(name); return 1; } static int dnsDelayedErrorNotifyHandler(TimeEventHandlerPtr event) { int done; GethostbynameRequestRec request = *(GethostbynameRequestPtr)event->data; done = request.handler(-EDNS_HOST_NOT_FOUND, &request); assert(done); releaseAtom(request.name); request.name = NULL; releaseAtom(request.addr); request.addr = NULL; releaseAtom(request.error_message); request.error_message = NULL; return 1; } static int dnsDelayedDoneNotifyHandler(TimeEventHandlerPtr event) { int done; GethostbynameRequestRec request = *(GethostbynameRequestPtr)event->data; done = request.handler(1, &request); assert(done); releaseAtom(request.name); request.name = NULL; releaseAtom(request.addr); request.addr = NULL; releaseAtom(request.error_message); request.error_message = NULL; return 1; } static int dnsDelayedNotify(int error, GethostbynameRequestPtr request) { TimeEventHandlerPtr handler; if(error) handler = scheduleTimeEvent(0, dnsDelayedErrorNotifyHandler, sizeof(*request), request); else handler = scheduleTimeEvent(0, dnsDelayedDoneNotifyHandler, sizeof(*request), request); if(handler == NULL) { do_log(L_ERROR, "Couldn't schedule DNS notification.\n"); return -1; } return 1; } #ifdef HAVE_IPv6 AtomPtr rfc2732(AtomPtr name) { char buf[40]; /* 8*4 (hexdigits) + 7 (colons) + 1 ('\0') */ int rc; AtomPtr a = NULL; if(name->length < 40+2 && name->string[0] == '[' && name->string[name->length - 1] == ']') { struct in6_addr in6a; memcpy(buf, name->string + 1, name->length - 2); buf[name->length - 2] = '\0'; rc = inet_pton(AF_INET6, buf, &in6a); if(rc == 1) { char s[1 + sizeof(HostAddressRec)]; memset(s, 0, sizeof(s)); s[0] = DNS_A; s[1] = 6; memcpy(s + 2, &in6a, 16); a = internAtomN(s, 1 + sizeof(HostAddressRec)); if(a == NULL) return NULL; } } return a; } /* Used for sorting host addresses depending on the value of dnsQueryIPv6 */ int compare_hostaddr(const void *av, const void *bv) { const HostAddressRec *a = av, *b = bv; int r; if(a->af == 4) { if(b->af == 4) r = 0; else r = -1; } else { if(b->af == 6) r = 0; else r = 1; } if(dnsQueryIPv6 >= 2) return -r; else return r; } #ifndef NO_STANDARD_RESOLVER static int really_do_gethostbyname(AtomPtr name, ObjectPtr object) { struct addrinfo *ai, *entry, hints; int rc; int error, i; char buf[1024]; AtomPtr a; a = rfc2732(name); if(a) { object->headers = a; object->age = current_time.tv_sec; object->expires = current_time.tv_sec + 240; object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS); notifyObject(object); return 0; } memset(&hints, 0, sizeof(hints)); hints.ai_protocol = IPPROTO_TCP; if(dnsQueryIPv6 <= 0) hints.ai_family = AF_INET; else if(dnsQueryIPv6 >= 3) hints.ai_family = AF_INET6; rc = getaddrinfo(name->string, NULL, &hints, &ai); switch(rc) { case 0: error = 0; break; case EAI_FAMILY: #ifdef EAI_ADDRFAMILY case EAI_ADDRFAMILY: #endif case EAI_SOCKTYPE: error = EAFNOSUPPORT; break; case EAI_BADFLAGS: error = EINVAL; break; case EAI_SERVICE: error = EDNS_NO_RECOVERY; break; #ifdef EAI_NONAME case EAI_NONAME: #endif #ifdef EAI_NODATA case EAI_NODATA: #endif error = EDNS_NO_ADDRESS; break; case EAI_FAIL: error = EDNS_NO_RECOVERY; break; case EAI_AGAIN: error = EDNS_TRY_AGAIN; break; #ifdef EAI_MEMORY case EAI_MEMORY: error = ENOMEM; break; #endif case EAI_SYSTEM: error = errno; break; default: error = EUNKNOWN; } if(error == EDNS_NO_ADDRESS) { object->headers = NULL; object->age = current_time.tv_sec; object->expires = current_time.tv_sec + dnsNegativeTtl; object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS); notifyObject(object); return 0; } else if(error) { do_log_error(L_ERROR, error, "Getaddrinfo failed"); object->flags &= ~OBJECT_INPROGRESS; abortObject(object, 404, internAtomError(error, "Getaddrinfo failed")); notifyObject(object); return 0; } entry = ai; buf[0] = DNS_A; i = 0; while(entry) { HostAddressRec host; int host_valid = 0; if(entry->ai_family == AF_INET && entry->ai_protocol == IPPROTO_TCP) { if(dnsQueryIPv6 < 3) { host.af = 4; memset(host.data, 0, sizeof(host.data)); memcpy(&host.data, &((struct sockaddr_in*)entry->ai_addr)->sin_addr, 4); host_valid = 1; } } else if(entry->ai_family == AF_INET6 && entry->ai_protocol == IPPROTO_TCP) { if(dnsQueryIPv6 > 0) { host.af = 6; memset(&host.data, 0, sizeof(host.data)); memcpy(&host.data, &((struct sockaddr_in6*)entry->ai_addr)->sin6_addr, 16); host_valid = 1; } } if(host_valid) { if(i >= 1024 / sizeof(HostAddressRec) - 2) { do_log(L_ERROR, "Too many addresses for host %s\n", name->string); break; } memcpy(buf + 1 + i * sizeof(HostAddressRec), &host, sizeof(HostAddressRec)); i++; } entry = entry->ai_next; } freeaddrinfo(ai); if(i == 0) { do_log(L_ERROR, "Getaddrinfo returned no useful addresses\n"); object->flags &= ~OBJECT_INPROGRESS; abortObject(object, 404, internAtom("Getaddrinfo returned no useful addresses")); notifyObject(object); return 0; } if(1 <= dnsQueryIPv6 && dnsQueryIPv6 <= 2) qsort(buf + 1, i, sizeof(HostAddressRec), compare_hostaddr); a = internAtomN(buf, 1 + i * sizeof(HostAddressRec)); if(a == NULL) { object->flags &= ~OBJECT_INPROGRESS; abortObject(object, 501, internAtom("Couldn't allocate address")); notifyObject(object); return 0; } object->headers = a; object->age = current_time.tv_sec; object->expires = current_time.tv_sec + dnsGethostbynameTtl; object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS); notifyObject(object); return 0; } #endif #else #ifndef NO_STANDARD_RESOLVER static int really_do_gethostbyname(AtomPtr name, ObjectPtr object) { struct hostent *host; char *s; AtomPtr a; int i, j; int error; host = gethostbyname(name->string); if(host == NULL) { switch(h_errno) { case HOST_NOT_FOUND: error = EDNS_HOST_NOT_FOUND; break; #ifdef NO_ADDRESS case NO_ADDRESS: error = EDNS_NO_ADDRESS; break; #endif case NO_RECOVERY: error = EDNS_NO_RECOVERY; break; case TRY_AGAIN: error = EDNS_TRY_AGAIN; break; default: error = EUNKNOWN; break; } if(error == EDNS_HOST_NOT_FOUND) { object->headers = NULL; object->age = current_time.tv_sec; object->expires = current_time.tv_sec + dnsNegativeTtl; object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS); object->flags &= ~OBJECT_INPROGRESS; notifyObject(object); return 0; } else { do_log_error(L_ERROR, error, "Gethostbyname failed"); abortObject(object, 404, internAtomError(error, "Gethostbyname failed")); object->flags &= ~OBJECT_INPROGRESS; notifyObject(object); return 0; } } if(host->h_addrtype != AF_INET) { do_log(L_ERROR, "Address is not AF_INET.\n"); object->flags &= ~OBJECT_INPROGRESS; abortObject(object, 404, internAtom("Address is not AF_INET")); notifyObject(object); return -1; } if(host->h_length != sizeof(struct in_addr)) { do_log(L_ERROR, "Address size inconsistent.\n"); object->flags &= ~OBJECT_INPROGRESS; abortObject(object, 404, internAtom("Address size inconsistent")); notifyObject(object); return 0; } i = 0; while(host->h_addr_list[i] != NULL) i++; s = malloc(1 + i * sizeof(HostAddressRec)); if(s == NULL) { a = NULL; } else { memset(s, 0, 1 + i * sizeof(HostAddressRec)); s[0] = DNS_A; for(j = 0; j < i; j++) { s[j * sizeof(HostAddressRec) + 1] = 4; memcpy(&s[j * sizeof(HostAddressRec) + 2], host->h_addr_list[j], sizeof(struct in_addr)); } a = internAtomN(s, i * sizeof(HostAddressRec) + 1); free(s); } if(!a) { object->flags &= ~OBJECT_INPROGRESS; abortObject(object, 501, internAtom("Couldn't allocate address")); notifyObject(object); return 0; } object->headers = a; object->age = current_time.tv_sec; object->expires = current_time.tv_sec + dnsGethostbynameTtl; object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS); notifyObject(object); return 0; } #endif #endif #ifdef NO_STANDARD_RESOLVER static int really_do_gethostbyname(AtomPtr name, ObjectPtr object) { abort(); } #endif #ifndef NO_FANCY_RESOLVER static int dnsSocket = -1; static FdEventHandlerPtr dnsSocketHandler = NULL; static int dnsHandler(int status, ConditionHandlerPtr chandler) { GethostbynameRequestRec request = *(GethostbynameRequestPtr)chandler->data; ObjectPtr object = request.object; assert(!(object->flags & OBJECT_INPROGRESS)); if(object->headers) { request.addr = retainAtom(object->headers); dnsDelayedNotify(0, &request); } else { if(object->message) request.error_message = retainAtom(object->message); dnsDelayedNotify(1, &request); } releaseObject(object); return 1; } static int queryInFlight(DnsQueryPtr query) { DnsQueryPtr other; other = inFlightDnsQueries; while(other) { if(other == query) return 1; other = other->next; } return 0; } static void removeQuery(DnsQueryPtr query) { DnsQueryPtr previous; if(query == inFlightDnsQueries) { inFlightDnsQueries = query->next; if(inFlightDnsQueries == NULL) inFlightDnsQueriesLast = NULL; } else { previous = inFlightDnsQueries; while(previous->next) { if(previous->next == query) break; previous = previous->next; } assert(previous->next != NULL); previous->next = query->next; if(previous->next == NULL) inFlightDnsQueriesLast = previous; } } static void insertQuery(DnsQueryPtr query) { if(inFlightDnsQueriesLast) inFlightDnsQueriesLast->next = query; else inFlightDnsQueries = query; inFlightDnsQueriesLast = query; } static DnsQueryPtr findQuery(int id, AtomPtr name) { DnsQueryPtr query; query = inFlightDnsQueries; while(query) { if(query->id == id && (name == NULL || query->name == name)) return query; query = query->next; } return NULL; } static int dnsTimeoutHandler(TimeEventHandlerPtr event) { DnsQueryPtr query = *(DnsQueryPtr*)event->data; ObjectPtr object = query->object; int rc; /* People are reporting that this does happen. And I have no idea why. */ if(!queryInFlight(query)) { do_log(L_ERROR, "BUG: timing out martian query (%s, flags: 0x%x).\n", scrub(query->name->string), (unsigned)object->flags); return 1; } query->timeout = MAX(10, query->timeout * 2); if(query->timeout > dnsMaxTimeout) { abortObject(object, 501, internAtom("Timeout")); goto fail; } else { rc = sendQuery(query); if(rc < 0) { if(rc != -EWOULDBLOCK && rc != -EAGAIN && rc != -ENOBUFS) { abortObject(object, 501, internAtomError(-rc, "Couldn't send DNS query")); goto fail; } /* else let it timeout */ } query->timeout_handler = scheduleTimeEvent(query->timeout, dnsTimeoutHandler, sizeof(query), &query); if(query->timeout_handler == NULL) { do_log(L_ERROR, "Couldn't schedule DNS timeout handler.\n"); abortObject(object, 501, internAtom("Couldn't schedule DNS timeout handler")); goto fail; } return 1; } fail: removeQuery(query); object->flags &= ~OBJECT_INPROGRESS; if(query->inet4) releaseAtom(query->inet4); if(query->inet6) releaseAtom(query->inet6); free(query); releaseNotifyObject(object); return 1; } static int establishDnsSocket() { int rc; #ifdef HAVE_IPv6 int inet6 = (nameserverAddress.sa_family == AF_INET6); int pf = inet6 ? PF_INET6 : PF_INET; int sa_size = inet6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); #else int pf = PF_INET; int sa_size = sizeof(struct sockaddr_in); #endif if(dnsSocket < 0) { assert(!dnsSocketHandler); dnsSocket = socket(pf, SOCK_DGRAM, 0); if(dnsSocket < 0) { do_log_error(L_ERROR, errno, "Couldn't create DNS socket"); return -errno; } rc = connect(dnsSocket, &nameserverAddress, sa_size); if(rc < 0) { CLOSE(dnsSocket); dnsSocket = -1; do_log_error(L_ERROR, errno, "Couldn't create DNS \"connection\""); return -errno; } } if(!dnsSocketHandler) { dnsSocketHandler = registerFdEvent(dnsSocket, POLLIN, dnsReplyHandler, 0, NULL); if(dnsSocketHandler == NULL) { do_log(L_ERROR, "Couldn't register DNS socket handler.\n"); CLOSE(dnsSocket); dnsSocket = -1; return -ENOMEM; } } return 1; } static int sendQuery(DnsQueryPtr query) { char buf[512]; int buflen; int rc; int af[2]; int i; if(dnsSocket < 0) return -1; if(dnsQueryIPv6 <= 0) { af[0] = 4; af[1] = 0; } else if(dnsQueryIPv6 <= 2) { af[0] = 4; af[1] = 6; } else { af[0] = 6; af[1] = 0; } for(i = 0; i < 2; i++) { if(af[i] == 0) continue; if(af[i] == 4 && query->inet4) continue; else if(af[i] == 6 && query->inet6) continue; buflen = dnsBuildQuery(query->id, buf, 0, 512, query->name, af[i]); if(buflen <= 0) { do_log(L_ERROR, "Couldn't build DNS query.\n"); return buflen; } rc = send(dnsSocket, buf, buflen, 0); if(rc < buflen) { if(rc >= 0) { do_log(L_ERROR, "Couldn't send DNS query: partial send.\n"); return -EAGAIN; } else { do_log_error(L_ERROR, errno, "Couldn't send DNS query"); return -errno; } } } return 1; } static int really_do_dns(AtomPtr name, ObjectPtr object) { int rc; DnsQueryPtr query; AtomPtr message = NULL; int id; AtomPtr a = NULL; if(a == NULL) { if(name == atomLocalhost || name == atomLocalhostDot) { char s[1 + sizeof(HostAddressRec)]; memset(s, 0, sizeof(s)); s[0] = DNS_A; s[1] = 4; s[2] = 127; s[3] = 0; s[4] = 0; s[5] = 1; a = internAtomN(s, 1 + sizeof(HostAddressRec)); if(a == NULL) { abortObject(object, 501, internAtom("Couldn't allocate address")); notifyObject(object); errno = ENOMEM; return -1; } } } if(a == NULL) { struct in_addr ina; rc = inet_aton(name->string, &ina); if(rc == 1) { char s[1 + sizeof(HostAddressRec)]; memset(s, 0, sizeof(s)); s[0] = DNS_A; s[1] = 4; memcpy(s + 2, &ina, 4); a = internAtomN(s, 1 + sizeof(HostAddressRec)); if(a == NULL) { abortObject(object, 501, internAtom("Couldn't allocate address")); notifyObject(object); errno = ENOMEM; return -1; } } } #ifdef HAVE_IPv6 if(a == NULL) a = rfc2732(name); #endif if(a) { object->headers = a; object->age = current_time.tv_sec; object->expires = current_time.tv_sec + 240; object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS); notifyObject(object); return 0; } rc = establishDnsSocket(); if(rc < 0) { do_log_error(L_ERROR, -rc, "Couldn't establish DNS socket.\n"); message = internAtomError(-rc, "Couldn't establish DNS socket"); goto fallback; } /* The id is used to speed up detecting replies to queries that are no longer current -- see dnsReplyHandler. */ id = (idSeed++) & 0xFFFF; query = malloc(sizeof(DnsQueryRec)); if(query == NULL) { do_log(L_ERROR, "Couldn't allocate DNS query.\n"); message = internAtom("Couldn't allocate DNS query"); goto fallback; } query->id = id; query->inet4 = NULL; query->inet6 = NULL; query->name = name; query->time = current_time.tv_sec; query->object = retainObject(object); query->timeout = 4; query->timeout_handler = NULL; query->next = NULL; query->timeout_handler = scheduleTimeEvent(query->timeout, dnsTimeoutHandler, sizeof(query), &query); if(query->timeout_handler == NULL) { do_log(L_ERROR, "Couldn't schedule DNS timeout handler.\n"); message = internAtom("Couldn't schedule DNS timeout handler"); goto free_fallback; } insertQuery(query); object->flags |= OBJECT_INPROGRESS; rc = sendQuery(query); if(rc < 0) { if(rc != -EWOULDBLOCK && rc != -EAGAIN && rc != -ENOBUFS) { object->flags &= ~OBJECT_INPROGRESS; message = internAtomError(-rc, "Couldn't send DNS query"); goto remove_fallback; } /* else let it timeout */ } releaseAtom(message); return 1; remove_fallback: removeQuery(query); free_fallback: releaseObject(query->object); cancelTimeEvent(query->timeout_handler); free(query); fallback: if(dnsUseGethostbyname >= 1) { releaseAtom(message); do_log(L_WARN, "Falling back on gethostbyname.\n"); return really_do_gethostbyname(name, object); } else { abortObject(object, 501, message); notifyObject(object); return 1; } } static int dnsReplyHandler(int abort, FdEventHandlerPtr event) { int fd = event->fd; char buf[2048]; int len, rc; ObjectPtr object; unsigned ttl = 0; AtomPtr name, value, message = NULL; int id; int af; DnsQueryPtr query; AtomPtr cname = NULL; if(abort) { dnsSocketHandler = NULL; rc = establishDnsSocket(); if(rc < 0) { do_log(L_ERROR, "Couldn't reestablish DNS socket.\n"); /* At this point, we should abort all in-flight DNS requests. Oh, well, they'll timeout anyway. */ } return 1; } len = recv(fd, buf, 2048, 0); if(len <= 0) { if(errno == EINTR || errno == EAGAIN) return 0; /* This is where we get ECONNREFUSED for an ICMP port unreachable */ do_log_error(L_ERROR, errno, "DNS: recv failed"); dnsGethostbynameFallback(-1, message); return 0; } /* This could be a late reply to a query that timed out and was resent, a reply to a query that timed out, or a reply to an AAAA query when we already got a CNAME reply to the associated A. We filter such replies straight away, without trying to parse them. */ rc = dnsReplyId(buf, 0, len, &id); if(rc < 0) { do_log(L_WARN, "Short DNS reply.\n"); return 0; } if(!findQuery(id, NULL)) { return 0; } rc = dnsDecodeReply(buf, 0, len, &id, &name, &value, &af, &ttl); if(rc < 0) { assert(value == NULL); /* We only want to fallback on gethostbyname if we received a reply that we could not understand. What about truncated replies? */ if(rc < 0) { do_log_error(L_WARN, -rc, "DNS"); if(dnsUseGethostbyname >= 2 || (dnsUseGethostbyname && (rc != -EDNS_HOST_NOT_FOUND && rc != -EDNS_NO_RECOVERY && rc != -EDNS_FORMAT))) { dnsGethostbynameFallback(id, message); return 0; } else { message = internAtom(pstrerror(-rc)); } } else { assert(name != NULL && id >= 0 && af >= 0); } } query = findQuery(id, name); if(query == NULL) { /* Duplicate id ? */ releaseAtom(value); releaseAtom(name); return 0; } /* We're going to use the information in this reply. If it was an error, construct an empty atom to distinguish it from information we're still waiting for. */ if(value == NULL) value = internAtom(""); again: if(af == 4) { if(query->inet4 == NULL) { query->inet4 = value; query->ttl4 = current_time.tv_sec + ttl; } else releaseAtom(value); } else if(af == 6) { if(query->inet6 == NULL) { query->inet6 = value; query->ttl6 = current_time.tv_sec + ttl; } else releaseAtom(value); } else if(af == 0) { /* Ignore errors in this case. */ if(query->inet4 && query->inet4->length == 0) { releaseAtom(query->inet4); query->inet4 = NULL; } if(query->inet6 && query->inet6->length == 0) { releaseAtom(query->inet6); query->inet6 = NULL; } if(query->inet4 || query->inet6) { do_log(L_WARN, "Host %s has both %s and CNAME -- " "ignoring CNAME.\n", scrub(query->name->string), query->inet4 ? "A" : "AAAA"); releaseAtom(value); value = internAtom(""); af = query->inet4 ? 4 : 6; goto again; } else { cname = value; } } if(rc >= 0 && !cname && ((dnsQueryIPv6 < 3 && query->inet4 == NULL) || (dnsQueryIPv6 > 0 && query->inet6 == NULL))) return 0; /* This query is complete */ cancelTimeEvent(query->timeout_handler); object = query->object; if(object->flags & OBJECT_INITIAL) { assert(!object->headers); if(cname) { assert(query->inet4 == NULL && query->inet6 == NULL); object->headers = cname; object->expires = current_time.tv_sec + ttl; } else if((!query->inet4 || query->inet4->length == 0) && (!query->inet6 || query->inet6->length == 0)) { releaseAtom(query->inet4); releaseAtom(query->inet6); object->expires = current_time.tv_sec + dnsNegativeTtl; abortObject(object, 500, retainAtom(message)); } else if(!query->inet4 || query->inet4->length == 0) { object->headers = query->inet6; object->expires = query->ttl6; releaseAtom(query->inet4); } else if(!query->inet6 || query->inet6->length == 0) { object->headers = query->inet4; object->expires = query->ttl4; releaseAtom(query->inet6); } else { /* need to merge results */ char buf[1024]; if(query->inet4->length + query->inet6->length > 1024) { releaseAtom(query->inet4); releaseAtom(query->inet6); abortObject(object, 500, internAtom("DNS reply too long")); } else { if(dnsQueryIPv6 <= 1) { memcpy(buf, query->inet4->string, query->inet4->length); memcpy(buf + query->inet4->length, query->inet6->string + 1, query->inet6->length - 1); } else { memcpy(buf, query->inet6->string, query->inet6->length); memcpy(buf + query->inet6->length, query->inet4->string + 1, query->inet4->length - 1); } object->headers = internAtomN(buf, query->inet4->length + query->inet6->length - 1); if(object->headers == NULL) abortObject(object, 500, internAtom("Couldn't allocate DNS atom")); } object->expires = MIN(query->ttl4, query->ttl6); } object->age = current_time.tv_sec; object->flags &= ~(OBJECT_INITIAL | OBJECT_INPROGRESS); } else { do_log(L_WARN, "DNS object ex nihilo for %s.\n", scrub(query->name->string)); } removeQuery(query); free(query); releaseAtom(name); releaseAtom(message); releaseNotifyObject(object); return 0; } static int dnsGethostbynameFallback(int id, AtomPtr message) { DnsQueryPtr query, previous; ObjectPtr object; if(inFlightDnsQueries == NULL) { releaseAtom(message); return 1; } query = NULL; if(id < 0 || inFlightDnsQueries->id == id) { previous = NULL; query = inFlightDnsQueries; } else { previous = inFlightDnsQueries; while(previous->next) { if(previous->next->id == id) { query = previous->next; break; } previous = previous->next; } if(!query) { previous = NULL; query = inFlightDnsQueries; } } if(previous == NULL) { inFlightDnsQueries = query->next; if(inFlightDnsQueries == NULL) inFlightDnsQueriesLast = NULL; } else { previous->next = query->next; if(query->next == NULL) inFlightDnsQueriesLast = NULL; } object = makeObject(OBJECT_DNS, query->name->string, query->name->length, 1, 0, NULL, NULL); if(!object) { do_log(L_ERROR, "Couldn't make DNS object.\n"); releaseAtom(query->name); releaseAtom(message); releaseObject(query->object); cancelTimeEvent(query->timeout_handler); free(query); return -1; } if(dnsUseGethostbyname >= 1) { releaseAtom(message); do_log(L_WARN, "Falling back to using system resolver.\n"); really_do_gethostbyname(retainAtom(query->name), object); } else { releaseAtom(object->message); object->message = message; object->flags &= ~OBJECT_INPROGRESS; releaseNotifyObject(object); } cancelTimeEvent(query->timeout_handler); releaseAtom(query->name); if(query->inet4) releaseAtom(query->inet4); if(query->inet6) releaseAtom(query->inet6); releaseObject(query->object); free(query); return 1; } static int stringToLabels(char *buf, int offset, int n, char *string) { int i = offset; int j = 0, k = 0; while(1) { while(string[k] != '.' && string[k] != '\0') k++; if(k >= j + 256) return -1; buf[i] = (unsigned char)(k - j); i++; if(i >= n) return -1; while(j < k) { buf[i] = string[j]; i++; j++; if(i >= n) return -1; } if(string[j] == '\0') { buf[i] = '\0'; i++; if(i >= n) return -1; break; } j++; k++; } return i; } #ifdef UNALIGNED_ACCESS #define DO_NTOHS(_d, _s) _d = ntohs(*(unsigned short*)(_s)); #define DO_NTOHL(_d, _s) _d = ntohl(*(unsigned*)(_s)) #define DO_HTONS(_d, _s) *(unsigned short*)(_d) = htons(_s); #define DO_HTONL(_d, _s) *(unsigned*)(_d) = htonl(_s) #else #define DO_NTOHS(_d, _s) \ do { unsigned short _dd; \ memcpy(&(_dd), (_s), sizeof(unsigned short)); \ _d = ntohs(_dd); } while(0) #define DO_NTOHL(_d, _s) \ do { unsigned _dd; \ memcpy(&(_dd), (_s), sizeof(unsigned)); \ _d = ntohl(_dd); } while(0) #define DO_HTONS(_d, _s) \ do { unsigned short _dd; \ _dd = htons(_s); \ memcpy((_d), &(_dd), sizeof(unsigned short)); } while(0); #define DO_HTONL(_d, _s) \ do { unsigned _dd; \ _dd = htonl(_s); \ memcpy((_d), &(_dd), sizeof(unsigned)); } while(0); #endif static int labelsToString(char *buf, int offset, int n, char *d, int m, int *j_return) { int i = offset, j, k; int ll, rc; j = 0; while(1) { if(i >= n) return -1; ll = *(unsigned char*)&buf[i]; i++; if(ll == 0) { break; } if((ll & (3 << 6)) == (3 << 6)) { /* RFC 1035, 4.1.4 */ int o; if(i >= n) return -1; o = (ll & ~(3 << 6)) << 8 | *(unsigned char*)&buf[i]; i++; rc = labelsToString(buf, o, n, &d[j], m - j, &k); if(rc < 0) return -1; j += k; break; } else if((ll & (3 << 6)) == 0) { for(k = 0; k < ll; k++) { if(i >= n || j >= m) return -1; d[j++] = buf[i++]; } if(i >= n) return -1; if(buf[i] != '\0') { if(j >= m) return -1; d[j++] = '.'; } } else { return -1; } } *j_return = j; return i; } static int dnsBuildQuery(int id, char *buf, int offset, int n, AtomPtr name, int af) { int i = offset; int type; switch(af) { case 4: type = 1; break; case 6: type = 28; break; default: return -EINVAL; } if(i + 12 >= n) return -1; DO_HTONS(&buf[i], id); i += 2; DO_HTONS(&buf[i], 1<<8); i += 2; DO_HTONS(&buf[i], 1); i += 2; DO_HTONS(&buf[i], 0); i += 2; DO_HTONS(&buf[i], 0); i += 2; DO_HTONS(&buf[i], 0); i += 2; i = stringToLabels(buf, i, n, name->string); if(i < 0) return -ENAMETOOLONG; if(i + 4 >= n) return -ENAMETOOLONG; DO_HTONS(&buf[i], type); i += 2; DO_HTONS(&buf[i], 1); i += 2; return i; } static int dnsReplyId(char *buf, int offset, int n, int *id_return) { if(n - offset < 12) return -1; DO_NTOHS(*id_return, &buf[offset]); return 1; } static int dnsDecodeReply(char *buf, int offset, int n, int *id_return, AtomPtr *name_return, AtomPtr *value_return, int *af_return, unsigned *ttl_return) { int i = offset, j, m; int id = -1, b23, qdcount, ancount, nscount, arcount, rdlength; int class, type; unsigned int ttl; char b[2048]; int af = -1; AtomPtr name = NULL, value; char addresses[1024]; int addr_index = 0; int error = EDNS_NO_ADDRESS; unsigned final_ttl = 7 * 24 * 3600; int dnserror; if(n - i < 12) { error = EDNS_INVALID; goto fail; } DO_NTOHS(id, &buf[i]); i += 2; DO_NTOHS(b23, &buf[i]); i += 2; DO_NTOHS(qdcount, &buf[i]); i += 2; DO_NTOHS(ancount, &buf[i]); i += 2; DO_NTOHS(nscount, &buf[i]); i += 2; DO_NTOHS(arcount, &buf[i]); i += 2; do_log(D_DNS, "DNS id %d, b23 0x%x, qdcount %d, ancount %d, " "nscount %d, arcount %d\n", id, b23, qdcount, ancount, nscount, arcount); if((b23 & (0xF870)) != 0x8000) { do_log(L_ERROR, "Incorrect DNS reply (b23 = 0x%x).\n", b23); error = EDNS_INVALID; goto fail; } dnserror = b23 & 0xF; if(b23 & 0x200) { do_log(L_WARN, "Truncated DNS reply (b23 = 0x%x).\n", b23); } if(dnserror || qdcount != 1) { if(!dnserror) do_log(L_ERROR, "Unexpected number %d of DNS questions.\n", qdcount); if(dnserror == 1) error = EDNS_FORMAT; else if(dnserror == 2) error = EDNS_NO_RECOVERY; else if(dnserror == 3) error = EDNS_HOST_NOT_FOUND; else if(dnserror == 4 || dnserror == 5) error = EDNS_REFUSED; else if(dnserror == 0) error = EDNS_INVALID; else error = EUNKNOWN; goto fail; } /* We do this early, so that we can return the address family to the caller in case of error. */ i = labelsToString(buf, i, n, b, 2048, &m); if(i < 0) { error = EDNS_FORMAT; goto fail; } DO_NTOHS(type, &buf[i]); i += 2; DO_NTOHS(class, &buf[i]); i += 2; if(type == 1) af = 4; else if(type == 28) af = 6; else { error = EDNS_FORMAT; goto fail; } do_log(D_DNS, "DNS q: "); do_log_n(D_DNS, b, m); do_log(D_DNS, " (%d, %d)\n", type, class); name = internAtomLowerN(b, m); if(name == NULL) { error = ENOMEM; goto fail; } if(class != 1) { error = EDNS_FORMAT; goto fail; } #define PARSE_ANSWER(kind, label) \ do { \ i = labelsToString(buf, i, 1024, b, 2048, &m); \ if(i < 0) goto label; \ DO_NTOHS(type, &buf[i]); i += 2; if(i > 1024) goto label; \ DO_NTOHS(class, &buf[i]); i += 2; if(i > 1024) goto label; \ DO_NTOHL(ttl, &buf[i]); i += 4; if(i > 1024) goto label; \ DO_NTOHS(rdlength, &buf[i]); i += 2; if(i > 1024) goto label; \ do_log(D_DNS, "DNS " kind ": "); \ do_log_n(D_DNS, b, m); \ do_log(D_DNS, " (%d, %d): %d bytes, ttl %u\n", \ type, class, rdlength, ttl); \ } while(0) for(j = 0; j < ancount; j++) { PARSE_ANSWER("an", fail); if(strcasecmp_n(name->string, b, m) == 0) { if(class != 1) { do_log(D_DNS, "DNS: %s: unknown class %d.\n", name->string, class); error = EDNS_UNSUPPORTED; goto cont; } if(type == 1 || type == 28) { if((type == 1 && rdlength != 4) || (type == 28 && rdlength != 16)) { do_log(L_ERROR, "DNS: %s: unexpected length %d of %s record.\n", scrub(name->string), rdlength, type == 1 ? "A" : "AAAA"); error = EDNS_INVALID; if(rdlength <= 0 || rdlength >= 32) goto fail; goto cont; } if(af == 0) { do_log(L_WARN, "DNS: %s: host has both A and CNAME -- " "ignoring CNAME.\n", scrub(name->string)); addr_index = 0; af = -1; } if(type == 1) { if(af < 0) af = 4; else if(af == 6) { do_log(L_WARN, "Unexpected AAAA reply.\n"); goto cont; } } else { if(af < 0) af = 6; else if(af == 4) { do_log(L_WARN, "Unexpected A reply.\n"); goto cont; } } if(addr_index == 0) { addresses[0] = DNS_A; addr_index++; } else { if(addr_index > 1000) { error = EDNS_INVALID; goto fail; } } assert(addresses[0] == DNS_A); if(final_ttl > ttl) final_ttl = ttl; memset(&addresses[addr_index], 0, sizeof(HostAddressRec)); if(type == 1) { addresses[addr_index] = 4; memcpy(addresses + addr_index + 1, buf + i, 4); } else { addresses[addr_index] = 6; memcpy(addresses + addr_index + 1, buf + i, 16); } addr_index += sizeof(HostAddressRec); } else if(type == 5) { int j, k; if(af != 0 && addr_index > 0) { do_log(L_WARN, "DNS: host has both CNAME and A -- " "ignoring CNAME.\n"); goto cont; } af = 0; if(addr_index != 0) { /* Only warn if the CNAMEs are not identical */ char tmp[512]; int jj, kk; assert(addresses[0] == DNS_CNAME); jj = labelsToString(buf, i, n, tmp, 512, &kk); if(jj < 0 || kk != strlen(addresses + 1) || memcmp(addresses + 1, tmp, kk) != 0) { do_log(L_WARN, "DNS: " "%s: host has multiple CNAMEs -- " "ignoring subsequent.\n", scrub(name->string)); } goto cont; } addresses[0] = DNS_CNAME; addr_index++; j = labelsToString(buf, i, n, addresses + 1, 1020, &k); if(j < 0) { addr_index = 0; error = ENAMETOOLONG; continue; } addr_index = k + 1; } else { error = EDNS_NO_ADDRESS; i += rdlength; continue; } } cont: i += rdlength; } #if (LOGGING_MAX & D_DNS) for(j = 0; j < nscount; j++) { PARSE_ANSWER("ns", nofail); i += rdlength; } for(j = 0; j < arcount; j++) { PARSE_ANSWER("ar", nofail); i += rdlength; } nofail: #endif #undef PARSE_ANSWER do_log(D_DNS, "DNS: %d bytes\n", addr_index); if(af < 0) goto fail; value = internAtomN(addresses, addr_index); if(value == NULL) { error = ENOMEM; goto fail; } assert(af >= 0); *id_return = id; *name_return = name; *value_return = value; *af_return = af; *ttl_return = final_ttl; return 1; fail: *id_return = id; *name_return = name; *value_return = NULL; *af_return = af; return -error; } #else static int really_do_dns(AtomPtr name, ObjectPtr object) { abort(); } #endif