Irkkibotti

Enec 26.02.08 17:55

Vähän tällänen ikuisuusprojekti, joka nyt kuitenkin on käytettävässä kunnossa. Listaus komennoista selityksineen:http://cene.ath.cx/scb/commands.html --- Uusin edit: pieni bugifix ainakin unrealircd:lle

 Tekstiversio  Arvo: 7 (7 ääntä)  Äänestä: +  -
/* SCB - Simple C Bot
 *
 * An IRC Bot written in C
 *
 * Copyright (c) 2007 - 2008, Joeli Hokkanen <cene@fix.fi>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *
 * Should build on all Linux and BSD systems with GCC.
 *
 ***********************************************************
 *                                                         *
 * REQUIRES LIBCONFUSE FROM http://www.nongnu.org/confuse/ *
 * Build with gcc -lconfuse                                *
 *                                                         *
 ***********************************************************
 *
 */


#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <stdarg.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <confuse.h>

#define BUFFER_SIZE 1024
#define MAX_ARGS 256
#define MAXLEN 100

#define ERR_FILECREATED 74
#define ERR_NOFILE 73
#define ERR_PARSE 78
#define ERR_CONN 75
#define ERR_SOCKET 70

#define CONFFILE "scb.cfg"

#define VERSION 1.1

/* The error message we'll send when someone tried to do something (s)he wasn't allowed to. */
#define NOAUTH sendl(sock, "NOTICE %s :Sorry, you lack the sufficient access to do that or tried to access a command that is available only via private message from a channel.", who);

/* Default arguments to send to a IRC command.
 * These must be the same to all of the commands that
 * are executed through a ommand in IRC, because they
 * will all be given the same arguments
 * through the execcmd function.
 */

#define CARGS int sock, char *from, char **args, int arg_count

/* If something goes wrong or needs attention, these will do it in a standardized format. */
#define derr(msg) fprintf(stderr, "=> Error: %s\n", msg);
#define dwarn(msg) fprintf(stderr, "=> Warning: %s\n", msg);

#ifdef DEBUG
/* Only for debug use, to give additional information to (surprisingly!) help debugging. */
  #define dinfo(msg) printf("=> (DEBUG) %s\n", msg);
#endif


/* Introducing most of our functions.. */

void cmd_quit(CARGS),
     cmd_nick(CARGS),
     cmd_abt(CARGS),
     cmd_catcher(int sig),
     inslist(char *name, char *desc, void *execcmd),
     execcmd(char *name, CARGS),
     cmd_list(CARGS),
     cmd_kick(CARGS),
     cmd_op(CARGS),
     cmd_deop(CARGS),
     cmd_voice(CARGS),
     cmd_devoice(CARGS),
     cmd_mode(CARGS),
     cmd_topic(CARGS),
     cmd_join(CARGS),
     cmd_part(CARGS),
     cmd_auth(int sock, char* nick),
     addop(CARGS),
     addvoice(CARGS),
     deluser(CARGS),
     inslist_usr(int lvl, char *name),
     rehash(CARGS),
     listusers(CARGS);

int conn(int sock, const char *remotehost, unsigned short remote_port),
    sendl(int sock, const char *format, ...),
    recvln(int sock, char *line, unsigned int line_size, FILE *log),
    parse(void);

char *strsave(char *word);

int i,
    sock,          /* Declares the socket */
    own      = 0/* Switch to see if the line came from an owner */
    voiced   = 0/* Switch to see if the line came from a voiced person */
    tolog    = 1/* Shall we take logs? Turned off via -l command line switch */
    tofork   = 1/* Shall we fork? turned off via -f command line switch or defining DEBUG on build */
    authed   = 0/* Are we authed yet? */
    AOP      = 0/* Shall owners be auto-opped? Turned on from the config file */
    AVOICE   = 0/* Shall voices be auto-opped? Turned on from the config file */
    IRC_PORT = 0/* Port of the irc server to be connected to. Changed via the config file or -p switch */
    react    = 0/* Shall we react on the line? For security reasons channel operator functions like listops and such
                    * are disabled if we didn't get the order to do it via private message. */


char *IRC_SERVER      = NULL/* These all should be pretty self-explanatory. */
     *IRC_NICK        = NULL/* All these values (except for the last one) */
     *IRC_REALNAME    = NULL/* Are turned on via the config file. */
     *IRC_CHAN        = NULL,
     *AUTH_COMMAND    = NULL,
     *LOGFILE         = NULL,
     *who             = NULL/* This is a variable where we will store the person's nick who said
                                * the given line.
                                */


const char IRC_CMD_TRIGGER = '-'; /* Command trigger. All lines that the bot will react to must
                                   * begin with this character.
                                   */


FILE *fp, /* Few file switches that are easier to be left global */
     *gg; /* Than given to each of those functions that want them. */


/* All the commands are stored in this linked list,
 * and will be added to it via the
 * inslist function.
 */

struct irc_cmd {
  const char *cmd_name_p,
             *desc_p;
  void (*exec_cmd)(CARGS);
  struct irc_cmd *next; /* Linked list */
};

/* All users will be stored in this one.
 * New users are added with the
 * inslist_usr function.
 */

struct users {
  char *mask;
  int lvl;      /* user level. 1 = op, 2 = voice */
  struct users *next;
};

typedef struct users *UPTR;
UPTR user = NULL;

typedef struct irc_cmd *LISTPTR;
LISTPTR cmd = NULL;

LISTPTR lalloc (void)/* Lalloc and olalloc will allocate the needed space */
UPTR olalloc(void);     /* For inslist() and inslist_usr() */

void execcmd(char *name, CARGS) {
  /* This is the function that executes the commands
   * from the irc_cmd linked list.
   * The functionality is pretty self-explanatory. First we'll
   * create a new list pointer and scan with it if any
   * command name matches. If it does we'll execute the command.
   */

  LISTPTR run = cmd;
  while(run) {
    if(!strcmp(name, run->cmd_name_p)) { /* Got match */
      run->exec_cmd(sock, from, args, arg_count);
    }
    run = run->next;
  }
}

void inslist (char *name, char *desc, void *execcmd) {
  /* Inserting new values to the irc_cmd list.
   * first we'll create yet another list pointer,
   * then allocate the required space,
   * save the command name and description
   * with strsave() and the pointer to the function itself.
   * Then we'll fix the list to adjust to the newly added stuff.
   */

  LISTPTR new;
  new = lalloc();
  new->cmd_name_p = strsave (name);
  new->desc_p = strsave (desc);
  new->exec_cmd = execcmd;
  new->next = NULL; /* Not necessary, since lalloc does it already */
  if(cmd) {
    LISTPTR run = cmd;
    while(run->next)
      run = run->next;
      run->next = new;
  } else
    cmd = new;
}

void inslist_usr(int lvl, char *name) {
  /* For explanation look above to the inslist() function.
   * This does exactly the same, but with different values and to a
   * different list (the owners and voices).
   */

  UPTR new;
  new = olalloc();
  new->mask = strsave(name);
  new->lvl = lvl;
  new->next = NULL;
  if(user) {
    UPTR urun = user;
    while(urun->next)
      urun = urun->next;
    urun->next = new;
  }
  else
    user = new;
}

/* These are the functions to allocate space for new entries in the linked
 * lists.
 */

LISTPTR lalloc() {
  return (LISTPTR) calloc(sizeof(struct irc_cmd), 1);
}

UPTR olalloc(void) {
  return (UPTR) calloc(sizeof(struct users), 1);
}

/* Function to duplicate the given string. */
char *strsave (char *word) {
  return strdup(word);
}


int parse(void) {

  /* Inside this function is the place we need libConfuse and the reason
   * you need it.
   * libConfuse is the easiest and most flexible way to scan a config file.
   */


  FILE *qq;
  if ((gg = fopen(CONFFILE, "r")) == NULL) {
    /* If we couldn't open the conf file for reading, it means
     * it probably doesn't exist.
     */

    derr("No config file found!");
    derr("Creating " CONFFILE " and exiting. Edit the file to fit your needs and then re-run the bot.");
    if ((qq = fopen(CONFFILE, "w+")) == NULL) {
      /* If opening the file for writing failed too,
       * then there's something wrong that the user must find out first.
       * Maybe permission denied?
       */

      derr("Cannot create file named " CONFFILE);
      return ERR_NOFILE;
    }
    /* If we could get it open for writing, we'll print a base
     * for the configuration file
     * and tell the user to edit it.
     */

    fprintf(qq, "# Configuration file for scb\n"
            "# All files beginning with # are comments,\n"
            "# And will be ignored.\n\n"
            "# IRC server and port to conn to.\n"
            "server = \"irc.quakenet.org\"\n"
            "port = 6667\n\n"
            "# Real name and nick of the bot\n"
            "nick = \"scb\"\n"
            "realname = \"Simple C Bot\"\n\n"
            "# Channel to join to\n"
            "chan = \"#simplecbot\"\n\n"
            "# Owner(s) of the bot (A part of the\n"
            "# hostmasks of the users who should be able to\n"
            "# execute any command with the bot.\n"
            "owners = { \"foo!bar@Foo.users.quakenet.org\", \"bar!foo@bar.users.quakenet.org\" }\n\n"
            "# Voices. Users with limited access to commands (voice, kick, devoice mainly)\n"
            "# and will be automatically voiced on join.\n"
            "voices = { \"some!simple@guy\", \"Just!another@user\" }\n\n"
            "# Should we automatically op owners and voice those on the voices group?\n"
            "autoop = 1\n"
            "autovoice = 1\n\n"
            "# Log file to log to. \n"
            "logfile = \"scb.log\"\n\n"
            "# Auth command. Comment to disable.\n"
            "# The example below provides a correct syntax for\n"
            "# Quakenet authing, and should be used as a base for any\n"
            "# Other network too.\n"
            "authcmd = \"PRIVMSG Q@CServe.quakenet.org :AUTH acc pass\"\n\n\n");
    fclose(qq);
    return ERR_FILECREATED;
  }

  fclose(gg); /* We can close the config file for now. */


  cfg_opt_t opts[] = {
    /* Here we'll declare all the
     * values that are to be parsed from the
     * config file.
     */

    CFG_SIMPLE_INT("port",&IRC_PORT),
    CFG_SIMPLE_INT("autoop", &AOP),
    CFG_SIMPLE_INT("autovoice", &AVOICE),
    CFG_SIMPLE_STR("server", &IRC_SERVER),
    CFG_SIMPLE_STR("realname", &IRC_REALNAME),
    CFG_SIMPLE_STR("nick", &IRC_NICK),
    CFG_SIMPLE_STR("chan", &IRC_CHAN),
    CFG_STR_LIST("owners", 0, CFGF_NODEFAULT),
    CFG_STR_LIST("voices", 0, CFGF_NODEFAULT),
    CFG_SIMPLE_STR("logfile", &LOGFILE),
    CFG_SIMPLE_STR("authcmd", &AUTH_COMMAND),
    CFG_END()
  };

#ifdef DEBUG
  dinfo("set cfg_opt_t opts[]");
#endif

  cfg_t *cfg;
  cfg = cfg_init(opts, CFGF_NONE);

#ifdef DEBUG
  dinfo("Starting cfg_parse()");
#endif

  /* This is where the actual parsing and inserting the values
   * to variables happen.
   */

  if (cfg_parse(cfg, ("%s", CONFFILE)) == CFG_PARSE_ERROR) { 
    /* ("%s", CONFFILE) is required here, otherwise strange
     * things will happen. Ignore the warning gcc gives with -Wall.
     */

    derr("Config parse error!");
    /* Hmm, something went wrong. I haven't faced a single situation which
     * yielded CFG_PARSE_ERROR though. But according to the manual of
     * libConfuse it can happen. Maybe a syntax error in the config file?
     */

    return ERR_PARSE;
  }

#ifdef DEBUG
  /* I had some trouble with this part and rehash().
   * I still do.
   * Doesn't hurt to check things out.
   */

  dinfo("Parsing owners");
  dinfo("======================");
  dinfo("testing cfg_size()");
  printf("%i\n", cfg_size(cfg, "owners"));
  dinfo("done");
  dinfo("testin cfg_getnstr()");
  printf("%s\n", cfg_getnstr(cfg, "owners", 0));
  dinfo("All done");
#endif

  /* Here we'll insert the owners to the linked list.
   * cfg_size gives the total amount of owners in the list,
   * and cfg_getnstr gives i:th the owner from the list
   */

  for(i = 0; i < cfg_size(cfg, "owners"); i++)
    inslist_usr(1, (cfg_getnstr(cfg, "owners", i)));

#ifdef DEBUG
  dinfo("Parsing voices");
#endif

  /* Same thing for voices.
   * Look up for explanation.
   */

  for (i = 0; i < cfg_size(cfg, "voices"); i++)
    inslist_usr(2, (cfg_getnstr(cfg, "voices", i)));

#ifdef DEBUG
  dinfo("All done, running cfg_free()");
#endif

  /* Finally we're freeing anything that isn't needed anymore.
   */

  cfg_free(cfg);

  return 0;

}


int main(int argc, char **argv) {
  int argcount,
      fd;

  /* Inserting the commands, their names and their descriptions to the
   * irc_cmd list.
   */

  inslist("about", "Info about the bot", &cmd_abt);
  inslist("addop", "Add channel operator", &addop);
  inslist("addvoice", "Add voiced user", &addvoice);
  inslist("rehash", "Rehash the config file", &rehash);
  inslist("listusers", "List all owners", &listusers);
  inslist("deluser", "Remove user from the owners or voices list", &deluser);
  inslist("join", "Join the given channel", &cmd_join);
  inslist("part", "Part the given channel", &cmd_part);
  inslist("kick", "Kick an user", &cmd_kick);
  inslist("k","Kick an user", &cmd_kick);
  inslist("op","Op an user", &cmd_op);
  inslist("deop", "Deop an user",& cmd_deop);
  inslist("voice", "Voice an user", &cmd_voice);
  inslist("devoice", "Devoice an user", &cmd_devoice);
  inslist("mode", "Change channel modes", &cmd_mode);
  inslist("topic", "Change channel topic", &cmd_topic);
  inslist("quit", "Make me quit", &cmd_quit);
  inslist("nick", "Make me change my nick", &cmd_nick);
  inslist("list", "List all commands", &cmd_list);
  inslist("ls", "List all commands", &cmd_list);


#ifdef DEBUG
  dinfo("inslist done, starting cfg parsing");
#endif

  /* This is where we'll parse the config and quit if it doesn't return 0.
   */

  int c;
  if((c = parse()) != 0)
    return c;

  /* Getting the command line options. The reason it's made this far is that options
   * given from here would override the ones parsed from the
   * config file.
   */

  while ((i = getopt(argc, argv, "hfls:c:n:r:")) != -1)
    switch (i) {

      case 'h':
        fprintf(stderr, "%s - Simple IRC Bot written in C\n\n"
               "Version %f\n\n"
               "Usage: %s [-hfl] [-s irc.server.net] [-c channel]\n"
               "          [-n nick] [-r \"real name\"]\n\n"
               "-h    : Show this help.\n"
               "-l    : Turn off logging.\n"
               "-f    : Don't fork to background, run in console instead.\n"
               "-s    : Server to connect to.\n"
               "-c    : Channel to join to.\n"
               "-n    : Nick to be used by the bot.\n"
               "-r    : Real name.\n"
               "        All values entered here will override the ones set on config file.\n",
               argv[0], VERSION, argv[0]);
        exit(0);

      case 'l':
        tolog = 0;
        break;

      case 'f':
        tofork = 0;
        break;

      case 's':
        IRC_SERVER = optarg;
        break;

      case 'c':
        IRC_CHAN = optarg;
        break;

      case 'n':
        IRC_NICK = optarg;
        break;

      case 'r':
        IRC_REALNAME = optarg;
        break;
    }
#ifdef DEBUG
  tofork = 0; /* We will want to stay on the console if we enabled DEBUG. */
#endif

  /* Printing the 'cool' start-up message. First thing an user will see unless (s)he specified
   * the -h flag.
   */

  printf("===============================================\n"
         "         Simple C Bot starting up . . .\n"
         "===============================================\n\n"
         "=> Nick set to `%s'\n"
         "=> Server set to `%s' on port %i\n"
         "=> Channel set to `%s'\n"
         "=> Real name set to `%s'\n",
         IRC_NICK, IRC_SERVER, IRC_PORT, IRC_CHAN, IRC_REALNAME);

  if (tolog == 1) {
    printf("=> Opening logfile `%s'\n", LOGFILE);
    fp = fopen(LOGFILE, "a+");
  }

  char buffer[512]  = {0},
       from[56]     = {0},
       nick[32]     = {0},
       *argument[MAX_ARGS],
       *token;

  /* Creating the socket */
  if ((sock = socket(PF_INET,
                     SOCK_STREAM,
                     IPPROTO_TCP)) == -1) {
    derr("Oh shit! socket() failed!");
    return(ERR_SOCKET);
  }

  printf("=> Created socket\n");

  /* Setting signals to the catcher to allow graceful stopping of the bot. */
  signal(SIGHUP, cmd_catcher);
  signal(SIGINT, cmd_catcher);
  signal(SIGTERM, cmd_catcher);

  /* Then connecting to the specified server.
   * This calls the conn() function that is below the main.
   */

  if (conn(sock, IRC_SERVER, IRC_PORT) != 0 ) {
    derr(("Failed to connect to %s", IRC_SERVER));
    return ERR_CONN;
  }

  printf("=> Connected to %s:%i\n", IRC_SERVER, IRC_PORT);

 
  /* Then we're forking unless the user specified (s)he didn't want it via the
   * -f flag or defining DEBUG.
   */


  if (tofork == 1) {
    printf("=> Forking to background\n\n");
    pid_t pid;
    pid = fork();
    if (pid < 0)
      exit(1);
    if (pid != 0)
      exit(0);

    setsid();
    signal (SIGHUP, SIG_IGN);
    pid = fork();

    if (pid < 0)
      exit(1);
    if (pid != 0)
      exit(0);
    umask(0);

    for (i = 0; i < 3; ++i) close(i);
    fd = open("/dev/null", O_RDWR);
    dup(fd);
    dup(fd);

  }

  snprintf(nick, sizeof(nick), "%s", IRC_NICK);      /* We don't want too long nicks */
  sendl(sock, "NICK %s", nick);                      /* Telling the server our nick */
  sendl(sock, "USER scb foo bar :%s", IRC_REALNAME); /* And logging in */


  /* This is the loop that will run while the connection is active.
   * Everything is handled from here.
   */

  while(1) {

    /* Clearing the buffer from previous lines*/
    memset(buffer, 0, sizeof(buffer));

    /* Waiting until we receive a new line and insert it to buffer. */
    if (recvln(sock, buffer, sizeof(buffer), fp) == 1)
      break;

    /* Then we'll split the buffer to pieces
     * to make it easier to handle. Also counting the total number of arguments.
     */

    token = strtok(buffer, " ");
    argcount = 0;
    while (token != NULL) {
      argument[argcount] = token;
      token = strtok(NULL, " ");
      argcount++;
    }

    /* Two arguments means there were two entries seperated with spaces in the input.
     */

    if (argcount > 2) {
      if (strcmp(argument[1], "001") == 0) {
        /* 001 is the answer from IRC server announcing
         * that everything went fine.
         */


        if (authed == 0)
          cmd_auth(sock, nick);
        sendl(sock, "JOIN %s", IRC_CHAN);           /* so we're joining to the channel. */
        continue;
      }

    else if (strcmp(argument[1], "433") == 0) {    /* 433 means that the nick is already in use. */
      strncat(nick, "^", 1);                       /* so we're adding ^ to the end of the nick */
      sendl(sock, "NICK %s", nick);                /* And telling the server we did so */
      if (authed == 0)
        cmd_auth(sock, nick);
      continue;
    }
  }

  if (argcount == 2) {
    if (strcmp(argument[0], "PING") == 0) {
      /* IRC servers requires a PONG response
       * for every PING it sends or we'll get disconnected
       * with a reason "ping timeout". Usually this means that
       * the connection between the server and the user is broken, so
       * the server didn't get PONG in time.
       */

      sendl(sock, "PONG %s", argument[1]);
      continue;
    }
  }

  /* Here we're going through the users list to see if argument[0], which contains the hostmask
   * of the user that said something, is listed on the owners or voices list.
   * If it is, we'll set own or voiced variable to true, so that commands
   * that require ownership or to be voiced can be executed.
   */

  UPTR scan = user;
  while(scan) {
    if ((strstr(argument[0], scan->mask)) != NULL) {
#ifdef DEBUG
      dinfo(("Checking whether %s is on the users list", scan->mask));
#endif
      if (scan->lvl == 1) {
#ifdef DEBUG
        dinfo("op");
#endif
        own = 1;
      }
      else if (scan->lvl == 2) {
#ifdef DEBUG
        dinfo("voice");
#endif
        voiced = 1;
      }
    }
    scan = scan->next;
  }

  who = strtok((strsave(argument[0])), ":!");
  /* wtf strtok sucks, have to use strsave to keep
   * the original argument[0] intact.
   *
   * The who variable holds the nick of the user who said
   * the current line. This is used by many commands which will
   * send a NOTICE to the user.
   */


  /* Three arguments most likely mean a join, part, quit or similiar. */
  if (argcount == 3)
    if (strcmp(argument[1], "JOIN") == 0) {             /* so if someone's joining.. */

      if ((strncmp(argument[2], ":", 1)) == 0) {        /* Fix for unrealircd */
        char *fix = strtok(strdup(argument[2]), ":");
        argument[2] = fix;
      }

      if ((own == 1) && (AOP == 1))                     /* .. and if the user is a owner and auto-op is set to yes.. */
        sendl(sock, "MODE %s +o %s", argument[2], who); /* We'll op him/her. */
      else if ((voiced == 1) && (AVOICE == 1))          /* Or if it was a voiced user and autovoice is enabled */
        sendl(sock, "MODE %s +v %s", argument[2], who); /* We'll voice him/her. */
    }

  /* If the target of the message was the bot, meaning a private
   * message, we can assume that it is safe to react to it.
   */

  if (strcmp(argument[2], nick) == 0)
    react = 1;

  /* Four or more arguments are most likely a cause of somebody saying something. */
  if (argcount >= 4) {
    if (strcmp(argument[1], "PRIVMSG") == 0) {    /* This confirms if someone indeed said something. */
      snprintf(from, sizeof(from), "%s", argument[2]); /* And then gets the channel the line was said in. */
      if (argument[3][1] == IRC_CMD_TRIGGER) {
        /* Here we'll check if the first letter of the first word that
         * the user said was the IRC_CMD_TRIGGER, '-' by default.
         *
         * If it was, we'll connect all the arguments to
         * one string to make it easier for the
         * command that was called to do anything
         * with them.
         */

        *argument += (strlen(argument[0]) + 1) +
                     (strlen(argument[1]) + 1) +
                     (strlen(argument[2]) + 3);
        execcmd(argument[0], sock, from, argument, argcount - 3);
        /* And then we'll call execcmd which will scan through the list of irc_cmd
         * and execute the command that was given to it.
         */

        }
      }
    }

    continue;
  }
  /* The loop closed here, which means that the connection was closed. We'll start the
   * shutdown prodecure then.
   */


  if (tolog == 1) {
    /* First we'll flush and close the log file.
     * flush isn't necessary, as it's most likely already done,
     * but better to be sure.
     */

    printf("=> Closing log file `%s'...", LOGFILE);
    fflush(fp);
    fclose(fp);
    printf("Done!\n");
  }
  printf("=> Disconnected\n");
  return 0;
}


/* connect function */
int conn(int sock, const char *remotehost, unsigned short remote_port) {
  printf("=> Resolving %s... ", remotehost);

  struct sockaddr_in s_in;
  struct hostent *hostent;

  /* First we need to perform DNS query to get the IP of the server */
  if (!(hostent = gethostbyname(remotehost)))
    return 0;

  printf("Success!\n");

  memset(&s_in, 0, sizeof(s_in));
  s_in.sin_family = PF_INET;                        /* IPv4 */
  s_in.sin_addr.s_addr = *(long *) hostent->h_addr; /* Taking the returned IP */
  s_in.sin_port = htons(remote_port);               /* Converting remote_port to network order byte */
  printf("=> Resolved %s to %s\n", remotehost, inet_ntoa(s_in.sin_addr));

  /* Here we'll use the socket to connect to the
   * IP we resolved moments ago.
   */

  if (connect(sock, (struct sockaddr *) &s_in, sizeof(s_in)) == -1)
    return 1;

  return 0;
}


/* This is the function that will handle the sending
 * of all data to IRC server.
 */

int sendl(int sock, const char *format, ...) {
  va_list args; /* va_list is used when there is a variable amount of arguments for a function */
  char buffer[512] = {0};

  va_start(args, format); /* Here we'll append every variable given to the function to buffer. */
  vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  strncat(buffer, "\r\n", (sizeof(buffer) - strlen(buffer)));
  /* Adding CRLF to the end of the string,
   * so the IRCd correctly recognizes it as a end of line.
   */


  /* No need to see PONGs, so we'll skip over the printing of the line if it is a PONG response. */
  if (strstr(buffer,"PONG :") == NULL)
    printf(">> %s", buffer);

  /* This is where we'll actually send the line to the server.
   * The send() returns int, so we know if something went wrong with the
   * return value.
   */

  return(send(sock, buffer, strlen(buffer), 0));
}


/* Receive line function */
int recvln(int sock, char *line, unsigned int line_size, FILE *log) {
  char byte = 0;
  react = 0;
  own = 0;      /* We'll reset the react, own and voiced values from the previous line. */
  voiced = 0;

  while (byte != '\n' && strlen(line) < line_size) { /* Getting one byte at a time until newline */
    if (!recv(sock, (char *) &byte, 1, 0))
      return 1;

    if (byte != '\r' && byte != '\n' && byte != '\0')
      strncat(line, (char *) &byte, 1);
  }

  if (tolog == 1) {
    /* If we're taking logs and the line is either a PRIVMSG to the channel, a quit message,
     * a join message or a part message we'll write it to the log together
     * with the current UNIX time.
     * This log format gives us full compatibility with the oer log format
     * for pisg to generate stats from the log if you want to.
     */

    if ((strstr(line,"PRIVMSG") != NULL && strstr(line,IRC_CHAN) != NULL) ||
          strstr(line,"QUIT :") != NULL || strstr(line,"JOIN") != NULL ||
            strstr(line,"PART") != NULL) {
        fprintf(log, "%ld %s\n", (long)time(NULL), line);
        fflush(log);
    }
  }

  /* We don't want to see ping and pong, it's enough that they happen. */
  if (strstr(line,"PING :") == NULL)
    printf("<< %s\n", line);

  return 0;
}

/* Function to change nick */
void cmd_nick(CARGS) {
  if (own == 1 && react == 1) {
    sendl(sock, "NICK %s", args[4]);
    IRC_NICK = args[4];
  }
  else
    NOAUTH;
}

/* Quit function */
void cmd_quit(CARGS) {
  if (own == 1 && react == 1)
    if (((strcmp(args[4], "-y")) == 0) || (strcmp(args[4], "-yes")) == 0) /* If confirmed the quit with -y or -yes */
      sendl(sock, "QUIT :Oh god how did this get in here i am not good with computers");
    else
      sendl(sock, "NOTICE %s :Are you sure you want me to quit? :(  -quit -yes to confirm.", who);
  else
    NOAUTH;
}

/* Auth function */
void cmd_auth (int sock, char* nick) {
  authed = 1;
  sendl(sock, AUTH_COMMAND);
  sendl(sock, "MODE %s +x", nick);
  sleep(0.5); /* We better sleep a moment for mode +x to take effect before joining.. */
}

/* About function. Probably going to disappear as soon as I
 * have time to implement CTCP.
 */

void cmd_abt (CARGS) {
  sendl(sock, "PRIVMSG %s :Simple C Bot, version %f (built at %s %s)", from, VERSION, __DATE__, __TIME__);
  sendl(sock, "PRIVMSG %s :Send bugs, suggestions etc to cene@fix.fi", from);
}

/* Joining to channels */
void cmd_join (CARGS) {
   if (own == 1 && react == 1)
     sendl(sock, "JOIN %s", args[4]);
   else
     NOAUTH;
}

/* Function to part channels */
void cmd_part (CARGS) {
   if (own == 1 && react == 1) {
     if (args[4]) /* If the user specified a channel to part */
       sendl(sock, "PART %s", args[4]);
     else /* Else we'll part the current one */
       sendl(sock, "PART %s", from);
   }
   else
     NOAUTH;
}

/* Kick function */
void cmd_kick(CARGS) {
    if (own == 1) {
      if (arg_count >= 3) {
        /* If there is more than three
         * arguments, it means that the user who issued
         * the kick command specified a kick reason.
         * We'll append the reason to one string "mtmp" and
         * use it as a kick reason.
         */

        char *mtmp = NULL;
        int len = 0;
        for(i = 5; i < arg_count + 4; i++)
          len += (strlen(args[i]) + 2);
        mtmp = malloc(len * sizeof(char) + 1);
        mtmp[0] = (char)0;

        for (i = 5; i < arg_count + 4; i++) {
          strcat(mtmp, args[i]);
          strcat(mtmp, " ");
        }
        sendl(sock, "KICK %s %s :%s", from, args[4], mtmp);
      }
      /* If there were only two arguments, there is no reason specified
       * so we don't need to look for it either.
       */

      else
        sendl(sock, "KICK %s %s", from, args[4]);
    }
    else
      NOAUTH;
}

/* Op function */
void cmd_op(CARGS) {
    if (own == 1)
      sendl(sock, "MODE %s +o %s", from, args[4]);
    else
      NOAUTH;
}

/* Deop function */
void cmd_deop(CARGS) {
    if (own == 1)
      sendl(sock, "MODE %s -o %s", from, args[4]);
    else
      NOAUTH;
}

/* Voice function */
void cmd_voice(CARGS) {
    if (own == 1)
      sendl(sock, "MODE %s +v %s", from, args[4]);
    else
      NOAUTH;
}

/* Devoice function */
void cmd_devoice(CARGS) {
    if (own == 1)
      sendl(sock, "MODE %s -v %s", from, args[4]);
    else
      NOAUTH;
}

/* Modes */
void cmd_mode(CARGS) {
    if (own == 1) {
      if (arg_count > 2) {
        /* If there were more than one args[]
         * that include the modes to modify,
         * we need to append them to a single
         * string first.
         */

        char *mtmp = NULL;
        int len = 0;
        for (i = 4; i < arg_count + 3; i++)
          len += (strlen(args[i]) + 2);
        mtmp = malloc(len * sizeof(char) + 1);
        mtmp[0] = (char)0;

        for (i = 4; i < arg_count + 3; i++) {
          strcat(mtmp, args[i]);
          strcat(mtmp, " ");
        }

        sendl(sock, "MODE %s %s", from, mtmp);
        free(mtmp);
      }
      else
        sendl(sock, "MODE %s %s", from, args[4]);
    }
    else
      NOAUTH;
}

/* Topic */
void cmd_topic(CARGS) {
  if (own == 1) {
    /* Appending anything past args[4] to
     * a single string and then setting it as a topic.
     * Nothing special.
     */

    char *tmp = NULL;
    int len = 0;
    for(i = 4; i < arg_count + 3; i++)
      len += (strlen(args[i]) + 2);

    tmp = malloc(len * sizeof(char) + 1);
    tmp[0] = (char)0;

    for(i = 4; i < arg_count + 3; i++) {
      strcat(tmp, args[i]);
      strcat(tmp, " ");
    }

#ifdef DEBUG
    dinfo(tmp);
#endif

    sendl(sock, "TOPIC %s :%s", from, tmp);
    free(tmp);
  }
  else
    NOAUTH;
}

void deluser(CARGS) {
  if (own == 1 && react == 1) {
    int rm = 0;       /* How many users we've deleted. Will be used later on */
    UPTR scan = user;
    UPTR tmp = NULL;
    for (scan = user; scan != NULL; tmp = scan, scan = scan->next) {
      /* Scanning through the list of users looking for a matching string. */
      if ((strstr(scan->mask, args[4])) != NULL) {
        if(scan->lvl == 1) {
          sendl(sock, "NOTICE %s :Regex '%s' matches '%s', removing owner status from '%s'",
                who, args[4], scan->mask, scan->mask);
        }
        else if(scan->lvl == 2) {
          sendl(sock, "NOTICE %s :Regex '%s' matches '%s', removing voiced status from '%s'",
                who, args[4], scan->mask, scan->mask);
        }
        if (tmp == NULL)
          user = scan->next;
        else
          tmp->next = scan->next;
        free(scan);
        rm++;
      }
    }
    if (rm != 0) {
      /* If anyone was removed, printing the reminder that these changes are not permanent. */
      sendl(sock, "NOTICE %s :Remember, these changes are not (yet) permanent.", who);
      sendl(sock, "NOTICE %s :To make these changes permanent you must remove the demoted user(s) from the config file after stopping the bot.", who);
    }
    else {
      sendl(sock, "NOTICE %s :Sorry, no hits with your search term '%s'", who, args[4]);
    }
#ifdef DEBUG
    UPTR sscan = user;
    while(sscan) {
      dinfo(sscan->mask);
      sscan = sscan->next;
    }
#endif
  }
  else
    NOAUTH;
}

void addop(CARGS) {
  if (own == 1 && react == 1) {
    /* Adding new owners. -perm adds them permanently (aka prints the hostmask
     * to the config file too),
     * and -temp adds temporarily, lasting only until the bot is shut down.
     */

    int ok = 0; /* Switch to check if the syntax was correct. */
    if (arg_count != 3) { /* Exactly three arguments are required. */
      sendl(sock, "NOTICE %s :Syntax: %caddop (-perm|-temp) hostmask", who, IRC_CMD_TRIGGER);
    }
    else {
      /* If it was for temporary use only, we can switch ok on and just continue. */
      if ((strcmp(args[4], "-temp")) == 0)
        ok = 1;

      if ((strcmp(args[4], "-perm")) == 0) {
        /* If (s)he was added permanently, we need to open the config file for appending,
         * and write the new owner there.
         * Then we can continue.
         */

        gg = fopen(CONFFILE, "a+");
        fprintf(gg, "owners += { \"%s\" }\n", args[5]);
        ok = 1;
        fflush(gg);
        fclose(gg);
      }
      if (ok == 1) {
        inslist_usr(1, args[5]); /* Here we'll insert the hostmask to users. 1 means it will be added as a owner. */
        sendl(sock, "NOTICE %s :Added hostmask '%s' to owners.", who, args[5]);
      }
      else {
        sendl(sock, "NOTICE %s :Syntax: %caddop (-perm|-temp) hostmask", who, IRC_CMD_TRIGGER);
      }
#ifdef DEBUG
      UPTR scan = user;
      while(scan) {
        dinfo(scan->mask);
        scan = scan->next;
      }
#endif
    }
  }
  else
    NOAUTH;
}
void addvoice(CARGS) {
  if (own == 1 && react == 1) {
    /* Same function as addop, but just for voices. See the comments there
     * if you need something explained.
     */

    int ok = 0;
    if (arg_count != 3) {
      sendl(sock, "NOTICE %s :Syntax: %caddvoice (-perm|-temp) hostmask", who, IRC_CMD_TRIGGER);
    }
    else {
      if ((strcmp(args[4], "-temp")) == 0) {
        ok = 1;
      }
      if ((strcmp(args[4], "-perm")) == 0) {
        gg = fopen(CONFFILE, "a+");
        fprintf(gg, "voices += { \"%s\" }\n", args[5]);
        fflush(gg);
        fclose(gg);
        ok = 1;
      }
      if (ok == 1) {
        sendl(sock, "NOTICE %s :Added hostmask '%s' to voices list.", who, args[5]);
        inslist_usr(2, args[5]);
      }
      else {
        sendl(sock, "NOTICE %s :Syntax: %caddvoice (-perm|-temp) hostmask", who, IRC_CMD_TRIGGER);
      }
    }
  }
  else
    NOAUTH;
}

/* Listing all commands */
void cmd_list(CARGS) {
  if (((strcmp(args[4], "-y")) == 0) || (strcmp(args[4], "-yes") == 0)) {
    /* If the listing was confirmed with the -yes trigger, then
     * we'll print all the commands.
     */

    LISTPTR prun = cmd;
    while(prun) {
      sendl(sock, "NOTICE %s :%s - %s", who, prun->cmd_name_p, prun->desc_p);
      prun = prun->next;
      sleep(1);
      /* We will want to sleep for a moment between each sent line to avoid getting removed
       * from the network because of excessive spam.
       */

    }
  }
  else {
    LISTPTR run = cmd;
    int g = 0;
    while(run) {
      g++;          /* Checking how many commands there are. */
      run = run->next;
    }
    sendl(sock, "NOTICE %s :There are %i different commands currently available.", who, g);
    sendl(sock, "NOTICE %s :If you want to see all of them, type -list -yes", who);
    sendl(sock, "NOTICE %s :All the commands are also explained here to avoid spam: http://cene.ath.cx/scb/commands.html", who);
  }
}

/* Listing all users. */
void listusers(CARGS) {
  if (own == 1 && react == 1) {
    UPTR q = user;
    while(q) {
      if (q->lvl == 1)
        sendl(sock, "NOTICE %s :User level 1 (op) - %s", who, q->mask);
      else if (q->lvl == 2)
        sendl(sock, "NOTICE %s :User level 2 (voice) - %s", who, q->mask);
      q = q->next;
    }
  }
  else
    NOAUTH;
}

/* Rehashing,
 * aka reloading the config file for changes on
 * variables and/or owners.
 *
 * Currently crashes if variables are changed, and feels
 * a bit unrealiable otherwise too. Cleaning of the users list is a bit ugly too.
 *
 * Any ideas on how to make this thing work better are welcome.
 */

void rehash(CARGS) {
  dwarn("This really sucks. Don't be surprised if the bot crashes. Any ideas on how to make it better are welcome.");
  if (own == 1 && react == 1) {
    sendl(sock, "NOTICE %s :Rehashing.. ", who);
   
   
    /* Clearing the lists so they can be reassigned */
    UPTR tmp = user,
         L = user;
   
    int i = 0;
    while(L != NULL) {
      L = L->next;
      tmp->next = NULL;
      if (i != 0)
        free(tmp);
      tmp = L;
      i++;
    }


#ifdef DEBUG
    UPTR dscan = user;
      while(dscan) {
        dinfo(dscan->mask);
        dscan = dscan->next;
      }
#endif
   
    int c;
    if ((c = parse