new upstream release (3.3.0); modify package compatibility for Stretch
[ossec-hids.git] / src / agentlessd / agentlessd.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 "agentlessd.h"
13
14 /* Prototypes */
15 static int  save_agentless_entry(const char *host, const char *script, const char *agttype);
16 static int  send_intcheck_msg(const char *script, const char *host, const char *msg);
17 static int  send_log_msg(const char *script, const char *host, const char *msg);
18 static int  gen_diff_alert(const char *host, const char *script, time_t alert_diff_time);
19 static int  check_diff_file(const char *host, const char *script);
20 static FILE *open_diff_file(const char *host, const char *script);
21 static int  run_periodic_cmd(agentlessd_entries *entry, int test_it);
22
23 /* Global variables */
24 agentlessd_config lessdc;
25
26
27 /* Save agentless entry for the control tools to gather */
28 static int save_agentless_entry(const char *host, const char *script, const char *agttype)
29 {
30     FILE *fp;
31     char sys_location[1024 + 1];
32
33     sys_location[1024] = '\0';
34     snprintf(sys_location, 1024, "%s/(%s) %s",
35              AGENTLESS_ENTRYDIRPATH, script, host);
36
37     fp = fopen(sys_location, "w");
38     if (fp) {
39         fprintf(fp, "type: %s\n", agttype);
40         fclose(fp);
41     } else {
42         merror(FOPEN_ERROR, ARGV0, sys_location, errno, strerror(errno));
43     }
44
45     return (0);
46 }
47
48 /* Send integrity checking message */
49 static int send_intcheck_msg(const char *script, const char *host, const char *msg)
50 {
51     char sys_location[1024 + 1];
52
53     sys_location[1024] = '\0';
54     snprintf(sys_location, 1024, "(%s) %s->%s", script, host, SYSCHECK);
55
56     if (SendMSG(lessdc.queue, msg, sys_location, SYSCHECK_MQ) < 0) {
57         merror(QUEUE_SEND, ARGV0);
58
59         if ((lessdc.queue = StartMQ(DEFAULTQPATH, WRITE)) < 0) {
60             ErrorExit(QUEUE_FATAL, ARGV0, DEFAULTQPATH);
61         }
62
63         /* If we reach here, we can try to send it again */
64         SendMSG(lessdc.queue, msg, sys_location, SYSCHECK_MQ);
65     }
66
67     return (0);
68 }
69
70 /* Send generic log message */
71 static int send_log_msg(const char *script, const char *host, const char *msg)
72 {
73     char sys_location[1024 + 1];
74
75     sys_location[1024] = '\0';
76     snprintf(sys_location, 1024, "(%s) %s->%s", script, host, SYSCHECK);
77
78     if (SendMSG(lessdc.queue, msg, sys_location, LOCALFILE_MQ) < 0) {
79         merror(QUEUE_SEND, ARGV0);
80         if ((lessdc.queue = StartMQ(DEFAULTQPATH, WRITE)) < 0) {
81             ErrorExit(QUEUE_FATAL, ARGV0, DEFAULTQPATH);
82         }
83
84         /* If we reach here, we can try to send it again */
85         SendMSG(lessdc.queue, msg, sys_location, LOCALFILE_MQ);
86     }
87     return (0);
88 }
89
90 /* Generate diffs alert */
91 static int gen_diff_alert(const char *host, const char *script, time_t alert_diff_time)
92 {
93     size_t n;
94     FILE *fp;
95     char *tmp_str;
96     char buf[2048 + 1];
97     char diff_alert[4096 + 1];
98
99     buf[2048] = '\0';
100     diff_alert[4096] = '\0';
101
102     snprintf(buf, 2048, "%s/%s->%s/diff.%d",
103              DIFF_DIR_PATH, host, script, (int)alert_diff_time);
104
105     fp = fopen(buf, "r");
106     if (!fp) {
107         merror("%s: ERROR: Unable to generate diff alert.", ARGV0);
108         return (0);
109     }
110
111     n = fread(buf, 1, 2048 - 1, fp);
112     if (n <= 0) {
113         merror("%s: ERROR: Unable to generate diff alert (fread).", ARGV0);
114         fclose(fp);
115         return (0);
116     } else if (n >= 2040) {
117         /* We need to clear the last newline */
118         buf[n] = '\0';
119         tmp_str = strrchr(buf, '\n');
120         if (tmp_str) {
121             *tmp_str = '\0';
122         } else {
123             /* Weird diff with only one large line */
124             buf[256] = '\0';
125         }
126     } else {
127         buf[n] = '\0';
128     }
129
130     n = 0;
131
132     /* Get up to 8 line changes */
133     tmp_str = buf;
134
135     while (tmp_str && (*tmp_str != '\0')) {
136         tmp_str = strchr(tmp_str, '\n');
137         if (!tmp_str) {
138             break;
139         } else if (n >= 7) {
140             *tmp_str = '\0';
141             break;
142         }
143         n++;
144         tmp_str++;
145     }
146
147     /* Create alert */
148     snprintf(diff_alert, 4096 - 1, "ossec: agentless: Change detected:\n%s%s",
149              buf, n >= 7 ?
150              "\nMore changes.." :
151              "");
152
153     snprintf(buf, 1024, "(%s) %s->agentless", script, host);
154
155     if (SendMSG(lessdc.queue, diff_alert, buf, LOCALFILE_MQ) < 0) {
156         merror(QUEUE_SEND, ARGV0);
157
158         if ((lessdc.queue = StartMQ(DEFAULTQPATH, WRITE)) < 0) {
159             ErrorExit(QUEUE_FATAL, ARGV0, DEFAULTQPATH);
160         }
161
162         /* If we reach here, we can try to send it again */
163         SendMSG(lessdc.queue, diff_alert, buf, LOCALFILE_MQ);
164     }
165
166     save_agentless_entry(host, script, "diff");
167
168     fclose(fp);
169     return (0);
170 }
171
172 /* Check if the file has changed */
173 static int check_diff_file(const char *host, const char *script)
174 {
175     time_t date_of_change;
176     char old_location[1024 + 1];
177     char new_location[1024 + 1];
178     char tmp_location[1024 + 1];
179     char diff_cmd[2048 + 1];
180
181     os_md5 md5sum_old;
182     os_md5 md5sum_new;
183
184     old_location[1024] = '\0';
185     new_location[1024] = '\0';
186     tmp_location[1024] = '\0';
187     diff_cmd[2048] = '\0';
188
189     snprintf(new_location, 1024, "%s/%s->%s/%s", DIFF_DIR_PATH, host, script,
190              DIFF_NEW_FILE);
191     snprintf(old_location, 1024, "%s/%s->%s/%s", DIFF_DIR_PATH, host, script,
192              DIFF_LAST_FILE);
193
194     /* If the file is not there, rename new location to last location */
195     if (OS_MD5_File(old_location, md5sum_old, OS_TEXT) != 0) {
196         if (rename(new_location, old_location) != 0) {
197             merror(RENAME_ERROR, ARGV0, new_location, old_location, errno, strerror(errno));
198         }
199         return (0);
200     }
201
202     /* Get md5sum of the new file */
203     if (OS_MD5_File(new_location, md5sum_new, OS_TEXT) != 0) {
204         merror("%s: ERROR: Invalid internal state (missing '%s').",
205                ARGV0, new_location);
206         return (0);
207     }
208
209     /* If they match, keep the old file and remove the new */
210     if (strcmp(md5sum_new, md5sum_old) == 0) {
211         unlink(new_location);
212         return (0);
213     }
214
215     /* Save the old file at timestamp and rename new to last */
216     date_of_change = File_DateofChange(old_location);
217     snprintf(tmp_location, 1024, "%s/%s->%s/state.%d", DIFF_DIR_PATH, host, script,
218              (int)date_of_change);
219
220     if (rename(old_location, tmp_location) != 0) {
221         merror(RENAME_ERROR, ARGV0, old_location, tmp_location, errno, strerror(errno));
222         return (0);
223     }
224     if (rename(new_location, old_location) != 0) {
225         merror(RENAME_ERROR, ARGV0, new_location, old_location, errno, strerror(errno));
226         return (0);
227     }
228
229     /* Run diff */
230     date_of_change = File_DateofChange(old_location);
231     snprintf(diff_cmd, 2048, "diff \"%s\" \"%s\" > \"%s/%s->%s/diff.%d\" "
232              "2>/dev/null",
233              tmp_location, old_location,
234              DIFF_DIR_PATH, host, script, (int)date_of_change);
235     if (system(diff_cmd) != 256) {
236         merror("%s: ERROR: Unable to run diff for %s->%s",
237                ARGV0,  host, script);
238         return (0);
239     }
240
241     /* Generate alert */
242     gen_diff_alert(host, script, date_of_change);
243
244     return (0);
245 }
246
247 /* Get the diff file */
248 static FILE *open_diff_file(const char *host, const char *script)
249 {
250     FILE *fp = NULL;
251     char sys_location[1024 + 1];
252
253     sys_location[1024] = '\0';
254     snprintf(sys_location, 1024, "%s/%s->%s/%s", DIFF_DIR_PATH, host, script,
255              DIFF_NEW_FILE);
256
257     fp = fopen(sys_location, "w");
258
259     /* If we can't open, try creating the directory */
260     if (!fp) {
261         snprintf(sys_location, 1024, "%s/%s->%s", DIFF_DIR_PATH, host, script);
262         if (IsDir(sys_location) == -1) {
263             if (mkdir(sys_location, 0770) == -1) {
264                 merror(MKDIR_ERROR, ARGV0, sys_location, errno, strerror(errno));
265                 return (NULL);
266             }
267         }
268
269         snprintf(sys_location, 1024, "%s/%s->%s/%s", DIFF_DIR_PATH, host,
270                  script, DIFF_NEW_FILE);
271         fp = fopen(sys_location, "w");
272         if (!fp) {
273             merror(FOPEN_ERROR, ARGV0, sys_location, errno, strerror(errno));
274             return (NULL);
275         }
276     }
277
278     return (fp);
279 }
280
281 /* Run periodic commands */
282 static int run_periodic_cmd(agentlessd_entries *entry, int test_it)
283 {
284     int i = 0;
285     char *tmp_str;
286     char buf[OS_SIZE_2048 + 1];
287     char command[OS_SIZE_1024 + 1];
288     FILE *fp;
289     FILE *fp_store = NULL;
290
291     buf[0] = '\0';
292     command[0] = '\0';
293     command[OS_SIZE_1024] = '\0';
294
295     while (entry->server[i]) {
296         /* Ignored entry */
297         if (entry->server[i][0] == '\0') {
298             i++;
299             continue;
300         }
301
302         /* We only test for the first server entry */
303         else if (test_it) {
304             int ret_code = 0;
305             snprintf(command, OS_SIZE_1024,
306                      "%s/%s test test >/dev/null 2>&1",
307                      AGENTLESSDIRPATH, entry->type);
308             ret_code = system(command);
309
310             /* Check if the test worked */
311             if (ret_code != 0) {
312                 if (ret_code == 32512) {
313                     merror("%s: ERROR: Expect command not found (or bad "
314                            "arguments) for '%s'.",
315                            ARGV0, entry->type);
316                 }
317                 merror("%s: ERROR: Test failed for '%s' (%d). Ignoring.",
318                        ARGV0, entry->type, ret_code / 256);
319                 entry->error_flag = 99;
320                 return (-1);
321             }
322
323             verbose("%s: INFO: Test passed for '%s'.", ARGV0, entry->type);
324             return (0);
325         }
326
327         if (entry->server[i][0] == 's') {
328             snprintf(command, OS_SIZE_1024, "%s/%s \"use_su\" \"%s\" %s 2>&1",
329                      AGENTLESSDIRPATH, entry->type, entry->server[i] + 1,
330                      entry->options);
331         } else if (entry->server[i][0] == 'o') {
332             snprintf(command, OS_SIZE_1024, "%s/%s \"use_sudo\" \"%s\" %s 2>&1",
333                      AGENTLESSDIRPATH, entry->type, entry->server[i] + 1,
334                      entry->options);
335         } else {
336             snprintf(command, OS_SIZE_1024, "%s/%s \"%s\" %s 2>&1",
337                      AGENTLESSDIRPATH, entry->type, entry->server[i] + 1,
338                      entry->options);
339         }
340
341         fp = popen(command, "r");
342         if (fp) {
343             while (fgets(buf, OS_SIZE_2048, fp) != NULL) {
344                 /* Remove newlines and carriage returns */
345                 tmp_str = strchr(buf, '\r');
346                 if (tmp_str) {
347                     *tmp_str = '\0';
348                 }
349                 tmp_str = strchr(buf, '\n');
350                 if (tmp_str) {
351                     *tmp_str = '\0';
352                 }
353
354                 if (strncmp(buf, "ERROR: ", 7) == 0) {
355                     merror("%s: ERROR: %s: %s: %s", ARGV0,
356                            entry->type, entry->server[i] + 1, buf + 7);
357                     entry->error_flag++;
358                     break;
359                 } else if (strncmp(buf, "INFO: ", 6) == 0) {
360                     verbose("%s: INFO: %s: %s: %s", ARGV0,
361                             entry->type, entry->server[i] + 1, buf + 6);
362                 } else if (strncmp(buf, "FWD: ", 4) == 0) {
363                     tmp_str = buf + 5;
364                     send_intcheck_msg(entry->type, entry->server[i] + 1,
365                                       tmp_str);
366                 } else if (strncmp(buf, "LOG: ", 4) == 0) {
367                     tmp_str = buf + 5;
368                     send_log_msg(entry->type, entry->server[i] + 1,
369                                  tmp_str);
370                 } else if ((entry->state & LESSD_STATE_DIFF) &&
371                            (strncmp(buf, "STORE: ", 7) == 0)) {
372                     if (fp_store) {
373                         fclose(fp_store);
374                     }
375                     fp_store = open_diff_file(entry->server[i] + 1,
376                                               entry->type);
377                 } else if (fp_store) {
378                     fprintf(fp_store, "%s\n", buf);
379                 } else {
380                     debug1("%s: DEBUG: buffer: %s", ARGV0, buf);
381                 }
382             }
383
384             if (fp_store) {
385                 fclose(fp_store);
386                 fp_store = NULL;
387
388                 check_diff_file(entry->server[i] + 1, entry->type);
389             } else {
390                 save_agentless_entry(entry->server[i] + 1,
391                                      entry->type, "syscheck");
392             }
393             pclose(fp);
394         } else {
395             merror("%s: ERROR: popen failed on '%s' for '%s'.", ARGV0,
396                    entry->type, entry->server[i] + 1);
397             entry->error_flag++;
398         }
399
400         i++;
401     }
402
403     if (fp_store) {
404         fclose(fp_store);
405     }
406
407     return (0);
408 }
409
410 /* Main agentlessd */
411 void Agentlessd()
412 {
413     time_t tm;
414     struct tm *p;
415
416     int today = 0;
417     int test_it = 1;
418
419     char str[OS_SIZE_1024 + 1];
420
421     /* Wait a few seconds to settle */
422     sleep(2);
423     memset(str, '\0', OS_SIZE_1024 + 1);
424
425     /* Get current time before starting */
426     tm = time(NULL);
427     p = localtime(&tm);
428
429     today = p->tm_mday;
430
431     /* Connect to the message queue. Exit if it fails. */
432     if ((lessdc.queue = StartMQ(DEFAULTQPATH, WRITE)) < 0) {
433         ErrorExit(QUEUE_FATAL, ARGV0, DEFAULTQUEUE);
434     }
435
436     /* Main monitor loop */
437     while (1) {
438         unsigned int i = 0;
439         tm = time(NULL);
440         p = localtime(&tm);
441
442         /* Day changed, deal with log files */
443         if (today != p->tm_mday) {
444             today = p->tm_mday;
445         }
446
447         while (lessdc.entries[i]) {
448             if (lessdc.entries[i]->error_flag >= 10) {
449                 if (lessdc.entries[i]->error_flag != 99) {
450                     merror("%s: ERROR: Too many failures for '%s'. Ignoring it.",
451                            ARGV0, lessdc.entries[i]->type);
452                     lessdc.entries[i]->error_flag = 99;
453                 }
454
455                 i++;
456                 sleep(i);
457                 continue;
458             }
459
460             /* Run the check again if the frequency has elapsed */
461             if ((lessdc.entries[i]->state & LESSD_STATE_PERIODIC) &&
462                     ((lessdc.entries[i]->current_state +
463                       lessdc.entries[i]->frequency) < tm)) {
464                 run_periodic_cmd(lessdc.entries[i], test_it);
465                 if (!test_it) {
466                     lessdc.entries[i]->current_state = tm;
467                 }
468             }
469
470             i++;
471
472             sleep(i);
473         }
474
475         /* We only check every minute */
476         test_it = 0;
477         sleep(60);
478     }
479 }
480