new upstream release (3.3.0); modify package compatibility for Stretch
[ossec-hids.git] / src / syscheckd / seechanges.c
1 /* Copyright (C) 2009 Trend Micro Inc.
2  * All rights reserved.
3  *
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
7  * Foundation
8  */
9
10 #include "shared.h"
11 #include "os_crypto/md5/md5_op.h"
12 #include "syscheck.h"
13
14 /* Prototypes */
15 static char *gen_diff_alert(const char *filename, time_t alert_diff_time) __attribute__((nonnull));
16 static int seechanges_dupfile(const char *old, const char *current) __attribute__((nonnull));
17 static int seechanges_createpath(const char *filename) __attribute__((nonnull));
18
19 #ifdef USE_MAGIC
20 #include <magic.h>
21
22 /* Global variables */
23 extern magic_t magic_cookie;
24
25
26 int is_text(magic_t cookie, const void *buf, size_t len)
27 {
28     const char *magic = magic_buffer(cookie, buf, len);
29
30     if (!magic) {
31         const char *err = magic_error(cookie);
32         merror("%s: ERROR: magic_buffer: %s", ARGV0, err ? err : "unknown");
33         return (1); // TODO default to true?
34     } else {
35         if (strncmp(magic, "text/", 5) == 0) {
36             return (1);
37         }
38     }
39
40     return (0);
41 }
42 #endif
43
44 /* Return TRUE if the file name match one of the ``nodiff`` entries.
45    Return FALSE otherwise */
46 int is_nodiff(const char *filename){
47     if (syscheck.nodiff){
48         int i;
49         for (i = 0; syscheck.nodiff[i] != NULL; i++){
50             if (strncasecmp(syscheck.nodiff[i], filename,
51                             strlen(syscheck.nodiff[i])) == 0) {
52                 return (TRUE);
53             }
54         }
55     }
56     if (syscheck.nodiff_regex) {
57         int i;
58         for (i = 0; syscheck.nodiff_regex[i] != NULL; i++) {
59             if (OSMatch_Execute(filename, strlen(filename),
60                                 syscheck.nodiff_regex[i])) {
61                  return (TRUE);
62             }
63         }
64     }
65     return (FALSE);
66 }
67
68 /* Generate diffs alerts */
69 static char *gen_diff_alert(const char *filename, time_t alert_diff_time)
70 {
71     size_t n = 0;
72     FILE *fp;
73     char *tmp_str;
74     char buf[OS_MAXSTR + 1];
75     char diff_alert[OS_MAXSTR + 1];
76
77     buf[OS_MAXSTR] = '\0';
78     diff_alert[OS_MAXSTR] = '\0';
79
80     snprintf(buf, OS_MAXSTR, "%s/local/%s/diff.%d",
81              DIFF_DIR_PATH, filename,  (int)alert_diff_time);
82
83     fp = fopen(buf, "r");
84     if (!fp) {
85         merror("%s: ERROR: Unable to generate diff alert.", ARGV0);
86         return (NULL);
87     }
88
89     n = fread(buf, 1, 4096 - 1, fp);
90     if (n <= 0) {
91         merror("%s: ERROR: Unable to generate diff alert (fread).", ARGV0);
92         fclose(fp);
93         return (NULL);
94     } else if (n >= 4000) {
95         /* Clear the last newline */
96         buf[n] = '\0';
97         tmp_str = strrchr(buf, '\n');
98         if (tmp_str) {
99             *tmp_str = '\0';
100         } else {
101             /* Weird diff with only one large line */
102             buf[256] = '\0';
103         }
104     } else {
105         buf[n] = '\0';
106     }
107
108     n = 0;
109
110     /* Get up to 20 line changes */
111     tmp_str = buf;
112
113     while (tmp_str && (*tmp_str != '\0')) {
114         tmp_str = strchr(tmp_str, '\n');
115         if (!tmp_str) {
116             break;
117         } else if (n >= 19) {
118             *tmp_str = '\0';
119             break;
120         }
121         n++;
122         tmp_str++;
123     }
124
125     /* Create alert */
126     snprintf(diff_alert, 4096 - 1, "%s%s",
127              buf, n >= 19 ?
128              "\nMore changes.." :
129              "");
130
131     fclose(fp);
132     return (strdup(diff_alert));
133 }
134
135 static int seechanges_dupfile(const char *old, const char *current)
136 {
137     size_t n;
138     FILE *fpr;
139     FILE *fpw;
140     unsigned char buf[2048 + 1];
141
142     buf[2048] = '\0';
143
144     fpr = fopen(old, "r");
145     if (!fpr) {
146         return (0);
147     }
148
149     fpw = fopen(current, "w");
150     if (!fpw) {
151         fclose(fpr);
152         return (0);
153     }
154
155     n = fread(buf, 1, 2048, fpr);
156 #ifdef USE_MAGIC
157     if (is_text(magic_cookie, buf, n) == 0) {
158         goto cleanup;
159     }
160 #endif
161
162     do {
163         buf[n] = '\0';
164         fwrite(buf, n, 1, fpw);
165     } while ((n = fread(buf, 1, 2048, fpr)) > 0);
166
167 #ifdef USE_MAGIC
168 cleanup:
169 #endif
170     fclose(fpr);
171     fclose(fpw);
172     return (1);
173 }
174
175 static int seechanges_createpath(const char *filename)
176 {
177     char *buffer = NULL;
178     char *tmpstr = NULL;
179     char *newdir = NULL;
180
181     os_strdup(filename, buffer);
182     newdir = buffer;
183     tmpstr = strchr(buffer + 1, '/');
184     if (!tmpstr) {
185         merror("%s: ERROR: Invalid path name: '%s'", ARGV0, filename);
186         free(buffer);
187         return (0);
188     }
189     *tmpstr = '\0';
190     tmpstr++;
191
192     while (1) {
193         if (IsDir(newdir) != 0) {
194 #ifndef WIN32
195             if (mkdir(newdir, 0770) == -1)
196 #else
197             if (mkdir(newdir) == -1)
198 #endif
199             {
200                 merror(MKDIR_ERROR, ARGV0, newdir, errno, strerror(errno));
201                 free(buffer);
202                 return (0);
203             }
204         }
205
206         if (*tmpstr == '\0') {
207             break;
208         }
209
210         tmpstr[-1] = '/';
211         tmpstr = strchr(tmpstr, '/');
212         if (!tmpstr) {
213             break;
214         }
215         *tmpstr = '\0';
216         tmpstr++;
217     }
218
219     free(buffer);
220     return (1);
221 }
222
223 /* Check if the file has changed */
224 char *seechanges_addfile(const char *filename)
225 {
226     time_t old_date_of_change;
227     time_t new_date_of_change;
228     char old_location[OS_MAXSTR + 1];
229     char tmp_location[OS_MAXSTR + 1];
230     char diff_location[OS_MAXSTR + 1];
231     char old_tmp[OS_MAXSTR + 1];
232     char new_tmp[OS_MAXSTR + 1];
233     char diff_tmp[OS_MAXSTR + 1];
234     char diff_cmd[OS_MAXSTR + 1];
235     os_md5 md5sum_old;
236     os_md5 md5sum_new;
237     int status = -1;
238
239     old_location[OS_MAXSTR] = '\0';
240     tmp_location[OS_MAXSTR] = '\0';
241     diff_location[OS_MAXSTR] = '\0';
242     old_tmp[OS_MAXSTR] = '\0';
243     new_tmp[OS_MAXSTR] = '\0';
244     diff_tmp[OS_MAXSTR] = '\0';
245     diff_cmd[OS_MAXSTR] = '\0';
246     md5sum_new[0] = '\0';
247     md5sum_old[0] = '\0';
248
249     snprintf(
250         old_location,
251         OS_MAXSTR,
252         "%s/local/%s/%s",
253         DIFF_DIR_PATH,
254         filename + 1,
255         DIFF_LAST_FILE
256     );
257
258     /* If the file is not there, rename new location to last location */
259     if (OS_MD5_File(old_location, md5sum_old, OS_BINARY) != 0) {
260         seechanges_createpath(old_location);
261         if (seechanges_dupfile(filename, old_location) != 1) {
262             merror(RENAME_ERROR, ARGV0, filename, old_location, errno, strerror(errno));
263         }
264         return (NULL);
265     }
266
267     /* Get md5sum of the new file */
268     if (OS_MD5_File(filename, md5sum_new, OS_BINARY) != 0) {
269         return (NULL);
270     }
271
272     /* If they match, keep the old file and remove the new */
273     if (strcmp(md5sum_new, md5sum_old) == 0) {
274         return (NULL);
275     }
276
277     /* Save the old file at timestamp and rename new to last */
278     old_date_of_change = File_DateofChange(old_location);
279
280     snprintf(
281         tmp_location,
282         OS_MAXSTR,
283         "%s/local/%s/state.%d",
284         DIFF_DIR_PATH,
285         filename + 1,
286         (int)old_date_of_change
287     );
288
289     /* Saving the old file at timestamp and renaming new to last. */
290     old_date_of_change = File_DateofChange(old_location);
291
292     snprintf(
293         tmp_location,
294         sizeof(tmp_location),
295         "%s/local/%s/state.%d",
296         DIFF_DIR_PATH,
297         filename + 1,
298        (int)old_date_of_change
299     );
300
301
302     if((rename(old_location, tmp_location)) < 0) {
303         merror("%s: ERROR rename of %s failed: %s", ARGV0, old_location, strerror(errno));
304     }
305     if(seechanges_dupfile(filename, old_location) != 1)
306     {
307         merror("%s: ERROR: Unable to create snapshot for %s",ARGV0, filename);
308         return(NULL);
309     }
310
311     if (seechanges_dupfile(filename, old_location) != 1) {
312         merror("%s: ERROR: Unable to create snapshot for %s", ARGV0, filename);
313         return (NULL);
314     }
315
316     new_date_of_change = File_DateofChange(old_location);
317
318     /* Create file names */
319     snprintf(
320         old_tmp,
321         OS_MAXSTR,
322         "%s/%s/syscheck-changes-%s-%d",
323         DEFAULTDIR,
324         TMP_DIR,
325         md5sum_old,
326         (int)old_date_of_change
327     );
328
329     snprintf(
330         new_tmp,
331         OS_MAXSTR,
332         "%s/%s/syscheck-changes-%s-%d",
333         DEFAULTDIR,
334         TMP_DIR,
335         md5sum_new,
336         (int)new_date_of_change
337     );
338
339     snprintf(
340         diff_tmp,
341         OS_MAXSTR,
342         "%s/%s/syscheck-changes-%s-%d-%s-%d",
343         DEFAULTDIR,
344         TMP_DIR,
345         md5sum_old,
346         (int)old_date_of_change,
347         md5sum_new,
348         (int)new_date_of_change
349     );
350     /* Create diff location */
351     snprintf(
352         diff_location,
353         OS_MAXSTR,
354         "%s/local/%s/diff.%d",
355         DIFF_DIR_PATH,
356         filename + 1,
357         (int)new_date_of_change
358     );
359
360     /* Create symlinks */
361     if (symlink(old_location, old_tmp) == -1) {
362         merror(LINK_ERROR, ARGV0, old_location, old_tmp, errno, strerror(errno));
363         goto cleanup;
364     }
365
366     if (symlink(tmp_location, new_tmp) == -1) {
367         merror(LINK_ERROR, ARGV0, tmp_location, new_tmp, errno, strerror(errno));
368         goto cleanup;
369     }
370
371     if (symlink(diff_location, diff_tmp) == -1) {
372         merror(LINK_ERROR, ARGV0, diff_location, diff_tmp, errno, strerror(errno));
373         goto cleanup;
374     }
375
376     if (is_nodiff((filename))) {
377         /* Dont leak sensible data with a diff hanging around */
378         FILE *fdiff;
379         char* nodiff_message = "<Diff truncated because nodiff option>";
380         fdiff = fopen(diff_location, "w");
381         if (!fdiff){
382             merror("%s: ERROR: Unable to open file for writing `%s`", ARGV0, diff_location);
383             goto cleanup;
384         }
385         fwrite(nodiff_message, strlen(nodiff_message) + 1, 1, fdiff);
386         fclose(fdiff);
387         /* Success */
388         status = 0;
389     } else {
390         /* OK, run diff */
391         snprintf(
392             diff_cmd,
393             2048,
394             "diff \"%s\" \"%s\" > \"%s\" 2> /dev/null",
395             new_tmp,
396             old_tmp,
397             diff_tmp
398         );
399
400         if (system(diff_cmd) != 256) {
401             merror("%s: ERROR: Unable to run `%s`", ARGV0, diff_cmd);
402             goto cleanup;
403         }
404
405         /* Success */
406         status = 0;
407     };
408
409 cleanup:
410     unlink(old_tmp);
411     unlink(new_tmp);
412     unlink(diff_tmp);
413
414     if (status == -1)
415         return (NULL);
416
417     /* Generate alert */
418     return (gen_diff_alert(filename, new_date_of_change));
419 }