/* Main source file.

   Cygnus IRC Services - Copyright (c) 2001-2002 Darcy Grexton
   Contact: skold@habber.net, skold @ HabberNet

   See doc/LICENSE for licensing details.
 */

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

Mail *emaillist[HASHSIZE];
Zone *zonelist = NULL;

char inbuf[BUFSIZE * 8], quitmsg[BUFSIZE];
int8 servsock;
uint8 debuglevel, servcnt, bursting, bursted = 1, force, rehashing;
uint32 usercnt, chancnt, userstat, maxuptime, runflags;
time_t last_update, last_check, news_time, burststart;
#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 (s_RootServ, "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 (s_RootServ, "Fatal Error: Services terminating on signal %d", signum);
	snprintf (quitmsg, BUFSIZE, "Services 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(
"Cygnus IRC Services - Copyright (c) 2001-2002 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 Cygnus 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;
    int i;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

#ifndef WIN32
    if (chdir (SERVICES_DIR) < 0)
    {
	perror (SERVICES_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);

    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 ("Cygnus IRC Services 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 the news time in case its not in the DB */
    news_time = time (NULL);

    /* Set up RootServ stuff */
    if (rootserv_on == TRUE)
    {
	rs.version = 3;
	load_rs_dbase ();
    }

    /* Set up NickServ stuff */
    if (nickserv_on == TRUE)
    {
	ns.version = 3;
	load_ns_dbase ();
    }

    /* Set up ChanServ stuff */
    if (chanserv_on == TRUE)
    {
	cs.version = 3;
	load_cs_dbase ();
    }

    /* This is down here to keep the logging nice :X */
    if (nickserv_on == TRUE)
	load_zones ();

    /* Count our stuff */
    userstat += servcount ();

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

    if (tv2ms (&bursttmp) > 1000)
	log ("Cygnus startup completed in %s.", duration (tv2ms (&bursttmp) / 1000, 1));
    else
	log ("Cygnus 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 VHP 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 (rootserv_on == TRUE)
    {
	intro_user (s_RootServ, rs.user, rs.host, rs.real, rs.mode);
	rs.isonline = 1;
    }

    if (nickserv_on == TRUE)
    {
	intro_user (s_NickServ, ns.user, ns.host, ns.real, ns.mode);
	ns.isonline = 1;
    }

    if (chanserv_on == TRUE)
    {
	intro_user (s_ChanServ, cs.user, cs.host, cs.real, cs.mode);
	cs.isonline = 1;
    }

    if (memoserv_on == TRUE)
    {
	intro_user (s_MemoServ, ms.user, ms.host, ms.real, ms.mode);
	ms.isonline = 1;
    }

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

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

    last_update = time (NULL);

    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;

    /* Send status global if nessecary. */
    if (statglobal_on == TRUE)
	noticeall (RPL_SERVICESBACK);

    runflags &= ~RUN_STARTING;

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

    /* Let the users know we're shutting down */
    if (statglobal_on == TRUE)
	noticeall (RPL_SERVICESLEAVING);

    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 (SERVICES_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 for various timeouts */
void chk ()
{
    time_t curtime = time (NULL);

    check_timeouts ();

    /* Various every-minute things */
    if (curtime - last_check > 60)
    {
	ChanInfo *ci;
	Channel *c;

	/* Read web.log */
	if (webint_on == TRUE)
	    read_web_log ();

	/* Keep the limit of every channel with CF_LIMITED within 5 of the users. */
	for (c = firstchan (); c; c = nextchan ())
	{
	    ci = cs_findchan (c->name);

	    if (ci && (ci->flags & CF_LIMITED) && (time (NULL) - c->lastlimit) > 60)
	    {
		if (c->limit < c->usercnt + 5 || c->limit > c->usercnt + 5)
		{
		    if (!(c->mode & CMODE_l))
			c->mode |= CMODE_l;

		    c->limit = c->usercnt + 5;
		    c->lastlimit = time (NULL);
		    send_cmode (s_ChanServ, ci->name, "+l", itoa (c->limit));
		}
	    }
	}

	last_check = curtime;
    }

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

	expire_akills ();

	if (nickserv_on == TRUE)
	{
	    expire_emails ();
	    expire_nicks ();
	}

	if (chanserv_on == TRUE)
	    expire_chans ();

	save_rs_dbase ();

	if (nickserv_on == TRUE)
	    save_ns_dbase ();

	if (chanserv_on == TRUE)
	{
	    scan_verifies ();
	    save_cs_dbase ();
	}

	if (webint_on == TRUE)
	    save_web_dbase ();

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

	last_update = curtime;
	runflags &= ~RUN_SAVEDBS;

	if (logupdates_on == TRUE)
	    snoop (s_RootServ, RS_DB_SYNC_COMPLETE);
    }
}
