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 */
-