/* * * Copyright (C) 2004, 2005 Rainer Wichmann, Patrice Bourgin, * Yoann Vandoorselaere * * 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* * * 03/12/2004 : R.W.: * fix more memory leaks in get_file_infos() * workaround (re-init) for file descriptor closing problem with GPG * * R.W.: fix missing treatment of alternative XML-style messages * fix get_value (if terminating '>' or '"' is not found, may * overwrite the NULL terminator of the string ...) * fix memory leaks in get_file_infos(), retrieve_time() * * 13/03/2004 : This file is modified by Patrice Bourgin * * * R.W.: Some problems with the patch by Patrice Bourgin fixed * (e.g. memory leak) * * Modifications (13/03/2004) by Patrice bourgin (pbourgin@xpconseil.com) : * Comment : thanks for memory leak fix :p * 1 : remove translation of HTML tag * 2 : send detailled information about files to prelude (with two * functions : get_value and get_file_infos) * these two functions were written by Yoann, but for future * version of prelude, I adapt them to work * with version 0.8.10 of libprelude * 3 : send a heartbeat just after initialization, to alert prelude that * samhain is started * 4 : these modifications was tested successfully, and all informations are * correctly transmitted to prelude and displayed with piwi * * Modifications (7/03/2004) by Patrice bourgin (pbourgin@xpconseil.com) : * 1 : translation of HTML tag <> to tag () in alert to permit * displaying alerts on piwi * 2 : add the address in the source and in the target for displaying on piwi * 3 : add information about the classification, because there was only * one classification and it was not enough * 4 : add impact field to classify alert in prelude, becuse impact is * needed to treat information * 5 : correct some errors with transmission to prelude with libprelude */ #include "config_xor.h" #include #include #include #if TIME_WITH_SYS_TIME #include #include #else #if HAVE_SYS_TIME_H #include #else #include #endif #endif #include #include #if defined(HAVE_LIBPRELUDE) && !defined(HAVE_LIBPRELUDE_9) /* * _() macros are samhain specific; they are used to replace string * constants at runtime. This is part of the samhain stealth mode * (fill string constants with encoded strings, decode at runtime). */ #define FIL__ _("sh_prelude.c") #if defined(__GNUC__) extern char *strptime (const char * s, const char * fmt, struct tm * tp); #endif #include #include #include #include #ifdef HAVE_LIBGEN_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_BASENAME #define basename(a) ((NULL == strrchr(a, '/')) ? (a) : (strrchr(a, '/'))) #endif /* * includes for samhain-specific functions (sl_strstr, sh_error_handle) */ #include "slib.h" #include "sh_mem.h" #include "sh_cat.h" #include "sh_error_min.h" #include "sh_prelude.h" #define SH_NEED_GETHOSTBYXXX #include "sh_static.h" #include "sh_pthread.h" static char programname[64]; static idmef_heartbeat_t heartbeat; static prelude_msgbuf_t * hb_msgbuf = NULL; static struct utsname * uname_data = NULL; static char hostname[256]; static char model[64]; static char class[64]; static char version[8]; static char manufacturer[64]; static char name[16]; static char url[32]; static char meaning[64]; static char description[128]; static char * path_basename = NULL; static char * path_fullname = NULL; /* safe string duplication function */ static char * xstrdup (const char * str) { size_t len; char * ret; if (!str) return NULL; len = sl_strlen(str); ret = SH_ALLOC(len+1); sl_strlcpy (ret, str, len+1); return (ret); } /* Get the value for a key. The key is built from toktmp + toksuffix. * msg is modified temporarily, so it should not be declared 'const'. */ static char *get_value (char *msg, const char *toktmp, const char *toksuffix) { char * ret = NULL, *ptr, tok[128]; snprintf(tok, sizeof(tok), "%s%s", toktmp, (toksuffix) ? toksuffix : ""); ptr = strstr(msg, tok); if ( ! ptr ) return NULL; #ifdef SH_USE_XML while (*ptr && *ptr != '"') ptr++; if (*ptr) { ret = ptr + 1; ptr = ret; } while (*ptr && *ptr != '"') ptr++; if (*ptr) { *ptr = '\0'; if (ret) ret = xstrdup(ret); *ptr = '"'; } else { if (ret) ret = xstrdup(ret); } #else while (*ptr && *ptr != '<') ptr++; if (*ptr) ret = ptr + 1; while (*ptr && *ptr != '>') ptr++; if (*ptr) { *ptr = '\0'; if (ret) ret = xstrdup(ret); *ptr = '>'; } else { if (ret) ret = xstrdup(ret); } #endif return ret; } int sh_prelude_init () { int ret = -1; if (uname_data != NULL) { SH_FREE(uname_data); uname_data = NULL; } uname_data = SH_ALLOC(sizeof(struct utsname)); /* only once */ if (!uname_data) { return -1; } ret = uname(uname_data); if (ret < 0) { uname_data = NULL; return -1; } /* ------- LibPrelude Init ------- */ strncpy(programname, _("Samhain"), 64); programname[63] = '\0'; if ( prelude_sensor_init(programname, NULL, 0, NULL) < 0) { sh_error_handle((-1), FIL__, __LINE__, -1, MSG_E_SUBGEN, _("Failed to initialize Prelude"), _("sh_prelude_init")); return -1; } strncpy(model, _("Samhain"), 64); model[63] = '\0'; strncpy(class, _("Samhain Host Intrusion Detection System"), 64); class[63] = '\0'; strncpy(version, VERSION, 8); version[7] = '\0'; strncpy(manufacturer, _("Samhain by Rainer Wichmann"), 64); manufacturer[63] = '\0'; /* if (sh.host.name[0] != '\0') { strncpy(hostname, sh.host.name, 256); hostname[255] = '\0'; } else { gethostname (hostname, 256); hostname[255] = '\0'; } */ /* According to the manpage, if gethostname returns a truncated hostname, * it may or may not be NULL terminated. So we terminate explicitely. */ gethostname (hostname, 256); hostname[255] = '\0'; strncpy (name, _("Samhain HIDS"), 16); name[15] = '\0'; strncpy (url, _("http://www.la-samhna.de/samhain/"), 32); url[31] = '\0'; strncpy (meaning, _("Message generated by Samhain"), 64); meaning[63] = '\0'; /* analyzer information */ idmef_string_set (&heartbeat.analyzer.model, model); idmef_string_set (&heartbeat.analyzer.class, class); idmef_string_set (&heartbeat.analyzer.version, version); /* analyzer address */ idmef_analyzer_node_new(&heartbeat.analyzer); idmef_string_set (&heartbeat.analyzer.node->name, hostname); /* analyzer type */ idmef_string_set(&heartbeat.analyzer.ostype, uname_data->sysname); idmef_string_set(&heartbeat.analyzer.osversion, uname_data->release); INIT_LIST_HEAD(&heartbeat.additional_data_list); if (hb_msgbuf != NULL) { prelude_msgbuf_close (hb_msgbuf); hb_msgbuf = NULL; } hb_msgbuf = prelude_msgbuf_new(0); if (!hb_msgbuf) { return -1; } /* prelude_heartbeat_register_cb(&SendHeartbeat, NULL); */ return 1; } /* Retrieve the content of "msg=" for adding informations on * impact tag with prelude */ char *RetrieveImpact(const char *msg) { char *tmp1; char *tmp2; char *tmp0; char *ret = NULL; tmp2 = xstrdup(msg); /* * don't use strtok - strtok (str, delim) is 'one of the chars * in delim', not the full 'delim' string */ if (tmp2) tmp1 = sl_strstr (tmp2, _("msg=")); else return NULL; if (tmp1) { tmp1 += 5; #ifdef SH_USE_XML tmp0 = strchr(tmp1, '"'); #else tmp0 = strchr(tmp1, '>'); #endif if (tmp0) *tmp0 = '\0'; ret = xstrdup(tmp1); } SH_FREE(tmp2); /* fix memory leak */ return ret; } /* Transform the string time from the event into time */ time_t retrieve_time(char *stime) { #ifdef HAVE_STRPTIME struct tm tmptime; time_t rettime = -1; char *tmp0, *tmp1, *tmp2; /* fix more memory leaks */ if ( stime ) { tmp0 = xstrdup(stime); tmp1 = tmp0; tmp2 = tmp1 + 1; while (*tmp1 && *tmp1 != ']') tmp1++; if (*tmp1) { *tmp1 = '\0'; tmp2 = xstrdup(tmp2); SH_FREE (tmp0); } else { tmp2 = xstrdup(tmp2); SH_FREE (tmp0); } memset (&tmptime, '\0', sizeof(struct tm)); strptime(tmp2,"%Y-%m-%dT%H:%M:%S", &tmptime); rettime = mktime(&tmptime); SH_FREE(tmp2); if ( rettime != -1 ) return rettime; else return 0; } #endif return 0; } /* msg is modified temporarily in get_value(), * so it should not be declared 'const'. */ void get_file_infos(idmef_target_t *target, char *msg, idmef_file_category_t category) { char *ptr; idmef_file_t *file; idmef_time_t *temps; idmef_inode_t *inode; const char *suffix = (category == current) ? "_new" : "_old"; file = idmef_target_file_new(target); if ( ! file ) return; file->category = category; /* * Fix memory leak - the pointer to get_value(msg, "path", NULL) is lost * libprelude does not strdup, only sets a pointer, so need a global pointer * to keep track of this :( */ if (category == original) path_fullname = get_value(msg, "path", NULL); idmef_string_set (&file->path, path_fullname); if (category == original) path_basename = get_value(msg, "path", NULL); if (path_basename) { idmef_string_set (&file->name, basename(path_basename)); } ptr = get_value(msg, "size", suffix); if ( ptr ) { file->data_size = strtoul(ptr, NULL, 10); SH_FREE(ptr); } ptr = get_value(msg, "mtime", suffix); if ( ptr ) { temps = idmef_file_modify_time_new(file); temps->sec = retrieve_time(ptr); SH_FREE(ptr); } ptr = get_value(msg, "ctime", suffix); if ( ptr ) { temps = idmef_file_create_time_new(file); temps->sec = retrieve_time(ptr); SH_FREE(ptr); } ptr = get_value(msg, "atime", suffix); if ( ptr ) { temps = idmef_file_access_time_new(file); temps->sec = retrieve_time(ptr); SH_FREE(ptr); } ptr = get_value(msg, "inode", suffix); if ( ptr ) { inode = idmef_file_inode_new(file); inode->number = strtoul(ptr, NULL, 10); SH_FREE(ptr); } } void sh_prelude_reset() { (void) sh_prelude_alert (0, 0, NULL, 0, 0); return; } int sh_prelude_alert (int priority, int sh_class, char * message, long msgflags, unsigned long msgid) { static int initialized = 0; struct timeval tv; idmef_alert_t * alert; idmef_message_t * idmef; prelude_msgbuf_t * msgbuf; idmef_classification_t * classification; idmef_target_t * target; idmef_address_t * taddr; idmef_source_t * source; idmef_assessment_t * assessment; idmef_additional_data_t * data; /* To store impact */ char * impactmsg = NULL; struct hostent * myhost; char * src_ip = NULL; static int some_error = 0; (void) msgflags; (void) msgid; /* Workaround for the file closing bug */ if (message == NULL && priority == 0 && sh_class == 0) { initialized = 0; return 0; } if (initialized == 0) { /* initialize */ initialized = sh_prelude_init(); /* send a heartbeat after initialization to say to prelude "I'm alive" */ gettimeofday(&tv, NULL); heartbeat.create_time.sec = tv.tv_sec; heartbeat.create_time.usec = tv.tv_usec; /* * we could use additional data to send stats. */ prelude_msgbuf_set_header(hb_msgbuf, PRELUDE_MSG_IDMEF, 0); idmef_send_heartbeat(hb_msgbuf, &heartbeat); prelude_msgbuf_mark_end(hb_msgbuf); } if (initialized == -1) { /* init failed */ if (some_error == 0) { sh_error_handle((-1), FIL__, __LINE__, -1, MSG_E_SUBGEN, _("Problem with prelude-ids support: init failed"), _("sh_prelude_alert")); } some_error = 1; return -1; } if (sh_class == STAMP) { gettimeofday(&tv, NULL); heartbeat.create_time.sec = tv.tv_sec; heartbeat.create_time.usec = tv.tv_usec; /* * we could use additional data to send stats. */ prelude_msgbuf_set_header(hb_msgbuf, PRELUDE_MSG_IDMEF, 0); idmef_send_heartbeat(hb_msgbuf, &heartbeat); prelude_msgbuf_mark_end(hb_msgbuf); return 0; } /* This function serves to initialize a message structure. * The returned idmef_message_t structure is a static variable * declared in idmef_message_new(). */ idmef = idmef_message_new(); if ( ! idmef ) goto err; /* 'alert' is a static variable that gets initialized and * associated with the idmef_message_t idmef_alert_t member. * -> no new memory allocated; not signal-safe or thread-safe. */ idmef_alert_new(idmef); alert = idmef->message.alert; /* Set the 'detect time'. idmef_alert_new() will already set the * 'create time', whatever the difference is supposed to be. */ gettimeofday(&tv, NULL); idmef_alert_detect_time_new(alert); alert->detect_time->sec = tv.tv_sec; alert->detect_time->usec = tv.tv_usec; /* ------- Analyzer. ------- * * This apparently is supposed to provide some information * about the sensor to the server (what sensor process ? where ?). * * idmef_string_set (x, y) is a macro that will make x * a pointer to y. Therefore the caller must guarantee that y will * never be overwritten until the alert is sent. * With the samhain _() macros, this means we must copy to another * storage region. * * N.B.: with constant strings, you can use idmef_string_set_constant() * instead. */ idmef_string_set (&alert->analyzer.model, model); idmef_string_set (&alert->analyzer.class, class); idmef_string_set (&alert->analyzer.version, version); idmef_string_set (&alert->analyzer.manufacturer, manufacturer); /* Here we add some information on the host OS. */ idmef_string_set (&alert->analyzer.ostype, uname_data->sysname); idmef_string_set (&alert->analyzer.osversion, uname_data->release); /* ------- Analyzer / Process ------- * * Fill in minimal info about the process. Apparently one could also * supply things like path, argv, env (?). */ idmef_analyzer_process_new (&alert->analyzer); alert->analyzer.process->pid = getpid(); /* ------- Analyzer / Node ------- * * Provide the name of this node, i.e. host. */ idmef_analyzer_node_new (&alert->analyzer); idmef_string_set (&alert->analyzer.node->name, hostname); /* ------- Classification ------- * * Apparently 'classification' provides details about the sensor * program. * * For reasons unbeknown to me (did not care to investigate), * this function does allocate memory, instead of using a static variable. * */ classification = idmef_alert_classification_new(alert); if ( ! classification ) goto err; impactmsg = RetrieveImpact(message); if (impactmsg) idmef_string_set (&classification->name, impactmsg); idmef_string_set (&classification->url, url); classification->origin = vendor_specific; /* Get information about ip address */ SH_MUTEX_LOCK(mutex_resolv); myhost = sh_gethostbyname(hostname); src_ip = xstrdup(inet_ntoa(*((struct in_addr *)myhost->h_addr_list[0]))); SH_MUTEX_UNLOCK(mutex_resolv); /* ------- Target ------- * * Purpose ? To provide informations about destination of alert * * Allocates memory. * */ target = idmef_alert_target_new(alert); if ( ! target ) goto err; idmef_target_node_new(target); idmef_string_set(&target->node->name, hostname); if ( strstr(message, "path=") ) { get_file_infos(target, message, original); get_file_infos(target, message, current); } if (src_ip) { taddr = idmef_node_address_new(target->node); if (!taddr) goto err; taddr->category = ipv4_addr; idmef_string_set(&taddr->address, src_ip); } /* ------- Source ------- * * Purpose ? To provide informations about source of alert * * Allocates memory. * */ source = idmef_alert_source_new(alert); if ( ! source ) goto err; /* ------- Impact ------- */ idmef_alert_assessment_new(alert); assessment = alert->assessment; idmef_assessment_impact_new(assessment); if ((priority == SH_ERR_SEVERE) || (priority == SH_ERR_FATAL)) { assessment->impact->severity = impact_high; } else if ((priority == SH_ERR_ALL) || (priority == SH_ERR_INFO) || (priority == SH_ERR_NOTICE)) { assessment->impact->severity = impact_low; } else { assessment->impact->severity = impact_medium; } if (NULL != sl_strstr(message, _("POLICY"))) { if (NULL != sl_strstr(message, _("POLICY KERNEL"))) { assessment->impact->severity = impact_high; assessment->impact->completion = succeeded; assessment->impact->type = other; strncpy(description, _("Kernel modification detected by Samhain."), 128); description[127] = '\0'; } else { assessment->impact->severity = impact_high; assessment->impact->completion = succeeded; assessment->impact->type = file; strncpy(description, _("File system modification detected by Samhain."), 128); description[127] = '\0'; } } else { if ( ((NULL != sl_strstr(message, _("Login"))) || (NULL != sl_strstr(message, _("Multiple login"))) || (NULL != sl_strstr(message, _("Logout")))) && (NULL == sl_strstr(message, _("Checking")))) { assessment->impact->completion = succeeded; assessment->impact->type = user; strncpy(description, _("Login/logout detected by Samhain."), 128); description[127] = '\0'; } else { /* assessment->impact->severity = impact_low; */ assessment->impact->completion = succeeded; assessment->impact->type = other; strncpy(description, _("Message by Samhain."), 128); description[127] = '\0'; } } idmef_string_set (&assessment->impact->description, description); idmef_assessment_confidence_new(assessment); assessment->confidence->rating = high; /* ------- Additional Data ------- * * Here we supply the log message. * */ data = idmef_alert_additional_data_new(alert); if ( ! data ) goto err; data->type = string; idmef_string_set (&data->meaning, meaning); if (message) idmef_additional_data_set_data (data, string, message, strlen(message) + 1); /* ------- Send ------- * * Finally, the preparated message is sent. */ msgbuf = prelude_msgbuf_new(0); if ( ! msgbuf ) goto err; /* Always return 0 (in libprelude 0.8.10); i.e. no useful * exit status */ idmef_msg_send(msgbuf, idmef, PRELUDE_MSG_PRIORITY_HIGH); /* Cleanup */ idmef_message_free(idmef); prelude_msgbuf_close(msgbuf); if (path_basename) { SH_FREE(path_basename); path_basename = NULL; } if (path_fullname) { SH_FREE(path_fullname); path_fullname = NULL; } if (impactmsg) SH_FREE(impactmsg); if (src_ip) SH_FREE(src_ip); some_error = 0; return 0; err: /* Cleanup */ idmef_message_free(idmef); if (0 == some_error) { sh_error_handle((-1), FIL__, __LINE__, -1, MSG_E_SUBGEN, _("Problem with IDMEF for prelude-ids support: alert lost"), _("sh_prelude_alert")); } if (path_basename) { SH_FREE(path_basename); path_basename = NULL; } if (path_fullname) { SH_FREE(path_fullname); path_fullname = NULL; } if (impactmsg) SH_FREE(impactmsg); if (src_ip) SH_FREE(src_ip); some_error = 1; return -1; } /* HAVE_LIBPRELUDE */ #endif