blob: 0b711fbd61e0c3c57bc4bb6e08592ae40ac0e4e9 [file] [log] [blame]
/*
* Dynamic Loadable Agent Modules MIB (UCD-DLMOD-MIB) - dlmod.c
*
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>
#include <ctype.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <stdio.h>
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#if defined(WIN32)
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include "dlmod.h"
#ifndef SNMPDLMODPATH
#define SNMPDLMODPATH "/usr/local/lib/snmp/dlmod"
#endif
struct dlmod {
struct dlmod *next;
int index;
char name[64 + 1];
char path[255 + 1];
char error[255 + 1];
void *handle;
int status;
};
#define DLMOD_LOADED 1
#define DLMOD_UNLOADED 2
#define DLMOD_ERROR 3
#define DLMOD_LOAD 4
#define DLMOD_UNLOAD 5
#define DLMOD_CREATE 6
#define DLMOD_DELETE 7
static struct dlmod *dlmods;
static unsigned int dlmod_next_index = 1;
static char dlmod_path[1024];
static 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;
}
static 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;
}
}
#if defined(WIN32)
/*
* See also Microsoft, "Overview of x64 Calling Conventions", MSDN
* (http://msdn.microsoft.com/en-us/library/ms235286.aspx).
*/
#ifdef _M_X64
typedef int (*dl_function_ptr)(void);
#else
typedef int (__stdcall *dl_function_ptr)(void);
#endif
#else
typedef int (*dl_function_ptr)(void);
#endif
#if defined(WIN32)
static const char dlmod_dl_suffix[] = "dll";
#else
static const char dlmod_dl_suffix[] = "so";
#endif
static void* dlmod_dlopen(const char *path)
{
#if defined(WIN32)
return LoadLibrary(path);
#elif defined(RTLD_NOW)
return dlopen(path, RTLD_NOW);
#else
return dlopen(path, RTLD_LAZY);
#endif
}
static void dlmod_dlclose(void *handle)
{
#if defined(WIN32)
FreeLibrary(handle);
#else
dlclose(handle);
#endif
}
static void *dlmod_dlsym(void *handle, const char *symbol)
{
#if defined(WIN32)
return GetProcAddress(handle, symbol);
#else
return dlsym(handle, symbol);
#endif
}
static const char *dlmod_dlerror(void)
{
#if defined(WIN32)
static char errstr[256];
const DWORD dwErrorcode = GetLastError();
LPTSTR lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, dwErrorcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf, 0, NULL);
if (lpMsgBuf) {
LPTSTR p;
/*
* Remove trailing "\r\n".
*/
p = strchr(lpMsgBuf, '\r');
if (p)
*p = '\0';
snprintf(errstr, sizeof(errstr), "%s", lpMsgBuf);
LocalFree(lpMsgBuf);
} else {
snprintf(errstr, sizeof(errstr), "error code %ld", dwErrorcode);
}
return errstr;
#else
return dlerror();
#endif
}
static int dlmod_is_abs_path(const char *path)
{
#if defined(WIN32)
return (strncmp(path, "//", 2) == 0 || strncmp(path, "\\\\", 2) == 0) ||
(isalpha((u_char)path[0]) && path[1] == ':' &&
(path[2] == '/' || path[2] == '\\'));
#else
return path[0] == '/';
#endif
}
static void
dlmod_load_module(struct dlmod *dlm)
{
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 (dlmod_is_abs_path(dlm->path)) {
dlm->handle = dlmod_dlopen(dlm->path);
if (dlm->handle == NULL) {
snprintf(dlm->error, sizeof(dlm->error),
"dlopen(%s) failed: %s", dlm->path, dlmod_dlerror());
dlm->status = DLMOD_ERROR;
return;
}
} else {
char *st, *p, tmp_path[255];
for (p = strtok_r(dlmod_path, ENV_SEPARATOR, &st); p;
p = strtok_r(NULL, ENV_SEPARATOR, &st)) {
snprintf(tmp_path, sizeof(tmp_path), "%s/%s.%s", p, dlm->path,
dlmod_dl_suffix);
DEBUGMSGTL(("dlmod", "p: %s tmp_path: %s\n", p, tmp_path));
dlm->handle = dlmod_dlopen(tmp_path);
if (dlm->handle == NULL) {
snprintf(dlm->error, sizeof(dlm->error),
"dlopen(%s) failed: %s", tmp_path, dlmod_dlerror());
dlm->status = DLMOD_ERROR;
}
}
strlcpy(dlm->path, tmp_path, sizeof(dlm->path));
if (dlm->status == DLMOD_ERROR)
return;
}
{
char sym_init[64 + sizeof("init_")];
dl_function_ptr dl_init;
snprintf(sym_init, sizeof(sym_init), "init_%s", dlm->name);
dl_init = dlmod_dlsym(dlm->handle, sym_init);
if (dl_init == NULL) {
dlmod_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;
}
static void
dlmod_unload_module(struct dlmod *dlm)
{
char sym_deinit[64 + sizeof("shutdown_")];
dl_function_ptr dl_deinit;
if (!dlm || dlm->status != DLMOD_LOADED)
return;
snprintf(sym_deinit, sizeof(sym_deinit), "deinit_%s", dlm->name);
dl_deinit = dlmod_dlsym(dlm->handle, sym_deinit);
if (!dl_deinit) {
snprintf(sym_deinit, sizeof(sym_deinit), "shutdown_%s", dlm->name);
dl_deinit = dlmod_dlsym(dlm->handle, sym_deinit);
}
if (dl_deinit) {
DEBUGMSGTL(("dlmod", "Calling %s()\n", sym_deinit));
dl_deinit();
} else {
DEBUGMSGTL(("dlmod", "No destructor for %s\n", dlm->name));
}
dlmod_dlclose(dlm->handle);
dlm->status = DLMOD_UNLOADED;
DEBUGMSGTL(("dlmod", "Module %s unloaded\n", dlm->name));
}
static 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;
}
/*
* Functions to parse config lines
*/
static void
dlmod_parse_config(const char *token, char *cptr)
{
char *dlm_name, *dlm_path;
struct dlmod *dlm;
char *st;
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_r(cptr, "\t ", &st);
if (dlm_name == NULL) {
config_perror("Bad dlmod line");
dlmod_delete_module(dlm);
return;
}
strlcpy(dlm->name, dlm_name, sizeof(dlm->name));
/*
* dynamic module path
*/
dlm_path = strtok_r(NULL, "\t ", &st);
if (dlm_path)
strlcpy(dlm->path, dlm_path, sizeof(dlm->path));
else
strlcpy(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;
}
/*
* Functions to handle SNMP management
*/
#define DLMODNEXTINDEX 1
#define DLMODINDEX 2
#define DLMODNAME 3
#define DLMODPATH 4
#define DLMODERROR 5
#define DLMODSTATUS 6
/*
* 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(newname, vp->name, vp->namelen * sizeof(oid));
newname[DLMOD_NAME_LENGTH] = 0;
result = snmp_oid_compare(name, *length, newname, vp->namelen + 1);
if ((exact && (result != 0)) || (!exact && (result >= 0))) {
return MATCH_FAILED;
}
memcpy(name, newname, (vp->namelen + 1) * sizeof(oid));
*length = vp->namelen + 1;
*write_method = 0;
*var_len = sizeof(long); /* default to 'long' results */
return MATCH_SUCCEEDED;
}
static 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 isn't 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 NULL;
/*
* 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 NULL;
}
static 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)-1) {
snmp_log(LOG_ERR, "write to dlmodName: bad length: too long\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;
}
static 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)-1) {
snmp_log(LOG_ERR, "write to dlmodPath: bad length: too long\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;
}
static 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;
}
/*
* 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 = NULL;
unsigned int dlmod_index;
memcpy(newname, vp->name, 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: %p dlmod_index: %d\n",
dlm, dlmod_index));
if (dlm) {
newname[12] = dlmod_index;
result = snmp_oid_compare(name, *length, newname, 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(name, newname, (vp->namelen + 1) * sizeof(oid));
*length = vp->namelen + 1;
*var_len = sizeof(long);
return dlm;
}
static 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 NULL;
/*
* 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 NULL;
}
/*
* this variable defines function callbacks and type return
* information for the dlmod mib
*/
static struct variable4 dlmod_variables[] = {
{DLMODNEXTINDEX, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
var_dlmod, 1, {1}},
{DLMODNAME, ASN_OCTET_STR, NETSNMP_OLDAPI_RWRITE,
var_dlmodEntry, 3, {2, 1, 2}},
{DLMODPATH, ASN_OCTET_STR, NETSNMP_OLDAPI_RWRITE,
var_dlmodEntry, 3, {2, 1, 3}},
{DLMODERROR, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
var_dlmodEntry, 3, {2, 1, 4}},
{DLMODSTATUS, ASN_INTEGER, NETSNMP_OLDAPI_RWRITE,
var_dlmodEntry, 3, {2, 1, 5}},
};
static oid dlmod_variables_oid[] = { 1, 3, 6, 1, 4, 1, 2021, 13, 14 };
void
init_dlmod(void)
{
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");
{
const char * const p = getenv("SNMPDLMODPATH");
strncpy(dlmod_path, SNMPDLMODPATH, sizeof(dlmod_path));
dlmod_path[ sizeof(dlmod_path) - 1 ] = 0;
if (p) {
if (p[0] == ':') {
int len = strlen(dlmod_path);
if (dlmod_path[len - 1] != ':') {
strncat(dlmod_path, ":", sizeof(dlmod_path) - len - 1);
len++;
}
strncat(dlmod_path, p + 1, sizeof(dlmod_path) - len);
} else
strncpy(dlmod_path, p, sizeof(dlmod_path));
}
}
dlmod_path[ sizeof(dlmod_path)-1 ] = 0;
DEBUGMSGTL(("dlmod", "dlmod_path: %s\n", dlmod_path));
}
netsnmp_feature_require(snmpd_unregister_config_handler)
void
shutdown_dlmod(void)
{
snmpd_unregister_config_handler("dlmod");
unregister_mib(dlmod_variables_oid, OID_LENGTH(dlmod_variables_oid));
}