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