X-Git-Url: http://git.pk910.de/?a=blobdiff_plain;f=ircd%2Fircd.c;h=6d59e35f1fc42fac0553b2d7afe120c58af762a8;hb=refs%2Fheads%2Fupstream;hp=f49033145a66d2041e123f0419feebc38c32d5c7;hpb=0c8060248693049c19591a9243f8a4ea6a7f4c72;p=ircu2.10.12-pk.git diff --git a/ircd/ircd.c b/ircd/ircd.c index f490331..6d59e35 100644 --- a/ircd/ircd.c +++ b/ircd/ircd.c @@ -16,26 +16,36 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * $Id$ */ +/** @file + * @brief Entry point and other initialization functions for the daemon. + * @version $Id$ + */ +#include "config.h" + #include "ircd.h" #include "IPcheck.h" #include "class.h" #include "client.h" #include "crule.h" +#include "destruct_event.h" #include "hash.h" -#include "ircd_alloc.h" /* set_nomem_handler */ +#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 "listener.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" @@ -43,552 +53,494 @@ #include "s_conf.h" #include "s_debug.h" #include "s_misc.h" +#include "s_stats.h" #include "send.h" -#include "struct.h" #include "sys.h" #include "uping.h" #include "userload.h" #include "version.h" #include "whowas.h" -#include "msg.h" -#include +/* #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 -#include + + +/*---------------------------------------------------------------------------- + * External stuff + *--------------------------------------------------------------------------*/ extern void init_counters(void); +extern void mem_dbg_initialise(void); +/*---------------------------------------------------------------------------- + * Constants / Enums + *--------------------------------------------------------------------------*/ enum { - BOOT_DEBUG = 1, - BOOT_TTY = 2 + BOOT_DEBUG = 1, /**< Enable debug output. */ + BOOT_TTY = 2, /**< Stay connected to TTY. */ + BOOT_CHKCONF = 4 /**< Exit after reading configuration file. */ }; -struct Client me; /* That's me */ -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() */ -static struct Daemon thisServer = { 0 }; /* server process info */ +/*---------------------------------------------------------------------------- + * 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) +{ + /* 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; +} -char *configfile = CPATH; /* Server configuration file */ -int debuglevel = -1; /* Server debug level */ -char *debugmode = ""; /* -"- -"- -"- */ -static char *dpath = DPATH; +/*---------------------------------------------------------------------------- + * 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) +{ + /* 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); +} -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 */ +/*---------------------------------------------------------------------------- + * 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) +{ + static int restarting = 0; -#ifdef PROFIL -extern etext(void); -#endif + /* 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; -static void server_reboot(const char* message) -{ sendto_opmask_butone(0, SNO_OLDSNO, "Restarting server: %s", message); Debug((DEBUG_NOTICE, "Restarting server...")); flush_connections(0); - close_log(); - close_connections(!(thisServer.bootopt & (BOOT_TTY | BOOT_DEBUG))); + 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 - */ - open_log(*thisServer.argv); - ircd_log(L_CRIT, "execv(%s,%s) failed: %m\n", 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); Debug((DEBUG_FATAL, "Couldn't restart server \"%s\": %s", SPATH, (strerror(errno)) ? strerror(errno) : "")); - exit(2); -} - -void server_die(const char* message) -{ - ircd_log(L_CRIT, "Server terminating: %s", message); - sendto_opmask_butone(0, SNO_OLDSNO, "Server terminating: %s", message); - flush_connections(0); - close_connections(1); - thisServer.running = 0; + exit(8); } -void server_restart(const char* message) -{ - static int restarting = 0; - - ircd_log(L_WARNING, "Restarting Server: %s", message); - if (restarting == 0) { - restarting = 1; - server_reboot(message); - } -} -static void outofmemory(void) -{ +/*---------------------------------------------------------------------------- + * 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"); -} +} -static void write_pidfile(void) -{ -#ifdef PPATH - int fd; + +/*---------------------------------------------------------------------------- + * write_pidfile + *--------------------------------------------------------------------------*/ +/** Write process ID to PID file. */ +static void write_pidfile(void) { char buff[20]; - if ((fd = open(PPATH, O_CREAT | O_WRONLY, 0600)) == -1) { - Debug((DEBUG_NOTICE, "Error opening pid file \"%s\": %s", - PPATH, strerror(errno))); + + 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; } - memset(buff, 0, sizeof(buff)); - sprintf(buff, "%5d\n", getpid()); - if (write(fd, buff, strlen(buff)) == -1) - Debug((DEBUG_NOTICE, "Error writing to pid file %s", PPATH)); - close(fd); -#endif + Debug((DEBUG_NOTICE, "Error opening pid file %s: %m", + feature_str(FEAT_PPATH))); } -/* - * 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...) +/** 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) { + struct flock lock; + + 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 Client* cptr; struct ConfItem** pconf; - int connecting; - int confrq; - time_t next = 0; - struct ConfClass* cltmp; - struct ConfItem* cconf; - struct ConfItem* con_conf = NULL; + time_t next; struct Jupe* ajupe; - unsigned int con_class = 0; + int hold; + int done; + + assert(ET_EXPIRE == ev_type(ev)); + assert(0 != ev_timer(ev)); - connecting = FALSE; Debug((DEBUG_NOTICE, "Connection check at : %s", myctime(CurrentTime))); - for (aconf = GlobalConfList; aconf; aconf = aconf->next) - { - /* Also when already connecting! (update holdtimes) --SRB */ - if (!(aconf->status & CONF_SERVER) || aconf->port == 0) - continue; + next = CurrentTime + feature_int(FEAT_CONNECTFREQUENCY); + done = 0; - /* Also skip juped servers */ - if ((ajupe = jupe_find(aconf->name)) && JupeIsActive(ajupe)) + 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; - 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 >;) ] - */ + /* Do we need to postpone this connection further? */ + hold = aconf->hold > CurrentTime; - if ((aconf->hold > CurrentTime)) - { - if ((next > aconf->hold) || (next == 0)) + /* Update next possible connection check time. */ + if (hold && next > aconf->hold) next = aconf->hold; - continue; - } - confrq = get_con_freq(cltmp); - aconf->hold = CurrentTime + confrq; - /* - * Found a CONNECT config with port specified, scan clients - * and see if this server is already connected? + /* 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 = GlobalConfList; 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; - } - } - 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 = &GlobalConfList; (aconf = *pconf); pconf = &(aconf->next)) - if (aconf == con_conf) + /* 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; - (*pconf = con_conf)->next = 0; + /* Reinsert it at the end of the list (where pconf is now). */ + *pconf = aconf; + aconf->next = 0; } - if (connect_server(con_conf, 0, 0)) + + /* Activate the connection itself. */ + if (connect_server(aconf, 0)) sendto_opmask_butone(0, SNO_OLDSNO, "Connection to %s activated.", - con_conf->name); + 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) -{ - int expire=0; - /* Temp to figure out what time this connection will next need - * to be checked. - */ - int next_check = CurrentTime + PINGFREQUENCY; - /* - * The current lowest expire time - ie: the time that check_pings - * needs to be called next. - */ - int max_ping = 0; - /* - * The time you've got before a ping is sent/your connection is - * terminated. - */ - - int i=0; /* loop counter */ + +/** 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; - - cptr = LocalClientArray[i]; + /* Scan through the client table */ + for (i=0; i <= HighestFd; i++) { + struct Client *cptr = LocalClientArray[i]; - /* Skip empty entries */ - if (!cptr) - continue; + if (!cptr) + continue; - assert(&me != cptr); /* I should never be in the local client array, - * so if I am, dying is a good thing(tm). - */ + assert(&me != cptr); /* I should never be in the local client array! */ - /* Remove dead clients. - * We will have sent opers a message when we set the dead flag, - * so don't bother to send one now. - */ - if (IsDead(cptr)) { - exit_client(cptr, cptr, &me, cptr->info); - continue; - } - - /* Should we concider adding a class 0 for 'unregistered clients', - * where we can specify their 'ping timeout' etc? - */ - max_ping = IsRegistered(cptr) ? get_client_ping(cptr) : CONNECTTIMEOUT; - - Debug((DEBUG_DEBUG, "check_pings(%s)=status:%s limit: %d current: %d", - cptr->name, (cptr->flags & FLAGS_PINGSENT) ? "[Ping Sent]" : "[]", - max_ping, (int)(CurrentTime - cptr->lasttime))); - - /* Ok, the thing that will happen most frequently, is that someone will - * have sent something recently. Cover this first for speed. - */ - if (CurrentTime-cptr->lasttime < max_ping) { - expire=cptr->lasttime + max_ping; - if (expirelasttime >= (max_ping*2) ) { - - /* If it was a server, then tell ops about it. */ - if (IsServer(cptr) || IsConnecting(cptr) || IsHandshake(cptr)) - sendto_opmask_butone(0, SNO_OLDSNO, - "No response from %s, closing link", cptr->name); - exit_client_msg(cptr, cptr, &me, "Ping timeout"); + /* Remove dead clients. */ + if (IsDead(cptr)) { + exit_client(cptr, cptr, &me, cli_info(cptr)); continue; - } /* of testing to see if ping has been sent */ - + } + + Debug((DEBUG_DEBUG, "check_pings(%s)=status:%s current: %d", + cli_name(cptr), + IsPingSent(cptr) ? "[Ping Sent]" : "[]", + (int)(CurrentTime - cli_lasttime(cptr)))); + /* 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 (!IsRegistered(cptr)) { - /* Display message if they have sent a NICK and a USER but no - * nospoof PONG. - */ - if (*cptr->name && cptr->user && *cptr->user->username) { - send_reply(cptr, RPL_EXPLICIT | ERR_BADPING, - ":Your client may not be compatible with this server."); - send_reply(cptr, RPL_EXPLICIT | ERR_BADPING, - ":Compatible clients are available at " - "ftp://ftp.undernet.org/pub/irc/clients"); - } - exit_client_msg(cptr,cptr,&me, "Ping Timeout"); - continue; - } /* of not registered */ - - if (0 == (cptr->flags & FLAGS_PINGSENT)) { - /* - * If we havent PINGed the connection and we havent heard from it in a - * while, PING it to make sure it is still alive. - */ - cptr->flags |= FLAGS_PINGSENT; + assert(!IsServer(cptr)); + max_ping = feature_int(FEAT_CONNECTTIMEOUT); + /* 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; + if (!IsRegistered(cptr)) { + /* 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; + } + } - /* - * If we're late in noticing don't hold it against them :) - */ - cptr->lasttime = CurrentTime - max_ping; - - if (IsUser(cptr)) - sendrawto_one(cptr, MSG_PING " :%s", me.name); - else - sendcmdto_one(&me, CMD_PING, cptr, ":%s", me.name); - } /* of if not ping sent... */ - - expire=cptr->lasttime+max_ping*2; - - if (expire=CurrentTime); - - Debug((DEBUG_DEBUG, "[%i] check_pings() again in %is",CurrentTime,next_check-CurrentTime)); - - return next_check; -} + /* 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; -#if 0 -static time_t check_pings(void) -{ - struct Client *cptr; - int max_ping = 0; - int i; - time_t oldest = CurrentTime + PINGFREQUENCY; - time_t timeout; + 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); + } - /* For each client... */ - for (i = 0; i <= HighestFd; i++) { - if (!(cptr = LocalClientArray[i])) /* oops! not a client... */ - continue; - /* - * me is never in the local client array - */ - assert(cptr != &me); - /* - * Note: No need to notify opers here. - * It's already done when "FLAGS_DEADSOCKET" is set. + 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 (IsDead(cptr)) { - exit_client(cptr, cptr, &me, cptr->info); + if ((CurrentTime-cli_lasttime(cptr) < max_ping) && IsRegistered(cptr)) { + expire = cli_lasttime(cptr) + max_ping; + if (expire < next_check) + next_check = expire; continue; } - max_ping = IsRegistered(cptr) ? get_client_ping(cptr) : CONNECTTIMEOUT; - - Debug((DEBUG_DEBUG, "check_pings(%s)=status:%d ping: %d current: %d", - cptr->name, cptr->status, max_ping, - (int)(CurrentTime - 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 - */ - /* - * If this is a registered client that we've heard of in a reasonable - * time, then skip them. - */ - if (IsRegistered(cptr) && (max_ping >= CurrentTime - 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 - cptr->lasttime) >= (2 * ping) && (cptr->flags & FLAGS_PINGSENT)) || - (!IsRegistered(cptr) && !IsHandshake(cptr) && (CurrentTime - cptr->firsttime) >= ping)) + /* Quit the client after max_ping*2 - they should have answered by now */ + if (CurrentTime-cli_lasttime(cptr) >= (max_ping*2) ) { + /* 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", cptr->name); /* XXX DEAD */ - exit_client(cptr, cptr, &me, "Ping timeout"); - continue; - } - else { - if (!IsRegistered(cptr) && *cptr->name && *cptr->user->username) { - sendto_one(cptr, /* XXX DEAD */ - ":%s %d %s :Your client may not be compatible with this server.", - me.name, ERR_BADPING, cptr->name); - sendto_one(cptr, /* XXX DEAD */ - ":%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"); - } + 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) && 0 == (cptr->flags & FLAGS_PINGSENT)) { - /* - * If we havent PINGed the connection and we havent - * 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 + + if (!IsPingSent(cptr)) + { + /* 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->lasttime = CurrentTime - 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); /* XXX DEAD */ + sendrawto_one(cptr, MSG_PING " :%s", cli_name(&me)); else - sendto_one(cptr, "%s " TOK_PING " :%s", NumServ(&me), me.name); /* XXX DEAD */ + sendcmdto_prio_one(&me, CMD_PING, cptr, ":%s", cli_name(&me)); } -ping_timeout: - timeout = cptr->lasttime + max_ping; - while (timeout <= CurrentTime) - timeout += max_ping; - if (timeout < oldest) - oldest = timeout; + + expire = cli_lasttime(cptr) + max_ping * 2; + if (expire < next_check) + next_check=expire; } - if (oldest < CurrentTime) - oldest = CurrentTime + PINGFREQUENCY; - Debug((DEBUG_NOTICE, - "Next check_ping() call at: %s, %d " TIME_T_FMT " " TIME_T_FMT, - myctime(oldest), ping, oldest, CurrentTime)); - - return (oldest); -} -#endif - -/* - * bad_command - * - * This is called when the commandline is not acceptable. - * Give error message and exit without starting anything. - */ -static void print_usage(void) -{ - printf("Usage: ircd [-f config] [-h servername] [-x loglevel] [-ntv]\n"); - printf("\n -n -t\t Don't detach\n -v\t display version\n\n"); - printf("Server not started\n"); + + 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); } -/* - * for getopt - * ZZZ this is going to need confirmation on other OS's - * - * #include - * Solaris has getopt.h, you should too... hopefully - * BSD declares them in stdlib.h - * extern char *optarg; - * - * for FreeBSD the following are defined: - * - * extern char *optarg; - * extern int optind; - * extern in optopt; - * extern int opterr; - * extern in optreset; - * - * - * All command line parameters have the syntax "-f string" or "-fstring" - * OPTIONS: - * -d filename - specify d:line file - * -f filename - specify config file - * -h hostname - specify server name - * -k filename - specify k:line file (hybrid) - * -l filename - specify log file - * -n - do not fork, run in foreground - * -t - do not fork send debugging info to tty - * -v - print version and exit - * -x - set debug level, if compiled for debug logging +/** 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:ntvx:"; +static void parse_command_line(int argc, char** argv) { + const char *options = "d:f:h:nktvx:c:"; int opt; if (thisServer.euid != thisServer.uid) setuid(thisServer.uid); - while ((opt = getopt(argc, argv, options)) != EOF) { + /* Do we really need to sanity check the non-NULLness of optarg? That's + * getopt()'s job... Removing those... -zs + */ + while ((opt = getopt(argc, argv, options)) != EOF) switch (opt) { - case 'd': - if (optarg) - dpath = optarg; - break; - case 'f': - if (optarg) - configfile = optarg; - break; - case 'h': - if (optarg) - ircd_strncpy(me.name, optarg, HOSTLEN); - break; + 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 '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': - if (optarg) { - debuglevel = atoi(optarg); - if (debuglevel < 0) - debuglevel = 0; - debugmode = optarg; - thisServer.bootopt |= BOOT_DEBUG; - } + 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: - print_usage(); + 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); } - } } -/* - * daemon_init - */ -static void daemon_init(int no_fork) -{ - if (!init_connection_limits()) - exit(2); - close_connections(!(thisServer.bootopt & (BOOT_DEBUG | BOOT_TTY))); +/** Become a daemon. + * @param[in] no_fork If non-zero, do not fork into the background. + */ +static void daemon_init(int no_fork) { if (no_fork) return; if (fork()) exit(0); + #ifdef TIOCNOTTY { int fd; @@ -598,268 +550,220 @@ static void daemon_init(int no_fork) } } #endif + setsid(); } +/** 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; -static void event_loop(void) -{ - time_t delay = 0; - - while (thisServer.running) { - /* - * 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 && CurrentTime >= nextconnect) - nextconnect = try_connections(); - /* - * DNS checks. One to timeout queries, one for cache expiries. - */ - nextdnscheck = timeout_resolver(CurrentTime); - /* - * 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 = IRCD_MIN(nextping, nextconnect); - else - delay = nextping; - delay = IRCD_MIN(nextdnscheck, delay); - delay -= CurrentTime; - /* - * 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 = IRCD_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 (CurrentTime >= nextping) - nextping = check_pings(); - - /* - * timeout pending queries that haven't been responded to - */ - timeout_auth_queries(CurrentTime); + 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); - IPcheck_expire(); - if (GlobalRehashFlag) { - rehash(&me, 1); - GlobalRehashFlag = 0; - } - if (GlobalRestartFlag) - server_restart("caught signal: SIGINT"); - } + return 0; } -int main(int argc, char *argv[]) -{ + +/*---------------------------------------------------------------------------- + * set_core_limit + *--------------------------------------------------------------------------*/ #if defined(HAVE_SETRLIMIT) && defined(RLIMIT_CORE) +/** 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_cur = corelim.rlim_max; + if (setrlimit(RLIMIT_CORE, &corelim)) + fprintf(stderr, "Setting rlimit core size failed: %s\n", strerror(errno)); +} #endif - CurrentTime = time(NULL); - /* - * sanity check - */ - if (MAXCONNECTIONS < 64 || MAXCONNECTIONS > 256000) { - fprintf(stderr, "%s: MAXCONNECTIONS insane: %d\n", *argv, MAXCONNECTIONS); - return 2; + + +/** 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; } + + return 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 PROFIL - monstartup(0, etext); - moncontrol(1); - signal(SIGUSR1, s_monitor); + +#ifdef MDEBUG + mem_dbg_initialise(); #endif -#ifdef CHROOTDIR - if (chdir(DPATH)) { - fprintf(stderr, "Fail: Cannot chdir(%s): %s\n", DPATH, strerror(errno)); - exit(2); - } - if (chroot(DPATH)) { - fprintf(stderr, "Fail: Cannot chroot(%s): %s\n", DPATH, strerror(errno)); - exit(5); - } - dpath = "/"; -#endif /*CHROOTDIR */ +#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_CORE) + set_core_limit(); +#endif umask(077); /* better safe than sorry --SRB */ memset(&me, 0, sizeof(me)); - me.fd = -1; + memset(&me_con, 0, sizeof(me_con)); + cli_connect(&me) = &me_con; + cli_fd(&me) = -1; - setup_signals(); - initload(); - -#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_CORE) - 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_cur = corelim.rlim_max; - if (setrlimit(RLIMIT_CORE, &corelim)) - fprintf(stderr, "Setting rlimit core size failed: %s\n", strerror(errno)); -#endif parse_command_line(argc, argv); if (chdir(dpath)) { - fprintf(stderr, "Fail: Cannot chdir(%s): %s\n", dpath, strerror(errno)); - exit(2); + fprintf(stderr, "Fail: Cannot chdir(%s): %s, check DPATH\n", dpath, strerror(errno)); + return 2; } -#ifndef IRC_UID - if ((thisServer.uid != thisServer.euid) && !thisServer.euid) { - fprintf(stderr, - "ERROR: do not run ircd setuid root. Make it setuid a normal user.\n"); - exit(2); - } -#endif + if (!set_userid_if_needed()) + return 3; -#if !defined(CHROOTDIR) || (defined(IRC_UID) && defined(IRC_GID)) - if (thisServer.euid != thisServer.uid) { - setuid(thisServer.uid); - setuid(thisServer.euid); - } + /* Check paths for accessibility */ + if (!check_file_access(SPATH, 'S', X_OK) || + !check_file_access(configfile, 'C', R_OK)) + return 4; - if (0 == getuid()) { -#if defined(IRC_UID) && defined(IRC_GID) + if (!init_connection_limits()) + return 9; - /* 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(2); -#endif - } -#endif /*CHROOTDIR/UID/GID */ + close_connections(!(thisServer.bootopt & (BOOT_DEBUG | BOOT_TTY | BOOT_CHKCONF))); - /* Sanity checks */ - { - char c; - char *path; - - 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 - } - } - } + /* 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(2); + if (fd != 2 && dup2(fd, 2) < 0) { + fprintf(stderr, "Unable to reserve fd 2; dup2 said: %s\n", + strerror(errno)); + return 8; } } +#endif + + event_init(MAXCONNECTIONS); + + setup_signals(); + feature_init(); /* initialize features... */ + log_init(*argv); + set_nomem_handler(outofmemory); + initload(); init_list(); - hash_init(); - initclass(); + init_hash(); + init_class(); initwhowas(); initmsgtree(); initstats(); - debug_init(thisServer.bootopt & BOOT_TTY); - daemon_init(thisServer.bootopt & BOOT_TTY); + /* we need this for now, when we're modular this + should be removed -- hikari */ + ircd_crypt_init(); - set_nomem_handler(outofmemory); - init_resolver(); + motd_init(); - open_log(*argv); + if (!init_conf()) { + log_write(LS_SYSTEM, L_CRIT, 0, "Failed to read configuration file %s", + configfile); + return 7; + } - if (!conf_init()) { - Debug((DEBUG_FATAL, "Failed in reading configuration file %s", configfile)); - printf("Couldn't open configuration file %s\n", configfile); - exit(2); + 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 (!init_server_identity()) { - Debug((DEBUG_FATAL, "Failed to initialize server identity")); + + debug_init(thisServer.bootopt & BOOT_TTY); + if (check_pid()) { + Debug((DEBUG_FATAL, "Failed to acquire PID file lock after fork")); exit(2); } + + init_server_identity(); + uping_init(); - read_tlines(); - rmotd = read_motd(RPATH); - motd = read_motd(MPATH); + + 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); - me.from = &me; + 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; - me.handler = SERVER_HANDLER; + + 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 = CurrentTime; + cli_lasttime(&me) = cli_since(&me) = cli_firsttime(&me) = CurrentTime; + hAddClient(&me); + SetIPv6(&me); - check_class(); write_pidfile(); - init_counters(); Debug((DEBUG_NOTICE, "Server ready...")); - ircd_log(L_NOTICE, "Server Ready"); - thisServer.running = 1; + log_write(LS_SYSTEM, L_NOTICE, 0, "Server Ready"); event_loop(); + return 0; }