From 0fe9ee8d6a212e5b676f327b727c9d404edc7781 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Fri, 17 Mar 2006 17:27:28 +0000 Subject: Move src/ to module/ subdir --- module/bsnmp-regex.c | 1025 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1025 insertions(+) create mode 100644 module/bsnmp-regex.c (limited to 'module/bsnmp-regex.c') diff --git a/module/bsnmp-regex.c b/module/bsnmp-regex.c new file mode 100644 index 0000000..98a94a9 --- /dev/null +++ b/module/bsnmp-regex.c @@ -0,0 +1,1025 @@ + +#include "usuals.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef WITH_PCRE +#include +#else +#include +#endif + +#include "regex_tree.h" +#include "regex_oid.h" + +#define DEFAULT_CONFIG CONF_PREFIX "/rrdbot" +#define DEFAULT_FIFO "/var/run/snmp-regex.fifo" + +/* 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; + +#ifdef WITH_PCRE + pcre *regex; + pcre_extra *extra; +#else + regex_t regex; +#endif + 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; + + +/* configuration */ +static u_char *regex_config = NULL; +static char *config_memory = NULL; + +/* The FIFO log */ +static u_char *regex_fifo = NULL; +static int fifo_fd = -1; +static void *fifo_sel = NULL; + +/* Buffer for parsing logs */ +#define LINE_LENGTH 1024 +static char line_buffer[LINE_LENGTH]; + +/* ----------------------------------------------------------------------------- + * 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; +} + +static uint64_t +getcurrticks () +{ + struct timeval tp; + uint64_t t; + + /* Update tick count */ + if (gettimeofday (&tp, NULL) < 0) { + emsg ("couldn't get current time: %s", strerror (errno)); + return 0; + } + + t = (((uint64_t)tp.tv_sec) * 100) + (((uint64_t)tp.tv_usec) / 10000); + return t; +} + +#ifndef WITH_PCRE + +static const char* +regex_msg (int code) +{ + ASSERT (code != 0); + + switch (code) { + case REG_NOMATCH: + return "The regexec() function failed to match"; + case REG_BADPAT: + return "invalid regular expression"; + case REG_ECOLLATE: + return "invalid collating element"; + case REG_ECTYPE: + return "invalid character class"; + case REG_EESCAPE: + return "'\' applied to unescapable character"; + case REG_ESUBREG: + return "invalid backreference number"; + case REG_EBRACK: + return "brackets '[ ]' not balanced"; + case REG_EPAREN: + return "parentheses '( )' not balanced"; + case REG_EBRACE: + return "braces '{ }' not balanced"; + case REG_BADBR: + return "invalid repetition count(s) in '{ }'"; + case REG_ERANGE: + return "invalid character range in '[ ]'"; + case REG_ESPACE: + return "ran out of memory"; + case REG_BADRPT: + return "'?', '*', or '+' operand invalid"; + case REG_EMPTY: + return "empty (sub)expression"; + case REG_ILLSEQ: + return "illegal byte sequence (bad multibyte character)"; + + case REG_ASSERT: + case REG_INVARG: + default: + return "internal or unknown error"; + } +} + +#endif /* WITH_PCRE */ + +/* ----------------------------------------------------------------------------- + * MATCHING + */ + +static int +process_match (struct data_entry *data, char *result) +{ + char *t; + int i; + + switch(data->type) { + case TYPE_COUNTER: + data->value++; + break; + case TYPE_INTEGER: + if (!result) { + emsg ("match, but no result data for '%s'", data->descr); + return -1; + } + + i = strtol (result, &t, 10); + if (*t) { + emsg ("invalid integer for '%s': %s", data->descr, result); + return -1; + } + + data->value = i; + break; + + default: + ASSERT(0); + break; + } + + data->last_update = getcurrticks (); + + /* + fprintf(stderr, "updated '%s' to value '%lld' at '%lld'\n", + data->descr, data->value, data->last_update); + */ + + return 0; +} + +static char* +process_result (struct data_entry *data, char *line, int len, +#ifdef WITH_PCRE + int *ovector, int substrings) +#else + regmatch_t *pm) +#endif +{ + char *result; + char *p, *t; + int rlen, l; + int idx; + + /* Some nice little optimizations */ + if (!data->result) + return NULL; + if (strchr (data->result, '\\') == NULL) + return strdup (data->result); + + /* Figure out the string length */ + rlen = strlen (data->result) + 1; + for (p = data->result; *p; p++) { + if(p[0] == '\\') { + p++; + if(p[0] == '\\' || !isdigit(p[0])) + continue; + idx = p[0] - '0'; + ASSERT (idx >= 0 && idx <= 9); +#ifdef WITH_PCRE + if (idx < substrings) { + ASSERT (ovector[(idx * 2) + 1] >= ovector[idx * 2]); + ASSERT (ovector[(idx * 2) + 1] < len); + ASSERT (ovector[idx * 2] < len); + rlen += (ovector[(idx * 2) + 1]) - (ovector[idx * 2]); + rlen += 1; + } +#else /* WITH_PCRE */ + if (pm[idx].rm_so != -1 && pm[idx].rm_eo != -1) { + ASSERT (pm[idx].rm_eo >= pm[idx].rm_so); + ASSERT (pm[idx].rm_eo < len); + ASSERT (pm[idx].rm_so < len); + rlen += (pm[idx].rm_eo - pm[idx].rm_so); + rlen += 1; + } +#endif /* WITH_PCRE */ + } + } + + result = (char*)calloc(rlen, 1); + if (!result) { + emsg ("out of memory"); + return NULL; + } + + for (p = data->result, t = result; *p; p++) { + if (p[0] == '\\') { + p++; + if (isdigit(p[0])) { + idx = p[0] - '0'; +#ifdef WITH_PCRE + if (idx < substrings) { + l = (ovector[(idx * 2) + 1]) - (ovector[idx * 2]); + memcpy (t, line + (ovector[idx * 2]), l); + t += l; + } +#else /* WITH_PCRE */ + if (pm[idx].rm_so != -1 && pm[idx].rm_eo != -1) { + l = pm[idx].rm_eo - pm[idx].rm_so; + memcpy (t, line + pm[idx].rm_so, l); + t += l; + } +#endif /* WITH _PCRE */ + + continue; + } + } + + *t++ = *p; + } + + *t = 0; + return result; +} + +static void +process_log (char *line, int len) +{ + struct data_entry *data; + char *result; + int r; + +#ifdef WITH_PCRE + int ovector[30]; +#else + regmatch_t pm[10]; +#endif + + for (data = TAILQ_FIRST (&entries); data; + data = TAILQ_NEXT (data, link)) { + + result = NULL; + +#ifdef WITH_PCRE + memset (ovector, 0, sizeof (ovector)); + r = pcre_exec (data->regex, data->extra, line, len, 0, + PCRE_NOTEMPTY | PCRE_NO_UTF8_CHECK, ovector, 30); + + if (r == PCRE_ERROR_NOMATCH) + continue; + else if (r == PCRE_ERROR_NOMEMORY) { + emsg ("out of memory"); + return; + } else if (r < 0) { + emsg ("internal error in matching code: %d", r); + return; + } + + result = process_result (data, line, len, ovector, r); + +#else + r = regexec (&(data->regex), line, 10, pm, 0); + if (r == REG_NOMATCH) + continue; + else if (r != 0) { + emsg ("internal error in matching code: %d", r); + return; + } + + result = process_result (data, line, len, pm); +#endif + + process_match (data, result); + free (result); + } +} + +/* ----------------------------------------------------------------------------- + * LOG READING + */ + +static void +close_fifo () +{ + if (fifo_sel) { + fd_deselect (fifo_sel); + fifo_sel = NULL; + } + + if (fifo_fd != -1) { + close (fifo_fd); + fifo_fd = -1; + } + + memset (line_buffer, 0, sizeof (line_buffer)); +} + +static void +receive_log (int fd, void *data) +{ + char *t; + int len; + int r, l; + + ASSERT (fd == fifo_fd); + + len = strlen (line_buffer); + ASSERT (len < LINE_LENGTH); + + do { + r = read (fd, line_buffer + len, (LINE_LENGTH - 1) - len); + + if (r < 0 && errno != EAGAIN) { + emsg ("couldn't read from fifo: %s: %s", regex_fifo, strerror (errno)); + return; + + /* Got data, null terminate */ + } else if (r > 0) { + len += r; + ASSERT (len < LINE_LENGTH); + line_buffer[len] = 0; + } + + /* Break really long lines */ + if (len >= LINE_LENGTH - 1) + line_buffer[len - 1] = '\n'; + + for (;;) { + t = strchr (line_buffer, '\n'); + if (t == NULL) + break; + + /* Break line (also DOS line) */ + *t = 0; + if (line_buffer != t && *(t - 1) == '\r') + *(t - 1) = 0; + l = (t + 1) - line_buffer; + + /* Send it off */ + process_log (line_buffer, l); + + /* Move data to front of buffer */ + ASSERT (l <= len); + memmove (line_buffer, t + 1, (len - l) + 1); + len -= l; + } + + } while (r > 0); +} + +static int +open_fifo () +{ + struct stat sb; + + close_fifo (); + + ASSERT (regex_fifo && regex_fifo[0]); + + if (stat (regex_fifo, &sb) == 0) { + /* Complain if it's not a FIFO */ + if (!S_ISFIFO (sb.st_mode)) { + emsg ("file or directory already exists: %s", regex_fifo); + return -1; + } + + } else if (errno == ENOENT) { + /* Try and create it */ + if (mkfifo (regex_fifo, S_IRWXU | S_IRWXG | S_IRWXO) < 0) { + emsg ("couldn't create fifo: %s: %s", regex_fifo, strerror (errno)); + return -1; + } + + } else { + emsg ("couldn't access fifo: %s: %s", regex_fifo, strerror (errno)); + return -1; + } + + fifo_fd = open (regex_fifo, O_RDONLY | O_NONBLOCK); + if (fifo_fd < 0) { + emsg ("couldn't open fifo: %s: %s", regex_fifo, strerror (errno)); + return -1; + } + + fifo_sel = fd_select (fifo_fd, receive_log, NULL, module); + if (!fifo_sel) { + emsg ("couldn't listen on fifo: %s: %s", regex_fifo, strerror (errno)); + return -1; + } + + memset (line_buffer, 0, sizeof (line_buffer)); + return 0; +} + +/* ----------------------------------------------------------------------------- + * CONFIG PARSING + */ + +static void +config_free (struct data_entry *data) +{ +#ifdef WITH_PCRE + if (data->regex) + free (data->regex); + if (data->extra) + free (data->extra); +#else + regfree (&(data->regex)); +#endif + + 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) +{ +#ifdef WITH_PCRE + const char *errptr; + int erroffset; + int options = 0; +#else + int options = REG_EXTENDED; + int r; +#endif + + 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': +#ifdef WITH_PCRE + options |= PCRE_CASELESS; +#else + options |= REG_ICASE; +#endif + break; + default: + emsg ("[line %d] invalid flag: %c", line, flags[0]); + return -1; + } + } + + /* The name */ + data->descr = name; + + /* Parse the regex */ +#ifdef WITH_PCRE + 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); +#else + r = regcomp (&(data->regex), regex, options); + if (r != 0) { + emsg ("[line %d] invalid regular expression: %s", line, regex_msg (r)); + return -1; + } +#endif + + /* 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; + + ASSERT (regex_config && regex_config[0]); + + 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 = SNMP_ERR_NOERROR; + + 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) { + if (!regex_config[0]) + r = SNMP_ERR_WRONG_VALUE; + else if (config_parse () < 0) + r = SNMP_ERR_GENERR; + } + if (r != SNMP_ERR_NOERROR) + string_rollback (ctx, ®ex_config); + break; + case SNMP_OP_COMMIT: + string_commit (ctx); + break; + case SNMP_OP_ROLLBACK: + string_rollback (ctx, ®ex_config); + break; + default: + ASSERT(0); + break; + } + + return r; + + + 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) { + if (!regex_fifo[0]) + r = SNMP_ERR_WRONG_VALUE; + else if (open_fifo () < 0) + r = SNMP_ERR_GENERR; + } + if (r != SNMP_ERR_NOERROR) + string_rollback (ctx, ®ex_config); + break; + case SNMP_OP_COMMIT: + string_commit (ctx); + break; + case SNMP_OP_ROLLBACK: + string_rollback (ctx, ®ex_fifo); + break; + default: + ASSERT(0); + break; + } + + return r; + } + + 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; + uint64_t ticks; + + 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: + if (data->last_update) { + ticks = getcurrticks (); + if (ticks == 0) + return SNMP_ERR_GENERR; + value->v.uint32 = (ticks - data->last_update); + } else { + value->v.uint32 = 0; + } + 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 (DEFAULT_CONFIG); + if (!regex_config) + return ENOMEM; + + regex_fifo = strdup (DEFAULT_FIFO); + 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); + + /* Initial reads */ + receive_log (fifo_fd, NULL); +} + +/* 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); + + close_fifo (); + config_free_all (); + + 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, +}; + + -- cgit v1.2.3