/*
 *  Dynamic Loadable Agent Modules MIB (UCD-DLMOD-MIB) - dlmod.c
 *
 */
#include <config.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <stdio.h>
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>

#if HAVE_WINSOCK_H
#include <winsock.h>
#endif

#include "mibincl.h"
#include "struct.h"
#include "read_config.h"
#include "agent_read_config.h"
#include "util_funcs.h"
#include "snmp_debug.h"

#if defined(HAVE_DLFCN_H) && defined(HAVE_DLOPEN)

#include <dlfcn.h>
#include "dlmod.h"

static struct dlmod *dlmods = NULL;
static int          dlmod_next_index = 1;
static char         dlmod_path[1024];

static void dlmod_parse_config(const char *, char *);
static void dlmod_free_config(void);

/*
 * this variable defines function callbacks and type return
 * information for the dlmod mib
 */
static struct variable4 dlmod_variables[] = {
    {DLMODNEXTINDEX, ASN_INTEGER, RONLY, var_dlmod, 1, {1}},
    {DLMODNAME, ASN_OCTET_STR, RWRITE, var_dlmodEntry, 3, {2, 1, 2}},
    {DLMODPATH, ASN_OCTET_STR, RWRITE, var_dlmodEntry, 3, {2, 1, 3}},
    {DLMODERROR, ASN_OCTET_STR, RONLY, var_dlmodEntry, 3, {2, 1, 4}},
    {DLMODSTATUS, ASN_INTEGER, RWRITE, var_dlmodEntry, 3, {2, 1, 5}},
};

static oid dlmod_variables_oid[] = {1, 3, 6, 1, 4, 1, 2021, 13, 14};
static int dlmod_variables_oid_len = 9;

void 
init_dlmod (void) 
{
    char *p;
    int l;

    REGISTER_MIB("dlmod", dlmod_variables, variable4, dlmod_variables_oid);

    /* TODO: REGISTER_SYSOR_ENTRY */
    
    DEBUGMSGTL(("dlmod", "register mib\n"));

    snmpd_register_config_handler("dlmod", dlmod_parse_config,
				  dlmod_free_config,
				  "module-name module-path");

    p = getenv("SNMPDLMODPATH");
    strncpy(dlmod_path, SNMPDLMODPATH, sizeof(dlmod_path));
    if (p) {
	if (p[0] == ':') {
	    l = strlen(dlmod_path);
	    if (dlmod_path[l - 1] != ':') 
		strncat(dlmod_path, ":", sizeof(dlmod_path) - l);
	    strncat(dlmod_path, p + 1, 
		    sizeof(dlmod_path) - strlen(dlmod_path));
	} else 
	    strncpy(dlmod_path, p, sizeof(dlmod_path));
    }
    DEBUGMSGTL(("dlmod", "dlmod_path: %s\n", dlmod_path));
}

void 
deinit_dlmod (void)
{
    unregister_mib(dlmod_variables_oid, dlmod_variables_oid_len);
    snmpd_unregister_config_handler("dlmod");
}

struct dlmod *
dlmod_create_module (void) 
{
    struct dlmod **pdlmod, *dlm;

    DEBUGMSGTL(("dlmod", "dlmod_create_module\n"));
    dlm = calloc(1, sizeof(struct dlmod));
    if (dlm == NULL) 
	return NULL;

    dlm->index = dlmod_next_index++;
    dlm->status = DLMOD_UNLOADED;

    for (pdlmod = &dlmods; *pdlmod != NULL; pdlmod = &((*pdlmod)->next)) 
	;
    (*pdlmod) = dlm;

    return dlm;
}

void
dlmod_delete_module(struct dlmod *dlm)
{
    struct dlmod **pdlmod;
 
    DEBUGMSGTL(("dlmod", "dlmod_delete_module\n"));
    if (!dlm || dlm->status != DLMOD_UNLOADED)
	return;

    for (pdlmod = &dlmods; *pdlmod; pdlmod = &((*pdlmod)->next)) 
	if (*pdlmod == dlm) {
	    *pdlmod = dlm->next;
	    free(dlm);
	    return;
	}
}

void
dlmod_load_module(struct dlmod *dlm)
{
    char sym_init[64];
    char *p, tmp_path[255];
    int (*dl_init)(void);
    
    DEBUGMSGTL(("dlmod", "dlmod_load_module %s: %s\n", dlm->name, dlm->path));
  
    if (!dlm || !dlm->path || !dlm->name ||
	(dlm->status != DLMOD_UNLOADED && dlm->status != DLMOD_ERROR))
	return;

    if (dlm->path[0] == '/') { 
#ifdef RTLD_NOW
	dlm->handle = dlopen(dlm->path, RTLD_NOW);
#else
	dlm->handle = dlopen(dlm->path, RTLD_LAZY);
#endif
	if (dlm->handle == NULL) {
	    snprintf(dlm->error, sizeof(dlm->error), 
		     "dlopen failed: %s", dlerror());
	    dlm->status = DLMOD_ERROR;
	    return;
	}
    } else {
	for (p = strtok(dlmod_path, ":"); p; p = strtok(NULL, ":")) {
	    snprintf(tmp_path, sizeof(tmp_path), "%s/%s.so", p, dlm->path);
	    DEBUGMSGTL(("dlmod", "p: %s tmp_path: %s\n", p, tmp_path));
#ifdef RTLD_NOW
	    dlm->handle = dlopen(tmp_path, RTLD_NOW);
#else
	    dlm->handle = dlopen(tmp_path, RTLD_LAZY);
#endif
	    if (dlm->handle == NULL) {
		snprintf(dlm->error, sizeof(dlm->error), 
			 "dlopen failed: %s", dlerror());
		dlm->status = DLMOD_ERROR;
	    }
	}
	strncpy(dlm->path, tmp_path, sizeof(dlm->path));
	if (dlm->status == DLMOD_ERROR) 
	    return;
    }
    snprintf(sym_init, sizeof(sym_init), "init_%s", dlm->name);
    dl_init = dlsym(dlm->handle, sym_init);
    if (dl_init == NULL) {
	dlclose(dlm->handle);
	snprintf(dlm->error, sizeof(dlm->error), 
		 "dlsym failed: can't find \'%s\'", sym_init);
	dlm->status = DLMOD_ERROR;
	return;
    }
	
    dl_init();
    dlm->error[0] = '\0';
    dlm->status = DLMOD_LOADED;
}

void 
dlmod_unload_module (struct dlmod *dlm)
{
    char sym_deinit[64];
    int (* dl_deinit)(void);
  
    if (!dlm || dlm->status != DLMOD_LOADED) 
	return;

    snprintf(sym_deinit, sizeof(sym_deinit), "deinit_%s", dlm->name);
    dl_deinit = dlsym(dlm->handle, sym_deinit);
    if (dl_deinit == NULL) {
	snprintf(dlm->error, sizeof(dlm->error), 
		 "dlsym failed: can't find \'%s\'", sym_deinit);
    } else {
	dl_deinit();
    }
    dlclose(dlm->handle);
    dlm->status = DLMOD_UNLOADED;
    DEBUGMSGTL(("dlmod", "Module %s unloaded\n", dlm->name));
}

struct dlmod *
dlmod_get_by_index (int iindex)
{
    struct dlmod *dlmod;
  
    for (dlmod = dlmods; dlmod; dlmod=dlmod->next) 
	if (dlmod->index == iindex)
	    return dlmod;

    return NULL;
}

static void 
dlmod_parse_config(const char *token,
		   char *cptr)
{
    char *dlm_name, *dlm_path;
    struct dlmod *dlm;
  
    if (cptr == NULL) {
	config_perror("Bad dlmod line");
	return;
    }
    /* remove comments */
    *(cptr + strcspn(cptr, "#;\r\n")) = '\0';

    dlm = dlmod_create_module();
    if (!dlm)
	return;

    /* dynamic module name */
    dlm_name = strtok(cptr, "\t "); 
    if (dlm_name == NULL) {
	config_perror("Bad dlmod line");
	dlmod_delete_module(dlm);
	return;
    }
    strncpy(dlm->name, dlm_name, sizeof(dlm->name));

    /* dynamic module path */
    dlm_path = strtok(NULL, "\t ");
    if (dlm_path) 
	strncpy(dlm->path, dlm_path, sizeof(dlm->path));
    else
	strncpy(dlm->path, dlm_name, sizeof(dlm->path));

    dlmod_load_module(dlm);
	
    if (dlm->status == DLMOD_ERROR) 
	snmp_log(LOG_ERR, "%s\n", dlm->error);
}

static void 
dlmod_free_config (void) 
{
    struct dlmod *dtmp, *dtmp2;
  
    for (dtmp = dlmods; dtmp != NULL;) {
	dtmp2 = dtmp;
	dtmp = dtmp->next;
	dlmod_unload_module(dtmp2);
	free(dtmp2);
    }
    dlmods = NULL;
}


/*
  header_dlmod(...
  Arguments:
  vp	  IN      - pointer to variable entry that points here
  name    IN/OUT  - IN/name requested, OUT/name found
  length  IN/OUT  - length of IN/OUT oid's 
  exact   IN      - TRUE if an exact match was requested
  var_len OUT     - length of variable or 0 if function returned
  write_method
*/

static int
header_dlmod(struct variable *vp,
	     oid *name,
	     size_t *length,
	     int exact,
	     size_t *var_len,
	     WriteMethod **write_method)
{
#define DLMOD_NAME_LENGTH 10
    oid             newname[MAX_OID_LEN];
    int             result;
    
    memcpy((char *)newname, (char *)vp->name, (int) vp->namelen * sizeof(oid));
    newname[DLMOD_NAME_LENGTH] = 0;

    result = snmp_oid_compare(name, *length, newname, (int)vp->namelen + 1);
    if ((exact && (result != 0)) || (!exact && (result >= 0))) {
	return MATCH_FAILED;
    }
	
    memcpy((char *)name, (char *)newname,
	   ((int)vp->namelen + 1) * sizeof(oid));
    *length = vp->namelen + 1;
    *write_method = 0;
    *var_len = sizeof(long); /* default to 'long' results */
    return MATCH_SUCCEEDED;
}


u_char *
var_dlmod(struct variable *vp,
	  oid *name,
	  size_t *length,
	  int exact,
	  size_t *var_len,
	  WriteMethod **write_method)
{

    /* variables we may use later */

    *write_method = 0;       /* assume it isnt writable for the time being */
    *var_len = sizeof(int);  /* assume an integer and change later
			      * if not */
    
    if (header_dlmod(vp, name, length, exact,
		     var_len, write_method) == MATCH_FAILED)
	return 0;
    
    /* this is where we do the value assignments for the mib results. */
    switch (vp->magic) {
    case DLMODNEXTINDEX:
        long_return = dlmod_next_index;
	return (unsigned char *)&long_return;
    default:
	DEBUGMSGTL(("dlmod", "unknown sub-id %d in var_dlmod\n", vp->magic));
    }
    return 0;
}


/*
  header_dlmodEntry(...
  Arguments:
  vp	  IN      - pointer to variable entry that points here
  name    IN/OUT  - IN/name requested, OUT/name found
  length  IN/OUT  - length of IN/OUT oid's 
  exact   IN      - TRUE if an exact match was requested
  var_len OUT     - length of variable or 0 if function returned
  write_method
  
*/


static struct dlmod *
header_dlmodEntry(struct variable *vp,
		  oid *name,
		  size_t *length,
		  int exact,
		  size_t *var_len,
		  WriteMethod **write_method)
{
#define DLMODENTRY_NAME_LENGTH 12
    oid	newname[MAX_OID_LEN];
    int	result;
    struct dlmod   *dlm = 0;
    int dlmod_index;
    
    memcpy((char *)newname, (char *)vp->name, (int)vp->namelen * sizeof(oid));
    *write_method = 0;

    for (dlmod_index = 1; dlmod_index < dlmod_next_index; dlmod_index++)  {
	dlm = dlmod_get_by_index(dlmod_index);

	DEBUGMSGTL(("dlmod", "dlmodEntry dlm: %x dlmod_index: %d\n",
		    (int)dlm, dlmod_index));
	
	if (dlm) {
	    newname[12] = dlmod_index;
	    result = snmp_oid_compare(name, *length, newname,
				      (int)vp->namelen + 1);

	    if ((exact && (result == 0)) || (!exact && (result < 0))) 
		break;
	}
    }
    
    if (dlmod_index >= dlmod_next_index) {
	if (dlmod_index == dlmod_next_index && 
	    exact && vp->magic == DLMODSTATUS)  
	    
	    *write_method = write_dlmodStatus;
	return NULL;
    }
    
    memcpy((char *)name, (char *)newname,
	   ((int)vp->namelen + 1) * sizeof(oid));
    *length = vp->namelen + 1;
    *var_len = sizeof(long);
    return dlm;
}

u_char *
var_dlmodEntry(struct variable *vp,
	       oid *name,
	       size_t *length,
	       int exact,
	       size_t *var_len,
	       WriteMethod **write_method)
{
    /* variables we may use later */
    struct dlmod   *dlm;

    *var_len = sizeof(int);	/* assume an integer and change later
				 * if not */

    dlm = header_dlmodEntry(vp, name, length, exact, var_len, write_method);
    if (dlm == NULL)
	return 0;

    /* this is where we do the value assignments for the mib results. */
    switch (vp->magic) {
    case DLMODNAME:
	*write_method = write_dlmodName;
	*var_len = strlen(dlm->name);
	return (unsigned char*) dlm->name;
    case DLMODPATH:
	*write_method = write_dlmodPath;
	*var_len = strlen(dlm->path);
	return (unsigned char*) dlm->path;
    case DLMODERROR:
	*var_len = strlen(dlm->error);
	return (unsigned char*) dlm->error;
    case DLMODSTATUS:
	*write_method = write_dlmodStatus;
	long_return = dlm->status;
	return (unsigned char *) &long_return;
    default:
	DEBUGMSGTL(("dlmod", "unknown sub-id %d in var_dlmodEntry\n",
		    vp->magic));
    }
    return 0;
}

int
write_dlmodName(int action,
		u_char *var_val,
		u_char var_val_type,
		size_t var_val_len,
		u_char *statP,
		oid *name,
		size_t name_len)
{
    static struct dlmod  *dlm;

    if (var_val_type != ASN_OCTET_STR) {
	snmp_log(LOG_ERR, "write to dlmodName not ASN_OCTET_STR\n");
	return SNMP_ERR_WRONGTYPE;
    }
    if (var_val_len > sizeof(dlm->name)) {
	snmp_log(LOG_ERR, "write to dlmodName: bad length\n");
	return SNMP_ERR_WRONGLENGTH;
    }
    if (action == COMMIT) {
	dlm = dlmod_get_by_index(name[12]);
	if (!dlm || dlm->status == DLMOD_LOADED) 
	    return SNMP_ERR_RESOURCEUNAVAILABLE;
	strncpy(dlm->name, (const char*)var_val, var_val_len);
	dlm->name[var_val_len] = 0;
    }
    return SNMP_ERR_NOERROR;
}

int
write_dlmodPath(int action,
		u_char *var_val,
		u_char var_val_type,
		size_t var_val_len,
		u_char *statP,
		oid *name,
		size_t name_len)
{
    static struct dlmod  *dlm;
    
    if (var_val_type != ASN_OCTET_STR) {
	snmp_log(LOG_ERR, "write to dlmodPath not ASN_OCTET_STR\n");
	return SNMP_ERR_WRONGTYPE;
    }
    if (var_val_len > sizeof(dlm->path)) {
	snmp_log(LOG_ERR, "write to dlmodPath: bad length\n");
	return SNMP_ERR_WRONGLENGTH;
    }
    if (action == COMMIT) {
	dlm = dlmod_get_by_index(name[12]);
	if (!dlm || dlm->status == DLMOD_LOADED) 
	    return SNMP_ERR_RESOURCEUNAVAILABLE;
	strncpy(dlm->path, (const char*)var_val, var_val_len);
	dlm->path[var_val_len] = 0;
    }
    return SNMP_ERR_NOERROR;
}

int
write_dlmodStatus(int action,
		  u_char *var_val,
		  u_char var_val_type,
		  size_t var_val_len,
		  u_char *statP,
		  oid *name,
		  size_t name_len)
{
    /* variables we may use later */
    struct dlmod *dlm;

    if (var_val_type != ASN_INTEGER) {
	snmp_log(LOG_ERR, "write to dlmodStatus not ASN_INTEGER\n");
	return SNMP_ERR_WRONGTYPE;
    }
    if (var_val_len > sizeof(long)) {
	snmp_log(LOG_ERR, "write to dlmodStatus: bad length\n");
	return SNMP_ERR_WRONGLENGTH;
    }
    if (action == COMMIT) {
	/* object identifier in form .1.3.6.1.4.1.2021.13.14.2.1.4.x 
	   where X is index with offset 12 */

	dlm = dlmod_get_by_index(name[12]);
	switch (*((long *)var_val)) {
	case DLMOD_CREATE:
	    if (dlm || (name[12] != dlmod_next_index))
		return SNMP_ERR_RESOURCEUNAVAILABLE;
	    dlm = dlmod_create_module();
	    if (!dlm) 
		return SNMP_ERR_RESOURCEUNAVAILABLE;
	    break;
	case DLMOD_LOAD:
	    if (!dlm || dlm->status == DLMOD_LOADED) 
		return SNMP_ERR_RESOURCEUNAVAILABLE;
	    dlmod_load_module(dlm);
	    break;	
	case DLMOD_UNLOAD:
	    if (!dlm || dlm->status != DLMOD_LOADED) 
		return SNMP_ERR_RESOURCEUNAVAILABLE;
	    dlmod_unload_module(dlm);
	    break;	
	case DLMOD_DELETE:
	    if (!dlm || dlm->status == DLMOD_LOADED) 
		return SNMP_ERR_RESOURCEUNAVAILABLE;
	    dlmod_delete_module(dlm);
	    break;	
	default:
	    return SNMP_ERR_WRONGVALUE;
	}
    }
    return SNMP_ERR_NOERROR;
}

#else /* no dlopen support */

void 
init_dlmod (void)
{
    DEBUGMSGTL(("dlmod","Dynamic modules not support on this platform\n"));
}

#endif
