/* authserv.c -- sample authentication server main program
 *
 * BSD+ License  <http://access1.sun.com/codesamples/BSD.html>
 *
 * Copyright (c) 2002 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * o  Redistribution of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * o  Redistribution in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT
 * BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING,
 * MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO
 * EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE,
 * PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE
 * THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE
 * THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or
 * intended for use in the design, construction, operation or
 * maintenance of any nuclear facility.
 *
 *  Contributor(s): Chris Newman <chris.newman@sun.com>
 *
 ************************************************************************/

#if defined(__sun) && !defined(_REENTRANT)
#error "Compile with -mt switch on Solaris for thread support"
#endif

#include <assert.h>
#include <pthread.h>		/* use pthreads */
#include <stdlib.h>		/* malloc/realloc */
#include <unistd.h>             /* sleep */
#include <stdio.h>              /* fprintf */
#include <stdarg.h>             /* vfprintf */
#include <string.h>             /* strerror */
#include <errno.h>              /* errno */
#include <sys/types.h>          /* sockaddr */
#include <sys/socket.h>         /* socket */
#include <sys/uio.h>		/* writev and struct iovec */
#include <netinet/in.h>         /* sockaddr_in */
#include <arpa/inet.h>          /* inet_addr */

#include "authserv.h"

/* this should stay as 127.0.0.1 until protocol is revised to add
 * security (likely using BEEP RFC 3080)
 */
#define BINDIP                      "127.0.0.1"

/* these are good candidates for configuration options
 */
#define BINDPORT                    56
#define LISTEN_QUEUE                1024
#define MAX_CONCURRENT_CONNECTIONS  256
#define MAX_PAYLOAD                 8192
#define MAX_ATTRS                   64
#define MAX_VALS                    128

/* debugging macro */
#if DEBUG
#define DODEBUG(arglist) logf arglist
#else
#define DODEBUG
#endif

/* error handling macro */
#define FD_TEMPORARY_ERR()	(errno == EINTR || errno == ENOMEM)

/* constant for a carrage-return/line-feed sequence */
const static char crlf[] = "\r\n";

/* server connection context
 */
struct conn {
    int fd;			/* file descriptor */
    unsigned datasize;		/* size of semi-persistant data buffer */
    unsigned replysize;		/* size of transient reply buffer */
    unsigned numattr;		/* number of attributes in reply */
    unsigned numval;		/* number of values in reply */
    unsigned replyok;		/* flag for successful reply sent */
    void *data;			/* semi-persistant data buffer */
    char *reply;		/* transient reply buffer */
    struct conn *next;		/* next connection context */
    struct threadpool *pool;	/* thread pool context */
    char info[64];		/* connection logging information */
    char buf[2048];		/* IO buffer for connection */
    char sentinel;		/* sentinel for IO buffer */
};

/* thread pool */
struct threadpool {
    pthread_mutex_t lock, logm;
    pthread_cond_t cond;
    int threads_total;
    int threads_waiting;
    int exit_flag;
    struct conn *head, *tail, *unused;
    void *context;
};

/* log an error
 */
void logf(struct threadpool *pool, const char *fmt, ...)
{
    va_list pvar;
    time_t now;
    struct tm tm;

    time(&now);
    localtime_r(&now, &tm);
    va_start(pvar, fmt);
    if (pool != (struct threadpool *)0) pthread_mutex_lock(&pool->logm);
    fprintf(stderr, "%04d-%02d-%02d %02d:%02d:%02d ",
             tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
             tm.tm_hour, tm.tm_min, tm.tm_sec);
    vfprintf(stderr, fmt, pvar);
    if (pool != (struct threadpool *)0) pthread_mutex_unlock(&pool->logm);
    va_end(pvar);
}

/* wait for a thread pool event
 */
void pool_wait(struct threadpool *pool)
{
    int err;
    
    err = pthread_cond_wait(&pool->cond, &pool->lock);
    if (err != 0) {
        logf(pool, "FATAL: pthread_cond_wait: %s (%d)\n",
                strerror(err), err);
        exit(1);
    }
}

/* signal a thread pool event
 */
void pool_signal(struct threadpool *pool)
{
    int err;
    
    err = pthread_cond_signal(&pool->cond);
    if (err != 0) {
        logf(pool, "FATAL: pthread_cond_signal: %s (%d)\n",
                strerror(err), err);
        exit(1);
    }
}

/* lock the thread pool context
 */
void pool_lock(struct threadpool *pool)
{
    int err;

    err = pthread_mutex_lock(&pool->lock);
    if (err != 0) {
        logf(pool, "FATAL: pthread_mutex_lock: %s (%d)\n",
                strerror(err), err);
        exit(1);
    }
}

/* unlock the thread pool context
 */
void pool_unlock(struct threadpool *pool)
{
    int err;

    err = pthread_mutex_unlock(&pool->lock);
    if (err != 0) {
        logf(pool, "FATAL: pthread_mutex_unlock: %s (%d)\n",
                strerror(err), err);
        exit(1);
    }
}

/* retry a writev until all data is written or a fatal error occurs
 *  NOTE: this may not work on HPUX due to bugs in HPUX writev
 */
long retry_writev(int fd, const struct iovec *iov, int iovcnt)
{
    long n;
    long written = 0;
    struct iovec *iovp;
    struct iovec iov_saved = {0, 0};
    
    while (iovcnt > 0) {
	if (!iov[0].iov_len) {
	    iov++;
	    iovcnt--;
	    continue;
	}
	n = writev(fd, (struct iovec *)iov, iovcnt);
	if (n == -1) {
	    if (FD_TEMPORARY_ERR())
		continue;
#ifdef __hpux
	    /* workaround HP bug, see Sun bugtraq 4560296,4576405 */
	    if (errno == EFAULT)
		continue;
#endif
	    if (iov_saved.iov_base) {
		((struct iovec *)iov)[0] = iov_saved;
	    }
	    return -1;
	}
#ifdef __hpux
	/* workaround HP bug, see Sun bugtraq 4550812 for details */
	if (n == 0) {
	    errno = EPIPE;
	    return -1;
	}
#endif	
	written += n;
	while (n) {
	    if (iov[0].iov_len > n) {
		if (!iov_saved.iov_base) {
		    iov_saved = iov[0];
		}
	    	iovp = (struct iovec *)iov;
		iovp->iov_base = (char *)iovp->iov_base + n;
		iovp->iov_len -= n;
		n = 0;
	    } else {
		n -= iov[0].iov_len;
		if (iov_saved.iov_base) {
		    ((struct iovec *)iov)[0] = iov_saved;
		    iov_saved.iov_base = 0;
		}
		iov++;
		iovcnt--;
	    }
	}
    }
    
    return (written);
}

/* callback to signal authentication failed
 */
static void auth_fail(const struct authdata *adat,
		      int errcode,
		      const char *errlang,
		      const char *errtext)
{
    const static char a_errlang[] = "errlang ";
    const static char a_errtext[] = "errtext ";
    const static char a_errcode[] = "errcode ";
    iovec_t reply[14];
    char head[256], code[10];
    unsigned nio = 1;
    unsigned total = 0;
    int len;
    struct conn *conn;

    if (adat == (const struct authdata *)0 || errcode >= SASL_OK ||
	(errtext != (char *)0 && strchr(errtext, '\r') != (char *)0)) {
	return;
    }
    conn = adat->priv;
    if (errlang != (char *)0) {
	reply[nio].iov_base = (void *) a_errlang;
	total += reply[nio].iov_len = sizeof (a_errlang) - 1;
	reply[nio+1].iov_base = (void *) errlang;
	total += reply[nio+1].iov_len = strlen(errlang);
	reply[nio+2].iov_base = (void *) crlf;
	total += reply[nio+2].iov_len = 2;
	nio += 3;
	++conn->numattr, ++conn->numval;
    }
    if (errtext != (char *)0) {
	reply[nio].iov_base = (void *) a_errtext;
	total += reply[nio].iov_len = sizeof (a_errtext) - 1;
	reply[nio+1].iov_base = (void *) errtext;
	total += reply[nio+1].iov_len = strlen(errtext);
	reply[nio+2].iov_base = (void *) crlf;
	total += reply[nio+2].iov_len = 2;
	nio += 3;
	++conn->numattr, ++conn->numval;
    }
    len = snprintf(code, sizeof (code), "%d", errcode);
    assert(len > 0 && len < sizeof (code));
    reply[nio].iov_base = (void *) a_errcode;
    total += reply[nio].iov_len = sizeof (a_errcode) - 1;
    reply[nio+1].iov_base = code;
    total += reply[nio+1].iov_len = len;
    reply[nio+2].iov_base = (void *) crlf;
    total += reply[nio+2].iov_len = 2;
    reply[nio+3].iov_base = (void *) crlf;
    total += reply[nio+3].iov_len = 2;
    nio += 4;
    ++conn->numattr, ++conn->numval;

    /* format the header */
    reply[0].iov_base = head;
    len = snprintf(head, sizeof (head), "%u %u %u\r\n",
		   total, conn->numattr, conn->numval);
    assert(len > 0 && len < sizeof (head));
    reply[0].iov_len = len;

    /* write the reply */
    len = retry_writev(conn->fd, reply, nio);
    if (len < 0) {
	logf(conn->pool, "%s: error result from writev: %s (%d)\n",
	     conn->info, strerror(errno), errno);
	return;
    } else if (len == 0) {
	logf(conn->pool, "%s: zero result from writev\n",
	     conn->info);
	return;
    }

    conn->replyok = 1;
}

/* callback to signal authentication successful
 */
static void auth_success(const struct authdata *adat,
			 const struct replaydata *rdat)
{
    const static char replayauth[] = "replayauth ";
    const static char replayuser[] = "replayuser ";
    const static char replaypass[] = "replaypass ";
    const static char errcode[] = "errcode 0\r\n\r\n";
    iovec_t reply[14];
    char head[256];
    unsigned nio = 1;
    unsigned total = 0;
    int len;
    struct conn *conn;

    if (adat == (const struct authdata *)0) return;
    conn = adat->priv;
    if (rdat != (const struct replaydata *)0) {
	if (rdat->authname != (char *)0) {
	    reply[nio].iov_base = (void *) replayauth;
	    total += reply[nio].iov_len = sizeof (replayauth) - 1;
	    reply[nio+1].iov_base = rdat->authname;
	    total += reply[nio+1].iov_len = strlen(rdat->authname);
	    reply[nio+2].iov_base = (void *) crlf;
	    total += reply[nio+2].iov_len = 2;
	    nio += 3;
	    ++conn->numattr, ++conn->numval;
	}
	if (rdat->username != (char *)0) {
	    reply[nio].iov_base = (void *) replayuser;
	    total += reply[nio].iov_len = sizeof (replayuser) - 1;
	    reply[nio+1].iov_base = rdat->username;
	    total += reply[nio+1].iov_len = strlen(rdat->username);
	    reply[nio+2].iov_base = (void *) crlf;
	    total += reply[nio+2].iov_len = 2;
	    nio += 3;
	    ++conn->numattr, ++conn->numval;
	}
	if (rdat->password != (char *)0) {
	    reply[nio].iov_base = (void *) replaypass;
	    total += reply[nio].iov_len = sizeof (replaypass) - 1;
	    reply[nio+1].iov_base = rdat->password;
	    total += reply[nio+1].iov_len = strlen(rdat->password);
	    reply[nio+2].iov_base = (void *) crlf;
	    total += reply[nio+2].iov_len = 2;
	    nio += 3;
	    ++conn->numattr, ++conn->numval;
	}
    }
    reply[nio].iov_base = (void *) errcode;
    total += reply[nio].iov_len = sizeof (errcode) - 1;
    ++conn->numattr, ++conn->numval;
    ++nio;

    /* LDAP attributes, if any */
    if (conn->reply != (char *)0) {
	reply[nio].iov_base = conn->reply;
	total += reply[nio].iov_len = conn->replysize;
	++nio;
    }

    /* format the header */
    reply[0].iov_base = head;
    len = snprintf(head, sizeof (head), "%u %u %u\r\n",
		   total, conn->numattr, conn->numval);
    assert(len > 0 && len < sizeof (head));
    reply[0].iov_len = len;

    /* write the reply */
    len = retry_writev(conn->fd, reply, nio);
    if (len < 0) {
	logf(conn->pool, "%s: error result from writev: %s (%d)\n",
	     conn->info, strerror(errno), errno);
	return;
    } else if (len == 0) {
	logf(conn->pool, "%s: zero result from writev\n",
	     conn->info);
	return;
    }

    conn->replyok = 1;
}

/* callback to set an LDAP-style attribute
 *  this actually copies and formats the data for the reply protocol
 */
static int set_attr(const struct authdata *adat,
		    const char *attrname,
		    const char **values,
		    unsigned numvals)
{
    unsigned j, total, len;
    struct conn *conn;
    char *reply;

    /* validate parameters */
    if (adat == (const struct authdata *)0 ||
	attrname == (const char *)0 ||
	(numvals > 0 && values == (const char **)0)) {
	return (-1);
    }
    conn = (struct conn *) adat->priv;

    /* determine space needed for protocol */
    len = strlen(attrname);
    total = len + 2;
    for (j = 0; j < numvals; ++j) {
	total += strlen(values[j]) + 2;
    }
    if (j == 0) total += 2;

    /* allocate space for protocol */
    reply = realloc(conn->reply, conn->replysize + total);
    if (reply == (char *)0) return (-1);
    conn->reply = reply;
    reply += conn->replysize;

    /* copy attribute/value data into reply buffer */
    memcpy(reply, attrname, len);
    reply[len] = ' ';
    reply += len + 1;
    for (j = 0; j < numvals; ++j) {
	len = strlen(values[j]);
	memcpy(reply, values[j], len);
	reply[len] = '\r';
	reply[len+1] = '\n';
	reply += len + 2;
    }
    if (j == 0) {
	reply[0] = '\r';
	reply[1] = '\n';
	reply += 2;
    }
    *reply = '\0';
    conn->replysize = reply - conn->reply;
    ++conn->numattr;
    conn->numval += numvals;
}

/* callback to get an LDAP-style attribute
 */
static const char **get_attr(const struct authdata *adat,
			     const char *attrname,
			     unsigned *numvals)
{
    struct conn *conn;
    char **dptr, **dbase;

    /* validate parameters */
    if (adat == (const struct authdata *)0 || attrname == (const char *)0) {
	return ((const char **)0);
    }

    /* loop through LDAP attributes looking for a match */
    conn = (struct conn *) adat->priv;
    dptr = conn->data;
    while (*dptr != (char *)0) {
	dbase = dptr++;
	while (*dptr != (char *)0) ++dptr;
	if (strcasecmp(attrname, *dbase++) == 0) {
	    if (numvals != (unsigned *)0) {
		*numvals = dptr - dbase;
	    } else if (dptr - dbase > 1) {
		break;
	    }
	    return ((const char **) dbase);
	}
	++dptr;
    }

    return ((const char **)0);
}

/* parse an integer from a buffer
 *  returns -1 on error, 0 on success
 */
int parseint(char **bptr, const char *end, int *val)
{
    char *scan = *bptr;
    
    *val = 0;
    while (scan < end && *scan >= '0' && *scan <= '9') {
        *val = *val * 10 + (*scan - '0');
        ++scan;
    }
    if (scan == *bptr) return (-1);
    if (scan < end && *scan++ != ' ') return (-1);
    *bptr = scan;

    return (0);
}

/* parse an IP address string and port pair into a sockaddr_in
 *  NOTE: needs updating for IPv6
 */
void parse_addr(char *str, struct sockaddr_in *addr)
{
    char *split;

    split = strchr(str, ' ');
    if (split != (char *)0) *split = '\0';
    addr->sin_addr.s_addr = inet_addr(str);
    if (split != (char *)0) {
	addr->sin_port = htons(atoi(split + 1));
	*split = ' ';
    }
}

/* handle payload of a request
 *  returns -1 on serious parse error
 */
int handle_payload(struct conn *conn, char *dat, int dsize,
                   int numattr, int numval)
{
    char **dptr, **dbase;
    char *attr, *start, *scan, *datend, *dupcheck, *seclevel;
    int valnum, attnum = 1;
    int ldap_attr = 0, permit_multi;
    struct authdata adat;
    struct sockaddr_in laddr, raddr;
    
    /* initialize authentication data */
    memset(&adat, 0, sizeof (adat));
    memset(&laddr, 0, sizeof (laddr));
    memset(&raddr, 0, sizeof (raddr));
    adat.raddr = (struct sockaddr *) &raddr;
    adat.laddr = (struct sockaddr *) &laddr;

    /* set up pointer to handle attribute/value lists */
    dptr = dbase = conn->data;
    datend = dat + dsize;
    scan = dat;

    while (scan < datend) {
        /* validate attribute count */
        if (numattr-- == 0) {
            logf(conn->pool, "%s: too many attributes found: %d\n",
                 conn->info, attnum);
            return (-1);
        }
        
        /* parse attribute */
        attr = scan;
        while (scan < datend && *scan >= 0x21 && *scan <= 0x7E) {
            ++scan;
        }
        if (scan == datend || *scan != ' ' || attr == scan) {
            logf(conn->pool, "%s: Parse error at attribute %d\n",
                 conn->info, attnum);
            return (-1);
        }
        *scan = '\0';
        ++scan;
        *dptr++ = attr;

        /* loop over values */
        valnum = 1;
        do {
            /* validate value count */
            if (numval-- == 0) {
                logf(conn->pool,
                     "%s: too many values found at attribute %d value %d\n",
                     conn->info, attnum, valnum);
                return (-1);
            }

            /* parse value */
            start = scan;
            while (scan < datend && *scan != '\0'
                   && *scan != '\r' && *scan != '\n') {
                ++scan;
            }
            if (scan == datend || *scan != '\r'
                || scan + 1 == datend || scan[1] != '\n') {
                logf(conn->pool, "%s: Parse error at attribute %d value %d\n",
                     conn->info, attnum, valnum);
                return (-1);
            }
            *scan = '\0';
            scan += 2;
            *dptr++ = start;
            ++valnum;
        } while (scan < datend && *scan == ' ' && ++scan < datend);
        ++attnum;
        *dptr++ = (char *)0;

        /* check for defined attributes */
        if (ldap_attr == 0) {
            permit_multi = 1;
            dupcheck = (char *)0;
            switch (*attr) {
              case 'a':
                if (strcmp(attr, "authname") != 0) break;
                dupcheck = adat.authname;
                adat.authname = dbase[1];
                permit_multi = 0;
                break;

              case 'l':
                if (strcmp(attr, "localaddr") == 0) {
		    dupcheck = adat.localaddr;
		    adat.localaddr = dbase[1];
		    permit_multi = 0;
		    parse_addr(adat.localaddr, &laddr);
                } else if (strcmp(attr, "lang") == 0) {
                    dupcheck = adat.lang;
                    adat.lang = dbase[1];
                    permit_multi = 0;
                }
                break;

              case 'p':
                if (strcmp(attr, "password") != 0) break;
                dupcheck = adat.password;
                adat.password = dbase[1];
                permit_multi = 0;
                break;

              case 'r':
                if (strcmp(attr, "remoteaddr") != 0) break;
		dupcheck = adat.remoteaddr;
		adat.remoteaddr = dbase[1];
		permit_multi = 0;
		parse_addr(adat.remoteaddr, &raddr);
                break;
                
              case 's':
                if (strcmp(attr, "saslmech") == 0) {
                    dupcheck = adat.saslmech;
                    adat.saslmech = dbase[1];
                    permit_multi = 0;
                } else if (strcmp(attr, "seclevel") == 0) {
                    dupcheck = seclevel;
                    seclevel = dbase[1];
                    adat.seclevel = atoi(seclevel);
                    permit_multi = 0;
                } else if (strcmp(attr, "service") == 0) {
                    dupcheck = adat.service;
                    adat.service = dbase[1];
                    permit_multi = 0;
                }
                break;

              case 'u':
                if (strcmp(attr, "username") != 0) break;
                dupcheck = adat.username;
                adat.username = dbase[1];
                permit_multi = 0;
                break;
            }

	    if (dupcheck != (char *)0) {
		logf(conn->pool, "%s: duplicate attribute '%s'\n",
		     conn->info, attr);
		return (-1);
	    }
	    if (permit_multi == 0 && dbase[2] != (char *)0) {
		logf(conn->pool,
		     "%s: attribute '%s' must not be multi-valued\n",
		     conn->info, attr);
		return (-1);
	    }
        }
        
        /* don't save defined attributes */
        if (ldap_attr == 0) dptr = dbase;

        /* check for blank line between defined and LDAP attributes */
        if (ldap_attr == 0 && scan < datend && *scan == '\r'
            && scan + 1 < datend && scan[1] == '\n') {
            ldap_attr = 1;
            scan += 2;
        }
    }
    *dptr = '\0';

    if (ldap_attr == 0) {
	logf(conn->pool,
	     "%s: missing blank line between defined and LDAP attributes\n",
	     conn->info);
	return (-1);
    }
    if (numattr > 0) {
	logf(conn->pool, "%s: too few attributes found %d of %d expected\n",
	     conn->info, attnum + numattr, numattr);
	return (-1);
    }
    if (numval > 0) {
	logf(conn->pool, "%s: too few values found; expected %d additional\n",
	     conn->info, numval);
	return (-1);
    }

    /* prepare for reply */
    conn->reply = (char *)0;
    conn->replysize = 0;
    conn->replyok = 0;
    conn->numattr = 0;
    conn->numval = 0;
    adat.version = AUTHDATA_VERSION;
    adat.priv = conn;
    adat.raddrsz = sizeof (raddr);
    adat.laddrsz = sizeof (laddr);
    laddr.sin_family = AF_INET;
    raddr.sin_family = AF_INET;
    adat.get_attr = get_attr;
    adat.set_attr = set_attr;
    adat.auth_success = auth_success;
    adat.auth_fail = auth_fail;
    if (adat.saslmech == (char *)0) adat.saslmech = "PLAIN";

    /* call custom authentication handler */
    authdat_handler(conn->pool->context, &adat);

    /* cleanup reply storage */
    if (conn->reply != (char *)0) {
	free(conn->reply);
	conn->reply = (char *)0;
    }
    
    return (conn->replyok ? 0 : -1);
}

const char GREETING[] = "version sample-authserver-v1.0\r\n";
#define GREETATTR 1
#define GREETVAL  1

/* handle an authentication server connection
 */
void handle_connection(struct conn *conn)
{
    int len, outlen, used, headlen, bufsize, err;
    int dsize, numattr, numval;
    unsigned datasize;
    char *endline, *scan, *bufend;

    /* write greeting */
    len = snprintf(conn->buf, sizeof (conn->buf), "authserver %u %u %u\r\n%s",
                   sizeof (GREETING) - 1, GREETATTR, GREETVAL, GREETING);
    assert(len > 0 && len < sizeof (conn->buf));
    do {
        outlen = write(conn->fd, conn->buf, len);
    } while (outlen == -1 && errno == EINTR);
    if (outlen == -1) {
        fprintf(stderr, "%s: greeting write error: %s (%d)\n",
                conn->info, strerror(errno), errno);
        return;
    }
    if (outlen < len) {
        fprintf(stderr, "%s: greeting short write: %d of %d\n",
                conn->info, outlen, len);
        return;
    }

    /* handle requests */
    used = 0;
    for (;;) {
        len = read(conn->fd, conn->buf + used, sizeof (conn->buf) - used);
        if (len == 0) return;   /* normal completion */
        if (len == -1) {
            if (errno == EINTR) continue;
            logf(conn->pool, "%s: read error: %s (%d)\n",
                    conn->info, strerror(errno), errno);
            return;
        }
        
        /* check for complete line */
        used += len;
        bufend = conn->buf + used;
        for (endline = conn->buf; endline < bufend; ++endline) {
            /* endline[1] test is safe due to sentinel */
            if (endline[0] == '\r' && endline[1] == '\n')
                break;
        }
        if (endline == bufend) {
            if (used == sizeof (conn->buf)) {
                logf(conn->pool, "%s: attempt to overwrite buffer\n",
                        conn->info);
                return;
            }
            continue;
        }

        /* parse header */
        scan = conn->buf;
        if (parseint(&scan, endline, &dsize) == -1
            || parseint(&scan, endline, &numattr) == -1
            || parseint(&scan, endline, &numval) == -1) {
            logf(conn->pool, "%s: invalid header: %.*s\n",
                    conn->info, endline - conn->buf, conn->buf);
            return;
        }

        /* validate header */
        if (dsize == 0
            || dsize > MAX_PAYLOAD
            || dsize < numattr * 4
            || numattr == 0
            || numattr > MAX_ATTRS
            || numval > MAX_VALS
            || numval < numattr) {
            logf(conn->pool, "%s: invalid header %d %d %d\n",
                 conn->info, dsize, numattr, numval);
            return;
        }

        /* determine and allocate space for the payload */
        datasize = (numattr * 2 + numval + 1) * sizeof (void *);
        if (sizeof (conn->buf) - headlen <= dsize) datasize += dsize;
        if (conn->datasize < datasize) {
            conn->data = realloc(conn->data, datasize);
            if (conn->data == (void *)0) {
                conn->datasize = 0;
                logf(conn->pool, "%s: out of memory\n", conn->info);
                return;
            }
            conn->datasize = datasize;
        }

        /* set up the buffer for the payload */
        headlen = endline + 2 - conn->buf;
        if (sizeof (conn->buf) - headlen <= dsize) {
            scan = (char *) (((void **) conn->data) + numattr * 2 + numval);
            bufsize = dsize;
            if (used > headlen) {
                memcpy(scan, conn->buf + headlen, used - headlen);
                used -= headlen;
            } else {
                used = 0;
            }
        } else {
            scan = conn->buf + headlen;
            used -= headlen;
            bufsize = sizeof (conn->buf) - headlen;
        }

        /* fill in the payload buffer if necessary */
        while (used < dsize) {
            len = read(conn->fd, scan + used, bufsize - used);
            if (len == 0) {
                logf(conn->pool,
                     "%s: connected closed in payload %d %d %d\n",
                     conn->info, dsize, numattr, numval);
                return;
            }
            if (len == -1) {
                if (errno == EINTR) continue;
                logf(conn->pool,
                     "%s: read error in payload %d %d %d: %s %d\n",
                     conn->info, dsize, numattr, numval,
                     strerror(errno), errno);
                return;
            }
            used += len;
        }

        /* parse the payload */
        err = handle_payload(conn, scan, dsize, numattr, numval);

        /* if there was an error, drop the connection */
        if (err < 0) return;

        /* it's possible we read multiple requests into the default buffer */
        if (used > dsize) {
            used -= dsize;
            memmove(conn->buf, conn->buf + headlen + dsize, used);
        } else {
            used = 0;
        }
    }
}

/* thread pool handler
 *  Since threads are started on an as-needed basis, the first thing
 *  this does is attempt to pull a connection from the ready list.
 *  If no connection is ready or a connection is completed, this
 *  waits for a new connection to be ready.
 */
void *threadpool_work(void *arg)
{
    struct threadpool *pool = arg;
    struct conn *conn;

    pool_lock(pool);
    ++pool->threads_total;
    DODEBUG((pool, "Thread id %u started\n", (unsigned int) pthread_self()));
    while (!pool->exit_flag) {
        /* pull the connection from the list */
        conn = pool->head;
        if (conn != (struct conn *)0) {
            pool->head = conn->next;
            if (conn->next == (struct conn *)0) {
                pool->tail = (struct conn *)0;
            }

            /* handle the connection */
            pool_unlock(pool);
            DODEBUG((pool, "Thread id %u got connection %d\n",
                     (unsigned int) pthread_self(), conn->fd));
            handle_connection(conn);
            close(conn->fd);
            conn->fd = -1;
            pool_lock(pool);

            /* push connection context on unused list */
            conn->next = pool->unused;
            pool->unused = conn;

            /* dispose connection buffer for idle connections */
            conn = conn->next;
            if (conn != (struct conn *)0 && conn->data != (void *)0) {
                /* wipe potentially sensitive information from memory */
                memset(conn->data, 0, conn->datasize);
                free(conn->data);
                conn->data = (void *)0;
                conn->datasize = 0;
            }
        }
        if (pool->exit_flag) break;

        /* wait for a connection */
        if (pool->threads_waiting++ == 0
         && pool->threads_total >= MAX_CONCURRENT_CONNECTIONS) {
            /* signal if we're the first ready have a backlog */
            pool_signal(pool);
        }
        pool_wait(pool);
        --pool->threads_waiting;
    }
    DODEBUG((pool, "Thread id %u exit\n", (unsigned int) pthread_self()));
    --pool->threads_total;
    pool_unlock(pool);

    return ((void *)0);
}

/* connection accept loop
 */
void do_accept(int sock, struct threadpool *pool)
{
    struct sockaddr_in addr;
    int newsock;
    int err;
    int addrlen;
    struct conn *newconn;
    pthread_t threadid;
    pthread_attr_t tattr;

    newconn = (struct conn *)0;
    for (;;) {
        /* accept new connection */
	addrlen = sizeof (addr);
        newsock = accept(sock, (struct sockaddr *) &addr, &addrlen);
        if (newsock < 1) {
            logf(pool, "accept: %s (%d)\n", strerror(errno), errno);
            sleep(1);
            continue;
        }

        /* allocate space for connection, if needed */
        while (newconn == (struct conn *)0) {
            newconn = (struct conn *) malloc(sizeof (struct conn));
            if (newconn == (struct conn *)0) {
                logf(pool, "malloc: out of memory; sleeping\n");
                sleep(1);
                continue;
            }
            newconn->data = (void *)0;
            newconn->datasize = 0;
        }
        newconn->fd = newsock;
        newconn->pool = pool;
        newconn->next = (struct conn *)0;
        newconn->sentinel = '\0';

        /* set connection information */
        snprintf(newconn->info, sizeof (newconn->info),
                 "%d %d.%d.%d.%d %d",
                 newsock,
		 ((char *) &addr.sin_addr.s_addr)[0],
		 ((char *) &addr.sin_addr.s_addr)[1],
		 ((char *) &addr.sin_addr.s_addr)[2],
		 ((char *) &addr.sin_addr.s_addr)[3],
                 ntohs(addr.sin_port));

        /* access the thread pool */
        pool_lock(pool);

        /* add to end of ready list */
        if (pool->tail == (struct conn *)0) {
            pool->head = pool->tail = newconn;
        } else {
            pool->tail->next = newconn;
            pool->tail = newconn;
        }
        DODEBUG((pool, "%s: accepted\n", newconn->info));

        /* wait for completion if necessary */
        if (pool->threads_waiting == 0
         && pool->threads_total >= MAX_CONCURRENT_CONNECTIONS) {
            DODEBUG((pool, "Max connections reached, waiting\n"));
            pool_wait(pool);
        }

        /* create a thread if necessary */
        while (pool->threads_waiting == 0) {
	    /* create detached threads to save resources */
	    err = pthread_attr_init(&tattr);
	    if (err != 0) {
		logf(pool, "pthread_attr_init: %s (%d)\n",
		     strerror(err), err);
		pool_wait(pool);
		continue;
	    }
	    err = pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
	    if (err != 0) {
		logf(pool, "pthread_attr_setdetachstate: %s (%d)\n",
		     strerror(err), err);
		pool_wait(pool);
		continue;
	    }

	    /* create a thread */
            err = pthread_create(&threadid, &tattr,
				 threadpool_work, (void *) pool);
            if (err == 0) {
                DODEBUG((pool, "New thread created: %u\n",
                         (unsigned int) threadid));
                break;
            }
            logf(pool, "pthread_create: %s (%d)\n",
                    strerror(err), err);
            pool_wait(pool);
        }

        /* reuse an old connection context if possible */
        if (pool->unused != (struct conn *)0) {
            newconn = pool->unused;
            pool->unused = newconn->next;
        }

        pool_unlock(pool);

        /* signal thread that connection is ready */
        pool_signal(pool);
    }
}

main()
{
    struct threadpool pool;
    int sock;
    int on;
    int err;
    struct sockaddr_in addr;

    /* set up connection pool */
    memset(&pool, 0, sizeof (pool));
    pthread_mutex_init(&pool.lock, (pthread_mutexattr_t *)0);
    pthread_mutex_init(&pool.logm, (pthread_mutexattr_t *)0);
    pthread_cond_init(&pool.cond, (pthread_condattr_t *)0);
    
    /* set up address */
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(BINDIP);
    addr.sin_port = htons(BINDPORT);

    /* create socket */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        exit(1);
    }

    /* allow address reuse */
    on = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on));

    /* bind & listen for connections */
    err = bind(sock, (struct sockaddr *) &addr, sizeof (addr));
    if (err < 0) {
        perror("bind");
        exit(1);
    }
    err = listen(sock, LISTEN_QUEUE);
    if (err < 0) {
        perror("listen");
        exit(1);
    }

    /* initialize subsystem */
    err = authdat_init(&pool.context);
    if (err != 0) {
        fprintf(stderr, "FATAL: authdat_init failed\n");
        exit(1);
    }

    /* accept connections */
    do_accept(sock, &pool);

    exit(0);
}
