INSTALL_ROOT resolution bug fix (found by Kat)
[psad.git] / kmsgsd.c
1 /*
2 ******************************************************************************
3 *
4 *  File: kmsgsd.c
5 *
6 *  Purpose: kmsgsd separates iptables messages from all other
7 *           kernel messages.
8 *
9 *  Strategy: read messages from the /var/log/psadfifo named pipe and
10 *            print any firewall related dop/reject/deny messages to
11 *            the psad data file "/var/log/psad/fwdata".
12 *
13 *  Author: Michael Rash (mbr@cipherdyne.org)
14 *
15 *  Credits:  (see the CREDITS file)
16 *
17 *  Copyright (C) 1999-2007 Michael Rash (mbr@cipherdyne.org)
18 *
19 *  License (GNU Public License):
20 *
21 *     This program is distributed in the hope that it will be useful,
22 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
23 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 *     GNU General Public License for more details.
25 *
26 *     You should have received a copy of the GNU General Public License
27 *     along with this program; if not, write to the Free Software
28 *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
29 *     USA
30 ******************************************************************************
31 */
32
33 /* includes */
34 #include "psad.h"
35 #include <getopt.h>
36
37 /* defines */
38 #define CONFIG_FILE "/etc/psad/psad.conf"
39
40 /* Maximum number of overwrite files allowed on the command line */
41 #define MAX_OVW_FILES   3
42
43 /* globals */
44 static volatile sig_atomic_t received_sighup = 0;
45 extern char *optarg; /* for getopt */
46 extern int   optind; /* for getopt */
47 char *fw_msg_search[MAX_GEN_LEN];
48 char psadfifo_file[MAX_PATH_LEN];
49 char fwdata_file[MAX_PATH_LEN];
50 char fw_search_file[MAX_PATH_LEN];
51 char snort_sid_str[MAX_PATH_LEN];
52 char install_root[MAX_PATH_LEN];
53 char psad_dir[MAX_PATH_LEN];
54 char psad_fifo_dir[MAX_PATH_LEN];
55 char psad_run_dir[MAX_PATH_LEN];
56 char kmsgsd_pid_file[MAX_PATH_LEN];
57 int num_fw_search_strings;
58 int fw_search_all_flag;
59 unsigned char dump_cfg;
60
61 /* prototypes */
62 static void usage(void);
63 static void clean_settings(void);
64 static void parse_config(char *file);
65 static void check_config(void);
66 static void dump_config(void);
67 static int match_fw_msg(char *fw_mgs);
68 static void find_sub_var_value(
69     char *value,
70     char *sub_var,
71     char *pre_str,
72     char *post_str
73 );
74
75 static void expand_config_vars(void);
76 static void sighup_handler(int sig);
77
78 /* main */
79 int main(int argc, char *argv[]) {
80
81     char **ovw_file_ptr;
82     char  *overwrite_files[MAX_OVW_FILES+1];
83     char   overwrite_cmd[MAX_PATH_LEN];
84     char   config_file[MAX_PATH_LEN];
85     char   buf[MAX_LINE_BUF];
86     int    fifo_fd, fwdata_fd;  /* file descriptors */
87     int    cmdlopt, numbytes;
88 #ifdef DEBUG
89     int    matched_ipt_log_msg = 0;
90     int    fwlinectr = 0;
91 #endif
92
93 #ifdef DEBUG
94     fprintf(stderr, "[+] Entering DEBUG mode\n");
95     fprintf(stderr, "[+] Firewall messages will be written to both ");
96     fprintf(stderr, "STDOUT _and_ to fwdata.\n\n");
97 #endif
98
99     overwrite_files[0] = NULL;
100     strlcpy(config_file, CONFIG_FILE, MAX_PATH_LEN);
101     dump_cfg = 0;
102
103     while((cmdlopt = getopt(argc, argv, "c:O:Dh")) != -1) {
104         switch(cmdlopt) {
105             case 'c':
106                 strlcpy(config_file, optarg, MAX_PATH_LEN);
107                 break;
108             case 'O':
109                 strlcpy(overwrite_cmd, optarg, MAX_PATH_LEN);
110                 list_to_array(overwrite_cmd, ',', overwrite_files, MAX_OVW_FILES);
111                 break;
112             case 'D':
113                 dump_cfg = 1;
114                 break;
115             default:
116                 usage();
117         }
118     }
119
120     /* clean our settings */
121     clean_settings();
122
123     /* Parse both the overwrite and configuration file */
124     for (ovw_file_ptr=overwrite_files; *ovw_file_ptr!=NULL; ovw_file_ptr++)
125         parse_config(*ovw_file_ptr);
126     parse_config(config_file);
127
128     /* Check our settings */
129     check_config();
130
131     if (dump_cfg == 1)
132         dump_config();
133
134     /* make sure there isn't another kmsgsd already running */
135     check_unique_pid(kmsgsd_pid_file, "kmsgsd");
136
137 #ifndef DEBUG
138     /* become a daemon */
139     daemonize_process(kmsgsd_pid_file);
140 #endif
141
142     /* install signal handler for HUP signals */
143     signal(SIGHUP, sighup_handler);
144
145     /* start doing the real work now that the daemon is running and
146      * the config file has been processed */
147
148     /* open the psadfifo named pipe.  Note that we are opening the pipe
149      * _without_ the O_NONBLOCK flag since we want the read on the file
150      * descriptor to block until there is something new in the pipe.
151      * Also, note that we are opening with O_RDWR, since this seems to
152      * fix the problem with kmsgsd not blocking on the read() if the
153      * system logger dies (and hence closes its file descriptor for the
154      * psadfifo). */
155     if ((fifo_fd = open(psadfifo_file, O_RDWR)) < 0) {
156         fprintf(stderr, "[*] Could not open %s for reading.\n",
157             psadfifo_file);
158         exit(EXIT_FAILURE);  /* could not open psadfifo named pipe */
159     }
160
161     /* open the fwdata file in append mode so we can write messages from
162      * the pipe into this file. */
163     if ((fwdata_fd = open(fwdata_file,
164             O_CREAT|O_WRONLY|O_APPEND, 0600)) < 0) {
165         fprintf(stderr, "[*] Could not open %s for writing.\n", fwdata_file);
166         exit(EXIT_FAILURE);  /* could not open fwdata file */
167     }
168
169     /* MAIN LOOP;
170      * Read data from the pipe indefinitely (we opened it _without_
171      * O_NONBLOCK) and write it to the fwdata file if it is a firewall message
172      */
173     while ((numbytes = read(fifo_fd, buf, MAX_LINE_BUF-1)) >= 0) {
174
175 #ifdef DEBUG
176         fprintf(stderr,
177             "read %d bytes from %s fifo.\n", numbytes, psadfifo_file);
178 #endif
179
180         /* make sure the buf contents qualifies as a string */
181         buf[numbytes] = '\0';
182
183         if (received_sighup) {
184
185             /* clear the signal flag */
186             received_sighup = 0;
187
188             /* clean our settings */
189             clean_settings();
190
191             /* reparse the config file since we received a HUP signal */
192             for (ovw_file_ptr=overwrite_files; *ovw_file_ptr!=NULL; ovw_file_ptr++)
193                 parse_config(*ovw_file_ptr);
194             parse_config(config_file);
195
196             check_config();
197
198             /* close file descriptors and re-open them after
199              * re-reading config file */
200             close(fifo_fd);
201             close(fwdata_fd);
202
203             /* re-open psadfifo and fwdata files */
204             if ((fifo_fd = open(psadfifo_file, O_RDWR)) < 0) {
205                 fprintf(stderr, "[*] Could not open %s for reading.\n",
206                     psadfifo_file);
207                 exit(EXIT_FAILURE);  /* could not open psadfifo named pipe */
208             }
209
210             if ((fwdata_fd = open(fwdata_file, O_CREAT|O_WRONLY|O_APPEND,
211                     0600)) < 0) {
212                 fprintf(stderr, "[*] Could not open %s for writing.\n",
213                     fwdata_file);
214                 exit(EXIT_FAILURE);  /* could not open fwdata file */
215             }
216             slogr("psad(kmsgsd)", "received HUP signal");
217         }
218
219         /* see if we matched a firewall message and write it to the
220          * fwdata file */
221         if ((strstr(buf, "OUT=") != NULL
222                 && strstr(buf, "IN=") != NULL)) {
223             if (! fw_search_all_flag) {
224                 /* we are looking for specific log prefixes */
225                 if (match_fw_msg(buf) || strstr(buf, snort_sid_str) != NULL) {
226                     if (write(fwdata_fd, buf, numbytes) < 0) {
227                         exit(EXIT_FAILURE);  /* could not write to the fwdata file */
228                     }
229 #ifdef DEBUG
230                     matched_ipt_log_msg = 1;
231 #endif
232                 }
233             } else {
234                 if (write(fwdata_fd, buf, numbytes) < 0)
235                     exit(EXIT_FAILURE);  /* could not write to the fwdata file */
236 #ifdef DEBUG
237                 matched_ipt_log_msg = 1;
238 #endif
239             }
240 #ifdef DEBUG
241             if (matched_ipt_log_msg) {
242                 puts(buf);
243                 fprintf(stderr, "[+] Line matched search strings.\n");
244                 fwlinectr++;
245                 if (fwlinectr % 50 == 0)
246                     fprintf(stderr,
247                         "[+] Processed %d firewall lines.\n", fwlinectr);
248                 matched_ipt_log_msg = 0;
249             } else {
250                 puts(buf);
251                 fprintf(stderr, "[-] Line did not match search strings.\n");
252             }
253 #endif
254         }
255     }
256
257     /* these statements don't get executed, but for completeness... */
258     close(fifo_fd);
259     close(fwdata_fd);
260
261     exit(EXIT_SUCCESS);
262 }
263 /******************** end main ********************/
264
265 static int match_fw_msg(char *fw_msg)
266 {
267     int i;
268     for (i=0; i < num_fw_search_strings; i++)
269         if (strstr(fw_msg, fw_msg_search[i]) != NULL)
270             return 1;
271     return 0;
272 }
273
274 static void parse_config(char * file)
275 {
276     FILE *config_ptr;   /* FILE pointer to the config file */
277     int linectr = 0, i;
278     char config_buf[MAX_LINE_BUF];
279     char tmp_fw_search_buf[MAX_GEN_LEN], *index;
280
281     for (i=0; i < num_fw_search_strings; i++)
282         if (fw_msg_search[i] != NULL)
283             free(fw_msg_search[i]);
284
285     num_fw_search_strings = 0;
286     fw_msg_search[num_fw_search_strings] = NULL;
287
288 #ifdef DEBUG
289     fprintf(stderr, "[+] Parsing file %s\n", file);
290 #endif
291
292     if ((config_ptr = fopen(file, "r")) == NULL) {
293         perror("[*] Could not open config file");
294         exit(EXIT_FAILURE);
295     }
296
297     /* increment through each line of the config file */
298     while ((fgets(config_buf, MAX_LINE_BUF, config_ptr)) != NULL) {
299         linectr++;
300         /* set the index pointer to the beginning of the line */
301         index = config_buf;
302
303         /* advance the index pointer through any whitespace
304          * at the beginning of the line */
305         while (*index == ' ' || *index == '\t') index++;
306
307         /* skip comments and blank lines, etc. */
308         if ((*index != '#') && (*index != '\n') &&
309                 (*index != ';') && (index != NULL)) {
310
311             find_char_var("INSTALL_ROOT", install_root, index);
312             find_char_var("PSAD_DIR", psad_dir, index);
313             find_char_var("PSAD_FIFO_DIR", psad_fifo_dir, index);
314             find_char_var("PSAD_RUN_DIR", psad_run_dir, index);
315             find_char_var("SNORT_SID_STR", snort_sid_str, index);
316             find_char_var("PSAD_FIFO_FILE", psadfifo_file, index);
317             find_char_var("FW_DATA_FILE", fwdata_file, index);
318             find_char_var("KMSGSD_PID_FILE", kmsgsd_pid_file, index);
319             if (find_char_var("FW_MSG_SEARCH", tmp_fw_search_buf, index)) {
320                 fw_msg_search[num_fw_search_strings]
321                     = (char *) safe_malloc(strlen(tmp_fw_search_buf)+1);
322                 strlcpy(fw_msg_search[num_fw_search_strings],
323                     tmp_fw_search_buf, strlen(tmp_fw_search_buf)+1);
324                 num_fw_search_strings++;
325             }
326             if (find_char_var("FW_SEARCH_ALL", tmp_fw_search_buf, index)) {
327                 if (tmp_fw_search_buf[0] == 'N')
328                     fw_search_all_flag = 0;
329             }
330         }
331     }
332     fclose(config_ptr);
333
334     return;
335 }
336
337 static void expand_config_vars(void)
338 {
339     char sub_var[MAX_GEN_LEN]  = "";
340     char pre_str[MAX_GEN_LEN]  = "";
341     char post_str[MAX_GEN_LEN] = "";
342     int found_sub_var = 1, resolve_ctr = 0;
343
344     while (found_sub_var) {
345         resolve_ctr++;
346         if (resolve_ctr >= 20) {
347             fprintf(stderr, "[*] Exceeded maximum variable resolution attempts.\n");
348             exit(EXIT_FAILURE);
349         }
350         found_sub_var = 0;
351
352         if (has_sub_var("INSTALL_ROOT", install_root, sub_var,
353                 pre_str, post_str)) {
354             find_sub_var_value(install_root, sub_var, pre_str, post_str);
355             found_sub_var = 1;
356         }
357
358         if (has_sub_var("SNORT_SID_STR", snort_sid_str, sub_var,
359                 pre_str, post_str)) {
360             find_sub_var_value(snort_sid_str, sub_var, pre_str, post_str);
361             found_sub_var = 1;
362         }
363
364         if (has_sub_var("FW_DATA_FILE", fwdata_file, sub_var,
365                 pre_str, post_str)) {
366             find_sub_var_value(fwdata_file, sub_var, pre_str, post_str);
367             found_sub_var = 1;
368         }
369
370         if (has_sub_var("PSAD_FIFO_FILE", psadfifo_file, sub_var,
371                 pre_str, post_str)) {
372             find_sub_var_value(psadfifo_file, sub_var, pre_str, post_str);
373             found_sub_var = 1;
374         }
375
376         if (has_sub_var("KMSGSD_PID_FILE", kmsgsd_pid_file, sub_var,
377                 pre_str, post_str)) {
378             find_sub_var_value(kmsgsd_pid_file, sub_var, pre_str, post_str);
379             found_sub_var = 1;
380         }
381     }
382     return;
383 }
384
385 static void find_sub_var_value(char *value, char *sub_var, char *pre_str,
386     char *post_str)
387 {
388     int found_var = 0;
389     if (strncmp(sub_var, "PSAD_DIR", MAX_GEN_LEN) == 0) {
390         strlcpy(sub_var, psad_dir, MAX_GEN_LEN);
391         found_var = 1;
392     } else if (strncmp(sub_var, "PSAD_FIFO_DIR", MAX_GEN_LEN) == 0) {
393         strlcpy(sub_var, psad_fifo_dir, MAX_GEN_LEN);
394         found_var = 1;
395     } else if (strncmp(sub_var, "INSTALL_ROOT", MAX_GEN_LEN) == 0) {
396         strlcpy(sub_var, install_root, MAX_GEN_LEN);
397         found_var = 1;
398     } else if (strncmp(sub_var, "PSAD_RUN_DIR", MAX_GEN_LEN) == 0) {
399         strlcpy(sub_var, psad_run_dir, MAX_GEN_LEN);
400         found_var = 1;
401     } else if (strncmp(sub_var, "SNORT_SID_STR", MAX_GEN_LEN) == 0) {
402         strlcpy(sub_var, snort_sid_str, MAX_GEN_LEN);
403         found_var = 1;
404     } else if (strncmp(sub_var, "FW_DATA_FILE", MAX_GEN_LEN) == 0) {
405         strlcpy(sub_var, fwdata_file, MAX_GEN_LEN);
406         found_var = 1;
407     } else if (strncmp(sub_var, "PSAD_FIFO_FILE", MAX_GEN_LEN) == 0) {
408         strlcpy(sub_var, psadfifo_file, MAX_GEN_LEN);
409         found_var = 1;
410     } else if (strncmp(sub_var, "KMSGSD_PID_FILE", MAX_GEN_LEN) == 0) {
411         strlcpy(sub_var, kmsgsd_pid_file, MAX_GEN_LEN);
412         found_var = 1;
413     }
414
415     if (found_var) {
416
417         /* substitute the variable value */
418         expand_sub_var_value(value, sub_var, pre_str, post_str);
419
420     } else {
421         fprintf(stderr, "[*] Could not resolve sub-var: %s to a value.\n",
422             sub_var);
423         exit(EXIT_FAILURE);
424     }
425     return;
426 }
427
428 static void dump_config(void)
429 {
430     fprintf(stderr, "[+] dump_config()\n");
431     fprintf(stderr, "    INSTALL_ROOT: %s\n", install_root);
432     fprintf(stderr, "    PSAD_DIR: %s\n", psad_dir);
433     fprintf(stderr, "    PSAD_RUN_DIR: %s\n", psad_run_dir);
434     fprintf(stderr, "    PSAD_FIFO_FILE: %s\n", psadfifo_file);
435     fprintf(stderr, "    FW_DATA_FILE: %s\n", fwdata_file);
436     fprintf(stderr, "    SNORT_SID_STR: %s\n", snort_sid_str);
437     fprintf(stderr, "    KMSGSD_PID_FILE: %s\n", kmsgsd_pid_file);
438
439     exit(EXIT_SUCCESS);
440 }
441
442 static void check_config(void)
443 {
444     unsigned char err;
445
446 #ifdef DEBUG
447     fprintf(stderr, "[+] Checking configuration...\n");
448 #endif
449
450     err = 1;
451     if (psad_dir[0] == '\0')
452         fprintf(stderr, "[*] Could not find PSAD_DIR\n");
453
454     else if (install_root[0] == '\0')
455         fprintf(stderr, "[*] Could not find INSTALL_ROOT\n");
456
457     else if (psad_run_dir[0] == '\0')
458         fprintf(stderr, "[*] Could not find PSAD_RUN_DIR\n");
459
460     else if (psadfifo_file[0] == '\0')
461         fprintf(stderr, "[*] Could not find PSAD_FIFO_FILE\n");
462
463     else if (fwdata_file[0] == '\0')
464         fprintf(stderr, "[*] Could not find FW_DATA_FILE\n");
465
466     else if (snort_sid_str[0] == '\0')
467         fprintf(stderr, "[*] Could not find SNORT_SID_STR\n");
468
469     else if (kmsgsd_pid_file[0] == '\0')
470         fprintf(stderr, "[*] Could not find KMSGSD_PID_FILE\n");
471
472     /* Resolve any embedded variables */
473     else {
474         expand_config_vars();
475
476         /* there are no FW_MSG_SEARCH vars in fw_search.conf; default
477          * to "DROP".  Psad will generate a syslog warning.  */
478         if (! fw_search_all_flag && num_fw_search_strings == 0) {
479             fw_msg_search[num_fw_search_strings]
480                 = (char *) safe_malloc(strlen("DROP")+1);
481             strlcpy(fw_msg_search[0], "DROP", strlen("DROP")+1);
482             num_fw_search_strings++;
483         }
484
485         err = 0;
486     }
487
488     if (err == 1)
489         exit(EXIT_FAILURE);
490 }
491
492 static void clean_settings (void)
493 {
494
495 #ifdef DEBUG
496     fprintf(stderr, "[+] Cleaning settings\n");
497 #endif
498
499     /* default to parse all iptables messages */
500     num_fw_search_strings = 0;
501     fw_search_all_flag    = 1;
502
503     *psad_dir        = '\0';
504     *psad_fifo_dir   = '\0';
505     *install_root    = '\0';
506     *psad_run_dir    = '\0';
507     *psadfifo_file   = '\0';
508     *fwdata_file     = '\0';
509     *snort_sid_str   = '\0';
510     *kmsgsd_pid_file = '\0';
511 }
512
513
514 static void sighup_handler(int sig)
515 {
516     received_sighup = 1;
517 }
518
519 /*
520  * Usage message to be displayed when -h option is supplied or a bad option
521  * is passed to the daemon. This function ends the execution of the program.
522  */
523 static void usage (void)
524 {
525     fprintf(stderr,
526 "kmsgsd - separates iptables messages from all other kernel messages\n\n");
527
528     fprintf(stderr, "[+] Version: %s\n", PSAD_VERSION);
529     fprintf(stderr,
530 "    By Michael Rash (mbr@cipherdyne.org)\n"
531 "    URL: http://www.cipherdyne.org/psad/\n\n");
532
533     fprintf(stderr, "Usage: kmsgsd [options]\n\n");
534
535     fprintf(stderr,
536 "Options:\n"
537 "    -c <file>          - Specify path to config file instead of using the\n"
538 "                         default $config_file.\n"
539 "    -D                 - Dump  the  configuration values that psad\n"
540 "                         derives from the /etc/psad/psad.conf (or other\n"
541 "                         override files) on STDERR\n"
542 "    -h                 - Display this usage message and exit\n"
543 "    -O <file>          - Override config variable values that are normally\n"
544 "                         read from the /etc/psad/psad.conf file with\n"
545 "                         values from the specified file\n");
546
547     exit(EXIT_FAILURE);
548 }