/* $Id: mod-snmpcommon.c 8676 2008-01-17 23:11:17Z ispringer $
 * harrie@covalent.net
 */
/* Apache includes */
#include <httpd.h>
#include <http_config.h>
#include <http_log.h>
#include <scoreboard.h>  /* only needed for pre_mpm declaration */
#include <apr_hash.h>
#include <apr_optional.h>
#include <apr_portable.h>
#ifdef COVL_LICENSING
#include "mod_covalent.h"
#include "covalent_license.h"
#endif

/* UCD-SNMP includes */
#include <ucd-snmp-config.h>
#include <asn1.h>
#include <snmp_api.h>
#include <snmp_vars.h>
#include <snmp_debug.h>
#include <agent_trap.h>
#include <default_store.h>
#include <ds_agent.h>

/* module includes */
#include "covalent-snmp-config.h"
#include "snmpcommon/snmpcommon.h"
#include "www-mib/www-mib.h"


typedef struct {
    int www_index;
    char *www_protocol;
} snmpcommon_sconf;

APR_DECLARE_OPTIONAL_FN(int, covalent_snmp_set_www_service_protocol,
                             (apr_pool_t *p, server_rec *s, char *protocol));

/* The following pool is required just as a dumb work-around
 * where Apache does not create all the sconf parts for 
 * virtual hosts (server_rec's)
 */
module AP_MODULE_DECLARE_DATA snmpcommon_module;
static unsigned int nr_www_services = 0;
static char *http_protocol = "HTTP"; /* Used as default. */
static unsigned long applIndex = 0;

SNMPCOMMON_DECLARE(void)
init_appl_index(void)
{
#ifndef WIN32
    /* We take the PID as applIndex. */
    applIndex = getpid();
#else
    applIndex = GetCurrentProcessId();
#endif
}

SNMPCOMMON_DECLARE(unsigned long)
get_appl_index(void)
{
    return(applIndex);
}

SNMPCOMMON_DECLARE(unsigned int)
get_www_service_total(void)
{
    return(nr_www_services);
}

server_rec * www_service_root;
SNMPCOMMON_DECLARE(void)
set_www_service_root(server_rec *s)
{
    www_service_root = s;
}

SNMPCOMMON_DECLARE(server_rec *)
get_www_service_root()
{
    return(www_service_root);
}

/*
 * Function to set the www protocol for a perticular www service.
 * USE: Optional function by other www protocol modules.
 *      The value of protocol must be static as given.
 */
int
covalent_snmp_set_www_service_protocol(apr_pool_t* p, server_rec *s, char *protocol)
{
    snmpcommon_sconf *ptr = (snmpcommon_sconf *)
                ap_get_module_config(s->module_config, &snmpcommon_module);

    if ((ptr == NULL) || (p == NULL)) {
        ptr = (snmpcommon_sconf *) apr_pcalloc(p, sizeof(snmpcommon_sconf));
            ap_set_module_config(s->module_config, &snmpcommon_module, ptr);
    }
    if (ptr) {
        ptr->www_protocol = protocol;
    } else {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                "SNMP: could not set www service protocol '%s' for '%s'",
                                        protocol, s->server_hostname);
	return(-1);
    }
    return(0);
}

/*
 * Function to retrieve the www protocol for a perticular www service.
 */
SNMPCOMMON_DECLARE(char *)
get_www_service_protocol(const server_rec *s)
{
    snmpcommon_sconf *ptr = (snmpcommon_sconf *)
                ap_get_module_config(s->module_config, &snmpcommon_module);

   return(ptr->www_protocol);
}


/*
 * Function to set the www index for a perticular www service.
 * USE: Only within Covalent SNMP. Just to make sure the numbering is correct.
 */
SNMPCOMMON_DECLARE(int)
set_www_service_index(apr_pool_t *p, server_rec *s, int index)
{
    snmpcommon_sconf *ptr = (snmpcommon_sconf *)
		ap_get_module_config(s->module_config, &snmpcommon_module);

    if ((ptr == NULL) || (p == NULL)) {
        ptr = (snmpcommon_sconf *) apr_pcalloc(p, sizeof(snmpcommon_sconf));
        ap_set_module_config(s->module_config, &snmpcommon_module, ptr);
    }
    if (ptr) {
        ptr->www_index = index;
    } else {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                "SNMP: could not set www service index '%d' for '%s'",
                                        index, s->server_hostname);
	return(-1);
    }
    return(0);
}

/*
 * Function to retrieve the www protocol for a perticular www service.
 */
SNMPCOMMON_DECLARE(unsigned int)
get_www_service_index(const server_rec *s)
{
    snmpcommon_sconf *ptr = (snmpcommon_sconf *)
		ap_get_module_config(s->module_config, &snmpcommon_module);

    if (ptr == NULL) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, s,
                "SNMP: sconfig is not available");
	return(-1);
    }
    return(ptr->www_index);
}

static int
snmpcommon_post_config(apr_pool_t *p, apr_pool_t *ptemp,
                         apr_pool_t *plog, server_rec *s)
{
int i;

#ifdef COVL_LICENSING
    static APR_OPTIONAL_FN_TYPE(covl_register_product) *register_product;


    register_product = APR_RETRIEVE_OPTIONAL_FN(covl_register_product);
    if (!register_product) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
                     "Invalid license");
        return DONE;
    }
    register_product(s, SNMP);
#endif

    init_appl_index();
    set_www_service_root(s);
    for (i = 0 ; s ; i++, s = s->next) {
        set_www_service_index(p, s, i);
    }
    nr_www_services = i;

    return(0);
}

#ifdef WIN32
/* This function exists only on Win32. It is designed to keep the
 * DLL loaded through restarts by increasing the DLL LibraryLoad
 * reference count to the snmpcommon DLL.
 */
int
covalent_snmpcommon_pre_mpm(apr_pool_t *p_snmp, ap_scoreboard_e sb_type)
{
    /* Load the DLL for our module again to increase the ref count so
     * we don't get unloaded when pconf gets cleared. */
    if (snmpcommon_module.dynamic_load_handle == NULL) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
                     0, NULL, "SNMP: snmpcommon must be loaded as a DSO on Win32");
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    else {
        /* reload our module and attach it to the global pool.
         * Win32 will keep a reference count for us, how kind! */
        char fspec[APR_PATH_MAX];
        apr_os_dso_handle_t dso_handle;
        apr_os_dso_handle_get(&dso_handle,
                              snmpcommon_module.dynamic_load_handle);

        if (GetModuleFileName((HMODULE)dso_handle,
                              fspec, APR_PATH_MAX) == 0) {
            apr_status_t rv;
            rv = apr_get_os_error();
            ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL,
                         "SNMP: unable to retrieve DSO filename");
            return rv;
        }

        /* Increase the reference count on this DLL, let it stick around
         * until the process dies. */
        (void) LoadLibrary(fspec);
    }

    return OK;
}
#endif /* WIN32 */

static void
snmpcommon_register_hooks(apr_pool_t *p)
{
    ap_hook_post_config(snmpcommon_post_config, NULL, NULL, APR_HOOK_MIDDLE);
    /* Register other protocols besides HTTP.                       */

#ifdef WIN32
    /* Hook only necessary on Win32 -- keeps the DLL loaded across restarts. */
    ap_hook_pre_mpm(covalent_snmpcommon_pre_mpm, NULL, NULL, APR_HOOK_LAST);
#endif /* WIN32 */

    APR_REGISTER_OPTIONAL_FN(covalent_snmp_set_www_service_protocol);
}

static const char *
set_snmpcommon_config_dir(cmd_parms *cmd, void *dummy, const char *arg)
{
    apr_finfo_t finfo;
    apr_status_t rv;
    const char *dir = ap_server_root_relative(cmd->pool, arg);

    rv = apr_stat(&finfo, dir, APR_FINFO_MIN, cmd->pool);
    if (rv != APR_SUCCESS) {
        return "Failure opening SNMPconf directory";
    }

    ds_set_string(DS_LIBRARY_ID, DS_LIB_CONFIGURATION_DIR, dir);
    return NULL;
}

static const char *
set_snmpcommon_persistent_dir(cmd_parms *cmd, void *dummy, const char *arg)
{
    apr_finfo_t finfo;
    apr_status_t rv;
    const char *dir = ap_server_root_relative(cmd->pool, arg);
    char *tempfn;
    apr_file_t *tempfd;

    rv = apr_stat(&finfo, dir, APR_FINFO_TYPE, cmd->pool);
    /* First make sure the directory exists */
    if (rv != APR_SUCCESS) {
        return "Failure opening SNMPvar directory";
    }
    /* Now make sure that it's a directory */
    if (finfo.filetype != APR_DIR) {
        return "SNMPvar is not a directory";
    }
    /* Now try to create a file in that directory */
    rv = apr_filepath_merge(&tempfn, dir, "snmptempXXXXXX",
                            APR_FILEPATH_TRUENAME, cmd->pool);
    if (rv != APR_SUCCESS) {
        return "Failure detecting SNMPvar write permissions";
    }
    rv = apr_file_mktemp(&tempfd, tempfn,
                         APR_READ|APR_WRITE|APR_DELONCLOSE|APR_CREATE,
                         cmd->pool);
    if (rv != APR_SUCCESS) {
        return "Unable to write to SNMPvar directory";
    }
    apr_file_close(tempfd);

    ds_set_string(DS_LIBRARY_ID, DS_LIB_PERSISTENT_DIR, dir);
    return NULL;
}

static const char *
set_snmpcommon_debug(cmd_parms *cmd, void *dummy, const char *arg)
{
    /* can eat multiple args to specify what we want */
    debug_register_tokens((char *)arg);
    snmp_set_do_debugging(1);
    return NULL;
}

static const char *
set_snmpcommon_agentxsocket(cmd_parms *cmd, void *dummy, const char *arg)
{
    ds_set_string(DS_APPLICATION_ID, DS_AGENT_X_SOCKET, arg);
    return NULL;
}

static const command_rec snmpcommon_cmds[] = {
    AP_INIT_TAKE1("SNMPconf", set_snmpcommon_config_dir,
        NULL, RSRC_CONF,
        "Wrong configuration directory"),
    AP_INIT_TAKE1("SNMPvar", set_snmpcommon_persistent_dir,
        NULL, RSRC_CONF,
        "Wrong persistent directory"),
    AP_INIT_RAW_ARGS("SNMPdebug", set_snmpcommon_debug,
        NULL, RSRC_CONF,
        "Wrong debug token"),
    AP_INIT_TAKE1("SNMPagentxsocket", set_snmpcommon_agentxsocket,
        NULL, RSRC_CONF,
        "Wrong agentx address"),
    { NULL }
};

module AP_MODULE_DECLARE_DATA snmpcommon_module = {
    STANDARD20_MODULE_STUFF,
    NULL,                       /* dir config creater */
    NULL,                       /* dir merger --- default is to override */
    NULL,			/* server config */
    NULL,                       /* merge server config */
    snmpcommon_cmds,            /* command ap_table_t */
    snmpcommon_register_hooks   /* register hooks */
};


