]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/ypldap/parse.y
MFV: file 5.45.
[FreeBSD/FreeBSD.git] / usr.sbin / ypldap / parse.y
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
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
358 kw_cmp(const void *k, const void *e)
359 {
360         return (strcmp(k, ((const struct keywords *)e)->k_name));
361 }
362
363 int
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
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
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
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
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
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 *
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
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
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
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
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 *
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 }