/* 
 * Copyright (c) 2011 QUALCOMM Incorporated.  All rights reserved.
 * The file license.txt specifies the terms for use, modification,
 * and redistribution.
 *
 * Revisions:
 *    07/12/09   [rg]
 *              - Stop using sys_nerr and sys_errlist.
 *
 *    12/04/07   [rg]
 *              - Updated IPv6 patch (to check for socklen_t)
 *                from Hajimu UMEMOTO,
 *
 *    09/19/06   [rcg]
 *              - Updated IPv6 patch from Hajimu UMEMOTO
 *
 *    08/08/06   [rcg]
 *              - IPv6 patch by Hajimu UMEMOTO
 *
 *    03/09/06   [rcg]
 *              - Now setting SO_KEEPALIVE on socket.
 *
 *     04/25/05  [rcg]
 *              - Fix '-V' for standalone.
 *
 *     12/15/04  [rcg]
 *              - Log facility set on command line now applies to
 *                daemon as well.  [Thanks to Helge Oldach]
 *
 *     07/23/04  [rcg]
 *              - when configured as --with-pam and building for 
 *                Darwin, include <pam/pam_appl.h> instead of 
 *                <security/pam_appl.h> (otherwise build fails);
 *                also work around PAM bug;
 *                also open stdout and stderr write-only instead of read-only
 *                (thanks to Kyle McKay for the patch)
 *
 *     11/27/01  [rcg]
 *              - Worked around a bug (since fixed) in OpenBSD by
 *                setting file descriptor to be blocking *before*
 *                we fork, and ignoring what fcntl() says it is.
 *
 *     04/03/01  [rcg]
 *              - Be a little nicer with "-v".
 *
 *     02/13/01  [rcg]
 *              - Don't abort on weird accept() errors.
 *
 *     02/12/01  [rcg]
 *              - HUP now closes and reopens trace file in standalone mode.
 *
 *     12/21/00  [rcg]
 *              - Fixed option recognition.
 *              - Ignore additional accept() errors, per Stevens 5.11
 *                (thanks to Carles Munyoz for reporting this).
 *
 *     12/06/00  [rcg]
 *              - Ensure daemon terminates on signal, by making listen
 *                file descriptor non-blocking and calling select()
 *                before accept(), since accept() does not return on
 *                signals on some platforms.
 *
 *     11/03/00  [rcg]
 *              - Ignore SIGCHLD on non-BSD systems.
 *              - HUP signal now closes and reopens log.
 *              - Log to specified facility.
 *              - Corrected '<strings.h>' to '<string.h>'
 *
 *     10/10/00  [rcg]
 *              - Delete duplicate '#include <sys/wait.h>'
 *
 *     10/13/00  [rcg]
 *              - Added new TLS options (to avoid getopt errors).
 *
 *     09/29/00  [rcg]
 *              - Revert signals to default in child (avoids conflict
 *                with BSDI authentication).
 *
 *     09/20/00  [rcg]
 *              - Modified wait3() calls to avoid looping before quit,
 *                and handle differences in return values.
 *
 *     08/23/00  [rcg]
 *              - Pass all valid options to getopt, to avoid error messages.
 *
 *     08/21/00  [rcg]
 *              - Now becomes a daemon (unless compiled with _DEBUG).
 *
 *     08/17/00  [rcg]
 *              - Modified to eliminate compiler warnings etc.,
 *              - Can now specify IP address and/or port number
 *                as parameter 1, e.g., 'popper 199.46.50.7:8110 -S'
 *                or 'popper 8110 -S -T600'.  If not specified,
 *                IP address defaults to all available.  The default
 *                port is 110 except when _DEBUG is defined, then 8765.
 *              - Better error messages for common situations.
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/socket.h> /* this needs to be after other .h files */

#include "config.h"
#include "popper.h"
#include "snprintf.h"
#include "logit.h"

#if HAVE_UNISTD_H
#  include <unistd.h>
#endif /* HAVE_UNISTD_H */

#if HAVE_SYS_UNISTD_H
#  include <sys/unistd.h>
#endif /* HAVE_SYS_UNISTD_H */

#ifdef HAVE_SYS_STAT_H
#  include <sys/stat.h>
#endif /* HAVE_SYS_STAT_H */

#ifdef HAVE_FCNTL_H
#  include <fcntl.h>
#endif /* HAVE_FCNTL_H */

#ifdef HAVE_SYS_FCNTL_H
#  include <sys/fcntl.h>
#endif /* HAVE_SYS_FCNTL_H */

#ifdef USE_PAM
#  ifdef    HAVE_PAM_PAM_APPL_H
#    include <pam/pam_appl.h>
#  elif defined HAVE_SECURITY_PAM_APPL_H
#    include <security/pam_appl.h>
#  endif /* HAVE_PAM_PAM_APPL_H */
#endif /* USE_PAM */

#ifndef   HAVE_SOCKLEN_T
typedef unsigned int socklen_t;
#endif /* HAVE_SOCKLEN_T */

#ifdef    INET6
#  include <netdb.h>
#else /* not INET6 */
#  ifndef HAVE_GETADDRINFO
struct addrinfo {
	int	ai_flags;
	int	ai_family;
	int	ai_socktype;
	int	ai_protocol;
	socklen_t ai_addrlen;
	char	*ai_canonname;
	struct	sockaddr *ai_addr;
	struct	addrinfo *ai_next;
};
#  endif /* not HAVE_GETADDRINFO */
#endif /* INET6 */

#ifndef  STANDALONE

/*
 * When not in standalone mode, main() is a jacket for qpopper().
 */
int
main ( int argc, char *argv[] )
{

#ifdef USE_PAM
    {
        /* Workaround a bug where if we wait until after all the daemonizing
         * closing and opening of file handles, etc., pam_start may appear
         * to succeed, but will fail to authenticate the user.
         */
        pam_handle_t    *pamdummy    = NULL;
        struct pam_conv pamdummyconv = {NULL, NULL};
        if ( pam_start ( USE_PAM, NULL, &pamdummyconv, &pamdummy) == PAM_SUCCESS)
            pam_end ( pamdummy, 0 );
    }
#endif /* USE_PAM */

    if ( argc >= 2 && ( strncmp ( argv[1], "-v",  2 ) == 0 ||
                        strncmp ( argv[1], "-V",  2 ) == 0 ||
                        strncmp ( argv[1], "--V", 3 ) == 0 ||
                        strncmp ( argv[1], "--v", 3 ) == 0   ) )
    {
        printf ( "%s%.*s%s version %s (non-standalone)\n",
                 QPOP_NAME,
                 (strlen(BANNERSFX)>0 ? 1 : 0), " ",
                 BANNERSFX,
                 VERSION );
        return 0;
    }
    else
        return qpopper ( argc, argv );
}

#else /* STANDALONE */


#ifdef    INET6
#  ifdef _DEBUG
#    define SERV_TCP_PORT "8765"
#  else
#    define SERV_TCP_PORT  "110"
#  endif /* _DEBUG */
#else
#  ifdef _DEBUG
#    define SERV_TCP_PORT 8765
#  else
#    define SERV_TCP_PORT  110
#  endif /* _DEBUG */
#endif /* INET6 */

#define BAD_ADDR ( (unsigned long) -1 )

/*
 * Be careful using TRACE in an 'if' statement!
 */
#define TRACE if ( debug ) logit


/*
 * System prototypes (functions often missing prototypes in
 * system header files)
 */
#if defined(SOME_OLD_PLATFORM)
void bzero();
pid_t wait3();
#else
#include <strings.h>
#include <sys/wait.h>
#endif


/*
 * Local prototypes
 */
int     main ( int argc, char *argv[] );
void    msg      ( WHENCE, const char *format, ... );
void    err_msg  ( WHENCE, const char *format, ... );
void    err_dump ( WHENCE, const char *format, ... );
void    my_perror   ( void );
char   *sys_err_str ( void );
int     reaper  ( SIGPARAM );
int     hupit   ( SIGPARAM );
int     cleanup ( SIGPARAM );
void    roll_it ( void );
void    motherforker ( int newsockfd, int pollfd[], int npollfd );


/*
 * Prototypes from other Qpopper files
 */
char * sock_ntop ( struct sockaddr *p, int salen );
int    sock_port ( struct sockaddr *p, int salen );


/*
 * Globals
 */
char           *pname       = NULL;
volatile BOOL   bClean      = FALSE;
volatile BOOL   bRollover   = FALSE;
char          **Qargv       = NULL;
int             Qargc       = 0;
BOOL            Qargv_alloc = FALSE;
BOOL            debug       = FALSE;
FILE           *trace_file  = NULL;
char           *trace_name  = NULL;
char            msg_buf [ 2048 ] = "";
FILE           *msg_out     = NULL;
FILE           *err_out     = NULL;


/*
 * In standalone mode, main() is the daemon function.  The port number
 * can be specified as argv[1], e.g., 'popper 110'.
 */
int
main ( int argc, char *argv[] )
{
    int                 sockfd      = -1;
    int                 newsockfd   = -1;
    int                 clilen      =  0;
    int                 i           =  0;
    int                 rslt        =  0;
    char               *ptr         = NULL;
    struct addrinfo     hints, *res, *r;
#ifdef    INET6
    struct sockaddr_storage cli_addr;
    char               *port        = SERV_TCP_PORT;
    char               *addr        = NULL;
    int                 error;
    char                b [ NI_MAXHOST ] = "";
#else
    struct sockaddr_in  cli_addr;
    struct sockaddr_in  serv_addr;
    unsigned short      port        = SERV_TCP_PORT;
    unsigned long       addr        = INADDR_ANY;
#endif /* INET6 */
    int                *pollfd;
    int                 npollfd     =  0;
    int                 maxfd       =  -1;
    fd_set              defreadfds, readfds;
    fd_set              fdset_templ;
    fd_set              fdset_read;
    int                 fd_flags    = 0;
    FILE		*fp;


#ifdef USE_PAM
    {
        /* Workaround a bug where if we wait until after all the daemonizing
         * closing and opening of file handles, etc., pam_start may appear
         * to succeed, but will fail to authenticate the user.
         */
        pam_handle_t    *pamdummy    = NULL;
        struct pam_conv pamdummyconv = {NULL, NULL};
        if ( pam_start ( USE_PAM, NULL, &pamdummyconv, &pamdummy) == PAM_SUCCESS)
            pam_end ( pamdummy, 0 );
    }
#endif /* USE_PAM */

    if ( argc >= 2 && ( strncmp ( argv[1], "-v",  2 ) == 0 ||
                        strncmp ( argv[1], "-V",  2 ) == 0 ||
                        strncmp ( argv[1], "--V", 3 ) == 0 ||
                        strncmp ( argv[1], "--v", 3 ) == 0   ) )
    {
        printf ( "%s%.*s%s version %s (standalone)\n",
                 QPOP_NAME,
                 ( strlen(BANNERSFX) > 0 ? 1 : 0 ), " ",
                 BANNERSFX,
                 VERSION );
        return 0;
    }

    err_out = msg_out  = fopen ( "/dev/null", "w+" ); /* until we get set up */

#ifndef INET6
    /*
     * Ensure default port & address is in network order
     */
    addr = htonl ( addr );
    port = htons ( port );
#endif /* not INET6 */

    /*
     * Set defaults for Qargc and Qargv
     */
    Qargc = argc;
    Qargv = argv;

    /*
     * Get our own name (if provided)
     */
    pname = argv [ 0 ];
    if ( pname != NULL && *pname != '\0' )
    {
        ptr = strrchr ( pname, '/' );
        if ( ptr != NULL && strlen(ptr) > 1 )
            pname = ptr + 1;
    }

    /*
     * The first specified parameter may be an IP address
     * and/or a port number.  If so, this is what we bind
     * to.  Otherwise we use defaults.
     */
    ptr = argv [ 1 ];
    if ( argc >= 2 && ( *ptr == ':' ||
#ifdef    INET6
			*ptr == '[' ||
#endif /* INET6 */
			isdigit ( (int) *ptr ) ) )
    {
#ifndef INET6
        int            j = 0;
        unsigned long  a = addr;
        unsigned short n = port;
        char           b [ 25 ] = "";
#endif /* not INET6 */
        char          *q = b;

        /*
         * We might have an ip address first
         */
        if ( strchr ( ptr, '.' ) != NULL )
            while ( *ptr == '.' || isdigit ( (int) *ptr ) )
                *q++ = *ptr++;
#ifdef    INET6
    else if ( *ptr == '[' && strchr ( ptr + 1, ']' ) != NULL )
    {
        ptr++;
        while ( *ptr != '\0' && *ptr != ']' )
                *q++ = *ptr++;
    }
#endif /* INET6 */
        
        if ( *b != '\0' )
        {
#ifndef INET6
            a   = inet_addr ( b );
#endif /* not INET6 */
            ptr = strchr ( ptr, ':' );
            if ( ptr != NULL )
                ptr++;
        }
        else
        {
            ptr = argv [ 1 ];
            if ( *ptr == ':' )
                ptr++;
        }

#ifdef    INET6
        if ( *b != '\0' )
            addr = b;
        if ( ptr != NULL )
            port = ptr;
        memset ( &hints, 0, sizeof(hints) );
        hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
        hints.ai_family = PF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        if ( getaddrinfo ( addr, port, &hints, &res ) != 0 )
                err_dump ( HERE, "invalid address and/or port: \"%s\"", argv[1] );
        freeaddrinfo ( res );
#else /* not INET6 */
        /*
         * We might have a port number
         */
        if ( ptr != NULL )
            j = atoi ( ptr );

        if ( a == BAD_ADDR || j == 0 || j > USHRT_MAX )
            err_dump ( HERE, "invalid address and/or port: \"%s\"", argv[1] );
        n = j;

        port = htons ( n );
        addr = a;
#endif
        
        /*
         * Since we consumed the first specified parameter,
         * create our own argv that omits it, to pass on to
         * Qpopper.
         */
        rslt  = sizeof(char *) * argc;
        Qargv = malloc ( rslt );
        if ( Qargv == NULL )
            err_dump ( HERE, "unable to allocate memory" );
        Qargv_alloc = TRUE;
        bzero ( Qargv, rslt );
        Qargv [ 0 ] = argv [ 0 ];
        for ( rslt = 2; rslt < argc; rslt++ )
            Qargv [ rslt - 1 ] = argv [ rslt ];
        Qargc = argc - 1;
    }

    /*
     * Open the log
     */
#ifdef SYSLOG42
    openlog ( pname, 0 );
#else
    openlog ( pname, POP_LOGOPTS, POP_FACILITY );
#endif

    /*
     * See if debug, trace, or log facility options specified
     */
    i = getopt ( Qargc, Qargv, "b:BcCdD:e:f:FkK:l:L:p:RsSt:T:uUvVxy:" );
    while ( i != EOF )
    {
        switch ( i )
        {
            case 'd':
                debug = TRUE;
                break;

            case 't':
                debug = TRUE;
                trace_name = strdup ( optarg );
                trace_file = fopen  ( trace_name, "a" );
                if ( trace_file == NULL )
                    err_dump ( HERE, 
                               "Unable to open trace file \"%.255s\"", 
                               trace_name );
                TRACE ( trace_file, POP_DEBUG, HERE,
                        "Opened trace file \"%.255s\" as %d",
                        trace_name, fileno(trace_file) );
                break;

            case 'y':  /* log facility */
                {
#ifndef    SYSLOG42
                    POP config;         // these are dummy, just to pass to
                    POP *p = &config;   // set_option; we'll do it for real
                                        // later, in pop_init.
                    if ( set_option ( p, "log-facility", 
                                      optarg ) == POP_SUCCESS )
                    {
                        closelog();
                        openlog ( pname, POP_LOGOPTS, p->log_facility );
                    } 
                    else 
                    {
                        fprintf ( stderr, 
                                  "Error setting '-y' to %.255s\n", 
                                  optarg );
                        return 1;
                    }
#endif /* !SYSLOG42 */
                }
                break;


            default:
                break;
        }

        i = getopt ( Qargc, Qargv, "b:BcCdD:e:f:FkK:l:L:p:RsSt:T:uUvVxy:" );
    }
    optind = 1; /* reset for pop_init */

#ifdef _DEBUG
    msg_out = err_out = fopen ( "/dev/tty", "w+" );
    if ( msg_out == NULL )
    {
        msg_out = stdout;
        err_out = stderr;
    }
#else

    /*
     * Become a daemon
     */

    /*
     * First we `fork()' so our parent can exit; this returns control
     * to the command line or shell that invoked us.  This step is
     * required so that the new process is guaranteed not to be a
     * process group leader. The next step, `setsid()', fails if we're
     * a process group leader.
     */
    rslt = fork();
    if ( rslt == -1 )
        err_dump ( HERE, "fork() failed" );
    if ( rslt > 0 )
    {
        TRACE ( trace_file, POP_DEBUG, HERE, 
                "%s: Server: first fork(); child=%i; exiting",
                pname, rslt );
         exit ( 0 ); /* if we're the parent we just go away */
    }
    TRACE ( trace_file, POP_DEBUG, HERE, 
            "%s: Server: child of first fork(); pid=%i",
            pname, getpid() );

    /*
     * Next call `setsid()' to become a process group and session
     * group leader. Since a controlling terminal is associated with
     * a session, and this new session has not yet acquired a
     * controlling terminal our process now has no controlling
     * terminal, which is a Good Thing for daemons.
     */
    rslt = setsid();       /* Disassociate from controlling terminal */
    if ( rslt == -1 )
        err_dump ( HERE, "setsid() failed" );

    /*
     * Now we `fork()' again so the parent, (the session group leader),
     * can exit.  This means that we, as a non-session group leader,
     * can never regain a controlling terminal.
     */
    rslt = fork();
    if ( rslt == -1 )
        err_dump ( HERE, "fork() failed" );
    if ( rslt > 0 )
    {
        TRACE ( trace_file, POP_DEBUG, HERE,
                "%s: Server: second fork(); child=%i; exiting",
                pname, rslt );
         exit ( 0 ); /* if we're the parent we just go away */
    }
    TRACE ( trace_file, POP_DEBUG, HERE,
            "%s: Server: child of second fork(); pid=%i",
            pname, getpid() );

     /*
      * Now we `chdir("/")' to ensure that our process doesn't keep
      * any directory in use.  Failure to do this could make it so
      * that an administrator couldn't unmount a filesystem, because
      * it was our current directory.
      *
      * (Equivalently, we could change to any directory containing
      * files important to our operation.)
      */
    TRACE ( trace_file, POP_DEBUG, HERE, "calling chdir()" );
    rslt = chdir ( "/" );
    if ( rslt == -1 )
        err_dump ( HERE, "chdir(\"/\") failed" );

    /*
     * Normally, a daemon next calls umask(0)' so that it has complete
     * control over the permissions of anything it writes;  we don't
     * know what umask we may have inherited.  However, Qpopper calls
     * umask as it gets going, for safety (since it runs as root for
     * a while).
     */

    /*
     * Next `close()' fds 0, 1, and 2. This releases the standard in,
     * out, and error we inherited from our parent process.  We have
     * no way of knowing where these fds might have been redirected
     * to.  Note that many daemons use `sysconf()' to determine the
     * limit `_SC_OPEN_MAX'.  `_SC_OPEN_MAX' tells you the maximun
     * open files/process.  Then in a loop, the daemon can close all
     * possible file descriptors.
     */
    TRACE ( trace_file, POP_DEBUG, HERE, 
            "closing file descs %d to 0", 
            sysconf ( _SC_OPEN_MAX )  );
    for ( i = sysconf ( _SC_OPEN_MAX ); i >= 0; i-- )
    {
        if ( debug == FALSE || trace_file == NULL 
                            || i != fileno(trace_file) )
            close ( i );
    }

    /*
     * Finally, establish new open descriptors for stdin, stdout and
     * stderr.  Even if we don't plan to use them, it is still a good
     * idea to have them open.  The precise handling of these is a
     * matter of taste; if you have a logfile, for example, you might
     * wish to open it as stdout or stderr, and open `/dev/null' as
     * stdin; alternatively, you could open `/dev/console' as stderr
     * and/or stdout, and `/dev/null' as stdin, or any other combination
     * that makes sense for your particular daemon.
     */
    i    = open ( "/dev/null",    O_RDONLY );           /* stdin  */
    rslt = open ( "/dev/console", O_WRONLY );           /* stdout */
    rslt = open ( "/dev/console", O_WRONLY );           /* stderr */
    if ( rslt > 0 )
        msg_out = err_out = fdopen ( rslt, "w+" );
    if ( msg_out == NULL )
        msg_out = err_out = fopen ( "/dev/null", "w+" );
    
    TRACE ( trace_file, POP_DEBUG, HERE, 
            "opened stdin=%d; stdout=%d stderr=%d; i=%d; rslt=%d; msg_out=%p",
            fileno(stdin), fileno(stdout), fileno(stderr), i, rslt, msg_out );

#endif /* not _DEBUG */

    memset ( &hints, 0, sizeof(hints) );
    hints.ai_socktype = SOCK_STREAM;
#ifdef INET6
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = PF_UNSPEC;
    error = getaddrinfo ( addr, port, &hints, &res );
    if ( error )
    {
        fprintf ( stderr, "%s\n", gai_strerror(error) );
        return 1;
    }
#else /* not INET6 */
    memset ( &serv_addr, 0, sizeof(serv_addr) );
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = addr;
    serv_addr.sin_port        = port;
    hints.ai_addr = (struct sockaddr *)&serv_addr;
    hints.ai_addrlen = sizeof(serv_addr);
    hints.ai_family = serv_addr.sin_family;
    res = &hints;
#endif /* INET6 */

    /* Count max number of sockets we may open */
    for ( npollfd = 0, r = res; r; r = r->ai_next, npollfd++ )
        ;

    pollfd = malloc ( npollfd * sizeof(int) );
    if ( !pollfd )
    {
        fprintf ( stderr, "couldn't allocate memory for sockets\n" );
        return 1;
    }

    memset ( &fdset_templ, 0, sizeof(fdset_templ) );
    npollfd = 0;   /* num of sockets counter at start of array */
    i = 1;
    for ( r = res; r; r = r->ai_next )
    {
        /*
         * Set up the socket on which we listen
         */
        sockfd = socket ( r->ai_family, r->ai_socktype, r->ai_protocol );
        if ( sockfd < 0 )
            continue;
        TRACE ( trace_file, POP_DEBUG, HERE,
                "opened stream socket; sockfd = %d", sockfd );

        rslt = setsockopt ( sockfd, SOL_SOCKET, SO_REUSEADDR,
                            (char *) &i, sizeof(i) );
        if ( rslt == -1 )
        {
            TRACE ( trace_file, POP_DEBUG, HERE, 
                    "%s: Server: setsockopt(SO_REUSEADDR) failed", pname );
            close ( sockfd );
            continue;
        }

        rslt = setsockopt ( sockfd, SOL_SOCKET, SO_KEEPALIVE,
                            (char *) &i, sizeof(i) );
        if ( rslt == -1 )
        {
            TRACE ( trace_file, POP_DEBUG, HERE,
                    "%s: Server: setsockopt(SO_KEEPALIVE) failed", pname );
            close ( sockfd );
            continue;
        }

#ifdef    IPV6_V6ONLY
        if ( r->ai_family == AF_INET6 )
        {
            rslt = setsockopt ( sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
                                (char *) &i, sizeof(i) );
            if ( rslt == -1 )
            {
                TRACE ( trace_file, POP_DEBUG, HERE, 
                        "%s: Server: setsockopt(IPV6_V6ONLY) failed",
                        pname );
                close ( sockfd );
                continue;
            }
        }
#endif /* IPV6_V6ONLY */

        if ( debug )
        {
            rslt = setsockopt ( sockfd, SOL_SOCKET, SO_DEBUG,
                                (char *) &i, sizeof(i) );
            if ( rslt == -1 )
                TRACE ( trace_file, POP_DEBUG, HERE, 
                        "%s: Server: setsockopt(SO_DEBUG) failed", pname );
        }

        TRACE ( trace_file, POP_DEBUG, HERE,
                "set stream socket options; sockfd = %d", sockfd );

        rslt = bind ( sockfd, r->ai_addr, r->ai_addrlen );
        if ( rslt < 0 )
        {
            if ( errno == EADDRINUSE )
                fprintf ( stderr, "%s:%d in use\n",
                          sock_ntop ( r->ai_addr, r->ai_addrlen ),
                          sock_port ( r->ai_addr, r->ai_addrlen ) );
            else
                fprintf ( stderr, "Can't bind local address %s:%d",
                          sock_ntop ( r->ai_addr, r->ai_addrlen ),
                          sock_port ( r->ai_addr, r->ai_addrlen ) );
            close ( sockfd );
            continue;
        }

        TRACE ( trace_file, POP_DEBUG, HERE,
                "did bind on stream socket; sockfd = %d",
                sockfd );

        /*
         * Now we're ready to go
         */
        msg ( HERE,
              ( r->ai_family == AF_INET6 ) ? "listening on [%s]:%d\n" :
                                             "listening on %s:%d\n",
              sock_ntop ( r->ai_addr, r->ai_addrlen ),
              sock_port ( r->ai_addr, r->ai_addrlen ) );

        TRACE ( trace_file, POP_DEBUG, HERE, "listening using socket fd %d",
                sockfd );

#ifndef SOMAXCONN
#define SOMAXCONN 5
#endif
	listen ( sockfd, SOMAXCONN );

        /*
         * Set file descriptor to be non-blocking in case there isn't really a
         * connection available if the select succeeds.  This avoids us
         * blocking there.
         */
        fd_flags = fcntl ( sockfd, F_GETFL, 0 );
        rslt     = fcntl ( sockfd, F_SETFL, fd_flags | O_NONBLOCK );
        if ( rslt == -1 )
            err_dump ( HERE, "Unable to set sockfd(%d) to be non-blocking",
                       sockfd );
        TRACE ( trace_file, POP_DEBUG, HERE, "set fd %d non-blocking (%#x)",
                sockfd, fcntl ( sockfd, F_GETFL, 0 ) );

        /*
         * Build the template descriptor set
         */
        FD_SET ( sockfd, &fdset_templ );

        if ( maxfd < sockfd )
            maxfd = sockfd;
        pollfd[npollfd++] = sockfd;
    }

#ifdef INET6
    if ( res )
        freeaddrinfo ( res );
#endif /* INET6 */

    if ( npollfd == 0 )
        err_dump ( HERE, "Can't bind to any socket" );

#ifdef    BSD
    signal ( SIGCHLD, VOIDSTAR reaper  );
#else /* not BSD */
    signal ( SIGCHLD, SIG_IGN          ); /* ignore them and they'll go away */
#endif /* BSD */

    signal ( SIGHUP,  VOIDSTAR hupit   );
    signal ( SIGTERM, VOIDSTAR cleanup );

#ifndef PIDFILE
#define        PIDFILE "/var/run/qpopper.pid"
#endif
    fp = fopen(PIDFILE, "w");
    if (fp == NULL) {
      err_dump ( HERE, "Unable to open %s", PIDFILE);
    }
    fprintf(fp, "%d\n", getpid());
    fclose(fp);

    while ( TRUE ) 
    {
        if ( bClean )
        {
            msg   ( HERE, "cleaning up and exiting normally" );
            for ( i = 0; i < npollfd; i++ ) {
                close ( pollfd[i] );
                pollfd[i] = -1;
            }
            if ( trace_file != NULL )
            {
                fclose ( trace_file );
                trace_file = NULL;
            }
            exit  ( 0 );
        }

        if ( bRollover )
        {
            roll_it();
            bRollover = FALSE;
            signal ( SIGHUP,  VOIDSTAR hupit   );
        }

        /*
         * Copy the template descriptor set into our read set (since
         * select() modifies the fd set we pass in).
         */
        fdset_read = fdset_templ;

        /*
         * Check for a new connection with select() before calling accept(),
         * since accept() does not return on signals on some platforms.
         */
        rslt = select ( maxfd + 1, &fdset_read, NULL, NULL, NULL );
        if ( rslt == -1 && errno != EINTR )
            err_dump ( HERE, "select() error" );

        if ( rslt == 0 )
            err_dump ( HERE, "unexpected select() result" ); 

        for ( i = 0; i < npollfd; ++i )
        {
            if ( !FD_ISSET ( pollfd[i], &fdset_read ) )
                continue;

            clilen    = sizeof(cli_addr);
            newsockfd = accept ( pollfd[i], (struct sockaddr *) &cli_addr,
                                 &clilen );

            TRACE ( trace_file, POP_DEBUG, HERE, 
                    "accept=%d; sockfd=%d; clilen=%d; cli_addr=%s:%d\n",
                    newsockfd, pollfd[i], clilen, 
                    sock_ntop ( (struct sockaddr *) &cli_addr, clilen ),
                    sock_port ( (struct sockaddr *) &cli_addr, clilen ) );

            if ( newsockfd > 0 )
                motherforker ( newsockfd, pollfd, npollfd ); 
            else
                if ( errno != EINTR && errno != EWOULDBLOCK 
                                    && errno != EPROTO
                                    && errno != ECONNABORTED )
                    err_msg ( HERE, "accept() error" );
        }

    } /* main loop */

    return 0;
}


/*
 * Logs a message
 */
void
msg ( WHENCE, const char *format, ... )
{
    va_list  ap;                       /* Pointer into stack to extract
                                        *     parameters */
    int   left   = sizeof(msg_buf);
    int   len    = 0;
    int   iChunk = 0;


    va_start ( ap, format );

    if ( pname != NULL )
    {
        iChunk = Qsnprintf ( msg_buf + len, left, "%s: ", pname );
        left  -= ( iChunk > 0 ? iChunk : left );
        len   += ( iChunk > 0 ? iChunk : left );
    }

    iChunk = Qsnprintf ( msg_buf + len, left, "Server: " );
    left  -= ( iChunk > 0 ? iChunk : left );
    len   += ( iChunk > 0 ? iChunk : left );

    iChunk = Qvsnprintf ( msg_buf + len, left, format, ap );
    left  -= ( iChunk > 0 ? iChunk : left );
    len   += ( iChunk > 0 ? iChunk : left );

    va_end   ( ap );

    fprintf ( msg_out, "%s\n", msg_buf );
    logit   ( trace_file, POP_DEBUG, fn, ln, "%s", msg_buf );
    fflush  ( msg_out );
}


/*
 * Logs a message, dumps core, terminates.
 */
void
err_dump ( WHENCE, const char *format, ... )
{
    va_list  ap;                       /* Pointer into stack to extract
                                        *     parameters */
    int   left   = sizeof(msg_buf);
    int   len    = 0;
    int   iChunk = 0;


    va_start ( ap, format );

    if ( pname != NULL )
    {
        iChunk = Qsnprintf ( msg_buf + len, left, "%s: ", pname );
        left  -= ( iChunk > 0 ? iChunk : left );
        len   += ( iChunk > 0 ? iChunk : left );
    }

    iChunk = Qsnprintf ( msg_buf + len, left, "Server: " );
    left  -= ( iChunk > 0 ? iChunk : left );
    len   += ( iChunk > 0 ? iChunk : left );

    iChunk = Qvsnprintf ( msg_buf + len, left, format, ap );
    left  -= ( iChunk > 0 ? iChunk : left );
    len   += ( iChunk > 0 ? iChunk : left );

    iChunk = Qsnprintf ( msg_buf + len, left, ": %s [%s:%zu]",
                         sys_err_str(), fn, ln );
    left  -= ( iChunk > 0 ? iChunk : left );
    len   += ( iChunk > 0 ? iChunk : left );

    va_end   ( ap );

    fprintf ( err_out, "%s\n", msg_buf );
    logit   ( trace_file, POP_PRIORITY, fn, ln, "%s", msg_buf );

    if ( Qargv_alloc )
    {
        free ( Qargv );
        Qargv = NULL;
    }

    if ( trace_file != NULL )
    {
        fclose ( trace_file );
        trace_file = NULL;
    }

    fflush ( err_out );

    abort();
    exit ( 1 );
}


/*
 * Logs a message, with error information automatically appended.
 */
void
err_msg ( WHENCE, const char *format, ... )
{
    va_list  ap;                       /* Pointer into stack to extract
                                        *     parameters */

    int   left   = sizeof(msg_buf);
    int   len    = 0;
    int   iChunk = 0;


    va_start ( ap, format );

    if ( pname != NULL )
    {
        iChunk = Qsnprintf ( msg_buf + len, left, "%s: ", pname );
        left  -= ( iChunk > 0 ? iChunk : left );
        len   += ( iChunk > 0 ? iChunk : left );
    }

    iChunk = Qsnprintf ( msg_buf + len, left, "Server: " );
    left  -= ( iChunk > 0 ? iChunk : left );
    len   += ( iChunk > 0 ? iChunk : left );

    iChunk = Qvsnprintf ( msg_buf + len, left, format, ap );
    left  -= ( iChunk > 0 ? iChunk : left );
    len   += ( iChunk > 0 ? iChunk : left );

    va_end   ( ap );

    iChunk = Qsnprintf ( msg_buf + len, left, ": %s [%s:%zu]",
                         sys_err_str(), fn, ln );
    left  -= ( iChunk > 0 ? iChunk : left );
    len   += ( iChunk > 0 ? iChunk : left );

    fprintf ( err_out, "%s\n", msg_buf );
    logit   ( trace_file, POP_PRIORITY, fn, ln, "%s", msg_buf );
    fflush  ( err_out );
}


void
my_perror()
{
    fprintf ( stderr, "%s\n", sys_err_str() );
}


char *
sys_err_str()
{
    static char msgstr [ 200 ];
    if ( errno != 0 )
    {
        if ( errno > 0 )
            Qsnprintf ( msgstr, sizeof(msgstr), "(%d) %s", 
                        errno, STRERROR ( errno ) );
        else
            Qsnprintf ( msgstr, sizeof(msgstr), "errno = %d", errno );

        msgstr [ sizeof(msgstr) -1 ] = '\0';
    }
    else
        msgstr[0] = '\0';
    
    return ( msgstr );
}


int
reaper ( SIGPARAM )
{
    int   stts;
    pid_t child_pid = 0;


    TRACE  ( trace_file, POP_DEBUG, HERE, "reaper signal handler called" );

    do
    {
        child_pid = wait3 ( &stts, WNOHANG, (struct rusage *) 0 );
        TRACE  ( trace_file, POP_DEBUG, HERE, 
                 "...wait3() returned %d; error: %s (%d) ",
                 child_pid,
                 ( child_pid > 0 ? "" : STRERROR(errno) ), 
                 ( child_pid > 0 ?  0 : errno ) );
    }
        while  ( child_pid > 0 );

    signal ( SIGCHLD, VOIDSTAR reaper );
    return 0;
}


int
cleanup ( SIGPARAM )
{
    int   stts;
    pid_t child_pid = 0;


    TRACE  ( trace_file, POP_DEBUG, HERE, "cleanup signal handler called" );

    while  ( child_pid > 0 )
    {
        child_pid = wait3 ( &stts, WNOHANG, (struct rusage *) 0 );
        TRACE  ( trace_file, POP_DEBUG, HERE, 
                 "...wait3() returned %d; errno=%d",
                 child_pid, errno );
    }

    bClean = TRUE;
    return 0;
}


int
hupit ( SIGPARAM )
{
    TRACE  ( trace_file, POP_DEBUG, HERE, "hupit signal handler called" );
    signal ( SIGHUP, SIG_IGN );
    bRollover = TRUE;
    return 0;
}


void
roll_it ( void )
{
    TRACE  ( trace_file, POP_DEBUG, HERE, "rolling over log..." );

    closelog();

#ifdef SYSLOG42
    openlog ( pname, 0 );
#else
    openlog ( pname, POP_LOGOPTS, LOG_DAEMON /*POP_FACILITY*/ );
#endif

    if ( trace_file != NULL && trace_name != NULL )
    {
        TRACE  ( trace_file, POP_DEBUG, HERE, "rolling over trace file..." );
        fflush ( trace_file );
        fclose ( trace_file );

        trace_file = fopen ( trace_name, "a" );
        if ( trace_file == NULL )
            err_dump ( HERE, "Unable to open trace file '%s'", trace_name );

        TRACE ( trace_file, POP_DEBUG, HERE, "Opened trace file \"%s\" as %d",
                trace_name, fileno(trace_file) );
    }

}


/*
 * Handles new client connection
 */
void
motherforker ( int newsockfd, int pollfd[], int npollfd )
{
    int     childpid    = 0;
    int     fd_flags    = 0;
    int     rslt        = 0;
    int     i;


    TRACE ( trace_file, POP_DEBUG, HERE, "new connection; fd=%d", newsockfd );

    /*
     * Make sure we pass a blocking socket to Qpopper.  Do this before we
     * fork, and even if fcntl() insists the socket is already blocking,
     * because of a bug in older versions of OpenBSD.
     */
    fd_flags = fcntl ( newsockfd, F_GETFL, 0 );
    TRACE ( trace_file, POP_DEBUG, HERE, "newsockfd (%d) flags: %#x",
            newsockfd, fd_flags );
    rslt = fcntl ( newsockfd, F_SETFL, fd_flags & ~O_NONBLOCK );
    if ( rslt == -1 )
        err_dump ( HERE, "Unable to set newsockfd (%d) to be blocking",
                   newsockfd );
    TRACE ( trace_file, POP_DEBUG, HERE, "set fd %d blocking (%#x)",
            newsockfd, fcntl ( newsockfd, F_GETFL, 0 ) );

#ifndef _DEBUG
    childpid = fork();
    if ( childpid < 0 )
        err_dump ( HERE, "fork() error" );
    
    else if ( childpid == 0 )
    { /* I'm the child */
        TRACE ( trace_file, POP_DEBUG, HERE, "new child for connection" );

        /*
         * Children should not trap signals
         */
        signal ( SIGCHLD, SIG_DFL );
        signal ( SIGTERM, SIG_DFL );
        signal ( SIGHUP,  SIG_DFL );

        /*
         * We don't need sockfd
         */
	for ( i = 0; i < npollfd; ++i ) {
	    close ( pollfd[i] );
	    pollfd[i] = -1;
	}

#endif /* not _DEBUG */

        dup2    ( newsockfd, 0 );
        dup2    ( newsockfd, 1 );
        dup2    ( newsockfd, 2 );
        close   ( newsockfd    );
        newsockfd = -1;
        qpopper ( Qargc, Qargv );
            
#ifdef _DEBUG
	for ( i = 0; i < npollfd; ++i ) {
	    close ( pollfd[i] );
	    pollfd[i] = -1;
	}
#endif /* not _DEBUG */

        TRACE ( trace_file, POP_DEBUG, HERE, "exiting after Qpopper returned" );

        if ( Qargv_alloc )
        {
            free ( Qargv );
            Qargv = NULL;
        }

        if ( trace_file != NULL )
        {
            fclose ( trace_file );
            trace_file = NULL;
        }

        _exit ( 0 );

#ifndef _DEBUG
    } /* I'm the child */
    else
    { /* I'm the parent */
        TRACE ( trace_file, POP_DEBUG, HERE, "forked() for new connection; pid=%d",
                childpid );
        close ( newsockfd );
        newsockfd = -1;
    } /* I'm the parent */
#endif /* not _DEBUG */
}


#endif /* STANDALONE */
