/* $Id: hostlist.c,v 1.8 2004/02/13 14:59:57 ossi Exp $ * * * puf 0.9 Copyright (C) 2000-2003 by Oswald Buddenhagen * based on puf 0.1.x (C) 1999,2000 by Anders Gavare * * You may modify and distribute this code under the terms of the GPL. * There is NO WARRANTY of any kind. See COPYING for details. * * hostlist.c - manage the hostname cache * */ #include "puf.h" int always_primary_name; host_t *hostlist; /* list of known hosts */ linear_queue(queue_dns_lookup, whost_t); /* waiting for start of lookup */ linear_na_queue(queue_dns_busy, dnsproc_t); /* helper processes performing lookup */ linear_na_queue(queue_dns_idle, dnsproc_t); /* helper process pool */ /* create new hostname structure */ static host_t * new_host(char *name, int len, host_t **hptr) { host_t *h; if ((h = mmalloc(sizeof(host_t) + len))) { memcpy(h->name, name, len); h->info = 0; if (hptr) { h->next = *hptr; *hptr = h; } } return h; } /* create a new hostname structure, if it's not the initiating hostname */ /* put the new or already existing structure into the host list */ static host_t * p_new_host(whost_t *wh, hinfo_t *hi, int *hapi, char *name, int len, host_t **hptr) { host_t *h; if (*hapi && !memcmp(wh->host->name, name, len)) { *hapi = 0; h = wh->host; h->next = *hptr; *hptr = h; } else if (!(h = new_host(name, len, hptr))) return 0; h->info = hi; return h; } /* tolower() on whole string */ static int lcase(char *d, const char *s) { int l; for (l = 0; (d[l] = tolower((int)s[l])); l++); return l + 1; } /* FIXME: maybe, we should use inet_aton (or inet_addr) before doing the gethostbyname magic. this would save the forks for numerical input. also, gethostbyname reportedly fails addresses like http://2165339403/~f98anga/ on openbsd */ /* return a cached host entry for the given host */ host_t * host_lookup_fast(char *name, int namlen) { host_t *h; dbg(DNS, ("host_lookup_fast for '%.*s'\n", namlen, name)); /* known host? */ for (h = hostlist; h; h = h->next) { if (!memcmp(name, h->name, namlen + 1)) return h; } return 0; } /* starts an asynchronous dns lookup for the given host */ /* the first name referred to determines the local directory name for this host. */ host_t * host_lookup_full(char *name, int namlen, url_t *u, proxy_t *prox) { host_t *h; whost_t *wh; wobj_t *wo; dnsproc_t *pr; dbg(DNS, ("host_lookup_full for '%.*s'\n", namlen, name)); /* prepare url for enqueuing */ if (!(wo = mmalloc(sizeof(*wo)))) return 0; wo->url = u; wo->proxy = prox; /* pending lookup? */ for (wh = queue_dns_lookup; wh; wh = wh->next) { if (!memcmp(name, wh->host->name, namlen + 1)) { wo->next = wh->objq; wh->objq = wo; return wh->host; } } for (pr = queue_dns_busy; pr; pr = pr->next) { if (pr->whost && !memcmp(name, pr->whost->host->name, namlen + 1)) { wo->next = pr->whost->objq; pr->whost->objq = wo; return pr->whost->host; } } /* new host entry */ if (!(h = new_host(name, namlen + 1, 0))) { free(wo); return 0; } /* prepare lookup for enqueuing */ if (!(wh = mmalloc(sizeof(*wh)))) { free(h); free(wo); return 0; } wo->next = 0; wh->objq = wo; wh->host = h; lq_append(queue_dns_lookup, wh); /* XXX optimize idle case */ return h; } int start_lookup(dnsproc_t *proc) { int l = strlen(proc->whost->host->name) + 1; dbg(DNS, ("starting dns lookup, helper %d\n", proc->pid)); if (send(proc->fd, proc->whost->host->name, l, MSG_NOSIGNAL) != l) { prx(ERR, "cannot talk to DNS helper!\n"); return 0; } return 1; } int finish_lookup(dnsproc_t *proc) { whost_t *wh; host_t *h, *sh; hinfo_t *hi; int i, na, cp, hapi = 1; u_char buf[1024]; dbg(DNS, ("finishing dns lookup, helper %d\n", proc->pid)); if (recv(proc->fd, buf, sizeof(buf), MSG_NOSIGNAL) < (int)sizeof(int)) { prx(ERR, "cannot talk to DNS helper!\n"); return 0; } wh = proc->whost; na = ((int *)buf)[0]; if (!na) { prx(ERR, "DNS lookup for '%s' failed!\n", wh->host->name); goto badhost; } if (na < 0) { prx(ERR, "DNS lookup for '%s' timed out!\n", wh->host->name); goto badhost; } if (((int *)buf)[1] != sizeof(struct in_addr)) { prx(ERR, "cannot handle address returned for '%s'!\n", wh->host->name); goto badhost; } cp = 2 * sizeof(int) + na * sizeof(struct in_addr); /* check if this is a new alias for a host already in the list */ for (h = hostlist; h; h = h->next) if (!memcmp(h->name, buf + cp + 1, buf[cp])) { hi = h->info; dbg(DNS, ("found a new alias for '%s':\n", h->name)); goto havho; } dbg(DNS, ("creating new host '%s'\n", buf + cp + 1)); /* create new hostinfo structure ... */ if (!(hi = mmalloc(sizeof(hinfo_t) + na * sizeof(haddr_t)))) goto badhost; /* create hostname structure for real name */ if (!(h = p_new_host(wh, hi, &hapi, buf + cp + 1, buf[cp], &hostlist))) { free(hi); goto badhost; } /* ... and initialize it */ hi->name = h->name; hi->lname = always_primary_name ? h->name : wh->host->name; hi->is_http11 = 1; hi->cur_ip = 0; hi->num_ips = na; /* copy list of ip-addresses and initialize retry counters */ for (i = 0; i < na; i++) { hi->ips[i].addr = ((struct in_addr *)(((int *)buf) + 2))[i]; hi->ips[i].retry_time = 0; hi->ips[i].last_errt = 0; hi->ips[i].attempt = 0; } havho: /* save the alias names if we got any: */ for (;;) { cp += buf[cp] + 1; if (!buf[cp]) break; /* check, if we already know this alias. theoretically sh should be h->next, but it happens, that the alias was reported as a primary name already (namely if we used the alias first and it is listed in /etc/hosts */ for (sh = h; sh && sh->info == hi; sh = sh->next) if (!memcmp(sh->name, buf + cp + 1, buf[cp])) goto havho; dbg(DNS, ("adding new alias '%s'\n", buf + cp + 1)); if (!p_new_host(wh, hi, &hapi, buf + cp + 1, buf[cp], &(h->next))) { if (hapi) /* no host info -> real problem */ goto badhost; else /* missed alias */ return 1; } } if (hapi) { /* was it a not fully qualified domain name? */ #ifdef CORRECT_DNS int nl = strlen(wh->host->name); for (sh = h; sh && sh->info == hi; sh = sh->next) if (!memcmp(sh->name, wh->host->name, nl) && sh->name[nl] == '.') goto neho; prx(WRN, "your DNS resolver configuration seems to be messed up.\n"); neho: #endif dbg(DNS, ("adding not fully qualified hostname '%s'\n", wh->host->name)); wh->host->next = h->next; h->next = wh->host; wh->host->info = hi; } return 1; badhost: wh->host->next = hostlist; hostlist = wh->host; return 1; } #include jmp_buf alrmjmp; static void sigalrm() { longjmp(alrmjmp, 1); } dnsproc_t * fork_dnsproc() { dnsproc_t *proc; struct hostent *he; int hl, i, na, pid, fds[2]; u_int cp; sigset_t ss, oss; char buf[1024]; dbg(DNS, ("forking new dns helper\n")); if (!(proc = mmalloc(sizeof(*proc)))) return 0; if (socketpair(PF_UNIX, SOCK_STREAM, 0, fds) && (!free_fd() || (socketpair(PF_UNIX, SOCK_STREAM, 0, fds) && (!free_fd() || socketpair(PF_UNIX, SOCK_STREAM, 0, fds))))) { free(proc); return 0; } sigfillset(&ss); sigprocmask(SIG_SETMASK, &ss, &oss); if ((pid = fork())) { sigprocmask(SIG_SETMASK, &oss, 0); close(fds[1]); if (pid < 0) { dbg(DNS, (" failed\n")); close(fds[0]); free(proc); return 0; } else { dbg(DNS, (" pid = %d\n", pid)); proc->fd = fds[0]; proc->pid = pid; return proc; } } signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); sigprocmask(SIG_SETMASK, &oss, 0); close(fds[0]); for (;;) { if (!setjmp(alrmjmp)) { dbg(DNS, ("dns helper %d: awaiting request\n", getpid())); if (read(fds[1], buf, sizeof(buf)) <= 0) { prx(ERR, "DNS helper: cannot read control socket!\n"); exit(1); } dbg(DNS, ("dns helper %d: looking up '%s'\n", getpid(), buf)); signal(SIGALRM, (void (*)(int))sigalrm); alarm(timeout_dns); he = gethostbyname(buf); alarm(0); if (!he) { dbg(DNS, ("dns helper %d: lookup failed\n", getpid())); ((int *)buf)[0] = 0; if (write(fds[1], buf, sizeof(int)) != sizeof(int)) { prx(ERR, "DNS helper: cannot write control socket!\n"); exit(1); } } else { /* count ip-addresses for this name */ for (na = 0, cp = 2 * sizeof(int); he->h_addr_list[na]; na++, cp += he->h_length) memcpy(buf + cp, he->h_addr_list[na], he->h_length); dbg(DNS, ("dns helper %d: lookup successful, %d addresses\n", getpid(), na)); ((int *)buf)[0] = na; ((int *)buf)[1] = he->h_length; /* copy name and aliases */ hl = lcase(buf + cp + 1, he->h_name); buf[cp] = hl; cp += hl + 1; for (i = 0; he->h_aliases[i]; i++) { hl = lcase(buf + cp + 1, he->h_aliases[i]); buf[cp] = hl; cp += hl + 1; } buf[cp++] = 0; if (write(fds[1], buf, cp) != (int)cp) { prx(ERR, "DNS helper: cannot write control socket!\n"); exit(1); } } } else { dbg(DNS, ("dns helper %d: lookup timed out\n", getpid())); ((int *)buf)[0] = -1; if (write(fds[1], buf, sizeof(int)) != sizeof(int)) { prx(ERR, "DNS helper: cannot write control socket!\n"); exit(1); } } } } void reap_dnsproc(dnsproc_t *proc) { dbg(DNS, ("reaping dns helper %d\n", proc->pid)); kill(proc->pid, SIGTERM); waitpid(proc->pid, 0, 0); close(proc->fd); free(proc); }