diff options
Diffstat (limited to 'contrib/bsnmp/snmpd/config.c')
-rw-r--r-- | contrib/bsnmp/snmpd/config.c | 1361 |
1 files changed, 1361 insertions, 0 deletions
diff --git a/contrib/bsnmp/snmpd/config.c b/contrib/bsnmp/snmpd/config.c new file mode 100644 index 0000000..e247797 --- /dev/null +++ b/contrib/bsnmp/snmpd/config.c @@ -0,0 +1,1361 @@ +/* + * Copyright (c) 2001-2003 + * Fraunhofer Institute for Open Communication Systems (FhG Fokus). + * All rights reserved. + * + * Author: Harti Brandt <harti@freebsd.org> + * + * Redistribution of this software and documentation and use in source and + * binary forms, with or without modification, are permitted provided that + * the following conditions are met: + * + * 1. Redistributions of source code or documentation must retain the above + * copyright notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY FRAUNHOFER FOKUS + * AND ITS 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 + * FRAUNHOFER FOKUS OR ITS 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. + * + * $Begemot: bsnmp/snmpd/config.c,v 1.18 2003/03/11 15:30:01 hbb Exp $ + * + * Parse configuration file. + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <ctype.h> +#include <errno.h> +#include <syslog.h> +#include <unistd.h> +#include <limits.h> +#include <netdb.h> +#include <setjmp.h> +#include <inttypes.h> + +#include "snmpmod.h" +#include "snmpd.h" +#include "tree.h" + +/* +#define DEBUGGING +*/ + +/* + * config_file: EMPTY | config_file line + * + * line: oid '=' value + * | '%' STRING + * | STRING := REST_OF_LINE + * | STRING ?= REST_OF_LINE + * | . INCLUDE STRING + * + * oid: STRING suboid + * + * suboid: EMPTY | suboid '.' subid + * + * subid: NUM | STRING | '[' STRING ']' + * + * value: EMPTY | STRING | NUM + */ + +/* + * Input context for macros and includes + */ +enum input_type { + INPUT_FILE = 1, + INPUT_STRING +}; +struct input { + enum input_type type; + union { + struct { + FILE *fp; + char *filename; + u_int lno; + } file; + struct { + char *macro; + char *str; + char *ptr; + size_t left; + } str; + } u; + LIST_ENTRY(input) link; +}; +static LIST_HEAD(, input) inputs; + +#define input_fp u.file.fp +#define input_filename u.file.filename +#define input_lno u.file.lno +#define input_macro u.str.macro +#define input_str u.str.str +#define input_ptr u.str.ptr +#define input_left u.str.left + +static int input_push; +static int input_buf[2]; + +/* + * Configuration data. The configuration file is handled as one single + * SNMP transaction. So we need to keep the assignment data for the + * commit or rollback pass. Note, that dependencies and finish functions + * are NOT allowed here. + */ +struct assign { + struct snmp_value value; + struct snmp_scratch scratch; + const char *node_name; + + TAILQ_ENTRY(assign) link; +}; +static TAILQ_HEAD(assigns, assign) assigns = TAILQ_HEAD_INITIALIZER(assigns); + + +static struct snmp_context *snmp_ctx; + +struct macro { + char *name; + char *value; + size_t length; + LIST_ENTRY(macro) link; + int perm; +}; +static LIST_HEAD(, macro) macros = LIST_HEAD_INITIALIZER(¯os); + +enum { + TOK_EOF = 0200, + TOK_EOL, + TOK_NUM, + TOK_STR, + TOK_HOST, + TOK_ASSIGN, + TOK_QASSIGN, +}; + +/* lexer values and last token */ +static uint64_t numval; +static char strval[_POSIX2_LINE_MAX]; +static size_t strvallen; +static int token; + +/* error return */ +static jmp_buf errjmp[4]; +static volatile int errstk; + +# define ERRPUSH() (setjmp(errjmp[errstk++])) +# define ERRPOP() ((void)(errstk--)) +# define ERRNEXT() (longjmp(errjmp[--errstk], 1)) +# define ERR() (longjmp(errjmp[--errstk], 1)) + +/* section context */ +static int ignore; + +/* + * Report an error and jump to the error label + */ +static void report(const char *fmt, ...) __dead2 __printflike(1, 2); + +static void +report(const char *fmt, ...) +{ + va_list ap; + const struct input *input; + + va_start(ap, fmt); + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); + + LIST_FOREACH(input, &inputs, link) { + switch (input->type) { + + case INPUT_FILE: + syslog(LOG_ERR, " in file %s line %u", + input->input_filename, input->input_lno); + break; + + case INPUT_STRING: + syslog(LOG_ERR, " in macro %s pos %td", + input->input_macro, + input->input_ptr - input->input_str); + break; + } + } + ERR(); +} + +/* + * Open a file for input + */ +static int +input_open_file(const char *fname, int sysdir) +{ + struct input *input; + FILE *fp; + char path[PATH_MAX + 1]; + char *col; + const char *ptr; + + if (sysdir) { + ptr = syspath; + fp = NULL; + while (*ptr != '\0') { + if ((col = strchr(ptr, ':')) == NULL) + snprintf(path, sizeof(path), "%s/%s", + ptr, fname); + else if (col == ptr) + snprintf(path, sizeof(path), "./%s", fname); + else + snprintf(path, sizeof(path), "%.*s/%s", + (int)(col - ptr), ptr, fname); + if ((fp = fopen(path, "r")) != NULL) + break; + ptr = col + 1; + } + } else + fp = fopen(fname, "r"); + + if (fp == NULL) + report("%s: %m", fname); + + if ((input = malloc(sizeof(*input))) == NULL) { + fclose(fp); + return (-1); + } + if ((input->input_filename = malloc(strlen(fname) + 1)) == NULL) { + fclose(fp); + free(input); + return (-1); + } + strcpy(input->input_filename, fname); + input->input_fp = fp; + input->input_lno = 1; + input->type = INPUT_FILE; + LIST_INSERT_HEAD(&inputs, input, link); + return (0); +} + +/* + * Make a macro the next input + */ +static void +input_open_macro(struct macro *m) +{ + struct input *input; + + if ((input = malloc(sizeof(*input))) == NULL) + report("%m"); + input->type = INPUT_STRING; + input->input_macro = m->name; + if ((input->input_str = malloc(m->length)) == NULL) { + free(input); + report("%m"); + } + memcpy(input->input_str, m->value, m->length); + input->input_ptr = input->input_str; + input->input_left = m->length; + LIST_INSERT_HEAD(&inputs, input, link); +} + +/* + * Close top input source + */ +static void +input_close(void) +{ + struct input *input; + + if ((input = LIST_FIRST(&inputs)) == NULL) + abort(); + switch (input->type) { + + case INPUT_FILE: + fclose(input->input_fp); + free(input->input_filename); + break; + + case INPUT_STRING: + free(input->input_str); + break; + } + LIST_REMOVE(input, link); + free(input); +} + +/* + * Close all inputs + */ +static void +input_close_all(void) +{ + while (!LIST_EMPTY(&inputs)) + input_close(); +} + +/* + * Push back one character + */ +static void +input_ungetc(int c) +{ + if (c == EOF) + report("pushing EOF"); + if (input_push == 2) + report("pushing third char"); + input_buf[input_push++] = c; +} + + +/* + * Return next character from the input without preprocessing. + */ +static int +input_getc_raw(void) +{ + int c; + struct input *input; + + if (input_push != 0) { + c = input_buf[--input_push]; + goto ok; + } + while ((input = LIST_FIRST(&inputs)) != NULL) { + switch (input->type) { + + case INPUT_FILE: + if ((c = getc(input->input_fp)) == EOF) { + if (ferror(input->input_fp)) + report("read error: %m"); + input_close(); + break; + } + if (c == '\n') + input->input_lno++; + goto ok; + + case INPUT_STRING: + if (input->input_left-- == 0) { + input_close(); + break; + } + c = *input->input_ptr++; + goto ok; + } + } +# ifdef DEBUGGING + fprintf(stderr, "EOF"); +# endif + return (EOF); + + ok: +# ifdef DEBUGGING + if (!isascii(c) || !isprint(c)) + fprintf(stderr, "'%#2x'", c); + else + fprintf(stderr, "'%c'", c); +# endif + return (c); +} + +/* + * Get character with and \\n -> processing. + */ +static int +input_getc_plain(void) +{ + int c; + + again: + if ((c = input_getc_raw()) == '\\') { + if ((c = input_getc_raw()) == '\n') + goto again; + if (c != EOF) + input_ungetc(c); + return ('\\'); + } + return (c); +} + +/* + * Get next character with substitution of macros + */ +static int +input_getc(void) +{ + int c; + struct macro *m; + char name[_POSIX2_LINE_MAX]; + size_t namelen; + + again: + if ((c = input_getc_plain()) != '$') + return (c); + + if ((c = input_getc()) == EOF) + report("unexpected EOF"); + if (c != '(') + report("expecting '(' after '$'"); + + namelen = 0; + while ((c = input_getc()) != EOF && c != ')') { + if (isalpha(c) || c == '_' || (namelen != 0 && isdigit(c))) + name[namelen++] = c; + else + goto badchar; + } + if (c == EOF) + report("unexpected EOF"); + name[namelen++] = '\0'; + + LIST_FOREACH(m, ¯os, link) + if (strcmp(m->name, name) == 0) + break; + if (m == NULL) + report("undefined macro '%s'", name); + + input_open_macro(m); + goto again; + + badchar: + if (!isascii(c) || !isprint(c)) + report("unexpected character %#2x", (u_int)c); + else + report("bad character '%c'", c); +} + + +static void +input_getnum(u_int base, u_int flen) +{ + int c; + u_int cnt; + + cnt = 0; + numval = 0; + while (flen == 0 || cnt < flen) { + if ((c = input_getc()) == EOF) { + if (cnt == 0) + report("bad number"); + return; + } + if (isdigit(c)) { + if (base == 8 && (c == '8' || c == '9')) { + input_ungetc(c); + if (cnt == 0) + report("bad number"); + return; + } + numval = numval * base + (c - '0'); + } else if (base == 16 && isxdigit(c)) { + if (islower(c)) + numval = numval * base + (c - 'a' + 10); + else + numval = numval * base + (c - 'A' + 10); + } else { + input_ungetc(c); + if (cnt == 0) + report("bad number"); + return; + } + cnt++; + } +} + +static int +# ifdef DEBUGGING +_gettoken(void) +# else +gettoken(void) +# endif +{ + int c; + char *end; + static const char esc[] = "abfnrtv"; + static const char chr[] = "\a\b\f\n\r\t\v"; + + /* + * Skip any whitespace before the next token + */ + while ((c = input_getc()) != EOF) { + if (!isspace(c) || c == '\n') + break; + } + if (c == EOF) + return (token = TOK_EOF); + if (!isascii(c)) + goto badchar; + + /* + * Skip comments + */ + if (c == '#') { + while ((c = input_getc_plain()) != EOF) { + if (c == '\n') + return (token = TOK_EOL); + } + goto badeof; + } + + /* + * Single character tokens + */ + if (c == '\n') + return (token = TOK_EOL); + if (c == '.' || c == '%' || c == '=' || c == '<' || c == '>') + return (token = c); + if (c == ':') { + if ((c = input_getc()) == '=') + return (token = TOK_ASSIGN); + input_ungetc(c); + return (token = ':'); + } + if (c == '?') { + if ((c = input_getc()) == '=') + return (token = TOK_QASSIGN); + input_ungetc(c); + goto badchar; + } + + /* + * Sort out numbers + */ + if (isdigit(c)) { + if (c == '0') { + if ((c = input_getc()) == 'x' || c == 'X') { + input_getnum(16, 0); + } else if (isdigit(c)) { + input_ungetc(c); + input_getnum(8, 0); + } else { + if (c != EOF) + input_ungetc(c); + numval = 0; + c = 1; + } + } else { + input_ungetc(c); + input_getnum(10, 0); + } + return (token = TOK_NUM); + } + + /* + * Must be a string then + */ + strvallen = 0; + +# define GETC(C) do { \ + if ((c = input_getc()) == EOF) \ + goto badeof; \ + if (!isascii(c) || (!isprint(c) && c != '\t')) \ + goto badchar; \ +} while(0) + + if (c == '"') { + for(;;) { + GETC(c); + if (c == '"') { + strval[strvallen] = '\0'; + break; + } + if (c != '\\') { + strval[strvallen++] = c; + continue; + } + GETC(c); + if ((end = strchr(esc, c)) != NULL) { + strval[strvallen++] = chr[end - esc]; + continue; + } + if (c == 'x') { + input_getnum(16, 2); + c = numval; + } else if (c >= '0' && c <= '7') { + input_ungetc(c); + input_getnum(8, 3); + c = numval; + } + strval[strvallen++] = c; + } +# undef GETC + + } else if (c == '[') { + /* + * Skip leading space + */ + while ((c = input_getc()) != EOF && isspace(c)) + ; + if (c == EOF) + goto badeof; + while (c != ']' && !isspace(c)) { + if (!isalnum(c) && c != '.' && c != '-') + goto badchar; + strval[strvallen++] = c; + if ((c = input_getc()) == EOF) + goto badeof; + } + while (c != ']' && isspace(c)) { + if ((c = input_getc()) == EOF) + goto badeof; + } + if (c != ']') + goto badchar; + strval[strvallen] = '\0'; + return (token = TOK_HOST); + + } else if (!isalpha(c) && c != '_') { + goto badchar; + + } else { + for (;;) { + strval[strvallen++] = c; + if ((c = input_getc()) == EOF) + goto badeof; + if (!isalnum(c) && c != '_' && c != '-') { + input_ungetc(c); + strval[strvallen] = '\0'; + break; + } + } + } + + return (token = TOK_STR); + + badeof: + report("unexpected EOF"); + + badchar: + if (!isascii(c) || !isprint(c)) + report("unexpected character %#2x", (u_int)c); + else + report("bad character '%c'", c); +} + +# ifdef DEBUGGING +static int +gettoken() +{ + _gettoken(); + if (isascii(token) && isprint(token)) + printf("(%c)", token); + else { + switch (token) { + + case TOK_EOF: + printf("(EOF)"); + break; + case TOK_EOL: + printf("(EOL)"); + break; + case TOK_NUM: + printf("(NUM %llu)", numval); + break; + case TOK_STR: + printf("(STR %.*s)", (int)strvallen, strval); + break; + case TOK_HOST: + printf("(HOST %s)", strval); + break; + default: + printf("(%#2x)", token); + break; + } + } + return (token); +} +#endif + + +/* + * Try to execute the assignment. + */ +static void +handle_assignment(const struct snmp_node *node, struct asn_oid *vindex, + const struct snmp_value *value) +{ + u_int i; + int err; + struct assign *tp; + char nodename[100]; + + if (node->type == SNMP_NODE_LEAF) { + /* index must be one single zero or no index at all */ + if (vindex->len > 1 || (vindex->len == 1 && + vindex->subs[0] != 0)) + report("bad index on leaf node"); + vindex->len = 1; + vindex->subs[0] = 0; + } else { + /* resulting oid must not be too long */ + if (node->oid.len + vindex->len > ASN_MAXOIDLEN) + report("resulting OID too long"); + } + + /* + * Get the next assignment entry for the transaction. + */ + if ((tp = malloc(sizeof(*tp))) == NULL) + report("%m"); + + tp->value = *value; + tp->node_name = node->name; + + /* + * Build the OID + */ + tp->value.var = node->oid; + for (i = 0; i < vindex->len; i++) + tp->value.var.subs[tp->value.var.len++] = vindex->subs[i]; + + /* + * Puzzle together the variables for the call and call the + * set routine. The set routine may make our node pointer + * invalid (if we happend to call the module loader) so + * get a copy of the node name beforehands. + */ + snprintf(nodename, sizeof(nodename), "%s", node->name); + snmp_ctx->scratch = &tp->scratch; + snmp_ctx->var_index = 0; + err = (*node->op)(snmp_ctx, &tp->value, node->oid.len, node->index, + SNMP_OP_SET); + if (err != 0) { + free(tp); + report("assignment to %s.%s returns %d", nodename, + asn_oid2str(vindex), err); + } + + TAILQ_INSERT_TAIL(&assigns, tp, link); +} + + +/* + * Parse the section statement + */ +static void +parse_section(const struct lmodule *mod) +{ + if (token != TOK_STR) + report("expecting section name"); + + if (strcmp(strval, "snmpd") == 0) { + if (mod != NULL) + /* loading a module - ignore common stuff */ + ignore = 1; + else + /* global configuration - don't ignore */ + ignore = 0; + } else { + if (mod == NULL) { + /* global configuration - ignore module stuff */ + ignore = 1; + } else { + /* loading module - check if it's our section */ + ignore = (strcmp(strval, mod->section) != 0); + } + } + gettoken(); +} + +/* + * Convert a hostname to four u_chars + */ +static void +gethost(const char *host, u_char *ip) +{ + struct addrinfo hints, *res; + int error; + struct sockaddr_in *sain; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_PASSIVE; + error = getaddrinfo(host, NULL, &hints, &res); + if (error != 0) + report("%s: %s", host, gai_strerror(error)); + if (res == NULL) + report("%s: unknown hostname", host); + + sain = (struct sockaddr_in *)(void *)res->ai_addr; + sain->sin_addr.s_addr = ntohl(sain->sin_addr.s_addr); + ip[0] = sain->sin_addr.s_addr >> 24; + ip[1] = sain->sin_addr.s_addr >> 16; + ip[2] = sain->sin_addr.s_addr >> 8; + ip[3] = sain->sin_addr.s_addr >> 0; + + freeaddrinfo(res); +} + +/* + * Parse the left hand side of a config line. + */ +static const struct snmp_node * +parse_oid(const char *varname, struct asn_oid *oid) +{ + struct snmp_node *node; + u_int i; + u_char ip[4]; + + for (node = tree; node < &tree[tree_size]; node++) + if (strcmp(varname, node->name) == 0) + break; + if (node == &tree[tree_size]) + node = NULL; + + oid->len = 0; + while (token == '.') { + if (gettoken() == TOK_NUM) { + if (numval > ASN_MAXID) + report("subid too large %#"PRIx64, numval); + if (oid->len == ASN_MAXOIDLEN) + report("index too long"); + oid->subs[oid->len++] = numval; + + } else if (token == TOK_STR) { + if (strvallen + oid->len + 1 > ASN_MAXOIDLEN) + report("oid too long"); + oid->subs[oid->len++] = strvallen; + for (i = 0; i < strvallen; i++) + oid->subs[oid->len++] = strval[i]; + + } else if (token == TOK_HOST) { + gethost(strval, ip); + if (oid->len + 4 > ASN_MAXOIDLEN) + report("index too long"); + for (i = 0; i < 4; i++) + oid->subs[oid->len++] = ip[i]; + + } else + report("bad token in index"); + gettoken(); + } + + return (node); +} + +/* + * Parse the value for an assignment. + */ +static void +parse_syntax_null(struct snmp_value *value __unused) +{ + if (token != TOK_EOL) + report("bad NULL syntax"); +} + +static void +parse_syntax_integer(struct snmp_value *value) +{ + if (token != TOK_NUM) + report("bad INTEGER syntax"); + if (numval > 0x7fffffff) + report("INTEGER too large %"PRIu64, numval); + + value->v.integer = numval; + gettoken(); +} + +static void +parse_syntax_counter64(struct snmp_value *value) +{ + if (token != TOK_NUM) + report("bad COUNTER64 syntax"); + + value->v.counter64 = numval; + gettoken(); +} + +static void +parse_syntax_octetstring(struct snmp_value *value) +{ + u_long alloc; + u_char *noct; + + if (token == TOK_STR) { + value->v.octetstring.len = strvallen; + value->v.octetstring.octets = malloc(strvallen); + (void)memcpy(value->v.octetstring.octets, strval, strvallen); + gettoken(); + return; + } + + /* XX:XX:XX syntax */ + value->v.octetstring.octets = NULL; + value->v.octetstring.len = 0; + + if (token != TOK_NUM) + /* empty string is allowed */ + return; + + if (ERRPUSH()) { + free(value->v.octetstring.octets); + ERRNEXT(); + } + + alloc = 0; + for (;;) { + if (token != TOK_NUM) + report("bad OCTETSTRING syntax"); + if (numval > 0xff) + report("byte value too large"); + if (alloc == value->v.octetstring.len) { + alloc += 100; + noct = realloc(value->v.octetstring.octets, alloc); + if (noct == NULL) + report("%m"); + value->v.octetstring.octets = noct; + } + value->v.octetstring.octets[value->v.octetstring.len++] + = numval; + if (gettoken() != ':') + break; + gettoken(); + } + ERRPOP(); +} + +static void +parse_syntax_oid(struct snmp_value *value) +{ + value->v.oid.len = 0; + + if (token != TOK_NUM) + return; + + for (;;) { + if (token != TOK_NUM) + report("bad OID syntax"); + if (numval > ASN_MAXID) + report("subid too large"); + if (value->v.oid.len == ASN_MAXOIDLEN) + report("OID too long"); + value->v.oid.subs[value->v.oid.len++] = numval; + if (gettoken() != '.') + break; + gettoken(); + } +} + +static void +parse_syntax_ipaddress(struct snmp_value *value) +{ + int i; + u_char ip[4]; + + if (token == TOK_NUM) { + /* numerical address */ + i = 0; + for (;;) { + if (numval >= 256) + report("ip address part too large"); + value->v.ipaddress[i++] = numval; + if (i == 4) + break; + if (gettoken() != '.') + report("expecting '.' in ip address"); + } + gettoken(); + + } else if (token == TOK_HOST) { + /* host name */ + gethost(strval, ip); + for (i = 0; i < 4; i++) + value->v.ipaddress[i] = ip[i]; + gettoken(); + + } else + report("bad ip address syntax"); +} + +static void +parse_syntax_uint32(struct snmp_value *value) +{ + + if (token != TOK_NUM) + report("bad number syntax"); + if (numval > 0xffffffff) + report("number too large"); + value->v.uint32 = numval; + gettoken(); +} + +/* + * Parse an assignement line + */ +static void +parse_assign(const char *varname) +{ + struct snmp_value value; + struct asn_oid vindex; + const struct snmp_node *node; + + node = parse_oid(varname, &vindex); + if (token != '=') + report("'=' expected"); + gettoken(); + + if (ignore) { + /* skip rest of line */ + while (token != TOK_EOL && token != TOK_EOF) + gettoken(); + return; + } + if (node == NULL) + report("unknown variable"); + + switch (value.syntax = node->syntax) { + + case SNMP_SYNTAX_NULL: + parse_syntax_null(&value); + break; + + case SNMP_SYNTAX_INTEGER: + parse_syntax_integer(&value); + break; + + case SNMP_SYNTAX_COUNTER64: + parse_syntax_counter64(&value); + break; + + case SNMP_SYNTAX_OCTETSTRING: + parse_syntax_octetstring(&value); + break; + + case SNMP_SYNTAX_OID: + parse_syntax_oid(&value); + break; + + case SNMP_SYNTAX_IPADDRESS: + parse_syntax_ipaddress(&value); + break; + + case SNMP_SYNTAX_COUNTER: + case SNMP_SYNTAX_GAUGE: + case SNMP_SYNTAX_TIMETICKS: + parse_syntax_uint32(&value); + break; + + case SNMP_SYNTAX_NOSUCHOBJECT: + case SNMP_SYNTAX_NOSUCHINSTANCE: + case SNMP_SYNTAX_ENDOFMIBVIEW: + abort(); + } + + if (ERRPUSH()) { + snmp_value_free(&value); + ERRNEXT(); + } + + handle_assignment(node, &vindex, &value); + + ERRPOP(); +} + +/* + * Handle macro definition line + * We have already seen the := and the input now stands at the character + * after the =. Skip whitespace and then call the input routine directly to + * eat up characters. + */ +static void +parse_define(const char *varname) +{ + char *volatile string; + char *new; + volatile size_t alloc, length; + int c; + struct macro *m; + int t = token; + + alloc = 100; + length = 0; + if ((string = malloc(alloc)) == NULL) + report("%m"); + + if (ERRPUSH()) { + free(string); + ERRNEXT(); + } + + while ((c = input_getc_plain()) != EOF) { + if (c == '\n' || !isspace(c)) + break; + } + + while (c != EOF && c != '#' && c != '\n') { + if (alloc == length) { + alloc *= 2; + if ((new = realloc(string, alloc)) == NULL) + report("%m"); + string = new; + } + string[length++] = c; + c = input_getc_plain(); + } + if (c == '#') { + while ((c = input_getc_plain()) != EOF && c != '\n') + ; + } + if (c == EOF) + report("EOF in macro definition"); + + LIST_FOREACH(m, ¯os, link) + if (strcmp(m->name, varname) == 0) + break; + + if (m == NULL) { + if ((m = malloc(sizeof(*m))) == NULL) + report("%m"); + if ((m->name = malloc(strlen(varname) + 1)) == NULL) { + free(m); + report("%m"); + } + strcpy(m->name, varname); + m->perm = 0; + LIST_INSERT_HEAD(¯os, m, link); + + m->value = string; + m->length = length; + } else { + if (t != TOK_ASSIGN) { + free(m->value); + m->value = string; + m->length = length; + } + } + + token = TOK_EOL; + + ERRPOP(); +} + +/* + * Free all macros + */ +static void +macro_free_all(void) +{ + static struct macro *m, *m1; + + m = LIST_FIRST(¯os); + while (m != NULL) { + m1 = LIST_NEXT(m, link); + if (!m->perm) { + free(m->name); + free(m->value); + LIST_REMOVE(m, link); + free(m); + } + m = m1; + } +} + +/* + * Parse an include directive and switch to the new file + */ +static void +parse_include(void) +{ + int sysdir = 0; + char fname[_POSIX2_LINE_MAX]; + + if (gettoken() == '<') { + sysdir = 1; + if (gettoken() != TOK_STR) + report("expecting filename after in .include"); + } else if (token != TOK_STR) + report("expecting filename after in .include"); + + strcpy(fname, strval); + if (sysdir && gettoken() != '>') + report("expecting '>'"); + gettoken(); + if (input_open_file(fname, sysdir) == -1) + report("%s: %m", fname); +} + +/* + * Parse the configuration file + */ +static void +parse_file(const struct lmodule *mod) +{ + char varname[_POSIX2_LINE_MAX]; + + while (gettoken() != TOK_EOF) { + if (token == TOK_EOL) + /* empty line */ + continue; + if (token == '%') { + gettoken(); + parse_section(mod); + } else if (token == '.') { + if (gettoken() != TOK_STR) + report("keyword expected after '.'"); + if (strcmp(strval, "include") == 0) + parse_include(); + else + report("unknown keyword '%s'", strval); + } else if (token == TOK_STR) { + strcpy(varname, strval); + if (gettoken() == TOK_ASSIGN || token == TOK_QASSIGN) + parse_define(varname); + else + parse_assign(varname); + } + if (token != TOK_EOL) + report("eol expected"); + } +} + +/* + * Do rollback on errors + */ +static void +do_rollback(void) +{ + struct assign *tp; + struct snmp_node *node; + + while ((tp = TAILQ_LAST(&assigns, assigns)) != NULL) { + TAILQ_REMOVE(&assigns, tp, link); + for (node = tree; node < &tree[tree_size]; node++) + if (node->name == tp->node_name) { + snmp_ctx->scratch = &tp->scratch; + (void)(*node->op)(snmp_ctx, &tp->value, + node->oid.len, node->index, + SNMP_OP_ROLLBACK); + break; + } + if (node == &tree[tree_size]) + syslog(LOG_ERR, "failed to find node for " + "rollback"); + snmp_value_free(&tp->value); + free(tp); + } +} + +/* + * Do commit + */ +static void +do_commit(void) +{ + struct assign *tp; + struct snmp_node *node; + + while ((tp = TAILQ_FIRST(&assigns)) != NULL) { + TAILQ_REMOVE(&assigns, tp, link); + for (node = tree; node < &tree[tree_size]; node++) + if (node->name == tp->node_name) { + snmp_ctx->scratch = &tp->scratch; + (void)(*node->op)(snmp_ctx, &tp->value, + node->oid.len, node->index, SNMP_OP_COMMIT); + break; + } + if (node == &tree[tree_size]) + syslog(LOG_ERR, "failed to find node for commit"); + snmp_value_free(&tp->value); + free(tp); + } +} + +/* + * Read the configuration file. Handle the entire file as one transaction. + * + * If lodmod is NULL, the sections for 'snmpd' and all loaded modules are + * executed. If it is not NULL, only the sections for that module are handled. + */ +int +read_config(const char *fname, struct lmodule *lodmod) +{ + int err; + char objbuf[ASN_OIDSTRLEN]; + char idxbuf[ASN_OIDSTRLEN]; + + ignore = 0; + + input_push = 0; + if (input_open_file(fname, 0) == -1) { + syslog(LOG_ERR, "%s: %m", fname); + return (-1); + } + community = COMM_INITIALIZE; + + if ((snmp_ctx = snmp_init_context()) == NULL) { + syslog(LOG_ERR, "%m"); + return (-1); + } + + if (ERRPUSH()) { + do_rollback(); + input_close_all(); + macro_free_all(); + free(snmp_ctx); + return (-1); + } + parse_file(lodmod); + ERRPOP(); + + if ((err = snmp_dep_commit(snmp_ctx)) != SNMP_ERR_NOERROR) { + syslog(LOG_ERR, "init dep failed: %u %s %s", err, + asn_oid2str_r(&snmp_ctx->dep->obj, objbuf), + asn_oid2str_r(&snmp_ctx->dep->idx, idxbuf)); + snmp_dep_rollback(snmp_ctx); + do_rollback(); + input_close_all(); + macro_free_all(); + free(snmp_ctx); + return (-1); + } + + do_commit(); + macro_free_all(); + + free(snmp_ctx); + + return (0); +} + +/* + * Define a permanent macro + */ +int +define_macro(const char *name, const char *value) +{ + struct macro *m; + + if ((m = malloc(sizeof(*m))) == NULL) + return (-1); + if ((m->name = malloc(strlen(name) + 1)) == NULL) { + free(m); + return (-1); + } + strcpy(m->name, name); + if ((m->value = malloc(strlen(value) + 1)) == NULL) { + free(m->name); + free(m); + return (-1); + } + strcpy(m->value, value); + m->length = strlen(value); + return (0); +} |