xref: /openbsd/usr.bin/tmux/cmd-parse.y (revision a01f743b)
1 /* $OpenBSD: cmd-parse.y,v 1.50 2023/03/15 08:15:39 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 %{
20 
21 #include <sys/types.h>
22 
23 #include <ctype.h>
24 #include <errno.h>
25 #include <pwd.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <wchar.h>
30 
31 #include "tmux.h"
32 
33 static int			 yylex(void);
34 static int			 yyparse(void);
35 static int printflike(1,2)	 yyerror(const char *, ...);
36 
37 static char			*yylex_token(int);
38 static char			*yylex_format(void);
39 
40 struct cmd_parse_scope {
41 	int				 flag;
42 	TAILQ_ENTRY (cmd_parse_scope)	 entry;
43 };
44 
45 enum cmd_parse_argument_type {
46 	CMD_PARSE_STRING,
47 	CMD_PARSE_COMMANDS,
48 	CMD_PARSE_PARSED_COMMANDS
49 };
50 
51 struct cmd_parse_argument {
52 	enum cmd_parse_argument_type	 type;
53 	char				*string;
54 	struct cmd_parse_commands	*commands;
55 	struct cmd_list			*cmdlist;
56 
57 	TAILQ_ENTRY(cmd_parse_argument)	 entry;
58 };
59 TAILQ_HEAD(cmd_parse_arguments, cmd_parse_argument);
60 
61 struct cmd_parse_command {
62 	u_int				 line;
63 	struct cmd_parse_arguments	 arguments;
64 
65 	TAILQ_ENTRY(cmd_parse_command)	 entry;
66 };
67 TAILQ_HEAD(cmd_parse_commands, cmd_parse_command);
68 
69 struct cmd_parse_state {
70 	FILE				*f;
71 
72 	const char			*buf;
73 	size_t				 len;
74 	size_t				 off;
75 
76 	int				 condition;
77 	int				 eol;
78 	int				 eof;
79 	struct cmd_parse_input		*input;
80 	u_int				 escapes;
81 
82 	char				*error;
83 	struct cmd_parse_commands	*commands;
84 
85 	struct cmd_parse_scope		*scope;
86 	TAILQ_HEAD(, cmd_parse_scope)	 stack;
87 };
88 static struct cmd_parse_state parse_state;
89 
90 static char	*cmd_parse_get_error(const char *, u_int, const char *);
91 static void	 cmd_parse_free_command(struct cmd_parse_command *);
92 static struct cmd_parse_commands *cmd_parse_new_commands(void);
93 static void	 cmd_parse_free_commands(struct cmd_parse_commands *);
94 static void	 cmd_parse_build_commands(struct cmd_parse_commands *,
95 		     struct cmd_parse_input *, struct cmd_parse_result *);
96 static void	 cmd_parse_print_commands(struct cmd_parse_input *,
97 		     struct cmd_list *);
98 
99 %}
100 
101 %union
102 {
103 	char					 *token;
104 	struct cmd_parse_arguments		 *arguments;
105 	struct cmd_parse_argument		 *argument;
106 	int					  flag;
107 	struct {
108 		int				  flag;
109 		struct cmd_parse_commands	 *commands;
110 	} elif;
111 	struct cmd_parse_commands		 *commands;
112 	struct cmd_parse_command		 *command;
113 }
114 
115 %token ERROR
116 %token HIDDEN
117 %token IF
118 %token ELSE
119 %token ELIF
120 %token ENDIF
121 %token <token> FORMAT TOKEN EQUALS
122 
123 %type <token> expanded format
124 %type <arguments> arguments
125 %type <argument> argument
126 %type <flag> if_open if_elif
127 %type <elif> elif elif1
128 %type <commands> argument_statements statements statement
129 %type <commands> commands condition condition1
130 %type <command> command
131 
132 %%
133 
134 lines		: /* empty */
135 		| statements
136 		{
137 			struct cmd_parse_state	*ps = &parse_state;
138 
139 			ps->commands = $1;
140 		}
141 
142 statements	: statement '\n'
143 		{
144 			$$ = $1;
145 		}
146 		| statements statement '\n'
147 		{
148 			$$ = $1;
149 			TAILQ_CONCAT($$, $2, entry);
150 			free($2);
151 		}
152 
153 statement	: /* empty */
154 		{
155 			$$ = xmalloc (sizeof *$$);
156 			TAILQ_INIT($$);
157 		}
158 		| hidden_assignment
159 		{
160 			$$ = xmalloc (sizeof *$$);
161 			TAILQ_INIT($$);
162 		}
163 		| condition
164 		{
165 			struct cmd_parse_state	*ps = &parse_state;
166 
167 			if (ps->scope == NULL || ps->scope->flag)
168 				$$ = $1;
169 			else {
170 				$$ = cmd_parse_new_commands();
171 				cmd_parse_free_commands($1);
172 			}
173 		}
174 		| commands
175 		{
176 			struct cmd_parse_state	*ps = &parse_state;
177 
178 			if (ps->scope == NULL || ps->scope->flag)
179 				$$ = $1;
180 			else {
181 				$$ = cmd_parse_new_commands();
182 				cmd_parse_free_commands($1);
183 			}
184 		}
185 
186 format		: FORMAT
187 		{
188 			$$ = $1;
189 		}
190 		| TOKEN
191 		{
192 			$$ = $1;
193 		}
194 
195 expanded	: format
196 		{
197 			struct cmd_parse_state	*ps = &parse_state;
198 			struct cmd_parse_input	*pi = ps->input;
199 			struct format_tree	*ft;
200 			struct client		*c = pi->c;
201 			struct cmd_find_state	*fsp;
202 			struct cmd_find_state	 fs;
203 			int			 flags = FORMAT_NOJOBS;
204 
205 			if (cmd_find_valid_state(&pi->fs))
206 				fsp = &pi->fs;
207 			else {
208 				cmd_find_from_client(&fs, c, 0);
209 				fsp = &fs;
210 			}
211 			ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
212 			format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp);
213 
214 			$$ = format_expand(ft, $1);
215 			format_free(ft);
216 			free($1);
217 		}
218 
219 optional_assignment	: /* empty */
220 			| assignment
221 
222 assignment	: EQUALS
223 		{
224 			struct cmd_parse_state	*ps = &parse_state;
225 			int			 flags = ps->input->flags;
226 
227 			if ((~flags & CMD_PARSE_PARSEONLY) &&
228 			    (ps->scope == NULL || ps->scope->flag))
229 				environ_put(global_environ, $1, 0);
230 			free($1);
231 		}
232 
233 hidden_assignment : HIDDEN EQUALS
234 		{
235 			struct cmd_parse_state	*ps = &parse_state;
236 			int			 flags = ps->input->flags;
237 
238 			if ((~flags & CMD_PARSE_PARSEONLY) &&
239 			    (ps->scope == NULL || ps->scope->flag))
240 				environ_put(global_environ, $2, ENVIRON_HIDDEN);
241 			free($2);
242 		}
243 
244 if_open		: IF expanded
245 		{
246 			struct cmd_parse_state	*ps = &parse_state;
247 			struct cmd_parse_scope	*scope;
248 
249 			scope = xmalloc(sizeof *scope);
250 			$$ = scope->flag = format_true($2);
251 			free($2);
252 
253 			if (ps->scope != NULL)
254 				TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry);
255 			ps->scope = scope;
256 		}
257 
258 if_else		: ELSE
259 		{
260 			struct cmd_parse_state	*ps = &parse_state;
261 			struct cmd_parse_scope	*scope;
262 
263 			scope = xmalloc(sizeof *scope);
264 			scope->flag = !ps->scope->flag;
265 
266 			free(ps->scope);
267 			ps->scope = scope;
268 		}
269 
270 if_elif		: ELIF expanded
271 		{
272 			struct cmd_parse_state	*ps = &parse_state;
273 			struct cmd_parse_scope	*scope;
274 
275 			scope = xmalloc(sizeof *scope);
276 			$$ = scope->flag = format_true($2);
277 			free($2);
278 
279 			free(ps->scope);
280 			ps->scope = scope;
281 		}
282 
283 if_close	: ENDIF
284 		{
285 			struct cmd_parse_state	*ps = &parse_state;
286 
287 			free(ps->scope);
288 			ps->scope = TAILQ_FIRST(&ps->stack);
289 			if (ps->scope != NULL)
290 				TAILQ_REMOVE(&ps->stack, ps->scope, entry);
291 		}
292 
293 condition	: if_open '\n' statements if_close
294 		{
295 			if ($1)
296 				$$ = $3;
297 			else {
298 				$$ = cmd_parse_new_commands();
299 				cmd_parse_free_commands($3);
300 			}
301 		}
302 		| if_open '\n' statements if_else '\n' statements if_close
303 		{
304 			if ($1) {
305 				$$ = $3;
306 				cmd_parse_free_commands($6);
307 			} else {
308 				$$ = $6;
309 				cmd_parse_free_commands($3);
310 			}
311 		}
312 		| if_open '\n' statements elif if_close
313 		{
314 			if ($1) {
315 				$$ = $3;
316 				cmd_parse_free_commands($4.commands);
317 			} else if ($4.flag) {
318 				$$ = $4.commands;
319 				cmd_parse_free_commands($3);
320 			} else {
321 				$$ = cmd_parse_new_commands();
322 				cmd_parse_free_commands($3);
323 				cmd_parse_free_commands($4.commands);
324 			}
325 		}
326 		| if_open '\n' statements elif if_else '\n' statements if_close
327 		{
328 			if ($1) {
329 				$$ = $3;
330 				cmd_parse_free_commands($4.commands);
331 				cmd_parse_free_commands($7);
332 			} else if ($4.flag) {
333 				$$ = $4.commands;
334 				cmd_parse_free_commands($3);
335 				cmd_parse_free_commands($7);
336 			} else {
337 				$$ = $7;
338 				cmd_parse_free_commands($3);
339 				cmd_parse_free_commands($4.commands);
340 			}
341 		}
342 
343 elif		: if_elif '\n' statements
344 		{
345 			if ($1) {
346 				$$.flag = 1;
347 				$$.commands = $3;
348 			} else {
349 				$$.flag = 0;
350 				$$.commands = cmd_parse_new_commands();
351 				cmd_parse_free_commands($3);
352 			}
353 		}
354 		| if_elif '\n' statements elif
355 		{
356 			if ($1) {
357 				$$.flag = 1;
358 				$$.commands = $3;
359 				cmd_parse_free_commands($4.commands);
360 			} else if ($4.flag) {
361 				$$.flag = 1;
362 				$$.commands = $4.commands;
363 				cmd_parse_free_commands($3);
364 			} else {
365 				$$.flag = 0;
366 				$$.commands = cmd_parse_new_commands();
367 				cmd_parse_free_commands($3);
368 				cmd_parse_free_commands($4.commands);
369 			}
370 		}
371 
372 commands	: command
373 		{
374 			struct cmd_parse_state	*ps = &parse_state;
375 
376 			$$ = cmd_parse_new_commands();
377 			if (!TAILQ_EMPTY(&$1->arguments) &&
378 			    (ps->scope == NULL || ps->scope->flag))
379 				TAILQ_INSERT_TAIL($$, $1, entry);
380 			else
381 				cmd_parse_free_command($1);
382 		}
383 		| commands ';'
384 		{
385 			$$ = $1;
386 		}
387 		| commands ';' condition1
388 		{
389 			$$ = $1;
390 			TAILQ_CONCAT($$, $3, entry);
391 			free($3);
392 		}
393 		| commands ';' command
394 		{
395 			struct cmd_parse_state	*ps = &parse_state;
396 
397 			if (!TAILQ_EMPTY(&$3->arguments) &&
398 			    (ps->scope == NULL || ps->scope->flag)) {
399 				$$ = $1;
400 				TAILQ_INSERT_TAIL($$, $3, entry);
401 			} else {
402 				$$ = cmd_parse_new_commands();
403 				cmd_parse_free_commands($1);
404 				cmd_parse_free_command($3);
405 			}
406 		}
407 		| condition1
408 		{
409 			$$ = $1;
410 		}
411 
412 command		: assignment
413 		{
414 			struct cmd_parse_state	*ps = &parse_state;
415 
416 			$$ = xcalloc(1, sizeof *$$);
417 			$$->line = ps->input->line;
418 			TAILQ_INIT(&$$->arguments);
419 		}
420 		| optional_assignment TOKEN
421 		{
422 			struct cmd_parse_state		*ps = &parse_state;
423 			struct cmd_parse_argument	*arg;
424 
425 			$$ = xcalloc(1, sizeof *$$);
426 			$$->line = ps->input->line;
427 			TAILQ_INIT(&$$->arguments);
428 
429 			arg = xcalloc(1, sizeof *arg);
430 			arg->type = CMD_PARSE_STRING;
431 			arg->string = $2;
432 			TAILQ_INSERT_HEAD(&$$->arguments, arg, entry);
433 		}
434 		| optional_assignment TOKEN arguments
435 		{
436 			struct cmd_parse_state		*ps = &parse_state;
437 			struct cmd_parse_argument	*arg;
438 
439 			$$ = xcalloc(1, sizeof *$$);
440 			$$->line = ps->input->line;
441 			TAILQ_INIT(&$$->arguments);
442 
443 			TAILQ_CONCAT(&$$->arguments, $3, entry);
444 			free($3);
445 
446 			arg = xcalloc(1, sizeof *arg);
447 			arg->type = CMD_PARSE_STRING;
448 			arg->string = $2;
449 			TAILQ_INSERT_HEAD(&$$->arguments, arg, entry);
450 		}
451 
452 condition1	: if_open commands if_close
453 		{
454 			if ($1)
455 				$$ = $2;
456 			else {
457 				$$ = cmd_parse_new_commands();
458 				cmd_parse_free_commands($2);
459 			}
460 		}
461 		| if_open commands if_else commands if_close
462 		{
463 			if ($1) {
464 				$$ = $2;
465 				cmd_parse_free_commands($4);
466 			} else {
467 				$$ = $4;
468 				cmd_parse_free_commands($2);
469 			}
470 		}
471 		| if_open commands elif1 if_close
472 		{
473 			if ($1) {
474 				$$ = $2;
475 				cmd_parse_free_commands($3.commands);
476 			} else if ($3.flag) {
477 				$$ = $3.commands;
478 				cmd_parse_free_commands($2);
479 			} else {
480 				$$ = cmd_parse_new_commands();
481 				cmd_parse_free_commands($2);
482 				cmd_parse_free_commands($3.commands);
483 			}
484 		}
485 		| if_open commands elif1 if_else commands if_close
486 		{
487 			if ($1) {
488 				$$ = $2;
489 				cmd_parse_free_commands($3.commands);
490 				cmd_parse_free_commands($5);
491 			} else if ($3.flag) {
492 				$$ = $3.commands;
493 				cmd_parse_free_commands($2);
494 				cmd_parse_free_commands($5);
495 			} else {
496 				$$ = $5;
497 				cmd_parse_free_commands($2);
498 				cmd_parse_free_commands($3.commands);
499 			}
500 		}
501 
502 elif1		: if_elif commands
503 		{
504 			if ($1) {
505 				$$.flag = 1;
506 				$$.commands = $2;
507 			} else {
508 				$$.flag = 0;
509 				$$.commands = cmd_parse_new_commands();
510 				cmd_parse_free_commands($2);
511 			}
512 		}
513 		| if_elif commands elif1
514 		{
515 			if ($1) {
516 				$$.flag = 1;
517 				$$.commands = $2;
518 				cmd_parse_free_commands($3.commands);
519 			} else if ($3.flag) {
520 				$$.flag = 1;
521 				$$.commands = $3.commands;
522 				cmd_parse_free_commands($2);
523 			} else {
524 				$$.flag = 0;
525 				$$.commands = cmd_parse_new_commands();
526 				cmd_parse_free_commands($2);
527 				cmd_parse_free_commands($3.commands);
528 			}
529 		}
530 
531 arguments	: argument
532 		{
533 			$$ = xcalloc(1, sizeof *$$);
534 			TAILQ_INIT($$);
535 
536 			TAILQ_INSERT_HEAD($$, $1, entry);
537 		}
538 		| argument arguments
539 		{
540 			TAILQ_INSERT_HEAD($2, $1, entry);
541 			$$ = $2;
542 		}
543 
544 argument	: TOKEN
545 		{
546 			$$ = xcalloc(1, sizeof *$$);
547 			$$->type = CMD_PARSE_STRING;
548 			$$->string = $1;
549 		}
550 		| EQUALS
551 		{
552 			$$ = xcalloc(1, sizeof *$$);
553 			$$->type = CMD_PARSE_STRING;
554 			$$->string = $1;
555 		}
556 		| '{' argument_statements
557 		{
558 			$$ = xcalloc(1, sizeof *$$);
559 			$$->type = CMD_PARSE_COMMANDS;
560 			$$->commands = $2;
561 		}
562 
563 argument_statements	: statement '}'
564 			{
565 				$$ = $1;
566 			}
567 			| statements statement '}'
568 			{
569 				$$ = $1;
570 				TAILQ_CONCAT($$, $2, entry);
571 				free($2);
572 			}
573 
574 %%
575 
576 static char *
577 cmd_parse_get_error(const char *file, u_int line, const char *error)
578 {
579 	char	*s;
580 
581 	if (file == NULL)
582 		s = xstrdup(error);
583 	else
584 		xasprintf(&s, "%s:%u: %s", file, line, error);
585 	return (s);
586 }
587 
588 static void
cmd_parse_print_commands(struct cmd_parse_input * pi,struct cmd_list * cmdlist)589 cmd_parse_print_commands(struct cmd_parse_input *pi, struct cmd_list *cmdlist)
590 {
591 	char	*s;
592 
593 	if (pi->item == NULL || (~pi->flags & CMD_PARSE_VERBOSE))
594 		return;
595 	s = cmd_list_print(cmdlist, 0);
596 	if (pi->file != NULL)
597 		cmdq_print(pi->item, "%s:%u: %s", pi->file, pi->line, s);
598 	else
599 		cmdq_print(pi->item, "%u: %s", pi->line, s);
600 	free(s);
601 }
602 
603 static void
cmd_parse_free_argument(struct cmd_parse_argument * arg)604 cmd_parse_free_argument(struct cmd_parse_argument *arg)
605 {
606 	switch (arg->type) {
607 	case CMD_PARSE_STRING:
608 		free(arg->string);
609 		break;
610 	case CMD_PARSE_COMMANDS:
611 		cmd_parse_free_commands(arg->commands);
612 		break;
613 	case CMD_PARSE_PARSED_COMMANDS:
614 		cmd_list_free(arg->cmdlist);
615 		break;
616 	}
617 	free(arg);
618 }
619 
620 static void
cmd_parse_free_arguments(struct cmd_parse_arguments * args)621 cmd_parse_free_arguments(struct cmd_parse_arguments *args)
622 {
623 	struct cmd_parse_argument	*arg, *arg1;
624 
625 	TAILQ_FOREACH_SAFE(arg, args, entry, arg1) {
626 		TAILQ_REMOVE(args, arg, entry);
627 		cmd_parse_free_argument(arg);
628 	}
629 }
630 
631 static void
cmd_parse_free_command(struct cmd_parse_command * cmd)632 cmd_parse_free_command(struct cmd_parse_command *cmd)
633 {
634 	cmd_parse_free_arguments(&cmd->arguments);
635 	free(cmd);
636 }
637 
638 static struct cmd_parse_commands *
cmd_parse_new_commands(void)639 cmd_parse_new_commands(void)
640 {
641 	struct cmd_parse_commands	*cmds;
642 
643 	cmds = xmalloc(sizeof *cmds);
644 	TAILQ_INIT(cmds);
645 	return (cmds);
646 }
647 
648 static void
cmd_parse_free_commands(struct cmd_parse_commands * cmds)649 cmd_parse_free_commands(struct cmd_parse_commands *cmds)
650 {
651 	struct cmd_parse_command	*cmd, *cmd1;
652 
653 	TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) {
654 		TAILQ_REMOVE(cmds, cmd, entry);
655 		cmd_parse_free_command(cmd);
656 	}
657 	free(cmds);
658 }
659 
660 static struct cmd_parse_commands *
cmd_parse_run_parser(char ** cause)661 cmd_parse_run_parser(char **cause)
662 {
663 	struct cmd_parse_state	*ps = &parse_state;
664 	struct cmd_parse_scope	*scope, *scope1;
665 	int			 retval;
666 
667 	ps->commands = NULL;
668 	TAILQ_INIT(&ps->stack);
669 
670 	retval = yyparse();
671 	TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) {
672 		TAILQ_REMOVE(&ps->stack, scope, entry);
673 		free(scope);
674 	}
675 	if (retval != 0) {
676 		*cause = ps->error;
677 		return (NULL);
678 	}
679 
680 	if (ps->commands == NULL)
681 		return (cmd_parse_new_commands());
682 	return (ps->commands);
683 }
684 
685 static struct cmd_parse_commands *
cmd_parse_do_file(FILE * f,struct cmd_parse_input * pi,char ** cause)686 cmd_parse_do_file(FILE *f, struct cmd_parse_input *pi, char **cause)
687 {
688 	struct cmd_parse_state	*ps = &parse_state;
689 
690 	memset(ps, 0, sizeof *ps);
691 	ps->input = pi;
692 	ps->f = f;
693 	return (cmd_parse_run_parser(cause));
694 }
695 
696 static struct cmd_parse_commands *
cmd_parse_do_buffer(const char * buf,size_t len,struct cmd_parse_input * pi,char ** cause)697 cmd_parse_do_buffer(const char *buf, size_t len, struct cmd_parse_input *pi,
698     char **cause)
699 {
700 	struct cmd_parse_state	*ps = &parse_state;
701 
702 	memset(ps, 0, sizeof *ps);
703 	ps->input = pi;
704 	ps->buf = buf;
705 	ps->len = len;
706 	return (cmd_parse_run_parser(cause));
707 }
708 
709 static void
cmd_parse_log_commands(struct cmd_parse_commands * cmds,const char * prefix)710 cmd_parse_log_commands(struct cmd_parse_commands *cmds, const char *prefix)
711 {
712 	struct cmd_parse_command	*cmd;
713 	struct cmd_parse_argument	*arg;
714 	u_int				 i, j;
715 	char				*s;
716 
717 	i = 0;
718 	TAILQ_FOREACH(cmd, cmds, entry) {
719 		j = 0;
720 		TAILQ_FOREACH(arg, &cmd->arguments, entry) {
721 			switch (arg->type) {
722 			case CMD_PARSE_STRING:
723 				log_debug("%s %u:%u: %s", prefix, i, j,
724 				    arg->string);
725 				break;
726 			case CMD_PARSE_COMMANDS:
727 				xasprintf(&s, "%s %u:%u", prefix, i, j);
728 				cmd_parse_log_commands(arg->commands, s);
729 				free(s);
730 				break;
731 			case CMD_PARSE_PARSED_COMMANDS:
732 				s = cmd_list_print(arg->cmdlist, 0);
733 				log_debug("%s %u:%u: %s", prefix, i, j, s);
734 				free(s);
735 				break;
736 			}
737 			j++;
738 		}
739 		i++;
740 	}
741 }
742 
743 static int
cmd_parse_expand_alias(struct cmd_parse_command * cmd,struct cmd_parse_input * pi,struct cmd_parse_result * pr)744 cmd_parse_expand_alias(struct cmd_parse_command *cmd,
745     struct cmd_parse_input *pi, struct cmd_parse_result *pr)
746 {
747 	struct cmd_parse_argument	*arg, *arg1, *first;
748 	struct cmd_parse_commands	*cmds;
749 	struct cmd_parse_command	*last;
750 	char				*alias, *name, *cause;
751 
752 	if (pi->flags & CMD_PARSE_NOALIAS)
753 		return (0);
754 	memset(pr, 0, sizeof *pr);
755 
756 	first = TAILQ_FIRST(&cmd->arguments);
757 	if (first == NULL || first->type != CMD_PARSE_STRING) {
758 		pr->status = CMD_PARSE_SUCCESS;
759 		pr->cmdlist = cmd_list_new();
760 		return (1);
761 	}
762 	name = first->string;
763 
764 	alias = cmd_get_alias(name);
765 	if (alias == NULL)
766 		return (0);
767 	log_debug("%s: %u alias %s = %s", __func__, pi->line, name, alias);
768 
769 	cmds = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause);
770 	free(alias);
771 	if (cmds == NULL) {
772 		pr->status = CMD_PARSE_ERROR;
773 		pr->error = cause;
774 		return (1);
775 	}
776 
777 	last = TAILQ_LAST(cmds, cmd_parse_commands);
778 	if (last == NULL) {
779 		pr->status = CMD_PARSE_SUCCESS;
780 		pr->cmdlist = cmd_list_new();
781 		return (1);
782 	}
783 
784 	TAILQ_REMOVE(&cmd->arguments, first, entry);
785 	cmd_parse_free_argument(first);
786 
787 	TAILQ_FOREACH_SAFE(arg, &cmd->arguments, entry, arg1) {
788 		TAILQ_REMOVE(&cmd->arguments, arg, entry);
789 		TAILQ_INSERT_TAIL(&last->arguments, arg, entry);
790 	}
791 	cmd_parse_log_commands(cmds, __func__);
792 
793 	pi->flags |= CMD_PARSE_NOALIAS;
794 	cmd_parse_build_commands(cmds, pi, pr);
795 	pi->flags &= ~CMD_PARSE_NOALIAS;
796 	return (1);
797 }
798 
799 static void
cmd_parse_build_command(struct cmd_parse_command * cmd,struct cmd_parse_input * pi,struct cmd_parse_result * pr)800 cmd_parse_build_command(struct cmd_parse_command *cmd,
801     struct cmd_parse_input *pi, struct cmd_parse_result *pr)
802 {
803 	struct cmd_parse_argument	*arg;
804 	struct cmd			*add;
805 	char				*cause;
806 	struct args_value		*values = NULL;
807 	u_int				 count = 0, idx;
808 
809 	memset(pr, 0, sizeof *pr);
810 
811 	if (cmd_parse_expand_alias(cmd, pi, pr))
812 		return;
813 
814 	TAILQ_FOREACH(arg, &cmd->arguments, entry) {
815 		values = xrecallocarray(values, count, count + 1,
816 		    sizeof *values);
817 		switch (arg->type) {
818 		case CMD_PARSE_STRING:
819 			values[count].type = ARGS_STRING;
820 			values[count].string = xstrdup(arg->string);
821 			break;
822 		case CMD_PARSE_COMMANDS:
823 			cmd_parse_build_commands(arg->commands, pi, pr);
824 			if (pr->status != CMD_PARSE_SUCCESS)
825 				goto out;
826 			values[count].type = ARGS_COMMANDS;
827 			values[count].cmdlist = pr->cmdlist;
828 			break;
829 		case CMD_PARSE_PARSED_COMMANDS:
830 			values[count].type = ARGS_COMMANDS;
831 			values[count].cmdlist = arg->cmdlist;
832 			values[count].cmdlist->references++;
833 			break;
834 		}
835 		count++;
836 	}
837 
838 	add = cmd_parse(values, count, pi->file, pi->line, &cause);
839 	if (add == NULL) {
840 		pr->status = CMD_PARSE_ERROR;
841 		pr->error = cmd_parse_get_error(pi->file, pi->line, cause);
842 		free(cause);
843 		goto out;
844 	}
845 	pr->status = CMD_PARSE_SUCCESS;
846 	pr->cmdlist = cmd_list_new();
847 	cmd_list_append(pr->cmdlist, add);
848 
849 out:
850 	for (idx = 0; idx < count; idx++)
851 		args_free_value(&values[idx]);
852 	free(values);
853 }
854 
855 static void
cmd_parse_build_commands(struct cmd_parse_commands * cmds,struct cmd_parse_input * pi,struct cmd_parse_result * pr)856 cmd_parse_build_commands(struct cmd_parse_commands *cmds,
857     struct cmd_parse_input *pi, struct cmd_parse_result *pr)
858 {
859 	struct cmd_parse_command	*cmd;
860 	u_int				 line = UINT_MAX;
861 	struct cmd_list			*current = NULL, *result;
862 	char				*s;
863 
864 	memset(pr, 0, sizeof *pr);
865 
866 	/* Check for an empty list. */
867 	if (TAILQ_EMPTY(cmds)) {
868 		pr->status = CMD_PARSE_SUCCESS;
869 		pr->cmdlist = cmd_list_new();
870 		return;
871 	}
872 	cmd_parse_log_commands(cmds, __func__);
873 
874 	/*
875 	 * Parse each command into a command list. Create a new command list
876 	 * for each line (unless the flag is set) so they get a new group (so
877 	 * the queue knows which ones to remove if a command fails when
878 	 * executed).
879 	 */
880 	result = cmd_list_new();
881 	TAILQ_FOREACH(cmd, cmds, entry) {
882 		if (((~pi->flags & CMD_PARSE_ONEGROUP) && cmd->line != line)) {
883 			if (current != NULL) {
884 				cmd_parse_print_commands(pi, current);
885 				cmd_list_move(result, current);
886 				cmd_list_free(current);
887 			}
888 			current = cmd_list_new();
889 		}
890 		if (current == NULL)
891 			current = cmd_list_new();
892 		line = pi->line = cmd->line;
893 
894 		cmd_parse_build_command(cmd, pi, pr);
895 		if (pr->status != CMD_PARSE_SUCCESS) {
896 			cmd_list_free(result);
897 			cmd_list_free(current);
898 			return;
899 		}
900 		cmd_list_append_all(current, pr->cmdlist);
901 		cmd_list_free(pr->cmdlist);
902 	}
903 	if (current != NULL) {
904 		cmd_parse_print_commands(pi, current);
905 		cmd_list_move(result, current);
906 		cmd_list_free(current);
907 	}
908 
909 	s = cmd_list_print(result, 0);
910 	log_debug("%s: %s", __func__, s);
911 	free(s);
912 
913 	pr->status = CMD_PARSE_SUCCESS;
914 	pr->cmdlist = result;
915 }
916 
917 struct cmd_parse_result *
cmd_parse_from_file(FILE * f,struct cmd_parse_input * pi)918 cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi)
919 {
920 	static struct cmd_parse_result	 pr;
921 	struct cmd_parse_input		 input;
922 	struct cmd_parse_commands	*cmds;
923 	char				*cause;
924 
925 	if (pi == NULL) {
926 		memset(&input, 0, sizeof input);
927 		pi = &input;
928 	}
929 	memset(&pr, 0, sizeof pr);
930 
931 	cmds = cmd_parse_do_file(f, pi, &cause);
932 	if (cmds == NULL) {
933 		pr.status = CMD_PARSE_ERROR;
934 		pr.error = cause;
935 		return (&pr);
936 	}
937 	cmd_parse_build_commands(cmds, pi, &pr);
938 	cmd_parse_free_commands(cmds);
939 	return (&pr);
940 
941 }
942 
943 struct cmd_parse_result *
cmd_parse_from_string(const char * s,struct cmd_parse_input * pi)944 cmd_parse_from_string(const char *s, struct cmd_parse_input *pi)
945 {
946 	struct cmd_parse_input	input;
947 
948 	if (pi == NULL) {
949 		memset(&input, 0, sizeof input);
950 		pi = &input;
951 	}
952 
953 	/*
954 	 * When parsing a string, put commands in one group even if there are
955 	 * multiple lines. This means { a \n b } is identical to "a ; b" when
956 	 * given as an argument to another command.
957 	 */
958 	pi->flags |= CMD_PARSE_ONEGROUP;
959 	return (cmd_parse_from_buffer(s, strlen(s), pi));
960 }
961 
962 enum cmd_parse_status
cmd_parse_and_insert(const char * s,struct cmd_parse_input * pi,struct cmdq_item * after,struct cmdq_state * state,char ** error)963 cmd_parse_and_insert(const char *s, struct cmd_parse_input *pi,
964     struct cmdq_item *after, struct cmdq_state *state, char **error)
965 {
966 	struct cmd_parse_result	*pr;
967 	struct cmdq_item	*item;
968 
969 	pr = cmd_parse_from_string(s, pi);
970 	switch (pr->status) {
971 	case CMD_PARSE_ERROR:
972 		if (error != NULL)
973 			*error = pr->error;
974 		else
975 			free(pr->error);
976 		break;
977 	case CMD_PARSE_SUCCESS:
978 		item = cmdq_get_command(pr->cmdlist, state);
979 		cmdq_insert_after(after, item);
980 		cmd_list_free(pr->cmdlist);
981 		break;
982 	}
983 	return (pr->status);
984 }
985 
986 enum cmd_parse_status
cmd_parse_and_append(const char * s,struct cmd_parse_input * pi,struct client * c,struct cmdq_state * state,char ** error)987 cmd_parse_and_append(const char *s, struct cmd_parse_input *pi,
988     struct client *c, struct cmdq_state *state, char **error)
989 {
990 	struct cmd_parse_result	*pr;
991 	struct cmdq_item	*item;
992 
993 	pr = cmd_parse_from_string(s, pi);
994 	switch (pr->status) {
995 	case CMD_PARSE_ERROR:
996 		if (error != NULL)
997 			*error = pr->error;
998 		else
999 			free(pr->error);
1000 		break;
1001 	case CMD_PARSE_SUCCESS:
1002 		item = cmdq_get_command(pr->cmdlist, state);
1003 		cmdq_append(c, item);
1004 		cmd_list_free(pr->cmdlist);
1005 		break;
1006 	}
1007 	return (pr->status);
1008 }
1009 
1010 struct cmd_parse_result *
cmd_parse_from_buffer(const void * buf,size_t len,struct cmd_parse_input * pi)1011 cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi)
1012 {
1013 	static struct cmd_parse_result	 pr;
1014 	struct cmd_parse_input		 input;
1015 	struct cmd_parse_commands	*cmds;
1016 	char				*cause;
1017 
1018 	if (pi == NULL) {
1019 		memset(&input, 0, sizeof input);
1020 		pi = &input;
1021 	}
1022 	memset(&pr, 0, sizeof pr);
1023 
1024 	if (len == 0) {
1025 		pr.status = CMD_PARSE_SUCCESS;
1026 		pr.cmdlist = cmd_list_new();
1027 		return (&pr);
1028 	}
1029 
1030 	cmds = cmd_parse_do_buffer(buf, len, pi, &cause);
1031 	if (cmds == NULL) {
1032 		pr.status = CMD_PARSE_ERROR;
1033 		pr.error = cause;
1034 		return (&pr);
1035 	}
1036 	cmd_parse_build_commands(cmds, pi, &pr);
1037 	cmd_parse_free_commands(cmds);
1038 	return (&pr);
1039 }
1040 
1041 struct cmd_parse_result *
cmd_parse_from_arguments(struct args_value * values,u_int count,struct cmd_parse_input * pi)1042 cmd_parse_from_arguments(struct args_value *values, u_int count,
1043     struct cmd_parse_input *pi)
1044 {
1045 	static struct cmd_parse_result	 pr;
1046 	struct cmd_parse_input		 input;
1047 	struct cmd_parse_commands	*cmds;
1048 	struct cmd_parse_command	*cmd;
1049 	struct cmd_parse_argument	*arg;
1050 	u_int				 i;
1051 	char				*copy;
1052 	size_t				 size;
1053 	int				 end;
1054 
1055 	/*
1056 	 * The commands are already split up into arguments, so just separate
1057 	 * into a set of commands by ';'.
1058 	 */
1059 
1060 	if (pi == NULL) {
1061 		memset(&input, 0, sizeof input);
1062 		pi = &input;
1063 	}
1064 	memset(&pr, 0, sizeof pr);
1065 
1066 	cmds = cmd_parse_new_commands();
1067 
1068 	cmd = xcalloc(1, sizeof *cmd);
1069 	cmd->line = pi->line;
1070 	TAILQ_INIT(&cmd->arguments);
1071 
1072 	for (i = 0; i < count; i++) {
1073 		end = 0;
1074 		if (values[i].type == ARGS_STRING) {
1075 			copy = xstrdup(values[i].string);
1076 			size = strlen(copy);
1077 			if (size != 0 && copy[size - 1] == ';') {
1078 				copy[--size] = '\0';
1079 				if (size > 0 && copy[size - 1] == '\\')
1080 					copy[size - 1] = ';';
1081 				else
1082 					end = 1;
1083 			}
1084 			if (!end || size != 0) {
1085 				arg = xcalloc(1, sizeof *arg);
1086 				arg->type = CMD_PARSE_STRING;
1087 				arg->string = copy;
1088 				TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry);
1089 			} else
1090 				free(copy);
1091 		} else if (values[i].type == ARGS_COMMANDS) {
1092 			arg = xcalloc(1, sizeof *arg);
1093 			arg->type = CMD_PARSE_PARSED_COMMANDS;
1094 			arg->cmdlist = values[i].cmdlist;
1095 			arg->cmdlist->references++;
1096 			TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry);
1097 		} else
1098 			fatalx("unknown argument type");
1099 		if (end) {
1100 			TAILQ_INSERT_TAIL(cmds, cmd, entry);
1101 			cmd = xcalloc(1, sizeof *cmd);
1102 			cmd->line = pi->line;
1103 			TAILQ_INIT(&cmd->arguments);
1104 		}
1105 	}
1106 	if (!TAILQ_EMPTY(&cmd->arguments))
1107 		TAILQ_INSERT_TAIL(cmds, cmd, entry);
1108 	else
1109 		free(cmd);
1110 
1111 	cmd_parse_build_commands(cmds, pi, &pr);
1112 	cmd_parse_free_commands(cmds);
1113 	return (&pr);
1114 }
1115 
1116 static int printflike(1, 2)
yyerror(const char * fmt,...)1117 yyerror(const char *fmt, ...)
1118 {
1119 	struct cmd_parse_state	*ps = &parse_state;
1120 	struct cmd_parse_input	*pi = ps->input;
1121 	va_list			 ap;
1122 	char			*error;
1123 
1124 	if (ps->error != NULL)
1125 		return (0);
1126 
1127 	va_start(ap, fmt);
1128 	xvasprintf(&error, fmt, ap);
1129 	va_end(ap);
1130 
1131 	ps->error = cmd_parse_get_error(pi->file, pi->line, error);
1132 	free(error);
1133 	return (0);
1134 }
1135 
1136 static int
yylex_is_var(char ch,int first)1137 yylex_is_var(char ch, int first)
1138 {
1139 	if (ch == '=')
1140 		return (0);
1141 	if (first && isdigit((u_char)ch))
1142 		return (0);
1143 	return (isalnum((u_char)ch) || ch == '_');
1144 }
1145 
1146 static void
yylex_append(char ** buf,size_t * len,const char * add,size_t addlen)1147 yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
1148 {
1149 	if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
1150 		fatalx("buffer is too big");
1151 	*buf = xrealloc(*buf, (*len) + 1 + addlen);
1152 	memcpy((*buf) + *len, add, addlen);
1153 	(*len) += addlen;
1154 }
1155 
1156 static void
yylex_append1(char ** buf,size_t * len,char add)1157 yylex_append1(char **buf, size_t *len, char add)
1158 {
1159 	yylex_append(buf, len, &add, 1);
1160 }
1161 
1162 static int
yylex_getc1(void)1163 yylex_getc1(void)
1164 {
1165 	struct cmd_parse_state	*ps = &parse_state;
1166 	int			 ch;
1167 
1168 	if (ps->f != NULL)
1169 		ch = getc(ps->f);
1170 	else {
1171 		if (ps->off == ps->len)
1172 			ch = EOF;
1173 		else
1174 			ch = ps->buf[ps->off++];
1175 	}
1176 	return (ch);
1177 }
1178 
1179 static void
yylex_ungetc(int ch)1180 yylex_ungetc(int ch)
1181 {
1182 	struct cmd_parse_state	*ps = &parse_state;
1183 
1184 	if (ps->f != NULL)
1185 		ungetc(ch, ps->f);
1186 	else if (ps->off > 0 && ch != EOF)
1187 		ps->off--;
1188 }
1189 
1190 static int
yylex_getc(void)1191 yylex_getc(void)
1192 {
1193 	struct cmd_parse_state	*ps = &parse_state;
1194 	int			 ch;
1195 
1196 	if (ps->escapes != 0) {
1197 		ps->escapes--;
1198 		return ('\\');
1199 	}
1200 	for (;;) {
1201 		ch = yylex_getc1();
1202 		if (ch == '\\') {
1203 			ps->escapes++;
1204 			continue;
1205 		}
1206 		if (ch == '\n' && (ps->escapes % 2) == 1) {
1207 			ps->input->line++;
1208 			ps->escapes--;
1209 			continue;
1210 		}
1211 
1212 		if (ps->escapes != 0) {
1213 			yylex_ungetc(ch);
1214 			ps->escapes--;
1215 			return ('\\');
1216 		}
1217 		return (ch);
1218 	}
1219 }
1220 
1221 static char *
yylex_get_word(int ch)1222 yylex_get_word(int ch)
1223 {
1224 	char	*buf;
1225 	size_t	 len;
1226 
1227 	len = 0;
1228 	buf = xmalloc(1);
1229 
1230 	do
1231 		yylex_append1(&buf, &len, ch);
1232 	while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
1233 	yylex_ungetc(ch);
1234 
1235 	buf[len] = '\0';
1236 	log_debug("%s: %s", __func__, buf);
1237 	return (buf);
1238 }
1239 
1240 static int
yylex(void)1241 yylex(void)
1242 {
1243 	struct cmd_parse_state	*ps = &parse_state;
1244 	char			*token, *cp;
1245 	int			 ch, next, condition;
1246 
1247 	if (ps->eol)
1248 		ps->input->line++;
1249 	ps->eol = 0;
1250 
1251 	condition = ps->condition;
1252 	ps->condition = 0;
1253 
1254 	for (;;) {
1255 		ch = yylex_getc();
1256 
1257 		if (ch == EOF) {
1258 			/*
1259 			 * Ensure every file or string is terminated by a
1260 			 * newline. This keeps the parser simpler and avoids
1261 			 * having to add a newline to each string.
1262 			 */
1263 			if (ps->eof)
1264 				break;
1265 			ps->eof = 1;
1266 			return ('\n');
1267 		}
1268 
1269 		if (ch == ' ' || ch == '\t') {
1270 			/*
1271 			 * Ignore whitespace.
1272 			 */
1273 			continue;
1274 		}
1275 
1276 		if (ch == '\n') {
1277 			/*
1278 			 * End of line. Update the line number.
1279 			 */
1280 			ps->eol = 1;
1281 			return ('\n');
1282 		}
1283 
1284 		if (ch == ';' || ch == '{' || ch == '}') {
1285 			/*
1286 			 * A semicolon or { or } is itself.
1287 			 */
1288 			return (ch);
1289 		}
1290 
1291 		if (ch == '#') {
1292 			/*
1293 			 * #{ after a condition opens a format; anything else
1294 			 * is a comment, ignore up to the end of the line.
1295 			 */
1296 			next = yylex_getc();
1297 			if (condition && next == '{') {
1298 				yylval.token = yylex_format();
1299 				if (yylval.token == NULL)
1300 					return (ERROR);
1301 				return (FORMAT);
1302 			}
1303 			while (next != '\n' && next != EOF)
1304 				next = yylex_getc();
1305 			if (next == '\n') {
1306 				ps->input->line++;
1307 				return ('\n');
1308 			}
1309 			continue;
1310 		}
1311 
1312 		if (ch == '%') {
1313 			/*
1314 			 * % is a condition unless it is all % or all numbers,
1315 			 * then it is a token.
1316 			 */
1317 			yylval.token = yylex_get_word('%');
1318 			for (cp = yylval.token; *cp != '\0'; cp++) {
1319 				if (*cp != '%' && !isdigit((u_char)*cp))
1320 					break;
1321 			}
1322 			if (*cp == '\0')
1323 				return (TOKEN);
1324 			ps->condition = 1;
1325 			if (strcmp(yylval.token, "%hidden") == 0) {
1326 				free(yylval.token);
1327 				return (HIDDEN);
1328 			}
1329 			if (strcmp(yylval.token, "%if") == 0) {
1330 				free(yylval.token);
1331 				return (IF);
1332 			}
1333 			if (strcmp(yylval.token, "%else") == 0) {
1334 				free(yylval.token);
1335 				return (ELSE);
1336 			}
1337 			if (strcmp(yylval.token, "%elif") == 0) {
1338 				free(yylval.token);
1339 				return (ELIF);
1340 			}
1341 			if (strcmp(yylval.token, "%endif") == 0) {
1342 				free(yylval.token);
1343 				return (ENDIF);
1344 			}
1345 			free(yylval.token);
1346 			return (ERROR);
1347 		}
1348 
1349 		/*
1350 		 * Otherwise this is a token.
1351 		 */
1352 		token = yylex_token(ch);
1353 		if (token == NULL)
1354 			return (ERROR);
1355 		yylval.token = token;
1356 
1357 		if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
1358 			for (cp = token + 1; *cp != '='; cp++) {
1359 				if (!yylex_is_var(*cp, 0))
1360 					break;
1361 			}
1362 			if (*cp == '=')
1363 				return (EQUALS);
1364 		}
1365 		return (TOKEN);
1366 	}
1367 	return (0);
1368 }
1369 
1370 static char *
yylex_format(void)1371 yylex_format(void)
1372 {
1373 	char	*buf;
1374 	size_t	 len;
1375 	int	 ch, brackets = 1;
1376 
1377 	len = 0;
1378 	buf = xmalloc(1);
1379 
1380 	yylex_append(&buf, &len, "#{", 2);
1381 	for (;;) {
1382 		if ((ch = yylex_getc()) == EOF || ch == '\n')
1383 			goto error;
1384 		if (ch == '#') {
1385 			if ((ch = yylex_getc()) == EOF || ch == '\n')
1386 				goto error;
1387 			if (ch == '{')
1388 				brackets++;
1389 			yylex_append1(&buf, &len, '#');
1390 		} else if (ch == '}') {
1391 			if (brackets != 0 && --brackets == 0) {
1392 				yylex_append1(&buf, &len, ch);
1393 				break;
1394 			}
1395 		}
1396 		yylex_append1(&buf, &len, ch);
1397 	}
1398 	if (brackets != 0)
1399 		goto error;
1400 
1401 	buf[len] = '\0';
1402 	log_debug("%s: %s", __func__, buf);
1403 	return (buf);
1404 
1405 error:
1406 	free(buf);
1407 	return (NULL);
1408 }
1409 
1410 static int
yylex_token_escape(char ** buf,size_t * len)1411 yylex_token_escape(char **buf, size_t *len)
1412 {
1413 	int	 ch, type, o2, o3, mlen;
1414 	u_int	 size, i, tmp;
1415 	char	 s[9], m[MB_LEN_MAX];
1416 
1417 	ch = yylex_getc();
1418 
1419 	if (ch >= '4' && ch <= '7') {
1420 		yyerror("invalid octal escape");
1421 		return (0);
1422 	}
1423 	if (ch >= '0' && ch <= '3') {
1424 		o2 = yylex_getc();
1425 		if (o2 >= '0' && o2 <= '7') {
1426 			o3 = yylex_getc();
1427 			if (o3 >= '0' && o3 <= '7') {
1428 				ch = 64 * (ch - '0') +
1429 				      8 * (o2 - '0') +
1430 					  (o3 - '0');
1431 				yylex_append1(buf, len, ch);
1432 				return (1);
1433 			}
1434 		}
1435 		yyerror("invalid octal escape");
1436 		return (0);
1437 	}
1438 
1439 	switch (ch) {
1440 	case EOF:
1441 		return (0);
1442 	case 'a':
1443 		ch = '\a';
1444 		break;
1445 	case 'b':
1446 		ch = '\b';
1447 		break;
1448 	case 'e':
1449 		ch = '\033';
1450 		break;
1451 	case 'f':
1452 		ch = '\f';
1453 		break;
1454 	case 's':
1455 		ch = ' ';
1456 		break;
1457 	case 'v':
1458 		ch = '\v';
1459 		break;
1460 	case 'r':
1461 		ch = '\r';
1462 		break;
1463 	case 'n':
1464 		ch = '\n';
1465 		break;
1466 	case 't':
1467 		ch = '\t';
1468 		break;
1469 	case 'u':
1470 		type = 'u';
1471 		size = 4;
1472 		goto unicode;
1473 	case 'U':
1474 		type = 'U';
1475 		size = 8;
1476 		goto unicode;
1477 	}
1478 
1479 	yylex_append1(buf, len, ch);
1480 	return (1);
1481 
1482 unicode:
1483 	for (i = 0; i < size; i++) {
1484 		ch = yylex_getc();
1485 		if (ch == EOF || ch == '\n')
1486 			return (0);
1487 		if (!isxdigit((u_char)ch)) {
1488 			yyerror("invalid \\%c argument", type);
1489 			return (0);
1490 		}
1491 		s[i] = ch;
1492 	}
1493 	s[i] = '\0';
1494 
1495 	if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
1496 	    (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
1497 		yyerror("invalid \\%c argument", type);
1498 		return (0);
1499 	}
1500 	mlen = wctomb(m, tmp);
1501 	if (mlen <= 0 || mlen > (int)sizeof m) {
1502 		yyerror("invalid \\%c argument", type);
1503 		return (0);
1504 	}
1505 	yylex_append(buf, len, m, mlen);
1506 	return (1);
1507 }
1508 
1509 static int
yylex_token_variable(char ** buf,size_t * len)1510 yylex_token_variable(char **buf, size_t *len)
1511 {
1512 	struct environ_entry	*envent;
1513 	int			 ch, brackets = 0;
1514 	char			 name[1024];
1515 	size_t			 namelen = 0;
1516 	const char		*value;
1517 
1518 	ch = yylex_getc();
1519 	if (ch == EOF)
1520 		return (0);
1521 	if (ch == '{')
1522 		brackets = 1;
1523 	else {
1524 		if (!yylex_is_var(ch, 1)) {
1525 			yylex_append1(buf, len, '$');
1526 			yylex_ungetc(ch);
1527 			return (1);
1528 		}
1529 		name[namelen++] = ch;
1530 	}
1531 
1532 	for (;;) {
1533 		ch = yylex_getc();
1534 		if (brackets && ch == '}')
1535 			break;
1536 		if (ch == EOF || !yylex_is_var(ch, 0)) {
1537 			if (!brackets) {
1538 				yylex_ungetc(ch);
1539 				break;
1540 			}
1541 			yyerror("invalid environment variable");
1542 			return (0);
1543 		}
1544 		if (namelen == (sizeof name) - 2) {
1545 			yyerror("environment variable is too long");
1546 			return (0);
1547 		}
1548 		name[namelen++] = ch;
1549 	}
1550 	name[namelen] = '\0';
1551 
1552 	envent = environ_find(global_environ, name);
1553 	if (envent != NULL && envent->value != NULL) {
1554 		value = envent->value;
1555 		log_debug("%s: %s -> %s", __func__, name, value);
1556 		yylex_append(buf, len, value, strlen(value));
1557 	}
1558 	return (1);
1559 }
1560 
1561 static int
yylex_token_tilde(char ** buf,size_t * len)1562 yylex_token_tilde(char **buf, size_t *len)
1563 {
1564 	struct environ_entry	*envent;
1565 	int			 ch;
1566 	char			 name[1024];
1567 	size_t			 namelen = 0;
1568 	struct passwd		*pw;
1569 	const char		*home = NULL;
1570 
1571 	for (;;) {
1572 		ch = yylex_getc();
1573 		if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
1574 			yylex_ungetc(ch);
1575 			break;
1576 		}
1577 		if (namelen == (sizeof name) - 2) {
1578 			yyerror("user name is too long");
1579 			return (0);
1580 		}
1581 		name[namelen++] = ch;
1582 	}
1583 	name[namelen] = '\0';
1584 
1585 	if (*name == '\0') {
1586 		envent = environ_find(global_environ, "HOME");
1587 		if (envent != NULL && *envent->value != '\0')
1588 			home = envent->value;
1589 		else if ((pw = getpwuid(getuid())) != NULL)
1590 			home = pw->pw_dir;
1591 	} else {
1592 		if ((pw = getpwnam(name)) != NULL)
1593 			home = pw->pw_dir;
1594 	}
1595 	if (home == NULL)
1596 		return (0);
1597 
1598 	log_debug("%s: ~%s -> %s", __func__, name, home);
1599 	yylex_append(buf, len, home, strlen(home));
1600 	return (1);
1601 }
1602 
1603 static char *
yylex_token(int ch)1604 yylex_token(int ch)
1605 {
1606 	char			*buf;
1607 	size_t			 len;
1608 	enum { START,
1609 	       NONE,
1610 	       DOUBLE_QUOTES,
1611 	       SINGLE_QUOTES }	 state = NONE, last = START;
1612 
1613 	len = 0;
1614 	buf = xmalloc(1);
1615 
1616 	for (;;) {
1617 		/* EOF or \n are always the end of the token. */
1618 		if (ch == EOF) {
1619 			log_debug("%s: end at EOF", __func__);
1620 			break;
1621 		}
1622 		if (state == NONE && ch == '\n') {
1623 			log_debug("%s: end at EOL", __func__);
1624 			break;
1625 		}
1626 
1627 		/* Whitespace or ; or } ends a token unless inside quotes. */
1628 		if (state == NONE && (ch == ' ' || ch == '\t')) {
1629 			log_debug("%s: end at WS", __func__);
1630 			break;
1631 		}
1632 		if (state == NONE && (ch == ';' || ch == '}')) {
1633 			log_debug("%s: end at %c", __func__, ch);
1634 			break;
1635 		}
1636 
1637 		/*
1638 		 * Spaces and comments inside quotes after \n are removed but
1639 		 * the \n is left.
1640 		 */
1641 		if (ch == '\n' && state != NONE) {
1642 			yylex_append1(&buf, &len, '\n');
1643 			while ((ch = yylex_getc()) == ' ' || ch == '\t')
1644 				/* nothing */;
1645 			if (ch != '#')
1646 				continue;
1647 			ch = yylex_getc();
1648 			if (strchr(",#{}:", ch) != NULL) {
1649 				yylex_ungetc(ch);
1650 				ch = '#';
1651 			} else {
1652 				while ((ch = yylex_getc()) != '\n' && ch != EOF)
1653 					/* nothing */;
1654 			}
1655 			continue;
1656 		}
1657 
1658 		/* \ ~ and $ are expanded except in single quotes. */
1659 		if (ch == '\\' && state != SINGLE_QUOTES) {
1660 			if (!yylex_token_escape(&buf, &len))
1661 				goto error;
1662 			goto skip;
1663 		}
1664 		if (ch == '~' && last != state && state != SINGLE_QUOTES) {
1665 			if (!yylex_token_tilde(&buf, &len))
1666 				goto error;
1667 			goto skip;
1668 		}
1669 		if (ch == '$' && state != SINGLE_QUOTES) {
1670 			if (!yylex_token_variable(&buf, &len))
1671 				goto error;
1672 			goto skip;
1673 		}
1674 		if (ch == '}' && state == NONE)
1675 			goto error;  /* unmatched (matched ones were handled) */
1676 
1677 		/* ' and " starts or end quotes (and is consumed). */
1678 		if (ch == '\'') {
1679 			if (state == NONE) {
1680 				state = SINGLE_QUOTES;
1681 				goto next;
1682 			}
1683 			if (state == SINGLE_QUOTES) {
1684 				state = NONE;
1685 				goto next;
1686 			}
1687 		}
1688 		if (ch == '"') {
1689 			if (state == NONE) {
1690 				state = DOUBLE_QUOTES;
1691 				goto next;
1692 			}
1693 			if (state == DOUBLE_QUOTES) {
1694 				state = NONE;
1695 				goto next;
1696 			}
1697 		}
1698 
1699 		/* Otherwise add the character to the buffer. */
1700 		yylex_append1(&buf, &len, ch);
1701 
1702 	skip:
1703 		last = state;
1704 
1705 	next:
1706 		ch = yylex_getc();
1707 	}
1708 	yylex_ungetc(ch);
1709 
1710 	buf[len] = '\0';
1711 	log_debug("%s: %s", __func__, buf);
1712 	return (buf);
1713 
1714 error:
1715 	free(buf);
1716 	return (NULL);
1717 }
1718