/* 
   cmds.c -- handles:
     commands from a user via dcc

   dprintf'ized, 3nov95
*/
/*
   This file is part of the eggdrop source code
   copyright (c) 1997 Robey Pointer
   and is distributed according to the GNU general public license.
   For full details, read the top of 'main.c' or the file called
   COPYING that was distributed with this code.
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "eggdrop.h"
#include "users.h"
#include "chan.h"
#include "proto.h"
#include "tclegg.h"
#include "../lush.h"

extern int serv;
extern char botuserhost[];
extern char newserver[];
extern char newserverpass[];
extern int newserverport;
extern int require_x;
extern int require_p;
extern int use_info;
extern char origbotname[];
extern int dcc_total;
extern struct dcc_t dcc[];
extern char botname[];
extern int isolate;
extern char botuser[];
extern char dccdir[];
extern struct userrec *userlist;
extern char owner[];
extern struct chanset_t *chanset;
extern int dcc_users;
extern int make_userfile;
extern int conmask;
extern char cx_file[];
extern int cx_line;
extern Tcl_Interp *interp;
extern tcl_timer_t *timer,*utimer;

void cmd_who(idx,par)
int idx; char *par;
{
  int i;
  if (par[0]) {
    if (dcc[idx].u.chat->channel<0) {
      dprintf(idx,"You have chat turned off.\n");
      return;
    }
    putlog(LOG_CMDS,"*","#%s# who %s",dcc[idx].nick,par);
    if (strcasecmp(par,origbotname)==0) tell_who(idx,dcc[idx].u.chat->channel);
    else {
      i=nextbot(par); if (i<0) {
	dprintf(idx,"That bot isn't connected.\n");
/*	tandout("who %d:%s@%s %s %d\n",dcc[idx].sock,dcc[idx].nick,
		origbotname,par,dcc[idx].u.chat->channel);  */
      }
      else tprintf(dcc[i].sock,"who %d:%s@%s %s %d\n",dcc[idx].sock,
		   dcc[idx].nick,origbotname,par,dcc[idx].u.chat->channel);
    }
  }
  else {
    putlog(LOG_CMDS,"*","#%s# who",dcc[idx].nick);
    if (dcc[idx].u.chat->channel<0) tell_who(idx,0);
    else tell_who(idx,dcc[idx].u.chat->channel);
  }
}

void cmd_botinfo(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# botinfo",dcc[idx].nick);
  tandout("info? %d:%s@%s\n",dcc[idx].sock,dcc[idx].nick,origbotname);
}

void cmd_whom(idx,par)
int idx; char *par;
{
  if (dcc[idx].u.chat->channel<0) {
    dprintf(idx,"You have chat turned off.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# whom %s",dcc[idx].nick,par);
  if (!par[0]) answer_local_whom(idx,dcc[idx].u.chat->channel);
  else if (par[0]=='*') answer_local_whom(idx,-1);
  else {
    int chan=atoi(par);
    if ((par[0]<'0') || (par[0]>'9')) {
      chan=get_assoc(par);
      if (chan<0) {
	dprintf(idx,"No such channel.\n");
	return;
      }
    }
    if ((chan<0) || (chan>99999)) {
      dprintf(idx,"Channel # out of range: must be 0-99999\n");
      return;
    }
    answer_local_whom(idx,chan);
  }
}

void cmd_me(idx,par)
int idx; char *par;
{
  int i;
  if (dcc[idx].u.chat->channel<0) {
    dprintf(idx,"You have chat turned off.\n");
    return;
  }
  if (!par[0]) {
    dprintf(idx,"Usage: me <action>\n");
    return;
  }
  if (dcc[idx].u.chat->away!=NULL) not_away(idx);
  for (i=0; i<dcc_total; i++) {
    if ((dcc[i].type==DCC_CHAT) && 
	(dcc[i].u.chat->channel==dcc[idx].u.chat->channel) &&
	((i!=idx) || (dcc[i].u.chat->status&STAT_ECHO)))
      dprintf(i,"* %s %s\n",dcc[idx].nick,par);
    if (!isolate) if (dcc[i].type==DCC_BOT)
      tprintf(dcc[i].sock,"actchan %s@%s %d %s\n",dcc[idx].nick,origbotname,
	      dcc[idx].u.chat->channel,par);
  }
  check_tcl_act(dcc[idx].nick,dcc[idx].u.chat->channel,par);
}

void cmd_motd(idx,par)
int idx; char *par;
{
  int i;
  if (par[0]) {
    putlog(LOG_CMDS,"*","#%s# motd %s",dcc[idx].nick,par);
    if (strcasecmp(par,origbotname)==0) show_motd(idx);
    else {
      i=nextbot(par); if (i<0) 
	dprintf(idx,"That bot isn't connected.\n");
      else tprintf(dcc[i].sock,"motd %d:%s@%s %s\n",dcc[idx].sock,
		   dcc[idx].nick,origbotname,par);
    }
  }
  else {
    putlog(LOG_CMDS,"*","#%s# motd",dcc[idx].nick);
    show_motd(idx);
  }
}

#ifndef NO_IRC
void cmd_files(idx,par)
int idx; char *par;
{
  if (dccdir[0]==0) dprintf(idx,"There is no file transfer area.\n");
  else if (too_many_filers()) {
    dprintf(idx,"The maximum of %d people are in the file area right now.\n",
	    dcc_users);
    dprintf(idx,"Please try again later.\n");
  }
  else {
    if ((!(dcc[idx].u.chat->status&STAT_MASTER)) && (require_x) &&
	(!(dcc[idx].u.chat->status&STAT_XFER))) {
      dprintf(idx,"You don't have access to the file area.\n");
    }
    else if ((dcc[idx].u.chat->status&STAT_PARTY) &&
	     !(dcc[idx].u.chat->status&STAT_XFER)) {
      dprintf(idx,"You don't have access to the file area.\n");
    }
    else {
      putlog(LOG_CMDS,"*","#%s# files",dcc[idx].nick);
      dprintf(idx,"Entering file system...\n");
      if (dcc[idx].u.chat->channel>=0) {
	chanout2(dcc[idx].u.chat->channel,"%s is away, file area.\n",
		 dcc[idx].nick);
	tandout("away %s %s File area.\n",origbotname,dcc[idx].nick);
      }
      set_files(idx); dcc[idx].type=DCC_FILES;
      dcc[idx].u.file->chat->status|=STAT_CHAT;
      if (!welcome_to_files(idx)) {
	struct chat_info *ci=dcc[idx].u.file->chat;
	nfree(dcc[idx].u.file); dcc[idx].u.chat=ci;
	dcc[idx].type=DCC_CHAT;
	dprintf(idx,"The file system is broken.\n");
	putlog(LOG_FILES,"*","File system broken.");
        tandout("unaway %s %s\n",origbotname,dcc[idx].nick);
        chanout2(dcc[idx].u.chat->channel,"%s is back from the file area.\n",
                     dcc[idx].nick);
      }
    }
  }
}
#endif

void cmd_note(idx,par)
int idx; char *par;
{
  char handle[512],*p; int echo;
  split(handle,par); if (!handle[0]) {
    dprintf(idx,"Format: note <to-whom> <message>\n");
    return;
  }
  /* could be file system user */
  echo=(dcc[idx].type==DCC_CHAT) ?
    (dcc[idx].u.chat->status&STAT_ECHO) :
    (dcc[idx].u.file->chat->status&STAT_ECHO);
  p=strchr(handle,','); while (p!=NULL) {
    *p=0; p++;
    add_note(handle,dcc[idx].nick,par,idx,echo);
    strcpy(handle,p); p=strchr(handle,',');
  }
  add_note(handle,dcc[idx].nick,par,idx,echo);
}

void cmd_away(idx,par)
int idx; char *par;
{
  if (strlen(par)>60) par[60]=0;
  set_away(idx,par);
}

void cmd_newpass(idx,par)
int idx; char *par;
{
  char new[512];
  nsplit(new,par);
  if (strlen(new)>9) new[9]=0;
  if (strlen(new)<4) {
    dprintf(idx,"Please use at least 4 characters.\n");
    return;
  }
  change_pass_by_handle(dcc[idx].nick,new);
  putlog(LOG_CMDS,"*","#%s# newpass...",dcc[idx].nick);
  dprintf(idx,"Changed password to '%s'\n",new);
}

void cmd_bots(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# bots",dcc[idx].nick);
  tell_bots(idx);
}

void cmd_bottree(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# bottree",dcc[idx].nick);
  tell_bottree(idx);
}

void cmd_help(idx,par)
int idx; char *par;
{
  int atr=get_attr_handle(dcc[idx].nick);
  if (par[0]) {
    putlog(LOG_CMDS,"*","#%s# help %s",dcc[idx].nick,par);
    tellhelp(idx,par,(atr&USER_OP),(atr&USER_MASTER));
  }
  else {
    putlog(LOG_CMDS,"*","#%s# help",dcc[idx].nick);
    if (!(atr & (USER_OP|USER_MASTER)))
      tellhelp(idx,"help.party",0,0);
    else
      tellhelp(idx,"help",(atr&USER_OP),(atr&USER_MASTER));
  }
}

#ifndef NO_IRC
void cmd_act(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# act %s",dcc[idx].nick,par);
  mprintf(serv,"PRIVMSG %s :\001ACTION %s\001\n",dcc[idx].u.chat->con_chan,
	  par);
}
#endif

#ifndef NO_IRC
void cmd_msg(idx,par)
int idx; char *par;
{
  char nick[512];
  split(nick,par); if (!nick[0]) {
    dprintf(idx,"Usage: msg <nick> <message>\n");
  }
  else {
    putlog(LOG_CMDS,"*","#%s# msg %s %s",dcc[idx].nick,nick,par);
    mprintf(serv,"PRIVMSG %s :%s\n",nick,par);
  }
}
#endif

#ifndef NO_IRC
void cmd_say(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# say %s",dcc[idx].nick,par);
  mprintf(serv,"PRIVMSG %s :%s\n",dcc[idx].u.chat->con_chan,par);
}
#endif

#ifndef NO_IRC
void cmd_kickban(idx,par)
int idx; char *par;
{
  struct chanset_t *chan;
  if (!par[0]) {
    dprintf(idx,"Usage: kickban <nick> [reason]\n");
    return;
  }
  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) {
    dprintf(idx,"Your console channel is invalid.\n");
    return;
  }
  if (!me_op(chan)) {
    dprintf(idx,"I can't help you now because I'm not a channel op.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# kickban %s",dcc[idx].nick,par);
  user_kickban(idx,par);
}
#endif

#ifndef NO_IRC
void cmd_op(idx,par)
int idx; char *par;
{
  struct chanset_t *chan; char nick[512];
  nsplit(nick,par);
  if (par[0]) {
    chan=findchan(par);
    if (chan==NULL) {
      dprintf(idx,"I'm not on channel %s.\n",par);
      return;
    }
  }
  else {
    chan=findchan(dcc[idx].u.chat->con_chan);
    if (chan==NULL) {
      dprintf(idx,"Your console channel is invalid.\n");
      return;
    }
  }
  if (!me_op(chan)) {
    dprintf(idx,"I can't help you now because I'm not a channel op.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# op %s %s",dcc[idx].nick,nick,par);
  give_op(nick,chan,idx);
}
#endif

#ifndef NO_IRC
void cmd_deop(idx,par)
int idx; char *par;
{
  struct chanset_t *chan; char nick[512];
  nsplit(nick,par);
  if (par[0]) {
    chan=findchan(par);
    if (chan==NULL) {
      dprintf(idx,"I'm not on channel %s.\n",par);
      return;
    }
  }
  else {
    chan=findchan(dcc[idx].u.chat->con_chan);
    if (chan==NULL) {
      dprintf(idx,"Your console channel is invalid.\n");
      return;
    }
  }
  if (!me_op(chan)) {
    dprintf(idx,"I can't help you now because I'm not a channel op.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# deop %s %s",dcc[idx].nick,nick,par);
  give_deop(nick,chan,idx);
}
#endif

#ifndef NO_IRC
void cmd_kick(idx,par)
int idx; char *par;
{
  struct chanset_t *chan;
  if (!par[0]) {
    dprintf(idx,"Usage: kick <nick> [reason]\n");
    return;
  }
  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) {
    dprintf(idx,"Your console channel is invalid.\n");
    return;
  }
  if (!me_op(chan)) {
    dprintf(idx,"I can't help you now because I'm not a channel op.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# kick %s",dcc[idx].nick,par);
  user_kick(idx,par);
}
#endif

#ifndef NO_IRC
void cmd_invite(idx,par)
int idx; char *par;
{
  struct chanset_t *chan;
  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) {
    dprintf(idx,"Your console channel is invalid.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# invite %s",dcc[idx].nick,par);
  if (!me_op(chan) && (chan->channel.mode&CHANINV)) {
    dprintf(idx,"I'm not chop, so I can't invite anyone.\n");
    return;
  }
  if (ischanmember(chan->name,par) && !is_split(chan->name,par)) {
    dprintf(idx,"%s is already on the channel!\n",par);
    return;
  }
  mprintf(serv,"INVITE %s %s\n",par,chan->name);
  dprintf(idx,"Inviting %s to %s.\n",par,chan->name);
}
#endif

void cmd_resetbans(idx,par)
int idx; char *par;
{
  struct chanset_t *chan;
  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) {
    dprintf(idx,"Your console channel is invalid.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# resetbans",dcc[idx].nick);
  dprintf(idx,"Resetting bans on %s...\n",chan->name); resetbans(chan);
}

void cmd_pls_ban(idx,par)
int idx; char *par;
{
  char who[512],note[512],s[UHOSTLEN]; struct chanset_t *chan;
  char chname[512];
  chname[0]=0;
  if (!par[0]) {
    dprintf(idx,"Usage: +ban <hostmask> [channel] [reason]\n");
    return;
  }
  nsplit(who,par); rmspace(who);
   if (par[0]=='#') {
     nsplit(chname,par); rmspace(chname);
  }
  rmspace(par);
  if (!par[0]) strcpy(note,"request");
  else { strncpy(note,par,65); note[65]=0; }
  /* fix missing ! or @ BEFORE checking against myself */
  if (strchr(who,'!')==NULL) strcat(s,"!*@*");   /* lame nick ban */
  if (strchr(who,'@')==NULL) strcat(s,"@*");     /* brain-dead? */
  sprintf(s,"%s!%s",botname,botuserhost);
  if (wild_match(who,s)) {
    dprintf(idx,"Duh...  I think I'll ban myself today, Marge!\n");
    putlog(LOG_CMDS,"*","#%s# attempted +ban %s",dcc[idx].nick,who);
    return;
  }
  if (strlen(who)>70) who[70]=0;
  /* irc can't understand bans longer than that */
  if (chname[0]!=0) {
    if ((chan=findchan(chname))==NULL) {
      dprintf (idx,"No such channel.\n");
      return;
    }
    u_addban(chan->bans,who,dcc[idx].nick,note,0L);
    putlog(LOG_CMDS,"*","#%s# +b %s %s (%s)",dcc[idx].nick,who,chname,note);
    dprintf(idx,"New %s ban: %s (%s)\n",chname,who,note);
    if (me_op(chan)) add_mode(chan,'+','b',who);
    recheck_channel(chan);
    return;
  }
  addban(who,dcc[idx].nick,note,0L);
  putlog(LOG_CMDS,"*","#%s# +ban %s (%s)",dcc[idx].nick,who,note);
  dprintf(idx,"New ban: %s (%s)\n",who,note);
  chan=chanset; while (chan!=NULL) {
    if (me_op(chan)) add_mode(chan,'+','b',who);
    recheck_channel(chan);
    chan=chan->next;
  }
}

void cmd_mns_ban(idx,par)
int idx; char *par;
{
  int i,j,k; struct chanset_t *chan; char s[UHOSTLEN],ban[512],chname[512];
  chname[0]=0;
  if (!par[0]) {
    dprintf(idx,"Usage: -ban <hostmask|ban #> [channel]\n");
    return;
   }
  nsplit(ban,par); rmspace(ban); rmspace(par);
  if (par[0]=='#') strcpy(chname,par);
  i=delban(ban);
  if (i>0) {
    putlog(LOG_CMDS,"*","#%s# -ban %s",dcc[idx].nick,ban);
    dprintf(idx,"Removed ban: %s\n",ban);
    chan=chanset; while (chan!=NULL) {
      if (me_op(chan)) add_mode(chan,'-','b',ban);
      chan=chan->next;
    }
    return;
  }
  /* channel-specific ban? */
  if (chname[0]!=0) {
    if ((chan=findchan(chname))==NULL) {
      dprintf (idx,"No such channel.\n");
      return;
   }
  } else
  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) {
    dprintf(idx,"Invalid console channel.\n"); return;
  }
  sprintf(s,"%d",i+atoi(ban));
  j=u_delban(chan->bans,s);
  if (j>0) {
    putlog(LOG_CMDS,"*","#%s# -ban %s",dcc[idx].nick,s);
    dprintf(idx,"Removed channel ban: %s\n",s);
    add_mode(chan,'-','b',s);
    return;
  } else {
   k=u_delban(chan->bans,ban);
   if (k>0) {
    putlog(LOG_CMDS,"*","#%s# -ban %s",dcc[idx].nick,ban);
    dprintf(idx,"Removed channel ban: %s\n",ban);
    add_mode(chan,'-','b',ban);
    return;
   }
  }
  /* okay, not in any ban list -- might be ban on channel */
  if (atoi(ban)>0) {
    if (kill_chanban(dcc[idx].u.chat->con_chan,idx,1-i-j,atoi(ban)))
      putlog(LOG_CMDS,"*","#%s# -ban %s",dcc[idx].nick,ban);
  }
  else if (kill_chanban_name(dcc[idx].u.chat->con_chan,idx,ban))
    putlog(LOG_CMDS,"*","#%s# -ban %s",dcc[idx].nick,ban);
}

void cmd_bans(idx,par)
int idx; char *par;
{
  if (strcasecmp(par,"all")==0) {
    putlog(LOG_CMDS,"*","#%s# bans all",dcc[idx].nick);
    tell_bans(idx,1,"");
  }
  else {
    putlog(LOG_CMDS,"*","#%s# bans %s",dcc[idx].nick,par);
    tell_bans(idx,0,par);
  }
}

#ifndef NO_IRC
void cmd_channel(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# channel %s",dcc[idx].nick,par);
  tell_verbose_chan_info(idx,par);
}
#endif

void cmd_addlog(idx,par)
int idx; char *par;
{
  dprintf(idx,"Placed entry in the log file.\n");
  putlog(LOG_MISC,"*","%s: %s",dcc[idx].nick,par);
}

#ifndef NO_IRC
void cmd_servers(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# servers",dcc[idx].nick);
  tell_servers(idx);
}
#endif

void cmd_whois(idx,par)
int idx; char *par;
{
  if (!par[0]) {
    dprintf(idx,"Usage: whois <handle>\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# whois %s",dcc[idx].nick,par);
  tell_user_ident(idx,par,dcc[idx].u.chat->status&STAT_MASTER);
}

void cmd_match(idx,par)
int idx; char *par;
{
  int start=1,limit=20; char s[512],s1[512];
  putlog(LOG_CMDS,"*","#%s# match %s",dcc[idx].nick,par);
  nsplit(s,par);
  if (atoi(par)>0) {
    split(s1,par);
    if (atoi(s1)>0) start=atoi(s1);
    limit=atoi(par);
  }
  tell_users_match(idx,s,start,limit,dcc[idx].u.chat->status&STAT_MASTER);
}

void cmd_status(idx,par)
int idx; char *par;
{
  if (strcasecmp(par,"all")==0) {
    putlog(LOG_CMDS,"*","#%s# status all",dcc[idx].nick);
    tell_verbose_status(idx,1);
    tell_mem_status_dcc(idx);
    dprintf(idx,"\n");
    tell_settings(idx);
  }
  else {
    putlog(LOG_CMDS,"*","#%s# status",dcc[idx].nick);
    tell_verbose_status(idx,1);
    tell_mem_status_dcc(idx);
  }
}

void cmd_dccstat(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# dccstat",dcc[idx].nick);
  tell_dcc(idx);
}

void cmd_pls_ignore(idx,par)
int idx; char *par;
{
  char who[512],note[66];
  if (!par[0]) {
    dprintf(idx,"Usage: +ignore <hostmask> [comment]\n");
    return;
  }
  nsplit(who,par); who[UHOSTLEN-1]=0;
  strncpy(note,par,65); note[65]=0;
  if (match_ignore(who)) {
    dprintf(idx,"That already matches an existing ignore.\n");
    return;
  }
  dprintf(idx,"Now ignoring: %s (%s)\n",who,note);
  addignore(who,dcc[idx].nick,note,0L);
  putlog(LOG_CMDS,"*","#%s# +ignore %s %s",dcc[idx].nick,who,note);
}

void cmd_mns_ignore(idx,par)
int idx; char *par;
{
  if (delignore(par)) {
    putlog(LOG_CMDS,"*","#%s# -ignore %s",dcc[idx].nick,par);
    dprintf(idx,"No longer ignoring: %s\n",par);
  }
  else dprintf(idx,"Can't find that ignore.\n");
}

void cmd_ignores(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# ignores %s",dcc[idx].nick,par);
  tell_ignores(idx,par);
}

void cmd_boot(idx,par)
int idx; char *par;
{
  int i,files=0,ok=0; char who[512];
  nsplit(who,par);
  if (strchr(who,'@')!=NULL) {
    char whonick[512];
    splitc(whonick,who,'@'); whonick[20]=0;
    if (strcasecmp(who,origbotname)==0) {
      cmd_boot(idx,whonick);
      return;
    }
#ifdef REMOTE_BOOTS
    i=nextbot(who); if (i<0) {
      dprintf(idx,"No such bot connected.\n");
      return;
    }
    tprintf(dcc[i].sock,"reject %s@%s %s@%s %s\n",dcc[idx].nick,origbotname,
	    whonick,who,par[0]?par:dcc[idx].nick);
    putlog(LOG_MISC,"*","#%s# boot %s@%s (%s)",dcc[idx].nick,whonick,who,
	   par[0]?par:dcc[idx].nick);
#else
    dprintf(idx,"Remote boots are disabled here.\n");
#endif
    return;
  }
  for (i=0; i<dcc_total; i++)
    if ((strcasecmp(dcc[i].nick,who)==0) && (!ok) &&
	((dcc[i].type==DCC_CHAT) || (dcc[i].type==DCC_FILES))) {
#ifdef OWNER
      if ((get_attr_handle(who) & USER_OWNER) &&
	  (strcasecmp(dcc[idx].nick,who)!=0)) {
	dprintf(idx,"Can't boot the bot owner.\n");
	return;
      }
#endif
      files=(dcc[i].type==DCC_FILES);
      if (files) dprintf(idx,"Booted %s from the file section.\n",dcc[i].nick);
      else dprintf(idx,"Booted %s from the bot.\n",dcc[i].nick);
      putlog(LOG_CMDS,"*","#%s# boot %s %s",dcc[idx].nick,who,par);
      do_boot(i,dcc[idx].nick,par); ok=1;
    }
  if (!ok) dprintf(idx,"Who?  No such person on the party line.\n");
}

void cmd_console(idx,par)
int idx; char *par;
{
  char nick[512],s[2],s1[512]; int dest=0,i,ok=0,pls,md;
  if (!par[0]) {
    dprintf(idx,"Your console is %s: %s (%s)\n",dcc[idx].u.chat->con_chan,
	    masktype(dcc[idx].u.chat->con_flags),
	    maskname(dcc[idx].u.chat->con_flags));
    return;
  }
  strcpy(s1,par);
  split(nick,par);
  if ((nick[0]) && (nick[0]!='#') && (nick[0]!='&') &&
      (dcc[idx].u.chat->status&STAT_MASTER)) {
    for (i=0; i<dcc_total; i++)
      if ((strcasecmp(nick,dcc[i].nick)==0) && (dcc[i].type==DCC_CHAT) &&
	  (!ok)) {
	ok=1; dest=i;
      }
    if (!ok) {
      dprintf(idx,"No such user on the party line!\n");
      return;
    }
    nick[0]=0;
  }
  else dest=idx;
  if (!nick[0]) nsplit(nick,par);

  while (nick[0]) {
    if ((nick[0]=='#') || (nick[0]=='&') || (nick[0]=='*')) {
      if ((nick[0]!='*') && (findchan(nick)==NULL)) {
	dprintf(idx,"Invalid console channel: %s\n",nick);
	return;
      }
      strncpy(dcc[dest].u.chat->con_chan,nick,80);
      dcc[dest].u.chat->con_chan[80]=0;
    }
    else {
      pls=1;
      if ((nick[0]!='+') && (nick[0]!='-')) dcc[dest].u.chat->con_flags=0;
      for (i=0; i<strlen(nick); i++) {
	if (nick[i]=='+') pls=1;
	else if (nick[i]=='-') pls=(-1);
	else {
	  s[0]=nick[i]; s[1]=0;
	  md=logmodes(s);
	  if ((dest==idx) && !(dcc[idx].u.chat->status&STAT_MASTER) && pls)
	    md&=~(LOG_MISC|LOG_CMDS|LOG_RAW|LOG_FILES|LOG_LEV1|LOG_LEV2|
		  LOG_LEV3|LOG_LEV4|LOG_LEV5|LOG_LEV6|LOG_LEV7|LOG_LEV8|
		  LOG_WALL|LOG_DEBUG);
	  if (pls==1) dcc[dest].u.chat->con_flags|=md;
	  else dcc[dest].u.chat->con_flags&=~md;
	}
      }
    }
    nsplit(nick,par);
  }
  putlog(LOG_CMDS,"*","#%s# console %s",dcc[idx].nick,s1);
  if (dest==idx) {
    dprintf(idx,"Set your console to %s: %s (%s)\n",
	    dcc[idx].u.chat->con_chan,
	    masktype(dcc[idx].u.chat->con_flags),
	    maskname(dcc[idx].u.chat->con_flags));
  }
  else {
    dprintf(idx,"Set console of %s to %s: %s (%s)\n",dcc[dest].nick,
	    dcc[dest].u.chat->con_chan,
	    masktype(dcc[dest].u.chat->con_flags),
	    maskname(dcc[dest].u.chat->con_flags));
    dprintf(dest,"%s set your console to %s: %s (%s)\n",dcc[idx].nick,
	    dcc[dest].u.chat->con_chan,
	    masktype(dcc[dest].u.chat->con_flags),
	    maskname(dcc[dest].u.chat->con_flags));
  }
}

#ifndef NO_IRC
void cmd_adduser(idx,par)
int idx; char *par;
{
  if ((par==NULL) || (!par[0])) {
    dprintf(idx,"Usage: adduser <nick>\n");
    return;
  }
  if (add_chan_user(par,idx))
    putlog(LOG_CMDS,"*","#%s# adduser %s",dcc[idx].nick,par);
}
#endif

void cmd_pls_user(idx,par)
int idx; char *par;
{
  char handle[512],host[512];
  nsplit(handle,par); nsplit(host,par);
  if (!host[0]) {
    dprintf(idx,"Format: +user <handle> <hostmask>\n");
    return;
  }
  if (strlen(handle)>9) handle[9]=0;  /* max len = 9 */
  if (is_user(handle)) {
    dprintf(idx,"Someone already exists by that name.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# +user %s %s",dcc[idx].nick,handle,host);
  userlist=adduser(userlist,handle,host,"-",0);
  dprintf(idx,"Added %s (%s) with no password or flags.\n",handle,host);
}

void cmd_pls_bot(idx,par)
int idx; char *par;
{
  char handle[512],addr[512];
  nsplit(handle,par); if (!par[0]) {
    dprintf(idx,"Format: +bot <handle> <address:port#>\n");
    return;
  }
  nsplit(addr,par);
  if (strlen(handle)>9) handle[9]=0;  /* max len = 9 */
  if (is_user(handle)) {
    dprintf(idx,"Someone already exists by that name.\n");
    return;
  }
  if (strlen(addr)>60) addr[60]=0;
  if (strchr(addr,':')==NULL) strcat(addr,":3333");
  putlog(LOG_CMDS,"*","#%s# +bot %s %s",dcc[idx].nick,handle,addr);
  userlist=adduser(userlist,handle,"none","-",USER_BOT);
  set_handle_info(userlist,handle,addr);
  dprintf(idx,"Added bot '%s' with address '%s' and no password.\n",
	  handle,addr);
  if (!add_bot_hostmask(idx,handle))
    dprintf(idx,"You'll want to add a hostmask if this bot will ever %s",
	    "be on any channels that I'm on.\n");
}

void cmd_mns_user(idx,par)
int idx; char *par;
{
#ifdef OWNER
  if (!(get_attr_handle(dcc[idx].nick) & USER_OWNER) &&
      (get_attr_handle(par) & USER_OWNER)) {
    dprintf(idx,"Can't remove the bot owner!\n");
    return;
  }
#endif
  if (deluser(par)) {
    putlog(LOG_CMDS,"*","#%s# -user %s",dcc[idx].nick,par);
    dprintf(idx,"Deleted %s.\n",par);
  }
  else dprintf(idx,"Failed.\n");
}

void cmd_chnick(idx,par)
int idx; char *par;
{
  char hand[512]; int i;
  split(hand,par); if ((!hand[0]) || (!par[0])) {
    dprintf(idx,"Format: chnick <oldnick> <newnick>\n");
    return;
  }
  if (strlen(par)>9) par[9]=0;
  for (i=0; i<strlen(par); i++)
    if ((par[i]<=32) || (par[i]>=127) || (par[i]=='@')) par[i]='?';
  if (par[0]=='*') {
    dprintf(idx,"Bizarre quantum forces prevent nicknames from starting with *\n");
    return;
  }
  if ((is_user(par)) && (strcasecmp(hand,par)!=0)) {
    dprintf(idx,"Already a user %s.\n",par);
    return;
  }
  if (strcasecmp(par,origbotname)==0) {
    dprintf(idx,"Hey!  That MY name!\n",par);
    return;
  }
#ifdef OWNER
  if ((get_attr_handle(hand) & USER_OWNER) &&
      (strcasecmp(dcc[idx].nick,hand)!=0)) {
    dprintf(idx,"Can't change the bot owner's handle.\n");
    return;
  }
#endif
  if (change_handle(hand,par)){
    notes_change(idx,hand,par);
    putlog(LOG_CMDS,"*","#%s# chnick %s %s",dcc[idx].nick,hand,par);
    dprintf(idx,"Changed.\n");
    for (i=0; i<dcc_total; i++) {
      if ((strcasecmp(dcc[i].nick,hand)==0) && (dcc[i].type!=DCC_BOT)) {
	char s[10];
	strcpy(s,dcc[i].nick); strcpy(dcc[i].nick,par);
	if ((dcc[i].type==DCC_CHAT) && (dcc[i].u.chat->channel>=0)) {
	  chanout2(dcc[i].u.chat->channel,"Nick change: %s -> %s\n",s,par);
	  tandout("part %s %s\n",origbotname,s);
	  tandout("join %s %s %d %c %s\n",origbotname,par,
		  dcc[i].u.chat->channel,geticon(i),dcc[i].host);
	}
      }
    }
  }
  else dprintf(idx,"Failed.\n");
}

void cmd_nick(idx,par)
int idx; char *par;
{
  int i; char icon;
  if (!par[0]) {
    dprintf(idx,"Format: nick <new-handle>\n");
    return;
  }
  if (strlen(par)>9) par[9]=0;
  for (i=0; i<strlen(par); i++)
    if ((par[i]<=32) || (par[i]>=127) || (par[i]=='@')) par[i]='?';
  if (par[0]=='*') {
    dprintf(idx,"Bizarre quantum forces prevent nicknames from starting with *\n");
    return;
  }
  if ((is_user(par)) && (strcasecmp(dcc[idx].nick,par)!=0)) {
    dprintf(idx,"Somebody is already using %s.\n",par);
    return;
  }
  if (strcasecmp(par,origbotname)==0) {
    dprintf(idx,"Hey!  That MY name!\n",par);
    return;
  }
  icon=geticon(idx);
  if (change_handle(dcc[idx].nick,par)) {
    notes_change(idx,dcc[idx].nick,par);
    putlog(LOG_CMDS,"*","#%s# nick %s",dcc[idx].nick,par);
    dprintf(idx,"Okay, changed.\n");
    if (dcc[idx].u.chat->channel>=0) {
      chanout2(dcc[idx].u.chat->channel,"Nick change: %s -> %s\n",
	       dcc[idx].nick,par);
      tandout("part %s %s\n",origbotname,dcc[idx].nick);
      tandout("join %s %s %d %c %s\n",origbotname,par,dcc[idx].u.chat->channel,
	      icon,dcc[idx].host);
    }
    for (i=0; i<dcc_total; i++)
      if ((idx!=i) && (strcasecmp(dcc[idx].nick,dcc[i].nick)==0))
	strcpy(dcc[i].nick,par);
    strcpy(dcc[idx].nick,par);
  }
  else dprintf(idx,"Failed.\n");
}

void cmd_pls_host(idx,par)
int idx; char *par;
{
  char handle[512],host[512];
  nsplit(handle,par); nsplit(host,par);
  if (!host[0]) {
    dprintf(idx,"Format: +host <handle> <newhostmask>\n");
    return;
  }
  if (!is_user(handle)) {
    dprintf(idx,"No such user.\n");
    return;
  }
  if (ishost_for_handle(handle,host)) {
    dprintf(idx,"That hostmask is already there.\n");
    return;
  }
#ifdef OWNER
  if ((get_attr_handle(handle) & USER_OWNER) &&
      !(get_attr_handle(dcc[idx].nick) & USER_OWNER) &&
      (strcasecmp(handle,dcc[idx].nick)!=0)) {
    dprintf(idx,"Can't add hostmasks to the bot owner.\n");
    return;
  }
#endif
  putlog(LOG_CMDS,"*","#%s# +host %s %s",dcc[idx].nick,handle,host);
  addhost_by_handle(handle,host);
  dprintf(idx,"Added '%s' to %s\n",host,handle);
}

void cmd_mns_host(idx,par)
int idx; char *par;
{
  char handle[512],host[512];
  nsplit(handle,par); nsplit(host,par);
  if (!host[0]) {
    dprintf(idx,"Format: -host <handle> <hostmask>\n");
    return;
  }
  if (!is_user(handle)) {
    dprintf(idx,"No such user.\n");
    return;
  }
#ifdef OWNER
  if ((get_attr_handle(handle) & USER_OWNER) &&
      !(get_attr_handle(dcc[idx].nick) & USER_OWNER) &&
      (strcasecmp(handle,dcc[idx].nick)!=0)) {
    dprintf(idx,"Can't remove hostmasks from the bot owner.\n");
    return;
  }
#endif
  if (delhost_by_handle(handle,host)) {
    putlog(LOG_CMDS,"*","#%s# -host %s %s",dcc[idx].nick,handle,host);
    dprintf(idx,"Removed '%s' from %s\n",host,handle);
  }
  else dprintf(idx,"Failed.\n");
}

void cmd_chpass(idx,par)
int idx; char *par;
{
  char handle[512],new[512];
  if (!par[0]) {
    dprintf(idx,"Format: chpass <handle> [password]\n");
    return;
  }
  split(handle,par); if (!handle[0]) {
    if (!is_user(par)) {
      dprintf(idx,"No such user.\n");
      return;
    }
    if (get_attr_handle(par) & USER_BOT)
     if ((get_attr_handle(par) & BOT_SHARE) && 
	(!(get_attr_handle(dcc[idx].nick) & USER_OWNER))) {
	  dprintf(idx,"Only bot owners can change a shared bot's password.\n");
	  return;
     }
#ifdef OWNER
    if ((get_attr_handle(par) & USER_OWNER) &&
	(strcasecmp(par,dcc[idx].nick)!=0)) {
      dprintf(idx,"Can't change the bot owner's password.\n");
      return;
    }
#endif
    putlog(LOG_CMDS,"*","#%s# chpass %s [nothing]",dcc[idx].nick,par);
    change_pass_by_handle(par,"-");
    dprintf(idx,"Removed password.\n");
    return;
  }
  if (!is_user(handle)) {
    dprintf(idx,"No such user.\n");
    return;
  }
  if (get_attr_handle(handle) & USER_BOT)
   if ((get_attr_handle(handle) & BOT_SHARE) &&
      (!(get_attr_handle(dcc[idx].nick) & USER_OWNER))) {
	dprintf(idx,"Only bot owners can change a shared bot's password.\n");
	return;
   }
#ifdef OWNER
  if ((get_attr_handle(handle) & USER_OWNER) &&
      (strcasecmp(handle,dcc[idx].nick)!=0)) {
    dprintf(idx,"Can't change the bot owner's password.\n");
    return;
  }
#endif
  nsplit(new,par);
  if (strlen(new)>9) new[9]=0;
  if (strlen(new)<4) {
    dprintf(idx,"Please use at least 4 characters.\n");
    return;
  }
  change_pass_by_handle(handle,new);
  putlog(LOG_CMDS,"*","#%s# chpass %s [something]",dcc[idx].nick,handle);
  dprintf(idx,"Changed password.\n");
}

void cmd_chaddr(idx,par)
int idx; char *par;
{
  char handle[512],addr[512];
  nsplit(handle,par); nsplit(addr,par);
  if (!handle[0]) {
    dprintf(idx,"Format: chaddr <botname> <host[:port#]>\n");
    return;
  }
  if (!(get_attr_handle(handle) & USER_BOT)) {
    dprintf(idx,"Useful only for tandem bots.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# chaddr %s %s",dcc[idx].nick,handle,addr);
  dprintf(idx,"Changed bot's address.\n");
  set_handle_info(userlist,handle,addr);
}

/*****/

void cmd_comment(idx,par)
int idx; char *par;
{
  char handle[512];
  split(handle,par); if (!handle[0]) {
    dprintf(idx,"Format: comment <handle> <newcomment>\n");
    return;
  }
  if (!is_user(handle)) {
    dprintf(idx,"No such user!\n");
    return;
  }
#ifdef OWNER
  if ((get_attr_handle(handle) & USER_OWNER) &&
      (strcasecmp(handle,dcc[idx].nick)!=0)) {
    dprintf(idx,"Can't change comment on the bot owner.\n");
    return;
  }
#endif
  putlog(LOG_CMDS,"*","#%s# comment %s %s",dcc[idx].nick,handle,par);
  if (strcasecmp(par,"none")==0) {
    dprintf(idx,"Okay, comment blanked.\n");
    set_handle_comment(userlist,handle,"");
    return;
  }
  dprintf(idx,"Changed comment.\n");
  set_handle_comment(userlist,handle,par);
}

void cmd_email(idx,par)
int idx; char *par;
{
  char s[161];
  if (!par[0]) {
    putlog(LOG_CMDS,"*","#%s# email",dcc[idx].nick);
    get_handle_email(dcc[idx].nick,s);
    if (s[0]) {
      dprintf(idx,"Your email address is: %s\n",s);
      dprintf(idx,"(You can remove it with '.email none')\n");
    }
    else dprintf(idx,"You have no email address set.\n");
    return;
  }
  if (strcasecmp(par,"none")==0) {
    dprintf(idx,"Removed your email address.\n");
    set_handle_email(userlist,dcc[idx].nick,"");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# email %s",dcc[idx].nick,par);
  dprintf(idx,"Your email address: %s\n",par);
  set_handle_email(userlist,dcc[idx].nick,par);
}

void cmd_chemail(idx,par)
int idx; char *par;
{
  char handle[512];
  nsplit(handle,par);
  if (!handle[0]) {
    dprintf(idx,"Usage: chemail <handle> [address]\n");
    return;
  }
  if (!is_user(handle)) {
    dprintf(idx,"No such user.\n");
    return;
  }
#ifdef OWNER
  if ((get_attr_handle(handle) & USER_OWNER) &&
      (strcasecmp(handle,dcc[idx].nick)!=0)) {
    dprintf(idx,"Can't change email address of the bot owner.\n");
    return;
  }
#endif
  if (!par[0]) {
    putlog(LOG_CMDS,"*","#%s# chemail %s",dcc[idx].nick,handle);
    dprintf(idx,"Wiped email for %s\n",handle);
    set_handle_email(userlist,handle,"");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# chemail %s %s",dcc[idx].nick,handle,par);
  dprintf(idx,"Changed email for %s to: %s\n",handle,par);
  set_handle_email(userlist,handle,par);
}

#ifndef NO_IRC
void cmd_dump(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# dump %s",dcc[idx].nick,par);
  mprintf(serv,"%s\n",par);
}
#endif

void cmd_reset(idx,par)
int idx; char *par;
{
  struct chanset_t *chan;
  if (par[0]) {
    chan=findchan(par);
    if (chan==NULL) {
      dprintf(idx,"I don't monitor channel %s\n",par);
      return;
    }
    putlog(LOG_CMDS,"*","#%s# reset %s",dcc[idx].nick,par);
    dprintf(idx,"Resetting channel info for %s...\n",par);
    reset_chan_info(chan); return;
  }
  putlog(LOG_CMDS,"*","#%s# reset all",dcc[idx].nick);
  dprintf(idx,"Resetting channel info for all channels...\n");
  chan=chanset; while (chan!=NULL) {
    reset_chan_info(chan); chan=chan->next;
  }
}

void cmd_restart(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# restart",dcc[idx].nick);
  dprintf(idx,"Restarting.\n");
  if (make_userfile) {
    putlog(LOG_MISC,"*","Uh, guess you don't need to create a new userfile.");
    make_userfile=0;
  }
  write_userfile();
  putlog(LOG_MISC,"*","Restarting ...");
  wipe_timers(interp,&utimer);
  wipe_timers(interp,&timer);
  Tcl_DeleteInterp(interp);
  init_tcl();
  rehash();
}

void cmd_rehash(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# rehash",dcc[idx].nick);
  dprintf(idx,"Rehashing.\n");
  if (make_userfile) {
    putlog(LOG_MISC,"*","Uh, guess you don't need to create a new userfile.");
    make_userfile=0;
  }
  write_userfile();
  putlog(LOG_MISC,"*","Rehashing ...");
  rehash();
}

void cmd_reload(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# reload",dcc[idx].nick);
  dprintf(idx,"Reloading user file...\n");
  reload();
}

void cmd_die(idx,par)
int idx; char *par;
{
  char s[512];
  putlog(LOG_CMDS,"*","#%s# die %s",dcc[idx].nick,par);
  if (par[0]) {
    chatout("*** BOT SHUTDOWN (%s: %s)\n",dcc[idx].nick,par);
    tandout("chat %s BOT SHUTDOWN (%s: %s)\n",origbotname,dcc[idx].nick,par);
    tprintf(serv,"QUIT :%s\n",par);
  }
  else {
    chatout("*** BOT SHUTDOWN (authorized by %s)\n",dcc[idx].nick);
    tandout("chat %s BOT SHUTDOWN (authorized by %s)\n",origbotname,
	    dcc[idx].nick);
    tprintf(serv,"QUIT :%s\n",dcc[idx].nick);
  }
  tandout("bye\n");
  write_userfile();
  sleep(3);   /* give the server time to understand */
  sprintf(s,"DIE BY %s!%s (%s)",dcc[idx].nick,dcc[idx].host,par[0]?par:
	  "request");
  fatal(s,0);
}

#ifndef NO_IRC
void cmd_jump(idx,par)
int idx; char *par;
{
  char other[512],port[512];
  if (par[0]) {
    nsplit(other,par); nsplit(port,par);
    if (!port[0]) sprintf(port,"%d",DEFAULT_PORT);
    putlog(LOG_CMDS,"*","#%s# jump %s %s %s",dcc[idx].nick,other,port,par);
    strcpy(newserver,other); newserverport=atoi(port);
    strcpy(newserverpass,par);
  }
  else putlog(LOG_CMDS,"*","#%s# jump",dcc[idx].nick);
  dprintf(idx,"Jumping servers...\n");
  tprintf(serv,"QUIT :changing servers\n");
  killsock(serv); serv=(-1);
}
#endif

void cmd_debug(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# debug",dcc[idx].nick);
  debug_mem_to_dcc(idx);
}

void cmd_info(idx,par)
int idx; char *par;
{
  char s[512];
  if (!use_info) {
    dprintf(idx,"Info storage is turned off.\n");
    return;
  }
  get_handle_info(dcc[idx].nick,s);
  if (!par[0]) {
    if (s[0]=='@') strcpy(s,&s[1]);
    if (s[0]) {
      dprintf(idx,"Info: %s\n",s);
      dprintf(idx,"Use '.info none' to remove it.\n");
    }
    else dprintf(idx,"No info has been set for you.\n");
    putlog(LOG_CMDS,"*","#%s# info",dcc[idx].nick);
    return;
  }
  if ((s[0]=='@') && (!(dcc[idx].u.chat->status&STAT_MASTER))) {
    dprintf(idx,"Your info line is locked.  Sorry.\n");
    return;
  }
  if (strcasecmp(par,"none")==0) {
    putlog(LOG_CMDS,"*","#%s# info none",dcc[idx].nick);
    par[0]=0; set_handle_info(userlist,dcc[idx].nick,par);
    dprintf(idx,"Removed your info line.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# info...",dcc[idx].nick);
  if (par[0]=='@') strcpy(par,&par[1]);
  set_handle_info(userlist,dcc[idx].nick,par);
  dprintf(idx,"Your info is now: %s\n",par);
}

void cmd_chinfo(idx,par)
int idx; char *par;
{
  char handle[512];
  if (!use_info) {
    dprintf(idx,"Info storage is turned off.\n");
    return;
  }
  split(handle,par);
  if (!handle[0]) {
    dprintf(idx,"Usage: chinfo <handle> <new-info>\n");
    return;
  }
  if (!is_user(handle)) {
    dprintf(idx,"No such user.\n");
    return;
  }
  if (get_attr_handle(handle) & USER_BOT) {
    dprintf(idx,"Useful only for users.\n");
    return;
  }
#ifdef OWNER
  if ((get_attr_handle(handle) & USER_OWNER) &&
      (strcasecmp(handle,dcc[idx].nick)!=0)) {
    dprintf(idx,"Can't change info for the bot owner.\n");
    return;
  }
#endif
  putlog(LOG_CMDS,"*","#%s# chinfo %s %s",dcc[idx].nick,handle,par);
  if (strcasecmp(par,"none")==0) par[0]=0;
  set_handle_info(userlist,handle,par);
  if (par[0]=='@')
    dprintf(idx,"New info (LOCKED) for %s: %s\n",handle,&par[1]);
  else if (par[0])
    dprintf(idx,"New info for %s: %s\n",handle,par);
  else dprintf(idx,"Wiped info for %s\n",handle);
}

void cmd_simul(idx,par)
int idx; char *par;
{
  char nick[512]; int i,ok=0;
  nsplit(nick,par);
  for (i=0; i<dcc_total; i++)
    if ((strcasecmp(nick,dcc[i].nick)==0) && (!ok) &&
	((dcc[i].type==DCC_CHAT) || (dcc[i].type==DCC_FILES))) {
      putlog(LOG_CMDS,"*","#%s# simul %s %s",dcc[idx].nick,nick,par);
      dcc_activity(dcc[i].sock,par,strlen(par)); ok=1;
    }
  if (!ok) dprintf(idx,"No such user on the party line.\n");
}

void cmd_link(idx,par)
int idx; char *par;
{
  char s[512]; int i;
  putlog(LOG_CMDS,"*","#%s# link %s",dcc[idx].nick,par);
  if (strchr(par,' ')==NULL) botlink(dcc[idx].nick,idx,par);
  else {
    split(s,par); i=nextbot(s); if (i<0) {
      dprintf(idx,"No such bot online.\n");
      return;
    }
    tprintf(dcc[i].sock,"link %d:%s@%s %s %s\n",dcc[idx].sock,dcc[idx].nick,
	    origbotname,s,par);
  }
}

void cmd_unlink(idx,par)
int idx; char *par;
{
  int i;
  putlog(LOG_CMDS,"*","#%s# unlink %s",dcc[idx].nick,par);
  i=nextbot(par); if (i<0) {
    botunlink(idx,par);
    return;
  }
  /* if we're directly connected to that bot, just do it */
  if (strcasecmp(dcc[i].nick,par)==0) botunlink(idx,par);
  else {
    tprintf(dcc[i].sock,"unlink %d:%s@%s %s %s\n",dcc[idx].sock,dcc[idx].nick,
	    origbotname,lastbot(par),par);
  }
}

void cmd_relay(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# relay %s",dcc[idx].nick,par);
  tandem_relay(idx,par);
}

void cmd_save(idx,par)
int idx; char *par;
{
  putlog(LOG_CMDS,"*","#%s# save",dcc[idx].nick);
  write_userfile();
}

void cmd_trace(idx,par)
int idx; char *par;
{
  int i;
  if (!par[0]) {
    dprintf(idx,"Usage: trace <botname>\n");
    return;
  }
  if (strcasecmp(par,origbotname)==0) {
    dprintf(idx,"That's me!  Hiya! :)\n");
    return;
  }
  i=nextbot(par); if (i<0) {
    dprintf(idx,"Unreachable bot.\n");
    return;
  }
  putlog(LOG_CMDS,"*","#%s# trace %s",dcc[idx].nick,par);
  tprintf(dcc[i].sock,"trace %d:%s@%s %s %s\n",dcc[idx].sock,dcc[idx].nick,
	  origbotname,par,origbotname);
}

#ifndef NO_IRC
void cmd_topic(idx,par)
int idx; char *par;
{
  struct chanset_t *chan;
  chan=findchan(dcc[idx].u.chat->con_chan);
  if (chan==NULL) {
    dprintf(idx,"Your console channel is invalid.\n");
    return;
  }
  if (!par[0]) {
    if (chan->channel.topic[0]==0) {
        dprintf(idx,"No topic is set for %s\n",chan->name);
        return;
    } else {
        dprintf(idx,"The topic for %s is: %s\n",chan->name,chan->channel.topic);
        return;
   }
  }
  if ((channel_optopic(chan)) && (!me_op(chan))) {
    dprintf(idx,"I'm not a channel op and the channel is +t.\n");
    return;
  }
  mprintf(serv,"TOPIC %s :%s\n",chan->name,par);
  dprintf(idx,"Changing topic...\n");
  strcpy(chan->channel.topic,par);
  putlog(LOG_CMDS,"*","#%s# topic %s",dcc[idx].nick,par);
}
#endif

void cmd_binds(idx,par)
int idx; char *par;
{
  tell_binds(idx,par);
  putlog(LOG_CMDS,"*","#%s# binds %s",dcc[idx].nick,par);
}

void cmd_banner(idx,par)
int idx; char *par;
{
  char s[540]; int i;
  if (!par[0]) {
    dprintf(idx,"Usage: banner <message>\n");
    return;
  }
  sprintf(s,"### \007\007\007BOTWIDE MESSAGE FROM %s:\n",dcc[idx].nick);
  for (i=0; i<dcc_total; i++)
    if ((dcc[i].type==DCC_CHAT) || (dcc[i].type==DCC_FILES))
      dprintf(i,s);
  sprintf(s,"###   %s\n",par);
  for (i=0; i<dcc_total; i++)
    if ((dcc[i].type==DCC_CHAT) || (dcc[i].type==DCC_FILES))
      dprintf(i,s);
}

/* after messing with someone's user flags, make sure the dcc-chat flags
   are set correctly */
int check_dcc_attrs(hand,flags,oatr)
char *hand; int flags,oatr;
{
  int i,stat;
#ifdef OWNER
/* if it matches someone in the owner list, make sure he/she has +n */
  if (owner[0]) {
    char *p,s[512];
    strcpy(s,owner); p=strchr(s,','); while (p!=NULL) {
      *p=0; rmspace(s);
      if (strcasecmp(hand,s)==0) flags|=USER_OWNER;
      strcpy(s,p+1); p=strchr(s,',');
    }
    rmspace(s); if (strcasecmp(hand,s)==0) flags|=USER_OWNER;
    set_attr_handle(hand,flags);
  }
#endif
  for (i=0; i<dcc_total; i++) {
    if (((dcc[i].type==DCC_CHAT) || (dcc[i].type==DCC_FILES)) &&
	(strcasecmp(hand,dcc[i].nick)==0)) {
      if (dcc[i].type==DCC_CHAT) stat=dcc[i].u.chat->status;
      else stat=dcc[i].u.file->chat->status;
      if ((dcc[i].type==DCC_CHAT) && ((flags & (USER_OP|USER_MASTER|USER_OWNER)) != (oatr & (USER_OP|USER_MASTER|USER_OWNER)))) {
	tandout("part %s %s\n",origbotname,dcc[i].nick);
	tandout("join %s %s %d %c %s\n",origbotname,dcc[i].nick,
		dcc[i].u.chat->channel,geticon(i),dcc[i].host);
      }
      if ((stat&STAT_MASTER) && !(flags&USER_MASTER)) {
	stat&=~STAT_MASTER;
	dcc[i].u.chat->con_flags &= ~(LOG_MISC|LOG_CMDS|LOG_RAW|LOG_FILES|
				      LOG_LEV1|LOG_LEV2|LOG_LEV3|LOG_LEV4|
				      LOG_LEV5|LOG_LEV6|LOG_LEV7|LOG_LEV8|
				      LOG_WALL|LOG_DEBUG);
	dprintf(i,"*** POOF! ***\n");
	dprintf(i,"You are no longer a master on this bot.\n");
      }
      if (!(stat&STAT_MASTER) && (flags&USER_MASTER)) {
	stat|=STAT_MASTER;
	dcc[i].u.chat->con_flags |= conmask;
	dprintf(i,"*** POOF! ***\n");
	dprintf(i,"You are now a master on this bot.\n");
      }
#ifdef OWNER
      if (!(oatr & USER_OWNER) && (flags&USER_OWNER)) {
	dprintf(i,"@@@ POOF! @@@\n");
	dprintf(i,"You are now an OWNER of this bot.\n");
      }
      if ((oatr & USER_OWNER) && !(flags&USER_OWNER)) {
	dprintf(i,"@@@ POOF! @@@\n");
	dprintf(i,"You are no longer an owner of this bot.\n");
      }
#endif
      if ((stat&STAT_PARTY) && (flags&USER_OP)) stat&=~STAT_PARTY;
      if (!(stat&STAT_PARTY) && !(flags&USER_OP) && !(flags&USER_MASTER))
	stat|=STAT_PARTY;
      if ((stat&STAT_XFER) && !(flags&USER_XFER) && !(flags&USER_MASTER) &&
	  (!(flags&USER_OP) || require_x))
	stat&=~STAT_XFER;
      if (!(stat&STAT_XFER) && (((flags&USER_OP) && !require_x) ||
				(flags&USER_MASTER) || (flags&USER_XFER)))
	stat|=STAT_XFER;
      if ((stat&STAT_CHAT) && !(flags&USER_PARTY) && !(flags&USER_MASTER) &&
	  (!(flags&USER_OP) || require_x))
	stat&=~STAT_CHAT;
      if ((dcc[i].type==DCC_FILES) && !(stat&STAT_CHAT) && 
	  ((flags&USER_MASTER) || (flags&USER_PARTY) || 
	   ((flags&USER_OP) && !require_p)))
	stat|=STAT_CHAT;
      if (dcc[i].type==DCC_CHAT) dcc[i].u.chat->status=stat;
      else dcc[i].u.file->chat->status=stat;
      /* check if they no longer have access to wherever they are */
      /* DON'T kick someone off the party line just cos they lost +p */
      /* (pinvite script removes +p after 5 mins automatically) */
      if ((dcc[i].type==DCC_FILES) && !(stat&STAT_XFER)) {
	dprintf(i,"-+- POOF! -+-\n");
	dprintf(i,"You no longer have file area access.\n\n");
	putlog(LOG_MISC,"*","DCC user [%s]%s removed from file system",
	       dcc[i].nick,dcc[i].host);
	if (dcc[i].u.file->chat->status&STAT_CHAT) {
	  struct chat_info *ci;
	  ci=dcc[i].u.file->chat; nfree(dcc[i].u.file);
	  dcc[i].u.chat=ci;
	  dcc[i].u.chat->status&=(~STAT_CHAT); dcc[i].type=DCC_CHAT;
	  if (dcc[i].u.chat->channel>=0) {
	    chanout2(dcc[i].u.chat->channel,"%s rejoined the party line.\n",
		     dcc[i].nick);
	    tandout("unaway %s %s\n",origbotname,dcc[i].nick);
	  }
	}
	else {
	  killsock(dcc[i].sock); 
	  dcc[i].sock=dcc[i].type; dcc[i].type=DCC_LOST;
          tandout("part %s %s\n",origbotname,dcc[i].nick);
	}
      }
    }
    if ((dcc[i].type==DCC_BOT) && (strcasecmp(hand,dcc[i].nick)==0)) {
      if ((dcc[i].u.bot->status&STAT_LEAF) && !(flags&BOT_LEAF))
	dcc[i].u.bot->status&=~(STAT_LEAF|STAT_WARNED);
      if (!(dcc[i].u.bot->status&STAT_LEAF) && (flags&BOT_LEAF))
	dcc[i].u.bot->status|=STAT_LEAF;
    }
  }
  return flags;
}

/* some flags are mutually exclusive -- this roots them out */
int sanity_check(atr)
int atr;
{
  if ((atr&USER_OP) && (atr&USER_DEOP)) atr&=~(USER_OP|USER_DEOP);
/*  if ((atr&BOT_SHARE) && (atr&BOT_ALT)) atr&=~(BOT_ALT); */
  if ((atr&BOT_HUB) && (atr&BOT_ALT)) atr&=~BOT_ALT;
  if (atr&BOT_REJECT) {
    if (atr&BOT_SHARE) atr&=~(BOT_SHARE|BOT_REJECT);
    if (atr&BOT_HUB) atr&=~(BOT_HUB|BOT_REJECT);
    if (atr&BOT_ALT) atr&=~(BOT_ALT|BOT_REJECT);
  }
  if (atr&USER_BOT) {
    if (atr & (USER_PARTY|USER_MASTER|USER_COMMON|USER_OWNER))
      atr &= ~(USER_PARTY|USER_MASTER|USER_COMMON|USER_OWNER);
  }
  else {
    if (atr & (BOT_LEAF|BOT_REJECT|BOT_ALT|BOT_SHARE|BOT_HUB))
      atr &= ~(BOT_LEAF|BOT_REJECT|BOT_ALT|BOT_SHARE|BOT_HUB);
  }
#ifdef OWNER
  /* can't be owner without also being master */
  if (atr&USER_OWNER) atr|=USER_MASTER;
#endif
  return atr;
}

void cmd_chattr(idx,par)
int idx; char *par;
{
  char hand[512],s[21]; int i,pos,f,atr,oatr,recheck=0,own;
  context;
  if (!par[0]) {
    dprintf(idx,"Usage: chattr <handle> [changes]\n");
    return;
  }
  nsplit(hand,par); 
  if ((hand[0]=='*') || (!is_user(hand))) {
    dprintf(idx,"No such user!\n");
    return;
  }
  own=(get_attr_handle(dcc[idx].nick) & USER_OWNER);
  oatr=atr=get_attr_handle(hand); pos=1;
  if (par[0]) {
    /* make changes */
    for (i=0; i<strlen(par); i++) {
      if (par[i]=='+') pos=1;
      else if (par[i]=='-') pos=0;
      else {
	s[1]=0; s[0]=par[i]; f=str2flags(s);
	/* nobody can modify the +b bot flag */
	f&=~USER_BOT;
#ifdef OWNER
	if (!own) f&=(~(USER_OWNER|USER_MASTER|BOT_SHARE));
	/* only owner can do +/- owner,master,share */
#endif
	atr=pos?(atr|f):(atr&~f);
	if (f & (USER_KICK|USER_OP|USER_DEOP)) recheck=1;
      }
    }
    atr=sanity_check(atr);
    set_attr_handle(hand,atr);
    atr=check_dcc_attrs(hand,atr,oatr);
  }
  /* get current flags and display them */
  flags2str(atr,s);
  putlog(LOG_CMDS,"*","#%s# chattr %s %s",dcc[idx].nick,hand,par);
  dprintf(idx,"Flags for %s are now +%s\n",hand,s);
  if (recheck) recheck_channels();
}

void cmd_notes(idx,par)
int idx; char *par;
{
  char fcn[512];
  if (!par[0]) {
    dprintf(idx,"Usage: notes index\n");
    dprintf(idx,"       notes read <# or ALL>\n");
    dprintf(idx,"       notes erase <# or ALL>\n");
    return;
  }
  nsplit(fcn,par);
  if (strcasecmp(fcn,"index")==0) notes_read(dcc[idx].nick,"",-1,idx);
  else if (strcasecmp(fcn,"read")==0) {
    if (strcasecmp(par,"all")==0) notes_read(dcc[idx].nick,"",0,idx);
    else notes_read(dcc[idx].nick,"",atoi(par),idx);
  }
  else if (strcasecmp(fcn,"erase")==0) {
    if (strcasecmp(par,"all")==0) notes_del(dcc[idx].nick,"",0,idx);
    else notes_del(dcc[idx].nick,"",atoi(par),idx);
  }
  else dprintf(idx,"Function must be one of INDEX, READ, or ERASE.\n");
  putlog(LOG_CMDS,"*","#%s# notes %s %s",dcc[idx].nick,fcn,par);
}

void cmd_chat(idx,par)
int idx; char *par;
{
  int newchan,oldchan;
  if (strcasecmp(par,"off")==0) {
    /* turn chat off */
    if (dcc[idx].u.chat->channel<0)
      dprintf(idx,"You weren't in chat anyway!\n");
    else {
      dprintf(idx,"Leaving chat mode...\n");
      chanout2(dcc[idx].u.chat->channel,"%s left the party line.\n",
	       dcc[idx].nick);
      tandout("part %s %s\n",origbotname,dcc[idx].nick);
    }
    dcc[idx].u.chat->channel=(-1);
  }
  else {
    if (((par[0]<'0') || (par[0]>'9')) && (par[0])) {
      if (strcasecmp(par,"on")==0) newchan=0;
      else newchan=get_assoc(par);
      if (newchan<0) {
	dprintf(idx,"No channel by that name.\n");
	return;
      }
    }
    else newchan=atoi(par);
    if ((newchan<0) || (newchan>99999)) {
      dprintf(idx,"Channel # out of range: must be between 0 and 99999.\n");
      return;
    }
    /* if coming back from being off the party line, make sure they're
       not away */
    if ((dcc[idx].u.chat->channel<0) && (dcc[idx].u.chat->away!=NULL))
      not_away(idx);
    if (dcc[idx].u.chat->channel==newchan) {
      if (newchan==0) dprintf(idx,"You're already on the party line!\n");
      else dprintf(idx,"You're already on channel %d!\n",newchan);
    }
    else {
      oldchan=dcc[idx].u.chat->channel;
      if (oldchan==0)
	chanout2(0,"%s left the party line.\n",dcc[idx].nick);
      else if (oldchan>0)
	chanout2(oldchan,"%s left the channel.\n",dcc[idx].nick);
      dcc[idx].u.chat->channel=newchan;
      if (newchan==0) {
	dprintf(idx,"Entering the party line...\n");
	chanout2(0,"%s joined the party line.\n",dcc[idx].nick);
      }
      else {
	dprintf(idx,"Joining channel '%s'...\n",par);
	chanout2(newchan,"%s joined the channel.\n",dcc[idx].nick);
      }
      tandout("join %s %s %d\n",origbotname,dcc[idx].nick,newchan);
    }
  }
}

void cmd_echo(idx,par)
int idx; char *par;
{
  if (!par[0]) {
    dprintf(idx,"Echo is currently %s.\n",dcc[idx].u.chat->status&STAT_ECHO?
	    "on":"off");
    return;
  }
  if (strcasecmp(par,"on")==0) {
    dprintf(idx,"Echo turned on.\n");
    dcc[idx].u.chat->status|=STAT_ECHO;
    return;
  }
  if (strcasecmp(par,"off")==0) {
    dprintf(idx,"Echo turned off.\n");
    dcc[idx].u.chat->status&=~STAT_ECHO;
    return;
  }
  dprintf(idx,"Usage: echo <on/off>\n");
}

void cmd_assoc(idx,par)
int idx; char *par;
{
  char num[512]; int chan;
  if (!par[0]) {
    putlog(LOG_CMDS,"*","#%s# assoc",dcc[idx].nick);
    dump_assoc(idx);
    return;
  }
  nsplit(num,par);
  chan=atoi(num);
  if (chan==0) {
    dprintf(idx,"You can't name the main party line; it's just a party line.\n");
    return;
  }
  if ((chan<1) || (chan>99999)) {
    dprintf(idx,"Channel # out of range: must be 1-99999\n");
    return;
  }
  if (!par[0]) {
    /* remove an association */
    if (get_assoc_name(chan)==NULL) {
      dprintf(idx,"Channel %d has no name.\n",chan);
      return;
    }
    kill_assoc(chan);
    putlog(LOG_CMDS,"*","#%s# assoc %d",dcc[idx].nick,chan);
    dprintf(idx,"Okay, removed name for channel %d.\n",chan);
    chanout2(chan,"%s removed this channel's name.\n",dcc[idx].nick);
    tandout("assoc %d 0\n",chan);
    return;
  }
  if (strlen(par)>20) {
    dprintf(idx,"Channel's name can't be that long (20 chars max).\n");
    return;
  }
  if ((par[0]>='0') && (par[0]<='9')) {
    dprintf(idx,"First character of the channel name can't be a digit.\n");
    return;
  }
  add_assoc(par,chan);
  putlog(LOG_CMDS,"*","#%s# assoc %d %s",dcc[idx].nick,chan,par);
  dprintf(idx,"Okay, channel %d is '%s' now.\n",chan,par);
  chanout2(chan,"%s named this channel '%s'\n",dcc[idx].nick,par);
  tandout("assoc %d 0\n",chan,par);  /* wipe it first */
  tandout("assoc %d 0\n",chan,par);
  tandout("assoc %d %s\n",chan,par);
}

void cmd_fries(idx,par)
int idx; char *par;
{
  dprintf(idx,"* %s juliennes some fries for you.\n",origbotname);
  dprintf(idx,"Enjoy!\n");
}

void cmd_flush(idx,par)
int idx; char *par;
{
  if (!par[0]) {
    dprintf(idx,"Usage: flush <botname>\n");
    return;
  }
  if (flush_tbuf(par))
    dprintf(idx,"Flushed resync buffer for %s\n",par);
  else dprintf(idx,"There is no resync buffer for that bot.\n");
}
