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
55 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
56 static struct file {
57 TAILQ_ENTRY(file) entry;
58 FILE *stream;
59 char *name;
60 int lineno;
61 int errors;
62 } *file, *topfile;
63 struct file *pushfile(const char *, int);
64 int popfile(void);
65 int check_file_secrecy(int, const char *);
66 int yyparse(void);
67 int yylex(void);
68 int yyerror(const char *, ...)
69 __attribute__((__format__ (printf, 1, 2)))
70 __attribute__((__nonnull__ (1)));
71 int kw_cmp(const void *, const void *);
72 int lookup(char *);
73 int lgetc(int);
74 int lungetc(int);
75 int findeol(void);
76
77 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
78 struct sym {
79 TAILQ_ENTRY(sym) entry;
80 int used;
81 int persist;
82 char *nam;
83 char *val;
84 };
85 int symset(const char *, const char *, int);
86 char *symget(const char *);
87
88 struct env *conf = NULL;
89 struct idm *idm = NULL;
90 static int errors = 0;
91
92 typedef 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
113 grammar : /* 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
122 nl : '\n' optnl
123 ;
124
125 optnl : '\n' optnl
126 | /* empty */
127 ;
128
129
130 include : 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
145 varset : 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
161 port : /* empty */ { $$ = NULL; }
162 | PORT STRING { $$ = $2; }
163 ;
164
165 opcode : GROUP { $$ = 0; }
166 | PASSWD { $$ = 1; }
167 ;
168
169
170 attribute : 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
186 diropt : 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
272 directory : 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
292 main : 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
330 diropts : diropts diropt nl
331 | diropt optnl
332 ;
333
334 %%
335
336 struct keywords {
337 const char *k_name;
338 int k_val;
339 };
340
341 int
yyerror(const char * fmt,...)342 yyerror(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
357 int
kw_cmp(const void * k,const void * e)358 kw_cmp(const void *k, const void *e)
359 {
360 return (strcmp(k, ((const struct keywords *)e)->k_name));
361 }
362
363 int
lookup(char * s)364 lookup(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
416 u_char *parsebuf;
417 int parseindex;
418 u_char pushback_buffer[MAXPUSHBACK];
419 int pushback_index = 0;
420
421 int
lgetc(int quotec)422 lgetc(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
469 int
lungetc(int c)470 lungetc(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
485 int
findeol(void)486 findeol(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
508 int
yylex(void)509 yylex(void)
510 {
511 u_char buf[8096];
512 u_char *p, *val;
513 int quotec, next, c;
514 int token;
515
516 top:
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 {
618 nodigits:
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
657 int
check_file_secrecy(int fd,const char * fname)658 check_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
677 struct file *
pushfile(const char * name,int secret)678 pushfile(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
708 int
popfile(void)709 popfile(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
724 int
parse_config(struct env * x_conf,const char * filename,int opts)725 parse_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
773 int
symset(const char * nam,const char * val,int persist)774 symset(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
812 int
cmdline_symset(char * s)813 cmdline_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
834 char *
symget(const char * nam)835 symget(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