tmux.c revision 1.51
1/* $OpenBSD: tmux.c,v 1.51 2009/10/22 10:04:07 nicm Exp $ */
2
3/*
4 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/stat.h>
21
22#include <errno.h>
23#include <paths.h>
24#include <pwd.h>
25#include <signal.h>
26#include <stdlib.h>
27#include <string.h>
28#include <syslog.h>
29#include <unistd.h>
30
31#include "tmux.h"
32
33#ifdef DEBUG
34const char	*malloc_options = "AFGJPX";
35#endif
36
37volatile sig_atomic_t sigwinch;
38volatile sig_atomic_t sigterm;
39volatile sig_atomic_t sigcont;
40volatile sig_atomic_t sigchld;
41volatile sig_atomic_t sigusr1;
42volatile sig_atomic_t sigusr2;
43
44char		*cfg_file;
45struct options	 global_s_options;	/* session options */
46struct options	 global_w_options;	/* window options */
47struct environ	 global_environ;
48
49int		 debug_level;
50int		 be_quiet;
51time_t		 start_time;
52char		*socket_path;
53int		 login_shell;
54
55__dead void	 usage(void);
56void	 	 fill_session(struct msg_command_data *);
57char 		*makesockpath(const char *);
58int		 dispatch_imsg(struct imsgbuf *, const char *, int *);
59__dead void	 shell_exec(const char *, const char *);
60
61__dead void
62usage(void)
63{
64	fprintf(stderr,
65	    "usage: %s [-28dlquv] [-c shell-command] [-f file] [-L socket-name]\n"
66	    "            [-S socket-path] [command [flags]]\n",
67	    __progname);
68	exit(1);
69}
70
71void
72logfile(const char *name)
73{
74	char	*path;
75
76	log_close();
77	if (debug_level > 0) {
78		xasprintf(&path, "tmux-%s-%ld.log", name, (long) getpid());
79		log_open_file(debug_level, path);
80		xfree(path);
81	}
82}
83
84void
85sighandler(int sig)
86{
87	int	saved_errno;
88
89	saved_errno = errno;
90	switch (sig) {
91	case SIGWINCH:
92		sigwinch = 1;
93		break;
94	case SIGTERM:
95		sigterm = 1;
96		break;
97	case SIGCHLD:
98		sigchld = 1;
99		break;
100	case SIGCONT:
101		sigcont = 1;
102		break;
103	case SIGUSR1:
104		sigusr1 = 1;
105		break;
106	case SIGUSR2:
107		sigusr2 = 1;
108		break;
109	}
110	errno = saved_errno;
111}
112
113void
114siginit(void)
115{
116	struct sigaction	 act;
117
118	memset(&act, 0, sizeof act);
119	sigemptyset(&act.sa_mask);
120	act.sa_flags = SA_RESTART;
121
122	act.sa_handler = SIG_IGN;
123	if (sigaction(SIGPIPE, &act, NULL) != 0)
124		fatal("sigaction failed");
125	if (sigaction(SIGINT, &act, NULL) != 0)
126		fatal("sigaction failed");
127	if (sigaction(SIGTSTP, &act, NULL) != 0)
128		fatal("sigaction failed");
129	if (sigaction(SIGQUIT, &act, NULL) != 0)
130		fatal("sigaction failed");
131
132	act.sa_handler = sighandler;
133	if (sigaction(SIGWINCH, &act, NULL) != 0)
134		fatal("sigaction failed");
135	if (sigaction(SIGTERM, &act, NULL) != 0)
136		fatal("sigaction failed");
137	if (sigaction(SIGCHLD, &act, NULL) != 0)
138		fatal("sigaction failed");
139	if (sigaction(SIGUSR1, &act, NULL) != 0)
140		fatal("sigaction failed");
141	if (sigaction(SIGUSR2, &act, NULL) != 0)
142		fatal("sigaction failed");
143}
144
145void
146sigreset(void)
147{
148	struct sigaction act;
149
150	memset(&act, 0, sizeof act);
151	sigemptyset(&act.sa_mask);
152
153	act.sa_handler = SIG_DFL;
154	if (sigaction(SIGPIPE, &act, NULL) != 0)
155		fatal("sigaction failed");
156	if (sigaction(SIGUSR1, &act, NULL) != 0)
157		fatal("sigaction failed");
158	if (sigaction(SIGUSR2, &act, NULL) != 0)
159		fatal("sigaction failed");
160	if (sigaction(SIGINT, &act, NULL) != 0)
161		fatal("sigaction failed");
162	if (sigaction(SIGTSTP, &act, NULL) != 0)
163		fatal("sigaction failed");
164	if (sigaction(SIGQUIT, &act, NULL) != 0)
165		fatal("sigaction failed");
166	if (sigaction(SIGWINCH, &act, NULL) != 0)
167		fatal("sigaction failed");
168	if (sigaction(SIGTERM, &act, NULL) != 0)
169		fatal("sigaction failed");
170	if (sigaction(SIGCHLD, &act, NULL) != 0)
171		fatal("sigaction failed");
172}
173
174const char *
175getshell(void)
176{
177	struct passwd	*pw;
178	const char	*shell;
179
180	shell = getenv("SHELL");
181	if (checkshell(shell))
182		return (shell);
183
184	pw = getpwuid(getuid());
185	if (pw != NULL && checkshell(pw->pw_shell))
186		return (pw->pw_shell);
187
188	return (_PATH_BSHELL);
189}
190
191int
192checkshell(const char *shell)
193{
194	if (shell == NULL || *shell == '\0' || areshell(shell))
195		return (0);
196	if (access(shell, X_OK) != 0)
197		return (0);
198	return (1);
199}
200
201int
202areshell(const char *shell)
203{
204	const char	*progname, *ptr;
205
206	if ((ptr = strrchr(shell, '/')) != NULL)
207		ptr++;
208	else
209		ptr = shell;
210	progname = __progname;
211	if (*progname == '-')
212		progname++;
213	if (strcmp(ptr, progname) == 0)
214		return (1);
215	return (0);
216}
217
218void
219fill_session(struct msg_command_data *data)
220{
221	char		*env, *ptr1, *ptr2, buf[256];
222	size_t		 len;
223	const char	*errstr;
224	long long	 ll;
225
226	data->pid = -1;
227	if ((env = getenv("TMUX")) == NULL)
228		return;
229
230	if ((ptr2 = strrchr(env, ',')) == NULL || ptr2 == env)
231		return;
232	for (ptr1 = ptr2 - 1; ptr1 > env && *ptr1 != ','; ptr1--)
233		;
234	if (*ptr1 != ',')
235		return;
236	ptr1++;
237	ptr2++;
238
239	len = ptr2 - ptr1 - 1;
240	if (len > (sizeof buf) - 1)
241		return;
242	memcpy(buf, ptr1, len);
243	buf[len] = '\0';
244
245	ll = strtonum(buf, 0, LONG_MAX, &errstr);
246	if (errstr != NULL)
247		return;
248	data->pid = ll;
249
250	ll = strtonum(ptr2, 0, UINT_MAX, &errstr);
251	if (errstr != NULL)
252		return;
253	data->idx = ll;
254}
255
256char *
257makesockpath(const char *label)
258{
259	char		base[MAXPATHLEN], *path;
260	struct stat	sb;
261	u_int		uid;
262
263	uid = getuid();
264	xsnprintf(base, MAXPATHLEN, "%s/tmux-%d", _PATH_TMP, uid);
265
266	if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST)
267		return (NULL);
268
269	if (lstat(base, &sb) != 0)
270		return (NULL);
271	if (!S_ISDIR(sb.st_mode)) {
272		errno = ENOTDIR;
273		return (NULL);
274	}
275	if (sb.st_uid != uid || (sb.st_mode & (S_IRWXG|S_IRWXO)) != 0) {
276		errno = EACCES;
277		return (NULL);
278	}
279
280	xasprintf(&path, "%s/%s", base, label);
281	return (path);
282}
283
284int
285main(int argc, char **argv)
286{
287	struct cmd_list		*cmdlist;
288 	struct cmd		*cmd;
289	struct pollfd		 pfd;
290	enum msgtype		 msg;
291	struct passwd		*pw;
292	struct options		*so, *wo;
293	struct keylist		*keylist;
294	struct imsgbuf		*ibuf;
295	struct msg_command_data	 cmddata;
296	char			*s, *shellcmd, *path, *label, *home, *cause;
297	char			 cwd[MAXPATHLEN], **var;
298	void			*buf;
299	size_t			 len;
300	int	 		 nfds, retcode, opt, flags, cmdflags = 0;
301
302	flags = 0;
303	shellcmd = label = path = NULL;
304	login_shell = (**argv == '-');
305	while ((opt = getopt(argc, argv, "28c:df:lL:qS:uUv")) != -1) {
306		switch (opt) {
307		case '2':
308			flags |= IDENTIFY_256COLOURS;
309			flags &= ~IDENTIFY_88COLOURS;
310			break;
311		case '8':
312			flags |= IDENTIFY_88COLOURS;
313			flags &= ~IDENTIFY_256COLOURS;
314			break;
315		case 'c':
316			if (shellcmd != NULL)
317				xfree(shellcmd);
318			shellcmd = xstrdup(optarg);
319			break;
320		case 'd':
321			flags |= IDENTIFY_HASDEFAULTS;
322			break;
323		case 'f':
324			if (cfg_file != NULL)
325				xfree(cfg_file);
326			cfg_file = xstrdup(optarg);
327			break;
328		case 'l':
329			login_shell = 1;
330			break;
331		case 'L':
332			if (label != NULL)
333				xfree(label);
334			label = xstrdup(optarg);
335			break;
336		case 'q':
337			be_quiet = 1;
338			break;
339		case 'S':
340			if (path != NULL)
341				xfree(path);
342			path = xstrdup(optarg);
343			break;
344		case 'u':
345			flags |= IDENTIFY_UTF8;
346			break;
347		case 'v':
348			debug_level++;
349			break;
350                default:
351			usage();
352                }
353        }
354	argc -= optind;
355	argv += optind;
356
357	if (shellcmd != NULL && argc != 0)
358		usage();
359
360	log_open_tty(debug_level);
361	siginit();
362
363	if (!(flags & IDENTIFY_UTF8)) {
364		/*
365		 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG
366		 * exist (in that order) to contain UTF-8, it is a safe
367		 * assumption that either they are using a UTF-8 terminal, or
368		 * if not they know that output from UTF-8-capable programs may
369		 * be wrong.
370		 */
371		if ((s = getenv("LC_ALL")) == NULL) {
372			if ((s = getenv("LC_CTYPE")) == NULL)
373				s = getenv("LANG");
374		}
375		if (s != NULL && (strcasestr(s, "UTF-8") != NULL ||
376		    strcasestr(s, "UTF8") != NULL))
377			flags |= IDENTIFY_UTF8;
378	}
379
380	environ_init(&global_environ);
381 	for (var = environ; *var != NULL; var++)
382		environ_put(&global_environ, *var);
383
384	options_init(&global_s_options, NULL);
385	so = &global_s_options;
386	options_set_number(so, "base-index", 0);
387	options_set_number(so, "bell-action", BELL_ANY);
388	options_set_number(so, "buffer-limit", 9);
389	options_set_string(so, "default-command", "%s", "");
390	options_set_string(so, "default-shell", "%s", getshell());
391	options_set_string(so, "default-terminal", "screen");
392	options_set_number(so, "display-panes-colour", 4);
393	options_set_number(so, "display-panes-time", 1000);
394	options_set_number(so, "display-time", 750);
395	options_set_number(so, "history-limit", 2000);
396	options_set_number(so, "lock-after-time", 0);
397	options_set_string(so, "lock-command", "lock -np");
398	options_set_number(so, "lock-server", 1);
399	options_set_number(so, "message-attr", 0);
400	options_set_number(so, "message-bg", 3);
401	options_set_number(so, "message-fg", 0);
402	options_set_number(so, "mouse-select-pane", 0);
403	options_set_number(so, "repeat-time", 500);
404	options_set_number(so, "set-remain-on-exit", 0);
405	options_set_number(so, "set-titles", 0);
406	options_set_string(so, "set-titles-string", "#S:#I:#W - \"#T\"");
407	options_set_number(so, "status", 1);
408	options_set_number(so, "status-attr", 0);
409	options_set_number(so, "status-bg", 2);
410	options_set_number(so, "status-fg", 0);
411	options_set_number(so, "status-interval", 15);
412	options_set_number(so, "status-justify", 0);
413	options_set_number(so, "status-keys", MODEKEY_EMACS);
414	options_set_string(so, "status-left", "[#S]");
415	options_set_number(so, "status-left-attr", 0);
416	options_set_number(so, "status-left-bg", 8);
417	options_set_number(so, "status-left-fg", 8);
418	options_set_number(so, "status-left-length", 10);
419	options_set_string(so, "status-right", "\"#22T\" %%H:%%M %%d-%%b-%%y");
420	options_set_number(so, "status-right-attr", 0);
421	options_set_number(so, "status-right-bg", 8);
422	options_set_number(so, "status-right-fg", 8);
423	options_set_number(so, "status-right-length", 40);
424	options_set_string(so, "terminal-overrides",
425	    "*88col*:colors=88,*256col*:colors=256");
426	options_set_string(so, "update-environment", "DISPLAY "
427	    "WINDOWID SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION");
428	options_set_number(so, "visual-activity", 0);
429	options_set_number(so, "visual-bell", 0);
430	options_set_number(so, "visual-content", 0);
431
432	keylist = xmalloc(sizeof *keylist);
433	ARRAY_INIT(keylist);
434	ARRAY_ADD(keylist, '\002');
435	options_set_data(so, "prefix", keylist, xfree);
436
437	options_init(&global_w_options, NULL);
438	wo = &global_w_options;
439	options_set_number(wo, "aggressive-resize", 0);
440	options_set_number(wo, "automatic-rename", 1);
441	options_set_number(wo, "clock-mode-colour", 4);
442	options_set_number(wo, "clock-mode-style", 1);
443	options_set_number(wo, "force-height", 0);
444	options_set_number(wo, "force-width", 0);
445	options_set_number(wo, "main-pane-height", 24);
446	options_set_number(wo, "main-pane-width", 81);
447	options_set_number(wo, "mode-attr", 0);
448	options_set_number(wo, "mode-bg", 3);
449	options_set_number(wo, "mode-fg", 0);
450	options_set_number(wo, "mode-keys", MODEKEY_EMACS);
451	options_set_number(wo, "mode-mouse", 0);
452	options_set_number(wo, "monitor-activity", 0);
453	options_set_string(wo, "monitor-content", "%s", "");
454	options_set_number(wo, "window-status-attr", 0);
455	options_set_number(wo, "window-status-bg", 8);
456	options_set_number(wo, "window-status-current-attr", 0);
457	options_set_number(wo, "window-status-current-bg", 8);
458	options_set_number(wo, "window-status-current-fg", 8);
459	options_set_number(wo, "window-status-fg", 8);
460	options_set_number(wo, "xterm-keys", 0);
461 	options_set_number(wo, "remain-on-exit", 0);
462	options_set_number(wo, "synchronize-panes", 0);
463
464 	if (flags & IDENTIFY_UTF8) {
465		options_set_number(so, "status-utf8", 1);
466		options_set_number(wo, "utf8", 1);
467	} else {
468		options_set_number(so, "status-utf8", 0);
469		options_set_number(wo, "utf8", 0);
470	}
471
472	if (getcwd(cwd, sizeof cwd) == NULL) {
473		pw = getpwuid(getuid());
474		if (pw->pw_dir != NULL && *pw->pw_dir != '\0')
475			strlcpy(cwd, pw->pw_dir, sizeof cwd);
476		else
477			strlcpy(cwd, "/", sizeof cwd);
478	}
479	options_set_string(so, "default-path", "%s", cwd);
480
481	if (cfg_file == NULL) {
482		home = getenv("HOME");
483		if (home == NULL || *home == '\0') {
484			pw = getpwuid(getuid());
485			if (pw != NULL)
486				home = pw->pw_dir;
487		}
488		xasprintf(&cfg_file, "%s/%s", home, DEFAULT_CFG);
489		if (access(cfg_file, R_OK) != 0) {
490			xfree(cfg_file);
491			cfg_file = NULL;
492		}
493	} else {
494		if (access(cfg_file, R_OK) != 0) {
495			log_warn("%s", cfg_file);
496			exit(1);
497		}
498	}
499
500	if (label == NULL)
501		label = xstrdup("default");
502	if (path == NULL && (path = makesockpath(label)) == NULL) {
503		log_warn("can't create socket");
504		exit(1);
505	}
506	xfree(label);
507
508	if (shellcmd != NULL) {
509		msg = MSG_SHELL;
510		buf = NULL;
511		len = 0;
512	} else {
513		fill_session(&cmddata);
514
515		cmddata.argc = argc;
516		if (cmd_pack_argv(
517		    argc, argv, cmddata.argv, sizeof cmddata.argv) != 0) {
518			log_warnx("command too long");
519			exit(1);
520		}
521
522		msg = MSG_COMMAND;
523		buf = &cmddata;
524		len = sizeof cmddata;
525	}
526
527	if (shellcmd != NULL)
528		cmdflags |= CMD_STARTSERVER;
529	else if (argc == 0)	/* new-session is the default */
530		cmdflags |= CMD_STARTSERVER|CMD_SENDENVIRON;
531	else {
532		/*
533		 * It sucks parsing the command string twice (in client and
534		 * later in server) but it is necessary to get the start server
535		 * flag.
536		 */
537		if ((cmdlist = cmd_list_parse(argc, argv, &cause)) == NULL) {
538			log_warnx("%s", cause);
539			exit(1);
540		}
541		cmdflags &= ~CMD_STARTSERVER;
542		TAILQ_FOREACH(cmd, cmdlist, qentry) {
543			if (cmd->entry->flags & CMD_STARTSERVER)
544				cmdflags |= CMD_STARTSERVER;
545			if (cmd->entry->flags & CMD_SENDENVIRON)
546				cmdflags |= CMD_SENDENVIRON;
547		}
548		cmd_list_free(cmdlist);
549	}
550
551	if ((ibuf = client_init(path, cmdflags, flags)) == NULL)
552		exit(1);
553	xfree(path);
554
555 	imsg_compose(ibuf, msg, PROTOCOL_VERSION, -1, -1, buf, len);
556
557	retcode = 0;
558	for (;;) {
559		pfd.fd = ibuf->fd;
560		pfd.events = POLLIN;
561		if (ibuf->w.queued != 0)
562			pfd.events |= POLLOUT;
563
564		if ((nfds = poll(&pfd, 1, INFTIM)) == -1) {
565			if (errno == EAGAIN || errno == EINTR)
566				continue;
567			fatal("poll failed");
568		}
569		if (nfds == 0)
570			continue;
571
572		if (pfd.revents & (POLLERR|POLLHUP|POLLNVAL))
573			fatalx("socket error");
574
575                if (pfd.revents & POLLIN) {
576			if (dispatch_imsg(ibuf, shellcmd, &retcode) != 0)
577				break;
578		}
579
580		if (pfd.revents & POLLOUT) {
581			if (msgbuf_write(&ibuf->w) < 0)
582				fatalx("msgbuf_write failed");
583		}
584	}
585
586	options_free(&global_s_options);
587	options_free(&global_w_options);
588
589	return (retcode);
590}
591
592int
593dispatch_imsg(struct imsgbuf *ibuf, const char *shellcmd, int *retcode)
594{
595	struct imsg		imsg;
596	ssize_t			n, datalen;
597	struct msg_print_data	printdata;
598	struct msg_shell_data	shelldata;
599
600        if ((n = imsg_read(ibuf)) == -1 || n == 0)
601		fatalx("imsg_read failed");
602
603	for (;;) {
604		if ((n = imsg_get(ibuf, &imsg)) == -1)
605			fatalx("imsg_get failed");
606		if (n == 0)
607			return (0);
608		datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
609
610		switch (imsg.hdr.type) {
611		case MSG_EXIT:
612		case MSG_SHUTDOWN:
613			if (datalen != 0)
614				fatalx("bad MSG_EXIT size");
615
616			return (-1);
617		case MSG_ERROR:
618			*retcode = 1;
619			/* FALLTHROUGH */
620		case MSG_PRINT:
621			if (datalen != sizeof printdata)
622				fatalx("bad MSG_PRINT size");
623			memcpy(&printdata, imsg.data, sizeof printdata);
624			printdata.msg[(sizeof printdata.msg) - 1] = '\0';
625
626			log_info("%s", printdata.msg);
627			break;
628		case MSG_READY:
629			if (datalen != 0)
630				fatalx("bad MSG_READY size");
631
632			client_main();	/* doesn't return */
633		case MSG_VERSION:
634			if (datalen != 0)
635				fatalx("bad MSG_VERSION size");
636
637			log_warnx("protocol version mismatch (client %u, "
638			    "server %u)", PROTOCOL_VERSION, imsg.hdr.peerid);
639			*retcode = 1;
640			return (-1);
641		case MSG_SHELL:
642			if (datalen != sizeof shelldata)
643				fatalx("bad MSG_SHELL size");
644			memcpy(&shelldata, imsg.data, sizeof shelldata);
645			shelldata.shell[(sizeof shelldata.shell) - 1] = '\0';
646
647			shell_exec(shelldata.shell, shellcmd);
648		default:
649			fatalx("unexpected message");
650		}
651
652		imsg_free(&imsg);
653	}
654}
655
656__dead void
657shell_exec(const char *shell, const char *shellcmd)
658{
659	const char	*shellname, *ptr;
660	char		*argv0;
661
662	sigreset();
663
664	ptr = strrchr(shell, '/');
665	if (ptr != NULL && *(ptr + 1) != '\0')
666		shellname = ptr + 1;
667	else
668		shellname = shell;
669	if (login_shell)
670		xasprintf(&argv0, "-%s", shellname);
671	else
672		xasprintf(&argv0, "%s", shellname);
673	setenv("SHELL", shell, 1);
674
675	execl(shell, argv0, "-c", shellcmd, (char *) NULL);
676	fatal("execl failed");
677}
678