]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/ypldap/parse.y
MFV r299451: 6764 zfs issues with inheritance flags during chmod(2) with
[FreeBSD/FreeBSD.git] / usr.sbin / ypldap / parse.y
1 /*      $OpenBSD: parse.y,v 1.18 2015/01/16 06:40:22 deraadt Exp $      */
2 /*      $FreeBSD$ */
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
56 TAILQ_HEAD(files, file)          files = TAILQ_HEAD_INITIALIZER(files);
57 static struct file {
58         TAILQ_ENTRY(file)        entry;
59         FILE                    *stream;
60         char                    *name;
61         int                      lineno;
62         int                      errors;
63 } *file, *topfile;
64 struct file     *pushfile(const char *, int);
65 int              popfile(void);
66 int              check_file_secrecy(int, const char *);
67 int              yyparse(void);
68 int              yylex(void);
69 int              yyerror(const char *, ...)
70     __attribute__((__format__ (printf, 1, 2)))
71     __attribute__((__nonnull__ (1)));
72 int              kw_cmp(const void *, const void *);
73 int              lookup(char *);
74 int              lgetc(int);
75 int              lungetc(int);
76 int              findeol(void);
77
78 TAILQ_HEAD(symhead, sym)         symhead = TAILQ_HEAD_INITIALIZER(symhead);
79 struct sym {
80         TAILQ_ENTRY(sym)         entry;
81         int                      used;
82         int                      persist;
83         char                    *nam;
84         char                    *val;
85 };
86 int              symset(const char *, const char *, int);
87 char            *symget(const char *);
88
89 struct env              *conf = NULL;
90 struct idm              *idm = NULL;
91 static int               errors = 0;
92
93 typedef 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
114 grammar         : /* 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
123 nl              : '\n' optnl
124                 ;
125
126 optnl           : '\n' optnl
127                 | /* empty */
128                 ;
129
130
131 include         : 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
146 varset          : STRING '=' STRING                     {
147                         if (symset($1, $3, 0) == -1)
148                                 fatal("cannot store variable");
149                         free($1);
150                         free($3);
151                 }
152                 ;
153
154 port            : /* empty */   { $$ = NULL; }
155                 | PORT STRING   { $$ = $2; }
156                 ;
157
158 opcode          : GROUP                                 { $$ = 0; }
159                 | PASSWD                                { $$ = 1; }
160                 ;
161
162
163 attribute       : 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
179 diropt          : 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
265 directory       : 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
285 main            : 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
323 diropts         : diropts diropt nl
324                 | diropt optnl
325                 ;
326
327 %%
328
329 struct keywords {
330         const char      *k_name;
331         int              k_val;
332 };
333
334 int
335 yyerror(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
350 int
351 kw_cmp(const void *k, const void *e)
352 {
353         return (strcmp(k, ((const struct keywords *)e)->k_name));
354 }
355
356 int
357 lookup(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
409 u_char  *parsebuf;
410 int      parseindex;
411 u_char   pushback_buffer[MAXPUSHBACK];
412 int      pushback_index = 0;
413
414 int
415 lgetc(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
462 int
463 lungetc(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
478 int
479 findeol(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
501 int
502 yylex(void)
503 {
504         u_char   buf[8096];
505         u_char  *p, *val;
506         int      quotec, next, c;
507         int      token;
508
509 top:
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 {
611 nodigits:
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
650 int
651 check_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
670 struct file *
671 pushfile(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
701 int
702 popfile(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
717 int
718 parse_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
766 int
767 symset(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
805 int
806 cmdline_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
827 char *
828 symget(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 }