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