diff --git a/etc/rules/ossec_rules.xml b/etc/rules/ossec_rules.xml index fa2c7da55..2c2ce565a 100755 --- a/etc/rules/ossec_rules.xml +++ b/etc/rules/ossec_rules.xml @@ -211,6 +211,13 @@ syscheck,agentless + + ossec + syscheck_allowed + Integrity checksum changed (allowed). + syscheck, + + ossec diff --git a/src/Makefile b/src/Makefile index 9b308fa63..87ea73cb8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -379,6 +379,7 @@ install-common: build install -d -m 0770 -o ${OSSEC_USER} -g ${OSSEC_GROUP} ${PREFIX}/queue/alerts install -d -m 0750 -o ${OSSEC_USER} -g ${OSSEC_GROUP} ${PREFIX}/queue/ossec install -d -m 0750 -o ${OSSEC_USER} -g ${OSSEC_GROUP} ${PREFIX}/queue/syscheck + install -d -m 0750 -o ${OSSEC_USER} -g ${OSSEC_GROUP} ${PREFIX}/queue/syscheck-allowchange install -d -m 0750 -o ${OSSEC_USER} -g ${OSSEC_GROUP} ${PREFIX}/queue/diff install -d -m 0550 -o root -g ${OSSEC_GROUP} ${PREFIX}/etc diff --git a/src/analysisd/analysisd.c b/src/analysisd/analysisd.c index d63cddf0f..a2f199cf7 100644 --- a/src/analysisd/analysisd.c +++ b/src/analysisd/analysisd.c @@ -32,6 +32,7 @@ #include "cleanevent.h" #include "dodiff.h" #include "output/jsonout.h" +#include "decoders/syscheck-allow.h" #ifdef PICVIZ_OUTPUT_ENABLED #include "output/picviz.h" @@ -755,6 +756,15 @@ void OS_ReadMSG_analysisd(int m_queue) lf->size = strlen(lf->log); } + /* Allowchange event decoding */ + else if (msg[0] == ALLOWCHANGE_MQ) { + if (!DecodeAllowchange(lf)) { + /* We don't process allowchange events further */ + goto CLMEM; + } + lf->size = strlen(lf->log); + } + /* Host information special decoder */ else if (msg[0] == HOSTINFO_MQ) { if (!DecodeHostinfo(lf)) { diff --git a/src/analysisd/decoders/decode-xml.c b/src/analysisd/decoders/decode-xml.c index 6e99c330e..5cd89801e 100644 --- a/src/analysisd/decoders/decode-xml.c +++ b/src/analysisd/decoders/decode-xml.c @@ -691,6 +691,7 @@ int SetDecodeXML() addDecoder2list(SYSCHECK_MOD); addDecoder2list(SYSCHECK_MOD2); addDecoder2list(SYSCHECK_MOD3); + addDecoder2list(SYSCHECK_ALLOWED); addDecoder2list(SYSCHECK_NEW); addDecoder2list(SYSCHECK_DEL); addDecoder2list(HOSTINFO_NEW); diff --git a/src/analysisd/decoders/syscheck-allow.c b/src/analysisd/decoders/syscheck-allow.c new file mode 100644 index 000000000..22cd403b7 --- /dev/null +++ b/src/analysisd/decoders/syscheck-allow.c @@ -0,0 +1,110 @@ +/* Copyright (C) 2009 Trend Micro Inc. + * All right reserved. + * + * This program is a free software; you can redistribute it + * and/or modify it under the terms of the GNU General Public + * License (version 2) as published by the FSF - Free Software + * Foundation + */ + +/* Syscheck decoder */ + +#include "eventinfo.h" +#include "os_regex/os_regex.h" +#include "config.h" +#include "decoder.h" + +/* We write allowed filenames in a database located at /queue/syscheck-allowchange/ + * Each line represents an entry which is structured as + * + * where is either the char '0' or '1', + * and indicate whereas this entry have already been consummed. + */ + + +/* Determine if this filename have at least one allowed change, and + consumme it */ +int consumeAllowchange(const char *filename, Eventinfo *lf){ + FILE *db_file; + char db_filename[OS_FLSIZE]; + char line[OS_FLSIZE*2]; + int allowed = 0; + time_t current; + snprintf(db_filename, OS_FLSIZE , "%s/%s", ALLOWCHANGE_DIR, lf->hostname); + db_file = fopen(db_filename, "r+"); + if (!db_file) { + db_file = fopen(db_filename, "w+"); + if (!db_file) { + verbose("failed to open %s", db_filename); + return 0; + } + } + rewind(db_file); + current = time(0); + + while (fgets(line, OS_FLSIZE*2, db_file) != NULL) { + /* Attempt to parse the line */ + int validity; + time_t until; + char path[OS_FLSIZE]; + sscanf(line, "%d %ld %s", &validity, &until, path); + if (validity) { + if (until > current) { + if (strcmp(path, filename) == 0){ + int len = strlen(line); + /* Rewrite current entry */ + fseek(db_file, -len, SEEK_CUR); + fprintf(db_file, "0"); + fclose(db_file); + return until; + } + } + } + } + + fclose(db_file); + return allowed; +} + +/* Save an Allowchange record in our "database" +*/ +int produceAllowchange(time_t timestamp, const char *filename, Eventinfo *lf){ + FILE *db_file; + char db_filename[OS_FLSIZE]; + snprintf(db_filename, OS_FLSIZE , "%s/%s", ALLOWCHANGE_DIR, lf->hostname); + db_file = fopen(db_filename, "a"); + if (db_file) { + fprintf(db_file, "%d %ld %s\n", 1, timestamp, filename); + fclose(db_file); + return timestamp; + } + verbose("Failed to open %s", db_filename); + return 0; +} + + + +/* A special decoder to read an AllowChange event */ +int DecodeAllowchange(Eventinfo *lf) +{ + time_t timestamp; + char *f_name; + + /* Allowchange messages must be in the following format: + * timestamp filename + */ + f_name = strchr(lf->log, ' '); + if (f_name == NULL) { + merror(SK_INV_MSG, ARGV0); + return (0); + } + + /* Zero to get the timestamp */ + *f_name = '\0'; + f_name++; + + /* Get timestamp */ + timestamp = atoi(lf->log); + produceAllowchange(timestamp, f_name, lf); + return timestamp; +} diff --git a/src/analysisd/decoders/syscheck-allow.h b/src/analysisd/decoders/syscheck-allow.h new file mode 100644 index 000000000..362580bab --- /dev/null +++ b/src/analysisd/decoders/syscheck-allow.h @@ -0,0 +1,10 @@ +#ifndef __SYSCHECK_ALLOW_H +#define __SYSCHECK_ALLOW_H + +#include "shared.h" + +int consumeAllowchange(const char *filename, Eventinfo *lf); +int produceAllowchange(time_t timestamp, const char *filename, Eventinfo *lf); +int DecodeAllowchange(Eventinfo *lf); + +#endif diff --git a/src/analysisd/decoders/syscheck.c b/src/analysisd/decoders/syscheck.c index 10c8432eb..fcd18089f 100644 --- a/src/analysisd/decoders/syscheck.c +++ b/src/analysisd/decoders/syscheck.c @@ -14,6 +14,7 @@ #include "config.h" #include "alerts/alerts.h" #include "decoder.h" +#include "syscheck-allow.h" typedef struct __sdb { char buf[OS_MAXSTR + 1]; @@ -124,6 +125,7 @@ static int __iscompleted(const char *agent) return (0); } + /* Set the database of a specific agent as completed */ static void DB_SetCompleted(const Eventinfo *lf) { @@ -176,7 +178,6 @@ static FILE *DB_File(const char *agent, int *agent_id) /* Get agent file */ snprintf(sdb.buf, OS_FLSIZE , "%s/%s", SYSCHECK_DIR, agent); - /* r+ to read and write. Do not truncate */ sdb.agent_fps[i] = fopen(sdb.buf, "r+"); if (!sdb.agent_fps[i]) { @@ -304,9 +305,8 @@ static int DB_Search(const char *f_name, const char *c_sum, Eventinfo *lf) } /* Check the number of changes */ - if (!Config.syscheck_auto_ignore) { - sdb.syscheck_dec->id = sdb.id1; - } else { + sdb.syscheck_dec->id = sdb.id1; + if (Config.syscheck_auto_ignore) { switch (p) { case 0: sdb.syscheck_dec->id = sdb.id1; @@ -619,6 +619,7 @@ int DecodeSyscheck(Eventinfo *lf) { const char *c_sum; char *f_name; + int status; /* Every syscheck message must be in the following format: * checksum filename @@ -668,6 +669,12 @@ int DecodeSyscheck(Eventinfo *lf) c_sum = lf->log; /* Search for file changes */ - return (DB_Search(f_name, c_sum, lf)); -} + status = DB_Search(f_name, c_sum, lf); + /* Check if the file have been allowed to change */ + if (consumeAllowchange(f_name, lf)){ + lf->decoder_info->id = getDecoderfromlist(SYSCHECK_ALLOWED); + lf->decoder_info->name = SYSCHECK_ALLOWED; + } + return status; +} diff --git a/src/analysisd/rules.h b/src/analysisd/rules.h index 2a9a9c972..ec2ee799d 100644 --- a/src/analysisd/rules.h +++ b/src/analysisd/rules.h @@ -232,6 +232,7 @@ int _setlevels(RuleNode *node, int nnode); #define SYSCHECK_MOD3 "syscheck_integrity_changed_3rd" #define SYSCHECK_NEW "syscheck_new_entry" #define SYSCHECK_DEL "syscheck_deleted" +#define SYSCHECK_ALLOWED "syscheck_allowed" /* Global variables */ extern int _max_freq; diff --git a/src/headers/defs.h b/src/headers/defs.h index d2b1d5e13..65541bc75 100644 --- a/src/headers/defs.h +++ b/src/headers/defs.h @@ -32,6 +32,7 @@ #define OS_FLSIZE OS_SIZE_256 /* Maximum file size */ #define OS_HEADER_SIZE OS_SIZE_128 /* Maximum header size */ #define OS_LOG_HEADER OS_SIZE_256 /* Maximum log header size */ +#define OS_MAXPATH OS_SIZE_1024 /* Maximum filepath length */ #define IPSIZE INET6_ADDRSTRLEN /* IP Address size */ /* Some global names */ @@ -117,6 +118,9 @@ published by the Free Software Foundation. For more details, go to \n\ /* Rootcheck directory */ #define ROOTCHECK_DIR "/queue/rootcheck" +/* Allowchange directory */ +#define ALLOWCHANGE_DIR "/queue/syscheck-allowchange" + /* Diff queue */ #define DIFF_DIR "/queue/diff" #define DIFF_DIR_PATH DEFAULTDIR DIFF_DIR @@ -126,6 +130,7 @@ published by the Free Software Foundation. For more details, go to \n\ /* Syscheck data */ #define SYSCHECK "syscheck" #define SYSCHECK_REG "syscheck-registry" +#define ALLOWCHANGE "syscheck-allowchange" /* Rule path */ #define RULEPATH "/rules" diff --git a/src/headers/mq_op.h b/src/headers/mq_op.h index b425c53c1..af04b20dc 100644 --- a/src/headers/mq_op.h +++ b/src/headers/mq_op.h @@ -17,6 +17,7 @@ #define SECURE_MQ '4' #define SYSCHECK_MQ '8' #define ROOTCHECK_MQ '9' +#define ALLOWCHANGE_MQ 'c' /* Queues for additional log types */ #define MYSQL_MQ 'a' diff --git a/src/syscheckd/syscheck.c b/src/syscheckd/syscheck.c index 6c07aaa0e..f68d9989e 100644 --- a/src/syscheckd/syscheck.c +++ b/src/syscheckd/syscheck.c @@ -66,6 +66,23 @@ static void read_internal(int debug_level) return; } +/* Send a message to the monitor that we allow one change events on + this file until timestamp + */ +static int allowChange(char* filename, time_t timestamp) +{ + char msg[OS_MAXPATH*2]; + sprintf(msg, "%ld %s", timestamp, filename); + if ((syscheck.queue = StartMQ(DEFAULTQPATH, WRITE)) < 0) { + ErrorExit(QUEUE_FATAL, ARGV0, DEFAULTQPATH); + } + if (SendMSG(syscheck.queue, msg, ALLOWCHANGE, ALLOWCHANGE_MQ) < 0) { + merror(QUEUE_SEND, ARGV0); + } + debug1("%s: send_allowchange_msg: %s to %s\n", ARGV0, msg, DEFAULTQPATH); + return 0; +} + #ifdef WIN32 /* syscheck main for Windows */ int Start_win32_Syscheck() @@ -167,15 +184,17 @@ int Start_win32_Syscheck() static void help_syscheckd() { print_header(); - print_out(" %s: -[Vhdtf] [-c config]", ARGV0); - print_out(" -V Version and license message"); - print_out(" -h This help message"); - print_out(" -d Execute in debug mode. This parameter"); - print_out(" can be specified multiple times"); - print_out(" to increase the debug level."); - print_out(" -t Test configuration"); - print_out(" -f Run in foreground"); - print_out(" -c Configuration file to use (default: %s)", DEFAULTCPATH); + print_out(" %s: -[Vhdtf] [-c config] [-a filename -u timestamp]", ARGV0); + print_out(" -V Version and license message"); + print_out(" -h This help message"); + print_out(" -d Execute in debug mode. This parameter"); + print_out(" can be specified multiple times"); + print_out(" to increase the debug level."); + print_out(" -t Test configuration"); + print_out(" -f Run in foreground"); + print_out(" -c Configuration file to use (default: %s)", DEFAULTCPATH); + print_out(" -a Allow changes on filename"); + print_out(" -u Allow changes until timestamp"); print_out(" "); exit(1); } @@ -187,12 +206,16 @@ int main(int argc, char **argv) int c, r; int debug_level = 0; int test_config = 0, run_foreground = 0; + int allow_change = 0; const char *cfg = DEFAULTCPATH; + char allow_filename[OS_MAXPATH]; + time_t allow_timestamp = 0; /* Set the name */ OS_SetName(ARGV0); + *allow_filename = '\0'; - while ((c = getopt(argc, argv, "Vtdhfc:")) != -1) { + while ((c = getopt(argc, argv, "Vtdhfc:a:u:")) != -1) { switch (c) { case 'V': print_version(); @@ -213,6 +236,20 @@ int main(int argc, char **argv) } cfg = optarg; break; + case 'a': + if (!optarg) { + ErrorExit("%s: -a needs a filename", ARGV0); + } + strncpy(allow_filename, optarg, OS_MAXPATH); + allow_change = 1; + break; + case 'u': + if (!optarg) { + ErrorExit("%s: -w needs a timestamp", ARGV0); + } + allow_timestamp = atoi(optarg); + allow_change = 1; + break; case 't': test_config = 1; break; @@ -252,6 +289,27 @@ int main(int argc, char **argv) } } + + if (allow_change){ + if (allow_timestamp == 0){ + merror("%s: WARN: Missing timestamp for allow change", ARGV0); + exit(1); + } else if (*allow_filename != '\0') { + allowChange(allow_filename, allow_timestamp); + exit(0); + } else { + debug1("%s: Reading filenames from stdin, one path per line", ARGV0); + while (fgets(allow_filename, OS_MAXPATH, stdin)) { + /* Remove the newline character */ + if (allow_filename[strlen(allow_filename) - 1] == '\n') { + allow_filename[strlen(allow_filename) - 1] = '\0'; + } + allowChange(allow_filename, allow_timestamp); + } + exit(0); + } + } + /* Rootcheck config */ if (rootcheck_init(test_config) == 0) { syscheck.rootcheck = 1; @@ -360,4 +418,3 @@ int main(int argc, char **argv) } #endif /* !WIN32 */ -