/*
 * s_handlers.c
 * (C) James Craig 1998, Email: jcraig@mad.scientist.com
 * (C) Christian Magnusson 1999,2000, Email: d1mag@dtek.chalmers.se
 * This program is copyright, and covered by the Gnu Public License.
 * The Q bot.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "settings.h"
#include "bottypes.h"
#include "globals.h"

extern void m_parse(char *, char *);
void s_ctcp();

void kick_user_reason_(struct activeuser *u, struct chanopts *c, char *reason);
void kick_user_reason2_(struct activeuser *u, struct chanopts *c, char *reason, int force);
void kick_user_reason_banned(struct activeuser *u, struct chanopts *c);
void kick_user_reason_banned2(struct activeuser *u, struct chanopts *c, int force);


void s_ignorecommand()
{
  int offset = 1;
  printf("WARNING: ignorecommand [%s] [%s] [%s] [%s]\n", sender, args[offset + 0], args[offset + 1],
    args[offset + 2]);
  return;
}

void s_gline(char *buffer)
{
  int offset = 1;
  char *ptr;
#if 0
  printf("Parse: GLINE [%s] [%s] [%s]\n", sender, args[offset + 1], args[offset + 2]);
#endif

  if (match(args[offset + 1], q_server_name)) {
    /* This wasn't ment for me... */
    return;
  }
  ptr = strstr(buffer, args[offset + 3]);
  if (!ptr) {
    return;
  }
  ptr = &ptr[strlen(args[offset + 3]) + 2];

  /* reason string will be incorrect */
  if (args[offset + 2][0] == '+') {
    (void) gline_add(&args[offset + 2][1], args[offset + 3], ptr, 0, 1);
  } else if (args[offset + 2][0] == '-') {
    (void) gline_del(&args[offset + 2][1]);
  } else {
    log("s_gline: Unknown format [%s] [%s] [%s] [%s] [%s]\n", sender, args[offset + 1],
      args[offset + 2], args[offset + 3], ptr);
  }

  return;
}

/* The other server telling us its name. */
void s_server(void)
{
  int offset = 2;
#if DEBUG
  printf("Parse: server=[%s] [%s]\n", args[offset + 0], args[offset + 5]);
#endif

  /* this is the main server, no parent server for this one */
  if (strlen(args[7]) == 3) {
    args[7][1] = args[7][0];
    args[7][0] = 'A';
  }
  add_server(args[offset + 0], args[offset + 5][0], args[offset + 5][1], 0, 0);

  /* Set Q's parent server */
  servers[atoi(q_server_number)].parent_server[0] = args[offset + 5][0];
  servers[atoi(q_server_number)].parent_server[1] = args[offset + 5][1];

  /* make sure it nick collides */
  /* 2 == HOPCOUNT+1 */
  putserver("%c%c N %s %d %lu %s %s +odikw B]AAAB %s :The %s Bot\r\n", yxx[0], yxx[1], BOT_NAME, 1,
    (unsigned long)100, BOT_USERNAME, BOT_HOSTNAME, yxx, BOT_NAME);
/* (fox) The following line is useless, it is included in the line above */
/*  putserver(":%s MODE %s +odikw\r\n", BOT_NAME, BOT_NAME); */

/* Lets do that after EA */
/*  join_all_channels(1); */

  putserver("%c%c EB\r\n", yxx[0], yxx[1]);

  return;
}

/*
 * Ah... A command, most likely - need a 2nd level of parsing for bot commands.
 */
void s_privmsg(void)
{
  int i, j;
  /* Woo. Parse a bot command here! */
  if (argcount<4) { return; }
  if (strcasecmp(&args[2][1], BOT_NAME) && strcasecmp(args[2], yxx)
      && strcasecmp(args[2],ourmsghost)) {
    /* 
     * Message wasn't really intended for us.
     * Actually, we could allow aliases for the bot and arrange it so that
     * we *could* do it properly, but right now, I'm not gonna bother.
     * The channel "!seen" command etc. will need changes here.
     */
    return;
  }
  if (args[3][0] == 1) {
    /* We might have a CTCP command */
    for (i = 0; i < strlen(args[argcount-1]); i++) {
      if ((args[argcount-1][i] == '\n') || (args[argcount-1][i] == '\r')
        || (args[argcount-1][i] == 0)) break;
    }
    if (args[argcount-1][i - 1] == 1) {
      s_ctcp();
      return;
      /* 
       * It might not be a bad idea to skip the return;
       * After all, we might as well be able to CTCP bot commands
       * as MSG them, and vice-versa.
       */
    }
  }
  /* 
   * OK, it's a regular /MSG command. Oh, bugger. Another bloody huge
   * command-parsing table. *just* what I need to do with my saturday
   * mornings.
   */
  /* Strip out the actual command. */
  for (i = 0;
       ((args[3][i] != ' ') && (args[3][i] != '\n')
         && (args[3][i] != '\r') && (args[3][i] != 0) && (i < 512));
       i++) {
    command[i] = args[3][i];
  }
  command[i] = 0;

  j = 0;
  if (strlen(getword(&args[3][i], &j)) == 0) {
    /* 
       The command line seems to end here. Make sure we parse an empty string. */
    args[3][i + 1] = '\0';
  }
  if (strcasecmp(command,"auth")==0) {
    /* Someone is trying to auth. Lets check if he msgd botnick@host */
    if (strcasecmp(args[2],ourmsghost)!=0) {
      /* No, he didn't */
      char message[BIGBUF];
      sprintf(message,"Thanks to too many people blindly /msging %s on other networks,",BOT_NAME);
      say_y(sender,message);
      say_y(sender,"and the resulting password-thefts, you are not allowed to auth");
      sprintf(message,"with /msg %s auth username password anymore. Instead you have to use:",BOT_NAME);
      say_y(sender,message);
      sprintf(message,"/msg %s auth username password",ourmsghost);
      say_y(sender,message);
      return;
    }
  }
  m_parse(command, &(args[3][i + 1]));
  return;
}

/*
 * Parse a CTCP-style command. Not really necessary - we'd probably
 * best implement VERSION and PING to stop lusers moaning though.
 */
void s_ctcp(void) {
  // printf("CTCP: [argcount=%d] From [%s] [%s]\n",argcount,sender,args[3]);
  if (strncmp(args[3], "\1VERSION", 8) == 0) {
    ctcpreply(sender, "VERSION " CTCP_VERSION_REPLY);
    return;
  }
  if (strncmp(args[3], "\1PING ", 6) == 0) {
    if (strlen(args[3])>50) { return; }
    putserver("%s O %s :%s\r\n", yxx, sender, args[3]);
    return;
  }
  return;
}

void s_version(void)
{
  ctcpreply(sender, "VERSION " CTCP_VERSION_REPLY);
}

/*
 * This function must add a user to the list of people who are currently
 * connected to the IRC network. Should simply(!) be a case of filling in a
 * user structure and adding it to the main linked lists.
 */
void s_nick(void)
{
  int offset = 1;
  char tmp[100];
  struct dbaseuser *d;
  struct ban *b;
  struct activeuser *u;
  struct chanopts *c;
  struct chanmod *cm, *cm2;
  /* Add a user to the currently-active-users-list. * OR CHANGE CURRENT NICKNAME! */
  fprintf(stderr, "s_nick: %s %s\n", sender, args[offset + 0]);

  if (!(u = findauser(sender))) {
    /* the sender is probably a server */
    if (!(u = findauser(args[offset + 0]))) {
      /* the new nick wasn't know before. User is new from server. */
#if DEBUG
      printf("s_nick: new user [%s] [%s] [%s] [%s] [%s]\n", sender, args[offset + 0],
        args[offset + 3], args[offset + 4], args[offset + 6]);
      log("s_nick: new user [%s] [%s] [%s] [%s] [%s]\n", sender, args[offset + 0], args[offset + 3],
        args[offset + 4], args[offset + 6]);
#endif

#if 1
      /* Sending [BAA NICK Mag2 911130140] to mag.se.quakenet.eu.org */
      addauser(args[offset + 0], args[offset + 3], args[offset + 4], args[offset + 6], "", NULL,
        sender);
#else
      addauser(args[offset + 0], args[offset + 3], args[offset + 4], args[offset + 6], sender, NULL,
        NULL);
#endif
    } else {
      /* it's probably a server reconnection that syncs */
#if DEBUG
      printf("s_nick: resync from [%s] [%s] [%s] [%s] [%s]\n", sender, args[offset + 0],
        args[offset + 3], args[offset + 4], args[offset + 6]);
      log("s_nick: resync from [%s] [%s] [%s] [%s] [%s]\n", sender, args[offset + 0],
        args[offset + 3], args[offset + 4], args[offset + 6]);
#endif
    }
  } else {
    /* Make sure the userid is saved correct in hashtable */
#if DEBUG
    ListElem *el;
    el = Find(auser_nick, u->nick, compare_strcasecmp);
    if (((struct activeuser *) el->data) != u) {
      printf("*** s_nick: el->data != u\n");
      log("*** s_nick: el->data != u\n");
    }
#endif

//    add_nick_flood(u->yxx, u->nick, args[offset + 0]);

    Remove(u->h_elem_nick);
    strncpy(u->nick, args[offset + 0], NICKLEN);
    u->nick[NICKLEN] = '\0';
    AddElem(auser_nick, u->h_elem_nick);
#if DEBUG
    printf("s_nick: user changed nick [%s] [%s]\n", sender, args[offset + 0]);
    log("s_nick: user changed nick [%s] [%s]\n", sender, args[offset + 0]);
#endif
  }

  /* 
     check if user should be banned due to his nick change */
  if (u->chanmodes) {
    cm = u->chanmodes;
    while (cm) {
      cm2 = cm->next;
      c = findchannel(cm->channel);
      if (c && !c->suspended) {
        if ((b = isbanned(u, c))) {
          log("s_nick: [%s] changed nick at [%s] but was BANNED\n", u->nick, c->channel);
          putserver("%s M %s +b %s\r\n", yxx, c->channel, b->ban);
          addban_ch(c, b->ban, 0, 0);
          if (b->placedby) {
            d = finduserfromid(b->placedby);
            if (d) {
              sprintf(tmp, "You are BANNED from this channel by %s", d->nick);
              kick_user_reason_(u, c, tmp);
            } else {
              kick_user_reason_banned(u, c);
            }
          } else {
            kick_user_reason_banned(u, c);
          }
        }
      }
      cm = cm2;
    }
  }
  return;
}

/*
 * This is another one of those must-be-implemented functions.
 * This time, it keeps track of the modes currently set on a user.
 */
void s_mode(void)
{
  int offset = 1;
  struct chanopts *c;
  struct activeuser *u;
  int param, direction, i;
  /* Register a mode change on that user - e.g. +O (IRCop). */
  if ((args[offset + 1][0] != '&') && (args[offset + 1][0] != '#')) {
    /* Then it's a user mode change. */
    u = findauser(args[offset + 1]);
    if (u) {
#if DEBUG2
      printf("s_mode: change of user-mode [%s] [%s]\n", args[offset + 1], args[offset + 2]);
#endif
      updatemodes(&(u->mode), args[offset + 2]);
      updateauth(u);
    } else {
#if DEBUG2
      printf("s_mode: couldn't find user [%s] [%s]\n", args[offset + 1], args[offset + 2]);
#endif
    }
  } else {
    /* 
     * It's a channel mode change, which we don't handle yet.
     * The Q bot will need to check whether it's meant to be
     * bitchy on this channel and stuff before this bit can
     * be done properly.
     */
#if DEBUG2
    printf("s_mode: change of channel-mode [%s] [%s]\n", args[offset + 1], args[offset + 2]);
#endif
    /* 
     * OK, it's a *REAL* channel, it's a *REAL* mode change.
     * We have to parse this one change at a time.
     */
    param = 3;
    direction = 1;              /* + */
    i = 0;
    while (args[offset + 2][i] != 0) {
      switch (args[offset + 2][i]) {
      case '+':
        direction = 1;
        break;
      case '-':
        direction = 0;
        break;
      case 'b':
        c_ban(direction, args[offset + 1], args[offset + param]);
        param++;
        break;
      case 'k':
        c_key(direction, args[offset + 1], args[offset + param]);
        /* bug fixed, only increase parameter if +k direction. */
        /* (fox) Bug refixed, you NEED to increase it on -k as well :) */
        param++;
        break;
      case 'o':
        normnum(args[offset + param]);
        if (!direction && (!strcmp(args[offset + param],yxx))) {
          /* If Q is deopped then opself again ! */
          c = findchannel(args[offset + 1]);
          if (c) {
            log("s_mode: [%s] deoped me at [%s]\n", sender, args[offset + 1]);
            opself(args[offset + 1]);
            /* i don't think it makes sense to kill whoever deopped Q */
            /* if ((u = find_yxx(sender))) {
              char tmp[256];
              log("s_mode: kill [%s] deop'ed %s in [%s]\n", sender, BOT_NAME, args[offset + 1]);
              sprintf(tmp, "You deop'ed %s in [%s]", BOT_NAME, args[offset + 1]);
              putserver("%c%c D %s :%s\r\n", yxx[0], yxx[1], u->yxx, tmp);
              disconnect_auser(u);
            } */
          }
        } else {
          c_op(direction, args[offset + 1], args[offset + param]);
        }
        param++;
        break;
      case 'v':
        normnum(args[offset + param]);
        c_voice(direction, args[offset + 1], args[offset + param]);
        param++;
        break;
      case 'l':
        c_limit(direction, args[offset + 1], args[offset + param]);
        /* bug fixed, only increase parameter if +l direction. */
        if (direction)
          param++;
        break;
      case 't':
        c_opertopic(direction, args[offset + 1]);
        break;
      case 'p':
        c_protect(direction, args[offset + 1]);
        break;
      case 'n':
        c_nomsg(direction, args[offset + 1]);
        break;
      case 'm':
        c_moderate(direction, args[offset + 1]);
        break;
      case 's':
        c_secret(direction, args[offset + 1]);
        break;
      case 'i':
        c_invite(direction, args[offset + 1]);
        break;
      }
      i++;
    }
  }
}

/*
 * I can't remember why we need this. :)
 */
void s_user()
{
  /* Add some info on the aforementioned user - MAKE SURE IT EXISTS FIRST! */
  return;
}

void disconnect_auser(struct activeuser *u)
{
  struct chanmod *cm;
  if (!u)
    return;

#if DEBUG2
  printf("Debug: Deleting nick %s. yxx=[%s]\n", u->nick, u->yxx);
#endif

  /* change server statistics */
  change_server_users(u->yxx[0], u->yxx[1], -1);

  if (u->oper & OPER_O_MASK) {
    change_server_opers(u->yxx[0], u->yxx[1], -1);
  }

  /* remove user from all channels he has joined */
  while ((cm = u->chanmodes)) {
    if (!removeauserfromchan(u, cm->channel, NULL)) {
      log("disconnect_auser: ERROR couldn't remove user from chan [%s]\n", u->chanmodes->channel);
#if DEBUG
      printf("disconnect_auser: ERROR couldn't remove user from chan [%s]\n",
        u->chanmodes->channel);
#endif
    }
  }

#if DEBUG2
  irclog(DEBUG_INFO2, ":%s disconnect_auser YXX=%s\n", u->nick, u->yxx);
#endif

  /* remove user from nick hashtable */
  Remove(u->h_elem_nick);
  FreeElem(u->h_elem_nick);
  u->h_elem_nick = NULL;

  /* remove user from yxx hashtable */
  Remove(u->h_elem_yxx);
  FreeElem(u->h_elem_yxx);
  u->h_elem_yxx = NULL;

#if DEBUG
  if (((struct activeuser *) u->l_elem->data) != u) {
    log("ERROR: disconnect_auser: u->l_elem->data != u\n");
    printf("ERROR: disconnect_auser: u->l_elem->data != u\n");
  }
#endif

  /* 
     last thing to do, is to remove user from the linked list, this will free activeuser struct u */
  Remove(u->l_elem);
  FreeElem(u->l_elem);          /* this will free u pointer */
  return;
}

/*
 * Remove a user from the list of people currently on the network.
 * Needs to remove their entry from the linked lists, and more importantly,
 * free the memory they were using.
 */
void s_quit()
{
  /* User has quit/split - remove them from the lists. */
  struct activeuser *u = find_yxx(sender);
  if (u) {
    disconnect_auser(u);
  } else {
    log("s_quit: Can't find [%s]\n", sender);
  }
  return;
}

void s_squit()
{
  int offset = 1;
#if DEBUG
  // printf("SQUIT %s\n", args[offset+0]);
  log("s_squit: %s\n", args[offset + 0]);
#endif
  del_server(args[offset + 0]);
  return;
}

void add_auth_history(struct chanopts *c, int userid, char *nick)
{
#ifdef AUTH_HISTORY
  int pos = c->auth_history_pos;
  strncpy(c->auth_history_nick[pos], nick, NICKLEN);
  c->auth_history_userid[pos] = userid;
  c->auth_history_pos = (c->auth_history_pos + 1) % AUTH_HISTORY_LEN;
#endif
  return;
}

/*
 * Do some of the stuff per actual channel join, not per JOIN message here.
 *
 * return 1  if user succeed to join
 * return 0  if user is kicked
 * return -1 if user is killed - (fox) shouldn't be possible anymore, should it?!
 */
int handlejoin_u(struct activeuser *u, char *channel, int force_op_check)
{
  struct dbaseuser *d;
  struct ban *b;
  struct chanopts *c;
  char ban_name[NICKLEN + USERLEN + HOSTLEN + 10];
  char tmp[BIGBUF];
  int userlevel;
  int current_op;
  int new_join = 0;
  if (!u) {
    log("Warning: handlejoin_u: u==NULL [%s]\n", channel);
    return 0;
  }

  /* add the user to the channel now */
  if (!addausertochan_u(u, channel)) {
    /* malloc failed, user authed, changed nick or tried to join twice */
#if 0
    log("handlejoin_u: addausertochan_u failed [%s] [%s]\n", u->nick, channel);
    printf("handlejoin_u: addausertochan_u failed [%s] [%s]\n", u->nick, channel);
#endif
  } else {
    /* 
       User wasn't in the channel before, and is added to it now. */
    new_join = 1;
  }

  /* 
     update stats about the channel This will be saved in the channel database later. */
  c = findchannel(channel);
  if (c) {
#if 0
    /* CO_AUTO_JOIN +a flag not supported right now */
    /* if the flag for not autojoin is set, then join now shouldn't really be used */
    if (!c->suspended && !c->bot_joined && !(c->flags & CO_AUTO_JOIN)) {
      joinchannel(c);
      updatebans(c);
    }
#endif
    /* print welcome message */
    if (new_join) {
      /* successful join, then update the channel counter */
      if (!c->first_joined)
        c->first_joined = time(NULL);
      c->nr_joined++;
      c->current_joined++;

      if (c->flags & CO_WELCOME) {
        if (!c->suspended) {
          if (c->welcome[0] && c->welcome[1]) {
            /* only send welcome message if it's longer than 1 char. This will remove the default
               message '!' */
            say_y(u->yxx, c->welcome);
          }
        }
      }
    }
  }

  userlevel = userlevelonchan_d(u->dbuser, channel);

  /* 
     Kick user at once if user has +b flag.. This will be removed. */
  if (c) {
    /* 
       exit here if the channel is suspended. Say the join was ok. */
    if (c->suspended) { return 1; }
    b = isbanned(u, c);
    if (userlevel & CHAN_ALL) {
      /* if user has any flags on the channel, then update the time */
      c->last_joined = time(NULL);
    }
  } else {
    b = NULL;
  }

  if ((userlevel & CHAN_BANNED) || b) {
    log("handlejoin_u: [%s] joined [%s] but was BANNED\n", u->nick, channel);
    if (b) {
      /* set a permanent ban */
      /* change this as soon as possible... Q could perhaps need to rejoin the channel to sync all
         servers. */
      putserver("%s M %s -b %s\r\n", yxx, channel, b->ban);
      putserver("%s M %s +b %s\r\n", yxx, channel, b->ban);
      /* 
         putserver("%c MODE %s -b %s\r\n", yxx[0], channel, b->ban); putserver("%c MODE %s +b
         %s\r\n", yxx[0], channel, b->ban); */

      if (c) { addban_ch(c, b->ban, 0, 0); }

      if (b->placedby) {
        d = finduserfromid(b->placedby);
        if (d) {
          sprintf(tmp, "You are BANNED from this channel by %s", d->nick);
          kick_user_reason_(u, c, tmp);
        } else {
          kick_user_reason_banned(u, c);
        }
      } else {
        if (!c) {
          log("ERROR: handlejoin_u: 2 c==NULL [%s]\n", channel);
        }
        /* kick_user_reason_banned(u, c); */
        kick_user_reason_banned2(u, c, 0);
      }
    } else {
      /* set a banstring to avoid multiple kick, joins. */
      putserver("%s M %s +b %s!%s@%s\r\n", yxx, channel, u->nick, u->username,
        u->hostname);
      if (c) {
        sprintf(ban_name, "%s!%s@%s", u->nick, u->username, u->hostname);
        addban_ch(c, ban_name, 0, 0);
        kick_user_reason_banned(u, c);
      }
    }
    return 0;
  }

  /* Add the user to the channel's AUTHHISTORY list if he gained op. */
  if (new_join || force_op_check) {
    if (userlevel & CHAN_AUTOOP || force_op_check) {
      if (userlevel & CHAN_OPER) {
        current_op = ((c_op_get_modes(channel, u) & (1 << ('o' - 'a'))));
        if (!current_op) {
#ifdef AUTH_HISTORY
          if (c) {
            add_auth_history(c, u->userid, u->nick);
          }
#endif
          if (userlevel & CHAN_AUTOOP) {
            putserver("%s M %s +o %s\r\n", yxx, channel, u->yxx);
            c_op_set_modes(1, channel, u);
          }
        }
      } else if (userlevel & CHAN_VOICE) {
        if (userlevel & CHAN_AUTOOP) {
          putserver("%s M %s +v %s\r\n", yxx, channel, u->yxx);
          c_voice_set_modes(1, channel, u);
        }
      }
    }
  }

  return 1;
}

void kick_user_reason2_(struct activeuser *u, struct chanopts *c, char *reason, int force)
{
  if (!u) {
    log("kick_user_reason_: ERROR? u=NULL");
    return;
  }
  if (!c) {
    log("kick_user_reason_: ERROR? c=NULL");
    return;
  }
  if (force) {
    putserver("%c%c K %s %s :%s\r\n", yxx[0], yxx[1], c->channel, u->yxx, reason);
  } else {
    putserver("%s K %s %s :%s\r\n", yxx, c->channel, u->yxx, reason);
  }
  removeauserfromchan(u, c->channel, c);
  return;
}

void kick_user_reason_(struct activeuser *u, struct chanopts *c, char *reason)
{
  kick_user_reason2_(u, c, reason, 0);
  return;
}

void kick_user_reason_banned(struct activeuser *u, struct chanopts *c)
{
  return kick_user_reason_(u, c, "You are BANNED from this channel.");
}

void kick_user_reason_banned2(struct activeuser *u, struct chanopts *c, int force)
{
  return kick_user_reason2_(u, c, "You are BANNED from this channel.", force);
}



/*
 * User has joined a particular channel. (Possibly having just un-net-split.)
 * I daresay we don't actually need to do very much with this for U. For Q,
 * it's a little different.
 */
void s_join()
{
  int offset = 1;
  char channel[200 + CHANNELLEN + 1];
  struct activeuser *u;
  int i, j, err;
  char *str;
  int nr;

  u = find_yxx(sender);
  if (!u) {
#if DEBUG
    log("ERROR: s_join: can't find [%s] [%s]\n", sender, args[offset + 1]);
#endif
    return;
  }

  /* Need to split up args[offset+1] at commas here. */
  i = 0;
  j = 0;
  if (strcmp(args[2],"0")==0) {  /* /join 0 is a way to leave all channels */
    struct chanopts *co; struct chanmod *cm;
    cm=u->chanmodes;
    if (!cm) { return; /* User wasn't on any chan */ }
    do {
      strcpy(channel,cm->channel);
      co = findchannel(channel);
      if (removeauserfromchan(u, channel, co)) {
        if (co) {
          clear_if_empty_channel(co);
        }
      }
      cm=u->chanmodes;
    } while (cm);
    return;
  }
  if (!args[offset + 1][i])
    return;
  do {
    if (args[offset + 1][i] && (args[offset + 1][i] != ',')) {
      channel[j++] = args[offset + 1][i];
    } else {
      channel[j] = 0;
#if DEBUG
      if (strstr(channel, ",")) {
        log("ERROR: s_join: channel has comma [%s] [%s]\n", channel, sender);
      }
      str = channel;
      nr = 0;
      while ((str = strstr(&str[1], "#"))) {
        nr++;
      }
      if (nr > 1) {
        log("ERROR: s_join: channel has # [%s] [%s]\n", channel, sender);
      }
#endif
      err = handlejoin_u(u, channel, 0);
      if (err == -1) {
        /* user was killed due to his join */
        return;
      } else if (err == 0) {
        /* user was banned due to his join */
      } else if (err == 1) {
        /* ok. */
      }
      if (!args[offset + 1][i])
        return;
      j = 0;
      channel[0] = 0;
    }
  } while (args[offset + 1][i++]);
  return;
}


void clear_if_empty_channel(struct chanopts *co)
{
  if (!co) {
#if DEBUG2
    log("ERROR: clear_if_empty_channel: co==NULL\n");
#endif
    return;
  }

  /* 
     Should make sure channel is owned by Q. Q is NOT listed in activeuser-list, so it will not
     cause any problem if not checked. */
  if (co->current_joined > 0) {
    return;
  }
#if DEBUG2
  printf("no users left, try clear_chanmodes()\n");
#endif
  clear_chanmodes(co, 1);
  return;
}

/*
 * User has left a channel. Much the same deal as above.
 */
void s_part()
{
  int offset = 1;
  char channel[256 + CHANNELLEN + 1];
  struct activeuser *u;
  struct chanopts *co;
  int i, j;
  /* Need to split up args[offset+0] at commas here. */
  i = 0;
  j = 0;

  u = find_yxx(sender);
  if (!u) {
#if DEBUG
    log("ERROR: s_part: can't find [%s] [%s]\n", sender, args[offset + 1]);
#endif
    return;
  }
  if (!args[offset + 1][i]) {
    log("ERROR: s_part: [%s] args[offset+1] empty\n", sender);
    printf("ERROR: s_part: [%s] args[offset+1] empty\n", sender);
    return;
  }

  do {
    if (args[offset + 1][i] && (args[offset + 1][i] != ',')) {
      channel[j++] = args[offset + 1][i];
    } else {
      channel[j] = 0;
      co = findchannel(channel);
      if (removeauserfromchan(u, channel, co)) {
        if (co) {
          clear_if_empty_channel(co);
        } else {
          /* Q have probably not joined the channel */
          // printf("s_part: Couldn't find [%s]\n", channel);
        }
      } else {
#if DEBUG2
        char tmp[2048];
        int len;
        struct chanmod *cm;
        log("s_part: ERROR2 couldn't remove user [%s] from chan [%s]\n", u->nick, channel);
        printf("s_part: ERROR2 couldn't remove user [%s] from chan [%s]\n", u->nick, channel);

        len = sprintf(tmp, "User is still at: ");
        cm = u->chanmodes;
        while (cm) {
          len += sprintf(&tmp[len], "[%s] ", cm->channel);
          if (len > 1500)
            break;
          cm = cm->next;
        }
        len += sprintf(&tmp[len], "\n");
        log(tmp);
        // printf(tmp);
#endif
      }
      /* if(!args[offset+1][i]) return; */
      j = 0;
      channel[0] = 0;
    }
  } while (args[offset + 1][i++]);
  return;
}

/*
 * Erk! Someone just tried a /KILL on us - it might be an idea to do something
 * sensible when this happens, since it should actually be impossible (you
 * can't do a /KILL on an IRCop. Or you shouldn't be able to anyway).
 */
void s_kill() {
  struct activeuser *u2;
#if DEBUG
  log("s_kill: [%s] [%s]\n", command, args[2]);
#endif
  if (strlen(sender)==5) {
    log("s_kill: Kill sender=[%s]\n", sender);
    putserver("%c%c D %s :Don't try to kill the channelservice you dork!\r\n",
      yxx[0], yxx[1], sender);
    u2=find_yxx(sender);
    if (u2) { disconnect_auser(u2); }
  } else {
    log("s_kill: Can't find [%s]\n", sender);
  }
  putserver("%c%c N %s %d %lu %s %s +odikw B]AAAB %s :The %s Bot\r\n", yxx[0], yxx[1], BOT_NAME, 1,
    (unsigned long)startup_time, BOT_USERNAME, BOT_HOSTNAME, yxx, BOT_NAME);
  join_all_channels(1);
  return;
}

void s_pong(void)
{
  /* 
   * We should use alarm() and related functions to handle a timeout
   * so we know when the server has dropped. Not implemented yet.
   */
  return;
}

/*
 * Respond to a PING from a server.
 */
void s_ping()
{
  int offset = 1;
  pong(args[offset + 1]);
  return;
}

void s_ison()
{
  return;
}

void s_silence()
{
#if DEBUG2
  int offset = 1;
  char tmp[1024];
  int i, len;
  /* print ALL server messages */
  len = sprintf(tmp, "Warning: Unhandled SILENCE sender=[%s] command=[%s] ", sender, command);
  for (i = 0; i < argcount; i++) {
    len += sprintf(&tmp[len], "[%s] ", args[offset + i]);
  }
  len += sprintf(&tmp[len], "\n");
  log(tmp);
#endif
  return;
}

void s_lusers()
{
#if DEBUG
  int offset = 1;
  int i;
  struct tm *tm_;
#if DEBUG2
  char tmp[1024];
  /* print ALL server messages */
  int len;
  len = sprintf(tmp, "Warning: Unhandled LUSERS sender=[%s] command=[%s] ", sender, command);
  for (i = 0; i < argcount; i++) {
    len += sprintf(&tmp[len], "[%s] ", args[offset + i]);
  }
  len += sprintf(&tmp[len], "\n");
  log(tmp);
#endif
  /* 
     [Tue 990928 01:51:35] Warning: Unhandled LUSERS sender=[Q] command=[LUSERS] [*] [E] */

  if (argcount < 1)
    return;

  i = 0;
  while (i < argcount) {
    switch (args[offset + i++][0]) {
    case '*':
      if (args[offset + i][0] == yxx[0]) {
        struct activeuser *u;
        char pyxx[4];
        time_t tnow;
        tnow = time(NULL);
        tnow -= startup_time;
        tm_ = localtime(&tnow);
        /* does not print uptime over 1 year correct */

        if ((u = nick_to_yxx(sender, pyxx))) {
          int nr_servers = 0;
          int nr_channels = 0;
          for (i = 0; i < MAX_SERVERS; i++) {
            if (servers[i].connected) {
              nr_servers++;
            }
          }
#if 0
          {
            ListElem *el;
            /* this is a waste of time... but... hmmm */
            el = GetHead(channel_list);
            while (el) {
              nr_channels++;
              el = GetSucc(el);
            }
          }
#else
          nr_channels = 1;
#endif
          putserver("%c%c 251 %s :There are %d users and 0 invisible on %d servers\r\n", yxx[0],
            yxx[1], sender, servers[MAX_SERVERS].users, nr_servers);
          putserver("%c%c 252 %s %d :operator(s) online\r\n", yxx[0], yxx[1], sender,
            servers[MAX_SERVERS].opers);
          putserver("%c%c 254 %s %d :channels formed\r\n", yxx[0], yxx[1], sender, nr_channels);
          putserver("%c%c 255 %s :I have 1 clients and 1 servers\r\n", yxx[0], yxx[1], sender);
          ctcpreply(sender, "Highest connection count: 1 (1 clients)\r\n");
        }
      }
      return;
      break;
    default:
      break;
    }
    i++;
  }
#endif
  return;
}

void s_topic(char *buffer)
{
  int offset = 1;
  int len;
  char *ptr, *p;
  struct chanopts *c = findchannel(args[offset + 1]);
  if (!c)
    return;

  /* If someone tried to set the topic, then change it back. */
  if (c->flags & CO_FORCE_TOPIC) {
    settopic(c, c->topic);
  } else {
    ptr = strstr(buffer, args[offset + 1]);
    if (ptr) {
      p = &ptr[strlen(args[offset + 1]) + 2];
      strncpy(c->last_topic, p, TOPICLEN);
      c->last_topic[TOPICLEN] = '\0';
      len = strlen(c->last_topic);
      if (c->last_topic[len - 1] == '\012' || c->last_topic[len - 1] == '\015') {
        len--;
        c->last_topic[len] = '\0';
      }
      if (c->last_topic[len - 1] == '\012' || c->last_topic[len - 1] == '\015') {
        len--;
        c->last_topic[len] = '\0';
      }
    }
  }
  return;
}

void s_topic_old()
{
#if 0
  int offset = 1;
  char *ptr, *p;
  struct chanopts *c = findchannel(args[offset + 0]);
  if (!c)
    return;

  /* If someone tried to set the topic, then change it back. */
  if (c->flags & CO_FORCE_TOPIC) {
    settopic(c, c->topic);
  } else {
#if DEBUG
    log("s_topic: change last_topic to [%s]\n", buffer);
#endif
    ptr = strstr(buffer, args[offset + 0]);
    if (ptr) {
      p = &ptr[strlen(args[offset + 0]) + 1];
      strncpy(c->last_topic, p, TOPICLEN);
      c->last_topic[TOPICLEN] = '\0';
#if DEBUG
      log("[%s]\n", p);
#endif
    }
  }
#endif
  return;
}

void s_kick()
{
  struct activeuser *u2;
  u2 = find_yxx(args[3]);
  if (u2) {
    if (!removeauserfromchan(u2, args[2], NULL)) {
      log("s_kick: 1 cant remove user [%s] [%s] [%s]\n", u2->nick, args[3], args[2]);
    }
  } else {
    log("s_kick: 2 cant find yxx [%s] [%s] [%s]\n", u2->nick, args[2], args[3]);
  }
  return;
}

void s_handleburst(char *buffer) {
  char pyxx[YXXLEN + 1];
  char modestr[64 + 1];
  char *ch, *ch_end;
  struct activeuser *u;
  struct chanopts *c;
  int len, have_modes, have_limit, have_key;
  int last_user_on_chan;
  int next_arg;
  unsigned int modes, cur_mode;

  c = findchannel(args[2]);
  /* 
     activeuser struct will be complete with a channel list. Channel struct will only be
     filled in if it's registrated. (if c!=NULL) */
  have_modes = 0;
  have_limit = 0;
  have_key = 0;
  if (args[4][0] == '+') {
    have_modes = 1;
    modes = 0;
    updatemodes(&modes, args[4]);
    if (modes & (1 << ('l' - 'a'))) {
      have_limit = 1;
    }
    if (modes & (1 << ('k' - 'a'))) {
      have_key = 1;
    }
  }
  if (argcount >= 4) {
    if (c && have_modes) {
      c->modes = modes;
      if (have_limit) {
        if (c->flags & (1 << ('l' - 'a'))) {
          /* 
             if force limit is set, then ignore this string */
        } else {
          strncpy(c->limit, args[4 + have_modes], LIMITLEN);
          c->limit[LIMITLEN] = '\0';
	  strcpy(c->cur_limit, c->limit);
        }
      }
      if (have_key) {
        if (c->flags & (1 << ('k' - 'a'))) {
          /* 
             if force key is set, then ignore this string */
        } else {
          strncpy(c->key, args[4 + have_modes + have_limit], KEYLEN);
          c->key[KEYLEN] = '\0';
        }
      }
    }
    next_arg = 4;

    ch = args[next_arg + have_modes + have_limit + have_key];
    /* while(ch && (strlen(ch)>0) &&  */
    while (ch && *ch && ((next_arg + have_modes + have_limit + have_key) < argcount)) {
#if DEBUG2
      printf("*************************** next_arg=%d\n", next_arg);
#endif
      if (ch[0] == '%') {
        int ban_nr, last_ban_on_chan;
        /* add eventually bans on the channel */
        len = strlen(ch);
        ban_nr = 0;       /* first users has only joined..no modes */
        last_ban_on_chan = 0;
        do {
          ch_end = strchr(ch, ' ');
          if (!ch_end && (ch < &args[next_arg + have_modes + have_limit + have_key][len - 1])) {
            ch_end = &args[next_arg + have_modes + have_limit + have_key][len];
            last_ban_on_chan = 1;
          }
          if (ch_end) {
            *ch_end = '\0';
            if (ban_nr++ == 0) {
              /* remove first sign % in the first ban ??? */
              ch++;
            }
            /* add the ban, but don't kick */
            if (c)
              addban_ch(c, ch, 0, 0);
            ch = ch_end + 1;
          }
        } while (!last_ban_on_chan && ch_end && (ch_end < &ch[len]));
      } else {
        int plen;
        /* add users in the channel */
        len = strlen(ch);
        cur_mode = 0;     /* first users has only joined.. no modes */
        /* (fox) this is not exactly correct: If a channel is split over
           more than one burstline, you have to continue with the modes
           of the previous line */

        last_user_on_chan = 0;
        do {
          ch_end = strchr(ch, ',');
          if (!ch_end && (ch < &args[next_arg + have_modes + have_limit + have_key][len - 1])) {
            ch_end = &args[next_arg + have_modes + have_limit + have_key][len];
            last_user_on_chan = 1;
          }
          if (ch_end) {
            *ch_end = '\0';
            strncpy(pyxx, ch, YXXLEN);
            pyxx[YXXLEN] = '\0';
            if (strlen(ch) == 3) {  /* 2.10.07 num without :mode */
              pyxx[4] = pyxx[2];
              pyxx[3] = pyxx[1];
              pyxx[2] = 'A';
              pyxx[1] = pyxx[0];
              pyxx[0] = 'A';
              pyxx[5] = 0;
              plen = 3;
            } else {      /* could be: SCC:m or SCC:mm or SSCCC or SSCCC:m or SSCCC:mm */
              if (ch[3] == ':') { /* Old num */
                pyxx[0] = 'A';
                pyxx[1] = ch[0];
                pyxx[2] = 'A';
                pyxx[3] = ch[1];
                pyxx[4] = ch[2];
                pyxx[5] = 0;
                plen = 3;
              } else {    /* New num */
                plen = 5;
              }
            }
            if ((u = find_yxx(pyxx))) {
              if (strlen(ch) == plen) {
                /* use the cur_mode for following user */
              } else {
                /* new modes for the rest of the users */
                cur_mode = 0;
                strcpy(modestr, "+");
                strcat(modestr, &ch[plen + 1]);
                updatemodes(&cur_mode, modestr);
              }
              /* Handlejoin will add a channel to the activeuser structure */
              if (handlejoin_u(u, args[2], 0) != -1) {
                struct chanmod *cm;
                cm = u->chanmodes;
                while (cm) {
                  if (!strcasecmp(cm->channel, args[2])) {
                    cm->mode=cur_mode;
                  }
                  cm = cm->next;
                }
                /* if (cur_mode & (1 << ('v' - 'a'))) {
                  c_voice(1, args[2], u->yxx);
                  cur_mode |= (1 << ('v' - 'a'));
                }
                if (cur_mode & (1 << ('o' - 'a'))) {
                  c_op(1, args[2], u->yxx);
                  cur_mode |= (1 << ('o' - 'a'));
                } */
              }
            } else {
#if DEBUG
              printf("*** couldn't find YXX [%s] [%s]\n", pyxx, args[2]);
#endif
            }
            ch = ch_end + 1;
          }
        } while (!last_user_on_chan && ch_end && (ch_end < &ch[len]));
      }
      next_arg++;
      ch = args[next_arg + have_modes + have_limit + have_key];
    }
  } else {
#if DEBUG
    log("ERROR: *** unknown format 4 [%s] [%s]\n", command, buffer);
#endif
  }
}

void s_handlenicks(char *buffer) {
	dl_command_entry_t *cmd;
	if (argcount == 11) {
		/* user has some modes set (e.g. +s, +o) */
		addauser(args[2], args[5], args[6], args[argcount - 1], NULL, args[argcount - 4],
			args[argcount - 2]);
	} else if (argcount == 10) {
		/* user has NO modes set */
		addauser(args[2], args[5], args[6], args[argcount - 1], NULL, NULL, args[argcount - 2]);
	} else {
#if DEBUG
		log("Warning: *** Unhandled modes NICK argcount=%d ??\n", argcount);
#endif
	}
	/* 
	   Check for clones. */
	cmd = findcommand("trustcheck");
	if (cmd && cmd->func) {
		cmd->func((char *) args[6]);
	}
}

void s_handlenickc(char *buffer, struct activeuser *u) {
  struct chanmod *cm, *cm2;
  /* remove from hash table, change name and then insert again */
//  add_nick_flood(u->yxx, u->nick, args[2]);

  Remove(u->h_elem_nick);
  strncpy(u->nick, args[2], NICKLEN);
  u->nick[NICKLEN] = '\0';
  AddElem(auser_nick, u->h_elem_nick);
  /* could perhaps make a handlejoin for all channels here to trig the bans etc... */
  cm = u->chanmodes;
  while (cm) {
    /* 
       user can be kicked from a channel due to nickchange. Find successor before
       handlejoin. */
    cm2 = cm->next;
    if (handlejoin_u(u, cm->channel, 0) == -1) {
      log("Warning: user killed during NICK change [%s] [%s] [%s] [%s]\n", command, sender,
        u->nick, cm->channel);
      /* user was killed */
      return;
    }
    cm = cm2;
  }
}

void s_handlestats() {
  if (argcount<4) { log("Warning: illegal stats-request (<4 args)\n"); return; }
  if (!strcmp(args[2],"P")) {
    /* STATS P: shows open ports */
    putserver("%c%c 217 %s P none 0 0x2000\r\n",yxx[0],yxx[1],sender);
    putserver("%c%c 219 %s P :End of /STATS report\r\n",yxx[0], yxx[1], sender);
    return;
  }
  if (!strcmp(args[2],"u")) {
    /* STATS u: shows uptime */
    long iamup, days, hours, minutes;
    iamup=time(NULL)-startup_time;
    days=iamup/(24*3600); iamup%=(24*3600);
    hours=iamup/3600; iamup%=3600;
    minutes=iamup/60; iamup%=60;
    putserver("%c%c 242 %s :Server Up %ld days, %02ld:%02ld:%02ld\r\n",
      yxx[0], yxx[1],sender,days,hours,minutes,iamup);
    putserver("%c%c 250 %s :Highest connection count: 2 (1 client)\r\n",yxx[0],
      yxx[1],sender);
    putserver("%c%c 219 %s u :End of /STATS report\r\n",yxx[0],yxx[1],sender);
    return;
  }
}
