/* darkstat 3 * copyright (c) 2001-2007 Emil Mikulic. * * darkstat.c: signals, cmdline parsing, program body. * * You may use, modify and redistribute this file under the terms of the * GNU General Public License version 2. (see COPYING.GPL) */ #include "darkstat.h" #include "acct.h" #include "cap.h" #include "conv.h" #include "daylog.h" #include "db.h" #include "dns.h" #include "http.h" #include "hosts_db.h" #include "localip.h" #include "ncache.h" #include "pidfile.h" #include "err.h" #include #include #include #include #include #include #include #include #include #include "now.h" time_t now; #ifndef INADDR_NONE # define INADDR_NONE (-1) /* Solaris */ #endif /* --- Signal handling --- */ static volatile int running = 1; static void sig_shutdown(int signum _unused_) { running = 0; } static volatile int reset_pending = 0; static void sig_reset(int signum _unused_) { reset_pending = 1; } /* --- Commandline parsing --- */ const char *interface = NULL; static void cb_interface(const char *arg) { interface = arg; } int want_daemonize = 1; static void cb_debug(const char *arg _unused_) { want_daemonize = 0; } int want_promisc = 1; static void cb_no_promisc(const char *arg _unused_) { want_promisc = 0; } int want_dns = 1; static void cb_no_dns(const char *arg _unused_) { want_dns = 0; } unsigned short bindport = 667; static void cb_port(const char *arg) { unsigned long p; char *ep; errno = 0; p = strtoul(arg, &ep, 10); if (*ep != '\0') errx(1, "\"%s\" is not a valid number", arg); if ((errno == ERANGE) || (p > 65535)) errx(1, "\"%s\" is out of range", arg); bindport = (unsigned short)p; } in_addr_t bindaddr = INADDR_ANY; static void cb_bindaddr(const char *arg) { bindaddr = inet_addr(arg); if (bindaddr == (in_addr_t)INADDR_NONE) errx(1, "malformed address \"%s\"", arg); } const char *filter = NULL; static void cb_filter(const char *arg) { filter = arg; } static void cb_local(const char *arg) { acct_init_localnet(arg); } const char *chroot_dir = NULL; static void cb_chroot(const char *arg) { chroot_dir = arg; } const char *privdrop_user = NULL; static void cb_user(const char *arg) { privdrop_user = arg; } const char *daylog_fn = NULL; static void cb_daylog(const char *arg) { if (chroot_dir == NULL) errx(1, "the daylog file is relative to the chroot.\n" "You must specify a --chroot dir before you can use --daylog."); else daylog_fn = arg; } const char *import_fn = NULL; static void cb_import(const char *arg) { if (chroot_dir == NULL) errx(1, "the import file is relative to the chroot.\n" "You must specify a --chroot dir before you can use --import."); else import_fn = arg; } const char *export_fn = NULL; static void cb_export(const char *arg) { if (chroot_dir == NULL) errx(1, "the export file is relative to the chroot.\n" "You must specify a --chroot dir before you can use --export."); else export_fn = arg; } static const char *pid_fn = NULL; static void cb_pidfile(const char *arg) { if (chroot_dir == NULL) errx(1, "the pidfile is relative to the chroot.\n" "You must specify a --chroot dir before you can use --pidfile."); else pid_fn = arg; } /* --- */ struct cmdline_arg { const char *name, *arg_name; /* NULL arg_name means no arg */ const enum { MANDATORY, OPTIONAL } need; void (*callback)(const char *arg); int num_seen; }; static struct cmdline_arg cmdline_args[] = { {"-i", "interface", MANDATORY, cb_interface, 0}, {"--debug", NULL, OPTIONAL, cb_debug, 0}, {"--no-promisc", NULL, OPTIONAL, cb_no_promisc, 0}, {"--no-dns", NULL, OPTIONAL, cb_no_dns, 0}, {"-p", "port", OPTIONAL, cb_port, 0}, {"-b", "bindaddr", OPTIONAL, cb_bindaddr, 0}, {"-f", "filter", OPTIONAL, cb_filter, 0}, {"-l", "network/netmask", OPTIONAL, cb_local, 0}, {"--chroot", "dir", OPTIONAL, cb_chroot, 0}, {"--user", "username", OPTIONAL, cb_user, 0}, {"--daylog", "filename", OPTIONAL, cb_daylog, 0}, {"--import", "filename", OPTIONAL, cb_import, 0}, {"--export", "filename", OPTIONAL, cb_export, 0}, {"--pidfile", "filename", OPTIONAL, cb_pidfile, 0}, {NULL, NULL, OPTIONAL, NULL, 0} }; static void pad(const int width) { int i; for (i=0; iname != NULL; arg++) { if (first) first = 0; else pad(width); if (arg->need == OPTIONAL) printf("[ "); printf("%s", arg->name); if (arg->arg_name != NULL) printf(" %s", arg->arg_name); if (arg->need == OPTIONAL) printf(" ]"); printf("\n"); } printf("\n" "Please refer to the darkstat(1) manual page for further\n" "documentation and usage examples.\n"); } static void parse_sub_cmdline(const int argc, char * const *argv) { struct cmdline_arg *arg; if (argc == 0) return; for (arg = cmdline_args; arg->name != NULL; arg++) if (strcmp(argv[0], arg->name) == 0) { if ((arg->arg_name != NULL) && (argc == 1)) { printf("\nerror: argument \"%s\" requires parameter \"%s\"\n", arg->name, arg->arg_name); usage(); exit(EXIT_FAILURE); } if (arg->num_seen > 0) { printf("\nerror: already specified argument \"%s\"\n", arg->name); usage(); exit(EXIT_FAILURE); } arg->num_seen++; if (arg->arg_name == NULL) { arg->callback(NULL); parse_sub_cmdline(argc-1, argv+1); } else { arg->callback(argv[1]); parse_sub_cmdline(argc-2, argv+2); } return; } printf("\nerror: illegal argument: \"%s\"\n", argv[0]); usage(); exit(EXIT_FAILURE); } static void parse_cmdline(const int argc, char * const *argv) { struct cmdline_arg *arg; if (argc < 1) { /* Not enough args. */ usage(); exit(EXIT_FAILURE); } parse_sub_cmdline(argc, argv); for (arg = cmdline_args; arg->name != NULL; arg++) if ((arg->need == MANDATORY) && (arg->num_seen == 0)) { printf("\nerror: failed to specify mandatory argument \"%s\"\n", arg->name); usage(); exit(EXIT_FAILURE); } /* some default values */ if (chroot_dir == NULL) chroot_dir = CHROOT_DIR; if (privdrop_user == NULL) privdrop_user = PRIVDROP_USER; } /* --- Program body --- */ int main(int argc, char **argv) { test_64order(); printf(PACKAGE_STRING " (built with libpcap %d.%d)\n", PCAP_VERSION_MAJOR, PCAP_VERSION_MINOR); parse_cmdline(argc-1, argv+1); /* must verbosef() before first fork to init lock */ verbosef("starting up"); if (pid_fn) pidfile_create(chroot_dir, pid_fn, privdrop_user); if (want_daemonize) { verbosef("daemonizing to run in the background!"); daemonize_start(); verbosef("I am the main process"); } if (pid_fn) pidfile_write_close(); /* do this first as it forks - minimize memory use */ if (want_dns) dns_init(privdrop_user); /* Need root privs for these: */ cap_init(interface, filter, want_promisc); http_init(bindaddr, bindport, /*maxconn=*/ -1); ncache_init(); /* don't need priv, but must do before chroot */ privdrop(chroot_dir, privdrop_user); /* Don't need root privs for these: */ if (daylog_fn != NULL) daylog_init(daylog_fn); graph_init(); hosts_db_init(); if (import_fn != NULL) db_import(import_fn); localip_init(interface); if (signal(SIGTERM, sig_shutdown) == SIG_ERR) errx(1, "signal(SIGTERM) failed"); if (signal(SIGINT, sig_shutdown) == SIG_ERR) errx(1, "signal(SIGINT) failed"); if (signal(SIGUSR1, sig_reset) == SIG_ERR) errx(1, "signal(SIGUSR1) failed"); verbosef("entering main loop"); daemonize_finish(); while (running) { int select_ret, max_fd = -1, use_timeout = 0; struct timeval timeout; fd_set rs, ws; if (reset_pending) { if (export_fn != NULL) db_export(export_fn); /* FIXME: USR2? */ hosts_db_reset(); graph_reset(); reset_pending = 0; } now = time(NULL); FD_ZERO(&rs); FD_ZERO(&ws); cap_fd_set(&rs, &max_fd, &timeout, &use_timeout); http_fd_set(&rs, &ws, &max_fd, &timeout, &use_timeout); select_ret = select(max_fd+1, &rs, &ws, NULL, (use_timeout) ? &timeout : NULL); if ((select_ret == 0) && (!use_timeout)) errx(1, "select() erroneously timed out"); if (select_ret == -1) { if (errno == EINTR) continue; else err(1, "select()"); } else { hosts_db_reduce(); graph_rotate(); cap_poll(&rs); dns_poll(); http_poll(&rs, &ws); } } verbosef("shutting down"); cap_stop(); dns_stop(); if (export_fn != NULL) db_export(export_fn); hosts_db_free(); graph_free(); if (daylog_fn != NULL) daylog_free(); ncache_free(); if (pid_fn) pidfile_unlink(); verbosef("shut down"); return (EXIT_SUCCESS); } /* vim:set ts=3 sw=3 tw=78 expandtab: */