/* 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
 * Major additions, rewrites, enhancements by KO4KS
 */

#include "global.h"
#ifdef CONVERS
#include "commands.h"
#include "ctype.h"
#ifdef	UNIX
#include <sys/stat.h>
#endif
#ifdef MSDOS
#include <io.h>
#else
#include <time.h>
#include "session.h"
#endif
#ifdef MAILBOX
#include "mailbox.h"
#endif
#include "files.h"
#ifdef LZW
#include "lzw.h"
#endif
#include "x.h"
#include "domain.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: convers.c,v 1.36 2001/05/06 16:32:55 brian Exp $";
#endif

extern char stars[];

static int conv_rand (void);

/* #pragma option -zEMYFAR */

#define	LINK	1
#define space


#if defined(LZW)
void togglelzw (int soc, int mode);
#endif


int32 CT4init = 7200;			/* 2 hours default */
static int CChannel = 0;		/* default entry to channel 0 */
static int HMaxQ = 6 * 1024L;
static int UMaxQ = 3 * 1024L;
int Sconv = -1;
static int ConvNet0 = 0;
static int ConvHeader = 0;
static int TimeStamp = 0;
extern char shortversion[];
extern char *Months[];		/*lint !e15 * in smtpserv.c */
static int Conv_mutex = TNOS_MUTEX_UNLOCKED;
static int Group_mutex = TNOS_MUTEX_UNLOCKED;

#define PREFIXLEN 10
#define CONVLINELEN   79

static const char *suits[] = { "Hearts", "Clubs", "Diamonds", "Spades" };
static const char *cards[] = { "Ace", "Deuce", "Three", "Four", "Five", "Six",
	"Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King" };

#ifdef STATS_USE
long localConfUsers = 0;
#endif

int Conflogins = 0;

#ifdef space
static char cnumber[] = "*** Channel numbers must be in the range 0..%d.\n";
#else
static char cnumber[] = "* range 0..%d.\n";
#endif


static char trailer[] = "***\n";
static char noinfo[] = "No additional information available";
static char sysinfoheader[] = "%sSystem Information for: %s - email to sysop@%s\n";
static char msgtext[] = "<*%s*>: %s\n";
static char theysigned[] = "%s%s signed o%s at %s.\n";
static char theyswitched[] = "%s%s switched to channel %d at %s.\n";
static char conversd[] = "conversd";
static char fullstr[] = "%s";
static char fullstrcr[] = "%s\n";
static char sosorry[] = "*** Sorry, ";
static char sorry[] = "%s%s Net on %s %d!\n";
static char busystr[] = "%s'%s' is already a %s!\n";
static char logfilestr[] = "%sLogfile is %sed\n";
static char netcontrolstr[] = "Net Control";
static char privatestr[] = "Closed";
static char clearedstr[] = "cleared";
static char passwordstr[] = "Password";
static char nowchannel[] = "%sNow on %s %d (%d user%s).\n";
static char nicknamestr[] = "%s%s%sset to '%s'.\n";
static char channelstr[] = "Channel";
static char systemstr[] = "SYSTEM";
static char unknownstr[] = "*** Unknown ";
static char unkcmdstr[] = "%scommand '/%s'.%s";
static char nouserstr[] = "%suser!\n";
static char gethelpstr[] = " Type /HELP for help.\n";
static char noopenstr[] = "Can't open '%s'\n";
static char activenetstr[] = "%sActive Net '%s'\n";
static char net2str[] = "%sNet %s %s\n";
static char conferencestr[] = "*** TNOS Conference @ ";
static char namecmdstr[] = "%sPlease login with '/n <call>'\n";
static char nonetstr[] = "%sNo Net!\n";
static char recheckstr[] = "*** Recheck";
static char questionstr[] = "*** Question";
static char nicknmstr[] = "Nickname ";
static char tmstr[] = "Time";
static char userstr[] = "User";
static char hoststr[] = "Host";
static char newuserstr[] = "%sNew user '%s' has entered conference area on channel '%d'";
static char urnotstr[] = "%sYou are not %s\n";
static char urassigned[] = "%s'%s' has assigned you as %s on %s %d\n";
static char wrongchannel[] = "%s%s '%s' not on this %s\n";
static char openminutes[] = "%sMinutes starting at %s\n%sNet '%s' - %s is '%s'\n";
static char closeminutes[] = "%sMinutes ending at %s\n";
static char headerstr[] = "User       Host       Via         Channel  Time Personal\n";
static char userdata[] = "%-10.10s %-10.10s %-11.11s %6d %s %-31.31s\n";
static char summary[] = "%sTNOS Conference Bridge command summary:\n ";
static char extendedsummary[] = "\n%sExtended Remote Commands:\n ";
static char modeis[] = "Mode is: %s\n";
static char acceptstr[] = "Accept";
static char refuse[] = "Refuse";
static char redun[] = "Conf. redundancy timer (sec)";
static char entrystr[] = "Conf. entry channel";
static char givehead[] = "Display login header on connect";
static char allownets[] = "Allow Nets on Channel 0";
static char timestampstr[] = "Timestamp all user messages";
static char maxwaitstr[] = "Re-link max wait (sec)";
static char hostqueuestr[] = "Max. Host Queue (bytes)";
static char userqueuestr[] = "Max. User Queue (bytes)";
static char motdstr[] = "Conference MOTD: %s\n";
static char sysinfostr[] = "SYStem INFOrmation: %s\n";
static char timestampfmt[] = "<*%s:%s*>:";
static char nontimestampfmt[] = "<*%s*>:";
static char timestampfmt2[] = "<%s:%s>:";
static char nontimestampfmt2[] = "<%s>:";
static char alreadyon[] = "%s%s %s is already on this channel.";
static char exitingstr[] = "Exiting %s%s\n";
static char welcome[] = "%s%s%sNet '%s'";
static char welcome2[] = " - %s is '%s'...";
static char welcome3[] = "\n Welcome, %s!\n";
static char welcomeback[] = "%sWelcome back, %s\n";
static char noinvite[] = "%s%s net, only %s can invite\n";
static char nosuchuser[] = "%sNo such user: %s.\n";
static char online[] = "%sThere are %d users online\n";
static char groupsavail[] = "%sThere %s %d group%s available\n";
static char colorstatus[] = "%sCurrently ANSI Color graphics are O%s\n";
static char personalset[] = "%sPersonal data set to: %s\n";
#ifdef ALLSERV
static char quotebanner[] = "*** Quote of the day:\n";
#endif
static char whobanner[] = "%s             %s  %s Personal\n";
static char rollstr[] = "%s'%s' has rolled a %d and a %d for a total of %d! %s";
static char cutstr[] = "%s'%s' has cut the deck and selected the %s of %s! %s";
static char gloghdr[] = "*** Current Check-ins\n";
static char glogstr[] = "    %s     %s         In    %s\n";
static char grouphdr[] = "%sAvailable Groups:\n Channel  Group Name\n =======  ==========\n";

#ifdef space
static char youare[] = "%sYou are %son channel %d.\n";
static char amessage[] = "\n*** Message from ";
static char invitetext[] = "%s%s at %s ...\n%sPlease join conference channel %d.\n";
static char mbinvitetext[] = "%s%s at %s ...\n%sPlease type 'CONF %d' to join conference channel %d.\n";
static char responsetext[] = "%sInvitation sent to %s @ %s";
#else
static char youare[] = "%sOn channel %d.\n";
static char amessage[] = "\n*** Msg frm ";
static char invitetext[] = "%s%s at %s ...\n%sPse join ch. %d.\n";
static char mbinvitetext[] = "%s at %s ...\n%sPse hit 'CONF %d' for conference ch. %d.\n";
static char responsetext[] = "%ssent to %s @ %s";
#endif

static char hinvi[] = "/\377\200INVI %s %s %d %s\n";
static char uaddstr[] = "/\377\200UADD %s %s %s %d %s\n";
static char bumpstr[] = "/\377\200BUMP %s %d\n";
static char ndatstr[] = "/\377\200NDAT %d %d %d %d %d %s|%s|%s|\n";
static char topicstr[] = "/\377\200TOPI %s %s %ld %d %s\n";
static char quesstr[] = "/\377\200QUES %d %c %s %s\n";
static char umsgstr[] = "/\377\200UMSG %s %s %s\n";
static char cmsgstr[] = "/\377\200CMSG %s %d %s\n";
static char hhoststr[] = "/\377\200HOST %s %s %s\n";
static char huserstr[] = "/\377\200USER %s %s %ld %d %d %s\n";
static char loopstr[] = "/\377\200LOOP %s %s %s\n";
static char SYSCOLORS[] = "0C";
static char TEXTCOLORS[] = "09";
static char INFOCOLORS[] = "0B";

#if 0
static char INPUTCOLORS[] = "0F";
#endif

static char ff_str[] = "ff";
static char n_str[] = "n";
static char empty[] = "";


static char myfeatures[] = "dnpu";

#define MAXCHANNEL 	((int) 32767)
#ifndef LINELEN
#define LINELEN 	256
#endif
#define INBUFLEN	2048
#define MAX_WAITTIME	(60*60*3)
#define NAMELEN 16

static long CMaxwait = MAX_WAITTIME;


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 */
	char nickname[NAMELEN + 1];	/* Nickname of user */
	char password[NAMELEN + 1];	/* Password of user */
	struct convection *via;	/* Pointer to neighbor host */
	char *data;		/* room for some personal data */
	int channel;		/* Channel number */
	int net;		/* Channel of controlled net */
	time_t nettime;		/* Time entered channel (used by nets) */
	time_t 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 CHANGED_INFO 4
#define USE_LZW 8
#define USE_COLOR 16
	char colorset[2];	/* current color set */
	int features;
#define FEATURE_AWAY    1	/* a - "away feature" */
#define FEATURE_FWD     2	/* d - "destination forwarding" */
#define FEATURE_MODES   4	/* m - "channel modes" */
#define FEATURE_LINK    8	/* p - "ping pong link measurement" */
#define FEATURE_UDAT    16	/* u - "udat command extension and user command understood both" */
#define FEATURE_NICK    32	/* n - "TNOS Nickname extensions" */
	/* 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[INBUFLEN];
	 */
	char *ibuf;		/* Input buffer */
	char *ibufpt;		/* 2nd buffer pointer, for parsing command line */
	int received;		/* Number of bytes received */
	int xmitted;		/* Number of bytes transmitted */
	int paged;		/* Last channel invited to (paged) */
	struct convection *next;/* Linked list pointer */
};


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

#define NULLCONNECTION	((struct convection *) 0)

static struct convection *convections;


struct permlink {
	char name[NAMELEN + 1];	/* Name of link */
	char rev[NAMELEN + 1];	/* revision of software (CT_HOST) */
	uint32 addr;		/* address to link to */
	struct convection *convection;	/* Pointer to associated connection */
	time_t statetime;	/* Time of last (dis)connect */
	int tries;		/* Number of connect tries */
	time_t waittime;	/* Time between connect tries */
	time_t retrytime;	/* Time of next connect try */
	time_t testwaittime;	/* Time between tries */
	time_t testnexttime;	/* Time of next test */
	time_t rxtime;		/* rtt by other side */
	time_t txtime;		/* rtt found out by me */
	unsigned short port;	/* port number to connect to */
	int fd;			/* socket descriptor */
	struct permlink *next;	/* Linked list pointer */
};

#define NULLPERMLINK ((struct permlink *) 0)

static struct permlink *permlinks;



struct filter_link {
	struct filter_link *next;
	uint32 addr;
};

#ifndef _lint
#define NULLFL ((struct filter_link *) 0)
#endif

static struct filter_link *Filterlinks;
static int FilterMode;


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


#if 0
#define NR_PERMLINKS 20		/* MUST be changed, later */
#endif

struct destination {
	char name[NAMELEN + 1];	/* destination name */
	char rev[NAMELEN + 1];	/* revision of software (CT_HOST) */
	struct permlink *link;	/* link to this destination */
	long rtt;		/* round trip time to this host */
	long last_sent_rtt;	/* last donwnstream sent rtt */
#if 0
	int downstream[NR_PERMLINKS];	/* all up/downstream times */
#endif
	struct destination *next;	/* a one dimensional list is ok for now :-) */
};


#define NULLDESTINATION  ((struct destination *) 0)
static struct destination *destinations;


extern void statlog (const char *buf);
extern const char *displayMBstatus (int state, int issysop);
#ifndef UNIX
extern int colorchange (register const char *input, register char *last);
#endif
static int ShowConfDest (int s, const char *dest, const char *name);
static void update_destinations (struct permlink * p, char *name, long rtt, const char *rev);

#ifdef ALLSERV
extern char *getquote (void);
#endif

static void conv_randomize (void);
static int conv_random (int num, int base);
static void sysinfo_command (struct convection * cp);
static void h_sysi_command (struct convection * cp);
static void cmdsummary_command (struct convection * cp);
static void personal_command (struct convection * cp);
static void list_command (struct convection * cp);
static void me_command (struct convection * cp);
static void cq_command (struct convection * cp);
static void imsg_command (struct convection * cp);
static void version_command (struct convection * cp);
static void hosts_command (struct convection * cp);
static void nickname_command (struct convection * cp);
static void convcolorchange (struct convection * p, char *str);
static void roll_command (struct convection * cp);
static void cut_command (struct convection * cp);
static void save_personal (struct convection * cp);
static void conv_incom (int s, void *t, void *p);

static void free_connection (register struct convection * cp);
static void free_closed_connections (void);
static struct convection *alloc_connection (int fd);
static void check_buffer_overload (void);
static void clear_locks (void);
static void send_sounds (struct convection * p);
static char *timestring (time_t gmt);
static void send_user_change_msg (char *name, char *host, time_t thetime, int oldchannel, int newchannel, char *pers);
static char *formatline (char *prefix, const char *text);
static void send_msg_to_user (const char *fromname, const char *toname, const char *text);
static void send_msg_to_channel (const char *fromname, int channel, const char *text);
static void send_invite_msg (char *fromname, char *toname, int channel, char *msg);
static void time_command (struct convection * cp);
static int onchannel (int channel);
static void mystatus (struct convection * cp, int old);
static void channel_command (struct convection * cp);
static void uptime_command (struct convection * cp);
static int gatekeeper (struct convection * cp, int channel);
static void accept_command (struct convection * cp);
static void help_command (struct convection * cp);
static void cstat_command (struct convection * cp);
static void news_command (struct convection * cp);
static void invite_command (struct convection * cp);
static void links_command (struct convection * cp);
static void msg_command (struct convection * cp);
static void announce_new_user (struct convection * cp);
static void color_command (struct convection * cp);
static void set_personal (struct convection * cp);
static void name_command (struct convection * cp);

#ifdef LZW
static void compressed_command (struct convection * cp);
#endif

static void sounds_command (struct convection * cp);
static void update_user_data (struct convection * cp, int personal);
static void password_command (struct convection * cp);
static int isrosedigit (char c);
static char *getVia (char *call);
int CountConfUsers (void);
static int CountConfGroups (void);
#ifdef ALLSERV
static void quote_command (struct convection * cp);
#endif
static void who_command (struct convection * cp);
static void whois_command (struct convection * cp);
static void realname_command (struct convection * cp);
static void h_ecmd_command (struct convection * cp);
static void h_cmsg_command (struct convection * cp);
static void h_unknown_command (struct convection * cp);
static void h_dest_command (struct convection * cp);
static void h_topi_command (struct convection * cp);
static void h_rout_command (struct convection * cp);
static void h_ping_command (struct convection * cp);
static void h_pong_command (struct convection * cp);
static void h_link_command (struct convection * cp);
static int Allow_host (int s);
static void h_host_command (struct convection * cp);
static void h_invi_command (struct convection * cp);
static void h_loop_command (struct convection * cp);
static void h_uadd_command (struct convection * cp);
static void initialusers (int channel);
static void h_ndat_command (struct convection * cp);
static void h_ques_command (struct convection * cp);
static void h_umsg_command (struct convection * cp);
static void h_bump_command (struct convection * cp);
static void h_user_command (struct convection * cp);
static void gname_command (struct convection * cp, char *cptr);
static void assignnet_command (struct convection * cp);
static void bumpnet_command (struct convection * cp);
static void process_question (int channel, char type, char *name, const char *cptr);
static void question_command (struct convection * cp, char *cptr);
static void log_command (struct convection * cp, char *cptr);
static void nonet_command (struct convection * cp);
static void gpassword_command (struct convection * cp, char *cptr, int disableit);
static void gprivate_command (struct convection * cp, char *cptr);
static void group_command (struct convection * cp);
static void smiley_command (struct convection * cp);

#ifdef SAMCALLB
static void call_command (struct convection * cp);
#endif

static void glog_command (struct convection * cp);
static void net_command (struct convection * cp);
static void list_groups_command (struct convection * cp);
static void join_command (struct convection * cp);
static void process_commands (struct convection * cp, struct mbx * m);
void conversWriteall (char *str);
void conversWrite (char *str, char *user);
int sockblock (int s, int value);

#if 0
static void conv_usflush (int s);
#endif

static char *ts3 (time_t seconds, char *buffer);
static char *ts4 (time_t seconds);
static int ecmd_exists (char *cmdname);
static void getTXname (struct convection * cp, char *buf);
static int ShowConfLinks2 (int s, char *user, int full);


#undef CNAMELEN
#define CNAMELEN 16
char Chostname[CNAMELEN + 1], CConsole[CNAMELEN + 1];
static char *mysysinfo;


static char *confMOTD = NULLCHAR;

#define TOPICLEN 63

struct group {
	int channel;		/* channel # for this group info */
	char name[TOPICLEN + 1];/* group's name */
	char password[NAMELEN + 1];	/* password required to enter group */
	char moderator[NAMELEN + 1];	/* real name of current moderator */
	FILE *qfile;		/* stream of current question file */
	short nextq;		/* last question number in qfile */
	short totalq;		/* total questions in qfile */
	FILE *logfile;		/* stream of current logfile */
	char logged;		/* status of log file (1=open) */
	char private;		/* is it private (1 = yes) */
	struct group *next;	/* next group in queue */
};

#define NULLGROUP ((struct group *) 0)
#define NOCONTROL ((struct group *) -1)



struct extendedcmds {
	char *name;
	struct extendedcmds *next;
};

#define NULLEXTCMD ((struct extendedcmds *)0)
static struct extendedcmds *ecmds;


static char nonetoverride;
static struct group *groups;
static struct group *find_group (int channel);
static void bye_command (struct convection * cp);
static void update_net_data (struct group * gp);
static struct group *get_group (struct convection * cp);
static struct group *can_gcontrol (struct convection * cp);
static struct group *lookup_group (char *name);

extern char *confMOTD;
extern char Ccall[AXALEN], Calias[AXALEN];
extern time_t StartTime;

static void setparamptr (struct convection *cp, char *str);
static char *getparam (struct convection *cp);
static char *getparamline (struct convection *cp);


static int docfilter (int argc, char *argv[], void *p);
static int doconvconsole (int argc, char *argv[], void *p);
static int dochostname (int argc, char *argv[], void *p);
static int doconvstat (int argc, char *argv[], void *p);
static int doconvcstat (int argc, char *argv[], void *p);
static int dociface (int argc, char *argv[], void *p);
static int doconfcall (int argc, char *argv[], void *p);
static int doconfalias (int argc, char *argv[], void *p);
static int doclink (int argc, char *argv[], void *p);
static int docunlink (int argc, char *argv[], void *p);
static int dodrop (int argc, char *argv[], void *p);
static int doct4 (int argc, char *argv[], void *p);
static int doentrychannel (int argc, char *argv[], void *p);
static int doconfmotd (int argc, char *argv[], void *p);
static int dosysinfo (int argc, char *argv[], void *p);
static int doconvnet0 (int argc, char *argv[], void *p);
static int dotimestamp (int argc, char *argv[], void *p);
static int docmaxwait (int argc, char *argv[], void *p);
static int dohmaxq (int argc, char *argv[], void *p);
static int doumaxq (int argc, char *argv[], void *p);
static int doheader (int argc, char *argv[], void *p);

static struct cmds Ccmds[] =
{
#ifdef AX25
	{ "alias",		doconfalias,	0, 0, NULLCHAR },
#endif
	{ "console",		doconvconsole,	0, 0, NULLCHAR },
	{ "cstat",		doconvcstat,	0, 0, NULLCHAR },
	{ "drop",		dodrop,		0, 0, NULLCHAR },
	{ "entrychannel",	doentrychannel,	0, 0, NULLCHAR },
	{ "filter",		docfilter,	0, 0, NULLCHAR },
	{ "hmaxq",		dohmaxq,	0, 0, NULLCHAR },
	{ "header",		doheader,	0, 0, NULLCHAR },
	{ "hostname",		dochostname,	0, 0, NULLCHAR },
	{ "interface",		dociface,	0, 0, NULLCHAR },
#ifdef LINK
	{ "link",		doclink,	0, 0, NULLCHAR },
#endif
	{ "maxwait",		docmaxwait,	0, 0, NULLCHAR },
#ifdef AX25
	{ "motd",		doconfmotd,	0, 0, NULLCHAR },
	{ "mycall",		doconfcall,	0, 0, NULLCHAR },
#endif
	{ "net0",		doconvnet0,	0, 0, NULLCHAR },
	{ "online",		doconvstat,	0, 0, NULLCHAR },
	{ "sysinfo",		dosysinfo,	0, 0, NULLCHAR },
#ifdef AX25
	{ "t4",			doct4,		0, 0, NULLCHAR },
	{ "timestamp",		dotimestamp,	0, 0, NULLCHAR },
#endif
	{ "umaxq",		doumaxq,	0, 0, NULLCHAR },
#ifdef LINK
	{ "unlink",		docunlink,	0, 0, NULLCHAR },
#endif
	{ NULLCHAR,		0,		0, 0, NULLCHAR }
};



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



static void setparamptr (struct convection *cp, char *str)
{
	cp->ibufpt = str;
}


/*---------------------------------------------------------------------------*/

static char *
getparamline (struct convection *cp)
{
	cp->ibufpt = skipwhite (cp->ibufpt);
	return cp->ibufpt;
}


/*---------------------------------------------------------------------------*/

static char *getparam (struct convection *cp)
{
char *arg;

	cp->ibufpt = skipwhite (cp->ibufpt);
	arg = cp->ibufpt;
	cp->ibufpt = skipnonwhite (cp->ibufpt);
	if (*(cp->ibufpt))
		*(cp->ibufpt)++ = '\0';
	return arg;
}



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

	if (argc < 2) {
		tprintf (fullstrcr, pax25 (tmp, Ccall));
		return 0;
	}
	if (setcall (Ccall, argv[1]) == -1)
		return -1;
	return 0;
}



/* Display or change our AX.25 conference alias */
static int
doconfalias (int argc, char *argv[], void *p OPTIONAL)
{
char tmp[AXBUF];

	if (argc < 2) {
		tprintf (fullstrcr, pax25 (tmp, Calias));
		return 0;
	}
	if (setcall (Calias, argv[1]) == -1)
		return -1;
	return 0;
}



/* Set link redundancy timer */
static int
doct4 (int argc, char *argv[], void *p OPTIONAL)
{
	return setlong (&CT4init, redun, argc, argv);
}

#endif /* AX25 */



/* Set entry channel number */
static int
doentrychannel (int argc, char *argv[], void *p OPTIONAL)
{
	return setint (&CChannel, entrystr, argc, argv);
}



/* Allow nets to be held on channel 0 */
static int
doconvnet0 (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&ConvNet0, allownets, argc, argv);
}



static int
doconvstat (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
	if (Current == Command)
		Command->flowmode = 0;	/* clear 'more' paging on command screen */
	(void) ShowConfUsers (Curproc->output, NULLCHAR);
	tputs ("\n");
	if (Current == Command)
		Command->flowmode = 1;	/* set 'more' paging on command screen */
	return 0;
}



static int
doconvcstat (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
	(void) ShowConfLinks (Curproc->output, 1);
	tputs ("\n");
	(void) ShowConfDest (Curproc->output, "", "");
	tputs ("\n");
	return 0;
}



/* Give login header */
static int
doheader (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&ConvHeader, givehead, argc, argv);
}



/* Allow broadcasts to be timestamped */
static int
dotimestamp (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&TimeStamp, timestampstr, argc, argv);
}



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



/* Set maxwait time for timed out links */
static int
docmaxwait (int argc, char *argv[], void *p OPTIONAL)
{
	return setlong (&CMaxwait, maxwaitstr, argc, argv);
}



/* Set max qlimit for host links */
static int
dohmaxq (int argc, char *argv[], void *p OPTIONAL)
{
	return setint (&HMaxQ, hostqueuestr, argc, argv);
}



/* Set max qlimit for user links */
static int
doumaxq (int argc, char *argv[], void *p OPTIONAL)
{
	return setint (&UMaxQ, userqueuestr, argc, argv);
}



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



static int
doconvconsole (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc == 1)
		tprintf (fullstrcr, CConsole);
	else {
		strncpy (CConsole, argv[1], NAMELEN);
		CConsole[NAMELEN] = '\0';
	}
	return 0;
}



/* View/Change the message we send to new conference connects. */

static int 
doconfmotd (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2)
		tprintf (motdstr, confMOTD);
	else {
		if (confMOTD != NULL)
			free (confMOTD);
		confMOTD = strdup (argv[1]);
	}
	return 0;
}



/* View/Change the message we send in response to sysinfo requests. */

static int 
dosysinfo (int argc, char *argv[], void *p OPTIONAL)
{
	if (argc < 2)
		tprintf (sysinfostr, mysysinfo);
	else {
		if (mysysinfo != NULL)
			free (mysysinfo);
		mysysinfo = strdup (argv[1]);
	}
	return 0;
}



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

	if (argc == 1) {
		if (Filterlinks) {
			tprintf (modeis, FilterMode ? acceptstr : refuse);
			for (fl = Filterlinks; fl; fl = fl->next)
				tprintf (fullstrcr, inet_ntoa (fl->addr));
		}
		return 0;
	}
	if (!stricmp (argv[1], "mode")) {
		if (argc == 2)
			tprintf (modeis, FilterMode ? acceptstr : 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
static int
doclink (int argc, char *argv[], void *p OPTIONAL)
{
uint32 addr;
struct permlink *pl;

	if (argc == 1) {
		for (pl = permlinks; pl; pl = pl->next)
			tprintf (fullstrcr, inet_ntoa (pl->addr));
		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;
	pl->next = permlinks;
	permlinks = pl;
	pl->port = (int16) ((argc > 2) ? atoi (argv[2]) : IPPORT_CONVERS);
	if (argc > 3)
		strncpy (pl->name, argv[3], NAMELEN);
	else
		strncpy (pl->name, argv[1], NAMELEN);

	update_permlinks (pl->name, NULLCONNECTION);
	pl->retrytime -= 55;	/* 1st start in 5 seconds */
	pl->waittime = 30;	/* 2nd after 1 minute */

	if (!Linker)
		Linker = newproc ("Converse linker", 1024, connect_permlinks, 0, 0, NULL, 0);

	return 0;
}



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

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

	pls = NULLPERMLINK;
	for (pl = permlinks; pl; pls = pl, pl = pl->next)
		if ((pl->addr == addr) || !stricmp (pl->name, argv[1])) {
			if (pls)
				pls->next = pl->next;
			else
				permlinks = pl->next;
			tprintf ("Unlinking %s: %s\n", inet_ntoa (pl->addr), pl->name);
			(void) 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
dodrop (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
struct convection *pl, *pls;

	pls = NULLCONNECTION;
	for (pl = convections; pl; pls = pl, pl = pl->next)
		if (!stricmp (pl->name, argv[1])) {
			if (pls)
				pls->next = pl->next;
			else
				convections = pl->next;
			tprintf ("Droping %s\n", pl->name);
			(void) shutdown (pl->fd, 2);
			close_s (pl->fd);
			free (pl);
			return 0;
		}
	tprintf ("Cannot drop %s: not connected\n", argv[1]);
	return 0;
}



/* Stop convers server */
int
conv0 (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
#ifdef LINK
	if (Linker) {
		killproc (Linker);
		Linker = 0;
	}
#endif
	return (deleteserver (&Sconv));
}



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

	Conv_mutex = TNOS_MUTEX_UNLOCKED;
	Group_mutex = TNOS_MUTEX_UNLOCKED;
	if (!Chostname[0])	{
		if (Hostname)
			strncpy (Chostname, Hostname, CNAMELEN);
		else
			strcpy (Chostname, "localhost");
	}
	port = (argc > 1) ? atoi (argv[1]) : IPPORT_CONVERS;
	(void) installserver (argc, argv, &Sconv, "Conference listener", port,
		INADDR_ANY, conversd, conv_incom, 1024, NULL);
#ifdef LINK
	if (Linker) {
		killproc (Linker);
		Linker = 0;
	}
#endif
	return 0;
}



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

	for (p = permlinks; p; p = p->next)
		if (p->convection == cp)
			p->convection = NULLCONNECTION;
	free (cp->ibuf);
	if (cp->flags & CLOSE_SOCK)
		close_s (cp->fd);
	free ((char *) cp);
}



static void
free_closed_connections (void)
{
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 (char *name, struct convection *cp)
{
register struct permlink *p;
time_t currtime;
struct destination *d;

	for (p = permlinks; p; p = p->next) {
		if (!strcmp (p->name, name)) {
			for (d = destinations; d; d = d->next) {
				if (d->rtt && (d->link == p))
					update_destinations (p, d->name, 0, "");
			}
			currtime = time (&currtime);
			p->convection = cp;
			p->statetime = currtime;
			p->tries = 0;
			p->waittime = 60;
			p->rxtime = 0;
			p->txtime = 0;
			p->testwaittime = currtime;
			p->testnexttime = currtime + 60;
			p->retrytime = currtime + p->waittime;
		}
	}
}



static struct convection *
alloc_connection (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, INBUFLEN + 1);
	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->paged = -1;
	cp->net = -1;
	cp->features = 0;
	kmutex_lock (&Conv_mutex);
	cp->next = convections;
	convections = cp;
	kmutex_unlock (&Conv_mutex);
	return cp;
}



#ifdef LINK
/* check the host links for backlogged data.
 * If larger then set threshold, kill the link.
 * WG7J, 930208
 */
static 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)) {
			/* Blow this one out of the water */
			(void) shutdown (p->fd, 2);
			close_s (p->fd);
		}
}



static void
connect_permlinks (int a OPTIONAL, void *b OPTIONAL, void *c OPTIONAL)
{
int s;
register struct permlink *p;
struct sockaddr_in cport;
time_t currtime;
struct destination *d;
char buffer[256];

	server_disconnect_io ();
	for ( ; ; ) {
		kpause (15000L);
		currtime = time (&currtime);
		for (p = permlinks; p; p = p->next) {
			/* Update our existing connections */
			if (p->convection) {
				if (p->testnexttime < currtime) {
					if ((p->testwaittime + 7300) < currtime) {
						p->rxtime = 0;
						p->txtime = 0;
						for (d = destinations; d; d = d->next) {
							if (d->link == p)
								update_destinations (p, d->name, 0, "");
						}
					}
					p->convection->xmitted += usprintf (p->convection->fd, "/\377\200PING\n");
					p->testwaittime = currtime;
					p->testnexttime = currtime + 7300;
				}
			}
		}

		for (p = permlinks; p; p = p->next) {
			if (p->convection || p->retrytime > currtime)
				continue;
			p->tries++;
			p->waittime <<= 1;	/*lint !e703 */
			if (p->waittime > (time_t) CMaxwait)
				p->waittime = (time_t) CMaxwait;
			p->retrytime = p->waittime + currtime;
			cport.sin_family = AF_INET;
			cport.sin_port = p->port;
			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) {
				(void) shutdown (s, 2);	/* to make sure it doesn't linger around */
				close_s (s);	/* WG7J - 9207228 */
				continue;
			}
			p->fd = s;
			sprintf (buffer, "permlink: %s", inet_ntoa (p->addr));
			if (newproc (buffer, 2048, conv_incom, s, 0, NULL, 0) == NULLPROC) {
				(void) shutdown (s, 2);	/* blow it out of the water :-) */
				close_s (s);
			}
		}
		check_buffer_overload ();
	}
}
#endif



static int
ecmd_exists (char *cmdname)
{
struct extendedcmds *echk;

	echk = ecmds;
	while (echk && echk->name) {
		if (!stricmp (echk->name, cmdname))
			return 1;
		echk = echk->next;
	}
	return 0;
}



static void
clear_locks (void)
{
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 += usputs (p->fd, "");
}



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

	currtime = time (&currtime);


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



static void
send_user_change_msg (
char *name,
char *host,
time_t mytime,
int oldchannel,
int newchannel,
char *pers
) {
register struct convection *p;
time_t currtime;
register struct group *gold, *gnew;

	currtime = time (&currtime);

	gold = find_group (oldchannel);
	gnew = find_group (newchannel);
	if (gold && gold->logfile) {
		if (newchannel >= 0)
			fprintf (gold->logfile, theyswitched, stars,
				 name, newchannel, timestring (currtime));
		else
			fprintf (gold->logfile, theysigned, stars, name, ff_str, timestring (currtime));
	}
	if (gnew && gnew->logfile)
		fprintf (gnew->logfile, theysigned, stars, name, n_str, timestring (currtime));

	for (p = convections; p; p = p->next) {
		kwait (NULL);
		if (p->type == CT_USER && !p->via && !p->locked) {
			if ((newchannel == oldchannel) && (newchannel != -1)) {
				if (p->channel == newchannel) {
					if (pers && (*pers != '\0') && (strcmp (pers, "@")))
						p->xmitted += usprintf (p->fd, "*** (%s) %s@%s on channel %d set personal text:\n    %s\n", timestring (currtime), name, host, newchannel, pers);
					else
						p->xmitted += usprintf (p->fd, "*** (%s) %s@%s on channel %d removed personal text.\n", timestring (currtime), name, host, newchannel);
					p->locked = 1;
				}
			} else {
				if (p->channel == oldchannel) {
					convcolorchange (p, SYSCOLORS);
					if (newchannel >= 0)
						p->xmitted += usprintf (p->fd, theyswitched, stars,
							name, newchannel, timestring (currtime));
					else
						p->xmitted += usprintf (p->fd,
							theysigned, stars, name, ff_str, timestring (currtime));
					p->locked = 1;
				}
				if (p->channel == newchannel) {
					convcolorchange (p, SYSCOLORS);
					send_sounds (p);
					p->xmitted += usprintf (p->fd,
						theysigned, stars, name, n_str, timestring (currtime));
					p->locked = 1;
				}
			}
		}
		if (p->type == CT_HOST && !p->locked) {
			p->xmitted += usprintf (p->fd, huserstr, name, host, mytime, oldchannel, newchannel, (pers) ? pers : "");
			p->locked = 1;
		}
	}
#ifdef XSERVER
	xnotify (X_CONF);
#endif
}



static char *
formatline (char *prefix, const char *text)
{
register const char *f, *x;
register char *t;
register int l, lw;
static char buf[2 * LINELEN];

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

	for ( ; ; ) {
		while (isspace (uchar (*f))) {
#ifndef NOSPACES
			*t++ = *f;
			l++;
#endif
			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;
		}
#ifndef NOSPACES
		while (l < PREFIXLEN) {
#else
		do {
#endif
			*t++ = ' ';
			l++;
#ifndef NOSPACES
		}
#else
		} while (l < PREFIXLEN);
#endif
		while (lw--) {
			*t++ = *f++;
			l++;
		}
	}
}



static void
send_msg_to_user (const char *fromname, const char *toname, const char *text)
{
register struct convection *p;
char buff2[INBUFLEN];

	strncpy (buff2, text, INBUFLEN);
	rip (buff2);
	for (p = convections; p; p = p->next) {
		kwait (NULL);
		if (p->type == CT_USER && (!strcmpi (p->name, toname) || !strcmpi (p->nickname, toname)))	{
			if (p->via) {
				if (!p->via->locked) {
					p->via->xmitted += usprintf (p->via->fd,
						umsgstr, fromname, toname, buff2);
					p->via->locked = 1;
				}
			} else {
				if (!p->locked) {
					if (strcmp (fromname, conversd)) {
						char buffer[8 + (NAMELEN * 2)];
						time_t currtime;

						currtime = time (&currtime);
						convcolorchange (p, INFOCOLORS);
						sprintf (buffer, (TimeStamp) ? timestampfmt : nontimestampfmt, fromname, timestring (currtime));
						p->xmitted += usprintf (p->fd, fullstr, formatline (buffer, buff2));
					} else {
						convcolorchange (p, SYSCOLORS);
						p->xmitted += usprintf (p->fd, fullstrcr, buff2);
					}
					p->locked = 1;
				}
			}
		}
	}
}



static void
send_msg_to_channel (const char *fromname, int channel, const char *text)
{
char buffer[8 + (NAMELEN * 2)];
register struct convection *p;
register struct group *gp;
time_t currtime;
char buff2[INBUFLEN];

	strncpy (buff2, text, INBUFLEN);
	rip (buff2);

	currtime = time (&currtime);
	sprintf (buffer, (TimeStamp) ? timestampfmt2 : nontimestampfmt2, fromname, timestring (currtime));
	gp = find_group (channel);
	if (gp && gp->logfile)	{
		if (!strcmp (fromname, conversd))
			fprintf (gp->logfile, fullstrcr, buff2);
		else
			fprintf (gp->logfile, fullstr, formatline (buffer, buff2));
	}

	for (p = convections; p; p = p->next) {
		kwait (NULL);
		if (p->type == CT_USER && p->channel == channel)	{
			if (p->via) {	
				if (!p->via->locked) {
					if ((p->via->features & FEATURE_NICK) || !strchr (fromname, ':'))
						p->via->xmitted += usprintf (p->via->fd,
							cmsgstr, fromname, channel, buff2);
					else {
						char *cp2;
						char *ctmp = strdup (fromname);

						cp2 = strchr (fromname, ':');
						if (cp2)
							*cp2 = 0;
						p->via->xmitted += usprintf (p->via->fd,
							cmsgstr, ctmp, channel, buff2);
						free (ctmp);
					}
					p->via->locked = 1;
				}
			} else {
				if (!p->locked) {
					convcolorchange (p, TEXTCOLORS);
					if (!strcmp (fromname, conversd))
						p->xmitted += usprintf (p->fd, fullstrcr, buff2);
					else
						p->xmitted += usprintf (p->fd, fullstr, formatline (buffer, buff2));
					p->locked = 1;
					usflush (p->fd);
				}
			}
		}
	}
}



static void
send_invite_msg (
char *fromname,
char *toname,
int channel,
char *msg
) {
char buffer[80];
struct convection *p;
time_t currtime;
#ifdef MAILBOX
int i;
struct mbx *m = 0;
#endif

	currtime = time (&currtime);

#ifdef MAILBOX
	for (i = 0; i < NUMMBX; i++) {
		if ((m = Mbox[i]) != NULLMBX) {
			if (m->state == MBX_CMD && !stricmp (m->name, toname)) {
				usprintf (m->user, mbinvitetext, amessage, fromname, timestring (currtime), stars, channel, channel);
				if (msg[0])
					usprintf (m->user, msgtext, fromname, msg);
				usflush (m->user);
				clear_locks ();
				sprintf (buffer, responsetext, stars, toname, "BBS@");
				strcat (buffer, Hostname);
				send_msg_to_user (conversd, fromname, buffer);
				return;
			}
		}
	}
#endif

	/* check the current convers users */
	for (p = convections; p; p = p->next) {
		if (p->type == CT_USER && !strcmpi (p->name, toname)) {
			if (p->channel == channel) {
				clear_locks ();
				sprintf (buffer, alreadyon, stars, userstr, toname);
				send_msg_to_user (conversd, fromname, buffer);
				return;
			}
			if (!p->via && !p->locked) {
				convcolorchange (p, INFOCOLORS);
				p->paged = channel;
				p->xmitted += usprintf (p->fd, invitetext, amessage, fromname, \
					timestring (currtime), stars, channel);
				if (msg[0])
					p->xmitted += usprintf (p->fd, msgtext, fromname, msg);
				clear_locks ();
				sprintf (buffer, responsetext, stars, toname, Chostname);
				send_msg_to_user (conversd, fromname, buffer);
				return;
			}
			if (p->via && !p->via->locked) {
				p->via->xmitted += usprintf (p->via->fd, hinvi, fromname, toname, channel, msg);
				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, hinvi, fromname, toname, channel, msg);
	}
}



static void
getTXname (struct convection *cp, char *buf)
{
	if (!stricmp (cp->nickname, cp->name))
		strcpy (buf, cp->name);
	else
		sprintf (buf, "%s:%s", cp->name, cp->nickname);
}



static void
cq_command (struct convection *cp)
{
register struct convection *p;
char *s, tmpbuff[2048];
char buf[(NAMELEN * 2) + 2];

	s = getparamline (cp);
	getTXname (cp, buf);
	sprintf (tmpbuff, "*** %s@%s channel %d calling CQ %s", buf, cp->host, cp->channel, s);
	for (p = convections; p; p = p->next) {
		if (p != cp)
			send_msg_to_user ("conversd", p->name, tmpbuff);
	}
}



static void
time_command (struct convection *cp)
{
time_t currtime;
char buff[24];

	currtime = time (&currtime);
	sprintf (buff, "%s%s is %s\n", stars, tmstr, timestring (currtime));
	clear_locks ();
	send_msg_to_channel (conversd, cp->channel, buff);
}



static void
version_command (struct convection *cp)
{
	cp->xmitted += usprintf (cp->fd, "%s%s Conference Bridge\n", stars, Version);
}



static int
onchannel (int channel)
{
int cnt = 0;
struct convection *p;

	for (p = convections; p; p = p->next)
		if (p->type == CT_USER && channel == p->channel)
			cnt++;
	return (cnt);
}



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

	if (cp->net != -1) {
		int old = cp->channel;

		cp->channel = cp->net;
		nonet_command (cp);
		cp->channel = old;
	}
	switch (cp->type) {
		case CT_UNKNOWN:
			cp->type = CT_CLOSED;
			break;
		case CT_USER:
			cp->type = CT_CLOSED;
			convcolorchange (cp, SYSCOLORS);
			cp->xmitted += usprintf (cp->fd, exitingstr, conferencestr, Chostname);
			clear_locks ();
			send_user_change_msg (cp->name, cp->host, cp->time, cp->channel, -1, cp->data);
			save_personal (cp);
			break;
		case CT_HOST:
			cp->type = CT_CLOSED;
			for (pp = permlinks; pp; pp = pp->next)
				if (pp->convection == cp) {
					update_permlinks (pp->name, NULLCONNECTION);
					break;
				}
			for (p = convections; p; p = p->next)
				if (p->via == cp) {
					p->type = CT_CLOSED;
					clear_locks ();
					send_user_change_msg (p->name, p->host, p->time, p->channel, -1, p->data);
				}
			break;
		case CT_CLOSED:
		default:
			break;
	}
}



static void
mystatus (struct convection *cp, int old)
{
int now;
struct group *gp;

	now = onchannel (cp->channel);
	cp->xmitted += usprintf (cp->fd, nowchannel, stars, channelstr, cp->channel, now, (now > 1) ? "s" : empty);
	gp = find_group (cp->channel);
	if (gp != NULLGROUP) {
		convcolorchange (cp, SYSCOLORS);
		cp->xmitted += usprintf (cp->fd, welcome, stars, (gp->private) ? privatestr : empty,
				     (gp->private) ? " " : empty, gp->name);
		if (*gp->moderator)
			cp->xmitted += usprintf (cp->fd, welcome2, netcontrolstr, gp->moderator);
		cp->xmitted += usprintf (cp->fd, welcome3, cp->nickname);
	}
	send_user_change_msg (cp->name, cp->host, cp->time, old, cp->channel, cp->data);
}



static void
channel_command (struct convection *cp)
{
char *s;
int newchannel, oldchannel;

	s = getparam (cp);
	if (*s == '\0') {
		convcolorchange (cp, SYSCOLORS);
		cp->xmitted += usprintf (cp->fd, youare, stars, empty, cp->channel);
		return;
	}
	newchannel = atoi (s);
	/*	if(newchannel < 0 || newchannel > MAXCHANNEL) {		*/
	if (newchannel < 0) {
		convcolorchange (cp, SYSCOLORS);
		cp->xmitted += usprintf (cp->fd, cnumber, MAXCHANNEL);
		return;
	}
	if (newchannel == cp->channel) {
		convcolorchange (cp, SYSCOLORS);
		cp->xmitted += usprintf (cp->fd, youare, stars, "already ", cp->channel);
		return;
	}
	if (gatekeeper (cp, newchannel))
		return;
	oldchannel = cp->channel;
	cp->channel = newchannel;
	cp->nettime = time (&cp->nettime);
	cp->locked = 1;		/* for bump command	*/
	mystatus (cp, oldchannel);
}



static void
accept_command (struct convection *cp)
{
	if (cp->paged == -1) {
		convcolorchange (cp, SYSCOLORS);
		cp->xmitted += usprintf (cp->fd, "??? %s\n", channelstr);
	} else {
		sprintf (cp->ibuf, "/c %d", cp->paged);
		setparamptr (cp, &cp->ibuf[3]);
		channel_command (cp);
	}
}



static void
help_command (struct convection *cp)
{
int k;
char *cptr;
char const *file2open = 0;

	(void) sockblock (cp->fd, SOCK_BLOCK);
	cptr = getparam (cp);

	file2open = (tolower (*cptr) != 'n') ? ConfHlp : (tolower (cptr[2]) == 'w') ? ConfNews : NetControlHlp;
	convcolorchange (cp, INFOCOLORS);
	k = DisplayFile (file2open, cp->fd);
	if (k)
		cp->xmitted += k;
	else {
		convcolorchange (cp, SYSCOLORS);
		cp->xmitted += usprintf (cp->fd, noopenstr, file2open);
	}
	cp->xmitted += usprintf (cp->fd, trailer);
	(void) sockblock (cp->fd, SOCK_NOTXBLOCK);
}



static void
news_command (struct convection *cp)
{
	strcpy (cp->ibuf, "h news");
	setparamptr (cp, &cp->ibuf[2]);
	help_command (cp);
}



static void
invite_command (struct convection *cp)
{
struct group *gp;
char *toname, *chk, *cp2;

	toname = getparam (cp);
	chk = getparamline (cp);
	if (*chk)
		chk[51] = 0;

	if (*toname) {
		if ((cp2 = strchr (toname, '@')) != NULLCHAR)
			*cp2 = '\0';
		gp = find_group (cp->channel);
		if (gp && gp->private && cp->net != cp->channel) {
			convcolorchange (cp, SYSCOLORS);
			cp->xmitted += usprintf (cp->fd, noinvite, stars, privatestr, netcontrolstr);
		} else
			send_invite_msg (cp->name, toname, cp->channel, chk);
	}
}



static char *
ts4 (time_t seconds)
{
int days, hours, minutes;
static char buffer[50];
char buf[20];

	days = (int) (seconds / 86400);
	seconds -= days * 86400;
	hours = (int) (seconds / 3600);
	seconds -= hours * 3600;
	minutes = (int) (seconds / 60);
	seconds -= minutes * 60;
	buffer[0] = '\0';
	if (days) {
		sprintf (buf, "%d day%s, ", days, (days == 1) ? "" : "s");
		strncpy (buffer, buf, 50);
	}
	if (days + hours) {
		sprintf (buf, "%d hour%s, ", hours, (hours == 1) ? "" : "s");
		strcat (buffer, buf);
	}
	if (days + hours + minutes) {
		sprintf (buf, "%d minute%s, ", minutes, (minutes == 1) ? "" : "s");
		strcat (buffer, buf);
	}
	sprintf (buf, "%ld second%s.", seconds, (seconds == 1) ? "" : "s");
	strcat (buffer, buf);
	return buffer;
}



static char *
ts3 (time_t seconds, char *buffer)
{
	if (seconds < 100)
		sprintf (buffer, "%lds", seconds);
	else if (seconds < 6000)
		sprintf (buffer, "%ldm", seconds / 60);
	else if (seconds < 360000)
		sprintf (buffer, "%ldh", seconds / 3600);
	else
		sprintf (buffer, "%ldd", seconds / 86400);
	return buffer;
}



static int 
ShowConfLinks2 (int s, char *user, int full)
{
int num = 0;
struct convection *pc;
struct permlink *pp;
char buffer[100], tmp[20], tmp2[64], tmp3[64], tmp4[64];

	sprintf (buffer, "Host       State        Quality Software  Since%s",
		 (full) ? " NextTry Tries Queue    RX    TX" : "");
	if (user) {
		clear_locks ();
		send_msg_to_user (conversd, user, buffer);
	} else
		num += usprintf (s, "%s\n", buffer);
	for (pc = convections; pc; pc = pc->next) {
		kwait (NULL);
		strcpy (tmp2, "  ---  ");
		for (pp = permlinks; pp; pp = pp->next) {
			if (pp->convection == pc) {
				if (pp->txtime || pp->rxtime) {
					if (pp->rxtime == (time_t) -1)
						sprintf (tmp2, "  %3s  ", ts3 (pp->txtime, tmp3));
					else
						sprintf (tmp2, "%3s/%-3s", ts3 (pp->txtime, tmp3), ts3 (pp->rxtime, tmp4));
				}
				break;
			}
		}
		if (pc->type == CT_HOST) {
			sprintf (buffer,
				 (full) ?
				 "%-10.10s Connected    %7.7s %8.8s %s               %5d %4dK %4dK" :
				 "%-10.10s Connected    %7.7s %8.8s %s",
				 pc->name, tmp2, (pp) ? pp->rev : "",
				 timestring (pc->time),
				 socklen (pc->fd, 1),
				 (pc->received / 1024),
				 (pc->xmitted / 1024));
			if (user) {
				clear_locks ();
				send_msg_to_user (conversd, user, buffer);
			} else
				num += usprintf (s, "%s\n", buffer);
		}
	}

	for (pp = permlinks; pp; pp = pp->next) {
		kwait (NULL);
		if (!pp->convection || pp->convection->type != CT_HOST) {
			strcpy (tmp, timestring (pp->retrytime));
			sprintf (buffer,
				 (full) ?
			    "%-10.10s %-12.12s   ---   %8.8s %s %s  %5d\n" :
				 "%-10.10s %-12.12s   ---   %8.8s %s",
				 pp->name,
			     pp->convection ? "Connecting" : "Disconnected",
				 pp->rev, timestring (pp->statetime),
				 tmp,
				 pp->tries);
			if (user) {
				clear_locks ();
				send_msg_to_user (conversd, user, buffer);
			} else
				num += usprintf (s, "%s\n", buffer);
		}
	}
	return num;
}



int 
ShowConfLinks (int s, int full)
{
	return (ShowConfLinks2 (s, NULLCHAR, full));
}



static void
cstat_command (struct convection *cp)
{
	cp->xmitted += ShowConfLinks (cp->fd, 1);
	cp->xmitted += usprintf (cp->fd, "\n");
	cp->xmitted += ShowConfDest (cp->fd, "", cp->name);
	cp->xmitted += usprintf (cp->fd, "%s\n", stars);
}



static void
links_command (struct convection *cp)
{
char *host;
struct convection *p;

	host = getparam (cp);
	if (*host && stricmp (host, Chostname)) {
		for (p = convections; p; p = p->next)
			if (p->type == CT_HOST)
				p->xmitted += usprintf (p->fd, "/\377\200LINK %s %s\n", cp->name, host);
	} else {
		cp->xmitted += ShowConfLinks (cp->fd, 1);
		cp->xmitted += usprintf (cp->fd, "***\n");
	}
	return;
}



static void
h_link_command (struct convection *cp)
{
char *name, *host, buffer[100];

	name = getparam (cp);
	host = getparam (cp);
	if (stricmp (host, Chostname))
		return;
	sprintf (buffer, "*** Links at %s", Chostname);
	clear_locks ();
	send_msg_to_user (conversd, name, buffer);
	(void) ShowConfLinks2 (0, name, 1);
	clear_locks ();
	send_msg_to_user (conversd, name, "***");
}



static void
msg_command (struct convection *cp)
{
char *toname, *text;
register struct convection *p;
char buf[(NAMELEN * 2) + 2];

	toname = getparam (cp);
	text = getparamline (cp);

	if (!*text)
		return;
	for (p = convections; p; p = p->next)
		if (p->type == CT_USER && (!strcmpi (p->name, toname) || !strcmpi (p->nickname, toname))) {
			getTXname (cp, buf);
			send_msg_to_user (buf, toname, text);
			return;
		}
	convcolorchange (cp, SYSCOLORS);
	cp->xmitted += usprintf (cp->fd, nosuchuser, stars, toname);
}



static void
announce_new_user (struct convection *cp)
{
char dummy[80];
register struct group *gp;
int numgroups;

	if (CountConfUsers () > 1) {
		cp->xmitted += usprintf (cp->fd, online, stars, CountConfUsers ());
		if ((numgroups = CountConfGroups ()) != 0)
			cp->xmitted += usprintf (cp->fd, groupsavail, stars, (numgroups > 1) ? "are" : "is", numgroups, (numgroups > 1) ? "s" : empty);
	}
	sprintf (dummy, newuserstr, stars, cp->name, cp->channel);
	for (gp = groups; gp; gp = gp->next)
		if (gp->channel != cp->channel)
			send_msg_to_user (conversd, gp->moderator, dummy);
	send_user_change_msg (cp->name, cp->host, cp->time, -1, cp->channel, cp->data);
}



static void
color_command (struct convection *cp)
{
char *dummy;

	dummy = getparam (cp);
	if (*dummy) {
		if (!stricmp (dummy, "on"))
			cp->flags |= USE_COLOR;
		if (!stricmp (dummy, "off"))
			cp->flags &= ~USE_COLOR;
	}
	cp->colorset[0] = '\0';
	convcolorchange (cp, SYSCOLORS);
	cp->xmitted += usprintf (cp->fd, colorstatus, stars, (cp->flags & USE_COLOR) ? n_str : ff_str);
	cp->flags |= CHANGED_INFO;
}



/* save the personal information for this user */
static void 
save_personal (struct convection *cp)
{
FILE *old, *new;
char *newname;
int found = 0;

	if ((!cp->nickname[0] && !cp->data) || !(cp->flags & CHANGED_INFO))
		return;
	if ((old = fopen (ConfInfo, UPDATE_TEXT)) == NULL)
		if ((old = fopen (ConfInfo, CREATE_TEXT)) == NULL)
			return;
	rewind (old);
	newname = (char *) mallocw (strlen (ConfInfo) + 5);
	sprintf (newname, "%s.new", ConfInfo);
	if ((new = fopen (newname, CREATE_TEXT)) == NULLFILE) {
		(void) fclose (old);
		free (newname);
		return;
	}
	while (fgets (cp->ibuf, LINELEN, old) != NULLCHAR) {
		kwait (NULL);
		rip (cp->ibuf);
		if (!stricmp (cp->ibuf, cp->name)) {
			found = 1;
			for (;;) {
				if (fgets (cp->ibuf, LINELEN, old) == NULLCHAR)
					break;
				if (cp->ibuf[0] != ' ') {
					/*					fputs (cp->ibuf, new);		*/
					break;
				}
			}
			break;
		}
		fprintf (new, fullstrcr, cp->ibuf);
	}
	fprintf (new, fullstrcr, cp->name);
	if (*cp->nickname)
		fprintf (new, " nickname %s\n", cp->nickname);
	if (cp->data)
		fprintf (new, " personal %s\n", cp->data);
	if (cp->flags & USE_COLOR)
		fprintf (new, " color ON\n");
	if (found)		/* add rest of file */
		do {
			fputs (cp->ibuf, new);
		} while (fgets (cp->ibuf, LINELEN, old) != NULLCHAR);
	(void) fclose (new);
	(void) fclose (old);
	unlink (ConfInfo);
	(void) rename (newname, ConfInfo);
	free (newname);
	return;
}



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

	if ((fp = fopen (ConfInfo, READ_TEXT)) == NULL)
		return;
	while (fgets (cp->ibuf, LINELEN, fp) != NULL) {
		kwait (NULL);
		if (*(cp->ibuf) == ' ')
			continue;
		rip (cp->ibuf);
		if (stricmp (cp->name, cp->ibuf))
			continue;
		if (fgets (cp->ibuf, LINELEN, fp) != NULL) {
			if (!strnicmp (cp->ibuf, " nick", 5)) {
				/* Found nickname data ! */
				setparamptr (cp, &cp->ibuf[1]);
				(void) getparam (cp);
				nickname_command (cp);
				(void) fgets (cp->ibuf, LINELEN, fp);
			}
			if (!strnicmp (cp->ibuf, " pers", 5)) {
				/* Found personal data ! */
				setparamptr (cp, &cp->ibuf[1]);
				(void) getparam (cp);
				personal_command (cp);
				(void) fgets (cp->ibuf, LINELEN, fp);
			}
			if (!strnicmp (cp->ibuf, " color", 6)) {
				/* Found color data ! */
				setparamptr (cp, &cp->ibuf[1]);
				(void) getparam (cp);
				color_command (cp);
			}
			cp->xmitted += usprintf (cp->fd, welcomeback, stars, cp->nickname);
		}
	}
	(void) fclose (fp);
	return;
}



static void
name_command (struct convection *cp)
{
int newchannel;
char *dummy, *name;

	name = getparam (cp);
	if (!*name)
		return;

	dummy = getparam (cp);
	if (*dummy)
		newchannel = atoi (dummy);
	else
		newchannel = CChannel;
	
	strncpy (cp->name, name, NAMELEN);
	strncpy (cp->nickname, name, NAMELEN);
	(void) strlwr (cp->name);

	strncpy (cp->host, Chostname, NAMELEN);
	cp->type = CT_USER;
	cp->channel = (newchannel == -1) ? CChannel : newchannel;

	announce_new_user (cp);
	set_personal (cp);
	cp->flags &= ~CHANGED_INFO;
	mystatus (cp, (int) -1);
}



#ifdef LZW
/* Set or show the status of the 'compressed' flag - KO4KS */
static void
compressed_command (struct convection *cp)
{
char *cp2;
struct usock *up;

	cp2 = getparam (cp);
	if (*cp2) {
		if (*cp2 == 'o' || *cp2 == 'O') {	/* There is an argument */
			if (cp2[1] == 'f' || cp2[1] == 'F')	/* Turn it off */
				cp->flags &= ~USE_LZW;
			else
				cp->flags |= USE_LZW;
			up = itop (cp->fd);
			if ((cp->flags & USE_LZW) || (up->zout != NULLLZW))
				togglelzw (cp->fd, cp->flags & USE_LZW);

		}
	}
	convcolorchange (cp, SYSCOLORS);
	usprintf (cp->fd, "%sStream compression o%s\n", stars, (cp->flags & USE_LZW) ? n_str : ff_str);
	return;
}

#endif



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

	cp2 = getparam (cp);
	if (*cp2) {
		if (*cp2 == 'o' || *cp2 == 'O') {	/* There is an argument */
			if (cp2[1] == 'f' || cp2[1] == 'F')	/* Turn it off */
				cp->flags &= ~USE_SOUND;
			else
				cp->flags |= USE_SOUND;
		}
	}
	convcolorchange (cp, SYSCOLORS);
	usprintf (cp->fd, "%sSounds o%s\n", stars, (cp->flags & USE_SOUND) ? n_str : ff_str);
	return;
}



/* Send updated net data */
static void
update_net_data (struct group *gp)
{
struct convection *p;
time_t currtime;

	currtime = time (&currtime);
	/* update all links */
	for (p = convections; p; p = p->next) {
		kwait (NULL);
		if (p->type == CT_HOST) {
			p->xmitted += usprintf (p->fd, ndatstr,
						gp->channel, gp->private, gp->logged, gp->nextq, gp->totalq,
				     gp->name, gp->moderator, gp->password);
			p->xmitted += usprintf (p->fd, topicstr,
						gp->moderator, Chostname, currtime, gp->channel, gp->name);
		}
	}
}



/* Send updated personal data, nickname */
static void
update_user_data (struct convection *cp, int personal)
{
struct convection *p;

	/* update all links */
	for (p = convections; p; p = p->next) {
		kwait (NULL);
		if (p->type == CT_HOST) {
			if (personal)
				send_user_change_msg (cp->name, cp->host, cp->time, cp->channel, cp->channel, cp->data);
			if (p->features & FEATURE_NICK)
				p->xmitted += usprintf (p->fd, uaddstr,
							cp->name, cp->host, cp->nickname, cp->net, (cp->data) ? cp->data : "~~");
		}
	}
}



static int
ShowConfDest (int s, const char *dest, const char *name)
{
char tmp[64];
struct destination *d;
int i = 1;
int xmitted = 0;

	if (*dest == '\0') {
		for (d = destinations; d; d = d->next) {
			if (d->rtt) {
				xmitted += usprintf (s, "%-9.9s (%8.8s) %3s", d->name, d->rev, ts3 (d->rtt, tmp));
				if ((i == 3) || !(d->next)) {
					xmitted += usprintf (s, "\n");
					i = 0;
				} else
					xmitted += usprintf (s, "   ");
				i++;
			}
		}
		if ((i != 1))
			xmitted += usprintf (s, "\n");
	} else {
		for (d = destinations; d; d = d->next) {
			if (!stricmp (d->name, dest)) {
				clear_locks ();
				xmitted = usprintf (s, "*** route: %s -> %s -> %s (%ld)\n", Chostname, d->link->name, dest, d->rtt);
				if (stricmp (dest, d->link->name))
					xmitted += usprintf (d->link->convection->fd, "/\377\200ROUT %s %s 99\n", dest, name);
				break;
			}
		}
		if (d == NULLDESTINATION)
			xmitted += usprintf (s, "*** no route to %s\n", dest);
	}
	return xmitted;
}



static void 
hosts_command (struct convection *cp)
{
char *dest;

	dest = getparam (cp);
	cp->xmitted += ShowConfDest (cp->fd, dest, cp->name);
	if (!*dest)
		cp->xmitted += usprintf (cp->fd, "%s\n", stars);
}



/* Set some personal data, like name and qth - WG7J */
static void
personal_command (struct convection *cp)
{
char *cptr;

	cptr = getparamline (cp);
	if (*cptr) {
		if (cp->data)
			free (cp->data);
		rip (cptr);	/* get rid of ending '\n' */
		cp->data = strdup (cptr);
		update_user_data (cp, 1);
		cp->flags |= CHANGED_INFO;
	}
	convcolorchange (cp, SYSCOLORS);
	cp->xmitted += usprintf (cp->fd, personalset, stars, (cp->data) ? cp->data : empty);
}



static void
nickname_command (struct convection *cp)
{
int nonick = 0, err = 0;

	convcolorchange (cp, SYSCOLORS);
	if (tolower (cp->ibuf[2]) == 'o') {
		nonick = 1;
		strncpy (cp->nickname, cp->name, NAMELEN);
	} else {
		char *new;
		new = getparam (cp);
		if (*new)
			if (strcmp (new, systemstr))
				strncpy (cp->nickname, new, NAMELEN);
			else {
				cp->xmitted += usprintf (cp->fd, nicknamestr, stars, nicknmstr, "can't be ", new);
				err = 1;
			}
	}
	cp->xmitted += usprintf (cp->fd, nicknamestr, stars, nicknmstr, (nonick) ? "re" : empty, cp->nickname);
	if (!err)
		update_user_data (cp, 0);
	cp->flags |= CHANGED_INFO;
}



static void
password_command (struct convection *cp)
{
char *new;

	convcolorchange (cp, SYSCOLORS);
	if (tolower (cp->ibuf[2]) == 'o') {
		cp->password[0] = '\0';
		cp->xmitted += usprintf (cp->fd, "%s %s!\n", passwordstr, clearedstr);
		return;
	}
	new = getparam (cp);
	if (*new) {
		strncpy (cp->password, new, NAMELEN);
		cp->xmitted += usprintf (cp->fd, "%s%s is set!\n", stars, passwordstr);
	} else {
		if (cp->password[0])
			cp->xmitted += usprintf (cp->fd, "%s%s is '%s'.\n", stars,
				passwordstr, cp->password);
		else
			cp->xmitted += usprintf (cp->fd, "%sNo %s in use.\n", stars, passwordstr);
	}
}



static int
isrosedigit (char c)
{
int retval;

	retval = isdigit (c);
	if (!retval)
		switch (tolower (c)) {
			case 'l':
			case 'o':
			case 'i':
				retval = 1;
				break;
			default:
				break;
		}
	return retval;
}



static char *
getVia (char *call)
{
char tmp[AXBUF];
static char roseaddr[14];
int i, k;
register struct ax_route *axr;

	roseaddr[0] = 0;
	for (axr = Ax_routes; axr != NULLAXR; axr = axr->next) {
		(void) pax25 (tmp, axr->target);
		if (!strnicmp (call, tmp, strlen (call))) {
			for (i = 0; i < axr->ndigis; i++) {
				(void) pax25 (tmp, axr->digis[i]);
				if (strlen (tmp) != 6)
					continue;
				for (k = 0; k < 6; k++) {
					if (!isrosedigit (tmp[k]))
						break;
				}
				if (k != 6)
					continue;
				sprintf (roseaddr, "ROSE:%-6.6s", tmp);
				return (roseaddr);
			}
			break;
		}
	}
	return (roseaddr);
}



/* this is called from the finger-daemon when 'conf' is fingered - WG7J */
int 
ShowConfUsers (int s, char *athost)
{
struct convection *p;
int cnt;
int next_channel, channel = 0;

	cnt = usprintf (s, headerstr);
	next_channel = 0;
	clear_locks ();
	do {
		channel = next_channel;
		next_channel = 0;
		for (p = convections; p; p = p->next) {
			kwait (NULL);
			if (p->type == CT_USER && !p->locked) {
				if (athost && strnicmp (athost, p->host, strlen (athost)))
					continue;
				if (channel < p->channel) {
					if (next_channel) {
						if (p->channel < next_channel)
							next_channel = p->channel;
					} else
						next_channel = p->channel;
				} else if (channel == p->channel) {
					cnt += usprintf (s, userdata, p->name, p->host, (p->via) ? p->via->name : getVia (p->name),
							 p->channel, timestring (p->time), (p->data) ? p->data : empty);
					usflush (s);	/* might not be neccesary */
					p->locked = 1;
				}
			}
		}
	} while (next_channel > channel);
	return (cnt);
}



int 
CountConfUsers (void)
{
struct convection *p;
int cnt = 0;

	for (p = convections; p; p = p->next)
		if (p->type == CT_USER)
			cnt++;
	return (cnt);
}



static int 
CountConfGroups (void)
{
int cnt = 0;
register struct group *gp;

	for (gp = groups; gp; gp = gp->next)
		cnt++;
	return (cnt);
}



#ifdef ALLSERV
static void
quote_command (struct convection *cp OPTIONAL)
{
char *p;

	p = getquote ();
	if (p) {
		clear_locks ();
		send_msg_to_channel (conversd, cp->channel, quotebanner);
		clear_locks ();
		send_msg_to_channel (conversd, cp->channel, p);
		clear_locks ();
		send_msg_to_channel (conversd, cp->channel, trailer);
		free (p);
	}
}
#endif



static void
list_command (struct convection *cp)
{
char buffer[80];
struct convection *p;
int channel;
int next_channel, header;
struct group *gp;
char buf[(NAMELEN * 2) + 2];

	cp->xmitted += usprintf (cp->fd, "%s  %ss\n", channelstr, userstr);
	next_channel = 0;
	clear_locks ();
	do {
		channel = next_channel;
		next_channel = 0;
		header = 0;
		for (p = convections; p; p = p->next) {
			kwait (NULL);
			if (p->type == CT_USER && !p->locked) {
				if (channel < p->channel) {
					if (next_channel) {
						if (p->channel < next_channel)
							next_channel = p->channel;
					} else
						next_channel = p->channel;
				} else if (channel == p->channel) {
					if (!header) {
						header = 1;
						sprintf (buffer, "%7d ", channel);
						if ((gp = find_group (channel)) != NULLGROUP) {
							sprintf (&buffer[8], "       %s", gp->name);
							cp->xmitted += usprintf (cp->fd, fullstrcr, buffer);
							strcpy (buffer, "        ");
						}
					}
					strcat (buffer, " ");
					if (strlen (buffer) > 65) {
						cp->xmitted += usprintf (cp->fd, fullstrcr, buffer);
						strcpy (buffer, "         ");
					}
					getTXname (p, buf);
					strcat (buffer, buf);
					p->locked = 1;
				}
			}
		}
		if (header)
			cp->xmitted += usprintf (cp->fd, fullstrcr, buffer);
	} while (next_channel > channel);
}



static void
who_command (struct convection *cp)
{
char *buffer, buf[(NAMELEN * 2) + 2];
int next_channel, channel = 0, quick = 0, realname = 0, here = 0;
struct convection *p;
struct group *gp;
char *whois;
char *athost = NULLCHAR;
#ifdef MAILBOX
int i;
struct mbx *m = 0;
#endif

	convcolorchange (cp, INFOCOLORS);
	buffer = getparam (cp);
	whois = getparam (cp);
	switch (tolower (buffer[0])) {
		case 'q':
			quick = 1;
			break;
		case 'n':
			quick = 0;
			break;
		case 'r':
			realname = 1;
			break;
		case '@':
			athost = whois;
			break;
		case '*':
			here = 1;
			channel = cp->channel;
			break;
		default:
			if (buffer[0] == '0') {
				here = 1;
				channel = 0;
			} else if ((channel = atoi (buffer)) != 0)
				here = 1;
			else
				quick = 1;
	}

	if (quick)
		list_command (cp);
	else if (here) {
		cp->xmitted += usprintf (cp->fd, "%s  %ss\n", channelstr, userstr);
		sprintf (buffer, "%7d ", channel);
		if ((gp = find_group (channel)) != NULLGROUP) {
			sprintf (&buffer[8], "       %s", gp->name);
			cp->xmitted += usprintf (cp->fd, fullstrcr, buffer);
			strcpy (buffer, "        ");
		}
		for (p = convections; p; p = p->next) {
			kwait (NULL);
			if (p->type == CT_USER && p->channel == channel) {
				strcat (buffer, " ");
				if (strlen (buffer) > 65) {
					cp->xmitted += usprintf (cp->fd, fullstrcr, buffer);
					strcpy (buffer, "         ");
				}
				getTXname (p, buf);
				strcat (buffer, buf);
			}
		}
		cp->xmitted += usprintf (cp->fd, fullstrcr, buffer);
		quick = 1;
	} else if (realname) {
		cp->xmitted += usprintf (cp->fd, whobanner, userstr, nicknmstr, channelstr);
		next_channel = 0;
		clear_locks ();
		do {
			channel = next_channel;
			next_channel = 0;
			for (p = convections; p; p = p->next) {
				kwait (NULL);
				if (p->type == CT_USER && !p->locked) {
					if (channel < p->channel) {
						if (next_channel) {
							if (p->channel < next_channel)
								next_channel = p->channel;
						} else
							next_channel = p->channel;
					} else {
						if (!*whois || !strnicmp (whois, p->nickname, strlen (whois)) || !strnicmp (whois, p->name, strlen (whois)))
							cp->xmitted += usprintf (cp->fd, "%-16.16s %-16.16s %7d %s\n",
										 p->name, (!stricmp (p->name, p->nickname)) ? "" : p->nickname, p->channel, (p->data) ? p->data : empty);
						p->locked = 1;
					}
				}
			}
		} while (next_channel > channel);
	} else
		cp->xmitted += ShowConfUsers (cp->fd, athost);

#ifdef MAILBOX
	if (!realname) {
		cp->xmitted += usprintf (cp->fd, "\nPBBS/NODE %ss\n", userstr);
		for (i = 0; i < NUMMBX; i++)
			if ((m = Mbox[i]) != NULLMBX) {
				realname = 1;	/* we found one! */
				if (quick)
					cp->xmitted += usprintf (cp->fd, " BBS %-16s (%s)\n", m->name, displayMBstatus (m->state, 0));
				else
					cp->xmitted += usprintf (cp->fd, "%-16s BBS@%-32s (%s)\n", m->name, Hostname, displayMBstatus (m->state, 0));
			}
		if (!realname)
			cp->xmitted += usprintf (cp->fd, "(none)\n");
	}
#endif
	cp->xmitted += usprintf (cp->fd, trailer);
}



static void
imsg_command (struct convection *cp)
{
char *toname, *text;
struct convection *p, *q;
char buf[(NAMELEN * 2) + 2];

	toname = getparam (cp);
	text = getparamline (cp);
	if (!*text)
		return;

	for (p = convections; p; p = p->next) {
		if (p->type == CT_USER && p->channel == cp->channel && stricmp (p->name, toname) != 0 && stricmp (p->nickname, toname) != 0) {
			getTXname (cp, buf);
			send_msg_to_user (buf, p->name, text);
			q = p->next;
			while (q) {
				if (!strcmp (p->name, q->name))
					q->locked = 1;
				q = q->next;
			}
		}
		if (p->via)
			p->via->locked = 0;
	}
}



static void
me_command (struct convection *cp)
{
char *text;
char buffer[LINELEN + (NAMELEN * 2) + 7];

	text = getparamline(cp);
	if (*text) {
		cp->locked = 1;
		sprintf (buffer, "%s%s@%s %s", stars, cp->name, cp->host, text);
		send_msg_to_channel ("conversd", cp->channel, buffer);
	}
}



static void
realname_command (struct convection *cp)
{
char name[LINELEN];

	strncpy (name, getparam (cp), LINELEN);
	sprintf (cp->ibuf, "/w r %s", name);
	setparamptr (cp, &cp->ibuf[3]);
	who_command (cp);
}



static void
h_dest_command (struct convection *cp)
{
char *name, *rev;
int rtt;
struct permlink *l;

	name = getparam (cp);
	rev = getparam (cp);
	if (*rev)
		return;
	rtt = atoi (rev);
	rev = getparam (cp);
	
	if (!stricmp (name, Chostname))
		return;

	for (l = permlinks; l; l = l->next) {
		if (l->convection && (l->convection == cp))
			break;
	}
	if (l)
		update_destinations (l, name, rtt, rev);
}



static void 
update_destinations (struct permlink *p, char *name, long rtt, const char *rev)
{
struct permlink *l;
struct destination *d, *d1;
char buffer[256];

	for (d = destinations; d; d = d->next)
		if (!stricmp (d->name, name))
			break;

	if (!d) {
		d = (struct destination *) mallocw (sizeof (struct destination));

		if (d) {
			d->last_sent_rtt = 0;
			d->link = p;
			strncpy (d->name, name, NAMELEN);
			strncpy (d->rev, rev, NAMELEN);
			if (!destinations || (!destinations->name) || (strcmp (destinations->name, name) > 0)) {
				d->next = destinations;
				destinations = d;
			} else {
				d1 = destinations;
				while (d1->next) {
					if (stricmp (d1->next->name, name) > 0) {
						d->next = d1->next;
						d1->next = d;
						break;
					}
					d1 = d1->next;
				}
				if (!d1->next) {
					d->next = d1->next;
					d1->next = d;
				}
			}
		}
	} else {
		strncpy (d->rev, rev, NAMELEN);
		d->link = p;
	}
	if (!d)
		return;

	d->rtt = rtt;
	if (abs (rtt - d->last_sent_rtt) > (d->last_sent_rtt / 8)) {
		for (l = permlinks; l; l = l->next) {
			buffer[0] = 0;
			if ((l->convection) && (l->convection->type == CT_HOST)) {
				if (rtt)
					sprintf (buffer, "/\377\200DEST %s %ld %s\n", name, rtt + (l->txtime + l->rxtime) / 2L, rev);
				else
					sprintf (buffer, "/\377\200DEST %s 0\n", name);
			}
			if (*buffer && strcmp (d->link->name, l->name)) {
				l->convection->xmitted += usprintf (l->convection->fd, buffer);
				d->last_sent_rtt = rtt;
			}
		}
	}
}



static void 
h_rout_command (struct convection *cp)
{
char *dest, *user;
struct destination *d;
char buffer[2048];
int ttl;

	dest = getparam (cp);
	user = getparam (cp);
	ttl = atoi (getparam (cp));
	clear_locks ();
	for (d = destinations; d; d = d->next) {
		if (!strcmp (d->name, dest)) {
			sprintf (buffer, "*** route: %s -> %s -> %s (%ld)", Chostname, d->link->name, dest, d->rtt);
			send_msg_to_user ("conversd", user, buffer);
			if (ttl && strcmp (d->link->name, dest)) {
				ttl--;
				d->link->convection->xmitted += usprintf (d->link->convection->fd, "/\377\200ROUT %s %s %d\n", dest, user, ttl);
			}
		}
	}
}



static void 
h_ping_command (struct convection *cp)
{
register struct permlink *pp;

	for (pp = permlinks; pp; pp = pp->next)
		if (pp->convection == cp) {
			cp->xmitted += usprintf (cp->fd, "/\377\200PONG %ld\n", pp->txtime);
			break;
		}
}



static void 
h_pong_command (struct convection *cp)
{
register struct permlink *l;
time_t currtime;
long pongtime;

	currtime = time (&currtime);
	pongtime = atol (getparam (cp));
	for (l = permlinks; l; l = l->next)
		if (l->convection == cp) {
			l->rxtime = pongtime;
			l->txtime = max (currtime - l->testwaittime, 1);
			if (l->rxtime == 0)
				l->rxtime = l->txtime;
			if ((abs ((int) l->rxtime) > 20001) || (abs ((int) l->txtime) > 20001)) {
				l->rxtime = 0;
				l->txtime = 0;
			}
			l->testnexttime = l->testwaittime + max (min (60 * (l->txtime), 7200), 120);
			/* I hacked this because of it's nasty behave - rewrite of the whole stuff is in progress */
			update_destinations (l, l->name,
				(long) ((l->rxtime == (time_t) -1) ? l->txtime : (l->rxtime + l->txtime) / 2L), l->rev);
			break;
		}
}



static void
h_cmsg_command (struct convection *cp)
{
char *text, *cptr;
int channel;
char *name;

	name = getparam (cp);
	channel = atoi (getparam (cp));
	text = getparamline (cp);
	if (*text == '|') {
		text++;		/* skip the '|' */
		cptr = strrchr (text, '|');
		if (cptr)
			*cptr = 0;
	} else if (*(text - 1) == ' ') {
		do {
			text--;
		} while (*(text - 1) == ' ');
		text++;
	}
	/*	text[strlen(text) - 1] = 0;	*/
	if (isprint (*text))
		send_msg_to_channel (name, channel, text);
}



/* Return 1 if the host is to be allowed, or 0 if refused - WG7J */
static 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 */
		if (getpeername (s, (char *) &fsocket, &i) != -1)	{
			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 (struct convection *cp)
{
char *name, *rev, *features;
register struct convection *p;
register struct permlink *pp;
struct destination *d;

	if (!Allow_host (cp->fd)) {
		bye_command (cp);
		return;
	}
	name = getparam (cp);
	rev = getparam (cp);
	features = getparam (cp);
	
	(void) strlwr (rev);
	if (name[0] == '\0') {
		bye_command (cp);
		return;
	}
	if (strchr (features, 'a'))
		cp->features |= FEATURE_AWAY;
	if (strchr (features, 'd'))
		cp->features |= FEATURE_FWD;
	if (strchr (features, 'm'))
		cp->features |= FEATURE_MODES;
	if (strchr (features, 'p'))
		cp->features |= FEATURE_LINK;
	if (strchr (features, 'u'))
		cp->features |= FEATURE_UDAT;
	if (strchr (features, 'n'))
		cp->features |= FEATURE_NICK;
	/* #define DEBUG */
#ifdef DEBUG
	tcmdprintf ("Features '%s': %s%s%s%s%s%s\n", name,
		(cp->features & FEATURE_AWAY) ? "a" : "",
		(cp->features & FEATURE_FWD) ? "d" : "",
		(cp->features & FEATURE_MODES) ? "m" : "",
		(cp->features & FEATURE_LINK) ? "p" : "",
		(cp->features & FEATURE_UDAT) ? "u" : "",
		(cp->features & FEATURE_NICK) ? "n" : "");
#endif
	for (p = convections; p; p = p->next)
		if (!strcmp (p->name, name)) {
			bye_command (p);
			return;
		}
	for (pp = permlinks; pp; pp = pp->next) {
		if (!stricmp (pp->name, name) && pp->convection && pp->convection != cp) {
			bye_command ((strcmp (Chostname, name) < 0) ? pp->convection : cp);
			return;
		}
		if (!stricmp (pp->name, name) && pp->convection && pp->convection == cp)
			strncpy (pp->rev, rev, NAMELEN);
		update_destinations (pp, name, 0, rev);
	}
	if (cp->type != CT_UNKNOWN)
		return;
	cp->type = CT_HOST;
	cp->maxq = HMaxQ;
	strncpy (cp->name, name, NAMELEN);	/* already allocated */
	update_permlinks (name, cp);
	cp->xmitted += usprintf (cp->fd, hhoststr, Chostname, shortversion, myfeatures);
	for (p = convections; p; p = p->next)
		if (p->type == CT_USER) {
			cp->xmitted += usprintf (cp->fd, huserstr,
						 p->name, p->host, p->time, -1, p->channel, (p->data) ? p->data : "");
			cp->xmitted += usprintf (cp->fd, uaddstr,
						 p->name, p->host, p->nickname, p->net, (p->data) ? p->data : "~~");
		}
	for (d = destinations; d; d = d->next)
		if (d->rtt)
			cp->xmitted += usprintf (cp->fd, "/\377\200DEST %s %ld %s\n", d->name, d->rtt + 99, d->rev);
}



static void
h_invi_command (struct convection *cp)
{
char *fromname, *toname, *msg;
int channel;

	fromname = getparam (cp);
	toname = getparam (cp);
	channel = atoi (getparam (cp));
	msg = getparamline (cp);
	send_invite_msg (fromname, toname, channel, msg);
}



static void
h_loop_command (struct convection *cp)
{
char *host;

	host = getparam (cp);
	log (cp->fd, "conversd rx: LOOP %s", host);
	bye_command (cp);
}



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

	/* do a validity check first */
	name = getparam (cp);
	host = getparam (cp);
	nickname = getparam (cp);
	channel = getparam (cp);
	data = getparam (cp);
	if (!*name || !*host || !*nickname || !*channel)
		return;

	if ((*data == '~' && data[1] == '~') || *data == '@')
		*data = 0;
	else
		data[strlen (data) - 1] = 0;
	/* everything seems fine, now find user ! */
	for (p = convections; p; p = p->next)
		if (!strcmp (p->name, name) && !strcmp (p->host, host)) {
			strncpy (p->nickname, nickname, NAMELEN);
			p->net = atoi (channel);
			if (strlen (data)) {
				if (p->data)
					free (p->data);
				p->data = strdup (data);
			}
			break;
		}
}



static void
initialusers (int channel)
{
register struct convection *p;

	for (p = convections; p; p = p->next)
		if (p->type == CT_USER && p->channel == channel)
			p->nettime = time (&p->nettime);
}



/* this command simply passes on any host requests that we don't understand,
   since our neighbors MIGHT understand them */

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

	for (p = convections; p; p = p->next) {
		if (p->type == CT_HOST && p != cp)
			cp->xmitted += usprintf (p->fd, cp->ibuf);
	}
}



/* Command to take channel topic across a link */
static void
h_topi_command (struct convection *cp)
{
char *name;
int channel;

	(void) getparam (cp);
	(void) getparam (cp);
	(void) getparam (cp);
	channel = atoi (getparam (cp));
	name = getparamline (cp);
	rip (name);
	if (strlen (name) > TOPICLEN) {
		name[TOPICLEN] = 0;
		log (-1, "Truncated a long convers topic name");
	}
	name = strdup (name);
	sprintf (cp->ibuf, ndatstr, channel, 0, 0, 0, 0, name, "", "");
	free (name);
	setparamptr (cp, cp->ibuf);
	(void) getparam (cp);
	h_ndat_command (cp);
}



/* Command to take net's data across a link */
static void
h_ndat_command (struct convection *cp)
{
char *name, *moderator, *password, *cptr;
struct group *p;
int channel, private, logged, nextq, totalq, madenew = 0;

	channel = atoi (getparam (cp));
	private = atoi (getparam (cp));
	logged = atoi (getparam (cp));
	nextq = atoi (getparam (cp));
	totalq = atoi (getparam (cp));

	if (!channel && !ConvNet0)
		return;
	cptr = getparamline (cp);
	name = cptr;
	if ((moderator = strchr (name, '|')) == NULLCHAR)
		return;
	*moderator++ = '\0';
	if ((password = strchr (moderator, '|')) == NULLCHAR)
		return;
	*password++ = '\0';
	if ((cptr = strchr (password, '|')) == NULLCHAR)
		return;
	*cptr = '\0';
	/* everything seems fine, now find net ! */
	p = find_group (channel);
	if (p == NULLGROUP) {
		p = (struct group *) callocw (1, sizeof (struct group));

		kmutex_lock (&Group_mutex);
		p->next = groups;
		groups = p;
		kmutex_unlock (&Group_mutex);
		p->channel = channel;
		madenew = 1;
		initialusers (channel);
	}
	p->private = (char) private;
	p->logged = (char) logged;
	p->nextq = (short) nextq;
	p->totalq = (short) totalq;
	strncpy (p->name, name, TOPICLEN);
	strncpy (p->moderator, moderator, NAMELEN);
	strncpy (p->password, password, NAMELEN);
	if (private == -1) {	/* deleting net */
		cp->channel = channel;
		nonetoverride = 1;
		nonet_command (cp);
		madenew = 0;
	}
	if (madenew) {
		char buf[100];

		sprintf (buf, activenetstr, stars, p->name);
		send_msg_to_channel (conversd, p->channel, buf);
	}
}



/* Command to take net questions across a link */
static void
h_ques_command (struct convection *cp)
{
char *name, *cptr;
struct group *p;
int channel;
char type;

	channel = atoi (getparam (cp));
	name = getparam (cp);
	type = *name;
	name = getparam (cp);
	cptr = getparamline (cp);

	p = find_group (channel);
	if (p != NULLGROUP && p->qfile)
		process_question (channel, type, name, cptr);
}



static void
h_umsg_command (struct convection *cp)
{
char *fromname, *toname, *text;

	fromname = getparam (cp);
	toname = getparam (cp);
	text = getparamline (cp);
	if (*text)
		send_msg_to_user (fromname, toname, text);
}



static void
h_bump_command (struct convection *cp)
{
char name[NAMELEN];
char channel[NAMELEN];
register struct convection *p;

	strncpy (name, getparam (cp), NAMELEN);
	strncpy (channel, getparam (cp), NAMELEN);
	for (p = convections; p; p = p->next)
		if (p->type == CT_USER && !p->via && (!strcmpi (p->name, name) || !strcmpi (p->nickname, name))) {
			sprintf (p->ibuf, "/c %s", channel);
			setparamptr (cp, &cp->ibuf[3]);
			cp->locked = 0;
			channel_command (p);
			break;
		}
}



static void
h_user_command (struct convection *cp)
{
char *host, *name, *timestr;
char *pers;
int newchannel, oldchannel, validaddr = 0;
register struct convection *p;
time_t currtime;

	name = getparam (cp);
	host = getparam (cp);
	timestr = getparam (cp);
	oldchannel = atoi (getparam (cp));
	newchannel = atoi (getparam (cp));

	if (!*name)
		return;		/* invalid user name */
	pers = name;
	while (*pers) {
		if (!isgraph (*pers))
			return;	/* invalid user name */
		pers++;
	}

	if (!*host)
		return;		/* invalid host name */

	if (!strcmp (host, "-1"))
		return;		/* invalid host name */

	pers = host;
	while (*pers) {
		if (!isgraph (*pers))
			return;	/* invalid host name */
		if (!isdigit (*pers))
			validaddr++;
		pers++;
	}
	if (!validaddr)
		return;		/* invalid host name - all digits */

	currtime = atol (timestr);
	if (currtime == 0)
		currtime = time ((time_t *) 0);

	/* is this a loopback of one of our own users? */
	if (!stricmp (host, Chostname)) {
		/* try to eliminate the errant entry */
		cp->xmitted += usprintf (cp->fd, huserstr, name, host, currtime, newchannel, -1, empty);
#ifdef MAILBOX
		mail_error ("Tried to eliminate a convers loop from %s - user: '%s'", cp->name, name);
#endif
		log (-1, "Tried to eliminate a convers loop from %s - user: '%s'", cp->name, name);
		return;
	}
	pers = getparamline (cp);
	rip (pers);

	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, loopstr, 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;
		strncpy (p->name, name, NAMELEN);
		strncpy (p->nickname, name, NAMELEN);
		strncpy (p->host, host, NAMELEN);
		p->via = cp;
		p->channel = newchannel;
		kmutex_lock (&Conv_mutex);
		p->next = convections;
		convections = p;
		kmutex_unlock (&Conv_mutex);
	}
	p->time = currtime;
	if (p->data)
		free (p->data);
	if ((*pers == '~' && pers[1] == '~') || *pers == '@')
		*pers = 0;
	p->data = strdup (pers);
	if ((p->channel = newchannel) < 0) {
		p->type = CT_CLOSED;
		free_closed_connections ();	/*  VE3DTE Apr 5/93 */
	}
	send_user_change_msg (name, host, currtime, oldchannel, newchannel, pers);
}



static struct group *
find_group (int channel)
{
register struct group *gp;

	for (gp = groups; gp; gp = gp->next)
		if (gp->channel == channel)
			return (gp);
	return (NULLGROUP);
}



static struct group *
get_group (struct convection *cp)
{
register struct group *gp;

	gp = find_group (cp->channel);
	if (gp == NULLGROUP) {
		gp = (struct group *) callocw (1, sizeof (struct group));

		gp->channel = cp->channel;
		strncpy (gp->moderator, cp->name, NAMELEN);
		strncpy (gp->password, cp->password, NAMELEN);
		kmutex_lock (&Group_mutex);
		gp->next = groups;
		groups = gp;
		kmutex_unlock (&Group_mutex);
		initialusers (cp->channel);
	}
	return (gp);
}



static struct group *
can_gcontrol (struct convection *cp)
{
	if (cp->net != cp->channel) {
		convcolorchange (cp, SYSCOLORS);
		cp->xmitted += usprintf (cp->fd, urnotstr, stars, netcontrolstr);
		return (NOCONTROL);
	}
	return (find_group (cp->channel));

}



static void
gname_command (struct convection *cp, char *cptr)
{
register struct group *gp;

	convcolorchange (cp, SYSCOLORS);
	gp = can_gcontrol (cp);
	if (gp == NOCONTROL || gp == NULLGROUP)
		return;
	if (*cptr) {
		char buf[100];

		strncpy (gp->name, cptr, 32);
		clear_locks ();
		sprintf (buf, activenetstr, stars, cptr);
		send_msg_to_channel (conversd, gp->channel, buf);
		update_net_data (gp);
	} else
		cp->xmitted += usprintf (cp->fd, activenetstr, stars, gp->name);
}



static void
assignnet_command (struct convection *cp)
{
register struct convection *p;
register struct group *gp;
char buf2[(NAMELEN * 2) + 2];
char *cptr;

	convcolorchange (cp, SYSCOLORS);
	cptr = getparamline (cp);
	if (cp->net == -1) {
		cp->xmitted += usprintf (cp->fd, urnotstr, stars, netcontrolstr);
		return;
	}
	if (!*cptr)
		cp->xmitted += usprintf (cp->fd, nouserstr, unknownstr);
	else {
		gp = find_group (cp->net);
		if (gp == NOCONTROL || gp == NULLGROUP)
			return;
		for (p = convections; p; p = p->next) {
			if (p->type == CT_USER && (!strcmpi (p->name, cptr) || !strcmpi (p->nickname, cptr)))
				break;
		}
		if (!p) {
			cp->xmitted += usprintf (cp->fd, nouserstr, unknownstr);
			return;
		}
		if (p->net != -1)
			cp->xmitted += usprintf (cp->fd, busystr, sosorry, cptr, netcontrolstr);
		else {
			char buf[80];

			p->net = cp->net;
			cp->net = -1;
			strncpy (gp->moderator, p->name, NAMELEN);
			clear_locks ();
			sprintf (buf, urassigned, stars, cp->nickname, netcontrolstr, channelstr, gp->channel);
			send_msg_to_user (conversd, p->name, buf);
			getTXname (p, buf2);
			sprintf (buf, "%s'%s' is now %s\n", stars, buf2, netcontrolstr);
			if (p->net != cp->channel)
				cp->xmitted += usprintf (cp->fd, buf);
			send_msg_to_channel (conversd, gp->channel, buf);
			update_user_data (p, 0);
			update_user_data (cp, 0);
			update_net_data (gp);
		}
	}
}



static void
bumpnet_command (struct convection *cp)
{
register struct convection *p, *p2;
register struct group *gp;
char *dummy;
int thischannel = 0;
char *cptr;

	convcolorchange (cp, SYSCOLORS);
	dummy = getparam (cp);
	cptr = getparam (cp);
	gp = can_gcontrol (cp);
	if (gp == NOCONTROL || gp == NULLGROUP)
		return;
	if (!*dummy)
		cp->xmitted += usprintf (cp->fd, nouserstr, unknownstr);
	else {
		for (p = convections; p; p = p->next) {
			if (p->type == CT_USER && (!strcmpi (p->name, dummy) || !strcmpi (p->nickname, dummy)))
				break;
		}
		if (!p) {
			cp->xmitted += usprintf (cp->fd, nouserstr, unknownstr);
			return;
		}
		if (p->channel != cp->channel)
			cp->xmitted += usprintf (cp->fd, wrongchannel, stars, userstr, dummy, channelstr);
		else {
			thischannel = atoi (cptr);
			if (p->via) {
				for (p2 = convections; p2; p2 = p2->next)
					if (p2->type == CT_HOST)
						p2->xmitted += usprintf (p2->fd, bumpstr, p->name, thischannel);
			} else {
				sprintf (p->ibuf, "/c %d\n", thischannel);
				setparamptr (cp, &cp->ibuf[3]);
				cp->locked = 0;
				channel_command (p);
			}
		}
	}
}



static void
process_question (
int channel,
char type,
char *name,
const char *cptr
) {
register struct group *gp;
struct convection *p;
char buf[LINELEN], nmbuf[32], tmbuf[10], nerf[60], *ptr;
int here, sendperm = 1;
time_t currtime;

	gp = find_group (channel);
	if (gp == NULLGROUP)
		return;
	if (type == 'a') {
		if (!gp->totalq && !gp->qfile) {
			gp->qfile = tmpfile ();
			setbuf (gp->qfile, (char *) 0);
		}
		if (gp->qfile) {
			gp->totalq++;
			update_net_data (gp);
			sendperm = 0;
			here = ftell (gp->qfile);
			fseek (gp->qfile, 0, SEEK_END);
			currtime = time (&currtime);
			fputs (name, gp->qfile);
			fputc ('\n', gp->qfile);
			fputs (timestring (currtime), gp->qfile);
			fputc ('\n', gp->qfile);
			fputs (cptr, gp->qfile);
			fputc (0, gp->qfile);
			fseek (gp->qfile, here, SEEK_SET);
			sprintf (buf, "%s from '%s'", questionstr, name);
			send_msg_to_user (conversd, gp->moderator, buf);
		}
	} else {
		if (gp->qfile) {
			sendperm = 0;
			here = ftell (gp->qfile);
			(void) fgets (nmbuf, 16, gp->qfile);
			nmbuf[strlen (nmbuf) - 1] = 0;
			(void) fgets (tmbuf, 9, gp->qfile);
			ptr = buf;
			do {
				*ptr = (char) fgetc (gp->qfile);
			} while (*ptr++);
			if (type == 'p') {
				send_msg_to_user (nmbuf, gp->moderator, buf);
				fseek (gp->qfile, here, SEEK_SET);
			} else {
				gp->nextq++;
				clear_locks ();
				if (type != 's') {
					sprintf (nerf, "%sQuestion #%-d, sent by '%s' at%s", stars, gp->nextq, nmbuf, tmbuf);
					send_msg_to_channel (conversd, gp->channel, nerf);
					clear_locks ();
					send_msg_to_channel (nmbuf, gp->channel, buf);
				} else {
					sprintf (nerf, "%sSkipped Question #%d", stars, gp->nextq);
					send_msg_to_user (conversd, gp->moderator, nerf);
				}
				update_net_data (gp);
			}
		}
	}
	if (sendperm) {		/* qfile not local, send to remote links */
		for (p = convections; p; p = p->next)
			if (p->type == CT_HOST)
				p->xmitted += usprintf (p->fd, quesstr,
						 channel, type, name, cptr);
	}
}



static void
question_command (struct convection *cp, char *cptr)
{
register struct group *gp;
char type;
char buf[(NAMELEN * 2) + 2];

	gp = find_group (cp->channel);
	if (gp == NULLGROUP) {
		cp->xmitted += usprintf (cp->fd, nonetstr, stars);
		return;
	}
	getTXname (cp, buf);
	cp->locked = 0;
	if (cp->net != cp->channel) {	/* not net control */
		process_question (cp->channel, 'a', buf, cptr);
		cp->xmitted += usprintf (cp->fd, fullstrcr, questionstr);
	} else {		/* options only available to net control */
		if (*cptr == '?')
			cp->xmitted += usprintf (cp->fd, "%s%d questions (%d unread)\n", stars, gp->totalq, gp->totalq - gp->nextq);
		else {		/* p=preview nc only, s=skip, other=display */
			if (!gp->totalq || !(gp->totalq - gp->nextq)) {
				cp->xmitted += usprintf (cp->fd, "%sNone\n", stars);
				return;
			}
			type = (char) tolower (*cptr);
			if (type != 'p' && type != 's')
				type = 'r';
			process_question (cp->channel, type, buf, cptr);
		}
	}
}



static void
log_command (struct convection *cp, char *cptr)
{
register struct group *gp;
int changed = 0;
time_t currtime;
char tempname[256];

	convcolorchange (cp, SYSCOLORS);
	currtime = time (&currtime);
	gp = can_gcontrol (cp);
	if (gp == NOCONTROL || gp == NULLGROUP)
		return;
	if (gp->logfile) {
		fprintf (gp->logfile, closeminutes, stars, timestring (currtime));
		(void) fclose (gp->logfile);
		gp->logfile = (FILE *) 0;
		gp->logged = 0;
		changed = 1;
		cp->xmitted += usprintf (cp->fd, logfilestr, stars, "clos");
	}
	if (*cptr) {
		sprintf (tempname, "%s%s%s", PublicDir, (*cptr != '/' && *cptr != '\\') ? "/" : "", cptr);
		gp->logfile = fopen (tempname, APPEND_TEXT);
		if (gp->logfile) {
			fprintf (gp->logfile, openminutes, stars, timestring (currtime), stars, gp->name, netcontrolstr, gp->moderator);
			cp->xmitted += usprintf (cp->fd, logfilestr, stars, "open");
			gp->logged = 1;
			changed = 0;
		} else
			cp->xmitted += usprintf (cp->fd, noopenstr, tempname);
	}
	if (changed)
		update_net_data (gp);
	if (gp->logged)
		glog_command (cp);
}



static void
nonet_command (struct convection *cp)
{
register struct group *gp, *g;

	gp = can_gcontrol (cp);
	if (gp == NOCONTROL || gp == NULLGROUP) {
		if (!nonetoverride)
			return;
		else {
			gp = find_group (cp->channel);
			if (gp == NULLGROUP)
				return;
		}
	}
	if (gp->qfile)
		(void) fclose (gp->qfile);
	if (!nonetoverride) {
		log_command (cp, empty);
		gp->private = -1;	/* indicator to remotes that we're done */
		update_net_data (gp);
		clear_locks ();
		send_msg_to_channel (conversd, gp->channel, "*** Net complete!\n");
		cp->net = -1;
		update_user_data (cp, 0);
	}
	if (gp == groups)
		groups = gp->next;
	else
		for (g = groups; g; g = g->next)
			if (g->next == gp) {
				g->next = gp->next;
				break;
			}
	free (gp);
}



static void
gpassword_command (struct convection *cp, char *cptr, int disableit)
{
register struct group *gp;

	convcolorchange (cp, SYSCOLORS);
	gp = can_gcontrol (cp);
	if (gp == NOCONTROL || gp == NULLGROUP)
		return;
	if (disableit) {
		gp->password[0] = '\0';
		cp->xmitted += usprintf (cp->fd, net2str, stars, passwordstr, clearedstr);
		update_net_data (gp);
		return;
	}
	if (*cptr) {
		strncpy (gp->password, cptr, NAMELEN);
		cp->xmitted += usprintf (cp->fd, net2str, stars, passwordstr, "set");
		update_net_data (gp);
	} else {
		if (gp->password[0])
			cp->xmitted += usprintf (cp->fd, "%sNet %s: '%s'.\n", stars,
				passwordstr, gp->password);
		else
			cp->xmitted += usprintf (cp->fd, "%sNo Net %s\n", stars, passwordstr);
	}
}



static void
gprivate_command (struct convection *cp, char *cptr)
{
struct group *gp;

	convcolorchange (cp, SYSCOLORS);
	gp = can_gcontrol (cp);
	if (gp != NOCONTROL && gp != NULLGROUP) {
		gp->private = (tolower (*cptr) == 'c') ? 1 : 0;
		cp->xmitted += usprintf (cp->fd, "%sNet no%s %s\n", stars,
			(gp->private) ? "w" : "t", privatestr);
		update_net_data (gp);
	}
}



static void 
uptime_command (struct convection *cp)
{
	time_t currtime;

	currtime = time (&currtime);
	cp->xmitted += usprintf (cp->fd, "*** %s is up for %s\n", Chostname, ts4 (currtime - StartTime));
}



static void
group_command (struct convection *cp)
{
struct group *p;
int channel;

	convcolorchange (cp, SYSCOLORS);
	if (groups == NULLGROUP)
		cp->xmitted += usprintf (cp->fd, "%sNo Active Nets!\n", stars);
	else {
		cp->xmitted += usprintf (cp->fd, "%s  Net Topic\n", channelstr);
		for (channel = 0; channel <= MAXCHANNEL; channel++) {
			for (p = groups; p; p = p->next)
				if (p->channel == channel)
					cp->xmitted += usprintf (cp->fd, "%7d  %-32s %s%s%s\n", p->channel, p->name, (*p->moderator) ? "<" : "", p->moderator, (*p->moderator) ? ">" : "");
		}
		cp->xmitted += usprintf (cp->fd, trailer);
	}
}



static void
smiley_command (struct convection *cp)
{
int offset, k;
char buff[200];
FILE *fp;

	conv_randomize ();
	offset = conv_random (810, 1);
	if ((fp = fopen (SMILEYFile, "r")) == NULLFILE)
		return;
	sprintf (buff, "%sSmiley: ", stars);
	for (k = 0; k < offset; k++) {
		kwait (NULL);
		(void) fgets (&buff[12], 188, fp);
	}
	(void) fclose (fp);
	clear_locks ();
	send_msg_to_channel (conversd, cp->channel, buff);
}



#ifdef SAMCALLB
extern int SAMoutbytes;

static void
call_command (struct convection *cp)
{
char *buff;
int k;

	buff = getparam (cp);
	if (*buff) {
		k = cb_lookup (cp->fd, buff, (FILE *) 0);
		if (SAMoutbytes) {
			cp->xmitted += SAMoutbytes;
			if (k != 2)
				cp->xmitted += usprintf (cp->fd, trailer);
		} else
			cp->xmitted += usprintf (cp->fd, "%sCallbook server not active at %s\n", stars, Hostname);
	} else
		cp->xmitted += usprintf (cp->fd, "%sNo callsign given\n", stars);
}
#endif



static void
roll_command (struct convection *cp)
{
int die1, die2;
char buff[80];
char buf[(NAMELEN * 2) + 2];

	conv_randomize ();
	die1 = conv_random (6, 1);
	die2 = conv_random (6, 1);
	clear_locks ();
	getTXname (cp, buf);
	sprintf (buff, rollstr, stars, buf, die1, die2, die1 + die2, trailer);
	send_msg_to_channel (conversd, cp->channel, buff);
}



static void
cut_command (struct convection *cp)
{
int die1, die2;
char buff[100];
char buf[(NAMELEN * 2) + 2];

	conv_randomize ();
	die1 = conv_random (4, 0);
	die2 = conv_random (13, 0);
	clear_locks ();
	getTXname (cp, buf);
	sprintf (buff, cutstr, stars, buf, cards[die2], suits[die1], trailer);
	send_msg_to_channel (conversd, cp->channel, buff);
}



static void
glog_command (struct convection *cp)
{
char buffer[75];
struct convection *p;
struct group *gp;

	gp = can_gcontrol (cp);
	if (gp == NOCONTROL || gp == NULLGROUP)
		return;
	clear_locks ();
	send_msg_to_channel (conversd, gp->channel, gloghdr);
	clear_locks ();
	sprintf (buffer, glogstr, userstr, nicknmstr, hoststr);
	send_msg_to_channel (conversd, gp->channel, buffer);
	for (p = convections; p; p = p->next)
		if (p->type == CT_USER && gp->channel == p->channel) {
			sprintf (buffer, "    %-8.8s %-15.15s  %s  %-30.30s\n", p->name,
			     p->nickname, timestring (p->nettime), p->host);
			clear_locks ();
			send_msg_to_channel (conversd, gp->channel, buffer);
		}
	clear_locks ();
	send_msg_to_channel (conversd, gp->channel, trailer);
}



static void
net_command (struct convection *cp)
{
char *cptr;
register struct group *gp;
char buf[4];
char buf2[(NAMELEN * 2) + 2];

	convcolorchange (cp, SYSCOLORS);
	cptr = getparam (cp);
	if (!cp->channel && !ConvNet0 && (tolower (*cptr) == 's')) {
		cp->xmitted += usprintf (cp->fd, "%sNo nets on %s 0\n", stars, channelstr);
		return;
	}
	gp = find_group (cp->channel);
	switch (tolower (*cptr)) {
		case 0:
			group_command (cp);
			break;
		case 'c':
			if (tolower (cptr[1]) == 'o') {
				if (gp == NULLGROUP)
					cp->xmitted += usprintf (cp->fd, nonetstr, stars);
				else
					cp->xmitted += usprintf (cp->fd, "%s is '%s'.\n", netcontrolstr,
							     gp->moderator);
			} else
				gprivate_command (cp, cptr);
			break;
		case 'r':
			if (gp == NULLGROUP)
				cp->xmitted += usprintf (cp->fd, nonetstr, stars);
			else {
				char new[75];

				cp->xmitted += usprintf (cp->fd, fullstrcr, recheckstr);
				getTXname (cp, buf2);
				sprintf (new, "%s: %s", recheckstr, buf2);
				send_msg_to_user (conversd, gp->moderator, new);
			}
			break;
		case 'o':
			gprivate_command (cp, cptr);
			break;
		case 'l':
			glog_command (cp);
			break;
		case 'i':
			if (cp->net != -1) {
				if (cp->net != cp->channel)
					gp = find_group (cp->net);
				if (gp == NULLGROUP)
					return;
				cp->xmitted += usprintf (cp->fd, "%sYou are %s on %s %d\n", stars, netcontrolstr, channelstr, gp->channel);
				gname_command (cp, empty);
				cp->xmitted += usprintf (cp->fd, "%sNet is %s and the minutes are %sbeing recorded\n", stars,
					gp->private ? "closed" : "open", gp->logged ? empty : "not ");
				gpassword_command (cp, empty, 0);
				sprintf (buf, "?");
				question_command (cp, buf);
			} else
				glog_command (cp);	/* prints my error msg */
			break;
		case 't':
			gname_command (cp, getparamline (cp));
			break;
		case 'm':
			log_command (cp, getparamline (cp));
			break;
		case 'e':
			nonet_command (cp);
			break;
		case 'a':
			assignnet_command (cp);
			break;
		case 'b':
			bumpnet_command (cp);
			break;
		case 'w':
			cptr = getparamline (cp);
			getTXname (cp, buf2);
			if (gp == NULLGROUP)
				cp->xmitted += usprintf (cp->fd, nonetstr, stars);
			else
				send_msg_to_user (buf2, gp->moderator, cptr);
			break;
		case 'q':
			question_command (cp, getparamline (cp));
			break;
		case '?':
		case 'h':
			strcpy (cp->ibuf, "h net");
			setparamptr (cp, &cp->ibuf[2]);
			help_command (cp);
			break;
		case 'n':
			gpassword_command (cp, getparamline (cp), 1);
			break;
		case 'p':
			gpassword_command (cp, getparamline (cp), 0);
			break;
		case 's':
			if (cp->net != -1) {
				cp->xmitted += usprintf (cp->fd, "%sAlready a %s\n", stars, netcontrolstr);
				return;
			}
			if (gp != NULLGROUP) {
				cp->xmitted += usprintf (cp->fd, "%sThere's a %s\n", stars, netcontrolstr);
				return;
			}
			gp = get_group (cp);
			cp->net = cp->channel;
			update_user_data (cp, 0);
			gname_command (cp, getparamline (cp));
			break;
		default:
			cp->xmitted += usprintf (cp->fd, unkcmdstr, unknownstr, cptr, gethelpstr);
	}

}



static struct group *
lookup_group (char *name)
{
register struct group *gp;

	if (name) {
		for (gp = groups; gp; gp = gp->next)
			if (!strnicmp (gp->name, name, strlen (name)))
				return (gp);
	}
	return (NULLGROUP);
}



static void
list_groups_command (struct convection *cp)
{
register struct group *gp;
struct convection *p;
int cnt, channel;

	cp->xmitted += usprintf (cp->fd, grouphdr, stars);
	for (channel = 0; channel <= MAXCHANNEL; channel++) {
		for (gp = groups; gp; gp = gp->next) {
			if (gp->channel == channel) {
				cnt = 0;
				for (p = convections; p; p = p->next) {
					if ((p->type == CT_USER) && (p->channel == gp->channel))
						cnt++;
				}
				cp->xmitted += usprintf (cp->fd, "   %5d  %-34.34s  (%02d user%s  %9s  %s\n", gp->channel,
					gp->name, cnt, (cnt > 1) ? "s)" : ") ", gp->private ? "[PRIVATE]" : empty,
					gp->password[0] ? "[PASSWORD]" : empty);
			}
		}
	}
	cp->xmitted += usprintf (cp->fd, trailer);
}



static void
join_command (struct convection *cp)
{
char *s;
register struct group *gp;
int k, old;

	s = getparam (cp);
	if (!*s)
		return;
	if ((gp = lookup_group (s)) == NULLGROUP) {
		old = cp->channel;
		for (k = 1; k <= MAXCHANNEL; k++) {
			if ((gp = find_group (k)) == NULLGROUP)
				break;
		}
		cp->channel = k;
		gp = get_group (cp);
		if (cp->net != -1) {
			int oldchan = cp->channel;

			cp->channel = cp->net;
			nonet_command (cp);
			cp->channel = oldchan;
		}
		cp->net = cp->channel;
		update_user_data (cp, 0);
		gname_command (cp, s);
		cp->channel = old;
	}
	sprintf (cp->ibuf, "/c %d", gp->channel);
	setparamptr (cp, &cp->ibuf[3]);
	channel_command (cp);
}



static int
gatekeeper (struct convection *cp, int channel)
{
register struct group *gp;
char buf[40];

	gp = find_group (channel);
	if (gp == NULLGROUP)
		return (0);	/* okay, there's no net control */
	if (cp->paged == channel) {	/* we were invited! */
		cp->paged = -1;
		return (0);
	}
	if (cp->net == channel)	/* we control the net */
		return (0);
	kwait (NULL);
	sprintf (buf, "%s'%s' tried to join", stars, cp->name);
	if (gp->private) {
		cp->xmitted += usprintf (cp->fd, sorry, sosorry, privatestr, channelstr, channel);
		send_msg_to_user (conversd, gp->moderator, buf);
		return (1);
	}
	if (gp->password[0])
		if (strcmpi (cp->password, gp->password)) {
			cp->xmitted += usprintf (cp->fd, sorry, sosorry, passwordstr, channelstr, channel);
			send_msg_to_user (conversd, gp->moderator, buf);
			return (1);
		}
	return (0);
}



static void 
whois_command (struct convection *cp)
{
struct convection *p;
char *name;
int found = 0;

	name = getparam (cp);
	if (!*name)
		cp->xmitted += usprintf (cp->fd, "%sNeed username...", stars);
	else {
		for (p = convections; p; p = p->next) {
			kwait (NULL);
			if ((p->type == CT_USER) && (!stricmp (p->name, name) || !stricmp (p->nickname, name))) {
				found = 1;
				cp->xmitted += usprintf (cp->fd, headerstr);
				cp->xmitted += usprintf (cp->fd, userdata,
					 p->name, p->host, (p->via) ? p->via->name : getVia (p->name),
					 p->channel, timestring (p->time), (p->data) ? p->data : empty);
				if (strcmp (p->name, p->nickname))
					cp->xmitted += usprintf (cp->fd, "\n      Nickname: %s", p->nickname);
				if (p->data && strcmp (p->data, "@"))
					cp->xmitted += usprintf (cp->fd, "\n      Personal: %s", p->data);
				cp->xmitted += usprintf (cp->fd, "\n       Channel: %d", p->channel);
				break;
			}
		}		/* for convections */
		if (!found)
			cp->xmitted += usprintf (cp->fd, "%sUser not found...", stars);
	}
	cp->xmitted += usprintf (cp->fd, "\n%s", trailer);
}



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

	strncpy (host, getparam (cp), NAMELEN);
	if (!*host || !stricmp (host, Chostname) || !stricmp (host, "all")) {
		cp->xmitted += usprintf (cp->fd, sysinfoheader, stars, Chostname, Hostname);
		cp->xmitted += usprintf (cp->fd, "%s%s: %s\n", stars, Chostname, (mysysinfo) ? mysysinfo : noinfo);
		if (stricmp (host, "all"))
			return;
	}
	sprintf (cp->ibuf, "/\377\200SYSI %s %s\n", cp->name, host);
	h_unknown_command (cp);
}



static void 
h_sysi_command (struct convection *cp)
{
char user[NAMELEN + 1], host[NAMELEN + 1];
char buffer[256];

	strncpy (user, getparam (cp), NAMELEN);
	strncpy (host, getparam (cp), NAMELEN);
	cp->locked = 0;

	if (!stricmp (host, Chostname) || !stricmp (host, "all")) {	/* if for us */
		sprintf (buffer, sysinfoheader, stars, Chostname, Hostname);
		send_msg_to_user ("conversd", user, buffer);
		clear_locks ();
		sprintf (buffer, "%s%s: %s\n", stars, Chostname, (mysysinfo) ? mysysinfo : noinfo);
		send_msg_to_user ("conversd", user, buffer);
		if (stricmp (host, "all"))
			return;
	}
	/* else, pass it on */
	sprintf (cp->ibuf, "/\377\200SYSI %s %s\n", user, host);
	h_unknown_command (cp);
}



#ifdef TESTTPP
void 
test1_command (struct convection *cp)
{
	sprintf (cp->ibuf, "/\377\200ECMD conversd test3\n");
	h_unknown_command (cp);
}



void 
test2_command (struct convection *cp)
{
	sprintf (cp->ibuf, "/\377\200ECMD %s ecmdtest\n", cp->name);
	h_unknown_command (cp);
}



void 
test3_command (struct convection *cp)
{
	clear_locks ();
	send_msg_to_channel (conversd, 6809, "Test3 command executed\n");
}
#endif



#if 0
static void
conv_usflush (int s)
{
register struct usock *up;

	if ((up = itop (s)) == NULLUSOCK)
		return;

	if (up->obuf == NULLBUF)
		return;
	if (len_p (up->obuf) < 2400)
		return;
	usflush (s);
}
#endif



static struct cmdtable {
	const char *name;
	void (*fnc) (struct convection *);
	int states;
} cmdtable[] = {
	{ "?",			cmdsummary_command,	CM_USER },
	{ "accept",		accept_command,		CM_USER },
	{ "action",		me_command,		CM_USER },
	{ "bye",		bye_command,		CM_USER },
	{ "beep",		sounds_command,		CM_USER },
	{ "bell",		sounds_command,		CM_USER },
	{ "channel",		channel_command,	CM_USER },
#ifdef SAMCALLB
	{ "call",		call_command,		CM_USER },
#endif
	{ "color",		color_command,		CM_USER },
#ifdef LZW
	{ "compressed",		compressed_command,	CM_USER },
#endif
	{ "cq",			cq_command,		CM_USER },
	{ "cstat",		cstat_command,		CM_USER },
	{ "cut",		cut_command,		CM_USER },
	{ "destinations",	hosts_command,		CM_USER },
	{ "exit",		bye_command,		CM_USER },
	{ "exclude",		imsg_command,		CM_USER },
	{ "groups",		list_groups_command,	CM_USER },
	{ "help",		help_command,		CM_USER },
	{ "hosts",		hosts_command,		CM_USER },
	{ "invite",		invite_command,		CM_USER },
	{ "imsg",		imsg_command,		CM_USER },
	{ "iwrite",		imsg_command,		CM_USER },
	{ "join",		join_command,		CM_USER },
	{ "links",		links_command,		CM_USER },
	{ "list",		list_command,		CM_USER },
	{ "msg",		msg_command,		CM_USER },
	{ "me",			me_command,		CM_USER },
	{ "name",		name_command,		CM_UNKNOWN },
	{ "net",		net_command,		CM_USER },
	{ "news",		news_command,		CM_USER },
	{ "nickname",		nickname_command,	CM_USER },
	{ "nonickname",		nickname_command,	CM_USER },
	{ "nopassword",		password_command,	CM_USER },
	{ "note",		personal_command,	CM_USER },
	{ "page",		invite_command,		CM_USER },
	{ "password",		password_command,	CM_USER },
	{ "personal",		personal_command,	CM_USER },
	{ "quit",		bye_command,		CM_USER },
#ifdef ALLSERV
	{ "quote",		quote_command,		CM_USER },
#endif
	{ "realname",		realname_command,	CM_USER },
	{ "roll",		roll_command,		CM_USER },
	{ "send",		msg_command,		CM_USER },
	{ "smiley",		smiley_command,		CM_USER },
	{ "sounds",		sounds_command,		CM_USER },
	{ "sysinfo",		sysinfo_command,	CM_USER },
#ifdef TESTTPP
	{ "test1",		test1_command,		CM_USER },
	{ "test2",		test2_command,		CM_USER },
	{ "test3",		test3_command,		CM_USER },
#endif
	{ "time",		time_command,		CM_USER },
	{ "uptime",		uptime_command,		CM_USER },
	{ "users",		who_command,		CM_USER },
	{ "version",		version_command,	CM_USER },
	{ "who",		who_command,		CM_USER },
	{ "whois",		whois_command,		CM_USER },
	{ "write",		msg_command,		CM_USER },

	{ "\377\200bump",	h_bump_command,		CM_HOST },
	{ "\377\200cmsg",	h_cmsg_command,		CM_HOST },
	{ "\377\200dest",	h_dest_command,		CM_HOST },
	{ "\377\200ecmd",	h_ecmd_command,		CM_HOST },
	{ "\377\200host",	h_host_command,		CM_UNKNOWN },
	{ "\377\200invi",	h_invi_command,		CM_HOST },
	{ "\377\200link",	h_link_command,		CM_HOST },
	{ "\377\200loop",	h_loop_command,		CM_HOST },
	{ "\377\200ndat",	h_ndat_command,		CM_HOST },
	{ "\377\200ping",	h_ping_command,		CM_HOST },
	{ "\377\200pong",	h_pong_command,		CM_HOST },
	{ "\377\200ques",	h_ques_command,		CM_HOST },
	{ "\377\200rout",	h_rout_command,		CM_HOST },
	{ "\377\200sysi",	h_sysi_command,		CM_HOST },
	{ "\377\200topi",	h_topi_command,		CM_HOST },
	{ "\377\200uadd",	h_uadd_command,		CM_HOST },
	{ "\377\200umsg",	h_umsg_command,		CM_HOST },
	{ "\377\200user",	h_user_command,		CM_HOST },
	{ NULLCHAR,		0,			0 }
};



static void
process_commands (struct convection *cp, struct mbx *m)
{
char *arg;
int arglen, size;
int firstcmd = 1;
char *ccp, *params;
struct cmdtable *cmdp;
char buf[(NAMELEN * 2) + 2];
int prevblock;

	Conflogins++;
#ifdef STATS_USE
	localConfUsers++;
	STATS_adduse (1);
#endif
#ifdef XSERVER
	xnotify (X_CONF);
#endif
	if (m)
		firstcmd = 0;
	if (m == (struct mbx *) 1)
		m = NULLMBX;
	if (confMOTD != NULL && !cp->features)
		cp->xmitted += usprintf (cp->fd, "\n%s\n", confMOTD);
	for (;;) {
	      loop:
#ifdef MBXTDISC
		if (m)		/* Restart the inactivity timer - WG7J */
			start_timer (&m->tdisc);
#endif
#if 0
		conv_usflush (cp->fd);
#else
		usflush (cp->fd);	/* might not be neccesary */
#endif
#if 1
		(void) setflush (cp->fd, '\n');	/* automatic flushing each line */
#endif
		kwait (NULL);
		if (cp->type == CT_CLOSED)
			break;
		memset (cp->ibuf, 0, INBUFLEN);
		prevblock = getblock (cp->fd);
		(void) sockblock (cp->fd, prevblock & ~SOCK_NORXBLOCK);
		if ((size = recvline (cp->fd, (unsigned char *) cp->ibuf, INBUFLEN)) <= 0)
			break;
#if 0
		sockblock (cp->fd, prevblock);
#endif
		if (size == (INBUFLEN))
			log (-1, "maximum sized convers line received");
#if 1
		usflush (cp->fd);	/* might not be neccesary */
		(void) setflush (cp->fd, -1);	/* let command output queue */
#endif
		cp->received += size;
		clear_locks ();
		cp->locked = 1;
		if (*cp->ibuf == '/') {
			setparamptr (cp, &cp->ibuf[1]);
			arg = getparam (cp);
			arglen = (int) strlen (arg);
			for (cmdp = cmdtable; cmdp->name; cmdp++) {
				if (!strncmpi (cmdp->name, arg, (unsigned int) arglen)) {
					if (cmdp->states & (1 << cp->type)) {
						(*cmdp->fnc) (cp);
						if (cp->type != CT_UNKNOWN)
							firstcmd = 0;
					}
					if (firstcmd)
						cp->xmitted += usprintf (cp->fd, namecmdstr, stars);
					goto loop;
				}
			}
			/* didn't find match in table - see if it is a host command */
			if (uchar(cp->ibuf[1]) == uchar('\377') && uchar(cp->ibuf[2]) == uchar('\200')) {	/*lint !e743 */
				if (*(cp->ibufpt))
					*(cp->ibufpt - 1) = ' ';
				h_unknown_command (cp);	/* yep, pass it on */
				goto loop;
			}
			if (firstcmd)
				cp->xmitted += usprintf (cp->fd, namecmdstr, stars);
			else if (cp->type == CT_USER) {
				if (!ecmd_exists (arg))
					cp->xmitted += usprintf (cp->fd, unkcmdstr, unknownstr, arg, gethelpstr);
				else {
					char buff[INBUFLEN];

					params = getparamline(cp);
					sprintf (buff, "/\377\200ECMD %s %s %s\n", cp->name, arg, params);
					strncpy (cp->ibuf, buff, INBUFLEN);
					h_unknown_command (cp);
				}
			}
			goto loop;
		}
		if (firstcmd) {
			/* only before a valid user */
			if (!cp->features)	/* not a pending host link */
				cp->xmitted += usprintf (cp->fd, namecmdstr, stars);
			goto loop;
		}
#ifndef TNOS_68K
		if ((ccp = strpbrk (cp->ibuf, "\r\n")) != NULLCHAR)
#else
		if ((ccp = strpbrk (cp->ibuf, "\r\l")) != NULLCHAR)
#endif
			*ccp = '\0';
#ifdef NOCTLCHARS
		if (isprint (cp->ibuf[0]) != 0 && cp->type == CT_USER) {
#else
		if (cp->type == CT_USER) {
#endif
			getTXname (cp, buf);
			send_msg_to_channel (buf, cp->channel, cp->ibuf);
		}
	}
	(void) sockblock (cp->fd, SOCK_BLOCK);
	if (m)
		(void) setflush (cp->fd, -1);
#ifdef STATS_USE
	localConfUsers--;
#endif
#ifdef XSERVER
	xnotify (X_CONF);
#endif
	bye_command (cp);
	free_closed_connections ();
}



/* Incoming convers session */
static void
conv_incom (int s, void *t OPTIONAL, void *p OPTIONAL)
{
struct convection *cp;
struct sockaddr fsocket;
int trans, i = SOCKSIZE;
char *cptr, *cptr2;

	(void) sockowner (s, Curproc);	/* We own it now */
	(void) sockmode (s, SOCK_ASCII);
	(void) sockblock (s, SOCK_NOTXBLOCK);	/* prevent backlogs ! */
	cp = alloc_connection (s);

	if (strncmp (Curproc->name, "permlink: ", 10))	{
		trans = DTranslate;
		DTranslate = 0;		/* force output to be numeric IP addr */
		if (getpeername (s, (char *) &fsocket, &i) != -1)	{
			cptr = psocket (&fsocket);
			DTranslate = trans;	/* Restore original state */
			if ((cptr2 = strchr (cptr, ':')) != NULLCHAR)
				*cptr2 = 0;
			sprintf (cp->ibuf, "conversd: from %s", cptr);
		} else
			sprintf (cp->ibuf, "conversd: from (unknown host)");
		chname (Curproc, cp->ibuf);
	}

	cp->channel = CChannel;
	statlog ("CONFERENCE - New Telnet User");
	{
		struct permlink *pl;

		for (pl = permlinks; pl; pl = pl->next) {
			if (pl->fd == s) {
				pl->convection = cp;
				cp->xmitted += usprintf (s, hhoststr, Chostname, shortversion, myfeatures);
				cp->features = 1;	/* to suppress error msgs */
				break;
			}
		}

		if (pl == NULLPERMLINK) {
			cp->xmitted += usprintf (cp->fd,
			   "%s%s %s", conferencestr, Chostname, gethelpstr);
			if (ConvHeader)
				usprintf (cp->fd, namecmdstr, stars);
		}
	}
	process_commands (cp, (struct mbx *) 0);
}



#ifdef MAILBOX
/* this is for Mailbox users */
void
mbox_converse (int fd, char *name, int channel, struct mbx *mbox)
{
struct convection *cp;
char *oldprocname;

	if (Sconv == -1)
		return;
	oldprocname = strdup (Curproc->name);
	(void) sockblock (fd, SOCK_NOTXBLOCK);	/* prevent backlogs ! */
	cp = alloc_connection (fd);
	sprintf (cp->ibuf, "convers: user '%s'", name);
	chname (Curproc, cp->ibuf);
	cp->ibuf[0] = 0;
	cp->channel = (channel == -1) ? CChannel : channel;
	strncpy (cp->name, name, NAMELEN);
	strncpy (cp->nickname, cp->name, NAMELEN);
	strncpy (cp->host, Chostname, NAMELEN);
	cp->type = CT_USER;
	cp->flags &= ~CLOSE_SOCK;	/* do not close socket on exit */
#if 0
	setflush (fd, '\n');	/* automatic flushing each line */
#endif
	cp->xmitted += usprintf (fd, "%s%s %s", conferencestr, Chostname, gethelpstr);
	announce_new_user (cp);
	set_personal (cp);
	clear_locks ();
	cp->locked = 1;		/* send to everyone but ourself */
	mystatus (cp, (int) -1);
	process_commands (cp, (mbox) ? mbox : (struct mbx *) 1);
	chname (Curproc, oldprocname);
}
#endif /* MAILBOX */



void 
conversWriteall (char *str)
{
struct convection *p;

	for (p = convections; p; p = p->next)
		if (p->type == CT_USER) {
			p->xmitted += usprintf (p->fd, "%s SYSOP: %s\n", amessage, str);
			usflush (p->fd);
		}
}



void 
conversWrite (char *str, char *user)
{
struct convection *p;

	for (p = convections; p; p = p->next)
		if ((p->type == CT_USER) && !stricmp (p->name, user)) {
			p->xmitted += usprintf (p->fd, "%s SYSOP: %s\n", amessage, str);
			usflush (p->fd);
		}
}



static void
convcolorchange (struct convection *p OPTIONAL, char *str OPTIONAL)
{
#ifndef UNIX
	int old;

	if (p->flags & USE_COLOR) {
		old = Curproc->output;
		Curproc->output = p->fd;
		p->xmitted += colorchange (str, p->colorset);
		Curproc->output = old;
	}
#endif
}



static void
cmdsummary_command (struct convection *cp)
{
int i;
char buf[82];
struct cmdtable *cmdp = cmdtable;
int width = 13, count = 6;
struct extendedcmds *ecmdp;

	cp->xmitted += usprintf (cp->fd, summary, stars);
	memset (buf, ' ', sizeof (buf));
	buf[77] = '\n';
	buf[78] = '\0';
	for (i = 0; cmdp->name; cmdp++, i = ((i + 1) % count)) {
		if ((unsigned char) *cmdp->name == 255)
			break;
		strncpy (&buf[i * width], cmdp->name, strlen (cmdp->name));
		if (i == (count - 1)) {
			cp->xmitted = usprintf (cp->fd, "%s ", buf);
			memset (buf, ' ', 77);
		}
	}
	if (i)
		cp->xmitted += usprintf (cp->fd, buf);

	if (ecmds != NULLEXTCMD && ecmds->name) {
		ecmdp = ecmds;
		memset (buf, ' ', sizeof (buf));
		buf[77] = '\n';
		buf[78] = '\0';
		cp->xmitted += usprintf (cp->fd, extendedsummary, stars);
		for (i = 0; ecmdp && ecmdp->name; ecmdp = ecmdp->next, i = ((i + 1) % count)) {
			strncpy (&buf[i * width], ecmdp->name, strlen (ecmdp->name));
			if (i == (count - 1)) {
				cp->xmitted += usprintf (cp->fd, "%s ", buf);
				memset (buf, ' ', 77);
			}
		}
		if (i)
			cp->xmitted += usprintf (cp->fd, buf);
	}
	cp->xmitted += usprintf (cp->fd, trailer);
}



static void
h_ecmd_command (struct convection *cp)
{
struct cmdtable *cmdp;
char arg[64], user[NAMELEN + 1], params[INBUFLEN + 1];
struct convection *usercp;
int arglen, addedbuf = 0;
struct extendedcmds *eadd;

	strncpy (user, getparam (cp), NAMELEN);
	strncpy (arg, getparam (cp), 64);
	arglen = (int) strlen (arg);
	strncpy (params, getparamline (cp), INBUFLEN);

	if (!stricmp (user, conversd)) {	/* adding a new command */
		if (!ecmd_exists (arg)) {
			eadd = mallocw (sizeof (struct extendedcmds));

			eadd->next = ecmds;
			eadd->name = strdup(arg);
			ecmds = eadd;
		}
		sprintf (cp->ibuf, "/\377\200ECMD conversd %s\n", arg);
		h_unknown_command (cp);
		return;
	}
	/* find the actual user who sent the command */
	for (usercp = convections; usercp; usercp = usercp->next) {
		if (!stricmp (usercp->name, user))
			break;
	}
	if (usercp == NULLCONNECTION)
		return;

	/* now lookup the command locally */
	for (cmdp = cmdtable; cmdp->name; cmdp++)
		if (!strncmp (cmdp->name, arg, (unsigned) arglen)) {
			if (cmdp->states & (1 << usercp->type)) {
				if (!usercp->ibuf) {
					addedbuf = 1;
					usercp->ibuf = (char *) callocw (1, INBUFLEN + 1);
				}
				sprintf (usercp->ibuf, "%s %s\n", arg, params);
				(*cmdp->fnc) (usercp);
				if (addedbuf)
					free (usercp->ibuf);
			}
			return;
		}
	/* if not found locally, then send it on to others */
	sprintf (cp->ibuf, "/\377\200ECMD %s %s %s\n", user, arg, params);
	h_unknown_command (cp);
}


static long seed = 1L;

#define CONV_RAND_MAX 0x7fffffff

static int
conv_rand (void)
{
	seed = (1103515245L * seed + 12345) & CONV_RAND_MAX;
	return ((int) (seed & 077777));
}



static void
conv_randomize (void)
{
	seed = (time (0) & CONV_RAND_MAX);
}



static int
conv_random (int num, int base)
{
	return (((long) conv_rand () % num) + base);
}


#endif /* CONVERS */

