1 /*
2 
3   ----------------------------------------------------
4   httpry - HTTP logging and information retrieval tool
5   ----------------------------------------------------
6 
7   Copyright (c) 2005-2014 Jason Bittel <jason.bittel@gmail.com>
8 
9 */
10 
11 #include <ctype.h>
12 #include <fcntl.h>
13 #include <grp.h>
14 #include <pcap.h>
15 #include <pwd.h>
16 #include <signal.h>
17 #include <sys/stat.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <time.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24 #include <sys/socket.h>
25 #include "config.h"
26 #include "error.h"
27 #include "format.h"
28 #include "methods.h"
29 #include "tcp.h"
30 #include "rate.h"
31 
32 /* Function declarations */
33 int getopt(int, char * const *, const char *);
34 pcap_t *prepare_capture(char *interface, int promisc, char *filename, char *capfilter);
35 void set_link_offset(int header_type);
36 void open_outfiles();
37 void runas_daemon();
38 void change_user(char *name);
39 void parse_http_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *pkt);
40 int process_ip6_nh(const u_char *pkt, int size_ip, unsigned int caplen, unsigned int offset);
41 char *parse_header_line(char *header_line);
42 int parse_client_request(char *header_line);
43 int parse_server_response(char *header_line);
44 void handle_signal(int sig);
45 void cleanup();
46 void print_stats();
47 void display_banner();
48 void display_usage();
49 
50 /* Program flags/options, set by arguments or config file */
51 static unsigned int parse_count = 0;
52 static int daemon_mode = 0;
53 static int eth_skip_bits = 0;
54 static char *use_infile = NULL;
55 static char *interface = NULL;
56 static char *capfilter = NULL;
57 static char *use_outfile = NULL;
58 static int set_promisc = 1;
59 static char *pid_filename = NULL;
60 static char *new_user = NULL;
61 static char *format_str = NULL;
62 static char *methods_str = NULL;
63 static char *use_dumpfile = NULL;
64 static int rate_stats = 0;
65 static int rate_interval = DEFAULT_RATE_INTERVAL;
66 static int rate_threshold = DEFAULT_RATE_THRESHOLD;
67 static int force_flush = 0;
68 int quiet_mode = 0;               /* Defined as extern in error.h */
69 int use_syslog = 0;               /* Defined as extern in error.h */
70 
71 static pcap_t *pcap_hnd = NULL;   /* Opened pcap device handle */
72 static char *buf = NULL;
73 static unsigned int num_parsed = 0;      /* Count of fully parsed HTTP packets */
74 static time_t start_time = 0;      /* Start tick for statistics calculations */
75 static int link_offset = 0;
76 static pcap_dumper_t *dumpfile = NULL;
77 static char default_capfilter[] = DEFAULT_CAPFILTER;
78 static char default_format[] = DEFAULT_FORMAT;
79 static char rate_format[] = RATE_FORMAT;
80 static char default_methods[] = DEFAULT_METHODS;
81 
82 /* Find and prepare ethernet device for capturing */
prepare_capture(char * interface,int promisc,char * filename,char * capfilter)83 pcap_t *prepare_capture(char *interface, int promisc, char *filename, char *capfilter) {
84         char errbuf[PCAP_ERRBUF_SIZE];
85         pcap_t *pcap_hnd;
86         char *dev = NULL;
87         bpf_u_int32 net, mask;
88         struct bpf_program filter;
89 
90         if (!filename) {
91                 /* Starting live capture, so find and open network device */
92                 if (!interface) {
93                         dev = pcap_lookupdev(errbuf);
94                         if (dev == NULL)
95                                 LOG_DIE("Cannot find a valid capture device: %s", errbuf);
96                 } else {
97                         dev = interface;
98                 }
99 
100                 if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) net = 0;
101 
102                 pcap_hnd = pcap_open_live(dev, BUFSIZ, promisc, 1000, errbuf);
103 
104                 if (pcap_hnd == NULL)
105                         LOG_DIE("Cannot open live capture on '%s': %s", dev, errbuf);
106         } else {
107                 /* Reading from a saved capture, so open file */
108                 pcap_hnd = pcap_open_offline(filename, errbuf);
109 
110                 if (pcap_hnd == NULL)
111                         LOG_DIE("Cannot open saved capture file: %s", errbuf);
112         }
113 
114         set_link_offset(pcap_datalink(pcap_hnd));
115 
116         /* Compile capture filter and apply to handle */
117         if (pcap_compile(pcap_hnd, &filter, capfilter, 0, net) == -1)
118                 LOG_DIE("Cannot compile capture filter '%s': %s", capfilter, pcap_geterr(pcap_hnd));
119 
120         if (pcap_setfilter(pcap_hnd, &filter) == -1)
121                 LOG_DIE("Cannot apply capture filter: %s", pcap_geterr(pcap_hnd));
122 
123         pcap_freecode(&filter);
124 
125         if (!filename) LOG_PRINT("Starting capture on %s interface", dev);
126 
127         return pcap_hnd;
128 }
129 
130 /* Set the proper packet header offset length based on the datalink type */
set_link_offset(int header_type)131 void set_link_offset(int header_type) {
132 
133 #ifdef DEBUG
134         ASSERT(header_type >= 0);
135 #endif
136 
137         switch (header_type) {
138                 case DLT_EN10MB:
139                         link_offset = 14;
140                         break;
141 #ifdef DLT_IEEE802_11
142                 case DLT_IEEE802_11:
143                         link_offset = 32;
144                         break;
145 #endif
146 #ifdef DLT_LINUX_SLL
147                 case DLT_LINUX_SLL:
148                         link_offset = 16;
149                         break;
150 #endif
151 #ifdef DLT_LOOP
152                 case DLT_LOOP:
153                         link_offset = 4;
154                         break;
155 #endif
156                 case DLT_NULL:
157                         link_offset = 4;
158                         break;
159                 case DLT_RAW:
160                         link_offset = 0;
161                         break;
162                 case DLT_PPP:
163                         link_offset = 4;
164                         break;
165 #ifdef DLT_PPP_SERIAL
166                 case DLT_PPP_SERIAL:
167 #endif
168                 case DLT_PPP_ETHER:
169                         link_offset = 8;
170                         break;
171                 default:
172                         LOG_DIE("Unsupported datalink type: %s", pcap_datalink_val_to_name(header_type));
173                         break;
174         }
175 
176         return;
177 }
178 
179 /* Open any requested output files */
open_outfiles()180 void open_outfiles() {
181         /* Redirect stdout to the specified output file if requested */
182         if (use_outfile) {
183                 if (daemon_mode && (use_outfile[0] != '/'))
184                         LOG_WARN("Output file path is not absolute and may be inaccessible after daemonizing");
185 
186                 if (freopen(use_outfile, "a", stdout) == NULL)
187                         LOG_DIE("Cannot reopen output stream to '%s'", use_outfile);
188 
189                 PRINT("Writing output to file: %s", use_outfile);
190 
191                 printf("# %s version %s\n", PROG_NAME, PROG_VER);
192                 print_format_list();
193         }
194 
195         /* Open pcap binary capture file if requested */
196         if (use_dumpfile) {
197                 if (daemon_mode && (use_dumpfile[0] != '/'))
198                         LOG_WARN("Binary capture file path is not absolute and may be inaccessible after daemonizing");
199 
200                 if ((dumpfile = pcap_dump_open(pcap_hnd, use_dumpfile)) == NULL)
201                         LOG_DIE("Cannot open binary dump file '%s'", use_dumpfile);
202                 PRINT("Writing binary dump file: %s", use_dumpfile);
203         }
204 
205         return;
206 }
207 
208 /* Run program as a daemon process */
runas_daemon()209 void runas_daemon() {
210         int child_pid;
211         FILE *pid_file;
212 
213         if (getppid() == 1) return; /* We're already a daemon */
214 
215         fflush(NULL);
216 
217         child_pid = fork();
218         if (child_pid < 0) LOG_DIE("Cannot fork child process");
219         if (child_pid > 0) exit(0); /* Parent bows out */
220 
221         /* Configure default output streams */
222         dup2(1,2);
223         close(0);
224         if (freopen(NULL_FILE, "a", stderr) == NULL)
225                 LOG_DIE("Cannot reopen stderr to '%s'", NULL_FILE);
226 
227         /* Assign new process group for child */
228         if (setsid() == -1)
229                 LOG_WARN("Cannot assign new session for child process");
230 
231         umask(022); /* Reset file creation mask */
232         if (chdir("/") == -1)
233                 LOG_DIE("Cannot change run directory to '/'");
234 
235         /* Create PID file */
236         if (pid_filename[0] != '/')
237                 LOG_WARN("PID file path is not absolute and may be inaccessible after daemonizing");
238         if ((pid_file = fopen(pid_filename, "w"))) {
239                 fprintf(pid_file, "%d", getpid());
240                 fclose(pid_file);
241         } else {
242                 LOG_WARN("Cannot open PID file '%s'", pid_filename);
243         }
244 
245         signal(SIGCHLD, SIG_IGN);
246         signal(SIGTSTP, SIG_IGN);
247         signal(SIGTTOU, SIG_IGN);
248         signal(SIGTTIN, SIG_IGN);
249         signal(SIGTERM, &handle_signal);
250 
251         fflush(NULL);
252 
253         return;
254 }
255 
256 /* Change process owner to specified username */
change_user(char * name)257 void change_user(char *name) {
258         struct passwd *user = NULL;
259 
260 #ifdef DEBUG
261         ASSERT(name);
262 #endif
263 
264         if ((getuid() != 0) && (geteuid() != 0))
265                 LOG_DIE("You must be root to switch users");
266 
267         if (!(user = getpwnam(name)))
268                 LOG_DIE("User '%s' not found in system", name);
269 
270         /* Change ownership of output files before we drop privs */
271         if (use_outfile) {
272                 if (chown(use_outfile, user->pw_uid, user->pw_gid) < 0)
273                         LOG_WARN("Cannot change ownership of output file '%s'", use_outfile);
274         }
275 
276         if (use_dumpfile) {
277                 if (chown(use_dumpfile, user->pw_uid, user->pw_gid) < 0)
278                         LOG_WARN("Cannot change ownership of dump file '%s'", use_dumpfile);
279         }
280 
281         if (initgroups(name, user->pw_gid))
282                 LOG_DIE("Cannot initialize the group access list");
283 
284         if (setgid(user->pw_gid)) LOG_DIE("Cannot set GID");
285         if (setuid(user->pw_uid)) LOG_DIE("Cannot set UID");
286 
287         /* Test to see if we actually made it to the new user */
288         if ((getegid() != user->pw_gid) || (geteuid() != user->pw_uid))
289                 LOG_DIE("Cannot change process owner to '%s'", name);
290 
291         return;
292 }
293 
294 /* Process each packet that passes the capture filter */
parse_http_packet(u_char * args,const struct pcap_pkthdr * header,const u_char * pkt)295 void parse_http_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *pkt) {
296         struct tm *pkt_time;
297         char *header_line, *req_value;
298         char saddr[INET6_ADDRSTRLEN], daddr[INET6_ADDRSTRLEN];
299         char sport[PORTSTRLEN], dport[PORTSTRLEN];
300         char ts[MAX_TIME_LEN];
301         int is_request = 0, is_response = 0;
302         unsigned int eth_type = 0, offset;
303 
304         const struct eth_header *eth;
305         const struct ip_header *ip;
306         const struct ip6_header *ip6;
307         const struct tcp_header *tcp;
308         const char *data;
309         int size_ip, size_tcp, size_data, family;
310 
311         /* Check the ethernet type and insert a VLAN offset if necessary */
312         eth = (struct eth_header *) pkt;
313         eth_type = ntohs(eth->ether_type);
314         if (eth_type == ETHER_TYPE_VLAN) {
315                 offset = link_offset + 4;
316         } else {
317                 offset = link_offset;
318         }
319 
320         offset += eth_skip_bits;
321 
322         /* Position pointers within packet stream and do sanity checks */
323         ip = (struct ip_header *) (pkt + offset);
324         ip6 = (struct ip6_header *) (pkt + offset);
325 
326         switch (IP_V(ip)) {
327                 case 4: family = AF_INET; break;
328                 case 6: family = AF_INET6; break;
329                 default: return;
330         }
331 
332         if (family == AF_INET) {
333                 size_ip = IP_HL(ip) * 4;
334                 if (size_ip < 20) return;
335                 if (ip->ip_p != IPPROTO_TCP) return;
336         } else { /* AF_INET6 */
337                 size_ip = sizeof(struct ip6_header);
338                 if (ip6->ip6_nh != IPPROTO_TCP)
339                         size_ip = process_ip6_nh(pkt, size_ip, header->caplen, offset);
340                 if (size_ip < 40) return;
341         }
342 
343         tcp = (struct tcp_header *) (pkt + offset + size_ip);
344         size_tcp = TH_OFF(tcp) * 4;
345         if (size_tcp < 20) return;
346 
347         data = (char *) (pkt + offset + size_ip + size_tcp);
348         size_data = (header->caplen - (offset + size_ip + size_tcp));
349         if (size_data <= 0) return;
350 
351         /* Check if we appear to have a valid request or response */
352         if (is_request_method(data)) {
353                 is_request = 1;
354         } else if (strncmp(data, HTTP_STRING, strlen(HTTP_STRING)) == 0) {
355                 is_response = 1;
356         } else {
357                 return;
358         }
359 
360         /* Copy packet data to editable buffer that was created in main() */
361         if (size_data > BUFSIZ) size_data = BUFSIZ;
362         memcpy(buf, data, size_data);
363         buf[size_data] = '\0';
364 
365         /* Parse header line, bail if malformed */
366         if ((header_line = parse_header_line(buf)) == NULL) return;
367 
368         if (is_request) {
369                 if (parse_client_request(header_line)) return;
370         } else if (is_response) {
371                 if (parse_server_response(header_line)) return;
372         }
373 
374         /* Iterate through request/entity header fields */
375         while ((header_line = parse_header_line(NULL)) != NULL) {
376                 if ((req_value = strchr(header_line, ':')) == NULL) continue;
377                 *req_value++ = '\0';
378                 while (isspace(*req_value)) req_value++;
379 
380                 insert_value(header_line, req_value);
381         }
382 
383         /* Grab source/destination IP addresses */
384         if (family == AF_INET) {
385                 inet_ntop(family, &ip->ip_src, saddr, sizeof(saddr));
386                 inet_ntop(family, &ip->ip_dst, daddr, sizeof(daddr));
387         } else { /* AF_INET6 */
388                 inet_ntop(family, &ip6->ip_src, saddr, sizeof(saddr));
389                 inet_ntop(family, &ip6->ip_dst, daddr, sizeof(daddr));
390         }
391         insert_value("source-ip", saddr);
392         insert_value("dest-ip", daddr);
393 
394         /* Grab source/destination ports */
395         snprintf(sport, PORTSTRLEN, "%d", ntohs(tcp->th_sport));
396         snprintf(dport, PORTSTRLEN, "%d", ntohs(tcp->th_dport));
397         insert_value("source-port", sport);
398         insert_value("dest-port", dport);
399 
400         /* Extract packet capture time */
401         pkt_time = localtime((time_t *) &header->ts.tv_sec);
402         strftime(ts, MAX_TIME_LEN, "%Y-%m-%d %H:%M:%S", pkt_time);
403         insert_value("timestamp", ts);
404 
405         if (rate_stats) {
406                 update_host_stats(get_value("host"), header->ts.tv_sec);
407                 clear_values();
408         } else {
409                 print_format_values();
410         }
411 
412         if (dumpfile)
413                 pcap_dump((unsigned char *) dumpfile, header, pkt);
414 
415         num_parsed++;
416         if (parse_count && (num_parsed >= parse_count))
417                 pcap_breakloop(pcap_hnd);
418 
419         return;
420 }
421 
422 /* Iterate through IPv6 extension headers looking for a TCP header. Returns
423    the total size of the IPv6 header, including all extension headers.
424    Return 0 to abort processing of this packet. */
process_ip6_nh(const u_char * pkt,int size_ip,unsigned int caplen,unsigned int offset)425 int process_ip6_nh(const u_char *pkt, int size_ip, unsigned int caplen, unsigned int offset) {
426         const struct ip6_ext_header *ip6_eh;
427         unsigned int len = caplen - offset;
428         ip6_eh = (struct ip6_ext_header *) (pkt + offset + size_ip);
429 
430         while (ip6_eh->ip6_eh_nh != IPPROTO_TCP) {
431                 switch (ip6_eh->ip6_eh_nh) {
432                         case 0:  /* Hop-by-hop options */
433                         case 43: /* Routing */
434                         case 44: /* Fragment */
435                         case 51: /* Authentication Header */
436                         case 50: /* Encapsulating Security Payload */
437                         case 60: /* Destination Options */
438                                 size_ip = size_ip + (ip6_eh->ip6_eh_len * 8) + 8;
439                                 break;
440                         case 59: /* No next header */
441                         default:
442                                 return 0;
443                 }
444 
445                 if (size_ip > len) return 0;
446 
447                 ip6_eh = (struct ip6_ext_header *) (pkt + offset + size_ip);
448         }
449 
450         /* Next header is TCP, so increment past the final extension header */
451         size_ip = size_ip + (ip6_eh->ip6_eh_len * 8) + 8;
452 
453         return size_ip;
454 }
455 
456 /* Tokenize a HTTP header into lines; the first call should pass the string
457    to tokenize, all subsequent calls for the same string should pass NULL */
parse_header_line(char * header_line)458 char *parse_header_line(char *header_line) {
459         static char *pos;
460         char *tmp;
461 
462         if (header_line) pos = header_line;
463 
464         /* Search for a '\n' line terminator, ignoring a leading
465            '\r' if it exists (per RFC2616 section 19.3) */
466         tmp = strchr(pos, '\n');
467         if (!tmp && header_line) {
468                 return header_line;
469         } else if (!tmp) {
470                 return NULL;
471         }
472         *tmp = '\0';
473         if (*(tmp - 1) == '\r') *(--tmp) = '\0';
474 
475         if (tmp == pos) return NULL; /* Reached the end of the header */
476 
477         header_line = pos;
478         /* Increment past the '\0' character(s) inserted above */
479         if (*tmp == '\0') {
480                 tmp++;
481                 if (*tmp == '\0') tmp++;
482         }
483         pos = tmp;
484 
485         return header_line;
486 }
487 
488 /* Parse a HTTP client request; bail at first sign of an invalid request */
parse_client_request(char * header_line)489 int parse_client_request(char *header_line) {
490         char *method, *request_uri, *http_version;
491 
492 #ifdef DEBUG
493         ASSERT(header_line);
494         ASSERT(strlen(header_line) > 0);
495 #endif
496 
497         method = header_line;
498 
499         if ((request_uri = strchr(method, ' ')) == NULL) return 1;
500         *request_uri++ = '\0';
501         while (isspace(*request_uri)) request_uri++;
502 
503         if ((http_version = strchr(request_uri, ' ')) != NULL) {
504                 *http_version++ = '\0';
505                 while (isspace(*http_version)) http_version++;
506                 if (strncmp(http_version, HTTP_STRING, strlen(HTTP_STRING)) != 0) return 1;
507                 insert_value("http-version", http_version);
508         }
509 
510         insert_value("method", method);
511         insert_value("request-uri", request_uri);
512         insert_value("direction", ">");
513 
514         return 0;
515 }
516 
517 /* Parse a HTTP server response; bail at first sign of an invalid response */
parse_server_response(char * header_line)518 int parse_server_response(char *header_line) {
519         char *http_version, *status_code, *reason_phrase;
520 
521 #ifdef DEBUG
522         ASSERT(header_line);
523         ASSERT(strlen(header_line) > 0);
524 #endif
525 
526         http_version = header_line;
527 
528         if ((status_code = strchr(http_version, ' ')) == NULL) return 1;
529         *status_code++ = '\0';
530         while (isspace(*status_code)) status_code++;
531 
532         if ((reason_phrase = strchr(status_code, ' ')) == NULL) return 1;
533         *reason_phrase++ = '\0';
534         while (isspace(*reason_phrase)) reason_phrase++;
535 
536         insert_value("http-version", http_version);
537         insert_value("status-code", status_code);
538         insert_value("reason-phrase", reason_phrase);
539         insert_value("direction", "<");
540 
541         return 0;
542 }
543 
544 /* Handle signals for clean reloading or shutdown */
handle_signal(int sig)545 void handle_signal(int sig) {
546 
547 #ifdef DEBUG
548         ASSERT(sig > 0);
549 #endif
550 
551         switch (sig) {
552                 case SIGHUP:
553                         LOG_PRINT("Caught SIGHUP, reloading...");
554                         print_stats();
555                         if (rate_stats)
556                                 cleanup_rate_stats();
557                         open_outfiles();
558                         if (rate_stats)
559                                 init_rate_stats(rate_interval, use_infile, rate_threshold);
560                         return;
561                 case SIGINT:
562                         LOG_PRINT("Caught SIGINT, shutting down...");
563                         print_stats();
564                         cleanup();
565                         break;
566                 case SIGTERM:
567                         LOG_PRINT("Caught SIGTERM, shutting down...");
568                         print_stats();
569                         cleanup();
570                         break;
571                 default:
572                         LOG_WARN("Ignoring unknown signal '%d'", sig);
573                         return;
574         }
575 
576         exit(sig);
577 }
578 
579 /* Perform end of run tasks and prepare to exit gracefully */
cleanup()580 void cleanup() {
581         /* This may have already been called, but might not
582            have depending on how we got here */
583         if (pcap_hnd) pcap_breakloop(pcap_hnd);
584         if (rate_stats) cleanup_rate_stats();
585 
586         fflush(NULL);
587 
588         free_format();
589         free_methods();
590         if (buf) free(buf);
591 
592         /* Note that this won't get removed if we've switched to a
593            user that doesn't have permission to delete the file */
594         if (daemon_mode) remove(pid_filename);
595         if (pcap_hnd) pcap_close(pcap_hnd);
596 
597         return;
598 }
599 
600 /* Print packet capture statistics */
print_stats()601 void print_stats() {
602         struct pcap_stat pkt_stats;
603         float run_time;
604 
605         if (rate_stats)
606                 display_rate_stats(use_infile, rate_threshold);
607 
608         if (pcap_hnd && !use_infile) {
609                 if (pcap_stats(pcap_hnd, &pkt_stats) != 0) {
610                         WARN("Cannot obtain packet capture statistics: %s", pcap_geterr(pcap_hnd));
611                         return;
612                 }
613 
614                 LOG_PRINT("%u packets received, %u packets dropped, %u http packets parsed", \
615                      pkt_stats.ps_recv, pkt_stats.ps_drop, num_parsed);
616 
617                 run_time = (float) (time(0) - start_time);
618                 if (run_time > 0) {
619                         LOG_PRINT("%0.1f packets/min, %0.1f http packets/min", \
620                              ((pkt_stats.ps_recv * 60) / run_time), ((num_parsed * 60) / run_time));
621                 }
622         } else if (pcap_hnd) {
623                 PRINT("%u http packets parsed", num_parsed);
624         }
625 
626         return;
627 }
628 
629 /* Display startup/informational banner */
display_banner()630 void display_banner() {
631         PRINT("%s version %s -- "
632               "HTTP logging and information retrieval tool", PROG_NAME, PROG_VER);
633         PRINT("Copyright (c) 2005-2014 Jason Bittel <jason.bittel@gmail.com>");
634 
635         return;
636 }
637 
638 /* Display program usage information */
display_usage()639 void display_usage() {
640         display_banner();
641 
642         printf("Usage: %s [ -dFhpqs ] [-b file ] [ -f format ] [ -i device ] [ -l threshold ]\n"
643                "              [ -m methods ] [ -n count ] [ -o file ] [ -P file ] [ -r file ]\n"
644                "              [ -t seconds] [ -u user ] [ 'expression' ]\n\n", PROG_NAME);
645 
646         printf("   -b file      write HTTP packets to a binary dump file\n"
647                "   -d           run as daemon\n"
648                "   -f format    specify output format string\n"
649                "   -F           force output flush\n"
650                "   -h           print this help information\n"
651                "   -i device    listen on this interface\n"
652                "   -l threshold specify a rps threshold for rate statistics\n"
653                "   -m methods   specify request methods to parse\n"
654                "   -n count     set number of HTTP packets to parse\n"
655                "   -o file      write output to a file\n"
656                "   -p           disable promiscuous mode\n"
657                "   -P file      use custom PID filename when running in daemon mode \n"
658                "   -q           suppress non-critical output\n"
659                "   -r file      read packets from input file\n"
660                "   -s           run in HTTP requests per second mode\n"
661                "   -t seconds   specify the display interval for rate statistics\n"
662                "   -u user      set process owner\n"
663                "   expression   specify a bpf-style capture filter\n\n");
664 
665         printf("Additional information can be found at:\n"
666                "   http://dumpsterventures.com/jason/httpry\n\n");
667 
668         exit(EXIT_SUCCESS);
669 }
670 
main(int argc,char ** argv)671 int main(int argc, char **argv) {
672         int opt;
673         extern char *optarg;
674         extern int optind;
675         int loop_status;
676 
677         signal(SIGHUP, &handle_signal);
678         signal(SIGINT, &handle_signal);
679 
680         /* Process command line arguments */
681         while ((opt = getopt(argc, argv, "b:df:Fhpqi:l:m:n:o:P:r:st:u:S:")) != -1) {
682                 switch (opt) {
683                         case 'b': use_dumpfile = optarg; break;
684                         case 'd': daemon_mode = 1; use_syslog = 1; break;
685                         case 'f': format_str = optarg; break;
686                         case 'F': force_flush = 1; break;
687                         case 'h': display_usage(); break;
688                         case 'i': interface = optarg; break;
689                         case 'l': rate_threshold = atoi(optarg); break;
690                         case 'm': methods_str = optarg; break;
691                         case 'n': parse_count = atoi(optarg); break;
692                         case 'o': use_outfile = optarg; break;
693                         case 'p': set_promisc = 0; break;
694                         case 'P': pid_filename = optarg; break;
695                         case 'q': quiet_mode = 1; break;
696                         case 'r': use_infile = optarg; break;
697                         case 's': rate_stats = 1; break;
698                         case 't': rate_interval = atoi(optarg); break;
699                         case 'u': new_user = optarg; break;
700                         case 'S': eth_skip_bits = atoi(optarg); break;
701                         default: display_usage();
702                 }
703         }
704 
705         display_banner();
706 
707         if (daemon_mode && !use_outfile)
708                 LOG_DIE("Daemon mode requires an output file");
709 
710         if (parse_count < 0)
711                 LOG_DIE("Invalid -n value, must be 0 or greater");
712 
713         if (rate_interval < 1)
714                 LOG_DIE("Invalid -t value, must be 1 or greater");
715 
716         if (rate_threshold < 1)
717                 LOG_DIE("Invalid -l value, must be 1 or greater");
718 
719         if (argv[optind] && *(argv[optind])) {
720                 capfilter = argv[optind];
721         } else {
722                 capfilter = default_capfilter;
723         }
724 
725         if (!format_str) format_str = default_format;
726         if (rate_stats) format_str = rate_format;
727         parse_format_string(format_str);
728 
729         if (!methods_str) methods_str = default_methods;
730         parse_methods_string(methods_str);
731 
732         if (force_flush) {
733                 if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
734                         LOG_WARN("Cannot disable buffering on stdout");
735         }
736 
737         if (!pid_filename) pid_filename = PID_FILENAME;
738 
739         pcap_hnd = prepare_capture(interface, set_promisc, use_infile, capfilter);
740 
741         open_outfiles();
742 
743         if (daemon_mode) runas_daemon();
744         if (new_user) change_user(new_user);
745 
746         if ((buf = malloc(BUFSIZ + 1)) == NULL)
747                 LOG_DIE("Cannot allocate memory for packet data buffer");
748 
749         if (rate_stats)
750                 init_rate_stats(rate_interval, use_infile, rate_threshold);
751 
752         start_time = time(0);
753         loop_status = pcap_loop(pcap_hnd, -1, &parse_http_packet, NULL);
754         if (loop_status == -1) {
755                 LOG_DIE("Problem reading packets from interface: %s", pcap_geterr(pcap_hnd));
756         } else if (loop_status == -2) {
757                 PRINT("Loop halted, shutting down...");
758         }
759 
760         print_stats();
761         cleanup();
762 
763         return loop_status == -1 ? EXIT_FAILURE : EXIT_SUCCESS;
764 }
765