freebsd-skq/contrib/bsnmp/snmpd/config.c
antoine 157223c604 (S)LIST_HEAD_INITIALIZER takes a (S)LIST_HEAD as an argument.
Fix some wrong usages.
Note: this does not affect generated binaries as this argument is not used.

Approved by:	harti@
2010-03-27 13:43:18 +00:00

1368 lines
26 KiB
C

/*
* Copyright (c) 2001-2003
* Fraunhofer Institute for Open Communication Systems (FhG Fokus).
* All rights reserved.
*
* Author: Harti Brandt <harti@freebsd.org>
*
* Redistribution 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 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.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
*
* $Begemot: bsnmp/snmpd/config.c,v 1.25 2006/02/14 09:04:20 brandt_h 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(macros);
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];
const 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);
col = ptr + strlen(ptr) - 1;
} 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, &macros, 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 %#"QUADXFMT, 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 %"QUADFMT, 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, &macros, 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(&macros, 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(&macros);
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 (ERRPUSH())
return (-1);
if (input_open_file(fname, 0) == -1) {
syslog(LOG_ERR, "%s: %m", fname);
return (-1);
}
ERRPOP();
community = COMM_INITIALIZE;
if ((snmp_ctx = snmp_init_context()) == NULL) {
input_close_all();
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();
snmp_dep_finish(snmp_ctx);
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);
m->perm = 1;
LIST_INSERT_HEAD(&macros, m, link);
return (0);
}