/************************************************************************ * IRC - Internet Relay Chat, ircd/ircd.c * Copyright (C) 1990 Jarkko Oikarinen and * University of Oulu, Computing Center * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * 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. */ #ifndef lint static char sccsid[] = "@(#)ircd.c 2.48 3/9/94 (C) 1988 University of Oulu, \ Computing Center and Jarkko Oikarinen"; #endif #include "struct.h" #include "common.h" #include "sys.h" #include "numeric.h" #include "userload.h" #include #include #include #include #include #include #include #include #include #include "h.h" aClient me; /* That's me */ aClient *client = &me; /* Pointer to beginning of Client list */ void server_reboot(); void restart PROTO((char *)); static void open_debugfile(), setup_signals(); char **myargv; int portnum = -1; /* Server port number, listening this */ char *configfile = CONFIGFILE; /* Server configuration file */ int debuglevel = -1; /* Server debug level */ int bootopt = 0; /* Server boot option flags */ char *debugmode = ""; /* -"- -"- -"- */ void *sbrk0; /* initial sbrk(0) */ int dorehash = 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 */ extern char *last_dead_comment; #ifdef PROFIL extern etext(); VOIDSIG s_monitor() { static int mon = 0; #ifdef POSIX_SIGNALS struct sigaction act; #endif (void)moncontrol(mon); mon = 1 - mon; #ifdef POSIX_SIGNALS act.sa_handler = s_rehash; act.sa_flags = 0; (void)sigemptyset(&act.sa_mask); (void)sigaddset(&act.sa_mask, SIGUSR1); (void)sigaction(SIGUSR1, &act, NULL); #else (void)signal(SIGUSR1, s_monitor); #endif } #endif VOIDSIG s_die() { #ifdef USE_SYSLOG (void)syslog(LOG_CRIT, "Server Killed By SIGTERM"); #endif flush_connections(me.fd); exit(-1); } static VOIDSIG s_rehash() { #ifdef POSIX_SIGNALS struct sigaction act; #endif dorehash = 1; #ifdef POSIX_SIGNALS act.sa_handler = s_rehash; act.sa_flags = 0; (void)sigemptyset(&act.sa_mask); (void)sigaddset(&act.sa_mask, SIGHUP); (void)sigaction(SIGHUP, &act, NULL); #else (void)signal(SIGHUP, s_rehash); /* sysV -argv */ #endif } void restart(mesg) char *mesg; { #ifdef USE_SYSLOG (void)syslog(LOG_WARNING, "Restarting Server because: %s",mesg); #endif server_reboot(); } VOIDSIG s_restart() { static int restarting = 0; #ifdef USE_SYSLOG (void)syslog(LOG_WARNING, "Server Restarting on SIGINT"); #endif if (restarting == 0) { /* Send (or attempt to) a dying scream to oper if present */ restarting = 1; server_reboot(); } } void server_reboot() { Reg1 int i; sendto_ops("Aieeeee!!! Restarting server..."); 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 (void)closelog(); #endif for (i = 3; i < MAXCONNECTIONS; i++) (void)close(i); if (!(bootopt & (BOOT_TTY|BOOT_DEBUG))) (void)close(2); (void)close(1); if ((bootopt & BOOT_CONSOLE) || isatty(0)) (void)close(0); if (!(bootopt & (BOOT_INETD|BOOT_OPER))) (void)execv(MYNAME, myargv); #ifdef USE_SYSLOG /* Have to reopen since it has been closed above */ openlog(myargv[0], LOG_PID|LOG_NDELAY, LOG_FACILITY); syslog(LOG_CRIT, "execv(%s,%s) failed: %m\n", MYNAME, myargv[0]); closelog(); #endif Debug((DEBUG_FATAL,"Couldn't restart server: %s", strerror(errno))); exit(-1); } /* ** 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...) */ static time_t try_connections() { Reg1 aConfItem *aconf; Reg2 aClient *cptr; aConfItem **pconf; int connecting, confrq; time_t next = 0; aClass *cltmp; aConfItem *cconf, *con_conf; 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 = Class(aconf); /* ** 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 >;) ] */ if ((aconf->hold > now)) { if ((next > aconf->hold) || (next == 0)) next = aconf->hold; 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? */ cptr = find_name(aconf->name, (aClient *)NULL); if (!cptr && (Links(cltmp) < MaxLinks(cltmp)) && (!connecting || (Class(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) && (matches(cconf->host, aconf->name) == 0)) if (crule_eval (cconf->passwd)) break; if (!cconf) { con_class = Class(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? */ { for (pconf = &conf; (aconf = *pconf); pconf = &(aconf->next)) /* put the current one at the end and * make sure we try all connections */ 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); } Debug((DEBUG_NOTICE,"Next connection check : %s", myctime(next))); return (next); } static time_t check_pings() { Reg1 aClient *cptr; int ping = 0, i, rflag = 0; time_t oldest = 0, timeout; for (i = 0; i <= highest_fd; i++) { if (!(cptr = local[i]) || IsMe(cptr) || IsLog(cptr) || IsPing(cptr)) continue; /* ** Note: No need to notify opers here. It's ** already done when "FLAGS_DEADSOCKET" is set. */ if (IsDead(cptr)) { (void)exit_client(cptr, cptr, &me, last_dead_comment); continue; } #ifdef R_LINES_OFTEN rflag = IsPerson(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, 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 */ 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 (rflag || ((now - cptr->lasttime) >= (2 * ping) && (cptr->flags & FLAGS_PINGSENT)) || (!IsRegistered(cptr) && !IsHandshake(cptr) && (now - cptr->firsttime) >= ping)) { if (!IsRegistered(cptr) && (DoingDNS(cptr) || DoingAuth(cptr))) { if (cptr->authfd >= 0) { (void)close(cptr->authfd); cptr->authfd = -1; cptr->count = 0; *cptr->buffer = '\0'; } Debug((DEBUG_NOTICE, "DNS/AUTH timeout %s", get_client_name(cptr,TRUE))); del_queries((char *)cptr); ClearAuth(cptr); ClearDNS(cptr); SetAccess(cptr); cptr->firsttime = now; cptr->lasttime = now; continue; } if (IsServer(cptr) || IsConnecting(cptr) || IsHandshake(cptr)) { sendto_ops("No response from %s, closing link", get_client_name(cptr, FALSE)); (void)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 (IsPerson(cptr) && rflag) { sendto_ops("Restricting %s, closing link.", get_client_name(cptr,FALSE)); (void)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); } (void)exit_client_msg(cptr, cptr, &me, "Ping timeout for %s", get_client_name(cptr,FALSE)); } continue; } else if (IsRegistered(cptr) && (cptr->flags & FLAGS_PINGSENT) == 0) { /* * 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 */ cptr->lasttime = now - ping; sendto_one(cptr, "PING :%s", me.name); } ping_timeout: timeout = cptr->lasttime + ping; while (timeout <= now) timeout += ping; if (timeout < oldest || !oldest) oldest = timeout; } if (!oldest || oldest < now) oldest = now + PINGFREQUENCY; Debug((DEBUG_NOTICE,"Next check_ping() call at: %s, %d %d %d", myctime(oldest), ping, oldest, now)); return (oldest); } /* ** 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 ); (void)printf("Server not started\n\n"); return (-1); } int main(argc, argv) int argc; char *argv[]; { int portarg = 0; uid_t uid, euid; time_t delay = 0; #ifdef FORCE_CORE struct rlimit corelim; #endif sbrk0 = sbrk(0); uid = getuid(); euid = geteuid(); now = time(NULL); #ifdef PROFIL (void)monstartup(0, etext); (void)moncontrol(1); (void)signal(SIGUSR1, s_monitor); #endif #ifdef CHROOTDIR if (chdir(dpath)) { perror("chdir"); exit(-1); } res_init(); if (chroot(DPATH)) { (void)fprintf(stderr,"ERROR: Cannot chdir/chroot\n"); exit(5); } #endif /*CHROOTDIR*/ myargv = argv; (void)umask(077); /* better safe than sorry --SRB */ bzero((char *)&me, sizeof(me)); setup_signals(); initload(); #ifdef FORCE_CORE corelim.rlim_cur = corelim.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_CORE, &corelim)) printf("unlimit core size failed; errno = %d\n", 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' : (void)setuid((uid_t)uid); dpath = p; break; case 'o': /* Per user local daemon... */ (void)setuid((uid_t)uid); bootopt |= BOOT_OPER; break; #ifdef CMDLINE_CONFIG case 'f': (void)setuid((uid_t)uid); configfile = p; break; #endif case 'h': strncpyzt(me.name, p, sizeof(me.name)); break; case 'i': bootopt |= BOOT_INETD|BOOT_AUTODIE; break; case 'p': if ((portarg = atoi(p)) > 0 ) portnum = portarg; break; case 't': (void)setuid((uid_t)uid); bootopt |= BOOT_TTY; break; case 'v': (void)printf("ircd %s\n", version); exit(0); case 'x': #ifdef DEBUGMODE (void)setuid((uid_t)uid); debuglevel = atoi(p); debugmode = *p ? p : "0"; bootopt |= BOOT_DEBUG; break; #else (void)fprintf(stderr, "%s: DEBUGMODE must be defined for -x y\n", myargv[0]); exit(0); #endif default: bad_command(); break; } } #ifndef CHROOT if (chdir(dpath)) { perror("chdir"); exit(-1); } #endif #ifndef IRC_UID if ((uid != euid) && !euid) { (void)fprintf(stderr, "ERROR: do not run ircd setuid root. Make it setuid a\ normal user.\n"); exit(-1); } #endif #if !defined(CHROOTDIR) || (defined(IRC_UID) && defined(IRC_GID)) # ifndef AIX (void)setuid((uid_t)uid); (void)setuid((uid_t)euid); # endif if ((int)getuid() == 0) { # if defined(IRC_UID) && defined(IRC_GID) /* run as a specified user */ (void)fprintf(stderr,"WARNING: running ircd with uid = %d\n", IRC_UID); (void)fprintf(stderr," changing to gid %d.\n",IRC_GID); (void)setuid(IRC_UID); (void)setgid(IRC_GID); #else /* check for setuid root as usual */ (void)fprintf(stderr, "ERROR: do not run ircd setuid root. Make it setuid a\ normal user.\n"); exit(-1); # endif } #endif /*CHROOTDIR/UID/GID*/ /* didn't set debuglevel */ /* but asked for debugging output to tty */ if ((debuglevel < 0) && (bootopt & BOOT_TTY)) { (void)fprintf(stderr, "you specified -t without -x. use -x \n"); exit(-1); } if (argc > 0) return bad_command(); /* This should exit out */ clear_client_hash_table(); clear_channel_hash_table(); initlists(); initclass(); initwhowas(); initstats(); open_debugfile(); if (portnum < 0) portnum = PORTNUM; me.port = portnum; (void)init_sys(); me.flags = FLAGS_LISTEN; if (bootopt & BOOT_INETD) { me.fd = 0; local[0] = &me; me.flags = FLAGS_LISTEN; } 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)); (void)printf("Couldn't open configuration file %s\n", configfile); exit(-1); } 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 = %d", portnum)); if (inetport(&me, star, portnum)) exit(1); } else if (inetport(&me, "*", 0)) exit(1); (void)setup_ping(); (void)get_my_name(&me, me.sockhost, sizeof(me.sockhost)-1); if (me.name[0] == '\0') strncpyzt(me.name, "undernet.org", sizeof(me.name)); now = time(NULL); me.hopcount = 0; me.authfd = -1; me.confs = NULL; me.next = NULL; me.user = NULL; me.from = &me; SetMe(&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.serv->client = NULL; me.lasttime = me.since = me.firsttime = now; (void)add_to_client_hash_table(me.name, &me); check_class(); if (bootopt & BOOT_OPER) { aClient *tmp = add_connection(&me, 0); if (!tmp) exit(1); SetMaster(tmp); } else write_pidfile(); Debug((DEBUG_NOTICE,"Server ready...")); #ifdef USE_SYSLOG syslog(LOG_NOTICE, "Server Ready"); #endif 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); (void)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(); if (dorehash) { (void)rehash(&me, &me, 1); dorehash = 0; } /* ** Flush output buffers on all connections now if they ** have data in them (or at least try to flush) ** -avalon */ /* What is this doing here ??? Pure cpu waste... flush_connections(me.fd); writes are already done in read_message(), which uses select(). flush_connections doesn't even !!! --Run */ } } /* * 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. */ static void open_debugfile() { #ifdef DEBUGMODE int fd; aClient *cptr; if (debuglevel >= 0) { cptr = make_client(NULL); cptr->fd = 2; SetLog(cptr); cptr->port = debuglevel; cptr->flags = 0; cptr->acpt = cptr; local[2] = cptr; (void)strcpy(cptr->sockhost, me.sockhost); (void)printf("isatty = %d ttyname = 0x%p\n", isatty(2), ttyname(2)); if (!(bootopt & BOOT_TTY)) /* leave debugging output on fd 2 */ { (void)truncate(LOGFILE, 0); if ((fd = open(LOGFILE, O_WRONLY | O_CREAT, 0600)) < 0) if ((fd = open("/dev/null", O_WRONLY)) < 0) exit(-1); if (fd != 2) { (void)dup2(fd, 2); (void)close(fd); } strncpyzt(cptr->name, LOGFILE, sizeof(cptr->name)); } else if (isatty(2) && ttyname(2)) strncpyzt(cptr->name, ttyname(2), sizeof(cptr->name)); else (void)strcpy(cptr->name, "FD2-Pipe"); Debug((DEBUG_FATAL, "Debug: File <%s> Level: %d at %s", cptr->name, cptr->port, myctime(now))); } else local[2] = NULL; #endif return; } static void setup_signals() { #ifdef POSIX_SIGNALS struct sigaction act; act.sa_handler = SIG_IGN; act.sa_flags = 0; (void)sigemptyset(&act.sa_mask); (void)sigaddset(&act.sa_mask, SIGPIPE); (void)sigaddset(&act.sa_mask, SIGALRM); # ifdef SIGWINCH (void)sigaddset(&act.sa_mask, SIGWINCH); (void)sigaction(SIGWINCH, &act, NULL); # endif (void)sigaction(SIGPIPE, &act, NULL); act.sa_handler = dummy; (void)sigaction(SIGALRM, &act, NULL); act.sa_handler = s_rehash; (void)sigemptyset(&act.sa_mask); (void)sigaddset(&act.sa_mask, SIGHUP); (void)sigaction(SIGHUP, &act, NULL); act.sa_handler = s_restart; (void)sigaddset(&act.sa_mask, SIGINT); (void)sigaction(SIGINT, &act, NULL); act.sa_handler = s_die; (void)sigaddset(&act.sa_mask, SIGTERM); (void)sigaction(SIGTERM, &act, NULL); #else # ifndef HAVE_RELIABLE_SIGNALS (void)signal(SIGPIPE, dummy); # ifdef SIGWINCH (void)signal(SIGWINCH, dummy); # endif # else # ifdef SIGWINCH (void)signal(SIGWINCH, SIG_IGN); # endif (void)signal(SIGPIPE, SIG_IGN); # endif (void)signal(SIGALRM, dummy); (void)signal(SIGHUP, s_rehash); (void)signal(SIGTERM, s_die); (void)signal(SIGINT, s_restart); #endif #ifdef RESTARTING_SYSTEMCALLS /* ** 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. */ (void)siginterrupt(SIGALRM, 1); #endif }