/* $Id: daemonlogger.c,v 1.16 2007/11/09 20:47:30 roesch Exp $ */ /*************** IMPORTANT DAEMONLOGGER LICENSE TERMS **************** * * This Daemonlogger software is the copyrighted work of Sourcefire, Inc. * (C) 2007 Sourcefire, Inc. All Rights Reserved. This program is free * software; you may use, redistribute and/or modify this software only under * the terms and conditions of the GNU General Public License as published by * the Free Software Foundation; Version 2 with the clarifications and * exceptions described below. If you wish to embed this Daemonlogger * technology into proprietary software, we sell alternative licenses (contact * snort-license@sourcefire.com). * * Note that the GPL requires that any work that contains or is derived from * any GPL licensed work also must be distributed under the GPL. However, * there exists no definition of what is a "derived work." To avoid * misunderstandings, we consider an application to constitute a "derivative * work" for the purpose of this license if it does any of the following: * - Integrates source code from Daemonlogger. * - Includes Daemonlogger copyrighted data files. * - Integrates/includes/aggregates Daemonlogger into a proprietary executable * installer, such as those produced by InstallShield. * - Links to a library or executes a program that does any of the above where * the linked output is not available under the GPL. * * The term "Daemonlogger" should be taken to also include any portions or * derived works of Daemonlogger. This list is not exclusive, but is just * meant to clarify our interpretation of derived works with some common * examples. These restrictions only apply when you actually redistribute * Daemonlogger. For example, nothing stops you from writing and selling a * proprietary front-end to Daemonlogger. Just distribute it by itself, and * point people to http://www.snort.org/dl to download Daemonlogger. * * We don't consider these to be added restrictions on top of the GPL, but just * a clarification of how we interpret "derived works" as it applies to our * GPL-licensed Snort product. This is similar to the way Linus Torvalds has * announced his interpretation of how "derived works" applies to Linux kernel * modules. Our interpretation refers only to Daemonlogger - we don't speak * for any other GPL products. * * If you have any questions about the GPL licensing restrictions on using * Daemonlogger in non-GPL works, we would be happy to help. As mentioned * above, we also offer alternative license to integrate Daemonlogger into * proprietary applications and appliances. These contracts can generally * include a perpetual license as well as providing for priority support and * updates as well as helping to fund the continued development of Daemonlogger * technology. Please email snort-license@sourcefire.com for further * information. * * If you received these files with a written license agreement or contract * stating terms other than the terms above, then that alternative license * agreement takes precedence over these comments. * * Source is provided to this software because we believe users have a right to * know exactly what a program is going to do before they run it. This also * allows you to audit the software for security holes. * * Source code also allows you to port Daemonlogger to new platforms, fix bugs, * and add new features. You are highly encouraged to send your changes to * roesch@sourcefire.com for possible incorporation into the main distribution. * By sending these changes to Sourcefire or one of the Sourcefire-moderated * mailing lists or forums, you are granting to Sourcefire, Inc. the unlimited, * perpetual, non-exclusive right to reuse, modify, and/or relicense the code. * Daemonlogger will always be available Open Source, but this is important * because the inability to relicense code has caused devastating problems for * other Free Software projects (such as KDE and NASM). We also occasionally * relicense the code to third parties as discussed above. If you wish to * specify special license conditions of your contributions, just say so when * you send them. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; including without limitation any implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details at http://www.gnu.org/copyleft/gpl.html, * or in the COPYING file included with Daemonlogger. * */ /* ** Copyright (C) 2006 Sourcefire Inc. All Rights Reserved. ** Author: Martin Roesch */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SUCCESS 0 #define ERROR 1 #define STDBUF 1024 #define GIGABYTE 1 << 30 #define VERSION "1.0.1" typedef enum { MINUTES=1, HOURS, DAYS } interval; static char *interval_names[] = { "none", "minutes", "hours", "days" }; static int count; static int daemon_mode; static int rollover; static int maxfiles; static int filecount; static int showver; static int datalink; static int shutdown_requested; static int restart_requested; static int ringbuffer; static int use_syslog; static int readback_mode; static int snaplen; static int drop_privs_flag; static int chroot_flag; static int rollover_interval; static int flush_flag; static char *interface; static char *retrans_interface; static char *logpath; static char *logfilename; static char *pcap_cmd; static char *readfile; static char *pidfile = "daemonlogger.pid"; static char *pidpath = "/var/run"; static char *true_pid_name; static char *group_name; static char *user_name; static char *chroot_dir; static char logdir[STDBUF]; static u_int32_t rollsize; static time_t lastroll; static time_t nextroll; static pcap_t *pd; static pcap_dumper_t *pdp; static eth_t *eth_retrans; void (*packet_handler)(char *user, struct pcap_pkthdr *pkthdr, u_char *pkt); static int sniff_loop(); static int set_rollover_time(); static void fatal(const char *format, ...) { char buf[STDBUF+1]; va_list ap; va_start(ap, format); vsnprintf(buf, STDBUF, format, ap); if(use_syslog) { syslog(LOG_CONS | LOG_DAEMON | LOG_ERR, "FATAL ERROR: %s", buf); } else { fprintf(stderr, "ERROR: %s\n", buf); fprintf(stderr,"Fatal Error, Quitting..\n"); } va_end(ap); exit(1); } static void msg(const char *format, ...) { char buf[STDBUF+1]; va_list ap; va_start(ap, format); vsnprintf(buf, STDBUF, format, ap); if(use_syslog) { syslog(LOG_DAEMON | LOG_NOTICE, "%s", buf); } else { fprintf(stderr, "%s\n", buf); } va_end(ap); } static int is_valid_path(char *path) { struct stat st; if(path == NULL) return 0; if(stat(path, &st) != 0) return 0; if(!S_ISDIR(st.st_mode) || access(path, W_OK) == -1) { return 0; } return 1; } static int create_pid_file(char *path, char *filename) { char filepath[STDBUF]; char *fp = NULL; char *fn = NULL; char pid_buffer[12]; struct flock lock; int rval; int fd; memset(filepath, 0, STDBUF); if(!filename) fn = pidfile; else fn = filename; if(!path) fp = pidpath; else fp = path; if(is_valid_path(fp)) snprintf(filepath, STDBUF-1, "%s/%s", fp, fn); else fatal("PID path \"%s\" isn't a writeable directory!", fp); true_pid_name = strdup(filename); if((fd = open(filepath, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { return ERROR; } /* pid file locking */ lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_SET; lock.l_len = 0; if (fcntl(fd, F_SETLK, &lock) == -1) { if (errno == EACCES || errno == EAGAIN) { rval = ERROR; } else { rval = ERROR; } close(fd); return rval; } snprintf(pid_buffer, sizeof(pid_buffer), "%d\n", (int) getpid()); ftruncate(fd, 0); write(fd, pid_buffer, strlen(pid_buffer)); return SUCCESS; } int daemonize() { pid_t pid; int fd; pid = fork(); if (pid > 0) exit(0); /* parent */ use_syslog = 1; if (pid < 0) return ERROR; /* new process group */ setsid(); /* close file handles */ if ((fd = open("/dev/null", O_RDWR)) >= 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if (fd > 2) close(fd); } if (pidfile) return create_pid_file(pidpath, pidfile); return SUCCESS; } char *get_filename() { time_t currtime; memset(logdir, 0, STDBUF); currtime = time(NULL); if(logpath != NULL) { if(snprintf(logdir, STDBUF, "%s/%s.%lu", logpath, logfilename, currtime) < 0) return NULL; } else { if(snprintf(logdir, STDBUF, "%s.%lu", logfilename, currtime) < 0) return NULL; } return logdir; } static int go_daemon() { return daemonize(NULL); } static void dl_shutdown(int signal) { msg("Quitting!"); if(retrans_interface != NULL) { eth_close(eth_retrans); } else { if(pdp != NULL) { pcap_dump_flush(pdp); pcap_dump_close(pdp); } } if(pd != NULL) pcap_close(pd); if(true_pid_name != NULL) unlink(true_pid_name); exit(0); } static void quitter(int signal) { shutdown_requested = 1; alarm(1); } static int prune_oldest_file() { DIR *dirp; struct dirent *dp; struct stat sb; time_t oldtime = 0; char *oldname = NULL; char fpath[STDBUF+1]; memset(fpath, 0, STDBUF+1); if(logpath != NULL) { dirp = opendir(logpath); } else dirp = opendir("."); if(dirp == NULL) { msg("opendir failed\n"); return 0; } while((dp = readdir(dirp)) != NULL) { snprintf(fpath, STDBUF, "%s/%s", logpath?logpath:".", dp->d_name); if(stat(fpath, &sb) != 0) msg("stat failed for \"%s\": %s\n", fpath, strerror(errno)); if((sb.st_mode & S_IFMT) == S_IFREG) { if(strstr(dp->d_name, logfilename)) { if(oldtime == 0 || sb.st_mtime < oldtime) { oldtime = sb.st_mtime; if(oldname != NULL) { free(oldname); } oldname = strdup(fpath); } } } } closedir(dirp); msg("[!] Ringbuffer: deleting %s", oldname); if(*oldname != 0) unlink(oldname); return 0; } static int open_log_file() { char *filepath = get_filename(); if(maxfiles == 0 || (maxfiles > 0 && filecount > 0)) { if(maxfiles > 0) { filecount--; if(ringbuffer == 0) msg("%d files to go before quitting", filecount+1); } } else { if(ringbuffer == 0) { msg("Max file count reached, exiting"); quitter(1); return ERROR; } else { prune_oldest_file(); } } if(filepath != NULL) { msg("Logging packets to %s", filepath); if((pdp = pcap_dump_open(pd, filepath)) == NULL) { fatal("Unable to open log file %s\n", pcap_geterr(pd)); } } else return ERROR; return SUCCESS; } static int drop_privs(void) { struct group *gr; struct passwd *pw; char *endptr; int i; int do_setuid = 0; int do_setgid = 0; unsigned long groupid = 0; unsigned long userid = 0; if(group_name != NULL) { do_setgid = 1; if(isdigit(group_name[0]) == 0) { gr = getgrnam(group_name); groupid = gr->gr_gid; } else { groupid = strtoul(group_name, &endptr, 10); } } if(user_name != NULL) { do_setuid = 1; do_setgid = 1; if(isdigit(user_name[0]) == 0) { pw = getpwnam(user_name); userid = pw->pw_uid; } else { userid = strtoul(user_name, &endptr, 10); pw = getpwuid(userid); } if(group_name == NULL) groupid = pw->pw_gid; } if(do_setgid) { if((i = setgid(groupid)) < 0) fatal("Unable to set group ID: %s", strerror(i)); } endgrent(); endpwent(); if(do_setuid) { if(getuid() == 0 && initgroups(user_name, groupid) < 0) fatal("Unable to init group names (%s/%lu)", user_name, groupid); if((i = setuid(userid)) < 0) fatal("Unable to set user ID: %s\n", strerror(i)); } return 0; } char *get_abs_path(char *dir) { char *savedir, *dirp; if(dir == NULL) { return NULL; } if((savedir = getcwd(NULL, 0)) == NULL) { msg("ERROR: getcwd() failed: %s", strerror(errno)); return NULL; } if(chdir(dir) < 0) { msg("ERROR: Can't change to directory: %s\n", dir); free(savedir); return NULL; } dirp = getcwd(NULL, 0); if(chdir(savedir) < 0) { msg("Can't change back to directory: %s\n", dir); free(savedir); return NULL; } free(savedir); return (char *) dirp; } static int set_chroot(void) { char *absdir; int abslen; char *logdir; logdir = get_abs_path(logpath); /* change to the directory */ if(chdir(chroot_dir) != 0) { fatal("set_chroot: Can not chdir to \"%s\": %s\n", chroot_dir, strerror(errno)); } /* always returns an absolute pathname */ absdir = getcwd(NULL, 0); abslen = strlen(absdir); /* make the chroot call */ if(chroot(absdir) < 0) { fatal("Can not chroot to \"%s\": absolute: %s: %s\n", chroot_dir, absdir, strerror(errno)); } if(chdir("/") < 0) { fatal("Can not chdir to \"/\" after chroot: %s\n", strerror(errno)); } return 0; } static int init_retrans() { if((eth_retrans = eth_open(retrans_interface)) == NULL) fatal("init_retrans() eth_open failed\n"); return 0; } static int start_sniffing() { bpf_u_int32 localnet, netmask; /* net addr holders */ struct bpf_program fcode; /* Finite state machine holder */ char errorbuf[PCAP_ERRBUF_SIZE]; /* buffer to put error strings in */ bpf_u_int32 defaultnet = 0xFFFFFF00; if(readback_mode == 0) { if(interface == NULL) { interface = pcap_lookupdev(errorbuf); if(interface == NULL) { fatal("start_sniffing() interface lookup: \n\t%s\n", errorbuf); } } msg("sniffing on interface %s", interface); pd = pcap_open_live(interface, snaplen?snaplen:65535, 1, 500, errorbuf); if(pd == NULL) { fatal("start_sniffing(): interface %s open: %s\n", interface, errorbuf); } } else { msg("Reading network traffic from \"%s\" file.\n", readfile); pd = pcap_open_offline(readfile, errorbuf); if(pd == NULL) { fatal("unable to open file \"%s\" for readback: %s\n", readfile, errorbuf); } snaplen = pcap_snapshot(pd); msg("snaplen = %d\n", snaplen); } if(pcap_lookupnet(interface, &localnet, &netmask, errorbuf) < 0) { msg("start_sniffing() device %s network lookup: " "\t%s", interface, errorbuf); netmask = htonl(defaultnet); } if(pcap_compile(pd, &fcode, pcap_cmd, 1, netmask) < 0) { fatal("start_sniffing() FSM compilation failed: \n\t%s\n" "PCAP command: %s\n", pcap_geterr(pd), pcap_cmd); } /* set the pcap filter */ if(pcap_setfilter(pd, &fcode) < 0) { fatal("start_sniffing() setfilter: \n\t%s\n", pcap_geterr(pd)); } /* get data link type */ datalink = pcap_datalink(pd); if(datalink < 0) { fatal("OpenPcap() datalink grab: \n\t%s\n", pcap_geterr(pd)); } return 0; } static int log_rollover() { msg("Rolling over logfile..."); if(pdp != NULL) { pcap_dump_flush(pdp); pcap_dump_close(pdp); pdp = NULL; } open_log_file(); return SUCCESS; } static void dl_restart() { restart_requested = 0; if(retrans_interface == NULL) { pcap_dump_flush(pdp); pcap_dump_close(pdp); } else { eth_close(eth_retrans); } pcap_close(pd); start_sniffing(); sniff_loop(); } static void restarter(int signal) { msg("Caught SIGHUP, restarting..."); restart_requested = 1; } static char *load_bpf_file(char *filename) { int fd; int readbytes; char *filebuf; char *comment; struct stat buf; if((fd = open(filename, O_RDONLY)) < 0) fatal("Unable to open BPF filter file %s: %s\n", filename, pcap_strerror(errno)); if(fstat(fd, &buf) < 0) fatal("Stat failed on %s: %s\n", filename, pcap_strerror(errno)); filebuf = calloc((unsigned int)buf.st_size + 1, sizeof(unsigned char)); if((readbytes = read(fd, filebuf, (int) buf.st_size)) < 0) fatal("Read failed on %s: %s\n", filename, pcap_strerror(errno)); if(readbytes != buf.st_size) fatal("Read bytes != file bytes on %s (%d != %d)\n", filename, readbytes, (int) buf.st_size); filebuf[(int)buf.st_size] = '\0'; close(fd); /* strip comments and 's */ while((comment = strchr(filebuf, '#')) != NULL) { while(*comment != '\r' && *comment != '\n' && comment != '\0') { *comment++ = ' '; } } return (filebuf); } void packet_dump(char *user, struct pcap_pkthdr *pkthdr, u_char *pkt) { time_t now; if(rollover) { now = time(NULL); if(rollover_interval == 0) { if(lastroll + rollover < now) { log_rollover(); lastroll = now; } } else { if(now > nextroll) { log_rollover(); set_rollover_time(); } } } if(shutdown_requested == 1) dl_shutdown(0); if(restart_requested == 1) dl_restart(); pcap_dump((u_char *) pdp, pkthdr, pkt); if(flush_flag) pcap_dump_flush(pdp); if((u_int32_t)ftell((FILE *) pdp) > rollsize) { log_rollover(); } return; } void packet_retrans(char *user, struct pcap_pkthdr *pkthdr, u_char *pkt) { eth_send(eth_retrans, pkt, pkthdr->caplen); if(shutdown_requested) dl_shutdown(0); if(restart_requested) dl_restart(); return; } static int sniff_loop() { if(chroot_flag) set_chroot(); if(retrans_interface != NULL) { init_retrans(); if(drop_privs_flag) drop_privs(); } else { if(drop_privs_flag) drop_privs(); open_log_file(); } lastroll = time(NULL); /* Read all packets on the device. Continue until cnt packets read */ if(pcap_loop(pd, count, (pcap_handler) packet_handler, NULL) < 0) { msg("pcap_loop: %s", pcap_geterr(pd)); quitter(1); } return SUCCESS; } char *copy_argv(char **argv) { char **p; u_int len = 0; char *buf; char *src, *dst; void ftlerr(char *,...); p = argv; if(*p == 0) return NULL; while(*p) len += strlen(*p++) + 1; buf = (char *) malloc(len); if(buf == NULL) { fatal("malloc() failed: %s\n", strerror(errno)); } p = argv; dst = buf; while((src = *p++) != NULL) { while((*dst++ = *src++) != '\0'); dst[-1] = ' '; } dst[-1] = '\0'; return buf; } static int set_rollover_time() { time_t now; struct tm *curtime; now = time(NULL); curtime = localtime(&now); switch(rollover_interval) { case MINUTES: curtime->tm_min += rollover; curtime->tm_sec = 0; break; case HOURS: curtime->tm_hour += rollover; curtime->tm_min = 0; curtime->tm_sec = 0; break; case DAYS: curtime->tm_mday += rollover; curtime->tm_hour = 0; curtime->tm_min = 0; curtime->tm_sec = 0; break; } nextroll = mktime(curtime); return 0; } static void usage() { printf("USAGE: daemonlogger [-options] \n"); printf(" -c Log packets and exit\n"); printf(" -d Daemonize at startup\n"); printf(" -f Load BPF filter from \n"); printf(" -F Flush the pcap buffer for each packet\n"); printf(" -g Set group ID to \n"); printf(" -h Show this usage statement\n"); printf(" -i Grab packets from interface \n"); printf(" -l Log to directory \n"); printf(" -m Generate log files and quit\n"); printf(" -n Set output filename prefix to \n"); printf(" -o Disable logging, retransmit data from\n" " to \n"); printf(" -p Use for PID filename\n"); printf(" -P Use for PID directory\n"); printf(" -r Activate ringbuffer mode\n"); printf(" -R Read packets from \n"); printf(" -s Rollover the log file every \n"); printf(" -S Set the number of bytes per packet to " "capture to \n"); printf(" -t