Coverage Report

Created: 2025-07-12 06:33

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