xref: /openbsd/usr.bin/tmux/tmux.c (revision 2b80f9f7)
1*2b80f9f7Snicm /* $OpenBSD: tmux.c,v 1.123 2013/10/10 12:03:22 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;
5207bd7befSnicm pid_t		 environ_pid = -1;
530a539d5dSnicm int		 environ_session_id = -1;
544ccba3b3Snicm 
55311827fbSnicm __dead void	 usage(void);
5665439d22Snicm void	 	 parseenvironment(void);
5765439d22Snicm char 		*makesocketpath(const char *);
587724d0b0Snicm 
59311827fbSnicm __dead void
60311827fbSnicm usage(void)
61311827fbSnicm {
626ca2ce32Ssobrado 	fprintf(stderr,
6304924df5Snicm 	    "usage: %s [-28lquv] [-c shell-command] [-f file] [-L socket-name]\n"
6465b1f011Snicm 	    "            [-S socket-path] [command [flags]]\n",
65311827fbSnicm 	    __progname);
66311827fbSnicm 	exit(1);
67311827fbSnicm }
68311827fbSnicm 
69311827fbSnicm void
70311827fbSnicm logfile(const char *name)
71311827fbSnicm {
72311827fbSnicm 	char	*path;
73311827fbSnicm 
74311827fbSnicm 	if (debug_level > 0) {
753a4fea3bSnicm 		xasprintf(&path, "tmux-%s-%ld.log", name, (long) getpid());
7636fa3172Snicm 		log_open(debug_level, path);
777d053cf9Snicm 		free(path);
78311827fbSnicm 	}
79311827fbSnicm }
80311827fbSnicm 
813a5ec08bSnicm const char *
823a5ec08bSnicm getshell(void)
833a5ec08bSnicm {
843a5ec08bSnicm 	struct passwd	*pw;
853a5ec08bSnicm 	const char	*shell;
863a5ec08bSnicm 
873a5ec08bSnicm 	shell = getenv("SHELL");
883a5ec08bSnicm 	if (checkshell(shell))
893a5ec08bSnicm 		return (shell);
903a5ec08bSnicm 
913a5ec08bSnicm 	pw = getpwuid(getuid());
923a5ec08bSnicm 	if (pw != NULL && checkshell(pw->pw_shell))
933a5ec08bSnicm 		return (pw->pw_shell);
943a5ec08bSnicm 
953a5ec08bSnicm 	return (_PATH_BSHELL);
963a5ec08bSnicm }
973a5ec08bSnicm 
983a5ec08bSnicm int
993a5ec08bSnicm checkshell(const char *shell)
1003a5ec08bSnicm {
101a89a0ceeSnicm 	if (shell == NULL || *shell == '\0' || *shell != '/')
102a89a0ceeSnicm 		return (0);
103a89a0ceeSnicm 	if (areshell(shell))
1043a5ec08bSnicm 		return (0);
1053a5ec08bSnicm 	if (access(shell, X_OK) != 0)
1063a5ec08bSnicm 		return (0);
1073a5ec08bSnicm 	return (1);
1083a5ec08bSnicm }
1093a5ec08bSnicm 
1103a5ec08bSnicm int
1113a5ec08bSnicm areshell(const char *shell)
1123a5ec08bSnicm {
1133a5ec08bSnicm 	const char	*progname, *ptr;
1143a5ec08bSnicm 
1153a5ec08bSnicm 	if ((ptr = strrchr(shell, '/')) != NULL)
1163a5ec08bSnicm 		ptr++;
1173a5ec08bSnicm 	else
1183a5ec08bSnicm 		ptr = shell;
1193a5ec08bSnicm 	progname = __progname;
1203a5ec08bSnicm 	if (*progname == '-')
1213a5ec08bSnicm 		progname++;
1223a5ec08bSnicm 	if (strcmp(ptr, progname) == 0)
1233a5ec08bSnicm 		return (1);
1243a5ec08bSnicm 	return (0);
1253a5ec08bSnicm }
1263a5ec08bSnicm 
12735a092d6Snicm const char*
12835a092d6Snicm get_full_path(const char *wd, const char *path)
12935a092d6Snicm {
130c4a631faSnicm 	int		 fd;
13135a092d6Snicm 	static char	 newpath[MAXPATHLEN];
13241c58c32Snicm 	const char	*retval;
13335a092d6Snicm 
134c4a631faSnicm 	fd = open(".", O_RDONLY);
135c4a631faSnicm 	if (fd == -1)
13635a092d6Snicm 		return (NULL);
137c4a631faSnicm 
13841c58c32Snicm 	retval = NULL;
13941c58c32Snicm 	if (chdir(wd) == 0) {
14041c58c32Snicm 		if (realpath(path, newpath) == 0)
14141c58c32Snicm 			retval = newpath;
14241c58c32Snicm 	}
143c4a631faSnicm 
144c4a631faSnicm 	if (fchdir(fd) != 0)
145c4a631faSnicm 		chdir("/");
146c4a631faSnicm 	close(fd);
147c4a631faSnicm 
14841c58c32Snicm 	return (retval);
14935a092d6Snicm }
15035a092d6Snicm 
151a2f71d82Snicm void
15265439d22Snicm parseenvironment(void)
153a2f71d82Snicm {
15407bd7befSnicm 	char	*env, path[256];
15507bd7befSnicm 	long	 pid;
1560a539d5dSnicm 	int	 id;
157a2f71d82Snicm 
158a2f71d82Snicm 	if ((env = getenv("TMUX")) == NULL)
159a2f71d82Snicm 		return;
160a2f71d82Snicm 
1610a539d5dSnicm 	if (sscanf(env, "%255[^,],%ld,%d", path, &pid, &id) != 3)
162a2f71d82Snicm 		return;
16307bd7befSnicm 	environ_path = xstrdup(path);
16407bd7befSnicm 	environ_pid = pid;
1650a539d5dSnicm 	environ_session_id = id;
166a2f71d82Snicm }
167a2f71d82Snicm 
168311827fbSnicm char *
16965439d22Snicm makesocketpath(const char *label)
170311827fbSnicm {
17135194355Snicm 	char		base[MAXPATHLEN], realbase[MAXPATHLEN], *path, *s;
172311827fbSnicm 	struct stat	sb;
173311827fbSnicm 	u_int		uid;
174311827fbSnicm 
175311827fbSnicm 	uid = getuid();
17640b64c41Snicm 	if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0')
17740b64c41Snicm 		xsnprintf(base, sizeof base, "%s/", s);
17840b64c41Snicm 	else if ((s = getenv("TMPDIR")) != NULL && *s != '\0')
179e3b883c9Snicm 		xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid);
18040b64c41Snicm 	else
18140b64c41Snicm 		xsnprintf(base, sizeof base, "%s/tmux-%u", _PATH_TMP, uid);
182311827fbSnicm 
183311827fbSnicm 	if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST)
184311827fbSnicm 		return (NULL);
185311827fbSnicm 
186311827fbSnicm 	if (lstat(base, &sb) != 0)
187311827fbSnicm 		return (NULL);
188311827fbSnicm 	if (!S_ISDIR(sb.st_mode)) {
189311827fbSnicm 		errno = ENOTDIR;
190311827fbSnicm 		return (NULL);
191311827fbSnicm 	}
192*2b80f9f7Snicm 	if (sb.st_uid != uid || (!S_ISDIR(sb.st_mode) &&
193*2b80f9f7Snicm 		sb.st_mode & (S_IRWXG|S_IRWXO)) != 0) {
194311827fbSnicm 		errno = EACCES;
195311827fbSnicm 		return (NULL);
196311827fbSnicm 	}
197311827fbSnicm 
19835194355Snicm 	if (realpath(base, realbase) == NULL)
19935194355Snicm 		strlcpy(realbase, base, sizeof realbase);
20035194355Snicm 
20135194355Snicm 	xasprintf(&path, "%s/%s", realbase, label);
202311827fbSnicm 	return (path);
203311827fbSnicm }
204311827fbSnicm 
205a0ee4254Snicm void
206a0ee4254Snicm setblocking(int fd, int state)
207a0ee4254Snicm {
208a0ee4254Snicm 	int mode;
209a0ee4254Snicm 
210a0ee4254Snicm 	if ((mode = fcntl(fd, F_GETFL)) != -1) {
211a0ee4254Snicm 		if (!state)
212a0ee4254Snicm 			mode |= O_NONBLOCK;
213a0ee4254Snicm 		else
214a0ee4254Snicm 			mode &= ~O_NONBLOCK;
215a0ee4254Snicm 		fcntl(fd, F_SETFL, mode);
216a0ee4254Snicm 	}
217a0ee4254Snicm }
218a0ee4254Snicm 
2197724d0b0Snicm __dead void
2207724d0b0Snicm shell_exec(const char *shell, const char *shellcmd)
2217724d0b0Snicm {
2227724d0b0Snicm 	const char	*shellname, *ptr;
2237724d0b0Snicm 	char		*argv0;
2247724d0b0Snicm 
2257724d0b0Snicm 	ptr = strrchr(shell, '/');
2267724d0b0Snicm 	if (ptr != NULL && *(ptr + 1) != '\0')
2277724d0b0Snicm 		shellname = ptr + 1;
2287724d0b0Snicm 	else
2297724d0b0Snicm 		shellname = shell;
2307724d0b0Snicm 	if (login_shell)
2317724d0b0Snicm 		xasprintf(&argv0, "-%s", shellname);
2327724d0b0Snicm 	else
2337724d0b0Snicm 		xasprintf(&argv0, "%s", shellname);
2347724d0b0Snicm 	setenv("SHELL", shell, 1);
2357724d0b0Snicm 
236a0ee4254Snicm 	setblocking(STDIN_FILENO, 1);
237a0ee4254Snicm 	setblocking(STDOUT_FILENO, 1);
238a0ee4254Snicm 	setblocking(STDERR_FILENO, 1);
239498ee386Snicm 	closefrom(STDERR_FILENO + 1);
240498ee386Snicm 
2417724d0b0Snicm 	execl(shell, argv0, "-c", shellcmd, (char *) NULL);
2427724d0b0Snicm 	fatal("execl failed");
2437724d0b0Snicm }
2447724d0b0Snicm 
245311827fbSnicm int
246311827fbSnicm main(int argc, char **argv)
247311827fbSnicm {
248311827fbSnicm 	struct passwd	*pw;
24965439d22Snicm 	char		*s, *path, *label, *home, **var;
250c9347692Snicm 	int	 	 opt, flags, quiet, keys;
251311827fbSnicm 
252bbc36c02Snicm #ifdef DEBUG
253bbc36c02Snicm 	malloc_options = (char *) "AFGJPX";
254bbc36c02Snicm #endif
255bbc36c02Snicm 
256622ef518Snicm 	setlocale(LC_TIME, "");
257622ef518Snicm 
258c9347692Snicm 	quiet = flags = 0;
25965439d22Snicm 	label = path = NULL;
26065b1f011Snicm 	login_shell = (**argv == '-');
2610e3eb495Snicm 	while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUv")) != -1) {
262311827fbSnicm 		switch (opt) {
263311827fbSnicm 		case '2':
264311827fbSnicm 			flags |= IDENTIFY_256COLOURS;
265311827fbSnicm 			break;
266ef8ef121Snicm 		case 'c':
2677d053cf9Snicm 			free(shell_cmd);
26865439d22Snicm 			shell_cmd = xstrdup(optarg);
269ef8ef121Snicm 			break;
27090896992Snicm 		case 'C':
27190896992Snicm 			if (flags & IDENTIFY_CONTROL)
27290896992Snicm 				flags |= IDENTIFY_TERMIOS;
27390896992Snicm 			else
27490896992Snicm 				flags |= IDENTIFY_CONTROL;
27590896992Snicm 			break;
276311827fbSnicm 		case 'f':
2777d053cf9Snicm 			free(cfg_file);
278311827fbSnicm 			cfg_file = xstrdup(optarg);
279311827fbSnicm 			break;
28065b1f011Snicm 		case 'l':
28165b1f011Snicm 			login_shell = 1;
28265b1f011Snicm 			break;
283311827fbSnicm 		case 'L':
2847d053cf9Snicm 			free(label);
285311827fbSnicm 			label = xstrdup(optarg);
286311827fbSnicm 			break;
287481abfa9Snicm 		case 'q':
288f79ec119Snicm 			quiet = 1;
289481abfa9Snicm 			break;
290311827fbSnicm 		case 'S':
2917d053cf9Snicm 			free(path);
292311827fbSnicm 			path = xstrdup(optarg);
293311827fbSnicm 			break;
294311827fbSnicm 		case 'u':
295311827fbSnicm 			flags |= IDENTIFY_UTF8;
296311827fbSnicm 			break;
297311827fbSnicm 		case 'v':
298311827fbSnicm 			debug_level++;
299311827fbSnicm 			break;
300311827fbSnicm 		default:
301311827fbSnicm 			usage();
302311827fbSnicm 		}
303311827fbSnicm 	}
304311827fbSnicm 	argc -= optind;
305311827fbSnicm 	argv += optind;
306311827fbSnicm 
30765439d22Snicm 	if (shell_cmd != NULL && argc != 0)
308ef8ef121Snicm 		usage();
309ef8ef121Snicm 
310461e0674Snicm 	if (!(flags & IDENTIFY_UTF8)) {
311461e0674Snicm 		/*
312461e0674Snicm 		 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG
313461e0674Snicm 		 * exist (in that order) to contain UTF-8, it is a safe
314461e0674Snicm 		 * assumption that either they are using a UTF-8 terminal, or
315461e0674Snicm 		 * if not they know that output from UTF-8-capable programs may
316461e0674Snicm 		 * be wrong.
317461e0674Snicm 		 */
31854810b57Snicm 		if ((s = getenv("LC_ALL")) == NULL || *s == '\0') {
31954810b57Snicm 			if ((s = getenv("LC_CTYPE")) == NULL || *s == '\0')
320461e0674Snicm 				s = getenv("LANG");
321461e0674Snicm 		}
322a7a86d62Snicm 		if (s != NULL && (strcasestr(s, "UTF-8") != NULL ||
323a7a86d62Snicm 		    strcasestr(s, "UTF8") != NULL))
324461e0674Snicm 			flags |= IDENTIFY_UTF8;
325461e0674Snicm 	}
326461e0674Snicm 
327bd0e97c4Snicm 	environ_init(&global_environ);
328bd0e97c4Snicm 	for (var = environ; *var != NULL; var++)
329bd0e97c4Snicm 		environ_put(&global_environ, *var);
330bd0e97c4Snicm 
331f79ec119Snicm 	options_init(&global_options, NULL);
3323affa6cbSnicm 	options_table_populate_tree(server_options_table, &global_options);
3333affa6cbSnicm 	options_set_number(&global_options, "quiet", quiet);
334f79ec119Snicm 
335eaecedb2Snicm 	options_init(&global_s_options, NULL);
3363affa6cbSnicm 	options_table_populate_tree(session_options_table, &global_s_options);
3373affa6cbSnicm 	options_set_string(&global_s_options, "default-shell", "%s", getshell());
338311827fbSnicm 
3393affa6cbSnicm 	options_init(&global_w_options, NULL);
3403affa6cbSnicm 	options_table_populate_tree(window_options_table, &global_w_options);
3413affa6cbSnicm 
3423affa6cbSnicm 	/* Enable UTF-8 if the first client is on UTF-8 terminal. */
343bd0e97c4Snicm 	if (flags & IDENTIFY_UTF8) {
3443affa6cbSnicm 		options_set_number(&global_s_options, "status-utf8", 1);
345699995ddSnicm 		options_set_number(&global_s_options, "mouse-utf8", 1);
3463affa6cbSnicm 		options_set_number(&global_w_options, "utf8", 1);
347bd0e97c4Snicm 	}
348bd0e97c4Snicm 
3493affa6cbSnicm 	/* Override keys to vi if VISUAL or EDITOR are set. */
350c9347692Snicm 	if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
351c9347692Snicm 		if (strrchr(s, '/') != NULL)
352c9347692Snicm 			s = strrchr(s, '/') + 1;
353c9347692Snicm 		if (strstr(s, "vi") != NULL)
354c9347692Snicm 			keys = MODEKEY_VI;
3553affa6cbSnicm 		else
3563affa6cbSnicm 			keys = MODEKEY_EMACS;
3573affa6cbSnicm 		options_set_number(&global_s_options, "status-keys", keys);
3583affa6cbSnicm 		options_set_number(&global_w_options, "mode-keys", keys);
359c9347692Snicm 	}
360c9347692Snicm 
36165439d22Snicm 	/* Locate the configuration file. */
362311827fbSnicm 	if (cfg_file == NULL) {
363311827fbSnicm 		home = getenv("HOME");
364311827fbSnicm 		if (home == NULL || *home == '\0') {
365311827fbSnicm 			pw = getpwuid(getuid());
366311827fbSnicm 			if (pw != NULL)
367311827fbSnicm 				home = pw->pw_dir;
368311827fbSnicm 		}
369ebf82b56Snicm 		xasprintf(&cfg_file, "%s/.tmux.conf", home);
3709a53e128Snicm 		if (access(cfg_file, R_OK) != 0 && errno == ENOENT) {
3717d053cf9Snicm 			free(cfg_file);
372311827fbSnicm 			cfg_file = NULL;
373311827fbSnicm 		}
374311827fbSnicm 	}
375311827fbSnicm 
3762fa50cfcSnicm 	/*
37765439d22Snicm 	 * Figure out the socket path. If specified on the command-line with -S
37865439d22Snicm 	 * or -L, use it, otherwise try $TMUX or assume -L default.
3792fa50cfcSnicm 	 */
38065439d22Snicm 	parseenvironment();
3812fa50cfcSnicm 	if (path == NULL) {
38265439d22Snicm 		/* If no -L, use the environment. */
3832fa50cfcSnicm 		if (label == NULL) {
38465439d22Snicm 			if (environ_path != NULL)
38565439d22Snicm 				path = xstrdup(environ_path);
38665439d22Snicm 			else
387311827fbSnicm 				label = xstrdup("default");
3882fa50cfcSnicm 		}
3892fa50cfcSnicm 
3902fa50cfcSnicm 		/* -L or default set. */
3912fa50cfcSnicm 		if (label != NULL) {
39265439d22Snicm 			if ((path = makesocketpath(label)) == NULL) {
393*2b80f9f7Snicm 				fprintf(stderr, "can't create socket: %s\n",
394*2b80f9f7Snicm 					strerror(errno));
395311827fbSnicm 				exit(1);
396311827fbSnicm 			}
3972fa50cfcSnicm 		}
3982fa50cfcSnicm 	}
3997d053cf9Snicm 	free(label);
40065439d22Snicm 	strlcpy(socket_path, path, sizeof socket_path);
4017d053cf9Snicm 	free(path);
402311827fbSnicm 
40365439d22Snicm 	/* Set process title. */
40465439d22Snicm 	setproctitle("%s (%s)", __progname, socket_path);
405311827fbSnicm 
40665439d22Snicm 	/* Pass control to the client. */
40765439d22Snicm 	ev_base = event_init();
40865439d22Snicm 	exit(client_main(argc, argv, flags));
409fd234c13Snicm }
410