src/nntpcache.c

/* [<][>]
[^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following functions.
  1. bigToStr
  2. settaskinfo
  3. task_info_init
  4. task_info_new
  5. task_info_free
  6. sigterm
  7. sigint
  8. sigsegv
  9. sigusr1
  10. sigusr2
  11. sighup
  12. sigalrm
  13. set_client_sigset
  14. check_child
  15. sigchld
  16. sigpipe
  17. ncExit
  18. make_vm_proc
  19. retire_vm_proc
  20. load_config
  21. decomment
  22. load_groups
  23. set_cfg_shm
  24. load_servers
  25. findScfg
  26. perform_chroot
  27. usage
  28. drop_priv
  29. drop_idle_servers
  30. emit_banner
  31. relay_unknown
  32. client_cmd
  33. client_cmd_loop
  34. client_handler
  35. createPort
  36. master_loop
  37. detach
  38. main
  39. calloc_dummy

/* $Id: nntpcache.c,v 1.19 2002/04/04 11:09:27 proff Exp $
 * $Copyright$
 */

#include "nglobal.h"
#include "network.h"

#include "dbz.h"
#include "mmalloc.h"

#include "acc.h"
#include "article.h"
#include "authinfo.h"
#include "authinfo_radius.h"
#include "build_history.h"
#include "date.h"
#include "debug.h"
#include "expire.h"
#include "group.h"
#include "http.h"
#include "ihave.h"
#include "ipc.h"
#include "mmap.h"
#include "newgroups.h"
#include "newnews.h"
#include "next.h"
#include "nocem.h"
#include "post.h"
#include "xover.h"
#include "xpath.h"

#include "nntpcache.h"

extern char *optarg;

EXPORT bool volatile HoldForks = FALSE;
EXPORT bool volatile HoldForksClient = FALSE;
EXPORT bool volatile HoldForksHttp = FALSE;
EXPORT bool volatile HoldForksUpdate = FALSE;
EXPORT bool volatile HoldForksNocem = FALSE;
EXPORT bool SwapWithChild = FALSE;
EXPORT struct nnconf *con = &nnconf;
EXPORT struct strStack *slaveClient;
EXPORT bool f_cleanSlate = TRUE;
EXPORT time_t ClientTimeStarted;
EXPORT struct newsgroup *CurrentGroupNode = {0};
EXPORT char CurrentDir[MAX_PATH];
EXPORT int CurrentGroupArtNum;
EXPORT int CurrentGroupArtRead;

EXPORT bool GroupNextNoCache = FALSE;   /* set by post.c to force a one-shot cache miss on the next GROUP command */
EXPORT bool CurrentGroupXoverIsFilt;
EXPORT bool CurrentGroupNocem;
EXPORT struct authent *CurrentGroupAuth;
EXPORT struct authent *ConnectAuth;
EXPORT struct strList *overviewFmt;
EXPORT n_u32 overviewFmt_hash;
EXPORT struct strList *overviewFmtBozo;
EXPORT n_u32 overviewFmtDef_hash;
EXPORT big_t ClientBytes;
EXPORT char ClientHost[128 + 1 + MAX_HOST];
EXPORT char ClientHostNormal[MAX_HOST];
EXPORT char ClientHostLocal[MAX_HOST];
EXPORT char ClientHostRFC931[128 + 1 + MAX_HOST];
EXPORT char ClientHostLocalRFC931[128 + 1 + MAX_HOST];
EXPORT char ClientHostAddr[MAX_HOST];
EXPORT char ClientHostAddrRFC931[128 + 1 + MAX_HOST];
EXPORT struct sockaddr_in ClientRemoteAddr;
EXPORT char *RemoteHosts[] =
{
        ClientHost, ClientHostNormal, ClientHostLocal, ClientHostRFC931, ClientHostLocalRFC931,
        ClientHostAddr, ClientHostAddrRFC931, NULL
};

EXPORT char Host[MAX_HOST];
EXPORT bool ModeReader = FALSE; /* ModeReader sent */
EXPORT struct server_cfg *ServerList = NULL;
EXPORT struct group_cfg *GroupList = NULL;
EXPORT struct server_cfg *CurrentGroupScfg;
EXPORT struct server_cfg *CurrentIDScfg;
EXPORT enum auth_state AuthState = none;
EXPORT bool MakeHistory = FALSE;
EXPORT void *Mbase;
EXPORT bool mmapAnon = FALSE;
static int NNTPportFD = -1;
static int HTTPportFD = -1;
static char PidFile[MAX_PATH] = "";
EXPORT int Master_fd = -1;
EXPORT int Watch_fd = -1;
EXPORT int Debug_fd = 2;
EXPORT fd_set r_set;            /* master client fd list */
static int volatile high_fd;    /* highest fd in r_set */
static int ncUID;
static int ncGID;
static struct sigaction myaction;
static struct task_info dummy_task;     /* this is for oneshots */
EXPORT struct task_info *Task = &dummy_task;
EXPORT struct task_info *TaskList;
static int task_max = FD_HIGH+40;
EXPORT struct command *Command;
EXPORT struct cache_stats *CS;
EXPORT char *Argv0 = "/usr/local/sbin/nntpcached";
EXPORT char *Version = VERSION;
EXPORT bool Detached = FALSE;
EXPORT struct command commands[] = 
{
        {"ARTICLE", c_article, "[<msgid> | artno]"},
        {"AUTHINFO", c_authinfo, "USER username|PASS password"},
        {"BODY", c_body, "[<msgid> | artno]"},
        {"DATE", c_date, ""},
        {"GROUP", c_group, "newsgroup"},
        {"HEAD", c_head, "[<msgid> | artno]"},
        {"HELP", c_help, ""},
        {"IHAVE", c_ihave, "<msgid>"},
        {"LAST", c_last, ""},
        {"LIST", c_list, "[ACTIVE | ACTIVE.TIMES | NEWSGROUPS | SUBSCRIPTIONS | OVERVIEW.FMT] [pattern]"},
        {"LISTGROUP", c_listgroup, "[newsgroup]"},
        {"MODE", c_mode, "[READER | QUERY]"},
        {"NEWGROUPS", c_newgroups, "yymmdd hhmmss [GMT] [distributions]"},
        {"NEWNEWS", c_newnews, "newsgroups yymmdd hhmmss [GMT] [distributions]"},
        {"NEXT", c_next, ""},
        {"NOOP", c_noop, ""},
        {"POST", c_post, ""},
        {"QUIT", c_quit, ""},
        {"SLAVE", c_slave, ""},
        {"STAT", c_stat, "[<msgid> | artno}"},
        {"XGTITLE", c_xgtitle, "[pattern]"},
        {"XHDR", c_xhdr, "header [<msgid> | range]"},
        {"XOVER", c_xover, "[<msgid> | range]"},
        {"XPATH", c_xpath, "[<msgid | artno]"},
        {NULL, c_none}
};

EXPORT char *task_desc[] = /* keep in-sync with enum task_state! */
{
        "none",
        "master",
        "client",
        "update",
        "expire",
        "nocem",
        "oneshot",
        "http",
        "watch",
        NULL /* nc_last */
};

/* bit length resiliant binary to decimal converter */

EXPORT char *bigToStr(big_t big)
/* [<][>][^][v][top][bottom][index][help] */
{
        int n;
        bool neg;
        char *p;
#define RING_NUM 64 /* > max number of bigToStr's ever used as concurrent function arguments */
        static char *ring[RING_NUM];    
        static int ring_idx;
        char buf[128];
        buf[sizeof(buf)-1] = '\0';
        n=sizeof(buf)-2;
        if (big<0)
        {
                big *=-1;
                neg = TRUE;
        }
        else
                neg = FALSE;
        do
        {
                buf[n] = big%10 + '0';
                big/=10;
        } while (--n > 0  && big >=1);
        if (neg)
                buf[n--] = neg;
        if ((p=ring[ring_idx]))
                free(p);
        p = ring[ring_idx] = Sstrdup(&buf[n+1]);
        if (++ring_idx >= RING_NUM)
                ring_idx = 0;
        return p;
}

EXPORT void settaskinfo (char *fmt, ...)
/* [<][>][^][v][top][bottom][index][help] */
{
        va_list ap;
        char buf[MAX_LINE];
        va_start(ap, fmt);
        vsnprintf(buf, sizeof buf, fmt, ap);
        setproctitle("%s", buf);
        strncpy(Task->ti_status_line, buf, sizeof(Task->ti_status_line)-1);
        Task->ti_status_line[sizeof(Task->ti_status_line)-1] = '\0';
        va_end(ap);
}

static void task_info_init ()
/* [<][>][^][v][top][bottom][index][help] */
{
        TaskList = XMcalloc(sizeof(struct task_info), task_max);
        return;
}

/*
 * name MUST be in the text segment or permanetly in the shared data segment.
 */

static struct task_info *task_info_new (enum task_state state, char *name)
/* [<][>][^][v][top][bottom][index][help] */
{
        int n;
        struct task_info *t;
    
        for (n=0; n<task_max; n++)
                if (TaskList[n].ti_state == nc_none)
                        goto found;
        loge (("task_info_new() no more tasks (max = %d)", task_max));
        return NULL;
found:
        t = &TaskList[n];
        memset(t, 0, sizeof *t);
        t->ti_started = time(NULL);
        t->ti_state = state;
        t->ti_pid = getpid();
        t->ti_name = name;
        t->ti_idx = n;
        Stats->task_stats[state].invocations++;
        if (Stats->task_high<n)
                Stats->task_high = n;
        if (state == nc_client)
                Stats->clientsActive++;
        return t;
}

static void task_info_free (int n)
/* [<][>][^][v][top][bottom][index][help] */
{
        struct task_info *p = &TaskList[n];
        if (p->ti_state == nc_none)
                return;
        if (p->ti_state == nc_client)
                Stats->clientsActive--;
        if (Stats->task_high<n)
        {
                for (n=Stats->task_high;n>=0 && TaskList[n].ti_state == nc_none; n--) {}
                Stats->task_high=n;
        }
        p->ti_state = nc_none;
}

static RETSIGTYPE sigterm (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        signal (SIGTERM, sigterm);
        if (Task->ti_state == nc_master)
        {
                errno = 0;
                logw (("caught SIGTERM: syncing database, syncing disks, exiting"));
        }
        retire_vm_proc (0);
}

static RETSIGTYPE sigint (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        static struct nnconf *nn_orig, nn_new;

        signal (SIGINT, sigint);
        if (nn_orig)
        {
                log(("Caught SIGINT - logging set normal"));
                con = nn_orig;
                nn_orig = NULL;
        } else
        {
                nn_orig = con;
                nn_new = *con;
                con = &nn_new;
                con->logFromClient              = TRUE;
                con->logToClient                = TRUE;
                con->logFromServer              = TRUE;
                con->logToServer                = TRUE;
                con->logDebug                   = TRUE;
                con->logInfo                    = TRUE;
                con->logWarnings                = TRUE;
                con->logErrors                  = TRUE;
                con->logListMerge               = TRUE;
                con->logListMergeCorrelation    = TRUE;
                con->logInn                     = TRUE;
                log(("Caught SIGINT - full debug logging set"));
        }
}

static RETSIGTYPE sigsegv (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        signal (SIGSEGV, SIG_DFL);
        loge (("page error"));
        Exit (1);
}

static RETSIGTYPE sigusr1 (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        signal (SIGUSR1, SIG_IGN);
        updateDaemon (TRUE);
}

static RETSIGTYPE sigusr2 (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        signal (SIGUSR2, SIG_IGN);
        expire (TRUE);
}

static int sig_hup = FALSE;

static RETSIGTYPE sighup (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        
        sig_hup = TRUE;
        signal (SIGHUP, sighup);
}

static RETSIGTYPE sigalrm (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        emitf ("%d Timeout after %s, closing connection.\r\n", NNTP_TEMPERR_VAL, nnitod(con->idleTimeout));
        log (("timeout after %s", nnitod(con->idleTimeout)));
        loginn (("%s timeout", ClientHostNormal));
        retire_vm_proc (0);
}

static void set_client_sigset ()
/* [<][>][^][v][top][bottom][index][help] */
{
        sigemptyset(&myaction.sa_mask);
        sigaddset(&myaction.sa_mask, SIGALRM);
        sigaddset(&myaction.sa_mask, SIGTERM);
        sigaddset(&myaction.sa_mask, SIGPIPE);
        myaction.sa_flags = 0;
        myaction.sa_handler = sigalrm;
        sigaction(SIGALRM, &myaction, NULL);
}

static void check_child ()
/* [<][>][^][v][top][bottom][index][help] */
{
        int n;
#ifdef HAVE_WAIT3
        int pid = wait3 (NULL, WNOHANG, NULL);
#else
#ifdef HAVE_WAITPID
        int pid = waitpid ((pid_t) - 1, NULL, WNOHANG);
#else
#error no wait3 or waitpid for this system
#endif
#endif
        if (pid<1)
                return;
        for (n=0; n<=Stats->task_high; n++)
                if (TaskList[n].ti_state != nc_none && TaskList[n].ti_pid == pid)
                        goto found;
        loge (("check_child() returned unknow pid (%d)", pid));
        return;
found:
        task_info_free(n);
        if (pid == UpdateDaemonPid)
        {
                UpdateDaemonPid = 0;
                f_cleanSlate = FALSE;
                signal (SIGUSR1, sigusr1);
        } else if (pid == ExpireDaemonPid)
        {
                ExpireDaemonPid = 0;
                signal (SIGUSR2, sigusr2);
        } else if (pid == NocemDaemonPid)
        {
                NocemDaemonPid = 0;
        }
        statsUpdateMaster();
}

static RETSIGTYPE sigchld (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        check_child ();
        signal (SIGCHLD, sigchld);
}

static RETSIGTYPE sigpipe (int sig)
/* [<][>][^][v][top][bottom][index][help] */
{
        signal (SIGPIPE, SIG_IGN);      /* syslog can cause a SIGPIPE ! */
        logd (("client disconnected unexpectedly"));
        retire_vm_proc (0);
}


EXPORT void ncExit (int code)
/* [<][>][^][v][top][bottom][index][help] */
{
        sigset_t set;
        struct sigaction action;
        /* ignore anyone interrupting us */
        alarm(0);
        sigemptyset(&action.sa_mask);
        action.sa_flags = 0;
        action.sa_handler = SIG_IGN;
        sigaction(SIGTERM, &action, NULL);
        sigaction(SIGPIPE, &action, NULL);
        if (code != 0)
                debugSelf();
        if (Task->ti_state == nc_master)
        {
                chdir(con->cacheDir);
                writeMmapBase();
                dbzsync ();
                dbmclose ();
                if (Stats)
                        saveStats (con->statsFile);
                unlink (PidFile);
                sync ();
        } else
        {
                if (Task->ti_state == nc_client)
                {
                        struct tms buffer;
                        
                        n_u32 u, s;
                        
                        if (CurrentGroupArtRead && CurrentGroupScfg && CurrentGroupScfg->group)
                        {
                                loginn (("%s group %s %d", ClientHostNormal, 
                                         CurrentGroupScfg->group, CurrentGroupArtRead));
                        }
                        loginn (("%s exit articles %d groups %d bytes %s", ClientHostNormal, 
                                 ArtRead, GroupsEntered, bigToStr(ClientBytes)));
                        if (PostsReceived>0 || PostsRejected>0)
                                loginn (("%s posts received %d rejected %d", ClientHostNormal, PostsReceived, PostsRejected));
                        times(&buffer);
                        u = buffer.tms_utime;
                        s = buffer.tms_stime;
                        loginn (("%s times user %.2f system %.2f elapsed %d.00",
                                 ClientHostNormal,
                                 (double) u/ CLK_TCK,
                                 (double) s/ CLK_TCK,
                                 (int)(time(NULL) - ClientTimeStarted)));
                }
        }
        if (Master_fd >= 0)
                close (Master_fd);
        if (Debug_fd >= 0)
                close (Debug_fd);
        if (Watch_fd >= 0)
                close (Watch_fd);
#ifdef MMALLOC
        if (Mbase)
                mmalloc_detach (Mbase);
#endif
    
        /* restore default SIGALRM behavior (ie, crash us out) */
        /* sigemptyset(&action.sa_mask); already done above */
        /* sigaddset(&action.sa_mask, SIGALRM); can't remember why I did this in this 1st place */
        /* action.sa_flags = 0;  already done above */
        action.sa_handler = SIG_DFL;
        sigaction(SIGALRM, &action, NULL);
    
        /* unblock, since we might be in a SIGALRM handler right now! */
        sigemptyset(&set);
        sigaddset(&set, SIGALRM);
        sigprocmask (SIG_UNBLOCK, &set, NULL);
    
        alarm(60);
        flush ();       /* better finish this in 60 seconds, toots. */
        if (code == 0 || code == 2) 
        {
                log (("clean shutdown"));
                closelog ();
                exit (code);
        }
        log (("clean shutdown with error %d. dumping core for debug analysis", code));
        chdir(con->cacheDir);
        abort ();
}

EXPORT int make_vm_proc (enum task_state state, int clientfd, char *name)
/* [<][>][^][v][top][bottom][index][help] */
{
        int i[2];
        int pid;
        struct task_info *task;
        logd (("starting %s task", name));
        if (socketpair (AF_UNIX, SOCK_STREAM, 0, i) == -1)
        {
                loge (("socketpair() failed"));
                return -1;
        }
        task  = task_info_new(state, name);
        pid = fork ();
        if (pid == -1)
        {
                loge (("couldn't fork()"));
                task_info_free(task->ti_idx);           /*  -an */
                if (clientfd>=0)
                        close (clientfd);
                close (i[1]);
                close (i[0]);
                return -1;
        }
        if (pid == 0)
                while (HoldForks) {}
        if (SwapWithChild? pid != 0 : pid == 0)
        {
                int n;
                int yes = 1;
                struct server_cfg *scfg;
                char buf[MAX_SYSLOG];
                char *id;
                int ni = 0;

                Task = task;
                Task->ti_pid = getpid();
                switch (state)
                {
                case nc_master: ni = con->niceMaster; break;
                case nc_client: ni = con->niceClient; break;
                case nc_update: ni = con->niceUpdate; break;
                case nc_expire: ni = con->niceExpire; break;
                case nc_nocem: ni = con->niceNoCem; break;
                case nc_http: ni = con->niceHTTP; break;
                default:
                        break;
                }
                if (ni>0)
                {
#ifdef HAVE_SETPRIORITY
                        ni += getpriority(PRIO_PROCESS, 0);
                        setpriority(PRIO_PROCESS, 0, ni);
#endif
                }
                Master_fd = i[1];           
                settaskinfo("starting %s task", name);
                dbzcancel ();
                dbmclose ();
                signal (SIGCHLD, SIG_DFL);
                signal (SIGHUP, SIG_DFL);
                sigemptyset(&myaction.sa_mask);
                sigaddset(&myaction.sa_mask, SIGTERM);
                sigaddset(&myaction.sa_mask, SIGALRM);
                if (clientfd>=0)
                {
                        sigaddset(&myaction.sa_mask, SIGPIPE);
                        myaction.sa_flags = 0;
                        myaction.sa_handler = sigpipe;
                        sigaction(SIGPIPE, &myaction, NULL);
                }
                closelog ();
                for (scfg = ServerList; scfg; scfg=scfg->next)
                        if (scfg->fd >= 0 &&
                            scfg->fd != clientfd)
                        { 
                                close (scfg->fd);
                                scfg->fd=-1;
                        }
                if (clientfd >= 0)
                {
#ifdef SO_SNDBUF
                        setsockopt (clientfd, SOL_SOCKET, SO_SNDBUF, (char *)&con->outputBufferSize, sizeof con->outputBufferSize);
#endif
#ifdef SO_KEEPALIVE
                        if (setsockopt (clientfd, SOL_SOCKET, SO_KEEPALIVE, (char*) &yes, sizeof yes))
                                logd(("keepalive setsockopt failed for %s", name));
#endif
                        clientin = fdopen (clientfd, "r");
                        clientout = fdopen (clientfd, "w");
#ifdef CRASHES_UNDER_LINUX
                        setvbuf(clientout, io_buf, _IOFBF, IO_BUF_LEN);
#endif
                }
                close (i[0]);

                /* close sockets to siblings */
                for (n = 0; n <= high_fd; n++)
                {
                        if (FD_ISSET (n, &r_set))
                        {
                                close (n);
                                FD_CLR(n, &r_set);
                        }
                }
                FD_SET(Master_fd, &r_set);
                sprintf (buf, "nntpcache-%.80s", name);
                id = Sstrdup(buf);
                openlog (id, LOG_PID|LOG_NDELAY, LOG_NEWS);
                log (("%s task awakening", name));
                ClientTimeStarted = time (NULL);
                ClientBytes = 0;
        }
        else /* parent */
        {
                if (clientfd >= 0)
                        close (clientfd);
                close (i[1]);
                FD_SET (i[0], &r_set);
                if (i[0] > high_fd)
                        high_fd = i[0];
        }
        return pid;
}

EXPORT void retire_vm_proc (int err)
/* [<][>][^][v][top][bottom][index][help] */
{
        struct tms tms;
        struct task_stats *ts;
        char *name;
        if (Task)
        {
                if (Stats && Task->ti_state != nc_master)
                {
                        times(&tms);
                        ts = &Stats->task_stats[Task->ti_state];
                        ts->cpu_user += tms.tms_utime + tms.tms_cutime;
                        ts->cpu_system += tms.tms_stime + tms.tms_cstime;
                        ts->elapsed += time(NULL) - Task->ti_started;
                }
                name = task_desc[Task->ti_state];
        }
        else
                name = "unspecified";
        log (("%s task retiring", name));
        Exit (err);
}

static bool load_config (char *file)
/* [<][>][^][v][top][bottom][index][help] */
{
        FILE *fp;
        char *msg;

        if ((fp = fopen (file, "r")) == NULL)
        {
                loge (("couldn't load config %s", file));
                return FALSE;
        }
        msg = confused (fp, "", nnconf_idx);
        fclose (fp);
        if (msg)
        {
                logen (("error in config file %s: %s", file, msg));
                return FALSE;
        }
        return TRUE;
}

static int decomment(char *buf, int comment_depth)
/* [<][>][^][v][top][bottom][index][help] */
{
        char *p;
        for (p = buf; *p; p++)
        {
                if (*p == '/' && p[1] == '*')
                {
                        comment_depth++;
                        p++;
                        continue;
                        
                }
                if (comment_depth && *p == '*' && p[1] == '/')
                {
                        comment_depth--;
                        p++;
                        continue;
                }
                if (!comment_depth)
                        *buf++ = *p;
        }
        *buf = *p;
        return comment_depth;
}

static bool load_groups(char *file, FILE *fp)
/* [<][>][^][v][top][bottom][index][help] */
{
        char buf[MAX_LINE];
        int n;
        struct group_cfg *list=NULL;
        int comment_depth=0;

        for (n = 0; fgets(buf, sizeof(buf), fp); ++n)
        {
                char host[MAX_HOST], group_pat[MAX_HOST];
                char *p;
                comment_depth = decomment(buf, comment_depth);
                if (!buf[0] || buf[0] == '\n' || buf[0] == '#')
                        continue;
                if (sscanf(buf, "%127s %127s", group_pat, host) != 2)
                {
                        loge (("invalid config line %s:%d: %s", file, n, buf));
                        continue;
                }
                for (p=strtok(group_pat, ","); p; (p=strtok(NULL, ",")))
                {
                        if (!list)
                        {
                                list = Scalloc (1, sizeof *list);
                                list->head = list;
                                list->next = NULL;
                        } else
                        {
                                struct group_cfg *head = list->head;
                                list->next = Scalloc (1, sizeof *list);
                                list = list->next;
                                list->next = NULL;
                                list->head = head;
                        }
                        list->server_cfg = findScfg(host);
                        list->group_pat = Sstrdup (group_pat);
                }
        }
        if (ferror(fp))
        {
                loge (("error reading groups config from %s", file));
                Exit(1);
        }
        if (!list)
        {
                loge (("group file %s contains no group/server tuples!", file));
                return FALSE;
        }
        GroupList = list->head;
        return TRUE;
}

static void set_cfg_shm()
/* [<][>][^][v][top][bottom][index][help] */
{
        struct server_cfg *l;
        for (l=ServerList; l; l=l->next)
        {
                if (!l->share)
                        l->share = XMcalloc(1, sizeof *l->share);
        }
}

/*
 * XXX this code needs to be put into the general form
 */

static bool load_servers(char *file)
/* [<][>][^][v][top][bottom][index][help] */
{

        FILE *fp;
        char buf[MAX_LINE];
        int n;
        struct server_cfg *list=NULL;
        int comment_depth=0;

        if ((fp = fopen(file, "r")) == NULL) {
                loge(("couldn't load servers file %s", file));
                return FALSE;
        }
        for (n = 0; fgets(buf, sizeof(buf), fp); ++n)
        {
                char host[MAX_HOST], us[MAX_HOST], active_timeoutS[32], active_times_timeoutS[32], newsgroups_timeoutS[32], group_timeoutS[32], xover_timeoutS[32], article_timeoutS[32], *username, *password, *hostname;
                int active_timeout, active_times_timeout, newsgroups_timeout, group_timeout, xover_timeout, article_timeout;
                if (!buf[0] || buf[0] == '\n')
                        continue;
                comment_depth = decomment(buf, comment_depth);
                if (!buf[0] || buf[0] == '#' || buf[0] == '\n')
                        continue;
                strStripEOL(buf);
                if (!buf[0])
                        continue;
                if (strCaseEq(buf, "%BeginGroups"))
                        break;
                if (sscanf(buf, "%127s %127s %31s %31s %31s %31s %31s %31[^\t\r\n ]s", host, us, active_timeoutS, active_times_timeoutS, newsgroups_timeoutS, group_timeoutS, xover_timeoutS, article_timeoutS) != 8)
                {
                        loge (("invalid config line %s:%d: %s", file, n, buf));
                        continue;
                }
                if ((active_timeout = nndtoi (active_timeoutS)) < 0)
                {
                        loge (("invalid active file timeout %s:%d: %s", file, n, buf));
                        continue;
                }
                if ((active_times_timeout = nndtoi (active_times_timeoutS)) < 0)
                {
                        loge (("invalid active.times file timeout %s:%d: %s", file, n, buf));
                        continue;
                }
                if ((newsgroups_timeout = nndtoi (newsgroups_timeoutS)) < 0)
                {
                        loge (("invalid newsgroups timeout %s:%d: %s", file, n, buf));
                        continue;
                }
                if ((group_timeout = nndtoi (group_timeoutS)) < 0)
                {
                        loge (("invalid group timeout %s:%d: %s", file, n, buf));
                        continue;
                }
                if ((xover_timeout = nndtoi (xover_timeoutS)) < 0)
                {
                        loge (("invalid xover timeout %s:%d: %s", file, n, buf));
                        continue;
                }
                if ((article_timeout = nndtoi (article_timeoutS)) < 0)
                {
                        loge (("invalid article timeout %s:%d: %s", file, n, buf));
                        continue;
                }
                if (!list)
                {
                        list = Scalloc (1, sizeof *list);
                        list->head = list;
                } else
                {
                        struct server_cfg *head = list->head;
                        list->next = Scalloc (1, sizeof *list);
                        list = list->next;
                        list->head = head;
                }
                /* if there's a @ in host there's a user and password */
                hostname = username = password = NULL;
                if (strchr(host, '@') != NULL)
                {
                        if ((password = strrchr(host, ':')) != NULL)
                                *(password++) = '\0';
                        else
                        {
                                loge (("missing password in %s:%d: %s", file, n, buf));
                                continue;
                        }
                        if ((hostname = strrchr(password-2, '@')) != NULL) {
                                *(hostname++) = '\0';
                                username = host;
                                list->user = Sstrdup (username);
                                list->pass = Sstrdup (password);
                        }
                } else {
                        hostname = host;
                }
                list->host = Sstrdup (hostname);
                list->us = Sstrdup (us);
                list->active_timeout = active_timeout;
                list->active_times_timeout = active_times_timeout;
                list->newsgroups_timeout = newsgroups_timeout;
                list->group_timeout = group_timeout;
                list->listgroup_timeout = group_timeout;
                list->xover_timeout = xover_timeout;
                list->article_timeout = article_timeout;
                list->overview_fmt_timeout = con->overviewFmtTimeout;
                list->fd=-1;
        }
        if (ferror(fp))
        {
                loge (("error reading servers config from %s", file));
                Exit(1);
        }
        if (!list)
        {
                loge (("servers file %s contains no servers!", file));
                fclose(fp);
                return FALSE;
        }
        ServerList = list->head;
        load_groups(file, fp);
        fclose (fp);
        return TRUE;
}

EXPORT struct server_cfg *findScfg(char *name)
/* [<][>][^][v][top][bottom][index][help] */
{
        struct server_cfg *l=ServerList;
        for (;l;l=l->next)
        {
                if (strCaseEq(l->host, name))
                        return l;
        }
        return NULL;
}

static void perform_chroot()
/* [<][>][^][v][top][bottom][index][help] */
{
#ifdef HAVE_CHROOT
        if (chdir (con->chrootDir) != 0 || chroot (".") != 0)
        {
                loge (("unable to chroot(\"%s\")", con->chrootDir));
                Exit(2);
        }
#else
        loge (("no chroot() call available on this system"));
        Exit(2);
#endif
}

static void usage (char *argv0)
/* [<][>][^][v][top][bottom][index][help] */
{
        fprintf (stderr, "usage: %s [ehinrs] [-b addr:port] [-c config_file]\n", argv0);
        exit (1);
}

static void drop_priv(int uid, int gid)
/* [<][>][^][v][top][bottom][index][help] */
{
        /* Can't drop priviledges if we're not root. */
        if (geteuid() != 0)
                return;

        if (setgid (gid) == -1)
        {
                loge (("unable to set gid to %d", gid));
#ifndef DEBUG
                Exit (2);
#endif
        }
        if (setuid (uid) == -1)
        {
                loge (("unable to set uid to %d", uid));
#ifndef DEBUG
                Exit (2);
#endif
        }
}

static void drop_idle_servers()
/* [<][>][^][v][top][bottom][index][help] */
{
        struct server_cfg *p;
        time_t ti = time(NULL);
        for (p=ServerList; p; p=p->next)
                if (p->last_active_time &&
                    ti - p->last_active_time > con->remoteIdleTimeout)
                        detachServer(p);
}

static void emit_banner(bool post_ok)
/* [<][>][^][v][top][bottom][index][help] */
{
        emitf ("%d %s NNTPCache server V%s [see www.nntpcache.com] "
               " (c) 1996-2002 Julian Assange <proff@iq.org> %s ready"
               " (posting %s, %d groups available).\r\n",
               post_ok? NNTP_POSTOK_VAL: NNTP_NOPOSTOK_VAL,
               Host,
               VERSION,
               __DATE__,
               post_ok? "ok": "not permitted",
               (int)Stats->list_stats[l_active].entries
              );
}

static bool relay_unknown (char *buf)
/* [<][>][^][v][top][bottom][index][help] */
{
        Cemit (buf);
        Cflush (buf);
        if (!Cget (buf, sizeof buf))
        {
                CurrentScfg->share->relay_fail++;
                emitrn (NNTP_SERVERDOWN);
                return FALSE;
        }
        CurrentScfg->share->relay_good++;
        emit (buf);
        switch (strToi(buf))
        {
        case NNTP_HELPOK_VAL:
        case NNTP_LIST_FOLLOWS_VAL:
        case NNTP_ARTICLE_FOLLOWS_VAL:
        case NNTP_HEAD_FOLLOWS_VAL:
        case NNTP_BODY_FOLLOWS_VAL:
        case NNTP_OVERVIEW_FOLLOWS_VAL:
        case 230:
        case NNTP_NEWGROUPS_FOLLOWS_VAL:
        case NNTP_XGTITLE_OK_VAL:
                getArt (CurrentScfg, clientout);
                break;
        case NNTP_GOODBYE_ACK_VAL:
                retire_vm_proc (0);
                break;
        case NNTP_GROUPOK_VAL:
                /* case NNTP_NOTHING_FOLLOWS_VAL: */
                if (strlen (buf) > (size_t)5 && !isdigit (buf[5]))
                {
                        getArt (CurrentScfg, clientout);
                }
                break;
        case NNTP_AUTH_NEEDED_VAL:
        case NNTP_AUTH_NEXT_VAL:
        case NNTP_AUTH_OK_VAL:
                break;
        default:
                break;
        }
        return TRUE;    /* XXX dubious */
}

static bool client_cmd (char *buf)
/* [<][>][^][v][top][bottom][index][help] */
{
        struct command *cmd;
        bool ret = FALSE;
        char a0[32]="", a1[501]="";
        int i;
        sscanf(buf, "%31[^\r\n\t ]%*[\r\n\t ]%480[^\r\n]", a0, a1);
        if (a0[0] == '\0')
                return FALSE;
        settaskinfo("%s [%s]: %.32s %.32s", ClientHost, (*CurrentGroup && Task->ti_state == nc_client && con->taskInfoPrivacy)? "private": CurrentGroup, a0, a1);
        for (cmd = commands; cmd->cmd; cmd++)
                if (strCaseEq(a0, cmd->cmd))
                        break;
        if (CurrentGroupScfg)
                CurrentScfg = CurrentGroupScfg;
        CS = &Stats->cache_stats[cmd->val];
        CS->requests++;
        CS->clientFromBytes+=strlen(buf);
        Command = cmd;
        alarm(con->idleTimeout);
        if (ConnectAuth && ConnectAuth->authinfo && ConnectAuth->authinfo->type != AUTHINFO_NONE && authinfo_ok_cmd(cmd->val) == 0) {
                emitf("%d Authentication required for command\r\n", NNTP_AUTH_NEEDED_VAL );
                return FALSE;
        }
        switch(cmd->val)
        {
        case c_post:
                ModeReader = TRUE;
                ret = CMDpost ();
                break;
        case c_ihave:
                ret = CMDihave (buf);
                break;
        case c_listgroup:
                ModeReader = TRUE;
                ret = CMDlistgroup (buf);
                break;
        case c_newnews:
                ModeReader = TRUE;
                ret = CMDnewnews(buf);
                break;
        case c_newgroups:
                ModeReader = TRUE;
                ret = CMDnewgroups(buf);
                break;
        case c_xgtitle:
                sprintf(buf, "list %.120s %.120s", a0, a1);
                /* FALL-THOUGH */
        case c_list:
                ret = CMDlist (buf);
                break;
        case c_xover:
                ModeReader = TRUE;
                ret = CMDxover (buf);
                break;
        case c_xhdr:
                ModeReader = TRUE;
                ret = CMDxhdr (buf);
                break;
        case c_group:
                ModeReader = TRUE;
                ret = CMDgroup (buf);
                break;
        case c_date:
                ret = CMDdate (buf);
                break;
        case c_help:
                emitrn(NNTP_HELP_FOLLOWS);
                for (i = 0; commands[i].cmd; i++)
                        emitf ("  %s %s\r\n", commands[i].cmd, commands[i].desc);
                emitf ("Report local configuration problems to <%s> or NNTPCache specific problems to <nntpcache@nntpcache.com>\r\n", con->adminEmail);
                emitrn (".");
                break;
        case c_article:
        case c_head:
        case c_body:
        case c_stat:
                ModeReader = TRUE;
                ret = CMDarticle (cmd, buf, FALSE);
                break;
        case c_next:
                ModeReader = TRUE;
                ret = CMDnext (buf);
                break;
        case c_last:
                ModeReader = TRUE;
                ret = CMDlast (buf);
                break;
        case c_slave:
                emitrn ("202 Unsupported");
                ret = FALSE;
                break;
        case c_noop:
                emitrn ("500 noop");
                break;
        case c_xpath:
                ModeReader = TRUE;
                ret = CMDxpath (buf);
                break;
        case c_quit:
        {
                settaskinfo("%s QUITing", ClientHost);
                sigemptyset(&myaction.sa_mask);
                myaction.sa_flags = 0;
                myaction.sa_handler = SIG_IGN;
                sigaction(SIGPIPE, &myaction, NULL);
                emitrn (NNTP_GOODBYE_ACK);
                CS->requests_good++;
                retire_vm_proc (0);
                NOTREACHED;
        }
        case c_authinfo:
                ret = CMDauthinfo (buf);
                break;
        case c_mode:
                if (strnCaseEq(a1, "reader", 6) ||
                    strnCaseEq(a1, "query", 5))
                {
                        ModeReader = TRUE;
                        emit_banner(ConnectAuth->post);
                        break;
                }
                /* FALL-THROUGH */
        default:
                if (con->relayUnknowns)
                        ret = relay_unknown (buf);
                else
                {
                        strStripEOL(buf);
                        loginn (("%s unrecognized command %.128s", ClientHostNormal, buf));
                        emitrn (NNTP_BAD_COMMAND);
                        ret = FALSE;
                }
        }
        if (ret)
                CS->requests_good++;
        else
                CS->requests_failed++;
        return ret;
}

static void client_cmd_loop ()
/* [<][>][^][v][top][bottom][index][help] */
{
        for (;;)
        {
                char buf [MAX_CMD];
                alarm(con->idleTimeout);
                flush ();       /* required! */
                settaskinfo("%s [%s]: waiting for input", ClientHost, (*CurrentGroup && Task->ti_state == nc_client && con->taskInfoPrivacy)? "private": CurrentGroup);
                drop_idle_servers();
            
                if (!Get (buf, sizeof buf))
                {
                        char *p = strerror(errno);
                        logd (("client '%s' diconnected before QUIT", ClientHost));
                        loginn (("%s cant read %s", ClientHostNormal, p));
                        settaskinfo("%s diconnected before QUIT", ClientHost);
                        retire_vm_proc (0);
                }
                client_cmd (buf);
        }
}

static bool client_handler ()
/* [<][>][^][v][top][bottom][index][help] */
{
        while (HoldForksClient) {}
        Stats->clientConnects++;
        Stats->clientConnectsFailed++; /* presume failure */

        alarm(con->idleTimeout);
        settaskinfo("authenticating client");
        if (!fillAuth(fileno(clientin), "<nntp>"))
        {
                emitf ("%d NNTPCache-%s access denied <%s>, you do not have connect permissions in the %s file.\r\n", NNTP_ACCESS_VAL, VERSION, ClientHost, con->accessFile);
                log (("refused connect from %s (%s)", ClientHost, ClientHostAddr));
                loginn (("%s no_access", ClientHostNormal));
                return FALSE;
        }
        if (Stats->clientsActive > con->maxReaders)
        {
                emitf ("%d NNTPCache-%s too many concurrent reader sessions already (%d). try again later\r\n", NNTP_TEMPERR_VAL, VERSION,  Stats->clientsActive);
                log (("%s romance refused with %s (%s) due to too many users (%d)", ClientHostNormal, ClientHost, ClientHostAddr, Stats->clientsActive));
                return FALSE;
        }
        if ((f_cleanSlate && UpdateDaemonPid) ||
            con->minActive > Stats->list_stats[l_active].entries)
        {
                int togo = con->minActive - Stats->list_stats[l_active].entries;
                emitf ("%d NNTPCache-%s %s server rebuild in progress (%d groups complete, at least %d groups to go. Please try again later. If you think this is an error, get your news admin to check minGroups (in %s), %s and the nntpcache web-server output\r\n",
                       NNTP_SERVERDOWN_VAL,
                       VERSION,
                       (f_cleanSlate && UpdateDaemonPid)? "Initial": "Failing",
                       (int)Stats->list_stats[l_active].entries,
                       (togo>0)? togo: 0,
                       con->configFile,
                       con->serversFile);

                log (("romance refused with %s (%s) due to %s server rebuild in progress (%d groups complete, at least %d groups to go)",
                      ClientHost,
                      ClientHostAddr,
                      (f_cleanSlate && UpdateDaemonPid)? "initial": "failing",
                      (int)Stats->list_stats[l_active].entries,
                      (togo>0)? togo: 0));
                return FALSE;
        }
        if (con->contentFilters)
                setenv ("CLIENTHOST", ClientHost, 1);
        log (("%s connect from %s (%s)", ClientHostNormal, ClientHostRFC931, ClientHostAddr));
        loginn (("%s connect", ClientHostNormal));
        settaskinfo("%s: starting", ClientHost);
        emit_banner(ConnectAuth->post);
        CurrentScfg = ServerList->head;
        Stats->clientConnectsFailed--;
        client_cmd_loop ();
        NOTREACHED;
}

int createPort (char *addr)
/* [<][>][^][v][top][bottom][index][help] */
{
        int fd;
        struct sockaddr_in *in;
        int yes = 1;
        fd = socket (AF_INET, SOCK_STREAM, 0);
        if (fd == -1)
        {
                loge (("couldn't get socket()"));
                retire_vm_proc (1);
        }
        setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (char*) &yes, sizeof yes);
        in = getHostAddr (addr);
        if (!in || bind (fd, (struct sockaddr *) in, sizeof *in) == -1)
        {
                loge (("couldn't bind %s", addr));
                retire_vm_proc (1);
        }
        listen (fd, 50);
        FD_SET (fd, &r_set);
        if (fd > high_fd)
                high_fd = fd;
        return fd;
}

/*
 * return's TRUE for reload
 */

static bool master_loop ()
/* [<][>][^][v][top][bottom][index][help] */
{
#ifdef CRASHES_UNDER_LINUX
        char *io_buf;
#endif
        fd_set rt_set, et_set;
        if (!sig_hup)
        {
                FILE *fh;
                FD_ZERO (&r_set);
                FD_ZERO (&rt_set);
                FD_ZERO (&et_set);
                NNTPportFD = createPort (con->bindAddr);
                if (con->httpServer)
                        HTTPportFD = createPort (con->httpBindAddr);
                signal (SIGCHLD, sigchld);
                signal (SIGHUP, sighup);
                signal (SIGPIPE, SIG_IGN);
                signal (SIGUSR1, sigusr1);
                signal (SIGUSR2, sigusr2);
                open_mmap();
                task_info_init ();
                loadStats (con->statsFile);
                Task = task_info_new (nc_master, "master");
                watchInit();
                drop_priv(ncUID, ncGID);
                sprintf (PidFile, "%.127s.%.164s", con->pidFile, con->bindAddr);
                if (!(fh = fopen (PidFile, "w")))
                        logw (("couldn't open pid file '%s'", PidFile));
                else
                {
                        fprintf (fh, "%d\n", (int) getpid ());
                        fclose (fh);
                }
        }
        sig_hup = FALSE;
        overviewFmt = overviewFmtGen(NULL, con->overviewFmtInternal, &overviewFmt_hash);
        set_cfg_shm();
        expire (FALSE);
        updateDaemon (TRUE);
#ifdef CRASHES_UNDER_LINUX
        io_buf = Smalloc(con->outputBufferSize);
#endif
        log (("waiting for NTTP connections on %s", con->bindAddr));
        if (con->httpServer)
                log (("waiting for HTTP connections on %s", con->httpBindAddr));
        for (;;)
        {
                struct sockaddr_in remote;
                int remlen = sizeof remote;
                int client;
                int sel;
                int n;
                int s_errno;
                settaskinfo("waiting for connections");
                rt_set = et_set = r_set;
                sel = select (high_fd + 1, &rt_set, NULL, &et_set, NULL);
                s_errno = errno;
                check_child ();
                if (sel < 1) /* TODO: fix signal race window */
                {
                        if (s_errno != EINTR)
                        {
                                errno = s_errno;
                                logw (("main daemon select() failed"));
                                continue;
                        }
                        if (sig_hup)
                        {
                                errno = 0;
                                logw (("caught SIGHUP, restarting..."));
                                return TRUE;
                        }
                        continue;
                }
                if (FD_ISSET (NNTPportFD, &rt_set))
                {
                        if ((client = accept (NNTPportFD, (struct sockaddr *) &remote, &remlen)) == -1)
                        {
                                if (errno == EINTR)
                                {
                                        if (sig_hup)
                                        {
                                                logw (("caught SIGHUP, restarting..."));
                                                return TRUE;
                                        }
                                } else
                                        loge (("accept() failed"));
                                goto play_with_children;
                        }
                        if (sig_hup)
                        {
                                errno = 0;
                                logw (("caught SIGHUP, restarting..."));
                                return TRUE;
                        }
                        if (make_vm_proc (nc_client, client, "client") == 0) /* child == 0 */
                        {
                                set_client_sigset ();
                                client_handler ();
                                retire_vm_proc (0);
                        }
                        updateDaemon (FALSE);
                        if (!f_cleanSlate || !UpdateDaemonPid)
                        {
                                nocemDaemon ();
                                expire (FALSE);
                        }
                }
                if (con->httpServer && FD_ISSET (HTTPportFD, &rt_set))
                {
                        int http_client;
                        if ((http_client = accept (HTTPportFD, (struct sockaddr *) &remote, &remlen)) == -1)
                        {
                                if (errno == EINTR)
                                {
                                        if (sig_hup)
                                        {
                                                logw (("caught SIGHUP, restarting..."));
                                                return TRUE;
                                        }
                                } else
                                        loge (("accept() failed"));
                                goto play_with_children;
                        }
                        if (sig_hup)
                        {
                                errno = 0;
                                logw (("caught SIGHUP, restarting..."));
                                return TRUE;
                        }
                        if (make_vm_proc (nc_http, http_client, "http") == 0) /* child == 0 */
                        {
                                set_client_sigset ();
                                httpHandler ();
                                retire_vm_proc (0);
                        }
                }
        play_with_children:
                for (n = high_fd; n > MAX(NNTPportFD, HTTPportFD); n--)
                {
                        if (FD_ISSET (n, &rt_set) || FD_ISSET (n, &et_set))
                        {
                                if (!DoIPC (n) || FD_ISSET(n, &et_set))
                                {
                                        FD_CLR (n, &r_set);
                                        close (n);
                                        if (n == high_fd)
                                                high_fd--;
                                }
                        }
                }
        }
        NOTREACHED;
}

void
detach()
/* [<][>][^][v][top][bottom][index][help] */
{
#ifndef HAVE_DAEMON
        int fd;
#endif
        int n =
#ifndef IDIOTIC_BUGS_IN_LINUX_OPENLOG_NOT_PRESENT
            3;
#else
#  ifdef HAVE_DTABLESIZE
                getdtablesize ();
#  else
                256;
#  endif
#endif
        logd (("detaching from tty"));
        while (n)
                close (--n);
                
#ifdef HAVE_DAEMON
        daemon (1, 0);
#else
        if (fork ())
                exit (0);
        
        if ((fd = open ("/dev/null", O_RDWR)) != -1)
        {
                if (fd !=0 )
                        dup2 (fd, 0);
                dup2 (fd, 1);
                dup2 (fd, 2);
        }
#  ifdef TIOCNOTTY
        fd = open ("/dev/tty", O_RDWR | O_NOCTTY);
        if (fd != -1)
        {
                ioctl (0, TIOCNOTTY, NULL);
                close (fd);
        }
#  else
        setsid ();
#  endif
#endif
        Detached = TRUE;
}
    
int main (int argc, char **argv, char **envp)
/* [<][>][^][v][top][bottom][index][help] */
{
    char buf[MAX_CMD];
    int c;
    struct passwd *pw;
    struct group *gr;
    int nodetach = FALSE;
    int expireonly = FALSE;
    int zorch = FALSE;
    char *config_file = con->configFile;
    char *access_file = con->accessFile;
    char *bindAddr = NULL;
    struct hostent *hp;
    enum task_state task;
    char *p = NULL;
    bool reloading = FALSE;
    
    openlog ("nntpcached", LOG_PID|LOG_NDELAY, LOG_NEWS);
    enableCoreDump();
    
         fprintf (stderr, "\
NNTPCache-" VERSION "\n\
Copyright (c) 1996-2002 Julian Assange <proff@iq.org>\n\
Copyright (c) 1998-2002 Australian National Cognitive Facility\n\
See the files \"COPYING\", \"FAQ\", \"LICENSING\" and \n\
http://www.nntpcache.com/ for copyright details\n");
         fflush(stderr);
         Argv0 = Sstrdup(argv[0]);
         /* start a master task unless told otherwise */
         assert(task_desc[nc_last] == NULL);
         task = nc_master;

         while ((c = getopt (argc, argv, "ef:hnb:rc:s")) != -1)
                switch (c)
                {
                case 'a':
                        access_file = Sstrdup(optarg);
                        break;
                case 'e':
                        expireonly = TRUE;
                        task = nc_oneshot;
                        break;
                case 'f':
                    for (p = optarg; *p; p++)
                        switch (*p)
                        {
                            case 'a':
                                HoldForks = TRUE;
                                break;
                            case 'c':
                                HoldForksClient = TRUE;
                                break;
                            case 'h':
                                HoldForksHttp = TRUE;
                                break;
                            case 'n':
                                HoldForksNocem = TRUE;
                                break;
                            case 'u':
                                HoldForksUpdate  = TRUE;
                                break;
                        }
                    break;
                case 'c':
                        config_file = Sstrdup(optarg);
                        break;
                case 'n':
                        nodetach = TRUE;
                        break;
                case 'b':
                        bindAddr = Sstrdup(optarg);
                        break;
                case 's':
                        SwapWithChild = TRUE;
                        break;
                case 'h':
                        task = nc_oneshot;
                        MakeHistory = TRUE;
                        break;
                case 'z':
                        task = nc_oneshot;
                        zorch = TRUE;
                        break;
                default:
                        usage (argv[0]);
                }
        Task->ti_state = nc_master;
        initsetproctitle(argc, argv, envp);
        settaskinfo("initialising");
        umask (022);
        gethostname (Host, sizeof (Host));
        if ((hp = gethostbyname (Host)))
                strncpy (Host, hp->h_name, sizeof Host);
      reload:
        if (chdir (con->configDir) == -1)
        {
                loge (("couldn't set cwd to %s", con->configDir));
                Exit (2);
        }
        if (!load_config (config_file))
                Exit (2);
        umask (con->umask);
        safeGroupInit (con->safeGroup);
        if (!nocemInit ())
            con->nocem = FALSE;
        if (!postInit())
                Exit (1);
        if (zorch && !reloading)
        {
            printf ("zorching %s/{cache.mmap,%s.{dir,pag}}!\n", con->cacheDir, con->historyFile);
            chdir (con->cacheDir);
            unlink (con->mmapFile);
            unlink (con->mmapBaseFile);
            unlink (con->historyFile);
            sprintf (buf, "%.127s.pag", con->historyFile);
            unlink (buf);
            sprintf (buf, "%.127s.dir", con->historyFile);
            unlink (buf);
            zorch = FALSE;      /* don't zorch again on reload */
        }
        if (expireonly && !reloading)
        {
                puts ("Running expire (only)...");
                if (chdir (con->cacheDir) != 0)
                {
                        perror (con->cacheDir);
                        exit (1);
                }
                open_mmap();
                task_info_init ();
                loadStats (con->statsFile);
                expire (TRUE);
                exit (0);
        }
        if (bindAddr)
        {
                if (con->bindAddr) free(con->bindAddr);
                con->bindAddr = Sstrdup(bindAddr);
        }
        if (chdir (con->configDir) == -1)
        {
                loge (("couldn't set cwd to %s", con->configDir));
                Exit (2);
        }
        logd(("cwd now %s", con->configDir));
        if (!load_servers (con->serversFile))
                Exit (2);
        CurrentScfg = ServerList;
        if (!authReadConfig (con->accessFile))
                Exit (2);
        if (!(pw = getpwnam (con->user)))
        {
                loge (("configuration error: no such user '%s'", con->user));
                Exit (2);
        }
        ncUID = pw->pw_uid;
        if (!(gr = getgrnam (con->group)))
        {
                loge (("configuration error: no such group '%s'", con->group));
                Exit (2);
        }
        ncGID = gr->gr_gid;
        if (con->chroot && !reloading)
                perform_chroot();
        if (Task->ti_state != nc_master && !reloading)
                drop_priv(ncUID, ncGID);
        if (chdir (con->cacheDir) == -1)
        {
                loge (("couldn't set cwd to %s", con->cacheDir));
                Exit (2);
        }
        logd(("cwd now %s", con->cacheDir));
        if (!nodetach && !reloading)
        {
                detach();
                Debug_fd = -1;
        }
        if (!reloading && nodetach)
        {
                Debug_fd = dup(2);
        }
#ifdef AUTHINFO_RADIUS
        if (!authinfo_radius_init())
                Exit (1);
#endif
        sigemptyset(&myaction.sa_mask);
        sigaddset(&myaction.sa_mask, SIGTERM);
        sigaddset(&myaction.sa_mask, SIGALRM);
        sigaddset(&myaction.sa_mask, SIGPIPE);
        myaction.sa_flags = 0;
        myaction.sa_handler = sigterm;
        sigaction(SIGTERM, &myaction, NULL);
        signal (SIGINT, sigint);
        signal (SIGSEGV, sigsegv);
        signal (SIGFPE, SIG_IGN);
        if (MakeHistory && !reloading)
        {
                settaskinfo("rebuilding history file");
                build_history();
        }
        if (Task->ti_state != nc_master)
            Exit (0);
        if (master_loop ())
        {  
            reloading = TRUE;
            goto reload;
        }
        NOTREACHED;
}

/*
 * Without the following, our custom calloc may not get linked resulting
 * in a version mismatch.
 */

void calloc_dummy() {
/* [<][>][^][v][top][bottom][index][help] */
        calloc(0, 0);
}

/* [<][>][^][v][top][bottom][index][help] */