#ifdef RCS
static char rcsid[]="$Id: dancer.c,v 1.2 2000/11/13 18:16:33 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/dancer.c,v $
 * $Revision: 1.2 $
 * $Date: 2000/11/13 18:16:33 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>      /* open() the log for StackTrace() */

#include "dancer.h"

#ifdef HAVE_PWD_H
# include <pwd.h>
#endif

#include "trio.h"
#include "strio.h"
#include "list.h"
#include "setup.h"
#include "server.h"
#include "netstuff.h"
#include "function.h"
#include "user.h"
#include "explain.h"
#include "seen.h"
#include "country.h"
#include "convert.h"
#include "bans.h"
#include "transfer.h"
#include "parse.h"
#include "link.h"
#include "servfunc.h"
#include "regex.h"
#include "tell.h"
#include "news.h"
#include "files.h"
#include "fplrun.h"

#ifdef PROFILING
extern etext();
#endif

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

extern time_t now;

extern char channel[];
extern char nickname[], realname[], myhost[];
extern char userfile[], logfile[];
extern bool autoop;
extern bool talkative;
extern bool masterflood;
extern bool loopservers;
extern int numretry;
extern int netburp;
extern int pendingpings;
extern time_t lastpost;
extern time_t commontime;
extern struct timeval pongval;
extern itemclient *client;
extern itemserv *servHead;

char shutdownby[NICKLEN+1];
char shutdownreason[MIDBUFFER];
char servername[MIDBUFFER];
char serverpasswd[MINIBUFFER] = "";
int serverport;
int reload_configuration = 0;

bool connected    = FALSE; /* Are we connected? */
bool cleanup      = FALSE;
bool restart      = TRUE;  /* Restart or try next server? */
bool shuttingdown = FALSE;
bool nickflag     = TRUE;

int serverSocket  = -1;
int retry         = 0;

time_t uptime     = 0;
time_t lastevent  = 0;
time_t mastertime = 0;
time_t shutdowntime = 0;

itemserv *currentserv;


/* --- AlarmCheck ------------------------------------------------- */

/*
 *  SIGALRM, StackTrace(), LogSnaphots() and _exit() in 10 minutes
 *  after last alarm() unless we manage to respond in time.
 *  Only to trace down the busy loops that still exist.
 */

static void AlarmCheck(void)
{
#if defined(DEBUG) && defined(SIGALRM)
  static time_t lastalarm = 0;
  time_t current_time;

  snapshot;
  current_time = time(NULL);
  if ((current_time - lastalarm) > 2*SECINMIN) {
    lastalarm = current_time;
    alarm(10*SECINMIN);
  }
#endif
}

/* --- SloppyEvents ----------------------------------------------- */

time_t lastserverscan = 0;
time_t lastfilesave = 0;

void SloppyEvents(void) /* Events where timing isn't critical */
{
  extern time_t lastservertime;

  snapshot;
  AlarmCheck();

  if (masterflood && ((now - mastertime) > netburp))
    masterflood = FALSE;

  if ((now - lastserverscan) > 15*SECINMIN) {
    ScanSplitServers(FALSE);
    lastserverscan = now;
  }

  if (connected) {
    if (!pendingpings && ((now - pongval.tv_sec) > PINGDELAY))
      PingServer();

    if (reload_configuration) {
        extern char expfile[];
        extern char funcfile[];
        
        snapshot;
        Log(LOG, "SIGHUP received, attempting to reload configuration.");
        UserReload(userfile);
        ExplainReload(expfile);
        FuncReload(funcfile);
        reload_configuration = 0;
    }

    if (((now - pongval.tv_sec) > 20*SECINMIN) &&
        ((now - lastservertime) > 20*SECINMIN)) {
      /* X minutes since last server-PONG and
         Y minutes since last line was received */
      Debug("Lost contact with server");
      restart = cleanup = TRUE;
    }

    if (autoop)
      CheckForAutoOp();
  }
  else {
    /* Try to connect to server for two minutes max */
    if ((now - lastevent) > 2*SECINMIN) {
      if (retry < numretry) {
        sleep(++retry);
        cleanup = restart = TRUE;
      }
      else {
        Debug("Timeout while trying to connect to server");
        restart = FALSE;
        cleanup = TRUE;
      }
    }
  }

  /* Periodically save important files */
  if ((now - lastfilesave) > 2*SECINHOUR) {
    SeenSave();
    /*TellSave();*/
    BanSave();
    WarnSave();
    UserSave();
    lastfilesave = now;
  }
}

/* --- TimeEvents ------------------------------------------------- */

time_t lastbantimeout = 0;

void TimeEvents(void) /* Events where timing is critical */
{
  static int count = 0;

  snapshot;
  CheckLinkQueue();
  MessageQueue();

  /* Shutdown */
  if (shuttingdown && (now >= shutdowntime))
    Quit(shutdownby, shutdownreason);

  if ((now - lastbantimeout) > 5) {
    BanTimeout();
    lastbantimeout = now;
  }

  fTimerCheck();

  if (0 == count)
    SloppyEvents();

  if (++count >= TIMERCHK)
    count = 0;
}

/* --- Hello ------------------------------------------------------ */

void Hello(itemserv *s) /* Introduce ourselves to the server */
{
  char *uname;
#ifdef HAVE_PWD_H
  struct passwd *pw;
#endif

  snapshot;
  connected = FALSE;

  /* Recommended order is PASS, NICK, then USER */
  if (serverpasswd[0])
    WriteServer("PASS %s", serverpasswd);
  NewNick();
  WriteServer("NICK %s", nickname);
#ifdef HAVE_PWD_H
  pw = getpwuid(getuid());
  if (pw)
    uname = pw->pw_name;
  else /* getenv() below */
#endif
    uname = getenv("LOGNAME");

#if defined(OVERRIDE_USER) || defined(__CYGWIN32__)
  if (getenv("BOTUSER"))
    uname = getenv("BOTUSER");
#endif

  /* Client cannot provide host and server, but we're just being nice */
  WriteServer("USER %s %s %s :%s", (uname ? uname : "bot"),
              myhost, servername, (realname[0] ? realname : "An IRC bot"));

  if (NULL == s)
    s = FindServ(servername, serverport);

  ConnectServ(s); /* Report "we're connected" */
}

/* --- Start ------------------------------------------------------ */

/* Returns whether the next server should be tried */

bool Start(itemserv *s)
{
  snapshot;
  StrCopyMax(servername, sizeof(servername), s->s.name);
  StrCopyMax(serverpasswd, sizeof(serverpasswd),
             s->s.passwd ? s->s.passwd : "");
  serverport = s->s.port;

#if 0
  /* Check valid chars in RFC952 */
  switch (StrScan(server, "%[a-zA-Z0-9.-]%*[ :]%d%*[ :]%[^\n]",
                  servername, &serverport, serverpasswd)) {
  case 0:
    return TRUE; /* No server, let's try the next */
  case 1:
    serverport = IRCPORT;
    *serverpasswd = NIL;
    break;
  case 2:
    *serverpasswd = NIL;
    break;
  default:
    break;
  }
#endif

  serverSocket = OpenServerConnection(servername, serverport);
  if (serverSocket != -1) {
    lastevent = time(NULL);
    Logf(LOG, "Connected to %s on port %d", servername, serverport);    
    Logf(LOG, "Logfile for %s started", channel);
    Hello(s);
    EventLoop();

    return !restart;
  }

  return TRUE;
}

/* --- SignalGetName ---------------------------------------------- */

const char *SignalGetName(int sig)
{
  switch (sig) {
#ifdef SIGALRM
  case SIGALRM:
    return "SIGALRM";
#endif
#ifdef SIGBUS
  case SIGBUS:
    return "SIGBUS";
#endif
#ifdef SIGFPE
  case SIGFPE:
    return "SIGFPE";
#endif
#ifdef SIGILL
  case SIGILL:
    return "SIGILL";
#endif
#ifdef SIGINT
  case SIGINT:
    return "SIGINT";
#endif
#ifdef SIGKILL
  case SIGKILL:
    return "SIGKILL";
#endif
#ifdef SIGPIPE
  case SIGPIPE:
    return "SIGPIPE";
#endif
#ifdef SIGQUIT
  case SIGQUIT:
    return "SIGQUIT";
#endif
#ifdef SIGSEGV
  case SIGSEGV:
    return "SIGSEGV";
#endif
#ifdef SIGTERM
  case SIGTERM:
    return "SIGTERM";
#endif
  default:
    return "UNKNOWN";
  }
}

/* --- SignalCrashHandler ----------------------------------------- */

/* StackTrace() doesn't seem to work with my gdb -gr8ron */
#if defined(DEBUG) && !defined(AMIGA)
#include "stacktrace.c"
#endif

void SignalCrashHandler(int sig)
{
#if defined(DEBUG)
#if !defined(AMIGA)
  int fd = -1;

  if (logfile[0]) {
    fd = open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0600);
  }

  if (0 <= fd) {
    globalOutFD = fd;
  }

  StackTrace();

  if (0 <= fd) {
    close(fd);
  }
#endif /* !AMIGA */
  LogSnapshots();
#endif /* DEBUG */

  Debug("Received FATAL signal %d (%s), crashing", sig, SignalGetName(sig));

  signal(sig, SIG_DFL);
  _exit(1);
}

/* --- SignalTerminateHandler ------------------------------------- */

void SignalTerminateHandler(int sig)
{
  Debug("Received signal %d (%s), terminating", sig, SignalGetName(sig));

  SeenInsertAll(SEENQUITED, NULL, NULL);
  restart = FALSE;
  cleanup = TRUE;

  /* Install default signal handler to avoid race conditions */
  signal(sig, SIG_DFL);
}

/* --- SignalPipeHandler ------------------------------------------ */

void SignalPipeHandler(int sig)
{
  if (!Alive(serverSocket)) {
    Debug("Received signal %d (SIGPIPE)", sig);
    restart = FALSE;
    cleanup = TRUE;
    signal(sig, SIG_DFL);
  }

  if (client) {
    Debug("Broken pipe from %s", client->ident->userdomain);
    RemoveClient(client);
    client = NULL;
    signal(sig, SignalPipeHandler);
  }
}

/* --- SignalQueReload ------------------------------------------- */

void SignalQueReload(int sig)
{
  reload_configuration = 1;
}

/* --- SignalInit ------------------------------------------------ */

void SignalInit(void)
{
  snapshot;

  /* Catch SIGHUP and reload config */
#ifdef SIGHUP
  signal(SIGHUP,  SignalQueReload);
#endif

  /* Crash */
#ifdef SIGALRM
  signal(SIGALRM, SignalCrashHandler);  /* alarm(2) */
#endif
#ifdef SIGBUS
  signal(SIGBUS,  SignalCrashHandler);  /* Bus error */
#endif
#ifdef SIGFPE
  signal(SIGFPE,  SignalCrashHandler);  /* Floating point exception */
#endif
#ifdef SIGILL
  signal(SIGILL,  SignalCrashHandler);  /* Illegal instruction */
#endif
#ifdef SIGSEGV
  signal(SIGSEGV, SignalCrashHandler);  /* Segmentation violation */
#endif

  /* Terminate */
#ifdef SIGINT
  signal(SIGINT,  SignalTerminateHandler);  /* CTRL-C */
#endif
#ifdef SIGKILL
  signal(SIGKILL, SignalTerminateHandler);  /* Kill (cannot be ignored) */
#endif
#ifdef SIGQUIT
  signal(SIGQUIT, SignalTerminateHandler);  /* CTRL-| */
#endif
#ifdef SIGTERM
  signal(SIGTERM, SignalTerminateHandler);  /* Software termination signal from kill */
#endif

  /* Pipe */
#ifdef SIGPIPE
  signal(SIGPIPE, SignalPipeHandler);  /* Write on a pipe with no one to read it */
#endif

  /* Ignore the rest */
#ifdef SIGABRT
  signal(SIGABRT, SIG_IGN);   /* abort(3) */
#endif
#ifdef SIGSTOP
  signal(SIGSTOP, SIG_IGN);   /* Sendable stop signal not from tty */
#endif
#ifdef SIGTSTP
  signal(SIGTSTP, SIG_IGN);   /* CTRL-Z (stop signal from tty) */
#endif
}

/* --- Cleanup ---------------------------------------------------- */

void Cleanup(void)
{
  snapshot;
  Log(LOG, "Cleanup");
  DeleteGuests();
  FlushMessages();
  BanDisable();
  if (restart) {
    cleanup = FALSE;
    NetCleanup();
  }
  else {
    NetCleanup();
    LinkQuit();
    TellCleanup();
    SeenCleanup();
    ExplainCleanup();
    UserCleanup();
    ConfigCleanup();
    BanCleanup();
    KickCleanup();
    WarnCleanup();
    ServCleanup();
    NewsCleanup();

#ifdef HAVE_LIBFPL
    FPLCleanup();
#endif

    Log(LOG, "Exit");
    exit(0);
  }
}

/* --- TimeInit --------------------------------------------------- */

void TimeInit(void)
{
  snapshot;
  uptime = lastevent = lastpost = lastserverscan = lastfilesave = \
           lastbantimeout = commontime = now = time(NULL);
}

/* --- main ------------------------------------------------------- */

int main(int argc, char *argv[])
{
  char buffer[MINIBUFFER];
  bool configok;
  itemserv *s;

  snapshot;
#if defined(DEBUG) && !defined(AMIGA)
  globalProgName = argv[0];
#endif

  TimeInit();
  SignalInit();

  StrFormatMax(buffer, sizeof(buffer), "echo %d > .pid", (int)getpid());
  system(buffer);

  /* Any background process should be made nice */
  nice(NICE);

#if defined(PROFILING)
  monstartup(2, etext);
#endif

  re_syntax_options |= RE_NO_BK_VBAR | RE_NO_BK_PARENS;

  CommandInit(); /* Must be done prior to new command levels / ConfigInit() */
  configok = ConfigInit();

  if ((argc > 1) && StrEqualMax(argv[1], 4, "conf")) {
    MakeConfig();
    exit(1);
  }

  if (configok) {
    UserInit();
    if (UserLoad(userfile)) {
      LogInit();
      RandomInit(uptime);
      NetInit();
      SeenInit();
      ExplainInit();
      TellInit();
      CountryInit();
      TimeZoneInit();
      BanInit();
      KickInit();
      WarnInit();
      FuncInit();
#if 0
      PatternInit();
#endif
      ServInit();
      NewsInit();

#ifdef HAVE_LIBFPL
      FPLInit();
#endif

      Log(LOG, "Started " VERSIONMSG );

      snapshot;
      do {
        for (s = First(servHead); s && restart; s = Next(s)) {
          if (s->s.flags & SERV_DISABLED) {
            /* Server made disabled, please continue */
            Logf(LOG, "Server %s is disabled", s->s.name);
            continue;
          }

          retry = 0; /* Try enough number of times */

          do {
            AlarmCheck();
            cleanup = FALSE;
            if (!Start(s)) {
              /* This means it exited properly. */
              break;
            }
            if (!restart)
              break;
            if (++retry >= numretry) {
              Logf(LOG, "Unable to connect to %s on port %d (tried %d times)",
                   servername, serverport, retry);
              break;
            }
            /* try again! */
            sleep(retry);
          } while(1);

          if (restart) {
            /* We should loop, cleanup! */
            Cleanup();
          }
        } /* Now, try next server or exit */

        if (loopservers && restart) {
          /* Lets see if there are any enabled servers before looping */
          for (s = First(servHead); s; s = Next(s)) {
            if (0 == (s->s.flags & SERV_DISABLED))
              break;
          }

          if (s) {
            static time_t lastloop = 0;

            /* Lets try to avoid abusing the server(s) with too quick reconnections */
            if ((now - lastloop) < 5*SECINMIN) {
/*
 * We will get rid of all the sleep() calls once we have event driven
 * code implemented, but until then this will have to do.
 */
              Debug("Sleeping for one minute - patience");
              sleep(SECINMIN);
            }

            lastloop = now;
          }
          else {
            restart = FALSE;
          }
        }
      } while (loopservers && restart);

      restart = FALSE;
      cleanup = TRUE;
      Cleanup();
    }
    else
      Logf(LOG, "Userfile \"%s\" not found!", userfile);
  }
  else
    Logf(LOG, CONFIGFILE " not found!");

  restart = FALSE;
  Cleanup();

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1