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 {
147 if (symset($1, $3, 0) == -1)
148 fatal("cannot store variable");
154 port : /* empty */ { $$ = NULL; }
155 | PORT STRING { $$ = $2; }
158 opcode : GROUP { $$ = 0; }
163 attribute : NAME { $$ = 0; }
173 | GROUPNAME { $$ = 10; }
174 | GROUPPASSWD { $$ = 11; }
175 | GROUPGID { $$ = 12; }
176 | GROUPMEMBERS { $$ = 13; }
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");
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");
202 if (strlcpy(idm->idm_basedn, $2,
203 sizeof(idm->idm_basedn)) >=
204 sizeof(idm->idm_basedn)) {
205 yyerror("directory basedn truncated");
212 if(strlcpy(idm->idm_groupdn, $2,
213 sizeof(idm->idm_groupdn)) >=
214 sizeof(idm->idm_groupdn)) {
215 yyerror("directory groupdn truncated");
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");
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");
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");
249 idm->idm_flags |= F_FIXED_ATTR($3);
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");
260 idm->idm_list |= F_LIST($2);
265 directory : DIRECTORY STRING port {
266 if ((idm = calloc(1, sizeof(*idm))) == NULL)
268 idm->idm_id = conf->sc_maxid++;
270 if (strlcpy(idm->idm_name, $2,
271 sizeof(idm->idm_name)) >=
272 sizeof(idm->idm_name)) {
273 yyerror("attribute truncated");
279 } '{' optnl diropts '}' {
280 TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry);
285 main : INTERVAL NUMBER {
286 conf->sc_conf_tv.tv_sec = $2;
287 conf->sc_conf_tv.tv_usec = 0;
290 if (strlcpy(conf->sc_domainname, $2,
291 sizeof(conf->sc_domainname)) >=
292 sizeof(conf->sc_domainname)) {
293 yyerror("domainname truncated");
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;
315 yyerror("unsupported map type: %s", $3);
323 diropts : diropts diropt nl
335 yyerror(const char *fmt, ...)
342 if (vasprintf(&msg, fmt, ap) == -1)
343 fatalx("yyerror vasprintf");
345 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
351 kw_cmp(const void *k, const void *e)
353 return (strcmp(k, ((const struct keywords *)e)->k_name));
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 },
367 { "directory", DIRECTORY },
368 { "domain", DOMAIN },
369 { "expire", EXPIRE },
370 { "filter", FILTER },
375 { "groupdn", GROUPDN },
376 { "groupgid", GROUPGID },
377 { "groupmembers", GROUPMEMBERS },
378 { "groupname", GROUPNAME },
379 { "grouppasswd", GROUPPASSWD },
381 { "include", INCLUDE },
382 { "interval", INTERVAL },
387 { "passwd", PASSWD },
389 { "provide", PROVIDE },
390 { "server", SERVER },
396 const struct keywords *p;
398 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
399 sizeof(keywords[0]), kw_cmp);
407 #define MAXPUSHBACK 128
411 u_char pushback_buffer[MAXPUSHBACK];
412 int pushback_index = 0;
420 /* Read character from the parsebuffer instead of input. */
421 if (parseindex >= 0) {
422 c = parsebuf[parseindex++];
431 return (pushback_buffer[--pushback_index]);
434 if ((c = getc(file->stream)) == EOF) {
435 yyerror("reached end of file while parsing "
437 if (file == topfile || popfile() == EOF)
444 while ((c = getc(file->stream)) == '\\') {
445 next = getc(file->stream);
450 yylval.lineno = file->lineno;
455 if (file == topfile || popfile() == EOF)
457 c = getc(file->stream);
472 if (pushback_index < MAXPUSHBACK-1)
473 return (pushback_buffer[pushback_index++] = c);
485 /* skip to either EOF or the first real EOL */
488 c = pushback_buffer[--pushback_index];
511 while ((c = lgetc(0)) == ' ' || c == '\t')
514 yylval.lineno = file->lineno;
516 while ((c = lgetc(0)) != '\n' && c != EOF)
518 if (c == '$' && parsebuf == NULL) {
520 if ((c = lgetc(0)) == EOF)
523 if (p + 1 >= buf + sizeof(buf) - 1) {
524 yyerror("string too long");
527 if (isalnum(c) || c == '_') {
537 yyerror("macro '%s' not defined", buf);
550 if ((c = lgetc(quotec)) == EOF)
555 } else if (c == '\\') {
556 if ((next = lgetc(quotec)) == EOF)
558 if (next == quotec || c == ' ' || c == '\t')
560 else if (next == '\n') {
565 } else if (c == quotec) {
568 } else if (c == '\0') {
569 yyerror("syntax error");
572 if (p + 1 >= buf + sizeof(buf) - 1) {
573 yyerror("string too long");
578 yylval.v.string = strdup(buf);
579 if (yylval.v.string == NULL)
580 err(1, "yylex: strdup");
584 #define allowed_to_end_number(x) \
585 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
587 if (c == '-' || isdigit(c)) {
590 if ((unsigned)(p-buf) >= sizeof(buf)) {
591 yyerror("string too long");
594 } while ((c = lgetc(0)) != EOF && isdigit(c));
596 if (p == buf + 1 && buf[0] == '-')
598 if (c == EOF || allowed_to_end_number(c)) {
599 const char *errstr = NULL;
602 yylval.v.number = strtonum(buf, LLONG_MIN,
605 yyerror("\"%s\" invalid number: %s",
620 #define allowed_in_string(x) \
621 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
622 x != '{' && x != '}' && x != '<' && x != '>' && \
623 x != '!' && x != '=' && x != '#' && \
626 if (isalnum(c) || c == ':' || c == '_') {
629 if ((unsigned)(p-buf) >= sizeof(buf)) {
630 yyerror("string too long");
633 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
636 if ((token = lookup(buf)) == STRING)
637 if ((yylval.v.string = strdup(buf)) == NULL)
638 err(1, "yylex: strdup");
642 yylval.lineno = file->lineno;
651 check_file_secrecy(int fd, const char *fname)
655 if (fstat(fd, &st)) {
656 log_warn("cannot stat %s", fname);
659 if (st.st_uid != 0 && st.st_uid != getuid()) {
660 log_warnx("%s: owner not root or current user", fname);
663 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
664 log_warnx("%s: group writable or world read/writable", fname);
671 pushfile(const char *name, int secret)
675 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
679 if ((nfile->name = strdup(name)) == NULL) {
684 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
685 log_warn("%s", nfile->name);
690 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
691 fclose(nfile->stream);
697 TAILQ_INSERT_TAIL(&files, nfile, entry);
706 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
707 prev->errors += file->errors;
709 TAILQ_REMOVE(&files, file, entry);
710 fclose(file->stream);
714 return (file ? 0 : EOF);
718 parse_config(struct env *x_conf, const char *filename, int opts)
720 struct sym *sym, *next;
723 bzero(conf, sizeof(*conf));
725 TAILQ_INIT(&conf->sc_idms);
726 conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL;
727 conf->sc_conf_tv.tv_usec = 0;
731 if ((file = pushfile(filename, 1)) == NULL) {
737 * parse configuration
742 errors = file->errors;
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 "
754 TAILQ_REMOVE(&symhead, sym, entry);
767 symset(const char *nam, const char *val, int persist)
771 for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
772 sym = TAILQ_NEXT(sym, entry))
776 if (sym->persist == 1)
781 TAILQ_REMOVE(&symhead, sym, entry);
785 if ((sym = calloc(1, sizeof(*sym))) == NULL)
788 sym->nam = strdup(nam);
789 if (sym->nam == NULL) {
793 sym->val = strdup(val);
794 if (sym->val == NULL) {
800 sym->persist = persist;
801 TAILQ_INSERT_TAIL(&symhead, sym, entry);
806 cmdline_symset(char *s)
812 if ((val = strrchr(s, '=')) == NULL)
815 len = strlen(s) - strlen(val) + 1;
816 if ((sym = malloc(len)) == NULL)
817 errx(1, "cmdline_symset: malloc");
819 (void)strlcpy(sym, s, len);
821 ret = symset(sym, val + 1, 1);
828 symget(const char *nam)
832 TAILQ_FOREACH(sym, &symhead, entry)
833 if (strcmp(nam, sym->nam) == 0) {