static void fillSpecialObject(ObjectPtr object, void (*fn) (FILE *, char *), void *closure) { int rc; int filedes[2]; pid_t pid; sigset_t ss, old_mask; if (object->flags & OBJECT_INPROGRESS) return; rc = pipe(filedes); if (rc < 0) { do_log_error(L_ERROR, errno, "Couldn't create pipe"); abortObject(object, 503, internAtomError(errno, "Couldn't create pipe")); return; } fflush(stdout); fflush(stderr); flushLog(); interestingSignals(&ss); do { rc = sigprocmask(SIG_BLOCK, &ss, &old_mask); } while (rc < 0 && errno == EINTR); if (rc < 0) { do_log_error(L_ERROR, errno, "Sigprocmask failed"); abortObject(object, 503, internAtomError(errno, "Sigprocmask failed")); close(filedes[0]); close(filedes[1]); return; } pid = fork(); if (pid < 0) { do_log_error(L_ERROR, errno, "Couldn't fork"); abortObject(object, 503, internAtomError(errno, "Couldn't fork")); close(filedes[0]); close(filedes[1]); do { rc = sigprocmask(SIG_SETMASK, &old_mask, NULL); } while (rc < 0 && errno == EINTR); if (rc < 0) { do_log_error(L_ERROR, errno, "Couldn't restore signal mask"); s_serverExit(); } return; } if (pid > 0) { SpecialRequestPtr request; close(filedes[1]); do { rc = sigprocmask(SIG_SETMASK, &old_mask, NULL); } while (rc < 0 && errno == EINTR); if (rc < 0) { do_log_error(L_ERROR, errno, "Couldn't restore signal mask"); s_serverExit(); return; } request = malloc(sizeof(SpecialRequestRec)); if (request == NULL) { kill(pid, SIGTERM); close(filedes[0]); abortObject(object, 503, internAtom("Couldn't allocate request\n")); notifyObject(object); } else { request->buf = get_chunk(); if (request->buf == NULL) { kill(pid, SIGTERM); close(filedes[0]); free(request); abortObject(object, 503, internAtom ("Couldn't allocate request\n")); notifyObject(object); } } object->flags |= OBJECT_INPROGRESS; retainObject(object); request->object = object; request->fd = filedes[0]; request->pid = pid; request->offset = 0; do_stream(IO_READ, filedes[0], 0, request->buf, CHUNK_SIZE, specialRequestHandler, request); } else { close(filedes[0]); uninitEvents(); do { rc = sigprocmask(SIG_SETMASK, &old_mask, NULL); } while (rc < 0 && errno == EINTR); if (rc < 0) exit(1); if (filedes[1] != 1) dup2(filedes[1], 1); (*fn) (stdout, closure); exit(0); } }
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 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; case NO_ADDRESS: error = EDNS_NO_ADDRESS; break; 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; }
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; }
int specialRequestHandler(int status, FdEventHandlerPtr event, StreamRequestPtr srequest) { SpecialRequestPtr request = srequest->data; int rc; int killed = 0; 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 we're the only person interested in this object, let's abort it now. */ 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); /* That's a blocking wait. It shouldn't block for long, as we've either already killed the child, or else we got EOF from it. */ 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 void fillSpecialObject(ObjectPtr object, void (*fn)(FILE*, char*), void* closure) { int rc; int filedes[2]; pid_t pid; sigset_t ss, old_mask; if(object->flags & OBJECT_INPROGRESS) return; rc = pipe(filedes); if(rc < 0) { do_log_error(L_ERROR, errno, "Couldn't create pipe"); abortObject(object, 503, internAtomError(errno, "Couldn't create pipe")); return; } fflush(stdout); fflush(stderr); flushLog(); /* Block signals that we handle specially until the child can disable the handlers. */ interestingSignals(&ss); /* I'm a little confused. POSIX doesn't allow EINTR here, but I think that both Linux and SVR4 do. */ do { rc = sigprocmask(SIG_BLOCK, &ss, &old_mask); } while (rc < 0 && errno == EINTR); if(rc < 0) { do_log_error(L_ERROR, errno, "Sigprocmask failed"); abortObject(object, 503, internAtomError(errno, "Sigprocmask failed")); close(filedes[0]); close(filedes[1]); return; } pid = fork(); if(pid < 0) { do_log_error(L_ERROR, errno, "Couldn't fork"); abortObject(object, 503, internAtomError(errno, "Couldn't fork")); close(filedes[0]); close(filedes[1]); do { rc = sigprocmask(SIG_SETMASK, &old_mask, NULL); } while (rc < 0 && errno == EINTR); if(rc < 0) { do_log_error(L_ERROR, errno, "Couldn't restore signal mask"); polipoExit(); } return; } if(pid > 0) { SpecialRequestPtr request; close(filedes[1]); do { rc = sigprocmask(SIG_SETMASK, &old_mask, NULL); } while (rc < 0 && errno == EINTR); if(rc < 0) { do_log_error(L_ERROR, errno, "Couldn't restore signal mask"); polipoExit(); return; } request = malloc(sizeof(SpecialRequestRec)); if(request == NULL) { kill(pid, SIGTERM); close(filedes[0]); abortObject(object, 503, internAtom("Couldn't allocate request\n")); notifyObject(object); return; } else { request->buf = get_chunk(); if(request->buf == NULL) { kill(pid, SIGTERM); close(filedes[0]); free(request); abortObject(object, 503, internAtom("Couldn't allocate request\n")); notifyObject(object); return; } } object->flags |= OBJECT_INPROGRESS; retainObject(object); request->object = object; request->fd = filedes[0]; request->pid = pid; request->offset = 0; /* Under any sensible scheduler, the child will run before the parent. So no need for IO_NOTNOW. */ do_stream(IO_READ, filedes[0], 0, request->buf, CHUNK_SIZE, specialRequestHandler, request); } else { /* child */ close(filedes[0]); uninitEvents(); do { rc = sigprocmask(SIG_SETMASK, &old_mask, NULL); } while (rc < 0 && errno == EINTR); if(rc < 0) exit(1); if(filedes[1] != 1) dup2(filedes[1], 1); (*fn)(stdout, closure); exit(0); } }