Coverage Report

Created: 2026-07-16 06:53

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.221 2026/06/29 18:17:28 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", __func__,
182
0
        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 char *
205
make_label(const char *label, char **cause)
206
0
{
207
0
  char    **paths, *path, *base;
208
0
  u_int     i, n;
209
0
  struct stat   sb;
210
0
  uid_t     uid;
211
212
0
  *cause = NULL;
213
0
  if (label == NULL)
214
0
    label = "default";
215
0
  uid = getuid();
216
217
0
  expand_paths(TMUX_SOCK, &paths, &n, 0);
218
0
  if (n == 0) {
219
0
    xasprintf(cause, "no suitable socket path");
220
0
    return (NULL);
221
0
  }
222
0
  path = paths[0]; /* can only have one socket! */
223
0
  for (i = 1; i < n; i++)
224
0
    free(paths[i]);
225
0
  free(paths);
226
227
0
  xasprintf(&base, "%s/tmux-%ld", path, (long)uid);
228
0
  free(path);
229
0
  if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) {
230
0
    xasprintf(cause, "couldn't create directory %s (%s)", base,
231
0
        strerror(errno));
232
0
    goto fail;
233
0
  }
234
0
  if (lstat(base, &sb) != 0) {
235
0
    xasprintf(cause, "couldn't read directory %s (%s)", base,
236
0
        strerror(errno));
237
0
    goto fail;
238
0
  }
239
0
  if (!S_ISDIR(sb.st_mode)) {
240
0
    xasprintf(cause, "%s is not a directory", base);
241
0
    goto fail;
242
0
  }
243
0
  if (sb.st_uid != uid || (sb.st_mode & TMUX_SOCK_PERM) != 0) {
244
0
    xasprintf(cause, "directory %s has unsafe permissions", base);
245
0
    goto fail;
246
0
  }
247
0
  xasprintf(&path, "%s/%s", base, label);
248
0
  free(base);
249
0
  return (path);
250
251
0
fail:
252
0
  free(base);
253
0
  return (NULL);
254
0
}
255
256
char *
257
shell_argv0(const char *shell, int is_login)
258
0
{
259
0
  const char  *slash, *name;
260
0
  char    *argv0;
261
262
0
  slash = strrchr(shell, '/');
263
0
  if (slash != NULL && slash[1] != '\0')
264
0
    name = slash + 1;
265
0
  else
266
0
    name = shell;
267
0
  if (is_login)
268
0
    xasprintf(&argv0, "-%s", name);
269
0
  else
270
0
    xasprintf(&argv0, "%s", name);
271
0
  return (argv0);
272
0
}
273
274
void
275
setblocking(int fd, int state)
276
0
{
277
0
  int mode;
278
279
0
  if ((mode = fcntl(fd, F_GETFL)) != -1) {
280
0
    if (!state)
281
0
      mode |= O_NONBLOCK;
282
0
    else
283
0
      mode &= ~O_NONBLOCK;
284
0
    fcntl(fd, F_SETFL, mode);
285
0
  }
286
0
}
287
288
uint64_t
289
get_timer(void)
290
0
{
291
0
  struct timespec ts;
292
293
  /*
294
   * We want a timestamp in milliseconds suitable for time measurement,
295
   * so prefer the monotonic clock.
296
   */
297
0
  if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
298
0
    clock_gettime(CLOCK_REALTIME, &ts);
299
0
  return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL));
300
0
}
301
302
char *
303
clean_name(const char *name, int untrusted)
304
0
{
305
0
  char  *copy, *cp, *new_name;
306
307
0
  if (!utf8_isvalid(name))
308
0
    return (NULL);
309
0
  copy = xstrdup(name);
310
0
  for (cp = copy; *cp != '\0'; cp++) {
311
0
    if (untrusted && cp[0] == '#' && cp[1] == '(')
312
0
      *cp = '_';
313
0
  }
314
0
  utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
315
0
  free(copy);
316
0
  return (new_name);
317
0
}
318
319
int
320
check_name(const char *name)
321
0
{
322
0
  if (!utf8_isvalid(name))
323
0
    return (0);
324
0
  return (1);
325
0
}
326
327
const char *
328
sig2name(int signo)
329
0
{
330
0
     static char  s[11];
331
332
#ifdef HAVE_SYS_SIGNAME
333
     if (signo > 0 && signo < NSIG)
334
       return (sys_signame[signo]);
335
#endif
336
0
     xsnprintf(s, sizeof s, "%d", signo);
337
0
     return (s);
338
0
}
339
340
const char *
341
find_cwd(void)
342
0
{
343
0
  char     resolved1[PATH_MAX], resolved2[PATH_MAX];
344
0
  static char  cwd[PATH_MAX];
345
0
  const char  *pwd;
346
347
0
  if (getcwd(cwd, sizeof cwd) == NULL)
348
0
    return (NULL);
349
0
  if ((pwd = getenv("PWD")) == NULL || *pwd == '\0')
350
0
    return (cwd);
351
352
  /*
353
   * We want to use PWD so that symbolic links are maintained,
354
   * but only if it matches the actual working directory.
355
   */
356
0
  if (realpath(pwd, resolved1) == NULL)
357
0
    return (cwd);
358
0
  if (realpath(cwd, resolved2) == NULL)
359
0
    return (cwd);
360
0
  if (strcmp(resolved1, resolved2) != 0)
361
0
    return (cwd);
362
0
  return (pwd);
363
0
}
364
365
const char *
366
find_home(void)
367
0
{
368
0
  struct passwd   *pw;
369
0
  static const char *home;
370
371
0
  if (home != NULL)
372
0
    return (home);
373
374
0
  home = getenv("HOME");
375
0
  if (home == NULL || *home == '\0') {
376
0
    pw = getpwuid(getuid());
377
0
    if (pw != NULL)
378
0
      home = pw->pw_dir;
379
0
    else
380
0
      home = NULL;
381
0
  }
382
383
0
  return (home);
384
0
}
385
386
const char *
387
getversion(void)
388
0
{
389
0
  return (TMUX_VERSION);
390
0
}
391
392
int
393
main(int argc, char **argv)
394
0
{
395
0
  char          *path = NULL, *label = NULL;
396
0
  char          *cause, **var;
397
0
  const char        *s, *cwd;
398
0
  int          opt, keys, feat = 0, fflag = 0;
399
0
  uint64_t         flags = 0;
400
0
  const struct options_table_entry  *oe;
401
0
  u_int          i;
402
403
0
  if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL &&
404
0
      setlocale(LC_CTYPE, "C.UTF-8") == NULL) {
405
0
    if (setlocale(LC_CTYPE, "") == NULL)
406
0
      errx(1, "invalid LC_ALL, LC_CTYPE or LANG");
407
0
    s = nl_langinfo(CODESET);
408
0
    if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0)
409
0
      errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s);
410
0
  }
411
412
0
  setlocale(LC_TIME, "");
413
0
  tzset();
414
415
0
  if (**argv == '-')
416
0
    flags = CLIENT_LOGIN;
417
418
0
  global_environ = environ_create();
419
0
  for (var = environ; *var != NULL; var++)
420
0
    environ_put(global_environ, *var, 0);
421
0
  if ((cwd = find_cwd()) != NULL)
422
0
    environ_set(global_environ, "PWD", 0, "%s", cwd);
423
0
  expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1);
424
425
0
  while ((opt = getopt(argc, argv, "2c:CDdf:hlL:NqS:T:uUvV")) != -1) {
426
0
    switch (opt) {
427
0
    case '2':
428
0
      tty_add_features(&feat, "256", ":,");
429
0
      break;
430
0
    case 'c':
431
0
      shell_command = optarg;
432
0
      break;
433
0
    case 'D':
434
0
      flags |= CLIENT_NOFORK;
435
0
      break;
436
0
    case 'C':
437
0
      if (flags & CLIENT_CONTROL)
438
0
        flags |= CLIENT_CONTROLCONTROL;
439
0
      else
440
0
        flags |= CLIENT_CONTROL;
441
0
      break;
442
0
    case 'f':
443
0
      if (!fflag) {
444
0
        fflag = 1;
445
0
        for (i = 0; i < cfg_nfiles; i++)
446
0
          free(cfg_files[i]);
447
0
        cfg_nfiles = 0;
448
0
      }
449
0
      cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1,
450
0
          sizeof *cfg_files);
451
0
      cfg_files[cfg_nfiles++] = xstrdup(optarg);
452
0
      cfg_quiet = 0;
453
0
      break;
454
0
    case 'h':
455
0
      usage(0);
456
0
    case 'V':
457
0
      printf("tmux %s\n", getversion());
458
0
      exit(0);
459
0
    case 'l':
460
0
      flags |= CLIENT_LOGIN;
461
0
      break;
462
0
    case 'L':
463
0
      free(label);
464
0
      label = xstrdup(optarg);
465
0
      break;
466
0
    case 'N':
467
0
      flags |= CLIENT_NOSTARTSERVER;
468
0
      break;
469
0
    case 'q':
470
0
      break;
471
0
    case 'S':
472
0
      free(path);
473
0
      path = xstrdup(optarg);
474
0
      break;
475
0
    case 'T':
476
0
      tty_add_features(&feat, optarg, ":,");
477
0
      break;
478
0
    case 'u':
479
0
      flags |= CLIENT_UTF8;
480
0
      break;
481
0
    case 'v':
482
0
      log_add_level();
483
0
      break;
484
0
    default:
485
0
      usage(1);
486
0
    }
487
0
  }
488
0
  argc -= optind;
489
0
  argv += optind;
490
491
0
  if (shell_command != NULL && argc != 0)
492
0
    usage(1);
493
0
  if ((flags & CLIENT_NOFORK) && argc != 0)
494
0
    usage(1);
495
496
0
  if ((ptm_fd = getptmfd()) == -1)
497
0
    err(1, "getptmfd");
498
0
  if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd "
499
0
      "recvfd proc exec tty ps", NULL) != 0)
500
0
    err(1, "pledge");
501
502
  /*
503
   * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8.
504
   * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain
505
   * UTF-8, it is a safe assumption that either they are using a UTF-8
506
   * terminal, or if not they know that output from UTF-8-capable
507
   * programs may be wrong.
508
   */
509
0
  if (getenv("TMUX") != NULL)
510
0
    flags |= CLIENT_UTF8;
511
0
  else {
512
0
    s = getenv("LC_ALL");
513
0
    if (s == NULL || *s == '\0')
514
0
      s = getenv("LC_CTYPE");
515
0
    if (s == NULL || *s == '\0')
516
0
      s = getenv("LANG");
517
0
    if (s == NULL || *s == '\0')
518
0
      s = "";
519
0
    if (strcasestr(s, "UTF-8") != NULL ||
520
0
        strcasestr(s, "UTF8") != NULL)
521
0
      flags |= CLIENT_UTF8;
522
0
  }
523
524
0
  global_options = options_create(NULL);
525
0
  global_s_options = options_create(NULL);
526
0
  global_w_options = options_create(NULL);
527
0
  for (oe = options_table; oe->name != NULL; oe++) {
528
0
    if (oe->scope & OPTIONS_TABLE_SERVER)
529
0
      options_default(global_options, oe);
530
0
    if (oe->scope & OPTIONS_TABLE_SESSION)
531
0
      options_default(global_s_options, oe);
532
0
    if (oe->scope & OPTIONS_TABLE_WINDOW)
533
0
      options_default(global_w_options, oe);
534
0
  }
535
536
  /*
537
   * The default shell comes from SHELL or from the user's passwd entry
538
   * if available.
539
   */
540
0
  options_set_string(global_s_options, "default-shell", 0, "%s",
541
0
      getshell());
542
543
  /* Override keys to vi if VISUAL or EDITOR are set. */
544
0
  if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
545
0
    options_set_string(global_options, "editor", 0, "%s", s);
546
0
    if (strrchr(s, '/') != NULL)
547
0
      s = strrchr(s, '/') + 1;
548
0
    if (strstr(s, "vi") != NULL)
549
0
      keys = MODEKEY_VI;
550
0
    else
551
0
      keys = MODEKEY_EMACS;
552
0
    options_set_number(global_s_options, "status-keys", keys);
553
0
    options_set_number(global_w_options, "mode-keys", keys);
554
0
  }
555
556
  /*
557
   * If socket is specified on the command-line with -S or -L, it is
558
   * used. Otherwise, $TMUX is checked and if that fails "default" is
559
   * used.
560
   */
561
0
  if (path == NULL && label == NULL) {
562
0
    s = getenv("TMUX");
563
0
    if (s != NULL && *s != '\0' && *s != ',') {
564
0
      path = xstrdup(s);
565
0
      path[strcspn(path, ",")] = '\0';
566
0
    }
567
0
  }
568
0
  if (path == NULL) {
569
0
    if ((path = make_label(label, &cause)) == NULL) {
570
0
      if (cause != NULL) {
571
0
        fprintf(stderr, "%s\n", cause);
572
0
        free(cause);
573
0
      }
574
0
      exit(1);
575
0
    }
576
0
    flags |= CLIENT_DEFAULTSOCKET;
577
0
  }
578
0
  socket_path = path;
579
0
  free(label);
580
581
  /* Pass control to the client. */
582
0
  exit(client_main(osdep_event_init(), argc, argv, flags, feat));
583
0
}