new upstream release (3.3.0); modify package compatibility for Stretch
[ossec-hids.git] / src / syscheckd / run_realtime.c
1 /* Copyright (C) 2009 Trend Micro Inc.
2  * All right 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 <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15 #include <limits.h>
16 #include <errno.h>
17
18 #ifdef WIN32
19 #define sleep(x) Sleep(x * 1000)
20 #endif
21
22 #ifdef INOTIFY_ENABLED
23 #include <sys/inotify.h>
24 #define OS_SIZE_6144    6144
25 #define OS_MAXSTR       OS_SIZE_6144    /* Size for logs, sockets, etc */
26 #else
27 #include "shared.h"
28 #endif
29
30 #include "fs_op.h"
31 #include "hash_op.h"
32 #include "debug_op.h"
33 #include "syscheck.h"
34 #include "error_messages/error_messages.h"
35
36 /* Prototypes */
37 int realtime_checksumfile(const char *file_name) __attribute__((nonnull));
38
39
40 /* Checksum of the realtime file being monitored */
41 int realtime_checksumfile(const char *file_name)
42 {
43     char *buf;
44
45     buf = (char *) OSHash_Get(syscheck.fp, file_name);
46     if (buf != NULL) {
47         char c_sum[256 + 2];
48
49         c_sum[0] = '\0';
50         c_sum[255] = '\0';
51
52         /* If it returns < 0, we have already alerted */
53         if (c_read_file(file_name, buf, c_sum) < 0) {
54             return (0);
55         }
56
57         if (strcmp(c_sum, buf + 6) != 0) {
58             char alert_msg[OS_MAXSTR + 1];
59
60             alert_msg[OS_MAXSTR] = '\0';
61
62             #ifdef WIN32
63             snprintf(alert_msg, 912, "%s %s", c_sum, file_name);
64             #else
65             char *fullalert = NULL;
66
67             if (buf[5] == 's' || buf[5] == 'n') {
68                 fullalert = seechanges_addfile(file_name);
69                 if (fullalert) {
70                     snprintf(alert_msg, OS_MAXSTR, "%s %s\n%s", c_sum, file_name, fullalert);
71                     free(fullalert);
72                     fullalert = NULL;
73                 } else {
74                     snprintf(alert_msg, 912, "%s %s", c_sum, file_name);
75                 }
76             } else {
77                 snprintf(alert_msg, 912, "%s %s", c_sum, file_name);
78             }
79             #endif
80             send_syscheck_msg(alert_msg);
81
82             return (1);
83         }
84         return (0);
85     } else {
86         /* New file */
87         char *c;
88         int i;
89         buf = strdup(file_name);
90
91         /* Find container directory */
92
93         while (c = strrchr(buf, '/'), c && c != buf) {
94             *c = '\0';
95
96             for (i = 0; syscheck.dir[i]; i++) {
97                 if (strcmp(syscheck.dir[i], buf) == 0) {
98                     debug1("%s: DEBUG: Scanning new file '%s' with options for directory '%s'.", ARGV0, file_name, buf);
99                     read_dir(file_name, syscheck.opts[i], syscheck.filerestrict[i]);
100                     break;
101                 }
102             }
103
104             if (syscheck.dir[i]) {
105                 break;
106             }
107         }
108
109         free(buf);
110     }
111
112     return (0);
113 }
114
115 #ifdef INOTIFY_ENABLED
116 #include <sys/inotify.h>
117
118 #define REALTIME_MONITOR_FLAGS  IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE|IN_DELETE_SELF
119 #define REALTIME_EVENT_SIZE     (sizeof (struct inotify_event))
120 #define REALTIME_EVENT_BUFFER   (2048 * (REALTIME_EVENT_SIZE + 16))
121
122 /* Start real time monitoring using inotify */
123 int realtime_start()
124 {
125     verbose("%s: INFO: Initializing real time file monitoring (not started).", ARGV0);
126
127     syscheck.realtime = (rtfim *) calloc(1, sizeof(rtfim));
128     if (syscheck.realtime == NULL) {
129         ErrorExit(MEM_ERROR, ARGV0, errno, strerror(errno));
130     }
131     syscheck.realtime->dirtb = OSHash_Create();
132     syscheck.realtime->fd = -1;
133
134 #ifdef INOTIFY_ENABLED
135     syscheck.realtime->fd = inotify_init();
136     if (syscheck.realtime->fd < 0) {
137         merror("%s: ERROR: Unable to initialize inotify.", ARGV0);
138         return (-1);
139     }
140 #endif
141
142     return (1);
143 }
144
145 /* Add a directory to real time checking */
146 int realtime_adddir(const char *dir)
147 {
148     if (!syscheck.realtime) {
149         realtime_start();
150     }
151
152     /* Check if it is ready to use */
153     if (syscheck.realtime->fd < 0) {
154         return (-1);
155     } else {
156         int wd = 0;
157
158         if(syscheck.skip_nfs) {
159             short is_nfs = IsNFS(dir);
160             if( is_nfs == 1 ) {
161                 merror("%s: ERROR: %s NFS Directories do not support iNotify.", ARGV0, dir);
162                 return(-1);
163             }
164             else {
165                 debug2("%s: DEBUG: syscheck.skip_nfs=%d, %s::is_nfs=%d", ARGV0, syscheck.skip_nfs, dir, is_nfs);
166             }
167         }
168
169         wd = inotify_add_watch(syscheck.realtime->fd,
170                                dir,
171                                REALTIME_MONITOR_FLAGS);
172         if (wd < 0) {
173             merror("%s: ERROR: Unable to add directory to real time "
174                    "monitoring: '%s'. %d %s", ARGV0, dir, wd, strerror(errno));
175         } else {
176             char wdchar[32 + 1];
177             wdchar[32] = '\0';
178             snprintf(wdchar, 32, "%d", wd);
179
180             /* Entry not present */
181             if (!OSHash_Get(syscheck.realtime->dirtb, wdchar)) {
182                 char *ndir;
183
184                 ndir = strdup(dir);
185                 if (ndir == NULL) {
186                     ErrorExit("%s: ERROR: Out of memory. Exiting.", ARGV0);
187                 }
188
189                 OSHash_Add(syscheck.realtime->dirtb, wdchar, ndir);
190                 debug1("%s: DEBUG: Directory added for real time monitoring: "
191                        "'%s'.", ARGV0, ndir);
192             }
193         }
194     }
195
196     return (1);
197 }
198
199 /* Process events in the real time queue */
200 int realtime_process()
201 {
202     ssize_t len;
203     size_t i = 0;
204     char buf[REALTIME_EVENT_BUFFER + 1];
205     struct inotify_event *event;
206
207     buf[REALTIME_EVENT_BUFFER] = '\0';
208
209     len = read(syscheck.realtime->fd, buf, REALTIME_EVENT_BUFFER);
210     if (len < 0) {
211         merror("%s: ERROR: Unable to read from real time buffer.", ARGV0);
212     } else if (len > 0) {
213         buf[len] = '\0';
214         while (i < (size_t) len) {
215             event = (struct inotify_event *) (void *) &buf[i];
216
217             if (event->len) {
218                 char wdchar[32 + 1];
219                 char final_name[MAX_LINE + 1];
220
221                 wdchar[32] = '\0';
222                 final_name[MAX_LINE] = '\0';
223
224                 snprintf(wdchar, 32, "%d", event->wd);
225
226                 snprintf(final_name, MAX_LINE, "%s/%s",
227                          (char *)OSHash_Get(syscheck.realtime->dirtb, wdchar),
228                          event->name);
229                 /* Need a sleep here to avoid triggering on vim edits
230                  * (and finding the file removed)
231                  */
232                 sleep(1);
233
234                 realtime_checksumfile(final_name);
235             }
236
237             i += REALTIME_EVENT_SIZE + event->len;
238         }
239     }
240
241     return (0);
242 }
243
244 #elif defined(WIN32)
245 typedef struct _win32rtfim {
246     HANDLE h;
247     OVERLAPPED overlap;
248
249     char *dir;
250     TCHAR buffer[1228800];
251 } win32rtfim;
252
253 int realtime_win32read(win32rtfim *rtlocald);
254
255 void CALLBACK RTCallBack(DWORD dwerror, DWORD dwBytes, LPOVERLAPPED overlap)
256 {
257     int lcount;
258     size_t offset = 0;
259     char *ptfile;
260     char wdchar[32 + 1];
261     char final_path[MAX_LINE + 1];
262     win32rtfim *rtlocald;
263     PFILE_NOTIFY_INFORMATION pinfo;
264     TCHAR finalfile[MAX_PATH];
265
266     if (dwBytes == 0) {
267         merror("%s: ERROR: real time call back called, but 0 bytes.", ARGV0);
268         rtlocald = OSHash_Get(syscheck.realtime->dirtb, "0");
269         if(rtlocald)
270             realtime_win32read(rtlocald);
271
272         return;
273     }
274
275     if (dwerror != ERROR_SUCCESS) {
276         merror("%s: ERROR: real time call back called, but error is set.",
277                ARGV0);
278         return;
279     }
280
281     /* Get hash to parse the data */
282     wdchar[32] = '\0';
283     snprintf(wdchar, 32, "%d", (int)overlap->Offset);
284     rtlocald = OSHash_Get(syscheck.realtime->dirtb, wdchar);
285     if (rtlocald == NULL) {
286         merror("%s: ERROR: real time call back called, but hash is empty.",
287                ARGV0);
288         return;
289     }
290
291     do {
292         pinfo = (PFILE_NOTIFY_INFORMATION) &rtlocald->buffer[offset];
293         offset += pinfo->NextEntryOffset;
294
295         lcount = WideCharToMultiByte(CP_ACP, 0, pinfo->FileName,
296                                      pinfo->FileNameLength / sizeof(WCHAR),
297                                      finalfile, MAX_PATH - 1, NULL, NULL);
298         finalfile[lcount] = TEXT('\0');
299
300         /* Change forward slashes to backslashes on finalfile */
301         ptfile = strchr(finalfile, '\\');
302         while (ptfile) {
303             *ptfile = '/';
304             ptfile++;
305
306             ptfile = strchr(ptfile, '\\');
307         }
308
309         final_path[MAX_LINE] = '\0';
310         snprintf(final_path, MAX_LINE, "%s/%s", rtlocald->dir, finalfile);
311
312         /* Check the change */
313         realtime_checksumfile(final_path);
314     } while (pinfo->NextEntryOffset != 0);
315
316     realtime_win32read(rtlocald);
317
318     return;
319 }
320
321 int realtime_start()
322 {
323     verbose("%s: INFO: Initializing real time file monitoring (not started).", ARGV0);
324
325     os_calloc(1, sizeof(rtfim), syscheck.realtime);
326     syscheck.realtime->dirtb = (void *)OSHash_Create();
327     syscheck.realtime->fd = -1;
328     syscheck.realtime->evt = CreateEvent(NULL, TRUE, FALSE, NULL);
329
330     return (0);
331 }
332
333 int realtime_win32read(win32rtfim *rtlocald)
334 {
335     int rc;
336
337     rc = ReadDirectoryChangesW(rtlocald->h,
338                                rtlocald->buffer,
339                                sizeof(rtlocald->buffer) / sizeof(TCHAR),
340                                TRUE,
341                                FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SECURITY,
342                                0,
343                                &rtlocald->overlap,
344                                RTCallBack);
345     if (rc == 0) {
346         merror("%s: ERROR: Unable to set directory for monitoring: %s",
347                ARGV0, rtlocald->dir);
348         sleep(2);
349     }
350
351     return (0);
352 }
353
354 int realtime_adddir(const char *dir)
355 {
356     char wdchar[32 + 1];
357     win32rtfim *rtlocald;
358
359     if (!syscheck.realtime) {
360         realtime_start();
361     }
362
363     /* Maximum limit for realtime on Windows */
364     if (syscheck.realtime->fd > 256) {
365         merror("%s: ERROR: Unable to add directory to real time "
366                "monitoring: '%s' - Maximum size permitted.", ARGV0, dir);
367         return (0);
368     }
369
370     os_calloc(1, sizeof(win32rtfim), rtlocald);
371
372     rtlocald->h = CreateFile(dir,
373                              FILE_LIST_DIRECTORY,
374                              FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
375                              NULL,
376                              OPEN_EXISTING,
377                              FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
378                              NULL);
379
380
381     if (rtlocald->h == INVALID_HANDLE_VALUE ||
382             rtlocald->h == NULL) {
383         free(rtlocald);
384         rtlocald = NULL;
385         merror("%s: ERROR: Unable to add directory to real time "
386                "monitoring: '%s'.", ARGV0, dir);
387         return (0);
388     }
389
390     rtlocald->overlap.Offset = ++syscheck.realtime->fd;
391
392     /* Set key for hash */
393     wdchar[32] = '\0';
394     snprintf(wdchar, 32, "%d", (int)rtlocald->overlap.Offset);
395
396     if (OSHash_Get(syscheck.realtime->dirtb, wdchar)) {
397         merror("%s: ERROR: Entry already in the real time hash: %s",
398                ARGV0, wdchar);
399         CloseHandle(rtlocald->overlap.hEvent);
400         free(rtlocald);
401         rtlocald = NULL;
402         return (0);
403     }
404
405     /* Add final elements to the hash */
406     os_strdup(dir, rtlocald->dir);
407     OSHash_Add(syscheck.realtime->dirtb, strdup(wdchar), rtlocald);
408
409     /* Add directory to be monitored */
410     realtime_win32read(rtlocald);
411
412     return (1);
413 }
414
415 #else /* !WIN32 */
416
417 int realtime_start()
418 {
419     verbose("%s: ERROR: Unable to initialize real time file monitoring.", ARGV0);
420
421     return (0);
422 }
423
424 int realtime_adddir(__attribute__((unused)) const char *dir)
425 {
426     return (0);
427 }
428
429 int realtime_process()
430 {
431     return (0);
432 }
433
434 #endif /* WIN32 */
435