1 /* dircproxy
2  * Copyright (C) 2000-2003 Scott James Remnant <scott at netsplit dot com>
3  *
4  * Copyright (C) 2004-2008 Francois Harvey <contact at francoisharvey dot ca>
5  *
6  * Copyright (C) 2008-2009 Noel Shrum <noel dot w8tvi at gmail dot com>
7  *                         Francois Harvey <contact at francoisharvey dot ca>
8  *
9  *
10  * main.c
11  *  - Program main loop
12  *  - Command line handling
13  *  - Initialisation and shutdown
14  *  - Signal handling
15  *  - Debug functions
16  * --
17  * @(#) $Id: main.c,v 1.57 2004/02/13 23:39:34 bear Exp $
18  *
19  * This program is free software; you can redistribute it and/or modify
20  * it under the terms of the GNU General Public License as published by
21  * the Free Software Foundation; either version 2 of the License, or
22  * (at your option) any later version.
23  *
24  * This program is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27  * GNU General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program; if not, write to the Free Software
31  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
32  */
33 
34 #include <pwd.h>
35 #include <stdio.h>
36 #include <sys/types.h>
37 #include <sys/wait.h>
38 #include <sys/stat.h>
39 #include <string.h>
40 #include <stdlib.h>
41 #include <stdarg.h>
42 #include <unistd.h>
43 #include <signal.h>
44 #include <syslog.h>
45 #include <errno.h>
46 
47 #include <dircproxy.h>
48 #include "getopt/getopt.h"
49 #include "sprintf.h"
50 #include "cfgfile.h"
51 #include "irc_net.h"
52 #include "irc_client.h"
53 #include "irc_server.h"
54 #include "dcc_net.h"
55 #include "timers.h"
56 #include "dns.h"
57 #include "net.h"
58 
59 /* forward declarations */
60 static void _sig_term(int);
61 static void _sig_hup(int);
62 static void _sig_child(int);
63 #ifdef DEBUG_MEMORY
64 static void _sig_usr(int);
65 #endif /* DEBUG_MEMORY */
66 static int _reload_config(void);
67 static int _print_usage(void);
68 static int _print_version(void);
69 static int _print_help(void);
70 
71 /* This is so "ident" and "what" can query version etc - useful (not) */
72 const char *rcsid = "@(#) $Id: main.c,v 1.57 2004/02/13 23:39:34 bear Exp $";
73 
74 /* The name of the program */
75 static char *progname;
76 
77 /* whether we went in the background or not */
78 static int in_background = 0;
79 
80 /* set to 1 to abort the main loop */
81 static int stop_poll = 0;
82 
83 /* set to 1 to reload the configuration file */
84 static int reload_config = 0;
85 
86 /* Port we're listening on */
87 static char *listen_port;
88 
89 /* File to write our pid to */
90 static char *pid_file;
91 
92 /* The configuration file we used */
93 static char *config_file;
94 
95 /* Global variables */
96 struct globalvars g;
97 
98 /* Long options */
99 static struct option long_opts[] = {
100   { "config-file", 1, NULL, 'f' },
101   { "help", 0, NULL, 'h' },
102   { "version", 0, NULL, 'v' },
103 #ifndef DEBUG
104   { "no-daemon", 0, NULL, 'D' },
105 #else /* DEBUG */
106   { "daemon", 0, NULL, 'D' },
107 #endif /* DEBUG */
108   { "inetd", 0, NULL, 'I' },
109   { "listen-port", 1, NULL, 'P' },
110   { "pid-file", 1, NULL, 'p' },
111   { 0, 0, 0, 0 }
112 };
113 
114 /* Options */
115 #define GETOPTIONS "f:hvDIP:"
116 
117 /* We need this */
main(int argc,char * argv[])118 int main(int argc, char *argv[]) {
119   int optc, show_help, show_version, show_usage;
120   char *local_file, *cmd_listen_port, *cmd_pid_file;
121   int inetd_mode, no_daemon;
122 
123   /* Set up some globals */
124   progname = argv[0];
125   listen_port = x_strdup(DEFAULT_LISTEN_PORT);
126   pid_file = (DEFAULT_PID_FILE ? x_strdup(DEFAULT_PID_FILE) : 0);
127 
128 #ifndef DEBUG
129   no_daemon = 0;
130 #else /* DEBUG */
131   no_daemon = 1;
132 #endif /* DEBUG */
133   local_file = cmd_listen_port = cmd_pid_file = 0;
134   show_help = show_version = show_usage = inetd_mode = 0;
135   while ((optc = getopt_long(argc, argv, GETOPTIONS, long_opts, NULL)) != -1) {
136     switch (optc) {
137       case 'h':
138         show_help = 1;
139         break;
140       case 'v':
141         show_version = 1;
142         break;
143       case 'D':
144 #ifndef DEBUG
145         no_daemon = 1;
146 #else /* DEBUG */
147         no_daemon = 0;
148 #endif /* DEBUG */
149         break;
150       case 'I':
151         inetd_mode = 1;
152         break;
153       case 'P':
154         free(cmd_listen_port);
155         cmd_listen_port = x_strdup(optarg);
156         break;
157       case 'p':
158         free(cmd_pid_file);
159         cmd_pid_file = x_strdup(optarg);
160         break;
161       case 'f':
162         free(local_file);
163         local_file = x_strdup(optarg);
164         break;
165       default:
166         show_usage = 1;
167         break;
168     }
169   }
170 
171   if (show_usage || (optind < argc)) {
172     _print_usage();
173     return 1;
174   }
175 
176   if (show_version) {
177     _print_version();
178     if (!show_help)
179       return 0;
180   }
181 
182   if (show_help) {
183     _print_help();
184     return 0;
185   }
186 
187   /* If no -f was specified use the home directory */
188   if (!local_file && !inetd_mode) {
189     struct stat statinfo;
190     struct passwd *pw;
191 
192     pw = getpwuid(geteuid());
193     if (pw && pw->pw_dir) {
194       local_file = x_sprintf("%s/%s", pw->pw_dir, USER_CONFIG_FILENAME);
195       debug("Local config file: %s", local_file);
196       if (!stat(local_file, &statinfo) && (statinfo.st_mode & 0077)) {
197         fprintf(stderr, "%s: Permissions of %s must be 0700 or "
198                         "more restrictive\n", progname, local_file);
199         free(local_file);
200         return 2;
201       }
202       if (cfg_read(local_file, &listen_port, &pid_file, &g)) {
203         /* If the local one didn't exist, set to 0 so we open
204            global one */
205         free(local_file);
206         local_file = 0;
207       } else {
208         config_file = x_strdup(local_file);
209       }
210     }
211   } else if (local_file) {
212     if (cfg_read(local_file, &listen_port, &pid_file, &g)) {
213       /* This is fatal! */
214       fprintf(stderr, "%s: Couldn't read configuration from %s: %s\n",
215               progname, local_file, strerror(errno));
216       free(local_file);
217       return 2;
218     } else {
219       config_file = x_strdup(local_file);
220     }
221   }
222 
223   /* Read global config file if local one not found */
224   if (!local_file) {
225     char *global_file;
226 
227     /* Not fatal if it doesn't exist */
228     global_file = x_sprintf("%s/%s", SYSCONFDIR, GLOBAL_CONFIG_FILENAME);
229     debug("Global config file: %s", global_file);
230     cfg_read(global_file, &listen_port, &pid_file, &g);
231     config_file = x_strdup(global_file);
232     free(global_file);
233   } else {
234     free(local_file);
235   }
236 
237   /* Check we got some connection classes */
238   if (!connclasses) {
239     fprintf(stderr, "%s: No connection classes have been defined.\n", progname);
240     return 2;
241   }
242 
243   /* -P overrides config file */
244   if (cmd_listen_port) {
245     free(listen_port);
246     listen_port = cmd_listen_port;
247   }
248 
249   /* -p overrides pid file */
250   if (cmd_pid_file) {
251     free(pid_file);
252     pid_file = cmd_pid_file;
253   }
254 
255   /* Set signal handlers */
256   signal(SIGTERM, _sig_term);
257   signal(SIGINT, _sig_term);
258   signal(SIGHUP, _sig_hup);
259   signal(SIGCHLD, _sig_child);
260 #ifdef DEBUG_MEMORY
261   signal(SIGUSR1, _sig_usr);
262   signal(SIGUSR2, _sig_usr);
263 #endif /* DEBUG_MEMORY */
264 
265   /* Broken Pipe?  This means that someone disconnected while we were
266      sending stuff.  Naughty! */
267   signal(SIGPIPE, SIG_IGN);
268 
269   if (!inetd_mode) {
270     debug("Ordinary console dodge-monkey mode");
271 
272     /* Make listening socket before we fork */
273     if (ircnet_listen(listen_port)) {
274       fprintf(stderr, "%s: Unable to establish listen port\n", progname);
275       return 3;
276     }
277 
278     /* go daemon here */
279     if (!no_daemon) {
280       switch (go_daemon()) {
281         case -1:
282           return -1;
283         case 0:
284           break;
285         default:
286           return 0;
287       }
288     }
289 
290   } else {
291     /* running under inetd means we are backgrounded right *now* */
292     in_background = 1;
293 
294     debug("Inetd SuperTed mode!");
295 
296     /* Hook STDIN into a new proxy */
297     ircnet_hooksocket(STDIN_FILENO);
298   }
299 
300   /* Open a connection to syslog if we're in the background */
301   if (in_background)
302     openlog(PACKAGE, LOG_PID, LOG_USER);
303 
304   if (pid_file) {
305     FILE *pidfile;
306 
307     pidfile = fopen(pid_file, "w");
308     if (pidfile) {
309       fchmod(fileno(pidfile), 0600);
310       fprintf(pidfile, "%d\n", getpid());
311       fclose(pidfile);
312     } else {
313       syscall_fail("fopen", pid_file, 0);
314     }
315   }
316 
317   /* Main loop! */
318   while (!stop_poll) {
319     int ns, nt, status;
320     pid_t pid;
321 
322     ircnet_expunge_proxies();
323     dccnet_expunge_proxies();
324     ns = net_poll();
325     nt = timer_poll();
326 
327     /* Reap any children */
328     while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
329       debug("Reaped process %d, exit status %d", pid, status);
330 
331       /* Handle any DNS children */
332       dns_endrequest(pid, status);
333     }
334 
335     /* Reload the configuration file? */
336     if (reload_config) {
337       _reload_config();
338       reload_config = 0;
339     }
340 
341     if (!ns && !nt)
342       break;
343   }
344 
345   if (pid_file) {
346     unlink(pid_file);
347   }
348 
349   /* Free up stuff */
350   ircnet_flush();
351   dccnet_flush();
352   dns_flush();
353   timer_flush();
354 
355   /* Do a lingering close on all sockets */
356   net_closeall();
357   net_flush();
358 
359   /* Close down and free up memory */
360   if (!inetd_mode && !no_daemon)
361     closelog();
362   free(listen_port);
363   free(pid_file);
364   free(config_file);
365 
366 #ifdef DEBUG_MEMORY
367   mem_report("termination");
368 #endif /* DEBUG_MEMORY */
369 
370   return 0;
371 }
372 
373 /* Signal to stop polling */
_sig_term(int sig)374 static void _sig_term(int sig) {
375   debug("Received signal %d to stop", sig);
376   stop();
377 }
378 
379 /* Signal to reload configuration file */
_sig_hup(int sig)380 static void _sig_hup(int sig) {
381   debug("Received signal %d to reload config", sig);
382   reload_config = 1;
383 
384   /* Restore the signal */
385   signal(sig, _sig_hup);
386 }
387 
388 /* Signal to reap child process.  Don't do anything other than interrupt
389  * whatever we're doing so we reach the waitpid loop in the main loop quicker
390  */
_sig_child(int sig)391 static void _sig_child(int sig) {
392   debug("Received signal %d to reap", sig);
393 
394   /* Restore the signal */
395   signal(sig, _sig_child);
396 }
397 
398 
399 #ifdef DEBUG_MEMORY
400 /* On USR signals, dump debug information */
_sig_usr(int sig)401 static void _sig_usr(int sig) {
402   mem_report(sig == SIGUSR1 ? 0 : "signal");
403   signal(sig, _sig_usr);
404 }
405 #endif /* DEBUG_MEMORY */
406 
407 /* Reload the configuration file */
_reload_config(void)408 static int _reload_config(void) {
409   struct ircconnclass *oldclasses, *c;
410   struct globalvars newglobals;
411   char *new_listen_port, *new_pid_file;
412 
413   debug("Reloading config from %s", config_file);
414   new_listen_port = x_strdup(DEFAULT_LISTEN_PORT);
415   new_pid_file = (DEFAULT_PID_FILE ? x_strdup(DEFAULT_PID_FILE) : 0);
416   oldclasses = connclasses;
417   connclasses = 0;
418 
419   if (cfg_read(config_file, &new_listen_port, &new_pid_file, &newglobals)) {
420     /* Config file reload failed */
421     error("Reload of configuration file %s failed", config_file);
422     ircnet_flush_connclasses(&connclasses);
423 
424     connclasses = oldclasses;
425     free(new_listen_port);
426     free(new_pid_file);
427     return -1;
428   }
429 
430   /* Copy over new globals */
431   memcpy(&g, &newglobals, sizeof(struct globalvars));
432 
433   /* Listen port changed */
434   if (strcmp(listen_port, new_listen_port)) {
435     debug("Changing listen_port from %s to %s", listen_port, new_listen_port);
436     if (ircnet_listen(new_listen_port)) {
437       /* This isn't fatal */
438       error("Unable to change listen_port from %s to %s",
439             listen_port, new_listen_port);
440     } else {
441       free(listen_port);
442       listen_port = new_listen_port;
443       new_listen_port = 0;
444     }
445   }
446 
447   /* Change the pid file variable (don't bother rewriting it) */
448   free(pid_file);
449   pid_file = new_pid_file;
450   new_pid_file = 0;
451 
452   /* Match everything back up to the old stuff */
453   c = connclasses;
454   while (c) {
455     struct ircconnclass *o;
456 
457     o = oldclasses;
458     while (o) {
459       if (!strcmp(c->password, o->password)) {
460         struct ircproxy *p;
461 
462         p = ircnet_fetchclass(o);
463         if (p)
464           p->conn_class = c;
465 
466         break;
467       }
468 
469       o = o->next;
470     }
471 
472     c = c->next;
473   }
474 
475   /* Kill anyone who got lost in the reload */
476   c = oldclasses;
477   while (c) {
478     struct ircproxy *p;
479 
480     p = ircnet_fetchclass(c);
481     if (p) {
482       p->conn_class = 0;
483       ircserver_send_command(p, "QUIT", ":Permission revoked - %s %s",
484                              PACKAGE, VERSION);
485       ircserver_close_sock(p);
486 
487       ircclient_send_error(p, "No longer permitted to use this proxy");
488       ircclient_close(p);
489     }
490 
491     c = c->next;
492   }
493 
494   /* Clean up */
495   ircnet_flush_connclasses(&oldclasses);
496   free(new_listen_port);
497 
498   return 0;
499 }
500 
501 /* Print the usage instructions to stderr */
_print_usage(void)502 static int _print_usage(void) {
503   fprintf(stderr, "%s: Try '%s --help' for more information.\n",
504           progname, progname);
505 
506   return 0;
507 }
508 
509 /* Print the version number to stderr */
_print_version(void)510 static int _print_version(void) {
511   fprintf(stderr, "%s %s - (C) 2009 Noel Shrum & Francois Harvey - http://dircproxy.googlecode.com/", PACKAGE, VERSION);
512 
513 #ifdef SSL
514   fprintf(stderr, " - SSL");
515 #endif /* SSL */
516 #ifdef DEBUG
517   fprintf(stderr, " - DEBUG");
518 #endif /* DEBUG */
519    fprintf(stderr, "\n");
520   return 0;
521 }
522 
523 /* Print the help to stderr */
_print_help(void)524 static int _print_help(void) {
525   fprintf(stderr, "%s %s.  Detachable IRC proxy.\n", PACKAGE, VERSION);
526   fprintf(stderr, "(C) 2009 Noel Shrum & Francois Harvey - http://dircproxy.googlecode.com/\n\n");
527   fprintf(stderr, "Usage: %s [OPTION]...\n\n", progname);
528   fprintf(stderr, "If a long option shows an argument as mandatory, then "
529                   "it is mandatory\nfor the equivalent short option also.  "
530                   "Similarly for optional arguments.\n\n");
531   fprintf(stderr, "  -h, --help              Print a summary of the options\n");
532   fprintf(stderr, "  -v, --version           Print the version number\n");
533 #ifndef DEBUG
534   fprintf(stderr, "  -D, --no-daemon         Remain in the foreground\n");
535 #else /* DEBUG */
536   fprintf(stderr, "  -D, --daemon            Run as a daemon to debug it\n");
537 #endif /* DEBUG */
538   fprintf(stderr, "  -I, --inetd             Being run from inetd "
539                                             "(implies -D)\n");
540   fprintf(stderr, "  -P, --listen-port=PORT  Port to listen for clients on\n");
541   fprintf(stderr, "  -p, --pid-file=FILE     Write PID to this file\n");
542   fprintf(stderr, "  -f, --config-file=FILE  Use this file instead of the "
543                                             "default\n\n");
544 
545   return 0;
546 }
547 
548 /* Called when a system call fails.  Print it to stderr or syslog */
syscall_fail(const char * function,const char * arg,const char * message)549 int syscall_fail(const char *function, const char *arg, const char *message) {
550   char *msg;
551   int err;
552 
553   err = errno;
554 
555   msg = x_sprintf("%s(%s) failed: %s", function, (arg ? arg : ""),
556                   (message ? message : strerror(err)));
557   if (in_background) {
558     syslog(LOG_NOTICE, "%s", msg);
559   } else {
560 #ifdef DEBUG
561     fprintf(stderr, "%s: \033[33;1m%s\033[m\n", progname, msg);
562 #else /* DEBUG */
563     fprintf(stderr, "%s: %s\n", progname, msg);
564 #endif /* DEBUG */
565   }
566 
567   free(msg);
568   return 0;
569 }
570 
571 /* Called to log an error */
error(const char * format,...)572 int error(const char *format, ...) {
573   va_list ap;
574   char *msg;
575 
576   va_start(ap, format);
577   msg = x_vsprintf(format, ap);
578   va_end(ap);
579 
580   if (in_background) {
581     syslog(LOG_ERR, "%s", msg);
582   } else {
583 #ifdef DEBUG
584     fprintf(stderr, "%s: \033[31;1m%s\033[m\n", progname, msg);
585 #else /* DEBUG */
586     fprintf(stderr, "%s: %s\n", progname, msg);
587 #endif /* DEBUG */
588   }
589 
590   free(msg);
591   return 0;
592 }
593 
594 /* Called to output debugging information to stderr or syslog */
debug(const char * format,...)595 int debug(const char *format, ...) {
596 #ifdef DEBUG
597   va_list ap;
598   char *msg;
599 
600   va_start(ap, format);
601 #ifdef DEBUG_MEMORY
602   msg = xx_vsprintf(0, 0, format, ap);
603 #else /* DEBUG_MEMORY */
604   msg = x_vsprintf(format, ap);
605 #endif /* DEBUG_MEMORY */
606   va_end(ap);
607 
608   if (in_background) {
609     syslog(LOG_DEBUG, "%s", msg);
610   } else {
611     printf("%s\n", msg);
612   }
613 
614   free(msg);
615 #endif /* DEBUG */
616 
617   return 0;
618 }
619 
620 /* Called to stop dircproxy */
stop(void)621 int stop(void) {
622   stop_poll = 1;
623 
624   return 0;
625 }
626 
627 /* Called to set queue config reload */
reload(void)628 int reload(void) {
629   reload_config = 1;
630 
631   return 0;
632 }
633 
634 /* Called to go into the background */
go_daemon(void)635 int go_daemon(void) {
636   if (in_background)
637     return 0;
638 
639   debug("Running in the background");
640 
641   switch (fork()) {
642     case -1:
643       syscall_fail("fork", "first", 0);
644       return -1;
645     case 0:
646       break;
647     default:
648       return 1;
649   }
650 
651   /* Become process group leader */
652   setsid();
653 
654   switch (fork()) {
655     case -1:
656       syscall_fail("fork", "second", 0);
657       return -1;
658     case 0:
659       break;
660     default:
661       return 2;
662   }
663 
664   /* Set our umask */
665   umask(0);
666 
667   /* Okay, we're in the background now */
668   in_background = 1;
669   return 0;
670 }
671