
/*  $Header$
 *
 *  Conversd this was originally based on the conversd by the German team.
 *  This was distributed with WAMPES 931001 and has since been modified to
 *  obtain a degree of compatability with JNOS systems, but JNOS should still
 *  need to add support for personal text on the end of the /..USER command.
 *
 *  Any modification to this source code must continue to retain its original
 *  credits and copyright.
 *                                                                     - G7LED
 *
 *  
 *
 *  All modifications of the source since the 2.55 version distributed with
 *  WAMPES931001 remain the Copyright (C) 1994 by Darryl Miles, permission is
 *  granted to redistribute and/or modify these changes for use with and by
 *  Amateur Radio operators or for educational purposes.
 *
 *  Conversd WAMPES TEAM ??/??/???? - 22/09/1993.
 *                           
 *
 *  Darryl Miles (G7LED) 04/04/1994 - 07/09/1994
 *
 *  Version G7LED_LNET 1.0 - April 1994
 *                           Ground work, little bitz'n'pieces added see README.
 *
 *          G7LED_LNET 1.1 - June  1994
 *                           LZW added, personal text limit added.
 *
 *          G7LED_LNET 1.2 - June  1994
 *                           /INV user@host support, /..LOOP lockout on,
 *                           a decompression error will now automatically close
 *                           the link.  The /etc/conversd.inf file now retains
 *                           user information, of ALL convers users.
 *
 *          G7LED_LNET 1.3 - July  1994
 *                           Config files moved to a /etc/convers directory,
 *                           MOTD and NEWS files have been created,
 *                           Channel -1 support (Admin channel),
 *                           User reboot notification,
 *                           Auto reboot if exec or conversd.conf is changed,
 *                           Extra bomb proofing and improved debugging,
 *                           Config file host name feature fix,
 *                           /INV comment has been fixed, and /INV improved,
 *                           Input buffer allocation SIGSERV prob fixed,
 *                           /QUERY time format changed, and local servers
 *                             stats added, also issued on login,
 *                           JNOS user info bug fix,
 *                           User existance checking with UDAT etc,
 *                           JNOS auto detection with -DAUTO,
 *                           /HOSTS [all], this added 'all',
 *                           /LINKS indicates actual number of bits being
 *                             used,
 *                           on-line file works, /HELP <topic>,
 *                           SYSLOG support with some useful messages.
 *
 *          G7LED_LNET 1.4 - September 1994 (ALPHA)
 *                           /INVITE changes, to the way in which the users
 *                             is informed.
 *                           Addition of two fields into the conversd.conf
 *                             file to allow for later host restrictions.
 *                           /..LOOP bug fix, it will now catch Linux system
 *                             loops.
 *                           Compiled in Maxq of 15Kb added to all links.
 *                           Forced specifically due to a host link breaking
 *                             it not detectable by users.
 *                           During a turnaround in link connection the host
 *                             vias are all switched over as well.
 *                           Idler timer small fix.
 *                           User option handling/storing improved, for
 *                             later use.
 *                           /QUERY displays the last time a user logged in
 *                             locally if this information is available.
 *                           /USERS last, added to display the last 60 mins of
 *                             users that were logged.
 *                           Syslog messages and priorties have been improved.
 *                           SIGUSR1 will dump all user info for debugging.
 *                           User lockout flag has been added for the naughty
 *                             word filter.
 *                           The conversd.inf text file has undergone change
 *                             to produce a dbm version to speed up lookups.
 *                           Conversd.inf personal text will be retained if
 *                             the user logs on to another host which would
 *                             reset it.
 *                           /HOSTS has additions to request HDAT information
 *                             from a remote system.
 *                           Compile defines HOST8/HOST10 and USER8/USER10
 *                             added to specify how many significant
 *                             characters to use on user/hostnames.
 *                           /HOSTS to and /HOSTS hops have been added to HDAT.
 *                           User link are now appeneded to the bottom of the
 *                             linking list.
 *                           User sign on info is not repeated back down the
 *                             link at turnaround time.
 *
**/

/*  Defines:
 *
 *
 *  UDAT     - This is for UDAT host command support.
 *  LZW      - For Xconvers support.
 *  LOOP     - For convers server de-looper support as with JNOS.
 *  AUTO     - Auto detection of neighbour support for /..USER personal text.
 *             (More or less auto JNOS detection) (NOT COMPLETE)
 *  SYSLOG   - Syslog support.
 *  NDBM     - DBM support for the conversd.inf file.
 *  TEXT     - Old Text baised conversd.inf file support.
 *  HDAT     - Host Data support.
 *
 *  USER8    - 8 char significant usernames.
 *  USER10   - 10 char significant usernames.
 *  HOST8    - 8 char significant hostnames.
 *  HOST10   - 10 char significant hostnames.
 *
 *  Standards?  What are they?
 *
 *  - G7LED
 *
**/


/*
 * Original RCS
 *

#ifndef __lint
   #ifdef rcsid
      static char rcsid[] = "@(#) Header: conversd.c,v 2.55 93/09/22 16:44:35 deyke Exp ";
   #endif
#endif

**/
 
#include <sys/types.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <time.h>
#include <unistd.h>
#include <utmp.h>

#ifdef _AIX
#include <sys/select.h>
#endif

#if defined __hpux \
 || defined _AIX \
 || defined linux \
 || defined __386BSD__ \
 || defined __bsdi__ \
 || defined sun \
 || defined ULTRIX_RISC \
 || defined macII
   #include <sys/uio.h>
   #ifndef MAXIOV
      #define MAXIOV          16
   #endif
#else
   #undef  MAXIOV
#endif

#ifdef SYSLOG
   #include <syslog.h>
#endif

#ifdef NDBM
   #include <ndbm.h>
#endif

#ifndef SOMAXCONN
   #define SOMAXCONN       20
#endif

#ifndef S_IWOTH
   #define S_IWOTH         2
#endif

#ifndef UTMP_FILE
   #ifdef _PATH_UTMP
      #define UTMP_FILE       _PATH_UTMP
   #else
      #define UTMP_FILE       "/etc/utmp"
   #endif
#endif

#ifndef WNOHANG
   #define WNOHANG         1
#endif

#ifdef __hpux
   #define SEL_ARG(x) ((int *) (x))
#else
   #define SEL_ARG(x) (x)
#endif

extern char *optarg;
extern int errno;
extern int optind;

#define USE_MD5         1

#include "buildsaddr.h"

#if USE_MD5
   #include "md5.h"
#endif
#ifdef LZW
   #include "lzw.h"
   #include "mbuf.h"
#endif

#define HOLDTIME        (  60*60)
#define ADM_CHANNEL	-1
#define MAX_CHANNEL     32767
#define MAX_HIDLETIME   (  60*60)
#define MAX_UIDLETIME	(  10*60)
#define MAX_UNKNOWNTIME (  30*60)	/* Allow user to have a cup of coffee **/
#define MAX_WAITTIME    (3*60*60)
#define MIN_WAITTIME    (     60)
#define MAX_NOTELEN	(     43)
#define NO_NOTE         "@"
#define UNIQMARKER      (' ' | 0x80)

#define	MAXQ		15369

#define	BUFFER		2048
#define	FUZZ		1048

#ifdef LZW
   #define NORM	0
   #define LZW	1
#endif

#ifndef CONF_FILE
   #define CONF_FILE		"/etc/convers/conversd.conf"
#endif

static char *conffile = CONF_FILE;

#ifndef EXEC_FILE
   #ifdef LZW
      #define EXEC_FILE		"/etc/xconversd"
   #else
      #define EXEC_FILE		"/etc/conversd"
   #endif
#endif

#ifndef DATA_FILE
   #define DATA_FILE		"/etc/convers/conversd.inf"
#endif

#ifndef DATA_FILE_TMP
   #define DATA_FILE_TMP	"/etc/convers/conversd.tmp"
#endif

#ifndef MOTD_FILE
   #define MOTD_FILE		"/etc/convers/conversd.motd"
#endif

#ifndef NEWS_FILE
   #define NEWS_FILE		"/etc/convers/conversd.mesg"
#endif

#ifndef HELP_FILE
   #define HELP_FILE		"/etc/convers/conversd.help"
#endif

#ifdef NDBM
   #ifndef DBM_DATA_FILE
      #define DBM_DATA_FILE		"/etc/convers/conversd"
   #endif
#endif

#define NULLCHAR        ((char *) 0)

struct conv_mbuf {
  struct conv_mbuf *m_next;     /* Linked list pointer */
  char *m_data;                 /* Active working pointer */
  int m_cnt;                    /* Number of bytes in data buffer */
};
#define NULLMBUF        ((struct conv_mbuf *) 0)

struct host {
  char *h_name;                 /* Name of host */
  struct link *h_link;          /* Link to this host */
  struct host *h_next;          /* Linked list pointer */
};
#define NULLHOST        ((struct host *) 0)

struct user {
  char *u_name;                 /* Name of user */
  struct host *u_host;          /* Host where user is logged on */
  unsigned long u_seq;          /* Sequence number of this entry */
  struct link *u_link;          /* Link to this user */
  int u_channel;                /* Channel number */
  int u_oldchannel;             /* Prev channel number */
  int u_force;			/* Forced logout **/
  char *u_note;                 /* Personal note */
  long u_stime;                 /* Connect time */
  struct user *u_next;          /* Linked list pointer */
  int u_number;			/* Number of update(s) to info file **/
  unsigned long u_opt;		/* Options **/
  int u_lockout;		/* Bad boy! **/
};
#define NULLUSER        ((struct user *) 0)

struct link {
  struct user *l_user;          /* Pointer to user if user link */
  struct host *l_host;          /* Pointer to host if host link */
  int l_locked;                 /* Set if mesg already sent */
  int l_fd;                     /* Socket descriptor */
  char l_ibuf[BUFFER];          /* Input buffer */
  int l_icnt;                   /* Number of bytes in input buffer */
  long l_received;              /* Number of bytes received */
  struct conv_mbuf *l_sndq;     /* Output queue */
  long l_xmitted;               /* Number of bytes transmitted */
  long l_stime;                 /* Time of last state change */
  long l_mtime;                 /* Time of last input/output */
  struct link *l_next;          /* Linked list pointer */
  int l_lockout;		/* LOOP - Input ignore **/
#ifdef LZW
  int l_mode;			/* LZW Compression ? - G7LED **/
  struct lzw *lzw;
  struct conv_mbuf *l_zsndq;    /* Compressed output queue */
  long xl_xmitted;
  long xl_received;
#endif
#ifdef AUTO
  int l_personal;		/* This host has /..USER personal text support **/
#endif
};
#define NULLLINK        ((struct link *) 0)

struct peer {
  char *p_system;
  char *p_socket;               /* Name of socket to connect to */
  char *p_command;              /* Optional connect command */
  long p_stime;                 /* Time of last state change */
  int p_tries;                  /* Number of connect tries */
  long p_retrytime;             /* Time of next connect try */
  struct link *p_link;          /* Pointer to link if connected */
  struct peer *p_next;          /* Linked list pointer */
  int p_active;			/* Try to connect? **/
  int p_area;			/* Coverage - G7LED **/
#ifdef LZW
  int p_mode;			/* LZW Compression ? - G7LED **/
  int p_bits;			/* Number of bits to use **/
#endif
};
#define NULLPEER        ((struct peer *) 0)

struct info {
   char *i_name;
   char *i_host;
   int i_channel;
   unsigned long i_stime;
   unsigned long i_etime;
   unsigned long i_ltime;
   unsigned long i_utime;
   unsigned long i_number;
   unsigned long i_opt;
   unsigned long i_times;
   char *i_note;
};
#define NULLINFO	((struct info *) 0)

#define	I_SOUND		0x0001
#define	I_VERBOSE	0x0002
#define	I_TIMESTAMP	0x0004
#define	I_LOCKED	0x0008
#define	I_NULL		0x0010
#define	I_DEFAULT	(I_LOCKED|I_NULL)

static struct fd_set chkread;
static void (*readfnc[FD_SETSIZE])(void *);
static void *readarg[FD_SETSIZE];

static struct fd_set chkwrite;
static void (*writefnc[FD_SETSIZE])(void *);
static void *writearg[FD_SETSIZE];


static const char version[] = "G7LED_LNET v1.4A";


static int debug;

#define	DEBUG_TX	0x01
#define	DEBUG_RX	0x02
#define	DEBUG_MISC	0x04
#define	DEBUG_INFO	0x08
#define	DEBUG_UDAT	0x10
#define	DEBUG_USER	0x20
#define	DEBUG_LOOP	0x40
#define	DEBUG_PROC	0x80


#ifdef SYSLOG
static int Syslog = 3;	/* How much logging? **/
/*  1 = Minimal, errors only.
 *  2 = All possible errors.
 *  3 = Host connections and statistics.
 *  4 = User connections.
 *  5 = All users.
 *  6 = Debugging.
 **/
#endif


#ifdef NDBM
   static DBM *dbm;
#endif

#ifdef HDAT
   static char *hdattext = NULL;
#endif


static int maxfd = -1;
static int min_waittime = MIN_WAITTIME;
static long currtime;
static long starttime;
static int reason = 0;
static int terminate = 0;
static int fld = -1;
static struct host my, *hosts = &my;
static struct link *links;
static struct peer *peers;
static struct user *users;

static int is_string_unique(const char *string);
static void make_string_unique(char *string);
static struct host *hostptr(const char *name, int create);
static struct user *userptr(const char *name, struct host *hp, int create);

static int validname(const char *name);

static void trace(int outbound, struct link *lp, const char *string);
static void send_string(struct link *lp, const char *string);
static void send_bytes(struct link *lp, const char *string, int size, int ovr);
static int queuelength(const struct conv_mbuf *mp);
static void free_resources(void);
static char *getarg(char *line, int all);
static char *formatline(const char *prefix, const char *text, int prefixlen);
static char *localtimestring(long utc);
static void accept_connect_request(const int *flistenptr);
#ifdef LZW
   static void x_accept_connect_request(const int *flistenptr);
#endif
static void clear_locks(void);
static void send_user_change_msg(const struct user *up);
#ifdef UDAT
   static void send_note_change_msg(const struct user *up);
#endif
static void send_msg_to_user(const char *fromname, const char *toname, const char *text, int make_unique);
static void send_msg_to_channel(const char *fromname, int channel, char *text, int make_unique);
static void send_invite_msg(const char *fromname, const char *toname, int channel, const char *text, int make_unique);
static void connect_peers(void);

static void close_link(struct link *lp);
static void channel_command(struct link *lp);
static void help_command(struct link *lp);
static void hosts_command(struct link *lp);
static void invite_command(struct link *lp);
static void links_command(struct link *lp);
static void msg_command(struct link *lp);
static void note_command(struct link *lp);
static void peers_command(struct link *lp);
static void query_command(struct link *lp);
static void name_command(struct link *lp);
static void sound_command(struct link *lp);
static void users_command(struct link *lp);
static void who_command(struct link *lp);

static void h_cmsg_command(struct link *lp);
static void h_host_command(struct link *lp);
static void h_invi_command(struct link *lp);
static void h_umsg_command(struct link *lp);
static void h_user_command(struct link *lp);
#ifdef UDAT
   static void h_udat_command(struct link *lp);
#endif
#ifdef LOOP
   static void h_loop_command(struct link *lp);
#endif
#ifdef HDAT
   static void h_hdat_command(struct link *lp);
#endif
static void h_thru_command(struct link *lp);

static void process_input(struct link *lp);
static void read_configuration(void);
static void check_files_changed(char **argv);
static void link_recv(struct link *lp);
#ifdef LZW
static void x_link_recv(struct link *lp);
#endif
static void link_send(struct link *lp);
#ifdef LZW
static void x_link_send(struct link *lp);
#endif

static void userinfo_update(struct user *up);
static void userinfo_free(struct info *in);
static int userinfo_put(struct info *in);
#if (!defined(NDBM)) || (defined(TEXT))
static int userinfo_del(char *name);
#endif
static struct info *userinfo_get(char *name);

static char *make_opt(struct info *in);
static void get_opt(struct info *in, char *cp);

static void kick_connects(int sig);
static void save_info(int sig);
static void advise_shutdown(int sig);
static void dumpuser_info(int sig);


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

/* Work around bug in prototype generator */

/*
static void dummy(void)
{
}
*/

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

static int is_string_unique(const char *string)
{

#if USE_MD5

  struct string {
    struct string *s_next;
    long s_time;
    unsigned char s_digest[16];
  };

  MD5_CTX mdContext;
  int i;
  static struct string *strings[256];
  static struct string *stringstail[256];
  struct string *sp;

  MD5Init(&mdContext);
  MD5Update(&mdContext, string, strlen(string));
  MD5Final(&mdContext);
  i = mdContext.digest[0] & 0xff;

  while ((sp = strings[i]) && sp->s_time + HOLDTIME < currtime) {
    strings[i] = sp->s_next;
    free(sp);
  }

  for (sp = strings[i]; sp; sp = sp->s_next)
    if (!memcmp((char *) sp->s_digest, (char *) mdContext.digest, sizeof(mdContext.digest)))
      return 0;

  sp = (struct string *)malloc(sizeof(*sp));
  sp->s_next = 0;
  sp->s_time = currtime;
  memcpy((char *) sp->s_digest, (char *) mdContext.digest, sizeof(mdContext.digest));
  if (strings[i])
    stringstail[i]->s_next = sp;
  else
    strings[i] = sp;
  stringstail[i] = sp;
  return 1;

#else

  static const unsigned short crc_table[] = {
    0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
    0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
    0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
    0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
    0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
    0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
    0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
    0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
    0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
    0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
    0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
    0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
    0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
    0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
    0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
    0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
    0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
    0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
    0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
    0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
    0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
    0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
    0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
    0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
    0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
    0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
    0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
    0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
    0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
    0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
    0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
    0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
  };

  struct string {
    struct string *s_next;
    long s_time;
    unsigned short s_len;
    unsigned short s_sum;
    unsigned short s_crc;
  };

  const char *cp;
  int i;
  static struct string *strings[256];
  static struct string *stringstail[256];
  struct string *sp;
  unsigned short crc = 0;
  unsigned short len = 0;
  unsigned short sum = 0;

  for (cp = string; *cp; cp++) {
    len++;
    sum += (*cp & 0xff);
    crc = (crc >> 8) ^ crc_table[(crc ^ *cp) & 0xff];
  }
  i = crc & 0xff;

  while ((sp = strings[i]) && sp->s_time + HOLDTIME < currtime) {
    strings[i] = sp->s_next;
    free(sp);
  }

  for (sp = strings[i]; sp; sp = sp->s_next)
    if (sp->s_len == len && sp->s_sum == sum && sp->s_crc == crc) return 0;

  sp = (struct string *)malloc(sizeof(*sp));
  sp->s_next = 0;
  sp->s_time = currtime;
  sp->s_len = len;
  sp->s_sum = sum;
  sp->s_crc = crc;
  if (strings[i])
    stringstail[i]->s_next = sp;
  else
    strings[i] = sp;
  stringstail[i] = sp;
  return 1;

#endif

}

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

static void make_string_unique(char *string)
{

  char *cp;
  int i;

  if (is_string_unique(string)) return;
  cp = strchr(string, UNIQMARKER);
  if (cp) {
    cp++;
    i = atoi(cp);
  } else {
    for (cp = string; *cp; cp++) ;
    if (cp > string && cp[-1] == '\n') cp--;
    *cp++ = UNIQMARKER;
    i = 0;
  }
  for (; ; ) {
    sprintf(cp, "%d\n", ++i);
    if (is_string_unique(string)) return;
  }
}

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

static struct host *hostptr(const char *name, int create)
{

  int r;
  struct host **hpp;
  struct host *hp;
  
  if(validname(name))
     return 0;

  for (hpp = &hosts; ; hpp = &hp->h_next) {
    hp = *hpp;
    if (!hp || (r = 
#if	defined(HOST8)
    strncmp(hp->h_name, name, 8)
#elif	defined(HOST10)
    strncmp(hp->h_name, name, 10)
#else
    strcmp(hp->h_name, name)
#endif
    ) >= 0) {
      if ((!hp || r) && create) {
	hp = (struct host *) calloc(1, sizeof(*hp));
	hp->h_name = strdup((char *) name);
	hp->h_next = *hpp;
	hp->h_link = 0;
	*hpp = hp;
      }
      return hp;
    }
  }
}

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

static struct user *userptr(const char *name, struct host *hp, int create)
{

  int r;
  struct user **upp;
  struct user *up;
  
  if(!hp)
     return 0;

  if(validname(name))
     return 0;

  for (upp = &users; ; upp = &up->u_next) {
    up = *upp;
    if (!up ||
	(r =
#if	defined(USER8)
         strncmp(up->u_name, name, 8)
#elif	defined(USER10)
         strncmp(up->u_name, name, 10)
#else
         strcmp(up->u_name, name)
#endif
           ) > 0 ||
	(!r && (r =
#if	defined(HOST8)
         strncmp(up->u_host->h_name, hp->h_name, 8)
#elif	defined(HOST10)
         strncmp(up->u_host->h_name, hp->h_name, 10)
#else
         strcmp(up->u_host->h_name, hp->h_name)
#endif
           ) >= 0)) {
      if ((!up || r) && create) {
	up = (struct user *) calloc(1, sizeof(*up));
	up->u_name = strdup((char *) name);
	up->u_host = hp;
	up->u_channel = up->u_oldchannel = -1;
	up->u_note = strdup(NO_NOTE);
	up->u_stime = currtime;
	up->u_next = *upp;
	up->u_opt = I_DEFAULT;
	up->u_seq = 0;		/* Paranoid **/
	up->u_force = 0;	/* Paranoid **/
	up->u_lockout = 0;	/* Paranoid **/
	up->u_link = 0;
	*upp = up;
      }
      return up;
    }
  }
}

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

static int
validname(name)
const char *name;
{
   const unsigned char *p;
   
   for(p = name; *p && (isalnum(*p) || ispunct(*p) || isspace(*p)) && *p < 128; p++)
      ;
      
   if(*p)
      return 1;
   else
      return 0;
}

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

static void trace(int outbound, struct link *lp, const char *string)
{
   char *cp;
   const unsigned char *c;
   char *name;
   char buf[16];
   char buffer[BUFFER+FUZZ];

   for(c = string, cp = buffer; ; c++, cp++) {
      if(!*c) {
         *cp = *c;
         break;
      }
      if((isalnum(*c) || ispunct(*c)) && *c < 128)
         *cp = *c;
      else
         *cp = ' ';
   }
   if(lp->l_user)
      name = lp->l_user->u_name;
   else if(lp->l_host)
      name = lp->l_host->h_name;
   else
      sprintf(name = buf, "%p", lp);
   cp = ctime(&currtime);
   cp[24] = 0;
   if(outbound)
      printf("\n%s SEND %s->%s:\n%s", cp, my.h_name, name, string);
   else
      printf("\n%s RECV %s->%s:\n%s", cp, name, my.h_name, string);
   fflush(stdout);
  
#ifdef SYSLOG
   if(Syslog >= 6) {
      if(outbound && lp->l_user) {
         syslog(LOG_DEBUG, "U %s >> %s", name, buffer);
      } else if(lp->l_user) {
         syslog(LOG_DEBUG, "U %s << %s", name, buffer);
      } else if(outbound && lp->l_host) {
         syslog(LOG_DEBUG, "H %s >> %s", name, buffer);
      } else if(lp->l_host) {
         syslog(LOG_DEBUG, "H %s << %s", name, buffer);
      } else if(outbound) {
         syslog(LOG_DEBUG, "? %s >> %s", name, buffer);
      } else {
         syslog(LOG_DEBUG, "? %s << %s", name, buffer);
      }
   }
#endif

}

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

static void send_string(struct link *lp, const char *string)
{

  int len;
  struct conv_mbuf *mp;
  struct conv_mbuf *p;

  if (!*string) return;
  if (debug & DEBUG_TX) trace(1, lp, string);
  len = strlen(string);
  mp = (struct conv_mbuf *)malloc(sizeof(*mp) + len);
  mp->m_next = 0;
  memcpy(mp->m_data = (char *)(mp + 1), string, mp->m_cnt = len);
  if (lp->l_sndq) {
    for (p = lp->l_sndq; p->m_next; p = p->m_next) ;
    p->m_next = mp;
  } else {
    lp->l_sndq = mp;
#ifndef LZW
    writefnc[lp->l_fd] = (void (*)()) link_send;
#else
    if(lp->l_mode == NORM)
       writefnc[lp->l_fd] = (void (*)()) link_send;
    else
       writefnc[lp->l_fd] = (void (*)()) x_link_send;
#endif
    writearg[lp->l_fd] = lp;
    FD_SET(lp->l_fd, &chkwrite);
  }
  lp->l_locked = 1;

  /* Check the queuelength is not stupid, maybe some lower down protocol
     refuses to disconnect this link. **/
#ifdef LZW
  if(lp->l_mode == NORM) {
#endif
     if(queuelength(lp->l_sndq) > MAXQ)
        close_link(lp);
#ifdef LZW
  } else {
     if(queuelength(lp->l_zsndq) > MAXQ)
        close_link(lp);
  }
#endif
}

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

/* If this function is used on a compressed link, (hence 'ovr' is set to
 *  force it out uncompressed, such as is used with LNET to get the
 *  "binary\nconnect tcp <host> <port>\n" out through the FS suckit) then if
 *  the write in link_send fails everything will go to pot.  As the
 *  information does not get retried if it all does not get written, an error
 *  will more than likely occur LNET from the compressed data and it'll close
 *  the suckit from its end.
 *  
 *  Perhaps provision could be added later for the data to be retired in the
 *  uncompressed buffer...                                    - G7LED Darryl
**/

static void send_bytes(struct link *lp, const char *string, int size, int ovr)
{
  struct conv_mbuf *mp;
  struct conv_mbuf *p;

  if (debug & DEBUG_TX) trace(1, lp, string);
  mp = (struct conv_mbuf *)malloc(sizeof(*mp) + size);
  mp->m_next = 0;
  memcpy(mp->m_data = (char *) (mp + 1), string, mp->m_cnt = size);
  if (lp->l_sndq) {
    for (p = lp->l_sndq; p->m_next; p = p->m_next) ;
    p->m_next = mp;
  } else {
    lp->l_sndq = mp;
#ifndef LZW
    writefnc[lp->l_fd] = (void (*)()) link_send;
#else
    if(lp->l_mode == NORM)
       writefnc[lp->l_fd] = (void (*)()) link_send;
    else
       writefnc[lp->l_fd] = (void (*)()) x_link_send;
#endif
    writearg[lp->l_fd] = lp;
    FD_SET(lp->l_fd, &chkwrite);
  }
  lp->l_locked = 1;

  /* Check the queuelength is not stupid, maybe some lower down protocol
     refuses to disconnect this link. **/
#ifdef LZW
  if(lp->l_mode == NORM) {
#endif
     if(queuelength(lp->l_sndq) > MAXQ)
        close_link(lp);
#ifdef LZW
  } else {
     if(queuelength(lp->l_zsndq) > MAXQ)
        close_link(lp);
  }
#endif

  if(ovr)
     link_send(lp);
}

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

static int queuelength(const struct conv_mbuf *mp)
{
  int len;

  for (len = 0; mp; mp = mp->m_next)
    len += mp->m_cnt;
  return len;
}

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

static void free_resources(void)
{
   struct link **lpp;
   struct link *lp;
   struct conv_mbuf *mp;
   struct user **upp;
   struct user *up;
  
   for(lpp = &links; (lp = *lpp); ) {
      if(lp->l_user) {
         if(lp->l_mtime + MAX_UIDLETIME < currtime) {
            if(lp->l_user->u_opt & I_NULL) {
               send_bytes(lp, "\000", 1, 0);
               /* Reset the time which the ldler works from - G7LED **/
               lp->l_mtime = currtime;
#ifdef SYSLOG
               if(Syslog >= 4)
                  syslog(LOG_NOTICE, "UIDLE: Idle link, pip'ed, to %s", lp->l_user->u_name);
#endif
            }
         }
      } else if(lp->l_host) {
         if(lp->l_mtime + MAX_HIDLETIME < currtime) {
            send_string(lp, "\n");
            /* Reset the time which the ldler works from - G7LED **/
            lp->l_mtime = currtime;
#ifdef SYSLOG
         if(Syslog >= 2)
            syslog(LOG_NOTICE, "HIDLE: Idle link, pip'ed, to %s", lp->l_host->h_name);
#endif
         }
      } else if(lp->l_fd >= 0) {
         if(lp->l_stime + MAX_UNKNOWNTIME < currtime) {
            close_link(lp);
#ifdef SYSLOG
            if(Syslog >= 1)
               syslog(LOG_NOTICE, "IDLE: Idle unknown link, closed");
#endif
         }
      }
      if(lp->l_fd < 0) {
         *lpp = lp->l_next;
         while((mp = lp->l_sndq)) {
            lp->l_sndq = mp->m_next;
            free(mp);
         }
         free(lp);
      } else
         lpp = &lp->l_next;
      }

      for(upp = &users; (up = *upp); )
         if(up->u_channel < 0 && up->u_stime + HOLDTIME < currtime) {
            *upp = up->u_next;
            free(up->u_name);
            free(up->u_note);
            free(up);
         } else
            upp = &up->u_next;
}

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

static char *getarg(char *line, int all)
{

  char *arg;
  static char *cp;

  if (line) {
    for (cp = line; *cp; cp++) ;
    while (--cp >= line && isspace(*cp & 0xff)) ;
    cp[1] = 0;
    cp = line;
  }
  while (isspace(*cp & 0xff)) cp++;
  if (all) return cp;
  arg = cp;
  while (*cp && !isspace(*cp & 0xff)) {
    if (*cp >= 'A' && *cp <= 'Z') *cp = tolower(*cp);
    cp++;
  }
  if (*cp) *cp++ = 0;
  return arg;
}

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

/* Returns a formatted version of the given text.
 * The prefix appears at the beginning of the text, and each
 * line after the first will be indented to column PREFIXLEN.
 * All whitespace (SPACE and TAB) will be replaced by a single
 * SPACE character,
 * Lines will be filled to be CONVLINELEN characters long, wrapping
 * words as necessary.
 */

static char *formatline(const char *prefix, const char *text, int prefixlen)
{

#define PREFIXLEN       10
#define CONVLINELEN     79

  char *e, *t;
  const char *f, *x;
  int l, lw;
  static char buf[BUFFER];

  e = buf + (sizeof(buf) - 2);

  /* Copy prefix into buf, set l to length of prefix */
  for (f = prefix, t = buf; *f; f++)
    if (t < e) *t++ = *f;
  l = t - buf;

  for (f = text; ; ) {

    /* Skip leading spaces */
    while (isspace(*f & 0xff)) f++;

    /* Return if nothing more or no room left */
    if (!*f || t >= e) {
      *t++ = '\n';
      *t = 0;
      return buf;
    }

    /* Find length of next word (seq. of non-blanks) */
    for (x = f; *x && !isspace(*x & 0xff); x++) ;
    lw = x - f;

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

    /* Put out a single space, or indent to column PREFIXLEN (prefixlen) */
    do {
      if (t < e) *t++ = ' ';
      l++;
    } while (l < prefixlen);

    /* Put out the word */
    while (lw--) {
      if (t < e) *t++ = *f++;
      l++;
    }
  }
}


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

static char *localtimestring(long utc)
{

  static char buffer[8];
  static const char monthnames[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
  struct tm *tm;

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

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

/* Watch out for the slow connection accept call. **/

static void accept_connect_request(const int *flistenptr)
{

  int addrlen;
  int fd;
  int flags;
  struct link *lp;
  struct sockaddr addr;

  addrlen = sizeof(addr);
  if ((fd = accept(*flistenptr, &addr, &addrlen)) < 0) return;
  if ((flags = fcntl(fd, F_GETFL, 0)) == -1 ||
      fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
    close(fd);
    return;
  }
  lp = (struct link *) calloc(1, sizeof(*lp));
  lp->l_fd = fd;
  lp->l_stime = lp->l_mtime = currtime;
  lp->l_lockout = 0;
#ifdef LZW
  lp->l_mode = NORM;
#endif
#ifdef AUTO
  lp->l_personal = 1;
#endif
  lp->l_next = links;
  links = lp;
  readfnc[fd] = (void (*)()) link_recv;
  readarg[fd] = lp;
  FD_SET(fd, &chkread);
  if (maxfd < fd) maxfd = fd;
}

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

#ifdef LZW

/* Watch out for the slow connection accept call. **/

static void x_accept_connect_request(const int *flistenptr)
{

  int addrlen;
  int fd;
  int flags;
  struct link *lp;
  struct sockaddr addr;

  addrlen = sizeof(addr);
  if ((fd = accept(*flistenptr, &addr, &addrlen)) < 0) return;
  if ((flags = fcntl(fd, F_GETFL, 0)) == -1 ||
      fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
    close(fd);
    return;
  }
  lp = (struct link *) calloc(1, sizeof(*lp));
  lp->l_fd = fd;
  lp->l_stime = lp->l_mtime = currtime;

  lp->l_lockout = 0;
  lp->l_mode = LZW;
  lp->lzw = LZW_Init(16, LZWCOMPACT);

#ifdef AUTO
  lp->l_personal = 1;
#endif
  lp->l_next = links;
  links = lp;
  readfnc[fd] = (void (*)()) x_link_recv;
  readarg[fd] = lp;
  FD_SET(fd, &chkread);
  if (maxfd < fd) maxfd = fd;
}

#endif /* LZW **/

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

static void clear_locks(void)
{
  struct link *lp;

  for (lp = links; lp; lp = lp->l_next) lp->l_locked = 0;
}

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

static void send_user_change_msg(const struct user *up)
{

  char buffer[BUFFER+FUZZ];
  struct link *lp;


  for (lp = links; lp; lp = lp->l_next) {
    if (!lp->l_locked) {
      if (lp->l_user && up->u_oldchannel != up->u_channel) {
	if (lp->l_user->u_channel == up->u_oldchannel) {
	  if (up->u_channel >= 0)
	    sprintf(buffer, "*** %s switched to channel %d.\n", up->u_name, up->u_channel);
	  else {
	    if(up->u_force)
	      sprintf(buffer, "*** %s forced off.\n", up->u_name);
	    else
	      sprintf(buffer, "*** %s signed off.\n", up->u_name);
	  }
	  send_string(lp, buffer);
	} else if (lp->l_user->u_channel == up->u_channel) {
	  sprintf(buffer,
	     "*** %s signed on%s\n",
	     up->u_name,
	     (lp->l_user->u_opt & I_SOUND) ? ".\007" : "." );
	  send_string(lp, buffer);
	}
      } else if (lp->l_host) {
#ifdef AUTO
        if(lp->l_personal == 1) {
#endif
	  sprintf(buffer,
		"/\377\200USER %s %s %lu %d %d %s\n",
		up->u_name,
		up->u_host->h_name,
		up->u_seq,
		(up->u_force) ? -1 : up->u_oldchannel,
		up->u_channel,
		up->u_channel >= 0 ? up->u_note : "");
	  send_string(lp, buffer);
#ifdef AUTO
	} else {
	  sprintf(buffer,
		"/\377\200USER %s %s %lu %d %d\n",
		up->u_name,
		up->u_host->h_name,
		up->u_seq,
		(up->u_force) ? -1 : up->u_oldchannel,
		up->u_channel);
	  send_string(lp, buffer);
#ifdef UDAT
          if(up->u_oldchannel == -1 && up->u_channel >= 0 &&
           strcmp(up->u_note, NO_NOTE)) {
	    sprintf(buffer,
		"/\377\200UDAT %s %s %s\n",
		up->u_name,
		up->u_host->h_name,
		up->u_note);
	    send_string(lp, buffer);
	  }
#endif
	}
#endif /* AUTO **/
      }
    }
  }
}

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

#ifdef UDAT

static void send_note_change_msg(const struct user *up)
{

  char buffer[BUFFER+FUZZ];
  struct link *lp;

  for (lp = links; lp; lp = lp->l_next) {
    if (!lp->l_locked) {
      if (lp->l_host) {
	sprintf(buffer,
		"/\377\200UDAT %s %s %s\n",
		up->u_name,
		up->u_host->h_name,
		up->u_note);
	send_string(lp, buffer);
      }
    }
  }
}

#endif

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

static void send_msg_to_user(const char *fromname, const char *toname, const char *text, int make_unique)
{

  char *cp;
  char *formatted_line;
  char host_buffer[BUFFER+FUZZ];
  char prefix[BUFFER];
  struct link *lp;
  struct user *up;

  for(up = users; up &&
#if	defined(USER8)
   strncmp(up->u_name, fromname, 8)
#elif	defined(USER10)
   strncmp(up->u_name, fromname, 10)
#else
   strcmp(up->u_name, fromname)
#endif
    ; up = up->u_next)
     ;
  if(up && up->u_lockout)
     return;
  else if(strcasecmp("conversd", fromname) && !up) {
#ifdef SYSLOG
   if(Syslog >= 2)
      syslog(LOG_WARNING, "UMSG: from an unknown user (%s)", fromname);
#endif
  }
  if (!*text) return;
  sprintf(host_buffer, "/\377\200UMSG %s %s %s\n", fromname, toname, text);
  if (make_unique)
    make_string_unique(host_buffer);
  else if (!is_string_unique(host_buffer))
    return;
  if ((cp = strchr(text, UNIQMARKER))) *cp = 0;
  if (strcmp(fromname, "conversd")) {
    sprintf(prefix, "<*%s*>:", fromname);
    formatted_line = formatline(prefix, text, PREFIXLEN);
  } else {
    sprintf(prefix, "%s\n", text);
    formatted_line = prefix;
  }
  for (up = users; up; up = up->u_next)
    if (up->u_channel >= 0) {
      lp = up->u_link;
      if (!lp->l_locked &&
#if	defined(USER8)
       !strncmp(up->u_name, toname, 8)
#elif	defined(USER10)
       !strncmp(up->u_name, toname, 10)
#else
       !strcmp(up->u_name, toname)
#endif
        ) {
	send_string(lp, lp->l_host ? host_buffer : formatted_line);
	return;
      }
    }
    
  if(debug & DEBUG_MISC) printf("*** - Warning user (%s) is unknown\n", toname);

#ifdef SYSLOG
   if(Syslog >= 2)
      syslog(LOG_WARNING, "UMSG: to an unknown user (%s)", toname);
#endif

}

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

static void send_msg_to_channel(const char *fromname, int channel, char *text, int make_unique)
{

  char *cp;
  char *formatted_line;
  char host_buffer[BUFFER+FUZZ];
  char prefix[BUFFER];
  struct link *lp;
  struct user *up;

  for(up = users; up &&
#if	defined(USER8)
   strncmp(up->u_name, fromname, 8)
#elif	defined(USER10)
   strncmp(up->u_name, fromname, 10)
#else
   strcmp(up->u_name, fromname)
#endif
    ; up = up->u_next)
     ;
  if(up && up->u_lockout)
    return;
  else if(strcasecmp("conversd", fromname) && !up) {
#ifdef SYSLOG
   if(Syslog >= 2)
      syslog(LOG_WARNING, "CMSG: from an unknown user (%s)", fromname);
#endif
  }
  if (!*text) return;
  sprintf(host_buffer, "/\377\200CMSG %s %d %s\n", fromname, channel, text);
  if (make_unique)
    make_string_unique(host_buffer);
  else if (!is_string_unique(host_buffer))
    return;
  if ((cp = strchr(text, UNIQMARKER))) *cp = 0;
  sprintf(prefix, "<%s>:", fromname);
  formatted_line = formatline(prefix, text, PREFIXLEN);
  for (up = users; up; up = up->u_next)
    if (up->u_channel >= 0) {
      lp = up->u_link;
      if (!lp->l_locked && up->u_channel == channel)
	send_string(lp, lp->l_host ? host_buffer : formatted_line);
    }
    
  if(channel == -1) {

#ifdef SYSLOG
   if(Syslog >= 1)
      syslog(LOG_NOTICE, "ADMIN: %s -> %s", fromname, text);
#endif

    for (lp = links; lp; lp = lp->l_next)
      if(lp->l_host)
        send_string(lp, host_buffer);
  }
}

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

static void send_invite_msg(const char *fromname, const char *toname, int channel, const char *text, int make_unique)
{

   static const char invitetext[] =
   "\n\007*** Invitation from %s at %s ... Please join convers channel %d.\007\n";

   static const char invitetext2[] =
   "*** Message: %-0.67s\n";

   static const char responsetext[] =
   "*** Invitation sent to %s @ %s.";

   char buffer[BUFFER+FUZZ], buffer2[BUFFER+FUZZ];
   char host_buffer[BUFFER+FUZZ];
   char *p;
   int fdtty;
   int fdut;
   struct link *lp;
   struct stat statbuf;
   struct user *up;
   struct utmp utmpbuf;

/*   if(!text || !*text)
      text = invitetext2;*/
      
   /* Setup host_buffer - G7LED **/
   sprintf(host_buffer, "/\377\200INVI %s %s %d %s\n", fromname, toname, channel, text);
   if(make_unique)
      make_string_unique(host_buffer);
   else if(!is_string_unique(host_buffer))
      return;

   /* Let see if the @ is in the invite username, if so process this to
      check for our hostname, if it matches then continue, if not then
      dump it to all host connection bar the one it came from,
      the lock will already be setup. - G7LED **/
   if((p = strchr(toname, '@')) != NULL) {
      *p++ = '\0';
      if(strcasecmp(p, my.h_name)) {
         for(lp = links; lp; lp = lp->l_next) {
            if(lp->l_host && !lp->l_locked)
               send_string(lp, host_buffer);
         }
         return;
      }
   }

   /* Is the user is on the channel requested anywhere on the convers system
      - G7LED **/
   for(up = users; up; up = up->u_next)
      if(up->u_channel == channel &&
#if	defined(USER8)
       !strncmp(up->u_name, toname, 8)
#elif	defined(USER10)
       !strncmp(up->u_name, toname, 10)
#else
       !strcmp(up->u_name, toname)
#endif
        ) {
         clear_locks();
         sprintf(buffer, "*** User %s is already on this channel.", toname);
         send_msg_to_user("conversd", fromname, buffer, 1);
         return;
      }

   /* If the user is locally connected to convers - G7LED **/
   for(up = users; up; up = up->u_next)
      if(up->u_channel >= 0 && up->u_link->l_user &&
#if	defined(USER8)
       !strncmp(up->u_name, toname, 8)
#elif	defined(USER10)
       !strncmp(up->u_name, toname, 10)
#else
       !strcmp(up->u_name, toname)
#endif
        ) {
         sprintf(buffer, invitetext, fromname, localtimestring(currtime), channel);
         send_string(up->u_link, buffer);
         if(*text) {
            sprintf(buffer, invitetext2, text);
            send_string(up->u_link, buffer);
         }
         clear_locks();
         sprintf(buffer, responsetext, toname, my.h_name);
         send_msg_to_user("conversd", fromname, buffer, 1);
#ifdef SYSLOG
         if(Syslog >= 2)
            syslog(LOG_NOTICE, "INVI: Sent locally for a user of conversd to (%s) from (%s)", toname, fromname);
#endif
         return;
      }

   /* Send to all host link, who have this user name signed on - G7LED **/
   for (up = users; up; up = up->u_next)
      if (up->u_channel >= 0 && !up->u_link->l_locked &&
#if	defined(USER8)
       !strncmp(up->u_name, toname, 8)
#elif	defined(USER10)
       !strncmp(up->u_name, toname, 10)
#else
       !strcmp(up->u_name, toname)
#endif
        ) {
#ifdef SYSLOG
      if(Syslog >= 5)
         syslog(LOG_DEBUG, "INVI: Sent remotely to host (%s) from (%s) for (%s)", up->u_host->h_name, toname, fromname);
#endif
         send_string(up->u_link, host_buffer);
         return;
      }

   /* Send to any users of the system, who are logged in with that name
      - G7LED **/
   if ((fdut = open(UTMP_FILE, O_RDONLY, 0644)) >= 0) {
      while (read(fdut, &utmpbuf, sizeof(utmpbuf)) == sizeof(utmpbuf))
         if (
#ifdef USER_PROCESS
           utmpbuf.ut_type == USER_PROCESS &&
#endif
           !strncmp(utmpbuf.ut_name, toname, sizeof(utmpbuf.ut_name))) {
            strcpy(buffer, "/dev/");
            strncat(buffer, utmpbuf.ut_line, sizeof(utmpbuf.ut_line));
            if (stat(buffer, &statbuf)) continue;
            if (!(statbuf.st_mode & S_IWOTH)) continue;
            if ((fdtty = open(buffer, O_WRONLY | O_NOCTTY, 0644)) < 0) continue;
            sprintf(buffer, invitetext, fromname, localtimestring(currtime), channel);
            sprintf(buffer2, invitetext, text);
            if (!fork()) {
               write(fdtty, buffer, strlen(buffer));
               write(fdtty, buffer2, strlen(buffer2));
               _exit(0);
            }
            close(fdtty);
            close(fdut);
            clear_locks();
            sprintf(buffer, responsetext, toname, my.h_name);
            send_msg_to_user("conversd", fromname, buffer, 1);
            return;
         }
      close(fdut);
   }

   /* Send invite message down all host connection - G7LED **/
   for (lp = links; lp; lp = lp->l_next)
      if (lp->l_host && !lp->l_locked)
         send_string(lp, host_buffer);
}

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

static void connect_peers(void)
{

  char *cp1;
  char *cp;
  char buffer[BUFFER+FUZZ];
  int addrlen;
  int fd;
  int flags;
  int wt;
  struct link *lp;
  struct peer *pp;
  struct sockaddr *addr;

  for (pp = peers; pp; pp = pp->p_next) {
    if (!pp->p_active) continue;	/* We don't attempt connection **/
    if (pp->p_link || pp->p_retrytime > currtime) continue;
    wt = min_waittime * (1 << pp->p_tries);
    pp->p_tries++;
    if (pp->p_tries >= 16 || wt > MAX_WAITTIME) wt = MAX_WAITTIME;
    pp->p_retrytime = currtime + wt;
    if (!(addr = build_sockaddr(pp->p_socket, &addrlen))) continue;
    if ((fd = socket(addr->sa_family, SOCK_STREAM, 0)) < 0) continue;
    if ((flags = fcntl(fd, F_GETFL, 0)) == -1 ||
	fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
      close(fd);
      continue;
    }
    if (connect(fd, addr, addrlen) && errno != EINPROGRESS) {
      if (debug & DEBUG_MISC) perror("connect()");
#ifdef SYSLOG
      if(Syslog >= 1)
         syslog(LOG_ERR, "CONNECT: error during peer connect to system %s", pp->p_system);
#endif
      close(fd);
      continue;
    }
#ifdef SYSLOG
    if(Syslog >= 3)
       syslog(LOG_NOTICE, "CONNECT: attempting connection to %s", pp->p_system);
#endif
    lp = pp->p_link = (struct link *) calloc(1, sizeof(*lp));
    lp->l_fd = fd;
    lp->l_stime = lp->l_mtime = currtime;
    lp->l_lockout = 0;
    lp->l_personal = 1;
#ifdef LZW
    if(pp->p_mode == LZW) {
       lp->l_mode = LZW;
       lp->lzw = LZW_Init(pp->p_bits, LZWCOMPACT);
    } else
       lp->l_mode = NORM;
#endif
    lp->l_next = links;
    links = lp;
#ifndef LZW
    readfnc[fd] = (void (*)()) link_recv;
#else
    if(lp->l_mode == NORM)
       readfnc[fd] = (void (*)()) link_recv;
    else
       readfnc[fd] = (void (*)()) x_link_recv;
#endif
    readarg[fd] = lp;
    FD_SET(fd, &chkread);
    if (maxfd < fd) maxfd = fd;
    *buffer = 0;
    strcat(buffer, "binary\n");		/* Stop translating CR/LF's **/
    cp = pp->p_command;
    while (*cp) {
      if ((cp1 = strstr(cp, "\\n"))) {
	strncat(buffer, cp, cp1 - cp);
	cp = cp1 + 2;
      } else {
	strcat(buffer, cp);
	cp = "";
      }
      strcat(buffer, "\n");
    }
    send_bytes(lp, buffer, strlen(buffer), 1);
    lp->l_xmitted -= strlen(buffer);		/* Time to be perfect :-) **/
    strcpy(buffer, "convers\n/\377\200HOST ");
    strcat(buffer, my.h_name);
    strcat(buffer, "\n");
    send_string(lp, buffer);
  }
}

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

static void close_link(struct link *lp)
{

  int fd, host = 0;
  struct host *hp;
  struct peer *pp;
  struct user *up;

#ifdef SYSLOG
   if(Syslog >= 3) {
#ifdef LZW
      if(lp->l_host && lp->l_mode == LZW) {
         syslog(LOG_NOTICE, "HLINK: close to %s, tx %lu, rx %lu; LZW tx %lu, rx %lu",
            lp->l_host->h_name, lp->l_xmitted, lp->l_received,
            lp->xl_xmitted, lp->xl_received);
      } else
#endif
         if(lp->l_host) {
            syslog(LOG_NOTICE, "HLINK: close to %s, tx %lu, rx %lu",
               lp->l_host->h_name, lp->l_xmitted, lp->l_received);
      }
   }
   if(Syslog >= 4) {
#ifdef LZW
      if(lp->l_user && lp->l_mode == LZW) {
         syslog(LOG_NOTICE, "ULINK: close to %s, tx %lu, rx %lu; LZW tx %lu, rx %lu",
            lp->l_user->u_name, lp->l_xmitted, lp->l_received,
            lp->xl_xmitted, lp->xl_received);
      } else
#endif
         if(lp->l_user) {
            syslog(LOG_NOTICE, "ULINK: close to %s, tx %lu, rx %lu",
               lp->l_user->u_name, lp->l_xmitted, lp->l_received);
      }
   }
#endif

 #ifdef LZW
  if(lp->l_mode == LZW) {
     if(lp->lzw)
        LZW_Free(lp->lzw);
  }
#endif

  if ((fd = lp->l_fd) >= 0) {
    readfnc[fd] = 0;
    FD_CLR(fd, &chkread);
    writefnc[fd] = 0;
    FD_CLR(fd, &chkwrite);
    if (fd == maxfd)
      while (--maxfd >= 0)
	if (FD_ISSET(maxfd, &chkread)) break;
    close(fd);
    lp->l_fd = -1;
  }
  
  lp->l_user = 0;
  if(lp->l_host)
     host = 1;
  lp->l_host = 0;

  for (pp = peers; pp; pp = pp->p_next)
    if (pp->p_link == lp) {
      pp->p_stime = currtime;
      if (pp->p_retrytime < currtime + min_waittime)
	pp->p_retrytime = currtime + min_waittime;
      pp->p_link = 0;
    }

  for (up = users; up; up = up->u_next)
    if (up->u_channel >= 0 && up->u_link == lp) {
      if (up->u_seq) {
	up->u_seq++;
	if (up->u_host == &my && up->u_seq < currtime) up->u_seq = currtime;
      }
      up->u_link = 0;
      if(host)
         up->u_force = 1;	/* We're forcing these off, link broke **/

      up->u_oldchannel = up->u_channel;
      up->u_channel = -1;
      userinfo_update(up);	/* Save logout time, and user note **/
      up->u_stime = currtime;
      clear_locks();
      send_user_change_msg(up);	/* Send /..USER <u> <h> <o> -1 **/
      if(up->u_seq > 2)		/* 3 or more, then alter. **/
         up->u_seq -= 2;		/* Fix the sequence number at this host **/
    }

  for (hp = hosts; hp; hp = hp->h_next)
    if (hp->h_link == lp) hp->h_link = 0;

}

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

static void channel_command(struct link *lp)
{

  char *cp;
  char buffer[BUFFER+FUZZ];
  int newchannel;
  struct user *up;

  up = lp->l_user;
  cp = getarg(NULLCHAR, 0);
  if (!*cp) {
    sprintf(buffer, "*** You are on channel %d.\n", up->u_channel);
    send_string(lp, buffer);
    return;
  }
  newchannel = atoi(cp);
  if (newchannel < 0 || newchannel > MAX_CHANNEL) {
    sprintf(buffer, "*** Channel numbers must be in the range 0..%d.\n", MAX_CHANNEL);
    send_string(lp, buffer);
    return;
  }
  if (newchannel == up->u_channel) {
    sprintf(buffer, "*** Already on channel %d.\n", up->u_channel);
    send_string(lp, buffer);
    return;
  }
  if (++up->u_seq < currtime) up->u_seq = currtime;
  up->u_oldchannel = up->u_channel;
  up->u_channel = newchannel;
  send_user_change_msg(up);
  sprintf(buffer, "*** Now on channel %d.\n", up->u_channel);
  send_string(lp, buffer);
  userinfo_update(up);
}

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

static void help_command(struct link *lp)
{
  FILE *fp;
  char buffer[BUFFER];
  char *cp, *tp;
  int flag = 0;

  cp = getarg(NULLCHAR, 0);

  if(!*cp) {
    send_string(lp,
      "Commands may be abbreviated.  Commands are:\n"

      "/HELP MENU       Help tutorial information\n"
      "/? [topic]       Show help information\n"
/*      "/HELP                Show help information\n" */

      "/Bye             Terminate the convers session\n"
/*      "/EXIT                Terminate the convers session\n" */
/*      "/QUIT                Terminate the convers session\n" */

      "/Who             Show active channels and users\n"

      "/Channel n       Switch to channel n\n"
/*      "/NOTE [text...]      Set personal note to text\n" */
      "/Personal txt..  Set personal note to text\n"
/*      "/NOTE @              Clear personal note\n" */
      "/Personal @      Clear personal note\n"

      "/Msg user text.. Send a private message to user\n"
/*      "/WRITE user text...  Send a private message to user\n" */

      "/Invite user     Invite user to join your channel\n"
      "/Query user      Query convers users status\n"
      "/Sound [y|n]     Switch bell's on and off\n"

      "/Hosts           Show all hosts\n"
      "/Links           Show all links\n"
      "/PEErs           Show all peers\n"
      "/Users           Show all users\n"

      "***\n");
      return;
  }

  if((fp = fopen(HELP_FILE, "r"))) {
    while (fgets(buffer, sizeof(buffer), fp)) {
      if(*buffer == '$') {
        if(flag)
          break;
        for(tp = buffer; *tp; tp++)
          if(*tp == '\n' || *tp == '\r') {
            *tp = 0;
            break;
          }
        if(debug & DEBUG_MISC) printf("*** H Help file, found index to topic '%s' matching '%s'\n", buffer, cp);
        if(!strcasecmp(&buffer[1], cp)) {
          flag++;
          sprintf(buffer, "*** Help found on topic '%s'\n", cp);
          send_string(lp, buffer);
        }
      } else {
        if(flag) {
          send_string(lp, "* ");
          send_string(lp, buffer);
        }
      }
    }
    fclose(fp);
    if(!flag) {
      sprintf(buffer, "*** Sorry help is not available for '%s'.\n", cp);
      send_string(lp, buffer);
    } else
      send_string(lp, "***\n");
  } else
    send_string(lp, "*** Sorry can not access help file, please notify sysop.\n");
    
}

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

static void hosts_command(struct link *lp)
{

  char buffer[BUFFER+FUZZ];
  struct host *hp;
  struct user *up;
#ifdef HDAT
  struct link *lpp;
#endif
  char *cp;
  
  cp = getarg(NULLCHAR, 0);
  
  if(*cp && !strcasecmp("all", cp)) {
    send_string(lp, "Name       Via\n");
    for (hp = hosts; hp; hp = hp->h_next) {
      sprintf(buffer,
	    "%-10.10s %-10.10s\n",
	    hp->h_name,
	    hp->h_link ? hp->h_link->l_host->h_name : "");
      send_string(lp, buffer);
    }
#ifdef HDAT
  } else if(*cp && !strcasecmp("to", cp)) {
     cp = getarg(NULLCHAR, 0);
     hp = hostptr(cp, 0);
     if (hp == &my) {
        sprintf(buffer, "*** HDAT to %s [%s] this is the local host.\n",
         my.h_name, version);
        send_string(lp, buffer);
        return;
     } else {
        sprintf(buffer, "/\377\200HDAT %s %s 0 %s\n", lp->l_user->u_name, cp,
         my.h_name);
        for (lpp = links; lpp; lpp = lpp->l_next)
           if(lpp->l_host)
              send_string(lpp, buffer);
        return;
     }
  } else if(*cp && !strcasecmp("hops", cp)) {
    int hops;
    
    cp = getarg(NULLCHAR, 0);
    hops = atol(cp);
    cp = getarg(NULLCHAR, 0);
    if(hops > 0) {
       sprintf(buffer, "/\377\200HDAT %s * %d %d hop%s",
        lp->l_user->u_name, hops, hops, (hops == 1) ? "\n" : "s\n" );
       for (lpp = links; lpp; lpp = lpp->l_next)
         if(lpp->l_host)
            send_string(lpp, buffer);
       return;
    } else {
       sprintf(buffer, "*** Illegal number of hops\n");
       send_string(lp, buffer);
       return;
    }
  } else if(*cp) {
    hp = hostptr(cp, 0);
    if (hp == &my) {
      sprintf(buffer, "Hdat on %s using [%s], Ltime: %19.19s.\n",
       my.h_name, version, ctime(&currtime));
      send_string(lp, buffer);
      sprintf(buffer, "%s\n", (hdattext == NULL) ? "No local information" : hdattext);
      send_string(lp, buffer);
    } else {
      sprintf(buffer, "/\377\200HDAT %s %s -1\n", lp->l_user->u_name, cp);
      for (lpp = links; lpp; lpp = lpp->l_next)
        if(lpp->l_host)
           send_string(lpp, buffer);
      return;
    }
#endif
  } else {
    send_string(lp, "Name       Via\n");
    for (hp = hosts; hp; hp = hp->h_next) {
      for (up = users; up; up = up->u_next) {
        if(up->u_host == hp && up->u_channel >= 0) {
          sprintf(buffer,
	    "%-10.10s %-10.10s\n",
	    hp->h_name,
	    hp->h_link ? hp->h_link->l_host->h_name : "");
          send_string(lp, buffer);
          break;
        }
      }
    }  
  }
  send_string(lp, "***\n");
}

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

static void invite_command(struct link *lp)
{
  char *toname;
  char *text;

  toname = getarg(NULLCHAR, 0);
  text = getarg(NULLCHAR, 1);
  if (*toname)
    send_invite_msg(lp->l_user->u_name, toname, lp->l_user->u_channel, text, 1);
}

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

static void links_command(struct link *lp)
{

  char *name;
  char *state;
  char buffer[BUFFER+FUZZ];
  struct link *p;

#ifdef LZW
  #ifdef AUTO
    send_string(lp, "Name       State      Since  Idle Queue Receivd Xmitted P LZW Receivd Xmitted\n");
  #else
    send_string(lp, "Name       State      Since  Idle Queue Receivd Xmitted LZW Receivd Xmitted\n");
  #endif
#else
  #ifdef AUTO
    send_string(lp, "Name       State      Since  Idle Queue Receivd Xmitted P\n");
  #else
    send_string(lp, "Name       State      Since  Idle Queue Receivd Xmitted\n");
  #endif
#endif

  for (p = links; p; p = p->l_next) {
    if (p->l_user) {
      state = "User";
      name = p->l_user->u_name;
    } else if (p->l_host) {
      state = "Host";
      name = p->l_host->h_name;
    } else if (p->l_fd >= 0) {
      state = "Conn pend";
      name = "";
    } else {
      state = "Closed";
      name = "";
    }
#ifdef LZW
    if(p->l_mode == NORM) {
#endif
       sprintf(buffer,
#ifdef LZW
  #ifdef AUTO
          "%-10.10s %-9s %s %5ld %5d %7ld %7ld %c NRM\n",
  #else
          "%-10.10s %-9s %s %5ld %5d %7ld %7ld NRM\n",
  #endif
#else
  #ifdef AUTO
          "%-10.10s %-9s %s %5ld %5d %7ld %7ld %c\n",
  #else
          "%-10.10s %-9s %s %5ld %5d %7ld %7ld\n",
  #endif
#endif
	    name,
	    state,
	    localtimestring(p->l_stime),
	    currtime - p->l_mtime,
	    queuelength(p->l_sndq),
	    p->l_received,
	    p->l_xmitted
#ifdef AUTO
	    , (p->l_personal == 1) ? 'P' : ' '
#endif
	    );
#ifdef LZW
   } else {
       sprintf(buffer,
#ifdef AUTO
            "%-10.10s %-9s %s %5ld %5d %7ld %7ld %c L%2d %7ld %7ld\n",
#else
            "%-10.10s %-9s %s %5ld %5d %7ld %7ld L%2d %7ld %7ld\n",
#endif
	    name,
	    state,
	    localtimestring(p->l_stime),
	    currtime - p->l_mtime,
	    queuelength(p->l_sndq),
	    p->l_received,
	    p->l_xmitted,
#ifdef AUTO
	    (p->l_personal == 1) ? 'P' : ' ',
#endif
            (p->lzw && p->lzw->zin->maxbits > 0) ? p->lzw->zin->maxbits : 0,
            p->xl_received,
            p->xl_xmitted);
   }
#endif

    send_string(lp, buffer);
  }
  send_string(lp, "***\n");
}

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

static void msg_command(struct link *lp)
{

  char *text;
  char *toname;
  char buffer[BUFFER+FUZZ];
  struct user *up;

  toname = getarg(NULLCHAR, 0);
  text = getarg(NULLCHAR, 1);
  for (up = users; up; up = up->u_next)
    if (up->u_channel >= 0 &&
#if	defined(USER8)
     !strncmp(up->u_name, toname, 8)
#elif	defined(USER10)
     !strncmp(up->u_name, toname, 10)
#else
     !strcmp(up->u_name, toname)
#endif
      ) {
      send_msg_to_user(lp->l_user->u_name, toname, text, 1);
      return;
    }
  sprintf(buffer, "*** No such user: %s.\n", toname);
  send_string(lp, buffer);
}

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

static void note_command(struct link *lp)
{

  char *note;
  char buffer[MAX_NOTELEN+128];	/* MUST BE BIGGER THAN 'MAX_NOTELEN' **/
  struct user *up;

  up = lp->l_user;
  note = getarg(NULLCHAR, 1);
  if (*note && strcmp(up->u_note, note)) {
    free(up->u_note);
    if(strlen(note) < MAX_NOTELEN)
       strcpy(buffer, note);
    else {
       strncpy(buffer, note, MAX_NOTELEN);
       buffer[MAX_NOTELEN] = '\0';
    }
    up->u_note = strdup(buffer);
    if (++up->u_seq < currtime) up->u_seq = currtime;
/*    up->u_oldchannel = up->u_channel; ** TOCHK **/
#ifdef UDAT
    send_note_change_msg(up);
#else
    send_user_change_msg(up);
#endif
  }
  sprintf(buffer, "*** Your personal note is set to \"%s\".\n", strcmp(up->u_note, NO_NOTE) ? up->u_note : "");
  send_string(lp, buffer);
  userinfo_update(up);
}

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

static void peers_command(struct link *lp)
{

  char *name;
  char *state;
  char buffer[BUFFER+FUZZ];
  char nexttry[8];
#ifdef LZW
  char strmode[5];
  char string[6];
#endif
  struct peer *pp;

#ifndef LZW
  send_string(lp, "Address                               State      Since NextTry Tries LinkedTo\n");
#else
  send_string(lp, "LZW  Area Address                     State      Since NextTry Tries LinkedTo\n");
#endif
  for (pp = peers; pp; pp = pp->p_next) {
    strcpy(nexttry, "      ");
    if (!pp->p_link) {
      state = "Wait";
      name = "";
      /* It just so happens an inactive connection will stay in Wait state. **/
      if (!pp->p_active)
        strcpy(nexttry, "Never ");
      if (pp->p_retrytime > currtime)
	strcpy(nexttry, localtimestring(pp->p_retrytime));
    } else if (pp->p_link->l_user) {
      state = "User";
      name = pp->p_link->l_user->u_name;
    } else if (pp->p_link->l_host) {
      state = "Connected";
      name = pp->p_link->l_host->h_name;
    } else if (pp->p_link->l_fd >= 0) {
      state = "Conn pend";
      name = "";
    } else {
      state = "Closed";
      name = "";
    }
#ifdef LZW
    if(pp->p_mode == LZW) {
       sprintf(strmode, "L%2d", pp->p_bits);
    } else
       sprintf(strmode, "NRM");
    if(pp->p_area > MAX_CHANNEL)
       strcpy(string, " all ");
    else
       sprintf(string, "%5d", pp->p_area);
#endif
    sprintf(buffer,
#ifndef LZW
	    "%-37.37s %-9s %s  %s %5d %-10.10s\n",
#else
	    "%s %s %-27.27s %-9s %s  %s %5d %-10.10s\n",
	    strmode,
	    string,
#endif
	    *pp->p_command ? pp->p_command : pp->p_socket,
	    state,
	    localtimestring(pp->p_stime),
	    nexttry,
	    pp->p_tries,
	    name);
    send_string(lp, buffer);
  }
  send_string(lp, "***\n");
}

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

static void query_command(struct link *lp)
{

  char *name;
  char buffer[BUFFER+FUZZ];
  char intime[26];
  int valid = 0;
  struct info *in;
  struct user *up;
  struct link *lp2;
  

  name = getarg(NULLCHAR, 0);
  if (!*name) {
     int ur = 0, li = 0, lh = 0, lu = 0;
     long uptime, rem;
     
     for(up = users; up; up = up->u_next)
       if(up->u_channel >= 0)
         ur++;
     for(lp2 = links; lp2; lp2 = lp2->l_next) {
       if(lp2->l_host || lp2->l_user)
         li++;
       if(lp2->l_host)
         lh++;
       if(lp2->l_user)
         lu++;
     }
     uptime = currtime - starttime;
     rem = uptime % (60*60*24);
     sprintf(intime, "%4ld day%c %2.2ld:%2.2ld:%2.2ld",
       uptime / (60*60*24),
       ((uptime / (60*60*24)) == 1) ? ' ' : 's',
       rem / (60*60),
       (rem % (60*60)) / 60,
       (rem % (60*60)) % 60);
     sprintf(buffer, "Local server stats: Uptime %s, %d Users (%d Local),\n",
       intime, ur, lu);
     send_string(lp, buffer);
     sprintf(buffer, "                    %d Local links (%d Host%c).\n",
       li, lh, (lh == 1) ? ' ' : 's');
     send_string(lp, buffer);
     send_string(lp, "***\n");
     return;
  }
  if((in = userinfo_get(name)) == NULLINFO) {
     sprintf(buffer, "*** No user info for %s\n", name);
     send_string(lp, buffer);
     return;
  }
  for(up = users; up; up = up->u_next) {
     if(
#if	defined(USER8)
      !strncmp(up->u_name, name, 8)
#elif	defined(USER10)
      !strncmp(up->u_name, name, 10)
#else
      !strcmp(up->u_name, name)
#endif
       && up->u_channel >= 0)
        valid++;
  }
  if(in->i_etime || !valid) {
     strcpy(intime, ctime(&in->i_etime));
     if(in->i_ltime) {
        sprintf(buffer, "User info: %s on %s,  last local: %19.19s\n",
         in->i_name, in->i_host, ctime(&in->i_ltime));
     } else
        sprintf(buffer, "User info: %s on %s,\n", in->i_name, in->i_host);
     send_string(lp, buffer);
     sprintf(buffer, "           last login: %19.19s, last logout: %19.19s,\n",
       ctime(&in->i_stime), (in->i_etime) ? intime : "????? Unknown ?????");
     send_string(lp, buffer);
     sprintf(buffer, "           channel %u, note: %s\n",
       in->i_channel,
       strcmp(in->i_note, NO_NOTE) ? in->i_note : "");
     send_string(lp, buffer);
  } else {
     sprintf(buffer, "User info: %s on %s, login time: %19.19s,\n",
     in->i_name,
     in->i_host,
     ctime(&in->i_stime));
     send_string(lp, buffer);
     sprintf(buffer, "           on channel %u, note: %s\n",
       in->i_channel,
       strcmp(in->i_note, NO_NOTE) ? in->i_note : "");
     send_string(lp, buffer);
  }

  userinfo_free(in);       
     
  send_string(lp, "***\n");
}

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

static void name_command(struct link *lp)
{

  char *name;
  char *note;
  char *cp;
  char buffer[MAX_NOTELEN+256]; /* MUST BE BIGGER THAN 'MAX_NOTELEN' **/
  char tmp[] = "/QUERY\n";
  struct link *lpold;
  struct user *up;
  struct info *in;
  struct stat st;
/*  int flag = 0;*/
  FILE *fp;

  name = getarg(NULLCHAR, 0);
  if (!*name) return;
  up = userptr(name, &my, 1);
  if(!up) {
#ifdef SYSLOG
    if(Syslog >= 1)
      syslog(LOG_ERR, "UNAME: corrupt local username received");
#endif
    return;
  }
  
  /* Change the order of the links in the table, users at the bottom **/
  /* This does look ugly, and should be improved lata! - G7LED **/
  if(links == lp && lp->l_next) {
     links = lp->l_next;
     for(lpold = links; lpold->l_next; lpold = lpold->l_next)
        ;
     lp->l_next = 0;
     lpold->l_next = lp;
  } else if(links != lp) {
     for(lpold = links; lpold && lpold->l_next != lp; lpold = lpold->l_next)
        ;
     if(lpold) {
        lpold->l_next = lpold->l_next->l_next;	/* lp->l_next **/
        for(lpold = links; lpold->l_next; lpold = lpold->l_next)
           ;
        lp->l_next = 0;
        lpold->l_next = lp;
     }
  }

  if (++up->u_seq < currtime) up->u_seq = currtime;
  lpold = up->u_link;
  up->u_link = lp;
  if (up->u_channel >= 0 && lpold) close_link(lpold);
  lp->l_user = up;
  lp->l_stime = currtime;
  lp->l_personal = 0;		/* Means nothing for a user connection... **/
  sprintf(buffer, "conversd @ %s [%s] Type /HELP for help.\n", my.h_name, version);
  send_string(lp, buffer);
  for(cp = buffer; *cp && *cp != '\n'; cp++)
    *cp = '=';			/* Prettyness - G7LED :-) **/
  send_string(lp, buffer);

#ifdef SYSLOG
   if(Syslog >= 4)
      syslog(LOG_NOTICE, "ULINK: open to %s", name);
#endif

  in = userinfo_get(name);

  /* G7LED - Can do convers motd information here **/
  if((fp = fopen(MOTD_FILE, "r"))) {
    while (fgets(buffer, sizeof(buffer), fp))
      send_string(lp, buffer);
    fclose(fp);
/*    flag++;*/
  }

  if(stat(NEWS_FILE, &st) == 0) {
    if(in == NULLINFO || (st.st_mtime + (60*60*24)) > in->i_ltime) {
      if((fp = fopen(NEWS_FILE, "r"))) {
        while (fgets(buffer, sizeof(buffer), fp))
          send_string(lp, buffer);
        fclose(fp);
      }
    }
/*    flag++;*/
  }

/*
  if(flag)
     send_string(lp, "***\n");
*/       

  up->u_oldchannel = up->u_channel;
  up->u_channel = atoi(getarg(NULLCHAR, 0));
  if (up->u_channel < 0 || up->u_channel > MAX_CHANNEL) {
    sprintf(buffer, "*** Channel numbers must be in the range 0..%d.\n", MAX_CHANNEL);
    send_string(lp, buffer);
    up->u_channel = 0;
  }
  note = getarg(NULLCHAR, 1);
  if (!*note) {
     if(in == NULLINFO)
        note = NO_NOTE;
     else
        note = in->i_note;
  }
  if(in != NULLINFO)
     up->u_opt = in->i_opt;
  else
     up->u_opt = I_DEFAULT;
     
  up->u_number = in->i_number;
  if (strcmp(up->u_note, note)) {
    free(up->u_note);
    if(strlen(note) < MAX_NOTELEN)
       strcpy(buffer, note);
    else {
       strncpy(buffer, note, MAX_NOTELEN);
       buffer[MAX_NOTELEN] = '\0';
    }
    up->u_note = strdup(buffer);
  }
  getarg(tmp, 0);	/* System stats on login **/
  query_command(lp);

  send_user_change_msg(up);
  userinfo_update(up);
  userinfo_free(in);
}

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

static void users_command(struct link *lp)
{
   char buffer[BUFFER+FUZZ];
   struct user *up;
   char *p;
   int tmp = 0;
   int flag = 0;

   if((p = getarg(NULLCHAR, 0)) && *p != 0) {
      if(!strcasecmp(p, "last")) {
         if((p = getarg(NULLCHAR, 0)) && *p != 0)
            flag = 1;
         for(up = users; up; up = up->u_next) {
            if(up->u_channel < 0 && (flag || up->u_oldchannel != -1)) {
               if(!tmp++)
                  send_string(lp, "User     Host       Channel     Time Personal note\n");
               sprintf(buffer,
                  "%-8.8s %-10.10s %7d %c %s %s\n",
                  up->u_name,
                  up->u_host->h_name,
                  up->u_oldchannel,
                  (up->u_force) ? 'F' : ' ',
                  localtimestring(up->u_stime),
                  strcmp(up->u_note, NO_NOTE) ? up->u_note : "");
               send_string(lp, buffer);
            }
         }
         if(tmp)
            send_string(lp, "***\n");
         else {
            sprintf(buffer, "*** No users have logged off in the last %d mins.\n", (HOLDTIME/60));
            send_string(lp, buffer);
         }
         return;
      }
      for(; *p; p = getarg(NULLCHAR, 0), flag = 0) {
         for(up = users; up; up = up->u_next) {
            if(
#if	defined(USER8)
             !strncmp(up->u_name, p, 8)
#elif	defined(USER10)
             !strncmp(up->u_name, p, 10)
#else
             !strcmp(up->u_name, p)
#endif
              && up->u_channel >= 0) {
               if(!tmp++)
                  send_string(lp, "User     Host       Channel   Time Personal note\n");
               sprintf(buffer,
                  "%-8.8s %-10.10s %7d %s %s\n",
                  up->u_name,
                  up->u_host->h_name,
                  up->u_channel,
                  localtimestring(up->u_stime),
                  strcmp(up->u_note, NO_NOTE) ? up->u_note : "");
               send_string(lp, buffer);
               flag = 1;
            } else if(!up->u_next && !flag) {
               sprintf(buffer, "*** User '%s' is not using convers\n", p);
               send_string(lp, buffer);
            }
         }
      }
      if(tmp)
         send_string(lp, "***\n");
   } else {
      send_string(lp, "User     Host       Channel   Time Personal note\n");
      for (up = users; up; up = up->u_next)
         if (up->u_channel >= 0) {
            sprintf(buffer,
               "%-8.8s %-10.10s %7d %s %s\n",
               up->u_name,
               up->u_host->h_name,
               up->u_channel,
               localtimestring(up->u_stime),
               strcmp(up->u_note, NO_NOTE) ? up->u_note : "");
           send_string(lp, buffer);
         }
      send_string(lp, "***\n");
   }
}

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

#define WHOLEN 8

static void who_command(struct link *lp)
{
   char buffer[BUFFER+FUZZ];
   char prefix[BUFFER];
   char *formatted_line;
   int channel;
   int nextchannel;
   int flag = 0;
   struct user *up;
   char *p;

   if((p = getarg(NULLCHAR, 0)) && *p) {
      for(; *p; p = getarg(NULLCHAR, 0), flag = 0) {
         for(up = users; up; up = up->u_next) {
            if(
#if	defined(USER8)
             !strncmp(up->u_name, p, 8)
#elif	defined(USER10)
             !strncmp(up->u_name, p, 10)
#else
             !strcmp(up->u_name, p)
#endif
              && up->u_channel >= 0) {
               sprintf(buffer,
                  "*** User '%s' on channel %d\n",
                  up->u_name,
                  up->u_channel);
               send_string(lp, buffer);
               flag = 1;
            } else if(!up->u_next && !flag) {
               sprintf(buffer, "*** User '%s' is not using convers\n", p);
               send_string(lp, buffer);
            }
         }
      }
   } else {
      send_string(lp, "Channel Users\n");
      nextchannel = MAX_CHANNEL + 1;
      for (up = users; up; up = up->u_next)
         if (up->u_channel >= 0 && up->u_channel < nextchannel)
            nextchannel = up->u_channel;
      while (nextchannel <= MAX_CHANNEL) {
         channel = nextchannel;
         nextchannel = MAX_CHANNEL + 1;
         *buffer = *prefix = 0;
         for (up = users; up; up = up->u_next) {
            if (up->u_channel > channel && up->u_channel < nextchannel)
               nextchannel = up->u_channel;
            else if (up->u_channel == channel) {
               if (!*prefix) sprintf(prefix, "%7d", channel);
               strcat(buffer, " ");
               strcat(buffer, up->u_name);
            }
         }
         strcat(buffer, "\n");
         formatted_line = formatline(prefix, buffer, WHOLEN);
         send_string(lp, formatted_line);
      }
      send_string(lp, "***\n");
   }
}

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

static void sound_command(struct link *lp)
{
   struct user *up;
   char *p;
   
   up = lp->l_user;
   if((p = getarg(NULLCHAR, 0)) && *p != 0) {
      if(*p == 'y' || *p == 'Y' || *p == '1') {
         send_string(lp, "*** Your sound option is on\n");
         up->u_opt |= I_SOUND;
      } else {
         send_string(lp, "*** Your sound option is off\n");
         up->u_opt &= ~I_SOUND;
      }
      userinfo_update(up);
   } else {
      if(up->u_opt & I_SOUND)
         send_string(lp, "*** Your sound option is on\n");
      else
         send_string(lp, "*** Your sound option is off\n");
   }
}

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

static void h_cmsg_command(struct link *lp)
{

  char *name;
  char *channel;
  char *text;

  name = getarg(NULLCHAR, 0);
  channel = getarg(NULLCHAR, 0);
  text = getarg(NULLCHAR, 1);

#ifdef SYSLOG
   if(Syslog >= 6)
      syslog(LOG_INFO, "/..CMSG %s %s %s", name, channel, text);
#endif

  send_msg_to_channel(name, atoi(channel), text, 0);
}

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

static void h_host_command(struct link *lp)
{

  char *name;
  char buffer[BUFFER+FUZZ];
  struct host *hp, *hhp;
  struct link *lpold;
  struct peer *pp;
  struct user *up;

  name = getarg(NULLCHAR, 0);

#ifdef SYSLOG
   if(Syslog >= 6)
      syslog(LOG_INFO, "/..HOST %s", name);
#endif

  if (!*name) return;
  hp = hostptr(name, 1);
  if (!hp || hp == &my) {
#ifdef SYSLOG
   if(Syslog >= 1 && !hp)
      syslog(LOG_WARNING, "HOST: Corrupt hostname received");
#endif
    close_link(lp);
    return;
  }
  hp->h_link = lp;
  lp->l_host = hp;
  lp->l_stime = currtime;

#ifdef SYSLOG
   if(Syslog >= 3)
      syslog(LOG_NOTICE, "HLINK: open to %s", name);
#endif

  for (lpold = links; lpold; lpold = lpold->l_next)
    if (lpold != lp && lpold->l_host == hp) {
      for (pp = peers; pp; pp = pp->p_next)
	if (pp->p_link == lpold) pp->p_link = lp;
      for (up = users; up; up = up->u_next)
	if (up->u_link == lpold) up->u_link = lp;
      /* Change all host via's over as well - G7LED **/
      for (hhp = hosts; hhp; hhp = hhp->h_next)
         if (hhp->h_link == lpold) hhp->h_link = lp;
      close_link(lpold);
    }

  for (pp = peers; pp; pp = pp->p_next)
    if (pp->p_link == lp) {
      pp->p_stime = currtime;
      pp->p_tries = 0;
      pp->p_retrytime = currtime + min_waittime;
    }

  sprintf(buffer, "/\377\200HOST %s\n", my.h_name);
  send_string(lp, buffer);		/* G7LED - It this needed?
					   I can not see why its there.  **/

  for (up = users; up; up = up->u_next)
    if ((up->u_channel >= 0 || up->u_seq) && up->u_link != lp) {
    /* Added check, if users already exists through this host link then
       don't repeat it again on the turn around... **/
#ifdef AUTO	/* Added here, as it might be possible later to setup from
		   bootup, if people are really fussy! - G7LED **/
      if(lp->l_personal == 1) {
#endif
	sprintf(buffer,
		"/\377\200USER %s %s %lu %d %d %s\n",
		up->u_name,
		up->u_host->h_name,
/* Fix to sequence, as if the up->force flag is set then its real seq is
   two more than that stored here.  The u_channel == -1 check might be
   paranoia setting in again, not enough time to check out **/
		(up->u_force && up->u_channel == -1) ? (up->u_seq + 2) : up->u_seq,
		(up->u_force || up->u_channel >= 0) ? -1 : up->u_oldchannel,
		up->u_channel,
		(up->u_channel >= 0) ? up->u_note : "");
	send_string(lp, buffer);
#ifdef AUTO
      } else {
	sprintf(buffer,
		"/\377\200USER %s %s %lu %d %d\n",
		up->u_name,
		up->u_host->h_name,
/* Fix to sequence, as if the up->force flag is set then its real seq is
   two more than that stored here.  The u_channel == -1 check might be
   paranoia setting in again, not enough time to check out **/
		(up->u_force && up->u_channel == -1) ? (up->u_seq + 2) : up->u_seq,
		(up->u_force || up->u_channel >= 0) ? -1 : up->u_oldchannel,
		up->u_channel);
	send_string(lp, buffer);
#ifdef UDAT
        if(up->u_channel >= 0) {
	  sprintf(buffer,
		"/\377\200UDAT %s %s %s\n",
		up->u_name,
		up->u_host->h_name,
		up->u_note);
	    send_string(lp, buffer);
	}
#endif
      }
#endif /* AUTO **/
    }

}

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

static void h_invi_command(struct link *lp)
{

  char *channel;
  char *fromname;
  char *toname;
  char *text;

  fromname = getarg(NULLCHAR, 0);
  toname = getarg(NULLCHAR, 0);
  channel = getarg(NULLCHAR, 0);
  text = getarg(NULLCHAR, 1);

#ifdef SYSLOG
   if(Syslog >= 6)
      syslog(LOG_INFO, "/..INVI %s %s %s %s", fromname, toname, channel, text);
#endif

  if (*channel)
    send_invite_msg(fromname, toname, atoi(channel), getarg(NULLCHAR, 1), 0);
}

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

static void h_umsg_command(struct link *lp)
{
   char *fromname;
   char *toname;
   char *text;

   fromname = getarg(NULLCHAR, 0);
   toname = getarg(NULLCHAR, 0);
   text = getarg(NULLCHAR, 1);

#ifdef SYSLOG
   if(Syslog >= 6)
      syslog(LOG_INFO, "/..UMSG %s %s %s", fromname, toname, text);
#endif

   send_msg_to_user(fromname, toname, text, 0);
}

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

static void h_user_command(struct link *lp)
{
   char *channel;
   char *channel2;
   char *host;
   char *name;
   char *note;
   char buffer[BUFFER+FUZZ];
   int newchannel;
   int oldchannel;
   unsigned long seq;
   struct host *hp;
   struct user *up;
#ifdef AUTO
   struct user *up2;
#endif

   name = getarg(NULLCHAR, 0);
   host = getarg(NULLCHAR, 0);
   seq = strtol((getarg(NULLCHAR, 0)), (char **) NULL, 10);
   channel2 = getarg(NULLCHAR, 0);
   channel = getarg(NULLCHAR, 0);
   note = getarg(NULLCHAR, 1);

#ifdef SYSLOG
   if(Syslog >= 6)
      syslog(LOG_INFO, "/..USER: %s %s %lu %s %s %s", name, host, seq,
       channel2, channel, (note) ? note : " ");
#endif

   if(!*channel || !*channel2) {
#ifdef SYSLOG
   if(Syslog >= 1)
      syslog(LOG_ERR, "USER: syntax error in format from (%s)", name);
#endif
      if(debug & DEBUG_USER) printf("*** # Syntax error: ignored.\n");
      return;
   }

   oldchannel = atoi(channel2);
   newchannel = atoi(channel);
   hp = hostptr(host, 1);
   up = userptr(name, hp, 1);
   if(!hp || !up) {
#ifdef SYSLOG
      if(Syslog >= 1)
         syslog(LOG_ERR, "USER: corrupt hostname or username received");
#endif
      return;
   }

#ifdef AUTO   
   if(!*note && (lp->l_personal == 1) && newchannel >= 0)
      lp->l_personal = -1;		/* l_personal is not suported **/
#endif


   if(!*note)
      note = up->u_note;

   if(newchannel > MAX_CHANNEL) {
#ifdef SYSLOG
      if(Syslog >= 1)
         syslog(LOG_ERR, "USER: Channel %d requested and not supported here", newchannel);
#endif
      return;
   }

   if((seq > up->u_seq) ||
     ((seq == 0 && up->u_seq == 0 && (!up->u_link || lp == up->u_link) &&
     ((newchannel != up->u_channel || newchannel >= 0)
     /* || strcmp(note, up->u_note) */ )))) {
			  /* G7LED - 10/05/94 - JNOS (in)Compatability **/
      up->u_seq = seq;

      if (hp == &my) {
         if (debug & DEBUG_USER) printf("*** * Got info about my own user: rejected.\n");
#ifdef SYSLOG
         if(Syslog >= 2)
            syslog(LOG_WARNING, "USER: Got info about my own user from (%s) user (%s)", lp->l_host->h_name, name);
#endif
         if (++up->u_seq < currtime) up->u_seq = currtime;
#ifdef AUTO
         if (lp->l_personal == 1) {
#endif
           sprintf(buffer, "/\377\200USER %s %s %lu %d %d %s\n", name, host, up->u_seq, newchannel, up->u_channel, up->u_note);
           send_string(lp, buffer);
#ifdef AUTO
         } else {
           sprintf(buffer, "/\377\200USER %s %s %lu %d %d\n", name, host, up->u_seq, newchannel, up->u_channel);
           send_string(lp, buffer);
           sprintf(buffer, "/\377\200UDAT %s %s %s\n", name, host, up->u_note);
           send_string(lp, buffer);
         }
#endif
      } else if(hp->h_link && hp->h_link != lp) {
         if(newchannel < 0) {
            if(debug & DEBUG_USER) printf("*** * Got info about a host I have a link to (sign off): rejected.\n");
#ifdef SYSLOG
         if(Syslog >= 5)
            syslog(LOG_NOTICE, "USER: Got info about a host I have a link to (sign off) from %s", lp->l_host->h_name);
#endif
            return;		/* G7LED - Link has probably just broke, **/
         }			/* this host it trying to logout all the **/
				/* users I probably gave him **/

         if (debug & DEBUG_USER) printf("*** - Got info about a host I have a link to (sign on): rejected.\n");
#ifdef SYSLOG
         if(Syslog >= 5)
            syslog(LOG_NOTICE, "USER: Got info about a host I have a link to (sign on) from %s", lp->l_host->h_name);
#endif
         lp->l_lockout = 1;
#ifdef SYSLOG
         if(Syslog >= 1)
            syslog(LOG_ERR, "LOOP: detected on link to (%s) about host (%s) is via (%s)",
             lp->l_host->h_name, host, hp->h_link->l_host->h_name);
#endif
#ifdef LOOP
         /* This is where the convers LOOP detection comes in. **/
         sprintf(buffer, "/\377\200LOOP %s %s %s\n", my.h_name,
           hp->h_link->l_host->h_name, host);
         send_string(lp, buffer);
         /* Leave connection open, let other end disc it. - G7LED **/
#endif
      } else {
         if(debug & DEBUG_USER) printf("*** + New user info: accepted.\n");
#ifdef SYSLOG
         if(Syslog >= 5)
            syslog(LOG_NOTICE, "USER: New info accepted from (%s) user (%s) host (%s)", lp->l_host->h_name, name, host);
#endif
         /* Moved to after to check for my own host, it also optimizes with
            setting up the u_force flag as well. **/
         if(oldchannel == -1 && newchannel == -1)
            up->u_force = 1;
         else
            up->u_force = 0;	     /* Clear this should the link the got
         				dropped come back. **/
         if(newchannel < 0)
            up->u_link = lp;
         else
            up->u_link = hp->h_link = lp;
         up->u_oldchannel = up->u_channel;
         up->u_channel = newchannel;
         if(strcmp(up->u_note, note)) {
            if(validname(note)) {
#ifdef SYSLOG
               if(Syslog >= 1)
                  syslog(LOG_ERR, "USER: Corrupt note given so no change (%s)", name);
#endif
            } else {
               free(up->u_note);
               up->u_note = strdup(note);
            }
         }
         userinfo_update(up);		/* Before u_stime is changed **/
         if((up->u_oldchannel ^ up->u_channel) < 0) up->u_stime = currtime;
         send_user_change_msg(up);
         if(oldchannel == -1 && newchannel == -1 && up->u_seq > 2)
            up->u_seq = seq - 2;	/* Major old bug fix - 22/07/94 - G7LED **/
#ifdef AUTO	
	/* Here because the above uses the lock's **/
         if(lp->l_personal == -1) {
            if(debug & DEBUG_MISC) printf("*** + Detected JNOS link: confirming personal text.\n");
#ifdef SYSLOG
            if(Syslog >= 2)
               syslog(LOG_WARNING, "USER: Switching to UDAT on link to %s", lp->l_host->h_name);
#endif
            for (up2 = users; up2; up2 = up2->u_next)
               if (up2->u_channel >= 0) {
                  if (up2->u_link != lp) {
                     sprintf(buffer, "/\377\200UDAT %s %s %s\n", up2->u_name, up2->u_host->h_name, up2->u_note);
                     send_string(lp, buffer);
                  }
               }
            lp->l_personal = 0;
         }
#endif
      }
   } else {
      /***************************************/
      if(hp->h_link && hp->h_link != lp && newchannel >= 0) {
         if (debug & DEBUG_USER) printf("*** - Got info about a host I have a link to (sign on): rejected.\n");
#ifdef SYSLOG
         if(Syslog >= 4)
            syslog(LOG_NOTICE, "USER: Got info about a host I have a link to (sign on) from %s", lp->l_host->h_name);
#endif
         lp->l_lockout = 1;
#ifdef SYSLOG
         if(Syslog >= 1)
            syslog(LOG_ERR, "LOOP: detected on link to (%s) about host (%s) is via (%s)",
             lp->l_host->h_name, host, hp->h_link->l_host->h_name);
#endif
#ifdef LOOP
         /* This is where the convers LOOP detection comes in. **/
         sprintf(buffer, "/\377\200LOOP %s %s %s\n", my.h_name,
           hp->h_link->l_host->h_name, host);
         send_string(lp, buffer);
         /* Leave connection open, let other end disc it. - G7LED **/
#endif
      } else {
         if (debug & DEBUG_USER) printf("*** - Bad sequencer or no change: ignored.\n");
#ifdef SYSLOG
         if(Syslog >= 5)
            syslog(LOG_NOTICE, "USER: Bad sequence or no change user");
#endif
      }
   }
}

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


#ifdef UDAT

/*  This is for JNOS as it seems to have this command but it don't use
 *  sequence numbers :-(  Shouldn't need to either.
 *
 *  /..UDAT <name> <host> <note>
 *
 *  Darryl G7LED (04/04/1994)
 *
 */

static void h_udat_command(struct link *lp)
{
  char *host;
  char *name;
  char *note;
  char buffer[BUFFER+FUZZ];
  struct host *hp;
  struct user *up;

  name = getarg(NULLCHAR, 0);
  host = getarg(NULLCHAR, 0);
  note = getarg(NULLCHAR, 1);
  hp = hostptr(host, 0);
  if(!hp)
     return;
  up = userptr(name, hp, 0);
  if (!*note) note = up->u_note;		/* This should RESET it (!) **/
#ifdef SYSLOG
  if(Syslog >= 6)
     syslog(LOG_INFO, "/..UDAT %s %s %s", name, host, note);
#endif
  if(!up) {
      if(debug & DEBUG_MISC) printf("*** - Warning user (%s) is unknown\n", name);
#ifdef SYSLOG
      if(Syslog >= 1)
         syslog(LOG_ERR, "UDAT: received for an unknown user (%s) host (%s)", name, host);
#endif
      return;
  }
  if (lp == up->u_link && lp == hp->h_link && strcmp(note, up->u_note)) {
    if (hp == &my) {
      if (debug  & DEBUG_UDAT) printf("*** * Got info about my own user: rejected.\n");
#ifdef SYSLOG
  if(Syslog >= 1)
     syslog(LOG_ERR, "UDAT: Got info about my own user (%s)", name);
#endif
      sprintf(buffer, "/\377\200UDAT %s %s %s\n", name, host, up->u_note);
      send_string(lp, buffer);
    } else {
      if (debug & DEBUG_UDAT) printf("*** + New user personal text: accepted.\n");
#ifdef SYSLOG
      if(Syslog >= 5)
        syslog(LOG_NOTICE, "UDAT: User info accepted user (%s) host (%s)", name, host);
#endif

/*      up->u_link = hp->h_link = lp; */

      if(validname(note)) {
#ifdef SYSLOG
         if(Syslog >= 1)
            syslog(LOG_ERR, "UDAT: Corrupt note given so no change (%s)", name);
#endif
      } else {
         free(up->u_note);
         up->u_note = strdup(note);
      }

      send_note_change_msg(up);
      userinfo_update(up);
    }
  } else {
    if (debug & DEBUG_UDAT) printf("*** - Bad sequencer or no change: ignored.\n");
#ifdef SYSLOG
    if(Syslog >= 5)
       syslog(LOG_NOTICE, "UDAT: no note change for (%s)", name);
#endif
  }
}

#endif

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

#ifdef LOOP

/*  This is in JNOS as it seems to have this command we had better have it to
 *  keep up with the Jone's :-(
 *
 *  Credit would appear to be to DL9SAU for this idea, from JNOS source.
 *
 *  Format appears to be /..LOOP <Sending_host> <Its_neighbour> <About_host>
 *
 *  Darryl G7LED (10/05/1994)
 *
 */

static void h_loop_command(struct link *lp)
{
   char *host1;
   char *host2;
   char *host3;
   struct host *hp1;
   struct host *hp2;
   struct host *hp3;

   host1 = getarg(NULLCHAR, 0);
   host2 = getarg(NULLCHAR, 0);
   host3 = getarg(NULLCHAR, 0);
   hp1 = hostptr(host1, 0);
   hp2 = hostptr(host2, 0);
   hp3 = hostptr(host3, 0);

   /* Yeah well what else, is there to do - G7LED **/

   if (debug & DEBUG_LOOP) printf("*** - Loop command accepted: Link closed.\n");

#ifdef SYSLOG
   if(Syslog >= 6)
      syslog(LOG_INFO, "/..LOOP %s %s %s", host1, host2, host3);
   if(Syslog >= 1)
      syslog(LOG_ERR, "LOOP: received from (%s) about host (%s) its already linked via (%s)", lp->l_host->h_name, host3, host2);
#endif

   close_link(lp);
}

#endif /* LOOP **/

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

/*  
 *  This is in to pass all unimplemented convers key words, through to all
 *  other connected hosts.
 *
 *  It will allow the possible testing of something new, even though this
 *  convers system is between the two system carrying out the testing.
 *  Makes sense this really.
 *
 *  Darryl G7LED (18/07/1994)
 *
 */

static void h_thru_command(struct link *lp)
{
   char *command, *cp;
   char buffer[BUFFER+FUZZ];
   int i;
   struct link *lp2;
  
   command = getarg(lp->l_ibuf, 1);

   for (lp2 = links; lp2; lp2 = lp2->l_next) {
      if (!lp2->l_locked && lp2->l_host) {
         sprintf(buffer, "%s\n", command);
         send_string(lp2, buffer);
      }
   }

   if (debug& DEBUG_MISC) printf("*** * Unknown/Unimplemented command: passing through to all hosts.\n");
   
#ifdef SYSLOG
   if(Syslog >= 2) {
      syslog(LOG_WARNING, "THRU: Unimplemented command received from %s", lp->l_host->h_name);
      for(cp = command, i = 0; *cp && i < (sizeof(buffer) - 1); cp++, i++) {
         if(isprint(*cp))
            buffer[i] = *cp;
      }
      buffer[i] = '\000';
      syslog(LOG_WARNING, "THRU: command is '%s'", buffer);
   }
#endif
}

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

/*  
 *  Darryl G7LED (18/07/1994)
 */

static void h_dummy_command(struct link *lp)
{

   if (debug & DEBUG_MISC) printf("*** * Dummy command to host.\n");
#ifdef SYSLOG
   if(Syslog >= 6)
      syslog(LOG_INFO, "/..DUMMY: Dummy command used on link to %s", lp->l_host->h_name);
#endif
}

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

#ifdef HDAT

/*  Requesting additional info from a host.
 *
 *  /..HDAT <user> <host>
 *
 *  Darryl G7LED (01/09/1994)
 *
 */

static void h_hdat_command(struct link *lp)
{
  int hops;
  char *host;
  char *name;
  char *number;
  char *info;
  char buffer[BUFFER+FUZZ];
  char buffer2[BUFFER+FUZZ];	/* Watch out with this, check before
  					changing its size **/
  struct host *hp;
  struct user *up;
  struct link *lpp;

  name = getarg(NULLCHAR, 0);
  host = getarg(NULLCHAR, 0);
  number = getarg(NULLCHAR, 0);
  info = getarg(NULLCHAR, 1);
  hp = hostptr(host, 0);
  up = userptr(name, hp, 0);
  hops = atoi(number);

#ifdef SYSLOG
  if(Syslog >= 6)
     syslog(LOG_INFO, "/..HDAT %s %s %s %s", name, host, number, info);
#endif

  if(!up) {
      if(debug & DEBUG_MISC) printf("*** - Warning user (%s) is unknown\n", name);
#ifdef SYSLOG
      if(Syslog >= 1)
         syslog(LOG_ERR, "HDAT: received for an unknown user (%s) host (%s)", name, host);
#endif
      return;
  }
  if(hops > 0) {
     /* Hop decrement query **/
     if(--hops) {
        for(lpp = links; lpp; lpp = lpp->l_next)
           if(!lpp->l_locked && lpp->l_host) {
              sprintf(buffer, "/\377\200HDAT %s %s %d %s\n", name, host, hops,
               info ? info : " ");
              send_string(lpp, buffer);
           }
     } else {
        /* This has got to go back down the link which requested it, clear_locks() **/
        sprintf(buffer, "*** HDAT: %s [%s] %s", my.h_name, version,
         info ? info : " ");
        clear_locks();
        send_msg_to_user("conversd", name, buffer, 1);
     }
  } else if(hops < 0) {
     /* Specific host query **/
     if(hp == &my) {
        /* This has got to go back down the link which requested it, clear_locks() **/
        sprintf(buffer, "Hdat on %s using [%s], Ltime: %19.19s.",
         my.h_name, version, ctime(&currtime));
        clear_locks();
        send_msg_to_user("conversd", name, buffer, 1);
        sprintf(buffer, "%s", (hdattext == NULL) ? "No local information" : hdattext);
        clear_locks();
        send_msg_to_user("conversd", name, buffer, 1);
        clear_locks();
        send_msg_to_user("conversd", name, "***", 1);
     } else {
        for(lpp = links; lpp; lpp = lpp->l_next)
           if(!lpp->l_locked && lpp->l_host) {
              sprintf(buffer, "/\377\200HDAT %s %s %s %s\n", name, host,
               number, info ? info : " ");
              send_string(lpp, buffer);
           }
     }
  } else {
     /* == 0, List all hosts going out **/
     if(hp == &my) {
        /* This has got to go back down the link which requested it, clear_locks() **/
        sprintf(buffer, "*** HDAT to %s via %s", my.h_name, info);
        clear_locks();
        send_msg_to_user("conversd", name, buffer, 1);
     } else {
        if(strlen(info) < BUFFER) {	/* Use FUZZ factor **/
           strcpy(buffer2, info);	/* Watch out with this **/
           if(*buffer2)
              strcat(buffer2, " ");
           strcat(buffer2, my.h_name);
           for(lpp = links; lpp; lpp = lpp->l_next)
              if(!lpp->l_locked && lpp->l_host) {
                 sprintf(buffer, "/\377\200HDAT %s %s %s %s\n", name, host,
                  number, buffer2);
                 send_string(lpp, buffer);
              }
        } else {
           /* This has got to go back down the link which requested it, clear_locks() **/
           sprintf(buffer, "*** HDAT fail to %s stopped at %s via %s",
            host, my.h_name, info);
           clear_locks();
           send_msg_to_user("conversd", name, buffer, 1);
        }
     }
  }
}

#endif

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

static void process_input(struct link *lp)
{

  struct command {
    const char *c_name;
    void (*c_fnc)(struct link *);
  };

  static const struct command login_commands[] = {

    "\377\200host",     h_host_command,
    "name",             name_command,

    0,                  0
  };

  static const struct command user_commands[] = {

    "?",                help_command,
    "bye",              close_link,
    "channel",          channel_command,
    "exit",             close_link,
    "hosts",            hosts_command,
    "help",             help_command,
    "invite",           invite_command,
    "links",            links_command,
    "msg",              msg_command,
    "note",             note_command,
    "personal",         note_command,
    "peers",            peers_command,
    "query",		query_command,
/*    "quit",             close_link,*/
    "sound",		sound_command,
    "users",            users_command,
    "who",              who_command,
    "write",            msg_command,

    0,                  0
  };

  static const struct command host_commands[] = {

    "\377\200cmsg",     h_cmsg_command,
    "\377\200invi",     h_invi_command,
    "\377\200umsg",     h_umsg_command,
    "\377\200user",     h_user_command,
#ifdef UDAT
    "\377\200udat",	h_udat_command,
#endif
#ifdef LOOP
    "\377\200loop",	h_loop_command,
#else
    "\377\200loop",	h_dummy_command,
#endif
#ifdef HDAT
    "\377\200hdat",	h_hdat_command,
#endif
    "\377\200host",	h_dummy_command,

    0,                  0
  };

  char *arg;
  char buffer[BUFFER+FUZZ];
  char cp_buffer[sizeof(lp->l_ibuf)];
  const struct command * cp;
  int arglen;

  if (debug & DEBUG_RX) trace(0, lp, lp->l_ibuf);

  strcpy(cp_buffer, lp->l_ibuf);

  clear_locks();
  lp->l_locked = 1;

  if (*lp->l_ibuf == '/') {
    arglen = strlen(arg = getarg(lp->l_ibuf + 1, 0));
    if (lp->l_user)
      cp = user_commands;
    else if (lp->l_host)
      cp = host_commands;
    else
      cp = login_commands;
    for (; cp->c_name; cp++)
      if (!strncmp(cp->c_name, arg, arglen)) {
	(*cp->c_fnc)(lp);
	return;
      }
    if (lp->l_host) {
      if(!strncmp("\377\200", arg, 2)) {
        strcpy(lp->l_ibuf, cp_buffer);	 /* Passes unknown/unimplemented **/
        h_thru_command(lp);		 /* commands, in the aid to help **/
        return;				 /* experminentation **/
      }
    }
    if (lp->l_user) {
      sprintf(buffer, "*** Unknown command '/%s'.  Type /HELP for help.\n", arg);
      send_string(lp, buffer);
    }
  } else if (lp->l_user)
    send_msg_to_channel(lp->l_user->u_name, lp->l_user->u_channel, getarg(lp->l_ibuf, 1), 1);
}

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

static void read_configuration(void)
{

  FILE * fp;
  char *cp;
  char *host_name;
  char *system_name;
  char line[1024];
  struct peer *pp;
  char *p;

  if ((fp = fopen(conffile, "r"))) {
    while (fgets(line, sizeof(line), fp)) {	/* First uncommented **/
      if ((cp = strchr(line, '#'))) continue;
      host_name = getarg(line, 0);
      if (*host_name) {
	free(my.h_name);
	my.h_name = strdup(host_name);
	break;
      }
    }
    while (fgets(line, sizeof(line), fp)) {	/* Second uncommented **/
      if ((cp = strchr(line, '#'))) continue;
      host_name = getarg(line, 1);
      if (*host_name) {
#ifdef HDAT
        char hinfo[77];

        strncpy(hinfo, host_name, 78);
	free(hdattext);
	hdattext = strdup(hinfo);
#endif
	break;
      }
    }
    while (fgets(line, sizeof(line), fp)) {
      if ((cp = strchr(line, '#'))) *cp = 0;
      system_name = getarg(line, 0);
      if (*system_name) {
	pp = (struct peer *) calloc(1, sizeof(*pp));
	pp->p_system = strdup(system_name);
	p = getarg(NULLCHAR, 0);
        pp->p_active = (!strncasecmp(p, "Y", 1)) ? 1 : 0;
	pp->p_area = atoi(getarg(NULLCHAR, 0));
	pp->p_socket = strdup(getarg(NULLCHAR, 0));
#ifdef LZW
        p = getarg(NULLCHAR, 0);
        pp->p_mode = (!strncasecmp(p, "Y", 1)) ? LZW : NORM;
        pp->p_bits = atol(getarg(NULLCHAR, 0));
        if(pp->p_bits < 9 || pp->p_bits > 16)	/* Idiot proof **/
           pp->p_bits = 12;
#else
        getarg(NULLCHAR, 0);
        getarg(NULLCHAR, 0);	/* Ignore this **/
#endif
	pp->p_command = strdup(getarg(NULLCHAR, 1));
	pp->p_stime = currtime;
	pp->p_retrytime = currtime + min_waittime;
	pp->p_next = peers;
	peers = pp;
      }
    }
    fclose(fp);
  } else
    exit(2);
}

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

static void check_files_changed(char **argv)
{

  static long conftime;
  static long nexttime;
  static long progtime;
  struct stat statbuf;
  pid_t pid;
  char buffer[BUFFER];
  int status;

  if (nexttime > currtime) return;
  nexttime = currtime + 600;

  if (!stat(conffile, &statbuf)) {
    if (!conftime) conftime = statbuf.st_mtime;
    if (conftime != statbuf.st_mtime && statbuf.st_mtime + 60 < currtime)
      reason = 1;
      
      /* G7LED - For now, could dump all host connections, re-read config
                 file, open links all back up.  Later examine current connection
                 information compare to the config file, if its different then dump the
                 convers host link, if the same stay with existing one, also add new
                 entries in. **/
  }
  
  if (!stat(EXEC_FILE, &statbuf)) {
    if (!progtime) progtime = statbuf.st_mtime;
    if (progtime != statbuf.st_mtime && statbuf.st_mtime + 60 < currtime)
      reason = 2;
  }

  if(reason)
    switch((pid = fork())) {
      /* G7LED - Fork, send a SIGTERM to myself and exit normally, sleep
                 until other process does not exist, and then exec new
                 version of the program. **/
      case 0:
        raise(SIGTERM);
        return;
        break;

      case -1:
        if(debug & DEBUG_MISC) printf("*** - Can not fork, to restart binary: continuing with old one.\n");
#ifdef SYSLOG
        if(Syslog >= 1)
           syslog(LOG_ERR, "FORK: unable to fork to restart binary, continuing with old binary");
#endif
        break;
          
      default:
        waitpid(pid, &status, WNOHANG);
        execv(EXEC_FILE, argv);
        if(debug & DEBUG_MISC) printf("*** - Failed to execute new binary '%s'.\n", EXEC_FILE);
          /* G7LED - We failed to exec **/
#ifdef SYSLOG
        if(Syslog >= 1)
           syslog(LOG_ERR, "FORK: failed to execute new binary");
#endif
        sprintf(buffer, "*** [%s] %s host failed the auto reboot.\n", version, my.h_name);
        send_msg_to_channel("conversd", ADM_CHANNEL, buffer, 1);
        break;
    }
  reason = 0;
}

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

static void link_recv(struct link *lp)
{
  char buffer[BUFFER];
  int i;
  int n;

  n = read(lp->l_fd, buffer, sizeof(buffer));
  if (n <= 0)
    close_link(lp);
  else {
    lp->l_mtime = currtime;
    lp->l_received += n;
    for(i = 0; i < n; i++)
      switch(buffer[i]) {
      case '\b':
	if(lp->l_icnt)
	   lp->l_icnt--;
	break;
      case '\n':
      case '\r':
	if(lp->l_icnt) {
           lp->l_ibuf[lp->l_icnt++] = '\n';
           lp->l_ibuf[lp->l_icnt] = 0;
           lp->l_icnt = 0;
           if(!lp->l_lockout)
              process_input(lp);
	}
	break;
      default:
	if(lp->l_icnt < sizeof(lp->l_ibuf) - 5)
	   lp->l_ibuf[lp->l_icnt++] = buffer[i];
	else {
	   if(debug & DEBUG_MISC) printf("*** * Input overflow: trimming\n");
#ifdef SYSLOG
           if(Syslog >= 2) {
              if(lp->l_host) {
                 syslog(LOG_WARNING, "INPUT: input overflow in link to %s", lp->l_host->h_name);
              } else if(lp->l_user) {
                 syslog(LOG_WARNING, "INPUT: input overflow in link to %s", lp->l_host->h_name);
              }
           }
#endif
        }
	break;
      }
  }
}

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

#ifdef LZW

static void x_link_recv(struct link *lp)
{
  char buffer[BUFFER];
  int i;
  int n;

  n = read(lp->l_fd, buffer, sizeof(buffer));
  if (n <= 0)
    close_link(lp);
  else {
     ADD_Buf(&lp->lzw->zin->ibuf, buffer, n);
     lp->xl_received += n;
     if(LZW_Decode(lp->lzw) == -2) {	/* Decompression error, key not
     					   expected **/
        if(debug & DEBUG_MISC) printf("*** - LZW Decompression error: closing link\n");
#ifdef SYSLOG
           if(Syslog >= 1) {
              if(lp->l_host) {
                 syslog(LOG_ERR, "LZW: decompression error with link to %s", lp->l_host->h_name);
              } else if(lp->l_user) {
                 syslog(LOG_ERR, "LZW: decompression error with link to %s", lp->l_user->u_name);
              }
           }
#endif
        close_link(lp);
        return;
     }
     while((n = GET_Buf(&lp->lzw->zin->obuf, buffer, sizeof(buffer)))) {
        lp->l_mtime = currtime;
        lp->l_received += n;
        for(i = 0; i < n; i++)
           switch(buffer[i]) {
              case '\b':
                 if (lp->l_icnt)
                    lp->l_icnt--;
                 break;
              case '\n':
              case '\r':
                 if(lp->l_icnt) {
                    lp->l_ibuf[lp->l_icnt++] = '\n';
                    lp->l_ibuf[lp->l_icnt] = 0;
                    lp->l_icnt = 0;
                    if(!lp->l_lockout)
                       process_input(lp);
                 }
                 break;
              default:
                 if(lp->l_icnt < sizeof(lp->l_ibuf) - 5)
                    lp->l_ibuf[lp->l_icnt++] = buffer[i];
                 else {
                    if(debug & DEBUG_MISC) printf("*** * Input overflow: trimming\n");
#ifdef SYSLOG
                    if(Syslog >= 2) {
                       if(lp->l_host) {
                          syslog(LOG_WARNING, "INPUT: input overflow in link to %s", lp->l_host->h_name);
                       } else if(lp->l_user) {
                          syslog(LOG_WARNING, "INPUT: input overflow in link to %s", lp->l_user->u_name);
                       }
                    }
#endif
                 }                    
                 break;
           }
     }
  }
}

#endif

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

static void link_send(struct link *lp)
{

  int n;
  struct conv_mbuf *mp;

#ifdef MAXIOV

  struct iovec iov[MAXIOV];

  n = 0;
  for (mp = lp->l_sndq; mp && n < MAXIOV; mp = mp->m_next) {
    iov[n].iov_base = mp->m_data;
    iov[n].iov_len = mp->m_cnt;
    n++;
  }
  n = writev(lp->l_fd, iov, n);

#else

  n = write(lp->l_fd, lp->l_sndq->m_data, lp->l_sndq->m_cnt);

#endif

  if (n <= 0)
    close_link(lp);
  else {
    lp->l_mtime = currtime;
    lp->l_xmitted += n;
    while (n > 0) {
      if (n >= lp->l_sndq->m_cnt) {
	n -= lp->l_sndq->m_cnt;
	mp = lp->l_sndq;
	lp->l_sndq = mp->m_next;
	free(mp);
      } else {
	lp->l_sndq->m_data += n;
	lp->l_sndq->m_cnt -= n;
	n = 0;
      }
    }
  }
  if (!lp->l_sndq) FD_CLR(lp->l_fd, &chkwrite);
}

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

#ifdef LZW

static void x_link_send(struct link *lp)
{

  int n, tmp;
  struct conv_mbuf *mp;
  struct conv_mbuf *p;
  char buffer[BUFFER];

/* #ifdef MAXIOV */
/*
  struct iovec iov[MAXIOV];

  n = 0;
  for (mp = lp->l_sndq; mp && n < MAXIOV; mp = mp->m_next) {
    iov[n].iov_base = mp->m_data;
    iov[n].iov_len = mp->m_cnt;
    n++;
  }
  n = writev(lp->l_fd, iov, n);
*/
/* #else */

   while(lp->l_sndq && lp->l_sndq->m_cnt) {
      while(lp->l_sndq->m_cnt && (tmp = OUTBYTE(&lp->lzw->zout->ibuf,
       *lp->l_sndq->m_data))) {
         LZW_Encode(lp->lzw);
         if(*lp->l_sndq->m_data == '\n')
            LZW_Flush(lp->lzw);
         lp->l_sndq->m_cnt -= tmp;
         lp->l_sndq->m_data += tmp;
         lp->l_xmitted += tmp;
      }
      mp = lp->l_sndq;
      lp->l_sndq = mp->m_next;
      free(mp);
   }

   while((n = GET_Buf(&lp->lzw->zout->obuf, buffer, sizeof(buffer)))) {
      mp = (struct conv_mbuf *)malloc(sizeof(*mp) + n);
      mp->m_next = 0;
      memcpy(mp->m_data = (char *) (mp + 1), buffer, mp->m_cnt = n);
      if (lp->l_zsndq) {
         for (p = lp->l_zsndq; p->m_next; p = p->m_next) ;
         p->m_next = mp;
      } else {
         lp->l_zsndq = mp;
      }
   }

   n = write(lp->l_fd, lp->l_zsndq->m_data, lp->l_zsndq->m_cnt);

/* #endif */

  if (n <= 0)
    close_link(lp);
  else {
    lp->l_mtime = currtime;
    lp->xl_xmitted += n;
    while (n > 0) {
      if (n >= lp->l_zsndq->m_cnt) {
	n -= lp->l_zsndq->m_cnt;
	mp = lp->l_zsndq;
	lp->l_zsndq = mp->m_next;
	free(mp);
      } else {
	lp->l_zsndq->m_data += n;
	lp->l_zsndq->m_cnt -= n;
	n = 0;
      }
    }
  }
  if (!lp->l_zsndq) FD_CLR(lp->l_fd, &chkwrite);
}

#endif

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

/* G7LED - User information, added mainly to stop the bad use of the gecos
 *         field!  But it can also hold some other (maybe) usefull
 *         information.
**/

static void
userinfo_update(up)
struct user *up;
{
   struct info *in;
   
   if(!up || up == NULLUSER)
      return;
   
   if(up->u_oldchannel == -1 && up->u_channel == -1)
      return;

#ifdef TEXT
   if(strchr(up->u_name, ':') != NULL)
      return;			/* Bomb proof ? **/
#endif 
  
   if((in = userinfo_get(up->u_name)) == NULLINFO) {
      in = (struct info *)calloc(1, sizeof(*in));
      in->i_name = strdup(up->u_name);
      in->i_note = strdup(NO_NOTE);
      in->i_number = 0;
   }
   
   free(in->i_host);
   in->i_host = strdup(up->u_host->h_name);

   in->i_opt = up->u_opt;

   if(up->u_host == &my) {		/* Stuff we only change if at my **/
      in->i_ltime = currtime;		/* host **/
      free(in->i_note);
      in->i_note = strdup(up->u_note);
   } else {
      if(up->u_opt & I_LOCKED) {
         if(strcmp(up->u_note, NO_NOTE)) {
            free(in->i_note);
            in->i_note = strdup(up->u_note);
         }
      } else {
         free(in->i_note);
         in->i_note = strdup(up->u_note);
      }
   }

   if(up->u_channel >= 0) {
      in->i_channel = up->u_channel;
      in->i_etime = 0;
   } else {
      in->i_channel = up->u_oldchannel;
      in->i_etime = currtime;
      in->i_number = ++up->u_number;
   }
   
   in->i_stime = up->u_stime;
   
   userinfo_put(in);
   userinfo_free(in);
}

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

static struct info *
userinfo_get(name)
char *name;
{
   FILE *fp;
   struct info *in = 0;
   char buffer[2048];
   char *p;
#ifdef NDBM
   datum key;
   datum ele;
#endif

   if(!name || !*name)
      return NULLINFO;

   if(debug & DEBUG_INFO) printf("*** + userinfo_get(%s)\n",name);

#ifdef NDBM
   key.dptr = name;
   key.dsize = strlen(name) + 1;
   ele = dbm_fetch(dbm, key);
   if(ele.dptr) {
      if(debug & DEBUG_INFO) printf("*** + found dbm entry %d '%s'\n", ele.dsize, ele.dptr);
      if(!(p = strtok(ele.dptr, ":")))
         goto Fail;
      in = (struct info *)calloc(1, sizeof(*in));
      in->i_name = strdup(name);
      in->i_host = strdup(p);
      if(!(p = strtok(NULLCHAR, ":")))
         goto Fail;
      in->i_channel = atoi(p);
      if(!(p = strtok(NULLCHAR, ":")))
         goto Fail;
      in->i_stime = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         goto Fail;
      in->i_etime = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         goto Fail;
      in->i_ltime = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         goto Fail;
      in->i_utime = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         goto Fail;
      in->i_number = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         goto Fail;

      get_opt(in, p);

#if 0
      if(*p == 'S') {		/* Order important! Sound ? **/
         in->i_opt |= I_SOUND;
         p++;
      }
      if(*p == 'V') {		/* Order important! Verbose ? **/
         in->i_opt |= I_VERBOSE;
         p++;
      }
#endif

      if((p = strtok(NULLCHAR, "\n")))	/* Not occur so safe **/
         in->i_note = strdup(p);
      else
         in->i_note = strdup(NO_NOTE);
      return in;
Fail:
   if(debug & DEBUG_INFO) printf("*** + FORMAT FAIL dbm very bad thing this error\n");
   }
#endif
#ifdef TEXT
   if(debug & DEBUG_INFO) printf("*** + finding in text (%s)\n",name);
   
   if((fp = fopen(DATA_FILE, "r")) == NULL)
      return NULLINFO;
   
   while((fgets(buffer, sizeof(buffer), fp))) {
      if(in)
         userinfo_free(in);
      if(!(p = strtok(buffer, ":")))
         continue;
      if(strcmp(p, name))
         continue;
      if(debug & DEBUG_INFO) printf("*** + found in text (%s)\n",p);
      in = (struct info *)calloc(1, sizeof(*in));
      in->i_name = strdup(p);
      if(!(p = strtok(NULLCHAR, ":")))
         continue;
      in->i_host = strdup(p);
      if(!(p = strtok(NULLCHAR, ":")))
         continue;
      in->i_channel = atoi(p);
      if(!(p = strtok(NULLCHAR, ":")))
         continue;
      in->i_stime = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         continue;
      in->i_etime = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         continue;
      in->i_ltime = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         continue;
      in->i_utime = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         continue;
      in->i_number = atol(p);
      if(!(p = strtok(NULLCHAR, ":")))
         continue;

      if(debug & DEBUG_INFO) printf("*** + found in options '%s'\n",p);
      get_opt(in, p);
      if(debug & DEBUG_INFO) printf("*** + found set to %lu\n", in->i_opt);

#if 0
      if(*p == 'S') {		/* Order important! Sound ? **/
         in->i_opt |= I_SOUND;
         p++;
      }
      if(*p == 'V') {		/* Order important! Verbose ? **/
         in->i_opt |= I_VERBOSE;
         p++;
      }
#endif

      if((p = strtok(NULLCHAR, "\n")))
         in->i_note = strdup(p);
      else
         in->i_note = strdup(NO_NOTE);
      fclose(fp);
      return in;
   }
   fclose(fp);
#endif
   return NULLINFO;
}

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

static int
userinfo_put(in)
struct info *in;
{
   char buffer[2048];
#ifdef NDBM
   datum key;
   datum ele;
#else
   FILE *fp;
#endif

#ifdef NDBM
   /* Dont overwrite the VERSION key **/
   if(!strcmp("VERSION", in->i_name))
      return -1;

   sprintf(buffer, "%s:%u:%lu:%lu:%lu:%lu:%lu:",
     in->i_host, in->i_channel, in->i_stime, in->i_etime,
     in->i_ltime, currtime, in->i_number);
   /* Increase the count if signing off **/
   strcat(buffer, make_opt(in));
   strcat(buffer, ":");
   strcat(buffer, in->i_note);

   key.dptr = in->i_name;
   key.dsize = strlen(in->i_name) + 1;
   ele.dptr = buffer;
   ele.dsize = strlen(buffer) + 1;
   dbm_store(dbm, key, ele, DBM_REPLACE);
#ifdef TEXT		/* Slowly remove entries from the text .inf file **/
   userinfo_del(in->i_name);
#endif
#else  
   sprintf(buffer, "%s:%s:%u:%lu:%lu:%lu:%lu:%lu:",
     in->i_name, in->i_host, in->i_channel, in->i_stime, in->i_etime,
     in->i_ltime, currtime, in->i_number);
   /* Increase the count if signing off **/

#if 0
   if(in->i_opt & I_SOUND)
     strcat(buffer, "S");
   if(in->i_opt & I_VERBOSE)
     strcat(buffer, "V");
   strcat(buffer, "$");		/* Pick a char, any char, hmm strtok fix! **/
#endif

   strcat(buffer, make_opt(in));
   strcat(buffer, ":");
   strcat(buffer, in->i_note);
   strcat(buffer, "\n");

   userinfo_del(in->i_name);

   if((fp = fopen(DATA_FILE, "a+")) == NULL)
      return -1;

   fputs(buffer, fp);
   fclose(fp);
#endif
   
   return 0;
}

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

#if (!defined(NDBM)) || (defined(TEXT))

static int
userinfo_del(name)
char *name;
{
   FILE *fp, *fp1;
   int tmp;
   char buffer[2048];

   if(!name || !*name)
      return -1;

   if((fp = fopen(DATA_FILE, "r")) == NULL)
      return -1;
   if((fp1 = fopen(DATA_FILE_TMP, "w")) == NULL)
      return -1;

   tmp = 1;
   while((fgets(buffer, sizeof(buffer), fp))) {
      if(!strncmp(buffer, name, strlen(name)) && buffer[strlen(name)] == ':') {
         tmp = 0;
         continue;
      }
      fputs(buffer, fp1);
   }
   fclose(fp);
   fclose(fp1);
   rename(DATA_FILE_TMP, DATA_FILE);
   return tmp;
}

#endif

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

static void
userinfo_free(in)
struct info *in;
{
   if(in) {
      if(in->i_name)
         free(in->i_name);
      if(in->i_host)
         free(in->i_host);
      if(in->i_note)
         free(in->i_note);
      free((struct info *)in);
   }
}

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

static char *
make_opt(in)
struct info *in;
{
   static char info[1024];

   *info = '\000';
   
   if(in->i_opt & I_SOUND)
      strcat(info, "S");
   if(in->i_opt & I_VERBOSE)
      strcat(info, "V");
   if(in->i_opt & I_TIMESTAMP)
      strcat(info, "T");
   if(in->i_opt & I_LOCKED)
      strcat(info, "L");
   if(in->i_opt & I_NULL)
      strcat(info, "N");
   strcat(info, "$");
   
   return info;
}

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

static void
get_opt(in, cp)
struct info *in;
char *cp;
{
   char *p;

   in->i_opt = 0;

   for(p = cp; *p && *p != ','; p++) {
      switch(*p) {
      case 'S':
         in->i_opt |= I_SOUND;
         break;
      case 'V':
         in->i_opt |= I_VERBOSE;
         break;
      case 'T':
         in->i_opt |= I_TIMESTAMP;
         break;
      case 'L':
         in->i_opt |= I_LOCKED;
         break;
      case 'N':
         in->i_opt |= I_NULL;
         break;
      case '$':
         return;
         break;
      }
   }
   return;
}

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

int main(int argc, char **argv)
{

  static struct listeners {
    const char *s_name;
    int s_fd;
#ifdef LZW
    int mode;
#endif
  } listeners[] = {
#ifdef LZW
    "unix:/tcp/sockets/convers",        -1,	NORM,
    "*:3600",                           -1,	NORM,
    "unix:/tcp/sockets/xconvers",	-1,	LZW,
    "*:3601",				-1,	LZW,
    0,                                  -1,	-1
#else
    "unix:/tcp/sockets/convers",        -1,
    "*:3600",                           -1,
    0,                                  -1,
#endif
  };

  char *cp;
  char *progname;
  char buffer[256];
  int addrlen;
  int arg;
  int chr;
  int errflag = 0;
  int i;
  int status;
  int cycle = 0;
  struct fd_set actread;
  struct fd_set actwrite;
  struct listeners *sp;
  struct sockaddr *addr;
  struct timeval timeout;

   if((progname = strrchr(argv[0], '/')) == NULL)
     progname = argv[0];
   else
     progname++;
  
  while((chr =
#ifndef SYSLOG
   getopt(argc, argv, "a:c:d:")
#else
   getopt(argc, argv, "a:c:d:s:")
#endif
    ) != EOF) {
    switch (chr) {
    case 'a':
      listeners[0].s_name = optarg;
      listeners[1].s_name = 0;
      break;
    case 'c':
      conffile = optarg;
      break;
    case 'd':
      debug = atol(optarg);
      min_waittime = 1;
      break;
#ifdef SYSLOG
    case 's':
      Syslog = atol(optarg);
      break;
#endif
    case '?':
      errflag = 1;
      break;
    }
  }

  if (errflag || optind < argc) {
#ifdef SYSLOG
    fprintf(stderr, "Usage: %s [-a address] [-c conffile] [-d#] [-s#]\n", progname);
#else
    fprintf(stderr, "Usage: %s [-a address] [-c conffile] [-d#]\n", progname);
#endif
    exit(1);
  }

  umask(022);

  for (i = 3; i < FD_SETSIZE; i++) close(i);

#ifdef TEXT
  if((i = open(DATA_FILE, O_RDONLY|O_CREAT, 0644)) < 0) {
     fprintf(stderr, "%s: Cannot open/create user info file\n", progname);
     exit(3);
  } else
     close(i);
#endif

  if((fld = open(CONF_FILE, O_RDONLY, 0644)) < 0) {
     fprintf(stderr, "%s: Cannot open/create conf info file\n", progname);
     exit(2);
  }
     
  if(flock(fld, LOCK_EX|LOCK_NB) < 0) {
     fprintf(stderr, "%s: Cannot flock conversd.conf file\n", progname);
     close(fld);
     exit(4);
  }
  if(debug & DEBUG_MISC) printf("flock on conversd.conf file is fd %d\n", fld);

#ifdef NDBM
  if((dbm = dbm_open(DBM_DATA_FILE, O_RDWR, 0644)) == NULL) {
     datum key, ele;
     char *t;
     
     if((dbm = dbm_open(DBM_DATA_FILE, O_RDWR|O_CREAT, 0644)) == NULL) {
        fprintf(stderr, "%s: Cannot open/create ndbm user info file\n", progname);
        exit(3);
     }

     /* Keep version info, maybe useful to reformat if needed later **/
     t = strdup(version);
     key.dptr = "VERSION";
     key.dsize = strlen("VERSION") + 1;
     ele.dptr = t;
     ele.dsize = strlen(t) + 1;
     dbm_store(dbm, key, ele, DBM_REPLACE);
     free(t);

     if(debug & DEBUG_MISC) printf("conversd.inf dbm file created\n");
  }
#endif

   if(!debug) {
      close(0);
      close(1);
      close(2);
   }

#ifdef SYSLOG
   openlog(progname, LOG_PID, LOG_LOCAL6);
   syslog(LOG_NOTICE, "started");
#endif

  chdir("/");
  setsid();
  signal(SIGPIPE, SIG_IGN);
  signal(SIGHUP, kick_connects);
  signal(SIGTERM, advise_shutdown);
  signal(SIGUSR1, dumpuser_info);
/*  if (!getenv("TZ")) putenv("TZ=MEZ-1MESZ"); **/

  gethostname(buffer, sizeof(buffer));
  if ((cp = strchr(buffer, '.'))) *cp = 0;
  my.h_name = strdup(buffer);

  time(&currtime);
  time(&starttime);

  read_configuration();

  for (sp = listeners; sp->s_name; sp++)
    if ((addr = build_sockaddr(sp->s_name, &addrlen)) &&
	(sp->s_fd = socket(addr->sa_family, SOCK_STREAM, 0)) >= 0) {
      switch (addr->sa_family) {
      case AF_UNIX:
	unlink(addr->sa_data);
	break;
      case AF_INET:
	arg = 1;
	setsockopt(sp->s_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &arg, sizeof(arg));
	break;
      }
      if (bind(sp->s_fd, addr, addrlen) || listen(sp->s_fd, SOMAXCONN)) {
	close(sp->s_fd);
	sp->s_fd = -1;
      } else {
#ifdef LZW
        if(sp->mode == NORM)
#endif
          readfnc[sp->s_fd] = (void (*)()) accept_connect_request;
#ifdef LZW
        else
          readfnc[sp->s_fd] = (void (*)()) x_accept_connect_request;
#endif
	readarg[sp->s_fd] = &sp->s_fd;
	FD_SET(sp->s_fd, &chkread);
	if (maxfd < sp->s_fd) maxfd = sp->s_fd;
      }
    }

  for (; ; ) {
    if(!terminate) {
       waitpid(-1, &status, WNOHANG);
       free_resources();
       connect_peers();
       check_files_changed(argv);
       timeout.tv_sec = min_waittime;
       timeout.tv_usec = 0;
    } else {
       timeout.tv_sec = 0;
       timeout.tv_usec = 250;
       cycle++;
    }
    
    actread = chkread;
    actwrite = chkwrite;

    i = select(maxfd + 1, SEL_ARG(&actread), SEL_ARG(&actwrite), SEL_ARG(0), &timeout);
    time(&currtime);
    if (i > 0)
      for (i = maxfd; i >= 0; i--) {
	if (readfnc [i] && FD_ISSET(i, &actread )) (*readfnc [i])(readarg [i]);
	if (writefnc[i] && FD_ISSET(i, &actwrite)) (*writefnc[i])(writearg[i]);
      }
    if(terminate && cycle > 10)
      save_info(SIGTERM);
  }
}

static void
kick_connects(sig)
int sig;
{
   struct peer *pp;

   signal(SIGHUP, kick_connects);

   for (pp = peers; pp; pp = pp->p_next) {
      pp->p_retrytime = currtime + 1;
   }
}

static void
advise_shutdown(sig)
int sig;
{
   struct link *lp;
   char buffer[BUFFER+FUZZ];

   switch(reason) {
     case 0:
       sprintf(buffer, "*** [%s] %s host is shutting down.\n", version, my.h_name);
       break;
       
     case 1:
       sprintf(buffer, "*** [%s] %s host restarting for configuration change.\n", version, my.h_name);
       break;
       
     case 2:
       sprintf(buffer, "*** [%s] %s host is shutting down, server is being update.\n", version, my.h_name);
       break;
   }
   send_msg_to_channel("conversd", ADM_CHANNEL, buffer, 1);

   for(lp = links; lp; lp = lp->l_next)	{	/* Force logout status **/
      if(lp->l_user) {
        switch(reason) {
           case 0:
             send_string(lp, "*** Your local convers server is being shut down.\n");
             send_string(lp, "*** Please log back into convers soon.\n");
             break;
           
           case 1:
             send_string(lp, "*** Your local convers server has changed its configuration\n");
             send_string(lp, "*** Please log back into convers soon.\n");
             break;

           case 2:
             send_string(lp, "*** Your local convers server is being updated with new software.\n");
             send_string(lp, "*** Please log straight back into convers, to resume your chat.\n");
             break;
        }
      }
   }   
   if(debug & DEBUG_MISC) printf("*** - Advising a shutdown.\n");

#ifdef SYSLOG
   if(Syslog >= 1)
      syslog(LOG_ERR, "Advising Shutdown");
#endif

   terminate = 1;
}

static void
save_info(sig)
int sig;
{
   struct link *lp;

   for(lp = links; lp; lp = lp->l_next)	/* Force logout status **/
      close_link(lp);			/* This will update userinfo **/

   if(debug & DEBUG_MISC) printf("*** - A shutdown is now taking place.\n");

#ifdef SYSLOG
   if(Syslog >= 1)
      syslog(LOG_ERR, "Shutdown");
   closelog();
#endif

#ifdef NDBM
   dbm_close(dbm);
#endif

   flock(fld, LOCK_UN);
   exit(0);
}

static void
dumpuser_info(sig)
int sig;
{
   struct user *up;

   for(up = users; up; up = up->u_next) {
#ifdef SYSLOG
      syslog(LOG_DEBUG, "UDUMP: %s @ %s, %s, %lu, %d, %d (%c%c%c), %lu, '%s'",
       (up->u_name) ? up->u_name : "Unknown",
       (up->u_host) ? up->u_host->h_name : "Unknown",
       (up->u_link) ? "Linked" : "No_Link",
       up->u_seq,
       up->u_oldchannel,
       up->u_channel,
       (up->u_force) ? 'F' : ' ',
       (up->u_opt & I_SOUND) ? 'S' : ' ',
       (up->u_lockout) ? 'L' : ' ',
       up->u_stime,
       up->u_note);
#endif
   }

}
