1 /* $OpenBSD: parse.y,v 1.7 2014/11/20 05:51:20 jsg 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 if (symset($1, $3, 0) == -1) 159 err(1, "cannot store variable"); 160 free($1); 161 free($3); 162 } 163 ; 164 165 optnl : '\n' optnl 166 | 167 ; 168 169 nl : '\n' optnl /* one or more newlines */ 170 ; 171 172 initiator : ISID STRING NUMBER NUMBER { 173 u_int32_t mask1, mask2; 174 175 if (!strcasecmp($2, "oui")) { 176 conf->initiator.isid_base = ISCSI_ISID_OUI; 177 mask1 = 0x3fffff00; 178 mask2 = 0x000000ff; 179 } else if (!strcasecmp($2, "en")) { 180 conf->initiator.isid_base = ISCSI_ISID_EN; 181 mask1 = 0x00ffffff; 182 mask2 = 0; 183 } else if (!strcasecmp($2, "rand")) { 184 conf->initiator.isid_base = ISCSI_ISID_RAND; 185 mask1 = 0x00ffffff; 186 mask2 = 0; 187 } else { 188 yyerror("isid type %s unknown", $2); 189 free($2); 190 YYERROR; 191 } 192 free($2); 193 conf->initiator.isid_base |= $3 & mask1; 194 conf->initiator.isid_base |= ($4 >> 16) & mask2; 195 conf->initiator.isid_qual = $4; 196 } 197 ; 198 199 target : TARGET STRING { 200 struct session_ctlcfg *scelm; 201 202 scelm = calloc(1, sizeof(*scelm)); 203 session = &scelm->session; 204 if (strlcpy(session->SessionName, $2, 205 sizeof(session->SessionName)) >= 206 sizeof(session->SessionName)) { 207 yyerror("target name \"%s\" too long", $2); 208 free($2); 209 free(scelm); 210 YYERROR; 211 } 212 free($2); 213 SIMPLEQ_INSERT_TAIL(&conf->sessions, scelm, entry); 214 } '{' optnl targetopts_l '}' 215 ; 216 217 targetopts_l : targetopts_l targetoptsl nl 218 | targetoptsl optnl 219 ; 220 221 targetoptsl : state { session->disabled = $1; } 222 | type { session->SessionType = $1; } 223 | TARGETNAME STRING { session->TargetName = $2; } 224 | INITIATORNAME STRING { session->InitiatorName = $2; } 225 | TARGETADDR addrinfo { 226 bcopy($2->ai_addr, &session->connection.TargetAddr, 227 $2->ai_addr->sa_len); 228 freeaddrinfo($2); 229 } 230 | INITIATORADDR addrinfo { 231 ((struct sockaddr_in *)$2->ai_addr)->sin_port = 0; 232 bcopy($2->ai_addr, &session->connection.LocalAddr, 233 $2->ai_addr->sa_len); 234 freeaddrinfo($2); 235 } 236 ; 237 238 addrinfo : STRING addrinfo_opts { 239 struct addrinfo hints; 240 char *hostname; 241 int error; 242 243 $$ = NULL; 244 245 if ($2.port == NULL) { 246 if (($2.port = strdup("iscsi")) == NULL) { 247 free($1); 248 yyerror("port strdup"); 249 YYERROR; 250 } 251 } 252 253 memset(&hints, 0, sizeof(hints)); 254 hints.ai_family = $2.af; 255 hints.ai_socktype = SOCK_STREAM; 256 hints.ai_protocol = IPPROTO_TCP; 257 258 if (strcmp($1, "*") == 0) { 259 hostname = NULL; 260 hints.ai_flags = AI_PASSIVE; 261 } else 262 hostname = $1; 263 264 error = getaddrinfo(hostname, $2.port, &hints, &$$); 265 if (error) { 266 yyerror("%s (%s %s)", gai_strerror(error), 267 $1, $2.port); 268 free($1); 269 free($2.port); 270 YYERROR; 271 } 272 273 free($1); 274 free($2.port); 275 } 276 ; 277 278 addrinfo_opts : { 279 addrinfo_opts.port = NULL; 280 addrinfo_opts.af = PF_UNSPEC; 281 } 282 addrinfo_opts_l { $$ = addrinfo_opts; } 283 | /* empty */ { 284 addrinfo_opts.port = NULL; 285 addrinfo_opts.af = PF_UNSPEC; 286 $$ = addrinfo_opts; 287 } 288 ; 289 290 addrinfo_opts_l : addrinfo_opts_l addrinfo_opt 291 | addrinfo_opt 292 ; 293 294 addrinfo_opt : port { 295 if (addrinfo_opts.port != NULL) { 296 yyerror("port cannot be redefined"); 297 YYERROR; 298 } 299 addrinfo_opts.port = $1; 300 } 301 | af { 302 if (addrinfo_opts.af != PF_UNSPEC) { 303 yyerror("address family cannot be redefined"); 304 YYERROR; 305 } 306 addrinfo_opts.af = $1; 307 } 308 ; 309 310 port : PORT STRING { $$ = $2; } 311 ; 312 313 af : INET { $$ = PF_INET; } 314 | INET6 { $$ = PF_INET6; } 315 ; 316 317 state : ENABLED { $$ = 0; } 318 | DISABLED { $$ = 1; } 319 ; 320 321 type : NORMAL { $$ = SESSION_TYPE_NORMAL; } 322 | DISCOVERY { $$ = SESSION_TYPE_DISCOVERY; } 323 ; 324 325 326 %% 327 328 struct keywords { 329 const char *k_name; 330 int k_val; 331 }; 332 333 int 334 yyerror(const char *fmt, ...) 335 { 336 va_list ap; 337 338 file->errors++; 339 va_start(ap, fmt); 340 fprintf(stderr, "%s:%d: ", file->name, yylval.lineno); 341 vfprintf(stderr, fmt, ap); 342 fprintf(stderr, "\n"); 343 va_end(ap); 344 return (0); 345 } 346 347 int 348 kw_cmp(const void *k, const void *e) 349 { 350 return (strcmp(k, ((const struct keywords *)e)->k_name)); 351 } 352 353 int 354 lookup(char *s) 355 { 356 /* this has to be sorted always */ 357 static const struct keywords keywords[] = { 358 {"address", ADDRESS}, 359 {"disabled", DISABLED}, 360 {"discovery", DISCOVERY}, 361 {"enabled", ENABLED}, 362 {"include", INCLUDE}, 363 {"inet", INET}, 364 {"inet4", INET}, 365 {"inet6", INET6}, 366 {"initiatoraddr", INITIATORADDR}, 367 {"initiatorname", INITIATORNAME}, 368 {"isid", ISID}, 369 {"normal", NORMAL}, 370 {"port", PORT}, 371 {"target", TARGET}, 372 {"targetaddr", TARGETADDR}, 373 {"targetname", TARGETNAME} 374 }; 375 const struct keywords *p; 376 377 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), 378 sizeof(keywords[0]), kw_cmp); 379 380 if (p) 381 return (p->k_val); 382 else 383 return (STRING); 384 } 385 386 #define MAXPUSHBACK 128 387 388 u_char *parsebuf; 389 int parseindex; 390 u_char pushback_buffer[MAXPUSHBACK]; 391 int pushback_index; 392 393 int 394 lgetc(int quotec) 395 { 396 int c, next; 397 398 if (parsebuf) { 399 /* Read character from the parsebuffer instead of input. */ 400 if (parseindex >= 0) { 401 c = parsebuf[parseindex++]; 402 if (c != '\0') 403 return (c); 404 parsebuf = NULL; 405 } else 406 parseindex++; 407 } 408 409 if (pushback_index) 410 return (pushback_buffer[--pushback_index]); 411 412 if (quotec) { 413 if ((c = getc(file->stream)) == EOF) { 414 yyerror("reached end of file while parsing " 415 "quoted string"); 416 if (file == topfile || popfile() == EOF) 417 return (EOF); 418 return (quotec); 419 } 420 return (c); 421 } 422 423 while ((c = getc(file->stream)) == '\\') { 424 next = getc(file->stream); 425 if (next != '\n') { 426 c = next; 427 break; 428 } 429 yylval.lineno = file->lineno; 430 file->lineno++; 431 } 432 433 while (c == EOF) { 434 if (file == topfile || popfile() == EOF) 435 return (EOF); 436 c = getc(file->stream); 437 } 438 return (c); 439 } 440 441 int 442 lungetc(int c) 443 { 444 if (c == EOF) 445 return (EOF); 446 if (parsebuf) { 447 parseindex--; 448 if (parseindex >= 0) 449 return (c); 450 } 451 if (pushback_index < MAXPUSHBACK-1) 452 return (pushback_buffer[pushback_index++] = c); 453 else 454 return (EOF); 455 } 456 457 int 458 findeol(void) 459 { 460 int c; 461 462 parsebuf = NULL; 463 464 /* skip to either EOF or the first real EOL */ 465 while (1) { 466 if (pushback_index) 467 c = pushback_buffer[--pushback_index]; 468 else 469 c = lgetc(0); 470 if (c == '\n') { 471 file->lineno++; 472 break; 473 } 474 if (c == EOF) 475 break; 476 } 477 return (ERROR); 478 } 479 480 int 481 yylex(void) 482 { 483 u_char buf[8096]; 484 u_char *p, *val; 485 int quotec, next, c; 486 int token; 487 488 top: 489 p = buf; 490 while ((c = lgetc(0)) == ' ' || c == '\t') 491 ; /* nothing */ 492 493 yylval.lineno = file->lineno; 494 if (c == '#') 495 while ((c = lgetc(0)) != '\n' && c != EOF) 496 ; /* nothing */ 497 if (c == '$' && parsebuf == NULL) { 498 while (1) { 499 if ((c = lgetc(0)) == EOF) 500 return (0); 501 502 if (p + 1 >= buf + sizeof(buf) - 1) { 503 yyerror("string too long"); 504 return (findeol()); 505 } 506 if (isalnum(c) || c == '_') { 507 *p++ = c; 508 continue; 509 } 510 *p = '\0'; 511 lungetc(c); 512 break; 513 } 514 val = symget(buf); 515 if (val == NULL) { 516 yyerror("macro '%s' not defined", buf); 517 return (findeol()); 518 } 519 parsebuf = val; 520 parseindex = 0; 521 goto top; 522 } 523 524 switch (c) { 525 case '\'': 526 case '"': 527 quotec = c; 528 while (1) { 529 if ((c = lgetc(quotec)) == EOF) 530 return (0); 531 if (c == '\n') { 532 file->lineno++; 533 continue; 534 } else if (c == '\\') { 535 if ((next = lgetc(quotec)) == EOF) 536 return (0); 537 if (next == quotec || c == ' ' || c == '\t') 538 c = next; 539 else if (next == '\n') { 540 file->lineno++; 541 continue; 542 } else 543 lungetc(next); 544 } else if (c == quotec) { 545 *p = '\0'; 546 break; 547 } else if (c == '\0') { 548 yyerror("syntax error"); 549 return (findeol()); 550 } 551 if (p + 1 >= buf + sizeof(buf) - 1) { 552 yyerror("string too long"); 553 return (findeol()); 554 } 555 *p++ = c; 556 } 557 yylval.v.string = strdup(buf); 558 if (yylval.v.string == NULL) 559 err(1, "yylex: strdup"); 560 return (STRING); 561 } 562 563 #define allowed_to_end_number(x) \ 564 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') 565 566 if (c == '-' || isdigit(c)) { 567 do { 568 *p++ = c; 569 if ((unsigned)(p-buf) >= sizeof(buf)) { 570 yyerror("string too long"); 571 return (findeol()); 572 } 573 } while ((c = lgetc(0)) != EOF && isdigit(c)); 574 lungetc(c); 575 if (p == buf + 1 && buf[0] == '-') 576 goto nodigits; 577 if (c == EOF || allowed_to_end_number(c)) { 578 const char *errstr = NULL; 579 580 *p = '\0'; 581 yylval.v.number = strtonum(buf, LLONG_MIN, 582 LLONG_MAX, &errstr); 583 if (errstr) { 584 yyerror("\"%s\" invalid number: %s", 585 buf, errstr); 586 return (findeol()); 587 } 588 return (NUMBER); 589 } else { 590 nodigits: 591 while (p > buf + 1) 592 lungetc(*--p); 593 c = *--p; 594 if (c == '-') 595 return (c); 596 } 597 } 598 599 #define allowed_in_string(x) \ 600 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ 601 x != '{' && x != '}' && \ 602 x != '!' && x != '=' && x != '#' && \ 603 x != ',')) 604 605 if (isalnum(c) || c == ':' || c == '_') { 606 do { 607 *p++ = c; 608 if ((unsigned)(p-buf) >= sizeof(buf)) { 609 yyerror("string too long"); 610 return (findeol()); 611 } 612 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); 613 lungetc(c); 614 *p = '\0'; 615 if ((token = lookup(buf)) == STRING) 616 if ((yylval.v.string = strdup(buf)) == NULL) 617 err(1, "yylex: strdup"); 618 return (token); 619 } 620 if (c == '\n') { 621 yylval.lineno = file->lineno; 622 file->lineno++; 623 } 624 if (c == EOF) 625 return (0); 626 return (c); 627 } 628 629 struct file * 630 pushfile(const char *name, int secret) 631 { 632 struct file *nfile; 633 634 if ((nfile = calloc(1, sizeof(struct file))) == NULL) { 635 warn("malloc"); 636 return (NULL); 637 } 638 if ((nfile->name = strdup(name)) == NULL) { 639 warn("malloc"); 640 free(nfile); 641 return (NULL); 642 } 643 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { 644 warn("%s", nfile->name); 645 free(nfile->name); 646 free(nfile); 647 return (NULL); 648 } 649 nfile->lineno = 1; 650 TAILQ_INSERT_TAIL(&files, nfile, entry); 651 return (nfile); 652 } 653 654 int 655 popfile(void) 656 { 657 struct file *prev; 658 659 if ((prev = TAILQ_PREV(file, files, entry)) != NULL) 660 prev->errors += file->errors; 661 662 TAILQ_REMOVE(&files, file, entry); 663 fclose(file->stream); 664 free(file->name); 665 free(file); 666 file = prev; 667 return (file ? 0 : EOF); 668 } 669 670 struct iscsi_config * 671 parse_config(char *filename) 672 { 673 struct sym *sym, *next; 674 675 file = pushfile(filename, 1); 676 if (file == NULL) 677 return (NULL); 678 topfile = file; 679 680 conf = calloc(1, sizeof(struct iscsi_config)); 681 if (conf == NULL) 682 return (NULL); 683 SIMPLEQ_INIT(&conf->sessions); 684 685 yyparse(); 686 errors = file->errors; 687 popfile(); 688 689 /* Free macros and check which have not been used. */ 690 for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { 691 next = TAILQ_NEXT(sym, entry); 692 if (!sym->persist) { 693 free(sym->nam); 694 free(sym->val); 695 TAILQ_REMOVE(&symhead, sym, entry); 696 free(sym); 697 } 698 } 699 700 if (errors) { 701 clear_config(conf); 702 return (NULL); 703 } 704 705 return (conf); 706 } 707 708 int 709 cmdline_symset(char *s) 710 { 711 char *sym, *val; 712 int ret; 713 size_t len; 714 715 if ((val = strrchr(s, '=')) == NULL) 716 return (-1); 717 718 len = strlen(s) - strlen(val) + 1; 719 if ((sym = malloc(len)) == NULL) 720 errx(1, "cmdline_symset: malloc"); 721 722 strlcpy(sym, s, len); 723 724 ret = symset(sym, val + 1, 1); 725 free(sym); 726 727 return (ret); 728 } 729 730 char * 731 symget(const char *nam) 732 { 733 struct sym *sym; 734 735 TAILQ_FOREACH(sym, &symhead, entry) 736 if (strcmp(nam, sym->nam) == 0) { 737 sym->used = 1; 738 return (sym->val); 739 } 740 return (NULL); 741 } 742 743 int 744 symset(const char *nam, const char *val, int persist) 745 { 746 struct sym *sym; 747 748 for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); 749 sym = TAILQ_NEXT(sym, entry)) 750 ; /* nothing */ 751 752 if (sym != NULL) { 753 if (sym->persist == 1) 754 return (0); 755 else { 756 free(sym->nam); 757 free(sym->val); 758 TAILQ_REMOVE(&symhead, sym, entry); 759 free(sym); 760 } 761 } 762 if ((sym = calloc(1, sizeof(*sym))) == NULL) 763 return (-1); 764 765 sym->nam = strdup(nam); 766 if (sym->nam == NULL) { 767 free(sym); 768 return (-1); 769 } 770 sym->val = strdup(val); 771 if (sym->val == NULL) { 772 free(sym->nam); 773 free(sym); 774 return (-1); 775 } 776 sym->used = 0; 777 sym->persist = persist; 778 TAILQ_INSERT_TAIL(&symhead, sym, entry); 779 return (0); 780 } 781 782 void 783 clear_config(struct iscsi_config *c) 784 { 785 struct session_ctlcfg *s; 786 787 while ((s = SIMPLEQ_FIRST(&c->sessions))) { 788 SIMPLEQ_REMOVE_HEAD(&c->sessions, entry); 789 free(s->session.TargetName); 790 free(s->session.InitiatorName); 791 free(s); 792 } 793 794 free(c); 795 } 796