1 /* Copyright (C) 2009 Trend Micro Inc.
4 * This program is a free software; you can redistribute it
5 * and/or modify it under the terms of the GNU General Public
6 * License (version 2) as published by the FSF - Free Software
10 /* Syscheck decoder */
12 #include "eventinfo.h"
13 #include "os_regex/os_regex.h"
15 #include "alerts/alerts.h"
19 #include "syscheck-sqlite.h"
22 typedef struct __sdb {
23 char buf[OS_MAXSTR + 1];
24 char comment[OS_MAXSTR + 1];
26 char size[OS_FLSIZE + 1];
27 char perm[OS_FLSIZE + 1];
28 char owner[OS_FLSIZE + 1];
29 char gowner[OS_FLSIZE + 1];
30 char md5[OS_FLSIZE + 1];
31 char sha1[OS_FLSIZE + 1];
33 char agent_cp[MAX_AGENTS + 1][1];
34 char *agent_ips[MAX_AGENTS + 1];
35 FILE *agent_fps[MAX_AGENTS + 1];
47 OSDecoderInfo *syscheck_dec;
49 /* File search variables */
52 } _sdb; /* syscheck db information */
57 /* Extract a token from a string */
58 char *extract_token(const char *s, char *delim, int position) {
60 char tmp[OS_MAXSTR + 1];
62 strncpy(tmp,s, OS_MAXSTR);
63 token = strtok(tmp, delim);
64 while (token != NULL) {
66 token = strtok(NULL, delim);
67 if (count == position) {
75 /* Validate a MD5 string format */
76 int validate_md5(char *s) {
78 char *hex_chars = "abcdefABCDEF0123456789";
79 if (strlen(s) != 32) {
82 for (i = 0; i < strlen(s); i++) {
83 if (!strchr(hex_chars, s[i])) return(0);
89 /* Initialize the necessary information to process the syscheck information */
96 for (; i <= MAX_AGENTS; i++) {
97 sdb.agent_ips[i] = NULL;
98 sdb.agent_fps[i] = NULL;
99 sdb.agent_cp[i][0] = '0';
102 /* Clear db memory */
103 memset(sdb.buf, '\0', OS_MAXSTR + 1);
104 memset(sdb.comment, '\0', OS_MAXSTR + 1);
106 memset(sdb.size, '\0', OS_FLSIZE + 1);
107 memset(sdb.perm, '\0', OS_FLSIZE + 1);
108 memset(sdb.owner, '\0', OS_FLSIZE + 1);
109 memset(sdb.gowner, '\0', OS_FLSIZE + 1);
110 memset(sdb.md5, '\0', OS_FLSIZE + 1);
111 memset(sdb.sha1, '\0', OS_FLSIZE + 1);
114 os_calloc(1, sizeof(OSDecoderInfo), sdb.syscheck_dec);
115 sdb.syscheck_dec->id = getDecoderfromlist(SYSCHECK_MOD);
116 sdb.syscheck_dec->name = SYSCHECK_MOD;
117 sdb.syscheck_dec->type = OSSEC_RL;
118 sdb.syscheck_dec->fts = 0;
120 sdb.id1 = getDecoderfromlist(SYSCHECK_MOD);
121 sdb.id2 = getDecoderfromlist(SYSCHECK_MOD2);
122 sdb.id3 = getDecoderfromlist(SYSCHECK_MOD3);
123 sdb.idn = getDecoderfromlist(SYSCHECK_NEW);
124 sdb.idd = getDecoderfromlist(SYSCHECK_DEL);
126 debug1("%s: SyscheckInit completed.", ARGV0);
130 /* Check if the db is completed for that specific agent */
131 #define DB_IsCompleted(x) (sdb.agent_cp[x][0] == '1')?1:0
133 static void __setcompleted(const char *agent)
138 snprintf(sdb.buf, OS_FLSIZE , "%s/.%s.cpt", SYSCHECK_DIR, agent);
140 fp = fopen(sdb.buf, "w");
147 static int __iscompleted(const char *agent)
152 snprintf(sdb.buf, OS_FLSIZE , "%s/.%s.cpt", SYSCHECK_DIR, agent);
154 fp = fopen(sdb.buf, "r");
162 /* Set the database of a specific agent as completed */
163 static void DB_SetCompleted(const Eventinfo *lf)
167 /* Find file pointer */
168 while (sdb.agent_ips[i] != NULL && i < MAX_AGENTS) {
169 if (strcmp(sdb.agent_ips[i], lf->location) == 0) {
170 /* Return if already set as completed */
171 if (DB_IsCompleted(i)) {
175 __setcompleted(lf->location);
177 /* Set as completed in memory */
178 sdb.agent_cp[i][0] = '1';
187 /* Return the file pointer to be used to verify the integrity */
188 static FILE *DB_File(const char *agent, int *agent_id)
192 /* Find file pointer */
193 while (sdb.agent_ips[i] != NULL && i < MAX_AGENTS) {
194 if (strcmp(sdb.agent_ips[i], agent) == 0) {
195 /* Point to the beginning of the file */
196 fseek(sdb.agent_fps[i], 0, SEEK_SET);
198 return (sdb.agent_fps[i]);
204 /* If here, our agent wasn't found */
205 if (i == MAX_AGENTS) {
206 merror("%s: Unable to open integrity file. Increase MAX_AGENTS.", ARGV0);
210 os_strdup(agent, sdb.agent_ips[i]);
213 snprintf(sdb.buf, OS_FLSIZE , "%s/%s", SYSCHECK_DIR, agent);
215 /* r+ to read and write. Do not truncate */
216 sdb.agent_fps[i] = fopen(sdb.buf, "r+");
217 if (!sdb.agent_fps[i]) {
218 /* Try opening with a w flag, file probably does not exist */
219 sdb.agent_fps[i] = fopen(sdb.buf, "w");
220 if (sdb.agent_fps[i]) {
221 fclose(sdb.agent_fps[i]);
222 sdb.agent_fps[i] = fopen(sdb.buf, "r+");
227 if (!sdb.agent_fps[i]) {
228 merror("%s: Unable to open '%s'", ARGV0, sdb.buf);
230 free(sdb.agent_ips[i]);
231 sdb.agent_ips[i] = NULL;
235 /* Return the opened pointer (the beginning of it) */
236 fseek(sdb.agent_fps[i], 0, SEEK_SET);
239 /* Check if the agent was completed */
240 if (__iscompleted(agent)) {
241 sdb.agent_cp[i][0] = '1';
244 return (sdb.agent_fps[i]);
247 /* Search the DB for any entry related to the file being received */
248 static int DB_Search(const char *f_name, const char *c_sum, Eventinfo *lf)
259 /* Expose filename variable for active response */
260 os_strdup(f_name, lf->filename);
265 fp = DB_File(lf->location, &agent_id);
267 merror("%s: Error handling integrity database.", ARGV0);
273 /* Read the integrity file and search for a possible entry */
274 if (fgetpos(fp, &sdb.init_pos) == -1) {
275 merror("%s: Error handling integrity database (fgetpos).", ARGV0);
279 /* Loop over the file */
280 while (fgets(sdb.buf, OS_MAXSTR, fp) != NULL) {
281 /* Ignore blank lines and lines with a comment */
282 if (sdb.buf[0] == '\n' || sdb.buf[0] == '#') {
283 fgetpos(fp, &sdb.init_pos); /* Get next location */
288 saved_name = strchr(sdb.buf, ' ');
289 if (saved_name == NULL) {
290 merror("%s: Invalid integrity message in the database.", ARGV0);
291 fgetpos(fp, &sdb.init_pos); /* Get next location */
297 /* New format - with a timestamp */
298 if (*saved_name == '!') {
299 saved_name = strchr(saved_name, ' ');
300 if (saved_name == NULL) {
301 merror("%s: Invalid integrity message in the database", ARGV0);
302 fgetpos(fp, &sdb.init_pos); /* Get next location */
308 /* Remove newline from saved_name */
309 sn_size = strlen(saved_name);
311 if (saved_name[sn_size] == '\n') {
312 saved_name[sn_size] = '\0';
315 /* If name is different, go to next one */
316 if (strcmp(f_name, saved_name) != 0) {
317 /* Save current location */
318 fgetpos(fp, &sdb.init_pos);
324 /* First three bytes are for frequency check */
327 /* Checksum match, we can just return and keep going */
328 if (strcmp(saved_sum, c_sum) == 0) {
336 /* If we reached here, the checksum of the file has changed */
337 if (saved_sum[-3] == '!') {
339 if (saved_sum[-2] == '!') {
341 if (saved_sum[-1] == '!') {
343 } else if (saved_sum[-1] == '?') {
349 /* Check the number of changes */
350 if (!Config.syscheck_auto_ignore) {
351 sdb.syscheck_dec->id = sdb.id1;
355 sdb.syscheck_dec->id = sdb.id1;
359 sdb.syscheck_dec->id = sdb.id2;
363 sdb.syscheck_dec->id = sdb.id3;
373 /* Add new checksum to the database */
374 /* Commenting the file entry and adding a new one later */
375 if (fsetpos(fp, &sdb.init_pos)) {
376 merror("%s: Error handling integrity database (fsetpos).", ARGV0);
381 /* Add the new entry at the end of the file */
382 fseek(fp, 0, SEEK_END);
383 fprintf(fp, "%c%c%c%s !%ld %s\n",
386 p == 2 ? '!' : (p > 2) ? '?' : '+',
393 if (c_sum[0] == '-' && c_sum[1] == '1') {
394 sdb.syscheck_dec->id = sdb.idd;
395 snprintf(sdb.comment, OS_MAXSTR,
396 "File '%.756s' was deleted. Unable to retrieve "
397 "checksum.", f_name);
400 /* If file was re-added, do not compare changes */
401 else if (saved_sum[0] == '-' && saved_sum[1] == '1') {
402 sdb.syscheck_dec->id = sdb.idn;
403 snprintf(sdb.comment, OS_MAXSTR,
404 "File '%.756s' was re-added.", f_name);
408 int oldperm = 0, newperm = 0;
410 /* Provide more info about the file change */
411 const char *oldsize = NULL, *newsize = NULL;
412 char *olduid = NULL, *newuid = NULL;
413 char *c_oldperm = NULL, *c_newperm = NULL;
414 char *oldgid = NULL, *newgid = NULL;
415 char *oldmd5 = NULL, *newmd5 = NULL;
416 char *oldsha1 = NULL, *newsha1 = NULL;
421 c_oldperm = strchr(saved_sum, ':');
422 c_newperm = strchr(c_sum, ':');
424 /* Get old/new permissions */
425 if (c_oldperm && c_newperm) {
432 /* Get old/new uid/gid */
433 olduid = strchr(c_oldperm, ':');
434 newuid = strchr(c_newperm, ':');
436 if (olduid && newuid) {
442 oldgid = strchr(olduid, ':');
443 newgid = strchr(newuid, ':');
445 if (oldgid && newgid) {
452 oldmd5 = strchr(oldgid, ':');
453 newmd5 = strchr(newgid, ':');
455 if (oldmd5 && newmd5) {
462 oldsha1 = strchr(oldmd5, ':');
463 newsha1 = strchr(newmd5, ':');
465 if (oldsha1 && newsha1) {
476 /* Get integer values */
477 if (c_newperm && c_oldperm) {
478 newperm = atoi(c_newperm);
479 oldperm = atoi(c_oldperm);
482 /* Generate size message */
483 if (!oldsize || !newsize || strcmp(oldsize, newsize) == 0) {
486 snprintf(sdb.size, OS_FLSIZE,
487 "Size changed from '%s' to '%s'\n",
490 os_strdup(oldsize, lf->size_before);
491 os_strdup(newsize, lf->size_after);
494 /* Permission message */
495 if (oldperm == newperm) {
497 } else if (oldperm > 0 && newperm > 0) {
501 strncpy(opstr, agent_file_perm(c_oldperm), sizeof(opstr) - 1);
502 strncpy(npstr, agent_file_perm(c_newperm), sizeof(npstr) - 1);
504 snprintf(sdb.perm, OS_FLSIZE, "Permissions changed from "
505 "'%9.9s' to '%9.9s'\n", opstr, npstr);
507 lf->perm_before = oldperm;
508 lf->perm_after = newperm;
511 /* Ownership message */
512 if (!newuid || !olduid || strcmp(newuid, olduid) == 0) {
515 snprintf(sdb.owner, OS_FLSIZE, "Ownership was '%s', "
520 os_strdup(olduid, lf->owner_before);
521 os_strdup(newuid, lf->owner_after);
524 /* Group ownership message */
525 if (!newgid || !oldgid || strcmp(newgid, oldgid) == 0) {
526 sdb.gowner[0] = '\0';
528 snprintf(sdb.gowner, OS_FLSIZE, "Group ownership was '%s', "
531 os_strdup(oldgid, lf->gowner_before);
532 os_strdup(newgid, lf->gowner_after);
536 if (!newmd5 || !oldmd5 || strcmp(newmd5, oldmd5) == 0) {
539 snprintf(sdb.md5, OS_FLSIZE, "Old md5sum was: '%s'\n"
540 "New md5sum is : '%s'\n",
542 os_strdup(oldmd5, lf->md5_before);
543 os_strdup(newmd5, lf->md5_after);
547 if (!newsha1 || !oldsha1 || strcmp(newsha1, oldsha1) == 0) {
550 snprintf(sdb.sha1, OS_FLSIZE, "Old sha1sum was: '%s'\n"
551 "New sha1sum is : '%s'\n",
553 os_strdup(oldsha1, lf->sha1_before);
554 os_strdup(newsha1, lf->sha1_after);
557 /* Provide information about the file */
558 snprintf(sdb.comment, OS_MAXSTR, "Integrity checksum changed for: "
574 lf->data == NULL ? "" : "What changed:\n",
575 lf->data == NULL ? "" : lf->data
579 /* Create a new log message */
581 os_strdup(sdb.comment, lf->full_log);
582 lf->log = lf->full_log;
586 lf->decoder_info = sdb.syscheck_dec;
592 /* If we reach here, this file is not present in our database */
593 fseek(fp, 0, SEEK_END);
594 fprintf(fp, "+++%s !%ld %s\n", c_sum, (long int)lf->time, f_name);
597 /* Alert if configured to notify on new files */
598 /* TODO: debugging this - Scott */
599 /* if ((Config.syscheck_alert_new == 1) && (DB_IsCompleted(agent_id))) { */
600 if (Config.syscheck_alert_new == 1) {
601 sdb.syscheck_dec->id = sdb.idn;
603 /* New file message */
604 snprintf(sdb.comment, OS_MAXSTR,
606 "added to the file system.", f_name);
609 /* Create a new log message */
611 os_strdup(sdb.comment, lf->full_log);
612 lf->log = lf->full_log;
615 lf->decoder_info = sdb.syscheck_dec;
625 /* Special decoder for syscheck
626 * Not using the default decoding lib for simplicity
627 * and to be less resource intensive
629 int DecodeSyscheck(Eventinfo *lf)
634 #ifdef SQLITE_ENABLED
636 char stmt[OS_MAXSTR + 1];
641 #endif // SQLITE_ENABLED
643 /* Every syscheck message must be in the following format:
646 f_name = strchr(lf->log, ' ');
647 if (f_name == NULL) {
648 /* If we don't have a valid syscheck message, it may be
649 * a database completed message
651 if (strcmp(lf->log, HC_SK_DB_COMPLETED) == 0) {
656 merror(SK_INV_MSG, ARGV0);
660 /* Zero to get the check sum */
665 lf->data = strchr(f_name, '\n');
673 /* Check if file is supposed to be ignored */
674 if (Config.syscheck_ignore) {
675 char **ff_ig = Config.syscheck_ignore;
678 if (strncasecmp(*ff_ig, f_name, strlen(*ff_ig)) == 0) {
687 /* Checksum is at the beginning of the log */
690 /* Extract the MD5 hash and search for it in the allowlist
692 * 0:0:0:0:78f5c869675b1d09ddad870adad073f9:bd6c8d7a58b462aac86475e59af0e22954039c50
694 #ifdef SQLITE_ENABLED
695 if (Config.md5_allowlist) {
696 extern sqlite3 *conn;
697 if ((p = extract_token(c_sum, ":", 4))) {
698 if (!validate_md5(p)) { /* Never trust input from other origin */
699 merror("%s: Not a valid MD5 hash: '%s'", ARGV0, p);
702 debug1("%s: Checking MD5 '%s' in %s", ARGV0, p, Config.md5_allowlist);
703 sprintf(stmt, "select md5sum from files where md5sum = \"%s\"", p);
704 error = sqlite3_prepare_v2(conn, stmt, 1000, &res, &tail);
705 if (error == SQLITE_OK) {
706 while (sqlite3_step(res) == SQLITE_ROW) {
710 sqlite3_finalize(res);
711 //sqlite3_close(conn);
712 merror(MD5_NOT_CHECKED, ARGV0, p);
716 sqlite3_finalize(res);
722 /* Search for file changes */
723 return (DB_Search(f_name, c_sum, lf));