xref: /openbsd/usr.bin/tmux/tmux.c (revision 41c58c32)
1*41c58c32Snicm /* $OpenBSD: tmux.c,v 1.122 2013/10/05 10:40:49 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];
132*41c58c32Snicm 	const char	*retval;
13335a092d6Snicm 
134c4a631faSnicm 	fd = open(".", O_RDONLY);
135c4a631faSnicm 	if (fd == -1)
13635a092d6Snicm 		return (NULL);
137c4a631faSnicm 
138*41c58c32Snicm 	retval = NULL;
139*41c58c32Snicm 	if (chdir(wd) == 0) {
140*41c58c32Snicm 		if (realpath(path, newpath) == 0)
141*41c58c32Snicm 			retval = newpath;
142*41c58c32Snicm 	}
143c4a631faSnicm 
144c4a631faSnicm 	if (fchdir(fd) != 0)
145c4a631faSnicm 		chdir("/");
146c4a631faSnicm 	close(fd);
147c4a631faSnicm 
148*41c58c32Snicm 	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 	}
192311827fbSnicm 	if (sb.st_uid != uid || (sb.st_mode & (S_IRWXG|S_IRWXO)) != 0) {
193311827fbSnicm 		errno = EACCES;
194311827fbSnicm 		return (NULL);
195311827fbSnicm 	}
196311827fbSnicm 
19735194355Snicm 	if (realpath(base, realbase) == NULL)
19835194355Snicm 		strlcpy(realbase, base, sizeof realbase);
19935194355Snicm 
20035194355Snicm 	xasprintf(&path, "%s/%s", realbase, label);
201311827fbSnicm 	return (path);
202311827fbSnicm }
203311827fbSnicm 
204a0ee4254Snicm void
205a0ee4254Snicm setblocking(int fd, int state)
206a0ee4254Snicm {
207a0ee4254Snicm 	int mode;
208a0ee4254Snicm 
209a0ee4254Snicm 	if ((mode = fcntl(fd, F_GETFL)) != -1) {
210a0ee4254Snicm 		if (!state)
211a0ee4254Snicm 			mode |= O_NONBLOCK;
212a0ee4254Snicm 		else
213a0ee4254Snicm 			mode &= ~O_NONBLOCK;
214a0ee4254Snicm 		fcntl(fd, F_SETFL, mode);
215a0ee4254Snicm 	}
216a0ee4254Snicm }
217a0ee4254Snicm 
2187724d0b0Snicm __dead void
2197724d0b0Snicm shell_exec(const char *shell, const char *shellcmd)
2207724d0b0Snicm {
2217724d0b0Snicm 	const char	*shellname, *ptr;
2227724d0b0Snicm 	char		*argv0;
2237724d0b0Snicm 
2247724d0b0Snicm 	ptr = strrchr(shell, '/');
2257724d0b0Snicm 	if (ptr != NULL && *(ptr + 1) != '\0')
2267724d0b0Snicm 		shellname = ptr + 1;
2277724d0b0Snicm 	else
2287724d0b0Snicm 		shellname = shell;
2297724d0b0Snicm 	if (login_shell)
2307724d0b0Snicm 		xasprintf(&argv0, "-%s", shellname);
2317724d0b0Snicm 	else
2327724d0b0Snicm 		xasprintf(&argv0, "%s", shellname);
2337724d0b0Snicm 	setenv("SHELL", shell, 1);
2347724d0b0Snicm 
235a0ee4254Snicm 	setblocking(STDIN_FILENO, 1);
236a0ee4254Snicm 	setblocking(STDOUT_FILENO, 1);
237a0ee4254Snicm 	setblocking(STDERR_FILENO, 1);
238498ee386Snicm 	closefrom(STDERR_FILENO + 1);
239498ee386Snicm 
2407724d0b0Snicm 	execl(shell, argv0, "-c", shellcmd, (char *) NULL);
2417724d0b0Snicm 	fatal("execl failed");
2427724d0b0Snicm }
2437724d0b0Snicm 
244311827fbSnicm int
245311827fbSnicm main(int argc, char **argv)
246311827fbSnicm {
247311827fbSnicm 	struct passwd	*pw;
24865439d22Snicm 	char		*s, *path, *label, *home, **var;
249c9347692Snicm 	int	 	 opt, flags, quiet, keys;
250311827fbSnicm 
251bbc36c02Snicm #ifdef DEBUG
252bbc36c02Snicm 	malloc_options = (char *) "AFGJPX";
253bbc36c02Snicm #endif
254bbc36c02Snicm 
255622ef518Snicm 	setlocale(LC_TIME, "");
256622ef518Snicm 
257c9347692Snicm 	quiet = flags = 0;
25865439d22Snicm 	label = path = NULL;
25965b1f011Snicm 	login_shell = (**argv == '-');
2600e3eb495Snicm 	while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUv")) != -1) {
261311827fbSnicm 		switch (opt) {
262311827fbSnicm 		case '2':
263311827fbSnicm 			flags |= IDENTIFY_256COLOURS;
264311827fbSnicm 			break;
265ef8ef121Snicm 		case 'c':
2667d053cf9Snicm 			free(shell_cmd);
26765439d22Snicm 			shell_cmd = xstrdup(optarg);
268ef8ef121Snicm 			break;
26990896992Snicm 		case 'C':
27090896992Snicm 			if (flags & IDENTIFY_CONTROL)
27190896992Snicm 				flags |= IDENTIFY_TERMIOS;
27290896992Snicm 			else
27390896992Snicm 				flags |= IDENTIFY_CONTROL;
27490896992Snicm 			break;
275311827fbSnicm 		case 'f':
2767d053cf9Snicm 			free(cfg_file);
277311827fbSnicm 			cfg_file = xstrdup(optarg);
278311827fbSnicm 			break;
27965b1f011Snicm 		case 'l':
28065b1f011Snicm 			login_shell = 1;
28165b1f011Snicm 			break;
282311827fbSnicm 		case 'L':
2837d053cf9Snicm 			free(label);
284311827fbSnicm 			label = xstrdup(optarg);
285311827fbSnicm 			break;
286481abfa9Snicm 		case 'q':
287f79ec119Snicm 			quiet = 1;
288481abfa9Snicm 			break;
289311827fbSnicm 		case 'S':
2907d053cf9Snicm 			free(path);
291311827fbSnicm 			path = xstrdup(optarg);
292311827fbSnicm 			break;
293311827fbSnicm 		case 'u':
294311827fbSnicm 			flags |= IDENTIFY_UTF8;
295311827fbSnicm 			break;
296311827fbSnicm 		case 'v':
297311827fbSnicm 			debug_level++;
298311827fbSnicm 			break;
299311827fbSnicm 		default:
300311827fbSnicm 			usage();
301311827fbSnicm 		}
302311827fbSnicm 	}
303311827fbSnicm 	argc -= optind;
304311827fbSnicm 	argv += optind;
305311827fbSnicm 
30665439d22Snicm 	if (shell_cmd != NULL && argc != 0)
307ef8ef121Snicm 		usage();
308ef8ef121Snicm 
309461e0674Snicm 	if (!(flags & IDENTIFY_UTF8)) {
310461e0674Snicm 		/*
311461e0674Snicm 		 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG
312461e0674Snicm 		 * exist (in that order) to contain UTF-8, it is a safe
313461e0674Snicm 		 * assumption that either they are using a UTF-8 terminal, or
314461e0674Snicm 		 * if not they know that output from UTF-8-capable programs may
315461e0674Snicm 		 * be wrong.
316461e0674Snicm 		 */
31754810b57Snicm 		if ((s = getenv("LC_ALL")) == NULL || *s == '\0') {
31854810b57Snicm 			if ((s = getenv("LC_CTYPE")) == NULL || *s == '\0')
319461e0674Snicm 				s = getenv("LANG");
320461e0674Snicm 		}
321a7a86d62Snicm 		if (s != NULL && (strcasestr(s, "UTF-8") != NULL ||
322a7a86d62Snicm 		    strcasestr(s, "UTF8") != NULL))
323461e0674Snicm 			flags |= IDENTIFY_UTF8;
324461e0674Snicm 	}
325461e0674Snicm 
326bd0e97c4Snicm 	environ_init(&global_environ);
327bd0e97c4Snicm 	for (var = environ; *var != NULL; var++)
328bd0e97c4Snicm 		environ_put(&global_environ, *var);
329bd0e97c4Snicm 
330f79ec119Snicm 	options_init(&global_options, NULL);
3313affa6cbSnicm 	options_table_populate_tree(server_options_table, &global_options);
3323affa6cbSnicm 	options_set_number(&global_options, "quiet", quiet);
333f79ec119Snicm 
334eaecedb2Snicm 	options_init(&global_s_options, NULL);
3353affa6cbSnicm 	options_table_populate_tree(session_options_table, &global_s_options);
3363affa6cbSnicm 	options_set_string(&global_s_options, "default-shell", "%s", getshell());
337311827fbSnicm 
3383affa6cbSnicm 	options_init(&global_w_options, NULL);
3393affa6cbSnicm 	options_table_populate_tree(window_options_table, &global_w_options);
3403affa6cbSnicm 
3413affa6cbSnicm 	/* Enable UTF-8 if the first client is on UTF-8 terminal. */
342bd0e97c4Snicm 	if (flags & IDENTIFY_UTF8) {
3433affa6cbSnicm 		options_set_number(&global_s_options, "status-utf8", 1);
344699995ddSnicm 		options_set_number(&global_s_options, "mouse-utf8", 1);
3453affa6cbSnicm 		options_set_number(&global_w_options, "utf8", 1);
346bd0e97c4Snicm 	}
347bd0e97c4Snicm 
3483affa6cbSnicm 	/* Override keys to vi if VISUAL or EDITOR are set. */
349c9347692Snicm 	if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
350c9347692Snicm 		if (strrchr(s, '/') != NULL)
351c9347692Snicm 			s = strrchr(s, '/') + 1;
352c9347692Snicm 		if (strstr(s, "vi") != NULL)
353c9347692Snicm 			keys = MODEKEY_VI;
3543affa6cbSnicm 		else
3553affa6cbSnicm 			keys = MODEKEY_EMACS;
3563affa6cbSnicm 		options_set_number(&global_s_options, "status-keys", keys);
3573affa6cbSnicm 		options_set_number(&global_w_options, "mode-keys", keys);
358c9347692Snicm 	}
359c9347692Snicm 
36065439d22Snicm 	/* Locate the configuration file. */
361311827fbSnicm 	if (cfg_file == NULL) {
362311827fbSnicm 		home = getenv("HOME");
363311827fbSnicm 		if (home == NULL || *home == '\0') {
364311827fbSnicm 			pw = getpwuid(getuid());
365311827fbSnicm 			if (pw != NULL)
366311827fbSnicm 				home = pw->pw_dir;
367311827fbSnicm 		}
368ebf82b56Snicm 		xasprintf(&cfg_file, "%s/.tmux.conf", home);
3699a53e128Snicm 		if (access(cfg_file, R_OK) != 0 && errno == ENOENT) {
3707d053cf9Snicm 			free(cfg_file);
371311827fbSnicm 			cfg_file = NULL;
372311827fbSnicm 		}
373311827fbSnicm 	}
374311827fbSnicm 
3752fa50cfcSnicm 	/*
37665439d22Snicm 	 * Figure out the socket path. If specified on the command-line with -S
37765439d22Snicm 	 * or -L, use it, otherwise try $TMUX or assume -L default.
3782fa50cfcSnicm 	 */
37965439d22Snicm 	parseenvironment();
3802fa50cfcSnicm 	if (path == NULL) {
38165439d22Snicm 		/* If no -L, use the environment. */
3822fa50cfcSnicm 		if (label == NULL) {
38365439d22Snicm 			if (environ_path != NULL)
38465439d22Snicm 				path = xstrdup(environ_path);
38565439d22Snicm 			else
386311827fbSnicm 				label = xstrdup("default");
3872fa50cfcSnicm 		}
3882fa50cfcSnicm 
3892fa50cfcSnicm 		/* -L or default set. */
3902fa50cfcSnicm 		if (label != NULL) {
39165439d22Snicm 			if ((path = makesocketpath(label)) == NULL) {
39236fa3172Snicm 				fprintf(stderr, "can't create socket\n");
393311827fbSnicm 				exit(1);
394311827fbSnicm 			}
3952fa50cfcSnicm 		}
3962fa50cfcSnicm 	}
3977d053cf9Snicm 	free(label);
39865439d22Snicm 	strlcpy(socket_path, path, sizeof socket_path);
3997d053cf9Snicm 	free(path);
400311827fbSnicm 
40165439d22Snicm 	/* Set process title. */
40265439d22Snicm 	setproctitle("%s (%s)", __progname, socket_path);
403311827fbSnicm 
40465439d22Snicm 	/* Pass control to the client. */
40565439d22Snicm 	ev_base = event_init();
40665439d22Snicm 	exit(client_main(argc, argv, flags));
407fd234c13Snicm }
408