tmux.c revision 1.196
1/* $OpenBSD: tmux.c,v 1.196 2020/04/09 15:35:27 nicm Exp $ */ 2 3/* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 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#include <sys/utsname.h> 22 23#include <err.h> 24#include <errno.h> 25#include <event.h> 26#include <fcntl.h> 27#include <langinfo.h> 28#include <locale.h> 29#include <paths.h> 30#include <pwd.h> 31#include <stdlib.h> 32#include <string.h> 33#include <time.h> 34#include <unistd.h> 35#include <util.h> 36 37#include "tmux.h" 38 39struct options *global_options; /* server options */ 40struct options *global_s_options; /* session options */ 41struct options *global_w_options; /* window options */ 42struct environ *global_environ; 43 44struct timeval start_time; 45const char *socket_path; 46int ptm_fd = -1; 47const char *shell_command; 48 49static __dead void usage(void); 50static char *make_label(const char *, char **); 51 52static int areshell(const char *); 53static const char *getshell(void); 54 55static __dead void 56usage(void) 57{ 58 fprintf(stderr, 59 "usage: %s [-2CluvV] [-c shell-command] [-f file] [-L socket-name]\n" 60 " [-S socket-path] [command [flags]]\n", 61 getprogname()); 62 exit(1); 63} 64 65static const char * 66getshell(void) 67{ 68 struct passwd *pw; 69 const char *shell; 70 71 shell = getenv("SHELL"); 72 if (checkshell(shell)) 73 return (shell); 74 75 pw = getpwuid(getuid()); 76 if (pw != NULL && checkshell(pw->pw_shell)) 77 return (pw->pw_shell); 78 79 return (_PATH_BSHELL); 80} 81 82int 83checkshell(const char *shell) 84{ 85 if (shell == NULL || *shell != '/') 86 return (0); 87 if (areshell(shell)) 88 return (0); 89 if (access(shell, X_OK) != 0) 90 return (0); 91 return (1); 92} 93 94static int 95areshell(const char *shell) 96{ 97 const char *progname, *ptr; 98 99 if ((ptr = strrchr(shell, '/')) != NULL) 100 ptr++; 101 else 102 ptr = shell; 103 progname = getprogname(); 104 if (*progname == '-') 105 progname++; 106 if (strcmp(ptr, progname) == 0) 107 return (1); 108 return (0); 109} 110 111static char * 112make_label(const char *label, char **cause) 113{ 114 char *base, resolved[PATH_MAX], *path, *s; 115 struct stat sb; 116 uid_t uid; 117 118 *cause = NULL; 119 120 if (label == NULL) 121 label = "default"; 122 uid = getuid(); 123 124 if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0') 125 xasprintf(&base, "%s/tmux-%ld", s, (long)uid); 126 else 127 xasprintf(&base, "%s/tmux-%ld", _PATH_TMP, (long)uid); 128 if (realpath(base, resolved) == NULL && 129 strlcpy(resolved, base, sizeof resolved) >= sizeof resolved) { 130 errno = ERANGE; 131 free(base); 132 goto fail; 133 } 134 free(base); 135 136 if (mkdir(resolved, S_IRWXU) != 0 && errno != EEXIST) 137 goto fail; 138 if (lstat(resolved, &sb) != 0) 139 goto fail; 140 if (!S_ISDIR(sb.st_mode)) { 141 errno = ENOTDIR; 142 goto fail; 143 } 144 if (sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0) { 145 errno = EACCES; 146 goto fail; 147 } 148 xasprintf(&path, "%s/%s", resolved, label); 149 return (path); 150 151fail: 152 xasprintf(cause, "error creating %s (%s)", resolved, strerror(errno)); 153 return (NULL); 154} 155 156void 157setblocking(int fd, int state) 158{ 159 int mode; 160 161 if ((mode = fcntl(fd, F_GETFL)) != -1) { 162 if (!state) 163 mode |= O_NONBLOCK; 164 else 165 mode &= ~O_NONBLOCK; 166 fcntl(fd, F_SETFL, mode); 167 } 168} 169 170const char * 171find_cwd(void) 172{ 173 char resolved1[PATH_MAX], resolved2[PATH_MAX]; 174 static char cwd[PATH_MAX]; 175 const char *pwd; 176 177 if (getcwd(cwd, sizeof cwd) == NULL) 178 return (NULL); 179 if ((pwd = getenv("PWD")) == NULL || *pwd == '\0') 180 return (cwd); 181 182 /* 183 * We want to use PWD so that symbolic links are maintained, 184 * but only if it matches the actual working directory. 185 */ 186 if (realpath(pwd, resolved1) == NULL) 187 return (cwd); 188 if (realpath(cwd, resolved2) == NULL) 189 return (cwd); 190 if (strcmp(resolved1, resolved2) != 0) 191 return (cwd); 192 return (pwd); 193} 194 195const char * 196find_home(void) 197{ 198 struct passwd *pw; 199 static const char *home; 200 201 if (home != NULL) 202 return (home); 203 204 home = getenv("HOME"); 205 if (home == NULL || *home == '\0') { 206 pw = getpwuid(getuid()); 207 if (pw != NULL) 208 home = pw->pw_dir; 209 else 210 home = NULL; 211 } 212 213 return (home); 214} 215 216const char * 217getversion(void) 218{ 219 static char *version; 220 struct utsname u; 221 222 if (version == NULL) { 223 if (uname(&u) < 0) 224 fatalx("uname failed"); 225 xasprintf(&version, "openbsd-%s", u.release); 226 } 227 return (version); 228} 229 230int 231main(int argc, char **argv) 232{ 233 char *path, *label, *cause, **var; 234 const char *s, *shell, *cwd; 235 int opt, flags, keys; 236 const struct options_table_entry *oe; 237 238 if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL && 239 setlocale(LC_CTYPE, "C.UTF-8") == NULL) { 240 if (setlocale(LC_CTYPE, "") == NULL) 241 errx(1, "invalid LC_ALL, LC_CTYPE or LANG"); 242 s = nl_langinfo(CODESET); 243 if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0) 244 errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s); 245 } 246 247 setlocale(LC_TIME, ""); 248 tzset(); 249 250 if (**argv == '-') 251 flags = CLIENT_LOGIN; 252 else 253 flags = 0; 254 255 label = path = NULL; 256 while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUvV")) != -1) { 257 switch (opt) { 258 case '2': 259 flags |= CLIENT_256COLOURS; 260 break; 261 case 'c': 262 shell_command = optarg; 263 break; 264 case 'C': 265 if (flags & CLIENT_CONTROL) 266 flags |= CLIENT_CONTROLCONTROL; 267 else 268 flags |= CLIENT_CONTROL; 269 break; 270 case 'f': 271 set_cfg_file(optarg); 272 break; 273 case 'V': 274 printf("%s %s\n", getprogname(), getversion()); 275 exit(0); 276 case 'l': 277 flags |= CLIENT_LOGIN; 278 break; 279 case 'L': 280 free(label); 281 label = xstrdup(optarg); 282 break; 283 case 'q': 284 break; 285 case 'S': 286 free(path); 287 path = xstrdup(optarg); 288 break; 289 case 'u': 290 flags |= CLIENT_UTF8; 291 break; 292 case 'v': 293 log_add_level(); 294 break; 295 default: 296 usage(); 297 } 298 } 299 argc -= optind; 300 argv += optind; 301 302 if (shell_command != NULL && argc != 0) 303 usage(); 304 305 if ((ptm_fd = getptmfd()) == -1) 306 err(1, "getptmfd"); 307 if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd " 308 "recvfd proc exec tty ps", NULL) != 0) 309 err(1, "pledge"); 310 311 /* 312 * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8. 313 * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain 314 * UTF-8, it is a safe assumption that either they are using a UTF-8 315 * terminal, or if not they know that output from UTF-8-capable 316 * programs may be wrong. 317 */ 318 if (getenv("TMUX") != NULL) 319 flags |= CLIENT_UTF8; 320 else { 321 s = getenv("LC_ALL"); 322 if (s == NULL || *s == '\0') 323 s = getenv("LC_CTYPE"); 324 if (s == NULL || *s == '\0') 325 s = getenv("LANG"); 326 if (s == NULL || *s == '\0') 327 s = ""; 328 if (strcasestr(s, "UTF-8") != NULL || 329 strcasestr(s, "UTF8") != NULL) 330 flags |= CLIENT_UTF8; 331 } 332 333 global_environ = environ_create(); 334 for (var = environ; *var != NULL; var++) 335 environ_put(global_environ, *var, 0); 336 if ((cwd = find_cwd()) != NULL) 337 environ_set(global_environ, "PWD", 0, "%s", cwd); 338 339 global_options = options_create(NULL); 340 global_s_options = options_create(NULL); 341 global_w_options = options_create(NULL); 342 for (oe = options_table; oe->name != NULL; oe++) { 343 if (oe->scope & OPTIONS_TABLE_SERVER) 344 options_default(global_options, oe); 345 if (oe->scope & OPTIONS_TABLE_SESSION) 346 options_default(global_s_options, oe); 347 if (oe->scope & OPTIONS_TABLE_WINDOW) 348 options_default(global_w_options, oe); 349 } 350 351 /* 352 * The default shell comes from SHELL or from the user's passwd entry 353 * if available. 354 */ 355 shell = getshell(); 356 options_set_string(global_s_options, "default-shell", 0, "%s", shell); 357 358 /* Override keys to vi if VISUAL or EDITOR are set. */ 359 if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) { 360 if (strrchr(s, '/') != NULL) 361 s = strrchr(s, '/') + 1; 362 if (strstr(s, "vi") != NULL) 363 keys = MODEKEY_VI; 364 else 365 keys = MODEKEY_EMACS; 366 options_set_number(global_s_options, "status-keys", keys); 367 options_set_number(global_w_options, "mode-keys", keys); 368 } 369 370 /* 371 * If socket is specified on the command-line with -S or -L, it is 372 * used. Otherwise, $TMUX is checked and if that fails "default" is 373 * used. 374 */ 375 if (path == NULL && label == NULL) { 376 s = getenv("TMUX"); 377 if (s != NULL && *s != '\0' && *s != ',') { 378 path = xstrdup(s); 379 path[strcspn(path, ",")] = '\0'; 380 } 381 } 382 if (path == NULL) { 383 if ((path = make_label(label, &cause)) == NULL) { 384 if (cause != NULL) { 385 fprintf(stderr, "%s\n", cause); 386 free(cause); 387 } 388 exit(1); 389 } 390 flags |= CLIENT_DEFAULTSOCKET; 391 } 392 socket_path = path; 393 free(label); 394 395 /* Pass control to the client. */ 396 exit(client_main(event_init(), argc, argv, flags)); 397} 398