/* * Dynamic Loadable Agent Modules MIB (UCD-DLMOD-MIB) - dlmod.c * */ #include #include #include #if HAVE_STDLIB_H #include #endif #include #if HAVE_STRING_H #include #else #include #endif #include #include #if defined(WIN32) #include #else #include #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; 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->error); 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; free(dlm->error); dlm->error = NULL; if (dlmod_is_abs_path(dlm->path)) { dlm->handle = dlmod_dlopen(dlm->path); if (dlm->handle == NULL) { if (asprintf(&dlm->error, "dlopen(%s) failed: %s", dlm->path, dlmod_dlerror()) < 0) dlm->error = NULL; dlm->status = DLMOD_ERROR; return; } } else { char *st, *p, *tmp_path = NULL; for (p = strtok_r(dlmod_path, ENV_SEPARATOR, &st); p; p = strtok_r(NULL, ENV_SEPARATOR, &st)) { free(tmp_path); if (asprintf(&tmp_path, "%s/%s.%s", p, dlm->path, dlmod_dl_suffix) < 0) { dlm->status = DLMOD_ERROR; return; } DEBUGMSGTL(("dlmod", "p: %s tmp_path: %s\n", p, tmp_path)); dlm->handle = tmp_path ? dlmod_dlopen(tmp_path) : NULL; if (dlm->handle == NULL) { free(dlm->error); if (asprintf(&dlm->error, "dlopen(%s) failed: %s", tmp_path, dlmod_dlerror()) < 0) dlm->error = NULL; dlm->status = DLMOD_ERROR; } } strlcpy(dlm->path, tmp_path, sizeof(dlm->path)); free(tmp_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); free(dlm->error); if (asprintf(&dlm->error, "dlsym failed: can't find \'%s\'", sym_init) < 0) dlm->error = NULL; dlm->status = DLMOD_ERROR; return; } dl_init(); } dlm->error = NULL; 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->error); 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 = NULL; *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 = NULL; /* 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 = NULL; 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 = dlm->error ? strlen(dlm->error) : 0; 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)); }