/***************************************************************************
 *                                                                         *
 *   IRCFNC.C                                                              *
 *                                                                         *
 *   Copyright (c) 1995-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   Internet Relay Chat client functions for Worldgroup.                  *
 *                                                                         *
 *                                        - Craig Yap 7/5/95               *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "gcspsrv.h"
#include "tcpip.h"
#include "dns.h"
#include "irc.h"
#include "alias.h"
#include "galirc.h"

#define FILREV "$Revision: 11 $"

#define CTCPREQT     1             /* client to client request             */
#define CTCPRPLY     0             /* client to client reply               */

static INT *lstrply;               /* limits of -MIN for LIST command      */
static INT minlst;                 /* default minimum for LIST command     */

VOID ircinitcmd(VOID);
static VOID inctcp(CHAR *,CHAR *,CHAR *,CHAR *,SHORT);
static GBOOL ischan(CHAR *);
static CHAR *stotim(LONG);
static VOID initocmds(VOID);
static VOID initirpls(VOID);
static VOID oninvite(CHAR *,CHAR *,CHAR *);
static VOID onnotice(CHAR *,CHAR *,CHAR *);
static VOID onjoin(CHAR *,CHAR *,CHAR *);
static VOID onkick(CHAR *,CHAR *,CHAR *);
static VOID onmode(CHAR *,CHAR *,CHAR *);
static VOID onmsg(CHAR *,CHAR *,CHAR *);
static VOID onpart(CHAR *,CHAR *,CHAR *);
static VOID ontopic(CHAR *,CHAR *,CHAR *);
static VOID on353(CHAR *,CHAR *,CHAR *);
static VOID finmotd(CHAR *,CHAR *,CHAR *);
static VOID onwho(CHAR *,CHAR *,CHAR *);
static VOID onwhois(CHAR *,CHAR *,CHAR *);
static VOID onlist(CHAR *,CHAR *,CHAR *);
static VOID onnick(CHAR *,CHAR *,CHAR *);
static VOID onquit(CHAR *,CHAR *,CHAR *);
static VOID on433(CHAR *,CHAR *,CHAR *);
static VOID on341(CHAR *,CHAR *,CHAR *);
static VOID onnull(CHAR *,CHAR *,CHAR *);
static VOID help(CHAR *,struct ocmdstrt *);
static VOID hhelp(VOID);
static VOID join(CHAR *,struct ocmdstrt *);
static VOID hjoin(VOID);
static VOID kick(CHAR *,struct ocmdstrt *);
static VOID hkick(VOID);
static VOID list(CHAR *,struct ocmdstrt *);
static VOID hlist(VOID);
static VOID msg(CHAR *,struct ocmdstrt *);
static VOID hmsg(VOID);
static VOID names(CHAR *,struct ocmdstrt *);
static VOID hnames(VOID);
static VOID nick(CHAR *,struct ocmdstrt *);
static VOID hnick(VOID);
static VOID part(CHAR *,struct ocmdstrt *);
static VOID hpart(VOID);
static VOID quit(CHAR *,struct ocmdstrt *);
static VOID hquit(VOID);
static VOID who(CHAR *,struct ocmdstrt *);
static VOID hwho(VOID);
static VOID whois(CHAR *,struct ocmdstrt *);
static VOID hwhois(VOID);
static VOID ctcp(CHAR *,struct ocmdstrt *);
static VOID hctcp(VOID);
static VOID mode(CHAR *,struct ocmdstrt *);
static VOID hmode(VOID);
static VOID invite(CHAR *,struct ocmdstrt *);
static VOID hinvite(VOID);
static VOID me(CHAR *,struct ocmdstrt *);
static VOID hme(VOID);
static CHAR *incstr(CHAR *str,INT n);
static struct ocmdstrt *gocinf(CHAR *cmdnam,INT hmsgno,INT kmsgno,
       VOID (*cmdfunc)(CHAR *,struct ocmdstrt *),VOID (*cmdhelp)(VOID));
static GBOOL banchan(CHAR *chan);

VOID
ircinitcmd(VOID)                   /* initializes incoming/outgoing cmd    */
{
     initirpls();
     initocmds();
     lstrply=(INT *)alcmem(sizeof(INT)*nterms);
     minlst=numopt(MINLIST,0,100);
}

static VOID
initirpls(VOID)                    /* initializes incoming replies         */
{
     struct irplstrt icmd;

     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=ontopic;
     regiircmd(&icmd,RPL_TOPIC);
     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=onwho;
     regiircmd(&icmd,RPL_WHOREPLY);
     regiircmd(&icmd,RPL_ENDOFWHO);
     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=onlist;
     regiircmd(&icmd,RPL_LISTSTART);
     regiircmd(&icmd,RPL_LIST);
     regiircmd(&icmd,RPL_LISTEND);
     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=on353;
     regiircmd(&icmd,RPL_NAMREPLY);
     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=onwhois;
     regiircmd(&icmd,RPL_WHOISUSER);
     regiircmd(&icmd,RPL_WHOISSERVER);
     regiircmd(&icmd,RPL_WHOISOPERATOR);
     regiircmd(&icmd,RPL_WHOISIDLE);
     regiircmd(&icmd,RPL_ENDOFWHOIS);
     regiircmd(&icmd,RPL_WHOISCHANNELS);
     regiircmd(&icmd,RPL_AWAY);
     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=finmotd;
     regiircmd(&icmd,RPL_ENDOFMOTD);
     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=on341;
     regiircmd(&icmd,RPL_INVITING);
     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=finmotd;
     regiircmd(&icmd,ERR_NOMOTD);
     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=on433;
     regiircmd(&icmd,ERR_NICKNAMEINUSE);
     setmem(&icmd,sizeof(struct irplstrt),0);
     icmd.rplfunc=onnull;
     regiircmd(&icmd,RPL_ENDOFNAMES);
     stzcpy(icmd.rplname,"INVITE",IRCMDSIZ);
     icmd.rplfunc=oninvite;
     regiircmd(&icmd,NORPLY);
     stzcpy(icmd.rplname,"JOIN",IRCMDSIZ);
     icmd.rplfunc=onjoin;
     regiircmd(&icmd,NORPLY);
     stzcpy(icmd.rplname,"KICK",IRCMDSIZ);
     icmd.rplfunc=onkick;
     regiircmd(&icmd,NORPLY);
     stzcpy(icmd.rplname,"MODE",IRCMDSIZ);
     icmd.rplfunc=onmode;
     regiircmd(&icmd,NORPLY);
     stzcpy(icmd.rplname,"NICK",IRCMDSIZ);
     icmd.rplfunc=onnick;
     regiircmd(&icmd,NORPLY);
     stzcpy(icmd.rplname,"NOTICE",IRCMDSIZ);
     icmd.rplfunc=onnotice;
     regiircmd(&icmd,NORPLY);
     stzcpy(icmd.rplname,"PART",IRCMDSIZ);
     icmd.rplfunc=onpart;
     regiircmd(&icmd,NORPLY);
     stzcpy(icmd.rplname,"PRIVMSG",IRCMDSIZ);
     icmd.rplfunc=onmsg;
     regiircmd(&icmd,NORPLY);
     stzcpy(icmd.rplname,"QUIT",IRCMDSIZ);
     icmd.rplfunc=onquit;
     regiircmd(&icmd,NORPLY);
}

static VOID
initocmds(VOID)                    /* initializes outgoing commands        */
{
     regoircmd(gocinf("HELP",HELPSHLP,HELPKEY,help,hhelp));
     regoircmd(gocinf("CTCP",CTCPSHLP,CTCPKEY,ctcp,hctcp));
     regoircmd(gocinf("JOIN",JOINSHLP,JOINKEY,join,hjoin));
     regoircmd(gocinf("KICK",KICKSHLP,KICKKEY,kick,hkick));
     regoircmd(gocinf("LIST",LISTSHLP,LISTKEY,list,hlist));
     regoircmd(gocinf("MODE",MODESHLP,MODEKEY,mode,hmode));
     regoircmd(gocinf("INVITE",INVSHLP,INVITKEY,invite,hinvite));
     regoircmd(gocinf("ME",MESHLP,MEKEY,me,hme));
     regoircmd(gocinf("MSG",MSGSHLP,MSGKEY,msg,hmsg));
     regoircmd(gocinf("NAMES",NAMESHLP,NAMEKEY,names,hnames));
     regoircmd(gocinf("NICK",NICKSHLP,NICKKEY,nick,hnick));
     regoircmd(gocinf("PART",PARTSHLP,PARTKEY,part,hpart));
     regoircmd(gocinf("QUIT",QUITSHLP,QUITKEY,quit,hquit));
     regoircmd(gocinf("WHO",WHOSHLP,WHOKEY,who,hwho));
     regoircmd(gocinf("WHOIS",WHOISHLP,WHOIKEY,whois,hwhois));
}

/***************************************************************************
 *  default registered commands from server                                *
 ***************************************************************************/

static VOID
oninvite(                          /* INVITE received                      */
CHAR *from,                        /*   nick!user@host of person inviting  */
CHAR *cmd,                         /*   "INVITE" reply                     */
CHAR *rest)                        /*   remainder of line after "INVITE"   */
{
     CHAR *channel,*nick;

     (VOID)cmd;
     if ((nick=strtok(from,"!")) == NULL
      || (channel=strchr(rest,':')) == NULL) {
          return;
     }
     channel++;
     if (banchan(channel)) {
          return;
     }
     pfordy(ONINVIT,"oninvit",nick,channel,NULL,NULL);
}

static VOID
onjoin(                            /* JOIN from server received            */
CHAR *from,                        /*   nick!user@host of person joining   */
CHAR *cmd,                         /*   "JOIN" reply                       */
CHAR *rest)                        /*   remainder of line after "JOIN"     */
{
     CHAR *nick,*userhost;

     (VOID)cmd;
     nick=from;
     if ((userhost=strchr(nick,'!')) == NULL
      || rest == NULL) {
          return;
     }
     *userhost++='\0';
     if (sameas(nick,ircptr->nick)) {
          stzcpy(ircptr->curchan,rest,CHNSIZ);
     }
     pfordy(JOIN,"join",nick,userhost,rest,NULL);
}

static VOID
onkick(                            /* KICK received                        */
CHAR *from,                        /*   nick!user@host of kicker           */
CHAR *cmd,                         /*   "KICK" reply                       */
CHAR *rest)                        /*   remainder of line after "KICK"     */
{
     CHAR *channl,*kickee,*commnt,*cp;

     (VOID)cmd;
     if ((cp=strchr(from,'!')) == NULL
      || (channl=strtok(rest," ")) == NULL
      || (kickee=strtok(NULL," ")) == NULL) {
          return;
     }
     if ((commnt=strtok(NULL,"")) == NULL) {
          commnt="";
     }
     *cp='\0';
     if (*commnt == ':') {
          commnt++;
     }
     if (sameas(kickee,ircptr->nick)) {
          *ircptr->curchan='\0';
          pfordy(UKICK,"ukick",channl,from,commnt,NULL);
     }
     else {
          pfordy(ONKICK,"onkick",kickee,channl,from,commnt);
     }
}

static VOID
onlist(                            /* numeric from server relates to       */
                                   /*   LIST command                       */
CHAR *from,                        /*   server name                        */
CHAR *cmd,                         /*   reply number from server           */
CHAR *rest)                        /*   remainder of line after number     */
{
     CHAR *channl,*usrcnt,*topic;

     (VOID)from;
     switch (atoi(cmd)) {
     case RPL_LISTSTART:
          pfordy(BGLIST,"bglist",NULL,NULL,NULL,NULL);
          break;
     case RPL_LIST:
          if ((channl=strchr(rest,' ')) == NULL
           || (usrcnt=strchr(channl+1,' ')) == NULL
           || (topic=strchr(usrcnt+1,':')) == NULL) {
               return;
          }
          channl++;
          if (banchan(channl)) {
               break;
          }
          *usrcnt++='\0';
          *topic++='\0';
          if (atoi(usrcnt) >= lstrply[usrnum]) {
               pfordy(ONLIST,"onlist",channl,usrcnt,topic,NULL);
          }
          break;
     case RPL_LISTEND:
          pfordy(ENDLIST,"endlist",NULL,NULL,NULL,NULL);
          break;
     }
}

static VOID
onmode(                            /* MODE received                        */
CHAR *from,                        /*   nick!user@host of cmd originator   */
CHAR *cmd,                         /*   "MODE" reply                       */
CHAR *rest)                        /*   remainder of line after "MODE"     */
{
     CHAR *nick,*channel,*cp;

     (VOID)cmd;
     if (strchr(from,'!') == NULL) {
          if (strchr(from,'.') == NULL) {
               nick=rest;
               if ((rest=strchr(nick,':')) != NULL) {
                    rest++;
                    pfordy(UMODE,"umode",from,rest,NULL,NULL);
               }
          }
          else {
               /*-----------------07-19-95 04:27pm-----------------
                This is for server mode.  Do something useful here.
               --------------------------------------------------*/
          }
     }
     else {
          nick=from;
          channel=rest;
          if ((cp=strchr(nick,'!')) == NULL
           || (rest=strchr(channel,' ')) == NULL) {
               return;
          }
          *cp='\0';
          *rest++='\0';
          pfordy(CMODE,"cmode",nick,channel,rest,NULL);
     }
}

static VOID
onmsg(                             /* PRIVMSG received                     */
CHAR *from,                        /*   nick!user@host of msg originator   */
CHAR *cmd,                         /*   "PRIVMSG" reply                    */
CHAR *rest)                        /*   remainder of line after "PRIVMSG"  */
{
     CHAR *nick,*cp,*to;

     (VOID)cmd;
     nick=from;
     to=rest;
     if ((cp=strchr(nick,'!')) == NULL || (rest=strchr(to,' ')) == NULL) {
          return;
     }
     *cp='\0';
     rest++;
     if (*rest == ':') {
          rest++;
     }
     if (*rest == '\x01') {
          inctcp(nick,cp,to,++rest,CTCPREQT);
     }
     else {
          if (ischan(to)) {
               pfordy(PUBMSG,"pubmsg",nick,rest,NULL,NULL);
          }
          else {
               pfordy(PRIVMSG,"privmsg",nick,rest,NULL,NULL);
          }
     }
}

static VOID
on353(                             /* numeric reply for /NAMES command     */
CHAR *from,                        /*   server name                        */
CHAR *cmd,                         /*   numeric reply                      */
CHAR *rest)                        /*   remainder of line after number     */
{
     CHAR *cp;

     (VOID)from;
     (VOID)cmd;
     if ((cp=strchr(rest,':')) != NULL) {
          cp++;
          pfordy(ONNAME,"onname",ircptr->curchan,cp,NULL,NULL);
     }
}

static VOID
onnick(                            /* NICK reply                           */
CHAR *from,                        /*   nick!usr@host - user changing nick */
CHAR *cmd,                         /*   "NICK" reply                       */
CHAR *rest)                        /*   nickname user changed to           */
{
     CHAR *cp;

     (VOID)cmd;
     if ((cp=strchr(from,'!')) == NULL) {
          return;
     }
     *cp='\0';
     if (sameas(from,ircptr->nick)) {
          stzcpy(ircptr->nick,rest,NICKLEN);
     }
     pfordy(ONNICK,"onnick",from,rest,NULL,NULL);
}

static VOID
onnotice(                          /* NOTICE reply                         */
CHAR *from,                        /*   where NOTICE originated            */
CHAR *cmd,                         /*   "NOTICE" reply                     */
CHAR *rest)                        /*   message from NOTICE                */
{
     CHAR *nick,*to,*userhost,*cp;

     (VOID)cmd;
     nick=from;
     if ((userhost=strchr(from,'!')) == NULL) {
          pfordy(ONNOTIC,"onnotic",from,rest,NULL,NULL);
     }
     else {
          *userhost++='\0';
          if (ischan(to=strtok(rest,":"))) {
               pfordy(ONNOTIC,"onnotic",nick,strtok(NULL,""),NULL,NULL);
          }
          else {
               if ((cp=strtok(NULL,"")) != NULL && *cp) {
                    if (*cp == '\x01') {
                         inctcp(nick,userhost,to,++cp,CTCPRPLY);
                    }
                    else {
                         pfordy(ONNOTIC,"onnotic",nick,cp,NULL,NULL);
                    }
               }
          }
     }
}

static VOID
onpart(                            /* PART reply                           */
CHAR *from,                        /*   nick!usr@host of person PARTing    */
CHAR *cmd,                         /*   "PART" command                     */
CHAR *rest)                        /*   channel user left                  */
{
     CHAR *nick,*userhost;

     (VOID)cmd;
     nick=from;
     if ((userhost=strchr(nick,'!')) == NULL) {
          return;
     }
     *userhost++='\0';
     if (sameas(nick,ircptr->nick)) {
          *ircptr->curchan='\0';
     }
     pfordy(PART,"part",nick,userhost,rest,NULL);
}

static VOID
onquit(                            /* QUIT reply                           */
CHAR *from,                        /*   nick!user@host of person who quit  */
CHAR *cmd,                         /*   "QUIT" command                     */
CHAR *rest)                        /*   quit message                       */
{
     CHAR *nick;

     (VOID)cmd;
     if ((nick=strtok(from,"!")) == NULL) {
          return;
     }
     pfordy(ONQUIT,"onquit",nick,rest,NULL,NULL);
}

static VOID
ontopic(                           /* numeric reply for /TOPIC command     */
CHAR *from,                        /*   server name                        */
CHAR *cmd,                         /*   numeric reply                      */
CHAR *rest)                        /*   remainder of line after number     */
{
     CHAR *cp;

     (VOID)from;
     (VOID)cmd;
     if ((cp=strchr(rest,':')) != NULL) {
          cp++;
          pfordy(ONTOPIC,"ontopic",cp,NULL,NULL,NULL);
     }
}

static VOID
onwho(                             /* numeric reply for /WHO command       */
CHAR *from,                        /*   server name                        */
CHAR *cmd,                         /*   numeric reply                      */
CHAR *rest)                        /*   remainder of line after number     */
{
     CHAR *chan,*user,*host,*serv,*nick,*stat,*ircn,*cp;

     (VOID)from;
     switch (atoi(cmd)) {
     case RPL_WHOREPLY:
          if ((cp=strchr(rest,' ')) == NULL) {
               return;
          }
          cp++;
          if ((chan=strtok(cp," ")) == NULL
           || (user=strtok(NULL," ")) == NULL
           || (host=strtok(NULL," ")) == NULL
           || (serv=strtok(NULL," ")) == NULL
           || (nick=strtok(NULL," ")) == NULL
           || (stat=strtok(NULL," ")) == NULL
           || (ircn=strtok(NULL,"")) == NULL) {
               return;
          }
          (VOID)serv;
          if (banchan(chan)) {
               return;
          }
          if (usrptr->flags&ISGCSU) {
               ircdyna("whoreply",chan,nick,stat,incstr(ircn,3));
          }
          else {
               prfmsg(WHOREPLY,chan,nick,stat,user,host,incstr(ircn,3));
          }
          break;
     case RPL_ENDOFWHO:
          pfordy(ENDWHO,"endwho",NULL,NULL,NULL,NULL);
          break;
     }
}

static VOID
onwhois(                           /* numeric reply for /WHOIS command     */
CHAR *from,                        /*   server name                        */
CHAR *cmd,                         /*   numeric reply                      */
CHAR *rest)                        /*   remainder of line after number     */
{
     CHAR *nick,*user,*host,*ircn,*serv,*itim,*cp;

     (VOID)from;
     if ((nick=strchr(rest,' ')) == NULL) {
          return;
     }
     nick++;
     switch (atoi(cmd)) {
     case RPL_AWAY:
          if ((cp=strchr(nick,' ')) == NULL) {
               return;
          }
          *cp='\0';
          pfordy(USRAWAY,"usraway",nick,incstr(cp,2),NULL,NULL);
          break;
     case RPL_WHOISUSER:
          if ((user=strchr(nick,' ')) == NULL
           || (host=strchr(user+1,' ')) == NULL
           || (cp=strchr(host+1,' ')) == NULL
           || (ircn=strchr(cp,':')) == NULL) {
               return;
          }
          ircn++;
          *user++='\0';
          *host++='\0';
          *cp='\0';
          pfordy(WHOISUSR,"whoisusr",nick,user,host,ircn);
          break;
     case RPL_WHOISSERVER:
          if ((serv=strchr(nick,' ')) == NULL
           || (cp=strchr(serv+1,' ')) == NULL) {
               return;
          }
          *serv++='\0';
          *cp++='\0';
          pfordy(WHOISERV,"whoiserv",nick,serv,incstr(cp,1),NULL);
          break;
     case RPL_WHOISOPERATOR:
          if ((cp=strchr(nick,' ')) == NULL) {
               return;
          }
          *cp='\0';
          pfordy(WHOISOPR,"whoisopr",nick,NULL,NULL,NULL);
          break;
     case RPL_WHOISIDLE:
          if ((itim=strchr(nick,' ')) == NULL
           || (cp=strchr(itim+1,' ')) == NULL) {
               return;
          }
          *itim++='\0';
          *cp='\0';
          pfordy(WHOISIDL,"whoisidl",nick,stotim(atol(itim)),NULL,NULL);
          break;
     case RPL_ENDOFWHOIS:
          pfordy(ENDWHOIS,"endwhois",NULL,NULL,NULL,NULL);
          break;
     case RPL_WHOISCHANNELS:
          if ((cp=strchr(nick,':')) == NULL) {
               return;
          }
          cp++;
          pfordy(WHOISCHN,"whoischn",cp,NULL,NULL,NULL);
          break;
     }
}

static VOID
finmotd(                           /* end of MOTD (disable NQC's)          */
CHAR *from,                        /*   server name                        */
CHAR *cmd,                         /*   numeric reply                      */
CHAR *rest)                        /*   remainder of line after number     */
{
     (VOID)from;
     (VOID)cmd;
     (VOID)rest;
     if (!(usrptr->flags&ISGCSU)) {
          btuxnf(usrnum,0,19);
     }
     ircptr->flags|=IRCACTIVE;
}

static VOID
on341(                             /* invited to a channel                 */
CHAR *from,
CHAR *cmd,
CHAR *rest)
{
    CHAR *invitee,*channel;

    (VOID)from;
    (VOID)cmd;
    if ((invitee=strchr(rest,' ')) == NULL
     || (channel=strchr(invitee+1,' ')) == NULL) {
         return;
    }
    invitee++;
    channel++;
    pfordy(ON341,"on341",invitee,channel,NULL,NULL);
}

static VOID
on433(                             /* nickname already in use message      */
CHAR *from,
CHAR *cmd,
CHAR *rest)
{
     (VOID)from;
     (VOID)cmd;
     (VOID)rest;
     if (ircptr->flags&IRCACTIVE) {
          pfordy(ANICKUSE,"anickuse",NULL,NULL,NULL,NULL);
     }
     else {
          pfordy(NNICKUSE,"nnickuse",NULL,NULL,NULL,NULL);
          if (usrptr->flags&ISGCSU) {
               ircptr->psubstt=IRCEXIT;
          }
          else {
               usrptr->substt=IRCEXIT;
          }
     }
}

static VOID
onnull(                            /* keep numeric codes quiet             */
CHAR *from,
CHAR *cmd,
CHAR *rest)
{
     (VOID)from;
     (VOID)cmd;
     (VOID)rest;
     return;
}

/***************************************************************************
 *  default registered commands from user                                  *
 ***************************************************************************/

static VOID
help(                              /* function callback for /HELP command  */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   HELP outgoing command structure    */
{
     CHAR *cp;
     struct olnkcmd *tocmd;

     (VOID)ocmd;
     if ((cp=strchr(inpt,' ')) == NULL) {
          if (usrptr->flags&ISGCSU) {
               prf("help\t");
          }
          else {
               rstrxf();
          }
          for (tocmd=ocmdhead ; tocmd != NULL ; tocmd=tocmd->next) {
               prfmsg(SHRTHLP,tocmd->comstruct.cmdname,
                      tocmd->comstruct.shrthlp);
               if (!(usrptr->flags&ISGCSU)) {
                    outprf(usrnum);
                    clrprf();
               }
          }
          prfmsg(ENDHELP);
          if (usrptr->flags&ISGCSU) {
               r2wprf(TRUE);
          }
          else {
               btuxnf(usrnum,0,19);
          }
     }
     else {
          cp=skpwht(cp);
          for (tocmd=ocmdhead ; tocmd != NULL ; tocmd=tocmd->next) {
               if (sameto(tocmd->comstruct.cmdname,cp)) {
                    tocmd->comstruct.cmdhelp();
                    break;
               }
          }
          if (tocmd == NULL) {
               pfor2w(CMDNOFND,"cmdnofnd",cp,NULL,NULL,NULL);
          }
     }
}

static VOID
hhelp(VOID)                        /* extended help for HELP command       */
{
     pfor2w(HLPHELP,"hlphelp",NULL,NULL,NULL,NULL);
}

static VOID
join(                              /* function callback for /JOIN command  */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   JOIN outgoing command structure    */
{
     CHAR *cp;

     if ((cp=strtok(inpt," ")) == NULL
      || (cp=strtok(NULL,"")) == NULL) {
          if (*ircptr->curchan == '\0') {
               pfor2w(SHRTHLP,"shrthlp",ocmd->cmdname,ocmd->shrthlp,NULL,NULL);
          }
          else {
               pfor2w(JONCHAN,"jonchan",ircptr->curchan,NULL,NULL,NULL);
          }
          return;
     }
     else if (banchan(cp) & !(usrptr->flags&ISGCSU)) {
          prfmsg(NOJBAN);
          return;
     }
     else if (*ircptr->curchan != '\0') {
          sprintf(vdatmp,"PART %s\r\n",ircptr->curchan);
          ircsbf(vdatmp,strlen(vdatmp));
          condis();
     }
     sprintf(vdatmp,"JOIN %s\r\n",cp);
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hjoin(VOID)                        /* extended help for JOIN command       */
{
     pfor2w(HLPJOIN,"hlpjoin",NULL,NULL,NULL,NULL);
}

static VOID
kick(                              /* function callback for /KICK command  */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   KICK outgoing command structure    */
{
     CHAR *nick,*cp;

     (VOID)ocmd;
     if ((cp=strchr(inpt,' ')) == NULL
      || (nick=skpwht(cp)) == (CHAR *)NULL) {
          pfor2w(KNONICK,"knonick",NULL,NULL,NULL,NULL);
          return;
     }
     sprintf(vdatmp,"KICK %s ",ircptr->curchan);
     if ((cp=strchr(nick,' ')) == NULL) {
          stzcat(vdatmp,nick,vdasiz-2);
     }
     else {
          *cp++='\0';
          stzcat(vdatmp,nick,vdasiz);
          stzcat(vdatmp," :",vdasiz);
          stzcat(vdatmp,cp,vdasiz-2);
     }
     stzcat(vdatmp,"\r\n",vdasiz);
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hkick(VOID)                        /* extended help for KICK command       */
{
     pfor2w(HLPKICK,"hkick",NULL,NULL,NULL,NULL);
}

static VOID
list(                              /* function callback for /LIST command  */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   LIST outgoing command structure    */
{
     CHAR *cp,*cp1;

     (VOID)ocmd;
     stzcpy(vdatmp,"LIST\r\n",vdasiz);
     if ((cp=strchr(inpt,' ')) == NULL
      || (cp=skpwht(cp)) == NULL) {
          lstrply[usrnum]=minlst;
     }
     else {
          if (*(cp1=firstwd(cp)) == '\0'
           || !sameas("-MIN",cp1)
           || *(cp1=nextwd()) == '\0') {
               lstrply[usrnum]=minlst;
          }
          else {
               lstrply[usrnum]=max(atoi(cp1),minlst);
          }
     }
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hlist(VOID)                        /* extended help for LIST command       */
{
     CHAR stg[6];

     pfor2w(HLPLIST,"hlplist",itoa(minlst,stg,10),NULL,NULL,NULL);
}

static VOID
msg(                               /* function callback for /MSG command   */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   MSG outgoing command structure     */
{
     CHAR *nick,*cp;

     (VOID)ocmd;
     if ((nick=strchr(inpt,' ')) == NULL
      || (cp=strchr(nick+1,' ')) == NULL) {
          ifcs2w();
          return;
     }
     nick++;
     *cp++='\0';
     if (strlen(nick) > 9) {
          pfor2w(ERRNICK,"errnick",NULL,NULL,NULL,NULL);
          return;
     }
     sprintf(vdatmp,"PRIVMSG %s :%s\r\n",nick,cp);
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hmsg(VOID)                         /* extended help for MSG command        */
{
     pfor2w(HLPMSG,"hlpmsg",NULL,NULL,NULL,NULL);
}

static VOID
names(                             /* function callback for /NAMES command */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   NAME outgoing command structure    */
{
     (VOID)inpt;
     (VOID)ocmd;
     if (*ircptr->curchan == '\0') {
          pfor2w(INLIMBO,"inlimbo",NULL,NULL,NULL,NULL);
     }
     else {
          sprintf(vdatmp,"NAMES %s\r\n",ircptr->curchan);
          ircsbf(vdatmp,strlen(vdatmp));
          condis();
          ifcs2w();
     }
}

static VOID
hnames(VOID)                       /* extended help for NAMES command      */
{
     pfor2w(HLPNAME,"hlpname",NULL,NULL,NULL,NULL);
}

static VOID
nick(                              /* function callback for /NICK command  */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   NICK outgoing command structure    */
{
     CHAR *nick,*cp;

     (VOID)ocmd;
     if ((cp=strchr(inpt,' ')) == NULL
      || (nick=skpwht(cp)) == NULL) {
          pfor2w(NNONICK,"nnonick",NULL,NULL,NULL,NULL);
          return;
     }
     sprintf(vdatmp,"NICK %s\r\n",nick);
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hnick(VOID)                        /* extended help for NICK command       */
{
     pfor2w(HLPNICK,"hlpnick",NULL,NULL,NULL,NULL);
}

static VOID
part(                              /* function callback for /PART command  */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   PART outgoing command structure    */
{
     CHAR *cp;

     (VOID)ocmd;
     stzcpy(vdatmp,"PART ",vdasiz);
     if (samend("PART",inpt)) {
          if (*ircptr->curchan == '\0') {
               pfor2w(INLIMBO,"inlimbo",NULL,NULL,NULL,NULL);
               return;
          }
          else {
               stzcat(vdatmp,ircptr->curchan,vdasiz);
          }
     }
     else {
          if ((cp=strchr(inpt,' ')) != NULL) {
               stzcat(vdatmp,cp+1,vdasiz-2);
          }
          else if (*ircptr->curchan == '\0') {
               pfor2w(INLIMBO,"inlimbo",NULL,NULL,NULL,NULL);
               return;
          }
          else {
               stzcat(vdatmp,ircptr->curchan,vdasiz);
          }
     }
     stzcat(vdatmp,"\r\n",vdasiz);
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hpart(VOID)                        /* extended help for PART command       */
{
     pfor2w(HLPPART,"hlppart",NULL,NULL,NULL,NULL);
}

static VOID
quit(                              /* function callback for /QUIT command  */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   QUIT outgoing command structure    */
{
     CHAR *cp;

     (VOID)ocmd;
     if ((cp=strchr(inpt,' ')) != NULL) {
          stzcpy(vdatmp,"QUIT :",vdasiz);
          stzcat(vdatmp,++cp,vdasiz-2);
          stzcat(vdatmp,"\r\n",vdasiz);
     }
     else {
          sprintf(vdatmp,"QUIT :%s\r\n",rawmsg(QUITMSG));
     }
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     if (usrptr->flags&ISGCSU) {
          ircptr->psubstt=IRCEXIT;
          rsp2write(TRUE,0,NULL,NULL);
     }
     else {
          usrptr->substt=IRCEXIT;
     }
}

static VOID
hquit(VOID)                        /* extended help for QUIT command       */
{
     pfor2w(HLPQUIT,"hlpquit",NULL,NULL,NULL,NULL);
}

static VOID
who(                               /* function callback for /WHO command   */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   WHO outgoing command structure     */
{
     CHAR *cp;

     (VOID)ocmd;
     stzcpy(vdatmp,"WHO ",vdasiz);
     if ((cp=strchr(inpt,' ')) == NULL || (cp=skpwht(cp)) == NULL) {
          if (*ircptr->curchan == '\0') {
               pfor2w(WHONOPRM,"whonoprm",NULL,NULL,NULL,NULL);
               return;
          }
          else {
               stzcat(vdatmp,ircptr->curchan,vdasiz-2);
          }
     }
     else {
          stzcat(vdatmp,cp,vdasiz-2);
     }
     stzcat(vdatmp,"\r\n",vdasiz);
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hwho(VOID)                         /* extended help for WHO command        */
{
     pfor2w(HLPWHO,"hlpwho",NULL,NULL,NULL,NULL);
}

static VOID
whois(                             /* function callback for /WHOIS command */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   WHOIS outgoing command structure   */
{
     CHAR *cp;

     (VOID)ocmd;
     stzcpy(vdatmp,"WHOIS ",vdasiz);
     if ((cp=strchr(inpt,' ')) == NULL || (cp=skpwht(cp)) == NULL) {
          stzcat(vdatmp,ircptr->nick,vdasiz-2);
     }
     else {
          stzcat(vdatmp,cp,vdasiz-2);
     }
     stzcat(vdatmp,"\r\n",vdasiz);
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hwhois(VOID)                       /* extended help for WHOIS command      */
{
     pfor2w(HLPWHOI,"hlpwhoi",NULL,NULL,NULL,NULL);
}

static VOID
ctcp(                              /* function callback for /CTCP command  */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   CTCP outgoing command structure    */
{
     CHAR *nick,*request,*rest;

     (VOID)ocmd;
     if ((rest=strchr(inpt,' ')) == NULL
      || (nick=strtok(rest+1," ")) == NULL
      || (request=strtok(NULL,"")) == NULL) {
          pfor2w(HLPCTCP,"hlpctcp",NULL,NULL,NULL,NULL);
          return;
     }
     sprintf(vdatmp,"PRIVMSG %s :\x01%s\x01\r\n",nick,strupr(request));
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hctcp(VOID)                        /* extended help for CTCP command       */
{
     pfor2w(HLPCTCP,"hlpctcp",NULL,NULL,NULL,NULL);
}

static VOID
mode(                              /* function callback for /MODE command  */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   MODE outgoing command structure    */
{
     (VOID)ocmd;
     if (*ircptr->curchan == '\0') {
          pfor2w(INLIMBO,"inlimbo",NULL,NULL,NULL,NULL);
          return;
     }
     sprintf(vdatmp,"MODE %s %s\r\n",ircptr->curchan,skpwht(inpt+4));
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hmode(VOID)                        /* extended help for MODE command       */
{
     pfor2w(HLPMODE,"hlpmode",NULL,NULL,NULL,NULL);
}

static VOID
invite(                            /* function callback for /INVITE cmd    */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   INVITE outgoing command structure  */
{
     CHAR *cp;

     (VOID)ocmd;
     if (*ircptr->curchan == '\0' || (cp=strchr(inpt,' ')) == NULL) {
          ifcs2w();
          return;
     }
     sprintf(vdatmp,"INVITE %s %s\r\n",skpwht(cp),ircptr->curchan);
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hinvite(VOID)                      /* extended help for INVITE command     */
{
     pfor2w(HLPINVIT,"hlpinvit",NULL,NULL,NULL,NULL);
}

static VOID
me(                                /* function callback for /ME command    */
CHAR *inpt,                        /*   entire input line after cmd char   */
struct ocmdstrt *ocmd)             /*   ME outgoing command structure      */
{
     CHAR *cp;

     (VOID)ocmd;
     if (*ircptr->curchan == '\0') {
          pfor2w(INLIMBO,"inlimbo",NULL,NULL,NULL,NULL);
          return;
     }
     else if ((cp=(inpt+2)) == '\0') {
          ifcs2w();
          return;
     }
     sprintf(vdatmp,"PRIVMSG %s :\x01""ACTION %s\x01\r\n",ircptr->curchan,
             skpwht(cp));
     ircsbf(vdatmp,strlen(vdatmp));
     condis();
     ifcs2w();
}

static VOID
hme(VOID)                          /* extended help for ME command         */
{
     pfor2w(HLPME,"hlpme",NULL,NULL,NULL,NULL);
}

/***************************************************************************
 *  default registered commands for ctcp's (Client To Client Protocol)     *
 ***************************************************************************/

static VOID
inctcp(                            /* function to handle CTCPs             */
CHAR *nick,                        /*   nick of originator                 */
CHAR *userhost,                    /*   userhost of originator             */
CHAR *to,                          /*   who msg was sent to                */
CHAR *rest,                        /*   rest of message                    */
SHORT reqrpl)                      /*   1=request, 0=reply                 */
{
     CHAR *cp,*reply;
     static CHAR timbuf[256];

     (VOID)userhost;
     if (reqrpl) {
          if ((cp=strchr(rest,'\x01')) == NULL) {
               if (ischan(to)) {
                    pfordy(PUBMSG,"pubmsg",nick,rest,NULL,NULL);
               }
               else {
                    pfordy(PRIVMSG,"privmsg",nick,rest,NULL,NULL);
               }
               return;
          }
          *cp='\0';
          if (sameto("ACTION",rest)) {
               if ((cp=strchr(rest,' ')) != NULL) {
                    if (ischan(to)) {
                         pfordy(ONPUBACT,"onpubact",nick,skpwht(cp),NULL,NULL);
                    }
                    else {
                         pfordy(ONPRVACT,"onprvact",nick,skpwht(cp),NULL,NULL);
                    }
               }
          }
          else if (sameto("PING",rest) && (cp=strchr(rest,' ')) != NULL) {
               if (!ischan(to)) {
                    sprintf(vdatmp,"NOTICE %s :\x01PING %s\x01\r\n",nick,
                            skpwht(cp));
                    ircsbf(vdatmp,strlen(vdatmp));
                    condis();
               }
          }
          else if (sameto("FINGER",rest)) {
               stzcpy(timbuf,stotim(time(NULL)-ircptr->lstinp),sizeof(timbuf));
               sprintf(vdatmp,"NOTICE %s :%cFINGER (%s@%s.%s) has been "
                              "idle %s%c\r\n",nick,1,(*alsofusr)(usaptr->userid),
                              hostnam,domain,timbuf,1);
               ircsbf(vdatmp,strlen(vdatmp));
               condis();
          }
     }
     else {
          if ((cp=strchr(rest,'\x01')) == NULL) {
               pfordy(ONNOTIC,"onnotic",nick,rest,NULL,NULL);
               return;
          }
          *cp='\0';
          if ((reply=strtok(rest," ")) == NULL) {
               pfordy(ONNOTIC,"onnotic",nick,rest,NULL,NULL);
               return;
          }
          if (sameas("FINGER",reply)) {
               pfordy(FINGRPLY,"fingrply",nick,strtok(NULL,""),NULL,NULL);
          }
     }
}


/***************************************************************************
 * Utilities                                                               *
 ***************************************************************************/

static GBOOL                       /*   return TRUE if public channel      */
ischan(                            /* is this a public channel?            */
CHAR *chan)                        /*   channel name to check              */
{
     return(*chan == '#' || *chan == '&');
}

static CHAR *                      /*   returns pointer to time string     */
stotim(                            /* converts a number in seconds to      */
                                   /*   weeks, days, hours, minutes, and   */
                                   /*   seconds  i.e. stotim(90) will      */
                                   /*   return "1 minute 30 seconds"       */
LONG rtim)                         /*   input string                       */
{
     LONG now;
     INT secs,mins,hrs,dys,wks;

     now=rtim;
     wks=(INT)(now/604800L);
     now%=604800L;
     dys=(INT)(now/86400L);
     now%=86400L;
     hrs=(INT)(now/3600L);
     now%=3600L;
     mins=(INT)(now/60L);
     secs=(INT)(now%60L);
     clrprf();
     setmem(vdatmp,vdasiz,0);
     if (wks > 0) {
          prfmsg(wks == 1 ? IRCWEEK : IRCWEEKS,wks);
     }
     if (dys > 0) {
          prfmsg(dys == 1 ? IRCDAY : IRCDAYS,dys);
     }
     if (hrs > 0) {
          prfmsg(hrs == 1 ? IRCHOUR : IRCHOURS,hrs);
     }
     if (mins > 0) {
          prfmsg(mins == 1 ? IRCMIN : IRCMINS,mins);
     }
     if (secs >= 0) {
          prfmsg(secs == 1 ? IRCSEC : IRCSECS,secs);
     }
     stlcpy(vdatmp,unpad(stpans(prfbuf)),vdasiz);
     clrprf();
     return(vdatmp);
}

static CHAR *                      /*   returns pointer to string          */
incstr(                            /* increment string pointer by n chars  */
CHAR *str,                         /*   string to increment                */
INT n)                             /*   increment value                    */
{
     return(str+min(strlen(str)-1,n));
}

static struct ocmdstrt *           /*   returns ptr to filled-in structure */
gocinf(                            /* get outgoing command information     */
CHAR *cmdnam,                      /*   command name                       */
INT hmsgno,                        /*   command short help message block   */
INT kmsgno,                        /*   key req'd for command msg block    */
VOID (*cmdfunc)(                   /*   command callback function          */
     CHAR *,
     struct ocmdstrt *),
VOID (*cmdhfnc)(VOID))             /*   command help callback function     */
{
     static struct ocmdstrt cmd;

     stzcpy(cmd.cmdname,cmdnam,IRCMDSIZ);
     stzcpy(cmd.shrthlp,rawmsg(hmsgno),HLPSIZ);
     stzcpy(cmd.key,rawmsg(kmsgno),KEYSIZ);
     cmd.cmdfunc=cmdfunc;
     cmd.cmdhelp=cmdhfnc;
     return(&cmd);
}

static GBOOL
banchan(                                /* is this channel banned?         */
CHAR *chan)                             /*   channel name to check         */
{
     INT i;

     for (i=0 ; i < nbanned ; i++) {
          if (strstr(strlwr(chan),banlst[i]) != NULL) {
               return(TRUE);
          }
     }
     return(FALSE);
}
