/* 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);
}