/*
Copyright (C) 2000 - 2006 Christian Kreibich <christian@whoop.org>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <libnd_globals.h>
#include <libnd_trace.h>
#include <libnd_prefs.h>
#include <libnd_misc.h>
#include <libnd_protocol.h>
#include <libnd_protocol_inst.h>
#include <libnd_tcpdump.h>
/* Tcpdump communication handling */
#define TCPDUMP_OPS_HARDCODED "-l -r"
static char tcpdump_dyn_opt[MAXPATHLEN];
static char *tokenpacket_tstamp = "01:23:45.678901 ";
gint libnet_tcpdump_print_timestamp;
/*
* libnd_tcpdump_fill_in_options - creates a vector of options to pass to tcpdump.
* @opt: string to paste parameters into
* @opt_vec: array of char*s to receive options
* @opt_size: length of @opt_vec
*
* This function fills in the given array of strings
* by analyzing the command line options and setting
* each string in the array to one option.
*
* Returns: number of options used.
*/
static int
tcpdump_fill_in_options(char *opt, char **opt_vec, int opt_size)
{
int flag;
int index = 0;
char *p;
sprintf(opt, "tcpdump ");
p = opt + strlen(opt);
if (libnd_prefs_get_int_item(LND_DOM_NETDUDE, "tcpdump_resolve", &flag))
{
if (!flag)
{
sprintf(p, "-nnn ");
p = p + strlen(p);
}
}
if (libnd_prefs_get_int_item(LND_DOM_NETDUDE, "tcpdump_domains", &flag))
{
if (!flag)
{
sprintf(p, "-N ");
p = p + strlen(p);
}
}
if (libnd_prefs_get_int_item(LND_DOM_NETDUDE, "tcpdump_quick", &flag))
{
if (flag)
{
sprintf(p, "-q ");
p = p + strlen(p);
}
}
if (libnd_prefs_get_int_item(LND_DOM_NETDUDE, "tcpdump_print_link", &flag))
{
if (flag)
{
sprintf(p, "-e ");
p = p + strlen(p);
}
}
/* We ALWAYS ask for a timestamp -- we need this for our packet filtering!
* The flag can be looked at again here once there is a reasonable way
* to force buffer flushing in tcpdump from the outside ...
*/
sprintf(p, tcpdump_dyn_opt);
p = p + strlen(p);
sprintf(p, " " TCPDUMP_OPS_HARDCODED " -");
p = opt;
while (TRUE)
{
if (index == opt_size - 2)
break;
if (*p == 0)
break;
if (*p == ' ')
{
*p = 0;
p++;
}
else
{
opt_vec[index++] = p;
while (*p != ' ' && *p != 0)
p++;
}
}
/* Last string must be NULL for execv */
opt_vec[index++] = NULL;
return index;
}
gboolean
libnd_tcpdump_init(void)
{
char *tcpdump_path;
if (libnd_prefs_get_str_item(LND_DOM_NETDUDE, "tcpdump_path", &tcpdump_path))
{
if (libnd_misc_can_exec(tcpdump_path))
return TRUE;
}
g_snprintf(libnd_pcap_errbuf, PCAP_ERRBUF_SIZE, "tcpdump executable '%s' not found.", tcpdump_path);
tcpdump_dyn_opt[0] = '\0';
return FALSE;
}
gboolean
libnd_tcpdump_open(LND_Trace *trace)
{
int fd[2];
char *tcpdump_path;
if (!trace)
return FALSE;
if (!libnd_prefs_get_str_item(LND_DOM_NETDUDE, "tcpdump_path", &tcpdump_path))
return FALSE;
if (!libnd_misc_can_exec(tcpdump_path))
return FALSE;
if (!libnd_trace_initialized(trace))
return FALSE;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
{
fprintf(stderr, "Could not create socket pair -- exiting.\n");
exit(1);
}
if ( (trace->tcpdump.pid = fork()) < 0)
{
fprintf(stderr, "Fork error -- exiting.\n");
exit(1);
}
else if (trace->tcpdump.pid > 0)
{
close(fd[1]);
trace->tcpdump.fd = fd[0];
if (fcntl(trace->tcpdump.fd, F_SETFL, O_NONBLOCK) < 0)
{
fprintf(stderr, "Can not fcntl socket -- exiting.\n");
exit(-1);
}
if (libnd_trace_initialized(trace))
libnd_tcpdump_send_header(trace);
}
else
{
/* The child replaces the process image with a new tcpdump */
char options[MAXPATHLEN + 50];
char *options_vec[20];
int options_index;
options_index = tcpdump_fill_in_options(options, options_vec, 20);
close(fd[0]);
if (fd[1] != STDIN_FILENO)
{
if (dup2(fd[1], STDIN_FILENO) != STDIN_FILENO)
{
fprintf(stderr, "stdin pipe error\n");
exit(-1);
}
}
if (fd[1] != STDOUT_FILENO)
{
if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO)
{
fprintf(stderr, "stdout pipe error\n");
exit(-1);
}
}
if (execv(tcpdump_path, options_vec) < 0)
{
perror("Ooops");
fprintf(stderr, "tcpdump error forking %s\n", tcpdump_path);
exit(-1);
}
}
return TRUE;
}
/*
* libnd_tcpdump_send_header - writes a pcap file header to a tcpdump process
* @trace: the trace whose tcpdump and save file header should be used
*/
void
libnd_tcpdump_send_header(LND_Trace *trace)
{
/* The parent process writes the pcap trace file header
to the tcpdump child, as the beginning of the file:
*/
if (libnd_misc_writen(trace->tcpdump.fd, (guchar *) &(trace->tcpdump.pfh),
sizeof(struct pcap_file_header))
!= sizeof(struct pcap_file_header))
fprintf(stderr, "Write error in pipe when sending header\n");
}
void
libnd_tcpdump_close(LND_Trace *trace)
{
if (!trace)
return;
if (trace->tcpdump.pid <= 0)
return;
kill(trace->tcpdump.pid, SIGKILL);
close(trace->tcpdump.fd);
if (waitpid(trace->tcpdump.pid, NULL, 0) != trace->tcpdump.pid)
{
D(("Error in waitpid()\n"));
}
trace->tcpdump.pid = 0;
trace->tcpdump.fd = 0;
}
static void
tcpdump_update_line_cb(LND_Packet *packet,
LND_ProtoData *pd,
void *user_data)
{
char *line = (char *) user_data;
if (!packet || !pd)
return;
if (pd->inst.proto->is_stateful &&
pd->inst.proto->update_tcpdump_line)
pd->inst.proto->update_tcpdump_line(packet, line);
}
static gboolean
tcpdump_send_packet(int td_fd,
const struct pcap_pkthdr *hdr,
const guchar *data)
{
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(td_fd, &fdset);
if (select(td_fd + 1, NULL, &fdset, NULL, NULL) <= 0)
goto error_exit;
/* printf("Sending packet: %u %u\n", hdr->len, hdr->caplen); */
if (write(td_fd, (guchar *) hdr, sizeof(struct pcap_pkthdr)) != sizeof(struct pcap_pkthdr))
goto error_exit;
if (write(td_fd, data, hdr->caplen) != (int) hdr->caplen)
goto error_exit;
/* fsync(td_fd); doesn't make sense on a socketpair() socket? */
return TRUE;
error_exit:
fprintf(stderr, "Write error in pipe\n");
return FALSE;
}
static void
tcpdump_get_token_timestamp(struct bpf_timeval *tv)
{
static guint32 diff= 0;
static gboolean done = FALSE;
time_t t;
int dir;
struct tm *gmttime, *loctime;
struct tm tm;
if (!done)
{
t = time(NULL);
gmttime = &tm;
*gmttime = *gmtime(&t);
loctime = localtime(&t);
diff = (loctime->tm_hour - gmttime->tm_hour) * 3600 +
(loctime->tm_min - gmttime->tm_min) * 60;
dir = loctime->tm_year - gmttime->tm_year;
if (dir == 0)
dir = loctime->tm_yday - gmttime->tm_yday;
diff += dir * 24 * 3600;
}
tv->tv_sec = 5025 - diff;
tv->tv_usec = 678901;
}
/*
* tcpdump_send_dummypacket - sends a hand-crafted packet to tcpdump
* @td_fd: tcpdump file descriptor
*
* This function is basically a gross hack to avoid problems with tcpdump
* output for a single packet that spans multiple lines. We cannot rely on
* receiving all the output in one burst, and there's currently no way to
* force an fflush() on the tcpdump side from the outside (might be worth
* adding a signal handler for that, or add an option that causes a flush
* after each packet -- should only be a couple lines). What this function
* does is sending a tiny dummy packet to tcpdump that'll cause the remaining
* data to arrive. libnd_tcpdump_get_packet_line() uses this function and filters
* the correct lines from the output. This is far from perfect to say the
* least, but it's better than doing nothing ... and maybe the best we can do
* without requiring our own version of tcpdump!
*
* Returns: %TRUE if everything worked, %FALSE otherwise.
*/
static gboolean
tcpdump_send_dummypacket(int td_fd)
{
struct pcap_pkthdr pkthdr;
guchar data;
/* Initialize our pseudo-packet data: */
memset(&pkthdr, 0, sizeof(struct pcap_pkthdr));
tcpdump_get_token_timestamp(&pkthdr.ts);
pkthdr.len = 1;
pkthdr.caplen = 1;
data = 0;
return tcpdump_send_packet(td_fd, &pkthdr, &data);
}
/*
* tcpdump_find_next_packet_line - scans string for packet start
* sp: string to scan
* splen: length of @sp
*
* The function looks for the first line in the string that does
* not start with whitespace or our well-known timestamp. If such
* a line is found, a pointer to its start is returned, NULL otherwise.
*
* Returns: search result.
*/
static char *
tcpdump_find_next_packet_line(char *sp, int splen)
{
char *endp = sp + splen;
while (sp < endp)
{
/* If we pass all these conditions, the line is considered okay */
if ((strstr(sp, tokenpacket_tstamp) != sp) && /* not our special timestamp */
(sp[0] >= 48 && sp[0] <= 57) && /* must start with a number */
(sp[2] == ':')) /* and our timestamp format */
/* okay line -- return it */
return sp;
/* Okay -- this is a bogus line, try to skip to the next line. */
if (! (sp = strchr(sp, '\n')))
return NULL;
sp++;
/* sp now at beginning of next line */
}
return NULL;
}
gboolean
libnd_tcpdump_get_packet_line(const LND_Packet *packet,
char *output, int output_len,
gboolean find_context)
{
LND_Trace *trace;
fd_set fdset;
static char s[MAXPATHLEN];
char *sp, *eol = NULL, *result = NULL;
int td_fd, remaining, n;
gboolean success;
if (!packet)
return FALSE;
if (! (trace = libnd_packet_get_trace(packet)))
return FALSE;
/* Make sure we actually have a tcpdump process to talk to */
if (trace->tcpdump.pid == 0)
{
if (! libnd_tcpdump_open(trace))
{
D(("Couldn't initiate tcpdump communication.\n"));
strncpy(output, "[tcpdump communication failed]", output_len);
output[output_len-1] = '\0';
return FALSE;
}
}
td_fd = trace->tcpdump.fd;
if (! tcpdump_send_packet(td_fd, &(packet->ph), packet->data))
return FALSE;
for ( ; ; )
{
tcpdump_send_dummypacket(td_fd);
sp = s;
remaining = MAXPATHLEN-1;
FD_ZERO(&fdset);
FD_SET(td_fd, &fdset);
select(td_fd + 1, &fdset, NULL, NULL, NULL);
success = FALSE;
result = s;
n = libnd_misc_readline(td_fd, s, MAXPATHLEN-1);
if (n == 0)
continue;
/* Whatever we read -- terminate it and reset sp to start of string */
*(s + n) = '\0';
/* printf("We got '%s'\n", s); */
/* Now find next useful line in output: */
if (! (sp = tcpdump_find_next_packet_line(s, MAXPATHLEN-1)))
continue;
/* It can be empty -- if it is, force more flushing from tcpdump: */
if (sp[0] == '\0')
continue;
break;
}
if ( (eol = strchr(sp, '\n')))
*eol = '\0';
if (! libnet_tcpdump_print_timestamp)
sp += 16;
result = sp;
/* printf("Received: '%s'\n", result); */
if (find_context)
{
libnd_packet_foreach_proto((LND_Packet *) packet,
tcpdump_update_line_cb,
result);
}
strncpy(output, result, output_len);
output[output_len-1] = '\0';
return TRUE;
}
void
libnd_tcpdump_options_reset(void)
{
tcpdump_dyn_opt[0] = '\0';
}
void
libnd_tcpdump_options_add(const char *option)
{
if (!option || !*option)
return;
strncat(tcpdump_dyn_opt, " ", MAXPATHLEN - strlen(tcpdump_dyn_opt));
strncat(tcpdump_dyn_opt, option, MAXPATHLEN - strlen(tcpdump_dyn_opt));
D(("Dynamic tcpdump options now '%s'\n", tcpdump_dyn_opt));
}
syntax highlighted by Code2HTML, v. 0.9.1