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
22 #include <err.h>
23 #include <errno.h>
24 #include <event.h>
25 #include <fcntl.h>
26 #include <getopt.h>
27 #include <langinfo.h>
28 #include <locale.h>
29 #include <pwd.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33 #include <unistd.h>
34
35 #include "tmux.h"
36 #include "tmate.h"
37
38 struct options *global_options; /* server options */
39 struct options *global_s_options; /* session options */
40 struct options *global_w_options; /* window options */
41 struct environ *global_environ;
42 struct hooks *global_hooks;
43
44 struct timeval start_time;
45 const char *socket_path;
46
47 __dead void usage(void);
48 static char *make_label(const char *);
49
50 #ifndef HAVE___PROGNAME
51 char *__progname = (char *) "tmate";
52 #endif
53
54 #ifdef TMATE
55 int tmate_foreground;
56 #endif
57
58 __dead void
usage(void)59 usage(void)
60 {
61 fprintf(stderr,
62 "Usage: %s [options] [tmux-command [flags]]\n"
63 "\n"
64 "Basic options:\n"
65 " -n <name> specify the session token instead of getting a random one\n"
66 " -r <name> same, but for the read-only token\n"
67 " -k <key> specify an api-key, necessary for using named sessions on tmate.io\n"
68 " -F set the foreground mode, useful for setting remote access\n"
69 " -f <path> set the config file path\n"
70 " -S <path> set the socket path, useful to issue commands to a running tmate instance\n"
71 " -v set verbosity (can be repeated)\n"
72 " -V print version\n"
73 ,__progname);
74 exit(1);
75 }
76
77 const char *
getshell(void)78 getshell(void)
79 {
80 struct passwd *pw;
81 const char *shell;
82
83 shell = getenv("SHELL");
84 if (checkshell(shell))
85 return (shell);
86
87 pw = getpwuid(getuid());
88 if (pw != NULL && checkshell(pw->pw_shell))
89 return (pw->pw_shell);
90
91 return (_PATH_BSHELL);
92 }
93
94 int
checkshell(const char * shell)95 checkshell(const char *shell)
96 {
97 if (shell == NULL || *shell == '\0' || *shell != '/')
98 return (0);
99 if (areshell(shell))
100 return (0);
101 if (access(shell, X_OK) != 0)
102 return (0);
103 return (1);
104 }
105
106 int
areshell(const char * shell)107 areshell(const char *shell)
108 {
109 const char *progname, *ptr;
110
111 if ((ptr = strrchr(shell, '/')) != NULL)
112 ptr++;
113 else
114 ptr = shell;
115 progname = __progname;
116 if (*progname == '-')
117 progname++;
118 if (strcmp(ptr, progname) == 0)
119 return (1);
120 return (0);
121 }
122
123 static char *
make_label(const char * label)124 make_label(const char *label)
125 {
126 char *base, resolved[PATH_MAX], *path, *s;
127 struct stat sb;
128 u_int uid;
129 int saved_errno;
130 #ifdef TMATE
131 int do_random_label = label == NULL;
132 #endif
133
134 if (label == NULL)
135 label = "default";
136
137 uid = getuid();
138
139 if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0')
140 xasprintf(&base, "%s/tmate-%u", s, uid);
141 else
142 xasprintf(&base, "%s/tmate-%u", _PATH_TMP, uid);
143
144 if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST)
145 goto fail;
146
147 if (lstat(base, &sb) != 0)
148 goto fail;
149 if (!S_ISDIR(sb.st_mode)) {
150 errno = ENOTDIR;
151 goto fail;
152 }
153 if (sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0) {
154 errno = EACCES;
155 goto fail;
156 }
157
158 #ifdef TMATE
159 if (do_random_label)
160 label = "XXXXXX";
161 #endif
162
163 if (realpath(base, resolved) == NULL)
164 strlcpy(resolved, base, sizeof resolved);
165 xasprintf(&path, "%s/%s", resolved, label);
166 #ifdef TMATE
167 if (do_random_label)
168 mktemp(path);
169 #endif
170 return (path);
171
172 fail:
173 saved_errno = errno;
174 free(base);
175 errno = saved_errno;
176 return (NULL);
177 }
178
179 void
setblocking(int fd,int state)180 setblocking(int fd, int state)
181 {
182 int mode;
183
184 if ((mode = fcntl(fd, F_GETFL)) != -1) {
185 if (!state)
186 mode |= O_NONBLOCK;
187 else
188 mode &= ~O_NONBLOCK;
189 fcntl(fd, F_SETFL, mode);
190 }
191 }
192
193 const char *
find_home(void)194 find_home(void)
195 {
196 struct passwd *pw;
197 static const char *home;
198
199 if (home != NULL)
200 return (home);
201
202 home = getenv("HOME");
203 if (home == NULL || *home == '\0') {
204 pw = getpwuid(getuid());
205 if (pw != NULL)
206 home = pw->pw_dir;
207 else
208 home = NULL;
209 }
210
211 return (home);
212 }
213
214 #ifdef TMATE
215 static char *api_key;
216 static char *session_name;
217 static char *session_name_ro;
218 static char *authorized_keys;
219
tmate_load_cli_options(void)220 void tmate_load_cli_options(void)
221 {
222 #define SET_OPT(name, val) ({\
223 if (val) { \
224 run_headless_command(3, (const char *[]){"set-option", name, val}, DEFER_ERRORS_CFG, NULL); \
225 free(val); \
226 val = NULL; \
227 } \
228 })
229 SET_OPT("tmate-api-key", api_key);
230 SET_OPT("tmate-session-name", session_name);
231 SET_OPT("tmate-session-name-ro", session_name_ro);
232 SET_OPT("tmate-authorized-keys", authorized_keys);
233 #undef SET_OPT
234 }
235 #endif
236
237 int
main(int argc,char ** argv)238 main(int argc, char **argv)
239 {
240 char *path, *label, **var, tmp[PATH_MAX], *shellcmd = NULL;
241 const char *s;
242 int opt, flags, keys;
243
244 if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL &&
245 setlocale(LC_CTYPE, "C.UTF-8") == NULL) {
246 if (setlocale(LC_CTYPE, "") == NULL)
247 errx(1, "invalid LC_ALL, LC_CTYPE or LANG");
248 s = nl_langinfo(CODESET);
249 if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0)
250 errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s);
251 }
252
253 setlocale(LC_TIME, "");
254 tzset();
255
256 if (**argv == '-')
257 flags = CLIENT_LOGIN;
258 else
259 flags = 0;
260
261 #ifdef TMATE
262 tmate_catch_sigsegv();
263 flags |= CLIENT_256COLOURS | CLIENT_UTF8;
264 #endif
265
266 label = path = NULL;
267 while ((opt = getopt(argc, argv, "h2c:CdFf:lL:qS:uUVvk:n:r:a:")) != -1) {
268 switch (opt) {
269 case '2':
270 flags |= CLIENT_256COLOURS;
271 break;
272 case 'c':
273 free(shellcmd);
274 shellcmd = xstrdup(optarg);
275 break;
276 case 'C':
277 if (flags & CLIENT_CONTROL)
278 flags |= CLIENT_CONTROLCONTROL;
279 else
280 flags |= CLIENT_CONTROL;
281 break;
282 case 'V':
283 printf("%s %s\n", __progname, VERSION);
284 exit(0);
285 case 'f':
286 set_cfg_file(optarg);
287 break;
288 case 'l':
289 flags |= CLIENT_LOGIN;
290 break;
291 case 'L':
292 free(label);
293 label = xstrdup(optarg);
294 break;
295 case 'q':
296 break;
297 case 'S':
298 free(path);
299 path = xstrdup(optarg);
300 break;
301 case 'u':
302 flags |= CLIENT_UTF8;
303 break;
304 case 'v':
305 log_add_level();
306 break;
307 case 'F':
308 tmate_foreground = 1;
309 log_add_level();
310 unsetenv("TMUX");
311 break;
312 case 'k':
313 api_key = xstrdup(optarg);
314 break;
315 case 'n':
316 session_name = xstrdup(optarg);
317 break;
318 case 'r':
319 session_name_ro = xstrdup(optarg);
320 break;
321 case 'a':
322 authorized_keys = xstrdup(optarg);
323 break;
324 case 'h':
325 default:
326 usage();
327 }
328 }
329 argc -= optind;
330 argv += optind;
331
332 if (shellcmd != NULL && argc != 0)
333 usage();
334
335 #ifdef __OpenBSD__
336 if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd "
337 "recvfd proc exec tty ps", NULL) != 0)
338 err(1, "pledge");
339 #endif
340
341 /*
342 * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8.
343 * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain
344 * UTF-8, it is a safe assumption that either they are using a UTF-8
345 * terminal, or if not they know that output from UTF-8-capable
346 * programs may be wrong.
347 */
348 if (getenv("TMUX") != NULL)
349 flags |= CLIENT_UTF8;
350 else {
351 s = getenv("LC_ALL");
352 if (s == NULL || *s == '\0')
353 s = getenv("LC_CTYPE");
354 if (s == NULL || *s == '\0')
355 s = getenv("LANG");
356 if (s == NULL || *s == '\0')
357 s = "";
358 if (strcasestr(s, "UTF-8") != NULL ||
359 strcasestr(s, "UTF8") != NULL)
360 flags |= CLIENT_UTF8;
361 }
362
363 global_hooks = hooks_create(NULL);
364
365 global_environ = environ_create();
366 for (var = environ; *var != NULL; var++)
367 environ_put(global_environ, *var);
368 if (getcwd(tmp, sizeof tmp) != NULL)
369 environ_set(global_environ, "PWD", "%s", tmp);
370
371 global_options = options_create(NULL);
372 options_table_populate_tree(OPTIONS_TABLE_SERVER, global_options);
373
374 global_s_options = options_create(NULL);
375 options_table_populate_tree(OPTIONS_TABLE_SESSION, global_s_options);
376 options_set_string(global_s_options, "default-shell", "%s", getshell());
377
378 global_w_options = options_create(NULL);
379 options_table_populate_tree(OPTIONS_TABLE_WINDOW, global_w_options);
380
381 /* Override keys to vi if VISUAL or EDITOR are set. */
382 if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
383 if (strrchr(s, '/') != NULL)
384 s = strrchr(s, '/') + 1;
385 if (strstr(s, "vi") != NULL)
386 keys = MODEKEY_VI;
387 else
388 keys = MODEKEY_EMACS;
389 options_set_number(global_s_options, "status-keys", keys);
390 options_set_number(global_w_options, "mode-keys", keys);
391 }
392
393 /*
394 * If socket is specified on the command-line with -S or -L, it is
395 * used. Otherwise, $TMUX is checked and if that fails "default" is
396 * used.
397 */
398 if (path == NULL && label == NULL) {
399 s = getenv("TMUX");
400 if (s != NULL && *s != '\0' && *s != ',') {
401 path = xstrdup(s);
402 path[strcspn (path, ",")] = '\0';
403 }
404 }
405 if (path == NULL && (path = make_label(label)) == NULL) {
406 fprintf(stderr, "can't create socket: %s\n", strerror(errno));
407 exit(1);
408 }
409 socket_path = path;
410 free(label);
411
412 /* Pass control to the client. */
413 exit(client_main(event_init(), argc, argv, flags, shellcmd));
414 }
415