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