/* * Copyright (c) 2006, Stefan Walter * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and * the following disclaimer in the documentation and/or * other materials provided with the distribution. * * The names of contributors to this software may not be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * * CONTRIBUTORS * Stef Walter */ #include "usuals.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sysctl_sup.h" #include "sysctl_tree.h" #include "sysctl_oid.h" #define DEFAULT_CONFIG "/usr/local/etc" "/bsnmp-sysctl.conf" #define DEFAULT_CACHE 10 /* our module handle */ static struct lmodule *module; /* OIDs */ static const struct asn_oid oid_sysctl = OIDX_sysctl; /* the Object Resource registration index */ static u_int reg_index = 0; struct data_entry { uint32_t index; TAILQ_ENTRY(data_entry) link; char *descr; char *fmt; u_int type; uint64_t cache; int error; char *sysctl; int *name; u_int namelen; uint64_t last_update; int64_t value_int; char *value_raw; size_t value_rawlen; char *value_str; size_t value_strlen; }; TAILQ_HEAD(data_entry_list, data_entry); /* list of sysctls */ static struct data_entry_list entries = TAILQ_HEAD_INITIALIZER(entries); static uint32_t entry_count = 0; /* configuration */ static u_char *sysctl_config = NULL; static char *config_memory = NULL; static uint64_t option_cache = DEFAULT_CACHE; /* ----------------------------------------------------------------------------- * HELPERS */ static void emsg(const char *format, ...) { va_list va; va_start(va, format); vsyslog(LOG_ERR, format, va); va_end(va); } static void strcln (char* data, char ch) { char* p; for (p = data; *data; data++, p++) { while (*data == ch) data++; *p = *data; } /* null terminate */ *p = 0; } static void stretrim (char* data) { char* t = data + strlen (data); while (t > data && isspace (*(t - 1))) { t--; *t = 0; } } static char* strbtrim (char* data) { while (*data && isspace (*data)) ++data; return (char*)data; } static char* strtrim (char* data) { data = (char*)strbtrim (data); stretrim (data); return data; } static uint64_t getcurrticks (void) { return get_ticks (); } /* ----------------------------------------------------------------------------- * CONFIG PARSING */ static void config_free (struct data_entry *data) { free(data->name); free(data->fmt); free (data->value_str); free (data->value_raw); free (data); } static void config_free_all (void) { struct data_entry *data; while ((data = TAILQ_FIRST(&entries)) != NULL) { TAILQ_REMOVE (&entries, data, link); config_free (data); } } static int config_entry (struct data_entry *data, char *name, char *value, int line) { ASSERT (data); ASSERT (name); ASSERT (value); (void)value; /* The name */ data->descr = name; /* XXX - from sysctl desc */ data->sysctl = name; data->error = 0; /* Parse the sysctl */ data->namelen = CTL_MAXNAME; data->name = malloc(sizeof *data->name * data->namelen); data->fmt = NULL; data->value_str = NULL; data->value_raw = NULL; if (data->name == NULL) { emsg ("out of memory parsing config file"); return -1; } do { if (sysctlnametomib(name, data->name, &data->namelen) == -1) { emsg ("[line %d] sysctlnametomib: %s", line, strerror(errno)); break; } data->name = realloc(data->name, sizeof *data->name * data->namelen); if (sysctl_oidfmt(data->name, data->namelen, &data->fmt, &data->type)) { emsg ("[line %d] sysctl_oidfmt: %s", line, strerror(errno)); break; } /* Options */ data->cache = option_cache; return 0; } while (0); free(data->name); free(data->fmt); return -1; } static int config_line (char *name, char *value, int line) { struct data_entry *data; int r; /* config_parse trims this for us */ ASSERT (!isspace(value[0])); /* Now populate an entry */ data = (struct data_entry*)calloc(1, sizeof *data); if (!data) { emsg ("out of memory"); return -1; } /* Now make an entry out of it all */ r = config_entry (data, name, value, line); if (r < 0) { free (data); return r; } /* Put it in our table */ data->index = entry_count++; INSERT_OBJECT_INT (data, &entries); return 0; } static void config_var (char *name, char *value, int line) { char *t2; int i; /* The cache variable */ if (strcmp (name, "cache") == 0) { i = strtol (value, &t2, 10); if (i <= 0 || *t2) emsg("[line %d] invalid value for '%s' variable. ignoring: %s", line, name, value); else option_cache = i * 100; return; } emsg ("unknown or invalid variable. ignoring: %s", name); } static int config_read (void) { FILE *f = NULL; long len; ASSERT (sysctl_config && sysctl_config[0]); f = fopen (sysctl_config, "r"); if (f == NULL) { emsg ("couldn't open config file: %s", sysctl_config); return -1; } /* Figure out size */ if (fseek (f, 0, SEEK_END) == -1 || (len = ftell (f)) == -1 || fseek (f, 0, SEEK_SET) == -1) { emsg ("couldn't seek config file: %s", sysctl_config); return -1; } ASSERT (!config_memory); if ((config_memory = (char*)malloc (len + 2)) == NULL) { emsg ("out of memory"); return -1; } /* And read in one block */ if(fread(config_memory, 1, len, f) != (size_t)len) { emsg ("couldn't read config file: %s", sysctl_config); free (config_memory); config_memory = NULL; return -1; } fclose (f); /* Null terminate the data */ config_memory[len] = '\n'; config_memory[len + 1] = 0; return 0; } static int config_parse (void) { char* next; char* p; char* t; int line = 0; int var = 0; int r; config_free_all (); /* Reads raw config file into config_memory */ if (config_read () < 0) return -1; ASSERT (config_memory); /* Remove nasty dos line endings */ strcln(config_memory, '\r'); next = config_memory; while ((t = strchr (next, '\n')) != NULL) { *t = 0; p = next; next = t + 1; t = strbtrim (p); line++; /* blank lines, comments */ if (!*t || *t == '#') continue; t = t + strcspn (t, ":="); if (!*t) { emsg ("invalid config line: %s", p); return -1; } /* Equal sign denotes a variable */ var = (*t == '='); *t = 0; t++; /* Pass variables to the appropriate place */ if (var) { config_var (strtrim (p), strtrim (t), line); /* And config lines to the appropriate place */ } else { r = config_line (strtrim (p), strtrim (t), line); if (r < 0) { /* If -2 was returned, no error message was printed */ if (r == -2) emsg ("[line %d] invalid configuration file", line); return -1; } } } return 0; } /* ----------------------------------------------------------------------------- * CALLBACKS */ int op_sysctlconfig (struct snmp_context *ctx, struct snmp_value *value, u_int sub, u_int iidx, enum snmp_op op) { asn_subid_t which = value->var.subs[sub - 1]; int r = SNMP_ERR_NOERROR; (void)iidx; switch (which) { case LEAF_sysctlConfig: if (op == SNMP_OP_GET) return string_get (value, sysctl_config, -1); /* Remainder only at initialization */ if (community != COMM_INITIALIZE) return SNMP_ERR_NOT_WRITEABLE; switch (op) { case SNMP_OP_SET: if ((r = string_save (value, ctx, -1, &sysctl_config)) == SNMP_ERR_NOERROR) { if (!sysctl_config[0]) r = SNMP_ERR_WRONG_VALUE; else if (config_parse () < 0) r = SNMP_ERR_GENERR; } if (r != SNMP_ERR_NOERROR) string_rollback (ctx, &sysctl_config); break; case SNMP_OP_COMMIT: string_commit (ctx); break; case SNMP_OP_ROLLBACK: string_rollback (ctx, &sysctl_config); break; default: ASSERT(0); break; } return r; } ASSERT(0); return -1; } int op_sysctl (struct snmp_context *ctx, struct snmp_value *value, u_int sub, u_int iidx, enum snmp_op op) { asn_subid_t which = value->var.subs[sub - 1]; int stdval = 0x01234567; (void)ctx; (void)iidx; switch (op) { case SNMP_OP_GET: break; case SNMP_OP_SET: /* XXX - maybe in the future */ return SNMP_ERR_NOT_WRITEABLE; case SNMP_OP_ROLLBACK: case SNMP_OP_COMMIT: return SNMP_ERR_NOERROR; default: ASSERT(0); break; } switch (which) { case LEAF_sysctlCount: value->v.integer = entry_count; break; case LEAF_sysctlEndian: return string_get(value, (char *)&stdval, sizeof stdval); break; default: ASSERT(0); break; } return SNMP_ERR_NOERROR; } #if 0 static int strisprint(char *str, size_t buflen) { while (buflen--) { if (!isprint(*str) && !isspace(*str)) return 0; str++; } return 1; } #endif static char * makehexstr(unsigned char *buf, size_t buflen) { char *ostr, *str; ostr = str = malloc(buflen * 2 + 1); while (buflen--) { sprintf(str, "%02x", (unsigned)*buf++); str += 2; } str[0] = '\x00'; return ostr; } int op_sysctlentry (struct snmp_context *ctx, struct snmp_value *value, u_int sub, u_int iidx, enum snmp_op op) { char buffer[512]; asn_subid_t which = value->var.subs[sub - 1]; struct data_entry *data; uint64_t ticks; size_t buflen; int fetch = 1; (void)ctx; switch (op) { case SNMP_OP_GETNEXT: data = NEXT_OBJECT_INT (&entries, &value->var, sub); if (data == NULL) return SNMP_ERR_NOSUCHNAME; value->var.len = sub + 1; value->var.subs[sub] = data->index; break; case SNMP_OP_GET: data = FIND_OBJECT_INT (&entries, &value->var, sub); if (data == NULL) return SNMP_ERR_NOSUCHNAME; break; case SNMP_OP_SET: if (index_decode (&value->var, sub, iidx, &data)) return SNMP_ERR_NO_CREATION; data = FIND_OBJECT_INT (&entries, &value->var, sub); if (data != NULL) return SNMP_ERR_NOT_WRITEABLE; return SNMP_ERR_NO_CREATION; default: ASSERT(0); break; } /* Figure out if we should fetch or not */ /* XXX - only fetch on value fetches? */ ticks = getcurrticks (); if (ticks == 0) return SNMP_ERR_GENERR; if (data->cache && data->last_update) { if (ticks < (data->last_update + data->cache)) fetch = 0; } if (fetch) { free(data->value_raw); data->value_raw = NULL; data->value_rawlen = 0; free(data->value_str); data->value_str = NULL; data->value_strlen = 0; /* XXX - oidfmt and friends from sysctl */ /* XXX - length */ buflen = sizeof buffer; if (sysctl(data->name, data->namelen, buffer, &buflen, NULL, 0) == -1) { data->error = errno; data->value_int = 0; } else { data->last_update = getcurrticks(); data->error = 0; data->value_raw = malloc(buflen); data->value_rawlen = buflen; memcpy(data->value_raw, buffer, buflen); data->value_int = 0; emsg("test: buflen %d, sysctl: %s, ticks: %llu, lu: %llu", buflen, data->descr, ticks, data->last_update); if (strcmp("A", data->fmt) == 0) { data->value_str = malloc(buflen); strlcpy(data->value_str, buffer, buflen); } else if (strcmp("I", data->fmt) == 0) { data->value_int = *(int32_t *)buffer; } else { data->value_str = makehexstr(buffer, buflen); } } } if (data->value_str == NULL) asprintf(&data->value_str, "%jd", (intmax_t)data->value_int); data->value_strlen = strlen(data->value_str); switch (which) { case LEAF_sysctlIndex: value->v.integer = data->index; break; case LEAF_sysctlDescr: return (string_get (value, data->descr, -1)); case LEAF_sysctlType: value->v.uint32 = data->type; break; case LEAF_sysctlFmt: return (string_get (value, data->fmt, -1)); case LEAF_sysctlErrno: value->v.uint32 = data->error; break; case LEAF_sysctlLast: if (data->last_update) value->v.uint32 = (ticks - data->last_update); else value->v.uint32 = 0; break; case LEAF_sysctlInteger: value->v.uint32 = data->value_int; break; case LEAF_sysctlValue: return string_get(value, data->value_str, data->value_strlen); case LEAF_sysctlRaw: return string_get(value, data->value_raw, data->value_rawlen); case LEAF_sysctlCounter: value->v.counter64 = data->value_int; break; default: ASSERT(0); break; }; return SNMP_ERR_NOERROR; } /* ----------------------------------------------------------------------------- * MODULE */ /* the initialisation function */ static int module_init(struct lmodule *mod, int argc, char *argv[]) { (void)argv; module = mod; if (argc != 0) { syslog(LOG_ERR, "bad number of arguments for %s", __func__); return EINVAL; } sysctl_config = strdup(DEFAULT_CONFIG); if (!sysctl_config) return ENOMEM; return 0; } /* Module is started */ static void module_start (void) { reg_index = or_register (&oid_sysctl, "The MIB for sysctl data.", module); } /* Called, when the module is to be unloaded after it was successfully loaded */ static int module_fini (void) { if (reg_index) or_unregister (reg_index); ASSERT (sysctl_config); free (sysctl_config); config_free_all (); return 0; } const struct snmp_module config = { .comment = "This module implements SNMP listing of data from sysctl.", .init = module_init, .start = module_start, .fini = module_fini, .tree = sysctl_ctree, .tree_size = sysctl_CTREE_SIZE, };