new upstream release (3.3.0); modify package compatibility for Stretch
[ossec-hids.git] / src / os_execd / execd.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 "shared.h"
11 #include "list_op.h"
12 #include "os_regex/os_regex.h"
13 #include "os_net/os_net.h"
14 #include "execd.h"
15
16 int repeated_offenders_timeout[] = {0, 0, 0, 0, 0, 0, 0};
17
18 #ifndef WIN32
19
20 /* Prototypes */
21 static void help_execd(void) __attribute__((noreturn));
22 static void execd_shutdown(int sig) __attribute__((noreturn));
23 static void ExecdStart(int q) __attribute__((noreturn));
24
25 /* Global variables */
26 static OSList *timeout_list;
27 static OSListNode *timeout_node;
28 static OSHash *repeated_hash;
29
30
31 /* Print help statement */
32 static void help_execd()
33 {
34     print_header();
35     print_out("  %s: -[Vhdtf] [-g group] [-c config]", ARGV0);
36     print_out("    -V          Version and license message");
37     print_out("    -h          This help message");
38     print_out("    -d          Execute in debug mode. This parameter");
39     print_out("                can be specified multiple times");
40     print_out("                to increase the debug level.");
41     print_out("    -t          Test configuration");
42     print_out("    -f          Run in foreground");
43     print_out("    -g <group>  Group to run as (default: %s)", GROUPGLOBAL);
44     print_out("    -c <config> Configuration file to use (default: %s)", DEFAULTCPATH);
45     print_out(" ");
46     exit(1);
47 }
48
49 /* Shut down execd properly */
50 static void execd_shutdown(int sig)
51 {
52     int status;
53
54     /* Remove pending active responses */
55     merror(EXEC_SHUTDOWN, ARGV0);
56
57     timeout_node = OSList_GetFirstNode(timeout_list);
58     while (timeout_node) {
59         timeout_data *list_entry;
60
61         list_entry = (timeout_data *)timeout_node->data;
62
63         ExecCmd(list_entry->command);
64         wait(&status);
65
66         /* Delete current node - already sets the pointer to next */
67         OSList_DeleteCurrentlyNode(timeout_list);
68         timeout_node = OSList_GetCurrentlyNode(timeout_list);
69     }
70
71     HandleSIG(sig);
72 }
73
74 int main(int argc, char **argv)
75 {
76     int c;
77     int test_config = 0, run_foreground = 0;
78     gid_t gid;
79     int m_queue = 0;
80
81     const char *group = GROUPGLOBAL;
82     const char *cfg = DEFAULTCPATH;
83
84     /* Set the name */
85     OS_SetName(ARGV0);
86
87     while ((c = getopt(argc, argv, "Vtdhfg:c:")) != -1) {
88         switch (c) {
89             case 'V':
90                 print_version();
91                 break;
92             case 'h':
93                 help_execd();
94                 break;
95             case 'd':
96                 nowDebug();
97                 break;
98             case 'f':
99                 run_foreground = 1;
100                 break;
101             case 'g':
102                 if (!optarg) {
103                     ErrorExit("%s: -g needs an argument.", ARGV0);
104                 }
105                 group = optarg;
106                 break;
107             case 'c':
108                 if (!optarg) {
109                     ErrorExit("%s: -c needs an argument.", ARGV0);
110                 }
111                 cfg = optarg;
112                 break;
113             case 't':
114                 test_config = 1;
115                 break;
116             default:
117                 help_execd();
118                 break;
119         }
120     }
121
122     /* Check if the group given is valid */
123     gid = Privsep_GetGroup(group);
124     if (gid == (gid_t) - 1) {
125         ErrorExit(USER_ERROR, ARGV0, "", group);
126     }
127
128     /* Privilege separation */
129     if (Privsep_SetGroup(gid) < 0) {
130         ErrorExit(SETGID_ERROR, ARGV0, group, errno, strerror(errno));
131     }
132
133     /* Read config */
134     if ((c = ExecdConfig(cfg)) < 0) {
135         ErrorExit(CONFIG_ERROR, ARGV0, cfg);
136     }
137
138     /* Exit if test_config */
139     if (test_config) {
140         exit(0);
141     }
142
143     /* Signal manipulation */
144     StartSIG2(ARGV0, execd_shutdown);
145
146     if (!run_foreground) {
147         /* Going daemon */
148         nowDaemon();
149         goDaemon();
150     }
151
152     /* Active response disabled */
153     if (c == 1) {
154         verbose(EXEC_DISABLED, ARGV0);
155         exit(0);
156     }
157
158     /* Create the PID file */
159     if (CreatePID(ARGV0, getpid()) < 0) {
160         merror(PID_ERROR, ARGV0);
161     }
162
163     /* Start exec queue */
164     if ((m_queue = StartMQ(EXECQUEUEPATH, READ)) < 0) {
165         ErrorExit(QUEUE_ERROR, ARGV0, EXECQUEUEPATH, strerror(errno));
166     }
167
168     /* Start up message */
169     verbose(STARTUP_MSG, ARGV0, (int)getpid());
170
171     /* The real daemon Now */
172     ExecdStart(m_queue);
173
174     exit(0);
175 }
176
177 #endif
178
179 /* Free the timeout entry
180  * Must be called after popping it from the timeout list
181  */
182 void FreeTimeoutEntry(timeout_data *timeout_entry)
183 {
184     char **tmp_str;
185
186     if (!timeout_entry) {
187         return;
188     }
189
190     tmp_str = timeout_entry->command;
191
192     /* Clear the command arguments */
193     if (tmp_str) {
194         while (*tmp_str) {
195             os_free(*tmp_str);
196             *tmp_str = NULL;
197             tmp_str++;
198         }
199         os_free(timeout_entry->command);
200         timeout_entry->command = NULL;
201     }
202
203     free(timeout_entry);
204 }
205
206 #ifndef WIN32
207
208 /* Main function on the execd. Does all the data receiving, etc. */
209 static void ExecdStart(int q)
210 {
211     int i, childcount = 0;
212     time_t curr_time;
213
214     char buffer[OS_MAXSTR + 1];
215     char *tmp_msg = NULL;
216     char *name;
217     char *command;
218     char *cmd_args[MAX_ARGS + 2];
219
220     /* Select */
221     fd_set fdset;
222     struct timeval socket_timeout;
223
224     /* Clear the buffer */
225     memset(buffer, '\0', OS_MAXSTR + 1);
226
227     /* Initialize the cmd arguments */
228     for (i = 0; i <= MAX_ARGS + 1; i++) {
229         cmd_args[i] = NULL;
230     }
231
232     /* Create list for timeout */
233     timeout_list = OSList_Create();
234     if (!timeout_list) {
235         ErrorExit(LIST_ERROR, ARGV0);
236     }
237
238     if (repeated_offenders_timeout[0] != 0) {
239         repeated_hash = OSHash_Create();
240     } else {
241         repeated_hash = NULL;
242     }
243
244     /* Main loop */
245     while (1) {
246         int timeout_value;
247         int added_before = 0;
248         char **timeout_args;
249         timeout_data *timeout_entry;
250
251         /* Clean up any children */
252         while (childcount) {
253             int wp;
254             wp = waitpid((pid_t) - 1, NULL, WNOHANG);
255             if (wp < 0) {
256                 merror(WAITPID_ERROR, ARGV0, errno, strerror(errno));
257                 break;
258             }
259
260             /* if = 0, we still need to wait for the child process */
261             else if (wp == 0) {
262                 break;
263             }
264             /* Child completed if wp > 0 */
265             else {
266                 childcount--;
267             }
268         }
269
270         /* Get current time */
271         curr_time = time(0);
272
273         /* Check if there is any timed out command to execute */
274         timeout_node = OSList_GetFirstNode(timeout_list);
275         while (timeout_node) {
276             timeout_data *list_entry;
277
278             list_entry = (timeout_data *)timeout_node->data;
279
280             /* Timed out */
281             if ((curr_time - list_entry->time_of_addition) >
282                     list_entry->time_to_block) {
283                 ExecCmd(list_entry->command);
284
285                 /* Delete current node - already sets the pointer to next */
286                 OSList_DeleteCurrentlyNode(timeout_list);
287                 timeout_node = OSList_GetCurrentlyNode(timeout_list);
288
289                 /* Clear the memory */
290                 FreeTimeoutEntry(list_entry);
291
292                 childcount++;
293             } else {
294                 timeout_node = OSList_GetNextNode(timeout_list);
295             }
296         }
297
298         /* Set timeout to EXECD_TIMEOUT */
299         socket_timeout.tv_sec = EXECD_TIMEOUT;
300         socket_timeout.tv_usec = 0;
301
302         /* Set FD values */
303         FD_ZERO(&fdset);
304         FD_SET(q, &fdset);
305
306         /* Add timeout */
307         if (select(q + 1, &fdset, NULL, NULL, &socket_timeout) == 0) {
308             /* Timeout */
309             continue;
310         }
311
312         /* Check for error */
313         if (!FD_ISSET(q, &fdset)) {
314             merror(SELECT_ERROR, ARGV0, errno, strerror(errno));
315             continue;
316         }
317
318         /* Receive the message */
319         if (OS_RecvUnix(q, OS_MAXSTR, buffer) == 0) {
320             merror(QUEUE_ERROR, ARGV0, EXECQUEUEPATH, strerror(errno));
321             continue;
322         }
323
324         /* Current time */
325         curr_time = time(0);
326
327         /* Get application name */
328         name = buffer;
329
330         /* Zero the name */
331         tmp_msg = strchr(buffer, ' ');
332         if (!tmp_msg) {
333             merror(EXECD_INV_MSG, ARGV0, buffer);
334             continue;
335         }
336         *tmp_msg = '\0';
337         tmp_msg++;
338
339         /* Get the command to execute (valid name) */
340         command = GetCommandbyName(name, &timeout_value);
341         if (!command) {
342             ReadExecConfig();
343             command = GetCommandbyName(name, &timeout_value);
344             if (!command) {
345                 merror(EXEC_INV_NAME, ARGV0, name);
346                 continue;
347             }
348         }
349
350         /* Command not present */
351         if (command[0] == '\0') {
352             continue;
353         }
354
355         /* Allocate memory for the timeout argument */
356         os_calloc(MAX_ARGS + 2, sizeof(char *), timeout_args);
357
358         /* Add initial variables to the cmd_arg and to the timeout cmd */
359         cmd_args[0] = command;
360         cmd_args[1] = ADD_ENTRY;
361         os_strdup(command, timeout_args[0]);
362         os_strdup(DELETE_ENTRY, timeout_args[1]);
363
364         cmd_args[2] = NULL;
365         timeout_args[2] = NULL;
366
367         /* Get the arguments */
368         i = 2;
369         while (i < (MAX_ARGS - 1)) {
370             cmd_args[i] = tmp_msg;
371             cmd_args[i + 1] = NULL;
372
373             tmp_msg = strchr(tmp_msg, ' ');
374             if (!tmp_msg) {
375                 timeout_args[i] = strdup(cmd_args[i]);
376                 timeout_args[i + 1] = NULL;
377                 break;
378             }
379             *tmp_msg = '\0';
380             tmp_msg++;
381
382             timeout_args[i] = strdup(cmd_args[i]);
383             timeout_args[i + 1] = NULL;
384
385             i++;
386         }
387
388         /* Check if this command was already executed */
389         timeout_node = OSList_GetFirstNode(timeout_list);
390         added_before = 0;
391
392         /* Check for the username and IP argument */
393         if (!timeout_args[2] || !timeout_args[3]) {
394             added_before = 1;
395             merror("%s: Invalid number of arguments.", ARGV0);
396         }
397
398         while (timeout_node) {
399             timeout_data *list_entry;
400
401             list_entry = (timeout_data *)timeout_node->data;
402             if ((strcmp(list_entry->command[3], timeout_args[3]) == 0) &&
403                     (strcmp(list_entry->command[0], timeout_args[0]) == 0)) {
404                 /* Means we executed this command before
405                  * and we don't need to add it again
406                  */
407                 added_before = 1;
408
409                 /* Update the timeout */
410                 list_entry->time_of_addition = curr_time;
411
412                 if (repeated_offenders_timeout[0] != 0 &&
413                         repeated_hash != NULL &&
414                         strncmp(timeout_args[3], "-", 1) != 0) {
415                     char *ntimes = NULL;
416                     char rkey[256];
417                     rkey[255] = '\0';
418                     snprintf(rkey, 255, "%s%s", list_entry->command[0],
419                              timeout_args[3]);
420
421                     if ((ntimes = (char *) OSHash_Get(repeated_hash, rkey))) {
422                         int ntimes_int = 0;
423                         int i2 = 0;
424                         int new_timeout = 0;
425                         ntimes_int = atoi(ntimes);
426                         while (repeated_offenders_timeout[i2] != 0) {
427                             i2++;
428                         }
429                         if (ntimes_int >= i2) {
430                             new_timeout = repeated_offenders_timeout[i2 - 1] * 60;
431                         } else {
432                             free(ntimes);       /* In hash_op.c, data belongs to caller */
433                             os_calloc(10, sizeof(char), ntimes);
434                             new_timeout = repeated_offenders_timeout[ntimes_int] * 60;
435                             ntimes_int++;
436                             snprintf(ntimes, 9, "%d", ntimes_int);
437                             OSHash_Update(repeated_hash, rkey, ntimes);
438                         }
439                         list_entry->time_to_block = new_timeout;
440                     }
441                 }
442                 break;
443             }
444
445             /* Continue with the next entry in timeout list*/
446             timeout_node = OSList_GetNextNode(timeout_list);
447         }
448
449         /* If it wasn't added before, do it now */
450         if (!added_before) {
451             /* Execute command */
452             ExecCmd(cmd_args);
453
454             /* We don't need to add to the list if the timeout_value == 0 */
455             if (timeout_value) {
456                 char *ntimes;
457                 char rkey[256];
458                 rkey[255] = '\0';
459                 snprintf(rkey, 255, "%s%s", timeout_args[0],
460                          timeout_args[3]);
461
462                 if (repeated_hash != NULL) {
463                     if ((ntimes = (char *) OSHash_Get(repeated_hash, rkey))) {
464                         int ntimes_int = 0;
465                         int i2 = 0;
466                         int new_timeout = 0;
467
468                         ntimes_int = atoi(ntimes);
469                         while (repeated_offenders_timeout[i2] != 0) {
470                             i2++;
471                         }
472                         if (ntimes_int >= i2) {
473                             new_timeout = repeated_offenders_timeout[i2 - 1] * 60;
474                         } else {
475                             os_calloc(10, sizeof(char), ntimes);
476                             new_timeout = repeated_offenders_timeout[ntimes_int] * 60;
477                             ntimes_int++;
478                             snprintf(ntimes, 9, "%d", ntimes_int);
479                             OSHash_Update(repeated_hash, rkey, ntimes);
480                         }
481                         timeout_value = new_timeout;
482                     } else {
483                         /* Add to the repeat offenders list */
484                         OSHash_Add(repeated_hash,
485                                    rkey, strdup("0"));
486                     }
487                 }
488
489                 /* Create the timeout entry */
490                 os_calloc(1, sizeof(timeout_data), timeout_entry);
491                 timeout_entry->command = timeout_args;
492                 timeout_entry->time_of_addition = curr_time;
493                 timeout_entry->time_to_block = timeout_value;
494
495                 /* Add command to the timeout list */
496                 if (!OSList_AddData(timeout_list, timeout_entry)) {
497                     merror(LIST_ADD_ERROR, ARGV0);
498                     FreeTimeoutEntry(timeout_entry);
499                 }
500             }
501
502             /* If no timeout, we still need to free it in here */
503             else {
504                 char **ss_ta = timeout_args;
505                 while (*timeout_args) {
506                     os_free(*timeout_args);
507                     *timeout_args = NULL;
508                     timeout_args++;
509                 }
510                 os_free(ss_ta);
511             }
512
513             childcount++;
514         }
515
516         /* We didn't add it to the timeout list */
517         else {
518             char **ss_ta = timeout_args;
519
520             /* Clear the timeout arguments */
521             while (*timeout_args) {
522                 os_free(*timeout_args);
523                 *timeout_args = NULL;
524                 timeout_args++;
525             }
526
527             os_free(ss_ta);
528         }
529
530         /* Some cleanup */
531         while (i > 0) {
532             cmd_args[i] = NULL;
533             i--;
534         }
535     }
536 }
537
538 #endif /* !WIN32 */
539