/* ulogd, Version $LastChangedRevision$ * * $Id$ * * userspace logging daemon for the iptables ULOG target * of the linux 2.4 netfilter subsystem. * * (C) 2000-2003 by Harald Welte * * 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$ * * Modifications: * 14 Jun 2001 Martin Josefsson * - added SIGHUP handler for logfile cycling * * 10 Feb 2002 Alessandro Bono * - added support for non-fork mode * - added support for logging to stdout * * 09 Sep 2003 Magnus Boden * - added support for more flexible multi-section conffile * * 20 Apr 2004 Nicolas Pougetoux * - added suppurt for seteuid() */ #define ULOGD_VERSION "1.23" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Size of the socket recevive memory. Should be at least the same size as the * 'nlbufsiz' module loadtime parameter of ipt_ULOG.o * If you have _big_ in-kernel queues, you may have to increase this number. ( * --qthreshold 100 * 1500 bytes/packet = 150kB */ #define ULOGD_RMEM_DEFAULT 131071 /* Size of the receive buffer for the netlink socket. Should be at least of * RMEM_DEFAULT size. */ #define ULOGD_BUFSIZE_DEFAULT 150000 #ifdef DEBUG #define DEBUGP(format, args...) fprintf(stderr, format, ## args) #else #define DEBUGP(format, args...) #endif /* default config parameters, if not changed in configfile */ #ifndef ULOGD_LOGFILE_DEFAULT #define ULOGD_LOGFILE_DEFAULT "/var/log/ulogd.log" #endif #ifndef ULOGD_NLGROUP_DEFAULT #define ULOGD_NLGROUP_DEFAULT 32 #endif /* where to look for the config file */ #ifndef ULOGD_CONFIGFILE #define ULOGD_CONFIGFILE "/etc/ulogd.conf" #endif /* global variables */ static struct ipulog_handle *libulog_h; /* our libipulog handle */ static unsigned char* libulog_buf; /* the receive buffer */ static FILE *logfile = NULL; /* logfile pointer */ static char *ulogd_configfile = ULOGD_CONFIGFILE; /* linked list for all registered interpreters */ static ulog_interpreter_t *ulogd_interpreters; /* linked list for all registered output targets */ static ulog_output_t *ulogd_outputs; /*********************************************************************** * INTERPRETER AND KEY HASH FUNCTIONS (new in 0.9) ***********************************************************************/ /* We keep hashtables of interpreters and registered keys. The hash-tables * are allocated dynamically at program load time. You may control the * allocation granularity of both hashes (i.e. the amount of hashtable * entries are allocated at one time) through modification of the constants * INTERH_ALLOC_GRAN and KEYH_ALLOC_GRAN */ /* allocation granularith */ #define INTERH_ALLOC_GRAN 5 /* hashtable for all registered interpreters */ static ulog_interpreter_t **ulogd_interh; /* current hashtable size */ static unsigned int ulogd_interh_ids_alloc; /* total number of registered ids */ static unsigned int ulogd_interh_ids; /* allocate a new interpreter id and write it into the interpreter struct */ static unsigned int interh_allocid(ulog_interpreter_t *ip) { unsigned int id; id = ++ulogd_interh_ids; if (id >= ulogd_interh_ids_alloc) { if (!ulogd_interh) ulogd_interh = (ulog_interpreter_t **) malloc(INTERH_ALLOC_GRAN * sizeof(ulog_interpreter_t)); else ulogd_interh = (ulog_interpreter_t **) realloc(ulogd_interh, (INTERH_ALLOC_GRAN + ulogd_interh_ids_alloc) * sizeof(ulog_interpreter_t)); ulogd_interh_ids_alloc += INTERH_ALLOC_GRAN; } ip->id = id; ulogd_interh[id] = ip; return id; } /* get interpreter id by name */ unsigned int interh_getid(const char *name) { unsigned int i; for (i = 1; i <= ulogd_interh_ids; i++) if (!strcmp(name, (ulogd_interh[i])->name)) return i; return 0; } #ifdef DEBUG /* dump out the contents of the interpreter hash */ static void interh_dump(void) { unsigned int i; for (i = 1; i <= ulogd_interh_ids; i++) ulogd_log(ULOGD_DEBUG, "ulogd_interh[%d] = %s\n", i, (ulogd_interh[i])->name); } #endif /* key hash allocation granularity */ #define KEYH_ALLOC_GRAN 20 /* hash table for key ids */ struct ulogd_keyh_entry *ulogd_keyh; /* current size of the hashtable */ static unsigned int ulogd_keyh_ids_alloc; /* total number of registered keys */ static unsigned int ulogd_keyh_ids; /* allocate a new key_id */ static unsigned int keyh_allocid(ulog_interpreter_t *ip, unsigned int offset, const char *name) { unsigned int id; id = ++ulogd_keyh_ids; if (id >= ulogd_keyh_ids_alloc) { if (!ulogd_keyh) { ulogd_keyh = (struct ulogd_keyh_entry *) malloc(KEYH_ALLOC_GRAN * sizeof(struct ulogd_keyh_entry)); if (!ulogd_keyh) { ulogd_log(ULOGD_ERROR, "OOM!\n"); return 0; } } else { ulogd_keyh = (struct ulogd_keyh_entry *) realloc(ulogd_keyh, (KEYH_ALLOC_GRAN +ulogd_keyh_ids_alloc) * sizeof(struct ulogd_keyh_entry)); if (!ulogd_keyh) { ulogd_log(ULOGD_ERROR, "OOM!\n"); return 0; } } ulogd_keyh_ids_alloc += KEYH_ALLOC_GRAN; } ulogd_keyh[id].interp = ip; ulogd_keyh[id].offset = offset; ulogd_keyh[id].name = name; return id; } #ifdef DEBUG /* dump the keyhash to standard output */ static void keyh_dump(void) { unsigned int i; printf("dumping keyh\n"); for (i = 1; i <= ulogd_keyh_ids; i++) printf("ulogd_keyh[%lu] = %s:%u\n", i, ulogd_keyh[i].interp->name, ulogd_keyh[i].offset); } #endif /* get keyid by name */ unsigned int keyh_getid(const char *name) { unsigned int i; for (i = 1; i <= ulogd_keyh_ids; i++) if (!strcmp(name, ulogd_keyh[i].name)) return i; return 0; } /* get key name by keyid */ char *keyh_getname(unsigned int id) { if (id > ulogd_keyh_ids) { ulogd_log(ULOGD_NOTICE, "keyh_getname called with invalid id%u\n", id); return NULL; } return ulogd_keyh[id].interp->name; } /* get result for given key id. does not check if result valid */ ulog_iret_t *keyh_getres(unsigned int id) { ulog_iret_t *ret; if (id > ulogd_keyh_ids) { ulogd_log(ULOGD_NOTICE, "keyh_getres called with invalid id %d\n", id); return NULL; } ret = &ulogd_keyh[id].interp->result[ulogd_keyh[id].offset]; return ret; } /*********************************************************************** * INTERPRETER MANAGEMENT ***********************************************************************/ /* try to lookup a registered interpreter for a given name */ static ulog_interpreter_t *find_interpreter(const char *name) { unsigned int id; id = interh_getid(name); if (!id) return NULL; return ulogd_interh[id]; } /* the function called by all interpreter plugins for registering their * target. */ void register_interpreter(ulog_interpreter_t *me) { unsigned int i; /* check if we already have an interpreter with this name */ if (find_interpreter(me->name)) { ulogd_log(ULOGD_NOTICE, "interpreter `%s' already registered\n", me->name); return; } ulogd_log(ULOGD_INFO, "registering interpreter `%s'\n", me->name); /* allocate a new interpreter id for it */ if (!interh_allocid(me)) { ulogd_log(ULOGD_ERROR, "unable to obtain interh_id for " "interpreter '%s'\n", me->name); return; } /* - allocate one keyh_id for each result of this interpreter * - link the elements to each other */ for (i = 0; i < me->key_num; i++) { if (!keyh_allocid(me, i, me->result[i].key)) { ulogd_log(ULOGD_ERROR, "unable to obtain keyh_id " "for interpreter %s, key %d", me->name, me->result[i].key); continue; } if (i != me->key_num - 1) me->result[i].next = &me->result[i+1]; } /* all work done, we can prepend the new interpreter to the list */ if (ulogd_interpreters) me->result[me->key_num - 1].next = &ulogd_interpreters->result[0]; me->next = ulogd_interpreters; ulogd_interpreters = me; } /*********************************************************************** * OUTPUT MANAGEMENT ***********************************************************************/ /* try to lookup a registered output plugin for a given name */ static ulog_output_t *find_output(const char *name) { ulog_output_t *ptr; for (ptr = ulogd_outputs; ptr; ptr = ptr->next) { if (strcmp(name, ptr->name) == 0) return ptr; } return NULL; } /* the function called by all output plugins for registering themselves */ void register_output(ulog_output_t *me) { if (find_output(me->name)) { ulogd_log(ULOGD_NOTICE, "output `%s' already registered\n", me->name); exit(EXIT_FAILURE); } ulogd_log(ULOGD_INFO, "registering output `%s'\n", me->name); me->next = ulogd_outputs; ulogd_outputs = me; } /*********************************************************************** * MAIN PROGRAM ***********************************************************************/ static FILE syslog_dummy; static inline int ulogd2syslog_level(int level) { int syslog_level = LOG_WARNING; switch (level) { case ULOGD_DEBUG: syslog_level = LOG_DEBUG; break; case ULOGD_INFO: syslog_level = LOG_INFO; break; case ULOGD_NOTICE: syslog_level = LOG_NOTICE; break; case ULOGD_ERROR: syslog_level = LOG_ERR; break; case ULOGD_FATAL: syslog_level = LOG_CRIT; break; } return syslog_level; } /* propagate results to all registered output plugins */ static void propagate_results(ulog_iret_t *ret) { ulog_output_t *p; for (p = ulogd_outputs; p; p = p->next) { (*p->output)(ret); } } /* clean results (set all values to 0 and free pointers) */ static void clean_results(ulog_iret_t *ret) { ulog_iret_t *r; for (r = ret; r; r = r->next) { if (r->flags & ULOGD_RETF_FREE) { free(r->value.ptr); r->value.ptr = NULL; } memset(&r->value, 0, sizeof(r->value)); r->flags &= ~ULOGD_RETF_VALID; } } /* call all registered interpreters and hand the results over to * propagate_results */ static void handle_packet(ulog_packet_msg_t *pkt) { ulog_iret_t *ret; ulog_iret_t *allret = NULL; ulog_interpreter_t *ip; unsigned int i,j; /* If there are no interpreters registered yet, * ignore this packet */ if (!ulogd_interh_ids) { ulogd_log(ULOGD_NOTICE, "packet received, but no interpreters found\n"); return; } for (i = 1; i <= ulogd_interh_ids; i++) { ip = ulogd_interh[i]; /* call interpreter */ if ((ret = ((ip)->interp)(ip, pkt))) { /* create references for result linked-list */ for (j = 0; j < ip->key_num; j++) { if (IS_VALID(ip->result[j])) { ip->result[j].cur_next = allret; allret = &ip->result[j]; } } } } propagate_results(allret); clean_results(ulogd_interpreters->result); } /* plugin loader to dlopen() a plugins */ static int load_plugin(char *file) { if (!dlopen(file, RTLD_NOW)) { ulogd_log(ULOGD_ERROR, "load_plugins: '%s': %s\n", file, dlerror()); return 1; } return 0; } /* open the logfile */ static int logfile_open(const char *name) { if (!strcmp(name, "syslog")) { openlog("ulogd", LOG_PID, LOG_DAEMON); logfile = &syslog_dummy; } else if (!strcmp(name,"stdout")) logfile = stdout; else { logfile = fopen(name, "a"); if (!logfile) { fprintf(stderr, "ERROR: can't open logfile %s: %s\n", name, strerror(errno)); exit(2); } } ulogd_log(ULOGD_INFO, "ulogd Version %s starting\n", ULOGD_VERSION); return 0; } /* wrapper to handle conffile error codes */ static int parse_conffile(const char *section, config_entry_t *ce) { int err; err = config_parse_file(section, ce); switch(err) { case 0: return 0; break; case -ERROPEN: ulogd_log(ULOGD_ERROR, "unable to open configfile: %s\n", ulogd_configfile); break; case -ERRMAND: ulogd_log(ULOGD_ERROR, "mandatory option \"%s\" not found\n", config_errce->key); break; case -ERRMULT: ulogd_log(ULOGD_ERROR, "option \"%s\" occurred more than once\n", config_errce->key); break; case -ERRUNKN: ulogd_log(ULOGD_ERROR, "unknown config key \"%s\"\n", config_errce->key); break; case -ERRSECTION: ulogd_log(ULOGD_ERROR, "section \"%s\" not found\n", section); break; } return 1; } /* configuration directives of the main program */ static config_entry_t logf_ce = { NULL, "logfile", CONFIG_TYPE_STRING, CONFIG_OPT_NONE, 0, { string: ULOGD_LOGFILE_DEFAULT } }; static config_entry_t bufsiz_ce = { &logf_ce, "bufsize", CONFIG_TYPE_INT, CONFIG_OPT_NONE, 0, { value: ULOGD_BUFSIZE_DEFAULT } }; static config_entry_t plugin_ce = { &bufsiz_ce, "plugin", CONFIG_TYPE_CALLBACK, CONFIG_OPT_MULTI, 0, { parser: &load_plugin } }; static config_entry_t nlgroup_ce = { &plugin_ce, "nlgroup", CONFIG_TYPE_INT, CONFIG_OPT_NONE, 0, { value: ULOGD_NLGROUP_DEFAULT } }; static config_entry_t loglevel_ce = { &nlgroup_ce, "loglevel", CONFIG_TYPE_INT, CONFIG_OPT_NONE, 0, { value: ULOGD_NOTICE } }; static config_entry_t rmem_ce = { &loglevel_ce, "rmem", CONFIG_TYPE_INT, CONFIG_OPT_NONE, 0, { value: ULOGD_RMEM_DEFAULT } }; /* log message to the logfile */ void __ulogd_log(int level, char *file, int line, const char *format, ...) { char *timestr; va_list ap; time_t tm; FILE *outfd; /* log only messages which have level at least as high as loglevel */ if (level < loglevel_ce.u.value) return; if (logfile == &syslog_dummy) { /* FIXME: this omit's the 'file' string */ va_start(ap, format); vsyslog(ulogd2syslog_level(level), format, ap); va_end(ap); } else { if (logfile) outfd = logfile; else outfd = stderr; va_start(ap, format); tm = time(NULL); timestr = ctime(&tm); timestr[strlen(timestr)-1] = '\0'; fprintf(outfd, "%s <%1.1d> %s:%d ", timestr, level, file, line); vfprintf(outfd, format, ap); va_end(ap); /* flush glibc's buffer */ fflush(outfd); } } static void sigterm_handler(int signal) { ulog_output_t *p; ulogd_log(ULOGD_NOTICE, "sigterm received, exiting\n"); ipulog_destroy_handle(libulog_h); free(libulog_buf); if (logfile != stdout && logfile != &syslog_dummy) fclose(logfile); for (p = ulogd_outputs; p; p = p->next) { if (p->fini) (*p->fini)(); } exit(0); } static void sighup_handler(int signal) { ulog_output_t *p; if (logfile != stdout && logfile != &syslog_dummy) { fclose(logfile); logfile = fopen(logf_ce.u.string, "a"); if (!logfile) sigterm_handler(signal); } ulogd_log(ULOGD_NOTICE, "sighup received, calling plugin handlers\n"); for (p = ulogd_outputs; p; p = p->next) { if (p->signal) (*p->signal)(SIGHUP); } } static void print_usage(void) { /* FIXME */ printf("ulogd Version %s\n", ULOGD_VERSION); printf("Copyright (C) 2000-2005 Harald Welte " "\n"); printf("This is free software with ABSOLUTELY NO WARRANTY.\n\n"); printf("Parameters:\n"); printf("\t-h --help\tThis help page\n"); printf("\t-V --version\tPrint version information\n"); printf("\t-d --daemon\tDaemonize (fork into background)\n"); printf("\t-c --configfile\tUse alternative Configfile\n"); printf("\t-u --uid\tChange UID/GID\n"); } static struct option opts[] = { { "version", 0, NULL, 'V' }, { "daemon", 0, NULL, 'd' }, { "help", 0, NULL, 'h' }, { "configfile", 1, NULL, 'c'}, { "uid", 1, NULL, 'u' }, { 0 } }; int main(int argc, char* argv[]) { int len; int argch; int daemonize = 0; int change_uid = 0; char *user = NULL; struct passwd *pw; uid_t uid = 0; gid_t gid = 0; ulog_packet_msg_t *upkt; ulog_output_t *p; while ((argch = getopt_long(argc, argv, "c:dh::Vu:", opts, NULL)) != -1) { switch (argch) { default: case '?': if (isprint(optopt)) fprintf(stderr, "Unknown option `-%c'.\n", optopt); else fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt); print_usage(); exit(1); break; case 'h': print_usage(); exit(0); break; case 'd': daemonize = 1; break; case 'V': printf("ulogd Version %s\n", ULOGD_VERSION); printf("Copyright (C) 2000-2005 Harald Welte " "\n"); exit(0); break; case 'c': ulogd_configfile = optarg; break; case 'u': change_uid = 1; user = strdup(optarg); pw = getpwnam(user); if (!pw) { printf("Unknown user %s.\n", user); free(user); exit(1); } uid = pw->pw_uid; gid = pw->pw_gid; break; } } if (config_register_file(ulogd_configfile)) { ulogd_log(ULOGD_FATAL, "error registering configfile \"%s\"\n", ulogd_configfile); exit(1); } /* parse config file */ if (parse_conffile("global", &rmem_ce)) { ulogd_log(ULOGD_FATAL, "parse_conffile\n"); exit(1); } /* allocate a receive buffer */ libulog_buf = (unsigned char *) malloc(bufsiz_ce.u.value); if (!libulog_buf) { ulogd_log(ULOGD_FATAL, "unable to allocate receive buffer" "of %d bytes\n", bufsiz_ce.u.value); ipulog_perror(NULL); exit(1); } /* create ipulog handle */ libulog_h = ipulog_create_handle(ipulog_group2gmask(nlgroup_ce.u.value), rmem_ce.u.value); if (!libulog_h) { /* if some error occurrs, print it to stderr */ ulogd_log(ULOGD_FATAL, "unable to create ipulogd handle\n"); ipulog_perror(NULL); exit(1); } if (change_uid) { ulogd_log(ULOGD_NOTICE, "Changing UID / GID\n"); if (setgid(gid)) { ulogd_log(ULOGD_FATAL, "can't set GID\n"); ipulog_perror(NULL); exit(1); } if (setegid(gid)) { ulogd_log(ULOGD_FATAL, "can't sett effective GID\n"); ipulog_perror(NULL); exit(1); } if (initgroups(user, gid)) { ulogd_log(ULOGD_FATAL, "can't set user secondary GID\n"); ipulog_perror(NULL); exit(1); } if (setuid(uid)) { ulogd_log(ULOGD_FATAL, "can't set UID\n"); ipulog_perror(NULL); exit(1); } if (seteuid(uid)) { ulogd_log(ULOGD_FATAL, "can't set effective UID\n"); ipulog_perror(NULL); exit(1); } } logfile_open(logf_ce.u.string); for (p = ulogd_outputs; p; p = p->next) { if (p->init) (*p->init)(); } #ifdef DEBUG /* dump key and interpreter hash */ interh_dump(); keyh_dump(); #endif if (daemonize){ if (fork()) { exit(0); } if (logfile != stdout) fclose(stdout); fclose(stderr); fclose(stdin); setsid(); } /* send SIGINT to the term handler, since they hit CTRL-C */ signal(SIGINT, &sigterm_handler); signal(SIGHUP, &sighup_handler); signal(SIGTERM, &sigterm_handler); ulogd_log(ULOGD_INFO, "initialization finished, entering main loop\n"); /* endless loop receiving packets and handling them over to * handle_packet */ while ((len = ipulog_read(libulog_h, libulog_buf, bufsiz_ce.u.value, 1))) { if (len <= 0) { /* this is not supposed to happen */ ulogd_log(ULOGD_ERROR, "ipulog_read == %d! " "ipulog_errno == %d, errno = %d\n", len, ipulog_errno, errno); } else { while ((upkt = ipulog_get_packet(libulog_h, libulog_buf, len))) { DEBUGP("==> packet received\n"); handle_packet(upkt); } } } /* hackish, but result is the same */ sigterm_handler(SIGTERM); return(0); }