Coverage Report

Created: 2026-10-05 06:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/tmux.c
Line
Count
Source
1
/* $OpenBSD: tmux.c,v 1.225 2026/09/25 13:44:49 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 <errno.h>
24
#include <fcntl.h>
25
#include <langinfo.h>
26
#include <locale.h>
27
#include <pwd.h>
28
#include <signal.h>
29
#include <stdlib.h>
30
#include <string.h>
31
#include <time.h>
32
#include <unistd.h>
33
34
#include "tmux.h"
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 timeval   start_time;
42
const char  *socket_path;
43
int    ptm_fd = -1;
44
const char  *shell_command;
45
46
static __dead void   usage(int);
47
static char   *make_label(const char *, char **);
48
49
static int     areshell(const char *);
50
static const char *getshell(void);
51
52
#ifdef ASAN
53
__attribute__((used)) const char *__asan_default_options(void);
54
__attribute__((used)) const char *
55
__asan_default_options(void)
56
{
57
  return (
58
      "abort_on_error=1:"
59
      "halt_on_error=1:"
60
      "detect_leaks=0:"
61
      "detect_stack_use_after_return=1:"
62
      "strict_string_checks=1:"
63
      "check_initialization_order=1:"
64
      "log_path=/tmp/tmux-asan:"
65
      "log_exe_name=1"
66
  );
67
}
68
#endif
69
70
static __dead void
71
usage(int status)
72
0
{
73
0
  fprintf(status ? stderr : stdout,
74
0
      "usage: %s [-2CDhlNuVv] [-c shell-command] [-f file] [-L socket-name]\n"
75
0
      "            [-S socket-path] [-T features] [command [flags]]\n",
76
0
      getprogname());
77
0
  exit(status);
78
0
}
79
80
static const char *
81
getshell(void)
82
0
{
83
0
  struct passwd *pw;
84
0
  const char  *shell;
85
86
0
  shell = getenv("SHELL");
87
0
  if (checkshell(shell))
88
0
    return (shell);
89
90
0
  pw = getpwuid(getuid());
91
0
  if (pw != NULL && checkshell(pw->pw_shell))
92
0
    return (pw->pw_shell);
93
94
0
  return (_PATH_BSHELL);
95
0
}
96
97
int
98
checkshell(const char *shell)
99
0
{
100
0
  if (shell == NULL || *shell != '/')
101
0
    return (0);
102
0
  if (areshell(shell))
103
0
    return (0);
104
0
  if (access(shell, X_OK) != 0)
105
0
    return (0);
106
0
  return (1);
107
0
}
108
109
static int
110
areshell(const char *shell)
111
0
{
112
0
  const char  *progname, *ptr;
113
114
0
  if ((ptr = strrchr(shell, '/')) != NULL)
115
0
    ptr++;
116
0
  else
117
0
    ptr = shell;
118
0
  progname = getprogname();
119
0
  if (*progname == '-')
120
0
    progname++;
121
0
  if (strcmp(ptr, progname) == 0)
122
0
    return (1);
123
0
  return (0);
124
0
}
125
126
static char *
127
expand_path(const char *path, const char *home)
128
0
{
129
0
  char      *expanded, *name;
130
0
  const char    *end;
131
0
  struct environ_entry  *value;
132
133
0
  if (strncmp(path, "~/", 2) == 0) {
134
0
    if (home == NULL)
135
0
      return (NULL);
136
0
    xasprintf(&expanded, "%s%s", home, path + 1);
137
0
    return (expanded);
138
0
  }
139
140
0
  if (*path == '$') {
141
0
    end = strchr(path, '/');
142
0
    if (end == NULL)
143
0
      name = xstrdup(path + 1);
144
0
    else
145
0
      name = xstrndup(path + 1, end - path - 1);
146
0
    value = environ_find(global_environ, name);
147
0
    free(name);
148
0
    if (value == NULL)
149
0
      return (NULL);
150
0
    if (end == NULL)
151
0
      end = "";
152
0
    xasprintf(&expanded, "%s%s", value->value, end);
153
0
    return (expanded);
154
0
  }
155
156
0
  return (xstrdup(path));
157
0
}
158
159
static void
160
expand_paths(const char *s, char ***paths, u_int *n, int no_realpath)
161
0
{
162
0
  const char  *home = find_home();
163
0
  char    *copy, *next, *tmp, resolved[PATH_MAX], *expanded;
164
0
  char    *path;
165
0
  u_int    i;
166
167
0
  *paths = NULL;
168
0
  *n = 0;
169
170
0
  copy = tmp = xstrdup(s);
171
0
  while ((next = strsep(&tmp, ":")) != NULL) {
172
0
    expanded = expand_path(next, home);
173
0
    if (expanded == NULL) {
174
0
      log_debug("%s: invalid path: %s", __func__, next);
175
0
      continue;
176
0
    }
177
0
    if (no_realpath)
178
0
      path = expanded;
179
0
    else {
180
0
      if (realpath(expanded, resolved) == NULL) {
181
0
        log_debug("%s: realpath(\"%s\") failed: %s",
182
0
            __func__, expanded, strerror(errno));
183
0
        free(expanded);
184
0
        continue;
185
0
      }
186
0
      path = xstrdup(resolved);
187
0
      free(expanded);
188
0
    }
189
0
    for (i = 0; i < *n; i++) {
190
0
      if (strcmp(path, (*paths)[i]) == 0)
191
0
        break;
192
0
    }
193
0
    if (i != *n) {
194
0
      log_debug("%s: duplicate path: %s", __func__, path);
195
0
      free(path);
196
0
      continue;
197
0
    }
198
0
    *paths = xreallocarray(*paths, (*n) + 1, sizeof *paths);
199
0
    (*paths)[(*n)++] = path;
200
0
  }
201
0
  free(copy);
202
0
}
203
204
static int
205
check_socket_path(const char *path, char **cause)
206
0
{
207
0
  char  *copy, *next, *tmp;
208
209
0
  if (*path != '/') {
210
0
    xasprintf(cause, "socket directory %s is not an absolute path",
211
0
        path);
212
0
    return (0);
213
0
  }
214
0
  copy = tmp = xstrdup(path);
215
0
  while ((next = strsep(&tmp, "/")) != NULL) {
216
0
    if (strcmp(next, "..") == 0) {
217
0
      xasprintf(cause, "socket directory %s contains ..",
218
0
          path);
219
0
      free(copy);
220
0
      return (0);
221
0
    }
222
0
  }
223
0
  free(copy);
224
0
  return (1);
225
0
}
226
227
static char *
228
make_label(const char *label, char **cause)
229
0
{
230
0
  char    **paths, *path = NULL, *base, resolved[PATH_MAX];
231
0
  u_int     i, n;
232
0
  struct stat   sb;
233
0
  uid_t     uid;
234
235
0
  *cause = NULL;
236
0
  if (label == NULL)
237
0
    label = "default";
238
0
  uid = getuid();
239
240
  /*
241
   * An unset variable has already been dropped and an empty one is
242
   * skipped, but anything else must be an existing absolute path with no
243
   * ".." or it is an error.
244
   */
245
0
  expand_paths(TMUX_SOCK, &paths, &n, 1);
246
0
  for (i = 0; i < n; i++) {
247
0
    if (*paths[i] == '\0')
248
0
      continue;
249
0
    if (!check_socket_path(paths[i], cause))
250
0
      break;
251
0
    if (realpath(paths[i], resolved) == NULL) {
252
0
      xasprintf(cause,
253
0
          "couldn't resolve socket directory %s (%s)",
254
0
          paths[i], strerror(errno));
255
0
      break;
256
0
    }
257
0
    path = xstrdup(resolved);
258
0
    break;
259
0
  }
260
0
  for (i = 0; i < n; i++)
261
0
    free(paths[i]);
262
0
  free(paths);
263
0
  if (path == NULL) {
264
0
    if (*cause == NULL)
265
0
      xasprintf(cause, "no suitable socket path");
266
0
    return (NULL);
267
0
  }
268
269
0
  xasprintf(&base, "%s/tmux-%ld", path, (long)uid);
270
0
  free(path);
271
0
  if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) {
272
0
    xasprintf(cause, "couldn't create directory %s (%s)", base,
273
0
        strerror(errno));
274
0
    goto fail;
275
0
  }
276
0
  if (lstat(base, &sb) != 0) {
277
0
    xasprintf(cause, "couldn't read directory %s (%s)", base,
278
0
        strerror(errno));
279
0
    goto fail;
280
0
  }
281
0
  if (!S_ISDIR(sb.st_mode)) {
282
0
    xasprintf(cause, "%s is not a directory", base);
283
0
    goto fail;
284
0
  }
285
0
  if (sb.st_uid != uid || (sb.st_mode & TMUX_SOCK_PERM) != 0) {
286
0
    xasprintf(cause, "directory %s has unsafe permissions", base);
287
0
    goto fail;
288
0
  }
289
0
  xasprintf(&path, "%s/%s", base, label);
290
0
  free(base);
291
0
  return (path);
292
293
0
fail:
294
0
  free(base);
295
0
  return (NULL);
296
0
}
297
298
char *
299
shell_argv0(const char *shell, int is_login)
300
0
{
301
0
  const char  *slash, *name;
302
0
  char    *argv0;
303
304
0
  slash = strrchr(shell, '/');
305
0
  if (slash != NULL && slash[1] != '\0')
306
0
    name = slash + 1;
307
0
  else
308
0
    name = shell;
309
0
  if (is_login)
310
0
    xasprintf(&argv0, "-%s", name);
311
0
  else
312
0
    xasprintf(&argv0, "%s", name);
313
0
  return (argv0);
314
0
}
315
316
void
317
setblocking(int fd, int state)
318
0
{
319
0
  int mode;
320
321
0
  if ((mode = fcntl(fd, F_GETFL)) != -1) {
322
0
    if (!state)
323
0
      mode |= O_NONBLOCK;
324
0
    else
325
0
      mode &= ~O_NONBLOCK;
326
0
    fcntl(fd, F_SETFL, mode);
327
0
  }
328
0
}
329
330
uint64_t
331
get_timer(void)
332
48.5k
{
333
48.5k
  struct timespec ts;
334
335
  /*
336
   * We want a timestamp in milliseconds suitable for time measurement,
337
   * so prefer the monotonic clock.
338
   */
339
48.5k
  if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
340
0
    clock_gettime(CLOCK_REALTIME, &ts);
341
48.5k
  return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL));
342
48.5k
}
343
344
char *
345
clean_name(const char *name, int untrusted)
346
14.8k
{
347
14.8k
  char  *copy, *cp, *new_name;
348
349
14.8k
  if (!utf8_isvalid(name))
350
404
    return (NULL);
351
14.4k
  copy = xstrdup(name);
352
169k
  for (cp = copy; *cp != '\0'; cp++) {
353
154k
    if (untrusted && cp[0] == '#' && cp[1] == '(')
354
197
      *cp = '_';
355
154k
  }
356
14.4k
  utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
357
14.4k
  free(copy);
358
14.4k
  return (new_name);
359
14.8k
}
360
361
int
362
check_name(const char *name)
363
0
{
364
0
  if (!utf8_isvalid(name))
365
0
    return (0);
366
0
  return (1);
367
0
}
368
369
const char *
370
sig2name(int signo)
371
0
{
372
0
     static char  s[11];
373
374
#ifdef HAVE_SYS_SIGNAME
375
     if (signo > 0 && signo < NSIG)
376
       return (sys_signame[signo]);
377
#endif
378
0
     xsnprintf(s, sizeof s, "%d", signo);
379
0
     return (s);
380
0
}
381
382
const char *
383
find_cwd(void)
384
0
{
385
0
  char     resolved1[PATH_MAX], resolved2[PATH_MAX];
386
0
  static char  cwd[PATH_MAX];
387
0
  const char  *pwd;
388
389
0
  if (getcwd(cwd, sizeof cwd) == NULL)
390
0
    return (NULL);
391
0
  if ((pwd = getenv("PWD")) == NULL || *pwd == '\0')
392
0
    return (cwd);
393
394
  /*
395
   * We want to use PWD so that symbolic links are maintained,
396
   * but only if it matches the actual working directory.
397
   */
398
0
  if (realpath(pwd, resolved1) == NULL)
399
0
    return (cwd);
400
0
  if (realpath(cwd, resolved2) == NULL)
401
0
    return (cwd);
402
0
  if (strcmp(resolved1, resolved2) != 0)
403
0
    return (cwd);
404
0
  return (pwd);
405
0
}
406
407
const char *
408
find_home(void)
409
0
{
410
0
  struct passwd   *pw;
411
0
  static const char *home;
412
413
0
  if (home != NULL)
414
0
    return (home);
415
416
0
  home = getenv("HOME");
417
0
  if (home == NULL || *home == '\0') {
418
0
    pw = getpwuid(getuid());
419
0
    if (pw != NULL)
420
0
      home = xstrdup(pw->pw_dir);
421
0
    else
422
0
      home = NULL;
423
0
  }
424
425
0
  return (home);
426
0
}
427
428
const char *
429
getversion(void)
430
266
{
431
266
  return (TMUX_VERSION);
432
266
}
433
434
int
435
main(int argc, char **argv)
436
0
{
437
0
  char          *path = NULL, *label = NULL;
438
0
  char          *cause, **var;
439
0
  const char        *s, *cwd;
440
0
  int          opt, keys, feat = 0, fflag = 0;
441
0
  uint64_t         flags = 0;
442
0
  const struct options_table_entry  *oe;
443
0
  u_int          i;
444
445
0
  if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL &&
446
0
      setlocale(LC_CTYPE, "C.UTF-8") == NULL) {
447
0
    if (setlocale(LC_CTYPE, "") == NULL)
448
0
      errx(1, "invalid LC_ALL, LC_CTYPE or LANG");
449
0
    s = nl_langinfo(CODESET);
450
0
    if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0)
451
0
      errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s);
452
0
  }
453
454
0
  setlocale(LC_TIME, "");
455
0
  tzset();
456
457
0
  if (**argv == '-')
458
0
    flags = CLIENT_LOGIN;
459
460
0
  global_environ = environ_create();
461
0
  for (var = environ; *var != NULL; var++)
462
0
    environ_put(global_environ, *var, 0);
463
0
  if ((cwd = find_cwd()) != NULL)
464
0
    environ_set(global_environ, "PWD", 0, "%s", cwd);
465
0
  expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1);
466
467
0
  while ((opt = getopt(argc, argv, "2c:CDdf:hlL:NqS:T:uUvV")) != -1) {
468
0
    switch (opt) {
469
0
    case '2':
470
0
      tty_parse_features("256", ":,", &feat, NULL);
471
0
      break;
472
0
    case 'c':
473
0
      shell_command = optarg;
474
0
      break;
475
0
    case 'D':
476
0
      flags |= CLIENT_NOFORK;
477
0
      break;
478
0
    case 'C':
479
0
      if (flags & CLIENT_CONTROL)
480
0
        flags |= CLIENT_CONTROLCONTROL;
481
0
      else
482
0
        flags |= CLIENT_CONTROL;
483
0
      break;
484
0
    case 'f':
485
0
      if (!fflag) {
486
0
        fflag = 1;
487
0
        for (i = 0; i < cfg_nfiles; i++)
488
0
          free(cfg_files[i]);
489
0
        cfg_nfiles = 0;
490
0
      }
491
0
      cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1,
492
0
          sizeof *cfg_files);
493
0
      cfg_files[cfg_nfiles++] = xstrdup(optarg);
494
0
      cfg_quiet = 0;
495
0
      break;
496
0
    case 'h':
497
0
      usage(0);
498
0
    case 'V':
499
0
      printf("tmux %s\n", getversion());
500
0
      exit(0);
501
0
    case 'l':
502
0
      flags |= CLIENT_LOGIN;
503
0
      break;
504
0
    case 'L':
505
0
      free(label);
506
0
      label = xstrdup(optarg);
507
0
      break;
508
0
    case 'N':
509
0
      flags |= CLIENT_NOSTARTSERVER;
510
0
      break;
511
0
    case 'q':
512
0
      break;
513
0
    case 'S':
514
0
      free(path);
515
0
      path = xstrdup(optarg);
516
0
      break;
517
0
    case 'T':
518
0
      tty_parse_features(optarg, ":,", &feat, NULL);
519
0
      break;
520
0
    case 'u':
521
0
      flags |= CLIENT_UTF8;
522
0
      break;
523
0
    case 'v':
524
0
      log_add_level();
525
0
      break;
526
0
    default:
527
0
      usage(1);
528
0
    }
529
0
  }
530
0
  argc -= optind;
531
0
  argv += optind;
532
533
0
  if (shell_command != NULL && argc != 0)
534
0
    usage(1);
535
0
  if ((flags & CLIENT_NOFORK) && argc != 0)
536
0
    usage(1);
537
538
0
  if ((ptm_fd = getptmfd()) == -1)
539
0
    err(1, "getptmfd");
540
0
  if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd "
541
0
      "recvfd proc exec tty ps", NULL) != 0)
542
0
    err(1, "pledge");
543
544
  /*
545
   * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8.
546
   * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain
547
   * UTF-8, it is a safe assumption that either they are using a UTF-8
548
   * terminal, or if not they know that output from UTF-8-capable
549
   * programs may be wrong.
550
   */
551
0
  if (getenv("TMUX") != NULL)
552
0
    flags |= CLIENT_UTF8;
553
0
  else {
554
0
    s = getenv("LC_ALL");
555
0
    if (s == NULL || *s == '\0')
556
0
      s = getenv("LC_CTYPE");
557
0
    if (s == NULL || *s == '\0')
558
0
      s = getenv("LANG");
559
0
    if (s == NULL || *s == '\0')
560
0
      s = "";
561
0
    if (strcasestr(s, "UTF-8") != NULL ||
562
0
        strcasestr(s, "UTF8") != NULL)
563
0
      flags |= CLIENT_UTF8;
564
0
  }
565
566
0
  global_options = options_create(NULL);
567
0
  global_s_options = options_create(NULL);
568
0
  global_w_options = options_create(NULL);
569
0
  for (oe = options_table; oe->name != NULL; oe++) {
570
0
    if (oe->scope & OPTIONS_TABLE_SERVER)
571
0
      options_default(global_options, oe);
572
0
    if (oe->scope & OPTIONS_TABLE_SESSION)
573
0
      options_default(global_s_options, oe);
574
0
    if (oe->scope & OPTIONS_TABLE_WINDOW)
575
0
      options_default(global_w_options, oe);
576
0
  }
577
578
  /*
579
   * The default shell comes from SHELL or from the user's passwd entry
580
   * if available.
581
   */
582
0
  options_set_string(global_s_options, "default-shell", 0, "%s",
583
0
      getshell());
584
585
  /* Override keys to vi if VISUAL or EDITOR are set. */
586
0
  if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
587
0
    options_set_string(global_options, "editor", 0, "%s", s);
588
0
    if (strrchr(s, '/') != NULL)
589
0
      s = strrchr(s, '/') + 1;
590
0
    if (strstr(s, "vi") != NULL)
591
0
      keys = MODEKEY_VI;
592
0
    else
593
0
      keys = MODEKEY_EMACS;
594
0
    options_set_number(global_s_options, "status-keys", keys);
595
0
    options_set_number(global_w_options, "mode-keys", keys);
596
0
  }
597
598
  /*
599
   * If socket is specified on the command-line with -S or -L, it is
600
   * used. Otherwise, $TMUX is checked and if that fails "default" is
601
   * used.
602
   */
603
0
  if (path == NULL && label == NULL) {
604
0
    s = getenv("TMUX");
605
0
    if (s != NULL && *s != '\0' && *s != ',') {
606
0
      path = xstrdup(s);
607
0
      path[strcspn(path, ",")] = '\0';
608
0
    }
609
0
  }
610
0
  if (path == NULL) {
611
0
    if ((path = make_label(label, &cause)) == NULL) {
612
0
      if (cause != NULL) {
613
0
        fprintf(stderr, "%s\n", cause);
614
0
        free(cause);
615
0
      }
616
0
      exit(1);
617
0
    }
618
0
    flags |= CLIENT_DEFAULTSOCKET;
619
0
  }
620
0
  socket_path = path;
621
0
  free(label);
622
623
  /* Pass control to the client. */
624
0
  exit(client_main(osdep_event_init(), argc, argv, flags, feat));
625
0
}