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