mirror of
				https://github.com/telekom-security/tpotce.git
				synced 2025-10-31 04:22:52 +00:00 
			
		
		
		
	
		
			
	
	
		
			1290 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			1290 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |    p0f - main entry point and all the pcap / unix socket innards | ||
|  |    ------------------------------------------------------------- | ||
|  | 
 | ||
|  |    Copyright (C) 2012 by Michal Zalewski <lcamtuf@coredump.cx> | ||
|  | 
 | ||
|  |    Distributed under the terms and conditions of GNU LGPL. | ||
|  | 
 | ||
|  |  */ | ||
|  | 
 | ||
|  | #define _GNU_SOURCE
 | ||
|  | #define _FROM_P0F
 | ||
|  | 
 | ||
|  | #include <stdio.h>
 | ||
|  | #include <stdlib.h>
 | ||
|  | #include <unistd.h>
 | ||
|  | #include <signal.h>
 | ||
|  | #include <getopt.h>
 | ||
|  | #include <errno.h>
 | ||
|  | #include <dirent.h>
 | ||
|  | #include <pwd.h>
 | ||
|  | #include <grp.h>
 | ||
|  | #include <poll.h>
 | ||
|  | #include <time.h>
 | ||
|  | #include <locale.h>
 | ||
|  | #include <jansson.h>
 | ||
|  | 
 | ||
|  | #include <sys/types.h>
 | ||
|  | #include <sys/socket.h>
 | ||
|  | #include <arpa/inet.h>
 | ||
|  | #include <sys/un.h>
 | ||
|  | #include <sys/fcntl.h>
 | ||
|  | #include <sys/stat.h>
 | ||
|  | #include <sys/file.h>
 | ||
|  | #include <sys/wait.h>
 | ||
|  | #include <netinet/in.h>
 | ||
|  | 
 | ||
|  | #include <pcap.h>
 | ||
|  | 
 | ||
|  | #ifdef NET_BPF
 | ||
|  | #  include <net/bpf.h>
 | ||
|  | #else
 | ||
|  | #  include <pcap-bpf.h>
 | ||
|  | #endif /* !NET_BPF */
 | ||
|  | 
 | ||
|  | #include "types.h"
 | ||
|  | #include "debug.h"
 | ||
|  | #include "alloc-inl.h"
 | ||
|  | #include "process.h"
 | ||
|  | #include "readfp.h"
 | ||
|  | #include "api.h"
 | ||
|  | #include "tcp.h"
 | ||
|  | #include "fp_http.h"
 | ||
|  | #include "p0f.h"
 | ||
|  | 
 | ||
|  | #ifndef PF_INET6
 | ||
|  | #  define PF_INET6          10
 | ||
|  | #endif /* !PF_INET6 */
 | ||
|  | 
 | ||
|  | #ifndef O_NOFOLLOW
 | ||
|  | #  define O_NOFOLLOW 0
 | ||
|  | #endif /* !O_NOFOLLOW */
 | ||
|  | 
 | ||
|  | #ifndef O_LARGEFILE
 | ||
|  | #  define O_LARGEFILE 0
 | ||
|  | #endif /* !O_LARGEFILE */
 | ||
|  | 
 | ||
|  | static json_t *json_record = NULL; | ||
|  | 
 | ||
|  | static u8 *use_iface,                   /* Interface to listen on             */ | ||
|  |           *orig_rule,                   /* Original filter rule               */ | ||
|  |           *switch_user,                 /* Target username                    */ | ||
|  |           *log_file,                    /* Binary log file name               */ | ||
|  |           *api_sock,                    /* API socket file name               */ | ||
|  |           *fp_file;                     /* Location of p0f.fp                 */ | ||
|  | 
 | ||
|  | u8* read_file;                          /* File to read pcap data from        */ | ||
|  | 
 | ||
|  | static u32 | ||
|  |   api_max_conn    = API_MAX_CONN;       /* Maximum number of API connections  */ | ||
|  | 
 | ||
|  | u32 | ||
|  |   max_conn        = MAX_CONN,           /* Connection entry count limit       */ | ||
|  |   max_hosts       = MAX_HOSTS,          /* Host cache entry count limit       */ | ||
|  |   conn_max_age    = CONN_MAX_AGE,       /* Maximum age of a connection entry  */ | ||
|  |   host_idle_limit = HOST_IDLE_LIMIT;    /* Host cache idle timeout            */ | ||
|  | 
 | ||
|  | static struct api_client *api_cl;       /* Array with API client state        */ | ||
|  |            | ||
|  | static s32 null_fd = -1,                /* File descriptor of /dev/null       */ | ||
|  |            api_fd = -1;                 /* API socket descriptor              */ | ||
|  | 
 | ||
|  | static FILE* lf;                        /* Log file stream                    */ | ||
|  | 
 | ||
|  | static u8 stop_soon;                    /* Ctrl-C or so pressed?              */ | ||
|  | 
 | ||
|  | u8 daemon_mode;                         /* Running in daemon mode?            */ | ||
|  | u8 json_mode;                           /* Log in JSON?                       */ | ||
|  | u8 line_buffered_mode;                  /* Line Buffered Mode?                */ | ||
|  | 
 | ||
|  | static u8 set_promisc;                  /* Use promiscuous mode?              */ | ||
|  | 
 | ||
|  | static pcap_t *pt;                      /* PCAP capture thingy                */ | ||
|  | 
 | ||
|  | s32 link_type;                          /* PCAP link type                     */ | ||
|  | 
 | ||
|  | u32 hash_seed;                          /* Hash seed                          */ | ||
|  | 
 | ||
|  | static u8 obs_fields;                   /* No of pending observation fields   */ | ||
|  | 
 | ||
|  | /* Memory allocator data: */ | ||
|  | 
 | ||
|  | #ifdef DEBUG_BUILD
 | ||
|  | struct TRK_obj* TRK[ALLOC_BUCKETS]; | ||
|  | u32 TRK_cnt[ALLOC_BUCKETS]; | ||
|  | #endif /* DEBUG_BUILD */
 | ||
|  | 
 | ||
|  | #define LOGF(_x...) fprintf(lf, _x)
 | ||
|  | 
 | ||
|  | /* Display usage information */ | ||
|  | 
 | ||
|  | static void usage(void) { | ||
|  | 
 | ||
|  |   ERRORF( | ||
|  | 
 | ||
|  | "Usage: p0f [ ...options... ] [ 'filter rule' ]\n" | ||
|  | "\n" | ||
|  | "Network interface options:\n" | ||
|  | "\n" | ||
|  | "  -i iface  - listen on the specified network interface\n" | ||
|  | "  -r file   - read offline pcap data from a given file\n" | ||
|  | "  -p        - put the listening interface in promiscuous mode\n" | ||
|  | "  -L        - list all available interfaces\n" | ||
|  | "\n" | ||
|  | "Operating mode and output settings:\n" | ||
|  | "\n" | ||
|  | "  -f file   - read fingerprint database from 'file' (%s)\n" | ||
|  | "  -o file   - write information to the specified log file\n" | ||
|  | "  -j        - Log in JSON format.\n" | ||
|  | "  -l        - Line buffered mode for logging to output file.\n" | ||
|  | #ifndef __CYGWIN__
 | ||
|  | "  -s name   - answer to API queries at a named unix socket\n" | ||
|  | #endif /* !__CYGWIN__ */
 | ||
|  | "  -u user   - switch to the specified unprivileged account and chroot\n" | ||
|  | "  -d        - fork into background (requires -o or -s)\n" | ||
|  | "\n" | ||
|  | "Performance-related options:\n" | ||
|  | "\n" | ||
|  | #ifndef __CYGWIN__
 | ||
|  | "  -S limit  - limit number of parallel API connections (%u)\n" | ||
|  | #endif /* !__CYGWIN__ */
 | ||
|  | "  -t c,h    - set connection / host cache age limits (%us,%um)\n" | ||
|  | "  -m c,h    - cap the number of active connections / hosts (%u,%u)\n" | ||
|  | "\n" | ||
|  | "Optional filter expressions (man tcpdump) can be specified in the command\n" | ||
|  | "line to prevent p0f from looking at incidental network traffic.\n" | ||
|  | "\n" | ||
|  | "Problems? You can reach the author at <lcamtuf@coredump.cx>.\n", | ||
|  | 
 | ||
|  |     FP_FILE, | ||
|  | #ifndef __CYGWIN__
 | ||
|  |     API_MAX_CONN, | ||
|  | #endif /* !__CYGWIN__ */
 | ||
|  |     CONN_MAX_AGE, HOST_IDLE_LIMIT, MAX_CONN,  MAX_HOSTS); | ||
|  | 
 | ||
|  |   exit(1); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Obtain hash seed: */ | ||
|  | 
 | ||
|  | static void get_hash_seed(void) { | ||
|  | 
 | ||
|  |   s32 f = open("/dev/urandom", O_RDONLY); | ||
|  | 
 | ||
|  |   if (f < 0) PFATAL("Cannot open /dev/urandom for reading."); | ||
|  | 
 | ||
|  | #ifndef DEBUG_BUILD
 | ||
|  | 
 | ||
|  |   /* In debug versions, use a constant seed. */ | ||
|  | 
 | ||
|  |   if (read(f, &hash_seed, sizeof(hash_seed)) != sizeof(hash_seed)) | ||
|  |     FATAL("Cannot read data from /dev/urandom."); | ||
|  | 
 | ||
|  | #endif /* !DEBUG_BUILD */
 | ||
|  | 
 | ||
|  |   close(f); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Get rid of unnecessary file descriptors */ | ||
|  | 
 | ||
|  | static void close_spare_fds(void) { | ||
|  | 
 | ||
|  |   s32 i, closed = 0; | ||
|  |   DIR* d; | ||
|  |   struct dirent* de; | ||
|  | 
 | ||
|  |   d = opendir("/proc/self/fd"); | ||
|  | 
 | ||
|  |   if (!d) { | ||
|  |     /* Best we could do... */ | ||
|  |     for (i = 3; i < 256; i++)  | ||
|  |       if (!close(i)) closed++; | ||
|  |     return; | ||
|  |   } | ||
|  | 
 | ||
|  |   while ((de = readdir(d))) { | ||
|  |     i = atol(de->d_name); | ||
|  |     if (i > 2 && !close(i)) closed++; | ||
|  |   } | ||
|  | 
 | ||
|  |   closedir(d); | ||
|  | 
 | ||
|  |   if (closed) | ||
|  |     SAYF("[+] Closed %u file descriptor%s.\n", closed, closed == 1 ? "" : "s" ); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Create or open log file */ | ||
|  | 
 | ||
|  | static void open_log(void) { | ||
|  | 
 | ||
|  |   struct stat st; | ||
|  |   s32 log_fd; | ||
|  | 
 | ||
|  |   log_fd = open((char*)log_file, O_WRONLY | O_APPEND | O_NOFOLLOW | O_LARGEFILE); | ||
|  | 
 | ||
|  |   if (log_fd >= 0) { | ||
|  | 
 | ||
|  |     if (fstat(log_fd, &st)) PFATAL("fstat() on '%s' failed.", log_file); | ||
|  | 
 | ||
|  |     if (!S_ISREG(st.st_mode)) FATAL("'%s' is not a regular file.", log_file); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     if (errno != ENOENT) PFATAL("Cannot open '%s'.", log_file); | ||
|  | 
 | ||
|  |     log_fd = open((char*)log_file, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, | ||
|  |                   LOG_MODE); | ||
|  | 
 | ||
|  |     if (log_fd < 0) PFATAL("Cannot open '%s'.", log_file); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (flock(log_fd, LOCK_EX | LOCK_NB)) | ||
|  |     FATAL("'%s' is being used by another process.", log_file); | ||
|  | 
 | ||
|  |   lf = fdopen(log_fd, "a"); | ||
|  | 
 | ||
|  |   if (!lf) FATAL("fdopen() on '%s' failed.", log_file); | ||
|  | 
 | ||
|  |   SAYF("[+] Log file '%s' opened for writing.\n", log_file); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Create and start listening on API socket */ | ||
|  | 
 | ||
|  | static void open_api(void) { | ||
|  | 
 | ||
|  |   s32 old_umask; | ||
|  |   u32 i; | ||
|  | 
 | ||
|  |   struct sockaddr_un u; | ||
|  |   struct stat st; | ||
|  | 
 | ||
|  |   api_fd = socket(PF_UNIX, SOCK_STREAM, 0); | ||
|  | 
 | ||
|  |   if (api_fd < 0) PFATAL("socket(PF_UNIX) failed."); | ||
|  | 
 | ||
|  |   memset(&u, 0, sizeof(u)); | ||
|  |   u.sun_family = AF_UNIX; | ||
|  | 
 | ||
|  |   if (strlen((char*)api_sock) >= sizeof(u.sun_path)) | ||
|  |     FATAL("API socket filename is too long for sockaddr_un (blame Unix)."); | ||
|  | 
 | ||
|  |   strcpy(u.sun_path, (char*)api_sock); | ||
|  | 
 | ||
|  |   /* This is bad, but you can't do any better with standard unix socket
 | ||
|  |      semantics today :-( */ | ||
|  | 
 | ||
|  |   if (!stat((char*)api_sock, &st) && !S_ISSOCK(st.st_mode)) | ||
|  |     FATAL("'%s' exists but is not a socket.", api_sock); | ||
|  | 
 | ||
|  |   if (unlink((char*)api_sock) && errno != ENOENT) | ||
|  |     PFATAL("unlink('%s') failed.", api_sock); | ||
|  | 
 | ||
|  |   old_umask = umask(0777 ^ API_MODE); | ||
|  | 
 | ||
|  |   if (bind(api_fd, (struct sockaddr*)&u, sizeof(u))) | ||
|  |     PFATAL("bind() on '%s' failed.", api_sock); | ||
|  |    | ||
|  |   umask(old_umask); | ||
|  | 
 | ||
|  |   if (listen(api_fd, api_max_conn)) | ||
|  |     PFATAL("listen() on '%s' failed.", api_sock); | ||
|  | 
 | ||
|  |   if (fcntl(api_fd, F_SETFL, O_NONBLOCK)) | ||
|  |     PFATAL("fcntl() to set O_NONBLOCK on API listen socket fails."); | ||
|  | 
 | ||
|  |   api_cl = DFL_ck_alloc(api_max_conn * sizeof(struct api_client)); | ||
|  | 
 | ||
|  |   for (i = 0; i < api_max_conn; i++) api_cl[i].fd = -1; | ||
|  | 
 | ||
|  |   SAYF("[+] Listening on API socket '%s' (max %u clients).\n", | ||
|  |        api_sock, api_max_conn); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Open log entry. */ | ||
|  | 
 | ||
|  | void start_observation(char* keyword, u8 field_cnt, u8 to_srv, | ||
|  |                        struct packet_flow* f) { | ||
|  | 
 | ||
|  |   if (obs_fields) FATAL("Premature end of observation."); | ||
|  | 
 | ||
|  |   if (!daemon_mode) { | ||
|  | 
 | ||
|  |     SAYF(".-[ %s/%u -> ", addr_to_str(f->client->addr, f->client->ip_ver), | ||
|  |          f->cli_port); | ||
|  |     SAYF("%s/%u (%s) ]-\n|\n", addr_to_str(f->server->addr, f->client->ip_ver), | ||
|  |          f->srv_port, keyword); | ||
|  | 
 | ||
|  |     SAYF("| %-8s = %s/%u\n", to_srv ? "client" : "server",  | ||
|  |          addr_to_str(to_srv ? f->client->addr : | ||
|  |          f->server->addr, f->client->ip_ver), | ||
|  |          to_srv ? f->cli_port : f->srv_port); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (log_file) { | ||
|  | 
 | ||
|  |     u8 tmp[64]; | ||
|  | 
 | ||
|  |     time_t ut = get_unix_time(); | ||
|  |     struct tm* lt = localtime(&ut); | ||
|  | 
 | ||
|  |     strftime((char*)tmp, 64, "%Y/%m/%d %H:%M:%S", lt); | ||
|  | 
 | ||
|  |     if (json_mode) { | ||
|  |         json_record = json_object(); | ||
|  |         json_object_set_new(json_record, "timestamp", json_string((char *)tmp)); | ||
|  |         json_object_set_new(json_record, "mod", json_string((char *)keyword)); | ||
|  |         json_object_set_new(json_record, "client_ip", json_string((char *)addr_to_str(f->client->addr, f->client->ip_ver))); | ||
|  |         json_object_set_new(json_record, "server_ip", json_string((char *)addr_to_str(f->server->addr, f->server->ip_ver))); | ||
|  |         json_object_set_new(json_record, "client_port", json_integer(f->cli_port)); | ||
|  |         json_object_set_new(json_record, "server_port", json_integer(f->srv_port)); | ||
|  |         json_object_set_new(json_record, "subject", json_string((char *)(to_srv ? "cli" : "srv"))); | ||
|  |     } else { | ||
|  |       LOGF("[%s] mod=%s|cli=%s/%u|",tmp, keyword, addr_to_str(f->client->addr, | ||
|  |          f->client->ip_ver), f->cli_port); | ||
|  | 
 | ||
|  |       LOGF("srv=%s/%u|subj=%s", addr_to_str(f->server->addr, f->server->ip_ver), | ||
|  |          f->srv_port, to_srv ? "cli" : "srv"); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   obs_fields = field_cnt; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Add log item. */ | ||
|  | 
 | ||
|  | void add_observation_field(char* key, u8* value) { | ||
|  | 
 | ||
|  |   if (!obs_fields) FATAL("Unexpected observation field ('%s').", key); | ||
|  | 
 | ||
|  |   if (!daemon_mode) | ||
|  |     SAYF("| %-8s = %s\n", key, value ? value : (u8*)"???"); | ||
|  | 
 | ||
|  |   if (log_file) { | ||
|  |     if (json_mode) { | ||
|  |       json_object_set_new(json_record, key, json_string( (char *)(value ? value : (u8*)"???") )); | ||
|  |     } else { | ||
|  |       LOGF("|%s=%s", key, value ? value : (u8*)"???"); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   obs_fields--; | ||
|  | 
 | ||
|  |   if (!obs_fields) { | ||
|  | 
 | ||
|  |     if (!daemon_mode) SAYF("|\n`----\n\n"); | ||
|  | 
 | ||
|  |     if (log_file){ | ||
|  | 
 | ||
|  |       if (json_mode) { | ||
|  |         json_dumpf(json_record, lf, 0); | ||
|  |         json_decref(json_record); | ||
|  |       } | ||
|  | 
 | ||
|  |       LOGF("\n"); | ||
|  |       if (line_buffered_mode) { | ||
|  |         fflush(lf); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Show PCAP interface list */ | ||
|  | 
 | ||
|  | static void list_interfaces(void) { | ||
|  | 
 | ||
|  |   char pcap_err[PCAP_ERRBUF_SIZE]; | ||
|  |   pcap_if_t *dev; | ||
|  |   u8 i = 0; | ||
|  | 
 | ||
|  |   /* There is a bug in several years' worth of libpcap releases that causes it
 | ||
|  |      to SEGV here if /sys/class/net is not readable. See http://goo.gl/nEnGx */
 | ||
|  | 
 | ||
|  |   if (access("/sys/class/net", R_OK | X_OK) && errno != ENOENT) | ||
|  |     FATAL("This operation requires access to /sys/class/net/, sorry."); | ||
|  | 
 | ||
|  |   if (pcap_findalldevs(&dev, pcap_err) == -1) | ||
|  |     FATAL("pcap_findalldevs: %s\n", pcap_err); | ||
|  | 
 | ||
|  |   if (!dev) FATAL("Can't find any interfaces. Maybe you need to be root?"); | ||
|  | 
 | ||
|  |   SAYF("\n-- Available interfaces --\n"); | ||
|  | 
 | ||
|  |   do { | ||
|  | 
 | ||
|  |     pcap_addr_t *a = dev->addresses; | ||
|  | 
 | ||
|  |     SAYF("\n%3d: Name        : %s\n", i++, dev->name); | ||
|  |     SAYF("     Description : %s\n", dev->description ? dev->description : "-"); | ||
|  | 
 | ||
|  |     /* Let's try to find something we can actually display. */ | ||
|  | 
 | ||
|  |     while (a && a->addr->sa_family != PF_INET && a->addr->sa_family != PF_INET6) | ||
|  |       a = a->next; | ||
|  | 
 | ||
|  |     if (a) { | ||
|  | 
 | ||
|  |       if (a->addr->sa_family == PF_INET) | ||
|  |         SAYF("     IP address  : %s\n", addr_to_str(((u8*)a->addr) + 4, IP_VER4)); | ||
|  |       else | ||
|  |         SAYF("     IP address  : %s\n", addr_to_str(((u8*)a->addr) + 8, IP_VER6)); | ||
|  | 
 | ||
|  |      } else SAYF("     IP address  : (none)\n"); | ||
|  | 
 | ||
|  |   } while ((dev = dev->next)); | ||
|  | 
 | ||
|  |   SAYF("\n"); | ||
|  | 
 | ||
|  |   pcap_freealldevs(dev); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #ifdef __CYGWIN__
 | ||
|  | 
 | ||
|  | /* List PCAP-recognized interfaces */ | ||
|  | 
 | ||
|  | static u8* find_interface(int num) { | ||
|  | 
 | ||
|  |   char pcap_err[PCAP_ERRBUF_SIZE]; | ||
|  |   pcap_if_t *dev; | ||
|  | 
 | ||
|  |   if (pcap_findalldevs(&dev, pcap_err) == -1) | ||
|  |     FATAL("pcap_findalldevs: %s\n", pcap_err); | ||
|  | 
 | ||
|  |   do { | ||
|  | 
 | ||
|  |     if (!num--) { | ||
|  |       u8* ret = DFL_ck_strdup((char*)dev->name); | ||
|  |       pcap_freealldevs(dev); | ||
|  |       return ret; | ||
|  |     } | ||
|  | 
 | ||
|  |   } while ((dev = dev->next)); | ||
|  | 
 | ||
|  |   FATAL("Interface not found (use -L to list all)."); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | #endif /* __CYGWIN__ */
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Initialize PCAP capture */ | ||
|  | 
 | ||
|  | static void prepare_pcap(void) { | ||
|  | 
 | ||
|  |   char pcap_err[PCAP_ERRBUF_SIZE]; | ||
|  |   u8* orig_iface = use_iface; | ||
|  | 
 | ||
|  |   if (read_file) { | ||
|  | 
 | ||
|  |     if (set_promisc) | ||
|  |       FATAL("Dude, how am I supposed to make a file promiscuous?"); | ||
|  | 
 | ||
|  |     if (use_iface) | ||
|  |       FATAL("Options -i and -r are mutually exclusive."); | ||
|  | 
 | ||
|  |     if (access((char*)read_file, R_OK)) | ||
|  |       PFATAL("Can't access file '%s'.", read_file); | ||
|  | 
 | ||
|  |     pt = pcap_open_offline((char*)read_file, pcap_err); | ||
|  | 
 | ||
|  |     if (!pt) FATAL("pcap_open_offline: %s", pcap_err); | ||
|  | 
 | ||
|  |     SAYF("[+] Will read pcap data from file '%s'.\n", read_file); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     if (!use_iface) { | ||
|  | 
 | ||
|  |       /* See the earlier note on libpcap SEGV - same problem here.
 | ||
|  |          Also, this returns something stupid on Windows, but hey... */ | ||
|  |       | ||
|  |       if (!access("/sys/class/net", R_OK | X_OK) || errno == ENOENT) | ||
|  |         use_iface = (u8*)pcap_lookupdev(pcap_err); | ||
|  | 
 | ||
|  |       if (!use_iface) | ||
|  |         FATAL("libpcap is out of ideas; use -i to specify interface."); | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  | #ifdef __CYGWIN__
 | ||
|  | 
 | ||
|  |     /* On Windows, interface names are unwieldy, and people prefer to use
 | ||
|  |        numerical IDs. */ | ||
|  | 
 | ||
|  |     else { | ||
|  | 
 | ||
|  |       int iface_id; | ||
|  | 
 | ||
|  |       if (sscanf((char*)use_iface, "%u", &iface_id) == 1) { | ||
|  |         use_iface = find_interface(iface_id); | ||
|  |       } | ||
|  |    | ||
|  |     } | ||
|  | 
 | ||
|  |     pt = pcap_open_live((char*)use_iface, SNAPLEN, set_promisc, 250, pcap_err); | ||
|  | 
 | ||
|  | #else 
 | ||
|  | 
 | ||
|  |     /* PCAP timeouts tend to be broken, so we'll use a very small value
 | ||
|  |        and rely on select() instead. */ | ||
|  | 
 | ||
|  |     pt = pcap_open_live((char*)use_iface, SNAPLEN, set_promisc, 5, pcap_err); | ||
|  | 
 | ||
|  | #endif /* ^__CYGWIN__ */
 | ||
|  | 
 | ||
|  |     if (!orig_iface) | ||
|  |       SAYF("[+] Intercepting traffic on default interface '%s'.\n", use_iface); | ||
|  |     else | ||
|  |       SAYF("[+] Intercepting traffic on interface '%s'.\n", use_iface); | ||
|  | 
 | ||
|  |     if (!pt) FATAL("pcap_open_live: %s", pcap_err); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   link_type = pcap_datalink(pt); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Initialize BPF filtering */ | ||
|  | 
 | ||
|  | static void prepare_bpf(void) { | ||
|  | 
 | ||
|  |   struct bpf_program flt; | ||
|  | 
 | ||
|  |   u8*  final_rule; | ||
|  |   u8   vlan_support; | ||
|  | 
 | ||
|  |   /* VLAN matching is somewhat brain-dead: you need to request it explicitly,
 | ||
|  |      and it alters the semantics of the remainder of the expression. */ | ||
|  | 
 | ||
|  |   vlan_support = (pcap_datalink(pt) == DLT_EN10MB); | ||
|  | 
 | ||
|  | retry_no_vlan: | ||
|  | 
 | ||
|  |   if (!orig_rule) { | ||
|  | 
 | ||
|  |     if (vlan_support) { | ||
|  |       final_rule = (u8*)"tcp or (vlan and tcp)"; | ||
|  |     } else { | ||
|  |       final_rule = (u8*)"tcp"; | ||
|  |     } | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     if (vlan_support) { | ||
|  | 
 | ||
|  |       final_rule = ck_alloc(strlen((char*)orig_rule) * 2 + 64); | ||
|  | 
 | ||
|  |       sprintf((char*)final_rule, "(tcp and (%s)) or (vlan and tcp and (%s))", | ||
|  |               orig_rule, orig_rule); | ||
|  | 
 | ||
|  |     } else { | ||
|  | 
 | ||
|  |       final_rule = ck_alloc(strlen((char*)orig_rule) + 16); | ||
|  | 
 | ||
|  |       sprintf((char*)final_rule, "tcp and (%s)", orig_rule); | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   DEBUG("[#] Computed rule: %s\n", final_rule); | ||
|  | 
 | ||
|  |   if (pcap_compile(pt, &flt, (char*)final_rule, 1, 0)) { | ||
|  | 
 | ||
|  |     if (vlan_support) { | ||
|  | 
 | ||
|  |       if (orig_rule) ck_free(final_rule); | ||
|  |       vlan_support = 0; | ||
|  |       goto retry_no_vlan; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     pcap_perror(pt, "[-] pcap_compile"); | ||
|  | 
 | ||
|  |     if (!orig_rule) | ||
|  |       FATAL("pcap_compile() didn't work, strange"); | ||
|  |     else | ||
|  |       FATAL("Syntax error! See 'man tcpdump' for help on filters."); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (pcap_setfilter(pt, &flt)) | ||
|  |     FATAL("pcap_setfilter() didn't work, strange."); | ||
|  | 
 | ||
|  |   pcap_freecode(&flt); | ||
|  | 
 | ||
|  |   if (!orig_rule) { | ||
|  | 
 | ||
|  |     SAYF("[+] Default packet filtering configured%s.\n", | ||
|  |          vlan_support ? " [+VLAN]" : ""); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     SAYF("[+] Custom filtering rule enabled: %s%s\n", | ||
|  |          orig_rule ? orig_rule : (u8*)"tcp", | ||
|  |          vlan_support ? " [+VLAN]" : ""); | ||
|  | 
 | ||
|  |     ck_free(final_rule); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Drop privileges and chroot(), with some sanity checks */ | ||
|  | 
 | ||
|  | static void drop_privs(void) { | ||
|  | 
 | ||
|  |   struct passwd* pw; | ||
|  | 
 | ||
|  |   pw = getpwnam((char*)switch_user); | ||
|  | 
 | ||
|  |   if (!pw) FATAL("User '%s' not found.", switch_user); | ||
|  | 
 | ||
|  |   if (!strcmp(pw->pw_dir, "/")) | ||
|  |     FATAL("User '%s' must have a dedicated home directory.", switch_user); | ||
|  | 
 | ||
|  |   if (!pw->pw_uid || !pw->pw_gid) | ||
|  |     FATAL("User '%s' must be non-root.", switch_user); | ||
|  | 
 | ||
|  |   if (initgroups(pw->pw_name, pw->pw_gid)) | ||
|  |     PFATAL("initgroups() for '%s' failed.", switch_user); | ||
|  | 
 | ||
|  |   if (chdir(pw->pw_dir)) | ||
|  |     PFATAL("chdir('%s') failed.", pw->pw_dir); | ||
|  | 
 | ||
|  |   if (chroot(pw->pw_dir)) | ||
|  |     PFATAL("chroot('%s') failed.", pw->pw_dir); | ||
|  | 
 | ||
|  |   if (chdir("/")) | ||
|  |     PFATAL("chdir('/') after chroot('%s') failed.", pw->pw_dir); | ||
|  | 
 | ||
|  |   if (!access("/proc/", F_OK) || !access("/sys/", F_OK)) | ||
|  |     FATAL("User '%s' must have a dedicated home directory.", switch_user); | ||
|  | 
 | ||
|  |   if (setgid(pw->pw_gid)) | ||
|  |     PFATAL("setgid(%u) failed.", pw->pw_gid); | ||
|  | 
 | ||
|  |   if (setuid(pw->pw_uid)) | ||
|  |     PFATAL("setuid(%u) failed.", pw->pw_uid); | ||
|  | 
 | ||
|  |   if (getegid() != pw->pw_gid || geteuid() != pw->pw_uid) | ||
|  |     FATAL("Inconsistent euid / egid after dropping privs."); | ||
|  | 
 | ||
|  |   SAYF("[+] Privileges dropped: uid %u, gid %u, root '%s'.\n", | ||
|  |        pw->pw_uid, pw->pw_gid, pw->pw_dir); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Enter daemon mode. */ | ||
|  | 
 | ||
|  | static void fork_off(void) { | ||
|  | 
 | ||
|  |   s32 npid; | ||
|  | 
 | ||
|  |   fflush(0); | ||
|  | 
 | ||
|  |   npid = fork(); | ||
|  | 
 | ||
|  |   if (npid < 0) PFATAL("fork() failed."); | ||
|  | 
 | ||
|  |   if (!npid) { | ||
|  | 
 | ||
|  |     /* Let's assume all this is fairly unlikely to fail, so we can live
 | ||
|  |        with the parent possibly proclaiming success prematurely. */ | ||
|  | 
 | ||
|  |     if (dup2(null_fd, 0) < 0) PFATAL("dup2() failed."); | ||
|  | 
 | ||
|  |     /* If stderr is redirected to a file, keep that fd and use it for
 | ||
|  |        normal output. */ | ||
|  | 
 | ||
|  |     if (isatty(2)) { | ||
|  | 
 | ||
|  |       if (dup2(null_fd, 1) < 0 || dup2(null_fd, 2) < 0) | ||
|  |         PFATAL("dup2() failed."); | ||
|  | 
 | ||
|  |     } else { | ||
|  | 
 | ||
|  |       if (dup2(2, 1) < 0) PFATAL("dup2() failed."); | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     close(null_fd); | ||
|  |     null_fd = -1; | ||
|  | 
 | ||
|  |     if (chdir("/")) PFATAL("chdir('/') failed."); | ||
|  | 
 | ||
|  |     setsid(); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     SAYF("[+] Daemon process created, PID %u (stderr %s).\n", npid, | ||
|  |       isatty(2) ? "not kept" : "kept as-is"); | ||
|  | 
 | ||
|  |     SAYF("\nGood luck, you're on your own now!\n"); | ||
|  | 
 | ||
|  |     exit(0); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Handler for Ctrl-C and related signals */ | ||
|  | 
 | ||
|  | static void abort_handler(int sig) { | ||
|  |   if (stop_soon) exit(1); | ||
|  |   stop_soon = 1; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | #ifndef __CYGWIN__
 | ||
|  | 
 | ||
|  | /* Regenerate pollfd data for poll() */ | ||
|  | 
 | ||
|  | static u32 regen_pfds(struct pollfd* pfds, struct api_client** ctable) { | ||
|  |   u32 i, count = 2; | ||
|  | 
 | ||
|  |   pfds[0].fd     = pcap_fileno(pt); | ||
|  |   pfds[0].events = (POLLIN | POLLERR | POLLHUP); | ||
|  | 
 | ||
|  |   DEBUG("[#] Recomputing pollfd data, pcap_fd = %d.\n", pfds[0].fd); | ||
|  | 
 | ||
|  |   if (!api_sock) return 1; | ||
|  | 
 | ||
|  |   pfds[1].fd     = api_fd; | ||
|  |   pfds[1].events = (POLLIN | POLLERR | POLLHUP); | ||
|  | 
 | ||
|  |   for (i = 0; i < api_max_conn; i++) { | ||
|  | 
 | ||
|  |     if (api_cl[i].fd == -1) continue; | ||
|  | 
 | ||
|  |     ctable[count] = api_cl + i; | ||
|  | 
 | ||
|  |     /* If we haven't received a complete query yet, wait for POLLIN.
 | ||
|  |        Otherwise, we want to write stuff. */ | ||
|  | 
 | ||
|  |     if (api_cl[i].in_off < sizeof(struct p0f_api_query)) | ||
|  |       pfds[count].events = (POLLIN | POLLERR | POLLHUP); | ||
|  |     else | ||
|  |       pfds[count].events = (POLLOUT | POLLERR | POLLHUP); | ||
|  | 
 | ||
|  |     pfds[count++].fd   = api_cl[i].fd; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   return count; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | #endif /* !__CYGWIN__ */
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Event loop! Accepts and dispatches pcap data, API queries, etc. */ | ||
|  | 
 | ||
|  | static void live_event_loop(void) { | ||
|  | 
 | ||
|  | #ifndef __CYGWIN__
 | ||
|  | 
 | ||
|  |   /* The huge problem with winpcap on cygwin is that you can't get a file
 | ||
|  |      descriptor suitable for poll() / select() out of it: | ||
|  | 
 | ||
|  |      http://www.winpcap.org/pipermail/winpcap-users/2009-April/003179.html
 | ||
|  | 
 | ||
|  |      The only alternatives seem to be additional processes / threads, a | ||
|  |      nasty busy loop, or a ton of Windows-specific code. If you need APi | ||
|  |      queries on Windows, you are welcome to fix this :-) */ | ||
|  | 
 | ||
|  |   struct pollfd *pfds; | ||
|  |   struct api_client** ctable; | ||
|  |   u32 pfd_count; | ||
|  | 
 | ||
|  |   /* We need room for pcap, and possibly api_fd + api_clients. */ | ||
|  | 
 | ||
|  |   pfds = ck_alloc((1 + (api_sock ? (1 + api_max_conn) : 0)) * | ||
|  |                   sizeof(struct pollfd)); | ||
|  | 
 | ||
|  |   ctable = ck_alloc((1 + (api_sock ? (1 + api_max_conn) : 0)) * | ||
|  |                     sizeof(struct api_client*)); | ||
|  | 
 | ||
|  |   pfd_count = regen_pfds(pfds, ctable); | ||
|  | 
 | ||
|  |   if (!daemon_mode)  | ||
|  |     SAYF("[+] Entered main event loop.\n\n"); | ||
|  | 
 | ||
|  |   while (!stop_soon) { | ||
|  | 
 | ||
|  |     s32 pret, i; | ||
|  |     u32 cur; | ||
|  | 
 | ||
|  |     /* We had a 250 ms timeout to keep Ctrl-C responsive without resortng
 | ||
|  |        to silly sigaction hackery or unsafe signal handler code. Unfortunately, | ||
|  |        if poll() timeout is much longer than pcap timeout, we end up with | ||
|  |        dropped packets on VMs. Seems like a kernel bug, but for now, this | ||
|  |        loop is a bit busier than it needs to be... */ | ||
|  | 
 | ||
|  | poll_again: | ||
|  | 
 | ||
|  |     pret = poll(pfds, pfd_count, 10); | ||
|  | 
 | ||
|  |     if (pret < 0) { | ||
|  |       if (errno == EINTR) break; | ||
|  |       PFATAL("poll() failed."); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!pret) { if (log_file) fflush(lf); continue; } | ||
|  | 
 | ||
|  |     /* Examine pfds... */ | ||
|  | 
 | ||
|  |     for (cur = 0; cur < pfd_count; cur++) { | ||
|  | 
 | ||
|  |       if (pfds[cur].revents & (POLLERR | POLLHUP)) switch (cur) { | ||
|  | 
 | ||
|  |         case 0: | ||
|  | 
 | ||
|  |           FATAL("Packet capture interface is down."); | ||
|  | 
 | ||
|  |         case 1: | ||
|  | 
 | ||
|  |           FATAL("API socket is down."); | ||
|  | 
 | ||
|  |         default: | ||
|  | 
 | ||
|  |           /* Shut down API connection and free its state. */ | ||
|  | 
 | ||
|  |           DEBUG("[#] API connection on fd %d closed.\n", pfds[cur].fd); | ||
|  | 
 | ||
|  |           close(pfds[cur].fd); | ||
|  |           ctable[cur]->fd = -1; | ||
|  |   | ||
|  |           pfd_count = regen_pfds(pfds, ctable); | ||
|  |           goto poll_again; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       if (pfds[cur].revents & POLLOUT) switch (cur) { | ||
|  | 
 | ||
|  |         case 0: case 1: | ||
|  | 
 | ||
|  |           FATAL("Unexpected POLLOUT on fd %d.\n", cur); | ||
|  | 
 | ||
|  |         default: | ||
|  | 
 | ||
|  |           /* Write API response, restart state when complete. */ | ||
|  | 
 | ||
|  |           if (ctable[cur]->in_off < sizeof(struct p0f_api_query)) | ||
|  |             FATAL("Inconsistent p0f_api_response state.\n"); | ||
|  | 
 | ||
|  |           i = write(pfds[cur].fd,  | ||
|  |                    ((char*)&ctable[cur]->out_data) + ctable[cur]->out_off, | ||
|  |                    sizeof(struct p0f_api_response) - ctable[cur]->out_off); | ||
|  | 
 | ||
|  |           if (i <= 0) PFATAL("write() on API socket fails despite POLLOUT."); | ||
|  | 
 | ||
|  |           ctable[cur]->out_off += i; | ||
|  | 
 | ||
|  |           /* All done? Back to square zero then! */ | ||
|  | 
 | ||
|  |           if (ctable[cur]->out_off == sizeof(struct p0f_api_response)) { | ||
|  | 
 | ||
|  |              ctable[cur]->in_off = ctable[cur]->out_off = 0; | ||
|  |              pfds[cur].events   = (POLLIN | POLLERR | POLLHUP); | ||
|  | 
 | ||
|  |           } | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       if (pfds[cur].revents & POLLIN) switch (cur) { | ||
|  |   | ||
|  |         case 0: | ||
|  | 
 | ||
|  |           /* Process traffic on the capture interface. */ | ||
|  | 
 | ||
|  |           if (pcap_dispatch(pt, -1, (pcap_handler)parse_packet, 0) < 0) | ||
|  |             FATAL("Packet capture interface is down."); | ||
|  | 
 | ||
|  |           break; | ||
|  | 
 | ||
|  |         case 1: | ||
|  | 
 | ||
|  |           /* Accept new API connection, limits permitting. */ | ||
|  | 
 | ||
|  |           if (!api_sock) FATAL("Unexpected API connection."); | ||
|  | 
 | ||
|  |           if (pfd_count - 2 < api_max_conn) { | ||
|  | 
 | ||
|  |             for (i = 0; i < api_max_conn && api_cl[i].fd >= 0; i++); | ||
|  | 
 | ||
|  |             if (i == api_max_conn) FATAL("Inconsistent API connection data."); | ||
|  | 
 | ||
|  |             api_cl[i].fd = accept(api_fd, NULL, NULL); | ||
|  | 
 | ||
|  |             if (api_cl[i].fd < 0) { | ||
|  | 
 | ||
|  |               WARN("Unable to handle API connection: accept() fails."); | ||
|  | 
 | ||
|  |             } else { | ||
|  | 
 | ||
|  |               if (fcntl(api_cl[i].fd, F_SETFL, O_NONBLOCK)) | ||
|  |                 PFATAL("fcntl() to set O_NONBLOCK on API connection fails."); | ||
|  | 
 | ||
|  |               api_cl[i].in_off = api_cl[i].out_off = 0; | ||
|  |               pfd_count = regen_pfds(pfds, ctable); | ||
|  | 
 | ||
|  |               DEBUG("[#] Accepted new API connection, fd %d.\n", api_cl[i].fd); | ||
|  | 
 | ||
|  |               goto poll_again; | ||
|  | 
 | ||
|  |             } | ||
|  | 
 | ||
|  |           } else WARN("Too many API connections (use -S to adjust).\n"); | ||
|  | 
 | ||
|  |           break; | ||
|  | 
 | ||
|  |         default: | ||
|  | 
 | ||
|  |           /* Receive API query, dispatch when complete. */ | ||
|  | 
 | ||
|  |           if (ctable[cur]->in_off >= sizeof(struct p0f_api_query)) | ||
|  |             FATAL("Inconsistent p0f_api_query state.\n"); | ||
|  | 
 | ||
|  |           i = read(pfds[cur].fd,  | ||
|  |                    ((char*)&ctable[cur]->in_data) + ctable[cur]->in_off, | ||
|  |                    sizeof(struct p0f_api_query) - ctable[cur]->in_off); | ||
|  | 
 | ||
|  |           if (i < 0) PFATAL("read() on API socket fails despite POLLIN."); | ||
|  | 
 | ||
|  |           ctable[cur]->in_off += i; | ||
|  | 
 | ||
|  |           /* Query in place? Compute response and prepare to send it back. */ | ||
|  | 
 | ||
|  |           if (ctable[cur]->in_off == sizeof(struct p0f_api_query)) { | ||
|  | 
 | ||
|  |             handle_query(&ctable[cur]->in_data, &ctable[cur]->out_data); | ||
|  |             pfds[cur].events = (POLLOUT | POLLERR | POLLHUP); | ||
|  | 
 | ||
|  |           } | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  | 
 | ||
|  |       /* Processed all reported updates already? If so, bail out early. */ | ||
|  | 
 | ||
|  |       if (pfds[cur].revents && !--pret) break; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   ck_free(ctable); | ||
|  |   ck_free(pfds); | ||
|  | 
 | ||
|  | #else
 | ||
|  | 
 | ||
|  |   if (!daemon_mode)  | ||
|  |     SAYF("[+] Entered main event loop.\n\n"); | ||
|  | 
 | ||
|  |   /* Ugh. The only way to keep SIGINT and other signals working is to have this
 | ||
|  |      funny loop with dummy I/O every 250 ms. Signal handlers don't get called | ||
|  |      in pcap_dispatch() or pcap_loop() unless there's I/O. */ | ||
|  | 
 | ||
|  |   while (!stop_soon) { | ||
|  | 
 | ||
|  |     s32 ret = pcap_dispatch(pt, -1, (pcap_handler)parse_packet, 0); | ||
|  | 
 | ||
|  |     if (ret < 0) return; | ||
|  | 
 | ||
|  |     if (log_file && !ret) fflush(lf); | ||
|  | 
 | ||
|  |     write(2, NULL, 0); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | #endif /* ^!__CYGWIN__ */
 | ||
|  | 
 | ||
|  |   WARN("User-initiated shutdown."); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Simple event loop for processing offline captures. */ | ||
|  | 
 | ||
|  | static void offline_event_loop(void) { | ||
|  | 
 | ||
|  |   if (!daemon_mode)  | ||
|  |     SAYF("[+] Processing capture data.\n\n"); | ||
|  | 
 | ||
|  |   while (!stop_soon)  { | ||
|  | 
 | ||
|  |     if (pcap_dispatch(pt, -1, (pcap_handler)parse_packet, 0) <= 0) return; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   WARN("User-initiated shutdown."); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Main entry point */ | ||
|  | 
 | ||
|  | int main(int argc, char** argv) { | ||
|  | 
 | ||
|  |   s32 r; | ||
|  | 
 | ||
|  |   setlinebuf(stdout); | ||
|  | 
 | ||
|  |   SAYF("--- p0f " VERSION " by Michal Zalewski <lcamtuf@coredump.cx> ---\n\n"); | ||
|  | 
 | ||
|  |   if (getuid() != geteuid()) | ||
|  |     FATAL("Please don't make me setuid. See README for more.\n"); | ||
|  | 
 | ||
|  |   while ((r = getopt(argc, argv, "+LS:djlf:i:m:o:pr:s:t:u:")) != -1) switch (r) { | ||
|  | 
 | ||
|  |     case 'L': | ||
|  | 
 | ||
|  |       list_interfaces(); | ||
|  |       exit(0); | ||
|  | 
 | ||
|  |     case 'S': | ||
|  | 
 | ||
|  | #ifdef __CYGWIN__
 | ||
|  | 
 | ||
|  |       FATAL("API mode not supported on Windows (see README)."); | ||
|  | 
 | ||
|  | #else
 | ||
|  | 
 | ||
|  |       if (api_max_conn != API_MAX_CONN) | ||
|  |         FATAL("Multiple -S options not supported."); | ||
|  | 
 | ||
|  |       api_max_conn = atol(optarg); | ||
|  | 
 | ||
|  |       if (!api_max_conn || api_max_conn > 100) | ||
|  |         FATAL("Outlandish value specified for -S."); | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  | #endif /* ^__CYGWIN__ */
 | ||
|  | 
 | ||
|  | 
 | ||
|  |     case 'd': | ||
|  | 
 | ||
|  |       if (daemon_mode) | ||
|  |         FATAL("Double werewolf mode not supported yet."); | ||
|  | 
 | ||
|  |       daemon_mode = 1; | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 'f': | ||
|  | 
 | ||
|  |       if (fp_file) | ||
|  |         FATAL("Multiple -f options not supported."); | ||
|  | 
 | ||
|  |       fp_file = (u8*)optarg; | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 'i': | ||
|  | 
 | ||
|  |       if (use_iface) | ||
|  |         FATAL("Multiple -i options not supported (try '-i any')."); | ||
|  | 
 | ||
|  |       use_iface = (u8*)optarg; | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 'j': | ||
|  | 
 | ||
|  |       if (json_mode) | ||
|  |         FATAL("Double werewolf mode not supported yet."); | ||
|  | 
 | ||
|  |       json_mode = 1; | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 'l': | ||
|  | 
 | ||
|  |       if (line_buffered_mode) | ||
|  |         FATAL("Double werewolf mode not supported yet."); | ||
|  | 
 | ||
|  |       line_buffered_mode = 1; | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 'm': | ||
|  | 
 | ||
|  |       if (max_conn != MAX_CONN || max_hosts != MAX_HOSTS) | ||
|  |         FATAL("Multiple -m options not supported."); | ||
|  | 
 | ||
|  |       if (sscanf(optarg, "%u,%u", &max_conn, &max_hosts) != 2 || | ||
|  |           !max_conn || max_conn > 100000 || | ||
|  |           !max_hosts || max_hosts > 500000) | ||
|  |         FATAL("Outlandish value specified for -m."); | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 'o': | ||
|  | 
 | ||
|  |       if (log_file) | ||
|  |         FATAL("Multiple -o options not supported."); | ||
|  | 
 | ||
|  |       log_file = (u8*)optarg; | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 'p': | ||
|  |      | ||
|  |       if (set_promisc) | ||
|  |         FATAL("Even more promiscuous? People will start talking!"); | ||
|  | 
 | ||
|  |       set_promisc = 1; | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 'r': | ||
|  | 
 | ||
|  |       if (read_file) | ||
|  |         FATAL("Multiple -r options not supported."); | ||
|  | 
 | ||
|  |       read_file = (u8*)optarg; | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 's': | ||
|  | 
 | ||
|  | #ifdef __CYGWIN__
 | ||
|  | 
 | ||
|  |       FATAL("API mode not supported on Windows (see README)."); | ||
|  | 
 | ||
|  | #else
 | ||
|  | 
 | ||
|  |       if (api_sock)  | ||
|  |         FATAL("Multiple -s options not supported."); | ||
|  | 
 | ||
|  |       api_sock = (u8*)optarg; | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  | #endif /* ^__CYGWIN__ */
 | ||
|  | 
 | ||
|  |     case 't': | ||
|  | 
 | ||
|  |       if (conn_max_age != CONN_MAX_AGE || host_idle_limit != HOST_IDLE_LIMIT) | ||
|  |         FATAL("Multiple -t options not supported."); | ||
|  | 
 | ||
|  |       if (sscanf(optarg, "%u,%u", &conn_max_age, &host_idle_limit) != 2 || | ||
|  |           !conn_max_age || conn_max_age > 1000000 || | ||
|  |           !host_idle_limit || host_idle_limit > 1000000) | ||
|  |         FATAL("Outlandish value specified for -t."); | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  |     case 'u': | ||
|  | 
 | ||
|  |       if (switch_user) | ||
|  |         FATAL("Split personality mode not supported."); | ||
|  | 
 | ||
|  |       switch_user = (u8*)optarg; | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  |     default: usage(); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (optind < argc) { | ||
|  | 
 | ||
|  |     if (optind + 1 == argc) orig_rule = (u8*)argv[optind]; | ||
|  |     else FATAL("Filter rule must be a single parameter (use quotes)."); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (read_file && api_sock) | ||
|  |     FATAL("API mode looks down on ofline captures."); | ||
|  | 
 | ||
|  |   if (!api_sock && api_max_conn != API_MAX_CONN) | ||
|  |     FATAL("Option -S makes sense only with -s."); | ||
|  | 
 | ||
|  |   if (daemon_mode) { | ||
|  | 
 | ||
|  |     if (read_file) | ||
|  |       FATAL("Daemon mode and offline captures don't mix."); | ||
|  | 
 | ||
|  |     if (!log_file && !api_sock) | ||
|  |       FATAL("Daemon mode requires -o or -s."); | ||
|  | 
 | ||
|  | #ifdef __CYGWIN__
 | ||
|  | 
 | ||
|  |     if (switch_user)  | ||
|  |       SAYF("[!] Note: under cygwin, -u is largely useless.\n"); | ||
|  | 
 | ||
|  | #else
 | ||
|  | 
 | ||
|  |     if (!switch_user)  | ||
|  |       SAYF("[!] Consider specifying -u in daemon mode (see README).\n"); | ||
|  | 
 | ||
|  | #endif /* ^__CYGWIN__ */
 | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   tzset(); | ||
|  |   setlocale(LC_TIME, "C"); | ||
|  | 
 | ||
|  |   close_spare_fds(); | ||
|  | 
 | ||
|  |   get_hash_seed(); | ||
|  | 
 | ||
|  |   http_init(); | ||
|  | 
 | ||
|  |   read_config(fp_file ? fp_file : (u8*)FP_FILE); | ||
|  | 
 | ||
|  |   prepare_pcap(); | ||
|  |   prepare_bpf(); | ||
|  | 
 | ||
|  |   if (log_file) open_log(); | ||
|  |   if (api_sock) open_api(); | ||
|  |    | ||
|  |   if (daemon_mode) { | ||
|  |     null_fd = open("/dev/null", O_RDONLY); | ||
|  |     if (null_fd < 0) PFATAL("Cannot open '/dev/null'."); | ||
|  |   } | ||
|  |    | ||
|  |   if (switch_user) drop_privs(); | ||
|  | 
 | ||
|  |   if (daemon_mode) fork_off(); | ||
|  | 
 | ||
|  |   signal(SIGHUP, daemon_mode ? SIG_IGN : abort_handler); | ||
|  |   signal(SIGINT, abort_handler); | ||
|  |   signal(SIGTERM, abort_handler); | ||
|  | 
 | ||
|  |   if (read_file) offline_event_loop(); else live_event_loop(); | ||
|  | 
 | ||
|  |   if (!daemon_mode) | ||
|  |     SAYF("\nAll done. Processed %llu packets.\n", packet_cnt); | ||
|  | 
 | ||
|  | #ifdef DEBUG_BUILD
 | ||
|  |   destroy_all_hosts(); | ||
|  |   TRK_report(); | ||
|  | #endif /* DEBUG_BUILD */
 | ||
|  | 
 | ||
|  |   return 0; | ||
|  | 
 | ||
|  | } |