diff options
Diffstat (limited to 'framework/src/audit/src')
67 files changed, 27598 insertions, 0 deletions
diff --git a/framework/src/audit/src/Makefile.am b/framework/src/audit/src/Makefile.am new file mode 100644 index 00000000..8d1af865 --- /dev/null +++ b/framework/src/audit/src/Makefile.am @@ -0,0 +1,57 @@ +# Makefile.am-- +# Copyright 2004-2006, 2008,2011-15 Red Hat Inc., Durham, North Carolina. +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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 +# +# Authors: +# Steve Grubb <sgrubb@redhat.com> +# + +CONFIG_CLEAN_FILES = *.rej *.orig +AUTOMAKE_OPTIONS = no-dependencies +SUBDIRS = test +AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/lib -I${top_srcdir}/src/libev -I${top_srcdir}/auparse +sbin_PROGRAMS = auditd auditctl aureport ausearch autrace +AM_CFLAGS = -D_GNU_SOURCE +noinst_HEADERS = auditd-config.h auditd-event.h auditd-listen.h ausearch-llist.h ausearch-options.h auditctl-llist.h aureport-options.h ausearch-parse.h aureport-scan.h ausearch-lookup.h ausearch-int.h auditd-dispatch.h ausearch-string.h ausearch-nvpair.h ausearch-common.h ausearch-avc.h ausearch-time.h ausearch-lol.h auditctl-listing.h ausearch-checkpt.h + +auditd_SOURCES = auditd.c auditd-event.c auditd-config.c auditd-reconfig.c auditd-sendmail.c auditd-dispatch.c +if ENABLE_LISTENER +auditd_SOURCES += auditd-listen.c +endif +auditd_CFLAGS = -fPIE -DPIE -g -D_REENTRANT -D_GNU_SOURCE -fno-strict-aliasing -pthread +auditd_LDFLAGS = -pie -Wl,-z,relro -Wl,-z,now +auditd_DEPENDENCIES = mt/libauditmt.a libev/libev.a +auditd_LDADD = @LIBWRAP_LIBS@ -Llibev -lev -Lmt -lauditmt -lpthread -lrt -lm $(gss_libs) + +auditctl_SOURCES = auditctl.c auditctl-llist.c delete_all.c auditctl-listing.c +auditctl_CFLAGS = -fPIE -DPIE -g -D_GNU_SOURCE +auditctl_LDFLAGS = -pie -Wl,-z,relro -Wl,-z,now +auditctl_LDADD = -L${top_builddir}/lib -laudit -L${top_builddir}/auparse -lauparse + +aureport_SOURCES = aureport.c auditd-config.c ausearch-llist.c aureport-options.c ausearch-string.c ausearch-parse.c aureport-scan.c aureport-output.c ausearch-lookup.c ausearch-int.c ausearch-time.c ausearch-nvpair.c ausearch-avc.c ausearch-lol.c +aureport_LDADD = -L${top_builddir}/lib -laudit + +ausearch_SOURCES = ausearch.c auditd-config.c ausearch-llist.c ausearch-options.c ausearch-report.c ausearch-match.c ausearch-string.c ausearch-parse.c ausearch-int.c ausearch-time.c ausearch-nvpair.c ausearch-lookup.c ausearch-avc.c ausearch-lol.c ausearch-checkpt.c +ausearch_LDADD = -L${top_builddir}/lib -laudit -L${top_builddir}/auparse -lauparse + +autrace_SOURCES = autrace.c delete_all.c auditctl-llist.c +autrace_LDADD = -L${top_builddir}/lib -laudit + +mt/libauditmt.a: + make -C mt +libev/libev.a: + make -C libev diff --git a/framework/src/audit/src/auditctl-listing.c b/framework/src/audit/src/auditctl-listing.c new file mode 100644 index 00000000..88dac6c8 --- /dev/null +++ b/framework/src/audit/src/auditctl-listing.c @@ -0,0 +1,577 @@ +/* auditctl-listing.c -- + * Copyright 2014 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "auditctl-listing.h" +#include "private.h" +#include "auditctl-llist.h" +#include "auparse-idata.h" + +/* Global vars */ +static llist l; +static int printed; +extern int list_requested, interpret; +extern char key[AUDIT_MAX_KEY_LEN+1]; +extern const char key_sep[2]; + +/* + * Returns 1 if rule should be printed & 0 if not + */ +int key_match(const struct audit_rule_data *r) +{ + int i; + size_t boffset = 0; + + if (key[0] == 0) + return 1; + + // At this point, we have a key + for (i = 0; i < r->field_count; i++) { + int field = r->fields[i] & ~AUDIT_OPERATORS; + if (field == AUDIT_FILTERKEY) { + char *keyptr; + if (asprintf(&keyptr, "%.*s", r->values[i], + &r->buf[boffset]) < 0) + keyptr = NULL; + else if (strstr(keyptr, key)) { + free(keyptr); + return 1; + } + free(keyptr); + } + if (((field >= AUDIT_SUBJ_USER && field <= AUDIT_OBJ_LEV_HIGH) + && field != AUDIT_PPID) || field == AUDIT_WATCH || + field == AUDIT_DIR || field == AUDIT_FILTERKEY) { + boffset += r->values[i]; + } + } + return 0; +} + +/* + * This function detects if we have a watch. A watch is detected when we + * have syscall == all and a perm field. + */ +static int is_watch(const struct audit_rule_data *r) +{ + int i, perm = 0, all = 1; + + for (i = 0; i < r->field_count; i++) { + int field = r->fields[i] & ~AUDIT_OPERATORS; + if (field == AUDIT_PERM) + perm = 1; + // Watches can have only 4 field types + if (field != AUDIT_PERM && field != AUDIT_FILTERKEY && + field != AUDIT_DIR && field != AUDIT_WATCH) + return 0; + } + + if (((r->flags & AUDIT_FILTER_MASK) != AUDIT_FILTER_USER) && + ((r->flags & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK) && + ((r->flags & AUDIT_FILTER_MASK) != AUDIT_FILTER_EXCLUDE)) { + for (i = 0; i < (AUDIT_BITMASK_SIZE-1); i++) { + if (r->mask[i] != (uint32_t)~0) { + all = 0; + break; + } + } + } + if (perm && all) + return 1; + return 0; +} + +static int print_arch(unsigned int value, int op) +{ + int machine; + _audit_elf = value; + machine = audit_elf_to_machine(_audit_elf); + if (machine < 0) + printf(" -F arch%s0x%X", audit_operator_to_symbol(op), + (unsigned)value); + else { + if (interpret == 0) { + if (__AUDIT_ARCH_64BIT & _audit_elf) + printf(" -F arch%sb64", + audit_operator_to_symbol(op)); + else + printf(" -F arch%sb32", + audit_operator_to_symbol(op)); + } else { + const char *ptr = audit_machine_to_name(machine); + printf(" -F arch%s%s", audit_operator_to_symbol(op), + ptr); + } + } + return machine; +} + +static int print_syscall(const struct audit_rule_data *r, unsigned int *sc) +{ + int count = 0; + int all = 1; + unsigned int i; + int machine = audit_detect_machine(); + + /* Rules on the following filters do not take a syscall */ + if (((r->flags & AUDIT_FILTER_MASK) == AUDIT_FILTER_USER) || + ((r->flags & AUDIT_FILTER_MASK) == AUDIT_FILTER_TASK) || + ((r->flags &AUDIT_FILTER_MASK) == AUDIT_FILTER_EXCLUDE)) + return 0; + + /* See if its all or specific syscalls */ + for (i = 0; i < (AUDIT_BITMASK_SIZE-1); i++) { + if (r->mask[i] != (uint32_t)~0) { + all = 0; + break; + } + } + + if (all) { + printf(" -S all"); + count = i; + } else for (i = 0; i < AUDIT_BITMASK_SIZE * 32; i++) { + int word = AUDIT_WORD(i); + int bit = AUDIT_BIT(i); + if (r->mask[word] & bit) { + const char *ptr; + if (_audit_elf) + machine = audit_elf_to_machine(_audit_elf); + if (machine < 0) + ptr = NULL; + else + ptr = audit_syscall_to_name(i, machine); + if (!count) + printf(" -S "); + if (ptr) + printf("%s%s", !count ? "" : ",", ptr); + else + printf("%s%d", !count ? "" : ",", i); + count++; + *sc = i; + } + } + return count; +} + +static void print_field_cmp(int value, int op) +{ + switch (value) + { + case AUDIT_COMPARE_UID_TO_OBJ_UID: + printf(" -C uid%sobj_uid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_GID_TO_OBJ_GID: + printf(" -C gid%sobj_gid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_EUID_TO_OBJ_UID: + printf(" -C euid%sobj_uid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_EGID_TO_OBJ_GID: + printf(" -C egid%sobj_gid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_AUID_TO_OBJ_UID: + printf(" -C auid%sobj_uid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_SUID_TO_OBJ_UID: + printf(" -C suid%sobj_uid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_SGID_TO_OBJ_GID: + printf(" -C sgid%sobj_gid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_FSUID_TO_OBJ_UID: + printf(" -C fsuid%sobj_uid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_FSGID_TO_OBJ_GID: + printf(" -C fsgid%sobj_gid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_UID_TO_AUID: + printf(" -C uid%sauid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_UID_TO_EUID: + printf(" -C uid%seuid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_UID_TO_FSUID: + printf(" -C uid%sfsuid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_UID_TO_SUID: + printf(" -C uid%ssuid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_AUID_TO_FSUID: + printf(" -C auid%sfsuid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_AUID_TO_SUID: + printf(" -C auid%ssuid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_AUID_TO_EUID: + printf(" -C auid%seuid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_EUID_TO_SUID: + printf(" -C euid%ssuid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_EUID_TO_FSUID: + printf(" -C euid%sfsuid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_SUID_TO_FSUID: + printf(" -C suid%sfsuid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_GID_TO_EGID: + printf(" -C gid%segid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_GID_TO_FSGID: + printf(" -C gid%sfsgid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_GID_TO_SGID: + printf(" -C gid%ssgid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_EGID_TO_FSGID: + printf(" -C egid%sfsgid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_EGID_TO_SGID: + printf(" -C egid%ssgid", + audit_operator_to_symbol(op)); + break; + case AUDIT_COMPARE_SGID_TO_FSGID: + printf(" -C sgid%sfsgid", + audit_operator_to_symbol(op)); + break; + } +} + +/* + * This function prints 1 rule from the kernel reply + */ +static void print_rule(const struct audit_rule_data *r) +{ + unsigned int i, count = 0, sc = 0; + size_t boffset = 0; + int mach = -1, watch = is_watch(r); + unsigned long long a0 = 0, a1 = 0; + + if (!watch) { /* This is syscall auditing */ + printf("-a %s,%s", + audit_action_to_name((int)r->action), + audit_flag_to_name(r->flags)); + + // Now find the arch and print it + for (i = 0; i < r->field_count; i++) { + int field = r->fields[i] & ~AUDIT_OPERATORS; + if (field == AUDIT_ARCH) { + int op = r->fieldflags[i] & AUDIT_OPERATORS; + mach = print_arch(r->values[i], op); + } + } + // And last do the syscalls + count = print_syscall(r, &sc); + } + + // Now iterate over the fields + for (i = 0; i < r->field_count; i++) { + const char *name; + int op = r->fieldflags[i] & AUDIT_OPERATORS; + int field = r->fields[i] & ~AUDIT_OPERATORS; + + if (field == AUDIT_ARCH) + continue; // already printed + + name = audit_field_to_name(field); + if (name) { + // Special cases to print the different field types + // in a meaningful way. + if (field == AUDIT_MSGTYPE) { + if (!audit_msg_type_to_name(r->values[i])) + printf(" -F %s%s%d", name, + audit_operator_to_symbol(op), + r->values[i]); + else + printf(" -F %s%s%s", name, + audit_operator_to_symbol(op), + audit_msg_type_to_name( + r->values[i])); + } else if ((field >= AUDIT_SUBJ_USER && + field <= AUDIT_OBJ_LEV_HIGH) + && field != AUDIT_PPID) { + printf(" -F %s%s%.*s", name, + audit_operator_to_symbol(op), + r->values[i], &r->buf[boffset]); + boffset += r->values[i]; + } else if (field == AUDIT_WATCH) { + if (watch) + printf("-w %.*s", r->values[i], + &r->buf[boffset]); + else + printf(" -F path=%.*s", r->values[i], + &r->buf[boffset]); + boffset += r->values[i]; + } else if (field == AUDIT_DIR) { + if (watch) + printf("-w %.*s/", r->values[i], + &r->buf[boffset]); + else + printf(" -F dir=%.*s", r->values[i], + &r->buf[boffset]); + + boffset += r->values[i]; + } else if (field == AUDIT_FILTERKEY) { + char *rkey, *ptr, *saved; + if (asprintf(&rkey, "%.*s", r->values[i], + &r->buf[boffset]) < 0) + rkey = NULL; + boffset += r->values[i]; + ptr = strtok_r(rkey, key_sep, &saved); + while (ptr) { + if (watch) + printf(" -k %s", ptr); + else + printf(" -F key=%s", ptr); + ptr = strtok_r(NULL, key_sep, &saved); + } + free(rkey); + } else if (field == AUDIT_PERM) { + char perms[5]; + int val=r->values[i]; + perms[0] = 0; + if (val & AUDIT_PERM_READ) + strcat(perms, "r"); + if (val & AUDIT_PERM_WRITE) + strcat(perms, "w"); + if (val & AUDIT_PERM_EXEC) + strcat(perms, "x"); + if (val & AUDIT_PERM_ATTR) + strcat(perms, "a"); + if (watch) + printf(" -p %s", perms); + else + printf(" -F perm=%s", perms); + } else if (field == AUDIT_INODE) { + // This is unsigned + printf(" -F %s%s%u", name, + audit_operator_to_symbol(op), + r->values[i]); + } else if (field == AUDIT_FIELD_COMPARE) { + print_field_cmp(r->values[i], op); + } else if (field >= AUDIT_ARG0 && field <= AUDIT_ARG3){ + if (field == AUDIT_ARG0) + a0 = r->values[i]; + else if (field == AUDIT_ARG1) + a1 = r->values[i]; + + // Show these as hex + if (count > 1 || interpret == 0) + printf(" -F %s%s0x%X", name, + audit_operator_to_symbol(op), + r->values[i]); + else { // Use ignore to mean interpret + const char *out; + idata id; + char val[32]; + int type; + + id.syscall = sc; + id.machine = mach; + id.a0 = a0; + id.a1 = a1; + id.name = name; + snprintf(val, 32, "%x", r->values[i]); + id.val = val; + type = auparse_interp_adjust_type( + AUDIT_SYSCALL, name, val); + out = auparse_do_interpretation(type, + &id); + printf(" -F %s%s%s", name, + audit_operator_to_symbol(op), + out); + free((void *)out); + } + } else if (field == AUDIT_EXIT) { + int e = abs((int)r->values[i]); + const char *err = audit_errno_to_name(e); + + if (((int)r->values[i] < 0) && err) + printf(" -F %s%s-%s", name, + audit_operator_to_symbol(op), + err); + else + printf(" -F %s%s%d", name, + audit_operator_to_symbol(op), + (int)r->values[i]); + } else { + // The default is signed decimal + printf(" -F %s%s%d", name, + audit_operator_to_symbol(op), + r->values[i]); + } + } else { + // The field name is unknown + printf(" f%d%s%d", r->fields[i], + audit_operator_to_symbol(op), + r->values[i]); + } + } + printf("\n"); +} + +void audit_print_init(void) +{ + printed = 0; + list_create(&l); +} + +const char *get_enable(unsigned e) +{ + switch (e) + { + case 0: + return "disable"; + case 1: + return "enabled"; + case 2: + return "enabl;ed+immutable"; + default: + return "unknown"; + } +} + +const char *get_failure(unsigned f) +{ + switch (f) + { + case 0: + return "silent"; + case 1: + return "printk"; + case 2: + return "panic"; + default: + return "unknown"; + } +} + +/* + * This function interprets the reply and prints it to stdout. It returns + * 0 if no more should be read and 1 to indicate that more messages of this + * type may need to be read. + */ +int audit_print_reply(struct audit_reply *rep, int fd) +{ + _audit_elf = 0; + + switch (rep->type) { + case NLMSG_NOOP: + return 1; + case NLMSG_DONE: + // Close the socket so kernel can do other things + audit_close(fd); + if (printed == 0) + printf("No rules\n"); + else { + lnode *n; + list_first(&l); + n = l.cur; + while (n) { + print_rule(n->r); + n = list_next(&l); + } + list_clear(&l); + } + break; + case NLMSG_ERROR: + printf("NLMSG_ERROR %d (%s)\n", + -rep->error->error, + strerror(-rep->error->error)); + printed = 1; + break; + case AUDIT_GET: + if (interpret) + printf("enabled %s\nfailure %s\n", + get_enable(rep->status->enabled), + get_failure(rep->status->failure)); + else + printf("enabled %u\nfailure %u\n", + rep->status->enabled, rep->status->failure); + printf("pid %u\nrate_limit %u\nbacklog_limit %u\n" + "lost %u\nbacklog %u\n", + rep->status->pid, rep->status->rate_limit, + rep->status->backlog_limit, rep->status->lost, + rep->status->backlog); +#if HAVE_DECL_AUDIT_VERSION_BACKLOG_WAIT_TIME + printf("backlog_wait_time %u\n", + rep->status->backlog_wait_time); +#endif + printed = 1; + break; +#if HAVE_DECL_AUDIT_FEATURE_VERSION + case AUDIT_GET_FEATURE: + { + uint32_t mask = AUDIT_FEATURE_TO_MASK(AUDIT_FEATURE_LOGINUID_IMMUTABLE); + if (rep->features->mask & mask) + printf("loginuid_immutable %u %s\n", + !!(rep->features->features & mask), + rep->features->lock & mask ? "locked" : + "unlocked"); + } + printed = 1; + break; +#endif + case AUDIT_LIST_RULES: + list_requested = 0; + if (key_match(rep->ruledata)) + list_append(&l, rep->ruledata, + sizeof(struct audit_rule_data) + + rep->ruledata->buflen); + printed = 1; + return 1; + default: + printf("Unknown: type=%d, len=%d\n", rep->type, + rep->nlh->nlmsg_len); + printed = 1; + break; + } + return 0; +} + diff --git a/framework/src/audit/src/auditctl-listing.h b/framework/src/audit/src/auditctl-listing.h new file mode 100644 index 00000000..8a412ef3 --- /dev/null +++ b/framework/src/audit/src/auditctl-listing.h @@ -0,0 +1,34 @@ +/* +* auditctl-listing.h - Header file for ausearch-llist.c +* Copyright (c) 2014 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#ifndef CTLLISTING_HEADER +#define CTLLISTING_HEADER + +#include "config.h" +#include "libaudit.h" + +void audit_print_init(void); +int audit_print_reply(struct audit_reply *rep, int fd); +int key_match(const struct audit_rule_data *r); + +#endif diff --git a/framework/src/audit/src/auditctl-llist.c b/framework/src/audit/src/auditctl-llist.c new file mode 100644 index 00000000..175310d7 --- /dev/null +++ b/framework/src/audit/src/auditctl-llist.c @@ -0,0 +1,105 @@ +/* +* ausearch-llist.c - Minimal linked list library +* Copyright (c) 2005 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include <stdlib.h> +#include <string.h> +#include "auditctl-llist.h" + +void list_create(llist *l) +{ + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + +void list_first(llist *l) +{ + l->cur = l->head; +} + +void list_last(llist *l) +{ + register lnode* window; + + if (l->head == NULL) + return; + + window = l->head; + while (window->next) + window = window->next; + l->cur = window; +} + +lnode *list_next(llist *l) +{ + if (l->cur == NULL) + return NULL; + l->cur = l->cur->next; + return l->cur; +} + +void list_append(llist *l, struct audit_rule_data *r, size_t sz) +{ + lnode* newnode; + + newnode = malloc(sizeof(lnode)); + + if (r) { + void *rr = malloc(sz); + if (rr) + memcpy(rr, r, sz); + newnode->r = rr; + } else + newnode->r = NULL; + + newnode->size = sz; + newnode->next = 0; + + // if we are at top, fix this up + if (l->head == NULL) + l->head = newnode; + else // Otherwise add pointer to newnode + l->cur->next = newnode; + + // make newnode current + l->cur = newnode; + l->cnt++; +} + +void list_clear(llist* l) +{ + lnode* nextnode; + register lnode* current; + + current = l->head; + while (current) { + nextnode=current->next; + free(current->r); + free(current); + current=nextnode; + } + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + diff --git a/framework/src/audit/src/auditctl-llist.h b/framework/src/audit/src/auditctl-llist.h new file mode 100644 index 00000000..371d0d7e --- /dev/null +++ b/framework/src/audit/src/auditctl-llist.h @@ -0,0 +1,56 @@ +/* +* auditctl-llist.h - Header file for ausearch-llist.c +* Copyright (c) 2005 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#ifndef CTLLIST_HEADER +#define CTLLIST_HEADER + +#include "config.h" +#include <sys/types.h> +#include "libaudit.h" + +/* This is the node of the linked list. message & item are the only elements + * at this time. Any data elements that are per item goes here. */ +typedef struct _lnode{ + struct audit_rule_data *r; // The rule from the kernel + size_t size; // Size of the rule struct + struct _lnode *next; // Next node pointer +} lnode; + +/* This is the linked list head. Only data elements that are 1 per + * event goes here. */ +typedef struct { + lnode *head; // List head + lnode *cur; // Pointer to current node + unsigned int cnt; // How many items in this list +} llist; + +void list_create(llist *l); +void list_first(llist *l); +void list_last(llist *l); +lnode *list_next(llist *l); +static inline lnode *list_get_cur(llist *l) { return l->cur; } +void list_append(llist *l, struct audit_rule_data *r, size_t sz); +void list_clear(llist* l); + +#endif + diff --git a/framework/src/audit/src/auditctl.c b/framework/src/audit/src/auditctl.c new file mode 100644 index 00000000..334f8021 --- /dev/null +++ b/framework/src/audit/src/auditctl.c @@ -0,0 +1,1472 @@ +/* auditctl.c -- + * Copyright 2004-2015 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * Rickard E. (Rik) Faith <faith@redhat.com> + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> /* strdup needs xopen define */ +#include <getopt.h> +#include <time.h> +#include <sys/stat.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/utsname.h> +#include <fcntl.h> +#include <errno.h> +#include <libgen.h> /* For basename */ +#include <limits.h> /* PATH_MAX */ +#include "libaudit.h" +#include "auditctl-listing.h" +#include "private.h" + +/* This define controls the size of the line that we will request when + * reading in rules from a file. + */ +#define LINE_SIZE 6144 + + +/* Global functions */ +static int handle_request(int status); +static void get_reply(void); +extern int delete_all_rules(int fd); + +/* Global vars */ +int list_requested = 0, interpret = 0; +char key[AUDIT_MAX_KEY_LEN+1]; +const char key_sep[2] = { AUDIT_KEY_SEPARATOR, 0 }; +static int keylen; +static int fd = -1; +static int add = AUDIT_FILTER_UNSET, del = AUDIT_FILTER_UNSET, action = -1; +static int ignore = 0, continue_error = 0; +static int exclude = 0; +static int multiple = 0; +static struct audit_rule_data *rule_new = NULL; + +/* + * This function will reset everything used for each loop when loading + * a ruleset from a file. + */ +static int reset_vars(void) +{ + list_requested = 0; + _audit_syscalladded = 0; + _audit_permadded = 0; + _audit_archadded = 0; + _audit_elf = 0; + add = AUDIT_FILTER_UNSET; + del = AUDIT_FILTER_UNSET; + action = -1; + exclude = 0; + multiple = 0; + + free(rule_new); + rule_new = malloc(sizeof(struct audit_rule_data)); + memset(rule_new, 0, sizeof(struct audit_rule_data)); + if (fd < 0) { + if ((fd = audit_open()) < 0) { + audit_msg(LOG_ERR, "Cannot open netlink audit socket"); + return 1; + } + } + return 0; +} + +static void usage(void) +{ + printf( + "usage: auditctl [options]\n" + " -a <l,a> Append rule to end of <l>ist with <a>ction\n" + " -A <l,a> Add rule at beginning of <l>ist with <a>ction\n" + " -b <backlog> Set max number of outstanding audit buffers\n" + " allowed Default=64\n" + " -c Continue through errors in rules\n" + " -C f=f Compare collected fields if available:\n" + " Field name, operator(=,!=), field name\n" + " -d <l,a> Delete rule from <l>ist with <a>ction\n" + " l=task,exit,user,exclude\n" + " a=never,always\n" + " -D Delete all rules and watches\n" + " -e [0..2] Set enabled flag\n" + " -f [0..2] Set failure flag\n" + " 0=silent 1=printk 2=panic\n" + " -F f=v Build rule: field name, operator(=,!=,<,>,<=,\n" + " >=,&,&=) value\n" + " -h Help\n" + " -i Ignore errors when reading rules from file\n" + " -k <key> Set filter key on audit rule\n" + " -l List rules\n" + " -m text Send a user-space message\n" + " -p [r|w|x|a] Set permissions filter on watch\n" + " r=read, w=write, x=execute, a=attribute\n" + " -q <mount,subtree> make subtree part of mount point's dir watches\n" + " -r <rate> Set limit in messages/sec (0=none)\n" + " -R <file> read rules from file\n" + " -s Report status\n" + " -S syscall Build rule: syscall name or number\n" + " -t Trim directory watches\n" + " -v Version\n" + " -w <path> Insert watch at <path>\n" + " -W <path> Remove watch at <path>\n" + " --loginuid-immutable Make loginuids unchangeable once set\n" + " --backlog_wait_time Set the kernel backlog_wait_time\n" + ); +} + +static int lookup_filter(const char *str, int *filter) +{ + if (strcmp(str, "task") == 0) + *filter = AUDIT_FILTER_TASK; + else if (strcmp(str, "entry") == 0) + *filter = AUDIT_FILTER_ENTRY; + else if (strcmp(str, "exit") == 0) + *filter = AUDIT_FILTER_EXIT; + else if (strcmp(str, "user") == 0) + *filter = AUDIT_FILTER_USER; + else if (strcmp(str, "exclude") == 0) { + *filter = AUDIT_FILTER_EXCLUDE; + exclude = 1; + } else + return 2; + return 0; +} + +static int lookup_action(const char *str, int *act) +{ + if (strcmp(str, "never") == 0) + *act = AUDIT_NEVER; + else if (strcmp(str, "possible") == 0) + return 1; + else if (strcmp(str, "always") == 0) + *act = AUDIT_ALWAYS; + else + return 2; + return 0; +} + +/* + * Returns 0 ok, 1 deprecated action, 2 rule error, + * 3 multiple rule insert/delete + */ +static int audit_rule_setup(char *opt, int *filter, int *act, int lineno) +{ + int rc; + char *p; + + if (++multiple != 1) + return 3; + + p = strchr(opt, ','); + if (p == NULL || strchr(p+1, ',')) + return 2; + *p = 0; + + /* Try opt both ways */ + if (lookup_filter(opt, filter) == 2) { + rc = lookup_action(opt, act); + if (rc != 0) { + *p = ','; + return rc; + } + } + + /* Repair the string */ + *p = ','; + opt = p+1; + + /* If flags are empty, p+1 must be the filter */ + if (*filter == AUDIT_FILTER_UNSET) + lookup_filter(opt, filter); + else { + rc = lookup_action(opt, act); + if (rc != 0) + return rc; + } + + /* Make sure we set both */ + if (*filter == AUDIT_FILTER_UNSET || *act == -1) + return 2; + + /* Consolidate rules on exit filter */ + if (*filter == AUDIT_FILTER_ENTRY) { + *filter = AUDIT_FILTER_EXIT; + if (lineno) + audit_msg(LOG_INFO, "Warning - entry rules deprecated, changing to exit rule in line %d", lineno); + else + audit_msg(LOG_INFO, + "Warning - entry rules deprecated, changing to exit rule"); + } + + return 0; +} + +/* + * This function will check the path before accepting it. It returns + * 1 on error and 0 on success. + */ +static int check_path(const char *path) +{ + char *ptr, *base; + size_t nlen; + size_t plen = strlen(path); + if (plen >= PATH_MAX) { + audit_msg(LOG_ERR, "The path passed for the watch is too big"); + return 1; + } + if (path[0] != '/') { + audit_msg(LOG_ERR, "The path must start with '/'"); + return 1; + } + ptr = strdup(path); + base = basename(ptr); + nlen = strlen(base); + free(ptr); + if (nlen > NAME_MAX) { + audit_msg(LOG_ERR, "The base name of the path is too big"); + return 1; + } + + /* These are warnings, not errors */ + if (strstr(path, "..")) + audit_msg(LOG_WARNING, + "Warning - relative path notation is not supported"); + if (strchr(path, '*') || strchr(path, '?')) + audit_msg(LOG_WARNING, + "Warning - wildcard notation is not supported"); + + return 0; +} + +/* + * Setup a watch. The "name" of the watch in userspace will be the <path> to + * the watch. When this potential watch reaches the kernel, it will resolve + * down to <name> (of terminating file or directory). + * Returns a 1 on success & -1 on failure. + */ +static int audit_setup_watch_name(struct audit_rule_data **rulep, char *path) +{ + int type = AUDIT_WATCH; + size_t len; + struct stat buf; + + if (check_path(path)) + return -1; + + // Trim trailing '/' should they exist + len = strlen(path); + if (len > 2 && path[len-1] == '/') { + while (path[len-1] == '/' && len > 1) { + path[len-1] = 0; + len--; + } + } + if (stat(path, &buf) == 0) { + if (S_ISDIR(buf.st_mode)) + type = AUDIT_DIR; + } + /* FIXME: might want to check to see that rule is empty */ + if (audit_add_watch_dir(type, rulep, path)) + return -1; + + return 1; +} + +/* + * Setup a watch permissions. + * Returns a 1 on success & -1 on failure. + */ +static int audit_setup_perms(struct audit_rule_data *rule, const char *opt) +{ + unsigned int i, len, val = 0; + + len = strlen(opt); + if (len > 4) + return -1; + + for (i = 0; i < len; i++) { + switch (tolower(opt[i])) { + case 'r': + val |= AUDIT_PERM_READ; + break; + case 'w': + val |= AUDIT_PERM_WRITE; + break; + case 'x': + val |= AUDIT_PERM_EXEC; + break; + case 'a': + val |= AUDIT_PERM_ATTR; + break; + default: + audit_msg(LOG_ERR, + "Permission %c isn't supported", + opt[i]); + return -1; + } + } + + if (audit_update_watch_perms(rule_new, val) == 0) { + _audit_permadded = 1; + return 1; + } + return -1; +} + +/* 0 success, -1 failure */ +static int lookup_itype(const char *kind) +{ + if (strcmp(kind, "sys") == 0) + return 0; + if (strcmp(kind, "file") == 0) + return 0; + if (strcmp(kind, "exec") == 0) + return 0; + if (strcmp(kind, "mkexe") == 0) + return 0; + return -1; +} + +/* 0 success, -1 failure */ +static int lookup_iseverity(const char *severity) +{ + if (strncmp(severity, "inf", 3) == 0) + return 0; + if (strncmp(severity, "low", 3) == 0) + return 0; + if (strncmp(severity, "med", 3) == 0) + return 0; + if (strncmp(severity, "hi", 2) == 0) + return 0; + return -1; +} + +/* 0 success, -1 failure */ +static int check_ids_key(const char *k) +{ + char *ptr, *kindptr, *ratingptr; + char keyptr[AUDIT_MAX_KEY_LEN+1]; + + if (strlen(k) > AUDIT_MAX_KEY_LEN) + goto fail_exit; + + strncpy(keyptr, k, sizeof(keyptr)); + keyptr[AUDIT_MAX_KEY_LEN] = 0; + ptr = strchr(keyptr, '-'); // There has to be a - because strncmp + kindptr = ptr + 1; + if (*kindptr == 0) + goto fail_exit; + + ptr = strchr(kindptr, '-'); + if (ptr) { + *ptr = 0; + ratingptr = ptr +1; + } else // The rules are misconfigured + goto fail_exit; + if (*ratingptr == 0) + goto fail_exit; + + if (lookup_itype(kindptr)) { + audit_msg(LOG_ERR, "ids key type is bad"); + return -1; + } + if (lookup_iseverity(ratingptr)) { + audit_msg(LOG_ERR, "ids key severity is bad"); + return -1; + } + return 0; + +fail_exit: + audit_msg(LOG_ERR, "ids key is bad"); + return -1; +} + +static int equiv_parse(char *optarg, char **mp, char **sub) +{ + char *ptr = strchr(optarg, ','); + if (ptr == NULL) + return -1; // no comma + *ptr = 0; + ptr++; + if (*ptr == 0) + return -1; // ends with comma + *mp = optarg; + *sub = ptr; + if (strchr(*sub, ',')) + return -1; // too many commas + return 0; +} + +int audit_request_rule_list(int fd) +{ + if (audit_request_rules_list_data(fd) > 0) { + list_requested = 1; + get_reply(); + return 1; + } + return 0; +} + +void check_rule_mismatch(int lineno, const char *option) +{ + struct audit_rule_data tmprule; + unsigned int old_audit_elf = _audit_elf; + int rc = 0; + + switch (_audit_elf) + { + case AUDIT_ARCH_X86_64: + _audit_elf = AUDIT_ARCH_I386; + break; + case AUDIT_ARCH_PPC64: + _audit_elf = AUDIT_ARCH_PPC; + break; + case AUDIT_ARCH_S390X: + _audit_elf = AUDIT_ARCH_S390; + break; + } + memset(&tmprule, 0, sizeof(struct audit_rule_data)); + audit_rule_syscallbyname_data(&tmprule, option); + if (memcmp(tmprule.mask, rule_new->mask, AUDIT_BITMASK_SIZE)) + rc = 1; + _audit_elf = old_audit_elf; + if (rc) { + if (lineno) + audit_msg(LOG_WARNING, "WARNING - 32/64 bit syscall mismatch in line %d, you should specify an arch", lineno); + else + audit_msg(LOG_WARNING, "WARNING - 32/64 bit syscall mismatch, you should specify an arch"); + } +} + +int report_status(int fd) +{ + int retval; + + retval = audit_request_status(fd); + if (retval == -1) { + if (errno == ECONNREFUSED) + fprintf(stderr, "The audit system is disabled\n"); + return -1; + } + get_reply(); + retval = audit_request_features(fd); + if (retval == -1) { + // errno is EINVAL if the kernel does support features API + if (errno == EINVAL) + return -2; + return -1; + } + get_reply(); + return -2; +} + +int parse_syscall(struct audit_rule_data *rule_new, const char *optarg) +{ + int retval = 0; + char *saved; + + if (strchr(optarg, ',')) { + char *ptr, *tmp = strdup(optarg); + if (tmp == NULL) + return -1; + ptr = strtok_r(tmp, ",", &saved); + while (ptr) { + retval = audit_rule_syscallbyname_data(rule_new, ptr); + if (retval != 0) { + if (retval == -1) { + audit_msg(LOG_ERR, + "Syscall name unknown: %s", + ptr); + retval = -3; // error reported + } + break; + } + ptr = strtok_r(NULL, ",", &saved); + } + free(tmp); + return retval; + } + + return audit_rule_syscallbyname_data(rule_new, optarg); +} + +struct option long_opts[] = +{ + {"loginuid-immutable", 0, NULL, 1}, + {"backlog_wait_time", 1, NULL, 2}, + {NULL, 0, NULL, 0} +}; + +// FIXME: Change these to enums +/* + * returns: -3 deprecated, -2 success - no reply, -1 error - noreply, + * 0 success - reply, > 0 success - rule + */ +static int setopt(int count, int lineno, char *vars[]) +{ + int c; + int retval = 0, rc; + + optind = 0; + opterr = 0; + key[0] = 0; + keylen = AUDIT_MAX_KEY_LEN; + + while ((retval >= 0) && (c = getopt_long(count, vars, + "hicslDvtC:e:f:r:b:a:A:d:S:F:m:R:w:W:k:p:q:", + long_opts, NULL)) != EOF) { + int flags = AUDIT_FILTER_UNSET; + rc = 10; // Init to something impossible to see if unused. + switch (c) { + case 'h': + usage(); + retval = -1; + break; + case 'i': + ignore = 1; + retval = -2; + break; + case 'c': + ignore = 1; + continue_error = 1; + retval = -2; + break; + case 's': + if (count > 3) { + audit_msg(LOG_ERR, + "Too many options for status command"); + retval = -1; + break; + } else if (optind == 2 && count == 3) { + if (strcmp(vars[optind], "-i") == 0) { + interpret = 1; + count -= 1; + } else { + audit_msg(LOG_ERR, + "Only -i option is allowed"); + retval = -1; + break; + } + } + retval = report_status(fd); + break; + case 'e': + if (optarg && ((strcmp(optarg, "0") == 0) || + (strcmp(optarg, "1") == 0) || + (strcmp(optarg, "2") == 0))) { + if (audit_set_enabled(fd, strtoul(optarg,NULL,0)) > 0) + audit_request_status(fd); + else + retval = -1; + } else { + audit_msg(LOG_ERR, "Enable must be 0, 1, or 2 was %s", + optarg); + retval = -1; + } + break; + case 'f': + if (optarg && ((strcmp(optarg, "0") == 0) || + (strcmp(optarg, "1") == 0) || + (strcmp(optarg, "2") == 0))) { + if (audit_set_failure(fd, strtoul(optarg,NULL,0)) > 0) + audit_request_status(fd); + else + return -1; + } else { + audit_msg(LOG_ERR, "Failure must be 0, 1, or 2 was %s", + optarg); + retval = -1; + } + break; + case 'r': + if (optarg && isdigit(optarg[0])) { + uint32_t rate; + errno = 0; + rate = strtoul(optarg,NULL,0); + if (errno) { + audit_msg(LOG_ERR, "Error converting rate"); + return -1; + } + if (audit_set_rate_limit(fd, rate) > 0) + audit_request_status(fd); + else + return -1; + } else { + audit_msg(LOG_ERR,"Rate must be a numeric value was %s", + optarg); + retval = -1; + } + break; + case 'b': + if (optarg && isdigit(optarg[0])) { + uint32_t limit; + errno = 0; + limit = strtoul(optarg,NULL,0); + if (errno) { + audit_msg(LOG_ERR, "Error converting backlog"); + return -1; + } + if (audit_set_backlog_limit(fd, limit) > 0) + audit_request_status(fd); + else + return -1; + } else { + audit_msg(LOG_ERR, + "Backlog must be a numeric value was %s", + optarg); + retval = -1; + } + break; + case 'l': + if (count > 4) { + audit_msg(LOG_ERR, + "Wrong number of options for list request"); + retval = -1; + break; + } + if (count == 3) { + if (strcmp(vars[optind], "-i") == 0) { + interpret = 1; + count -= 1; + } else { + audit_msg(LOG_ERR, + "Only -k or -i options are allowed"); + retval = -1; + } + } else if (count == 4) { + if (vars[optind] && strcmp(vars[optind], "-k") == 0) { + strncat(key, vars[3], keylen); + count -= 2; + } else { + audit_msg(LOG_ERR, + "Only -k or -i options are allowed"); + retval = -1; + break; + } + } + if (audit_request_rule_list(fd)) { + list_requested = 1; + retval = -2; + } else + retval = -1; + break; + case 'a': + if (strstr(optarg, "task") && _audit_syscalladded) { + audit_msg(LOG_ERR, + "Syscall auditing requested for task list"); + retval = -1; + } else { + rc = audit_rule_setup(optarg, &add, &action, lineno); + if (rc == 3) { + audit_msg(LOG_ERR, + "Multiple rule insert/delete operations are not allowed\n"); + retval = -1; + } else if (rc == 2) { + audit_msg(LOG_ERR, + "Append rule - bad keyword %s", + optarg); + retval = -1; + } else if (rc == 1) { + audit_msg(LOG_ERR, + "Append rule - possible is deprecated"); + return -3; /* deprecated - eat it */ + } else + retval = 1; /* success - please send */ + } + break; + case 'A': + if (strstr(optarg, "task") && _audit_syscalladded) { + audit_msg(LOG_ERR, + "Error: syscall auditing requested for task list"); + retval = -1; + } else { + rc = audit_rule_setup(optarg, &add, &action, lineno); + if (rc == 3) { + audit_msg(LOG_ERR, + "Multiple rule insert/delete operations are not allowed"); + retval = -1; + } else if (rc == 2) { + audit_msg(LOG_ERR, + "Add rule - bad keyword %s", optarg); + retval = -1; + } else if (rc == 1) { + audit_msg(LOG_WARNING, + "Append rule - possible is deprecated"); + return -3; /* deprecated - eat it */ + } else { + add |= AUDIT_FILTER_PREPEND; + retval = 1; /* success - please send */ + } + } + break; + case 'd': + rc = audit_rule_setup(optarg, &del, &action, lineno); + if (rc == 3) { + audit_msg(LOG_ERR, + "Multiple rule insert/delete operations are not allowed"); + retval = -1; + } else if (rc == 2) { + audit_msg(LOG_ERR, "Delete rule - bad keyword %s", + optarg); + retval = -1; + } else if (rc == 1) { + audit_msg(LOG_INFO, + "Delete rule - possible is deprecated"); + return -3; /* deprecated - eat it */ + } else + retval = 1; /* success - please send */ + break; + case 'S': { + int unknown_arch = !_audit_elf; + /* Do some checking to make sure that we are not adding a + * syscall rule to a list that does not make sense. */ + if (((add & (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) == + AUDIT_FILTER_TASK || (del & + (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) == + AUDIT_FILTER_TASK)) { + audit_msg(LOG_ERR, + "Error: syscall auditing being added to task list"); + return -1; + } else if (((add & (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) == + AUDIT_FILTER_USER || (del & + (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) == + AUDIT_FILTER_USER)) { + audit_msg(LOG_ERR, + "Error: syscall auditing being added to user list"); + return -1; + } else if (exclude) { + audit_msg(LOG_ERR, + "Error: syscall auditing cannot be put on exclude list"); + return -1; + } else { + if (unknown_arch) { + int machine; + unsigned int elf; + machine = audit_detect_machine(); + if (machine < 0) { + audit_msg(LOG_ERR, + "Error detecting machine type"); + return -1; + } + elf = audit_machine_to_elf(machine); + if (elf == 0) { + audit_msg(LOG_ERR, + "Error looking up elf type %d", + machine); + return -1; + } + _audit_elf = elf; + } + } + rc = parse_syscall(rule_new, optarg); + switch (rc) + { + case 0: + _audit_syscalladded = 1; + if (unknown_arch && add != AUDIT_FILTER_UNSET) + check_rule_mismatch(lineno, optarg); + break; + case -1: + audit_msg(LOG_ERR, "Syscall name unknown: %s", + optarg); + retval = -1; + break; + case -2: + audit_msg(LOG_ERR, "Elf type unknown: 0x%x", + _audit_elf); + retval = -1; + break; + case -3: // Error reported - do nothing here + retval = -1; + break; + }} + break; + case 'F': + if (add != AUDIT_FILTER_UNSET) + flags = add & AUDIT_FILTER_MASK; + else if (del != AUDIT_FILTER_UNSET) + flags = del & AUDIT_FILTER_MASK; + // if the field is arch & there is a -t option...we + // can allow it + else if ((optind >= count) || (strstr(optarg, "arch=") == NULL) + || (strcmp(vars[optind], "-t") != 0)) { + audit_msg(LOG_ERR, "List must be given before field"); + retval = -1; + break; + } + + rc = audit_rule_fieldpair_data(&rule_new,optarg,flags); + if (rc != 0) { + audit_number_to_errmsg(rc, optarg); + retval = -1; + } else { + if (rule_new->fields[rule_new->field_count-1] == + AUDIT_PERM) + _audit_permadded = 1; + } + + break; + case 'C': + if (add != AUDIT_FILTER_UNSET) + flags = add & AUDIT_FILTER_MASK; + else if (del != AUDIT_FILTER_UNSET) + flags = del & AUDIT_FILTER_MASK; + + rc = audit_rule_interfield_comp_data(&rule_new, optarg, flags); + if (rc != 0) { + audit_number_to_errmsg(rc, optarg); + retval = -1; + } else { + if (rule_new->fields[rule_new->field_count - 1] == + AUDIT_PERM) + _audit_permadded = 1; + } + break; + case 'm': + if (count > 3) { + audit_msg(LOG_ERR, + "The -m option must be only the only option and takes 1 parameter"); + retval = -1; + } else { + const char*s = optarg; + while (*s) { + if (*s < 32) { + audit_msg(LOG_ERR, + "Illegal character in audit event"); + return -1; + } + s++; + } + if (audit_log_user_message( fd, AUDIT_USER, + optarg, NULL, NULL, NULL, 1) <= 0) + retval = -1; + else + return -2; // success - no reply for this + } + break; + case 'R': + audit_msg(LOG_ERR, "Error - nested rule files not supported"); + retval = -1; + break; + case 'D': + if (count > 4 || count == 3) { + audit_msg(LOG_ERR, + "Wrong number of options for Delete all request"); + retval = -1; + break; + } + if (count == 4) { + if (strcmp(vars[optind], "-k") == 0) { + strncat(key, vars[3], keylen); + count -= 2; + } else { + audit_msg(LOG_ERR, + "Only the -k option is allowed"); + retval = -1; + break; + } + } + retval = delete_all_rules(fd); + if (retval == 0) { + (void)audit_request_rule_list(fd); + key[0] = 0; + retval = -2; + } + break; + case 'w': + if (add != AUDIT_FILTER_UNSET || + del != AUDIT_FILTER_UNSET) { + audit_msg(LOG_ERR, + "watch option can't be given with a syscall"); + retval = -1; + } else if (optarg) { + add = AUDIT_FILTER_EXIT; + action = AUDIT_ALWAYS; + _audit_syscalladded = 1; + retval = audit_setup_watch_name(&rule_new, optarg); + } else { + audit_msg(LOG_ERR, "watch option needs a path"); + retval = -1; + } + break; + case 'W': + if (optarg) { + del = AUDIT_FILTER_EXIT; + action = AUDIT_ALWAYS; + _audit_syscalladded = 1; + retval = audit_setup_watch_name(&rule_new, optarg); + } else { + audit_msg(LOG_ERR, "watch option needs a path"); + retval = -1; + } + break; + case 'k': + if (!(_audit_syscalladded || _audit_permadded ) || + (add==AUDIT_FILTER_UNSET && + del==AUDIT_FILTER_UNSET)) { + audit_msg(LOG_ERR, + "key option needs a watch or syscall given prior to it"); + retval = -1; + } else if (!optarg) { + audit_msg(LOG_ERR, "key option needs a value"); + retval = -1; + } else if ((strlen(optarg)+strlen(key)+(!!key[0])) > + AUDIT_MAX_KEY_LEN) { + audit_msg(LOG_ERR, "key option exceeds size limit"); + retval = -1; + } else { + if (strncmp(optarg, "ids-", 4) == 0) { + if (check_ids_key(optarg)) { + retval = -1; + break; + } + } + if (strchr(optarg, AUDIT_KEY_SEPARATOR)) + audit_msg(LOG_ERR, + "key %s has illegal character", optarg); + if (key[0]) { // Add the separator if we need to + strcat(key, key_sep); + keylen--; + } + strncat(key, optarg, keylen); + keylen = AUDIT_MAX_KEY_LEN - strlen(key); + } + break; + case 'p': + if (!add && !del) { + audit_msg(LOG_ERR, + "permission option needs a watch given prior to it"); + retval = -1; + } else if (!optarg) { + audit_msg(LOG_ERR, "permission option needs a filter"); + retval = -1; + } else + retval = audit_setup_perms(rule_new, optarg); + break; + case 'q': + if (_audit_syscalladded) { + audit_msg(LOG_ERR, + "Syscall auditing requested for make equivalent"); + retval = -1; + } else { + char *mp, *sub; + retval = equiv_parse(optarg, &mp, &sub); + if (retval < 0) { + audit_msg(LOG_ERR, + "Error parsing equivalent parts"); + retval = -1; + } else { + retval = audit_make_equivalent(fd, mp, sub); + if (retval <= 0) { + retval = -1; + } else + return -2; // success - no reply needed + } + } + break; + case 't': + retval = audit_trim_subtrees(fd); + if (retval <= 0) + retval = -1; + else + return -2; // success - no reply for this + break; + case 'v': + printf("auditctl version %s\n", VERSION); + retval = -2; + break; + // Now the long options + case 1: + retval = audit_set_loginuid_immutable(fd); + if (retval <= 0) + retval = -1; + else + return -2; // success - no reply for this + break; + case 2: +#if HAVE_DECL_AUDIT_VERSION_BACKLOG_WAIT_TIME + if (optarg && isdigit(optarg[0])) { + uint32_t bwt; + errno = 0; + bwt = strtoul(optarg,NULL,0); + if (errno) { + audit_msg(LOG_ERR, + "Error converting backlog_wait_time"); + return -1; + } + if (audit_set_backlog_wait_time(fd, bwt) > 0) + audit_request_status(fd); + else + return -1; + } else { + audit_msg(LOG_ERR, + "Backlog_wait_time must be a numeric value was %s", + optarg); + retval = -1; + } +#else + audit_msg(LOG_ERR, + "backlog_wait_time is not supported on your kernel"); + retval = -1; +#endif + break; + default: + usage(); + retval = -1; + break; + } + } + /* catch extra args or errors where the user types "- s" */ + if (optind == 1) + retval = -1; + else if ((optind < count) && (retval != -1)) { + audit_msg(LOG_ERR, "parameter passed without an option given"); + retval = -1; + } + + /* See if we were adding a key */ + if (key[0] && list_requested == 0) { + int flags = 0; + char *cmd=NULL; + + /* Get the flag */ + if (add != AUDIT_FILTER_UNSET) + flags = add & AUDIT_FILTER_MASK; + else if (del != AUDIT_FILTER_UNSET) + flags = del & AUDIT_FILTER_MASK; + + /* Build the command */ + if (asprintf(&cmd, "key=%s", key) < 0) { + cmd = NULL; + audit_msg(LOG_ERR, "Out of memory adding key"); + retval = -1; + } else { + /* Add this to the rule */ + int ret = audit_rule_fieldpair_data(&rule_new, cmd, flags); + if (ret < 0) + retval = -1; + free(cmd); + } + } + if (retval == -1 && errno == ECONNREFUSED) + audit_msg(LOG_ERR, "The audit system is disabled"); + return retval; +} + +static char *get_line(FILE *f, char *buf) +{ + if (fgets_unlocked(buf, LINE_SIZE, f)) { + /* remove newline */ + char *ptr = strchr(buf, 0x0a); + if (ptr) + *ptr = 0; + return buf; + } + return NULL; +} + + +void preprocess(char *buf) +{ + unsigned int i = 0; + bool esc_ctx = false; + + while (buf[i]) { + if (buf[i] == '\\' && esc_ctx == false) + esc_ctx = true; + else { + if (esc_ctx == true) { + if (buf[i] == ' ') { + buf[i] = 0x07; + buf[i - 1] = 0x07; + } else if (buf[i] == '\\') { + buf[i] = 0x04; + buf[i - 1] = 0x04; + } + + esc_ctx = false; + } + } + + i++; + } +} + + +void postprocess(char *buf) +{ + char *str = strdup(buf); + char *pos1 = str; + char *pos2 = buf; + + if (!str) + return; + + while (*pos1) { + if (*pos1 == 0x07) { + *pos2 = ' '; + pos1 += 2; + pos2++; + continue; + } else if (*pos1 == 0x04) { + *pos2 = '\\'; + pos1 += 2; + pos2++; + continue; + } + + *pos2 = *pos1; + pos2++; + pos1++; + } + + *pos2 = 0; + free(str); +} + + +/* + * This function reads the given file line by line and executes the rule. + * It returns 0 if everything went OK, 1 if there are problems before reading + * the file and -1 on error conditions after executing some of the rules. + * It will abort reading the file if it encounters any problems. + */ +static int fileopt(const char *file) +{ + int i, tfd, rc, lineno = 1; + struct stat st; + FILE *f; + char buf[LINE_SIZE]; + + /* Does the file exist? */ + rc = open(file, O_RDONLY); + if (rc < 0) { + if (errno != ENOENT) { + audit_msg(LOG_ERR,"Error opening %s (%s)", + file, strerror(errno)); + return 1; + } + audit_msg(LOG_INFO, "file %s doesn't exist, skipping", file); + return 0; + } + tfd = rc; + + /* Is the file permissions sane? */ + if (fstat(tfd, &st) < 0) { + audit_msg(LOG_ERR, "Error fstat'ing %s (%s)", + file, strerror(errno)); + close(tfd); + return 1; + } + if (st.st_uid != 0) { + audit_msg(LOG_ERR, "Error - %s isn't owned by root", file); + close(tfd); + return 1; + } + if ((st.st_mode & S_IWOTH) == S_IWOTH) { + audit_msg(LOG_ERR, "Error - %s is world writable", file); + close(tfd); + return 1; + } + if (!S_ISREG(st.st_mode)) { + audit_msg(LOG_ERR, "Error - %s is not a regular file", file); + close(tfd); + return 1; + } + + f = fdopen(tfd, "rm"); + if (f == NULL) { + audit_msg(LOG_ERR, "Error - fdopen failed (%s)", + strerror(errno)); + close(tfd); + return 1; + } + + /* Read until eof, lineno starts as 1 */ + while (get_line(f, buf)) { + char *ptr, **fields; + int idx=0, nf = (strlen(buf)/3) + 3; + + /* Weed out blank lines */ + while (buf[idx] == ' ') + idx++; + if (buf[idx] == 0) { + lineno++; + continue; + } + + preprocess(buf); + ptr = audit_strsplit(buf); + if (ptr == NULL) + break; + + /* allow comments */ + if (ptr[0] == '#') { + lineno++; + continue; + } + i = 0; + fields = malloc(nf * sizeof(char *)); + fields[i++] = "auditctl"; + fields[i++] = ptr; + while( (ptr=audit_strsplit(NULL)) && (i < nf-1)) { + postprocess(ptr); + fields[i++] = ptr; + } + + fields[i] = NULL; + + /* Parse it */ + if (reset_vars()) { + free(fields); + fclose(f); + return -1; + } + rc = setopt(i, lineno, fields); + free(fields); + + /* handle reply or send rule */ + if (rc != -3) { + if (handle_request(rc) == -1) { + if (errno != ECONNREFUSED) + audit_msg(LOG_ERR, + "There was an error in line %d of %s", + lineno, file); + else { + audit_msg(LOG_ERR, + "The audit system is disabled"); + fclose(f); + return 0; + } + if (ignore == 0) { + fclose(f); + return -1; + } + if (continue_error) + continue_error = -1; + } + } + lineno++; + } + fclose(f); + return 0; +} + +/* Return 1 if ready, 0 otherwise */ +static int is_ready(int fd) +{ + if (audit_is_enabled(fd) == 2) { + audit_msg(LOG_ERR, "The audit system is in immutable mode," + " no rule changes allowed"); + return 0; + } else if (errno == ECONNREFUSED) { + audit_msg(LOG_ERR, "The audit system is disabled"); + return 0; + } + return 1; +} + +int main(int argc, char *argv[]) +{ + int retval = 1; + + set_aumessage_mode(MSG_STDERR, DBG_NO); + + if (argc == 1) { + usage(); + return 1; + } +#ifndef DEBUG + /* Make sure we are root if we do anything except help */ + if (!(argc == 2 && (strcmp(argv[1], "--help")==0 || + strcmp(argv[1], "-h") == 0)) && (geteuid() != 0)) { + audit_msg(LOG_WARNING, "You must be root to run this program."); + return 4; + } +#endif + /* Check where the rules are coming from: commandline or file */ + if ((argc == 3) && (strcmp(argv[1], "-R") == 0)) { + // If reading a file, its most likely start up. Send problems + // to syslog where they will persist for later review + set_aumessage_mode(MSG_SYSLOG, DBG_NO); + fd = audit_open(); + if (is_ready(fd) == 0) + return 0; + else if (fileopt(argv[2])) { + free(rule_new); + return 1; + } else { + free(rule_new); + if (continue_error < 0) + return 1; + return 0; + } + } else { + if (reset_vars()) { + free(rule_new); + return 1; + } + retval = setopt(argc, 0, argv); + if (retval == -3) { + free(rule_new); + return 0; + } + } + + if (add != AUDIT_FILTER_UNSET || del != AUDIT_FILTER_UNSET) { + fd = audit_open(); + if (is_ready(fd) == 0) { + free(rule_new); + return 0; + } + } + retval = handle_request(retval); + free(rule_new); + return retval; +} + +/* + * This function is called after setopt to handle the return code. + * On entry, status = 0 means just get the reply. Greater than 0 means we + * are adding or deleting a rule or watch. -1 means an error occurred. + * -2 means everything is OK and no reply needed. Even if there's an + * error, we need to call this routine to close up the audit fd. + * The return code from this function is 0 success and -1 error. + */ +static int handle_request(int status) +{ + if (status == 0) { + if (_audit_syscalladded) { + audit_msg(LOG_ERR, "Error - no list specified"); + return -1; + } + get_reply(); + } else if (status == -2) + status = 0; // report success + else if (status > 0) { + int rc; + if (add != AUDIT_FILTER_UNSET) { + // if !task add syscall any if not specified + if ((add & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK && + _audit_syscalladded != 1) { + audit_rule_syscallbyname_data( + rule_new, "all"); + } + set_aumessage_mode(MSG_QUIET, DBG_NO); + rc = audit_add_rule_data(fd, rule_new, add, action); + set_aumessage_mode(MSG_STDERR, DBG_NO); + /* Retry for legacy kernels */ + if (rc < 0) { + if (errno == EINVAL && + rule_new->fields[0] == AUDIT_DIR) { + rule_new->fields[0] = AUDIT_WATCH; + rc = audit_add_rule_data(fd, rule_new, + add, action); + } else { + audit_msg(LOG_ERR, + "Error sending add rule data request (%s)", + errno == EEXIST ? + "Rule exists" : strerror(-rc)); + } + } + } + else if (del != AUDIT_FILTER_UNSET) { + if ((del & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK && + _audit_syscalladded != 1) { + audit_rule_syscallbyname_data( + rule_new, "all"); + } + set_aumessage_mode(MSG_QUIET, DBG_NO); + rc = audit_delete_rule_data(fd, rule_new, + del, action); + set_aumessage_mode(MSG_STDERR, DBG_NO); + /* Retry for legacy kernels */ + if (rc < 0) { + if (errno == EINVAL && + rule_new->fields[0] == AUDIT_DIR) { + rule_new->fields[0] = AUDIT_WATCH; + rc = audit_delete_rule_data(fd,rule_new, + del, action); + } else { + audit_msg(LOG_ERR, + "Error sending delete rule data request (%s)", + errno == EEXIST ? + "Rule exists" : strerror(-rc)); + } + } + } else { + usage(); + audit_close(fd); + exit(1); + } + if (rc <= 0) + status = -1; + else + status = 0; + } else + status = -1; + + if (!list_requested) + audit_close(fd); + fd = -1; + return status; +} + +/* + * A reply from the kernel is expected. Get and display it. + */ +static void get_reply(void) +{ + int i, retval; + int timeout = 40; /* loop has delay of .1 - so this is 4 seconds */ + struct audit_reply rep; + fd_set read_mask; + FD_ZERO(&read_mask); + FD_SET(fd, &read_mask); + + // Reset printing counter + audit_print_init(); + + for (i = 0; i < timeout; i++) { + struct timeval t; + + t.tv_sec = 0; + t.tv_usec = 100000; /* .1 second */ + do { + retval=select(fd+1, &read_mask, NULL, NULL, &t); + } while (retval < 0 && errno == EINTR); + // We'll try to read just in case + retval = audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0); + if (retval > 0) { + if (rep.type == NLMSG_ERROR && rep.error->error == 0) { + i = 0; /* reset timeout */ + continue; /* This was an ack */ + } + + if ((retval = audit_print_reply(&rep, fd)) == 0) + break; + else + i = 0; /* If getting more, reset timeout */ + } + } +} + diff --git a/framework/src/audit/src/auditd-config.c b/framework/src/audit/src/auditd-config.c new file mode 100644 index 00000000..0a99ffe0 --- /dev/null +++ b/framework/src/audit/src/auditd-config.c @@ -0,0 +1,1744 @@ +/* auditd-config.c -- + * Copyright 2004-2011,2013-14 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <dirent.h> +#include <ctype.h> +#include <stdlib.h> +#include <netdb.h> +#include <fcntl.h> /* O_NOFOLLOW needs gnu defined */ +#include <libgen.h> +#include <arpa/inet.h> +#include <limits.h> /* INT_MAX */ +#include "auditd-config.h" +#include "libaudit.h" +#include "private.h" + +#define TCP_PORT_MAX 65535 + +/* Local prototypes */ +struct nv_pair +{ + const char *name; + const char *value; + const char *option; +}; + +struct kw_pair +{ + const char *name; + int (*parser)(struct nv_pair *, int, struct daemon_conf *); + int max_options; +}; + +struct nv_list +{ + const char *name; + int option; +}; + +static char *get_line(FILE *f, char *buf, unsigned size, int *lineno, + const char *file); +static int nv_split(char *buf, struct nv_pair *nv); +static const struct kw_pair *kw_lookup(const char *val); +static int log_file_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int num_logs_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int log_group_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int qos_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int dispatch_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int name_format_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int name_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int max_log_size_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int max_log_size_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int log_format_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int flush_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int freq_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int space_left_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int space_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int action_mail_acct_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int admin_space_left_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int admin_space_left_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int disk_full_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int disk_error_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int priority_boost_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int tcp_listen_port_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int tcp_listen_queue_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int tcp_max_per_addr_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int use_libwrap_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int tcp_client_ports_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int tcp_client_max_idle_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int enable_krb5_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int krb5_principal_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int krb5_key_file_parser(struct nv_pair *nv, int line, + struct daemon_conf *config); +static int sanity_check(struct daemon_conf *config); + +static const struct kw_pair keywords[] = +{ + {"log_file", log_file_parser, 0 }, + {"log_format", log_format_parser, 0 }, + {"log_group", log_group_parser, 0 }, + {"flush", flush_parser, 0 }, + {"freq", freq_parser, 0 }, + {"num_logs", num_logs_parser, 0 }, + {"dispatcher", dispatch_parser, 0 }, + {"name_format", name_format_parser, 0 }, + {"name", name_parser, 0 }, + {"disp_qos", qos_parser, 0 }, + {"max_log_file", max_log_size_parser, 0 }, + {"max_log_file_action", max_log_size_action_parser, 0 }, + {"space_left", space_left_parser, 0 }, + {"space_left_action", space_action_parser, 1 }, + {"action_mail_acct", action_mail_acct_parser, 0 }, + {"admin_space_left", admin_space_left_parser, 0 }, + {"admin_space_left_action", admin_space_left_action_parser, 1 }, + {"disk_full_action", disk_full_action_parser, 1 }, + {"disk_error_action", disk_error_action_parser, 1 }, + {"priority_boost", priority_boost_parser, 0 }, + {"tcp_listen_port", tcp_listen_port_parser, 0 }, + {"tcp_listen_queue", tcp_listen_queue_parser, 0 }, + {"tcp_max_per_addr", tcp_max_per_addr_parser, 0 }, + {"use_libwrap", use_libwrap_parser, 0 }, + {"tcp_client_ports", tcp_client_ports_parser, 0 }, + {"tcp_client_max_idle", tcp_client_max_idle_parser, 0 }, + {"enable_krb5", enable_krb5_parser, 0 }, + {"krb5_principal", krb5_principal_parser, 0 }, + {"krb5_key_file", krb5_key_file_parser, 0 }, + { NULL, NULL } +}; + +static const struct nv_list log_formats[] = +{ + {"raw", LF_RAW }, + {"nolog", LF_NOLOG }, + { NULL, 0 } +}; + +static const struct nv_list flush_techniques[] = +{ + {"none", FT_NONE }, + {"incremental", FT_INCREMENTAL }, + {"data", FT_DATA }, + {"sync", FT_SYNC }, + { NULL, 0 } +}; + +static const struct nv_list failure_actions[] = +{ + {"ignore", FA_IGNORE }, + {"syslog", FA_SYSLOG }, + {"rotate", FA_ROTATE }, + {"email", FA_EMAIL }, + {"exec", FA_EXEC }, + {"suspend", FA_SUSPEND }, + {"single", FA_SINGLE }, + {"halt", FA_HALT }, + { NULL, 0 } +}; + +// Future ideas: e-mail, run command +static const struct nv_list size_actions[] = +{ + {"ignore", SZ_IGNORE }, + {"syslog", SZ_SYSLOG }, + {"suspend", SZ_SUSPEND }, + {"rotate", SZ_ROTATE }, + {"keep_logs", SZ_KEEP_LOGS}, + { NULL, 0 } +}; + +static const struct nv_list qos_options[] = +{ + {"lossy", QOS_NON_BLOCKING }, + {"lossless", QOS_BLOCKING }, + { NULL, 0 } +}; + +static const struct nv_list node_name_formats[] = +{ + {"none", N_NONE }, + {"hostname", N_HOSTNAME }, + {"fqd", N_FQD }, + {"numeric", N_NUMERIC }, + {"user", N_USER }, + { NULL, 0 } +}; + +static const struct nv_list yes_no_values[] = +{ + {"yes", 1 }, + {"no", 0 }, + { NULL, 0 } +}; + +const char *email_command = "/usr/lib/sendmail"; +static int allow_links = 0; + + +void set_allow_links(int allow) +{ + allow_links = allow; +} + +/* + * Set everything to its default value +*/ +void clear_config(struct daemon_conf *config) +{ + config->qos = QOS_NON_BLOCKING; + config->sender_uid = 0; + config->sender_pid = 0; + config->sender_ctx = NULL; + config->log_file = strdup("/var/log/audit/audit.log"); + config->log_format = LF_RAW; + config->log_group = 0; + config->priority_boost = 4; + config->flush = FT_NONE; + config->freq = 0; + config->num_logs = 0L; + config->dispatcher = NULL; + config->node_name_format = N_NONE; + config->node_name = NULL; + config->max_log_size = 0L; + config->max_log_size_action = SZ_IGNORE; + config->space_left = 0L; + config->space_left_action = FA_IGNORE; + config->space_left_exe = NULL; + config->action_mail_acct = strdup("root"); + config->admin_space_left= 0L; + config->admin_space_left_action = FA_IGNORE; + config->admin_space_left_exe = NULL; + config->disk_full_action = FA_IGNORE; + config->disk_full_exe = NULL; + config->disk_error_action = FA_SYSLOG; + config->disk_error_exe = NULL; + config->tcp_listen_port = 0; + config->tcp_listen_queue = 5; + config->tcp_max_per_addr = 1; + config->use_libwrap = 1; + config->tcp_client_min_port = 0; + config->tcp_client_max_port = TCP_PORT_MAX; + config->tcp_client_max_idle = 0; + config->enable_krb5 = 0; + config->krb5_principal = NULL; + config->krb5_key_file = NULL; +} + +static log_test_t log_test = TEST_AUDITD; +int load_config(struct daemon_conf *config, log_test_t lt) +{ + int fd, rc, mode, lineno = 1; + struct stat st; + FILE *f; + char buf[160]; + + clear_config(config); + log_test = lt; + + /* open the file */ + mode = O_RDONLY; + if (allow_links == 0) + mode |= O_NOFOLLOW; + rc = open(CONFIG_FILE, mode); + if (rc < 0) { + if (errno != ENOENT) { + audit_msg(LOG_ERR, "Error opening config file (%s)", + strerror(errno)); + return 1; + } + audit_msg(LOG_WARNING, + "Config file %s doesn't exist, skipping", CONFIG_FILE); + return 0; + } + fd = rc; + + /* check the file's permissions: owned by root, not world writable, + * not symlink. + */ + audit_msg(LOG_DEBUG, "Config file %s opened for parsing", + CONFIG_FILE); + if (fstat(fd, &st) < 0) { + audit_msg(LOG_ERR, "Error fstat'ing config file (%s)", + strerror(errno)); + close(fd); + return 1; + } + if (st.st_uid != 0) { + audit_msg(LOG_ERR, "Error - %s isn't owned by root", + CONFIG_FILE); + close(fd); + return 1; + } + if ((st.st_mode & S_IWOTH) == S_IWOTH) { + audit_msg(LOG_ERR, "Error - %s is world writable", + CONFIG_FILE); + close(fd); + return 1; + } + if (!S_ISREG(st.st_mode)) { + audit_msg(LOG_ERR, "Error - %s is not a regular file", + CONFIG_FILE); + close(fd); + return 1; + } + + /* it's ok, read line by line */ + f = fdopen(fd, "rm"); + if (f == NULL) { + audit_msg(LOG_ERR, "Error - fdopen failed (%s)", + strerror(errno)); + close(fd); + return 1; + } + + while (get_line(f, buf, sizeof(buf), &lineno, CONFIG_FILE)) { + // convert line into name-value pair + const struct kw_pair *kw; + struct nv_pair nv; + rc = nv_split(buf, &nv); + switch (rc) { + case 0: // fine + break; + case 1: // not the right number of tokens. + audit_msg(LOG_ERR, + "Wrong number of arguments for line %d in %s", + lineno, CONFIG_FILE); + break; + case 2: // no '=' sign + audit_msg(LOG_ERR, + "Missing equal sign for line %d in %s", + lineno, CONFIG_FILE); + break; + default: // something else went wrong... + audit_msg(LOG_ERR, + "Unknown error for line %d in %s", + lineno, CONFIG_FILE); + break; + } + if (nv.name == NULL) { + lineno++; + continue; + } + if (nv.value == NULL) { + fclose(f); + audit_msg(LOG_ERR, + "Not processing any more lines in %s", + CONFIG_FILE); + return 1; + } + + /* identify keyword or error */ + kw = kw_lookup(nv.name); + if (kw->name == NULL) { + audit_msg(LOG_ERR, + "Unknown keyword \"%s\" in line %d of %s", + nv.name, lineno, CONFIG_FILE); + fclose(f); + return 1; + } + + /* Check number of options */ + if (kw->max_options == 0 && nv.option != NULL) { + audit_msg(LOG_ERR, + "Keyword \"%s\" has invalid option " + "\"%s\" in line %d of %s", + nv.name, nv.option, lineno, CONFIG_FILE); + fclose(f); + return 1; + } + + /* dispatch to keyword's local parser */ + rc = kw->parser(&nv, lineno, config); + if (rc != 0) { + fclose(f); + return 1; // local parser puts message out + } + + lineno++; + } + + fclose(f); + if (lineno > 1) + return sanity_check(config); + return 0; +} + +static char *get_line(FILE *f, char *buf, unsigned size, int *lineno, + const char *file) +{ + int too_long = 0; + + while (fgets_unlocked(buf, size, f)) { + /* remove newline */ + char *ptr = strchr(buf, 0x0a); + if (ptr) { + if (!too_long) { + *ptr = 0; + return buf; + } + // Reset and start with the next line + too_long = 0; + *lineno = *lineno + 1; + } else { + // If a line is too long skip it. + // Only output 1 warning + if (!too_long) + audit_msg(LOG_ERR, + "Skipping line %d in %s: too long", + *lineno, file); + too_long = 1; + } + } + return NULL; +} + +static int nv_split(char *buf, struct nv_pair *nv) +{ + /* Get the name part */ + char *ptr; + + nv->name = NULL; + nv->value = NULL; + nv->option = NULL; + ptr = audit_strsplit(buf); + if (ptr == NULL) + return 0; /* If there's nothing, go to next line */ + if (ptr[0] == '#') + return 0; /* If there's a comment, go to next line */ + nv->name = ptr; + + /* Check for a '=' */ + ptr = audit_strsplit(NULL); + if (ptr == NULL) + return 1; + if (strcmp(ptr, "=") != 0) + return 2; + + /* get the value */ + ptr = audit_strsplit(NULL); + if (ptr == NULL) + return 1; + nv->value = ptr; + + /* See if there's an option */ + ptr = audit_strsplit(NULL); + if (ptr) { + nv->option = ptr; + + /* Make sure there's nothing else */ + ptr = audit_strsplit(NULL); + if (ptr) + return 1; + } + + /* Everything is OK */ + return 0; +} + +static const struct kw_pair *kw_lookup(const char *val) +{ + int i = 0; + while (keywords[i].name != NULL) { + if (strcasecmp(keywords[i].name, val) == 0) + break; + i++; + } + return &keywords[i]; +} + +static int log_file_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + char *dir = NULL, *tdir; + DIR *d; + int fd, mode; + struct stat buf; + + audit_msg(LOG_DEBUG, "log_file_parser called with: %s", nv->value); + + /* get dir from name. */ + tdir = strdup(nv->value); + if (tdir) + dir = dirname(tdir); + if (dir == NULL || strlen(dir) < 4) { // '/var' is shortest dirname + audit_msg(LOG_ERR, + "The directory name: %s is too short - line %d", + dir, line); + free((void *)tdir); + return 1; + } + + /* verify the directory path exists */ + d = opendir(dir); + if (d == NULL) { + audit_msg(LOG_ERR, "Could not open dir %s (%s)", dir, + strerror(errno)); + free((void *)tdir); + return 1; + } + free((void *)tdir); + closedir(d); + + /* if the file exists, see that its regular, owned by root, + * and not world anything */ + if (log_test == TEST_AUDITD) + mode = O_APPEND; + else + mode = O_RDONLY; + + fd = open(nv->value, mode); + if (fd < 0) { + if (errno == ENOENT) { + fd = create_log_file(nv->value); + if (fd < 0) + return 1; + } else { + audit_msg(LOG_ERR, "Unable to open %s (%s)", nv->value, + strerror(errno)); + return 1; + } + } + if (fstat(fd, &buf) < 0) { + audit_msg(LOG_ERR, "Unable to stat %s (%s)", + nv->value, strerror(errno)); + close(fd); + return 1; + } + close(fd); + if (!S_ISREG(buf.st_mode)) { + audit_msg(LOG_ERR, "%s is not a regular file", nv->value); + return 1; + } + if (buf.st_uid != 0) { + audit_msg(LOG_ERR, "%s is not owned by root", nv->value); + return 1; + } + if ( (buf.st_mode & (S_IXUSR|S_IWGRP|S_IXGRP|S_IRWXO)) ) { + audit_msg(LOG_ERR, "%s permissions should be 0600 or 0640", + nv->value); + return 1; + } + if ( !(buf.st_mode & S_IWUSR) ) { + audit_msg(LOG_ERR, "audit log is not writable by owner"); + return 1; + } + + free((void *)config->log_file); + config->log_file = strdup(nv->value); + if (config->log_file == NULL) + return 1; + return 0; +} + +static int num_logs_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "num_logs_parser called with: %s", nv->value); + + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned long */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + if (i > 99) { + audit_msg(LOG_ERR, "num_logs must be 99 or less"); + return 1; + } + config->num_logs = i; + return 0; +} + +static int qos_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + int i; + + audit_msg(LOG_DEBUG, "qos_parser called with: %s", nv->value); + for (i=0; qos_options[i].name != NULL; i++) { + if (strcasecmp(nv->value, qos_options[i].name) == 0) { + config->qos = qos_options[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +static int dispatch_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + char *dir = NULL, *tdir; + int fd; + struct stat buf; + + audit_msg(LOG_DEBUG, "dispatch_parser called with: %s", nv->value); + if (nv->value == NULL) { + config->dispatcher = NULL; + return 0; + } + + /* get dir from name. */ + tdir = strdup(nv->value); + if (tdir) + dir = dirname(tdir); + if (dir == NULL || strlen(dir) < 4) { // '/var' is shortest dirname + audit_msg(LOG_ERR, + "The directory name: %s is too short - line %d", + dir, line); + free(tdir); + return 1; + } + + free((void *)tdir); + + /* Bypass the perms check if group is not root since + * this will fail under normal circumstances */ + if ((config->log_group != 0 && getuid() != 0) || + (log_test == TEST_SEARCH)) + goto bypass; + + /* if the file exists, see that its regular, owned by root, + * and not world anything */ + fd = open(nv->value, O_RDONLY); + if (fd < 0) { + audit_msg(LOG_ERR, "Unable to open %s (%s)", nv->value, + strerror(errno)); + return 1; + } + if (fstat(fd, &buf) < 0) { + audit_msg(LOG_ERR, "Unable to stat %s (%s)", nv->value, + strerror(errno)); + close(fd); + return 1; + } + close(fd); + if (!S_ISREG(buf.st_mode)) { + audit_msg(LOG_ERR, "%s is not a regular file", nv->value); + return 1; + } + if (buf.st_uid != 0) { + audit_msg(LOG_ERR, "%s is not owned by root", nv->value); + return 1; + } + if ((buf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) != + (S_IRWXU|S_IRGRP|S_IXGRP) && + (buf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) != + (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) { + audit_msg(LOG_ERR, "%s permissions should be 0750 or 0755", + nv->value); + return 1; + } +bypass: + free((void *)config->dispatcher); + config->dispatcher = strdup(nv->value); + if (config->dispatcher == NULL) + return 1; + return 0; +} + +static int name_format_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + int i; + + audit_msg(LOG_DEBUG, "name_format_parser called with: %s", nv->value); + for (i=0; node_name_formats[i].name != NULL; i++) { + if (strcasecmp(nv->value, node_name_formats[i].name) == 0) { + config->node_name_format = node_name_formats[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +static int name_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + audit_msg(LOG_DEBUG, "name_parser called with: %s", nv->value); + if (nv->value == NULL) + config->node_name = NULL; + else + config->node_name = strdup(nv->value); + return 0; +} + +static int max_log_size_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "max_log_size_parser called with: %s", nv->value); + + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned long */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + config->max_log_size = i; + return 0; +} + +static int max_log_size_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + int i; + + audit_msg(LOG_DEBUG, "max_log_size_action_parser called with: %s", + nv->value); + for (i=0; size_actions[i].name != NULL; i++) { + if (strcasecmp(nv->value, size_actions[i].name) == 0) { + config->max_log_size_action = size_actions[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +static int log_format_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + int i; + + audit_msg(LOG_DEBUG, "log_format_parser called with: %s", nv->value); + for (i=0; log_formats[i].name != NULL; i++) { + if (strcasecmp(nv->value, log_formats[i].name) == 0) { + config->log_format = log_formats[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +static int log_group_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + gid_t gid = 0; + + audit_msg(LOG_DEBUG, "log_group_parser called with: %s", + nv->value); + if (isdigit(nv->value[0])) { + errno = 0; + gid = strtoul(nv->value,NULL,10); + if (errno) { + audit_msg(LOG_ERR, + "Numeric group ID conversion error (%s) for %s - line %d\n", + strerror(errno), nv->value, line); + return 1; + } + } else { + struct group *gr ; + + gr = getgrnam(nv->value); + if (gr == NULL) { + audit_msg(LOG_ERR, + "Group ID is non-numeric and unknown (%s) - line %d\n", + nv->value, line); + return 1; + } + gid = gr->gr_gid; + } + config->log_group = gid; + return 0; +} + +static int flush_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + int i; + + audit_msg(LOG_DEBUG, "flush_parser called with: %s", nv->value); + for (i=0; flush_techniques[i].name != NULL; i++) { + if (strcasecmp(nv->value, flush_techniques[i].name) == 0) { + config->flush = flush_techniques[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +static int freq_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "freq_parser called with: %s", nv->value); + + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned int */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + /* Check its range */ + if (i > INT_MAX) { + audit_msg(LOG_ERR, + "Error - converted number (%s) is too large - line %d", + nv->value, line); + return 1; + } + config->freq = (unsigned int)i; + return 0; +} + +static int space_left_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "space_left_parser called with: %s", nv->value); + + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned long */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + config->space_left = i; + return 0; +} + +static int check_exe_name(const char *val, int line) +{ + struct stat buf; + + if (val == NULL) { + audit_msg(LOG_ERR, "Executable path needed for line %d", line); + return -1; + } + + if (*val != '/') { + audit_msg(LOG_ERR, "Absolute path needed for %s - line %d", + val, line); + return -1; + } + + if (stat(val, &buf) < 0) { + audit_msg(LOG_ERR, "Unable to stat %s (%s) - line %d", val, + strerror(errno), line); + return -1; + } + if (!S_ISREG(buf.st_mode)) { + audit_msg(LOG_ERR, "%s is not a regular file - line %d", val, + line); + return -1; + } + if (buf.st_uid != 0) { + audit_msg(LOG_ERR, "%s is not owned by root - line %d", val, + line); + return -1; + } + if ((buf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) != + (S_IRWXU|S_IRGRP|S_IXGRP) && + (buf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) != + (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) { + audit_msg(LOG_ERR, + "%s permissions should be 0750 or 0755 - line %d", + val, line); + return -1; + } + return 0; +} + +static int space_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + int i; + + audit_msg(LOG_DEBUG, "space_action_parser called with: %s", nv->value); + for (i=0; failure_actions[i].name != NULL; i++) { + if (strcasecmp(nv->value, failure_actions[i].name) == 0) { + if (failure_actions[i].option == FA_EMAIL) { + if (access(email_command, X_OK)) { + audit_msg(LOG_ERR, + "Email option is specified but %s doesn't seem executable.", + email_command); + } + } else if (failure_actions[i].option == FA_EXEC) { + if (check_exe_name(nv->option, line)) + return 1; + config->space_left_exe = strdup(nv->option); + } + config->space_left_action = failure_actions[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +// returns 0 if OK, 1 on temp error, 2 on permanent error +static int validate_email(const char *acct) +{ + int i, len; + char *ptr1; + + if (acct == NULL) + return 2; + + len = strlen(acct); + if (len < 2) { + audit_msg(LOG_ERR, + "email: %s is too short, expecting at least 2 characters", + acct); + return 2; + } + + // look for illegal char + for (i=0; i<len; i++) { + if (! (isalnum(acct[i]) || (acct[i] == '@') || + (acct[i]=='.') || (acct[i]=='-') || + (acct[i] == '_')) ) { + audit_msg(LOG_ERR, "email: %s has illegal character", + acct); + return 2; + } + } + + if ((ptr1 = strchr(acct, '@'))) { + char *ptr2; + struct hostent *t_addr; + + ptr2 = strrchr(acct, '.'); // get last dot - sb after @ + if ((ptr2 == NULL) || (ptr1 > ptr2)) { + audit_msg(LOG_ERR, "email: %s should have . after @", + acct); + return 2; + } + + t_addr = gethostbyname(ptr1+1); + if (t_addr == 0) { + if ((h_errno == HOST_NOT_FOUND) || + (h_errno == NO_RECOVERY)) { + audit_msg(LOG_ERR, + "validate_email: failed looking up host for %s", + ptr1+1); + // FIXME: gethostbyname is having trouble + // telling when we have a temporary vs permanent + // dns failure. So, for now, treat all as temp + return 1; + } + else if (h_errno == TRY_AGAIN) + audit_msg(LOG_DEBUG, + "validate_email: temporary failure looking up domain for %s", + ptr1+1); + return 1; + } + } + return 0; +} + +static int action_mail_acct_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + char *tmail; + + audit_msg(LOG_DEBUG, "action_mail_acct_parser called with: %s", + nv->value); + tmail = strdup(nv->value); + if (tmail == NULL) + return 1; + + if (validate_email(tmail) > 1) { + free(tmail); + return 1; + } + + + if (config->action_mail_acct) + free((void *)config->action_mail_acct); + config->action_mail_acct = tmail; + return 0; +} + +static int admin_space_left_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "admin_space_left_parser called with: %s", + nv->value); + + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned long */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + config->admin_space_left = i; + return 0; +} + +static int admin_space_left_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + int i; + + audit_msg(LOG_DEBUG, "admin_space_left_action_parser called with: %s", + nv->value); + for (i=0; failure_actions[i].name != NULL; i++) { + if (strcasecmp(nv->value, failure_actions[i].name) == 0) { + if (failure_actions[i].option == FA_EMAIL) { + if (access(email_command, X_OK)) { + audit_msg(LOG_ERR, + "Email option is specified but %s doesn't seem executable.", + email_command); + } + } else if (failure_actions[i].option == FA_EXEC) { + if (check_exe_name(nv->option, line)) + return 1; + config->admin_space_left_exe = + strdup(nv->option); + } + config->admin_space_left_action = + failure_actions[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +static int disk_full_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + int i; + + audit_msg(LOG_DEBUG, "disk_full_action_parser called with: %s", + nv->value); + for (i=0; failure_actions[i].name != NULL; i++) { + if (strcasecmp(nv->value, failure_actions[i].name) == 0) { + if (failure_actions[i].option == FA_EMAIL) { + audit_msg(LOG_ERR, + "Illegal option %s for disk_full_action - line %d", + nv->value, line); + return 1; + } else if (failure_actions[i].option == FA_EXEC) { + if (check_exe_name(nv->option, line)) + return 1; + config->disk_full_exe = strdup(nv->option); + } + config->disk_full_action = failure_actions[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +static int disk_error_action_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + int i; + + audit_msg(LOG_DEBUG, "disk_error_action_parser called with: %s", + nv->value); + for (i=0; failure_actions[i].name != NULL; i++) { + if (strcasecmp(nv->value, failure_actions[i].name) == 0) { + if (failure_actions[i].option == FA_EMAIL || + failure_actions[i].option == FA_ROTATE) { + audit_msg(LOG_ERR, + "Illegal option %s for disk_error_action - line %d", + nv->value, line); + return 1; + } else if (failure_actions[i].option == FA_EXEC) { + if (check_exe_name(nv->option, line)) + return 1; + config->disk_error_exe = strdup(nv->option); + } + config->disk_error_action = failure_actions[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +static int priority_boost_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "priority_boost_parser called with: %s", + nv->value); + + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned int */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + /* Check its range */ + if (i > INT_MAX) { + audit_msg(LOG_ERR, + "Error - converted number (%s) is too large - line %d", + nv->value, line); + return 1; + } + config->priority_boost = (unsigned int)i; + return 0; +} + +static int tcp_listen_port_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "tcp_listen_port_parser called with: %s", + nv->value); + +#ifndef USE_LISTENER + audit_msg(LOG_DEBUG, + "Listener support is not enabled, ignoring value at line %d", + line); + return 0; +#else + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned int */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + /* Check its range */ + if (i > TCP_PORT_MAX) { + audit_msg(LOG_ERR, + "Error - converted number (%s) is too large - line %d", + nv->value, line); + return 1; + } + if (i < 1) { + audit_msg(LOG_ERR, + "Error - converted number (%s) is too small - line %d", + nv->value, line); + return 1; + } + config->tcp_listen_port = (unsigned int)i; + return 0; +#endif +} + +static int tcp_listen_queue_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "tcp_listen_queue_parser called with: %s", + nv->value); + +#ifndef USE_LISTENER + audit_msg(LOG_DEBUG, + "Listener support is not enabled, ignoring value at line %d", + line); + return 0; +#else + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned int */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + /* Check its range. While this value is technically + unlimited, it's limited by the kernel, and we limit it here + for sanity. */ + if (i > TCP_PORT_MAX) { + audit_msg(LOG_ERR, + "Error - converted number (%s) is too large - line %d", + nv->value, line); + return 1; + } + if (i < 1) { + audit_msg(LOG_ERR, + "Error - converted number (%s) is too small - line %d", + nv->value, line); + return 1; + } + config->tcp_listen_queue = (unsigned int)i; + return 0; +#endif +} + + +static int tcp_max_per_addr_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "tcp_max_per_addr_parser called with: %s", + nv->value); + +#ifndef USE_LISTENER + audit_msg(LOG_DEBUG, + "Listener support is not enabled, ignoring value at line %d", + line); + return 0; +#else + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned int */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + /* Check its range. While this value is technically + unlimited, it's limited by the kernel, and we limit it here + for sanity. */ + if (i > 1024) { + audit_msg(LOG_ERR, + "Error - converted number (%s) is too large - line %d", + nv->value, line); + return 1; + } + if (i < 1) { + audit_msg(LOG_ERR, + "Error - converted number (%s) is too small - line %d", + nv->value, line); + return 1; + } + config->tcp_max_per_addr = (unsigned int)i; + return 0; +#endif +} + +static int use_libwrap_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + unsigned long i; + + audit_msg(LOG_DEBUG, "use_libwrap_parser called with: %s", + nv->value); + + for (i=0; yes_no_values[i].name != NULL; i++) { + if (strcasecmp(nv->value, yes_no_values[i].name) == 0) { + config->use_libwrap = yes_no_values[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +} + +static int tcp_client_ports_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i, minv, maxv; + const char *saw_dash = NULL; + + audit_msg(LOG_DEBUG, "tcp_listen_queue_parser called with: %s", + nv->value); + +#ifndef USE_LISTENER + audit_msg(LOG_DEBUG, + "Listener support is not enabled, ignoring value at line %d", + line); + return 0; +#else + /* check that all chars are numbers, with an optional inclusive '-'. */ + for (i=0; ptr[i]; i++) { + if (i > 0 && ptr[i] == '-' && ptr[i+1] != '\0') { + saw_dash = ptr + i; + continue; + } + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers, or " + "two numbers separated by a dash - line %d", + nv->value, line); + return 1; + } + } + for (; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers, or " + "two numbers separated by a dash - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned int */ + errno = 0; + maxv = minv = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + if (saw_dash) { + maxv = strtoul(saw_dash + 1, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + } + /* Check their ranges. */ + if (minv > TCP_PORT_MAX) { + audit_msg(LOG_ERR, + "Error - converted number (%ld) is too large - line %d", + minv, line); + return 1; + } + if (maxv > TCP_PORT_MAX) { + audit_msg(LOG_ERR, + "Error - converted number (%ld) is too large - line %d", + maxv, line); + return 1; + } + if (minv > maxv) { + audit_msg(LOG_ERR, + "Error - converted range (%ld-%ld) is reversed - line %d", + minv, maxv, line); + return 1; + } + config->tcp_client_min_port = (unsigned int)minv; + config->tcp_client_max_port = (unsigned int)maxv; + return 0; +#endif +} + +static int tcp_client_max_idle_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + const char *ptr = nv->value; + unsigned long i; + + audit_msg(LOG_DEBUG, "tcp_client_max_idle_parser called with: %s", + nv->value); + +#ifndef USE_LISTENER + audit_msg(LOG_DEBUG, + "Listener support is not enabled, ignoring value at line %d", + line); + return 0; +#else + /* check that all chars are numbers */ + for (i=0; ptr[i]; i++) { + if (!isdigit(ptr[i])) { + audit_msg(LOG_ERR, + "Value %s should only be numbers - line %d", + nv->value, line); + return 1; + } + } + + /* convert to unsigned int */ + errno = 0; + i = strtoul(nv->value, NULL, 10); + if (errno) { + audit_msg(LOG_ERR, + "Error converting string to a number (%s) - line %d", + strerror(errno), line); + return 1; + } + /* Check its range. While this value is technically + unlimited, it's limited by the kernel, and we limit it here + for sanity. */ + if (i > INT_MAX) { + audit_msg(LOG_ERR, + "Error - converted number (%s) is too large - line %d", + nv->value, line); + return 1; + } + config->tcp_client_max_idle = (unsigned int)i; + return 0; +#endif +} + +static int enable_krb5_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + audit_msg(LOG_DEBUG, "enable_krb5_parser called with: %s", + nv->value); + +#ifndef USE_GSSAPI + audit_msg(LOG_DEBUG, + "GSSAPI support is not enabled, ignoring value at line %d", + line); + return 0; +#else + unsigned long i; + + for (i=0; yes_no_values[i].name != NULL; i++) { + if (strcasecmp(nv->value, yes_no_values[i].name) == 0) { + config->enable_krb5 = yes_no_values[i].option; + return 0; + } + } + audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line); + return 1; +#endif +} + +static int krb5_principal_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + audit_msg(LOG_DEBUG,"krb5_principal_parser called with: %s",nv->value); +#ifndef USE_GSSAPI + audit_msg(LOG_DEBUG, + "GSSAPI support is not enabled, ignoring value at line %d", + line); +#else + config->krb5_principal = strdup(nv->value); +#endif + return 0; +} + +static int krb5_key_file_parser(struct nv_pair *nv, int line, + struct daemon_conf *config) +{ + audit_msg(LOG_DEBUG, "krb5_key_file_parser called with: %s", nv->value); +#ifndef USE_GSSAPI + audit_msg(LOG_DEBUG, + "GSSAPI support is not enabled, ignoring value at line %d", + line); +#else + config->krb5_key_file = strdup(nv->value); +#endif + return 0; +} + +/* + * This function is where we do the integrated check of the audit config + * options. At this point, all fields have been read. Returns 0 if no + * problems and 1 if problems detected. + */ +static int sanity_check(struct daemon_conf *config) +{ + /* Error checking */ + if (config->space_left <= config->admin_space_left) { + audit_msg(LOG_ERR, + "Error - space_left(%lu) must be larger than admin_space_left(%lu)", + config->space_left, config->admin_space_left); + return 1; + } + if (config->flush == FT_INCREMENTAL && config->freq == 0) { + audit_msg(LOG_ERR, + "Error - incremental flushing chosen, but 0 selected for freq"); + return 1; + } + /* Warnings */ + if (config->flush > FT_INCREMENTAL && config->freq != 0) { + audit_msg(LOG_WARNING, + "Warning - freq is non-zero and incremental flushing not selected."); + } + return 0; +} + +const char *audit_lookup_format(int fmt) +{ + int i; + + for (i=0; log_formats[i].name != NULL; i++) { + if (log_formats[i].option == fmt) + return log_formats[i].name; + } + return NULL; +} + +int create_log_file(const char *val) +{ + int fd; + + umask(S_IRWXO); + fd = open(val, O_CREAT|O_EXCL|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP); + if (fd < 0) + audit_msg(LOG_ERR, "Unable to create %s (%s)", val, + strerror(errno)); + return fd; +} + +void free_config(struct daemon_conf *config) +{ + free((void *)config->sender_ctx); + free((void *)config->log_file); + free((void *)config->dispatcher); + free((void *)config->node_name); + free((void *)config->action_mail_acct); + free((void *)config->space_left_exe); + free((void *)config->admin_space_left_exe); + free((void *)config->disk_full_exe); + free((void *)config->disk_error_exe); + free((void *)config->krb5_principal); + free((void *)config->krb5_key_file); +} + +int resolve_node(struct daemon_conf *config) +{ + int rc = 0; + char tmp_name[255]; + + /* Get the host name representation */ + switch (config->node_name_format) + { + case N_NONE: + break; + case N_HOSTNAME: + if (gethostname(tmp_name, sizeof(tmp_name))) { + audit_msg(LOG_ERR, + "Unable to get machine name"); + rc = -1; + } else + config->node_name = strdup(tmp_name); + break; + case N_USER: + if (config->node_name == NULL) { + audit_msg(LOG_ERR, "User defined name missing"); + rc = -1; + } + break; + case N_FQD: + if (gethostname(tmp_name, sizeof(tmp_name))) { + audit_msg(LOG_ERR, + "Unable to get machine name"); + rc = -1; + } else { + int rc2; + struct addrinfo *ai; + struct addrinfo hints; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG | AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + + rc2 = getaddrinfo(tmp_name, NULL, &hints, &ai); + if (rc2 != 0) { + audit_msg(LOG_ERR, + "Cannot resolve hostname %s (%s)", + tmp_name, gai_strerror(rc)); + rc = -1; + break; + } + config->node_name = strdup(ai->ai_canonname); + freeaddrinfo(ai); + } + break; + case N_NUMERIC: + if (gethostname(tmp_name, sizeof(tmp_name))) { + audit_msg(LOG_ERR, + "Unable to get machine name"); + rc = -1; + } else { + int rc2; + struct addrinfo *ai; + struct addrinfo hints; + + audit_msg(LOG_DEBUG, + "Resolving numeric address for %s", + tmp_name); + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + + rc2 = getaddrinfo(tmp_name, NULL, &hints, &ai); + if (rc2) { + audit_msg(LOG_ERR, + "Cannot resolve hostname %s (%s)", + tmp_name, gai_strerror(rc2)); + rc = -1; + break; + } + inet_ntop(ai->ai_family, + ai->ai_family == AF_INET ? + (void *) &((struct sockaddr_in *)ai->ai_addr)->sin_addr : + (void *) &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr, + tmp_name, INET6_ADDRSTRLEN); + freeaddrinfo(ai); + config->node_name = strdup(tmp_name); + } + break; + } + if (rc == 0 && config->node_name) + audit_msg(LOG_DEBUG, "Resolved node name: %s", + config->node_name); + return rc; +} + diff --git a/framework/src/audit/src/auditd-config.h b/framework/src/audit/src/auditd-config.h new file mode 100644 index 00000000..5a3eb6bb --- /dev/null +++ b/framework/src/audit/src/auditd-config.h @@ -0,0 +1,100 @@ +/* auditd-config.h -- + * Copyright 2004-2009,2014 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#ifndef AUDITD_CONFIG_H +#define AUDITD_CONFIG_H + +#include "libaudit.h" +#include <grp.h> +#define CONFIG_FILE "/etc/audit/auditd.conf" +#define MEGABYTE 1048576UL + +typedef enum { D_FOREGROUND, D_BACKGROUND } daemon_t; +typedef enum { LF_RAW, LF_NOLOG } logging_formats; +typedef enum { FT_NONE, FT_INCREMENTAL, FT_DATA, FT_SYNC } flush_technique; +typedef enum { FA_IGNORE, FA_SYSLOG, FA_ROTATE, FA_EMAIL, FA_EXEC, FA_SUSPEND, + FA_SINGLE, FA_HALT } failure_action_t; +typedef enum { SZ_IGNORE, SZ_SYSLOG, SZ_SUSPEND, SZ_ROTATE, + SZ_KEEP_LOGS } size_action; +typedef enum { QOS_NON_BLOCKING, QOS_BLOCKING } qos_t; +typedef enum { TEST_AUDITD, TEST_SEARCH } log_test_t; +typedef enum { N_NONE, N_HOSTNAME, N_FQD, N_NUMERIC, N_USER } node_t; + +struct daemon_conf +{ + daemon_t daemonize; + qos_t qos; /* use blocking/non-blocking sockets */ + uid_t sender_uid; /* the uid for sender of sighup */ + pid_t sender_pid; /* the pid for sender of sighup */ + const char *sender_ctx; /* the context for the sender of sighup */ + const char *log_file; + logging_formats log_format; + gid_t log_group; + unsigned int priority_boost; + flush_technique flush; + unsigned int freq; + unsigned int num_logs; + const char *dispatcher; + node_t node_name_format; + const char *node_name; + unsigned long max_log_size; + size_action max_log_size_action; + unsigned long space_left; + failure_action_t space_left_action; + const char *space_left_exe; + const char *action_mail_acct; + unsigned long admin_space_left; + failure_action_t admin_space_left_action; + const char *admin_space_left_exe; + failure_action_t disk_full_action; + const char *disk_full_exe; + failure_action_t disk_error_action; + const char *disk_error_exe; + unsigned long tcp_listen_port; + unsigned long tcp_listen_queue; + unsigned long tcp_max_per_addr; + int use_libwrap; + unsigned long tcp_client_min_port; + unsigned long tcp_client_max_port; + unsigned long tcp_client_max_idle; + int enable_krb5; + const char *krb5_principal; + const char *krb5_key_file; +}; + +void set_allow_links(int allow); +int load_config(struct daemon_conf *config, log_test_t lt); +void clear_config(struct daemon_conf *config); +const char *audit_lookup_format(int fmt); +int create_log_file(const char *val); +int resolve_node(struct daemon_conf *config); + +void init_config_manager(void); +#ifdef AUDITD_EVENT_H +int start_config_manager(struct auditd_reply_list *rep); +#endif +void shutdown_config(void); +void free_config(struct daemon_conf *config); + +#endif + diff --git a/framework/src/audit/src/auditd-dispatch.c b/framework/src/audit/src/auditd-dispatch.c new file mode 100644 index 00000000..02681f03 --- /dev/null +++ b/framework/src/audit/src/auditd-dispatch.c @@ -0,0 +1,213 @@ +/* auditd-dispatch.c -- + * Copyright 2005-07,2013 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * Junji Kanemaru <junji.kanemaru@linuon.com> + */ + +#include "config.h" +#include <unistd.h> +#include <sys/uio.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#include "libaudit.h" +#include "private.h" +#include "auditd-dispatch.h" + +/* This is the communications channel between auditd & the dispatcher */ +static int disp_pipe[2] = {-1, -1}; +static pid_t pid = 0; +static int n_errs = 0; +#define REPORT_LIMIT 10 + +int dispatcher_pid(void) +{ + return pid; +} + +void dispatcher_reaped(void) +{ + audit_msg(LOG_INFO, "dispatcher %d reaped\n", pid); + pid = 0; + shutdown_dispatcher(); +} + +/* set_flags: to set flags to file desc */ +static int set_flags(int fn, int flags) +{ + int fl; + + if ((fl = fcntl(fn, F_GETFL, 0)) < 0) { + audit_msg(LOG_ERR, "fcntl failed. Cannot get flags (%s)\n", + strerror(errno)); + return fl; + } + + fl |= flags; + + return fcntl(fn, F_SETFL, fl); +} + +/* This function returns 1 on error & 0 on success */ +int init_dispatcher(const struct daemon_conf *config) +{ + struct sigaction sa; + + if (config->dispatcher == NULL) + return 0; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, disp_pipe)) { + audit_msg(LOG_ERR, "Failed creating disp_pipe"); + return 1; + } + + /* Make both disp_pipe non-blocking */ + if (config->qos == QOS_NON_BLOCKING) { + if (set_flags(disp_pipe[0], O_NONBLOCK) < 0 || + set_flags(disp_pipe[1], O_NONBLOCK) < 0) { + audit_msg(LOG_ERR, "Failed to set O_NONBLOCK flag"); + return 1; + } + } + + // do the fork + pid = fork(); + switch(pid) { + case 0: // child + dup2(disp_pipe[0], 0); + close(disp_pipe[0]); + close(disp_pipe[1]); + sigfillset (&sa.sa_mask); + sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); + setsid(); + execl(config->dispatcher, config->dispatcher, NULL); + audit_msg(LOG_ERR, "exec() failed"); + exit(1); + break; + case -1: // error + return 1; + break; + default: // parent + close(disp_pipe[0]); + disp_pipe[0] = -1; + /* Avoid leaking this */ + if (fcntl(disp_pipe[1], F_SETFD, FD_CLOEXEC) < 0) { + audit_msg(LOG_ERR, + "Failed to set FD_CLOEXEC flag"); + return 1; + } + audit_msg(LOG_INFO, "Started dispatcher: %s pid: %u", + config->dispatcher, pid); + break; + } + + return 0; +} + +void shutdown_dispatcher(void) +{ + // kill child + if (pid) + kill(pid, SIGTERM); + // wait for term + // if not in time, send sigkill + pid = 0; + + // cleanup comm pipe + if (disp_pipe[0] >= 0) { + close(disp_pipe[0]); + disp_pipe[0] = -1; + } + if (disp_pipe[1] >= 0) { + close(disp_pipe[1]); + disp_pipe[1] = -1; + } +} + +void reconfigure_dispatcher(const struct daemon_conf *config) +{ + // signal child or start it so it can see if config changed + if (pid) + kill(pid, SIGHUP); + else + init_dispatcher(config); +} + +/* Returns -1 on err, 0 on success, and 1 if eagain occurred and not an err */ +int dispatch_event(const struct audit_reply *rep, int is_err) +{ + int rc, count = 0; + struct iovec vec[2]; + struct audit_dispatcher_header hdr; + + if (disp_pipe[1] == -1) + return 0; + + // Don't send reconfig or rotate as they are purely internal to daemon + if (rep->type == AUDIT_DAEMON_RECONFIG || + rep->type == AUDIT_DAEMON_ROTATE) + return 0; + + hdr.ver = AUDISP_PROTOCOL_VER; /* Hard-coded to current protocol */ + hdr.hlen = sizeof(struct audit_dispatcher_header); + hdr.type = rep->type; + hdr.size = rep->len; + + vec[0].iov_base = (void*)&hdr; + vec[0].iov_len = sizeof(hdr); + vec[1].iov_base = (void*)rep->message; + vec[1].iov_len = rep->len; + + do { + rc = writev(disp_pipe[1], vec, 2); + } while (rc < 0 && errno == EAGAIN && count++ < 8); + + // close pipe if no child or peer has been lost + if (rc <= 0) { + if (errno == EPIPE) { + shutdown_dispatcher(); + n_errs = 0; + } else if (errno == EAGAIN && !is_err) { + return 1; + } else { + if (n_errs <= REPORT_LIMIT) { + audit_msg(LOG_ERR, + "dispatch err (%s) event lost", + errno == EAGAIN ? "pipe full" : + strerror(errno)); + n_errs++; + } + if (n_errs == REPORT_LIMIT) { + audit_msg(LOG_ERR, + "dispatch error reporting limit" + " reached - ending report" + " notification."); + n_errs++; + } + return -1; + } + } else + n_errs = 0; + return 0; +} + diff --git a/framework/src/audit/src/auditd-dispatch.h b/framework/src/audit/src/auditd-dispatch.h new file mode 100644 index 00000000..25d32352 --- /dev/null +++ b/framework/src/audit/src/auditd-dispatch.h @@ -0,0 +1,37 @@ +/* auditd-dispatch.h -- + * Copyright 2005,2007,2013 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#ifndef AUDITD_DISPATCH_H +#define AUDITD_DISPATCH_H + +#include "auditd-config.h" + +int dispatcher_pid(void); +void dispatcher_reaped(void); +int init_dispatcher(const struct daemon_conf *config); +void shutdown_dispatcher(void); +void reconfigure_dispatcher(const struct daemon_conf *config); +int dispatch_event(const struct audit_reply *rep, int is_err); + +#endif + diff --git a/framework/src/audit/src/auditd-event.c b/framework/src/audit/src/auditd-event.c new file mode 100644 index 00000000..ed0272e2 --- /dev/null +++ b/framework/src/audit/src/auditd-event.c @@ -0,0 +1,1407 @@ +/* auditd-event.c -- + * Copyright 2004-08,2011,2013 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <pthread.h> +#include <signal.h> +#include <fcntl.h> /* O_NOFOLLOW needs gnu defined */ +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <sys/time.h> +#include <sys/vfs.h> +#include <limits.h> /* POSIX_HOST_NAME_MAX */ +#include "auditd-event.h" +#include "auditd-dispatch.h" +#include "auditd-listen.h" +#include "libaudit.h" +#include "private.h" + +/* This is defined in auditd.c */ +extern volatile int stop; + +struct auditd_consumer_data { + struct daemon_conf *config; + pthread_mutex_t queue_lock; + pthread_cond_t queue_nonempty; + struct auditd_reply_list *head; + struct auditd_reply_list *tail; + int log_fd; + FILE *log_file; +}; + +/* Local function prototypes */ +static void *event_thread_main(void *arg); +static void handle_event(struct auditd_consumer_data *data); +static void write_to_log(const char *buf, struct auditd_consumer_data *data); +static void check_log_file_size(struct auditd_consumer_data *data); +static void check_space_left(int lfd, struct auditd_consumer_data *data); +static void do_space_left_action(struct auditd_consumer_data *data, int admin); +static void do_disk_full_action(struct auditd_consumer_data *data); +static void do_disk_error_action(const char *func, struct daemon_conf *config, + int err); +static void check_excess_logs(struct auditd_consumer_data *data); +static void rotate_logs_now(struct auditd_consumer_data *data); +static void rotate_logs(struct auditd_consumer_data *data, + unsigned int num_logs); +static void shift_logs(struct auditd_consumer_data *data); +static int open_audit_log(struct auditd_consumer_data *data); +static void change_runlevel(const char *level); +static void safe_exec(const char *exe); +static char *format_raw(const struct audit_reply *rep, + struct daemon_conf *config); +static void reconfigure(struct auditd_consumer_data *data); + + +/* Local Data */ +static struct auditd_consumer_data consumer_data; +static pthread_t event_thread; +static unsigned int disk_err_warning = 0; +static int fs_space_warning = 0; +static int fs_admin_space_warning = 0; +static int fs_space_left = 1; +static int logging_suspended = 0; +static const char *SINGLE = "1"; +static const char *HALT = "0"; +static char *format_buf = NULL; +static off_t log_size = 0; + + +void shutdown_events(void) +{ + /* Give it 5 seconds to clear the queue */ + alarm(5); + pthread_join(event_thread, NULL); + free((void *)format_buf); + fclose(consumer_data.log_file); +} + +int init_event(struct daemon_conf *config) +{ + /* Store the netlink descriptor and config info away */ + consumer_data.config = config; + consumer_data.log_fd = -1; + + /* Setup IPC mechanisms */ + pthread_mutex_init(&consumer_data.queue_lock, NULL); + pthread_cond_init(&consumer_data.queue_nonempty, NULL); + + /* Reset the queue */ + consumer_data.head = consumer_data.tail = NULL; + + /* Now open the log */ + if (config->daemonize == D_BACKGROUND) { + if (open_audit_log(&consumer_data)) + return 1; + } else { + consumer_data.log_fd = 1; // stdout + consumer_data.log_file = fdopen(consumer_data.log_fd, "a"); + if (consumer_data.log_file == NULL) { + audit_msg(LOG_ERR, + "Error setting up stdout descriptor (%s)", + strerror(errno)); + return 1; + } + /* Set it to line buffering */ + setlinebuf(consumer_data.log_file); + } + + /* Create the worker thread */ + if (pthread_create(&event_thread, NULL, + event_thread_main, &consumer_data) < 0) { + audit_msg(LOG_ERR, "Couldn't create event thread, exiting"); + fclose(consumer_data.log_file); + return 1; + } + + if (config->daemonize == D_BACKGROUND) { + check_log_file_size(&consumer_data); + check_excess_logs(&consumer_data); + check_space_left(consumer_data.log_fd, &consumer_data); + } + format_buf = (char *)malloc(MAX_AUDIT_MESSAGE_LENGTH + + _POSIX_HOST_NAME_MAX); + if (format_buf == NULL) { + audit_msg(LOG_ERR, "No memory for formatting, exiting"); + fclose(consumer_data.log_file); + return 1; + } + return 0; +} + +/* This function takes a malloc'd rep and places it on the queue. The + dequeue'r is responsible for freeing the memory. */ +void enqueue_event(struct auditd_reply_list *rep) +{ + char *buf = NULL; + int len; + + rep->ack_func = 0; + rep->ack_data = 0; + rep->sequence_id = 0; + + if (rep->reply.type != AUDIT_DAEMON_RECONFIG) { + switch (consumer_data.config->log_format) + { + case LF_RAW: + buf = format_raw(&rep->reply, consumer_data.config); + break; + case LF_NOLOG: + // We need the rotate event to get enqueued + if (rep->reply.type != AUDIT_DAEMON_ROTATE ) { + // Internal DAEMON messages should be free'd + if (rep->reply.type >= AUDIT_FIRST_DAEMON && + rep->reply.type <= AUDIT_LAST_DAEMON) + free((void *)rep->reply.message); + free(rep); + return; + } + break; + default: + audit_msg(LOG_ERR, + "Illegal log format detected %d", + consumer_data.config->log_format); + // Internal DAEMON messages should be free'd + if (rep->reply.type >= AUDIT_FIRST_DAEMON && + rep->reply.type <= AUDIT_LAST_DAEMON) + free((void *)rep->reply.message); + free(rep); + return; + } + + if (buf) { + len = strlen(buf); + if (len < MAX_AUDIT_MESSAGE_LENGTH - 1) + memcpy(rep->reply.msg.data, buf, len+1); + else { + // FIXME: is truncation the right thing to do? + memcpy(rep->reply.msg.data, buf, + MAX_AUDIT_MESSAGE_LENGTH-1); + rep->reply.msg.data[MAX_AUDIT_MESSAGE_LENGTH-1] = 0; + } + } + } + + rep->next = NULL; /* new packet goes at end - so zero this */ + + pthread_mutex_lock(&consumer_data.queue_lock); + if (consumer_data.head == NULL) { + consumer_data.head = consumer_data.tail = rep; + pthread_cond_signal(&consumer_data.queue_nonempty); + } else { + /* FIXME: wait for room on the queue */ + + /* OK there's room...add it in */ + consumer_data.tail->next = rep; /* link in at end */ + consumer_data.tail = rep; /* move end to newest */ + } + pthread_mutex_unlock(&consumer_data.queue_lock); +} + +/* This function takes a preformatted message and places it on the + queue. The dequeue'r is responsible for freeing the memory. */ +void enqueue_formatted_event(char *msg, ack_func_type ack_func, void *ack_data, uint32_t sequence_id) +{ + int len; + struct auditd_reply_list *rep; + + rep = (struct auditd_reply_list *) calloc (1, sizeof (*rep)); + if (rep == NULL) { + audit_msg(LOG_ERR, "Cannot allocate audit reply"); + return; + } + + rep->ack_func = ack_func; + rep->ack_data = ack_data; + rep->sequence_id = sequence_id; + + len = strlen (msg); + if (len < MAX_AUDIT_MESSAGE_LENGTH - 1) + memcpy (rep->reply.msg.data, msg, len+1); + else { + /* FIXME: is truncation the right thing to do? */ + memcpy (rep->reply.msg.data, msg, MAX_AUDIT_MESSAGE_LENGTH-1); + rep->reply.msg.data[MAX_AUDIT_MESSAGE_LENGTH-1] = 0; + } + + pthread_mutex_lock(&consumer_data.queue_lock); + if (consumer_data.head == NULL) { + consumer_data.head = consumer_data.tail = rep; + pthread_cond_signal(&consumer_data.queue_nonempty); + } else { + /* FIXME: wait for room on the queue */ + + /* OK there's room...add it in */ + consumer_data.tail->next = rep; /* link in at end */ + consumer_data.tail = rep; /* move end to newest */ + } + pthread_mutex_unlock(&consumer_data.queue_lock); +} + +void resume_logging(void) +{ + logging_suspended = 0; + fs_space_left = 1; + disk_err_warning = 0; + fs_space_warning = 0; + fs_admin_space_warning = 0; + audit_msg(LOG_ERR, "Audit daemon is attempting to resume logging."); +} + +static void *event_thread_main(void *arg) +{ + struct auditd_consumer_data *data = arg; + sigset_t sigs; + + /* This is a worker thread. Don't handle signals. */ + sigemptyset(&sigs); + sigaddset(&sigs, SIGALRM); + sigaddset(&sigs, SIGTERM); + sigaddset(&sigs, SIGHUP); + sigaddset(&sigs, SIGUSR1); + sigaddset(&sigs, SIGUSR2); + pthread_sigmask(SIG_SETMASK, &sigs, NULL); + + while (1) { + struct auditd_reply_list *cur; + int stop_req = 0; +// FIXME: wait for data + pthread_mutex_lock(&data->queue_lock); + while (data->head == NULL) { + pthread_cond_wait(&data->queue_nonempty, + &data->queue_lock); + } +// FIXME: at this point we can use data->head unlocked since it won't change. + handle_event(data); + cur = data->head; +// FIXME: relock at this point + if (data->tail == data->head) + data->tail = NULL; + data->head = data->head->next; + if (data->head == NULL && stop && + ( cur->reply.type == AUDIT_DAEMON_END || + cur->reply.type == AUDIT_DAEMON_ABORT) ) + stop_req = 1; + pthread_mutex_unlock(&data->queue_lock); + + /* Internal DAEMON messages should be free'd */ + if (cur->reply.type >= AUDIT_FIRST_DAEMON && + cur->reply.type <= AUDIT_LAST_DAEMON) { + free((void *)cur->reply.message); + } + free(cur); + if (stop_req) + break; + } + return NULL; +} + + +/* This function takes the newly dequeued event and handles it. */ +static unsigned int count = 0L; +static void handle_event(struct auditd_consumer_data *data) +{ + char *buf = data->head->reply.msg.data; + + if (data->head->reply.type == AUDIT_DAEMON_RECONFIG) { + reconfigure(data); + switch (consumer_data.config->log_format) + { + case LF_RAW: + buf = format_raw(&data->head->reply, consumer_data.config); + break; + case LF_NOLOG: + return; + default: + audit_msg(LOG_ERR, + "Illegal log format detected %d", + consumer_data.config->log_format); + return; + } + } else if (data->head->reply.type == AUDIT_DAEMON_ROTATE) { + rotate_logs_now(data); + if (consumer_data.config->log_format == LF_NOLOG) + return; + } + if (!logging_suspended) { + + write_to_log(buf, data); + + /* See if we need to flush to disk manually */ + if (data->config->flush == FT_INCREMENTAL) { + count++; + if ((count % data->config->freq) == 0) { + int rc; + errno = 0; + do { + rc = fflush(data->log_file); + } while (rc < 0 && errno == EINTR); + if (errno) { + if (errno == ENOSPC && + fs_space_left == 1) { + fs_space_left = 0; + do_disk_full_action(data); + } else + //EIO is only likely failure mode + do_disk_error_action("flush", + data->config, errno); + } + + /* EIO is only likely failure mode */ + if ((data->config->daemonize == D_BACKGROUND)&& + (fsync(data->log_fd) != 0)) { + do_disk_error_action("fsync", + data->config, errno); + } + } + } + } +} + +static void send_ack(struct auditd_consumer_data *data, int ack_type, + const char *msg) +{ + if (data->head->ack_func) { + unsigned char header[AUDIT_RMW_HEADER_SIZE]; + + AUDIT_RMW_PACK_HEADER(header, 0, ack_type, strlen(msg), + data->head->sequence_id); + + data->head->ack_func(data->head->ack_data, header, msg); + } +} + +/* This function writes the given buf to the current log file */ +static void write_to_log(const char *buf, struct auditd_consumer_data *data) +{ + int rc; + FILE *f = data->log_file; + struct daemon_conf *config = data->config; + int ack_type = AUDIT_RMW_TYPE_ACK; + const char *msg = ""; + + /* write it to disk */ + rc = fprintf(f, "%s\n", buf); + + /* error? Handle it */ + if (rc < 0) { + if (errno == ENOSPC) { + ack_type = AUDIT_RMW_TYPE_DISKFULL; + msg = "disk full"; + send_ack(data, ack_type, msg); + if (fs_space_left == 1) { + fs_space_left = 0; + do_disk_full_action(data); + } + } else { + int saved_errno = errno; + ack_type = AUDIT_RMW_TYPE_DISKERROR; + msg = "disk write error"; + send_ack(data, ack_type, msg); + do_disk_error_action("write", config, saved_errno); + } + } else { + /* check log file size & space left on partition */ + if (config->daemonize == D_BACKGROUND) { + // If either of these fail, I consider it an + // inconvenience as opposed to something that is + // actionable. There may be some temporary condition + // that the system recovers from. The real error + // occurs on write. + log_size += rc; + check_log_file_size(data); + check_space_left(data->log_fd, data); + } + + if (fs_space_warning) + ack_type = AUDIT_RMW_TYPE_DISKLOW; + send_ack(data, ack_type, msg); + disk_err_warning = 0; + } +} + +static void check_log_file_size(struct auditd_consumer_data *data) +{ + struct daemon_conf *config = data->config; + + /* did we cross the size limit? */ + off_t sz = log_size / MEGABYTE; + + if (sz >= config->max_log_size && (config->daemonize == D_BACKGROUND)) { + switch (config->max_log_size_action) + { + case SZ_IGNORE: + break; + case SZ_SYSLOG: + audit_msg(LOG_ERR, + "Audit daemon log file is larger than max size"); + break; + case SZ_SUSPEND: + audit_msg(LOG_ERR, + "Audit daemon is suspending logging due to logfile size."); + logging_suspended = 1; + break; + case SZ_ROTATE: + if (data->config->num_logs > 1) { + audit_msg(LOG_NOTICE, + "Audit daemon rotating log files"); + rotate_logs(data, 0); + } + break; + case SZ_KEEP_LOGS: + audit_msg(LOG_NOTICE, + "Audit daemon rotating log files with keep option"); + shift_logs(data); + break; + default: + audit_msg(LOG_ALERT, + "Audit daemon log file is larger than max size and unknown action requested"); + break; + } + } +} + +static void check_space_left(int lfd, struct auditd_consumer_data *data) +{ + int rc; + struct statfs buf; + struct daemon_conf *config = data->config; + + rc = fstatfs(lfd, &buf); + if (rc == 0) { + if (buf.f_bavail < 5) { + /* we won't consume the last 5 blocks */ + fs_space_left = 0; + do_disk_full_action(data); + } else { + unsigned long blocks; + unsigned long block_size = buf.f_bsize; + blocks = config->space_left * (MEGABYTE/block_size); + if (buf.f_bavail < blocks) { + if (fs_space_warning == 0) { + do_space_left_action(data, 0); + fs_space_warning = 1; + } + } else if (fs_space_warning && + config->space_left_action == FA_SYSLOG){ + // Auto reset only if failure action is syslog + fs_space_warning = 0; + } + blocks=config->admin_space_left * (MEGABYTE/block_size); + if (buf.f_bavail < blocks) { + if (fs_admin_space_warning == 0) { + do_space_left_action(data, 1); + fs_admin_space_warning = 1; + } + } else if (fs_admin_space_warning && + config->admin_space_left_action == FA_SYSLOG) { + // Auto reset only if failure action is syslog + fs_admin_space_warning = 0; + } + } + } + else audit_msg(LOG_DEBUG, "fstatfs returned:%d, %s", rc, + strerror(errno)); +} + +extern int sendmail(const char *subject, const char *content, + const char *mail_acct); +static void do_space_left_action(struct auditd_consumer_data *data, int admin) +{ + int action; + struct daemon_conf *config = data->config; + + if (admin) + action = config->admin_space_left_action; + else + action = config->space_left_action; + + switch (action) + { + case FA_IGNORE: + break; + case FA_SYSLOG: + audit_msg(LOG_ALERT, + "Audit daemon is low on disk space for logging"); + break; + case FA_ROTATE: + if (config->num_logs > 1) { + audit_msg(LOG_NOTICE, + "Audit daemon rotating log files"); + rotate_logs(data, 0); + } + break; + case FA_EMAIL: + if (admin == 0) { + sendmail("Audit Disk Space Alert", + "The audit daemon is low on disk space for logging! Please take action\nto ensure no loss of service.", + config->action_mail_acct); + audit_msg(LOG_ALERT, + "Audit daemon is low on disk space for logging"); + } else { + sendmail("Audit Admin Space Alert", + "The audit daemon is very low on disk space for logging! Immediate action\nis required to ensure no loss of service.", + config->action_mail_acct); + audit_msg(LOG_ALERT, + "Audit daemon is very low on disk space for logging"); + } + break; + case FA_EXEC: + if (admin) + safe_exec(config->admin_space_left_exe); + else + safe_exec(config->space_left_exe); + break; + case FA_SUSPEND: + audit_msg(LOG_ALERT, + "Audit daemon is suspending logging due to low disk space."); + logging_suspended = 1; + break; + case FA_SINGLE: + audit_msg(LOG_ALERT, + "The audit daemon is now changing the system to single user mode"); + change_runlevel(SINGLE); + break; + case FA_HALT: + audit_msg(LOG_ALERT, + "The audit daemon is now halting the system"); + change_runlevel(HALT); + break; + default: + audit_msg(LOG_ALERT, + "Audit daemon is low on disk space for logging and unknown action requested"); + break; + } +} + +static void do_disk_full_action(struct auditd_consumer_data *data) +{ + struct daemon_conf *config = data->config; + + audit_msg(LOG_ALERT, + "Audit daemon has no space left on logging partition"); + switch (config->disk_full_action) + { + case FA_IGNORE: + case FA_SYSLOG: /* Message is syslogged above */ + break; + case FA_ROTATE: + if (config->num_logs > 1) { + audit_msg(LOG_NOTICE, + "Audit daemon rotating log files"); + rotate_logs(data, 0); + } + break; + case FA_EXEC: + safe_exec(config->disk_full_exe); + break; + case FA_SUSPEND: + audit_msg(LOG_ALERT, + "Audit daemon is suspending logging due to no space left on logging partition."); + logging_suspended = 1; + break; + case FA_SINGLE: + audit_msg(LOG_ALERT, + "The audit daemon is now changing the system to single user mode due to no space left on logging partition"); + change_runlevel(SINGLE); + break; + case FA_HALT: + audit_msg(LOG_ALERT, + "The audit daemon is now halting the system due to no space left on logging partition"); + change_runlevel(HALT); + break; + default: + audit_msg(LOG_ALERT, "Unknown disk full action requested"); + break; + } +} + +static void do_disk_error_action(const char * func, struct daemon_conf *config, + int err) +{ + char text[128]; + + switch (config->disk_error_action) + { + case FA_IGNORE: + break; + case FA_SYSLOG: + if (disk_err_warning < 5) { + snprintf(text, sizeof(text), + "%s: Audit daemon detected an error writing an event to disk (%s)", + func, strerror(err)); + audit_msg(LOG_ALERT, "%s", text); + disk_err_warning++; + } + break; + case FA_EXEC: + safe_exec(config->disk_error_exe); + break; + case FA_SUSPEND: + audit_msg(LOG_ALERT, + "Audit daemon is suspending logging due to previously mentioned write error"); + logging_suspended = 1; + break; + case FA_SINGLE: + audit_msg(LOG_ALERT, + "The audit daemon is now changing the system to single user mode due to previously mentioned write error"); + change_runlevel(SINGLE); + break; + case FA_HALT: + audit_msg(LOG_ALERT, + "The audit daemon is now halting the system due to previously mentioned write error."); + change_runlevel(HALT); + break; + default: + audit_msg(LOG_ALERT, + "Unknown disk error action requested"); + break; + } +} + +static void rotate_logs_now(struct auditd_consumer_data *data) +{ + struct daemon_conf *config = data->config; + + if (config->max_log_size_action == SZ_KEEP_LOGS) + shift_logs(data); + else + rotate_logs(data, 0); +} + +/* Check for and remove excess logs so that we don't run out of room */ +static void check_excess_logs(struct auditd_consumer_data *data) +{ + int rc; + unsigned int i, len; + char *name; + + // Only do this if rotate is the log size action + // and we actually have a limit + if (data->config->max_log_size_action != SZ_ROTATE || + data->config->num_logs < 2) + return; + + len = strlen(data->config->log_file) + 16; + name = (char *)malloc(len); + if (name == NULL) { /* Not fatal - just messy */ + audit_msg(LOG_ERR, "No memory checking excess logs"); + return; + } + + // We want 1 beyond the normal logs + i=data->config->num_logs; + rc=0; + while (rc == 0) { + snprintf(name, len, "%s.%d", data->config->log_file, i++); + rc=unlink(name); + if (rc == 0) + audit_msg(LOG_NOTICE, + "Log %s removed as it exceeds num_logs parameter", + name); + } + free(name); +} + +static void rotate_logs(struct auditd_consumer_data *data, + unsigned int num_logs) +{ + int rc; + unsigned int len, i; + char *oldname, *newname; + + if (data->config->max_log_size_action == SZ_ROTATE && + data->config->num_logs < 2) + return; + + /* Close audit file. fchmod and fchown errors are not fatal because we + * already adjusted log file permissions and ownership when opening the + * log file. */ + if (fchmod(data->log_fd, data->config->log_group ? S_IRUSR|S_IRGRP : + S_IRUSR) < 0) { + audit_msg(LOG_NOTICE, "Couldn't change permissions while " + "rotating log file (%s)", strerror(errno)); + } + if (fchown(data->log_fd, 0, data->config->log_group) < 0) { + audit_msg(LOG_NOTICE, "Couldn't change ownership while " + "rotating log file (%s)", strerror(errno)); + } + fclose(data->log_file); + + /* Rotate */ + len = strlen(data->config->log_file) + 16; + oldname = (char *)malloc(len); + if (oldname == NULL) { /* Not fatal - just messy */ + audit_msg(LOG_ERR, "No memory rotating logs"); + logging_suspended = 1; + return; + } + newname = (char *)malloc(len); + if (newname == NULL) { /* Not fatal - just messy */ + audit_msg(LOG_ERR, "No memory rotating logs"); + free(oldname); + logging_suspended = 1; + return; + } + + /* If we are rotating, get number from config */ + if (num_logs == 0) + num_logs = data->config->num_logs; + + /* Handle this case first since it will not enter the for loop */ + if (num_logs == 2) + snprintf(oldname, len, "%s.1", data->config->log_file); + + for (i=num_logs - 1; i>1; i--) { + snprintf(oldname, len, "%s.%d", data->config->log_file, i-1); + snprintf(newname, len, "%s.%d", data->config->log_file, i); + /* if the old file exists */ + rc = rename(oldname, newname); + if (rc == -1 && errno != ENOENT) { + // Likely errors: ENOSPC, ENOMEM, EBUSY + int saved_errno = errno; + audit_msg(LOG_ERR, + "Error rotating logs from %s to %s (%s)", + oldname, newname, strerror(errno)); + if (saved_errno == ENOSPC && fs_space_left == 1) { + fs_space_left = 0; + do_disk_full_action(data); + } else + do_disk_error_action("rotate", data->config, + saved_errno); + } + } + free(newname); + + /* At this point, oldname should point to lowest number - use it */ + newname = oldname; + rc = rename(data->config->log_file, newname); + if (rc == -1 && errno != ENOENT) { + // Likely errors: ENOSPC, ENOMEM, EBUSY + int saved_errno = errno; + audit_msg(LOG_ERR, "Error rotating logs from %s to %s (%s)", + data->config->log_file, newname, strerror(errno)); + if (saved_errno == ENOSPC && fs_space_left == 1) { + fs_space_left = 0; + do_disk_full_action(data); + } else + do_disk_error_action("rotate2", data->config, + saved_errno); + + /* At this point, we've failed to rotate the original log. + * So, let's make the old log writable and try again next + * time */ + chmod(data->config->log_file, + data->config->log_group ? S_IWUSR|S_IRUSR|S_IRGRP : + S_IWUSR|S_IRUSR); + } + free(newname); + + /* open new audit file */ + if (open_audit_log(data)) { + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not reopen a log after rotating."); + logging_suspended = 1; + do_disk_error_action("reopen", data->config, saved_errno); + } +} + +static int last_log = 1; +static void shift_logs(struct auditd_consumer_data *data) +{ + // The way this has to work is to start scanning from .1 up until + // no file is found. Then do the rotate algorithm using that number + // instead of log_max. + unsigned int num_logs, len; + char *name; + + len = strlen(data->config->log_file) + 16; + name = (char *)malloc(len); + if (name == NULL) { /* Not fatal - just messy */ + audit_msg(LOG_ERR, "No memory shifting logs"); + return; + } + + // Find last log + num_logs = last_log; + while (num_logs) { + snprintf(name, len, "%s.%d", data->config->log_file, + num_logs); + if (access(name, R_OK) != 0) + break; + num_logs++; + } + + /* Our last known file disappeared, start over... */ + if (num_logs <= last_log && last_log > 1) { + audit_msg(LOG_WARNING, "Last known log disappeared (%s)", name); + num_logs = last_log = 1; + while (num_logs) { + snprintf(name, len, "%s.%d", data->config->log_file, + num_logs); + if (access(name, R_OK) != 0) + break; + num_logs++; + } + audit_msg(LOG_INFO, "Next log to use will be %s", name); + } + last_log = num_logs; + rotate_logs(data, num_logs+1); + free(name); +} + +/* + * This function handles opening a descriptor for the audit log + * file and ensuring the correct options are applied to the descriptor. + * It returns 0 on success and 1 on failure. + */ +static int open_audit_log(struct auditd_consumer_data *data) +{ + int flags, lfd; + + // Likely errors for open: Almost anything + // Likely errors on rotate: ENFILE, ENOMEM, ENOSPC +retry: + lfd = open(data->config->log_file, O_WRONLY|O_APPEND|O_NOFOLLOW); + if (lfd < 0) { + if (errno == ENOENT) { + lfd = create_log_file(data->config->log_file); + if (lfd < 0) { + audit_msg(LOG_ERR, + "Couldn't create log file %s (%s)", + data->config->log_file, + strerror(errno)); + return 1; + } + close(lfd); + lfd = open(data->config->log_file, + O_WRONLY|O_APPEND|O_NOFOLLOW); + log_size = 0; + } else if (errno == ENFILE) { + // All system descriptors used, try again... + goto retry; + } + if (lfd < 0) { + audit_msg(LOG_ERR, "Couldn't open log file %s (%s)", + data->config->log_file, strerror(errno)); + return 1; + } + } else { + // Get initial size + struct stat st; + + int rc = fstat(lfd, &st); + if (rc == 0) + log_size = st.st_size; + else { + close(lfd); + return 1; + } + } + + if (fcntl(lfd, F_SETFD, FD_CLOEXEC) == -1) { + close(lfd); + audit_msg(LOG_ERR, "Error setting log file CLOEXEC flag (%s)", + strerror(errno)); + return 1; + } + if (data->config->flush == FT_DATA) { + flags = fcntl(lfd, F_GETFL); + if (flags < 0) { + audit_msg(LOG_ERR, "Couldn't get log file flags (%s)", + strerror(errno)); + close(lfd); + return 1; + } + if (fcntl(lfd, F_SETFL, flags|O_DSYNC) < 0) { + audit_msg(LOG_ERR, + "Couldn't set data sync mode on log file (%s)", + strerror(errno)); + close(lfd); + return 1; + } + } + else if (data->config->flush == FT_SYNC){ + flags = fcntl(lfd, F_GETFL); + if (flags < 0) { + audit_msg(LOG_ERR, "Couldn't get log file flags (%s)", + strerror(errno)); + close(lfd); + return 1; + } + if (fcntl(lfd, F_SETFL, flags|O_SYNC) < 0) { + audit_msg(LOG_ERR, + "Couldn't set sync mode on log file (%s)", + strerror(errno)); + close(lfd); + return 1; + } + } + if (fchmod(lfd, data->config->log_group ? S_IRUSR|S_IWUSR|S_IRGRP : + S_IRUSR|S_IWUSR) < 0) { + audit_msg(LOG_ERR, + "Couldn't change permissions of log file (%s)", + strerror(errno)); + close(lfd); + return 1; + } + if (fchown(lfd, 0, data->config->log_group) < 0) { + audit_msg(LOG_ERR, "Couldn't change ownership of log file (%s)", + strerror(errno)); + close(lfd); + return 1; + } + + data->log_fd = lfd; + data->log_file = fdopen(lfd, "a"); + if (data->log_file == NULL) { + audit_msg(LOG_ERR, "Error setting up log descriptor (%s)", + strerror(errno)); + close(lfd); + return 1; + } + + /* Set it to line buffering */ + setlinebuf(consumer_data.log_file); + return 0; +} + +static void change_runlevel(const char *level) +{ + char *argv[3]; + int pid; + struct sigaction sa; + static const char *init_pgm = "/sbin/init"; + + pid = fork(); + if (pid < 0) { + audit_msg(LOG_ALERT, + "Audit daemon failed to fork switching runlevels"); + return; + } + if (pid) /* Parent */ + return; + /* Child */ + sigfillset (&sa.sa_mask); + sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); + + argv[0] = (char *)init_pgm; + argv[1] = (char *)level; + argv[2] = NULL; + execve(init_pgm, argv, NULL); + audit_msg(LOG_ALERT, "Audit daemon failed to exec %s", init_pgm); + exit(1); +} + +static void safe_exec(const char *exe) +{ + char *argv[2]; + int pid; + struct sigaction sa; + + if (exe == NULL) { + audit_msg(LOG_ALERT, + "Safe_exec passed NULL for program to execute"); + return; + } + + pid = fork(); + if (pid < 0) { + audit_msg(LOG_ALERT, + "Audit daemon failed to fork doing safe_exec"); + return; + } + if (pid) /* Parent */ + return; + /* Child */ + sigfillset (&sa.sa_mask); + sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); + + argv[0] = (char *)exe; + argv[1] = NULL; + execve(exe, argv, NULL); + audit_msg(LOG_ALERT, "Audit daemon failed to exec %s", exe); + exit(1); +} + +/* +* This function will take an audit structure and return a +* text buffer that's unformatted for writing to disk. If there +* is an error the return value is NULL. +*/ +static char *format_raw(const struct audit_reply *rep, + struct daemon_conf *config) +{ + char *ptr; + + if (rep==NULL) { + if (config->node_name_format != N_NONE) + snprintf(format_buf, MAX_AUDIT_MESSAGE_LENGTH + + _POSIX_HOST_NAME_MAX - 32, + "node=%s type=DAEMON msg=NULL reply", + config->node_name); + else + snprintf(format_buf, MAX_AUDIT_MESSAGE_LENGTH, + "type=DAEMON msg=NULL reply"); + } else { + int len, nlen; + const char *type, *message; + char unknown[32]; + type = audit_msg_type_to_name(rep->type); + if (type == NULL) { + snprintf(unknown, sizeof(unknown), + "UNKNOWN[%d]", rep->type); + type = unknown; + } + if (rep->message == NULL) { + message = "msg lost"; + len = 8; + } else { + message = rep->message; + len = rep->len; + } + + // Note: This can truncate messages if + // MAX_AUDIT_MESSAGE_LENGTH is too small + if (config->node_name_format != N_NONE) + nlen = snprintf(format_buf, MAX_AUDIT_MESSAGE_LENGTH + + _POSIX_HOST_NAME_MAX - 32, + "node=%s type=%s msg=%.*s\n", + config->node_name, type, len, message); + else + nlen = snprintf(format_buf, + MAX_AUDIT_MESSAGE_LENGTH - 32, + "type=%s msg=%.*s", type, len, message); + + /* Replace \n with space so it looks nicer. */ + ptr = format_buf; + while ((ptr = strchr(ptr, 0x0A)) != NULL) + *ptr = ' '; + + /* Trim trailing space off since it wastes space */ + if (format_buf[nlen-1] == ' ') + format_buf[nlen-1] = 0; + } + return format_buf; +} + +static void reconfigure(struct auditd_consumer_data *data) +{ + struct daemon_conf *nconf = data->head->reply.conf; + struct daemon_conf *oconf = data->config; + uid_t uid = nconf->sender_uid; + pid_t pid = nconf->sender_pid; + const char *ctx = nconf->sender_ctx; + struct timeval tv; + char txt[MAX_AUDIT_MESSAGE_LENGTH]; + char date[40]; + unsigned int seq_num; + int need_size_check = 0, need_reopen = 0, need_space_check = 0; + + snprintf(txt, sizeof(txt), + "config change requested by pid=%d auid=%u subj=%s", + pid, uid, ctx); + audit_msg(LOG_NOTICE, "%s", txt); + + /* Do the reconfiguring. These are done in a specific + * order from least invasive to most invasive. We will + * start with general system parameters. */ + + // start with disk error action. + oconf->disk_error_action = nconf->disk_error_action; + free((char *)oconf->disk_error_exe); + oconf->disk_error_exe = nconf->disk_error_exe; + disk_err_warning = 0; + + // numlogs is next + oconf->num_logs = nconf->num_logs; + + // flush freq + oconf->freq = nconf->freq; + + // priority boost + if (oconf->priority_boost != nconf->priority_boost) { + int rc; + + oconf->priority_boost = nconf->priority_boost; + errno = 0; + rc = nice(-oconf->priority_boost); + if (rc == -1 && errno) + audit_msg(LOG_NOTICE, "Cannot change priority in " + "reconfigure (%s)", strerror(errno)); + } + + // log format + oconf->log_format = nconf->log_format; + + // action_mail_acct + if (strcmp(oconf->action_mail_acct, nconf->action_mail_acct)) { + free((void *)oconf->action_mail_acct); + oconf->action_mail_acct = nconf->action_mail_acct; + } else + free((void *)nconf->action_mail_acct); + + // node_name + if (oconf->node_name_format != nconf->node_name_format || + (oconf->node_name && nconf->node_name && + strcmp(oconf->node_name, nconf->node_name) != 0)) { + oconf->node_name_format = nconf->node_name_format; + free((char *)oconf->node_name); + oconf->node_name = nconf->node_name; + } + + /* Now look at audit dispatcher changes */ + oconf->qos = nconf->qos; // dispatcher qos + + // do the dispatcher app change + if (oconf->dispatcher || nconf->dispatcher) { + // none before, start new one + if (oconf->dispatcher == NULL) { + oconf->dispatcher = strdup(nconf->dispatcher); + if (oconf->dispatcher == NULL) { + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not allocate dispatcher memory" + " in reconfigure"); + // Likely errors: ENOMEM + do_disk_error_action("reconfig", data->config, + saved_errno); + } + if(init_dispatcher(oconf)) {// dispatcher & qos is used + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not start dispatcher %s" + " in reconfigure", oconf->dispatcher); + // Likely errors: Socketpairs or exec perms + do_disk_error_action("reconfig", data->config, + saved_errno); + } + } + // have one, but none after this + else if (nconf->dispatcher == NULL) { + shutdown_dispatcher(); + free((char *)oconf->dispatcher); + oconf->dispatcher = NULL; + } + // they are different apps + else if (strcmp(oconf->dispatcher, nconf->dispatcher)) { + shutdown_dispatcher(); + free((char *)oconf->dispatcher); + oconf->dispatcher = strdup(nconf->dispatcher); + if (oconf->dispatcher == NULL) { + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not allocate dispatcher memory" + " in reconfigure"); + // Likely errors: ENOMEM + do_disk_error_action("reconfig", data->config, + saved_errno); + } + if(init_dispatcher(oconf)) {// dispatcher & qos is used + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not start dispatcher %s" + " in reconfigure", oconf->dispatcher); + // Likely errors: Socketpairs or exec perms + do_disk_error_action("reconfig", data->config, + saved_errno); + } + } + // they are the same app - just signal it + else { + reconfigure_dispatcher(oconf); + free((char *)nconf->dispatcher); + nconf->dispatcher = NULL; + } + } + + // network listener + auditd_tcp_listen_reconfigure(nconf, oconf); + + /* At this point we will work on the items that are related to + * a single log file. */ + + // max logfile action + if (oconf->max_log_size_action != nconf->max_log_size_action) { + oconf->max_log_size_action = nconf->max_log_size_action; + need_size_check = 1; + } + + // max log size + if (oconf->max_log_size != nconf->max_log_size) { + oconf->max_log_size = nconf->max_log_size; + need_size_check = 1; + } + + if (need_size_check) { + logging_suspended = 0; + check_log_file_size(data); + } + + // flush technique + if (oconf->flush != nconf->flush) { + oconf->flush = nconf->flush; + need_reopen = 1; + } + + // logfile + if (strcmp(oconf->log_file, nconf->log_file)) { + free((void *)oconf->log_file); + oconf->log_file = nconf->log_file; + need_reopen = 1; + need_space_check = 1; // might be on new partition + } else + free((void *)nconf->log_file); + + if (need_reopen) { + fclose(data->log_file); + if (open_audit_log(data)) { + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not reopen a log after reconfigure"); + logging_suspended = 1; + // Likely errors: ENOMEM, ENOSPC + do_disk_error_action("reconfig", data->config, + saved_errno); + } else { + logging_suspended = 0; + check_log_file_size(data); + } + } + + /* At this point we will start working on items that are + * related to the amount of space on the partition. */ + + // space left + if (oconf->space_left != nconf->space_left) { + oconf->space_left = nconf->space_left; + need_space_check = 1; + } + + // space left action + if (oconf->space_left_action != nconf->space_left_action) { + oconf->space_left_action = nconf->space_left_action; + need_space_check = 1; + } + + // space left exe + if (oconf->space_left_exe || nconf->space_left_exe) { + if (nconf->space_left_exe == NULL) + ; /* do nothing if new one is blank */ + else if (oconf->space_left_exe == NULL && nconf->space_left_exe) + need_space_check = 1; + else if (strcmp(oconf->space_left_exe, nconf->space_left_exe)) + need_space_check = 1; + free((char *)oconf->space_left_exe); + oconf->space_left_exe = nconf->space_left_exe; + } + + // admin space left + if (oconf->admin_space_left != nconf->admin_space_left) { + oconf->admin_space_left = nconf->admin_space_left; + need_space_check = 1; + } + + // admin space action + if (oconf->admin_space_left_action != nconf->admin_space_left_action) { + oconf->admin_space_left_action = nconf->admin_space_left_action; + need_space_check = 1; + } + + // admin space left exe + if (oconf->admin_space_left_exe || nconf->admin_space_left_exe) { + if (nconf->admin_space_left_exe == NULL) + ; /* do nothing if new one is blank */ + else if (oconf->admin_space_left_exe == NULL && + nconf->admin_space_left_exe) + need_space_check = 1; + else if (strcmp(oconf->admin_space_left_exe, + nconf->admin_space_left_exe)) + need_space_check = 1; + free((char *)oconf->admin_space_left_exe); + oconf->admin_space_left_exe = nconf->admin_space_left_exe; + } + // disk full action + if (oconf->disk_full_action != nconf->disk_full_action) { + oconf->disk_full_action = nconf->disk_full_action; + need_space_check = 1; + } + + // disk full exe + if (oconf->disk_full_exe || nconf->disk_full_exe) { + if (nconf->disk_full_exe == NULL) + ; /* do nothing if new one is blank */ + else if (oconf->disk_full_exe == NULL && nconf->disk_full_exe) + need_space_check = 1; + else if (strcmp(oconf->disk_full_exe, nconf->disk_full_exe)) + need_space_check = 1; + free((char *)oconf->disk_full_exe); + oconf->disk_full_exe = nconf->disk_full_exe; + } + + if (need_space_check) { + /* note save suspended flag, then do space_left. If suspended + * is still 0, then copy saved suspended back. This avoids + * having to call check_log_file_size to restore it. */ + int saved_suspend = logging_suspended; + + fs_space_warning = 0; + fs_admin_space_warning = 0; + fs_space_left = 1; + logging_suspended = 0; + check_excess_logs(data); + check_space_left(data->log_fd, data); + if (logging_suspended == 0) + logging_suspended = saved_suspend; + } + + // Next document the results + srand(time(NULL)); + seq_num = rand()%10000; + if (gettimeofday(&tv, NULL) == 0) { + snprintf(date, sizeof(date), "audit(%lu.%03u:%u)", tv.tv_sec, + (unsigned)(tv.tv_usec/1000), seq_num); + } else { + snprintf(date, sizeof(date), + "audit(%lu.%03u:%u)", (unsigned long)time(NULL), + 0, seq_num); + } + + data->head->reply.len = snprintf(txt, sizeof(txt), + "%s config changed, auid=%u pid=%d subj=%s res=success", date, + uid, pid, ctx ); + audit_msg(LOG_NOTICE, "%s", txt); + data->head->reply.type = AUDIT_DAEMON_CONFIG; + data->head->reply.message = strdup(txt); + if (!data->head->reply.message) { + data->head->reply.len = 0; + audit_msg(LOG_ERR, "Cannot allocate config message"); + // FIXME: Should call some error handler + } + free((char *)ctx); +} + diff --git a/framework/src/audit/src/auditd-event.h b/framework/src/audit/src/auditd-event.h new file mode 100644 index 00000000..71678259 --- /dev/null +++ b/framework/src/audit/src/auditd-event.h @@ -0,0 +1,49 @@ +/* auditd-event.h -- + * Copyright 2004, 2005, 2008 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#ifndef AUDITD_EVENT_H +#define AUDITD_EVENT_H + +#include "libaudit.h" + +typedef void (*ack_func_type)(void *ack_data, const unsigned char *header, const char *msg); + +struct auditd_reply_list { + struct audit_reply reply; + struct auditd_reply_list *next; + ack_func_type ack_func; + void *ack_data; + unsigned long sequence_id; +}; + +#include "auditd-config.h" + +void shutdown_events(void); +int init_event(struct daemon_conf *config); +void resume_logging(void); +void enqueue_event(struct auditd_reply_list *rep); +void enqueue_formatted_event(char *msg, ack_func_type ack_func, void *ack_data, uint32_t sequence_id); +void *consumer_thread_main(void *arg); + +#endif + diff --git a/framework/src/audit/src/auditd-listen.c b/framework/src/audit/src/auditd-listen.c new file mode 100644 index 00000000..d1977c63 --- /dev/null +++ b/framework/src/audit/src/auditd-listen.c @@ -0,0 +1,1065 @@ +/* auditd-listen.c -- + * Copyright 2008,2009,2011 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * DJ Delorie <dj@redhat.com> + * + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <dirent.h> +#include <ctype.h> +#include <stdlib.h> +#include <netdb.h> +#include <fcntl.h> /* O_NOFOLLOW needs gnu defined */ +#include <libgen.h> +#include <arpa/inet.h> +#include <limits.h> /* INT_MAX */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif +#ifdef USE_GSSAPI +#include <gssapi/gssapi.h> +#include <gssapi/gssapi_generic.h> +#include <krb5.h> +#endif +#include "libaudit.h" +#include "auditd-event.h" +#include "auditd-config.h" +#include "private.h" + +#include "ev.h" + +extern volatile int stop; +extern int send_audit_event(int type, const char *str); +#define DEFAULT_BUF_SZ 192 + +typedef struct ev_tcp { + struct ev_io io; + struct sockaddr_in addr; + struct ev_tcp *next, *prev; + unsigned int bufptr; + int client_active; +#ifdef USE_GSSAPI + /* This holds the negotiated security context for this client. */ + gss_ctx_id_t gss_context; + char *remote_name; + int remote_name_len; +#endif + unsigned char buffer [MAX_AUDIT_MESSAGE_LENGTH + 17]; +} ev_tcp; + +static int listen_socket; +static struct ev_io tcp_listen_watcher; +static struct ev_periodic periodic_watcher; +static int min_port, max_port, max_per_addr; +static int use_libwrap = 1; +#ifdef USE_GSSAPI +/* This is used to hold our own private key. */ +static gss_cred_id_t server_creds; +static char *my_service_name, *my_gss_realm; +static int use_gss = 0; +static char msgbuf[MAX_AUDIT_MESSAGE_LENGTH + 1]; +#endif + +static struct ev_tcp *client_chain = NULL; + +static char *sockaddr_to_ipv4(struct sockaddr_in *addr) +{ + unsigned char *uaddr = (unsigned char *)&(addr->sin_addr); + static char buf[40]; + + snprintf(buf, sizeof(buf), "%u.%u.%u.%u", + uaddr[0], uaddr[1], uaddr[2], uaddr[3]); + return buf; +} + +static char *sockaddr_to_addr4(struct sockaddr_in *addr) +{ + unsigned char *uaddr = (unsigned char *)&(addr->sin_addr); + static char buf[40]; + + snprintf(buf, sizeof(buf), "%u.%u.%u.%u:%u", + uaddr[0], uaddr[1], uaddr[2], uaddr[3], + ntohs (addr->sin_port)); + return buf; +} + +static void set_close_on_exec (int fd) +{ + int flags = fcntl (fd, F_GETFD); + if (flags == -1) + flags = 0; + flags |= FD_CLOEXEC; + fcntl (fd, F_SETFD, flags); +} + +static void release_client(struct ev_tcp *client) +{ + char emsg[DEFAULT_BUF_SZ]; + + snprintf(emsg, sizeof(emsg), "addr=%s port=%d res=success", + sockaddr_to_ipv4(&client->addr), ntohs (client->addr.sin_port)); + send_audit_event(AUDIT_DAEMON_CLOSE, emsg); +#ifdef USE_GSSAPI + if (client->remote_name) + free (client->remote_name); +#endif + shutdown(client->io.fd, SHUT_RDWR); + close(client->io.fd); + if (client_chain == client) + client_chain = client->next; + if (client->next) + client->next->prev = client->prev; + if (client->prev) + client->prev->next = client->next; +} + +static void close_client(struct ev_tcp *client) +{ + release_client (client); + free (client); +} + +static int ar_write (int sock, const void *buf, int len) +{ + int rc = 0, w; + while (len > 0) { + do { + w = write(sock, buf, len); + } while (w < 0 && errno == EINTR); + if (w < 0) + return w; + if (w == 0) + break; + rc += w; + len -= w; + buf = (const void *)((const char *)buf + w); + } + return rc; +} + +#ifdef USE_GSSAPI +static int ar_read (int sock, void *buf, int len) +{ + int rc = 0, r; + while (len > 0) { + do { + r = read(sock, buf, len); + } while (r < 0 && errno == EINTR); + if (r < 0) + return r; + if (r == 0) + break; + rc += r; + len -= r; + buf = (void *)((char *)buf + r); + } + return rc; +} + + +/* Communications under GSS is done by token exchanges. Each "token" + may contain a message, perhaps signed, perhaps encrypted. The + messages within are what we're interested in, but the network sees + the tokens. The protocol we use for transferring tokens is to send + the length first, four bytes MSB first, then the token data. We + return nonzero on error. */ +static int recv_token (int s, gss_buffer_t tok) +{ + int ret; + unsigned char lenbuf[4]; + unsigned int len; + + ret = ar_read(s, (char *) lenbuf, 4); + if (ret < 0) { + audit_msg(LOG_ERR, "GSS-API error reading token length"); + return -1; + } else if (!ret) { + return 0; + } else if (ret != 4) { + audit_msg(LOG_ERR, "GSS-API error reading token length"); + return -1; + } + + len = ((lenbuf[0] << 24) + | (lenbuf[1] << 16) + | (lenbuf[2] << 8) + | lenbuf[3]); + if (len > MAX_AUDIT_MESSAGE_LENGTH) { + audit_msg(LOG_ERR, + "GSS-API error: event length excedes MAX_AUDIT_LENGTH"); + return -1; + } + tok->length = len; + + tok->value = (char *) malloc(tok->length ? tok->length : 1); + if (tok->length && tok->value == NULL) { + audit_msg(LOG_ERR, "Out of memory allocating token data"); + return -1; + } + + ret = ar_read(s, (char *) tok->value, tok->length); + if (ret < 0) { + audit_msg(LOG_ERR, "GSS-API error reading token data"); + free(tok->value); + return -1; + } else if (ret != (int) tok->length) { + audit_msg(LOG_ERR, "GSS-API error reading token data"); + free(tok->value); + return -1; + } + + return 1; +} + +/* Same here. */ +int send_token(int s, gss_buffer_t tok) +{ + int ret; + unsigned char lenbuf[4]; + unsigned int len; + + if (tok->length > 0xffffffffUL) + return -1; + len = tok->length; + lenbuf[0] = (len >> 24) & 0xff; + lenbuf[1] = (len >> 16) & 0xff; + lenbuf[2] = (len >> 8) & 0xff; + lenbuf[3] = len & 0xff; + + ret = ar_write(s, (char *) lenbuf, 4); + if (ret < 0) { + audit_msg(LOG_ERR, "GSS-API error sending token length"); + return -1; + } else if (ret != 4) { + audit_msg(LOG_ERR, "GSS-API error sending token length"); + return -1; + } + + ret = ar_write(s, tok->value, tok->length); + if (ret < 0) { + audit_msg(LOG_ERR, "GSS-API error sending token data"); + return -1; + } else if (ret != (int) tok->length) { + audit_msg(LOG_ERR, "GSS-API error sending token data"); + return -1; + } + + return 0; +} + + +static void gss_failure_2 (const char *msg, int status, int type) +{ + OM_uint32 message_context = 0; + OM_uint32 min_status = 0; + gss_buffer_desc status_string; + + do { + gss_display_status (&min_status, + status, + type, + GSS_C_NO_OID, + &message_context, + &status_string); + + audit_msg (LOG_ERR, "GSS error: %s: %s", + msg, (char *)status_string.value); + + gss_release_buffer(&min_status, &status_string); + } while (message_context != 0); +} + +static void gss_failure (const char *msg, int major_status, int minor_status) +{ + gss_failure_2 (msg, major_status, GSS_C_GSS_CODE); + if (minor_status) + gss_failure_2 (msg, minor_status, GSS_C_MECH_CODE); +} + +#define KCHECK(x,f) if (x) { \ + const char *kstr = krb5_get_error_message(kcontext, x); \ + audit_msg(LOG_ERR, "krb5 error: %s in %s\n", kstr, f); \ + krb5_free_error_message(kcontext, kstr); \ + return -1; } + +/* These are our private credentials, which come from a key file on + our server. They are aquired once, at program start. */ +static int server_acquire_creds(const char *service_name, + gss_cred_id_t *server_creds) +{ + gss_buffer_desc name_buf; + gss_name_t server_name; + OM_uint32 major_status, minor_status; + + krb5_context kcontext = NULL; + int krberr; + + my_service_name = strdup (service_name); + name_buf.value = (char *)service_name; + name_buf.length = strlen(name_buf.value) + 1; + major_status = gss_import_name(&minor_status, &name_buf, + (gss_OID) gss_nt_service_name, + &server_name); + if (major_status != GSS_S_COMPLETE) { + gss_failure("importing name", major_status, minor_status); + return -1; + } + + major_status = gss_acquire_cred(&minor_status, + server_name, GSS_C_INDEFINITE, + GSS_C_NULL_OID_SET, GSS_C_ACCEPT, + server_creds, NULL, NULL); + if (major_status != GSS_S_COMPLETE) { + gss_failure("acquiring credentials", + major_status, minor_status); + return -1; + } + + (void) gss_release_name(&minor_status, &server_name); + + krberr = krb5_init_context (&kcontext); + KCHECK (krberr, "krb5_init_context"); + krberr = krb5_get_default_realm (kcontext, &my_gss_realm); + KCHECK (krberr, "krb5_get_default_realm"); + + audit_msg(LOG_DEBUG, "GSS creds for %s acquired", service_name); + + return 0; +} + +/* This is where we negotiate a security context with the client. In + the case of Kerberos, this is where the key exchange happens. + FIXME: While everything else is strictly nonblocking, this + negotiation blocks. */ +static int negotiate_credentials (ev_tcp *io) +{ + gss_buffer_desc send_tok, recv_tok; + gss_name_t client; + OM_uint32 maj_stat, min_stat, acc_sec_min_stat; + gss_ctx_id_t *context; + OM_uint32 sess_flags; + char *slashptr, *atptr; + + context = & io->gss_context; + *context = GSS_C_NO_CONTEXT; + + maj_stat = GSS_S_CONTINUE_NEEDED; + do { + /* STEP 1 - get a token from the client. */ + + if (recv_token(io->io.fd, &recv_tok) <= 0) { + audit_msg(LOG_ERR, + "TCP session from %s will be closed, error ignored", + sockaddr_to_addr4(&io->addr)); + return -1; + } + if (recv_tok.length == 0) + continue; + + /* STEP 2 - let GSS process that token. */ + + maj_stat = gss_accept_sec_context(&acc_sec_min_stat, + context, server_creds, + &recv_tok, + GSS_C_NO_CHANNEL_BINDINGS, &client, + NULL, &send_tok, &sess_flags, + NULL, NULL); + if (recv_tok.value) { + free(recv_tok.value); + recv_tok.value = NULL; + } + if (maj_stat != GSS_S_COMPLETE + && maj_stat != GSS_S_CONTINUE_NEEDED) { + gss_release_buffer(&min_stat, &send_tok); + if (*context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_stat, context, + GSS_C_NO_BUFFER); + gss_failure("accepting context", maj_stat, + acc_sec_min_stat); + return -1; + } + + /* STEP 3 - send any tokens to the client that GSS may + ask us to send. */ + + if (send_tok.length != 0) { + if (send_token(io->io.fd, &send_tok) < 0) { + gss_release_buffer(&min_stat, &send_tok); + audit_msg(LOG_ERR, + "TCP session from %s will be closed, error ignored", + sockaddr_to_addr4(&io->addr)); + if (*context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_stat, + context, GSS_C_NO_BUFFER); + return -1; + } + gss_release_buffer(&min_stat, &send_tok); + } + } while (maj_stat == GSS_S_CONTINUE_NEEDED); + + maj_stat = gss_display_name(&min_stat, client, &recv_tok, NULL); + gss_release_name(&min_stat, &client); + + if (maj_stat != GSS_S_COMPLETE) { + gss_failure("displaying name", maj_stat, min_stat); + return -1; + } + + audit_msg(LOG_INFO, "GSS-API Accepted connection from: %s", + (char *)recv_tok.value); + io->remote_name = strdup (recv_tok.value); + io->remote_name_len = strlen (recv_tok.value); + gss_release_buffer(&min_stat, &recv_tok); + + slashptr = strchr (io->remote_name, '/'); + atptr = strchr (io->remote_name, '@'); + + if (!slashptr || !atptr) { + audit_msg(LOG_ERR, "Invalid GSS name from remote client: %s", + io->remote_name); + return -1; + } + + *slashptr = 0; + if (strcmp (io->remote_name, my_service_name)) { + audit_msg(LOG_ERR, "Unauthorized GSS client name: %s (not %s)", + io->remote_name, my_service_name); + return -1; + } + *slashptr = '/'; + + if (strcmp (atptr+1, my_gss_realm)) { + audit_msg(LOG_ERR, "Unauthorized GSS client realm: %s (not %s)", + atptr+1, my_gss_realm); + return -1; + } + + return 0; +} +#endif /* USE_GSSAPI */ + +/* This is called from auditd-event after the message has been logged. + The header is already filled in. */ +static void client_ack (void *ack_data, const unsigned char *header, + const char *msg) +{ + ev_tcp *io = (ev_tcp *)ack_data; +#ifdef USE_GSSAPI + if (use_gss) { + OM_uint32 major_status, minor_status; + gss_buffer_desc utok, etok; + int rc, mlen; + + mlen = strlen (msg); + utok.length = AUDIT_RMW_HEADER_SIZE + mlen; + utok.value = malloc (utok.length + 1); + + memcpy (utok.value, header, AUDIT_RMW_HEADER_SIZE); + memcpy (utok.value+AUDIT_RMW_HEADER_SIZE, msg, mlen); + + /* Wrapping the message creates a token for the + client. Then we just have to worry about sending + the token. */ + + major_status = gss_wrap (&minor_status, + io->gss_context, + 1, + GSS_C_QOP_DEFAULT, + &utok, + NULL, + &etok); + if (major_status != GSS_S_COMPLETE) { + gss_failure("encrypting message", major_status, + minor_status); + free (utok.value); + return; + } + // FIXME: What were we going to do with rc? + rc = send_token (io->io.fd, &etok); + free (utok.value); + (void) gss_release_buffer(&minor_status, &etok); + + return; + } +#endif + // Send the header and a text error message if it exists + ar_write (io->io.fd, header, AUDIT_RMW_HEADER_SIZE); + if (msg[0]) + ar_write (io->io.fd, msg, strlen(msg)); +} + +static void client_message (struct ev_tcp *io, unsigned int length, + unsigned char *header) +{ + unsigned char ch; + uint32_t type, mlen, seq; + int hver, mver; + + if (AUDIT_RMW_IS_MAGIC (header, length)) { + AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, mlen, seq) + + ch = header[length]; + header[length] = 0; + if (length > 1 && header[length-1] == '\n') + header[length-1] = 0; + if (type == AUDIT_RMW_TYPE_HEARTBEAT) { + unsigned char ack[AUDIT_RMW_HEADER_SIZE]; + AUDIT_RMW_PACK_HEADER (ack, 0, AUDIT_RMW_TYPE_ACK, + 0, seq); + client_ack (io, ack, ""); + } else + enqueue_formatted_event(header+AUDIT_RMW_HEADER_SIZE, + client_ack, io, seq); + header[length] = ch; + } else { + header[length] = 0; + if (length > 1 && header[length-1] == '\n') + header[length-1] = 0; + enqueue_formatted_event (header, NULL, NULL, 0); + } +} + +static void auditd_tcp_client_handler( struct ev_loop *loop, + struct ev_io *_io, int revents ) +{ + struct ev_tcp *io = (struct ev_tcp *) _io; + int i, r; + int total_this_call = 0; + + io->client_active = 1; + + /* The socket is non-blocking, but we have a limited buffer + size. In the event that we get a packet that's bigger than + our buffer, we need to read it in multiple parts. Thus, we + keep reading/parsing/processing until we run out of ready + data. */ +read_more: + r = read (io->io.fd, + io->buffer + io->bufptr, + MAX_AUDIT_MESSAGE_LENGTH - io->bufptr); + + if (r < 0 && errno == EAGAIN) + r = 0; + + /* We need to keep track of the difference between "no data + * because it's closed" and "no data because we've read it + * all". */ + if (r == 0 && total_this_call > 0) { + return; + } + + /* If the connection is gracefully closed, the first read we + try will return zero. If the connection times out or + otherwise fails, the read will return -1. */ + if (r <= 0) { + if (r < 0) + audit_msg (LOG_WARNING, + "client %s socket closed unexpectedly", + sockaddr_to_addr4(&io->addr)); + + /* There may have been a final message without a LF. */ + if (io->bufptr) { + client_message (io, io->bufptr, io->buffer); + + } + + ev_io_stop (loop, _io); + close_client (io); + return; + } + + total_this_call += r; + +more_messages: +#ifdef USE_GSSAPI + /* If we're using GSS at all, everything will be encrypted, + one record per token. */ + if (use_gss) { + gss_buffer_desc utok, etok; + io->bufptr += r; + uint32_t len; + OM_uint32 major_status, minor_status; + + /* We need at least four bytes to test the length. If + we have more than four bytes, we can tell if we + have a whole token (or more). */ + + if (io->bufptr < 4) + return; + + len = ( ((uint32_t)(io->buffer[0] & 0xFF) << 24) + | ((uint32_t)(io->buffer[1] & 0xFF) << 16) + | ((uint32_t)(io->buffer[2] & 0xFF) << 8) + | (uint32_t)(io->buffer[3] & 0xFF)); + + /* Make sure we got something big enough and not too big */ + if (io->bufptr < 4 + len || len > MAX_AUDIT_MESSAGE_LENGTH) + return; + i = len + 4; + + etok.length = len; + etok.value = io->buffer + 4; + + /* Unwrapping the token gives us the original message, + which we know is already a single record. */ + major_status = gss_unwrap (&minor_status, io->gss_context, + &etok, &utok, NULL, NULL); + + if (major_status != GSS_S_COMPLETE) { + gss_failure("decrypting message", major_status, + minor_status); + } else { + /* client_message() wants to NUL terminate it, + so copy it to a bigger buffer. Plus, we + want to add our own tag. */ + memcpy (msgbuf, utok.value, utok.length); + while (utok.length > 0 && msgbuf[utok.length-1] == '\n') + utok.length --; + snprintf (msgbuf + utok.length, + MAX_AUDIT_MESSAGE_LENGTH - utok.length, + " krb5=%s", io->remote_name); + utok.length += 6 + io->remote_name_len; + client_message (io, utok.length, msgbuf); + gss_release_buffer(&minor_status, &utok); + } + } else +#endif + if (AUDIT_RMW_IS_MAGIC (io->buffer, (io->bufptr+r))) { + uint32_t type, len, seq; + int hver, mver; + unsigned char *header = (unsigned char *)io->buffer; + + io->bufptr += r; + + if (io->bufptr < AUDIT_RMW_HEADER_SIZE) + return; + + AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, len, seq); + + /* Make sure len is not too big */ + if (len > MAX_AUDIT_MESSAGE_LENGTH) + return; + + i = len; + i += AUDIT_RMW_HEADER_SIZE; + + /* See if we have enough bytes to extract the whole message. */ + if (io->bufptr < i) + return; + + /* We have an I-byte message in buffer. Send ACK */ + client_message (io, i, io->buffer); + + } else { + /* At this point, the buffer has IO->BUFPTR+R bytes in it. + The first IO->BUFPTR bytes do not have a LF in them (we've + already checked), we must check the R new bytes. */ + + for (i = io->bufptr; i < io->bufptr + r; i ++) + if (io->buffer [i] == '\n') + break; + + io->bufptr += r; + + /* Check for a partial message, with no LF yet. */ + if (i == io->bufptr) + return; + + i++; + + /* We have an I-byte message in buffer. Send ACK */ + client_message (io, i, io->buffer); + } + + /* Now copy any remaining bytes to the beginning of the + buffer. */ + memmove(io->buffer, io->buffer + i, io->bufptr - i); + io->bufptr -= i; + + /* See if this packet had more than one message in it. */ + if (io->bufptr > 0) { + r = io->bufptr; + io->bufptr = 0; + goto more_messages; + } + + /* Go back and see if there's more data to read. */ + goto read_more; +} + +#ifndef HAVE_LIBWRAP +#define auditd_tcpd_check(s) ({ 0; }) +#else +int allow_severity = LOG_INFO, deny_severity = LOG_NOTICE; +static int auditd_tcpd_check(int sock) +{ + struct request_info request; + + request_init(&request, RQ_DAEMON, "auditd", RQ_FILE, sock, 0); + fromhost(&request); + if (! hosts_access(&request)) + return 1; + return 0; +} +#endif + +/* + * This function counts the number of concurrent connections and returns + * a 1 if there are too many and a 0 otherwise. It assumes the incoming + * connection has not been added to the linked list yet. + */ +static int check_num_connections(struct sockaddr_in *aaddr) +{ + int num = 0; + struct ev_tcp *client = client_chain; + + while (client) { + if (memcmp(&aaddr->sin_addr, &client->addr.sin_addr, + sizeof(struct in_addr)) == 0) { + num++; + if (num >= max_per_addr) + return 1; + } + client = client->next; + } + return 0; +} + +static void auditd_tcp_listen_handler( struct ev_loop *loop, + struct ev_io *_io, int revents ) +{ + int one=1; + int afd; + socklen_t aaddrlen; + struct sockaddr_in aaddr; + struct ev_tcp *client; + char emsg[DEFAULT_BUF_SZ]; + + /* Accept the connection and see where it's coming from. */ + aaddrlen = sizeof(aaddr); + afd = accept (listen_socket, (struct sockaddr *)&aaddr, &aaddrlen); + if (afd == -1) { + audit_msg(LOG_ERR, "Unable to accept TCP connection"); + return; + } + + if (use_libwrap) { + if (auditd_tcpd_check(afd)) { + shutdown(afd, SHUT_RDWR); + close(afd); + audit_msg(LOG_ERR, "TCP connection from %s rejected", + sockaddr_to_addr4(&aaddr)); + snprintf(emsg, sizeof(emsg), + "op=wrap addr=%s port=%d res=no", + sockaddr_to_ipv4(&aaddr), + ntohs (aaddr.sin_port)); + send_audit_event(AUDIT_DAEMON_ACCEPT, emsg); + return; + } + } + + /* Verify it's coming from an authorized port. We assume the firewall + * will block attempts from unauthorized machines. */ + if (min_port > ntohs (aaddr.sin_port) || + ntohs (aaddr.sin_port) > max_port) { + audit_msg(LOG_ERR, "TCP connection from %s rejected", + sockaddr_to_addr4(&aaddr)); + snprintf(emsg, sizeof(emsg), + "op=port addr=%s port=%d res=no", + sockaddr_to_ipv4(&aaddr), + ntohs (aaddr.sin_port)); + send_audit_event(AUDIT_DAEMON_ACCEPT, emsg); + shutdown(afd, SHUT_RDWR); + close(afd); + return; + } + + /* Make sure we don't have too many connections */ + if (check_num_connections(&aaddr)) { + audit_msg(LOG_ERR, "Too many connections from %s - rejected", + sockaddr_to_addr4(&aaddr)); + snprintf(emsg, sizeof(emsg), + "op=dup addr=%s port=%d res=no", + sockaddr_to_ipv4(&aaddr), + ntohs (aaddr.sin_port)); + send_audit_event(AUDIT_DAEMON_ACCEPT, emsg); + shutdown(afd, SHUT_RDWR); + close(afd); + return; + } + + /* Connection is accepted...start setting it up */ + setsockopt(afd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof (int)); + setsockopt(afd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof (int)); + setsockopt(afd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof (int)); + set_close_on_exec (afd); + + /* Make the client data structure */ + client = (struct ev_tcp *) malloc (sizeof (struct ev_tcp)); + if (client == NULL) { + audit_msg(LOG_CRIT, "Unable to allocate TCP client data"); + snprintf(emsg, sizeof(emsg), + "op=alloc addr=%s port=%d res=no", + sockaddr_to_ipv4(&aaddr), + ntohs (aaddr.sin_port)); + send_audit_event(AUDIT_DAEMON_ACCEPT, emsg); + shutdown(afd, SHUT_RDWR); + close(afd); + return; + } + + memset (client, 0, sizeof (struct ev_tcp)); + client->client_active = 1; + + // Was watching for EV_ERROR, but libev 3.48 took it away + ev_io_init (&(client->io), auditd_tcp_client_handler, afd, EV_READ); + + memcpy (&client->addr, &aaddr, sizeof (struct sockaddr_in)); + +#ifdef USE_GSSAPI + if (use_gss && negotiate_credentials (client)) { + shutdown(afd, SHUT_RDWR); + close(afd); + free(client); + return; + } +#endif + + fcntl(afd, F_SETFL, O_NONBLOCK | O_NDELAY); + ev_io_start (loop, &(client->io)); + + /* Add the new connection to a linked list of active clients. */ + client->next = client_chain; + if (client->next) + client->next->prev = client; + client_chain = client; + + /* And finally log that we accepted the connection */ + snprintf(emsg, sizeof(emsg), + "addr=%s port=%d res=success", sockaddr_to_ipv4(&aaddr), + ntohs (aaddr.sin_port)); + send_audit_event(AUDIT_DAEMON_ACCEPT, emsg); +} + +static void auditd_set_ports(int minp, int maxp, int max_p_addr) +{ + min_port = minp; + max_port = maxp; + max_per_addr = max_p_addr; +} + +static void periodic_handler(struct ev_loop *loop, struct ev_periodic *per, + int revents ) +{ + struct daemon_conf *config = (struct daemon_conf *) per->data; + struct ev_tcp *ev, *next = NULL; + int active; + + if (!config->tcp_client_max_idle) + return; + + for (ev = client_chain; ev; ev = next) { + active = ev->client_active; + ev->client_active = 0; + if (active) + continue; + + audit_msg(LOG_NOTICE, + "client %s idle too long - closing connection\n", + sockaddr_to_addr4(&(ev->addr))); + ev_io_stop (loop, &ev->io); + release_client(ev); + next = ev->next; + free(ev); + } +} + +int auditd_tcp_listen_init ( struct ev_loop *loop, struct daemon_conf *config ) +{ + struct sockaddr_in address; + int one = 1; + + ev_periodic_init (&periodic_watcher, periodic_handler, + 0, config->tcp_client_max_idle, NULL); + periodic_watcher.data = config; + if (config->tcp_client_max_idle) + ev_periodic_start (loop, &periodic_watcher); + + /* If the port is not set, that means we aren't going to + listen for connections. */ + if (config->tcp_listen_port == 0) + return 0; + + listen_socket = socket (AF_INET, SOCK_STREAM, 0); + if (listen_socket < 0) { + audit_msg(LOG_ERR, "Cannot create tcp listener socket"); + return 1; + } + + set_close_on_exec (listen_socket); + setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, + (char *)&one, sizeof (int)); + + memset (&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons(config->tcp_listen_port); + address.sin_addr.s_addr = htonl(INADDR_ANY); + + /* This avoids problems if auditd needs to be restarted. */ + setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, + (char *)&one, sizeof (int)); + + if (bind(listen_socket, (struct sockaddr *)&address, sizeof(address))){ + audit_msg(LOG_ERR, + "Cannot bind tcp listener socket to port %ld", + config->tcp_listen_port); + close(listen_socket); + return 1; + } + + listen(listen_socket, config->tcp_listen_queue); + + audit_msg(LOG_DEBUG, "Listening on TCP port %ld", + config->tcp_listen_port); + + ev_io_init (&tcp_listen_watcher, auditd_tcp_listen_handler, + listen_socket, EV_READ); + ev_io_start (loop, &tcp_listen_watcher); + + use_libwrap = config->use_libwrap; + auditd_set_ports(config->tcp_client_min_port, + config->tcp_client_max_port, + config->tcp_max_per_addr); + +#ifdef USE_GSSAPI + if (config->enable_krb5) { + const char *princ = config->krb5_principal; + const char *key_file; + struct stat st; + + if (!princ) + princ = "auditd"; + use_gss = 1; + /* This may fail, but we don't care. */ + unsetenv ("KRB5_KTNAME"); + if (config->krb5_key_file) + key_file = config->krb5_key_file; + else + key_file = "/etc/audit/audit.key"; + setenv ("KRB5_KTNAME", key_file, 1); + + if (stat (key_file, &st) == 0) { + if ((st.st_mode & 07777) != 0400) { + audit_msg (LOG_ERR, + "%s is not mode 0400 (it's %#o) - compromised key?", + key_file, st.st_mode & 07777); + return -1; + } + if (st.st_uid != 0) { + audit_msg (LOG_ERR, + "%s is not owned by root (it's %d) - compromised key?", + key_file, st.st_uid); + return -1; + } + } + + server_acquire_creds(princ, &server_creds); + } +#endif + + return 0; +} + +void auditd_tcp_listen_uninit ( struct ev_loop *loop, + struct daemon_conf *config ) +{ +#ifdef USE_GSSAPI + OM_uint32 status; +#endif + + ev_io_stop ( loop, &tcp_listen_watcher ); + close ( listen_socket ); + +#ifdef USE_GSSAPI + if (use_gss) { + use_gss = 0; + gss_release_cred(&status, &server_creds); + } +#endif + + while (client_chain) { + unsigned char ack[AUDIT_RMW_HEADER_SIZE]; + + AUDIT_RMW_PACK_HEADER (ack, 0, AUDIT_RMW_TYPE_ENDING, 0, 0); + client_ack (client_chain, ack, ""); + ev_io_stop (loop, &client_chain->io); + close_client (client_chain); + } + + if (config->tcp_client_max_idle) + ev_periodic_stop (loop, &periodic_watcher); +} + +static void periodic_reconfigure(struct daemon_conf *config) +{ + struct ev_loop *loop = ev_default_loop (EVFLAG_AUTO); + if (config->tcp_client_max_idle) { + ev_periodic_set (&periodic_watcher, ev_now (loop), + config->tcp_client_max_idle, NULL); + ev_periodic_start (loop, &periodic_watcher); + } else { + ev_periodic_stop (loop, &periodic_watcher); + } +} + +void auditd_tcp_listen_reconfigure ( struct daemon_conf *nconf, + struct daemon_conf *oconf ) +{ + /* Look at network things that do not need restarting */ + if (oconf->tcp_client_min_port != nconf->tcp_client_min_port || + oconf->tcp_client_max_port != nconf->tcp_client_max_port || + oconf->tcp_max_per_addr != nconf->tcp_max_per_addr) { + oconf->tcp_client_min_port = nconf->tcp_client_min_port; + oconf->tcp_client_max_port = nconf->tcp_client_max_port; + oconf->tcp_max_per_addr = nconf->tcp_max_per_addr; + auditd_set_ports(oconf->tcp_client_min_port, + oconf->tcp_client_max_port, + oconf->tcp_max_per_addr); + } + if (oconf->tcp_client_max_idle != nconf->tcp_client_max_idle) { + oconf->tcp_client_max_idle = nconf->tcp_client_max_idle; + periodic_reconfigure(oconf); + } + if (oconf->tcp_listen_port != nconf->tcp_listen_port || + oconf->tcp_listen_queue != nconf->tcp_listen_queue) { + oconf->tcp_listen_port = nconf->tcp_listen_port; + oconf->tcp_listen_queue = nconf->tcp_listen_queue; + // FIXME: need to restart the network stuff + } +} diff --git a/framework/src/audit/src/auditd-listen.h b/framework/src/audit/src/auditd-listen.h new file mode 100644 index 00000000..69f9310a --- /dev/null +++ b/framework/src/audit/src/auditd-listen.h @@ -0,0 +1,55 @@ +/* auditd-config.h -- + * Copyright 2004-2007 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * DJ Delorie <dj@redhat.com> + * + */ + +#ifndef AUDITD_LISTEN_H +#define AUDITD_LISTEN_H + +#include "ev.h" + +#ifdef USE_LISTENER +int auditd_tcp_listen_init ( struct ev_loop *loop, struct daemon_conf *config ); +void auditd_tcp_listen_uninit ( struct ev_loop *loop, + struct daemon_conf *config ); +void auditd_tcp_listen_reconfigure ( struct daemon_conf *nconf, + struct daemon_conf *oconf ); +#else +static inline int auditd_tcp_listen_init ( struct ev_loop *loop, + struct daemon_conf *config ) +{ + return 0; +} + +static inline void auditd_tcp_listen_uninit ( struct ev_loop *loop, + struct daemon_conf *config ) +{ + return; +} + +static inline void auditd_tcp_listen_reconfigure ( struct daemon_conf *nconf, + struct daemon_conf *oconf ) +{ + return; +} +#endif /* USE_LISTENER */ + +#endif diff --git a/framework/src/audit/src/auditd-reconfig.c b/framework/src/audit/src/auditd-reconfig.c new file mode 100644 index 00000000..ac3bd030 --- /dev/null +++ b/framework/src/audit/src/auditd-reconfig.c @@ -0,0 +1,128 @@ +/* auditd-reconfig.c -- + * Copyright 2005 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#include "config.h" +#include <pthread.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include "libaudit.h" +#include "auditd-event.h" +#include "auditd-config.h" +#include "private.h" + +/* This is the configuration manager code */ +static pthread_t config_thread; +static pthread_mutex_t config_lock; +static void *config_thread_main(void *arg); + +void init_config_manager(void) +{ + pthread_mutex_init(&config_lock, NULL); + audit_msg(LOG_DEBUG, "config_manager init complete"); +} + +int start_config_manager(struct auditd_reply_list *rep) +{ + int retval, rc = 0; + + retval = pthread_mutex_trylock(&config_lock); + if (retval == 0) { + pthread_attr_t detached; + + pthread_attr_init(&detached); + pthread_attr_setdetachstate(&detached, + PTHREAD_CREATE_DETACHED); + + if (pthread_create(&config_thread, &detached, + config_thread_main, rep) < 0) { + audit_msg(LOG_ERR, + "Couldn't create config thread, no config changes"); + free(rep); + pthread_mutex_unlock(&config_lock); + rc = 1; + } + pthread_attr_destroy(&detached); + } else { + audit_msg(LOG_ERR, + "Config thread already running, no config changes"); + free(rep); + rc = 1; + } + return rc; +} + +void shutdown_config(void) +{ + pthread_cancel(config_thread); +} + +static void *config_thread_main(void *arg) +{ + sigset_t sigs; + struct auditd_reply_list *rep = (struct auditd_reply_list *)arg; + struct daemon_conf new_config; + extern int send_audit_event(int type, const char *str); + + /* This is a worker thread. Don't handle signals. */ + sigemptyset(&sigs); + sigaddset(&sigs, SIGALRM); + sigaddset(&sigs, SIGTERM); + sigaddset(&sigs, SIGHUP); + sigaddset(&sigs, SIGUSR1); + sigaddset(&sigs, SIGUSR2); + pthread_sigmask(SIG_SETMASK, &sigs, NULL); + + if (load_config(&new_config, TEST_AUDITD) == 0) { + /* We will re-use the current reply */ + new_config.sender_uid = rep->reply.signal_info->uid; + new_config.sender_pid = rep->reply.signal_info->pid; + if (rep->reply.len > 24) + new_config.sender_ctx = + strdup(rep->reply.signal_info->ctx); + else + new_config.sender_ctx = strdup("?"); + memcpy(rep->reply.msg.data, &new_config, sizeof(new_config)); + rep->reply.conf = (struct daemon_conf *)rep->reply.msg.data; + rep->reply.type = AUDIT_DAEMON_RECONFIG; + enqueue_event(rep); + } else { + // need to send a failed event message + char txt[MAX_AUDIT_MESSAGE_LENGTH]; + snprintf(txt, sizeof(txt), + "reconfig aborted, sending auid=%u pid=%d subj=%s res=failed", + rep->reply.signal_info->uid, + rep->reply.signal_info->pid, + (rep->reply.len > 24) ? + rep->reply.signal_info->ctx : "?"); + send_audit_event(AUDIT_DAEMON_CONFIG, txt); + free_config(&new_config); + free(rep); + } + + pthread_mutex_unlock(&config_lock); + return NULL; +} + diff --git a/framework/src/audit/src/auditd-sendmail.c b/framework/src/audit/src/auditd-sendmail.c new file mode 100644 index 00000000..ab0b901f --- /dev/null +++ b/framework/src/audit/src/auditd-sendmail.c @@ -0,0 +1,116 @@ +/* auditd-sendmail.c -- + * Copyright 2005 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#include "config.h" +#include <stdio.h> +#include <unistd.h> // for access() +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include "libaudit.h" +#include "private.h" +#include "auditd-config.h" + +extern const char *email_command; +static int safe_popen(pid_t *pid, const char *mail_acct); + +// returns 1 on error & 0 if OK +int sendmail(const char *subject, const char *content, const char *mail_acct) +{ + pid_t pid; + + if (access(email_command, 01) == 0) + { + FILE *mail; + int fd; + + fd = safe_popen(&pid, mail_acct); + if (fd < 0) + return 1; + mail = fdopen(fd, "w"); + if (mail == NULL) { + kill(pid, SIGKILL); + close(fd); + audit_msg(LOG_ERR, "Error - starting mail"); + return 1; + } + + fprintf(mail, "To: %s\n", mail_acct); + fprintf(mail, "From: root\n"); +// fprintf(mail, "X-Sender: %s\n", mail_acct); + fprintf(mail, "Subject: %s\n\n", subject); // End of Header + fprintf(mail, "%s\n", content); + fprintf(mail, ".\n\n"); // Close it up... + fclose(mail); + return 0; + } else + audit_msg(LOG_ERR, "Error - %s isn't executable", + email_command); + return 1; +} + +static int safe_popen(pid_t *pid, const char *mail_acct) +{ + char *argv[4]; + char acct[256]; + int pipe_fd[2]; + struct sigaction sa; + + if (pipe(pipe_fd)) { + audit_msg(LOG_ALERT, + "Audit daemon failed to create pipe while sending email alert"); + return -1; + } + + *pid = fork(); + if (*pid < 0) { + close(pipe_fd[0]); + close(pipe_fd[1]); + audit_msg(LOG_ALERT, + "Audit daemon failed to fork while sending email alert"); + return -1; + } + if (*pid) { /* Parent */ + close(pipe_fd[0]); // adjust pipe + return pipe_fd[1]; + } + /* Child */ + sigfillset (&sa.sa_mask); + sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); + + close(pipe_fd[1]); // adjust pipe + dup2(pipe_fd[0], 0); + + /* Make email acct param */ + snprintf(acct, sizeof(acct), "-f%s", mail_acct); + + /* Stuff arg list */ + argv[0] = (char *)email_command; + argv[1] = (char *)"-t"; + argv[2] = acct; + argv[3] = NULL; + execve(email_command, argv, NULL); + audit_msg(LOG_ALERT, "Audit daemon failed to exec %s", email_command); + exit(1); +} + diff --git a/framework/src/audit/src/auditd.c b/framework/src/audit/src/auditd.c new file mode 100644 index 00000000..5afebac2 --- /dev/null +++ b/framework/src/audit/src/auditd.c @@ -0,0 +1,917 @@ +/* auditd.c -- + * Copyright 2004-09,2011,2013 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * Rickard E. (Rik) Faith <faith@redhat.com> + */ + +#include "config.h" +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <pthread.h> +#include <sys/utsname.h> +#include <getopt.h> + +#include "libaudit.h" +#include "auditd-event.h" +#include "auditd-config.h" +#include "auditd-dispatch.h" +#include "auditd-listen.h" +#include "private.h" + +#include "ev.h" + +#define EV_STOP() ev_unloop (ev_default_loop (EVFLAG_AUTO), EVUNLOOP_ALL), stop = 1; + +#define DEFAULT_BUF_SZ 448 +#define DMSG_SIZE (DEFAULT_BUF_SZ + 48) +#define SUCCESS 0 +#define FAILURE 1 +#define SUBJ_LEN 4097 + +/* Global Data */ +volatile int stop = 0; + +/* Local data */ +static int fd = -1; +static struct daemon_conf config; +static const char *pidfile = "/var/run/auditd.pid"; +static int init_pipe[2]; +static int do_fork = 1; +static struct auditd_reply_list *rep = NULL; +static int hup_info_requested = 0; +static int usr1_info_requested = 0, usr2_info_requested = 0; +static char subj[SUBJ_LEN]; + +/* Local function prototypes */ +int send_audit_event(int type, const char *str); +static void close_down(void); +static void clean_exit(void); +static int get_reply(int fd, struct audit_reply *rep, int seq); +static char *getsubj(char *subj); + +enum startup_state {startup_disable=0, startup_enable, startup_nochange, + startup_INVALID}; +static const char *startup_states[] = {"disable", "enable", "nochange"}; + +/* + * Output a usage message + */ +static void usage(void) +{ + fprintf(stderr, "Usage: auditd [-f] [-l] [-n] [-s %s|%s|%s]\n", + startup_states[startup_disable], + startup_states[startup_enable], + startup_states[startup_nochange]); + + exit(2); +} + + +/* + * SIGTERM handler + */ +static void term_handler(struct ev_loop *loop, struct ev_signal *sig, + int revents) +{ + EV_STOP (); +} + +/* + * Used with sigalrm to force exit + */ +static void thread_killer( int sig ) +{ + exit(0); +} + +/* + * Used with sigalrm to force exit + */ +static void hup_handler( struct ev_loop *loop, struct ev_signal *sig, int revents ) +{ + int rc; + + rc = audit_request_signal_info(fd); + if (rc < 0) + send_audit_event(AUDIT_DAEMON_CONFIG, + "auditd error getting hup info - no change, sending auid=? pid=? subj=? res=failed"); + else + hup_info_requested = 1; +} + +/* + * Used to force log rotation + */ +static void user1_handler(struct ev_loop *loop, struct ev_signal *sig, + int revents) +{ + int rc; + + rc = audit_request_signal_info(fd); + if (rc < 0) + send_audit_event(AUDIT_DAEMON_ROTATE, + "auditd error getting usr1 info - no change, sending auid=? pid=? subj=? res=failed"); + else + usr1_info_requested = 1; +} + +/* + * Used to resume logging + */ +static void user2_handler( struct ev_loop *loop, struct ev_signal *sig, int revents ) +{ + int rc; + + rc = audit_request_signal_info(fd); + if (rc < 0) { + resume_logging(); + send_audit_event(AUDIT_DAEMON_RESUME, + "auditd resuming logging, sending auid=? pid=? subj=? res=success"); + } else + usr2_info_requested = 1; +} + +/* + * Used with email alerts to cleanup + */ +static void child_handler(struct ev_loop *loop, struct ev_signal *sig, + int revents) +{ + int pid; + + while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { + if (pid == dispatcher_pid()) + dispatcher_reaped(); + } +} + +static void distribute_event(struct auditd_reply_list *rep) +{ + int attempt = 0; + + /* Make first attempt to send to plugins */ + if (dispatch_event(&rep->reply, attempt) == 1) + attempt++; /* Failed sending, retry after writing to disk */ + + /* End of Event is for realtime interface - skip local logging of it */ + if (rep->reply.type != AUDIT_EOE) { + int yield = rep->reply.type <= AUDIT_LAST_DAEMON && + rep->reply.type >= AUDIT_FIRST_DAEMON ? 1 : 0; + /* Write to local disk */ + enqueue_event(rep); + if (yield) { + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 2 * 1000 * 1000; // 2 milliseconds + nanosleep(&ts, NULL); // Let other thread try to log it + } + } else + free(rep); // This function takes custody of the memory + + // FIXME: This is commented out since it fails to work. The + // problem is that the logger thread free's the buffer. Probably + // need a way to flag in the buffer if logger thread should free or + // move the free to this function. + + /* Last chance to send...maybe the pipe is empty now. */ +// if (attempt) +// dispatch_event(&rep->reply, attempt); +} + +/* + * This function is used to send start, stop, and abort messages + * to the audit log. + */ +static unsigned seq_num = 0; +int send_audit_event(int type, const char *str) +{ + struct auditd_reply_list *rep; + struct timeval tv; + + if ((rep = malloc(sizeof(*rep))) == NULL) { + audit_msg(LOG_ERR, "Cannot allocate audit reply"); + return 1; + } + + rep->reply.type = type; + rep->reply.message = (char *)malloc(DMSG_SIZE); + if (rep->reply.message == NULL) { + free(rep); + audit_msg(LOG_ERR, "Cannot allocate local event message"); + return 1; + } + if (seq_num == 0) { + srand(time(NULL)); + seq_num = rand()%10000; + } else + seq_num++; + if (gettimeofday(&tv, NULL) == 0) { + rep->reply.len = snprintf((char *)rep->reply.message, + DMSG_SIZE, "audit(%lu.%03u:%u): %s", + tv.tv_sec, (unsigned)(tv.tv_usec/1000), seq_num, str); + } else { + rep->reply.len = snprintf((char *)rep->reply.message, + DMSG_SIZE, "audit(%lu.%03u:%u): %s", + (unsigned long)time(NULL), 0, seq_num, str); + } + if (rep->reply.len > DMSG_SIZE) + rep->reply.len = DMSG_SIZE; + + distribute_event(rep); + return 0; +} + +static int write_pid_file(void) +{ + int pidfd, len; + char val[16]; + + len = snprintf(val, sizeof(val), "%u\n", getpid()); + if (len <= 0) { + audit_msg(LOG_ERR, "Pid error (%s)", strerror(errno)); + pidfile = 0; + return 1; + } + pidfd = open(pidfile, O_CREAT | O_TRUNC | O_NOFOLLOW | O_WRONLY, 0644); + if (pidfd < 0) { + audit_msg(LOG_ERR, "Unable to set pidfile (%s)", + strerror(errno)); + pidfile = 0; + return 1; + } + if (write(pidfd, val, (unsigned int)len) != len) { + audit_msg(LOG_ERR, "Unable to write pidfile (%s)", + strerror(errno)); + close(pidfd); + pidfile = 0; + return 1; + } + close(pidfd); + return 0; +} + +static void avoid_oom_killer(void) +{ + int oomfd, len, rc; + char *score = NULL; + + /* New kernels use different technique */ + if ((oomfd = open("/proc/self/oom_score_adj", + O_NOFOLLOW | O_WRONLY)) >= 0) { + score = "-1000"; + } else if ((oomfd = open("/proc/self/oom_adj", + O_NOFOLLOW | O_WRONLY)) >= 0) { + score = "-17"; + } else { + audit_msg(LOG_NOTICE, "Cannot open out of memory adjuster"); + return; + } + + len = strlen(score); + rc = write(oomfd, score, len); + if (rc != len) + audit_msg(LOG_NOTICE, "Unable to adjust out of memory score"); + + close(oomfd); +} + +/* + * This function will take care of becoming a daemon. The parent + * will wait until the child notifies it by writing into a special + * pipe to signify that it successfully initialized. This prevents + * a race in the init script where rules get loaded before the daemon + * is ready and they wind up in syslog. The child returns 0 on success + * and nonzero on failure. The parent returns nonzero on failure. On + * success, the parent calls _exit with 0. + */ +static int become_daemon(void) +{ + int fd, rc; + pid_t pid; + int status; + + if (do_fork) { + if (pipe(init_pipe) || + fcntl(init_pipe[0], F_SETFD, FD_CLOEXEC) || + fcntl(init_pipe[0], F_SETFD, FD_CLOEXEC)) + return -1; + pid = fork(); + } else + pid = 0; + + switch (pid) + { + case 0: + /* No longer need this... */ + if (do_fork) + close(init_pipe[0]); + + /* Open stdin,out,err to /dev/null */ + fd = open("/dev/null", O_RDWR); + if (fd < 0) { + audit_msg(LOG_ERR, "Cannot open /dev/null"); + return -1; + } + if ((dup2(fd, 0) < 0) || (dup2(fd, 1) < 0) || + (dup2(fd, 2) < 0)) { + audit_msg(LOG_ERR, + "Cannot reassign descriptors to /dev/null"); + close(fd); + return -1; + } + close(fd); + + /* Change to '/' */ + rc = chdir("/"); + if (rc < 0) { + audit_msg(LOG_ERR, + "Cannot change working directory to /"); + return -1; + } + + /* Become session/process group leader */ + setsid(); + break; + case -1: + return -1; + break; + default: + /* Wait for the child to say its done */ + rc = read(init_pipe[0], &status, sizeof(status)); + if (rc < 0) + return -1; + + /* Success - die a happy death */ + if (status == SUCCESS) + _exit(0); + else + return -1; + break; + } + + return 0; +} + +static void tell_parent(int status) +{ + int rc; + + if (config.daemonize != D_BACKGROUND || do_fork == 0) + return; + do { + rc = write(init_pipe[1], &status, sizeof(status)); + } while (rc < 0 && errno == EINTR); +} + +static void netlink_handler(struct ev_loop *loop, struct ev_io *io, + int revents) +{ + if (rep == NULL) { + if ((rep = malloc(sizeof(*rep))) == NULL) { + char emsg[DEFAULT_BUF_SZ]; + if (*subj) + snprintf(emsg, sizeof(emsg), + "auditd error halt, auid=%u pid=%d subj=%s res=failed", + audit_getloginuid(), getpid(), subj); + else + snprintf(emsg, sizeof(emsg), + "auditd error halt, auid=%u pid=%d res=failed", + audit_getloginuid(), getpid()); + EV_STOP (); + send_audit_event(AUDIT_DAEMON_ABORT, emsg); + audit_msg(LOG_ERR, + "Cannot allocate audit reply, exiting"); + close_down(); + if (pidfile) + unlink(pidfile); + shutdown_dispatcher(); + return; + } + } + if (audit_get_reply(fd, &rep->reply, + GET_REPLY_NONBLOCKING, 0) > 0) { + switch (rep->reply.type) + { /* For now dont process these */ + case NLMSG_NOOP: + case NLMSG_DONE: + case NLMSG_ERROR: + case AUDIT_GET: /* Or these */ + case AUDIT_LIST_RULES: + case AUDIT_FIRST_DAEMON...AUDIT_LAST_DAEMON: + break; + case AUDIT_SIGNAL_INFO: + if (hup_info_requested) { + audit_msg(LOG_DEBUG, + "HUP detected, starting config manager"); + if (start_config_manager(rep)) { + send_audit_event( + AUDIT_DAEMON_CONFIG, + "auditd error getting hup info - no change," + " sending auid=? pid=? subj=? res=failed"); + } + rep = NULL; + hup_info_requested = 0; + } else if (usr1_info_requested) { + char usr1[MAX_AUDIT_MESSAGE_LENGTH]; + if (rep->reply.len == 24) { + snprintf(usr1, sizeof(usr1), + "auditd sending auid=? pid=? subj=?"); + } else { + snprintf(usr1, sizeof(usr1), + "auditd sending auid=%u pid=%d subj=%s", + rep->reply.signal_info->uid, + rep->reply.signal_info->pid, + rep->reply.signal_info->ctx); + } + send_audit_event(AUDIT_DAEMON_ROTATE, usr1); + usr1_info_requested = 0; + } else if (usr2_info_requested) { + char usr2[MAX_AUDIT_MESSAGE_LENGTH]; + if (rep->reply.len == 24) { + snprintf(usr2, sizeof(usr2), + "auditd resuming logging, " + "sending auid=? pid=? subj=? " + "res=success"); + } else { + snprintf(usr2, sizeof(usr2), + "auditd resuming logging, " + "sending auid=%u pid=%d subj=%s res=success", + rep->reply.signal_info->uid, + rep->reply.signal_info->pid, + rep->reply.signal_info->ctx); + } + resume_logging(); + send_audit_event(AUDIT_DAEMON_RESUME, usr2); + usr2_info_requested = 0; + } + break; + default: + distribute_event(rep); + rep = NULL; + break; + } + } else { + if (errno == EFBIG) { + // FIXME do err action + } + } +} + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + struct rlimit limit; + int i, c, rc; + int opt_foreground = 0, opt_allow_links = 0; + enum startup_state opt_startup = startup_enable; + extern char *optarg; + extern int optind; + struct ev_loop *loop; + struct ev_io netlink_watcher; + struct ev_signal sigterm_watcher; + struct ev_signal sighup_watcher; + struct ev_signal sigusr1_watcher; + struct ev_signal sigusr2_watcher; + struct ev_signal sigchld_watcher; + + /* Get params && set mode */ + while ((c = getopt(argc, argv, "flns:")) != -1) { + switch (c) { + case 'f': + opt_foreground = 1; + break; + case 'l': + opt_allow_links=1; + break; + case 'n': + do_fork = 0; + break; + case 's': + for (i=0; i<startup_INVALID; i++) { + if (strncmp(optarg, startup_states[i], + strlen(optarg)) == 0) { + opt_startup = i; + break; + } + } + if (i == startup_INVALID) { + fprintf(stderr, "unknown startup mode '%s'\n", + optarg); + usage(); + } + break; + default: + usage(); + } + } + + /* check for trailing command line following options */ + if (optind < argc) { + usage(); + } + + if (opt_allow_links) + set_allow_links(1); + + if (opt_foreground) { + config.daemonize = D_FOREGROUND; + set_aumessage_mode(MSG_STDERR, DBG_YES); + } else { + config.daemonize = D_BACKGROUND; + set_aumessage_mode(MSG_SYSLOG, DBG_NO); + (void) umask( umask( 077 ) | 022 ); + } + +#ifndef DEBUG + /* Make sure we are root */ + if (getuid() != 0) { + fprintf(stderr, "You must be root to run this program.\n"); + return 4; + } +#endif + + /* Register sighandlers */ + sa.sa_flags = 0 ; + sigemptyset( &sa.sa_mask ) ; + /* Ignore all signals by default */ + sa.sa_handler = SIG_IGN; + for (i=1; i<NSIG; i++) + sigaction( i, &sa, NULL ); + + atexit(clean_exit); + + /* Raise the rlimits in case we're being started from a shell + * with restrictions. Not a fatal error. */ + limit.rlim_cur = RLIM_INFINITY; + limit.rlim_max = RLIM_INFINITY; + setrlimit(RLIMIT_FSIZE, &limit); + setrlimit(RLIMIT_CPU, &limit); + + /* Load the Configuration File */ + if (load_config(&config, TEST_AUDITD)) + return 6; + + if (config.priority_boost != 0) { + errno = 0; + rc = nice((int)-config.priority_boost); + if (rc == -1 && errno) { + audit_msg(LOG_ERR, "Cannot change priority (%s)", + strerror(errno)); + return 1; + } + } + + /* Daemonize or stay in foreground for debugging */ + if (config.daemonize == D_BACKGROUND) { + if (become_daemon() != 0) { + audit_msg(LOG_ERR, "Cannot daemonize (%s)", + strerror(errno)); + tell_parent(FAILURE); + return 1; + } + openlog("auditd", LOG_PID, LOG_DAEMON); + } + + /* Init netlink */ + if ((fd = audit_open()) < 0) { + audit_msg(LOG_ERR, "Cannot open netlink audit socket"); + tell_parent(FAILURE); + return 1; + } + + /* Init the event handler thread */ + write_pid_file(); + if (init_event(&config)) { + if (pidfile) + unlink(pidfile); + tell_parent(FAILURE); + return 1; + } + + if (init_dispatcher(&config)) { + if (pidfile) + unlink(pidfile); + tell_parent(FAILURE); + return 1; + } + + /* Get machine name ready for use */ + if (resolve_node(&config)) { + if (pidfile) + unlink(pidfile); + tell_parent(FAILURE); + return 1; + } + + /* Write message to log that we are alive */ + { + struct utsname ubuf; + char start[DEFAULT_BUF_SZ]; + const char *fmt = audit_lookup_format((int)config.log_format); + if (fmt == NULL) + fmt = "UNKNOWN"; + if (uname(&ubuf) != 0) { + if (pidfile) + unlink(pidfile); + tell_parent(FAILURE); + return 1; + } + if (getsubj(subj)) + snprintf(start, sizeof(start), + "auditd start, ver=%s format=%s " + "kernel=%.56s auid=%u pid=%d subj=%s res=success", + VERSION, fmt, ubuf.release, + audit_getloginuid(), getpid(), subj); + else + snprintf(start, sizeof(start), + "auditd start, ver=%s format=%s " + "kernel=%.56s auid=%u pid=%d res=success", + VERSION, fmt, ubuf.release, + audit_getloginuid(), getpid()); + if (send_audit_event(AUDIT_DAEMON_START, start)) { + audit_msg(LOG_ERR, "Cannot send start message"); + if (pidfile) + unlink(pidfile); + shutdown_dispatcher(); + tell_parent(FAILURE); + return 1; + } + } + + /* Tell kernel not to kill us */ + avoid_oom_killer(); + + /* let config manager init */ + init_config_manager(); + + if (opt_startup != startup_nochange && (audit_is_enabled(fd) < 2) && + audit_set_enabled(fd, (int)opt_startup) < 0) { + char emsg[DEFAULT_BUF_SZ]; + if (*subj) + snprintf(emsg, sizeof(emsg), + "auditd error halt, auid=%u pid=%d subj=%s res=failed", + audit_getloginuid(), getpid(), subj); + else + snprintf(emsg, sizeof(emsg), + "auditd error halt, auid=%u pid=%d res=failed", + audit_getloginuid(), getpid()); + stop = 1; + send_audit_event(AUDIT_DAEMON_ABORT, emsg); + audit_msg(LOG_ERR, + "Unable to set initial audit startup state to '%s', exiting", + startup_states[opt_startup]); + close_down(); + if (pidfile) + unlink(pidfile); + shutdown_dispatcher(); + tell_parent(FAILURE); + return 1; + } + + /* Tell the kernel we are alive */ + if (audit_set_pid(fd, getpid(), WAIT_YES) < 0) { + char emsg[DEFAULT_BUF_SZ]; + if (*subj) + snprintf(emsg, sizeof(emsg), + "auditd error halt, auid=%u pid=%d subj=%s res=failed", + audit_getloginuid(), getpid(), subj); + else + snprintf(emsg, sizeof(emsg), + "auditd error halt, auid=%u pid=%d res=failed", + audit_getloginuid(), getpid()); + stop = 1; + send_audit_event(AUDIT_DAEMON_ABORT, emsg); + audit_msg(LOG_ERR, "Unable to set audit pid, exiting"); + close_down(); + if (pidfile) + unlink(pidfile); + shutdown_dispatcher(); + tell_parent(FAILURE); + return 1; + } + + /* Depending on value of opt_startup (-s) set initial audit state */ + loop = ev_default_loop (EVFLAG_NOENV); + + ev_io_init (&netlink_watcher, netlink_handler, fd, EV_READ); + ev_io_start (loop, &netlink_watcher); + + ev_signal_init (&sigterm_watcher, term_handler, SIGTERM); + ev_signal_start (loop, &sigterm_watcher); + + ev_signal_init (&sighup_watcher, hup_handler, SIGHUP); + ev_signal_start (loop, &sighup_watcher); + + ev_signal_init (&sigusr1_watcher, user1_handler, SIGUSR1); + ev_signal_start (loop, &sigusr1_watcher); + + ev_signal_init (&sigusr2_watcher, user2_handler, SIGUSR2); + ev_signal_start (loop, &sigusr2_watcher); + + ev_signal_init (&sigchld_watcher, child_handler, SIGCHLD); + ev_signal_start (loop, &sigchld_watcher); + + if (auditd_tcp_listen_init (loop, &config)) { + char emsg[DEFAULT_BUF_SZ]; + if (*subj) + snprintf(emsg, sizeof(emsg), + "auditd error halt, auid=%u pid=%d subj=%s res=failed", + audit_getloginuid(), getpid(), subj); + else + snprintf(emsg, sizeof(emsg), + "auditd error halt, auid=%u pid=%d res=failed", + audit_getloginuid(), getpid()); + stop = 1; + send_audit_event(AUDIT_DAEMON_ABORT, emsg); + tell_parent(FAILURE); + } else { + /* Now tell parent that everything went OK */ + tell_parent(SUCCESS); + audit_msg(LOG_NOTICE, + "Init complete, auditd %s listening for events (startup state %s)", + VERSION, + startup_states[opt_startup]); + } + + /* Parent should be gone by now... */ + if (do_fork) + close(init_pipe[1]); + + // Init complete, start event loop + if (!stop) + ev_loop (loop, 0); + + auditd_tcp_listen_uninit (loop, &config); + + // Tear down IO watchers Part 1 + ev_signal_stop (loop, &sighup_watcher); + ev_signal_stop (loop, &sigusr1_watcher); + ev_signal_stop (loop, &sigusr2_watcher); + ev_signal_stop (loop, &sigterm_watcher); + + /* Write message to log that we are going down */ + rc = audit_request_signal_info(fd); + if (rc > 0) { + struct audit_reply trep; + + rc = get_reply(fd, &trep, rc); + if (rc > 0) { + char txt[MAX_AUDIT_MESSAGE_LENGTH]; + snprintf(txt, sizeof(txt), + "auditd normal halt, sending auid=%u " + "pid=%d subj=%s res=success", + trep.signal_info->uid, + trep.signal_info->pid, + trep.signal_info->ctx); + send_audit_event(AUDIT_DAEMON_END, txt); + } + } + if (rc <= 0) + send_audit_event(AUDIT_DAEMON_END, + "auditd normal halt, sending auid=? " + "pid=? subj=? res=success"); + free(rep); + + // Tear down IO watchers Part 2 + ev_io_stop (loop, &netlink_watcher); + + // Give DAEMON_END event a little time to be sent in case + // of remote logging + usleep(10000); // 10 milliseconds + shutdown_dispatcher(); + + // Tear down IO watchers Part 3 + ev_signal_stop (loop, &sigchld_watcher); + + close_down(); + free_config(&config); + ev_default_destroy(); + + return 0; +} + +static void close_down(void) +{ + struct sigaction sa; + + /* We are going down. Give the event thread a chance to shutdown. + Just in case it hangs, set a timer to get us out of trouble. */ + sa.sa_flags = 0 ; + sigemptyset( &sa.sa_mask ) ; + sa.sa_handler = thread_killer; + sigaction( SIGALRM, &sa, NULL ); + shutdown_events(); +} + + +/* + * A clean exit means : + * 1) we log that we are going down + * 2) deregister with kernel + * 3) close the netlink socket + */ +static void clean_exit(void) +{ + audit_msg(LOG_INFO, "The audit daemon is exiting."); + if (fd >= 0) { + audit_set_pid(fd, 0, WAIT_NO); + audit_close(fd); + } + if (pidfile) + unlink(pidfile); + closelog(); +} + +/* + * This function is used to get the reply for term info. + * Returns 1 on success & -1 on failure. + */ +static int get_reply(int fd, struct audit_reply *rep, int seq) +{ + int rc, i; + int timeout = 30; /* tenths of seconds */ + + for (i = 0; i < timeout; i++) { + struct timeval t; + fd_set read_mask; + + t.tv_sec = 0; + t.tv_usec = 100000; /* .1 second */ + FD_ZERO(&read_mask); + FD_SET(fd, &read_mask); + do { + rc = select(fd+1, &read_mask, NULL, NULL, &t); + } while (rc < 0 && errno == EINTR); + rc = audit_get_reply(fd, rep, + GET_REPLY_NONBLOCKING, 0); + if (rc > 0) { + /* Don't make decisions based on wrong packet */ + if (rep->nlh->nlmsg_seq != seq) + continue; + + /* If its not what we are expecting, keep looping */ + if (rep->type == AUDIT_SIGNAL_INFO) + return 1; + + /* If we get done or error, break out */ + if (rep->type == NLMSG_DONE || rep->type == NLMSG_ERROR) + break; + } + } + return -1; +} + +//get the subj of the daemon +static char *getsubj(char *subj) +{ + pid_t pid = getpid(); + char filename[48]; + ssize_t num_read; + int fd; + + snprintf(filename, sizeof(filename), "/proc/%u/attr/current", pid); + fd = open(filename, O_RDONLY); + if(fd == -1) { + subj[0] = 0; + return NULL; + } + do { + num_read = read(fd, subj, SUBJ_LEN-1); + } while (num_read < 0 && errno == EINTR); + close(fd); + if(num_read <= 0) { + subj[0] = 0; + return NULL; + } + subj[num_read] = '\0'; + return subj; +} + diff --git a/framework/src/audit/src/aureport-options.c b/framework/src/audit/src/aureport-options.c new file mode 100644 index 00000000..7508417f --- /dev/null +++ b/framework/src/audit/src/aureport-options.c @@ -0,0 +1,722 @@ +/* aureport-options.c - parse commandline options and configure aureport + * Copyright 2005-08,2010-11,2014 Red Hat Inc., Durham, North Carolina. + * Copyright (c) 2011 IBM Corp. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * Marcelo Henrique Cerri <mhcerri@br.ibm.com> + */ + +#include "config.h" +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <pwd.h> +#include <grp.h> +#include <time.h> +#include "aureport-options.h" +#include "ausearch-time.h" +#include "libaudit.h" + + +/* Global vars that will be accessed by the main program */ +char *user_file = NULL; +int force_logs = 0; +int no_config = 0; + +/* These are for compatibility with parser */ +unsigned int event_id = -1; +uid_t event_uid = -1, event_loginuid = -2, event_euid = -1; +gid_t event_gid = -1, event_egid = -1; +slist *event_node_list = NULL; +const char *event_key = NULL; +const char *event_filename = NULL; +const char *event_exe = NULL; +const char *event_comm = NULL; +const char *event_hostname = NULL; +const char *event_terminal = NULL; +const char *event_subject = NULL; +const char *event_object = NULL; +const char *event_uuid = NULL; +const char *event_vmname = NULL; +long long event_exit = 0; +int event_exit_is_set = 0; +int event_ppid = -1, event_session_id = -2; +int event_debug = 0, event_machine = -1; + +/* These are used by aureport */ +const char *dummy = "dummy"; +report_type_t report_type = RPT_UNSET; +report_det_t report_detail = D_UNSET; +report_t report_format = RPT_DEFAULT; +failed_t event_failed = F_BOTH; +conf_act_t event_conf_act = C_NEITHER; +success_t event_success = S_SUCCESS; +int event_pid = 0; + +struct nv_pair { + int value; + const char *name; +}; + +enum { R_INFILE, R_TIME_END, R_TIME_START, R_VERSION, R_SUMMARY, R_LOG_TIMES, + R_CONFIGS, R_LOGINS, R_USERS, R_TERMINALS, R_HOSTS, R_EXES, R_FILES, + R_AVCS, R_SYSCALLS, R_PIDS, R_EVENTS, R_ACCT_MODS, + R_INTERPRET, R_HELP, R_ANOMALY, R_RESPONSE, R_SUMMARY_DET, R_CRYPTO, + R_MAC, R_FAILED, R_SUCCESS, R_ADD, R_DEL, R_AUTH, R_NODE, R_IN_LOGS, + R_KEYS, R_TTY, R_NO_CONFIG, R_COMM, R_VIRT, R_INTEG }; + +static struct nv_pair optiontab[] = { + { R_AUTH, "-au" }, + { R_AUTH, "--auth" }, + { R_AVCS, "-a" }, + { R_AVCS, "--avc" }, + { R_ADD, "--add" }, + { R_CONFIGS, "-c" }, + { R_COMM, "--comm" }, + { R_CONFIGS, "--config" }, + { R_CRYPTO, "-cr" }, + { R_CRYPTO, "--crypto" }, + { R_DEL, "--delete" }, + { R_EVENTS, "-e" }, + { R_EVENTS, "--event" }, + { R_FILES, "-f" }, + { R_FILES, "--file" }, + { R_FAILED, "--failed" }, + { R_HOSTS, "-h" }, + { R_HOSTS, "--host" }, + { R_HELP, "--help" }, + { R_INTERPRET, "-i" }, + { R_INTERPRET, "--interpret" }, + { R_INFILE, "-if" }, + { R_INFILE, "--input" }, + { R_IN_LOGS, "--input-logs" }, + { R_INTEG, "--integrity" }, + { R_KEYS, "-k" }, + { R_KEYS, "--key" }, + { R_LOGINS, "-l" }, + { R_LOGINS, "--login" }, + { R_ACCT_MODS, "-m" }, + { R_ACCT_MODS, "--mods" }, + { R_MAC, "-ma" }, + { R_MAC, "--mac" }, + { R_NODE, "--node" }, + { R_NO_CONFIG, "-nc" }, + { R_NO_CONFIG, "--no-config" }, + { R_ANOMALY, "-n" }, + { R_ANOMALY, "--anomaly" }, + { R_PIDS, "-p" }, + { R_PIDS, "--pid" }, + { R_RESPONSE, "-r" }, + { R_RESPONSE, "--response" }, + { R_SYSCALLS, "-s" }, + { R_SYSCALLS, "--syscall" }, + { R_SUCCESS, "--success" }, + { R_SUMMARY_DET, "--summary" }, + { R_LOG_TIMES, "-t" }, + { R_LOG_TIMES, "--log" }, + { R_TIME_END, "-te"}, + { R_TIME_END, "--end"}, + { R_TERMINALS, "-tm"}, // don't like this + { R_TERMINALS, "--terminal"}, // don't like this + { R_TIME_START, "-ts" }, + { R_TTY, "--tty" }, + { R_TIME_START, "--start" }, + { R_USERS, "-u" }, + { R_USERS, "--user" }, + { R_VERSION, "-v" }, + { R_VERSION, "--version" }, + { R_EXES, "-x" }, + { R_EXES, "--executable" }, + { R_VIRT, "--virt" } +}; +#define OPTION_NAMES (sizeof(optiontab)/sizeof(optiontab[0])) + + +static int audit_lookup_option(const char *name) +{ + int i; + + for (i = 0; i < OPTION_NAMES; i++) + if (!strcmp(optiontab[i].name, name)) + return optiontab[i].value; + return -1; +} + +static void usage(void) +{ + printf("usage: aureport [options]\n" + "\t-a,--avc\t\t\tAvc report\n" + "\t-au,--auth\t\t\tAuthentication report\n" + "\t--comm\t\t\t\tCommands run report\n" + "\t-c,--config\t\t\tConfig change report\n" + "\t-cr,--crypto\t\t\tCrypto report\n" + "\t-e,--event\t\t\tEvent report\n" + "\t-f,--file\t\t\tFile name report\n" + "\t--failed\t\t\tonly failed events in report\n" + "\t-h,--host\t\t\tRemote Host name report\n" + "\t--help\t\t\t\thelp\n" + "\t-i,--interpret\t\t\tInterpretive mode\n" + "\t-if,--input <Input File name>\tuse this file as input\n" + "\t--input-logs\t\t\tUse the logs even if stdin is a pipe\n" + "\t--integrity\t\t\tIntegrity event report\n" + "\t-l,--login\t\t\tLogin report\n" + "\t-k,--key\t\t\tKey report\n" + "\t-m,--mods\t\t\tModification to accounts report\n" + "\t-ma,--mac\t\t\tMandatory Access Control (MAC) report\n" + "\t-n,--anomaly\t\t\taNomaly report\n" + "\t-nc,--no-config\t\t\tDon't include config events\n" + "\t--node <node name>\t\tOnly events from a specific node\n" + "\t-p,--pid\t\t\tPid report\n" + "\t-r,--response\t\t\tResponse to anomaly report\n" + "\t-s,--syscall\t\t\tSyscall report\n" + "\t--success\t\t\tonly success events in report\n" + "\t--summary\t\t\tsorted totals for main object in report\n" + "\t-t,--log\t\t\tLog time range report\n" + "\t-te,--end [end date] [end time]\tending date & time for reports\n" + "\t-tm,--terminal\t\t\tTerMinal name report\n" + "\t-ts,--start [start date] [start time]\tstarting data & time for reports\n" + "\t--tty\t\t\t\tReport about tty keystrokes\n" + "\t-u,--user\t\t\tUser name report\n" + "\t-v,--version\t\t\tVersion\n" + "\t--virt\t\t\t\tVirtualization report\n" + "\t-x,--executable\t\t\teXecutable name report\n" + "\tIf no report is given, the summary report will be displayed\n" + ); +} + +static int set_report(report_type_t r) +{ + if (report_type == RPT_UNSET) { + report_type = r; + return 0; + } else { + fprintf(stderr, "Error - only one report can be specified"); + return 1; + } +} + +static int set_detail(report_det_t d) +{ + if (report_detail == D_UNSET) { + report_detail = d; + return 0; + } else if (d == D_SUM) { + report_detail = d; + return 0; + } else { + return 1; + } +} + +/* + * This function examines the commandline parameters and sets various + * search options. It returns a 0 on success and < 0 on failure + */ +int check_params(int count, char *vars[]) +{ + int c = 1; + int retval = 0; + const char *optarg; + + while (c < count && retval == 0) { + // Go ahead and point to the next argument + if (c+1 < count) { + if (vars[c+1][0] != '-') + optarg = vars[c+1]; + else + optarg = NULL; + } else + optarg = NULL; + + switch (audit_lookup_option(vars[c])) { + case R_INFILE: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + user_file = strdup(optarg); + if (user_file == NULL) + retval = -1; + c++; + } + break; + case R_LOG_TIMES: + if (set_report(RPT_TIME)) + retval = -1; + else + set_detail(D_DETAILED); + break; + case R_AVCS: + if (set_report(RPT_AVC)) + retval = -1; + else { + set_detail(D_DETAILED); + event_comm = dummy; + event_subject = dummy; + event_object = dummy; + } + break; + case R_AUTH: + if (set_report(RPT_AUTH)) + retval = -1; + else { + set_detail(D_DETAILED); + event_exe = dummy; + event_hostname = dummy; + event_terminal = dummy; + event_uid = 1; + } + break; + case R_MAC: + if (set_report(RPT_MAC)) + retval = -1; + else { + set_detail(D_DETAILED); + event_loginuid = 1; + } + break; + case R_INTEG: + if (set_report(RPT_INTEG)) + retval = -1; + else { + set_detail(D_DETAILED); + event_loginuid = 1; + } + break; + case R_VIRT: + if (set_report(RPT_VIRT)) + retval = -1; + else { + set_detail(D_DETAILED); + } + break; + case R_CONFIGS: + if (set_report(RPT_CONFIG)) + retval = -1; + else { + set_detail(D_DETAILED); + event_loginuid = 1; + } + break; + case R_CRYPTO: + if (set_report(RPT_CRYPTO)) + retval = -1; + else { + set_detail(D_DETAILED); + event_loginuid = 1; + } + break; + case R_LOGINS: + if (set_report(RPT_LOGIN)) + retval = -1; + else { + set_detail(D_DETAILED); + event_exe = dummy; + event_hostname = dummy; + event_terminal = dummy; + event_loginuid = 1; + } + break; + case R_ACCT_MODS: + if (set_report(RPT_ACCT_MOD)) + retval = -1; + else { + set_detail(D_DETAILED); + event_exe = dummy; + event_hostname = dummy; + event_terminal = dummy; + event_loginuid = 1; + } + break; + case R_EVENTS: + if (set_report(RPT_EVENT)) + retval = -1; + else { +// if (!optarg) { + set_detail(D_DETAILED); + event_loginuid = 1; +// } else { +// UNIMPLEMENTED; +// set_detail(D_SPECIFIC); +// if (isdigit(optarg[0])) { +// errno = 0; +// event_id = strtoul(optarg, +// NULL, 10); +// if (errno) { +// fprintf(stderr, +// "Illegal value for audit event ID"); +// retval = -1; +// } +// c++; +// } else { +// fprintf(stderr, +// "Audit event id must be a numeric value, was %s\n", +// optarg); +// retval = -1; +// } +// } + } + break; + case R_FILES: + if (set_report(RPT_FILE)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_filename = dummy; + event_exe = dummy; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_HOSTS: + if (set_report(RPT_HOST)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_hostname = dummy; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_INTERPRET: + report_format = RPT_INTERP; + if (optarg) { + fprintf(stderr, + "Argument is NOT required for %s\n", + vars[c]); + retval = -1; + } + break; + case R_PIDS: + if (set_report(RPT_PID)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_exe = dummy; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_SYSCALLS: + if (set_report(RPT_SYSCALL)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_comm = dummy; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_TERMINALS: + if (set_report(RPT_TERM)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_terminal = dummy; + event_hostname = dummy; + event_exe = dummy; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_USERS: + if (set_report(RPT_USER)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_terminal = dummy; + event_hostname = dummy; + event_exe = dummy; + event_uid = 1; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_EXES: + if (set_report(RPT_EXE)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_terminal = dummy; + event_hostname = dummy; + event_exe = dummy; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_COMM: + if (set_report(RPT_COMM)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_terminal = dummy; + event_hostname = dummy; + event_comm = dummy; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_ANOMALY: + if (set_report(RPT_ANOMALY)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_terminal = dummy; + event_hostname = dummy; + event_exe = dummy; + event_comm = dummy; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_RESPONSE: + if (set_report(RPT_RESPONSE)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + } else { + UNIMPLEMENTED; + } + } + break; + case R_KEYS: + if (set_report(RPT_KEY)) + retval = -1; + else { + if (!optarg) { + set_detail(D_DETAILED); + event_exe = dummy; + event_key = dummy; + event_loginuid = 1; + } else { + UNIMPLEMENTED; + } + } + break; + case R_TTY: + if (set_report(RPT_TTY)) + retval = -1; + else { + set_detail(D_DETAILED); + event_session_id = 1; + event_loginuid = 1; + event_terminal = dummy; + event_comm = dummy; + } + break; + case R_TIME_END: + if (optarg) { + if ( (c+2 < count) && vars[c+2] && + (vars[c+2][0] != '-') ) { + /* Have both date and time - check order*/ + if (strchr(optarg, ':')) { + if (ausearch_time_end(vars[c+2], + optarg) != 0) + retval = -1; + } else { + if (ausearch_time_end(optarg, + vars[c+2]) != 0) + retval = -1; + } + c++; + } else { + // Check against recognized words + int t = lookup_time(optarg); + if (t >= 0) { + if (ausearch_time_end(optarg, + NULL) != 0) + retval = -1; + } else if ( (strchr(optarg, ':')) == NULL) { + /* Only have date */ + if (ausearch_time_end(optarg, + NULL) != 0) + retval = -1; + } else { + /* Only have time */ + if (ausearch_time_end(NULL, + optarg) != 0) + retval = -1; + } + } + c++; + break; + } + fprintf(stderr, + "%s requires either date and/or time\n", + vars[c]); + retval = -1; + break; + case R_TIME_START: + if (optarg) { + if ( (c+2 < count) && vars[c+2] && + (vars[c+2][0] != '-') ) { + /* Have both date and time - check order */ + if (strchr(optarg, ':')) { + if (ausearch_time_start( + vars[c+2], optarg) != 0) + retval = -1; + } else { + if (ausearch_time_start(optarg, + vars[c+2]) != 0) + retval = -1; + } + c++; + } else { + // Check against recognized words + int t = lookup_time(optarg); + if (t >= 0) { + if (ausearch_time_start(optarg, + "00:00:00") != 0) + retval = -1; + } else if ( strchr(optarg, ':') == NULL) { + /* Only have date */ + if (ausearch_time_start(optarg, + "00:00:00") != 0) + retval = -1; + } else { + /* Only have time */ + if (ausearch_time_start(NULL, + optarg) != 0) + retval = -1; + } + } + c++; + break; + } + fprintf(stderr, + "%s requires either date and/or time\n", + vars[c]); + retval = -1; + break; + case R_NODE: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + snode sn; + c++; + + if (!event_node_list) { + event_node_list = malloc(sizeof (slist)); + if (!event_node_list) { + retval = -1; + break; + } + slist_create(event_node_list); + } + + sn.str = strdup(optarg); + sn.key = NULL; + sn.hits=0; + slist_append(event_node_list, &sn); + } + break; + case R_SUMMARY_DET: + set_detail(D_SUM); + break; + case R_FAILED: + event_failed = F_FAILED; + break; + case R_SUCCESS: + event_failed = F_SUCCESS; + break; + case R_ADD: + event_conf_act = C_ADD; + break; + case R_DEL: + event_conf_act = C_DEL; + break; + case R_IN_LOGS: + force_logs = 1; + break; + case R_NO_CONFIG: + no_config = 1; + break; + case R_VERSION: + printf("aureport version %s\n", VERSION); + exit(0); + break; + case R_HELP: + usage(); + exit(0); + break; + default: + fprintf(stderr, "%s is an unsupported option\n", + vars[c]); + retval = -1; + break; + } + c++; + } + + if (retval >= 0) { + if (report_type == RPT_UNSET) { + if (set_report(RPT_SUMMARY)) + retval = -1; + else { + set_detail(D_SUM); + event_filename = dummy; + event_hostname = dummy; + event_terminal = dummy; + event_exe = dummy; + event_comm = dummy; + event_key = dummy; + event_loginuid = 1; + } + } + } else + usage(); + + return retval; +} + diff --git a/framework/src/audit/src/aureport-options.h b/framework/src/audit/src/aureport-options.h new file mode 100644 index 00000000..a559f645 --- /dev/null +++ b/framework/src/audit/src/aureport-options.h @@ -0,0 +1,55 @@ +/* aureport-options.h -- + * Copyright 2005-06, 2008,2014 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#ifndef AUREPORT_OPTIONS_H +#define AUREPORT_OPTIONS_H + +#include <time.h> +#include <sys/types.h> +#include "ausearch-common.h" + +/* Global variables that describe what search is to be performed */ +extern const char *event_context; + +typedef enum { RPT_UNSET, RPT_TIME, RPT_SUMMARY, RPT_AVC, RPT_MAC, + RPT_CONFIG, RPT_EVENT, RPT_FILE, RPT_HOST, RPT_LOGIN, + RPT_ACCT_MOD, RPT_PID, RPT_SYSCALL, RPT_TERM, RPT_USER, + RPT_EXE, RPT_ANOMALY, RPT_RESPONSE, RPT_CRYPTO, + RPT_AUTH, RPT_KEY, RPT_TTY, RPT_COMM, RPT_VIRT, + RPT_INTEG } report_type_t; + +typedef enum { D_UNSET, D_SUM, D_DETAILED, D_SPECIFIC } report_det_t; + +extern report_type_t report_type; +extern report_det_t report_detail; +extern report_t report_format; + + +/* Function to process commandline options */ +extern int check_params(int count, char *vars[]); + +#include <stdlib.h> +#define UNIMPLEMENTED { fprintf(stderr,"Unimplemented option\n"); exit(1); } + +#endif + diff --git a/framework/src/audit/src/aureport-output.c b/framework/src/audit/src/aureport-output.c new file mode 100644 index 00000000..9125d5ff --- /dev/null +++ b/framework/src/audit/src/aureport-output.c @@ -0,0 +1,1023 @@ +/* +* aureport-output.c - Print the report +* Copyright (c) 2005-06,2008,2014 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include "aureport-scan.h" +#include "aureport-options.h" +#include "ausearch-lookup.h" + +/* Locale functions */ +static void print_title_summary(void); +static void print_title_detailed(void); +static void do_summary_output(void); +static void do_file_summary_output(slist *sptr); +static void do_string_summary_output(slist *sptr); +static void do_user_summary_output(slist *sptr); +static void do_int_summary_output(ilist *sptr); +static void do_syscall_summary_output(ilist *sptr); +static void do_type_summary_output(ilist *sptr); + +/* Local Data */ +unsigned int line_item; + + +void print_title(void) +{ + line_item = 0U; + printf("\n"); + switch (report_detail) + { + case D_SUM: + print_title_summary(); + break; + case D_DETAILED: + print_title_detailed(); + break; + case D_SPECIFIC: + default: + break; + } +} + +static void print_title_summary(void) +{ + if (event_failed == F_FAILED) printf("Failed "); + if (event_failed == F_SUCCESS) printf("Success "); + switch (report_type) + { + case RPT_SUMMARY: + printf("Summary Report\n"); + printf("======================\n"); + break; + case RPT_AVC: + printf("Avc Object Summary Report\n"); + printf("=================================\n"); + printf("total obj\n"); + printf("=================================\n"); + break; + case RPT_MAC: + printf("MAC Summary Report\n"); + printf("==================\n"); + printf("total type\n"); + printf("==================\n"); + break; + case RPT_INTEG: + printf("Integrity Summary Report\n"); + printf("========================\n"); + printf("total type\n"); + printf("========================\n"); + break; + case RPT_VIRT: + printf("Virtualization Summary Report\n"); + printf("=============================\n"); + printf("total type\n"); + printf("=============================\n"); + break; + case RPT_CONFIG: + printf("Config Change Summary Report\n"); + printf("============================\n"); + printf("total type\n"); + printf("============================\n"); + break; + case RPT_AUTH: + printf("Authentication Summary Report\n"); + printf("=============================\n"); + printf("total acct\n"); + printf("=============================\n"); + break; + case RPT_LOGIN: + printf("Login Summary Report\n"); + printf("============================\n"); + printf("total auid\n"); + printf("============================\n"); + break; + case RPT_ACCT_MOD: + printf("Acct Modification Summary Report\n"); + printf("================================\n"); + printf("total type\n"); + printf("================================\n"); + break; + case RPT_TIME: + UNIMPLEMENTED; + break; + case RPT_EVENT: + printf("Event Summary Report\n"); + printf("======================\n"); + printf("total type\n"); + printf("======================\n"); + break; + case RPT_FILE: + printf("File Summary Report\n"); + printf("===========================\n"); + printf("total file\n"); + printf("===========================\n"); + break; + case RPT_HOST: + printf("Host Summary Report\n"); + printf("===========================\n"); + printf("total host\n"); + printf("===========================\n"); + break; + case RPT_PID: + printf("Pid Summary Report\n"); + printf("==========================\n"); + printf("total pid\n"); + printf("==========================\n"); + break; + case RPT_SYSCALL: + printf("Syscall Summary Report\n"); + printf("==========================\n"); + printf("total syscall\n"); + printf("==========================\n"); + break; + case RPT_TERM: + printf("Terminal Summary Report\n"); + printf("===============================\n"); + printf("total terminal\n"); + printf("===============================\n"); + break; + case RPT_USER: + printf("User Summary Report\n"); + printf("===========================\n"); + printf("total auid\n"); + printf("===========================\n"); + break; + case RPT_EXE: + printf("Executable Summary Report\n"); + printf("=================================\n"); + printf("total file\n"); + printf("=================================\n"); + break; + case RPT_COMM: + printf("Command Summary Report\n"); + printf("=================================\n"); + printf("total command\n"); + printf("=================================\n"); + break; + case RPT_ANOMALY: + printf("Anomaly Summary Report\n"); + printf("======================\n"); + printf("total type\n"); + printf("======================\n"); + break; + case RPT_RESPONSE: + printf("Anomaly Response Summary Report\n"); + printf("===============================\n"); + printf("total type\n"); + printf("===============================\n"); + break; + case RPT_CRYPTO: + printf("Crypto Summary Report\n"); + printf("=====================\n"); + printf("total type\n"); + printf("=====================\n"); + break; + case RPT_KEY: + printf("Key Summary Report\n"); + printf("===========================\n"); + printf("total key\n"); + printf("===========================\n"); + break; + case RPT_TTY: + UNIMPLEMENTED; + break; + default: + break; + } +} + +static void print_title_detailed(void) +{ + switch (report_type) + { + case RPT_AVC: + printf("AVC Report\n"); + printf( + "========================================================\n"); + printf( + "# date time comm subj syscall class permission obj event\n"); + printf( + "========================================================\n"); + break; + case RPT_CONFIG: + printf("Config Change Report\n"); + printf("===================================\n"); + printf("# date time type auid success event\n"); + printf("===================================\n"); + break; + case RPT_AUTH: + printf("Authentication Report\n"); + printf( + "============================================\n"); + printf( + "# date time acct host term exe success event\n"); + printf( + "============================================\n"); + break; + case RPT_LOGIN: + printf("Login Report\n"); + printf( + "============================================\n"); + printf( + "# date time auid host term exe success event\n"); + printf( + "============================================\n"); + break; + case RPT_ACCT_MOD: + printf("Account Modifications Report\n"); + printf( + "=================================================\n"); + printf( + "# date time auid addr term exe acct success event\n"); + printf( + "=================================================\n"); + break; + case RPT_TIME: + printf("Log Time Range Report\n"); + printf("=====================\n"); + break; + case RPT_EVENT: + if (report_detail == D_DETAILED) { + printf("Event Report\n"); + printf("===================================\n"); + printf("# date time event type auid success\n"); + printf("===================================\n"); + } else { + printf("Specific Event Report\n"); + printf("=====================\n"); + } + break; + case RPT_FILE: + if (report_detail == D_DETAILED) { + printf("File Report\n"); + printf( + "===============================================\n"); + printf( + "# date time file syscall success exe auid event\n"); + printf( + "===============================================\n"); + } else { + printf("Specific File Report\n"); + printf("====================\n"); + } + break; + case RPT_HOST: + if (report_detail == D_DETAILED) { + printf("Host Report\n"); + printf("===================================\n"); + printf("# date time host syscall auid event\n"); + printf("===================================\n"); + } else { + printf("Specific Host Report\n"); + printf("====================\n"); + } + break; + case RPT_PID: + if (report_detail == D_DETAILED) { + printf("Process ID Report\n"); + printf( + "======================================\n"); + printf( + "# date time pid exe syscall auid event\n"); + printf( + "======================================\n"); + } else { + printf("Specific Process ID Report\n"); + printf("==========================\n"); + } + break; + case RPT_SYSCALL: + if (report_detail == D_DETAILED) { + printf("Syscall Report\n"); + printf( + "=======================================\n"); + printf( + "# date time syscall pid comm auid event\n"); + printf( + "=======================================\n"); + } else { + printf("Specific Syscall Report\n"); + printf("=======================\n"); + } + break; + case RPT_TERM: + if (report_detail == D_DETAILED) { + printf("Terminal Report\n"); + printf( + "====================================\n"); + printf( + "# date time term host exe auid event\n"); + printf( + "====================================\n"); + } else { + printf("Specific Terminal Report\n"); + printf("========================\n"); + } + break; + case RPT_USER: + if (report_detail == D_DETAILED) { + printf("User ID Report\n"); + printf( + "====================================\n"); + printf( + "# date time auid term host exe event\n"); + printf( + "====================================\n"); + } else { + printf("Specific User ID Report\n"); + printf("=======================\n"); + } + break; + case RPT_EXE: + if (report_detail == D_DETAILED) { + printf("Executable Report\n"); + printf( + "====================================\n"); + printf( + "# date time exe term host auid event\n"); + printf( + "====================================\n"); + } else { + printf("Specific Executable Report\n"); + printf("==========================\n"); + } + break; + case RPT_COMM: + if (report_detail == D_DETAILED) { + printf("Command Report\n"); + printf( + "====================================\n"); + printf( + "# date time comm term host auid event\n"); + printf( + "=====================================\n"); + } else { + printf("Specific command Report\n"); + printf("=======================\n"); + } + break; + case RPT_ANOMALY: + if (report_detail == D_DETAILED) { + printf("Anomaly Report\n"); + printf( + "=========================================\n"); + printf( + "# date time type exe term host auid event\n"); + printf( + "=========================================\n"); + } else { + printf("Specific Anomaly Report\n"); + printf("=======================\n"); + } + break; + case RPT_RESPONSE: + if (report_detail == D_DETAILED) { + printf("Response to Anomaly Report\n"); + printf("==============================\n"); + printf("# date time type success event\n"); + printf("==============================\n"); + } else { + printf("Specific Response to Anomaly Report\n"); + printf("===================================\n"); + } + break; + case RPT_MAC: + if (report_detail == D_DETAILED) { + printf("MAC Report\n"); + printf("===================================\n"); + printf("# date time auid type success event\n"); + printf("===================================\n"); + } else { + printf("Specific Mandatory Access Control (MAC) Report\n"); + printf("===================================\n"); + } + break; + case RPT_INTEG: + if (report_detail == D_DETAILED) { + printf("Integrity Report\n"); + printf("==============================\n"); + printf("# date time type success event\n"); + printf("==============================\n"); + } else { + printf("Specific Integrity Report\n"); + printf("==============================\n"); + } + break; + case RPT_VIRT: + if (report_detail == D_DETAILED) { + printf("Virtualization Report\n"); + printf("==============================\n"); + printf("# date time type success event\n"); + printf("==============================\n"); + } else { + printf("Specific Virtualization Report\n"); + printf("==============================\n"); + } + break; + case RPT_CRYPTO: + if (report_detail == D_DETAILED) { + printf("Crypto Report\n"); + printf("===================================\n"); + printf("# date time auid type success event\n"); + printf("===================================\n"); + } else { + printf("Specific Crypto Report\n"); + printf("===================================\n"); + } + break; + case RPT_KEY: + if (report_detail == D_DETAILED) { + printf("Key Report\n"); + printf( + "===============================================\n"); + printf( + "# date time key success exe auid event\n"); + printf( + "===============================================\n"); + } else { + printf("Specific Key Report\n"); + printf("====================\n"); + } + break; + case RPT_TTY: + if (report_detail == D_DETAILED) { + printf("TTY Report\n"); + printf( + "===============================================\n"); + printf( + "# date time event auid term sess comm data\n"); + printf( + "===============================================\n"); + } else { + printf("Specific TTY Report\n"); + printf("====================\n"); + } + break; + default: + break; + } +} + +void print_per_event_item(llist *l) +{ + char buf[128]; + char name[64]; + char date[32]; + struct tm *tv; + + // The beginning is common to all reports + tv = localtime(&l->e.sec); + strftime(date, sizeof(date), "%x %T", tv); + if (report_type != RPT_AVC) { + line_item++; + printf("%u. %s ", line_item, date); + } + + switch (report_type) + { + case RPT_AVC: + alist_find_avc(l->s.avc); + do { + anode *an = l->s.avc->cur; + line_item++; + printf("%u. %s ", line_item, date); + // command subject syscall action obj res event + safe_print_string(l->s.comm ? l->s.comm : "?", 0); + printf(" %s %s %s %s %s %s %lu\n", + an->scontext, + aulookup_syscall(l, buf,sizeof(buf)), + an->avc_class, an->avc_perm, + an->tcontext, aulookup_result(an->avc_result), + l->e.serial); +//printf("items:%d\n", l->s.avc->cnt); + } while (alist_next_avc(l->s.avc)); + break; + case RPT_CONFIG: + // FIXME:who, action, what, outcome, event + // NOW: type auid success event + printf("%s %s %s %lu\n", + audit_msg_type_to_name(l->head->type), + aulookup_uid(l->s.loginuid, name, sizeof(name)), + aulookup_success(l->s.success), l->e.serial); + break; + case RPT_AUTH: + // who, addr, terminal, exe, success, event + // Special note...uid is used here because that is + // the way that the message works. This is because + // on failed logins, loginuid is not set. + safe_print_string(l->s.acct ? l->s.acct : + aulookup_uid(l->s.uid, name, sizeof(name)), 0); + printf(" %s %s %s %s %lu\n", + l->s.hostname, l->s.terminal, + l->s.exe, aulookup_success(l->s.success), + l->e.serial); + break; + case RPT_LOGIN: + // who, addr, terminal, exe, success, event + // Special note...uid is used here because that is + // the way that the message works. This is because + // on failed logins, loginuid is not set. + safe_print_string(((l->s.success == S_FAILED) && + l->s.acct) ? l->s.acct : + aulookup_uid(l->s.uid, name, sizeof(name)), 0); + printf(" %s %s %s %s %lu\n", + l->s.hostname, l->s.terminal, + l->s.exe, aulookup_success(l->s.success), + l->e.serial); + break; + case RPT_ACCT_MOD: + // who, addr, terminal, exe, success, event + safe_print_string( + aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %s %s %s %s %s %lu\n", + l->s.hostname ? l->s.hostname : "?", + l->s.terminal ? l->s.terminal : "?", + l->s.exe ? l->s.exe : "?", + l->s.acct ? l->s.acct : "?", + aulookup_success(l->s.success), + l->e.serial); + break; + case RPT_EVENT: // report_detail == D_DETAILED + // event, type, who, success + printf("%lu %s ", + l->e.serial, + audit_msg_type_to_name(l->head->type)); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %s\n", aulookup_success(l->s.success)); + break; + case RPT_FILE: // report_detail == D_DETAILED + // file, syscall, success, exe, who, event + slist_first(l->s.filename); + safe_print_string(l->s.filename->cur->str,0); + printf(" %s %s ", + aulookup_syscall(l,buf,sizeof(buf)), + aulookup_success(l->s.success)); + safe_print_string(l->s.exe ? l->s.exe : "?", 0); + putchar(' '); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_HOST: // report_detail == D_DETAILED + // host, syscall, who, event + printf("%s %s ", + l->s.hostname, + aulookup_syscall(l,buf,sizeof(buf))); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_PID: // report_detail == D_DETAILED + // pid, exe, syscall, who, event + printf("%u ", l->s.pid); + safe_print_string(l->s.exe ? l->s.exe : "?", 0); + printf(" %s ", aulookup_syscall(l,buf,sizeof(buf))); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_SYSCALL: // report_detail == D_DETAILED + // syscall, pid, comm, who, event + printf("%s %u ", aulookup_syscall(l,buf,sizeof(buf)), + l->s.pid); + safe_print_string(l->s.comm ? l->s.comm : "?", 0); + putchar(' '); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_TERM: // report_detail == D_DETAILED + // terminal, host, exe, who, event + printf("%s %s ", + l->s.terminal, l->s.hostname); + safe_print_string(l->s.exe, 0); + putchar(' '); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_USER: // report_detail == D_DETAILED + // who, terminal, host, exe, event + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %s %s ", + l->s.terminal ? l->s.terminal : "?", + l->s.hostname ? l->s.hostname : "?"); + safe_print_string(l->s.exe ? l->s.exe : "?", 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_EXE: // report_detail == D_DETAILED + // exe, terminal, host, who, event + safe_print_string(l->s.exe ? l->s.exe : "?", 0); + printf(" %s %s ", + l->s.terminal ? l->s.terminal : "?", + l->s.hostname ? l->s.hostname : "?"); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_COMM: // report_detail == D_DETAILED + // comm, terminal, host, who, event + safe_print_string(l->s.comm ? l->s.comm : "?", 0); + printf(" %s %s ", + l->s.terminal ? l->s.terminal : "?", + l->s.hostname ? l->s.hostname : "?"); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_ANOMALY: // report_detail == D_DETAILED + // type exe term host auid event + printf("%s ", audit_msg_type_to_name(l->head->type)); + safe_print_string(l->s.exe ? l->s.exe : + l->s.comm ? l->s.comm: "?", 0); + printf(" %s %s ", + l->s.terminal ? l->s.terminal : "?", + l->s.hostname ? l->s.hostname : "?"); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_RESPONSE: // report_detail == D_DETAILED + // type success event + printf("%s %s %lu\n", + audit_msg_type_to_name(l->head->type), + aulookup_success(l->s.success), + l->e.serial); + break; + case RPT_MAC: + // auid type success event + printf("%s %s %s %lu\n", + aulookup_uid(l->s.loginuid, name, sizeof(name)), + audit_msg_type_to_name(l->head->type), + aulookup_success(l->s.success), + l->e.serial); + break; + case RPT_INTEG: + // type success event + printf("%s %s %lu\n", + audit_msg_type_to_name(l->head->type), + aulookup_success(l->s.success), + l->e.serial); + break; + case RPT_VIRT: + // type success event + printf("%s %s %lu\n", + audit_msg_type_to_name(l->head->type), + aulookup_success(l->s.success), + l->e.serial); + break; + case RPT_CRYPTO: + // auid type success event + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %s %s %lu\n", + audit_msg_type_to_name(l->head->type), + aulookup_success(l->s.success), + l->e.serial); + break; + case RPT_KEY: // report_detail == D_DETAILED + // key, success, exe, who, event + slist_first(l->s.key); + printf("%s %s ", l->s.key->cur->str, + aulookup_success(l->s.success)); + safe_print_string(l->s.exe ? l->s.exe : "?", 0); + putchar(' '); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %lu\n", l->e.serial); + break; + case RPT_TTY: { + char *ch, *ptr = strstr(l->head->message, "data="); + if (!ptr) + break; + ptr += 5; + ch = strrchr(ptr, ' '); + if (ch) + *ch = 0; + // event who term sess data + printf("%lu ", l->e.serial); + safe_print_string(aulookup_uid(l->s.loginuid, name, + sizeof(name)), 0); + printf(" %s %u ", + l->s.terminal ? l->s.terminal : "?", + l->s.session_id); + safe_print_string(l->s.comm ? l->s.comm: "?", 0); + putchar(' '); + print_tty_data(ptr); + printf("\n"); + } + break; + default: + break; + } +} + +void print_wrap_up(void) +{ + if (report_detail != D_SUM) + return; + + switch (report_type) + { + case RPT_SUMMARY: + do_summary_output(); + break; + case RPT_AVC: + slist_sort_by_hits(&sd.avc_objs); + do_string_summary_output(&sd.avc_objs); + break; + case RPT_CONFIG: /* We will borrow the pid list */ + ilist_sort_by_hits(&sd.pids); + do_type_summary_output(&sd.pids); + break; + case RPT_AUTH: + slist_sort_by_hits(&sd.users); + do_user_summary_output(&sd.users); + break; + case RPT_LOGIN: + slist_sort_by_hits(&sd.users); + do_user_summary_output(&sd.users); + break; + case RPT_ACCT_MOD: /* We will borrow the pid list */ + ilist_sort_by_hits(&sd.pids); + do_type_summary_output(&sd.pids); + break; + case RPT_EVENT: /* We will borrow the pid list */ + ilist_sort_by_hits(&sd.pids); + do_type_summary_output(&sd.pids); + break; + case RPT_FILE: + slist_sort_by_hits(&sd.files); + do_file_summary_output(&sd.files); + break; + case RPT_HOST: + slist_sort_by_hits(&sd.hosts); + do_string_summary_output(&sd.hosts); + break; + case RPT_PID: + ilist_sort_by_hits(&sd.pids); + do_int_summary_output(&sd.pids); + break; + case RPT_SYSCALL: + ilist_sort_by_hits(&sd.sys_list); + do_syscall_summary_output(&sd.sys_list); + break; + case RPT_TERM: + slist_sort_by_hits(&sd.terms); + do_string_summary_output(&sd.terms); + break; + case RPT_USER: + slist_sort_by_hits(&sd.users); + do_user_summary_output(&sd.users); + break; + case RPT_EXE: + slist_sort_by_hits(&sd.exes); + do_file_summary_output(&sd.exes); + break; + case RPT_COMM: + slist_sort_by_hits(&sd.comms); + do_file_summary_output(&sd.comms); + break; + case RPT_ANOMALY: + ilist_sort_by_hits(&sd.anom_list); + do_type_summary_output(&sd.anom_list); + break; + case RPT_RESPONSE: + ilist_sort_by_hits(&sd.resp_list); + do_type_summary_output(&sd.resp_list); + break; + case RPT_MAC: + ilist_sort_by_hits(&sd.mac_list); + do_type_summary_output(&sd.mac_list); + break; + case RPT_INTEG: + ilist_sort_by_hits(&sd.integ_list); + do_type_summary_output(&sd.integ_list); + break; + case RPT_VIRT: + ilist_sort_by_hits(&sd.virt_list); + do_type_summary_output(&sd.virt_list); + break; + case RPT_CRYPTO: + ilist_sort_by_hits(&sd.crypto_list); + do_type_summary_output(&sd.crypto_list); + break; + case RPT_KEY: + slist_sort_by_hits(&sd.keys); + do_file_summary_output(&sd.keys); + break; + default: + break; + } +} + +static void do_summary_output(void) +{ + extern event very_first_event; + extern event very_last_event; + + printf("Range of time in logs: "); + { + struct tm *btm; + char tmp[48]; + + btm = localtime(&very_first_event.sec); + strftime(tmp, sizeof(tmp), "%x %T", btm); + printf("%s.%03d - ", tmp, very_first_event.milli); + btm = localtime(&very_last_event.sec); + strftime(tmp, sizeof(tmp), "%x %T", btm); + printf("%s.%03d\n", tmp, very_last_event.milli); + } + printf("Selected time for report: "); + { + struct tm *btm; + char tmp[48]; + + if (start_time) + btm = localtime(&start_time); + else + btm = localtime(&very_first_event.sec); + strftime(tmp, sizeof(tmp), "%x %T", btm); + printf("%s - ", tmp); + if (end_time) + btm = localtime(&end_time); + else + btm = localtime(&very_last_event.sec); + strftime(tmp, sizeof(tmp), "%x %T", btm); + if (end_time) + printf("%s\n", tmp); + else + printf("%s.%03d\n", tmp, very_last_event.milli); + } + printf("Number of changes in configuration: %lu\n", sd.changes); + printf("Number of changes to accounts, groups, or roles: %lu\n", + sd.acct_changes); + printf("Number of logins: %lu\n", sd.good_logins); + printf("Number of failed logins: %lu\n", sd.bad_logins); + printf("Number of authentications: %lu\n", sd.good_auth); + printf("Number of failed authentications: %lu\n", sd.bad_auth); + printf("Number of users: %u\n", sd.users.cnt); + printf("Number of terminals: %u\n", sd.terms.cnt); + printf("Number of host names: %u\n", sd.hosts.cnt); + printf("Number of executables: %u\n", sd.exes.cnt); + printf("Number of commands: %u\n", sd.comms.cnt); + printf("Number of files: %u\n", sd.files.cnt); + printf("Number of AVC's: %lu\n", sd.avcs); + printf("Number of MAC events: %lu\n", sd.mac); + printf("Number of failed syscalls: %lu\n", sd.failed_syscalls); + printf("Number of anomaly events: %lu\n", sd.anomalies); + printf("Number of responses to anomaly events: %lu\n", sd.responses); + printf("Number of crypto events: %lu\n", sd.crypto); + printf("Number of integrity events: %lu\n", sd.integ); + printf("Number of virt events: %lu\n", sd.virt); + printf("Number of keys: %u\n", sd.keys.cnt); + printf("Number of process IDs: %u\n", sd.pids.cnt); + printf("Number of events: %lu\n", sd.events); + printf("\n"); +} + +static void do_file_summary_output(slist *sptr) +{ + const snode *sn; + + if (sptr->cnt == 0) { + printf("<no events of interest were found>\n\n"); + return; + } + slist_first(sptr); + sn=slist_get_cur(sptr); + while (sn) { + printf("%u ", sn->hits); + safe_print_string(sn->str, 1); + sn=slist_next(sptr); + } +} + +static void do_string_summary_output(slist *sptr) +{ + const snode *sn; + + if (sptr->cnt == 0) { + printf("<no events of interest were found>\n\n"); + return; + } + slist_first(sptr); + sn=slist_get_cur(sptr); + while (sn) { + printf("%u %s\n", sn->hits, sn->str); + sn=slist_next(sptr); + } +} + +static void do_user_summary_output(slist *sptr) +{ + const snode *sn; + + if (sptr->cnt == 0) { + printf("<no events of interest were found>\n\n"); + return; + } + slist_first(sptr); + sn=slist_get_cur(sptr); + while (sn) { + long uid; + char name[64]; + + if (sn->str[0] == '-' || isdigit(sn->str[0])) { + uid = strtol(sn->str, NULL, 10); + printf("%u ", sn->hits); + safe_print_string(aulookup_uid(uid, name, + sizeof(name)), 1); + } else { + printf("%u ", sn->hits); + safe_print_string(sn->str, 1); + } + sn=slist_next(sptr); + } +} + +static void do_int_summary_output(ilist *sptr) +{ + const int_node *in; + + if (sptr->cnt == 0) { + printf("<no events of interest were found>\n\n"); + return; + } + ilist_first(sptr); + in=ilist_get_cur(sptr); + while (in) { + printf("%u %d\n", in->hits, in->num); + in=ilist_next(sptr); + } +} + +static void do_syscall_summary_output(ilist *sptr) +{ + const int_node *in; + + if (sptr->cnt == 0) { + printf("<no events of interest were found>\n\n"); + return; + } + ilist_first(sptr); + in=ilist_get_cur(sptr); + while (in) { + const char *sys = NULL; + int machine = audit_elf_to_machine(in->aux1); + if (machine >= 0) + sys = audit_syscall_to_name(in->num, machine); + if (sys) + printf("%u %s\n", in->hits, sys); + else + printf("%u %d\n", in->hits, in->num); + in=ilist_next(sptr); + } +} + +static void do_type_summary_output(ilist *sptr) +{ + const int_node *in; + + if (sptr->cnt == 0) { + printf("<no events of interest were found>\n\n"); + return; + } + ilist_first(sptr); + in=ilist_get_cur(sptr); + while (in) { + const char *name = audit_msg_type_to_name(in->num); + if (report_format == RPT_DEFAULT) + printf("%u %d\n", in->hits, in->num); + else + printf("%u %s\n", in->hits, name); + in=ilist_next(sptr); + } +} + diff --git a/framework/src/audit/src/aureport-scan.c b/framework/src/audit/src/aureport-scan.c new file mode 100644 index 00000000..6b2f5ee6 --- /dev/null +++ b/framework/src/audit/src/aureport-scan.c @@ -0,0 +1,974 @@ +/* +* aureport-scan.c - Extract interesting fields and check for match +* Copyright (c) 2005-06,2008,2011,2014-15 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include "config.h" +#include <string.h> +#include <stdio.h> +#include <pwd.h> +#include "libaudit.h" +#include "aureport-options.h" +#include "ausearch-parse.h" +#include "ausearch-string.h" +#include "ausearch-lookup.h" +#include "aureport-scan.h" + +static void do_summary_total(llist *l); +static int per_event_summary(llist *l); +static int per_event_detailed(llist *l); + +summary_data sd; + +/* This function inits the counters */ +void reset_counters(void) +{ + sd.changes = 0UL; + sd.crypto = 0UL; + sd.acct_changes = 0UL; + sd.good_logins = 0UL; + sd.bad_logins = 0UL; + sd.good_auth = 0UL; + sd.bad_auth = 0UL; + sd.events = 0UL; + sd.avcs = 0UL; + sd.mac = 0UL; + sd.failed_syscalls = 0UL; + sd.anomalies = 0UL; + sd.responses = 0UL; + sd.virt = 0UL; + sd.integ = 0UL; + slist_create(&sd.users); + slist_create(&sd.terms); + slist_create(&sd.files); + slist_create(&sd.hosts); + slist_create(&sd.exes); + slist_create(&sd.comms); + slist_create(&sd.avc_objs); + slist_create(&sd.keys); + ilist_create(&sd.pids); + ilist_create(&sd.sys_list); + ilist_create(&sd.anom_list); + ilist_create(&sd.mac_list); + ilist_create(&sd.resp_list); + ilist_create(&sd.crypto_list); + ilist_create(&sd.virt_list); + ilist_create(&sd.integ_list); +} + +/* This function inits the counters */ +void destroy_counters(void) +{ + sd.changes = 0UL; + sd.crypto = 0UL; + sd.acct_changes = 0UL; + sd.good_logins = 0UL; + sd.bad_logins = 0UL; + sd.good_auth = 0UL; + sd.bad_auth = 0UL; + sd.events = 0UL; + sd.avcs = 0UL; + sd.mac = 0UL; + sd.failed_syscalls = 0UL; + sd.anomalies = 0UL; + sd.responses = 0UL; + sd.virt = 0UL; + sd.integ = 0UL; + slist_clear(&sd.users); + slist_clear(&sd.terms); + slist_clear(&sd.files); + slist_clear(&sd.hosts); + slist_clear(&sd.exes); + slist_clear(&sd.comms); + slist_clear(&sd.avc_objs); + slist_clear(&sd.keys); + ilist_clear(&sd.pids); + ilist_clear(&sd.sys_list); + ilist_clear(&sd.anom_list); + ilist_create(&sd.mac_list); + ilist_clear(&sd.resp_list); + ilist_create(&sd.crypto_list); + ilist_create(&sd.virt_list); + ilist_create(&sd.integ_list); +} + +/* This function will return 0 on no match and 1 on match */ +int classify_success(const llist *l) +{ +//printf("%d,succ=%d:%d\n", l->head->type, event_failed, l->s.success); + // If match only failed... + if (event_failed == F_FAILED) + return l->s.success == S_FAILED ? 1 : 0; + // If match only success... + if (event_failed == F_SUCCESS) + return l->s.success == S_SUCCESS ? 1 : 0; + // Otherwise...we don't care so pretend it matched + return 1; +} + +/* This function will return 0 on no match and 1 on match */ +int classify_conf(const llist *l) +{ + int rc = 1; + extern int no_config; + + switch (l->head->type) + { + case AUDIT_CONFIG_CHANGE: + if (no_config) + rc = 0; + break; + case AUDIT_USYS_CONFIG: + break; + case AUDIT_ADD_USER: + if (event_conf_act == C_DEL) + rc = 0; + break; + case AUDIT_DEL_USER: + if (event_conf_act == C_ADD) + rc = 0; + break; + case AUDIT_ADD_GROUP: + if (event_conf_act == C_DEL) + rc = 0; + break; + case AUDIT_DEL_GROUP: + if (event_conf_act == C_ADD) + rc = 0; + break; + case AUDIT_MAC_CIPSOV4_ADD: + if (event_conf_act == C_DEL) + rc = 0; + break; + case AUDIT_MAC_CIPSOV4_DEL: + if (event_conf_act == C_ADD) + rc = 0; + break; + case AUDIT_MAC_MAP_ADD: + if (event_conf_act == C_DEL) + rc = 0; + break; + case AUDIT_MAC_MAP_DEL: + if (event_conf_act == C_ADD) + rc = 0; + break; + case AUDIT_MAC_IPSEC_ADDSA: + if (event_conf_act == C_DEL) + rc = 0; + break; + case AUDIT_MAC_IPSEC_DELSA: + if (event_conf_act == C_ADD) + rc = 0; + break; + case AUDIT_MAC_IPSEC_ADDSPD: + if (event_conf_act == C_DEL) + rc = 0; + break; + case AUDIT_MAC_IPSEC_DELSPD: + if (event_conf_act == C_ADD) + rc = 0; + break; + case AUDIT_MAC_UNLBL_STCADD: + if (event_conf_act == C_DEL) + rc = 0; + break; + case AUDIT_MAC_UNLBL_STCDEL: + if (event_conf_act == C_ADD) + rc = 0; + break; + default: + break; + } +//printf("conf=%d:%d\n", l->head->type, rc); + return rc; +} + +/* + * This function performs that matching of search params with the record. + * It returns 1 on a match, and 0 if no match. + */ +int scan(llist *l) +{ + // Are we within time range? + if (start_time == 0 || l->e.sec >= start_time) { + if (end_time == 0 || l->e.sec <= end_time) { + // OK - do the heavier checking + int rc = extract_search_items(l); + if (rc == 0) { + if (event_node_list) { + const snode *sn; + int found=0; + slist *sptr = event_node_list; + + if (l->e.node == NULL) + return 0; + + slist_first(sptr); + sn=slist_get_cur(sptr); + while (sn && !found) { + if (sn->str && (!strcmp(sn->str, l->e.node))) + found++; + else + sn=slist_next(sptr); + } + + if (!found) + return 0; + } + if (classify_success(l) && classify_conf(l)) + return 1; + return 0; + } + } + } + return 0; +} + +int per_event_processing(llist *l) +{ + int rc; + + switch (report_detail) + { + case D_SUM: + rc = per_event_summary(l); + break; + case D_DETAILED: + rc = per_event_detailed(l); + break; + case D_SPECIFIC: + default: + rc = 0; + break; + } + return rc; +} + +static int per_event_summary(llist *l) +{ + int rc = 0; + + switch (report_type) + { + case RPT_SUMMARY: + do_summary_total(l); + rc = 1; + break; + case RPT_AVC: + if (list_find_msg(l, AUDIT_AVC)) { + if (alist_find_avc(l->s.avc)) { + do { + slist_add_if_uniq(&sd.avc_objs, + l->s.avc->cur->tcontext); + } while (alist_next_avc(l->s.avc)); + } + } else { + if (list_find_msg(l, AUDIT_USER_AVC)) { + if (alist_find_avc(l->s.avc)) { + do { + slist_add_if_uniq( + &sd.avc_objs, + l->s.avc->cur->tcontext); + } while (alist_next_avc( + l->s.avc)); + } + } + } + break; + case RPT_MAC: + if (list_find_msg_range(l, AUDIT_MAC_POLICY_LOAD, + AUDIT_MAC_MAP_DEL)) { + ilist_add_if_uniq(&sd.mac_list, + l->head->type, 0); + } else { + if (list_find_msg_range(l, + AUDIT_FIRST_USER_LSPP_MSG, + AUDIT_LAST_USER_LSPP_MSG)) { + ilist_add_if_uniq(&sd.mac_list, + l->head->type, 0); + } + } + break; + case RPT_INTEG: + if (list_find_msg_range(l, + AUDIT_INTEGRITY_FIRST_MSG, + AUDIT_INTEGRITY_LAST_MSG)) { + ilist_add_if_uniq(&sd.integ_list, + l->head->type, 0); + } + break; + case RPT_VIRT: + if (list_find_msg_range(l, + AUDIT_FIRST_VIRT_MSG, + AUDIT_LAST_VIRT_MSG)) { + ilist_add_if_uniq(&sd.virt_list, + l->head->type, 0); + } + break; + case RPT_CONFIG: /* We will borrow the pid list */ + if (list_find_msg(l, AUDIT_CONFIG_CHANGE) || + list_find_msg(l, AUDIT_DAEMON_CONFIG) || + list_find_msg(l, AUDIT_USYS_CONFIG) || + list_find_msg(l, AUDIT_NETFILTER_CFG) || + list_find_msg(l, AUDIT_FEATURE_CHANGE) || + list_find_msg(l, AUDIT_USER_MAC_CONFIG_CHANGE)|| + list_find_msg_range(l, + AUDIT_MAC_POLICY_LOAD, + AUDIT_MAC_UNLBL_STCDEL)) { + ilist_add_if_uniq(&sd.pids, l->head->type, 0); + } + break; + case RPT_AUTH: + if (list_find_msg(l, AUDIT_USER_AUTH)) { + if (l->s.loginuid == -2 && l->s.acct) + slist_add_if_uniq(&sd.users, l->s.acct); + else { + char name[64]; + + slist_add_if_uniq(&sd.users, + aulookup_uid(l->s.loginuid, + name, + sizeof(name)) + ); + } + } else if (list_find_msg(l, AUDIT_USER_MGMT)) { + // Only count the failures + if (l->s.success == S_FAILED) { + if (l->s.loginuid == -2 && + l->s.acct != NULL) + slist_add_if_uniq(&sd.users, l->s.acct); + else { + char name[64]; + + slist_add_if_uniq(&sd.users, + aulookup_uid( + l->s.loginuid, + name, + sizeof(name)) + ); + } + } + } + break; + case RPT_LOGIN: + if (list_find_msg(l, AUDIT_USER_LOGIN)) { + if ((int)l->s.loginuid < 0 && l->s.acct) + slist_add_if_uniq(&sd.users, l->s.acct); + else { + char name[64]; + + slist_add_if_uniq(&sd.users, + aulookup_uid(l->s.loginuid, + name, + sizeof(name)) + ); + } + } + break; + case RPT_ACCT_MOD: /* We will borrow the pid list */ + if (list_find_msg(l, AUDIT_USER_CHAUTHTOK) || + list_find_msg_range(l, + AUDIT_ADD_USER, AUDIT_DEL_GROUP) || + list_find_msg(l, AUDIT_USER_MGMT) || + list_find_msg(l, AUDIT_GRP_MGMT) || + list_find_msg_range(l, + AUDIT_ROLE_ASSIGN, + AUDIT_ROLE_REMOVE)) { + ilist_add_if_uniq(&sd.pids, l->head->type, 0); + } + break; + case RPT_EVENT: /* We will borrow the pid list */ + if (l->head->type != -1) { + ilist_add_if_uniq(&sd.pids, l->head->type, 0); + } + break; + case RPT_FILE: + if (l->s.filename) { + const snode *sn; + slist *sptr = l->s.filename; + + slist_first(sptr); + sn=slist_get_cur(sptr); + while (sn) { + if (sn->str) + slist_add_if_uniq(&sd.files, + sn->str); + sn=slist_next(sptr); + } + } + break; + case RPT_HOST: + if (l->s.hostname) + slist_add_if_uniq(&sd.hosts, l->s.hostname); + break; + case RPT_PID: + if (l->s.pid != -1) { + ilist_add_if_uniq(&sd.pids, l->s.pid, 0); + } + break; + case RPT_SYSCALL: + if (l->s.syscall > 0) { + ilist_add_if_uniq(&sd.sys_list, + l->s.syscall, l->s.arch); + } + break; + case RPT_TERM: + if (l->s.terminal) + slist_add_if_uniq(&sd.terms, l->s.terminal); + break; + case RPT_USER: + if (l->s.loginuid != -2) { + char tmp[32]; + snprintf(tmp, sizeof(tmp), "%d", l->s.loginuid); + slist_add_if_uniq(&sd.users, tmp); + } + break; + case RPT_EXE: + if (l->s.exe) + slist_add_if_uniq(&sd.exes, l->s.exe); + break; + case RPT_COMM: + if (l->s.comm) + slist_add_if_uniq(&sd.comms, l->s.comm); + break; + case RPT_ANOMALY: + if (list_find_msg_range(l, AUDIT_FIRST_ANOM_MSG, + AUDIT_LAST_ANOM_MSG)) { + ilist_add_if_uniq(&sd.anom_list, + l->head->type, 0); + } else { + if (list_find_msg_range(l, + AUDIT_FIRST_KERN_ANOM_MSG, + AUDIT_LAST_KERN_ANOM_MSG)) { + ilist_add_if_uniq(&sd.anom_list, + l->head->type, 0); + } + } + break; + case RPT_RESPONSE: + if (list_find_msg_range(l, AUDIT_FIRST_ANOM_RESP, + AUDIT_LAST_ANOM_RESP)) { + ilist_add_if_uniq(&sd.resp_list, + l->head->type, 0); + } + break; + case RPT_CRYPTO: + if (list_find_msg_range(l, AUDIT_FIRST_KERN_CRYPTO_MSG, + AUDIT_LAST_KERN_CRYPTO_MSG)) { + ilist_add_if_uniq(&sd.crypto_list, + l->head->type, 0); + } else { + if (list_find_msg_range(l, + AUDIT_FIRST_CRYPTO_MSG, + AUDIT_LAST_CRYPTO_MSG)) { + ilist_add_if_uniq(&sd.crypto_list, + l->head->type, 0); + } + } + break; + case RPT_KEY: + if (l->s.key) { + const snode *sn; + slist *sptr = l->s.key; + + slist_first(sptr); + sn=slist_get_cur(sptr); + while (sn) { + if (sn->str && + strcmp(sn->str, "(null)")) + slist_add_if_uniq(&sd.keys, + sn->str); + sn=slist_next(sptr); + } + } + break; + case RPT_TTY: + UNIMPLEMENTED; + break; + default: + break; + } + return rc; +} + +static int per_event_detailed(llist *l) +{ + int rc = 0; + + switch (report_type) + { + case RPT_AVC: + if (list_find_msg(l, AUDIT_AVC)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg(l, AUDIT_USER_AVC)) { + print_per_event_item(l); + rc = 1; + } + break; + case RPT_MAC: + if (report_detail == D_DETAILED) { + if (list_find_msg_range(l, + AUDIT_MAC_POLICY_LOAD, + AUDIT_MAC_UNLBL_STCDEL)) { + print_per_event_item(l); + rc = 1; + } else { + if (list_find_msg_range(l, + AUDIT_FIRST_USER_LSPP_MSG, + AUDIT_LAST_USER_LSPP_MSG)) { + print_per_event_item(l); + rc = 1; + } + } + } + break; + case RPT_INTEG: + if (report_detail == D_DETAILED) { + if (list_find_msg_range(l, + AUDIT_INTEGRITY_FIRST_MSG, + AUDIT_INTEGRITY_LAST_MSG)) { + print_per_event_item(l); + rc = 1; + } + } + break; + case RPT_VIRT: + if (report_detail == D_DETAILED) { + if (list_find_msg_range(l, + AUDIT_FIRST_VIRT_MSG, + AUDIT_LAST_VIRT_MSG)) { + print_per_event_item(l); + rc = 1; + } + } + break; + case RPT_CONFIG: + if (list_find_msg(l, AUDIT_CONFIG_CHANGE)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg(l, AUDIT_DAEMON_CONFIG)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg(l, AUDIT_USYS_CONFIG)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg(l, AUDIT_NETFILTER_CFG)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg(l, AUDIT_FEATURE_CHANGE)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg(l, + AUDIT_USER_MAC_CONFIG_CHANGE)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg_range(l, + AUDIT_MAC_POLICY_LOAD, + AUDIT_MAC_UNLBL_STCDEL)) { + print_per_event_item(l); + rc = 1; + } + break; + case RPT_AUTH: + if (list_find_msg(l, AUDIT_USER_AUTH)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg(l, AUDIT_USER_MGMT)) { + // Only count the failed acct + if (l->s.success == S_FAILED) { + print_per_event_item(l); + rc = 1; + } + } + break; + case RPT_LOGIN: + if (list_find_msg(l, AUDIT_USER_LOGIN)) { + print_per_event_item(l); + rc = 1; + } + break; + case RPT_ACCT_MOD: + if (list_find_msg(l, AUDIT_USER_CHAUTHTOK)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg_range(l, + AUDIT_ADD_USER, AUDIT_DEL_GROUP)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg(l, AUDIT_USER_MGMT)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg(l, AUDIT_GRP_MGMT)) { + print_per_event_item(l); + rc = 1; + } else if (list_find_msg_range(l, + AUDIT_ROLE_ASSIGN, + AUDIT_ROLE_REMOVE)) { + print_per_event_item(l); + rc = 1; + } + break; + case RPT_EVENT: + list_first(l); + if (report_detail == D_DETAILED) { + print_per_event_item(l); + rc = 1; + } else { // specific event report + UNIMPLEMENTED; + } + break; + case RPT_FILE: + list_first(l); + if (report_detail == D_DETAILED) { + if (l->s.filename) { + print_per_event_item(l); + rc = 1; + } + } else { // specific file report + UNIMPLEMENTED; + } + break; + case RPT_HOST: + list_first(l); + if (report_detail == D_DETAILED) { + if (l->s.hostname) { + print_per_event_item(l); + rc = 1; + } + } else { // specific host report + UNIMPLEMENTED; + } + break; + case RPT_PID: + list_first(l); + if (report_detail == D_DETAILED) { + if (l->s.pid >= 0) { + print_per_event_item(l); + rc = 1; + } + } else { // specific pid report + UNIMPLEMENTED; + } + break; + case RPT_SYSCALL: + list_first(l); + if (report_detail == D_DETAILED) { + if (l->s.syscall) { + print_per_event_item(l); + rc = 1; + } + } else { // specific syscall report + UNIMPLEMENTED; + } + break; + case RPT_TERM: + list_first(l); + if (report_detail == D_DETAILED) { + if (l->s.terminal) { + print_per_event_item(l); + rc = 1; + } + } else { // specific terminal report + UNIMPLEMENTED; + } + break; + case RPT_USER: + list_first(l); + if (report_detail == D_DETAILED) { + if (l->s.uid != -1) { + print_per_event_item(l); + rc = 1; + } + } else { // specific user report + UNIMPLEMENTED; + } + break; + case RPT_EXE: + list_first(l); + if (report_detail == D_DETAILED) { + if (l->s.exe) { + print_per_event_item(l); + rc = 1; + } + } else { // specific exe report + UNIMPLEMENTED; + } + break; + case RPT_COMM: + list_first(l); + if (report_detail == D_DETAILED) { + if (l->s.comm) { + print_per_event_item(l); + rc = 1; + } + } else { // specific exe report + UNIMPLEMENTED; + } + break; + case RPT_ANOMALY: + if (report_detail == D_DETAILED) { + if (list_find_msg_range(l, + AUDIT_FIRST_ANOM_MSG, + AUDIT_LAST_ANOM_MSG)) { + print_per_event_item(l); + rc = 1; + } else { + if (list_find_msg_range(l, + AUDIT_FIRST_KERN_ANOM_MSG, + AUDIT_LAST_KERN_ANOM_MSG)) { + print_per_event_item(l); + rc = 1; + } + } + } else { // FIXME: specific anom report + UNIMPLEMENTED; + } + break; + case RPT_RESPONSE: + if (report_detail == D_DETAILED) { + if (list_find_msg_range(l, + AUDIT_FIRST_ANOM_RESP, + AUDIT_LAST_ANOM_RESP)) { + print_per_event_item(l); + rc = 1; + } + } else { // FIXME: specific resp report + UNIMPLEMENTED; + } + break; + case RPT_CRYPTO: + if (report_detail == D_DETAILED) { + if (list_find_msg_range(l, + AUDIT_FIRST_KERN_CRYPTO_MSG, + AUDIT_LAST_KERN_CRYPTO_MSG)) { + print_per_event_item(l); + rc = 1; + } else { + if (list_find_msg_range(l, + AUDIT_FIRST_CRYPTO_MSG, + AUDIT_LAST_CRYPTO_MSG)) { + print_per_event_item(l); + rc = 1; + } + } + } else { // FIXME: specific crypto report + UNIMPLEMENTED; + } + break; + case RPT_KEY: + list_first(l); + if (report_detail == D_DETAILED) { + if (l->s.key) { + slist_first(l->s.key); + if (strcmp(l->s.key->cur->str, + "(null)")) { + print_per_event_item(l); + rc = 1; + } + } + } else { // specific key report + UNIMPLEMENTED; + } + break; + case RPT_TTY: + if (l->head->type == AUDIT_TTY || + l->head->type == AUDIT_USER_TTY) { + print_per_event_item(l); + rc = 1; + } + break; + default: + break; + } + return rc; +} + +static void do_summary_total(llist *l) +{ + // add events + sd.events++; + + // add config changes + if (list_find_msg(l, AUDIT_CONFIG_CHANGE)) + sd.changes++; + if (list_find_msg(l, AUDIT_DAEMON_CONFIG)) + sd.changes++; + if (list_find_msg(l, AUDIT_USYS_CONFIG)) + sd.changes++; + if (list_find_msg(l, AUDIT_NETFILTER_CFG)) + sd.changes++; + if (list_find_msg(l, AUDIT_FEATURE_CHANGE)) + sd.changes++; + if (list_find_msg(l, AUDIT_USER_MAC_CONFIG_CHANGE)) + sd.changes++; + list_first(l); + if (list_find_msg_range(l, AUDIT_MAC_POLICY_LOAD, + AUDIT_MAC_UNLBL_STCDEL)) + sd.changes++; + + // add acct changes + if (list_find_msg(l, AUDIT_USER_CHAUTHTOK)) + sd.acct_changes++; + if (list_find_msg_range(l, AUDIT_ADD_USER, AUDIT_DEL_GROUP)) + sd.acct_changes++; + if (list_find_msg(l, AUDIT_USER_MGMT)) + sd.acct_changes++; + if (list_find_msg(l, AUDIT_GRP_MGMT)) + sd.acct_changes++; + list_first(l); + if (list_find_msg_range(l, AUDIT_ROLE_ASSIGN, AUDIT_ROLE_REMOVE)) + sd.acct_changes++; + + // Crypto + list_first(l); + if (list_find_msg_range(l, AUDIT_FIRST_KERN_CRYPTO_MSG, + AUDIT_LAST_KERN_CRYPTO_MSG)) + sd.crypto++; + if (list_find_msg_range(l, AUDIT_FIRST_CRYPTO_MSG, + AUDIT_LAST_CRYPTO_MSG)) + sd.crypto++; + + // add logins + if (list_find_msg(l, AUDIT_USER_LOGIN)) { + if (l->s.success == S_SUCCESS) + sd.good_logins++; + else if (l->s.success == S_FAILED) + sd.bad_logins++; + } + + // add use of auth + if (list_find_msg(l, AUDIT_USER_AUTH)) { + if (l->s.success == S_SUCCESS) + sd.good_auth++; + else if (l->s.success == S_FAILED) + sd.bad_auth++; + } else if (list_find_msg(l, AUDIT_USER_MGMT)) { + // Only count the failures + if (l->s.success == S_FAILED) + sd.bad_auth++; + } else if (list_find_msg(l, AUDIT_GRP_AUTH)) { + if (l->s.success == S_SUCCESS) + sd.good_auth++; + else if (l->s.success == S_FAILED) + sd.bad_auth++; + } + + // add users + if (l->s.loginuid != -2) { + char tmp[32]; + snprintf(tmp, sizeof(tmp), "%d", l->s.loginuid); + slist_add_if_uniq(&sd.users, tmp); + } + + // add terminals + if (l->s.terminal) + slist_add_if_uniq(&sd.terms, l->s.terminal); + + // add hosts + if (l->s.hostname) + slist_add_if_uniq(&sd.hosts, l->s.hostname); + + // add execs + if (l->s.exe) + slist_add_if_uniq(&sd.exes, l->s.exe); + + // add comms + if (l->s.comm) + slist_add_if_uniq(&sd.comms, l->s.comm); + + // add files + if (l->s.filename) { + const snode *sn; + slist *sptr = l->s.filename; + + slist_first(sptr); + sn=slist_get_cur(sptr); + while (sn) { + if (sn->str) + slist_add_if_uniq(&sd.files, sn->str); + sn=slist_next(sptr); + } + } + + // add avcs + if (list_find_msg(l, AUDIT_AVC)) + sd.avcs++; + else if (list_find_msg(l, AUDIT_USER_AVC)) + sd.avcs++; + + // MAC + list_first(l); + if (list_find_msg_range(l, AUDIT_MAC_POLICY_LOAD, + AUDIT_MAC_UNLBL_STCDEL)) + sd.mac++; + if (list_find_msg_range(l, AUDIT_FIRST_USER_LSPP_MSG, + AUDIT_LAST_USER_LSPP_MSG)) + sd.mac++; + + // Virt + list_first(l); + if (list_find_msg_range(l, AUDIT_FIRST_VIRT_MSG, + AUDIT_LAST_VIRT_MSG)) + sd.virt++; + + // Integrity + list_first(l); + if (list_find_msg_range(l, AUDIT_INTEGRITY_FIRST_MSG, + AUDIT_INTEGRITY_LAST_MSG)) + sd.integ++; + + // add failed syscalls + if (l->s.success == S_FAILED && l->s.syscall > 0) + sd.failed_syscalls++; + + // add pids + if (l->s.pid != -1) { + ilist_add_if_uniq(&sd.pids, l->s.pid, 0); + } + + // add anomalies + if (list_find_msg_range(l, AUDIT_FIRST_ANOM_MSG, AUDIT_LAST_ANOM_MSG)) + sd.anomalies++; + if (list_find_msg_range(l, AUDIT_FIRST_KERN_ANOM_MSG, + AUDIT_LAST_KERN_ANOM_MSG)) + sd.anomalies++; + + // add response to anomalies + if (list_find_msg_range(l, AUDIT_FIRST_ANOM_RESP, AUDIT_LAST_ANOM_RESP)) + sd.responses++; + + // add keys + if (l->s.key) { + const snode *sn; + slist *sptr = l->s.key; + + slist_first(sptr); + sn=slist_get_cur(sptr); + while (sn) { + if (sn->str && strcmp(sn->str, "(null)")) { + slist_add_if_uniq(&sd.keys, sn->str); + } + sn=slist_next(sptr); + } + } +} + diff --git a/framework/src/audit/src/aureport-scan.h b/framework/src/audit/src/aureport-scan.h new file mode 100644 index 00000000..5044d96d --- /dev/null +++ b/framework/src/audit/src/aureport-scan.h @@ -0,0 +1,76 @@ +/* aureport-scan.h -- + * Copyright 2005-06,2008,2014 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#ifndef AUREPORT_SCAN_H +#define AUREPORT_SCAN_H + +#include "ausearch-llist.h" +#include "ausearch-int.h" + +typedef struct sdata { + slist users; + slist terms; + slist files; + slist hosts; + slist exes; + slist comms; + slist avc_objs; + slist keys; + ilist pids; + ilist sys_list; + ilist anom_list; + ilist resp_list; + ilist mac_list; + ilist crypto_list; + ilist virt_list; + ilist integ_list; + unsigned long changes; + unsigned long crypto; + unsigned long acct_changes; + unsigned long good_logins; + unsigned long bad_logins; + unsigned long good_auth; + unsigned long bad_auth; + unsigned long events; + unsigned long avcs; + unsigned long mac; + unsigned long failed_syscalls; + unsigned long anomalies; + unsigned long responses; + unsigned long virt; + unsigned long integ; +} summary_data; + +void reset_counters(void); +void destroy_counters(void); +int scan(llist *l); +int per_event_processing(llist *l); + +void print_title(void); +void print_per_event_item(llist *l); +void print_wrap_up(void); + +extern summary_data sd; + +#endif + diff --git a/framework/src/audit/src/aureport.c b/framework/src/audit/src/aureport.c new file mode 100644 index 00000000..98511e01 --- /dev/null +++ b/framework/src/audit/src/aureport.c @@ -0,0 +1,338 @@ +/* + * aureport.c - main file for aureport utility + * Copyright 2005-08, 2010,11,2013 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + */ + +#include "config.h" +#include <stdio.h> +#include <stdio_ext.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> +#include <errno.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <locale.h> +#include <sys/param.h> +#include "libaudit.h" +#include "auditd-config.h" +#include "aureport-options.h" +#include "aureport-scan.h" +#include "ausearch-lol.h" +#include "ausearch-lookup.h" + + +event very_first_event, very_last_event; +static FILE *log_fd = NULL; +static lol lo; +static int found = 0; +static int files_to_process = 0; // Logs left when processing multiple +static int userfile_is_dir = 0; +static int process_logs(void); +static int process_log_fd(const char *filename); +static int process_stdin(void); +static int process_file(char *filename); +static int get_record(llist **); + +extern char *user_file; +extern int force_logs; + + +static int is_pipe(int fd) +{ + struct stat st; + + if (fstat(fd, &st) == 0) { + if (S_ISFIFO(st.st_mode)) + return 1; + } + return 0; +} + +int main(int argc, char *argv[]) +{ + struct rlimit limit; + int rc; + + /* Check params and build regexpr */ + setlocale (LC_ALL, ""); + if (check_params(argc, argv)) + return 1; + + /* Raise the rlimits in case we're being started from a shell + * with restrictions. Not a fatal error. */ + limit.rlim_cur = RLIM_INFINITY; + limit.rlim_max = RLIM_INFINITY; + setrlimit(RLIMIT_FSIZE, &limit); + setrlimit(RLIMIT_CPU, &limit); + set_aumessage_mode(MSG_STDERR, DBG_NO); + (void) umask( umask( 077 ) | 027 ); + very_first_event.sec = 0; + reset_counters(); + + print_title(); + lol_create(&lo); + if (user_file) { + struct stat sb; + if (stat(user_file, &sb) == -1) { + perror("stat"); + return 1; + } else { + switch (sb.st_mode & S_IFMT) { + case S_IFDIR: + userfile_is_dir = 1; + rc = process_logs(); + break; + case S_IFREG: + default: + rc = process_file(user_file); + break; + } + } + } else if (force_logs) + rc = process_logs(); + else if (is_pipe(0)) + rc = process_stdin(); + else + rc = process_logs(); + lol_clear(&lo); + if (rc) + return rc; + + if (!found && report_detail == D_DETAILED && report_type != RPT_TIME) { + printf("<no events of interest were found>\n\n"); + destroy_counters(); + aulookup_destroy_uid_list(); + aulookup_destroy_gid_list(); + return 1; + } else + print_wrap_up(); + destroy_counters(); + aulookup_destroy_uid_list(); + aulookup_destroy_gid_list(); + free(user_file); + return 0; +} + +static int process_logs(void) +{ + struct daemon_conf config; + char *filename; + int len, num = 0; + + if (user_file && userfile_is_dir) { + char dirname[MAXPATHLEN]; + clear_config (&config); + + strcpy(dirname, user_file); + if (dirname[strlen(dirname)-1] != '/') + strcat(dirname, "/"); + strcat (dirname, "audit.log"); + free((void *)config.log_file); + config.log_file=strdup(dirname); + fprintf(stderr, "NOTE - using logs in %s\n", config.log_file); + } else { + /* Load config so we know where logs are */ + if (load_config(&config, TEST_SEARCH)) + fprintf(stderr, "NOTE - using built-in logs: %s\n", + config.log_file); + } + + /* for each file */ + len = strlen(config.log_file) + 16; + filename = malloc(len); + if (!filename) { + fprintf(stderr, "No memory\n"); + free_config(&config); + return 1; + } + /* Find oldest log file */ + snprintf(filename, len, "%s", config.log_file); + do { + if (access(filename, R_OK) != 0) + break; +// FIXME: do a time check and put them on linked list for later + num++; + snprintf(filename, len, "%s.%d", config.log_file, num); + } while (1); + num--; + /* + * We note how many files we need to process + */ + files_to_process = num; + + /* Got it, now process logs from last to first */ + if (num > 0) + snprintf(filename, len, "%s.%d", config.log_file, num); + else + snprintf(filename, len, "%s", config.log_file); + do { + int ret; + if ((ret = process_file(filename))) { + free(filename); + free_config(&config); + return ret; + } + + /* Get next log file */ + files_to_process--; /* one less file to process */ + num--; + if (num > 0) + snprintf(filename, len, "%s.%d", config.log_file, num); + else if (num == 0) + snprintf(filename, len, "%s", config.log_file); + else + break; + } while (1); + free(filename); + free_config(&config); + return 0; +} + +static int process_log_fd(const char *filename) +{ + llist *entries; // entries in a record + int ret; + int first = 0; + event first_event, last_event; + + last_event.sec = 0; + last_event.milli = 0; + + /* For each record in file */ + do { + ret = get_record(&entries); + if ((ret != 0)||(entries->cnt == 0)) + break; + // If report is RPT_TIME or RPT_SUMMARY, get + if (report_type <= RPT_SUMMARY) { + if (first == 0) { + list_get_event(entries, &first_event); + first = 1; + if (very_first_event.sec == 0) + list_get_event(entries, + &very_first_event); + } + list_get_event(entries, &last_event); + } + if (scan(entries)) { + // This is the per entry action item + if (per_event_processing(entries)) + found = 1; + } + list_clear(entries); + free(entries); + } while (ret == 0); + fclose(log_fd); + // This is the per file action items + very_last_event.sec = last_event.sec; + very_last_event.milli = last_event.milli; + if (report_type == RPT_TIME) { + if (first == 0) { + printf("%s: no records\n", filename); + } else { + struct tm *btm; + char tmp[32]; + + printf("%s: ", filename); + btm = localtime(&first_event.sec); + strftime(tmp, sizeof(tmp), "%x %T", btm); + printf("%s.%03d - ", tmp, first_event.milli); + btm = localtime(&last_event.sec); + strftime(tmp, sizeof(tmp), "%x %T", btm); + printf("%s.%03d\n", tmp, last_event.milli); + } + } + + return 0; +} + +static int process_stdin(void) +{ + log_fd = stdin; + + return process_log_fd("stdin"); +} + +static int process_file(char *filename) +{ + log_fd = fopen(filename, "rm"); + if (log_fd == NULL) { + fprintf(stderr, "Error opening %s (%s)\n", filename, + strerror(errno)); + return 1; + } + + __fsetlocking(log_fd, FSETLOCKING_BYCALLER); + return process_log_fd(filename); +} + +/* + * This function returns a malloc'd buffer of the next record in the audit + * logs. It returns 0 on success, 1 on eof, -1 on error. + */ +static int get_record(llist **l) +{ + char *rc; + char *buff = NULL; + + *l = get_ready_event(&lo); + if (*l) + return 0; + + while (1) { + if (!buff) { + buff = malloc(MAX_AUDIT_MESSAGE_LENGTH); + if (!buff) + return -1; + } + rc = fgets_unlocked(buff, MAX_AUDIT_MESSAGE_LENGTH, + log_fd); + if (rc) { + if (lol_add_record(&lo, buff)) { + *l = get_ready_event(&lo); + if (*l) + break; + } + } else { + free(buff); + if (feof_unlocked(log_fd)) { + // Only mark all events complete if this is + // the last file. + if (files_to_process == 0) { + terminate_all_events(&lo); + } + *l = get_ready_event(&lo); + if (*l) + return 0; + else + return 1; + } else + return -1; + } + } + free(buff); + return 0; +} + diff --git a/framework/src/audit/src/ausearch-avc.c b/framework/src/audit/src/ausearch-avc.c new file mode 100644 index 00000000..2d3b3199 --- /dev/null +++ b/framework/src/audit/src/ausearch-avc.c @@ -0,0 +1,222 @@ +/* +* ausearch-avc.c - Minimal linked list library for avcs +* Copyright (c) 2006,2008,2014 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include "config.h" +#include <stdlib.h> +#include <string.h> +#include "ausearch-avc.h" + + +void alist_create(alist *l) +{ + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + +anode *alist_next(alist *l) +{ + if (l->cur == NULL) + return NULL; + l->cur = l->cur->next; + return l->cur; +} + +static void alist_last(alist *l) +{ + register anode* cur; + + if (l->head == NULL) + return; + + // Start with cur in hopes that we don't start at beginning + if (l->cur) + cur = l->cur; + else + cur = l->head; + + // Loop until no next value + while (cur->next) + cur = cur->next; + l->cur = cur; +} + +void alist_append(alist *l, anode *node) +{ + anode* newnode; + + newnode = malloc(sizeof(anode)); + + if (node->scontext) + newnode->scontext = node->scontext; + else + newnode->scontext = NULL; + + if (node->tcontext) + newnode->tcontext = node->tcontext; + else + newnode->tcontext = NULL; + + newnode->avc_result = node->avc_result; + + if (node->avc_perm) + newnode->avc_perm = node->avc_perm; + else + newnode->avc_perm = NULL; + + if (node->avc_class) + newnode->avc_class = node->avc_class; + else + newnode->avc_class = NULL; + + newnode->next = NULL; + + // Make sure cursor is at the end + alist_last(l); + + // if we are at top, fix this up + if (l->head == NULL) + l->head = newnode; + else // Otherwise add pointer to newnode + l->cur->next = newnode; + + // make newnode current + l->cur = newnode; + l->cnt++; +} + +int alist_find_subj(alist *l) +{ + register anode* window = l->head; + + while (window) { + if (window->scontext) { + l->cur = window; + return 1; + } + else + window = window->next; + } + return 0; +} + +anode *alist_next_subj(alist *l) +{ + if (l->cur == NULL) + return NULL; + while (l->cur->next) { + l->cur=l->cur->next; + if (l->cur->scontext) + return l->cur; + } + return NULL; +} + +int alist_find_obj(alist *l) +{ + register anode* window = l->head; + + while (window) { + if (window->tcontext) { + l->cur = window; + return 1; + } + else + window = window->next; + } + return 0; +} + +anode *alist_next_obj(alist *l) +{ + if (l->cur == NULL) + return NULL; + while (l->cur->next) { + l->cur=l->cur->next; + if (l->cur->tcontext) + return l->cur; + } + return NULL; +} + +int alist_find_avc(alist *l) +{ + register anode* window = l->head; + + while (window) { + if (window->avc_result != AVC_UNSET) { + l->cur = window; + return 1; + } + else + window = window->next; + } + return 0; +} + +anode *alist_next_avc(alist *l) +{ + if (l->cur == NULL) + return NULL; + while (l->cur->next) { + l->cur=l->cur->next; + if (l->cur->avc_result != AVC_UNSET) + return l->cur; + } + return NULL; +} + +void alist_clear(alist* l) +{ + anode* nextnode; + register anode* current; + + current = l->head; + while (current) { + nextnode=current->next; + anode_clear(current); + free(current); + current=nextnode; + } + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + +void anode_init(anode *an) +{ + an->scontext = NULL; + an->tcontext = NULL; + an->avc_result = AVC_UNSET; + an->avc_perm = NULL; + an->avc_class = NULL; +} + +void anode_clear(anode *an) +{ + free(an->scontext); + free(an->tcontext); + free(an->avc_perm); + free(an->avc_class); +} + diff --git a/framework/src/audit/src/ausearch-avc.h b/framework/src/audit/src/ausearch-avc.h new file mode 100644 index 00000000..c31293e1 --- /dev/null +++ b/framework/src/audit/src/ausearch-avc.h @@ -0,0 +1,72 @@ +/* +* ausearch-avc.h - Header file for ausearch-string.c +* Copyright (c) 2006,2008 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#ifndef AU_AVC_HEADER +#define AU_AVC_HEADER + +#include "config.h" +#include <sys/types.h> +#include "libaudit.h" + +typedef enum { AVC_UNSET, AVC_DENIED, AVC_GRANTED } avc_t; + +/* This is the node of the linked list. message & item are the only elements + * at this time. Any data elements that are per item goes here. */ +typedef struct _anode{ + char *scontext; // se linux subject context + char *tcontext; // se linux object context + avc_t avc_result; // se linux avc denied/granted + char *avc_perm; // se linux avc permission mentioned + char *avc_class; // se linux class mentioned + struct _anode* next; // Next string node pointer +} anode; + +/* This is the linked list head. Only data elements that are 1 per + * event goes here. */ +typedef struct { + anode *head; // List head + anode *cur; // Pointer to current node + unsigned int cnt; // How many items in this list +} alist; + +void alist_create(alist *l); +static inline void alist_first(alist *l) { l->cur = l->head; } +anode *alist_next(alist *l); +static inline anode *alist_get_cur(alist *l) { return l->cur; } +void alist_append(alist *l, anode *node); +void anode_init(anode *an); +void anode_clear(anode *an); +void alist_clear(alist* l); + +/* See if any subj exists in list */ +int alist_find_subj(alist *l); +anode *alist_next_subj(alist *l); +/* See if any obj exists in list */ +int alist_find_obj(alist *l); +anode *alist_next_obj(alist *l); +/* See if any avc exists in list */ +int alist_find_avc(alist *l); +anode *alist_next_avc(alist *l); + +#endif + diff --git a/framework/src/audit/src/ausearch-checkpt.c b/framework/src/audit/src/ausearch-checkpt.c new file mode 100644 index 00000000..e0d0022c --- /dev/null +++ b/framework/src/audit/src/ausearch-checkpt.c @@ -0,0 +1,263 @@ +/* + * ausearch-checkpt.c - ausearch checkpointing feature + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + */ +#include <stdlib.h> +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include "ausearch-checkpt.h" + +#define DBG 0 /* set to non-zero for debug */ + +/* Remember why we failed */ +unsigned checkpt_failure = 0; + +/* + * Remember the file we were processing when we had incomplete events. + * We remember this via it's dev and inode + */ +static dev_t checkpt_dev = (dev_t)NULL; +static ino_t checkpt_ino = (ino_t)NULL; + +/* Remember the last event output */ +static event last_event = {0, 0, 0, NULL, 0}; + +/* Loaded values from a given checkpoint file */ +dev_t chkpt_input_dev = (dev_t)NULL; +ino_t chkpt_input_ino = (ino_t)NULL; +event chkpt_input_levent = {0, 0, 0, NULL, 0}; + +/* + * Record the dev_t and ino_t of the given file + * + * Returns: + * 1 Failed to get status + * 0 OK + */ +int set_ChkPtFileDetails(const char *fn) +{ + struct stat sbuf; + + if (stat(fn, &sbuf) != 0) { + fprintf(stderr, "Cannot stat audit file for checkpoint " + "details - %s: %s\n", fn, strerror(errno)); + checkpt_failure |= CP_STATFAILED; + return 1; + } + checkpt_dev = sbuf.st_dev; + checkpt_ino = sbuf.st_ino; + + return 0; +} + +/* + * Save the given event in the last_event record + * Returns: + * 1 no memory + * 0 OK + */ +int set_ChkPtLastEvent(const event *e) +{ + /* Set the event node if necessary */ + if (e->node) { + if (last_event.node) { + if (strcmp(e->node, last_event.node) != 0) { + free((void *)last_event.node); + last_event.node = strdup(e->node); + } + } else + last_event.node = strdup(e->node); + if (last_event.node == NULL) { + fprintf(stderr, "No memory to allocate " + "checkpoint last event node name\n"); + return 1; + } + } else { + if (last_event.node) + free((void *)last_event.node); + last_event.node = NULL; + } + last_event.sec = e->sec; + last_event.milli = e->milli; + last_event.serial = e->serial; + last_event.type = e->type; + + return 0; +} + +/* Free all checkpoint memory */ +void free_ChkPtMemory(void) +{ + if (last_event.node) + (void)free((void *)last_event.node); + last_event.node = NULL; + if (chkpt_input_levent.node) + (void)free((void *)chkpt_input_levent.node); + chkpt_input_levent.node = NULL; +} + +/* + * Save the checkpoint to the given file + * Returns: + * 1 io error + * 0 OK + */ +void save_ChkPt(const char *fn) +{ + FILE *fd; + + if ((fd = fopen(fn, "w")) == NULL) { + fprintf(stderr, "Cannot open checkpoint file - %s: %s\n", + fn, strerror(errno)); + checkpt_failure |= CP_STATUSIO; + return; + } + fprintf(fd, "dev=0x%X\ninode=0x%X\n", + (unsigned int)checkpt_dev, (unsigned int)checkpt_ino); + fprintf(fd, "output=%s %lu.%03u:%lu 0x%X\n", + last_event.node ? last_event.node : "-", + (long unsigned int)last_event.sec, last_event.milli, + last_event.serial, last_event.type); + fclose(fd); +} + +/* + * Parse a checkpoint file "output=" record + * Returns + * 1 failed to parse or no memory + * 0 parsed OK + */ +static int parse_checkpt_event(char *lbuf, int ndix, event *e) +{ + char *rest; + + /* + * Find the space after the node, then make it '\0' so + * we terminate the node value. We leave 'rest' at the start + * of the event time/serial element + */ + rest = strchr(&lbuf[ndix], ' '); + if (rest == NULL) { + fprintf(stderr, "Malformed output/event checkpoint line " + "near node - [%s]\n", lbuf); + checkpt_failure |= CP_STATUSBAD; + return 1; + } + *rest++ = '\0'; + + if (lbuf[ndix] == '-') + e->node = NULL; + else { + e->node = strdup(&lbuf[ndix]); + if (e->node == NULL) { + fprintf(stderr, "No memory for node when loading " + "checkpoint line - [%s]\n", lbuf); + checkpt_failure |= CP_NOMEM; + return 1; + } + } + if (sscanf(rest, "%lu.%03u:%lu 0x%X", &e->sec, &e->milli, + &e->serial, &e->type) != 4) { + fprintf(stderr, "Malformed output/event checkpoint line " + "after node - [%s]\n", lbuf); + checkpt_failure |= CP_STATUSBAD; + return 1; + } + + return 0; +} + +/* + * Load the checkpoint from the given file + * Returns: + * < -1 error + * == -1 no file present + * == 0 loaded data + */ +int load_ChkPt(const char *fn) +{ +#define MAX_LN 1023 + FILE *fd; + char lbuf[MAX_LN]; + + if ((fd = fopen(fn, "r")) == NULL) { + if (errno == ENOENT) + return -1; + fprintf(stderr, "Cannot open checkpoint file - %s: %s\n", + fn, strerror(errno)); + return -2; + } + while (fgets(lbuf, MAX_LN, fd) != NULL) { + size_t len = strlen(lbuf); + + if (len && lbuf[len - 1] == '\n') /* drop the newline */ + lbuf[len - 1] = '\0'; + + if (strncmp(lbuf, "dev=", 4) == 0) { + errno = 0; + chkpt_input_dev = strtoul(&lbuf[4], NULL, 16); + if (errno) { + fprintf(stderr, "Malformed dev checkpoint " + "line - [%s]\n", lbuf); + checkpt_failure |= CP_STATUSBAD; + break; + } + } else if (strncmp(lbuf, "inode=", 6) == 0) { + errno = 0; + chkpt_input_ino = strtoul(&lbuf[6], NULL, 16); + if (errno) { + fprintf(stderr, "Malformed inode checkpoint " + "line - [%s]\n", lbuf); + checkpt_failure |= CP_STATUSBAD; + break; + } + } else if (strncmp(lbuf, "output=", 7) == 0) { + if (parse_checkpt_event(lbuf, 7, &chkpt_input_levent)) + break; + } else { + fprintf(stderr, "Unknown checkpoint line - [%s]\n", + lbuf); + checkpt_failure |= CP_STATUSBAD; + break; + } + } + if ( (chkpt_input_ino == (ino_t)NULL) || + (chkpt_input_dev == (dev_t)NULL) ) { + fprintf(stderr, "Missing dev/inode lines from checkpoint " + "file %s\n", fn); + checkpt_failure |= CP_STATUSBAD; + } + fclose(fd); + + if (checkpt_failure) + return -3; + +#if DBG + { + fprintf(stderr, "Loaded %s - dev: 0x%X, ino: 0x%X\n", + fn, chkpt_input_dev, chkpt_input_ino); + fprintf(stderr, "output:%s %d.%03d:%lu 0x%X\n", + chkpt_input_levent.node ? chkpt_input_levent.node : "-", + chkpt_input_levent.sec, chkpt_input_levent.milli, + chkpt_input_levent.serial, chkpt_input_levent.type); + } +#endif /* DBG */ + return 0; +} + diff --git a/framework/src/audit/src/ausearch-checkpt.h b/framework/src/audit/src/ausearch-checkpt.h new file mode 100644 index 00000000..db66c254 --- /dev/null +++ b/framework/src/audit/src/ausearch-checkpt.h @@ -0,0 +1,42 @@ +/* + * ausearch-checkpt.h - ausearch checkpointing feature header file + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + */ +#ifndef CHECKPT_HEADER +#define CHECKPT_HEADER + +#include <sys/types.h> +#include "ausearch-llist.h" + +int set_ChkPtFileDetails(const char *fn); +int set_ChkPtLastEvent(const event *e); +void free_ChkPtMemory(void); +void save_ChkPt(const char *fn); +int load_ChkPt(const char *fn); + +#define CP_NOMEM 0x0001 /* no memory when creating checkpoint list */ +#define CP_STATFAILED 0x0002 /* stat() call on last log file failed */ +#define CP_STATUSIO 0x0004 /* cannot open/read/write checkpoint file */ +#define CP_STATUSBAD 0x0008 /* malformed status checkpoint entries */ +#define CP_CORRUPTED 0x0010 /* corrupted times in checkpoint file */ + +extern unsigned checkpt_failure; + +extern dev_t chkpt_input_dev; +extern ino_t chkpt_input_ino; +extern event chkpt_input_levent; + +#endif /* CHECKPT_HEADER */ diff --git a/framework/src/audit/src/ausearch-common.h b/framework/src/audit/src/ausearch-common.h new file mode 100644 index 00000000..96b59c85 --- /dev/null +++ b/framework/src/audit/src/ausearch-common.h @@ -0,0 +1,73 @@ +/* ausearch-common.h -- + * Copyright 2006-08,2010,2014 Red Hat Inc., Durham, North Carolina. + * Copyright (c) 2011 IBM Corp. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * Marcelo Henrique Cerri <mhcerri@br.ibm.com> + * + */ + +#ifndef AUREPORT_COMMON_H +#define AUREPORT_COMMON_H + +#include "ausearch-string.h" + +/* + * MAX_EVENT_DELTA_SECS is the maximum number of seconds it would take for + * auditd and the kernel to emit all of an events' records. Thus, when scanning + * a list of audit records without any End of Event marker, we can determine if + * all an event's records have been collected if we compare that event's time + * with the time of the event we are currently scanning. If + * MAX_EVENT_DELTA_SECS have passed, then the event is deamed to be complete + * and we have all it's records. + */ +#define MAX_EVENT_DELTA_SECS 2 + +/* Global variables that describe what search is to be performed */ +extern time_t start_time, end_time; +extern unsigned int event_id; +extern gid_t event_gid, event_egid; +extern pid_t event_pid; +extern int event_exact_match; +extern uid_t event_uid, event_euid, event_loginuid; +slist *event_node_list; +extern const char *event_comm; +extern const char *event_filename; +extern const char *event_hostname; +extern const char *event_terminal; +extern int event_syscall; +extern int event_machine; +extern const char *event_exe; +extern int event_ua, event_ga; +extern long long event_exit; +extern int event_exit_is_set; +extern const char *event_uuid; +extern const char *event_vmname; + +typedef enum { F_BOTH, F_FAILED, F_SUCCESS } failed_t; +typedef enum { C_NEITHER, C_ADD, C_DEL } conf_act_t; +typedef enum { S_UNSET=-1, S_FAILED, S_SUCCESS } success_t; +typedef enum { RPT_RAW, RPT_DEFAULT, RPT_INTERP, RPT_PRETTY } report_t; + +extern failed_t event_failed; +extern conf_act_t event_conf_act; +extern success_t event_success; + +#endif + diff --git a/framework/src/audit/src/ausearch-int.c b/framework/src/audit/src/ausearch-int.c new file mode 100644 index 00000000..a6bf8eb4 --- /dev/null +++ b/framework/src/audit/src/ausearch-int.c @@ -0,0 +1,162 @@ +/* +* ausearch-int.c - Minimal linked list library for integers +* Copyright (c) 2005,2008 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include "config.h" +#include <stdlib.h> +#include <string.h> +#include "ausearch-int.h" + +void ilist_create(ilist *l) +{ + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + +int_node *ilist_next(ilist *l) +{ + if (l->cur == NULL) + return NULL; + l->cur = l->cur->next; + return l->cur; +} + +void ilist_append(ilist *l, int num, unsigned int hits, int aux) +{ + int_node* newnode; + + newnode = malloc(sizeof(int_node)); + + newnode->num = num; + newnode->hits = hits; + newnode->aux1 = aux; + newnode->next = NULL; + + // if we are at top, fix this up + if (l->head == NULL) + l->head = newnode; + else // Otherwise add pointer to newnode + l->cur->next = newnode; + + // make newnode current + l->cur = newnode; + l->cnt++; +} + +void ilist_clear(ilist* l) +{ + int_node* nextnode; + register int_node* current; + + if (l == NULL) + return; + + current = l->head; + while (current) { + nextnode=current->next; + free(current); + current=nextnode; + } + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + +int ilist_add_if_uniq(ilist *l, int num, int aux) +{ + register int_node *cur, *prev; + + prev = cur = l->head; + while (cur) { + if (cur->num == num) { + cur->hits++; + return 0; + } else if (num > cur->num) { + prev = cur; + cur = cur->next; + } else { + int head = 0; + + // Insert so list is from low to high + if (cur == l->head) { + l->head = NULL; + head = 1; + } else + l->cur = prev; + ilist_append(l, num, 1, aux); + if (head) + l->cur->next = prev; + else + l->cur->next = cur; + return 1; + } + } + + if (prev) + l->cur = prev; + + /* No matches, append to the end */ + ilist_append(l, num, 1, aux); + return 1; +} + +// If lprev would be NULL, use l->head +static void swap_nodes(int_node *lprev, int_node *left, int_node *right) +{ + int_node *t = right->next; + if (lprev) + lprev->next = right; + right->next = left; + left->next = t; +} + +// This will sort the list from most hits to least +void ilist_sort_by_hits(ilist *l) +{ + register int_node* cur, *prev; + + if (l->cnt <= 1) + return; + + prev = cur = l->head; + while (cur && cur->next) { + /* If the next node is bigger */ + if (cur->hits < cur->next->hits) { + if (cur == l->head) { + // Update the actual list head + l->head = cur->next; + prev = NULL; + } + swap_nodes(prev, cur, cur->next); + + // start over + prev = cur = l->head; + continue; + } + prev = cur; + cur = cur->next; + } + // End with cur pointing at first record + l->cur = l->head; +} + diff --git a/framework/src/audit/src/ausearch-int.h b/framework/src/audit/src/ausearch-int.h new file mode 100644 index 00000000..bc2c51fc --- /dev/null +++ b/framework/src/audit/src/ausearch-int.h @@ -0,0 +1,58 @@ +/* +* ausearch-int.h - Header file for ausearch-int.c +* Copyright (c) 2005,2008 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#ifndef AUINT_HEADER +#define AUINT_HEADER + +#include "config.h" + +/* This is the node of the linked list. Number & item are the only elements + * at this time. Any data elements that are per item goes here. */ +typedef struct _int_node{ + int num; // The number + unsigned int hits; // The number of times this was attempted to be added + int aux1; // Extra spot for data + struct _int_node* next; // Next string node pointer +} int_node; + +/* This is the linked list head. Only data elements that are 1 per + * event goes here. */ +typedef struct { + int_node *head; // List head + int_node *cur; // Pointer to current node + unsigned int cnt; // How many items in this list +} ilist; + +void ilist_create(ilist *l); +static inline void ilist_first(ilist *l) { l->cur = l->head; } +int_node *ilist_next(ilist *l); +static inline int_node *ilist_get_cur(ilist *l) { return l->cur; } +void ilist_append(ilist *l, int num, unsigned int hits, int aux); +void ilist_clear(ilist* l); + +/* append a number if its not already on the list */ +int ilist_add_if_uniq(ilist *l, int num, int aux); +void ilist_sort_by_hits(ilist *l); + +#endif + diff --git a/framework/src/audit/src/ausearch-llist.c b/framework/src/audit/src/ausearch-llist.c new file mode 100644 index 00000000..973941c5 --- /dev/null +++ b/framework/src/audit/src/ausearch-llist.c @@ -0,0 +1,257 @@ +/* +* ausearch-llist.c - Minimal linked list library +* Copyright (c) 2005-2008, 2011 Red Hat Inc., Durham, North Carolina. +* Copyright (c) 2011 IBM Corp. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +* Marcelo Henrique Cerri <mhcerri@br.ibm.com> +*/ + +#include <stdlib.h> +#include <string.h> +#include "ausearch-llist.h" + +void list_create(llist *l) +{ + l->head = NULL; + l->cur = NULL; + l->cnt = 0; + l->e.milli = 0L; + l->e.sec = 0L; + l->e.serial = 0L; + l->e.node = NULL; + l->e.type = 0; + l->s.gid = -1; + l->s.egid = -1; + l->s.ppid = -1; + l->s.pid = -1; + l->s.success = S_UNSET; + l->s.uid = -1; + l->s.euid = -1; + l->s.loginuid = -2; + l->s.hostname = NULL; + l->s.filename = NULL; + l->s.terminal = NULL; + l->s.cwd = NULL; + l->s.exe = NULL; + l->s.key = NULL; + l->s.comm = NULL; + l->s.avc = NULL; + l->s.acct = NULL; + l->s.arch = 0; + l->s.syscall = 0; + l->s.session_id = -2; + l->s.uuid = NULL; + l->s.vmname = NULL; + l->s.exit = 0; + l->s.exit_is_set = 0; +} + +void list_last(llist *l) +{ + register lnode* window; + + if (l->head == NULL) + return; + + window = l->head; + while (window->next) + window = window->next; + l->cur = window; +} + +lnode *list_next(llist *l) +{ + if (l->cur == NULL) + return NULL; + l->cur = l->cur->next; + return l->cur; +} + +lnode *list_prev(llist *l) +{ + if (l->cur == NULL) + return NULL; + + if (l->cur->item == 0) + return NULL; + + list_find_item(l, l->cur->item-1); + return l->cur; +} + +void list_append(llist *l, lnode *node) +{ + lnode* newnode; + + newnode = malloc(sizeof(lnode)); + + if (node->message) + newnode->message = node->message; + else + newnode->message = NULL; + + newnode->mlen = node->mlen; + newnode->type = node->type; + newnode->a0 = node->a0; + newnode->a1 = node->a1; + newnode->item = l->cnt; + newnode->next = NULL; + + // if we are at top, fix this up + if (l->head == NULL) + l->head = newnode; + else // Otherwise add pointer to newnode + l->cur->next = newnode; + + // make newnode current + l->cur = newnode; + l->cnt++; +} + +int list_find_item(llist *l, unsigned int i) +{ + register lnode* window; + + if (l->cur && (l->cur->item <= i)) + window = l->cur; /* Try to use where we are */ + else + window = l->head; /* Can't, start over */ + + while (window) { + if (window->item == i) { + l->cur = window; + return 1; + } else + window = window->next; + } + return 0; +} + +void list_clear(llist* l) +{ + lnode* nextnode; + register lnode* current; + + current = l->head; + while (current) { + nextnode=current->next; + free(current->message); + free(current); + current=nextnode; + } + l->head = NULL; + l->cur = NULL; + l->cnt = 0; + l->e.milli = 0L; + l->e.sec = 0L; + l->e.serial = 0L; + free((char *)l->e.node); + l->e.node = NULL; + l->e.type = 0; + l->s.gid = -1; + l->s.egid = -1; + l->s.ppid = -1; + l->s.pid = -1; + l->s.success = S_UNSET; + l->s.uid = -1; + l->s.euid = -1; + l->s.loginuid = -2; + free(l->s.hostname); + l->s.hostname = NULL; + if (l->s.filename) { + slist_clear(l->s.filename); + free(l->s.filename); + l->s.filename = NULL; + } + free(l->s.terminal); + l->s.terminal = NULL; + free(l->s.cwd); + l->s.cwd = NULL; + free(l->s.exe); + l->s.exe = NULL; + if (l->s.key) { + slist_clear(l->s.key); + free(l->s.key); + l->s.key = NULL; + } + free(l->s.comm); + l->s.comm = NULL; + if (l->s.avc) { + alist_clear(l->s.avc); + free(l->s.avc); + l->s.avc = NULL; + } + free(l->s.acct); + l->s.acct = NULL; + l->s.arch = 0; + l->s.syscall = 0; + l->s.session_id = -2; + free(l->s.uuid); + l->s.uuid = NULL; + free(l->s.vmname); + l->s.vmname = NULL; + l->s.exit = 0; + l->s.exit_is_set = 0; +} + +int list_get_event(llist* l, event *e) +{ + if (l == NULL || e == NULL) + return 0; + + e->sec = l->e.sec; + e->milli = l->e.milli; + e->serial = l->e.serial; + return 1; +} + +lnode *list_find_msg(llist *l, int i) +{ + register lnode* window; + + window = l->head; /* start at the beginning */ + while (window) { + if (window->type == i) { + l->cur = window; + return window; + } else + window = window->next; + } + return NULL; +} + +lnode *list_find_msg_range(llist *l, int low, int high) +{ + register lnode* window; + + if (high <= low) + return NULL; + + window = l->head; /* Start at the beginning */ + while (window) { + if (window->type >= low && window->type <= high) { + l->cur = window; + return window; + } else + window = window->next; + } + return NULL; +} + diff --git a/framework/src/audit/src/ausearch-llist.h b/framework/src/audit/src/ausearch-llist.h new file mode 100644 index 00000000..ada8ec81 --- /dev/null +++ b/framework/src/audit/src/ausearch-llist.h @@ -0,0 +1,117 @@ +/* +* ausearch-llist.h - Header file for ausearch-llist.c +* Copyright (c) 2005-2008, 2013-14 Red Hat Inc., Durham, North Carolina. +* Copyright (c) 2011 IBM Corp. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +* Marcelo Henrique Cerri <mhcerri@br.ibm.com> +*/ + +#ifndef AULIST_HEADER +#define AULIST_HEADER + +#include "config.h" +#include <sys/types.h> +#include "ausearch-string.h" +#include "ausearch-avc.h" +#include "ausearch-common.h" + + +typedef struct +{ + time_t sec; // Event seconds + unsigned int milli; // millisecond of the timestamp + unsigned long serial; // Serial number of the event + const char *node; // Machine's node name + int type; // type of first event +} event; + +typedef struct +{ + pid_t ppid; // parent process ID + pid_t pid; // process ID + uid_t uid; // user ID + uid_t euid; // effective user ID + uid_t loginuid; // login user ID + gid_t gid; // group ID + gid_t egid; // effective group ID + success_t success; // success flag, 1 = yes, 0 = no, -1 = unset + int arch; // arch + int syscall; // syscall + uint32_t session_id; // Login session id + long long exit; // Syscall exit code + int exit_is_set; // Syscall exit code is valid + char *hostname; // remote hostname + slist *filename; // filename list + char *cwd; // current working dir + char *exe; // executable + slist *key; // key field + char *terminal; // terminal + char *comm; // comm name + alist *avc; // avcs for the event + char *acct; // account used when uid is invalid + char *uuid; // virtual machine unique universal identifier + char *vmname; // virtual machine name +} search_items; + +/* This is the node of the linked list. Any data elements that are per + * record goes here. */ +typedef struct _lnode{ + char *message; // The whole unparsed message + unsigned mlen; // Length of the message + int type; // message type (KERNEL, USER, LOGIN, etc) + unsigned long long a0; // argv 0 + unsigned long long a1; // argv 1 + unsigned int item; // Which item of the same event + struct _lnode* next; // Next node pointer +} lnode; + +/* This is the linked list head. Only data elements that are 1 per + * event goes here. */ +typedef struct { + lnode *head; // List head + lnode *cur; // Pointer to current node + unsigned int cnt; // How many items in this list + + // Data we add as 1 per event + event e; // event - time & serial number + search_items s; // items in master rec that are searchable +} llist; + +void list_create(llist *l); +static inline void list_first(llist *l) { l->cur = l->head; } +void list_last(llist *l); +lnode *list_next(llist *l); +lnode *list_prev(llist *l); +static inline lnode *list_get_cur(llist *l) { return l->cur; } +void list_append(llist *l, lnode *node); +void list_clear(llist* l); +int list_get_event(llist* l, event *e); + +/* Given a numeric index, find that record. */ +int list_find_item(llist *l, unsigned int i); + +/* Given a message type, find the matching node */ +lnode *list_find_msg(llist *l, int i); + +/* Given two message types, find the first matching node */ +lnode *list_find_msg_range(llist *l, int low, int high); + +#endif + diff --git a/framework/src/audit/src/ausearch-lol.c b/framework/src/audit/src/ausearch-lol.c new file mode 100644 index 00000000..48005126 --- /dev/null +++ b/framework/src/audit/src/ausearch-lol.c @@ -0,0 +1,296 @@ +/* +* ausearch-lol.c - linked list of linked lists library +* Copyright (c) 2008,2010,2014 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include "ausearch-lol.h" +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include "ausearch-common.h" +#include "private.h" + +#define ARRAY_LIMIT 80 +static int ready = 0; + +void lol_create(lol *lo) +{ + int size = ARRAY_LIMIT * sizeof(lolnode); + + lo->maxi = -1; + lo->limit = ARRAY_LIMIT; + lo->array = (lolnode *)malloc(size); + memset(lo->array, 0, size); +} + +void lol_clear(lol *lo) +{ + int i; + + for (i=0; i<=lo->maxi; i++) { + if (lo->array[i].status) { + list_clear(lo->array[i].l); + free(lo->array[i].l); + } + } + free(lo->array); + lo->array = NULL; + lo->maxi = -1; +} + +static void lol_append(lol *lo, llist *l) +{ + int i; + size_t new_size; + lolnode *ptr; + + for(i=0; i<lo->limit; i++) { + lolnode *cur = &lo->array[i]; + if (cur->status == L_EMPTY) { + cur->l = l; + cur->status = L_BUILDING; + if (i > lo->maxi) + lo->maxi = i; + return; + } + } + // Overran the array...lets make it bigger + new_size = sizeof(lolnode) * (lo->limit + ARRAY_LIMIT); + ptr = realloc(lo->array, new_size); + if (ptr) { + lo->array = ptr; + memset(&lo->array[lo->limit], 0, sizeof(lolnode) * ARRAY_LIMIT); + lo->array[i].l = l; + lo->array[i].status = L_BUILDING; + lo->maxi = i; + lo->limit += ARRAY_LIMIT; + } +} + +static int str2event(char *s, event *e) +{ + char *ptr; + + errno = 0; + ptr = strchr(s+10, ':'); + if (ptr) { + e->serial = strtoul(ptr+1, NULL, 10); + *ptr = 0; + if (errno) + return -1; + } else + e->serial = 0; + ptr = strchr(s, '.'); + if (ptr) { + e->milli = strtoul(ptr+1, NULL, 10); + *ptr = 0; + if (errno) + return -1; + } else + e->milli = 0; + e->sec = strtoul(s, NULL, 10); + if (errno) + return -1; + return 0; +} + +static int inline events_are_equal(event *e1, event *e2) +{ + if (!(e1->serial == e2->serial && e1->milli == e2->milli && + e1->sec == e2->sec)) + return 0; + if (e1->node && e2->node) { + if (strcmp(e1->node, e2->node)) + return 0; + } else if (e1->node || e2->node) + return 0; + return 1; +} + +/* + * This function will look at the line and pick out pieces of it. + */ +static int extract_timestamp(const char *b, event *e) +{ + char *ptr, *tmp, *tnode, *ttype; + + e->node = NULL; + if (*b == 'n') + tmp = strndupa(b, 340); + else + tmp = strndupa(b, 80); + ptr = audit_strsplit(tmp); + if (ptr) { + // Check to see if this is the node info + if (*ptr == 'n') { + tnode = ptr+5; + ptr = audit_strsplit(NULL); + } else + tnode = NULL; + + // at this point we have type= + ttype = ptr+5; + + // Now should be pointing to msg= + ptr = audit_strsplit(NULL); + if (ptr) { + if (*(ptr+9) == '(') + ptr+=9; + else + ptr = strchr(ptr, '('); + if (ptr) { + // now we should be pointed at the timestamp + char *eptr; + ptr++; + eptr = strchr(ptr, ')'); + if (eptr) + *eptr = 0; + if (str2event(ptr, e)) { + fprintf(stderr, + "Error extracting time stamp (%s)\n", + ptr); + return 0; + } else if ((start_time && e->sec < start_time) + || (end_time && e->sec > end_time)) + return 0; + else { + if (tnode) + e->node = strdup(tnode); + e->type = audit_name_to_msg_type(ttype); + } + return 1; + } + // else we have a bad line + } + // else we have a bad line + } + // else we have a bad line + return 0; +} + +// This function will check events to see if they are complete +// FIXME: Can we think of other ways to determine if the event is done? +static void check_events(lol *lo, time_t sec) +{ + int i; + + for(i=0;i<=lo->maxi; i++) { + lolnode *cur = &lo->array[i]; + if (cur->status == L_BUILDING) { + // If 2 seconds have elapsed, we are done + if (cur->l->e.sec + 2 < sec) { + cur->status = L_COMPLETE; + ready++; + } else if (cur->l->e.type < AUDIT_FIRST_EVENT || + cur->l->e.type >= AUDIT_FIRST_ANOM_MSG) { + // If known to be 1 record event, we are done + cur->status = L_COMPLETE; + ready++; + } + } + } +} + +// This function adds a new record to an existing linked list +// or creates a new one if its a new event +int lol_add_record(lol *lo, char *buff) +{ + int i; + lnode n; + event e; + char *ptr; + llist *l; + + // Short circuit if event is not of interest + if (extract_timestamp(buff, &e) == 0) + return 0; + + ptr = strrchr(buff, 0x0a); + if (ptr) { + *ptr = 0; + n.mlen = ptr - buff; + } else + n.mlen = MAX_AUDIT_MESSAGE_LENGTH; + n.message=strdup(buff); + n.type = e.type; + + // Now see where this belongs + for (i=0; i<=lo->maxi; i++) { + if (lo->array[i].status == L_BUILDING) { + l = lo->array[i].l; + if (events_are_equal(&l->e, &e)) { + free((char *)e.node); + list_append(l, &n); + return 1; + } + } + } + // Create new event and fill it in + l = malloc(sizeof(llist)); + list_create(l); + l->e.milli = e.milli; + l->e.sec = e.sec; + l->e.serial = e.serial; + l->e.node = e.node; + l->e.type = e.type; + list_append(l, &n); + lol_append(lo, l); + check_events(lo, e.sec); + return 1; +} + +// This function will mark all events as "done" +void terminate_all_events(lol *lo) +{ + int i; + + for (i=0; i<=lo->maxi; i++) { + lolnode *cur = &lo->array[i]; + if (cur->status == L_BUILDING) { + cur->status = L_COMPLETE; + ready++; + } + } +//printf("maxi = %d\n",lo->maxi); +} + +/* Search the list for any event that is ready to go. The caller + * takes custody of the memory */ +llist* get_ready_event(lol *lo) +{ + int i; + + if (ready == 0) + return NULL; + + for (i=0; i<=lo->maxi; i++) { + lolnode *cur = &lo->array[i]; + if (cur->status == L_COMPLETE) { + cur->status = L_EMPTY; + ready--; + return cur->l; + } + } + + return NULL; +} + diff --git a/framework/src/audit/src/ausearch-lol.h b/framework/src/audit/src/ausearch-lol.h new file mode 100644 index 00000000..36b6bbca --- /dev/null +++ b/framework/src/audit/src/ausearch-lol.h @@ -0,0 +1,54 @@ +/* +* ausearch-lol.h - linked list of linked lists library header +* Copyright (c) 2008 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#ifndef AUSEARCH_LOL_HEADER +#define AUSEARCH_LOL_HEADER + +#include "config.h" +#include "ausearch-llist.h" + +typedef enum { L_EMPTY, L_BUILDING, L_COMPLETE } lol_t; + +/* This is the node of the linked list. message & item are the only elements + * at this time. Any data elements that are per item goes here. */ +typedef struct _lolnode{ + llist *l; // The linked list + int status; // 0 = empty, 1 in use, 2 complete +} lolnode; + +/* This is the linked list head. Only data elements that are 1 per + * event goes here. */ +typedef struct { + lolnode *array; + int maxi; // Largest index used + int limit; // Number of nodes in the array +} lol; + +void lol_create(lol *lo); +void lol_clear(lol *lo); +int lol_add_record(lol *lo, char *buff); +void terminate_all_events(lol *lo); +llist* get_ready_event(lol *lo); + +#endif + diff --git a/framework/src/audit/src/ausearch-lookup.c b/framework/src/audit/src/ausearch-lookup.c new file mode 100644 index 00000000..10a219a5 --- /dev/null +++ b/framework/src/audit/src/ausearch-lookup.c @@ -0,0 +1,500 @@ +/* +* ausearch-lookup.c - Lookup values to something more readable +* Copyright (c) 2005-06,2011-12,2015 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <linux/net.h> +#include "ausearch-lookup.h" +#include "ausearch-options.h" +#include "ausearch-nvpair.h" + +/* This is the name/value pair used by search tables */ +struct nv_pair { + int value; + const char *name; +}; + + +/* The machine based on elf type */ +static int machine = 0; +static const char *Q = "?"; +static const char *results[3]= { "unset", "denied", "granted" }; +static const char *success[3]= { "unset", "no", "yes" }; +static const char *aulookup_socketcall(long sc); +static const char *aulookup_ipccall(long ic); + +const char *aulookup_result(avc_t result) +{ + return results[result]; +} + +const char *aulookup_success(int s) +{ + switch (s) + { + default: + return success[0]; + break; + case S_FAILED: + return success[1]; + break; + case S_SUCCESS: + return success[2]; + break; + } +} + +const char *aulookup_syscall(llist *l, char *buf, size_t size) +{ + const char *sys; + + if (report_format <= RPT_DEFAULT) { + snprintf(buf, size, "%d", l->s.syscall); + return buf; + } + machine = audit_elf_to_machine(l->s.arch); + if (machine < 0) + return Q; + sys = audit_syscall_to_name(l->s.syscall, machine); + if (sys) { + const char *func = NULL; + if (strcmp(sys, "socketcall") == 0) { + if (list_find_item(l, AUDIT_SYSCALL)) + func = aulookup_socketcall((long)l->cur->a0); + } else if (strcmp(sys, "ipc") == 0) { + if(list_find_item(l, AUDIT_SYSCALL)) + func = aulookup_ipccall((long)l->cur->a0); + } + if (func) { + snprintf(buf, size, "%s(%s)", sys, func); + return buf; + } + return sys; + } + snprintf(buf, size, "%d", l->s.syscall); + return buf; +} + +// See include/linux/net.h +static struct nv_pair socktab[] = { + {SYS_SOCKET, "socket"}, + {SYS_BIND, "bind"}, + {SYS_CONNECT, "connect"}, + {SYS_LISTEN, "listen"}, + {SYS_ACCEPT, "accept"}, + {SYS_GETSOCKNAME, "getsockname"}, + {SYS_GETPEERNAME, "getpeername"}, + {SYS_SOCKETPAIR, "socketpair"}, + {SYS_SEND, "send"}, + {SYS_RECV, "recv"}, + {SYS_SENDTO, "sendto"}, + {SYS_RECVFROM, "recvfrom"}, + {SYS_SHUTDOWN, "shutdown"}, + {SYS_SETSOCKOPT, "setsockopt"}, + {SYS_GETSOCKOPT, "getsockopt"}, + {SYS_SENDMSG, "sendmsg"}, + {SYS_RECVMSG, "recvmsg"}, + {SYS_ACCEPT4, "accept4"}, + {19, "recvmmsg"}, + {20, "sendmmsg"} +}; +#define SOCK_NAMES (sizeof(socktab)/sizeof(socktab[0])) + +static const char *aulookup_socketcall(long sc) +{ + int i; + + for (i = 0; i < SOCK_NAMES; i++) + if (socktab[i].value == sc) + return socktab[i].name; + + return NULL; +} + +/* This is from asm/ipc.h. Copying it for now as some platforms + * have broken headers. */ +#define SEMOP 1 +#define SEMGET 2 +#define SEMCTL 3 +#define SEMTIMEDOP 4 +#define MSGSND 11 +#define MSGRCV 12 +#define MSGGET 13 +#define MSGCTL 14 +#define SHMAT 21 +#define SHMDT 22 +#define SHMGET 23 +#define SHMCTL 24 + +/* + * This table maps ipc calls to their text name + */ +static struct nv_pair ipctab[] = { + {SEMOP, "semop"}, + {SEMGET, "semget"}, + {SEMCTL, "semctl"}, + {SEMTIMEDOP, "semtimedop"}, + {MSGSND, "msgsnd"}, + {MSGRCV, "msgrcv"}, + {MSGGET, "msgget"}, + {MSGCTL, "msgctl"}, + {SHMAT, "shmat"}, + {SHMDT, "shmdt"}, + {SHMGET, "shmget"}, + {SHMCTL, "shmctl"} +}; +#define IPC_NAMES (sizeof(ipctab)/sizeof(ipctab[0])) + +static const char *aulookup_ipccall(long ic) +{ + int i; + + for (i = 0; i < IPC_NAMES; i++) + if (ipctab[i].value == ic) + return ipctab[i].name; + + return NULL; +} + +static nvlist uid_nvl; +static int uid_list_created=0; +const char *aulookup_uid(uid_t uid, char *buf, size_t size) +{ + char *name = NULL; + int rc; + + if (report_format <= RPT_DEFAULT) { + snprintf(buf, size, "%d", uid); + return buf; + } + if (uid == -1) { + snprintf(buf, size, "unset"); + return buf; + } + + // Check the cache first + if (uid_list_created == 0) { + nvlist_create(&uid_nvl); + nvlist_clear(&uid_nvl); + uid_list_created = 1; + } + rc = nvlist_find_val(&uid_nvl, uid); + if (rc) { + name = uid_nvl.cur->name; + } else { + // Add it to cache + struct passwd *pw; + pw = getpwuid(uid); + if (pw) { + nvnode nv; + nv.name = strdup(pw->pw_name); + nv.val = uid; + nvlist_append(&uid_nvl, &nv); + name = uid_nvl.cur->name; + } + } + if (name != NULL) + snprintf(buf, size, "%s", name); + else + snprintf(buf, size, "unknown(%d)", uid); + return buf; +} + +void aulookup_destroy_uid_list(void) +{ + if (uid_list_created == 0) + return; + + nvlist_clear(&uid_nvl); + uid_list_created = 0; +} + +static nvlist gid_nvl; +static int gid_list_created=0; +const char *aulookup_gid(gid_t gid, char *buf, size_t size) +{ + char *name = NULL; + int rc; + + if (report_format <= RPT_DEFAULT) { + snprintf(buf, size, "%d", gid); + return buf; + } + if (gid == -1) { + snprintf(buf, size, "unset"); + return buf; + } + + // Check the cache first + if (gid_list_created == 0) { + nvlist_create(&gid_nvl); + nvlist_clear(&gid_nvl); + gid_list_created = 1; + } + rc = nvlist_find_val(&gid_nvl, gid); + if (rc) { + name = gid_nvl.cur->name; + } else { + // Add it to cache + struct group *gr; + gr = getgrgid(gid); + if (gr) { + nvnode nv; + nv.name = strdup(gr->gr_name); + nv.val = gid; + nvlist_append(&gid_nvl, &nv); + name = gid_nvl.cur->name; + } + } + if (name != NULL) + snprintf(buf, size, "%s", name); + else + snprintf(buf, size, "unknown(%d)", gid); + return buf; +} + +void aulookup_destroy_gid_list(void) +{ + if (gid_list_created == 0) + return; + + nvlist_clear(&gid_nvl); + gid_list_created = 0; +} + +int is_hex_string(const char *str) +{ + int c=0; + while (*str) { + if (!isxdigit(*str)) + return 0; + str++; + c++; + } + return 1; +} +/* + * This function will take a pointer to a 2 byte Ascii character buffer and + * return the actual hex value. + */ +static unsigned char x2c(unsigned char *buf) +{ + static const char AsciiArray[17] = "0123456789ABCDEF"; + char *ptr; + unsigned char total=0; + + ptr = strchr(AsciiArray, (char)toupper(buf[0])); + if (ptr) + total = (unsigned char)(((ptr-AsciiArray) & 0x0F)<<4); + ptr = strchr(AsciiArray, (char)toupper(buf[1])); + if (ptr) + total += (unsigned char)((ptr-AsciiArray) & 0x0F); + + return total; +} + +/* returns a freshly malloc'ed and converted buffer */ +char *unescape(const char *buf) +{ + int len, i; + char *str, *strptr; + const char *ptr = buf; + + /* Find the end of the name */ + if (*ptr == '(') { + ptr = strchr(ptr, ')'); + if (ptr == NULL) + return NULL; + else + ptr++; + } else { + while (isxdigit(*ptr)) + ptr++; + } + str = strndup(buf, ptr - buf); + + if (*buf == '(') + return str; + + /* We can get away with this since the buffer is 2 times + * bigger than what we are putting there. + */ + len = strlen(str); + if (len < 2) { + free(str); + return NULL; + } + strptr = str; + for (i=0; i<len; i+=2) { + *strptr = x2c((unsigned char *)&str[i]); + strptr++; + } + *strptr = 0; + return str; +} + +int need_sanitize(const unsigned char *s, unsigned int len) +{ + unsigned int i = 0; + while (i < len) { + if (s[i] < 32) + return 1; + i++; + } + return 0; +} + +void sanitize(const char *s, unsigned int len) +{ + unsigned int i = 0; + while (i < len) { + if ((unsigned char)s[i] < 32) { + putchar('\\'); + putchar('0' + ((s[i] & 0300) >> 6)); + putchar('0' + ((s[i] & 0070) >> 3)); + putchar('0' + (s[i] & 0007)); + } else + putchar(s[i]); + i++; + } +} + +void safe_print_string_n(const char *s, unsigned int len, int ret) +{ + if (need_sanitize(s, len)) { + sanitize(s, len); + if (ret) + putchar('\n'); + } else if (ret) + puts(s); + else + printf("%s", s); +} + +void safe_print_string(const char *s, int ret) +{ + safe_print_string_n(s, strlen(s), ret); +} + +/* Represent c as a character within a quoted string, and append it to buf. */ +static void tty_printable_char(unsigned char c) +{ + if (c < 0x20 || c > 0x7E) { + putchar('\\'); + putchar('0' + ((c >> 6) & 07)); + putchar('0' + ((c >> 3) & 07)); + putchar('0' + (c & 07)); + } else { + if (c == '\\' || c == '"') + putchar('\\'); + putchar(c); + } +} + +/* Search for a name of a sequence of TTY bytes. + * If found, return the name and advance *INPUT. + * Return NULL otherwise. + */ +static const char *tty_find_named_key(unsigned char **input, size_t input_len) +{ + /* NUL-terminated list of (sequence, NUL, name, NUL) entries. + First match wins, even if a longer match were possible later */ + static const unsigned char named_keys[] = +#define E(SEQ, NAME) SEQ "\0" NAME "\0" +#include "auparse/tty_named_keys.h" +#undef E + "\0"; + + unsigned char *src; + const unsigned char *nk; + + src = *input; + if (*src >= ' ' && (*src < 0x7F || *src >= 0xA0)) + return NULL; /* Fast path */ + nk = named_keys; + do { + const unsigned char *p; + size_t nk_len; + + p = strchr((const char *)nk, '\0'); + nk_len = p - nk; + if (nk_len <= input_len && memcmp(src, nk, nk_len) == 0) { + *input += nk_len; + return (const char *)(p + 1); + } + nk = strchr((const char *)p + 1, '\0') + 1; + } while (*nk != '\0'); + return NULL; +} + +void print_tty_data(const char *val) +{ + int need_comma, in_printable = 0; + unsigned char *data, *data_pos, *data_end; + + if (!is_hex_string(val)) { + printf("%s", val); + return; + } + + if ((data = unescape((char *)val)) == NULL) { + printf("conversion error(%s)", val); + return; + } + + data_end = data + strlen(val) / 2; + data_pos = data; + need_comma = 0; + while (data_pos < data_end) { + /* FIXME: Unicode */ + const char *desc; + + desc = tty_find_named_key(&data_pos, data_end - data_pos); + if (desc != NULL) { + if (in_printable != 0) { + putchar('"'); + in_printable = 0; + } + if (need_comma != 0) + putchar(','); + printf("<%s>", desc); + } else { + if (in_printable == 0) { + if (need_comma != 0) + putchar(','); + putchar('"'); + in_printable = 1; + } + tty_printable_char(*data_pos); + data_pos++; + } + need_comma = 1; + } + if (in_printable != 0) + putchar('"'); + free(data); +} + diff --git a/framework/src/audit/src/ausearch-lookup.h b/framework/src/audit/src/ausearch-lookup.h new file mode 100644 index 00000000..ab12e176 --- /dev/null +++ b/framework/src/audit/src/ausearch-lookup.h @@ -0,0 +1,50 @@ +/* +* ausearch-lookup.h - Header file for ausearch-lookup.c +* Copyright (c) 2005-06,2014 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#ifndef AULOOKUP_HEADER +#define AULOOKUP_HEADER + +#include "config.h" +#include <pwd.h> +#include <grp.h> +#include "libaudit.h" +#include "ausearch-llist.h" + + +const char *aulookup_result(avc_t result); +const char *aulookup_success(int s); +const char *aulookup_syscall(llist *l, char *buf, size_t size); +const char *aulookup_uid(uid_t uid, char *buf, size_t size); +void aulookup_destroy_uid_list(void); +const char *aulookup_gid(gid_t gid, char *buf, size_t size); +void aulookup_destroy_gid_list(void); +char *unescape(const char *buf); +int is_hex_string(const char *str); +void print_tty_data(const char *val); +int need_sanitize(const unsigned char *s, unsigned int len); +void sanitize(const char *s, unsigned int len); +void safe_print_string_n(const char *s, unsigned int len, int ret); +void safe_print_string(const char *s, int ret); + +#endif + diff --git a/framework/src/audit/src/ausearch-match.c b/framework/src/audit/src/ausearch-match.c new file mode 100644 index 00000000..44c60887 --- /dev/null +++ b/framework/src/audit/src/ausearch-match.c @@ -0,0 +1,364 @@ +/* +* ausearch-match.c - Extract interesting fields and check for match +* Copyright (c) 2005-08, 2011 Red Hat Inc., Durham, North Carolina. +* Copyright (c) 2011 IBM Corp. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +* Marcelo Henrique Cerri <mhcerri@br.ibm.com> +*/ + +#include "config.h" +#include <string.h> +#include "libaudit.h" +#include "ausearch-options.h" +#include "ausearch-parse.h" + +static int strmatch(const char *needle, const char *haystack); +static int user_match(llist *l); +static int group_match(llist *l); +static int context_match(llist *l); + +/* + * This function performs that matching of search params with the record. + * It returns 1 on a match, and 0 if no match. The way that this function + * works is that it will try to determine if there is not a match and exit + * as soon as possible. We can do this since all command line params form + * an 'and' statement. If anything does not match, no need to evaluate the + * rest of the params. + */ +#include <stdio.h> +int match(llist *l) +{ + // Are we within time range? + if (start_time == 0 || l->e.sec >= start_time) { + if (end_time == 0 || l->e.sec <= end_time) { + if (event_id == -1 || event_id == l->e.serial) { + // OK - do the heavier checking + if (extract_search_items(l)) { + return 0; + } + + // perform additional tests for the field + if (event_node_list) { + const snode *sn; + int found=0; + slist *sptr = event_node_list; + + if (l->e.node == NULL) + return 0; + + slist_first(sptr); + sn=slist_get_cur(sptr); + while (sn && !found) { + if (sn->str && (!strcmp(sn->str, l->e.node))) + found++; + else + sn=slist_next(sptr); + } + if (!found) + return 0; + } + if (user_match(l) == 0) + return 0; + if (group_match(l) == 0) + return 0; + if ((event_ppid != -1) && + (event_ppid != l->s.ppid)) + return 0; + if ((event_pid != -1) && + (event_pid != l->s.pid)) + return 0; + if (event_machine != -1 && + (event_machine != + audit_elf_to_machine(l->s.arch))) + return 0; + if ((event_syscall != -1) && + (event_syscall != l->s.syscall)) + return 0; + if ((event_session_id != -2) && + (event_session_id != l->s.session_id)) + return 0; + if (event_exit_is_set) { + if (l->s.exit_is_set == 0) + return 0; + if (event_exit != l->s.exit) + return 0; + } + + if ((event_success != S_UNSET) && + (event_success != l->s.success)) + return 0; + // event_type requires looking at each item + if (event_type != NULL) { + int found = 0; + const lnode *n; + + list_first(l); + n = list_get_cur(l); + do { + int_node *in; + ilist_first(event_type); + in = ilist_get_cur(event_type); + do { + if (in->num == n->type){ + found = 1; + break; + } + } while((in = + ilist_next(event_type))); + if (found) + break; + } while ((n = list_next(l))); + if (!found) + return 0; + } + + // Done all the easy compares, now do the + // string searches. + if (event_filename) { + int found = 0; + if (l->s.filename == NULL && l->s.cwd == NULL) + return 0; + if (l->s.filename) { + const snode *sn; + slist *sptr = l->s.filename; + + slist_first(sptr); + sn=slist_get_cur(sptr); + do { + if (sn->str == NULL) + return 0; + if (strmatch( + event_filename, + sn->str)) { + found = 1; + break; + } + } while ((sn=slist_next(sptr))); + + if (!found && l->s.cwd == NULL) + return 0; + } + if (l->s.cwd && !found) { + /* Check cwd, too */ + if (strmatch(event_filename, + l->s.cwd) == 0) + return 0; + } + } + if (event_hostname) { + if (l->s.hostname == NULL) + return 0; + if (strmatch(event_hostname, + l->s.hostname) == 0) + return 0; + } + if (event_terminal) { + if (l->s.terminal == NULL) + return 0; + if (strmatch(event_terminal, + l->s.terminal) == 0) + return 0; + } + if (event_exe) { + if (l->s.exe == NULL) + return 0; + if (strmatch(event_exe, + l->s.exe) == 0) + return 0; + } + if (event_comm) { + if (l->s.comm == NULL) + return 0; + if (strmatch(event_comm, + l->s.comm) == 0) + return 0; + } + if (event_key) { + if (l->s.key == NULL) + return 0; + else { + int found = 0; + const snode *sn; + slist *sptr = l->s.key; + + slist_first(sptr); + sn=slist_get_cur(sptr); + do { + if (sn->str == NULL) + return 0; + if (strmatch( + event_key, + sn->str)) { + found = 1; + break; + } + } while ((sn=slist_next(sptr))); + if (!found) + return 0; + } + } + if (event_vmname) { + if (l->s.vmname == NULL) + return 0; + if (strmatch(event_vmname, + l->s.vmname) == 0) + return 0; + } + if (event_uuid) { + if (l->s.uuid == NULL) + return 0; + if (strmatch(event_uuid, + l->s.uuid) == 0) + return 0; + } + if (context_match(l) == 0) + return 0; + return 1; + } + } + } + return 0; +} + +/* + * This function compares strings. It returns a 0 if no match and a 1 if + * there is a match + */ +static int strmatch(const char *needle, const char *haystack) +{ + if (event_exact_match) { + if (strcmp(haystack, needle) != 0) + return 0; + } else { + if (strstr(haystack, needle) == NULL) + return 0; + } + return 1; +} + +/* + * This function compares user id's. It returns a 0 if no match and a 1 if + * there is a match + */ +static int user_match(llist *l) +{ + if (event_ua) { + // This will "or" the user tests + if (event_uid == l->s.uid) + return 1; + if (event_euid == l->s.euid) + return 1; + if (event_loginuid == l->s.loginuid) + return 1; + return 0; + } else { + // This will "and" the user tests + if ((event_uid != -1) && (event_uid != l->s.uid)) + return 0; + if ((event_euid != -1) &&(event_euid != l->s.euid)) + return 0; + if ((event_loginuid != -2) && + (event_loginuid != l->s.loginuid)) + return 0; + } + return 1; +} + +/* + * This function compares group id's. It returns a 0 if no match and a 1 if + * there is a match + */ +static int group_match(llist *l) +{ + if (event_ga) { + // This will "or" the group tests + if (event_gid == l->s.gid) + return 1; + if (event_egid == l->s.egid) + return 1; + return 0; + } else { + // This will "and" the group tests + if ((event_gid != -1) && (event_gid != l->s.gid)) + return 0; + if ((event_egid != -1) &&(event_egid != l->s.egid)) + return 0; + } + return 1; +} + +/* + * This function compares contexts. It returns a 0 if no match and a 1 if + * there is a match + */ +static int context_match(llist *l) +{ + if (event_se) { /* This does the "or" check if -se test */ + if (event_subject) { + if (l->s.avc && alist_find_subj(l->s.avc)) { + do { + if (strmatch(event_subject, + l->s.avc->cur->scontext)) + return 1; + } while(alist_next_subj(l->s.avc)); + } + } + if (event_object) { + if (l->s.avc) { + alist_first(l->s.avc); + if (alist_find_obj(l->s.avc)) { + do { + if (strmatch(event_object, + l->s.avc->cur->tcontext)) + return 1; + } while(alist_next_obj(l->s.avc)); + } + } + } + return 0; + } else { + if (event_subject) { + if (l->s.avc == NULL) + return 0; + if (alist_find_subj(l->s.avc)) { + do { + if (strmatch(event_subject, + l->s.avc->cur->scontext)) + return 1; + } while(alist_next_subj(l->s.avc)); + } + return 0; + } + if (event_object) { + if (l->s.avc == NULL) + return 0; + if (alist_find_obj(l->s.avc)) { + do { + if (strmatch(event_object, + l->s.avc->cur->tcontext)) + return 1; + } while(alist_next_obj(l->s.avc)); + } + return 0; + } + } + return 1; +} + diff --git a/framework/src/audit/src/ausearch-nvpair.c b/framework/src/audit/src/ausearch-nvpair.c new file mode 100644 index 00000000..3dfadb60 --- /dev/null +++ b/framework/src/audit/src/ausearch-nvpair.c @@ -0,0 +1,97 @@ +/* +* ausearch-nvpair.c - Minimal linked list library for name-value pairs +* Copyright (c) 2006-08 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include "config.h" +#include <stdlib.h> +#include "ausearch-nvpair.h" + + +void nvlist_create(nvlist *l) +{ + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + +nvnode *nvlist_next(nvlist *l) +{ + if (l->cur == NULL) + return NULL; + l->cur = l->cur->next; + return l->cur; +} + +void nvlist_append(nvlist *l, nvnode *node) +{ + nvnode* newnode = malloc(sizeof(nvnode)); + + newnode->name = node->name; + newnode->val = node->val; + newnode->next = NULL; + + // if we are at top, fix this up + if (l->head == NULL) + l->head = newnode; + else { // Add pointer to newnode and make sure we are at the end + while (l->cur->next) + l->cur = l->cur->next; + l->cur->next = newnode; + } + + // make newnode current + l->cur = newnode; + l->cnt++; +} + +int nvlist_find_val(nvlist *l, long val) +{ + register nvnode* window = l->head; + + while (window) { + if (window->val == val) { + l->cur = window; + return 1; + } + else + window = window->next; + } + return 0; +} + +void nvlist_clear(nvlist* l) +{ + nvnode* nextnode; + register nvnode* current; + + current = l->head; + while (current) { + nextnode=current->next; + free(current->name); + free(current); + current=nextnode; + } + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + diff --git a/framework/src/audit/src/ausearch-nvpair.h b/framework/src/audit/src/ausearch-nvpair.h new file mode 100644 index 00000000..9fb91ba9 --- /dev/null +++ b/framework/src/audit/src/ausearch-nvpair.h @@ -0,0 +1,57 @@ +/* +* ausearch-nvpair.h - Header file for ausearch-nvpair.c +* Copyright (c) 2006-08 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#ifndef AUNVPAIR_HEADER +#define AUNVPAIR_HEADER + +#include "config.h" +#include <sys/types.h> + +/* This is the node of the linked list. message & item are the only elements + * at this time. Any data elements that are per item goes here. */ +typedef struct _nvnode{ + char *name; // The name string + long val; // The value field + struct _nvnode* next; // Next nvpair node pointer +} nvnode; + +/* This is the linked list head. Only data elements that are 1 per + * event goes here. */ +typedef struct { + nvnode *head; // List head + nvnode *cur; // Pointer to current node + unsigned int cnt; // How many items in this list +} nvlist; + +void nvlist_create(nvlist *l); +static inline void nvlist_first(nvlist *l) { l->cur = l->head; } +nvnode *nvlist_next(nvlist *l); +static inline nvnode *nvlist_get_cur(nvlist *l) { return l->cur; } +void nvlist_append(nvlist *l, nvnode *node); +void nvlist_clear(nvlist* l); + +/* Given a numeric index, find that record. */ +int nvlist_find_val(nvlist *l, long val); + +#endif + diff --git a/framework/src/audit/src/ausearch-options.c b/framework/src/audit/src/ausearch-options.c new file mode 100644 index 00000000..50469723 --- /dev/null +++ b/framework/src/audit/src/ausearch-options.c @@ -0,0 +1,1175 @@ +/* ausearch-options.c - parse commandline options and configure ausearch + * Copyright 2005-08,2010-11,2014 Red Hat Inc., Durham, North Carolina. + * Copyright (c) 2011 IBM Corp. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Debora Velarde <dvelarde@us.ibm.com> + * Steve Grubb <sgrubb@redhat.com> + * Marcelo Henrique Cerri <mhcerri@br.ibm.com> + */ + +#include "config.h" +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <pwd.h> +#include <grp.h> +#include "ausearch-options.h" +#include "ausearch-time.h" +#include "ausearch-int.h" +#include "libaudit.h" + + +/* Global vars that will be accessed by the main program */ +char *user_file = NULL; +int force_logs = 0; + +/* Global vars that will be accessed by the match model */ +unsigned int event_id = -1; +gid_t event_gid = -1, event_egid = -1; +ilist *event_type = NULL; +pid_t event_pid = -1, event_ppid = -1; +success_t event_success = S_UNSET; +int event_exact_match = 0; +uid_t event_uid = -1, event_euid = -1, event_loginuid = -2; +int event_syscall = -1, event_machine = -1; +int event_ua = 0, event_ga = 0, event_se = 0; +int just_one = 0; +uint32_t event_session_id = -2; +long long event_exit = 0; +int event_exit_is_set = 0; +int line_buffered = 0; +int event_debug = 0; +int checkpt_timeonly = 0; +const char *event_key = NULL; +const char *event_filename = NULL; +const char *event_exe = NULL; +const char *event_comm = NULL; +const char *event_hostname = NULL; +const char *event_terminal = NULL; +const char *event_subject = NULL; +const char *event_object = NULL; +const char *event_uuid = NULL; +const char *event_vmname = NULL; +const char *checkpt_filename = NULL; /* checkpoint filename if present */ +report_t report_format = RPT_DEFAULT; +ilist *event_type; + +slist *event_node_list = NULL; + +struct nv_pair { + int value; + const char *name; +}; + + +enum { S_EVENT, S_COMM, S_FILENAME, S_ALL_GID, S_EFF_GID, S_GID, S_HELP, +S_HOSTNAME, S_INTERP, S_INFILE, S_MESSAGE_TYPE, S_PID, S_SYSCALL, S_OSUCCESS, +S_TIME_END, S_TIME_START, S_TERMINAL, S_ALL_UID, S_EFF_UID, S_UID, S_LOGINID, +S_VERSION, S_EXACT_MATCH, S_EXECUTABLE, S_CONTEXT, S_SUBJECT, S_OBJECT, +S_PPID, S_KEY, S_RAW, S_NODE, S_IN_LOGS, S_JUST_ONE, S_SESSION, S_EXIT, +S_LINEBUFFERED, S_UUID, S_VMNAME, S_DEBUG, S_CHECKPOINT, S_ARCH }; + +static struct nv_pair optiontab[] = { + { S_EVENT, "-a" }, + { S_ARCH, "--arch" }, + { S_EVENT, "--event" }, + { S_COMM, "-c" }, + { S_COMM, "--comm" }, + { S_CHECKPOINT, "--checkpoint" }, + { S_DEBUG, "--debug" }, + { S_EXIT, "-e" }, + { S_EXIT, "--exit" }, + { S_FILENAME, "-f" }, + { S_FILENAME, "--file" }, + { S_ALL_GID, "-ga" }, + { S_ALL_GID, "--gid-all" }, + { S_EFF_GID, "-ge" }, + { S_EFF_GID, "--gid-effective" }, + { S_GID, "-gi" }, + { S_GID, "--gid" }, + { S_HELP, "-h" }, + { S_HELP, "--help" }, + { S_HOSTNAME, "-hn" }, + { S_HOSTNAME, "--host" }, + { S_INTERP, "-i" }, + { S_INTERP, "--interpret" }, + { S_INFILE, "-if" }, + { S_INFILE, "--input" }, + { S_IN_LOGS, "--input-logs" }, + { S_JUST_ONE, "--just-one" }, + { S_KEY, "-k" }, + { S_KEY, "--key" }, + { S_LINEBUFFERED, "-l" }, + { S_LINEBUFFERED, "--line-buffered" }, + { S_MESSAGE_TYPE, "-m" }, + { S_MESSAGE_TYPE, "--message" }, + { S_NODE, "-n" }, + { S_NODE, "--node" }, + { S_OBJECT, "-o" }, + { S_OBJECT, "--object" }, + { S_PID, "-p" }, + { S_PID, "--pid" }, + { S_PPID, "-pp" }, + { S_PPID, "--ppid" }, + { S_RAW, "-r" }, + { S_RAW, "--raw" }, + { S_SYSCALL, "-sc" }, + { S_SYSCALL, "--syscall" }, + { S_CONTEXT, "-se" }, + { S_CONTEXT, "--context" }, + { S_SESSION, "--session" }, + { S_SUBJECT, "-su" }, + { S_SUBJECT, "--subject" }, + { S_OSUCCESS, "-sv" }, + { S_OSUCCESS, "--success" }, + { S_TIME_END, "-te"}, + { S_TIME_END, "--end"}, + { S_TIME_START, "-ts" }, + { S_TIME_START, "--start" }, + { S_TERMINAL, "-tm" }, + { S_TERMINAL, "--terminal" }, + { S_ALL_UID, "-ua" }, + { S_ALL_UID, "--uid-all" }, + { S_EFF_UID, "-ue" }, + { S_EFF_UID, "--uid-effective" }, + { S_UID, "-ui" }, + { S_UID, "--uid" }, + { S_UUID, "-uu" }, + { S_UUID, "--uuid" }, + { S_LOGINID, "-ul" }, + { S_LOGINID, "--loginuid" }, + { S_VERSION, "-v" }, + { S_VERSION, "--version" }, + { S_VMNAME, "-vm" }, + { S_VMNAME, "--vm-name" }, + { S_EXACT_MATCH, "-w" }, + { S_EXACT_MATCH, "--word" }, + { S_EXECUTABLE, "-x" }, + { S_EXECUTABLE, "--executable" } +}; +#define OPTION_NAMES (sizeof(optiontab)/sizeof(optiontab[0])) + + +static int audit_lookup_option(const char *name) +{ + int i; + + for (i = 0; i < OPTION_NAMES; i++) + if (!strcmp(optiontab[i].name, name)) + return optiontab[i].value; + return -1; +} + +static void usage(void) +{ + printf("usage: ausearch [options]\n" + "\t-a,--event <Audit event id>\tsearch based on audit event id\n" + "\t--arch <CPU>\t\t\tsearch based on the CPU architecture\n" + "\t-c,--comm <Comm name>\t\tsearch based on command line name\n" + "\t--checkpoint <checkpoint file>\tsearch from last complete event\n" + "\t--debug\t\t\tWrite malformed events that are skipped to stderr\n" + "\t-e,--exit <Exit code or errno>\tsearch based on syscall exit code\n" + "\t-f,--file <File name>\t\tsearch based on file name\n" + "\t-ga,--gid-all <all Group id>\tsearch based on All group ids\n" + "\t-ge,--gid-effective <effective Group id> search based on Effective\n\t\t\t\t\tgroup id\n" + "\t-gi,--gid <Group Id>\t\tsearch based on group id\n" + "\t-h,--help\t\t\thelp\n" + "\t-hn,--host <Host Name>\t\tsearch based on remote host name\n" + "\t-i,--interpret\t\t\tInterpret results to be human readable\n" + "\t-if,--input <Input File name>\tuse this file instead of current logs\n" + "\t--input-logs\t\t\tUse the logs even if stdin is a pipe\n" + "\t--just-one\t\t\tEmit just one event\n" + "\t-k,--key <key string>\t\tsearch based on key field\n" + "\t-l, --line-buffered\t\tFlush output on every line\n" + "\t-m,--message <Message type>\tsearch based on message type\n" + "\t-n,--node <Node name>\t\tsearch based on machine's name\n" + "\t-o,--object <SE Linux Object context> search based on context of object\n" + "\t-p,--pid <Process id>\t\tsearch based on process id\n" + "\t-pp,--ppid <Parent Process id>\tsearch based on parent process id\n" + "\t-r,--raw\t\t\toutput is completely unformatted\n" + "\t-sc,--syscall <SysCall name>\tsearch based on syscall name or number\n" + "\t-se,--context <SE Linux context> search based on either subject or\n\t\t\t\t\t object\n" + "\t--session <login session id>\tsearch based on login session id\n" + "\t-su,--subject <SE Linux context> search based on context of the Subject\n" + "\t-sv,--success <Success Value>\tsearch based on syscall or event\n\t\t\t\t\tsuccess value\n" + "\t-te,--end [end date] [end time]\tending date & time for search\n" + "\t-ts,--start [start date] [start time]\tstarting data & time for search\n" + "\t-tm,--terminal <TerMinal>\tsearch based on terminal\n" + "\t-ua,--uid-all <all User id>\tsearch based on All user id's\n" + "\t-ue,--uid-effective <effective User id> search based on Effective\n\t\t\t\t\tuser id\n" + "\t-ui,--uid <User Id>\t\tsearch based on user id\n" + "\t-ul,--loginuid <login id>\tsearch based on the User's Login id\n" + "\t-uu,--uuid <guest UUID>\t\tsearch for events related to the virtual\n" + "\t\t\t\t\tmachine with the given UUID.\n" + "\t-v,--version\t\t\tversion\n" + "\t-vm,--vm-name <guest name>\tsearch for events related to the virtual\n" + "\t\t\t\t\tmachine with the name.\n" + "\t-w,--word\t\t\tstring matches are whole word\n" + "\t-x,--executable <executable name> search based on executable name\n" + ); +} + +static int convert_str_to_msg(const char *optarg) +{ + int tmp, retval = 0; + + if (isdigit(optarg[0])) { + errno = 0; + tmp = strtoul(optarg, NULL, 10); + if (errno) { + fprintf(stderr, + "Numeric message type conversion error (%s) for %s\n", + strerror(errno), optarg); + retval = -1; + } + } else { + tmp = audit_name_to_msg_type(optarg); + if (tmp < 0) + retval = -1; + } + if (retval == 0) { + if (event_type == NULL) { + event_type = malloc(sizeof(ilist)); + if (event_type == NULL) + return -1; + ilist_create(event_type); + } + ilist_append(event_type, tmp, 1, 0); + } + return retval; +} + +static int parse_msg(const char *optarg) +{ + int retval = 0; + char *saved; + + if (strchr(optarg, ',')) { + char *ptr, *tmp = strdup(optarg); + if (tmp == NULL) + return -1; + ptr = strtok_r(tmp, ",", &saved); + while (ptr) { + retval = convert_str_to_msg(ptr); + if (retval != 0) + break; + ptr = strtok_r(NULL, ",", &saved); + } + free(tmp); + return retval; + } + + return convert_str_to_msg(optarg); +} + +/* + * This function examines the commandline parameters and sets various + * search options. It returns a 0 on success and < 0 on failure + */ +int check_params(int count, char *vars[]) +{ + int c = 1; + int retval = 0; + const char *optarg; + + if (count < 2) { + usage(); + return -1; + } + while (c < count && retval == 0) { + // Go ahead and point to the next argument + if (c+1 < count) { + if (vars[c+1][0] != '-') + optarg = vars[c+1]; + else + optarg = NULL; + } else + optarg = NULL; + + switch (audit_lookup_option(vars[c])) { + case S_EVENT: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_id = strtoul(optarg, NULL, 10); + if (errno) { + fprintf(stderr, + "Illegal value for audit event ID"); + retval = -1; + } + c++; + } else { + fprintf(stderr, + "Audit event id must be a numeric value, was %s\n", + optarg); + retval = -1; + } + break; + case S_COMM: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } else { + event_comm = strdup(optarg); + if (event_comm == NULL) + retval = -1; + c++; + } + break; + case S_FILENAME: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + event_filename = strdup(optarg); + if (event_filename == NULL) + retval = -1; + c++; + } + break; + case S_KEY: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + event_key = strdup(optarg); + if (event_key == NULL) + retval = -1; + c++; + } + break; + case S_ALL_GID: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_gid = strtoul(optarg,NULL,10); + if (errno) { + fprintf(stderr, + "Numeric group ID conversion error (%s) for %s\n", + strerror(errno), optarg); + retval = -1; + } + } else { + struct group *gr ; + + gr = getgrnam(optarg) ; + if (gr == NULL) { + fprintf(stderr, + "Group ID is non-numeric and unknown (%s)\n", + optarg); + retval = -1; + break; + } + event_gid = gr->gr_gid; + } + event_egid = event_gid; + event_ga = 1; + c++; + break; + case S_EFF_GID: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_egid = strtoul(optarg,NULL,10); + if (errno) { + fprintf(stderr, + "Numeric group ID conversion error (%s) for %s\n", + strerror(errno), optarg); + retval = -1; + } + } else { + struct group *gr ; + + gr = getgrnam(optarg) ; + if (gr == NULL) { + fprintf(stderr, + "Effective group ID is non-numeric and unknown (%s)\n", + optarg); + retval = -1; + break; + } + event_egid = gr->gr_gid; + } + c++; + break; + case S_GID: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_gid = strtoul(optarg,NULL,10); + if (errno) { + fprintf(stderr, + "Numeric group ID conversion error (%s) for %s\n", + strerror(errno), optarg); + retval = -1; + } + } else { + struct group *gr ; + + gr = getgrnam(optarg) ; + if (gr == NULL) { + fprintf(stderr, + "Group ID is non-numeric and unknown (%s)\n", + optarg); + retval = -1; + break; + } + event_gid = gr->gr_gid; + } + c++; + break; + case S_HELP: + usage(); + exit(0); + break; + case S_HOSTNAME: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + event_hostname = strdup(optarg); + if (event_hostname == NULL) + retval = -1; + c++; + } + break; + case S_INTERP: + if (report_format == RPT_DEFAULT) + report_format = RPT_INTERP; + else { + fprintf(stderr, + "Conflicting output format %s\n", + vars[c]); + retval = -1; + } + if (optarg) { + fprintf(stderr, + "Argument is NOT required for %s\n", + vars[c]); + retval = -1; + } + break; + case S_INFILE: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + user_file = strdup(optarg); + if (user_file == NULL) + retval = -1; + c++; + } + break; + case S_MESSAGE_TYPE: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + if (strcasecmp(optarg, "ALL") != 0) { + retval = parse_msg(optarg); + } + c++; + } + if (retval < 0) { + int i; + fprintf(stderr, + "Valid message types are: ALL "); + for (i=AUDIT_USER;i<=AUDIT_LAST_VIRT_MSG;i++){ + const char *name; + if (i == AUDIT_WATCH_INS) // Skip a few + i = AUDIT_FIRST_USER_MSG; + name = audit_msg_type_to_name(i); + if (name) + fprintf(stderr, "%s ", name); + } + fprintf(stderr, "\n"); + } + break; + case S_OBJECT: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } else { + event_object = strdup(optarg); + if (event_object == NULL) + retval = -1; + c++; + } + break; + case S_PPID: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_ppid = strtol(optarg,NULL,10); + if (errno) + retval = -1; + c++; + } else { + fprintf(stderr, + "Parent process id must be a numeric value, was %s\n", + optarg); + retval = -1; + } + break; + case S_PID: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_pid = strtol(optarg,NULL,10); + if (errno) + retval = -1; + c++; + } else { + fprintf(stderr, + "Process id must be a numeric value, was %s\n", + optarg); + retval = -1; + } + break; + case S_RAW: + if (report_format == RPT_DEFAULT) + report_format = RPT_RAW; + else { + fprintf(stderr, + "Conflicting output format --raw\n"); + retval = -1; + } + if (optarg) { + fprintf(stderr, + "Argument is NOT required for --raw\n"); + retval = -1; + } + break; + case S_NODE: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + snode sn; + c++; + + if (!event_node_list) { + event_node_list = malloc(sizeof (slist)); + if (!event_node_list) { + retval = -1; + break; + } + slist_create(event_node_list); + } + + sn.str = strdup(optarg); + sn.key = NULL; + sn.hits=0; + slist_append(event_node_list, &sn); + } + break; + case S_SYSCALL: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_syscall = (int)strtoul(optarg, NULL, 10); + if (errno) { + fprintf(stderr, + "Syscall numeric conversion error (%s) for %s\n", + strerror(errno), optarg); + retval = -1; + } + } else { + if (event_machine == -1) { + int machine; + machine = audit_detect_machine(); + if (machine < 0) { + fprintf(stderr, + "Error detecting machine type"); + retval = -1; + break; + } + event_machine = machine; + } + event_syscall = audit_name_to_syscall(optarg, + event_machine); + if (event_syscall == -1) { + fprintf(stderr, + "Syscall %s not found\n", + optarg); + retval = -1; + } + } + c++; + break; + case S_CONTEXT: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } else { + event_subject = strdup(optarg); + if (event_subject == NULL) + retval = -1; + event_object = strdup(optarg); + if (event_object == NULL) + retval = -1; + event_se = 1; + c++; + } + break; + case S_SUBJECT: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } else { + event_subject = strdup(optarg); + if (event_subject == NULL) + retval = -1; + c++; + } + break; + case S_OSUCCESS: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if ( (strstr(optarg, "yes")!=NULL) || + (strstr(optarg, "no")!=NULL) ) { + if (strcmp(optarg, "yes") == 0) + event_success = S_SUCCESS; + else + event_success = S_FAILED; + } else { + fprintf(stderr, + "Success must be 'yes' or 'no'.\n"); + retval = -1; + } + c++; + break; + case S_SESSION: + if (!optarg) { + if ((c+1 < count) && vars[c+1]) + optarg = vars[c+1]; + else { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + } + { + size_t len = strlen(optarg); + if (isdigit(optarg[0])) { + errno = 0; + event_session_id = strtoul(optarg,NULL,10); + if (errno) + retval = -1; + c++; + } else if (len >= 2 && *(optarg)=='-' && + (isdigit(optarg[1]))) { + errno = 0; + event_session_id = strtoul(optarg, NULL, 0); + if (errno) { + retval = -1; + fprintf(stderr, "Error converting %s\n", + optarg); + } + c++; + } else { + fprintf(stderr, + "Session id must be a numeric value, was %s\n", + optarg); + retval = -1; + } + } + break; + case S_EXIT: + if (!optarg) { + if ((c+1 < count) && vars[c+1]) + optarg = vars[c+1]; + else { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + } + { + size_t len = strlen(optarg); + if (isdigit(optarg[0])) { + errno = 0; + event_exit = strtoll(optarg, NULL, 0); + if (errno) { + retval = -1; + fprintf(stderr, "Error converting %s\n", + optarg); + } + } else if (len >= 2 && *(optarg)=='-' && + (isdigit(optarg[1]))) { + errno = 0; + event_exit = strtoll(optarg, NULL, 0); + if (errno) { + retval = -1; + fprintf(stderr, "Error converting %s\n", + optarg); + } + } else { + event_exit = audit_name_to_errno(optarg); + if (event_exit == 0) { + retval = -1; + fprintf(stderr, + "Unknown errno, was %s\n", + optarg); + } + } + c++; + if (retval != -1) + event_exit_is_set = 1; + } + break; + case S_TIME_END: + if (optarg) { + if ( (c+2 < count) && vars[c+2] && + (vars[c+2][0] != '-') ) { + /* Have both date and time - check order*/ + if (strchr(optarg, ':')) { + if (ausearch_time_end(vars[c+2], + optarg) != 0) + retval = -1; + } else { + if (ausearch_time_end(optarg, + vars[c+2]) != 0) + retval = -1; + } + c++; + } else { + // Check against recognized words + int t = lookup_time(optarg); + if (t >= 0) { + if (ausearch_time_end(optarg, + NULL) != 0) + retval = -1; + } else if ( (strchr(optarg, ':')) == NULL) { + /* Only have date */ + if (ausearch_time_end(optarg, + NULL) != 0) + retval = -1; + } else { + /* Only have time */ + if (ausearch_time_end(NULL, + optarg) != 0) + retval = -1; + } + } + c++; + break; + } + fprintf(stderr, + "%s requires either date and/or time\n", + vars[c]); + retval = -1; + break; + case S_TIME_START: + if (optarg) { + if ( (c+2 < count) && vars[c+2] && + (vars[c+2][0] != '-') ) { + /* Have both date and time - check order */ + if (strchr(optarg, ':')) { + if (ausearch_time_start( + vars[c+2], optarg) != 0) + retval = -1; + } else { + if (ausearch_time_start(optarg, + vars[c+2]) != 0) + retval = -1; + } + c++; + } else { + // Check against recognized words + int t = lookup_time(optarg); + if (t >= 0) { + if (ausearch_time_start(optarg, + "00:00:00") != 0) + retval = -1; + } else if (strcmp(optarg, + "checkpoint") == 0) { + // Only use the timestamp from within + // the checkpoint file + checkpt_timeonly++; + } else if (strchr(optarg, ':') == NULL){ + /* Only have date */ + if (ausearch_time_start(optarg, + "00:00:00") != 0) + retval = -1; + } else { + /* Only have time */ + if (ausearch_time_start(NULL, + optarg) != 0) + retval = -1; + } + } + c++; + break; + } + fprintf(stderr, + "%s requires either date and/or time\n", + vars[c]); + retval = -1; + break; + case S_TERMINAL: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + event_terminal = strdup(optarg); + if (event_terminal == NULL) + retval = -1; + c++; + } + break; + case S_UID: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_uid = strtoul(optarg,NULL,10); + if (errno) { + fprintf(stderr, + "Numeric user ID conversion error (%s) for %s\n", + strerror(errno), optarg); + retval = -1; + } + } else { + struct passwd *pw; + + pw = getpwnam(optarg); + if (pw == NULL) { + fprintf(stderr, + "Effective user ID is non-numeric and unknown (%s)\n", + optarg); + retval = -1; + break; + } + event_uid = pw->pw_uid; + } + c++; + break; + case S_EFF_UID: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_euid = strtoul(optarg,NULL,10); + if (errno) { + fprintf(stderr, + "Numeric user ID conversion error (%s) for %s\n", + strerror(errno), optarg); + retval = -1; + } + } else { + struct passwd *pw ; + + pw = getpwnam(optarg) ; + if (pw == NULL) { + fprintf(stderr, + "User ID is non-numeric and unknown (%s)\n", + optarg); + retval = -1; + break; + } + event_euid = pw->pw_uid; + } + c++; + break; + case S_ALL_UID: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (isdigit(optarg[0])) { + errno = 0; + event_uid = strtoul(optarg,NULL,10); + if (errno) { + fprintf(stderr, + "Numeric user ID conversion error (%s) for %s\n", + strerror(errno), optarg); + retval = -1; + } + } else { + struct passwd *pw ; + + pw = getpwnam(optarg) ; + if (pw == NULL) { + fprintf(stderr, + "User ID is non-numeric and unknown (%s)\n", + optarg); + retval = -1; + break; + } + event_uid = pw->pw_uid; + } + event_ua = 1; + event_euid = event_uid; + event_loginuid = event_uid; + c++; + break; + case S_LOGINID: + if (!optarg) { + if ((c+1 < count) && vars[c+1]) + optarg = vars[c+1]; + else { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + } + { + size_t len = strlen(optarg); + if (isdigit(optarg[0])) { + errno = 0; + event_loginuid = strtoul(optarg,NULL,10); + if (errno) { + fprintf(stderr, + "Numeric user ID conversion error (%s) for %s\n", + strerror(errno), optarg); + retval = -1; + } + } else if (len >= 2 && *(optarg)=='-' && + (isdigit(optarg[1]))) { + errno = 0; + event_loginuid = strtol(optarg, NULL, 0); + if (errno) { + retval = -1; + fprintf(stderr, "Error converting %s\n", + optarg); + } + } else { + struct passwd *pw ; + + pw = getpwnam(optarg) ; + if (pw == NULL) { + fprintf(stderr, + "Login user ID is non-numeric and unknown (%s)\n", + optarg); + retval = -1; + break; + } + event_loginuid = pw->pw_uid; + } + } + c++; + break; + case S_UUID: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + event_uuid = strdup(optarg); + if (event_uuid == NULL) { + retval = -1; + } + c++; + } + break; + case S_VMNAME: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + event_vmname= strdup(optarg); + if (event_vmname == NULL) { + retval = -1; + } + c++; + } + break; + case S_VERSION: + printf("ausearch version %s\n", VERSION); + exit(0); + break; + case S_EXACT_MATCH: + event_exact_match=1; + break; + case S_IN_LOGS: + force_logs = 1; + break; + case S_JUST_ONE: + just_one = 1; + break; + case S_EXECUTABLE: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + event_exe = strdup(optarg); + if (event_exe == NULL) + retval = -1; + c++; + } + break; + case S_LINEBUFFERED: + line_buffered = 1; + break; + case S_DEBUG: + event_debug = 1; + break; + case S_CHECKPOINT: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + } else { + checkpt_filename = strdup(optarg); + if (checkpt_filename == NULL) + retval = -1; + c++; + } + break; + case S_ARCH: + if (!optarg) { + fprintf(stderr, + "Argument is required for %s\n", + vars[c]); + retval = -1; + break; + } + if (event_machine != -1) { + if (event_syscall != -1) + fprintf(stderr, + "Arch needs to be defined before the syscall\n"); + else + fprintf(stderr, + "Arch is already defined\n"); + retval = -1; + break; + } else { + int machine = audit_determine_machine(optarg); + if (machine < 0) { + fprintf(stderr, "Unknown arch %s\n", + optarg); + retval = -1; + } + event_machine = machine; + } + c++; + break; + default: + fprintf(stderr, "%s is an unsupported option\n", + vars[c]); + retval = -1; + break; + } + c++; + } + + return retval; +} + diff --git a/framework/src/audit/src/ausearch-options.h b/framework/src/audit/src/ausearch-options.h new file mode 100644 index 00000000..1372762b --- /dev/null +++ b/framework/src/audit/src/ausearch-options.h @@ -0,0 +1,52 @@ +/* ausearch-options.h -- + * Copyright 2005,2008,2010 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#ifndef AUSEARCH_OPTIONS_H +#define AUSEARCH_OPTIONS_H + +#include <time.h> +#include <sys/types.h> +#include <stdint.h> +#include "ausearch-common.h" +#include "ausearch-int.h" + +/* Global variables that describe what search is to be performed */ +extern const char *event_key; +extern const char *event_subject; +extern const char *event_object; +extern int event_se; +extern int just_one; +extern int line_buffered; +extern int event_debug; +extern pid_t event_ppid; +extern uint32_t event_session_id; +extern ilist *event_type; + +/* Data type to govern output format */ +extern report_t report_format; + +/* Function to process commandline options */ +extern int check_params(int count, char *vars[]); + +#endif + diff --git a/framework/src/audit/src/ausearch-parse.c b/framework/src/audit/src/ausearch-parse.c new file mode 100644 index 00000000..1fb1c151 --- /dev/null +++ b/framework/src/audit/src/ausearch-parse.c @@ -0,0 +1,2310 @@ +/* +* ausearch-parse.c - Extract interesting fields and check for match +* Copyright (c) 2005-08,2011,2013-14 Red Hat Inc., Durham, North Carolina. +* Copyright (c) 2011 IBM Corp. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +* Marcelo Henrique Cerri <mhcerri@br.ibm.com> +*/ + +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netdb.h> +#include <limits.h> /* PATH_MAX */ +#include <ctype.h> +#include "libaudit.h" +#include "ausearch-options.h" +#include "ausearch-lookup.h" +#include "ausearch-parse.h" + +#define NAME_OFFSET 36 +static const char key_sep[2] = { AUDIT_KEY_SEPARATOR, 0 }; + +static int parse_syscall(lnode *n, search_items *s); +static int parse_dir(const lnode *n, search_items *s); +static int common_path_parser(search_items *s, char *path); +static int avc_parse_path(const lnode *n, search_items *s); +static int parse_path(const lnode *n, search_items *s); +static int parse_user(const lnode *n, search_items *s); +static int parse_obj(const lnode *n, search_items *s); +static int parse_login(const lnode *n, search_items *s); +static int parse_daemon1(const lnode *n, search_items *s); +static int parse_daemon2(const lnode *n, search_items *s); +static int parse_sockaddr(const lnode *n, search_items *s); +static int parse_avc(const lnode *n, search_items *s); +static int parse_integrity(const lnode *n, search_items *s); +static int parse_kernel_anom(const lnode *n, search_items *s); +static int parse_simple_message(const lnode *n, search_items *s); +static int parse_tty(const lnode *n, search_items *s); +static int parse_pkt(const lnode *n, search_items *s); + + +static int audit_avc_init(search_items *s) +{ + if (s->avc == NULL) { + //create + s->avc = malloc(sizeof(alist)); + if (s->avc == NULL) + return -1; + alist_create(s->avc); + } + return 0; +} + +/* + * This function will take the list and extract the searchable fields from it. + * It returns 0 on success and 1 on failure. + */ +int extract_search_items(llist *l) +{ + int ret = 0; + lnode *n; + search_items *s = &l->s; + list_first(l); + n = list_get_cur(l); + if (n) { + do { + switch (n->type) { + case AUDIT_SYSCALL: + ret = parse_syscall(n, s); + break; + case AUDIT_CWD: + ret = parse_dir(n, s); + break; + case AUDIT_AVC_PATH: + ret = avc_parse_path(n, s); + break; + case AUDIT_PATH: + ret = parse_path(n, s); + break; + case AUDIT_USER: + case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG: + case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2: + ret = parse_user(n, s); + break; + case AUDIT_SOCKADDR: + ret = parse_sockaddr(n, s); + break; + case AUDIT_LOGIN: + ret = parse_login(n, s); + break; + case AUDIT_IPC: + case AUDIT_OBJ_PID: + ret = parse_obj(n, s); + break; + case AUDIT_DAEMON_START: + case AUDIT_DAEMON_END: + case AUDIT_DAEMON_ABORT: + case AUDIT_DAEMON_CONFIG: + case AUDIT_DAEMON_ROTATE: + case AUDIT_DAEMON_RESUME: + ret = parse_daemon1(n, s); + break; + case AUDIT_DAEMON_ACCEPT: + case AUDIT_DAEMON_CLOSE: + ret = parse_daemon2(n, s); + break; + case AUDIT_CONFIG_CHANGE: + ret = parse_simple_message(n, s); + // We use AVC parser because it just looks for + // the one field. We don't care about return + // code since older events don't have path= + avc_parse_path(n, s); + break; + case AUDIT_AVC: + ret = parse_avc(n, s); + break; + case AUDIT_NETFILTER_PKT: + ret = parse_pkt(n, s); + break; + case AUDIT_SECCOMP: + case + AUDIT_FIRST_KERN_ANOM_MSG...AUDIT_LAST_KERN_ANOM_MSG: + ret = parse_kernel_anom(n, s); + break; + case AUDIT_MAC_POLICY_LOAD...AUDIT_MAC_UNLBL_STCDEL: + ret = parse_simple_message(n, s); + break; + case AUDIT_INTEGRITY_DATA...AUDIT_INTEGRITY_RULE: + ret = parse_integrity(n, s); + break; + case AUDIT_KERNEL: + case AUDIT_SELINUX_ERR: + case AUDIT_EXECVE: + case AUDIT_IPC_SET_PERM: + case AUDIT_MQ_OPEN: + case AUDIT_MQ_SENDRECV: + case AUDIT_MQ_NOTIFY: + case AUDIT_MQ_GETSETATTR: + case AUDIT_FD_PAIR: + case AUDIT_BPRM_FCAPS: + case AUDIT_CAPSET: + case AUDIT_MMAP: + case AUDIT_NETFILTER_CFG: + // Nothing to parse + break; + case AUDIT_TTY: + ret = parse_tty(n, s); + break; + default: + if (event_debug) + fprintf(stderr, + "Unparsed type:%d\n - skipped", + n->type); + break; + } + if (event_debug && ret) + fprintf(stderr, + "Malformed event skipped, rc=%d. %s\n", + ret, n->message); + } while ((n=list_next(l)) && ret == 0); + } + return ret; +} + +static int parse_syscall(lnode *n, search_items *s) +{ + char *ptr, *str, *term; + extern int event_machine; + + term = n->message; + if (report_format > RPT_DEFAULT || event_machine != -1) { + // get arch + str = strstr(term, "arch="); + if (str == NULL) + return 1; + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 2; + *term = 0; + errno = 0; + s->arch = (int)strtoul(ptr, NULL, 16); + if (errno) + return 3; + *term = ' '; + } + // get syscall + str = strstr(term, "syscall="); + if (str == NULL) + return 4; + ptr = str + 8; + term = strchr(ptr, ' '); + if (term == NULL) + return 5; + *term = 0; + errno = 0; + s->syscall = (int)strtoul(ptr, NULL, 10); + if (errno) + return 6; + *term = ' '; + // get success + if (event_success != S_UNSET) { + str = strstr(term, "success="); + if (str) { // exit_group does not set success !?! + ptr = str + 8; + term = strchr(ptr, ' '); + if (term == NULL) + return 7; + *term = 0; + if (strcmp(ptr, "yes") == 0) + s->success = S_SUCCESS; + else + s->success = S_FAILED; + *term = ' '; + } + } + // get exit + if (event_exit_is_set) { + str = strstr(term, "exit="); + if (str == NULL) + return 8; + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 9; + *term = 0; + errno = 0; + s->exit = strtoll(ptr, NULL, 0); + if (errno) + return 10; + s->exit_is_set = 1; + *term = ' '; + } + // get a0 + str = strstr(term, "a0="); + if (str == NULL) + return 11; + ptr = str + 3; + term = strchr(ptr, ' '); + if (term == NULL) + return 12; + *term = 0; + errno = 0; + // 64 bit dump on 32 bit machine looks bad here - need long long + n->a0 = strtoull(ptr, NULL, 16); // Hex + if (errno) + return 13; + *term = ' '; + // get a1 + str = strstr(term, "a1="); + if (str == NULL) + return 11; + ptr = str + 3; + term = strchr(ptr, ' '); + if (term == NULL) + return 12; + *term = 0; + errno = 0; + // 64 bit dump on 32 bit machine looks bad here - need long long + n->a1 = strtoull(ptr, NULL, 16); // Hex + if (errno) + return 13; + *term = ' '; + // ppid + if (event_ppid != -1) { + str = strstr(term, "ppid="); + if (str != NULL) { // ppid is an optional field + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 14; + *term = 0; + errno = 0; + s->ppid = strtoul(ptr, NULL, 10); + if (errno) + return 15; + *term = ' '; + } + } + // pid + if (event_pid != -1) { + str = strstr(term, " pid="); + if (str == NULL) + return 16; + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 17; + *term = 0; + errno = 0; + s->pid = strtoul(ptr, NULL, 10); + if (errno) + return 18; + *term = ' '; + } + // optionally get loginuid + if (event_loginuid != -2) { + str = strstr(term, "auid="); + if (str == NULL) { + str = strstr(term, "loginuid="); + if (str == NULL) + return 19; + ptr = str + 9; + } else + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 20; + *term = 0; + errno = 0; + s->loginuid = strtoul(ptr, NULL, 10); + if (errno) + return 21; + *term = ' '; + } + // optionally get uid + if (event_uid != -1) { + str = strstr(term, "uid="); + if (str == NULL) + return 22; + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 23; + *term = 0; + errno = 0; + s->uid = strtoul(ptr, NULL, 10); + if (errno) + return 24; + *term = ' '; + } + + // optionally get gid + if (event_gid != -1) { + str = strstr(term, "gid="); + if (str == NULL) + return 25; + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 26; + *term = 0; + errno = 0; + s->gid = strtoul(ptr, NULL, 10); + if (errno) + return 27; + *term = ' '; + } + + // euid + if (event_euid != -1) { + str = strstr(term, "euid="); + if (str == NULL) + return 28; + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 29; + *term = 0; + errno = 0; + s->euid = strtoul(ptr, NULL, 10); + if (errno) + return 30; + *term = ' '; + } + + // egid + if (event_egid != -1) { + str = strstr(term, "egid="); + if (str == NULL) + return 31; + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 32; + *term = 0; + errno = 0; + s->egid = strtoul(ptr, NULL, 10); + if (errno) + return 33; + *term = ' '; + } + + if (event_terminal) { + // dont do this search unless needed + str = strstr(term, "tty="); + if (str) { + str += 4; + term = strchr(str, ' '); + if (term == NULL) + return 34; + *term = 0; + s->terminal = strdup(str); + *term = ' '; + } + } + // ses + if (event_session_id != -2 ) { + str = strstr(term, "ses="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 35; + *term = 0; + errno = 0; + s->session_id = strtoul(ptr, NULL, 10); + if (errno) + return 36; + *term = ' '; + } + } + if (event_comm) { + // dont do this search unless needed + str = strstr(term, "comm="); + if (str) { + /* Make the syscall one override */ + if (s->comm) + free(s->comm); + str += 5; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 37; + *term = 0; + s->comm = strdup(str); + *term = '"'; + } else + s->comm = unescape(str); + } else + return 38; + } + if (event_exe) { + // dont do this search unless needed + str = strstr(n->message, "exe="); + if (str) { + str += 4; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 39; + *term = 0; + s->exe = strdup(str); + *term = '"'; + } else + s->exe = unescape(str); + } else + return 40; + } + if (event_subject) { + // scontext + str = strstr(term, "subj="); + if (str != NULL) { + str += 5; + term = strchr(str, ' '); + if (term == NULL) + return 41; + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.scontext = strdup(str); + alist_append(s->avc, &an); + *term = ' '; + } else + return 42; + } + } + if (event_key) { + str = strstr(term, "key="); + if (str) { + if (!s->key) { + //create + s->key = malloc(sizeof(slist)); + if (s->key == NULL) + return 43; + slist_create(s->key); + } + str += 4; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 44; + *term = 0; + if (s->key) { + // append + snode sn; + sn.str = strdup(str); + sn.key = NULL; + sn.hits = 1; + slist_append(s->key, &sn); + } + *term = '"'; + } else { + if (s->key) { + char *saved; + char *keyptr = unescape(str); + char *kptr = strtok_r(keyptr, + key_sep, &saved); + while (kptr) { + snode sn; + // append + sn.str = strdup(kptr); + sn.key = NULL; + sn.hits = 1; + slist_append(s->key, &sn); + kptr = strtok_r(NULL, + key_sep, &saved); + } + free(keyptr); + + } + } + } + } + return 0; +} + +static int parse_dir(const lnode *n, search_items *s) +{ + char *str, *term; + + if (event_filename) { + // dont do this search unless needed + str = strstr(n->message+NAME_OFFSET, " cwd="); + if (str) { + str += 5; + if (*str == '"') { + /* string is normal */ + str++; + term = strchr(str, '"'); + if (term == NULL) + return 1; + *term = 0; + if (!s->cwd) + s->cwd = strdup(str); + *term = '"'; + } else if (!s->cwd) + s->cwd = unescape(str); + } + } + return 0; +} + +static int common_path_parser(search_items *s, char *path) +{ + char *term; + + if (!s->filename) { + //create + s->filename = malloc(sizeof(slist)); + if (s->filename == NULL) + return 1; + slist_create(s->filename); + } + if (*path == '"') { + /* string is normal */ + path++; + term = strchr(path, '"'); + if (term == NULL) + return 2; + *term = 0; + if (s->filename) { + // append + snode sn; + sn.str = strdup(path); + sn.key = NULL; + sn.hits = 1; + // Attempt to rebuild path if relative + if ((sn.str[0] == '.') && ((sn.str[1] == '.') || + (sn.str[1] == '/')) && s->cwd) { + char *tmp = malloc(PATH_MAX); + if (tmp == NULL) { + free(sn.str); + return 3; + } + snprintf(tmp, PATH_MAX, + "%s/%s", s->cwd, sn.str); + free(sn.str); + sn.str = tmp; + } + slist_append(s->filename, &sn); + } + *term = '"'; + } else { + if (s->filename) { + // append + snode sn; + sn.key = NULL; + sn.hits = 1; + if (strncmp(path, "(null)", 6) == 0) { + sn.str = strdup("(null)"); + goto append; + } + if (!isxdigit(path[0])) + return 4; + if (path[0] == '0' && path[1] == '0') + sn.str = unescape(&path[2]); // Abstract name + else { + term = strchr(path, ' '); + if (term == NULL) + return 5; + *term = 0; + sn.str = unescape(path); + *term = ' '; + } + // Attempt to rebuild path if relative + if ((sn.str[0] == '.') && ((sn.str[1] == '.') || + (sn.str[1] == '/')) && s->cwd) { + char *tmp = malloc(PATH_MAX); + if (tmp == NULL) + return 6; + snprintf(tmp, PATH_MAX, "%s/%s", + s->cwd, sn.str); + free(sn.str); + sn.str = tmp; + } +append: + slist_append(s->filename, &sn); + } + } + return 0; +} + +/* Older AVCs have path separate from the AVC record */ +static int avc_parse_path(const lnode *n, search_items *s) +{ + char *str; + + if (event_filename) { + // dont do this search unless needed + str = strstr(n->message, " path="); + if (str) { + str += 6; + return common_path_parser(s, str); + } + return 1; + } + return 0; +} + +static int parse_path(const lnode *n, search_items *s) +{ + // We add 32 to message because we do not nee to look at + // anything before that. Its only time and type. + char *str, *term = n->message+NAME_OFFSET; + + if (event_filename) { + // dont do this search unless needed + str = strstr(term, " name="); + if (str) { + int rc; + str += 6; + rc = common_path_parser(s, str); + if (rc) + return rc; + } + } + if (event_object) { + // tcontext + str = strstr(term, "obj="); + if (str != NULL) { + str += 4; + term = strchr(str, ' '); + if (term) + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.tcontext = strdup(str); + alist_append(s->avc, &an); + if (term) + *term = ' '; + } else + return 7; + } + } + return 0; +} + +static int parse_obj(const lnode *n, search_items *s) +{ + char *str, *term; + + term = n->message; + if (event_object) { + // obj context + str = strstr(term, "obj="); + if (str != NULL) { + str += 4; + term = strchr(str, ' '); + if (term) + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.tcontext = strdup(str); + alist_append(s->avc, &an); + if (term) + *term = ' '; + } else + return 1; + } + } + return 0; +} + +static int parse_user(const lnode *n, search_items *s) +{ + char *ptr, *str, *term, saved, *mptr; + + term = n->message; + + // get pid + if (event_pid != -1) { + str = strstr(term, "pid="); + if (str == NULL) + return 1; + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 2; + *term = 0; + errno = 0; + s->pid = strtoul(ptr, NULL, 10); + if (errno) + return 3; + *term = ' '; + } + // optionally get uid + if (event_uid != -1) { + str = strstr(term, "uid="); + if (str == NULL) + return 4; + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 5; + *term = 0; + errno = 0; + s->uid = strtoul(ptr, NULL, 10); + if (errno) + return 6; + *term = ' '; + } + // optionally get loginuid + if (event_loginuid != -2) { + *term = ' '; + str = strstr(term, "auid="); + if (str == NULL) { // Try the older one + str = strstr(term, "loginuid="); + if (str == NULL) + return 7; + ptr = str + 9; + } else + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 8; + *term = 0; + errno = 0; + s->loginuid = strtoul(ptr, NULL, 10); + if (errno) + return 9; + *term = ' '; + } + // ses + if (event_session_id != -2 ) { + str = strstr(term, "ses="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 10; + *term = 0; + errno = 0; + s->session_id = strtoul(ptr, NULL, 10); + if (errno) + return 11; + *term = ' '; + } + } + if (event_subject) { + // scontext + str = strstr(term, "subj="); + if (str != NULL) { + str += 5; + term = strchr(str, ' '); + if (term == NULL) + return 12; + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.scontext = strdup(str); + alist_append(s->avc, &an); + *term = ' '; + } else + return 13; + } + } + // optionally get gid + if (event_gid != -1) { + if (n->type == AUDIT_ADD_GROUP || n->type == AUDIT_DEL_GROUP || + n->type == AUDIT_GRP_MGMT) { + str = strstr(term, " id="); + // Take second shot in the case of MGMT events + if (str == NULL && n->type == AUDIT_GRP_MGMT) + str = strstr(term, "gid="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 31; + *term = 0; + errno = 0; + s->gid = strtoul(ptr, NULL, 10); + if (errno) + return 32; + *term = ' '; + } + } + } + if (event_vmname) { + str = strstr(term, "vm="); + if (str) { + str += 3; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 23; + *term = 0; + s->vmname = strdup(str); + *term = '"'; + } else + s->vmname = unescape(str); + } + } + if (event_uuid) { + str = strstr(term, "uuid="); + if (str) { + str += 5; + term = str; + while (*term != ' ' && *term != ':') + term++; + if (term == str) + return 24; + saved = *term; + *term = 0; + s->uuid = strdup(str); + *term = saved; + } + } + if (event_subject) { + str = strstr(term, "vm-ctx="); + if (str != NULL) { + str += 7; + term = strchr(str, ' '); + if (term == NULL) + return 27; + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.scontext = strdup(str); + alist_append(s->avc, &an); + *term = ' '; + } else + return 28; + } + } + if (event_object) { + str = strstr(term, "img-ctx="); + if (str != NULL) { + str += 8; + term = strchr(str, ' '); + if (term == NULL) + return 29; + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.tcontext = strdup(str); + alist_append(s->avc, &an); + *term = ' '; + } else + return 30; + } + } + // optionally get uid - some records the second uid is what we want. + // USER_LOGIN for example. + if (event_uid != -1) { + str = strstr(term, "uid="); + if (str) { + if (*(str - 1) == 'a' || *(str - 1) == 's' || + *(str - 1) == 'u') + goto skip; + if (!(*(str - 1) == '\'' || *(str - 1) == ' ')) + return 25; + ptr = str + 4; + term = ptr; + while (isdigit(*term)) + term++; + if (term == ptr) + return 14; + + saved = *term; + *term = 0; + errno = 0; + s->uid = strtoul(ptr, NULL, 10); + if (errno) + return 15; + *term = saved; + } + } +skip: + mptr = term + 1; + + if (event_comm) { + // dont do this search unless needed + str = strstr(mptr, "comm="); + if (str) { + str += 5; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 16; + *term = 0; + s->comm = strdup(str); + *term = '"'; + } else + s->comm = unescape(str); + } + } + + // Get acct for user/group add/del + str = strstr(mptr, "acct="); + if (str != NULL) { + ptr = str + 5; + term = ptr + 1; + if (*ptr == '"') { + while (*term != '"') + term++; + saved = *term; + *term = 0; + ptr++; + s->acct = strdup(ptr); + *term = saved; + } else { + /* Handle legacy accts */ + char *end = ptr; + int legacy = 0; + + while (*end != ' ') { + if (!isxdigit(*end)) + legacy = 1; + end++; + } + term = end; + if (!legacy) + s->acct = unescape(ptr); + else { + saved = *term; + *term = 0; + s->acct = strdup(ptr); + *term = saved; + } + } + } + mptr = term + 1; + + // get hostname + if (event_hostname) { + // dont do this search unless needed + str = strstr(mptr, "hostname="); + if (str) { + str += 9; + term = strchr(str, ','); + if (term == NULL) { + term = strchr(str, ' '); + if (term == NULL) + return 17; + } + saved = *term; + *term = 0; + s->hostname = strdup(str); + *term = saved; + + // Lets see if there is something more + // meaningful in addr + if (strcmp(s->hostname, "?") == 0) { + term++; + str = strstr(term, "addr="); + if (str) { + str += 5; + term = strchr(str, ','); + if (term == NULL) { + term = strchr(str, ' '); + if (term == NULL) + return 18; + } + saved = *term; + *term = 0; + free(s->hostname); + s->hostname = strdup(str); + *term = saved; + } + } + } + } + if (event_filename) { + // dont do this search unless needed + str = strstr(mptr, "cwd="); + if (str) { + str += 4; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 20; + *term = 0; + s->cwd = strdup(str); + *term = '"'; + } else { + char *end = str; + int legacy = 0; + + while (*end != ' ') { + if (!isxdigit(*end)) { + legacy = 1; + } + end++; + } + term = end; + if (!legacy) + s->cwd = unescape(str); + else { + saved = *term; + *term = 0; + s->cwd = strdup(str); + *term = saved; + } + } + } + } + if (event_terminal) { + // dont do this search unless needed + str = strstr(mptr, "terminal="); + if (str) { + str += 9; + term = strchr(str, ' '); + if (term == NULL) { + term = strchr(str, ')'); + if (term == NULL) + return 19; + } + *term = 0; + s->terminal = strdup(str); + *term = ' '; + } + } + if (event_exe) { + // dont do this search unless needed + str = strstr(mptr, "exe="); + if (str) { + str += 4; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 26; + *term = 0; + s->exe = strdup(str); + *term = '"'; + } else { + char *end = str; + int legacy = 0; + + while (*end != ' ') { + if (!isxdigit(*end)) { + legacy = 1; + } + end++; + } + term = end; + if (!legacy) + s->exe = unescape(str); + else { + saved = *term; + *term = 0; + s->exe = strdup(str); + *term = saved; + } + } + } + } + + // get success + if (event_success != S_UNSET) { + str = strstr(mptr, "res="); + if (str) { + ptr = str + 4; + term = strchr(ptr, '\''); + if (term == NULL) + return 21; + *term = 0; + if (strncmp(ptr, "failed", 6) == 0) + s->success = S_FAILED; + else + s->success = S_SUCCESS; + *term = '\''; + } else if ((str = strstr(mptr, "result="))) { + ptr = str + 7; + term = strchr(ptr, ')'); + if (term == NULL) + return 22; + *term = 0; + if (strcasecmp(ptr, "success") == 0) + s->success = S_SUCCESS; + else + s->success = S_FAILED; + *term = ')'; + } + } + /* last return code used = 24 */ + return 0; +} + +static int parse_login(const lnode *n, search_items *s) +{ + char *ptr, *str, *term = n->message; + + // get pid + if (event_pid != -1) { + str = strstr(term, "pid="); + if (str == NULL) + return 1; + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 2; + *term = 0; + errno = 0; + s->pid = strtoul(ptr, NULL, 10); + if (errno) + return 3; + *term = ' '; + } + // optionally get uid + if (event_uid != -1) { + str = strstr(term, "uid="); + if (str == NULL) + return 4; + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 5; + *term = 0; + errno = 0; + s->uid = strtoul(ptr, NULL, 10); + if (errno) + return 6; + *term = ' '; + } + // optionally get subj + if (event_subject) { + str = strstr(term, "subj="); + if (str) { + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 12; + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.scontext = strdup(str); + alist_append(s->avc, &an); + *term = ' '; + } else + return 13; + *term = ' '; + } + } + // optionally get loginuid + if (event_loginuid != -2) { + str = strstr(term, "new auid="); + if (str == NULL) { + // 3.14 kernel changed it to the next line + str = strstr(term, " auid="); + if (str == NULL) { + str = strstr(term, "new loginuid="); + if (str == NULL) + return 7; + ptr = str + 13; + } else + ptr = str + 6; + } else + ptr = str + 9; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->loginuid = strtoul(ptr, NULL, 10); + if (errno) + return 8; + if (term) + *term = ' '; + } + + // success + if (event_success != S_UNSET) { + if (term == NULL) + term = n->message; + str = strstr(term, "res="); + if (str != NULL) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->success = strtoul(ptr, NULL, 10); + if (errno) + return 9; + if (term) + *term = ' '; + } else // Assume older kernel where always successful + s->success = S_SUCCESS; + } + // ses + if (event_session_id != -2 ) { + if (term == NULL) + term = n->message; + str = strstr(term, "new ses="); + if (str == NULL) { + // The 3.14 kernel changed it to the next line + str = strstr(term, " ses="); + if (str == NULL) + return 14; + ptr = str + 5; + } + else + ptr = str + 8; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->session_id = strtoul(ptr, NULL, 10); + if (errno) + return 11; + if (term) + *term = ' '; + } + return 0; +} + +static int parse_daemon1(const lnode *n, search_items *s) +{ + char *ptr, *str, *term, saved, *mptr; + + // Not all messages have a ')', use it if its there + mptr = strchr(n->message, ')'); + if (mptr == NULL) + mptr = n->message; + term = mptr; + + // optionally get auid + if (event_loginuid != -2 ) { + str = strstr(mptr, "auid="); + if (str == NULL) + return 1; + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 2; + saved = *term; + *term = 0; + errno = 0; + s->loginuid = strtoul(ptr, NULL, 10); + if (errno) + return 3; + *term = saved; + } + + // pid + if (event_pid != -1) { + str = strstr(term, "pid="); + if (str == NULL) + return 4; + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 5; + saved = *term; + *term = 0; + errno = 0; + s->pid = strtoul(ptr, NULL, 10); + if (errno) + return 6; + *term = saved; + } + + if (event_subject) { + // scontext + str = strstr(term, "subj="); + if (str != NULL) { + str += 5; + term = strchr(str, ' '); + if (term) + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.scontext = strdup(str); + alist_append(s->avc, &an); + } else + return 7; + if (term) + *term = ' '; + } + } + + // success + if (event_success != S_UNSET) { + str = strstr(mptr, "res="); + if (str) { + ptr = term = str + 4; + while (isalpha(*term)) + term++; + if (term == ptr) + return 9; + saved = *term; + *term = 0; + if (strncmp(ptr, "failed", 6) == 0) + s->success = S_FAILED; + else + s->success = S_SUCCESS; + *term = saved; + } + } + + return 0; +} + +static int parse_daemon2(const lnode *n, search_items *s) +{ + char *str, saved, *term = n->message; + + if (event_hostname) { + str = strstr(term, "addr="); + if (str) { + str += 5; + term = strchr(str, ':'); + if (term == NULL) { + term = strchr(str, ' '); + if (term == NULL) + return 1; + } + saved = *term; + *term = 0; + free(s->hostname); + s->hostname = strdup(str); + *term = saved; + } + } + + if (event_success != S_UNSET) { + char *str = strstr(term, "res="); + if (str) { + char *ptr, *term, saved; + + ptr = term = str + 4; + while (isalpha(*term)) + term++; + if (term == ptr) + return 2; + saved = *term; + *term = 0; + if (strncmp(ptr, "failed", 6) == 0) + s->success = S_FAILED; + else + s->success = S_SUCCESS; + *term = saved; + } + } + + return 0; +} + +static int parse_sockaddr(const lnode *n, search_items *s) +{ + char *str; + + if (event_hostname || event_filename) { + str = strstr(n->message, "saddr="); + if (str) { + int len; + struct sockaddr *saddr; + char name[NI_MAXHOST]; + + str += 6; + len = strlen(str)/2; + s->hostname = unescape(str); + saddr = (struct sockaddr *)s->hostname; + if (saddr->sa_family == AF_INET) { + if (len < sizeof(struct sockaddr_in)) { + fprintf(stderr, + "sockaddr len too short\n"); + return 1; + } + len = sizeof(struct sockaddr_in); + } else if (saddr->sa_family == AF_INET6) { + if (len < sizeof(struct sockaddr_in6)) { + fprintf(stderr, + "sockaddr6 len too short\n"); + return 2; + } + len = sizeof(struct sockaddr_in6); + } else if (saddr->sa_family == AF_UNIX) { + struct sockaddr_un *un = + (struct sockaddr_un *)saddr; + if (un->sun_path[0]) + len = strlen(un->sun_path); + else // abstract name + len = strlen(&un->sun_path[1]); + if (len == 0) { + fprintf(stderr, + "sun_path len too short\n"); + return 3; + } + if (event_filename) { + if (!s->filename) { + //create + s->filename = + malloc(sizeof(slist)); + if (s->filename == NULL) + return 4; + slist_create(s->filename); + } + if (s->filename) { + // append + snode sn; + sn.str = strdup(un->sun_path); + sn.key = NULL; + sn.hits = 1; + slist_append(s->filename, &sn); + } + free(s->hostname); + s->hostname = NULL; + return 0; + } else { // No file name - no need for socket + free(s->hostname); + s->hostname = NULL; + return 0; + } + } else { + // addr family we don't care about + free(s->hostname); + s->hostname = NULL; + return 0; + } + if (!event_hostname) { + // we entered here for files - discard + free(s->hostname); + s->hostname = NULL; + return 0; + } + if (getnameinfo(saddr, len, name, NI_MAXHOST, + NULL, 0, NI_NUMERICHOST) ) { + free(s->hostname); + s->hostname = NULL; + } else { + free(s->hostname); + s->hostname = strdup(name); + } + } + } + return 0; +} + +static int parse_integrity(const lnode *n, search_items *s) +{ + char *ptr, *str, *term; + + term = n->message; + // get pid + str = strstr(term, "pid="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 1; + *term = 0; + errno = 0; + s->pid = strtoul(ptr, NULL, 10); + if (errno) + return 2; + *term = ' '; + } + + // optionally get uid + if (event_uid != -1) { + str = strstr(term, " uid="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 3; + *term = 0; + errno = 0; + s->uid = strtoul(ptr, NULL, 10); + if (errno) + return 4; + *term = ' '; + } + } + + // optionally get loginuid + if (event_loginuid != -2) { + str = strstr(n->message, "auid="); + if (str) { + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 5; + *term = 0; + errno = 0; + s->loginuid = strtoul(ptr, NULL, 10); + if (errno) + return 6; + *term = ' '; + } + } + + // ses + if (event_session_id != -2 ) { + str = strstr(term, "ses="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 10; + *term = 0; + errno = 0; + s->session_id = strtoul(ptr, NULL, 10); + if (errno) + return 11; + *term = ' '; + } + } + + if (event_subject) { + // scontext + str = strstr(term, "subj="); + if (str) { + str += 5; + term = strchr(str, ' '); + if (term == NULL) + return 12; + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.scontext = strdup(str); + alist_append(s->avc, &an); + *term = ' '; + } else + return 13; + } + } + + if (event_comm) { + str = strstr(term, "comm="); + if (str) { + str += 5; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 7; + *term = 0; + s->comm = strdup(str); + *term = '"'; + } else + s->comm = unescape(str); + } + } + + if (event_filename) { + str = strstr(term, " name="); + if (str) { + str += 6; + if (common_path_parser(s, str)) + return 8; + } + } + + // and results (usually last) + if (event_success != S_UNSET) { + str = strstr(term, "res="); + if (str != NULL) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->success = strtoul(ptr, NULL, 10); + if (errno) + return 9; + if (term) + *term = ' '; + } + } + + return 0; +} + + +/* FIXME: If they are in permissive mode or hit an auditallow, there can + * be more that 1 avc in the same syscall. For now, we pickup just the first. + */ +static int parse_avc(const lnode *n, search_items *s) +{ + char *str, *term; + anode an; + int rc=0; + + term = n->message; + anode_init(&an); + + // get the avc message info. + str = strstr(term, "avc: "); + if (str) { + str += 5; + term = strchr(str, '{'); + if (term == NULL) + return 1; + if (event_success != S_UNSET) { + *term = 0; + // FIXME. Do not override syscall success if already + // set. Syscall pass/fail is the authoritative value. + if (strstr(str, "denied")) { + s->success = S_FAILED; + an.avc_result = AVC_DENIED; + } else { + s->success = S_SUCCESS; + an.avc_result = AVC_GRANTED; + } + *term = '{'; + } + + // Now get permission + str = term + 1; + while (*str == ' ') + str++; + term = strchr(str, '}'); + if (term == NULL) + return 2; + while (*(term-1) == ' ') + term--; + *term = 0; + an.avc_perm = strdup(str); + *term = ' '; + } + + // get pid + if (event_pid != -1) { + str = strstr(term, "pid="); + if (str) { + str = str + 4; + term = strchr(str, ' '); + if (term == NULL) { + rc = 3; + goto err; + } + *term = 0; + errno = 0; + s->pid = strtoul(str, NULL, 10); + if (errno) { + rc = 4; + goto err; + } + *term = ' '; + } + } + + if (event_comm && s->comm == NULL) { + // dont do this search unless needed + str = strstr(term, "comm="); + if (str == NULL) { + rc = 5; + goto err; + } + str += 5; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) { + rc = 6; + goto err; + } + *term = 0; + s->comm = strdup(str); + *term = '"'; + } else { + s->comm = unescape(str); + term = str + 6; + } + } + if (event_filename) { + // do we have a path? + str = strstr(term, " path="); + if (str) { + str += 6; + rc = common_path_parser(s, str); + if (rc) + goto err; + term += 7; + } else { + str = strstr(term, " name="); + if (str) { + str += 6; + rc = common_path_parser(s, str); + if (rc) + goto err; + term += 7; + } + } + } + if (event_subject) { + // scontext + str = strstr(term, "scontext="); + if (str != NULL) { + str += 9; + term = strchr(str, ' '); + if (term == NULL) { + rc = 7; + goto err; + } + *term = 0; + an.scontext = strdup(str); + *term = ' '; + } + } + + if (event_object) { + // tcontext + str = strstr(term, "tcontext="); + if (str != NULL) { + str += 9; + term = strchr(str, ' '); + if (term == NULL) { + rc = 8; + goto err; + } + *term = 0; + an.tcontext = strdup(str); + *term = ' '; + } + } + + // Now get the class...its at the end, so we do things different + str = strstr(term, "tclass="); + if (str == NULL) { + rc = 9; + goto err; + } + str += 7; + term = strchr(str, ' '); + if (term) + *term = 0; + an.avc_class = strdup(str); + if (term) + *term = ' '; + + if (audit_avc_init(s) == 0) { + alist_append(s->avc, &an); + } else { + rc = 10; + goto err; + } + + return 0; +err: + anode_clear(&an); + return rc; +} + +static int parse_kernel_anom(const lnode *n, search_items *s) +{ + char *str, *ptr, *term = n->message; + + // optionally get loginuid + if (event_loginuid != -2) { + str = strstr(term, "auid="); + if (str == NULL) + return 1; + ptr = str + 5; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->loginuid = strtoul(ptr, NULL, 10); + if (errno) + return 2; + if (term) + *term = ' '; + else + term = ptr; + } + + // optionally get uid + if (event_uid != -1) { + str = strstr(term, "uid="); // if promiscuous, we start over + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 3; + *term = 0; + errno = 0; + s->uid = strtoul(ptr, NULL, 10); + if (errno) + return 4; + *term = ' '; + } + } + + // optionally get gid + if (event_gid != -1) { + str = strstr(term, "gid="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 5; + *term = 0; + errno = 0; + s->gid = strtoul(ptr, NULL, 10); + if (errno) + return 6; + *term = ' '; + } + } + + if (event_session_id != -2) { + str = strstr(term, "ses="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->session_id = strtoul(ptr, NULL, 10); + if (errno) + return 7; + if (term) + *term = ' '; + else + term = ptr; + } + } + + if (event_subject) { + // scontext + str = strstr(term, "subj="); + if (str) { + str += 5; + term = strchr(str, ' '); + if (term == NULL) + return 8; + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.scontext = strdup(str); + alist_append(s->avc, &an); + *term = ' '; + } else + return 9; + } + } + + // get pid + if (event_pid != -1) { + str = strstr(term, "pid="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 10; + *term = 0; + errno = 0; + s->pid = strtoul(ptr, NULL, 10); + if (errno) + return 11; + *term = ' '; + } + } + + if (event_comm) { + // dont do this search unless needed + str = strstr(term, "comm="); + if (str) { + str += 5; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 12; + *term = 0; + s->comm = strdup(str); + *term = '"'; + } else + s->comm = unescape(str); + } + } + + if (n->type == AUDIT_SECCOMP) { + if (event_exe) { + // dont do this search unless needed + str = strstr(n->message, "exe="); + if (str) { + str += 4; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 13; + *term = 0; + s->exe = strdup(str); + *term = '"'; + } else + s->exe = unescape(str); + } else + return 14; + } + + // get arch + str = strstr(term, "arch="); + if (str == NULL) + return 0; // A few kernel versions don't have it + ptr = str + 5; + term = strchr(ptr, ' '); + if (term == NULL) + return 15; + *term = 0; + errno = 0; + s->arch = (int)strtoul(ptr, NULL, 16); + if (errno) + return 16; + *term = ' '; + // get syscall + str = strstr(term, "syscall="); + if (str == NULL) + return 17; + ptr = str + 8; + term = strchr(ptr, ' '); + if (term == NULL) + return 18; + *term = 0; + errno = 0; + s->syscall = (int)strtoul(ptr, NULL, 10); + if (errno) + return 19; + *term = ' '; + } + + return 0; +} + +// This is for messages that only have the loginuid as the item +// of interest. +static int parse_simple_message(const lnode *n, search_items *s) +{ + char *str, *ptr, *term = n->message; + + // optionally get loginuid - old kernels skip auid for CONFIG_CHANGE + if (event_loginuid != -2) { + str = strstr(term, "auid="); + if (str == NULL && n->type != AUDIT_CONFIG_CHANGE) + return 1; + if (str) { + ptr = str + 5; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->loginuid = strtoul(ptr, NULL, 10); + if (errno) + return 2; + if (term) + *term = ' '; + else + term = ptr; + } + } + + // ses + if (event_session_id != -2 ) { + str = strstr(term, "ses="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->session_id = strtoul(ptr, NULL, 10); + if (errno) + return 3; + if (term) + *term = ' '; + else + term = ptr; + } + } + + // Now get subj label + if (event_subject) { + // scontext + str = strstr(term, "subj="); + if (str != NULL) { + str += 5; + term = strchr(str, ' '); + if (term) + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.scontext = strdup(str); + alist_append(s->avc, &an); + if (term) + *term = ' '; + else // Set it back to something sane + term = str; + } else + return 4; + } + } + + if (event_key) { + str = strstr(term, "key="); + if (str != NULL) { + if (!s->key) { + //create + s->key = malloc(sizeof(slist)); + if (s->key == NULL) + return 5; + slist_create(s->key); + } + ptr = str + 4; + if (*ptr == '"') { + ptr++; + term = strchr(ptr, '"'); + if (term != NULL) { + *term = 0; + if (s->key) { + // append + snode sn; + sn.str = strdup(ptr); + sn.key = NULL; + sn.hits = 1; + slist_append(s->key, &sn); + } + *term = '"'; + } else + return 6; + } else { + if (s->key) { + char *saved; + char *keyptr = unescape(ptr); + char *kptr = strtok_r(keyptr, + key_sep, &saved); + while (kptr) { + snode sn; + // append + sn.str = strdup(kptr); + sn.key = NULL; + sn.hits = 1; + slist_append(s->key, &sn); + kptr = strtok_r(NULL, + key_sep, &saved); + } + free(keyptr); + } + } + } + } + + // defaulting this to 1 for these messages. The kernel generally + // does not log the res since it can be nothing but success. + // But it can still be overriden below if res= is found in the event + if (n->type == AUDIT_CONFIG_CHANGE) + s->success = S_SUCCESS; + + // and results (usually last) + if (event_success != S_UNSET) { + str = strstr(term, "res="); + if (str != NULL) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->success = strtoul(ptr, NULL, 10); + if (errno) + return 7; + if (term) + *term = ' '; + } + } + + return 0; +} + +static int parse_tty(const lnode *n, search_items *s) +{ + char *str, *ptr, *term=n->message; + + // get pid + if (event_pid != -1) { + str = strstr(n->message, "pid="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 1; + *term = 0; + errno = 0; + s->pid = strtoul(ptr, NULL, 10); + if (errno) + return 2; + *term = ' '; + } + } + + // optionally get uid + if (event_uid != -1) { + str = strstr(term, " uid="); // if promiscuous, we start over + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 3; + *term = 0; + errno = 0; + s->uid = strtoul(ptr, NULL, 10); + if (errno) + return 4; + *term = ' '; + } + } + + // optionally get loginuid + if (event_loginuid != -2) { + str = strstr(term, "auid="); + if (str == NULL) + return 5; + ptr = str + 5; + term = strchr(ptr, ' '); + if (term) + *term = 0; + errno = 0; + s->loginuid = strtoul(ptr, NULL, 10); + if (errno) + return 6; + if (term) + *term = ' '; + else + term = ptr; + } + + // ses + if (event_session_id != -2 ) { + str = strstr(term, "ses="); + if (str) { + ptr = str + 4; + term = strchr(ptr, ' '); + if (term == NULL) + return 7; + *term = 0; + errno = 0; + s->session_id = strtoul(ptr, NULL, 10); + if (errno) + return 8; + *term = ' '; + } + } + +/* if (event_subject) { + // scontext + str = strstr(term, "subj="); + if (str) { + str += 5; + term = strchr(str, ' '); + if (term == NULL) + return 9; + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.scontext = strdup(str); + alist_append(s->avc, &an); + *term = ' '; + } else + return 10; + } + } */ + + if (event_comm) { + // dont do this search unless needed + str = strstr(term, "comm="); + if (str) { + str += 5; + if (*str == '"') { + str++; + term = strchr(str, '"'); + if (term == NULL) + return 11; + *term = 0; + s->comm = strdup(str); + *term = '"'; + } else + s->comm = unescape(str); + } + } + + return 0; +} + +static int parse_pkt(const lnode *n, search_items *s) +{ + char *str, *ptr, *term=n->message; + + // get hostname + if (event_hostname) { + str = strstr(n->message, "saddr="); + if (str) { + ptr = str + 6; + term = strchr(ptr, ' '); + if (term == NULL) + return 1; + *term = 0; + s->hostname = strdup(ptr); + *term = ' '; + } + } + + // obj context + if (event_object) { + str = strstr(term, "obj="); + if (str != NULL) { + str += 4; + term = strchr(str, ' '); + if (term) + *term = 0; + if (audit_avc_init(s) == 0) { + anode an; + + anode_init(&an); + an.tcontext = strdup(str); + alist_append(s->avc, &an); + if (term) + *term = ' '; + } else + return 2; + } + } + + return 0; +} + diff --git a/framework/src/audit/src/ausearch-parse.h b/framework/src/audit/src/ausearch-parse.h new file mode 100644 index 00000000..36557105 --- /dev/null +++ b/framework/src/audit/src/ausearch-parse.h @@ -0,0 +1,33 @@ +/* +* ausearch-parse.h - Header file for ausearch-llist.c +* Copyright (c) 2005 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#ifndef AUPARSE_HEADER +#define AUPARSE_HEADER + +#include "config.h" +#include "ausearch-llist.h" + +int extract_search_items(llist *l); + +#endif + diff --git a/framework/src/audit/src/ausearch-report.c b/framework/src/audit/src/ausearch-report.c new file mode 100644 index 00000000..a4f1e15d --- /dev/null +++ b/framework/src/audit/src/ausearch-report.c @@ -0,0 +1,362 @@ +/* +* ausearch-report.c - Format and output events +* Copyright (c) 2005-09,2011-13 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include "config.h" +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include "libaudit.h" +#include "ausearch-options.h" +#include "ausearch-parse.h" +#include "ausearch-lookup.h" +#include "auparse-idata.h" +#include "auparse-defs.h" + +/* Local functions */ +static void output_raw(llist *l); +static void output_default(llist *l); +static void output_interpreted(llist *l); +static void output_interpreted_node(const lnode *n, const event *e); +static void interpret(char *name, char *val, int comma, int rtype); + +/* The machine based on elf type */ +static unsigned long machine = -1; +static int cur_syscall = -1; + +/* The first syscall argument */ +static unsigned long long a0, a1; + +/* This function branches to the correct output format */ +void output_record(llist *l) +{ + switch (report_format) { + case RPT_RAW: + output_raw(l); + break; + case RPT_DEFAULT: + output_default(l); + break; + case RPT_INTERP: + output_interpreted(l); + break; + case RPT_PRETTY: + break; + default: + fprintf(stderr, "Report format error"); + exit(1); + } +} + +/* This function will output the record as is */ +static void output_raw(llist *l) +{ + const lnode *n; + + list_first(l); + n = list_get_cur(l); + if (!n) { + fprintf(stderr, "Error - no elements in record."); + return; + } + do { + puts(n->message); + } while ((n=list_next(l))); +} + +/* + * This function will take the linked list and format it for output. No + * interpretation is performed. The output order is lifo for everything. + */ +static void output_default(llist *l) +{ + const lnode *n; + + list_last(l); + n = list_get_cur(l); + printf("----\ntime->%s", ctime(&l->e.sec)); + if (!n) { + fprintf(stderr, "Error - no elements in record."); + return; + } + if (n->type >= AUDIT_DAEMON_START && n->type < AUDIT_SYSCALL) + puts(n->message); // No injection possible + else { + do { + safe_print_string_n(n->message, n->mlen, 1); + } while ((n=list_prev(l))); + } +} + +/* + * This function will take the linked list and format it for output. + * Interpretation is performed to aid understanding of records. The output + * order is lifo for everything. + */ +static void output_interpreted(llist *l) +{ + const lnode *n; + + list_last(l); + n = list_get_cur(l); + printf("----\n"); + if (!n) { + fprintf(stderr, "Error - no elements in record."); + return; + } + if (n->type >= AUDIT_DAEMON_START && n->type < AUDIT_SYSCALL) + output_interpreted_node(n, &(l->e)); + else { + do { + output_interpreted_node(n, &(l->e)); + } while ((n=list_prev(l))); + } +} + +/* + * This function will cycle through a single record and lookup each field's + * value that it finds. + */ +static void output_interpreted_node(const lnode *n, const event *e) +{ + char *ptr, *str = n->message; + int found, comma = 0; + int num = n->type; + struct tm *btm; + char tmp[32]; + + // Reset these because each record could be different + machine = -1; + cur_syscall = -1; + + /* Check and see if we start with a node + * If we do, and there is a space in the line + * move the pointer to the first character past + * the space + */ + if (e->node) { + if ((ptr=strchr(str, ' ')) != NULL) { + str = ptr+1; + } + } + + // First locate time stamp. + ptr = strchr(str, '('); + if (ptr == NULL) { + fprintf(stderr, "can't find time stamp\n"); + return; + } + + *ptr++ = 0; /* move to the start of the timestamp */ + + // print everything up to it. + if (num >= 0) { + const char * bptr; + bptr = audit_msg_type_to_name(num); + if (bptr) { + if (e->node) + printf("node=%s ", e->node); + printf("type=%s msg=audit(", bptr); + goto no_print; + } + } + if (e->node) + printf("node=%s ", e->node); + printf("%s(", str); +no_print: + + str = strchr(ptr, ')'); + if(str == NULL) + return; + *str++ = 0; + btm = localtime(&e->sec); + strftime(tmp, sizeof(tmp), "%x %T", btm); + printf("%s", tmp); + printf(".%03d:%lu) ", e->milli, e->serial); + + if (n->type == AUDIT_SYSCALL) { + a0 = n->a0; + a1 = n->a1; + } + + // for each item. + found = 0; + while (str && *str && (ptr = strchr(str, '='))) { + char *name, *val; + comma = 0; + found = 1; + + // look back to last space - this is name + name = ptr; + while (*name != ' ' && name > str) + --name; + *ptr++ = 0; + + // print everything up to the '=' + printf("%s=", str); + + // Some user messages have msg='uid=500 in this case + // skip the msg= piece since the real stuff is the uid= + if (strcmp(name, "msg") == 0) { + str = ptr; + continue; + } + + // In the above case, after msg= we need to trim the ' from uid + if (*name == '\'') + name++; + + // get string after = to the next space or end - this is value + if (*ptr == '\'' || *ptr == '"') { + str = strchr(ptr+1, *ptr); + if (str) { + str++; + if (*str) + *str++ = 0; + } + } else { + str = strchr(ptr, ','); + val = strchr(ptr, ' '); + if (str && val && (str < val)) { + // Value side has commas and another field exists + // Known: LABEL_LEVEL_CHANGE banners=none,none + // Known: ROLL_ASSIGN new-role=r,r + // Known: any MAC LABEL can potentially have commas + int ftype = auparse_interp_adjust_type(n->type, + name, val); + if (ftype == AUPARSE_TYPE_MAC_LABEL) { + str = val; + *str++ = 0; + } else { + *str++ = 0; + comma = 1; + } + } else if (str && (val == NULL)) { + // Goes all the way to the end. Done parsing + // Known: MCS context in PATH rec obj=u:r:t:s0:c2,c7 + int ftype = auparse_interp_adjust_type(n->type, + name, ptr); + if (ftype == AUPARSE_TYPE_MAC_LABEL) + str = NULL; + else { + *str++ = 0; + comma = 1; + } + } else if (val) { + // There is another field, point to next (normal path) + str = val; + *str++ = 0; + } + } + // val points to begin & str 1 past end + val = ptr; + + // print interpreted string + interpret(name, val, comma, n->type); + } + // If nothing found, just print out as is + if (!found && ptr == NULL && str) + safe_print_string(str, 1); + + // If last field had comma, output the rest + else if (comma) + safe_print_string(str, 1); + printf("\n"); +} + +static void interpret(char *name, char *val, int comma, int rtype) +{ + int type; + idata id; + + while (*name == ' '||*name == '(') + name++; + + if (*name == 'a' && strcmp(name, "acct") == 0) { + // Remove trailing punctuation + int len = strlen(val); + if (val[len-1] == ':') + val[len-1] = 0; + } + type = auparse_interp_adjust_type(rtype, name, val); + + if (rtype == AUDIT_SYSCALL || rtype == AUDIT_SECCOMP) { + if (machine == (unsigned long)-1) + machine = audit_detect_machine(); + if (*name == 'a' && strcmp(name, "arch") == 0) { + unsigned long ival; + errno = 0; + ival = strtoul(val, NULL, 16); + if (errno) { + printf("arch conversion error(%s) ", val); + return; + } + machine = audit_elf_to_machine(ival); + } + if (cur_syscall < 0 && *name == 's' && + strcmp(name, "syscall") == 0) { + unsigned long ival; + errno = 0; + ival = strtoul(val, NULL, 10); + if (errno) { + printf("syscall conversion error(%s) ", val); + return; + } + cur_syscall = ival; + } + id.syscall = cur_syscall; + } else + id.syscall = 0; + id.machine = machine; + id.a0 = a0; + id.a1 = a1; + id.name = name; + id.val = val; + + char *out = auparse_do_interpretation(type, &id); + if (type == AUPARSE_TYPE_UNCLASSIFIED) + printf("%s%c", val, comma ? ',' : ' '); + else if (name[0] == 'k' && strcmp(name, "key") == 0) { + char *str, *ptr = out; + int count = 0; + while ((str = strchr(ptr, AUDIT_KEY_SEPARATOR))) { + *str = 0; + if (count == 0) { + printf("%s", ptr); + count++; + } else + printf(" key=%s", ptr); + ptr = str+1; + } + if (count == 0) + printf("%s ", out); + else + printf(" key=%s ", ptr); + } else if (type == AUPARSE_TYPE_TTY_DATA) + printf("%s", out); + else + printf("%s ", out); + + free(out); +} + diff --git a/framework/src/audit/src/ausearch-string.c b/framework/src/audit/src/ausearch-string.c new file mode 100644 index 00000000..3c5c55e3 --- /dev/null +++ b/framework/src/audit/src/ausearch-string.c @@ -0,0 +1,178 @@ +/* +* ausearch-string.c - Minimal linked list library for strings +* Copyright (c) 2005,2008,2014 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#include "ausearch-string.h" +#include <stdlib.h> +#include <string.h> + + +void slist_create(slist *l) +{ + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + +void slist_last(slist *l) +{ + register snode* cur; + + if (l->head == NULL) + return; + + // Try using cur so that we don't have to start at beginnning + if (l->cur) + cur = l->cur; + else + cur = l->head; + + // Loop until no next value + while (cur->next) + cur = cur->next; + l->cur = cur; +} + +snode *slist_next(slist *l) +{ + if (l->cur == NULL) + return NULL; + l->cur = l->cur->next; + return l->cur; +} + +void slist_append(slist *l, snode *node) +{ + snode* newnode; + + newnode = malloc(sizeof(snode)); + + if (node->str) + newnode->str = node->str; + else + newnode->str = NULL; + + if (node->key) + newnode->key = node->key; + else + newnode->key = NULL; + + newnode->hits = node->hits; + newnode->next = NULL; + + // Make sure cursor is at the end + slist_last(l); + + // if we are at top, fix this up + if (l->head == NULL) + l->head = newnode; + else // Otherwise add pointer to newnode + l->cur->next = newnode; + + // make newnode current + l->cur = newnode; + l->cnt++; +} + +void slist_clear(slist* l) +{ + snode* nextnode; + register snode* current; + + current = l->head; + while (current) { + nextnode=current->next; + free(current->str); + free(current->key); + free(current); + current=nextnode; + } + l->head = NULL; + l->cur = NULL; + l->cnt = 0; +} + +/* This function dominates the timing of aureport. Needs to be more efficient */ +int slist_add_if_uniq(slist *l, const char *str) +{ + snode sn; + register snode *cur; + + cur = l->head; + while (cur) { + if (strcmp(str, cur->str) == 0) { + cur->hits++; + l->cur = cur; + return 0; + } else + cur = cur->next; + } + + /* No matches, append to the end */ + sn.str = strdup(str); + sn.key = NULL; + sn.hits = 1; + slist_append(l, &sn); + return 1; +} + +// If lprev would be NULL, use l->head +static void swap_nodes(snode *lprev, snode *left, snode *right) +{ + snode *t = right->next; + if (lprev) + lprev->next = right; + right->next = left; + left->next = t; +} + +// This will sort the list from most hits to least +void slist_sort_by_hits(slist *l) +{ + register snode* cur, *prev; + + if (l->cnt <= 1) + return; + + prev = cur = l->head; + + while (cur && cur->next) { + /* If the next node is bigger */ + if (cur->hits < cur->next->hits) { + if (cur == l->head) { + // Update the actual list head + l->head = cur->next; + prev = NULL; + } + swap_nodes(prev, cur, cur->next); + + // start over + prev = cur = l->head; + continue; + } + prev = cur; + cur = cur->next; + } + // End with cur pointing at first record + l->cur = l->head; +} + diff --git a/framework/src/audit/src/ausearch-string.h b/framework/src/audit/src/ausearch-string.h new file mode 100644 index 00000000..08cf2ffc --- /dev/null +++ b/framework/src/audit/src/ausearch-string.h @@ -0,0 +1,59 @@ +/* +* ausearch-string.h - Header file for ausearch-string.c +* Copyright (c) 2005,2008 Red Hat Inc., Durham, North Carolina. +* All Rights Reserved. +* +* This software may be freely redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free +* Software Foundation; either version 2, or (at your option) any +* later version. +* +* 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; see the file COPYING. If not, write to the +* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* Authors: +* Steve Grubb <sgrubb@redhat.com> +*/ + +#ifndef AUSTRING_HEADER +#define AUSTRING_HEADER + +#include "config.h" + +/* This is the node of the linked list. message & item are the only elements + * at this time. Any data elements that are per item goes here. */ +typedef struct _snode{ + char *str; // The string + char *key; // The key string + unsigned int hits; // Number of times this string was attempted to be added + struct _snode* next; // Next string node pointer +} snode; + +/* This is the linked list head. Only data elements that are 1 per + * event goes here. */ +typedef struct { + snode *head; // List head + snode *cur; // Pointer to current node + unsigned int cnt; // How many items in this list +} slist; + +void slist_create(slist *l); +static inline void slist_first(slist *l) { l->cur = l->head; } +void slist_last(slist *l); +snode *slist_next(slist *l); +static inline snode *slist_get_cur(slist *l) { return l->cur; } +void slist_append(slist *l, snode *node); +void slist_clear(slist* l); + +/* append a string if its not already on the list */ +int slist_add_if_uniq(slist *l, const char *str); +void slist_sort_by_hits(slist *l); + +#endif + diff --git a/framework/src/audit/src/ausearch-time.c b/framework/src/audit/src/ausearch-time.c new file mode 100644 index 00000000..3cd33c87 --- /dev/null +++ b/framework/src/audit/src/ausearch-time.c @@ -0,0 +1,412 @@ +/* ausearch-time.c - time handling utility functions + * Copyright 2006-08,2011 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + */ + +#include "config.h" +#include <stdio.h> +#include <string.h> +#include "ausearch-time.h" + + +#define SECONDS_IN_DAY 24*60*60 +static void clear_tm(struct tm *t); +static void replace_time(struct tm *t1, struct tm *t2); +static void replace_date(struct tm *t1, struct tm *t2); + + +time_t start_time = 0, end_time = 0; + +struct nv_pair { + int value; + const char *name; +}; + +static struct nv_pair timetab[] = { + { T_NOW, "now" }, + { T_RECENT, "recent" }, + { T_TODAY, "today" }, + { T_YESTERDAY, "yesterday" }, + { T_THIS_WEEK, "this-week" }, + { T_WEEK_AGO, "week-ago" }, + { T_THIS_MONTH, "this-month" }, + { T_THIS_YEAR, "this-year" }, +}; +#define TIME_NAMES (sizeof(timetab)/sizeof(timetab[0])) + +int lookup_time(const char *name) +{ + int i; + + for (i = 0; i < TIME_NAMES; i++) { + if (strcmp(timetab[i].name, name) == 0) { + return timetab[i].value; + } + } + return -1; + +} + +static void clear_tm(struct tm *t) +{ + t->tm_sec = 0; /* seconds */ + t->tm_min = 0; /* minutes */ + t->tm_hour = 0; /* hours */ + t->tm_mday = 0; /* day of the month */ + t->tm_mon = 0; /* month */ + t->tm_year = 0; /* year */ + t->tm_isdst = 0; /* DST flag */ +} + +static void set_tm_now(struct tm *d) +{ + time_t t = time(NULL); + struct tm *tv = localtime(&t); + replace_time(d, tv); + replace_date(d, tv); +} + +static void set_tm_today(struct tm *d) +{ + time_t t = time(NULL); + struct tm *tv = localtime(&t); + d->tm_sec = 0; /* seconds */ + d->tm_min = 0; /* minutes */ + d->tm_hour = 0; /* hours */ + replace_date(d, tv); +} + +static void set_tm_yesterday(struct tm *d) +{ + time_t t = time(NULL) - (time_t)(SECONDS_IN_DAY); + struct tm *tv = localtime(&t); + d->tm_sec = 0; /* seconds */ + d->tm_min = 0; /* minutes */ + d->tm_hour = 0; /* hours */ + replace_date(d, tv); +} + +static void set_tm_recent(struct tm *d) +{ + time_t t = time(NULL) - (time_t)(10*60); /* 10 minutes ago */ + struct tm *tv = localtime(&t); + replace_time(d, tv); + replace_date(d, tv); +} + +static void set_tm_this_week(struct tm *d) +{ + time_t t = time(NULL); + struct tm *tv = localtime(&t); + d->tm_sec = 0; /* seconds */ + d->tm_min = 0; /* minutes */ + d->tm_hour = 0; /* hours */ + t -= (time_t)(tv->tm_wday*SECONDS_IN_DAY); + tv = localtime(&t); + replace_date(d, tv); +} + +static void set_tm_week_ago(struct tm *d) +{ + time_t t = time(NULL); + struct tm *tv; + d->tm_sec = 0; /* seconds */ + d->tm_min = 0; /* minutes */ + d->tm_hour = 0; /* hours */ + t -= (time_t)(7*SECONDS_IN_DAY); + tv = localtime(&t); + replace_date(d, tv); +} + +static void set_tm_this_month(struct tm *d) +{ + time_t t = time(NULL); + struct tm *tv = localtime(&t); + d->tm_sec = 0; /* seconds */ + d->tm_min = 0; /* minutes */ + d->tm_hour = 0; /* hours */ + replace_date(d, tv); + d->tm_mday = 1; /* override day of month */ +} + +static void set_tm_this_year(struct tm *d) +{ + time_t t = time(NULL); + struct tm *tv = localtime(&t); + d->tm_sec = 0; /* seconds */ + d->tm_min = 0; /* minutes */ + d->tm_hour = 0; /* hours */ + replace_date(d, tv); + d->tm_mday = 1; /* override day of month */ + d->tm_mon = 0; /* override month */ +} + +/* Combine date & time into 1 struct. Results in date. */ +static void add_tm(struct tm *d, struct tm *t) +{ + time_t dst; + struct tm *lt; + + replace_time(d, t); + + /* Now we need to figure out if DST is in effect */ + dst = time(NULL); + lt = localtime(&dst); + d->tm_isdst = lt->tm_isdst; +} + +/* The time in t1 is replaced by t2 */ +static void replace_time(struct tm *t1, struct tm *t2) +{ + t1->tm_sec = t2->tm_sec; /* seconds */ + t1->tm_min = t2->tm_min; /* minutes */ + t1->tm_hour = t2->tm_hour; /* hours */ +} + +/* The date in t1 is replaced by t2 */ +static void replace_date(struct tm *t1, struct tm *t2) +{ + t1->tm_mday = t2->tm_mday; /* day */ + t1->tm_mon = t2->tm_mon; /* month */ + t1->tm_year = t2->tm_year; /* year */ + t1->tm_isdst = t2->tm_isdst; /* daylight savings time */ +} + +/* Given 2 char strings, create a time struct * +void set_time(struct tm *t, int num, const char *t1, const char *t2) +{ + switch (num) + { + case 1: + // if keyword, init time + // elif time use today and replace time + // elif date, set to 00:00:01 and replace date + // else error + break; + case 2: + // if keyword + // init time with it + // get other time str and replace + // otherwise, figure out which is time + // and set time adding them + break; + default: + break; + } +} */ + +static int lookup_and_set_time(const char *da, struct tm *d) +{ + int retval = lookup_time(da); + if (retval >= 0) { + switch (retval) + { + case T_NOW: + set_tm_now(d); + break; + case T_RECENT: + set_tm_recent(d); + break; + case T_TODAY: + set_tm_today(d); + break; + case T_YESTERDAY: + set_tm_yesterday(d); + break; + case T_THIS_WEEK: + set_tm_this_week(d); + break; + case T_WEEK_AGO: + set_tm_week_ago(d); + break; + case T_THIS_MONTH: + set_tm_this_month(d); + break; + case T_THIS_YEAR: + set_tm_this_year(d); + break; + } + return 0; + } else + return -1; +} + +/* static void print_time(struct tm *d) +{ + char outstr[200]; + strftime(outstr, sizeof(outstr), "%c", d); + printf("%s\n", outstr); +} */ + +int ausearch_time_start(const char *da, const char *ti) +{ +/* If da == NULL, use current date */ +/* If ti == NULL, then use midnight 00:00:00 */ + int rc = 0; + struct tm d, t; + char *ret; + + if (da == NULL) + set_tm_now(&d); + else { + if (lookup_and_set_time(da, &d) < 0) { + ret = strptime(da, "%x", &d); + if (ret == NULL) { + fprintf(stderr, + "Invalid start date (%s). Month, Day, and Year are required.\n", + da); + return 1; + } + if (*ret != 0) { + fprintf(stderr, + "Error parsing start date (%s)\n", da); + return 1; + } + } else { + int keyword=lookup_time(da); + if (keyword == T_RECENT || keyword == T_NOW) { + if (ti == NULL || strcmp(ti, "00:00:00") == 0) + goto set_it; + } + } + } + + if (ti != NULL) { + char tmp_t[36]; + + if (strlen(ti) <= 5) { + snprintf(tmp_t, sizeof(tmp_t), "%s:00", ti); + } else { + tmp_t[0]=0; + strncat(tmp_t, ti, sizeof(tmp_t)-1); + } + ret = strptime(tmp_t, "%X", &t); + if (ret == NULL) { + fprintf(stderr, + "Invalid start time (%s). Hour, Minute, and Second are required.\n", + ti); + return 1; + } + if (*ret != 0) { + fprintf(stderr, "Error parsing start time (%s)\n", ti); + return 1; + } + } else + clear_tm(&t); + + add_tm(&d, &t); + if (d.tm_year < 104) { + fprintf(stderr, "Error - year is %d\n", d.tm_year+1900); + return -1; + } +set_it: + // printf("start is: %s\n", ctime(&start_time)); + start_time = mktime(&d); + if (start_time == -1) { + fprintf(stderr, "Error converting start time\n"); + rc = -1; + } + return rc; +} + +int ausearch_time_end(const char *da, const char *ti) +{ +/* If date == NULL, use current date */ +/* If ti == NULL, use current time */ + int rc = 0; + struct tm d, t; + char *ret; + + if (da == NULL) + set_tm_now(&d); + else { + if (lookup_and_set_time(da, &d) < 0) { + ret = strptime(da, "%x", &d); + if (ret == NULL) { + fprintf(stderr, + "Invalid end date (%s). Month, Day, and Year are required.\n", + da); + return 1; + } + if (*ret != 0) { + fprintf(stderr, + "Error parsing end date (%s)\n", da); + return 1; + } + } else { + int keyword=lookup_time(da); + if (keyword == T_RECENT || keyword == T_NOW) { + if (ti == NULL || strcmp(ti, "00:00:00") == 0) + goto set_it; + } + // Special case today + if (keyword == T_TODAY) { + set_tm_now(&d); + if (ti == NULL || strcmp(ti, "00:00:00") == 0) + goto set_it; + } + } + } + + if (ti != NULL) { + char tmp_t[36]; + + if (strlen(ti) <= 5) { + snprintf(tmp_t, sizeof(tmp_t), "%s:00", ti); + } else { + tmp_t[0]=0; + strncat(tmp_t, ti, sizeof(tmp_t)-1); + } + ret = strptime(tmp_t, "%X", &t); + if (ret == NULL) { + fprintf(stderr, + "Invalid end time (%s). Hour, Minute, and Second are required.\n", + ti); + return 1; + } + if (*ret != 0) { + fprintf(stderr, "Error parsing end time (%s)\n", ti); + return 1; + } + } else { + time_t tt = time(NULL); + struct tm *tv = localtime(&tt); + clear_tm(&t); + t.tm_hour = tv->tm_hour; + t.tm_min = tv->tm_min; + t.tm_sec = tv->tm_sec; + t.tm_isdst = tv->tm_isdst; + + } + add_tm(&d, &t); + if (d.tm_year < 104) { + fprintf(stderr, "Error - year is %d\n", d.tm_year+1900); + return -1; + } +set_it: + // printf("end is: %s\n", ctime(&end_time)); + end_time = mktime(&d); + if (end_time == -1) { + fprintf(stderr, "Error converting end time\n"); + rc = -1; + } + return rc; +} + diff --git a/framework/src/audit/src/ausearch-time.h b/framework/src/audit/src/ausearch-time.h new file mode 100644 index 00000000..15051a5a --- /dev/null +++ b/framework/src/audit/src/ausearch-time.h @@ -0,0 +1,38 @@ +/* ausearch-time.h - header file for ausearch-time.c + * Copyright 2006-07 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + */ + +#ifndef AUSEARCH_TIME_HEADERS +#define AUSEARCH_TIME_HEADERS + +#include <time.h> + +enum { T_NOW, T_RECENT, T_TODAY, T_YESTERDAY, T_THIS_WEEK, T_WEEK_AGO, + T_THIS_MONTH, T_THIS_YEAR }; + +extern time_t start_time, end_time; + +int lookup_time(const char *name); +int ausearch_time_start(const char *da, const char *ti); +int ausearch_time_end(const char *da, const char *ti); + +#endif + diff --git a/framework/src/audit/src/ausearch.c b/framework/src/audit/src/ausearch.c new file mode 100644 index 00000000..06213f8b --- /dev/null +++ b/framework/src/audit/src/ausearch.c @@ -0,0 +1,594 @@ +/* + * ausearch.c - main file for ausearch utility + * Copyright 2005-08,2010,2013,2014 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + */ + +#include "config.h" +#include <stdio.h> +#include <stdio_ext.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> +#include <errno.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <locale.h> +#include <signal.h> +#include "libaudit.h" +#include "auditd-config.h" +#include "ausearch-options.h" +#include "ausearch-lol.h" +#include "ausearch-lookup.h" +#include "auparse.h" +#include "ausearch-checkpt.h" + + +static FILE *log_fd = NULL; +static lol lo; +static int found = 0; +static int input_is_pipe = 0; +static int timeout_interval = 3; /* timeout in seconds */ +static int files_to_process = 0; /* number of log files yet to process when reading multiple */ +static int process_logs(void); +static int process_log_fd(void); +static int process_stdin(void); +static int process_file(char *filename); +static int get_record(llist **); + +extern const char *checkpt_filename; /* checkpoint file name */ +extern int checkpt_timeonly; /* use timestamp from within checkpoint file */ +static int have_chkpt_data = 0; /* have checkpt need to compare wit */ +extern char *user_file; +extern int force_logs; +extern int match(llist *l); +extern void output_record(llist *l); + +static int userfile_is_dir = 0; + +static int is_pipe(int fd) +{ + struct stat st; + int pipe_mode=0; + + if (fstat(fd, &st) == 0) { + if (S_ISFIFO(st.st_mode)) + pipe_mode = 1; + } + return pipe_mode; +} + +int main(int argc, char *argv[]) +{ + struct rlimit limit; + int rc; + struct stat sb; + + /* Check params and build regexpr */ + setlocale (LC_ALL, ""); + if (check_params(argc, argv)) + return 1; + + /* Raise the rlimits in case we're being started from a shell + * with restrictions. Not a fatal error. */ + limit.rlim_cur = RLIM_INFINITY; + limit.rlim_max = RLIM_INFINITY; + setrlimit(RLIMIT_FSIZE, &limit); + setrlimit(RLIMIT_CPU, &limit); + set_aumessage_mode(MSG_STDERR, DBG_NO); + (void) umask( umask( 077 ) | 027 ); + + /* Load the checkpoint file if requested */ + if (checkpt_filename) { + rc = load_ChkPt(checkpt_filename); + /* + * If < -1, then some load/parse error + * If == -1 then no file present (OK) + * If == 0, then checkpoint has data + */ + if (rc < -1) { + (void)free((void *)checkpt_filename); + free_ChkPtMemory(); + return 10; /* bad checkpoint status file */ + } else if (rc == -1) { + /* + * No file, so no checking required. This just means + * we have never checkpointed before and this is the + * first time. + */ + have_chkpt_data = 0; + } else { + /* We will need to check */ + have_chkpt_data++; + } + } + + lol_create(&lo); + if (user_file) { + if (stat(user_file, &sb) == -1) { + perror("stat"); + return 1; + } + switch (sb.st_mode & S_IFMT) { + case S_IFDIR: + userfile_is_dir = 1; + rc = process_logs(); + break; + case S_IFREG: + default: + rc = process_file(user_file); + break; + } + } else if (force_logs) + rc = process_logs(); + else if (is_pipe(0)) + rc = process_stdin(); + else + rc = process_logs(); + + /* Generate a checkpoint if required */ + if (checkpt_filename) { + /* Providing we found something and haven't failed */ + if (!checkpt_failure && found) + save_ChkPt(checkpt_filename); + free_ChkPtMemory(); + free((void *)checkpt_filename); + /* + * A checkpoint failure at this point means either + * - we failed in attempting to create the checkpouint file + * and so we will return 11 + * - we had a corrupted checkpoint file and so we will return 12 + */ + if (checkpt_failure) { + rc = ((checkpt_failure & CP_CORRUPTED) == + CP_CORRUPTED) ? 12 : 11; + } + } + + lol_clear(&lo); + ilist_clear(event_type); + free(event_type); + free(user_file); + free((char *)event_key); + auparse_destroy(NULL); + if (rc) + return rc; + if (!found) { + if (report_format != RPT_RAW) + fprintf(stderr, "<no matches>\n"); + return 1; + } + return 0; +} + +static int process_logs(void) +{ + struct daemon_conf config; + char *filename; + int len, num = 0; + int found_chkpt_file = -1; + int ret; + + if (user_file && userfile_is_dir) { + char dirname[MAXPATHLEN]; + clear_config (&config); + + strcpy(dirname, user_file); + if (dirname[strlen(dirname)-1] != '/') + strcat(dirname, "/"); + strcat (dirname, "audit.log"); + free((void *)config.log_file); + config.log_file=strdup(dirname); + fprintf(stderr, "NOTE - using logs in %s\n", config.log_file); + } + else { + /* Load config so we know where logs are */ + if (load_config(&config, TEST_SEARCH)) { + fprintf(stderr, + "NOTE - using built-in logs: %s\n", + config.log_file); + } + } + + /* for each file */ + len = strlen(config.log_file) + 16; + filename = malloc(len); + if (!filename) { + fprintf(stderr, "No memory\n"); + free_config(&config); + return 1; + } + /* Find oldest log file */ + snprintf(filename, len, "%s", config.log_file); + do { + if (access(filename, R_OK) != 0) + break; + + /* + * If we have prior checkpoint data, we ignore files till we + * find the file we last checkpointed from + */ + if (checkpt_filename && have_chkpt_data) { + struct stat sbuf; + + if (stat(filename, &sbuf)) { + fprintf(stderr, "Error stat'ing %s (%s)\n", + filename, strerror(errno)); + free(filename); + free_config(&config); + return 1; + } + /* + * Have we accessed the checkpointed file? + * If so, stop checking further files. + */ + if ( (sbuf.st_dev == chkpt_input_dev) && + (sbuf.st_ino == chkpt_input_ino) ) { + /* + * If we are ignoing all but time, then we + * don't stop checking more files and just + * let this loop go to completion and hence + * we will find the 'oldest' file. + */ + if (!checkpt_timeonly) { + found_chkpt_file = num++; + break; + } + } + } + + num++; + snprintf(filename, len, "%s.%d", config.log_file, num); + } while (1); + + /* If a checkpoint is loaded but can't find it's file, and we + * are not only just checking the timestamp from the checkpoint file, + * we need to error */ + if (checkpt_filename && have_chkpt_data && found_chkpt_file == -1 + && !checkpt_timeonly) { + free(filename); + free_config(&config); + return 10; + } + + num--; + + /* We note how many files we need to process */ + files_to_process = num; + + /* Got it, now process logs from last to first */ + if (num > 0) + snprintf(filename, len, "%s.%d", config.log_file, num); + else + snprintf(filename, len, "%s", config.log_file); + do { + if ((ret = process_file(filename))) { + free(filename); + free_config(&config); + return ret; + } + if (just_one && found) + break; + files_to_process--; /* one less file to process */ + + /* Get next log file */ + num--; + if (num > 0) + snprintf(filename, len, "%s.%d", config.log_file, num); + else if (num == 0) + snprintf(filename, len, "%s", config.log_file); + else + break; + } while (1); + /* + * If performing a checkpoint, set the checkpointed + * file details - ie remember the last file processed + */ + ret = 0; + if (checkpt_filename) + ret = set_ChkPtFileDetails(filename); + + free(filename); + free_config(&config); + return ret; +} + +/* + * Decide if we should start outputing events given we loaded a checkpoint. + * + * The previous checkpoint will have recorded the last event outputted, + * if there was one. If nothing is to be output, either the audit.log file + * is empty, all the events in it were incomplete, or ??? + * + * We can return + * 0 no output + * 1 can output + * 2 can output but not this event + * 3 we have found an event whose time is > MAX_EVENT_DELTA_SECS secs + * past our checkpoint time, which means this particulare event is + * complete. This should not happen, for we should have found our + * checkpoint event before ANY other completed event. + * + */ +static int chkpt_output_decision(event * e) +{ + static int can_output = 0; + + /* Short cut. Once we made the decision, it's made for good */ + if (can_output) + return 1; + + /* If there was no checkpoint file, we turn on output */ + if (have_chkpt_data == 0) { + can_output = 1; + return 1; /* can output on this event */ + } + + /* + * If the previous checkpoint had no recorded output, then + * we assume everything was partial so we turn on output + */ + if (chkpt_input_levent.sec == 0) { + can_output = 1; + return 1; /* can output on this event */ + } + + /* + * If we are ignoring all but event time from within the checkpoint + * file, then we output if the current event's time is greater than + * or equal to the checkpoint time. + */ + if (checkpt_timeonly) { + if ( (chkpt_input_levent.sec < e->sec) || + ( (chkpt_input_levent.sec == e->sec) && + (chkpt_input_levent.milli <= e->milli) ) ) { + can_output = 1; + return 1; /* can output on this event */ + } + } + + if (chkpt_input_levent.sec == e->sec && + chkpt_input_levent.milli == e->milli && + chkpt_input_levent.serial == e->serial && + chkpt_input_levent.type == e->type ) { + + /* So far a match, so now check the nodes */ + if (chkpt_input_levent.node == NULL && e->node == NULL) { + can_output = 1; + return 2; /* output after this event */ + } + if (chkpt_input_levent.node && e->node && + (strcmp(chkpt_input_levent.node, e->node) == 0) ) { + can_output = 1; + return 2; /* output after this event */ + } + /* + * The nodes are different. Drop through to further checks. + */ + } + /* + * If the event we are looking at is more than MAX_EVENT_DELTA_SECS + * seconds past our checkpoint event, then by definition we should + * have had a complete event (ie a complete event is one where at + * least MAX_EVENT_DELTA_SECS seconds have passed since it's last + * output record). + * This means there is a problem, for the recorded checkpoint event was + * the last complete event in the file when we last processed it. + * Normally we see this if the checkpoint is very old and the system + * has used the same inode again in an audit log file. + */ + if ( (chkpt_input_levent.sec < e->sec) && + ((e->sec - chkpt_input_levent.sec) > MAX_EVENT_DELTA_SECS) ) { +/* fprintf(stderr, "%s %lu.%03u:%lu vs %s %lu.%03u:%lu\n", + chkpt_input_levent.host ? chkpt_input_levent.host : "-", + chkpt_input_levent.sec, chkpt_input_levent.milli, + chkpt_input_levent.serial, + e->host, e->sec, e->milli, e->serial); */ + return 3; + } + + return 0; +} + +static int process_log_fd(void) +{ + llist *entries; // entries in a record + int ret; + int do_output = 1; + + /* For each record in file */ + do { + ret = get_record(&entries); + if ((ret != 0)||(entries->cnt == 0)) { + break; + } + /* + * We flush all events on the last log file being processed. + * Thus incomplete events are 'carried forward' to be + * completed from the rest of it's records we expect to find + * in the next file we are about to process. + */ + if (match(entries)) { + /* + * If we are checkpointing, decide if we output + * this event + */ + if (checkpt_filename) + do_output = chkpt_output_decision(&entries->e); + + if (do_output == 1) { + found = 1; + output_record(entries); + + /* Remember this event if checkpointing */ + if (checkpt_filename) { + if (set_ChkPtLastEvent(&entries->e)) { + list_clear(entries); + free(entries); + fclose(log_fd); + return 4; /* no memory */ + } + } + } else if (do_output == 3) { + fprintf(stderr, + "Corrupted checkpoint file. Inode match, but newer complete event (%lu.%03u:%lu) found before loaded checkpoint %lu.%03u:%lu\n", + entries->e.sec, entries->e.milli, + entries->e.serial, + chkpt_input_levent.sec, + chkpt_input_levent.milli, + chkpt_input_levent.serial); + checkpt_failure |= CP_CORRUPTED; + list_clear(entries); + free(entries); + fclose(log_fd); + return 10; + } + if (just_one) { + list_clear(entries); + free(entries); + break; + } + if (line_buffered) + fflush(stdout); + } + list_clear(entries); + free(entries); + } while (ret == 0); + fclose(log_fd); + + return 0; +} + +static void alarm_handler(int signal) +{ + /* will interrupt current syscall */ +} + +static int process_stdin(void) +{ + log_fd = stdin; + input_is_pipe=1; + + if (signal(SIGALRM, alarm_handler) == SIG_ERR || + siginterrupt(SIGALRM, 1) == -1) + return -1; + + return process_log_fd(); +} + +static int process_file(char *filename) +{ + log_fd = fopen(filename, "rm"); + if (log_fd == NULL) { + fprintf(stderr, "Error opening %s (%s)\n", filename, + strerror(errno)); + return 1; + } + + __fsetlocking(log_fd, FSETLOCKING_BYCALLER); + return process_log_fd(); +} + +/* + * This function returns a malloc'd buffer of the next record in the audit + * logs. It returns 0 on success, 1 on eof, -1 on error. + */ +static int get_record(llist **l) +{ + char *rc; + char *buff = NULL; + int rcount = 0, timer_running = 0; + + /* + * If we have any events ready to print ie have all records that + * make up the event, we just return. If not, we read more lines + * from the files until we get a complete event or finish reading + * input + */ + *l = get_ready_event(&lo); + if (*l) + return 0; + + while (1) { + rcount++; + + if (!buff) { + buff = malloc(MAX_AUDIT_MESSAGE_LENGTH); + if (!buff) + return -1; + } + + if (input_is_pipe && rcount > 1) { + timer_running = 1; + alarm(timeout_interval); + } + + rc = fgets_unlocked(buff, MAX_AUDIT_MESSAGE_LENGTH, + log_fd); + + if (timer_running) { + /* timer may have fired but thats ok */ + timer_running = 0; + alarm(0); + } + + if (rc) { + if (lol_add_record(&lo, buff)) { + *l = get_ready_event(&lo); + if (*l) + break; + } + } else { + free(buff); + /* + * If we get an EINTR error or we are at EOF, we check + * to see if we have any events to print and return + * appropriately. If we are the last file being + * processed, we mark all incomplete events as + * complete so they will be printed. + */ + if ((ferror_unlocked(log_fd) && + errno == EINTR) || feof_unlocked(log_fd)) { + /* + * Only mark all events as L_COMPLETE if we are + * the last file being processed. + * We DO NOT do this if we are checkpointing. + */ + if (files_to_process == 0) { + if (!checkpt_filename) + terminate_all_events(&lo); + } + *l = get_ready_event(&lo); + if (*l) + return 0; + else + return 1; + } else + return -1; /* all other errors are terminal */ + } + } + free(buff); + return 0; +} + diff --git a/framework/src/audit/src/autrace.c b/framework/src/audit/src/autrace.c new file mode 100644 index 00000000..03c0b556 --- /dev/null +++ b/framework/src/audit/src/autrace.c @@ -0,0 +1,329 @@ +/* autrace.c -- + * Copyright 2005-09,2011,2015 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + */ + +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> +#include <time.h> +#include <linux/net.h> +#include "libaudit.h" +#include "private.h" + +/* + * This program will add the audit rules to trace a process similar + * to strace. It will then execute the process. + */ +static int threat = 0; +static int count_rules(void); +static int count_em(int fd); +extern int delete_all_rules(int fd); + +static void usage(void) +{ + fprintf(stderr, "usage: autrace [-r] program\n"); +} + +static int insert_rule(int audit_fd, const char *field) +{ + int rc; + int flags = AUDIT_FILTER_EXIT; + int action = AUDIT_ALWAYS; + struct audit_rule_data *rule = malloc(sizeof(struct audit_rule_data)); + int machine = audit_detect_machine(); + char *t_field = NULL; + + if (rule == NULL) + goto err; + memset(rule, 0, sizeof(struct audit_rule_data)); + if (threat) { + rc = 0; + if (machine != MACH_AARCH64) { + rc |= audit_rule_syscallbyname_data(rule, "open"); + rc |= audit_rule_syscallbyname_data(rule, "creat"); + rc |= audit_rule_syscallbyname_data(rule, "rename"); + rc |= audit_rule_syscallbyname_data(rule, "unlink"); + rc |= audit_rule_syscallbyname_data(rule, "mknod"); + rc |= audit_rule_syscallbyname_data(rule, "mkdir"); + rc |= audit_rule_syscallbyname_data(rule, "rmdir"); + rc |= audit_rule_syscallbyname_data(rule, "chown"); + rc |= audit_rule_syscallbyname_data(rule, "lchown"); + rc |= audit_rule_syscallbyname_data(rule, "chmod"); + rc |= audit_rule_syscallbyname_data(rule, "link"); + rc |= audit_rule_syscallbyname_data(rule, "symlink"); + rc |= audit_rule_syscallbyname_data(rule, "readlink"); + } + rc |= audit_rule_syscallbyname_data(rule, "openat"); + rc |= audit_rule_syscallbyname_data(rule, "truncate"); + rc |= audit_rule_syscallbyname_data(rule, "renameat"); + rc |= audit_rule_syscallbyname_data(rule, "unlinkat"); + rc |= audit_rule_syscallbyname_data(rule, "mknodat"); + rc |= audit_rule_syscallbyname_data(rule, "mkdirat"); + rc |= audit_rule_syscallbyname_data(rule, "chdir"); + rc |= audit_rule_syscallbyname_data(rule, "fchownat"); + rc |= audit_rule_syscallbyname_data(rule, "fchmodat"); + rc |= audit_rule_syscallbyname_data(rule, "linkat"); + rc |= audit_rule_syscallbyname_data(rule, "symlinkat"); + rc |= audit_rule_syscallbyname_data(rule, "readlinkat"); + rc |= audit_rule_syscallbyname_data(rule, "execve"); + rc |= audit_rule_syscallbyname_data(rule, "name_to_handle_at"); + + if (machine != MACH_X86 && machine != MACH_S390X && + machine != MACH_S390) { + rc |= audit_rule_syscallbyname_data(rule, "connect"); + rc |= audit_rule_syscallbyname_data(rule, "bind"); + rc |= audit_rule_syscallbyname_data(rule, "accept"); + rc |= audit_rule_syscallbyname_data(rule, "sendto"); + rc |= audit_rule_syscallbyname_data(rule, "recvfrom"); + rc |= audit_rule_syscallbyname_data(rule, "accept4"); + } + + rc |= audit_rule_syscallbyname_data(rule, "sendfile"); + } else + rc = audit_rule_syscallbyname_data(rule, "all"); + if (rc < 0) + goto err; + t_field = strdup(field); + rc = audit_rule_fieldpair_data(&rule, t_field, flags); + free(t_field); + if (rc < 0) + goto err; + rc = audit_add_rule_data(audit_fd, rule, flags, action); + if (rc < 0) + goto err; + + // Now if i386, lets add its network rules + if (machine == MACH_X86 || machine == MACH_S390X || + machine == MACH_S390) { + int i, a0[6] = { SYS_CONNECT, SYS_BIND, SYS_ACCEPT, SYS_SENDTO, + SYS_RECVFROM, SYS_ACCEPT4 }; + for (i=0; i<6; i++) { + char pair[32]; + + memset(rule, 0, sizeof(struct audit_rule_data)); + rc |= audit_rule_syscallbyname_data(rule, "socketcall"); + snprintf(pair, sizeof(pair), "a0=%d", a0[i]); + rc |= audit_rule_fieldpair_data(&rule, pair, flags); + t_field = strdup(field); + rc |= audit_rule_fieldpair_data(&rule, t_field, flags); + free(t_field); + rc |= audit_add_rule_data(audit_fd, rule, flags, action); + } + } + free(rule); + return 0; +err: + fprintf(stderr, "Error inserting audit rule for %s\n", field); + free(rule); + return 1; +} + +int key_match(struct audit_reply *rep) +{ + return 1; +} + +/* + * Algorithm: + * check that user is root + * check to see if program exists + * if so fork, child waits for parent + * parent clears audit rules, loads audit all syscalls with child's pid + * parent tells child to go & waits for sigchld + * child exec's program + * parent deletes rules after getting sigchld + */ +int main(int argc, char *argv[]) +{ + int fd[2]; + int pid,cmd=1; + char buf[2]; + + if (argc < 2) { + usage(); + return 1; + } + if (strcmp(argv[cmd], "-h") == 0) { + usage(); + return 1; + } + if (strcmp(argv[cmd], "-r") == 0) { + threat = 1; + cmd++; + } + if (getuid() != 0) { + fprintf(stderr, "You must be root to run this program.\n"); + return 1; + } + if (access(argv[cmd], X_OK)) { + if (errno == ENOENT) + fprintf(stderr, "Error - can't find: %s\n", argv[cmd]); + else + fprintf(stderr, "Error checking %s (%s)\n", + argv[cmd], strerror(errno)); + return 1; + } + set_aumessage_mode(MSG_STDERR, DBG_NO); + switch (count_rules()) + { + case -1: + if (errno == ECONNREFUSED) + fprintf(stderr, + "The audit system is disabled\n"); + else + fprintf(stderr, + "Error - can't get rule count.\n"); + return 1; + case 0: + break; + default: + fprintf(stderr, + "autrace cannot be run with rules loaded.\n" + "Please delete all rules using 'auditctl -D' if you " + "really wanted to\nrun this command.\n"); + return 1; + } + if (pipe(fd) != 0) { + fprintf(stderr, "Error creating pipe.\n"); + return 1; + } + + switch ((pid=fork())) + { + case -1: + fprintf(stderr, "Error forking.\n"); + return 1; + case 0: /* Child */ + close(fd[1]); + printf("Waiting to execute: %s\n", argv[cmd]); + while (read(fd[0], buf, 1) == -1 && errno == EINTR) + /* blank */ ; + close(fd[0]); + execvp(argv[cmd], &argv[cmd]); + fprintf(stderr, "Failed to exec %s\n", argv[cmd]); + return 1; + default: /* Parent */ + close(fd[0]); + fcntl(fd[1], F_SETFD, FD_CLOEXEC); + { + char field[16]; + int audit_fd; + audit_fd = audit_open(); + if (audit_fd < 0) + exit(1); + snprintf(field, sizeof(field), "pid=%d", pid); + if (insert_rule(audit_fd, field)) { + kill(pid,SIGTERM); + (void)delete_all_rules(audit_fd); + exit(1); + } + snprintf(field, sizeof(field), "ppid=%d", pid); + if (insert_rule(audit_fd, field)) { + kill(pid,SIGTERM); + (void)delete_all_rules(audit_fd); + exit(1); + } + sleep(1); + if (write(fd[1],"1", 1) != 1) { + kill(pid,SIGTERM); + (void)delete_all_rules(audit_fd); + exit(1); + } + waitpid(pid, NULL, 0); + close(fd[1]); + puts("Cleaning up..."); + (void)delete_all_rules(audit_fd); + close(audit_fd); + } + printf("Trace complete. " + "You can locate the records with " + "\'ausearch -i -p %d\'\n", + pid); + break; + } + + return 0; +} + +static int count_rules(void) +{ + int fd, total, rc; + + fd = audit_open(); + if (fd < 0) + return -1; + + rc = audit_request_rules_list_data(fd); + if (rc > 0) + total = count_em(fd); + else + total = -1; + + close(fd); + return total; +} + +static int count_em(int fd) +{ + int i, retval, count = 0; + int timeout = 40; /* loop has delay of .1 - this is 4 seconds */ + struct audit_reply rep; + fd_set read_mask; + + FD_ZERO(&read_mask); + FD_SET(fd, &read_mask); + + for (i = 0; i < timeout; i++) { + retval = audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0); + if (retval > 0) { + struct timeval t; + + if (rep.type == NLMSG_ERROR && + rep.error->error == 0) + continue; + t.tv_sec = 0; + t.tv_usec = 100000; /* .1 second */ + do { + retval=select(fd+1, &read_mask, NULL, NULL, &t); + } while (retval < 0 && errno == EINTR); + switch (rep.type) + { + case NLMSG_DONE: + return count; + case AUDIT_LIST_RULES: + i = 0; + count++; + break; + case NLMSG_ERROR: + return -1; + default: + break; + } + } + } + return count; +} + diff --git a/framework/src/audit/src/delete_all.c b/framework/src/audit/src/delete_all.c new file mode 100644 index 00000000..4e0feed1 --- /dev/null +++ b/framework/src/audit/src/delete_all.c @@ -0,0 +1,109 @@ +/* delete_all.c -- + * Copyright 2005-06, 2008-09,2014 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + */ +#include "config.h" +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "libaudit.h" +#include "private.h" + +#include "auditctl-llist.h" + +extern int key_match(const struct audit_rule_data *r); + +/* Returns 0 for success and -1 for failure */ +int delete_all_rules(int fd) +{ + int seq, i, rc; + int timeout = 40; /* tenths of seconds */ + struct audit_reply rep; + fd_set read_mask; + llist l; + lnode *n; + + /* list the rules */ + seq = audit_request_rules_list_data(fd); + if (seq <= 0) + return -1; + + FD_ZERO(&read_mask); + FD_SET(fd, &read_mask); + list_create(&l); + + for (i = 0; i < timeout; i++) { + struct timeval t; + + t.tv_sec = 0; + t.tv_usec = 100000; /* .1 second */ + do { + rc = select(fd+1, &read_mask, NULL, NULL, &t); + } while (rc < 0 && errno == EINTR); + // We'll try to read just in case + rc = audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0); + if (rc > 0) { + /* Reset timeout */ + i = 0; + + /* Don't make decisions based on wrong packet */ + if (rep.nlh->nlmsg_seq != seq) + continue; + + /* If we get done or error, break out */ + if (rep.type == NLMSG_DONE) + break; + + if (rep.type == NLMSG_ERROR && rep.error->error) { + audit_msg(LOG_ERR, + "Error receiving rules list (%s)", + strerror(-rep.error->error)); + return -1; + } + + /* If its not what we are expecting, keep looping */ + if (rep.type != AUDIT_LIST_RULES) + continue; + + if (key_match(rep.ruledata)) + list_append(&l, rep.ruledata, + sizeof(struct audit_rule_data) + + rep.ruledata->buflen); + + } + } + list_first(&l); + n = l.cur; + while (n) { + /* Bounce it right back with delete */ + rc = audit_send(fd, AUDIT_DEL_RULE, n->r, n->size); + if (rc < 0) { + audit_msg(LOG_ERR, "Error deleting rule (%s)", + strerror(-rc)); + return -1; + } + n = list_next(&l); + } + list_clear(&l); + + return 0; +} + diff --git a/framework/src/audit/src/libev/Makefile.am b/framework/src/audit/src/libev/Makefile.am new file mode 100644 index 00000000..a35e7b0d --- /dev/null +++ b/framework/src/audit/src/libev/Makefile.am @@ -0,0 +1,29 @@ +# Makefile.am-- +# Copyright 2008,2011-12 Red Hat Inc., Durham, North Carolina. +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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 +# +# Authors: +# Steve Grubb <sgrubb@redhat.com> +# +VERSION_INFO = 4:0:0 +EXTRA_DIST = README ev_epoll.c ev_poll.c ev_select.c libev.m4 +AM_CFLAGS = -fPIC -DPIC -g -fno-strict-aliasing ${DEBUG} + +noinst_HEADERS = ev.h ev_vars.h ev_wrap.h event.h +noinst_LIBRARIES = libev.a + +libev_a_SOURCES = ev.c event.c diff --git a/framework/src/audit/src/libev/README b/framework/src/audit/src/libev/README new file mode 100644 index 00000000..31f61938 --- /dev/null +++ b/framework/src/audit/src/libev/README @@ -0,0 +1,58 @@ +libev is a high-performance event loop/event model with lots of features. +(see benchmark at http://libev.schmorp.de/bench.html) + + +ABOUT + + Homepage: http://software.schmorp.de/pkg/libev + Mailinglist: libev@lists.schmorp.de + http://lists.schmorp.de/cgi-bin/mailman/listinfo/libev + Library Documentation: http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod + + Libev is modelled (very losely) after libevent and the Event perl + module, but is faster, scales better and is more correct, and also more + featureful. And also smaller. Yay. + + Some of the specialties of libev not commonly found elsewhere are: + + - extensive and detailed, readable documentation (not doxygen garbage). + - fully supports fork, can detect fork in various ways and automatically + re-arms kernel mechanisms that do not support fork. + - highly optimised select, poll, epoll, kqueue and event ports backends. + - filesystem object (path) watching (with optional linux inotify support). + - wallclock-based times (using absolute time, cron-like). + - relative timers/timeouts (handle time jumps). + - fast intra-thread communication between multiple + event loops (with optional fast linux eventfd backend). + - extremely easy to embed (fully documented, no dependencies, + autoconf supported but optional). + - very small codebase, no bloated library, simple code. + - fully extensible by being able to plug into the event loop, + integrate other event loops, integrate other event loop users. + - very little memory use (small watchers, small event loop data). + - optional C++ interface allowing method and function callbacks + at no extra memory or runtime overhead. + - optional Perl interface with similar characteristics (capable + of running Glib/Gtk2 on libev). + - support for other languages (multiple C++ interfaces, D, Ruby, + Python) available from third-parties. + + Examples of programs that embed libev: the EV perl module, node.js, + auditd, rxvt-unicode, gvpe (GNU Virtual Private Ethernet), the + Deliantra MMORPG server (http://www.deliantra.net/), Rubinius (a + next-generation Ruby VM), the Ebb web server, the Rev event toolkit. + + +CONTRIBUTORS + + libev was written and designed by Marc Lehmann and Emanuele Giaquinta. + + The following people sent in patches or made other noteworthy + contributions to the design (for minor patches, see the Changes + file. If I forgot to include you, please shout at me, it was an + accident): + + W.C.A. Wijngaards + Christopher Layne + Chris Brody + diff --git a/framework/src/audit/src/libev/ev.c b/framework/src/audit/src/libev/ev.c new file mode 100644 index 00000000..3e7013f9 --- /dev/null +++ b/framework/src/audit/src/libev/ev.c @@ -0,0 +1,4971 @@ +/* + * libev event processing core, watcher management + * + * Copyright (c) 2007,2008,2009,2010,2011,2012,2013 Marc Alexander Lehmann <libev@schmorp.de> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +/* this big block deduces configuration from config.h */ +#ifndef EV_STANDALONE +# ifdef EV_CONFIG_H +# include EV_CONFIG_H +# else +# include "config.h" +# endif + +# if HAVE_FLOOR +# ifndef EV_USE_FLOOR +# define EV_USE_FLOOR 1 +# endif +# endif + +# if HAVE_CLOCK_SYSCALL +# ifndef EV_USE_CLOCK_SYSCALL +# define EV_USE_CLOCK_SYSCALL 1 +# ifndef EV_USE_REALTIME +# define EV_USE_REALTIME 0 +# endif +# ifndef EV_USE_MONOTONIC +# define EV_USE_MONOTONIC 1 +# endif +# endif +# elif !defined EV_USE_CLOCK_SYSCALL +# define EV_USE_CLOCK_SYSCALL 0 +# endif + +# if HAVE_CLOCK_GETTIME +# ifndef EV_USE_MONOTONIC +# define EV_USE_MONOTONIC 1 +# endif +# ifndef EV_USE_REALTIME +# define EV_USE_REALTIME 0 +# endif +# else +# ifndef EV_USE_MONOTONIC +# define EV_USE_MONOTONIC 0 +# endif +# ifndef EV_USE_REALTIME +# define EV_USE_REALTIME 0 +# endif +# endif + +# if HAVE_NANOSLEEP +# ifndef EV_USE_NANOSLEEP +# define EV_USE_NANOSLEEP EV_FEATURE_OS +# endif +# else +# undef EV_USE_NANOSLEEP +# define EV_USE_NANOSLEEP 0 +# endif + +# if HAVE_SELECT && HAVE_SYS_SELECT_H +# ifndef EV_USE_SELECT +# define EV_USE_SELECT EV_FEATURE_BACKENDS +# endif +# else +# undef EV_USE_SELECT +# define EV_USE_SELECT 0 +# endif + +# if HAVE_POLL && HAVE_POLL_H +# ifndef EV_USE_POLL +# define EV_USE_POLL EV_FEATURE_BACKENDS +# endif +# else +# undef EV_USE_POLL +# define EV_USE_POLL 0 +# endif + +# if HAVE_EPOLL_CTL && HAVE_SYS_EPOLL_H +# ifndef EV_USE_EPOLL +# define EV_USE_EPOLL EV_FEATURE_BACKENDS +# endif +# else +# undef EV_USE_EPOLL +# define EV_USE_EPOLL 0 +# endif + +# if HAVE_KQUEUE && HAVE_SYS_EVENT_H +# ifndef EV_USE_KQUEUE +# define EV_USE_KQUEUE EV_FEATURE_BACKENDS +# endif +# else +# undef EV_USE_KQUEUE +# define EV_USE_KQUEUE 0 +# endif + +# if HAVE_PORT_H && HAVE_PORT_CREATE +# ifndef EV_USE_PORT +# define EV_USE_PORT EV_FEATURE_BACKENDS +# endif +# else +# undef EV_USE_PORT +# define EV_USE_PORT 0 +# endif + +# if HAVE_INOTIFY_INIT && HAVE_SYS_INOTIFY_H +# ifndef EV_USE_INOTIFY +# define EV_USE_INOTIFY EV_FEATURE_OS +# endif +# else +# undef EV_USE_INOTIFY +# define EV_USE_INOTIFY 0 +# endif + +# if HAVE_SIGNALFD && HAVE_SYS_SIGNALFD_H +# ifndef EV_USE_SIGNALFD +# define EV_USE_SIGNALFD EV_FEATURE_OS +# endif +# else +# undef EV_USE_SIGNALFD +# define EV_USE_SIGNALFD 0 +# endif + +# if HAVE_EVENTFD +# ifndef EV_USE_EVENTFD +# define EV_USE_EVENTFD EV_FEATURE_OS +# endif +# else +# undef EV_USE_EVENTFD +# define EV_USE_EVENTFD 0 +# endif + +#endif + +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <stddef.h> + +#include <stdio.h> + +#include <assert.h> +#include <errno.h> +#include <sys/types.h> +#include <time.h> +#include <limits.h> + +#include <signal.h> + +#ifdef EV_H +# include EV_H +#else +# include "ev.h" +#endif + +#if EV_NO_THREADS +# undef EV_NO_SMP +# define EV_NO_SMP 1 +# undef ECB_NO_THREADS +# define ECB_NO_THREADS 1 +#endif +#if EV_NO_SMP +# undef EV_NO_SMP +# define ECB_NO_SMP 1 +#endif + +#ifndef _WIN32 +# include <sys/time.h> +# include <sys/wait.h> +# include <unistd.h> +#else +# include <io.h> +# define WIN32_LEAN_AND_MEAN +# include <winsock2.h> +# include <windows.h> +# ifndef EV_SELECT_IS_WINSOCKET +# define EV_SELECT_IS_WINSOCKET 1 +# endif +# undef EV_AVOID_STDIO +#endif + +/* OS X, in its infinite idiocy, actually HARDCODES + * a limit of 1024 into their select. Where people have brains, + * OS X engineers apparently have a vacuum. Or maybe they were + * ordered to have a vacuum, or they do anything for money. + * This might help. Or not. + */ +#define _DARWIN_UNLIMITED_SELECT 1 + +/* this block tries to deduce configuration from header-defined symbols and defaults */ + +/* try to deduce the maximum number of signals on this platform */ +#if defined EV_NSIG +/* use what's provided */ +#elif defined NSIG +# define EV_NSIG (NSIG) +#elif defined _NSIG +# define EV_NSIG (_NSIG) +#elif defined SIGMAX +# define EV_NSIG (SIGMAX+1) +#elif defined SIG_MAX +# define EV_NSIG (SIG_MAX+1) +#elif defined _SIG_MAX +# define EV_NSIG (_SIG_MAX+1) +#elif defined MAXSIG +# define EV_NSIG (MAXSIG+1) +#elif defined MAX_SIG +# define EV_NSIG (MAX_SIG+1) +#elif defined SIGARRAYSIZE +# define EV_NSIG (SIGARRAYSIZE) /* Assume ary[SIGARRAYSIZE] */ +#elif defined _sys_nsig +# define EV_NSIG (_sys_nsig) /* Solaris 2.5 */ +#else +# define EV_NSIG (8 * sizeof (sigset_t) + 1) +#endif + +#ifndef EV_USE_FLOOR +# define EV_USE_FLOOR 0 +#endif + +#ifndef EV_USE_CLOCK_SYSCALL +# if __linux && __GLIBC__ == 2 && __GLIBC_MINOR__ < 17 +# define EV_USE_CLOCK_SYSCALL EV_FEATURE_OS +# else +# define EV_USE_CLOCK_SYSCALL 0 +# endif +#endif + +#if !(_POSIX_TIMERS > 0) +# ifndef EV_USE_MONOTONIC +# define EV_USE_MONOTONIC 0 +# endif +# ifndef EV_USE_REALTIME +# define EV_USE_REALTIME 0 +# endif +#endif + +#ifndef EV_USE_MONOTONIC +# if defined _POSIX_MONOTONIC_CLOCK && _POSIX_MONOTONIC_CLOCK >= 0 +# define EV_USE_MONOTONIC EV_FEATURE_OS +# else +# define EV_USE_MONOTONIC 0 +# endif +#endif + +#ifndef EV_USE_REALTIME +# define EV_USE_REALTIME !EV_USE_CLOCK_SYSCALL +#endif + +#ifndef EV_USE_NANOSLEEP +# if _POSIX_C_SOURCE >= 199309L +# define EV_USE_NANOSLEEP EV_FEATURE_OS +# else +# define EV_USE_NANOSLEEP 0 +# endif +#endif + +#ifndef EV_USE_SELECT +# define EV_USE_SELECT EV_FEATURE_BACKENDS +#endif + +#ifndef EV_USE_POLL +# ifdef _WIN32 +# define EV_USE_POLL 0 +# else +# define EV_USE_POLL EV_FEATURE_BACKENDS +# endif +#endif + +#ifndef EV_USE_EPOLL +# if __linux && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 4)) +# define EV_USE_EPOLL EV_FEATURE_BACKENDS +# else +# define EV_USE_EPOLL 0 +# endif +#endif + +#ifndef EV_USE_KQUEUE +# define EV_USE_KQUEUE 0 +#endif + +#ifndef EV_USE_PORT +# define EV_USE_PORT 0 +#endif + +#ifndef EV_USE_INOTIFY +# if __linux && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 4)) +# define EV_USE_INOTIFY EV_FEATURE_OS +# else +# define EV_USE_INOTIFY 0 +# endif +#endif + +#ifndef EV_PID_HASHSIZE +# define EV_PID_HASHSIZE EV_FEATURE_DATA ? 16 : 1 +#endif + +#ifndef EV_INOTIFY_HASHSIZE +# define EV_INOTIFY_HASHSIZE EV_FEATURE_DATA ? 16 : 1 +#endif + +#ifndef EV_USE_EVENTFD +# if __linux && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 7)) +# define EV_USE_EVENTFD EV_FEATURE_OS +# else +# define EV_USE_EVENTFD 0 +# endif +#endif + +#ifndef EV_USE_SIGNALFD +# if __linux && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 7)) +# define EV_USE_SIGNALFD EV_FEATURE_OS +# else +# define EV_USE_SIGNALFD 0 +# endif +#endif + +#if 0 /* debugging */ +# define EV_VERIFY 3 +# define EV_USE_4HEAP 1 +# define EV_HEAP_CACHE_AT 1 +#endif + +#ifndef EV_VERIFY +# define EV_VERIFY (EV_FEATURE_API ? 1 : 0) +#endif + +#ifndef EV_USE_4HEAP +# define EV_USE_4HEAP EV_FEATURE_DATA +#endif + +#ifndef EV_HEAP_CACHE_AT +# define EV_HEAP_CACHE_AT EV_FEATURE_DATA +#endif + +#ifdef ANDROID +/* supposedly, android doesn't typedef fd_mask */ +# undef EV_USE_SELECT +# define EV_USE_SELECT 0 +/* supposedly, we need to include syscall.h, not sys/syscall.h, so just disable */ +# undef EV_USE_CLOCK_SYSCALL +# define EV_USE_CLOCK_SYSCALL 0 +#endif + +/* aix's poll.h seems to cause lots of trouble */ +#ifdef _AIX +/* AIX has a completely broken poll.h header */ +# undef EV_USE_POLL +# define EV_USE_POLL 0 +#endif + +/* on linux, we can use a (slow) syscall to avoid a dependency on pthread, */ +/* which makes programs even slower. might work on other unices, too. */ +#if EV_USE_CLOCK_SYSCALL +# include <sys/syscall.h> +# ifdef SYS_clock_gettime +# define clock_gettime(id, ts) syscall (SYS_clock_gettime, (id), (ts)) +# undef EV_USE_MONOTONIC +# define EV_USE_MONOTONIC 1 +# else +# undef EV_USE_CLOCK_SYSCALL +# define EV_USE_CLOCK_SYSCALL 0 +# endif +#endif + +/* this block fixes any misconfiguration where we know we run into trouble otherwise */ + +#ifndef CLOCK_MONOTONIC +# undef EV_USE_MONOTONIC +# define EV_USE_MONOTONIC 0 +#endif + +#ifndef CLOCK_REALTIME +# undef EV_USE_REALTIME +# define EV_USE_REALTIME 0 +#endif + +#if !EV_STAT_ENABLE +# undef EV_USE_INOTIFY +# define EV_USE_INOTIFY 0 +#endif + +#if !EV_USE_NANOSLEEP +/* hp-ux has it in sys/time.h, which we unconditionally include above */ +# if !defined _WIN32 && !defined __hpux +# include <sys/select.h> +# endif +#endif + +#if EV_USE_INOTIFY +# include <sys/statfs.h> +# include <sys/inotify.h> +/* some very old inotify.h headers don't have IN_DONT_FOLLOW */ +# ifndef IN_DONT_FOLLOW +# undef EV_USE_INOTIFY +# define EV_USE_INOTIFY 0 +# endif +#endif + +#if EV_USE_EVENTFD +/* our minimum requirement is glibc 2.7 which has the stub, but not the header */ +# include <stdint.h> +# ifndef EFD_NONBLOCK +# define EFD_NONBLOCK O_NONBLOCK +# endif +# ifndef EFD_CLOEXEC +# ifdef O_CLOEXEC +# define EFD_CLOEXEC O_CLOEXEC +# else +# define EFD_CLOEXEC 02000000 +# endif +# endif +EV_CPP(extern "C") int (eventfd) (unsigned int initval, int flags); +#endif + +#if EV_USE_SIGNALFD +/* our minimum requirement is glibc 2.7 which has the stub, but not the header */ +# include <stdint.h> +# ifndef SFD_NONBLOCK +# define SFD_NONBLOCK O_NONBLOCK +# endif +# ifndef SFD_CLOEXEC +# ifdef O_CLOEXEC +# define SFD_CLOEXEC O_CLOEXEC +# else +# define SFD_CLOEXEC 02000000 +# endif +# endif +EV_CPP (extern "C") int signalfd (int fd, const sigset_t *mask, int flags); + +struct signalfd_siginfo +{ + uint32_t ssi_signo; + char pad[128 - sizeof (uint32_t)]; +}; +#endif + +/**/ + +#if EV_VERIFY >= 3 +# define EV_FREQUENT_CHECK ev_verify (EV_A) +#else +# define EV_FREQUENT_CHECK do { } while (0) +#endif + +/* + * This is used to work around floating point rounding problems. + * This value is good at least till the year 4000. + */ +#define MIN_INTERVAL 0.0001220703125 /* 1/2**13, good till 4000 */ +/*#define MIN_INTERVAL 0.00000095367431640625 * 1/2**20, good till 2200 */ + +#define MIN_TIMEJUMP 1. /* minimum timejump that gets detected (if monotonic clock available) */ +#define MAX_BLOCKTIME 59.743 /* never wait longer than this time (to detect time jumps) */ + +#define EV_TV_SET(tv,t) do { tv.tv_sec = (long)t; tv.tv_usec = (long)((t - tv.tv_sec) * 1e6); } while (0) +#define EV_TS_SET(ts,t) do { ts.tv_sec = (long)t; ts.tv_nsec = (long)((t - ts.tv_sec) * 1e9); } while (0) + +/* the following is ecb.h embedded into libev - use update_ev_c to update from an external copy */ +/* ECB.H BEGIN */ +/* + * libecb - http://software.schmorp.de/pkg/libecb + * + * Copyright (©) 2009-2015 Marc Alexander Lehmann <libecb@schmorp.de> + * Copyright (©) 2011 Emanuele Giaquinta + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#ifndef ECB_H +#define ECB_H + +/* 16 bits major, 16 bits minor */ +#define ECB_VERSION 0x00010004 + +#ifdef _WIN32 + typedef signed char int8_t; + typedef unsigned char uint8_t; + typedef signed short int16_t; + typedef unsigned short uint16_t; + typedef signed int int32_t; + typedef unsigned int uint32_t; + #if __GNUC__ + typedef signed long long int64_t; + typedef unsigned long long uint64_t; + #else /* _MSC_VER || __BORLANDC__ */ + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; + #endif + #ifdef _WIN64 + #define ECB_PTRSIZE 8 + typedef uint64_t uintptr_t; + typedef int64_t intptr_t; + #else + #define ECB_PTRSIZE 4 + typedef uint32_t uintptr_t; + typedef int32_t intptr_t; + #endif +#else + #include <inttypes.h> + #if UINTMAX_MAX > 0xffffffffU + #define ECB_PTRSIZE 8 + #else + #define ECB_PTRSIZE 4 + #endif +#endif + +#define ECB_GCC_AMD64 (__amd64 || __amd64__ || __x86_64 || __x86_64__) +#define ECB_MSVC_AMD64 (_M_AMD64 || _M_X64) + +/* work around x32 idiocy by defining proper macros */ +#if ECB_GCC_AMD64 || ECB_MSVC_AMD64 + #if _ILP32 + #define ECB_AMD64_X32 1 + #else + #define ECB_AMD64 1 + #endif +#endif + +/* many compilers define _GNUC_ to some versions but then only implement + * what their idiot authors think are the "more important" extensions, + * causing enormous grief in return for some better fake benchmark numbers. + * or so. + * we try to detect these and simply assume they are not gcc - if they have + * an issue with that they should have done it right in the first place. + */ +#if !defined __GNUC_MINOR__ || defined __INTEL_COMPILER || defined __SUNPRO_C || defined __SUNPRO_CC || defined __llvm__ || defined __clang__ + #define ECB_GCC_VERSION(major,minor) 0 +#else + #define ECB_GCC_VERSION(major,minor) (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))) +#endif + +#define ECB_CLANG_VERSION(major,minor) (__clang_major__ > (major) || (__clang_major__ == (major) && __clang_minor__ >= (minor))) + +#if __clang__ && defined __has_builtin + #define ECB_CLANG_BUILTIN(x) __has_builtin (x) +#else + #define ECB_CLANG_BUILTIN(x) 0 +#endif + +#if __clang__ && defined __has_extension + #define ECB_CLANG_EXTENSION(x) __has_extension (x) +#else + #define ECB_CLANG_EXTENSION(x) 0 +#endif + +#define ECB_CPP (__cplusplus+0) +#define ECB_CPP11 (__cplusplus >= 201103L) + +#if ECB_CPP + #define ECB_C 0 + #define ECB_STDC_VERSION 0 +#else + #define ECB_C 1 + #define ECB_STDC_VERSION __STDC_VERSION__ +#endif + +#define ECB_C99 (ECB_STDC_VERSION >= 199901L) +#define ECB_C11 (ECB_STDC_VERSION >= 201112L) + +#if ECB_CPP + #define ECB_EXTERN_C extern "C" + #define ECB_EXTERN_C_BEG ECB_EXTERN_C { + #define ECB_EXTERN_C_END } +#else + #define ECB_EXTERN_C extern + #define ECB_EXTERN_C_BEG + #define ECB_EXTERN_C_END +#endif + +/*****************************************************************************/ + +/* ECB_NO_THREADS - ecb is not used by multiple threads, ever */ +/* ECB_NO_SMP - ecb might be used in multiple threads, but only on a single cpu */ + +#if ECB_NO_THREADS + #define ECB_NO_SMP 1 +#endif + +#if ECB_NO_SMP + #define ECB_MEMORY_FENCE do { } while (0) +#endif + +/* http://www-01.ibm.com/support/knowledgecenter/SSGH3R_13.1.0/com.ibm.xlcpp131.aix.doc/compiler_ref/compiler_builtins.html */ +#if __xlC__ && ECB_CPP + #include <builtins.h> +#endif + +#ifndef ECB_MEMORY_FENCE + #if ECB_GCC_VERSION(2,5) || defined __INTEL_COMPILER || (__llvm__ && __GNUC__) || __SUNPRO_C >= 0x5110 || __SUNPRO_CC >= 0x5110 + #if __i386 || __i386__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("lock; orb $0, -1(%%esp)" : : : "memory") + #define ECB_MEMORY_FENCE_ACQUIRE __asm__ __volatile__ ("" : : : "memory") + #define ECB_MEMORY_FENCE_RELEASE __asm__ __volatile__ ("") + #elif ECB_GCC_AMD64 + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("mfence" : : : "memory") + #define ECB_MEMORY_FENCE_ACQUIRE __asm__ __volatile__ ("" : : : "memory") + #define ECB_MEMORY_FENCE_RELEASE __asm__ __volatile__ ("") + #elif __powerpc__ || __ppc__ || __powerpc64__ || __ppc64__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("sync" : : : "memory") + #elif defined __ARM_ARCH_6__ || defined __ARM_ARCH_6J__ \ + || defined __ARM_ARCH_6K__ || defined __ARM_ARCH_6ZK__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("mcr p15,0,%0,c7,c10,5" : : "r" (0) : "memory") + #elif defined __ARM_ARCH_7__ || defined __ARM_ARCH_7A__ \ + || defined __ARM_ARCH_7M__ || defined __ARM_ARCH_7R__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("dmb" : : : "memory") + #elif __aarch64__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("dmb ish" : : : "memory") + #elif (__sparc || __sparc__) && !__sparcv8 + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("membar #LoadStore | #LoadLoad | #StoreStore | #StoreLoad" : : : "memory") + #define ECB_MEMORY_FENCE_ACQUIRE __asm__ __volatile__ ("membar #LoadStore | #LoadLoad" : : : "memory") + #define ECB_MEMORY_FENCE_RELEASE __asm__ __volatile__ ("membar #LoadStore | #StoreStore") + #elif defined __s390__ || defined __s390x__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("bcr 15,0" : : : "memory") + #elif defined __mips__ + /* GNU/Linux emulates sync on mips1 architectures, so we force its use */ + /* anybody else who still uses mips1 is supposed to send in their version, with detection code. */ + #define ECB_MEMORY_FENCE __asm__ __volatile__ (".set mips2; sync; .set mips0" : : : "memory") + #elif defined __alpha__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("mb" : : : "memory") + #elif defined __hppa__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("" : : : "memory") + #define ECB_MEMORY_FENCE_RELEASE __asm__ __volatile__ ("") + #elif defined __ia64__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("mf" : : : "memory") + #elif defined __m68k__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("" : : : "memory") + #elif defined __m88k__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("tb1 0,%%r0,128" : : : "memory") + #elif defined __sh__ + #define ECB_MEMORY_FENCE __asm__ __volatile__ ("" : : : "memory") + #endif + #endif +#endif + +#ifndef ECB_MEMORY_FENCE + #if ECB_GCC_VERSION(4,7) + /* see comment below (stdatomic.h) about the C11 memory model. */ + #define ECB_MEMORY_FENCE __atomic_thread_fence (__ATOMIC_SEQ_CST) + #define ECB_MEMORY_FENCE_ACQUIRE __atomic_thread_fence (__ATOMIC_ACQUIRE) + #define ECB_MEMORY_FENCE_RELEASE __atomic_thread_fence (__ATOMIC_RELEASE) + + #elif ECB_CLANG_EXTENSION(c_atomic) + /* see comment below (stdatomic.h) about the C11 memory model. */ + #define ECB_MEMORY_FENCE __c11_atomic_thread_fence (__ATOMIC_SEQ_CST) + #define ECB_MEMORY_FENCE_ACQUIRE __c11_atomic_thread_fence (__ATOMIC_ACQUIRE) + #define ECB_MEMORY_FENCE_RELEASE __c11_atomic_thread_fence (__ATOMIC_RELEASE) + + #elif ECB_GCC_VERSION(4,4) || defined __INTEL_COMPILER || defined __clang__ + #define ECB_MEMORY_FENCE __sync_synchronize () + #elif _MSC_VER >= 1500 /* VC++ 2008 */ + /* apparently, microsoft broke all the memory barrier stuff in Visual Studio 2008... */ + #pragma intrinsic(_ReadBarrier,_WriteBarrier,_ReadWriteBarrier) + #define ECB_MEMORY_FENCE _ReadWriteBarrier (); MemoryBarrier() + #define ECB_MEMORY_FENCE_ACQUIRE _ReadWriteBarrier (); MemoryBarrier() /* according to msdn, _ReadBarrier is not a load fence */ + #define ECB_MEMORY_FENCE_RELEASE _WriteBarrier (); MemoryBarrier() + #elif _MSC_VER >= 1400 /* VC++ 2005 */ + #pragma intrinsic(_ReadBarrier,_WriteBarrier,_ReadWriteBarrier) + #define ECB_MEMORY_FENCE _ReadWriteBarrier () + #define ECB_MEMORY_FENCE_ACQUIRE _ReadWriteBarrier () /* according to msdn, _ReadBarrier is not a load fence */ + #define ECB_MEMORY_FENCE_RELEASE _WriteBarrier () + #elif defined _WIN32 + #include <WinNT.h> + #define ECB_MEMORY_FENCE MemoryBarrier () /* actually just xchg on x86... scary */ + #elif __SUNPRO_C >= 0x5110 || __SUNPRO_CC >= 0x5110 + #include <mbarrier.h> + #define ECB_MEMORY_FENCE __machine_rw_barrier () + #define ECB_MEMORY_FENCE_ACQUIRE __machine_r_barrier () + #define ECB_MEMORY_FENCE_RELEASE __machine_w_barrier () + #elif __xlC__ + #define ECB_MEMORY_FENCE __sync () + #endif +#endif + +#ifndef ECB_MEMORY_FENCE + #if ECB_C11 && !defined __STDC_NO_ATOMICS__ + /* we assume that these memory fences work on all variables/all memory accesses, */ + /* not just C11 atomics and atomic accesses */ + #include <stdatomic.h> + /* Unfortunately, neither gcc 4.7 nor clang 3.1 generate any instructions for */ + /* any fence other than seq_cst, which isn't very efficient for us. */ + /* Why that is, we don't know - either the C11 memory model is quite useless */ + /* for most usages, or gcc and clang have a bug */ + /* I *currently* lean towards the latter, and inefficiently implement */ + /* all three of ecb's fences as a seq_cst fence */ + /* Update, gcc-4.8 generates mfence for all c++ fences, but nothing */ + /* for all __atomic_thread_fence's except seq_cst */ + #define ECB_MEMORY_FENCE atomic_thread_fence (memory_order_seq_cst) + #endif +#endif + +#ifndef ECB_MEMORY_FENCE + #if !ECB_AVOID_PTHREADS + /* + * if you get undefined symbol references to pthread_mutex_lock, + * or failure to find pthread.h, then you should implement + * the ECB_MEMORY_FENCE operations for your cpu/compiler + * OR provide pthread.h and link against the posix thread library + * of your system. + */ + #include <pthread.h> + #define ECB_NEEDS_PTHREADS 1 + #define ECB_MEMORY_FENCE_NEEDS_PTHREADS 1 + + static pthread_mutex_t ecb_mf_lock = PTHREAD_MUTEX_INITIALIZER; + #define ECB_MEMORY_FENCE do { pthread_mutex_lock (&ecb_mf_lock); pthread_mutex_unlock (&ecb_mf_lock); } while (0) + #endif +#endif + +#if !defined ECB_MEMORY_FENCE_ACQUIRE && defined ECB_MEMORY_FENCE + #define ECB_MEMORY_FENCE_ACQUIRE ECB_MEMORY_FENCE +#endif + +#if !defined ECB_MEMORY_FENCE_RELEASE && defined ECB_MEMORY_FENCE + #define ECB_MEMORY_FENCE_RELEASE ECB_MEMORY_FENCE +#endif + +/*****************************************************************************/ + +#if ECB_CPP + #define ecb_inline static inline +#elif ECB_GCC_VERSION(2,5) + #define ecb_inline static __inline__ +#elif ECB_C99 + #define ecb_inline static inline +#else + #define ecb_inline static +#endif + +#if ECB_GCC_VERSION(3,3) + #define ecb_restrict __restrict__ +#elif ECB_C99 + #define ecb_restrict restrict +#else + #define ecb_restrict +#endif + +typedef int ecb_bool; + +#define ECB_CONCAT_(a, b) a ## b +#define ECB_CONCAT(a, b) ECB_CONCAT_(a, b) +#define ECB_STRINGIFY_(a) # a +#define ECB_STRINGIFY(a) ECB_STRINGIFY_(a) +#define ECB_STRINGIFY_EXPR(expr) ((expr), ECB_STRINGIFY_ (expr)) + +#define ecb_function_ ecb_inline + +#if ECB_GCC_VERSION(3,1) || ECB_CLANG_VERSION(2,8) + #define ecb_attribute(attrlist) __attribute__ (attrlist) +#else + #define ecb_attribute(attrlist) +#endif + +#if ECB_GCC_VERSION(3,1) || ECB_CLANG_BUILTIN(__builtin_constant_p) + #define ecb_is_constant(expr) __builtin_constant_p (expr) +#else + /* possible C11 impl for integral types + typedef struct ecb_is_constant_struct ecb_is_constant_struct; + #define ecb_is_constant(expr) _Generic ((1 ? (struct ecb_is_constant_struct *)0 : (void *)((expr) - (expr)), ecb_is_constant_struct *: 0, default: 1)) */ + + #define ecb_is_constant(expr) 0 +#endif + +#if ECB_GCC_VERSION(3,1) || ECB_CLANG_BUILTIN(__builtin_expect) + #define ecb_expect(expr,value) __builtin_expect ((expr),(value)) +#else + #define ecb_expect(expr,value) (expr) +#endif + +#if ECB_GCC_VERSION(3,1) || ECB_CLANG_BUILTIN(__builtin_prefetch) + #define ecb_prefetch(addr,rw,locality) __builtin_prefetch (addr, rw, locality) +#else + #define ecb_prefetch(addr,rw,locality) +#endif + +/* no emulation for ecb_decltype */ +#if ECB_CPP11 + // older implementations might have problems with decltype(x)::type, work around it + template<class T> struct ecb_decltype_t { typedef T type; }; + #define ecb_decltype(x) ecb_decltype_t<decltype (x)>::type +#elif ECB_GCC_VERSION(3,0) || ECB_CLANG_VERSION(2,8) + #define ecb_decltype(x) __typeof__ (x) +#endif + +#if _MSC_VER >= 1300 + #define ecb_deprecated __declspec (deprecated) +#else + #define ecb_deprecated ecb_attribute ((__deprecated__)) +#endif + +#if _MSC_VER >= 1500 + #define ecb_deprecated_message(msg) __declspec (deprecated (msg)) +#elif ECB_GCC_VERSION(4,5) + #define ecb_deprecated_message(msg) ecb_attribute ((__deprecated__ (msg)) +#else + #define ecb_deprecated_message(msg) ecb_deprecated +#endif + +#if _MSC_VER >= 1400 + #define ecb_noinline __declspec (noinline) +#else + #define ecb_noinline ecb_attribute ((__noinline__)) +#endif + +#define ecb_unused ecb_attribute ((__unused__)) +#define ecb_const ecb_attribute ((__const__)) +#define ecb_pure ecb_attribute ((__pure__)) + +#if ECB_C11 || __IBMC_NORETURN + /* http://www-01.ibm.com/support/knowledgecenter/SSGH3R_13.1.0/com.ibm.xlcpp131.aix.doc/language_ref/noreturn.html */ + #define ecb_noreturn _Noreturn +#elif ECB_CPP11 + #define ecb_noreturn [[noreturn]] +#elif _MSC_VER >= 1200 + /* http://msdn.microsoft.com/en-us/library/k6ktzx3s.aspx */ + #define ecb_noreturn __declspec (noreturn) +#else + #define ecb_noreturn ecb_attribute ((__noreturn__)) +#endif + +#if ECB_GCC_VERSION(4,3) + #define ecb_artificial ecb_attribute ((__artificial__)) + #define ecb_hot ecb_attribute ((__hot__)) + #define ecb_cold ecb_attribute ((__cold__)) +#else + #define ecb_artificial + #define ecb_hot + #define ecb_cold +#endif + +/* put around conditional expressions if you are very sure that the */ +/* expression is mostly true or mostly false. note that these return */ +/* booleans, not the expression. */ +#define ecb_expect_false(expr) ecb_expect (!!(expr), 0) +#define ecb_expect_true(expr) ecb_expect (!!(expr), 1) +/* for compatibility to the rest of the world */ +#define ecb_likely(expr) ecb_expect_true (expr) +#define ecb_unlikely(expr) ecb_expect_false (expr) + +/* count trailing zero bits and count # of one bits */ +#if ECB_GCC_VERSION(3,4) \ + || (ECB_CLANG_BUILTIN(__builtin_clz) && ECB_CLANG_BUILTIN(__builtin_clzll) \ + && ECB_CLANG_BUILTIN(__builtin_ctz) && ECB_CLANG_BUILTIN(__builtin_ctzll) \ + && ECB_CLANG_BUILTIN(__builtin_popcount)) + /* we assume int == 32 bit, long == 32 or 64 bit and long long == 64 bit */ + #define ecb_ld32(x) (__builtin_clz (x) ^ 31) + #define ecb_ld64(x) (__builtin_clzll (x) ^ 63) + #define ecb_ctz32(x) __builtin_ctz (x) + #define ecb_ctz64(x) __builtin_ctzll (x) + #define ecb_popcount32(x) __builtin_popcount (x) + /* no popcountll */ +#else + ecb_function_ ecb_const int ecb_ctz32 (uint32_t x); + ecb_function_ ecb_const int + ecb_ctz32 (uint32_t x) + { + int r = 0; + + x &= ~x + 1; /* this isolates the lowest bit */ + +#if ECB_branchless_on_i386 + r += !!(x & 0xaaaaaaaa) << 0; + r += !!(x & 0xcccccccc) << 1; + r += !!(x & 0xf0f0f0f0) << 2; + r += !!(x & 0xff00ff00) << 3; + r += !!(x & 0xffff0000) << 4; +#else + if (x & 0xaaaaaaaa) r += 1; + if (x & 0xcccccccc) r += 2; + if (x & 0xf0f0f0f0) r += 4; + if (x & 0xff00ff00) r += 8; + if (x & 0xffff0000) r += 16; +#endif + + return r; + } + + ecb_function_ ecb_const int ecb_ctz64 (uint64_t x); + ecb_function_ ecb_const int + ecb_ctz64 (uint64_t x) + { + int shift = x & 0xffffffffU ? 0 : 32; + return ecb_ctz32 (x >> shift) + shift; + } + + ecb_function_ ecb_const int ecb_popcount32 (uint32_t x); + ecb_function_ ecb_const int + ecb_popcount32 (uint32_t x) + { + x -= (x >> 1) & 0x55555555; + x = ((x >> 2) & 0x33333333) + (x & 0x33333333); + x = ((x >> 4) + x) & 0x0f0f0f0f; + x *= 0x01010101; + + return x >> 24; + } + + ecb_function_ ecb_const int ecb_ld32 (uint32_t x); + ecb_function_ ecb_const int ecb_ld32 (uint32_t x) + { + int r = 0; + + if (x >> 16) { x >>= 16; r += 16; } + if (x >> 8) { x >>= 8; r += 8; } + if (x >> 4) { x >>= 4; r += 4; } + if (x >> 2) { x >>= 2; r += 2; } + if (x >> 1) { r += 1; } + + return r; + } + + ecb_function_ ecb_const int ecb_ld64 (uint64_t x); + ecb_function_ ecb_const int ecb_ld64 (uint64_t x) + { + int r = 0; + + if (x >> 32) { x >>= 32; r += 32; } + + return r + ecb_ld32 (x); + } +#endif + +ecb_function_ ecb_const ecb_bool ecb_is_pot32 (uint32_t x); +ecb_function_ ecb_const ecb_bool ecb_is_pot32 (uint32_t x) { return !(x & (x - 1)); } +ecb_function_ ecb_const ecb_bool ecb_is_pot64 (uint64_t x); +ecb_function_ ecb_const ecb_bool ecb_is_pot64 (uint64_t x) { return !(x & (x - 1)); } + +ecb_function_ ecb_const uint8_t ecb_bitrev8 (uint8_t x); +ecb_function_ ecb_const uint8_t ecb_bitrev8 (uint8_t x) +{ + return ( (x * 0x0802U & 0x22110U) + | (x * 0x8020U & 0x88440U)) * 0x10101U >> 16; +} + +ecb_function_ ecb_const uint16_t ecb_bitrev16 (uint16_t x); +ecb_function_ ecb_const uint16_t ecb_bitrev16 (uint16_t x) +{ + x = ((x >> 1) & 0x5555) | ((x & 0x5555) << 1); + x = ((x >> 2) & 0x3333) | ((x & 0x3333) << 2); + x = ((x >> 4) & 0x0f0f) | ((x & 0x0f0f) << 4); + x = ( x >> 8 ) | ( x << 8); + + return x; +} + +ecb_function_ ecb_const uint32_t ecb_bitrev32 (uint32_t x); +ecb_function_ ecb_const uint32_t ecb_bitrev32 (uint32_t x) +{ + x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1); + x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2); + x = ((x >> 4) & 0x0f0f0f0f) | ((x & 0x0f0f0f0f) << 4); + x = ((x >> 8) & 0x00ff00ff) | ((x & 0x00ff00ff) << 8); + x = ( x >> 16 ) | ( x << 16); + + return x; +} + +/* popcount64 is only available on 64 bit cpus as gcc builtin */ +/* so for this version we are lazy */ +ecb_function_ ecb_const int ecb_popcount64 (uint64_t x); +ecb_function_ ecb_const int +ecb_popcount64 (uint64_t x) +{ + return ecb_popcount32 (x) + ecb_popcount32 (x >> 32); +} + +ecb_inline ecb_const uint8_t ecb_rotl8 (uint8_t x, unsigned int count); +ecb_inline ecb_const uint8_t ecb_rotr8 (uint8_t x, unsigned int count); +ecb_inline ecb_const uint16_t ecb_rotl16 (uint16_t x, unsigned int count); +ecb_inline ecb_const uint16_t ecb_rotr16 (uint16_t x, unsigned int count); +ecb_inline ecb_const uint32_t ecb_rotl32 (uint32_t x, unsigned int count); +ecb_inline ecb_const uint32_t ecb_rotr32 (uint32_t x, unsigned int count); +ecb_inline ecb_const uint64_t ecb_rotl64 (uint64_t x, unsigned int count); +ecb_inline ecb_const uint64_t ecb_rotr64 (uint64_t x, unsigned int count); + +ecb_inline ecb_const uint8_t ecb_rotl8 (uint8_t x, unsigned int count) { return (x >> ( 8 - count)) | (x << count); } +ecb_inline ecb_const uint8_t ecb_rotr8 (uint8_t x, unsigned int count) { return (x << ( 8 - count)) | (x >> count); } +ecb_inline ecb_const uint16_t ecb_rotl16 (uint16_t x, unsigned int count) { return (x >> (16 - count)) | (x << count); } +ecb_inline ecb_const uint16_t ecb_rotr16 (uint16_t x, unsigned int count) { return (x << (16 - count)) | (x >> count); } +ecb_inline ecb_const uint32_t ecb_rotl32 (uint32_t x, unsigned int count) { return (x >> (32 - count)) | (x << count); } +ecb_inline ecb_const uint32_t ecb_rotr32 (uint32_t x, unsigned int count) { return (x << (32 - count)) | (x >> count); } +ecb_inline ecb_const uint64_t ecb_rotl64 (uint64_t x, unsigned int count) { return (x >> (64 - count)) | (x << count); } +ecb_inline ecb_const uint64_t ecb_rotr64 (uint64_t x, unsigned int count) { return (x << (64 - count)) | (x >> count); } + +#if ECB_GCC_VERSION(4,3) || (ECB_CLANG_BUILTIN(__builtin_bswap32) && ECB_CLANG_BUILTIN(__builtin_bswap64)) + #if ECB_GCC_VERSION(4,8) || ECB_CLANG_BUILTIN(__builtin_bswap16) + #define ecb_bswap16(x) __builtin_bswap16 (x) + #else + #define ecb_bswap16(x) (__builtin_bswap32 (x) >> 16) + #endif + #define ecb_bswap32(x) __builtin_bswap32 (x) + #define ecb_bswap64(x) __builtin_bswap64 (x) +#elif _MSC_VER + #include <stdlib.h> + #define ecb_bswap16(x) ((uint16_t)_byteswap_ushort ((uint16_t)(x))) + #define ecb_bswap32(x) ((uint32_t)_byteswap_ulong ((uint32_t)(x))) + #define ecb_bswap64(x) ((uint64_t)_byteswap_uint64 ((uint64_t)(x))) +#else + ecb_function_ ecb_const uint16_t ecb_bswap16 (uint16_t x); + ecb_function_ ecb_const uint16_t + ecb_bswap16 (uint16_t x) + { + return ecb_rotl16 (x, 8); + } + + ecb_function_ ecb_const uint32_t ecb_bswap32 (uint32_t x); + ecb_function_ ecb_const uint32_t + ecb_bswap32 (uint32_t x) + { + return (((uint32_t)ecb_bswap16 (x)) << 16) | ecb_bswap16 (x >> 16); + } + + ecb_function_ ecb_const uint64_t ecb_bswap64 (uint64_t x); + ecb_function_ ecb_const uint64_t + ecb_bswap64 (uint64_t x) + { + return (((uint64_t)ecb_bswap32 (x)) << 32) | ecb_bswap32 (x >> 32); + } +#endif + +#if ECB_GCC_VERSION(4,5) || ECB_CLANG_BUILTIN(__builtin_unreachable) + #define ecb_unreachable() __builtin_unreachable () +#else + /* this seems to work fine, but gcc always emits a warning for it :/ */ + ecb_inline ecb_noreturn void ecb_unreachable (void); + ecb_inline ecb_noreturn void ecb_unreachable (void) { } +#endif + +/* try to tell the compiler that some condition is definitely true */ +#define ecb_assume(cond) if (!(cond)) ecb_unreachable (); else 0 + +ecb_inline ecb_const unsigned char ecb_byteorder_helper (void); +ecb_inline ecb_const unsigned char +ecb_byteorder_helper (void) +{ + /* the union code still generates code under pressure in gcc, */ + /* but less than using pointers, and always seems to */ + /* successfully return a constant. */ + /* the reason why we have this horrible preprocessor mess */ + /* is to avoid it in all cases, at least on common architectures */ + /* or when using a recent enough gcc version (>= 4.6) */ +#if ((__i386 || __i386__) && !__VOS__) || _M_IX86 || ECB_GCC_AMD64 || ECB_MSVC_AMD64 + return 0x44; +#elif __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return 0x44; +#elif __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + return 0x11; +#else + union + { + uint32_t i; + uint8_t c; + } u = { 0x11223344 }; + return u.c; +#endif +} + +ecb_inline ecb_const ecb_bool ecb_big_endian (void); +ecb_inline ecb_const ecb_bool ecb_big_endian (void) { return ecb_byteorder_helper () == 0x11; } +ecb_inline ecb_const ecb_bool ecb_little_endian (void); +ecb_inline ecb_const ecb_bool ecb_little_endian (void) { return ecb_byteorder_helper () == 0x44; } + +#if ECB_GCC_VERSION(3,0) || ECB_C99 + #define ecb_mod(m,n) ((m) % (n) + ((m) % (n) < 0 ? (n) : 0)) +#else + #define ecb_mod(m,n) ((m) < 0 ? ((n) - 1 - ((-1 - (m)) % (n))) : ((m) % (n))) +#endif + +#if ECB_CPP + template<typename T> + static inline T ecb_div_rd (T val, T div) + { + return val < 0 ? - ((-val + div - 1) / div) : (val ) / div; + } + template<typename T> + static inline T ecb_div_ru (T val, T div) + { + return val < 0 ? - ((-val ) / div) : (val + div - 1) / div; + } +#else + #define ecb_div_rd(val,div) ((val) < 0 ? - ((-(val) + (div) - 1) / (div)) : ((val) ) / (div)) + #define ecb_div_ru(val,div) ((val) < 0 ? - ((-(val) ) / (div)) : ((val) + (div) - 1) / (div)) +#endif + +#if ecb_cplusplus_does_not_suck + /* does not work for local types (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2657.htm) */ + template<typename T, int N> + static inline int ecb_array_length (const T (&arr)[N]) + { + return N; + } +#else + #define ecb_array_length(name) (sizeof (name) / sizeof (name [0])) +#endif + +/*******************************************************************************/ +/* floating point stuff, can be disabled by defining ECB_NO_LIBM */ + +/* basically, everything uses "ieee pure-endian" floating point numbers */ +/* the only noteworthy exception is ancient armle, which uses order 43218765 */ +#if 0 \ + || __i386 || __i386__ \ + || ECB_GCC_AMD64 \ + || __powerpc__ || __ppc__ || __powerpc64__ || __ppc64__ \ + || defined __s390__ || defined __s390x__ \ + || defined __mips__ \ + || defined __alpha__ \ + || defined __hppa__ \ + || defined __ia64__ \ + || defined __m68k__ \ + || defined __m88k__ \ + || defined __sh__ \ + || defined _M_IX86 || defined ECB_MSVC_AMD64 || defined _M_IA64 \ + || (defined __arm__ && (defined __ARM_EABI__ || defined __EABI__ || defined __VFP_FP__ || defined _WIN32_WCE || defined __ANDROID__)) \ + || defined __aarch64__ + #define ECB_STDFP 1 + #include <string.h> /* for memcpy */ +#else + #define ECB_STDFP 0 +#endif + +#ifndef ECB_NO_LIBM + + #include <math.h> /* for frexp*, ldexp*, INFINITY, NAN */ + + /* only the oldest of old doesn't have this one. solaris. */ + #ifdef INFINITY + #define ECB_INFINITY INFINITY + #else + #define ECB_INFINITY HUGE_VAL + #endif + + #ifdef NAN + #define ECB_NAN NAN + #else + #define ECB_NAN ECB_INFINITY + #endif + + #if ECB_C99 || _XOPEN_VERSION >= 600 || _POSIX_VERSION >= 200112L + #define ecb_ldexpf(x,e) ldexpf ((x), (e)) + #define ecb_frexpf(x,e) frexpf ((x), (e)) + #else + #define ecb_ldexpf(x,e) (float) ldexp ((double) (x), (e)) + #define ecb_frexpf(x,e) (float) frexp ((double) (x), (e)) + #endif + + /* converts an ieee half/binary16 to a float */ + ecb_function_ ecb_const float ecb_binary16_to_float (uint16_t x); + ecb_function_ ecb_const float + ecb_binary16_to_float (uint16_t x) + { + int e = (x >> 10) & 0x1f; + int m = x & 0x3ff; + float r; + + if (!e ) r = ecb_ldexpf (m , -24); + else if (e != 31) r = ecb_ldexpf (m + 0x400, e - 25); + else if (m ) r = ECB_NAN; + else r = ECB_INFINITY; + + return x & 0x8000 ? -r : r; + } + + /* convert a float to ieee single/binary32 */ + ecb_function_ ecb_const uint32_t ecb_float_to_binary32 (float x); + ecb_function_ ecb_const uint32_t + ecb_float_to_binary32 (float x) + { + uint32_t r; + + #if ECB_STDFP + memcpy (&r, &x, 4); + #else + /* slow emulation, works for anything but -0 */ + uint32_t m; + int e; + + if (x == 0e0f ) return 0x00000000U; + if (x > +3.40282346638528860e+38f) return 0x7f800000U; + if (x < -3.40282346638528860e+38f) return 0xff800000U; + if (x != x ) return 0x7fbfffffU; + + m = ecb_frexpf (x, &e) * 0x1000000U; + + r = m & 0x80000000U; + + if (r) + m = -m; + + if (e <= -126) + { + m &= 0xffffffU; + m >>= (-125 - e); + e = -126; + } + + r |= (e + 126) << 23; + r |= m & 0x7fffffU; + #endif + + return r; + } + + /* converts an ieee single/binary32 to a float */ + ecb_function_ ecb_const float ecb_binary32_to_float (uint32_t x); + ecb_function_ ecb_const float + ecb_binary32_to_float (uint32_t x) + { + float r; + + #if ECB_STDFP + memcpy (&r, &x, 4); + #else + /* emulation, only works for normals and subnormals and +0 */ + int neg = x >> 31; + int e = (x >> 23) & 0xffU; + + x &= 0x7fffffU; + + if (e) + x |= 0x800000U; + else + e = 1; + + /* we distrust ldexpf a bit and do the 2**-24 scaling by an extra multiply */ + r = ecb_ldexpf (x * (0.5f / 0x800000U), e - 126); + + r = neg ? -r : r; + #endif + + return r; + } + + /* convert a double to ieee double/binary64 */ + ecb_function_ ecb_const uint64_t ecb_double_to_binary64 (double x); + ecb_function_ ecb_const uint64_t + ecb_double_to_binary64 (double x) + { + uint64_t r; + + #if ECB_STDFP + memcpy (&r, &x, 8); + #else + /* slow emulation, works for anything but -0 */ + uint64_t m; + int e; + + if (x == 0e0 ) return 0x0000000000000000U; + if (x > +1.79769313486231470e+308) return 0x7ff0000000000000U; + if (x < -1.79769313486231470e+308) return 0xfff0000000000000U; + if (x != x ) return 0X7ff7ffffffffffffU; + + m = frexp (x, &e) * 0x20000000000000U; + + r = m & 0x8000000000000000;; + + if (r) + m = -m; + + if (e <= -1022) + { + m &= 0x1fffffffffffffU; + m >>= (-1021 - e); + e = -1022; + } + + r |= ((uint64_t)(e + 1022)) << 52; + r |= m & 0xfffffffffffffU; + #endif + + return r; + } + + /* converts an ieee double/binary64 to a double */ + ecb_function_ ecb_const double ecb_binary64_to_double (uint64_t x); + ecb_function_ ecb_const double + ecb_binary64_to_double (uint64_t x) + { + double r; + + #if ECB_STDFP + memcpy (&r, &x, 8); + #else + /* emulation, only works for normals and subnormals and +0 */ + int neg = x >> 63; + int e = (x >> 52) & 0x7ffU; + + x &= 0xfffffffffffffU; + + if (e) + x |= 0x10000000000000U; + else + e = 1; + + /* we distrust ldexp a bit and do the 2**-53 scaling by an extra multiply */ + r = ldexp (x * (0.5 / 0x10000000000000U), e - 1022); + + r = neg ? -r : r; + #endif + + return r; + } + +#endif + +#endif + +/* ECB.H END */ + +#if ECB_MEMORY_FENCE_NEEDS_PTHREADS +/* if your architecture doesn't need memory fences, e.g. because it is + * single-cpu/core, or if you use libev in a project that doesn't use libev + * from multiple threads, then you can define ECB_AVOID_PTHREADS when compiling + * libev, in which cases the memory fences become nops. + * alternatively, you can remove this #error and link against libpthread, + * which will then provide the memory fences. + */ +# error "memory fences not defined for your architecture, please report" +#endif + +#ifndef ECB_MEMORY_FENCE +# define ECB_MEMORY_FENCE do { } while (0) +# define ECB_MEMORY_FENCE_ACQUIRE ECB_MEMORY_FENCE +# define ECB_MEMORY_FENCE_RELEASE ECB_MEMORY_FENCE +#endif + +#define expect_false(cond) ecb_expect_false (cond) +#define expect_true(cond) ecb_expect_true (cond) +#define noinline ecb_noinline + +#define inline_size ecb_inline + +#if EV_FEATURE_CODE +# define inline_speed ecb_inline +#else +# define inline_speed static noinline +#endif + +#define NUMPRI (EV_MAXPRI - EV_MINPRI + 1) + +#if EV_MINPRI == EV_MAXPRI +# define ABSPRI(w) (((W)w), 0) +#else +# define ABSPRI(w) (((W)w)->priority - EV_MINPRI) +#endif + +#define EMPTY /* required for microsofts broken pseudo-c compiler */ +#define EMPTY2(a,b) /* used to suppress some warnings */ + +typedef ev_watcher *W; +typedef ev_watcher_list *WL; +typedef ev_watcher_time *WT; + +#define ev_active(w) ((W)(w))->active +#define ev_at(w) ((WT)(w))->at + +#if EV_USE_REALTIME +/* sig_atomic_t is used to avoid per-thread variables or locking but still */ +/* giving it a reasonably high chance of working on typical architectures */ +static EV_ATOMIC_T have_realtime; /* did clock_gettime (CLOCK_REALTIME) work? */ +#endif + +#if EV_USE_MONOTONIC +static EV_ATOMIC_T have_monotonic; /* did clock_gettime (CLOCK_MONOTONIC) work? */ +#endif + +#ifndef EV_FD_TO_WIN32_HANDLE +# define EV_FD_TO_WIN32_HANDLE(fd) _get_osfhandle (fd) +#endif +#ifndef EV_WIN32_HANDLE_TO_FD +# define EV_WIN32_HANDLE_TO_FD(handle) _open_osfhandle (handle, 0) +#endif +#ifndef EV_WIN32_CLOSE_FD +# define EV_WIN32_CLOSE_FD(fd) close (fd) +#endif + +#ifdef _WIN32 +# include "ev_win32.c" +#endif + +/*****************************************************************************/ + +/* define a suitable floor function (only used by periodics atm) */ + +#if EV_USE_FLOOR +# include <math.h> +# define ev_floor(v) floor (v) +#else + +#include <float.h> + +/* a floor() replacement function, should be independent of ev_tstamp type */ +static ev_tstamp noinline +ev_floor (ev_tstamp v) +{ + /* the choice of shift factor is not terribly important */ +#if FLT_RADIX != 2 /* assume FLT_RADIX == 10 */ + const ev_tstamp shift = sizeof (unsigned long) >= 8 ? 10000000000000000000. : 1000000000.; +#else + const ev_tstamp shift = sizeof (unsigned long) >= 8 ? 18446744073709551616. : 4294967296.; +#endif + + /* argument too large for an unsigned long? */ + if (expect_false (v >= shift)) + { + ev_tstamp f; + + if (v == v - 1.) + return v; /* very large number */ + + f = shift * ev_floor (v * (1. / shift)); + return f + ev_floor (v - f); + } + + /* special treatment for negative args? */ + if (expect_false (v < 0.)) + { + ev_tstamp f = -ev_floor (-v); + + return f - (f == v ? 0 : 1); + } + + /* fits into an unsigned long */ + return (unsigned long)v; +} + +#endif + +/*****************************************************************************/ + +#ifdef __linux +# include <sys/utsname.h> +#endif + +static unsigned int noinline ecb_cold +ev_linux_version (void) +{ +#ifdef __linux + unsigned int v = 0; + struct utsname buf; + int i; + char *p = buf.release; + + if (uname (&buf)) + return 0; + + for (i = 3+1; --i; ) + { + unsigned int c = 0; + + for (;;) + { + if (*p >= '0' && *p <= '9') + c = c * 10 + *p++ - '0'; + else + { + p += *p == '.'; + break; + } + } + + v = (v << 8) | c; + } + + return v; +#else + return 0; +#endif +} + +/*****************************************************************************/ + +#if EV_AVOID_STDIO +static void noinline ecb_cold +ev_printerr (const char *msg) +{ + int rc; + do { + rc = write (STDERR_FILENO, msg, strlen (msg)); + } while (errno == EINTR && rc < 0); +} +#endif + +static void (*syserr_cb)(const char *msg) EV_THROW; + +void ecb_cold +ev_set_syserr_cb (void (*cb)(const char *msg) EV_THROW) EV_THROW +{ + syserr_cb = cb; +} + +static void noinline ecb_cold +ev_syserr (const char *msg) +{ + if (!msg) + msg = "(libev) system error"; + + if (syserr_cb) + syserr_cb (msg); + else + { +#if EV_AVOID_STDIO + ev_printerr (msg); + ev_printerr (": "); + ev_printerr (strerror (errno)); + ev_printerr ("\n"); +#else + perror (msg); +#endif + abort (); + } +} + +static void * +ev_realloc_emul (void *ptr, long size) EV_THROW +{ + /* some systems, notably openbsd and darwin, fail to properly + * implement realloc (x, 0) (as required by both ansi c-89 and + * the single unix specification, so work around them here. + * recently, also (at least) fedora and debian started breaking it, + * despite documenting it otherwise. + */ + + if (size) + return realloc (ptr, size); + + free (ptr); + return 0; +} + +static void *(*alloc)(void *ptr, long size) EV_THROW = ev_realloc_emul; + +void ecb_cold +ev_set_allocator (void *(*cb)(void *ptr, long size) EV_THROW) EV_THROW +{ + alloc = cb; +} + +inline_speed void * +ev_realloc (void *ptr, long size) +{ + ptr = alloc (ptr, size); + + if (!ptr && size) + { +#if EV_AVOID_STDIO + ev_printerr ("(libev) memory allocation failed, aborting.\n"); +#else + fprintf (stderr, "(libev) cannot allocate %ld bytes, aborting.", size); +#endif + abort (); + } + + return ptr; +} + +#define ev_malloc(size) ev_realloc (0, (size)) +#define ev_free(ptr) free (ptr) + +/*****************************************************************************/ + +/* set in reify when reification needed */ +#define EV_ANFD_REIFY 1 + +/* file descriptor info structure */ +typedef struct +{ + WL head; + unsigned char events; /* the events watched for */ + unsigned char reify; /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) */ + unsigned char emask; /* the epoll backend stores the actual kernel mask in here */ + unsigned char unused; +#if EV_USE_EPOLL + unsigned int egen; /* generation counter to counter epoll bugs */ +#endif +#if EV_SELECT_IS_WINSOCKET || EV_USE_IOCP + SOCKET handle; +#endif +#if EV_USE_IOCP + OVERLAPPED or, ow; +#endif +} ANFD; + +/* stores the pending event set for a given watcher */ +typedef struct +{ + W w; + int events; /* the pending event set for the given watcher */ +} ANPENDING; + +#if EV_USE_INOTIFY +/* hash table entry per inotify-id */ +typedef struct +{ + WL head; +} ANFS; +#endif + +/* Heap Entry */ +#if EV_HEAP_CACHE_AT + /* a heap element */ + typedef struct { + ev_tstamp at; + WT w; + } ANHE; + + #define ANHE_w(he) (he).w /* access watcher, read-write */ + #define ANHE_at(he) (he).at /* access cached at, read-only */ + #define ANHE_at_cache(he) (he).at = (he).w->at /* update at from watcher */ +#else + /* a heap element */ + typedef WT ANHE; + + #define ANHE_w(he) (he) + #define ANHE_at(he) (he)->at + #define ANHE_at_cache(he) +#endif + +#if EV_MULTIPLICITY + + struct ev_loop + { + ev_tstamp ev_rt_now; + #define ev_rt_now ((loop)->ev_rt_now) + #define VAR(name,decl) decl; + #include "ev_vars.h" + #undef VAR + }; + #include "ev_wrap.h" + + static struct ev_loop default_loop_struct; + EV_API_DECL struct ev_loop *ev_default_loop_ptr = 0; /* needs to be initialised to make it a definition despite extern */ + +#else + + EV_API_DECL ev_tstamp ev_rt_now = 0; /* needs to be initialised to make it a definition despite extern */ + #define VAR(name,decl) static decl; + #include "ev_vars.h" + #undef VAR + + static int ev_default_loop_ptr; + +#endif + +#if EV_FEATURE_API +# define EV_RELEASE_CB if (expect_false (release_cb)) release_cb (EV_A) +# define EV_ACQUIRE_CB if (expect_false (acquire_cb)) acquire_cb (EV_A) +# define EV_INVOKE_PENDING invoke_cb (EV_A) +#else +# define EV_RELEASE_CB (void)0 +# define EV_ACQUIRE_CB (void)0 +# define EV_INVOKE_PENDING ev_invoke_pending (EV_A) +#endif + +#define EVBREAK_RECURSE 0x80 + +/*****************************************************************************/ + +#ifndef EV_HAVE_EV_TIME +ev_tstamp +ev_time (void) EV_THROW +{ +#if EV_USE_REALTIME + if (expect_true (have_realtime)) + { + struct timespec ts; + clock_gettime (CLOCK_REALTIME, &ts); + return ts.tv_sec + ts.tv_nsec * 1e-9; + } +#endif + + struct timeval tv; + gettimeofday (&tv, 0); + return tv.tv_sec + tv.tv_usec * 1e-6; +} +#endif + +inline_size ev_tstamp +get_clock (void) +{ +#if EV_USE_MONOTONIC + if (expect_true (have_monotonic)) + { + struct timespec ts; + clock_gettime (CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec * 1e-9; + } +#endif + + return ev_time (); +} + +#if EV_MULTIPLICITY +ev_tstamp +ev_now (EV_P) EV_THROW +{ + return ev_rt_now; +} +#endif + +void +ev_sleep (ev_tstamp delay) EV_THROW +{ + if (delay > 0.) + { +#if EV_USE_NANOSLEEP + struct timespec ts; + + EV_TS_SET (ts, delay); + nanosleep (&ts, 0); +#elif defined _WIN32 + Sleep ((unsigned long)(delay * 1e3)); +#else + struct timeval tv; + + /* here we rely on sys/time.h + sys/types.h + unistd.h providing select */ + /* something not guaranteed by newer posix versions, but guaranteed */ + /* by older ones */ + EV_TV_SET (tv, delay); + select (0, 0, 0, 0, &tv); +#endif + } +} + +/*****************************************************************************/ + +#define MALLOC_ROUND 4096 /* prefer to allocate in chunks of this size, must be 2**n and >> 4 longs */ + +/* find a suitable new size for the given array, */ +/* hopefully by rounding to a nice-to-malloc size */ +inline_size int +array_nextsize (int elem, int cur, int cnt) +{ + int ncur = cur + 1; + + do + ncur <<= 1; + while (cnt > ncur); + + /* if size is large, round to MALLOC_ROUND - 4 * longs to accommodate malloc overhead */ + if (elem * ncur > MALLOC_ROUND - sizeof (void *) * 4) + { + ncur *= elem; + ncur = (ncur + elem + (MALLOC_ROUND - 1) + sizeof (void *) * 4) & ~(MALLOC_ROUND - 1); + ncur = ncur - sizeof (void *) * 4; + ncur /= elem; + } + + return ncur; +} + +static void * noinline ecb_cold +array_realloc (int elem, void *base, int *cur, int cnt) +{ + *cur = array_nextsize (elem, *cur, cnt); + return ev_realloc (base, elem * *cur); +} + +#define array_init_zero(base,count) \ + memset ((void *)(base), 0, sizeof (*(base)) * (count)) + +#define array_needsize(type,base,cur,cnt,init) \ + if (expect_false ((cnt) > (cur))) \ + { \ + int ecb_unused ocur_ = (cur); \ + (base) = (type *)array_realloc \ + (sizeof (type), (base), &(cur), (cnt)); \ + init ((base) + (ocur_), (cur) - ocur_); \ + } + +#if 0 +#define array_slim(type,stem) \ + if (stem ## max < array_roundsize (stem ## cnt >> 2)) \ + { \ + stem ## max = array_roundsize (stem ## cnt >> 1); \ + base = (type *)ev_realloc (base, sizeof (type) * (stem ## max));\ + fprintf (stderr, "slimmed down " # stem " to %d\n", stem ## max);/*D*/\ + } +#endif + +#define array_free(stem, idx) \ + ev_free (stem ## s idx); stem ## cnt idx = stem ## max idx = 0; stem ## s idx = 0 + +/*****************************************************************************/ + +/* dummy callback for pending events */ +static void noinline +pendingcb (EV_P_ ev_prepare *w, int revents) +{ +} + +void noinline +ev_feed_event (EV_P_ void *w, int revents) EV_THROW +{ + W w_ = (W)w; + int pri = ABSPRI (w_); + + if (expect_false (w_->pending)) + pendings [pri][w_->pending - 1].events |= revents; + else + { + w_->pending = ++pendingcnt [pri]; + array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w_->pending, EMPTY2); + pendings [pri][w_->pending - 1].w = w_; + pendings [pri][w_->pending - 1].events = revents; + } + + pendingpri = NUMPRI - 1; +} + +inline_speed void +feed_reverse (EV_P_ W w) +{ + array_needsize (W, rfeeds, rfeedmax, rfeedcnt + 1, EMPTY2); + rfeeds [rfeedcnt++] = w; +} + +inline_size void +feed_reverse_done (EV_P_ int revents) +{ + do + ev_feed_event (EV_A_ rfeeds [--rfeedcnt], revents); + while (rfeedcnt); +} + +inline_speed void +queue_events (EV_P_ W *events, int eventcnt, int type) +{ + int i; + + for (i = 0; i < eventcnt; ++i) + ev_feed_event (EV_A_ events [i], type); +} + +/*****************************************************************************/ + +inline_speed void +fd_event_nocheck (EV_P_ int fd, int revents) +{ + ANFD *anfd = anfds + fd; + ev_io *w; + + for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next) + { + int ev = w->events & revents; + + if (ev) + ev_feed_event (EV_A_ (W)w, ev); + } +} + +/* do not submit kernel events for fds that have reify set */ +/* because that means they changed while we were polling for new events */ +inline_speed void +fd_event (EV_P_ int fd, int revents) +{ + ANFD *anfd = anfds + fd; + + if (expect_true (!anfd->reify)) + fd_event_nocheck (EV_A_ fd, revents); +} + +void +ev_feed_fd_event (EV_P_ int fd, int revents) EV_THROW +{ + if (fd >= 0 && fd < anfdmax) + fd_event_nocheck (EV_A_ fd, revents); +} + +/* make sure the external fd watch events are in-sync */ +/* with the kernel/libev internal state */ +inline_size void +fd_reify (EV_P) +{ + int i; + +#if EV_SELECT_IS_WINSOCKET || EV_USE_IOCP + for (i = 0; i < fdchangecnt; ++i) + { + int fd = fdchanges [i]; + ANFD *anfd = anfds + fd; + + if (anfd->reify & EV__IOFDSET && anfd->head) + { + SOCKET handle = EV_FD_TO_WIN32_HANDLE (fd); + + if (handle != anfd->handle) + { + unsigned long arg; + + assert (("libev: only socket fds supported in this configuration", ioctlsocket (handle, FIONREAD, &arg) == 0)); + + /* handle changed, but fd didn't - we need to do it in two steps */ + backend_modify (EV_A_ fd, anfd->events, 0); + anfd->events = 0; + anfd->handle = handle; + } + } + } +#endif + + for (i = 0; i < fdchangecnt; ++i) + { + int fd = fdchanges [i]; + ANFD *anfd = anfds + fd; + ev_io *w; + + unsigned char o_events = anfd->events; + unsigned char o_reify = anfd->reify; + + anfd->reify = 0; + + /*if (expect_true (o_reify & EV_ANFD_REIFY)) probably a deoptimisation */ + { + anfd->events = 0; + + for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next) + anfd->events |= (unsigned char)w->events; + + if (o_events != anfd->events) + o_reify = EV__IOFDSET; /* actually |= */ + } + + if (o_reify & EV__IOFDSET) + backend_modify (EV_A_ fd, o_events, anfd->events); + } + + fdchangecnt = 0; +} + +/* something about the given fd changed */ +inline_size void +fd_change (EV_P_ int fd, int flags) +{ + unsigned char reify = anfds [fd].reify; + anfds [fd].reify |= flags; + + if (expect_true (!reify)) + { + ++fdchangecnt; + array_needsize (int, fdchanges, fdchangemax, fdchangecnt, EMPTY2); + fdchanges [fdchangecnt - 1] = fd; + } +} + +/* the given fd is invalid/unusable, so make sure it doesn't hurt us anymore */ +inline_speed void ecb_cold +fd_kill (EV_P_ int fd) +{ + ev_io *w; + + while ((w = (ev_io *)anfds [fd].head)) + { + ev_io_stop (EV_A_ w); + ev_feed_event (EV_A_ (W)w, EV_ERROR | EV_READ | EV_WRITE); + } +} + +/* check whether the given fd is actually valid, for error recovery */ +inline_size int ecb_cold +fd_valid (int fd) +{ +#ifdef _WIN32 + return EV_FD_TO_WIN32_HANDLE (fd) != -1; +#else + return fcntl (fd, F_GETFD) != -1; +#endif +} + +/* called on EBADF to verify fds */ +static void noinline ecb_cold +fd_ebadf (EV_P) +{ + int fd; + + for (fd = 0; fd < anfdmax; ++fd) + if (anfds [fd].events) + if (!fd_valid (fd) && errno == EBADF) + fd_kill (EV_A_ fd); +} + +/* called on ENOMEM in select/poll to kill some fds and retry */ +static void noinline ecb_cold +fd_enomem (EV_P) +{ + int fd; + + for (fd = anfdmax; fd--; ) + if (anfds [fd].events) + { + fd_kill (EV_A_ fd); + break; + } +} + +/* usually called after fork if backend needs to re-arm all fds from scratch */ +static void noinline +fd_rearm_all (EV_P) +{ + int fd; + + for (fd = 0; fd < anfdmax; ++fd) + if (anfds [fd].events) + { + anfds [fd].events = 0; + anfds [fd].emask = 0; + fd_change (EV_A_ fd, EV__IOFDSET | EV_ANFD_REIFY); + } +} + +/* used to prepare libev internal fd's */ +/* this is not fork-safe */ +inline_speed void +fd_intern (int fd) +{ +#ifdef _WIN32 + unsigned long arg = 1; + ioctlsocket (EV_FD_TO_WIN32_HANDLE (fd), FIONBIO, &arg); +#else + fcntl (fd, F_SETFD, FD_CLOEXEC); + fcntl (fd, F_SETFL, O_NONBLOCK); +#endif +} + +/*****************************************************************************/ + +/* + * the heap functions want a real array index. array index 0 is guaranteed to not + * be in-use at any time. the first heap entry is at array [HEAP0]. DHEAP gives + * the branching factor of the d-tree. + */ + +/* + * at the moment we allow libev the luxury of two heaps, + * a small-code-size 2-heap one and a ~1.5kb larger 4-heap + * which is more cache-efficient. + * the difference is about 5% with 50000+ watchers. + */ +#if EV_USE_4HEAP + +#define DHEAP 4 +#define HEAP0 (DHEAP - 1) /* index of first element in heap */ +#define HPARENT(k) ((((k) - HEAP0 - 1) / DHEAP) + HEAP0) +#define UPHEAP_DONE(p,k) ((p) == (k)) + +/* away from the root */ +inline_speed void +downheap (ANHE *heap, int N, int k) +{ + ANHE he = heap [k]; + ANHE *E = heap + N + HEAP0; + + for (;;) + { + ev_tstamp minat; + ANHE *minpos; + ANHE *pos = heap + DHEAP * (k - HEAP0) + HEAP0 + 1; + + /* find minimum child */ + if (expect_true (pos + DHEAP - 1 < E)) + { + /* fast path */ (minpos = pos + 0), (minat = ANHE_at (*minpos)); + if ( ANHE_at (pos [1]) < minat) (minpos = pos + 1), (minat = ANHE_at (*minpos)); + if ( ANHE_at (pos [2]) < minat) (minpos = pos + 2), (minat = ANHE_at (*minpos)); + if ( ANHE_at (pos [3]) < minat) (minpos = pos + 3), (minat = ANHE_at (*minpos)); + } + else if (pos < E) + { + /* slow path */ (minpos = pos + 0), (minat = ANHE_at (*minpos)); + if (pos + 1 < E && ANHE_at (pos [1]) < minat) (minpos = pos + 1), (minat = ANHE_at (*minpos)); + if (pos + 2 < E && ANHE_at (pos [2]) < minat) (minpos = pos + 2), (minat = ANHE_at (*minpos)); + if (pos + 3 < E && ANHE_at (pos [3]) < minat) (minpos = pos + 3), (minat = ANHE_at (*minpos)); + } + else + break; + + if (ANHE_at (he) <= minat) + break; + + heap [k] = *minpos; + ev_active (ANHE_w (*minpos)) = k; + + k = minpos - heap; + } + + heap [k] = he; + ev_active (ANHE_w (he)) = k; +} + +#else /* 4HEAP */ + +#define HEAP0 1 +#define HPARENT(k) ((k) >> 1) +#define UPHEAP_DONE(p,k) (!(p)) + +/* away from the root */ +inline_speed void +downheap (ANHE *heap, int N, int k) +{ + ANHE he = heap [k]; + + for (;;) + { + int c = k << 1; + + if (c >= N + HEAP0) + break; + + c += c + 1 < N + HEAP0 && ANHE_at (heap [c]) > ANHE_at (heap [c + 1]) + ? 1 : 0; + + if (ANHE_at (he) <= ANHE_at (heap [c])) + break; + + heap [k] = heap [c]; + ev_active (ANHE_w (heap [k])) = k; + + k = c; + } + + heap [k] = he; + ev_active (ANHE_w (he)) = k; +} +#endif + +/* towards the root */ +inline_speed void +upheap (ANHE *heap, int k) +{ + ANHE he = heap [k]; + + for (;;) + { + int p = HPARENT (k); + + if (UPHEAP_DONE (p, k) || ANHE_at (heap [p]) <= ANHE_at (he)) + break; + + heap [k] = heap [p]; + ev_active (ANHE_w (heap [k])) = k; + k = p; + } + + heap [k] = he; + ev_active (ANHE_w (he)) = k; +} + +/* move an element suitably so it is in a correct place */ +inline_size void +adjustheap (ANHE *heap, int N, int k) +{ + if (k > HEAP0 && ANHE_at (heap [k]) <= ANHE_at (heap [HPARENT (k)])) + upheap (heap, k); + else + downheap (heap, N, k); +} + +/* rebuild the heap: this function is used only once and executed rarely */ +inline_size void +reheap (ANHE *heap, int N) +{ + int i; + + /* we don't use floyds algorithm, upheap is simpler and is more cache-efficient */ + /* also, this is easy to implement and correct for both 2-heaps and 4-heaps */ + for (i = 0; i < N; ++i) + upheap (heap, i + HEAP0); +} + +/*****************************************************************************/ + +/* associate signal watchers to a signal signal */ +typedef struct +{ + EV_ATOMIC_T pending; +#if EV_MULTIPLICITY + EV_P; +#endif + WL head; +} ANSIG; + +static ANSIG signals [EV_NSIG]; + +/*****************************************************************************/ + +#if EV_SIGNAL_ENABLE || EV_ASYNC_ENABLE + +static void noinline ecb_cold +evpipe_init (EV_P) +{ + if (!ev_is_active (&pipe_w)) + { + int fds [2]; + +# if EV_USE_EVENTFD + fds [0] = -1; + fds [1] = eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC); + if (fds [1] < 0 && errno == EINVAL) + fds [1] = eventfd (0, 0); + + if (fds [1] < 0) +# endif + { + while (pipe (fds)) + ev_syserr ("(libev) error creating signal/async pipe"); + + fd_intern (fds [0]); + } + + evpipe [0] = fds [0]; + + if (evpipe [1] < 0) + evpipe [1] = fds [1]; /* first call, set write fd */ + else + { + /* on subsequent calls, do not change evpipe [1] */ + /* so that evpipe_write can always rely on its value. */ + /* this branch does not do anything sensible on windows, */ + /* so must not be executed on windows */ + + dup2 (fds [1], evpipe [1]); + close (fds [1]); + } + + fd_intern (evpipe [1]); + + ev_io_set (&pipe_w, evpipe [0] < 0 ? evpipe [1] : evpipe [0], EV_READ); + ev_io_start (EV_A_ &pipe_w); + ev_unref (EV_A); /* watcher should not keep loop alive */ + } +} + +inline_speed void +evpipe_write (EV_P_ EV_ATOMIC_T *flag) +{ + ECB_MEMORY_FENCE; /* push out the write before this function was called, acquire flag */ + + if (expect_true (*flag)) + return; + + *flag = 1; + ECB_MEMORY_FENCE_RELEASE; /* make sure flag is visible before the wakeup */ + + pipe_write_skipped = 1; + + ECB_MEMORY_FENCE; /* make sure pipe_write_skipped is visible before we check pipe_write_wanted */ + + if (pipe_write_wanted) + { + int old_errno, rc; + + pipe_write_skipped = 0; + ECB_MEMORY_FENCE_RELEASE; + + old_errno = errno; /* save errno because write will clobber it */ + +#if EV_USE_EVENTFD + if (evpipe [0] < 0) + { + uint64_t counter = 1; + do { + rc = write (evpipe [1], &counter, sizeof (uint64_t)); + } while (errno == EINTR && rc < 0); + } + else +#endif + { +#ifdef _WIN32 + WSABUF buf; + DWORD sent; + buf.buf = &buf; + buf.len = 1; + WSASend (EV_FD_TO_WIN32_HANDLE (evpipe [1]), &buf, 1, &sent, 0, 0, 0); +#else + do { + rc = write (evpipe [1], &(evpipe [1]), 1); + } while (errno == EINTR && rc < 0); +#endif + } + + errno = old_errno; + } +} + +/* called whenever the libev signal pipe */ +/* got some events (signal, async) */ +static void +pipecb (EV_P_ ev_io *iow, int revents) +{ + int i; + + if (revents & EV_READ) + { +#if EV_USE_EVENTFD + if (evpipe [0] < 0) + { + uint64_t counter; + read (evpipe [1], &counter, sizeof (uint64_t)); + } + else +#endif + { + char dummy[4]; +#ifdef _WIN32 + WSABUF buf; + DWORD recvd; + DWORD flags = 0; + buf.buf = dummy; + buf.len = sizeof (dummy); + WSARecv (EV_FD_TO_WIN32_HANDLE (evpipe [0]), &buf, 1, &recvd, &flags, 0, 0); +#else + read (evpipe [0], &dummy, sizeof (dummy)); +#endif + } + } + + pipe_write_skipped = 0; + + ECB_MEMORY_FENCE; /* push out skipped, acquire flags */ + +#if EV_SIGNAL_ENABLE + if (sig_pending) + { + sig_pending = 0; + + ECB_MEMORY_FENCE; + + for (i = EV_NSIG - 1; i--; ) + if (expect_false (signals [i].pending)) + ev_feed_signal_event (EV_A_ i + 1); + } +#endif + +#if EV_ASYNC_ENABLE + if (async_pending) + { + async_pending = 0; + + ECB_MEMORY_FENCE; + + for (i = asynccnt; i--; ) + if (asyncs [i]->sent) + { + asyncs [i]->sent = 0; + ECB_MEMORY_FENCE_RELEASE; + ev_feed_event (EV_A_ asyncs [i], EV_ASYNC); + } + } +#endif +} + +/*****************************************************************************/ + +void +ev_feed_signal (int signum) EV_THROW +{ +#if EV_MULTIPLICITY + EV_P; + ECB_MEMORY_FENCE_ACQUIRE; + EV_A = signals [signum - 1].loop; + + if (!EV_A) + return; +#endif + + signals [signum - 1].pending = 1; + evpipe_write (EV_A_ &sig_pending); +} + +static void +ev_sighandler (int signum) +{ +#ifdef _WIN32 + signal (signum, ev_sighandler); +#endif + + ev_feed_signal (signum); +} + +void noinline +ev_feed_signal_event (EV_P_ int signum) EV_THROW +{ + WL w; + + if (expect_false (signum <= 0 || signum >= EV_NSIG)) + return; + + --signum; + +#if EV_MULTIPLICITY + /* it is permissible to try to feed a signal to the wrong loop */ + /* or, likely more useful, feeding a signal nobody is waiting for */ + + if (expect_false (signals [signum].loop != EV_A)) + return; +#endif + + signals [signum].pending = 0; + ECB_MEMORY_FENCE_RELEASE; + + for (w = signals [signum].head; w; w = w->next) + ev_feed_event (EV_A_ (W)w, EV_SIGNAL); +} + +#if EV_USE_SIGNALFD +static void +sigfdcb (EV_P_ ev_io *iow, int revents) +{ + struct signalfd_siginfo si[2], *sip; /* these structs are big */ + + for (;;) + { + ssize_t res = read (sigfd, si, sizeof (si)); + + /* not ISO-C, as res might be -1, but works with SuS */ + for (sip = si; (char *)sip < (char *)si + res; ++sip) + ev_feed_signal_event (EV_A_ sip->ssi_signo); + + if (res < (ssize_t)sizeof (si)) + break; + } +} +#endif + +#endif + +/*****************************************************************************/ + +#if EV_CHILD_ENABLE +static WL childs [EV_PID_HASHSIZE]; + +static ev_signal childev; + +#ifndef WIFCONTINUED +# define WIFCONTINUED(status) 0 +#endif + +/* handle a single child status event */ +inline_speed void +child_reap (EV_P_ int chain, int pid, int status) +{ + ev_child *w; + int traced = WIFSTOPPED (status) || WIFCONTINUED (status); + + for (w = (ev_child *)childs [chain & ((EV_PID_HASHSIZE) - 1)]; w; w = (ev_child *)((WL)w)->next) + { + if ((w->pid == pid || !w->pid) + && (!traced || (w->flags & 1))) + { + ev_set_priority (w, EV_MAXPRI); /* need to do it *now*, this *must* be the same prio as the signal watcher itself */ + w->rpid = pid; + w->rstatus = status; + ev_feed_event (EV_A_ (W)w, EV_CHILD); + } + } +} + +#ifndef WCONTINUED +# define WCONTINUED 0 +#endif + +/* called on sigchld etc., calls waitpid */ +static void +childcb (EV_P_ ev_signal *sw, int revents) +{ + int pid, status; + + /* some systems define WCONTINUED but then fail to support it (linux 2.4) */ + if (0 >= (pid = waitpid (-1, &status, WNOHANG | WUNTRACED | WCONTINUED))) + if (!WCONTINUED + || errno != EINVAL + || 0 >= (pid = waitpid (-1, &status, WNOHANG | WUNTRACED))) + return; + + /* make sure we are called again until all children have been reaped */ + /* we need to do it this way so that the callback gets called before we continue */ + ev_feed_event (EV_A_ (W)sw, EV_SIGNAL); + + child_reap (EV_A_ pid, pid, status); + if ((EV_PID_HASHSIZE) > 1) + child_reap (EV_A_ 0, pid, status); /* this might trigger a watcher twice, but feed_event catches that */ +} + +#endif + +/*****************************************************************************/ + +#if EV_USE_IOCP +# include "ev_iocp.c" +#endif +#if EV_USE_PORT +# include "ev_port.c" +#endif +#if EV_USE_KQUEUE +# include "ev_kqueue.c" +#endif +#if EV_USE_EPOLL +# include "ev_epoll.c" +#endif +#if EV_USE_POLL +# include "ev_poll.c" +#endif +#if EV_USE_SELECT +# include "ev_select.c" +#endif + +int ecb_cold +ev_version_major (void) EV_THROW +{ + return EV_VERSION_MAJOR; +} + +int ecb_cold +ev_version_minor (void) EV_THROW +{ + return EV_VERSION_MINOR; +} + +/* return true if we are running with elevated privileges and should ignore env variables */ +int inline_size ecb_cold +enable_secure (void) +{ +#ifdef _WIN32 + return 0; +#else + return getuid () != geteuid () + || getgid () != getegid (); +#endif +} + +unsigned int ecb_cold +ev_supported_backends (void) EV_THROW +{ + unsigned int flags = 0; + + if (EV_USE_PORT ) flags |= EVBACKEND_PORT; + if (EV_USE_KQUEUE) flags |= EVBACKEND_KQUEUE; + if (EV_USE_EPOLL ) flags |= EVBACKEND_EPOLL; + if (EV_USE_POLL ) flags |= EVBACKEND_POLL; + if (EV_USE_SELECT) flags |= EVBACKEND_SELECT; + + return flags; +} + +unsigned int ecb_cold +ev_recommended_backends (void) EV_THROW +{ + unsigned int flags = ev_supported_backends (); + +#ifndef __NetBSD__ + /* kqueue is borked on everything but netbsd apparently */ + /* it usually doesn't work correctly on anything but sockets and pipes */ + flags &= ~EVBACKEND_KQUEUE; +#endif +#ifdef __APPLE__ + /* only select works correctly on that "unix-certified" platform */ + flags &= ~EVBACKEND_KQUEUE; /* horribly broken, even for sockets */ + flags &= ~EVBACKEND_POLL; /* poll is based on kqueue from 10.5 onwards */ +#endif +#ifdef __FreeBSD__ + flags &= ~EVBACKEND_POLL; /* poll return value is unusable (http://forums.freebsd.org/archive/index.php/t-10270.html) */ +#endif + + return flags; +} + +unsigned int ecb_cold +ev_embeddable_backends (void) EV_THROW +{ + int flags = EVBACKEND_EPOLL | EVBACKEND_KQUEUE | EVBACKEND_PORT; + + /* epoll embeddability broken on all linux versions up to at least 2.6.23 */ + if (ev_linux_version () < 0x020620) /* disable it on linux < 2.6.32 */ + flags &= ~EVBACKEND_EPOLL; + + return flags; +} + +unsigned int +ev_backend (EV_P) EV_THROW +{ + return backend; +} + +#if EV_FEATURE_API +unsigned int +ev_iteration (EV_P) EV_THROW +{ + return loop_count; +} + +unsigned int +ev_depth (EV_P) EV_THROW +{ + return loop_depth; +} + +void +ev_set_io_collect_interval (EV_P_ ev_tstamp interval) EV_THROW +{ + io_blocktime = interval; +} + +void +ev_set_timeout_collect_interval (EV_P_ ev_tstamp interval) EV_THROW +{ + timeout_blocktime = interval; +} + +void +ev_set_userdata (EV_P_ void *data) EV_THROW +{ + userdata = data; +} + +void * +ev_userdata (EV_P) EV_THROW +{ + return userdata; +} + +void +ev_set_invoke_pending_cb (EV_P_ ev_loop_callback invoke_pending_cb) EV_THROW +{ + invoke_cb = invoke_pending_cb; +} + +void +ev_set_loop_release_cb (EV_P_ void (*release)(EV_P) EV_THROW, void (*acquire)(EV_P) EV_THROW) EV_THROW +{ + release_cb = release; + acquire_cb = acquire; +} +#endif + +/* initialise a loop structure, must be zero-initialised */ +static void noinline ecb_cold +loop_init (EV_P_ unsigned int flags) EV_THROW +{ + if (!backend) + { + origflags = flags; + +#if EV_USE_REALTIME + if (!have_realtime) + { + struct timespec ts; + + if (!clock_gettime (CLOCK_REALTIME, &ts)) + have_realtime = 1; + } +#endif + +#if EV_USE_MONOTONIC + if (!have_monotonic) + { + struct timespec ts; + + if (!clock_gettime (CLOCK_MONOTONIC, &ts)) + have_monotonic = 1; + } +#endif + + /* pid check not overridable via env */ +#ifndef _WIN32 + if (flags & EVFLAG_FORKCHECK) + curpid = getpid (); +#endif + + if (!(flags & EVFLAG_NOENV) + && !enable_secure () + && getenv ("LIBEV_FLAGS")) + flags = atoi (getenv ("LIBEV_FLAGS")); + + ev_rt_now = ev_time (); + mn_now = get_clock (); + now_floor = mn_now; + rtmn_diff = ev_rt_now - mn_now; +#if EV_FEATURE_API + invoke_cb = ev_invoke_pending; +#endif + + io_blocktime = 0.; + timeout_blocktime = 0.; + backend = 0; + backend_fd = -1; + sig_pending = 0; +#if EV_ASYNC_ENABLE + async_pending = 0; +#endif + pipe_write_skipped = 0; + pipe_write_wanted = 0; + evpipe [0] = -1; + evpipe [1] = -1; +#if EV_USE_INOTIFY + fs_fd = flags & EVFLAG_NOINOTIFY ? -1 : -2; +#endif +#if EV_USE_SIGNALFD + sigfd = flags & EVFLAG_SIGNALFD ? -2 : -1; +#endif + + if (!(flags & EVBACKEND_MASK)) + flags |= ev_recommended_backends (); + +#if EV_USE_IOCP + if (!backend && (flags & EVBACKEND_IOCP )) backend = iocp_init (EV_A_ flags); +#endif +#if EV_USE_PORT + if (!backend && (flags & EVBACKEND_PORT )) backend = port_init (EV_A_ flags); +#endif +#if EV_USE_KQUEUE + if (!backend && (flags & EVBACKEND_KQUEUE)) backend = kqueue_init (EV_A_ flags); +#endif +#if EV_USE_EPOLL + if (!backend && (flags & EVBACKEND_EPOLL )) backend = epoll_init (EV_A_ flags); +#endif +#if EV_USE_POLL + if (!backend && (flags & EVBACKEND_POLL )) backend = poll_init (EV_A_ flags); +#endif +#if EV_USE_SELECT + if (!backend && (flags & EVBACKEND_SELECT)) backend = select_init (EV_A_ flags); +#endif + + ev_prepare_init (&pending_w, pendingcb); + +#if EV_SIGNAL_ENABLE || EV_ASYNC_ENABLE + ev_init (&pipe_w, pipecb); + ev_set_priority (&pipe_w, EV_MAXPRI); +#endif + } +} + +/* free up a loop structure */ +void ecb_cold +ev_loop_destroy (EV_P) +{ + int i; + +#if EV_MULTIPLICITY + /* mimic free (0) */ + if (!EV_A) + return; +#endif + +#if EV_CLEANUP_ENABLE + /* queue cleanup watchers (and execute them) */ + if (expect_false (cleanupcnt)) + { + queue_events (EV_A_ (W *)cleanups, cleanupcnt, EV_CLEANUP); + EV_INVOKE_PENDING; + } +#endif + +#if EV_CHILD_ENABLE + if (ev_is_default_loop (EV_A) && ev_is_active (&childev)) + { + ev_ref (EV_A); /* child watcher */ + ev_signal_stop (EV_A_ &childev); + } +#endif + + if (ev_is_active (&pipe_w)) + { + /*ev_ref (EV_A);*/ + /*ev_io_stop (EV_A_ &pipe_w);*/ + + if (evpipe [0] >= 0) EV_WIN32_CLOSE_FD (evpipe [0]); + if (evpipe [1] >= 0) EV_WIN32_CLOSE_FD (evpipe [1]); + } + +#if EV_USE_SIGNALFD + if (ev_is_active (&sigfd_w)) + close (sigfd); +#endif + +#if EV_USE_INOTIFY + if (fs_fd >= 0) + close (fs_fd); +#endif + + if (backend_fd >= 0) + close (backend_fd); + +#if EV_USE_IOCP + if (backend == EVBACKEND_IOCP ) iocp_destroy (EV_A); +#endif +#if EV_USE_PORT + if (backend == EVBACKEND_PORT ) port_destroy (EV_A); +#endif +#if EV_USE_KQUEUE + if (backend == EVBACKEND_KQUEUE) kqueue_destroy (EV_A); +#endif +#if EV_USE_EPOLL + if (backend == EVBACKEND_EPOLL ) epoll_destroy (EV_A); +#endif +#if EV_USE_POLL + if (backend == EVBACKEND_POLL ) poll_destroy (EV_A); +#endif +#if EV_USE_SELECT + if (backend == EVBACKEND_SELECT) select_destroy (EV_A); +#endif + + for (i = NUMPRI; i--; ) + { + array_free (pending, [i]); +#if EV_IDLE_ENABLE + array_free (idle, [i]); +#endif + } + + ev_free (anfds); anfds = 0; anfdmax = 0; + + /* have to use the microsoft-never-gets-it-right macro */ + array_free (rfeed, EMPTY); + array_free (fdchange, EMPTY); + array_free (timer, EMPTY); +#if EV_PERIODIC_ENABLE + array_free (periodic, EMPTY); +#endif +#if EV_FORK_ENABLE + array_free (fork, EMPTY); +#endif +#if EV_CLEANUP_ENABLE + array_free (cleanup, EMPTY); +#endif + array_free (prepare, EMPTY); + array_free (check, EMPTY); +#if EV_ASYNC_ENABLE + array_free (async, EMPTY); +#endif + + backend = 0; + +#if EV_MULTIPLICITY + if (ev_is_default_loop (EV_A)) +#endif + ev_default_loop_ptr = 0; +#if EV_MULTIPLICITY + else + ev_free (EV_A); +#endif +} + +#if EV_USE_INOTIFY +inline_size void infy_fork (EV_P); +#endif + +inline_size void +loop_fork (EV_P) +{ +#if EV_USE_PORT + if (backend == EVBACKEND_PORT ) port_fork (EV_A); +#endif +#if EV_USE_KQUEUE + if (backend == EVBACKEND_KQUEUE) kqueue_fork (EV_A); +#endif +#if EV_USE_EPOLL + if (backend == EVBACKEND_EPOLL ) epoll_fork (EV_A); +#endif +#if EV_USE_INOTIFY + infy_fork (EV_A); +#endif + +#if EV_SIGNAL_ENABLE || EV_ASYNC_ENABLE + if (ev_is_active (&pipe_w)) + { + /* pipe_write_wanted must be false now, so modifying fd vars should be safe */ + + ev_ref (EV_A); + ev_io_stop (EV_A_ &pipe_w); + + if (evpipe [0] >= 0) + EV_WIN32_CLOSE_FD (evpipe [0]); + + evpipe_init (EV_A); + /* iterate over everything, in case we missed something before */ + ev_feed_event (EV_A_ &pipe_w, EV_CUSTOM); + } +#endif + + postfork = 0; +} + +#if EV_MULTIPLICITY + +struct ev_loop * ecb_cold +ev_loop_new (unsigned int flags) EV_THROW +{ + EV_P = (struct ev_loop *)ev_malloc (sizeof (struct ev_loop)); + + memset (EV_A, 0, sizeof (struct ev_loop)); + loop_init (EV_A_ flags); + + if (ev_backend (EV_A)) + return EV_A; + + ev_free (EV_A); + return 0; +} + +#endif /* multiplicity */ + +#if EV_VERIFY +static void noinline ecb_cold +verify_watcher (EV_P_ W w) +{ + assert (("libev: watcher has invalid priority", ABSPRI (w) >= 0 && ABSPRI (w) < NUMPRI)); + + if (w->pending) + assert (("libev: pending watcher not on pending queue", pendings [ABSPRI (w)][w->pending - 1].w == w)); +} + +static void noinline ecb_cold +verify_heap (EV_P_ ANHE *heap, int N) +{ + int i; + + for (i = HEAP0; i < N + HEAP0; ++i) + { + assert (("libev: active index mismatch in heap", ev_active (ANHE_w (heap [i])) == i)); + assert (("libev: heap condition violated", i == HEAP0 || ANHE_at (heap [HPARENT (i)]) <= ANHE_at (heap [i]))); + assert (("libev: heap at cache mismatch", ANHE_at (heap [i]) == ev_at (ANHE_w (heap [i])))); + + verify_watcher (EV_A_ (W)ANHE_w (heap [i])); + } +} + +static void noinline ecb_cold +array_verify (EV_P_ W *ws, int cnt) +{ + while (cnt--) + { + assert (("libev: active index mismatch", ev_active (ws [cnt]) == cnt + 1)); + verify_watcher (EV_A_ ws [cnt]); + } +} +#endif + +#if EV_FEATURE_API +void ecb_cold +ev_verify (EV_P) EV_THROW +{ +#if EV_VERIFY + int i; + WL w, w2; + + assert (activecnt >= -1); + + assert (fdchangemax >= fdchangecnt); + for (i = 0; i < fdchangecnt; ++i) + assert (("libev: negative fd in fdchanges", fdchanges [i] >= 0)); + + assert (anfdmax >= 0); + for (i = 0; i < anfdmax; ++i) + { + int j = 0; + + for (w = w2 = anfds [i].head; w; w = w->next) + { + verify_watcher (EV_A_ (W)w); + + if (j++ & 1) + { + assert (("libev: io watcher list contains a loop", w != w2)); + w2 = w2->next; + } + + assert (("libev: inactive fd watcher on anfd list", ev_active (w) == 1)); + assert (("libev: fd mismatch between watcher and anfd", ((ev_io *)w)->fd == i)); + } + } + + assert (timermax >= timercnt); + verify_heap (EV_A_ timers, timercnt); + +#if EV_PERIODIC_ENABLE + assert (periodicmax >= periodiccnt); + verify_heap (EV_A_ periodics, periodiccnt); +#endif + + for (i = NUMPRI; i--; ) + { + assert (pendingmax [i] >= pendingcnt [i]); +#if EV_IDLE_ENABLE + assert (idleall >= 0); + assert (idlemax [i] >= idlecnt [i]); + array_verify (EV_A_ (W *)idles [i], idlecnt [i]); +#endif + } + +#if EV_FORK_ENABLE + assert (forkmax >= forkcnt); + array_verify (EV_A_ (W *)forks, forkcnt); +#endif + +#if EV_CLEANUP_ENABLE + assert (cleanupmax >= cleanupcnt); + array_verify (EV_A_ (W *)cleanups, cleanupcnt); +#endif + +#if EV_ASYNC_ENABLE + assert (asyncmax >= asynccnt); + array_verify (EV_A_ (W *)asyncs, asynccnt); +#endif + +#if EV_PREPARE_ENABLE + assert (preparemax >= preparecnt); + array_verify (EV_A_ (W *)prepares, preparecnt); +#endif + +#if EV_CHECK_ENABLE + assert (checkmax >= checkcnt); + array_verify (EV_A_ (W *)checks, checkcnt); +#endif + +# if 0 +#if EV_CHILD_ENABLE + for (w = (ev_child *)childs [chain & ((EV_PID_HASHSIZE) - 1)]; w; w = (ev_child *)((WL)w)->next) + for (signum = EV_NSIG - 1; signum--; ) if (signals [signum].pending) +#endif +# endif +#endif +} +#endif + +#if EV_MULTIPLICITY +struct ev_loop * ecb_cold +#else +int +#endif +ev_default_loop (unsigned int flags) EV_THROW +{ + if (!ev_default_loop_ptr) + { +#if EV_MULTIPLICITY + EV_P = ev_default_loop_ptr = &default_loop_struct; +#else + ev_default_loop_ptr = 1; +#endif + + loop_init (EV_A_ flags); + + if (ev_backend (EV_A)) + { +#if EV_CHILD_ENABLE + ev_signal_init (&childev, childcb, SIGCHLD); + ev_set_priority (&childev, EV_MAXPRI); + ev_signal_start (EV_A_ &childev); + ev_unref (EV_A); /* child watcher should not keep loop alive */ +#endif + } + else + ev_default_loop_ptr = 0; + } + + return ev_default_loop_ptr; +} + +void +ev_loop_fork (EV_P) EV_THROW +{ + postfork = 1; +} + +/*****************************************************************************/ + +void +ev_invoke (EV_P_ void *w, int revents) +{ + EV_CB_INVOKE ((W)w, revents); +} + +unsigned int +ev_pending_count (EV_P) EV_THROW +{ + int pri; + unsigned int count = 0; + + for (pri = NUMPRI; pri--; ) + count += pendingcnt [pri]; + + return count; +} + +void noinline +ev_invoke_pending (EV_P) +{ + pendingpri = NUMPRI; + + while (pendingpri) /* pendingpri possibly gets modified in the inner loop */ + { + --pendingpri; + + while (pendingcnt [pendingpri]) + { + ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri]; + + p->w->pending = 0; + EV_CB_INVOKE (p->w, p->events); + EV_FREQUENT_CHECK; + } + } +} + +#if EV_IDLE_ENABLE +/* make idle watchers pending. this handles the "call-idle */ +/* only when higher priorities are idle" logic */ +inline_size void +idle_reify (EV_P) +{ + if (expect_false (idleall)) + { + int pri; + + for (pri = NUMPRI; pri--; ) + { + if (pendingcnt [pri]) + break; + + if (idlecnt [pri]) + { + queue_events (EV_A_ (W *)idles [pri], idlecnt [pri], EV_IDLE); + break; + } + } + } +} +#endif + +/* make timers pending */ +inline_size void +timers_reify (EV_P) +{ + EV_FREQUENT_CHECK; + + if (timercnt && ANHE_at (timers [HEAP0]) < mn_now) + { + do + { + ev_timer *w = (ev_timer *)ANHE_w (timers [HEAP0]); + + /*assert (("libev: inactive timer on timer heap detected", ev_is_active (w)));*/ + + /* first reschedule or stop timer */ + if (w->repeat) + { + ev_at (w) += w->repeat; + if (ev_at (w) < mn_now) + ev_at (w) = mn_now; + + assert (("libev: negative ev_timer repeat value found while processing timers", w->repeat > 0.)); + + ANHE_at_cache (timers [HEAP0]); + downheap (timers, timercnt, HEAP0); + } + else + ev_timer_stop (EV_A_ w); /* nonrepeating: stop timer */ + + EV_FREQUENT_CHECK; + feed_reverse (EV_A_ (W)w); + } + while (timercnt && ANHE_at (timers [HEAP0]) < mn_now); + + feed_reverse_done (EV_A_ EV_TIMER); + } +} + +#if EV_PERIODIC_ENABLE + +static void noinline +periodic_recalc (EV_P_ ev_periodic *w) +{ + ev_tstamp interval = w->interval > MIN_INTERVAL ? w->interval : MIN_INTERVAL; + ev_tstamp at = w->offset + interval * ev_floor ((ev_rt_now - w->offset) / interval); + + /* the above almost always errs on the low side */ + while (at <= ev_rt_now) + { + ev_tstamp nat = at + w->interval; + + /* when resolution fails us, we use ev_rt_now */ + if (expect_false (nat == at)) + { + at = ev_rt_now; + break; + } + + at = nat; + } + + ev_at (w) = at; +} + +/* make periodics pending */ +inline_size void +periodics_reify (EV_P) +{ + EV_FREQUENT_CHECK; + + while (periodiccnt && ANHE_at (periodics [HEAP0]) < ev_rt_now) + { + do + { + ev_periodic *w = (ev_periodic *)ANHE_w (periodics [HEAP0]); + + /*assert (("libev: inactive timer on periodic heap detected", ev_is_active (w)));*/ + + /* first reschedule or stop timer */ + if (w->reschedule_cb) + { + ev_at (w) = w->reschedule_cb (w, ev_rt_now); + + assert (("libev: ev_periodic reschedule callback returned time in the past", ev_at (w) >= ev_rt_now)); + + ANHE_at_cache (periodics [HEAP0]); + downheap (periodics, periodiccnt, HEAP0); + } + else if (w->interval) + { + periodic_recalc (EV_A_ w); + ANHE_at_cache (periodics [HEAP0]); + downheap (periodics, periodiccnt, HEAP0); + } + else + ev_periodic_stop (EV_A_ w); /* nonrepeating: stop timer */ + + EV_FREQUENT_CHECK; + feed_reverse (EV_A_ (W)w); + } + while (periodiccnt && ANHE_at (periodics [HEAP0]) < ev_rt_now); + + feed_reverse_done (EV_A_ EV_PERIODIC); + } +} + +/* simply recalculate all periodics */ +/* TODO: maybe ensure that at least one event happens when jumping forward? */ +static void noinline ecb_cold +periodics_reschedule (EV_P) +{ + int i; + + /* adjust periodics after time jump */ + for (i = HEAP0; i < periodiccnt + HEAP0; ++i) + { + ev_periodic *w = (ev_periodic *)ANHE_w (periodics [i]); + + if (w->reschedule_cb) + ev_at (w) = w->reschedule_cb (w, ev_rt_now); + else if (w->interval) + periodic_recalc (EV_A_ w); + + ANHE_at_cache (periodics [i]); + } + + reheap (periodics, periodiccnt); +} +#endif + +/* adjust all timers by a given offset */ +static void noinline ecb_cold +timers_reschedule (EV_P_ ev_tstamp adjust) +{ + int i; + + for (i = 0; i < timercnt; ++i) + { + ANHE *he = timers + i + HEAP0; + ANHE_w (*he)->at += adjust; + ANHE_at_cache (*he); + } +} + +/* fetch new monotonic and realtime times from the kernel */ +/* also detect if there was a timejump, and act accordingly */ +inline_speed void +time_update (EV_P_ ev_tstamp max_block) +{ +#if EV_USE_MONOTONIC + if (expect_true (have_monotonic)) + { + int i; + ev_tstamp odiff = rtmn_diff; + + mn_now = get_clock (); + + /* only fetch the realtime clock every 0.5*MIN_TIMEJUMP seconds */ + /* interpolate in the meantime */ + if (expect_true (mn_now - now_floor < MIN_TIMEJUMP * .5)) + { + ev_rt_now = rtmn_diff + mn_now; + return; + } + + now_floor = mn_now; + ev_rt_now = ev_time (); + + /* loop a few times, before making important decisions. + * on the choice of "4": one iteration isn't enough, + * in case we get preempted during the calls to + * ev_time and get_clock. a second call is almost guaranteed + * to succeed in that case, though. and looping a few more times + * doesn't hurt either as we only do this on time-jumps or + * in the unlikely event of having been preempted here. + */ + for (i = 4; --i; ) + { + ev_tstamp diff; + rtmn_diff = ev_rt_now - mn_now; + + diff = odiff - rtmn_diff; + + if (expect_true ((diff < 0. ? -diff : diff) < MIN_TIMEJUMP)) + return; /* all is well */ + + ev_rt_now = ev_time (); + mn_now = get_clock (); + now_floor = mn_now; + } + + /* no timer adjustment, as the monotonic clock doesn't jump */ + /* timers_reschedule (EV_A_ rtmn_diff - odiff) */ +# if EV_PERIODIC_ENABLE + periodics_reschedule (EV_A); +# endif + } + else +#endif + { + ev_rt_now = ev_time (); + + if (expect_false (mn_now > ev_rt_now || ev_rt_now > mn_now + max_block + MIN_TIMEJUMP)) + { + /* adjust timers. this is easy, as the offset is the same for all of them */ + timers_reschedule (EV_A_ ev_rt_now - mn_now); +#if EV_PERIODIC_ENABLE + periodics_reschedule (EV_A); +#endif + } + + mn_now = ev_rt_now; + } +} + +int +ev_run (EV_P_ int flags) +{ +#if EV_FEATURE_API + ++loop_depth; +#endif + + assert (("libev: ev_loop recursion during release detected", loop_done != EVBREAK_RECURSE)); + + loop_done = EVBREAK_CANCEL; + + EV_INVOKE_PENDING; /* in case we recurse, ensure ordering stays nice and clean */ + + do + { +#if EV_VERIFY >= 2 + ev_verify (EV_A); +#endif + +#ifndef _WIN32 + if (expect_false (curpid)) /* penalise the forking check even more */ + if (expect_false (getpid () != curpid)) + { + curpid = getpid (); + postfork = 1; + } +#endif + +#if EV_FORK_ENABLE + /* we might have forked, so queue fork handlers */ + if (expect_false (postfork)) + if (forkcnt) + { + queue_events (EV_A_ (W *)forks, forkcnt, EV_FORK); + EV_INVOKE_PENDING; + } +#endif + +#if EV_PREPARE_ENABLE + /* queue prepare watchers (and execute them) */ + if (expect_false (preparecnt)) + { + queue_events (EV_A_ (W *)prepares, preparecnt, EV_PREPARE); + EV_INVOKE_PENDING; + } +#endif + + if (expect_false (loop_done)) + break; + + /* we might have forked, so reify kernel state if necessary */ + if (expect_false (postfork)) + loop_fork (EV_A); + + /* update fd-related kernel structures */ + fd_reify (EV_A); + + /* calculate blocking time */ + { + ev_tstamp waittime = 0.; + ev_tstamp sleeptime = 0.; + + /* remember old timestamp for io_blocktime calculation */ + ev_tstamp prev_mn_now = mn_now; + + /* update time to cancel out callback processing overhead */ + time_update (EV_A_ 1e100); + + /* from now on, we want a pipe-wake-up */ + pipe_write_wanted = 1; + + ECB_MEMORY_FENCE; /* make sure pipe_write_wanted is visible before we check for potential skips */ + + if (expect_true (!(flags & EVRUN_NOWAIT || idleall || !activecnt || pipe_write_skipped))) + { + waittime = MAX_BLOCKTIME; + + if (timercnt) + { + ev_tstamp to = ANHE_at (timers [HEAP0]) - mn_now; + if (waittime > to) waittime = to; + } + +#if EV_PERIODIC_ENABLE + if (periodiccnt) + { + ev_tstamp to = ANHE_at (periodics [HEAP0]) - ev_rt_now; + if (waittime > to) waittime = to; + } +#endif + + /* don't let timeouts decrease the waittime below timeout_blocktime */ + if (expect_false (waittime < timeout_blocktime)) + waittime = timeout_blocktime; + + /* at this point, we NEED to wait, so we have to ensure */ + /* to pass a minimum nonzero value to the backend */ + if (expect_false (waittime < backend_mintime)) + waittime = backend_mintime; + + /* extra check because io_blocktime is commonly 0 */ + if (expect_false (io_blocktime)) + { + sleeptime = io_blocktime - (mn_now - prev_mn_now); + + if (sleeptime > waittime - backend_mintime) + sleeptime = waittime - backend_mintime; + + if (expect_true (sleeptime > 0.)) + { + ev_sleep (sleeptime); + waittime -= sleeptime; + } + } + } + +#if EV_FEATURE_API + ++loop_count; +#endif + assert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */ + backend_poll (EV_A_ waittime); + assert ((loop_done = EVBREAK_CANCEL, 1)); /* assert for side effect */ + + pipe_write_wanted = 0; /* just an optimisation, no fence needed */ + + ECB_MEMORY_FENCE_ACQUIRE; + if (pipe_write_skipped) + { + assert (("libev: pipe_w not active, but pipe not written", ev_is_active (&pipe_w))); + ev_feed_event (EV_A_ &pipe_w, EV_CUSTOM); + } + + + /* update ev_rt_now, do magic */ + time_update (EV_A_ waittime + sleeptime); + } + + /* queue pending timers and reschedule them */ + timers_reify (EV_A); /* relative timers called last */ +#if EV_PERIODIC_ENABLE + periodics_reify (EV_A); /* absolute timers called first */ +#endif + +#if EV_IDLE_ENABLE + /* queue idle watchers unless other events are pending */ + idle_reify (EV_A); +#endif + +#if EV_CHECK_ENABLE + /* queue check watchers, to be executed first */ + if (expect_false (checkcnt)) + queue_events (EV_A_ (W *)checks, checkcnt, EV_CHECK); +#endif + + EV_INVOKE_PENDING; + } + while (expect_true ( + activecnt + && !loop_done + && !(flags & (EVRUN_ONCE | EVRUN_NOWAIT)) + )); + + if (loop_done == EVBREAK_ONE) + loop_done = EVBREAK_CANCEL; + +#if EV_FEATURE_API + --loop_depth; +#endif + + return activecnt; +} + +void +ev_break (EV_P_ int how) EV_THROW +{ + loop_done = how; +} + +void +ev_ref (EV_P) EV_THROW +{ + ++activecnt; +} + +void +ev_unref (EV_P) EV_THROW +{ + --activecnt; +} + +void +ev_now_update (EV_P) EV_THROW +{ + time_update (EV_A_ 1e100); +} + +void +ev_suspend (EV_P) EV_THROW +{ + ev_now_update (EV_A); +} + +void +ev_resume (EV_P) EV_THROW +{ + ev_tstamp mn_prev = mn_now; + + ev_now_update (EV_A); + timers_reschedule (EV_A_ mn_now - mn_prev); +#if EV_PERIODIC_ENABLE + /* TODO: really do this? */ + periodics_reschedule (EV_A); +#endif +} + +/*****************************************************************************/ +/* singly-linked list management, used when the expected list length is short */ + +inline_size void +wlist_add (WL *head, WL elem) +{ + elem->next = *head; + *head = elem; +} + +inline_size void +wlist_del (WL *head, WL elem) +{ + while (*head) + { + if (expect_true (*head == elem)) + { + *head = elem->next; + break; + } + + head = &(*head)->next; + } +} + +/* internal, faster, version of ev_clear_pending */ +inline_speed void +clear_pending (EV_P_ W w) +{ + if (w->pending) + { + pendings [ABSPRI (w)][w->pending - 1].w = (W)&pending_w; + w->pending = 0; + } +} + +int +ev_clear_pending (EV_P_ void *w) EV_THROW +{ + W w_ = (W)w; + int pending = w_->pending; + + if (expect_true (pending)) + { + ANPENDING *p = pendings [ABSPRI (w_)] + pending - 1; + p->w = (W)&pending_w; + w_->pending = 0; + return p->events; + } + else + return 0; +} + +inline_size void +pri_adjust (EV_P_ W w) +{ + int pri = ev_priority (w); + pri = pri < EV_MINPRI ? EV_MINPRI : pri; + pri = pri > EV_MAXPRI ? EV_MAXPRI : pri; + ev_set_priority (w, pri); +} + +inline_speed void +ev_start (EV_P_ W w, int active) +{ + pri_adjust (EV_A_ w); + w->active = active; + ev_ref (EV_A); +} + +inline_size void +ev_stop (EV_P_ W w) +{ + ev_unref (EV_A); + w->active = 0; +} + +/*****************************************************************************/ + +void noinline +ev_io_start (EV_P_ ev_io *w) EV_THROW +{ + int fd = w->fd; + + if (expect_false (ev_is_active (w))) + return; + + assert (("libev: ev_io_start called with negative fd", fd >= 0)); + assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE)))); + + EV_FREQUENT_CHECK; + + ev_start (EV_A_ (W)w, 1); + array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero); + wlist_add (&anfds[fd].head, (WL)w); + + /* common bug, apparently */ + assert (("libev: ev_io_start called with corrupted watcher", ((WL)w)->next != (WL)w)); + + fd_change (EV_A_ fd, (w->events & EV__IOFDSET) | EV_ANFD_REIFY); + w->events &= ~EV__IOFDSET; + + EV_FREQUENT_CHECK; +} + +void noinline +ev_io_stop (EV_P_ ev_io *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + assert (("libev: ev_io_stop called with illegal fd (must stay constant after start!)", w->fd >= 0 && w->fd < anfdmax)); + + EV_FREQUENT_CHECK; + + wlist_del (&anfds[w->fd].head, (WL)w); + ev_stop (EV_A_ (W)w); + + fd_change (EV_A_ w->fd, EV_ANFD_REIFY); + + EV_FREQUENT_CHECK; +} + +void noinline +ev_timer_start (EV_P_ ev_timer *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + ev_at (w) += mn_now; + + assert (("libev: ev_timer_start called with negative timer repeat value", w->repeat >= 0.)); + + EV_FREQUENT_CHECK; + + ++timercnt; + ev_start (EV_A_ (W)w, timercnt + HEAP0 - 1); + array_needsize (ANHE, timers, timermax, ev_active (w) + 1, EMPTY2); + ANHE_w (timers [ev_active (w)]) = (WT)w; + ANHE_at_cache (timers [ev_active (w)]); + upheap (timers, ev_active (w)); + + EV_FREQUENT_CHECK; + + /*assert (("libev: internal timer heap corruption", timers [ev_active (w)] == (WT)w));*/ +} + +void noinline +ev_timer_stop (EV_P_ ev_timer *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + { + int active = ev_active (w); + + assert (("libev: internal timer heap corruption", ANHE_w (timers [active]) == (WT)w)); + + --timercnt; + + if (expect_true (active < timercnt + HEAP0)) + { + timers [active] = timers [timercnt + HEAP0]; + adjustheap (timers, timercnt, active); + } + } + + ev_at (w) -= mn_now; + + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} + +void noinline +ev_timer_again (EV_P_ ev_timer *w) EV_THROW +{ + EV_FREQUENT_CHECK; + + clear_pending (EV_A_ (W)w); + + if (ev_is_active (w)) + { + if (w->repeat) + { + ev_at (w) = mn_now + w->repeat; + ANHE_at_cache (timers [ev_active (w)]); + adjustheap (timers, timercnt, ev_active (w)); + } + else + ev_timer_stop (EV_A_ w); + } + else if (w->repeat) + { + ev_at (w) = w->repeat; + ev_timer_start (EV_A_ w); + } + + EV_FREQUENT_CHECK; +} + +ev_tstamp +ev_timer_remaining (EV_P_ ev_timer *w) EV_THROW +{ + return ev_at (w) - (ev_is_active (w) ? mn_now : 0.); +} + +#if EV_PERIODIC_ENABLE +void noinline +ev_periodic_start (EV_P_ ev_periodic *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + if (w->reschedule_cb) + ev_at (w) = w->reschedule_cb (w, ev_rt_now); + else if (w->interval) + { + assert (("libev: ev_periodic_start called with negative interval value", w->interval >= 0.)); + periodic_recalc (EV_A_ w); + } + else + ev_at (w) = w->offset; + + EV_FREQUENT_CHECK; + + ++periodiccnt; + ev_start (EV_A_ (W)w, periodiccnt + HEAP0 - 1); + array_needsize (ANHE, periodics, periodicmax, ev_active (w) + 1, EMPTY2); + ANHE_w (periodics [ev_active (w)]) = (WT)w; + ANHE_at_cache (periodics [ev_active (w)]); + upheap (periodics, ev_active (w)); + + EV_FREQUENT_CHECK; + + /*assert (("libev: internal periodic heap corruption", ANHE_w (periodics [ev_active (w)]) == (WT)w));*/ +} + +void noinline +ev_periodic_stop (EV_P_ ev_periodic *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + { + int active = ev_active (w); + + assert (("libev: internal periodic heap corruption", ANHE_w (periodics [active]) == (WT)w)); + + --periodiccnt; + + if (expect_true (active < periodiccnt + HEAP0)) + { + periodics [active] = periodics [periodiccnt + HEAP0]; + adjustheap (periodics, periodiccnt, active); + } + } + + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} + +void noinline +ev_periodic_again (EV_P_ ev_periodic *w) EV_THROW +{ + /* TODO: use adjustheap and recalculation */ + ev_periodic_stop (EV_A_ w); + ev_periodic_start (EV_A_ w); +} +#endif + +#ifndef SA_RESTART +# define SA_RESTART 0 +#endif + +#if EV_SIGNAL_ENABLE + +void noinline +ev_signal_start (EV_P_ ev_signal *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + assert (("libev: ev_signal_start called with illegal signal number", w->signum > 0 && w->signum < EV_NSIG)); + +#if EV_MULTIPLICITY + assert (("libev: a signal must not be attached to two different loops", + !signals [w->signum - 1].loop || signals [w->signum - 1].loop == loop)); + + signals [w->signum - 1].loop = EV_A; + ECB_MEMORY_FENCE_RELEASE; +#endif + + EV_FREQUENT_CHECK; + +#if EV_USE_SIGNALFD + if (sigfd == -2) + { + sigfd = signalfd (-1, &sigfd_set, SFD_NONBLOCK | SFD_CLOEXEC); + if (sigfd < 0 && errno == EINVAL) + sigfd = signalfd (-1, &sigfd_set, 0); /* retry without flags */ + + if (sigfd >= 0) + { + fd_intern (sigfd); /* doing it twice will not hurt */ + + sigemptyset (&sigfd_set); + + ev_io_init (&sigfd_w, sigfdcb, sigfd, EV_READ); + ev_set_priority (&sigfd_w, EV_MAXPRI); + ev_io_start (EV_A_ &sigfd_w); + ev_unref (EV_A); /* signalfd watcher should not keep loop alive */ + } + } + + if (sigfd >= 0) + { + /* TODO: check .head */ + sigaddset (&sigfd_set, w->signum); + sigprocmask (SIG_BLOCK, &sigfd_set, 0); + + signalfd (sigfd, &sigfd_set, 0); + } +#endif + + ev_start (EV_A_ (W)w, 1); + wlist_add (&signals [w->signum - 1].head, (WL)w); + + if (!((WL)w)->next) +# if EV_USE_SIGNALFD + if (sigfd < 0) /*TODO*/ +# endif + { +# ifdef _WIN32 + evpipe_init (EV_A); + + signal (w->signum, ev_sighandler); +# else + struct sigaction sa; + + evpipe_init (EV_A); + + sa.sa_handler = ev_sighandler; + sigfillset (&sa.sa_mask); + sa.sa_flags = SA_RESTART; /* if restarting works we save one iteration */ + sigaction (w->signum, &sa, 0); + + if (origflags & EVFLAG_NOSIGMASK) + { + sigemptyset (&sa.sa_mask); + sigaddset (&sa.sa_mask, w->signum); + sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); + } +#endif + } + + EV_FREQUENT_CHECK; +} + +void noinline +ev_signal_stop (EV_P_ ev_signal *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + wlist_del (&signals [w->signum - 1].head, (WL)w); + ev_stop (EV_A_ (W)w); + + if (!signals [w->signum - 1].head) + { +#if EV_MULTIPLICITY + signals [w->signum - 1].loop = 0; /* unattach from signal */ +#endif +#if EV_USE_SIGNALFD + if (sigfd >= 0) + { + sigset_t ss; + + sigemptyset (&ss); + sigaddset (&ss, w->signum); + sigdelset (&sigfd_set, w->signum); + + signalfd (sigfd, &sigfd_set, 0); + sigprocmask (SIG_UNBLOCK, &ss, 0); + } + else +#endif + signal (w->signum, SIG_DFL); + } + + EV_FREQUENT_CHECK; +} + +#endif + +#if EV_CHILD_ENABLE + +void +ev_child_start (EV_P_ ev_child *w) EV_THROW +{ +#if EV_MULTIPLICITY + assert (("libev: child watchers are only supported in the default loop", loop == ev_default_loop_ptr)); +#endif + if (expect_false (ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + ev_start (EV_A_ (W)w, 1); + wlist_add (&childs [w->pid & ((EV_PID_HASHSIZE) - 1)], (WL)w); + + EV_FREQUENT_CHECK; +} + +void +ev_child_stop (EV_P_ ev_child *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + wlist_del (&childs [w->pid & ((EV_PID_HASHSIZE) - 1)], (WL)w); + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} + +#endif + +#if EV_STAT_ENABLE + +# ifdef _WIN32 +# undef lstat +# define lstat(a,b) _stati64 (a,b) +# endif + +#define DEF_STAT_INTERVAL 5.0074891 +#define NFS_STAT_INTERVAL 30.1074891 /* for filesystems potentially failing inotify */ +#define MIN_STAT_INTERVAL 0.1074891 + +static void noinline stat_timer_cb (EV_P_ ev_timer *w_, int revents); + +#if EV_USE_INOTIFY + +/* the * 2 is to allow for alignment padding, which for some reason is >> 8 */ +# define EV_INOTIFY_BUFSIZE (sizeof (struct inotify_event) * 2 + NAME_MAX) + +static void noinline +infy_add (EV_P_ ev_stat *w) +{ + w->wd = inotify_add_watch (fs_fd, w->path, + IN_ATTRIB | IN_DELETE_SELF | IN_MOVE_SELF | IN_MODIFY + | IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO + | IN_DONT_FOLLOW | IN_MASK_ADD); + + if (w->wd >= 0) + { + struct statfs sfs; + + /* now local changes will be tracked by inotify, but remote changes won't */ + /* unless the filesystem is known to be local, we therefore still poll */ + /* also do poll on <2.6.25, but with normal frequency */ + + if (!fs_2625) + w->timer.repeat = w->interval ? w->interval : DEF_STAT_INTERVAL; + else if (!statfs (w->path, &sfs) + && (sfs.f_type == 0x1373 /* devfs */ + || sfs.f_type == 0x4006 /* fat */ + || sfs.f_type == 0x4d44 /* msdos */ + || sfs.f_type == 0xEF53 /* ext2/3 */ + || sfs.f_type == 0x72b6 /* jffs2 */ + || sfs.f_type == 0x858458f6 /* ramfs */ + || sfs.f_type == 0x5346544e /* ntfs */ + || sfs.f_type == 0x3153464a /* jfs */ + || sfs.f_type == 0x9123683e /* btrfs */ + || sfs.f_type == 0x52654973 /* reiser3 */ + || sfs.f_type == 0x01021994 /* tmpfs */ + || sfs.f_type == 0x58465342 /* xfs */)) + w->timer.repeat = 0.; /* filesystem is local, kernel new enough */ + else + w->timer.repeat = w->interval ? w->interval : NFS_STAT_INTERVAL; /* remote, use reduced frequency */ + } + else + { + /* can't use inotify, continue to stat */ + w->timer.repeat = w->interval ? w->interval : DEF_STAT_INTERVAL; + + /* if path is not there, monitor some parent directory for speedup hints */ + /* note that exceeding the hardcoded path limit is not a correctness issue, */ + /* but an efficiency issue only */ + if ((errno == ENOENT || errno == EACCES) && strlen (w->path) < 4096) + { + char path [4096]; + strcpy (path, w->path); + + do + { + int mask = IN_MASK_ADD | IN_DELETE_SELF | IN_MOVE_SELF + | (errno == EACCES ? IN_ATTRIB : IN_CREATE | IN_MOVED_TO); + + char *pend = strrchr (path, '/'); + + if (!pend || pend == path) + break; + + *pend = 0; + w->wd = inotify_add_watch (fs_fd, path, mask); + } + while (w->wd < 0 && (errno == ENOENT || errno == EACCES)); + } + } + + if (w->wd >= 0) + wlist_add (&fs_hash [w->wd & ((EV_INOTIFY_HASHSIZE) - 1)].head, (WL)w); + + /* now re-arm timer, if required */ + if (ev_is_active (&w->timer)) ev_ref (EV_A); + ev_timer_again (EV_A_ &w->timer); + if (ev_is_active (&w->timer)) ev_unref (EV_A); +} + +static void noinline +infy_del (EV_P_ ev_stat *w) +{ + int slot; + int wd = w->wd; + + if (wd < 0) + return; + + w->wd = -2; + slot = wd & ((EV_INOTIFY_HASHSIZE) - 1); + wlist_del (&fs_hash [slot].head, (WL)w); + + /* remove this watcher, if others are watching it, they will rearm */ + inotify_rm_watch (fs_fd, wd); +} + +static void noinline +infy_wd (EV_P_ int slot, int wd, struct inotify_event *ev) +{ + if (slot < 0) + /* overflow, need to check for all hash slots */ + for (slot = 0; slot < (EV_INOTIFY_HASHSIZE); ++slot) + infy_wd (EV_A_ slot, wd, ev); + else + { + WL w_; + + for (w_ = fs_hash [slot & ((EV_INOTIFY_HASHSIZE) - 1)].head; w_; ) + { + ev_stat *w = (ev_stat *)w_; + w_ = w_->next; /* lets us remove this watcher and all before it */ + + if (w->wd == wd || wd == -1) + { + if (ev->mask & (IN_IGNORED | IN_UNMOUNT | IN_DELETE_SELF)) + { + wlist_del (&fs_hash [slot & ((EV_INOTIFY_HASHSIZE) - 1)].head, (WL)w); + w->wd = -1; + infy_add (EV_A_ w); /* re-add, no matter what */ + } + + stat_timer_cb (EV_A_ &w->timer, 0); + } + } + } +} + +static void +infy_cb (EV_P_ ev_io *w, int revents) +{ + char buf [EV_INOTIFY_BUFSIZE]; + int ofs; + int len = read (fs_fd, buf, sizeof (buf)); + + for (ofs = 0; ofs < len; ) + { + struct inotify_event *ev = (struct inotify_event *)(buf + ofs); + infy_wd (EV_A_ ev->wd, ev->wd, ev); + ofs += sizeof (struct inotify_event) + ev->len; + } +} + +inline_size void ecb_cold +ev_check_2625 (EV_P) +{ + /* kernels < 2.6.25 are borked + * http://www.ussg.indiana.edu/hypermail/linux/kernel/0711.3/1208.html + */ + if (ev_linux_version () < 0x020619) + return; + + fs_2625 = 1; +} + +inline_size int +infy_newfd (void) +{ +#if defined IN_CLOEXEC && defined IN_NONBLOCK + int fd = inotify_init1 (IN_CLOEXEC | IN_NONBLOCK); + if (fd >= 0) + return fd; +#endif + return inotify_init (); +} + +inline_size void +infy_init (EV_P) +{ + if (fs_fd != -2) + return; + + fs_fd = -1; + + ev_check_2625 (EV_A); + + fs_fd = infy_newfd (); + + if (fs_fd >= 0) + { + fd_intern (fs_fd); + ev_io_init (&fs_w, infy_cb, fs_fd, EV_READ); + ev_set_priority (&fs_w, EV_MAXPRI); + ev_io_start (EV_A_ &fs_w); + ev_unref (EV_A); + } +} + +inline_size void +infy_fork (EV_P) +{ + int slot; + + if (fs_fd < 0) + return; + + ev_ref (EV_A); + ev_io_stop (EV_A_ &fs_w); + close (fs_fd); + fs_fd = infy_newfd (); + + if (fs_fd >= 0) + { + fd_intern (fs_fd); + ev_io_set (&fs_w, fs_fd, EV_READ); + ev_io_start (EV_A_ &fs_w); + ev_unref (EV_A); + } + + for (slot = 0; slot < (EV_INOTIFY_HASHSIZE); ++slot) + { + WL w_ = fs_hash [slot].head; + fs_hash [slot].head = 0; + + while (w_) + { + ev_stat *w = (ev_stat *)w_; + w_ = w_->next; /* lets us add this watcher */ + + w->wd = -1; + + if (fs_fd >= 0) + infy_add (EV_A_ w); /* re-add, no matter what */ + else + { + w->timer.repeat = w->interval ? w->interval : DEF_STAT_INTERVAL; + if (ev_is_active (&w->timer)) ev_ref (EV_A); + ev_timer_again (EV_A_ &w->timer); + if (ev_is_active (&w->timer)) ev_unref (EV_A); + } + } + } +} + +#endif + +#ifdef _WIN32 +# define EV_LSTAT(p,b) _stati64 (p, b) +#else +# define EV_LSTAT(p,b) lstat (p, b) +#endif + +void +ev_stat_stat (EV_P_ ev_stat *w) EV_THROW +{ + if (lstat (w->path, &w->attr) < 0) + w->attr.st_nlink = 0; + else if (!w->attr.st_nlink) + w->attr.st_nlink = 1; +} + +static void noinline +stat_timer_cb (EV_P_ ev_timer *w_, int revents) +{ + ev_stat *w = (ev_stat *)(((char *)w_) - offsetof (ev_stat, timer)); + + ev_statdata prev = w->attr; + ev_stat_stat (EV_A_ w); + + /* memcmp doesn't work on netbsd, they.... do stuff to their struct stat */ + if ( + prev.st_dev != w->attr.st_dev + || prev.st_ino != w->attr.st_ino + || prev.st_mode != w->attr.st_mode + || prev.st_nlink != w->attr.st_nlink + || prev.st_uid != w->attr.st_uid + || prev.st_gid != w->attr.st_gid + || prev.st_rdev != w->attr.st_rdev + || prev.st_size != w->attr.st_size + || prev.st_atime != w->attr.st_atime + || prev.st_mtime != w->attr.st_mtime + || prev.st_ctime != w->attr.st_ctime + ) { + /* we only update w->prev on actual differences */ + /* in case we test more often than invoke the callback, */ + /* to ensure that prev is always different to attr */ + w->prev = prev; + + #if EV_USE_INOTIFY + if (fs_fd >= 0) + { + infy_del (EV_A_ w); + infy_add (EV_A_ w); + ev_stat_stat (EV_A_ w); /* avoid race... */ + } + #endif + + ev_feed_event (EV_A_ w, EV_STAT); + } +} + +void +ev_stat_start (EV_P_ ev_stat *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + ev_stat_stat (EV_A_ w); + + if (w->interval < MIN_STAT_INTERVAL && w->interval) + w->interval = MIN_STAT_INTERVAL; + + ev_timer_init (&w->timer, stat_timer_cb, 0., w->interval ? w->interval : DEF_STAT_INTERVAL); + ev_set_priority (&w->timer, ev_priority (w)); + +#if EV_USE_INOTIFY + infy_init (EV_A); + + if (fs_fd >= 0) + infy_add (EV_A_ w); + else +#endif + { + ev_timer_again (EV_A_ &w->timer); + ev_unref (EV_A); + } + + ev_start (EV_A_ (W)w, 1); + + EV_FREQUENT_CHECK; +} + +void +ev_stat_stop (EV_P_ ev_stat *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + +#if EV_USE_INOTIFY + infy_del (EV_A_ w); +#endif + + if (ev_is_active (&w->timer)) + { + ev_ref (EV_A); + ev_timer_stop (EV_A_ &w->timer); + } + + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} +#endif + +#if EV_IDLE_ENABLE +void +ev_idle_start (EV_P_ ev_idle *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + pri_adjust (EV_A_ (W)w); + + EV_FREQUENT_CHECK; + + { + int active = ++idlecnt [ABSPRI (w)]; + + ++idleall; + ev_start (EV_A_ (W)w, active); + + array_needsize (ev_idle *, idles [ABSPRI (w)], idlemax [ABSPRI (w)], active, EMPTY2); + idles [ABSPRI (w)][active - 1] = w; + } + + EV_FREQUENT_CHECK; +} + +void +ev_idle_stop (EV_P_ ev_idle *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + { + int active = ev_active (w); + + idles [ABSPRI (w)][active - 1] = idles [ABSPRI (w)][--idlecnt [ABSPRI (w)]]; + ev_active (idles [ABSPRI (w)][active - 1]) = active; + + ev_stop (EV_A_ (W)w); + --idleall; + } + + EV_FREQUENT_CHECK; +} +#endif + +#if EV_PREPARE_ENABLE +void +ev_prepare_start (EV_P_ ev_prepare *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + ev_start (EV_A_ (W)w, ++preparecnt); + array_needsize (ev_prepare *, prepares, preparemax, preparecnt, EMPTY2); + prepares [preparecnt - 1] = w; + + EV_FREQUENT_CHECK; +} + +void +ev_prepare_stop (EV_P_ ev_prepare *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + { + int active = ev_active (w); + + prepares [active - 1] = prepares [--preparecnt]; + ev_active (prepares [active - 1]) = active; + } + + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} +#endif + +#if EV_CHECK_ENABLE +void +ev_check_start (EV_P_ ev_check *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + ev_start (EV_A_ (W)w, ++checkcnt); + array_needsize (ev_check *, checks, checkmax, checkcnt, EMPTY2); + checks [checkcnt - 1] = w; + + EV_FREQUENT_CHECK; +} + +void +ev_check_stop (EV_P_ ev_check *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + { + int active = ev_active (w); + + checks [active - 1] = checks [--checkcnt]; + ev_active (checks [active - 1]) = active; + } + + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} +#endif + +#if EV_EMBED_ENABLE +void noinline +ev_embed_sweep (EV_P_ ev_embed *w) EV_THROW +{ + ev_run (w->other, EVRUN_NOWAIT); +} + +static void +embed_io_cb (EV_P_ ev_io *io, int revents) +{ + ev_embed *w = (ev_embed *)(((char *)io) - offsetof (ev_embed, io)); + + if (ev_cb (w)) + ev_feed_event (EV_A_ (W)w, EV_EMBED); + else + ev_run (w->other, EVRUN_NOWAIT); +} + +static void +embed_prepare_cb (EV_P_ ev_prepare *prepare, int revents) +{ + ev_embed *w = (ev_embed *)(((char *)prepare) - offsetof (ev_embed, prepare)); + + { + EV_P = w->other; + + while (fdchangecnt) + { + fd_reify (EV_A); + ev_run (EV_A_ EVRUN_NOWAIT); + } + } +} + +static void +embed_fork_cb (EV_P_ ev_fork *fork_w, int revents) +{ + ev_embed *w = (ev_embed *)(((char *)fork_w) - offsetof (ev_embed, fork)); + + ev_embed_stop (EV_A_ w); + + { + EV_P = w->other; + + ev_loop_fork (EV_A); + ev_run (EV_A_ EVRUN_NOWAIT); + } + + ev_embed_start (EV_A_ w); +} + +#if 0 +static void +embed_idle_cb (EV_P_ ev_idle *idle, int revents) +{ + ev_idle_stop (EV_A_ idle); +} +#endif + +void +ev_embed_start (EV_P_ ev_embed *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + { + EV_P = w->other; + assert (("libev: loop to be embedded is not embeddable", backend & ev_embeddable_backends ())); + ev_io_init (&w->io, embed_io_cb, backend_fd, EV_READ); + } + + EV_FREQUENT_CHECK; + + ev_set_priority (&w->io, ev_priority (w)); + ev_io_start (EV_A_ &w->io); + + ev_prepare_init (&w->prepare, embed_prepare_cb); + ev_set_priority (&w->prepare, EV_MINPRI); + ev_prepare_start (EV_A_ &w->prepare); + + ev_fork_init (&w->fork, embed_fork_cb); + ev_fork_start (EV_A_ &w->fork); + + /*ev_idle_init (&w->idle, e,bed_idle_cb);*/ + + ev_start (EV_A_ (W)w, 1); + + EV_FREQUENT_CHECK; +} + +void +ev_embed_stop (EV_P_ ev_embed *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + ev_io_stop (EV_A_ &w->io); + ev_prepare_stop (EV_A_ &w->prepare); + ev_fork_stop (EV_A_ &w->fork); + + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} +#endif + +#if EV_FORK_ENABLE +void +ev_fork_start (EV_P_ ev_fork *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + ev_start (EV_A_ (W)w, ++forkcnt); + array_needsize (ev_fork *, forks, forkmax, forkcnt, EMPTY2); + forks [forkcnt - 1] = w; + + EV_FREQUENT_CHECK; +} + +void +ev_fork_stop (EV_P_ ev_fork *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + { + int active = ev_active (w); + + forks [active - 1] = forks [--forkcnt]; + ev_active (forks [active - 1]) = active; + } + + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} +#endif + +#if EV_CLEANUP_ENABLE +void +ev_cleanup_start (EV_P_ ev_cleanup *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + ev_start (EV_A_ (W)w, ++cleanupcnt); + array_needsize (ev_cleanup *, cleanups, cleanupmax, cleanupcnt, EMPTY2); + cleanups [cleanupcnt - 1] = w; + + /* cleanup watchers should never keep a refcount on the loop */ + ev_unref (EV_A); + EV_FREQUENT_CHECK; +} + +void +ev_cleanup_stop (EV_P_ ev_cleanup *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + ev_ref (EV_A); + + { + int active = ev_active (w); + + cleanups [active - 1] = cleanups [--cleanupcnt]; + ev_active (cleanups [active - 1]) = active; + } + + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} +#endif + +#if EV_ASYNC_ENABLE +void +ev_async_start (EV_P_ ev_async *w) EV_THROW +{ + if (expect_false (ev_is_active (w))) + return; + + w->sent = 0; + + evpipe_init (EV_A); + + EV_FREQUENT_CHECK; + + ev_start (EV_A_ (W)w, ++asynccnt); + array_needsize (ev_async *, asyncs, asyncmax, asynccnt, EMPTY2); + asyncs [asynccnt - 1] = w; + + EV_FREQUENT_CHECK; +} + +void +ev_async_stop (EV_P_ ev_async *w) EV_THROW +{ + clear_pending (EV_A_ (W)w); + if (expect_false (!ev_is_active (w))) + return; + + EV_FREQUENT_CHECK; + + { + int active = ev_active (w); + + asyncs [active - 1] = asyncs [--asynccnt]; + ev_active (asyncs [active - 1]) = active; + } + + ev_stop (EV_A_ (W)w); + + EV_FREQUENT_CHECK; +} + +void +ev_async_send (EV_P_ ev_async *w) EV_THROW +{ + w->sent = 1; + evpipe_write (EV_A_ &async_pending); +} +#endif + +/*****************************************************************************/ + +struct ev_once +{ + ev_io io; + ev_timer to; + void (*cb)(int revents, void *arg); + void *arg; +}; + +static void +once_cb (EV_P_ struct ev_once *once, int revents) +{ + void (*cb)(int revents, void *arg) = once->cb; + void *arg = once->arg; + + ev_io_stop (EV_A_ &once->io); + ev_timer_stop (EV_A_ &once->to); + ev_free (once); + + cb (revents, arg); +} + +static void +once_cb_io (EV_P_ ev_io *w, int revents) +{ + struct ev_once *once = (struct ev_once *)(((char *)w) - offsetof (struct ev_once, io)); + + once_cb (EV_A_ once, revents | ev_clear_pending (EV_A_ &once->to)); +} + +static void +once_cb_to (EV_P_ ev_timer *w, int revents) +{ + struct ev_once *once = (struct ev_once *)(((char *)w) - offsetof (struct ev_once, to)); + + once_cb (EV_A_ once, revents | ev_clear_pending (EV_A_ &once->io)); +} + +void +ev_once (EV_P_ int fd, int events, ev_tstamp timeout, void (*cb)(int revents, void *arg), void *arg) EV_THROW +{ + struct ev_once *once = (struct ev_once *)ev_malloc (sizeof (struct ev_once)); + + if (expect_false (!once)) + { + cb (EV_ERROR | EV_READ | EV_WRITE | EV_TIMER, arg); + return; + } + + once->cb = cb; + once->arg = arg; + + ev_init (&once->io, once_cb_io); + if (fd >= 0) + { + ev_io_set (&once->io, fd, events); + ev_io_start (EV_A_ &once->io); + } + + ev_init (&once->to, once_cb_to); + if (timeout >= 0.) + { + ev_timer_set (&once->to, timeout, 0.); + ev_timer_start (EV_A_ &once->to); + } +} + +/*****************************************************************************/ + +#if EV_WALK_ENABLE +void ecb_cold +ev_walk (EV_P_ int types, void (*cb)(EV_P_ int type, void *w)) EV_THROW +{ + int i, j; + ev_watcher_list *wl, *wn; + + if (types & (EV_IO | EV_EMBED)) + for (i = 0; i < anfdmax; ++i) + for (wl = anfds [i].head; wl; ) + { + wn = wl->next; + +#if EV_EMBED_ENABLE + if (ev_cb ((ev_io *)wl) == embed_io_cb) + { + if (types & EV_EMBED) + cb (EV_A_ EV_EMBED, ((char *)wl) - offsetof (struct ev_embed, io)); + } + else +#endif +#if EV_USE_INOTIFY + if (ev_cb ((ev_io *)wl) == infy_cb) + ; + else +#endif + if ((ev_io *)wl != &pipe_w) + if (types & EV_IO) + cb (EV_A_ EV_IO, wl); + + wl = wn; + } + + if (types & (EV_TIMER | EV_STAT)) + for (i = timercnt + HEAP0; i-- > HEAP0; ) +#if EV_STAT_ENABLE + /*TODO: timer is not always active*/ + if (ev_cb ((ev_timer *)ANHE_w (timers [i])) == stat_timer_cb) + { + if (types & EV_STAT) + cb (EV_A_ EV_STAT, ((char *)ANHE_w (timers [i])) - offsetof (struct ev_stat, timer)); + } + else +#endif + if (types & EV_TIMER) + cb (EV_A_ EV_TIMER, ANHE_w (timers [i])); + +#if EV_PERIODIC_ENABLE + if (types & EV_PERIODIC) + for (i = periodiccnt + HEAP0; i-- > HEAP0; ) + cb (EV_A_ EV_PERIODIC, ANHE_w (periodics [i])); +#endif + +#if EV_IDLE_ENABLE + if (types & EV_IDLE) + for (j = NUMPRI; j--; ) + for (i = idlecnt [j]; i--; ) + cb (EV_A_ EV_IDLE, idles [j][i]); +#endif + +#if EV_FORK_ENABLE + if (types & EV_FORK) + for (i = forkcnt; i--; ) + if (ev_cb (forks [i]) != embed_fork_cb) + cb (EV_A_ EV_FORK, forks [i]); +#endif + +#if EV_ASYNC_ENABLE + if (types & EV_ASYNC) + for (i = asynccnt; i--; ) + cb (EV_A_ EV_ASYNC, asyncs [i]); +#endif + +#if EV_PREPARE_ENABLE + if (types & EV_PREPARE) + for (i = preparecnt; i--; ) +# if EV_EMBED_ENABLE + if (ev_cb (prepares [i]) != embed_prepare_cb) +# endif + cb (EV_A_ EV_PREPARE, prepares [i]); +#endif + +#if EV_CHECK_ENABLE + if (types & EV_CHECK) + for (i = checkcnt; i--; ) + cb (EV_A_ EV_CHECK, checks [i]); +#endif + +#if EV_SIGNAL_ENABLE + if (types & EV_SIGNAL) + for (i = 0; i < EV_NSIG - 1; ++i) + for (wl = signals [i].head; wl; ) + { + wn = wl->next; + cb (EV_A_ EV_SIGNAL, wl); + wl = wn; + } +#endif + +#if EV_CHILD_ENABLE + if (types & EV_CHILD) + for (i = (EV_PID_HASHSIZE); i--; ) + for (wl = childs [i]; wl; ) + { + wn = wl->next; + cb (EV_A_ EV_CHILD, wl); + wl = wn; + } +#endif +/* EV_STAT 0x00001000 * stat data changed */ +/* EV_EMBED 0x00010000 * embedded event loop needs sweep */ +} +#endif + +#if EV_MULTIPLICITY + #include "ev_wrap.h" +#endif + diff --git a/framework/src/audit/src/libev/ev.h b/framework/src/audit/src/libev/ev.h new file mode 100644 index 00000000..7c6e7eb8 --- /dev/null +++ b/framework/src/audit/src/libev/ev.h @@ -0,0 +1,854 @@ +/* + * libev native API header + * + * Copyright (c) 2007,2008,2009,2010,2011,2012,2015 Marc Alexander Lehmann <libev@schmorp.de> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#ifndef EV_H_ +#define EV_H_ + +#ifdef __cplusplus +# define EV_CPP(x) x +# if __cplusplus >= 201103L +# define EV_THROW noexcept +# else +# define EV_THROW throw () +# endif +#else +# define EV_CPP(x) +# define EV_THROW +#endif + +EV_CPP(extern "C" {) + +/*****************************************************************************/ + +/* pre-4.0 compatibility */ +#ifndef EV_COMPAT3 +# define EV_COMPAT3 1 +#endif + +#ifndef EV_FEATURES +# if defined __OPTIMIZE_SIZE__ +# define EV_FEATURES 0x7c +# else +# define EV_FEATURES 0x7f +# endif +#endif + +#define EV_FEATURE_CODE ((EV_FEATURES) & 1) +#define EV_FEATURE_DATA ((EV_FEATURES) & 2) +#define EV_FEATURE_CONFIG ((EV_FEATURES) & 4) +#define EV_FEATURE_API ((EV_FEATURES) & 8) +#define EV_FEATURE_WATCHERS ((EV_FEATURES) & 16) +#define EV_FEATURE_BACKENDS ((EV_FEATURES) & 32) +#define EV_FEATURE_OS ((EV_FEATURES) & 64) + +/* these priorities are inclusive, higher priorities will be invoked earlier */ +#ifndef EV_MINPRI +# define EV_MINPRI (EV_FEATURE_CONFIG ? -2 : 0) +#endif +#ifndef EV_MAXPRI +# define EV_MAXPRI (EV_FEATURE_CONFIG ? +2 : 0) +#endif + +#ifndef EV_MULTIPLICITY +# define EV_MULTIPLICITY EV_FEATURE_CONFIG +#endif + +#ifndef EV_PERIODIC_ENABLE +# define EV_PERIODIC_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_STAT_ENABLE +# define EV_STAT_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_PREPARE_ENABLE +# define EV_PREPARE_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_CHECK_ENABLE +# define EV_CHECK_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_IDLE_ENABLE +# define EV_IDLE_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_FORK_ENABLE +# define EV_FORK_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_CLEANUP_ENABLE +# define EV_CLEANUP_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_SIGNAL_ENABLE +# define EV_SIGNAL_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_CHILD_ENABLE +# ifdef _WIN32 +# define EV_CHILD_ENABLE 0 +# else +# define EV_CHILD_ENABLE EV_FEATURE_WATCHERS +#endif +#endif + +#ifndef EV_ASYNC_ENABLE +# define EV_ASYNC_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_EMBED_ENABLE +# define EV_EMBED_ENABLE EV_FEATURE_WATCHERS +#endif + +#ifndef EV_WALK_ENABLE +# define EV_WALK_ENABLE 0 /* not yet */ +#endif + +/*****************************************************************************/ + +#if EV_CHILD_ENABLE && !EV_SIGNAL_ENABLE +# undef EV_SIGNAL_ENABLE +# define EV_SIGNAL_ENABLE 1 +#endif + +/*****************************************************************************/ + +typedef double ev_tstamp; + +#include <string.h> /* for memmove */ + +#ifndef EV_ATOMIC_T +# include <signal.h> +# define EV_ATOMIC_T sig_atomic_t volatile +#endif + +#if EV_STAT_ENABLE +# ifdef _WIN32 +# include <time.h> +# include <sys/types.h> +# endif +# include <sys/stat.h> +#endif + +/* support multiple event loops? */ +#if EV_MULTIPLICITY +struct ev_loop; +# define EV_P struct ev_loop *loop /* a loop as sole parameter in a declaration */ +# define EV_P_ EV_P, /* a loop as first of multiple parameters */ +# define EV_A loop /* a loop as sole argument to a function call */ +# define EV_A_ EV_A, /* a loop as first of multiple arguments */ +# define EV_DEFAULT_UC ev_default_loop_uc_ () /* the default loop, if initialised, as sole arg */ +# define EV_DEFAULT_UC_ EV_DEFAULT_UC, /* the default loop as first of multiple arguments */ +# define EV_DEFAULT ev_default_loop (0) /* the default loop as sole arg */ +# define EV_DEFAULT_ EV_DEFAULT, /* the default loop as first of multiple arguments */ +#else +# define EV_P void +# define EV_P_ +# define EV_A +# define EV_A_ +# define EV_DEFAULT +# define EV_DEFAULT_ +# define EV_DEFAULT_UC +# define EV_DEFAULT_UC_ +# undef EV_EMBED_ENABLE +#endif + +/* EV_INLINE is used for functions in header files */ +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L || __GNUC__ >= 3 +# define EV_INLINE static inline +#else +# define EV_INLINE static +#endif + +#ifdef EV_API_STATIC +# define EV_API_DECL static +#else +# define EV_API_DECL extern +#endif + +/* EV_PROTOTYPES can be used to switch of prototype declarations */ +#ifndef EV_PROTOTYPES +# define EV_PROTOTYPES 1 +#endif + +/*****************************************************************************/ + +#define EV_VERSION_MAJOR 4 +#define EV_VERSION_MINOR 20 + +/* eventmask, revents, events... */ +enum { + EV_UNDEF = (int)0xFFFFFFFF, /* guaranteed to be invalid */ + EV_NONE = 0x00, /* no events */ + EV_READ = 0x01, /* ev_io detected read will not block */ + EV_WRITE = 0x02, /* ev_io detected write will not block */ + EV__IOFDSET = 0x80, /* internal use only */ + EV_IO = EV_READ, /* alias for type-detection */ + EV_TIMER = 0x00000100, /* timer timed out */ +#if EV_COMPAT3 + EV_TIMEOUT = EV_TIMER, /* pre 4.0 API compatibility */ +#endif + EV_PERIODIC = 0x00000200, /* periodic timer timed out */ + EV_SIGNAL = 0x00000400, /* signal was received */ + EV_CHILD = 0x00000800, /* child/pid had status change */ + EV_STAT = 0x00001000, /* stat data changed */ + EV_IDLE = 0x00002000, /* event loop is idling */ + EV_PREPARE = 0x00004000, /* event loop about to poll */ + EV_CHECK = 0x00008000, /* event loop finished poll */ + EV_EMBED = 0x00010000, /* embedded event loop needs sweep */ + EV_FORK = 0x00020000, /* event loop resumed in child */ + EV_CLEANUP = 0x00040000, /* event loop resumed in child */ + EV_ASYNC = 0x00080000, /* async intra-loop signal */ + EV_CUSTOM = 0x01000000, /* for use by user code */ + EV_ERROR = (int)0x80000000 /* sent when an error occurs */ +}; + +/* can be used to add custom fields to all watchers, while losing binary compatibility */ +#ifndef EV_COMMON +# define EV_COMMON void *data; +#endif + +#ifndef EV_CB_DECLARE +# define EV_CB_DECLARE(type) void (*cb)(EV_P_ struct type *w, int revents); +#endif +#ifndef EV_CB_INVOKE +# define EV_CB_INVOKE(watcher,revents) (watcher)->cb (EV_A_ (watcher), (revents)) +#endif + +/* not official, do not use */ +#define EV_CB(type,name) void name (EV_P_ struct ev_ ## type *w, int revents) + +/* + * struct member types: + * private: you may look at them, but not change them, + * and they might not mean anything to you. + * ro: can be read anytime, but only changed when the watcher isn't active. + * rw: can be read and modified anytime, even when the watcher is active. + * + * some internal details that might be helpful for debugging: + * + * active is either 0, which means the watcher is not active, + * or the array index of the watcher (periodics, timers) + * or the array index + 1 (most other watchers) + * or simply 1 for watchers that aren't in some array. + * pending is either 0, in which case the watcher isn't, + * or the array index + 1 in the pendings array. + */ + +#if EV_MINPRI == EV_MAXPRI +# define EV_DECL_PRIORITY +#elif !defined (EV_DECL_PRIORITY) +# define EV_DECL_PRIORITY int priority; +#endif + +/* shared by all watchers */ +#define EV_WATCHER(type) \ + int active; /* private */ \ + int pending; /* private */ \ + EV_DECL_PRIORITY /* private */ \ + EV_COMMON /* rw */ \ + EV_CB_DECLARE (type) /* private */ + +#define EV_WATCHER_LIST(type) \ + EV_WATCHER (type) \ + struct ev_watcher_list *next; /* private */ + +#define EV_WATCHER_TIME(type) \ + EV_WATCHER (type) \ + ev_tstamp at; /* private */ + +/* base class, nothing to see here unless you subclass */ +typedef struct ev_watcher +{ + EV_WATCHER (ev_watcher) +} ev_watcher; + +/* base class, nothing to see here unless you subclass */ +typedef struct ev_watcher_list +{ + EV_WATCHER_LIST (ev_watcher_list) +} ev_watcher_list; + +/* base class, nothing to see here unless you subclass */ +typedef struct ev_watcher_time +{ + EV_WATCHER_TIME (ev_watcher_time) +} ev_watcher_time; + +/* invoked when fd is either EV_READable or EV_WRITEable */ +/* revent EV_READ, EV_WRITE */ +typedef struct ev_io +{ + EV_WATCHER_LIST (ev_io) + + int fd; /* ro */ + int events; /* ro */ +} ev_io; + +/* invoked after a specific time, repeatable (based on monotonic clock) */ +/* revent EV_TIMEOUT */ +typedef struct ev_timer +{ + EV_WATCHER_TIME (ev_timer) + + ev_tstamp repeat; /* rw */ +} ev_timer; + +/* invoked at some specific time, possibly repeating at regular intervals (based on UTC) */ +/* revent EV_PERIODIC */ +typedef struct ev_periodic +{ + EV_WATCHER_TIME (ev_periodic) + + ev_tstamp offset; /* rw */ + ev_tstamp interval; /* rw */ + ev_tstamp (*reschedule_cb)(struct ev_periodic *w, ev_tstamp now) EV_THROW; /* rw */ +} ev_periodic; + +/* invoked when the given signal has been received */ +/* revent EV_SIGNAL */ +typedef struct ev_signal +{ + EV_WATCHER_LIST (ev_signal) + + int signum; /* ro */ +} ev_signal; + +/* invoked when sigchld is received and waitpid indicates the given pid */ +/* revent EV_CHILD */ +/* does not support priorities */ +typedef struct ev_child +{ + EV_WATCHER_LIST (ev_child) + + int flags; /* private */ + int pid; /* ro */ + int rpid; /* rw, holds the received pid */ + int rstatus; /* rw, holds the exit status, use the macros from sys/wait.h */ +} ev_child; + +#if EV_STAT_ENABLE +/* st_nlink = 0 means missing file or other error */ +# ifdef _WIN32 +typedef struct _stati64 ev_statdata; +# else +typedef struct stat ev_statdata; +# endif + +/* invoked each time the stat data changes for a given path */ +/* revent EV_STAT */ +typedef struct ev_stat +{ + EV_WATCHER_LIST (ev_stat) + + ev_timer timer; /* private */ + ev_tstamp interval; /* ro */ + const char *path; /* ro */ + ev_statdata prev; /* ro */ + ev_statdata attr; /* ro */ + + int wd; /* wd for inotify, fd for kqueue */ +} ev_stat; +#endif + +#if EV_IDLE_ENABLE +/* invoked when the nothing else needs to be done, keeps the process from blocking */ +/* revent EV_IDLE */ +typedef struct ev_idle +{ + EV_WATCHER (ev_idle) +} ev_idle; +#endif + +/* invoked for each run of the mainloop, just before the blocking call */ +/* you can still change events in any way you like */ +/* revent EV_PREPARE */ +typedef struct ev_prepare +{ + EV_WATCHER (ev_prepare) +} ev_prepare; + +/* invoked for each run of the mainloop, just after the blocking call */ +/* revent EV_CHECK */ +typedef struct ev_check +{ + EV_WATCHER (ev_check) +} ev_check; + +#if EV_FORK_ENABLE +/* the callback gets invoked before check in the child process when a fork was detected */ +/* revent EV_FORK */ +typedef struct ev_fork +{ + EV_WATCHER (ev_fork) +} ev_fork; +#endif + +#if EV_CLEANUP_ENABLE +/* is invoked just before the loop gets destroyed */ +/* revent EV_CLEANUP */ +typedef struct ev_cleanup +{ + EV_WATCHER (ev_cleanup) +} ev_cleanup; +#endif + +#if EV_EMBED_ENABLE +/* used to embed an event loop inside another */ +/* the callback gets invoked when the event loop has handled events, and can be 0 */ +typedef struct ev_embed +{ + EV_WATCHER (ev_embed) + + struct ev_loop *other; /* ro */ + ev_io io; /* private */ + ev_prepare prepare; /* private */ + ev_check check; /* unused */ + ev_timer timer; /* unused */ + ev_periodic periodic; /* unused */ + ev_idle idle; /* unused */ + ev_fork fork; /* private */ +#if EV_CLEANUP_ENABLE + ev_cleanup cleanup; /* unused */ +#endif +} ev_embed; +#endif + +#if EV_ASYNC_ENABLE +/* invoked when somebody calls ev_async_send on the watcher */ +/* revent EV_ASYNC */ +typedef struct ev_async +{ + EV_WATCHER (ev_async) + + EV_ATOMIC_T sent; /* private */ +} ev_async; + +# define ev_async_pending(w) (+(w)->sent) +#endif + +/* the presence of this union forces similar struct layout */ +union ev_any_watcher +{ + struct ev_watcher w; + struct ev_watcher_list wl; + + struct ev_io io; + struct ev_timer timer; + struct ev_periodic periodic; + struct ev_signal signal; + struct ev_child child; +#if EV_STAT_ENABLE + struct ev_stat stat; +#endif +#if EV_IDLE_ENABLE + struct ev_idle idle; +#endif + struct ev_prepare prepare; + struct ev_check check; +#if EV_FORK_ENABLE + struct ev_fork fork; +#endif +#if EV_CLEANUP_ENABLE + struct ev_cleanup cleanup; +#endif +#if EV_EMBED_ENABLE + struct ev_embed embed; +#endif +#if EV_ASYNC_ENABLE + struct ev_async async; +#endif +}; + +/* flag bits for ev_default_loop and ev_loop_new */ +enum { + /* the default */ + EVFLAG_AUTO = 0x00000000U, /* not quite a mask */ + /* flag bits */ + EVFLAG_NOENV = 0x01000000U, /* do NOT consult environment */ + EVFLAG_FORKCHECK = 0x02000000U, /* check for a fork in each iteration */ + /* debugging/feature disable */ + EVFLAG_NOINOTIFY = 0x00100000U, /* do not attempt to use inotify */ +#if EV_COMPAT3 + EVFLAG_NOSIGFD = 0, /* compatibility to pre-3.9 */ +#endif + EVFLAG_SIGNALFD = 0x00200000U, /* attempt to use signalfd */ + EVFLAG_NOSIGMASK = 0x00400000U /* avoid modifying the signal mask */ +}; + +/* method bits to be ored together */ +enum { + EVBACKEND_SELECT = 0x00000001U, /* about anywhere */ + EVBACKEND_POLL = 0x00000002U, /* !win */ + EVBACKEND_EPOLL = 0x00000004U, /* linux */ + EVBACKEND_KQUEUE = 0x00000008U, /* bsd */ + EVBACKEND_DEVPOLL = 0x00000010U, /* solaris 8 */ /* NYI */ + EVBACKEND_PORT = 0x00000020U, /* solaris 10 */ + EVBACKEND_ALL = 0x0000003FU, /* all known backends */ + EVBACKEND_MASK = 0x0000FFFFU /* all future backends */ +}; + +#if EV_PROTOTYPES +EV_API_DECL int ev_version_major (void) EV_THROW; +EV_API_DECL int ev_version_minor (void) EV_THROW; + +EV_API_DECL unsigned int ev_supported_backends (void) EV_THROW; +EV_API_DECL unsigned int ev_recommended_backends (void) EV_THROW; +EV_API_DECL unsigned int ev_embeddable_backends (void) EV_THROW; + +EV_API_DECL ev_tstamp ev_time (void) EV_THROW; +EV_API_DECL void ev_sleep (ev_tstamp delay) EV_THROW; /* sleep for a while */ + +/* Sets the allocation function to use, works like realloc. + * It is used to allocate and free memory. + * If it returns zero when memory needs to be allocated, the library might abort + * or take some potentially destructive action. + * The default is your system realloc function. + */ +EV_API_DECL void ev_set_allocator (void *(*cb)(void *ptr, long size) EV_THROW) EV_THROW; + +/* set the callback function to call on a + * retryable syscall error + * (such as failed select, poll, epoll_wait) + */ +EV_API_DECL void ev_set_syserr_cb (void (*cb)(const char *msg) EV_THROW) EV_THROW; + +#if EV_MULTIPLICITY + +/* the default loop is the only one that handles signals and child watchers */ +/* you can call this as often as you like */ +EV_API_DECL struct ev_loop *ev_default_loop (unsigned int flags EV_CPP (= 0)) EV_THROW; + +#ifdef EV_API_STATIC +EV_API_DECL struct ev_loop *ev_default_loop_ptr; +#endif + +EV_INLINE struct ev_loop * +ev_default_loop_uc_ (void) EV_THROW +{ + extern struct ev_loop *ev_default_loop_ptr; + + return ev_default_loop_ptr; +} + +EV_INLINE int +ev_is_default_loop (EV_P) EV_THROW +{ + return EV_A == EV_DEFAULT_UC; +} + +/* create and destroy alternative loops that don't handle signals */ +EV_API_DECL struct ev_loop *ev_loop_new (unsigned int flags EV_CPP (= 0)) EV_THROW; + +EV_API_DECL ev_tstamp ev_now (EV_P) EV_THROW; /* time w.r.t. timers and the eventloop, updated after each poll */ + +#else + +EV_API_DECL int ev_default_loop (unsigned int flags EV_CPP (= 0)) EV_THROW; /* returns true when successful */ + +EV_API_DECL ev_tstamp ev_rt_now; + +EV_INLINE ev_tstamp +ev_now (void) EV_THROW +{ + return ev_rt_now; +} + +/* looks weird, but ev_is_default_loop (EV_A) still works if this exists */ +EV_INLINE int +ev_is_default_loop (void) EV_THROW +{ + return 1; +} + +#endif /* multiplicity */ + +/* destroy event loops, also works for the default loop */ +EV_API_DECL void ev_loop_destroy (EV_P); + +/* this needs to be called after fork, to duplicate the loop */ +/* when you want to re-use it in the child */ +/* you can call it in either the parent or the child */ +/* you can actually call it at any time, anywhere :) */ +EV_API_DECL void ev_loop_fork (EV_P) EV_THROW; + +EV_API_DECL unsigned int ev_backend (EV_P) EV_THROW; /* backend in use by loop */ + +EV_API_DECL void ev_now_update (EV_P) EV_THROW; /* update event loop time */ + +#if EV_WALK_ENABLE +/* walk (almost) all watchers in the loop of a given type, invoking the */ +/* callback on every such watcher. The callback might stop the watcher, */ +/* but do nothing else with the loop */ +EV_API_DECL void ev_walk (EV_P_ int types, void (*cb)(EV_P_ int type, void *w)) EV_THROW; +#endif + +#endif /* prototypes */ + +/* ev_run flags values */ +enum { + EVRUN_NOWAIT = 1, /* do not block/wait */ + EVRUN_ONCE = 2 /* block *once* only */ +}; + +/* ev_break how values */ +enum { + EVBREAK_CANCEL = 0, /* undo unloop */ + EVBREAK_ONE = 1, /* unloop once */ + EVBREAK_ALL = 2 /* unloop all loops */ +}; + +#if EV_PROTOTYPES +EV_API_DECL int ev_run (EV_P_ int flags EV_CPP (= 0)); +EV_API_DECL void ev_break (EV_P_ int how EV_CPP (= EVBREAK_ONE)) EV_THROW; /* break out of the loop */ + +/* + * ref/unref can be used to add or remove a refcount on the mainloop. every watcher + * keeps one reference. if you have a long-running watcher you never unregister that + * should not keep ev_loop from running, unref() after starting, and ref() before stopping. + */ +EV_API_DECL void ev_ref (EV_P) EV_THROW; +EV_API_DECL void ev_unref (EV_P) EV_THROW; + +/* + * convenience function, wait for a single event, without registering an event watcher + * if timeout is < 0, do wait indefinitely + */ +EV_API_DECL void ev_once (EV_P_ int fd, int events, ev_tstamp timeout, void (*cb)(int revents, void *arg), void *arg) EV_THROW; + +# if EV_FEATURE_API +EV_API_DECL unsigned int ev_iteration (EV_P) EV_THROW; /* number of loop iterations */ +EV_API_DECL unsigned int ev_depth (EV_P) EV_THROW; /* #ev_loop enters - #ev_loop leaves */ +EV_API_DECL void ev_verify (EV_P) EV_THROW; /* abort if loop data corrupted */ + +EV_API_DECL void ev_set_io_collect_interval (EV_P_ ev_tstamp interval) EV_THROW; /* sleep at least this time, default 0 */ +EV_API_DECL void ev_set_timeout_collect_interval (EV_P_ ev_tstamp interval) EV_THROW; /* sleep at least this time, default 0 */ + +/* advanced stuff for threading etc. support, see docs */ +EV_API_DECL void ev_set_userdata (EV_P_ void *data) EV_THROW; +EV_API_DECL void *ev_userdata (EV_P) EV_THROW; +typedef void (*ev_loop_callback)(EV_P); +EV_API_DECL void ev_set_invoke_pending_cb (EV_P_ ev_loop_callback invoke_pending_cb) EV_THROW; +/* C++ doesn't allow the use of the ev_loop_callback typedef here, so we need to spell it out */ +EV_API_DECL void ev_set_loop_release_cb (EV_P_ void (*release)(EV_P) EV_THROW, void (*acquire)(EV_P) EV_THROW) EV_THROW; + +EV_API_DECL unsigned int ev_pending_count (EV_P) EV_THROW; /* number of pending events, if any */ +EV_API_DECL void ev_invoke_pending (EV_P); /* invoke all pending watchers */ + +/* + * stop/start the timer handling. + */ +EV_API_DECL void ev_suspend (EV_P) EV_THROW; +EV_API_DECL void ev_resume (EV_P) EV_THROW; +#endif + +#endif + +/* these may evaluate ev multiple times, and the other arguments at most once */ +/* either use ev_init + ev_TYPE_set, or the ev_TYPE_init macro, below, to first initialise a watcher */ +#define ev_init(ev,cb_) do { \ + ((ev_watcher *)(void *)(ev))->active = \ + ((ev_watcher *)(void *)(ev))->pending = 0; \ + ev_set_priority ((ev), 0); \ + ev_set_cb ((ev), cb_); \ +} while (0) + +#define ev_io_set(ev,fd_,events_) do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0) +#define ev_timer_set(ev,after_,repeat_) do { ((ev_watcher_time *)(ev))->at = (after_); (ev)->repeat = (repeat_); } while (0) +#define ev_periodic_set(ev,ofs_,ival_,rcb_) do { (ev)->offset = (ofs_); (ev)->interval = (ival_); (ev)->reschedule_cb = (rcb_); } while (0) +#define ev_signal_set(ev,signum_) do { (ev)->signum = (signum_); } while (0) +#define ev_child_set(ev,pid_,trace_) do { (ev)->pid = (pid_); (ev)->flags = !!(trace_); } while (0) +#define ev_stat_set(ev,path_,interval_) do { (ev)->path = (path_); (ev)->interval = (interval_); (ev)->wd = -2; } while (0) +#define ev_idle_set(ev) /* nop, yes, this is a serious in-joke */ +#define ev_prepare_set(ev) /* nop, yes, this is a serious in-joke */ +#define ev_check_set(ev) /* nop, yes, this is a serious in-joke */ +#define ev_embed_set(ev,other_) do { (ev)->other = (other_); } while (0) +#define ev_fork_set(ev) /* nop, yes, this is a serious in-joke */ +#define ev_cleanup_set(ev) /* nop, yes, this is a serious in-joke */ +#define ev_async_set(ev) /* nop, yes, this is a serious in-joke */ + +#define ev_io_init(ev,cb,fd,events) do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0) +#define ev_timer_init(ev,cb,after,repeat) do { ev_init ((ev), (cb)); ev_timer_set ((ev),(after),(repeat)); } while (0) +#define ev_periodic_init(ev,cb,ofs,ival,rcb) do { ev_init ((ev), (cb)); ev_periodic_set ((ev),(ofs),(ival),(rcb)); } while (0) +#define ev_signal_init(ev,cb,signum) do { ev_init ((ev), (cb)); ev_signal_set ((ev), (signum)); } while (0) +#define ev_child_init(ev,cb,pid,trace) do { ev_init ((ev), (cb)); ev_child_set ((ev),(pid),(trace)); } while (0) +#define ev_stat_init(ev,cb,path,interval) do { ev_init ((ev), (cb)); ev_stat_set ((ev),(path),(interval)); } while (0) +#define ev_idle_init(ev,cb) do { ev_init ((ev), (cb)); ev_idle_set ((ev)); } while (0) +#define ev_prepare_init(ev,cb) do { ev_init ((ev), (cb)); ev_prepare_set ((ev)); } while (0) +#define ev_check_init(ev,cb) do { ev_init ((ev), (cb)); ev_check_set ((ev)); } while (0) +#define ev_embed_init(ev,cb,other) do { ev_init ((ev), (cb)); ev_embed_set ((ev),(other)); } while (0) +#define ev_fork_init(ev,cb) do { ev_init ((ev), (cb)); ev_fork_set ((ev)); } while (0) +#define ev_cleanup_init(ev,cb) do { ev_init ((ev), (cb)); ev_cleanup_set ((ev)); } while (0) +#define ev_async_init(ev,cb) do { ev_init ((ev), (cb)); ev_async_set ((ev)); } while (0) + +#define ev_is_pending(ev) (0 + ((ev_watcher *)(void *)(ev))->pending) /* ro, true when watcher is waiting for callback invocation */ +#define ev_is_active(ev) (0 + ((ev_watcher *)(void *)(ev))->active) /* ro, true when the watcher has been started */ + +#define ev_cb_(ev) (ev)->cb /* rw */ +#define ev_cb(ev) (memmove (&ev_cb_ (ev), &((ev_watcher *)(ev))->cb, sizeof (ev_cb_ (ev))), (ev)->cb) + +#if EV_MINPRI == EV_MAXPRI +# define ev_priority(ev) ((ev), EV_MINPRI) +# define ev_set_priority(ev,pri) ((ev), (pri)) +#else +# define ev_priority(ev) (+(((ev_watcher *)(void *)(ev))->priority)) +# define ev_set_priority(ev,pri) ( (ev_watcher *)(void *)(ev))->priority = (pri) +#endif + +#define ev_periodic_at(ev) (+((ev_watcher_time *)(ev))->at) + +#ifndef ev_set_cb +# define ev_set_cb(ev,cb_) (ev_cb_ (ev) = (cb_), memmove (&((ev_watcher *)(ev))->cb, &ev_cb_ (ev), sizeof (ev_cb_ (ev)))) +#endif + +/* stopping (enabling, adding) a watcher does nothing if it is already running */ +/* stopping (disabling, deleting) a watcher does nothing unless it's already running */ +#if EV_PROTOTYPES + +/* feeds an event into a watcher as if the event actually occurred */ +/* accepts any ev_watcher type */ +EV_API_DECL void ev_feed_event (EV_P_ void *w, int revents) EV_THROW; +EV_API_DECL void ev_feed_fd_event (EV_P_ int fd, int revents) EV_THROW; +#if EV_SIGNAL_ENABLE +EV_API_DECL void ev_feed_signal (int signum) EV_THROW; +EV_API_DECL void ev_feed_signal_event (EV_P_ int signum) EV_THROW; +#endif +EV_API_DECL void ev_invoke (EV_P_ void *w, int revents); +EV_API_DECL int ev_clear_pending (EV_P_ void *w) EV_THROW; + +EV_API_DECL void ev_io_start (EV_P_ ev_io *w) EV_THROW; +EV_API_DECL void ev_io_stop (EV_P_ ev_io *w) EV_THROW; + +EV_API_DECL void ev_timer_start (EV_P_ ev_timer *w) EV_THROW; +EV_API_DECL void ev_timer_stop (EV_P_ ev_timer *w) EV_THROW; +/* stops if active and no repeat, restarts if active and repeating, starts if inactive and repeating */ +EV_API_DECL void ev_timer_again (EV_P_ ev_timer *w) EV_THROW; +/* return remaining time */ +EV_API_DECL ev_tstamp ev_timer_remaining (EV_P_ ev_timer *w) EV_THROW; + +#if EV_PERIODIC_ENABLE +EV_API_DECL void ev_periodic_start (EV_P_ ev_periodic *w) EV_THROW; +EV_API_DECL void ev_periodic_stop (EV_P_ ev_periodic *w) EV_THROW; +EV_API_DECL void ev_periodic_again (EV_P_ ev_periodic *w) EV_THROW; +#endif + +/* only supported in the default loop */ +#if EV_SIGNAL_ENABLE +EV_API_DECL void ev_signal_start (EV_P_ ev_signal *w) EV_THROW; +EV_API_DECL void ev_signal_stop (EV_P_ ev_signal *w) EV_THROW; +#endif + +/* only supported in the default loop */ +# if EV_CHILD_ENABLE +EV_API_DECL void ev_child_start (EV_P_ ev_child *w) EV_THROW; +EV_API_DECL void ev_child_stop (EV_P_ ev_child *w) EV_THROW; +# endif + +# if EV_STAT_ENABLE +EV_API_DECL void ev_stat_start (EV_P_ ev_stat *w) EV_THROW; +EV_API_DECL void ev_stat_stop (EV_P_ ev_stat *w) EV_THROW; +EV_API_DECL void ev_stat_stat (EV_P_ ev_stat *w) EV_THROW; +# endif + +# if EV_IDLE_ENABLE +EV_API_DECL void ev_idle_start (EV_P_ ev_idle *w) EV_THROW; +EV_API_DECL void ev_idle_stop (EV_P_ ev_idle *w) EV_THROW; +# endif + +#if EV_PREPARE_ENABLE +EV_API_DECL void ev_prepare_start (EV_P_ ev_prepare *w) EV_THROW; +EV_API_DECL void ev_prepare_stop (EV_P_ ev_prepare *w) EV_THROW; +#endif + +#if EV_CHECK_ENABLE +EV_API_DECL void ev_check_start (EV_P_ ev_check *w) EV_THROW; +EV_API_DECL void ev_check_stop (EV_P_ ev_check *w) EV_THROW; +#endif + +# if EV_FORK_ENABLE +EV_API_DECL void ev_fork_start (EV_P_ ev_fork *w) EV_THROW; +EV_API_DECL void ev_fork_stop (EV_P_ ev_fork *w) EV_THROW; +# endif + +# if EV_CLEANUP_ENABLE +EV_API_DECL void ev_cleanup_start (EV_P_ ev_cleanup *w) EV_THROW; +EV_API_DECL void ev_cleanup_stop (EV_P_ ev_cleanup *w) EV_THROW; +# endif + +# if EV_EMBED_ENABLE +/* only supported when loop to be embedded is in fact embeddable */ +EV_API_DECL void ev_embed_start (EV_P_ ev_embed *w) EV_THROW; +EV_API_DECL void ev_embed_stop (EV_P_ ev_embed *w) EV_THROW; +EV_API_DECL void ev_embed_sweep (EV_P_ ev_embed *w) EV_THROW; +# endif + +# if EV_ASYNC_ENABLE +EV_API_DECL void ev_async_start (EV_P_ ev_async *w) EV_THROW; +EV_API_DECL void ev_async_stop (EV_P_ ev_async *w) EV_THROW; +EV_API_DECL void ev_async_send (EV_P_ ev_async *w) EV_THROW; +# endif + +#if EV_COMPAT3 + #define EVLOOP_NONBLOCK EVRUN_NOWAIT + #define EVLOOP_ONESHOT EVRUN_ONCE + #define EVUNLOOP_CANCEL EVBREAK_CANCEL + #define EVUNLOOP_ONE EVBREAK_ONE + #define EVUNLOOP_ALL EVBREAK_ALL + #if EV_PROTOTYPES + EV_INLINE void ev_loop (EV_P_ int flags) { ev_run (EV_A_ flags); } + EV_INLINE void ev_unloop (EV_P_ int how ) { ev_break (EV_A_ how ); } + EV_INLINE void ev_default_destroy (void) { ev_loop_destroy (EV_DEFAULT); } + EV_INLINE void ev_default_fork (void) { ev_loop_fork (EV_DEFAULT); } + #if EV_FEATURE_API + EV_INLINE unsigned int ev_loop_count (EV_P) { return ev_iteration (EV_A); } + EV_INLINE unsigned int ev_loop_depth (EV_P) { return ev_depth (EV_A); } + EV_INLINE void ev_loop_verify (EV_P) { ev_verify (EV_A); } + #endif + #endif +#else + typedef struct ev_loop ev_loop; +#endif + +#endif + +EV_CPP(}) + +#endif + diff --git a/framework/src/audit/src/libev/ev_epoll.c b/framework/src/audit/src/libev/ev_epoll.c new file mode 100644 index 00000000..8a9b20f6 --- /dev/null +++ b/framework/src/audit/src/libev/ev_epoll.c @@ -0,0 +1,279 @@ +/* + * libev epoll fd activity backend + * + * Copyright (c) 2007,2008,2009,2010,2011 Marc Alexander Lehmann <libev@schmorp.de> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +/* + * general notes about epoll: + * + * a) epoll silently removes fds from the fd set. as nothing tells us + * that an fd has been removed otherwise, we have to continually + * "rearm" fds that we suspect *might* have changed (same + * problem with kqueue, but much less costly there). + * b) the fact that ADD != MOD creates a lot of extra syscalls due to a) + * and seems not to have any advantage. + * c) the inability to handle fork or file descriptors (think dup) + * limits the applicability over poll, so this is not a generic + * poll replacement. + * d) epoll doesn't work the same as select with many file descriptors + * (such as files). while not critical, no other advanced interface + * seems to share this (rather non-unixy) limitation. + * e) epoll claims to be embeddable, but in practise you never get + * a ready event for the epoll fd (broken: <=2.6.26, working: >=2.6.32). + * f) epoll_ctl returning EPERM means the fd is always ready. + * + * lots of "weird code" and complication handling in this file is due + * to these design problems with epoll, as we try very hard to avoid + * epoll_ctl syscalls for common usage patterns and handle the breakage + * ensuing from receiving events for closed and otherwise long gone + * file descriptors. + */ + +#include <sys/epoll.h> + +#define EV_EMASK_EPERM 0x80 + +static void +epoll_modify (EV_P_ int fd, int oev, int nev) +{ + struct epoll_event ev; + unsigned char oldmask; + + /* + * we handle EPOLL_CTL_DEL by ignoring it here + * on the assumption that the fd is gone anyways + * if that is wrong, we have to handle the spurious + * event in epoll_poll. + * if the fd is added again, we try to ADD it, and, if that + * fails, we assume it still has the same eventmask. + */ + if (!nev) + return; + + oldmask = anfds [fd].emask; + anfds [fd].emask = nev; + + /* store the generation counter in the upper 32 bits, the fd in the lower 32 bits */ + ev.data.u64 = (uint64_t)(uint32_t)fd + | ((uint64_t)(uint32_t)++anfds [fd].egen << 32); + ev.events = (nev & EV_READ ? EPOLLIN : 0) + | (nev & EV_WRITE ? EPOLLOUT : 0); + + if (expect_true (!epoll_ctl (backend_fd, oev && oldmask != nev ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &ev))) + return; + + if (expect_true (errno == ENOENT)) + { + /* if ENOENT then the fd went away, so try to do the right thing */ + if (!epoll_ctl (backend_fd, EPOLL_CTL_ADD, fd, &ev)) + return; + } + else if (expect_true (errno == EEXIST)) + { + /* EEXIST means we ignored a previous DEL, but the fd is still active */ + /* if the kernel mask is the same as the new mask, we assume it hasn't changed */ + if (oldmask == nev) + goto dec_egen; + + if (!epoll_ctl (backend_fd, EPOLL_CTL_MOD, fd, &ev)) + return; + } + else if (expect_true (errno == EPERM)) + { + /* EPERM means the fd is always ready, but epoll is too snobbish */ + /* to handle it, unlike select or poll. */ + anfds [fd].emask = EV_EMASK_EPERM; + + /* add fd to epoll_eperms, if not already inside */ + if (!(oldmask & EV_EMASK_EPERM)) + { + array_needsize (int, epoll_eperms, epoll_epermmax, epoll_epermcnt + 1, EMPTY2); + epoll_eperms [epoll_epermcnt++] = fd; + } + + return; + } + + fd_kill (EV_A_ fd); + +dec_egen: + /* we didn't successfully call epoll_ctl, so decrement the generation counter again */ + --anfds [fd].egen; +} + +static void +epoll_poll (EV_P_ ev_tstamp timeout) +{ + int i; + int eventcnt; + + if (expect_false (epoll_epermcnt)) + timeout = 0.; + + /* epoll wait times cannot be larger than (LONG_MAX - 999UL) / HZ msecs, which is below */ + /* the default libev max wait time, however. */ + EV_RELEASE_CB; + eventcnt = epoll_wait (backend_fd, epoll_events, epoll_eventmax, timeout * 1e3); + EV_ACQUIRE_CB; + + if (expect_false (eventcnt < 0)) + { + if (errno != EINTR) + ev_syserr ("(libev) epoll_wait"); + + return; + } + + for (i = 0; i < eventcnt; ++i) + { + struct epoll_event *ev = epoll_events + i; + + int fd = (uint32_t)ev->data.u64; /* mask out the lower 32 bits */ + int want = anfds [fd].events; + int got = (ev->events & (EPOLLOUT | EPOLLERR | EPOLLHUP) ? EV_WRITE : 0) + | (ev->events & (EPOLLIN | EPOLLERR | EPOLLHUP) ? EV_READ : 0); + + /* + * check for spurious notification. + * this only finds spurious notifications on egen updates + * other spurious notifications will be found by epoll_ctl, below + * we assume that fd is always in range, as we never shrink the anfds array + */ + if (expect_false ((uint32_t)anfds [fd].egen != (uint32_t)(ev->data.u64 >> 32))) + { + /* recreate kernel state */ + postfork = 1; + continue; + } + + if (expect_false (got & ~want)) + { + anfds [fd].emask = want; + + /* + * we received an event but are not interested in it, try mod or del + * this often happens because we optimistically do not unregister fds + * when we are no longer interested in them, but also when we get spurious + * notifications for fds from another process. this is partially handled + * above with the gencounter check (== our fd is not the event fd), and + * partially here, when epoll_ctl returns an error (== a child has the fd + * but we closed it). + */ + ev->events = (want & EV_READ ? EPOLLIN : 0) + | (want & EV_WRITE ? EPOLLOUT : 0); + + /* pre-2.6.9 kernels require a non-null pointer with EPOLL_CTL_DEL, */ + /* which is fortunately easy to do for us. */ + if (epoll_ctl (backend_fd, want ? EPOLL_CTL_MOD : EPOLL_CTL_DEL, fd, ev)) + { + postfork = 1; /* an error occurred, recreate kernel state */ + continue; + } + } + + fd_event (EV_A_ fd, got); + } + + /* if the receive array was full, increase its size */ + if (expect_false (eventcnt == epoll_eventmax)) + { + ev_free (epoll_events); + epoll_eventmax = array_nextsize (sizeof (struct epoll_event), epoll_eventmax, epoll_eventmax + 1); + epoll_events = (struct epoll_event *)ev_malloc (sizeof (struct epoll_event) * epoll_eventmax); + } + + /* now synthesize events for all fds where epoll fails, while select works... */ + for (i = epoll_epermcnt; i--; ) + { + int fd = epoll_eperms [i]; + unsigned char events = anfds [fd].events & (EV_READ | EV_WRITE); + + if (anfds [fd].emask & EV_EMASK_EPERM && events) + fd_event (EV_A_ fd, events); + else + { + epoll_eperms [i] = epoll_eperms [--epoll_epermcnt]; + anfds [fd].emask = 0; + } + } +} + +int inline_size +epoll_init (EV_P_ int flags) +{ +#ifdef EPOLL_CLOEXEC + backend_fd = epoll_create1 (EPOLL_CLOEXEC); + + if (backend_fd < 0 && (errno == EINVAL || errno == ENOSYS)) +#endif + backend_fd = epoll_create (256); + + if (backend_fd < 0) + return 0; + + fcntl (backend_fd, F_SETFD, FD_CLOEXEC); + + backend_mintime = 1e-3; /* epoll does sometimes return early, this is just to avoid the worst */ + backend_modify = epoll_modify; + backend_poll = epoll_poll; + + epoll_eventmax = 64; /* initial number of events receivable per poll */ + epoll_events = (struct epoll_event *)ev_malloc (sizeof (struct epoll_event) * epoll_eventmax); + + return EVBACKEND_EPOLL; +} + +void inline_size +epoll_destroy (EV_P) +{ + ev_free (epoll_events); + array_free (epoll_eperm, EMPTY); +} + +void inline_size +epoll_fork (EV_P) +{ + close (backend_fd); + + while ((backend_fd = epoll_create (256)) < 0) + ev_syserr ("(libev) epoll_create"); + + fcntl (backend_fd, F_SETFD, FD_CLOEXEC); + + fd_rearm_all (EV_A); +} + diff --git a/framework/src/audit/src/libev/ev_poll.c b/framework/src/audit/src/libev/ev_poll.c new file mode 100644 index 00000000..48323516 --- /dev/null +++ b/framework/src/audit/src/libev/ev_poll.c @@ -0,0 +1,148 @@ +/* + * libev poll fd activity backend + * + * Copyright (c) 2007,2008,2009,2010,2011 Marc Alexander Lehmann <libev@schmorp.de> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#include <poll.h> + +void inline_size +pollidx_init (int *base, int count) +{ + /* consider using memset (.., -1, ...), which is practically guaranteed + * to work on all systems implementing poll */ + while (count--) + *base++ = -1; +} + +static void +poll_modify (EV_P_ int fd, int oev, int nev) +{ + int idx; + + if (oev == nev) + return; + + array_needsize (int, pollidxs, pollidxmax, fd + 1, pollidx_init); + + idx = pollidxs [fd]; + + if (idx < 0) /* need to allocate a new pollfd */ + { + pollidxs [fd] = idx = pollcnt++; + array_needsize (struct pollfd, polls, pollmax, pollcnt, EMPTY2); + polls [idx].fd = fd; + } + + assert (polls [idx].fd == fd); + + if (nev) + polls [idx].events = + (nev & EV_READ ? POLLIN : 0) + | (nev & EV_WRITE ? POLLOUT : 0); + else /* remove pollfd */ + { + pollidxs [fd] = -1; + + if (expect_true (idx < --pollcnt)) + { + polls [idx] = polls [pollcnt]; + pollidxs [polls [idx].fd] = idx; + } + } +} + +static void +poll_poll (EV_P_ ev_tstamp timeout) +{ + struct pollfd *p; + int res; + + EV_RELEASE_CB; + res = poll (polls, pollcnt, timeout * 1e3); + EV_ACQUIRE_CB; + + if (expect_false (res < 0)) + { + if (errno == EBADF) + fd_ebadf (EV_A); + else if (errno == ENOMEM && !syserr_cb) + fd_enomem (EV_A); + else if (errno != EINTR) + ev_syserr ("(libev) poll"); + } + else + for (p = polls; res; ++p) + { + assert (("libev: poll() returned illegal result, broken BSD kernel?", p < polls + pollcnt)); + + if (expect_false (p->revents)) /* this expect is debatable */ + { + --res; + + if (expect_false (p->revents & POLLNVAL)) + fd_kill (EV_A_ p->fd); + else + fd_event ( + EV_A_ + p->fd, + (p->revents & (POLLOUT | POLLERR | POLLHUP) ? EV_WRITE : 0) + | (p->revents & (POLLIN | POLLERR | POLLHUP) ? EV_READ : 0) + ); + } + } +} + +int inline_size +poll_init (EV_P_ int flags) +{ + backend_mintime = 1e-3; + backend_modify = poll_modify; + backend_poll = poll_poll; + + pollidxs = 0; pollidxmax = 0; + polls = 0; pollmax = 0; pollcnt = 0; + + return EVBACKEND_POLL; +} + +void inline_size +poll_destroy (EV_P) +{ + ev_free (pollidxs); + ev_free (polls); +} + diff --git a/framework/src/audit/src/libev/ev_select.c b/framework/src/audit/src/libev/ev_select.c new file mode 100644 index 00000000..f38d6ca3 --- /dev/null +++ b/framework/src/audit/src/libev/ev_select.c @@ -0,0 +1,314 @@ +/* + * libev select fd activity backend + * + * Copyright (c) 2007,2008,2009,2010,2011 Marc Alexander Lehmann <libev@schmorp.de> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#ifndef _WIN32 +/* for unix systems */ +# include <inttypes.h> +# ifndef __hpux +/* for REAL unix systems */ +# include <sys/select.h> +# endif +#endif + +#ifndef EV_SELECT_USE_FD_SET +# ifdef NFDBITS +# define EV_SELECT_USE_FD_SET 0 +# else +# define EV_SELECT_USE_FD_SET 1 +# endif +#endif + +#if EV_SELECT_IS_WINSOCKET +# undef EV_SELECT_USE_FD_SET +# define EV_SELECT_USE_FD_SET 1 +# undef NFDBITS +# define NFDBITS 0 +#endif + +#if !EV_SELECT_USE_FD_SET +# define NFDBYTES (NFDBITS / 8) +#endif + +#include <string.h> + +static void +select_modify (EV_P_ int fd, int oev, int nev) +{ + if (oev == nev) + return; + + { +#if EV_SELECT_USE_FD_SET + + #if EV_SELECT_IS_WINSOCKET + SOCKET handle = anfds [fd].handle; + #else + int handle = fd; + #endif + + assert (("libev: fd >= FD_SETSIZE passed to fd_set-based select backend", fd < FD_SETSIZE)); + + /* FD_SET is broken on windows (it adds the fd to a set twice or more, + * which eventually leads to overflows). Need to call it only on changes. + */ + #if EV_SELECT_IS_WINSOCKET + if ((oev ^ nev) & EV_READ) + #endif + if (nev & EV_READ) + FD_SET (handle, (fd_set *)vec_ri); + else + FD_CLR (handle, (fd_set *)vec_ri); + + #if EV_SELECT_IS_WINSOCKET + if ((oev ^ nev) & EV_WRITE) + #endif + if (nev & EV_WRITE) + FD_SET (handle, (fd_set *)vec_wi); + else + FD_CLR (handle, (fd_set *)vec_wi); + +#else + + int word = fd / NFDBITS; + fd_mask mask = 1UL << (fd % NFDBITS); + + if (expect_false (vec_max <= word)) + { + int new_max = word + 1; + + vec_ri = ev_realloc (vec_ri, new_max * NFDBYTES); + vec_ro = ev_realloc (vec_ro, new_max * NFDBYTES); /* could free/malloc */ + vec_wi = ev_realloc (vec_wi, new_max * NFDBYTES); + vec_wo = ev_realloc (vec_wo, new_max * NFDBYTES); /* could free/malloc */ + #ifdef _WIN32 + vec_eo = ev_realloc (vec_eo, new_max * NFDBYTES); /* could free/malloc */ + #endif + + for (; vec_max < new_max; ++vec_max) + ((fd_mask *)vec_ri) [vec_max] = + ((fd_mask *)vec_wi) [vec_max] = 0; + } + + ((fd_mask *)vec_ri) [word] |= mask; + if (!(nev & EV_READ)) + ((fd_mask *)vec_ri) [word] &= ~mask; + + ((fd_mask *)vec_wi) [word] |= mask; + if (!(nev & EV_WRITE)) + ((fd_mask *)vec_wi) [word] &= ~mask; +#endif + } +} + +static void +select_poll (EV_P_ ev_tstamp timeout) +{ + struct timeval tv; + int res; + int fd_setsize; + + EV_RELEASE_CB; + EV_TV_SET (tv, timeout); + +#if EV_SELECT_USE_FD_SET + fd_setsize = sizeof (fd_set); +#else + fd_setsize = vec_max * NFDBYTES; +#endif + + memcpy (vec_ro, vec_ri, fd_setsize); + memcpy (vec_wo, vec_wi, fd_setsize); + +#ifdef _WIN32 + /* pass in the write set as except set. + * the idea behind this is to work around a windows bug that causes + * errors to be reported as an exception and not by setting + * the writable bit. this is so uncontrollably lame. + */ + memcpy (vec_eo, vec_wi, fd_setsize); + res = select (vec_max * NFDBITS, (fd_set *)vec_ro, (fd_set *)vec_wo, (fd_set *)vec_eo, &tv); +#elif EV_SELECT_USE_FD_SET + fd_setsize = anfdmax < FD_SETSIZE ? anfdmax : FD_SETSIZE; + res = select (fd_setsize, (fd_set *)vec_ro, (fd_set *)vec_wo, 0, &tv); +#else + res = select (vec_max * NFDBITS, (fd_set *)vec_ro, (fd_set *)vec_wo, 0, &tv); +#endif + EV_ACQUIRE_CB; + + if (expect_false (res < 0)) + { + #if EV_SELECT_IS_WINSOCKET + errno = WSAGetLastError (); + #endif + #ifdef WSABASEERR + /* on windows, select returns incompatible error codes, fix this */ + if (errno >= WSABASEERR && errno < WSABASEERR + 1000) + if (errno == WSAENOTSOCK) + errno = EBADF; + else + errno -= WSABASEERR; + #endif + + #ifdef _WIN32 + /* select on windows erroneously returns EINVAL when no fd sets have been + * provided (this is documented). what microsoft doesn't tell you that this bug + * exists even when the fd sets _are_ provided, so we have to check for this bug + * here and emulate by sleeping manually. + * we also get EINVAL when the timeout is invalid, but we ignore this case here + * and assume that EINVAL always means: you have to wait manually. + */ + if (errno == EINVAL) + { + if (timeout) + { + unsigned long ms = timeout * 1e3; + Sleep (ms ? ms : 1); + } + + return; + } + #endif + + if (errno == EBADF) + fd_ebadf (EV_A); + else if (errno == ENOMEM && !syserr_cb) + fd_enomem (EV_A); + else if (errno != EINTR) + ev_syserr ("(libev) select"); + + return; + } + +#if EV_SELECT_USE_FD_SET + + { + int fd; + + for (fd = 0; fd < anfdmax; ++fd) + if (anfds [fd].events) + { + int events = 0; + #if EV_SELECT_IS_WINSOCKET + SOCKET handle = anfds [fd].handle; + #else + int handle = fd; + #endif + + if (FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ; + if (FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE; + #ifdef _WIN32 + if (FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE; + #endif + + if (expect_true (events)) + fd_event (EV_A_ fd, events); + } + } + +#else + + { + int word, bit; + for (word = vec_max; word--; ) + { + fd_mask word_r = ((fd_mask *)vec_ro) [word]; + fd_mask word_w = ((fd_mask *)vec_wo) [word]; + #ifdef _WIN32 + word_w |= ((fd_mask *)vec_eo) [word]; + #endif + + if (word_r || word_w) + for (bit = NFDBITS; bit--; ) + { + fd_mask mask = 1UL << bit; + int events = 0; + + events |= word_r & mask ? EV_READ : 0; + events |= word_w & mask ? EV_WRITE : 0; + + if (expect_true (events)) + fd_event (EV_A_ word * NFDBITS + bit, events); + } + } + } + +#endif +} + +int inline_size +select_init (EV_P_ int flags) +{ + backend_mintime = 1e-6; + backend_modify = select_modify; + backend_poll = select_poll; + +#if EV_SELECT_USE_FD_SET + vec_ri = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_ri); + vec_ro = ev_malloc (sizeof (fd_set)); + vec_wi = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_wi); + vec_wo = ev_malloc (sizeof (fd_set)); + #ifdef _WIN32 + vec_eo = ev_malloc (sizeof (fd_set)); + #endif +#else + vec_max = 0; + vec_ri = 0; + vec_ro = 0; + vec_wi = 0; + vec_wo = 0; + #ifdef _WIN32 + vec_eo = 0; + #endif +#endif + + return EVBACKEND_SELECT; +} + +void inline_size +select_destroy (EV_P) +{ + ev_free (vec_ri); + ev_free (vec_ro); + ev_free (vec_wi); + ev_free (vec_wo); + #ifdef _WIN32 + ev_free (vec_eo); + #endif +} + diff --git a/framework/src/audit/src/libev/ev_vars.h b/framework/src/audit/src/libev/ev_vars.h new file mode 100644 index 00000000..04d4db16 --- /dev/null +++ b/framework/src/audit/src/libev/ev_vars.h @@ -0,0 +1,204 @@ +/* + * loop member variable declarations + * + * Copyright (c) 2007,2008,2009,2010,2011,2012,2013 Marc Alexander Lehmann <libev@schmorp.de> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#define VARx(type,name) VAR(name, type name) + +VARx(ev_tstamp, now_floor) /* last time we refreshed rt_time */ +VARx(ev_tstamp, mn_now) /* monotonic clock "now" */ +VARx(ev_tstamp, rtmn_diff) /* difference realtime - monotonic time */ + +/* for reverse feeding of events */ +VARx(W *, rfeeds) +VARx(int, rfeedmax) +VARx(int, rfeedcnt) + +VAR (pendings, ANPENDING *pendings [NUMPRI]) +VAR (pendingmax, int pendingmax [NUMPRI]) +VAR (pendingcnt, int pendingcnt [NUMPRI]) +VARx(int, pendingpri) /* highest priority currently pending */ +VARx(ev_prepare, pending_w) /* dummy pending watcher */ + +VARx(ev_tstamp, io_blocktime) +VARx(ev_tstamp, timeout_blocktime) + +VARx(int, backend) +VARx(int, activecnt) /* total number of active events ("refcount") */ +VARx(EV_ATOMIC_T, loop_done) /* signal by ev_break */ + +VARx(int, backend_fd) +VARx(ev_tstamp, backend_mintime) /* assumed typical timer resolution */ +VAR (backend_modify, void (*backend_modify)(EV_P_ int fd, int oev, int nev)) +VAR (backend_poll , void (*backend_poll)(EV_P_ ev_tstamp timeout)) + +VARx(ANFD *, anfds) +VARx(int, anfdmax) + +VAR (evpipe, int evpipe [2]) +VARx(ev_io, pipe_w) +VARx(EV_ATOMIC_T, pipe_write_wanted) +VARx(EV_ATOMIC_T, pipe_write_skipped) + +#if !defined(_WIN32) || EV_GENWRAP +VARx(pid_t, curpid) +#endif + +VARx(char, postfork) /* true if we need to recreate kernel state after fork */ + +#if EV_USE_SELECT || EV_GENWRAP +VARx(void *, vec_ri) +VARx(void *, vec_ro) +VARx(void *, vec_wi) +VARx(void *, vec_wo) +#if defined(_WIN32) || EV_GENWRAP +VARx(void *, vec_eo) +#endif +VARx(int, vec_max) +#endif + +#if EV_USE_POLL || EV_GENWRAP +VARx(struct pollfd *, polls) +VARx(int, pollmax) +VARx(int, pollcnt) +VARx(int *, pollidxs) /* maps fds into structure indices */ +VARx(int, pollidxmax) +#endif + +#if EV_USE_EPOLL || EV_GENWRAP +VARx(struct epoll_event *, epoll_events) +VARx(int, epoll_eventmax) +VARx(int *, epoll_eperms) +VARx(int, epoll_epermcnt) +VARx(int, epoll_epermmax) +#endif + +#if EV_USE_KQUEUE || EV_GENWRAP +VARx(pid_t, kqueue_fd_pid) +VARx(struct kevent *, kqueue_changes) +VARx(int, kqueue_changemax) +VARx(int, kqueue_changecnt) +VARx(struct kevent *, kqueue_events) +VARx(int, kqueue_eventmax) +#endif + +#if EV_USE_PORT || EV_GENWRAP +VARx(struct port_event *, port_events) +VARx(int, port_eventmax) +#endif + +#if EV_USE_IOCP || EV_GENWRAP +VARx(HANDLE, iocp) +#endif + +VARx(int *, fdchanges) +VARx(int, fdchangemax) +VARx(int, fdchangecnt) + +VARx(ANHE *, timers) +VARx(int, timermax) +VARx(int, timercnt) + +#if EV_PERIODIC_ENABLE || EV_GENWRAP +VARx(ANHE *, periodics) +VARx(int, periodicmax) +VARx(int, periodiccnt) +#endif + +#if EV_IDLE_ENABLE || EV_GENWRAP +VAR (idles, ev_idle **idles [NUMPRI]) +VAR (idlemax, int idlemax [NUMPRI]) +VAR (idlecnt, int idlecnt [NUMPRI]) +#endif +VARx(int, idleall) /* total number */ + +VARx(struct ev_prepare **, prepares) +VARx(int, preparemax) +VARx(int, preparecnt) + +VARx(struct ev_check **, checks) +VARx(int, checkmax) +VARx(int, checkcnt) + +#if EV_FORK_ENABLE || EV_GENWRAP +VARx(struct ev_fork **, forks) +VARx(int, forkmax) +VARx(int, forkcnt) +#endif + +#if EV_CLEANUP_ENABLE || EV_GENWRAP +VARx(struct ev_cleanup **, cleanups) +VARx(int, cleanupmax) +VARx(int, cleanupcnt) +#endif + +#if EV_ASYNC_ENABLE || EV_GENWRAP +VARx(EV_ATOMIC_T, async_pending) +VARx(struct ev_async **, asyncs) +VARx(int, asyncmax) +VARx(int, asynccnt) +#endif + +#if EV_USE_INOTIFY || EV_GENWRAP +VARx(int, fs_fd) +VARx(ev_io, fs_w) +VARx(char, fs_2625) /* whether we are running in linux 2.6.25 or newer */ +VAR (fs_hash, ANFS fs_hash [EV_INOTIFY_HASHSIZE]) +#endif + +VARx(EV_ATOMIC_T, sig_pending) +#if EV_USE_SIGNALFD || EV_GENWRAP +VARx(int, sigfd) +VARx(ev_io, sigfd_w) +VARx(sigset_t, sigfd_set) +#endif + +VARx(unsigned int, origflags) /* original loop flags */ + +#if EV_FEATURE_API || EV_GENWRAP +VARx(unsigned int, loop_count) /* total number of loop iterations/blocks */ +VARx(unsigned int, loop_depth) /* #ev_run enters - #ev_run leaves */ + +VARx(void *, userdata) +/* C++ doesn't support the ev_loop_callback typedef here. stinks. */ +VAR (release_cb, void (*release_cb)(EV_P) EV_THROW) +VAR (acquire_cb, void (*acquire_cb)(EV_P) EV_THROW) +VAR (invoke_cb , ev_loop_callback invoke_cb) +#endif + +#undef VARx + diff --git a/framework/src/audit/src/libev/ev_wrap.h b/framework/src/audit/src/libev/ev_wrap.h new file mode 100644 index 00000000..ad989ea7 --- /dev/null +++ b/framework/src/audit/src/libev/ev_wrap.h @@ -0,0 +1,200 @@ +/* DO NOT EDIT, automatically generated by update_ev_wrap */ +#ifndef EV_WRAP_H +#define EV_WRAP_H +#define acquire_cb ((loop)->acquire_cb) +#define activecnt ((loop)->activecnt) +#define anfdmax ((loop)->anfdmax) +#define anfds ((loop)->anfds) +#define async_pending ((loop)->async_pending) +#define asynccnt ((loop)->asynccnt) +#define asyncmax ((loop)->asyncmax) +#define asyncs ((loop)->asyncs) +#define backend ((loop)->backend) +#define backend_fd ((loop)->backend_fd) +#define backend_mintime ((loop)->backend_mintime) +#define backend_modify ((loop)->backend_modify) +#define backend_poll ((loop)->backend_poll) +#define checkcnt ((loop)->checkcnt) +#define checkmax ((loop)->checkmax) +#define checks ((loop)->checks) +#define cleanupcnt ((loop)->cleanupcnt) +#define cleanupmax ((loop)->cleanupmax) +#define cleanups ((loop)->cleanups) +#define curpid ((loop)->curpid) +#define epoll_epermcnt ((loop)->epoll_epermcnt) +#define epoll_epermmax ((loop)->epoll_epermmax) +#define epoll_eperms ((loop)->epoll_eperms) +#define epoll_eventmax ((loop)->epoll_eventmax) +#define epoll_events ((loop)->epoll_events) +#define evpipe ((loop)->evpipe) +#define fdchangecnt ((loop)->fdchangecnt) +#define fdchangemax ((loop)->fdchangemax) +#define fdchanges ((loop)->fdchanges) +#define forkcnt ((loop)->forkcnt) +#define forkmax ((loop)->forkmax) +#define forks ((loop)->forks) +#define fs_2625 ((loop)->fs_2625) +#define fs_fd ((loop)->fs_fd) +#define fs_hash ((loop)->fs_hash) +#define fs_w ((loop)->fs_w) +#define idleall ((loop)->idleall) +#define idlecnt ((loop)->idlecnt) +#define idlemax ((loop)->idlemax) +#define idles ((loop)->idles) +#define invoke_cb ((loop)->invoke_cb) +#define io_blocktime ((loop)->io_blocktime) +#define iocp ((loop)->iocp) +#define kqueue_changecnt ((loop)->kqueue_changecnt) +#define kqueue_changemax ((loop)->kqueue_changemax) +#define kqueue_changes ((loop)->kqueue_changes) +#define kqueue_eventmax ((loop)->kqueue_eventmax) +#define kqueue_events ((loop)->kqueue_events) +#define kqueue_fd_pid ((loop)->kqueue_fd_pid) +#define loop_count ((loop)->loop_count) +#define loop_depth ((loop)->loop_depth) +#define loop_done ((loop)->loop_done) +#define mn_now ((loop)->mn_now) +#define now_floor ((loop)->now_floor) +#define origflags ((loop)->origflags) +#define pending_w ((loop)->pending_w) +#define pendingcnt ((loop)->pendingcnt) +#define pendingmax ((loop)->pendingmax) +#define pendingpri ((loop)->pendingpri) +#define pendings ((loop)->pendings) +#define periodiccnt ((loop)->periodiccnt) +#define periodicmax ((loop)->periodicmax) +#define periodics ((loop)->periodics) +#define pipe_w ((loop)->pipe_w) +#define pipe_write_skipped ((loop)->pipe_write_skipped) +#define pipe_write_wanted ((loop)->pipe_write_wanted) +#define pollcnt ((loop)->pollcnt) +#define pollidxmax ((loop)->pollidxmax) +#define pollidxs ((loop)->pollidxs) +#define pollmax ((loop)->pollmax) +#define polls ((loop)->polls) +#define port_eventmax ((loop)->port_eventmax) +#define port_events ((loop)->port_events) +#define postfork ((loop)->postfork) +#define preparecnt ((loop)->preparecnt) +#define preparemax ((loop)->preparemax) +#define prepares ((loop)->prepares) +#define release_cb ((loop)->release_cb) +#define rfeedcnt ((loop)->rfeedcnt) +#define rfeedmax ((loop)->rfeedmax) +#define rfeeds ((loop)->rfeeds) +#define rtmn_diff ((loop)->rtmn_diff) +#define sig_pending ((loop)->sig_pending) +#define sigfd ((loop)->sigfd) +#define sigfd_set ((loop)->sigfd_set) +#define sigfd_w ((loop)->sigfd_w) +#define timeout_blocktime ((loop)->timeout_blocktime) +#define timercnt ((loop)->timercnt) +#define timermax ((loop)->timermax) +#define timers ((loop)->timers) +#define userdata ((loop)->userdata) +#define vec_eo ((loop)->vec_eo) +#define vec_max ((loop)->vec_max) +#define vec_ri ((loop)->vec_ri) +#define vec_ro ((loop)->vec_ro) +#define vec_wi ((loop)->vec_wi) +#define vec_wo ((loop)->vec_wo) +#else +#undef EV_WRAP_H +#undef acquire_cb +#undef activecnt +#undef anfdmax +#undef anfds +#undef async_pending +#undef asynccnt +#undef asyncmax +#undef asyncs +#undef backend +#undef backend_fd +#undef backend_mintime +#undef backend_modify +#undef backend_poll +#undef checkcnt +#undef checkmax +#undef checks +#undef cleanupcnt +#undef cleanupmax +#undef cleanups +#undef curpid +#undef epoll_epermcnt +#undef epoll_epermmax +#undef epoll_eperms +#undef epoll_eventmax +#undef epoll_events +#undef evpipe +#undef fdchangecnt +#undef fdchangemax +#undef fdchanges +#undef forkcnt +#undef forkmax +#undef forks +#undef fs_2625 +#undef fs_fd +#undef fs_hash +#undef fs_w +#undef idleall +#undef idlecnt +#undef idlemax +#undef idles +#undef invoke_cb +#undef io_blocktime +#undef iocp +#undef kqueue_changecnt +#undef kqueue_changemax +#undef kqueue_changes +#undef kqueue_eventmax +#undef kqueue_events +#undef kqueue_fd_pid +#undef loop_count +#undef loop_depth +#undef loop_done +#undef mn_now +#undef now_floor +#undef origflags +#undef pending_w +#undef pendingcnt +#undef pendingmax +#undef pendingpri +#undef pendings +#undef periodiccnt +#undef periodicmax +#undef periodics +#undef pipe_w +#undef pipe_write_skipped +#undef pipe_write_wanted +#undef pollcnt +#undef pollidxmax +#undef pollidxs +#undef pollmax +#undef polls +#undef port_eventmax +#undef port_events +#undef postfork +#undef preparecnt +#undef preparemax +#undef prepares +#undef release_cb +#undef rfeedcnt +#undef rfeedmax +#undef rfeeds +#undef rtmn_diff +#undef sig_pending +#undef sigfd +#undef sigfd_set +#undef sigfd_w +#undef timeout_blocktime +#undef timercnt +#undef timermax +#undef timers +#undef userdata +#undef vec_eo +#undef vec_max +#undef vec_ri +#undef vec_ro +#undef vec_wi +#undef vec_wo +#endif diff --git a/framework/src/audit/src/libev/event.c b/framework/src/audit/src/libev/event.c new file mode 100644 index 00000000..5586cd35 --- /dev/null +++ b/framework/src/audit/src/libev/event.c @@ -0,0 +1,425 @@ +/* + * libevent compatibility layer + * + * Copyright (c) 2007,2008,2009,2010,2012 Marc Alexander Lehmann <libev@schmorp.de> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#include <stddef.h> +#include <stdlib.h> +#include <assert.h> + +#ifdef EV_EVENT_H +# include EV_EVENT_H +#else +# include "event.h" +#endif + +#if EV_MULTIPLICITY +# define dLOOPev struct ev_loop *loop = (struct ev_loop *)ev->ev_base +# define dLOOPbase struct ev_loop *loop = (struct ev_loop *)base +#else +# define dLOOPev +# define dLOOPbase +#endif + +/* never accessed, will always be cast from/to ev_loop */ +struct event_base +{ + int dummy; +}; + +static struct event_base *ev_x_cur; + +static ev_tstamp +ev_tv_get (struct timeval *tv) +{ + if (tv) + { + ev_tstamp after = tv->tv_sec + tv->tv_usec * 1e-6; + return after ? after : 1e-6; + } + else + return -1.; +} + +#define EVENT_STRINGIFY(s) # s +#define EVENT_VERSION(a,b) EVENT_STRINGIFY (a) "." EVENT_STRINGIFY (b) + +const char * +event_get_version (void) +{ + /* returns ABI, not API or library, version */ + return EVENT_VERSION (EV_VERSION_MAJOR, EV_VERSION_MINOR); +} + +const char * +event_get_method (void) +{ + return "libev"; +} + +void *event_init (void) +{ +#if EV_MULTIPLICITY + if (ev_x_cur) + ev_x_cur = (struct event_base *)ev_loop_new (EVFLAG_AUTO); + else + ev_x_cur = (struct event_base *)ev_default_loop (EVFLAG_AUTO); +#else + assert (("libev: multiple event bases not supported when not compiled with EV_MULTIPLICITY", !ev_x_cur)); + + ev_x_cur = (struct event_base *)(long)ev_default_loop (EVFLAG_AUTO); +#endif + + return ev_x_cur; +} + +const char * +event_base_get_method (const struct event_base *base) +{ + return "libev"; +} + +struct event_base * +event_base_new (void) +{ +#if EV_MULTIPLICITY + return (struct event_base *)ev_loop_new (EVFLAG_AUTO); +#else + assert (("libev: multiple event bases not supported when not compiled with EV_MULTIPLICITY")); + return NULL; +#endif +} + +void event_base_free (struct event_base *base) +{ + dLOOPbase; + +#if EV_MULTIPLICITY + if (!ev_is_default_loop (loop)) + ev_loop_destroy (loop); +#endif +} + +int event_dispatch (void) +{ + return event_base_dispatch (ev_x_cur); +} + +#ifdef EV_STANDALONE +void event_set_log_callback (event_log_cb cb) +{ + /* nop */ +} +#endif + +int event_loop (int flags) +{ + return event_base_loop (ev_x_cur, flags); +} + +int event_loopexit (struct timeval *tv) +{ + return event_base_loopexit (ev_x_cur, tv); +} + +event_callback_fn event_get_callback +(const struct event *ev) +{ + return ev->ev_callback; +} + +static void +ev_x_cb (struct event *ev, int revents) +{ + revents &= EV_READ | EV_WRITE | EV_TIMER | EV_SIGNAL; + + ev->ev_res = revents; + ev->ev_callback (ev->ev_fd, (short)revents, ev->ev_arg); +} + +static void +ev_x_cb_sig (EV_P_ struct ev_signal *w, int revents) +{ + struct event *ev = (struct event *)(((char *)w) - offsetof (struct event, iosig.sig)); + + if (revents & EV_ERROR) + event_del (ev); + + ev_x_cb (ev, revents); +} + +static void +ev_x_cb_io (EV_P_ struct ev_io *w, int revents) +{ + struct event *ev = (struct event *)(((char *)w) - offsetof (struct event, iosig.io)); + + if ((revents & EV_ERROR) || !(ev->ev_events & EV_PERSIST)) + event_del (ev); + + ev_x_cb (ev, revents); +} + +static void +ev_x_cb_to (EV_P_ struct ev_timer *w, int revents) +{ + struct event *ev = (struct event *)(((char *)w) - offsetof (struct event, to)); + + event_del (ev); + + ev_x_cb (ev, revents); +} + +void event_set (struct event *ev, int fd, short events, void (*cb)(int, short, void *), void *arg) +{ + if (events & EV_SIGNAL) + ev_init (&ev->iosig.sig, ev_x_cb_sig); + else + ev_init (&ev->iosig.io, ev_x_cb_io); + + ev_init (&ev->to, ev_x_cb_to); + + ev->ev_base = ev_x_cur; /* not threadsafe, but it's how libevent works */ + ev->ev_fd = fd; + ev->ev_events = events; + ev->ev_pri = 0; + ev->ev_callback = cb; + ev->ev_arg = arg; + ev->ev_res = 0; + ev->ev_flags = EVLIST_INIT; +} + +int event_once (int fd, short events, void (*cb)(int, short, void *), void *arg, struct timeval *tv) +{ + return event_base_once (ev_x_cur, fd, events, cb, arg, tv); +} + +int event_add (struct event *ev, struct timeval *tv) +{ + dLOOPev; + + if (ev->ev_events & EV_SIGNAL) + { + if (!ev_is_active (&ev->iosig.sig)) + { + ev_signal_set (&ev->iosig.sig, ev->ev_fd); + ev_signal_start (EV_A_ &ev->iosig.sig); + + ev->ev_flags |= EVLIST_SIGNAL; + } + } + else if (ev->ev_events & (EV_READ | EV_WRITE)) + { + if (!ev_is_active (&ev->iosig.io)) + { + ev_io_set (&ev->iosig.io, ev->ev_fd, ev->ev_events & (EV_READ | EV_WRITE)); + ev_io_start (EV_A_ &ev->iosig.io); + + ev->ev_flags |= EVLIST_INSERTED; + } + } + + if (tv) + { + ev->to.repeat = ev_tv_get (tv); + ev_timer_again (EV_A_ &ev->to); + ev->ev_flags |= EVLIST_TIMEOUT; + } + else + { + ev_timer_stop (EV_A_ &ev->to); + ev->ev_flags &= ~EVLIST_TIMEOUT; + } + + ev->ev_flags |= EVLIST_ACTIVE; + + return 0; +} + +int event_del (struct event *ev) +{ + dLOOPev; + + if (ev->ev_events & EV_SIGNAL) + ev_signal_stop (EV_A_ &ev->iosig.sig); + else if (ev->ev_events & (EV_READ | EV_WRITE)) + ev_io_stop (EV_A_ &ev->iosig.io); + + if (ev_is_active (&ev->to)) + ev_timer_stop (EV_A_ &ev->to); + + ev->ev_flags = EVLIST_INIT; + + return 0; +} + +void event_active (struct event *ev, int res, short ncalls) +{ + dLOOPev; + + if (res & EV_TIMEOUT) + ev_feed_event (EV_A_ &ev->to, res & EV_TIMEOUT); + + if (res & EV_SIGNAL) + ev_feed_event (EV_A_ &ev->iosig.sig, res & EV_SIGNAL); + + if (res & (EV_READ | EV_WRITE)) + ev_feed_event (EV_A_ &ev->iosig.io, res & (EV_READ | EV_WRITE)); +} + +int event_pending (struct event *ev, short events, struct timeval *tv) +{ + short revents = 0; + dLOOPev; + + if (ev->ev_events & EV_SIGNAL) + { + /* sig */ + if (ev_is_active (&ev->iosig.sig) || ev_is_pending (&ev->iosig.sig)) + revents |= EV_SIGNAL; + } + else if (ev->ev_events & (EV_READ | EV_WRITE)) + { + /* io */ + if (ev_is_active (&ev->iosig.io) || ev_is_pending (&ev->iosig.io)) + revents |= ev->ev_events & (EV_READ | EV_WRITE); + } + + if (ev->ev_events & EV_TIMEOUT || ev_is_active (&ev->to) || ev_is_pending (&ev->to)) + { + revents |= EV_TIMEOUT; + + if (tv) + { + ev_tstamp at = ev_now (EV_A); + + tv->tv_sec = (long)at; + tv->tv_usec = (long)((at - (ev_tstamp)tv->tv_sec) * 1e6); + } + } + + return events & revents; +} + +int event_priority_init (int npri) +{ + return event_base_priority_init (ev_x_cur, npri); +} + +int event_priority_set (struct event *ev, int pri) +{ + ev->ev_pri = pri; + + return 0; +} + +int event_base_set (struct event_base *base, struct event *ev) +{ + ev->ev_base = base; + + return 0; +} + +int event_base_loop (struct event_base *base, int flags) +{ + dLOOPbase; + + return !ev_run (EV_A_ flags); +} + +int event_base_dispatch (struct event_base *base) +{ + return event_base_loop (base, 0); +} + +static void +ev_x_loopexit_cb (int revents, void *base) +{ + dLOOPbase; + + ev_break (EV_A_ EVBREAK_ONE); +} + +int event_base_loopexit (struct event_base *base, struct timeval *tv) +{ + ev_tstamp after = ev_tv_get (tv); + dLOOPbase; + + ev_once (EV_A_ -1, 0, after >= 0. ? after : 0., ev_x_loopexit_cb, (void *)base); + + return 0; +} + +struct ev_x_once +{ + int fd; + void (*cb)(int, short, void *); + void *arg; +}; + +static void +ev_x_once_cb (int revents, void *arg) +{ + struct ev_x_once *once = (struct ev_x_once *)arg; + + once->cb (once->fd, (short)revents, once->arg); + free (once); +} + +int event_base_once (struct event_base *base, int fd, short events, void (*cb)(int, short, void *), void *arg, struct timeval *tv) +{ + struct ev_x_once *once = (struct ev_x_once *)malloc (sizeof (struct ev_x_once)); + dLOOPbase; + + if (!once) + return -1; + + once->fd = fd; + once->cb = cb; + once->arg = arg; + + ev_once (EV_A_ fd, events & (EV_READ | EV_WRITE), ev_tv_get (tv), ev_x_once_cb, (void *)once); + + return 0; +} + +int event_base_priority_init (struct event_base *base, int npri) +{ + /*dLOOPbase;*/ + + return 0; +} + diff --git a/framework/src/audit/src/libev/event.h b/framework/src/audit/src/libev/event.h new file mode 100644 index 00000000..aa81928f --- /dev/null +++ b/framework/src/audit/src/libev/event.h @@ -0,0 +1,177 @@ +/* + * libevent compatibility header, only core events supported + * + * Copyright (c) 2007,2008,2010,2012 Marc Alexander Lehmann <libev@schmorp.de> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License ("GPL") version 2 or any later version, + * in which case the provisions of the GPL are applicable instead of + * the above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the BSD license, indicate your decision + * by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file under + * either the BSD or the GPL. + */ + +#ifndef EVENT_H_ +#define EVENT_H_ + +#ifdef EV_H +# include EV_H +#else +# include "ev.h" +#endif + +#ifndef EVLOOP_NONBLOCK +# define EVLOOP_NONBLOCK EVRUN_NOWAIT +#endif +#ifndef EVLOOP_ONESHOT +# define EVLOOP_ONESHOT EVRUN_ONCE +#endif +#ifndef EV_TIMEOUT +# define EV_TIMEOUT EV_TIMER +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* we need sys/time.h for struct timeval only */ +#if !defined (WIN32) || defined (__MINGW32__) +# include <time.h> /* mingw seems to need this, for whatever reason */ +# include <sys/time.h> +#endif + +struct event_base; + +#define EVLIST_TIMEOUT 0x01 +#define EVLIST_INSERTED 0x02 +#define EVLIST_SIGNAL 0x04 +#define EVLIST_ACTIVE 0x08 +#define EVLIST_INTERNAL 0x10 +#define EVLIST_INIT 0x80 + +typedef void (*event_callback_fn)(int, short, void *); + +struct event +{ + /* libev watchers we map onto */ + union { + struct ev_io io; + struct ev_signal sig; + } iosig; + struct ev_timer to; + + /* compatibility slots */ + struct event_base *ev_base; + event_callback_fn ev_callback; + void *ev_arg; + int ev_fd; + int ev_pri; + int ev_res; + int ev_flags; + short ev_events; +}; + +event_callback_fn event_get_callback (const struct event *ev); + +#define EV_READ EV_READ +#define EV_WRITE EV_WRITE +#define EV_PERSIST 0x10 +#define EV_ET 0x20 /* nop */ + +#define EVENT_SIGNAL(ev) ((int) (ev)->ev_fd) +#define EVENT_FD(ev) ((int) (ev)->ev_fd) + +#define event_initialized(ev) ((ev)->ev_flags & EVLIST_INIT) + +#define evtimer_add(ev,tv) event_add (ev, tv) +#define evtimer_set(ev,cb,data) event_set (ev, -1, 0, cb, data) +#define evtimer_del(ev) event_del (ev) +#define evtimer_pending(ev,tv) event_pending (ev, EV_TIMEOUT, tv) +#define evtimer_initialized(ev) event_initialized (ev) + +#define timeout_add(ev,tv) evtimer_add (ev, tv) +#define timeout_set(ev,cb,data) evtimer_set (ev, cb, data) +#define timeout_del(ev) evtimer_del (ev) +#define timeout_pending(ev,tv) evtimer_pending (ev, tv) +#define timeout_initialized(ev) evtimer_initialized (ev) + +#define signal_add(ev,tv) event_add (ev, tv) +#define signal_set(ev,sig,cb,data) event_set (ev, sig, EV_SIGNAL | EV_PERSIST, cb, data) +#define signal_del(ev) event_del (ev) +#define signal_pending(ev,tv) event_pending (ev, EV_SIGNAL, tv) +#define signal_initialized(ev) event_initialized (ev) + +const char *event_get_version (void); +const char *event_get_method (void); + +void *event_init (void); +void event_base_free (struct event_base *base); + +#define EVLOOP_ONCE EVLOOP_ONESHOT +int event_loop (int); +int event_loopexit (struct timeval *tv); +int event_dispatch (void); + +#define _EVENT_LOG_DEBUG 0 +#define _EVENT_LOG_MSG 1 +#define _EVENT_LOG_WARN 2 +#define _EVENT_LOG_ERR 3 +typedef void (*event_log_cb)(int severity, const char *msg); +void event_set_log_callback(event_log_cb cb); + +void event_set (struct event *ev, int fd, short events, void (*cb)(int, short, void *), void *arg); +int event_once (int fd, short events, void (*cb)(int, short, void *), void *arg, struct timeval *tv); + +int event_add (struct event *ev, struct timeval *tv); +int event_del (struct event *ev); +void event_active (struct event *ev, int res, short ncalls); /* ncalls is being ignored */ + +int event_pending (struct event *ev, short, struct timeval *tv); + +int event_priority_init (int npri); +int event_priority_set (struct event *ev, int pri); + +struct event_base *event_base_new (void); +const char *event_base_get_method (const struct event_base *); +int event_base_set (struct event_base *base, struct event *ev); +int event_base_loop (struct event_base *base, int); +int event_base_loopexit (struct event_base *base, struct timeval *tv); +int event_base_dispatch (struct event_base *base); +int event_base_once (struct event_base *base, int fd, short events, void (*cb)(int, short, void *), void *arg, struct timeval *tv); +int event_base_priority_init (struct event_base *base, int fd); + +/* next line is different in the libevent+libev version */ +/*libevent-include*/ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/framework/src/audit/src/libev/libev.m4 b/framework/src/audit/src/libev/libev.m4 new file mode 100644 index 00000000..439fbde2 --- /dev/null +++ b/framework/src/audit/src/libev/libev.m4 @@ -0,0 +1,42 @@ +dnl this file is part of libev, do not make local modifications +dnl http://software.schmorp.de/pkg/libev + +dnl libev support +AC_CHECK_HEADERS(sys/inotify.h sys/epoll.h sys/event.h port.h poll.h sys/select.h sys/eventfd.h sys/signalfd.h) + +AC_CHECK_FUNCS(inotify_init epoll_ctl kqueue port_create poll select eventfd signalfd) + +AC_CHECK_FUNCS(clock_gettime, [], [ + dnl on linux, try syscall wrapper first + if test $(uname) = Linux; then + AC_MSG_CHECKING(for clock_gettime syscall) + AC_LINK_IFELSE([AC_LANG_PROGRAM( + [#include <unistd.h> + #include <sys/syscall.h> + #include <time.h>], + [struct timespec ts; int status = syscall (SYS_clock_gettime, CLOCK_REALTIME, &ts)])], + [ac_have_clock_syscall=1 + AC_DEFINE(HAVE_CLOCK_SYSCALL, 1, Define to 1 to use the syscall interface for clock_gettime) + AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no)]) + fi + if test -z "$LIBEV_M4_AVOID_LIBRT" && test -z "$ac_have_clock_syscall"; then + AC_CHECK_LIB(rt, clock_gettime) + unset ac_cv_func_clock_gettime + AC_CHECK_FUNCS(clock_gettime) + fi +]) + +AC_CHECK_FUNCS(nanosleep, [], [ + if test -z "$LIBEV_M4_AVOID_LIBRT"; then + AC_CHECK_LIB(rt, nanosleep) + unset ac_cv_func_nanosleep + AC_CHECK_FUNCS(nanosleep) + fi +]) + +if test -z "$LIBEV_M4_AVOID_LIBM"; then + LIBM=m +fi +AC_SEARCH_LIBS(floor, $LIBM, [AC_DEFINE(HAVE_FLOOR, 1, Define to 1 if the floor function is available)]) + diff --git a/framework/src/audit/src/mt/Makefile.am b/framework/src/audit/src/mt/Makefile.am new file mode 100644 index 00000000..1d613d79 --- /dev/null +++ b/framework/src/audit/src/mt/Makefile.am @@ -0,0 +1,49 @@ +# Makefile.am -- +# Copyright 2005-06,2008,2012,2014-15 Red Hat Inc., Durham, North Carolina. +# All Rights Reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Authors: +# Steve Grubb <sgrubb@redhat.com> +# +# This make file builds a static multi-threaded libary for the audit +# daemon's own use. + +AUTOMAKE_OPTIONS = no-dependencies +AM_CPPFLAGS = -I${top_srcdir}/lib -I${top_builddir}/lib +CONFIG_CLEAN_FILES = *.rej *.orig +AM_CFLAGS = -fPIC -DPIC -D_REENTRANT -D_GNU_SOURCE -DNO_TABLES + +noinst_LIBRARIES = libauditmt.a + +libauditmt_a_SOURCES = ${top_srcdir}/lib/libaudit.c \ + ${top_srcdir}/lib/message.c ${top_srcdir}/lib/netlink.c \ + ${top_srcdir}/lib/lookup_table.c ${top_srcdir}/lib/audit_logging.c \ + ${top_srcdir}/lib/deprecated.c ${top_srcdir}/lib/strsplit.c +libauditmt_a_HEADERS: ${top_builddir}/config.h ${top_srcdir}/lib/libaudit.h \ + ${top_srcdir}/lib/private.h +libauditmt_a_DEPENDENCIES = $(libaudit_a_SOURCES) ${top_builddir}/config.h \ + ${top_srcdir}/lib/gen_tables.h ${top_builddir}/lib/i386_tables.h \ + ${top_builddir}/lib/ia64_tables.h ${top_builddir}/lib/ppc_tables.h \ + ${top_builddir}/lib/s390_tables.h ${top_builddir}/lib/s390x_tables.h \ + ${top_builddir}/lib/x86_64_tables.h ${top_srcdir}/lib/private.h \ + ${top_builddir}/lib/actiontabs.h ${top_builddir}/lib/fieldtabs.h \ + ${top_builddir}/lib/flagtabs.h ${top_builddir}/lib/ftypetabs.h \ + ${top_builddir}/lib/machinetabs.h ${top_builddir}/lib/msg_typetabs.h \ + ${top_builddir}/lib/optabs.h +AM_LDFLAGS = -Wl,-z,relro + +lib_OBJECTS = $(libauditmt_a_OBJECTS) diff --git a/framework/src/audit/src/test/Makefile.am b/framework/src/audit/src/test/Makefile.am new file mode 100644 index 00000000..b6f44edb --- /dev/null +++ b/framework/src/audit/src/test/Makefile.am @@ -0,0 +1,26 @@ +# Copyright 2008,2014,2015 Red Hat Inc., Durham, North Carolina. +# All Rights Reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Authors: +# Steve Grubb <sgrubb@redhat.com> +# + +AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/lib -I${top_srcdir}/src +check_PROGRAMS = ilist_test slist_test +TESTS = $(check_PROGRAMS) +ilist_test_LDADD = ${top_builddir}/src/ausearch-int.o +slist_test_LDADD = ${top_builddir}/src/ausearch-string.o diff --git a/framework/src/audit/src/test/ilist_test.c b/framework/src/audit/src/test/ilist_test.c new file mode 100644 index 00000000..85787126 --- /dev/null +++ b/framework/src/audit/src/test/ilist_test.c @@ -0,0 +1,69 @@ +#include <stdio.h> +#include "ausearch-int.h" + +int main(void) +{ + int i = 0; + ilist e; + int_node *node; + + ilist_create(&e); + + // This first test checks to see if list is + // created in a numeric order + ilist_add_if_uniq(&e, 6, 0); + ilist_add_if_uniq(&e, 5, 0); + ilist_add_if_uniq(&e, 7, 0); + ilist_add_if_uniq(&e, 1, 0); + ilist_add_if_uniq(&e, 8, 0); + ilist_add_if_uniq(&e, 2, 0); + ilist_add_if_uniq(&e, 9, 0); + ilist_add_if_uniq(&e, 0, 0); + ilist_add_if_uniq(&e, 4, 0); + ilist_add_if_uniq(&e, 3, 0); + + ilist_first(&e); + do { + node = ilist_get_cur(&e); + if (i != node->num) { + printf("Test failed - i:%d != num:%d\n", i, node->num); + return 1; + } + i++; + } while ((node = ilist_next(&e))); + + ilist_clear(&e); + puts("starting sort test"); + + // Now test to see if the sort function works + // Fill the list exactly backwards + ilist_add_if_uniq(&e, 3, 0); + ilist_add_if_uniq(&e, 3, 0); + ilist_add_if_uniq(&e, 4, 0); + ilist_add_if_uniq(&e, 3, 0); + ilist_add_if_uniq(&e, 4, 0); + ilist_add_if_uniq(&e, 2, 0); + ilist_add_if_uniq(&e, 4, 0); + ilist_add_if_uniq(&e, 2, 0); + ilist_add_if_uniq(&e, 4, 0); + ilist_add_if_uniq(&e, 1, 0); + + ilist_sort_by_hits(&e); + + i = 0; + ilist_first(&e); + do { + node = ilist_get_cur(&e); + if (node->hits != (4-i)) { + printf("Sort test failed - i:%d != ihits:%d\n", i, node->hits); + return 1; + } + i++; + } while ((node = ilist_next(&e))); + + ilist_clear(&e); + + printf("ilist tests passed\n"); + return 0; +} + diff --git a/framework/src/audit/src/test/slist_test.c b/framework/src/audit/src/test/slist_test.c new file mode 100644 index 00000000..e0336149 --- /dev/null +++ b/framework/src/audit/src/test/slist_test.c @@ -0,0 +1,98 @@ +#include <stdio.h> +#include <string.h> +#include "ausearch-string.h" + +slist s; + +int print_list(void) +{ + int cnt = 0; + slist_first(&s); + do { + snode *cur = slist_get_cur(&s); + if (cur) { + cnt++; + printf("%s\n", cur->str); + } + } while (slist_next(&s)); + return cnt; +} + +int main(void) +{ + snode n, *node; + int rc, i = 0; + + slist_create(&s); + + // This first test checks to see if list is + // created in a numeric order + slist_add_if_uniq(&s, "test1"); + slist_add_if_uniq(&s, "test2"); + slist_first(&s); + slist_add_if_uniq(&s, "test3"); + puts("should be 3"); + rc = print_list(); + if (s.cnt != 3 || rc !=3) { + puts("test count is wrong"); + return 1; + } + + n.str = strdup("test4"); + n.key = NULL; + n.hits = 1; + slist_append(&s, &n); + puts("should add a #4"); + rc = print_list(); + if (s.cnt != 4 || rc != 4) { + puts("test count is wrong"); + return 1; + } + + slist_add_if_uniq(&s, "test2"); + puts("should be same"); + rc = print_list(); + if (s.cnt != 4 || rc != 4) { + puts("test count is wrong"); + return 1; + } + + slist_clear(&s); + puts("should be empty"); + rc = print_list(); + if (s.cnt != 0 || rc != 0) { + puts("test count is wrong"); + return 1; + } + puts("starting sort test"); + + // Now test to see if the sort function works + // Fill the list exactly backwards + slist_add_if_uniq(&s, "test3"); + slist_add_if_uniq(&s, "test3"); + slist_add_if_uniq(&s, "test4"); + slist_add_if_uniq(&s, "test3"); + slist_add_if_uniq(&s, "test4"); + slist_add_if_uniq(&s, "test2"); + slist_add_if_uniq(&s, "test4"); + slist_add_if_uniq(&s, "test2"); + slist_add_if_uniq(&s, "test4"); + slist_add_if_uniq(&s, "test1"); + + slist_sort_by_hits(&s); + slist_first(&s); + do { + node = slist_get_cur(&s); + if (node->hits != (4-i)) { + printf("Sort test failed - i:%d != hits:%d\n", i, node->hits); + return 1; + } + i++; + } while ((node = slist_next(&s))); + puts("sort test passes"); + + slist_clear(&s); + + return 0; +} + |