2 * Copyright (c) 2001-2003
3 * Fraunhofer Institute for Open Communication Systems (FhG Fokus).
6 * Author: Harti Brandt <harti@freebsd.org>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * $Begemot: bsnmp/snmpd/config.c,v 1.25 2006/02/14 09:04:20 brandt_h Exp $
31 * Parse configuration file.
33 #include <sys/types.h>
34 #include <sys/socket.h>
58 * config_file: EMPTY | config_file line
62 * | STRING := REST_OF_LINE
63 * | STRING ?= REST_OF_LINE
68 * suboid: EMPTY | suboid '.' subid
70 * subid: NUM | STRING | '[' STRING ']'
72 * value: EMPTY | STRING | NUM
76 * Input context for macros and includes
97 LIST_ENTRY(input) link;
99 static LIST_HEAD(, input) inputs;
101 #define input_fp u.file.fp
102 #define input_filename u.file.filename
103 #define input_lno u.file.lno
104 #define input_macro u.str.macro
105 #define input_str u.str.str
106 #define input_ptr u.str.ptr
107 #define input_left u.str.left
109 static int input_push;
110 static int input_buf[2];
113 * Configuration data. The configuration file is handled as one single
114 * SNMP transaction. So we need to keep the assignment data for the
115 * commit or rollback pass. Note, that dependencies and finish functions
116 * are NOT allowed here.
119 struct snmp_value value;
120 struct snmp_scratch scratch;
121 const char *node_name;
123 TAILQ_ENTRY(assign) link;
125 static TAILQ_HEAD(assigns, assign) assigns = TAILQ_HEAD_INITIALIZER(assigns);
128 static struct snmp_context *snmp_ctx;
134 LIST_ENTRY(macro) link;
137 static LIST_HEAD(, macro) macros = LIST_HEAD_INITIALIZER(¯os);
149 /* lexer values and last token */
150 static uint64_t numval;
151 static char strval[_POSIX2_LINE_MAX];
152 static size_t strvallen;
156 static jmp_buf errjmp[4];
157 static volatile int errstk;
159 # define ERRPUSH() (setjmp(errjmp[errstk++]))
160 # define ERRPOP() ((void)(errstk--))
161 # define ERRNEXT() (longjmp(errjmp[--errstk], 1))
162 # define ERR() (longjmp(errjmp[--errstk], 1))
164 /* section context */
168 * Report an error and jump to the error label
170 static void report(const char *fmt, ...) __dead2 __printflike(1, 2);
173 report(const char *fmt, ...)
176 const struct input *input;
179 vsyslog(LOG_ERR, fmt, ap);
182 LIST_FOREACH(input, &inputs, link) {
183 switch (input->type) {
186 syslog(LOG_ERR, " in file %s line %u",
187 input->input_filename, input->input_lno);
191 syslog(LOG_ERR, " in macro %s pos %td",
193 input->input_ptr - input->input_str);
201 * Open a file for input
204 input_open_file(const char *fname, int sysdir)
208 char path[PATH_MAX + 1];
215 while (*ptr != '\0') {
216 if ((col = strchr(ptr, ':')) == NULL) {
217 snprintf(path, sizeof(path), "%s/%s",
219 col = ptr + strlen(ptr) - 1;
220 } else if (col == ptr)
221 snprintf(path, sizeof(path), "./%s", fname);
223 snprintf(path, sizeof(path), "%.*s/%s",
224 (int)(col - ptr), ptr, fname);
225 if ((fp = fopen(path, "r")) != NULL)
230 fp = fopen(fname, "r");
233 report("%s: %m", fname);
235 if ((input = malloc(sizeof(*input))) == NULL) {
239 if ((input->input_filename = malloc(strlen(fname) + 1)) == NULL) {
244 strcpy(input->input_filename, fname);
245 input->input_fp = fp;
246 input->input_lno = 1;
247 input->type = INPUT_FILE;
248 LIST_INSERT_HEAD(&inputs, input, link);
253 * Make a macro the next input
256 input_open_macro(struct macro *m)
260 if ((input = malloc(sizeof(*input))) == NULL)
262 input->type = INPUT_STRING;
263 input->input_macro = m->name;
264 if ((input->input_str = malloc(m->length)) == NULL) {
268 memcpy(input->input_str, m->value, m->length);
269 input->input_ptr = input->input_str;
270 input->input_left = m->length;
271 LIST_INSERT_HEAD(&inputs, input, link);
275 * Close top input source
282 if ((input = LIST_FIRST(&inputs)) == NULL)
284 switch (input->type) {
287 fclose(input->input_fp);
288 free(input->input_filename);
292 free(input->input_str);
295 LIST_REMOVE(input, link);
303 input_close_all(void)
305 while (!LIST_EMPTY(&inputs))
310 * Push back one character
316 report("pushing EOF");
318 report("pushing third char");
319 input_buf[input_push++] = c;
324 * Return next character from the input without preprocessing.
332 if (input_push != 0) {
333 c = input_buf[--input_push];
336 while ((input = LIST_FIRST(&inputs)) != NULL) {
337 switch (input->type) {
340 if ((c = getc(input->input_fp)) == EOF) {
341 if (ferror(input->input_fp))
342 report("read error: %m");
351 if (input->input_left-- == 0) {
355 c = *input->input_ptr++;
360 fprintf(stderr, "EOF");
366 if (!isascii(c) || !isprint(c))
367 fprintf(stderr, "'%#2x'", c);
369 fprintf(stderr, "'%c'", c);
375 * Get character with and \\n -> processing.
378 input_getc_plain(void)
383 if ((c = input_getc_raw()) == '\\') {
384 if ((c = input_getc_raw()) == '\n')
394 * Get next character with substitution of macros
401 char name[_POSIX2_LINE_MAX];
405 if ((c = input_getc_plain()) != '$')
408 if ((c = input_getc()) == EOF)
409 report("unexpected EOF");
411 report("expecting '(' after '$'");
414 while ((c = input_getc()) != EOF && c != ')') {
415 if (isalpha(c) || c == '_' || (namelen != 0 && isdigit(c)))
421 report("unexpected EOF");
422 name[namelen++] = '\0';
424 LIST_FOREACH(m, ¯os, link)
425 if (strcmp(m->name, name) == 0)
428 report("undefined macro '%s'", name);
434 if (!isascii(c) || !isprint(c))
435 report("unexpected character %#2x", (u_int)c);
437 report("bad character '%c'", c);
442 input_getnum(u_int base, u_int flen)
449 while (flen == 0 || cnt < flen) {
450 if ((c = input_getc()) == EOF) {
452 report("bad number");
456 if (base == 8 && (c == '8' || c == '9')) {
459 report("bad number");
462 numval = numval * base + (c - '0');
463 } else if (base == 16 && isxdigit(c)) {
465 numval = numval * base + (c - 'a' + 10);
467 numval = numval * base + (c - 'A' + 10);
471 report("bad number");
487 static const char esc[] = "abfnrtv";
488 static const char chr[] = "\a\b\f\n\r\t\v";
491 * Skip any whitespace before the next token
493 while ((c = input_getc()) != EOF) {
494 if (!isspace(c) || c == '\n')
498 return (token = TOK_EOF);
506 while ((c = input_getc_plain()) != EOF) {
508 return (token = TOK_EOL);
514 * Single character tokens
517 return (token = TOK_EOL);
518 if (c == '.' || c == '%' || c == '=' || c == '<' || c == '>')
521 if ((c = input_getc()) == '=')
522 return (token = TOK_ASSIGN);
524 return (token = ':');
527 if ((c = input_getc()) == '=')
528 return (token = TOK_QASSIGN);
538 if ((c = input_getc()) == 'x' || c == 'X') {
540 } else if (isdigit(c)) {
553 return (token = TOK_NUM);
557 * Must be a string then
561 # define GETC(C) do { \
562 if ((c = input_getc()) == EOF) \
564 if (!isascii(c) || (!isprint(c) && c != '\t')) \
572 strval[strvallen] = '\0';
576 strval[strvallen++] = c;
580 if ((end = strchr(esc, c)) != NULL) {
581 strval[strvallen++] = chr[end - esc];
587 } else if (c >= '0' && c <= '7') {
592 strval[strvallen++] = c;
596 } else if (c == '[') {
600 while ((c = input_getc()) != EOF && isspace(c))
604 while (c != ']' && !isspace(c)) {
605 if (!isalnum(c) && c != '.' && c != '-')
607 strval[strvallen++] = c;
608 if ((c = input_getc()) == EOF)
611 while (c != ']' && isspace(c)) {
612 if ((c = input_getc()) == EOF)
617 strval[strvallen] = '\0';
618 return (token = TOK_HOST);
620 } else if (!isalpha(c) && c != '_') {
625 strval[strvallen++] = c;
626 if ((c = input_getc()) == EOF)
628 if (!isalnum(c) && c != '_' && c != '-') {
630 strval[strvallen] = '\0';
636 return (token = TOK_STR);
639 report("unexpected EOF");
642 if (!isascii(c) || !isprint(c))
643 report("unexpected character %#2x", (u_int)c);
645 report("bad character '%c'", c);
653 if (isascii(token) && isprint(token))
654 printf("(%c)", token);
665 printf("(NUM %llu)", numval);
668 printf("(STR %.*s)", (int)strvallen, strval);
671 printf("(HOST %s)", strval);
674 printf("(%#2x)", token);
684 * Try to execute the assignment.
687 handle_assignment(const struct snmp_node *node, struct asn_oid *vindex,
688 const struct snmp_value *value)
695 if (node->type == SNMP_NODE_LEAF) {
696 /* index must be one single zero or no index at all */
697 if (vindex->len > 1 || (vindex->len == 1 &&
698 vindex->subs[0] != 0))
699 report("bad index on leaf node");
703 /* resulting oid must not be too long */
704 if (node->oid.len + vindex->len > ASN_MAXOIDLEN)
705 report("resulting OID too long");
709 * Get the next assignment entry for the transaction.
711 if ((tp = malloc(sizeof(*tp))) == NULL)
715 tp->node_name = node->name;
720 tp->value.var = node->oid;
721 for (i = 0; i < vindex->len; i++)
722 tp->value.var.subs[tp->value.var.len++] = vindex->subs[i];
725 * Puzzle together the variables for the call and call the
726 * set routine. The set routine may make our node pointer
727 * invalid (if we happend to call the module loader) so
728 * get a copy of the node name beforehands.
730 snprintf(nodename, sizeof(nodename), "%s", node->name);
731 snmp_ctx->scratch = &tp->scratch;
732 snmp_ctx->var_index = 0;
733 err = (*node->op)(snmp_ctx, &tp->value, node->oid.len, node->index,
737 report("assignment to %s.%s returns %d", nodename,
738 asn_oid2str(vindex), err);
741 TAILQ_INSERT_TAIL(&assigns, tp, link);
746 * Parse the section statement
749 parse_section(const struct lmodule *mod)
751 if (token != TOK_STR)
752 report("expecting section name");
754 if (strcmp(strval, "snmpd") == 0) {
756 /* loading a module - ignore common stuff */
759 /* global configuration - don't ignore */
763 /* global configuration - ignore module stuff */
766 /* loading module - check if it's our section */
767 ignore = (strcmp(strval, mod->section) != 0);
774 * Convert a hostname to four u_chars
777 gethost(const char *host, u_char *ip)
779 struct addrinfo hints, *res;
781 struct sockaddr_in *sain;
783 memset(&hints, 0, sizeof(hints));
784 hints.ai_family = AF_INET;
785 hints.ai_socktype = SOCK_DGRAM;
786 hints.ai_protocol = IPPROTO_UDP;
787 hints.ai_flags = AI_PASSIVE;
788 error = getaddrinfo(host, NULL, &hints, &res);
790 report("%s: %s", host, gai_strerror(error));
792 report("%s: unknown hostname", host);
794 sain = (struct sockaddr_in *)(void *)res->ai_addr;
795 sain->sin_addr.s_addr = ntohl(sain->sin_addr.s_addr);
796 ip[0] = sain->sin_addr.s_addr >> 24;
797 ip[1] = sain->sin_addr.s_addr >> 16;
798 ip[2] = sain->sin_addr.s_addr >> 8;
799 ip[3] = sain->sin_addr.s_addr >> 0;
805 * Parse the left hand side of a config line.
807 static const struct snmp_node *
808 parse_oid(const char *varname, struct asn_oid *oid)
810 struct snmp_node *node;
814 for (node = tree; node < &tree[tree_size]; node++)
815 if (strcmp(varname, node->name) == 0)
817 if (node == &tree[tree_size])
821 while (token == '.') {
822 if (gettoken() == TOK_NUM) {
823 if (numval > ASN_MAXID)
824 report("subid too large %#"QUADXFMT, numval);
825 if (oid->len == ASN_MAXOIDLEN)
826 report("index too long");
827 oid->subs[oid->len++] = numval;
829 } else if (token == TOK_STR) {
830 if (strvallen + oid->len + 1 > ASN_MAXOIDLEN)
831 report("oid too long");
832 oid->subs[oid->len++] = strvallen;
833 for (i = 0; i < strvallen; i++)
834 oid->subs[oid->len++] = strval[i];
836 } else if (token == TOK_HOST) {
838 if (oid->len + 4 > ASN_MAXOIDLEN)
839 report("index too long");
840 for (i = 0; i < 4; i++)
841 oid->subs[oid->len++] = ip[i];
844 report("bad token in index");
852 * Parse the value for an assignment.
855 parse_syntax_null(struct snmp_value *value __unused)
857 if (token != TOK_EOL)
858 report("bad NULL syntax");
862 parse_syntax_integer(struct snmp_value *value)
864 if (token != TOK_NUM)
865 report("bad INTEGER syntax");
866 if (numval > 0x7fffffff)
867 report("INTEGER too large %"QUADFMT, numval);
869 value->v.integer = numval;
874 parse_syntax_counter64(struct snmp_value *value)
876 if (token != TOK_NUM)
877 report("bad COUNTER64 syntax");
879 value->v.counter64 = numval;
884 parse_syntax_octetstring(struct snmp_value *value)
889 if (token == TOK_STR) {
890 value->v.octetstring.len = strvallen;
891 value->v.octetstring.octets = malloc(strvallen);
892 (void)memcpy(value->v.octetstring.octets, strval, strvallen);
897 /* XX:XX:XX syntax */
898 value->v.octetstring.octets = NULL;
899 value->v.octetstring.len = 0;
901 if (token != TOK_NUM)
902 /* empty string is allowed */
906 free(value->v.octetstring.octets);
912 if (token != TOK_NUM)
913 report("bad OCTETSTRING syntax");
915 report("byte value too large");
916 if (alloc == value->v.octetstring.len) {
918 noct = realloc(value->v.octetstring.octets, alloc);
921 value->v.octetstring.octets = noct;
923 value->v.octetstring.octets[value->v.octetstring.len++]
925 if (gettoken() != ':')
933 parse_syntax_oid(struct snmp_value *value)
935 value->v.oid.len = 0;
937 if (token != TOK_NUM)
941 if (token != TOK_NUM)
942 report("bad OID syntax");
943 if (numval > ASN_MAXID)
944 report("subid too large");
945 if (value->v.oid.len == ASN_MAXOIDLEN)
946 report("OID too long");
947 value->v.oid.subs[value->v.oid.len++] = numval;
948 if (gettoken() != '.')
955 parse_syntax_ipaddress(struct snmp_value *value)
960 if (token == TOK_NUM) {
961 /* numerical address */
965 report("ip address part too large");
966 value->v.ipaddress[i++] = numval;
969 if (gettoken() != '.')
970 report("expecting '.' in ip address");
974 } else if (token == TOK_HOST) {
977 for (i = 0; i < 4; i++)
978 value->v.ipaddress[i] = ip[i];
982 report("bad ip address syntax");
986 parse_syntax_uint32(struct snmp_value *value)
989 if (token != TOK_NUM)
990 report("bad number syntax");
991 if (numval > 0xffffffff)
992 report("number too large");
993 value->v.uint32 = numval;
998 * Parse an assignement line
1001 parse_assign(const char *varname)
1003 struct snmp_value value;
1004 struct asn_oid vindex;
1005 const struct snmp_node *node;
1007 node = parse_oid(varname, &vindex);
1009 report("'=' expected");
1013 /* skip rest of line */
1014 while (token != TOK_EOL && token != TOK_EOF)
1019 report("unknown variable");
1021 switch (value.syntax = node->syntax) {
1023 case SNMP_SYNTAX_NULL:
1024 parse_syntax_null(&value);
1027 case SNMP_SYNTAX_INTEGER:
1028 parse_syntax_integer(&value);
1031 case SNMP_SYNTAX_COUNTER64:
1032 parse_syntax_counter64(&value);
1035 case SNMP_SYNTAX_OCTETSTRING:
1036 parse_syntax_octetstring(&value);
1039 case SNMP_SYNTAX_OID:
1040 parse_syntax_oid(&value);
1043 case SNMP_SYNTAX_IPADDRESS:
1044 parse_syntax_ipaddress(&value);
1047 case SNMP_SYNTAX_COUNTER:
1048 case SNMP_SYNTAX_GAUGE:
1049 case SNMP_SYNTAX_TIMETICKS:
1050 parse_syntax_uint32(&value);
1053 case SNMP_SYNTAX_NOSUCHOBJECT:
1054 case SNMP_SYNTAX_NOSUCHINSTANCE:
1055 case SNMP_SYNTAX_ENDOFMIBVIEW:
1060 snmp_value_free(&value);
1064 handle_assignment(node, &vindex, &value);
1070 * Handle macro definition line
1071 * We have already seen the := and the input now stands at the character
1072 * after the =. Skip whitespace and then call the input routine directly to
1073 * eat up characters.
1076 parse_define(const char *varname)
1078 char *volatile string;
1080 volatile size_t alloc, length;
1087 if ((string = malloc(alloc)) == NULL)
1095 while ((c = input_getc_plain()) != EOF) {
1096 if (c == '\n' || !isspace(c))
1100 while (c != EOF && c != '#' && c != '\n') {
1101 if (alloc == length) {
1103 if ((new = realloc(string, alloc)) == NULL)
1107 string[length++] = c;
1108 c = input_getc_plain();
1111 while ((c = input_getc_plain()) != EOF && c != '\n')
1115 report("EOF in macro definition");
1117 LIST_FOREACH(m, ¯os, link)
1118 if (strcmp(m->name, varname) == 0)
1122 if ((m = malloc(sizeof(*m))) == NULL)
1124 if ((m->name = malloc(strlen(varname) + 1)) == NULL) {
1128 strcpy(m->name, varname);
1130 LIST_INSERT_HEAD(¯os, m, link);
1135 if (t == TOK_ASSIGN) {
1151 macro_free_all(void)
1153 static struct macro *m, *m1;
1155 m = LIST_FIRST(¯os);
1157 m1 = LIST_NEXT(m, link);
1161 LIST_REMOVE(m, link);
1169 * Parse an include directive and switch to the new file
1175 char fname[_POSIX2_LINE_MAX];
1177 if (gettoken() == '<') {
1179 if (gettoken() != TOK_STR)
1180 report("expecting filename after in .include");
1181 } else if (token != TOK_STR)
1182 report("expecting filename after in .include");
1184 strcpy(fname, strval);
1185 if (sysdir && gettoken() != '>')
1186 report("expecting '>'");
1188 if (input_open_file(fname, sysdir) == -1)
1189 report("%s: %m", fname);
1193 * Parse the configuration file
1196 parse_file(const struct lmodule *mod)
1198 char varname[_POSIX2_LINE_MAX];
1200 while (gettoken() != TOK_EOF) {
1201 if (token == TOK_EOL)
1207 } else if (token == '.') {
1208 if (gettoken() != TOK_STR)
1209 report("keyword expected after '.'");
1210 if (strcmp(strval, "include") == 0)
1213 report("unknown keyword '%s'", strval);
1214 } else if (token == TOK_STR) {
1215 strcpy(varname, strval);
1216 if (gettoken() == TOK_ASSIGN || token == TOK_QASSIGN)
1217 parse_define(varname);
1219 parse_assign(varname);
1221 if (token != TOK_EOL)
1222 report("eol expected");
1227 * Do rollback on errors
1233 struct snmp_node *node;
1235 while ((tp = TAILQ_LAST(&assigns, assigns)) != NULL) {
1236 TAILQ_REMOVE(&assigns, tp, link);
1237 for (node = tree; node < &tree[tree_size]; node++)
1238 if (node->name == tp->node_name) {
1239 snmp_ctx->scratch = &tp->scratch;
1240 (void)(*node->op)(snmp_ctx, &tp->value,
1241 node->oid.len, node->index,
1245 if (node == &tree[tree_size])
1246 syslog(LOG_ERR, "failed to find node for "
1248 snmp_value_free(&tp->value);
1260 struct snmp_node *node;
1262 while ((tp = TAILQ_FIRST(&assigns)) != NULL) {
1263 TAILQ_REMOVE(&assigns, tp, link);
1264 for (node = tree; node < &tree[tree_size]; node++)
1265 if (node->name == tp->node_name) {
1266 snmp_ctx->scratch = &tp->scratch;
1267 (void)(*node->op)(snmp_ctx, &tp->value,
1268 node->oid.len, node->index, SNMP_OP_COMMIT);
1271 if (node == &tree[tree_size])
1272 syslog(LOG_ERR, "failed to find node for commit");
1273 snmp_value_free(&tp->value);
1279 * Read the configuration file. Handle the entire file as one transaction.
1281 * If lodmod is NULL, the sections for 'snmpd' and all loaded modules are
1282 * executed. If it is not NULL, only the sections for that module are handled.
1285 read_config(const char *fname, struct lmodule *lodmod)
1288 char objbuf[ASN_OIDSTRLEN];
1289 char idxbuf[ASN_OIDSTRLEN];
1297 if (input_open_file(fname, 0) == -1) {
1298 syslog(LOG_ERR, "%s: %m", fname);
1302 community = COMM_INITIALIZE;
1304 if ((snmp_ctx = snmp_init_context()) == NULL) {
1306 syslog(LOG_ERR, "%m");
1320 if ((err = snmp_dep_commit(snmp_ctx)) != SNMP_ERR_NOERROR) {
1321 syslog(LOG_ERR, "init dep failed: %u %s %s", err,
1322 asn_oid2str_r(&snmp_ctx->dep->obj, objbuf),
1323 asn_oid2str_r(&snmp_ctx->dep->idx, idxbuf));
1324 snmp_dep_rollback(snmp_ctx);
1333 snmp_dep_finish(snmp_ctx);
1343 * Define a permanent macro
1346 define_macro(const char *name, const char *value)
1350 if ((m = malloc(sizeof(*m))) == NULL)
1352 if ((m->name = malloc(strlen(name) + 1)) == NULL) {
1356 strcpy(m->name, name);
1357 if ((m->value = malloc(strlen(value) + 1)) == NULL) {
1362 strcpy(m->value, value);
1363 m->length = strlen(value);
1365 LIST_INSERT_HEAD(¯os, m, link);