1 /* $OpenBSD: parse.y,v 1.18 2015/01/16 06:40:22 deraadt Exp $ */
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.
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.
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.
28 #include <sys/types.h>
29 #include <sys/param.h>
31 #include <sys/queue.h>
33 #include <sys/socket.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
56 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
58 TAILQ_ENTRY(file) entry;
64 struct file *pushfile(const char *, int);
66 int check_file_secrecy(int, const char *);
69 int yyerror(const char *, ...)
70 __attribute__((__format__ (printf, 1, 2)))
71 __attribute__((__nonnull__ (1)));
72 int kw_cmp(const void *, const void *);
78 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
80 TAILQ_ENTRY(sym) entry;
86 int symset(const char *, const char *, int);
87 char *symget(const char *);
89 struct env *conf = NULL;
90 struct idm *idm = NULL;
91 static int errors = 0;
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
114 grammar : /* empty */
116 | grammar include '\n'
117 | grammar varset '\n'
118 | grammar directory '\n'
120 | grammar error '\n' { file->errors++; }
131 include : INCLUDE STRING {
134 if ((nfile = pushfile($2, 0)) == NULL) {
135 yyerror("failed to include file %s", $2);
146 varset : STRING '=' STRING {
149 if (isspace((unsigned char) *s)) {
150 yyerror("macro name cannot contain "
155 if (symset($1, $3, 0) == -1)
156 fatal("cannot store variable");
162 port : /* empty */ { $$ = NULL; }
163 | PORT STRING { $$ = $2; }
166 opcode : GROUP { $$ = 0; }
171 attribute : NAME { $$ = 0; }
181 | GROUPNAME { $$ = 10; }
182 | GROUPPASSWD { $$ = 11; }
183 | GROUPGID { $$ = 12; }
184 | GROUPMEMBERS { $$ = 13; }
187 diropt : BINDDN STRING {
188 idm->idm_flags |= F_NEEDAUTH;
189 if (strlcpy(idm->idm_binddn, $2,
190 sizeof(idm->idm_binddn)) >=
191 sizeof(idm->idm_binddn)) {
192 yyerror("directory binddn truncated");
199 idm->idm_flags |= F_NEEDAUTH;
200 if (strlcpy(idm->idm_bindcred, $2,
201 sizeof(idm->idm_bindcred)) >=
202 sizeof(idm->idm_bindcred)) {
203 yyerror("directory bindcred truncated");
210 if (strlcpy(idm->idm_basedn, $2,
211 sizeof(idm->idm_basedn)) >=
212 sizeof(idm->idm_basedn)) {
213 yyerror("directory basedn truncated");
220 if(strlcpy(idm->idm_groupdn, $2,
221 sizeof(idm->idm_groupdn)) >=
222 sizeof(idm->idm_groupdn)) {
223 yyerror("directory groupdn truncated");
229 | opcode FILTER STRING {
230 if (strlcpy(idm->idm_filters[$1], $3,
231 sizeof(idm->idm_filters[$1])) >=
232 sizeof(idm->idm_filters[$1])) {
233 yyerror("filter truncated");
239 | ATTRIBUTE attribute MAPS TO STRING {
240 if (strlcpy(idm->idm_attrs[$2], $5,
241 sizeof(idm->idm_attrs[$2])) >=
242 sizeof(idm->idm_attrs[$2])) {
243 yyerror("attribute truncated");
249 | FIXED ATTRIBUTE attribute STRING {
250 if (strlcpy(idm->idm_attrs[$3], $4,
251 sizeof(idm->idm_attrs[$3])) >=
252 sizeof(idm->idm_attrs[$3])) {
253 yyerror("attribute truncated");
257 idm->idm_flags |= F_FIXED_ATTR($3);
260 | LIST attribute MAPS TO STRING {
261 if (strlcpy(idm->idm_attrs[$2], $5,
262 sizeof(idm->idm_attrs[$2])) >=
263 sizeof(idm->idm_attrs[$2])) {
264 yyerror("attribute truncated");
268 idm->idm_list |= F_LIST($2);
273 directory : DIRECTORY STRING port {
274 if ((idm = calloc(1, sizeof(*idm))) == NULL)
276 idm->idm_id = conf->sc_maxid++;
278 if (strlcpy(idm->idm_name, $2,
279 sizeof(idm->idm_name)) >=
280 sizeof(idm->idm_name)) {
281 yyerror("attribute truncated");
287 } '{' optnl diropts '}' {
288 TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry);
293 main : INTERVAL NUMBER {
294 conf->sc_conf_tv.tv_sec = $2;
295 conf->sc_conf_tv.tv_usec = 0;
298 if (strlcpy(conf->sc_domainname, $2,
299 sizeof(conf->sc_domainname)) >=
300 sizeof(conf->sc_domainname)) {
301 yyerror("domainname truncated");
307 | PROVIDE MAP STRING {
308 if (strcmp($3, "passwd.byname") == 0)
309 conf->sc_flags |= YPMAP_PASSWD_BYNAME;
310 else if (strcmp($3, "passwd.byuid") == 0)
311 conf->sc_flags |= YPMAP_PASSWD_BYUID;
312 else if (strcmp($3, "master.passwd.byname") == 0)
313 conf->sc_flags |= YPMAP_MASTER_PASSWD_BYNAME;
314 else if (strcmp($3, "master.passwd.byuid") == 0)
315 conf->sc_flags |= YPMAP_MASTER_PASSWD_BYUID;
316 else if (strcmp($3, "group.byname") == 0)
317 conf->sc_flags |= YPMAP_GROUP_BYNAME;
318 else if (strcmp($3, "group.bygid") == 0)
319 conf->sc_flags |= YPMAP_GROUP_BYGID;
320 else if (strcmp($3, "netid.byname") == 0)
321 conf->sc_flags |= YPMAP_NETID_BYNAME;
323 yyerror("unsupported map type: %s", $3);
331 diropts : diropts diropt nl
343 yyerror(const char *fmt, ...)
350 if (vasprintf(&msg, fmt, ap) == -1)
351 fatalx("yyerror vasprintf");
353 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
359 kw_cmp(const void *k, const void *e)
361 return (strcmp(k, ((const struct keywords *)e)->k_name));
367 /* this has to be sorted always */
368 static const struct keywords keywords[] = {
369 { "attribute", ATTRIBUTE },
370 { "basedn", BASEDN },
371 { "bindcred", BINDCRED },
372 { "binddn", BINDDN },
373 { "change", CHANGE },
375 { "directory", DIRECTORY },
376 { "domain", DOMAIN },
377 { "expire", EXPIRE },
378 { "filter", FILTER },
383 { "groupdn", GROUPDN },
384 { "groupgid", GROUPGID },
385 { "groupmembers", GROUPMEMBERS },
386 { "groupname", GROUPNAME },
387 { "grouppasswd", GROUPPASSWD },
389 { "include", INCLUDE },
390 { "interval", INTERVAL },
395 { "passwd", PASSWD },
397 { "provide", PROVIDE },
398 { "server", SERVER },
404 const struct keywords *p;
406 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
407 sizeof(keywords[0]), kw_cmp);
415 #define MAXPUSHBACK 128
419 u_char pushback_buffer[MAXPUSHBACK];
420 int pushback_index = 0;
428 /* Read character from the parsebuffer instead of input. */
429 if (parseindex >= 0) {
430 c = parsebuf[parseindex++];
439 return (pushback_buffer[--pushback_index]);
442 if ((c = getc(file->stream)) == EOF) {
443 yyerror("reached end of file while parsing "
445 if (file == topfile || popfile() == EOF)
452 while ((c = getc(file->stream)) == '\\') {
453 next = getc(file->stream);
458 yylval.lineno = file->lineno;
463 if (file == topfile || popfile() == EOF)
465 c = getc(file->stream);
480 if (pushback_index < MAXPUSHBACK-1)
481 return (pushback_buffer[pushback_index++] = c);
493 /* skip to either EOF or the first real EOL */
496 c = pushback_buffer[--pushback_index];
519 while ((c = lgetc(0)) == ' ' || c == '\t')
522 yylval.lineno = file->lineno;
524 while ((c = lgetc(0)) != '\n' && c != EOF)
526 if (c == '$' && parsebuf == NULL) {
528 if ((c = lgetc(0)) == EOF)
531 if (p + 1 >= buf + sizeof(buf) - 1) {
532 yyerror("string too long");
535 if (isalnum(c) || c == '_') {
545 yyerror("macro '%s' not defined", buf);
558 if ((c = lgetc(quotec)) == EOF)
563 } else if (c == '\\') {
564 if ((next = lgetc(quotec)) == EOF)
566 if (next == quotec || c == ' ' || c == '\t')
568 else if (next == '\n') {
573 } else if (c == quotec) {
576 } else if (c == '\0') {
577 yyerror("syntax error");
580 if (p + 1 >= buf + sizeof(buf) - 1) {
581 yyerror("string too long");
586 yylval.v.string = strdup(buf);
587 if (yylval.v.string == NULL)
588 err(1, "yylex: strdup");
592 #define allowed_to_end_number(x) \
593 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
595 if (c == '-' || isdigit(c)) {
598 if ((unsigned)(p-buf) >= sizeof(buf)) {
599 yyerror("string too long");
602 } while ((c = lgetc(0)) != EOF && isdigit(c));
604 if (p == buf + 1 && buf[0] == '-')
606 if (c == EOF || allowed_to_end_number(c)) {
607 const char *errstr = NULL;
610 yylval.v.number = strtonum(buf, LLONG_MIN,
613 yyerror("\"%s\" invalid number: %s",
628 #define allowed_in_string(x) \
629 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
630 x != '{' && x != '}' && x != '<' && x != '>' && \
631 x != '!' && x != '=' && x != '#' && \
634 if (isalnum(c) || c == ':' || c == '_') {
637 if ((unsigned)(p-buf) >= sizeof(buf)) {
638 yyerror("string too long");
641 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
644 if ((token = lookup(buf)) == STRING)
645 if ((yylval.v.string = strdup(buf)) == NULL)
646 err(1, "yylex: strdup");
650 yylval.lineno = file->lineno;
659 check_file_secrecy(int fd, const char *fname)
663 if (fstat(fd, &st)) {
664 log_warn("cannot stat %s", fname);
667 if (st.st_uid != 0 && st.st_uid != getuid()) {
668 log_warnx("%s: owner not root or current user", fname);
671 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
672 log_warnx("%s: group writable or world read/writable", fname);
679 pushfile(const char *name, int secret)
683 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
687 if ((nfile->name = strdup(name)) == NULL) {
692 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
693 log_warn("%s", nfile->name);
698 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
699 fclose(nfile->stream);
705 TAILQ_INSERT_TAIL(&files, nfile, entry);
714 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
715 prev->errors += file->errors;
717 TAILQ_REMOVE(&files, file, entry);
718 fclose(file->stream);
722 return (file ? 0 : EOF);
726 parse_config(struct env *x_conf, const char *filename, int opts)
728 struct sym *sym, *next;
731 bzero(conf, sizeof(*conf));
733 TAILQ_INIT(&conf->sc_idms);
734 conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL;
735 conf->sc_conf_tv.tv_usec = 0;
739 if ((file = pushfile(filename, 1)) == NULL) {
745 * parse configuration
750 errors = file->errors;
753 /* Free macros and check which have not been used. */
754 for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
755 next = TAILQ_NEXT(sym, entry);
756 if ((opts & YPLDAP_OPT_VERBOSE) && !sym->used)
757 fprintf(stderr, "warning: macro '%s' not "
762 TAILQ_REMOVE(&symhead, sym, entry);
775 symset(const char *nam, const char *val, int persist)
779 for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
780 sym = TAILQ_NEXT(sym, entry))
784 if (sym->persist == 1)
789 TAILQ_REMOVE(&symhead, sym, entry);
793 if ((sym = calloc(1, sizeof(*sym))) == NULL)
796 sym->nam = strdup(nam);
797 if (sym->nam == NULL) {
801 sym->val = strdup(val);
802 if (sym->val == NULL) {
808 sym->persist = persist;
809 TAILQ_INSERT_TAIL(&symhead, sym, entry);
814 cmdline_symset(char *s)
820 if ((val = strrchr(s, '=')) == NULL)
823 len = strlen(s) - strlen(val) + 1;
824 if ((sym = malloc(len)) == NULL)
825 errx(1, "cmdline_symset: malloc");
827 (void)strlcpy(sym, s, len);
829 ret = symset(sym, val + 1, 1);
836 symget(const char *nam)
840 TAILQ_FOREACH(sym, &symhead, entry)
841 if (strcmp(nam, sym->nam) == 0) {