/*
** iplog.c - iplog main routine.
** Copyright (C) 1999-2001 Ryan McCabe <odin@numb.org>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License, version 2,
** as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
**
** $Id: iplog.c,v 1.31 2001/01/01 19:36:03 odin Exp $
*/
#define _GNU_SOURCE
#include <config.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <pcap.h>
#ifdef HAVE_PTHREAD_CANCEL
# include <setjmp.h>
#endif
#ifndef __linux__
# define THREADED_LIBPCAP_IS_BROKEN 1
#endif
#ifdef THREADED_LIBPCAP_IS_BROKEN
# include <pcap-int.h>
#endif
#include <iplog.h>
#include <iplog_options.h>
#include <iplog_config.h>
#include <iplog_pcap.h>
#include <iplog_input.h>
#include <iplog_dns.h>
#include <iplog_scan.h>
#ifdef HAVE_PTHREAD_CANCEL
# define setup_thread_cancelstate() \
do { \
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); \
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); \
} while (0)
static void iplog_restart(int sig);
static void pcap_cleanup(void *data);
static sigjmp_buf jmpbuf;
#else
# define setup_thread_cancelstate() do { } while (0)
#endif
struct running {
struct running *next;
struct running *prev;
pthread_t pt;
};
static struct running *running = NULL;
static struct pcap_data *plist = NULL;
static volatile bool cap_rawsock = false;
static u_int reading = 0;
static pthread_mutex_t running_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t plist_lock = PTHREAD_MUTEX_INITIALIZER;
static void *read_packets(void *data);
static void *expire_data(void *unused);
static void iplog_cleanup(int sig);
static void pq_add(struct ip *ip, size_t len);
static struct packet_list *pq_get(void);
static void *queue_manager(void *data);
u_char *ifstring, *logfile, *user, *group, *lockfile;
struct packet_queue {
struct packet_list {
struct packet_list *next;
struct packet_list *prev;
struct ip *ip;
time_t received;
} *head, *tail;
pthread_mutex_t lock;
pthread_cond_t cond;
};
static struct packet_queue packet_queue = {
head: NULL,
tail: NULL,
lock: PTHREAD_MUTEX_INITIALIZER,
cond: PTHREAD_COND_INITIALIZER
};
/*
** Add a node to the packet queue.
*/
static void pq_add(struct ip *ip, size_t len) {
struct packet_list *new_node = xmalloc(sizeof(struct packet_list));
new_node->received = time(NULL);
/*
** libpcap stores packet data in a static buffer.
** Guess what happens without the copy if there's
** more than one interface open.
*/
new_node->ip = xmalloc(len);
memcpy(new_node->ip, ip, len);
pthread_mutex_lock(&packet_queue.lock);
dlist_prepend(new_node, &packet_queue.head);
if (packet_queue.tail == NULL)
packet_queue.tail = packet_queue.head;
pthread_cond_signal(&packet_queue.cond);
pthread_mutex_unlock(&packet_queue.lock);
}
/*
** Remove a node from the packet queue.
** This must be called with packet_queue.lock held.
**
** The caller is responsible for freeing any space associated with the
** removed node!
*/
static struct packet_list *pq_get(void) {
struct packet_list *old_tail = packet_queue.tail;
if (old_tail == NULL)
return (NULL);
packet_queue.tail = packet_queue.tail->prev;
dlist_remove(old_tail, &packet_queue.head);
pthread_mutex_unlock(&packet_queue.lock);
return (old_tail);
}
int main(int argc, char **argv) {
struct pcap_data *cur;
struct running rtemp;
volatile bool dropped = false;
int test_socket;
lockfile = xstrdup(LOCKFILE);
parse_config(CONFFILE);
/* Command-line options override the defaults set in the conf file. */
get_options(argc, argv);
check_options();
myopenlog("iplog", LOG_PID | LOG_NDELAY);
if (!opt_enabled(NO_FORK))
fork_to_back();
write_lockfile(lockfile);
if (opt_enabled(DNS_CACHE))
init_dns_table(opt_enabled(PROMISC) ? DNS_MAXSIZE_P : DNS_MAXSIZE_N);
if (opt_enabled(ANY_SCAN))
init_scan_table(opt_enabled(PROMISC) ? SCAN_TSIZE_P : SCAN_TSIZE_N);
init_frag_table(FRAG_TSIZE);
#ifdef HAVE_PTHREAD_CANCEL
sigsetjmp(jmpbuf, 1);
#endif
if (plist == NULL && setup_pcap(&plist, ifstring) == -1)
fatal("Couldn't initialize interfaces.");
if (dropped == false) {
dropped = true;
drop_privs(user, group);
}
memset(&rtemp, 0, sizeof(rtemp));
pthread_create(&rtemp.pt, NULL, queue_manager, NULL);
/* This is not the best check, but it's better than checking {e,}uid */
test_socket = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);
if (test_socket != -1) {
cap_rawsock = true;
close(test_socket);
}
if (opt_enabled(IGNORE_NS) && add_dns_ignore_rules() == -1)
mysyslog("Unable to add dns ignore rules: %s", strerror(errno));
pthread_mutex_lock(&running_lock);
pthread_mutex_lock(&plist_lock);
pthread_create(&rtemp.pt, NULL, expire_data, NULL);
dlist_copy_prepend(&rtemp, &running, sizeof(rtemp));
for (cur = plist ; cur != NULL ; cur = cur->next) {
pthread_create(&rtemp.pt, NULL, read_packets, cur);
dlist_copy_prepend(&rtemp, &running, sizeof(rtemp));
reading++;
}
pthread_mutex_unlock(&plist_lock);
pthread_mutex_unlock(&running_lock);
if (opt_enabled(NO_FORK))
signal(SIGINT, iplog_cleanup);
signal(SIGTERM, iplog_cleanup);
signal(SIGSEGV, iplog_cleanup);
#ifdef HAVE_PTHREAD_CANCEL
signal(SIGHUP, iplog_restart);
#else
signal(SIGHUP, SIG_IGN);
#endif
select(0, NULL, NULL, NULL, NULL);
exit(0);
}
/*
** Packet queue manager thread.
** This stays alive across restarts.
*/
static void *queue_manager(void *unused) {
struct packet_list *packet;
sigset_t expire_sigset;
sigemptyset(&expire_sigset);
if (opt_enabled(NO_FORK))
sigaddset(&expire_sigset, SIGINT);
sigaddset(&expire_sigset, SIGTERM);
sigaddset(&expire_sigset, SIGHUP);
pthread_sigmask(SIG_BLOCK, &expire_sigset, NULL);
pthread_mutex_lock(&packet_queue.lock);
for (;;) {
packet = pq_get();
if (packet == NULL)
pthread_cond_wait(&packet_queue.cond, &packet_queue.lock);
else {
parse_packet(packet->ip);
free(packet->ip);
free(packet);
pthread_mutex_lock(&packet_queue.lock);
}
}
return (unused);
}
/*
** Read in packets, send them off the appropriate function.
*/
static void *read_packets(void *data) {
u_char *packet;
struct pcap_pkthdr pkthdr;
struct pcap_data *pdev = (struct pcap_data *) data;
struct running *cur;
sigset_t iplog_sigset;
#ifdef THREADED_LIBPCAP_IS_BROKEN
/*
** I'm not sure if this crap is actually needed on all
** platforms that aren't Linux. BSD seemed to need it, but
** I don't know about any others (I don't have access to others
** so I can't test) ..
*/
struct timeval tv;
int fd = ((struct pcap *)pdev->pd)->fd;
fd_set rfds;
FD_ZERO(&rfds);
tv.tv_sec = 0;
tv.tv_usec = 10000;
#endif
setup_thread_cancelstate();
sigemptyset(&iplog_sigset);
if (opt_enabled(NO_FORK))
sigaddset(&iplog_sigset, SIGINT);
sigaddset(&iplog_sigset, SIGTERM);
sigaddset(&iplog_sigset, SIGHUP);
pthread_sigmask(SIG_BLOCK, &iplog_sigset, NULL);
for (;;) {
#ifdef THREADED_LIBPCAP_IS_BROKEN
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, &tv);
#endif
packet = (u_char *) pcap_next(pdev->pd, &pkthdr);
if (packet != NULL) {
packet += pdev->dl;
/* pq_add() handles locking. */
pq_add((struct ip *) packet, pkthdr.caplen);
if (pkthdr.caplen != pkthdr.len)
IDEBUG(("caplen=%lu != len=%lu\n", pkthdr.caplen, pkthdr.len));
} else if (errno == ENETDOWN) {
mysyslog("Warning: interface %s went down.", pdev->name);
pcap_close(pdev->pd);
if (cap_rawsock == false) {
mysyslog("Interface %s cannot be brought back up.", pdev->name);
if (--reading == 0)
fatal("No more interfaces open for reading. Exiting.");
pthread_mutex_lock(&running_lock);
cur = running;
/* Can't ever hit NULL */
while (cur->pt != pthread_self())
cur = cur->next;
dlist_delete(cur, &running);
pthread_mutex_unlock(&running_lock);
pthread_mutex_lock(&plist_lock);
dlist_delete(pdev, &plist);
pthread_mutex_unlock(&plist_lock);
pthread_exit(NULL);
} else {
struct pcap_data *dplist = NULL;
for (;;) {
if (setup_pcap(&dplist, pdev->name) != -1) {
if (ifflag_isset(dplist->name, IFF_UP)) {
mysyslog("Interface %s was reopened.",
dplist->name);
pthread_mutex_lock(&plist_lock);
dlist_delete(pdev, &plist);
pdev = dlist_prepend(dplist, &plist);
pthread_mutex_unlock(&plist_lock);
break;
} else {
pcap_close(dplist->pd);
dlist_destroy(dplist, NULL);
}
}
/* Try again in 30 seconds. */
xsleep(30);
}
}
}
}
return (NULL);
}
/*
** This function runs as a separate thread, expiring fragment data,
** scan hash table entries and dns hash table entries.
*/
static void *expire_data(void *unused) {
sigset_t expire_sigset;
setup_thread_cancelstate();
sigemptyset(&expire_sigset);
if (opt_enabled(NO_FORK))
sigaddset(&expire_sigset, SIGINT);
sigaddset(&expire_sigset, SIGTERM);
sigaddset(&expire_sigset, SIGHUP);
pthread_sigmask(SIG_BLOCK, &expire_sigset, NULL);
for (;;) {
xsleep(EXPIRE_INTERVAL);
expire_frags();
if (opt_enabled(ANY_SCAN))
expire_scans();
if (opt_enabled(DNS_CACHE))
expire_dns();
}
return (unused);
}
#ifdef HAVE_PTHREAD_CANCEL
/*
** Pcap cleanup function.
*/
static void pcap_cleanup(void *data) {
struct pcap_data *cur = data;
pcap_close(cur->pd);
}
/*
** Restart iplog. Intended to be a signal handler.
*/
static void iplog_restart(int sig) {
struct running *cur;
extern pthread_mutex_t log_lock;
pthread_mutex_lock(&packet_queue.lock);
xusleep(2000);
pthread_mutex_lock(&log_lock);
for (cur = running ; cur != NULL ;) {
pthread_cancel(cur->pt);
pthread_join(cur->pt, NULL);
cur = dlist_delete(cur, &running);
}
reading = 0;
if (opt_enabled(ANY_SCAN))
destroy_scan_table();
if (opt_enabled(DNS_CACHE))
destroy_dns_cache();
destroy_frag_table();
if (cap_rawsock == true) {
dlist_destroy(plist, pcap_cleanup);
plist = NULL;
}
destroy_filter_list(FIL_TCP);
destroy_filter_list(FIL_UDP);
destroy_filter_list(FIL_ICMP);
pthread_mutex_unlock(&log_lock);
pthread_mutex_unlock(&packet_queue.lock);
mysyslog("Restarting iplog (pid %d).", getpid());
mycloselog();
parse_config(CONFFILE);
check_options();
myopenlog("iplog", LOG_PID | LOG_NDELAY);
siglongjmp(jmpbuf, 1);
/* Quiet gcc */
(void) sig;
}
#endif
/*
** Iplog cleanup function.
*/
static void iplog_cleanup(int sig) {
if (unlink(lockfile) == -1)
mysyslog("Couldn't unlink \"%s\": %s", lockfile, strerror(errno));
mysyslog("Caught signal %d, exiting.", sig);
exit(0);
}
/* vim:ts=4:sw=8:tw=0 */
syntax highlighted by Code2HTML, v. 0.9.1