1 /*	$OpenBSD: parse.y,v 1.109 2012/10/14 11:58:23 gilles Exp $	*/
2 
3 /*
4  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
5  * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
6  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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/tree.h>
29 #include <sys/param.h>
30 #include <sys/socket.h>
31 #include <sys/stat.h>
32 #include <sys/ioctl.h>
33 
34 #include <ctype.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <event.h>
38 #include <inttypes.h>
39 #include <netdb.h>
40 #include <paths.h>
41 #include <pwd.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <util.h>
47 
48 #include "smtpscript.h"
49 
50 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
51 static struct file {
52 	TAILQ_ENTRY(file)	 entry;
53 	FILE			*stream;
54 	char			*name;
55 	int			 lineno;
56 	int			 errors;
57 } *file, *topfile;
58 struct file	*pushfile(const char *, int);
59 int		 popfile(void);
60 int		 check_file_secrecy(int, const char *);
61 int		 yyparse(void);
62 int		 yylex(void);
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 int		 yyerror(const char *, ...)
69     __attribute__ ((format (printf, 1, 2)));
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 void	   push_op(struct op *);
83 struct op *peek_op(void);
84 struct op *pop_op(void);
85 
86 #define MAXDEPTH 50
87 
88 static struct op *	opstack[MAXDEPTH];
89 static int		opstackidx;
90 
91 static int errors = 0;
92 
93 static struct script	*currscript;
94 static struct procedure	*currproc;
95 
96 int		 delaytonum(char *);
97 
98 typedef struct {
99 	union {
100 		int64_t		 number;
101 		char		*string;
102 		struct op	*op;
103 	} v;
104 	int lineno;
105 } YYSTYPE;
106 
107 %}
108 
109 %token  INCLUDE PORT REPEAT RANDOM NOOP
110 %token	PROC TESTCASE NAME NO_AUTOCONNECT EXPECT FAIL SKIP
111 %token	CALL CONNECT DISCONNECT STARTTLS SLEEP WRITE WRITELN
112 %token	SMTP OK TEMPFAIL PERMFAIL HELO
113 %token	ERROR ARROW
114 %token	<v.string>	STRING
115 %token  <v.number>	NUMBER
116 %type	<v.number>	quantifier port duration
117 %type	<v.op>		statement block
118 %%
119 
120 grammar		: /* empty */
121 		| grammar '\n'
122 		| grammar include '\n'
123 		| grammar varset '\n'
124 		| grammar proc '\n'
125 		| grammar testcase '\n'
126 		| grammar error '\n'		{ file->errors++; }
127 		;
128 
129 include		: INCLUDE STRING		{
130 			struct file	*nfile;
131 
132 			if ((nfile = pushfile($2, 0)) == 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 varset		: STRING '=' STRING		{
145 			if (symset($1, $3, 0) == -1)
146 				errx(1, "cannot store variable");
147 			free($1);
148 			free($3);
149 		}
150 		;
151 
152 optnl		: '\n' optnl
153 		|
154 		;
155 
156 nl		: '\n' optnl
157 		;
158 
159 quantifier      : /* empty */                   { $$ = 1; }
160 		| 's'				{ $$ = 1000; }
161 		| 'm'                           { $$ = 60 * 1000; }
162 		| 'h'                           { $$ = 3600 * 1000; }
163 		;
164 
165 duration	: NUMBER quantifier		{
166 			if ($1 < 0) {
167 				yyerror("invalid duration: %" PRId64, $1);
168 				YYERROR;
169 			}
170 			$$ = $1 * $2;
171 		}
172 		;
173 
174 port		: PORT STRING			{
175 			struct servent	*servent;
176 
177 			servent = getservbyname($2, "tcp");
178 			if (servent == NULL) {
179 				yyerror("port %s is invalid", $2);
180 				free($2);
181 				YYERROR;
182 			}
183 			$$ = ntohs(servent->s_port);
184 			free($2);
185 		}
186 		| PORT NUMBER			{
187 			if ($2 <= 0 || $2 >= (int)USHRT_MAX) {
188 				yyerror("invalid port: %" PRId64, $2);
189 				YYERROR;
190 			}
191 			$$ = $2;
192 		}
193 		| /* empty */ {
194 			$$ = 25;
195 		}
196 		;
197 
198 statement	: block
199 		| REPEAT NUMBER { push_op(NULL); } statement {
200 			pop_op();
201 			$$ = op_repeat(peek_op(), $2, $4);
202 		}
203 		| RANDOM { push_op(NULL); } block {
204 			pop_op();
205 			$$ = op_random(peek_op(), $3);
206 		}
207 		| CALL STRING {
208 			struct procedure *p;
209 			p = procedure_get_by_name(currscript, $2);
210 			if (p == NULL) {
211 				yyerror("call to undefined proc \"%s\"", $2);
212 				file->errors++;
213 			} else if (p == currproc) {
214 				yyerror("recursive call to proc \"%s\"", $2);
215 				file->errors++;
216 			} else {
217 				$$ = op_call(peek_op(), p);
218 			}
219 			free($2);
220 		}
221 		| NOOP {
222 			$$ = op_noop(peek_op());
223 		}
224 		| SLEEP duration {
225 			$$ = op_sleep(peek_op(), $2);
226 		}
227 		| FAIL STRING {
228 			$$ = op_fail(peek_op(), $2);
229 		}
230 		| CONNECT STRING port {
231 			$$ = op_connect(peek_op(), $2, $3);
232 		}
233 		| DISCONNECT {
234 			$$ = op_disconnect(peek_op());
235 		}
236 		| STARTTLS {
237 			$$ = op_starttls(peek_op());
238 		}
239 		| WRITE STRING {
240 			$$ = op_write(peek_op(), $2, strlen($2));
241 		}
242 		| WRITELN STRING {
243 			$$ = op_printf(peek_op(), "%s\r\n", $2);
244 			free($2);
245 		}
246 		| EXPECT DISCONNECT {
247 			$$ = op_expect_disconnect(peek_op());
248 		}
249 		| EXPECT SMTP {
250 			$$ = op_expect_smtp_response(peek_op(),
251 			    RESP_SMTP_ANY | RESP_SMTP_MULTILINE);
252 		}
253 		| EXPECT SMTP OK {
254 			$$ = op_expect_smtp_response(peek_op(),
255 			    RESP_SMTP_OK);
256 		}
257 		| EXPECT SMTP HELO {
258 			$$ = op_expect_smtp_response(peek_op(),
259 			    RESP_SMTP_OK | RESP_SMTP_MULTILINE);
260 		}
261 		| EXPECT SMTP TEMPFAIL {
262 			$$ = op_expect_smtp_response(peek_op(),
263 			    RESP_SMTP_TEMPFAIL);
264 		}
265 		| EXPECT SMTP PERMFAIL {
266 			$$ = op_expect_smtp_response(peek_op(),
267 			    RESP_SMTP_PERMFAIL);
268 		}
269 		;
270 
271 statement_list	: statement nl statement_list
272 		| statement
273 		| /* EMPTY */
274 		;
275 
276 block		: '{' {
277 			push_op(op_block(peek_op()));
278 		} optnl statement_list '}' {
279 			$$ = pop_op();
280 		}
281 		;
282 
283 procparam	: '%' STRING {
284 			if (proc_addvar(currproc, $2) == -1) {
285 				yyerror("cannot add parameter %s", $2);
286 				file->errors++;
287 			}
288 		}
289 		;
290 
291 procparams	: procparam procparams
292 		| /* EMPTY */
293 		;
294 
295 proc		: PROC STRING {
296 			printf("# proc %s\n", $2);
297 			currproc = procedure_create(currscript, $2);
298 			if (currproc == NULL)
299 				file->errors++;
300 		} procparams block {
301 			if (currproc)
302 				currproc->root = $5;
303 		}
304 		;
305 
306 testopt_name	: NAME STRING {
307 			if (procedure_get_by_name(currscript, $2)) {
308 				file->errors++;
309 			} else {
310 				free(currproc->name);
311 				currproc->name = ($2);
312 			}
313 		}
314 		| /* EMPTY */
315 		;
316 
317 testopt_cnx	: NO_AUTOCONNECT {
318 			currproc->flags |= PROC_NOCONNECT;
319 		}
320 		| /* EMPTY */
321 		;
322 testopt_fail	: EXPECT FAIL {
323 			currproc->flags |= PROC_EXPECTFAIL;
324 		}
325 		| /* EMPTY */
326 		;
327 
328 testopt_skip	: SKIP {
329 			currproc->flags |= PROC_SKIP;
330 		}
331 		| /* EMPTY */
332 		;
333 
334 testcaseopts	: testopt_name testopt_cnx testopt_fail testopt_skip;
335 
336 testcase	: TESTCASE {
337 			char buf[1024];
338 			snprintf(buf, sizeof buf, "<%s:%i>",
339 			    file->name, file->lineno);
340 			currproc = procedure_create(currscript, strdup(buf));
341 			if (currproc) {
342 				currproc->flags |= PROC_TESTCASE;
343 			} else {
344 				file->errors++;
345 			}
346 		} testcaseopts block {
347 			currproc->root = $4;
348 		}
349 		;
350 %%
351 
352 struct keywords {
353 	const char	*k_name;
354 	int		 k_val;
355 };
356 
357 int
yyerror(const char * fmt,...)358 yyerror(const char *fmt, ...)
359 {
360 	va_list		 ap;
361 
362 	file->errors++;
363 	va_start(ap, fmt);
364 	fprintf(stderr, "%s:%d: ", file->name, yylval.lineno);
365 	vfprintf(stderr, fmt, ap);
366 	fprintf(stderr, "\n");
367 	va_end(ap);
368 	return (0);
369 }
370 
371 int
kw_cmp(const void * k,const void * e)372 kw_cmp(const void *k, const void *e)
373 {
374 	return (strcmp(k, ((const struct keywords *)e)->k_name));
375 }
376 
377 int
lookup(char * s)378 lookup(char *s)
379 {
380 	/* this has to be sorted always */
381 	static const struct keywords keywords[] = {
382 		{ "call",		CALL },
383 		{ "connect",		CONNECT },
384 		{ "disconnect",		DISCONNECT },
385 		{ "expect",		EXPECT },
386 		{ "fail",		FAIL },
387 		{ "helo",		HELO },
388 		{ "name",		NAME },
389 		{ "no-autoconnect",	NO_AUTOCONNECT },
390 		{ "noop",		NOOP },
391 		{ "ok",			OK },
392 		{ "permfail",		PERMFAIL },
393 		{ "port",		PORT },
394 		{ "proc",		PROC },
395 		{ "random",		RANDOM },
396 		{ "repeat",		REPEAT },
397 		{ "skip",		SKIP },
398 		{ "sleep",		SLEEP },
399 		{ "smtp",		SMTP },
400 		{ "starttls",		STARTTLS },
401 		{ "tempfail",		TEMPFAIL },
402 		{ "test-case",		TESTCASE },
403 		{ "write",		WRITE },
404 		{ "writeln",		WRITELN },
405 	};
406 	const struct keywords	*p;
407 
408 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
409 	    sizeof(keywords[0]), kw_cmp);
410 
411 	if (p)
412 		return (p->k_val);
413 	else
414 		return (STRING);
415 }
416 
417 #define MAXPUSHBACK	128
418 
419 char	*parsebuf;
420 int	 parseindex;
421 char	 pushback_buffer[MAXPUSHBACK];
422 int	 pushback_index = 0;
423 
424 int
lgetc(int quotec)425 lgetc(int quotec)
426 {
427 	int		c, next;
428 
429 	if (parsebuf) {
430 		/* Read character from the parsebuffer instead of input. */
431 		if (parseindex >= 0) {
432 			c = parsebuf[parseindex++];
433 			if (c != '\0')
434 				return (c);
435 			parsebuf = NULL;
436 		} else
437 			parseindex++;
438 	}
439 
440 	if (pushback_index)
441 		return (pushback_buffer[--pushback_index]);
442 
443 	if (quotec) {
444 		if ((c = getc(file->stream)) == EOF) {
445 			yyerror("reached end of file while parsing "
446 			    "quoted string");
447 			if (file == topfile || popfile() == EOF)
448 				return (EOF);
449 			return (quotec);
450 		}
451 		return (c);
452 	}
453 
454 	while ((c = getc(file->stream)) == '\\') {
455 		next = getc(file->stream);
456 		if (next != '\n') {
457 			c = next;
458 			break;
459 		}
460 		yylval.lineno = file->lineno;
461 		file->lineno++;
462 	}
463 
464 	while (c == EOF) {
465 		if (file == topfile || popfile() == EOF)
466 			return (EOF);
467 		c = getc(file->stream);
468 	}
469 	return (c);
470 }
471 
472 int
lungetc(int c)473 lungetc(int c)
474 {
475 	if (c == EOF)
476 		return (EOF);
477 	if (parsebuf) {
478 		parseindex--;
479 		if (parseindex >= 0)
480 			return (c);
481 	}
482 	if (pushback_index < MAXPUSHBACK-1)
483 		return (pushback_buffer[pushback_index++] = c);
484 	else
485 		return (EOF);
486 }
487 
488 int
findeol(void)489 findeol(void)
490 {
491 	int	c;
492 
493 	parsebuf = NULL;
494 	pushback_index = 0;
495 
496 	/* skip to either EOF or the first real EOL */
497 	while (1) {
498 		c = lgetc(0);
499 		if (c == '\n') {
500 			file->lineno++;
501 			break;
502 		}
503 		if (c == EOF)
504 			break;
505 	}
506 	return (ERROR);
507 }
508 
509 int
yylex(void)510 yylex(void)
511 {
512 	char	 buf[8096];
513 	char	*p, *val;
514 	int	 quotec, next, c;
515 	int	 token;
516 
517 top:
518 	p = buf;
519 	while ((c = lgetc(0)) == ' ' || c == '\t')
520 		; /* nothing */
521 
522 	yylval.lineno = file->lineno;
523 	if (c == '#')
524 		while ((c = lgetc(0)) != '\n' && c != EOF)
525 			; /* nothing */
526 	if (c == '$' && parsebuf == NULL) {
527 		while (1) {
528 			if ((c = lgetc(0)) == EOF)
529 				return (0);
530 
531 			if (p + 1 >= buf + sizeof(buf) - 1) {
532 				yyerror("string too long");
533 				return (findeol());
534 			}
535 			if (isalnum(c) || c == '_') {
536 				*p++ = (char)c;
537 				continue;
538 			}
539 			*p = '\0';
540 			lungetc(c);
541 			break;
542 		}
543 		val = symget(buf);
544 		if (val == NULL) {
545 			yyerror("macro '%s' not defined", buf);
546 			return (findeol());
547 		}
548 		parsebuf = val;
549 		parseindex = 0;
550 		goto top;
551 	}
552 
553 	switch (c) {
554 	case '\'':
555 	case '"':
556 		quotec = c;
557 		while (1) {
558 			if ((c = lgetc(quotec)) == EOF)
559 				return (0);
560 			if (c == '\n') {
561 				file->lineno++;
562 				continue;
563 			} else if (c == '\\') {
564 				if ((next = lgetc(quotec)) == EOF)
565 					return (0);
566 				if (next == quotec || c == ' ' || c == '\t')
567 					c = next;
568 				else if (next == '\n') {
569 					file->lineno++;
570 					continue;
571 				} else
572 					lungetc(next);
573 			} else if (c == quotec) {
574 				*p = '\0';
575 				break;
576 			}
577 			if (p + 1 >= buf + sizeof(buf) - 1) {
578 				yyerror("string too long");
579 				return (findeol());
580 			}
581 			*p++ = (char)c;
582 		}
583 		yylval.v.string = strdup(buf);
584 		if (yylval.v.string == NULL)
585 			err(1, "yylex: strdup");
586 		return (STRING);
587 	}
588 
589 #define allowed_to_end_number(x) \
590 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
591 
592 	if (c == '-' || isdigit(c)) {
593 		do {
594 			*p++ = c;
595 			if ((unsigned)(p-buf) >= sizeof(buf)) {
596 				yyerror("string too long");
597 				return (findeol());
598 			}
599 		} while ((c = lgetc(0)) != EOF && isdigit(c));
600 		lungetc(c);
601 		if (p == buf + 1 && buf[0] == '-')
602 			goto nodigits;
603 		if (c == EOF || allowed_to_end_number(c)) {
604 			const char *errstr = NULL;
605 
606 			*p = '\0';
607 			yylval.v.number = strtonum(buf, LLONG_MIN,
608 			    LLONG_MAX, &errstr);
609 			if (errstr) {
610 				yyerror("\"%s\" invalid number: %s",
611 				    buf, errstr);
612 				return (findeol());
613 			}
614 			return (NUMBER);
615 		} else {
616 nodigits:
617 			while (p > buf + 1)
618 				lungetc(*--p);
619 			c = *--p;
620 			if (c == '-')
621 				return (c);
622 		}
623 	}
624 
625 	if (c == '=') {
626 		if ((c = lgetc(0)) != EOF && c == '>')
627 			return (ARROW);
628 		lungetc(c);
629 		c = '=';
630 	}
631 
632 #define allowed_in_string(x) \
633 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
634 	x != '{' && x != '}' && x != '<' && x != '>' && \
635 	x != '!' && x != '=' && x != '#' && \
636 	x != ','))
637 
638 	if (isalnum(c) || c == ':' || c == '_') {
639 		do {
640 			*p++ = c;
641 			if ((unsigned)(p-buf) >= sizeof(buf)) {
642 				yyerror("string too long");
643 				return (findeol());
644 			}
645 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
646 		lungetc(c);
647 		*p = '\0';
648 		if ((token = lookup(buf)) == STRING)
649 			if ((yylval.v.string = strdup(buf)) == NULL)
650 				err(1, "yylex: strdup");
651 		return (token);
652 	}
653 	if (c == '\n') {
654 		yylval.lineno = file->lineno;
655 		file->lineno++;
656 	}
657 	if (c == EOF)
658 		return (0);
659 	return (c);
660 }
661 
662 int
check_file_secrecy(int fd,const char * fname)663 check_file_secrecy(int fd, const char *fname)
664 {
665 	struct stat	st;
666 
667 	if (fstat(fd, &st)) {
668 		warn("cannot stat %s", fname);
669 		return (-1);
670 	}
671 	if (st.st_uid != 0 && st.st_uid != getuid()) {
672 		warnx("%s: owner not root or current user", fname);
673 		return (-1);
674 	}
675 	if (st.st_mode & (S_IRWXG | S_IRWXO)) {
676 		warnx("%s: group/world readable/writeable", fname);
677 		return (-1);
678 	}
679 	return (0);
680 }
681 
682 struct file *
pushfile(const char * name,int secret)683 pushfile(const char *name, int secret)
684 {
685 	struct file	*nfile;
686 
687 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
688 		warn("malloc");
689 		return (NULL);
690 	}
691 	if ((nfile->name = strdup(name)) == NULL) {
692 		warn("malloc");
693 		free(nfile);
694 		return (NULL);
695 	}
696 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
697 		warn("%s", nfile->name);
698 		free(nfile->name);
699 		free(nfile);
700 		return (NULL);
701 	} else if (secret &&
702 	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
703 		fclose(nfile->stream);
704 		free(nfile->name);
705 		free(nfile);
706 		return (NULL);
707 	}
708 	nfile->lineno = 1;
709 	TAILQ_INSERT_TAIL(&files, nfile, entry);
710 	return (nfile);
711 }
712 
713 int
popfile(void)714 popfile(void)
715 {
716 	struct file	*prev;
717 
718 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
719 		prev->errors += file->errors;
720 
721 	TAILQ_REMOVE(&files, file, entry);
722 	fclose(file->stream);
723 	free(file->name);
724 	free(file);
725 	file = prev;
726 	return (file ? 0 : EOF);
727 }
728 
729 struct script *
parse_script(const char * filename)730 parse_script(const char *filename)
731 {
732 	errors = 0;
733 
734 	currscript = calloc(1, sizeof *currscript);
735 	TAILQ_INIT(&currscript->procs);
736 	currproc = NULL;
737 
738 	opstackidx = 0;
739 
740 	if ((file = pushfile(filename, 0)) == NULL)
741 		return (NULL);
742 
743 	topfile = file;
744 
745 	/*
746 	 * parse configuration
747 	 */
748 	setservent(1);
749 	yyparse();
750 	errors = file->errors;
751 	popfile();
752 	endservent();
753 
754 	if (errors)
755 		return (NULL);
756 
757 	return (currscript);
758 }
759 
760 int
symset(const char * nam,const char * val,int persist)761 symset(const char *nam, const char *val, int persist)
762 {
763 	struct sym	*sym;
764 
765 	for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
766 	    sym = TAILQ_NEXT(sym, entry))
767 		;	/* nothing */
768 
769 	if (sym != NULL) {
770 		if (sym->persist == 1)
771 			return (0);
772 		else {
773 			free(sym->nam);
774 			free(sym->val);
775 			TAILQ_REMOVE(&symhead, sym, entry);
776 			free(sym);
777 		}
778 	}
779 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
780 		return (-1);
781 
782 	sym->nam = strdup(nam);
783 	if (sym->nam == NULL) {
784 		free(sym);
785 		return (-1);
786 	}
787 	sym->val = strdup(val);
788 	if (sym->val == NULL) {
789 		free(sym->nam);
790 		free(sym);
791 		return (-1);
792 	}
793 	sym->used = 0;
794 	sym->persist = persist;
795 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
796 	return (0);
797 }
798 
799 int
cmdline_symset(char * s)800 cmdline_symset(char *s)
801 {
802 	char	*sym, *val;
803 	int	ret;
804 	size_t	len;
805 
806 	if ((val = strrchr(s, '=')) == NULL)
807 		return (-1);
808 
809 	len = strlen(s) - strlen(val) + 1;
810 	if ((sym = malloc(len)) == NULL)
811 		errx(1, "cmdline_symset: malloc");
812 
813 	(void)strlcpy(sym, s, len);
814 
815 	ret = symset(sym, val + 1, 1);
816 	free(sym);
817 
818 	return (ret);
819 }
820 
821 char *
symget(const char * nam)822 symget(const char *nam)
823 {
824 	struct sym	*sym;
825 
826 	TAILQ_FOREACH(sym, &symhead, entry)
827 		if (strcmp(nam, sym->nam) == 0) {
828 			sym->used = 1;
829 			return (sym->val);
830 		}
831 	return (NULL);
832 }
833 
834 int
delaytonum(char * str)835 delaytonum(char *str)
836 {
837 	unsigned int     factor;
838 	size_t           len;
839 	const char      *errstr = NULL;
840 	int              delay;
841 
842 	/* we need at least 1 digit and 1 unit */
843 	len = strlen(str);
844 	if (len < 2)
845 		goto bad;
846 
847 	switch(str[len - 1]) {
848 
849 	case 's':
850 		factor = 1;
851 		break;
852 
853 	case 'm':
854 		factor = 60;
855 		break;
856 
857 	case 'h':
858 		factor = 60 * 60;
859 		break;
860 
861 	case 'd':
862 		factor = 24 * 60 * 60;
863 		break;
864 
865 	default:
866 		goto bad;
867 	}
868 
869 	str[len - 1] = '\0';
870 	delay = strtonum(str, 1, INT_MAX / factor, &errstr);
871 	if (errstr)
872 		goto bad;
873 
874 	return (delay * factor);
875 
876 bad:
877 	return (-1);
878 }
879 
880 
881 void
push_op(struct op * op)882 push_op(struct op *op)
883 {
884 	if (opstackidx == MAXDEPTH) {
885 		yyerror("too deep");
886 		return;
887 	}
888 	opstack[opstackidx++] = op;
889 }
890 
891 struct op *
pop_op(void)892 pop_op(void)
893 {
894 	if (opstackidx == 0)
895 		return (NULL);
896 	return (opstack[--opstackidx]);
897 }
898 
899 struct op *
peek_op(void)900 peek_op(void)
901 {
902 	if (opstackidx == 0)
903 		return (NULL);
904 	return (opstack[opstackidx - 1]);
905 }
906