/* General includes */
#include <sys/types.h>
#include <stdlib.h>
#include <signal.h>
#include <limits.h>
#include <sys/socket.h>
 
/* Apache includes */
#include "httpd.h"
#include "http_conf_globals.h"
#include "http_config.h"
#include "http_log.h"
#include "scoreboard.h"

/* SNMP includes */
#include "ucd-snmp-config.h"
#include "asn1.h"
#include "snmp.h"
#include "snmp_api.h"
#include "default_store.h"
#include "ds_agent.h"

#include "covalent-snmp-config.h"
#include "ietf-mibs/snmp-generic.h"
#include "covalent-snmp-logging.h"
#include "covalent-snmp.h"
#include "covalent-snmp-sconfig.h"
#include "apache-restarts.h"
#include "logging.h"
#include "covalent-mibs/apache-config-mib.h"

/* Is really define somewhere in a library */
extern int errno;

server_rec *www_services = NULL; /* www service records */
int nr_www_services = 0; /* number of www services */
int derived_hard_server_limit = 0;

/* The 2 file descriptors for the pipe */
int logpath_fd[2] = {-1, -1};

/* This function forks an SNMP-agent that is in waiting mode */
/* It waits until the first log message with special format comes */
/* from a child that initiates the logged message at child_init. */
static void go_into_waiting_mode(server_rec *s, pool *p)
{
    int num_recved = 0;
    snmp_generic_log_record mesg;
    int numfds;
    fd_set fdset;
    int count;
    int pid, ppid;

    /* on the first call to ap_init_modules
     * ap_restart_time has not been set.
     * before the second call it is set in standalone_main
     * and remains != 0 for the lifetime of the process.
     */

    if (ap_restart_time == 0) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, s,
                     "SNMP: Deferring waiting mode");
        return;
    }

    pipe(logpath_fd);

    if ((pid = fork())) {
        ap_note_subprocess(p, pid, kill_never);
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, s,
                     "SNMP: In waiting mode (pid=%d)", pid);

	return;
    }

    /* Child process. */
    while (num_recved < ap_daemons_to_start){
        numfds = logpath_fd[0]+1;
        FD_ZERO(&fdset);
        FD_SET(logpath_fd[0], &fdset);
        count = select(numfds, &fdset, NULL, NULL, NULL);
        if (count > 0){
            if (FD_ISSET(logpath_fd[0], &fdset)) { 
                if (read(logpath_fd[0], &mesg, sizeof(mesg)) == sizeof(mesg)) {
                    if (mesg.type == STARTUP) {
			num_recved++;
                    }
                }
            }
        } 
    }
    sleep(1);
    ppid = getppid();
    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, s,
                 "SNMP: Done waiting, sending SIGUSR1 to ppid=%d", ppid);
    kill(ppid, SIGUSR1);
    exit(0);
}

/*
 * Function:
 *	void covalent_snmp_module_init(server_rec *s, pool *p)
 * Description:
 *	Initialisation for 'apache-servers' to use the SNMP-module. This does
 *	NOT intialise the complete SNMP-AGENT. The SNMP-AGENT allocates some
 *	memory and it is not required that the rest of the http-server this
 *	knows.
 */
void	covalent_snmp_module_init(server_rec *s, pool *p)
{
    server_rec *s_ptr;
    int i, rgen=0;
    int snmp_init_state;

    if (derived_hard_server_limit == 0) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, s,
                     "SNMP: "
                     "The MaxClients directive needs "
                     "to be put after the last 'AddModule' or 'LoadModule' "
                     "directive in order to have the "
                     "SNMP agent working correctly.");
        exit(1);
    }
    /* We skip the first one. That one is initialised by 'create_server_config'.
     */
    for (s_ptr = s->next, i = 1 ; s_ptr ; i++, s_ptr = s_ptr->next) {
        covalent_snmp_setup_sconf_for_vhosts(p, s_ptr);
        set_sconfig_wwwServiceIndex(s_ptr, i);
    }
#if defined(MAX_WWW_SERVICES) && MAX_WWW_SERVICES > 0
    if (i > MAX_WWW_SERVICES + 1) {
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, s,
                     "SNMP: Exceeding the maximum allowed www services '%d' (exiting).", MAX_WWW_SERVICES);
	exit(1);
    }
#endif
    nr_www_services = i;
    ap_add_version_component(COVALENT_SNMP_VERSION);

    if (!ap_exists_scoreboard_image()) {
        snmp_init_state = 0;
    }
    else {
        rgen = snmp_init_state =
            ap_scoreboard_image->global.running_generation;

        /* WTF: on suse rgen == 0 */
        if (snmp_init_state == 0) {
            snmp_init_state = ap_my_generation;
        }
    }

    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, s,
                 "SNMP: init state=%d, generation=%d/%d, scoreboard=0x%lx",
                 snmp_init_state,
                 rgen, ap_my_generation,
                 (unsigned long)ap_scoreboard_image);

    /* Here we handle the logpath for www-requests. */
    shared_create(ap_pstrcat(p, ds_get_string(DS_LIBRARY_ID,
                                              DS_LIB_PERSISTENT_DIR),
                             "/shared", NULL));
    open_logpath(snmp_init_state, s);
    if (snmp_init_state == 0) {
        /*        signal(SIGCHLD, SIG_IGN);  */
        go_into_waiting_mode(s, p); 
    }
    if (snmp_init_state == 1) {
#ifdef COVALENT_APACHE_CONFIG_MIB
        init_covalent_apache_config_mib_setup_shmem(s, p);
#endif /* COVALENT_APACHE_CONFIG_MIB */
        covalent_snmp_main(s, p);

        /* close the pipe, we don't need it anymore */
        close(logpath_fd[0]);
        logpath_fd[0] = -1;

        close(logpath_fd[1]);
        logpath_fd[1] = -1;

        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, s,
                     "SNMP: closed startup pipe");
    }
    if (snmp_init_state > 1) {

#ifdef COVALENT_APACHE_CONFIG_MIB
	init_covalent_apache_config_mib_parent_copy(s, p); 
#endif /* COVALENT_APACHE_CONFIG_MIB */
        covalent_snmp_main(s, p);
       /* shared_close(); */
    }
    return;
}

/*
 * Function:
 *	int covalent_snmp_logger(request_rec *r)
 * Description:
 * This function fills the document log structure at the http-side
 * of the 'logpath' and sends (datagram) it to the snmp-side.
 */
int
covalent_snmp_logger(request_rec *r)
{
    int i;
    const char *statusMsg;
    snmp_generic_log_record logMsg;
    www_log_record *document = &(logMsg.data.www);

    if (r == NULL) {
	return(!OK);
    }

    while (r->next) {
        r = r->next;
    }
    
    logMsg.type = WWW;
    /* First we clear the wwwStatusMsg to nothing */
    strcpy(document->wwwStatusMsg, "");
    document->wwwServiceIndex = get_sconfig_wwwServiceIndex(r->server);
    document->wwwRequestTime = r->request_time;
    document->wwwBytesIn = r->read_length;
    if (!r->sent_bodyct) {
	document->wwwBytesOut = 0;
    } else {
	ap_bgetopt(r->connection->client, BO_BYTECT, &document->wwwBytesOut);
    }
    strncpy(document->wwwDocName, r->uri, sizeof(document->wwwDocName));
    document->wwwRequestInType = -1;
    for (i=0 ; i<(METHODS-1) ; i++) {
	if(!strcmp(r->method, requestTypes[i])) {
	    document->wwwRequestInType = i;
	    break;
	}
    }
    if (document->wwwRequestInType == -1) {
	snprintf(document->wwwStatusMsg, MAX_WWWDOCLASTNSTATUSMSG,
                 "Not supported method %s", r->method);
    }
    document->wwwResponseOutType = r->status;
    statusMsg = ap_table_get(r->notes, "error-notes");
    if (statusMsg) {
        strncpy(document->wwwStatusMsg, statusMsg,
                (MAX_WWWDOCLASTNSTATUSMSG - 1));
    }
    statusMsg = ap_table_get(r->notes, "SNMP_STATUS_MSG");
    if (statusMsg) {
	strncpy(document->wwwStatusMsg, statusMsg,
                (MAX_WWWDOCLASTNSTATUSMSG - 1));
    }
    if (0 >= snmp_generic_logger(&logMsg)) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r->server,
                     "SNMP: generic logger: Could not log www-info\n");
    }
    return(OK);
}


const char *
set_snmp_config_dir(cmd_parms *cmd, void *dummy, char *arg)
{
    const char *dir = ap_server_root_relative(cmd->pool, arg);

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

const char *
set_snmp_persistent_dir(cmd_parms *cmd, void *dummy, char *arg)
{
    const char *dir = ap_server_root_relative(cmd->pool, arg);

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

const char *
set_snmp_user(cmd_parms *cmd, void *dummy, char *arg)
{
    ds_set_int(DS_APPLICATION_ID, DS_AGENT_USERID, ap_uname2id(arg));
    return NULL;
}

const char *set_snmp_max_clients_limit (cmd_parms *cmd, void *dummy, char *arg)
{
    if (derived_hard_server_limit == 0) {
        derived_hard_server_limit = ap_daemons_limit;
    }
    ap_daemons_limit = atoi(arg);
    if ((ap_daemons_limit > derived_hard_server_limit) &&
        (ap_daemons_limit > HARD_SERVER_LIMIT)) {
        fprintf(stderr,
                "WARNING: MaxClients of %d exceeds compile time limit "
                "of %d the apache server or the SNMP module,\n",
                ap_daemons_limit,
                ((derived_hard_server_limit < HARD_SERVER_LIMIT) ?
                 derived_hard_server_limit : HARD_SERVER_LIMIT));

        fprintf(stderr, " lowering MaxClients to %d.  To increase, please "
                "see the\n", ((derived_hard_server_limit < HARD_SERVER_LIMIT) ?
                              derived_hard_server_limit : HARD_SERVER_LIMIT));

        fprintf(stderr, " HARD_SERVER_LIMIT define in src/include/httpd.h.\n");

        ap_daemons_limit = ((derived_hard_server_limit < HARD_SERVER_LIMIT) ?
                            derived_hard_server_limit : HARD_SERVER_LIMIT);
    }
    else if (ap_daemons_limit < 1) {
        fprintf(stderr, "WARNING: Require MaxClients > 0, setting to 1\n");
        ap_daemons_limit = 1;
    }
    return NULL;
}

const char *
set_snmp_request_types(cmd_parms *cmd, void *dummy, char *arg)
{
    int i;
    char *reqCode;
    int j = 0;
    char **tempReqTypes;
    int i2;

    /* Count the current number of request types */
    for (i = 0; requestTypes[i] != NULL; i++);

    tempReqTypes = ap_pcalloc(cmd->pool, i * sizeof(char *));

    j = 0;
    while ((reqCode = ap_getword_white(cmd->pool, (const char **)&arg)) != NULL) {
        int found = 0;
        if (!strcmp(reqCode, "")) {
            break;
        }
        if (j >= i) {
            /* Tried to add more request types than Apache 1.3 understands */
            return "Too many request types.";
        }

        /* Find current code in the current request types list */
        for (i2 = 0; i2 < i; i2++) {
            if (!strcmp(requestTypes[i2], reqCode)) {
                found = 1;
                break;
            }
        }
        if (!found) {
            return "Unrecognized request type.";
        }
        tempReqTypes[i2] = reqCode;
        j++;
    }

    /* Now collapse the TempRespTypes to remove all the empty slots. */
    for (j = 0, i2 = 0; j < i; j++ ) {
        if (tempReqTypes[j] != NULL) {
            requestTypes[i2++] = ap_pstrdup(cmd->pool, tempReqTypes[j]);
        }
    }
    requestTypes[i2] = NULL;
    
    return NULL;
}

const char *
set_snmp_response_types(cmd_parms *cmd, void *dummy, char *arg)
{
    int i;
    char *resCode;
    int j = 0;
    int *tempRespTypes;
    int i2;

    /* Count the current number of response types */
    for (i = 0; responseTypes[i] > 0; i++);

    tempRespTypes = ap_pcalloc(cmd->pool, i * sizeof(int));

    j = 0;
    while ((resCode = ap_getword_white(cmd->pool, (const char **)&arg)) != NULL) {
        int current = 0;
        
        if (!strcmp(resCode, "")) {
            break;
        }
        if (j >= i) {
            /* Tried to add more response codes than Apache 1.3 understands */
            return "Too many response codes.";
        }

        current = strtol(resCode, (char **)NULL, 10);
        /* Find current code in the current response types list */
        for (i2 = 0; i2 < i; i2++) {
            if (responseTypes[i2] == current) {
                break;
            }
        }
        if (responseTypes[i2] != current) {
            return "Unrecognized response code.";
        }
        tempRespTypes[i2] = current;
        j++;
    }

    /* Now collapse the TempRespTypes to remove all the empty slots. */
    for (j = 0, i2 = 0; j < i; j++ ) {
        if (tempRespTypes[j] != 0) {
            responseTypes[i2++] = tempRespTypes[j];
        }
    }
    responseTypes[i2] = 0;
    
    return NULL;
}

/*
 * Function:
 *	void covalent_snmp_child_init(server_rec *s, pool *p)
 * Description:
 *      First function called in child processes.  Currently, this funciton
 *      just registers a cleanup for the snmp_agent.  This ensures that the
 *      agent dies when the child is terminated. 
 */
void covalent_snmp_child_init(server_rec *s, pool *p)
{
    snmp_generic_log_record logrec;

    if (logpath_fd[0] >= 0) {
        memset(&logrec, 0, sizeof(snmp_generic_log_record));
        logrec.type = STARTUP;
        write(logpath_fd[1], (char *)&logrec, sizeof(snmp_generic_log_record));
    }
}
