xref: /openbsd/usr.bin/tmux/tmux.c (revision 6f631021)
1*6f631021Snicm /* $OpenBSD: tmux.c,v 1.127 2014/01/09 14:05:55 nicm Exp $ */
2311827fbSnicm 
3311827fbSnicm /*
4311827fbSnicm  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
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 
22311827fbSnicm #include <errno.h>
237724d0b0Snicm #include <event.h>
243808d149Snicm #include <fcntl.h>
25622ef518Snicm #include <locale.h>
26311827fbSnicm #include <paths.h>
27311827fbSnicm #include <pwd.h>
28311827fbSnicm #include <stdlib.h>
29311827fbSnicm #include <string.h>
30311827fbSnicm #include <unistd.h>
31311827fbSnicm 
32311827fbSnicm #include "tmux.h"
33311827fbSnicm 
34311827fbSnicm #ifdef DEBUG
35bbc36c02Snicm extern char	*malloc_options;
36311827fbSnicm #endif
37311827fbSnicm 
38f79ec119Snicm struct options	 global_options;	/* server options */
39eaecedb2Snicm struct options	 global_s_options;	/* session options */
40eaecedb2Snicm struct options	 global_w_options;	/* window options */
416f7d62ebSnicm struct environ	 global_environ;
42311827fbSnicm 
434291359cSnicm struct event_base *ev_base;
444291359cSnicm 
4565439d22Snicm char		*cfg_file;
4665439d22Snicm char		*shell_cmd;
47311827fbSnicm int		 debug_level;
48311827fbSnicm time_t		 start_time;
4965439d22Snicm char		 socket_path[MAXPATHLEN];
5065b1f011Snicm int		 login_shell;
5165439d22Snicm char		*environ_path;
524ccba3b3Snicm 
53311827fbSnicm __dead void	 usage(void);
5465439d22Snicm char 		*makesocketpath(const char *);
557724d0b0Snicm 
56311827fbSnicm __dead void
57311827fbSnicm usage(void)
58311827fbSnicm {
596ca2ce32Ssobrado 	fprintf(stderr,
6004924df5Snicm 	    "usage: %s [-28lquv] [-c shell-command] [-f file] [-L socket-name]\n"
6165b1f011Snicm 	    "            [-S socket-path] [command [flags]]\n",
62311827fbSnicm 	    __progname);
63311827fbSnicm 	exit(1);
64311827fbSnicm }
65311827fbSnicm 
66311827fbSnicm void
67311827fbSnicm logfile(const char *name)
68311827fbSnicm {
69311827fbSnicm 	char	*path;
70311827fbSnicm 
71311827fbSnicm 	if (debug_level > 0) {
723a4fea3bSnicm 		xasprintf(&path, "tmux-%s-%ld.log", name, (long) getpid());
7336fa3172Snicm 		log_open(debug_level, path);
747d053cf9Snicm 		free(path);
75311827fbSnicm 	}
76311827fbSnicm }
77311827fbSnicm 
783a5ec08bSnicm const char *
793a5ec08bSnicm getshell(void)
803a5ec08bSnicm {
813a5ec08bSnicm 	struct passwd	*pw;
823a5ec08bSnicm 	const char	*shell;
833a5ec08bSnicm 
843a5ec08bSnicm 	shell = getenv("SHELL");
853a5ec08bSnicm 	if (checkshell(shell))
863a5ec08bSnicm 		return (shell);
873a5ec08bSnicm 
883a5ec08bSnicm 	pw = getpwuid(getuid());
893a5ec08bSnicm 	if (pw != NULL && checkshell(pw->pw_shell))
903a5ec08bSnicm 		return (pw->pw_shell);
913a5ec08bSnicm 
923a5ec08bSnicm 	return (_PATH_BSHELL);
933a5ec08bSnicm }
943a5ec08bSnicm 
953a5ec08bSnicm int
963a5ec08bSnicm checkshell(const char *shell)
973a5ec08bSnicm {
98a89a0ceeSnicm 	if (shell == NULL || *shell == '\0' || *shell != '/')
99a89a0ceeSnicm 		return (0);
100a89a0ceeSnicm 	if (areshell(shell))
1013a5ec08bSnicm 		return (0);
1023a5ec08bSnicm 	if (access(shell, X_OK) != 0)
1033a5ec08bSnicm 		return (0);
1043a5ec08bSnicm 	return (1);
1053a5ec08bSnicm }
1063a5ec08bSnicm 
1073a5ec08bSnicm int
1083a5ec08bSnicm areshell(const char *shell)
1093a5ec08bSnicm {
1103a5ec08bSnicm 	const char	*progname, *ptr;
1113a5ec08bSnicm 
1123a5ec08bSnicm 	if ((ptr = strrchr(shell, '/')) != NULL)
1133a5ec08bSnicm 		ptr++;
1143a5ec08bSnicm 	else
1153a5ec08bSnicm 		ptr = shell;
1163a5ec08bSnicm 	progname = __progname;
1173a5ec08bSnicm 	if (*progname == '-')
1183a5ec08bSnicm 		progname++;
1193a5ec08bSnicm 	if (strcmp(ptr, progname) == 0)
1203a5ec08bSnicm 		return (1);
1213a5ec08bSnicm 	return (0);
1223a5ec08bSnicm }
1233a5ec08bSnicm 
124311827fbSnicm char *
12565439d22Snicm makesocketpath(const char *label)
126311827fbSnicm {
12735194355Snicm 	char		base[MAXPATHLEN], realbase[MAXPATHLEN], *path, *s;
128311827fbSnicm 	struct stat	sb;
129311827fbSnicm 	u_int		uid;
130311827fbSnicm 
131311827fbSnicm 	uid = getuid();
13240b64c41Snicm 	if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0')
13340b64c41Snicm 		xsnprintf(base, sizeof base, "%s/", s);
13440b64c41Snicm 	else if ((s = getenv("TMPDIR")) != NULL && *s != '\0')
135e3b883c9Snicm 		xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid);
13640b64c41Snicm 	else
13740b64c41Snicm 		xsnprintf(base, sizeof base, "%s/tmux-%u", _PATH_TMP, uid);
138311827fbSnicm 
139311827fbSnicm 	if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST)
140311827fbSnicm 		return (NULL);
141311827fbSnicm 
142311827fbSnicm 	if (lstat(base, &sb) != 0)
143311827fbSnicm 		return (NULL);
144311827fbSnicm 	if (!S_ISDIR(sb.st_mode)) {
145311827fbSnicm 		errno = ENOTDIR;
146311827fbSnicm 		return (NULL);
147311827fbSnicm 	}
1482b80f9f7Snicm 	if (sb.st_uid != uid || (!S_ISDIR(sb.st_mode) &&
1492b80f9f7Snicm 		sb.st_mode & (S_IRWXG|S_IRWXO)) != 0) {
150311827fbSnicm 		errno = EACCES;
151311827fbSnicm 		return (NULL);
152311827fbSnicm 	}
153311827fbSnicm 
15435194355Snicm 	if (realpath(base, realbase) == NULL)
15535194355Snicm 		strlcpy(realbase, base, sizeof realbase);
15635194355Snicm 
15735194355Snicm 	xasprintf(&path, "%s/%s", realbase, label);
158311827fbSnicm 	return (path);
159311827fbSnicm }
160311827fbSnicm 
161a0ee4254Snicm void
162a0ee4254Snicm setblocking(int fd, int state)
163a0ee4254Snicm {
164a0ee4254Snicm 	int mode;
165a0ee4254Snicm 
166a0ee4254Snicm 	if ((mode = fcntl(fd, F_GETFL)) != -1) {
167a0ee4254Snicm 		if (!state)
168a0ee4254Snicm 			mode |= O_NONBLOCK;
169a0ee4254Snicm 		else
170a0ee4254Snicm 			mode &= ~O_NONBLOCK;
171a0ee4254Snicm 		fcntl(fd, F_SETFL, mode);
172a0ee4254Snicm 	}
173a0ee4254Snicm }
174a0ee4254Snicm 
1757724d0b0Snicm __dead void
1767724d0b0Snicm shell_exec(const char *shell, const char *shellcmd)
1777724d0b0Snicm {
1787724d0b0Snicm 	const char	*shellname, *ptr;
1797724d0b0Snicm 	char		*argv0;
1807724d0b0Snicm 
1817724d0b0Snicm 	ptr = strrchr(shell, '/');
1827724d0b0Snicm 	if (ptr != NULL && *(ptr + 1) != '\0')
1837724d0b0Snicm 		shellname = ptr + 1;
1847724d0b0Snicm 	else
1857724d0b0Snicm 		shellname = shell;
1867724d0b0Snicm 	if (login_shell)
1877724d0b0Snicm 		xasprintf(&argv0, "-%s", shellname);
1887724d0b0Snicm 	else
1897724d0b0Snicm 		xasprintf(&argv0, "%s", shellname);
1907724d0b0Snicm 	setenv("SHELL", shell, 1);
1917724d0b0Snicm 
192a0ee4254Snicm 	setblocking(STDIN_FILENO, 1);
193a0ee4254Snicm 	setblocking(STDOUT_FILENO, 1);
194a0ee4254Snicm 	setblocking(STDERR_FILENO, 1);
195498ee386Snicm 	closefrom(STDERR_FILENO + 1);
196498ee386Snicm 
1977724d0b0Snicm 	execl(shell, argv0, "-c", shellcmd, (char *) NULL);
1987724d0b0Snicm 	fatal("execl failed");
1997724d0b0Snicm }
2007724d0b0Snicm 
201311827fbSnicm int
202311827fbSnicm main(int argc, char **argv)
203311827fbSnicm {
204311827fbSnicm 	struct passwd	*pw;
205e1803d63Snicm 	char		*s, *path, *label, *home, **var, tmp[MAXPATHLEN];
206d87b01fbSnicm 	char		 in[256];
207d87b01fbSnicm 	long long	 pid;
208d87b01fbSnicm 	int	 	 opt, flags, quiet, keys, session;
209311827fbSnicm 
210bbc36c02Snicm #ifdef DEBUG
211bbc36c02Snicm 	malloc_options = (char *) "AFGJPX";
212bbc36c02Snicm #endif
213bbc36c02Snicm 
214622ef518Snicm 	setlocale(LC_TIME, "");
215622ef518Snicm 
216c9347692Snicm 	quiet = flags = 0;
21765439d22Snicm 	label = path = NULL;
21865b1f011Snicm 	login_shell = (**argv == '-');
2190e3eb495Snicm 	while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUv")) != -1) {
220311827fbSnicm 		switch (opt) {
221311827fbSnicm 		case '2':
222cafa7ad7Snicm 			flags |= CLIENT_256COLOURS;
223311827fbSnicm 			break;
224ef8ef121Snicm 		case 'c':
2257d053cf9Snicm 			free(shell_cmd);
22665439d22Snicm 			shell_cmd = xstrdup(optarg);
227ef8ef121Snicm 			break;
22890896992Snicm 		case 'C':
229cafa7ad7Snicm 			if (flags & CLIENT_CONTROL)
230cafa7ad7Snicm 				flags |= CLIENT_CONTROLCONTROL;
23190896992Snicm 			else
232cafa7ad7Snicm 				flags |= CLIENT_CONTROL;
23390896992Snicm 			break;
234311827fbSnicm 		case 'f':
2357d053cf9Snicm 			free(cfg_file);
236311827fbSnicm 			cfg_file = xstrdup(optarg);
237311827fbSnicm 			break;
23865b1f011Snicm 		case 'l':
23965b1f011Snicm 			login_shell = 1;
24065b1f011Snicm 			break;
241311827fbSnicm 		case 'L':
2427d053cf9Snicm 			free(label);
243311827fbSnicm 			label = xstrdup(optarg);
244311827fbSnicm 			break;
245481abfa9Snicm 		case 'q':
246f79ec119Snicm 			quiet = 1;
247481abfa9Snicm 			break;
248311827fbSnicm 		case 'S':
2497d053cf9Snicm 			free(path);
250311827fbSnicm 			path = xstrdup(optarg);
251311827fbSnicm 			break;
252311827fbSnicm 		case 'u':
253cafa7ad7Snicm 			flags |= CLIENT_UTF8;
254311827fbSnicm 			break;
255311827fbSnicm 		case 'v':
256311827fbSnicm 			debug_level++;
257311827fbSnicm 			break;
258311827fbSnicm 		default:
259311827fbSnicm 			usage();
260311827fbSnicm 		}
261311827fbSnicm 	}
262311827fbSnicm 	argc -= optind;
263311827fbSnicm 	argv += optind;
264311827fbSnicm 
26565439d22Snicm 	if (shell_cmd != NULL && argc != 0)
266ef8ef121Snicm 		usage();
267ef8ef121Snicm 
268cafa7ad7Snicm 	if (!(flags & CLIENT_UTF8)) {
269461e0674Snicm 		/*
270461e0674Snicm 		 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG
271461e0674Snicm 		 * exist (in that order) to contain UTF-8, it is a safe
272461e0674Snicm 		 * assumption that either they are using a UTF-8 terminal, or
273461e0674Snicm 		 * if not they know that output from UTF-8-capable programs may
274461e0674Snicm 		 * be wrong.
275461e0674Snicm 		 */
27654810b57Snicm 		if ((s = getenv("LC_ALL")) == NULL || *s == '\0') {
27754810b57Snicm 			if ((s = getenv("LC_CTYPE")) == NULL || *s == '\0')
278461e0674Snicm 				s = getenv("LANG");
279461e0674Snicm 		}
280a7a86d62Snicm 		if (s != NULL && (strcasestr(s, "UTF-8") != NULL ||
281a7a86d62Snicm 		    strcasestr(s, "UTF8") != NULL))
282cafa7ad7Snicm 			flags |= CLIENT_UTF8;
283461e0674Snicm 	}
284461e0674Snicm 
285bd0e97c4Snicm 	environ_init(&global_environ);
286bd0e97c4Snicm 	for (var = environ; *var != NULL; var++)
287bd0e97c4Snicm 		environ_put(&global_environ, *var);
288e1803d63Snicm 	if (getcwd(tmp, sizeof tmp) != NULL)
289e1803d63Snicm 		environ_set(&global_environ, "PWD", tmp);
290bd0e97c4Snicm 
291f79ec119Snicm 	options_init(&global_options, NULL);
2923affa6cbSnicm 	options_table_populate_tree(server_options_table, &global_options);
2933affa6cbSnicm 	options_set_number(&global_options, "quiet", quiet);
294f79ec119Snicm 
295eaecedb2Snicm 	options_init(&global_s_options, NULL);
2963affa6cbSnicm 	options_table_populate_tree(session_options_table, &global_s_options);
2973affa6cbSnicm 	options_set_string(&global_s_options, "default-shell", "%s", getshell());
298311827fbSnicm 
2993affa6cbSnicm 	options_init(&global_w_options, NULL);
3003affa6cbSnicm 	options_table_populate_tree(window_options_table, &global_w_options);
3013affa6cbSnicm 
3023affa6cbSnicm 	/* Enable UTF-8 if the first client is on UTF-8 terminal. */
303cafa7ad7Snicm 	if (flags & CLIENT_UTF8) {
3043affa6cbSnicm 		options_set_number(&global_s_options, "status-utf8", 1);
305699995ddSnicm 		options_set_number(&global_s_options, "mouse-utf8", 1);
3063affa6cbSnicm 		options_set_number(&global_w_options, "utf8", 1);
307bd0e97c4Snicm 	}
308bd0e97c4Snicm 
3093affa6cbSnicm 	/* Override keys to vi if VISUAL or EDITOR are set. */
310c9347692Snicm 	if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
311c9347692Snicm 		if (strrchr(s, '/') != NULL)
312c9347692Snicm 			s = strrchr(s, '/') + 1;
313c9347692Snicm 		if (strstr(s, "vi") != NULL)
314c9347692Snicm 			keys = MODEKEY_VI;
3153affa6cbSnicm 		else
3163affa6cbSnicm 			keys = MODEKEY_EMACS;
3173affa6cbSnicm 		options_set_number(&global_s_options, "status-keys", keys);
3183affa6cbSnicm 		options_set_number(&global_w_options, "mode-keys", keys);
319c9347692Snicm 	}
320c9347692Snicm 
32165439d22Snicm 	/* Locate the configuration file. */
322311827fbSnicm 	if (cfg_file == NULL) {
323311827fbSnicm 		home = getenv("HOME");
324311827fbSnicm 		if (home == NULL || *home == '\0') {
325311827fbSnicm 			pw = getpwuid(getuid());
326311827fbSnicm 			if (pw != NULL)
327311827fbSnicm 				home = pw->pw_dir;
328311827fbSnicm 		}
329ebf82b56Snicm 		xasprintf(&cfg_file, "%s/.tmux.conf", home);
3309a53e128Snicm 		if (access(cfg_file, R_OK) != 0 && errno == ENOENT) {
3317d053cf9Snicm 			free(cfg_file);
332311827fbSnicm 			cfg_file = NULL;
333311827fbSnicm 		}
334311827fbSnicm 	}
335311827fbSnicm 
336d87b01fbSnicm 	/* Get path from environment. */
337d87b01fbSnicm 	s = getenv("TMUX");
338d87b01fbSnicm 	if (s != NULL && sscanf(s, "%255[^,],%lld,%d", in, &pid, &session) == 3)
339d87b01fbSnicm 		environ_path = xstrdup(in);
340d87b01fbSnicm 
3412fa50cfcSnicm 	/*
34265439d22Snicm 	 * Figure out the socket path. If specified on the command-line with -S
34365439d22Snicm 	 * or -L, use it, otherwise try $TMUX or assume -L default.
3442fa50cfcSnicm 	 */
3452fa50cfcSnicm 	if (path == NULL) {
34665439d22Snicm 		/* If no -L, use the environment. */
3472fa50cfcSnicm 		if (label == NULL) {
34865439d22Snicm 			if (environ_path != NULL)
34965439d22Snicm 				path = xstrdup(environ_path);
35065439d22Snicm 			else
351311827fbSnicm 				label = xstrdup("default");
3522fa50cfcSnicm 		}
3532fa50cfcSnicm 
3542fa50cfcSnicm 		/* -L or default set. */
3552fa50cfcSnicm 		if (label != NULL) {
35665439d22Snicm 			if ((path = makesocketpath(label)) == NULL) {
3572b80f9f7Snicm 				fprintf(stderr, "can't create socket: %s\n",
3582b80f9f7Snicm 					strerror(errno));
359311827fbSnicm 				exit(1);
360311827fbSnicm 			}
3612fa50cfcSnicm 		}
3622fa50cfcSnicm 	}
3637d053cf9Snicm 	free(label);
364*6f631021Snicm 
365*6f631021Snicm 	if (strlcpy(socket_path, path, sizeof socket_path) >= sizeof socket_path) {
366*6f631021Snicm 		fprintf(stderr, "socket path too long: %s\n", path);
367*6f631021Snicm 		exit(1);
368*6f631021Snicm 	}
3697d053cf9Snicm 	free(path);
370311827fbSnicm 
37165439d22Snicm 	/* Set process title. */
37265439d22Snicm 	setproctitle("%s (%s)", __progname, socket_path);
373311827fbSnicm 
37465439d22Snicm 	/* Pass control to the client. */
37565439d22Snicm 	ev_base = event_init();
37665439d22Snicm 	exit(client_main(argc, argv, flags));
377fd234c13Snicm }
378