e0fd476003
Submitted by: Maxim Konovalov <maxim@macomnet.ru>
1365 lines
26 KiB
C
1365 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.22 2004/08/12 17:09:49 brandt 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 (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);
|
|
return (0);
|
|
}
|