/*
 * This is Ping-Pong convers/conversd derived from the wampes
 * convers package written by Dieter Deyke <deyke@mdddhd.fc.hp.com>
 *
 * Modifications by Fred Baumgarten <dc6iq@insu1.etec.uni-karlsruhe.de>
 * $Revision: 2.99 $$Date: 1994/10/02 10:33:12 $
 */

#define REV "$Revision: 2.99 $"

#include <sys/types.h>

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

#if defined(_AIX) && defined(_IBMR2)
#include <time.h>
#include <sys/select.h>
#endif

#if defined(__OS2__)
#include <time.h>
#include <types.h>
#include <netdb.h>
#include <nerrno.h>
#include <sys\ioctl.h>
#include <sys\select.h>
#include "pp_os2.h"
#define FD_SET_TYPE fd_set
#else
#include <sys/wait.h>
#include <unistd.h>
#include <utmp.h>
#endif

#ifdef LOGGING
#include <netdb.h>
#include <netinet/in.h>
#if defined (mips)		/* rotten OS isn't it ? */
#include <sys/syslog.h>
#else
#include <syslog.h>
#endif
#include <arpa/inet.h>
#endif

#include "conversd.h"
#include "convert.h"

#if defined(mips)      /* no headerfiles for the whole stuff ? */
extern int select __ARGS((int nfsd, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout));
#endif

#if defined(hpux)
#define fd_set int	/* not sure about this... comments ? */
#endif

#if defined(mips) || defined(_AIX)     /* no headerfiles for the whole stuff ? */
extern void bzero __ARGS((char *b1, int length));
extern int socket __ARGS((int af, int type, int protocol));
extern int accept __ARGS((int s, struct sockaddr *addr, int *addrlen));
extern int connect __ARGS((int s, struct sockaddr *name, int namelen));
extern int putenv __ARGS((char *string));
extern void gethostname __ARGS((char *name, int namelen));
extern int setsockopt __ARGS((int s, int level, int optname, char *optval, int optlen));
extern int listen __ARGS((int s, int backlog));
extern int bind __ARGS((int s, struct sockaddr *name, int namelen));
#ifdef LOGGING
extern void closelog __ARGS((void));
extern void openlog __ARGS((__const char *, int, int));
extern int setlogmask __ARGS((int));
extern void syslog __ARGS((int, __const char * __DOTS));
#endif
#endif

extern struct sockaddr *build_sockaddr __ARGS((const char *name, int *addrlen));

static char progfile[128];
static char conffile[128];
static char *convcmd;
static FD_SET_TYPE chkread;
static FD_SET_TYPE chkwrite;

char convtype[512];
long boottime;
long currtime;
char *myhostname;
char userfile[128];
char motdfile[128];
char issuefile[128];
char listener[128];
int secretnumber;
int amprnet;	/* if set to 0 it's an internet environment - should be site-specific */
char myrev[80];
char myfeatures[] = "admpu";

static int maxfd = -1;

struct connection *connections;
struct permlink *permarray[NR_PERMLINKS];
struct destination *destinations;
struct channel *channels;

#include "user.h"
#include "host.h"

static void process_input __ARGS((struct connection *cp));
static void read_configuration __ARGS((void));
#ifdef CHECKFILES
static void check_files_changed __ARGS((void));
#endif

struct cmdtable cmdtable[] = {
                                       
  {"/priv",        operator_command,   NULL,        CM_USER},
  {"/quit",        bye_command,        NULL,        CM_USER},
  {"?",            help_command,       help_help,   CM_USER},
  {"away",         away_command,       help_away,   CM_USER},
  {"action",       me_command,         help_me,     CM_USER},
  {"all",          all_command,        help_all,    CM_USER},
  {"beep",         beep_command,       help_beep,   CM_USER},
  {"bell",         beep_command,       help_beep,   CM_USER},
  {"bye",          bye_command,        help_quit,   CM_USER},
  {"channel",      channel_command,    help_join,   CM_USER},
  {"charset",      charset_command,    help_char,   CM_USER},
  {"cstat",        cstat_command,      NULL,        CM_UNKNOWN},
  {"destinations", hosts_command,      help_dest,   CM_USER},
  {"exit",         bye_command,        help_quit,   CM_USER},
  {"exclude",      imsg_command,       help_excl,   CM_USER},
  {"help",         help_command,       help_help,   CM_USER},
  {"hosts",        hosts_command,      help_dest,   CM_USER},
  {"invite",       invite_command,     help_invi,   CM_USER},
  {"imsg",         imsg_command,       help_excl,   CM_USER},
  {"iwrite",       imsg_command,       help_excl,   CM_USER},
  {"join",         channel_command,    help_join,   CM_USER},
  {"links",        links_command,      help_link,   CM_USER},
  {"leave",        leave_command,      help_leav,   CM_USER},
  {"list",         list_command,       help_list,   CM_USER},
  {"last",         last_command,       help_last,   CM_USER},
  {"msg",          msg_command,        help_msg,    CM_USER},
  {"me",           me_command,         help_me,     CM_USER},
  {"mode",         mode_command,       help_mode,   CM_USER},
  {"name",         name_command,       NULL,        CM_UNKNOWN},
  {"notify",       notify_command,     help_noti,   CM_USER},
  {"note",         personal_command,   help_pers,   CM_USER},
  {"operator",     operator_command,   help_oper,   CM_USER},
  {"online",       who_command,        NULL,        CM_UNKNOWN},
  {"personal",     personal_command,   help_pers,   CM_USER},
  {"prompt",       prompt_command,     help_prom,   CM_USER},
  {"quit",         bye_command,        help_quit,   CM_USER},
  {"query",        query_command,      help_quer,   CM_USER},
  {"send",         msg_command,        help_msg,    CM_USER},
  {"topic",        topic_command,      help_topi,   CM_USER},
  {"users",        who_command,        help_who,    CM_USER},
  {"uptime",       uptime_command,     help_upti,   CM_USER},
  {"verbose",      verbose_command,    help_verb,   CM_USER},
  {"version",      version_command,    help_vers,   CM_USER},
  {"who",          who_command,        help_who,    CM_USER},
  {"width",        width_command,      help_widt,   CM_USER},
  {"write",        msg_command,        help_msg,    CM_USER},
  
  {"\377\200away", h_away_command,     NULL,        CM_HOST},
  {"\377\200cmsg", h_cmsg_command,     NULL,        CM_HOST},
  {"\377\200dest", h_dest_command,     NULL,        CM_HOST},
  {"\377\200host", h_host_command,     NULL,        CM_UNKNOWN},
  {"\377\200invi", h_invi_command,     NULL,        CM_HOST},
  {"\377\200mode", mode_command,       NULL,        CM_HOST},
  {"\377\200oper", h_oper_command,     NULL,        CM_HOST},
  {"\377\200ping", h_ping_command,     NULL,        CM_HOST},
  {"\377\200pong", h_pong_command,     NULL,        CM_HOST},
  {"\377\200rout", h_rout_command,     NULL,        CM_HOST},
  {"\377\200topi", h_topi_command,     NULL,        CM_HOST},
  {"\377\200udat", h_udat_command,     NULL,        CM_HOST},
  {"\377\200umsg", h_umsg_command,     NULL,        CM_HOST},
  {"\377\200user", h_user_command,     NULL,        CM_HOST},
  
  {NULL,           0,                  NULL,        0}
};

static char *getargp;

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

void appendstring(cp, string)
struct connection *cp;
const char *string;
{
  struct mbuf *bp, *p;
  char buffer[4096];
  const char *p_string;

  if (!*string) return;

  if (cp->type == CT_USER) {
    strcpy(buffer, string);
    convert(ISO, cp->charset_out, buffer);
    p_string = buffer;
  } else p_string = string;

  bp = (struct mbuf *) malloc(sizeof(*bp) + strlen(p_string) + 1);
  bp->next = 0;
  bp->data = strcpy((char *) (bp + 1), p_string);

  if (cp->obuf) {
    for (p = cp->obuf; p->next; p = p->next) ;
    p->next = bp;
  } else {
    cp->obuf = bp;
    FD_SET(cp->fd, &chkwrite);
  }
}

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

void appendc(cp, n, ast)
struct connection *cp;
const int n;
const int ast;
{
  static char x[2];
  char c;

  c = cp->prompt[n];
  if (c == '\0') {
    if (ast) {
      appendstring(cp, "***\n");
    }
  } else {
    x[0] = c;
    x[1] = '\0';
    appendstring(cp, x);
  }
}

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

void appendprompt(cp, ast)
struct connection *cp;
const int ast;
{
  if (*cp->query)
    appendc(cp, 1, ast);
  else
    appendc(cp, 2, ast);
#ifdef POSIX
  fflush(NULL);
#endif
}

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

int queuelength(bp)
const struct mbuf *bp;
{
  int len;

  for (len = 0; bp; bp = bp->next)
    len += strlen(bp->data);
  return len;
}

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

void destroy_channel(number)
int number;
{
  struct channel *ch, *ch1;

  ch1 = NULLCHANNEL;
  for (ch = channels; ch; ch = ch->next) {
    if (ch->chan == number) {
      if (ch1) {
	ch1->next = ch->next;
      } else {
	channels = ch->next;
      }
      free(ch);
      break;
    }
    ch1 = ch;
  }
}

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

void free_connection(cp)
struct connection *cp;
{
  struct mbuf *bp;
  struct permlink *p;
  int pl;

  for (pl = 0; pl < NR_PERMLINKS; pl++) {
    p = permarray[pl];
    if (p && (p->connection == cp)) {
      p->connection = NULLCONNECTION;
      break;
    }
  }
  if (cp->fd >= 0) {
#if defined(__OS2__)
    soclose(cp->fd);
#else
    close(cp->fd);
#endif
    FD_CLR(cp->fd, &chkread);
    FD_CLR(cp->fd, &chkwrite);
    if (cp->fd == maxfd)
      while (--maxfd >= 0)
	if (FD_ISSET(maxfd, &chkread)) break;
    cp->fd = -1;
  }
  while ((bp = cp->obuf) != NULL) {
    cp->obuf = bp->next;
    free(bp);
  }
  free(cp);
  if (pl < NR_PERMLINKS) {
    if (p && !p->permanent) {
      permarray[pl] = NULLPERMLINK;
    }
  }
}

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

void free_closed_connections()
{
  struct connection *cp, *p;
  struct permlink *l;
  int pl;
  
  for (p = NULLCONNECTION, cp = connections; cp; ) {
    l = NULLPERMLINK;
    for (pl = 0; pl < NR_PERMLINKS; pl++) {
      if ((permarray[pl]) && (permarray[pl]->connection == cp)) {
	l = permarray[pl];
	break;
      }
    }
    if (l) {
      if (cp->type == CT_CLOSED ||
	  (cp->type == CT_UNKNOWN && cp->time + l->waittime - 5 < currtime)) {
	if (p) {
	  p->next = cp->next;
	  free_connection(cp);
	  cp = p->next;
	} else {
	  connections = cp->next;
	  free_connection(cp);
	  cp = connections;
	}
      } else {
	p = cp;
	cp = cp->next;
      }
    } else {
      if (cp->type == CT_CLOSED) {
	if (p) {
	  p->next = cp->next;
	  free_connection(cp);
	  cp = p->next;
	} else {
	  connections = cp->next;
	  free_connection(cp);
	  cp = connections;
	}
      } else {
	p = cp;
	cp = cp->next;
      }
    }
  }
}

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

char *getarg(line, all)
char *line;
int all;
{
  char *arg;
  int c;

  if (line) getargp = line;
  while (isspace(uchar(*getargp))) getargp++;
  if (all) return getargp;
  arg = getargp;
  while (*getargp && !isspace(uchar(*getargp))) {
    c = tolower(uchar(*getargp));
    *getargp++ = c;
  }
  if (*getargp) *getargp++ = '\0';
  return arg;
}

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

char *getargcs(line, all)
char *line;
int all;
{
  char *arg;

  if (line) getargp = line;
  while (isspace(uchar(*getargp))) getargp++;
  if (all) return getargp;
  arg = getargp;
  while (*getargp && !isspace(uchar(*getargp))) {
    getargp++;
  }
  if (*getargp) *getargp++ = '\0';
  return arg;
}

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

char *formatline(prefix, text, linelen)
char *prefix, *text;
int linelen;
{
  char *f, *t, *x;
  int prefixlen, l, lw;
  static char buf[2048];

  linelen--;
  prefixlen = strlen(prefix);
  for (f = prefix, t = buf; *f; *t++ = *f++) ;
  l = t - buf;
  f = text;

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

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

char *ts(gmt)
long gmt;
{
  static char buffer[80];
  static char monthnames[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
  struct tm *tm;

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

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

char *ts2(gmt)
long gmt;
{
  static char buffer[80];
  static char monthnames[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
  struct tm *tm;

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

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

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

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

char *ts4(seconds)
long seconds;
{
  int days, hours, minutes;
  static char buffer[2048];
  char buf[128];

  days = seconds / 86400;
  seconds -= days * 86400;
  hours = seconds / 3600;
  seconds -= hours * 3600;
  minutes = seconds / 60;
  seconds -= minutes * 60;
  buffer[0] = '\0';
  if (days) {
    sprintf(buf, "%d days, ", days);
    strcpy(buffer, buf);
  }
  if (days+hours) {
    sprintf(buf, "%d hours, ", hours);
    strcat(buffer, buf);
  }
  if (days+hours+minutes) {
    sprintf(buf, "%d minutes, ", minutes);
    strcat(buffer, buf);
  }
  sprintf(buf, "%ld seconds.", seconds);
  strcat(buffer, buf);
  return buffer;
}

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

struct connection *alloc_connection(fd)
int fd;
{
  int flags;
  struct connection *cp;
#if defined(__OS2__)
  int mask = 1;
#endif

#if defined(__OS2__)
  if (ioctl(fd, FIONBIO, (unsigned char *)&mask, sizeof(mask)) == -1) {
    soclose(fd);
    return(0);
  }
#else
  if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
    close(fd);
    return 0;
  }
  if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
    close(fd);
    return 0;
  }
#endif
  cp = (struct connection *) calloc(1, sizeof(*cp));
  cp->fd = fd;
  cp->time = currtime;
  cp->verbose = 0;
  cp->prompt[0] = 0;
  cp->prompt[1] = 0;
  cp->prompt[2] = 0;
  cp->prompt[3] = 0;
  *cp->rev = '\0';
  *cp->away = '\0';
  cp->chan_list = NULLCLIST;
  cp->amprnet = amprnet;
  cp->atime = currtime;
  cp->mtime = 0L;
  *cp->pers = '\0';
  cp->width = 80;
  cp->next = connections;
  connections = cp;
  if (maxfd < fd) maxfd = fd;
  FD_SET(fd, &chkread);
  return cp;
}

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

void accept_connect_request(flisten)
int flisten;
{
  FILE *fdi;
  char buffer[2048];
  int addrlen;
  int fd, flags;
  struct sockaddr addr;
  struct connection *cp;
#ifdef LOGGING
  struct sockaddr_in *sin = (struct sockaddr_in *)&addr;
  struct hostent *hp;
  char hostname[128];
  int allow;
  extern int hosts_ctl __ARGS((char *daemon, char *name, char *addr, char *user));
#endif
#if defined(__OS2__)
  int mask = 1;
#endif

  addrlen = sizeof(addr);
  if ((fd = accept(flisten, &addr, &addrlen)) < 0) return;
#if defined (__OS2__)
  if (ioctl(fd, FIONBIO, (unsigned char *)&mask, sizeof(mask)) == -1 ) {
#else
  if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
    close(fd);
    return;
  }
  if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
#endif
#ifdef LOGGING
    openlog(convtype, LOG_PID, LOG_MAIL);
    syslog(LOG_INFO, "fcntl failed connection request, reason %d", errno);
    closelog();
#endif
#if defined(__OS2__)
    soclose(fd);
#else
    close(fd);
#endif
    return;
  }

#ifdef LOGGING
  if(sin->sin_family == AF_INET){
    openlog(convtype, LOG_PID, LOG_MAIL);

    if ((hp = gethostbyaddr((char *) &sin->sin_addr, sizeof(sin->sin_addr), AF_INET)) != NULL) {
      strcpy(hostname, hp->h_name);
      if (strcmp(hostname, "localhost")) {
        syslog(LOG_INFO, "connect from %s", hostname);
      }
    } else {
      syslog(LOG_INFO, "connect from %s", inet_ntoa(sin->sin_addr));
    }
    allow = hosts_ctl(convtype, "", inet_ntoa(sin->sin_addr), "");
    if (!allow){
      syslog(LOG_WARNING, "refused connect from %s",  inet_ntoa(sin->sin_addr));
      closelog();
#if defined (__OS2__)
      soclose(fd);
#else
      close(fd);
#endif
      return;
    }
    closelog();
  }
#endif
  cp = alloc_connection(fd);
  if (!access(issuefile, R_OK)) {
    fdi = fopen(issuefile, "r");
    buffer[0] = '*';
    buffer[1] = ' ';
    while (!feof(fdi)) {
      fgets(buffer+2, 2045, fdi);
      if (!feof(fdi)) {
        appendstring(cp, buffer);
      }
    }
    fclose(fdi);
  }
}

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

int count_user(channel)
int channel;
{
  struct connection *p;
  struct clist *cl;
  int n = 0;

  for (p = connections; p; p = p->next) {
    if (p->type == CT_USER) {
      if (p->via && (p->channel == channel)) {
	n++;
      } else {
	for (cl = p->chan_list; cl; cl = cl->next) {
	  if (cl->channel == channel) {
	    n++;
	    break;
	  }
	}
      }
    }
  }
  return n;
}

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

void clear_locks()
{
  struct connection *p;

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

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

void send_awaymsg(fromname, hostname, channel, time, text)
char *fromname, *hostname;
int channel;
long time;
char *text;
{
  char buffer[2048];
  register struct clist *cl;
  register struct connection *p;

  for (p = connections; p; p = p->next) {
    if (p->type == CT_HOST) {
      if (!p->locked) {
	sprintf(buffer, "/\377\200AWAY %s %s %d %ld %s\n", fromname, hostname, channel, time, text);
	appendstring(p, buffer);
	p->locked = 1;
      }
    } else {
      if (!p->locked && !p->via) {
	for (cl = p->chan_list; cl; cl = cl->next) {
	  if (cl->channel == channel) break;
	}
	if (cl) {
	  appendc(p, 0, 0);
	  appendc(p, 3, 0);
	  if (*text != '\0')
	    sprintf(buffer, "*** (%s) %s@%s has gone away:\n    %s\n", ts2(currtime), fromname, hostname, text);
	  else
	    sprintf(buffer, "*** (%s) %s@%s is back again.\n", ts2(currtime), fromname, hostname);
	  appendstring(p, buffer);
	  appendprompt(p, 0);
	}
	p->locked = 1;
      }
    }
  }
}

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

void send_mode(ch)
struct channel *ch;
{
  register struct connection *p;
  char buffer[2048];
  char flags[16];

  flags[0] = '\0';
  if (ch->flags & M_CHAN_S) {
    strcat(flags, "S");
  }
  if (ch->flags & M_CHAN_P) {
    strcat(flags, "P");
  }
  if (ch->flags & M_CHAN_T) {
    strcat(flags, "T");
  }
  if (ch->flags & M_CHAN_I) {
    strcat(flags, "I");
  }
  if (ch->flags & M_CHAN_M) {
    strcat(flags, "M");
  }
  if (ch->flags & M_CHAN_L) {
    strcat(flags, "L");
  }
  for (p = connections; p; p = p->next) {
    if (!p->locked && (p->type == CT_HOST)) {
      sprintf(buffer, "/\377\200MODE %d -sptiml+%s\n", ch->chan, flags);
      appendstring(p, buffer);
    }
  }
}

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

void send_opermsg(toname, hostname, fromname, channel)
char *toname, *hostname, *fromname;
int channel;
{
  char buffer[2048];
  register struct connection *p;

  for (p = connections; p; p = p->next) {
    if (!p->locked) {
      if (p->type == CT_HOST) {
	sprintf(buffer, "/\377\200OPER %s %d %s\n", fromname, channel, toname);
	appendstring(p, buffer);
	p->locked = 1;
      } else {
	if (p->type == CT_USER) {
	  if (!p->via && (channel != -1)) {
	    if (!strcmp(p->name,toname) && (p->channel == channel)) {
	      appendc(p, 0, 0);
	      appendc(p, 3, 0);
	      sprintf(buffer, "*** (%s) %s made you a channel operator for channel %d\n",
		      ts2(currtime), fromname, channel);
	      appendstring(p, buffer);
	      appendprompt(p, 0);
	      p->locked = 1;
	    } else {
	      if (p->verbose) {
		appendc(p, 0, 0);
		appendc(p, 3, 0);
		sprintf(buffer, "*** (%s) %s@%s is now a channel operator for channel %d\n",
			ts2(currtime), toname, hostname, channel);
		appendstring(p, buffer);
		appendprompt(p, 0);
		p->locked = 1;
	      }
	    }
	  }
	}
      }
    }
  }
}

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

void send_persmsg(fromname, hostname, channel, text, time)
char *fromname, *hostname;
int channel;
char *text;
long time;
{
  char buffer[2048];
  register struct connection *p;
  struct channel *ch;
  char chan[128];
  int mychannel;
  struct clist *cl;

  for (ch = channels; ch; ch = ch->next) {
    if (ch->chan == channel) break;
  }
  sprintf(chan, "channel %d", channel);
  if (ch) {
    if (ch->flags & M_CHAN_S) sprintf(chan, "secret channel %d", channel);
    if (ch->flags & M_CHAN_I) sprintf(chan, "invisible channel %d", channel);
  }
  for (p = connections; p; p = p->next) {
    if (p->type == CT_HOST) {
      if (!p->locked) {
	if (p->amprnet) {
	  sprintf(buffer, "/\377\200USER %s %s %ld %d %d %s\n", fromname, hostname, time, channel, channel, text);
	} else {
	  sprintf(buffer, "/\377\200UDAT %s %s %s\n", fromname, hostname, text);
	}
	appendstring(p, buffer);
	p->locked = 1;
      }
    } else {
      mychannel = -1;
      for (cl = p->chan_list; cl; cl = cl->next) {
	if (cl->channel == channel) {
	  mychannel = channel;
	  break;
	}
      }
      if ((p->type == CT_USER) && !p->locked && !p->via &&
	  (p->verbose || (mychannel == channel))) {
	appendc(p, 0, 0);
	appendc(p, 3, 0);
	if ((*text != '\0') && (strcmp(text, "@"))) {
	  sprintf(buffer, "*** (%s) %s@%s on %s set personal text:\n    %s\n", ts2(currtime), fromname, hostname, chan, text);
	} else {
	  sprintf(buffer, "*** (%s) %s@%s on %s removed personal text.\n", ts2(currtime), fromname, hostname, chan);
	}
	appendstring(p, buffer);
	appendprompt(p, 0);
      }
      p->locked = 1;
    }
  }
}

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

void send_topic(fromname, hostname, time, channel, text)
char *fromname, *hostname;
long time;
int channel;
char *text;
{
  char buffer[2048];
  register struct connection *p;
  register struct channel *ch;
  char chan[128];
  int mychannel, flags = 0;
  struct clist *cl;

  for (ch = channels; ch; ch = ch->next)
    if (ch->chan == channel) break;
  if (ch) {
    sprintf(chan, "channel %d", channel);
    flags = ch->flags;
    if (flags & M_CHAN_S) sprintf(chan, "secret channel %d", channel);
    if (flags & M_CHAN_I) sprintf(chan, "invisible channel %d", channel);
    if (ch->time < time) {
      strcpy(ch->topic, text);
      ch->time = time;
      for (p = connections; p; p = p->next) {
        if (p->type == CT_HOST) {
	  if (!p->locked) {
	    sprintf(buffer, "/\377\200TOPI %s %s %ld %d %s\n", fromname, hostname, time, channel, text);
	    appendstring(p, buffer);
	    p->locked = 1;
	  }
        } else {
	  mychannel = -1;
	  for (cl = p->chan_list; cl; cl = cl->next) {
	    if (cl->channel == channel) {
	      mychannel = channel;
	      break;
	    }
	  }
	  if ((p->type == CT_USER) && !p->locked && !p->via && 
	        ((!(ch->flags & M_CHAN_I) && p->verbose) || (mychannel==channel) )) {
	    appendc(p, 0, 0);
	    appendc(p, 3, 0);
	    if (*text != '\0')
	      sprintf(buffer, "*** (%s) %s@%s on %s set channel topic:\n    %s\n", ts2(currtime), fromname, hostname, chan, text);
	    else
	      sprintf(buffer, "*** (%s) %s@%s on %s removed channel topic.\n", ts2(currtime), fromname, hostname, chan);
	    appendstring(p, buffer);
	    appendprompt(p, 0);
	  }
	  p->locked = 1;
        }
      }
    }
  }
}

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

void log_user_change(name, host, oldchannel, newchannel)
char *name, *host;
int oldchannel, newchannel;
{
  if (oldchannel < 0 || newchannel < 0) {
    FILE *f;
    long found;
    char *p, *q;
    char xname[512];

    f = fopen(userfile,"r+");
    if (f == NULL) {
      f = fopen(userfile,"w+");
      if (f == NULL)
	return;
    }
    
    q = xname;
    for (p = name; isalnum(*p); p++) *q++ = *p;
    *q++ = '@';
    *q = '\0';
    strcat(xname, host);

    /* zuerst die passende zeile suchen */
    found = -1l;
    while (!feof(f)) {
      char buffer[512];
      found = ftell(f);
      fgets(buffer, 512, f);
      if (!strncmp(buffer,xname,strlen(xname))  && 
	  ((oldchannel < 0 && strstr(buffer," +")) ||
	   (newchannel < 0 && strstr(buffer," -"))))
	break;
    }

    rewind(f);
    if (found >= 0l) {
      fseek(f, found, SEEK_SET);
    }
    else {
      fclose(f);
      f = fopen(userfile,"a");
    }

    if (f != NULL) {
      struct tm *tm;

      tm = localtime(&currtime);
      fprintf(f,"%-20s %2d.%02d.%02d %2d:%02d %c\n", 
	      xname, tm->tm_mday,tm->tm_mon+1,tm->tm_year,tm->tm_hour,tm->tm_min,
	      (oldchannel < 0) ? '+' : '-');
    }
    fclose(f);
  }
}

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

void send_user_change_msg(name, host, oldchannel, newchannel, pers, time)
char *name, *host;
int oldchannel, newchannel;
char *pers;
long time;
{
  char buffer[2048];
  struct connection *p;
  char oldchan[128], newchan[128];
  struct channel *ch;
  int oldflags = 0, newflags = 0;
  int mychannel;
  struct clist *cl;

  sprintf(newchan, "channel %d", newchannel);
  sprintf(oldchan, "channel %d", oldchannel);
  for (ch = channels; ch; ch = ch->next)
    if (ch->chan == newchannel) break;
  if (ch) {
    newflags = ch->flags;
    if (newflags & M_CHAN_S) {
      sprintf(newchan, "secret channel %d", newchannel);
    }
    if (newflags & M_CHAN_I) {
      sprintf(newchan, "invisible channel %d", newchannel);
    }
  }
  for (ch = channels; ch; ch = ch->next)
    if (ch->chan == oldchannel) break;
  if (ch) {
    oldflags = ch->flags;
    if (oldflags & M_CHAN_S) {
      sprintf(oldchan, "secret channel %d", oldchannel);
    }
    if (oldflags & M_CHAN_I) {
      sprintf(oldchan, "invisible channel %d", oldchannel);
    }
  }
  log_user_change(name, host, oldchannel,newchannel);
  for (p = connections; p; p = p->next) {
    mychannel = -1;
    if (p->type == CT_USER && !p->via && !p->locked) {
      for (cl = p->chan_list; cl; cl = cl->next) {
	if (cl->channel == oldchannel) {
	  mychannel = oldchannel;
	  break;
	}
      }
      if ((newchannel == oldchannel) && (newchannel != -1)) {
	if ((p->verbose && !(newflags & M_CHAN_I)) || (mychannel == newchannel)) {
	  appendc(p, 0, 0);
	  appendc(p, 3, 0);
	  if ((*pers != '\0') && (strcmp(pers, "@")))
            sprintf(buffer, "*** (%s) %s@%s on %s set personal text:\n    %s\n", ts2(currtime), name, host, newchan, pers);
          else
            sprintf(buffer, "*** (%s) %s@%s on %s removed personal text.\n", ts2(currtime), name, host, newchan);
          appendstring(p, buffer);
          appendprompt(p, 0);
          p->locked = 1;
	}
      } else {
	if (oldchannel >= 0) {
	  if ((!(oldflags & M_CHAN_I) && p->verbose) || (mychannel == oldchannel)) {
	    appendc(p, 0, 0);
	    appendc(p, 3, 0);
	    if (strlen(pers) < 2) {
	      sprintf(buffer, "*** (%s) %s@%s left %s.\n", ts2(currtime), name, host, oldchan);
	    } else {
	      sprintf(buffer, "*** (%s) %s@%s left %s (%s).\n", ts2(currtime), name, host, oldchan, pers);
	    }
	    appendstring(p, buffer);
	    appendprompt(p, 0);
	    p->locked = 1;
	  }
	}
	for (cl = p->chan_list; cl; cl=cl->next) {
	  if (cl->channel == newchannel) {
	    break;
	  }
	}
	if (newchannel >= 0) {
	  sprintf(buffer, " %s ", name);
	  if ((cl) || (!(newflags & M_CHAN_I) && (p->verbose || strstr(p->notify, buffer)))) {
	    appendc(p, 0, 0);
	    appendc(p, 3, 0);
	    sprintf(buffer, "*** (%s) %s@%s joined %s\n", ts2(currtime), name, host, newchan);
	    appendstring(p, buffer);
	    appendprompt(p, 0);
	    p->locked = 1;
	  }
	}
      }
    }
    if (p->type == CT_HOST && !p->locked) {
      if (time == currtime) {
	time++;
      }
      if (p->amprnet) {
	sprintf(buffer, "/\377\200USER %s %s %ld %d %d %s\n", name, host, time, oldchannel, newchannel, pers);
      } else {
	sprintf(buffer, "/\377\200USER %s %s %ld %d %d\n", name, host, time, oldchannel, newchannel);
	if (newchannel != -1) {
	  if ((oldchannel != -1) || ((pers) && (*pers) && (*pers != '@'))) {
	    appendstring(p, buffer);
	    sprintf(buffer, "/\377\200UDAT %s %s %s\n", name, host, pers);
	  }
	}
      }
      appendstring(p, buffer);
      p->locked = 1;
    }
  }
}

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

void send_msg_to_local_user(fromname, toname, text)
char *fromname, *toname, *text;
{

  char buffer[2048];
  struct connection *p;

  for (p = connections; p; p = p->next)
    if (p->type == CT_USER && !strcmp(p->name, toname) && !p->locked)
      if (!p->via) {
        if (!p->locked) {
          appendc(p, 0, 0);
          appendc(p, 3, 0);
	  if (strcmp(fromname, "conversd")) {
            sprintf(buffer, "<*%s*>:", fromname);
            appendstring(p, formatline(buffer, text, p->width));
            if (p->away[0] != '\0' && text[0] != '*') {
              sprintf(buffer,"*** (%s) %s is away: %s", ts2(currtime), p->name, p->away);
              send_msg_to_local_user("conversd",fromname,buffer);
            }
          } else {
            appendstring(p, text);
            appendstring(p, "\n");
          }
          appendprompt(p, 0);
          p->locked = 1;
        }
      }
}

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

void send_msg_to_user(fromname, toname, text)
char *fromname, *toname, *text;
{

  char buffer[2048];
  struct connection *p;

  for (p = connections; p; p = p->next)
    if (p->type == CT_USER && !strcmp(p->name, toname) && !p->locked)
      if (p->via) {
	if (!p->via->locked) {
	  sprintf(buffer, "/\377\200UMSG %s %s %s\n", fromname, toname, text);
	  appendstring(p->via, buffer);
	  p->via->locked = 1;
	  p->locked = 1;
	}
      } else {
	if (!p->locked) {
          appendc(p, 0, 0);
	  appendc(p, 3, 0);
	  if (strcmp(fromname, "conversd")) {
	    sprintf(buffer, "<*%s*>:", fromname);
	    appendstring(p, formatline(buffer, text, p->width));
            if (p->away[0] != '\0' && text[0] != '*') {
              sprintf(buffer,"*** (%s) %s is away: %s", ts2(currtime), p->name, p->away);
              send_msg_to_local_user("conversd",fromname,buffer);
            }
 	  } else {
	    appendstring(p, text);
	    appendstring(p, "\n");
	  }
          appendprompt(p, 0);
	  p->locked = 1;
	}
      }
}

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

void send_msg_to_channel(fromname, channel, text)
char *fromname;
int channel;
char *text;
{
  char buffer[2048];
  struct connection *p;
  struct channel *ch;
  char addchan[16];
  struct clist *cl;
  int printit;

  for (ch = channels; ch; ch = ch->next) {
    if (ch->chan == channel) break;
  }
  for (p = connections; p; p = p->next) {
    if (p->type == CT_USER && !strcmp(p->name, fromname)) {
      p->mtime = currtime;
    }
  }
  for (p = connections; p; p = p->next) {
    if (p->type == CT_USER) {
      printit = 0;
      if (p->channel == channel) {
	printit = 1;
	addchan[0] = '\0';
      } else {
	for (cl = p->chan_list; cl ; cl = cl->next) {
	  if (cl->channel == channel) {
	    printit = 1;
	    sprintf(addchan, "%d:", channel);
	  }
	}
      }
      if (printit) {
	if (p->via) {
	  if (!p->via->locked && !(ch->flags & M_CHAN_L)) {
	    sprintf(buffer, "/\377\200CMSG %s %d %s\n", fromname, channel, text);
	    appendstring(p->via, buffer);
	    p->via->locked = 1;
	  }
	} else {
	  if (!p->locked) {
	    appendc(p, 0, 0);
	    appendc(p, 3, 0);
	    if (strcmp(fromname, "conversd")) {
	      sprintf(buffer, "<%s%s>:", addchan, fromname);
	      appendstring(p, formatline(buffer, text, p->width));
	    } else {
	      appendstring(p, text);
	      appendstring(p, "\n");
	    }
	    appendprompt(p, 0);
	    p->locked = 1;
	  }
	}
      }
    }
  }
}

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

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

  static char invitetext[] = "\n\007\007*** (%s) Message from %s...\nPlease join %s channel %d.\n\007\007\n";

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

  char buffer[2048];
  char convtype2[2048];
  int fdtty;
  int fdut;
  struct connection *p;
  struct stat stbuf;
#if !defined(__OS2__)
  struct utmp utmpbuf;
#endif

  strcpy(convtype2, convtype);
  convtype2[strlen(convtype2) - 1] = '\0';
  for (p = connections; p; p = p->next)
    if (p->type == CT_USER && !strcmp(p->name, toname)) {
      if (p->channel == channel) {
	clear_locks();
	sprintf(buffer, "*** (%s) User %s is already on this channel.", ts2(currtime), toname);
	send_msg_to_user("conversd", fromname, buffer);
	return;
      }
      if (!p->via && !p->locked) {
	sprintf(buffer, invitetext, ts2(currtime), fromname, convtype2, channel);
	p->invitation_channel = channel;
	appendstring(p, buffer);
	clear_locks();
	sprintf(buffer, responsetext, ts2(currtime), convtype2, toname, myhostname);
	send_msg_to_user("conversd", fromname, buffer);
	return;
      }
      if (p->via && !p->via->locked) {
	sprintf(buffer, "/\377\200INVI %s %s %d\n", fromname, toname, channel);
	appendstring(p->via, buffer);
	return;
      }
    }

#if !defined(__OS2__)
    if ((fdut = open("/etc/utmp", O_RDONLY, 0644)) >= 0) {
      while (read(fdut, (char *)&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, &stbuf)) continue;
	  if (!(stbuf.st_mode & 2)) continue;
	  if ((fdtty = open(buffer, O_WRONLY | O_NOCTTY, 0644)) < 0) continue;
	  sprintf(buffer, invitetext, ts2(currtime), fromname, convtype2, channel);
	  if (!fork()) {
	    write(fdtty, buffer, strlen(buffer));
	    exit(0);
	  }
	  close(fdtty);
	  close(fdut);
	  clear_locks();
	  sprintf(buffer, responsetext, ts2(currtime), convtype2, toname, myhostname);
	  send_msg_to_user("conversd", fromname, buffer);
	  return;
	}
      close(fdut);
    }
#endif

  for (p = connections; p; p = p->next)
    if (p->type == CT_HOST && !p->locked) {
      sprintf(buffer, "/\377\200INVI %s %s %d\n", fromname, toname, channel);
      appendstring(p, buffer);
    }

}

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

void update_destinations(p, name, rtt, rev)
struct permlink *p;
char *name;
long rtt;
char *rev;
{
  struct permlink *l;
  struct destination *d, *d1;
  int pl;
  char buffer[2048];
  
  for (d = destinations; d; d = d->next)
    if (!strcmp(d->name, name)) break;
  
  if (!d) {
    d = (struct destination *)malloc(sizeof(struct destination));
    if (d) {
      d->last_sent_rtt = 0;
      d->link = p;
      strcpy(d->name, name);
      strcpy(d->rev, rev);
      if (!destinations || (! destinations->name) || (strcmp(destinations->name, name) > 0)) {
	d->next = destinations;
	destinations = d;
      } else {
	d1 = destinations;
	while (d1->next) {
	  if (strcmp(d1->next->name, name) > 0) {
	    d->next = d1->next;
	    d1->next = d;
	    break;
	  }
	  d1 = d1->next;
	}
	if (!d1->next) {
	  d->next = d1->next;
	  d1->next = d;
	}
      }
    }
  } else {
    strcpy(d->rev, rev);
    d->link = p;
  }
      
  d->rtt = rtt;
  if (abs(rtt - d->last_sent_rtt) > (d->last_sent_rtt / 8)) {
    for (pl = 0; pl < NR_PERMLINKS; pl++) {
      l = permarray[pl];
      if (l && (l->connection) && (l->connection->type == CT_HOST)) {
	if (rtt) {
	  sprintf(buffer, "/\377\200DEST %s %ld %s\n", name, rtt+(l->txtime+l->rxtime)/2L, rev);
	} else {
	  sprintf(buffer, "/\377\200DEST %s 0\n", name);
	}
	if (strcmp(d->link->name, l->name)) {
	  appendstring(l->connection, buffer);
	  d->last_sent_rtt = rtt;
	}
      }
    }
  }
}
  
/*---------------------------------------------------------------------------*/

struct permlink *update_permlinks(name, cp, isperm)
char *name;
struct connection *cp;
int isperm;
{
  struct destination *d;
  struct permlink *p;
  int pl;

  for (pl = 0; pl < NR_PERMLINKS; pl++) {
    p = permarray[pl];
    if (p && !strcmp(p->name, name)) {
      for (d = destinations; d; d = d->next) {
	if (d->rtt && (d->link == p)) {
	  update_destinations(p, d->name, 0, "");
	}
      }
      if (cp) {
	p->connection = cp;
	p->statetime = currtime;
	p->tries = 0;
	p->waittime = 9;
	p->rxtime = 0;
	p->txtime = 0;
	p->testwaittime = currtime;
	p->testnexttime = currtime + 60;
	p->retrytime = currtime + p->waittime;
	if (isperm) {
	  p->permanent = isperm;
	}
      } else {
	if (!p->permanent) {
	  free(p);
	  permarray[pl] = NULLPERMLINK;
	}
      }
      return p;
    }
  }
  for (pl = 0; pl < NR_PERMLINKS; pl++) {
    if (!permarray[pl]) break;
  }
  if (pl < NR_PERMLINKS) {
    p = (struct permlink *) calloc(1, sizeof(*p));
    strcpy(p->name, name);
    *p->socket = '\0';
    *p->command = '\0';
    p->permanent = 0;
    p->connection = cp;
    p->statetime = currtime;
    p->tries = 0;
    p->waittime = 9;
    p->rxtime = 0;
    p->txtime = 0;
    p->testnexttime = currtime + 60;
    p->testwaittime = currtime;
    p->retrytime = currtime + p->waittime;
    p->permanent = isperm;
    permarray[pl] = p;
  } else {
    bye_command(cp);		/* no space left in array */
    return NULLPERMLINK;
  }
  return p;
}

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

void connect_permlinks()
{

#define MAX_WAITTIME   (60*60*3)

  char buffer[2048];
  int addrlen;
  int fd;
  int flags;
  int pl;
  struct connection *cp;
  struct permlink *p;
  struct sockaddr *addr;
  struct destination *d;
#if defined(__OS2__)
  int mask = 1;
#endif

  for (pl = 0; pl < NR_PERMLINKS; pl++) {
    p = permarray[pl];
    if (p && p->connection) {
      if (p->testnexttime < currtime) {
	if ((p->testwaittime + 7300) < currtime) {
	  p->rxtime = 0;
	  p->txtime = 0;
	  for (d = destinations; d; d = d->next) {
	    if (d->link == p) {
	      update_destinations(p, d->name, 0, "");
	    }
	  }
	}
        appendstring(p->connection, "/\377\200PING\n");
        p->testwaittime = currtime;
        p->testnexttime = currtime + 7300;
      }
    }
  }
  for (pl = 0; pl < NR_PERMLINKS; pl++) {
    p = permarray[pl];
    if (p && !p->connection) {
      if (p->retrytime < currtime) {
        p->tries++;
        if (p->waittime == 9) p->waittime = 300;	/* *2 = 10 minutes */
        p->waittime <<= 1;		/* 10,20,40,80,160,180,180,... min */
        if (p->waittime > MAX_WAITTIME) p->waittime = MAX_WAITTIME;
        p->retrytime = currtime + p->waittime;
        if (!(addr = build_sockaddr(p->socket, &addrlen))) continue;
        if ((fd = socket(addr->sa_family, SOCK_STREAM, 0)) < 0) continue;
#if defined(__OS2__)
        if (ioctl(fd, FIONBIO, (unsigned char *)&mask, sizeof(mask)) == -1) {
#else
	if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
	  close(fd);
	  continue;
	}
	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
#endif
#ifdef LOGGING
	  openlog(convtype, LOG_PID, LOG_MAIL);
	  syslog(LOG_INFO, "fcntl failed to %s connect, reason %d", p->name, errno);
	  closelog();
#endif
#if defined (__OS2__)
          soclose(fd);
#else
	  close(fd);
#endif
	  continue;
	}
        if (connect(fd, addr, addrlen)) {
#if defined(__OS2__)
          if (sock_errno() != SOCEINPROGRESS) {
#ifdef LOGGING
             sock_errno("connect");
#endif
             soclose(fd);
             continue;
          }
#else
	  if (errno != EINPROGRESS) {
#ifdef LOGGING
	    openlog(convtype, LOG_PID, LOG_MAIL);
	    syslog(LOG_INFO, "connect failed to %s, reason %d", p->name, errno);
	    closelog();
#endif
	    close(fd);
	    continue;
	  }
#endif
        }
	cp = alloc_connection(fd);
#ifdef LOGGING
	if (!cp) {
	  openlog(convtype, LOG_PID, LOG_MAIL);
	  syslog(LOG_INFO, "cp == 0 on %s connect", p->name);
	  closelog();
	}
#endif
	p->connection = cp;
        if (*p->command) appendstring(cp, p->command);
        appendstring(cp, convcmd);
        sprintf(buffer, "/\377\200HOST %s %s %s\n", myhostname, myrev, myfeatures);
        appendstring(cp, buffer);
        p->testnexttime = currtime + 5;
      }
    }
  }
}

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

static void process_input(cp)
struct connection *cp;
{
  char *arg;
  char buffer[2048];
  int arglen;
  struct cmdtable *cmdp;
  struct channel *ch;

  clear_locks();
  cp->locked = 1;

  if (cp->type == CT_USER)
    convert(cp->charset_in, ISO, cp->ibuf);

  if (*cp->ibuf == '/') {
    arglen = strlen(arg = getarg(cp->ibuf + 1, 0));
    for (cmdp = cmdtable; cmdp->name; cmdp++)
      if (!strncmp(cmdp->name, arg, arglen)) {
	if (cmdp->states & (1 << cp->type)) (*cmdp->fnc)(cp);
	return;
      }
    if (cp->type == CT_USER) {
      sprintf(buffer, "*** Unknown command '/%s'. Type /HELP for help.\n", arg);
      appendstring(cp, buffer);
      appendprompt(cp, 0);
    }
    return;
  }

  if (cp->type == CT_USER) {
    if (*cp->away) {
      sprintf(buffer, "*** You are away, aren't you ? :-)\n");
      appendstring(cp, buffer);
    }
    if (cp->expected) {
      getarg(cp->ibuf, 1);	/* just fill that static variable */
      operator_command(cp);
      cp->expected = 0;
    } else {
      if (cp->query[0] == '\0') {
	for (ch = channels; ch; ch=ch->next) {
	  if (ch->chan == cp->channel) break;
	}
	if (cp->operator || cp->channelop || !(ch->flags & M_CHAN_M)) {
	  send_msg_to_channel(cp->name, cp->channel, cp->ibuf);
	} else {
	  appendstring(cp, "*** This is a moderated channel. Only channel operators may write.\n");
	}
      } else {
	send_msg_to_user(cp->name, cp->query, cp->ibuf);
      }
    }
  }
  appendprompt(cp, 0);
}

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

static void read_configuration()
{
  FILE *fp;
  char *host_name, *sock_name;
  char line[1024];
  int group, quality;
  struct permlink *p;

  amprnet = 1;
  if (!(fp = fopen(conffile, "r"))) return;
  while (fgets(line, sizeof(line), fp)) {
    if ((line[0] != '#') && (line[0] != ';')) {
      host_name = getargcs(line, 0);
      secretnumber = atoi(getarg(0, 0));
      sock_name = getarg(0, 0);
      if (*host_name) {
        if (myhostname) free(myhostname);
        myhostname = (char *) malloc(strlen(host_name)+1);
        strcpy(myhostname, host_name);
      }
      if (*sock_name == 'i') {
	amprnet = 0;
      }
      if (*host_name) break;
    }
  }
  while (fgets(line, sizeof(line), fp)) {
    if ((line[0] != '#') && (line[0] != ';')) {
      host_name = getargcs(line, 0);
      group = atoi(getarg(0, 0));
      quality = atoi(getarg(0, 0));
      sock_name = getarg(0, 0);
      if (*host_name && *sock_name) {
        p = update_permlinks(host_name, NULLCONNECTION, 1);
        if (p) {
	  strcpy(p->name, host_name);
	  strcpy(p->socket, sock_name);
	  strcpy(p->command, getarg(0, 1));
	  p->group = group;
	  p->quality = quality;
	}
      }
    }
  }
  fclose(fp);
}

/*---------------------------------------------------------------------------*/
#ifdef CHECKFILES
static void check_files_changed()
{

  static long conftime;
  static long nexttime;
  static long progtime;
  struct stat statbuf;

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

  if (!stat(progfile, &statbuf)) {
    if (!progtime) progtime = statbuf.st_mtime;
    if (progtime != statbuf.st_mtime && statbuf.st_mtime < currtime - 60)
      exit(0);
  }
  if (!stat(conffile, &statbuf)) {
    if (!conftime) conftime = statbuf.st_mtime;
    if (conftime != statbuf.st_mtime && statbuf.st_mtime < currtime - 60)
      exit(0);
  }
}
#endif
/*---------------------------------------------------------------------------*/

int main(argc, argv)
int argc;
char **argv;
{
  static struct {
    char *name;
    int fd;
  } listeners[] = {
    {NULL, -1},
    {"*:3600", -1},
    {0, -1}
  };

  FD_SET_TYPE actread;
  FD_SET_TYPE actwrite;
  char *sp;
  char buffer[2048];
  int addrlen;
  int arg;
  int i;
  int size;
  int status;
  struct connection *cp;
  struct mbuf *bp;
  struct sockaddr *addr;
  struct timeval sel_timeout;
#if defined (__OS2__)
#define SEPARATOR "\\"
#else
#define SEPARATOR "/"
#endif

#if defined (__OS2__)
  sock_init();
#endif
  umask(000);
  time (&boottime);
  for (i = 0; i < FD_SETSIZE; i++) close(i);
  chdir(SEPARATOR);
#if !defined(__OS2__)
  setsid();
  signal(SIGPIPE, SIG_IGN);
  if (!getenv("TZ")) putenv("TZ=localtime");
#endif

  sprintf(myrev, "PP-%4.4s", strchr(REV, ':')+2);
  
  gethostname(buffer, sizeof(buffer));
  if ((sp = strchr(buffer, '.')) != NULL) *sp = 0;
  myhostname = (char *) malloc(strlen(buffer)+1);
  strcpy(myhostname, buffer);

#if defined(__OS2__)
  strcpy(convtype, "conversd");           /* for now only one conversd type supported */
#else
  sp = strrchr(argv[0],'/');
  if (sp) {
    sp++;
  } else {
    sp = argv[0];
  }
  strcpy(convtype, sp);
#endif

  strcpy(conffile, CONF_DIR);
  strcpy(progfile, BIN_DIR);
  strcpy(userfile, DATA_DIR);
  strcpy(motdfile, CONF_DIR);
  strcpy(issuefile, CONF_DIR);
  strcpy(listener, "unix:");
  strcat(listener, DATA_DIR);
  strcat(listener, "/sockets/");
  strcat(conffile, SEPARATOR);
  strcat(progfile, SEPARATOR);
  strcat(userfile, SEPARATOR);
  strcat(motdfile, SEPARATOR);
  strcat(issuefile, SEPARATOR);
  if (!strcmp(convtype, "conversd")) {
    strcat(listener, "convers");
    listeners[1].name = "*:3600";
    strcat(conffile, "convers.conf");
    strcat(progfile, "conversd");
    strcat(userfile, "convers.log");
    strcat(motdfile, "convers.motd");
    strcat(issuefile, "convers.issue");
    convcmd = "convers\n";
  }
  if (!strcmp(convtype, "suconversd")) {
    strcat(listener, "suconvers");
    listeners[1].name = "*:6811";
    strcat(conffile, "suconvers.conf");
    strcat(progfile, "suconversd");
    strcat(userfile, "suconvers.log");
    strcat(motdfile, "suconvers.motd");
    strcat(issuefile, "suconvers.issue");
    convcmd = "suconvers\n";
  }
  if (!strcmp(convtype, "kaconversd")) {
    strcat(listener, "kaconvers");
    listeners[1].name = "*:6809";
    strcat(conffile, "kaconvers.conf");
    strcat(progfile, "kaconversd");
    strcat(userfile, "kaconvers.log");
    strcat(motdfile, "kaconvers.motd");
    strcat(issuefile, "kaconvers.issue");
    convcmd = "kaconvers\n";
  }
  if (!strcmp(convtype, "lconversd")) {
    strcat(listener, "lconvers");
    listeners[1].name = "*:6810";
    strcat(conffile, "lconvers.conf");
    strcat(progfile, "lconversd");
    strcat(userfile, "lconvers.log");
    strcat(motdfile, "lconvers.motd");
    strcat(issuefile, "lconvers.issue");
    convcmd = "lconvers\n";
  }
  if (!strcmp(convtype, "wconversd")) {
    strcat(listener, "wconvers");
    listeners[1].name = "*:3610";
    strcat(conffile, "wconvers.conf");
    strcat(progfile, "wconversd");
    strcat(userfile, "wconvers.log");
    strcat(motdfile, "wconvers.motd");
    strcat(issuefile, "wconvers.issue");
    convcmd = "wconvers\n";
  }
  listeners[0].name = listener;
  time(&currtime);

  if (argc < 2) {
    read_configuration();
  } else {
    listeners[0].name = argv[1];
    listeners[1].name = 0;
  }

  for (i = 0; listeners[i].name; i++)
    if ((addr = build_sockaddr(listeners[i].name, &addrlen)) &&
	(listeners[i].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(listeners[i].fd, SOL_SOCKET, SO_REUSEADDR, (char *) &arg, sizeof(arg));
	break;
      }
      if (bind(listeners[i].fd, addr, addrlen) || listen(listeners[i].fd, SOMAXCONN)) {
#if defined(__OS2__)
	soclose(listeners[i].fd);
#else
	close(listeners[i].fd);
#endif
	listeners[i].fd = -1;
      } else {
	if (maxfd < listeners[i].fd) maxfd = listeners[i].fd;
	FD_SET(listeners[i].fd, &chkread);
      }
    }

  charset_init();

  for (; ; ) {

    free_closed_connections();
#if !defined(__OS2__)
    while (waitpid(-1, &status, WNOHANG) > 0) ;
#endif
#ifdef CHECKFILES
    check_files_changed();
#endif
    connect_permlinks();

    actread = chkread;
    actwrite = chkwrite;
    sel_timeout.tv_sec = 60;
    sel_timeout.tv_usec = 0;
#ifdef POSIX
    fflush(NULL);
#endif
    if (select(maxfd + 1, &actread, &actwrite, (fd_set *) 0, &sel_timeout) <= 0) {
#if defined(__OS2__)
      psock_errno("select:");
#endif
      FD_ZERO(&actread);
      FD_ZERO(&actwrite);
    }

    if (maxfd <0) sleep(1);

    time(&currtime);

    for (i = 0; listeners[i].name; i++)
      if (listeners[i].fd >= 0 && FD_ISSET(listeners[i].fd, &actread))
	accept_connect_request(listeners[i].fd);

    for (cp = connections; cp; cp = cp->next) {

      if (cp->fd >= 0 && FD_ISSET(cp->fd, &actread)) {
#if defined(__OS2__)
	size = recv(cp->fd, buffer, sizeof(buffer), 0);
#else
	size = read(cp->fd, buffer, sizeof(buffer));
#endif
	if (size <= 0) {
#if defined(__OS2__)
          psock_errno("recv:");
#endif
	  bye_command2(cp, "link failure");
	} else {
	  cp->received += size;
	  for (i = 0; i < size; i++)
	    switch (buffer[i]) {
	    case '\n':
 	    case '\r':
	      if (cp->icnt) {
		cp->ibuf[cp->icnt] = '\0';
		process_input(cp);
		cp->icnt = 0;
	      } else {
                appendprompt(cp, 0);
	      }
	      break;
	    default:
	      if (cp->icnt < sizeof(cp->ibuf) - 5)
		cp->ibuf[cp->icnt++] = buffer[i];
	      break;
	    }
	}
      }

      if (cp->fd >= 0 && FD_ISSET(cp->fd, &actwrite)) {
	bp = cp->obuf;
#if defined(__OS2__)
        size = send(cp->fd, bp->data, strlen(bp->data), 0);
#else
        size = write(cp->fd, bp->data, strlen(bp->data));
#endif
	if (size <= 0) {
#if defined(__OS2__)
          psock_errno("send:");
#endif
	  bye_command2(cp, "link failure");
	} else {
	  cp->xmitted += size;
	  bp->data += size;
	  if (!*bp->data) {
	    cp->obuf = bp->next;
	    free(bp);
	  }
	  if (!cp->obuf) FD_CLR(cp->fd, &chkwrite);
	}
      }
    }
  }
}
