src/nntpcache.c

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

FUNCTIONS

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. main
  38. calloc_dummy

/* $Id: nntpcache.c,v 1.4 1998/08/04 20:18:56 proff Exp $
 * $Copyright$
 */

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

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

#include "acc.h"
#include "article.h"
#include "build_history.h"
#include "date.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;
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 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 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;
EXPORT struct group_cfg *GroupList;
EXPORT struct server_cfg *CurrentGroupScfg;
EXPORT struct server_cfg *CurrentIDScfg;
EXPORT enum auth_state AuthState = none;
EXPORT char authUser[MAX_USERNAME] = "";
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 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 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",
    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 struct task_info **task_info_init ()
/* [<][>][^][v][top][bottom][index][help] */
{
    return (TaskList = XMcalloc(sizeof(struct task_info *), task_max));
}

/*
 * 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])
            goto found;
    loge (("task_info_new() no more tasks (max = %d)", task_max));
    return NULL;
 found:
    t = XMcalloc(sizeof *t, 1);
    t->ti_started = time(NULL);
    t->ti_state = state;
    t->ti_pid = getpid();
    t->ti_name = name;
    t->ti_idx = n;
    TaskList[n] = t;
    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];
    TaskList[n] = NULL;
    if (p)
        {
            if (p->ti_state == nc_client)
                Stats->clientsActive--;
            XMfree(p);
        }
    if (Stats->task_high<n)
        {
            for (n=Stats->task_high;n>=0 && !TaskList[n]; n--) {}
            Stats->task_high=n;
        }
}

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 (("SIGSEGV!"));
        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] && 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 */
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    action.sa_handler = SIG_IGN;
    sigaction(SIGALRM, &action, NULL);
    sigaction(SIGTERM, &action, NULL);
    sigaction(SIGPIPE, &action, NULL);
    
    if (Task->ti_state == nc_master)
        {
            chdir(con->cacheDir);
            if (!mmapAnon)
                {
                    int fd;
                    struct mmap_base mb;
                    bzero(&mb, sizeof mb);
                    mb.mmap_base = Mbase;
                    fd = open(con->mmapBaseFile, O_WRONLY|O_TRUNC|O_CREAT, 0664);
                    if (fd<0 || write(fd, &mb, sizeof mb)!=sizeof mb)
                        {
                            loge (("couldn't write '%s'", con->mmapBaseFile));
                            unlink (con->mmapBaseFile);
                        }
                    close (fd);
                }
            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", ClientHostNormal, 
                              ArtRead, GroupsEntered));
                        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,
                              time(NULL) - ClientTimeStarted));
                    }
            }
    if (Master_fd >= 0)
        {
            close (Master_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)
        {
            log (("clean shutdown"));
            closelog ();
            exit (code);
        }
    log (("clean shutdown with error %d. dumping core for debug analysis", code));
    chdir(con->cacheDir);
    abort ();
}

/*
 * if client is <0, then we presume we are not a regular client, but
 * an inernal daemon
 */

EXPORT int make_vm_proc (enum task_state state, int client, 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 (client>=0)
                close (client);
            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 (client>=0)
                {
                    sigaddset(&myaction.sa_mask, SIGPIPE);
                    myaction.sa_flags = 0;
                    myaction.sa_handler = sigpipe;
                    sigaction(SIGPIPE, &myaction, NULL);
                }
            closelog ();
            fclose (stdin);
            fclose (stdout);
            fclose (stderr);
            for (scfg = ServerList; scfg; scfg=scfg->next)
                if (scfg->fd >= 0 &&
                    scfg->fd != client)
                    { 
                        if (scfg->fd != client) /* XXX we should NOT need to do this! */
                            close (scfg->fd);
                        scfg->fd=-1;
                    }
            if (client >= 0)
                {
#ifdef SO_SNDBUF
                    setsockopt (client, SOL_SOCKET, SO_SNDBUF, (char *)&con->outputBufferSize, sizeof con->outputBufferSize);
#endif
                    if (client != 0)
                        {
                            dup2 (client, 0);
                            close (client);
                        }
                    dup2 (0, 1);
                    dup2 (1, 2);
#ifdef SO_KEEPALIVE
                    if (setsockopt (0, SOL_SOCKET, SO_KEEPALIVE, (char*) &yes, sizeof yes))
                        logd(("keepalive setsockopt failed for %s", name));
#endif
                    clientin = fdopen (0, "r");
                    clientout = fdopen (1, "w");
                    clienterr = fdopen (2, "w");
#ifdef CRASHES_UNDER_LINUX
                    setvbuf(clientout, io_buf, _IOFBF, IO_BUF_LEN);
#endif
                }
            close (i[0]);
            FD_SET(Master_fd, &r_set);
            for (n = 3; n <= high_fd; n++)
                {
                    if (n != Master_fd && FD_ISSET (n, &r_set))
                        {
                            close (n);
                            FD_CLR(n, &r_set);
                        }
                }
            sprintf (buf, "nntpcache-%.80s", name);
            id = Sstrdup(buf);
            openlog (id, LOG_PID, LOG_NEWS);
            log (("%s task awakening", name));
            ClientTimeStarted = time (NULL);
        }
    else /* parent */
        {
            if (client >= 0)
                    close (client);
            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];
                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;
                }
                list->host = Sstrdup (host);
                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(1);
        }
#else
        loge (("no chroot() call available on this system"));
        Exit(1);
#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 (1);
#endif
        }
        if (setuid (uid) == -1)
        {
                loge (("unable to set uid to %d", uid));
#ifndef DEBUG
                Exit (1);
#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.org] (c) 1996-1998 Julian Assange <proff@iq.org> %s ready (posting %s).\r\n", post_ok? NNTP_POSTOK_VAL: NNTP_NOPOSTOK_VAL, Host, VERSION, __DATE__, post_ok? "ok": "not permitted");
}

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);
    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.org>\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:
            emit("501 not supported\r\n");
            ret = FALSE;
            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 access denied <%s>, you do not have connect permissions in the %s file.\r\n", NNTP_ACCESS_VAL, 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 too many concurrent reader sessions already (%d). try again later\r\n", NNTP_TEMPERR_VAL, 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)
        {
            emitf ("%d initial server rebuild in progress (%d groups complete), please try again later.\r\n", NNTP_SERVERDOWN_VAL, Stats->list_stats[l_active].entries);
            log (("%s romance refused with %s (%s) due to rebuild in-progress (%d groups complete)", ClientHostNormal, ClientHost, ClientHostAddr, Stats->list_stats[l_active].entries));
            return FALSE;
        }
    if (con->minActive > Stats->list_stats[l_active].entries)
        {
            emitf ("%d  server rebuild in still in progress (%d , please try again later.\r\n", NNTP_SERVERDOWN_VAL);
            log (("%s romance refused with %s (%s) due to rebuild in-progress", ClientHostNormal, ClientHost, ClientHostAddr));
            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 (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);
            drop_priv(ncUID, ncGID);
            signal (SIGCHLD, sigchld);
            signal (SIGHUP, sighup);
            signal (SIGPIPE, SIG_IGN);
            signal (SIGUSR1, sigusr1);
            signal (SIGUSR2, sigusr2);
            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);
                }
            open_mmap();
            task_info_init ();
            loadStats (con->statsFile);
            Task = task_info_new (nc_master, "master"); /* task 0 == master daemon */
            Stats->task_stats[nc_master].invocations++;
            
        }
    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;
}
        
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 = 0;
    struct hostent *hp;
    enum task_state task;
    char *p;
    
         fprintf (stderr, "\
NNTPCache-" VERSION "\t(c) 1996-1998 Julian Assange <proff@iq.org>\n\
\t\t(c) 1996-1997 Luke Bowker <puke@suburbia.net>\n\
\t\tSee the file \"COPYING\", \"FAQ\", \"LICENSING\" or\n\
\t\thttp://www.nntpcache.org for copyright details\n");
         fflush(stderr);
         /* 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);
        if (!nodetach)
        {
#ifndef HAVE_DAEMON
                int fd;
#endif
                int n =
#ifdef HAVE_DTABLESIZE
                        getdtablesize ();
#else
                        256;
#endif
                while (n)
                        close (--n);

#ifdef HAVE_DAEMON
                daemon(1, 0);
#else
                if (fork ())
                        exit (0);

                if ((fd = open ("/dev/null", O_RDWR)) != -1)
                {
                        dup2 (fd, 0);
                        dup2 (0, 1);
                        dup2 (1, 2);
                }
#  ifdef TIOCNOTTY
                fd = open ("/dev/tty", O_RDWR | O_NOCTTY);
                if (fd != -1)
                {
                        ioctl (0, TIOCNOTTY, NULL);
                        close (fd);
                }
#  else
                setsid ();
#  endif
#endif
        }
        openlog ("nntpcached", LOG_PID, LOG_NEWS);
        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 (1);
        }
        if (!load_config (config_file))
                Exit (1);
        umask (con->umask);
        safeGroupInit (con->safeGroup);
        if (!nocemInit ())
            con->nocem = FALSE;
        if (!postInit())
                Exit (1);
        if (zorch)
        {
            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)
        {
                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 (1);
        }
        logd(("cwd now %s", con->configDir));
        if (!load_servers (con->serversFile))
                Exit (1);
        CurrentScfg = ServerList;
        if (!authReadConfig (con->accessFile))
                Exit (1);
        if (!sig_hup)
        {
                if (!(pw = getpwnam (con->user)))
                {
                        loge (("configuration error: no such user '%s'", con->user));
                        Exit (1);
                }
                ncUID = pw->pw_uid;
                if (!(gr = getgrnam (con->group)))
                {
                        loge (("configuration error: no such group '%s'", con->group));
                        Exit (1);
                }
                ncGID = gr->gr_gid;
                if (con->chroot)
                        perform_chroot();
                if (Task->ti_state != nc_master)
                        drop_priv(ncUID, ncGID);
        }
        if (chdir (con->cacheDir) == -1)
        {
                loge (("couldn't set cwd to %s", con->cacheDir));
                Exit (1);
        }
        logd(("cwd now %s", con->cacheDir));
        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)
        {
                settaskinfo("rebuilding history file");
                build_history();
        }
        if (Task->ti_state != nc_master)
            Exit (0);
        if (master_loop ())
            goto reload;
        NOTREACHED;
}

/*
 * Without this the 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] */