/*

  This module implements a frontend to the whois database, using the "whois"
shell command commonly found in unixes. It has *nothing* to do with irc's
whois. You should try to use it in your shell to know what i'm talking
about, in case you're unaware of its informative potencial. I use
whois.ripe.net, which means it returns info about IPs.

*/

#include "mbot.h"

#define LEVEL_WHOIS 0

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

/////////////////////
// class definition
/////////////////////

class CWhois {
public:
  CWhois (CNetServer *);
  ~CWhois (void);

  bool start (c_char, c_char);
  void parse (void);
  void work (void);
  void stop (void);

  bool in_use;
  CNetServer *s;					// server to which belongs

private:

  int pipes[2];
  pid_t pid;
  size_t linepos;
  char *host, *dest, text[MSG_SIZE+1], buf[MSG_SIZE+1], line[MSG_SIZE+1];

};

CList *whois_list;

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

static void whois_cmd_whois (CNetServer *);

static CWhois *server2whois (CNetServer *);
static void whois_conf (CNetServer *, c_char);
static void whois_stop (CModule *);
static void whois_start (CModule *);
static void whois_work (CNetServer *);

////////////////////
// class functions
////////////////////

CWhois::CWhois (CNetServer *server)
{
  in_use = 0;
  s = server;
  host = dest = NULL;
}

CWhois::~CWhois (void)
{
  if (host != NULL)
    free (host);
  if (dest != NULL)
    free (dest);
}

bool
CWhois::start (c_char hostname, c_char destination)
{
  strset (&host, hostname, HOST_SIZE);
  strset (&dest, destination, CHANNEL_SIZE);
  if (pipe (pipes) == -1)
    {
      perror ("pipe()");
      return 0;
    }
  pid = fork ();
  switch (pid)
    {
      case -1:
        close (pipes[0]);
        close (pipes[1]);
        return 0;
      case 0:
        snprintf (buf, MSG_SIZE, "-a %s@whois.ripe.net", host);
        dup2 (pipes[1], 1);
        dup2 (pipes[1], 2);
#ifdef WHOIS_PATH
        execl (WHOIS_PATH, "whois", buf, NULL);
        s->write_botlog ("error executing %s: %s", WHOIS_PATH, strerror (errno));
#endif
        exit (0);
    }
  close (pipes[1]);
  s->works.add ((void *)whois_work);
  in_use = 1;
  text[0] = 0;
  linepos = 0;
  return 1;
}

void
CWhois::parse (void)
{
  char buf2[2][MSG_SIZE+1], line[MSG_SIZE+1];
  for (size_t i = 0; buf[i] != 0; i++)
    {
      if (linepos < MSG_SIZE)
        line[linepos++] = buf[i];
      if (buf[i] == '\n' || linepos == MSG_SIZE)
        {
          line[linepos] = 0;
          linepos = 0;
          strip_crlf (line);
          strsplit (line, buf2, 1);
          if (strcasecmp (buf2[0], "descr:") == 0
              || strcasecmp (buf2[0], "country:") == 0
              || strcasecmp (buf2[0], "person:") == 0)
            {
              if (text[0] == 0)
                my_strncpy (text, buf2[1], MSG_SIZE);
              else
                {
                  char buf3[MSG_SIZE+1];
                  my_strncpy (buf3, text, MSG_SIZE);
                  snprintf (text, MSG_SIZE, "%s, %s", buf3, buf2[1]);
                }
            }
        } 
    }
}

void
CWhois::work (void)
{
  if (CNet::readok (pipes[0]))
    {
      int i = read (pipes[0], buf, MSG_SIZE);
      if (i == 0 || i == -1)
        {
          if (text[0] == 0)
            SEND_TEXT (dest, "No info returned for %s.", host);
          else
            SEND_TEXT (dest, "Whois %s: %s", host, text);
          stop ();
          return;
        }
      buf[i] = 0;
      parse ();
    }
}

void
CWhois::stop (void)
{
  s->works.del ((void *)whois_work);
  close (pipes[0]);
  kill (pid, SIGKILL);		// needed for !moddel
  waitpid (pid, NULL, 0);
  in_use = 0;
}

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

// !whois ip
static void
whois_cmd_whois (CNetServer *s)
{
#ifndef WHOIS_PATH
  SEND_TEXT (DEST, "This command is not available.");
#else
  CWhois *whois = server2whois (s);
  if (whois == NULL)
    {
      SEND_TEXT (DEST, "This command is not available.");
      return;
    }
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !whois ip");
      return;
    }
  if (whois->in_use)
    SEND_TEXT (DEST, "Another request is being dispatched.");
  else if (!whois->start (BUF[1], DEST))
    SEND_TEXT (DEST, "Whois request failed.");
#endif		// !WHOIS_PATH
}

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

// return the whois for a given server, NULL if nonexistant
static CWhois *
server2whois (CNetServer *s)
{
  CWhois *a;
  whois_list->rewind ();
  while ((a = (CWhois *)whois_list->next ()) != NULL)
    if (a->s == s)
      return a;
  return NULL;
}

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

  strsplit (bufread, buf, 1);

  if (strcasecmp (buf[0], "bot") == 0)
    {
      CWhois *whois = new CWhois (s);
      if (whois == NULL)
        s->bot->conf_error ("error initializing whois");
      whois_list->add ((void *)whois);
      s->script.bind_cmd (whois_cmd_whois, LEVEL_WHOIS, "!whois");
    }

}

// module termination
static void
whois_stop (CModule *m)
{
  CWhois *whois;
  whois_list->rewind ();
  while ((whois = (CWhois *)whois_list->next ()) != NULL)
    {
      if (whois->in_use)
        whois->stop ();
      whois->s->script.unbind_cmd ("!whois");
      delete whois;
    }
  delete whois_list;
}

// module initialization
static void
whois_start (CModule *m)
{
  whois_list = new CList ();
}

static void
whois_work (CNetServer *s)
{
  CWhois *whois = server2whois (s);
  if (whois == NULL)
    s->works.del ((void *)whois_work);
  else
    whois->work ();
}

struct CModule::module_type module = {
  MODULE_VERSION,
  "whois",
  whois_start,
  whois_stop,
  whois_conf,
  NULL
};

