xref: /openbsd/usr.sbin/snmpd/parse.y (revision 5dea098c)
1 /*	$OpenBSD: parse.y,v 1.90 2024/02/20 12:32:48 martijn Exp $	*/
2 
3 /*
4  * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org>
5  * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
6  * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
7  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
8  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
9  * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
10  * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
11  *
12  * Permission to use, copy, modify, and distribute this software for any
13  * purpose with or without fee is hereby granted, provided that the above
14  * copyright notice and this permission notice appear in all copies.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  */
24 
25 %{
26 #include <sys/stat.h>
27 #include <sys/queue.h>
28 #include <sys/socket.h>
29 #include <sys/time.h>
30 #include <sys/types.h>
31 #include <sys/un.h>
32 #include <sys/utsname.h>
33 
34 #include <arpa/inet.h>
35 
36 #include <netinet/in.h>
37 
38 #include <openssl/sha.h>
39 
40 #include <ber.h>
41 #include <ctype.h>
42 #include <err.h>
43 #include <errno.h>
44 #include <grp.h>
45 #include <inttypes.h>
46 #include <limits.h>
47 #include <netdb.h>
48 #include <pwd.h>
49 #include <stdarg.h>
50 #include <stdint.h>
51 #include <stdio.h>
52 #include <strings.h>
53 #include <syslog.h>
54 #include <unistd.h>
55 
56 #include "application.h"
57 #include "log.h"
58 #include "mib.h"
59 #include "snmpd.h"
60 #include "snmp.h"
61 
62 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
63 static struct file {
64 	TAILQ_ENTRY(file)	 entry;
65 	FILE			*stream;
66 	char			*name;
67 	size_t			 ungetpos;
68 	size_t			 ungetsize;
69 	u_char			*ungetbuf;
70 	int			 eof_reached;
71 	int			 lineno;
72 	int			 errors;
73 } *file, *topfile;
74 struct file	*pushfile(const char *, int);
75 int		 popfile(void);
76 int		 check_file_secrecy(int, const char *);
77 int		 yyparse(void);
78 int		 yylex(void);
79 int		 yyerror(const char *, ...)
80     __attribute__((__format__ (printf, 1, 2)))
81     __attribute__((__nonnull__ (1)));
82 int		 kw_cmp(const void *, const void *);
83 int		 lookup(char *);
84 int		 igetc(void);
85 int		 lgetc(int);
86 void		 lungetc(int);
87 int		 findeol(void);
88 
89 TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
90 struct sym {
91 	TAILQ_ENTRY(sym)	 entry;
92 	int			 used;
93 	int			 persist;
94 	char			*nam;
95 	char			*val;
96 };
97 int		 symset(const char *, const char *, int);
98 char		*symget(const char *);
99 
100 struct oid_sym {
101 	char			*descriptor;
102 	char			 file[PATH_MAX];
103 	int			 lineno;
104 };
105 
106 struct object_sym {
107 	struct oid_sym		 oid;
108 	char			*name;
109 	int			 isint;
110 	union {
111 		int32_t		 intval;
112 		char		*sval;
113 	};
114 };
115 
116 struct trapcmd_sym {
117 	struct oid_sym	 oid;
118 	struct trapcmd	*cmd;
119 };
120 
121 struct trapaddress_sym {
122 	struct oid_sym	 oid;
123 	struct trap_address *tr;
124 };
125 
126 struct snmpd			*conf = NULL;
127 static int			 errors = 0;
128 static struct usmuser		*user = NULL;
129 static int			 mibparsed = 0;
130 
131 static struct oid_sym		*blocklist = NULL;
132 static size_t			 nblocklist = 0;
133 static struct oid_sym		 sysoid = {};
134 static struct object_sym	*objects = NULL;
135 static size_t			 nobjects = 0;
136 static struct trapcmd_sym	*trapcmds = NULL;
137 static size_t			 ntrapcmds = 0;
138 static struct trapaddress_sym	*trapaddresses = NULL;
139 static size_t			 ntrapaddresses = 0;
140 
141 static uint8_t			 engineid[SNMPD_MAXENGINEIDLEN];
142 static int32_t			 enginepen;
143 static size_t			 engineidlen;
144 
145 int		 resolve_oid(struct ber_oid *, struct oid_sym *);
146 int		 resolve_oids(void);
147 int		 host(const char *, const char *, int, int,
148 		    struct sockaddr_storage *, int);
149 int		 listen_add(struct sockaddr_storage *, int, int);
150 
151 typedef struct {
152 	union {
153 		int64_t		 number;
154 		char		*string;
155 		struct host	*host;
156 		struct timeval	 tv;
157 		struct oid_sym	 oid;
158 		struct agentx_master ax;
159 		struct {
160 			int		 type;
161 			void		*data;
162 			long long	 value;
163 		}		 data;
164 		enum usmauth	 auth;
165 		enum usmpriv	 enc;
166 	} v;
167 	int lineno;
168 } YYSTYPE;
169 
170 #define axm_opts	axm_fd
171 #define AXM_PATH	1 << 0
172 #define AXM_OWNER	1 << 1
173 #define AXM_GROUP	1 << 2
174 #define AXM_MODE	1 << 3
175 
176 %}
177 
178 %token	INCLUDE
179 %token	LISTEN ON READ WRITE NOTIFY SNMPV1 SNMPV2 SNMPV3
180 %token	AGENTX PATH OWNER GROUP MODE
181 %token	ENGINEID PEN OPENBSD IP4 IP6 MAC TEXT OCTETS AGENTID HOSTHASH
182 %token	SYSTEM CONTACT DESCR LOCATION NAME OBJECTID SERVICES RTFILTER
183 %token	READONLY READWRITE OCTETSTRING INTEGER COMMUNITY TRAP RECEIVER
184 %token	SECLEVEL NONE AUTH ENC USER AUTHKEY ENCKEY ERROR
185 %token	HANDLE DEFAULT SRCADDR TCP UDP BLOCKLIST PORT
186 %token	MIB DIRECTORY
187 %token	<v.string>	STRING
188 %token	<v.number>	NUMBER
189 %type	<v.string>	usmuser community optcommunity
190 %type	<v.number>	listenproto listenflag listenflags
191 %type	<v.string>	srcaddr port
192 %type	<v.number>	optwrite yesno seclevel
193 %type	<v.data>	cmd hostauth hostauthv3 usmauthopts usmauthopt
194 %type	<v.oid>		oid hostoid trapoid
195 %type	<v.auth>	auth
196 %type	<v.enc>		enc
197 %type	<v.ax>		agentxopts agentxopt
198 
199 %%
200 
201 grammar		: /* empty */
202 		| grammar include '\n'
203 		| grammar '\n'
204 		| grammar varset '\n'
205 		| grammar main '\n'
206 		| grammar system '\n'
207 		| grammar object '\n'
208 		| grammar mib '\n'
209 		| grammar error '\n'		{ file->errors++; }
210 		;
211 
212 include		: INCLUDE STRING		{
213 			struct file	*nfile;
214 
215 			if ((nfile = pushfile($2, 0)) == NULL) {
216 				yyerror("failed to include file %s", $2);
217 				free($2);
218 				YYERROR;
219 			}
220 			free($2);
221 
222 			file = nfile;
223 			lungetc('\n');
224 		}
225 		;
226 
227 varset		: STRING '=' STRING	{
228 			char *s = $1;
229 			while (*s++) {
230 				if (isspace((unsigned char)*s)) {
231 					yyerror("macro name cannot contain "
232 					    "whitespace");
233 					free($1);
234 					free($3);
235 					YYERROR;
236 				}
237 			}
238 			if (symset($1, $3, 0) == -1)
239 				fatal("cannot store variable");
240 			free($1);
241 			free($3);
242 		}
243 		;
244 
245 yesno		:  STRING			{
246 			if (!strcmp($1, "yes"))
247 				$$ = 1;
248 			else if (!strcmp($1, "no"))
249 				$$ = 0;
250 			else {
251 				yyerror("syntax error, "
252 				    "either yes or no expected");
253 				free($1);
254 				YYERROR;
255 			}
256 			free($1);
257 		}
258 		;
259 
260 main		: LISTEN ON listen_udptcp
261 		| AGENTX agentxopts {
262 			struct agentx_master *master, *test;
263 			struct group *grp;
264 
265 			if ((master = malloc(sizeof(*master))) == NULL) {
266 				yyerror("malloc");
267 				YYERROR;
268 			}
269 			master->axm_fd = -1;
270 			master->axm_sun.sun_len = sizeof(master->axm_sun);
271 			master->axm_sun.sun_family = AF_UNIX;
272 			if ($2.axm_opts & AXM_PATH)
273 				strlcpy(master->axm_sun.sun_path,
274 				    $2.axm_sun.sun_path,
275 				    sizeof(master->axm_sun.sun_path));
276 			else
277 				strlcpy(master->axm_sun.sun_path,
278 				    AGENTX_MASTER_PATH,
279 				    sizeof(master->axm_sun.sun_path));
280 			master->axm_owner = $2.axm_opts & AXM_OWNER ?
281 			    $2.axm_owner : 0;
282 			if ($2.axm_opts & AXM_GROUP)
283 				master->axm_group = $2.axm_group;
284 			else {
285 				if ((grp = getgrnam(AGENTX_GROUP)) == NULL) {
286 					yyerror("agentx: group %s not found",
287 					    AGENTX_GROUP);
288 					YYERROR;
289 				}
290 				master->axm_group = grp->gr_gid;
291 			}
292 			master->axm_mode = $2.axm_opts & AXM_MODE ?
293 			    $2.axm_mode : 0660;
294 
295 			TAILQ_FOREACH(test, &conf->sc_agentx_masters,
296 			    axm_entry) {
297 				if (strcmp(test->axm_sun.sun_path,
298 				    master->axm_sun.sun_path) == 0) {
299 					yyerror("agentx: duplicate entry: %s",
300 					    test->axm_sun.sun_path);
301 					YYERROR;
302 				}
303 			}
304 
305 			TAILQ_INSERT_TAIL(&conf->sc_agentx_masters, master,
306 			    axm_entry);
307 		}
308 		| engineid_local {
309 			if (conf->sc_engineid_len != 0) {
310 				yyerror("Redefinition of engineid");
311 				YYERROR;
312 			}
313 			memcpy(conf->sc_engineid, engineid, engineidlen);
314 			conf->sc_engineid_len = engineidlen;
315 		}
316 		| READONLY COMMUNITY STRING	{
317 			if (strlcpy(conf->sc_rdcommunity, $3,
318 			    sizeof(conf->sc_rdcommunity)) >=
319 			    sizeof(conf->sc_rdcommunity)) {
320 				yyerror("r/o community name too long");
321 				free($3);
322 				YYERROR;
323 			}
324 			free($3);
325 		}
326 		| READWRITE COMMUNITY STRING	{
327 			if (strlcpy(conf->sc_rwcommunity, $3,
328 			    sizeof(conf->sc_rwcommunity)) >=
329 			    sizeof(conf->sc_rwcommunity)) {
330 				yyerror("r/w community name too long");
331 				free($3);
332 				YYERROR;
333 			}
334 			free($3);
335 		}
336 		| TRAP COMMUNITY STRING		{
337 			if (strlcpy(conf->sc_trcommunity, $3,
338 			    sizeof(conf->sc_trcommunity)) >=
339 			    sizeof(conf->sc_trcommunity)) {
340 				yyerror("trap community name too long");
341 				free($3);
342 				YYERROR;
343 			}
344 			free($3);
345 		}
346 		| TRAP RECEIVER	host
347 		| TRAP HANDLE trapoid cmd {
348 			struct trapcmd_sym *ttrapcmds;
349 
350 			if ((ttrapcmds = recallocarray(trapcmds, ntrapcmds,
351 			    ntrapcmds + 1, sizeof(*trapcmds))) == NULL) {
352 				yyerror("malloc");
353 				free($3.descriptor);
354 				free($4.data);
355 				YYERROR;
356 			}
357 
358 			trapcmds = ttrapcmds;
359 			trapcmds[ntrapcmds].oid = $3;
360 			trapcmds[ntrapcmds++].cmd = $4.data;
361 		}
362 		| BLOCKLIST oid {
363 			struct oid_sym *tblocklist;
364 
365 			if ((tblocklist = recallocarray(blocklist, nblocklist,
366 			    nblocklist + 1, sizeof(*blocklist))) == NULL) {
367 				yyerror("malloc");
368 				free($2.descriptor);
369 				YYERROR;
370 			}
371 			blocklist = tblocklist;
372 			blocklist[nblocklist++] = $2;
373 		}
374 		| RTFILTER yesno		{
375 			conf->sc_rtfilter = $2;
376 		}
377 		| seclevel {
378 			conf->sc_min_seclevel = $1;
379 		}
380 		| USER STRING			{
381 			const char *errstr;
382 			user = usm_newuser($2, &errstr);
383 			if (user == NULL) {
384 				yyerror("%s", errstr);
385 				free($2);
386 				YYERROR;
387 			}
388 		} userspecs {
389 			const char *errstr;
390 			if (usm_checkuser(user, &errstr) < 0) {
391 				yyerror("%s", errstr);
392 				YYERROR;
393 			}
394 			user = NULL;
395 		}
396 		;
397 
398 listenproto	: /* empty */			{ $$ = SOCK_DGRAM; }
399 		| UDP 				{ $$ = SOCK_DGRAM; }
400 		| TCP				{ $$ = SOCK_STREAM; }
401 		;
402 
403 listenflags	: /* empty */ { $$ = 0; }
404 		| listenflags listenflag { $$ |= $2; }
405 		;
406 
407 listenflag	: READ { $$ = ADDRESS_FLAG_READ; }
408 		| WRITE { $$ = ADDRESS_FLAG_WRITE; }
409 		| NOTIFY { $$ = ADDRESS_FLAG_NOTIFY; }
410 		| SNMPV1 { $$ = ADDRESS_FLAG_SNMPV1; }
411 		| SNMPV2 { $$ = ADDRESS_FLAG_SNMPV2; }
412 		| SNMPV3 { $$ = ADDRESS_FLAG_SNMPV3; }
413 		;
414 
415 listen_udptcp	: listenproto STRING port listenflags	{
416 			struct sockaddr_storage ss[16];
417 			int nhosts, j;
418 			char *address[2], *port = $3;
419 			size_t addresslen = 1, i;
420 
421 			if (port == NULL) {
422 				if (($4 & ADDRESS_FLAG_PERM) ==
423 				    ADDRESS_FLAG_NOTIFY)
424 					port = SNMPTRAP_PORT;
425 				else
426 					port = SNMP_PORT;
427 			}
428 
429 			if (strcmp($2, "any") == 0) {
430 				addresslen = 2;
431 				address[0] = "0.0.0.0";
432 				address[1] = "::";
433 			} else {
434 				addresslen = 1;
435 				address[0] = $2;
436 			}
437 
438 			for (i = 0; i < addresslen; i++) {
439 				if ((nhosts = host(address[i], port, AF_UNSPEC,
440 				    $1, ss, nitems(ss))) < 1) {
441 					yyerror("invalid address: %s", $2);
442 					free($2);
443 					free($3);
444 					YYERROR;
445 				}
446 				if (nhosts > (int)nitems(ss))
447 					log_warn("%s:%s resolves to more than "
448 					    "%zu hosts", $2, port, nitems(ss));
449 
450 				for (j = 0; j < nhosts; j++) {
451 					if (listen_add(&(ss[j]), $1, $4) == -1) {
452 						yyerror("calloc");
453 						YYERROR;
454 					}
455 				}
456 			}
457 			free($2);
458 			free($3);
459 		}
460 		;
461 
462 port		: /* empty */			{
463 			$$ = NULL;
464 		}
465 		| PORT STRING			{
466 			$$ = $2;
467 		}
468 		| PORT NUMBER			{
469 			char *number;
470 
471 			if ($2 > UINT16_MAX) {
472 				yyerror("port number too large");
473 				YYERROR;
474 			}
475 			if ($2 < 1) {
476 				yyerror("port number too small");
477 				YYERROR;
478 			}
479 			if (asprintf(&number, "%"PRId64, $2) == -1) {
480 				yyerror("malloc");
481 				YYERROR;
482 			}
483 			$$ = number;
484 		}
485 		;
486 
487 agentxopt	: PATH STRING			{
488 			if ($2[0] != '/') {
489 				yyerror("agentx path: must be absolute");
490 				YYERROR;
491 			}
492 			if (strlcpy($$.axm_sun.sun_path, $2,
493 			    sizeof($$.axm_sun.sun_path)) >=
494 			    sizeof($$.axm_sun.sun_path)) {
495 				yyerror("agentx path: too long");
496 				YYERROR;
497 			}
498 			$$.axm_opts = AXM_PATH;
499 		}
500 		| OWNER NUMBER			{
501 			if ($2 > UID_MAX) {
502 				yyerror("agentx owner: too large");
503 				YYERROR;
504 			}
505 			$$.axm_owner = $2;
506 			$$.axm_opts = AXM_OWNER;
507 		}
508 		| OWNER STRING			{
509 			struct passwd *pw;
510 			const char *errstr;
511 
512 			$$.axm_owner = strtonum($2, 0, UID_MAX, &errstr);
513 			if (errstr != NULL && errno == ERANGE) {
514 				yyerror("agentx owner: %s", errstr);
515 				YYERROR;
516 			}
517 			if ((pw = getpwnam($2)) == NULL) {
518 				yyerror("agentx owner: user not found");
519 				YYERROR;
520 			}
521 			$$.axm_owner = pw->pw_uid;
522 			$$.axm_opts = AXM_OWNER;
523 		}
524 		| GROUP NUMBER			{
525 			if ($2 > GID_MAX) {
526 				yyerror("agentx group: too large");
527 				YYERROR;
528 			}
529 			$$.axm_group = $2;
530 			$$.axm_opts = AXM_GROUP;
531 		}
532 		| GROUP STRING			{
533 			struct group *gr;
534 			const char *errstr;
535 
536 			$$.axm_group = strtonum($2, 0, GID_MAX, &errstr);
537 			if (errstr != NULL && errno == ERANGE) {
538 				yyerror("agentx group: %s", errstr);
539 				YYERROR;
540 			}
541 			if ((gr = getgrnam($2)) == NULL) {
542 				yyerror("agentx group: group not found");
543 				YYERROR;
544 			}
545 			$$.axm_group = gr->gr_gid;
546 			$$.axm_opts = AXM_GROUP;
547 		}
548 		| MODE NUMBER			{
549 			long mode;
550 			char *endptr, str[21];
551 
552 			snprintf(str, sizeof(str), "%lld", $2);
553 			errno = 0;
554 			mode = strtol(str, &endptr, 8);
555 			if (errno != 0 || endptr[0] != '\0' ||
556 			    mode <= 0 || mode > 0777) {
557 				yyerror("agentx mode: invalid");
558 				YYERROR;
559 			}
560 			$$.axm_mode = mode;
561 			$$.axm_opts = AXM_MODE;
562 		}
563 		;
564 
565 agentxopts	: /* empty */			{
566 			bzero(&$$, sizeof($$));
567 		}
568 		| agentxopts agentxopt {
569 			if ($$.axm_opts & $2.axm_opts) {
570 				yyerror("agentx: duplicate option");
571 				YYERROR;
572 			}
573 			switch ($2.axm_opts) {
574 			case AXM_PATH:
575 				strlcpy($$.axm_sun.sun_path,
576 				    $2.axm_sun.sun_path,
577 				    sizeof($$.axm_sun.sun_path));
578 				break;
579 			case AXM_OWNER:
580 				$$.axm_owner = $2.axm_owner;
581 				break;
582 			case AXM_GROUP:
583 				$$.axm_group = $2.axm_group;
584 				break;
585 			case AXM_MODE:
586 				$$.axm_mode = $2.axm_mode;
587 				break;
588 			}
589 			$$.axm_opts |= $2.axm_opts;
590 		}
591 		;
592 
593 enginefmt	: IP4 STRING			{
594 			struct in_addr addr;
595 
596 			engineid[engineidlen++] = SNMP_ENGINEID_FMT_IPv4;
597 			if (inet_pton(AF_INET, $2, &addr) != 1) {
598 				yyerror("Invalid ipv4 address: %s", $2);
599 				free($2);
600 				YYERROR;
601 			}
602 			memcpy(engineid + engineidlen, &addr,
603 			    sizeof(engineid) - engineidlen);
604 			engineid[0] |= SNMP_ENGINEID_NEW;
605 			engineidlen += sizeof(addr);
606 			free($2);
607 		}
608 		| IP6 STRING			{
609 			struct in6_addr addr;
610 
611 			engineid[engineidlen++] = SNMP_ENGINEID_FMT_IPv6;
612 			if (inet_pton(AF_INET6, $2, &addr) != 1) {
613 				yyerror("Invalid ipv6 address: %s", $2);
614 				free($2);
615 				YYERROR;
616 			}
617 			memcpy(engineid + engineidlen, &addr,
618 			    sizeof(engineid) - engineidlen);
619 			engineid[0] |= SNMP_ENGINEID_NEW;
620 			engineidlen += sizeof(addr);
621 			free($2);
622 		}
623 		| MAC STRING			{
624 			size_t i;
625 
626 			if (strlen($2) != 5 * 3 + 2) {
627 				yyerror("Invalid mac address: %s", $2);
628 				free($2);
629 				YYERROR;
630 			}
631 			engineid[engineidlen++] = SNMP_ENGINEID_FMT_MAC;
632 			for (i = 0; i < 6; i++) {
633 				if (fromhexstr(engineid + engineidlen + i,
634 				    $2 + (i * 3), 2) == NULL ||
635 				    $2[i * 3 + 2] != (i < 5 ? ':' : '\0')) {
636 					yyerror("Invalid mac address: %s", $2);
637 					free($2);
638 					YYERROR;
639 				}
640 			}
641 			engineid[0] |= SNMP_ENGINEID_NEW;
642 			engineidlen += 6;
643 			free($2);
644 		}
645 		| TEXT STRING			{
646 			size_t i, fmtstart;
647 
648 			engineid[engineidlen++] = SNMP_ENGINEID_FMT_TEXT;
649 			for (i = 0, fmtstart = engineidlen;
650 			    i < sizeof(engineid) - engineidlen && $2[i] != '\0';
651 			    i++) {
652 				if (!isprint($2[i])) {
653 					yyerror("invalid text character");
654 					free($2);
655 					YYERROR;
656 				}
657 				engineid[fmtstart + i] = $2[i];
658 				engineidlen++;
659 			}
660 			if (i == 0 || $2[i] != '\0') {
661 				yyerror("Invalid text length: %s", $2);
662 				free($2);
663 				YYERROR;
664 			}
665 			engineid[0] |= SNMP_ENGINEID_NEW;
666 			free($2);
667 		}
668 		| OCTETS STRING			{
669 			if (strlen($2) / 2 > sizeof(engineid) - 1) {
670 				yyerror("Invalid octets length: %s", $2);
671 				free($2);
672 				YYERROR;
673 			}
674 
675 			engineid[engineidlen++] = SNMP_ENGINEID_FMT_OCT;
676 			if (fromhexstr(engineid + engineidlen, $2,
677 			    strlen($2)) == NULL) {
678 				yyerror("Invalid octets: %s", $2);
679 				free($2);
680 				YYERROR;
681 			}
682 			engineidlen += strlen($2) / 2;
683 			engineid[0] |= SNMP_ENGINEID_NEW;
684 			free($2);
685 		}
686 		| AGENTID STRING		{
687 			if (strlen($2) / 2 != 8) {
688 				yyerror("Invalid agentid length: %s", $2);
689 				free($2);
690 				YYERROR;
691 			}
692 
693 			if (fromhexstr(engineid + engineidlen, $2,
694 			    strlen($2)) == NULL) {
695 				yyerror("Invalid agentid: %s", $2);
696 				free($2);
697 				YYERROR;
698 			}
699 			engineidlen += 8;
700 			engineid[0] |= SNMP_ENGINEID_OLD;
701 			free($2);
702 		}
703 		| HOSTHASH STRING		{
704 			if (enginepen != PEN_OPENBSD) {
705 				yyerror("hosthash only allowed for pen "
706 				    "openbsd");
707 				YYERROR;
708 			}
709 			engineid[engineidlen++] = SNMP_ENGINEID_FMT_HH;
710 			memcpy(engineid + engineidlen,
711 			    SHA256($2, strlen($2), NULL),
712 			    sizeof(engineid) - engineidlen);
713 			engineidlen = sizeof(engineid);
714 			engineid[0] |= SNMP_ENGINEID_NEW;
715 			free($2);
716 		}
717 		| NUMBER STRING			{
718 			if (enginepen == PEN_OPENBSD) {
719 				yyerror("%lld is only allowed when pen is not "
720 				    "openbsd", $1);
721 				YYERROR;
722 			}
723 
724 			if ($1 < 128 || $1 > 255) {
725 				yyerror("Invalid format number: %lld\n", $1);
726 				YYERROR;
727 			}
728 			if (strlen($2) / 2 > sizeof(engineid) - 1) {
729 				yyerror("Invalid octets length: %s", $2);
730 				free($2);
731 				YYERROR;
732 			}
733 
734 			engineid[engineidlen++] = (uint8_t)$1;
735 			if (fromhexstr(engineid + engineidlen, $2,
736 			    strlen($2)) == NULL) {
737 				yyerror("Invalid octets: %s", $2);
738 				free($2);
739 				YYERROR;
740 			}
741 			engineidlen += strlen($2) / 2;
742 			engineid[0] |= SNMP_ENGINEID_NEW;
743 			free($2);
744 		}
745 		;
746 
747 enginefmt_local	: enginefmt
748 		| HOSTHASH			{
749 			char hostname[HOST_NAME_MAX + 1];
750 
751 			if (enginepen != PEN_OPENBSD) {
752 				yyerror("hosthash only allowed for pen "
753 				    "openbsd");
754 				YYERROR;
755 			}
756 
757 			if (gethostname(hostname, sizeof(hostname)) == -1) {
758 				yyerror("gethostname: %s", strerror(errno));
759 				YYERROR;
760 			}
761 
762 			engineid[engineidlen++] = SNMP_ENGINEID_FMT_HH;
763 			memcpy(engineid + engineidlen,
764 			    SHA256(hostname, strlen(hostname), NULL),
765 			    sizeof(engineid) - engineidlen);
766 			engineidlen = sizeof(engineid);
767 			engineid[0] |= SNMP_ENGINEID_NEW;
768 		}
769 		;
770 
771 pen		: /* empty */			{
772 			enginepen = PEN_OPENBSD;
773 		}
774 		| PEN NUMBER			{
775 			if ($2 > INT32_MAX) {
776 				yyerror("pen number too large");
777 				YYERROR;
778 			}
779 			if ($2 <= 0) {
780 				yyerror("pen number too small");
781 				YYERROR;
782 			}
783 			enginepen = $2;
784 		}
785 		| PEN OPENBSD			{
786 			enginepen = PEN_OPENBSD;
787 		}
788 		;
789 
790 engineid_local	: ENGINEID pen			{
791 			uint32_t npen = htonl(enginepen);
792 
793 			memcpy(engineid, &npen, sizeof(enginepen));
794 			engineidlen = sizeof(enginepen);
795 		} enginefmt_local
796 
797 system		: SYSTEM sysmib
798 		;
799 
800 sysmib		: CONTACT STRING		{
801 			if (conf->sc_system.sys_contact[0] != '\0') {
802 				yyerror("system contact already defined");
803 				free($2);
804 				YYERROR;
805 			}
806 			if (strlcpy(conf->sc_system.sys_contact, $2,
807 			    sizeof(conf->sc_system.sys_contact)) >=
808 			    sizeof(conf->sc_system.sys_contact)) {
809 				yyerror("system contact too long");
810 				free($2);
811 				YYERROR;
812 			}
813 			free($2);
814 		}
815 		| DESCR STRING			{
816 			if (conf->sc_system.sys_descr[0] != '\0') {
817 				yyerror("system description already defined");
818 				free($2);
819 				YYERROR;
820 			}
821 			if (strlcpy(conf->sc_system.sys_descr, $2,
822 			    sizeof(conf->sc_system.sys_descr)) >=
823 			    sizeof(conf->sc_system.sys_descr)) {
824 				yyerror("system description too long");
825 				free($2);
826 				YYERROR;
827 			}
828 			free($2);
829 		}
830 		| LOCATION STRING		{
831 			if (conf->sc_system.sys_location[0] != '\0') {
832 				yyerror("system location already defined");
833 				free($2);
834 				YYERROR;
835 			}
836 			if (strlcpy(conf->sc_system.sys_location, $2,
837 			    sizeof(conf->sc_system.sys_location)) >=
838 			    sizeof(conf->sc_system.sys_location)) {
839 				yyerror("system location too long");
840 				free($2);
841 				YYERROR;
842 			}
843 			free($2);
844 		}
845 		| NAME STRING			{
846 			if (conf->sc_system.sys_name[0] != '\0') {
847 				yyerror("system name already defined");
848 				free($2);
849 				YYERROR;
850 			}
851 			if (strlcpy(conf->sc_system.sys_name, $2,
852 			    sizeof(conf->sc_system.sys_name)) >=
853 			    sizeof(conf->sc_system.sys_name)) {
854 				yyerror("system name too long");
855 				free($2);
856 				YYERROR;
857 			}
858 			free($2);
859 		}
860 		| OBJECTID oid			{
861 			if (sysoid.descriptor != NULL) {
862 				yyerror("system oid already defined");
863 				free($2.descriptor);
864 				YYERROR;
865 			}
866 			sysoid = $2;
867 		}
868 		| SERVICES NUMBER		{
869 			if (conf->sc_system.sys_services != -1) {
870 				yyerror("system services already defined");
871 				YYERROR;
872 			}
873 			if ($2 < 0) {
874 				yyerror("system services too small");
875 				YYERROR;
876 			} else if ($2 > 127) {
877 				yyerror("system services too large");
878 				YYERROR;
879 			}
880 			conf->sc_system.sys_services = $2;
881 		}
882 		;
883 
884 object		: OBJECTID oid NAME STRING optwrite 	{
885 			struct object_sym *tobjects;
886 
887 			if ((tobjects = recallocarray(objects, nobjects,
888 			    nobjects + 1, sizeof(*objects))) == NULL) {
889 				yyerror("malloc");
890 				free($2.descriptor);
891 				free($4);
892 			}
893 			objects = tobjects;
894 			nobjects++;
895 			objects[nobjects - 1].oid = $2;
896 			objects[nobjects - 1].name = $4;
897 		} objectvalue
898 		;
899 
900 objectvalue	: INTEGER NUMBER			{
901 			if ($2 < INT32_MIN) {
902 				yyerror("number too small");
903 				YYERROR;
904 			}
905 			if ($2 > INT32_MAX) {
906 				yyerror("number too large");
907 				YYERROR;
908 			}
909 			objects[nobjects - 1].isint = 1;
910 			objects[nobjects - 1].intval = $2;
911 		}
912 		| OCTETSTRING STRING			{
913 			objects[nobjects - 1].isint = 0;
914 			objects[nobjects - 1].sval = $2;
915 		}
916 		;
917 
918 optwrite	: READONLY				{ $$ = 0; }
919 		| READWRITE				{ $$ = 1; }
920 		;
921 
922 oid		: STRING				{
923 			$$.descriptor = $1;
924 			strlcpy($$.file, file->name, sizeof($$.file));
925 			$$.lineno = file->lineno;
926 		}
927 		;
928 
929 trapoid		: oid					{ $$ = $1; }
930 		| DEFAULT				{
931 			if (($$.descriptor = strdup("1.3")) == NULL) {
932 				yyerror("malloc");
933 				YYERROR;
934 			}
935 			strlcpy($$.file, file->name, sizeof($$.file));
936 			$$.lineno = file->lineno;
937 		}
938 		;
939 
940 hostoid		: /* empty */				{
941 			$$.descriptor = NULL;
942 			strlcpy($$.file, file->name, sizeof($$.file));
943 			$$.lineno = file->lineno;
944 		}
945 		| OBJECTID oid				{ $$ = $2; }
946 		;
947 
948 usmuser		: USER STRING				{
949 			if (strlen($2) > SNMPD_MAXUSERNAMELEN) {
950 				yyerror("User name too long: %s", $2);
951 				free($2);
952 				YYERROR;
953 			}
954 			$$ = $2;
955 		}
956 		;
957 
958 community	: COMMUNITY STRING			{
959 			if (strlen($2) > SNMPD_MAXCOMMUNITYLEN) {
960 				yyerror("Community too long: %s", $2);
961 				free($2);
962 				YYERROR;
963 			}
964 			$$ = $2;
965 		}
966 		;
967 
968 optcommunity	: /* empty */				{ $$ = NULL; }
969 		| community				{ $$ = $1; }
970 		;
971 
972 usmauthopt	: usmuser				{
973 			$$.data = $1;
974 			$$.value = -1;
975 		}
976 		| seclevel				{
977 			$$.data = 0;
978 			$$.value = $1;
979 		}
980 		;
981 
982 usmauthopts	: /* empty */				{
983 			$$.data = NULL;
984 			$$.value = -1;
985 		}
986 		| usmauthopts usmauthopt		{
987 			if ($2.data != NULL) {
988 				if ($$.data != NULL) {
989 					yyerror("user redefined");
990 					free($2.data);
991 					YYERROR;
992 				}
993 				$$.data = $2.data;
994 			} else {
995 				if ($$.value != -1) {
996 					yyerror("seclevel redefined");
997 					YYERROR;
998 				}
999 				$$.value = $2.value;
1000 			}
1001 		}
1002 		;
1003 
1004 hostauthv3	: usmauthopts				{
1005 			if ($1.data == NULL) {
1006 				yyerror("user missing");
1007 				YYERROR;
1008 			}
1009 			$$.data = $1.data;
1010 			$$.value = $1.value;
1011 		}
1012 		;
1013 
1014 hostauth	: hostauthv3				{
1015 			$$.type = SNMP_V3;
1016 			$$.data = $1.data;
1017 			$$.value = $1.value;
1018 		}
1019 		| SNMPV2 optcommunity			{
1020 			$$.type = SNMP_V2;
1021 			$$.data = $2;
1022 		}
1023 		| SNMPV3 hostauthv3			{
1024 			$$.type = SNMP_V3;
1025 			$$.data = $2.data;
1026 			$$.value = $2.value;
1027 		}
1028 		;
1029 
1030 srcaddr		: /* empty */				{ $$ = NULL; }
1031 		| SRCADDR STRING			{ $$ = $2; }
1032 		;
1033 
1034 hostdef		: STRING hostoid hostauth srcaddr	{
1035 			struct sockaddr_storage ss, ssl = {};
1036 			struct trap_address *tr;
1037 			struct trapaddress_sym *ttrapaddresses;
1038 
1039 			if (host($1, SNMPTRAP_PORT, AF_UNSPEC, SOCK_DGRAM,
1040 			    &ss, 1) <= 0) {
1041 				yyerror("invalid host: %s", $1);
1042 				free($1);
1043 				free($2.descriptor);
1044 				free($3.data);
1045 				free($4);
1046 				YYERROR;
1047 			}
1048 			free($1);
1049 			if ($4 != NULL) {
1050 				if (host($4, "0", ss.ss_family, SOCK_DGRAM,
1051 				    &ss, 1) <= 0) {
1052 					yyerror("invalid source-address: %s",
1053 					    $4);
1054 					free($2.descriptor);
1055 					free($3.data);
1056 					free($4);
1057 					YYERROR;
1058 				}
1059 				free($4);
1060 			}
1061 
1062 			ttrapaddresses = reallocarray(trapaddresses,
1063 			    ntrapaddresses + 1, sizeof(*trapaddresses));
1064 			if (ttrapaddresses == NULL) {
1065 				yyerror("malloc");
1066 				free($2.descriptor);
1067 				free($3.data);
1068 				YYERROR;
1069 			}
1070 			trapaddresses = ttrapaddresses;
1071 			ntrapaddresses++;
1072 
1073 			if ((tr = calloc(1, sizeof(*tr))) == NULL) {
1074 				yyerror("calloc");
1075 				free($2.descriptor);
1076 				free($3.data);
1077 				ntrapaddresses--;
1078 				YYERROR;
1079 			}
1080 
1081 			tr->ta_ss = ss;
1082 			tr->ta_sslocal = ssl;
1083 			tr->ta_version = $3.type;
1084 			if ($3.type == SNMP_V2) {
1085 				(void)strlcpy(tr->ta_community, $3.data,
1086 				    sizeof(tr->ta_community));
1087 				free($3.data);
1088 			} else {
1089 				tr->ta_usmusername = $3.data;
1090 				tr->ta_seclevel = $3.value;
1091 			}
1092 
1093 			trapaddresses[ntrapaddresses - 1].oid = $2;
1094 			trapaddresses[ntrapaddresses - 1].tr = tr;
1095 		}
1096 		;
1097 
1098 hostlist	: /* empty */
1099 		| hostlist comma hostdef
1100 		;
1101 
1102 host		: hostdef
1103 		| '{' hostlist '}'
1104 		;
1105 
1106 comma		: /* empty */
1107 		| ','
1108 		;
1109 
1110 seclevel	: SECLEVEL NONE	{ $$ = 0; }
1111 		| SECLEVEL AUTH	{ $$ = SNMP_MSGFLAG_AUTH; }
1112 		| SECLEVEL ENC	{ $$ = SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV; }
1113 		;
1114 
1115 userspecs	: /* empty */
1116 		| userspecs userspec
1117 		;
1118 
1119 userspec	: AUTHKEY STRING		{
1120 			user->uu_authkey = $2;
1121 		}
1122 		| AUTH auth			{
1123 			user->uu_auth = $2;
1124 		}
1125 		| ENCKEY STRING			{
1126 			user->uu_privkey = $2;
1127 		}
1128 		| ENC enc			{
1129 			user->uu_priv = $2;
1130 		}
1131 		;
1132 
1133 auth		: STRING			{
1134 			if (strcasecmp($1, "hmac-md5") == 0 ||
1135 			    strcasecmp($1, "hmac-md5-96") == 0)
1136 				$$ = AUTH_MD5;
1137 			else if (strcasecmp($1, "hmac-sha1") == 0 ||
1138 			     strcasecmp($1, "hmac-sha1-96") == 0)
1139 				$$ = AUTH_SHA1;
1140 			else if (strcasecmp($1, "hmac-sha224") == 0 ||
1141 			    strcasecmp($1, "usmHMAC128SHA224AuthProtocol") == 0)
1142 				$$ = AUTH_SHA224;
1143 			else if (strcasecmp($1, "hmac-sha256") == 0 ||
1144 			    strcasecmp($1, "usmHMAC192SHA256AuthProtocol") == 0)
1145 				$$ = AUTH_SHA256;
1146 			else if (strcasecmp($1, "hmac-sha384") == 0 ||
1147 			    strcasecmp($1, "usmHMAC256SHA384AuthProtocol") == 0)
1148 				$$ = AUTH_SHA384;
1149 			else if (strcasecmp($1, "hmac-sha512") == 0 ||
1150 			    strcasecmp($1, "usmHMAC384SHA512AuthProtocol") == 0)
1151 				$$ = AUTH_SHA512;
1152 			else {
1153 				yyerror("syntax error, bad auth hmac");
1154 				free($1);
1155 				YYERROR;
1156 			}
1157 			free($1);
1158 		}
1159 		;
1160 
1161 enc		: STRING			{
1162 			if (strcasecmp($1, "des") == 0 ||
1163 			    strcasecmp($1, "cbc-des") == 0)
1164 				$$ = PRIV_DES;
1165 			else if (strcasecmp($1, "aes") == 0 ||
1166 			    strcasecmp($1, "cfb128-aes-128") == 0)
1167 				$$ = PRIV_AES;
1168 			else {
1169 				yyerror("syntax error, bad encryption cipher");
1170 				free($1);
1171 				YYERROR;
1172 			}
1173 			free($1);
1174 
1175 		}
1176 		;
1177 
1178 cmd		: STRING		{
1179 			struct trapcmd	*cmd;
1180 			size_t		 span, limit;
1181 			char		*pos, **args, **args2;
1182 			int		 nargs = 32;		/* XXX */
1183 
1184 			if ((cmd = calloc(1, sizeof(*cmd))) == NULL ||
1185 			    (args = calloc(nargs, sizeof(char *))) == NULL) {
1186 				free(cmd);
1187 				free($1);
1188 				YYERROR;
1189 			}
1190 
1191 			pos = $1;
1192 			limit = strlen($1);
1193 
1194 			while (pos < $1 + limit &&
1195 			    (span = strcspn(pos, " \t")) != 0) {
1196 				pos[span] = '\0';
1197 				args[cmd->cmd_argc] = strdup(pos);
1198 				if (args[cmd->cmd_argc] == NULL) {
1199 					trapcmd_free(cmd);
1200 					free(args);
1201 					free($1);
1202 					YYERROR;
1203 				}
1204 				cmd->cmd_argc++;
1205 				if (cmd->cmd_argc >= nargs - 1) {
1206 					nargs *= 2;
1207 					args2 = calloc(nargs, sizeof(char *));
1208 					if (args2 == NULL) {
1209 						trapcmd_free(cmd);
1210 						free(args);
1211 						free($1);
1212 						YYERROR;
1213 					}
1214 					args = args2;
1215 				}
1216 				pos += span + 1;
1217 			}
1218 			free($1);
1219 			cmd->cmd_argv = args;
1220 			$$.data = cmd;
1221 		}
1222 		;
1223 
1224 mib		: MIB DIRECTORY STRING		{
1225 			mib_parsedir($3);
1226 			mibparsed = 1;
1227 		}
1228 		;
1229 
1230 %%
1231 
1232 struct keywords {
1233 	const char	*k_name;
1234 	int		 k_val;
1235 };
1236 
1237 int
1238 yyerror(const char *fmt, ...)
1239 {
1240 	va_list		 ap;
1241 	char		*msg;
1242 
1243 	file->errors++;
1244 	va_start(ap, fmt);
1245 	if (vasprintf(&msg, fmt, ap) == -1)
1246 		fatalx("yyerror vasprintf");
1247 	va_end(ap);
1248 	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
1249 	free(msg);
1250 	return (0);
1251 }
1252 
1253 int
1254 kw_cmp(const void *k, const void *e)
1255 {
1256 	return (strcmp(k, ((const struct keywords *)e)->k_name));
1257 }
1258 
1259 int
1260 lookup(char *s)
1261 {
1262 	/* this has to be sorted always */
1263 	static const struct keywords keywords[] = {
1264 		{ "agentid",			AGENTID },
1265 		{ "agentx",			AGENTX },
1266 		{ "auth",			AUTH },
1267 		{ "authkey",			AUTHKEY },
1268 		{ "blocklist",			BLOCKLIST },
1269 		{ "community",			COMMUNITY },
1270 		{ "contact",			CONTACT },
1271 		{ "default",			DEFAULT },
1272 		{ "description",		DESCR },
1273 		{ "directory",			DIRECTORY },
1274 		{ "enc",			ENC },
1275 		{ "enckey",			ENCKEY },
1276 		{ "engineid",			ENGINEID },
1277 		{ "filter-routes",		RTFILTER },
1278 		{ "group",			GROUP },
1279 		{ "handle",			HANDLE },
1280 		{ "hosthash",			HOSTHASH },
1281 		{ "include",			INCLUDE },
1282 		{ "integer",			INTEGER },
1283 		{ "ipv4",			IP4 },
1284 		{ "ipv6",			IP6 },
1285 		{ "listen",			LISTEN },
1286 		{ "location",			LOCATION },
1287 		{ "mac",			MAC },
1288 		{ "mib",			MIB },
1289 		{ "mode",			MODE },
1290 		{ "name",			NAME },
1291 		{ "none",			NONE },
1292 		{ "notify",			NOTIFY },
1293 		{ "octets",			OCTETS },
1294 		{ "oid",			OBJECTID },
1295 		{ "on",				ON },
1296 		{ "openbsd",			OPENBSD },
1297 		{ "owner",			OWNER },
1298 		{ "path",			PATH },
1299 		{ "pen",			PEN },
1300 		{ "port",			PORT },
1301 		{ "read",			READ },
1302 		{ "read-only",			READONLY },
1303 		{ "read-write",			READWRITE },
1304 		{ "receiver",			RECEIVER },
1305 		{ "seclevel",			SECLEVEL },
1306 		{ "services",			SERVICES },
1307 		{ "snmpv1",			SNMPV1 },
1308 		{ "snmpv2c",			SNMPV2 },
1309 		{ "snmpv3",			SNMPV3 },
1310 		{ "source-address",		SRCADDR },
1311 		{ "string",			OCTETSTRING },
1312 		{ "system",			SYSTEM },
1313 		{ "tcp",			TCP },
1314 		{ "text",			TEXT },
1315 		{ "trap",			TRAP },
1316 		{ "udp",			UDP },
1317 		{ "user",			USER },
1318 		{ "write",			WRITE }
1319 	};
1320 	const struct keywords	*p;
1321 
1322 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
1323 	    sizeof(keywords[0]), kw_cmp);
1324 
1325 	if (p)
1326 		return (p->k_val);
1327 	else
1328 		return (STRING);
1329 }
1330 
1331 #define START_EXPAND	1
1332 #define DONE_EXPAND	2
1333 
1334 static int	expanding;
1335 
1336 int
1337 igetc(void)
1338 {
1339 	int	c;
1340 
1341 	while (1) {
1342 		if (file->ungetpos > 0)
1343 			c = file->ungetbuf[--file->ungetpos];
1344 		else c = getc(file->stream);
1345 
1346 		if (c == START_EXPAND)
1347 			expanding = 1;
1348 		else if (c == DONE_EXPAND)
1349 			expanding = 0;
1350 		else
1351 			break;
1352 	}
1353 	return (c);
1354 }
1355 
1356 int
1357 lgetc(int quotec)
1358 {
1359 	int		c, next;
1360 
1361 	if (quotec) {
1362 		if ((c = igetc()) == EOF) {
1363 			yyerror("reached end of file while parsing quoted string");
1364 			if (file == topfile || popfile() == EOF)
1365 				return (EOF);
1366 			return (quotec);
1367 		}
1368 		return (c);
1369 	}
1370 
1371 	while ((c = igetc()) == '\\') {
1372 		next = igetc();
1373 		if (next != '\n') {
1374 			c = next;
1375 			break;
1376 		}
1377 		yylval.lineno = file->lineno;
1378 		file->lineno++;
1379 	}
1380 	if (c == '\t' || c == ' ') {
1381 		/* Compress blanks to a single space. */
1382 		do {
1383 			c = getc(file->stream);
1384 		} while (c == '\t' || c == ' ');
1385 		ungetc(c, file->stream);
1386 		c = ' ';
1387 	}
1388 
1389 	if (c == EOF) {
1390 		/*
1391 		 * Fake EOL when hit EOF for the first time. This gets line
1392 		 * count right if last line in included file is syntactically
1393 		 * invalid and has no newline.
1394 		 */
1395 		if (file->eof_reached == 0) {
1396 			file->eof_reached = 1;
1397 			return ('\n');
1398 		}
1399 		while (c == EOF) {
1400 			if (file == topfile || popfile() == EOF)
1401 				return (EOF);
1402 			c = igetc();
1403 		}
1404 	}
1405 	return (c);
1406 }
1407 
1408 void
1409 lungetc(int c)
1410 {
1411 	if (c == EOF)
1412 		return;
1413 
1414 	if (file->ungetpos >= file->ungetsize) {
1415 		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
1416 		if (p == NULL)
1417 			err(1, "%s", __func__);
1418 		file->ungetbuf = p;
1419 		file->ungetsize *= 2;
1420 	}
1421 	file->ungetbuf[file->ungetpos++] = c;
1422 }
1423 
1424 int
1425 findeol(void)
1426 {
1427 	int	c;
1428 
1429 	/* skip to either EOF or the first real EOL */
1430 	while (1) {
1431 		c = lgetc(0);
1432 		if (c == '\n') {
1433 			file->lineno++;
1434 			break;
1435 		}
1436 		if (c == EOF)
1437 			break;
1438 	}
1439 	return (ERROR);
1440 }
1441 
1442 int
1443 yylex(void)
1444 {
1445 	char	 buf[8096];
1446 	char	*p, *val;
1447 	int	 quotec, next, c;
1448 	int	 token;
1449 
1450 top:
1451 	p = buf;
1452 	while ((c = lgetc(0)) == ' ' || c == '\t')
1453 		; /* nothing */
1454 
1455 	yylval.lineno = file->lineno;
1456 	if (c == '#')
1457 		while ((c = lgetc(0)) != '\n' && c != EOF)
1458 			; /* nothing */
1459 	if (c == '$' && !expanding) {
1460 		while (1) {
1461 			if ((c = lgetc(0)) == EOF)
1462 				return (0);
1463 
1464 			if (p + 1 >= buf + sizeof(buf) - 1) {
1465 				yyerror("string too long");
1466 				return (findeol());
1467 			}
1468 			if (isalnum(c) || c == '_') {
1469 				*p++ = c;
1470 				continue;
1471 			}
1472 			*p = '\0';
1473 			lungetc(c);
1474 			break;
1475 		}
1476 		val = symget(buf);
1477 		if (val == NULL) {
1478 			yyerror("macro '%s' not defined", buf);
1479 			return (findeol());
1480 		}
1481 		p = val + strlen(val) - 1;
1482 		lungetc(DONE_EXPAND);
1483 		while (p >= val) {
1484 			lungetc((unsigned char)*p);
1485 			p--;
1486 		}
1487 		lungetc(START_EXPAND);
1488 		goto top;
1489 	}
1490 
1491 	switch (c) {
1492 	case '\'':
1493 	case '"':
1494 		quotec = c;
1495 		while (1) {
1496 			if ((c = lgetc(quotec)) == EOF)
1497 				return (0);
1498 			if (c == '\n') {
1499 				file->lineno++;
1500 				continue;
1501 			} else if (c == '\\') {
1502 				if ((next = lgetc(quotec)) == EOF)
1503 					return (0);
1504 				if (next == quotec || next == ' ' ||
1505 				    next == '\t')
1506 					c = next;
1507 				else if (next == '\n') {
1508 					file->lineno++;
1509 					continue;
1510 				} else
1511 					lungetc(next);
1512 			} else if (c == quotec) {
1513 				*p = '\0';
1514 				break;
1515 			} else if (c == '\0') {
1516 				yyerror("syntax error");
1517 				return (findeol());
1518 			}
1519 			if (p + 1 >= buf + sizeof(buf) - 1) {
1520 				yyerror("string too long");
1521 				return (findeol());
1522 			}
1523 			*p++ = c;
1524 		}
1525 		yylval.v.string = strdup(buf);
1526 		if (yylval.v.string == NULL)
1527 			err(1, "%s", __func__);
1528 		return (STRING);
1529 	}
1530 
1531 #define allowed_to_end_number(x) \
1532 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
1533 
1534 	if (c == '-' || isdigit(c)) {
1535 		do {
1536 			*p++ = c;
1537 			if ((size_t)(p-buf) >= sizeof(buf)) {
1538 				yyerror("string too long");
1539 				return (findeol());
1540 			}
1541 		} while ((c = lgetc(0)) != EOF && isdigit(c));
1542 		lungetc(c);
1543 		if (p == buf + 1 && buf[0] == '-')
1544 			goto nodigits;
1545 		if (c == EOF || allowed_to_end_number(c)) {
1546 			const char *errstr = NULL;
1547 
1548 			*p = '\0';
1549 			yylval.v.number = strtonum(buf, LLONG_MIN,
1550 			    LLONG_MAX, &errstr);
1551 			if (errstr) {
1552 				yyerror("\"%s\" invalid number: %s",
1553 				    buf, errstr);
1554 				return (findeol());
1555 			}
1556 			return (NUMBER);
1557 		} else {
1558 nodigits:
1559 			while (p > buf + 1)
1560 				lungetc((unsigned char)*--p);
1561 			c = (unsigned char)*--p;
1562 			if (c == '-')
1563 				return (c);
1564 		}
1565 	}
1566 
1567 #define allowed_in_string(x) \
1568 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
1569 	x != '{' && x != '}' && \
1570 	x != '!' && x != '=' && x != '#' && \
1571 	x != ','))
1572 
1573 	if (isalnum(c) || c == ':' || c == '_') {
1574 		do {
1575 			*p++ = c;
1576 			if ((size_t)(p-buf) >= sizeof(buf)) {
1577 				yyerror("string too long");
1578 				return (findeol());
1579 			}
1580 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
1581 		lungetc(c);
1582 		*p = '\0';
1583 		if ((token = lookup(buf)) == STRING)
1584 			if ((yylval.v.string = strdup(buf)) == NULL)
1585 				err(1, "%s", __func__);
1586 		return (token);
1587 	}
1588 	if (c == '\n') {
1589 		yylval.lineno = file->lineno;
1590 		file->lineno++;
1591 	}
1592 	if (c == EOF)
1593 		return (0);
1594 	return (c);
1595 }
1596 
1597 int
1598 check_file_secrecy(int fd, const char *fname)
1599 {
1600 	struct stat	st;
1601 
1602 	if (fstat(fd, &st)) {
1603 		log_warn("cannot stat %s", fname);
1604 		return (-1);
1605 	}
1606 	if (st.st_uid != 0 && st.st_uid != getuid()) {
1607 		log_warnx("%s: owner not root or current user", fname);
1608 		return (-1);
1609 	}
1610 	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
1611 		log_warnx("%s: group writable or world read/writable", fname);
1612 		return (-1);
1613 	}
1614 	return (0);
1615 }
1616 
1617 struct file *
1618 pushfile(const char *name, int secret)
1619 {
1620 	struct file	*nfile;
1621 
1622 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
1623 		log_warn("%s", __func__);
1624 		return (NULL);
1625 	}
1626 	if ((nfile->name = strdup(name)) == NULL) {
1627 		log_warn("%s", __func__);
1628 		free(nfile);
1629 		return (NULL);
1630 	}
1631 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
1632 		log_warn("%s: %s", __func__, nfile->name);
1633 		free(nfile->name);
1634 		free(nfile);
1635 		return (NULL);
1636 	} else if (secret &&
1637 	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
1638 		fclose(nfile->stream);
1639 		free(nfile->name);
1640 		free(nfile);
1641 		return (NULL);
1642 	}
1643 	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
1644 	nfile->ungetsize = 16;
1645 	nfile->ungetbuf = malloc(nfile->ungetsize);
1646 	if (nfile->ungetbuf == NULL) {
1647 		log_warn("%s", __func__);
1648 		fclose(nfile->stream);
1649 		free(nfile->name);
1650 		free(nfile);
1651 		return (NULL);
1652 	}
1653 	TAILQ_INSERT_TAIL(&files, nfile, entry);
1654 	return (nfile);
1655 }
1656 
1657 int
1658 popfile(void)
1659 {
1660 	struct file	*prev;
1661 
1662 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
1663 		prev->errors += file->errors;
1664 
1665 	TAILQ_REMOVE(&files, file, entry);
1666 	fclose(file->stream);
1667 	free(file->name);
1668 	free(file->ungetbuf);
1669 	free(file);
1670 	file = prev;
1671 	return (file ? 0 : EOF);
1672 }
1673 
1674 int
1675 resolve_oid(struct ber_oid *dst, struct oid_sym *src)
1676 {
1677 	struct file f = { .name = src->file, };
1678 	const char *error;
1679 
1680 	file = &f;
1681 	yylval.lineno = src->lineno;
1682 
1683 	if ((error = mib_string2oid(src->descriptor, dst)) != NULL) {
1684 		if (smi_string2oid(src->descriptor, dst) == -1) {
1685 			yyerror("%s", error);
1686 			free(src->descriptor);
1687 			return -1;
1688 		}
1689 		yyerror("deprecated oid format");
1690 	}
1691 	free(src->descriptor);
1692 
1693 	return 0;
1694 }
1695 
1696 int
1697 resolve_oids(void)
1698 {
1699 	struct file f;
1700 	struct ber_oid oid;
1701 	const char *error;
1702 	size_t i;
1703 
1704 	conf->sc_blocklist = calloc(nblocklist, sizeof(*conf->sc_blocklist));
1705 	if (conf->sc_blocklist == NULL)
1706 		fatal("malloc");
1707 
1708 	conf->sc_nblocklist = nblocklist;
1709 	for (i = 0; i < nblocklist; i++) {
1710 		if (resolve_oid(&conf->sc_blocklist[i], &blocklist[i]) == -1)
1711 			return -1;
1712 	}
1713 	free(blocklist);
1714 
1715 	if (sysoid.descriptor != NULL) {
1716 		if (resolve_oid(&conf->sc_system.sys_oid, &sysoid) == -1)
1717 			return -1;
1718 	}
1719 
1720 	for (i = 0; i < nobjects; i++) {
1721 		if (resolve_oid(&oid, &objects[i].oid) == -1)
1722 			return -1;
1723 		file = &f;
1724 		f.name = objects[i].oid.file;
1725 		yylval.lineno = objects[i].oid.lineno;
1726 		if ((error = smi_insert(&oid, objects[i].name)) != NULL) {
1727 			yyerror("%s", error);
1728 			return -1;
1729 		}
1730 		if (objects[i].isint) {
1731 			if ((error = appl_internal_object_int(
1732 			    &oid, objects[i].intval)) != NULL) {
1733 				yyerror("%s", error);
1734 				return -1;
1735 			}
1736 		} else {
1737 			if ((error = appl_internal_object_string(
1738 			    &oid, objects[i].sval)) != NULL) {
1739 				yyerror("%s", error);
1740 				return -1;
1741 			}
1742 		}
1743 		free(objects[i].name);
1744 	}
1745 	free(objects);
1746 
1747 	for (i = 0; i < ntrapcmds; i++) {
1748 		if (resolve_oid(
1749 		    &trapcmds[i].cmd->cmd_oid, &trapcmds[i].oid) == -1)
1750 			return -1;
1751 		f.name = trapcmds[i].oid.file;
1752 		yylval.lineno = trapcmds[i].oid.lineno;
1753 		file = &f;
1754 		if (trapcmd_add(trapcmds[i].cmd) != 0) {
1755 			yyerror("duplicate oid");
1756 			return -1;
1757 		}
1758 	}
1759 	free(trapcmds);
1760 
1761 	for (i = 0; i < ntrapaddresses; i++) {
1762 		if (trapaddresses[i].oid.descriptor == NULL)
1763 			trapaddresses[i].tr->ta_oid.bo_n = 0;
1764 		else {
1765 			if (resolve_oid(&trapaddresses[i].tr->ta_oid,
1766 			    &trapaddresses[i].oid) == -1)
1767 				return -1;
1768 		}
1769 		TAILQ_INSERT_TAIL(&conf->sc_trapreceivers,
1770 		    trapaddresses[i].tr, entry);
1771 	}
1772 	free(trapaddresses);
1773 
1774 	return 0;
1775 }
1776 
1777 struct snmpd *
1778 parse_config(const char *filename, u_int flags)
1779 {
1780 	struct sockaddr_storage ss;
1781 	struct utsname u;
1782 	struct sym	*sym, *next;
1783 	struct address	*h;
1784 	struct trap_address	*tr;
1785 	const struct usmuser	*up;
1786 	const char	*errstr;
1787 	char		 hostname[HOST_NAME_MAX + 1];
1788 	int		 found;
1789 	uint32_t	 npen = htonl(PEN_OPENBSD);
1790 
1791 	if ((conf = calloc(1, sizeof(*conf))) == NULL) {
1792 		log_warn("%s", __func__);
1793 		return (NULL);
1794 	}
1795 
1796 	conf->sc_system.sys_services = -1;
1797 	conf->sc_flags = flags;
1798 
1799 	conf->sc_oidfmt =
1800 	    flags & SNMPD_F_NONAMES ?  MIB_OIDNUMERIC : MIB_OIDSYMBOLIC;
1801 
1802 	conf->sc_confpath = filename;
1803 	TAILQ_INIT(&conf->sc_addresses);
1804 	TAILQ_INIT(&conf->sc_agentx_masters);
1805 	TAILQ_INIT(&conf->sc_trapreceivers);
1806 	conf->sc_min_seclevel = SNMP_MSGFLAG_AUTH | SNMP_MSGFLAG_PRIV;
1807 
1808 	if ((file = pushfile(filename, 0)) == NULL) {
1809 		free(conf);
1810 		return (NULL);
1811 	}
1812 	topfile = file;
1813 	setservent(1);
1814 
1815 	yyparse();
1816 	errors = file->errors;
1817 	popfile();
1818 
1819 	endservent();
1820 
1821 	if (errors) {
1822 		free(conf);
1823 		return (NULL);
1824 	}
1825 
1826 	if (!mibparsed)
1827 		mib_parsedir("/usr/share/snmp/mibs");
1828 	mib_resolve();
1829 
1830 	if (resolve_oids() == -1) {
1831 		free(conf);
1832 		return NULL;
1833 	}
1834 
1835 	if (uname(&u) == -1)
1836 		fatal("uname");
1837 
1838 	if (conf->sc_system.sys_descr[0] == '\0')
1839 		snprintf(conf->sc_system.sys_descr,
1840 		    sizeof(conf->sc_system.sys_descr), "%s %s %s %s %s",
1841 		    u.sysname, u.nodename, u.release, u.version, u.machine);
1842 	if (conf->sc_system.sys_oid.bo_n == 0)
1843 		conf->sc_system.sys_oid = OID(MIB_SYSOID_DEFAULT);
1844 	if (conf->sc_system.sys_contact[0] == '\0')
1845 		snprintf(conf->sc_system.sys_contact,
1846 		    sizeof(conf->sc_system.sys_contact), "root@%s", u.nodename);
1847 	if (conf->sc_system.sys_name[0] == '\0')
1848 		snprintf(conf->sc_system.sys_name,
1849 		    sizeof(conf->sc_system.sys_name), "%s", u.nodename);
1850 	if (conf->sc_system.sys_services == -1)
1851 		conf->sc_system.sys_services = 0;
1852 
1853 
1854 	/* Must be identical to enginefmt_local:HOSTHASH */
1855 	if (conf->sc_engineid_len == 0) {
1856 		if (gethostname(hostname, sizeof(hostname)) == -1)
1857 			fatal("gethostname");
1858 		memcpy(conf->sc_engineid, &npen, sizeof(npen));
1859 		conf->sc_engineid_len += sizeof(npen);
1860 		conf->sc_engineid[conf->sc_engineid_len++] |=
1861 		    SNMP_ENGINEID_FMT_HH;
1862 		memcpy(conf->sc_engineid + conf->sc_engineid_len,
1863 		    SHA256(hostname, strlen(hostname), NULL),
1864 		    sizeof(conf->sc_engineid) - conf->sc_engineid_len);
1865 		conf->sc_engineid_len = sizeof(conf->sc_engineid);
1866 		conf->sc_engineid[0] |= SNMP_ENGINEID_NEW;
1867 	}
1868 
1869 	/* Setup default listen addresses */
1870 	if (TAILQ_EMPTY(&conf->sc_addresses)) {
1871 		if (host("0.0.0.0", SNMP_PORT, AF_INET, SOCK_DGRAM,
1872 		    &ss, 1) != 1)
1873 			fatal("Unexpected resolving of 0.0.0.0");
1874 		if (listen_add(&ss, SOCK_DGRAM, 0) == -1)
1875 			fatal("calloc");
1876 		if (host("::", SNMP_PORT, AF_INET6, SOCK_DGRAM, &ss, 1) != 1)
1877 			fatal("Unexpected resolving of ::");
1878 		if (listen_add(&ss, SOCK_DGRAM, 0) == -1)
1879 			fatal("calloc");
1880 	}
1881 
1882 	if ((up = usm_check_mincred(conf->sc_min_seclevel, &errstr)) != NULL)
1883 		fatalx("user '%s': %s", up->uu_name, errstr);
1884 
1885 	found = 0;
1886 	TAILQ_FOREACH(h, &conf->sc_addresses, entry) {
1887 		if (h->flags & ADDRESS_FLAG_NOTIFY)
1888 			found = 1;
1889 	}
1890 	if (ntrapcmds && !found) {
1891 		log_warnx("trap handler needs at least one notify listener");
1892 		free(conf);
1893 		return (NULL);
1894 	}
1895 	if (!ntrapcmds && found) {
1896 		log_warnx("notify listener needs at least one trap handler");
1897 		free(conf);
1898 		return (NULL);
1899 	}
1900 
1901 	TAILQ_FOREACH(tr, &conf->sc_trapreceivers, entry) {
1902 		if (tr->ta_version == SNMP_V2 &&
1903 		    tr->ta_community[0] == '\0' &&
1904 		    conf->sc_trcommunity[0] == '\0') {
1905 			log_warnx("trap receiver: missing community");
1906 			free(conf);
1907 			return (NULL);
1908 		}
1909 		if (tr->ta_version == SNMP_V3) {
1910 			tr->ta_usmuser = usm_finduser(tr->ta_usmusername);
1911 			if (tr->ta_usmuser == NULL) {
1912 				log_warnx("trap receiver: user not defined: %s",
1913 				    tr->ta_usmusername);
1914 				free(conf);
1915 				return (NULL);
1916 			}
1917 		}
1918 	}
1919 
1920 	/* Free macros and check which have not been used. */
1921 	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
1922 		if ((conf->sc_flags & SNMPD_F_VERBOSE) && !sym->used)
1923 			fprintf(stderr, "warning: macro '%s' not "
1924 			    "used\n", sym->nam);
1925 		if (!sym->persist) {
1926 			free(sym->nam);
1927 			free(sym->val);
1928 			TAILQ_REMOVE(&symhead, sym, entry);
1929 			free(sym);
1930 		}
1931 	}
1932 
1933 	return (conf);
1934 }
1935 
1936 int
1937 symset(const char *nam, const char *val, int persist)
1938 {
1939 	struct sym	*sym;
1940 
1941 	TAILQ_FOREACH(sym, &symhead, entry) {
1942 		if (strcmp(nam, sym->nam) == 0)
1943 			break;
1944 	}
1945 
1946 	if (sym != NULL) {
1947 		if (sym->persist == 1)
1948 			return (0);
1949 		else {
1950 			free(sym->nam);
1951 			free(sym->val);
1952 			TAILQ_REMOVE(&symhead, sym, entry);
1953 			free(sym);
1954 		}
1955 	}
1956 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
1957 		return (-1);
1958 
1959 	sym->nam = strdup(nam);
1960 	if (sym->nam == NULL) {
1961 		free(sym);
1962 		return (-1);
1963 	}
1964 	sym->val = strdup(val);
1965 	if (sym->val == NULL) {
1966 		free(sym->nam);
1967 		free(sym);
1968 		return (-1);
1969 	}
1970 	sym->used = 0;
1971 	sym->persist = persist;
1972 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
1973 	return (0);
1974 }
1975 
1976 int
1977 cmdline_symset(char *s)
1978 {
1979 	char	*sym, *val;
1980 	int	ret;
1981 
1982 	if ((val = strrchr(s, '=')) == NULL)
1983 		return (-1);
1984 	sym = strndup(s, val - s);
1985 	if (sym == NULL)
1986 		errx(1, "%s: strndup", __func__);
1987 	ret = symset(sym, val + 1, 1);
1988 	free(sym);
1989 
1990 	return (ret);
1991 }
1992 
1993 char *
1994 symget(const char *nam)
1995 {
1996 	struct sym	*sym;
1997 
1998 	TAILQ_FOREACH(sym, &symhead, entry) {
1999 		if (strcmp(nam, sym->nam) == 0) {
2000 			sym->used = 1;
2001 			return (sym->val);
2002 		}
2003 	}
2004 	return (NULL);
2005 }
2006 
2007 int
2008 host(const char *s, const char *port, int family, int type,
2009     struct sockaddr_storage *ss, int max)
2010 {
2011 	struct addrinfo		 hints, *res0, *res;
2012 	int			 error, i;
2013 
2014 	bzero(&hints, sizeof(hints));
2015 	hints.ai_family = family;
2016 	hints.ai_socktype = type;
2017 	/*
2018 	 * Without AI_NUMERICHOST getaddrinfo might not resolve ip addresses
2019 	 * for families not specified in the "family" statement in resolv.conf.
2020 	 */
2021 	hints.ai_flags = AI_NUMERICHOST;
2022 	error = getaddrinfo(s, port, &hints, &res0);
2023 	if (error == EAI_NONAME) {
2024 		hints.ai_flags = 0;
2025 		error = getaddrinfo(s, port, &hints, &res0);
2026 	}
2027 	if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
2028 		return 0;
2029 	if (error) {
2030 		log_warnx("Could not parse \"%s\": %s", s, gai_strerror(error));
2031 		return -1;
2032 	}
2033 
2034 	for (i = 0, res = res0; res; res = res->ai_next, i++) {
2035 		if (res->ai_family != AF_INET &&
2036 		    res->ai_family != AF_INET6)
2037 			continue;
2038 		if (i >= max)
2039 			continue;
2040 
2041 		bcopy(res->ai_addr, &(ss[i]), res->ai_addrlen);
2042 	}
2043 	freeaddrinfo(res0);
2044 
2045 	return i;
2046 }
2047 
2048 int
2049 listen_add(struct sockaddr_storage *ss, int type, int flags)
2050 {
2051 	struct sockaddr_in *sin;
2052 	struct sockaddr_in6 *sin6;
2053 	struct address *h;
2054 
2055 	if ((h = calloc(1, sizeof(*h))) == NULL)
2056 		return -1;
2057 	bcopy(ss, &(h->ss), sizeof(*ss));
2058 	if (ss->ss_family == AF_INET) {
2059 		sin = (struct sockaddr_in *)ss;
2060 		h->port = ntohs(sin->sin_port);
2061 	} else {
2062 		sin6 = (struct sockaddr_in6*)ss;
2063 		h->port = ntohs(sin6->sin6_port);
2064 	}
2065 	h->type = type;
2066 	if (((h->flags = flags) & ADDRESS_FLAG_PERM) == 0) {
2067 		if (h->port == 162)
2068 			h->flags |= ADDRESS_FLAG_NOTIFY;
2069 		else
2070 			h->flags |= ADDRESS_FLAG_READ | ADDRESS_FLAG_WRITE;
2071 	}
2072 	if ((h->flags & ADDRESS_FLAG_MPS) == 0)
2073 		h->flags |= ADDRESS_FLAG_SNMPV3;
2074 	TAILQ_INSERT_TAIL(&(conf->sc_addresses), h, entry);
2075 
2076 	return 0;
2077 }
2078