parse.y revision 290937
1/*	$OpenBSD: parse.y,v 1.18 2015/01/16 06:40:22 deraadt Exp $	*/
2/*	$FreeBSD: head/usr.sbin/ypldap/parse.y 290937 2015-11-16 17:06:33Z rodrigc $ */
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			if (symset($1, $3, 0) == -1)
148				fatal("cannot store variable");
149			free($1);
150			free($3);
151		}
152		;
153
154port		: /* empty */	{ $$ = NULL; }
155		| PORT STRING	{ $$ = $2; }
156		;
157
158opcode		: GROUP					{ $$ = 0; }
159		| PASSWD				{ $$ = 1; }
160		;
161
162
163attribute	: NAME					{ $$ = 0; }
164		| PASSWD				{ $$ = 1; }
165		| UID					{ $$ = 2; }
166		| GID					{ $$ = 3; }
167		| CLASS					{ $$ = 4; }
168		| CHANGE				{ $$ = 5; }
169		| EXPIRE				{ $$ = 6; }
170		| GECOS					{ $$ = 7; }
171		| HOME					{ $$ = 8; }
172		| SHELL					{ $$ = 9; }
173		| GROUPNAME				{ $$ = 10; }
174		| GROUPPASSWD				{ $$ = 11; }
175		| GROUPGID				{ $$ = 12; }
176		| GROUPMEMBERS				{ $$ = 13; }
177		;
178
179diropt		: BINDDN STRING				{
180			idm->idm_flags |= F_NEEDAUTH;
181			if (strlcpy(idm->idm_binddn, $2,
182			    sizeof(idm->idm_binddn)) >=
183			    sizeof(idm->idm_binddn)) {
184				yyerror("directory binddn truncated");
185				free($2);
186				YYERROR;
187			}
188			free($2);
189		}
190		| BINDCRED STRING			{
191			idm->idm_flags |= F_NEEDAUTH;
192			if (strlcpy(idm->idm_bindcred, $2,
193			    sizeof(idm->idm_bindcred)) >=
194			    sizeof(idm->idm_bindcred)) {
195				yyerror("directory bindcred truncated");
196				free($2);
197				YYERROR;
198			}
199			free($2);
200		}
201		| BASEDN STRING			{
202			if (strlcpy(idm->idm_basedn, $2,
203			    sizeof(idm->idm_basedn)) >=
204			    sizeof(idm->idm_basedn)) {
205				yyerror("directory basedn truncated");
206				free($2);
207				YYERROR;
208			}
209			free($2);
210		}
211		| GROUPDN STRING		{
212			if(strlcpy(idm->idm_groupdn, $2,
213			    sizeof(idm->idm_groupdn)) >=
214			    sizeof(idm->idm_groupdn)) {
215				yyerror("directory groupdn truncated");
216				free($2);
217				YYERROR;
218			}
219			free($2);
220		}
221		| opcode FILTER STRING			{
222			if (strlcpy(idm->idm_filters[$1], $3,
223			    sizeof(idm->idm_filters[$1])) >=
224			    sizeof(idm->idm_filters[$1])) {
225				yyerror("filter truncated");
226				free($3);
227				YYERROR;
228			}
229			free($3);
230		}
231		| ATTRIBUTE attribute MAPS TO STRING	{
232			if (strlcpy(idm->idm_attrs[$2], $5,
233			    sizeof(idm->idm_attrs[$2])) >=
234			    sizeof(idm->idm_attrs[$2])) {
235				yyerror("attribute truncated");
236				free($5);
237				YYERROR;
238			}
239			free($5);
240		}
241		| FIXED ATTRIBUTE attribute STRING	{
242			if (strlcpy(idm->idm_attrs[$3], $4,
243			    sizeof(idm->idm_attrs[$3])) >=
244			    sizeof(idm->idm_attrs[$3])) {
245				yyerror("attribute truncated");
246				free($4);
247				YYERROR;
248			}
249			idm->idm_flags |= F_FIXED_ATTR($3);
250			free($4);
251		}
252		| LIST attribute MAPS TO STRING	{
253			if (strlcpy(idm->idm_attrs[$2], $5,
254			    sizeof(idm->idm_attrs[$2])) >=
255			    sizeof(idm->idm_attrs[$2])) {
256				yyerror("attribute truncated");
257				free($5);
258				YYERROR;
259			}
260			idm->idm_list |= F_LIST($2);
261			free($5);
262		}
263		;
264
265directory	: DIRECTORY STRING port {
266			if ((idm = calloc(1, sizeof(*idm))) == NULL)
267				fatal(NULL);
268			idm->idm_id = conf->sc_maxid++;
269
270			if (strlcpy(idm->idm_name, $2,
271			    sizeof(idm->idm_name)) >=
272			    sizeof(idm->idm_name)) {
273				yyerror("attribute truncated");
274				free($2);
275				YYERROR;
276			}
277
278			free($2);
279		} '{' optnl diropts '}'			{
280			TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry);
281			idm = NULL;
282		}
283		;
284
285main		: INTERVAL NUMBER			{
286			conf->sc_conf_tv.tv_sec = $2;
287			conf->sc_conf_tv.tv_usec = 0;
288		}
289		| DOMAIN STRING				{
290			if (strlcpy(conf->sc_domainname, $2,
291			    sizeof(conf->sc_domainname)) >=
292			    sizeof(conf->sc_domainname)) {
293				yyerror("domainname truncated");
294				free($2);
295				YYERROR;
296			}
297			free($2);
298		}
299		| PROVIDE MAP STRING			{
300			if (strcmp($3, "passwd.byname") == 0)
301				conf->sc_flags |= YPMAP_PASSWD_BYNAME;
302			else if (strcmp($3, "passwd.byuid") == 0)
303				conf->sc_flags |= YPMAP_PASSWD_BYUID;
304			else if (strcmp($3, "master.passwd.byname") == 0)
305				conf->sc_flags |= YPMAP_MASTER_PASSWD_BYNAME;
306			else if (strcmp($3, "master.passwd.byuid") == 0)
307				conf->sc_flags |= YPMAP_MASTER_PASSWD_BYUID;
308			else if (strcmp($3, "group.byname") == 0)
309				conf->sc_flags |= YPMAP_GROUP_BYNAME;
310			else if (strcmp($3, "group.bygid") == 0)
311				conf->sc_flags |= YPMAP_GROUP_BYGID;
312			else if (strcmp($3, "netid.byname") == 0)
313				conf->sc_flags |= YPMAP_NETID_BYNAME;
314			else {
315				yyerror("unsupported map type: %s", $3);
316				free($3);
317				YYERROR;
318			}
319			free($3);
320		}
321		;
322
323diropts		: diropts diropt nl
324		| diropt optnl
325		;
326
327%%
328
329struct keywords {
330	const char	*k_name;
331	int		 k_val;
332};
333
334int
335yyerror(const char *fmt, ...)
336{
337	va_list		 ap;
338	char		*msg;
339
340	file->errors++;
341	va_start(ap, fmt);
342	if (vasprintf(&msg, fmt, ap) == -1)
343		fatalx("yyerror vasprintf");
344	va_end(ap);
345	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
346	free(msg);
347	return (0);
348}
349
350int
351kw_cmp(const void *k, const void *e)
352{
353	return (strcmp(k, ((const struct keywords *)e)->k_name));
354}
355
356int
357lookup(char *s)
358{
359	/* this has to be sorted always */
360	static const struct keywords keywords[] = {
361		{ "attribute",		ATTRIBUTE },
362		{ "basedn",		BASEDN },
363		{ "bindcred",		BINDCRED },
364		{ "binddn",		BINDDN },
365		{ "change",		CHANGE },
366		{ "class",		CLASS },
367		{ "directory",		DIRECTORY },
368		{ "domain",		DOMAIN },
369		{ "expire",		EXPIRE },
370		{ "filter",		FILTER },
371		{ "fixed",		FIXED },
372		{ "gecos",		GECOS },
373		{ "gid",		GID },
374		{ "group",		GROUP },
375		{ "groupdn",		GROUPDN },
376		{ "groupgid",		GROUPGID },
377		{ "groupmembers",	GROUPMEMBERS },
378		{ "groupname",		GROUPNAME },
379		{ "grouppasswd",	GROUPPASSWD },
380		{ "home",		HOME },
381		{ "include",		INCLUDE },
382		{ "interval",		INTERVAL },
383		{ "list",		LIST },
384		{ "map",		MAP },
385		{ "maps",		MAPS },
386		{ "name",		NAME },
387		{ "passwd",		PASSWD },
388		{ "port",		PORT },
389		{ "provide",		PROVIDE },
390		{ "server",		SERVER },
391		{ "shell",		SHELL },
392		{ "to",			TO },
393		{ "uid",		UID },
394		{ "user",		USER },
395	};
396	const struct keywords	*p;
397
398	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
399	    sizeof(keywords[0]), kw_cmp);
400
401	if (p)
402		return (p->k_val);
403	else
404		return (STRING);
405}
406
407#define MAXPUSHBACK	128
408
409u_char	*parsebuf;
410int	 parseindex;
411u_char	 pushback_buffer[MAXPUSHBACK];
412int	 pushback_index = 0;
413
414int
415lgetc(int quotec)
416{
417	int		c, next;
418
419	if (parsebuf) {
420		/* Read character from the parsebuffer instead of input. */
421		if (parseindex >= 0) {
422			c = parsebuf[parseindex++];
423			if (c != '\0')
424				return (c);
425			parsebuf = NULL;
426		} else
427			parseindex++;
428	}
429
430	if (pushback_index)
431		return (pushback_buffer[--pushback_index]);
432
433	if (quotec) {
434		if ((c = getc(file->stream)) == EOF) {
435			yyerror("reached end of file while parsing "
436			    "quoted string");
437			if (file == topfile || popfile() == EOF)
438				return (EOF);
439			return (quotec);
440		}
441		return (c);
442	}
443
444	while ((c = getc(file->stream)) == '\\') {
445		next = getc(file->stream);
446		if (next != '\n') {
447			c = next;
448			break;
449		}
450		yylval.lineno = file->lineno;
451		file->lineno++;
452	}
453
454	while (c == EOF) {
455		if (file == topfile || popfile() == EOF)
456			return (EOF);
457		c = getc(file->stream);
458	}
459	return (c);
460}
461
462int
463lungetc(int c)
464{
465	if (c == EOF)
466		return (EOF);
467	if (parsebuf) {
468		parseindex--;
469		if (parseindex >= 0)
470			return (c);
471	}
472	if (pushback_index < MAXPUSHBACK-1)
473		return (pushback_buffer[pushback_index++] = c);
474	else
475		return (EOF);
476}
477
478int
479findeol(void)
480{
481	int	c;
482
483	parsebuf = NULL;
484
485	/* skip to either EOF or the first real EOL */
486	while (1) {
487		if (pushback_index)
488			c = pushback_buffer[--pushback_index];
489		else
490			c = lgetc(0);
491		if (c == '\n') {
492			file->lineno++;
493			break;
494		}
495		if (c == EOF)
496			break;
497	}
498	return (ERROR);
499}
500
501int
502yylex(void)
503{
504	u_char	 buf[8096];
505	u_char	*p, *val;
506	int	 quotec, next, c;
507	int	 token;
508
509top:
510	p = buf;
511	while ((c = lgetc(0)) == ' ' || c == '\t')
512		; /* nothing */
513
514	yylval.lineno = file->lineno;
515	if (c == '#')
516		while ((c = lgetc(0)) != '\n' && c != EOF)
517			; /* nothing */
518	if (c == '$' && parsebuf == NULL) {
519		while (1) {
520			if ((c = lgetc(0)) == EOF)
521				return (0);
522
523			if (p + 1 >= buf + sizeof(buf) - 1) {
524				yyerror("string too long");
525				return (findeol());
526			}
527			if (isalnum(c) || c == '_') {
528				*p++ = c;
529				continue;
530			}
531			*p = '\0';
532			lungetc(c);
533			break;
534		}
535		val = symget(buf);
536		if (val == NULL) {
537			yyerror("macro '%s' not defined", buf);
538			return (findeol());
539		}
540		parsebuf = val;
541		parseindex = 0;
542		goto top;
543	}
544
545	switch (c) {
546	case '\'':
547	case '"':
548		quotec = c;
549		while (1) {
550			if ((c = lgetc(quotec)) == EOF)
551				return (0);
552			if (c == '\n') {
553				file->lineno++;
554				continue;
555			} else if (c == '\\') {
556				if ((next = lgetc(quotec)) == EOF)
557					return (0);
558				if (next == quotec || c == ' ' || c == '\t')
559					c = next;
560				else if (next == '\n') {
561					file->lineno++;
562					continue;
563				} else
564					lungetc(next);
565			} else if (c == quotec) {
566				*p = '\0';
567				break;
568			} else if (c == '\0') {
569				yyerror("syntax error");
570				return (findeol());
571			}
572			if (p + 1 >= buf + sizeof(buf) - 1) {
573				yyerror("string too long");
574				return (findeol());
575			}
576			*p++ = c;
577		}
578		yylval.v.string = strdup(buf);
579		if (yylval.v.string == NULL)
580			err(1, "yylex: strdup");
581		return (STRING);
582	}
583
584#define allowed_to_end_number(x) \
585	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
586
587	if (c == '-' || isdigit(c)) {
588		do {
589			*p++ = c;
590			if ((unsigned)(p-buf) >= sizeof(buf)) {
591				yyerror("string too long");
592				return (findeol());
593			}
594		} while ((c = lgetc(0)) != EOF && isdigit(c));
595		lungetc(c);
596		if (p == buf + 1 && buf[0] == '-')
597			goto nodigits;
598		if (c == EOF || allowed_to_end_number(c)) {
599			const char *errstr = NULL;
600
601			*p = '\0';
602			yylval.v.number = strtonum(buf, LLONG_MIN,
603			    LLONG_MAX, &errstr);
604			if (errstr) {
605				yyerror("\"%s\" invalid number: %s",
606				    buf, errstr);
607				return (findeol());
608			}
609			return (NUMBER);
610		} else {
611nodigits:
612			while (p > buf + 1)
613				lungetc(*--p);
614			c = *--p;
615			if (c == '-')
616				return (c);
617		}
618	}
619
620#define allowed_in_string(x) \
621	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
622	x != '{' && x != '}' && x != '<' && x != '>' && \
623	x != '!' && x != '=' && x != '#' && \
624	x != ','))
625
626	if (isalnum(c) || c == ':' || c == '_') {
627		do {
628			*p++ = c;
629			if ((unsigned)(p-buf) >= sizeof(buf)) {
630				yyerror("string too long");
631				return (findeol());
632			}
633		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
634		lungetc(c);
635		*p = '\0';
636		if ((token = lookup(buf)) == STRING)
637			if ((yylval.v.string = strdup(buf)) == NULL)
638				err(1, "yylex: strdup");
639		return (token);
640	}
641	if (c == '\n') {
642		yylval.lineno = file->lineno;
643		file->lineno++;
644	}
645	if (c == EOF)
646		return (0);
647	return (c);
648}
649
650int
651check_file_secrecy(int fd, const char *fname)
652{
653	struct stat	st;
654
655	if (fstat(fd, &st)) {
656		log_warn("cannot stat %s", fname);
657		return (-1);
658	}
659	if (st.st_uid != 0 && st.st_uid != getuid()) {
660		log_warnx("%s: owner not root or current user", fname);
661		return (-1);
662	}
663	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
664		log_warnx("%s: group writable or world read/writable", fname);
665		return (-1);
666	}
667	return (0);
668}
669
670struct file *
671pushfile(const char *name, int secret)
672{
673	struct file	*nfile;
674
675	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
676		log_warn("malloc");
677		return (NULL);
678	}
679	if ((nfile->name = strdup(name)) == NULL) {
680		log_warn("malloc");
681		free(nfile);
682		return (NULL);
683	}
684	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
685		log_warn("%s", nfile->name);
686		free(nfile->name);
687		free(nfile);
688		return (NULL);
689	} else if (secret &&
690	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
691		fclose(nfile->stream);
692		free(nfile->name);
693		free(nfile);
694		return (NULL);
695	}
696	nfile->lineno = 1;
697	TAILQ_INSERT_TAIL(&files, nfile, entry);
698	return (nfile);
699}
700
701int
702popfile(void)
703{
704	struct file	*prev;
705
706	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
707		prev->errors += file->errors;
708
709	TAILQ_REMOVE(&files, file, entry);
710	fclose(file->stream);
711	free(file->name);
712	free(file);
713	file = prev;
714	return (file ? 0 : EOF);
715}
716
717int
718parse_config(struct env *x_conf, const char *filename, int opts)
719{
720	struct sym	*sym, *next;
721
722	conf = x_conf;
723	bzero(conf, sizeof(*conf));
724
725	TAILQ_INIT(&conf->sc_idms);
726	conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL;
727	conf->sc_conf_tv.tv_usec = 0;
728
729	errors = 0;
730
731	if ((file = pushfile(filename, 1)) == NULL) {
732		return (-1);
733	}
734	topfile = file;
735
736	/*
737	 * parse configuration
738	 */
739	setservent(1);
740	yyparse();
741	endservent();
742	errors = file->errors;
743	popfile();
744
745	/* Free macros and check which have not been used. */
746	for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
747		next = TAILQ_NEXT(sym, entry);
748		if ((opts & YPLDAP_OPT_VERBOSE) && !sym->used)
749			fprintf(stderr, "warning: macro '%s' not "
750			    "used\n", sym->nam);
751		if (!sym->persist) {
752			free(sym->nam);
753			free(sym->val);
754			TAILQ_REMOVE(&symhead, sym, entry);
755			free(sym);
756		}
757	}
758
759	if (errors) {
760		return (-1);
761	}
762
763	return (0);
764}
765
766int
767symset(const char *nam, const char *val, int persist)
768{
769	struct sym	*sym;
770
771	for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
772	    sym = TAILQ_NEXT(sym, entry))
773		;	/* nothing */
774
775	if (sym != NULL) {
776		if (sym->persist == 1)
777			return (0);
778		else {
779			free(sym->nam);
780			free(sym->val);
781			TAILQ_REMOVE(&symhead, sym, entry);
782			free(sym);
783		}
784	}
785	if ((sym = calloc(1, sizeof(*sym))) == NULL)
786		return (-1);
787
788	sym->nam = strdup(nam);
789	if (sym->nam == NULL) {
790		free(sym);
791		return (-1);
792	}
793	sym->val = strdup(val);
794	if (sym->val == NULL) {
795		free(sym->nam);
796		free(sym);
797		return (-1);
798	}
799	sym->used = 0;
800	sym->persist = persist;
801	TAILQ_INSERT_TAIL(&symhead, sym, entry);
802	return (0);
803}
804
805int
806cmdline_symset(char *s)
807{
808	char	*sym, *val;
809	int	ret;
810	size_t	len;
811
812	if ((val = strrchr(s, '=')) == NULL)
813		return (-1);
814
815	len = strlen(s) - strlen(val) + 1;
816	if ((sym = malloc(len)) == NULL)
817		errx(1, "cmdline_symset: malloc");
818
819	(void)strlcpy(sym, s, len);
820
821	ret = symset(sym, val + 1, 1);
822	free(sym);
823
824	return (ret);
825}
826
827char *
828symget(const char *nam)
829{
830	struct sym	*sym;
831
832	TAILQ_FOREACH(sym, &symhead, entry)
833		if (strcmp(nam, sym->nam) == 0) {
834			sym->used = 1;
835			return (sym->val);
836		}
837	return (NULL);
838}
839