#include "usuals.h" #include #include #include #include #include #include #include #include #include "regex_tree.h" #include "regex_oid.h" #define DEFAULT_CONFIG CONF_PREFIX "/rrdbot" /* our module handle */ static struct lmodule *module; /* OIDs */ static const struct asn_oid oid_regex = OIDX_regexData; /* the Object Resource registration index */ static u_int reg_index = 0; /* Various match types */ enum { TYPE_UNKNOWN = 0, TYPE_COUNTER, TYPE_INTEGER }; struct data_entry { uint32_t index; TAILQ_ENTRY(data_entry) link; int type; char *descr; pcre *regex; pcre_extra *extra; char *result; uint64_t last_update; uint64_t value; }; TAILQ_HEAD(data_entry_list, data_entry); /* list of peers */ static struct data_entry_list entries = TAILQ_HEAD_INITIALIZER(entries); static uint32_t entry_count = 0; static char *config_memory = NULL; /* configuration */ static u_char *regex_config = NULL; static u_char *regex_fifo = NULL; /* ----------------------------------------------------------------------------- * 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 (const char* data) { while (*data && isspace (*data)) ++data; return (char*)data; } static char* strtrim (char* data) { data = (char*)strbtrim (data); stretrim (data); return data; } /* ----------------------------------------------------------------------------- * CONFIG PARSING */ static void config_free (struct data_entry *data) { if (data->regex) free (data->regex); if (data->extra) free (data->extra); free (data); } static void config_free_all () { 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 type, char *regex, char *result, char *flags, int line) { const char *errptr; int erroffset; int options = 0; ASSERT (regex); ASSERT (flags); ASSERT (data); ASSERT (type); ASSERT (name); /* Figure out the type first */ switch (type) { case 'c': data->type = TYPE_COUNTER; break; case 'i': data->type = TYPE_INTEGER; break; case '\0': return -2; default: emsg ("[line %d] invalid or unknown entry type: %c", line, type); return -1; } /* Parse any options we have */ for (; *flags; flags++) { switch (flags[0]) { case 'i': options |= PCRE_CASELESS; break; default: emsg ("[line %d] invalid flag: %c", line, flags[0]); return -1; } } /* The name */ data->descr = name; /* Parse the regex */ data->regex = pcre_compile (regex, options, &errptr, &erroffset, NULL); if (!data->regex) { emsg ("[line %d] invalid regular expression: %s", line, errptr); return -1; } /* Optimize the regex, ignore errors */ data->extra = pcre_study (data->regex, 0, &errptr); /* Replacement data */ if (data->type != TYPE_COUNTER && !result) { emsg ("[line %d] no match text specified in entry", line); return -1; } data->result = result; return 0; } static int config_line (char *name, char *value, int line) { struct data_entry *data; char *regex = NULL; char *result = NULL; char *flags = NULL; char type; char delimiter; char *t; int r; /* config_parse trims this for us */ ASSERT (!isspace(value[0])); /* The type of entry */ type = value[0]; ++value; /* Next thing is the delimiter */ if (!*value) return -2; delimiter = value[0]; ++value; /* Look for the next delimiter */ t = strchr (value, delimiter); if (!t) return -2; *t = 0; regex = value; value = t + 1; /* And the result */ t = strchr (value, delimiter); if (t) { *t = 0; t++; result = value; } /* The flags */ flags = t ? t : value; /* 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, type, regex, result, flags, 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 int config_read () { FILE *f = NULL; long len; /* XXXXXXXXXXXxx Move DEFAULT_CONFIG settings to init */ if (!regex_config || !regex_config[0]) regex_config = DEFAULT_CONFIG; f = fopen (regex_config, "r"); if (f == NULL) { emsg ("couldn't open config file: %s", regex_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", regex_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) != len) { emsg ("couldn't read config file: %s", regex_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 () { char* next; char* p; char* t; int line = 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 = strchr (t, ':'); if (t == NULL) { emsg ("invalid config line: %s", p); return -1; } *t = 0; t++; /* Pass of the name and value */ 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_regexconfig (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; switch (which) { case LEAF_regexConfig: if (op == SNMP_OP_GET) return string_get (value, regex_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, ®ex_config)) != SNMP_ERR_NOERROR) return r; if (config_parse () < 0) { string_rollback (ctx, ®ex_config); return SNMP_ERR_GENERR; } break; case SNMP_OP_COMMIT: string_commit (ctx); break; case SNMP_OP_ROLLBACK: string_rollback (ctx, ®ex_config); break; default: ASSERT(0); break; } return SNMP_ERR_NOERROR; case LEAF_regexFifo: if (op == SNMP_OP_GET) return string_get (value, regex_fifo, -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, ®ex_fifo)) != SNMP_ERR_NOERROR) return r; /* TODO: Make and open the FIFO here.... if (config_parse () < 0) { string_rollback (ctx, ®ex_config); return SNMP_ERR_GENERR; } */ break; case SNMP_OP_COMMIT: string_commit (ctx); break; case SNMP_OP_ROLLBACK: string_rollback (ctx, ®ex_fifo); break; default: ASSERT(0); break; } return SNMP_ERR_NOERROR; } ASSERT(0); return -1; } int op_regex (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]; switch (op) { case SNMP_OP_GET: break; case SNMP_OP_SET: 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_regexCount: value->v.integer = entry_count; break; default: ASSERT(0); break; } return SNMP_ERR_NOERROR; } int op_regexentry (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]; struct data_entry *data; 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; } switch (which) { case LEAF_regexIndex: value->v.integer = data->index; break; case LEAF_regexDescr: return (string_get (value, data->descr, -1)); case LEAF_regexLast: value->v.uint32 = data->last_update; break; case LEAF_regexValue: value->v.uint32 = data->value; break; default: ASSERT(0); break; }; return SNMP_ERR_NOERROR; } /* ----------------------------------------------------------------------------- * MODULE */ /* the initialisation function */ static int module_init (struct lmodule *mod, int argc, char *argv[]) { module = mod; if (argc != 0) { syslog(LOG_ERR, "bad number of arguments for %s", __func__); return EINVAL; } regex_config = strdup (""); if (!regex_config) return ENOMEM; regex_fifo = strdup (""); if (!regex_fifo) { free (regex_config); return ENOMEM; } return 0; } /* Module is started */ static void module_start (void) { reg_index = or_register (&oid_regex, "The MIB for regex 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 (regex_config); free (regex_config); ASSERT (regex_fifo); free (regex_fifo); return 0; } const struct snmp_module config = { .comment = "This module implements SNMP listing of data from regular expressions", .init = module_init, .start = module_start, .fini = module_fini, .tree = regex_ctree, .tree_size = regex_CTREE_SIZE, };