tmux.c revision 1.145
1121326Sharti/* $OpenBSD: tmux.c,v 1.145 2015/10/23 16:07:29 nicm Exp $ */
2121326Sharti
3121326Sharti/*
4121326Sharti * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
5121326Sharti *
6121326Sharti * Permission to use, copy, modify, and distribute this software for any
7121326Sharti * purpose with or without fee is hereby granted, provided that the above
8121326Sharti * copyright notice and this permission notice appear in all copies.
9121326Sharti *
10121326Sharti * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11121326Sharti * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12121326Sharti * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13121326Sharti * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14121326Sharti * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15121326Sharti * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16121326Sharti * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17121326Sharti */
18121326Sharti
19121326Sharti#include <sys/types.h>
20121326Sharti#include <sys/stat.h>
21121326Sharti
22121326Sharti#include <err.h>
23121326Sharti#include <errno.h>
24121326Sharti#include <event.h>
25121326Sharti#include <fcntl.h>
26121326Sharti#include <getopt.h>
27121326Sharti#include <locale.h>
28121326Sharti#include <paths.h>
29131826Sharti#include <pwd.h>
30121326Sharti#include <stdlib.h>
31121326Sharti#include <string.h>
32121326Sharti#include <unistd.h>
33121326Sharti
34121326Sharti#include "tmux.h"
35121326Sharti
36121326Sharti#ifdef DEBUG
37121326Shartiextern char	*malloc_options;
38121326Sharti#endif
39121326Sharti
40121326Shartistruct options	 global_options;	/* server options */
41121326Shartistruct options	 global_s_options;	/* session options */
42121326Shartistruct options	 global_w_options;	/* window options */
43121326Shartistruct environ	 global_environ;
44121326Sharti
45121326Shartichar		*shell_cmd;
46121326Shartiint		 debug_level;
47121326Shartitime_t		 start_time;
48121326Shartichar		 socket_path[PATH_MAX];
49121326Sharti
50121326Sharti__dead void	 usage(void);
51121326Shartichar 		*makesocketpath(const char *);
52121326Sharti
53121326Sharti__dead void
54121326Shartiusage(void)
55121326Sharti{
56121326Sharti	fprintf(stderr,
57121326Sharti	    "usage: %s [-2Cluv] [-c shell-command] [-f file] [-L socket-name]\n"
58121326Sharti	    "            [-S socket-path] [command [flags]]\n",
59121326Sharti	    __progname);
60121326Sharti	exit(1);
61121326Sharti}
62121326Sharti
63121326Shartivoid
64121326Shartilogfile(const char *name)
65121326Sharti{
66121326Sharti	char	*path;
67121326Sharti
68121326Sharti	if (debug_level > 0) {
69121326Sharti		xasprintf(&path, "tmux-%s-%ld.log", name, (long) getpid());
70121326Sharti		log_open(path);
71121326Sharti		free(path);
72121326Sharti	}
73121326Sharti}
74
75const char *
76getshell(void)
77{
78	struct passwd	*pw;
79	const char	*shell;
80
81	shell = getenv("SHELL");
82	if (checkshell(shell))
83		return (shell);
84
85	pw = getpwuid(getuid());
86	if (pw != NULL && checkshell(pw->pw_shell))
87		return (pw->pw_shell);
88
89	return (_PATH_BSHELL);
90}
91
92int
93checkshell(const char *shell)
94{
95	if (shell == NULL || *shell == '\0' || *shell != '/')
96		return (0);
97	if (areshell(shell))
98		return (0);
99	if (access(shell, X_OK) != 0)
100		return (0);
101	return (1);
102}
103
104int
105areshell(const char *shell)
106{
107	const char	*progname, *ptr;
108
109	if ((ptr = strrchr(shell, '/')) != NULL)
110		ptr++;
111	else
112		ptr = shell;
113	progname = __progname;
114	if (*progname == '-')
115		progname++;
116	if (strcmp(ptr, progname) == 0)
117		return (1);
118	return (0);
119}
120
121char *
122makesocketpath(const char *label)
123{
124	char		base[PATH_MAX], realbase[PATH_MAX], *path, *s;
125	struct stat	sb;
126	u_int		uid;
127
128	uid = getuid();
129	if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0')
130		xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid);
131	else if ((s = getenv("TMPDIR")) != NULL && *s != '\0')
132		xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid);
133	else
134		xsnprintf(base, sizeof base, "%s/tmux-%u", _PATH_TMP, uid);
135
136	if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST)
137		return (NULL);
138
139	if (lstat(base, &sb) != 0)
140		return (NULL);
141	if (!S_ISDIR(sb.st_mode)) {
142		errno = ENOTDIR;
143		return (NULL);
144	}
145	if (sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0) {
146		errno = EACCES;
147		return (NULL);
148	}
149
150	if (realpath(base, realbase) == NULL)
151		strlcpy(realbase, base, sizeof realbase);
152
153	xasprintf(&path, "%s/%s", realbase, label);
154	return (path);
155}
156
157void
158setblocking(int fd, int state)
159{
160	int mode;
161
162	if ((mode = fcntl(fd, F_GETFL)) != -1) {
163		if (!state)
164			mode |= O_NONBLOCK;
165		else
166			mode &= ~O_NONBLOCK;
167		fcntl(fd, F_SETFL, mode);
168	}
169}
170
171const char *
172find_home(void)
173{
174	struct passwd		*pw;
175	static const char	*home;
176
177	if (home != NULL)
178		return (home);
179
180	home = getenv("HOME");
181	if (home == NULL || *home == '\0') {
182		pw = getpwuid(getuid());
183		if (pw != NULL)
184			home = pw->pw_dir;
185		else
186			home = NULL;
187	}
188
189	return (home);
190}
191
192int
193main(int argc, char **argv)
194{
195	char	*s, *path, *label, **var, tmp[PATH_MAX];
196	int	 opt, flags, keys;
197
198#ifdef DEBUG
199	malloc_options = (char *) "AFGJPX";
200#endif
201
202	setlocale(LC_TIME, "");
203	tzset();
204
205	if (**argv == '-')
206		flags = CLIENT_LOGIN;
207	else
208		flags = 0;
209
210	label = path = NULL;
211	while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUv")) != -1) {
212		switch (opt) {
213		case '2':
214			flags |= CLIENT_256COLOURS;
215			break;
216		case 'c':
217			free(shell_cmd);
218			shell_cmd = xstrdup(optarg);
219			break;
220		case 'C':
221			if (flags & CLIENT_CONTROL)
222				flags |= CLIENT_CONTROLCONTROL;
223			else
224				flags |= CLIENT_CONTROL;
225			break;
226		case 'f':
227			set_cfg_file(optarg);
228			break;
229		case 'l':
230			flags |= CLIENT_LOGIN;
231			break;
232		case 'L':
233			free(label);
234			label = xstrdup(optarg);
235			break;
236		case 'q':
237			break;
238		case 'S':
239			free(path);
240			path = xstrdup(optarg);
241			break;
242		case 'u':
243			flags |= CLIENT_UTF8;
244			break;
245		case 'v':
246			debug_level++;
247			break;
248		default:
249			usage();
250		}
251	}
252	argc -= optind;
253	argv += optind;
254
255	if (shell_cmd != NULL && argc != 0)
256		usage();
257
258	if (pledge("stdio rpath wpath cpath flock fattr unix sendfd recvfd "
259	    "proc exec tty ps", NULL) != 0)
260		err(1, "pledge");
261
262	if (!(flags & CLIENT_UTF8)) {
263		/*
264		 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG
265		 * exist (in that order) to contain UTF-8, it is a safe
266		 * assumption that either they are using a UTF-8 terminal, or
267		 * if not they know that output from UTF-8-capable programs may
268		 * be wrong.
269		 */
270		if ((s = getenv("LC_ALL")) == NULL || *s == '\0') {
271			if ((s = getenv("LC_CTYPE")) == NULL || *s == '\0')
272				s = getenv("LANG");
273		}
274		if (s != NULL && (strcasestr(s, "UTF-8") != NULL ||
275		    strcasestr(s, "UTF8") != NULL))
276			flags |= CLIENT_UTF8;
277	}
278
279	environ_init(&global_environ);
280	for (var = environ; *var != NULL; var++)
281		environ_put(&global_environ, *var);
282	if (getcwd(tmp, sizeof tmp) != NULL)
283		environ_set(&global_environ, "PWD", tmp);
284
285	options_init(&global_options, NULL);
286	options_table_populate_tree(server_options_table, &global_options);
287
288	options_init(&global_s_options, NULL);
289	options_table_populate_tree(session_options_table, &global_s_options);
290	options_set_string(&global_s_options, "default-shell", "%s",
291	    getshell());
292
293	options_init(&global_w_options, NULL);
294	options_table_populate_tree(window_options_table, &global_w_options);
295
296	/* Enable UTF-8 if the first client is on UTF-8 terminal. */
297	if (flags & CLIENT_UTF8) {
298		options_set_number(&global_s_options, "status-utf8", 1);
299		options_set_number(&global_s_options, "mouse-utf8", 1);
300		options_set_number(&global_w_options, "utf8", 1);
301	}
302
303	/* Override keys to vi if VISUAL or EDITOR are set. */
304	if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
305		if (strrchr(s, '/') != NULL)
306			s = strrchr(s, '/') + 1;
307		if (strstr(s, "vi") != NULL)
308			keys = MODEKEY_VI;
309		else
310			keys = MODEKEY_EMACS;
311		options_set_number(&global_s_options, "status-keys", keys);
312		options_set_number(&global_w_options, "mode-keys", keys);
313	}
314
315	/*
316	 * Figure out the socket path. If specified on the command-line with -S
317	 * or -L, use it, otherwise try $TMUX or assume -L default.
318	 */
319	if (path == NULL) {
320		/* If no -L, use the environment. */
321		if (label == NULL) {
322			s = getenv("TMUX");
323			if (s != NULL) {
324				path = xstrdup(s);
325				path[strcspn (path, ",")] = '\0';
326				if (*path == '\0') {
327					free(path);
328					label = xstrdup("default");
329				}
330			} else
331				label = xstrdup("default");
332		}
333
334		/* -L or default set. */
335		if (label != NULL) {
336			if ((path = makesocketpath(label)) == NULL) {
337				fprintf(stderr, "can't create socket: %s\n",
338				    strerror(errno));
339				exit(1);
340			}
341		}
342	}
343	free(label);
344
345	if (strlcpy(socket_path, path, sizeof socket_path) >=
346	    sizeof socket_path) {
347		fprintf(stderr, "socket path too long: %s\n", path);
348		exit(1);
349	}
350	free(path);
351
352	/* Set process title. */
353	setproctitle("%s (%s)", __progname, socket_path);
354
355	/* Pass control to the client. */
356	exit(client_main(event_init(), argc, argv, flags));
357}
358