Line | Count | Source (jump to first uncovered line) |
1 | | /* $OpenBSD$ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2007 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 | | #include <sys/stat.h> |
21 | | #include <sys/utsname.h> |
22 | | |
23 | | #include <errno.h> |
24 | | #include <fcntl.h> |
25 | | #include <langinfo.h> |
26 | | #include <locale.h> |
27 | | #include <pwd.h> |
28 | | #include <signal.h> |
29 | | #include <stdlib.h> |
30 | | #include <string.h> |
31 | | #include <time.h> |
32 | | #include <unistd.h> |
33 | | |
34 | | #include "tmux.h" |
35 | | |
36 | | struct options *global_options; /* server options */ |
37 | | struct options *global_s_options; /* session options */ |
38 | | struct options *global_w_options; /* window options */ |
39 | | struct environ *global_environ; |
40 | | |
41 | | struct timeval start_time; |
42 | | const char *socket_path; |
43 | | int ptm_fd = -1; |
44 | | const char *shell_command; |
45 | | |
46 | | static __dead void usage(int); |
47 | | static char *make_label(const char *, char **); |
48 | | |
49 | | static int areshell(const char *); |
50 | | static const char *getshell(void); |
51 | | |
52 | | static __dead void |
53 | | usage(int status) |
54 | 0 | { |
55 | 0 | fprintf(status ? stderr : stdout, |
56 | 0 | "usage: %s [-2CDhlNuVv] [-c shell-command] [-f file] [-L socket-name]\n" |
57 | 0 | " [-S socket-path] [-T features] [command [flags]]\n", |
58 | 0 | getprogname()); |
59 | 0 | exit(status); |
60 | 0 | } |
61 | | |
62 | | static const char * |
63 | | getshell(void) |
64 | 0 | { |
65 | 0 | struct passwd *pw; |
66 | 0 | const char *shell; |
67 | |
|
68 | 0 | shell = getenv("SHELL"); |
69 | 0 | if (checkshell(shell)) |
70 | 0 | return (shell); |
71 | | |
72 | 0 | pw = getpwuid(getuid()); |
73 | 0 | if (pw != NULL && checkshell(pw->pw_shell)) |
74 | 0 | return (pw->pw_shell); |
75 | | |
76 | 0 | return (_PATH_BSHELL); |
77 | 0 | } |
78 | | |
79 | | int |
80 | | checkshell(const char *shell) |
81 | 0 | { |
82 | 0 | if (shell == NULL || *shell != '/') |
83 | 0 | return (0); |
84 | 0 | if (areshell(shell)) |
85 | 0 | return (0); |
86 | 0 | if (access(shell, X_OK) != 0) |
87 | 0 | return (0); |
88 | 0 | return (1); |
89 | 0 | } |
90 | | |
91 | | static int |
92 | | areshell(const char *shell) |
93 | 0 | { |
94 | 0 | const char *progname, *ptr; |
95 | |
|
96 | 0 | if ((ptr = strrchr(shell, '/')) != NULL) |
97 | 0 | ptr++; |
98 | 0 | else |
99 | 0 | ptr = shell; |
100 | 0 | progname = getprogname(); |
101 | 0 | if (*progname == '-') |
102 | 0 | progname++; |
103 | 0 | if (strcmp(ptr, progname) == 0) |
104 | 0 | return (1); |
105 | 0 | return (0); |
106 | 0 | } |
107 | | |
108 | | static char * |
109 | | expand_path(const char *path, const char *home) |
110 | 0 | { |
111 | 0 | char *expanded, *name; |
112 | 0 | const char *end; |
113 | 0 | struct environ_entry *value; |
114 | |
|
115 | 0 | if (strncmp(path, "~/", 2) == 0) { |
116 | 0 | if (home == NULL) |
117 | 0 | return (NULL); |
118 | 0 | xasprintf(&expanded, "%s%s", home, path + 1); |
119 | 0 | return (expanded); |
120 | 0 | } |
121 | | |
122 | 0 | if (*path == '$') { |
123 | 0 | end = strchr(path, '/'); |
124 | 0 | if (end == NULL) |
125 | 0 | name = xstrdup(path + 1); |
126 | 0 | else |
127 | 0 | name = xstrndup(path + 1, end - path - 1); |
128 | 0 | value = environ_find(global_environ, name); |
129 | 0 | free(name); |
130 | 0 | if (value == NULL) |
131 | 0 | return (NULL); |
132 | 0 | if (end == NULL) |
133 | 0 | end = ""; |
134 | 0 | xasprintf(&expanded, "%s%s", value->value, end); |
135 | 0 | return (expanded); |
136 | 0 | } |
137 | | |
138 | 0 | return (xstrdup(path)); |
139 | 0 | } |
140 | | |
141 | | static void |
142 | | expand_paths(const char *s, char ***paths, u_int *n, int ignore_errors) |
143 | 0 | { |
144 | 0 | const char *home = find_home(); |
145 | 0 | char *copy, *next, *tmp, resolved[PATH_MAX], *expanded; |
146 | 0 | char *path; |
147 | 0 | u_int i; |
148 | |
|
149 | 0 | *paths = NULL; |
150 | 0 | *n = 0; |
151 | |
|
152 | 0 | copy = tmp = xstrdup(s); |
153 | 0 | while ((next = strsep(&tmp, ":")) != NULL) { |
154 | 0 | expanded = expand_path(next, home); |
155 | 0 | if (expanded == NULL) { |
156 | 0 | log_debug("%s: invalid path: %s", __func__, next); |
157 | 0 | continue; |
158 | 0 | } |
159 | 0 | if (realpath(expanded, resolved) == NULL) { |
160 | 0 | log_debug("%s: realpath(\"%s\") failed: %s", __func__, |
161 | 0 | expanded, strerror(errno)); |
162 | 0 | if (ignore_errors) { |
163 | 0 | free(expanded); |
164 | 0 | continue; |
165 | 0 | } |
166 | 0 | path = expanded; |
167 | 0 | } else { |
168 | 0 | path = xstrdup(resolved); |
169 | 0 | free(expanded); |
170 | 0 | } |
171 | 0 | for (i = 0; i < *n; i++) { |
172 | 0 | if (strcmp(path, (*paths)[i]) == 0) |
173 | 0 | break; |
174 | 0 | } |
175 | 0 | if (i != *n) { |
176 | 0 | log_debug("%s: duplicate path: %s", __func__, path); |
177 | 0 | free(path); |
178 | 0 | continue; |
179 | 0 | } |
180 | 0 | *paths = xreallocarray(*paths, (*n) + 1, sizeof *paths); |
181 | 0 | (*paths)[(*n)++] = path; |
182 | 0 | } |
183 | 0 | free(copy); |
184 | 0 | } |
185 | | |
186 | | static char * |
187 | | make_label(const char *label, char **cause) |
188 | 0 | { |
189 | 0 | char **paths, *path, *base; |
190 | 0 | u_int i, n; |
191 | 0 | struct stat sb; |
192 | 0 | uid_t uid; |
193 | |
|
194 | 0 | *cause = NULL; |
195 | 0 | if (label == NULL) |
196 | 0 | label = "default"; |
197 | 0 | uid = getuid(); |
198 | |
|
199 | 0 | expand_paths(TMUX_SOCK, &paths, &n, 1); |
200 | 0 | if (n == 0) { |
201 | 0 | xasprintf(cause, "no suitable socket path"); |
202 | 0 | return (NULL); |
203 | 0 | } |
204 | 0 | path = paths[0]; /* can only have one socket! */ |
205 | 0 | for (i = 1; i < n; i++) |
206 | 0 | free(paths[i]); |
207 | 0 | free(paths); |
208 | |
|
209 | 0 | xasprintf(&base, "%s/tmux-%ld", path, (long)uid); |
210 | 0 | free(path); |
211 | 0 | if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) { |
212 | 0 | xasprintf(cause, "couldn't create directory %s (%s)", base, |
213 | 0 | strerror(errno)); |
214 | 0 | goto fail; |
215 | 0 | } |
216 | 0 | if (lstat(base, &sb) != 0) { |
217 | 0 | xasprintf(cause, "couldn't read directory %s (%s)", base, |
218 | 0 | strerror(errno)); |
219 | 0 | goto fail; |
220 | 0 | } |
221 | 0 | if (!S_ISDIR(sb.st_mode)) { |
222 | 0 | xasprintf(cause, "%s is not a directory", base); |
223 | 0 | goto fail; |
224 | 0 | } |
225 | 0 | if (sb.st_uid != uid || (sb.st_mode & TMUX_SOCK_PERM) != 0) { |
226 | 0 | xasprintf(cause, "directory %s has unsafe permissions", base); |
227 | 0 | goto fail; |
228 | 0 | } |
229 | 0 | xasprintf(&path, "%s/%s", base, label); |
230 | 0 | free(base); |
231 | 0 | return (path); |
232 | | |
233 | 0 | fail: |
234 | 0 | free(base); |
235 | 0 | return (NULL); |
236 | 0 | } |
237 | | |
238 | | char * |
239 | | shell_argv0(const char *shell, int is_login) |
240 | 0 | { |
241 | 0 | const char *slash, *name; |
242 | 0 | char *argv0; |
243 | |
|
244 | 0 | slash = strrchr(shell, '/'); |
245 | 0 | if (slash != NULL && slash[1] != '\0') |
246 | 0 | name = slash + 1; |
247 | 0 | else |
248 | 0 | name = shell; |
249 | 0 | if (is_login) |
250 | 0 | xasprintf(&argv0, "-%s", name); |
251 | 0 | else |
252 | 0 | xasprintf(&argv0, "%s", name); |
253 | 0 | return (argv0); |
254 | 0 | } |
255 | | |
256 | | void |
257 | | setblocking(int fd, int state) |
258 | 0 | { |
259 | 0 | int mode; |
260 | |
|
261 | 0 | if ((mode = fcntl(fd, F_GETFL)) != -1) { |
262 | 0 | if (!state) |
263 | 0 | mode |= O_NONBLOCK; |
264 | 0 | else |
265 | 0 | mode &= ~O_NONBLOCK; |
266 | 0 | fcntl(fd, F_SETFL, mode); |
267 | 0 | } |
268 | 0 | } |
269 | | |
270 | | uint64_t |
271 | | get_timer(void) |
272 | 0 | { |
273 | 0 | struct timespec ts; |
274 | | |
275 | | /* |
276 | | * We want a timestamp in milliseconds suitable for time measurement, |
277 | | * so prefer the monotonic clock. |
278 | | */ |
279 | 0 | if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) |
280 | 0 | clock_gettime(CLOCK_REALTIME, &ts); |
281 | 0 | return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL)); |
282 | 0 | } |
283 | | |
284 | | const char * |
285 | | sig2name(int signo) |
286 | 0 | { |
287 | 0 | static char s[11]; |
288 | |
|
289 | | #ifdef HAVE_SYS_SIGNAME |
290 | | if (signo > 0 && signo < NSIG) |
291 | | return (sys_signame[signo]); |
292 | | #endif |
293 | 0 | xsnprintf(s, sizeof s, "%d", signo); |
294 | 0 | return (s); |
295 | 0 | } |
296 | | |
297 | | const char * |
298 | | find_cwd(void) |
299 | 0 | { |
300 | 0 | char resolved1[PATH_MAX], resolved2[PATH_MAX]; |
301 | 0 | static char cwd[PATH_MAX]; |
302 | 0 | const char *pwd; |
303 | |
|
304 | 0 | if (getcwd(cwd, sizeof cwd) == NULL) |
305 | 0 | return (NULL); |
306 | 0 | if ((pwd = getenv("PWD")) == NULL || *pwd == '\0') |
307 | 0 | return (cwd); |
308 | | |
309 | | /* |
310 | | * We want to use PWD so that symbolic links are maintained, |
311 | | * but only if it matches the actual working directory. |
312 | | */ |
313 | 0 | if (realpath(pwd, resolved1) == NULL) |
314 | 0 | return (cwd); |
315 | 0 | if (realpath(cwd, resolved2) == NULL) |
316 | 0 | return (cwd); |
317 | 0 | if (strcmp(resolved1, resolved2) != 0) |
318 | 0 | return (cwd); |
319 | 0 | return (pwd); |
320 | 0 | } |
321 | | |
322 | | const char * |
323 | | find_home(void) |
324 | 0 | { |
325 | 0 | struct passwd *pw; |
326 | 0 | static const char *home; |
327 | |
|
328 | 0 | if (home != NULL) |
329 | 0 | return (home); |
330 | | |
331 | 0 | home = getenv("HOME"); |
332 | 0 | if (home == NULL || *home == '\0') { |
333 | 0 | pw = getpwuid(getuid()); |
334 | 0 | if (pw != NULL) |
335 | 0 | home = pw->pw_dir; |
336 | 0 | else |
337 | 0 | home = NULL; |
338 | 0 | } |
339 | |
|
340 | 0 | return (home); |
341 | 0 | } |
342 | | |
343 | | const char * |
344 | | getversion(void) |
345 | 6.59k | { |
346 | 6.59k | return (TMUX_VERSION); |
347 | 6.59k | } |
348 | | |
349 | | int |
350 | | main(int argc, char **argv) |
351 | 0 | { |
352 | 0 | char *path = NULL, *label = NULL; |
353 | 0 | char *cause, **var; |
354 | 0 | const char *s, *cwd; |
355 | 0 | int opt, keys, feat = 0, fflag = 0; |
356 | 0 | uint64_t flags = 0; |
357 | 0 | const struct options_table_entry *oe; |
358 | 0 | u_int i; |
359 | |
|
360 | 0 | if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL && |
361 | 0 | setlocale(LC_CTYPE, "C.UTF-8") == NULL) { |
362 | 0 | if (setlocale(LC_CTYPE, "") == NULL) |
363 | 0 | errx(1, "invalid LC_ALL, LC_CTYPE or LANG"); |
364 | 0 | s = nl_langinfo(CODESET); |
365 | 0 | if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0) |
366 | 0 | errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s); |
367 | 0 | } |
368 | |
|
369 | 0 | setlocale(LC_TIME, ""); |
370 | 0 | tzset(); |
371 | |
|
372 | 0 | if (**argv == '-') |
373 | 0 | flags = CLIENT_LOGIN; |
374 | |
|
375 | 0 | global_environ = environ_create(); |
376 | 0 | for (var = environ; *var != NULL; var++) |
377 | 0 | environ_put(global_environ, *var, 0); |
378 | 0 | if ((cwd = find_cwd()) != NULL) |
379 | 0 | environ_set(global_environ, "PWD", 0, "%s", cwd); |
380 | 0 | expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1); |
381 | |
|
382 | 0 | while ((opt = getopt(argc, argv, "2c:CDdf:hlL:NqS:T:uUvV")) != -1) { |
383 | 0 | switch (opt) { |
384 | 0 | case '2': |
385 | 0 | tty_add_features(&feat, "256", ":,"); |
386 | 0 | break; |
387 | 0 | case 'c': |
388 | 0 | shell_command = optarg; |
389 | 0 | break; |
390 | 0 | case 'D': |
391 | 0 | flags |= CLIENT_NOFORK; |
392 | 0 | break; |
393 | 0 | case 'C': |
394 | 0 | if (flags & CLIENT_CONTROL) |
395 | 0 | flags |= CLIENT_CONTROLCONTROL; |
396 | 0 | else |
397 | 0 | flags |= CLIENT_CONTROL; |
398 | 0 | break; |
399 | 0 | case 'f': |
400 | 0 | if (!fflag) { |
401 | 0 | fflag = 1; |
402 | 0 | for (i = 0; i < cfg_nfiles; i++) |
403 | 0 | free(cfg_files[i]); |
404 | 0 | cfg_nfiles = 0; |
405 | 0 | } |
406 | 0 | cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1, |
407 | 0 | sizeof *cfg_files); |
408 | 0 | cfg_files[cfg_nfiles++] = xstrdup(optarg); |
409 | 0 | cfg_quiet = 0; |
410 | 0 | break; |
411 | 0 | case 'h': |
412 | 0 | usage(0); |
413 | 0 | case 'V': |
414 | 0 | printf("tmux %s\n", getversion()); |
415 | 0 | exit(0); |
416 | 0 | case 'l': |
417 | 0 | flags |= CLIENT_LOGIN; |
418 | 0 | break; |
419 | 0 | case 'L': |
420 | 0 | free(label); |
421 | 0 | label = xstrdup(optarg); |
422 | 0 | break; |
423 | 0 | case 'N': |
424 | 0 | flags |= CLIENT_NOSTARTSERVER; |
425 | 0 | break; |
426 | 0 | case 'q': |
427 | 0 | break; |
428 | 0 | case 'S': |
429 | 0 | free(path); |
430 | 0 | path = xstrdup(optarg); |
431 | 0 | break; |
432 | 0 | case 'T': |
433 | 0 | tty_add_features(&feat, optarg, ":,"); |
434 | 0 | break; |
435 | 0 | case 'u': |
436 | 0 | flags |= CLIENT_UTF8; |
437 | 0 | break; |
438 | 0 | case 'v': |
439 | 0 | log_add_level(); |
440 | 0 | break; |
441 | 0 | default: |
442 | 0 | usage(1); |
443 | 0 | } |
444 | 0 | } |
445 | 0 | argc -= optind; |
446 | 0 | argv += optind; |
447 | |
|
448 | 0 | if (shell_command != NULL && argc != 0) |
449 | 0 | usage(1); |
450 | 0 | if ((flags & CLIENT_NOFORK) && argc != 0) |
451 | 0 | usage(1); |
452 | | |
453 | 0 | if ((ptm_fd = getptmfd()) == -1) |
454 | 0 | err(1, "getptmfd"); |
455 | 0 | if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd " |
456 | 0 | "recvfd proc exec tty ps", NULL) != 0) |
457 | 0 | err(1, "pledge"); |
458 | | |
459 | | /* |
460 | | * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8. |
461 | | * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain |
462 | | * UTF-8, it is a safe assumption that either they are using a UTF-8 |
463 | | * terminal, or if not they know that output from UTF-8-capable |
464 | | * programs may be wrong. |
465 | | */ |
466 | 0 | if (getenv("TMUX") != NULL) |
467 | 0 | flags |= CLIENT_UTF8; |
468 | 0 | else { |
469 | 0 | s = getenv("LC_ALL"); |
470 | 0 | if (s == NULL || *s == '\0') |
471 | 0 | s = getenv("LC_CTYPE"); |
472 | 0 | if (s == NULL || *s == '\0') |
473 | 0 | s = getenv("LANG"); |
474 | 0 | if (s == NULL || *s == '\0') |
475 | 0 | s = ""; |
476 | 0 | if (strcasestr(s, "UTF-8") != NULL || |
477 | 0 | strcasestr(s, "UTF8") != NULL) |
478 | 0 | flags |= CLIENT_UTF8; |
479 | 0 | } |
480 | |
|
481 | 0 | global_options = options_create(NULL); |
482 | 0 | global_s_options = options_create(NULL); |
483 | 0 | global_w_options = options_create(NULL); |
484 | 0 | for (oe = options_table; oe->name != NULL; oe++) { |
485 | 0 | if (oe->scope & OPTIONS_TABLE_SERVER) |
486 | 0 | options_default(global_options, oe); |
487 | 0 | if (oe->scope & OPTIONS_TABLE_SESSION) |
488 | 0 | options_default(global_s_options, oe); |
489 | 0 | if (oe->scope & OPTIONS_TABLE_WINDOW) |
490 | 0 | options_default(global_w_options, oe); |
491 | 0 | } |
492 | | |
493 | | /* |
494 | | * The default shell comes from SHELL or from the user's passwd entry |
495 | | * if available. |
496 | | */ |
497 | 0 | options_set_string(global_s_options, "default-shell", 0, "%s", |
498 | 0 | getshell()); |
499 | | |
500 | | /* Override keys to vi if VISUAL or EDITOR are set. */ |
501 | 0 | if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) { |
502 | 0 | options_set_string(global_options, "editor", 0, "%s", s); |
503 | 0 | if (strrchr(s, '/') != NULL) |
504 | 0 | s = strrchr(s, '/') + 1; |
505 | 0 | if (strstr(s, "vi") != NULL) |
506 | 0 | keys = MODEKEY_VI; |
507 | 0 | else |
508 | 0 | keys = MODEKEY_EMACS; |
509 | 0 | options_set_number(global_s_options, "status-keys", keys); |
510 | 0 | options_set_number(global_w_options, "mode-keys", keys); |
511 | 0 | } |
512 | | |
513 | | /* |
514 | | * If socket is specified on the command-line with -S or -L, it is |
515 | | * used. Otherwise, $TMUX is checked and if that fails "default" is |
516 | | * used. |
517 | | */ |
518 | 0 | if (path == NULL && label == NULL) { |
519 | 0 | s = getenv("TMUX"); |
520 | 0 | if (s != NULL && *s != '\0' && *s != ',') { |
521 | 0 | path = xstrdup(s); |
522 | 0 | path[strcspn(path, ",")] = '\0'; |
523 | 0 | } |
524 | 0 | } |
525 | 0 | if (path == NULL) { |
526 | 0 | if ((path = make_label(label, &cause)) == NULL) { |
527 | 0 | if (cause != NULL) { |
528 | 0 | fprintf(stderr, "%s\n", cause); |
529 | 0 | free(cause); |
530 | 0 | } |
531 | 0 | exit(1); |
532 | 0 | } |
533 | 0 | flags |= CLIENT_DEFAULTSOCKET; |
534 | 0 | } |
535 | 0 | socket_path = path; |
536 | 0 | free(label); |
537 | | |
538 | | /* Pass control to the client. */ |
539 | 0 | exit(client_main(osdep_event_init(), argc, argv, flags, feat)); |
540 | 0 | } |