1 /* $OpenBSD: parse.y,v 1.18 2015/01/16 06:40:22 deraadt Exp $ */
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.
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.
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.
27 #include <sys/types.h>
28 #include <sys/param.h>
30 #include <sys/queue.h>
32 #include <sys/socket.h>
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
55 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
57 TAILQ_ENTRY(file) entry;
63 struct file *pushfile(const char *, int);
65 int check_file_secrecy(int, const char *);
68 int yyerror(const char *, ...)
69 __attribute__((__format__ (printf, 1, 2)))
70 __attribute__((__nonnull__ (1)));
71 int kw_cmp(const void *, const void *);
77 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
79 TAILQ_ENTRY(sym) entry;
85 int symset(const char *, const char *, int);
86 char *symget(const char *);
88 struct env *conf = NULL;
89 struct idm *idm = NULL;
90 static int errors = 0;
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
113 grammar : /* empty */
115 | grammar include '\n'
116 | grammar varset '\n'
117 | grammar directory '\n'
119 | grammar error '\n' { file->errors++; }
130 include : INCLUDE STRING {
133 if ((nfile = pushfile($2, 0)) == NULL) {
134 yyerror("failed to include file %s", $2);
145 varset : STRING '=' STRING {
148 if (isspace((unsigned char) *s)) {
149 yyerror("macro name cannot contain "
154 if (symset($1, $3, 0) == -1)
155 fatal("cannot store variable");
161 port : /* empty */ { $$ = NULL; }
162 | PORT STRING { $$ = $2; }
165 opcode : GROUP { $$ = 0; }
170 attribute : NAME { $$ = 0; }
180 | GROUPNAME { $$ = 10; }
181 | GROUPPASSWD { $$ = 11; }
182 | GROUPGID { $$ = 12; }
183 | GROUPMEMBERS { $$ = 13; }
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");
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");
209 if (strlcpy(idm->idm_basedn, $2,
210 sizeof(idm->idm_basedn)) >=
211 sizeof(idm->idm_basedn)) {
212 yyerror("directory basedn truncated");
219 if(strlcpy(idm->idm_groupdn, $2,
220 sizeof(idm->idm_groupdn)) >=
221 sizeof(idm->idm_groupdn)) {
222 yyerror("directory groupdn truncated");
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");
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");
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");
256 idm->idm_flags |= F_FIXED_ATTR($3);
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");
267 idm->idm_list |= F_LIST($2);
272 directory : DIRECTORY STRING port {
273 if ((idm = calloc(1, sizeof(*idm))) == NULL)
275 idm->idm_id = conf->sc_maxid++;
277 if (strlcpy(idm->idm_name, $2,
278 sizeof(idm->idm_name)) >=
279 sizeof(idm->idm_name)) {
280 yyerror("attribute truncated");
286 } '{' optnl diropts '}' {
287 TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry);
292 main : INTERVAL NUMBER {
293 conf->sc_conf_tv.tv_sec = $2;
294 conf->sc_conf_tv.tv_usec = 0;
297 if (strlcpy(conf->sc_domainname, $2,
298 sizeof(conf->sc_domainname)) >=
299 sizeof(conf->sc_domainname)) {
300 yyerror("domainname truncated");
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;
322 yyerror("unsupported map type: %s", $3);
330 diropts : diropts diropt nl
342 yyerror(const char *fmt, ...)
349 if (vasprintf(&msg, fmt, ap) == -1)
350 fatalx("yyerror vasprintf");
352 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
358 kw_cmp(const void *k, const void *e)
360 return (strcmp(k, ((const struct keywords *)e)->k_name));
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 },
374 { "directory", DIRECTORY },
375 { "domain", DOMAIN },
376 { "expire", EXPIRE },
377 { "filter", FILTER },
382 { "groupdn", GROUPDN },
383 { "groupgid", GROUPGID },
384 { "groupmembers", GROUPMEMBERS },
385 { "groupname", GROUPNAME },
386 { "grouppasswd", GROUPPASSWD },
388 { "include", INCLUDE },
389 { "interval", INTERVAL },
394 { "passwd", PASSWD },
396 { "provide", PROVIDE },
397 { "server", SERVER },
403 const struct keywords *p;
405 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
406 sizeof(keywords[0]), kw_cmp);
414 #define MAXPUSHBACK 128
418 u_char pushback_buffer[MAXPUSHBACK];
419 int pushback_index = 0;
427 /* Read character from the parsebuffer instead of input. */
428 if (parseindex >= 0) {
429 c = parsebuf[parseindex++];
438 return (pushback_buffer[--pushback_index]);
441 if ((c = getc(file->stream)) == EOF) {
442 yyerror("reached end of file while parsing "
444 if (file == topfile || popfile() == EOF)
451 while ((c = getc(file->stream)) == '\\') {
452 next = getc(file->stream);
457 yylval.lineno = file->lineno;
462 if (file == topfile || popfile() == EOF)
464 c = getc(file->stream);
479 if (pushback_index < MAXPUSHBACK-1)
480 return (pushback_buffer[pushback_index++] = c);
492 /* skip to either EOF or the first real EOL */
495 c = pushback_buffer[--pushback_index];
518 while ((c = lgetc(0)) == ' ' || c == '\t')
521 yylval.lineno = file->lineno;
523 while ((c = lgetc(0)) != '\n' && c != EOF)
525 if (c == '$' && parsebuf == NULL) {
527 if ((c = lgetc(0)) == EOF)
530 if (p + 1 >= buf + sizeof(buf) - 1) {
531 yyerror("string too long");
534 if (isalnum(c) || c == '_') {
544 yyerror("macro '%s' not defined", buf);
557 if ((c = lgetc(quotec)) == EOF)
562 } else if (c == '\\') {
563 if ((next = lgetc(quotec)) == EOF)
565 if (next == quotec || c == ' ' || c == '\t')
567 else if (next == '\n') {
572 } else if (c == quotec) {
575 } else if (c == '\0') {
576 yyerror("syntax error");
579 if (p + 1 >= buf + sizeof(buf) - 1) {
580 yyerror("string too long");
585 yylval.v.string = strdup(buf);
586 if (yylval.v.string == NULL)
587 err(1, "yylex: strdup");
591 #define allowed_to_end_number(x) \
592 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
594 if (c == '-' || isdigit(c)) {
597 if ((unsigned)(p-buf) >= sizeof(buf)) {
598 yyerror("string too long");
601 } while ((c = lgetc(0)) != EOF && isdigit(c));
603 if (p == buf + 1 && buf[0] == '-')
605 if (c == EOF || allowed_to_end_number(c)) {
606 const char *errstr = NULL;
609 yylval.v.number = strtonum(buf, LLONG_MIN,
612 yyerror("\"%s\" invalid number: %s",
627 #define allowed_in_string(x) \
628 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
629 x != '{' && x != '}' && x != '<' && x != '>' && \
630 x != '!' && x != '=' && x != '#' && \
633 if (isalnum(c) || c == ':' || c == '_') {
636 if ((unsigned)(p-buf) >= sizeof(buf)) {
637 yyerror("string too long");
640 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
643 if ((token = lookup(buf)) == STRING)
644 if ((yylval.v.string = strdup(buf)) == NULL)
645 err(1, "yylex: strdup");
649 yylval.lineno = file->lineno;
658 check_file_secrecy(int fd, const char *fname)
662 if (fstat(fd, &st)) {
663 log_warn("cannot stat %s", fname);
666 if (st.st_uid != 0 && st.st_uid != getuid()) {
667 log_warnx("%s: owner not root or current user", fname);
670 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
671 log_warnx("%s: group writable or world read/writable", fname);
678 pushfile(const char *name, int secret)
682 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
686 if ((nfile->name = strdup(name)) == NULL) {
691 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
692 log_warn("%s", nfile->name);
697 check_file_secrecy(fileno(nfile->stream), nfile->name)) {
698 fclose(nfile->stream);
704 TAILQ_INSERT_TAIL(&files, nfile, entry);
713 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
714 prev->errors += file->errors;
716 TAILQ_REMOVE(&files, file, entry);
717 fclose(file->stream);
721 return (file ? 0 : EOF);
725 parse_config(struct env *x_conf, const char *filename, int opts)
727 struct sym *sym, *next;
730 bzero(conf, sizeof(*conf));
732 TAILQ_INIT(&conf->sc_idms);
733 conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL;
734 conf->sc_conf_tv.tv_usec = 0;
738 if ((file = pushfile(filename, 1)) == NULL) {
744 * parse configuration
749 errors = file->errors;
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 "
761 TAILQ_REMOVE(&symhead, sym, entry);
774 symset(const char *nam, const char *val, int persist)
778 for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
779 sym = TAILQ_NEXT(sym, entry))
783 if (sym->persist == 1)
788 TAILQ_REMOVE(&symhead, sym, entry);
792 if ((sym = calloc(1, sizeof(*sym))) == NULL)
795 sym->nam = strdup(nam);
796 if (sym->nam == NULL) {
800 sym->val = strdup(val);
801 if (sym->val == NULL) {
807 sym->persist = persist;
808 TAILQ_INSERT_TAIL(&symhead, sym, entry);
813 cmdline_symset(char *s)
819 if ((val = strrchr(s, '=')) == NULL)
822 len = strlen(s) - strlen(val) + 1;
823 if ((sym = malloc(len)) == NULL)
824 errx(1, "cmdline_symset: malloc");
826 (void)strlcpy(sym, s, len);
828 ret = symset(sym, val + 1, 1);
835 symget(const char *nam)
839 TAILQ_FOREACH(sym, &symhead, entry)
840 if (strcmp(nam, sym->nam) == 0) {