/* Main source file.

   OperStats - Copyright (c) 2000-2003 Darcy Grexton
   Contact: skold@habber.net, skold @ HabberNet

   See doc/LICENSE for licensing details.
 */

#include "../inc/operstats.h"
#include "../inc/version.h"

char inbuf[BUFSIZE * 8], quitmsg[BUFSIZE];
int8 servsock;
uint8 debuglevel, bursting, bursted = 1, force, rehashing;
uint8 dailyservstat, maxservcnt, donereset;
uint32 usercnt, chancnt, servcnt, userstat, chanstat, servstat, maxhitcnt, maxopers;
uint32 dailyuserstat, dailychanstat, dailyhitcnt, maxusercnt, maxchancnt, runflags;
uint32 tldnores, tldnoreshits, lastwarnclients, awaystat, operstat;
uint64 speedstat;
time_t maxusertime, maxchantime, maxservtime, last_check, last_update, burststart, maxuptime;
time_t peaktime, dailyusertime, dailychantime, dailyservtime, lastwarncheck, last_sample;
#ifdef HAVE_GETTIMEOFDAY
struct timeval burstnow, burstms, bursttmp;
#endif
static jmp_buf panic_jmp;

static void do_help (char *prog);

/* If we get a weird signal, come here. */
static void sighandler (int signum)
{
    /* Rehash */
    if (signum == SIGHUP)
    {
	rehashing = 1;

	log ("Got SIGHUP, reloading %s", CONFIGFILE);

	globops (authserv (), "Got SIGHUP, reloading %s", CONFIGFILE);
	rehash ("system console");
    }
    else if (signum == SIGTERM)
    {
	signal (SIGTERM, SIG_IGN);
	return;
    }

    if (signum == SIGUSR1)
    {
	snprintf (quitmsg, BUFSIZE, "Out of memory!");
	runflags |= RUN_RESTART;
    }
    else
    {
	if (rehashing == 1)
	{
	    rehashing = 0;
	    return;
	}

	snoop (authserv (), "Fatal Error: OperStats terminating on signal %d", signum);
	snprintf (quitmsg, BUFSIZE, "OperStats terminating on signal %d.", signum);
	runflags |= RUN_RESTART;
    }

    log (RPL_QUIT, quitmsg);
    exit (1);
}

/* Command-line help message */
static void do_help (char *prog)
{
printf(
"OperStats - Copyright (c) 2000-2003 Darcy Grexton\n\n"
"Syntax: %s [parms] [options]\n\n"
"-help                This message\n"
"-debug [0|1|2|3]     Enable debug mode - log to debug.log\n"
"                         Level 0: No debug logging (Off)\n"
"                         Level 1: Server<->server traffic logged\n"
"                         Level 2: (Some) Function calls logged\n"
"                         Level 3: Server<->server traffic and (some) function\n"
"                                  calls logged\n"
"-live                Don't fork (log screen + log file).\n"
"-force               Force OperStats to work with incomplete databases\n", prog);
}

/* Main routine .. :P */
int main (int ac, char **av, char **envp)
{
#ifndef WIN32
    FILE *pid_file;
    struct rlimit rlim;
#else
    WSADATA wsaData;
#endif
    char *s;
    uint32 i;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

#ifndef WIN32
    if (chdir (OPERSTATS_DIR) < 0)
    {
	perror (OPERSTATS_DIR);
	return 20;
    }
#endif

#if HAVE_UMASK
    umask (077);
#endif

#ifndef WIN32
    /* Core file stuff. */
    if (!getrlimit (RLIMIT_CORE, &rlim))
    {
	rlim.rlim_cur = rlim.rlim_max;
	setrlimit (RLIMIT_CORE, &rlim);
    }
#endif

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&(start), NULL);
#endif

    /* Parse our command line options */
    for (i = 1; i < ac; ++i)
    {
	s = av[i];
	if (*s == '-' || *s == '/')
	{
	    ++s;
	    if (!stricmp (s, "-help") || !stricmp (s, "?") ||
		  !stricmp (s, "help"))
	    {
		do_help (av[0]);
		return 0;
	    }
	    else if (!stricmp (s, "debug"))
	    {
		if (++i >= ac)
		{
		    fprintf (stderr, "Error: -debug requires a parameter.\n");
		    return -1;
		}
		s = av[i];
		debuglevel = atoi (s);
		open_debug ();
		debug ("-----------------------------------------------------");
	    }
	    else if (!stricmp (s, "live") || !stricmp (s, "nofork"))
		runflags |= RUN_LIVE;
	    else if (!stricmp (s, "force"))
		force = 1;
	}
    }

    me.since = time (NULL);
    me.reset = time (NULL);
    last_check = time (NULL);

    runflags |= RUN_STARTING;

    snprintf (quitmsg, BUFSIZE, "Reason unknown");
    servsock = -1;

    /* Open the logfile */
    open_log ();

#ifndef WIN32
    /* Fork into the background */
    if (!(runflags & RUN_LIVE))
    {
	if ((i = fork ()) < 0)
	{
	    log ("Cannot fork!");
	    return -1;
	}
	else if (i != 0)
	    exit (0);

	if (setpgid (0, 0) < 0)
	    return -1;
    }

    pid_file = fopen (PIDFILE, "w");

    if (pid_file)
    {
	fprintf (pid_file, "%d\n", getpid ());
	fclose (pid_file);
    }
    else
	log (RPL_CANT_WRITE_PID);
#endif /* WIN32 */

    /* Write to the log so we know we're on a new session */
    log ("-----------------------------------------------------");
    log ("OperStats v%s starting up...", version);

    /* Set signal handlers */
    signal (SIGINT, sighandler);
    signal (SIGTERM, sighandler);
    signal (SIGFPE, sighandler);
    signal (SIGILL, sighandler);
#ifdef WIN32
    signal (SIGBREAK, sighandler);
    signal (SIGABRT, sighandler);
    signal (SIGSEGV, sighandler);
#else
#ifndef DUMP_CORE
    signal (SIGSEGV, sighandler);
#endif
    signal (SIGPIPE, SIG_IGN);
    signal (SIGQUIT, sighandler);
    signal (SIGBUS, sighandler);
    signal (SIGHUP, sighandler);
    signal (SIGTRAP, sighandler);
    signal (SIGIOT, sighandler);
    signal (SIGALRM, SIG_IGN);
    signal (SIGUSR2, SIG_IGN);
    signal (SIGCHLD, SIG_IGN);
    signal (SIGWINCH, SIG_IGN);
    signal (SIGTTIN, SIG_IGN);
    signal (SIGTTOU, SIG_IGN);
    signal (SIGTSTP, SIG_IGN);
    signal (SIGUSR1, sighandler);
#endif /* WIN32 */

    /* Load our conf */
    load_conf ();

    /* Check the conf */
    check_conf ();    

    /* Set up OperServ stuff */
    if (operserv_on == TRUE)
    {
	os.version = 2;
	load_os_dbase ();
    }

    /* Set up StatServ stuff */
    if (statserv_on == TRUE)
    {
	ss.version = 5;
	load_ss_dbase ();
	load_tlds ();
	load_zones ();
    }

    /* Count our stuff */
    if (ignoreulines_on == FALSE)
    {
	userstat += servcount ();
	operstat += countops ();
	servstat++;

	if (userstat > maxusercnt)
	{
	    maxusercnt = userstat;
	    maxusertime = time (NULL);
	}
    }

    /* How long did that take? */
#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&now, NULL);
    timersub (&now, &start, &tmp);

    if (tv2ms (&bursttmp) > 1000)
	log ("OperStats startup completed in %s.", duration (tv2ms (&bursttmp) / 1000, 1));
    else
	log ("OperStats startup completed in %d ms", tv2ms (&tmp));
#endif

#ifdef WIN32
    if (WSAStartup (MAKEWORD (1, 1), &wsaData))
    {
	log ("Unable to init winsock");
	return -1;
    }
#endif

    log_sameline (1, "Connecting to %s:%d... ", me.uplink, me.port);
    servsock = conn (me.uplink, me.port, me.bindhost, me.bindport);

    if (servsock < 0)
    {
	log_sameline (0, RPL_CANT_CONNECT);
	disconn (servsock);
	close_log ();

	if (debuglevel)
	    close_debug ();

	remove (PIDFILE);
	return 0;
    }

    log_sameline (0, "connected.");

    if (ircdtype != DREAMFORGE)
    {
	log_sameline (1, "Synching with uplink... ");
	burststart = time (NULL);
#ifdef HAVE_GETTIMEOFDAY
	gettimeofday (&(burstms), NULL);
#endif
    }

    /* Send our initial login info to the server. We don't tokenize
       at this point since we wouldn't have gotten much if anything
       from the server yet anyhow ..
     */
    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	send_cmd (NULL, "PROTOCTL TOKEN NICKv2 UMODE2 VL SJOIN SJOIN2 SJ3 NOQUIT");

    if (ircdtype == DREAMFORGE)
	send_cmd (NULL, "PROTOCTL TOKEN NOQUIT");

    if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
	send_cmd (NULL, "PASS %s :TS", me.pass);
    else
	send_cmd (NULL, "PASS :%s", me.pass);

    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	send_cmd (NULL, "SERVER %s 1 :U0-*-%d %s", me.name, unreal_numeric, me.desc);
    else
	send_cmd (NULL, "SERVER %s 1 :%s", me.name, me.desc);

    if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
    {
	send_cmd (NULL, "SVINFO 3 3 0 :%lu", time (NULL));
	send_cmd (NULL, "CAPAB TS3 NOQUIT");
    }

    /* Now bring on our clients.. */
    if (operserv_on == TRUE)
    {
	intro_user (s_OperServ, os.user, os.host, os.real, os.mode);
	os.isonline = 1;
    }

    if (statserv_on == TRUE)
    {
	intro_user (s_StatServ, ss.user, ss.host, ss.real, ss.mode);
	ss.isonline = 1;
    }

    if (globalnoticer_on == TRUE)
    {
	intro_user (s_GlobalNoticer, gn.user, gn.host, gn.real, gn.mode);
	gn.isonline = 1;
    }

    if (spamserv_on == TRUE)
    {
	intro_user (sp.nick, sp.user, sp.host, sp.real, "+");
	sp.isonline = 1;
    }

    /* Put all clients in the JoinChan */
    joinchan_join ();

    last_update = time (NULL);
    last_check = last_update;
    lastwarncheck = last_update;

    setjmp (panic_jmp);

    /* Bahamut sends two pings to indicate the end of the burst */
    if (ircdtype == BAHAMUT)
	bursting = 2;

    if (ircdtype == PROMETHEUS || ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	bursting = 1;

    runflags &= ~RUN_STARTING;

    /* Main loop */
    if (servsock <= 0)
	log (RPL_CANT_CONNECT);
    else
	read_loop ();

    if (!quitmsg)
	snprintf (quitmsg, BUFSIZE, "Terminating, reason unknown");

    log (RPL_QUIT, quitmsg);

    send_cmd (me.name, "%s %s :%s", me.token ? "-" : "SQUIT",
	me.name, quitmsg);

    disconn (servsock);

    /* Should we reboot? Sleep, then restart. */
    if (wait_restart > 0 && runflags & RUN_RESTART)
    {
	log (RPL_RESTARTING, duration (wait_restart, 1));
	close_log ();

	if (debuglevel)
	    close_debug ();

#ifndef WIN32
	sleep (wait_restart);
	execve (OS_BINARY, av, envp);
#endif
	return 1;
    }
    else
    {
	close_log ();

	if (debuglevel)
	    close_debug ();

	remove (PIDFILE);
	return 0;
    }

    return 0; 
}

/* Bring a new client onto the network. */
void intro_user (const char *nick, const char *user, const char *host,
		 const char *real, const char *modes)
{
    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	send_cmd (NULL, "%s %s 1 %lu %s %s %s 1 %s * :%s", me.token ? "&" : "NICK",
	    nick, time (NULL), user, host, me.name, modes, real);

    if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
	send_cmd (NULL, "NICK %s 1 %lu %s %s %s %s 0 :%s", nick, time (NULL), modes,
	    user, host, me.name, real);

    if (ircdtype == DREAMFORGE)
    {
	send_cmd (NULL, "%s %s 1 %lu %s %s %s 1 :%s", me.token ? "&" : "NICK",
	    nick, time (NULL), user, host, me.name, real);
	send_cmd (nick, "%s %s %s", me.token ? "G" : "MODE", nick, modes);
    }
}

/* Check if we should ping a server */
void checkpings ()
{
    Server *s;

    for (s = firstserv (); s; s = nextserv ())
    {
	/* Don't ping servers with U:Lined uplinks. 99.99% of the time
	   this indicates a jupe, and the server won't reply properly,
	   if at all.
	 */
	if (finduline (s->uplink))
	    continue;

	if ((time (NULL) - s->lastping.tv_sec) > ping_delay)
	{
	    send_cmd (me.name, "%s %s %s", me.token ? "8" : "PING",
		me.name, s->name);
	    gettimeofday (&(s->lastping), NULL);
	}
    }
}

/* Change SpamServ's nick every minute. */
void spamnick ()
{
    char guest[NICKLEN];
    uint32 guestnick;
    uint8 success = 0;

    while (success == 0)
    {
	/* Generate a random guest nick */
	guestnick = 1+(int) (99999.0*rand()/(RAND_MAX+10000.0));

	/* Sometimes rand() gives us a 4 digit number. If we get one,
	   we'll add 10000 to it, bringing it into the 5 digit range.
	 */
	if (guestnick < 10000)
	    guestnick = guestnick + 10000;

	snprintf (guest, sizeof (guest), "%s%d", sp.nickpre, guestnick);

	if (!finduser (guest))
	    success = 1;
    }	

    /* Change to the new nickname */
    send_cmd (sp.nick, "%s %s", me.token ? "&" : "NICK", guest);

    /* Now replace the nick */
    strscpy (sp.nick, guest, sizeof (sp.nick));
}

/* Check for various timeouts */
void chk ()
{
    time_t curtime = time (NULL);

    /* Reset the stat every second */
    if (curtime > last_sample && statserv_on == TRUE)
    {
	if (speedsample > speedstat && !bursting)
	{
	    speedstat = speedsample;
	    peaktime = curtime;

	    /* Only do this if we've been up for more than 60 seconds, to avoid
	       a small flood.
	     */
	    if (globalonspeed_on == TRUE && (curtime - me.since > 60))
		globops (s_StatServ, SS_NETSPEED_PEAKING, (float) SIZEREAL (speedstat),
		    SIZENAME (speedstat));
	}

	transfer += speedsample;
	speedsample = 0;
	last_sample = curtime;
    }

    /* Check various timeouts */
    if (curtime - last_check > 5)
    {
	if (warnclients && (curtime - lastwarncheck > 30))
	{
	    int32 i = userstat - lastwarnclients;

	    if (i >= warnclients)
		globops (s_OperServ, "\2Warning:\2 Client count has increased by %d in 30 seconds.", i);

	    lastwarnclients = userstat;
	    lastwarncheck = curtime;
	}

	/* Check SpamServ's nick */
	if (spamserv_on == TRUE)
	{
	    /* We change nicks every 300 seconds (5 minutes) */
	    if (curtime - sp.lastnick >= 300)
	    {
		sp.lastnick = curtime;
		spamnick ();
	    }
	}

	/* Check various StatServ stuff */
	if (statserv_on == TRUE)
	{
	    if (!bursting)
		checkpings ();

	    /* Check the time and reset the daily stats at
	       midnight. Borrowed from GeoStats.
	     */
	    if (is_midnight () == 1 && donereset == 0)
	    {
		reset_daily_stats ();
		donereset = 1;
	    }
	    else
	    {
		if (donereset == 1 && is_midnight () == 0)
		    donereset = 0;
	    }
	}

	last_check = time (NULL);
    }

    if ((runflags & RUN_SAVEDBS) ||
	   curtime - last_update >= update_timeout)
    {
	/* Make backups of all the DBs. */
	db_backup (0);

	if (statserv_on == TRUE)
	    save_ss_dbase ();

	if (operserv_on == TRUE)
	{
	    expire_akills ();
	    save_os_dbase ();
	}

	if (dumpchans_on == TRUE)
	    dumpchans ();

	if (dumptlds_on == TRUE)
	    dumptlds ();

	if (dumpstats_on == TRUE)
	    dumpstats ();

	if (dumpservers_on == TRUE)
	    dumpservers ();

	/* Remove the DB backups */
	db_backup (1);

	last_update = curtime;
	runflags &= ~RUN_SAVEDBS;

	if (logupdates_on == TRUE)
	    snoop (authserv (), OS_DB_SYNC_COMPLETE);
    }
}

