/*
 * this file is a part of yabbs - yet another bulletin board system.
 * Copyright (C) 1993, 1994 Alex Wetmore.  
 * email: alex@phred.org
 * address: 6 rech ave
 *          oreland pa 19075
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <stdarg.h>
#include <sys/wait.h>
#include "bbs_struct.h"
#include "bbs_files.h"
#include "llist.h"
#include "server.h"                         /* packet constants         */

#define YABBS
#define version "version 1.0beta"           /* current version          */
#define compiletime __DATE__                /* last compile time        */
#define MAXUTMP 128                         /* max people in "utmp"     */
                                            /* run from                 */

#define MAXTOKENS 512                       /* maximum number of tokens */
#define MAXPATH 1024                        /* max length of a pathname */
#define DOSYSLOG
#ifdef DOSYSLOG
#define SYSLOG syslog
#include <syslog.h>
#else
#define SYSLOG 1 ? NULL : fprintf
#endif

wrec    utmp[MAXUTMP];                      /* utmp file                */
brec    base[MBASES];                       /* the bases index          */
int     s,                                  /* our listen socket        */
        cl[MAXUTMP],                        /* our client sockets       */
        deadcl[MAXUTMP],                    /* true if client died      */
        clnum,                              /* number of clients        */
        sysop_base = -1,                    /* baseid of sysop base     */
        basenum[MBASES];                    /* number of messages for   */
                                            /*  each message base       */
urec    user[MAXUSERS];                     /* the user array           */
int     usernum;                            /* the number of users      */
int     shutdownpending = FALSE;            /* set when shutdown is on  */
/* the pointer in this should be a pointer to type int                  */
llist   log = NULL;                         /* ports that are logging   */
/* should be a pointer to type channel                                  */
llist   channels = NULL;                    /* talk channels            */
/* should be a pointer to type accesscont                               */
llist   accessl = NULL;                     /* access control list      */
char    commandkey[MAXCMDKEY];              /* command key              */

unsigned long int opn = 0, ipn = 0;

/*
 * useful macros 
 */

/* check to see if this user is a sysop or not                          */
#define sysop(utmpnum) \
    (((user[utmp[utmpnum].unum].options & O_SYSOP) == O_SYSOP) && \
    (utmp[utmpnum].valid))
/* check to see if this message base is for private messages            */
#define private(baseid) (baseid == 0)

/* function prototypes for things that need to be prototyped            */
void talkleave(int n, char *cname);
void killclient(int n);
int openmbase(char *uname, int baseid, FILE **fidx, char *idxmode, 
                           FILE **fdat, char *datmode);

#ifdef DEBUG
char *prettymsg(char *msg) {
    int x, l;
    static char buf[1024];

    for (x = 0, l = strlen(msg); x < l; x++) {
        if (msg[x] == 24) buf[x] = ':'; 
        else if ((msg[x] == 10) || (msg[x] == 13)) buf[x] = '\\';
        else buf[x] = msg[x];
    }
    buf[l] = 0;
    return buf;
}
#endif

char *bbsstrlwr(char *buf) {
    int i, l;

    l = strlen(buf);
    for (i = 0; i < l; i++) 
        buf[i] = lower(buf[i]);

    return buf;
}

void sendp(int n, int numarg, char *command, ...) {
    va_list ap;
    char *parameter, p2[MAXCMD], buf[MAXCMD];
    int i, x, y;

    if (deadcl[n]) return;

    va_start(ap, command);
    strcpy(buf, commandkey); strcat(buf, ":"); strcat(buf, command);
    for (i = 1; i < numarg; i++) {
        strcat(buf, ":");
        parameter = va_arg(ap, char *);
        for (x = 0, y = 0; parameter[x] != 0; x++) {
            if (parameter[x] == ':') p2[y++] = '\\'; 
            if (parameter[x] == '\\') p2[y++] = '\\'; 
            p2[y++] = parameter[x];
        }
        p2[y] = 0;
        strcat(buf, p2);
    }
    strcat(buf, "\n");

    if (send(cl[n], buf, strlen(buf), 0) <= 0) deadcl[n] = 1;
    dbg(printf("sending: (%u) %s to %i\n", ++opn, prettymsg(buf), n));
}

void asendp(int n, int numarg, char *command, ...) {
    va_list ap;
    char *parameter, p2[MAXCMD], buf[MAXCMD];
    int i, x, y;

    if (deadcl[n]) return;

    va_start(ap, command);
    strcpy(buf, "_ASYNC"); strcat(buf, ":"); strcat(buf, command);
    for (i = 1; i < numarg; i++) {
        strcat(buf, ":");
        parameter = va_arg(ap, char *);
        for (x = 0, y = 0; parameter[x] != 0; x++) {
            if (parameter[x] == ':') p2[y++] = '\\'; 
            if (parameter[x] == '\\') p2[y++] = '\\'; 
            p2[y++] = parameter[x];
        }
        p2[y] = 0;
        strcat(buf, p2);
    }
    strcat(buf, "\n");

    if (send(cl[n], buf, strlen(buf), 0) <= 0) deadcl[n] = 1;
    dbg(printf("sending: (%u) %s to %i\n", ++opn, prettymsg(buf), n));
}

void dothread(void (*func)(), int argc, char **argv) {
    func(argc, argv);
}

void forkthread(void (*func)(), int argc, char **argv) {
    int cpid;

    if ((cpid = fork()) != 0) {         /* break off a child process        */
        if (cpid == -1) {
            perror("server: dothread: fork");
            exit(1);
        }
        dbg(printf("dothread: spawned child (id = %i)\n", cpid));
        return;
    }

    func(argc, argv);

    dbg(printf("dothread: child finished\n"));
    exit(1);
}

FILE *openfile(char *filename, char *mode) {
    FILE *f;
#ifdef DEBUG
    char buf[MAXPATH];
#endif

    dbg(printf("opening %s (pwd = %s) for %s\n", filename, getwd(buf), mode));
    f = fopen(filename, mode);
    if (f == NULL) {
        dbg(printf("error opening %s for %s\n", filename, mode));
        dbg(perror("server:openfile"));
    }

    return f;
}

/*
 * generate the pathname to the user's private mailbox
 * ext == 'i' for index, 'd' for data.
 */
char *make_pbasename(char *buf, int basenum, char *uname, char ext) {
    sprintf(buf, "%s%s/%s%s", F_MSGDIR, base[basenum].dfile, uname,
        (ext == 'i') ? F_MIDXEXT : F_MDATAEXT);

    return buf;
}

/*
 * create an empty mailbox for a user
 */
int mk_mailbox(char *uname) {
    FILE *tmp;
    char buf[1024];
    irec idx;

    if ((tmp = openfile(make_pbasename(buf, 0, uname, 'i'), "w")) == NULL)
        return FALSE;

    idx.offset = 0;
    idx.length = HDRLEN;
    fwrite(&idx, sizeof(idx), 1, tmp);

    fclose(tmp);
    return TRUE;
}

/*
 * check to see if a user has a mailbox.  pass in the username or NULL
 */
int has_mailbox(char *uname) {
    FILE *tmp;
    char fname[MAXPATH];

    if ((tmp = openfile(make_pbasename(fname, 0, uname, 'i'), "r")) == NULL) 
        return FALSE;

    fclose(tmp);
    return TRUE;
}

/*
 * count the number of messages in base <baseid> for user <uname>.  
 */

int count_msgs(char *name, int baseid) {
    FILE *fidx, *fdat;
    int n = 0, tmp;

    if (!openmbase(name, baseid, &fidx, "rb", &fdat, "rb") == -1) 
        perror("server: count_msgs: openmbase");

    fseek(fidx, 0, SEEK_END);
    n = (int) (ftell(fidx) / sizeof(irec)) - 1;
    dbg(printf("base %i has %i messages\n", baseid, basenum[baseid]));

    fclose(fdat);
    fclose(fidx);
    return n;
}


/*
 * open a message base and return file pointers to the index and data files.
 * if this is a private base it opens the message base for user <uname>.  if
 * you don't care about that pass in NULL for uname.  Returns TRUE if
 * success, FALSE otherwise.
 */
int openmbase(char *uname, int baseid, FILE **fidx, char *idxmode, 
                           FILE **fdat, char *datmode) {
    char fnamedat[MAXPATH];
    char fnameidx[MAXPATH];

    if (private(baseid)) {
        if ((uname != NULL) && (has_mailbox(uname))) {
            /* open the mailbox for a private user                      */
            make_pbasename(fnameidx, baseid, uname, 'i');
        } else {
            /* open the nologin mailbox                                 */
            make_pbasename(fnameidx, baseid, "nologin", 'i');
            if (!has_mailbox("nologin")) {
                FILE *f;
                irec idx;

                f = openfile(fnameidx, "wb");
                if (f == NULL) perror("couldn't create nologin mailbox");

                idx.offset = 0;
                idx.length = HDRLEN;
                fwrite(&idx, sizeof(idx), 1, f);
                fclose(f);
            }
        }
    } else {
        /* open the public index                                        */
        strcpy(fnameidx, F_MSGDIR);
        strcat(fnameidx, base[baseid].dfile); 
        strcat(fnameidx, F_MIDXEXT);
    }

    strcpy(fnamedat, F_MSGDIR); 
    strcat(fnamedat, base[baseid].dfile); 
    strcat(fnamedat, F_MDATAEXT);

    *fidx = openfile(fnameidx, idxmode);
    *fdat = openfile(fnamedat, datmode);
    /* if the index file or data files weren't there create new ones    */
    if ((*fdat == NULL) || (*fidx == NULL)) {
        mrec msg;
        irec idx;
        time_t now;

        /* close files that opened successfully                         */
        if (*fdat != NULL) fclose(*fdat); 
        if (*fidx != NULL) fclose(*fidx); 

        /* make a blank message                                         */
        strcpy(msg.time, (char *) ctime((time_t *) &now));
        msg.time[strlen(msg.time) - 1] = 0;
        strcpy(msg.to, "null");
        strcpy(msg.from, "yabbsd");
        strcpy(msg.title, "New message base creation");
        msg.deleted = 'N';
        *(msg.msg) = 0;

        /* come up with the index record                                */
        idx.offset = 0;
        idx.length = HDRLEN + strlen(msg.msg);

        /* create a new data file and write our message                 */
        *fdat = openfile(fnamedat, "wb");
        if (*fdat == NULL) return FALSE;
        fwrite(&msg, idx.length, 1, *fdat);
        fclose(*fdat);

        /* write the index file                                         */
        *fidx = openfile(fnameidx, "wb");
        if (*fidx == NULL) return FALSE;
        fwrite(&idx, sizeof(idx), 1, *fidx);
        fclose(*fidx);

        /* if this is the email base and a sysop base exists update its */
        /* index file as well                                           */
        if (sysop_base != -1) {
            strcpy(fnameidx, F_MSGDIR);
            strcat(fnameidx, base[baseid].dfile); 
            strcat(fnameidx, F_MIDXEXT);
            *fidx = openfile(fnameidx, "wb");
            if (*fidx == NULL) return FALSE;
            fwrite(&idx, sizeof(idx), 1, *fidx);
            fclose(*fidx);
        }

        /* open the message bases we made in their requested mode       */
        *fidx = openfile(fnameidx, idxmode);
        *fdat = openfile(fnamedat, datmode);
    }
    if ((*fdat == NULL) || (*fidx == NULL)) return FALSE;
    return TRUE;
}

/*            n
 * return base .  Integers only.
 */

unsigned long int power(int base, int n) {
    int                 i;
    unsigned long int   p;

    p = 1;
    for (i = 1; i <= n; i++)
        p = p * base;
    return p;
}

/* 
 * check to see if user <u> has access to base <basenum>.
 */

int accesscheck(int u, int basenum) {
    if ((basenum == ' ') || (basenum == -1)) return TRUE;
    if ((power(2, basenum) & user[u].access) &&
        (!nullbase(basenum)))
            return TRUE; else return FALSE;
}

/*
 * check to see if user <u> is subscribed to base <basenum>.
 */

int subcheck(int u, int basenum) {
    if ((power(2, basenum) & user[u].sub) && (accesscheck(u, basenum))) 
        return TRUE;
    else
        return FALSE;
}

void gsendp(int s, char *msg) {
    send(s, msg, strlen(msg), 0);
}

char *greadp(int s) {
    int reading = 1, i = 0;
    char x, c; 
    static char buf[MAXCMD];

    /* read characters until we get a NL or end of a packet                 */
    while (reading) {
        x = read(s, &c, 1);
        if ((x < 1) || (c == '\n') || (i == 254)) reading = 0;
        else if (c != '\r') buf[i++] = c;
    }
    buf[i] = 0;

    return buf;
}

int gopherconnect(char *server, int port) {
    struct sockaddr_in sin;
    struct hostent *hp;
    int s;

    dbg(printf("gopher: connect to %s %i\n", server, port));
    hp = gethostbyname(server);
    if (hp == NULL) {
        return -1;
    }
    bzero((char *) &sin, sizeof(sin));
    bcopy(hp->h_addr, (char *) &sin.sin_addr, hp->h_length);
    sin.sin_family = hp->h_addrtype;
    sin.sin_port = htons(port);

    s = socket(AF_INET, SOCK_STREAM, 0);

    if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) == -1) {
        return -1;
    }

    return s;
}

void gopherreaddir(int n, char *server, int port, char *dirpath) {
    char *token[20], **tp = token, *p;
    int x, done, s, l;
    char msg[MAXCMD], name[MAXCMD], buf[MAXCMD]; 

    s = gopherconnect(server, port);
    if (s == -1) {
        sendp(n, 2, "GFILEINDEX", "NOFILE");
        return;
    }
    dbg(printf("gopher: getting directory %s\n", dirpath));

    gsendp(s, dirpath);
    gsendp(s, "\n");
    done = 0;
    sendp(n, 2, "GFILEINDEX", "START");
    while (!done) {
        strcpy(msg, greadp(s));
        if (strcmp(msg, ".") == 0) {
            done = 1;
        } else {
            p = msg;                        /* parse string p               */

            tp = token;
            for(x = 0; (x < 4); x++) {
                *(tp++) = (char *) strsep(&p, "\t");
            }
            if ((token[0][0] == '0') || (token[0][0] == '1')) {
                for(x = 1, l = strlen(token[0]); x < l; x++) 
                    name[x - 1] = token[0][x];
                name[x - 1] = 0;
                token[0][1] = 0;
                sprintf(buf, "%s\t%s\t%s", token[1], token[2], token[3]);
                sendp(n, 5, "GFILEINDEX", "LINE", token[0], name, buf);
            }
        }
    }
    sendp(n, 2, "GFILEINDEX", "END");
}

void gopherreadfile(int n, char *server, int port, char *dirpath) {
    char done, msg[MAXCMD]; 
    int s;

    s = gopherconnect(server, port);
    if (s == -1) {
        sendp(n, 2, "GFILEINDEX", "NOFILE");
        return;
    }
    dbg(printf("gopher: getting file %s\n", dirpath));

    gsendp(s, dirpath);
    gsendp(s, "\n");
    done = 0;
    sendp(n, 2, "GFILE", "START");
    while (!done) {
        strcpy(msg, greadp(s));
        if (strcmp(msg, ".") == 0) {
            done = 1;
        } else {
            sendp(n, 3, "GFILE", "LINE", msg);
        }
    }
    sendp(n, 2, "GFILE", "END");
}

/*
 * read in a base defination file.  If there isn't one, crash the program
 * and tell the sysop to make one.  Don't let this happen to your users, it
 * will just leave them hanging if they are networked.
 */

void readbases(void) {
    FILE    *fi;
    FILE    *fidx;
    FILE    *fdat;
    int     i;
    irec    idx;

    /* read the message base title database                                 */
    if ((fi = openfile(F_BASES, "rb")) == NULL) {
        fprintf(stderr, "server: unable to open %s, closing down\n", F_BASES);
        exit(1);
    }
    fread(base, sizeof(brec), MBASES, fi);
    fclose(fi);

    /* find a sysop base if there is one                                    */
    for (i = 0; i < MBASES; i++)
        if (sysopbase(i)) sysop_base = i;

    /* get a message count for each base                                    */
    for (i = 0; i < MBASES; i++) {
        if (!nullbase(i)) basenum[i] = count_msgs(NULL, i);
        else basenum[i] = 0;
    }
}

/*
 * read the user defination file and stick it into memory.
 */

void readusers(void) {
    FILE    *fi;

    if ((fi = openfile(F_USERS, "rb")) == NULL) {
        fprintf(stderr, "server: no user file, dying\n");
        exit(1);
    }
    usernum = 0;
    while (fread(&user[usernum], sizeof(urec), 1, fi) != 0) {
        usernum++;
    }
    dbg(printf("%i users defined\n", usernum));
    fclose(fi);
}

/*
 * write the user defination file from the user structure in memory.
 */

void writeusers(int argc, char **argv) {
    int fo;

    fo = open(F_USERS, O_WRONLY | O_TRUNC | O_CREAT, UMASK);
    if (fo == -1) {
        perror("server: writeusers: open");
        return;
    }
    write(fo, user, sizeof(urec) * usernum);
    close(fo);
    sync();
    dbg(printf("server: wrote user file\n"));
}

/* 
 * read a gfile index from <gfilname> into gfil.  return the number of items
 * in the index, or -1 on error.
 */

int readgfil(grec *gfil, char *gfilname) {
    int     fi, gnum;
    char    fname[MAXPATH];

    strcpy(fname, F_GFILDIR);
    strcat(fname, gfilname);
    if ((fi = open(fname, O_RDONLY, 0)) < 0) {
        return -1;
    } else {
        gnum = 0;
        while (read(fi, (char *) &gfil[gnum++], (off_t) sizeof(grec)) 
            == sizeof(grec));
        close(fi);
    }
    return(gnum - 1);
}

char *itoa(int i) {
    static char str[16];

    sprintf(str, "%i", i);
    return str;
}

/*
 * remove any occurences of the character <c> from the string <str>
 */

char *removechar(char *str, char c) {
    int i, j, l;

    l = strlen(str);
    for (i = j = 0; i < l; i++) 
        if (str[i] != c) str[j++] = str[i];
    str[j] = 0;

    return str;
}

void die(void) {
    SYSLOG(LOG_NOTICE, "yabbs shutdown");
    close(s);
    exit(0);
}

void sigpipe(void) {
}

void dofork(void) {
    int id, devnull;

    if ((id = fork()) > 0) {
        printf("server: spawned child id = %i\n", id);
        exit(0);
    } else {
        devnull = open("/dev/null", O_RDWR, 0);
        if (devnull != -1) {
            dup2(devnull, 0);
            dup2(devnull, 1);
            dup2(devnull, 2);
            if (devnull > 2) close(devnull);
        }
    }
}

int accesscontroladd(int n, char *numaddr, char *dnsaddr) {
    int l, i, j;
    llist p;
    accesscont *ac;
    char buf[255];

    bbsstrlwr(dnsaddr);
    p = accessl;
    while (p != NULL) {
        ac = (accesscont *) p->ptr;
        l = strlen(ac->hoststring);
        if ((strcmp(ac->hoststring, numaddr) == 0) || 
            (strcmp(ac->hoststring, dnsaddr) == 0)) {
            utmp[n].hostaccess = ac;
            if ((++ac->numusers > ac->maxusers)) return 0; else return 1;
        } else if (ac->hoststring[l] == '.') {
            if (strncmp(ac->hoststring, numaddr, l - 1) == 0) {
                utmp[n].hostaccess = ac;
                if ((++ac->numusers > ac->maxusers)) return 0; else return 1;
            }
        } else if (ac->hoststring[0] == '.') {
            j = strlen(dnsaddr);
            for (i = 0; i < l - 1; i++) 
                buf[i] = dnsaddr[((j - l) + i) + 1];
            buf[i] = 0;
            if (strncmp(&(ac->hoststring[1]), buf, l - 1) == 0) {
                utmp[n].hostaccess = ac;
                if ((++ac->numusers > ac->maxusers)) return 0; else return 1;
            }
                    
        }
        p = p->next;
    }
    /* this shouldn't ever happen */
    utmp[n].hostaccess = NULL;
    return 0;
}

void accesscontrolkill(int n) {
    if (utmp[n].hostaccess != NULL) utmp[n].hostaccess->numusers--;
}

void addclient(void) {
    int clsinlen, fromlen, c, x;
    struct sockaddr_in clsin = { AF_INET };
    struct sockaddr_in from = { AF_INET };
    char numaddr[33];
    struct hostent *hp;

    clsinlen = sizeof(clsin);
    c = clnum++;
    cl[c] = accept(s, (struct sockaddr *) &clsin, &clsinlen);
    if (cl[c] < 0) {
        perror("server: accept");
        exit(1);
    }
    dbg(printf("got client %i\n", c));

    fromlen = sizeof(from);
    if (getpeername(cl[c], (struct sockaddr *) &from, &fromlen) < 0) {
        perror("server: getpeername");
        exit(1);
    }

    hp = gethostbyaddr((char *) &from.sin_addr, sizeof(struct in_addr), 
        from.sin_family);
    strcpy(numaddr, (char *) inet_ntoa(from.sin_addr));
    if (hp != NULL)
        if (hp->h_name != NULL) strncpy(utmp[c].addr, hp->h_name, 32);
    else  
        strcpy(utmp[c].addr, numaddr);
    for (x = 0; x < 32; x++) utmp[c].addr[x] = lower(utmp[c].addr[x]);
    utmp[c].addr[32] = 0;

    strcpy(commandkey, "_NEWCLI");
    sendp(c, 2, "YABBSHI", SERVERVERSION);

    strcpy(utmp[c].name, "nologin");
    utmp[c].channels = NULL;
    utmp[c].timeon = time(NULL);
    utmp[c].timelast = time(NULL);
    utmp[c].valid = FALSE;
    utmp[c].unum = 0;
    utmp[c].announce = 0;
    deadcl[c] = 0;

    if ((accesscontroladd(c, numaddr, utmp[c].addr) == 0)) {
        sendp(c, 2, "BAD", "too many clients from your site already");
        close(cl[c]);
        killclient(c);
    }
    if (clnum == MAXUSERS) {
        sendp(c, 2, "BAD", "yabbs is full right now, try again later");
        close(cl[c]);
        killclient(c);
    }
}

int logptrcomp(void *p1, void *p2) {
    if (*((int *) p1) == *((int *) p2)) return 1; else return 0;
}

void writetolog(int n, int numarg, char *arg, ...) {
    char *parameter, buf[MAXCMD];
    va_list ap;
    int i;
    llist p;

    if (log == NULL) return;

    strcpy(buf, arg);
    va_start(ap, arg);
    for (i = 1; i < numarg; i++) {
        strcat(buf, "\t");
        parameter = va_arg(ap, char *);
        strcat(buf, parameter);
    }

    p = log;
    while (p != NULL) {
        asendp(*((int *) p->ptr), 4, "LOG", itoa(n), utmp[n].name, buf);
        p = p->next;
    }
}

/*
 * convert a channel name to something that is safe for a particular user
 * to see.  this handles channels being hidden and doing the right thing
 * when that happens
 */
char *convertcname(int n, char *channel) {
    crec c, getcrec(char *channel);
    static char cbuf[20];

    c = getcrec(channel);

    if (c->hidden) {
        if (useronchannel(n, channel)) {
            sprintf(cbuf, "*%s*", channel);
            return cbuf;
        } else {
            sprintf(cbuf, "*priv%x*", ((unsigned) c) & 0xffff);
            return cbuf;
        }
    } else return channel;
}

/*
 * announce an async action, like a user login.  if we were cool this
 * would be done better (so we would just have to change server.h and
 * add the appropriate announce() calls in server.c).  instead we need
 * to fix this function, the s_request function, and the two above
 * places.  such is life.
 */
void announceto(int x, int announcetype, char *arg1, char *arg2) {
    char abuf[12];

    if (utmp[x].announce & announcetype) {
        switch (announcetype) {
            case AN_LOGIN      : strcpy(abuf, "LOGIN"); break;
            case AN_LOGOUT     : strcpy(abuf, "LOGOUT"); break;
            case AN_NEWMSG     : strcpy(abuf, "NEWMSG"); break;
            case AN_CHANJOIN   : strcpy(abuf, "CHANJOIN"); break;
            case AN_CHANLEAVE  : strcpy(abuf, "CHANLEAVE"); break;
            case AN_CHANCREATE : strcpy(abuf, "CHANCREATE"); break;
            case AN_CHANDESTROY: strcpy(abuf, "CHANDESTROY"); break;
            case AN_CHANNEWNAME: strcpy(abuf, "CHANNEWNAME"); break;
            default:             strcpy(abuf, "UNKNOWN");
        }

        asendp(x, 4, "ANNOUNCE", abuf, arg1, arg2);
    }
}

void announce(int announcetype, char *arg1, char *arg2) {
    int x;

    for (x = 0; x < clnum; x++) announceto(x, announcetype, arg1, arg2);
}

void killclient(int n) {
    int x;
    llist p;
    char cname[255];

    accesscontrolkill(n);

    writetolog(n, 1, "LOGOUT");
    SYSLOG(LOG_INFO, "logout %s from %s", utmp[n].name, utmp[n].addr);
    if (utmp[n].valid) announce(AN_LOGOUT, utmp[n].name, utmp[n].addr);

    while (utmp[n].channels != NULL) {
        strcpy(cname, utmp[n].channels->ptr);
        for (x = 0; x < clnum; x++) 
            if (useronchannel(x, cname)) 
                asendp(x, 5, "TALK", "LEAVE", cname, utmp[n].name, "");
        talkleave(n, (char *) cname);
    }

    dbg(printf("closing client %i\n", n));
    close(cl[n]);
    for (x = n; x < clnum; x++) {
        cl[x] = cl[x + 1];
        utmp[x] = utmp[x + 1];
        deadcl[x] = deadcl[x + 1];
    }
    clnum--;
    log = deletefromlist(log, &n, logptrcomp);
    p = log;
    while (p != NULL) {
        if (*((int *) p->ptr) > n) *((int *) p->ptr) = *((int *) p->ptr) - 1;
        p = p->next;
    }
}

char *despaceandlower(char *str) {
    int i, j, l;

    if (str == NULL) return str;

    l = strlen(str);
    for (i = j = 0; i < l; i++) {
        if (str[i] != ' ') str[j++] = lower(str[i]);
    }
    str[j] = 0;
     
    return str;
}

char *fixcname(char *str) {
    int i, j, l;
    static char buf[255];

    l = strlen(str);
    for (i = j = 0; i < l; i++) {
        if ((str[i] != ' ') && (str[i] != '*')) buf[j++] = lower(str[i]);
    }
    buf[j] = 0;

    return buf;
}
int namecmp(char *n1, char *n2) {
    char name1[255], name2[255];

    strcpy(name1, n1); despaceandlower(name1);
    strcpy(name2, n2); despaceandlower(name2);
    return strcmp(name1, name2);
}

int stringptrcomp(void *p1, void *p2) {
    return (strcmp((char *) p1, (char *) p2) == 0);
}

int cnameptrcomp(void *p1, void *p2) {
    return (strcmp(((crec) p1)->name, (char *) p2) == 0);
}

crec getcrec(char *cname) {
    llist c;

    /* get channel pointer */
    c = channels;
    while ((strcmp(((crec) c->ptr)->name, cname)) && (c != NULL)) c = c->next;
    return (crec) c->ptr;
}

void talkjoin(int n, char *cname, int hidden) {
    char oname[20];             /* channel's original name (if hidden)      */
    llist p;                    /* linked list pointer                      */
    crec chn;                   /* channel record                           */
    char *cnamep, *userid;      /* channel name pointer, user id pointer    */
    int x;                      /* counter                                  */

    p = channels;
    while ((p != NULL) && strcmp(((crec) p->ptr)->name, cname)) p = p->next;
    if (p == NULL) {
        /* create a new channel                                             */
        chn = (crec) malloc(sizeof(struct chanrec));
        strcpy(chn->name, cname);
        chn->people = NULL;
        chn->hidden = hidden;
        channels = addtolist(channels, (void *) chn);

        /* add this user to the channel's user list                         */
        userid = (char *) malloc(sizeof(char) * (strlen(utmp[n].name) + 1));
        strcpy(userid, utmp[n].name);
        chn->people = addtolist(chn->people, (void *) userid);

        /* add channel to user                                              */
        cnamep = (char *) malloc(sizeof(char) * (strlen(cname) + 1));
        strcpy(cnamep, cname);
        utmp[n].channels = addtolist(utmp[n].channels, cnamep);

        /* announce channel creation                                        */
        for (x = 0; x < clnum; x++)
            announceto(x, AN_CHANCREATE, convertcname(x, cname), utmp[n].name);
    } else {
        /* add user to an already existing channel                          */
        chn = ((crec) p->ptr);
        if (hidden && !chn->hidden) {
            char oname[13], nname[13];

            chn->hidden = TRUE;

            if (strcmp(oname, nname) != 0) 
                for (x = 0; x < clnum; x++) {
                    chn->hidden = FALSE; strcpy(oname, convertcname(x, cname));
                    chn->hidden = TRUE ; strcpy(nname, convertcname(x, cname));
                    announceto(x, AN_CHANNEWNAME, oname, nname);
                }

            for (x = 0; x < clnum; x++) 
                if (useronchannel(x, cname))
                    asendp(x, 5, "TALK", "HIDE", cname, utmp[n].name, "");
            writetolog(n, 3, "TALK", "HIDE", cname);
        }

        /* record channels original name                                    */
        if (chn->hidden) strcpy(oname, convertcname(n, cname));

        /* add this user to the channel's user list                         */
        userid = (char *) malloc(sizeof(char) * (strlen(utmp[n].name) + 1));
        strcpy(userid, utmp[n].name);
        chn->people = addtolist(chn->people, (void *) userid);

        /* add channel to user                                              */
        cnamep = (char *) malloc(sizeof(char) * (strlen(cname) + 1));
        strcpy(cnamep, cname);
        utmp[n].channels = addtolist(utmp[n].channels, cnamep);

        /* if the channel was hidden then we need to let the user's client  */
        /* know about the channels real name through announce.              */
        if (chn->hidden)
            announceto(n, AN_CHANNEWNAME, oname, convertcname(n, cname));
    }

    /* announce join to all clients                                         */
    for (x = 0; x < clnum; x++)
        announceto(x, AN_CHANJOIN, convertcname(x, cname), utmp[n].name);
}

void talkleave(int n, char *cname) {
    crec chn;
    int x;
    char oname[20];
    
    /* get channel pointer                                                  */
    chn = getcrec(cname);
    if (chn == NULL) return;

    /* get channel's old name for client and hidden flag                    */
    strcpy(oname, convertcname(n, cname));

    /* remove user from channel                                             */
    chn->people = deletefromlist(chn->people, utmp[n].name, stringptrcomp);
    for (x = 0; x < clnum; x++)
        announceto(x, AN_CHANLEAVE, convertcname(x, cname), utmp[n].name);

    /* remove channel from user                                             */
    utmp[n].channels = deletefromlist(utmp[n].channels, cname, stringptrcomp);

    /* notify about the channel name changing if channel is hidden          */
    if (chn->hidden) 
        announceto(n, AN_CHANNEWNAME, oname, convertcname(n, cname));

    /* remove channel if it is empty                                        */
    if (chn->people == NULL) {
        for (x = 0; x < clnum; x++)
            announceto(x, AN_CHANDESTROY, convertcname(x, cname), utmp[n].name);
        channels = deletefromlist(channels, cname, cnameptrcomp);
    }
}

int useronchannel(int n, char *cname) {
    llist c;

    c = utmp[n].channels;
    while (c != NULL) {
        if (strcmp((char *) c->ptr, cname) == 0) return TRUE;
        c = c->next;
    }
    return FALSE;
}

void s_help(int n, char **argv) {
    int i;

    sendp(n, 2, "HELP", "START");
    for (i = 0; i < S_CNUM; i++)
        sendp(n, 3, "HELP", "LINE", s_ctable[i].cname);
    sendp(n, 2, "HELP", "END");
}

void s_login(int n, char **argv) {
    int     i, online;
    int     u = -1;
    FILE    *fi, *fo;
    char    last3[3][81];
    char    atime[40];

    if (utmp[n].valid) {
        sendp(n, 2, "BAD", "logout first");
    } else if (argv[1] == NULL) {
        sendp(n, 2, "LOGIN", "password cannot be blank");
    } else {
        /* check to make sure that this guy isn't do multiple logins    */
        online = FALSE;
        for (i = 0; i < clnum; i++) 
            if (namecmp(utmp[i].name, argv[0]) == 0) online = TRUE;
        if (online) {
            sendp(n, 2, "LOGIN", "that account is currently in use");
            return;
        }

        /* find a user with this name                                   */
        for (i = 0; i < usernum; i++)
            if (namecmp(user[i].name, argv[0]) == 0) u = i;

        if (u == -1) {                          /* none found           */
            sendp(n, 2, "LOGIN", "invalid login (no such user)");
            return;
        /* check password                                               */
        } else if ((strcmp(user[u].pass, argv[1]) != 0) || (*argv[1] == 0)) {
            sendp(n, 2, "LOGIN", "invalid login (bad password)");
            return;
        } 

        if (shutdownpending && ((user[u].options & O_SYSOP) != O_SYSOP)) {
            sendp(n, 2, "LOGIN", "invalid login (shutdown pending)");
            return;
        }

        /* log them in                                                  */
        strcpy(utmp[n].name, user[u].name);
        utmp[n].timeon = time(NULL);
        utmp[n].valid = TRUE;
        utmp[n].unum = u;
        dbg(printf("login: %s\n", argv[0]));
        sendp(n, 2, "LOGIN", "OK");

        for (i = 0; i < 3; i++) strcpy(last3[i], "");
        if ((fi = openfile(F_SLASTU, "r")) != NULL) {
            while (!feof(fi)) {
                strcpy(last3[0], last3[1]);
                strcpy(last3[1], last3[2]);
                fgets(last3[2], 80, fi);
            }
            fclose(fi);
        }
    
        if ((fo = openfile(F_SLASTU, "w")) != NULL) {
            strcpy(atime, (char *) ctime((time_t *) &utmp[n].timeon));
            atime[strlen(atime) - 1] = 0;
            fprintf(fo, "%s%s  %s at %s from %s\n", 
              &last3[0][0], &last3[1][0], utmp[n].name, 
              atime, utmp[n].addr);
            fclose(fo);
        }
        writetolog(n, 1, "LOGIN");
        announce(AN_LOGIN, utmp[n].name, utmp[n].addr);
        SYSLOG(LOG_INFO, "login %s from %s", utmp[n].name, utmp[n].addr);
    }
}

void s_logout(int n, char **argv) {
    dbg(printf("logout: %s\n", utmp[n].name));
    killclient(n);
}

void s_who(int n, char **argv) {
    int x;
    int show, logging;
    llist p;
    time_t now;
    char buf[255], onfrom[33];

    now = time(NULL);
    sendp(n, 6, "WHO", "START", "", "", "", "");
    for (x = 0; x < clnum; x++) {

        show = TRUE;
        logging = FALSE;
        if (strcmp(utmp[x].name, "nologin") == 0) show = FALSE;
        if (show) {
            for (p = log; p != NULL; p = p->next) { 
                if (*((int *) p->ptr) == x) logging = TRUE; 
            }
        }
        if (sysop(n)) {
            show = TRUE;
            if (logging) strcpy(onfrom, "logging"); 
            else strcpy(onfrom, utmp[x].addr);
        } else {
            if (logging) show = FALSE;
            strcpy(onfrom, utmp[x].addr);
        }
        if (show) {
            strcpy(buf, itoa(now - utmp[x].timelast));
            sendp(n, 6, "WHO", "LINE", utmp[x].name, onfrom, 
                itoa(utmp[x].timeon), buf);
        }
    }
    sendp(n, 6, "WHO", "END", "", "", "", "");
}

void s_channels(int n, char **argv) {
    llist c, p, l;
    int logging;
    char name[15], people[255];

    c = channels;
    sendp(n, 4, "CHANNELS", "START", "", "");
    while (c != NULL) {
        if (((crec) c->ptr)->hidden)
            if (useronchannel(n, ((crec) c->ptr)->name) || sysop(n))
                sprintf(name, "*%s*", ((crec) c->ptr)->name);
            else sprintf(name, "*priv%x*", ((unsigned) (c->ptr)) & 0xffff);
        else strcpy(name, ((crec) c->ptr)->name);
        strcpy(people, "");
        p = ((crec) c->ptr)->people;
        while ((p != NULL) && (strlen(people) < 200)) {
            l = log; 
            logging = FALSE;
            while (l != NULL) { 
                if (namecmp(utmp[*((int *) l->ptr)].name, p->ptr) == 0) 
                    logging = TRUE; 
                l = l->next; 
            }
            if (!logging) {
                strcat(people, (char *) p->ptr);
                strcat(people, ",");
            }
            p = p->next;
        }
        people[strlen(people) - 1] = 0;
        sendp(n, 4, "CHANNELS", "LINE", name, people);
        c = c->next;
    }
    sendp(n, 4, "CHANNELS", "END", "", "");
}

void s_talk(int n, char **argv) {
    char cname[15], name[15], cmd[21];
    crec chn;
    int x;
    llist p;

    if (argv[0] == NULL) strcpy(cmd, "");
    else { strncpy(cmd, despaceandlower(argv[0]), 10); cmd[10] = 0; }
    if (argv[1] == NULL) strcpy(cname, "");
    else { strncpy(cname, fixcname(argv[1]), 10); cname[10] = 0; }

    if (strcmp(cmd, "join") == 0) {
        if (!useronchannel(n, cname)) {
            talkjoin(n, cname, FALSE);
            for (x = 0; x < clnum; x++)
                if (useronchannel(x, cname))
                    asendp(x, 5, "TALK", "JOIN", cname, utmp[n].name, "");
            writetolog(n, 3, "TALK", "JOIN", cname);
        }
    } else if (strcmp(cmd, "joinpriv") == 0) {
        if (!useronchannel(n, cname)) {
            talkjoin(n, cname, TRUE);
            for (x = 0; x < clnum; x++)
                if (useronchannel(x, cname))
                    asendp(x, 5, "TALK", "JOIN", cname, utmp[n].name, "");
            writetolog(n, 3, "TALK", "JOIN", cname);
        }   
    } else if (strcmp(cmd, "leave") == 0) {
        if (useronchannel(n, cname)) {
            for (x = 0; x < clnum; x++) 
                if (useronchannel(x, cname)) 
                    asendp(x, 5, "TALK", "LEAVE", cname, utmp[n].name, "");
            talkleave(n, cname);
            writetolog(n, 3, "TALK", "LEAVE", cname);
        }
    } else if (strcmp(cmd, "quit") == 0) {
        p = utmp[n].channels;
        while (p != NULL) {
            strcpy(name, (char *) p->ptr);
            for (x = 0; x < clnum; x++) 
                if (useronchannel(x, name)) 
                    asendp(x, 5, "TALK", "LEAVE", name, utmp[n].name, "");
            talkleave(n, name);
            p = p->next;
        }
        writetolog(n, 2, "TALK", "QUIT");
    } else if (strcmp(cmd, "hide") == 0) {
        if (useronchannel(n, cname)) {
            char oname[20], nname[20];

            chn = getcrec(cname);
            if (chn->hidden) return;

            if (strcmp(oname, nname) != 0) 
                for (x = 0; x < clnum; x++) {
                    chn->hidden = FALSE; strcpy(oname, convertcname(x, cname));
                    chn->hidden = TRUE ; strcpy(nname, convertcname(x, cname));
                    announceto(x, AN_CHANNEWNAME, oname, nname);
                }

            for (x = 0; x < clnum; x++) 
                if (useronchannel(x, cname))
                    asendp(x, 5, "TALK", "HIDE", cname, utmp[n].name, "");
            writetolog(n, 3, "TALK", "HIDE", cname);
        }
    } else if (strcmp(cmd, "show") == 0) {
        if (useronchannel(n, cname)) {
            char oname[20], nname[20];
            
            chn = getcrec(cname);
            if (!chn->hidden) return;

            if (strcmp(oname, nname) != 0) 
                for (x = 0; x < clnum; x++) {
                    chn->hidden = TRUE ; strcpy(oname, convertcname(x, cname));
                    chn->hidden = FALSE; strcpy(nname, convertcname(x, cname));
                    announceto(x, AN_CHANNEWNAME, oname, nname);
                }

            for (x = 0; x < clnum; x++) 
                if (useronchannel(x, cname))
                    asendp(x, 5, "TALK", "SHOW", cname, utmp[n].name, "");
            writetolog(n, 3, "TALK", "SHOW", cname);
        } 
    } else if (strcmp(cmd, "public") == 0) {
        if (useronchannel(n, cname)) {
            for (x = 0; x < clnum; x++) 
                if (useronchannel(x, cname)) 
                    asendp(x,5,"TALK", "PUBLIC", cname, utmp[n].name, argv[2]);
            writetolog(n, 4, "TALK", "PUBLIC", cname, argv[2]);
        }
    } else if (strcmp(cmd, "action") == 0) {
        if (useronchannel(n, cname)) {
            for (x = 0; x < clnum; x++) 
                if (useronchannel(x, cname))
                    asendp(x,5,"TALK", "ACTION", cname, utmp[n].name, argv[2]);
            writetolog(n, 4, "TALK", "ACTION", cname, argv[2]);
        }
    } else {
        if (utmp[n].channels != NULL) {
            for (x = 0; x < clnum; x++) {
                if ((utmp[x].channels != NULL) && 
                    (namecmp(utmp[x].name, cmd) == 0)) 
                    asendp(x, 5, "TALK", "PRIVATE", "", utmp[n].name, argv[2]);
            }
            writetolog(n, 4, "TALK", "PRIVATE", cmd, argv[2]);
        }
    }
}

void s_page(int n, char **argv) {
    int x;

    for (x = 0; x < clnum; x++)
        if (namecmp(utmp[x].name, argv[0]) == 0) 
            asendp(x, 2, "PAGE", utmp[n].name);
    writetolog(n, 2, "PAGE", argv[0]);
}

void s_getmsghdrs(int n, char **argv) {
    mrec msg;
    irec idx;
    unsigned long minnum, maxnum, num, maxinbase;
    char baseid;
    FILE *fd, *fi;

    writetolog(n, 4, "GETMSGHDRS", argv[0], argv[1], argv[2]);

    baseid = atoi(argv[0]);
    minnum = atoi(argv[1]);
    maxnum = atoi(argv[2]);

    if ((baseid < 0) || (baseid >= MBASES)) {
        sendp(n, 5, "MSGHDR", "", "", "OOB", "");
        return;
    }

    if (nullbase(baseid)) {
        sendp(n, 5, "MSGHDR", "", "", "OOB", "");
        return;
    }

    if (baseid == 0) maxinbase = count_msgs(utmp[n].name, baseid);
    else maxinbase = basenum[baseid];

    if ((minnum < 0) || (minnum > maxinbase) || 
        (maxnum < 0) || (maxnum > maxinbase)) {
        sendp(n, 5, "MSGHDR", "", "", "OOB", "");
        return;
    }

    if (!openmbase(utmp[n].name, baseid, (FILE **) &fi, "rb", 
                                        (FILE **) &fd, "rb")) {
        sendp(n, 2, "BAD", "server file error");
        return;
    }

    for (num = minnum; num <= maxnum; num++) {
        fseek(fi, num * sizeof(irec), SEEK_SET);
        fread(&idx, sizeof(irec), 1, fi);
        fseek(fd, idx.offset, SEEK_SET);
        fread(&msg, HDRLEN, 1, fd);             /* only read the header     */
/* OOO - unnecessary in new mail system
        if (private(baseid)) {
            if ((namecmp(msg.to, utmp[n].name) == 0) ||
                (namecmp(msg.from, utmp[n].name) == 0) ||
                (sysop(n))) {
                    sendp(n, 5, "MSGHDR", msg.to, msg.from,
                        (msg.deleted == 'Y') ? "DELETED" : "", msg.title); 
            } else {
                sendp(n, 5, "MSGHDR", "", "", "PRIVATE", "");
            }
        } else {
            sendp(n, 5, "MSGHDR", msg.to, msg.from,
                (msg.deleted == 'Y') ? "DELETED" : "", msg.title); 
        }
*/
        sendp(n, 5, "MSGHDR", msg.to, msg.from, 
            (msg.deleted == 'Y') ? "DELETED" : "", msg.title);
    }
    fclose(fi);
    fclose(fd);
}

void s_getmsg(int n, char **argv) {
    mrec msg;
    irec idx;
    unsigned long num, msgmsglen;
    char buf[1024], baseid;
    int bp, x, nummsgs;
    FILE *fd, *fi;

    writetolog(n, 3, "GETMSG", argv[0], argv[1]);

    baseid = atoi(argv[0]);
    num = atoi(argv[1]);

    if (private(baseid)) nummsgs = count_msgs(utmp[n].name, baseid);
    else nummsgs = basenum[baseid];

    if ((baseid < 0) || (baseid >= MBASES)) {
        sendp(n, 3, "MSG", "MSGOOB", "");
        return;
    }

    if (nullbase(baseid)) {
        sendp(n, 3, "MSG", "MSGOOB", "");
        return;
    }

    if ((num < 0) || (num > nummsgs)) {
        sendp(n, 3, "MSG", "MSGOOB", "");
        return;
    }

    if (!openmbase(utmp[n].name, baseid, (FILE **) &fi, "rb", 
                                        (FILE **) &fd, "rb")) {
        sendp(n, 2, "BAD", "server file error");
        return;
    }

    fseek(fi, num * sizeof(irec), SEEK_SET);
    fread(&idx, sizeof(irec), 1, fi);
    fseek(fd, idx.offset, SEEK_SET);
    fread(&msg, idx.length, 1, fd);

/* OOO - don't need this with new mailboxes
    if (private(baseid)) {
        if ((namecmp(msg.to, utmp[n].name) != 0) &&
            (namecmp(msg.from, utmp[n].name) != 0) &&
            (!sysop(n))) {
                sendp(n, 3, "MSG", "PRIVATE", "");
                fclose(fi);
                fclose(fd);
                return;
            }
    }
*/
    sendp(n, 3, "MSG", "START", "");
    sendp(n, 3, "MSG", "TO", msg.to);
    sendp(n, 3, "MSG", "FROM", msg.from);
    sendp(n, 3, "MSG", "TITLE", msg.title);
    sendp(n, 3, "MSG", "TIME", msg.time);
    sendp(n, 3, "MSG", "DELETED", (msg.deleted == 'Y') ? "Y" : "N");

    if (msg.deleted != 'Y' || 
        sysop(n) ||
        (namecmp(utmp[n].name, msg.to) == 0) ||
        (namecmp(utmp[n].name, msg.from) == 0)) {
            msgmsglen = idx.length - HDRLEN;
            bp = 0;
            for (x = 0; x < msgmsglen; x++) {
                if (msg.msg[x] == '\n') {
                    buf[bp] = 0; 
                    sendp(n, 3, "MSG", "LINE", buf); 
                    bp = 0;
                } else if (isprint(msg.msg[x])) buf[bp++] = msg.msg[x];
            }
    }
    sendp(n, 3, "MSG", "END", "");
    fclose(fi);
    fclose(fd);
}

void s_getmsgnum(int n, char **argv) {
    int baseid;

    baseid = atoi(argv[0]);
    if ((baseid < 0) || (baseid >= MBASES)) {
        sendp(n, 3, "MSGNUM", "OOB", "");
        return;
    }

    if (private(baseid)) 
        sendp(n, 3, "MSGNUM", argv[0], itoa(count_msgs(utmp[n].name, baseid)));
    else
        sendp(n, 3, "MSGNUM", argv[0], itoa(basenum[baseid]));
}

void deletemsg(int n, unsigned long baseid, unsigned long num) {
    mrec msg;
    irec idx;
    FILE *fd, *fi;
    char baseids[10];

    if ((baseid < 0) || (baseid >= MBASES)) {
        sendp(n, 4, "DELMSGRET", "", "", "OOB");
        return;
    }

    if (nullbase(baseid)) {
        sendp(n, 4, "DELMSGRET", "", "", "OOB");
        return;
    }

    if (!openmbase(utmp[n].name, baseid, (FILE **) &fi, "rb", 
                                        (FILE **) &fd, "rb")) {
        sendp(n, 4, "DELMSGRET", "", "", "OOB");
        return;
    }

    /* XXX - should check message # out of bound */

    strcpy(baseids, itoa(baseid));

    fseek(fi, num * sizeof(irec), SEEK_SET);
    fread(&idx, sizeof(irec), 1, fi);
    fseek(fd, idx.offset, SEEK_SET);
    fread(&msg, HDRLEN, 1, fd);                 /* only read the header     */
    fclose(fi);
    fclose(fd);

    if (n != -1) {
        if (!((namecmp(msg.to, utmp[n].name) == 0) ||
              (namecmp(msg.from, utmp[n].name) == 0) ||
              (sysop(n)))) {
                sendp(n, 4, "DELMSGRET", baseids, itoa(num), "P");
                fclose(fi); fclose(fd);
                return;
        }
    }
 
    if (msg.deleted == 'Y') msg.deleted = 'N'; else msg.deleted = 'Y';

    if (!openmbase(utmp[n].name, baseid, (FILE **) &fi, "rb",   
                                        (FILE **) &fd, "r+b")) {
        sendp(n, 2, "BAD", "server file error");
        return;
    }
    fseek(fi, num * sizeof(irec), SEEK_SET);
    fread(&idx, sizeof(irec), 1, fi);
    fseek(fd, idx.offset, SEEK_SET);
    fwrite(&msg, HDRLEN, 1, fd);            /* only write the header    */
    fclose(fi);
    fclose(fd);

    if (n != -1) {
        sendp(n, 4, "DELMSGRET", baseids, itoa(num), 
            (msg.deleted == 'Y') ? "Y" : "N");
    }
}

void s_addmsg(int n, char **argv) {
    irec idx;
    unsigned long len;
    FILE *fd, *fi;
    FILE *ftmp, *fito, *fisysop;
    time_t tp;
    char cmd[21];

    strncpy(cmd, despaceandlower(argv[0]), 20); cmd[20] = 0;
    if (strcmp(cmd, "start") == 0) {
        utmp[n].msg.deleted = 'N';
        strcpy(utmp[n].msg.msg, "");
        strcpy(utmp[n].msg.from, utmp[n].name);

        tp = time(NULL);
        strcpy(utmp[n].msg.time, (char *) ctime(&tp));
        utmp[n].msg.time[strlen(utmp[n].msg.time) - 1] = 0; 
    
        utmp[n].msgbase = atoi(argv[1]);
        /* XXX make sure base is valid */
    } else if (strcmp(cmd, "to") == 0) {
        strcpy(utmp[n].msg.to, argv[1]);
    } else if (strcmp(cmd, "title") == 0) {
        strcpy(utmp[n].msg.title, argv[1]);
    } else if (strcmp(cmd, "line") == 0) {
        strcat(utmp[n].msg.msg, argv[1]); strcat(utmp[n].msg.msg, "\n");
    } else if (strcmp(cmd, "end") == 0) {
        dbg(printf("got message to %s from %s about %s\n",
            utmp[n].msg.to, utmp[n].msg.from, utmp[n].msg.title));

        if ((utmp[n].msgbase < 0) || (utmp[n].msgbase >= MBASES)) {
            sendp(n, 2, "BAD" ,"invalid base id");
            return;
        }

        if (strcmp(base[utmp[n].msgbase].title, "null base") == 0) {
            sendp(n, 2, "BAD", "invalid base id");
            return;
        }

        /* make sure that both the sender and reciever have mailboxes   */
        if (private(utmp[n].msgbase)) {
            int i;

            if (!has_mailbox(utmp[n].name)) {
                /* if the sender doesn't have a mailbox create it       */
                if (!mk_mailbox(utmp[n].name)) {
                    sendp(n, 2, "BAD", 
                        "server could not create sender mailbox");
                    return;
                }
            }

            /* only create the receiver mailbox if the receiver exists  */
            for (i = 0; i < usernum; i++) 
                if (namecmp(user[i].name, utmp[n].msg.to) == 0) break;

            if (i == usernum) {
                sendp(n, 2, "BAD", "sender must exist for private email");
            }

            if (!has_mailbox(utmp[n].msg.to)) {
                if (!mk_mailbox(utmp[n].msg.to)) {
                    sendp(n, 2, "BAD", 
                        "server could not create receiver mailbox");
                }
            }
        }


        /* for private mailboxes we need to write the index in two places   */
        if (private(utmp[n].msgbase)) {
            if (!openmbase(utmp[n].name, utmp[n].msgbase, (FILE **) &fi, "ab",
                          (FILE **) &fd, "ab")) {
                sendp(n, 2, "BAD", "server could not open sender index file");
                return;
            }

            if (!openmbase(utmp[n].msg.to, utmp[n].msgbase, (FILE **) &fito, 
                          "ab", (FILE **) &ftmp, "ab")) {
                sendp(n, 2, "BAD", "server could not open receiver index file");
                return;
            }
            fclose(ftmp);

            if (sysop_base != -1) {
                if (!openmbase(NULL, sysop_base, (FILE **) &fisysop, "ab",
                          (FILE **) &ftmp, "ab")) {
                    sendp(n, 2, "BAD", 
                        "server could not open sysop index file");
                    return;
                }
            }
            fclose(ftmp);
        } else {
            if (!openmbase(utmp[n].name, utmp[n].msgbase, (FILE **) &fi, "ab", 
                          (FILE **) &fd, "ab")) {
                sendp(n, 2, "BAD", "server file error");
                return;
            }
        }

        /* compute the index values */
        idx.offset = ftell(fd);
        idx.length = HDRLEN + strlen(utmp[n].msg.msg);
        
        /* write the message and its index */
        fwrite(&(utmp[n].msg), idx.length, 1, fd);
        fwrite(&idx, sizeof(idx), 1, fi);
        
        if (private(utmp[n].msgbase)) {
            if (namecmp(utmp[n].msg.to, utmp[n].name) != 0) {
                fwrite(&idx, sizeof(idx), 1, fito);
            }
            fclose(fito);
            if (sysop_base != -1) {
                fwrite(&idx, sizeof(idx), 1, fisysop);
                fclose(fisysop);
                basenum[sysop_base]++;
            }
        }
    
        fclose(fi);
        fclose(fd);
        basenum[utmp[n].msgbase]++;
        writetolog(n, 4, "ADDMSG", itoa(utmp[n].msgbase), utmp[n].msg.to, 
            utmp[n].msg.title);
        if (private(utmp[n].msgbase)) {
            int i;

            for (i = 0; i < clnum; i++) {
                if (namecmp(utmp[n].msg.to, utmp[i].name) == 0) {
                    announceto(i, AN_NEWMSG, itoa(utmp[n].msgbase), 
                               utmp[n].msg.to);
                    break;
                }
            }
        } else {
            int i;

            for (i = 0; i < clnum; i++) {
                if (accesscheck(utmp[i].unum, utmp[n].msgbase))
                    announceto(i, AN_NEWMSG, itoa(utmp[n].msgbase), 
                               utmp[n].msg.to);
            }
        }
    } else {
        sendp(n, 2, "BAD", "invalid message subcommand");
    }
}

void s_deletemsg(int n, char **argv) {
    unsigned long baseid, num;

    writetolog(n, 3, "DELETEMSG", argv[0], argv[1]);

    baseid = atoi(argv[0]);
    num = atoi(argv[1]);
    deletemsg(n, baseid, num);
}

void s_getbases(int n, char **argv) {
    int x;

    sendp(n, 4, "BASETITLE" ,"START", "", "");
    for (x = 0; x < MBASES; x++) {
        if (!nullbase(x) && accesscheck(utmp[n].unum, x)) {
            sendp(n, 4, "BASETITLE", "LINE", itoa(x), base[x].title);
        }
    }
    sendp(n, 4, "BASETITLE" ,"END", "", "");
}

void s_getopts(int n, char **argv) {
    int i;
    char sub[255], scrlen[10];

    strcpy(sub, "");
    for (i = 0; i < MBASES; i++) {
        if (subcheck(utmp[n].unum, i)) {
            strcat(sub, itoa(i)); strcat(sub, ",");
        }
    }
    i = strlen(sub) - 1; if (sub[i] == ',') sub[i] = 0;

    strcpy(scrlen, itoa(user[utmp[n].unum].scrlen));
    sendp(n, 5, "USEROPTS", user[utmp[n].unum].name, 
        scrlen, itoa(user[utmp[n].unum].options), sub);
}

void s_setopts(int n, char **argv) {
    char *tp, *p;
    unsigned long opts;

    /* get the screen length and the options code                           */
    user[utmp[n].unum].scrlen = atoi(argv[0]);
    opts = atoi(argv[1]);
    /* top 8 bits are sysop bits and can't be set by the user.              */
    user[utmp[n].unum].options = 
        (user[utmp[n].unum].options & 0xff000000) |
        (opts & 0x00ffffff);

    /* go through the list of comma delimited sub codes and set a bit for   */
    /* each one.                                                            */
    user[utmp[n].unum].sub = 0;
    if (*argv[2] != 0) {
        p = argv[2];
        while (p != NULL) {
            (char *) tp = (char *) strsep(&p, ",");
            user[utmp[n].unum].sub = user[utmp[n].unum].sub | 
                                     power(2, atoi(tp));
        }
    }
    s_getopts(n, NULL);
}

void s_getlastmsg(int n, char **argv) {
    int baseid;

    baseid = atoi(argv[0]);
    sendp(n, 3, "LASTMSG", argv[0], 
        itoa(user[utmp[n].unum].lastmsg[baseid]));
}

void s_setlastmsg(int n, char **argv) {
    int baseid, msgnum;

    baseid = atoi(argv[0]);
    msgnum = atoi(argv[1]);
    user[utmp[n].unum].lastmsg[baseid] = msgnum;
    sendp(n, 3, "LASTMSG", argv[0], itoa(msgnum));
}

void s_setpasswd(int n, char **argv) {
    strcpy(user[utmp[n].unum].pass, argv[0]);
    writetolog(n, 1, "SETPASSWD");
}

void s_finduser(int n, char **argv) {
    int i, x;

    sendp(n, 4, "FOUNDUSER", "START", "", "");
    for (i = 0, x = 1; i < usernum; i++) {
        if (strstr(user[i].name, argv[0]) != NULL) {
            sendp(n, 4, "FOUNDUSER", "LINE", itoa(x++), user[i].name);
        }
    }
    sendp(n, 4, "FOUNDUSER", "END", "", "");
}

void s_newuser(int n, char **argv) {
    int i, badname = FALSE;

    if (usernum == MAXUSERS) {
        sendp(n, 2, "NEWUSER", "Sorry, the user base is full");
        return;
    }

    if (shutdownpending) {
        sendp(n, 2, "NEWUSER", "no logins allowed, shutdown pending");
        return;
    }

    if (argv[1] == NULL) {
        sendp(n, 2, "LOGIN", "password cannot be blank");
        return;
    }

#ifdef NONEWUSER
    if (!sysop(n)) {
        sendp(n, 2, "NEWUSER", NONEWUSER);
        return;
    }
#endif
    for (i = 0; i < usernum; i++) 
        if (namecmp(user[i].name, argv[0]) == 0) badname = TRUE;

    if (strchr(argv[0], ':') != NULL) badname = TRUE;
    if (strchr(argv[0], ';') != NULL) badname = TRUE;
    if (strchr(argv[0], '|') != NULL) badname = TRUE;
    if (strchr(argv[0], ' ') != NULL) badname = TRUE;
    if (strchr(argv[0], ',') != NULL) badname = TRUE;
    if (strchr(argv[0], '/') != NULL) badname = TRUE;
    if (strchr(argv[0], '\\') != NULL) badname = TRUE;
    if (strchr(argv[0], '<') != NULL) badname = TRUE;
    if (strchr(argv[0], '>') != NULL) badname = TRUE;
    if (strchr(argv[0], '{') != NULL) badname = TRUE;
    if (strchr(argv[0], '}') != NULL) badname = TRUE;
    if (strchr(argv[0], '(') != NULL) badname = TRUE;
    if (strchr(argv[0], ')') != NULL) badname = TRUE;
    if (strchr(argv[0], '\'') != NULL) badname = TRUE;
    if (strchr(argv[0], '`') != NULL) badname = TRUE;
    if (strchr(argv[0], '?') != NULL) badname = TRUE;
    if (strchr(argv[0], '*') != NULL) badname = TRUE;
    if (strchr(argv[0], '"') != NULL) badname = TRUE;
    if (strchr(argv[0], '#') != NULL) badname = TRUE;
    if (strchr(argv[0], '[') != NULL) badname = TRUE;
    if (strchr(argv[0], ']') != NULL) badname = TRUE;
    if (namecmp(argv[0], "new") == 0) badname = TRUE;
    if (namecmp(argv[0], "guest") == 0) badname = TRUE;
    if (namecmp(argv[0], "nologin") == 0) badname = TRUE;
    if (badname) {
        sendp(n, 2, "NEWUSER", "That is an invalid name or is in use");
        return;
    }

    strcpy(user[usernum].name, argv[0]);
    strcpy(user[usernum].pass, argv[1]);
    user[usernum].access = D_SUBS;
    user[usernum].sub = D_SUBS;
    user[usernum].scrlen = D_SCRLEN;
    user[usernum].options = D_OPT;
    for (i = 0; i < MBASES; i++) {
        user[usernum].lastmsg[i] = basenum[i] - 10;
        if (user[usernum].lastmsg[i] < 0) user[usernum].lastmsg[i] = 0;
    }

    sendp(n, 2, "NEWUSER", "OK");
    usernum++;
    if (!utmp[n].valid) s_login(n, argv);
    writetolog(n, 1, "NEWUSER");
}

void s_userexist(int n, char **argv) {
    int i, found = FALSE;

    for (i = 0; i < usernum; i++) 
        if (namecmp(user[i].name, argv[0]) == 0) {
            found = TRUE;
            break;
        }

    if (found) sendp(n, 3, "USEREXIST", argv[0], "OK");
    else       sendp(n, 3, "USEREXIST", argv[0], "NO");
}

void dumpfile(int n, char *fname, char *shortname, char *clicmd) {
    char buf[255], c;
    FILE *fi;
    int bp;

    fi = openfile(fname, "r");
    if (fi == NULL) {
        sendp(n, 4, clicmd, shortname, "BADNAME", "");
        return;
    }

    sendp(n, 4, clicmd, shortname, "START", "");
    bp = 0;
    while (!feof(fi)) {
        c = fgetc(fi);
        if (c == '\n') {
            buf[bp] = 0; strcat(buf, "<nl>"); bp += 4;
            sendp(n, 4, clicmd, shortname, "LINE", buf); 
            bp = 0;
        } else if (c == 14) {
            buf[bp] = 0; strcat(buf, "<Bold>"); bp += 6;
        } else if (c == 15) {
            buf[bp] = 0; strcat(buf, "</Bold>"); bp += 7;
/*      } else if (c == '<') { */
/*          buf[bp] = 0; strcat(buf, "<lt>"); bp += 4; */
        } else if (isprint(c)) buf[bp++] = c;
    }
    sendp(n, 4, clicmd, shortname, "END", "");
    fclose(fi);
}

/*
 * Allow a text file to be shown
 */

void s_gettxtfile(int n, char **argv) {
    char fname[255];

    strcpy(fname, F_TXTDIR);
    strcat(fname, removechar(argv[0], '/'));
    dumpfile(n, fname, argv[0], "TXTFILE");
    writetolog(n, 2, "GETTXTFILE", fname);
}

void s_gnuinfo(int n, char **argv) {
    char fname[255];

    strcpy(fname, F_TXTDIR);
    strcat(fname, "COPYING-2.0");
    dumpfile(n, fname, argv[0], "GNUINFO");
    writetolog(n, 1, "GNUINFO");
}

/*
 * show a plan file for user <argv[0]>
 */

void s_getplanfile(int n, char **argv) {
    char fname[255];
    int i, found = FALSE;

    /* make sure the user exists */
    for (i = 0; i < usernum; i++) 
        if (namecmp(user[i].name, argv[0]) == 0) {
            found = TRUE;
            break;
        }
            
    if (!found) {
        sendp(n, 4, "PLANFILE", argv[0], "BADNAME", "");
        return;
    }

    strcpy(fname, F_PLANDIR);
    strcat(fname, removechar(despaceandlower(argv[0]), '/'));
    dumpfile(n, fname, argv[0], "PLANFILE");
    writetolog(n, 2, "GETPLANFILE", fname);
}

/* 
 * make a plan file 
 */

void s_putplanfile(int n, char **argv) {
    char fname[255];
    FILE *fo;
    char cmd[21];

    strncpy(cmd, removechar(despaceandlower(argv[0]), '/'), 20); cmd[20] = 0;

    if (strcmp(cmd, "start") == 0) {
        strcpy(utmp[n].msg.msg, "");
    } else if (strcmp(cmd, "line") == 0) {
        strcat(utmp[n].msg.msg, argv[1]); strcat(utmp[n].msg.msg, "\n");
    } else if (strcmp(cmd, "end") == 0) {
        strcpy(fname, F_PLANDIR);
        strcat(fname, despaceandlower(utmp[n].name));
        unlink(fname);
        fo = openfile(fname, "w");
        fprintf(fo, "%s", utmp[n].msg.msg);
        fclose(fo);
        writetolog(n, 1, "PUTPLANFILE");
    }
}

void s_gfileindex(int n, char **argv) {
    char *p, *token[4], **tp, msg[1024];
    int x, port;

    strcpy(msg, argv[0]);
    p = msg;                                /* parse string p               */
    if (p[0] != 0) {
        tp = token;
        for(x = 0; (x < 3); x++) {
            *(tp++) = (char *) strsep(&p, "\t");
        }
        if (token[2] == NULL) {
            sendp(n, 3, "GFILEINDEX", "NOFILE", "");
            return;
        }
        port = atoi(token[2]);
        gopherreaddir(n, token[1], port, token[0]);
    } else {
        gopherreaddir(n, GOPHERSVR, GOPHERPRT, "");
    }
    writetolog(n, 4, "GFILEINDEX", token[1], port, token[0]);
}

void s_getgfile(int n, char **argv) {
    char *p, *token[5], **tp;
    int x, port;
    char msg[1024];

    strcpy(msg, argv[0]);
    p = msg;
    if (p[0] == 0) {
        sendp(n, 3, "GFILE", "NOFILE", "");
        return;
    }

    tp = token;
    for(x = 0; (x < 3); x++) {
        *(tp++) = (char *) strsep(&p, "\t");
    }
    if (token[2] == NULL) {
        sendp(n, 3, "GFILE", "NOFILE", "");
        return;
    }
    port = atoi(token[2]);
    gopherreadfile(n, token[1], port, token[0]);
    writetolog(n, 4, "GETGFILE", token[1], port, token[0]);
}

void s_startlog(int n, char **argv) {
    int *id;

    id = (int *) malloc(sizeof(int));
    *id = n;

    if (sysop(n)) {
        log = addtolist(log, id);
    } else {
        sendp(n, 3, "LOG", "NOTSYSOP", "");
    }
    writetolog(n, 1, "STARTLOG");
}

void s_stoplog(int n, char **argv) {
    if (sysop(n)) {
        log = deletefromlist(log, &n, logptrcomp);
    } else {
        sendp(n, 3, "LOG", "NOTSYSOP", "");
    }
    writetolog(n, 1, "STOPLOG");
}

void s_shutdown(int n, char **argv) {
    int i;

    writetolog(n, 3, "SHUTDOWN", argv[0], argv[1]);
    if (!sysop(n)) {
        sendp(n, 2, "BAD", "only the sysop can shutdown");
        return;
    }
    for (i = 0; i < clnum; i++) asendp(i, 2, "SHUTDOWN", argv[1]);
    if (argv[0][0] == '0') {
        shutdownpending = FALSE;
        SYSLOG(LOG_NOTICE, "shutdown cancelled: %s", argv[1]);
    } else if (argv[0][0] == '1') {
        shutdownpending = TRUE;
        SYSLOG(LOG_NOTICE, "shutdown pending: %s", argv[1]);
    } else if (argv[0][0] == '2') { 
        writeusers(NULL, 0); die(); 
        SYSLOG(LOG_NOTICE, "shutdown now: %s", argv[1]);
    }
}

void s_onfrom(int n, char **argv) {
    if (strcmp(utmp[n].addr, "localhost") == 0) {
        strncpy(utmp[n].addr, argv[0], 30);
        strcat(utmp[n].addr, " *");
    }
}

void s_request(int n, char **argv) {
    int mode;

    despaceandlower(argv[0]);
    despaceandlower(argv[1]);

    if (strcmp(argv[1], "login") == 0) {
        mode = AN_LOGIN;
    } else if (strcmp(argv[1], "logout") == 0) {
        mode = AN_LOGOUT;
    } else if (strcmp(argv[1], "newmsg") == 0) {
        mode = AN_NEWMSG;
    } else if (strcmp(argv[1], "chanjoin") == 0) {
        mode = AN_CHANJOIN;
    } else if (strcmp(argv[1], "chanleave") == 0) {
        mode = AN_CHANLEAVE;
    } else if (strcmp(argv[1], "chancreate") == 0) {
        mode = AN_CHANCREATE;
    } else if (strcmp(argv[1], "chandestroy") == 0) {
        mode = AN_CHANDESTROY;
    } else if (strcmp(argv[1], "channewname") == 0) {
        mode = AN_CHANNEWNAME;
    } else {
        sendp(n, 2, "BAD", "invalid announce or unannounce request");
        return;
    }

    if (strcmp(argv[0], "announce") == 0) {
        utmp[n].announce |= mode;
    } else if (strcmp(argv[0], "unannounce") == 0) {    
        utmp[n].announce &= ~mode;
    } else {
        sendp(n, 2, "BAD", "invalid request");
        return;
    }
}

void handleclient(int n, char *fullmsg, int c) {
    char *key, *token[MAXCMD], *p, msg[MAXCMD];
    int x, i, j, tokennum, l, ci;
    time_t now;

    for (x = 0; x < 21; x++) {
        token[x] = NULL;
    }

    fullmsg[c] = 0;
    for (x = 0; 
        (fullmsg[x] != '\n') && (fullmsg[x] != '\r') && (fullmsg[x] != 0); 
        x++); 
    fullmsg[x] = 0;

    for (i = j = 0; fullmsg[i] != 0; i++, j++) {
        if (fullmsg[i] == '\\') {
            switch (fullmsg[i + 1]) {
                case 'n': msg[j] = '\n';            break;
                case 't': msg[j] = '\t';            break;
                case 'b': msg[j] = '\b';            break;
                default : msg[j] = fullmsg[i + 1];  break;
            }
            i++;
        } else if (fullmsg[i] == ':') {
            msg[j] = 0x18;
        } else msg[j] = fullmsg[i];
    }
    msg[j] = 0;

    dbg(printf("msg: (%u) %s from %i\n", ++ipn, prettymsg(msg), n));

    if (strchr(msg, 0x18) == NULL) {
        sendp(n, 2, "BAD", "invalid command syntax.  get a new client");
        return;
    }
    p = msg;                                /* parse string p               */
    (char *) key = (char *) strsep(&p, "\x18"); /* read first word          */
    strncpy(commandkey, key, MAXCMDKEY);    /* set the command key          */
    commandkey[MAXCMDKEY - 1] = 0;
    (char *) token[0] = (char *) strsep(&p, "\x18");    /* read first word  */
    for (x = 0, l = strlen(token[0]); x < l; x++)
        token[0][x] = upper(token[0][x]);
    for (ci = 0; ci < S_CNUM; ci++)         /* is it in our table?          */
        if (strcmp(s_ctable[ci].cname, token[0]) == 0) break;
    if (ci == S_CNUM) {                     /* no command with that name    */
        sendp(n, 2, "BAD", "invalid syntax or command");
        return;
    }                                       /* otherwise ci points to cmd   */
    tokennum = 0;
    while (p != NULL) {
        (char *) token[tokennum++] = (char *) strsep(&p, "\x18");
    }

    /* check to see if they are logged in and have to be                    */
    if (s_ctable[ci].login && !utmp[n].valid) {
        sendp(n, 2, "BAD", "must login first");
        return;
    }

    /* make sure they gave enough arguments                                 */
    if (s_ctable[ci].minargs > tokennum) {
        sendp(n, 2, "BAD", "command requires more arguments");
        return;
    }

    /* update the last command time for this user                           */
    now = time(NULL);
    if ((!strcmp(s_ctable[ci].cname, "TALK")) || 
        (!strcmp(s_ctable[ci].cname, "PAGE"))) {
        if ((now - utmp[n].timelast) <= MAXTALKTIME) {
            if (utmp[n].talkcommands++ == MAXTALKCOMMANDS) {
                    sendp(n, 2, "BAD", "flooding is anti-social");
                    close(cl[n]);
                    killclient(n);
                    return;
            }
        } else {
            utmp[n].timelast = now;
            utmp[n].talkcommands = 1;
        }
    } else utmp[n].timelast = now;

    /* call the function to handle this type of message                     */
    switch (s_ctable[ci].fork) {
        case O_NOFORK: dothread((void *) s_ctable[ci].f, n, token); break;
        case O_FORK: forkthread((void *) s_ctable[ci].f, n, token); break;
    }
}

void readaccesscontrol(void) {
    FILE *fi;
    accesscont *p;
    char buf[255];

    fi = fopen(F_ACCESSCONT, "r");
    if (fi != NULL) {
        while (!feof(fi)) {
            p = (accesscont *) malloc(sizeof(accesscont));
            fscanf(fi, "%s %i\n", buf, &p->maxusers);
            p->hoststring = (char *) malloc(sizeof(char) * (strlen(buf) + 1));
            bbsstrlwr(buf);
            strcpy(p->hoststring, buf);
            accessl = addtolist(accessl, (void *) p);
        }
        fclose(fi);
    }
    p = (accesscont *) malloc(sizeof(accesscont));
    p->hoststring = (char *) malloc(sizeof(char) * 2);
    p->hoststring[0] = '.'; p->hoststring[1] = 0;
    p->maxusers = MAXUSERS;
    accessl = addtolist(accessl, (void *) p);
}

void main(int argc, char **argv) {
    int maxfd, n, x, reading, opcount;
    char ch;
    struct sockaddr_in sin;
    fd_set fds;
    time_t now;
    int port;

    if (argc == 2) port = atoi(argv[1]); else port = YABBSPORT;

    printf("%s unix server - version %s,\n", YABBSNAME, SERVERVERSION);
    printf(YABBSGNUHI);
    printf(YABBSGNUSVR, port);

    chdir(F_PATH);

    /* various signals that should tell us to die                           */
    signal(SIGTERM, (void *) die);  
    signal(SIGQUIT, (void *) die);  
    signal(SIGHUP, SIG_IGN);  
    signal(SIGINT, (void *) die);  
    signal(SIGPIPE, (void *) sigpipe);  

    /* read the access control file                                         */
    readaccesscontrol();

    /* make our main socket that we will listen on                          */
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("\nserver: socket");
        exit(1);
    }

    /* setup the structures */
    sin.sin_port = htons(port);
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = 0;
    if (bind(s, (struct sockaddr *) &sin, sizeof(sin)) == -1) {
        perror("\nserver: bind");
        exit(1);
    }
    listen(s, 1);

#ifndef DEBUG
    dofork();
#endif

#ifdef DOSYSLOG
    openlog("yabbsd", LOG_PID, LOG_LOCAL0);
    syslog(LOG_NOTICE, "starting: pid = %i", getpid());
#endif

    readbases();
    readusers();
    opcount = 0;

    while (1) {
        /* 
         * poll the connecting socket plus each client until something
         * exciting happens on one of them.
         */
        FD_ZERO(&fds);                          /* zero select array        */
        FD_SET(s, &fds); maxfd = s;             /* always check socket <s>  */
        for (n = 0; n < clnum; n++) {           /* add each client to array */
            if (deadcl[n]) {
                /* connection closed during a send                          */
                killclient(n);
            } else {
                FD_SET(cl[n], &fds);
                if (cl[n] > maxfd) maxfd = cl[n];
            }
        }
        /* close children properly                                          */
        while (waitpid(-1, (int *)NULL, WNOHANG) > 0);
        /* wait for something to happen (should take very little cpu time)  */
        n = select(maxfd + 1, &fds, (fd_set *) NULL, (fd_set *) NULL, 
            (struct timeval *) NULL);
        if (n == -1) perror("server: select");

        /* 
         * check to see where the excitment is
         */
        if (FD_ISSET(s, &fds)) {            /* check connecting socket      */
            addclient();                    /* accept connection request    */
            utmp[clnum - 1].cmdbuflen = 0;
        } else {
            for (n = 0; n < clnum; n++) {   /* check each client            */
                if (FD_ISSET(cl[n], &fds)) {/* client n is doing something  */
                    /* read one char from the client                        */
                    x = read(cl[n], &ch, 1);
                    if ((x < 1) || (ch == '\n')) {
                        /* done reading a line                              */
                        utmp[n].cmdbuf[utmp[n].cmdbuflen] = 0;
                        if (utmp[n].cmdbuflen == 0) {
                            /* 0 bytes read means that the client is dead   */
                            killclient(n);
                        } else {
                            /* otherwise handle their command */
                            handleclient(n, utmp[n].cmdbuf, utmp[n].cmdbuflen);
                            if (opcount++ == SAVEUSROPS) { 
                                now = time(NULL);
                                for (x = 0; x < clnum; x++) {
                                    if ((now - utmp[x].timelast) > 
                                        INACTIVITYTIMEOUT) {
                                        sendp(x, 2, "BAD", 
                                            "inactivity timeout");
                                        close(cl[x]);
                                        killclient(x);
                                    }
                                }       
                                forkthread(writeusers, 0, NULL); opcount = 0;
                            }
                            utmp[n].cmdbuflen = 0;
                        }
                    } else if (utmp[n].cmdbuflen < MAXCMD - 1) {
                        /* read the character                               */
                        utmp[n].cmdbuf[utmp[n].cmdbuflen++] = ch;
                    } /* otherwise our buffer is full, eat extras */
                }
            }
        }
    }
}
