/* 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 #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); 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 *configfile = TAGR_CONFIGFILE; char *html_template = TAGR_TEMPLATE; char *user; char *basedir; char *hostname; int foreground = 0; int single_process_option = 0; int verbose_level; static int rebuild_option; static int rebuild_last_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, OPT_REBUILD_LAST }; 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}, {"rebuild-last", OPT_REBUILD_LAST, NULL, 0, N_("same as --rebuild, but refer to the last stored sample"), 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_REBUILD_LAST: rebuild_option = 1; rebuild_last_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 ()) { 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 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)); } if (open_db (TAGR_DB_WR)) exit (EX_UNAVAILABLE); 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); } 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); } 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); } static char **save_argv; int tagr_idle () { switch (command) { case command_none: break; case command_update: rebuild (0); command = command_none; alarm (update_interval); break; case command_reconfig: close_servers (); tagr_restart (save_argv); } return 0; } int main (int argc, char **argv) { int index, rc; fd_set read_fds; 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; if (user_option) user = user_option; if (!hostname) hostname = tagr_local_hostname (); 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 (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 (user) change_user (); if (log_to_stderr == -1) log_to_stderr = (import_option || read_option || rebuild_option || list_option || foreground) && isatty (0); grecs_log_to_stderr = log_to_stderr; init_syslog (program_invocation_short_name); 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 (TAGR_UPD_FORCE | (rebuild_last_option ? TAGR_UPD_LASTTIME : 0)); if (list_option) list_db (); if (rebuild_option || import_option || list_option || read_option) exit (0); 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); open_servers (); command = command_none; alarm (update_interval); server_loop (); close_servers (); exit (0); }