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