xref: /freebsd/contrib/bsnmp/snmpd/config.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
1 /*
2  * Copyright (c) 2001-2003
3  *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
4  *	All rights reserved.
5  *
6  * Author: Harti Brandt <harti@freebsd.org>
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
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.
16  *
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
27  * SUCH DAMAGE.
28  *
29  * $Begemot: bsnmp/snmpd/config.c,v 1.25 2006/02/14 09:04:20 brandt_h Exp $
30  *
31  * Parse configuration file.
32  */
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/un.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <stdarg.h>
40 #include <ctype.h>
41 #include <errno.h>
42 #include <syslog.h>
43 #include <unistd.h>
44 #include <limits.h>
45 #include <netdb.h>
46 #include <setjmp.h>
47 #include <inttypes.h>
48 
49 #include "snmpmod.h"
50 #include "snmpd.h"
51 #include "tree.h"
52 
53 /*
54 #define DEBUGGING
55 */
56 
57 /*
58  * config_file: EMPTY | config_file line
59  *
60  * line: oid '=' value
61  *     | '%' STRING
62  *     | STRING := REST_OF_LINE
63  *     | STRING ?= REST_OF_LINE
64  *     | . INCLUDE STRING
65  *
66  * oid: STRING suboid
67  *
68  * suboid: EMPTY | suboid '.' subid
69  *
70  * subid: NUM | STRING | '[' STRING ']'
71  *
72  * value: EMPTY | STRING | NUM
73  */
74 
75 /*
76  * Input context for macros and includes
77  */
78 enum input_type {
79 	INPUT_FILE	= 1,
80 	INPUT_STRING
81 };
82 struct input {
83 	enum input_type	type;
84 	union {
85 	    struct {
86 		FILE	*fp;
87 		char	*filename;
88 		u_int	lno;
89 	    }		file;
90 	    struct {
91 		char	*macro;
92 		char	*str;
93 		char	*ptr;
94 		size_t	left;
95 	    }		str;
96 	} u;
97 	LIST_ENTRY(input) link;
98 };
99 static LIST_HEAD(, input) inputs;
100 
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
108 
109 static int input_push;
110 static int input_buf[2];
111 
112 /*
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.
117  */
118 struct assign {
119 	struct snmp_value value;
120 	struct snmp_scratch scratch;
121 	const char *node_name;
122 
123 	TAILQ_ENTRY(assign) link;
124 };
125 static TAILQ_HEAD(assigns, assign) assigns = TAILQ_HEAD_INITIALIZER(assigns);
126 
127 
128 static struct snmp_context *snmp_ctx;
129 
130 struct macro {
131 	char	*name;
132 	char	*value;
133 	size_t	length;
134 	LIST_ENTRY(macro) link;
135 	int	perm;
136 };
137 static LIST_HEAD(, macro) macros = LIST_HEAD_INITIALIZER(&macros);
138 
139 enum {
140 	TOK_EOF	= 0200,
141 	TOK_EOL,
142 	TOK_NUM,
143 	TOK_STR,
144 	TOK_HOST,
145 	TOK_ASSIGN,
146 	TOK_QASSIGN,
147 };
148 
149 /* lexer values and last token */
150 static uint64_t	numval;
151 static char	strval[_POSIX2_LINE_MAX];
152 static size_t	strvallen;
153 static int	token;
154 
155 /* error return */
156 static jmp_buf	errjmp[4];
157 static volatile int errstk;
158 
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))
163 
164 /* section context */
165 static int ignore;
166 
167 /*
168  * Report an error and jump to the error label
169  */
170 static void report(const char *fmt, ...) __dead2 __printflike(1, 2);
171 
172 static void
173 report(const char *fmt, ...)
174 {
175 	va_list ap;
176 	const struct input *input;
177 
178 	va_start(ap, fmt);
179 	vsyslog(LOG_ERR, fmt, ap);
180 	va_end(ap);
181 
182 	LIST_FOREACH(input, &inputs, link) {
183 		switch (input->type) {
184 
185 		  case INPUT_FILE:
186 			syslog(LOG_ERR, "  in file %s line %u",
187 			    input->input_filename, input->input_lno);
188 			break;
189 
190 		  case INPUT_STRING:
191 			syslog(LOG_ERR, "  in macro %s pos %td",
192 			    input->input_macro,
193 			    input->input_ptr - input->input_str);
194 			break;
195 		}
196 	}
197 	ERR();
198 }
199 
200 /*
201  * Open a file for input
202  */
203 static int
204 input_open_file(const char *fname, int sysdir)
205 {
206 	struct input *input;
207 	FILE *fp;
208 	char path[PATH_MAX + 1];
209 	const char *col;
210 	const char *ptr;
211 
212 	if (sysdir) {
213 		ptr = syspath;
214 		fp = NULL;
215 		while (*ptr != '\0') {
216 			if ((col = strchr(ptr, ':')) == NULL) {
217 				snprintf(path, sizeof(path), "%s/%s",
218 				    ptr, fname);
219 				col = ptr + strlen(ptr) - 1;
220 			} else if (col == ptr)
221 				snprintf(path, sizeof(path), "./%s", fname);
222 			else
223 				snprintf(path, sizeof(path), "%.*s/%s",
224 				    (int)(col - ptr), ptr, fname);
225 			if ((fp = fopen(path, "r")) != NULL)
226 				break;
227 			ptr = col + 1;
228 		}
229 	} else
230 		fp = fopen(fname, "r");
231 
232 	if (fp == NULL)
233 		report("%s: %m", fname);
234 
235 	if ((input = malloc(sizeof(*input))) == NULL) {
236 		fclose(fp);
237 		return (-1);
238 	}
239 	if ((input->input_filename = malloc(strlen(fname) + 1)) == NULL) {
240 		fclose(fp);
241 		free(input);
242 		return (-1);
243 	}
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);
249 	return (0);
250 }
251 
252 /*
253  * Make a macro the next input
254  */
255 static void
256 input_open_macro(struct macro *m)
257 {
258 	struct input *input;
259 
260 	if ((input = malloc(sizeof(*input))) == NULL)
261 		report("%m");
262 	input->type = INPUT_STRING;
263 	input->input_macro = m->name;
264 	if ((input->input_str = malloc(m->length)) == NULL) {
265 		free(input);
266 		report("%m");
267 	}
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);
272 }
273 
274 /*
275  * Close top input source
276  */
277 static void
278 input_close(void)
279 {
280 	struct input *input;
281 
282 	if ((input = LIST_FIRST(&inputs)) == NULL)
283 		abort();
284 	switch (input->type) {
285 
286 	  case INPUT_FILE:
287 		fclose(input->input_fp);
288 		free(input->input_filename);
289 		break;
290 
291 	  case INPUT_STRING:
292 		free(input->input_str);
293 		break;
294 	}
295 	LIST_REMOVE(input, link);
296 	free(input);
297 }
298 
299 /*
300  * Close all inputs
301  */
302 static void
303 input_close_all(void)
304 {
305 	while (!LIST_EMPTY(&inputs))
306 		input_close();
307 }
308 
309 /*
310  * Push back one character
311  */
312 static void
313 input_ungetc(int c)
314 {
315 	if (c == EOF)
316 		report("pushing EOF");
317 	if (input_push == 2)
318 		report("pushing third char");
319 	input_buf[input_push++] = c;
320 }
321 
322 
323 /*
324  * Return next character from the input without preprocessing.
325  */
326 static int
327 input_getc_raw(void)
328 {
329 	int c;
330 	struct input *input;
331 
332 	if (input_push != 0) {
333 		c = input_buf[--input_push];
334 		goto ok;
335 	}
336 	while ((input = LIST_FIRST(&inputs)) != NULL) {
337 		switch (input->type) {
338 
339 		  case INPUT_FILE:
340 			if ((c = getc(input->input_fp)) == EOF) {
341 				if (ferror(input->input_fp))
342 					report("read error: %m");
343 				input_close();
344 				break;
345 			}
346 			if (c == '\n')
347 				input->input_lno++;
348 			goto ok;
349 
350 		  case INPUT_STRING:
351 			if (input->input_left-- == 0) {
352 				input_close();
353 				break;
354 			}
355 			c = *input->input_ptr++;
356 			goto ok;
357 		}
358 	}
359 # ifdef DEBUGGING
360 	fprintf(stderr, "EOF");
361 # endif
362 	return (EOF);
363 
364   ok:
365 # ifdef DEBUGGING
366 	if (!isascii(c) || !isprint(c))
367 		fprintf(stderr, "'%#2x'", c);
368 	else
369 		fprintf(stderr, "'%c'", c);
370 # endif
371 	return (c);
372 }
373 
374 /*
375  * Get character with and \\n -> processing.
376  */
377 static int
378 input_getc_plain(void)
379 {
380 	int c;
381 
382   again:
383 	if ((c = input_getc_raw()) == '\\') {
384 		if ((c = input_getc_raw()) == '\n')
385 			goto again;
386 		if (c != EOF)
387 			input_ungetc(c);
388 		return ('\\');
389 	}
390 	return (c);
391 }
392 
393 /*
394  * Get next character with substitution of macros
395  */
396 static int
397 input_getc(void)
398 {
399 	int c;
400 	struct macro *m;
401 	char	name[_POSIX2_LINE_MAX];
402 	size_t	namelen;
403 
404   again:
405 	if ((c = input_getc_plain()) != '$')
406 		return (c);
407 
408 	if ((c = input_getc()) == EOF)
409 		report("unexpected EOF");
410 	if (c != '(')
411 		report("expecting '(' after '$'");
412 
413 	namelen = 0;
414 	while ((c = input_getc()) != EOF && c != ')') {
415 		if (isalpha(c) || c == '_' || (namelen != 0 && isdigit(c)))
416 			name[namelen++] = c;
417 		else
418 			goto badchar;
419 	}
420 	if (c == EOF)
421 		report("unexpected EOF");
422 	name[namelen++] = '\0';
423 
424 	LIST_FOREACH(m, &macros, link)
425 		if (strcmp(m->name, name) == 0)
426 			break;
427 	if (m == NULL)
428 		report("undefined macro '%s'", name);
429 
430 	input_open_macro(m);
431 	goto again;
432 
433   badchar:
434 	if (!isascii(c) || !isprint(c))
435 		report("unexpected character %#2x", (u_int)c);
436 	else
437 		report("bad character '%c'", c);
438 }
439 
440 
441 static void
442 input_getnum(u_int base, u_int flen)
443 {
444 	int c;
445 	u_int cnt;
446 
447 	cnt = 0;
448 	numval = 0;
449 	while (flen == 0 || cnt < flen) {
450 		if ((c = input_getc()) == EOF) {
451 			if (cnt == 0)
452 				report("bad number");
453 			return;
454 		}
455 		if (isdigit(c)) {
456 			if (base == 8 && (c == '8' || c == '9')) {
457 				input_ungetc(c);
458 				if (cnt == 0)
459 					report("bad number");
460 				return;
461 			}
462 			numval = numval * base + (c - '0');
463 		} else if (base == 16 && isxdigit(c)) {
464 			if (islower(c))
465 				numval = numval * base + (c - 'a' + 10);
466 			else
467 				numval = numval * base + (c - 'A' + 10);
468 		} else {
469 			input_ungetc(c);
470 			if (cnt == 0)
471 				report("bad number");
472 			return;
473 		}
474 		cnt++;
475 	}
476 }
477 
478 static int
479 # ifdef DEBUGGING
480 _gettoken(void)
481 # else
482 gettoken(void)
483 # endif
484 {
485 	int c;
486 	char *end;
487 	static const char esc[] = "abfnrtv";
488 	static const char chr[] = "\a\b\f\n\r\t\v";
489 
490 	/*
491 	 * Skip any whitespace before the next token
492 	 */
493 	while ((c = input_getc()) != EOF) {
494 		if (!isspace(c) || c == '\n')
495 			break;
496 	}
497 	if (c == EOF)
498 		return (token = TOK_EOF);
499 	if (!isascii(c))
500 		goto badchar;
501 
502 	/*
503 	 * Skip comments
504 	 */
505 	if (c == '#') {
506 		while ((c = input_getc_plain()) != EOF) {
507 			if (c == '\n')
508 				return (token = TOK_EOL);
509 		}
510 		goto badeof;
511 	}
512 
513 	/*
514 	 * Single character tokens
515 	 */
516 	if (c == '\n')
517 		return (token = TOK_EOL);
518 	if (c == '.' || c == '%' || c == '=' || c == '<' || c == '>')
519 		return (token = c);
520 	if (c == ':') {
521 		if ((c = input_getc()) == '=')
522 			return (token = TOK_ASSIGN);
523 		input_ungetc(c);
524 		return (token = ':');
525 	}
526 	if (c == '?') {
527 		if ((c = input_getc()) == '=')
528 			return (token = TOK_QASSIGN);
529 		input_ungetc(c);
530 		goto badchar;
531 	}
532 
533 	/*
534 	 * Sort out numbers
535 	 */
536 	if (isdigit(c)) {
537 		if (c == '0') {
538 			if ((c = input_getc()) == 'x' || c == 'X') {
539 				input_getnum(16, 0);
540 			} else if (isdigit(c)) {
541 				input_ungetc(c);
542 				input_getnum(8, 0);
543 			} else {
544 				if (c != EOF)
545 					input_ungetc(c);
546 				numval = 0;
547 				c = 1;
548 			}
549 		} else {
550 			input_ungetc(c);
551 			input_getnum(10, 0);
552 		}
553 		return (token = TOK_NUM);
554 	}
555 
556 	/*
557 	 * Must be a string then
558 	 */
559 	strvallen = 0;
560 
561 # define GETC(C) do {							\
562 	if ((c = input_getc()) == EOF)					\
563 		goto badeof;						\
564 	if (!isascii(c) || (!isprint(c) && c != '\t')) 			\
565 		goto badchar;						\
566 } while(0)
567 
568 	if (c == '"') {
569 		for(;;) {
570 			GETC(c);
571 			if (c == '"') {
572 				strval[strvallen] = '\0';
573 				break;
574 			}
575 			if (c != '\\') {
576 				strval[strvallen++] = c;
577 				continue;
578 			}
579 			GETC(c);
580 			if ((end = strchr(esc, c)) != NULL) {
581 				strval[strvallen++] = chr[end - esc];
582 				continue;
583 			}
584 			if (c == 'x') {
585 				input_getnum(16, 2);
586 				c = numval;
587 			} else if (c >= '0' && c <= '7') {
588 				input_ungetc(c);
589 				input_getnum(8, 3);
590 				c = numval;
591 			}
592 			strval[strvallen++] = c;
593 		}
594 # undef GETC
595 
596 	} else if (c == '[') {
597 		/*
598 		 * Skip leading space
599 		 */
600 		while ((c = input_getc()) != EOF && isspace(c))
601 			;
602 		if (c == EOF)
603 			goto badeof;
604 		while (c != ']' && !isspace(c)) {
605 			if (!isalnum(c) && c != '.' && c != '-')
606 				goto badchar;
607 			strval[strvallen++] = c;
608 			if ((c = input_getc()) == EOF)
609 				goto badeof;
610 		}
611 		while (c != ']' && isspace(c)) {
612 			if ((c = input_getc()) == EOF)
613 				goto badeof;
614 		}
615 		if (c != ']')
616 			goto badchar;
617 		strval[strvallen] = '\0';
618 		return (token = TOK_HOST);
619 
620 	} else if (!isalpha(c) && c != '_') {
621 		goto badchar;
622 
623 	} else {
624 		for (;;) {
625 			strval[strvallen++] = c;
626 			if ((c = input_getc()) == EOF)
627 				goto badeof;
628 			if (!isalnum(c) && c != '_' && c != '-') {
629 				input_ungetc(c);
630 				strval[strvallen] = '\0';
631 				break;
632 			}
633 		}
634 	}
635 
636 	return (token = TOK_STR);
637 
638   badeof:
639 	report("unexpected EOF");
640 
641   badchar:
642 	if (!isascii(c) || !isprint(c))
643 		report("unexpected character %#2x", (u_int)c);
644 	else
645 		report("bad character '%c'", c);
646 }
647 
648 # ifdef DEBUGGING
649 static int
650 gettoken()
651 {
652 	_gettoken();
653 	if (isascii(token) && isprint(token))
654 		printf("(%c)", token);
655 	else {
656 		switch (token) {
657 
658 		  case TOK_EOF:
659 			printf("(EOF)");
660 			break;
661 		  case TOK_EOL:
662 			printf("(EOL)");
663 			break;
664 		  case TOK_NUM:
665 			printf("(NUM %llu)", numval);
666 			break;
667 		  case TOK_STR:
668 			printf("(STR %.*s)", (int)strvallen, strval);
669 			break;
670 		  case TOK_HOST:
671 			printf("(HOST %s)", strval);
672 			break;
673 		  default:
674 			printf("(%#2x)", token);
675 			break;
676 		}
677 	}
678 	return (token);
679 }
680 #endif
681 
682 
683 /*
684  * Try to execute the assignment.
685  */
686 static void
687 handle_assignment(const struct snmp_node *node, struct asn_oid *vindex,
688     const struct snmp_value *value)
689 {
690 	u_int i;
691 	int err;
692 	struct assign *tp;
693 	char nodename[100];
694 
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");
700 		vindex->len = 1;
701 		vindex->subs[0] = 0;
702 	} else {
703 		/* resulting oid must not be too long */
704 		if (node->oid.len + vindex->len > ASN_MAXOIDLEN)
705 			report("resulting OID too long");
706 	}
707 
708 	/*
709 	 * Get the next assignment entry for the transaction.
710 	 */
711 	if ((tp = malloc(sizeof(*tp))) == NULL)
712 		report("%m");
713 
714 	tp->value = *value;
715 	tp->node_name = node->name;
716 
717 	/*
718 	 * Build the OID
719 	 */
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];
723 
724 	/*
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.
729 	 */
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,
734 	    SNMP_OP_SET);
735 	if (err != 0) {
736 		free(tp);
737 		report("assignment to %s.%s returns %d", nodename,
738 		    asn_oid2str(vindex), err);
739 	}
740 
741 	TAILQ_INSERT_TAIL(&assigns, tp, link);
742 }
743 
744 
745 /*
746  * Parse the section statement
747  */
748 static void
749 parse_section(const struct lmodule *mod)
750 {
751 	if (token != TOK_STR)
752 		report("expecting section name");
753 
754 	if (strcmp(strval, "snmpd") == 0) {
755 		if (mod != NULL)
756 			/* loading a module - ignore common stuff */
757 			ignore = 1;
758 		else
759 			/* global configuration - don't ignore */
760 			ignore = 0;
761 	} else {
762 		if (mod == NULL) {
763 			/* global configuration - ignore module stuff */
764 			ignore = 1;
765 		} else {
766 			/* loading module - check if it's our section */
767 			ignore = (strcmp(strval, mod->section) != 0);
768 		}
769 	}
770 	gettoken();
771 }
772 
773 /*
774  * Convert a hostname to four u_chars
775  */
776 static void
777 gethost(const char *host, u_char *ip)
778 {
779 	struct addrinfo hints, *res;
780 	int error;
781 	struct sockaddr_in *sain;
782 
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);
789 	if (error != 0)
790 		report("%s: %s", host, gai_strerror(error));
791 	if (res == NULL)
792 		report("%s: unknown hostname", host);
793 
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;
800 
801 	freeaddrinfo(res);
802 }
803 
804 /*
805  * Parse the left hand side of a config line.
806  */
807 static const struct snmp_node *
808 parse_oid(const char *varname, struct asn_oid *oid)
809 {
810 	struct snmp_node *node;
811 	u_int i;
812 	u_char ip[4];
813 
814 	for (node = tree; node < &tree[tree_size]; node++)
815 		if (strcmp(varname, node->name) == 0)
816 			break;
817 	if (node == &tree[tree_size])
818 		node = NULL;
819 
820 	oid->len = 0;
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;
828 
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];
835 
836 		} else if (token == TOK_HOST) {
837 			gethost(strval, ip);
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];
842 
843 		} else
844 			report("bad token in index");
845 		gettoken();
846 	}
847 
848 	return (node);
849 }
850 
851 /*
852  * Parse the value for an assignment.
853  */
854 static void
855 parse_syntax_null(struct snmp_value *value __unused)
856 {
857 	if (token != TOK_EOL)
858 		report("bad NULL syntax");
859 }
860 
861 static void
862 parse_syntax_integer(struct snmp_value *value)
863 {
864 	if (token != TOK_NUM)
865 		report("bad INTEGER syntax");
866 	if (numval > 0x7fffffff)
867 		report("INTEGER too large %"QUADFMT, numval);
868 
869 	value->v.integer = numval;
870 	gettoken();
871 }
872 
873 static void
874 parse_syntax_counter64(struct snmp_value *value)
875 {
876 	if (token != TOK_NUM)
877 		report("bad COUNTER64 syntax");
878 
879 	value->v.counter64 = numval;
880 	gettoken();
881 }
882 
883 static void
884 parse_syntax_octetstring(struct snmp_value *value)
885 {
886 	u_long alloc;
887 	u_char *noct;
888 
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);
893 		gettoken();
894 		return;
895 	}
896 
897 	/* XX:XX:XX syntax */
898 	value->v.octetstring.octets = NULL;
899 	value->v.octetstring.len = 0;
900 
901 	if (token != TOK_NUM)
902 		/* empty string is allowed */
903 		return;
904 
905 	if (ERRPUSH()) {
906 		free(value->v.octetstring.octets);
907 		ERRNEXT();
908 	}
909 
910 	alloc = 0;
911 	for (;;) {
912 		if (token != TOK_NUM)
913 			report("bad OCTETSTRING syntax");
914 		if (numval > 0xff)
915 			report("byte value too large");
916 		if (alloc == value->v.octetstring.len) {
917 			alloc += 100;
918 			noct = realloc(value->v.octetstring.octets, alloc);
919 			if (noct == NULL)
920 				report("%m");
921 			value->v.octetstring.octets = noct;
922 		}
923 		value->v.octetstring.octets[value->v.octetstring.len++]
924 		    = numval;
925 		if (gettoken() != ':')
926 			break;
927 		gettoken();
928 	}
929 	ERRPOP();
930 }
931 
932 static void
933 parse_syntax_oid(struct snmp_value *value)
934 {
935 	value->v.oid.len = 0;
936 
937 	if (token != TOK_NUM)
938 		return;
939 
940 	for (;;) {
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() != '.')
949 			break;
950 		gettoken();
951 	}
952 }
953 
954 static void
955 parse_syntax_ipaddress(struct snmp_value *value)
956 {
957 	int i;
958 	u_char ip[4];
959 
960 	if (token == TOK_NUM) {
961 		/* numerical address */
962 		i = 0;
963 		for (;;) {
964 			if (numval >= 256)
965 				report("ip address part too large");
966 			value->v.ipaddress[i++] = numval;
967 			if (i == 4)
968 				break;
969 			if (gettoken() != '.')
970 				report("expecting '.' in ip address");
971 		}
972 		gettoken();
973 
974 	} else if (token == TOK_HOST) {
975 		/* host name */
976 		gethost(strval, ip);
977 		for (i = 0; i < 4; i++)
978 			value->v.ipaddress[i] = ip[i];
979 		gettoken();
980 
981 	} else
982 		report("bad ip address syntax");
983 }
984 
985 static void
986 parse_syntax_uint32(struct snmp_value *value)
987 {
988 
989 	if (token != TOK_NUM)
990 		report("bad number syntax");
991 	if (numval > 0xffffffff)
992 		report("number too large");
993 	value->v.uint32 = numval;
994 	gettoken();
995 }
996 
997 /*
998  * Parse an assignement line
999  */
1000 static void
1001 parse_assign(const char *varname)
1002 {
1003 	struct snmp_value value;
1004 	struct asn_oid vindex;
1005 	const struct snmp_node *node;
1006 
1007 	node = parse_oid(varname, &vindex);
1008 	if (token != '=')
1009 		report("'=' expected");
1010 	gettoken();
1011 
1012 	if (ignore) {
1013 		/* skip rest of line */
1014 		while (token != TOK_EOL && token != TOK_EOF)
1015 			gettoken();
1016 		return;
1017 	}
1018 	if (node == NULL)
1019 		report("unknown variable");
1020 
1021 	switch (value.syntax = node->syntax) {
1022 
1023 	  case SNMP_SYNTAX_NULL:
1024 		parse_syntax_null(&value);
1025 		break;
1026 
1027 	  case SNMP_SYNTAX_INTEGER:
1028 		parse_syntax_integer(&value);
1029 		break;
1030 
1031 	  case SNMP_SYNTAX_COUNTER64:
1032 		parse_syntax_counter64(&value);
1033 		break;
1034 
1035 	  case SNMP_SYNTAX_OCTETSTRING:
1036 		parse_syntax_octetstring(&value);
1037 		break;
1038 
1039 	  case SNMP_SYNTAX_OID:
1040 		parse_syntax_oid(&value);
1041 		break;
1042 
1043 	  case SNMP_SYNTAX_IPADDRESS:
1044 		parse_syntax_ipaddress(&value);
1045 		break;
1046 
1047 	  case SNMP_SYNTAX_COUNTER:
1048 	  case SNMP_SYNTAX_GAUGE:
1049 	  case SNMP_SYNTAX_TIMETICKS:
1050 		parse_syntax_uint32(&value);
1051 		break;
1052 
1053 	  case SNMP_SYNTAX_NOSUCHOBJECT:
1054 	  case SNMP_SYNTAX_NOSUCHINSTANCE:
1055 	  case SNMP_SYNTAX_ENDOFMIBVIEW:
1056 		abort();
1057 	}
1058 
1059 	if (ERRPUSH()) {
1060 		snmp_value_free(&value);
1061 		ERRNEXT();
1062 	}
1063 
1064 	handle_assignment(node, &vindex, &value);
1065 
1066 	ERRPOP();
1067 }
1068 
1069 /*
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.
1074  */
1075 static void
1076 parse_define(const char *varname)
1077 {
1078 	char *volatile string;
1079 	char *new;
1080 	volatile size_t alloc, length;
1081 	int c;
1082 	struct macro *m;
1083 	int t = token;
1084 
1085 	alloc = 100;
1086 	length = 0;
1087 	if ((string = malloc(alloc)) == NULL)
1088 		report("%m");
1089 
1090 	if (ERRPUSH()) {
1091 		free(string);
1092 		ERRNEXT();
1093 	}
1094 
1095 	while ((c = input_getc_plain()) != EOF) {
1096 		if (c == '\n' || !isspace(c))
1097 			break;
1098 	}
1099 
1100 	while (c != EOF && c != '#' && c != '\n') {
1101 		if (alloc == length) {
1102 			alloc *= 2;
1103 			if ((new = realloc(string, alloc)) == NULL)
1104 				report("%m");
1105 			string = new;
1106 		}
1107 		string[length++] = c;
1108 		c = input_getc_plain();
1109 	}
1110 	if (c == '#') {
1111 		while ((c = input_getc_plain()) != EOF && c != '\n')
1112 			;
1113 	}
1114 	if (c == EOF)
1115 		report("EOF in macro definition");
1116 
1117 	LIST_FOREACH(m, &macros, link)
1118 		if (strcmp(m->name, varname) == 0)
1119 			break;
1120 
1121 	if (m == NULL) {
1122 		if ((m = malloc(sizeof(*m))) == NULL)
1123 			report("%m");
1124 		if ((m->name = malloc(strlen(varname) + 1)) == NULL) {
1125 			free(m);
1126 			report("%m");
1127 		}
1128 		strcpy(m->name, varname);
1129 		m->perm = 0;
1130 		LIST_INSERT_HEAD(&macros, m, link);
1131 
1132 		m->value = string;
1133 		m->length = length;
1134 	} else {
1135 		if (t == TOK_ASSIGN) {
1136 			free(m->value);
1137 			m->value = string;
1138 			m->length = length;
1139 		}
1140 	}
1141 
1142 	token = TOK_EOL;
1143 
1144 	ERRPOP();
1145 }
1146 
1147 /*
1148  * Free all macros
1149  */
1150 static void
1151 macro_free_all(void)
1152 {
1153 	static struct macro *m, *m1;
1154 
1155 	m = LIST_FIRST(&macros);
1156 	while (m != NULL) {
1157 		m1 = LIST_NEXT(m, link);
1158 		if (!m->perm) {
1159 			free(m->name);
1160 			free(m->value);
1161 			LIST_REMOVE(m, link);
1162 			free(m);
1163 		}
1164 		m = m1;
1165 	}
1166 }
1167 
1168 /*
1169  * Parse an include directive and switch to the new file
1170  */
1171 static void
1172 parse_include(void)
1173 {
1174 	int sysdir = 0;
1175 	char fname[_POSIX2_LINE_MAX];
1176 
1177 	if (gettoken() == '<') {
1178 		sysdir = 1;
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");
1183 
1184 	strcpy(fname, strval);
1185 	if (sysdir && gettoken() != '>')
1186 		report("expecting '>'");
1187 	gettoken();
1188 	if (input_open_file(fname, sysdir) == -1)
1189 		report("%s: %m", fname);
1190 }
1191 
1192 /*
1193  * Parse the configuration file
1194  */
1195 static void
1196 parse_file(const struct lmodule *mod)
1197 {
1198 	char varname[_POSIX2_LINE_MAX];
1199 
1200 	while (gettoken() != TOK_EOF) {
1201 		if (token == TOK_EOL)
1202 			/* empty line */
1203 			continue;
1204 		if (token == '%') {
1205 			gettoken();
1206 			parse_section(mod);
1207 		} else if (token == '.') {
1208 			if (gettoken() != TOK_STR)
1209 				report("keyword expected after '.'");
1210 			if (strcmp(strval, "include") == 0)
1211 				parse_include();
1212 			else
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);
1218 			else
1219 				parse_assign(varname);
1220 		}
1221 		if (token != TOK_EOL)
1222 			report("eol expected");
1223 	}
1224 }
1225 
1226 /*
1227  * Do rollback on errors
1228  */
1229 static void
1230 do_rollback(void)
1231 {
1232 	struct assign *tp;
1233 	struct snmp_node *node;
1234 
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,
1242 				    SNMP_OP_ROLLBACK);
1243 				break;
1244 			}
1245 		if (node == &tree[tree_size])
1246 			syslog(LOG_ERR, "failed to find node for "
1247 			    "rollback");
1248 		snmp_value_free(&tp->value);
1249 		free(tp);
1250 	}
1251 }
1252 
1253 /*
1254  * Do commit
1255  */
1256 static void
1257 do_commit(void)
1258 {
1259 	struct assign *tp;
1260 	struct snmp_node *node;
1261 
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);
1269 				break;
1270 			}
1271 		if (node == &tree[tree_size])
1272 			syslog(LOG_ERR, "failed to find node for commit");
1273 		snmp_value_free(&tp->value);
1274 		free(tp);
1275 	}
1276 }
1277 
1278 /*
1279  * Read the configuration file. Handle the entire file as one transaction.
1280  *
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.
1283  */
1284 int
1285 read_config(const char *fname, struct lmodule *lodmod)
1286 {
1287 	int err;
1288 	char objbuf[ASN_OIDSTRLEN];
1289 	char idxbuf[ASN_OIDSTRLEN];
1290 
1291 	ignore = 0;
1292 
1293 	input_push = 0;
1294 
1295 	if (ERRPUSH())
1296 		return (-1);
1297 	if (input_open_file(fname, 0) == -1) {
1298 		syslog(LOG_ERR, "%s: %m", fname);
1299 		return (-1);
1300 	}
1301 	ERRPOP();
1302 	community = COMM_INITIALIZE;
1303 
1304 	if ((snmp_ctx = snmp_init_context()) == NULL) {
1305 		input_close_all();
1306 		syslog(LOG_ERR, "%m");
1307 		return (-1);
1308 	}
1309 
1310 	if (ERRPUSH()) {
1311 		do_rollback();
1312 		input_close_all();
1313 		macro_free_all();
1314 		free(snmp_ctx);
1315 		return (-1);
1316 	}
1317 	parse_file(lodmod);
1318 	ERRPOP();
1319 
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);
1325 		do_rollback();
1326 		input_close_all();
1327 		macro_free_all();
1328 		free(snmp_ctx);
1329 		return (-1);
1330 	}
1331 
1332 	do_commit();
1333 	snmp_dep_finish(snmp_ctx);
1334 
1335 	macro_free_all();
1336 
1337 	free(snmp_ctx);
1338 
1339 	return (0);
1340 }
1341 
1342 /*
1343  * Define a permanent macro
1344  */
1345 int
1346 define_macro(const char *name, const char *value)
1347 {
1348 	struct macro *m;
1349 
1350 	if ((m = malloc(sizeof(*m))) == NULL)
1351 		return (-1);
1352 	if ((m->name = malloc(strlen(name) + 1)) == NULL) {
1353 		free(m);
1354 		return (-1);
1355 	}
1356 	strcpy(m->name, name);
1357 	if ((m->value = malloc(strlen(value) + 1)) == NULL) {
1358 		free(m->name);
1359 		free(m);
1360 		return (-1);
1361 	}
1362 	strcpy(m->value, value);
1363 	m->length = strlen(value);
1364 	m->perm = 1;
1365 	LIST_INSERT_HEAD(&macros, m, link);
1366 	return (0);
1367 }
1368