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

/*
 * standard library for yabbs clients.  all functions designed for use by
 * clients start with bbs_ and are defined in yabbslib.h.  All internal
 * function start with bbsi_ or c_.  All functions expected to be provided
 * by the client start with cli_.
 */

#include <stdio.h>                          /* standard i/o stuff           */
#include <unistd.h>                         /* more standard stuff          */
#include <stdlib.h>                         /* standard library             */
#include <ctype.h>                          /* character type macros        */
#include <strings.h>                        /* string utils                 */
#include <time.h>                           /* time stuff                   */
#include <stdarg.h>                         /* variable argument macros     */
#include "network.h"                        /* packet types for server      */
#include <assert.h>                         /* assertion macros             */

#include "yabbslib.h"                       /* bbs header file              */

#define ASSERT assert

#define YABBS
#ifndef TRUE
#define TRUE 1                              /* you might have these         */
#define FALSE 0
#endif
#define QUICKKEY "noret"                    /* key for commands that don't  */
                                            /* return anything              */

/* convert a key string to the void * associated with it                    */
#define K2D(key) (bbs_keys[atoi((key))].data)

/*
 * yabbs lib keys.  these are used to pass client data information through
 * yabbslib.
 */

typedef struct yabbslib_key {
    int     used;                           /* key is in use                */
    char    str[5];                         /* string value of key          */
    void    *data;                          /* client data                  */
} ykey;

#define MAXCMDS 64                          /* maximum outstanding commands */

int     bbs_s;                              /* server socket                */
char    bbs_waitkey[64];                    /* message to wait for          */
int     bbs_msgnum, bbs_lastmsgnum;         /* set by getmsghdrs            */
int     bbs_loggedout = FALSE;              /* set when we have logged out  */
int     bbs_key = 0;                        /* current key in use           */
int     bbs_debug = 0;                      /* don't print debug messages   */
ykey    bbs_keys[MAXCMDS];                  /* array of yabbs keys          */
int     bbs_key_init = FALSE;               /* set true when keys are initd */
int     bbs_keysused = 0;                   /* XXX number of keys in use    */

/*
 * function: char bbsi_upper(char i);
 * input:    any valid character
 * returns:  uppercase version of character
 * notes:    internal function, should only be used by yabbslib functions
 */

char bbsi_upper(char i) {
    if ((i >= 'a') && (i <= 'z')) i = i + ('A' - 'a');
    return i;
}

/*
 * function: char *bbsi_strupr(char *str);
 * input:    a null terminated string
 * returns:  pointer to the string
 * notes:    uppercases the string (destroying the original string).
 */

char *bbsi_strupr(char *str) {
    int i, l;

    for (i = 0, l = strlen(str); i < l; i++) str[i] = bbsi_upper(str[i]);
    return str;
}

/*
 * function: char *bbsi_itoa(int i);
 * input:    any integer
 * output:   string representing integer
 * notes:    internal function, should only be used by yabbslib functions
 *           this function returns a pointer to static buffer
 */
char *bbsi_itoa(int i) {
    static char buf[255];

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

/*
 * function: char *bbsi_itoa(int i);
 * input:    any unsigned number
 * output:   string representing unsigned number
 * notes:    internal function, should only be used by yabbslib functions
 *           this function returns a pointer to static buffer
 */
char *bbsi_utoa(unsigned long i) {
    static char buf[255];

    sprintf(buf, "%lu", i);
    return buf;
}

char *bbsi_getkeydata(void *data) {
    int i;

    if (!bbs_key_init) {
        for (i = 0; i < MAXCMDS; i++) {
            bbs_keys[i].used = FALSE;
            sprintf(bbs_keys[i].str, "%i", i);
        }
        bbs_key_init = TRUE;
    }

    for (i = (bbs_key + 1) % MAXCMDS; i != bbs_key; i = (i + 1) % MAXCMDS) 
        if (!bbs_keys[i].used) break;

    /* XXX - this error case should be handled */
    assert(i != bbs_key);

    bbs_key = i;
    bbs_keys[i].used = TRUE;
    bbs_keys[i].data = data;
    bbs_keysused++;

    return bbs_keys[bbs_key].str;
}

/*
 * function: char* bbsi_getkey();
 * input:    none
 * output:   the next unique key for use in server messages
 * notes:    internal function, should only be used by yabbslib functions
 *           this function returns a pointer to static buffer
 */
char *bbsi_getkey(void) {
    return bbsi_getkeydata(NULL);
}

void bbsi_freekey(char *key) {
    bbs_keysused--;
    bbs_keys[atoi(key)].used = FALSE;
}

/*
 * function: char* bbsi_thiskey();
 * input:    none
 * output:   the current key for use in server messages
 * notes:    internal function, should only be used by yabbslib functions
 *           this function returns a pointer to static buffer
 */
char *bbsi_thiskey(void) {
    ASSERT((bbs_key >= 0) && (bbs_key < MAXCMDS));
    ASSERT(bbs_keys[bbs_key].used);
    return bbs_keys[bbs_key].str;
}

/*
 * -----------------------------------------------------------------------
 * all of the functions for dealing with protocol commands from the server
 * -----------------------------------------------------------------------
 */

int c_yabbshi(char **argv) {
    return 1;
}

int c_bad(char **argv) {
    char buf[255];

    sprintf(buf, "SERVER WARNING: %s", argv[1]);
    cli_error(buf);

    return 0;
}

int c_talk(char **argv) {
    char buf[255];

    if (strcmp(argv[1], "PUBLIC") == 0) {
        cli_talkline(argv[2], argv[3], argv[4], TL_PUBLIC);
    } else if (strcmp(argv[1], "PRIVATE") == 0) {
        cli_talkline(argv[2], argv[3], argv[4], TL_PRIVATE);
    } else if (strcmp(argv[1], "ACTION") == 0) {
        cli_talkline(argv[2], argv[3], argv[4], TL_ACTION);
    } else if (strcmp(argv[1], "HIDE") == 0) {
        cli_talkline(argv[2], argv[3], argv[4], TL_HIDE);
    } else if (strcmp(argv[1], "SHOW") == 0) {
        cli_talkline(argv[2], argv[3], argv[4], TL_SHOW);
    } else if (strcmp(argv[1], "JOIN") == 0) {
        cli_talkline(argv[2], argv[3], argv[4], TL_JOIN);
    } else if (strcmp(argv[1], "LEAVE") == 0) {
        cli_talkline(argv[2], argv[3], argv[4], TL_LEAVE);
    } else {
        sprintf(buf, "*** unknown message type TALK:%s from server", argv[1]);
        cli_error(buf);
    }

    return 0;
}

int c_page(char **argv) {
    cli_page(argv[1]);

    return 0;
}

int c_who(char **argv) {
    char buf[255], atime[32];
    int idletime;
    time_t tp;

    if (strcmp(argv[1], "START") == 0) {
        strcpy(bbs_waitkey, bbsi_thiskey());
    } else if (strcmp(argv[1], "LINE") == 0) {
        tp = (time_t) atoi(argv[4]);
        strcpy(atime, (char *) ctime(&tp));
        atime[strlen(atime) - 1] = 0;
        idletime = atoi(argv[5]);
        cli_wholine(argv[2], argv[3], atime, idletime, K2D(argv[0]));
        strcpy(bbs_waitkey, bbsi_thiskey());
    } else if (strcmp(argv[1], "END") == 0) {
        bbsi_freekey(argv[0]);
    } else {
        sprintf(buf, "yabbslib: c_who: invalid packet type WHO:%s from server", 
            argv[1]);
        bbsi_freekey(argv[0]);
        cli_error(buf);
    }

    return 0;
}

int c_channels(char **argv) {
    char buf[255];

    if (strcmp(argv[1], "START") == 0) {
        strcpy(bbs_waitkey, bbsi_thiskey());
    } else if (strcmp(argv[1], "LINE") == 0) {
        cli_channelline(argv[2], argv[3], K2D(argv[0]));
        strcpy(bbs_waitkey, bbsi_thiskey());
    } else if (strcmp(argv[1], "END") == 0) {
        bbsi_freekey(argv[0]);
    } else {
        sprintf(buf, 
            "yabbslib: c_channels: invalid packet type CHANNELS:%s from server", 
            argv[1]);
        bbsi_freekey(argv[0]);
        cli_error(buf);
    }

    return 0;
}

int c_msg(char **argv) {
    char buf[255];

    if (strcmp(argv[1], "START") == 0) {
    } else if (strcmp(argv[1], "TO") == 0) {
        cli_msgline(argv[2], ML_TO, K2D(argv[0]));
    } else if (strcmp(argv[1], "FROM") == 0) {
        cli_msgline(argv[2], ML_FROM, K2D(argv[0]));
    } else if (strcmp(argv[1], "TITLE") == 0) {
        cli_msgline(argv[2], ML_TITLE, K2D(argv[0]));
    } else if (strcmp(argv[1], "TIME") == 0) {
        cli_msgline(argv[2], ML_TIME, K2D(argv[0]));
    } else if (strcmp(argv[1], "DELETED") == 0) {
        cli_msgline(argv[2], ML_DELETED, K2D(argv[0]));
    } else if (strcmp(argv[1], "LINE") == 0) {
        cli_msgline(argv[2], ML_LINE, K2D(argv[0]));
    } else if (strcmp(argv[1], "END") == 0) {
        bbsi_freekey(argv[0]);
        return M_OK;
    } else if (strcmp(argv[1], "MSGOOB") == 0) {
        bbsi_freekey(argv[0]);
        return M_OOB;
    } else if (strcmp(argv[1], "PRIVATE") == 0) {
        bbsi_freekey(argv[0]);
        return M_PRIVATE;
    } else {
        sprintf(buf, "yabbslib: c_msg: invalid packet type MSG:%s from server", 
            argv[1]);
        cli_error(buf);
        bbsi_freekey(argv[0]);
        return M_ERR;
    }
    /* recurse until we get our whole message */
    strcpy(bbs_waitkey, bbsi_thiskey());

    return 0;
}

int c_msghdr(char **argv) {
    if (strcmp(argv[3], "OOB") == 0) {
        bbs_waitkey[0] = 0;
        bbsi_freekey(argv[0]);
        return M_OOB;                           /* invalid message number   */
    } else if (strcmp(argv[3], "PRIVATE") == 0) {
        return M_PRIVATE;                       /* private message          */
    }

    cli_msghdrline(bbs_msgnum, argv[1], argv[2], argv[3], argv[4], 
        K2D(argv[0]));

    if (bbs_msgnum != bbs_lastmsgnum) {
        strcpy(bbs_waitkey, bbsi_thiskey());
        bbs_msgnum++;
    } else bbsi_freekey(argv[0]);

    return M_OK;
}

int c_msgnum(char **argv) {
    int msgnum;

    msgnum = atoi(argv[2]);
    bbsi_freekey(argv[0]);

    return msgnum;
}

int c_delmsgret(char **argv) {
    bbsi_freekey(argv[0]);
    if (argv[3][0] == 'P') return M_PRIVATE;
    else return M_OK;
}

int c_basetitle(char **argv) {
    int baseid;

    if (strcmp(argv[1], "END") == 0) {
        bbsi_freekey(argv[0]);
        return 0;
    } else if (strcmp(argv[1], "START") == 0) {
        strcpy(bbs_waitkey, bbsi_thiskey());
    } else if (strcmp(argv[1], "LINE") == 0) {
        baseid = atoi(argv[2]);
        cli_msgbasename(baseid, argv[3], K2D(argv[0]));
        strcpy(bbs_waitkey, bbsi_thiskey());
    }

    return 0;
}

int c_useropts(char **argv) {
    cli_useroptions(argv[1], atoi(argv[2]), atoi(argv[3]), argv[4], 
        K2D(argv[0]));
    bbsi_freekey(argv[0]);

    return 0;
}

int c_login(char **argv) {
    char buf[255];

    bbsi_freekey(argv[0]);
    if (strcmp(argv[1], "OK") == 0) return TRUE;
    else {
        sprintf(buf, "problem logging in: %s", argv[1]);
        cli_error(buf);
        return FALSE;
    }
}

int c_lastmsg(char **argv) {
    bbsi_freekey(argv[0]);
    return atoi(argv[2]);
}

int c_newuser(char **argv) {
    char buf[255];

    bbsi_freekey(argv[0]);
    if (strcmp(argv[1], "OK") == 0) return TRUE; 
    else {
        sprintf(buf, "problem creating new user: %s", argv[1]);
        cli_error(buf);
        return FALSE;
    }
}

int c_founduser(char **argv) {
    if (strcmp(argv[1], "START") == 0) {
        strcpy(bbs_waitkey, bbsi_thiskey());
    } else if (strcmp(argv[1], "END") == 0) {
        bbsi_freekey(argv[0]);
        return 0;
    } else {
        cli_founduser(argv[3], K2D(argv[0]));
        strcpy(bbs_waitkey, bbsi_thiskey());
    }

    return 0;
}

int c_userexist(char **argv) {
    bbsi_freekey(argv[0]);
    if (strcmp(argv[2], "OK") == 0) return TRUE; else return FALSE;
}

int c_txtfile(char **argv) {
    if (strcmp(argv[2], "END") == 0) {
        bbsi_freekey(argv[0]);
        return 0;
    }
    else if (strcmp(argv[2], "LINE") == 0) cli_textline(argv[3], K2D(argv[0]));
    strcpy(bbs_waitkey, bbsi_thiskey());
    
    return 0;
}

int c_planfile(char **argv) {
    if (strcmp(argv[2], "END") == 0) {
        bbsi_freekey(argv[0]);
        return 0;
    }
    else if (strcmp(argv[2], "LINE") == 0) cli_planline(argv[3], K2D(argv[0]));
    else if (strcmp(argv[2], "BADNAME") == 0) {
        bbsi_freekey(argv[0]);
        cli_planline("No plan file<nl>", K2D(argv[0]));
        return 0;
    }
    strcpy(bbs_waitkey, bbsi_thiskey());

    return 0;
}

int c_gfileindex(char **argv) {
    if (strcmp(argv[1], "START") == 0) {
    } else if (strcmp(argv[1], "NOFILE") == 0) {
        cli_error("Problem accessing gfile");
        bbsi_freekey(argv[0]);
        return 0;
    } else if (strcmp(argv[1], "LINE") == 0) {
        cli_gfileindexline(argv[3], argv[4], argv[2], K2D(argv[0]));
    } else if (strcmp(argv[1], "END") == 0) {
        bbsi_freekey(argv[0]);
        return 1;
    }
    strcpy(bbs_waitkey, bbsi_thiskey());

    return 0;
}

int c_gfile(char **argv) {
    if (strcmp(argv[1], "START") == 0) {
    } else if (strcmp(argv[1], "NOFILE") == 0) {
        cli_error("Problem accessing gfile");
        bbsi_freekey(argv[0]);
        return 0;
    } else if (strcmp(argv[1], "LINE") == 0) {
        cli_gfileline(argv[2], K2D(argv[0]));
    } else if (strcmp(argv[1], "END") == 0) {
        bbsi_freekey(argv[0]);
        return 1;
    }
    strcpy(bbs_waitkey, bbsi_thiskey());

    return 0;
}

int c_shutdown(char **argv) {
    char buf[255];

    sprintf(buf, "system shutdown: %s", argv[1]);
    cli_error(buf);

    return 0;
}

int c_announce(char **argv) {
    int type;

    if      (strcmp(argv[1], "LOGIN") ==       0) type = AN_LOGIN;
    else if (strcmp(argv[1], "LOGOUT") ==      0) type = AN_LOGOUT;
    else if (strcmp(argv[1], "NEWMSG") ==      0) type = AN_NEWMSG;
    else if (strcmp(argv[1], "CHANJOIN") ==    0) type = AN_CHANJOIN;
    else if (strcmp(argv[1], "CHANLEAVE") ==   0) type = AN_CHANLEAVE;
    else if (strcmp(argv[1], "CHANCREATE") ==  0) type = AN_CHANCREATE;
    else if (strcmp(argv[1], "CHANDESTROY") == 0) type = AN_CHANDESTROY;
    else if (strcmp(argv[1], "CHANNEWNAME") == 0) type = AN_CHANNEWNAME;

    cli_announce(type, argv[2], argv[3]);

    return 0;
}

/*
 * ------------------------------------------------------------------------
 * functions that people should use to interact with the server.  many of
 * these return data though "call-back" functions, all of which start with
 * cli_
 * ------------------------------------------------------------------------
 */

/*
 * this deals with incoming messages from the server.  it should be called
 * whenever your select or SIGIO setup discovers that there is something
 * waiting on the server soocket (bbs_s).
 */
int bbs_handlemsg(void) {
    char *token[1024], **tp = token, *p, *key;
    int x, i, j, l, ci, plen, done, rcode, moremsgs;
    char fullmsg[1024], msg[1024], buf[255]; 

    moremsgs = TRUE;
    while (moremsgs) {
        strcpy(fullmsg, bbsi_creadp());

        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;

        done = FALSE;
        plen = strlen(msg);
    
        if (strchr(msg, 0x18) == NULL) {
            cli_fatalerror("this server speaks an older version of the yabbs protocol");
        } else {
            p = msg;                        /* parse string p               */
            tp = token;
            key = (char *) strsep(&p, "\x18");  /* read the command key     */
            *(tp++) = (char *) strsep(&p, "\x18");  /* read first word      */
            for (x = 0, l = strlen(token[0]); x < l; x++) 
                token[0][x] = bbsi_upper(token[0][x]); 
            for (ci = 0; ci < C_CNUM; ci++) /* is it in our table?          */
                if (strcmp(s_ctable[ci].cname, token[0]) == 0) break;
            if (ci == C_CNUM) {             /* no command with that name    */
                sprintf(buf, "yabbslib: bbs_handlemsg: invalid packet type %s from server", 
                    token[0]);
                cli_error(buf);
                return -1;
            }                               /* otherwise ci points to cmd   */
            while (p != NULL) {
                (char *) *(tp++) = (char *) strsep(&p, "\x18");
            }
        }
    
        /* clear the bbs_waitkey flag if this is the right type of message  */
        if (strcmp(bbs_waitkey, key) == 0) *bbs_waitkey = 0;

        /* put the key into token[0]                                        */
        token[0] = key;

        /* call the function to handle this type of message                 */
        rcode = s_ctable[ci].f(token);

        if (*bbs_waitkey == 0) moremsgs = FALSE;
    }
    return rcode;
}

/*
 * function: bbs_logout()
 * notes:    closes the connection to the yabbs server
 */
void bbs_logout() {
    bbs_loggedout = TRUE;
    bbsi_csendp(1, QUICKKEY, "LOGOUT");
}

/*
 * function: int bbs_login(char *name, char *pass)
 * input:    name - account name to login under
 *           pass - password to use for account
 * output:   TRUE if the login was valid, FALSE otherwise
 * notes:    if they weren't given access an error message will be send with
 *           cli_error() callback.
 */
int bbs_login(char *name, char *pass) {
    strcpy(bbs_waitkey, bbsi_getkey());
    bbsi_csendp(3, bbs_waitkey, "LOGIN", name, pass);
    return bbs_handlemsg();
}

/*
 * find a server with the name <server> in the yabbsrc file <yabbsrc>.
 * passes back the server's hostname.  returns TRUE if a server was found, 
 * false otherwise.
 */
int findserver(char *server, char *yabbsrc, char *hostname) {
    FILE *f;
    char fcmd[255], *p, *arg[10];
    int i;

    f = fopen(yabbsrc, "r");
    bbsi_strupr(server);
    if (f != NULL) {
        while (!feof(f)) {
            int l;

            fgets(fcmd, 255, f);

            p = fcmd;
            /* ignore leading whitespace                            */
            while (isspace(*p)) p++;

            /* kill trailing newline                                */
            l = strlen(fcmd) - 1;
            if (fcmd[l] == '\n') fcmd[l] = 0;

            i = 0;
            while ((p != NULL) && (i < 10)) 
                arg[i++] = (char *) strsep(&p, " ");
            bbsi_strupr(arg[0]);

            if (strcmp(arg[0], "SERVER") == 0) {
                if (strcmp(server, bbsi_strupr(arg[1])) == 0) {
                    strcpy(hostname, arg[2]);
                    return TRUE;
                }
            }
        }
        fclose(f);
    }
    return FALSE;
}

/*
 * connect to a server with the name provided in server:port format.
 */
void doconnect(char *server) {
    char *portbuf;
    int port;

    /* if the server name has : in it it is assumed to be in server:port    */
    /* format.                                                              */
    if ((portbuf = (char *) strchr(server, ':')) == NULL) {
        port = YABBSPORT;   
    } else {
        /* 
         * server will still point to a buffer with the server, the portbuf
         * will point to a buffer with the port in it
         */
        *portbuf = 0; portbuf++;
        port = atoi(portbuf);
    }
    bbs_connect_port(server, port);
}

/*
 * connect to a server given a server name.  searchs yabbsrc files to see
 * if the server name should be expanded, and tries to connect to a default
 * server if the parameter is NULL.
 */
void bbs_connect(char *serverarg) {
    char yabbsrc[1024], server[255], hostname[255];
    char *home = getenv("HOME");

    /* if the server wasn't given look for the default server in rc's       */
    if (serverarg == NULL) strcpy(server, "default"); 
    else strcpy(server, serverarg);
    
    /* first see if they have an entry for this server in their .yabbsrc    */
    sprintf(yabbsrc, "%s/%s", (home == NULL) ? "" : home, 
        cli_hiddenyabbsrcname());
    if (findserver(server, yabbsrc, hostname)) {
        doconnect(hostname);    
        return;
    }

    /* now try the system yabbsrc                                           */
    sprintf(yabbsrc, "%s/%s", LOCALETC, cli_yabbsrcname());
    if (findserver(server, yabbsrc, hostname)) {
        doconnect(hostname);    
        return;
    }

    /* the server wasn't in either default rc.  try using the hardcoded     */
    /* default server name if a name wasn't provided, otherwise try and     */
    /* connect to the server with the hostname provided.                    */
    if (serverarg == NULL) strcpy(server, SERVER);

    doconnect(server);
}

/*
 * function: bbs_getmsg(int baseid, int msgnum);
 * inputs:   baseid - the message base to get the message from
 *           msgnum - the number of the message to get
 * returns:  M_OK if it got the message, M_OOB if the baseid or message number
 *           is invalid, M_PRIVATE if the message is private, or M_ERR if there
 *           is another error.  If it returns M_ERR a reason will be sent back
 *           using the cli_error callback.
 *
 * notes:    the message is sent back using the cli_msgline(type, string) 
 *           callback.  the types and corresponding strings are:
 *             ML_TO       who the message is to
 *             ML_FROM     who the message is from
 *             ML_TITLE    the title of the message
 *             ML_TIME     when the message was sent
 *             ML_DELETED  deleted status of the message
 *             ML_LINE     the lines of the message
 *           there can be multiple ML_LINE messages, but only one of each
 *           of the other types.
 */
int bbs_getmsg(int baseid, int msgnum, void *data) {
    char baseid_st[10];

    strcpy(baseid_st, bbsi_itoa(baseid));
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(3, bbs_waitkey, "GETMSG", baseid_st, bbsi_itoa(msgnum));
    return bbs_handlemsg();
}

/*
 * function: bbs_getmsghdr(int baseid, int msgnum);
 * inputs:   baseid - the message base to get the message from
 *           msgnum - the message number to get
 * returns:  M_OOB if the message was out of bounds, M_PRIVATE if it private,
 *           or M_OK otherwise.
 * notes:    the data is returned using the cli_msghdrline() callback.  The
 *           prototype for this should be: cli_msghdrline(int msgnum, char *to, 
 *           char *from, char *deleted, char *subject);
 * 
 *           if you are reading multiple headers in a loop please use 
 *           bbs_getmsghdrs instead.  it is faster on the server.
 */
int bbs_getmsghdr(int baseid, int msgnum, void *data) {
    char baseid_st[10], msgnum_st[10];

    strcpy(baseid_st, bbsi_itoa(baseid));
    strcpy(msgnum_st, bbsi_itoa(msgnum));
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(4, bbs_waitkey, "GETMSGHDRS", baseid_st, msgnum_st, msgnum_st);
    bbs_msgnum = msgnum;
    bbs_lastmsgnum = msgnum;
    return bbs_handlemsg();
}

/*
 * function: bbs_getmsghdrs(int baseid, int msgnum1, msgnum2);
 * inputs:   baseid  - the message base to get the message from
 *           msgnum1 - the first message header to get
 *           msgnum2 - the last message header to get
 * returns:  M_OOB if the message was out of bounds, M_PRIVATE if it private,
 *           or M_OK otherwise (for the last message only).
 * notes:    the data is returned using the cli_msghdrline() callback.  There
 *           will be one call to this for each message header that the user
 *           has access to read.
 */
int bbs_getmsghdrs(int baseid, int msgnum1, int msgnum2, void *data) {
    char msgnum1_st[10], msgnum2_st[10];

    strcpy(msgnum1_st, bbsi_itoa(msgnum1));
    strcpy(msgnum2_st, bbsi_itoa(msgnum2));
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(4, bbs_waitkey, "GETMSGHDRS", bbsi_itoa(baseid), msgnum1_st, 
        msgnum2_st);
    bbs_msgnum = msgnum1;
    bbs_lastmsgnum = msgnum2;
    return bbs_handlemsg();
}

int bbs_getgfileindex(char *path, void *data) {
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(2, bbs_waitkey, "GFILEINDEX", path);
    return bbs_handlemsg();
}

int bbs_getgfile(char *path, void *data) {
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(2, bbs_waitkey, "GETGFILE", path);
    return bbs_handlemsg();
}

void bbs_getplanfile(char *filename, void *data) {
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(2, bbs_waitkey, "GETPLANFILE", filename);
    bbs_handlemsg();
}

void bbs_gettextfile(char *filename, void *data) {
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(2, bbs_waitkey, "GETTXTFILE", filename);
    bbs_handlemsg();
}

void bbs_who(void *data) {
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(1, bbs_waitkey, "WHO");
    bbs_handlemsg();
}

void bbs_channels(void *data) {
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(1, bbs_waitkey, "CHANNELS");
    bbs_handlemsg();
}

void bbs_talkjoin(char *channel) {
    bbsi_csendp(3, QUICKKEY, "TALK", "JOIN", channel);
}

void bbs_talkjoinpriv(char *channel) {
    bbsi_csendp(3, QUICKKEY, "TALK", "JOINPRIV", channel);
}

void bbs_talkleave(char *channel) {
    bbsi_csendp(3, QUICKKEY, "TALK", "LEAVE", channel);
}

void bbs_talkpublic(char *channel, char *text) {
    bbsi_csendp(4, QUICKKEY, "TALK", "PUBLIC", channel, text);
}

void bbs_talkprivate(char *to, char *text) {
    bbsi_csendp(4, QUICKKEY, "TALK", to, "", text);
}

void bbs_talkaction(char *channel, char *text) {
    bbsi_csendp(4, QUICKKEY, "TALK", "ACTION", channel, text);
}

void bbs_talkshow(char *channel) {
    bbsi_csendp(3, QUICKKEY, "TALK", "SHOW", channel);
}

void bbs_talkhide(char *channel) {
    bbsi_csendp(3, QUICKKEY, "TALK", "HIDE", channel);
}

void bbs_talkquit(void) {
    bbsi_csendp(3, QUICKKEY, "TALK", "QUIT", "");
}

void bbs_page(char *name) {
    bbsi_csendp(2, QUICKKEY, "PAGE", name);
}

int bbs_getmsgnum(int baseid) {
    strcpy(bbs_waitkey, bbsi_getkey());
    bbsi_csendp(2, bbs_waitkey, "GETMSGNUM", bbsi_itoa(baseid));
    return bbs_handlemsg();
}

void bbs_addmsg(int baseid, char *to, char *subject, char *msg) {
    char buf[255];
    int bp, x, msglen;

    bbsi_csendp(3, QUICKKEY, "ADDMSG", "START", bbsi_itoa(baseid));
    bbsi_csendp(3, QUICKKEY, "ADDMSG", "TO", to);
    bbsi_csendp(3, QUICKKEY, "ADDMSG", "TITLE", subject);

    msglen = strlen(msg);
    bp = 0;
    for (x = 0; x < msglen; x++) {
        if (msg[x] == '\n') {
            buf[bp] = 0; 
            bbsi_csendp(3, QUICKKEY, "ADDMSG", "LINE", buf);
            bp = 0;
        } else if (isprint(msg[x])) buf[bp++] = msg[x];
    }

    bbsi_csendp(3, QUICKKEY, "ADDMSG", "END", "");
}

void bbs_putplanfile(char *msg) {
    char buf[255];
    int bp, x, msglen;

    bbsi_csendp(3, QUICKKEY, "PUTPLANFILE", "START", "");

    msglen = strlen(msg);
    bp = 0;
    for (x = 0; x < msglen; x++) {
        if (msg[x] == '\n') {
            buf[bp] = 0; 
            bbsi_csendp(3, QUICKKEY, "PUTPLANFILE", "LINE", buf);
            bp = 0;
        } else if (isprint(msg[x])) buf[bp++] = msg[x];
    }

    bbsi_csendp(3, QUICKKEY, "PUTPLANFILE", "END", "");
}

int bbs_deletemsg(int baseid, int msgnum) {
    char baseid_st[10];

    strcpy(baseid_st, bbsi_itoa(baseid));
    strcpy(bbs_waitkey, bbsi_getkey());
    bbsi_csendp(3, bbs_waitkey, "DELETEMSG", baseid_st, bbsi_itoa(msgnum));
    return bbs_handlemsg();
}

void bbs_getbasenames(void *data) {
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(1, bbs_waitkey, "GETBASES");
    bbs_handlemsg();
}

void bbs_getoptions(void *data) {
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(1, bbs_waitkey, "GETOPTS");
    bbs_handlemsg();
}

void bbs_setoptions(int scrlen, unsigned long options, char *subs, void *data) {
    char scrlen_st[10];

    strcpy(scrlen_st, bbsi_itoa(scrlen));
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(4, bbs_waitkey, "SETOPTS", scrlen_st, bbsi_utoa(options), subs);
    bbs_handlemsg();
}

int bbs_getlastmsg(int baseid) {
    strcpy(bbs_waitkey, bbsi_getkey());
    bbsi_csendp(2, bbs_waitkey, "GETLASTMSG", bbsi_itoa(baseid));
    return bbs_handlemsg();
}

int bbs_setlastmsg(int baseid, int msgnum) {
    char baseid_st[10];

    strcpy(baseid_st, bbsi_itoa(baseid));
    strcpy(bbs_waitkey, bbsi_getkey());
    bbsi_csendp(3, bbs_waitkey, "SETLASTMSG", baseid_st, bbsi_itoa(msgnum));
    return bbs_handlemsg();
}

void bbs_setpassword(char *pass) {
    bbsi_csendp(2, QUICKKEY, "SETPASSWD", pass);
}

void bbs_finduser(char *partialname, void *data) {
    strcpy(bbs_waitkey, bbsi_getkeydata(data));
    bbsi_csendp(2, bbs_waitkey, "FINDUSER", partialname);
    bbs_handlemsg();
}

int bbs_newuser(char *name, char *pass) {
    strcpy(bbs_waitkey, bbsi_getkey());
    bbsi_csendp(3, bbs_waitkey, "NEWUSER", name, pass);
    return bbs_handlemsg();
}

int bbs_userexist(char *name) {
    strcpy(bbs_waitkey, bbsi_getkey());
    bbsi_csendp(2, bbs_waitkey, "USEREXIST", name);
    return bbs_handlemsg();
}

void bbs_shutdown(char *mode, char *message) {
    bbsi_csendp(3, QUICKKEY, "SHUTDOWN", mode, message);
}

void bbs_onfrom(char *onfrom) {
    bbsi_csendp(2, QUICKKEY, "ONFROM", onfrom);
}

void bbs_announce(int type) {
    char buf[16];

    switch (type) {
        case AN_LOGIN: strcpy(buf, "LOGIN"); break;
        case AN_LOGOUT: strcpy(buf, "LOGOUT"); break;
        case AN_NEWMSG: strcpy(buf, "NEWMSG"); break;
        case AN_CHANJOIN: strcpy(buf, "CHANJOIN"); break;
        case AN_CHANLEAVE: strcpy(buf, "CHANLEAVE"); break;
        case AN_CHANCREATE: strcpy(buf, "CHANCREATE"); break;
        case AN_CHANDESTROY: strcpy(buf, "CHANDESTROY"); break;
        case AN_CHANNEWNAME: strcpy(buf, "CHANNEWNAME"); break;
    }
    bbsi_csendp(3, QUICKKEY, "REQUEST", "ANNOUNCE", buf);
}

void bbs_unannounce(int type) {
    char buf[16];

    switch (type) {
        case AN_LOGIN: strcpy(buf, "LOGIN"); break;
        case AN_LOGOUT: strcpy(buf, "LOGOUT"); break;
        case AN_NEWMSG: strcpy(buf, "NEWMSG"); break;
        case AN_CHANJOIN: strcpy(buf, "CHANJOIN"); break;
        case AN_CHANLEAVE: strcpy(buf, "CHANLEAVE"); break;
        case AN_CHANCREATE: strcpy(buf, "CHANCREATE"); break;
        case AN_CHANDESTROY: strcpy(buf, "CHANDESTROY"); break;
        case AN_CHANNEWNAME: strcpy(buf, "CHANNEWNAME"); break;
    }
    bbsi_csendp(3, QUICKKEY, "REQUEST", "UNANNOUNCE", buf);
}
