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

#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include <net-snmp/library/snmp_logging.h>
#include "agent/nsLogging.h"

netsnmp_feature_require(logging_external)
#ifndef NETSNMP_NO_WRITE_SUPPORT
netsnmp_feature_require(table_iterator_insert_context)
#endif /* NETSNMP_NO_WRITE_SUPPORT */

/*
 * OID and columns for the logging table.
 */

#define  NSLOGGING_TYPE		3
#define  NSLOGGING_MAXLEVEL	4
#define  NSLOGGING_STATUS	5

void
init_nsLogging(void)
{
    netsnmp_table_registration_info *table_info;
    netsnmp_iterator_info           *iinfo;

    const oid nsLoggingTable_oid[] = { 1, 3, 6, 1, 4, 1, 8072, 1, 7, 2, 1};

    /*
     * Register the table.
     * We need to define the column structure and indexing....
     */

    table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
    if (!table_info) {
        return;
    }
    netsnmp_table_helper_add_indexes(table_info, ASN_INTEGER,
                                                 ASN_PRIV_IMPLIED_OCTET_STR, 0);
    table_info->min_column = NSLOGGING_TYPE;
    table_info->max_column = NSLOGGING_STATUS;


    /*
     * .... and the iteration information ....
     */
    iinfo      = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
    if (!iinfo) {
        SNMP_FREE(table_info);
        return;
    }
    iinfo->get_first_data_point = get_first_logging_entry;
    iinfo->get_next_data_point  = get_next_logging_entry;
    iinfo->table_reginfo        = table_info;


    /*
     * .... and register the table with the agent.
     */
    netsnmp_register_table_iterator2(
        netsnmp_create_handler_registration(
            "tzLoggingTable", handle_nsLoggingTable,
            nsLoggingTable_oid, OID_LENGTH(nsLoggingTable_oid),
            HANDLER_CAN_RWRITE),
        iinfo);
}


/*
 * nsLoggingTable handling
 */

netsnmp_variable_list *
get_first_logging_entry(void **loop_context, void **data_context,
                      netsnmp_variable_list *index,
                      netsnmp_iterator_info *data)
{
    long temp;
    netsnmp_log_handler  *logh_head = get_logh_head();
    if ( !logh_head )
        return NULL;

    temp = logh_head->priority;
    snmp_set_var_value(index, (u_char*)&temp,
		                 sizeof(temp));
    if ( logh_head->token )
        snmp_set_var_value(index->next_variable, (const u_char*)logh_head->token,
		                                   strlen(logh_head->token));
    else
        snmp_set_var_value(index->next_variable, NULL, 0);
    *loop_context = (void*)logh_head;
    *data_context = (void*)logh_head;
    return index;
}

netsnmp_variable_list *
get_next_logging_entry(void **loop_context, void **data_context,
                      netsnmp_variable_list *index,
                      netsnmp_iterator_info *data)
{
    long temp;
    netsnmp_log_handler *logh = (netsnmp_log_handler *)*loop_context;
    logh = logh->next;

    if ( !logh )
        return NULL;

    temp = logh->priority;
    snmp_set_var_value(index, (u_char*)&temp,
		                 sizeof(temp));
    if ( logh->token )
        snmp_set_var_value(index->next_variable, (const u_char*)logh->token,
		                                   strlen(logh->token));
    else
        snmp_set_var_value(index->next_variable, NULL, 0);
    *loop_context = (void*)logh;
    *data_context = (void*)logh;
    return index;
}


int
handle_nsLoggingTable(netsnmp_mib_handler *handler,
                netsnmp_handler_registration *reginfo,
                netsnmp_agent_request_info *reqinfo,
                netsnmp_request_info *requests)
{
    long temp;
    netsnmp_request_info       *request     = NULL;
    netsnmp_table_request_info *table_info  = NULL;
    netsnmp_log_handler        *logh        = NULL;
    netsnmp_variable_list      *idx         = NULL;

    switch (reqinfo->mode) {

    case MODE_GET:
        for (request=requests; request; request=request->next) {
            if (request->processed != 0)
                continue;
            logh = (netsnmp_log_handler*)netsnmp_extract_iterator_context(request);
            table_info  =                netsnmp_extract_table_info(request);

            switch (table_info->colnum) {
            case NSLOGGING_TYPE:
                if (!logh) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHINSTANCE);
                    continue;
		}
		temp = logh->type;
	        snmp_set_var_typed_value(request->requestvb, ASN_INTEGER,
                                         (u_char*)&temp,
                                            sizeof(temp));
	        break;

            case NSLOGGING_MAXLEVEL:
                if (!logh) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHINSTANCE);
                    continue;
		}
		temp = logh->pri_max;
	        snmp_set_var_typed_value(request->requestvb, ASN_INTEGER,
                                         (u_char*)&temp,
                                            sizeof(temp));
	        break;

            case NSLOGGING_STATUS:
                if (!logh) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHINSTANCE);
                    continue;
		}
		temp = (logh->type ?
	                   (logh->enabled ?
	                      RS_ACTIVE:
	                      RS_NOTINSERVICE) :
	                    RS_NOTREADY);
	        snmp_set_var_typed_value(request->requestvb, ASN_INTEGER,
                                         (u_char*)&temp, sizeof(temp));
	        break;

            default:
                netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
                continue;
	    }
	}
	break;


#ifndef NETSNMP_NO_WRITE_SUPPORT
    case MODE_SET_RESERVE1:
        for (request=requests; request; request=request->next) {
            if ( request->status != 0 ) {
                return SNMP_ERR_NOERROR;	/* Already got an error */
            }
            logh = (netsnmp_log_handler*)netsnmp_extract_iterator_context(request);
            table_info  =                 netsnmp_extract_table_info(request);

            switch (table_info->colnum) {
            case NSLOGGING_TYPE:
                if ( request->requestvb->type != ASN_INTEGER ) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_ERR_WRONGTYPE);
                    return SNMP_ERR_WRONGTYPE;
                }
                if (*request->requestvb->val.integer < 0 ) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_ERR_WRONGVALUE);
                    return SNMP_ERR_WRONGVALUE;
                }
		/*
		 * It's OK to create a new logging entry
		 *  (either in one go, or built up using createAndWait)
		 *  but it's not possible to change the type of an entry
		 *  once it's been created.
		 */
                if (logh && logh->type) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_ERR_NOTWRITABLE);
                    return SNMP_ERR_NOTWRITABLE;
		}
	        break;

            case NSLOGGING_MAXLEVEL:
                if ( request->requestvb->type != ASN_INTEGER ) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_ERR_WRONGTYPE);
                    return SNMP_ERR_WRONGTYPE;
                }
                if (*request->requestvb->val.integer < 0 ||
                    *request->requestvb->val.integer > 7 ) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_ERR_WRONGVALUE);
                    return SNMP_ERR_WRONGVALUE;
                }
	        break;

            case NSLOGGING_STATUS:
                if ( request->requestvb->type != ASN_INTEGER ) {
                    netsnmp_set_request_error(reqinfo, request, SNMP_ERR_WRONGTYPE);
                    return SNMP_ERR_WRONGTYPE;
                }
		switch ( *request->requestvb->val.integer ) {
                case RS_ACTIVE:
                case RS_NOTINSERVICE:
                    /*
		     * Can only work on existing rows
		     */
                    if (!logh) {
                        netsnmp_set_request_error(reqinfo, request,
                                                  SNMP_ERR_INCONSISTENTVALUE);
                        return SNMP_ERR_INCONSISTENTVALUE;
                    }
	            break;

                case RS_CREATEANDWAIT:
                case RS_CREATEANDGO:
                    /*
		     * Can only work with new rows
		     */
                    if (logh) {
                        netsnmp_set_request_error(reqinfo, request,
                                                  SNMP_ERR_INCONSISTENTVALUE);
                        return SNMP_ERR_INCONSISTENTVALUE;
                    }

                    /*
                     *  Normally, we'd create the row at a later stage
                     *   (probably during the RESERVE2 or ACTION passes)
                     *
                     *  But we need to check that the values are
                     *   consistent during the ACTION pass (which is the
                     *   latest that an error can be safely handled),
                     *   so the values all need to be set up before this
                     *      (i.e. during the RESERVE2 pass)
                     *  So the new row needs to be created before that
                     *   in order to have somewhere to put them.
                     *
                     *  That's why we're doing this here.
                     */
                    idx = table_info->indexes;
	            logh = netsnmp_register_loghandler(
				    /* not really, but we need a valid type */
				    NETSNMP_LOGHANDLER_STDOUT,
				    *idx->val.integer);
                    if (!logh) {
                        netsnmp_set_request_error(reqinfo, request,
                                                  SNMP_ERR_GENERR); /* ??? */
                        return SNMP_ERR_GENERR;
                    }
                    idx = idx->next_variable;
	            logh->type  = 0;
	            logh->token = strdup((char *) idx->val.string);
                    netsnmp_insert_iterator_context(request, (void*)logh);
	            break;

                case RS_DESTROY:
                    /*
		     * Can work with new or existing rows
		     */
                    break;

                case RS_NOTREADY:
		default:
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_ERR_WRONGVALUE);
                    return SNMP_ERR_WRONGVALUE;
                }
                break;

            default:
                netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT);
                return SNMP_NOSUCHOBJECT;
	    }
	}
	break;


    case MODE_SET_RESERVE2:
        for (request=requests; request; request=request->next) {
            if ( request->status != 0 ) {
                return SNMP_ERR_NOERROR;	/* Already got an error */
            }
            logh = (netsnmp_log_handler*)netsnmp_extract_iterator_context(request);
            table_info  =                 netsnmp_extract_table_info(request);

            switch (table_info->colnum) {
            case NSLOGGING_TYPE:
                /*
		 * If we're creating a row using createAndGo,
		 * we need to set the type early, so that we
		 * can validate it in the ACTION pass.
		 *
		 * Remember that we need to be able to reverse this
		 */
                if ( logh )
                    logh->type = *request->requestvb->val.integer;
	        break;
            /*
	     * Don't need to handle nsLogToken or nsLogStatus in this pass
	     */
	    }
	}
	break;

    case MODE_SET_ACTION:
        for (request=requests; request; request=request->next) {
            if (request->processed != 0)
                continue;
            if ( request->status != 0 ) {
                return SNMP_ERR_NOERROR;	/* Already got an error */
            }
            logh = (netsnmp_log_handler*)netsnmp_extract_iterator_context(request);
            table_info  =                 netsnmp_extract_table_info(request);

            switch (table_info->colnum) {
            case NSLOGGING_STATUS:
                /*
		 * This is where we can check the internal consistency
		 * of the request.  Basically, for a row to be marked
		 * 'active', then there needs to be a valid type value.
		 */
		switch ( *request->requestvb->val.integer ) {
                case RS_ACTIVE:
                case RS_CREATEANDGO:
                    if ( !logh || !logh->type ) {
                        netsnmp_set_request_error(reqinfo, request,
                                                  SNMP_ERR_INCONSISTENTVALUE);
                        return SNMP_ERR_INCONSISTENTVALUE;
		    }
	            break;
		}
	        break;
            /*
	     * Don't need to handle nsLogToken or nsLogType in this pass
	     */
	    }
	}
	break;

    case MODE_SET_FREE:
    case MODE_SET_UNDO:
        /*
         * If any resources were allocated in either of the
         *  two RESERVE passes, they need to be released here,
         *  and any assignments (in RESERVE2) reversed.
         *
         * Nothing additional will have been done during ACTION
         *  so this same code can do for UNDO as well.
         */
        for (request=requests; request; request=request->next) {
            if (request->processed != 0)
                continue;
            logh = (netsnmp_log_handler*)netsnmp_extract_iterator_context(request);
            table_info  =                 netsnmp_extract_table_info(request);

            switch (table_info->colnum) {
            case NSLOGGING_TYPE:
                /*
		 * If we've been setting the type, and the request
		 * has failed, then revert to an unset type.
		 *
		 * We need to be careful here - if the reason it failed is
		 *  that the type was already set, then we shouldn't "undo"
		 *  the assignment (since it won't actually have been made).
		 *
		 * Check the current value against the 'new' one.  If they're
		 * the same, then this is probably a successful assignment,
		 * and the failure was elsewhere, so we need to undo it.
		 *  (Or else there was an attempt to write the same value!)
		 */
                if ( logh && logh->type == *request->requestvb->val.integer )
                    logh->type = 0;
	        break;

            case NSLOGGING_STATUS:
                temp = *request->requestvb->val.integer;
                if ( logh && ( temp == RS_CREATEANDGO ||
                               temp == RS_CREATEANDWAIT)) {
		    netsnmp_remove_loghandler( logh );
		}
	        break;
            /*
	     * Don't need to handle nsLogToken in this pass
	     */
	    }
	}
	break;


    case MODE_SET_COMMIT:
        for (request=requests; request; request=request->next) {
            if (request->processed != 0)
                continue;
            if ( request->status != 0 ) {
                return SNMP_ERR_NOERROR;	/* Already got an error */
            }
            logh = (netsnmp_log_handler*)netsnmp_extract_iterator_context(request);
            if (!logh) {
                netsnmp_set_request_error(reqinfo, request, SNMP_ERR_COMMITFAILED);
                return SNMP_ERR_COMMITFAILED;	/* Shouldn't happen! */
            }
            table_info  =                 netsnmp_extract_table_info(request);

            switch (table_info->colnum) {
            case NSLOGGING_MAXLEVEL:
                logh->pri_max = *request->requestvb->val.integer;
	        break;

            case NSLOGGING_STATUS:
                switch (*request->requestvb->val.integer) {
                    case RS_ACTIVE:
                    case RS_CREATEANDGO:
                        netsnmp_enable_this_loghandler(logh);
                        break;
                    case RS_NOTINSERVICE:
                    case RS_CREATEANDWAIT:
                        netsnmp_disable_this_loghandler(logh);
                        break;
		    case RS_DESTROY:
		        netsnmp_remove_loghandler( logh );
                        break;
		}
	        break;
	    }
	}
	break;
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
    }

    return SNMP_ERR_NOERROR;
}
