/* convers server - based on conversd written by DK5SG
 * ported to WNOS by DB3FL - 9109xx/9110xx
 * ported to NOS by PE1NMB - 920120
 * Mods by PA0GRI
 * Cleanup, and additional mods by WG7J
 * Additions by N2RJT & WA2ZKD
 */

#include <time.h>
#include <ctype.h>
#include <alloc.h>
#ifdef  UNIX
#include <sys/types.h>
#include <sys/stat.h>
#endif
#ifdef MSDOS
#include <io.h>
#endif
#include "global.h"
#ifdef CONVERS
#include "mailbox.h"
#include "netuser.h"
#include "pktdrvr.h"
#include "timer.h"
#include "cmdparse.h"
#include "usock.h"
#include "socket.h"
#include "session.h"
#include "files.h"
#include "mailutil.h"

#define LINK    1
#define space


char Chostname[CNAMELEN+1];
void conv_incom __ARGS((int s,void *t,void *p));
void xconv_incom __ARGS((int s,void *t,void *p));
void conv_incom2 __ARGS((int s,void *t,void *p,struct convection *cp));
int ConvUsers;
int ConvHosts;

#ifdef space
static char cnumber[] = "*** Channel numbers must be in the range 0..%d.\n";
#ifdef LOCAL_CHANNELS
static char clocal[]  = "*** This is a local channel - no chatting with other nodes\n";
static char cmessage[]= "*** This is a message channel - only /msg allowed or received\n";
#endif
#else
static char cnumber[] = "* range 0..%d.\n";
#ifdef LOCAL_CHANNELS
static char clocal[]  = "* LOCAL CHANNEL\n";
static char cmessage[]= "* MESSAGE CHANNEL\n";
#endif
#endif

#define MAXCHANNEL  32767
#define LINELEN     256
/* The convers daemon stack needs to be AT LEAST 4 times the LINELEN !
 * since quiet a few of the commands have local vars of size 2 or 3 * LINELEN.
 */
#define CDAEMONSTACK 2048
#define CLINKSTACK 1024
#define MAX_WAITTIME    (60*60*3)
#define NAMELEN 10

struct convection {
    int  type;          /* Connection type */
#define CT_UNKNOWN      0
#define CT_USER         1
#define CT_HOST         2
#define CT_CLOSED       3
    char  name[NAMELEN+1];         /* Name of user or host */
    char  host[NAMELEN+1];         /* Name of host where user is logged on */
    struct convection *via;     /* Pointer to neighbor host */
    char *data;                 /* room for some personal data */
    int channel;            /* Channel number */
    int32 time;         /* Connect time */
    int maxq;           /* Maximum outstanding data before link reset */
    int locked;         /* Set if mesg already sent */
    int fd;             /* Socket descriptor */
    int flags;          /* User flags */
#define CLOSE_SOCK  1
#define USE_SOUND   2
#define USE_LZW     4
    /* This buffer is only needed for local users; a lot of space is wasted
     * for users from other hosts (256 bytes per user!). Fixed 930728 - WG7J
     * char ibuf[LINELEN];
     */
    char *ibuf;                      /* Input buffer */
    unsigned long received;          /* Number of bytes received */
    unsigned long xmitted;           /* Number of bytes transmitted */
    struct convection *next;    /* Linked list pointer */
};

#define CM_UNKNOWN  (1 << CT_UNKNOWN)
#define CM_USER     (1 << CT_USER)
#define CM_HOST     (1 << CT_HOST)
#define CM_CLOSED   (1 << CT_CLOSED)

#define NULLCONNECTION  ((struct convection *) 0)

static struct convection *convections;

/* N2RJT - added channel names */
#ifdef CHANNELNAMES

struct channelname {
    int channel;    /* channel number */
    char *name;     /* channel name   */
    struct channelname *next;   /* linked list pointer */
};

#define NULLCHANNELNAME ((struct channelname *) 0)

static struct channelname *channelnames = NULLCHANNELNAME;

#ifdef LOCAL_CHANNELS
#define ISLOCAL(chan) (!strncmpi(channame_of(chan),"loc",3))
#define ISMESSAGE(chan) (!strncmpi(channame_of(chan),"msg",3))
#endif
#define PERSONAL_LEN 24
#else
#define PERSONAL_LEN 31

#ifdef LOCAL_CHANNELS
#define ISLOCAL(chan) (chan>=30000 && chan < 32000)
#define ISMESSAGE(chan) (chan >= 32000)
#endif
#endif /* CHANNELNAMES */

#ifndef LOCAL_CHANNELS
#define ISLOCAL(chan) (0)
#define ISMESSAGE(chan) (0)
#endif

struct permlink {
    char name[NAMELEN+1];   /* Name of link */
    int32 addr;             /* address to link to */
#ifdef XCONVERS
    int use_lzw;
#endif
    struct convection *convection;  /* Pointer to associated connection */
    int32 statetime;        /* Time of last (dis)connect */
    int  tries;         /* Number of connect tries */
    int32 waittime;         /* Time between connect tries */
    int32 retrytime;        /* Time of next connect try */
    int fd;             /* socket descriptor */
    struct permlink *next;      /* Linked list pointer */
};
#define NULLPERMLINK ((struct permlink *) 0)

struct filter_link {
    struct filter_link *next;
    int32 addr;
};
#define NULLFL ((struct filter_link *) 0)

#ifdef LINK
struct proc *Linker;
static void connect_permlinks __ARGS((int a,void *b,void *c));
static void update_permlinks __ARGS((char *name,struct convection *cp));
#endif

static void free_connection __ARGS((register struct convection *cp));
static char *formatline __ARGS((char  *prefix,char *text));
static char  *timestring __ARGS((long  gmt));
static void clear_locks __ARGS((void));
static void send_msg_to_user __ARGS((char *fromname,char *toname,char *text));
static void send_user_change_msg __ARGS((char *name,char *host,int oldchannel,int newchannel));
static void send_link_change_msg __ARGS((struct convection *cp,char *name,char *change));
static void send_invite_msg __ARGS((char *fromname,char *toname,int channel));
static void personal_command __ARGS((struct convection *cp));
static void bye_command __ARGS((struct convection *cp));
static void send_msg_to_channel __ARGS((char *fromname,int channel,char *text));
static void free_closed_connections __ARGS((void));
static struct convection *alloc_connection __ARGS((int fd));
static void process_commands __ARGS((struct convection *cp,struct mbx *m));
static void set_personal __ARGS((struct convection *cp));
static void personal_data __ARGS((struct convection *cp));
static void store_personal __ARGS((struct convection *cp));
static void version_command __ARGS((struct convection *cp));
static void help_command __ARGS((struct convection *cp));
static int channum_of __ARGS((char *name));
static char *channame_of __ARGS((int num));
static void read_channels __ARGS((void));
static void show_channels __ARGS((struct convection *cp));
static char *pvia __ARGS((void *p));

static struct filter_link *Filterlinks;
static int FilterMode;
static struct permlink *permlinks;
extern char Ccall[AXALEN];
static int allow_info_updates;

static int docinfo __ARGS((int argc,char *argv[], void *p));
static int docfilter __ARGS((int argc,char *argv[],void *p));
static int dochostname __ARGS((int argc,char *argv[],void *p));
static int dociface __ARGS((int argc,char *argv[],void *p));
static int doconfcall __ARGS((int argc,char *argv[],void *p));
static int doclink __ARGS((int argc,char *argv[],void *p));
static int docxlink __ARGS((int argc,char *argv[],void *p));
static int doclinks __ARGS((int argc,char *argv[],void *p,int lzw));
static int docunlink __ARGS((int argc,char *argv[],void *p));
static int doct4 __ARGS((int argc,char *argv[],void *p));
static int docmaxwait __ARGS((int argc,char *argv[],void *p));
static int dohmaxq __ARGS((int argc,char *argv[],void *p));
static int doumaxq __ARGS((int argc,char *argv[],void *p));
static int dotdisc __ARGS((int argc,char *argv[],void *p));
static int docdefaultchannel __ARGS((int argc,char *argv[],void *p));

static struct cmds DFAR Ccmds[] = {
    "channel",  docdefaultchannel, 0, 0, NULLCHAR,
    "filter",   docfilter,  0, 0, NULLCHAR,
    "hmaxq",    dohmaxq,    0, 0, NULLCHAR,
    "hostname", dochostname,0, 0, NULLCHAR,
    "interface",dociface,   0, 0, NULLCHAR,
    "maxwait",  docmaxwait, 0, 0, NULLCHAR,
#ifdef AX25
    "mycall",   doconfcall, 0, 0, NULLCHAR,
#endif
#ifdef LINK
    "link",     doclink,    0, 0, NULLCHAR,
#endif
#ifdef CHANGE_PERSONAL
    "setinfo",  docinfo,    0, 0, NULLCHAR,
#endif
#ifdef AX25
    "t4",       doct4,      0, 0, NULLCHAR,
#endif
    "tdisc",    dotdisc,    0, 0, NULLCHAR,
    "umaxq",    doumaxq,    0, 0, NULLCHAR,
#ifdef LINK
    "drop",   docunlink,  0, 0, NULLCHAR,
#ifdef XCONVERS
    "xlink",    docxlink,   0, 0, NULLCHAR,
#endif
#endif
    NULLCHAR,
};

/* N2RJT - usputs doesn't return length. */
static int
usputscnt(int s, char *x)
{
    if (usputs(s,x)!=EOF)
        return strlen(x);
    else
        return -1;
}

#ifdef CHANNELNAMES
static int
channum_of(char *name)
{
    struct channelname *tick;
    tick = channelnames;
    while (tick != NULLCHANNELNAME) {
        if (tick->name != NULLCHAR)
            if (!stricmp(name,tick->name))
                return tick->channel;
        tick = tick->next;
    }
    return -1;
}

static char *
channame_of(int num)
{
    struct channelname *tick;
    tick = channelnames;
    while (tick != NULLCHANNELNAME) {
        if (num==tick->channel)
            return tick->name;
        tick = tick->next;
    }
    return NULLCHAR;
}

static void
read_channels()
{
    FILE *f;
    char line[256], *s2;
    struct channelname *temp, *list;
    if (channelnames != NULLCHANNELNAME)
        return;

    channelnames = NULLCHANNELNAME;
    list = NULLCHANNELNAME;
    if ((f=fopen(Channelfile,READ_TEXT))==NULLFILE)
        return;

    while (fgets(line,sizeof(line),f)!=NULL) {
        rip(line);
        temp = (struct channelname *) malloc (sizeof(struct channelname));
        temp->channel = atoi(line);
        temp->name = NULLCHAR;
        if ((s2 = strchr(line,' '))!=0) {
            s2++;
            temp->name = strdup(s2);
        }
        temp->next = NULLCHANNELNAME;
        if (list)
            list->next = temp;
        else
            channelnames = temp;
        list = temp;
    }
    fclose(f);
}

static void
show_channels(struct convection *cp)
{
    struct channelname *temp;
    if (channelnames != NULLCHANNELNAME) {
        cp->xmitted += usprintf(cp->fd, "Channel names:\n");
    }
    temp = channelnames;
    while (temp != NULLCHANNELNAME) {
        cp->xmitted += usprintf(cp->fd, "%5d %-8s %s\n",
            temp->channel, temp->name, ISLOCAL(temp->channel) ? "(local)" :
            ISMESSAGE(temp->channel) ? "(message)" : "");
        temp = temp->next;
    }
    cp->xmitted += usputscnt(cp->fd, "***\n");
}
#endif /* CHANNELNAMES */

/* Multiplexer for top-level convers command */
int
doconvers(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return subcmd(Ccmds,argc,argv,p);
}

#ifdef AX25
/* Display or change our AX.25 conference call */
static int
doconfcall(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char tmp[AXBUF];

    if(argc < 2){
        tprintf("%s\n",pax25(tmp,Ccall));
        return 0;
    }
    if(setcall(Ccall,argv[1]) == -1)
        return -1;
    return 0;
}

int32 CT4init = 7200;   /* 2 hours default */

/* Set link redundancy timer */
static int
doct4(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setlong(&CT4init,"Conf. redundancy timer (sec)",argc,argv);
}
#endif /* AX25 */

int32 CMaxwait = MAX_WAITTIME;

/* Set maxwait time for timed out links */
static int
docmaxwait(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setlong(&CMaxwait,"Re-link max wait (sec)",argc,argv);
}

int HMaxQ = 5*1024;

/* Set max qlimit for host links */
static int
dohmaxq(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setint(&HMaxQ,"Max. Host Queue (bytes)",argc,argv);
}

int UMaxQ = 1024;

/* Set max qlimit for user links */
static int
doumaxq(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setint(&UMaxQ,"Max. User Queue (bytes)",argc,argv);
}

int CDefaultChannel;

/* Set the initial channel */
static int
docdefaultchannel(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setint(&CDefaultChannel,"Default channel",argc,argv);
}

int32 Ctdiscinit = 0;

/* Set convers redundancy timer */
static int
dotdisc(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setlong(&Ctdiscinit,"redundancy timer (sec)",argc,argv);
}

static int
dochostname(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    if(argc == 1)
        tprintf("%s\n",Chostname);
    else {
        strncpy(Chostname,argv[1],NAMELEN);
        Chostname[CNAMELEN] = '\0';
    }
    return 0;
}

#ifdef CHANGE_PERSONAL
static int
docinfo(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setbool(&allow_info_updates,"Allow users to change info file",argc,argv);
}
#endif

static int
docfilter(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    int32 addr;
    struct filter_link *fl;

    if(argc == 1) {
        if(Filterlinks) {
            tprintf("Mode is: %s\n", FilterMode ? "Accept" : "Refuse");
            for(fl=Filterlinks;fl;fl=fl->next)
                tprintf("%s\n",inet_ntoa(fl->addr));
        }
        return 0;
    }
    if(!stricmp(argv[1],"mode")) {
        if(argc == 2)
            tprintf("Mode is: %s\n", FilterMode ? "Accept" : "Refuse");
        else {
            if(*argv[2] == 'a' || *argv[2] == 'A')
                FilterMode = 1;
            else
                FilterMode = 0;
        }
        return 0;
    }
    if((addr = resolve(argv[1])) == 0) {
        tprintf(Badhost,argv[1]);
        return 1;
    }
    /* check to see if we already have this in the list */
    for(fl=Filterlinks;fl;fl=fl->next)
        if(fl->addr == addr)
            return 0;       /* already have this one ! */

    /* Seems like a new one */
    fl = (struct filter_link *)callocw(1,sizeof(struct filter_link));
    fl->addr = addr;
    fl->next = Filterlinks;
    Filterlinks = fl;
    return 0;
}

#ifdef LINK
#ifdef XCONVERS
static int
doclinks(argc,argv,p, use_lzw)
#else
static int
doclink(argc,argv,p)
#endif
int argc;
char *argv[];
void *p;
#ifdef XCONVERS
int use_lzw;
#endif
{
    int32 addr;
    struct permlink *pl;

    if(argc == 1) {
        for(pl=permlinks;pl;pl=pl->next)
#ifdef XCONVERS
            tprintf("%10s: %s %s\n",pl->name,inet_ntoa(pl->addr), pl->use_lzw ?
              "XCONVERS" : "CONVERS");
#else
            tprintf("%10s: %s\n",pl->name,inet_ntoa(pl->addr));
#endif
        return 0;
    }
    if((addr = resolve(argv[1])) == 0) {
        tprintf(Badhost,argv[1]);
        return 1;
    }
    /* check to see if we already have a link to such animal,
     * this happens when we stop and restart the server - WG7J
     */
    for(pl=permlinks;pl;pl=pl->next)
        if(pl->addr == addr)
            return 1;       /* already have this one ! */

    /* Seems like a new link ! Go add it */
    pl = (struct permlink *)callocw(1,sizeof(struct permlink ));
    pl->addr = addr;
#ifdef XCONVERS
    pl->use_lzw = use_lzw;
#endif
    pl->next = permlinks;
    permlinks = pl;
    if(argc > 2) {
        strncpy(pl->name,argv[2],NAMELEN);
        update_permlinks(pl->name,NULLCONNECTION);
    } else
        strcpy(pl->name,"Unknown");
    if(!Linker)
        Linker = newproc("Clinker",CLINKSTACK,connect_permlinks,0,0,NULL,0);

    return 0;
}

#ifdef XCONVERS
static int
doclink(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return doclinks(argc,argv,p,0);
}

static int
docxlink(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return doclinks(argc,argv,p,1);
}
#endif

static int
docunlink(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    int fd;
    int32 addr;
    struct permlink *pl, *pls;

    if(argc == 1) {
        pl=permlinks;
        permlinks=NULLPERMLINK;
        while(pl) {
            tprintf("Unlinking %s\n",inet_ntoa(pl->addr));
#ifdef LINK_CHANGE_MSG
            send_link_change_msg(pl->convection,pl->name,"unlinked by sysop");
#endif
            shutdown(pl->fd,2);
            close_s(pl->fd);
            pls=pl->next;
            free(pl);
            pl=pls;
        }
        return 0;
    }
    if((addr = resolve(argv[1])) == 0) {
        tprintf(Badhost,argv[1]);
        return 1;
    }

    pls=NULLPERMLINK;
    for(pl=permlinks;pl;pls=pl,pl=pl->next)
        if(pl->addr == addr) {
            if (pls)
                pls->next = pl->next;
            else
                permlinks = pl->next;
#ifdef LINK_CHANGE_MSG
            send_link_change_msg(pl->convection,pl->name,"unlinked by sysop");
#endif
            tprintf("Unlinking %s\n",inet_ntoa(pl->addr));
            shutdown(pl->fd,2);
            close_s(pl->fd);
            free(pl);
            return 0;
        }

    tprintf("Not linked to %s\n",argv[1]);
    return 0;
}
#endif

static int
dociface(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setflag(argc,argv[1],IS_CONV_IFACE,argv[2]);
}

/* Remember how many we've started .. maybe second for LZW link */
static int convers_servers = 0;

/* Stop convers server */
int
conv0(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    int16 port;

    if(argc < 2)
        port = IPPORT_CONVERS;
    else
        port = atoi(argv[1]);
    if (convers_servers > 0)
	convers_servers--;
#ifdef LINK
    if(!convers_servers && Linker)
        killproc(Linker);
#endif
    return stop_tcp(port);
}

/* Start up convers server */
int
conv1(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    int16 port;

#ifdef CHANNELNAMES
    if (!convers_servers)
        read_channels();
#endif CHANNELNAMES
    convers_servers++;
    if(argc < 2)
        port = IPPORT_CONVERS;
    else
        port = atoi(argv[1]);

#ifdef XCONVERS
    if (port == IPPORT_XCONVERS)
        return start_tcp(port,"XCONVERS Server",xconv_incom,CDAEMONSTACK);
    else
#endif
        return start_tcp(port,"CONVERS Server",conv_incom,CDAEMONSTACK);
}

static void
free_connection(cp)
register struct convection *cp;
{
    register struct permlink *p;

    for(p = permlinks; p; p = p->next)
        if(p->convection == cp)
            p->convection = NULLCONNECTION;
    /* if(cp->data)   free() checks for NULL; smaller code :-) */
    free(cp->data);
    free(cp->ibuf);
    if(cp->flags & CLOSE_SOCK)
        close_s(cp->fd);
    free((char *) cp);
}

static void
free_closed_connections()
{
    register struct convection *cp,*p;
    time_t currtime;

    currtime = time(&currtime);

    for(p = NULLCONNECTION,cp = convections; cp; )
        if(cp->type == CT_CLOSED ||
        cp->type == CT_UNKNOWN && cp->time + 300 < currtime) {
            if(p) {
                p->next = cp->next;
                free_connection(cp);
                cp = p->next;
            } else {
                convections = cp->next;
                free_connection(cp);
                cp = convections;
            }
        } else {
            p = cp;
            cp = cp->next;
        }
}

static void
update_permlinks(name,cp)
char *name;
struct convection *cp;
{
    register struct permlink *p;
    time_t currtime;

    for(p = permlinks; p; p = p->next)
        if(!strcmp(p->name,name)) {
            currtime = time(&currtime);
            p->convection = cp;
            p->statetime = currtime;
            p->tries = 0;
            p->waittime = 60;
            p->retrytime = currtime + p->waittime;
        }
}

static struct convection *
alloc_connection(fd)
int  fd;
{
    register struct convection *cp;
    time_t currtime;

    currtime = time(NULL);

    cp = (struct convection *)callocw(1,sizeof(struct convection ));
    cp->ibuf = (char *)callocw(1,LINELEN);
    cp->fd = fd;
    cp->maxq = UMaxQ;       /* Maximum qlimit for user */
    cp->flags = CLOSE_SOCK+USE_SOUND;       /* close on exit, by default */
    cp->time = currtime;
    cp->next = convections;
    convections = cp;
    return cp;
}

#ifdef LINK
/* check the host links for backlogged data.
 * If larger then set threshold, kill the link.
 * WG7J, 930208
 */
void check_buffer_overload(void) {
    struct convection *p;

    /* check the size of the outstanding data buffers */
    for(p = convections; p; p = p->next)
        if((p->maxq != 0) && (socklen(p->fd,1) > p->maxq)) {
                /* notify everyone of this special :-) occasion */
            bye_command(p);
                /* Blow this one out of the water */
            shutdown(p->fd,2);
            close_s(p->fd);
        }
}

void
connect_permlinks(a,b,c)
int a;
void *b;
void *c;
{
    int s,x;
    register struct permlink *p;
    struct sockaddr_in cport;
    time_t currtime;

    for(;;) {
        pause(15000L);
        for(p = permlinks; p; p = p->next) {
            currtime = time(&currtime);
            if(p->convection || p->retrytime > currtime)
                continue;
            p->tries++;
            p->waittime <<= 1;
            if(p->waittime > CMaxwait)
                p->waittime = CMaxwait;
            p->retrytime = p->waittime + currtime;
#ifdef XCONVERS
            x = p->use_lzw;
#else
            x = 0;
#endif
            cport.sin_family = AF_INET;
            cport.sin_port = x ? IPPORT_XCONVERS : IPPORT_CONVERS;
            cport.sin_addr.s_addr = p->addr; /* we've resolved this earlier */
            if((s = socket(AF_INET,SOCK_STREAM,0)) == -1)
                continue;
            if(connect(s,(char *)&cport,SOCKSIZE) == -1) {
                shutdown(s,2);  /* to make sure it doesn't linger around */
                close_s(s);     /* WG7J - 9207228 */
                continue;
            }
            p->fd = s;
            if(newproc("permlink",CDAEMONSTACK,
#ifdef XCONVERS
               x ? xconv_incom : conv_incom,
#else
               conv_incom,
#endif
               s,(void *)TELNET,NULL,0) == NULLPROC){
                shutdown(s,2);  /* blow it out of the water :-) */
                close_s(s);
            }
        }
        /* This is now called from the garbage collect process, such that it
         * checks even when the linker process isn't running! - WG7J
        check_buffer_overload();
        */
    }
}
#endif


static void
clear_locks()
{
    register struct convection *p;

    for(p = convections;p;p = p->next)
        p->locked = 0;
}

static void send_sounds(struct convection *p) {
    if(p->flags & USE_SOUND)
        p->xmitted += usputscnt(p->fd,"");
}

static char *timestring();

#ifdef LINK
#ifdef LINK_CHANGE_MSG
static void
send_link_change_msg(cp,name,change)
struct convection *cp;
char *name,*change;
{
    register struct convection *p;
    time_t curtime;
    char   *now;
    char deadmsg[256];
    int users;

    curtime = time(NULL);
    now = timestring(curtime);

    if (change=="DEAD") {
        users = 0;
        strcpy(deadmsg, "link lost, users:");
        for(p = convections; p; p = p->next)
			if(p->via == cp) {
                p->type = CT_CLOSED;
                strcat(deadmsg, " ");
                strcat(deadmsg, p->name);
                users++;
            }
        if (users==0)
            strcpy(deadmsg, "link lost, no users.");

    } else
        strcpy(deadmsg,change);

    clear_locks();
    for(p = convections; p; p = p->next) {
	if(p->type == CT_USER && !p->via && !p->locked)
            p->xmitted += usprintf(p->fd,
              "***%s %s %s.\n", now,name,deadmsg);
    }
    return;
}
#endif
#endif

static void
send_user_change_msg(name,host,oldchannel,newchannel)
char  *name,*host;
int  oldchannel,newchannel;
{
    register struct convection *p;
    time_t curtime;
    char   *now;
    char   tempname[NAMELEN+CNAMELEN+3];
#ifdef CHANNELNAMES
    char *channame;
    channame = channame_of(newchannel);
#endif /* CHANNELNAMES */

    curtime = time(NULL);
    now = timestring(curtime);
    strcpy(tempname,name);
    if (strcmp(host,Chostname)) {
        strcat(tempname,"@");
        strcat(tempname,host);
    }

    for(p = convections; p; p = p->next) {
        if(p->type == CT_USER && !p->via && !p->locked
#ifdef LINK_CHANGE_MSG
          && newchannel != -2
#endif

#ifdef LOCAL_CHANNELS
	&& !ISMESSAGE(p->channel)
#endif

        ) {
            if(p->channel == oldchannel) {
                if(newchannel >= 0) {
#ifdef CHANNELNAMES
                    if (channame != NULLCHAR)
                        p->xmitted += usprintf(p->fd,
                        "***%s %s switched to channel %d (%s).\n",
                        now,tempname,newchannel,channame);
                    else
#endif  /* CHANNELNAMES */
                        p->xmitted += usprintf(p->fd,
                        "***%s %s switched to channel %d.\n",
                        now, tempname,newchannel);
                } else
                    p->xmitted += usprintf(p->fd,
                    "***%s %s signed off.\n",now, tempname);
                p->locked = 1;
            }
            if(p->channel == newchannel) {
                send_sounds(p);
                p->xmitted += usprintf(p->fd,
                "***%s %s signed on.\n",now, tempname);
                p->locked = 1;
            }
        }
        if(p->type == CT_HOST && !p->locked) {
            p->xmitted += usprintf(p->fd,
            "/\377\200USER %s %s %d %d %d\n",
            name,host,0,oldchannel,newchannel == -2 ? -1 : newchannel);
            p->locked = 1;
        }
    }
    return;
}

#ifdef Oldcode

static char *
formatline(prefix,text)
char  *prefix,*text;
{

#define PREFIXLEN 10
#define CONVLINELEN   79

    register char  *f,*t,*x;
    register int  l,lw;

    static char buf[2*LINELEN];

    for(f = prefix,t = buf; *f; *t++ = *f++) ;
    l = (int)(t - buf);
    f = text;

    for(;;) {
        while(isspace(uchar(*f)))
            f++;
        if(!*f) {
            *t++ = '\n';
            *t = '\0';
            return buf;
        }
        for(x = f; *x && !isspace(uchar(*x)); x++) ;
        lw = (int)(x - f);
        if(l > PREFIXLEN && l + 1 + lw > CONVLINELEN) {
            *t++ = '\n';
            l = 0;
        }
        do {
            *t++ = ' ';
            l++;
        } while(l < PREFIXLEN);
        while(lw--) {
            *t++ = *f++;
            l++;
        }
    }
}

static void
send_msg_to_user(fromname,toname,text)
char  *fromname,*toname,*text;
{
    register struct convection *p;
    char buffer[2*LINELEN];

    for(p = convections; p; p = p->next) {
        if(p->type == CT_USER && !strcmp(p->name,toname))
            if(p->via) {
                if(!p->via->locked) {
                    p->via->xmitted += usprintf(p->via->fd,
                    "/\377\200UMSG %s %s %s\n",fromname,toname,text);
                    p->via->locked = 1;
                }
            } else {
                if(!p->locked) {
                    if(strcmp(fromname,"conversd")) {
                        sprintf(buffer,"<*%s*>:",fromname);
                        p->xmitted += usputscnt(p->fd,formatline(buffer,text));
                    } else {
                        p->xmitted += usputscnt(p->fd,text);
                        p->xmitted += usputscnt("\n");
                    }
                    p->locked = 1;
                }
            }
    }
    return;
}

static void
send_msg_to_channel(fromname,channel,text)
char  *fromname;
int  channel;
char  *text;
{
    char  buffer[3*LINELEN];
    register struct convection *p;

#ifdef LOCAL_CHANNELS
    if (ISMESSAGE(p->channel))
        return;
#endif

    for(p = convections; p; p = p->next) {
        if(p->type == CT_USER && p->channel == channel)
            if(p->via && !ISLOCAL(p->channel)) {
                if(!p->via->locked) {
                    p->via->xmitted += usprintf(p->via->fd,
                    "/\377\200CMSG %s %d ",fromname,channel);
                    p->via->xmitted += usputscnt(p->via->fd, text);
                    p->via->xmitted += usputscnt(p->via->fd, "\n");
                    p->via->locked = 1;
                }
            } else {
                if(!p->locked) {
                    sprintf(buffer,"<%s>:",fromname);
                    p->xmitted += usputscnt(p->fd,formatline(&buffer[0],text));
                    p->locked = 1;
                }
            }
    }
}

#else /* Oldcode */

/* Returns a formatted version of the given text.
 * The prefix appears at the beginning of the text, and each
 * line after the first will be indented to column PREFIXLEN.
 * All whitespace (SPACE and TAB) will be replaced by a single
 * SPACE character,
 * Lines will be filled to be CONVLINELEN characters long, wrapping
 * words as necessary.
 *
 * This uses an static internal  buffer rather than one passed by
 * the caller.  This increases the static memory used by the program
 * even if converse isn't active.  If we passed a buffer  allocated
 * on the stack, the process's stack would have to be large enough.
 */
static char *
formatline(prefix, text)
char  *prefix, *text;
{

#   define PREFIXLEN    10
#   define CONVLINELEN  79
#   define FMTBUFLEN    LINELEN

    static char          buf[FMTBUFLEN];
    register char       *f, *t;
    register int         l, lw;
    register int         left = FMTBUFLEN-2;
    /* Runs of characters in delims[] will be collapsed into a single
       space by formatline().
     */
    char                *delims = " \t\n\r";
/*
#   define BPUTC(c)     if (left > 0) { left--; *t++ = (c); } else
*/
#   define BPUTC(c)     do{ if (left > 0) { left--; *t++ = (c); }} while(0)

    /* Copy prefix into buf; set l to length of prefix.
     */
    l = 0;
    for (f = prefix, t = buf; *f; ) {
        BPUTC (*f++);
        l++;
    }

    f = text;

    for (;;) {
        /* Skip leading spaces */
        while (isspace(uchar(*f)))
            f++;

        /* Return if nothing more or no room left */
        if (!*f || (left <= 0)) {
            *t++ = '\n';        /* don't use BPUTC; do even if !left */
            *t   = '\0';
            return buf;
        }

        /* Find length of next word (seq. of non-blanks) */
        lw = strcspn (f, delims);

        /* If the word would extend past end of line, do newline */
        if (l > PREFIXLEN && (l + 1 + lw) > CONVLINELEN) {
            BPUTC ('\n');
            l = 0;
        }

        /* Put out a single space */
        do {
            BPUTC (' ');
            l++;
        } while(l < PREFIXLEN);

        /* Put out the word */
        while(lw--) {
            BPUTC (*f++);
            l++;
        }
    }
#   undef BPUTC
}


static void
send_msg_to_user(fromname,toname,text)
char  *fromname,*toname,*text;
{
    register struct convection *p;

    for (p = convections; p; p = p->next) {
        if(p->type == CT_USER && !strcmp(p->name,toname))
            if(p->via) {
                if(!p->via->locked) {
                    p->via->xmitted += usprintf(p->via->fd,
                    "/\377\200UMSG %.10s %.10s %.220s\n",
                    fromname, toname, text);
                    p->via->locked = 1;
                }
            } else {
                if(!p->locked) {
                    if(strcmp(fromname,"conversd")) {
                        char prefix[NAMELEN+10];
                        char *buf;

                        sprintf(prefix,"<*%.10s*>:",fromname);

                        buf = formatline (prefix,text);
                        p->xmitted += strlen (buf);
                        (void) usputs (p->fd, buf);
                    } else {    /* not from conversd */
                        p->xmitted += strlen (text);
                        (void) usputs (p->fd, text);
                    }   /* not from conversd */
                    p->locked = 1;
                } /* if not locked */
            }   /* not via */
    } /* for */
}

static void
send_msg_to_channel(fromname,channel,text)
char  *fromname;
int  channel;
char  *text;
{
    register struct convection *p;

#ifdef LOCAL_CHANNELS
    if (ISMESSAGE(channel))
        return;
#endif

    for (p = convections; p; p = p->next) {
        if(p->type == CT_USER && p->channel == channel)
            if(p->via && !ISLOCAL(p->channel)) {
                if(!p->via->locked) {
                    p->via->xmitted += usprintf(p->via->fd,
                    "/\377\200CMSG %.10s %d %.220s\n",
                    fromname, channel, text);
                    p->via->locked = 1;
                }
            } else {
                if(!p->locked) {
                    char         prefix[NAMELEN+10];
                    char        *buf;

                    sprintf(prefix,"<%.10s>:", fromname);

                    buf = formatline (prefix, text);
                    p->xmitted += strlen (buf);
                    (void) usputs (p->fd, buf);

                    p->locked = 1;
                } /* not locked */
            } /* not via */
    } /* for */
}


#endif /* Oldcode */


extern char *Months[];      /* in smtpserv.c */

static char  *
timestring(gmt)
long  gmt;
{
    static char  buffer[10];
    struct tm *tm;
    time_t currtime;

    time(&currtime);
    tm = localtime(&gmt);
    if(gmt + 24 * 60 * 60 > currtime)
        sprintf(buffer," %2d:%02d",tm->tm_hour,tm->tm_min);
    else
        sprintf(buffer,"%-3.3s %2d",Months[tm->tm_mon],tm->tm_mday);
    return buffer;
}

#ifdef space
char invitetext[] = "\n*** Message from %s at%s ...\nPlease join convers channel %d.\n\n";
char mbinvitetext[] = "\n*** Message from %s at%s ...\nPlease join convers by typing 'CONV %d' !\n\n";
char responsetext[] = "*** Invitation sent to %s @ %s";
#else
char invitetext[] = "\n*** Msg frm %s at%s ...\nPse join ch. %d.\n\n";
char mbinvitetext[] = "\n*** Msg frm %s at%s ...\nPse hit 'CONV %d' to join convers.\n\n";
char responsetext[] = "*** sent to %s @ %s";
#endif
char cnvd[] = "conversd";

static void
send_invite_msg(fromname,toname,channel)
char  *fromname,*toname;
int  channel;
{

    char buffer[LINELEN];
    struct convection *p;
#ifdef MAILBOX
    struct mbx *m;
#endif
    time_t currtime;

    currtime = time(&currtime);

#ifdef MAILBOX
    /* Check users in the mailbox that aren't active */
    for(m=Mbox;m;m=m->next){
        if(m->state == MBX_CMD && !stricmp(m->name,toname)) {
            usprintf(m->user,mbinvitetext,fromname,timestring(currtime),channel);
            usflush(m->user);
            clear_locks();
            sprintf(buffer,responsetext,toname,"BBS@");
            strcat(buffer,Hostname);
	    strcat(buffer,"\n");
            send_msg_to_user(cnvd,fromname,buffer);
            return;
        }
    }
#endif

    /* check the current convers users */
    for(p = convections; p; p = p->next) {
        if(p->type == CT_USER && !stricmp(p->name,toname)) {
            if(p->channel == channel) {
                clear_locks();
                sprintf(buffer,"*** User %s is already on this channel.\n",toname);
                send_msg_to_user(cnvd,fromname,buffer);
                return;
            }
            if(!p->via && !p->locked) {
                p->xmitted += usprintf(p->fd,invitetext,fromname, \
                timestring(currtime),channel);
                clear_locks();
                sprintf(buffer,responsetext,toname,Chostname);
                strcat(buffer,"\n");
                send_msg_to_user(cnvd,fromname,buffer);
                return;
            }
            if(p->via && !p->via->locked) {
                p->via->xmitted += usprintf(p->via->fd,
                "/\377\200INVI %s %s %d\n",fromname,toname,channel);
                return;
            }
        }
    }
    /* Nothing found locally, invite user on all links */
    for(p = convections; p; p = p->next) {
        if(p->type == CT_HOST && !p->locked) {
            p->xmitted += usprintf(p->fd,
            "/\377\200INVI %s %s %d\n",fromname,toname,channel);
        }
    }
    return;
}

static void
bye_command(cp)
struct convection *cp;
{
    struct convection *p;

    switch(cp->type) {
        case CT_UNKNOWN:
            cp->type = CT_CLOSED;
            break;
        case CT_USER:
            cp->type = CT_CLOSED;
            clear_locks();
            send_user_change_msg(cp->name,cp->host,cp->channel,-1);
            ConvUsers--;
            break;
        case CT_HOST:
            cp->type = CT_CLOSED;
#ifdef LINK_CHANGE_MSG
            send_link_change_msg(cp,cp->name,"DEAD");
#endif
            update_permlinks(cp->name,NULLCONNECTION);
            for(p = convections; p; p = p->next)
                if(p->via == cp) {
                    p->type = CT_CLOSED;
                    clear_locks();
#ifdef LINK_CHANGE_MSG
                    send_user_change_msg(p->name,p->host,p->channel,-2);
#else
                    send_user_change_msg(p->name,p->host,p->channel,-1);
#endif
                }
            ConvHosts--;
            break;
        case CT_CLOSED:
            break;
    }
}

static void
channel_command(cp)
struct convection *cp;
{
#ifdef CHANNELNAMES
    char *channame;
    char s[256];
#else
    char  s[7];
#endif
    int  newchannel;

    s[0] = '\0';
    sscanf(cp->ibuf,"%*s %6s",s);
    if(s[0] == '\0') {
#ifdef space
        cp->xmitted += usprintf(cp->fd,"*** You are on channel %d",cp->channel);
#else
        cp->xmitted += usprintf(cp->fd,"* On channel %d",cp->channel);
#endif
#ifdef CHANNELNAMES
        if ((channame=channame_of(cp->channel))!=NULLCHAR)
            cp->xmitted += usprintf(cp->fd," (%s)",channame);
#endif
        cp->xmitted += usputscnt(cp->fd,".\n");
#ifdef LOCAL_CHANNELS
        if (ISLOCAL(cp->channel))
            cp->xmitted += usputscnt(cp->fd,clocal);
        if (ISMESSAGE(cp->channel))
            cp->xmitted += usputscnt(cp->fd,cmessage);
#endif
#ifdef CHANNELNAMES
        show_channels(cp);
#endif
        return;
    }
#ifdef CHANNELNAMES
    newchannel = channum_of(s);
    if (newchannel == -1){
#endif
        newchannel = atoi(s);
#ifdef CHANNELNAMES
        if (newchannel == 0 && strcmp(s,"0"))
            newchannel = -1;
    }
#endif
    if(newchannel < 0) {
        /*  || newchannel > MAXCHANNEL) { */
        cp->xmitted += usprintf(cp->fd,cnumber,MAXCHANNEL);
        return;
    }
    if(newchannel == cp->channel) {
        cp->xmitted += usprintf(cp->fd,
        "*** Already on channel %d.\n",cp->channel);
        return;
    }
    send_user_change_msg(cp->name,cp->host,cp->channel,newchannel);
    cp->channel = newchannel;
    cp->xmitted += usprintf(cp->fd,"*** Now on channel %d",cp->channel);
#ifdef CHANNELNAMES
    if ((channame=channame_of(cp->channel))!=NULLCHAR)
        cp->xmitted += usprintf(cp->fd," (%s)",channame);
#endif
        cp->xmitted += usputscnt(cp->fd,".\n");
#ifdef LOCAL_CHANNELS
    if (ISLOCAL(cp->channel))
        cp->xmitted += usputscnt(cp->fd,clocal);
    if (ISMESSAGE(cp->channel))
        cp->xmitted += usputscnt(cp->fd,cmessage);
#endif
    return;
}

static void
help_command(cp)
struct convection *cp;
{
    FILE *fp;
    char fname[FILE_PATH_SIZE];	
    sprintf(fname,"%s/convers.hlp",Helpdir);
    if((fp = fopen(fname,READ_TEXT)) != NULLFILE) {
        sendfile(fp, cp->fd, ASCII_TYPE, 0, NULL);
        fclose(fp);
    } else {
        cp->xmitted += usprintf(cp->fd,"No help available. /q to quit\n");
    }
    return;
}

static void
version_command(cp)
struct convection *cp;
{
    cp->xmitted +=
    usprintf(cp->fd,"%s, Compile: %s, Uptime: %s, Core: %lu\n",  /*zkd*/
    shortversion,whofor,tformat(secclock()),farcoreleft());
}

static void
invite_command(cp)
struct convection *cp;
{
    char toname[NAMELEN+1];
    char *cp1;

    toname[0] = '\0';
    sscanf(cp->ibuf,"%*s %10s",toname);
    if((cp1=strchr(toname,'@')) != NULLCHAR)
        *cp1 = '\0';
    strlwr(toname);
    if(toname[0] != '\0')
        send_invite_msg(cp->name,toname,cp->channel);
    return;
}

int ShowConfLinks(int s, int full) {
    int num;
    struct convection *pc;
    struct permlink *pp;
    char  tmp[20];

    num = usprintf(s,"Host       State         Since%s\n",
    (full) ? " NextTry Tries Receivd Xmitted TxQueue" : "");
    for(pc = convections; pc; pc = pc->next)
        if(pc->type == CT_HOST)
            num += usprintf(s,
            (full) ?
            "%-10s Connected    %s               %7lu %7lu %7u\n" :
            "%-10s Connected    %s\n",
            pc->name,
            timestring(pc->time),
            pc->received,
            pc->xmitted,
            socklen(pc->fd,1));

    for(pp = permlinks; pp; pp = pp->next)
        if(!pp->convection || pp->convection->type != CT_HOST) {
            strcpy(tmp,timestring(pp->retrytime));
            num += usprintf(s,
            (full) ?
            "%-10s %-12s %s %s %5d\n" :
            "%-10s %-12s %s\n",
            pp->name,
            pp->convection ? "Connecting" : "Disconnected",
            timestring(pp->statetime),
            tmp,
            pp->tries);
        }
    num += usputscnt(s,"***\n");
    return num;
}

static void
links_command(cp)
struct convection *cp;
{
    char full[3];
    int f = 0;
    time_t curtime;

    curtime = time(NULL);
    cp->xmitted += usprintf(cp->fd,"*** %s",ctime(&curtime));
    full[0] = '\0';
    sscanf(cp->ibuf,"%*s %2s",full);
    if(*full == 'l' || *full == 'L')
        f = 1;
    cp->xmitted += ShowConfLinks(cp->fd,f);
    return;
}

static void
msg_command(cp)
struct convection *cp;
{

    char dummy[LINELEN],toname[NAMELEN+1],*text;
    register struct convection *p;

    toname[0] = '\0';
    sscanf(cp->ibuf,"%s %10s",dummy,toname);
    text = &cp->ibuf[0];
    text += strlen(dummy) + strlen(toname) + 2;
    strlwr(toname);

    if(!*text)
        return;
    for(p = convections; p; p = p->next)
        if(p->type == CT_USER && !strcmp(p->name,toname))
            break;
    if(!p)
        cp->xmitted += usprintf(cp->fd,"*** No such user: %s.\n",toname);
    else
        send_msg_to_user(cp->name,toname,text);
    return;
}

/* Set some personal data, like name and qth - WG7J */

static void
#ifdef CHANGE_PERSONAL
personal_data(cp)
#else
personal_command(cp)
#endif
struct convection *cp;
{
    struct convection *p;
    char *cp2;

    if((cp2 = strchr(cp->ibuf,' ')) != NULLCHAR) {
        cp2++;
        if(*cp2) {  /* there actually is an argument */
            free(cp->data);
            rip(cp->ibuf);      /* get rid of ending '\n' */
            if(strlen(cp2) > PERSONAL_LEN)
                *(cp2+PERSONAL_LEN) = '\0';
            cp->data = strdup(cp2);
            /* update all links too ! - WG7J */
            for(p=convections;p;p=p->next)
                if(p->type == CT_HOST)
                    p->xmitted += usprintf(p->fd,"/\377\200UDAT %s %s %s\n",
                    cp->name,cp->host,cp->data);
            return;
        }
    }
    cp->xmitted += usprintf(cp->fd,"*** data set to: %s\n", \
    cp->data ? cp->data : "" );
    return;
}

/* find the personal information for this user */
void set_personal(struct convection *cp) {
    FILE *fp;
    char *cp1;

    if((fp = fopen(Cinfo,"r")) == NULL)
        return;
    while(fgets(cp->ibuf,LINELEN,fp) != NULL) {
        cp1 = cp->ibuf;
        /* find end of name */
        while(*cp1 != ' ' && *cp1 != '\t' && *cp1 != '\0')
            cp1++;
        if(!*cp1)
            continue;
        *cp1 = '\0';
        if(stricmp(cp->name,cp->ibuf))
            continue;
        /* Found personal data ! */
        *cp1 = ' ';
        fclose(fp);
        personal_command(cp);
        return;
    }
    fclose(fp);
    cp->xmitted += usputscnt(cp->fd,"*** Type /personal <your name> <your QTH>.\n");
}

#ifdef CHANGE_PERSONAL
/* Store personal data - N2RJT */
static void
store_personal(struct convection *cp)
{
    FILE *f1, *f2;
    char line[LINELEN];
    int namelen;

    namelen = strlen(cp->name);
    if (cp->data) {

        /* Save old personal file to backup */
        unlink(Cinfobak);
        if(rename(Cinfo,Cinfobak))
            return;

        /*Write all users back, but update this one!*/
        if((f2 = fopen(Cinfo,WRITE_TEXT)) == NULLFILE)
            /* Can't create defaults file ???*/
            return;

        if((f1 = fopen(Cinfobak,READ_TEXT)) != NULLFILE) {
            while (fgets(line, LINELEN, f1)!=NULLCHAR) {
                if (strncmp(line,cp->name,namelen))
                    fputs(line, f2);
            }
            fclose(f1);
        }
        fprintf(f2, "%s %s\n", cp->name, cp->data);
        fclose(f2);
    }
}

static void
personal_command(cp)
struct convection *cp;
{
    personal_data(cp);
    if (allow_info_updates)
        store_personal(cp);
}
#endif  /* CHANGE_PERSONAL */

void SendMotd(int s)
{
    FILE *fp;

    if((fp=fopen(ConvMotd,"r")) != NULL) {
        sendfile(fp, s, ASCII_TYPE, 0, NULL);
        fclose(fp);
    }
}


/* protected by ftpusers file - WG7J */
static void
name_command(cp)
struct convection *cp;
{
    int newchannel = CDefaultChannel;
    char dummy[7];
    char *path;
    int pwdignore;
    long privs;
#ifdef CALLCHECK
    int i,digits;
#endif

    cp->name[0] = '\0';
    dummy[0] = '\0';
    sscanf(cp->ibuf,"%*s %10s %6s",cp->name,dummy);
    if(dummy[0] != '\0')
        newchannel = atoi(dummy);
    if(cp->name[0] == '\0')
        return;
    /* now check with ftpusers file - WG7J */
    if((path = mallocw(MBXLINE)) == NULLCHAR)
        return;
    pwdignore = 1;
    privs = userlogin(cp->name,NULLCHAR,&path,MBXLINE,&pwdignore,"confdef");
    free(path);
    if(privs & NO_CONVERS)
        return;
/* N2RJT - validate the callsign, similar to the check done in mailbox.c */
/* In this context, a valid callsign is any with 3 or more characters,   */
/* and at least one digit.  Length and digits don't include anything     */
/* after a dash.                                                         */
#ifdef CALLCHECK
    for (digits=i=0; i<strlen(cp->name); i++) {
        if ((cp->name)[i] == '-')
            break;
        if (isdigit((cp->name)[i]))
            digits++;
    }
    if (i<3 || digits==0)
        return;
#endif /* CALLCHECK */
    strlwr(cp->name);
    strcpy(cp->host,Chostname);
    cp->type = CT_USER;
    cp->xmitted += usprintf(cp->fd,
    "Conference @ %s  Type /HELP for help.\n",Chostname);
    /* Send the motd */
    SendMotd(cp->fd);
    if(dummy[0] != '\0' && newchannel < 0) {
        /* || newchannel > MAXCHANNEL) { */
        cp->xmitted += usprintf(cp->fd,cnumber,MAXCHANNEL);
    } else
        cp->channel = newchannel;
    send_user_change_msg(cp->name,cp->host,-1,cp->channel);
#ifdef LOCAL_CHANNELS
    if (ISLOCAL(cp->channel))
        cp->xmitted += usputscnt(cp->fd,clocal);
    if (ISMESSAGE(cp->channel))
        cp->xmitted += usputscnt(cp->fd,cmessage);
#endif
    set_personal(cp);
    ConvUsers++;
    return;
}

/* Set or show the status of the 'sound' flag - WG7J */
static void
sounds_command(cp)
struct convection *cp;
{
    char *cp2;

    if((cp2 = strchr(cp->ibuf,' ')) != NULLCHAR) {
        cp2++;
        if(*cp2) {   /* There is an argument */
            if(*cp2 == 'n' || *cp2 == 'N') {   /* Turn it off */
                cp->flags &= ~USE_SOUND;
            } else
                cp->flags |= USE_SOUND;
            return;
        }
    }
    if(cp->flags & USE_SOUND)
        cp->xmitted += usputscnt(cp->fd,"*** Sounds on\n");
    else
        cp->xmitted += usputscnt(cp->fd,"*** Sounds off\n");
    return;
}

#ifdef ENHANCED_VIA
/* Convert a socket (address + port) to an ascii string identifying
 * the family and possibly neighbor.
 */
char *
pvia(p)
void *p;	/* Pointer to structure to decode */
{
	static char buf[30];
	union sp sp;
	struct socket socket;
	struct nrroute_tab *np;

	sp.p = p;
	switch(sp.sa->sa_family){
	case AF_LOCAL:
        strcpy(buf,"local");
        break;
	case AF_INET:
        strcpy(buf,"telnet");
		break;
#ifdef	AX25
	case AF_AX25:
		if(strlen(sp.ax->iface) != 0)
            sprintf(buf,"ax25:%s",sp.ax->iface);
		else
            strcpy(buf,"ax25");
		break;
#endif
#ifdef	NETROM
	case AF_NETROM:
    if ((np = find_nrroute(sp.nr->nr_addr.node)) != NULLNRRTAB)
        strcpy(buf,np->alias);
    else
        pax25(buf,sp.nr->nr_addr.node);
		break;
#endif
    default:
        strcpy(buf,"?");
        break;
    }
	return buf;
}
#endif



/* Print a user display, return the number of characters sent */
int ShowConfUsers(int s,int quick,char *name) {
    int num,channel,l;
    struct convection *p;
#ifdef MAILBOX
    struct mbx *m;
#endif
    char buffer[LINELEN];
#ifdef CHANNELNAMES
    char *channame;
#endif

    if(quick) {
        num = usputscnt(s,"Channel ");
#ifdef CHANNELNAMES
        num += usputscnt(s,"          ");
#endif /* CHANNELNAMES */
        num += usputscnt(s,"Users\n");
        clear_locks();
        do {
            channel = -1;
            for(p = convections; p; p = p->next) {
                if(p->type == CT_USER &&
                    !p->locked &&
                (channel < 0 || channel == p->channel)) {
                    if(channel < 0) {
                        channel = p->channel;
#ifdef CHANNELNAMES
                        if ((channame=channame_of(channel))!=NULLCHAR) {
                            sprintf(buffer,"%7d(%s)         ",channel,channame);
                            buffer[17] = '\0';
                        } else
                            sprintf(buffer,"%7d          ", channel);
#else
                        sprintf(buffer,"%7d",channel);
#endif
                    }
                    strcat(buffer," ");
                    strcat(buffer,p->name);
                    p->locked = 1;
                }
            }
            if(channel >= 0) {
                num += usputscnt(s,buffer);
                num += usputscnt(s,"\n");
            }
        } while(channel >= 0);
    } else {
        num = usputscnt(s,
#ifdef CHANNELNAMES
        "User       Host       Via      Channel Name       Time Personal\n");
#else
        "User       Host       Via        Channel Time Personal\n");
#endif
        for(p = convections; p; p = p->next) {
            if(p->type == CT_USER) {
                if(name == NULL || *name == '\0' || !stricmp(p->name,name)) {
                    num += usprintf(s,"%-10s %-10s ",p->name,p->host);

                    if (p->via)
                        num += usprintf(s, "%-10s ", p->via->name);
                    else {
#ifdef ENHANCED_VIA
                        l = LINELEN;
                        if (getpeername(p->fd,buffer,&l)){
                            l = LINELEN;
                            getsockname(p->fd,buffer,&l);
                        }
                        num += usprintf(s, "%-10s ", pvia(buffer));
#else
                        num += usprintf(s, "%-10s ", "");
#endif
                    }
                    num += usprintf(s,
#ifdef CHANNELNAMES
                    "%5d %-8s %s %s\n",
#else	
                    "%5d %s %s\n",
#endif /* CHANNELNAMES */
                    p->channel,
#ifdef CHANNELNAMES
                    ((channame = channame_of(p->channel))!=0) ? channame : "",
#endif /* CHANNELNAMES */
                    timestring(p->time),
                    p->data ? p->data : "" );
                }
            }
        }
    }
#ifdef MAILBOX
    if (name == NULL || *name == '\0') {
        for(m=Mbox;m;m=m->next) {
            if(m->state == MBX_CMD) {
                if(quick)
                    num += usprintf(s," BBS %s\n",m->name);
                else
                    num += usprintf(s,"%-10s BBS@%s\n",m->name,Hostname);
            }
        }
    }
#endif
    num += usputscnt(s,"***\n");
    return num;
}

static void
who_command(cp)
struct convection *cp;
{
    char buffer[LINELEN];
    int quick = 0;
    time_t curtime;

    curtime = time(NULL);
    cp->xmitted += usprintf(cp->fd,"*** %s", ctime(&curtime));

    buffer[0] = '\0';
    sscanf(cp->ibuf,"%*s %s",buffer);
    if(buffer[0] == 'q')
        quick = 1;
    cp->xmitted += ShowConfUsers(cp->fd,quick,buffer);
    return;
}

static void
h_cmsg_command(cp)
struct convection *cp;
{
    char *text;
    int  channel;
    char name[LINELEN],dummy[40];

    sscanf(cp->ibuf,"%s %10s %d",dummy,name,&channel);
    text = &cp->ibuf[0];
    text += strlen(dummy) + strlen(name) + 2;
    while(isspace(*text) == 0)
        text++;
    text++;
    if(isprint(*text) != 0)
        send_msg_to_channel(name,channel,text);
    return;
}

/* Return 1 if the host is to be allowed, or 0 if refused - WG7J */
int Allow_host(int s) {
    struct filter_link *fl;
    struct sockaddr_in fsocket;
    int i = sizeof(struct sockaddr_in);

    if(Filterlinks) {    /* Check for this ip address */
        getpeername(s,(char *)&fsocket,&i);
        for(fl=Filterlinks;fl;fl=fl->next)
            if(fl->addr == fsocket.sin_addr.s_addr)
                return FilterMode;
        /* Not found ! */
        return !FilterMode;
    }
    return 1;
}

static void
h_host_command(cp)
struct convection *cp;
{

    char name[NAMELEN+1];
    struct convection *p;
    struct permlink *pp;

    if(!Allow_host(cp->fd)) {
        bye_command(cp);
        return;
    }
    name[0] = '\0';
    sscanf(cp->ibuf,"%*s %10s",name);
    if(name[0] == '\0') {
        bye_command(cp);
        return;
    }
    for(p = convections; p; p = p->next)
        if(!strcmp(p->name,name)) {
            bye_command(p);
            return;
        }
    for(pp = permlinks; pp; pp = pp->next)
        if(!strcmp(pp->name,name) && pp->convection && pp->convection != cp) {
            bye_command((strcmp(Chostname,name) < 0) ? pp->convection : cp);
            return;
        }
/*
    if(cp->type != CT_UNKNOWN)
        return;
 */
    cp->type = CT_HOST;
    cp->maxq = HMaxQ;
    strcpy(cp->name,name);      /* already allocated */
    update_permlinks(name,cp);
#ifdef LINK_CHANGE_MSG
    send_link_change_msg(cp,cp->name,"linked");
#endif
    cp->xmitted += usprintf(cp->fd,"/\377\200HOST %s\n",Chostname);
    for(p = convections; p; p = p->next)
        if(p->type == CT_USER) {
            cp->xmitted += usprintf(cp->fd,
            "/\377\200USER %s %s %d %d %d\n",
            p->name,p->host,0,-1,p->channel);
            if(p->data)
                cp->xmitted += usprintf(cp->fd,
                "/\377\200UDAT %s %s %s\n",
                p->name,p->host,p->data);
        }
    ConvHosts++;
    return;
}

static void
h_invi_command(cp)
struct convection *cp;
{
    char fromname[NAMELEN+1],toname[NAMELEN+1];
    int  channel;

    sscanf(cp->ibuf,"%*s %10s %10s %d",fromname,toname,&channel);
    send_invite_msg(fromname,toname,channel);
    return;
}

static void
h_loop_command(cp)
struct convection *cp;
{
    char host[NAMELEN+1];

    sscanf(cp->ibuf,"%*s %10s",host);
    log(cp->fd, "conversd rx: LOOP %s",host);
    bye_command(cp);
}

/* Command to take user's personal data across a link - WG7J */
void
h_udat_command(cp)
struct convection *cp;
{
    char *name,*host,*data;
    int len;
    struct convection *p;

    /* do a validity check first */
    if((name = strchr(cp->ibuf,' ')) == NULLCHAR)
        return;
    name++;
    if((host = strchr(name,' ')) == NULLCHAR)
        return;
    *host++ = '\0';
    if((data = strchr(host,' ')) == NULLCHAR)
        return;
    *data++ = '\0';
    rip(data);      /* rip the '\n' */
    if((len=strlen(data)) == 0)
        return;
    if(len > PERSONAL_LEN)
        *(data+PERSONAL_LEN) = '\0';

    /* everything seems fine, now find user ! */
    for(p=convections;p;p=p->next) {
        if(!strcmp(p->name,name) && !strcmp(p->host,host)) {
            free(p->data);
            p->data = strdup(data);
        }
        /* update over other links  Apr 12/93 VE3DTE */
        if(p->type == CT_HOST && !p->locked)
            p->xmitted += usprintf(p->fd,"/\377\200UDAT %s %s %s\n",
            name,host,data);
    }
}

static void
h_umsg_command(cp)
struct convection *cp;
{
    char dummy[NAMELEN+1],fromname[NAMELEN+1],toname[NAMELEN+1],*text;

    sscanf(cp->ibuf,"%s %10s %10s",dummy,fromname,toname);
    text = &cp->ibuf[0];
    text += strlen(dummy) + strlen(fromname) + strlen(toname) + 3;
    if(*text)
        send_msg_to_user(fromname,toname,text);
    return;
}

static void
h_user_command(cp)
struct convection *cp;
{
    char host[3*NAMELEN+1],name[3*NAMELEN+1];
    int  newchannel,oldchannel;
    struct convection *p;
    time_t currtime;

    currtime = time(&currtime);

    sscanf(cp->ibuf,"%*s %s %s %*s %d %d",name,host,&oldchannel,&newchannel);
    /* Make sure the fields are not longer */
    host[NAMELEN] = name[NAMELEN] = '\0';

    for(p = convections; p; p = p->next)
        if(p->type == CT_USER) {
            /* new 920705 dl9sau */
            /* If Neighbour2 registers a user on HostX, while someone has already
             * been registered for HostX via Neighbour1, then we definitely have
             * a loop !  We send a loop detect message and then close the link:
             * /..LOOP <Chostname> <myneighbour> <host>
             *
             * The LOOP PREVENTION CODE detects ONLY a loop if it starts at this
             * host. That's, why I suggest this code to be implemented in every
             * conversd implementation.
             */
            if (oldchannel < 0 && p->via != cp && !stricmp(p->host, host)) {
                usprintf(cp->fd,"/\377\200LOOP %s %s %s\n", \
                Chostname, host,p->via ? p->via->name : Chostname);
                log(cp->fd, "conversd sent: LOOP %s",host);
                bye_command(cp);
                return;
            }
            if(p->channel == oldchannel && p->via == cp && \
                !strcmp(p->name,name) && !strcmp(p->host,host))
                break;
        }
    if(!p) {
        p = (struct convection *)callocw(1,sizeof(struct convection ));
        p->type = CT_USER;
        strcpy(p->name,name);
        strcpy(p->host,host);
        p->via = cp;
        p->channel = oldchannel;
        p->time = currtime;
        p->next = convections;
        convections = p;
    }
    if((p->channel = newchannel) < 0) {
        p->type = CT_CLOSED;
        free_closed_connections();  /*  VE3DTE Apr 5/93 */
    }
    send_user_change_msg(name,host,oldchannel,newchannel);
    return;
}

struct cmdtable {
    char  *name;
    void (*fnc)(struct convection *);
    int  states;
};
struct cmdtable DFAR cmdtable[] = {
    "?",        help_command,       CM_USER,
    "bye",      bye_command,        CM_USER,
    "channel",  channel_command,    CM_USER,
    "exit",     bye_command,        CM_USER,
    "help",     help_command,       CM_USER,
    "invite",   invite_command,     CM_USER,
    "links",    links_command,      CM_USER,
    "msg",      msg_command,        CM_USER,
    "name",     name_command,       CM_UNKNOWN,
    "personal", personal_command,   CM_USER,
    "quit",     bye_command,        CM_USER,
    "sounds",   sounds_command,     CM_USER,
    "version",  version_command,    CM_USER,
    "who",      who_command,        CM_USER,
    "write",    msg_command,        CM_USER,

    "\377\200cmsg", h_cmsg_command,     CM_HOST,
    "\377\200host", h_host_command,     CM_UNKNOWN,
    "\377\200invi", h_invi_command,     CM_HOST,
    "\377\200loop", h_loop_command,     CM_HOST,
    "\377\200udat", h_udat_command,     CM_HOST,
    "\377\200umsg", h_umsg_command,     CM_HOST,
    "\377\200user", h_user_command,     CM_HOST,
    0,      0,          0,
};

static void
process_commands(cp,m)
struct convection *cp;
struct mbx *m;
{
    char arg[LINELEN];
    int arglen,size;
    char *ccp;
    struct cmdtable *cmdp;

    for(;;) {
        loop:
        if(cp->type == CT_CLOSED)
            break;
        setflush(cp->fd,'\n');
        usflush(cp->fd);
        memset(cp->ibuf,0,LINELEN);
        if(cp->type != CT_HOST)
            alarm(Ctdiscinit * 1000L);
        if((size = recvline(cp->fd,cp->ibuf,LINELEN-1)) <= 0)
            break;
        alarm(0L);
        cp->received += size;
        clear_locks();
        cp->locked = 1;
        if(*cp->ibuf == '/') {
            ccp = &cp->ibuf[1];
            arg[0] = '\0';
            sscanf(ccp,"%s",arg);
            arglen = strlen(arg);
            /* We are about to parse a command; most likely there
             * is alot of output; try to avoid fragmenting that
             * data by doing our own flushing ! - WG7J
             */
            setflush(cp->fd,-1);
            for(cmdp = cmdtable; cmdp->name; cmdp++) {
                if(!strncmpi(cmdp->name,arg,arglen)) {
                    if(cmdp->states & (1 << cp->type))
                        (*cmdp->fnc)(cp);
                    goto loop;
                }
            }
            if(cp->type == CT_USER)
                cp->xmitted += usprintf(cp->fd,
                "*** Unknown command '/%s'. Type /HELP for help.\n",arg);
            goto loop;
        }
        if((ccp = strpbrk(cp->ibuf,"\r\n")) != NULLCHAR)
            *ccp = '\0';
        if(isprint(cp->ibuf[0]) != 0 && cp->type == CT_USER)
            send_msg_to_channel(cp->name,cp->channel,cp->ibuf);
    }
    bye_command(cp);
    sockblock(cp->fd,SOCK_BLOCK);
    free_closed_connections();
}

/* Incoming convers session */
void
conv_incom(s,t,p)
int s;
void *t;
void *p;
{
    struct convection *cp;

    sockowner(s,Curproc);   /* We own it now */
    sockmode(s,SOCK_BINARY);
    sockblock(s,SOCK_NOTXBLOCK);   /* prevent backlogs ! */
    cp = alloc_connection(s);
    conv_incom2(s,t,p,cp);
}

#ifdef XCONVERS
/* Incoming LZW convers session */
void
xconv_incom(s,t,p)
int s;
void *t;
void *p;
{
    struct convection *cp;

    sockowner(s,Curproc);	/* We own it now */
    sockmode(s,SOCK_BINARY);
    sockblock(s,SOCK_NOTXBLOCK);   /* prevent backlogs ! */
    cp = alloc_connection(s);
    lzwinit(s,Lzwbits,Lzwmode);
    cp->flags |= USE_LZW;
    conv_incom2(s,t,p,cp);
}
#endif

static void
conv_incom2(s,t,p,cp)
int s;
void *t;
void *p;
struct convection *cp;
{
    struct permlink *pl;

    cp->channel = CDefaultChannel;

    for(pl = permlinks; pl; pl = pl->next)
        if(pl->fd == s) {
            pl->convection = cp;
            cp->xmitted += usprintf(s,"/\377\200HOST %s\n",Chostname);
        }

    if(pl == NULLPERMLINK) {
#ifdef AX25
        if((int)t == AX25TNC) { /* figure out call from socket */
            struct usock *up;
            char *chrp;

            if((up = itop(s)) == NULLUSOCK) {
                free_connection(cp);
                return;
            }
            sockmode(s,SOCK_ASCII);
            pax25(cp->name,up->cb.ax25->remote);
            if((chrp=strchr(cp->name,'-')) != NULL)
                *chrp = '\0';
            strlwr(cp->name);
            strcpy(cp->host,Chostname);
            cp->type = CT_USER;
            cp->xmitted += usprintf(s,
            "Conference @ %s  Type /HELP for help.\n",Chostname);
            SendMotd(s);
            clear_locks();
            cp->locked = 1; /* send to everyone but ourself */
            send_user_change_msg(cp->name,cp->host,-1,CDefaultChannel);
            set_personal(cp);
            ConvUsers++;
        } else
#endif
            usputscnt(cp->fd,"\nPlease login with '/n <call> [channel #]'\n\n");
    }
    process_commands(cp,NULLMBX);
}

#ifdef MAILBOX

/* this is for Mailbox users */
void
mbox_converse(struct mbx *m,int channel)
{
    struct convection *cp;
    int oldflush;

    sockblock(m->user,SOCK_NOTXBLOCK);  /* prevent backlogs ! */
    oldflush = setflush(m->user,'\n');  /* automatic line flushing */
    cp = alloc_connection(m->user);
    cp->channel = channel;
    strcpy(cp->name,m->name);
    strcpy(cp->host,Chostname);
    cp->type = CT_USER;
    cp->flags &= ~CLOSE_SOCK;     /* do not close socket on exit */
    cp->xmitted += usprintf(m->user,
    "Conference @ %s  Type /HELP for help.\n",Chostname);
    SendMotd(m->user);
    clear_locks();
    cp->locked = 1; /* send to everyone but ourself */
    send_user_change_msg(cp->name,cp->host,-1,cp->channel);
    set_personal(cp);
    ConvUsers++;

#ifdef LOCAL_CHANNELS
    if (ISLOCAL(cp->channel))
        cp->xmitted += usputscnt(cp->fd,clocal);
    if (ISMESSAGE(cp->channel))
        cp->xmitted += usputscnt(cp->fd,cmessage);
#endif
    process_commands(cp,m);

    setflush(m->user,oldflush);

}
#endif /* MAILBOX */

#endif /* CONVERS */





