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