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; }
int specialRequestHandler(int status, FdEventHandlerPtr event, StreamRequestPtr srequest) { SpecialRequestPtr request = srequest->data; int rc; int killed = 0; (void)event; if (status < 0) { kill(request->pid, SIGTERM); killed = 1; request->object->flags &= ~OBJECT_INPROGRESS; abortObject(request->object, 502, internAtomError(-status, "Couldn't read from client")); goto done; } if (srequest->offset > 0) { rc = objectAddData(request->object, request->buf, request->offset, srequest->offset); if (rc < 0) { kill(request->pid, SIGTERM); killed = 1; request->object->flags &= ~OBJECT_INPROGRESS; abortObject(request->object, 503, internAtom ("Couldn't add data to connection")); goto done; } request->offset += srequest->offset; } if (status) { request->object->flags &= ~OBJECT_INPROGRESS; request->object->length = request->object->size; goto done; } if (request->object->refcount <= 1) { kill(request->pid, SIGTERM); killed = 1; request->object->flags &= ~OBJECT_INPROGRESS; abortObject(request->object, 500, internAtom("Aborted")); goto done; } notifyObject(request->object); do_stream(IO_READ | IO_NOTNOW, request->fd, 0, request->buf, CHUNK_SIZE, specialRequestHandler, request); return 1; done: close(request->fd); dispose_chunk(request->buf); releaseNotifyObject(request->object); do { rc = waitpid(request->pid, &status, 0); } while (rc < 0 && errno == EINTR); if (rc < 0) { do_log(L_ERROR, "Wait for %d: %d\n", (int)request->pid, errno); } else { int normal = (WIFEXITED(status) && WEXITSTATUS(status) == 0) || (killed && WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM); char *reason = WIFEXITED(status) ? "with status" : WIFSIGNALED(status) ? "on signal" : "with unknown status"; int value = WIFEXITED(status) ? WEXITSTATUS(status) : WIFSIGNALED(status) ? WTERMSIG(status) : status; do_log(normal ? D_CHILD : L_ERROR, "Child %d exited %s %d.\n", (int)request->pid, reason, value); } free(request); return 1; }
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; }
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 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; }