 
/* $Date: 2008-01-18 07:11:17 +0800 (Fri, 18 Jan 2008) $ $Id: agt-main.c 8676 2008-01-17 23:11:17Z ispringer $ harrie@covalent.net
 */
/* 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_core.h"
#include "http_log.h"
#include "http_main.h"

/* SNMP includes */
#include "ucd-snmp-config.h"
#include "asn1.h"
#include "snmp.h"
#include "snmp_api.h"
#include "snmp_agent.h"
#include "snmp_vars.h"
#include "var_struct.h"
#include "ucd_snmpd.h"
#include "agent_trap.h"
#include "read_config.h"
#include "agent_read_config.h"
#include "default_store.h"
#include "ds_agent.h"
#include "snmp_debug.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 "application-mib-modules.h"

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

struct snmp_oc {
    ap_pool *p;
    pid_t pid;
    int fd;
};

int logpath_fd[2]; /* The 2 file descriptors for the pipe */
extern covalent_snmp_logpath_t logpath;

void covalent_snmp_shutdown(int signal)
{
    send_easy_trap (SNMP_TRAP_ENTERPRISESPECIFIC, 3);
    snmp_shutdown("snmpd");
    exit(0);  
}

#ifndef WIN32
#ifndef NO_OTHER_CHILD
/* Blatantly stolen from cgid_maint. */
static void snmpagent_maint(int reason, void *data, ap_wait_t status)
{
    struct snmp_oc *sd = data;

    switch (reason) {
        case OC_REASON_DEATH:
        case OC_REASON_RESTART: {
            /* When the server is going down, unregister the child (which
             * means we'll send it a SIGHUP. */
            ap_unregister_other_child(data);
            break;
        }
        case OC_REASON_LOST:
            /* it would be better to restart just the snmp child
             * process but for now we'll gracefully restart the entire
             * server by sending AP_SIG_GRACEFUL to ourself, the httpd
             * parent process
             */
            kill(getpid(), SIGUSR1);
            break;
        case OC_REASON_UNREGISTER:
            /* we get here when p_snmp is cleaned up; p_snmp gets cleaned
             * up when pconf gets cleaned up
             */
            kill(sd->pid, SIGTERM); /* send signal to daemon telling it to die */
            break;
    }
}
#endif /* APR_HAS_OTHER_CHILD */
#endif /* WIN32 */

static void snmpagent_cleanup(void *data) {
    ap_unregister_other_child(data);
}

void
covalent_snmp_main(server_rec *s, pool *p)
{
int count;
int numfds;
fd_set fdset;
struct timeval timeout, *tvp;
int block;
pid_t snmpagent_pid;
uid_t snmpuserid;
char *confdir;
char *persdir;
#ifdef EVAL_VERSION
struct timeval now;
#endif

    snmpagent_pid = fork();
    if (snmpagent_pid == -1) { /* Fork error */
	ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s,
					"SNMP: unable to fork agent");
    } else if (snmpagent_pid > 0) { 
        struct snmp_oc *sd = ap_palloc(p, sizeof(*sd));
        
        ap_note_subprocess(p, snmpagent_pid, kill_always);
#ifndef NO_OTHER_CHILD
        sd->p = p;
        sd->pid = snmpagent_pid;
        ap_register_other_child(snmpagent_pid, snmpagent_maint,
                                sd, -1);
        ap_register_cleanup(p, sd, snmpagent_cleanup, ap_null_cleanup);
#endif 
	return;
    }

    /* SNMP uses its own (module internal) global variable for this. */
    www_services = s;
    confdir = ds_get_string(DS_LIBRARY_ID, DS_LIB_CONFIGURATION_DIR);
    if (confdir == NULL) {
	confdir = ap_server_root_relative(p, COVALENT_SNMP_CONFIGURATION_DIR);
	ds_set_string(DS_LIBRARY_ID, DS_LIB_CONFIGURATION_DIR, strdup(confdir));
    }
    persdir = ds_get_string(DS_LIBRARY_ID, DS_LIB_PERSISTENT_DIR);
    if (persdir == NULL) {
	persdir = ap_server_root_relative(p, COVALENT_SNMP_PERSISTENT_DIR);
	ds_set_string(DS_LIBRARY_ID, DS_LIB_PERSISTENT_DIR, strdup(persdir));
    }
    setenv( "MIBS", "", 1 );

    /* do what we need to do first. */
    init_agent("snmpd");
#ifdef SNMP_UCD_DEBUGGING
    snmp_set_do_debugging(1);
#endif

    /* register/initialize the MIB modules */
    /* Framework MIB modules required by a master agent. */
    init_system_mib();
    init_sysORTable();
    init_snmp_mib();
    init_vacm_vars();
    init_snmpEngine();
    init_snmpMPDStats();
    init_usmStats();
    init_usmUser();
    /* application MIB modules */
    init_application_mib_modules(s, p, persdir);

    /* start library */
    init_snmp("snmpd");

    if (init_master_agent(0, snmp_check_packet, snmp_check_parse )) {
	ap_log_error(APLOG_MARK, APLOG_ERR, s,
                    "SNMP: %s could not be started", COVALENT_SNMP_VERSION);
	exit(0);
    }

#ifdef SIGKILL
    signal(SIGKILL, covalent_snmp_shutdown);
#endif

#ifdef SIGTERM
    signal(SIGTERM, covalent_snmp_shutdown);
#endif

#ifdef SIGUSR1
    signal(SIGUSR1, covalent_snmp_shutdown);
#endif

#ifdef SIGHUP
    signal(SIGHUP, covalent_snmp_shutdown);
#endif

#ifdef EVAL_VERSION
    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
                    "SNMP: This is an evaluation version. Waiting 10 seconds to start.");
    sleep(10);
#endif
    
    /* send coldstart trap via snmptrap(1) if possible */
    send_easy_trap(0, 0);

    snmpuserid = ds_get_int(DS_APPLICATION_ID, DS_AGENT_USERID);
    if ((snmpuserid != 0) && !geteuid()) {
         if (setuid(snmpuserid)) {
             ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
                        "SNMP: Cannot change user id.");
         } else {
	     set_restart_capability(OFF);
         }
    }
    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
         "SNMP: %s started (user '%d' - address '%s' - pid '%d' snmpset '%s')",
                COVALENT_SNMP_VERSION,
                geteuid(), ds_get_string(DS_APPLICATION_ID, DS_AGENT_PORTS) ?
                ds_get_string(DS_APPLICATION_ID, DS_AGENT_PORTS) : "161@0.0.0.0",
                getpid(), (check_restart_capability() ? "disable" : "enabled"));

    /* Listen to the network */
    while(1){
#ifdef EVAL_VERSION
        gettimeofday(&now, NULL);
	if (timeval_uptime(&now) > EVAL_MAX_SYSUPTIME) {
	    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_EMERG, s,
                    "SNMP: Evaluation version. Maximum sysUpTime reached (exiting).");
	    exit(-1);
	}
#endif
	tvp =  &timeout;
        tvp->tv_sec = 0;
        tvp->tv_usec = 500000L;

        numfds = logpath.socket+1;
        FD_ZERO(&fdset);
	block = 1;
	snmp_select_info(&numfds, &fdset, tvp, &block);
        FD_SET(logpath.socket, &fdset);

	if (block == 1) {
	    tvp = NULL;
	}

        count = select(numfds, &fdset, 0, 0, tvp);
        if (count > 0){
            snmp_read(&fdset);
            if (FD_ISSET(logpath.socket, &fdset)) {
		snmp_generic_log_sink(logpath.socket);
	    }
        } else switch(count){
            case 0:
                break;
            case -1:
                if (errno == EINTR) {
		    continue;
                } else {
		    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
				"SNMP: select error '%s'\n", strerror(errno));
                }
            default:
		ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
                		"SNMP: select returned %d\n", count);
        }
    }
}

