/* Portions of this file are subject to the following copyrights.  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 */
/*
 * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */
#include <net-snmp/net-snmp-config.h>

#include "logmatch.h"

#ifdef HAVE_REGEX_H

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <regex.h>
#include <time.h>

#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

#include "util_funcs/header_generic.h"
#include "util_funcs/header_simple_table.h"

/*
 * ------------------------------------------------
 *  This function checks if the filename pattern
 *  contains the % character indicating a variable
 *  filename (i.e. it uses date/time format control
 *  codes, see 'man date') then expands those control
 *  codes based on current time and sets the
 *  filename field in the struct.
 *  Returns 1 if the filename changed, 0 otherwise
 *  -------------------------------------------------
 */

static int
logmatch_update_filename(const char * pattern, char * currentFilename)
{
    time_t t;
    struct tm *tmp;
    char newFilename[256];

    /*
     * -------------------------------------------------------------------
     * if the filename pattern doesn't have the "%" character just return,
     * since there is no need for further processing
     * -------------------------------------------------------------------
     */
    if (strchr(pattern, '%') == NULL) {
        return 0;
    }

    t = time(NULL);
    tmp = localtime(&t);

    if (tmp == NULL) {
        perror("localtime");
        return 0;
    }

    /* result of expansion must fit into newFilename, otherwise returning */
    if (strftime(newFilename, sizeof(newFilename), pattern, tmp) == 0) {
        return 0;
    }

    /* if same as current filename, just return */
    if (strcmp(currentFilename, newFilename) == 0) {
        return 0;
    } else {
        /* otherwise update currentFilename and return 1 */
        strcpy(currentFilename, newFilename);
        return 1;
    }
}

struct logmatchstat {
    char            filenamePattern[256];
    char            filename[256];
    char            regEx[256];
    char            name[256];
    FILE           *logfile;
    long            currentFilePosition;
    unsigned long   globalMatchCounter;
    unsigned long   currentMatchCounter;
    unsigned long   matchCounter;
    regex_t         regexBuffer;
    int             myRegexError;
    int             virgin;
    int             thisIndex;
    int             frequency;
};

#define MAXLOGMATCH   250

static struct logmatchstat logmatchTable[MAXLOGMATCH];
static int                 logmatchCount = 0;

/***************************************************************
*                                                              *
* updateLogmatch                                               *
* this function is called back by snmpd alarms                 *
*                                                              *
***************************************************************/

static void
updateLogmatch(int iindex)
{

    int             matchResultCode;
    char            inbuf[1024];
    char            perfilename[1024];
    FILE           *perfile;
    unsigned long   pos, ccounter, counter;
    int             result;
    int             toobig;
    int             anyChanges = FALSE;
    struct stat     sb;
    char            lastFilename[256];

    /*
     * ------------------------------------ 
     * we can never be sure if this is the  
     * last time we are being called here,  
     * so we always update a persistent     
     * data file with our current file      
     * position                             
     * ------------------------------------ 
     */

    snprintf(perfilename, sizeof(perfilename), "%s/snmpd_logmatch_%s.pos",
             get_persistent_directory(), logmatchTable[iindex].name);

    if (logmatchTable[iindex].virgin) {

        /*
         * ------------------------------------ 
         * this is the first time we are being  
         * called; let's try to find an old     
         * file position stored in a persistent 
         * data file and restore it             
         * ------------------------------------ 
         */

        if ((perfile = fopen(perfilename, "r"))) {

            /*
             * ------------------------------------ 
             * the persistent data file exists so   
             * let's read it out                    
             * ------------------------------------ 
             */


            pos = counter = ccounter = 0;

            if (fscanf(perfile, "%lu %lu %lu %s",
                       &pos, &ccounter, &counter, lastFilename)) {


                /*
                 * ------------------------------------ 
                 * the data could be read; now let's    
                 * try to open the  logfile to be       
                 * scanned                              
                 * ------------------------------------ 
                 */

                if (logmatch_update_filename(logmatchTable[iindex].filenamePattern,
                                             lastFilename) == 0) {

                    /*
                     * ---------------------------------
                     * the filename is still the same as 
                     * the one stored in the persistent
                     * data file.
                     * ---------------------------------
                     */

                    if ((logmatchTable[iindex].logfile =
                        fopen(logmatchTable[iindex].filename, "r"))) {


                        /*
                         * ------------------------------------ 
                         * the log file could be opened; now    
                         * let's try to set the pointer         
                         * ------------------------------------ 
                         */

                        if (!fseek
                            (logmatchTable[iindex].logfile, pos, SEEK_SET)) {


                            /*
                             * ------------------------------------ 
                             * the pointer could be set - this is   
                             * the most that we can do: if the      
                             * pointer is smaller than the file     
                             * size we must assume that the pointer 
                             * still points to where it read the    
                             * file last time; let's restore the    
                             * data                                 
                             * ------------------------------------ 
                             */

                            logmatchTable[iindex].currentFilePosition = pos;
                            logmatchTable[iindex].currentMatchCounter =
                                ccounter;
                        }

                        fclose(logmatchTable[iindex].logfile);
                    }
                }
                logmatchTable[iindex].globalMatchCounter = counter;
            }

            fclose(perfile);
        }

        logmatchTable[iindex].virgin = FALSE;
    }

    /*
     * -------------------------------------------
     * check if a new input file needs to be opened
     * if yes, reset counter and position
     * -------------------------------------------
     */

    if (logmatch_update_filename(logmatchTable[iindex].filenamePattern,
                                 logmatchTable[iindex].filename) == 1) {
        logmatchTable[iindex].currentFilePosition = 0; 
        logmatchTable[iindex].currentMatchCounter = 0;
    }


    /*
     * ------------------------------------ 
     * now the pointer and the counter are  
     * set either zero or reset to old      
     * value; now let's try to read some    
     * data                                 
     * ------------------------------------ 
     */

    if (stat(logmatchTable[iindex].filename, &sb) == 0) {

        if (logmatchTable[iindex].currentFilePosition > sb.st_size) {
            toobig = TRUE;
        } else {
            toobig = FALSE;
        }

        if ((logmatchTable[iindex].logfile =
            fopen(logmatchTable[iindex].filename, "r"))) {

            result =
                fseek(logmatchTable[iindex].logfile,
                      logmatchTable[iindex].currentFilePosition, SEEK_SET);

            if (result || toobig || (errno == EINVAL)
                || feof(logmatchTable[iindex].logfile)) {


                /*
                 * ------------------------------------ 
                 * when we are here that means we       
                 * could't set the file position maybe  
                 * the file was rotated; let's reset    
                 * the filepointer, but not the counter 
                 * ------------------------------------ 
                 */


                logmatchTable[iindex].currentFilePosition = 0;
                logmatchTable[iindex].currentMatchCounter = 0;
                fseek(logmatchTable[iindex].logfile, 0, SEEK_SET);
                anyChanges = TRUE;
            }

            while (fgets
                   (inbuf, sizeof(inbuf), logmatchTable[iindex].logfile)) {

                matchResultCode =
                    regexec(&(logmatchTable[iindex].regexBuffer),
                            inbuf, 0, NULL, REG_NOTEOL);

                if (matchResultCode == 0) {
                    logmatchTable[iindex].globalMatchCounter++;
                    logmatchTable[iindex].currentMatchCounter++;
                    logmatchTable[iindex].matchCounter++;
                    anyChanges = TRUE;
                }
            }

            logmatchTable[iindex].currentFilePosition =
                ftell(logmatchTable[iindex].logfile);
            fclose(logmatchTable[iindex].logfile);
        }
    }


    /*
     * ------------------------------------ 
     * at this point we can be safe that    
     * our current file position is         
     * straightened out o.k. - we never     
     * know if this is the last time we are 
     * being called so save the position    
     * in a file                            
     * ------------------------------------ 
     */

    if (anyChanges && (perfile = fopen(perfilename, "w"))) {


        /*
         * ------------------------------------ 
         * o.k. lets write out our variable     
         * ------------------------------------ 
         */

        fprintf(perfile, "%lu %lu %lu %s\n",
                logmatchTable[iindex].currentFilePosition,
                logmatchTable[iindex].currentMatchCounter,
                logmatchTable[iindex].globalMatchCounter,
                logmatchTable[iindex].filename);

        fclose(perfile);
    }

}


static void
updateLogmatch_Scheduled(unsigned int registrationNumber,
                         struct logmatchstat *logmatchtable)
{
    updateLogmatch(logmatchtable->thisIndex);
}

/***************************************************************
*                                                              *
* logmatch_parse_config                                        *
* parse one line from snmpd.conf                               *
*                                                              *
***************************************************************/

static void
logmatch_parse_config(const char *token, char *cptr)
{

    char space_name;
    char space_path;

    if (logmatchCount < MAXLOGMATCH) {
        logmatchTable[logmatchCount].frequency = 30;
        logmatchTable[logmatchCount].thisIndex = logmatchCount;


        /*
         * ------------------------------------
         * be careful this counter needs to be
         * reset from persistent storage
         * ------------------------------------
         */

        logmatchTable[logmatchCount].globalMatchCounter = 0;
        logmatchTable[logmatchCount].currentMatchCounter = 0;
        logmatchTable[logmatchCount].matchCounter = 0;
        logmatchTable[logmatchCount].virgin = TRUE;
        logmatchTable[logmatchCount].currentFilePosition = 0;


        /*
         * ------------------------------------
         * be careful: the flag 255 must fit to
         * the size of regEx as definded in
         * logmatch.h
         * ------------------------------------
         */

        sscanf(cptr, "%255s%c%255s%c %d %255c\n",
               logmatchTable[logmatchCount].name,
	       &space_name,
               logmatchTable[logmatchCount].filenamePattern,
	       &space_path,
               &(logmatchTable[logmatchCount].frequency),
               logmatchTable[logmatchCount].regEx);

        /* fill in filename with initial data */
        strcpy(logmatchTable[logmatchCount].filename,
               logmatchTable[logmatchCount].filenamePattern);
        logmatch_update_filename(logmatchTable[logmatchCount].filenamePattern,
                                 logmatchTable[logmatchCount].filename);

	/*
	 * Log an error then return if any of the strings scanned in were
	 * larger then they should have been.
	 */
	if (space_name != ' ') {
		snmp_log(LOG_ERR, "logmatch_parse_config: the name scanned " \
		 "in from line %s is too large. logmatchCount = %d\n",
		 cptr, logmatchCount);
		return;
	} else if (space_path != ' ') {
		snmp_log(LOG_ERR, "logmatch_parse_config: the file name " \
		 "scanned in from line %s is too large. logmatchCount = %d\n",
		    cptr, logmatchCount);
		return;
	}

        /*
         * ------------------------------------
         * just to be safe "NULL" the end of
         * the arary regEx as sscanf won't do
         * it with the %c modifier
         * ------------------------------------
         */

        logmatchTable[logmatchCount].regEx[255] = '\0';


        /*
         * ------------------------------------
         * now compile the regular expression
         * ------------------------------------
         */

        logmatchTable[logmatchCount].myRegexError =
            regcomp(&logmatchTable[logmatchCount].regexBuffer,
                    logmatchTable[logmatchCount].regEx,
                    REG_EXTENDED | REG_NOSUB);

        if (logmatchTable[logmatchCount].myRegexError) {
            char regexErrorString[100];
            regerror(logmatchTable[logmatchCount].myRegexError,
                     &logmatchTable[logmatchCount].regexBuffer,
                     regexErrorString, 100);
            snmp_log(LOG_ERR, "Could not process the logmatch regex - %s," \
                     "\n since regcomp() failed with - %s\n",
                     logmatchTable[logmatchCount].regEx, regexErrorString);
        }
        else if (logmatchTable[logmatchCount].frequency > 0) {
            snmp_alarm_register(logmatchTable[logmatchCount].frequency,
                                SA_REPEAT,
                                (SNMPAlarmCallback *) updateLogmatch_Scheduled,
                                &logmatchTable[logmatchCount]);
        }

        logmatchCount++;
    }
}

/***************************************************************
*                                                              *
* logmatch_free_config                                         *
* free memory allocated by this mib module                     *
*                                                              *
***************************************************************/

static void
logmatch_free_config(void)
{
    int             i;

    /*
     * ------------------------------------
     * the only memory we have allocated
     * is the memory allocated by regcomp
     * ------------------------------------
     */

    for (i = 0; i < logmatchCount; i++) {
        if (logmatchTable[i].myRegexError == 0)
            regfree(&logmatchTable[i].regexBuffer);
    }
    logmatchCount = 0;
}


#define LOGMATCH_INFO       0
#define LOGMATCH_INDEX      1
#define LOGMATCH_NAME       2
#define LOGMATCH_FILENAME   3
#define LOGMATCH_REGEX      4
#define LOGMATCH_GLOBALCTR  5
#define LOGMATCH_GLOBALCNT  6
#define LOGMATCH_CURRENTCTR 7
#define LOGMATCH_CURRENTCNT 8
#define LOGMATCH_COUNTER    9
#define LOGMATCH_COUNT      10
#define LOGMATCH_FREQ       11
#define LOGMATCH_ERROR      100
#define LOGMATCH_MSG        101

/*
 * OID functions 
 */

static u_char *
var_logmatch_table(struct variable *vp,
                   oid * name,
                   size_t * length,
                   int exact,
                   size_t * var_len, WriteMethod ** write_method)
{
    static long     long_ret;
    static char     message[1024];
    int             iindex;
    struct logmatchstat *logmatch;

    if (vp->magic == LOGMATCH_INFO) {
        if (header_generic(vp, name, length, exact, var_len, write_method)
            == MATCH_FAILED)
            return (NULL);
    } else {
        if (header_simple_table
            (vp, name, length, exact, var_len, write_method,
             logmatchCount))
            return (NULL);

        iindex = name[*length - 1] - 1;
        logmatch = &logmatchTable[iindex];

        if (logmatch->myRegexError == 0)
            updateLogmatch(iindex);
    }

    switch (vp->magic) {
    case LOGMATCH_INFO:
        long_ret = MAXLOGMATCH;
        return (u_char *) & long_ret;

    case LOGMATCH_INDEX:
        long_ret = iindex + 1;
        return (u_char *) & long_ret;

    case LOGMATCH_NAME:
        *var_len = strlen(logmatch->name);
        return (u_char *) logmatch->name;

    case LOGMATCH_FILENAME:
        *var_len = strlen(logmatch->filename);
        return (u_char *) logmatch->filename;

    case LOGMATCH_REGEX:
        *var_len = strlen(logmatch->regEx);
        return (u_char *) logmatch->regEx;

    case LOGMATCH_GLOBALCTR:
    case LOGMATCH_GLOBALCNT:
        long_ret = (logmatch->globalMatchCounter);
        return (u_char *) & long_ret;

    case LOGMATCH_CURRENTCTR:
    case LOGMATCH_CURRENTCNT:
        long_ret = (logmatch->currentMatchCounter);
        return (u_char *) & long_ret;

    case LOGMATCH_COUNTER:
    case LOGMATCH_COUNT:
        long_ret = (logmatch->matchCounter);
        logmatch->matchCounter = 0;
        return (u_char *) & long_ret;

    case LOGMATCH_FREQ:
        long_ret = logmatch->frequency;
        return (u_char *) & long_ret;

    case LOGMATCH_ERROR:
        if (logmatch->frequency >= 0 && logmatch->myRegexError == 0)
            long_ret = 0;
        else
            long_ret = 1;

        return (u_char *) & long_ret;

    case LOGMATCH_MSG:

        regerror(logmatch->myRegexError, &(logmatch->regexBuffer), message,
                 sizeof(message));

        *var_len = strlen(message);
        return (u_char *) message;

    default:
        DEBUGMSGTL(("snmpd", "unknown sub-id %d in var_logmatch_table\n",
                    vp->magic));
    }

    return NULL;
}

void
init_logmatch(void)
{
    struct variable2 logmatch_info[] = {
        {LOGMATCH_INFO, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 0}
    };

    struct variable2 logmatch_table[] = {
        {LOGMATCH_INDEX, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {1}},
        {LOGMATCH_NAME, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {2}},
        {LOGMATCH_FILENAME, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {3}},
        {LOGMATCH_REGEX, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {4}},
        {LOGMATCH_GLOBALCTR, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {5}},
        {LOGMATCH_GLOBALCNT, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {6}},
        {LOGMATCH_CURRENTCTR, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {7}},
        {LOGMATCH_CURRENTCNT, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {8}},
        {LOGMATCH_COUNTER, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {9}},
        {LOGMATCH_COUNT, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {10}},
        {LOGMATCH_FREQ, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {11}},
        {LOGMATCH_ERROR, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {100}},
        {LOGMATCH_MSG, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         var_logmatch_table, 1, {101}}
    };

    /*
     * Define the OID pointer to the top of the mib tree that we're
     * registering underneath
     */
    oid             logmatch_info_oid[] = { NETSNMP_UCDAVIS_MIB, 16, 1 };
    oid             logmatch_variables_oid[] = { NETSNMP_UCDAVIS_MIB, 16, 2, 1 };

    /*
     * register ourselves with the agent to handle our mib tree
     */
    REGISTER_MIB("ucd-snmp/logmatch", logmatch_info, variable2,
                 logmatch_info_oid);
    REGISTER_MIB("ucd-snmp/logmatch", logmatch_table, variable2,
                 logmatch_variables_oid);

    snmpd_register_config_handler("logmatch", logmatch_parse_config,
                                  logmatch_free_config,
                                  "logmatch name path cycletime regex");

}

#endif /* HAVE_REGEX_H */
