#ifdef RCS
static char rcsid[]="$Id: transfer.c,v 1.1.1.1 2000/11/13 02:42:49 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/transfer.c,v $
 * $Revision: 1.1.1.1 $
 * $Date: 2000/11/13 02:42:49 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "function.h"
#include "user.h"
#include "transfer.h"
#include "netstuff.h"
#include "seen.h"
#include "servfunc.h"
#include "link.h"
#include "flood.h"
#include "bans.h"
#include "command.h"
#include "ctcp.h"

#include <stdarg.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

extern time_t now;

extern char nickname[];
extern char channel[];
extern char logfile[];
extern char botmatch[];
extern bool botop;
extern bool chat;
extern bool connected;
extern bool cleanup;
extern bool restart;
extern bool execprotect;
extern int lastpost;
extern fd_set rdset;
extern itemclient *client;
extern itemclient *clientHead;
extern itemguest *guestHead;
extern itemident *current;

struct Execstruct ExecHead = {
  NULL, NULL, NULL, NULL, -1, NULL, FALSE
};

struct Msgstruct MsgHead = {
  NULL, NULL, NULL, NULL
};

int kickQ = 0; /* Number of kicks in the queue */

itemexec *execHead = &ExecHead;
itemmsg *msgHead = &MsgHead;


/* --- SendNick --------------------------------------------------- */
/* Send directly to nick */

inline void SendNick(char *nick, char *msg)
{
  itemmsg *m;

  snapshot;
  m = NewEntry(itemmsg);
  if (m) {
    InsertLast(msgHead, m);
    m->nick = StrDuplicate(nick);
    m->msg = StrDuplicate(msg);
  }
}

void SendNickf(char *nick, const char *format, ...)
{
  char buffer[BIGBUFFER];
  va_list args;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  SendNick(nick, buffer);
}

/* --- Send ------------------------------------------------------- */
/* Send to client if possible, else to nick */

void Send(char *nick, char *msg)
{
  snapshot;
  if (msg) {
    if (chat) {
      if (client->status == CL_CONNECTED) {
	/* Ship immediately to current client */
	WriteSocket(client->socket, msg);
      }
      else {
	SendNick(client->ident->nick, msg);
      }
    }
    else if (nick) {
      /* This is only /msg, don't flood */
      SendNick(nick, msg);
    }
  }
}

void Sendf(char *nick, const char *format, ...)
{
  char buffer[BIGBUFFER];
  va_list args;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  Send(nick, buffer);
}

/* --- SendMulti -------------------------------------------------- */

void SendMulti(char *nick, const char *format, ...)
{
  char *buffer, *pointer;
  size_t size = MAXLINE - 10; /* Preserve space for CR-LF and some extra */
  va_list args;

  snapshot;

  va_start(args, format);
  buffer = trio_vaprintf(format, args);
  va_end(args);

  if (buffer) {
    if (nick) {
      /* Preserve space for ":<botmatch> NOTICE <from> :" */
      size -= (StrLength(botmatch) + StrLength(nick) + 20);
    }

    for (pointer = StrSplitMax(buffer, size); pointer;
         pointer = StrSplitMax(NULL, size)) {
      Send(nick, pointer);
    }

    free(buffer);
  }
}

/* --- SendCtcp --------------------------------------------------- */

void ReplyCtcp(char *nick, char *msg)
{
  char buffer[BIGBUFFER];

  snapshot;
  CtcpQuote(buffer, sizeof(buffer), msg);
  WriteServer("NOTICE %s :\001%s\001", nick, buffer);
}

void ReplyCtcpf(char *nick, const char *format, ...)
{
  char buffer[BIGBUFFER];
  va_list args;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  ReplyCtcp(nick, buffer);
}

void SendCtcp(char *nick, char *msg)
{
  char buffer[BIGBUFFER];

  snapshot;
  CtcpQuote(buffer, sizeof(buffer), msg);
  WriteServer("PRIVMSG %s :\001%s\001", nick, buffer);
}

void SendCtcpf(char *nick, const char *format, ...)
{
  char buffer[BIGBUFFER];
  va_list args;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  SendCtcp(nick, buffer);
}

/* --- Multicast -------------------------------------------------- */
/* types: see *CAST in header file */

void MulticastLocal(int type, char *msg)
{
  itemclient *p;

  for (p = First(clientHead); p; p = Next(p)) {
    if ((type&CHATCAST) &&         /* this is a CHAT message */
        current &&
        current->client &&         /* the current user has a client connected */
        (current->client == p) &&  /* this client is the current user's */
        !p->chatecho)              /* this client has turned off echo */
      continue; /* don't send anything to this client */
    if ((p->status == CL_CONNECTED) && (p->flags & type)) {
      WriteSocket(p->socket, msg);
    }
  }
}

void MulticastLocalf(int type, const char *format, ...)
{
  char buffer[BIGGERBUFFER];
  va_list args;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  MulticastLocal(type, buffer);
}

void Multicast(int type, char *msg)
{
  MulticastLocal(type, msg);
  SendLinkAll(IBCP_MULTICAST, "%d %s", type, msg);
}

void Multicastf(int type, const char *format, ...)
{
  char buffer[BIGGERBUFFER];
  va_list args;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  Multicast(type, buffer);
}

/* --- FreeMessage ------------------------------------------------ */

void FreeMessage(void *v)
{
  itemmsg *m;

  snapshot;
  m = (itemmsg *)v;
  if (m) {
    if (m->nick)
      StrFree(m->nick);
    if (m->msg)
      StrFree(m->msg);
  }
}

void FlushMessages(void)
{
  FlushList(msgHead, FreeMessage);
}

/* --- MessageQueue ----------------------------------------------- */
/* Used to send delayed messages */

void MessageQueue(void)
{
  static time_t lastsent = 0;
  static int sentmsgs = 0;
  itemmsg *m;

  snapshot;
  if (connected) {
    if (kickQ && botop) {
      if (!KickFromQueue(&lastsent))
        kickQ = 0; /* cleared */
    }
    else {
      AlertMode(ALERT_OFF); /* Switches off alert mode with timeout */
      /*
       * Send queued messages if present and we haven't sent too many too fast
       */
      for (m = First(msgHead); m && (sentmsgs < 3); m = First(msgHead), sentmsgs++) {
        WriteNick(m->nick, m->msg);
        lastsent = now;
        DeleteEntry(msgHead, m, FreeMessage);
      }
    }

    if ((sentmsgs > 0) && ((lastsent + 2) <= now)) {
      sentmsgs--;
    }
  }
}

/* --- MessageReaper ---------------------------------------------- */

void MessageReaper(char *target) /* Removes all messages to 'target' */
{
  itemmsg *m, *next;

  snapshot;
  for (m = First(msgHead); m; m = next) {
    next = Next(m);

    if (StrEqual(m->nick, target))
      DeleteEntry(msgHead, m, FreeMessage);
  }
}

/* --- Execute ---------------------------------------------------- */
/* Remember to quote ALL user input to avoid cracking.
 * Remember to redirect stderr to stdout by appending "2>&1"
 */

itemexec *Exec(char *nick, char *cmd)
{
  itemexec *px;
  FILE *f;

  snapshot;
  f = popen(cmd, "r");
  if (f) {
    px = NewEntry(itemexec);
    if (px) {
      InsertLast(execHead, px);
      px->client = client;
      if (nick)
        px->nick = StrDuplicate(nick);
      px->pipe = f;
      px->socket = fileno(px->pipe);

      FD_SET(px->socket, &rdset);
      return px;
    }
  }
  return NULL;
}

bool IsQualifier(char c)
{
  snapshot;
  switch (c) {
    case '-': case '+': case ' ': case '#': case '.':
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
    case 'h': case 'l': case 'L':
      return TRUE;
  }
  return FALSE;
}

itemexec *Execute(char *nick, char *format, ...)
{
  char buffer[BIGGERBUFFER];
  char *pointer, *p;
  bool wrong = FALSE;
  va_list args;

  snapshot;
  if (execprotect) {
    Send(nick, GetText(msg_execprotect_on));
    return NULL;
  }

  /*
   * Search string arguments for end-quotes.
   * These checks are done to avoid cracking.
   * Note: if user input isn't quoted they can
   *       access ';', '&', '|' and '^' special
   *       characters.
   */
  va_start(args, format);
  for (pointer = format; *pointer; pointer++) {
    if ('%' == *pointer) {
      for (pointer++; IsQualifier(*pointer); pointer++);

      if ((char)0 == *pointer)
        break;

      switch (*pointer) {

        /* Examine strings */
        case 's':
          p = va_arg(args, char *);
          while (*p) {
            switch (*p++) {

              case '"':
              case '`':
#if 0
              case ';':
              case '&':
              case '|':
              case '^':
#endif

                wrong = TRUE;
                break;

              default:
                break;
            }
          }
          break;

        /* Ignore the rest */
        case 'e': case 'E':
        case 'g': case 'G':
        case 'f':
          va_arg(args, double);
          break;

        case 'd': case 'i':
        case 'o': case 'u':
        case 'x': case 'X':
        case 'c':
          va_arg(args, int);
          break;

        case 'p':
          va_arg(args, void *);
          break;

        default:
          va_arg(args, void *);
          break;
      }
    }
  }

  if (wrong) {
    Send(nick, GetText(msg_cannot_do_that));
  }
  else {
    va_start(args, format);
    trio_vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);

    return Exec(nick, buffer);
  }

  return NULL;
}

void FreeExec(void *v)
{
  itemexec *px;

  px = (itemexec *)v;
  if (v) {
    if (px->nick)
      StrFree(px->nick);
    if ((0 <= px->socket) && FD_ISSET(px->socket, &rdset))
      FD_CLR(px->socket, &rdset);
    if (px->pipe)
      pclose(px->pipe);
  }
}

/* --- GotExec ---------------------------------------------------- */
/* Ought to check if socket is still active */

void GotExec(itemexec *px)
{
  char buffer[MAXLINE];

  snapshot;
  chat = (px->client != NULL);

  if (fgets(buffer, sizeof(buffer), px->pipe)) {
    if (StrTokenize(buffer, "\r\n") == buffer) {
      client = px->client;
      Send(px->nick, buffer);
    }
  }
  else {
    if (px->notify) {
      /* This is a file transfer client, we should therefor set the END OF
         TRANSFER bit in that struct to let it know */
      itemclient *c;

      for (c = First(clientHead); c; c = Next(c)) {
        if (c->ident && StrEqual(c->ident->nick, px->nick) &&
            (OUT_SEND == c->linksort)) { /* Get the send, nothing else */
          c->fileflags |= CLF_FILECOMPLETE;
          break;
        }
      }
    }

    client = px->client;
    if (client) {
      Send(NULL, GetText(msg_done));
    }
    else if (px->nick && !IsChannel(px->nick)) {
      Send(px->nick, GetText(msg_done));
    }

    DeleteEntry(execHead, px, FreeExec);
  }
}

/* --- SnoopCommand ----------------------------------------------- */

void SnoopCommand(char *from, char *cmd, char *param)
{
  char buffer[BIGGERBUFFER];
  char *who;
  struct Command *command;

  snapshot;

  /* Skip if stealth user or no clients are attached */
  if (First(clientHead) && current && ((current->user &&
      !current->user->flags.stealth) || !current->user)) {

    who = (from ? from : client->ident->nick);
    if (cmd && *cmd) {
      command = FindCommand(cmd);
      /* Filter certain command from spylink */
      if (command && command->hide) {
        StrFormatMax(buffer, sizeof(buffer), GetDefaultText(msg_spy_hidden_command),
                     who, cmd, GetDefaultText(msg_snoop_prevented));
      }
      else {
        StrFormatMax(buffer, sizeof(buffer), GetDefaultText(msg_spy_who_did_what),
                     who, cmd, *param ? " " : "", param);
      }
      Multicast(SPYCAST, buffer);
    }
  }
}

/* --- Say -------------------------------------------------------- */

void Say(char *msg)
{
  snapshot;
  WriteServer("PRIVMSG %s :%s", channel, msg);
  lastpost = now;
}

void Sayf(const char *format, ...)
{
  char buffer[BIGBUFFER];
  va_list args;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  Say(buffer);
}

/* --- Action ----------------------------------------------------- */

void Action(char *msg)
{
  snapshot;

  /* If we don't send this raw we can't use i.e control codes etc */
  WriteServer("PRIVMSG %s :\001ACTION %s\001", channel, msg);
  lastpost = now;
}

void Actionf(const char *format, ...)
{
  char buffer[BIGBUFFER];
  va_list args;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  Action(buffer);
}

/* --- Mode ------------------------------------------------------- */

void Mode(const char *format, ...)
{
  char buffer[BIGBUFFER];
  va_list args;

  snapshot;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  WriteServer("MODE %s %s", channel, buffer);
  Logf(LOGDBUG, "sent MODE %s %s", channel, buffer);
}

/* --- Kick ------------------------------------------------------- */

time_t kicklast = 0;
int kickcount = 0;

void Kick(char *nick, char *msg)
{
  itemguest *w;

  snapshot;
  if ((kicklast + 1) >= now)
    kickcount++;
  else
    kickcount = 0;

  w = FindNick(nick);
  if (NULL == w)
    return;

  w->flags.kick = TRUE;
  w->kicktime = now;

  if (!botop || kickQ || (kickcount > 2)) {
    kickQ++; /* One more in the pipe */

    if (NULL == w->kickmsg) /* Only get the first message */
      w->kickmsg = StrDuplicate(msg);

    /* 
     * Stress situation, stop replying to CTCPs for a little while!
     * We do this is we're not chanops too, since then we'll be able to
     * kick this person as soon as we get opped! ;)
     */
    CTCPignore();
    Logf(LOGDBUG, "Enqueued KICK %s", nick);
  }
  else {
    if (!w->flags.kicked) {
      Logf(LOGDBUG, "Sent KICK %s to server", nick);
      WriteServer("KICK %s %s :%s", channel, nick, (msg ? msg : nickname));
      w->flags.kicked = TRUE;
    }
    kicklast = now;
  }
}

void StickyKick(itemguest *w, char *msg)
{
  snapshot;
  AddKick(w->ident, nickname, msg, KICK_BOT);

  if (!w->flags.kick)
    Kick(w->ident->nick, msg);
#if 0
  else
    Logf(LOGDBUG, "Held back KICK %s (already issued)", w->ident->nick);
#endif
}

/* --- Invite ----------------------------------------------------- */

void Invite(char *who)
{
  snapshot;
  WriteServer("INVITE %s %s", who, channel);
}

/* --- Log -------------------------------------------------------- */

int lastlogday = -1;
int logdays = 10;
ulong activelog = -1;

static void RenameAndDelete(void)
{
  char buffer[BIGBUFFER];
  time_t then = now;
  struct tm *t;

  snapshot;
  then -= (SECINDAY); /* 24 hours ago we were in yesterday land */
  t = localtime(&then);
  StrFormatMax(buffer, sizeof(buffer), "%s.%d%02d%02d",
               logfile, 1900 + t->tm_year, t->tm_mon + 1, t->tm_mday);
  rename(logfile, buffer); /* Rename yesterday's logfile */

  then -= (SECINDAY*logdays); /* Even further back */
  t = localtime(&then); /* Get the date to delete */
  StrFormatMax(buffer, sizeof(buffer), "%s.%d%02d%02d",
               logfile, 1900 + t->tm_year, t->tm_mon + 1, t->tm_mday);
  remove(buffer); /* Remove the old one */
}

void LogInit(void)
{
  struct stat stbuf;
  struct tm *t;

  snapshot;
  t = localtime(&now);
  lastlogday = t->tm_yday;

  if (logfile[0]) {
    if (-1 != stat(logfile, &stbuf)) {
      t = localtime(&stbuf.st_mtime);
      if (t->tm_yday != lastlogday) /* Backup logfile */
        RenameAndDelete();
    }
  }
}

/* See enum in header */
char *logtypes[] = {
  "***",    "Join",   "Part",
  "Quit",   "Nick",   "Mode",
  "Kick",   ">>>",    "#>>",
  "-->",    "Ctcp",   "Client",
  "Warn",   "NSplit", "NJoin",
  "NHeal",  "DEBUG",  "DBUG",
  "INIT",   "#",
  "Kill",   "Topic",
  NULL
};

void Log(int type, char *buffer)
{
  extern char servername[];
  extern time_t uptime;
  bool logfile_exists;
  struct tm *t;
  FILE *f;

  t = localtime(&now);

#if defined(DBUG)
  printf("%02d.%02d.%02d %-7s %s\n",
         t->tm_hour, t->tm_min, t->tm_sec, logtypes[type], buffer);

#else /* !DBUG */

  if ((activelog & (1 << type)) && (-1 != lastlogday) && logfile[0]) {
    logfile_exists = FileExist(logfile);
    if (lastlogday != t->tm_yday) {
      lastlogday = t->tm_yday;
      if (logfile_exists) {
        RenameAndDelete();   /* Timestamp logfile */
        t = localtime(&now); /* struct tm is static date that the
                                RenameAndDelete() ruined */
        logfile_exists = FALSE;
      }
    }

    f = fopen(logfile, "a");    
    if (f) {
      if (!logfile_exists) {
        fprintf(f,
                "\n"
                "--- Log for %02d.%02d.%d  Server: %s  Channel: %s\n"
                "--- Nick: %s%s  Version: %s  Started: %s ago\n"
                "\n",
                t->tm_mday, t->tm_mon + 1, t->tm_year,
                servername[0] ? servername : "Not connected",
                (servername[0] && channel[0]) ? channel : "None joined",
                botop ? "@" : "",
                nickname[0] ? nickname : "No nick",
                VERSIONMSG, TimeAgo(uptime));
      }

      fprintf(f, "%02d.%02d.%02d %-7s %s\n",
              t->tm_hour, t->tm_min, t->tm_sec, logtypes[type], buffer);

      fclose(f);
    }
  }
  else if (-1 == lastlogday) {
    fprintf(stderr, "%02d.%02d.%02d %-7s %s\n",
            t->tm_hour, t->tm_min, t->tm_sec, logtypes[type], buffer);
  }
#endif
}

void Logf(int type, const char *format, ...)
{
  char buffer[BIGGERBUFFER];
  va_list args;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  Log(type, buffer);
}

/* --- Snapshot --------------------------------------------------- */

#ifdef DEBUG
#define MAXSNAP 20

static int snapnum = MAXSNAP - 1;

static struct snapstructure {
  char *file;
  int line;
  int hits;
} snap[MAXSNAP];

void MakeSnapshot(char *file, int line)
{
  if ((snap[snapnum].line != line) ||
      (snap[snapnum].file != file)) {
    if (++snapnum >= MAXSNAP)
      snapnum = 0;
    snap[snapnum].file = file;
    snap[snapnum].line = line;
    snap[snapnum].hits = 1;
  }
  else
    snap[snapnum].hits++;
}

void LogSnapshots(void)
{
  char *buffer;
  int fd, amount, index, i;

  if (logfile[0]) {
    fd = open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0600);
    if (0 <= fd) {
      for (amount = 0; (amount < MAXSNAP) && snap[i].file; amount++);

      write(fd,       "\n--- List of recent snapshots\n",
            StrLength("\n--- List of recent snapshots\n"));

      for (i = 1, index = snapnum; i <= amount; index--, i++) {
        if (0 > index) {
          index = MAXSNAP - 1;
        }

        buffer = trio_aprintf("#%-2d in %s line %d [%d]\n",
                              i,
                              snap[index].file,
                              snap[index].line,
                              snap[index].hits);
        if (buffer) {
          write(fd, buffer, StrLength(buffer));
          free(buffer);
        }
      }

      write(fd,       "--- End of list\n\n",
            StrLength("--- End of list\n\n"));

      close(fd);
    }
  }
}

char *GetSnapFile(void)
{
  return snap[snapnum].file;
}

int GetSnapLine(void)
{
  return snap[snapnum].line;
}
#endif /* DEBUG */

/* --- Debug ------------------------------------------------------ */

void Debug(const char *format, ...)
{
  char buffer[BIGGERBUFFER];
  va_list args;

  va_start(args, format);
  trio_vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

#ifdef DEBUG
  StrFormatAppendMax(buffer, sizeof(buffer), " (snapshot: %s line %d)",
                     GetSnapFile(), GetSnapLine());
#endif

  Log(LOGDEBUG, buffer);
  Multicastf(DEBUGCAST, "DEBUG: %s", buffer);
}

/* --- Quit ------------------------------------------------------- */

void Quit(char *from, char *reason)
{
  snapshot;

  /* Seen & Delete also handled by OnQuit, but how about restart/cleanup? */
  SeenInsertAll(SEENQUITED, NULL, NULL);
  DeleteGuests();

  WriteServer("QUIT :%s", ((reason && *reason) ? reason : NOREASON));
  DisconnectServ("QUIT %s", ((reason && *reason) ? reason : NOREASON));

  restart = FALSE;
  cleanup = TRUE;
}


syntax highlighted by Code2HTML, v. 0.9.1