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