1 /* $Id: tmux.c,v 1.2 2012/05/31 19:14:56 martin 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 <event.h> 24 #include <fcntl.h> 25 #include <pwd.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 30 #include "tmux.h" 31 32 #if defined(DEBUG) && defined(__OpenBSD__) 33 extern char *malloc_options; 34 #endif 35 36 struct options global_options; /* server options */ 37 struct options global_s_options; /* session options */ 38 struct options global_w_options; /* window options */ 39 struct environ global_environ; 40 41 struct event_base *ev_base; 42 43 char *cfg_file; 44 char *shell_cmd; 45 int debug_level; 46 time_t start_time; 47 char socket_path[MAXPATHLEN]; 48 int login_shell; 49 char *environ_path; 50 pid_t environ_pid = -1; 51 int environ_idx = -1; 52 53 __dead void usage(void); 54 void parseenvironment(void); 55 char *makesocketpath(const char *); 56 57 #ifndef HAVE___PROGNAME 58 char *__progname = (char *) "tmux"; 59 #endif 60 61 __dead void 62 usage(void) 63 { 64 fprintf(stderr, 65 "usage: %s [-28lquvV] [-c shell-command] [-f file] [-L socket-name]\n" 66 " [-S socket-path] [command [flags]]\n", 67 __progname); 68 exit(1); 69 } 70 71 void 72 logfile(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 84 const char * 85 getshell(void) 86 { 87 struct passwd *pw; 88 const char *shell; 89 90 shell = getenv("SHELL"); 91 if (checkshell(shell)) 92 return (shell); 93 94 pw = getpwuid(getuid()); 95 if (pw != NULL && checkshell(pw->pw_shell)) 96 return (pw->pw_shell); 97 98 return (_PATH_BSHELL); 99 } 100 101 int 102 checkshell(const char *shell) 103 { 104 if (shell == NULL || *shell == '\0' || areshell(shell)) 105 return (0); 106 if (access(shell, X_OK) != 0) 107 return (0); 108 return (1); 109 } 110 111 int 112 areshell(const char *shell) 113 { 114 const char *progname, *ptr; 115 116 if ((ptr = strrchr(shell, '/')) != NULL) 117 ptr++; 118 else 119 ptr = shell; 120 progname = __progname; 121 if (*progname == '-') 122 progname++; 123 if (strcmp(ptr, progname) == 0) 124 return (1); 125 return (0); 126 } 127 128 void 129 parseenvironment(void) 130 { 131 char *env, path[256]; 132 long pid; 133 int idx; 134 135 if ((env = getenv("TMUX")) == NULL) 136 return; 137 138 if (sscanf(env, "%255[^,],%ld,%d", path, &pid, &idx) != 3) 139 return; 140 environ_path = xstrdup(path); 141 environ_pid = pid; 142 environ_idx = idx; 143 } 144 145 char * 146 makesocketpath(const char *label) 147 { 148 char base[MAXPATHLEN], *path, *s; 149 struct stat sb; 150 u_int uid; 151 152 uid = getuid(); 153 if ((s = getenv("TMPDIR")) == NULL || *s == '\0') 154 xsnprintf(base, sizeof base, "%s/tmux-%u", _PATH_TMP, uid); 155 else 156 xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid); 157 158 if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) 159 return (NULL); 160 161 if (lstat(base, &sb) != 0) 162 return (NULL); 163 if (!S_ISDIR(sb.st_mode)) { 164 errno = ENOTDIR; 165 return (NULL); 166 } 167 if (sb.st_uid != uid || (sb.st_mode & (S_IRWXG|S_IRWXO)) != 0) { 168 errno = EACCES; 169 return (NULL); 170 } 171 172 xasprintf(&path, "%s/%s", base, label); 173 return (path); 174 } 175 176 void 177 setblocking(int fd, int state) 178 { 179 int mode; 180 181 if ((mode = fcntl(fd, F_GETFL)) != -1) { 182 if (!state) 183 mode |= O_NONBLOCK; 184 else 185 mode &= ~O_NONBLOCK; 186 fcntl(fd, F_SETFL, mode); 187 } 188 } 189 190 __dead void 191 shell_exec(const char *shell, const char *shellcmd) 192 { 193 const char *shellname, *ptr; 194 char *argv0; 195 196 ptr = strrchr(shell, '/'); 197 if (ptr != NULL && *(ptr + 1) != '\0') 198 shellname = ptr + 1; 199 else 200 shellname = shell; 201 if (login_shell) 202 xasprintf(&argv0, "-%s", shellname); 203 else 204 xasprintf(&argv0, "%s", shellname); 205 setenv("SHELL", shell, 1); 206 207 setblocking(STDIN_FILENO, 1); 208 setblocking(STDOUT_FILENO, 1); 209 setblocking(STDERR_FILENO, 1); 210 closefrom(STDERR_FILENO + 1); 211 212 execl(shell, argv0, "-c", shellcmd, (char *) NULL); 213 fatal("execl failed"); 214 } 215 216 static void 217 init_std_fds(void) 218 { 219 int fd; 220 221 /* 222 * Make sure the standard file descriptors are populated, so we 223 * don't end up forwarding (for example) the event descriptor 224 * instead of stdin to the server. 225 */ 226 227 while ((fd = open("/dev/null", O_RDWR)) <= 2) 228 ; 229 close(fd); 230 } 231 232 int 233 main(int argc, char **argv) 234 { 235 struct passwd *pw; 236 struct keylist *keylist; 237 char *s, *path, *label, *home, **var; 238 int opt, flags, quiet, keys; 239 240 #if defined(DEBUG) && defined(__OpenBSD__) 241 malloc_options = (char *) "AFGJPX"; 242 #endif 243 244 quiet = flags = 0; 245 label = path = NULL; 246 login_shell = (**argv == '-'); 247 while ((opt = getopt(argc, argv, "28c:df:lL:qS:uUvV")) != -1) { 248 switch (opt) { 249 case '2': 250 flags |= IDENTIFY_256COLOURS; 251 flags &= ~IDENTIFY_88COLOURS; 252 break; 253 case '8': 254 flags |= IDENTIFY_88COLOURS; 255 flags &= ~IDENTIFY_256COLOURS; 256 break; 257 case 'c': 258 if (shell_cmd != NULL) 259 xfree(shell_cmd); 260 shell_cmd = xstrdup(optarg); 261 break; 262 case 'V': 263 printf("%s %s\n", __progname, VERSION); 264 exit(0); 265 case 'f': 266 if (cfg_file != NULL) 267 xfree(cfg_file); 268 cfg_file = xstrdup(optarg); 269 break; 270 case 'l': 271 login_shell = 1; 272 break; 273 case 'L': 274 if (label != NULL) 275 xfree(label); 276 label = xstrdup(optarg); 277 break; 278 case 'q': 279 quiet = 1; 280 break; 281 case 'S': 282 if (path != NULL) 283 xfree(path); 284 path = xstrdup(optarg); 285 break; 286 case 'u': 287 flags |= IDENTIFY_UTF8; 288 break; 289 case 'v': 290 debug_level++; 291 break; 292 default: 293 usage(); 294 } 295 } 296 argc -= optind; 297 argv += optind; 298 299 if (shell_cmd != NULL && argc != 0) 300 usage(); 301 302 init_std_fds(); 303 log_open_tty(debug_level); 304 305 if (!(flags & IDENTIFY_UTF8)) { 306 /* 307 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG 308 * exist (in that order) to contain UTF-8, it is a safe 309 * assumption that either they are using a UTF-8 terminal, or 310 * if not they know that output from UTF-8-capable programs may 311 * be wrong. 312 */ 313 if ((s = getenv("LC_ALL")) == NULL) { 314 if ((s = getenv("LC_CTYPE")) == NULL) 315 s = getenv("LANG"); 316 } 317 if (s != NULL && (strcasestr(s, "UTF-8") != NULL || 318 strcasestr(s, "UTF8") != NULL)) 319 flags |= IDENTIFY_UTF8; 320 } 321 322 environ_init(&global_environ); 323 for (var = environ; *var != NULL; var++) 324 environ_put(&global_environ, *var); 325 326 options_init(&global_options, NULL); 327 options_table_populate_tree(server_options_table, &global_options); 328 options_set_number(&global_options, "quiet", quiet); 329 330 options_init(&global_s_options, NULL); 331 options_table_populate_tree(session_options_table, &global_s_options); 332 options_set_string(&global_s_options, "default-shell", "%s", getshell()); 333 334 options_init(&global_w_options, NULL); 335 options_table_populate_tree(window_options_table, &global_w_options); 336 337 /* Set the prefix option (its a list, so not in the table). */ 338 keylist = xmalloc(sizeof *keylist); 339 ARRAY_INIT(keylist); 340 ARRAY_ADD(keylist, '\002'); 341 options_set_data(&global_s_options, "prefix", keylist, xfree); 342 343 /* Enable UTF-8 if the first client is on UTF-8 terminal. */ 344 if (flags & IDENTIFY_UTF8) { 345 options_set_number(&global_s_options, "status-utf8", 1); 346 options_set_number(&global_s_options, "mouse-utf8", 1); 347 options_set_number(&global_w_options, "utf8", 1); 348 } 349 350 /* Override keys to vi if VISUAL or EDITOR are set. */ 351 if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) { 352 if (strrchr(s, '/') != NULL) 353 s = strrchr(s, '/') + 1; 354 if (strstr(s, "vi") != NULL) 355 keys = MODEKEY_VI; 356 else 357 keys = MODEKEY_EMACS; 358 options_set_number(&global_s_options, "status-keys", keys); 359 options_set_number(&global_w_options, "mode-keys", keys); 360 } 361 362 /* Locate the configuration file. */ 363 if (cfg_file == NULL) { 364 home = getenv("HOME"); 365 if (home == NULL || *home == '\0') { 366 pw = getpwuid(getuid()); 367 if (pw != NULL) 368 home = pw->pw_dir; 369 } 370 xasprintf(&cfg_file, "%s/%s", home, DEFAULT_CFG); 371 if (access(cfg_file, R_OK) != 0 && errno == ENOENT) { 372 xfree(cfg_file); 373 cfg_file = NULL; 374 } 375 } 376 377 /* 378 * Figure out the socket path. If specified on the command-line with -S 379 * or -L, use it, otherwise try $TMUX or assume -L default. 380 */ 381 parseenvironment(); 382 if (path == NULL) { 383 /* If no -L, use the environment. */ 384 if (label == NULL) { 385 if (environ_path != NULL) 386 path = xstrdup(environ_path); 387 else 388 label = xstrdup("default"); 389 } 390 391 /* -L or default set. */ 392 if (label != NULL) { 393 if ((path = makesocketpath(label)) == NULL) { 394 log_warn("can't create socket"); 395 exit(1); 396 } 397 } 398 } 399 if (label != NULL) 400 xfree(label); 401 if (realpath(path, socket_path) == NULL) 402 strlcpy(socket_path, path, sizeof socket_path); 403 xfree(path); 404 405 #ifdef HAVE_SETPROCTITLE 406 /* Set process title. */ 407 setproctitle("%s (%s)", __progname, socket_path); 408 #endif 409 410 /* Pass control to the client. */ 411 ev_base = osdep_event_init(); 412 exit(client_main(argc, argv, flags)); 413 } 414