/* @(#) $Id: logcollector.c,v 1.37 2006/10/13 14:55:39 dcid Exp $ */

/* Copyright (C) 2003-2006 Daniel B. Cid <dcid@ossec.net>
 * All right reserved.
 *
 * This program is a free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public
 * License (version 2) as published by the FSF - Free Software
 * Foundation
 */




#include "shared.h"

#include "logcollector.h"

int _cday = 0;
int update_fname(int i);


/** void LogCollectorStart() v0.4
 * Handle file management.
 */
void LogCollectorStart()
{
    int i = 0, r = 0;
    int max_file = 0;
    int f_check = 0;
    
    /* To check for inode changes */
    struct stat tmp_stat;
    
                
    #ifndef WIN32
    int int_error = 0;
    struct timeval fp_timeout;
    #endif

    debug1("%s: DEBUG: Entering LogCollectorStart().", ARGV0);
    
    /* Initializing each file and structure */
    for(i = 0;;i++)
    {
        if(logff[i].file == NULL)
            break;
       
        if(strcmp(logff[i].logformat,"eventlog") == 0)
        {
            #ifdef WIN32
            verbose(READING_EVTLOG, ARGV0, logff[i].file);
            win_startel(logff[i].file);
            #endif
            logff[i].file = NULL;
            logff[i].fp = NULL;
        }
        
        else
        {
            /* Initializing the files */    
            if(logff[i].ffile)
            {
                /* Day must be zero for all files to be initialized */
                _cday = 0;
                if(update_fname(i))
                {
                    handle_file(i, 1, 1);
                }
                else
                {
                    ErrorExit(PARSE_ERROR, ARGV0, logff[i].ffile);
                }
                    
            }
            else
            {
                handle_file(i, 1, 1);
            }
            
            verbose(READING_FILE, ARGV0, logff[i].file);
            
            /* Getting the log type */
            if(strcmp("snort-full", logff[i].logformat) == 0)
            {
                logff[i].read = (void *)read_snortfull;
            }
            else if(strcmp("nmapg", logff[i].logformat) == 0)
            {
                logff[i].read = (void *)read_nmapg;
            }
            else
            {
                logff[i].read = (void *)read_syslog;
            }

            /* More tweaks for Windows. For some reason IIS places
             * some wierd characters at the end of the files and getc
             * always returns 0 (even after clearerr).
             */
            #ifdef WIN32
            {
                if(logff[i].fp)
                {
                    logff[i].read(i, &r, 1);
                }
            }
            #endif
        }
    }


    /* Start up message */
    verbose(STARTUP_MSG, ARGV0, getpid());
        
    max_file = i -1;


    /* Cannot be zero */
    if(max_file < 0)
    {
        max_file = 0;
    }
    
    
    /* Daemon loop */
    while(1)
    {
        #ifndef WIN32
        fp_timeout.tv_sec = loop_timeout;
        fp_timeout.tv_usec = 0;

        /* Waiting for the select timeout */ 
        if ((r = select(0, NULL, NULL, NULL, &fp_timeout)) < 0)
        {
            merror(SELECT_ERROR, ARGV0);
            int_error++;

            if(int_error >= 5)
            {
                ErrorExit(SYSTEM_ERROR, ARGV0);
            }
            continue;
        }
        #else
        /* Windows don't like select that way */
        Sleep((loop_timeout + 4) * 1000);
        #endif

        #ifdef WIN32
        /* Check for messages in the event viewer */
        win_readel();
        #endif
        
        f_check++;

        
        /* Checking which file is available */
        for(i = 0; i <= max_file; i++)
        {
            if(!logff[i].fp)
                continue;

            /* Windows with IIS logs is very strange.
             * For some reason it always returns 0 (not EOF)
             * the fgetc. To solve this problem, we always
             * pass it to the function pointer directly.
             */
            #ifndef WIN32
            /* We check for the end of file. If is returns EOF,
             * we don't attempt to read it.
             */
            if((r = fgetc(logff[i].fp)) == EOF)
            {
                clearerr(logff[i].fp);
                continue;
            }


            /* If it is not EOF, we need to return the read character */
            ungetc(r, logff[i].fp);
            #endif


            /* Finally, send to the function pointer to read it */
            logff[i].read(i, &r, 0);


            /* Checking for error */
            if(!ferror(logff[i].fp))
            {
                /* Clearing EOF */
                clearerr(logff[i].fp);

                /* Parsing error */
                if(r != 0)
                {
                    logff[i].ign++;
                }
            }
            /* If ferror is set */
            else
            {
                merror(FREAD_ERROR, ARGV0, logff[i].file);
                #ifndef WIN32
                if(fseek(logff[i].fp, 0, SEEK_END) < 0)
                #else
                if(1)
                #endif
                {
                    merror(FSEEK_ERROR, ARGV0, logff[i].file);

                    /* Closing the file */
                    if(logff[i].fp)
                        fclose(logff[i].fp);
                    logff[i].fp = NULL;

                    /* Trying to open it again */
                    if(handle_file(i, 1, 1) != 0)
                    {
                        logff[i].ign++;
                        continue;
                    }
                    
                    #ifdef WIN32
                    logff[i].read(i, &r, 1);
                    #endif
                }

                /* Increase the error count  */
                logff[i].ign++;
                clearerr(logff[i].fp);
            }
        }

        
        /* Only check bellow if check > VCHECK_FILES */
        if(f_check <= VCHECK_FILES)
            continue;

            
        /* Send keep alive message */
        SendMSG(logr_queue, "--MARK--", "ossec-keepalive", LOCALFILE_MQ);


        /* Zeroing f_check */    
        f_check = 0;


        /* Checking if any file has been renamed/removed */
        for(i = 0; i <= max_file; i++)
        {
            /* These are the windows logs or ignored files */
            if(!logff[i].file)
                continue;
            
            
            /* Files with date -- check for day change */
            if(logff[i].ffile)
            {
                if(update_fname(i))
                {
                    if(logff[i].fp)
                    {
                        fclose(logff[i].fp);
                    }
                    logff[i].fp = NULL;
                    handle_file(i, 0, 1);
                    continue;
                }

                /* Variable file name */
                else if(!logff[i].fp)
                {
                    handle_file(i, 0, 0);
                    continue;
                }
            }
            
            
            /* Check for file change -- if the file is open already */
            if(logff[i].fp)
            {
                if(stat(logff[i].file, &tmp_stat) == -1)
                {
                    fclose(logff[i].fp);
                    logff[i].fp = NULL;
                    
                    merror(FILE_ERROR, ARGV0, logff[i].file);
                }

                else if(logff[i].fd != tmp_stat.st_ino)
                {
                    debug1("%s: DEBUG: File inode changed. %s",
                            ARGV0, logff[i].file);
                    
                    fclose(logff[i].fp);
                    logff[i].fp = NULL;
                    handle_file(i, 0, 1);
                    continue;
                }
            }
            
            
            /* Too many errors for the file */ 
            if(logff[i].ign > open_file_attempts)
            {
                /* 999 Maximum ignore */
                if(logff[i].ign == 999)
                {
                    continue;
                }
                
                merror(LOGC_FILE_ERROR, ARGV0, logff[i].file);
                if(logff[i].fp)
                    fclose(logff[i].fp);
                    
                logff[i].fp = NULL;


                /* If the file has a variable date, ignore it for
                 * today only.
                 */
                if(!logff[i].ffile)
                {
                    logff[i].file = NULL;
                }
                logff[i].ign = 999;
                continue;
            }
           
           
            /* File not opened */ 
            if(!logff[i].fp)
            {
                if(logff[i].ign >= 999)
                    continue;
                else
                {
                    /* Try for a few times to open the file */
                    if(handle_file(i, 1, 1) < 0)
                    {
                        logff[i].ign++;
                    }
                    continue;
                }
            }
        }
    }
}


/**int update_fname(int i): updates file name */
int update_fname(int i)
{
    struct tm *p;
    time_t __ctime = time(0);
    
    char lfile[OS_FLSIZE + 1];
    size_t ret;

    p = localtime(&__ctime);

    /* Handle file */
    if(p->tm_mday == _cday)
    {
        return(0);
    }


    lfile[OS_FLSIZE] = '\0';
    ret = strftime(lfile, OS_FLSIZE, logff[i].ffile, p);
    if(ret == 0)
    {
        ErrorExit(PARSE_ERROR, ARGV0, logff[i].ffile);
    }
    
    
    /* Update the file name */
    if(strcmp(lfile, logff[i].file) != 0)
    {
        os_free(logff[i].file);

        os_strdup(lfile, logff[i].file);    

        verbose(VAR_LOG_MON, ARGV0, logff[i].file);
        
        /* Setting cday to zero because other files may need
         * to be changed.
         */
        _cday = 0;
        return(1);
    }

    _cday = p->tm_mday;
    return(0);
}


/* handle_file: Open, get the fileno, seek to the end and update mtime */
int handle_file(int i, int do_fseek, int do_log)
{
    int fd;
    struct stat stat_fd;
    
    /* We must be able to open the file, fseek and get the
     * time of change from it.
     */
    logff[i].fp = fopen(logff[i].file, "r");
    if(!logff[i].fp)
    {
        if(do_log == 1)
        {
            merror(FOPEN_ERROR, ARGV0, logff[i].file);
        }
        return(-1);
    }

    /* Only seek the end of the file if set to. */
    if(do_fseek == 1)
    {
        /* Windows and fseek causes some weird issues.. */
        #ifndef WIN32
        if(fseek(logff[i].fp, 0, SEEK_END) < 0)
        {
            merror(FSEEK_ERROR, ARGV0,logff[i].file);
            fclose(logff[i].fp);
            logff[i].fp = NULL;
            return(-1);
        }
        #endif
    }
    
    
    /* Getting inode number for fp */
    fd = fileno(logff[i].fp);
    if(fstat(fd, &stat_fd) == -1)
    {
        merror(FILE_ERROR,ARGV0,logff[i].file);
        fclose(logff[i].fp);
        logff[i].fp = NULL;
        return(-1);
    }
    logff[i].fd = stat_fd.st_ino;


    /* Setting ignore to zero */
    logff[i].ign = 0;
    return(0);
}


/* EOF */
