config.c revision 156067
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 */
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	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 */
255static void
256input_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 */
277static void
278input_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 */
302static void
303input_close_all(void)
304{
305	while (!LIST_EMPTY(&inputs))
306		input_close();
307}
308
309/*
310 * Push back one character
311 */
312static void
313input_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 */
326static int
327input_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 */
377static int
378input_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 */
396static int
397input_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
441static void
442input_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
478static int
479# ifdef DEBUGGING
480_gettoken(void)
481# else
482gettoken(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
649static int
650gettoken()
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 */
686static void
687handle_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 */
748static void
749parse_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 */
776static void
777gethost(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 */
807static const struct snmp_node *
808parse_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 */
854static void
855parse_syntax_null(struct snmp_value *value __unused)
856{
857	if (token != TOK_EOL)
858		report("bad NULL syntax");
859}
860
861static void
862parse_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
873static void
874parse_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
883static void
884parse_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
932static void
933parse_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
954static void
955parse_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
985static void
986parse_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 */
1000static void
1001parse_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 */
1075static void
1076parse_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 */
1150static void
1151macro_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 */
1171static void
1172parse_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 */
1195static void
1196parse_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 */
1229static void
1230do_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 */
1256static void
1257do_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 */
1284int
1285read_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 */
1345int
1346define_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