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