/* This file is part of tagr. Copyright (C) 2000, 2005, 2006, 2009 Max Bouglacoff, Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef RETSIGTYPE # define RETSIGTYPE void #endif static RETSIGTYPE sig_quit (int); static RETSIGTYPE sig_fatal (int); static RETSIGTYPE sig_hup (int); static RETSIGTYPE sig_child (int); struct grecs_sockaddr listen_sockaddr; int preprocess_only = 0; int log_to_stderr = -1; int test_template_option; int lint_option; char *pidfile = TAGR_PIDFILE; unsigned update_interval = 5*60; char i_recv_buffer[reply_size (MAXADDR)]; char *recv_buffer = i_recv_buffer; char *configfile = TAGR_CONFIGFILE; char *html_template = TAGR_TEMPLATE; char *user; char *basedir; int port; int sockfd; int foreground = 0; int single_process_option = 0; int verbose_level; static int rebuild_option; static int import_option; static int read_option; static int list_option; static char *check_mode = 0; static char *user_option = NULL; static char *html_template_option = NULL; const char *program_version = "tagr (" PACKAGE_STRING ")"; static char doc[] = N_("tagr -- traffic analyzer and grapher"); static char args_doc[] = "[FILES or DIRS...]"; enum { OPT_IMPORT = 256, OPT_TEST_TEMPLATE, OPT_SHOW_DEFAULTS, OPT_SYSLOG, OPT_STDERR, OPT_PREPROCESSOR, OPT_NO_PREPROCESSOR, OPT_DUMP_GRAMMAR_TRACE, OPT_DUMP_LEX_TRACE, OPT_CONFIG_HELP, OPT_READ, }; static struct argp_option options[] = { #define GRID 10 {NULL, 0, NULL, 0, N_("Main operation mode"), GRID }, {"lint", 't', NULL, 0, N_("parse configuration file and exit"), GRID+1}, {NULL, 'E', NULL, 0, N_("preprocess configuration file and exit"), GRID+1}, {"test-template", OPT_TEST_TEMPLATE, NULL, 0, N_("test page template file syntax"), GRID+1}, {"import", OPT_IMPORT, NULL, 0, N_("import old (mrtg-style) log files from DIR (or the basedir, if not given)"), GRID+1 }, {"read", OPT_READ, NULL, 0, N_("read statistics from given FILEs or standard input") }, {"rebuild", 'b', NULL, 0, N_("rebuild graphs using existing statistics"), GRID+1}, {"list", 'l', NULL, 0, N_("list contents of the rate database"), GRID+1}, {"show-defaults", OPT_SHOW_DEFAULTS, NULL, 0, N_("Show configuration default values"), GRID+1}, #undef GRID #define GRID 20 {NULL, 0, NULL, 0, N_("Operation mode modifiers"), GRID }, {"foreground", 'f', NULL, 0, N_("run in foreground mode"), GRID+1}, {"config-file", 'c', "FILE", 0, N_("read specified configuration FILE"), GRID+1}, {"single-process", 's', NULL, 0, N_("run in single-process mode"), GRID+1}, #undef GRID #define GRID 30 {NULL, 0, NULL, 0, N_("Logging"), GRID }, {"syslog", OPT_SYSLOG, NULL, 0, N_("log to syslog"), GRID+1}, {"stderr", OPT_STDERR, NULL, 0, N_("log to stderr"), GRID+1}, #undef GRID #define GRID 40 {NULL, 0, NULL, 0, N_("Preprocessor control"), GRID }, {"include-directory", 'I', N_("DIR"), 0, N_("add include directory"), GRID+1}, {"define", 'D', N_("SYMBOL[=VALUE]"), 0, N_("define a preprocessor symbol"), GRID+1}, {"preprocessor", OPT_PREPROCESSOR, N_("COMMAND"), 0, N_("use COMMAND instead of the default preprocessor"), GRID+1}, {"no-preprocessor", OPT_NO_PREPROCESSOR, NULL, 0, N_("disable preprocessing"), GRID+1}, #undef GRID #define GRID 50 {NULL, 0, NULL, 0, N_("Debugging"), GRID }, {"verbose", 'v', NULL, 0, N_("increase verbosity level"), GRID+1}, {"dump-grammar-trace", OPT_DUMP_GRAMMAR_TRACE, NULL, 0, N_("dump configuration grammar traces"), GRID+1 }, {"dump-lex-trace", OPT_DUMP_LEX_TRACE, NULL, 0, N_("dump lexical analyzer traces"), GRID+1 }, #undef GRID #define GRID 60 {NULL, 0, NULL, 0, N_("Other settings"), GRID }, {"html-template", 'T', N_("FILE"), 0, N_("use given HTML template file"), GRID+1}, {"user", 'u', N_("USER-NAME"), 0, N_("run with this user privileges"), GRID+1}, #undef GRID #define GRID 70 {NULL, 0, NULL, 0, N_("Additional help"), GRID }, {"config-help", OPT_CONFIG_HELP, NULL, 0, N_("show configuration file summary"), GRID+1}, #undef GRID {NULL} }; static void show_defaults () { printf (_("Configuration file: %s\n"), TAGR_CONFIGFILE); printf (_("Page template file: %s\n"), TAGR_TEMPLATE); printf (_("PID file: %s\n"), TAGR_PIDFILE); printf (_("DB file name: %s\n"), TAGR_DBNAME); printf (_("DB file permissions: %#o\n"), TAGR_DBMODE); printf (_("Syslog facility number: %d\n"), LOG_FACILITY); } static void add_check_mode (int c) { char s[2]; s[0] = c; s[1] = 0; if (!check_mode) check_mode = xstrdup (s); else strcat (xrealloc (check_mode, strlen (check_mode) + 2), s); } static error_t parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case 'f': foreground++; break; case 'b': rebuild_option = 1; break; case 'c': configfile = arg; break; case 'E': preprocess_only = 1; break; case 'l': list_option = 1; break; case 'I': grecs_preproc_add_include_dir (arg); break; case 'D': define_symbol (arg); break; case 's': single_process_option++; break; case 't': lint_option = 1; break; case 'T': html_template_option = arg; break; case 'u': user_option = arg; break; case 'v': verbose_level++; break; case OPT_CONFIG_HELP: config_help (); exit (0); case OPT_DUMP_GRAMMAR_TRACE: grecs_gram_trace (1); break; case OPT_DUMP_LEX_TRACE: grecs_lex_trace (1); break; case OPT_IMPORT: import_option++; break; case OPT_PREPROCESSOR: grecs_preprocessor = arg; break; case OPT_READ: read_option = 1; break; case OPT_NO_PREPROCESSOR: grecs_preprocessor = NULL; break; case OPT_SYSLOG: log_to_stderr = 0; break; case OPT_STDERR: log_to_stderr = 1; break; case OPT_TEST_TEMPLATE: test_template_option = 1; break; case OPT_SHOW_DEFAULTS: show_defaults (); exit (0); default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = { options, parse_opt, args_doc, doc, NULL, NULL, NULL }; /* Change to the given uid/gid. Clear the supplementary group list. On success returns 0. On failure returns 1 (or exits, depending on topt settings. See anubis_error) */ static int change_privs (uid_t uid, gid_t gid) { int rc = 0; gid_t emptygidset[1]; /* Reset group permissions */ emptygidset[0] = gid ? gid : getegid (); if (geteuid () == 0 && setgroups (1, emptygidset)) { logmsg (L_ERR, _("setgroups(1, %lu) failed: %s"), (u_long) emptygidset[0], strerror (errno)); rc = 1; } /* Switch to the user's gid. On some OSes the effective gid must be reset first */ #if defined(HAVE_SETEGID) if ((rc = setegid (gid)) < 0) logmsg (L_ERR, _("setegid(%lu) failed: %s"), (u_long) gid, strerror (errno)); #elif defined(HAVE_SETREGID) if ((rc = setregid (gid, gid)) < 0) logmsg (L_ERR, _("setregid(%lu,%lu) failed: %s"), (u_long) gid, (u_long) gid, strerror (errno)); #elif defined(HAVE_SETRESGID) if ((rc = setresgid (gid, gid, gid)) < 0) logmsg (L_ERR, _("setresgid(%lu,%lu,%lu) failed: %s"), (u_long) gid, (u_long) gid, (u_long) gid, strerror (errno)); #endif if (rc == 0 && gid != 0) { if ((rc = setgid (gid)) < 0 && getegid () != gid) logmsg (L_ERR, _("setgid(%lu) failed: %s"), (u_long) gid, strerror (errno)); if (rc == 0 && getegid () != gid) { logmsg (L_ERR, _("cannot set effective gid to %lu: %s"), (u_long) gid, strerror (errno)); rc = 1; } } /* now reset uid */ if (rc == 0 && uid != 0) { uid_t euid; if (setuid (uid) || geteuid () != uid || (getuid () != uid && (geteuid () == 0 || getuid () == 0))) { #if defined(HAVE_SETREUID) if (geteuid () != uid) { if (setreuid (uid, -1) < 0) { logmsg (L_ERR, _("setreuid(%lu,-1) failed: %s"), (u_long) uid, strerror (errno)); rc = 1; } if (setuid (uid) < 0) { logmsg (L_ERR, _("second setuid(%lu) failed: %s"), (u_long) uid, strerror (errno)); rc = 1; } } else #endif { logmsg (L_ERR, _("setuid(%lu) failed: %s"), (u_long) uid, strerror (errno)); rc = 1; } } if (html_template_option) html_template = html_template_option; euid = geteuid (); if (uid != 0 && setuid (0) == 0) { logmsg (L_ERR, _("seteuid(0) succeeded when it should not")); rc = 1; } else if (uid != euid && setuid (euid) == 0) { logmsg (L_ERR, _("cannot drop non-root setuid privileges")); rc = 1; } } return rc; } void change_user () { struct passwd *pwd; if (user == NULL) return; if (getuid () == 0) { logmsg (L_NOTICE, _("not a superuser: ignoring the `user' statement")); return; } pwd = getpwnam (user); if (pwd) { if (change_privs (pwd->pw_uid, pwd->pw_gid)) exit (EX_NOUSER); chdir (pwd->pw_dir); } } void decode_buffer () { int i; Stat_reply *reply; Stat *sp; int child = 0; reply = (Stat_reply *) recv_buffer; reply->n_addr = ntohl (reply->n_addr); if (reply->n_addr > MAXADDR) { logmsg (L_NOTICE, _("got invalid packet: n_addr = %d"), reply->n_addr); return; } reply->timestamp = ntohl (reply->timestamp); if (verbose_level) { char tbuf[sizeof("2009-04-01 00:00:00")]; strftime (tbuf, sizeof tbuf, "%Y-%m-%d %H:%M:%S", gmtime (&reply->timestamp)); logmsg (L_INFO, _("Received packet: %d %lu - %s"), reply->n_addr, (unsigned long) reply->timestamp, tbuf); } sp = reply->stat; if (!single_process_option) { pid_t pid = fork (); if (pid > 0) return; else if (pid < 0) logmsg (L_ERR, _("cannot fork: %s"), strerror (errno)); else { signal (SIGHUP, SIG_IGN); signal (SIGCHLD, SIG_IGN); child = 1; } } open_db (TAGR_DB_WR); for (i = 0; i < reply->n_addr; i++, sp++) { sp->in = ntohl (sp->in); sp->out = ntohl (sp->out); verbose (1, _("Monitor %s: %lu %lu"), sp->name, sp->in, sp->out); report (sp, reply->timestamp); } close_db (); if (child) exit (0); } void read_input (const char *name) { FILE *fp; char *buf = NULL; size_t bufsize = 0; unsigned long line = 0; unsigned long t; if (!name || strcmp (name, "-") == 0) { name = "-"; fp = stdin; } else { fp = fopen (name, "r"); if (!fp) die (EX_OSERR, _("cannot open file `%s': %s"), name, strerror (errno)); } verbose (2, _("Reading `%s'"), name); open_db (TAGR_DB_WR); while (getline (&buf, &bufsize, fp) > 0) { char *p; int i; Stat st; line++; for (p = buf; *p && isascii (*p) && isspace (*p); p++) ; if (*p == '#' || *p == 0) continue; i = 0; while (*p && !isspace (*p)) { if (i > MAX_NAME_LENGTH) die (EX_DATAERR, _("%s:%lu: ID too long"), name, line); st.name[i++] = *p++; } st.name[i] = 0; if (sscanf (p, " %lu %lu %lu\n", &t, &st.in, &st.out) != 3) die (EX_DATAERR, _("%s:%lu: invalid input line"), name, line); report (&st, t); } fclose (fp); close_db (); verbose (2, _("Finished reading `%s'"), name); } int get_port (char *str) { int pn; if (isdigit (str[0])) pn = htons (atoi (str)); else { struct servent *s = getservbyname (str, "udp"); if (s) pn = s->s_port; else { logmsg (L_ERR, _("no such service: %s"), str); return 0; } } return pn; } enum command { command_none, command_update, command_reconfig }; enum command command; RETSIGTYPE sig_quit (int sig) { logmsg (L_INFO, _("exiting on signal %d"), sig); unlink (pidfile); exit (0); } RETSIGTYPE sig_fatal (int sig) { logmsg (L_ERR, _("FATAL: exiting on signal %d"), sig); unlink (pidfile); exit (EX_UNAVAILABLE); } RETSIGTYPE sig_hup (int sig) { command = command_reconfig; signal (sig, sig_hup); } RETSIGTYPE sig_alrm (int sig) { command = command_update; signal (sig, sig_alrm); } RETSIGTYPE sig_child (int sig) { int status; while (waitpid ((pid_t)-1, &status, WNOHANG) > 0) ; signal (sig, sig_child); } void assign_string (char **pstr, const char *s) { free (*pstr); if (s) { *pstr = xmalloc (strlen (s) + 1); strcpy (*pstr, s); } else *pstr = NULL; } void assign_string_n (char **pstr, const char *s, size_t length) { free (*pstr); if (s) { *pstr = xmalloc (length + 1); memcpy (*pstr, s, length); (*pstr)[length] = 0; } else *pstr = NULL; } char * mkfilename (const char *dir, const char *name, const char *suffix) { int ret; char *buf; int len = strlen (name) + (suffix ? strlen (suffix) : 0); if (dir) len += strlen (dir) + 1; buf = xmalloc (len + 1); buf[0] = 0; if (dir) { strcat (buf, dir); strcat (buf, "/"); } strcat (buf, name); if (suffix) strcat (buf, suffix); return buf; } void tagr_restart (char **argv) { int i; int minfd = log_to_stderr ? 2 : 0; logmsg (L_NOTICE, _("tagr restarting")); unlink (pidfile); signal (SIGHUP, SIG_DFL); signal (SIGUSR1, SIG_DFL); signal (SIGUSR2, SIG_DFL); signal (SIGQUIT, SIG_DFL); signal (SIGTERM, SIG_DFL); signal (SIGIOT, SIG_DFL); signal (SIGCHLD, SIG_DFL); signal (SIGPIPE, SIG_DFL); signal (SIGALRM, SIG_DFL); for (i = getdtablesize (); i > minfd; i--) close (i); execv (argv[0], argv); logmsg (L_ERR, _("cannot restart: %s"), strerror (errno)); exit (EX_UNAVAILABLE); } #define CHECK_USAGE(cond, opt, mode_opt) \ do \ if (cond) \ die (EX_USAGE, _("%s is meaningless with %s"), opt, mode_opt); \ while (0) #define CHECK_OPTION(opt, optname) \ do \ { \ if (opt != import_option) \ CHECK_USAGE (import_option, "--import", optname); \ if (opt != rebuild_option) \ CHECK_USAGE (rebuild_option, "--rebuild", optname); \ if (opt != list_option) \ CHECK_USAGE (list_option, "--list", optname); \ if (opt != read_option) \ CHECK_USAGE (read_option, "--read", optname); \ } \ while (0) const char version_etc_copyright[] = /* Do *not* mark this string for translation. %s is a copyright symbol suitable for this locale, and %d is the copyright year. */ "Copyright %s 2000-%d Sergey Poznyakoff"; static void tagr_version (FILE *stream, struct argp_state *state) { version_etc (stream, "tagr", PACKAGE_NAME, PACKAGE_VERSION, "Max Bouglacoff", "Sergey Poznyakoff", NULL); } int main (int argc, char **argv) { int index, rc; fd_set read_fds; char **save_argv = argv; argp_program_bug_address = "<" PACKAGE_BUGREPORT ">"; argp_program_version_hook = tagr_version; if (!isatty (2)) { log_to_stderr = 0; } init_syslog (argv[0]); if (argp_parse (&argp, argc, argv, 0, &index, NULL)) exit (EX_USAGE); if (html_template_option) html_template = html_template_option; argc -= index; argv += index; if (argc != 0 && !(list_option || import_option)) die (EX_USAGE, _("Too many arguments")); if (readconfig ()) exit (EX_CONFIG); if (lint_option) { CHECK_USAGE (import_option, "--import", "--lint"); CHECK_USAGE (rebuild_option, "--rebuild", "--lint"); CHECK_USAGE (list_option, "--list", "--lint"); CHECK_USAGE (read_option, "--read", "--lint"); exit (0); } if (log_to_stderr == -1) log_to_stderr = foreground && isatty (0); grecs_log_to_stderr = log_to_stderr; init_syslog (program_invocation_short_name); if (test_template_option) { CHECK_USAGE (import_option, "--import", "--test-template"); CHECK_USAGE (rebuild_option, "--rebuild", "--test-template"); CHECK_USAGE (list_option, "--list", "--test-template"); CHECK_USAGE (read_option, "--read", "--test-template"); exit (check_template ()); } if (import_option) { CHECK_USAGE (read_option, "--read", "--import"); if (argc) while (argc--) import (*argv++); else import (basedir); } if (read_option) { if (argc) while (argc--) read_input (*argv++); else read_input (NULL); } if (rebuild_option) rebuild (1); if (list_option) list_db (); if (rebuild_option || import_option || list_option || read_option) exit (0); if (listen_sockaddr.sa == NULL) { logmsg (L_CRIT, _("listener address is not configured")); exit (EX_CONFIG); } if (user_option) change_user (); sockfd = socket (listen_sockaddr.sa->sa_family, SOCK_DGRAM, 0); if (sockfd < 0) die (EX_OSERR, "socket: %s", strerror (errno)); if ((rc = fcntl (sockfd, F_GETFD, 0)) == -1 || fcntl (sockfd, F_SETFD, rc | FD_CLOEXEC) == -1) logmsg (L_ERR, _("cannot set close-on-exec: %s"), strerror (errno)); switch (listen_sockaddr.sa->sa_family) { case PF_INET: { int yes = 1; if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &yes, sizeof(yes)) == -1) logmsg (L_ERR, _("setting reuseaddr failed: %s"), strerror (errno)); } break; case PF_UNIX: { struct sockaddr_un *s_un = (struct sockaddr_un *) listen_sockaddr.sa; if (unlink (s_un->sun_path)) logmsg (L_ERR, _("cannot remove socket %s: %s"), s_un->sun_path, strerror (errno)); } } if (bind (sockfd, listen_sockaddr.sa, listen_sockaddr.len) < 0) die (EX_OSERR, "bind: %s", strerror (errno)); if (save_argv[0][0] != '/') { logmsg (L_ERR, _("program file name is not absolute: SIGHUP will not work")); signal (SIGHUP, sig_quit); } else signal (SIGHUP, sig_hup); signal (SIGUSR1, SIG_IGN); signal (SIGUSR2, SIG_IGN); signal (SIGQUIT, sig_quit); signal (SIGTERM, sig_quit); signal (SIGIOT, sig_fatal); signal (SIGCHLD, sig_child); signal (SIGPIPE, SIG_IGN); signal (SIGALRM, sig_alrm); if (!foreground) { FILE *fp; if (daemon (0, 0)) die (EX_OSERR, _("cannot become daemon: %s"), strerror (errno)); if ((fp = fopen (pidfile, "w")) != NULL) { fprintf (fp, "%lu\n", (unsigned long) getpid ()); fclose (fp); } else { logmsg (L_ERR, _("cannot write pid file %s: %s"), pidfile, strerror (errno)); } } logmsg (L_INFO, _("%s started"), program_version); command = command_none; alarm (update_interval); for (;;) { switch (command) { case command_none: break; case command_update: rebuild (0); command = command_none; alarm (update_interval); break; case command_reconfig: tagr_restart (save_argv); } FD_ZERO (&read_fds); FD_SET (sockfd, &read_fds); rc = select (sockfd + 1, &read_fds, NULL, NULL, NULL); if (rc < 0) { if (errno != EINTR) logmsg (L_ERR, "select: %s", strerror (errno)); continue; } if (rc == 1) { struct sockaddr saremote; int salen = sizeof saremote; rc = recvfrom (sockfd, (char *) recv_buffer, sizeof (i_recv_buffer), 0, &saremote, &salen); if (rc > 0) decode_buffer (); else if (rc < 0 && errno != EINTR) logmsg (L_ERR, "recvfrom: %s", strerror (errno)); } } }