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