/*
 * 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.
 */

/*
 * miscutil.c - part of the yabbs bulletin board package by alex wetmore
 *              see bbs.c for information on the system and copying.
 */

#include "bbsdefs.h"

/* 
 * return a string version of an integer <i>.
 */

char *bbs_itoa(int i) {
    static char a[10];

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

/*
 * return true if a <fname> exists, otherwise return false
 */

int fexist(char *fname) {
    FILE *fi;

    if ((fi = fopen(fname, "r")) == NULL) {
        return FALSE;
    } else {
        fclose(fi);
        return TRUE;
    }
}

/*
 * Clear the screen (if their terminal supports it), and reset lines to 0.
 */

void bbs_clrscr(void) {
    clearscreen();
    line = 1;   
}

/*
 * check to see who by asking the server.  output is defined in cli_who().
 */

void who(void) {
    char buf[255];
    
    sprintf(buf, "[%-8s] [%-24s] [%-28s] [%-4s]\n", 
        "name", "on since", "on from", "idle");
    sputs(buf);
    sputs("[--------] [------------------------] [----------------------------] [----]\n");
    bbs_who(NULL);
}

void cli_wholine(char *name, char *onfrom, char *onsince, int idle, void *n) {
    char buf[255];
    
    sprintf(buf, "[%-8s] [%-24s] [%-28.28s] [%4i]\n", 
        name, onsince, onfrom, idle / 60);
    sputs(buf);
}

/*
 * this signal is called whenever the user closes connection without using
 * ^d, goodbye, or any of another 1000 means.
 */

void sigquit(int i) {
    if (ynprompt("\n\nDo you want to logout? [yN] ", FALSE)) {
        killconnection(K_USER);
    }
}

#ifdef JOBCONTROL
void sigstop(int i) {
    echoon();
    kill(getpid(), SIGSTOP);
}

void sigcont(int i) {
    echooff();
}
#endif

/*
 * close the current connection, and do house cleanup.  Should be called
 * no matter how yabbs exits.
 */

void killconnection(int message) {
    bbs_logout();
    switch (message) {
        case K_USER: sputs("\n\nConnection terminated by user\n\n"); break;
        case K_TIME: sputs("\n\nInactivity timeout\n\n"); break;
        case K_FERR: sputs("\n\nFile error\n\n"); break;
        case K_SILENT: /* lets be silent */ break;
        case K_HUP: sputs("\n\nConsole forced logout!\n\n"); break;
        case K_FERR_NW: sputs("\n\nFile error\n\n"); break;
        case K_NETWORK: sputs("\n\nNetwork error\n\n"); break;
    }
    echoon();
    termclose();
    exit(message);
}

/*
 * wait for the user to hit a character that is in <okaych>.  When we get one
 * return.
 */

char waitfor(char *okaych) {
    char ch;
    int  x, done = FALSE;

    done = FALSE;
    while (!done) {
        ch = lower(readc());
        for (x = 0; x < strlen(okaych); x++)
            if (ch == lower(okaych[x])) done = TRUE;
    }
    return ch; 
}

/*
 * return T or F depending on whether the user has <option> enabled
 */

char opton(int option) {
    if ((userinfo.options & option) == option) 
        return 'T'; 
    else 
        return 'F';
}

/* 
 * write a string to the user.  handles highlighting.
 */
void writes(char *s) {
    int i, l = strlen(s);

    for (i = 0; i < l; i++) {
        if      (s[i] == 016) hion();
        else if (s[i] == 017) hioff();
        else                  writec(s[i]);
    }
}

/*
 * put a line on the screen.  Everytime we see a '\n' check to see if we have
 * reached the end of the users screen.  If so prompt them with [more] and
 * reset the line variable, otherwise just keep printing.  '\n' is printed
 * as '\r\n' to work with more terminals.
 */
void sputs(char *st) {
    int     i, l = strlen(st);
    static  int nowrite;

    for (i = 0; i < l; i++) {
        if ((st[i] == '\n') && (!nowrite)) {
            writec('\r');
            if ((++line == userinfo.scrlen) && (userinfo.scrlen > 0)) {
                writes("\n<slam that key>"); fflush(stdout);
                if (readc_pause() != '=') line = 1;
                writes("\r                    \r");
            } else { 
                writec(st[i]); 
            }
        } else if (st[i] == 14) { 
            hion();
        } else if (st[i] == 15) {
            hioff();
        } else if (!nowrite) {
            if ((st[i] == ']') && (sputs_process)) hioff();
            writec(st[i]); 
            if ((st[i] == '[') && (sputs_process)) hion();
        }
    }
    fflush(stdout);
}

/*
 * just like sputs but it does word wrapping on 80 cols
 */

#if 0
void sputs_wr(char *st) {
    int     oldi, i, l = strlen(st), col;
    static  int nowrite;

    col = 1;
    for (i = 0; i < l; i++) {
        if ((st[i] == '\n') && (!nowrite)) {
            writec('\r');
            if ((++line == userinfo.scrlen) && (userinfo.scrlen > 0)) {
                writes("\n<slam that key>"); fflush(stdout);
                if (readc_pause() != '=') line = 1;
                writes("\r                    \r");
            } else { 
                writec(st[i]); 
            }
            col = 1;
        } else if (st[i] == 14) { 
            hion();
        } else if (st[i] == 15) {
            hioff();
        } else if (!nowrite) {
            writec(st[i]); 
            col++;
            oldi = i;
            if (col == 77) {
                sputs(" \b");
                while ((st[i--] != ' ') && (col-- != 1)) sputs("\b \b");
                if (col <= 2) return;
                sputs("\n   ");
                col = 1;
            }
        }
    }
    fflush(stdout);
}
#endif

void sputs_wr(char *st) {
    int     oldi, i, l = strlen(st), col, bufpos;
    static  int nowrite;
    char    buf[255] = "";
    int     toolong = 0;

    col = 0;    
    bufpos = 0;
    for (i = 0; i < l; i++) {
        if ((st[i] == '\n') && (!nowrite)) {
            writes(buf);
            buf[1] = '\0';
            writec('\r');   
            if ((++line == userinfo.scrlen) && (userinfo.scrlen > 0)) {
                writes("\n<slam that key>"); fflush(stdout);
                fgetc(stdin); line = 1;
                writes("\r                    \r");
            } else { 
                writec(st[i]); 
            }
            col = 0;    
            bufpos = 0;
        } else if (!nowrite) {
            buf[bufpos] = st[i];    
            buf[++bufpos] = '\0';
            col++;
            oldi = i;
            if ((col == 77)||(bufpos == 119)) {
                if(!toolong)    {
                    while ((st[i--] != ' ') && (col-- != 1)) 
                        buf[--bufpos] = '\0';
                    if (col <= 1) toolong = 1;
                } else  {
                    toolong = 0;
                }
                writes(buf);
                sputs("\n   ");
                col = 0;    bufpos = 0;
            }
        }
    }
    fflush(stdout);
}

/* 
 * just like sputs but it handles simple mime
 *
 * based loosly on the demonstration program in rfc1123.
 */
void sputs_mime(char *st) {
    int c, i, l, x;
    char token[50];
    static int comment = 0; 

    l = strlen(st); i = 0;
    while (i < l) {
        c = st[i++];
        /* this is a MIME command.                                          */
        if (c == '<') {
            /* read out up to 50 characters of a token                      */
            for (x = 0; ((x < 49) && ((c = st[i++]) != '>') && (i < l)); x++) {
                token[x] = lower(c);
            }
            token[x] = 0;
            if (i > l) break;                       /* end of string        */
            /* if there was any more read till the '>'                      */
            if (c != '>') while (((c = st[i++]) != '>') && (i < l));
            if (i > l) break;                       /* end of string        */

            if (comment) {
                if (strcmp(token, "/comment") == 0) comment--;
            } else {
                if        (strcmp(token, "lt"        ) == 0) {
                    writec('<');
                } else if (strcmp(token, "nl"        ) == 0) { 
                    sputs("\r\n");
                } else if (strcmp(token, "/paragraph") == 0) {
                    sputs("\r\n\n");
                } else if (strcmp(token, "comment"   ) == 0) {
                    comment++; 
                } else if (strcmp(token, "bold"      ) == 0) {
                    hion();
                } else if (strcmp(token, "/bold"     ) == 0) {
                    hioff();
                }
            } 
        } else if ((!comment) && (c != '\n')) {
            writec(c);
        }
    }
    fflush(stdout);
}

void cli_error(char *error) {
    sputs("\n\nwarning: "); sputs_mime(error); sputs("\n\n");
}

void cli_fatalerror(char *error) {
    sputs("\n\nFATAL: "); sputs(error); sputs("\n\n");
    killconnection(K_SILENT);
}

/*
 * usage:  readl(string, maxlen, echo)
 * desc:   read a string
 * input:  string - string to save to
 *         maxlen - maximum length of string
 *         echo - if TRUE then echo, otherwise no echoing (for pws).
 * output: error stuff
 */

int readl(char *ptr, int maxlen, int echo, char *fill) {
    int i, x, ch, done, startout;

    redraw = done = FALSE;
    strcpy(ptr, fill);
    i = strlen(ptr) - 1;
    startout = 0;
    while (!done) {
        ch = readc();
        if (redraw) { 
            sputs("\r"); clearline(); sputs("\r");
            sputs(&(ptr[startout])); redraw = FALSE; 
        }
        if ((ch == 10) || (ch == 13)) done = 1;             /* return       */
        else if (((ch == 127) || (ch == 8)) && (i >= 0)) {  /* backspace    */
            *(ptr + i--) = 0;
            if (echo) sputs("\b \b");
            if ((echo) && (startout > 0) && ((i - startout) < 50)) {
                startout -= 20; if (startout < 0) startout = 0;
                redraw = TRUE;
            }
        } else if ((ch == 21) && (i >= 0)) {                /* erase input  */
            while (i >= 0) {
                *(ptr + i--) = 0;
                if (echo) sputs("\b \b");
            }
        } else if ((ch == 18) || (ch == 12)) {              /* redraw       */
            sputs("\n");
            for (x = startout; x <= i; x++) {
                if (echo) { writec(*(ptr + x)); fflush(stdout); }
            }
        } else if ((ch == '\t')) {                          /* tab          */
            for (x = 0; (x < 4) && (i < maxlen - 1); x++) {
                *(ptr + ++i) = ' ';
                if (echo) { writec(' '); fflush(stdout); }
            }
            *(ptr + i + 1) = 0;
            if ((echo) && ((i - startout) > 75)) {
                startout += 20;
                redraw = TRUE;
            }
        } else if ((i < maxlen - 1) && (ch >= 32) && (ch <= 126)) {
            *(ptr + ++i) = ch;
            *(ptr + i + 1) = 0;
            if (echo) { writec(ch); fflush(stdout); }
            if ((echo) && ((i - startout) > 75)) {
                startout += 20;
                redraw = TRUE;
            }
        }
    }
    sputs("\n");
    return ++i;
}

/*
 * usage:  readlww(string, maxlen, wrap_bs)
 * desc:   read a string with wordwrap
 * input:  ptr - string to save to
 *         maxlen - maximum length of string
 * output: error stuff
 */

int readlww(char *ptr, int maxlen, int wrap_bs) {
    int     i,                                  /* current column in line   */
            x,                                  /* general purpose counter  */
            lastsp,                             /* last space in line       */
            ch,                                 /* last character read      */
            done,                               /* flag high when done      */
            cc = -1;                            /* length of c              */
    char    c[255];                             /* current word (since last */
                                                /*  space, tab, or cr)      */
    rlwrap = redraw = done = FALSE;             /* redraw is used by page   */
    strcpy(c, "");                              /* clear current word       */
    /* rlstart contains the leftover when the line wordwraps.  It should be */
    /* be blank if this is a new message                                    */
    strcpy(ptr, rlstart);
    sputs(ptr);                                 /* write what we have and   */
    i = strlen(ptr) - 1;                        /*  update i                */
    while (!done) {
        ch = readc();                           /* read character           */
        if (redraw) {                           /* check redraw flag        */
            sputs(ptr); fflush(stdout);         /*  this gets set if there  */
            redraw = FALSE;                     /*  is a page request       */
        }
        if ((ch == 10) || (ch == 13)) {         /* if it is a CR we're done */
            *rlstart = 0;                       /* no leftover to save      */
            done = 1;
        } else if (((ch == 127) || (ch == 8)) && (i >= 0)) {
            /* the user backspaced.  We have to fix <ptr> and <c> to keep   */
            /* wrap accurate.  We backspace and clear to erase the char     */
            *(ptr + i--) = 0;
            sputs("\b \b"); fflush(stdout);
            if (cc > 0) {
                /* XXX - check this */
                *(c + cc--) = 0;
            } else {
                /* the user backed up beyond the last space, so we have */
                /*   to make <c> and <cc> reflect the new current word  */
                /* search for spaces, keeping track of the last one on  */
                /*   this line in the variable <lastsp>                 */
                for (lastsp = x = 0; x < i; x++) {
                    if (*(ptr + x) == ' ') lastsp = x;
                }
                /* copy the word from <lastsp> to the current position  */
                /* into <c>, and keep <cc> up to date also              */
                for (cc = 0, x = lastsp; x < i; x++)
                    c[cc++] = *(ptr + x);
            }
        } else if (((ch == 127) || (ch == 8)) && (i == -1) && (wrap_bs)) {
            return -1;
        } else if ((ch == 21) && (i >= 0)) {    /* ^u = erase line      */
            while (i >= 0) {                    /* keep clearing        */
                sputs("\b \b"); i--; fflush(stdout);
            }
            i = -1;                             /* fix <ptr> and <i>    */
            *ptr = 0;
            cc = -1;                            /* fix <cc> and <c>     */
            *c = 0;
        } else if ((ch == 18) || (ch == 12)) {  /* ^l or ^r = redraw    */
            sputs("\n"); fflush(stdout);
            for (x = 0; x <= i; x++) {
                writec(*(ptr + x)); fflush(stdout);
            }
        } else if ((ch == '\t')) {              /* tab, do 4 spaces     */
            for (x = 0; (x < 4) && (i < maxlen - 1); x++) {
                *(ptr + ++i) = ' ';
                writec(' ');
            }
            *(ptr + i + 1) = 0; fflush(stdout);
        } else if ((ch >= 32) && (ch <= 126)) { /* normal char, so we   */
            *(ptr + ++i) = ch;                  /*  add to <ptr> and    */
            *(ptr + i + 1) = 0;                 /*  <cc>, and write it  */
            *(c + ++cc) = ch;
            *(c + cc + 1) = 0;
            writec(ch); fflush(stdout);
        }
        /* if we had any white space characters fix <c> and <cc>        */
        if ((ch == 10) || (ch == 13) || (ch == 32) || (ch == '\t')) {
            *c = 0;
            cc = -1;
        } 
        /* if the next is at <maxlen> then we have to wrap, so we bs    */
        /*  the word in <c> and save it in rlstart.                     */
        if (i >= maxlen - 1) {
            for (x = 0; x <= cc; x++) {
                sputs("\b \b"); fflush(stdout);
                *(ptr + i--) = 0;
            }
            strcpy(rlstart, c);
            done = rlwrap = TRUE;
        } 
    }
    sputs("\n");
    return ++i;                                 /* return chars read    */
}

/*
 * read a line in from the user.
 */

void sgets(char *st, int max) {
    readl(st, max, TRUE, "");
}

/*
 * read a password from the user.  8 chars max, no echo.
 */

void sgetpw(char *st) {
    readl(st, 8, FALSE, "");
}

/*
 * dump a file to the user.
 */

void dump(char *filename) {
    FILE    *fi;
    char    s[255];
    int x;

    fi = fopen(filename, "r");
    if (fi == NULL) {
        sprintf(s, "error opening [%s] for input.\n", filename);
        sputs(s);
        termclose();
        exit(1);
    }
    x = 0;
    while (!feof(fi)) {
        s[x++] = fgetc(fi); s[x] = 0;
        if (x == 80) { sputs(s); x = 0; }
    }
    sputs(s);
    fclose(fi);
}

void dumpsvr(char *filename) {
    bbs_gettextfile(filename, NULL);
}

void cli_textline(char *line, void *null) {
    sputs_mime(line);
}

void more(char *readfile, int scrlen) {
    FILE    *fi;
    char    x = 1, c, s[256], key;
    char    pagerbin[256];
    int     done = FALSE, nonstop = FALSE;

    if (!getfromenv("pager", pagerbin)) *pagerbin = 0;
    else if (strcmp(pagerbin, "internal") == 0) *pagerbin = 0;

    if (pagerbin[0] == 0) {
        bbs_clrscr();
        fi = fopen(readfile, "r");
        if (fi == NULL) {
            sprintf(s, "error opening [%s] for input.\n", readfile);
            sputs(s);
            sputs("<slam that key>");
            key = readc_pause();
            sputs("\r               \r");
        } else {
            while ((!feof(fi)) && (!done)) {
                c = fgetc(fi);
                if (c == '\n') {
                    x = 1;
                    writec('\r');
                    if ((++line == scrlen - 1) && (scrlen > 0) && 
                        (!nonstop)) {
                        writes("\nmore? [Yn!] "); fflush(stdout);
                        key = lower(readc());
                        if (key == 'n') done = TRUE;
                        if (key == '!') nonstop = TRUE;
                        writes("\r            \r");
                        line = 1;
                    } else { 
                        writec(c); 
                    }
                } else if (c == '\t') {
                    if (x % 4 == 1) { writec(' '); writec(' '); x += 2; }
                    for (; x % 4 != 1; x++) writec(' ');
                } else {
                    x++;
                    writec(c);
                }
            }
            if (!done) {
                sputs("<slam that key>");
                key = readc_pause();
                sputs("\r               \r");
            }
            fclose(fi);
        }
    } else {
        strcat(pagerbin, " ");
        strcat(pagerbin, readfile);

        system(pagerbin);

        sputs("<slam that key>");
        key = readc_pause();
        sputs("\r               \r");
    }
}

/*            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;
}

/*
 * prompt the user with <st> and wait for a yes/no response.  Return
 * true if yes, false if no.  Default to yes if <def> is true, else
 * default to no.
 */

int ynprompt(char *st, int def) {
    char    yn;

    sputs(st);
    yn = waitfor("yn\n\r ");
    if      ((def)  && (yn == 'n')) { sputs("no\n"); return FALSE; }
    else if ((def)  && (yn != 'n')) { sputs("yes\n"); return TRUE; }
    else if ((!def) && (yn == 'y')) { sputs("yes\n"); return TRUE; }
    else if ((!def) && (yn != 'y')) { sputs("no\n"); return FALSE; }
    else return FALSE;
}

/*
 * copy <n> characters in <src> to <dup> starting at <beg>
 */

void strmid(char *dup, char *src, int beg, int n) {
    int         i;

    for (i = beg; i < (beg + n); i++) {
        dup[i - beg] = src[i];
    }
    dup[n] = 0;
}

/*
 * take a subs string made from inttosubs and turn it back into an int.
 * never used, just here to make things complete.
 */

void substoint(unsigned long int *subsi, char *subs) {
    char        i;

    *subsi = 0;
    for (i = 0; i < MBASES; i++)
        if (subs[i] == '+') *subsi = (*subsi | power(2, i));
}

/*
 * take a 32bit integer and turn it into a string that has a + wherever
 * a bit is high, and a minus wherever it is low.  Used to graphically
 * show access control.
 */

void inttosubs(char *subs, unsigned long int *subsi) {
    char        i;

    for (i = 0; i < MBASES; i++)
        if (*subsi & power(2, i)) subs[i] = '+'; else subs[i] = '-';
    subs[MBASES] = 0;
}

/*
 * check to see if user <u> has access to base <basenum>.
 * if the basenum is 32 (space) then return TRUE.
 */

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

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

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

/*
 * get the names of the message bases from the yabbs server.
 */

void readbases(void) {
    int i;

    userinfo.access = 0;
    for (i = 0; i < MBASES; i++) strcpy(base[i].title, S_NULLBASE);
    bbs_getbasenames(NULL);
}

void cli_msgbasename(int baseid, char *name, void *null) {
    strcpy(base[baseid].title, name);
    userinfo.access = userinfo.access | power(2, baseid);
}

/*
 * Put all of the vital information for user <u> on the screen.
 */

void drawuser(void) {
    char    temp[255];
    int     i;

    sprintf(temp, "\nname: [%s]  scrlen: [%u]  expert: [%c]  sysop: [%c]\n",
        userinfo.name, userinfo.scrlen, opton(O_XPERT), 
            opton(O_SYSOP));
    sputs(temp);

    sputs("        ");
    for (i = 0; i < MBASES; i++) {
        temp[i] = i + BASEOF;
    }
    temp[MBASES] = 0;
    sputs(temp); sputs("\n");
    inttosubs(temp, &userinfo.sub);
    sputs("bases:  "); sputs(temp); sputs("\n");
    inttosubs(temp, &userinfo.access);
    sputs("access: "); sputs(temp); sputs("\n\n");
}

/*
 * allow a user to select a new password.  Read it in twice to make sure
 * they got it right.
 */

void changepw(void) {
    char    pass1[9], pass2[9];
    int     passokay;

    do {
        do {
            passokay = TRUE;
            sputs("\nA good password should be between 3 and 8 characters, and contain");
            sputs("\nboth upper and lower case letters, plus a few numbers.\n");
            sputs("\nWhat would you like your password to be? ");
            sgetpw(pass1);
            if (strlen(pass1) < 3) {
                sputs("\nPlease use a longer password\n");
                passokay = FALSE;
            }
        } while (!passokay);

        sputs("Again? ");
        sgetpw(pass2);
        if (strcmp(pass1, pass2) == 0)
            strcpy(userinfo.pass, pass1);
           else {
            sputs("\nYour passwords didn't match.  Please try again.\n");
            passokay = FALSE;
        }
    } while (!passokay);
    bbs_setpassword(userinfo.pass);
}

/*
 * allow a user to change the bases that they are subscribed to.  Just list
 * all of the bases and have the user enter the character abrev for each
 * base that they want to toggle subscriptions to.
 */

void changesubs(void) {
    int     i, x;
    char    subscribed;
    char    temp[80];
    char    subs[64];

    drawuser();

    for (i = 0; i < MBASES; i++) {
        if (accesscheck(i)) {
            if (subcheck(i)) subscribed = '+'; else subscribed = '-';
            sprintf(temp, "%c %c) %s\n", subscribed, (i + BASEOF), base[i].title);
            sputs(temp);
        }
    }

    sputs("\nNOTICE: If you are currently subscribed to a message base");
    sputs("\n        and you select it below, you will lose your subscription");
    sputs("\nThe bases that you are currently subscribed to are shown with");
    sputs("\n+'s above, and unsubscribed bases are shown with -'s.\n");
    sputs("\nWhich bases do you want to toggle? ");
    sgets(subs, 63);
    for (i = 0; i < strlen(subs); i++) {
        x = upper(subs[i]) - BASEOF;
        if ((x >= 0) && (x < MBASES) && accesscheck(x))
            userinfo.sub = (userinfo.sub ^ power(2, x));
    }
    saveopts();
}

/*
 * find the user with name <name> and return TRUE if they exist, otherwise
 * return FALSE.
 */

int finduser(char *name) {
    return bbs_userexist(name);
}

void listusers(void) {
    char    pname[9];

    sputs("\nEnter a partial name or \"*\" for all users: ");
    sgets(pname, 8);
    if (*pname == 0) return; 
    else if (strcmp(pname, "*") == 0) 
        if (ynprompt("This will take a while.  Are you sure? [Ny] ", FALSE))
            strcpy(pname, "");
        else
            return;
    bbs_finduser(pname, NULL);
}

void cli_founduser(char *name, void *null) {
    sputs(name); sputs("\n");
}

void updatelast(int usr, char *buf) {
    FILE    *fi, *fo;
    char    last3[3][81];
    int     i;

    for (i = 0; i < 3; i++) strcpy(last3[i], "");
    if ((fi = fopen(F_LASTU, "r")) != NULL) {
        while (!feof(fi)) {
            strcpy(last3[0], last3[1]);
            strcpy(last3[1], last3[2]);
            fgets(last3[2], 80, fi);
        }
    }

    if ((fo = fopen(F_LASTU, "w")) != NULL) {
        fprintf(fo, "%s%s  %s at %s\n", 
          &last3[0][0], &last3[1][0], userinfo.name, buf);
        fclose(fo);
    }
}

void changeplan(char *name) {
    char file[255], buf[65535];
    FILE *fi;
    int i;

    sputs("\nYour information file (plan) can contain anything you want.\n");
    sputs("Common uses are to list internet email addresses, quotes, or just something\n");
    sputs("interesting about yourself.\n\n");
    waitkey();

    strcpy(file, F_EDTMP);
    strcat(file, bbs_itoa(pid));
    entermsg(file);
    fi = fopen(file, "r");
    if (fi == NULL) {
        sputs("plan not changed\n");
        return;
    }
    i = 0;
    while (!feof(fi)) buf[i++] = fgetc(fi);
    fclose(fi);
    bbs_putplanfile(buf);
    unlink(file);
}

void readplan(char *defname) {
    char name[9];

    sputs("get information on user? ["); sputs(defname); sputs("] ");
    sgets(name, 8);
    if (*name == 0) strcpy(name, defname);

    if (*name == 0) {
        sputs("canceled\n");
    } else if (!finduser(name)) {
        sputs("no such user\n");
    } else {
        sputs("\n---- Plan file for ["); sputs(name); sputs("] ----\n");
        bbs_getplanfile(name, NULL);
        sputs("----\n");
    }
}

void cli_planline(char *line, void *null) {
    sputs_mime(line);
}

void waitkey(void) {
    sputs("\n<slam that key>"); readc_pause();
    sputs("\r               \r");
}

void saveopts(void) {
    int i;
    char buf[1024];

    strcpy(buf, "");
    for (i = 0; i < MBASES; i++) {
        if (power(2, i) & userinfo.sub) {
            strcat(buf, bbs_itoa(i)); strcat(buf, ",");
        }
    }

    /* remove last comma if there is one.                                   */
    i = strlen(buf) - 1; if (buf[i] == ',') buf[i] = 0;
    bbs_setoptions(userinfo.scrlen, userinfo.options, buf, NULL);
}

void cli_useroptions(char *name, int scrlen, unsigned long opts, char *subs, 
    void *data) {
    char *tp, *p;

    /* get the screen length and the options code                           */
    userinfo.scrlen = scrlen;
    userinfo.options = opts;

    /* go through the list of comma delimited access codes and set a bit    */
    /* for each one.                                                        */
    userinfo.sub = 0;
    if (*subs != 0) {
        p = subs;
        while (p != NULL) {
            (char *) tp = (char *) strsep(&p, ",");
            userinfo.sub = userinfo.sub |
                           power(2, atoi(tp));
        }
    }
}

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

int parseandrun(functable_t *functable, llist aliases, char *string, 
                char *errorstring) {
    char *arg[MAXPARSEARGS], *p;
    char *arg1[MAXPARSEARGS];
    int argnum, arg1num, i, l;
    int matches = 0, match;
    llist lp;

    /* kill whitespace at the end                                           */
    l = strlen(string) - 1;
    while ((l > 0) && (isspace(string[l]))) l--;
    if (l == -1) {
        strcpy(errorstring, "empty command");
        return 0;
    }
    string[l + 1] = 0;

    p = string;
    /* kill initial whitespace                                              */
    while ((*p != 0) && (isspace(*p))) p++;

    /* go through the string breaking it into words                         */
    for (argnum = 0; p != NULL; argnum++) {
        if (argnum > MAXPARSEARGS) break;

        if ((argnum == 1) && (*p == '(')) {
            int done = FALSE;

            /* argument one is a list                                       */
            p++;
            if (*p == 0) {
                arg[0] = (p - 1);
                arg1num = 1;
            } else {
                for (arg1num = 0; !done; arg1num++) {
                    arg1[arg1num] = (char *) strsep(&p, " \t");
                    l = strlen(arg1[arg1num]) - 1;
                    if (arg1[arg1num][l] == ')') {
                        arg1[arg1num][l] = 0;
                        done = TRUE;
                    } else if (p == NULL) done = TRUE;
                }
            }
        } else {
            /* pull off one more argument                                   */
            arg[argnum] = (char *) strsep(&p, " \t");
            if (argnum == 1) { arg1num = 1; arg1[0] = arg[1]; }
        }
    }

    for (i = argnum; i < MAXPARSEARGS; i++) arg[i] = NULL;

    /* lower case the command                                               */
    for (i = 0, l = strlen(arg[0]); i < l; i++) arg[0][i] = lower(arg[0][i]);

    /* see if there is an alias for this command                            */
    for (lp = aliases; lp != NULL; lp = lp->next) {
        env_t *a = (env_t *) lp->ptr;

        if (l > strlen(functable[i].commandst)) continue;
        if (memcmp(arg[0], a->name, l) == 0) {
            char buf[1024];
            int i;

            strcpy(buf, a->value);
            for (i = 1; i < argnum; i++) {
                strcat(buf, " ");
                strcat(buf, arg[i]);
            }
            return parseandrun(functable, NULL, buf, errorstring);
        }
    }
        
    /* otherwise see if we can find this command                            */
    l = strlen(arg[0]);
    for (i = 0; functable[i].commandst != NULL; i++) {
        if (l > strlen(functable[i].commandst)) continue;

        if (memcmp(arg[0], functable[i].commandst, l) == 0) {
            matches++;
            match = i;
        }
    }

    if (matches == 0) {
        /* we couldn't find a command                                       */
        sprintf(errorstring, "invalid command: %s", arg[0]);
    } else if (matches == 1) {
        /* we found it, run the command                                     */
        int rc;

        if (argnum > 1) {
            arg[0] = functable[match].commandst;
            for (i = 0; i < arg1num; i++) {
                arg[1] = arg1[i];
                rc = functable[match].f(string, argnum, arg);
            }
            return rc;
        } else return functable[match].f(string, argnum, arg);
    } else {
        /* we found many possible commands                                  */
        sprintf(errorstring, "ambiguous command: %s", arg[0]);
    }
    return 0;
}

void addtoenv(char *name, char *value) {
    char *argv[3];

    argv[0] = "set";
    argv[1] = name;
    if (value != NULL) argv[2] = value;
    t_manageenv(NULL, (value == NULL) ? 2 : 3, argv);
}

int getfromenv(char *name, char *value) {
    llist p;
    env_t e1;

    e1.name = name;

    p = env;
    while (p != NULL) {
        env_t *e = (env_t *) p->ptr;

        if (envptrcomp(e, &e1)) {
            strcpy(value, e->value);
            return TRUE;
        }
        p = p->next;
    }
    return FALSE;
}
