/* $Id: hostlist.c,v 1.8 2004/02/13 14:59:57 ossi Exp $ *
 *
 * puf 0.9  Copyright (C) 2000-2003 by Oswald Buddenhagen <puf@ossi.cjb.net>
 * based on puf 0.1.x (C) 1999,2000 by Anders Gavare <gavare@hotmail.com>
 *
 * 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, 0) != 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), 0) < (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 <setjmp.h>

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);
}


syntax highlighted by Code2HTML, v. 0.9.1