/*
 * newq.c
 * (C) James Craig 1998, Email: jcraig@mad.scientist.com
 * (C) Christian Magnusson 1999, Email: d1mag@dtek.chalmers.se
 * (C) QuakeNet dev-com 2001, Email: dev-com@quakenet.eu.org
 * This program is copyright, and covered by the Gnu Public License.
 * The Q bot.
 */
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <ctype.h>
#include <errno.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>

#include "bottypes.h"
#include "hash.h"
#include "os.h"
#include "settings.h"
#include "globals.h"


#define AUSER_HASH_LEN 35000
#define AUTHED_HASH_LEN 10000
#define CHANNEL_HASH_LEN 30000
#define COMMAND_HASH_LEN 30
#define DBUSER_HASH_LEN 100000


char *USER_DBASE_FILE;
char *CHANNEL_DBASE_FILE;
char *GLINE_DBASE_FILE;
char *TRUSTED_DBASE_FILE;
char *COMMANDS_FILE;
char *CHANTYPE_DBASE_FILE;
char *COMMANDS_LIB_DIR;
char *MAX_USERS_FILE;
API_PUBLIC char *BOT_NAME;
char *VIRTUAL_SERVER_NAME;
API_PUBLIC char *BOT_HOSTNAME;
char *SENDMAIL;
char *VIRTUAL_SERVER_PASSWORD;
char *DEBUG_LOGPATH;
char *LOGPATH;
char *LOGPATH_MALLOC;
char *q_remote_server;
char *q_port;
char *q_database;
char *q_log;
char *q_server_number;
char *q_server_name;
char *q_server_passwd;
char *q_stats_dir;
char *q_stats_domain;
char *q_commands_dir;

/* 
 * FD of IRC server - handy to keep global. I might eventually restrict it
 * to only the bits that really need it, but I hardly see the point, frankly.
 */
int serverlink = 0;

/*
 * globals used to pass arguments between functions - much simpler and a bit
 * faster than doing it "properly".
 */
char args[MAX_ARGS][BIGBUF];
int argcount;
char sender[BIGBUF];
struct activeuser *sender_au;
char command[BIGBUF];
char what[BIGBUF];
char yxx[YXXLEN + 1];           /* (fox) That seems to be the numeric of Q */
int convert_database = 0;       /* hack like hell */
char sock_name[BIGBUF];
int burst_mode = 0;
int getline_errors = 0;
int sighup_received = 0;
char *ourmsghost = NULL;        /* Users have to /msg botnick@servername to
                                   auth. This is done to prevent password-
                                   stealing on other nets */
int donewithburst = 0;          /* (fox) there is an int "burst" but i'm not
                                   sure what it is used for. So i set up my
                                   own variable. It's 1 as soon as netjoin
                                   is completed. */

/* Just a funny thing to record the user record */
//int max_users = 12137;
//time_t max_users_time = 968266709;
//int max_users = 13159;
//time_t max_users_time = 969216721;
//int max_users = 13264;
//time_t max_users_time = 970426596;
//int max_users = 13913;
//time_t max_users_time = 971895290;
//int max_users = 16858;
//time_t max_users_time = 974663515;
int max_users = 31342;
time_t max_users_time = 988400000; // Just an estimate

/* flag to turn on/off debug messages to log-files */
int debuglog = 2;
//int debuglog = 10;

int chanlimit_delay = 60;

char filler123[100];            /* yxx seem to write too much */
struct Server servers[MAX_SERVERS + 1];

/*
  Lists with:
  All users connected to the net.
  All registered channels.
  All registered nick names
*/
List *auser_list;
List *channel_list;
List *dbuser_list;
List *gline_list;
List *chantype_list;
List *nickchange_list;
List *trusted_list;

/*
  Hashtables to make it quicker to search through the lists
*/
HashTable *auser_yxx;           /* search who a message came from in all server commands */
HashTable *auser_nick;          /* search for target person in all user commands */
HashTable *channels;            /* search for channel in both server & user commands */
HashTable *dbusers_nick;        /* search for dbusers name, mostly user commands */
HashTable *dbusers_lastyxx;     /* this will speedup startup only */
HashTable *dbusers_userid;      /* search for userid, mostly bans etc. */
HashTable *commands;            /* user commands */


/*
 * Some time-stamps of update etc.
 */
time_t startup_time;
long time_diff = 0L;

/*
 * Trigger to warn opers about multiple authing.
 */
int authid_history_trigger = 3;

/*
 * Number of users we trust for every host.
 */
int trusted_users = 5;

/*
 * Counters of global users/opers totally on the net..
 */
int nr_users_total, nr_opers_total;

/*
 * Maximum encountered UID so far. Should be changed later to find deleted
 * userid's.
 */
int maxuid = 0;

/*
  save the function we want to reload, and do it in the main loop.
  this is just because we have to avoid reload a module which is in use.
*/
API_PUBLIC int unload_one_command = 0;
API_PUBLIC int reload_command = 0;
API_PUBLIC char reload_command_name[256];
int init_dllib(void);

/*
  made a test to decrease systemcalls and optimize reading
*/
#define MAX_INBUFLEN 32768
#define READ_LEN 16384
char inbuf[MAX_INBUFLEN + 1];  /* one more than defined maximum length */
int inbuf_pos = 0;
int inbuf_len = 0;

#ifdef DOSQLLOGGING
char *sqlhost, *sqlusername, *sqlpassword, *sqldb, *sqllogtable = NULL;
unsigned int sqlport=0;
#endif

char *crypt(const char *key, const char *salt);
char *crypt_r(const char *key, const char *salt)
{
  return crypt(key, salt);
}

void normnum(char* a) {
  int b;
  b=strlen(a);
  if (b==3) {
    a[4]=a[2];
    a[3]=a[1];
    a[2]='A';
    a[1]=a[0];
    a[0]='A';
    a[5]='\0';
    return;
  }
  if (b==1) {
    a[1]=a[0];
    a[0]='A';
    a[2]='\0';
    return;
  }
  if ((b!=5) && (b!=2)) {
    log("Warning: Couldn't norm numeric %s",a);
  }
}

/*
 * OK, Useful functions like those used to join a channel, part a channel, say
 * something, do something, send a CTCP or a CTCP reply, and hopefully DCC
 * utilities eventually go here. This code just handles sending commands to
 * the server, and has absolutely nothing to do with parsing the replies.
 */

void settopic(struct chanopts *c, char *topic)
{
  if (!c)
    return;
  putserver("%s T %s :%s\r\n", yxx, c->channel, topic);
  strncpy(c->last_topic, topic, TOPICLEN);
  c->last_topic[TOPICLEN] = '\0';
  return;
}

void setkey(struct chanopts *c, char *key)
{
  if (!c)
    return;
  putserver("%s M %s +k %s\r\n", yxx, c->channel, key);
  strncpy(c->key, key, KEYLEN);
  c->key[KEYLEN] = '\0';
  return;
}

void wallop_w(char *message)
{
  putserver("%s WA :%s\r\n", yxx, message);
}

void wallop(char *message, ...)
{
  va_list va;
  char tmp[BIGBUF + 1];
  struct activeuser *u;
  ListElem *el;
  int len;

  if (!message)
    return;
  va_start(va, message);
  len = vsnprintf(tmp, BIGBUF, message, va);
  va_end(va);

  el = GetHead(auser_list);
  while (el) {
    u = (struct activeuser *) (el->data);
    if (u->oper) {
      say_y(u->yxx, tmp);
    }
    el = GetSucc(el);
  }
  return;
}

void wallop_d(int level, char *message, ...)
{
  va_list va;
  char tmp[BIGBUF + 1];
  struct activeuser *u;
  ListElem *el;
  int len;

  if (!message)
    return;
  va_start(va, message);
  len = vsnprintf(tmp, BIGBUF, message, va);
  va_end(va);

  el = GetHead(auser_list);
  while (el) {
    u = (struct activeuser *) (el->data);
    if (u->oper) {
      if (((u->oper & OPER_DEBUG_MASK) >> OPER_DEBUG_BIT) >= level) {
        say_y(u->yxx, tmp);
      }
    }
    el = GetSucc(el);
  }
  return;
}

char *gettime(void)
{
  static char buffer[32];
  time_t t = time(NULL);
  t += time_diff;
  sprintf(buffer, "%ld", (long) t);
  return buffer;
}

void joinchannel(struct chanopts *c)
{
  if (!c)
    return;
  if (c->suspended)
    return;
  c->bot_joined = 1;
  putserver("%s J %s\r\n", yxx, c->channel);
  opself(c->channel);
}

void join_all_channels(int burst)
{
  struct chanopts *c;
  struct ListElem *el = GetHead(channel_list);
  while (el) {
    c = (struct chanopts *) el->data;
    if (!c->suspended) {
      if (c->flags & CO_AUTO_JOIN) {
        burstchannel(c);
        /* updatebans(c); */
      }
    }
    el = GetSucc(el);
  }
  printf("Joined all channels\r\n");
  return;
}

void burstchannel(struct chanopts *c)
{
  if (!c)
    return;
  if (c->suspended)
    return;

  /* mark the bot has joined */
  c->bot_joined = 1;

  /* (fox) Changed this whole mess. It now does the channeljoins PROPERLY. First netburst, then
     join all channels, hacking modes only on those that already exist. */

  if (c->current_joined == 0) {

//    printf("DEBUG: No users on %s, creating it\n",c->channel);

    putserver("%s C %s %lu\r\n", yxx, c->channel, (unsigned long) startup_time);
  } else {
    putserver("%s J %s\r\n", yxx, c->channel);
    opself(c->channel);
  }
  return;
}

void partchannel(struct chanopts *c)
{
  if (!c)
    return;
  c->bot_joined = 0;
  c->current_joined = 0;        /* force this to 0 to mark nobody has joined */

  putserver("%s L %s\r\n", yxx, c->channel);
  return;
}

void say(char *victim, char *words)
{
  char pyxx[64];
  if (!nick_to_yxx(victim, pyxx)) {
    return;
  }
  putserver("%s O %s :%s\r\n", yxx, pyxx, words);
}

void say_y(char *pyxx, char *words)
{
  if (!pyxx || !words)
    return;
  putserver("%s O %s :%s\r\n", yxx, pyxx, words);
}

void say_au(struct activeuser *au, char *words)
{
  if (!au)
    return;
  putserver("%s O %s :%s\r\n", yxx, au->yxx, words);
}

void say_sender(char *words)
{
  if (!sender_au)
    return;
  putserver("%s O %s :%s\r\n", yxx, sender_au->yxx, words);
}

void say_sender_(char *words, ...)
{
  char tmp[BIGBUF + 1];
  va_list va;
  int len;
  if (!sender_au)
    return;

  va_start(va, words);
  len = vsnprintf(tmp, BIGBUF, words, va);
  putserver("%s O %s :%s\r\n", yxx, sender_au->yxx, tmp);
  return;
}

void dumpmessage_u(struct activeuser *u, char *message)
{
  char buffer[2 * BIGBUF + 1];
  int i, j, len;
  i = 0;
  j = 0;
  if (!u)
    return;

  len = strlen(message);
  while (i < strlen(message)) {
    j = 0;
    while ((message[i] != '\n') && (j < 2 * BIGBUF) && (message[i])) {
      buffer[j] = message[i];
      j++;
      i++;
    }
    buffer[j] = 0;
    say_y(u->yxx, buffer);
    i++;
  }
}

void dumpmessage(char *victim, char *message)
{
  struct activeuser *u = findauser(victim);
  if (!u)
    return;
  return dumpmessage_u(u, message);
}

void notice(char *victim, char *words)
{
  return;
}

void act(char *channel, char *words)
{
  char buffer[513];
  strcpy(buffer, "ACTION ");
  strcpy(buffer + strlen(buffer), words);
  ctcp(channel, buffer);
  return;
}

void privmsg(char *victim, char *what)
{
  char pyxx[64];
  if (!nick_to_yxx(victim, pyxx)) {
    return;
  }
  putserver("%s P %s :%s\r\n", yxx, pyxx, what);
  return;
}

void ctcp(char *victim, char *what)
{
  char pyxx[64];
  if (!nick_to_yxx(victim, pyxx)) {
    return;
  }
  putserver("%s P %s :\01%s\01\r\n", yxx, pyxx, what);
  return;
}

void ctcpreply(char *victim, char *what)
{
  putserver("%s O %s :\01%s\01\r\n", yxx, victim, what);
  return;
}

void opself(char *channel)
{
  putserver("%c%c M %s +o %s\r\n", yxx[0], yxx[1], channel, yxx);
  return;
}

void pong(char *server)
{
  putserver("%c%c Z %s :%s\r\n", yxx[0], yxx[1], VIRTUAL_SERVER_NAME, server);
  return;
}


int is_empty_channel(char *channel)
{
  struct chanpriv *p;
  ListElem *el;

  el = GetHead(dbuser_list);
  while (el) {
    p = ((struct dbaseuser *) el->data)->privs;
    while (p) {
      if (strcasecmp(channel, p->channel) == 0) {
        /* found a user on that channel */
        return 0;
      }
      p = p->next;
    }
    el = GetSucc(el);
  }
  /* there aren't any users on that channel */
  return 1;
}

int is_oper(void)
{
  if (!sender_au)
    return 0;

  if (sender_au->oper & OPER_O_MASK) {
#if DEBUG2
    printf("is_oper: Used an oper command [%s] [%d]\n", sender_au->nick, sender_au->oper);
#endif
    return 1;
  }
#if DEBUG2
  log("is_oper: Failed the test for oper command [%s]\n", sender_au->nick);
#endif
  return 0;
}


/* Utility functions that actually do read/writes from the server. */

void putserver(char *text, ...)
{
#define PUTSERVER_BUF_LEN 512
  char tmp[PUTSERVER_BUF_LEN + 1];
  va_list va;
  int len, nr, pos, retry, left;
  va_start(va, text);
  len = vsnprintf(tmp, PUTSERVER_BUF_LEN, text, va);
  va_end(va);

  pos = 0;
  retry = 0;
  left = len;

  // fprintf(stderr, "putserver: %s\n", tmp);

  while (left) {
    nr = write(serverlink, &tmp[pos], left);
    if (nr == left)
      break;
#if 0
  [Sun 000514 15: 06: 07] ERROR: putserver:len = 161 nr = 10 left = 161 errno = 84
#define EILSEQ          84      /* Illegal byte sequence */
#endif
      log("ERROR: putserver: len=%d nr=%d left=%d errno=%d\n", len, nr, left, errno);
    if (nr == -1) {
      switch (errno) {
      case EINTR:
        log("ERROR: putserver: Got EINTR\n");
        break;
      case EAGAIN:
        log("ERROR: putserver: Got EAGAIN\n");
        break;
      case EPIPE:
        log("ERROR: putserver: Got EPIPE\n");
        startlink();
        return;
        break;
      default:
        log("ERROR: putserver: Got errno=%d\n", errno);
        startlink();
        return;
        break;
      }
    } else {
      left -= nr;
      pos += nr;
    }
    if (retry++ > 10) {
      log("ERROR: putserver: EXIT retry = 10\n");
      exit(1);
    }
  }
  /* 
     Added just to force write one row at the time. A long result string from Q to the user, could
     result in a major flood to the irc server which reads on 8kb chucks. Bufferoverrun could
     occour, and core is dumped from the server. */
  /* 
     nr = fsync(serverlink); if(nr < 0) { log("ERROR: putserver: fsync nr=%d errno=%d\n", nr,
     errno); } */
  return;
}

void putserver1(char *text)
{
#define PUTSERVER_BUF_LEN 512
  char tmp[PUTSERVER_BUF_LEN + 1];
  int len, nr, pos, retry, left;
  len = strlen(text);
  if (len > PUTSERVER_BUF_LEN)
    len = PUTSERVER_BUF_LEN;
  strncpy(tmp, text, len);
  tmp[len] = 0;

  pos = 0;
  retry = 0;
  left = len;

  while (left) {
    nr = write(serverlink, &tmp[pos], left);
    if (nr == left)
      break;
#if 0
  [Sun 000514 15: 06: 07] ERROR: putserver:len = 161 nr = 10 left = 161 errno = 84
#define EILSEQ          84      /* Illegal byte sequence */
#endif
      log("ERROR: putserver: len=%d nr=%d left=%d errno=%d\n", len, nr, left, errno);
    if (nr == -1) {
      switch (errno) {
      case EINTR:
        log("ERROR: putserver: Got EINTR\n");
        break;
      case EAGAIN:
        log("ERROR: putserver: Got EAGAIN\n");
        break;
      case EPIPE:
        log("ERROR: putserver: Got EPIPE\n");
        startlink();
        return;
        break;
      default:
        log("ERROR: putserver: Got errno=%d\n", errno);
        startlink();
        return;
        break;
      }
    } else {
      left -= nr;
      pos += nr;
    }
    if (retry++ > 10) {
      log("ERROR: putserver: EXIT retry = 10\n");
      exit(1);
    }
  }
  /* 
     Added just to force write one row at the time. A long result string from Q to the user, could
     result in a major flood to the irc server which reads on 8kb chucks. Bufferoverrun could
     occour, and core is dumped from the server. */
  /* 
     nr = fsync(serverlink); if(nr < 0) { log("ERROR: putserver: fsync nr=%d errno=%d\n", nr,
     errno); } */
  return;
}

char *getline(int rc)
{
  static char buffer[512 + 1];  /* one more than defined maximum length */
  int i = 0, err;
  char ch = '\0';

  if(rc) {
    /* select says we have waiting chars */
    if((inbuf_pos + inbuf_len + READ_LEN) >= MAX_INBUFLEN) {
      memcpy(inbuf, &(inbuf[inbuf_pos]), inbuf_len);
      inbuf_pos = 0;
    }
    err = read(serverlink, &(inbuf[inbuf_pos+inbuf_len]), READ_LEN);
    if (err > 0) {
      /* got a new chars */
      inbuf_len += err;
    } else if (err == 0) {
      log("ERROR: getline: err=%d errno=%d\n", err, errno);
      if (++getline_errors > 10) {
        log("ERROR: getline: Restarting serverlink err=%d errno=%d\n", err, errno);
        getline_errors = 0;
        startlink();
        buffer[0] = 0;
        return NULL;
      }
      sleep(1);
      return NULL;
    } else {
      switch (errno) {
      case EINTR:
        log("ERROR: getline: EINTR err=%d errno=%d\n", err, errno);
	return NULL;
        break;
      default:
        log("ERROR: getline: Restarting serverlink err=%d errno=%d\n", err, errno);
        perror("read");
        sleep(5);
        /* restart link to server */
        /* reloadchannels(); */
        startlink();
        buffer[0] = 0;
        return NULL;
        break;
      }
    }
  }

  i = 0;
  ch = 0;
  buffer[0] = '\0';
  while((i < 512) && (inbuf_len>0)) {
    buffer[i++] = inbuf[inbuf_pos++];
    inbuf_len--;
    ch = buffer[i-1];
    if(!ch || (ch == 13) || (ch == 10)) {
      buffer[i-1] = '\0';
      break;
    }
  }
  buffer[i] = '\0';

  while(inbuf_len > 0) {
    /* remove continious cr */
    ch = inbuf[inbuf_pos];
    if((ch == 13) || (ch == 10)) {
      inbuf_len--;
      inbuf_pos++;
    } else {
      break;
    }
  }
  return buffer;
}


/*
 * convert2y[] converts a numeric to the corresponding character.
 * The following characters are currently known to be forbidden:
 *
 * '\0' : Because we use '\0' as end of line.
 *
 * ' '  : Because parse_*() uses this as parameter seperator.
 * ':'  : Because parse_server() uses this to detect if a prefix is a
 *        numeric or a name.
 * '+'  : Because m_nick() uses this to determine if parv[6] is a
 *        umode or not.
 * '&', '#', '+', '$', '@' and '%' :
 *        Because m_message() matches these characters to detect special cases.
 */
char convert2y[NUMNICKBASE] = {
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  '[', ']'
};

u_char convert2n[NUMNICKMAXCHAR + 1] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0,
  0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 62, 0, 63, 0, 0, 0, 26, 27, 28,
  29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
  49, 50, 51
};


void startlink(void)
{
  time_t t = time(NULL);
  int port = atoi(q_port);
  struct hostent *hent;
  struct sockaddr_in server_addr;
  char *q_ip;

  burst_mode = 300;
  donewithburst = 0;
  inbuf_len = 0;
  inbuf_pos = 0;

  if (serverlink) {
    close(serverlink);
  }
  hent = gethostbyname(q_remote_server);

  serverlink = socket(AF_INET, SOCK_STREAM, 0);
  q_ip = getenv("Q_IP");
  if (q_ip)
  {
    struct sockaddr_in sa;
    memset(&sa,0,sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr(q_ip);

    if (bind(serverlink, (struct sockaddr *) &sa, sizeof(sa)))
    {
      perror("bind ip");
      log("ERROR: bind ip");
      exit(2);
    }
  }

  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(port);
  memcpy(&(server_addr.sin_addr.s_addr), hent->h_addr, hent->h_length);

  if (connect(serverlink, (struct sockaddr *) &server_addr, sizeof(server_addr))) {
    perror("connect");
    log("ERROR: startlink: connect\n");
    exit(2);
  }

  memset(servers, 0, sizeof(struct Server) * MAX_SERVERS);

  /* The bot's server info */
  add_server(q_server_name, convert2y[atoi(q_server_number) / 64],
    convert2y[atoi(q_server_number) % 64], convert2y[atoi(q_server_number) / 64],
    convert2y[atoi(q_server_number) % 64]);
  /* Stats for all servers on the network */
  add_server(q_stats_domain, 0, 0, 0, 0);

  /* Establish a server connection. */
  yxx[0] = convert2y[atoi(q_server_number) / 64];
  yxx[1] = convert2y[atoi(q_server_number) % 64];
  yxx[2] = 'A';
  yxx[3] = 'A';
  yxx[4] = 'A';
  yxx[5] = '\0';
  putserver("PASS %s\n", VIRTUAL_SERVER_PASSWORD);
  putserver("SERVER %s 1 %lu %lu J10 %c%cA]] 0 :The %s Bot\n", VIRTUAL_SERVER_NAME, t, t, yxx[0],
    yxx[1], BOT_NAME);
#if DEBUG
  printf("PASS & SERVER sent!\n");
#endif

  /* (fox) Lots of old shit removed */

  return;
}



void save_info_file(char *server)
{
  // struct activeuser *p;
  unsigned int o_mode;
  int i;
  char filename[255];
  FILE *fp;
  // ListElem *el;
  if (!server || !strlen(server))
    return;

  o_mode = 0;
  updatemodes(&o_mode, "+o");

  if (q_stats_dir) {
    sprintf(filename, "%s/%s.log", q_stats_dir, server);
    if (!(fp = fopen(filename, "w"))) {
      return;
    }
  } else {
    return;
  }

  /* 
     this must really be changed soon... These loops are just there to count opers, and could
     easily be replaced with something else */
  if (!strcmp(server, q_stats_domain)) {
    /* stats for the whole network */
    i = MAX_SERVERS;

  } else {

    /* stats for one specific server */
    for (i = 0; i < MAX_SERVERS; i++) {
      if (!strcasecmp(servers[i].name, server)) {
        /* found the server in the list */
        break;
      }
    }
    if (i >= MAX_SERVERS) {
      fclose(fp);
      return;
    }
  }
  fprintf(fp, "%d\n%d\n%s\n%s connections\n", servers[i].users, servers[i].opers,
    servers[i].uptime_str, servers[i].name);
  fclose(fp);
  return;
}

void save_server_info(char *server, int infonr, int usage_perc, int usage_opers, char *tmp)
{
  int first_unused, i;
  char *str;

  if (!q_stats_dir)
    return;

  switch (infonr) {
  case 0:
    /* Got a new server, poll server */
    putserver("%s R u %s\r\n", yxx, server);
    break;
  case 9:
#if 0
    /* 
       Used to save total information of all servers These information is collected continiously
       now. */
    i = MAX_SERVERS;
    strncpy(servers[i].name, server, HOSTLEN);
    servers[i].users = nr_users_total;
    servers[i].users_tot = nr_users_total;
    servers[i].last_tm = time(NULL);
    servers[i].uptime_str[0] = '\0';
#endif
    save_info_file(server);
    break;
  case 10:
    /* normal start of a server poll */
    first_unused = -1;
    for (i = 0; i < MAX_SERVERS; i++) {
      if ((first_unused < 0) && (strlen(servers[i].name) == 0)) {
        first_unused = i;
      }
      if (!strcasecmp(servers[i].name, server)) {
        /* found the server in the list */
        break;
      }
    }
    if (i >= MAX_SERVERS) {
      /* create a new server record, should not be done. trust on the KNOWN_SERVERS file */
      if (first_unused >= 0) {
        strncpy(servers[first_unused].name, server, HOSTLEN);
      }
      break;
    }
    /* ask for uptime */
    {
      char pyxx[YXXLEN + 1];
      if (server_to_yxx(server, pyxx)) {
        putserver("%s R u %c%c\r\n", yxx, pyxx[0], pyxx[1]);
      } else {
        /* printf("couln't find YXX for server\n"); */
      }
    }
    break;
  case 242:
    /* :irc.quake.inet.fi 242 Q :Server Up 19 days, 21:08:35\n */
    for (i = 0; i < MAX_SERVERS; i++) {
      if (!strcmp(servers[i].yxx, server)) {
        /* update old information */
#define SERVER_UP "Server Up "
        str = strstr(tmp, SERVER_UP);
        if (str) {
          strncpy(servers[i].uptime_str, &str[strlen(SERVER_UP)], UPTIMELEN);
          str = servers[i].uptime_str;
          if ((strlen(str) > 0) && (str[strlen(str) - 1] == '\n')) {
            str[strlen(str) - 1] = '\0';
          }
        }
        /* (fox) I'm not sure where i broke MRTG, but this should fix it. */
        save_info_file(servers[i].name);
        break;
      }
    }
    if (i >= MAX_SERVERS) {
      break;
    }
    /* 
       ask for user stats Should return 251 and 255 if server is ok */
    {
      /* :Q LUSERS * :C */
      /* (fox) Useless old shit i think */
/*      char pyxx[80];
      if (server_to_yxx(server, pyxx)) {
        putserver("%c%c LUSERS * %c%c\r\n", yxx[0], yxx[1], pyxx[0], pyxx[1]);
      } */
    }
    break;
  default:
    break;
  }
  return;
}


void change_channel_mode(char *sender, int dir, char *buffer)
{
  char *str;
  char channel[CHANNELLEN + 1];
  char mode[81];
  int i, pos;
  if (dir == 1) {
    str = strstr(buffer, "#");
    if (str) {
      i = 0;
      strncpy(channel, getword(str, &i), CHANNELLEN);
      pos = 0;
      while (str[pos] && str[pos] != ' ')
        pos++;
      strncpy(mode, &str[pos + 1], 80);
      if (mode[0] == '-')
        mode[0] = '+';
      putserver("%s M %s %s\n", yxx, channel, mode);
    } else {
#if DEBUG
      printf("Couldn't find the channel.\n");
#endif
    }
  } else if (dir == -1) {
    str = strstr(buffer, "#");
    if (str) {
      i = 0;
      strncpy(channel, getword(str, &i), CHANNELLEN);
      pos = 0;
      while (str[pos] && str[pos] != ' ')
        pos++;
      strncpy(mode, &str[pos + 1], 80);
      if (mode[0] == '+')
        mode[0] = '-';
      putserver("%s M %s %s\n", yxx, channel, mode);
      putserver("%s M %s +nt\n", yxx, channel);
    } else {
#if DEBUG
      printf("Couldn't find the channel.\n");
#endif
    }
  }
  return;
}

/* never used */
void set_default_chanmodes(struct chanopts *c)
{
  char modestr[80];
  if (!c)
    return;

  /* this only adds +nt to channel modes */
  if ((c->modes & (1 << ('k' - 'a'))) || (c->modes & (1 << ('l' - 'a')))) {
    return;
  }

  c->modes |= (1 << ('n' - 'a'));
  c->modes |= (1 << ('t' - 'a'));
  createmodes(c->modes, modestr);

  putserver("%s M %s %s\r\n", yxx, c->channel, modestr);
  return;
}

int clear_chanmodes(struct chanopts *c, int keep_nt)
{
  char modestr[80];
  int tmp_modes, len;
  if (!c)
    return 0;

  if (!((c->flags) & CO_LIMIT)) {
    /* never reset the key if perm-key is set */
    if (((c->modes) & (1 << ('l' - 'a')))) {
      if (c->cur_limit[0]) {
        putserver("%s M %s -l\r\n", yxx, c->channel);
        c->modes &= (~0 - (1 << ('l' - 'a')));
        c->limit[0] = '\0';
        c->cur_limit[0] = '\0';
      } else {
#if DEBUG
        printf("*** modes has key set, but no c->cur_limit string\n");
#endif
      }
    }
  }
  if (!((c->flags) & (1 << ('k' - 'a')))) {
    /* never reset the key if perm-key is set */
    if (((c->modes) & (1 << ('k' - 'a')))) {
      if (strlen(c->key)) {
        putserver("%s M %s -k %s\r\n", yxx, c->channel, c->key);
        c->modes &= (~0 - (1 << ('k' - 'a')));
        c->key[0] = '\0';
      } else {
#if DEBUG
        printf("*** modes has key set, but no c->key string\n");
#endif
      }
    }
  }

  /* remove all -mips modes. */
  tmp_modes = 0;
  tmp_modes |= ((1 << ('m' - 'a')));
  tmp_modes |= ((1 << ('i' - 'a')));
  tmp_modes |= ((1 << ('p' - 'a')));
  tmp_modes |= ((1 << ('s' - 'a')));
  createmodes(tmp_modes, modestr);
  modestr[0] = '-';
  len = strlen(modestr);
  /* add eventually +nt modes. */
  tmp_modes = c->modes;
  tmp_modes &= (~0 - (1 << ('m' - 'a')));
  tmp_modes &= (~0 - (1 << ('i' - 'a')));
  tmp_modes &= (~0 - (1 << ('p' - 'a')));
  tmp_modes &= (~0 - (1 << ('s' - 'a')));
  createmodes(tmp_modes, &modestr[len]);
  if (modestr[strlen(modestr) - 1] == '+')
    modestr[strlen(modestr) - 1] = '\0';
  updatemodes(&c->modes, modestr);

  putserver("%s M %s %s\r\n", yxx, c->channel, modestr);
  return 1;
}

void add_server(char *name, char yxx1, char yxx2, char connected_to_yxx1, char connected_to_yxx2)
{
  int nr;
  if (strcasecmp(name, q_stats_domain) == 0) {
    /* this is used for total stats for all network */
#if DEBUG
    log("add_server: [%s] (total stats)\n", name);
#endif
    nr = MAX_SERVERS;
    strncpy(servers[nr].name, name, SERVERLEN);
    servers[nr].name[SERVERLEN] = '\0';
    servers[nr].passwd[0] = '\0';
    servers[nr].parent_server[0] = 0;
    servers[nr].parent_server[1] = 0;
    servers[nr].connected = 1;
    servers[nr].opers = 0;
    servers[nr].users = 0;
    servers[nr].yxx[0] = 0;
    servers[nr].yxx[1] = 0;
    return;
  }

  if ((yxx1 > NUMNICKMAXCHAR) || (yxx2 > NUMNICKMAXCHAR)) {
    log("*** add_server: ERROR yxx=%d %d\n", (int) yxx1, (int) yxx2);
    return;
  }
  nr = (int) convert2n[(int) yxx1] * 64 + (int) convert2n[(int) yxx2];
#if DEBUG
  if (connected_to_yxx1) {
    log("add_server: [%s] yxx=%c%c nr=%d connected_to_yxx=[%c%c]\n", name, yxx1, yxx2, nr,
      connected_to_yxx1, connected_to_yxx2);
  } else {
    log("add_server: [%s] yxx=%c%c nr=%d main server\n", name, yxx1, yxx2, nr);
  }
#endif
  servers[nr].parent_server[0] = connected_to_yxx1;
  servers[nr].parent_server[1] = connected_to_yxx2;
  strncpy(servers[nr].name, name, SERVERLEN);
  servers[nr].name[SERVERLEN] = '\0';
  servers[nr].passwd[0] = '\0';
  servers[nr].connected = 1;
  servers[nr].opers = 0;
  servers[nr].users = 0;
  servers[nr].yxx[0] = yxx1;
  servers[nr].yxx[1] = yxx2;
  burst_mode = 60;
  return;
}

int del_server(char *name)
{
  int nr, i;
  char yxx[2];
  struct activeuser *u;
  ListElem *el, *el2;
  int removed;
  for (i = 0; i < MAX_SERVERS; i++) {
    if (!strcasecmp(name, servers[i].name)) {
      yxx[0] = servers[i].yxx[0];
      yxx[1] = servers[i].yxx[1];
      break;
    }
  }
  if (i == MAX_SERVERS) {
    log("del_server: ERROR: can't find %s\n", name);
    return 0;
  }
  if ((yxx[0] > NUMNICKMAXCHAR) || (yxx[1] > NUMNICKMAXCHAR)) {
#if DEBUG
    log("del_server: ERROR yxx=%c%c\n", yxx[0], yxx[1]);
#endif
    return 0;
  }
  nr = (int) convert2n[(int) yxx[0]] * 64 + (int) convert2n[(int) yxx[1]];

  if (servers[nr].connected) {
#if DEBUG
    log("del_server: yxx=%c%c nr=%d [%s]\n", yxx[0], yxx[1], nr, name);
//    printf("del_server: yxx=%c%c nr=%d [%s]\n", yxx[0], yxx[1], nr, name);
#endif
    removed = 0;

    el = GetHead(auser_list);
    while (el) {
      u = (struct activeuser *) el->data;
      if ((u->yxx[0] == yxx[0]) && (u->yxx[1] == yxx[1])) {
        el2 = GetSucc(el);
        disconnect_auser(u);
        removed++;
        el = el2;
      } else {
        el = GetSucc(el);
      }
    }
#if DEBUG
    log("del_server: %d users removed\n", removed);
#endif
    servers[nr].connected = 0;

    for (i = 0; i < MAX_SERVERS; i++) {
      if ((servers[nr].yxx[0] == servers[i].parent_server[0])
        && (servers[nr].yxx[1] == servers[i].parent_server[1])) {
        /* 
           recursive deletion */
        del_server(servers[i].name);
      }
    }

    memset(&servers[nr], 0, sizeof(struct Server));
    return 1;
  } else {
#if 0
    log("del_server: not connected yxx=%c nr=%d\n", yxx, nr);
#endif
  }
  return 0;
}


int server_to_yxx(char *name, char *pyxx)
{
  int i;
  for (i = 0; i < MAX_SERVERS; i++) {
    if (!strcasecmp(name, servers[i].name)) {
      *pyxx = servers[i].yxx[0];
      *(pyxx + 1) = servers[i].yxx[1];
      return 1;
    }
  }
  return 0;
}


void add_userid_history(struct activeuser *u, int userid)
{
#ifdef USERID_HISTORY_LEN
  int i, j, len;
  struct dbaseuser *d_tmp;

  for (i = 0; i < USERID_HISTORY_LEN; i++) {
    if (u->userid_history[i] == 0) {
      /* save the userid if position not used before */
      u->userid_history[i] = userid;

      if (i >= (authid_history_trigger - 1)) {
        char tmp[500];
        len =
          sprintf(tmp, "Warning: Multiple auth from [%s!%s@%s] USERNAME:", u->nick, u->username,
          u->hostname);
        for (j = 0; j <= i; j++) {
          d_tmp = finduserfromid(u->userid_history[j]);
          if (d_tmp)
            len += sprintf(&tmp[len], " %s", d_tmp->nick);
        }
        wallop(tmp);
        len += sprintf(&tmp[len], "\n");
#ifdef DEBUG
        log(tmp);
#endif
      }
      break;
    } else if (u->userid_history[i] == userid) {
      /* known userid */
      break;
    }
  }
#endif
  return;
}





void parse(char *buffer)
{
  char tmp1[1024];
  int tmp1l;
  struct activeuser *u;
  int i, j;
  int command_len;
  int len;
  int server_pass = 0;
  int offset;

  if ((buffer == NULL) || !*buffer)
    return;
  for (i = 0; i < MAX_ARGS; i++) {
    args[i][0] = 0;
  }
  argcount = 0;
  sender[0] = 0;
  sender_au = NULL;
  command[0] = 0;
  what[0] = 0;

  /* Skip over any leading spaces. */
  for (i = 0; buffer[i] && buffer[i] == ' '; i++);
  /* 
   * "i" had better increase monatomically from here on, or we're in
   * a lot of trouble. :)
   */
  /* Check to see who sent it. */
  if (buffer[i] == ':') {
    struct activeuser *autmp;
    i++;
    j = 0;
    for (; buffer[i] && (buffer[i] != ' '); i++) {
      sender[j] = buffer[i];
      j++;
    }
    sender[j] = 0;
    /* We will have to convert sender to a numeric! */
    autmp = findauser(sender);
    if (autmp == NULL) {        /* It's a server, not a client */
      int i;
      int found = 0;
      for (i = 0; i < MAX_SERVERS; i++) {
        if (servers[i].connected) {
          if (strcmp(servers[i].name, sender) == 0) {
            sprintf(sender, "%c%c", servers[i].yxx[0], servers[i].yxx[1]);
            found = 1;
            break;
          }
        }
      }
      if (!found) {
        printf("Could not translate nick to numeric! [%s]\n", buffer);
        return;
      }
    } else {
      strcpy(sender, autmp->yxx);
    }
  } else {
    j = 0;
    for (; buffer[i + j] && (buffer[i + j] != ' '); j++) {
      sender[j] = buffer[i + j];
    }
    i += j;
    sender[j] = 0;
    /* convert new 2.10.10 server id's */
    /* (fox) No, no messy workarounds, lets handle this RIGHT */
    len = strlen(sender);
    if (len == 3) {             /* We have an old 2.10.07 clientnumeric */
      /* User YXX */
      sender[4] = sender[2];
      sender[3] = sender[1];
      sender[1] = sender[0];
      sender[0] = 'A';
      sender[2] = 'A';
      sender[5] = '\0';
    } else if (len == 1) {      /* Old 2.10.07 servernumeric */
      /* Server YXX */
      sender[1] = sender[0];
      sender[0] = 'A';
      sender[2] = '\0';
    } else if ((len != 5) && (len != 2)) {
      if ((!strcmp(sender, "SERVER")) || (!strcmp(sender, "PASS"))) {
        server_pass = 1;
      } else {
        log("parse: Unknown yxx [%s]\n", sender);
        sender[1] = sender[0];
        sender[0] = 'A';
        sender[2] = '\0';
      }
    }
  }

  if (!buffer[i]) {
    // should not happen
    return;
  }
  /* Skip over leading spaces */
  while ((buffer[i] != '\0') && (buffer[i] == ' ')) { i++; }
  j = 0;
  if (server_pass) { // We either got PASS or SERVER from our uplink
    strcpy(command, sender);
    command_len = strlen(command);
    strcpy(sender, "$UPL$"); // $UPL$ is not a valid numeric of course, we use
                             // it to mark it is our uplink
  } else {
    while ((buffer[i + j]) && !isspace((int) buffer[i + j]) && j < 512)
      j++;
    memcpy(command, buffer + i, j);
    command[j] = 0;
    command_len = j;
  }

  irclog(DEBUG_ALL, "DUMP: [%s] [%s]\n", sender, &buffer[i]);
  /* 
   * Good news, we got the command and the sender (probably).
   * Now all we need is the parameters and we're in business. :)
   */

  // quick fix to put command into first argument
  strcpy(args[argcount++], sender);
  strcpy(args[argcount++], command);

  i = i + j;
  while ((buffer[i]) && (buffer[i] != '\n') && (buffer[i] != '\r')) {
    while (buffer[i] == ' ')
      i++;                      /* Again, do a standard spaces-skip job. */
    j = 0;
    if (buffer[i] == ':') {
      i++;                      /* Skip over the : */
      while ((buffer[i] != 0) && (buffer[i] != '\n') && (buffer[i] != '\r')) {
        args[argcount][j] = buffer[i];
        i++;                    /* Next char in both the source */
        j++;                    /* and the destination strings  */
      }
      args[argcount][j] = 0;
    } else {
      while (buffer[i] && (buffer[i] != '\n') && (buffer[i] != '\r') && (buffer[i] != ' ')) {
        args[argcount][j++] = buffer[i++];
      }
      args[argcount][j] = 0;
    }
    argcount++;
  }

#if 0
  /* DEBUG2 */
  /* print ALL server messages */
  printf("Parsing: [%s] [%s] :  ", sender, command);
  for (i = 0; i < argcount; i++) {
    printf("[%s] ", args[i]);
  }
  printf("\n");
#endif

  /* First we'll handle the commands where it is not important if they came
     from a server or from a user, or where only one is possible anyways */
  if (!strcmp(command, "219")) {
    /* [219 CServe.quakenet.org u :End of /STATS report */
  } else if (!strcmp(command, "242")) {
    /* :irc.quake.inet.fi 242 Q :Server Up 19 days, 21:08:35\n */
    save_server_info(sender, 242, 0, 0, buffer);
  } else if (!strcmp(command, "250")) {
    /* [250 CServe.quakenet.org :Highest connection count: 2 (1 clients) */
  } else if (!strcmp(command, "255")) {
    /* :irc.quake.inet.fi 255 Q :I have 56 clients and 1 servers\n */
    save_server_info(sender, 255, 0, 0, buffer);
  } else if (!strcmp(command, "401")) {
    return;
  } else if (!strcmp(command, "403")) {
    return;
  } else if ((!strcmp(command, "SERVER")) && (!strcmp(sender, "$UPL$"))) {
    /* Our uplink-server is introducing itself */
    s_server();
    save_server_info(sender, 0,0,0,buffer);
    return;
  } else if (!strcmp(command, "PASS")) {
    /* ignore the password */
    return;
  } else if (!strcmp(command, "O") || !strcmp(command, "NOTICE")) {
    /* we don't care about notices sent to us */
    return;
  } else if (!strcmp(command, "SQUIT") || !strcmp(command, "SQ")) {
    del_server(args[2]);
    return;
  } else if ((!strcmp(command, "R")) || (!strcmp(command, "STATS"))) {
    s_handlestats();
    return;
  } else if (!strcmp(command, "D") || !strcmp(command, "KILL")) {
    /* If Uworld kills a user, or nickcollide occours, then we get this nasty message... */
    if (!strcmp(args[2],yxx)) {
      s_kill();
    } else {
      struct activeuser *u;
      u=find_yxx(args[2]);
      if (u) { disconnect_auser(u); }
    }
    return;
  } else if (!strcmp(command, "M") || !strcmp(command, "MODE")) {
     s_mode();
     return;
  } else if ((!strcmp(command, "WA")) || (!strcmp(command, "WALLOPS"))) {
    /* we don't give a fuck about wallops */
    return;
  } else {
    if (sender[2] == 0) {
      /* A command was received from a server */
#if 0
      if (debuglog >= DEBUG_ALL) {
        /* print ALL server messages */
        tmp1l = 0;
        tmp1l += sprintf(&tmp1[tmp1l], "SParsing: [%s] [%s]%d:  ", sender, command, command_len);
        for (i = 0; i < argcount; i++) {
          tmp1l += sprintf(&tmp1[tmp1l], "[%s] ", args[i]);
        }
        tmp1l += sprintf(&tmp1[tmp1l], "%s", "\n");
        irclog_(DEBUG_ALL, tmp1);
      }
#endif
      if (!strcmp(command, "N") || !strcmp(command, "NICK")) {
        s_handlenicks(buffer);
        return;
      } else if (!strcmp(command, "B") || !strcmp(command, "BURST")) {
        s_handleburst(buffer);
        return;
      } else if (!strcmp(command, "EB") || !strcmp(command, "END_OF_BURST")) {
        int nr1 = atoi(q_server_number);
        log("END_OF_BURST command=[%s] nr1=%d parent=%c%c\n", command, nr1,
          servers[nr1].parent_server[0], servers[nr1].parent_server[1]);
        if ((sender[0] == servers[nr1].parent_server[0])
          && (sender[1] == servers[nr1].parent_server[1])) {
          log("Send EA\n");
          putserver("%c%c EA\r\n", yxx[0], yxx[1]);
          donewithburst = 1;
          /* Join all channels and hack modes now */
          join_all_channels(1);
          /* update all keys and limit after joining all channels */
          update_keys_limits();
        }
        return;
      } else if (!strcmp(command, "EA") || !strcmp(command, "EOB_ACK")) {
        int nr1 = atoi(q_server_number);
        if ((sender[0] == servers[nr1].parent_server[0])
          && (sender[1] == servers[nr1].parent_server[1])) {
          burst_mode = 0;
        }
        return;
      } else if (!strcmp(command, "S") || !strcmp(command, "SERVER")) {
        if (strlen(args[7]) == 3) {
          add_server(args[2], 'A', args[7][0], sender[0], sender[1]);
        } else {
          add_server(args[2], args[7][0], args[7][1], sender[0], sender[1]);
        }
        save_server_info(sender, 0,0,0,buffer);
        return;
      } else if (!strcmp(command, "G") || !strcmp(command, "PING")) {
        s_ping();
        return;
      } else if ((!strcmp(command, "K")) || (!strcmp(command, "KICK"))) {
        s_kick();
        return;
      } else if ((!strcmp(command, "GL")) || (!strcmp(command, "GLINE"))) {
        s_gline(buffer);
        return;
      } else if ((!strcmp(command, "SE")) || (!strcmp(command, "SETTIME"))) {
        /* We really should handle settime, else we can get into trouble */
        return;
      }
#if DEBUG
      printf("*** unknown server command from [%s] [%s] [%s]\n", sender, command, buffer);
      log("*** unknown server command from [%s] [%s] [%s]\n", sender, command, buffer);
#endif
      return;
    } else {
      /* Those are commands that come from a Usernumeric */
      if (debuglog >= DEBUG_ALL) {
        /* print ALL server messages */
        tmp1l = 0;
        tmp1l += sprintf(&tmp1[tmp1l], "CParsing: [%s] [%s]%d:  ", sender, command, command_len);
        for (i = 0; i < argcount; i++) {
          tmp1l += sprintf(&tmp1[tmp1l], "[%s] ", args[i]);
        }
        tmp1l += sprintf(&tmp1[tmp1l], "%s", "\n");
        irclog_(DEBUG_ALL, tmp1);
      }
      if ((u = find_yxx(sender))) {
        offset = 1;
        if (!strcmp(command, "N") || !strcmp(command, "NICK")) {
          s_handlenickc(buffer,u);
          return;
        } else if (!strcmp(command, "J") || !strcmp(command, "JOIN")) {
          s_join();
          return;
        } else if (!strcmp(command, "C") || !strcmp(command, "CREATE")) {
          s_join();
          return;
        } else if (!strcmp(command, "L") || !strcmp(command, "PART")) {
          s_part();
          return;
        } else if (!strcmp(command, "P") || !strcmp(command, "PRIVMSG")) {
          s_privmsg();
          return;
        } else if (!strcmp(command, "T") || !strcmp(command, "TOPIC")) {
          s_topic(buffer);
          return;
        } else if (!strcmp(command, "Q") || !strcmp(command, "QUIT")) {
          s_quit();
          return;
        } else if (!strcmp(command, "V") || !strcmp(command, "VERSION")) {
          s_version();
          return;
        } else if (!strcmp(command, "O") || !strcmp(command, "NOTICE")) {
          /* We don't care about notices */
          return;
        } else if (!strcmp(command, "K") || !strcmp(command, "KICK")) {
          s_kick();
          return;
        } else if (!strcmp(command, "A") || !strcmp(command, "AWAY")) {
          /* Seem to be away message */
          return;
        } else {
#if DEBUG
          log("Warning: *** Parse: unknown user-command [%s] [%s]\n", command, args[offset + 0]);
#endif
        }
      }
#if DEBUG
      log("Parse: Unknown servercommand id. [%s] [%s] [%s]\n", command, args[1], buffer);
#endif
      return;
    }
  }
  return;
}

void write_pidfile()
{
  int fd;
  char buff[30];
  char file[256];
  sprintf(file, "%s/q.pid", q_database);
  if ((fd = open(file, O_CREAT | O_WRONLY, 0600)) >= 0) {
    bzero(buff, sizeof(buff));
    sprintf(buff, "%5d\n", (int) getpid());
    if (write(fd, buff, strlen(buff)) == -1)
      fprintf(stderr, "Error writing to pid file %s", file);
    close(fd);
    return;
  }
  return;
}

void init_env(void)
{
  /* (fox) Uhm - correct me if I'm wrong, but according to my docs, repeated
     calls to getenv might overwrite previous results, so this is BAD */
  BOT_NAME = getenv("Q_NAME");
  SENDMAIL = getenv("SENDMAIL");
  q_remote_server = getenv("Q_REMOTE_SERVER");
  q_port = getenv("Q_PORT");
  q_database = getenv("Q_DATABASE");
  q_log = getenv("Q_LOG");
  q_server_number = getenv("Q_SERVER_NUMBER");
  q_server_name = getenv("Q_SERVER_NAME");
  q_server_passwd = getenv("Q_SERVER_PASSWD");
  q_stats_dir = getenv("Q_STATS_DIR");
  q_stats_domain = getenv("Q_STATS_DOMAIN");
  q_commands_dir = getenv("Q_COMMANDS_DIR");

  if (!q_remote_server) {
    fprintf(stderr, "Q_REMOTE_SERVER not defined\n");
    exit(1);
  }
  if (!q_port) {
    fprintf(stderr, "Q_PORT not defined\n");
    exit(1);
  }
  if (!q_commands_dir) {
    fprintf(stderr, "Q_COMMANDS_DIR not defined\n");
    exit(1);
  }
  if (!BOT_NAME) {
    fprintf(stderr, "Q_NAME not defined\n");
    exit(1);
  }
  if (!SENDMAIL) {
    fprintf(stderr, "SENDMAIL not defined\n");
    SENDMAIL = strdup("/usr/sbin/sendmail");
  }
  if (!q_database) {
    fprintf(stderr, "Q_DATABASE not defined\n");
    exit(1);
  }
  USER_DBASE_FILE = malloc(strlen(q_database) + strlen("users") + 2);
  sprintf(USER_DBASE_FILE, "%s/users", q_database);
  CHANTYPE_DBASE_FILE = malloc(strlen(q_database) + strlen("chantype") + 2);
  sprintf(CHANTYPE_DBASE_FILE, "%s/chantype", q_database);
  CHANNEL_DBASE_FILE = malloc(strlen(q_database) + strlen("channels") + 2);
  sprintf(CHANNEL_DBASE_FILE, "%s/channels", q_database);
  GLINE_DBASE_FILE = malloc(strlen(q_database) + strlen("gline") + 2);
  sprintf(GLINE_DBASE_FILE, "%s/gline", q_database);
  TRUSTED_DBASE_FILE = malloc(strlen(q_database) + strlen("trusted") + 2);
  sprintf(TRUSTED_DBASE_FILE, "%s/trusted", q_database);
  COMMANDS_FILE = malloc(strlen(q_database) + strlen("commands") + 2);
  sprintf(COMMANDS_FILE, "Commands");
  MAX_USERS_FILE = malloc(strlen(q_database) + strlen("max_users") + 2);
  sprintf(MAX_USERS_FILE, "%s/max_users", q_database);

  if (!q_log) {
    fprintf(stderr, "Q_LOG not defined\n");
    exit(1);
  }
  DEBUG_LOGPATH = malloc(strlen(q_log) + strlen("qdebug.log") + 2);
  sprintf(DEBUG_LOGPATH, "%s/qdebug.log", q_log);
  LOGPATH = malloc(strlen(q_log) + strlen("qlog.log") + 2);
  sprintf(LOGPATH, "%s/qlog.log", q_log);
  LOGPATH_MALLOC = malloc(strlen(q_log) + strlen("malloc.log") + 2);
  sprintf(LOGPATH_MALLOC, "%s/malloc.log", q_log);

  if (!q_server_number) {
    fprintf(stderr, "Q_SERVER_NUMBER not defined\n");
    exit(1);
  }
  if (!q_server_name) {
    fprintf(stderr, "Q_SERVER_NAME not defined\n");
    exit(1);
  }
  VIRTUAL_SERVER_NAME = q_server_name;
  BOT_HOSTNAME = q_server_name;
  if (!q_server_passwd) {
    fprintf(stderr, "Q_SERVER_PASSWD not defined\n");
    exit(1);
  }
  VIRTUAL_SERVER_PASSWORD = q_server_passwd;

  if (!q_stats_dir) {
    fprintf(stderr, "Q_STATS_DIR not defined\n");
  }
  if (!q_stats_domain) {
    fprintf(stderr, "Q_STATS_DOMAIN not defined\n");
    exit(1);
  }
#ifdef DOSQLLOGGING
  if ((getenv("Q_SQLHOST")==NULL) || (getenv("Q_SQLUSER")==NULL) ||
      (getenv("Q_SQLPASS")==NULL) || (getenv("Q_SQLDB")==NULL) ||
      (getenv("Q_SQLLOGTABLE")==NULL) || (getenv("Q_SQLPORT")==NULL)) {
        fprintf(stderr, "SQL-Logging is enabled, but at least one of the SQL variables (Q_SQL* where *=(HOST,USER,PASS,DB,LOGTABLE,PORT)) is not set.\n");
        exit(1);
  }
  sqlhost=malloc(strlen(getenv("Q_SQLHOST"))+1);
  sqlusername=malloc(strlen(getenv("Q_SQLUSER"))+1);
  sqlpassword=malloc(strlen(getenv("Q_SQLPASS"))+1);
  sqldb=malloc(strlen(getenv("Q_SQLDB"))+1);
  sqllogtable=malloc(strlen(getenv("Q_SQLLOGTABLE"))+1);
  sqlport=strtol(getenv("Q_SQLPORT"),NULL,10);
  strcpy(sqlhost,getenv("Q_SQLHOST"));
  strcpy(sqlusername,getenv("Q_SQLUSER"));
  strcpy(sqlpassword,getenv("Q_SQLPASS"));
  strcpy(sqldb,getenv("Q_SQLDB"));
  strcpy(sqllogtable,getenv("Q_SQLLOGTABLE"));
#endif
  return;
}

void program_info()
{
  fprintf(stderr, "This program was made for ircu2.10.10 and is used at\n");
  fprintf(stderr, "QuakeNet (http://www.quakenet.eu.org/)\n");
  fprintf(stderr, "Main function is\n  channel service for Quake clans\n");
  fprintf(stderr, "  generate serverstats for mrtg\n");
  fprintf(stderr, "  remove users from illegal channels\n");
  fprintf(stderr, "  operator tool to replace Uworld etc.\n");
  fprintf(stderr,
    "\nOriginally written 1998 by MadHacker, James Craig, jcraig@mad.scientist.com\n");
  fprintf(stderr,
    "Development after Sep 1999 by Mag, Christian Magnusson, d1mag@dtek.chalmers.se\n");
  fprintf(stderr, "Development after Apr 2001 by QuakeNet dev-com, dev-com@quakenet.eu.org\n");
  fprintf(stderr, "Version %d.%d\n", VERSION_MAJOR, VERSION_MINOR);
  return;
}

void usage(char *prg)
{
  program_info();
  fprintf(stderr, "Usage:\n");
  fprintf(stderr, "Normally started without any parameters. There are some parameters to\n");
  fprintf(stderr, "show some stats from the user database though.\n");
  fprintf(stderr, "-h\tUsage\n");
  fprintf(stderr, "-l\tlist users (combined with -f -t)\n");
  fprintf(stderr, "-p\tprint users only\n");
  fprintf(stderr, "-c\tlist unused channels\n");
  fprintf(stderr, "-d\tdelete users (not implemented)\n");
  fprintf(stderr, "-f xx\tfrom xx days old\n");
  fprintf(stderr, "-t xx\tto xx days old\n");
  fprintf(stderr, "-v\tshow version string\n");
  fprintf(stderr, "\n");
  exit(0);
}

void print_channel_few(void);
void print_channel_unused(void);
void print_users_only(void);

/* (fox) Doing all this in a signal-handler? Not a good IMHO. Will have to fix
   this later. Also, signal() should not be used, use sigaction() instead. 
   (Explanation, from GNU C docs and university: While the signalhandler is
   running, the action set for the signal is SIG_DFL. In other words, if you
   kill -1, the bot starts unloading, saving things etc. You're impatient,
   maybe think the signal got lost, so you kill -1 again, and *whoops* program
   gets killed, maybe in the middle of a save, which could seriously fuck
   things up)
*/
void signal_sighup(int sig)
{
  strcpy(reload_command_name, "all");
  reload_command = 1;
  sighup_received = 1;
  signal(SIGHUP, signal_sighup);
  return;
}

void signal_chld(int sig)
{
  int stat;
  int rc;
  /* 
     char buf[256]; */

  while ((rc = waitpid(-1, &stat, WNOHANG)) > 0) {
#if 0
    sprintf(buf, "pid: %d\n", rc);
    write(2, buf, strlen(buf));
#endif
    /* 
       log("Got SIGCLD: pid=%d\n", rc); */
  }
#if 0
  if (rc < 0) {
    char *p = strerror(errno);
    write(2, p, strlen(p));
  }
#endif
  signal(SIGCHLD, signal_chld);
  return;
}

#if 0
static void inc_channel2(struct chan_stat *list, char *channel)
{
  struct chan_stat *c, *lc, *newc;
  lc = list;
  c = list->next;
  while (c) {
    if (strcasecmp(c->channel, channel) == 0) {
      c->count++;
      return;
    }
    lc = c;
    c = c->next;
  }
  newc = (struct chan_stat *) Malloc(sizeof(struct chan_stat));
  if (newc) {
    strncpy(newc->channel, channel, CHANNELLEN);
    newc->channel[CHANNELLEN] = '\0';
    newc->count = 1;
    newc->next = NULL;
    lc->next = newc;
  }
  return;
}
#endif

int main(int argc, char **argv)
{
  struct stat st;
  int rc, i, ret, sockstream;
  fd_set r;
  struct sockaddr_un sa;
  struct timeval tm, t_tmp, t_next, t_nick, t_trust, t_limit, t_save;
  long intervall = 150L;
  long from_age = 0L;
  long to_age = 0L;
  int print_users = 0;
  int list_users = 0;
  int delete_users = 0;
  struct tm *tm_from, *tm_to;
  time_t tnow;
  char tmp[512 + 1];
  char *pt;
  char *pt2;
  int errarg = 0;
  int list_channel_unused = 0;
  int list_channel_few = 0;
  struct gline *g;
  struct trusted *tr;
  struct chanopts *c;
  ListElem *el, *el2;

  nr_users_total = nr_opers_total = 0;
  for (i = 1; i < argc; i++) {
    if (i < argc) {
      errarg = 0;
      pt = argv[i];
      if (*pt != '-') {
        usage(*argv);
      }
      pt++;
      pt2 = pt;
      while (isalpha((int) *pt2)) {
        pt2++;
      }
      if (!strcmp(pt, "f")) {
        i++;
        if (i >= argc)
          usage(*argv);
        from_age = atol(argv[i]) * 3600 * 24;
        if (!from_age)
          from_age = 1;
        printf("From age = %ld\n", from_age);
        errarg = 1;
      }
      if (!strcmp(pt, "t")) {
        i++;
        if (i >= argc)
          usage(*argv);
        to_age = atol(argv[i]) * 3600 * 24;
        if (!to_age)
          to_age = 1;
        printf("To age = %ld\n", to_age);
        errarg = 1;
      }
      if (!strcmp(pt, "p")) {
        print_users = 1;
        errarg = 1;
      }
      if (!strcmp(pt, "u")) {
        list_users = 1;
        errarg = 1;
      }
      if (!strcmp(pt, "c")) {
        list_channel_unused = 1;
        errarg = 1;
      }
      if (!strcmp(pt, "x")) {
        convert_database = 1;
        errarg = 1;
      }
      if (!strcmp(pt, "b")) {
        list_channel_few = 1;
        errarg = 1;
      }
      if (!strcmp(pt, "d")) {
        delete_users = 1;
        errarg = 1;
      }
      if (!strcmp(pt, "v")) {
        program_info();
        exit(0);
      }
      if (!strcmp(pt, "h")) {
        usage(*argv);
        exit(0);
      }
      if (errarg == 0) {
        printf("Unknown argument '%s'\n", pt - 1);
        usage(*argv);
      }
    }
  }
  if (from_age) {
    tnow = time(NULL);
    tnow -= from_age;
    from_age = tnow;
    tm_from = localtime(&from_age);
    strftime(tmp, 60, "From time: %c\n", tm_from);
    printf("%s", tmp);
  }
  if (to_age) {
    tnow = time(NULL);
    tnow -= to_age;
    to_age = tnow;
    tm_to = localtime(&to_age);
    strftime(tmp, 60, "To time: %c\n", tm_to);
    printf("%s", tmp);
  }
  signal(SIGWINCH, SIG_IGN);
  signal(SIGCHLD, signal_chld);

  if (1) {
    char *str;
    str = crypt_r("test", "aa");
  }

  init_env();

  /* Create hashtables for fast search in the databases. */
  auser_yxx = NewHashTable(AUSER_HASH_LEN, H_AUSER_YXX);
  auser_nick = NewHashTable(AUSER_HASH_LEN, H_AUSER_NICK);
  channels = NewHashTable(CHANNEL_HASH_LEN, H_CHANNEL_NAME);
  dbusers_nick = NewHashTable(DBUSER_HASH_LEN, H_DBUSER_NICK);
  dbusers_lastyxx = NewHashTable(DBUSER_HASH_LEN, H_DBUSER_YXX);
  dbusers_userid = NewHashTable(DBUSER_HASH_LEN, H_DBUSER_USERID);
  commands = NewHashTable(COMMAND_HASH_LEN, H_COMMAND_NAME);
  auser_list = NewList(L_AUSER);
  dbuser_list = NewList(L_DBUSER);
  channel_list = NewList(L_CHANNEL);
  chantype_list = NewList(L_CHANTYPE);
  nickchange_list = NewList(L_NICKCHANGE);
  trusted_list = NewList(L_TRUSTED);

  gline_list = NewList(L_GLINE);

  if (!load_chantypelist()) {
    printf("Couldn't load chantypelist\n");
    exit(1);
  }
  if (!load_max_users()) {
    printf("Couldn't load max_users\n");
    exit(1);
  }
  if (!loadudbase()) {
    printf("Couldn't load userdatabase\n");
    exit(1);
  }
  if (print_users) {
    print_users_only();
    exit(0);
  }
  if (list_users) {
    print_userlist(from_age, to_age);
    exit(0);
  }
  if (delete_users) {
    printf("Not implemented yet.. use CLEANUPDB\n");
    exit(0);
  }

  if (!loadchannellist()) {
    printf("Couldn't load channeldatabase\n");
    exit(1);
  }
  if (!loadglinelist()) {
    printf("Couldn't load G-line database\n");
    exit(1);
  }
  if (!loadtrustedlist()) {
    printf("Couldn't load Trusted database\n");
    exit(1);
  }
  if (sqlqinit()!=0) {
    printf("Failed to init MySQL\n");
    exit(1);
  }
  if (list_channel_unused) {
    print_channel_unused();
    exit(0);
  }
  if (list_channel_few) {
    print_channel_few();
    exit(0);
  }
  if (convert_database) {
    saveudbase();
    printf("User database saved\n");
    /* 
       if(saveudbase()) { printf("Couldn't save channeldatabase\n"); } else {
       printf("Channeldatabase saved\n"); } */
    exit(0);
  }

  write_pidfile();

  /* 
     Open a sock-stream to listen for messages from children */
  {
    int last_slash = 0;
    for (i = 0; i < strlen(argv[0]); i++) {
      if (argv[0][i] == '/')
        last_slash = i + 1;
    }
    sprintf(sock_name, "/tmp/.%s", &argv[0][last_slash]);
  }

  rc = stat(sock_name, &st);
  if (rc == -1) {
    if (errno == ENOENT) {
      /* socket does not exist... good... */
    } else {
      log("stat failed: %s", strerror(errno));
      exit(1);
    }
  } else {
    if (S_ISSOCK(st.st_mode))
      unlink(sock_name);
  }

  sockstream = socket(PF_UNIX, SOCK_STREAM, 0);

  sa.sun_family = AF_UNIX;
  strcpy(sa.sun_path, sock_name);
  if (bind(sockstream, (struct sockaddr *) &sa, sizeof(struct sockaddr_un)) < 0) {
    log("bind failed: %s\n", strerror(errno));
    printf("bind failed: %s\n", strerror(errno));
    exit(1);
  }

  if (listen(sockstream, 5) < 0) {
    log("listen failed: %s", strerror(errno));
    exit(1);
  }

  log("Starting TheQBot Version %d.%d\n", VERSION_MAJOR, VERSION_MINOR);
  log("%s\n", BOT_WHOIS);

  if (!init_dllib()) {
    exit(1);
  }
  signal(SIGHUP, signal_sighup);

  /* 
     Connect to the net and join all registrated channels */
  startlink();                  /* in s_server() Q joins all channels. */

  startup_time = time(NULL);
  gettimeofday(&t_next, NULL);
  t_next.tv_sec += intervall;

  gettimeofday(&t_limit, NULL);
  gettimeofday(&t_nick, NULL);
  gettimeofday(&t_trust, NULL);
  gettimeofday(&t_save, NULL);
  t_limit.tv_sec += 30;
  t_nick.tv_sec += 1;
  t_trust.tv_sec += 1;
  t_save.tv_sec += SEC_BEFORE_SAVE;
  
  ourmsghost=malloc(strlen(BOT_NAME)+strlen(VIRTUAL_SERVER_NAME)+2);
  if (ourmsghost==NULL) {
    printf("out of memory.\n"); exit(1);
  }
  strcpy(ourmsghost,BOT_NAME);
  strcat(ourmsghost,"@");
  strcat(ourmsghost,VIRTUAL_SERVER_NAME);

  /* Main loop */
  while (1) {
    FD_ZERO(&r);
    FD_SET(sockstream, &r);

    /* Wait quietly for something to happen. */
    /* 
     * This form of waiting is like a sleep() so it's not CPU-heavy.
     * Note that the way this is written, if the server doesn't send
     * anything and doesn't close the link, the bot will do exactly
     * nothing.
     */
    rc = 0;
    if(inbuf_len < 512) {
      FD_SET(serverlink, &r);
      tm.tv_usec = 100000;
      tm.tv_sec = 0;
      /* perhaps starvation on sockstream, but who cares */
      rc = select(MAX(serverlink, sockstream) + 1, &r, NULL, NULL, &tm);
    }

    if ((rc>=0) || (inbuf_len>0)) {
      if (FD_ISSET(serverlink, &r) || (inbuf_len>0)) {
        /* 
         * Parse whatever the server just said.
         */
        parse(getline(FD_ISSET(serverlink, &r) > 0));
      } else if (FD_ISSET(sockstream, &r)) {
        /* 
         * Parse message from the sockstream
         */
        struct sockaddr_un sa;
        int sz = sizeof(struct sockaddr_un);
        int t;

        if ((t = accept(sockstream, (struct sockaddr *) &sa, &sz)) < 0) {
          if (errno == EINTR) {
            log("main: accept failed (EINTR): %s", strerror(errno));
            continue;
          }
          log("main: accept failed: %s", strerror(errno));
          close(sockstream);
          exit(1);
        } else {
          char host[256];
          memset(host, 0, sizeof(host));
          rc = read(t, host, sizeof(host));
          if (rc <= 0) {
            log("main: Error reading host\n");
          } else {
            /* (fox) Sockcheck-remainings deleted */
          }
        }
        close(t);
      }
    }
#if DEBUG
    else if (rc == -1) {
      if (errno != EINTR) {
        log("Warning: newq: select() errno=%d\n", errno);
      } else {
        /* log("newq: select == EINTR\n"); */
      }
    }
#endif

    if(sighup_received) {
      /* nobody should get any output from say_sender */
      log("Got SIGHUP\n");
      sighup_received = 0;
      sender_au = NULL;
    }

    if (unload_one_command) {
      unload_one_command = 0;
      ret = unload_command(reload_command_name);
      if (ret < 0) {
        say_sender("Failed to unload module.");
      } else if (ret == 0) {
        say_sender("Module wasn't loaded before.");
      } else {
        say_sender("Module unloaded.");
      }
    }
    if (reload_command) {
      reload_command = 0;
      if (strcasecmp(reload_command_name, "all") == 0) {
        unload_command(NULL);
        init_dllib();
        say_sender("All modules reloaded.");
      } else {
        ret = unload_command(reload_command_name);
        if (ret < 0) {
          say_sender("Failed to unload module.");
        } else {
          if (ret == 0) {
            say_sender("Module wasn't loaded before.");
          } else {
            say_sender("Module unloaded.");
          }
          if (load_command(reload_command_name)) {
            say_sender("Module loaded.");
          } else {
            say_sender("Failed to reload module.");
          }
        }
      }
    }

    gettimeofday(&t_tmp, NULL);
    if (t_tmp.tv_sec >= t_nick.tv_sec) {
      t_nick.tv_sec += 1;

      if (burst_mode > 0)
        burst_mode--;

    }
    if (t_tmp.tv_sec >= t_limit.tv_sec) {
      int l;
      t_limit.tv_sec += 10;
      tnow = time(NULL);
      el = GetHead(channel_list);
      while (el) {
        c = (struct chanopts *) el->data;
        if ((c->flags & CO_CHANLIMIT) && c->bot_joined &&
	    (tnow > c->limit_next_check)) {
          l = c->limit_last_joined;
          if (c->modes & (1 << ('l' - 'a'))) {
            if (sscanf(c->cur_limit, "%d", &i) == 1) {
              if (i < l)
                l = i;
            }
          }

          if (c->current_joined >= (l + c->limit_change / 2)) {
            /* raise the limit */
            c->limit_last_joined = c->current_joined;
            i = c->current_joined + c->limit_change;
            putserver("%s M %s +l %d\r\n", yxx, c->channel, i);
	    snprintf(c->cur_limit, LIMITLEN, "%d", i);
	    irclog(DEBUG_ALL, "autolimit inc. [%s] [%d]\n", c->channel, i);
          } else if (c->current_joined <= (l - c->limit_change / 2)) {
            /* lower the limit */
            c->limit_last_joined = c->current_joined;
            i = c->current_joined + c->limit_change;
            putserver("%s M %s +l %d\r\n", yxx, c->channel, i);
	    snprintf(c->cur_limit, LIMITLEN, "%d", i);
	    irclog(DEBUG_ALL, "autolimit decr [%s] [%d]\n", c->channel, i);
          }
          c->limit_next_check = tnow + c->limit_delay;
        }
        el = GetSucc(el);
      }
    }

    if (t_tmp.tv_sec >= t_save.tv_sec) {
      t_save.tv_sec += SEC_BEFORE_SAVE;
      if (donewithburst) {
	save_everything();
      }
    }

    if (t_tmp.tv_sec >= t_trust.tv_sec) {
      t_trust.tv_sec += 60;

      tnow = time(NULL);
      el = GetHead(trusted_list);
      while (el) {
        tr = (struct trusted *) el->data;
        el2 = GetSucc(el);
        if (tnow > tr->expire) {
          trusted_del(tr->host);
        }
        el = el2;
      }

#if 0
      {
        struct chan_stat first_chanstat;
        struct chan_stat *cs, *cstmp;
        // struct activeuser *u;
        struct chanopts *co;
        struct chanmod *c;
        ListElem *el;
        int channels, matches;

        first_chanstat.next = NULL;
        first_chanstat.count = 0;
        first_chanstat.channel[0] = '\0';
        matches = 0;

        el = GetHead(auser_list);
        while (el) {
          u = (struct activeuser *) el->data;
          c = u->chanmodes;
          while (c) {
            inc_channel2(&first_chanstat, c->channel);
            matches++;
            c = c->next;
          }
          el = GetSucc(el);
        }
        channels = 0;
        if (matches) {
          cs = first_chanstat.next;
          while (cs) {
            channels++;
            co = findchannel(cs->channel);
            if (co) {
              if (co->current_joined != cs->count) {
                log("CNT: cur=%d count=%d\n", co->current_joined, cs->count);
                co->current_joined = cs->count;
              }
            }
            cstmp = cs;
            cs = cs->next;
            Free(cstmp);        /* free it at once */
            cstmp = NULL;
          }
        }
      }
#endif
    }
    if (t_tmp.tv_sec >= t_next.tv_sec) {
      t_next.tv_sec += intervall;

      /* should be moved to another shorter timeout 150s. is too long */
      tnow = time(NULL);
      el = GetHead(gline_list);
      while (el) {
        g = (struct gline *) el->data;
        el = GetSucc(el);
        if (tnow > g->expire) {
          putserver("%c%c GL * -%s\r\n", yxx[0], yxx[1], g->gline);
          gline_del(g->gline);
        }
      }

      /* 
         try to count the users all by ourself */
      for (i = 0; i < MAX_SERVERS; i++) {
        if (strlen(servers[i].name) > 0) {
          if (strcasecmp(servers[i].name, VIRTUAL_SERVER_NAME) != 0) {
            /* don't ask yourself */
            save_server_info(servers[i].name, 10, 0, 0, NULL);
          }
        }
      }
      /* 
         And save total information about all servers */
      save_server_info(q_stats_domain, 9, 0, 0, NULL);
    }
  }
}

void addkey_ch(struct chanopts *c, char *key, int permanent)
{
  if (!c)
    return;
  strncpy(c->key, key, KEYLEN);
  c->key[KEYLEN] = '\0';
  if (permanent) {
    if(c->key[0]) {
      putserver("%s M %s +k %s\r\n", yxx, c->channel, c->key);
      c->modes |= (1 << ('k' - 'a'));
    }
  }
  return;
}

void addkey(char *channel, char *key, int permanent)
{
  struct chanopts *c = findchannel(channel);
  return addkey_ch(c, key, permanent);
}

void delkey_ch(struct chanopts *c, int permanent)
{
  if (!c)
    return;

  putserver("%s M %s -k %s\r\n", yxx, c->channel, c->key);
  c->modes &= (~0 - (1 << ('k' - 'a')));
  if (permanent)
    c->key[0] = '\0';
  return;
}

void delkey(char *channel, int permanent)
{
  struct chanopts *c = findchannel(channel);
  return delkey_ch(c, permanent);
}


void addlimit_ch(struct chanopts *c, char *limit, int permanent)
{
  if (!c)
    return;
  strncpy(c->limit, limit, LIMITLEN);
  c->limit[LIMITLEN] = '\0';
  if (permanent) {
    irclog(DEBUG_ALL, "addlimit_ch: [%s] [%s] (%d)\n", c->channel, c->limit, strlen(c->limit));
    if(c->limit[0]) {
      putserver("%s M %s +l %s\r\n", yxx, c->channel, c->limit);
      strcpy(c->cur_limit, c->limit);
      c->modes |= (1 << ('l' - 'a'));
    }
  }
  return;
}

void addlimit(char *channel, char *limit, int permanent)
{
  struct chanopts *c = findchannel(channel);
  return addlimit_ch(c, limit, permanent);
}


void dellimit_ch(struct chanopts *c, int permanent)
{
  if (!c)
    return;

  putserver("%s M %s -l\r\n", yxx, c->channel);
  c->cur_limit[0] = '\0';
  c->modes &= (~0 - (1 << ('l' - 'a')));
  if (permanent)
    c->limit[0] = '\0';
  return;
}

void dellimit(char *channel, int permanent)
{
  struct chanopts *c = findchannel(channel);
  return dellimit_ch(c, permanent);
}


void addchanlimit_ch(struct chanopts *c, int limit, int delay)
{
  int nr, l;
  if (!c)
    return;

  l = limit;
  if (l > 500)
    l = 500;
  if (l < 2)
    l = 2;

  nr = c->current_joined + l;
  c->limit_change = l;
  c->limit_last_joined = c->current_joined;
  c->limit_next_check = time(NULL) + delay;

  c->limit_delay = delay;
  chanlimit_delay = delay;

  putserver("%s M %s +l %d\r\n", yxx, c->channel, nr);
  // fixed bug, this was really wrong...
  //  c->modes |= (1 << ('c' - 'a'));
  c->modes |= (1 << ('l' - 'a'));
  snprintf(c->cur_limit, LIMITLEN, "%d", nr);
  irclog(DEBUG_ALL, "addchanlimit_ch: [%s] [%d]\n", c->channel, nr);
  return;
}

void addchanlimit(char *channel, int limit, int delay)
{
  struct chanopts *c = findchannel(channel);
  return addchanlimit_ch(c, limit, delay);
}

void delchanlimit_ch(struct chanopts *c)
{
  if (!c)
    return;

  putserver("%s M %s -l\r\n", yxx, c->channel);
  // fixed bug
  //  c->modes &= (~0 - (1 << ('c' - 'a')));
  c->modes &= (~0 - (1 << ('l' - 'a')));
  c->cur_limit[0] = '\0';
  return;
}

void delchanlimit(char *channel)
{
  struct chanopts *c = findchannel(channel);
  return delchanlimit_ch(c);
}
