/*
 * 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 <stdio.h>
#include <stdlib.h>

#if defined (__OS2__)
#include <string.h>
#include <utils.h>
#include "pp_os2.h"
#else
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#endif

#include <time.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include "conversd.h"
#include "user.h"
#include "host.h"
#include "convert.h"

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

#if defined(mips) || defined (_AIX)     /* no headerfiles for the whole stuff ? */
extern int strncasecmp __ARGS((char *s1, char *s2, int n));
#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));
extern void vsyslog __ARGS((int, __const char *, va_list));
#endif
#endif

#if defined(sun)                       /* brrrr - how ugly :-( */
#define PATH_MAX 128
#endif

char help_all[]="If you are in /query mode, a text written with /all at the front will\nbe displayed as a normal convers text written to all users on this channel.\n";

char help_away[]="/away sets an away-text for the other users. called with no additional\narguments, you are marked as being back again.\n";

char help_beep[]="The beep command toggles a warning bell (^G) being sent before every message\ncoming from another user. This command really is a subset of the /prompt command,\nsee there.\n";

char help_char[]="With this command, you can tell the convers deamon which type of umlauts you\nprefer to use. The Syntax is /char [intype [outtype]]. For example, if you work\non an Atari st you could say: \"/char atari atari\". If you use a pc and like to\nwrite your own umlauts in TeX style, \"/char tex pc\" may work. Play a bit around\nwith this feature ! Special Thanks to Thommy, <dl9sau@zedat.fu-berlin.de> (Inter-\nnet mail) <dl9sau@db0sao.ampr.org> (AmPR-Net mail) who wrote this nice feature.\nSuggestions to this feature should be redirected to him :-)\n";

char help_dest[]="All Pingpong hosts in the network being connected to each other are listed.\nThe numbers shown in the list are response times in seconds.\n";

char help_excl[]="The exclude command is the opposite of the /msg command. You can send messages\nto all users on the channel except the one given as the first parameter. In-\nternally these messages are sent as private messages to the users, so links\nare flooded a bit more :-)\n";

char help_help[]="The help command may be invoked with an additional parameter. I believe you\nhacked in \"help help\" to see this, right ? :-). better try out help options\nthat are followed by an additional command. BTW: the preceding \"/\" must not be\ngiven for the command's name in help command.\n";

char help_invi[]="An invitation tho the named user is sent. The invite is passed through all\nconvers nodes in the net. If the user is on another channel, he will be able\nto join your private channel. If he is online on a unix machine, he will\nreceive the invitation message via standard broadcast by conversd. In the\nlatter case he will be unable to join a private channel directly.\n";

char help_join[]="Join another channel. Unlike other other conversd implementations Ping-Pong-\nconversd supports multiple channel joins the same time. Thus the old channel\nis not left. You may do this by invoking \"/leave\".\n";

char help_last[]="The last sign on and sign of of the specified user is diplayed. You must at\nknow his name then the data base will be searched to look up the login/out\ntime. Special thanks to Wolfgang Tremmel for his basic work on this item\nHis email adresses are <dh2paf@db0sao.ampr.org> and <tremmel@ira.uka.de>.\n"; 

char help_leav[]="Invoking this command you will leave the actual channel or the specified\nchannel. If the given channel is your last existence in the convers channel,\nyou'll quit the whole program.\n";

char help_link[]="The current link state is displayed. In the standard version, host name,\nlink state, link round trip times, neighbour revision codes and state time\nfollowed by time of next try and count of connect tries (on disconnected or\nconnecting links) are shown, connected lines print out their queue length\nand total transmit statistics.\nIf you are sysop, you may also set up or remove links at runtime.\nAdding a link might happen like this:\n\"/l dc6iq-u 2 4 unix:/tcp/.sockets/netcmd connect tcp dc6iq 3600\"\nIn this command a host called dc6iq-u will be entered into group 2 by\nquality 4. The link will be established by using WAMPES.\n";

char help_list[]="All channels, their topics (if set), flags (if some) and users are displayed.\n";

char help_me[]="If you want to display an action you take, you might use this command. E.g.\nentering the line \"/me yawns\" all connected users in this channel will see\na message like: \"*** dc6iq yawns\"\n";

char help_mode[]="The mode command is one of the most complicated ones. It is invoked with\n/mode <channel> <+|-><t|i|s|m|p|l|o<name>>. Flags may be a set of the following:\n\tt - channel topic may be set by channel operators only\n\ti - channel is invisible for users not on it\n\ts - channel is secrect, only its number not displayed\n\tm - channel is moderated, only channel-operators may talk\n\tp - channel is private, invitation needed to join here\n\tl - channel is local, no text forwarding to other nodes (NYI)\n\to<name> - make <name> a channel operator\n\nThe plus indicates an option to be set, a dash resets the option. A combination\nof + and - is allowed so the following command will work: \"/mode 69 -s+todc6iq\".\nChannel 69 is no longer secret, but topics may be set only by channel operators.\nIn addition the user dc6iq becomes a channel operator.\n"; 

char help_msg[]="Send a text to one special user or a joined channel. If the message should be\nredirected to a channel, The command must be invoked in this syntax:\n\"/msg #<channel> <text>\". The adressee (if user) can indicate the private\ncharacter of the message due to additional asterisks in his received lines.\na mesage written by dc6iq to dc1ik looks like this: \"/m dc1ik This is a Test\"\nwill be received by dc1ik like this: \"<*dc6iq*>: This is a test\".\n";

char help_noti[]="You are notified if one person out of the notification list appears on any\nchannel in this convers. E.g. \"/notify + dc1ik\" adds the user dc1ik to the\nnotification list, \"/notify - db4ut\" removes him from the list. The list is\ninitially treated as if a plus \"+\"-sign was given, multiple lists may be\nspecified. The following command would be legal and would result in adding\ndc1ik, db4ut and dg3kcr to the list, while removing dc6iq and dh2paf:\n\"/notify dh2paf + dc1ik db4ut - dc6iq dh2paf +dg3kcr\"\nRemoving non-members to the list is ignored.\n";

char help_oper[]="Invoking this command, a random number from 0..99999 will be displyed.\nTake each digit, multiply with the corresponding digit in the secret number\nat the configuration file, sum it and enter the number by invoking \"/oper <n>\".\n";

char help_pers[]="A brief description can be set up, accessible to all users via the \"/who\"-\ncommand. A typical Message looks like this: \"/pers Fred, Buechig, JN49fb\".\n";

char help_prom[]="The prompt command takes up to four arguments in one concatenated string.\nThus \"/prompt abcd\" will set up \"a\" for the query prompt, \"b\" for the\nstandard prompt. \"d\" is the character to remove the prompt, and \"c\" is\ndisplayed whenever a text is sent to you. Normally you make \"c\" a control-G\n(beep char) and \"d\" a backspace or delete character (control-h or 0x7f).\n";

char help_quer[]="The username given as argument will be the only recipient for all further\ntexts. They are send as private messages (just like /m). With no optional\nargument, the further texts go into the whole channel again.\n";

char help_quit[]="Passing this command to conversd, you will retire from the famous Ping-Pong\nconversation. I hope you enjoyed this stuff :-)\n";

char help_topi[]="A brief description of the channel topic can be set. Other users can look\nit up with the \"/who quick\" or \"/list\" commands. If the channel number is\nomitted, the topic will be set to the current channel, else to the desired one.\nif no #channel is given, the topic will appear on the current channel, if speci-\nfied, the topic on that channel will be set (only if youre logged on to that\nchannel). To clear a topic, just try to set a topic like \"@\" :-)\nAn empty topic command diplays the current's channel topic.\n";

char help_upti[]="The uptime command tells us how long this conversd is up and running.\n";

char help_verb[]="The verbose flag is toggled. You will be flooded with information if you\nenable this feature.\n";

char help_vers[]="Print out version information to this software.\n";

char help_who[]="The who command supports up to four switches. An \"a\" will diplay all users\nand their Away texts, if set. A \"l\" generates a _long_ list containing personal\ntexts, away texts and Queue informations. A \"q\" gives a brief list. An asterisk\n\"*\" displays the users on your current channel in normal mode.\nIf you want to get informations about specific users, you can use the \"who u userlist\" variant.";

char help_whoi[]="This is a question to conversd. \"/whois dc6iq\" will show all available\ninformations about dc6iq, i.e. joined channels, personal description, away message\nand times.";

char help_widt[]="Tell conversd about your screen width. Then formatted messages will use the\nentire screen. It is set to 80 by default.\n";

extern struct cmdtable cmdtable[];
static int breakins;

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

void all_command(cp)
struct connection *cp;
{
  struct channel *ch;
  char *s;

  s = getarg(0, 1);

  for (ch = channels; ch; ch = ch->next) {
    if (ch->chan == cp->channel) break;
  }
  if (cp->channelop || !(ch->flags & M_CHAN_M)) {
    if (*s) {
      send_msg_to_channel(cp->name, cp->channel, s);
    }
  } else {
    appendstring(cp, "*** This is a moderated channel. Only channel operators may write.\n");
  }
  appendprompt(cp, 0);
}

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

void away_command(cp)
struct connection *cp;
{
  char *s;
  char buffer[2048];

  s = getarg(0, 1);
  if (*s) {
    sprintf(buffer, "*** (%s) You are marked as being away.\n", ts2(currtime));
    appendstring(cp, buffer);
    strcpy(cp->away, s);
    cp->atime = currtime;
    cp->mtime = currtime;
    send_awaymsg(cp->name, myhostname, cp->channel, currtime, s);
  } else {
    if (*cp->away) {
      sprintf(buffer, "*** (%s) You are no longer marked as being away.\n", ts2(currtime));
      appendstring(cp, buffer);
      *cp->away = '\0';
      cp->atime = currtime;
      cp->mtime = currtime;
      send_awaymsg(cp->name, myhostname, cp->channel, currtime, s);
    } else {
      sprintf(buffer, "*** (%s) Actually you were marked as being here :-)\n", ts2(currtime));
      appendstring(cp, buffer);
    }
  }
  appendprompt(cp, 0);
}

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

void beep_command(cp)
struct connection *cp;
{
  if (cp->prompt[3] == '\0')
    cp->prompt[3] = '\007';
  else
    cp->prompt[3] = '\0';
  if (cp->prompt[3] == '\007')
    appendstring(cp, "*** Beep mode enabled\n");
  else
    appendstring(cp, "*** Beep mode disabled\n");
  appendprompt(cp, 0);
}

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

void bye_command2(cp, reason)
struct connection *cp;
char *reason;
{
  struct connection *p;
  int users_left;
  struct clist *cl = NULLCLIST;
  int channel;

  switch (cp->type) {
  case CT_UNKNOWN:
    cp->type = CT_CLOSED;
    break;
  case CT_USER:
    cp->type = CT_CLOSED;
    cl = cp->chan_list;
    while (cl) {
      users_left = count_user(cl->channel);
      if (!users_left) destroy_channel(cl->channel);
      clear_locks();
      send_user_change_msg(cp->name, cp->host, cl->channel, -1, reason, currtime);
      cp->chan_list = cl->next;
      free (cl);
      cl = cp->chan_list;
    }
    break;
  case CT_HOST:
    cp->type = CT_CLOSED;
    update_permlinks(cp->name, NULLCONNECTION, 0);
    for (p = connections; p; p = p->next)
      if (p->via == cp) {
	p->type = CT_CLOSED;
	clear_locks();
	channel = p->channel;
	send_user_change_msg(p->name, p->host, p->channel, -1, reason, currtime);
	users_left = count_user(channel);
	if (!users_left) destroy_channel(channel);
      }
    break;
  case CT_CLOSED:
    break;
  }
}

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

void bye_command(cp)
struct connection *cp;
{
  char *s;
  char buffer[2048];

  s = getarg(0, 1);
  if (!s || !*s) {
    bye_command2(cp, "/quit");
  } else {
    sprintf(buffer, "\"%s\"", s);
    bye_command2(cp, buffer);
  }
}

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

void channel_command(cp)
struct connection *cp;
{
  char *s;
  char buffer[2048];
  char chan[64];
  int newchannel;
  struct channel *ch;
  struct connection *p;
  int printit, first;
  struct clist *cl;

  s = getarg(0, 0);

  if (!s || !*s) {
    sprintf(buffer, "*** (%s) You are talking to channel %d. There are %d users.\n",
	    ts2(currtime), cp->channel, count_user(cp->channel));
    appendstring(cp, buffer);
    for (ch = channels; ch; ch = ch->next) {
      if (ch->chan == cp->channel) break;
    }
    if (*ch->topic) {
      sprintf(buffer, "*** current Topic is: %s\n", ch->topic);
      appendstring(cp, buffer);
    }
    sprintf(buffer, "*** Also attached:");
    printit = 0;
    first = 1;
    for (cl = cp->chan_list; cl; cl = cl->next) {
      if (cl->channel != cp->channel) {
	if (first) {
	  first = 0;
	} else {
	  strcat(buffer, ",");
	}
	printit = count_user(cl->channel);
	if (printit == 1) {
	  sprintf(chan, " channel %d (alone)", cl->channel);
	} else {
	  sprintf(chan, " channel %d (%d users)", cl->channel, printit);
	  printit = 1;
	}
	strcat(buffer, chan);
      }
    }
    strcat(buffer, ".\n");
    if (printit) appendstring(cp, buffer);
    appendprompt(cp, 0);
    return;
  }
  newchannel = atoi(s);
  if (newchannel < 0 || newchannel > MAXCHANNEL) {
    sprintf(buffer, "*** (%s) Channel numbers must be in the range 0..%d.\n", ts2(currtime), MAXCHANNEL);
    appendstring(cp, buffer);
    appendprompt(cp, 0);
    return;
  }
  if (newchannel == cp->channel) {
    sprintf(buffer, "*** (%s) Channel %d is already default.\n", ts2(currtime), cp->channel);
    appendstring(cp, buffer);
    appendprompt(cp, 0);
    return;
  }
  for (ch = channels; ch; ch = ch->next) {
    if (ch->chan == newchannel) break;
  }
  for (cl = cp->chan_list; cl; cl = cl->next) {
    if (cl->channel == newchannel) break;
  }
  if (!((cp->invitation_channel == newchannel) || !(ch) || !(ch->flags & M_CHAN_P)) && !cl) {
    sprintf(buffer, "*** (%s) You need an invitation to join the private channel %d.\n", ts2(currtime), newchannel);
    appendstring(cp, buffer);
    appendprompt(cp, 0);
    return;
  }
  if (!cl) {
    cp->locked = 1;
    send_user_change_msg(cp->name, cp->host, -1, newchannel, cp->pers, cp->time);
    cl = (struct clist *)malloc(sizeof(struct clist));
    cl->time = currtime;
    cp->mtime = currtime;
    if (!ch) {
      cl->channelop = 1;
    } else {
      cl->channelop = 0;
    }
    cl->channel = newchannel;
    cl->next = cp->chan_list;
    cp->chan_list = cl;
    cp->locked = 0;
  }
  cp->channel = newchannel;
  sprintf(buffer, "*** (%s) You are now talking to channel %d. ", ts2(currtime), cp->channel);
  appendstring(cp, buffer);
  if (ch) {
    if (count_user(cp->channel) == 1) {
      sprintf(buffer, "You're alone.\n");
    } else {
      sprintf(buffer, "There are %d users.\n", count_user(cp->channel));
    }
    appendstring(cp, buffer);
    if (*ch->topic) {
      sprintf(buffer, "*** current Topic is: %s\n", ch->topic);
      appendstring(cp, buffer);
    }
    cp->channelop = cl->channelop;
  } else {
    sprintf(buffer, "You're alone.\n");
    appendstring(cp, buffer);
    for (p = connections; p; p = p->next) {
      if (p->type == CT_HOST) {
        sprintf(buffer, "/\377\200MODE %d +o%s\n", cp->channel, cp->name);
        appendstring(p, buffer);
      }
    }
    cp->channelop = 1;
    ch = (struct channel *) malloc(sizeof(struct channel));
    ch->next = channels;
    ch->chan = cp->channel;
    ch->time = 0;
    ch->topic[0] = '\0';
    ch->name[0] = '\0';
    ch->flags = 0;
    channels = ch;
  }
  appendprompt(cp, 0);
}

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

void charset_command(cp)
struct connection *cp;
{

  char *s1, *s2;
  char buffer[2048];
  int charset_in, charset_out;

  s1 = getarg(0, 0);

  if (!*s1) {
    sprintf(buffer, "*** Charset in/out is %s/%s.\n",
    			get_charset_by_ind(cp->charset_in),
    			get_charset_by_ind(cp->charset_out));
    appendstring(cp, buffer);
    appendprompt(cp, 0);
    return;
  }

  charset_in = get_charset_by_name(s1);

  s2 = getarg(0, 0);
  if (*s2) charset_out = get_charset_by_name(s2);
  else charset_out = charset_in;

  if (charset_in < 0 || charset_out < 0) {
    sprintf(buffer, "Unknown charset: '%s'.  You may use one of them:\n%s***\n",
    			(charset_in < 0) ? s1 : s2, list_charsets());
    appendstring(cp, buffer);
    appendprompt(cp, 0);
    return;
  }

  cp->charset_in = charset_in;
  cp->charset_out = charset_out;

  sprintf(buffer, "*** Charset in/out set to %s/%s.\n",
    			get_charset_by_ind(cp->charset_in),
    			get_charset_by_ind(cp->charset_out));
  appendstring(cp, buffer);
  appendprompt(cp, 0);
}

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

void cstat_command(cp)
struct connection *cp;
{
  links_command(cp);
  hosts_command(cp);
}

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

void help_command(cp)
struct connection *cp;
{
  char *s1;
  struct cmdtable *cmdp;

  s1 = getarg(0, 0);

  if (*s1 == '\0') {
    appendstring(cp, "Commands may be abbreviated. Commands are:\n");

#ifdef VERBOSEHELP
    appendstring(cp, "/?                      Print help information\n");
#endif
    appendstring(cp, "/Away [text]            Mark user as being away\n");
#ifdef VERBOSEHELP
    appendstring(cp, "/ACtion text            write action to channel\n");
#endif
    appendstring(cp, "/ALl text               send text to all users\n");
    appendstring(cp, "/Beep                   toggle Beep-Mode\n");
#ifdef VERBOSEHELP
    appendstring(cp, "/Bell                   toggle Beep-Mode\n");
    appendstring(cp, "/BYe                    Terminate the convers session\n");
#endif
    appendstring(cp, "/Channel n              Switch to channel n\n");
    appendstring(cp, "/CHARset [in [out]]     Change terminal emulation (default ansi)\n");
    appendstring(cp, "/Destinations           List reachable ping-pong hosts\n");
#ifdef VERBOSEHELP
    appendstring(cp, "/Exit                   Terminate the convers session\n");
    appendstring(cp, "/EXClude user text      Send Text to all on channel except user\n");
#endif
    appendstring(cp, "/Help [command]         Print help information\n");
#ifdef VERBOSEHELP
    appendstring(cp, "/HOsts                  List reachable ping-pong hosts\n");
#endif
    appendstring(cp, "/Invite user            Invite user to join your channel\n");
    appendstring(cp, "/IMsg user text         Send Text to all on channel except user\n");
#ifdef VERBOSEHELP
    appendstring(cp, "/IWrite user text       Send Text to all on channel except user\n");
    appendstring(cp, "/Join                   Switch to channel n\n");
#endif
    appendstring(cp, "/Links [args]           List or setup links (see \"help links\")\n");
    appendstring(cp, "/LISt                   List all channels and topics\n");
    appendstring(cp, "/LAst user              List last appearances of user\n");
    appendstring(cp, "/LEave [channel]        Leaves specified or default channel\n");
    appendstring(cp, "/Msg user|#channel text Send message to a user or joined channel\n");
    appendstring(cp, "/ME text                write action to channel\n");
    appendstring(cp, "/MOde #channel options  Set channel options\n");
    appendstring(cp, "/NOtify [calls]         Send notice if one of calls signs on\n");
#ifdef VERBOSEHELP
    appendstring(cp, "/NOTE [text]            set personal description\n");
#endif
    appendstring(cp, "/Operator [number]      become operator\n");
    appendstring(cp, "/Personal [text]        set personal description\n");
    appendstring(cp, "/PRompt abcd            prompts a=query b=std c=ctrl-g d=ctrl-h\n");
    appendstring(cp, "/Quit                   Terminate the convers session\n");
    appendstring(cp, "/QUEry [user]           Start/End a private conversation\n");
#ifdef VERBOSEHELP
    appendstring(cp, "/Send user text         Send a private message to user\n");
#endif
    appendstring(cp, "/Topic [#chan] [text]   set channel topic. Text=@ removes topic\n");
#ifdef VERBOSEHELP
    appendstring(cp, "/Users [*|A|L|Q]        List users and their channel numbers\n");
#endif
    appendstring(cp, "/UPtime                 how long is this conversd up ?\n");
    appendstring(cp, "/Verbose                toggles verbose Flag\n");
    appendstring(cp, "/VERSion                show version information\n");
    appendstring(cp, "/Who [*|A|L|Q|U users]  List users and their channel numbers\n");
    appendstring(cp, "/WIdth [value]          set/show terminal width\n");
#ifdef VERBOSEHELP
    appendstring(cp, "/WRite user text        Send a private message to user\n");
#endif
  } else {
    for (cmdp = cmdtable; cmdp->name; cmdp++) {
      if ((cmdp->states & (1 << cp->type)) && !strncasecmp(cmdp->name, s1, strlen(s1))) {
	break;
      }
    }
    if (cmdp->name) {
      if (cmdp->help) {
	appendstring(cp, cmdp->help);
      } else {
	appendstring(cp, "Help on this command not yet implemented...\nWrite it - or try another one :-)\n");
      }
    } else {
      appendstring(cp, "No such command...\n");
    }
  }
  appendprompt(cp, 1);
}

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

void hosts_command(cp)
struct connection *cp;
{
  char buffer[2048], tmp[64];
  struct destination *d;
  int i = 1;
  char *dest;
  
  dest = getargcs(0, 0);

  if (*dest == '\0') {
    for (d = destinations; d; d = d->next) {
      if (d->rtt) {
        sprintf(buffer, "%-9.9s (%8.8s) %3s", d->name, d->rev, ts3(d->rtt,tmp));
        appendstring(cp, buffer);
        if ((i == 3) || !(d->next)) {
	  appendstring(cp, "\n");
	  i = 0;
        } else {
	  appendstring(cp, "   ");
        }
        i++;
      }
    }
    if ((i != 1)) {
      appendstring(cp, "\n");
    }
  } else {
    for (d = destinations; d; d = d->next) {
      if (!strcmp(d->name, dest)) {
	clear_locks();
	sprintf(buffer, "*** route: %s -> %s -> %s (%ld)\n", myhostname, d->link->name, dest, d->rtt);
        appendstring(cp, buffer);
	if (strcmp(dest, d->link->name)) {
	  sprintf(buffer, "/\377\200ROUT %s %s 99\n", dest, cp->name);
	  appendstring(d->link->connection, buffer);
	}
	break;
      }
    }
    if (d == NULLDESTINATION) {
      sprintf(buffer, "*** no route to %s\n", dest);
      appendstring(cp, buffer);
    }
  }
  appendprompt(cp, 1);
}

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

void imsg_command(cp)
struct connection *cp;
{
  char *toname, *text;
  struct connection *p, *q;

  toname = getarg(0, 0);
  text = getarg(0, 1);
  if (!*text) {
    appendprompt(cp, 0);
    return;
  }
  for (p = connections; p; p = p->next) {
    if (p->type == CT_USER && p->channel == cp->channel && strcmp(p->name, toname) != 0) {
      send_msg_to_user(cp->name, p->name, text);
      q = p->next;
      while (q) {
        if (!strcmp(p->name, q->name)) {
	  q->locked = 1;
        }
        q = q->next;
      }
    } 
    if (p->via) p->via->locked = 0;
  }
  appendprompt(cp, 0);
}

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

void invite_command(cp)
struct connection *cp;
{
  char *toname;

  toname = getarg(0, 0);
  if (*toname) send_invite_msg(cp->name, toname, cp->channel);
  appendprompt(cp, 0);
}

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

void last_command(cp)
struct connection *cp;
{
  char *toname, *p;
  char buffer[128];
  char buffer2[128];
  FILE *f;
  int found = 0;
  
  toname = getarg(0, 0);
  if (!*toname) {
    appendstring(cp, "*** need username\n");
    appendprompt(cp, 0);
    return;
  }
  
  f = fopen(userfile,"r+");
  if (f == NULL) {
    appendstring(cp, "*** can't read userfile\n");
    appendprompt(cp, 0);
    return;
  }
  
  while (!feof(f)) {
    fgets(buffer2, 512, f);
    if (!strncmp(buffer2,toname,strlen(toname))) {
      found++;
      for (p = buffer2; *p != ' '; p++) ;
      *p++ = '\0';
      for (; *p == ' '; p++) ;
      if (p[strlen(p)-2] == '+') {
	p[strlen(p)-3] = '\0';
	sprintf(buffer, "%s last sign on: %s\n", buffer2, p);
      } else {
	p[strlen(p)-3] = '\0';
	sprintf(buffer, "%s last sign off: %s\n", buffer2, p);
      }
      appendstring(cp, buffer);
    }
  }
  if (!found) {
    sprintf(buffer, "*** %s was never logged on\n", toname);
    appendstring(cp, buffer);
  }
  fclose(f);
  appendprompt(cp, 1);
}

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

void leave_command(cp)
struct connection *cp;
{
  int chan;
  int users_left;
  struct clist *cl, *cl2;
  char *arg;
  char buffer[2048];

  arg = getarg(0, 0);
  if (*arg) {
    chan = atoi(arg);
  } else {
    chan = cp->channel;
  }
  if ((chan == cp->channel) && (cp->chan_list->next == NULLCLIST)) {
    bye_command(cp);
    return;
  }
  cl2 = cp->chan_list;
  if (cl2->channel == chan) {
    cp->chan_list = cl2->next;
    free(cl2);
    users_left = count_user(chan);
    if (!users_left) destroy_channel(chan);
    cp->locked = 1;
    send_user_change_msg(cp->name, cp->host, chan, -1, "/leave", cp->time);
    cp->locked = 0;
    if (chan == cp->channel) {
      cp->channel = cp->chan_list->channel;
      cp->channelop = cp->chan_list->channelop;
      sprintf(buffer, "*** (%s) Default channel is now %d.\n", ts2(currtime), cp->channel);
      appendstring(cp, buffer);
      appendprompt(cp, 0);
    } else {
      sprintf(buffer, "*** (%s) Left channel %d.\n", ts2(currtime), chan);
      appendstring(cp, buffer);
      appendprompt(cp, 0);
    }
    return;
  }
  cl = cp->chan_list;
  while (cl) {
    if (cl->channel == chan) {
      cl2->next = cl->next;
      free (cl);
      cl = cl2;
      users_left = count_user(chan);
      if (!users_left) destroy_channel(chan);
      cp->locked = 1;
      send_user_change_msg(cp->name, cp->host, chan, -1, "@", cp->time);
      cp->locked = 0;
      if (chan == cp->channel) {
	cp->channel = cp->chan_list->channel;
	cp->channelop = cp->chan_list->channelop;
	sprintf(buffer, "*** (%s) Default channel is now %d.\n", ts2(currtime), cp->channel);
	appendstring(cp, buffer);
	appendprompt(cp, 0);
	return;
      } else {
	sprintf(buffer, "*** (%s) Left channel %d.\n", ts2(currtime), chan);
	appendstring(cp, buffer);
	appendprompt(cp, 0);
	return;
      }
    } else {
      cl2 = cl;
    }
    cl = cl->next;
  }
  sprintf(buffer, "*** (%s) You were not on channel %d.\n", ts2(currtime), chan);
  appendstring(cp, buffer);
  appendprompt(cp, 0);
}

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

void links_command(cp)
struct connection *cp;
{
  char buffer[2048], tmp[64], tmp2[64], tmp3[64], tmp4[64];
  char *hostname, *sock_name, *cmd;
  int quality, group;
  int pl;
  struct permlink *pp, *p;

  hostname = getargcs(0, 0);
  if (hostname && *hostname) {
    if (!cp->operator) {
      appendstring(cp, "You must be an operator to set up new links\n");
    } else {
      if (strcmp(hostname, "-")) {
	for (pl = 0; pl < NR_PERMLINKS; pl++) {
	  pp = permarray[pl];
	  if (pp && !strcmp(pp->name, hostname)) {
	    if (pp->connection->type == CT_HOST) {
	      bye_command2(pp->connection, "link killed");
	    }
	    permarray[pl] = NULLPERMLINK;
	    free(pp);
	    break;
	  }
	}
	p = update_permlinks(hostname, NULLCONNECTION, 0);
	if (p) {
	  group = atoi(getarg(0, 0));
	  quality = atoi(getarg(0, 0));
	  sock_name = getarg(0, 0);
	  cmd = getarg(0, 1);
	  strcpy(p->name, hostname);
	  strcpy(p->socket, sock_name);
	  strcpy(p->command, cmd);
	  p->group = group;
	  p->quality = quality;
	  p->permanent = 1;
	  permarray[pl] = p;
	} else {
	  appendstring(cp, "Link table full !\n");
	}
      } else {
	hostname = getargcs(0, 0);
	for (pl = 0; pl < NR_PERMLINKS; pl++) {
	  pp = permarray[pl];
	  if (pp && !strcmp(pp->name, hostname)) {
	    if (pp->connection->type == CT_HOST) {
	      bye_command2(pp->connection, "link killed");
	    }
	    permarray[pl] = NULLPERMLINK;
	    free(pp);
	    break;
	  }
	}
      }
    }
  } else {
    appendstring(cp, "Host      State        Quality Revision  Since NextTry Tries Queue    RX    TX\n");
    for (pl = 0; pl < NR_PERMLINKS; pl++) {
      pp = permarray[pl];
      if (pp) {
	sprintf(buffer, "%-9.9s ", pp->name);
	appendstring(cp, buffer);
	if (!pp->connection) {
	  strcpy(tmp, ts(pp->retrytime)),
	  sprintf(buffer, "Disconnected   ---            %s %s %5d\n",
		  ts(pp->statetime), tmp, pp->tries);
	  appendstring(cp, buffer);
	} else {
	  if (pp->connection->type == CT_HOST) {
	    if (pp->txtime || pp->rxtime) {
	      if (pp->rxtime == -1) {
		sprintf(tmp2, "  %3s  ", ts3(pp->txtime,tmp3));
	      } else {
		sprintf(tmp2, "%3s/%-3s", ts3(pp->txtime,tmp3), ts3(pp->rxtime,tmp4));
	      }
	    } else {
	      strcpy(tmp2, "  ---  ");
	    }
	    sprintf(buffer, "Connected    %s %-8.8s %s               %5d %4dK %4dK\n",
		    tmp2, pp->connection->rev, ts(pp->connection->time),
		    queuelength(pp->connection->obuf), pp->connection->received/1024,
		    pp->connection->xmitted/1024);
	    appendstring(cp, buffer);
	  } else {
	    strcpy(tmp, ts(pp->retrytime)),
	    sprintf(buffer, "Connecting     ---            %s %s %5d\n",
		    ts(pp->statetime), tmp, pp->tries);
	    appendstring(cp, buffer);
	  }
	}
      }
    }
  }
  if (cp->type == CT_USER) {
    appendprompt(cp, 1);
  }
}

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

void list_command(cp)
struct connection *cp;
{
  char buffer[2048];
  char flags[16];
  register struct channel *ch;
  int invisible;
  int secret;
  int showit;
  int isonchan;
  struct connection *p;
  struct clist *cl;
  int n;
  
  appendstring(cp, "Channel Flags  Topic\n        Users\n");
  for (ch = channels; ch; ch = ch->next) {
    flags[0] = '\0';
    isonchan = 0;
    secret = 0;
    invisible = 0;
    for (cl = cp->chan_list; cl; cl = cl->next) {
      if (cl->channel == ch->chan) {
	isonchan++;
      }
    }
    if (ch->flags & M_CHAN_S) {
      strcat(flags, "S");
      invisible++;
      secret++;
    }
    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");
      invisible++;
    }
    if (ch->flags & M_CHAN_M) {
      strcat(flags, "M");
    }
    if (ch->flags & M_CHAN_L) {
      strcat(flags, "L");
    }
    showit = 0;
    if ((ch->chan == cp->channel) || isonchan || cp->operator || !invisible) {
      if (!ch->flags && (ch->topic[0] == '\0')) {
	sprintf(buffer, "%7d", ch->chan);
      } else {
	sprintf(buffer, "%7d %-6.6s %s\n       ", ch->chan, flags, ch->topic);
      }
      showit++;
    } else {
      if (secret) {
	sprintf(buffer, "------- %-6.6s %s\n       ", flags, ch->topic);
        showit++;
      }
    }
    n = 9;
    if (showit) {
      for (p = connections; p; p = p->next) {
        if (n > cp->width - 12) {
          n = 9;
	  strcat(buffer, "\n       ");
        }
	if (p->type == CT_USER) {
	  if (p->via) {
	    if (p->channel == ch->chan) {
	      strcat(buffer, " ");
	      strcat(buffer, p->name);
	      strcat(buffer, "(");
	      n += 2 + strlen(p->name);
	      if (p->operator) {
		strcat(buffer, "!");
		n++;
	      } else if (p->channelop) {
		strcat(buffer, "@");
		n++;
	      }
	      if (*p->away) {
		strcat(buffer, "G");
		n++;
	      }
	      if (buffer[strlen(buffer)-1] == '(') {
		buffer[strlen(buffer)-1] = '\0';
		n--;
	      } else {
		strcat(buffer, ")");
		n++;
	      }
	    }
	  } else {
	    for (cl = p->chan_list; cl; cl = cl->next) {
	      if (cl->channel == ch->chan) {
	        n += 2 + strlen(p->name);
		strcat(buffer, " ");
		strcat(buffer, p->name);
		strcat(buffer, "(");
	        if (p->operator) {
		  strcat(buffer, "!");
		  n++;
		} else if (cl->channelop) {
		  strcat(buffer, "@");
		  n++;
		}
		if (*p->away) {
		  strcat(buffer, "G");
		  n++;
		}
		if (buffer[strlen(buffer)-1] == '(') {
		  buffer[strlen(buffer)-1] = '\0';
		  n--;
		} else {
		  strcat(buffer, ")");
		  n++;
		}
	      }
	    }
	  }
	}
      }
      strcat(buffer, "\n");
      appendstring(cp, buffer);
    }
  }
  appendprompt(cp, 1);
}

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

void me_command(cp)
struct connection *cp;
{
  char *text;
  char buffer[2048];

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

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

void msg_command(cp)
struct connection *cp;
{
  char *toname, *text;
  char buffer[2048];
  struct connection *p;
  int chan;
  struct channel *ch;
  struct clist *cl;

  toname = getarg(0, 0);
  text = getarg(0, 1);
  if (!*text) {
    appendprompt(cp, 0);
    return;
  }
  if (toname[0] == '#') {
    chan = atoi(toname+1);
    for (cl = cp->chan_list; cl; cl = cl->next) {
      if (cl->channel == chan) break;
    }
    if (!cl) {
      sprintf(buffer, "*** (%s) You have not joined channel %d.\n", ts2(currtime), chan);
      appendstring(cp, buffer);
    } else {
      for (ch = channels; ch; ch = ch->next) {
	if (ch->chan == cp->channel) break;
      }
      if (cl->channelop || !(ch->flags & M_CHAN_M)) {
	send_msg_to_channel(cp->name, cl->channel, text);
      } else {
	appendstring(cp, "*** This is a moderated channel. Only channel operators may write.\n");
      }
    }
  } else {
    for (p = connections; p; p = p->next)
      if (p->type == CT_USER && !strcmp(p->name, toname)) break;
    if (!p) {
      sprintf(buffer, "*** (%s) No such user: %s.\n", ts2(currtime), toname);
      appendstring(cp, buffer);
    } else {
      if (*p->away) {
	sprintf(buffer, "*** (%s) %s is away: %s\n", ts2(currtime), toname, p->away);
	appendstring(cp, buffer);
      }
      send_msg_to_user(cp->name, toname, text);
    }
  }
  appendprompt(cp, 0);
}

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

void mode_command(cp)
struct connection *cp;
{
  char *arg, *c;
  int remove = 0;
  int channel;
  struct connection *p;
  register struct channel *ch;
  int oldflags = 0;
  struct clist *cl;
  int op = 0;

  channel = atoi(getarg(0, 0));
  arg = getarg(0, 1);
  for (cl = cp->chan_list; cl; cl = cl->next) {
    if ((cl->channel == channel) && (cl->channelop)) {
      op++;
      break;
    }
  }
  if (op || cp->operator || (cp->type == CT_HOST)) {
    for (ch = channels; ch; ch = ch->next) {
      if (channel == ch->chan) break;
    }
    if (!ch || channel == 0) {
      if (channel == 0) {
        appendstring(cp, "*** no modes on channel 0 !\n");
      } else {
        appendstring(cp, "*** non existing channel !\n");
      }
    } else {
      oldflags = ch->flags;
      while (*arg) {
        switch (*arg) {
        case '+': {
	  remove = 0;
	  arg++;
	  break;
        }
        case '-': {
	  remove = 1;
	  arg++;
	  break;
        }
        case 'S':
        case 's': {
	  if (remove && ch) {
	    ch->flags -= (ch->flags & M_CHAN_S);
	  } else {
	    ch->flags |= M_CHAN_S;
	  }
	  arg++;
	  break;
        }
        case 'P':
        case 'p': {
	  if (remove && ch) {
	    ch->flags -= (ch->flags & M_CHAN_P);
	  } else {
	    ch->flags |= M_CHAN_P;
	  }
	  arg++;
	  break;
        }
        case 'T':
        case 't': {
	  if (remove && ch) {
	    ch->flags -= (ch->flags & M_CHAN_T);
	  } else {
	    ch->flags |= M_CHAN_T;
	  }
	  arg++;
	  break;
        }
        case 'I':
        case 'i': {
	  if (remove && ch) {
	    ch->flags -= (ch->flags & M_CHAN_I);
	    } else {
	      ch->flags |= M_CHAN_I;
	    }
	  arg++;
	  break;
        }
        case 'L':
        case 'l': {
	  if (remove && ch) {
	    ch->flags -= (ch->flags & M_CHAN_L);
	  } else {
	    ch->flags |= M_CHAN_L;
	  }
	  arg++;
	  break;
        }
        case 'M':
        case 'm': {
	  if (remove && ch) {
	    ch->flags -= (ch->flags & M_CHAN_M);
	  } else {
	    ch->flags |= M_CHAN_M;
	  }
	  arg++;
	  break;
        }
        case 'O':
        case 'o': {
	  arg++;
	  while (*arg) {
	    while (*arg == ' ') arg++;
	    c = arg;
	    while (*c != ' ') {
	      if (*c != '\0') {
	        c++;
	      } else break;
	    }
	    if (*c != '\0') {
	      *c++ = '\0';
	    }
	    for (p = connections; p; p = p->next) {
	      if ((channel == -1) && (cp->type == CT_HOST)) {
	        if ((!strcmp(p->name, arg)) && (p->type == CT_USER)) {
		  p->operator = 1;
		  send_opermsg(arg, p->host, cp->name, -1);
	        }
	      } else {
	        if (!strcmp(p->name, arg)) {
		  if (p->channel == channel) {
		    p->channelop = 1;
		  }
		  for (cl = p->chan_list; cl; cl = cl->next) {
		    if (cl->channel == p->channel) cl->channelop = 1;
		  }
		  send_opermsg(arg, p->host, cp->name, p->channel);
	        }
	      }
	    }
	    arg = c;
	  }
	  break;
        }
        default: {
	  arg++;
	  break;
        }
        }
        if (ch && (ch->flags != oldflags)) {
	  if (cp->type == CT_HOST) {
	    cp->locked = 1;
	  }
	  send_mode(ch);
        }
      }
    }
  } else {
    appendstring(cp, "*** You are not an operator !\n");
  }
  if (cp->type == CT_USER) {
    appendprompt(cp, 0);
  }
}

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

void name_command(cp)
struct connection *cp;
{
  char buffer[2048];
  char usernotefile[PATH_MAX];
  int unf;
  int newchannel;
  FILE *fd;
  struct channel *ch;
  struct clist *cl;
  int cnt;
  
  strcpy(cp->name, getarg(0, 0));
  if (!*cp->name) {
    appendprompt(cp, 0);
    return;
  }

  cp->type = CT_USER;
  strcpy(cp->host, myhostname);
  sprintf(buffer, "%s @ %s PingPong-Release %4.4s - Type /HELP for help.\n", convtype, myhostname, strchr(REV, ':')+2);
  appendstring(cp, buffer);
  if (!access(motdfile, R_OK)) {
    fd = fopen(motdfile, "r");
    buffer[0] = '*';
    buffer[1] = ' ';
    while (!feof(fd)) {
      fgets(buffer + 2, 2045, fd);
      if (!feof(fd)) {
        appendstring(cp, buffer);
      }
    }
    fclose(fd);
  }
  newchannel = atoi(getarg(0, 0));
  if (newchannel < 0 || newchannel > MAXCHANNEL) {
    sprintf(buffer, "*** (%s) Channel numbers must be in the range 0..%d.\n", ts2(currtime), MAXCHANNEL);
    appendstring(cp, buffer);
    newchannel = 0;
  }
  cp->channel = newchannel;
  for (ch = channels; ch; ch = ch->next) {
    if (ch->chan == cp->channel) break;
  }
  if (ch && (ch->flags & M_CHAN_P)) {
    sprintf(buffer, "*** (%s) You need an invitation to join channel %d.\n", ts2(currtime), newchannel);
    appendstring(cp, buffer);
    cp->channel = 0;
    newchannel = 0;
    for (ch = channels; ch; ch = ch->next) {
      if (ch->chan == cp->channel) break;
    }
  }
  if (ch) {
    cnt = count_user(newchannel);
    sprintf(buffer, "*** (%s) You are now talking to channel %d. ", ts2(currtime), newchannel);
    appendstring(cp, buffer);
    if (!cnt) {
      sprintf(buffer, "You're alone.\n");
    } else {
      sprintf(buffer, "There are %d users.\n", cnt+1);
    }
    appendstring(cp, buffer);
    if (*ch->topic) {
      sprintf(buffer, "*** current Topic is: %s\n", ch->topic);
      appendstring(cp, buffer);
    }
    cp->channelop = 0;
  } else {
    sprintf(buffer, "*** You created a new channel %d.\n", cp->channel);
    appendstring(cp, buffer);
    cp->channelop = 1;
    ch = (struct channel *) malloc(sizeof(struct channel));
    ch->next = channels;
    ch->chan = cp->channel;
    ch->topic[0] = '\0';
    ch->time = 0;
    ch->name[0] = '\0';
    ch->flags = 0;
    channels = ch;
  }
  cl = (struct clist *) malloc(sizeof(struct clist));
  cl->next = NULLCLIST;
  cl->channelop = cp->channelop;
  cl->channel = cp->channel;
  cl->time = currtime;
  cp->mtime = currtime;
  cp->chan_list = cl;
  strcpy(cp->pers, "@");

  strcpy(usernotefile, DATA_DIR);
#if defined(__OS2__)
  strcat(usernotefile, "\\personals\\");
#else
  strcat(usernotefile, "/personals/");
#endif
  strcat(usernotefile, cp->name);
  if (!access(usernotefile,R_OK)) {
    unf = open(usernotefile, O_RDONLY);
    cnt = read(unf, buffer, 512);
    close(unf);
    if (cnt != 0 ) {
      strncpy(cp->pers, buffer, cnt);
      sprintf(buffer, "*** Personal data set from file: %s\n", cp->pers);
    } else {
      sprintf(buffer, "*** Failed to read personal data file\n");
    }
    appendstring(cp, buffer);
  }

  send_user_change_msg(cp->name, cp->host, -1, cp->channel, cp->pers, currtime);
  if (cp->channelop) {
    clear_locks();
    send_opermsg(cp->name, cp->host, "conversd", cp->channel);
  }
}

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

void notify_command(cp)
struct connection *cp;
{
  char *p, *q, *toname;
  struct connection *pc;
  char buffer[512], t[512];
  int action;

  toname = getarg(0, 0);
  strcpy(t, toname);
  toname = t;

  if (*toname == '\0') {
    sprintf(buffer, "*** (%s) You are notified if one of the following users sign on/off:\n",
            ts2(currtime));
    strcat(buffer, cp->notify);
    appendstring(cp, buffer);
    appendstring(cp, "\n");
  }

  action = 0;
  while (*toname) {
    while ((*toname == '+') || (*toname == '-')) {
      while (*toname == '+') {
        action = 0;
        toname++;
        if (*toname == '\0') {
	  toname = getarg(0, 0);
	  strcpy(t, toname);
	  toname = t;
	  if (*toname == '\0') {
	    break;
	  }
        }
      }
      while (*toname == '-') {
        action = 1;
        toname++;
        if (*toname == '\0') {
	  toname = getarg(0, 0);
	  strcpy(t, toname);
	  toname = t;
	  if (*toname == '\0') {
	    break;
	  }
        }
      }
    }
    strcat(toname, " ");
    p = cp->notify;
    p = strstr(p, toname);
    while (p) {
      p--;
      if (*p != ' ') {
	p++;
	p++;
      } else {
	q = ++p;
	while (*q != ' ') q++;
	while (*q == ' ') q++;
	while (*q != '\0') *p++ = *q++;
	*p = *q;
	p = cp->notify;
      }
      p = strstr(p, toname);
    }
    if (action == 0) {
      if (cp->notify[0] == '\0') {
	strcpy(cp->notify, " ");
      }
      strcat(cp->notify, toname);
      for (pc = connections; pc; pc = pc->next) {
	sprintf(buffer, "%s ", pc->name);
	if ((pc->type == CT_USER) && !strncmp(t, buffer, strlen(buffer))) {
	  sprintf(buffer, "*** %s is online.\n", pc->name);
	  appendstring(cp, buffer);
	  break;
	}
      }
    }
    toname = getarg(0, 0);
    strcpy(t, toname);
    toname = t;
  }
  appendprompt(cp, 0);
}

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

void operator_command(cp)
struct connection *cp;
{
  int number, answer;
  char buffer[2048];

  answer = atoi(getarg(0, 0));
  if (!answer) {
    number = currtime % 100000;
    /* TODO: This is no good number :-) */
    cp->expected = 0;
    sprintf(buffer, "(%d) %d>", breakins, number);
    answer = secretnumber;
    while (answer) {
      cp->expected += (number % 10) * (answer % 10);
      number /= 10;
      answer /= 10;
    }
  } else {
    if (answer == cp->expected) {
#ifdef LOGGING
      openlog(convtype, LOG_PID, LOG_MAIL);
      syslog(LOG_WARNING, "Operator success by %s", cp->name);
      closelog();
#endif
      cp->operator = 1;
      send_opermsg(cp->name, myhostname, cp->name, -1);
      sprintf(buffer, "*** (%s) You are now an operator.\n", ts2(currtime));
    } else {
#ifdef LOGGING
      openlog(convtype, LOG_PID, LOG_MAIL);
      syslog(LOG_WARNING, "Operator breakin by %s", cp->name);
      closelog();
#endif
      sprintf(buffer, "*** (%s) operator breakin attempt notified.\n", ts2(currtime));
      breakins++;
    }
  }
  appendstring(cp, buffer);
  if (answer) {
    appendprompt(cp, 0);
  }
#ifdef POSIX
  fflush(NULL);
#endif
}

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

void personal_command(cp)
struct connection *cp;
{

  char *s;
  char buffer[2048];
  char usernotefile[PATH_MAX];
  int unf;

  strcpy(usernotefile, DATA_DIR);
  strcat(usernotefile, "/personals/");
  strcat(usernotefile, cp->name);

  s = getarg(0, 1);
  if (!*s || (!strcmp(s, "@"))) {
    strcpy(s, "@");
    sprintf(buffer, "*** (%s) Personal text deleted.\n", ts2(currtime));
    unlink(usernotefile);
  } else {
    sprintf(buffer, "*** (%s) Personal text set.\n", ts2(currtime));
    if ((unf = creat(usernotefile, 0644)) != -1 ) {
       write(unf, s, strlen(s) );
       close(unf);
    }
  }
  cp->mtime = currtime;
  appendstring(cp, buffer);
  strcpy(cp->pers, s);
  send_persmsg(cp->name, myhostname, cp->channel, s, currtime);
  appendprompt(cp, 0);
}

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

void prompt_command(cp)
struct connection *cp;
{
  char *args;

  args = getarg(0, 1);
  cp->prompt[2] = '\0';
  cp->prompt[3] = '\0';
  cp->prompt[0] = '\0';
  if ((cp->prompt[1] = args[0]) != '\0')
    if ((cp->prompt[2] = args[1]) != '\0')
      if ((cp->prompt[3] = args[2]) != '\0')
	cp->prompt[0] = args[3];
  if (cp->prompt[3] == '\b' && cp->prompt[0] == '\0') {
    cp->prompt[0] = '\b';
    cp->prompt[3] = '\0';
  }
  if (*args)
    appendstring(cp, "*** Prompting mode enabled\n");
  else
    appendstring(cp, "*** Prompting mode disabled\n");
  appendprompt(cp, 0);
}

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

void query_command(cp)
struct connection *cp;
{

  char *toname;
  char buffer[2048];
  struct connection *p;

  toname = getarg(0, 0);

  if (*toname) {
    for (p = connections; p; p = p->next)
      if (p->type == CT_USER && !strcmp(p->name, toname)) break;
    if (!p) {
      sprintf(buffer, "*** (%s) No such user: %s.\n", ts2(currtime), toname);
      appendstring(cp, buffer);
    }
    else {
      strcpy(cp->query, toname);
      sprintf(buffer, "*** (%s) Starting private conversation with %s.\n", ts2(currtime), cp->query);
      appendstring(cp, buffer);
    }
  }
  else if (cp->query[0] != '\0') {
    sprintf(buffer, "*** (%s) Ending private conversation with %s.\n", ts2(currtime), cp->query);
    appendstring(cp, buffer);
    cp->query[0] = '\0';
  }
  appendprompt(cp, 0);
}

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

void topic_command(cp)
struct connection *cp;
{
  struct channel *ch;
  struct connection *p;
  register char *topic;
  char buffer[2048];
  long time;
  int channel;

  channel = cp->channel;
  topic = getarg(0, 1);
  if (*topic == '#') {
    topic++;
    while (*topic == ' ') topic++;
    channel = atoi(topic);
    while ((*topic != ' ') && (*topic != '\0')) topic++;
    while (*topic == ' ') topic++;
  }
  for (p = connections; p; p = p->next)
    if (p->type == CT_USER && !strcmp(p->name, cp->name) && (p->channel == channel)) break;
  time = currtime;
  for (ch = channels; ch; ch = ch->next) {
    if (ch->chan == channel) break;
  }
  if (ch) {
    if (ch->time > time) time = ch->time+1L;
    if (*topic) {
      if (p && (((ch->flags & M_CHAN_T) == 0) || cp->operator || p->channelop)) {
	if (*topic == '@') {
	  *topic = '\0';
	  sprintf(buffer, "*** (%s) Channel topic on channel %d removed.\n", ts2(currtime), channel);
	} else {
	  sprintf(buffer, "*** (%s) Channel topic set on channel %d.\n", ts2(currtime), channel);
	}
        send_topic(cp->name, cp->host, time, channel, topic);
      } else {
	sprintf(buffer, "*** (%s) You are not an operator.\n", ts2(currtime));
      }
    } else {
      if (*ch->topic) {
	sprintf(buffer, "*** (%s) Current channel topic on channel %d is\n%s\n", ts2(currtime), channel, ch->topic);
      } else {
	sprintf(buffer, "*** (%s) No current channel topic on channel %d.\n", ts2(currtime), channel);
      }
    }
  } else {
    sprintf(buffer, "*** (%s) Channel channel %d non existent.\n", ts2(currtime), channel);
  }
  appendstring(cp, buffer);
  appendprompt(cp, 0);
}

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

void uptime_command(cp)
struct connection *cp;
{
  char buffer[2048];
#if defined(linux)
  long uptime;
  FILE *fd;
#endif

  sprintf(buffer, "*** %s@%s is up for %s\n", convtype, myhostname, ts4(currtime - boottime));
#if defined(linux)
  if (!access("/proc/uptime", R_OK)) {
    appendstring(cp, buffer);
    fd = fopen("/proc/uptime", "r");
    fscanf(fd, "%ld", &uptime);
    fclose(fd);
    sprintf(buffer, "*** Linux is up for %s\n", ts4(uptime));
  }
#endif
  appendstring(cp, buffer);
  appendprompt(cp, 0);
}

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

void verbose_command(cp)
struct connection *cp;
{
  cp->verbose = 1-cp->verbose;
  if (cp->verbose)
    appendstring(cp, "*** Verbose mode enabled\n");
  else
    appendstring(cp, "*** Verbose mode disabled\n");
  appendprompt(cp, 0);
}

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

void version_command(cp)
struct connection *cp;
{
  char buffer[2048];
  
  sprintf(buffer, "*** conversd PingPong-Release %4.4s\n", strchr(REV, ':')+2);
  appendstring(cp, buffer);
  appendstring(cp, "  This conversd implementation was originally written by Dieter Deyke\n");
  appendstring(cp, "  <deyke@mdddhd.fc.hp.com>. Now I am maintaining this derived source tree\n");
  appendstring(cp, "  Report bugs to me, Fred Baumgarten <dc6iq@insu1.etec.uni-karlsruhe.de>.\n");
  appendstring(cp, "  AmPR-Net address is <dc6iq@db0sao.ampr.org>. Have fun - 73, Fred\n");
  if (*getarg(0, 0)) {
    appendstring(cp, "Compiler switches:\n");
#ifdef READLINE
    appendstring(cp, "-DREADLINE  ");
#endif
#ifdef LOGGING
    appendstring(cp, "-DLOGGING  ");
#endif
#ifdef POSIX
    appendstring(cp, "-DPOSIX  ");
#endif
    appendstring(cp, "\n");
  }
  appendprompt(cp, 0);
}

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

void who_command(cp)
struct connection *cp;
{
  char buffer[2048];
  char tmp[20];
  int flags;
  int thischan = 0;
  int full = 0;
  int quick = 0;
  int aways = 0;
  int user = 0;
  char channelstr[16];
  char *options;
  struct connection *p;
  struct channel *ch;
  struct clist *cl;
  int showit;
  int opstat;
  int isop;

  options = getarg(0, 0);
  switch (*options) {
  case '*':
    thischan = 1;
    break;
  case 'a':
    aways = 1;
    break;
  case 'l':
    full = 1;
    break;
  case 'q':
    quick = 1;
    break;
  case 'u':
    user = 1;
    full = 1;
    options++;
    if (*options == '\0') {
      options = getarg(0, 1);
    }
    break;
  }

  if (quick) {
    list_command(cp);
    return;
  }

  appendstring(cp, "User      Host      Via       Channel  ");
  if (thischan) {
    if (full) {
      appendstring(cp, " Idle Queue RX      TX\n");
    } else {
      appendstring(cp, " Idle\n");
    }
  } else {
    if (full) {
      appendstring(cp, "Login Queue RX      TX\n");
    } else {
      appendstring(cp, "Login\n");
    }
  }
  for (ch = channels; ch; ch = ch->next) {
    flags = ch->flags;
    for (p = connections; p; p = p->next) {
      showit = 0;
      opstat = 0;
      isop = 0;
      if (p->channel == ch->chan) {
	opstat = p->channelop;
	showit = 1;
      }
      isop = cp->operator;
      for (cl = cp->chan_list; cl; cl = cl->next) {
	if (ch->chan == cl->channel) {
	  isop |= cl->channelop;
	  break;
	}
      }
      for (cl = p->chan_list; cl; cl = cl->next) {
	if (ch->chan == cl->channel) {
	  opstat = cl->channelop;
	  showit = 1;
	  break;
	}
      }
      if (thischan && (cp->channel != ch->chan)) showit = 0;
      if (showit) {
	if (!(flags & M_CHAN_I) || (isop) || (ch->chan == cp->channel)) {
	  if (!(flags & M_CHAN_S) || (isop) || (ch->chan == cp->channel)) {
	    sprintf(channelstr, "%7d", ch->chan);
	  } else {
	    strcpy(channelstr, "-------");
	  }
	  if (p->type == CT_USER) {
	    sprintf(buffer, full ?
		    "%-8.8s%c %-9.9s %-9.9s %7s %6s %5d %7d %7d" :
		    "%-8.8s%c %-9.9s %-9.9s %7s %6s",
		    p->name, p->operator ? '!' : opstat ? '@' : ' ', p->host,
		    p->via ? p->via->name : "", channelstr,
		    thischan ? ts3(currtime - p->mtime, tmp) : ts(p->time),
		    queuelength(p->obuf), p->received, p->xmitted);
	    if ((*p->pers && strcmp(p->pers, "@"))||aways) {
	      if (full) {
		int n;

		n = cp->width - 21;
		strcat(buffer, "\n          Personal: ");
		strncat(buffer, p->pers, n);
	      } else {
		strcat(buffer, " ");
		if (aways) {
		  if (*p->away) {
                    int n;

                    n = cp->width - 61;
		    strncat(buffer, p->away, n);
		    strcat(buffer, " (since ");
		    strcat(buffer, ts2(p->atime));
		    strcat(buffer, ")");
		  } else {
		    strcat(buffer, "(here)");
		  }
		} else {
                  int n;

	          if (*p->away) {
                    n = cp->width - 53;
                  } else {
                    n = cp->width - 46;
		  }
		  strncat(buffer, p->pers, n);
		}
	      }
            }
	    if (!aways) {
	      if (*p->away) {
		if (full) {
		  int n;

		  n = cp->width - 32;
		  strcat(buffer, "\n          Away: ");
		  strncat(buffer, p->away, n);
		  strcat(buffer, " (since ");
		  strcat(buffer, ts2(p->atime));
		  strcat(buffer, ")");
		} else {
		  strcat(buffer, " (AWAY)");
		}
	      } else {
		if (full) {
		  if (p->mtime) {
		    strcat(buffer, "\n          Last Activity: ");
		    strcat(buffer, ts2(p->mtime));
		  }
		}
	      }
	    }
	    strcat(buffer, "\n");
            if (!user || (strstr(options, p->name))) {
	      appendstring(cp, buffer);
            }
	  }
	}
      }
    }
  }
  appendprompt(cp, 1);
}


#if 0

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

void whois_command(cp)
struct connection *cp;
{
  char buffer[2048];
  int flags;
  int thischan = 0;
  int full = 0;
  int quick = 0;
  int aways = 0;
  char channelstr[32];
  struct connection *p;
  struct connection *last_shown;
  struct channel *ch;
  struct clist *cl;
  int showit;
  int opstat;
  char *name;

  name = getarg(0, 0);
  if (name) {
    for (ch = channels; ch; ch = ch->next) {
      flags = ch->flags;
      for (p = connections; p; p = p->next) {
	showit = 0;
	opstat = 0;
	if (p->channel == ch->chan) {
	  showit = 1;
	  opstat = p->channelop;
	}
	for (cl = p->chan_list; cl; cl = cl->next) {
	  if (ch->chan == cl->channel) {
	    showit = 1;
	    opstat = cl->channelop;
	    break;
	  }
	}
	if (thischan && (cp->channel != ch->chan)) showit = 0;
	if (showit) {
	  if (!(flags & M_CHAN_I) || (cp->operator) || (ch->chan == cl->channel)) {
	    if (!(flags & M_CHAN_S) || (cp->operator) || (ch->chan == cl->channel)) {
	      sprintf(channelstr, "on channel %7d", ch->chan);
	    } else {
	      strcpy(channelstr, "on secret channel");
	    }
	    if ((p->type == CT_USER) &&(!strcmp(p->name, ))) {
	      appendstring(cp,"User      Host      Via       Channel   Time Queue Receivd Xmitted\n"];
	      if (!last_shown) {
		sprintf(buffer, "User %s on host %s", p->name, p->host);
		appendstring(cp, buffer);
	      } else {
		appendstring(cp, ",");
	      }
	      if (opstat) {
		      p->via ? p->via->name : "", channelstr, ts(p->time),
		      queuelength(p->obuf), p->received, p->xmitted);
	      if ((*p->pers && strcmp(p->pers, "@"))||aways) {
		strcat(buffer, "\n          Personal: ");
		strcat(buffer, p->pers);
	      }
	      if (*p->away) {
		strcat(buffer, "\n          Away: ");
		strcat(buffer, p->away);
		strcat(buffer, " (since ");
		strcat(buffer, ts2(p->atime));
		strcat(buffer, ")");
	      } else {
		if (p->mtime) {
		  strcat(buffer, "\n          Last Message: ");
		  strcat(buffer, ts2(p->mtime));
		}
	      }
	      strcat(buffer, "\n");
	      appendstring(cp, buffer);
	    }
	  }
	}
      }
    }
  } else {
    appendstring(cp,"Need username...\n"];
  }
  appendprompt(cp, 1);
}

#endif

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

void width_command(cp)
struct connection *cp;
{
  int neww;
  char buffer[128];

  neww = atoi(getarg(0,0));

  if (neww == 0) {
    sprintf(buffer, "*** Current screen width is %d\n", cp->width);
    appendstring(cp, buffer);
  } else {
    cp->width = neww;
    sprintf(buffer, "*** Screen width set to %d\n", cp->width);
    appendstring(cp, buffer);
  }
  appendprompt(cp, 0);
}
