2 * copyright (c) 2001-2010 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)
24 #include <arpa/inet.h>
25 #include <sys/socket.h>
41 # define INADDR_NONE (-1) /* Solaris */
44 /* --- Signal handling --- */
45 static volatile int running
= 1;
46 static void sig_shutdown(int signum _unused_
) { running
= 0; }
48 static volatile int reset_pending
= 0, export_pending
= 0;
49 static void sig_reset(int signum _unused_
)
55 static void sig_export(int signum _unused_
) { export_pending
= 1; }
57 /* --- Commandline parsing --- */
59 parsenum(const char *str
, unsigned long max
/* 0 for no max */)
65 n
= strtoul(str
, &end
, 10);
67 errx(1, "\"%s\" is not a valid number", str
);
69 errx(1, "\"%s\" is out of range", str
);
70 if ((max
!= 0) && (n
> max
))
71 errx(1, "\"%s\" is out of range (max %lu)", str
, max
);
75 const char *interface
= NULL
;
76 static void cb_interface(const char *arg
) { interface
= arg
; }
78 const char *capfile
= NULL
;
79 static void cb_capfile(const char *arg
) { capfile
= arg
; }
81 int want_snaplen
= -1;
82 static void cb_snaplen(const char *arg
)
83 { want_snaplen
= (int)parsenum(arg
, 0); }
86 static void cb_pppoe(const char *arg _unused_
) { want_pppoe
= 1; }
88 static void cb_syslog(const char *arg _unused_
) { want_syslog
= 1; }
90 static void cb_verbose(const char *arg _unused_
) { want_verbose
= 1; }
92 int want_daemonize
= 1;
93 static void cb_no_daemon(const char *arg _unused_
) { want_daemonize
= 0; }
96 static void cb_no_promisc(const char *arg _unused_
) { want_promisc
= 0; }
99 static void cb_no_dns(const char *arg _unused_
) { want_dns
= 0; }
102 static void cb_no_macs(const char *arg _unused_
) { want_macs
= 0; }
104 int want_lastseen
= 1;
105 static void cb_no_lastseen(const char *arg _unused_
) { want_lastseen
= 0; }
107 unsigned short bindport
= 667;
108 static void cb_port(const char *arg
)
109 { bindport
= (unsigned short)parsenum(arg
, 65536); }
111 const char *bindaddr
= NULL
;
112 static void cb_bindaddr(const char *arg
)
114 struct addrinfo hints
, *ai
;
116 memset(&hints
, 0, sizeof(hints
));
117 hints
.ai_flags
= AI_PASSIVE
;
119 hints
.ai_flags
|= AI_ADDRCONFIG
;
121 hints
.ai_family
= AF_UNSPEC
;
122 hints
.ai_socktype
= SOCK_STREAM
;
124 if (getaddrinfo(arg
, NULL
, &hints
, &ai
))
125 errx(1, "malformed address \"%s\"", arg
);
131 const char *filter
= NULL
;
132 static void cb_filter(const char *arg
) { filter
= arg
; }
134 static void cb_local(const char *arg
) { acct_init_localnet(arg
); }
136 const char *chroot_dir
= NULL
;
137 static void cb_chroot(const char *arg
) { chroot_dir
= arg
; }
139 const char *base
= NULL
;
140 static void cb_base(const char *arg
) { base
= arg
; }
142 const char *privdrop_user
= NULL
;
143 static void cb_user(const char *arg
) { privdrop_user
= arg
; }
145 const char *daylog_fn
= NULL
;
146 static void cb_daylog(const char *arg
)
148 if (chroot_dir
== NULL
)
149 errx(1, "the daylog file is relative to the chroot.\n"
150 "You must specify a --chroot dir before you can use --daylog.");
155 const char *import_fn
= NULL
;
156 static void cb_import(const char *arg
)
158 if (chroot_dir
== NULL
)
159 errx(1, "the import file is relative to the chroot.\n"
160 "You must specify a --chroot dir before you can use --import.");
165 const char *export_fn
= NULL
;
166 static void cb_export(const char *arg
)
168 if ((chroot_dir
== NULL
) && (capfile
== NULL
))
169 errx(1, "the export file is relative to the chroot.\n"
170 "You must specify a --chroot dir before you can use --export.");
175 static const char *pid_fn
= NULL
;
176 static void cb_pidfile(const char *arg
)
178 if (chroot_dir
== NULL
)
179 errx(1, "the pidfile is relative to the chroot.\n"
180 "You must specify a --chroot dir before you can use --pidfile.");
185 unsigned int hosts_max
= 1000;
186 static void cb_hosts_max(const char *arg
)
187 { hosts_max
= parsenum(arg
, 0); }
189 unsigned int hosts_keep
= 500;
190 static void cb_hosts_keep(const char *arg
)
191 { hosts_keep
= parsenum(arg
, 0); }
193 unsigned int ports_max
= 200;
194 static void cb_ports_max(const char *arg
)
195 { ports_max
= parsenum(arg
, 65536); }
197 unsigned int ports_keep
= 30;
198 static void cb_ports_keep(const char *arg
)
199 { ports_keep
= parsenum(arg
, 65536); }
201 unsigned int highest_port
= 65535;
202 static void cb_highest_port(const char *arg
)
203 { highest_port
= parsenum(arg
, 65535); }
206 static void cb_wait_secs(const char *arg
)
207 { wait_secs
= (int)parsenum(arg
, 0); }
209 int want_hexdump
= 0;
210 static void cb_hexdump(const char *arg _unused_
) { want_hexdump
= 1; }
215 const char *name
, *arg_name
; /* NULL arg_name means unary */
216 void (*callback
)(const char *arg
);
220 static struct cmdline_arg cmdline_args
[] = {
221 {"-i", "interface", cb_interface
, 0},
222 {"-r", "file", cb_capfile
, 0},
223 {"--snaplen", "bytes", cb_snaplen
, 0},
224 {"--pppoe", NULL
, cb_pppoe
, 0},
225 {"--syslog", NULL
, cb_syslog
, 0},
226 {"--verbose", NULL
, cb_verbose
, 0},
227 {"--no-daemon", NULL
, cb_no_daemon
, 0},
228 {"--no-promisc", NULL
, cb_no_promisc
, 0},
229 {"--no-dns", NULL
, cb_no_dns
, 0},
230 {"--no-macs", NULL
, cb_no_macs
, 0},
231 {"--no-lastseen", NULL
, cb_no_lastseen
, 0},
232 {"-p", "port", cb_port
, 0},
233 {"-b", "bindaddr", cb_bindaddr
, 0},
234 {"--base", "path", cb_base
, 0},
235 {"-f", "filter", cb_filter
, 0},
236 {"-l", "network/netmask", cb_local
, 0},
237 {"--chroot", "dir", cb_chroot
, 0},
238 {"--user", "username", cb_user
, 0},
239 {"--daylog", "filename", cb_daylog
, 0},
240 {"--import", "filename", cb_import
, 0},
241 {"--export", "filename", cb_export
, 0},
242 {"--pidfile", "filename", cb_pidfile
, 0},
243 {"--hosts-max", "count", cb_hosts_max
, 0},
244 {"--hosts-keep", "count", cb_hosts_keep
, 0},
245 {"--ports-max", "count", cb_ports_max
, 0},
246 {"--ports-keep", "count", cb_ports_keep
, 0},
247 {"--highest-port", "port", cb_highest_port
, 0},
248 {"--wait", "secs", cb_wait_secs
, 0},
249 {"--hexdump", NULL
, cb_hexdump
, 0},
250 {NULL
, NULL
, NULL
, 0}
257 for (i
=0; i
<width
; i
++) printf(" ");
261 * We autogenerate the usage statement from the cmdline_args data structure.
267 struct cmdline_arg
*arg
;
269 printf(PACKAGE_STRING
" (built with libpcap %d.%d)\n\n",
270 PCAP_VERSION_MAJOR
, PCAP_VERSION_MINOR
);
272 width
= printf("usage: darkstat ");
275 for (arg
= cmdline_args
; arg
->name
!= NULL
; arg
++) {
276 if (first
) first
= 0; else pad(width
);
277 printf("[ %s", arg
->name
);
278 if (arg
->arg_name
!= NULL
) printf(" %s", arg
->arg_name
);
282 "Please refer to the darkstat(8) manual page for further\n"
283 "documentation and usage examples.\n");
287 parse_sub_cmdline(const int argc
, char * const *argv
)
289 struct cmdline_arg
*arg
;
291 if (argc
== 0) return;
292 for (arg
= cmdline_args
; arg
->name
!= NULL
; arg
++)
293 if (strcmp(argv
[0], arg
->name
) == 0) {
294 if ((arg
->arg_name
!= NULL
) && (argc
== 1)) {
296 "error: argument \"%s\" requires parameter \"%s\"\n",
297 arg
->name
, arg
->arg_name
);
301 if (arg
->num_seen
> 0) {
303 "error: already specified argument \"%s\"\n",
310 if (arg
->arg_name
== NULL
) {
312 parse_sub_cmdline(argc
-1, argv
+1);
314 arg
->callback(argv
[1]);
315 parse_sub_cmdline(argc
-2, argv
+2);
320 fprintf(stderr
, "error: illegal argument: \"%s\"\n", argv
[0]);
326 parse_cmdline(const int argc
, char * const *argv
)
329 /* Not enough args. */
334 parse_sub_cmdline(argc
, argv
);
336 /* start syslogging as early as possible */
337 if (want_syslog
) openlog("darkstat", LOG_NDELAY
| LOG_PID
, LOG_DAEMON
);
339 /* some default values */
340 if (chroot_dir
== NULL
) chroot_dir
= CHROOT_DIR
;
341 if (privdrop_user
== NULL
) privdrop_user
= PRIVDROP_USER
;
343 /* sanity check args */
344 if ((interface
== NULL
) && (capfile
== NULL
))
345 errx(1, "must specify either interface (-i) or capture file (-r)");
347 if ((interface
!= NULL
) && (capfile
!= NULL
))
348 errx(1, "can't specify both interface (-i) and capture file (-r)");
350 if ((hosts_max
!= 0) && (hosts_keep
>= hosts_max
)) {
351 hosts_keep
= hosts_max
/ 2;
352 warnx("reducing --hosts-keep to %u, to be under --hosts-max (%u)",
353 hosts_keep
, hosts_max
);
355 verbosef("max %u hosts, cutting down to %u when exceeded",
356 hosts_max
, hosts_keep
);
358 if ((ports_max
!= 0) && (ports_keep
>= ports_max
)) {
359 ports_keep
= ports_max
/ 2;
360 warnx("reducing --ports-keep to %u, to be under --ports-max (%u)",
361 ports_keep
, ports_max
);
363 verbosef("max %u ports per host, cutting down to %u when exceeded",
364 ports_max
, ports_keep
);
366 if (want_hexdump
&& !want_verbose
) {
368 verbosef("--hexdump implies --verbose");
371 if (want_hexdump
&& want_daemonize
) {
373 verbosef("--hexdump implies --no-daemon");
378 run_from_capfile(void)
382 cap_from_file(capfile
, filter
);
384 if (export_fn
!= NULL
) db_export(export_fn
);
387 verbosef("Total packets: %qu, bytes: %qu", total_packets
, total_bytes
);
390 /* --- Program body --- */
392 main(int argc
, char **argv
)
395 parse_cmdline(argc
-1, argv
+1);
399 * This is very different from a regular run against a network
406 /* must verbosef() before first fork to init lock */
407 verbosef("starting up");
408 if (pid_fn
) pidfile_create(chroot_dir
, pid_fn
, privdrop_user
);
410 if (want_daemonize
) {
411 verbosef("daemonizing to run in the background!");
413 verbosef("I am the main process");
415 if (pid_fn
) pidfile_write_close();
417 /* do this first as it forks - minimize memory use */
418 if (want_dns
) dns_init(privdrop_user
);
419 cap_init(interface
, filter
, want_promisc
); /* needs root */
420 http_init(base
, bindaddr
, bindport
, /*maxconn=*/-1);
421 ncache_init(); /* must do before chroot() */
423 privdrop(chroot_dir
, privdrop_user
);
425 /* Don't need root privs for these: */
427 if (daylog_fn
!= NULL
) daylog_init(daylog_fn
);
430 if (import_fn
!= NULL
) db_import(import_fn
);
431 localip_init(interface
);
433 if (signal(SIGTERM
, sig_shutdown
) == SIG_ERR
)
434 errx(1, "signal(SIGTERM) failed");
435 if (signal(SIGINT
, sig_shutdown
) == SIG_ERR
)
436 errx(1, "signal(SIGINT) failed");
437 if (signal(SIGUSR1
, sig_reset
) == SIG_ERR
)
438 errx(1, "signal(SIGUSR1) failed");
439 if (signal(SIGUSR2
, sig_export
) == SIG_ERR
)
440 errx(1, "signal(SIGUSR2) failed");
442 verbosef("entering main loop");
446 int select_ret
, max_fd
= -1, use_timeout
= 0;
447 struct timeval timeout
;
452 if (export_pending
) {
453 if (export_fn
!= NULL
)
454 db_export(export_fn
);
467 cap_fd_set(&rs
, &max_fd
, &timeout
, &use_timeout
);
468 http_fd_set(&rs
, &ws
, &max_fd
, &timeout
, &use_timeout
);
470 select_ret
= select(max_fd
+1, &rs
, &ws
, NULL
,
471 (use_timeout
) ? &timeout
: NULL
);
473 if ((select_ret
== 0) && (!use_timeout
))
474 errx(1, "select() erroneously timed out");
476 if (select_ret
== -1) {
490 verbosef("shutting down");
491 verbosef("pcap stats: %u packets received, %u packets dropped",
492 pkts_recv
, pkts_drop
);
496 if (export_fn
!= NULL
) db_export(export_fn
);
499 if (daylog_fn
!= NULL
) daylog_free();
501 if (pid_fn
) pidfile_unlink();
502 verbosef("shut down");
503 return (EXIT_SUCCESS
);
506 /* vim:set ts=3 sw=3 tw=78 expandtab: */