#include "config_xor.h" #include #include #include #include #include #include #ifdef USE_LOGFILE_MONITOR #undef FIL__ #define FIL__ _("sh_log_evalrule.c") /* Debian/Ubuntu: libpcre3-dev */ #include #include "samhain.h" #include "sh_pthread.h" #include "sh_utils.h" #include "sh_string.h" #include "sh_log_check.h" #include "sh_log_evalrule.h" #include "zAVLTree.h" /* #define DEBUG_EVALRULES */ #ifdef DEBUG_EVALRULES void DEBUG(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); /* flawfinder: ignore *//* we control fmt string */ va_end(ap); return; } #else void DEBUG(const char *fmt, ...) { (void) fmt; return; } #endif enum policies { EVAL_REPORT, EVAL_SUM }; struct sh_ceval /* Counter for summarizing */ { sh_string * hostname; sh_string * counted_str; sh_string * filename; unsigned long count; time_t start; time_t interval; }; void sh_ceval_free(void * item) { struct sh_ceval * counter = (struct sh_ceval *) item; if (!counter) return; sh_string_destroy(&(counter->hostname)); sh_string_destroy(&(counter->counted_str)); sh_string_destroy(&(counter->filename)); SH_FREE(counter); } struct sh_qeval /* Queue with definitions */ { sh_string * label; enum policies policy; int severity; time_t interval; /* if EVAL_SUM, interval */ struct sh_qeval * next; }; struct sh_geval /* Group of rules (may be a single rule) */ { sh_string * label; /* label for this group */ pcre * rule; /* compiled regex for rule */ pcre_extra * rule_extra; int * ovector; /* captured substrings */ int ovecnum; /* how many captured */ int captures; /* (captures+1)*3 required */ zAVLTree * counterlist; /* counters if EVAL_SUM */ struct sh_qeval * queue; /* queue for this rule */ struct sh_geval * nextrule; /* next rule in this group */ struct sh_geval * next; /* next group of rules */ }; struct sh_heval /* host-specific rules */ { pcre * hostname; /* compiled regex for hostname */ pcre_extra * hostname_extra; struct sh_geval * rulegroups; /* list of group of rules */ struct sh_heval * next; }; static struct sh_heval * hostlist = NULL; static struct sh_qeval * queuelist = NULL; static struct sh_geval * grouplist = NULL; /* These flags are set if we are within * the define of a host/rule group. */ static struct sh_heval * host_open = NULL; static struct sh_geval * group_open = NULL; int sh_eval_gend (const char * str) { (void) str; if (group_open) { group_open = NULL; return 0; } return -1; } int sh_eval_gadd (const char * str) { struct sh_geval * ng; struct sh_geval * tmp; pcre * group; pcre_extra * group_extra; const char * error; int erroffset; unsigned int nfields = 2; size_t lengths[2]; char * new = sh_util_strdup(str); char ** splits = split_array(new, &nfields, ':', lengths); if (group_open) group_open = NULL; if (nfields != 2) { SH_FREE(splits); SH_FREE(new); return -1; } group = pcre_compile(splits[1], PCRE_NO_AUTO_CAPTURE, &error, &erroffset, NULL); if (!group) { sh_string * msg = sh_string_new(0); sh_string_add_from_char(msg, _("Bad regex: ")); sh_string_add_from_char(msg, splits[1]); SH_MUTEX_LOCK(mutex_thread_nolog); sh_error_handle(SH_ERR_ERR, FIL__, __LINE__, 0, MSG_E_SUBGEN, sh_string_str(msg), _("sh_eval_gadd")); SH_MUTEX_UNLOCK(mutex_thread_nolog); sh_string_destroy(&msg); SH_FREE(splits); SH_FREE(new); return -1; } group_extra = NULL; /* pcre_study(group, 0, &error); */ ng = SH_ALLOC(sizeof(struct sh_geval)); ng->label = sh_string_new_from_lchar(splits[0], lengths[0]); ng->rule = group; ng->rule_extra = group_extra; ng->ovector = NULL; ng->captures = 0; ng->counterlist = NULL; ng->queue = NULL; ng->nextrule = NULL; /* * Insert at end, to keep user-defined order */ ng->next = NULL; if (grouplist) { tmp = grouplist; while (tmp->next != NULL) { tmp = tmp->next; } tmp->next = ng; } else { grouplist = ng; } if (!host_open) { if (0 != sh_eval_hadd("^.*")) { pcre_free(group); sh_string_destroy(&(ng->label)); SH_FREE(splits); SH_FREE(new); SH_FREE(ng); return -1; } } /* * If there is an open host group, add it to its * rulegroups */ if (host_open) { if (host_open->rulegroups) { tmp = host_open->rulegroups; while (tmp->next != NULL) { tmp = tmp->next; } tmp->next = ng; } else { host_open->rulegroups = ng; } } group_open = ng; SH_FREE(splits); SH_FREE(new); return 0; } int sh_eval_hend (const char * str) { (void) str; if (host_open) { host_open = NULL; return 0; } return -1; } int sh_eval_hadd (const char * str) { struct sh_heval * nh; struct sh_heval * tmp; pcre * host; pcre_extra * host_extra; const char * error; int erroffset; if (host_open) host_open = NULL; host = pcre_compile(str, PCRE_NO_AUTO_CAPTURE, &error, &erroffset, NULL); if (!host) { sh_string * msg = sh_string_new(0); sh_string_add_from_char(msg, _("Bad regex: ")); sh_string_add_from_char(msg, str); SH_MUTEX_LOCK(mutex_thread_nolog); sh_error_handle(SH_ERR_ERR, FIL__, __LINE__, 0, MSG_E_SUBGEN, sh_string_str(msg), _("sh_eval_hadd")); SH_MUTEX_UNLOCK(mutex_thread_nolog); sh_string_destroy(&msg); return -1; } host_extra = NULL; /* pcre_study(host, 0, &error); */ nh = SH_ALLOC(sizeof(struct sh_heval)); nh->hostname = host; nh->hostname_extra = host_extra; nh->rulegroups = NULL; /* * Insert at end, to keep user-defined order */ nh->next = NULL; if (hostlist) { tmp = hostlist; while (tmp->next != NULL) { tmp = tmp->next; } tmp->next = nh; } else { hostlist = nh; } host_open = nh; return 0; } int sh_eval_qadd (const char * str) { struct sh_qeval * nq; int severity; unsigned int nfields = 4; /* label:interval:(report|sum):severity */ size_t lengths[4]; char * new = sh_util_strdup(str); char ** splits = split_array(new, &nfields, ':', lengths); if (nfields != 4) { SH_FREE(splits); SH_FREE(new); return -1; } if (strcmp(splits[2], _("sum")) && strcmp(splits[2], _("report"))) { SH_FREE(splits); SH_FREE(new); return -1; } if (!strcmp(splits[2], _("sum")) && atoi(splits[1]) < 0) { SH_FREE(splits); SH_FREE(new); return -1; } severity = sh_error_convert_level (splits[3]); if (severity < 0) { SH_FREE(splits); SH_FREE(new); return -1; } nq = SH_ALLOC(sizeof(struct sh_qeval)); nq->label = sh_string_new_from_lchar(splits[0], lengths[0]); DEBUG("debug: splits[2] = %s, policy = %d\n",splits[2],nq->policy); if (0 == strcmp(splits[2], _("report"))) { nq->policy = EVAL_REPORT; nq->interval = 0; } else { nq->policy = EVAL_SUM; nq->interval = (time_t) atoi(splits[1]); } nq->severity = severity; nq->next = queuelist; queuelist = nq; SH_FREE(splits); SH_FREE(new); return 0; } static struct sh_qeval * find_queue(const char * str) { struct sh_qeval * retval = queuelist; if (!str) return NULL; while (retval) { if (0 == strcmp(str, sh_string_str(retval->label))) break; retval = retval->next; } return retval; } int sh_eval_radd (const char * str) { struct sh_geval * nr; struct sh_geval * tmp; struct sh_qeval * queue = NULL; pcre * rule; pcre_extra * rule_extra; const char * error; int erroffset; int captures = 0; unsigned int nfields = 2; /* queue:regex */ size_t lengths[2]; char * new = sh_util_strdup(str); char ** splits = split_array(new, &nfields, ':', lengths); if (nfields != 2) { SH_FREE(splits); SH_FREE(new); return -1; } if (0 != strcmp(splits[0], _("trash"))) { queue = find_queue(splits[0]); if (!queue) { SH_FREE(splits); SH_FREE(new); return -1; } } rule = pcre_compile(splits[1], 0, &error, &erroffset, NULL); if (!rule) { sh_string * msg = sh_string_new(0); sh_string_add_from_char(msg, _("Bad regex: ")); sh_string_add_from_char(msg, splits[1]); SH_MUTEX_LOCK(mutex_thread_nolog); sh_error_handle(SH_ERR_ERR, FIL__, __LINE__, 0, MSG_E_SUBGEN, sh_string_str(msg), _("sh_eval_radd")); SH_MUTEX_UNLOCK(mutex_thread_nolog); sh_string_destroy(&msg); SH_FREE(splits); SH_FREE(new); return -1; } rule_extra = NULL; /* pcre_study(rule, 0, &error); */ pcre_fullinfo(rule, rule_extra, PCRE_INFO_CAPTURECOUNT, &captures); DEBUG("adding rule: |%s| with %d captures\n", splits[1], captures); SH_FREE(splits); SH_FREE(new); nr = SH_ALLOC(sizeof(struct sh_geval)); nr->label = NULL; nr->rule = rule; nr->rule_extra = rule_extra; nr->captures = captures; nr->ovector = SH_ALLOC(sizeof(int) * (captures+1) * 3); nr->counterlist = NULL; nr->queue = queue; nr->nextrule = NULL; nr->next = NULL; /* * If there is an open group, add it to its * rules */ if (group_open) { if (group_open->nextrule) { tmp = group_open->nextrule; while (tmp->next != NULL) { tmp = tmp->next; } tmp->next = nr; } else { group_open->nextrule = nr; } } /* * ..else, add it to the currently open host (open the * default host, if there is no open one) */ else { /* * Add rule as member to grouplist, to facilitate cleanup */ if (grouplist) { tmp = grouplist; while (tmp->next != NULL) { tmp = tmp->next; } tmp->next = nr; } else { grouplist = nr; } if (!host_open) { if (0 != sh_eval_hadd("^.*")) { SH_FREE(nr); return -1; } } /* * Add rule to host rulegroups */ if (host_open) { if (host_open->rulegroups) { /* Second, third, ... rule go to host_open->rulegroups->nextrule, * since test_rules() iterates over nextrules */ tmp = host_open->rulegroups; while (tmp->nextrule != NULL) { tmp = tmp->nextrule; } tmp->nextrule = nr; } else { /* First rule goes to host_open->rulegroups */ host_open->rulegroups = nr; } } else { SH_FREE(nr); return -1; } } return 0; } void sh_eval_cleanup() { struct sh_geval * nr; struct sh_geval * tmp; struct sh_geval * gtmp; struct sh_qeval * qtmp; struct sh_heval * htmp; gtmp = grouplist; while (gtmp) { if (gtmp->label) sh_string_destroy(&(gtmp->label)); if (gtmp->rule_extra) (*pcre_free)(gtmp->rule_extra); if (gtmp->rule) (*pcre_free)(gtmp->rule); if (gtmp->counterlist) zAVLFreeTree(gtmp->counterlist, sh_ceval_free); if (gtmp->ovector) SH_FREE(gtmp->ovector); if (gtmp->nextrule) { tmp = gtmp->nextrule; do { nr = tmp->nextrule; if (tmp->rule_extra) (*pcre_free)(tmp->rule_extra); if (tmp->rule) (*pcre_free)(tmp->rule); if (tmp->counterlist) zAVLFreeTree(tmp->counterlist, sh_ceval_free); if (tmp->ovector) SH_FREE(tmp->ovector); SH_FREE(tmp); tmp = nr; } while (tmp); } grouplist = gtmp->next; SH_FREE(gtmp); gtmp = grouplist; } qtmp = queuelist; while (qtmp) { if (qtmp->label) sh_string_destroy(&(qtmp->label)); queuelist = qtmp->next; SH_FREE(qtmp); qtmp = queuelist; } htmp = hostlist; while (htmp) { if (htmp->hostname_extra) (*pcre_free)(htmp->hostname_extra); if (htmp->hostname) (*pcre_free)(htmp->hostname); hostlist = htmp->next; SH_FREE(htmp); htmp = hostlist; } } /********************************************************************** * * Actual rule processing * **********************************************************************/ /* Test a list of rules against msg; return matched rule, with ovector * filled in */ static struct sh_geval * test_rule (struct sh_geval * rule, sh_string *msg) { int res, count; if (!rule) DEBUG("debug: (NULL) rule\n"); if (rule && sh_string_len(msg) < (size_t)INT_MAX) { count = 1; do { DEBUG("debug: check rule %d for <%s>\n", count, msg->str); res = pcre_exec(rule->rule, rule->rule_extra, sh_string_str(msg), (int)sh_string_len(msg), 0, 0, rule->ovector, (3*(1+rule->captures))); if (res >= 0) { rule->ovecnum = res; DEBUG("debug: rule %d matches, result = %d\n", count, res); break; /* return the matching rule; ovector is filled in */ } DEBUG("debug: rule %d did not match\n", count); rule = rule->nextrule; ++count; } while (rule); } if (!rule) DEBUG("debug: no match found\n"); /* If there was no match, this is NULL */ return rule; } /* Test a (struct sh_geval *), which may be single rule or a group of rules, * against msg (if it's a group of rules, test against prefix first). */ static struct sh_geval * test_grules (struct sh_heval * host, sh_string *prefix, sh_string *msg) { struct sh_geval * result = NULL; struct sh_geval * rules = host->rulegroups; if (rules && sh_string_len(prefix) < (size_t)INT_MAX) { DEBUG("debug: if rules\n"); do { if(rules->label != NULL) { /* this is a rule group; only groups have labels */ DEBUG("debug: if rules->label %s\n", rules->label->str); if (pcre_exec(rules->rule, rules->rule_extra, sh_string_str(prefix), (int) sh_string_len(prefix), 0, 0, NULL, 0) >= 0) { result = test_rule(rules->nextrule, msg); if (result) break; } } else { /* First rule is in host->rulegroups */ DEBUG("debug: else (single rule)\n"); result = test_rule(rules, msg); if (result) break; } rules = rules->next; /* next group of rules */ } while (rules); } return result; } /* Top-level find_rule() function */ static struct sh_geval * find_rule (sh_string *host, sh_string *prefix, sh_string *msg) { struct sh_geval * result = NULL; struct sh_heval * hlist = hostlist; if (hlist && sh_string_len(host) < (size_t)INT_MAX) { do { if (pcre_exec(hlist->hostname, hlist->hostname_extra, sh_string_str(host), (int) sh_string_len(host), 0, 0, NULL, 0) >= 0) { /* matching host, check rules/groups of rules */ result = test_grules(hlist, prefix, msg); if (result) break; } hlist = hlist->next; } while (hlist); } return result; } static void msg_report(int severity, struct sh_logrecord * record) { char * tmp; char * msg; char * ttt; SH_MUTEX_LOCK(mutex_thread_nolog); tmp = sh_util_safe_name (record->filename); msg = sh_util_safe_name (sh_string_str(record->message)); ttt = sh_util_safe_name (sh_string_str(record->timestr)); sh_error_handle (severity, FIL__, __LINE__, 0, MSG_LOGMON_REP, msg, ttt, sh_string_str(record->host), tmp); SH_FREE(ttt); SH_FREE(msg); SH_FREE(tmp); SH_MUTEX_UNLOCK(mutex_thread_nolog); } static void sum_report(int severity, sh_string * host, sh_string * message, sh_string * path) { char * tmp; char * msg; SH_MUTEX_LOCK(mutex_thread_nolog); tmp = sh_util_safe_name (sh_string_str(path)); msg = sh_util_safe_name (sh_string_str(message)); sh_error_handle (severity, FIL__, __LINE__, 0, MSG_LOGMON_SUM, msg, sh_string_str(host), tmp); SH_FREE(msg); SH_FREE(tmp); SH_MUTEX_UNLOCK(mutex_thread_nolog); } static zAVLKey sh_eval_getkey(void const *item) { return ((struct sh_ceval *)item)->hostname->str; } /* Find the counter, or initialize one if there is none already */ static struct sh_ceval * find_counter(struct sh_geval * rule, sh_string * host, time_t interval) { struct sh_ceval * counter; if (!(rule->counterlist)) { DEBUG("debug: allocate new counterlist AVL tree\n"); rule->counterlist = zAVLAllocTree(sh_eval_getkey); } counter = (struct sh_ceval *) zAVLSearch (rule->counterlist, sh_string_str(host)); if (!counter) { DEBUG("debug: no counter found\n"); counter = SH_ALLOC(sizeof(struct sh_ceval)); counter->hostname = sh_string_new_from_lchar(sh_string_str(host), sh_string_len(host)); counter->counted_str = NULL; counter->filename = NULL; counter->count = 0; counter->start = time(NULL); counter->interval = interval; zAVLInsert(rule->counterlist, counter); } return counter; } /* copy the message and replace captured substrings with '___' */ static sh_string * replace_captures(const sh_string * message, int * ovector, int ovecnum) { sh_string * retval = sh_string_new_from_lchar(sh_string_str(message), sh_string_len(message)); if (ovecnum > 1) { retval = sh_string_replace(retval, &(ovector[2]), (ovecnum-1), "___", 3); } return retval; } /* process the counter for a SUM rule */ static int process_counter(struct sh_ceval * counter, struct sh_geval * rule, struct sh_logrecord * record) { int retval = -1; time_t now; if (!(counter->counted_str)) { counter->counted_str = replace_captures(record->message, rule->ovector, rule->ovecnum); counter->filename = sh_string_new_from_lchar(record->filename, strlen(record->filename)); DEBUG("debug: counted_str after replace: %s\n", sh_string_str(counter->counted_str)); } ++(counter->count); now = time(NULL); now -= counter->start; DEBUG("debug: count %lu, interval %lu, time %lu\n", counter->count, counter->interval, now); if (now >= counter->interval) { DEBUG("debug: report count\n"); sum_report(rule->queue->severity, counter->hostname, counter->counted_str, counter->filename); counter->start = time(NULL); counter->count = 0; } return retval; } /* Process a rule */ static int process_rule(struct sh_geval * rule, struct sh_logrecord * record) { int retval = -1; struct sh_qeval * queue = rule->queue; if (queue) { DEBUG("debug: queue policy = %d found\n", queue->policy); if (queue->policy == EVAL_REPORT) { DEBUG("debug: EVAL_REPORT host: %s, prefix: %s, message: %s\n", sh_string_str(record->host), sh_string_str(record->prefix), sh_string_str(record->message)); msg_report(queue->severity, record); retval = 0; } else if (queue->policy == EVAL_SUM) { struct sh_ceval * counter = find_counter(rule, record->host, queue->interval); DEBUG("debug: EVAL_SUM host: %s, prefix: %s, message: %s\n", sh_string_str(record->host), sh_string_str(record->prefix), sh_string_str(record->message)); if (counter) { DEBUG("debug: counter found\n"); retval = process_counter(counter, rule, record); } } } else { DEBUG("debug: no queue found -- trash\n"); /* No queue means 'trash' */ retval = 0; } return retval; } #define DEFAULT_SEVERITY (-1) int sh_eval_process_msg(struct sh_logrecord * record) { static unsigned long i = 0; if (record) { struct sh_geval * rule = find_rule (record->host, record->prefix, record->message); if (rule) { DEBUG("debug: (%lu) rule found\n", i); ++i; return process_rule(rule, record); } else { DEBUG("debug: (%lu) no rule found\n", i); ++i; msg_report(DEFAULT_SEVERITY, record); } return 0; } return -1; } #endif