X-Git-Url: http://git.pk910.de/?a=blobdiff_plain;f=ircd%2Fircd.c;h=2a521703be67091563096cad4b0340801e93da48;hb=7fbb742f7d849cb57b23f2a76d90950174094719;hp=6d361f28c93d6b2a854fbb6ca4fbac23b7d2cfcb;hpb=6c6f21e86b44e933449b988779b41c65c9df8949;p=ircu2.10.12-pk.git diff --git a/ircd/ircd.c b/ircd/ircd.c index 6d361f2..2a52170 100644 --- a/ircd/ircd.c +++ b/ircd/ircd.c @@ -17,942 +17,754 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/** @file + * @brief Entry point and other initialization functions for the daemon. + * @version $Id$ + */ +#include "config.h" -#include "sys.h" -#if HAVE_SYS_FILE_H -#include -#endif -#include -#include -#include -#if HAVE_FCNTL_H -#include -#endif -#ifdef HPUX -#define _KERNEL -#endif -#include -#ifdef HPUX -#undef _KERNEL -#endif -#if HAVE_UNISTD_H -#include -#endif -#include -#include -#ifdef USE_SYSLOG -#include -#endif -#ifdef CHROOTDIR -#include -#include -#include -#endif -#ifdef VIRTUAL_HOST -#include /* Needed for AF_INET on some OS */ -#endif -#include "h.h" -#include "res.h" -#include "struct.h" -#include "s_serv.h" -#include "send.h" #include "ircd.h" -#include "s_conf.h" +#include "IPcheck.h" #include "class.h" -#include "s_misc.h" -#include "parse.h" +#include "client.h" +#include "crule.h" +#include "destruct_event.h" +#include "hash.h" +#include "ircd_alloc.h" +#include "ircd_events.h" +#include "ircd_features.h" +#include "ircd_log.h" +#include "ircd_reply.h" +#include "ircd_signal.h" +#include "ircd_string.h" +#include "ircd_crypt.h" +#include "jupe.h" +#include "list.h" #include "match.h" +#include "motd.h" +#include "msg.h" +#include "numeric.h" +#include "numnicks.h" +#include "opercmds.h" +#include "parse.h" +#include "res.h" +#include "s_auth.h" #include "s_bsd.h" -#include "crule.h" +#include "s_conf.h" +#include "s_debug.h" +#include "s_misc.h" +#include "s_stats.h" +#include "send.h" +#include "sys.h" +#include "uping.h" #include "userload.h" -#include "numeric.h" -#include "hash.h" -#include "bsd.h" #include "version.h" #include "whowas.h" -#include "numnicks.h" - -RCSTAG_CC("$Id$"); - -extern void init_counters(void); -aClient me; /* That's me */ -aClient *client = &me; /* Pointer to beginning of Client list */ -time_t TSoffset = 0; /* Global variable; Offset of timestamps to - system clock */ - -char **myargv; -unsigned short int portnum = 0; /* Server port number, listening this */ -char *configfile = CPATH; /* Server configuration file */ -int debuglevel = -1; /* Server debug level */ -unsigned int bootopt = 0; /* Server boot option flags */ -char *debugmode = ""; /* -"- -"- -"- */ -int dorehash = 0; -int restartFlag = 0; -static char *dpath = DPATH; - -time_t nextconnect = 1; /* time for next try_connections call */ -time_t nextping = 1; /* same as above for check_pings() */ -time_t nextdnscheck = 0; /* next time to poll dns to force timeouts */ -time_t nextexpire = 1; /* next expire run on the dns cache */ - -time_t now; /* Updated every time we leave select(), - and used everywhere else */ - -#ifdef PROFIL -extern etext(void); - -RETSIGTYPE s_monitor(HANDLER_ARG(int UNUSED(sig))) -{ - static int mon = 0; -#ifdef POSIX_SIGNALS - struct sigaction act; +/* #include -- Now using assert in ircd_log.h */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_RESOURCE_H +#include #endif +#include +#include +#include +#include - moncontrol(mon); - mon = 1 - mon; -#ifdef POSIX_SIGNALS - act.sa_handler = s_rehash; - act.sa_flags = 0; - sigemptyset(&act.sa_mask); - sigaddset(&act.sa_mask, SIGUSR1); - sigaction(SIGUSR1, &act, NULL); -#else - signal(SIGUSR1, s_monitor); -#endif -} -#endif -RETSIGTYPE s_die(HANDLER_ARG(int UNUSED(sig))) -{ -#ifdef USE_SYSLOG - syslog(LOG_CRIT, "Server Killed By SIGTERM"); -#endif - flush_connections(me.fd); - exit(-1); -} - -static RETSIGTYPE s_rehash(HANDLER_ARG(int UNUSED(sig))) +/*---------------------------------------------------------------------------- + * External stuff + *--------------------------------------------------------------------------*/ +extern void init_counters(void); +extern void mem_dbg_initialise(void); + +/*---------------------------------------------------------------------------- + * Constants / Enums + *--------------------------------------------------------------------------*/ +enum { + BOOT_DEBUG = 1, /**< Enable debug output. */ + BOOT_TTY = 2, /**< Stay connected to TTY. */ + BOOT_CHKCONF = 4 /**< Exit after reading configuration file. */ +}; + + +/*---------------------------------------------------------------------------- + * Global data (YUCK!) + *--------------------------------------------------------------------------*/ +struct Client me; /**< That's me */ +struct Connection me_con; /**< That's me too */ +struct Client *GlobalClientList = &me; /**< Pointer to beginning of + Client list */ +time_t TSoffset = 0; /**< Offset of timestamps to system clock */ +int GlobalRehashFlag = 0; /**< do a rehash if set */ +int GlobalRestartFlag = 0; /**< do a restart if set */ +time_t CurrentTime; /**< Updated every time we leave select() */ + +char *configfile = CPATH; /**< Server configuration file */ +int debuglevel = -1; /**< Server debug level */ +char *debugmode = ""; /**< Server debug level */ +static char *dpath = DPATH; /**< Working directory for daemon */ +static char *dbg_client; /**< Client specifier for chkconf */ + +static struct Timer connect_timer; /**< timer structure for try_connections() */ +static struct Timer ping_timer; /**< timer structure for check_pings() */ +static struct Timer destruct_event_timer; /**< timer structure for exec_expired_destruct_events() */ + +/** Daemon information. */ +static struct Daemon thisServer = { 0, 0, 0, 0, 0, 0, -1 }; + +/** Non-zero until we want to exit. */ +int running = 1; + + +/*---------------------------------------------------------------------------- + * API: server_die + *--------------------------------------------------------------------------*/ +/** Terminate the server with a message. + * @param[in] message Message to log and send to operators. + */ +void server_die(const char *message) { -#ifdef POSIX_SIGNALS - struct sigaction act; -#endif - dorehash = 1; -#ifdef POSIX_SIGNALS - act.sa_handler = s_rehash; - act.sa_flags = 0; - sigemptyset(&act.sa_mask); - sigaddset(&act.sa_mask, SIGHUP); - sigaction(SIGHUP, &act, NULL); -#else - signal(SIGHUP, s_rehash); /* sysV -argv */ -#endif + /* log_write will send out message to both log file and as server notice */ + log_write(LS_SYSTEM, L_CRIT, 0, "Server terminating: %s", message); + flush_connections(0); + close_connections(1); + running = 0; } -#ifdef USE_SYSLOG -void restart(char *mesg) -#else -void restart(char *UNUSED(mesg)) -#endif +/*---------------------------------------------------------------------------- + * API: server_panic + *--------------------------------------------------------------------------*/ +/** Immediately terminate the server with a message. + * @param[in] message Message to log, but not send to operators. + */ +void server_panic(const char *message) { -#ifdef USE_SYSLOG - syslog(LOG_WARNING, "Restarting Server because: %s", mesg); -#endif - server_reboot(); + /* inhibit sending server notice--we may be panicking due to low memory */ + log_write(LS_SYSTEM, L_CRIT, LOG_NOSNOTICE, "Server panic: %s", message); + flush_connections(0); + log_close(); + close_connections(1); + exit(1); } -RETSIGTYPE s_restart(HANDLER_ARG(int UNUSED(sig))) +/*---------------------------------------------------------------------------- + * API: server_restart + *--------------------------------------------------------------------------*/ +/** Restart the server with a message. + * @param[in] message Message to log and send to operators. + */ +void server_restart(const char *message) { - restartFlag = 1; -} + static int restarting = 0; -void server_reboot(void) -{ - Reg1 int i; + /* inhibit sending any server notices; we may be in a loop */ + log_write(LS_SYSTEM, L_WARNING, LOG_NOSNOTICE, "Restarting Server: %s", + message); + if (restarting++) /* increment restarting to prevent looping */ + return; - sendto_ops("Aieeeee!!! Restarting server..."); + sendto_opmask_butone(0, SNO_OLDSNO, "Restarting server: %s", message); Debug((DEBUG_NOTICE, "Restarting server...")); - flush_connections(me.fd); - /* - * fd 0 must be 'preserved' if either the -d or -i options have - * been passed to us before restarting. - */ -#ifdef USE_SYSLOG - closelog(); -#endif - for (i = 3; i < MAXCONNECTIONS; i++) - close(i); - if (!(bootopt & (BOOT_TTY | BOOT_DEBUG))) - close(2); - close(1); - if ((bootopt & BOOT_CONSOLE) || isatty(0)) - close(0); - if (!(bootopt & BOOT_INETD)) - execv(SPATH, myargv); -#ifdef USE_SYSLOG + flush_connections(0); + + log_close(); + + close_connections(!(thisServer.bootopt & (BOOT_TTY | BOOT_DEBUG | BOOT_CHKCONF))); + + reap_children(); + + execv(SPATH, thisServer.argv); + /* Have to reopen since it has been closed above */ + log_reopen(); + + log_write(LS_SYSTEM, L_CRIT, 0, "execv(%s,%s) failed: %m", SPATH, + *thisServer.argv); - openlog(myargv[0], LOG_PID | LOG_NDELAY, LOG_FACILITY); - syslog(LOG_CRIT, "execv(%s,%s) failed: %m\n", SPATH, myargv[0]); - closelog(); -#endif Debug((DEBUG_FATAL, "Couldn't restart server \"%s\": %s", - SPATH, strerror(errno))); - exit(-1); + SPATH, (strerror(errno)) ? strerror(errno) : "")); + exit(8); } -/* - * try_connections - * - * Scan through configuration and try new connections. - * - * Returns the calendar time when the next call to this - * function should be made latest. (No harm done if this - * is called earlier or later...) + +/*---------------------------------------------------------------------------- + * outofmemory: Handler for out of memory conditions... + *--------------------------------------------------------------------------*/ +/** Handle out-of-memory condition. */ +static void outofmemory(void) { + Debug((DEBUG_FATAL, "Out of memory: restarting server...")); + server_restart("Out of Memory"); +} + + +/*---------------------------------------------------------------------------- + * write_pidfile + *--------------------------------------------------------------------------*/ +/** Write process ID to PID file. */ +static void write_pidfile(void) { + char buff[20]; + + if (thisServer.pid_fd >= 0) { + memset(buff, 0, sizeof(buff)); + sprintf(buff, "%5d\n", (int)getpid()); + if (write(thisServer.pid_fd, buff, strlen(buff)) == -1) + Debug((DEBUG_NOTICE, "Error writing to pid file %s: %m", + feature_str(FEAT_PPATH))); + return; + } + Debug((DEBUG_NOTICE, "Error opening pid file %s: %m", + feature_str(FEAT_PPATH))); +} + +/** Try to create the PID file. + * @return Zero on success; non-zero on any error. */ -static time_t try_connections(void) +static int check_pid(void) { - Reg1 aConfItem *aconf; - Reg2 aClient *cptr; - aConfItem **pconf; - int connecting, confrq; - time_t next = 0; - aConfClass *cltmp; - aConfItem *cconf, *con_conf = NULL; - unsigned int con_class = 0; - - connecting = FALSE; - Debug((DEBUG_NOTICE, "Connection check at : %s", myctime(now))); - for (aconf = conf; aconf; aconf = aconf->next) - { - /* Also when already connecting! (update holdtimes) --SRB */ - if (!(aconf->status & CONF_CONNECT_SERVER) || aconf->port == 0) - continue; - cltmp = aconf->confClass; - /* - * Skip this entry if the use of it is still on hold until - * future. Otherwise handle this entry (and set it on hold - * until next time). Will reset only hold times, if already - * made one successfull connection... [this algorithm is - * a bit fuzzy... -- msa >;) ] - */ + struct flock lock; - if ((aconf->hold > now)) - { - if ((next > aconf->hold) || (next == 0)) - next = aconf->hold; + lock.l_type = F_WRLCK; + lock.l_start = 0; + lock.l_whence = SEEK_SET; + lock.l_len = 0; + + if ((thisServer.pid_fd = open(feature_str(FEAT_PPATH), O_CREAT | O_RDWR, + 0600)) >= 0) + return fcntl(thisServer.pid_fd, F_SETLK, &lock) == -1; + + return 1; +} + + +/** Look for any connections that we should try to initiate. + * Reschedules itself to run again at the appropriate time. + * @param[in] ev Timer event (ignored). + */ +static void try_connections(struct Event* ev) { + struct ConfItem* aconf; + struct ConfItem** pconf; + time_t next; + struct Jupe* ajupe; + int hold; + int done; + + assert(ET_EXPIRE == ev_type(ev)); + assert(0 != ev_timer(ev)); + + Debug((DEBUG_NOTICE, "Connection check at : %s", myctime(CurrentTime))); + next = CurrentTime + feature_int(FEAT_CONNECTFREQUENCY); + done = 0; + + for (aconf = GlobalConfList; aconf; aconf = aconf->next) { + /* Only consider server items with non-zero port and non-zero + * connect times that are not actively juped. + */ + if (!(aconf->status & CONF_SERVER) + || aconf->address.port == 0 + || !(aconf->flags & CONF_AUTOCONNECT) + || ((ajupe = jupe_find(aconf->name)) && JupeIsActive(ajupe))) continue; - } - confrq = get_con_freq(cltmp); - aconf->hold = now + confrq; - /* - * Found a CONNECT config with port specified, scan clients - * and see if this server is already connected? + /* Do we need to postpone this connection further? */ + hold = aconf->hold > CurrentTime; + + /* Update next possible connection check time. */ + if (hold && next > aconf->hold) + next = aconf->hold; + + /* Do not try to connect if its use is still on hold until future, + * we have already initiated a connection this try_connections(), + * too many links in its connection class, it is already linked, + * or if connect rules forbid a link now. */ - cptr = FindServer(aconf->name); + if (hold || done + || (ConfLinks(aconf) > ConfMaxLinks(aconf)) + || FindServer(aconf->name) + || conf_eval_crule(aconf->name, CRULE_MASK)) + continue; - if (!cptr && (Links(cltmp) < MaxLinks(cltmp)) && - (!connecting || (ConClass(cltmp) > con_class))) - { - /* Check connect rules to see if we're allowed to try */ - for (cconf = conf; cconf; cconf = cconf->next) - if ((cconf->status & CONF_CRULE) && - (match(cconf->host, aconf->name) == 0)) - if (crule_eval(cconf->passwd)) - break; - if (!cconf) - { - con_class = ConClass(cltmp); - con_conf = aconf; - /* We connect only one at time... */ - connecting = TRUE; - } + /* Ensure it is at the end of the list for future checks. */ + if (aconf->next) { + /* Find aconf's location in the list and splice it out. */ + for (pconf = &GlobalConfList; *pconf; pconf = &(*pconf)->next) + if (*pconf == aconf) + *pconf = aconf->next; + /* Reinsert it at the end of the list (where pconf is now). */ + *pconf = aconf; + aconf->next = 0; } - if ((next > aconf->hold) || (next == 0)) - next = aconf->hold; - } - if (connecting) - { - if (con_conf->next) /* are we already last? */ - { - /* Put the current one at the end and make sure we try all connections */ - for (pconf = &conf; (aconf = *pconf); pconf = &(aconf->next)) - if (aconf == con_conf) - *pconf = aconf->next; - (*pconf = con_conf)->next = 0; - } - if (connect_server(con_conf, (aClient *)NULL, (struct hostent *)NULL) == 0) - sendto_ops("Connection to %s[%s] activated.", - con_conf->name, con_conf->host); + + /* Activate the connection itself. */ + if (connect_server(aconf, 0)) + sendto_opmask_butone(0, SNO_OLDSNO, "Connection to %s activated.", + aconf->name); + + /* And stop looking for further candidates. */ + done = 1; } + Debug((DEBUG_NOTICE, "Next connection check : %s", myctime(next))); - return (next); + timer_add(&connect_timer, try_connections, 0, TT_ABSOLUTE, next); } -static time_t check_pings(void) -{ - Reg1 aClient *cptr; - int ping = 0, i, rflag = 0; - time_t oldest = 0, timeout; - for (i = 0; i <= highest_fd; i++) - { - if (!(cptr = loc_clients[i]) || IsMe(cptr) || IsLog(cptr) || IsPing(cptr)) +/** Check for clients that have not sent a ping response recently. + * Reschedules itself to run again at the appropriate time. + * @param[in] ev Timer event (ignored). + */ +static void check_pings(struct Event* ev) { + int expire = 0; + int next_check = CurrentTime; + int max_ping = 0; + int i; + + assert(ET_EXPIRE == ev_type(ev)); + assert(0 != ev_timer(ev)); + + next_check += feature_int(FEAT_PINGFREQUENCY); + + /* Scan through the client table */ + for (i=0; i <= HighestFd; i++) { + struct Client *cptr = LocalClientArray[i]; + + if (!cptr) continue; + + assert(&me != cptr); /* I should never be in the local client array! */ + - /* - * Note: No need to notify opers here. - * It's already done when "FLAGS_DEADSOCKET" is set. - */ - if (IsDead(cptr)) - { - exit_client(cptr, cptr, &me, LastDeadComment(cptr)); + /* Remove dead clients. */ + if (IsDead(cptr)) { + exit_client(cptr, cptr, &me, cli_info(cptr)); continue; } -#if defined(R_LINES) && defined(R_LINES_OFTEN) - rflag = IsUser(cptr) ? find_restrict(cptr) : 0; -#endif - ping = IsRegistered(cptr) ? get_client_ping(cptr) : CONNECTTIMEOUT; - Debug((DEBUG_DEBUG, "c(%s)=%d p %d r %d a %d", - cptr->name, cptr->status, ping, rflag, (int)(now - cptr->lasttime))); - /* - * Ok, so goto's are ugly and can be avoided here but this code - * is already indented enough so I think its justified. -avalon + max_ping = IsRegistered(cptr) ? client_get_ping(cptr) : + feature_int(FEAT_CONNECTTIMEOUT); + + Debug((DEBUG_DEBUG, "check_pings(%s)=status:%s limit: %d current: %d", + cli_name(cptr), + IsPingSent(cptr) ? "[Ping Sent]" : "[]", + max_ping, (int)(CurrentTime - cli_lasttime(cptr)))); + + /* If it's a server and we have not sent an AsLL lately, do so. */ + if (IsServer(cptr)) { + if (CurrentTime - cli_serv(cptr)->asll_last >= max_ping) { + char *asll_ts; + + SetPingSent(cptr); + cli_serv(cptr)->asll_last = CurrentTime; + expire = cli_serv(cptr)->asll_last + max_ping; + asll_ts = militime_float(NULL); + sendcmdto_prio_one(&me, CMD_PING, cptr, "!%s %s %s", asll_ts, + cli_name(cptr), asll_ts); + } + + expire = cli_serv(cptr)->asll_last + max_ping; + if (expire < next_check) + next_check = expire; + } + + /* Ok, the thing that will happen most frequently, is that someone will + * have sent something recently. Cover this first for speed. + * -- + * If it's an unregistered client and hasn't managed to register within + * max_ping then it's obviously having problems (broken client) or it's + * just up to no good, so we won't skip it, even if its been sending + * data to us. + * -- hikari */ - if (!rflag && IsRegistered(cptr) && (ping >= now - cptr->lasttime)) - goto ping_timeout; - /* - * If the server hasnt talked to us in 2*ping seconds - * and it has a ping time, then close its connection. - * If the client is a user and a KILL line was found - * to be active, close this connection too. + if ((CurrentTime-cli_lasttime(cptr) < max_ping) && IsRegistered(cptr)) { + expire = cli_lasttime(cptr) + max_ping; + if (expire < next_check) + next_check = expire; + continue; + } + + /* Unregistered clients pingout after max_ping seconds, they don't + * get given a second chance - if they were then people could not quite + * finish registration and hold resources without being subject to k/g + * lines */ - if (rflag || - ((now - cptr->lasttime) >= (2 * ping) && - (cptr->flags & FLAGS_PINGSENT)) || - (!IsRegistered(cptr) && !IsHandshake(cptr) && - (now - cptr->firsttime) >= ping)) + if (!IsRegistered(cptr)) { + assert(!IsServer(cptr)); + /* If client authorization time has expired, ask auth whether they + * should be checked again later. */ + if ((CurrentTime-cli_firsttime(cptr) >= max_ping) + && auth_ping_timeout(cptr)) + continue; + /* OK, they still have enough time left, so we'll just skip to the + * next client. Set the next check to be when their time is up, if + * that's before the currently scheduled next check -- hikari */ + expire = cli_firsttime(cptr) + max_ping; + if (expire < next_check) + next_check = expire; + continue; + } + + /* Quit the client after max_ping*2 - they should have answered by now */ + if (CurrentTime-cli_lasttime(cptr) >= (max_ping*2) ) { - if (!IsRegistered(cptr) && (DoingDNS(cptr) || DoingAuth(cptr))) - { - Debug((DEBUG_NOTICE, "%s/%s timeout %s", DoingDNS(cptr) ? "DNS" : "", - DoingAuth(cptr) ? "AUTH" : "", get_client_name(cptr, TRUE))); - if (cptr->authfd >= 0) - { - close(cptr->authfd); - cptr->authfd = -1; - cptr->count = 0; - *cptr->buffer = '\0'; - } - del_queries((char *)cptr); - ClearAuth(cptr); - ClearDNS(cptr); - SetAccess(cptr); - cptr->firsttime = now; - cptr->lasttime = now; - continue; - } + /* If it was a server, then tell ops about it. */ if (IsServer(cptr) || IsConnecting(cptr) || IsHandshake(cptr)) - { - sendto_ops("No response from %s, closing link", - get_client_name(cptr, FALSE)); - exit_client(cptr, cptr, &me, "Ping timeout"); - continue; - } - /* - * This is used for KILL lines with time restrictions - * on them - send a messgae to the user being killed first. - */ -#if defined(R_LINES) && defined(R_LINES_OFTEN) - else if (IsUser(cptr) && rflag) - { - sendto_ops("Restricting %s, closing link.", - get_client_name(cptr, FALSE)); - exit_client(cptr, cptr, &me, "R-lined"); - } -#endif - else - { - if (!IsRegistered(cptr) && *cptr->name && *cptr->user->username) - { - sendto_one(cptr, - ":%s %d %s :Your client may not be compatible with this server.", - me.name, ERR_BADPING, cptr->name); - sendto_one(cptr, - ":%s %d %s :Compatible clients are available at " - "ftp://ftp.undernet.org/pub/irc/clients", - me.name, ERR_BADPING, cptr->name); - } - exit_client_msg(cptr, cptr, &me, "Ping timeout for %s", - get_client_name(cptr, FALSE)); - } + sendto_opmask_butone(0, SNO_OLDSNO, + "No response from %s, closing link", + cli_name(cptr)); + exit_client_msg(cptr, cptr, &me, "Ping timeout"); continue; } - else if (IsRegistered(cptr) && (cptr->flags & FLAGS_PINGSENT) == 0) + + if (!IsPingSent(cptr)) { - /* - * If we havent PINGed the connection and we havent - * heard from it in a while, PING it to make sure - * it is still alive. + /* If we haven't PINGed the connection and we haven't heard from it in a + * while, PING it to make sure it is still alive. */ - cptr->flags |= FLAGS_PINGSENT; - /* not nice but does the job */ - cptr->lasttime = now - ping; + SetPingSent(cptr); + + /* If we're late in noticing don't hold it against them :) */ + cli_lasttime(cptr) = CurrentTime - max_ping; + if (IsUser(cptr)) - sendto_one(cptr, "PING :%s", me.name); + sendrawto_one(cptr, MSG_PING " :%s", cli_name(&me)); else - sendto_one(cptr, ":%s PING :%s", me.name, me.name); + sendcmdto_prio_one(&me, CMD_PING, cptr, ":%s", cli_name(&me)); } - ping_timeout: - timeout = cptr->lasttime + ping; - while (timeout <= now) - timeout += ping; - if (timeout < oldest || !oldest) - oldest = timeout; + + expire = cli_lasttime(cptr) + max_ping * 2; + if (expire < next_check) + next_check=expire; } - if (!oldest || oldest < now) - oldest = now + PINGFREQUENCY; - Debug((DEBUG_NOTICE, - "Next check_ping() call at: %s, %d " TIME_T_FMT " " TIME_T_FMT, - myctime(oldest), ping, oldest, now)); - - return (oldest); + + assert(next_check >= CurrentTime); + + Debug((DEBUG_DEBUG, "[%i] check_pings() again in %is", + CurrentTime, next_check-CurrentTime)); + + timer_add(&ping_timer, check_pings, 0, TT_ABSOLUTE, next_check); } -/* - * bad_command - * - * This is called when the commandline is not acceptable. - * Give error message and exit without starting anything. - */ -static int bad_command(void) -{ - printf("Usage: ircd %s[-h servername] [-p portnumber] [-x loglevel] [-t]\n", -#ifdef CMDLINE_CONFIG - "[-f config] " -#else - "" -#endif - ); - printf("Server not started\n\n"); - return (-1); -} -static void setup_signals(void) -{ -#ifdef POSIX_SIGNALS - struct sigaction act; - - act.sa_handler = SIG_IGN; - act.sa_flags = 0; - sigemptyset(&act.sa_mask); - sigaddset(&act.sa_mask, SIGPIPE); - sigaddset(&act.sa_mask, SIGALRM); -#ifdef SIGWINCH - sigaddset(&act.sa_mask, SIGWINCH); - sigaction(SIGWINCH, &act, NULL); -#endif - sigaction(SIGPIPE, &act, NULL); - act.sa_handler = dummy; - sigaction(SIGALRM, &act, NULL); - act.sa_handler = s_rehash; - sigemptyset(&act.sa_mask); - sigaddset(&act.sa_mask, SIGHUP); - sigaction(SIGHUP, &act, NULL); - act.sa_handler = s_restart; - sigaddset(&act.sa_mask, SIGINT); - sigaction(SIGINT, &act, NULL); - act.sa_handler = s_die; - sigaddset(&act.sa_mask, SIGTERM); - sigaction(SIGTERM, &act, NULL); +/** Parse command line arguments. + * Global variables are updated to reflect the arguments. + * As a side effect, makes sure the process's effective user id is the + * same as the real user id. + * @param[in] argc Number of arguments on command line. + * @param[in,out] argv Command-lne arguments. + */ +static void parse_command_line(int argc, char** argv) { + const char *options = "d:f:h:nktvx:c:"; + int opt; -#else -#ifndef HAVE_RELIABLE_SIGNALS - signal(SIGPIPE, dummy); -#ifdef SIGWINCH - signal(SIGWINCH, dummy); -#endif -#else -#ifdef SIGWINCH - signal(SIGWINCH, SIG_IGN); -#endif - signal(SIGPIPE, SIG_IGN); -#endif - signal(SIGALRM, dummy); - signal(SIGHUP, s_rehash); - signal(SIGTERM, s_die); - signal(SIGINT, s_restart); -#endif + if (thisServer.euid != thisServer.uid) + setuid(thisServer.uid); -#ifdef HAVE_RESTARTABLE_SYSCALLS - /* - * At least on Apollo sr10.1 it seems continuing system calls - * after signal is the default. The following 'siginterrupt' - * should change that default to interrupting calls. + /* Do we really need to sanity check the non-NULLness of optarg? That's + * getopt()'s job... Removing those... -zs */ - siginterrupt(SIGALRM, 1); -#endif + while ((opt = getopt(argc, argv, options)) != EOF) + switch (opt) { + case 'k': thisServer.bootopt |= BOOT_CHKCONF | BOOT_TTY; break; + case 'c': dbg_client = optarg; break; + case 'n': + case 't': thisServer.bootopt |= BOOT_TTY; break; + case 'd': dpath = optarg; break; + case 'f': configfile = optarg; break; + case 'h': ircd_strncpy(cli_name(&me), optarg, HOSTLEN); break; + case 'v': + printf("ircd %s\n", version); + printf("Event engines: "); +#ifdef USE_KQUEUE + printf("kqueue() "); +#endif +#ifdef USE_DEVPOLL + printf("/dev/poll "); +#endif +#ifdef USE_EPOLL + printf("epoll_*() "); +#endif +#ifdef USE_POLL + printf("poll()"); +#else + printf("select()"); +#endif + printf("\nCompiled for a maximum of %d connections.\n", MAXCONNECTIONS); + + + exit(0); + break; + + case 'x': + debuglevel = atoi(optarg); + if (debuglevel < 0) + debuglevel = 0; + debugmode = optarg; + thisServer.bootopt |= BOOT_DEBUG; +#ifndef DEBUGMODE + printf("WARNING: DEBUGMODE disabled; -x has no effect.\n"); +#endif + break; + + default: + printf("Usage: ircd [-f config] [-h servername] [-x loglevel] [-ntv] [-k [-c clispec]]\n" + "\n -f config\t specify explicit configuration file" + "\n -x loglevel\t set debug logging verbosity" + "\n -n or -t\t don't detach" + "\n -v\t\t display version" + "\n -k\t\t exit after checking config" + "\n -c clispec\t search for client/kill blocks matching client" + "\n\t\t clispec is comma-separated list of user@host," + "\n\t\t user@ip, $Rrealname, and port number" + "\n\nServer not started.\n"); + exit(1); + } } -/* - * open_debugfile - * - * If the -t option is not given on the command line when the server is - * started, all debugging output is sent to the file set by LPATH in config.h - * Here we just open that file and make sure it is opened to fd 2 so that - * any fprintf's to stderr also goto the logfile. If the debuglevel is not - * set from the command line by -x, use /dev/null as the dummy logfile as long - * as DEBUGMODE has been defined, else dont waste the fd. + +/** Become a daemon. + * @param[in] no_fork If non-zero, do not fork into the background. */ -static void open_debugfile(void) -{ -#ifdef DEBUGMODE - int fd; - aClient *cptr; +static void daemon_init(int no_fork) { + if (no_fork) + return; - if (debuglevel >= 0) + if (fork()) + exit(0); + +#ifdef TIOCNOTTY { - cptr = make_client(NULL, STAT_LOG); - cptr->fd = 2; - cptr->port = debuglevel; - cptr->flags = 0; - cptr->acpt = cptr; - loc_clients[2] = cptr; - strcpy(cptr->sockhost, me.sockhost); - - printf("isatty = %d ttyname = %#x\n", isatty(2), (unsigned int)ttyname(2)); - if (!(bootopt & BOOT_TTY)) /* leave debugging output on fd 2 */ - { - if ((fd = creat(LOGFILE, 0600)) < 0) - if ((fd = open("/dev/null", O_WRONLY)) < 0) - exit(-1); - if (fd != 2) - { - dup2(fd, 2); - close(fd); - } - strncpy(cptr->name, LOGFILE, sizeof(cptr->name)); - cptr->name[sizeof(cptr->name) - 1] = 0; - } - else if (isatty(2) && ttyname(2)) - { - strncpy(cptr->name, ttyname(2), sizeof(cptr->name)); - cptr->name[sizeof(cptr->name) - 1] = 0; + int fd; + if ((fd = open("/dev/tty", O_RDWR)) > -1) { + ioctl(fd, TIOCNOTTY, 0); + close(fd); } - else - strcpy(cptr->name, "FD2-Pipe"); - Debug((DEBUG_FATAL, "Debug: File <%s> Level: %u at %s", - cptr->name, cptr->port, myctime(now))); } - else - loc_clients[2] = NULL; #endif - return; -} -int main(int argc, char *argv[]) -{ - unsigned short int portarg = 0; - uid_t uid; - uid_t euid; - time_t delay = 0; -#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_CORE) - struct rlimit corelim; -#endif + setsid(); +} - uid = getuid(); - euid = geteuid(); - now = time(NULL); -#ifdef PROFIL - monstartup(0, etext); - moncontrol(1); - signal(SIGUSR1, s_monitor); -#endif +/** Check that we have access to a particular file. + * If we do not have access to the file, complain on stderr. + * @param[in] path File name to check for access. + * @param[in] which Configuration character associated with file. + * @param[in] mode Bitwise combination of R_OK, W_OK, X_OK and/or F_OK. + * @return Non-zero if we have the necessary access, zero if not. + */ +static char check_file_access(const char *path, char which, int mode) { + if (!access(path, mode)) + return 1; -#ifdef CHROOTDIR - if (chdir(DPATH)) - { - fprintf(stderr, "Fail: Cannot chdir(%s): %s\n", DPATH, strerror(errno)); - exit(-1); - } - res_init(); - if (chroot(DPATH)) - { - fprintf(stderr, "Fail: Cannot chroot(%s): %s\n", DPATH, strerror(errno)); - exit(5); - } - dpath = "/"; -#endif /*CHROOTDIR */ + fprintf(stderr, + "Check on %cPATH (%s) failed: %s\n" + "Please create this file and/or rerun `configure' " + "using --with-%cpath and recompile to correct this.\n", + which, path, strerror(errno), which); - myargv = argv; - umask(077); /* better safe than sorry --SRB */ - memset(&me, 0, sizeof(me)); -#ifdef VIRTUAL_HOST - memset(&vserv, 0, sizeof(vserv)); -#endif + return 0; +} - setup_signals(); - initload(); +/*---------------------------------------------------------------------------- + * set_core_limit + *--------------------------------------------------------------------------*/ #if defined(HAVE_SETRLIMIT) && defined(RLIMIT_CORE) - if (getrlimit(RLIMIT_CORE, &corelim)) - { +/** Set the core size soft limit to the same as the hard limit. */ +static void set_core_limit(void) { + struct rlimit corelim; + + if (getrlimit(RLIMIT_CORE, &corelim)) { fprintf(stderr, "Read of rlimit core size failed: %s\n", strerror(errno)); - corelim.rlim_max = RLIM_INFINITY; /* Try to recover */ + corelim.rlim_max = RLIM_INFINITY; /* Try to recover */ } + corelim.rlim_cur = corelim.rlim_max; if (setrlimit(RLIMIT_CORE, &corelim)) fprintf(stderr, "Setting rlimit core size failed: %s\n", strerror(errno)); +} #endif - /* - * All command line parameters have the syntax "-fstring" - * or "-f string" (e.g. the space is optional). String may - * be empty. Flag characters cannot be concatenated (like - * "-fxyz"), it would conflict with the form "-fstring". - */ - while (--argc > 0 && (*++argv)[0] == '-') - { - char *p = argv[0] + 1; - int flag = *p++; - if (flag == '\0' || *p == '\0') - { - if (argc > 1 && argv[1][0] != '-') - { - p = *++argv; - argc -= 1; - } - else - p = ""; - } - switch (flag) - { - case 'a': - bootopt |= BOOT_AUTODIE; - break; - case 'c': - bootopt |= BOOT_CONSOLE; - break; - case 'q': - bootopt |= BOOT_QUICK; - break; - case 'd': - if (euid != uid) - setuid((uid_t) uid); - dpath = p; - break; -#ifdef CMDLINE_CONFIG - case 'f': - if (euid != uid) - setuid((uid_t) uid); - configfile = p; - break; -#endif - case 'h': - strncpy(me.name, p, sizeof(me.name)); - me.name[sizeof(me.name) - 1] = 0; - break; - case 'i': - bootopt |= BOOT_INETD | BOOT_AUTODIE; - break; - case 'p': - if ((portarg = atoi(p)) > 0) - portnum = portarg; - break; - case 't': - if (euid != uid) - setuid((uid_t) uid); - bootopt |= BOOT_TTY; - break; - case 'v': - printf("ircd %s\n", version); - exit(0); -#ifdef VIRTUAL_HOST - case 'w': - { - struct hostent *hep; - if (!(hep = gethostbyname(p))) - { - fprintf(stderr, "%s: Error creating virtual host \"%s\": %d", - argv[0], p, h_errno); - return -1; - } - if (hep->h_addrtype == AF_INET && hep->h_addr_list[0] && - !hep->h_addr_list[1]) - { - memcpy(&vserv.sin_addr, hep->h_addr_list[0], sizeof(struct in_addr)); - vserv.sin_family = AF_INET; - } - else - { - fprintf(stderr, "%s: Error creating virtual host \"%s\": " - "Use -w \n", argv[0], p); - return -1; - } - break; - } -#endif - case 'x': -#ifdef DEBUGMODE - if (euid != uid) - setuid((uid_t) uid); - debuglevel = atoi(p); - debugmode = *p ? p : "0"; - bootopt |= BOOT_DEBUG; - break; -#else - fprintf(stderr, "%s: DEBUGMODE must be defined for -x y\n", myargv[0]); - exit(0); -#endif - default: - bad_command(); - break; - } +/** Complain to stderr if any user or group ID belongs to the superuser. + * @return Non-zero if all IDs are okay, zero if some are 0. + */ +static int set_userid_if_needed(void) { + if (getuid() == 0 || geteuid() == 0 || + getgid() == 0 || getegid() == 0) { + fprintf(stderr, "ERROR: This server will not run as superuser.\n"); + return 0; } - if (chdir(dpath)) - { - fprintf(stderr, "Fail: Cannot chdir(%s): %s\n", dpath, strerror(errno)); - exit(-1); - } + return 1; +} -#ifndef IRC_UID - if ((uid != euid) && !euid) - { - fprintf(stderr, - "ERROR: do not run ircd setuid root. Make it setuid a normal user.\n"); - exit(-1); - } + +/*---------------------------------------------------------------------------- + * main - entrypoint + * + * TODO: This should set the basic environment up and start the main loop. + * we're doing waaaaaaaaay too much server initialization here. I hate + * long and ugly control paths... -smd + *--------------------------------------------------------------------------*/ +/** Run the daemon. + * @param[in] argc Number of arguments in \a argv. + * @param[in] argv Arguments to program execution. + */ +int main(int argc, char **argv) { + CurrentTime = time(NULL); + + thisServer.argc = argc; + thisServer.argv = argv; + thisServer.uid = getuid(); + thisServer.euid = geteuid(); + +#ifdef MDEBUG + mem_dbg_initialise(); #endif -#if !defined(CHROOTDIR) || (defined(IRC_UID) && defined(IRC_GID)) -#ifndef _AIX - if (euid != uid) - { - setuid((uid_t) uid); - setuid((uid_t) euid); - } +#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_CORE) + set_core_limit(); #endif - if ((int)getuid() == 0) - { -#if defined(IRC_UID) && defined(IRC_GID) + umask(077); /* better safe than sorry --SRB */ + memset(&me, 0, sizeof(me)); + memset(&me_con, 0, sizeof(me_con)); + cli_connect(&me) = &me_con; + cli_fd(&me) = -1; - /* run as a specified user */ - fprintf(stderr, "WARNING: running ircd with uid = %d\n", IRC_UID); - fprintf(stderr, " changing to gid %d.\n", IRC_GID); - setuid(IRC_UID); - setgid(IRC_GID); -#else - /* check for setuid root as usual */ - fprintf(stderr, - "ERROR: do not run ircd setuid root. Make it setuid a normal user.\n"); - exit(-1); -#endif + parse_command_line(argc, argv); + + if (chdir(dpath)) { + fprintf(stderr, "Fail: Cannot chdir(%s): %s, check DPATH\n", dpath, strerror(errno)); + return 2; } -#endif /*CHROOTDIR/UID/GID */ - if (argc > 0) - return bad_command(); /* This should exit out */ + if (!set_userid_if_needed()) + return 3; -#if HAVE_UNISTD_H - /* Sanity checks */ - { - char c; - char *path; + /* Check paths for accessibility */ + if (!check_file_access(SPATH, 'S', X_OK) || + !check_file_access(configfile, 'C', R_OK)) + return 4; - c = 'S'; - path = SPATH; - if (access(path, X_OK) == 0) - { - c = 'C'; - path = CPATH; - if (access(path, R_OK) == 0) - { - c = 'M'; - path = MPATH; - if (access(path, R_OK) == 0) - { - c = 'R'; - path = RPATH; - if (access(path, R_OK) == 0) - { -#ifndef DEBUG - c = 0; -#else - c = 'L'; - path = LPATH; - if (access(path, W_OK) == 0) - c = 0; -#endif - } - } - } + if (!init_connection_limits()) + return 9; + + close_connections(!(thisServer.bootopt & (BOOT_DEBUG | BOOT_TTY | BOOT_CHKCONF))); + + /* daemon_init() must be before event_init() because kqueue() FDs + * are, perversely, not inherited across fork(). + */ + daemon_init(thisServer.bootopt & BOOT_TTY); + +#ifdef DEBUGMODE + /* Must reserve fd 2... */ + if (debuglevel >= 0 && !(thisServer.bootopt & BOOT_TTY)) { + int fd; + if ((fd = open("/dev/null", O_WRONLY)) < 0) { + fprintf(stderr, "Unable to open /dev/null (to reserve fd 2): %s\n", + strerror(errno)); + return 8; } - if (c) - { - fprintf(stderr, "Check on %cPATH (%s) failed: %s\n", - c, path, strerror(errno)); - fprintf(stderr, - "Please create file and/or rerun `make config' and recompile to correct this.\n"); -#ifdef CHROOTDIR - fprintf(stderr, - "Keep in mind that all paths are relative to CHROOTDIR.\n"); -#endif - exit(-1); + if (fd != 2 && dup2(fd, 2) < 0) { + fprintf(stderr, "Unable to reserve fd 2; dup2 said: %s\n", + strerror(errno)); + return 8; } } #endif - hash_init(); -#ifdef DEBUGMODE - initlists(); -#endif - initclass(); + event_init(MAXCONNECTIONS); + + setup_signals(); + feature_init(); /* initialize features... */ + log_init(*argv); + set_nomem_handler(outofmemory); + + initload(); + init_list(); + init_hash(); + init_class(); initwhowas(); initmsgtree(); initstats(); - open_debugfile(); - if (portnum == 0) - portnum = PORTNUM; - me.port = portnum; - init_sys(); - me.flags = FLAGS_LISTEN; - if ((bootopt & BOOT_INETD)) - { - me.fd = 0; - loc_clients[0] = &me; - me.flags = FLAGS_LISTEN; + + /* we need this for now, when we're modular this + should be removed -- hikari */ + ircd_crypt_init(); + + motd_init(); + + if (!init_conf()) { + log_write(LS_SYSTEM, L_CRIT, 0, "Failed to read configuration file %s", + configfile); + return 7; } - else - me.fd = -1; -#ifdef USE_SYSLOG - openlog(myargv[0], LOG_PID | LOG_NDELAY, LOG_FACILITY); -#endif - if (initconf(bootopt) == -1) - { - Debug((DEBUG_FATAL, "Failed in reading configuration file %s", configfile)); - printf("Couldn't open configuration file %s\n", configfile); - exit(-1); + if (thisServer.bootopt & BOOT_CHKCONF) { + if (dbg_client) + conf_debug_iline(dbg_client); + fprintf(stderr, "Configuration file %s checked okay.\n", configfile); + return 0; } - if (!(bootopt & BOOT_INETD)) - { - static char star[] = "*"; - aConfItem *aconf; - if ((aconf = find_me()) && portarg == 0 && aconf->port != 0) - portnum = aconf->port; - Debug((DEBUG_ERROR, "Port = %u", portnum)); - if (inetport(&me, star, portnum)) - exit(1); + debug_init(thisServer.bootopt & BOOT_TTY); + if (check_pid()) { + Debug((DEBUG_FATAL, "Failed to acquire PID file lock after fork")); + exit(2); } - else if (inetport(&me, "*", 0)) - exit(1); - - read_tlines(); - rmotd = read_motd(RPATH); - motd = read_motd(MPATH); - setup_ping(); - get_my_name(&me, me.sockhost, sizeof(me.sockhost) - 1); - now = time(NULL); - me.hopcount = 0; - me.authfd = -1; - me.confs = NULL; - me.next = NULL; - me.user = NULL; - me.from = &me; + + init_server_identity(); + + uping_init(); + + stats_init(); + + IPcheck_init(); + timer_add(timer_init(&connect_timer), try_connections, 0, TT_RELATIVE, 1); + timer_add(timer_init(&ping_timer), check_pings, 0, TT_RELATIVE, 1); + timer_add(timer_init(&destruct_event_timer), exec_expired_destruct_events, 0, TT_PERIODIC, 60); + + CurrentTime = time(NULL); + SetMe(&me); + cli_magic(&me) = CLIENT_MAGIC; + cli_from(&me) = &me; make_server(&me); - /* Abuse own link timestamp as start timestamp: */ - me.serv->timestamp = TStime(); - me.serv->prot = atoi(MAJOR_PROTOCOL); - me.serv->up = &me; - me.serv->down = NULL; + + cli_serv(&me)->timestamp = TStime(); /* Abuse own link timestamp as start TS */ + cli_serv(&me)->prot = atoi(MAJOR_PROTOCOL); + cli_serv(&me)->up = &me; + cli_serv(&me)->down = NULL; + cli_handler(&me) = SERVER_HANDLER; SetYXXCapacity(&me, MAXCLIENTS); - me.lasttime = me.since = me.firsttime = now; + cli_lasttime(&me) = cli_since(&me) = cli_firsttime(&me) = CurrentTime; + hAddClient(&me); +#ifdef IPV6 + SetIPv6(&me); +#endif - check_class(); write_pidfile(); - init_counters(); Debug((DEBUG_NOTICE, "Server ready...")); -#ifdef USE_SYSLOG - syslog(LOG_NOTICE, "Server Ready"); -#endif + log_write(LS_SYSTEM, L_NOTICE, 0, "Server Ready"); - for (;;) - { - /* - * We only want to connect if a connection is due, - * not every time through. Note, if there are no - * active C lines, this call to Tryconnections is - * made once only; it will return 0. - avalon - */ - if (nextconnect && now >= nextconnect) - nextconnect = try_connections(); - /* - * DNS checks. One to timeout queries, one for cache expiries. - */ - if (now >= nextdnscheck) - nextdnscheck = timeout_query_list(); - if (now >= nextexpire) - nextexpire = expire_cache(); - /* - * Take the smaller of the two 'timed' event times as - * the time of next event (stops us being late :) - avalon - * WARNING - nextconnect can return 0! - */ - if (nextconnect) - delay = MIN(nextping, nextconnect); - else - delay = nextping; - delay = MIN(nextdnscheck, delay); - delay = MIN(nextexpire, delay); - delay -= now; - /* - * Adjust delay to something reasonable [ad hoc values] - * (one might think something more clever here... --msa) - * We don't really need to check that often and as long - * as we don't delay too long, everything should be ok. - * waiting too long can cause things to timeout... - * i.e. PINGS -> a disconnection :( - * - avalon - */ - if (delay < 1) - delay = 1; - else - delay = MIN(delay, TIMESEC); - read_message(delay); - - Debug((DEBUG_DEBUG, "Got message(s)")); - - /* - * ...perhaps should not do these loops every time, - * but only if there is some chance of something - * happening (but, note that conf->hold times may - * be changed elsewhere--so precomputed next event - * time might be too far away... (similarly with - * ping times) --msa - */ - if (now >= nextping) - nextping = check_pings(); + event_loop(); - if (dorehash) - { - rehash(&me, 1); - dorehash = 0; - } - if (restartFlag) - server_reboot(); - } + return 0; } + +