1 /* $OpenBSD$ */
2 
3 /*
4  * Copyright (c) 2008 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 #include <sys/types.h>
20 
21 #include <errno.h>
22 #include <pwd.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 
28 #include "tmux.h"
29 
30 /*
31  * Parse a command from a string.
32  */
33 
34 int	 cmd_string_getc(const char *, size_t *);
35 void	 cmd_string_ungetc(size_t *);
36 void	 cmd_string_copy(char **, char *, size_t *);
37 char	*cmd_string_string(const char *, size_t *, char, int);
38 char	*cmd_string_variable(const char *, size_t *);
39 char	*cmd_string_expand_tilde(const char *, size_t *);
40 
41 int
cmd_string_getc(const char * s,size_t * p)42 cmd_string_getc(const char *s, size_t *p)
43 {
44 	const u_char	*ucs = s;
45 
46 	if (ucs[*p] == '\0')
47 		return (EOF);
48 	return (ucs[(*p)++]);
49 }
50 
51 void
cmd_string_ungetc(size_t * p)52 cmd_string_ungetc(size_t *p)
53 {
54 	(*p)--;
55 }
56 
57 /*
58  * Parse command string. Returns -1 on error. If returning -1, cause is error
59  * string, or NULL for empty command.
60  */
61 int
cmd_string_parse(const char * s,struct cmd_list ** cmdlist,const char * file,u_int line,char ** cause)62 cmd_string_parse(const char *s, struct cmd_list **cmdlist, const char *file,
63     u_int line, char **cause)
64 {
65 	size_t		p;
66 	int		ch, i, argc, rval;
67 	char	      **argv, *buf, *t;
68 	const char     *whitespace, *equals;
69 	size_t		len;
70 
71 	argv = NULL;
72 	argc = 0;
73 
74 	buf = NULL;
75 	len = 0;
76 
77 	*cause = NULL;
78 
79 	*cmdlist = NULL;
80 	rval = -1;
81 
82 	p = 0;
83 	for (;;) {
84 		ch = cmd_string_getc(s, &p);
85 		switch (ch) {
86 		case '\'':
87 			if ((t = cmd_string_string(s, &p, '\'', 0)) == NULL)
88 				goto error;
89 			cmd_string_copy(&buf, t, &len);
90 			break;
91 		case '"':
92 			if ((t = cmd_string_string(s, &p, '"', 1)) == NULL)
93 				goto error;
94 			cmd_string_copy(&buf, t, &len);
95 			break;
96 		case '$':
97 			if ((t = cmd_string_variable(s, &p)) == NULL)
98 				goto error;
99 			cmd_string_copy(&buf, t, &len);
100 			break;
101 		case '#':
102 			/* Comment: discard rest of line. */
103 			while ((ch = cmd_string_getc(s, &p)) != EOF)
104 				;
105 			/* FALLTHROUGH */
106 		case EOF:
107 		case ' ':
108 		case '\t':
109 			if (buf != NULL) {
110 				buf = xrealloc(buf, len + 1);
111 				buf[len] = '\0';
112 
113 				argv = xreallocarray(argv, argc + 1,
114 				    sizeof *argv);
115 				argv[argc++] = buf;
116 
117 				buf = NULL;
118 				len = 0;
119 			}
120 
121 			if (ch != EOF)
122 				break;
123 
124 			while (argc != 0) {
125 				equals = strchr(argv[0], '=');
126 				whitespace = argv[0] + strcspn(argv[0], " \t");
127 				if (equals == NULL || equals > whitespace)
128 					break;
129 				environ_put(global_environ, argv[0]);
130 				argc--;
131 				memmove(argv, argv + 1, argc * (sizeof *argv));
132 			}
133 			if (argc == 0)
134 				goto out;
135 
136 			*cmdlist = cmd_list_parse(argc, argv, file, line, cause);
137 			if (*cmdlist == NULL)
138 				goto out;
139 
140 			rval = 0;
141 			goto out;
142 		case '~':
143 			if (buf == NULL) {
144 				t = cmd_string_expand_tilde(s, &p);
145 				if (t == NULL)
146 					goto error;
147 				cmd_string_copy(&buf, t, &len);
148 				break;
149 			}
150 			/* FALLTHROUGH */
151 		default:
152 			if (len >= SIZE_MAX - 2)
153 				goto error;
154 
155 			buf = xrealloc(buf, len + 1);
156 			buf[len++] = ch;
157 			break;
158 		}
159 	}
160 
161 error:
162 	xasprintf(cause, "invalid or unknown command: %s", s);
163 
164 out:
165 	free(buf);
166 
167 	if (argv != NULL) {
168 		for (i = 0; i < argc; i++)
169 			free(argv[i]);
170 		free(argv);
171 	}
172 
173 	return (rval);
174 }
175 
176 void
cmd_string_copy(char ** dst,char * src,size_t * len)177 cmd_string_copy(char **dst, char *src, size_t *len)
178 {
179 	size_t srclen;
180 
181 	srclen = strlen(src);
182 
183 	*dst = xrealloc(*dst, *len + srclen + 1);
184 	strlcpy(*dst + *len, src, srclen + 1);
185 
186 	*len += srclen;
187 	free(src);
188 }
189 
190 char *
cmd_string_string(const char * s,size_t * p,char endch,int esc)191 cmd_string_string(const char *s, size_t *p, char endch, int esc)
192 {
193 	int	ch;
194 	char   *buf, *t;
195 	size_t	len;
196 
197 	buf = NULL;
198 	len = 0;
199 
200 	while ((ch = cmd_string_getc(s, p)) != endch) {
201 		switch (ch) {
202 		case EOF:
203 			goto error;
204 		case '\\':
205 			if (!esc)
206 				break;
207 			switch (ch = cmd_string_getc(s, p)) {
208 			case EOF:
209 				goto error;
210 			case 'e':
211 				ch = '\033';
212 				break;
213 			case 'r':
214 				ch = '\r';
215 				break;
216 			case 'n':
217 				ch = '\n';
218 				break;
219 			case 't':
220 				ch = '\t';
221 				break;
222 			}
223 			break;
224 		case '$':
225 			if (!esc)
226 				break;
227 			if ((t = cmd_string_variable(s, p)) == NULL)
228 				goto error;
229 			cmd_string_copy(&buf, t, &len);
230 			continue;
231 		}
232 
233 		if (len >= SIZE_MAX - 2)
234 			goto error;
235 		buf = xrealloc(buf, len + 1);
236 		buf[len++] = ch;
237 	}
238 
239 	buf = xrealloc(buf, len + 1);
240 	buf[len] = '\0';
241 	return (buf);
242 
243 error:
244 	free(buf);
245 	return (NULL);
246 }
247 
248 char *
cmd_string_variable(const char * s,size_t * p)249 cmd_string_variable(const char *s, size_t *p)
250 {
251 	int			ch, fch;
252 	char		       *buf, *t;
253 	size_t			len;
254 	struct environ_entry   *envent;
255 
256 #define cmd_string_first(ch) ((ch) == '_' || \
257 	((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z'))
258 #define cmd_string_other(ch) ((ch) == '_' || \
259 	((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z') || \
260 	((ch) >= '0' && (ch) <= '9'))
261 
262 	buf = NULL;
263 	len = 0;
264 
265 	fch = EOF;
266 	switch (ch = cmd_string_getc(s, p)) {
267 	case EOF:
268 		goto error;
269 	case '{':
270 		fch = '{';
271 
272 		ch = cmd_string_getc(s, p);
273 		if (!cmd_string_first(ch))
274 			goto error;
275 		/* FALLTHROUGH */
276 	default:
277 		if (!cmd_string_first(ch)) {
278 			xasprintf(&t, "$%c", ch);
279 			return (t);
280 		}
281 
282 		buf = xrealloc(buf, len + 1);
283 		buf[len++] = ch;
284 
285 		for (;;) {
286 			ch = cmd_string_getc(s, p);
287 			if (ch == EOF || !cmd_string_other(ch))
288 				break;
289 			else {
290 				if (len >= SIZE_MAX - 3)
291 					goto error;
292 				buf = xrealloc(buf, len + 1);
293 				buf[len++] = ch;
294 			}
295 		}
296 	}
297 
298 	if (fch == '{' && ch != '}')
299 		goto error;
300 	if (ch != EOF && fch != '{')
301 		cmd_string_ungetc(p); /* ch */
302 
303 	buf = xrealloc(buf, len + 1);
304 	buf[len] = '\0';
305 
306 	envent = environ_find(global_environ, buf);
307 	free(buf);
308 	if (envent == NULL)
309 		return (xstrdup(""));
310 	return (xstrdup(envent->value));
311 
312 error:
313 	free(buf);
314 	return (NULL);
315 }
316 
317 char *
cmd_string_expand_tilde(const char * s,size_t * p)318 cmd_string_expand_tilde(const char *s, size_t *p)
319 {
320 	struct passwd		*pw;
321 	struct environ_entry	*envent;
322 	char			*home, *path, *user, *cp;
323 	int			 last;
324 
325 	home = NULL;
326 
327 	last = cmd_string_getc(s, p);
328 	if (last == EOF || last == '/' || last == ' '|| last == '\t') {
329 		envent = environ_find(global_environ, "HOME");
330 		if (envent != NULL && *envent->value != '\0')
331 			home = envent->value;
332 		else if ((pw = getpwuid(getuid())) != NULL)
333 			home = pw->pw_dir;
334 	} else {
335 		cmd_string_ungetc(p);
336 
337 		cp = user = xmalloc(strlen(s));
338 		for (;;) {
339 			last = cmd_string_getc(s, p);
340 			if (last == EOF || last == '/' || last == ' '|| last == '\t')
341 				break;
342 			*cp++ = last;
343 		}
344 		*cp = '\0';
345 
346 		if ((pw = getpwnam(user)) != NULL)
347 			home = pw->pw_dir;
348 		free(user);
349 	}
350 
351 	if (home == NULL)
352 		return (NULL);
353 
354 	if (last != EOF)
355 		xasprintf(&path, "%s%c", home, last);
356 	else
357 		xasprintf(&path, "%s", home);
358 	return (path);
359 }
360