config.c revision 133594
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.22 2004/08/12 17:09:49 brandt 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 */
78enum input_type {
79	INPUT_FILE	= 1,
80	INPUT_STRING
81};
82struct 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};
99static 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
109static int input_push;
110static 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 */
118struct assign {
119	struct snmp_value value;
120	struct snmp_scratch scratch;
121	const char *node_name;
122
123	TAILQ_ENTRY(assign) link;
124};
125static TAILQ_HEAD(assigns, assign) assigns = TAILQ_HEAD_INITIALIZER(assigns);
126
127
128static struct snmp_context *snmp_ctx;
129
130struct macro {
131	char	*name;
132	char	*value;
133	size_t	length;
134	LIST_ENTRY(macro) link;
135	int	perm;
136};
137static LIST_HEAD(, macro) macros = LIST_HEAD_INITIALIZER(&macros);
138
139enum {
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 */
150static uint64_t	numval;
151static char	strval[_POSIX2_LINE_MAX];
152static size_t	strvallen;
153static int	token;
154
155/* error return */
156static jmp_buf	errjmp[4];
157static 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 */
165static int ignore;
166
167/*
168 * Report an error and jump to the error label
169 */
170static void report(const char *fmt, ...) __dead2 __printflike(1, 2);
171
172static void
173report(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 */
203static int
204input_open_file(const char *fname, int sysdir)
205{
206	struct input *input;
207	FILE *fp;
208	char path[PATH_MAX + 1];
209	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			else if (col == ptr)
220				snprintf(path, sizeof(path), "./%s", fname);
221			else
222				snprintf(path, sizeof(path), "%.*s/%s",
223				    (int)(col - ptr), ptr, fname);
224			if ((fp = fopen(path, "r")) != NULL)
225				break;
226			ptr = col + 1;
227		}
228	} else
229		fp = fopen(fname, "r");
230
231	if (fp == NULL)
232		report("%s: %m", fname);
233
234	if ((input = malloc(sizeof(*input))) == NULL) {
235		fclose(fp);
236		return (-1);
237	}
238	if ((input->input_filename = malloc(strlen(fname) + 1)) == NULL) {
239		fclose(fp);
240		free(input);
241		return (-1);
242	}
243	strcpy(input->input_filename, fname);
244	input->input_fp = fp;
245	input->input_lno = 1;
246	input->type = INPUT_FILE;
247	LIST_INSERT_HEAD(&inputs, input, link);
248	return (0);
249}
250
251/*
252 * Make a macro the next input
253 */
254static void
255input_open_macro(struct macro *m)
256{
257	struct input *input;
258
259	if ((input = malloc(sizeof(*input))) == NULL)
260		report("%m");
261	input->type = INPUT_STRING;
262	input->input_macro = m->name;
263	if ((input->input_str = malloc(m->length)) == NULL) {
264		free(input);
265		report("%m");
266	}
267	memcpy(input->input_str, m->value, m->length);
268	input->input_ptr = input->input_str;
269	input->input_left = m->length;
270	LIST_INSERT_HEAD(&inputs, input, link);
271}
272
273/*
274 * Close top input source
275 */
276static void
277input_close(void)
278{
279	struct input *input;
280
281	if ((input = LIST_FIRST(&inputs)) == NULL)
282		abort();
283	switch (input->type) {
284
285	  case INPUT_FILE:
286		fclose(input->input_fp);
287		free(input->input_filename);
288		break;
289
290	  case INPUT_STRING:
291		free(input->input_str);
292		break;
293	}
294	LIST_REMOVE(input, link);
295	free(input);
296}
297
298/*
299 * Close all inputs
300 */
301static void
302input_close_all(void)
303{
304	while (!LIST_EMPTY(&inputs))
305		input_close();
306}
307
308/*
309 * Push back one character
310 */
311static void
312input_ungetc(int c)
313{
314	if (c == EOF)
315		report("pushing EOF");
316	if (input_push == 2)
317		report("pushing third char");
318	input_buf[input_push++] = c;
319}
320
321
322/*
323 * Return next character from the input without preprocessing.
324 */
325static int
326input_getc_raw(void)
327{
328	int c;
329	struct input *input;
330
331	if (input_push != 0) {
332		c = input_buf[--input_push];
333		goto ok;
334	}
335	while ((input = LIST_FIRST(&inputs)) != NULL) {
336		switch (input->type) {
337
338		  case INPUT_FILE:
339			if ((c = getc(input->input_fp)) == EOF) {
340				if (ferror(input->input_fp))
341					report("read error: %m");
342				input_close();
343				break;
344			}
345			if (c == '\n')
346				input->input_lno++;
347			goto ok;
348
349		  case INPUT_STRING:
350			if (input->input_left-- == 0) {
351				input_close();
352				break;
353			}
354			c = *input->input_ptr++;
355			goto ok;
356		}
357	}
358# ifdef DEBUGGING
359	fprintf(stderr, "EOF");
360# endif
361	return (EOF);
362
363  ok:
364# ifdef DEBUGGING
365	if (!isascii(c) || !isprint(c))
366		fprintf(stderr, "'%#2x'", c);
367	else
368		fprintf(stderr, "'%c'", c);
369# endif
370	return (c);
371}
372
373/*
374 * Get character with and \\n -> processing.
375 */
376static int
377input_getc_plain(void)
378{
379	int c;
380
381  again:
382	if ((c = input_getc_raw()) == '\\') {
383		if ((c = input_getc_raw()) == '\n')
384			goto again;
385		if (c != EOF)
386			input_ungetc(c);
387		return ('\\');
388	}
389	return (c);
390}
391
392/*
393 * Get next character with substitution of macros
394 */
395static int
396input_getc(void)
397{
398	int c;
399	struct macro *m;
400	char	name[_POSIX2_LINE_MAX];
401	size_t	namelen;
402
403  again:
404	if ((c = input_getc_plain()) != '$')
405		return (c);
406
407	if ((c = input_getc()) == EOF)
408		report("unexpected EOF");
409	if (c != '(')
410		report("expecting '(' after '$'");
411
412	namelen = 0;
413	while ((c = input_getc()) != EOF && c != ')') {
414		if (isalpha(c) || c == '_' || (namelen != 0 && isdigit(c)))
415			name[namelen++] = c;
416		else
417			goto badchar;
418	}
419	if (c == EOF)
420		report("unexpected EOF");
421	name[namelen++] = '\0';
422
423	LIST_FOREACH(m, &macros, link)
424		if (strcmp(m->name, name) == 0)
425			break;
426	if (m == NULL)
427		report("undefined macro '%s'", name);
428
429	input_open_macro(m);
430	goto again;
431
432  badchar:
433	if (!isascii(c) || !isprint(c))
434		report("unexpected character %#2x", (u_int)c);
435	else
436		report("bad character '%c'", c);
437}
438
439
440static void
441input_getnum(u_int base, u_int flen)
442{
443	int c;
444	u_int cnt;
445
446	cnt = 0;
447	numval = 0;
448	while (flen == 0 || cnt < flen) {
449		if ((c = input_getc()) == EOF) {
450			if (cnt == 0)
451				report("bad number");
452			return;
453		}
454		if (isdigit(c)) {
455			if (base == 8 && (c == '8' || c == '9')) {
456				input_ungetc(c);
457				if (cnt == 0)
458					report("bad number");
459				return;
460			}
461			numval = numval * base + (c - '0');
462		} else if (base == 16 && isxdigit(c)) {
463			if (islower(c))
464				numval = numval * base + (c - 'a' + 10);
465			else
466				numval = numval * base + (c - 'A' + 10);
467		} else {
468			input_ungetc(c);
469			if (cnt == 0)
470				report("bad number");
471			return;
472		}
473		cnt++;
474	}
475}
476
477static int
478# ifdef DEBUGGING
479_gettoken(void)
480# else
481gettoken(void)
482# endif
483{
484	int c;
485	char *end;
486	static const char esc[] = "abfnrtv";
487	static const char chr[] = "\a\b\f\n\r\t\v";
488
489	/*
490	 * Skip any whitespace before the next token
491	 */
492	while ((c = input_getc()) != EOF) {
493		if (!isspace(c) || c == '\n')
494			break;
495	}
496	if (c == EOF)
497		return (token = TOK_EOF);
498	if (!isascii(c))
499		goto badchar;
500
501	/*
502	 * Skip comments
503	 */
504	if (c == '#') {
505		while ((c = input_getc_plain()) != EOF) {
506			if (c == '\n')
507				return (token = TOK_EOL);
508		}
509		goto badeof;
510	}
511
512	/*
513	 * Single character tokens
514	 */
515	if (c == '\n')
516		return (token = TOK_EOL);
517	if (c == '.' || c == '%' || c == '=' || c == '<' || c == '>')
518		return (token = c);
519	if (c == ':') {
520		if ((c = input_getc()) == '=')
521			return (token = TOK_ASSIGN);
522		input_ungetc(c);
523		return (token = ':');
524	}
525	if (c == '?') {
526		if ((c = input_getc()) == '=')
527			return (token = TOK_QASSIGN);
528		input_ungetc(c);
529		goto badchar;
530	}
531
532	/*
533	 * Sort out numbers
534	 */
535	if (isdigit(c)) {
536		if (c == '0') {
537			if ((c = input_getc()) == 'x' || c == 'X') {
538				input_getnum(16, 0);
539			} else if (isdigit(c)) {
540				input_ungetc(c);
541				input_getnum(8, 0);
542			} else {
543				if (c != EOF)
544					input_ungetc(c);
545				numval = 0;
546				c = 1;
547			}
548		} else {
549			input_ungetc(c);
550			input_getnum(10, 0);
551		}
552		return (token = TOK_NUM);
553	}
554
555	/*
556	 * Must be a string then
557	 */
558	strvallen = 0;
559
560# define GETC(C) do {							\
561	if ((c = input_getc()) == EOF)					\
562		goto badeof;						\
563	if (!isascii(c) || (!isprint(c) && c != '\t')) 			\
564		goto badchar;						\
565} while(0)
566
567	if (c == '"') {
568		for(;;) {
569			GETC(c);
570			if (c == '"') {
571				strval[strvallen] = '\0';
572				break;
573			}
574			if (c != '\\') {
575				strval[strvallen++] = c;
576				continue;
577			}
578			GETC(c);
579			if ((end = strchr(esc, c)) != NULL) {
580				strval[strvallen++] = chr[end - esc];
581				continue;
582			}
583			if (c == 'x') {
584				input_getnum(16, 2);
585				c = numval;
586			} else if (c >= '0' && c <= '7') {
587				input_ungetc(c);
588				input_getnum(8, 3);
589				c = numval;
590			}
591			strval[strvallen++] = c;
592		}
593# undef GETC
594
595	} else if (c == '[') {
596		/*
597		 * Skip leading space
598		 */
599		while ((c = input_getc()) != EOF && isspace(c))
600			;
601		if (c == EOF)
602			goto badeof;
603		while (c != ']' && !isspace(c)) {
604			if (!isalnum(c) && c != '.' && c != '-')
605				goto badchar;
606			strval[strvallen++] = c;
607			if ((c = input_getc()) == EOF)
608				goto badeof;
609		}
610		while (c != ']' && isspace(c)) {
611			if ((c = input_getc()) == EOF)
612				goto badeof;
613		}
614		if (c != ']')
615			goto badchar;
616		strval[strvallen] = '\0';
617		return (token = TOK_HOST);
618
619	} else if (!isalpha(c) && c != '_') {
620		goto badchar;
621
622	} else {
623		for (;;) {
624			strval[strvallen++] = c;
625			if ((c = input_getc()) == EOF)
626				goto badeof;
627			if (!isalnum(c) && c != '_' && c != '-') {
628				input_ungetc(c);
629				strval[strvallen] = '\0';
630				break;
631			}
632		}
633	}
634
635	return (token = TOK_STR);
636
637  badeof:
638	report("unexpected EOF");
639
640  badchar:
641	if (!isascii(c) || !isprint(c))
642		report("unexpected character %#2x", (u_int)c);
643	else
644		report("bad character '%c'", c);
645}
646
647# ifdef DEBUGGING
648static int
649gettoken()
650{
651	_gettoken();
652	if (isascii(token) && isprint(token))
653		printf("(%c)", token);
654	else {
655		switch (token) {
656
657		  case TOK_EOF:
658			printf("(EOF)");
659			break;
660		  case TOK_EOL:
661			printf("(EOL)");
662			break;
663		  case TOK_NUM:
664			printf("(NUM %llu)", numval);
665			break;
666		  case TOK_STR:
667			printf("(STR %.*s)", (int)strvallen, strval);
668			break;
669		  case TOK_HOST:
670			printf("(HOST %s)", strval);
671			break;
672		  default:
673			printf("(%#2x)", token);
674			break;
675		}
676	}
677	return (token);
678}
679#endif
680
681
682/*
683 * Try to execute the assignment.
684 */
685static void
686handle_assignment(const struct snmp_node *node, struct asn_oid *vindex,
687    const struct snmp_value *value)
688{
689	u_int i;
690	int err;
691	struct assign *tp;
692	char nodename[100];
693
694	if (node->type == SNMP_NODE_LEAF) {
695		/* index must be one single zero or no index at all */
696		if (vindex->len > 1 || (vindex->len == 1 &&
697		    vindex->subs[0] != 0))
698			report("bad index on leaf node");
699		vindex->len = 1;
700		vindex->subs[0] = 0;
701	} else {
702		/* resulting oid must not be too long */
703		if (node->oid.len + vindex->len > ASN_MAXOIDLEN)
704			report("resulting OID too long");
705	}
706
707	/*
708	 * Get the next assignment entry for the transaction.
709	 */
710	if ((tp = malloc(sizeof(*tp))) == NULL)
711		report("%m");
712
713	tp->value = *value;
714	tp->node_name = node->name;
715
716	/*
717	 * Build the OID
718	 */
719	tp->value.var = node->oid;
720	for (i = 0; i < vindex->len; i++)
721		tp->value.var.subs[tp->value.var.len++] = vindex->subs[i];
722
723	/*
724	 * Puzzle together the variables for the call and call the
725	 * set routine. The set routine may make our node pointer
726	 * invalid (if we happend to call the module loader) so
727	 * get a copy of the node name beforehands.
728	 */
729	snprintf(nodename, sizeof(nodename), "%s", node->name);
730	snmp_ctx->scratch = &tp->scratch;
731	snmp_ctx->var_index = 0;
732	err = (*node->op)(snmp_ctx, &tp->value, node->oid.len, node->index,
733	    SNMP_OP_SET);
734	if (err != 0) {
735		free(tp);
736		report("assignment to %s.%s returns %d", nodename,
737		    asn_oid2str(vindex), err);
738	}
739
740	TAILQ_INSERT_TAIL(&assigns, tp, link);
741}
742
743
744/*
745 * Parse the section statement
746 */
747static void
748parse_section(const struct lmodule *mod)
749{
750	if (token != TOK_STR)
751		report("expecting section name");
752
753	if (strcmp(strval, "snmpd") == 0) {
754		if (mod != NULL)
755			/* loading a module - ignore common stuff */
756			ignore = 1;
757		else
758			/* global configuration - don't ignore */
759			ignore = 0;
760	} else {
761		if (mod == NULL) {
762			/* global configuration - ignore module stuff */
763			ignore = 1;
764		} else {
765			/* loading module - check if it's our section */
766			ignore = (strcmp(strval, mod->section) != 0);
767		}
768	}
769	gettoken();
770}
771
772/*
773 * Convert a hostname to four u_chars
774 */
775static void
776gethost(const char *host, u_char *ip)
777{
778	struct addrinfo hints, *res;
779	int error;
780	struct sockaddr_in *sain;
781
782	memset(&hints, 0, sizeof(hints));
783	hints.ai_family = AF_INET;
784	hints.ai_socktype = SOCK_DGRAM;
785	hints.ai_protocol = IPPROTO_UDP;
786	hints.ai_flags = AI_PASSIVE;
787	error = getaddrinfo(host, NULL, &hints, &res);
788	if (error != 0)
789		report("%s: %s", host, gai_strerror(error));
790	if (res == NULL)
791		report("%s: unknown hostname", host);
792
793	sain = (struct sockaddr_in *)(void *)res->ai_addr;
794	sain->sin_addr.s_addr = ntohl(sain->sin_addr.s_addr);
795	ip[0] = sain->sin_addr.s_addr >> 24;
796	ip[1] = sain->sin_addr.s_addr >> 16;
797	ip[2] = sain->sin_addr.s_addr >>  8;
798	ip[3] = sain->sin_addr.s_addr >>  0;
799
800	freeaddrinfo(res);
801}
802
803/*
804 * Parse the left hand side of a config line.
805 */
806static const struct snmp_node *
807parse_oid(const char *varname, struct asn_oid *oid)
808{
809	struct snmp_node *node;
810	u_int i;
811	u_char ip[4];
812
813	for (node = tree; node < &tree[tree_size]; node++)
814		if (strcmp(varname, node->name) == 0)
815			break;
816	if (node == &tree[tree_size])
817		node = NULL;
818
819	oid->len = 0;
820	while (token == '.') {
821		if (gettoken() == TOK_NUM) {
822			if (numval > ASN_MAXID)
823				report("subid too large %#"PRIx64, numval);
824			if (oid->len == ASN_MAXOIDLEN)
825				report("index too long");
826			oid->subs[oid->len++] = numval;
827
828		} else if (token == TOK_STR) {
829			if (strvallen + oid->len + 1 > ASN_MAXOIDLEN)
830				report("oid too long");
831			oid->subs[oid->len++] = strvallen;
832			for (i = 0; i < strvallen; i++)
833				oid->subs[oid->len++] = strval[i];
834
835		} else if (token == TOK_HOST) {
836			gethost(strval, ip);
837			if (oid->len + 4 > ASN_MAXOIDLEN)
838				report("index too long");
839			for (i = 0; i < 4; i++)
840				oid->subs[oid->len++] = ip[i];
841
842		} else
843			report("bad token in index");
844		gettoken();
845	}
846
847	return (node);
848}
849
850/*
851 * Parse the value for an assignment.
852 */
853static void
854parse_syntax_null(struct snmp_value *value __unused)
855{
856	if (token != TOK_EOL)
857		report("bad NULL syntax");
858}
859
860static void
861parse_syntax_integer(struct snmp_value *value)
862{
863	if (token != TOK_NUM)
864		report("bad INTEGER syntax");
865	if (numval > 0x7fffffff)
866		report("INTEGER too large %"PRIu64, numval);
867
868	value->v.integer = numval;
869	gettoken();
870}
871
872static void
873parse_syntax_counter64(struct snmp_value *value)
874{
875	if (token != TOK_NUM)
876		report("bad COUNTER64 syntax");
877
878	value->v.counter64 = numval;
879	gettoken();
880}
881
882static void
883parse_syntax_octetstring(struct snmp_value *value)
884{
885	u_long alloc;
886	u_char *noct;
887
888	if (token == TOK_STR) {
889		value->v.octetstring.len = strvallen;
890		value->v.octetstring.octets = malloc(strvallen);
891		(void)memcpy(value->v.octetstring.octets, strval, strvallen);
892		gettoken();
893		return;
894	}
895
896	/* XX:XX:XX syntax */
897	value->v.octetstring.octets = NULL;
898	value->v.octetstring.len = 0;
899
900	if (token != TOK_NUM)
901		/* empty string is allowed */
902		return;
903
904	if (ERRPUSH()) {
905		free(value->v.octetstring.octets);
906		ERRNEXT();
907	}
908
909	alloc = 0;
910	for (;;) {
911		if (token != TOK_NUM)
912			report("bad OCTETSTRING syntax");
913		if (numval > 0xff)
914			report("byte value too large");
915		if (alloc == value->v.octetstring.len) {
916			alloc += 100;
917			noct = realloc(value->v.octetstring.octets, alloc);
918			if (noct == NULL)
919				report("%m");
920			value->v.octetstring.octets = noct;
921		}
922		value->v.octetstring.octets[value->v.octetstring.len++]
923		    = numval;
924		if (gettoken() != ':')
925			break;
926		gettoken();
927	}
928	ERRPOP();
929}
930
931static void
932parse_syntax_oid(struct snmp_value *value)
933{
934	value->v.oid.len = 0;
935
936	if (token != TOK_NUM)
937		return;
938
939	for (;;) {
940		if (token != TOK_NUM)
941			report("bad OID syntax");
942		if (numval > ASN_MAXID)
943			report("subid too large");
944		if (value->v.oid.len == ASN_MAXOIDLEN)
945			report("OID too long");
946		value->v.oid.subs[value->v.oid.len++] = numval;
947		if (gettoken() != '.')
948			break;
949		gettoken();
950	}
951}
952
953static void
954parse_syntax_ipaddress(struct snmp_value *value)
955{
956	int i;
957	u_char ip[4];
958
959	if (token == TOK_NUM) {
960		/* numerical address */
961		i = 0;
962		for (;;) {
963			if (numval >= 256)
964				report("ip address part too large");
965			value->v.ipaddress[i++] = numval;
966			if (i == 4)
967				break;
968			if (gettoken() != '.')
969				report("expecting '.' in ip address");
970		}
971		gettoken();
972
973	} else if (token == TOK_HOST) {
974		/* host name */
975		gethost(strval, ip);
976		for (i = 0; i < 4; i++)
977			value->v.ipaddress[i] = ip[i];
978		gettoken();
979
980	} else
981		report("bad ip address syntax");
982}
983
984static void
985parse_syntax_uint32(struct snmp_value *value)
986{
987
988	if (token != TOK_NUM)
989		report("bad number syntax");
990	if (numval > 0xffffffff)
991		report("number too large");
992	value->v.uint32 = numval;
993	gettoken();
994}
995
996/*
997 * Parse an assignement line
998 */
999static void
1000parse_assign(const char *varname)
1001{
1002	struct snmp_value value;
1003	struct asn_oid vindex;
1004	const struct snmp_node *node;
1005
1006	node = parse_oid(varname, &vindex);
1007	if (token != '=')
1008		report("'=' expected");
1009	gettoken();
1010
1011	if (ignore) {
1012		/* skip rest of line */
1013		while (token != TOK_EOL && token != TOK_EOF)
1014			gettoken();
1015		return;
1016	}
1017	if (node == NULL)
1018		report("unknown variable");
1019
1020	switch (value.syntax = node->syntax) {
1021
1022	  case SNMP_SYNTAX_NULL:
1023		parse_syntax_null(&value);
1024		break;
1025
1026	  case SNMP_SYNTAX_INTEGER:
1027		parse_syntax_integer(&value);
1028		break;
1029
1030	  case SNMP_SYNTAX_COUNTER64:
1031		parse_syntax_counter64(&value);
1032		break;
1033
1034	  case SNMP_SYNTAX_OCTETSTRING:
1035		parse_syntax_octetstring(&value);
1036		break;
1037
1038	  case SNMP_SYNTAX_OID:
1039		parse_syntax_oid(&value);
1040		break;
1041
1042	  case SNMP_SYNTAX_IPADDRESS:
1043		parse_syntax_ipaddress(&value);
1044		break;
1045
1046	  case SNMP_SYNTAX_COUNTER:
1047	  case SNMP_SYNTAX_GAUGE:
1048	  case SNMP_SYNTAX_TIMETICKS:
1049		parse_syntax_uint32(&value);
1050		break;
1051
1052	  case SNMP_SYNTAX_NOSUCHOBJECT:
1053	  case SNMP_SYNTAX_NOSUCHINSTANCE:
1054	  case SNMP_SYNTAX_ENDOFMIBVIEW:
1055		abort();
1056	}
1057
1058	if (ERRPUSH()) {
1059		snmp_value_free(&value);
1060		ERRNEXT();
1061	}
1062
1063	handle_assignment(node, &vindex, &value);
1064
1065	ERRPOP();
1066}
1067
1068/*
1069 * Handle macro definition line
1070 * We have already seen the := and the input now stands at the character
1071 * after the =. Skip whitespace and then call the input routine directly to
1072 * eat up characters.
1073 */
1074static void
1075parse_define(const char *varname)
1076{
1077	char *volatile string;
1078	char *new;
1079	volatile size_t alloc, length;
1080	int c;
1081	struct macro *m;
1082	int t = token;
1083
1084	alloc = 100;
1085	length = 0;
1086	if ((string = malloc(alloc)) == NULL)
1087		report("%m");
1088
1089	if (ERRPUSH()) {
1090		free(string);
1091		ERRNEXT();
1092	}
1093
1094	while ((c = input_getc_plain()) != EOF) {
1095		if (c == '\n' || !isspace(c))
1096			break;
1097	}
1098
1099	while (c != EOF && c != '#' && c != '\n') {
1100		if (alloc == length) {
1101			alloc *= 2;
1102			if ((new = realloc(string, alloc)) == NULL)
1103				report("%m");
1104			string = new;
1105		}
1106		string[length++] = c;
1107		c = input_getc_plain();
1108	}
1109	if (c == '#') {
1110		while ((c = input_getc_plain()) != EOF && c != '\n')
1111			;
1112	}
1113	if (c == EOF)
1114		report("EOF in macro definition");
1115
1116	LIST_FOREACH(m, &macros, link)
1117		if (strcmp(m->name, varname) == 0)
1118			break;
1119
1120	if (m == NULL) {
1121		if ((m = malloc(sizeof(*m))) == NULL)
1122			report("%m");
1123		if ((m->name = malloc(strlen(varname) + 1)) == NULL) {
1124			free(m);
1125			report("%m");
1126		}
1127		strcpy(m->name, varname);
1128		m->perm = 0;
1129		LIST_INSERT_HEAD(&macros, m, link);
1130
1131		m->value = string;
1132		m->length = length;
1133	} else {
1134		if (t != TOK_ASSIGN) {
1135			free(m->value);
1136			m->value = string;
1137			m->length = length;
1138		}
1139	}
1140
1141	token = TOK_EOL;
1142
1143	ERRPOP();
1144}
1145
1146/*
1147 * Free all macros
1148 */
1149static void
1150macro_free_all(void)
1151{
1152	static struct macro *m, *m1;
1153
1154	m = LIST_FIRST(&macros);
1155	while (m != NULL) {
1156		m1 = LIST_NEXT(m, link);
1157		if (!m->perm) {
1158			free(m->name);
1159			free(m->value);
1160			LIST_REMOVE(m, link);
1161			free(m);
1162		}
1163		m = m1;
1164	}
1165}
1166
1167/*
1168 * Parse an include directive and switch to the new file
1169 */
1170static void
1171parse_include(void)
1172{
1173	int sysdir = 0;
1174	char fname[_POSIX2_LINE_MAX];
1175
1176	if (gettoken() == '<') {
1177		sysdir = 1;
1178		if (gettoken() != TOK_STR)
1179			report("expecting filename after in .include");
1180	} else if (token != TOK_STR)
1181		report("expecting filename after in .include");
1182
1183	strcpy(fname, strval);
1184	if (sysdir && gettoken() != '>')
1185		report("expecting '>'");
1186	gettoken();
1187	if (input_open_file(fname, sysdir) == -1)
1188		report("%s: %m", fname);
1189}
1190
1191/*
1192 * Parse the configuration file
1193 */
1194static void
1195parse_file(const struct lmodule *mod)
1196{
1197	char varname[_POSIX2_LINE_MAX];
1198
1199	while (gettoken() != TOK_EOF) {
1200		if (token == TOK_EOL)
1201			/* empty line */
1202			continue;
1203		if (token == '%') {
1204			gettoken();
1205			parse_section(mod);
1206		} else if (token == '.') {
1207			if (gettoken() != TOK_STR)
1208				report("keyword expected after '.'");
1209			if (strcmp(strval, "include") == 0)
1210				parse_include();
1211			else
1212				report("unknown keyword '%s'", strval);
1213		} else if (token == TOK_STR) {
1214			strcpy(varname, strval);
1215			if (gettoken() == TOK_ASSIGN || token == TOK_QASSIGN)
1216				parse_define(varname);
1217			else
1218				parse_assign(varname);
1219		}
1220		if (token != TOK_EOL)
1221			report("eol expected");
1222	}
1223}
1224
1225/*
1226 * Do rollback on errors
1227 */
1228static void
1229do_rollback(void)
1230{
1231	struct assign *tp;
1232	struct snmp_node *node;
1233
1234	while ((tp = TAILQ_LAST(&assigns, assigns)) != NULL) {
1235		TAILQ_REMOVE(&assigns, tp, link);
1236		for (node = tree; node < &tree[tree_size]; node++)
1237			if (node->name == tp->node_name) {
1238				snmp_ctx->scratch = &tp->scratch;
1239				(void)(*node->op)(snmp_ctx, &tp->value,
1240				    node->oid.len, node->index,
1241				    SNMP_OP_ROLLBACK);
1242				break;
1243			}
1244		if (node == &tree[tree_size])
1245			syslog(LOG_ERR, "failed to find node for "
1246			    "rollback");
1247		snmp_value_free(&tp->value);
1248		free(tp);
1249	}
1250}
1251
1252/*
1253 * Do commit
1254 */
1255static void
1256do_commit(void)
1257{
1258	struct assign *tp;
1259	struct snmp_node *node;
1260
1261	while ((tp = TAILQ_FIRST(&assigns)) != NULL) {
1262		TAILQ_REMOVE(&assigns, tp, link);
1263		for (node = tree; node < &tree[tree_size]; node++)
1264			if (node->name == tp->node_name) {
1265				snmp_ctx->scratch = &tp->scratch;
1266				(void)(*node->op)(snmp_ctx, &tp->value,
1267				    node->oid.len, node->index, SNMP_OP_COMMIT);
1268				break;
1269			}
1270		if (node == &tree[tree_size])
1271			syslog(LOG_ERR, "failed to find node for commit");
1272		snmp_value_free(&tp->value);
1273		free(tp);
1274	}
1275}
1276
1277/*
1278 * Read the configuration file. Handle the entire file as one transaction.
1279 *
1280 * If lodmod is NULL, the sections for 'snmpd' and all loaded modules are
1281 * executed. If it is not NULL, only the sections for that module are handled.
1282 */
1283int
1284read_config(const char *fname, struct lmodule *lodmod)
1285{
1286	int err;
1287	char objbuf[ASN_OIDSTRLEN];
1288	char idxbuf[ASN_OIDSTRLEN];
1289
1290	ignore = 0;
1291
1292	input_push = 0;
1293
1294	if (ERRPUSH())
1295		return (-1);
1296	if (input_open_file(fname, 0) == -1) {
1297		syslog(LOG_ERR, "%s: %m", fname);
1298		return (-1);
1299	}
1300	ERRPOP();
1301	community = COMM_INITIALIZE;
1302
1303	if ((snmp_ctx = snmp_init_context()) == NULL) {
1304		input_close_all();
1305		syslog(LOG_ERR, "%m");
1306		return (-1);
1307	}
1308
1309	if (ERRPUSH()) {
1310		do_rollback();
1311		input_close_all();
1312		macro_free_all();
1313		free(snmp_ctx);
1314		return (-1);
1315	}
1316	parse_file(lodmod);
1317	ERRPOP();
1318
1319	if ((err = snmp_dep_commit(snmp_ctx)) != SNMP_ERR_NOERROR) {
1320		syslog(LOG_ERR, "init dep failed: %u %s %s", err,
1321		    asn_oid2str_r(&snmp_ctx->dep->obj, objbuf),
1322		    asn_oid2str_r(&snmp_ctx->dep->idx, idxbuf));
1323		snmp_dep_rollback(snmp_ctx);
1324		do_rollback();
1325		input_close_all();
1326		macro_free_all();
1327		free(snmp_ctx);
1328		return (-1);
1329	}
1330
1331	do_commit();
1332	snmp_dep_finish(snmp_ctx);
1333
1334	macro_free_all();
1335
1336	free(snmp_ctx);
1337
1338	return (0);
1339}
1340
1341/*
1342 * Define a permanent macro
1343 */
1344int
1345define_macro(const char *name, const char *value)
1346{
1347	struct macro *m;
1348
1349	if ((m = malloc(sizeof(*m))) == NULL)
1350		return (-1);
1351	if ((m->name = malloc(strlen(name) + 1)) == NULL) {
1352		free(m);
1353		return (-1);
1354	}
1355	strcpy(m->name, name);
1356	if ((m->value = malloc(strlen(value) + 1)) == NULL) {
1357		free(m->name);
1358		free(m);
1359		return (-1);
1360	}
1361	strcpy(m->value, value);
1362	m->length = strlen(value);
1363	return (0);
1364}
1365