2 * copyright (c) 2001-2011 Emil Mikulic.
4 * darkstat.c: signals, cmdline parsing, program body.
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
38 # define INADDR_NONE (-1) /* Solaris */
41 /* --- Signal handling --- */
42 static volatile int running
= 1;
43 static void sig_shutdown(int signum _unused_
) { running
= 0; }
45 static volatile int reset_pending
= 0, export_pending
= 0;
46 static void sig_reset(int signum _unused_
) {
51 static void sig_export(int signum _unused_
) { export_pending
= 1; }
53 /* --- Commandline parsing --- */
54 static unsigned long parsenum(const char *str
,
55 unsigned long max
/* 0 for no max */) {
60 n
= strtoul(str
, &end
, 10);
62 errx(1, "\"%s\" is not a valid number", str
);
64 errx(1, "\"%s\" is out of range", str
);
65 if ((max
!= 0) && (n
> max
))
66 errx(1, "\"%s\" is out of range (max %lu)", str
, max
);
70 static int opt_iface_seen
= 0;
71 static void cb_interface(const char *arg
) {
76 static void cb_filter(const char *arg
) { cap_add_filter(arg
); }
78 const char *opt_capfile
= NULL
;
79 static void cb_capfile(const char *arg
) { opt_capfile
= arg
; }
81 int opt_want_snaplen
= -1;
82 static void cb_snaplen(const char *arg
)
83 { opt_want_snaplen
= (int)parsenum(arg
, 0); }
85 int opt_want_pppoe
= 0;
86 static void cb_pppoe(const char *arg _unused_
) { opt_want_pppoe
= 1; }
88 int opt_want_syslog
= 0;
89 static void cb_syslog(const char *arg _unused_
) { opt_want_syslog
= 1; }
91 int opt_want_verbose
= 0;
92 static void cb_verbose(const char *arg _unused_
) { opt_want_verbose
= 1; }
94 int opt_want_daemonize
= 1;
95 static void cb_no_daemon(const char *arg _unused_
) { opt_want_daemonize
= 0; }
97 int opt_want_promisc
= 1;
98 static void cb_no_promisc(const char *arg _unused_
) { opt_want_promisc
= 0; }
100 int opt_want_dns
= 1;
101 static void cb_no_dns(const char *arg _unused_
) { opt_want_dns
= 0; }
103 int opt_want_macs
= 1;
104 static void cb_no_macs(const char *arg _unused_
) { opt_want_macs
= 0; }
106 int opt_want_lastseen
= 1;
107 static void cb_no_lastseen(const char *arg _unused_
) { opt_want_lastseen
= 0; }
109 unsigned short opt_bindport
= 667;
110 static void cb_port(const char *arg
)
111 { opt_bindport
= (unsigned short)parsenum(arg
, 65536); }
113 static void cb_bindaddr(const char *arg
) { http_add_bindaddr(arg
); }
115 static int is_localnet_specified
= 0;
116 static void cb_local(const char *arg
)
118 acct_init_localnet(arg
);
119 is_localnet_specified
= 1;
122 int opt_want_local_only
= 0;
123 static void cb_local_only(const char *arg _unused_
)
124 { opt_want_local_only
= 1; }
126 const char *opt_chroot_dir
= NULL
;
127 static void cb_chroot(const char *arg
) { opt_chroot_dir
= arg
; }
129 const char *opt_privdrop_user
= NULL
;
130 static void cb_user(const char *arg
) { opt_privdrop_user
= arg
; }
132 const char *daylog_fn
= NULL
;
133 static void cb_daylog(const char *arg
)
135 if (opt_chroot_dir
== NULL
)
136 errx(1, "the daylog file is relative to the chroot.\n"
137 "You must specify a --chroot dir before you can use --daylog.");
142 const char *import_fn
= NULL
;
143 static void cb_import(const char *arg
)
145 if (opt_chroot_dir
== NULL
)
146 errx(1, "the import file is relative to the chroot.\n"
147 "You must specify a --chroot dir before you can use --import.");
152 const char *export_fn
= NULL
;
153 static void cb_export(const char *arg
)
155 if ((opt_chroot_dir
== NULL
) && (opt_capfile
== NULL
))
156 errx(1, "the export file is relative to the chroot.\n"
157 "You must specify a --chroot dir before you can use --export.");
162 static const char *pid_fn
= NULL
;
163 static void cb_pidfile(const char *arg
)
165 if (opt_chroot_dir
== NULL
)
166 errx(1, "the pidfile is relative to the chroot.\n"
167 "You must specify a --chroot dir before you can use --pidfile.");
172 unsigned int opt_hosts_max
= 1000;
173 static void cb_hosts_max(const char *arg
)
174 { opt_hosts_max
= parsenum(arg
, 0); }
176 unsigned int opt_hosts_keep
= 500;
177 static void cb_hosts_keep(const char *arg
)
178 { opt_hosts_keep
= parsenum(arg
, 0); }
180 unsigned int opt_ports_max
= 200;
181 static void cb_ports_max(const char *arg
)
182 { opt_ports_max
= parsenum(arg
, 65536); }
184 unsigned int opt_ports_keep
= 30;
185 static void cb_ports_keep(const char *arg
)
186 { opt_ports_keep
= parsenum(arg
, 65536); }
188 unsigned int opt_highest_port
= 65535;
189 static void cb_highest_port(const char *arg
)
190 { opt_highest_port
= parsenum(arg
, 65535); }
192 int opt_wait_secs
= -1;
193 static void cb_wait_secs(const char *arg
)
194 { opt_wait_secs
= (int)parsenum(arg
, 0); }
196 int opt_want_hexdump
= 0;
197 static void cb_hexdump(const char *arg _unused_
)
198 { opt_want_hexdump
= 1; }
200 int opt_want_help
= 0;
201 static void cb_help(const char *arg _unused_
)
202 { opt_want_help
= 1; }
203 static void cb_version(const char *arg _unused_
)
204 { opt_want_help
= -1; }
209 const char *name
, *arg_name
; /* NULL arg_name means unary */
210 void (*callback
)(const char *arg
);
214 static struct cmdline_arg cmdline_args
[] = {
215 {"-i", "interface", cb_interface
, -1},
216 {"-f", "filter", cb_filter
, -1},
217 {"-r", "capfile", cb_capfile
, 0},
218 {"-p", "port", cb_port
, 0},
219 {"-b", "bindaddr", cb_bindaddr
, -1},
220 {"-l", "network/netmask", cb_local
, 0},
221 {"--local-only", NULL
, cb_local_only
, 0},
222 {"--snaplen", "bytes", cb_snaplen
, 0},
223 {"--pppoe", NULL
, cb_pppoe
, 0},
224 {"--syslog", NULL
, cb_syslog
, 0},
225 {"--verbose", NULL
, cb_verbose
, 0},
226 {"--no-daemon", NULL
, cb_no_daemon
, 0},
227 {"--no-promisc", NULL
, cb_no_promisc
, 0},
228 {"--no-dns", NULL
, cb_no_dns
, 0},
229 {"--no-macs", NULL
, cb_no_macs
, 0},
230 {"--no-lastseen", NULL
, cb_no_lastseen
, 0},
231 {"--chroot", "dir", cb_chroot
, 0},
232 {"--user", "username", cb_user
, 0},
233 {"--daylog", "filename", cb_daylog
, 0},
234 {"--import", "filename", cb_import
, 0},
235 {"--export", "filename", cb_export
, 0},
236 {"--pidfile", "filename", cb_pidfile
, 0},
237 {"--hosts-max", "count", cb_hosts_max
, 0},
238 {"--hosts-keep", "count", cb_hosts_keep
, 0},
239 {"--ports-max", "count", cb_ports_max
, 0},
240 {"--ports-keep", "count", cb_ports_keep
, 0},
241 {"--highest-port", "port", cb_highest_port
, 0},
242 {"--wait", "secs", cb_wait_secs
, 0},
243 {"--hexdump", NULL
, cb_hexdump
, 0},
244 {"--version", NULL
, cb_version
, 0},
245 {"--help", NULL
, cb_help
, 0},
246 {NULL
, NULL
, NULL
, 0}
249 /* We autogenerate the usage statement from the cmdline_args data structure. */
250 static void usage(void) {
251 static char intro
[] = "usage: darkstat ";
252 char indent
[sizeof(intro
)];
253 struct cmdline_arg
*arg
;
255 printf(PACKAGE_STRING
" (using %s)\n", pcap_lib_version());
256 if (opt_want_help
== -1) return;
258 memset(indent
, ' ', sizeof(indent
));
259 indent
[0] = indent
[sizeof(indent
) - 1] = 0;
261 printf("\n%s", intro
);
262 for (arg
= cmdline_args
; arg
->name
!= NULL
; arg
++) {
263 printf("%s[ %s%s%s ]\n",
266 arg
->arg_name
!= NULL
? " " : "",
267 arg
->arg_name
!= NULL
? arg
->arg_name
: "");
271 "Please refer to the darkstat(8) manual page for further\n"
272 "documentation and usage examples.\n");
275 static void parse_sub_cmdline(const int argc
, char * const *argv
) {
276 struct cmdline_arg
*arg
;
278 if (argc
== 0) return;
279 for (arg
= cmdline_args
; arg
->name
!= NULL
; arg
++)
280 if (strcmp(argv
[0], arg
->name
) == 0) {
281 if ((arg
->arg_name
!= NULL
) && (argc
== 1)) {
283 "error: argument \"%s\" requires parameter \"%s\"\n",
284 arg
->name
, arg
->arg_name
);
288 if (arg
->num_seen
> 0) {
290 "error: already specified argument \"%s\"\n",
296 if (arg
->num_seen
!= -1) /* accept more than one */
299 if (arg
->arg_name
== NULL
) {
301 parse_sub_cmdline(argc
-1, argv
+1);
303 arg
->callback(argv
[1]);
304 parse_sub_cmdline(argc
-2, argv
+2);
309 fprintf(stderr
, "error: illegal argument: \"%s\"\n", argv
[0]);
314 static void parse_cmdline(const int argc
, char * const *argv
) {
316 /* Not enough args. */
321 parse_sub_cmdline(argc
, argv
);
328 /* start syslogging as early as possible */
330 openlog("darkstat", LOG_NDELAY
| LOG_PID
, LOG_DAEMON
);
332 /* some default values */
333 if (opt_chroot_dir
== NULL
)
334 opt_chroot_dir
= CHROOT_DIR
;
335 if (opt_privdrop_user
== NULL
)
336 opt_privdrop_user
= PRIVDROP_USER
;
338 /* sanity check args */
339 if (!opt_iface_seen
&& opt_capfile
== NULL
)
340 errx(1, "must specify either interface (-i) or capture file (-r)");
342 if (opt_iface_seen
&& opt_capfile
!= NULL
)
343 errx(1, "can't specify both interface (-i) and capture file (-r)");
345 if ((opt_hosts_max
!= 0) && (opt_hosts_keep
>= opt_hosts_max
)) {
346 opt_hosts_keep
= opt_hosts_max
/ 2;
347 warnx("reducing --hosts-keep to %u, to be under --hosts-max (%u)",
348 opt_hosts_keep
, opt_hosts_max
);
350 verbosef("max %u hosts, cutting down to %u when exceeded",
351 opt_hosts_max
, opt_hosts_keep
);
353 if ((opt_ports_max
!= 0) && (opt_ports_keep
>= opt_ports_max
)) {
354 opt_ports_keep
= opt_ports_max
/ 2;
355 warnx("reducing --ports-keep to %u, to be under --ports-max (%u)",
356 opt_ports_keep
, opt_ports_max
);
358 verbosef("max %u ports per host, cutting down to %u when exceeded",
359 opt_ports_max
, opt_ports_keep
);
361 if (opt_want_hexdump
&& !opt_want_verbose
) {
362 opt_want_verbose
= 1;
363 verbosef("--hexdump implies --verbose");
366 if (opt_want_hexdump
&& opt_want_daemonize
) {
367 opt_want_daemonize
= 0;
368 verbosef("--hexdump implies --no-daemon");
371 if (opt_want_local_only
&& !is_localnet_specified
)
372 verbosef("WARNING: --local-only without -l only matches the local host");
375 static void run_from_capfile(void) {
379 cap_from_file(opt_capfile
);
380 if (export_fn
!= NULL
) db_export(export_fn
);
383 verbosef("Total packets: %llu, bytes: %llu",
384 (unsigned long long)acct_total_packets
,
385 (unsigned long long)acct_total_bytes
);
388 /* --- Program body --- */
390 main(int argc
, char **argv
)
393 parse_cmdline(argc
-1, argv
+1);
400 /* must verbosef() before first fork to init lock */
401 verbosef("starting up");
402 if (pid_fn
) pidfile_create(opt_chroot_dir
, pid_fn
, opt_privdrop_user
);
404 if (opt_want_daemonize
) {
405 verbosef("daemonizing to run in the background!");
407 verbosef("I am the main process");
409 if (pid_fn
) pidfile_write_close();
411 /* do this first as it forks - minimize memory use */
412 if (opt_want_dns
) dns_init(opt_privdrop_user
);
413 cap_start(opt_want_promisc
); /* needs root */
414 http_listen(opt_bindport
);
415 ncache_init(); /* must do before chroot() */
417 privdrop(opt_chroot_dir
, opt_privdrop_user
);
419 /* Don't need root privs for these: */
421 if (daylog_fn
!= NULL
) daylog_init(daylog_fn
);
424 if (import_fn
!= NULL
) db_import(import_fn
);
426 if (signal(SIGTERM
, sig_shutdown
) == SIG_ERR
)
427 errx(1, "signal(SIGTERM) failed");
428 if (signal(SIGINT
, sig_shutdown
) == SIG_ERR
)
429 errx(1, "signal(SIGINT) failed");
430 if (signal(SIGUSR1
, sig_reset
) == SIG_ERR
)
431 errx(1, "signal(SIGUSR1) failed");
432 if (signal(SIGUSR2
, sig_export
) == SIG_ERR
)
433 errx(1, "signal(SIGUSR2) failed");
435 verbosef("entering main loop");
439 int select_ret
, max_fd
= -1, use_timeout
= 0;
440 struct timeval timeout
;
446 cap_fd_set(&rs
, &max_fd
, &timeout
, &use_timeout
);
447 http_fd_set(&rs
, &ws
, &max_fd
, &timeout
, &use_timeout
);
449 select_ret
= select(max_fd
+1, &rs
, &ws
, NULL
,
450 (use_timeout
) ? &timeout
: NULL
);
451 if (select_ret
== 0 && !use_timeout
)
452 errx(1, "select() erroneously timed out");
453 if (select_ret
== -1) {
463 if (export_pending
) {
464 if (export_fn
!= NULL
)
465 db_export(export_fn
);
471 continue; /* export before reset */
481 timer_stop(&t
, 1000000000, "event processing took longer than a second");
484 verbosef("shutting down");
485 verbosef("pcap stats: %u packets received, %u packets dropped",
486 cap_pkts_recv
, cap_pkts_drop
);
490 if (export_fn
!= NULL
) db_export(export_fn
);
493 if (daylog_fn
!= NULL
) daylog_free();
495 if (pid_fn
) pidfile_unlink();
496 verbosef("shut down");
497 return (EXIT_SUCCESS
);
500 /* vim:set ts=3 sw=3 tw=78 expandtab: */