Author: Bleep <tomh@inxpress.net>
[ircu2.10.12-pk.git] / ircd / ircd.c
1 /*
2  * IRC - Internet Relay Chat, ircd/ircd.c
3  * Copyright (C) 1990 Jarkko Oikarinen and
4  *                    University of Oulu, Computing Center
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 1, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  *
20  * $Id$
21  */
22 #include "ircd.h"
23 #include "class.h"
24 #include "client.h"
25 #include "crule.h"
26 #include "hash.h"
27 #include "ircd_alloc.h" /* set_nomem_handler */
28 #include "ircd_log.h"
29 #include "ircd_signal.h"
30 #include "ircd_string.h"
31 #include "jupe.h"
32 #include "list.h"
33 #include "listener.h"
34 #include "match.h"
35 #include "numeric.h"
36 #include "numnicks.h"
37 #include "parse.h"
38 #include "res.h"
39 #include "s_auth.h"
40 #include "s_bsd.h"
41 #include "s_conf.h"
42 #include "s_debug.h"
43 #include "s_misc.h"
44 #include "send.h"
45 #include "struct.h"
46 #include "sys.h"
47 #include "userload.h"
48 #include "version.h"
49 #include "whowas.h"
50 #include "msg.h"
51
52 #include <assert.h>
53 #include <errno.h>
54 #include <fcntl.h>
55 #include <pwd.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <sys/socket.h>
60 #include <sys/stat.h>
61 #include <unistd.h>
62 #include <netdb.h>
63
64 extern void init_counters(void);
65
66 struct Client  me;                      /* That's me */
67 struct Client* GlobalClientList = &me;  /* Pointer to beginning of Client list */
68 time_t         TSoffset = 0;      /* Offset of timestamps to system clock */
69 int            GlobalRehashFlag = 0;    /* do a rehash if set */
70 int            GlobalRestartFlag = 0;   /* do a restart if set */
71 time_t         CurrentTime;       /* Updated every time we leave select() */
72
73 char **myargv;
74 char *configfile = CPATH;       /* Server configuration file */
75 int debuglevel = -1;            /* Server debug level */
76 unsigned int bootopt = 0;       /* Server boot option flags */
77 char *debugmode = "";           /*  -"-    -"-   -"-  */
78 static char *dpath = DPATH;
79
80 time_t nextconnect = 1;         /* time for next try_connections call */
81 time_t nextping = 1;            /* same as above for check_pings() */
82 time_t nextdnscheck = 0;        /* next time to poll dns to force timeouts */
83 time_t nextexpire = 1;          /* next expire run on the dns cache */
84
85 #ifdef PROFIL
86 extern etext(void);
87 #endif
88
89 static void server_reboot(const char* message)
90 {
91   int i;
92
93   sendto_ops("Restarting server: %s", message);
94   Debug((DEBUG_NOTICE, "Restarting server..."));
95   flush_connections(0);
96   /*
97    * fd 0 must be 'preserved' if either the -d or -i options have
98    * been passed to us before restarting.
99    */
100   close_log();
101
102   for (i = 3; i < MAXCONNECTIONS; i++)
103     close(i);
104   if (!(bootopt & (BOOT_TTY | BOOT_DEBUG)))
105     close(2);
106   close(1);
107   close(0);
108
109   execv(SPATH, myargv);
110
111   /* Have to reopen since it has been closed above */
112   open_log(myargv[0]);
113   ircd_log(L_CRIT, "execv(%s,%s) failed: %m\n", SPATH, myargv[0]);
114
115   Debug((DEBUG_FATAL, "Couldn't restart server \"%s\": %s",
116          SPATH, (strerror(errno)) ? strerror(errno) : ""));
117   exit(-1);
118 }
119
120 void server_die(const char* message)
121 {
122   ircd_log(L_CRIT, "Server terminating: %s", message);
123   sendto_ops("Server terminating: %s", message);
124   flush_connections(0);
125   exit(-1);
126 }
127
128 void server_restart(const char* message)
129 {
130   static int restarting = 0;
131
132   ircd_log(L_WARNING, "Restarting Server: %s", message);
133   if (restarting == 0) {
134     restarting = 1;
135     server_reboot(message);
136   }
137 }
138
139 static void outofmemory(void)
140 {
141   Debug((DEBUG_FATAL, "Out of memory: restarting server..."));
142   server_restart("Out of Memory");
143
144
145 static void write_pidfile(void)
146 {
147 #ifdef PPATH
148   int fd;
149   char buff[20];
150   if ((fd = open(PPATH, O_CREAT | O_WRONLY, 0600)) >= 0) {
151     memset(buff, 0, sizeof(buff));
152     sprintf(buff, "%5d\n", (int)getpid());
153     if (write(fd, buff, strlen(buff)) == -1)
154       Debug((DEBUG_NOTICE, "Error writing to pid file %s", PPATH));
155     close(fd);
156     return;
157   }
158   Debug((DEBUG_NOTICE, "Error opening pid file \"%s\": %s",
159         PPATH, (strerror(errno)) ? strerror(errno) : ""));
160 #endif
161 }
162
163 /*
164  * try_connections
165  *
166  * Scan through configuration and try new connections.
167  *
168  * Returns the calendar time when the next call to this
169  * function should be made latest. (No harm done if this
170  * is called earlier or later...)
171  */
172 static time_t try_connections(void)
173 {
174   struct ConfItem *aconf;
175   struct Client *cptr;
176   struct ConfItem **pconf;
177   int connecting, confrq;
178   time_t next = 0;
179   struct ConfClass *cltmp;
180   struct ConfItem *cconf, *con_conf = NULL;
181   struct Jupe *ajupe;
182   unsigned int con_class = 0;
183
184   connecting = FALSE;
185   Debug((DEBUG_NOTICE, "Connection check at   : %s", myctime(CurrentTime)));
186   for (aconf = GlobalConfList; aconf; aconf = aconf->next)
187   {
188     /* Also when already connecting! (update holdtimes) --SRB */
189     if (!(aconf->status & CONF_SERVER) || aconf->port == 0)
190       continue;
191
192     /* Also skip juped servers */
193     if ((ajupe = jupe_find(aconf->name)) && JupeIsActive(ajupe))
194       continue;
195
196     cltmp = aconf->confClass;
197     /*
198      * Skip this entry if the use of it is still on hold until
199      * future. Otherwise handle this entry (and set it on hold
200      * until next time). Will reset only hold times, if already
201      * made one successfull connection... [this algorithm is
202      * a bit fuzzy... -- msa >;) ]
203      */
204
205     if ((aconf->hold > CurrentTime))
206     {
207       if ((next > aconf->hold) || (next == 0))
208         next = aconf->hold;
209       continue;
210     }
211
212     confrq = get_con_freq(cltmp);
213     aconf->hold = CurrentTime + confrq;
214     /*
215      * Found a CONNECT config with port specified, scan clients
216      * and see if this server is already connected?
217      */
218     cptr = FindServer(aconf->name);
219
220     if (!cptr && (Links(cltmp) < MaxLinks(cltmp)) &&
221         (!connecting || (ConClass(cltmp) > con_class)))
222     {
223       /* Check connect rules to see if we're allowed to try */
224       for (cconf = GlobalConfList; cconf; cconf = cconf->next)
225         if ((cconf->status & CONF_CRULE) &&
226             (match(cconf->host, aconf->name) == 0))
227           if (crule_eval(cconf->passwd))
228             break;
229       if (!cconf)
230       {
231         con_class = ConClass(cltmp);
232         con_conf = aconf;
233         /* We connect only one at time... */
234         connecting = TRUE;
235       }
236     }
237     if ((next > aconf->hold) || (next == 0))
238       next = aconf->hold;
239   }
240   if (connecting)
241   {
242     if (con_conf->next)         /* are we already last? */
243     {
244       /* Put the current one at the end and make sure we try all connections */
245       for (pconf = &GlobalConfList; (aconf = *pconf); pconf = &(aconf->next))
246         if (aconf == con_conf)
247           *pconf = aconf->next;
248       (*pconf = con_conf)->next = 0;
249     }
250     if (connect_server(con_conf, 0, 0))
251       sendto_ops("Connection to %s activated.", con_conf->name);
252   }
253   Debug((DEBUG_NOTICE, "Next connection check : %s", myctime(next)));
254   return (next);
255 }
256
257 static time_t check_pings(void)
258 {
259   struct Client *cptr;
260   int ping = 0;
261   int i;
262   time_t oldest = CurrentTime + PINGFREQUENCY;
263   time_t timeout;
264
265   for (i = 0; i <= HighestFd; i++) {
266     if (!(cptr = LocalClientArray[i]))
267       continue;
268     /*
269      * me is never in the local client array
270      */
271     assert(cptr != &me);
272     /*
273      * Note: No need to notify opers here.
274      * It's already done when "FLAGS_DEADSOCKET" is set.
275      */
276     if (IsDead(cptr)) {
277       exit_client(cptr, cptr, &me, LastDeadComment(cptr));
278       continue;
279     }
280
281     ping = IsRegistered(cptr) ? get_client_ping(cptr) : CONNECTTIMEOUT;
282     Debug((DEBUG_DEBUG, "c(%s)=%d p %d a %d",
283           cptr->name, cptr->status, ping, 
284           (int)(CurrentTime - cptr->lasttime)));
285     /*
286      * Ok, so goto's are ugly and can be avoided here but this code
287      * is already indented enough so I think its justified. -avalon
288      */
289     if (IsRegistered(cptr) && (ping >= CurrentTime - cptr->lasttime))
290       goto ping_timeout;
291     /*
292      * If the server hasnt talked to us in 2 * ping seconds
293      * and it has a ping time, then close its connection.
294      * If the client is a user and a KILL line was found
295      * to be active, close this connection too.
296      */
297     if (((CurrentTime - cptr->lasttime) >= (2 * ping) && (cptr->flags & FLAGS_PINGSENT)) ||
298         (!IsRegistered(cptr) && !IsHandshake(cptr) && (CurrentTime - cptr->firsttime) >= ping))
299     {
300       if (IsServer(cptr) || IsConnecting(cptr) || IsHandshake(cptr))
301       {
302         sendto_ops("No response from %s, closing link", cptr->name);
303         exit_client(cptr, cptr, &me, "Ping timeout");
304         continue;
305       }
306       else {
307         if (!IsRegistered(cptr) && *cptr->name && *cptr->user->username) {
308           sendto_one(cptr,
309               ":%s %d %s :Your client may not be compatible with this server.",
310               me.name, ERR_BADPING, cptr->name);
311           sendto_one(cptr,
312               ":%s %d %s :Compatible clients are available at "
313               "ftp://ftp.undernet.org/pub/irc/clients",
314               me.name, ERR_BADPING, cptr->name);
315         }
316         exit_client_msg(cptr, cptr, &me, "Ping timeout for %s",
317                         get_client_name(cptr, HIDE_IP));
318       }
319       continue;
320     }
321     else if (IsRegistered(cptr) && 0 == (cptr->flags & FLAGS_PINGSENT)) {
322       /*
323        * If we havent PINGed the connection and we havent
324        * heard from it in a while, PING it to make sure
325        * it is still alive.
326        */
327       cptr->flags |= FLAGS_PINGSENT;
328       /*
329        * not nice but does the job
330        */
331       cptr->lasttime = CurrentTime - ping;
332       if (IsUser(cptr))
333         sendto_one(cptr, "PING :%s", me.name);
334       else
335         sendto_one(cptr, "%s " TOK_PING " :%s", NumServ(&me), me.name);
336     }
337 ping_timeout:
338     timeout = cptr->lasttime + ping;
339     while (timeout <= CurrentTime)
340       timeout += ping;
341     if (timeout < oldest)
342       oldest = timeout;
343   }
344   if (oldest < CurrentTime)
345     oldest = CurrentTime + PINGFREQUENCY;
346   Debug((DEBUG_NOTICE,
347         "Next check_ping() call at: %s, %d " TIME_T_FMT " " TIME_T_FMT,
348         myctime(oldest), ping, oldest, CurrentTime));
349
350   return (oldest);
351 }
352
353 /*
354  * bad_command
355  *
356  * This is called when the commandline is not acceptable.
357  * Give error message and exit without starting anything.
358  */
359 static int bad_command(void)
360 {
361   printf("Usage: ircd %s[-h servername] [-x loglevel] [-t]\n",
362 #ifdef CMDLINE_CONFIG
363       "[-f config] "
364 #else
365       ""
366 #endif
367       );
368   printf("Server not started\n\n");
369   return (-1);
370 }
371
372 int main(int argc, char *argv[])
373 {
374   uid_t uid;
375   uid_t euid;
376   time_t delay = 0;
377 #if defined(HAVE_SETRLIMIT) && defined(RLIMIT_CORE)
378   struct rlimit corelim;
379 #endif
380
381   CurrentTime = time(NULL);
382
383   /*
384    * sanity check
385    */
386   if (MAXCONNECTIONS < 64 || MAXCONNECTIONS > 256000) {
387     fprintf(stderr, "%s: MAXCONNECTIONS insane: %d\n", *argv, MAXCONNECTIONS);
388     return 2;
389   }
390     
391   uid = getuid();
392   euid = geteuid();
393 #ifdef PROFIL
394   monstartup(0, etext);
395   moncontrol(1);
396   signal(SIGUSR1, s_monitor);
397 #endif
398
399 #ifdef CHROOTDIR
400   if (chdir(DPATH))
401   {
402     fprintf(stderr, "Fail: Cannot chdir(%s): %s\n", DPATH, (strerror(errno)) ? strerror(errno) : "");
403     exit(2);
404   }
405   if (chroot(DPATH))
406   {
407     fprintf(stderr, "Fail: Cannot chroot(%s): %s\n", DPATH, (strerror(errno)) ? strerror(errno) : "");
408     exit(5);
409   }
410   dpath = "/";
411 #endif /*CHROOTDIR */
412
413   myargv = argv;
414   umask(077);                   /* better safe than sorry --SRB */
415   memset(&me, 0, sizeof(me));
416   me.fd = -1;
417
418 #if 0
419 #ifdef VIRTUAL_HOST
420   memset(&vserv, 0, sizeof(vserv));
421 #endif
422 #endif
423
424   setup_signals();
425   initload();
426
427 #if defined(HAVE_SETRLIMIT) && defined(RLIMIT_CORE)
428   if (getrlimit(RLIMIT_CORE, &corelim))
429   {
430     fprintf(stderr, "Read of rlimit core size failed: %s\n", (strerror(errno) ? strerror(errno) : "");
431     corelim.rlim_max = RLIM_INFINITY;   /* Try to recover */
432   }
433   corelim.rlim_cur = corelim.rlim_max;
434   if (setrlimit(RLIMIT_CORE, &corelim))
435     fprintf(stderr, "Setting rlimit core size failed: %s\n", (strerror(errno) ? strerror(errno) : "");
436 #endif
437
438   /*
439    * All command line parameters have the syntax "-fstring"
440    * or "-f string" (e.g. the space is optional). String may
441    * be empty. Flag characters cannot be concatenated (like
442    * "-fxyz"), it would conflict with the form "-fstring".
443    */
444   while (--argc > 0 && (*++argv)[0] == '-')
445   {
446     char *p = argv[0] + 1;
447     int flag = *p++;
448
449     if (flag == '\0' || *p == '\0')
450     {
451       if (argc > 1 && argv[1][0] != '-')
452       {
453         p = *++argv;
454         argc -= 1;
455       }
456       else
457         p = "";
458     }
459
460     switch (flag)
461     {
462       case 'q':
463         bootopt |= BOOT_QUICK;
464         break;
465       case 'd':
466         if (euid != uid)
467           setuid((uid_t) uid);
468         dpath = p;
469         break;
470 #ifdef CMDLINE_CONFIG
471       case 'f':
472         if (euid != uid)
473           setuid((uid_t) uid);
474         configfile = p;
475         break;
476 #endif
477       case 'h':
478         ircd_strncpy(me.name, p, HOSTLEN);
479         break;
480       case 't':
481         if (euid != uid)
482           setuid((uid_t) uid);
483         bootopt |= BOOT_TTY;
484         break;
485       case 'v':
486         printf("ircd %s\n", version);
487         exit(0);
488 #if 0
489 #ifdef VIRTUAL_HOST
490       case 'w':
491       {
492         struct hostent *hep;
493         if (!(hep = gethostbyname(p)))
494         {
495           fprintf(stderr, "%s: Error creating virtual host \"%s\": %d",
496               argv[0], p, h_errno);
497           return 2;
498         }
499         if (hep->h_addrtype == AF_INET && hep->h_addr_list[0] &&
500             !hep->h_addr_list[1])
501         {
502           memcpy(&vserv.sin_addr, hep->h_addr_list[0], sizeof(struct in_addr));
503           vserv.sin_family = AF_INET;
504         }
505         else
506         {
507           fprintf(stderr, "%s: Error creating virtual host \"%s\": "
508               "Use -w <IP-number of interface>\n", argv[0], p);
509           return 2;
510         }
511         break;
512       }
513 #endif
514 #endif
515       case 'x':
516 #ifdef  DEBUGMODE
517         if (euid != uid)
518           setuid((uid_t) uid);
519         debuglevel = atoi(p);
520         debugmode = *p ? p : "0";
521         bootopt |= BOOT_DEBUG;
522         break;
523 #else
524         fprintf(stderr, "%s: DEBUGMODE must be defined for -x y\n", myargv[0]);
525         exit(0);
526 #endif
527       default:
528         bad_command();
529         break;
530     }
531   }
532
533   if (chdir(dpath))
534   {
535     fprintf(stderr, "Fail: Cannot chdir(%s): %s\n", dpath, (strerror(errno)) ? strerror(errno) : "");
536     exit(2);
537   }
538
539 #ifndef IRC_UID
540   if ((uid != euid) && !euid)
541   {
542     fprintf(stderr,
543         "ERROR: do not run ircd setuid root. Make it setuid a normal user.\n");
544     exit(2);
545   }
546 #endif
547
548 #if !defined(CHROOTDIR) || (defined(IRC_UID) && defined(IRC_GID))
549   if (euid != uid)
550   {
551     setuid(uid);
552     setuid(euid);
553   }
554
555   if (0 == getuid())
556   {
557 #if defined(IRC_UID) && defined(IRC_GID)
558
559     /* run as a specified user */
560     fprintf(stderr, "WARNING: running ircd with uid = %d\n", IRC_UID);
561     fprintf(stderr, "         changing to gid %d.\n", IRC_GID);
562     setuid(IRC_UID);
563     setgid(IRC_GID);
564 #else
565     /* check for setuid root as usual */
566     fprintf(stderr,
567         "ERROR: do not run ircd setuid root. Make it setuid a normal user.\n");
568     exit(2);
569 #endif
570   }
571 #endif /*CHROOTDIR/UID/GID */
572
573   if (argc > 0)
574     return bad_command();       /* This should exit out */
575
576   /* Sanity checks */
577   {
578     char c;
579     char *path;
580
581     c = 'S';
582     path = SPATH;
583     if (access(path, X_OK) == 0) {
584       c = 'C';
585       path = CPATH;
586       if (access(path, R_OK) == 0) {
587         c = 'M';
588         path = MPATH;
589         if (access(path, R_OK) == 0) {
590           c = 'R';
591           path = RPATH;
592           if (access(path, R_OK) == 0) {
593 #ifndef DEBUG
594             c = 0;
595 #else
596             c = 'L';
597             path = LPATH;
598             if (access(path, W_OK) == 0)
599               c = 0;
600 #endif
601           }
602         }
603       }
604     }
605     if (c)
606     {
607       fprintf(stderr, "Check on %cPATH (%s) failed: %s\n", 
608               c, path, (strerror(errno)) ? strerror(errno) : "");
609       fprintf(stderr,
610           "Please create file and/or rerun `make config' and recompile to correct this.\n");
611 #ifdef CHROOTDIR
612       fprintf(stderr,
613           "Keep in mind that all paths are relative to CHROOTDIR.\n");
614 #endif
615       exit(2);
616     }
617   }
618
619   init_list();
620   hash_init();
621   initclass();
622   initwhowas();
623   initmsgtree();
624   initstats();
625   open_debugfile();
626   init_sys();
627   set_nomem_handler(outofmemory);
628
629   me.fd = -1;
630
631   open_log(myargv[0]);
632
633   if (initconf(bootopt) == -1) {
634     Debug((DEBUG_FATAL, "Failed in reading configuration file %s", configfile));
635     printf("Couldn't open configuration file %s\n", configfile);
636     exit(2);
637   }
638   if (!init_server_identity()) {
639     Debug((DEBUG_FATAL, "Failed to initialize server identity"));
640     exit(2);
641   }
642   read_tlines();
643   rmotd = read_motd(RPATH);
644   motd = read_motd(MPATH);
645   CurrentTime = time(NULL);
646   me.from = &me;
647   SetMe(&me);
648   make_server(&me);
649   /*
650    * Abuse own link timestamp as start timestamp:
651    */
652   me.serv->timestamp = TStime();
653   me.serv->prot = atoi(MAJOR_PROTOCOL);
654   me.serv->up = &me;
655   me.serv->down = NULL;
656   me.handler = SERVER_HANDLER;
657
658   SetYXXCapacity(&me, MAXCLIENTS);
659
660   me.lasttime = me.since = me.firsttime = CurrentTime;
661   hAddClient(&me);
662
663   check_class();
664   write_pidfile();
665
666   init_counters();
667
668   Debug((DEBUG_NOTICE, "Server ready..."));
669   ircd_log(L_NOTICE, "Server Ready");
670
671   for (;;)
672   {
673     /*
674      * We only want to connect if a connection is due,
675      * not every time through.   Note, if there are no
676      * active C lines, this call to Tryconnections is
677      * made once only; it will return 0. - avalon
678      */
679     if (nextconnect && CurrentTime >= nextconnect)
680       nextconnect = try_connections();
681     /*
682      * DNS checks. One to timeout queries, one for cache expiries.
683      */
684     nextdnscheck = timeout_resolver(CurrentTime);
685     /*
686      * Take the smaller of the two 'timed' event times as
687      * the time of next event (stops us being late :) - avalon
688      * WARNING - nextconnect can return 0!
689      */
690     if (nextconnect)
691       delay = IRCD_MIN(nextping, nextconnect);
692     else
693       delay = nextping;
694     delay = IRCD_MIN(nextdnscheck, delay);
695     delay -= CurrentTime;
696     /*
697      * Adjust delay to something reasonable [ad hoc values]
698      * (one might think something more clever here... --msa)
699      * We don't really need to check that often and as long
700      * as we don't delay too long, everything should be ok.
701      * waiting too long can cause things to timeout...
702      * i.e. PINGS -> a disconnection :(
703      * - avalon
704      */
705     if (delay < 1)
706       delay = 1;
707     else
708       delay = IRCD_MIN(delay, TIMESEC);
709     read_message(delay);
710
711     Debug((DEBUG_DEBUG, "Got message(s)"));
712
713     /*
714      * ...perhaps should not do these loops every time,
715      * but only if there is some chance of something
716      * happening (but, note that conf->hold times may
717      * be changed elsewhere--so precomputed next event
718      * time might be too far away... (similarly with
719      * ping times) --msa
720      */
721     if (CurrentTime >= nextping)
722       nextping = check_pings();
723     
724     /*
725      * timeout pending queries that haven't been responded to
726      */
727     timeout_auth_queries(CurrentTime);
728
729     if (GlobalRehashFlag) {
730       rehash(&me, 1);
731       GlobalRehashFlag = 0;
732     }
733     if (GlobalRestartFlag)
734       server_restart("caught signal: SIGINT");
735   }
736 }