xref: /openbsd/usr.bin/tmux/tmux.c (revision f038d384)
1*f038d384Snicm /* $OpenBSD: tmux.c,v 1.142 2015/09/01 10:10:59 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>
2526529d6fSnicm #include <getopt.h>
26622ef518Snicm #include <locale.h>
27311827fbSnicm #include <paths.h>
28311827fbSnicm #include <pwd.h>
29311827fbSnicm #include <stdlib.h>
30311827fbSnicm #include <string.h>
31311827fbSnicm #include <unistd.h>
32311827fbSnicm 
33311827fbSnicm #include "tmux.h"
34311827fbSnicm 
35311827fbSnicm #ifdef DEBUG
36bbc36c02Snicm extern char	*malloc_options;
37311827fbSnicm #endif
38311827fbSnicm 
39f79ec119Snicm struct options	 global_options;	/* server options */
40eaecedb2Snicm struct options	 global_s_options;	/* session options */
41eaecedb2Snicm struct options	 global_w_options;	/* window options */
426f7d62ebSnicm struct environ	 global_environ;
43311827fbSnicm 
4465439d22Snicm char		*shell_cmd;
45311827fbSnicm int		 debug_level;
46311827fbSnicm time_t		 start_time;
4726529d6fSnicm char		 socket_path[PATH_MAX];
484ccba3b3Snicm 
49311827fbSnicm __dead void	 usage(void);
5065439d22Snicm char 		*makesocketpath(const char *);
517724d0b0Snicm 
52311827fbSnicm __dead void
53311827fbSnicm usage(void)
54311827fbSnicm {
556ca2ce32Ssobrado 	fprintf(stderr,
563da585bcSjmc 	    "usage: %s [-2Cluv] [-c shell-command] [-f file] [-L socket-name]\n"
5765b1f011Snicm 	    "            [-S socket-path] [command [flags]]\n",
58311827fbSnicm 	    __progname);
59311827fbSnicm 	exit(1);
60311827fbSnicm }
61311827fbSnicm 
62311827fbSnicm void
63311827fbSnicm logfile(const char *name)
64311827fbSnicm {
65311827fbSnicm 	char	*path;
66311827fbSnicm 
67311827fbSnicm 	if (debug_level > 0) {
683a4fea3bSnicm 		xasprintf(&path, "tmux-%s-%ld.log", name, (long) getpid());
6946b1c757Snicm 		log_open(path);
707d053cf9Snicm 		free(path);
71311827fbSnicm 	}
72311827fbSnicm }
73311827fbSnicm 
743a5ec08bSnicm const char *
753a5ec08bSnicm getshell(void)
763a5ec08bSnicm {
773a5ec08bSnicm 	struct passwd	*pw;
783a5ec08bSnicm 	const char	*shell;
793a5ec08bSnicm 
803a5ec08bSnicm 	shell = getenv("SHELL");
813a5ec08bSnicm 	if (checkshell(shell))
823a5ec08bSnicm 		return (shell);
833a5ec08bSnicm 
843a5ec08bSnicm 	pw = getpwuid(getuid());
853a5ec08bSnicm 	if (pw != NULL && checkshell(pw->pw_shell))
863a5ec08bSnicm 		return (pw->pw_shell);
873a5ec08bSnicm 
883a5ec08bSnicm 	return (_PATH_BSHELL);
893a5ec08bSnicm }
903a5ec08bSnicm 
913a5ec08bSnicm int
923a5ec08bSnicm checkshell(const char *shell)
933a5ec08bSnicm {
94a89a0ceeSnicm 	if (shell == NULL || *shell == '\0' || *shell != '/')
95a89a0ceeSnicm 		return (0);
96a89a0ceeSnicm 	if (areshell(shell))
973a5ec08bSnicm 		return (0);
983a5ec08bSnicm 	if (access(shell, X_OK) != 0)
993a5ec08bSnicm 		return (0);
1003a5ec08bSnicm 	return (1);
1013a5ec08bSnicm }
1023a5ec08bSnicm 
1033a5ec08bSnicm int
1043a5ec08bSnicm areshell(const char *shell)
1053a5ec08bSnicm {
1063a5ec08bSnicm 	const char	*progname, *ptr;
1073a5ec08bSnicm 
1083a5ec08bSnicm 	if ((ptr = strrchr(shell, '/')) != NULL)
1093a5ec08bSnicm 		ptr++;
1103a5ec08bSnicm 	else
1113a5ec08bSnicm 		ptr = shell;
1123a5ec08bSnicm 	progname = __progname;
1133a5ec08bSnicm 	if (*progname == '-')
1143a5ec08bSnicm 		progname++;
1153a5ec08bSnicm 	if (strcmp(ptr, progname) == 0)
1163a5ec08bSnicm 		return (1);
1173a5ec08bSnicm 	return (0);
1183a5ec08bSnicm }
1193a5ec08bSnicm 
120311827fbSnicm char *
12165439d22Snicm makesocketpath(const char *label)
122311827fbSnicm {
12326529d6fSnicm 	char		base[PATH_MAX], realbase[PATH_MAX], *path, *s;
124311827fbSnicm 	struct stat	sb;
125311827fbSnicm 	u_int		uid;
126311827fbSnicm 
127311827fbSnicm 	uid = getuid();
12840b64c41Snicm 	if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0')
129cb12d36bSnicm 		xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid);
13040b64c41Snicm 	else if ((s = getenv("TMPDIR")) != NULL && *s != '\0')
131e3b883c9Snicm 		xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid);
13240b64c41Snicm 	else
13340b64c41Snicm 		xsnprintf(base, sizeof base, "%s/tmux-%u", _PATH_TMP, uid);
134311827fbSnicm 
135311827fbSnicm 	if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST)
136311827fbSnicm 		return (NULL);
137311827fbSnicm 
138311827fbSnicm 	if (lstat(base, &sb) != 0)
139311827fbSnicm 		return (NULL);
140311827fbSnicm 	if (!S_ISDIR(sb.st_mode)) {
141311827fbSnicm 		errno = ENOTDIR;
142311827fbSnicm 		return (NULL);
143311827fbSnicm 	}
1449945ad17Snicm 	if (sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0) {
145311827fbSnicm 		errno = EACCES;
146311827fbSnicm 		return (NULL);
147311827fbSnicm 	}
148311827fbSnicm 
14935194355Snicm 	if (realpath(base, realbase) == NULL)
15035194355Snicm 		strlcpy(realbase, base, sizeof realbase);
15135194355Snicm 
15235194355Snicm 	xasprintf(&path, "%s/%s", realbase, label);
153311827fbSnicm 	return (path);
154311827fbSnicm }
155311827fbSnicm 
156a0ee4254Snicm void
157a0ee4254Snicm setblocking(int fd, int state)
158a0ee4254Snicm {
159a0ee4254Snicm 	int mode;
160a0ee4254Snicm 
161a0ee4254Snicm 	if ((mode = fcntl(fd, F_GETFL)) != -1) {
162a0ee4254Snicm 		if (!state)
163a0ee4254Snicm 			mode |= O_NONBLOCK;
164a0ee4254Snicm 		else
165a0ee4254Snicm 			mode &= ~O_NONBLOCK;
166a0ee4254Snicm 		fcntl(fd, F_SETFL, mode);
167a0ee4254Snicm 	}
168a0ee4254Snicm }
169a0ee4254Snicm 
170179ef399Snicm const char *
171179ef399Snicm find_home(void)
172179ef399Snicm {
173179ef399Snicm 	struct passwd		*pw;
174*f038d384Snicm 	static const char	*home;
175*f038d384Snicm 
176*f038d384Snicm 	if (home != NULL)
177*f038d384Snicm 		return (home);
178179ef399Snicm 
179179ef399Snicm 	home = getenv("HOME");
180179ef399Snicm 	if (home == NULL || *home == '\0') {
181179ef399Snicm 		pw = getpwuid(getuid());
182179ef399Snicm 		if (pw != NULL)
183179ef399Snicm 			home = pw->pw_dir;
184179ef399Snicm 		else
185179ef399Snicm 			home = NULL;
186179ef399Snicm 	}
187179ef399Snicm 
18882bdfec0Snicm 	return (home);
189179ef399Snicm }
190179ef399Snicm 
191311827fbSnicm int
192311827fbSnicm main(int argc, char **argv)
193311827fbSnicm {
19426529d6fSnicm 	char	*s, *path, *label, **var, tmp[PATH_MAX];
1954fa31997Snicm 	int	 opt, flags, keys;
196311827fbSnicm 
197bbc36c02Snicm #ifdef DEBUG
198bbc36c02Snicm 	malloc_options = (char *) "AFGJPX";
199bbc36c02Snicm #endif
200bbc36c02Snicm 
201622ef518Snicm 	setlocale(LC_TIME, "");
202622ef518Snicm 
2036b014674Snicm 	if (**argv == '-')
2046b014674Snicm 		flags = CLIENT_LOGIN;
2056b014674Snicm 	else
2067f6133c3Snicm 		flags = 0;
2076b014674Snicm 
20865439d22Snicm 	label = path = NULL;
2090e3eb495Snicm 	while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUv")) != -1) {
210311827fbSnicm 		switch (opt) {
211311827fbSnicm 		case '2':
212cafa7ad7Snicm 			flags |= CLIENT_256COLOURS;
213311827fbSnicm 			break;
214ef8ef121Snicm 		case 'c':
2157d053cf9Snicm 			free(shell_cmd);
21665439d22Snicm 			shell_cmd = xstrdup(optarg);
217ef8ef121Snicm 			break;
21890896992Snicm 		case 'C':
219cafa7ad7Snicm 			if (flags & CLIENT_CONTROL)
220cafa7ad7Snicm 				flags |= CLIENT_CONTROLCONTROL;
22190896992Snicm 			else
222cafa7ad7Snicm 				flags |= CLIENT_CONTROL;
22390896992Snicm 			break;
224311827fbSnicm 		case 'f':
225*f038d384Snicm 			set_cfg_file(optarg);
226311827fbSnicm 			break;
22765b1f011Snicm 		case 'l':
2286b014674Snicm 			flags |= CLIENT_LOGIN;
22965b1f011Snicm 			break;
230311827fbSnicm 		case 'L':
2317d053cf9Snicm 			free(label);
232311827fbSnicm 			label = xstrdup(optarg);
233311827fbSnicm 			break;
234481abfa9Snicm 		case 'q':
235481abfa9Snicm 			break;
236311827fbSnicm 		case 'S':
2377d053cf9Snicm 			free(path);
238311827fbSnicm 			path = xstrdup(optarg);
239311827fbSnicm 			break;
240311827fbSnicm 		case 'u':
241cafa7ad7Snicm 			flags |= CLIENT_UTF8;
242311827fbSnicm 			break;
243311827fbSnicm 		case 'v':
244311827fbSnicm 			debug_level++;
245311827fbSnicm 			break;
246311827fbSnicm 		default:
247311827fbSnicm 			usage();
248311827fbSnicm 		}
249311827fbSnicm 	}
250311827fbSnicm 	argc -= optind;
251311827fbSnicm 	argv += optind;
252311827fbSnicm 
25365439d22Snicm 	if (shell_cmd != NULL && argc != 0)
254ef8ef121Snicm 		usage();
255ef8ef121Snicm 
256cafa7ad7Snicm 	if (!(flags & CLIENT_UTF8)) {
257461e0674Snicm 		/*
258461e0674Snicm 		 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG
259461e0674Snicm 		 * exist (in that order) to contain UTF-8, it is a safe
260461e0674Snicm 		 * assumption that either they are using a UTF-8 terminal, or
261461e0674Snicm 		 * if not they know that output from UTF-8-capable programs may
262461e0674Snicm 		 * be wrong.
263461e0674Snicm 		 */
26454810b57Snicm 		if ((s = getenv("LC_ALL")) == NULL || *s == '\0') {
26554810b57Snicm 			if ((s = getenv("LC_CTYPE")) == NULL || *s == '\0')
266461e0674Snicm 				s = getenv("LANG");
267461e0674Snicm 		}
268a7a86d62Snicm 		if (s != NULL && (strcasestr(s, "UTF-8") != NULL ||
269a7a86d62Snicm 		    strcasestr(s, "UTF8") != NULL))
270cafa7ad7Snicm 			flags |= CLIENT_UTF8;
271461e0674Snicm 	}
272461e0674Snicm 
273bd0e97c4Snicm 	environ_init(&global_environ);
274bd0e97c4Snicm 	for (var = environ; *var != NULL; var++)
275bd0e97c4Snicm 		environ_put(&global_environ, *var);
276e1803d63Snicm 	if (getcwd(tmp, sizeof tmp) != NULL)
277e1803d63Snicm 		environ_set(&global_environ, "PWD", tmp);
278bd0e97c4Snicm 
279f79ec119Snicm 	options_init(&global_options, NULL);
2803affa6cbSnicm 	options_table_populate_tree(server_options_table, &global_options);
281f79ec119Snicm 
282eaecedb2Snicm 	options_init(&global_s_options, NULL);
2833affa6cbSnicm 	options_table_populate_tree(session_options_table, &global_s_options);
2847f6133c3Snicm 	options_set_string(&global_s_options, "default-shell", "%s",
2857f6133c3Snicm 	    getshell());
286311827fbSnicm 
2873affa6cbSnicm 	options_init(&global_w_options, NULL);
2883affa6cbSnicm 	options_table_populate_tree(window_options_table, &global_w_options);
2893affa6cbSnicm 
2903affa6cbSnicm 	/* Enable UTF-8 if the first client is on UTF-8 terminal. */
291cafa7ad7Snicm 	if (flags & CLIENT_UTF8) {
2923affa6cbSnicm 		options_set_number(&global_s_options, "status-utf8", 1);
293699995ddSnicm 		options_set_number(&global_s_options, "mouse-utf8", 1);
2943affa6cbSnicm 		options_set_number(&global_w_options, "utf8", 1);
295bd0e97c4Snicm 	}
296bd0e97c4Snicm 
2973affa6cbSnicm 	/* Override keys to vi if VISUAL or EDITOR are set. */
298c9347692Snicm 	if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
299c9347692Snicm 		if (strrchr(s, '/') != NULL)
300c9347692Snicm 			s = strrchr(s, '/') + 1;
301c9347692Snicm 		if (strstr(s, "vi") != NULL)
302c9347692Snicm 			keys = MODEKEY_VI;
3033affa6cbSnicm 		else
3043affa6cbSnicm 			keys = MODEKEY_EMACS;
3053affa6cbSnicm 		options_set_number(&global_s_options, "status-keys", keys);
3063affa6cbSnicm 		options_set_number(&global_w_options, "mode-keys", keys);
307c9347692Snicm 	}
308c9347692Snicm 
3092fa50cfcSnicm 	/*
31065439d22Snicm 	 * Figure out the socket path. If specified on the command-line with -S
31165439d22Snicm 	 * or -L, use it, otherwise try $TMUX or assume -L default.
3122fa50cfcSnicm 	 */
3132fa50cfcSnicm 	if (path == NULL) {
31465439d22Snicm 		/* If no -L, use the environment. */
3152fa50cfcSnicm 		if (label == NULL) {
3164fa31997Snicm 			s = getenv("TMUX");
3174fa31997Snicm 			if (s != NULL) {
3184fa31997Snicm 				path = xstrdup(s);
3194fa31997Snicm 				path[strcspn (path, ",")] = '\0';
3204fa31997Snicm 				if (*path == '\0') {
3214fa31997Snicm 					free(path);
3224fa31997Snicm 					label = xstrdup("default");
3234fa31997Snicm 				}
3244fa31997Snicm 			}
32565439d22Snicm 			else
326311827fbSnicm 				label = xstrdup("default");
3272fa50cfcSnicm 		}
3282fa50cfcSnicm 
3292fa50cfcSnicm 		/* -L or default set. */
3302fa50cfcSnicm 		if (label != NULL) {
33165439d22Snicm 			if ((path = makesocketpath(label)) == NULL) {
3322b80f9f7Snicm 				fprintf(stderr, "can't create socket: %s\n",
3332b80f9f7Snicm 				    strerror(errno));
334311827fbSnicm 				exit(1);
335311827fbSnicm 			}
3362fa50cfcSnicm 		}
3372fa50cfcSnicm 	}
3387d053cf9Snicm 	free(label);
3396f631021Snicm 
3404fa31997Snicm 	if (strlcpy(socket_path, path, sizeof socket_path) >=
3414fa31997Snicm 	    sizeof socket_path) {
3426f631021Snicm 		fprintf(stderr, "socket path too long: %s\n", path);
3436f631021Snicm 		exit(1);
3446f631021Snicm 	}
3457d053cf9Snicm 	free(path);
346311827fbSnicm 
34765439d22Snicm 	/* Set process title. */
34865439d22Snicm 	setproctitle("%s (%s)", __progname, socket_path);
349311827fbSnicm 
35065439d22Snicm 	/* Pass control to the client. */
3517b560ed5Snicm 	exit(client_main(event_init(), argc, argv, flags));
352fd234c13Snicm }
353