#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, };