1 /* $OpenBSD: parse.y,v 1.9 2010/08/03 18:42:41 henning 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/time.h> 29 #include <sys/queue.h> 30 #include <sys/tree.h> 31 #include <sys/param.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 <unistd.h> 51 52 #include "ypldap.h" 53 54 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); 55 static struct file { 56 TAILQ_ENTRY(file) entry; 57 FILE *stream; 58 char *name; 59 int lineno; 60 int errors; 61 } *file, *topfile; 62 struct file *pushfile(const char *, int); 63 int popfile(void); 64 int check_file_secrecy(int, const char *); 65 int yyparse(void); 66 int yylex(void); 67 int yyerror(const char *, ...); 68 int kw_cmp(const void *, const void *); 69 int lookup(char *); 70 int lgetc(int); 71 int lungetc(int); 72 int findeol(void); 73 74 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); 75 struct sym { 76 TAILQ_ENTRY(sym) entry; 77 int used; 78 int persist; 79 char *nam; 80 char *val; 81 }; 82 int symset(const char *, const char *, int); 83 char *symget(const char *); 84 85 struct env *conf = NULL; 86 struct idm *idm = NULL; 87 static int errors = 0; 88 89 typedef struct { 90 union { 91 int64_t number; 92 char *string; 93 } v; 94 int lineno; 95 } YYSTYPE; 96 97 %} 98 99 %token SERVER FILTER ATTRIBUTE BASEDN BINDDN BINDCRED MAPS CHANGE DOMAIN PROVIDE 100 %token USER GROUP TO EXPIRE HOME SHELL GECOS UID GID INTERVAL 101 %token PASSWD NAME FIXED LIST GROUPNAME GROUPPASSWD GROUPGID MAP 102 %token INCLUDE DIRECTORY CLASS PORT ERROR GROUPMEMBERS 103 %token <v.string> STRING 104 %token <v.number> NUMBER 105 %type <v.number> opcode attribute 106 %type <v.string> port 107 108 %% 109 110 grammar : /* empty */ 111 | grammar '\n' 112 | grammar include '\n' 113 | grammar varset '\n' 114 | grammar directory '\n' 115 | grammar main '\n' 116 | grammar error '\n' { file->errors++; } 117 ; 118 119 nl : '\n' optnl 120 ; 121 122 optnl : '\n' optnl 123 | /* empty */ 124 ; 125 126 127 include : INCLUDE STRING { 128 struct file *nfile; 129 130 if ((nfile = pushfile($2, 0)) == NULL) { 131 yyerror("failed to include file %s", $2); 132 free($2); 133 YYERROR; 134 } 135 free($2); 136 137 file = nfile; 138 lungetc('\n'); 139 } 140 ; 141 142 varset : STRING '=' STRING { 143 if (symset($1, $3, 0) == -1) 144 fatal("cannot store variable"); 145 free($1); 146 free($3); 147 } 148 ; 149 150 port : /* empty */ { $$ = NULL; } 151 | PORT STRING { $$ = $2; } 152 ; 153 154 opcode : GROUP { $$ = 0; } 155 | PASSWD { $$ = 1; } 156 ; 157 158 159 attribute : NAME { $$ = 0; } 160 | PASSWD { $$ = 1; } 161 | UID { $$ = 2; } 162 | GID { $$ = 3; } 163 | CLASS { $$ = 4; } 164 | CHANGE { $$ = 5; } 165 | EXPIRE { $$ = 6; } 166 | GECOS { $$ = 7; } 167 | HOME { $$ = 8; } 168 | SHELL { $$ = 9; } 169 | GROUPNAME { $$ = 10; } 170 | GROUPPASSWD { $$ = 11; } 171 | GROUPGID { $$ = 12; } 172 | GROUPMEMBERS { $$ = 13; } 173 ; 174 175 diropt : BINDDN STRING { 176 idm->idm_flags |= F_NEEDAUTH; 177 if (strlcpy(idm->idm_binddn, $2, 178 sizeof(idm->idm_binddn)) >= 179 sizeof(idm->idm_binddn)) { 180 yyerror("directory binddn truncated"); 181 free($2); 182 YYERROR; 183 } 184 free($2); 185 } 186 | BINDCRED STRING { 187 idm->idm_flags |= F_NEEDAUTH; 188 if (strlcpy(idm->idm_bindcred, $2, 189 sizeof(idm->idm_bindcred)) >= 190 sizeof(idm->idm_bindcred)) { 191 yyerror("directory bindcred truncated"); 192 free($2); 193 YYERROR; 194 } 195 free($2); 196 } 197 | BASEDN STRING { 198 if (strlcpy(idm->idm_basedn, $2, 199 sizeof(idm->idm_basedn)) >= 200 sizeof(idm->idm_basedn)) { 201 yyerror("directory basedn truncated"); 202 free($2); 203 YYERROR; 204 } 205 free($2); 206 } 207 | opcode FILTER STRING { 208 if (strlcpy(idm->idm_filters[$1], $3, 209 sizeof(idm->idm_filters[$1])) >= 210 sizeof(idm->idm_filters[$1])) { 211 yyerror("filter truncated"); 212 free($3); 213 YYERROR; 214 } 215 free($3); 216 } 217 | ATTRIBUTE attribute MAPS TO STRING { 218 if (strlcpy(idm->idm_attrs[$2], $5, 219 sizeof(idm->idm_attrs[$2])) >= 220 sizeof(idm->idm_attrs[$2])) { 221 yyerror("attribute truncated"); 222 free($5); 223 YYERROR; 224 } 225 free($5); 226 } 227 | FIXED ATTRIBUTE attribute STRING { 228 if (strlcpy(idm->idm_attrs[$3], $4, 229 sizeof(idm->idm_attrs[$3])) >= 230 sizeof(idm->idm_attrs[$3])) { 231 yyerror("attribute truncated"); 232 free($4); 233 YYERROR; 234 } 235 idm->idm_flags |= F_FIXED_ATTR($3); 236 free($4); 237 } 238 | LIST 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 idm->idm_list |= F_LIST($2); 247 free($5); 248 } 249 ; 250 251 directory : DIRECTORY STRING port { 252 if ((idm = calloc(1, sizeof(*idm))) == NULL) 253 fatal(NULL); 254 idm->idm_id = conf->sc_maxid++; 255 256 if (strlcpy(idm->idm_name, $2, 257 sizeof(idm->idm_name)) >= 258 sizeof(idm->idm_name)) { 259 yyerror("attribute truncated"); 260 free($2); 261 YYERROR; 262 } 263 264 free($2); 265 } '{' optnl diropts '}' { 266 TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry); 267 idm = NULL; 268 } 269 ; 270 271 main : INTERVAL NUMBER { 272 conf->sc_conf_tv.tv_sec = $2; 273 conf->sc_conf_tv.tv_usec = 0; 274 } 275 | DOMAIN STRING { 276 if (strlcpy(conf->sc_domainname, $2, 277 sizeof(conf->sc_domainname)) >= 278 sizeof(conf->sc_domainname)) { 279 yyerror("domainname truncated"); 280 free($2); 281 YYERROR; 282 } 283 free($2); 284 } 285 | PROVIDE MAP STRING { 286 if (strcmp($3, "passwd.byname") == 0) 287 conf->sc_flags |= YPMAP_PASSWD_BYNAME; 288 else if (strcmp($3, "passwd.byuid") == 0) 289 conf->sc_flags |= YPMAP_PASSWD_BYUID; 290 else if (strcmp($3, "master.passwd.byname") == 0) 291 conf->sc_flags |= YPMAP_MASTER_PASSWD_BYNAME; 292 else if (strcmp($3, "master.passwd.byuid") == 0) 293 conf->sc_flags |= YPMAP_MASTER_PASSWD_BYUID; 294 else if (strcmp($3, "group.byname") == 0) 295 conf->sc_flags |= YPMAP_GROUP_BYNAME; 296 else if (strcmp($3, "group.bygid") == 0) 297 conf->sc_flags |= YPMAP_GROUP_BYGID; 298 else { 299 yyerror("unsupported map type: %s", $3); 300 free($3); 301 YYERROR; 302 } 303 free($3); 304 } 305 ; 306 307 diropts : diropts diropt nl 308 | diropt optnl 309 ; 310 311 %% 312 313 struct keywords { 314 const char *k_name; 315 int k_val; 316 }; 317 318 int 319 yyerror(const char *fmt, ...) 320 { 321 va_list ap; 322 323 file->errors++; 324 va_start(ap, fmt); 325 fprintf(stderr, "%s:%d: ", file->name, yylval.lineno); 326 vfprintf(stderr, fmt, ap); 327 fprintf(stderr, "\n"); 328 va_end(ap); 329 return (0); 330 } 331 332 int 333 kw_cmp(const void *k, const void *e) 334 { 335 return (strcmp(k, ((const struct keywords *)e)->k_name)); 336 } 337 338 int 339 lookup(char *s) 340 { 341 /* this has to be sorted always */ 342 static const struct keywords keywords[] = { 343 { "attribute", ATTRIBUTE }, 344 { "basedn", BASEDN }, 345 { "bindcred", BINDCRED }, 346 { "binddn", BINDDN }, 347 { "change", CHANGE }, 348 { "class", CLASS }, 349 { "directory", DIRECTORY }, 350 { "domain", DOMAIN }, 351 { "expire", EXPIRE }, 352 { "filter", FILTER }, 353 { "fixed", FIXED }, 354 { "gecos", GECOS }, 355 { "gid", GID }, 356 { "group", GROUP }, 357 { "groupgid", GROUPGID }, 358 { "groupmembers", GROUPMEMBERS }, 359 { "groupname", GROUPNAME }, 360 { "grouppasswd", GROUPPASSWD }, 361 { "home", HOME }, 362 { "include", INCLUDE }, 363 { "interval", INTERVAL }, 364 { "list", LIST }, 365 { "map", MAP }, 366 { "maps", MAPS }, 367 { "name", NAME }, 368 { "passwd", PASSWD }, 369 { "port", PORT }, 370 { "provide", PROVIDE }, 371 { "server", SERVER }, 372 { "shell", SHELL }, 373 { "to", TO }, 374 { "uid", UID }, 375 { "user", USER }, 376 }; 377 const struct keywords *p; 378 379 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), 380 sizeof(keywords[0]), kw_cmp); 381 382 if (p) 383 return (p->k_val); 384 else 385 return (STRING); 386 } 387 388 #define MAXPUSHBACK 128 389 390 char *parsebuf; 391 int parseindex; 392 char pushback_buffer[MAXPUSHBACK]; 393 int pushback_index = 0; 394 395 int 396 lgetc(int quotec) 397 { 398 int c, next; 399 400 if (parsebuf) { 401 /* Read character from the parsebuffer instead of input. */ 402 if (parseindex >= 0) { 403 c = parsebuf[parseindex++]; 404 if (c != '\0') 405 return (c); 406 parsebuf = NULL; 407 } else 408 parseindex++; 409 } 410 411 if (pushback_index) 412 return (pushback_buffer[--pushback_index]); 413 414 if (quotec) { 415 if ((c = getc(file->stream)) == EOF) { 416 yyerror("reached end of file while parsing " 417 "quoted string"); 418 if (file == topfile || popfile() == EOF) 419 return (EOF); 420 return (quotec); 421 } 422 return (c); 423 } 424 425 while ((c = getc(file->stream)) == '\\') { 426 next = getc(file->stream); 427 if (next != '\n') { 428 c = next; 429 break; 430 } 431 yylval.lineno = file->lineno; 432 file->lineno++; 433 } 434 435 while (c == EOF) { 436 if (file == topfile || popfile() == EOF) 437 return (EOF); 438 c = getc(file->stream); 439 } 440 return (c); 441 } 442 443 int 444 lungetc(int c) 445 { 446 if (c == EOF) 447 return (EOF); 448 if (parsebuf) { 449 parseindex--; 450 if (parseindex >= 0) 451 return (c); 452 } 453 if (pushback_index < MAXPUSHBACK-1) 454 return (pushback_buffer[pushback_index++] = c); 455 else 456 return (EOF); 457 } 458 459 int 460 findeol(void) 461 { 462 int c; 463 464 parsebuf = NULL; 465 466 /* skip to either EOF or the first real EOL */ 467 while (1) { 468 if (pushback_index) 469 c = pushback_buffer[--pushback_index]; 470 else 471 c = lgetc(0); 472 if (c == '\n') { 473 file->lineno++; 474 break; 475 } 476 if (c == EOF) 477 break; 478 } 479 return (ERROR); 480 } 481 482 int 483 yylex(void) 484 { 485 char buf[8096]; 486 char *p, *val; 487 int quotec, next, c; 488 int token; 489 490 top: 491 p = buf; 492 while ((c = lgetc(0)) == ' ' || c == '\t') 493 ; /* nothing */ 494 495 yylval.lineno = file->lineno; 496 if (c == '#') 497 while ((c = lgetc(0)) != '\n' && c != EOF) 498 ; /* nothing */ 499 if (c == '$' && parsebuf == NULL) { 500 while (1) { 501 if ((c = lgetc(0)) == EOF) 502 return (0); 503 504 if (p + 1 >= buf + sizeof(buf) - 1) { 505 yyerror("string too long"); 506 return (findeol()); 507 } 508 if (isalnum(c) || c == '_') { 509 *p++ = (char)c; 510 continue; 511 } 512 *p = '\0'; 513 lungetc(c); 514 break; 515 } 516 val = symget(buf); 517 if (val == NULL) { 518 yyerror("macro '%s' not defined", buf); 519 return (findeol()); 520 } 521 parsebuf = val; 522 parseindex = 0; 523 goto top; 524 } 525 526 switch (c) { 527 case '\'': 528 case '"': 529 quotec = c; 530 while (1) { 531 if ((c = lgetc(quotec)) == EOF) 532 return (0); 533 if (c == '\n') { 534 file->lineno++; 535 continue; 536 } else if (c == '\\') { 537 if ((next = lgetc(quotec)) == EOF) 538 return (0); 539 if (next == quotec || c == ' ' || c == '\t') 540 c = next; 541 else if (next == '\n') { 542 file->lineno++; 543 continue; 544 } else 545 lungetc(next); 546 } else if (c == quotec) { 547 *p = '\0'; 548 break; 549 } 550 if (p + 1 >= buf + sizeof(buf) - 1) { 551 yyerror("string too long"); 552 return (findeol()); 553 } 554 *p++ = (char)c; 555 } 556 yylval.v.string = strdup(buf); 557 if (yylval.v.string == NULL) 558 err(1, "yylex: strdup"); 559 return (STRING); 560 } 561 562 #define allowed_to_end_number(x) \ 563 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') 564 565 if (c == '-' || isdigit(c)) { 566 do { 567 *p++ = c; 568 if ((unsigned)(p-buf) >= sizeof(buf)) { 569 yyerror("string too long"); 570 return (findeol()); 571 } 572 } while ((c = lgetc(0)) != EOF && isdigit(c)); 573 lungetc(c); 574 if (p == buf + 1 && buf[0] == '-') 575 goto nodigits; 576 if (c == EOF || allowed_to_end_number(c)) { 577 const char *errstr = NULL; 578 579 *p = '\0'; 580 yylval.v.number = strtonum(buf, LLONG_MIN, 581 LLONG_MAX, &errstr); 582 if (errstr) { 583 yyerror("\"%s\" invalid number: %s", 584 buf, errstr); 585 return (findeol()); 586 } 587 return (NUMBER); 588 } else { 589 nodigits: 590 while (p > buf + 1) 591 lungetc(*--p); 592 c = *--p; 593 if (c == '-') 594 return (c); 595 } 596 } 597 598 #define allowed_in_string(x) \ 599 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ 600 x != '{' && x != '}' && x != '<' && x != '>' && \ 601 x != '!' && x != '=' && x != '#' && \ 602 x != ',')) 603 604 if (isalnum(c) || c == ':' || c == '_') { 605 do { 606 *p++ = c; 607 if ((unsigned)(p-buf) >= sizeof(buf)) { 608 yyerror("string too long"); 609 return (findeol()); 610 } 611 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); 612 lungetc(c); 613 *p = '\0'; 614 if ((token = lookup(buf)) == STRING) 615 if ((yylval.v.string = strdup(buf)) == NULL) 616 err(1, "yylex: strdup"); 617 return (token); 618 } 619 if (c == '\n') { 620 yylval.lineno = file->lineno; 621 file->lineno++; 622 } 623 if (c == EOF) 624 return (0); 625 return (c); 626 } 627 628 int 629 check_file_secrecy(int fd, const char *fname) 630 { 631 struct stat st; 632 633 if (fstat(fd, &st)) { 634 log_warn("cannot stat %s", fname); 635 return (-1); 636 } 637 if (st.st_uid != 0 && st.st_uid != getuid()) { 638 log_warnx("%s: owner not root or current user", fname); 639 return (-1); 640 } 641 if (st.st_mode & (S_IRWXG | S_IRWXO)) { 642 log_warnx("%s: group/world readable/writeable", fname); 643 return (-1); 644 } 645 return (0); 646 } 647 648 struct file * 649 pushfile(const char *name, int secret) 650 { 651 struct file *nfile; 652 653 if ((nfile = calloc(1, sizeof(struct file))) == NULL) { 654 log_warn("malloc"); 655 return (NULL); 656 } 657 if ((nfile->name = strdup(name)) == NULL) { 658 log_warn("malloc"); 659 free(nfile); 660 return (NULL); 661 } 662 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { 663 log_warn("%s", nfile->name); 664 free(nfile->name); 665 free(nfile); 666 return (NULL); 667 } else if (secret && 668 check_file_secrecy(fileno(nfile->stream), nfile->name)) { 669 fclose(nfile->stream); 670 free(nfile->name); 671 free(nfile); 672 return (NULL); 673 } 674 nfile->lineno = 1; 675 TAILQ_INSERT_TAIL(&files, nfile, entry); 676 return (nfile); 677 } 678 679 int 680 popfile(void) 681 { 682 struct file *prev; 683 684 if ((prev = TAILQ_PREV(file, files, entry)) != NULL) 685 prev->errors += file->errors; 686 687 TAILQ_REMOVE(&files, file, entry); 688 fclose(file->stream); 689 free(file->name); 690 free(file); 691 file = prev; 692 return (file ? 0 : EOF); 693 } 694 695 int 696 parse_config(struct env *x_conf, const char *filename, int opts) 697 { 698 struct sym *sym, *next; 699 700 conf = x_conf; 701 bzero(conf, sizeof(*conf)); 702 703 TAILQ_INIT(&conf->sc_idms); 704 conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL; 705 conf->sc_conf_tv.tv_usec = 0; 706 707 errors = 0; 708 709 if ((file = pushfile(filename, 1)) == NULL) { 710 return (-1); 711 } 712 topfile = file; 713 714 /* 715 * parse configuration 716 */ 717 setservent(1); 718 yyparse(); 719 endservent(); 720 errors = file->errors; 721 popfile(); 722 723 /* Free macros and check which have not been used. */ 724 for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { 725 next = TAILQ_NEXT(sym, entry); 726 if ((opts & YPLDAP_OPT_VERBOSE) && !sym->used) 727 fprintf(stderr, "warning: macro '%s' not " 728 "used\n", sym->nam); 729 if (!sym->persist) { 730 free(sym->nam); 731 free(sym->val); 732 TAILQ_REMOVE(&symhead, sym, entry); 733 free(sym); 734 } 735 } 736 737 if (errors) { 738 return (-1); 739 } 740 741 return (0); 742 } 743 744 int 745 symset(const char *nam, const char *val, int persist) 746 { 747 struct sym *sym; 748 749 for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); 750 sym = TAILQ_NEXT(sym, entry)) 751 ; /* nothing */ 752 753 if (sym != NULL) { 754 if (sym->persist == 1) 755 return (0); 756 else { 757 free(sym->nam); 758 free(sym->val); 759 TAILQ_REMOVE(&symhead, sym, entry); 760 free(sym); 761 } 762 } 763 if ((sym = calloc(1, sizeof(*sym))) == NULL) 764 return (-1); 765 766 sym->nam = strdup(nam); 767 if (sym->nam == NULL) { 768 free(sym); 769 return (-1); 770 } 771 sym->val = strdup(val); 772 if (sym->val == NULL) { 773 free(sym->nam); 774 free(sym); 775 return (-1); 776 } 777 sym->used = 0; 778 sym->persist = persist; 779 TAILQ_INSERT_TAIL(&symhead, sym, entry); 780 return (0); 781 } 782 783 int 784 cmdline_symset(char *s) 785 { 786 char *sym, *val; 787 int ret; 788 size_t len; 789 790 if ((val = strrchr(s, '=')) == NULL) 791 return (-1); 792 793 len = strlen(s) - strlen(val) + 1; 794 if ((sym = malloc(len)) == NULL) 795 errx(1, "cmdline_symset: malloc"); 796 797 (void)strlcpy(sym, s, len); 798 799 ret = symset(sym, val + 1, 1); 800 free(sym); 801 802 return (ret); 803 } 804 805 char * 806 symget(const char *nam) 807 { 808 struct sym *sym; 809 810 TAILQ_FOREACH(sym, &symhead, entry) 811 if (strcmp(nam, sym->nam) == 0) { 812 sym->used = 1; 813 return (sym->val); 814 } 815 return (NULL); 816 } 817