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
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 size_t ungetpos;
54 size_t ungetsize;
55 u_char *ungetbuf;
56 int eof_reached;
57 int lineno;
58 int errors;
59 } *file, *topfile;
60 struct file *pushfile(const char *, int);
61 int popfile(void);
62 int yyparse(void);
63 int yylex(void);
64 int yyerror(const char *, ...)
65 __attribute__((__format__ (printf, 1, 2)))
66 __attribute__((__nonnull__ (1)));
67 int kw_cmp(const void *, const void *);
68 int lookup(char *);
69 int igetc(void);
70 int lgetc(int);
71 void lungetc(int);
72 int findeol(void);
73
74 void clear_config(struct iscsi_config *);
75
76 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
77 struct sym {
78 TAILQ_ENTRY(sym) entry;
79 int used;
80 int persist;
81 char *nam;
82 char *val;
83 };
84 int symset(const char *, const char *, int);
85 char *symget(const char *);
86
87 static int errors;
88 static struct iscsi_config *conf;
89 static struct session_config *session;
90
91 struct addrinfo_opts {
92 int af;
93 char *port;
94 } addrinfo_opts;
95
96 typedef 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
125 grammar : /* 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
134 include : 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
149 string : 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
162 varset : 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
180 optnl : '\n' optnl
181 |
182 ;
183
184 nl : '\n' optnl /* one or more newlines */
185 ;
186
187 initiator : 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
214 target : 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
232 targetopts_l : targetopts_l targetoptsl nl
233 | targetoptsl optnl
234 ;
235
236 targetoptsl : 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
253 addrinfo : 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
293 addrinfo_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
305 addrinfo_opts_l : addrinfo_opts_l addrinfo_opt
306 | addrinfo_opt
307 ;
308
309 addrinfo_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
325 port : PORT STRING { $$ = $2; }
326 ;
327
328 af : INET { $$ = PF_INET; }
329 | INET6 { $$ = PF_INET6; }
330 ;
331
332 state : ENABLED { $$ = 0; }
333 | DISABLED { $$ = 1; }
334 ;
335
336 type : NORMAL { $$ = SESSION_TYPE_NORMAL; }
337 | DISCOVERY { $$ = SESSION_TYPE_DISCOVERY; }
338 ;
339
340
341 %%
342
343 struct keywords {
344 const char *k_name;
345 int k_val;
346 };
347
348 int
yyerror(const char * fmt,...)349 yyerror(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
362 int
kw_cmp(const void * k,const void * e)363 kw_cmp(const void *k, const void *e)
364 {
365 return (strcmp(k, ((const struct keywords *)e)->k_name));
366 }
367
368 int
lookup(char * s)369 lookup(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
404 static int expanding;
405
406 int
igetc(void)407 igetc(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
427 int
lgetc(int quotec)428 lgetc(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
461 void
lungetc(int c)462 lungetc(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
477 int
findeol(void)478 findeol(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
495 int
yylex(void)496 yylex(void)
497 {
498 char buf[8096];
499 char *p, *val;
500 int quotec, next, c;
501 int token;
502
503 top:
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 {
611 nodigits:
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
650 struct file *
pushfile(const char * name,int secret)651 pushfile(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
684 int
popfile(void)685 popfile(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
701 struct iscsi_config *
parse_config(char * filename)702 parse_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
738 int
cmdline_symset(char * s)739 cmdline_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
755 char *
symget(const char * nam)756 symget(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
769 int
symset(const char * nam,const char * val,int persist)770 symset(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
809 void
clear_config(struct iscsi_config * c)810 clear_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