/*
 * 4m v2.15 (c_cmds.c)
 * (C) Copyright 1990 by Carrick Sean Casey
 * (C) Copyright 1993 by Scott Chasin and Michaeljon Miller
 *
 * 3-24-93 See file COPYING for copyright information.
 *
 * Local client commands
 */

#include "4m.h"
#include "externs.h"
#include "../h/term.h"
#include "../common/ipcf.h"
#include "../common/externs.h"
#include "../common/select.h"

#include "ctcp.h"

#ifdef SYSTIME
# include <sys/time.h>
# else
# include <time.h>
#endif

#include <sys/wait.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>

#if !defined(NO_STDLIB_H)
#include <stdlib.h>
#endif

FILE *logfp = NULL;

static STRLIST *hushhead = NULL, *hushtail = NULL;
static STRLIST *bufhead = NULL, *buftail = NULL;
static int bufmessages = 0;

extern SIGTYPE do_exit ();
extern SIGTYPE askquit ();

extern int getsockname();
extern int shutdown();

extern int connecttoport();
extern void linebuffer();
extern int match();
extern int strcasecmp();
extern STRLIST *strmakenode();
extern STRLIST *strgetnode();
extern void strunlink();
extern char *tildexpand();
extern char *local_ampm();
extern char *nlget ();

extern char *getword();
extern char *get_tail();

void hushlist();
int startsessionlog();
void closesessionlog();
void xfer_delete();
void bufferlist();
void list_alias();
void xfer_list();


extern void errormsg ();
extern void putl ();
extern int chdir ();
/* extern char *getcwd (); */
extern void histclear ();
int ishushed ();
int hushadd ();
int hushdelete ();
extern void strlinkalpha ();
int logging ();
extern time_t time ();
extern int fclose ();
extern void strlinktail ();
extern void catargs ();
extern void unset_term ();
/* extern pid_t fork (); */
extern int execlp ();
extern void set_term ();
extern int write ();
extern int puts ();
extern int printf ();
extern int _filbuf ();
extern void title ();
extern void status ();
extern void sendpersonal ();
extern int close ();
extern unsigned int sleep ();
extern int nlcount ();
extern int nldelete ();
extern void nlput ();
int xfer_lookup ();
extern char *inet_ntoa (/* ??? */);
extern void *memset ();
extern char *strncpy ();
extern char *strcat ();
extern int c_sendopen ();
extern int pclose ();
extern void sendcmd ();

void c_clear(argc, argv)
     int argc;
     char **argv;
{
    register int i;

    if (!dumb) {
        savecursor();
        hidecursor();

        for (i = lines - 4; i > 0; i--) {
            gotoxy(0, i);
            clear_eol();
        }

        cpos = 1;
        scrolling = FALSE;
        gotoxy(0, lines - 2);

        restorecursor();
        showcursor();
    }

    return;
}

void c_eclear(argc, argv)
     int argc;
     char **argv;
{
    if (dumb)
        return;

    savecursor();
    hidecursor();

    gotoxy(0, lines - 4);
    clear_eol();

    restorecursor();
    showcursor();

    return;
}

void c_chdir(argc, argv)
     int argc;
     char **argv;
{
    static char *usage = "[Usage] /lcd <dir>";
    struct stat buf;
    extern char *sys_errlist[];
    extern int errno;

    if (gv.restricted) {
        errormsg("Command restricted.");
        return;
    }

    if (argc > 2) {
        putl(usage, PL_SCR);
        return;
    } else if (argc == 1) {
        c_pwd(argc, argv);
        return;
    }

    if (stat(argv[1], &buf) == -1) {
        sprintf(mbuf, "No such file or directory %s.", argv[1]);
        errormsg(mbuf);
        return;
    }

    if (!S_ISDIR(buf.st_mode)) {
        sprintf(mbuf, "%s is not a directory.", argv[1]);
        errormsg(mbuf);
        return;
    }

    if (chdir(argv[1])) {
        sprintf(mbuf, "Couldn't change directory: %s.", sys_errlist[errno]);
        errormsg(mbuf);
        return;
    } else {
        putl("[ChDir] Successfully changed directories.", PL_SCR);
        c_pwd(1, NULL);
    }

    return;
}

void c_pwd(argc, argv)
     int argc;
     char **argv;
{
    static char *usage = "[Usage] /pwd";
    char curdir[1024];
    char *wrkd = NULL;

    if (gv.restricted) {
        errormsg("Command restricted.");
        return;
    }

    if (argc > 1) {
        putl(usage, PL_SCR);
        return;
    }

#ifdef USE_GETWD
    wrkd = (char *) getwd(curdir);
#else
    wrkd = (char *) getcwd(curdir, 1024);
#endif

    if (!wrkd) {
        putl("Couldn't get current directory", PL_SCR);
        return;
    }

    sprintf(mbuf, "[Pwd] %s.", curdir);
    putl(mbuf, PL_SCR);

    return;
}

void c_hclear(argc, argv)
     int argc;
     char **argv;
{
    static char *usage = "[Usage] /TABclear";

    if (argc > 1) {
        putl(usage, PL_SCR);
        return;
    }

    histclear();

    return;
}

void c_hush(argc, argv)
     int argc;
     char **argv;
{

    if (argc == 1 || !*argv[1]) {
        hushlist();
    } else if (!ishushed(argv[1])) {
        if (hushadd(argv[1]) < 0)
            return;
    } else if (hushdelete(argv[1]) < 0)
        return;
    return;
}


int hushadd(nick)
     char *nick;
{
    STRLIST *hp;

 /* skip whitespace */
    while (*nick == ' ' || *nick == '\t')
        nick++;

    if (*nick == '\0') {
        errormsg("Can't hush a null nickname.");
        return (-1);
    }

    if (strlen(nick) > MAX_NICKLEN)
        *(nick + MAX_NICKLEN) = '\0';

    if ((hp = strmakenode(strlen(nick))) == NULL) {
        errormsg("Out of memory.");
        return (-1);
    }

    strcpy(hp->str, nick);
    strlinkalpha(hp, &hushhead, &hushtail, 1);
    sprintf(mbuf, "[Hush] %s added to hush list.", hp->str);
    putl(mbuf, PL_SL);

    return (0);
}

int hushdelete(nick)
     char *nick;
{
    STRLIST *s;
    char usagestr[100];

    if ((s = strgetnode(nick, hushhead, 1)) == NULL) {
        sprintf(usagestr, "%s is not being hushed.", nick);
        errormsg(usagestr);
        return (-1);
    }

    sprintf(mbuf, "[Hush] %s deleted from hush list.", nick);
    putl(mbuf, PL_SL);
    strunlink(s, &hushhead, &hushtail);
    free(s);

    return (0);
}

void hushlist()
{
    STRLIST *p;
    char *s, *t;

    if (!dumb)
        hidecursor();

    if (hushhead) {
        putl("[Hush] Users currently hushed:", PL_SCR);
    } else {
        putl("[Hush] You are not currently hushing anyone.", PL_SCR);
        if (!dumb)
            showcursor();
        return;
    }

    s = mbuf;

    for (p = hushhead; p; p = p->next) {
        if (s != mbuf)
            *s++ = ' ';
        for (t = p->str; *t != '\0'; t++)
            *s++ = *t;
        if (s - mbuf > 60) {
            *s = '\0';
            putl(mbuf, PL_SCR);
            s = mbuf;
        }
    }

    if (s != mbuf) {
        *s = '\0';
        putl(mbuf, PL_SCR);
    }

    if (!dumb)
        showcursor();

    return;
}

int ishushed(nick)
     char nick[15];
{
    STRLIST *p;

    for (p = hushhead; p; p = p->next) {
        if (!strcasecmp(p->str, nick) || match(p->str, nick)) {
            return (1);
        }
    }

    return (0);
}

void c_log(argc, argv)
     int argc;
     char **argv;
{

 /* disallow use in restricted mode */
    if (gv.restricted) {
        errormsg("Command restricted.");
        return;
    }

    if (argc == 1 || !argv[1]) {
        if (logging())
            closesessionlog();
        else if (startsessionlog(0) < 0)
            return;
    } else {
        if (logging()) {
            errormsg("Session logging already on.");
            return;
        } else if (startsessionlog(argv[1]) < 0)
            return;
    }

    return;
}

int startsessionlog(path)
     char *path;
{
    char *aptr, usagestr[100];
    struct tm *t;
    time_t clock;
    extern int errno, sys_nerr;
    extern char *sys_errlist[];

 /* determine pathname to use */
    if (path == NULL || *path == '\0') {
        if (gv.logfile) {
            path = gv.logfile;
        } else {
            path = "4m.log";
        }
    }

 /* expand a tilde style path */
    if (*path == '~') {
        if ((path = tildexpand(path)) == NULL) {
            errormsg("Bad login id in path.");
            return (-1);
        }
    }

 /* open the session log */
    if ((logfp = fopen(path, "a")) == NULL) {
        if (errno > sys_nerr) {
            sprintf(usagestr, "Can't open \"%s\": errno %d.", path, errno);
            errormsg(usagestr);
        } else {
            sprintf(usagestr,
                    "Can't open \"%s\": %s.", path, sys_errlist[errno]);
            errormsg(usagestr);
        }
        return (-1);
    }

 /* line buffer it */
    linebuffer(logfp);

 /* get the time so we can timestamp it */
    time(&clock);
    t = gmtime(&clock);
    aptr = local_ampm(time(NULL), 0);

    if (*aptr == ' ')
        aptr++;

    sprintf(mbuf, "[Log] Session log \"%s\" started at %d/%02d/%-2d %s.",
            path, t->tm_mon + 1, t->tm_mday, t->tm_year, aptr);
    putl(mbuf, PL_SCR | PL_LOG);

    return (0);
}

void closesessionlog()
{
    char *aptr;
    struct tm *t;
    time_t clock;

 /* do nothing if we're not really logging */
    if (logfp == NULL) {
        return;
    }

 /* get the time so we can timestamp it (deja vu?) */
    time(&clock);
    t = gmtime(&clock);
    aptr = local_ampm(time(NULL), 0);

    if (*aptr == ' ')
        aptr++;


 /* timestamp it */
    sprintf(mbuf, "[Log] Session log closed at %d/%02d/%-2d %s.",
            t->tm_mon + 1, t->tm_mday, t->tm_year, aptr);
    putl(mbuf, PL_SCR | PL_LOG);

 /* close it */
    fclose(logfp);
    logfp = NULL;

    return;
}


int logging()
{
    return (logfp == NULL) ? 0 : 1;
}

void c_quit(argc, argv)
     int argc;
     char **argv;
{
    static char *usage = "[Usage] /Quit";

    if (argc != 1)
        putl(usage, PL_SCR);

    if (gv.verifyquit) {
        askquit();
        return;
    }

    do_exit();

    return;
}

void c_replay(argc, argv)
     int argc;
     char **argv;
{
    int lines = 0;  /* to make AIX happy.... */
    int flag = FALSE;
    char *str = 0;

    if (argc == 1 || !*argv[1]) 
        lines = bufmessages;
    else 
    if (argc == 2)
     {
       if (numeric (argv[1]))
          lines = atoi(argv[1]);
       else {
          str = argv[1];
          lines = bufmessages;
          flag = TRUE;
       }
     }
    else
    if (argc > 2)
     {
       lines = atoi(argv[1]);
       str = argv[2];
       flag = TRUE;
     }                    

    if (lines == 0) {
        errormsg("Lines must be greater than zero.");
        return;
    }

    bufferlist(lines, str, flag);
    return;
}


/* add a line of text to the replay buffer */

void bufferadd(text)
     char *text;
{
    STRLIST *bp;
    int len;

    len = strlen(text);

 /* add this line to the buffer */
    if ((bp = strmakenode(len)) == NULL) {
        errormsg("Not enough memory for buffer.");
        return;
    }

    memcpy(bp->str, text, len + 1);
    strlinktail(bp, &bufhead, &buftail);

 /* delete entries as necessary to get buffer down to size */
    if (++bufmessages > gv.bufferlines) {
        while (bufmessages > gv.bufferlines) {
            bp = bufhead;
            strunlink(bp, &bufhead, &buftail);
            free(bp);
            bufmessages--;
        }
    }

    return;
}


/* list some or all of the replay buffer */

void bufferlist(lines, str, flag)
     int lines, flag;
     char *str;
{
    STRLIST *bp;

 /* user may want to interrupt this command */
    continued = 0;

    if (!bufmessages)
        putl("[There are no lines in the buffer]", PL_ALL);

    if (lines > bufmessages)
        lines = bufmessages;

    if (!flag) {
       sprintf(mbuf, "[Replaying last %d messages]", lines);
       putl(mbuf, PL_SCR);
    }  else 
       putl("[Replaying buffer]", PL_SCR);
   

 /* find the start point */
    for (bp = buftail; bp->prev && lines; bp = bp->prev, lines--);

 /* print the lines */
    for (; bp && !continued; bp = bp->next) {
        if (!flag)
           putl(bp->str, PL_SCR);
        else
        if (scanstr (bp->str, str))
           putl(bp->str, PL_SCR);
    }
    continued = 0;
    putl("[End of review buffer]", PL_SCR);

    return;
}

void c_shell(argc, argv)
     int argc;
     char **argv;
{
    static char *usage = "usage: /Shell [command]";
    int w, pid;
    int shellout = 0;
    int stat;
    char *shell;

 /* disallow use if restricted mode set */
    if (gv.restricted) {
        errormsg("Command restricted.");
        return;
    }

 /* is this a shellout or a command? */
    if (argc == 1 || !*argv[1])
        shellout++;

    if (gv.shell && strlen(gv.shell) > 0)
        shell = gv.shell;
    else if (getenv("SHELL") != NULL)
        shell = getenv("SHELL");
    else
        shell = "/bin/csh";

 /* announce what we are doing */
    if (shellout) {
        sprintf(mbuf, "[%s]\r\n", shell);
        putl(mbuf, PL_SCR);
    } else {
        catargs(&argv[1]);
        sprintf(mbuf2, "[%s]\r\n", mbuf);
        putl(mbuf2, PL_SCR);
    }

 /* restore the terminal for the duration */
    unset_term();

    if ((pid = fork()) == 0) {
        if (shellout)
            execlp(shell, shell, 0);
        else
            execlp(shell, shell, "-c", mbuf, 0);

    /* exec failed if we make it here */
        sprintf(mbuf, "[Error] Can't execute shell \"%s\".", shell);
        putl(mbuf, PL_SL);
        putl(usage, PL_SCR);
        exit(-1);
    }

 /* trap signals that might mess conference */
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);

 /* wait for the child(ren) to die */
    while ((w = wait(&stat)) != pid && w != -1) {
       /* empty */
    }

 /* restore the terminal */
    set_term();

 /* restore signal processing */
    signal(SIGINT, askquit);
    signal(SIGQUIT, SIG_DFL);

 /* clean up a little */
    if (shellout)
        write(1, "\r", 1);

    if (dumb)
        puts("[Returning to 4m]");
    else {
        printf("\n[Press any key to return to 4m]: ");
        getchar();
        page();
        title();
        status(0);
    }

    return;
}

void c_time(argc, argv)
     int argc;
     char **argv;
{
    static char *usage = "[Usage] /Time <nick>";

    if (argc > 2 && argc != 1) {
        putl(usage, PL_SCR);
        return;
    }

    if (argc == 2) {
        sprintf(mbuf, "%s \002%c", words[1], CTCP_TIME);
        sendpersonal(mbuf);
        return;
    }

    sprintf(mbuf, "[Time] The time is %s.", local_ampm(time(NULL), 0));
    putl(mbuf, PL_SL);

    return;
}


void c_server(argc, argv)
     int argc;
     char **argv;
{
    unsigned short port;

    if (argc != 2 && (argc != 3 || !strlen(argv[1]))) {
        putl("[Usage] /server <hostname> <port>", PL_SCR);
        return;
    }

    shutdown(port_fd, 2);
    close(port_fd);

    port = (unsigned short)((argc == 2) ? gv.default_port : atoi(argv[2]));

    sprintf(mbuf, "[Server] Connecting to %s on port %d.\r",
            argv[1], port);

    putl(mbuf, PL_ALL);

    sleep(2);

    if ((connecttoport(argv[1], port, SERVER) < 0)) {
        putl("[Server] Remote server not running.", PL_ALL);
        do_exit();
        /* NOTREACHED */
    }

    return;
}

void c_alias(argc, argv)
     int argc;
     char **argv;
{
    int i, x, y, cnt, found = FALSE;
    extern int numcom;
    char *cp, *alias;

    if (argc == 1 || !*argv[1]) {
        list_alias();
        return;
    }

    cnt = nlcount(cmdalias);

    for (y = 0; y < cnt; y++) {
        cp = nlget(cmdalias);
        alias = getword(cp);

        if (!strcasecmp(alias, words[1])) {
            found = TRUE;
            break;
        }
    }

    if (found) {
        nldelete(cmdalias, cp);
        sprintf(mbuf, "[Alias] \"%s\" deleted from alias list.", alias);
        putl(mbuf, PL_ALL);
    } else {
        for (i = 0; i < numcom; i++) {
            if (!strcasecmp(verbs[i].name, words[2])) {
                for (x = 0; x < numcom; x++) {
                    if (!strcasecmp(verbs[x].name, words[1])) {
                        putl("[Alias] Command looping is not allowed.", PL_ALL);
                        return;
                    }
                }
                nlput(cmdalias, args);
                sprintf(mbuf, "[Alias] \"%s\" added to alias list.", words[1]);
                putl(mbuf, PL_ALL);
                return;
            }
        }
        putl("[Alias] You may only alias valid commands.", PL_ALL);
    }

    return;
}

void list_alias()
{
    int i, count = 0;
    char *cp, *cmd, alias[15];

    count = nlcount(cmdalias);

    if (!count) {
        putl("[Alias] You have no aliases set.", PL_ALL);
        return;
    }

    if (!dumb)
        hidecursor();

    putl("[Alias] You have the following aliases set: ", PL_ALL);
    sprintf(mbuf, "%-10s%-10s   %s", "[Alias]", "[Command]", "[Args]");
    putl(mbuf, PL_ALL);

    for (i = 0; i < count; i++) {
        cp = nlget(cmdalias);
        strcpy(alias, getword(cp));
        cmd = getword(get_tail(cp));
        args = get_tail(get_tail(cp));
        sprintf(mbuf, "%-10s%-10s   %s", alias, cmd, args);
        putl(mbuf, PL_ALL);
    }

    if (!dumb)
        showcursor();

    return;
}

void c_history (argc, argv)
     int argc;
     char **argv;
{
     int i, n;
     char *s, tab [50][256];
    
     if (argc == 1) {

        putl ("[History] Command history:", PL_SCR);

        n = i = nlcount (cmd_history);

        for (/* yip */; i >= 1; i--)
        {
           s = nlget (cmd_history);
           strcpy (tab[i], s);
        }

        for (i=1; i <= n; i++)
        {
           sprintf (mbuf, "%3d: %s", i, tab [i]);
           putl (mbuf, PL_SCR);
        }
      }
      else {
        if (argc == 2 && !strcasecmp ("clear", argv[1])) {
           nlclear (cmd_history);
           putl ("[Status] Command history cleared.", PL_SCR);
        }
       else 
           putl ("[Usage] /HISTORY [clear]", PL_SCR);
      }
}

void c_histget (str)
      char *str;
{
      int i, n;
      char *s, tab [50][256];

      n = i = nlcount (cmd_history);

      for (/* yip */; i >= 1; i--)
        {
           s = nlget (cmd_history);
           strcpy (tab[i], s);
        }

      for (/* yip */; n >= 0; n--)
        {
           if (!str) {
              fill_inputline (tab[n]);
              return;
           }
           if (numeric(str)) {
              if (n == atoi(str)) {
                 fill_inputline (tab[n]);
                 return;
              }
           }
           if (scanstr (tab[n], str)) 
           {
              fill_inputline (tab[n]);
              return;
           }
        }
       sprintf (mbuf, "[Error] %s: Event not found", str);
       putl (mbuf, PL_SCR);
}

void c_send(argc, argv)
     int argc;
     char **argv;
{
    FILE *fp;
    int size, sla, s, xindex;
    char *path;
    unsigned short port;
    struct stat stb;
    struct sockaddr_in saddr;

    if (gv.restricted) {
        errormsg("Command restricted.");
        return;
    }

    if ((argc > 4 || argc < 3) && argc != 1) {
        putl("[Usage] /Send <filename> <nick>", PL_ALL);
        return;
    }

    if (argc == 1) {
        xfer_list();
        return;
    }

    if (!strcmp(argv[1], "delete")) {
        if (argc != 4) {
            sprintf(mbuf, "[Usage] /%s delete <filename> <nickname>", argv[0]);
            putl(mbuf, PL_ALL);
            return;
        }

        xindex = xfer_lookup(argv[3], argv[2], GET);

        if (xindex == -1) {
            sprintf(mbuf, "Request for \"%s\" to/from %s doesn't exist.",
                    argv[2], argv[3]);
            errormsg(mbuf);
            return;
        }

        xfer_delete(xindex);
        return;
    }

    if (argv[1][0] == '~') {
        if ((path = tildexpand(argv[1])) == NULL) {
            putl("[Send] Unknown user id in filename.", PL_ALL);
            return;
        }
    } else {
        path = argv[1];
    }

    if (stat(path, &stb) == -1) {
        sprintf(mbuf, "[Send] %s does not exist.", path);
        putl(mbuf, PL_ALL);
        return;
    }

    if ((fp = fopen(path, "r")) == NULL) {
        sprintf(mbuf, "[Send] Can't open %s for reading.", path);
        putl(mbuf, PL_ALL);
        return;
    }

    size = stb.st_size;
    xindex = xfer_lookup(argv[2], path, SEND);

    if (xindex == -1) {
        sprintf(mbuf, "[Send] There is an identical send request for %s.", argv[2]);
        putl(mbuf, PL_ALL);
        return;
    }

    xfer_tab[xindex].size = size;
    strcpy(xfer_tab[xindex].filename, path);
    strcpy(xfer_tab[xindex].who, argv[2]);

    s = connecttoport(localhost, 0, SEND);

    if (s == -1) {
        errormsg("Error in binding port for transfer.");
        return;
    }

    sla = sizeof(struct sockaddr_in);
    getsockname(s, (struct sockaddr *) & saddr, &sla);
    port = saddr.sin_port;
    sprintf(mbuf, "%s \002%c %s %d %lu %u", argv[2], CTCP_XFER, path, size,
            (unsigned long) local_ip, (unsigned short) port);

    xfer_tab[xindex].s = s;
    xfer_tab[xindex].ip = (u_long) local_ip;
    xfer_tab[xindex].port = (u_short) port;
    xfer_tab[xindex].type = SEND;
    xfer_tab[xindex].status = WAIT;

    sendpersonal(mbuf);

    sprintf(mbuf, "[Send] %s offered to %s (%d bytes).", path, argv[2], size);
    putl(mbuf, PL_ALL);

    return;
}

void c_get(argc, argv)
     int argc;
     char **argv;
{
    int xindex;
    char *path;
    struct in_addr saddr;

    if (gv.restricted) {
        errormsg("Command restricted.");
        return;
    }

    if ((argc > 4 || argc < 3) && argc != 1) {
        putl("[Usage] /get <filename> <nickname> <new filename>", PL_ALL);
        return;
    }

    if (argc == 1) {
        xfer_list();
        return;
    }

    if (!strcmp(argv[1], "delete")) {
        if (argc != 4) {
            sprintf(mbuf, "[Usage] /%s delete <filename> <nickname>", argv[0]);
            putl(mbuf, PL_ALL);
            return;
        }

        xindex = xfer_lookup(argv[3], argv[2], GET);

        if (xindex == -1) {
            sprintf(mbuf, "Request for \"%s\" to/from %s doesn't exist.",
                    argv[2], argv[3]);
            errormsg(mbuf);
            return;
        }

        xfer_delete(xindex);
        return;
    }

    xindex = xfer_lookup(argv[2], argv[1], GET);

    if (xindex == -1) {
        sprintf(mbuf, "[Get] \"%s\" is not being offered to you by %s.",
                argv[1], argv[2]);
        putl(mbuf, PL_ALL);
        return;
    }

    path = argv[1];

    if (argc == 4) {
        if (argv[3][0] == '~') {
            if ((path = tildexpand(argv[3])) == NULL) {
                putl("[Get] Unknown user id in filename.", PL_ALL);
                return;
            }
        } else {
            path = argv[3];
        }
    }

    strcpy(xfer_tab[xindex].filename, path);

    if (!xfer_tab[xindex].size) {
        putl("[Get] There is nothing for you to receive.", PL_ALL);
        return;
    }

    saddr.s_addr = xfer_tab[xindex].ip;

    if (connecttoport(inet_ntoa(saddr), xfer_tab[xindex].port, GET) == -1) {
        sprintf(mbuf, "[Get] Error in connecting to %s (port %d).",
                inet_ntoa(saddr), xfer_tab[xindex].port);
        putl(mbuf, PL_ALL);
        xfer_delete(xindex);
        return;
    }

    sprintf(mbuf, "[Xfer] Data transfer connection established to %s.",
            xfer_tab[xindex].who);
    putl(mbuf, PL_ALL);

    return;
}

int xfer_lookup(who, path, type)
     char *who;
     char *path;
     int type;
{
    int i;

    for (i = 0; i < MAX_XFERS; i++) {
        if (!xfer_tab[i].size && type == SEND)
            return (i);

        if (!xfer_tab[i].size && type == GET)
            continue;

        if (!strcasecmp(who, xfer_tab[i].who) &&
            !strcmp(path, xfer_tab[i].filename) && type == SEND)
            return(-1);

        if (!strcasecmp(who, xfer_tab[i].who) &&
            !strcmp(path, xfer_tab[i].filename) && type == GET)
            return (i);
    }
    return(-1);
}

void xfer_erase(i)
     int i;
{
    xfer_tab[i].size = 0;
    xfer_tab[i].s = -1;
    xfer_tab[i].fd = -1;
    xfer_tab[i].bytes = 0L;

    memset(xfer_tab[i].who, 0, strlen(xfer_tab[i].who) + 1);
    memset(xfer_tab[i].filename, 0, strlen(xfer_tab[i].filename) + 1);

    return;
}

void xfer_list()
{
    int i, flag = 0;
    char *status, *type, filename[25];

    for (i = 0; i < MAX_XFERS; i++) {
        if (strlen(xfer_tab[i].filename) > 0) {
            flag++;
        }
    }

    if (!flag) {
        putl("[Xfer] You currently have no transfer requests.", PL_ALL);
        return;
    }

    if (!dumb)
        hidecursor();

    sprintf(mbuf, "\n%-5s %-9s %-12s %-20s %-10s %-10s", "Type", "Status",
            "Who", "Filename", "Size", "Xfered");
    putl(mbuf, PL_ALL);

    for (i = 0; i < MAX_XFERS; i++) {
        if (xfer_tab[i].size > 0 && strlen(xfer_tab[i].filename) > 0) {
            type = (xfer_tab[i].type == SEND) ? "Send" : "Get";
            status = (xfer_tab[i].status == WAIT) ? "Waiting" : "Active";

            strcpy(filename, xfer_tab[i].filename);

            if (strlen(xfer_tab[i].filename) > 15) {
                memset(filename, 0, sizeof(filename));
                strncpy(filename, xfer_tab[i].filename, 10);

                if (strlen(xfer_tab[i].filename) > 10)
                    filename[10] = 0;

                strcat(filename, "...");
            }

            sprintf(mbuf, "%-5s %-9s %-12s %-20s %-10d %-10lu", type, status,
                    xfer_tab[i].who, filename, xfer_tab[i].size,
                    xfer_tab[i].bytes);
            putl(mbuf, PL_ALL);
        }
    }

    if (!dumb)
        showcursor();

    return;
}


void xfer_delete(xindex)
     int xindex;
{
    if (xfer_tab[xindex].status == ACTIVE) {
        FD_CLR(xfer_tab[xindex].s, &fdset);
        close(xfer_tab[xindex].s);
        close(xfer_tab[xindex].fd);
    }

    if (xfer_tab[xindex].type == SEND) {
        sprintf(mbuf, "[Xfer] \"%s\" offered to %s has been removed.",
                xfer_tab[xindex].filename, xfer_tab[xindex].who);
    } else {
        sprintf(mbuf, "[Xfer] \"%s\" offered by %s has been removed.",
                xfer_tab[xindex].filename, xfer_tab[xindex].who);
    }

    putl(mbuf, PL_ALL);

    sprintf(mbuf, "%s \002SEND DELETE %s", xfer_tab[xindex].who,
            xfer_tab[xindex].filename);
    sendpersonal(mbuf);

    xfer_erase(xindex);

    return;
}


void c_pipe(argc, argv)
     int argc;
     char **argv;
{
    static char *usage = "[Usage] /Pipe <@group | -nick> [command]";
    char line[512];
    char out[1024];
    int n;
    FILE *in;
    char *dest = NULL;

 /* disallow use if restricted mode set */
    if (gv.restricted) {
        errormsg("Command restricted.");
        return;
    }

    if (argc < 2) {
        putl(usage, PL_SCR);
        return;
    }

    if (*argv[1] == '-' || *argv[1] == '@') {
        dest = argv[1];
    }

    if (dest && *dest == '-')
        dest++;

    if (dest != NULL)
        catargs(&argv[2]);
    else
        catargs(&argv[1]);

    sprintf(line, "[%s]", mbuf);
    putl(line, PL_SCR);

    if ((in = popen(mbuf, "r")) == NULL) {
        sprintf(mbuf, "Command '%s' failed", mbuf);
        putl(mbuf, PL_SCR);
        return;
    }

    while (fgets(line, 512, in) != NULL) { n = strlen(line);
        line[n - 1] = 0;
        if (dest != NULL) {
            sprintf(out, "%s %s", dest, line);
            sendpersonal(out);
        } else {
            c_sendopen(line);
        }
    }

    pclose(in);

    return;
}

void c_finger(argc, argv)
     int argc;
     char **argv;
{
    static char *usage = "[Usage] /Finger <nick>";

    if (argc != 2) {
        putl(usage, PL_SCR);
        return;
    }

    sprintf(mbuf, "%s \002%c", words[1], CTCP_FINGER);
    sendpersonal(mbuf);

    return;
}

void c_version(argc, argv)
     int argc;
     char **argv;
{

    static char *usage = "[Usage] /Version <nick>";

    if (argc > 2 && argc != 1) {
        putl(usage, PL_SCR);
        return;
    }

    if (argc == 2) {
        sprintf(mbuf, "%s \002%c", words[1], CTCP_VERSION);
        sendpersonal(mbuf);
        return;
    }

    sprintf(mbuf, "[Version] Client version %s.", VERSION);
    putl(mbuf, PL_ALL);

    sendcmd("version", "");

    return;
}

int 
find_slot()
{
    register int i;
    int slot = -1;

    for (i = 0; i < MAX_XFERS; i++) {
        if (!secure_tab[i].active) {
            slot = i;
            break;
        }
    }

    return (slot);
}

int 
find_chatter(chatter)
     char *chatter;
{
    register int i;
    int slot = -1;

    for (i = 0; i < MAX_XFERS; i++) {
        if (!strcasecmp(secure_tab[i].who, chatter)) {
            slot = i;
            break;
        }
    }

    return (slot);
}

void c_chat(argc, argv)
     int argc;
     char **argv;
{
    static char *usage = "[Usage] /chat <[+|-]nick> [key]";
    register int i;
    unsigned short port;
    struct sockaddr_in saddr;
    struct in_addr addr;
    int slot = -1, do_title = 0, sla, s;

    enum {
        ADD_SECURE, DEL_SECURE
    }   add_del;

    char *chatter;
    char buf[100], key[65];

    if (argc != 1 && argc > 3) {
        putl(usage, PL_SCR);
        return;
    }

    if (argc == 1) {
        for (i = 0; i < MAX_XFERS; i++) {
            if (secure_tab[i].active) {
                do_title = 1;
                break;
            }
        }

        if (do_title) {
            putl("Who             Since     Port   Status   Packets", PL_SCR);
        } else {
            putl("[Chat] No active secure links.", PL_SCR);
            return;
        }

        for (i = 0; i < MAX_XFERS; i++) {
            if (secure_tab[i].active) {
                sprintf(buf, "%-16s%-5s   %4d   %-8s %s", 
                            secure_tab[i].who,
                            local_ampm(secure_tab[i].start, 0), 
                            secure_tab[i].port,
                            secure_tab[i].status == WAIT ? "Pending" : "Active",
                            secure_tab[i].crypt ? "Encrypted" : "Plain text");
                putl(buf, PL_SCR);
            }
        }

        return;
    }

    if (argc == 3) {
        if (strlen(argv[2]) < 3) {
            putl("[Chat] Encryption key must be greater than 3 characters.",
                 PL_SCR);
            return;
        }

        if (strlen(argv[2]) > 65) {
            putl("[Chat] Encryption key must be less than 65 characters.",
                 PL_SCR);
            return;
        }

        strcpy(key, argv[2]);
    }

    if (argc == 2 || argc == 3) {
        if (*argv[1] != '+' && *argv[1] != '-') {
            putl(usage, PL_SCR);
            return;
        }

        add_del = (*argv[1] == '+') ? ADD_SECURE : DEL_SECURE;

        chatter = ++argv[1];

        if (add_del == ADD_SECURE) {
        /* initiate chat with so and so */
            if ((slot = find_chatter(chatter)) != -1) {
                if (secure_tab[slot].status == WAIT) {
                    addr.s_addr = secure_tab[slot].ip;
                    if (connecttoport(inet_ntoa(addr), secure_tab[slot].port,
                                      3) == -1) {
                        sprintf(mbuf, "[Chat] Error in connecting to %s (port %d).",
                                inet_ntoa(saddr), secure_tab[slot].port);
                        putl(mbuf, PL_ALL);
                        secure_tab[slot].active = 0;
                        return;
                    }
                    sprintf(mbuf, "[Chat] Secure link established with %s.",
                            secure_tab[slot].who);
                    putl(mbuf, PL_ALL);
                    if (argc == 3) {
                        memset(secure_tab[slot].key, 0,
                               sizeof(secure_tab[slot].key));
                        strncpy(secure_tab[slot].key, key,
                                sizeof(secure_tab[slot].key) - 1);
                        secure_tab[slot].crypt = 1;
                    }
                    secure_tab[slot].status = ACTIVE;
                    return;
                }
                sprintf(buf, "[Chat] Secure link already enabled with %s.",
                        chatter);
                putl(buf, PL_SCR);
                return;
            }
            if ((slot = find_slot()) == -1) {
            /* too many chats */
                putl("[Chat] Too many secure links; Disconnect one and try again.",
                     PL_SCR);
                return;
            }
            s = connecttoport(localhost, 0, SEND);
            if (s == -1) {
                errormsg("Error in binding port for transfer.");
                return;
            }
            sla = sizeof(struct sockaddr_in);
            getsockname(s, (struct sockaddr *) & saddr, &sla);
            port = saddr.sin_port;

        /* add the chatter and fill the slot */
            secure_tab[slot].s = s;
            secure_tab[slot].start = time(NULL);
            secure_tab[slot].ip = (u_long) local_ip;
            secure_tab[slot].port = (u_short) port;
            secure_tab[slot].status = WAIT;
            secure_tab[slot].active = 1;
            secure_tab[slot].initiator = 1;
            memset(secure_tab[slot].who, 0, sizeof(secure_tab[slot].who));
            strncpy(secure_tab[slot].who, chatter, sizeof(secure_tab[slot].who) - 1);
            if (argc == 3) {
                memset(secure_tab[slot].key, 0, sizeof(secure_tab[slot].key));
                strncpy(secure_tab[slot].key, key, sizeof(secure_tab[slot].key) - 1);
                secure_tab[slot].crypt = 1;
            }
            sprintf(mbuf, "%s \002%c ADD_SECURE %lu %u",
               chatter, CTCP_CHATIN, (unsigned long) secure_tab[slot].ip,
                    (unsigned short) secure_tab[slot].port);

            sendpersonal(mbuf);

            sprintf(buf, "[Chat] Secure link requested with %s.", chatter);
            putl(buf, PL_SCR);

        } else {
        /* clear chat with so and so */
            if ((slot = find_chatter(chatter)) == -1) {
                sprintf(buf, "[Chat] No secure link exists with %s.", chatter);
                putl(buf, PL_SCR);
                return;
            }
        /* clear the chatter and fill the slot */
            secure_tab[slot].active = 0;
            secure_tab[slot].initiator = 0;
            secure_tab[slot].crypt = 0;
            memset(secure_tab[slot].who, 0, sizeof(secure_tab[slot].who));

            sprintf(mbuf, "%s \002%c DEL_SECURE", chatter, CTCP_CHATIN);

            sendpersonal(mbuf);

            sprintf(buf, "[Chat] Cleared connection to %s.", chatter);
            putl(buf, PL_SCR);
        }
    }
    return;
}
