xref: /openbsd/usr.sbin/ypldap/parse.y (revision 8932bfb7)
1 /*	$OpenBSD: parse.y,v 1.9 2010/08/03 18:42:41 henning Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
5  * Copyright (c) 2007, 2008 Reyk Floeter <reyk@openbsd.org>
6  * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
7  * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
8  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
9  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
10  * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
11  * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
12  *
13  * Permission to use, copy, modify, and distribute this software for any
14  * purpose with or without fee is hereby granted, provided that the above
15  * copyright notice and this permission notice appear in all copies.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
18  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
19  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
20  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
21  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
22  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
23  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24  */
25 
26 %{
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/queue.h>
30 #include <sys/tree.h>
31 #include <sys/param.h>
32 #include <sys/socket.h>
33 #include <sys/stat.h>
34 
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 
38 #include <ctype.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <event.h>
42 #include <fcntl.h>
43 #include <limits.h>
44 #include <netdb.h>
45 #include <pwd.h>
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 #include "ypldap.h"
53 
54 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
55 static struct file {
56 	TAILQ_ENTRY(file)	 entry;
57 	FILE			*stream;
58 	char			*name;
59 	int			 lineno;
60 	int			 errors;
61 } *file, *topfile;
62 struct file	*pushfile(const char *, int);
63 int		 popfile(void);
64 int		 check_file_secrecy(int, const char *);
65 int		 yyparse(void);
66 int		 yylex(void);
67 int		 yyerror(const char *, ...);
68 int		 kw_cmp(const void *, const void *);
69 int		 lookup(char *);
70 int		 lgetc(int);
71 int		 lungetc(int);
72 int		 findeol(void);
73 
74 TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
75 struct sym {
76 	TAILQ_ENTRY(sym)	 entry;
77 	int			 used;
78 	int			 persist;
79 	char			*nam;
80 	char			*val;
81 };
82 int		 symset(const char *, const char *, int);
83 char		*symget(const char *);
84 
85 struct env		*conf = NULL;
86 struct idm		*idm = NULL;
87 static int		 errors = 0;
88 
89 typedef struct {
90 	union {
91 		int64_t		 number;
92 		char		*string;
93 	} v;
94 	int lineno;
95 } YYSTYPE;
96 
97 %}
98 
99 %token	SERVER FILTER ATTRIBUTE BASEDN BINDDN BINDCRED MAPS CHANGE DOMAIN PROVIDE
100 %token	USER GROUP TO EXPIRE HOME SHELL GECOS UID GID INTERVAL
101 %token	PASSWD NAME FIXED LIST GROUPNAME GROUPPASSWD GROUPGID MAP
102 %token	INCLUDE DIRECTORY CLASS PORT ERROR GROUPMEMBERS
103 %token	<v.string>	STRING
104 %token  <v.number>	NUMBER
105 %type	<v.number>	opcode attribute
106 %type	<v.string>	port
107 
108 %%
109 
110 grammar		: /* empty */
111 		| grammar '\n'
112 		| grammar include '\n'
113 		| grammar varset '\n'
114 		| grammar directory '\n'
115 		| grammar main '\n'
116 		| grammar error '\n'			{ file->errors++; }
117 		;
118 
119 nl		: '\n' optnl
120 		;
121 
122 optnl		: '\n' optnl
123 		| /* empty */
124 		;
125 
126 
127 include		: INCLUDE STRING			{
128 			struct file	*nfile;
129 
130 			if ((nfile = pushfile($2, 0)) == NULL) {
131 				yyerror("failed to include file %s", $2);
132 				free($2);
133 				YYERROR;
134 			}
135 			free($2);
136 
137 			file = nfile;
138 			lungetc('\n');
139 		}
140 		;
141 
142 varset		: STRING '=' STRING			{
143 			if (symset($1, $3, 0) == -1)
144 				fatal("cannot store variable");
145 			free($1);
146 			free($3);
147 		}
148 		;
149 
150 port		: /* empty */	{ $$ = NULL; }
151 		| PORT STRING	{ $$ = $2; }
152 		;
153 
154 opcode		: GROUP					{ $$ = 0; }
155 		| PASSWD				{ $$ = 1; }
156 		;
157 
158 
159 attribute	: NAME					{ $$ = 0; }
160 		| PASSWD				{ $$ = 1; }
161 		| UID					{ $$ = 2; }
162 		| GID					{ $$ = 3; }
163 		| CLASS					{ $$ = 4; }
164 		| CHANGE				{ $$ = 5; }
165 		| EXPIRE				{ $$ = 6; }
166 		| GECOS					{ $$ = 7; }
167 		| HOME					{ $$ = 8; }
168 		| SHELL					{ $$ = 9; }
169 		| GROUPNAME				{ $$ = 10; }
170 		| GROUPPASSWD				{ $$ = 11; }
171 		| GROUPGID				{ $$ = 12; }
172 		| GROUPMEMBERS				{ $$ = 13; }
173 		;
174 
175 diropt		: BINDDN STRING				{
176 			idm->idm_flags |= F_NEEDAUTH;
177 			if (strlcpy(idm->idm_binddn, $2,
178 			    sizeof(idm->idm_binddn)) >=
179 			    sizeof(idm->idm_binddn)) {
180 				yyerror("directory binddn truncated");
181 				free($2);
182 				YYERROR;
183 			}
184 			free($2);
185 		}
186 		| BINDCRED STRING			{
187 			idm->idm_flags |= F_NEEDAUTH;
188 			if (strlcpy(idm->idm_bindcred, $2,
189 			    sizeof(idm->idm_bindcred)) >=
190 			    sizeof(idm->idm_bindcred)) {
191 				yyerror("directory bindcred truncated");
192 				free($2);
193 				YYERROR;
194 			}
195 			free($2);
196 		}
197 		| BASEDN STRING			{
198 			if (strlcpy(idm->idm_basedn, $2,
199 			    sizeof(idm->idm_basedn)) >=
200 			    sizeof(idm->idm_basedn)) {
201 				yyerror("directory basedn truncated");
202 				free($2);
203 				YYERROR;
204 			}
205 			free($2);
206 		}
207 		| opcode FILTER STRING			{
208 			if (strlcpy(idm->idm_filters[$1], $3,
209 			    sizeof(idm->idm_filters[$1])) >=
210 			    sizeof(idm->idm_filters[$1])) {
211 				yyerror("filter truncated");
212 				free($3);
213 				YYERROR;
214 			}
215 			free($3);
216 		}
217 		| ATTRIBUTE attribute MAPS TO STRING	{
218 			if (strlcpy(idm->idm_attrs[$2], $5,
219 			    sizeof(idm->idm_attrs[$2])) >=
220 			    sizeof(idm->idm_attrs[$2])) {
221 				yyerror("attribute truncated");
222 				free($5);
223 				YYERROR;
224 			}
225 			free($5);
226 		}
227 		| FIXED ATTRIBUTE attribute STRING	{
228 			if (strlcpy(idm->idm_attrs[$3], $4,
229 			    sizeof(idm->idm_attrs[$3])) >=
230 			    sizeof(idm->idm_attrs[$3])) {
231 				yyerror("attribute truncated");
232 				free($4);
233 				YYERROR;
234 			}
235 			idm->idm_flags |= F_FIXED_ATTR($3);
236 			free($4);
237 		}
238 		| LIST attribute MAPS TO STRING	{
239 			if (strlcpy(idm->idm_attrs[$2], $5,
240 			    sizeof(idm->idm_attrs[$2])) >=
241 			    sizeof(idm->idm_attrs[$2])) {
242 				yyerror("attribute truncated");
243 				free($5);
244 				YYERROR;
245 			}
246 			idm->idm_list |= F_LIST($2);
247 			free($5);
248 		}
249 		;
250 
251 directory	: DIRECTORY STRING port {
252 			if ((idm = calloc(1, sizeof(*idm))) == NULL)
253 				fatal(NULL);
254 			idm->idm_id = conf->sc_maxid++;
255 
256 			if (strlcpy(idm->idm_name, $2,
257 			    sizeof(idm->idm_name)) >=
258 			    sizeof(idm->idm_name)) {
259 				yyerror("attribute truncated");
260 				free($2);
261 				YYERROR;
262 			}
263 
264 			free($2);
265 		} '{' optnl diropts '}'			{
266 			TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry);
267 			idm = NULL;
268 		}
269 		;
270 
271 main		: INTERVAL NUMBER			{
272 			conf->sc_conf_tv.tv_sec = $2;
273 			conf->sc_conf_tv.tv_usec = 0;
274 		}
275 		| DOMAIN STRING				{
276 			if (strlcpy(conf->sc_domainname, $2,
277 			    sizeof(conf->sc_domainname)) >=
278 			    sizeof(conf->sc_domainname)) {
279 				yyerror("domainname truncated");
280 				free($2);
281 				YYERROR;
282 			}
283 			free($2);
284 		}
285 		| PROVIDE MAP STRING			{
286 			if (strcmp($3, "passwd.byname") == 0)
287 				conf->sc_flags |= YPMAP_PASSWD_BYNAME;
288 			else if (strcmp($3, "passwd.byuid") == 0)
289 				conf->sc_flags |= YPMAP_PASSWD_BYUID;
290 			else if (strcmp($3, "master.passwd.byname") == 0)
291 				conf->sc_flags |= YPMAP_MASTER_PASSWD_BYNAME;
292 			else if (strcmp($3, "master.passwd.byuid") == 0)
293 				conf->sc_flags |= YPMAP_MASTER_PASSWD_BYUID;
294 			else if (strcmp($3, "group.byname") == 0)
295 				conf->sc_flags |= YPMAP_GROUP_BYNAME;
296 			else if (strcmp($3, "group.bygid") == 0)
297 				conf->sc_flags |= YPMAP_GROUP_BYGID;
298 			else {
299 				yyerror("unsupported map type: %s", $3);
300 				free($3);
301 				YYERROR;
302 			}
303 			free($3);
304 		}
305 		;
306 
307 diropts		: diropts diropt nl
308 		| diropt optnl
309 		;
310 
311 %%
312 
313 struct keywords {
314 	const char	*k_name;
315 	int		 k_val;
316 };
317 
318 int
319 yyerror(const char *fmt, ...)
320 {
321 	va_list		 ap;
322 
323 	file->errors++;
324 	va_start(ap, fmt);
325 	fprintf(stderr, "%s:%d: ", file->name, yylval.lineno);
326 	vfprintf(stderr, fmt, ap);
327 	fprintf(stderr, "\n");
328 	va_end(ap);
329 	return (0);
330 }
331 
332 int
333 kw_cmp(const void *k, const void *e)
334 {
335 	return (strcmp(k, ((const struct keywords *)e)->k_name));
336 }
337 
338 int
339 lookup(char *s)
340 {
341 	/* this has to be sorted always */
342 	static const struct keywords keywords[] = {
343 		{ "attribute",		ATTRIBUTE },
344 		{ "basedn",		BASEDN },
345 		{ "bindcred",		BINDCRED },
346 		{ "binddn",		BINDDN },
347 		{ "change",		CHANGE },
348 		{ "class",		CLASS },
349 		{ "directory",		DIRECTORY },
350 		{ "domain",		DOMAIN },
351 		{ "expire",		EXPIRE },
352 		{ "filter",		FILTER },
353 		{ "fixed",		FIXED },
354 		{ "gecos",		GECOS },
355 		{ "gid",		GID },
356 		{ "group",		GROUP },
357 		{ "groupgid",		GROUPGID },
358 		{ "groupmembers",	GROUPMEMBERS },
359 		{ "groupname",		GROUPNAME },
360 		{ "grouppasswd",	GROUPPASSWD },
361 		{ "home",		HOME },
362 		{ "include",		INCLUDE },
363 		{ "interval",		INTERVAL },
364 		{ "list",		LIST },
365 		{ "map",		MAP },
366 		{ "maps",		MAPS },
367 		{ "name",		NAME },
368 		{ "passwd",		PASSWD },
369 		{ "port",		PORT },
370 		{ "provide",		PROVIDE },
371 		{ "server",		SERVER },
372 		{ "shell",		SHELL },
373 		{ "to",			TO },
374 		{ "uid",		UID },
375 		{ "user",		USER },
376 	};
377 	const struct keywords	*p;
378 
379 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
380 	    sizeof(keywords[0]), kw_cmp);
381 
382 	if (p)
383 		return (p->k_val);
384 	else
385 		return (STRING);
386 }
387 
388 #define MAXPUSHBACK	128
389 
390 char	*parsebuf;
391 int	 parseindex;
392 char	 pushback_buffer[MAXPUSHBACK];
393 int	 pushback_index = 0;
394 
395 int
396 lgetc(int quotec)
397 {
398 	int		c, next;
399 
400 	if (parsebuf) {
401 		/* Read character from the parsebuffer instead of input. */
402 		if (parseindex >= 0) {
403 			c = parsebuf[parseindex++];
404 			if (c != '\0')
405 				return (c);
406 			parsebuf = NULL;
407 		} else
408 			parseindex++;
409 	}
410 
411 	if (pushback_index)
412 		return (pushback_buffer[--pushback_index]);
413 
414 	if (quotec) {
415 		if ((c = getc(file->stream)) == EOF) {
416 			yyerror("reached end of file while parsing "
417 			    "quoted string");
418 			if (file == topfile || popfile() == EOF)
419 				return (EOF);
420 			return (quotec);
421 		}
422 		return (c);
423 	}
424 
425 	while ((c = getc(file->stream)) == '\\') {
426 		next = getc(file->stream);
427 		if (next != '\n') {
428 			c = next;
429 			break;
430 		}
431 		yylval.lineno = file->lineno;
432 		file->lineno++;
433 	}
434 
435 	while (c == EOF) {
436 		if (file == topfile || popfile() == EOF)
437 			return (EOF);
438 		c = getc(file->stream);
439 	}
440 	return (c);
441 }
442 
443 int
444 lungetc(int c)
445 {
446 	if (c == EOF)
447 		return (EOF);
448 	if (parsebuf) {
449 		parseindex--;
450 		if (parseindex >= 0)
451 			return (c);
452 	}
453 	if (pushback_index < MAXPUSHBACK-1)
454 		return (pushback_buffer[pushback_index++] = c);
455 	else
456 		return (EOF);
457 }
458 
459 int
460 findeol(void)
461 {
462 	int	c;
463 
464 	parsebuf = NULL;
465 
466 	/* skip to either EOF or the first real EOL */
467 	while (1) {
468 		if (pushback_index)
469 			c = pushback_buffer[--pushback_index];
470 		else
471 			c = lgetc(0);
472 		if (c == '\n') {
473 			file->lineno++;
474 			break;
475 		}
476 		if (c == EOF)
477 			break;
478 	}
479 	return (ERROR);
480 }
481 
482 int
483 yylex(void)
484 {
485 	char	 buf[8096];
486 	char	*p, *val;
487 	int	 quotec, next, c;
488 	int	 token;
489 
490 top:
491 	p = buf;
492 	while ((c = lgetc(0)) == ' ' || c == '\t')
493 		; /* nothing */
494 
495 	yylval.lineno = file->lineno;
496 	if (c == '#')
497 		while ((c = lgetc(0)) != '\n' && c != EOF)
498 			; /* nothing */
499 	if (c == '$' && parsebuf == NULL) {
500 		while (1) {
501 			if ((c = lgetc(0)) == EOF)
502 				return (0);
503 
504 			if (p + 1 >= buf + sizeof(buf) - 1) {
505 				yyerror("string too long");
506 				return (findeol());
507 			}
508 			if (isalnum(c) || c == '_') {
509 				*p++ = (char)c;
510 				continue;
511 			}
512 			*p = '\0';
513 			lungetc(c);
514 			break;
515 		}
516 		val = symget(buf);
517 		if (val == NULL) {
518 			yyerror("macro '%s' not defined", buf);
519 			return (findeol());
520 		}
521 		parsebuf = val;
522 		parseindex = 0;
523 		goto top;
524 	}
525 
526 	switch (c) {
527 	case '\'':
528 	case '"':
529 		quotec = c;
530 		while (1) {
531 			if ((c = lgetc(quotec)) == EOF)
532 				return (0);
533 			if (c == '\n') {
534 				file->lineno++;
535 				continue;
536 			} else if (c == '\\') {
537 				if ((next = lgetc(quotec)) == EOF)
538 					return (0);
539 				if (next == quotec || c == ' ' || c == '\t')
540 					c = next;
541 				else if (next == '\n') {
542 					file->lineno++;
543 					continue;
544 				} else
545 					lungetc(next);
546 			} else if (c == quotec) {
547 				*p = '\0';
548 				break;
549 			}
550 			if (p + 1 >= buf + sizeof(buf) - 1) {
551 				yyerror("string too long");
552 				return (findeol());
553 			}
554 			*p++ = (char)c;
555 		}
556 		yylval.v.string = strdup(buf);
557 		if (yylval.v.string == NULL)
558 			err(1, "yylex: strdup");
559 		return (STRING);
560 	}
561 
562 #define allowed_to_end_number(x) \
563 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
564 
565 	if (c == '-' || isdigit(c)) {
566 		do {
567 			*p++ = c;
568 			if ((unsigned)(p-buf) >= sizeof(buf)) {
569 				yyerror("string too long");
570 				return (findeol());
571 			}
572 		} while ((c = lgetc(0)) != EOF && isdigit(c));
573 		lungetc(c);
574 		if (p == buf + 1 && buf[0] == '-')
575 			goto nodigits;
576 		if (c == EOF || allowed_to_end_number(c)) {
577 			const char *errstr = NULL;
578 
579 			*p = '\0';
580 			yylval.v.number = strtonum(buf, LLONG_MIN,
581 			    LLONG_MAX, &errstr);
582 			if (errstr) {
583 				yyerror("\"%s\" invalid number: %s",
584 				    buf, errstr);
585 				return (findeol());
586 			}
587 			return (NUMBER);
588 		} else {
589 nodigits:
590 			while (p > buf + 1)
591 				lungetc(*--p);
592 			c = *--p;
593 			if (c == '-')
594 				return (c);
595 		}
596 	}
597 
598 #define allowed_in_string(x) \
599 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
600 	x != '{' && x != '}' && x != '<' && x != '>' && \
601 	x != '!' && x != '=' && x != '#' && \
602 	x != ','))
603 
604 	if (isalnum(c) || c == ':' || c == '_') {
605 		do {
606 			*p++ = c;
607 			if ((unsigned)(p-buf) >= sizeof(buf)) {
608 				yyerror("string too long");
609 				return (findeol());
610 			}
611 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
612 		lungetc(c);
613 		*p = '\0';
614 		if ((token = lookup(buf)) == STRING)
615 			if ((yylval.v.string = strdup(buf)) == NULL)
616 				err(1, "yylex: strdup");
617 		return (token);
618 	}
619 	if (c == '\n') {
620 		yylval.lineno = file->lineno;
621 		file->lineno++;
622 	}
623 	if (c == EOF)
624 		return (0);
625 	return (c);
626 }
627 
628 int
629 check_file_secrecy(int fd, const char *fname)
630 {
631 	struct stat	st;
632 
633 	if (fstat(fd, &st)) {
634 		log_warn("cannot stat %s", fname);
635 		return (-1);
636 	}
637 	if (st.st_uid != 0 && st.st_uid != getuid()) {
638 		log_warnx("%s: owner not root or current user", fname);
639 		return (-1);
640 	}
641 	if (st.st_mode & (S_IRWXG | S_IRWXO)) {
642 		log_warnx("%s: group/world readable/writeable", fname);
643 		return (-1);
644 	}
645 	return (0);
646 }
647 
648 struct file *
649 pushfile(const char *name, int secret)
650 {
651 	struct file	*nfile;
652 
653 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
654 		log_warn("malloc");
655 		return (NULL);
656 	}
657 	if ((nfile->name = strdup(name)) == NULL) {
658 		log_warn("malloc");
659 		free(nfile);
660 		return (NULL);
661 	}
662 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
663 		log_warn("%s", nfile->name);
664 		free(nfile->name);
665 		free(nfile);
666 		return (NULL);
667 	} else if (secret &&
668 	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
669 		fclose(nfile->stream);
670 		free(nfile->name);
671 		free(nfile);
672 		return (NULL);
673 	}
674 	nfile->lineno = 1;
675 	TAILQ_INSERT_TAIL(&files, nfile, entry);
676 	return (nfile);
677 }
678 
679 int
680 popfile(void)
681 {
682 	struct file	*prev;
683 
684 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
685 		prev->errors += file->errors;
686 
687 	TAILQ_REMOVE(&files, file, entry);
688 	fclose(file->stream);
689 	free(file->name);
690 	free(file);
691 	file = prev;
692 	return (file ? 0 : EOF);
693 }
694 
695 int
696 parse_config(struct env *x_conf, const char *filename, int opts)
697 {
698 	struct sym	*sym, *next;
699 
700 	conf = x_conf;
701 	bzero(conf, sizeof(*conf));
702 
703 	TAILQ_INIT(&conf->sc_idms);
704 	conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL;
705 	conf->sc_conf_tv.tv_usec = 0;
706 
707 	errors = 0;
708 
709 	if ((file = pushfile(filename, 1)) == NULL) {
710 		return (-1);
711 	}
712 	topfile = file;
713 
714 	/*
715 	 * parse configuration
716 	 */
717 	setservent(1);
718 	yyparse();
719 	endservent();
720 	errors = file->errors;
721 	popfile();
722 
723 	/* Free macros and check which have not been used. */
724 	for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
725 		next = TAILQ_NEXT(sym, entry);
726 		if ((opts & YPLDAP_OPT_VERBOSE) && !sym->used)
727 			fprintf(stderr, "warning: macro '%s' not "
728 			    "used\n", sym->nam);
729 		if (!sym->persist) {
730 			free(sym->nam);
731 			free(sym->val);
732 			TAILQ_REMOVE(&symhead, sym, entry);
733 			free(sym);
734 		}
735 	}
736 
737 	if (errors) {
738 		return (-1);
739 	}
740 
741 	return (0);
742 }
743 
744 int
745 symset(const char *nam, const char *val, int persist)
746 {
747 	struct sym	*sym;
748 
749 	for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
750 	    sym = TAILQ_NEXT(sym, entry))
751 		;	/* nothing */
752 
753 	if (sym != NULL) {
754 		if (sym->persist == 1)
755 			return (0);
756 		else {
757 			free(sym->nam);
758 			free(sym->val);
759 			TAILQ_REMOVE(&symhead, sym, entry);
760 			free(sym);
761 		}
762 	}
763 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
764 		return (-1);
765 
766 	sym->nam = strdup(nam);
767 	if (sym->nam == NULL) {
768 		free(sym);
769 		return (-1);
770 	}
771 	sym->val = strdup(val);
772 	if (sym->val == NULL) {
773 		free(sym->nam);
774 		free(sym);
775 		return (-1);
776 	}
777 	sym->used = 0;
778 	sym->persist = persist;
779 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
780 	return (0);
781 }
782 
783 int
784 cmdline_symset(char *s)
785 {
786 	char	*sym, *val;
787 	int	ret;
788 	size_t	len;
789 
790 	if ((val = strrchr(s, '=')) == NULL)
791 		return (-1);
792 
793 	len = strlen(s) - strlen(val) + 1;
794 	if ((sym = malloc(len)) == NULL)
795 		errx(1, "cmdline_symset: malloc");
796 
797 	(void)strlcpy(sym, s, len);
798 
799 	ret = symset(sym, val + 1, 1);
800 	free(sym);
801 
802 	return (ret);
803 }
804 
805 char *
806 symget(const char *nam)
807 {
808 	struct sym	*sym;
809 
810 	TAILQ_FOREACH(sym, &symhead, entry)
811 		if (strcmp(nam, sym->nam) == 0) {
812 			sym->used = 1;
813 			return (sym->val);
814 		}
815 	return (NULL);
816 }
817