xref: /openbsd/usr.bin/tmux/tmux.c (revision 7d053cf9)
1*7d053cf9Snicm /* $OpenBSD: tmux.c,v 1.112 2012/07/10 11:53:01 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>
25311827fbSnicm #include <paths.h>
26311827fbSnicm #include <pwd.h>
27311827fbSnicm #include <stdlib.h>
28311827fbSnicm #include <string.h>
29311827fbSnicm #include <unistd.h>
30311827fbSnicm 
31311827fbSnicm #include "tmux.h"
32311827fbSnicm 
33311827fbSnicm #ifdef DEBUG
34bbc36c02Snicm extern char	*malloc_options;
35311827fbSnicm #endif
36311827fbSnicm 
37f79ec119Snicm struct options	 global_options;	/* server options */
38eaecedb2Snicm struct options	 global_s_options;	/* session options */
39eaecedb2Snicm struct options	 global_w_options;	/* window options */
406f7d62ebSnicm struct environ	 global_environ;
41311827fbSnicm 
424291359cSnicm struct event_base *ev_base;
434291359cSnicm 
4465439d22Snicm char		*cfg_file;
4565439d22Snicm char		*shell_cmd;
46311827fbSnicm int		 debug_level;
47311827fbSnicm time_t		 start_time;
4865439d22Snicm char		 socket_path[MAXPATHLEN];
4965b1f011Snicm int		 login_shell;
5065439d22Snicm char		*environ_path;
5107bd7befSnicm pid_t		 environ_pid = -1;
5207bd7befSnicm int		 environ_idx = -1;
534ccba3b3Snicm 
54311827fbSnicm __dead void	 usage(void);
5565439d22Snicm void	 	 parseenvironment(void);
5665439d22Snicm char 		*makesocketpath(const char *);
577724d0b0Snicm 
58311827fbSnicm __dead void
59311827fbSnicm usage(void)
60311827fbSnicm {
616ca2ce32Ssobrado 	fprintf(stderr,
6204924df5Snicm 	    "usage: %s [-28lquv] [-c shell-command] [-f file] [-L socket-name]\n"
6365b1f011Snicm 	    "            [-S socket-path] [command [flags]]\n",
64311827fbSnicm 	    __progname);
65311827fbSnicm 	exit(1);
66311827fbSnicm }
67311827fbSnicm 
68311827fbSnicm void
69311827fbSnicm logfile(const char *name)
70311827fbSnicm {
71311827fbSnicm 	char	*path;
72311827fbSnicm 
73311827fbSnicm 	if (debug_level > 0) {
743a4fea3bSnicm 		xasprintf(&path, "tmux-%s-%ld.log", name, (long) getpid());
7536fa3172Snicm 		log_open(debug_level, path);
76*7d053cf9Snicm 		free(path);
77311827fbSnicm 	}
78311827fbSnicm }
79311827fbSnicm 
803a5ec08bSnicm const char *
813a5ec08bSnicm getshell(void)
823a5ec08bSnicm {
833a5ec08bSnicm 	struct passwd	*pw;
843a5ec08bSnicm 	const char	*shell;
853a5ec08bSnicm 
863a5ec08bSnicm 	shell = getenv("SHELL");
873a5ec08bSnicm 	if (checkshell(shell))
883a5ec08bSnicm 		return (shell);
893a5ec08bSnicm 
903a5ec08bSnicm 	pw = getpwuid(getuid());
913a5ec08bSnicm 	if (pw != NULL && checkshell(pw->pw_shell))
923a5ec08bSnicm 		return (pw->pw_shell);
933a5ec08bSnicm 
943a5ec08bSnicm 	return (_PATH_BSHELL);
953a5ec08bSnicm }
963a5ec08bSnicm 
973a5ec08bSnicm int
983a5ec08bSnicm checkshell(const char *shell)
993a5ec08bSnicm {
100a89a0ceeSnicm 	if (shell == NULL || *shell == '\0' || *shell != '/')
101a89a0ceeSnicm 		return (0);
102a89a0ceeSnicm 	if (areshell(shell))
1033a5ec08bSnicm 		return (0);
1043a5ec08bSnicm 	if (access(shell, X_OK) != 0)
1053a5ec08bSnicm 		return (0);
1063a5ec08bSnicm 	return (1);
1073a5ec08bSnicm }
1083a5ec08bSnicm 
1093a5ec08bSnicm int
1103a5ec08bSnicm areshell(const char *shell)
1113a5ec08bSnicm {
1123a5ec08bSnicm 	const char	*progname, *ptr;
1133a5ec08bSnicm 
1143a5ec08bSnicm 	if ((ptr = strrchr(shell, '/')) != NULL)
1153a5ec08bSnicm 		ptr++;
1163a5ec08bSnicm 	else
1173a5ec08bSnicm 		ptr = shell;
1183a5ec08bSnicm 	progname = __progname;
1193a5ec08bSnicm 	if (*progname == '-')
1203a5ec08bSnicm 		progname++;
1213a5ec08bSnicm 	if (strcmp(ptr, progname) == 0)
1223a5ec08bSnicm 		return (1);
1233a5ec08bSnicm 	return (0);
1243a5ec08bSnicm }
1253a5ec08bSnicm 
12635a092d6Snicm const char*
12735a092d6Snicm get_full_path(const char *wd, const char *path)
12835a092d6Snicm {
12935a092d6Snicm 	static char	newpath[MAXPATHLEN];
13035a092d6Snicm 	char		oldpath[MAXPATHLEN];
13135a092d6Snicm 
13235a092d6Snicm 	if (getcwd(oldpath, sizeof oldpath) == NULL)
13335a092d6Snicm 		return (NULL);
13435a092d6Snicm 	if (chdir(wd) != 0)
13535a092d6Snicm 		return (NULL);
13635a092d6Snicm 	if (realpath(path, newpath) != 0)
13735a092d6Snicm 		return (NULL);
13835a092d6Snicm 	chdir(oldpath);
13935a092d6Snicm 	return (newpath);
14035a092d6Snicm }
14135a092d6Snicm 
142a2f71d82Snicm void
14365439d22Snicm parseenvironment(void)
144a2f71d82Snicm {
14507bd7befSnicm 	char	*env, path[256];
14607bd7befSnicm 	long	 pid;
14707bd7befSnicm 	int	 idx;
148a2f71d82Snicm 
149a2f71d82Snicm 	if ((env = getenv("TMUX")) == NULL)
150a2f71d82Snicm 		return;
151a2f71d82Snicm 
152019abd7fSnicm 	if (sscanf(env, "%255[^,],%ld,%d", path, &pid, &idx) != 3)
153a2f71d82Snicm 		return;
15407bd7befSnicm 	environ_path = xstrdup(path);
15507bd7befSnicm 	environ_pid = pid;
15607bd7befSnicm 	environ_idx = idx;
157a2f71d82Snicm }
158a2f71d82Snicm 
159311827fbSnicm char *
16065439d22Snicm makesocketpath(const char *label)
161311827fbSnicm {
162e3b883c9Snicm 	char		base[MAXPATHLEN], *path, *s;
163311827fbSnicm 	struct stat	sb;
164311827fbSnicm 	u_int		uid;
165311827fbSnicm 
166311827fbSnicm 	uid = getuid();
167e3b883c9Snicm 	if ((s = getenv("TMPDIR")) == NULL || *s == '\0')
168e3b883c9Snicm 		xsnprintf(base, sizeof base, "%s/tmux-%u", _PATH_TMP, uid);
169e3b883c9Snicm 	else
170e3b883c9Snicm 		xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid);
171311827fbSnicm 
172311827fbSnicm 	if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST)
173311827fbSnicm 		return (NULL);
174311827fbSnicm 
175311827fbSnicm 	if (lstat(base, &sb) != 0)
176311827fbSnicm 		return (NULL);
177311827fbSnicm 	if (!S_ISDIR(sb.st_mode)) {
178311827fbSnicm 		errno = ENOTDIR;
179311827fbSnicm 		return (NULL);
180311827fbSnicm 	}
181311827fbSnicm 	if (sb.st_uid != uid || (sb.st_mode & (S_IRWXG|S_IRWXO)) != 0) {
182311827fbSnicm 		errno = EACCES;
183311827fbSnicm 		return (NULL);
184311827fbSnicm 	}
185311827fbSnicm 
186311827fbSnicm 	xasprintf(&path, "%s/%s", base, label);
187311827fbSnicm 	return (path);
188311827fbSnicm }
189311827fbSnicm 
190a0ee4254Snicm void
191a0ee4254Snicm setblocking(int fd, int state)
192a0ee4254Snicm {
193a0ee4254Snicm 	int mode;
194a0ee4254Snicm 
195a0ee4254Snicm 	if ((mode = fcntl(fd, F_GETFL)) != -1) {
196a0ee4254Snicm 		if (!state)
197a0ee4254Snicm 			mode |= O_NONBLOCK;
198a0ee4254Snicm 		else
199a0ee4254Snicm 			mode &= ~O_NONBLOCK;
200a0ee4254Snicm 		fcntl(fd, F_SETFL, mode);
201a0ee4254Snicm 	}
202a0ee4254Snicm }
203a0ee4254Snicm 
2047724d0b0Snicm __dead void
2057724d0b0Snicm shell_exec(const char *shell, const char *shellcmd)
2067724d0b0Snicm {
2077724d0b0Snicm 	const char	*shellname, *ptr;
2087724d0b0Snicm 	char		*argv0;
2097724d0b0Snicm 
2107724d0b0Snicm 	ptr = strrchr(shell, '/');
2117724d0b0Snicm 	if (ptr != NULL && *(ptr + 1) != '\0')
2127724d0b0Snicm 		shellname = ptr + 1;
2137724d0b0Snicm 	else
2147724d0b0Snicm 		shellname = shell;
2157724d0b0Snicm 	if (login_shell)
2167724d0b0Snicm 		xasprintf(&argv0, "-%s", shellname);
2177724d0b0Snicm 	else
2187724d0b0Snicm 		xasprintf(&argv0, "%s", shellname);
2197724d0b0Snicm 	setenv("SHELL", shell, 1);
2207724d0b0Snicm 
221a0ee4254Snicm 	setblocking(STDIN_FILENO, 1);
222a0ee4254Snicm 	setblocking(STDOUT_FILENO, 1);
223a0ee4254Snicm 	setblocking(STDERR_FILENO, 1);
224498ee386Snicm 	closefrom(STDERR_FILENO + 1);
225498ee386Snicm 
2267724d0b0Snicm 	execl(shell, argv0, "-c", shellcmd, (char *) NULL);
2277724d0b0Snicm 	fatal("execl failed");
2287724d0b0Snicm }
2297724d0b0Snicm 
230311827fbSnicm int
231311827fbSnicm main(int argc, char **argv)
232311827fbSnicm {
233311827fbSnicm 	struct passwd	*pw;
23465439d22Snicm 	char		*s, *path, *label, *home, **var;
235c9347692Snicm 	int	 	 opt, flags, quiet, keys;
236311827fbSnicm 
237bbc36c02Snicm #ifdef DEBUG
238bbc36c02Snicm 	malloc_options = (char *) "AFGJPX";
239bbc36c02Snicm #endif
240bbc36c02Snicm 
241c9347692Snicm 	quiet = flags = 0;
24265439d22Snicm 	label = path = NULL;
24365b1f011Snicm 	login_shell = (**argv == '-');
24490896992Snicm 	while ((opt = getopt(argc, argv, "28c:Cdf:lL:qS:uUv")) != -1) {
245311827fbSnicm 		switch (opt) {
246311827fbSnicm 		case '2':
247311827fbSnicm 			flags |= IDENTIFY_256COLOURS;
248311827fbSnicm 			flags &= ~IDENTIFY_88COLOURS;
249311827fbSnicm 			break;
250311827fbSnicm 		case '8':
251311827fbSnicm 			flags |= IDENTIFY_88COLOURS;
252311827fbSnicm 			flags &= ~IDENTIFY_256COLOURS;
253311827fbSnicm 			break;
254ef8ef121Snicm 		case 'c':
255*7d053cf9Snicm 			free(shell_cmd);
25665439d22Snicm 			shell_cmd = xstrdup(optarg);
257ef8ef121Snicm 			break;
25890896992Snicm 		case 'C':
25990896992Snicm 			if (flags & IDENTIFY_CONTROL)
26090896992Snicm 				flags |= IDENTIFY_TERMIOS;
26190896992Snicm 			else
26290896992Snicm 				flags |= IDENTIFY_CONTROL;
26390896992Snicm 			break;
264311827fbSnicm 		case 'f':
265*7d053cf9Snicm 			free(cfg_file);
266311827fbSnicm 			cfg_file = xstrdup(optarg);
267311827fbSnicm 			break;
26865b1f011Snicm 		case 'l':
26965b1f011Snicm 			login_shell = 1;
27065b1f011Snicm 			break;
271311827fbSnicm 		case 'L':
272*7d053cf9Snicm 			free(label);
273311827fbSnicm 			label = xstrdup(optarg);
274311827fbSnicm 			break;
275481abfa9Snicm 		case 'q':
276f79ec119Snicm 			quiet = 1;
277481abfa9Snicm 			break;
278311827fbSnicm 		case 'S':
279*7d053cf9Snicm 			free(path);
280311827fbSnicm 			path = xstrdup(optarg);
281311827fbSnicm 			break;
282311827fbSnicm 		case 'u':
283311827fbSnicm 			flags |= IDENTIFY_UTF8;
284311827fbSnicm 			break;
285311827fbSnicm 		case 'v':
286311827fbSnicm 			debug_level++;
287311827fbSnicm 			break;
288311827fbSnicm 		default:
289311827fbSnicm 			usage();
290311827fbSnicm 		}
291311827fbSnicm 	}
292311827fbSnicm 	argc -= optind;
293311827fbSnicm 	argv += optind;
294311827fbSnicm 
29565439d22Snicm 	if (shell_cmd != NULL && argc != 0)
296ef8ef121Snicm 		usage();
297ef8ef121Snicm 
298461e0674Snicm 	if (!(flags & IDENTIFY_UTF8)) {
299461e0674Snicm 		/*
300461e0674Snicm 		 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG
301461e0674Snicm 		 * exist (in that order) to contain UTF-8, it is a safe
302461e0674Snicm 		 * assumption that either they are using a UTF-8 terminal, or
303461e0674Snicm 		 * if not they know that output from UTF-8-capable programs may
304461e0674Snicm 		 * be wrong.
305461e0674Snicm 		 */
30654810b57Snicm 		if ((s = getenv("LC_ALL")) == NULL || *s == '\0') {
30754810b57Snicm 			if ((s = getenv("LC_CTYPE")) == NULL || *s == '\0')
308461e0674Snicm 				s = getenv("LANG");
309461e0674Snicm 		}
310a7a86d62Snicm 		if (s != NULL && (strcasestr(s, "UTF-8") != NULL ||
311a7a86d62Snicm 		    strcasestr(s, "UTF8") != NULL))
312461e0674Snicm 			flags |= IDENTIFY_UTF8;
313461e0674Snicm 	}
314461e0674Snicm 
315bd0e97c4Snicm 	environ_init(&global_environ);
316bd0e97c4Snicm 	for (var = environ; *var != NULL; var++)
317bd0e97c4Snicm 		environ_put(&global_environ, *var);
318bd0e97c4Snicm 
319f79ec119Snicm 	options_init(&global_options, NULL);
3203affa6cbSnicm 	options_table_populate_tree(server_options_table, &global_options);
3213affa6cbSnicm 	options_set_number(&global_options, "quiet", quiet);
322f79ec119Snicm 
323eaecedb2Snicm 	options_init(&global_s_options, NULL);
3243affa6cbSnicm 	options_table_populate_tree(session_options_table, &global_s_options);
3253affa6cbSnicm 	options_set_string(&global_s_options, "default-shell", "%s", getshell());
326311827fbSnicm 
3273affa6cbSnicm 	options_init(&global_w_options, NULL);
3283affa6cbSnicm 	options_table_populate_tree(window_options_table, &global_w_options);
3293affa6cbSnicm 
3303affa6cbSnicm 	/* Enable UTF-8 if the first client is on UTF-8 terminal. */
331bd0e97c4Snicm 	if (flags & IDENTIFY_UTF8) {
3323affa6cbSnicm 		options_set_number(&global_s_options, "status-utf8", 1);
333699995ddSnicm 		options_set_number(&global_s_options, "mouse-utf8", 1);
3343affa6cbSnicm 		options_set_number(&global_w_options, "utf8", 1);
335bd0e97c4Snicm 	}
336bd0e97c4Snicm 
3373affa6cbSnicm 	/* Override keys to vi if VISUAL or EDITOR are set. */
338c9347692Snicm 	if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
339c9347692Snicm 		if (strrchr(s, '/') != NULL)
340c9347692Snicm 			s = strrchr(s, '/') + 1;
341c9347692Snicm 		if (strstr(s, "vi") != NULL)
342c9347692Snicm 			keys = MODEKEY_VI;
3433affa6cbSnicm 		else
3443affa6cbSnicm 			keys = MODEKEY_EMACS;
3453affa6cbSnicm 		options_set_number(&global_s_options, "status-keys", keys);
3463affa6cbSnicm 		options_set_number(&global_w_options, "mode-keys", keys);
347c9347692Snicm 	}
348c9347692Snicm 
34965439d22Snicm 	/* Locate the configuration file. */
350311827fbSnicm 	if (cfg_file == NULL) {
351311827fbSnicm 		home = getenv("HOME");
352311827fbSnicm 		if (home == NULL || *home == '\0') {
353311827fbSnicm 			pw = getpwuid(getuid());
354311827fbSnicm 			if (pw != NULL)
355311827fbSnicm 				home = pw->pw_dir;
356311827fbSnicm 		}
357311827fbSnicm 		xasprintf(&cfg_file, "%s/%s", home, DEFAULT_CFG);
3589a53e128Snicm 		if (access(cfg_file, R_OK) != 0 && errno == ENOENT) {
359*7d053cf9Snicm 			free(cfg_file);
360311827fbSnicm 			cfg_file = NULL;
361311827fbSnicm 		}
362311827fbSnicm 	}
363311827fbSnicm 
3642fa50cfcSnicm 	/*
36565439d22Snicm 	 * Figure out the socket path. If specified on the command-line with -S
36665439d22Snicm 	 * or -L, use it, otherwise try $TMUX or assume -L default.
3672fa50cfcSnicm 	 */
36865439d22Snicm 	parseenvironment();
3692fa50cfcSnicm 	if (path == NULL) {
37065439d22Snicm 		/* If no -L, use the environment. */
3712fa50cfcSnicm 		if (label == NULL) {
37265439d22Snicm 			if (environ_path != NULL)
37365439d22Snicm 				path = xstrdup(environ_path);
37465439d22Snicm 			else
375311827fbSnicm 				label = xstrdup("default");
3762fa50cfcSnicm 		}
3772fa50cfcSnicm 
3782fa50cfcSnicm 		/* -L or default set. */
3792fa50cfcSnicm 		if (label != NULL) {
38065439d22Snicm 			if ((path = makesocketpath(label)) == NULL) {
38136fa3172Snicm 				fprintf(stderr, "can't create socket\n");
382311827fbSnicm 				exit(1);
383311827fbSnicm 			}
3842fa50cfcSnicm 		}
3852fa50cfcSnicm 	}
386*7d053cf9Snicm 	free(label);
38765439d22Snicm 	if (realpath(path, socket_path) == NULL)
38865439d22Snicm 		strlcpy(socket_path, path, sizeof socket_path);
389*7d053cf9Snicm 	free(path);
390311827fbSnicm 
39165439d22Snicm 	/* Set process title. */
39265439d22Snicm 	setproctitle("%s (%s)", __progname, socket_path);
393311827fbSnicm 
39465439d22Snicm 	/* Pass control to the client. */
39565439d22Snicm 	ev_base = event_init();
39665439d22Snicm 	exit(client_main(argc, argv, flags));
397fd234c13Snicm }
398