GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/switchd/parse.y Lines: 108 267 40.4 %
Date: 2017-11-07 Branches: 68 195 34.9 %

Line Branch Exec Source
1
/*	$OpenBSD: parse.y,v 1.6 2017/08/28 06:00:05 florian Exp $	*/
2
3
/*
4
 * Copyright (c) 2007-2016 Reyk Floeter <reyk@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/stat.h>
29
#include <sys/un.h>
30
31
#include <ctype.h>
32
#include <errno.h>
33
#include <limits.h>
34
#include <netdb.h>
35
#include <stdarg.h>
36
#include <stdio.h>
37
#include <syslog.h>
38
#include <unistd.h>
39
40
#include "switchd.h"
41
42
TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
43
static struct file {
44
	TAILQ_ENTRY(file)	 entry;
45
	FILE			*stream;
46
	char			*name;
47
	int			 lineno;
48
	int			 errors;
49
} *file, *topfile;
50
struct file	*pushfile(const char *, int);
51
int		 popfile(void);
52
int		 yyparse(void);
53
int		 yylex(void);
54
int		 yyerror(const char *, ...)
55
    __attribute__((__format__ (printf, 1, 2)))
56
    __attribute__((__nonnull__ (1)));
57
int		 kw_cmp(const void *, const void *);
58
int		 lookup(char *);
59
int		 lgetc(int);
60
int		 lungetc(int);
61
int		 findeol(void);
62
int		 host(const char *, struct sockaddr *, socklen_t);
63
64
struct switchd *conf;
65
66
TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
67
struct sym {
68
	TAILQ_ENTRY(sym)	 entry;
69
	int			 used;
70
	int			 persist;
71
	char			*nam;
72
	char			*val;
73
};
74
int		 symset(const char *, const char *, int);
75
char		*symget(const char *);
76
77
typedef struct {
78
	union {
79
		int64_t		 number;
80
		char		*string;
81
		in_port_t	 port;
82
		struct switch_client
83
				*conn;
84
	} v;
85
	int lineno;
86
} YYSTYPE;
87
88
%}
89
90
%token	INCLUDE ERROR LISTEN ON TLS PORT DEVICE FORWARD TO
91
%token	<v.string>	STRING
92
%token  <v.number>	NUMBER
93
%type	<v.number>	opttls
94
%type	<v.conn>	optofcconn
95
%type	<v.port>	port
96
97
%%
98
99
grammar		: /* empty */
100
		| grammar '\n'
101
		| grammar include '\n'
102
		| grammar listen '\n'
103
		| grammar device '\n'
104
		| grammar varset '\n'
105
		| grammar error '\n'		{ file->errors++; }
106
		;
107
108
include		: INCLUDE STRING		{
109
			struct file	*nfile;
110
111
			if ((nfile = pushfile($2, 0)) == NULL) {
112
				yyerror("failed to include file %s", $2);
113
				free($2);
114
				YYERROR;
115
			}
116
			free($2);
117
118
			file = nfile;
119
			lungetc('\n');
120
		}
121
		;
122
123
listen		: LISTEN ON STRING opttls port {
124
			if (host($3,
125
			    (struct sockaddr *)&conf->sc_server.srv_addr,
126
			    sizeof(conf->sc_server.srv_addr)) != 0) {
127
				free($3);
128
				YYERROR;
129
			}
130
			free($3);
131
			conf->sc_server.srv_tls = $4;
132
			((struct sockaddr_in *)&conf->sc_server.srv_addr)
133
			    ->sin_port = $5;
134
		}
135
		| LISTEN ON STRING opttls {
136
			if (host($3,
137
			    (struct sockaddr *)&conf->sc_server.srv_addr,
138
			    sizeof(conf->sc_server.srv_addr)) != 0) {
139
				free($3);
140
				YYERROR;
141
			}
142
			free($3);
143
		}
144
		;
145
146
port		: PORT NUMBER {
147
			if ($2 <= 0 || $2 > (int)USHRT_MAX) {
148
				yyerror("invalid port: %lld", $2);
149
				YYERROR;
150
			}
151
			$$ = htons($2);
152
		}
153
		;
154
155
opttls		: /* empty */	{ $$ = 0; }
156
		| TLS		{ $$ = 1; }
157
		;
158
159
device		: DEVICE STRING optofcconn {
160
			struct switch_client		*c;
161
			struct switch_address		 s;
162
			struct sockaddr_un		*un;
163
164
			memset(&s, 0, sizeof(s));
165
			un = (struct sockaddr_un *)&s.swa_addr;
166
167
			if (*$2 != '/') {
168
				yyerror("not an absolute path: %s", $2);
169
				free($2);
170
				YYERROR;
171
			}
172
173
			un->sun_family = AF_LOCAL;
174
			un->sun_len = sizeof(*un);
175
			if (strlcpy(un->sun_path, $2,
176
			    sizeof(un->sun_path)) >= sizeof(un->sun_path)) {
177
				yyerror("device name is too long: %s", $2);
178
				free($2);
179
				YYERROR;
180
			}
181
			free($2);
182
183
			TAILQ_FOREACH(c, &conf->sc_clients, swc_next) {
184
				if (sockaddr_cmp((struct sockaddr *)
185
				    &c->swc_addr.swa_addr,
186
				    (struct sockaddr *)&s.swa_addr, -1) == 0)
187
					break;
188
			}
189
			if (c != NULL) {
190
				yyerror("device name is duplicated");
191
				YYERROR;
192
			}
193
194
			memcpy(&$3->swc_addr, &s, sizeof(s));
195
196
			TAILQ_INSERT_TAIL(&conf->sc_clients, $3, swc_next);
197
		}
198
		;
199
200
optofcconn	: /* empty */ {
201
			if (($$ = calloc(1,
202
			    sizeof(struct switch_client))) == NULL)
203
				fatal("calloc");
204
			$$->swc_addr.swa_type = $$->swc_target.swa_type =
205
			    SWITCH_CONN_LOCAL;
206
		}
207
		| FORWARD TO STRING {
208
			size_t	 len;
209
210
			if (($$ = calloc(1,
211
			    sizeof(struct switch_client))) == NULL)
212
				fatal("calloc");
213
			len = 4;
214
			if (strncmp($3, "tcp:", len) == 0)
215
				$$->swc_target.swa_type = SWITCH_CONN_TCP;
216
			else if (strncmp($3, "tls:", len) == 0)
217
				$$->swc_target.swa_type = SWITCH_CONN_TLS;
218
			else {
219
				len = 0;
220
				$$->swc_target.swa_type = SWITCH_CONN_TCP;
221
			}
222
			if (parsehostport($3 + len,
223
			    (struct sockaddr *)&$$->swc_target.swa_addr,
224
			    sizeof($$->swc_target.swa_addr)) == -1) {
225
				yyerror("could not parse host and port part "
226
				    "of forward target");
227
				free($$);
228
				free($3);
229
				YYERROR;
230
			}
231
			free($3);
232
		}
233
		;
234
235
varset		: STRING '=' STRING	{
236
			if (symset($1, $3, 0) == -1)
237
				fatal("cannot store variable");
238
			free($1);
239
			free($3);
240
		}
241
		;
242
243
%%
244
245
struct keywords {
246
	const char	*k_name;
247
	int		 k_val;
248
};
249
250
int
251
yyerror(const char *fmt, ...)
252
{
253
	va_list		 ap;
254
	char		*msg;
255
256
	file->errors++;
257
	va_start(ap, fmt);
258
	if (vasprintf(&msg, fmt, ap) == -1)
259
		fatal("yyerror vasprintf");
260
	va_end(ap);
261
	log_warnx("%s:%d: %s", file->name, yylval.lineno, msg);
262
	free(msg);
263
	return (0);
264
}
265
266
int
267
kw_cmp(const void *k, const void *e)
268
{
269
330
	return (strcmp(k, ((const struct keywords *)e)->k_name));
270
}
271
272
int
273
lookup(char *s)
274
{
275
	/* this has to be sorted always */
276
	static const struct keywords keywords[] = {
277
		{ "device",		DEVICE },
278
		{ "forward",		FORWARD },
279
		{ "include",		INCLUDE },
280
		{ "listen",		LISTEN },
281
		{ "on",			ON },
282
		{ "port",		PORT },
283
		{ "tls",		TLS },
284
		{ "to",			TO },
285
	};
286
	const struct keywords	*p;
287
288
120
	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
289
	    sizeof(keywords[0]), kw_cmp);
290
291
60
	if (p)
292
45
		return (p->k_val);
293
	else
294
15
		return (STRING);
295
60
}
296
297
#define MAXPUSHBACK	128
298
299
u_char	*parsebuf;
300
int	 parseindex;
301
u_char	 pushback_buffer[MAXPUSHBACK];
302
int	 pushback_index = 0;
303
304
int
305
lgetc(int quotec)
306
{
307
	int		c, next;
308
309
1940
	if (parsebuf) {
310
		/* Read character from the parsebuffer instead of input. */
311
		if (parseindex >= 0) {
312
			c = parsebuf[parseindex++];
313
			if (c != '\0')
314
				return (c);
315
			parsebuf = NULL;
316
		} else
317
			parseindex++;
318
	}
319
320
970
	if (pushback_index)
321
120
		return (pushback_buffer[--pushback_index]);
322
323
850
	if (quotec) {
324
		if ((c = getc(file->stream)) == EOF) {
325
			yyerror("reached end of file while parsing "
326
			    "quoted string");
327
			if (file == topfile || popfile() == EOF)
328
				return (EOF);
329
			return (quotec);
330
		}
331
		return (c);
332
	}
333
334

3400
	while ((c = getc(file->stream)) == '\\') {
335
		next = getc(file->stream);
336
		if (next != '\n') {
337
			c = next;
338
			break;
339
		}
340
		yylval.lineno = file->lineno;
341
		file->lineno++;
342
	}
343
850
	if (c == '\t' || c == ' ') {
344
		/* Compress blanks to a single space. */
345
		do {
346

360
			c = getc(file->stream);
347
90
		} while (c == '\t' || c == ' ');
348
90
		ungetc(c, file->stream);
349
		c = ' ';
350
90
	}
351
352
850
	while (c == EOF) {
353

15
		if (file == topfile || popfile() == EOF)
354
15
			return (EOF);
355
		c = getc(file->stream);
356
	}
357
835
	return (c);
358
970
}
359
360
int
361
lungetc(int c)
362
{
363
240
	if (c == EOF)
364
		return (EOF);
365
120
	if (parsebuf) {
366
		parseindex--;
367
		if (parseindex >= 0)
368
			return (c);
369
	}
370
120
	if (pushback_index < MAXPUSHBACK-1)
371
120
		return (pushback_buffer[pushback_index++] = c);
372
	else
373
		return (EOF);
374
120
}
375
376
int
377
findeol(void)
378
{
379
	int	c;
380
381
	parsebuf = NULL;
382
383
	/* skip to either EOF or the first real EOL */
384
	while (1) {
385
		if (pushback_index)
386
			c = pushback_buffer[--pushback_index];
387
		else
388
			c = lgetc(0);
389
		if (c == '\n') {
390
			file->lineno++;
391
			break;
392
		}
393
		if (c == EOF)
394
			break;
395
	}
396
	return (ERROR);
397
}
398
399
int
400
yylex(void)
401
{
402
240
	u_char	 buf[8096];
403
	u_char	*p, *val;
404
	int	 quotec, next, c;
405
120
	int	 token;
406
407
top:
408
120
	p = buf;
409
300
	while ((c = lgetc(0)) == ' ' || c == '\t')
410
		; /* nothing */
411
412
120
	yylval.lineno = file->lineno;
413
120
	if (c == '#')
414
370
		while ((c = lgetc(0)) != '\n' && c != EOF)
415
			; /* nothing */
416
120
	if (c == '$' && parsebuf == NULL) {
417
		while (1) {
418
			if ((c = lgetc(0)) == EOF)
419
				return (0);
420
421
			if (p + 1 >= buf + sizeof(buf) - 1) {
422
				yyerror("string too long");
423
				return (findeol());
424
			}
425
			if (isalnum(c) || c == '_') {
426
				*p++ = c;
427
				continue;
428
			}
429
			*p = '\0';
430
			lungetc(c);
431
			break;
432
		}
433
		val = symget(buf);
434
		if (val == NULL) {
435
			yyerror("macro '%s' not defined", buf);
436
			return (findeol());
437
		}
438
		parsebuf = val;
439
		parseindex = 0;
440
		goto top;
441
	}
442
443
120
	switch (c) {
444
	case '\'':
445
	case '"':
446
		quotec = c;
447
		while (1) {
448
			if ((c = lgetc(quotec)) == EOF)
449
				return (0);
450
			if (c == '\n') {
451
				file->lineno++;
452
				continue;
453
			} else if (c == '\\') {
454
				if ((next = lgetc(quotec)) == EOF)
455
					return (0);
456
				if (next == quotec || c == ' ' || c == '\t')
457
					c = next;
458
				else if (next == '\n') {
459
					file->lineno++;
460
					continue;
461
				} else
462
					lungetc(next);
463
			} else if (c == quotec) {
464
				*p = '\0';
465
				break;
466
			} else if (c == '\0') {
467
				yyerror("syntax error");
468
				return (findeol());
469
			}
470
			if (p + 1 >= buf + sizeof(buf) - 1) {
471
				yyerror("string too long");
472
				return (findeol());
473
			}
474
			*p++ = c;
475
		}
476
		yylval.v.string = strdup(buf);
477
		if (yylval.v.string == NULL)
478
			fatal("yylex: strdup");
479
		return (STRING);
480
	}
481
482
#define allowed_to_end_number(x) \
483
	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
484
485

240
	if (c == '-' || isdigit(c)) {
486
		do {
487
105
			*p++ = c;
488
105
			if ((unsigned)(p-buf) >= sizeof(buf)) {
489
				yyerror("string too long");
490
				return (findeol());
491
			}
492

210
		} while ((c = lgetc(0)) != EOF && isdigit(c));
493
30
		lungetc(c);
494

30
		if (p == buf + 1 && buf[0] == '-')
495
			goto nodigits;
496

60
		if (c == EOF || allowed_to_end_number(c)) {
497
15
			const char *errstr = NULL;
498
499
15
			*p = '\0';
500
15
			yylval.v.number = strtonum(buf, LLONG_MIN,
501
			    LLONG_MAX, &errstr);
502
15
			if (errstr) {
503
				yyerror("\"%s\" invalid number: %s",
504
				    buf, errstr);
505
				return (findeol());
506
			}
507
15
			return (NUMBER);
508
15
		} else {
509
nodigits:
510
75
			while (p > buf + 1)
511
30
				lungetc(*--p);
512
			c = *--p;
513
15
			if (c == '-')
514
				return (c);
515
		}
516
	}
517
518
#define allowed_in_string(x) \
519
	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
520
	x != '{' && x != '}' && \
521
	x != '!' && x != '=' && x != '#' && \
522
	x != ','))
523
524
105
	if (isalnum(c) || c == ':' || c == '_' || c == '/') {
525
		do {
526
315
			*p++ = c;
527
315
			if ((unsigned)(p-buf) >= sizeof(buf)) {
528
				yyerror("string too long");
529
				return (findeol());
530
			}
531

735
		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
532
60
		lungetc(c);
533
60
		*p = '\0';
534
60
		if ((token = lookup(buf)) == STRING)
535
15
			if ((yylval.v.string = strdup(buf)) == NULL)
536
				fatal("yylex: strdup");
537
60
		return (token);
538
	}
539
45
	if (c == '\n') {
540
30
		yylval.lineno = file->lineno;
541
30
		file->lineno++;
542
30
	}
543
45
	if (c == EOF)
544
15
		return (0);
545
30
	return (c);
546
120
}
547
548
struct file *
549
pushfile(const char *name, int secret)
550
{
551
	struct file	*nfile;
552
553
30
	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
554
		log_warn("malloc");
555
		return (NULL);
556
	}
557
15
	if ((nfile->name = strdup(name)) == NULL) {
558
		log_warn("malloc");
559
		free(nfile);
560
		return (NULL);
561
	}
562
15
	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
563
		free(nfile->name);
564
		free(nfile);
565
		return (NULL);
566
	}
567
15
	nfile->lineno = 1;
568
15
	TAILQ_INSERT_TAIL(&files, nfile, entry);
569
15
	return (nfile);
570
15
}
571
572
int
573
popfile(void)
574
{
575
	struct file	*prev;
576
577
30
	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
578
		prev->errors += file->errors;
579
580
30
	TAILQ_REMOVE(&files, file, entry);
581
15
	fclose(file->stream);
582
15
	free(file->name);
583
15
	free(file);
584
15
	file = prev;
585
15
	return (file ? 0 : EOF);
586
}
587
588
int
589
parse_config(const char *filename, struct switchd *sc)
590
{
591
	struct sym		*sym;
592
	int			 errors = 0;
593
	struct sockaddr_in	*sin4;
594
595
30
	conf = sc;
596
597
	/* Set the default 0.0.0.0 6633/tcp */
598
15
	memset(&conf->sc_server.srv_addr, 0, sizeof(conf->sc_server.srv_addr));
599
15
	sin4 = (struct sockaddr_in *)&conf->sc_server.srv_addr;
600
15
	sin4->sin_family = AF_INET;
601
15
	sin4->sin_port = htons(SWITCHD_CTLR_PORT);
602
15
	sin4->sin_len = sizeof(struct sockaddr_in);
603
604
15
	if ((file = pushfile(filename, 0)) == NULL) {
605
		log_warn("failed to open %s", filename);
606
		return (0);
607
	}
608
15
	topfile = file;
609
15
	setservent(1);
610
611
15
	yyparse();
612
15
	errors = file->errors;
613
15
	popfile();
614
615
15
	endservent();
616
617
	/* Free macros and check which have not been used. */
618
30
	while ((sym = TAILQ_FIRST(&symhead))) {
619
		if (!sym->used)
620
			log_debug("warning: macro '%s' not "
621
			    "used\n", sym->nam);
622
		free(sym->nam);
623
		free(sym->val);
624
		TAILQ_REMOVE(&symhead, sym, entry);
625
		free(sym);
626
	}
627
628
15
	return (errors ? -1 : 0);
629
15
}
630
631
int
632
symset(const char *nam, const char *val, int persist)
633
{
634
	struct sym	*sym;
635
636
	TAILQ_FOREACH(sym, &symhead, entry) {
637
		if (strcmp(nam, sym->nam) == 0)
638
			break;
639
	}
640
641
	if (sym != NULL) {
642
		if (sym->persist == 1)
643
			return (0);
644
		else {
645
			free(sym->nam);
646
			free(sym->val);
647
			TAILQ_REMOVE(&symhead, sym, entry);
648
			free(sym);
649
		}
650
	}
651
	if ((sym = calloc(1, sizeof(*sym))) == NULL)
652
		return (-1);
653
654
	sym->nam = strdup(nam);
655
	if (sym->nam == NULL) {
656
		free(sym);
657
		return (-1);
658
	}
659
	sym->val = strdup(val);
660
	if (sym->val == NULL) {
661
		free(sym->nam);
662
		free(sym);
663
		return (-1);
664
	}
665
	sym->used = 0;
666
	sym->persist = persist;
667
	TAILQ_INSERT_TAIL(&symhead, sym, entry);
668
	return (0);
669
}
670
671
int
672
cmdline_symset(char *s)
673
{
674
	char	*sym, *val;
675
	int	ret;
676
	size_t	len;
677
678
	if ((val = strrchr(s, '=')) == NULL)
679
		return (-1);
680
681
	len = (val - s) + 1;
682
	if ((sym = malloc(len)) == NULL)
683
		fatal("cmdline_symset: malloc");
684
685
	(void)strlcpy(sym, s, len);
686
687
	ret = symset(sym, val + 1, 1);
688
	free(sym);
689
690
	return (ret);
691
}
692
693
char *
694
symget(const char *nam)
695
{
696
	struct sym	*sym;
697
698
	TAILQ_FOREACH(sym, &symhead, entry) {
699
		if (strcmp(nam, sym->nam) == 0) {
700
			sym->used = 1;
701
			return (sym->val);
702
		}
703
	}
704
	return (NULL);
705
}
706
707
int
708
host(const char *str, struct sockaddr *sa, socklen_t salen)
709
{
710
30
	struct addrinfo  hints, *ai0;
711
	int		 error;
712
713
15
	memset(&hints, 0, sizeof(hints));
714
15
	hints.ai_flags = AI_NUMERICHOST;
715
15
	hints.ai_family = AF_UNSPEC;
716
717
15
	if ((error = getaddrinfo(str, NULL, &hints, &ai0)) != 0) {
718
		yyerror("invalid listen address: %s: %s", str,
719
		    gai_strerror(error));
720
		return (-1);
721
	}
722
15
	if (salen >= ai0->ai_addrlen)
723
15
		memcpy(sa, ai0->ai_addr, ai0->ai_addrlen);
724
	else {
725
		yyerror("addrlen is invalid: %d", (int)ai0->ai_addrlen);
726
		freeaddrinfo(ai0);
727
		return (-1);
728
	}
729
15
	freeaddrinfo(ai0);
730
731
15
	return (0);
732
15
}