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