1 /* Copyright (C) 2009 Trend Micro Inc.
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
11 #include "logcollector.h"
14 int update_fname(int i);
16 /* Global variables */
19 int open_file_attempts;
24 static char *rand_keepalive_str(char *dst, int size)
26 static const char text[] = "abcdefghijklmnopqrstuvwxyz"
27 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
29 "!@#$%^&*()_+-=;'[],./?";
30 int i, len = rand() % (size - 10);
32 strncpy(dst, "--MARK--: ", 12);
33 for ( i = 10; i < len; ++i ) {
34 dst[i] = text[(unsigned int)rand() % (sizeof text - 1)];
40 /* Handle file management */
41 void LogCollectorStart()
52 struct timeval fp_timeout;
53 /* To check for inode changes */
56 BY_HANDLE_FILE_INFORMATION lpFileInformation;
58 /* Check if we are on Windows Vista */
61 /* Read vista descriptions */
67 debug1("%s: DEBUG: Entering LogCollectorStart().", ARGV0);
69 /* Initialize each file and structure */
71 if (logff[i].file == NULL) {
75 /* Remove duplicate entries */
76 for (r = 0; r < i; r++) {
77 if (logff[r].file && strcmp(logff[i].file, logff[r].file) == 0) {
78 merror("%s: WARN: Duplicated log file given: '%s'.",
79 ARGV0, logff[i].file);
81 logff[i].command = NULL;
88 if (logff[i].file == NULL) {
89 /* Do nothing, duplicated entry */
92 else if (strcmp(logff[i].logformat, "eventlog") == 0) {
95 verbose(READING_EVTLOG, ARGV0, logff[i].file);
96 win_startel(logff[i].file);
100 logff[i].command = NULL;
104 else if (strcmp(logff[i].logformat, "eventchannel") == 0) {
107 #ifdef EVENTCHANNEL_SUPPORT
108 verbose(READING_EVTLOG, ARGV0, logff[i].file);
109 win_start_event_channel(logff[i].file, logff[i].future, logff[i].query);
111 merror("%s: WARN: eventchannel not available on this version of OSSEC", ARGV0);
116 logff[i].file = NULL;
117 logff[i].command = NULL;
121 else if (strcmp(logff[i].logformat, "command") == 0) {
122 logff[i].file = NULL;
126 if (logff[i].command) {
127 logff[i].read = read_command;
129 verbose("%s: INFO: Monitoring output of command(%d): %s", ARGV0, logff[i].ign, logff[i].command);
131 if (!logff[i].alias) {
132 os_strdup(logff[i].command, logff[i].alias);
135 merror("%s: ERROR: Missing command argument. Ignoring it.",
138 } else if (strcmp(logff[i].logformat, "full_command") == 0) {
139 logff[i].file = NULL;
142 if (logff[i].command) {
143 logff[i].read = read_fullcommand;
145 verbose("%s: INFO: Monitoring full output of command(%d): %s", ARGV0, logff[i].ign, logff[i].command);
147 if (!logff[i].alias) {
148 os_strdup(logff[i].command, logff[i].alias);
151 merror("%s: ERROR: Missing command argument. Ignoring it.",
157 logff[i].command = NULL;
159 /* Initialize the files */
160 if (logff[i].ffile) {
161 /* Day must be zero for all files to be initialized */
163 if (update_fname(i)) {
164 handle_file(i, 1, 1);
166 ErrorExit(PARSE_ERROR, ARGV0, logff[i].ffile);
170 handle_file(i, 1, 1);
173 verbose(READING_FILE, ARGV0, logff[i].file);
175 /* Get the log type */
176 if (strcmp("snort-full", logff[i].logformat) == 0) {
177 logff[i].read = read_snortfull;
180 if (strcmp("ossecalert", logff[i].logformat) == 0) {
181 logff[i].read = read_ossecalert;
184 else if (strcmp("nmapg", logff[i].logformat) == 0) {
185 logff[i].read = read_nmapg;
186 } else if (strcmp("mysql_log", logff[i].logformat) == 0) {
187 logff[i].read = read_mysql_log;
188 } else if (strcmp("mssql_log", logff[i].logformat) == 0) {
189 logff[i].read = read_mssql_log;
190 } else if (strcmp("postgresql_log", logff[i].logformat) == 0) {
191 logff[i].read = read_postgresql_log;
192 } else if (strcmp("djb-multilog", logff[i].logformat) == 0) {
193 if (!init_djbmultilog(i)) {
194 merror(INV_MULTILOG, ARGV0, logff[i].file);
199 logff[i].file = NULL;
201 logff[i].read = read_djbmultilog;
202 } else if (logff[i].logformat[0] >= '0' && logff[i].logformat[0] <= '9') {
203 logff[i].read = read_multiline;
204 } else if (strcmp("audit", logff[i].logformat) == 0) {
205 logff[i].read = read_audit;
207 logff[i].read = read_syslog;
210 /* More tweaks for Windows. For some reason IIS places
211 * some weird characters at the end of the files and getc
212 * always returns 0 (even after clearerr).
216 logff[i].read(i, &r, 1);
221 if (logff[i].alias) {
223 while (logff[i].alias[ii] != '\0') {
224 if (logff[i].alias[ii] == ':') {
225 logff[i].alias[ii] = '\\';
232 /* Start up message */
233 verbose(STARTUP_MSG, ARGV0, (int)getpid());
245 fp_timeout.tv_sec = loop_timeout;
246 fp_timeout.tv_usec = 0;
248 /* Wait for the select timeout */
249 if ((r = select(0, NULL, NULL, NULL, &fp_timeout)) < 0) {
250 merror(SELECT_ERROR, ARGV0, errno, strerror(errno));
253 if (int_error >= 5) {
254 ErrorExit(SYSTEM_ERROR, ARGV0);
260 /* Windows doesn't like select that way */
261 sleep(loop_timeout + 2);
263 /* Check for messages in the event viewer */
269 /* Check which file is available */
270 for (i = 0; i <= max_file; i++) {
272 /* Run the command */
273 if (logff[i].command && (f_check % 2)) {
275 if ((curr_time - logff[i].size) >= logff[i].ign) {
276 logff[i].size = curr_time;
277 logff[i].read(i, &r, 0);
283 /* Windows with IIS logs is very strange.
284 * For some reason it always returns 0 (not EOF)
285 * the fgetc. To solve this problem, we always
286 * pass it to the function pointer directly.
289 /* We check for the end of file. If is returns EOF,
290 * we don't attempt to read it.
292 if ((r = fgetc(logff[i].fp)) == EOF) {
293 clearerr(logff[i].fp);
297 /* If it is not EOF, we need to return the read character */
298 ungetc(r, logff[i].fp);
301 /* Finally, send to the function pointer to read it */
302 logff[i].read(i, &r, 0);
304 /* Check for error */
305 if (!ferror(logff[i].fp)) {
307 clearerr(logff[i].fp);
314 /* If ferror is set */
316 merror(FREAD_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
318 if (fseek(logff[i].fp, 0, SEEK_END) < 0)
325 merror(FSEEK_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
332 CloseHandle(logff[i].h);
338 /* Try to open it again */
339 if (handle_file(i, 1, 1) != 0) {
344 logff[i].read(i, &r, 1);
347 /* Increase the error count */
349 clearerr(logff[i].fp);
353 /* Only check below if check > VCHECK_FILES */
354 if (f_check <= VCHECK_FILES) {
358 /* Send keep alive message */
359 rand_keepalive_str(keepalive, 700);
360 SendMSG(logr_queue, keepalive, "ossec-keepalive", LOCALFILE_MQ);
365 /* Check if any file has been renamed/removed */
366 for (i = 0; i <= max_file; i++) {
367 /* These are the windows logs or ignored files */
368 if (!logff[i].file) {
372 /* Files with date -- check for day change */
373 if (logff[i].ffile) {
374 if (update_fname(i)) {
378 CloseHandle(logff[i].h);
382 handle_file(i, 0, 1);
386 /* Variable file name */
387 else if (!logff[i].fp) {
388 handle_file(i, 0, 0);
393 /* Check for file change -- if the file is open already */
397 /* To help detect a file rollover, temporarily open the file a second time.
398 * Previously the fstat would work on "cached" file data, but this should
399 * ensure it's fresh when hardlinks are used (like alerts.log).
402 tf = fopen(logff[i].file, "r");
404 merror(FOPEN_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
407 else if ((fstat(fileno(tf), &tmp_stat)) == -1) {
412 merror(FSTAT_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
414 else if(fclose(tf) == EOF) {
415 merror("Closing the temporary file %s did not work (%d): %s", logff[i].file, errno, strerror(errno));
420 h1 = CreateFile(logff[i].file, GENERIC_READ,
421 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
422 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
423 if (h1 == INVALID_HANDLE_VALUE) {
425 CloseHandle(logff[i].h);
427 merror(FILE_ERROR, ARGV0, logff[i].file);
428 } else if (GetFileInformationByHandle(h1, &lpFileInformation) == 0) {
430 CloseHandle(logff[i].h);
433 merror(FILE_ERROR, ARGV0, logff[i].file);;
438 else if (logff[i].fd != (lpFileInformation.nFileIndexLow + lpFileInformation.nFileIndexHigh))
440 else if (logff[i].fd != tmp_stat.st_ino)
443 char msg_alert[512 + 1];
445 snprintf(msg_alert, 512, "ossec: File rotated (inode "
449 /* Send message about log rotated */
450 SendMSG(logr_queue, msg_alert,
451 "ossec-logcollector", LOCALFILE_MQ);
453 debug1("%s: DEBUG: File inode changed. %s",
454 ARGV0, logff[i].file);
459 CloseHandle(logff[i].h);
464 handle_file(i, 0, 1);
468 else if (logff[i].size > (lpFileInformation.nFileSizeHigh + lpFileInformation.nFileSizeLow))
470 else if (logff[i].size > tmp_stat.st_size)
473 char msg_alert[512 + 1];
475 snprintf(msg_alert, 512, "ossec: File size reduced "
476 "(inode remained): '%s'.",
479 /* Send message about log rotated */
480 SendMSG(logr_queue, msg_alert,
481 "ossec-logcollector", LOCALFILE_MQ);
483 debug1("%s: DEBUG: File size reduced. %s",
484 ARGV0, logff[i].file);
490 CloseHandle(logff[i].h);
495 handle_file(i, 1, 1);
505 /* Too many errors for the file */
506 if (logff[i].ign > open_file_attempts) {
507 /* 999 Maximum ignore */
508 if (logff[i].ign == 999) {
512 merror(LOGC_FILE_ERROR, ARGV0, logff[i].file);
516 CloseHandle(logff[i].h);
522 /* If the file has a variable date, ignore it for today only */
523 if (!logff[i].ffile) {
524 /* Variable log files should always be attempted
527 //logff[i].file = NULL;
535 if (logff[i].ign >= 999) {
538 /* Try for a few times to open the file */
539 if (handle_file(i, 1, 1) < 0) {
546 /* Update file size */
548 logff[i].size = lpFileInformation.nFileSizeHigh + lpFileInformation.nFileSizeLow;
550 logff[i].size = tmp_stat.st_size;
556 int update_fname(int i)
559 time_t __ctime = time(0);
560 char lfile[OS_FLSIZE + 1];
563 p = localtime(&__ctime);
566 if (p->tm_mday == _cday) {
570 lfile[OS_FLSIZE] = '\0';
571 ret = strftime(lfile, OS_FLSIZE, logff[i].ffile, p);
573 ErrorExit(PARSE_ERROR, ARGV0, logff[i].ffile);
576 /* Update the filename */
577 if (strcmp(lfile, logff[i].file) != 0) {
578 os_free(logff[i].file);
580 os_strdup(lfile, logff[i].file);
582 verbose(VAR_LOG_MON, ARGV0, logff[i].file);
584 /* Setting cday to zero because other files may need
595 /* Open, get the fileno, seek to the end and update mtime */
596 int handle_file(int i, int do_fseek, int do_log)
601 /* We must be able to open the file, fseek and get the
602 * time of change from it.
605 logff[i].fp = fopen(logff[i].file, "r");
608 merror(FOPEN_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
612 /* Get inode number for fp */
613 fd = fileno(logff[i].fp);
614 if (fstat(fd, &stat_fd) == -1) {
615 merror(FSTAT_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
621 logff[i].fd = stat_fd.st_ino;
622 logff[i].size = stat_fd.st_size;
626 BY_HANDLE_FILE_INFORMATION lpFileInformation;
629 logff[i].h = CreateFile(logff[i].file, GENERIC_READ,
630 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
631 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
632 if (logff[i].h == INVALID_HANDLE_VALUE) {
634 merror(FOPEN_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
638 fd = _open_osfhandle((long)logff[i].h, 0);
640 merror(FOPEN_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
641 CloseHandle(logff[i].h);
644 logff[i].fp = _fdopen(fd, "r");
645 if (logff[i].fp == NULL) {
646 merror(FOPEN_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
647 CloseHandle(logff[i].h);
652 /* On windows, we also need the real inode, which is the combination
653 * of the index low + index high numbers.
655 if (GetFileInformationByHandle(logff[i].h, &lpFileInformation) == 0) {
656 merror("%s: Unable to get file information by handle.", ARGV0);
658 CloseHandle(logff[i].h);
663 logff[i].fd = (lpFileInformation.nFileIndexLow + lpFileInformation.nFileIndexHigh);
664 logff[i].size = (lpFileInformation.nFileSizeHigh + lpFileInformation.nFileSizeLow);
668 /* Only seek the end of the file if set to */
669 if (do_fseek == 1 && S_ISREG(stat_fd.st_mode)) {
670 /* Windows and fseek causes some weird issues */
672 if (fseek(logff[i].fp, 0, SEEK_END) < 0) {
673 merror(FSEEK_ERROR, ARGV0, logff[i].file, errno, strerror(errno));
681 /* Set ignore to zero */
688 /* Remove newlines and replace tabs in the argument fields with spaces */
689 void win_format_event_string(char *string)
691 if (string == NULL) {
695 while (*string != '\0') {
696 if (*string == '\n' || *string == '\r' || *string == ':') {
697 if (*string == '\n' || *string == '\r') {
703 while (*string == '\t') {