#ifdef RCS
static char rcsid[]="$Id: ctcp.c,v 1.1.1.1 2000/11/13 02:42:41 holsta Exp $";
#endif
/******************************************************************************
 *                    Internetting Cooperating Programmers
 * ----------------------------------------------------------------------------
 *
 *  ____    PROJECT
 * |  _ \  __ _ _ __   ___ ___ _ __ 
 * | | | |/ _` | '_ \ / __/ _ \ '__|
 * | |_| | (_| | | | | (_|  __/ |   
 * |____/ \__,_|_| |_|\___\___|_|   the IRC bot
 *
 * All files in this archive are subject to the GNU General Public License.
 *
 * $Source: /cvsroot/dancer/dancer/src/ctcp.c,v $
 * $Revision: 1.1.1.1 $
 * $Date: 2000/11/13 02:42:41 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "function.h"
#include "user.h"
#include "transfer.h"
#include "flood.h"
#include "netstuff.h"
#include "ctcp.h"
#include "ibcp.h"
#include "fplrun.h"
#include "link.h"

/* --- Global ----------------------------------------------------- */

extern time_t now;

extern char channel[];
extern char nickname[], ctcpversion[], ctcpuserinfo[];
extern char opaction[];
extern bool avalance, uppercheck;
extern bool floodmode, xdccmode, repeatmode;
extern bool ctcpmode, shuttingdown;
extern bool public;
extern int serverport;
extern int opactionlen;
extern long levels[];

bool ctcpignore = FALSE;
int ctcpcount = 0;
int ctcpignorednum = 0;
time_t lastctcptime = 0;
time_t ctcpignorestart = 0;
time_t ctcptimeout; /* the actual time-out, no more replies for a while */

/* --- Private CTCPs ---------------------------------------------- */

struct
{
  char *name;
  void (*function)(char *a, char *b);
} ctcps[] =
{
  {"PING",       CtcpPing},
  {"DCC",        CtcpDCC},
  {"CLIENTINFO", CtcpClientinfo},
  {"VERSION",    CtcpVersion},
  {"TIME",       CtcpTime},
  {"USERINFO",   CtcpUserinfo},
  {NULL, (void(*)())(NULL)}
};

/* --- Public CTCPs ----------------------------------------------- */

struct
{
  char *name;
  void (*function)(char *a, char *b);
} pubctcps[] =
{
  {"ACTION",     CtcpAction},
  {"PING",       CtcpPing},
  {"CLIENTINFO", CtcpClientinfo},
  {"VERSION",    CtcpVersion},
  {"TIME",       CtcpTime},
  {"USERINFO",   CtcpUserinfo},
  {"XDCC",       CtcpXdcc},
  {NULL, (void(*)())(NULL)}
};


/* --- IgnoreCtcp ------------------------------------------------- */

/*
 * This deals with private CTCP commands to the bot. CTCPFloodCheck()
 * takes care of the ones in the channel.
 */
bool IgnoreCtcp(char *from, char *cmd)
{
  /* Got more than 3 requests within a certain time */
  if ((now - lastctcptime) <= 3) {
    if (++ctcpcount > 2) {
      lastctcptime = now;
      if (!ctcpignore) {
        char buf[BIGBUFFER];

        StrFormatMax(buf, sizeof(buf), "Flood: Ignoring %s flood by %s", cmd, from);
        Multicast(REPORTCAST, buf);
        Log(LOGCTCP, buf);
      }
      CTCPignore();
      ctcpignore = TRUE;
      return TRUE; /* Ignore request */
    }
  }
  else
    ctcpcount = 0;

  lastctcptime = now;
  ctcpignore = FALSE;
  return FALSE;
}

void CTCPignore(void)
{
  if (0 == ctcpignorednum)
    ctcpignorestart = now;
  ctcptimeout = now + TIME_CTCPIGNORE;
  ctcpignorednum++;
}

/* --- CTCP ------------------------------------------------------- */

void CTCP(char *from, char *line)
{
  char nick[NICKLEN+1], msg[BIGBUFFER];
  char *cmd, *param;
  int i;
  itemguest *g;

  snapshot;
  CtcpUnquote(msg, sizeof(msg), line);

#ifdef HAVE_LIBFPL
  if (runfpl(RUN_CTCPPRIV, msg, FPLRUN_PRE))
    return;
#endif

  cmd = StrTokenize(msg, " ");
  if (cmd) {
    param = StrTokenize(NULL, "");

    /*
     * We will always let LEVELCHANOP+ users' DCC chat requests through!
     * (when they're in the channel with us)
     */
    if (StrEqual(cmd, "DCC") &&
        (1 == StrScan(from, "%"NICKLENTXT"[^!]", nick)) &&
        (g = FindNick(nick)) &&
        (g->ident->level >= LEVELCHANOP))
      ;
    else {
      if (now < ctcptimeout) {
        /* Ignoring CTCPs */
        CTCPignore();
        /*Logf(LOGCTCP, "Ignores %s from %s", cmd, from);*/
        return; /* ignored */
      }
      else if (IgnoreCtcp(from, cmd)) {
        return;
      }

      if (ctcpignorednum > 0) {
        Logf(LOGCTCP, "Ignored %d CTCP requests in %s at the last flood",
             ctcpignorednum,
             TimeAgo(ctcptimeout - TIME_CTCPIGNORE - ctcpignorestart + now));
        ctcpignorednum = 0;
      }
    }

    /* Handle normal private ctcp request */
    for (i=0; ctcps[i].name; i++) {
      if (StrEqual(ctcps[i].name, cmd)) {
        ctcps[i].function(from, param);
        break;
      }
    }
  }

  runfpl(RUN_CTCPPRIV, msg, FPLRUN_POST);
}

/* --- PubCTCP ---------------------------------------------------- */

void PubCTCP(char *from, char *line)
{
  char nick[NICKLEN+1], msg[BIGBUFFER];
  char *cmd, *param;
  int i;
  itemguest *g;

  snapshot;
  CtcpUnquote(msg, sizeof(msg), line);

#ifdef HAVE_LIBFPL
  if (runfpl(RUN_CTCPPUB, msg, FPLRUN_PRE))
    return;
#endif

  if (1 == StrScan(from, "%"NICKLENTXT"[^!]", nick)) {
    g = FindNick(nick);
    if (g) {
      /* Handle public CTCP avalances */
      if (avalance)
        AvalanceCheck(g, msg);
    }

    /* Slower flood check on ACTION */
    if (CTCPFloodCheck(g, StrEqualMax(msg, 6, "ACTION")) ||
        (now < ctcptimeout)) {
      /* ignoring CTCPs */
      CTCPignore();
      /*Logf(LOGCTCP, "Ignores channel '%s' from %s", msg, from);*/
      return;
    }

    if (ctcpignorednum > 0) {
      Logf(LOGCTCP, "Ignored %d channel CTCP requests in %s at the last flood",
           ctcpignorednum,
           TimeAgo(ctcptimeout - TIME_CTCPIGNORE - ctcpignorestart + now));
      ctcpignorednum = 0;
    }

    cmd = StrTokenize(msg, " ");
    if (cmd) {
      param = StrTokenize(NULL, "");
      for (i=0; pubctcps[i].name; i++) {
        if (StrEqual(pubctcps[i].name, cmd)) {
          pubctcps[i].function(from, param);
          break;
        }
      }
    }
  }

  runfpl(RUN_CTCPPUB, msg, FPLRUN_POST);
}

/* --- CtcpXdcc --------------------------------------------------- */

void CtcpXdcc(char *from, char *msg)
{
  if (xdccmode) {
    itemguest *g;

    StrTokenize(from, "!");
    g = FindNick(from);
    if (g)
      StickyKick(g, "xdcc this!");
  }
}

/* --- CtcpClientinfo --------------------------------------------- */

void CtcpClientinfo(char *from, char *msg)
{
  if (public)
    Logf(LOGCTCP, "Clientinfo on %s from %s", channel, from);
  else
    Logf(LOGCTCP, "Clientinfo from %s", from);
  StrTokenize(from, "!");
  ReplyCtcpf(from, "CLIENTINFO %s", CLIENTINFOMSG);
}

/* --- CtcpPing --------------------------------------------------- */

void CtcpPing(char *from, char *msg)
{
  if (public)
    Logf(LOGCTCP, "Ping on %s from %s", channel, from);
  else
    Logf(LOGCTCP, "Ping from %s", from);
  StrTokenize(from, "!");
  if (msg && *msg)
    ReplyCtcpf(from, "PING %s", msg);
  else
    ReplyCtcp(from, "ERRMSG Incorrect Ping request - time missing");
}

/* --- CtcpVersion ------------------------------------------------ */

void CtcpVersion(char *from, char *msg)
{
  if (public)
    Logf(LOGCTCP, "Version on %s from %s", channel, from);
  else
    Logf(LOGCTCP, "Version from %s", from);
  StrTokenize(from, "!");
  ReplyCtcpf(from, "VERSION %s", ctcpversion);
}

/* --- CtcpUserinfo ----------------------------------------------- */

void CtcpUserinfo(char *from, char *msg)
{
  if (public)
    Logf(LOGCTCP, "Userinfo on %s from %s", channel, from);
  else
    Logf(LOGCTCP, "Userinfo from %s", from);
  StrTokenize(from, "!");
  ReplyCtcpf(from, "USERINFO %s", ctcpuserinfo);
}

/* --- CtcpTime --------------------------------------------------- */

void CtcpTime(char *from, char *msg)
{
  char timebuf[MIDBUFFER];
  time_t time = now;

  if (public)
    Logf(LOGCTCP, "Time on %s from %s", channel, from);
  else
    Logf(LOGCTCP, "Time from %s", from);
  StrTokenize(from, "!");
  if (time != -1) {
    StrCopyMax(timebuf, sizeof(timebuf), ctime(&time));
    StrTokenize(timebuf, "\n"); /* ctime() appends a \n, get rid of it */
    ReplyCtcpf(from, "TIME %s", timebuf);
  }
  else
    ReplyCtcp(from, "ERRMSG Unable to get systemtime");
}

/* --- CtcpAction ------------------------------------------------- */

void CtcpAction(char *from, char *msg)
{
  itemguest *g;

#ifdef HAVE_LIBFPL
  if (runfpl(RUN_ACTION, msg, FPLRUN_PRE))
    return;
#endif

  StrTokenize(from, "!");
  g = FindNick(from);
  if (g) {
    if (opaction[0]) {  /* A kind of passive auto-op */
      if ((g->ident->level >= LEVELBOT) &&
          StrEqualMax(opaction, opactionlen, msg)) {
        Mode("+o %s", from);
      }
    }
    g->posts++;

    Check(g, msg);

    if (repeatmode)
      RepeatCheck(g, msg);

    g->posttime = now;
    Logf(LOGPUB, "* %s %s", from, msg ? msg : "");
  }
  runfpl(RUN_ACTION, msg, FPLRUN_POST);
}

/* --- OpenDccChat ------------------------------------------------ */

void OpenDccChat(char *from, char *userhost, ulong num, ushort port)
{
  itemuser *u;
  itemclient *k;

  u = FindUser(from, userhost);
  if (u) {
    k = FindClientByNick(from);
    if (k) { /* Only one client per nick */
      if (k->status == CL_CONNECTED)
        ReplyCtcp(from, "ERRMSG Another connection already exist");
      else
        ReplyCtcp(from, "ERRMSG Another attempt to connect exist. Use CUT to cancel that attempt");
    }
    else
      OpenClientConnection(from, userhost, u, MakeIP(num), port);
  }
  else /* Only allow registered users */
    ReplyCtcp(from, "ERRMSG No authentication");
}

/* --- OpenUdpLink ------------------------------------------------ */

void OpenUdpLink(char *from, char *userhost, ulong num, ushort port)
{
  itemuser *u;
  itemlink *r;

  u = FindUser(from, userhost);
  if (u && u->flags.linkbot) {
    r = AddLink(from, u, num, port);
    if (r) {
      r->tmpvalue = Random();
      SendLink(r, IBCP_HELLO, "%d %d %s %lu",
               IBCP_VERSION, IBCP_BOT_DANCER, nickname, r->tmpvalue);
    }
    else
      ReplyCtcp(from, "ERRMSG Insufficient resources");
  }
  else
    ReplyCtcp(from, "ERRMSG No authentication");
}

/* --- CtcpDCC ---------------------------------------------------- */

void CtcpDCC(char *from, char *msg)
{
  char darg[BIGBUFFER];
  char snum[MIDBUFFER];
  char sport[MIDBUFFER];
  char type[5];
  char *userhost;
  ushort port;

  if (msg && *msg) {
    Logf(LOGCTCP, "DCC request from %s (%s)", from, msg);

    StrTokenize(from, "!");
    userhost = StrTokenize(NULL, "");

    if (shuttingdown) {
      ReplyCtcp(from, "ERRMSG Shutdown in progress. Cannot accept connections");
      return; /* Make sure we exit */
    }

    if (4 == StrScan(msg, "%4s %"BIGBUFFERTXT"s %"MIDBUFFERTXT"s %"MIDBUFFERTXT"s",
                     type, darg, snum, sport)) {
      port = atol(sport);
      if (port == serverport) {
        /* User sucks, don't even answer */
        Debug("%s (%s) attempted to DCC to serverport (%d)",
              from, userhost, port);
      }
      else if (port < 1024) {
        ReplyCtcp(from, "ERRMSG Connection rejected. Privileged port");
        Debug("%s (%s) rejected - privileged port (%d)",
              from, userhost, port);
      }
      else {
        if (StrEqual(type, "CHAT")) {
          /* darg ought to be "chat", but some clients use username
             instead. We'll just ignore it. */
          OpenDccChat(from, userhost, atoul(snum), port);
        }
        else if (StrEqual(type, "LINK") && StrEqual(darg, "UDP")) {
          OpenUdpLink(from, userhost, atoul(snum), port);
        }
        else {
          ReplyCtcp(from, "ERRMSG Unsupported DCC request");
          Debug("Unsupported DCC request: %s %s", type, darg);
        }
      }
    }
    else {
      ReplyCtcp(from, "ERRMSG Erroneous DCC request");
      Debug("Wrong number of DCC arguments");
    }
  }
}

/* --- Quoting ---------------------------------------------------- */

#define CTCPQUOTE '\020'
#define CTCPQUOTE_FIRST '\000'
#define CTCPQUOTE_LAST '\037' /* Should be \040 */
#define CTCPQUOTE_OFFSET '\101'

void CtcpQuote(char *target, size_t len, char *source)
{
  snapshot;
#if 0
  register char c;

  while (c = *source++) {
    if (c == CTCPQUOTE)
      *target++ = CTCPQUOTE;
    else if ((c >= CTCPQUOTE_FIRST) && (c <= CTCPQUOTE_LAST)) {
      *target++ = CTCPQUOTE;
      c += CTCPQUOTE_OFFSET;
    }
    *target++ = c;
  }
  *target = (char)0;
#else
  StrCopyMax(target, len, source);
#endif
}

void CtcpUnquote(char *target, size_t len, char *source)
{
  snapshot;
#if 0
  register char c;

  while (c = *source++) {
    if (c == CTCPQUOTE) {
      c = *source++;
      if ((c >= (CTCPQUOTE_FIRST + CTCPQUOTE_OFFSET)) &&
	  (c <= (CTCPQUOTE_LAST + CTCPQUOTE_OFFSET)))
        c -= CTCPQUOTE_OFFSET;
    }
    *target++ = c;
  }
  *target = (char)0;
#else
  StrCopyMax(target, len, source);
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1