xref: /openbsd/usr.bin/tmux/tmux.c (revision ced21769)
1*ced21769Snicm /* $OpenBSD: tmux.c,v 1.172 2016/10/11 13:21:59 nicm Exp $ */
2311827fbSnicm 
3311827fbSnicm /*
498ca8272Snicm  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5311827fbSnicm  *
6311827fbSnicm  * Permission to use, copy, modify, and distribute this software for any
7311827fbSnicm  * purpose with or without fee is hereby granted, provided that the above
8311827fbSnicm  * copyright notice and this permission notice appear in all copies.
9311827fbSnicm  *
10311827fbSnicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11311827fbSnicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12311827fbSnicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13311827fbSnicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14311827fbSnicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15311827fbSnicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16311827fbSnicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17311827fbSnicm  */
18311827fbSnicm 
19311827fbSnicm #include <sys/types.h>
20311827fbSnicm #include <sys/stat.h>
21311827fbSnicm 
224e7a7adeSnicm #include <err.h>
23311827fbSnicm #include <errno.h>
247724d0b0Snicm #include <event.h>
253808d149Snicm #include <fcntl.h>
2626529d6fSnicm #include <getopt.h>
273b05404cSnicm #include <langinfo.h>
28622ef518Snicm #include <locale.h>
29311827fbSnicm #include <paths.h>
30311827fbSnicm #include <pwd.h>
31311827fbSnicm #include <stdlib.h>
32311827fbSnicm #include <string.h>
335b8ac713Snicm #include <time.h>
34311827fbSnicm #include <unistd.h>
35311827fbSnicm 
36311827fbSnicm #include "tmux.h"
37311827fbSnicm 
38d89252e5Snicm struct options	*global_options;	/* server options */
39d89252e5Snicm struct options	*global_s_options;	/* session options */
40d89252e5Snicm struct options	*global_w_options;	/* window options */
41fb46cb3dSnicm struct environ	*global_environ;
421443aefcSnicm struct hooks	*global_hooks;
43311827fbSnicm 
448c1ade38Snicm struct timeval	 start_time;
459c764ad4Snicm const char	*socket_path;
464ccba3b3Snicm 
479883b791Snicm static __dead void	 usage(void);
489c764ad4Snicm static char		*make_label(const char *);
497724d0b0Snicm 
50*ced21769Snicm static const char	*getshell(void);
51*ced21769Snicm static int		 checkshell(const char *);
52*ced21769Snicm 
539883b791Snicm static __dead void
54311827fbSnicm usage(void)
55311827fbSnicm {
566ca2ce32Ssobrado 	fprintf(stderr,
573da585bcSjmc 	    "usage: %s [-2Cluv] [-c shell-command] [-f file] [-L socket-name]\n"
5865b1f011Snicm 	    "            [-S socket-path] [command [flags]]\n",
59d3b99136Snicm 	    getprogname());
60311827fbSnicm 	exit(1);
61311827fbSnicm }
62311827fbSnicm 
63*ced21769Snicm static const char *
643a5ec08bSnicm getshell(void)
653a5ec08bSnicm {
663a5ec08bSnicm 	struct passwd	*pw;
673a5ec08bSnicm 	const char	*shell;
683a5ec08bSnicm 
693a5ec08bSnicm 	shell = getenv("SHELL");
703a5ec08bSnicm 	if (checkshell(shell))
713a5ec08bSnicm 		return (shell);
723a5ec08bSnicm 
733a5ec08bSnicm 	pw = getpwuid(getuid());
743a5ec08bSnicm 	if (pw != NULL && checkshell(pw->pw_shell))
753a5ec08bSnicm 		return (pw->pw_shell);
763a5ec08bSnicm 
773a5ec08bSnicm 	return (_PATH_BSHELL);
783a5ec08bSnicm }
793a5ec08bSnicm 
80*ced21769Snicm static int
813a5ec08bSnicm checkshell(const char *shell)
823a5ec08bSnicm {
83a89a0ceeSnicm 	if (shell == NULL || *shell == '\0' || *shell != '/')
84a89a0ceeSnicm 		return (0);
85a89a0ceeSnicm 	if (areshell(shell))
863a5ec08bSnicm 		return (0);
873a5ec08bSnicm 	if (access(shell, X_OK) != 0)
883a5ec08bSnicm 		return (0);
893a5ec08bSnicm 	return (1);
903a5ec08bSnicm }
913a5ec08bSnicm 
923a5ec08bSnicm int
933a5ec08bSnicm areshell(const char *shell)
943a5ec08bSnicm {
953a5ec08bSnicm 	const char	*progname, *ptr;
963a5ec08bSnicm 
973a5ec08bSnicm 	if ((ptr = strrchr(shell, '/')) != NULL)
983a5ec08bSnicm 		ptr++;
993a5ec08bSnicm 	else
1003a5ec08bSnicm 		ptr = shell;
101d3b99136Snicm 	progname = getprogname();
1023a5ec08bSnicm 	if (*progname == '-')
1033a5ec08bSnicm 		progname++;
1043a5ec08bSnicm 	if (strcmp(ptr, progname) == 0)
1053a5ec08bSnicm 		return (1);
1063a5ec08bSnicm 	return (0);
1073a5ec08bSnicm }
1083a5ec08bSnicm 
1099c764ad4Snicm static char *
1109c764ad4Snicm make_label(const char *label)
111311827fbSnicm {
1129c764ad4Snicm 	char		*base, resolved[PATH_MAX], *path, *s;
113311827fbSnicm 	struct stat	 sb;
114311827fbSnicm 	u_int		 uid;
1159c764ad4Snicm 	int		 saved_errno;
1169c764ad4Snicm 
1179c764ad4Snicm 	if (label == NULL)
1189c764ad4Snicm 		label = "default";
119311827fbSnicm 
120311827fbSnicm 	uid = getuid();
1219c764ad4Snicm 
12240b64c41Snicm 	if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0')
1239c764ad4Snicm 		xasprintf(&base, "%s/tmux-%u", s, uid);
12440b64c41Snicm 	else
1259c764ad4Snicm 		xasprintf(&base, "%s/tmux-%u", _PATH_TMP, uid);
126311827fbSnicm 
127311827fbSnicm 	if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST)
1289c764ad4Snicm 		goto fail;
129311827fbSnicm 
130311827fbSnicm 	if (lstat(base, &sb) != 0)
1319c764ad4Snicm 		goto fail;
132311827fbSnicm 	if (!S_ISDIR(sb.st_mode)) {
133311827fbSnicm 		errno = ENOTDIR;
1349c764ad4Snicm 		goto fail;
135311827fbSnicm 	}
1369945ad17Snicm 	if (sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0) {
137311827fbSnicm 		errno = EACCES;
1389c764ad4Snicm 		goto fail;
139311827fbSnicm 	}
140311827fbSnicm 
1419c764ad4Snicm 	if (realpath(base, resolved) == NULL)
1429c764ad4Snicm 		strlcpy(resolved, base, sizeof resolved);
1439c764ad4Snicm 	xasprintf(&path, "%s/%s", resolved, label);
144311827fbSnicm 	return (path);
1459c764ad4Snicm 
1469c764ad4Snicm fail:
1479c764ad4Snicm 	saved_errno = errno;
1489c764ad4Snicm 	free(base);
1499c764ad4Snicm 	errno = saved_errno;
1509c764ad4Snicm 	return (NULL);
151311827fbSnicm }
152311827fbSnicm 
153a0ee4254Snicm void
154a0ee4254Snicm setblocking(int fd, int state)
155a0ee4254Snicm {
156a0ee4254Snicm 	int mode;
157a0ee4254Snicm 
158a0ee4254Snicm 	if ((mode = fcntl(fd, F_GETFL)) != -1) {
159a0ee4254Snicm 		if (!state)
160a0ee4254Snicm 			mode |= O_NONBLOCK;
161a0ee4254Snicm 		else
162a0ee4254Snicm 			mode &= ~O_NONBLOCK;
163a0ee4254Snicm 		fcntl(fd, F_SETFL, mode);
164a0ee4254Snicm 	}
165a0ee4254Snicm }
166a0ee4254Snicm 
167179ef399Snicm const char *
168179ef399Snicm find_home(void)
169179ef399Snicm {
170179ef399Snicm 	struct passwd		*pw;
171f038d384Snicm 	static const char	*home;
172f038d384Snicm 
173f038d384Snicm 	if (home != NULL)
174f038d384Snicm 		return (home);
175179ef399Snicm 
176179ef399Snicm 	home = getenv("HOME");
177179ef399Snicm 	if (home == NULL || *home == '\0') {
178179ef399Snicm 		pw = getpwuid(getuid());
179179ef399Snicm 		if (pw != NULL)
180179ef399Snicm 			home = pw->pw_dir;
181179ef399Snicm 		else
182179ef399Snicm 			home = NULL;
183179ef399Snicm 	}
184179ef399Snicm 
18582bdfec0Snicm 	return (home);
186179ef399Snicm }
187179ef399Snicm 
188311827fbSnicm int
189311827fbSnicm main(int argc, char **argv)
190311827fbSnicm {
191e192934cSnicm 	char		*path, *label, **var, tmp[PATH_MAX], *shellcmd = NULL;
192f6fb1232Snicm 	const char	*s;
1934fa31997Snicm 	int		 opt, flags, keys;
194311827fbSnicm 
1953b05404cSnicm 	if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL) {
1963b05404cSnicm 		if (setlocale(LC_CTYPE, "") == NULL)
1973b05404cSnicm 			errx(1, "invalid LC_ALL, LC_CTYPE or LANG");
1983b05404cSnicm 		s = nl_langinfo(CODESET);
1995fcc80c9Snicm 		if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0)
2003b05404cSnicm 			errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s);
2013b05404cSnicm 	}
202f5015ed6Snicm 
20331a85510Snicm 	setlocale(LC_TIME, "");
2045396add0Snicm 	tzset();
205622ef518Snicm 
2066b014674Snicm 	if (**argv == '-')
2076b014674Snicm 		flags = CLIENT_LOGIN;
2086b014674Snicm 	else
2097f6133c3Snicm 		flags = 0;
2106b014674Snicm 
21165439d22Snicm 	label = path = NULL;
2120e3eb495Snicm 	while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUv")) != -1) {
213311827fbSnicm 		switch (opt) {
214311827fbSnicm 		case '2':
215cafa7ad7Snicm 			flags |= CLIENT_256COLOURS;
216311827fbSnicm 			break;
217ef8ef121Snicm 		case 'c':
218e192934cSnicm 			free(shellcmd);
219e192934cSnicm 			shellcmd = xstrdup(optarg);
220ef8ef121Snicm 			break;
22190896992Snicm 		case 'C':
222cafa7ad7Snicm 			if (flags & CLIENT_CONTROL)
223cafa7ad7Snicm 				flags |= CLIENT_CONTROLCONTROL;
22490896992Snicm 			else
225cafa7ad7Snicm 				flags |= CLIENT_CONTROL;
22690896992Snicm 			break;
227311827fbSnicm 		case 'f':
228f038d384Snicm 			set_cfg_file(optarg);
229311827fbSnicm 			break;
23065b1f011Snicm 		case 'l':
2316b014674Snicm 			flags |= CLIENT_LOGIN;
23265b1f011Snicm 			break;
233311827fbSnicm 		case 'L':
2347d053cf9Snicm 			free(label);
235311827fbSnicm 			label = xstrdup(optarg);
236311827fbSnicm 			break;
237481abfa9Snicm 		case 'q':
238481abfa9Snicm 			break;
239311827fbSnicm 		case 'S':
2407d053cf9Snicm 			free(path);
241311827fbSnicm 			path = xstrdup(optarg);
242311827fbSnicm 			break;
243311827fbSnicm 		case 'u':
244cafa7ad7Snicm 			flags |= CLIENT_UTF8;
245311827fbSnicm 			break;
246311827fbSnicm 		case 'v':
2470a607e68Snicm 			log_add_level();
248311827fbSnicm 			break;
249311827fbSnicm 		default:
250311827fbSnicm 			usage();
251311827fbSnicm 		}
252311827fbSnicm 	}
253311827fbSnicm 	argc -= optind;
254311827fbSnicm 	argv += optind;
255311827fbSnicm 
256e192934cSnicm 	if (shellcmd != NULL && argc != 0)
257ef8ef121Snicm 		usage();
258ef8ef121Snicm 
259cb19d99cSnicm 	if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd "
260cb19d99cSnicm 	    "recvfd proc exec tty ps", NULL) != 0)
2614e7a7adeSnicm 		err(1, "pledge");
2624e7a7adeSnicm 
263461e0674Snicm 	/*
264f6fb1232Snicm 	 * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8.
265f6fb1232Snicm 	 * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain
266f6fb1232Snicm 	 * UTF-8, it is a safe assumption that either they are using a UTF-8
267f6fb1232Snicm 	 * terminal, or if not they know that output from UTF-8-capable
268f6fb1232Snicm 	 * programs may be wrong.
269461e0674Snicm 	 */
270f6fb1232Snicm 	if (getenv("TMUX") != NULL)
271f6fb1232Snicm 		flags |= CLIENT_UTF8;
272f6fb1232Snicm 	else {
273f6fb1232Snicm 		s = getenv("LC_ALL");
274f6fb1232Snicm 		if (s == NULL || *s == '\0')
275f6fb1232Snicm 			s = getenv("LC_CTYPE");
276f6fb1232Snicm 		if (s == NULL || *s == '\0')
277461e0674Snicm 			s = getenv("LANG");
278f6fb1232Snicm 		if (s == NULL || *s == '\0')
279f6fb1232Snicm 			s = "";
280f6fb1232Snicm 		if (strcasestr(s, "UTF-8") != NULL ||
281f6fb1232Snicm 		    strcasestr(s, "UTF8") != NULL)
282cafa7ad7Snicm 			flags |= CLIENT_UTF8;
283461e0674Snicm 	}
284461e0674Snicm 
2851443aefcSnicm 	global_hooks = hooks_create(NULL);
2861443aefcSnicm 
287fb46cb3dSnicm 	global_environ = environ_create();
288bd0e97c4Snicm 	for (var = environ; *var != NULL; var++)
289fb46cb3dSnicm 		environ_put(global_environ, *var);
290e1803d63Snicm 	if (getcwd(tmp, sizeof tmp) != NULL)
291c4ce9d7aSnicm 		environ_set(global_environ, "PWD", "%s", tmp);
292bd0e97c4Snicm 
293d89252e5Snicm 	global_options = options_create(NULL);
2941a5f7318Snicm 	options_table_populate_tree(OPTIONS_TABLE_SERVER, global_options);
295f79ec119Snicm 
296d89252e5Snicm 	global_s_options = options_create(NULL);
2971a5f7318Snicm 	options_table_populate_tree(OPTIONS_TABLE_SESSION, global_s_options);
298d89252e5Snicm 	options_set_string(global_s_options, "default-shell", "%s", getshell());
299311827fbSnicm 
300d89252e5Snicm 	global_w_options = options_create(NULL);
3011a5f7318Snicm 	options_table_populate_tree(OPTIONS_TABLE_WINDOW, global_w_options);
3023affa6cbSnicm 
3033affa6cbSnicm 	/* Override keys to vi if VISUAL or EDITOR are set. */
304c9347692Snicm 	if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
305c9347692Snicm 		if (strrchr(s, '/') != NULL)
306c9347692Snicm 			s = strrchr(s, '/') + 1;
307c9347692Snicm 		if (strstr(s, "vi") != NULL)
308c9347692Snicm 			keys = MODEKEY_VI;
3093affa6cbSnicm 		else
3103affa6cbSnicm 			keys = MODEKEY_EMACS;
311d89252e5Snicm 		options_set_number(global_s_options, "status-keys", keys);
312d89252e5Snicm 		options_set_number(global_w_options, "mode-keys", keys);
313c9347692Snicm 	}
314c9347692Snicm 
3152fa50cfcSnicm 	/*
3169c764ad4Snicm 	 * If socket is specified on the command-line with -S or -L, it is
3179c764ad4Snicm 	 * used. Otherwise, $TMUX is checked and if that fails "default" is
3189c764ad4Snicm 	 * used.
3192fa50cfcSnicm 	 */
3209c764ad4Snicm 	if (path == NULL && label == NULL) {
3214fa31997Snicm 		s = getenv("TMUX");
3229c764ad4Snicm 		if (s != NULL && *s != '\0' && *s != ',') {
3234fa31997Snicm 			path = xstrdup(s);
3244fa31997Snicm 			path[strcspn (path, ",")] = '\0';
3254fa31997Snicm 		}
3262fa50cfcSnicm 	}
3279c764ad4Snicm 	if (path == NULL && (path = make_label(label)) == NULL) {
3289c764ad4Snicm 		fprintf(stderr, "can't create socket: %s\n", strerror(errno));
329311827fbSnicm 		exit(1);
330311827fbSnicm 	}
3319c764ad4Snicm 	socket_path = path;
3327d053cf9Snicm 	free(label);
3336f631021Snicm 
33465439d22Snicm 	/* Pass control to the client. */
335e192934cSnicm 	exit(client_main(event_init(), argc, argv, flags, shellcmd));
336fd234c13Snicm }
337