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