1 /* $OpenBSD: parse.y,v 1.8 2016/06/21 21:35:24 benno Exp $ */ 2 3 /* 4 * Copyright (c) 2010 David Gwynne <dlg@openbsd.org> 5 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> 6 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> 7 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> 8 * Copyright (c) 2001 Markus Friedl. All rights reserved. 9 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. 10 * Copyright (c) 2001 Theo de Raadt. All rights reserved. 11 * 12 * Permission to use, copy, modify, and distribute this software for any 13 * purpose with or without fee is hereby granted, provided that the above 14 * copyright notice and this permission notice appear in all copies. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 17 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 19 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 21 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 22 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 23 */ 24 25 %{ 26 #include <sys/types.h> 27 #include <sys/queue.h> 28 #include <sys/socket.h> 29 #include <sys/stat.h> 30 #include <sys/uio.h> 31 #include <netinet/in.h> 32 #include <arpa/inet.h> 33 #include <ctype.h> 34 #include <err.h> 35 #include <errno.h> 36 #include <event.h> 37 #include <limits.h> 38 #include <netdb.h> 39 #include <stdarg.h> 40 #include <stdio.h> 41 #include <string.h> 42 #include <unistd.h> 43 44 #include <scsi/iscsi.h> 45 #include "iscsid.h" 46 #include "iscsictl.h" 47 48 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); 49 static struct file { 50 TAILQ_ENTRY(file) entry; 51 FILE *stream; 52 char *name; 53 int lineno; 54 int errors; 55 } *file, *topfile; 56 struct file *pushfile(const char *, int); 57 int popfile(void); 58 int yyparse(void); 59 int yylex(void); 60 int yyerror(const char *, ...) 61 __attribute__((__format__ (printf, 1, 2))) 62 __attribute__((__nonnull__ (1))); 63 int kw_cmp(const void *, const void *); 64 int lookup(char *); 65 int lgetc(int); 66 int lungetc(int); 67 int findeol(void); 68 69 void clear_config(struct iscsi_config *); 70 71 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); 72 struct sym { 73 TAILQ_ENTRY(sym) entry; 74 int used; 75 int persist; 76 char *nam; 77 char *val; 78 }; 79 int symset(const char *, const char *, int); 80 char *symget(const char *); 81 82 static int errors; 83 static struct iscsi_config *conf; 84 static struct session_config *session; 85 86 struct addrinfo_opts { 87 int af; 88 char *port; 89 } addrinfo_opts; 90 91 typedef struct { 92 union { 93 int i; 94 int64_t number; 95 char *string; 96 struct addrinfo_opts addrinfo_opts; 97 struct addrinfo *addrinfo; 98 } v; 99 int lineno; 100 } YYSTYPE; 101 102 %} 103 104 %token TARGET TARGETNAME TARGETADDR 105 %token INITIATORNAME INITIATORADDR ISID 106 %token ENABLED DISABLED NORMAL DISCOVERY 107 %token ADDRESS INET INET6 PORT 108 %token INCLUDE 109 %token ERROR 110 %token <v.string> STRING 111 %token <v.number> NUMBER 112 %type <v.i> af state type 113 %type <v.string> port 114 %type <v.addrinfo> addrinfo 115 %type <v.addrinfo_opts> addrinfo_opts addrinfo_opts_l addrinfo_opt 116 %type <v.string> string 117 118 %% 119 120 grammar : /* empty */ 121 | grammar '\n' 122 | grammar include '\n' 123 | grammar varset '\n' 124 | grammar initiator '\n' 125 | grammar target '\n' 126 | grammar error '\n' { file->errors++; } 127 ; 128 129 include : INCLUDE STRING { 130 struct file *nfile; 131 132 if ((nfile = pushfile($2, 1)) == NULL) { 133 yyerror("failed to include file %s", $2); 134 free($2); 135 YYERROR; 136 } 137 free($2); 138 139 file = nfile; 140 lungetc('\n'); 141 } 142 ; 143 144 string : string STRING { 145 if (asprintf(&$$, "%s %s", $1, $2) == -1) { 146 free($1); 147 free($2); 148 yyerror("string: asprintf"); 149 YYERROR; 150 } 151 free($1); 152 free($2); 153 } 154 | STRING 155 ; 156 157 varset : STRING '=' string { 158 char *s = $1; 159 while (*s++) { 160 if (isspace((unsigned char)*s)) { 161 yyerror("macro name cannot contain " 162 "whitespace"); 163 YYERROR; 164 } 165 } 166 if (symset($1, $3, 0) == -1) 167 err(1, "cannot store variable"); 168 free($1); 169 free($3); 170 } 171 ; 172 173 optnl : '\n' optnl 174 | 175 ; 176 177 nl : '\n' optnl /* one or more newlines */ 178 ; 179 180 initiator : ISID STRING NUMBER NUMBER { 181 u_int32_t mask1, mask2; 182 183 if (!strcasecmp($2, "oui")) { 184 conf->initiator.isid_base = ISCSI_ISID_OUI; 185 mask1 = 0x3fffff00; 186 mask2 = 0x000000ff; 187 } else if (!strcasecmp($2, "en")) { 188 conf->initiator.isid_base = ISCSI_ISID_EN; 189 mask1 = 0x00ffffff; 190 mask2 = 0; 191 } else if (!strcasecmp($2, "rand")) { 192 conf->initiator.isid_base = ISCSI_ISID_RAND; 193 mask1 = 0x00ffffff; 194 mask2 = 0; 195 } else { 196 yyerror("isid type %s unknown", $2); 197 free($2); 198 YYERROR; 199 } 200 free($2); 201 conf->initiator.isid_base |= $3 & mask1; 202 conf->initiator.isid_base |= ($4 >> 16) & mask2; 203 conf->initiator.isid_qual = $4; 204 } 205 ; 206 207 target : TARGET STRING { 208 struct session_ctlcfg *scelm; 209 210 scelm = calloc(1, sizeof(*scelm)); 211 session = &scelm->session; 212 if (strlcpy(session->SessionName, $2, 213 sizeof(session->SessionName)) >= 214 sizeof(session->SessionName)) { 215 yyerror("target name \"%s\" too long", $2); 216 free($2); 217 free(scelm); 218 YYERROR; 219 } 220 free($2); 221 SIMPLEQ_INSERT_TAIL(&conf->sessions, scelm, entry); 222 } '{' optnl targetopts_l '}' 223 ; 224 225 targetopts_l : targetopts_l targetoptsl nl 226 | targetoptsl optnl 227 ; 228 229 targetoptsl : state { session->disabled = $1; } 230 | type { session->SessionType = $1; } 231 | TARGETNAME STRING { session->TargetName = $2; } 232 | INITIATORNAME STRING { session->InitiatorName = $2; } 233 | TARGETADDR addrinfo { 234 bcopy($2->ai_addr, &session->connection.TargetAddr, 235 $2->ai_addr->sa_len); 236 freeaddrinfo($2); 237 } 238 | INITIATORADDR addrinfo { 239 ((struct sockaddr_in *)$2->ai_addr)->sin_port = 0; 240 bcopy($2->ai_addr, &session->connection.LocalAddr, 241 $2->ai_addr->sa_len); 242 freeaddrinfo($2); 243 } 244 ; 245 246 addrinfo : STRING addrinfo_opts { 247 struct addrinfo hints; 248 char *hostname; 249 int error; 250 251 $$ = NULL; 252 253 if ($2.port == NULL) { 254 if (($2.port = strdup("iscsi")) == NULL) { 255 free($1); 256 yyerror("port strdup"); 257 YYERROR; 258 } 259 } 260 261 memset(&hints, 0, sizeof(hints)); 262 hints.ai_family = $2.af; 263 hints.ai_socktype = SOCK_STREAM; 264 hints.ai_protocol = IPPROTO_TCP; 265 266 if (strcmp($1, "*") == 0) { 267 hostname = NULL; 268 hints.ai_flags = AI_PASSIVE; 269 } else 270 hostname = $1; 271 272 error = getaddrinfo(hostname, $2.port, &hints, &$$); 273 if (error) { 274 yyerror("%s (%s %s)", gai_strerror(error), 275 $1, $2.port); 276 free($1); 277 free($2.port); 278 YYERROR; 279 } 280 281 free($1); 282 free($2.port); 283 } 284 ; 285 286 addrinfo_opts : { 287 addrinfo_opts.port = NULL; 288 addrinfo_opts.af = PF_UNSPEC; 289 } 290 addrinfo_opts_l { $$ = addrinfo_opts; } 291 | /* empty */ { 292 addrinfo_opts.port = NULL; 293 addrinfo_opts.af = PF_UNSPEC; 294 $$ = addrinfo_opts; 295 } 296 ; 297 298 addrinfo_opts_l : addrinfo_opts_l addrinfo_opt 299 | addrinfo_opt 300 ; 301 302 addrinfo_opt : port { 303 if (addrinfo_opts.port != NULL) { 304 yyerror("port cannot be redefined"); 305 YYERROR; 306 } 307 addrinfo_opts.port = $1; 308 } 309 | af { 310 if (addrinfo_opts.af != PF_UNSPEC) { 311 yyerror("address family cannot be redefined"); 312 YYERROR; 313 } 314 addrinfo_opts.af = $1; 315 } 316 ; 317 318 port : PORT STRING { $$ = $2; } 319 ; 320 321 af : INET { $$ = PF_INET; } 322 | INET6 { $$ = PF_INET6; } 323 ; 324 325 state : ENABLED { $$ = 0; } 326 | DISABLED { $$ = 1; } 327 ; 328 329 type : NORMAL { $$ = SESSION_TYPE_NORMAL; } 330 | DISCOVERY { $$ = SESSION_TYPE_DISCOVERY; } 331 ; 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 346 file->errors++; 347 va_start(ap, fmt); 348 fprintf(stderr, "%s:%d: ", file->name, yylval.lineno); 349 vfprintf(stderr, fmt, ap); 350 fprintf(stderr, "\n"); 351 va_end(ap); 352 return (0); 353 } 354 355 int 356 kw_cmp(const void *k, const void *e) 357 { 358 return (strcmp(k, ((const struct keywords *)e)->k_name)); 359 } 360 361 int 362 lookup(char *s) 363 { 364 /* this has to be sorted always */ 365 static const struct keywords keywords[] = { 366 {"address", ADDRESS}, 367 {"disabled", DISABLED}, 368 {"discovery", DISCOVERY}, 369 {"enabled", ENABLED}, 370 {"include", INCLUDE}, 371 {"inet", INET}, 372 {"inet4", INET}, 373 {"inet6", INET6}, 374 {"initiatoraddr", INITIATORADDR}, 375 {"initiatorname", INITIATORNAME}, 376 {"isid", ISID}, 377 {"normal", NORMAL}, 378 {"port", PORT}, 379 {"target", TARGET}, 380 {"targetaddr", TARGETADDR}, 381 {"targetname", TARGETNAME} 382 }; 383 const struct keywords *p; 384 385 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), 386 sizeof(keywords[0]), kw_cmp); 387 388 if (p) 389 return (p->k_val); 390 else 391 return (STRING); 392 } 393 394 #define MAXPUSHBACK 128 395 396 u_char *parsebuf; 397 int parseindex; 398 u_char pushback_buffer[MAXPUSHBACK]; 399 int pushback_index; 400 401 int 402 lgetc(int quotec) 403 { 404 int c, next; 405 406 if (parsebuf) { 407 /* Read character from the parsebuffer instead of input. */ 408 if (parseindex >= 0) { 409 c = parsebuf[parseindex++]; 410 if (c != '\0') 411 return (c); 412 parsebuf = NULL; 413 } else 414 parseindex++; 415 } 416 417 if (pushback_index) 418 return (pushback_buffer[--pushback_index]); 419 420 if (quotec) { 421 if ((c = getc(file->stream)) == EOF) { 422 yyerror("reached end of file while parsing " 423 "quoted string"); 424 if (file == topfile || popfile() == EOF) 425 return (EOF); 426 return (quotec); 427 } 428 return (c); 429 } 430 431 while ((c = getc(file->stream)) == '\\') { 432 next = getc(file->stream); 433 if (next != '\n') { 434 c = next; 435 break; 436 } 437 yylval.lineno = file->lineno; 438 file->lineno++; 439 } 440 441 while (c == EOF) { 442 if (file == topfile || popfile() == EOF) 443 return (EOF); 444 c = getc(file->stream); 445 } 446 return (c); 447 } 448 449 int 450 lungetc(int c) 451 { 452 if (c == EOF) 453 return (EOF); 454 if (parsebuf) { 455 parseindex--; 456 if (parseindex >= 0) 457 return (c); 458 } 459 if (pushback_index < MAXPUSHBACK-1) 460 return (pushback_buffer[pushback_index++] = c); 461 else 462 return (EOF); 463 } 464 465 int 466 findeol(void) 467 { 468 int c; 469 470 parsebuf = NULL; 471 472 /* skip to either EOF or the first real EOL */ 473 while (1) { 474 if (pushback_index) 475 c = pushback_buffer[--pushback_index]; 476 else 477 c = lgetc(0); 478 if (c == '\n') { 479 file->lineno++; 480 break; 481 } 482 if (c == EOF) 483 break; 484 } 485 return (ERROR); 486 } 487 488 int 489 yylex(void) 490 { 491 u_char buf[8096]; 492 u_char *p, *val; 493 int quotec, next, c; 494 int token; 495 496 top: 497 p = buf; 498 while ((c = lgetc(0)) == ' ' || c == '\t') 499 ; /* nothing */ 500 501 yylval.lineno = file->lineno; 502 if (c == '#') 503 while ((c = lgetc(0)) != '\n' && c != EOF) 504 ; /* nothing */ 505 if (c == '$' && parsebuf == NULL) { 506 while (1) { 507 if ((c = lgetc(0)) == EOF) 508 return (0); 509 510 if (p + 1 >= buf + sizeof(buf) - 1) { 511 yyerror("string too long"); 512 return (findeol()); 513 } 514 if (isalnum(c) || c == '_') { 515 *p++ = c; 516 continue; 517 } 518 *p = '\0'; 519 lungetc(c); 520 break; 521 } 522 val = symget(buf); 523 if (val == NULL) { 524 yyerror("macro '%s' not defined", buf); 525 return (findeol()); 526 } 527 parsebuf = val; 528 parseindex = 0; 529 goto top; 530 } 531 532 switch (c) { 533 case '\'': 534 case '"': 535 quotec = c; 536 while (1) { 537 if ((c = lgetc(quotec)) == EOF) 538 return (0); 539 if (c == '\n') { 540 file->lineno++; 541 continue; 542 } else if (c == '\\') { 543 if ((next = lgetc(quotec)) == EOF) 544 return (0); 545 if (next == quotec || c == ' ' || c == '\t') 546 c = next; 547 else if (next == '\n') { 548 file->lineno++; 549 continue; 550 } else 551 lungetc(next); 552 } else if (c == quotec) { 553 *p = '\0'; 554 break; 555 } else if (c == '\0') { 556 yyerror("syntax error"); 557 return (findeol()); 558 } 559 if (p + 1 >= buf + sizeof(buf) - 1) { 560 yyerror("string too long"); 561 return (findeol()); 562 } 563 *p++ = c; 564 } 565 yylval.v.string = strdup(buf); 566 if (yylval.v.string == NULL) 567 err(1, "yylex: strdup"); 568 return (STRING); 569 } 570 571 #define allowed_to_end_number(x) \ 572 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') 573 574 if (c == '-' || isdigit(c)) { 575 do { 576 *p++ = c; 577 if ((unsigned)(p-buf) >= sizeof(buf)) { 578 yyerror("string too long"); 579 return (findeol()); 580 } 581 } while ((c = lgetc(0)) != EOF && isdigit(c)); 582 lungetc(c); 583 if (p == buf + 1 && buf[0] == '-') 584 goto nodigits; 585 if (c == EOF || allowed_to_end_number(c)) { 586 const char *errstr = NULL; 587 588 *p = '\0'; 589 yylval.v.number = strtonum(buf, LLONG_MIN, 590 LLONG_MAX, &errstr); 591 if (errstr) { 592 yyerror("\"%s\" invalid number: %s", 593 buf, errstr); 594 return (findeol()); 595 } 596 return (NUMBER); 597 } else { 598 nodigits: 599 while (p > buf + 1) 600 lungetc(*--p); 601 c = *--p; 602 if (c == '-') 603 return (c); 604 } 605 } 606 607 #define allowed_in_string(x) \ 608 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ 609 x != '{' && x != '}' && \ 610 x != '!' && x != '=' && x != '#' && \ 611 x != ',')) 612 613 if (isalnum(c) || c == ':' || c == '_') { 614 do { 615 *p++ = c; 616 if ((unsigned)(p-buf) >= sizeof(buf)) { 617 yyerror("string too long"); 618 return (findeol()); 619 } 620 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); 621 lungetc(c); 622 *p = '\0'; 623 if ((token = lookup(buf)) == STRING) 624 if ((yylval.v.string = strdup(buf)) == NULL) 625 err(1, "yylex: strdup"); 626 return (token); 627 } 628 if (c == '\n') { 629 yylval.lineno = file->lineno; 630 file->lineno++; 631 } 632 if (c == EOF) 633 return (0); 634 return (c); 635 } 636 637 struct file * 638 pushfile(const char *name, int secret) 639 { 640 struct file *nfile; 641 642 if ((nfile = calloc(1, sizeof(struct file))) == NULL) { 643 warn("malloc"); 644 return (NULL); 645 } 646 if ((nfile->name = strdup(name)) == NULL) { 647 warn("malloc"); 648 free(nfile); 649 return (NULL); 650 } 651 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { 652 warn("%s", nfile->name); 653 free(nfile->name); 654 free(nfile); 655 return (NULL); 656 } 657 nfile->lineno = 1; 658 TAILQ_INSERT_TAIL(&files, nfile, entry); 659 return (nfile); 660 } 661 662 int 663 popfile(void) 664 { 665 struct file *prev; 666 667 if ((prev = TAILQ_PREV(file, files, entry)) != NULL) 668 prev->errors += file->errors; 669 670 TAILQ_REMOVE(&files, file, entry); 671 fclose(file->stream); 672 free(file->name); 673 free(file); 674 file = prev; 675 return (file ? 0 : EOF); 676 } 677 678 struct iscsi_config * 679 parse_config(char *filename) 680 { 681 struct sym *sym, *next; 682 683 file = pushfile(filename, 1); 684 if (file == NULL) 685 return (NULL); 686 topfile = file; 687 688 conf = calloc(1, sizeof(struct iscsi_config)); 689 if (conf == NULL) 690 return (NULL); 691 SIMPLEQ_INIT(&conf->sessions); 692 693 yyparse(); 694 errors = file->errors; 695 popfile(); 696 697 /* Free macros and check which have not been used. */ 698 for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { 699 next = TAILQ_NEXT(sym, entry); 700 if (!sym->persist) { 701 free(sym->nam); 702 free(sym->val); 703 TAILQ_REMOVE(&symhead, sym, entry); 704 free(sym); 705 } 706 } 707 708 if (errors) { 709 clear_config(conf); 710 return (NULL); 711 } 712 713 return (conf); 714 } 715 716 int 717 cmdline_symset(char *s) 718 { 719 char *sym, *val; 720 int ret; 721 size_t len; 722 723 if ((val = strrchr(s, '=')) == NULL) 724 return (-1); 725 726 len = strlen(s) - strlen(val) + 1; 727 if ((sym = malloc(len)) == NULL) 728 errx(1, "cmdline_symset: malloc"); 729 730 strlcpy(sym, s, len); 731 732 ret = symset(sym, val + 1, 1); 733 free(sym); 734 735 return (ret); 736 } 737 738 char * 739 symget(const char *nam) 740 { 741 struct sym *sym; 742 743 TAILQ_FOREACH(sym, &symhead, entry) 744 if (strcmp(nam, sym->nam) == 0) { 745 sym->used = 1; 746 return (sym->val); 747 } 748 return (NULL); 749 } 750 751 int 752 symset(const char *nam, const char *val, int persist) 753 { 754 struct sym *sym; 755 756 for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); 757 sym = TAILQ_NEXT(sym, entry)) 758 ; /* nothing */ 759 760 if (sym != NULL) { 761 if (sym->persist == 1) 762 return (0); 763 else { 764 free(sym->nam); 765 free(sym->val); 766 TAILQ_REMOVE(&symhead, sym, entry); 767 free(sym); 768 } 769 } 770 if ((sym = calloc(1, sizeof(*sym))) == NULL) 771 return (-1); 772 773 sym->nam = strdup(nam); 774 if (sym->nam == NULL) { 775 free(sym); 776 return (-1); 777 } 778 sym->val = strdup(val); 779 if (sym->val == NULL) { 780 free(sym->nam); 781 free(sym); 782 return (-1); 783 } 784 sym->used = 0; 785 sym->persist = persist; 786 TAILQ_INSERT_TAIL(&symhead, sym, entry); 787 return (0); 788 } 789 790 void 791 clear_config(struct iscsi_config *c) 792 { 793 struct session_ctlcfg *s; 794 795 while ((s = SIMPLEQ_FIRST(&c->sessions))) { 796 SIMPLEQ_REMOVE_HEAD(&c->sessions, entry); 797 free(s->session.TargetName); 798 free(s->session.InitiatorName); 799 free(s); 800 } 801 802 free(c); 803 } 804