• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 21220316014

21 Jan 2026 06:02PM UTC coverage: 92.638% (-0.4%) from 93.026%
21220316014

push

github

web-flow
Issue #2020: Guard against inadvertently accessing bytes before the allocated memory range when processing FTP control connection characters. 

In certain conditions, we might use a -1 index, thus leading to a single byte read of out-of-bounds memory.

3 of 3 new or added lines in 1 file covered. (100.0%)

898 existing lines in 28 files now uncovered.

47238 of 50992 relevant lines covered (92.64%)

237.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.87
/src/support.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 1997, 1998 Public Flood Software
4
 * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
5
 * Copyright (c) 2001-2025 The ProFTPD Project team
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20
 *
21
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
22
 * and other respective copyright holders give permission to link this program
23
 * with OpenSSL, and distribute the resulting executable, without including
24
 * the source code for OpenSSL in the source distribution.
25
 */
26

27
/* Various basic support routines for ProFTPD, used by all modules
28
 * and not specific to one or another.
29
 */
30

31
#include "conf.h"
32

33
#ifdef PR_USE_OPENSSL
34
# include <openssl/crypto.h>
35
#endif /* PR_USE_OPENSSL */
36

37
/* Keep a counter of the number of times signals_block()/signals_unblock()
38
 * have been called, to handle nesting of calls.
39
 */
40
static unsigned int sigs_nblocked = 0;
41

42
typedef struct sched_obj {
43
  struct sched_obj *next, *prev;
44

45
  pool *pool;
46
  void (*cb)(void *, void *, void *, void *);
47
  int nloops;
48
  void *arg1, *arg2, *arg3, *arg4;
49
} sched_t;
50

51
static xaset_t *scheds = NULL;
52

53
/* Masks/unmasks all important signals (as opposed to blocking alarms)
54
 */
55
static void mask_signals(unsigned char block) {
134✔
56
  static sigset_t mask_sigset;
57

58
  if (block) {
134✔
59
    sigemptyset(&mask_sigset);
67✔
60

61
    sigaddset(&mask_sigset, SIGTERM);
67✔
62
    sigaddset(&mask_sigset, SIGCHLD);
67✔
63
    sigaddset(&mask_sigset, SIGUSR1);
67✔
64
    sigaddset(&mask_sigset, SIGINT);
67✔
65
    sigaddset(&mask_sigset, SIGQUIT);
67✔
66
    sigaddset(&mask_sigset, SIGALRM);
67✔
67
#ifdef SIGIO
68
    sigaddset(&mask_sigset, SIGIO);
67✔
69
#endif
70
#ifdef SIGBUS
71
    sigaddset(&mask_sigset, SIGBUS);
67✔
72
#endif
73
    sigaddset(&mask_sigset, SIGHUP);
67✔
74

75
    if (sigprocmask(SIG_BLOCK, &mask_sigset, NULL) < 0) {
67✔
76
      pr_log_pri(PR_LOG_NOTICE,
×
77
        "unable to block signal set: %s", strerror(errno));
×
78
    }
79

80
  } else {
81
    if (sigprocmask(SIG_UNBLOCK, &mask_sigset, NULL) < 0) {
67✔
82
      pr_log_pri(PR_LOG_NOTICE,
×
83
        "unable to unblock signal set: %s", strerror(errno));
×
84
    }
85
  }
86
}
134✔
87

88
void pr_signals_block(void) {
73✔
89
  if (sigs_nblocked == 0) {
73✔
90
    mask_signals(TRUE);
67✔
91
    pr_trace_msg("signal", 5, "signals blocked");
67✔
92

93
  } else {
94
    pr_trace_msg("signal", 9, "signals already blocked (block count = %u)",
6✔
95
      sigs_nblocked);
96
  }
97

98
  sigs_nblocked++;
73✔
99
}
73✔
100

101
void pr_signals_unblock(void) {
73✔
102
  if (sigs_nblocked == 0) {
73✔
103
    pr_trace_msg("signal", 5, "signals already unblocked");
×
104
    pr_signals_handle();
×
105
    return;
×
106
  }
107

108
  if (sigs_nblocked == 1) {
73✔
109
    mask_signals(FALSE);
67✔
110
    pr_trace_msg("signal", 5, "signals unblocked");
67✔
111
    pr_signals_handle();
67✔
112

113
  } else {
114
    pr_trace_msg("signal", 9, "signals already unblocked (block count = %u)",
6✔
115
      sigs_nblocked);
116
  }
117

118
  sigs_nblocked--;
73✔
119
}
120

121
void schedule(void (*cb)(void *, void *, void *, void *), int nloops,
6✔
122
    void *arg1, void *arg2, void *arg3, void *arg4) {
123
  pool *p, *sub_pool;
124
  sched_t *s;
125

126
  if (cb == NULL ||
12✔
127
      nloops < 0) {
6✔
128
    return;
129
  }
130

131
  if (scheds == NULL) {
4✔
132
    p = make_sub_pool(permanent_pool);
1✔
133
    pr_pool_tag(p, "Schedules Pool");
1✔
134
    scheds = xaset_create(p, NULL);
1✔
135

136
  } else {
137
    p = scheds->pool;
3✔
138
  }
139

140
  sub_pool = make_sub_pool(p);
4✔
141
  pr_pool_tag(sub_pool, "schedule pool");
4✔
142

143
  s = pcalloc(sub_pool, sizeof(sched_t));
4✔
144
  s->pool = sub_pool;
4✔
145
  s->cb = cb;
4✔
146
  s->arg1 = arg1;
4✔
147
  s->arg2 = arg2;
4✔
148
  s->arg3 = arg3;
4✔
149
  s->arg4 = arg4;
4✔
150
  s->nloops = nloops;
4✔
151
  xaset_insert(scheds, (xasetmember_t *) s);
4✔
152
}
153

154
void run_schedule(void) {
146✔
155
  sched_t *s, *snext;
156

157
  if (scheds == NULL ||
153✔
158
      scheds->xas_list == NULL) {
7✔
159
    return;
160
  }
161

162
  for (s = (sched_t *) scheds->xas_list; s; s = snext) {
11✔
163
    snext = s->next;
6✔
164

165
    pr_signals_handle();
6✔
166

167
    if (s->nloops-- <= 0) {
6✔
168
      s->cb(s->arg1, s->arg2, s->arg3, s->arg4);
4✔
169
      xaset_remove(scheds, (xasetmember_t *) s);
4✔
170
      destroy_pool(s->pool);
4✔
171
    }
172
  }
173
}
174

175
/* Get the maximum size of a file name (pathname component).
176
 * If a directory file descriptor, e.g. the d_fd DIR structure element,
177
 * is not available, the second argument should be 0.
178
 *
179
 * Note: a POSIX compliant system typically should NOT define NAME_MAX,
180
 * since the value almost certainly varies across different file system types.
181
 * Refer to POSIX 1003.1a, Section 2.9.5, Table 2-5.
182
 * Alas, current (Jul 2000) Linux systems define NAME_MAX anyway.
183
 * NB: NAME_MAX_GUESS is defined in support.h.
184
 */
185

186
static int get_fpathconf_name_max(int fd, long *name_max) {
187
#if defined(HAVE_FPATHCONF)
188
  *name_max = fpathconf(fd, _PC_NAME_MAX);
1✔
189
  return 0;
190
#else
191
  errno = ENOSYS;
192
  return -1;
193
#endif /* HAVE_FPATHCONF */
194
}
195

196
static int get_pathconf_name_max(char *dir, long *name_max) {
197
#if defined(HAVE_PATHCONF)
198
  *name_max = pathconf(dir, _PC_NAME_MAX);
1✔
199
  return 0;
200
#else
201
  errno = ENOSYS;
202
  return -1;
203
#endif /* HAVE_PATHCONF */
204
}
205

206
long get_name_max(char *dir_name, int dir_fd) {
3✔
207
  int res;
208
  long name_max = 0;
3✔
209

210
  if (dir_name == NULL &&
6✔
211
      dir_fd < 0) {
3✔
212
    errno = EINVAL;
1✔
213
    return -1;
1✔
214
  }
215

216
  /* Try the fd first. */
217
  if (dir_fd >= 0) {
2✔
218
    res = get_fpathconf_name_max(dir_fd, &name_max);
1✔
219
    if (res == 0) {
220
      if (name_max < 0) {
1✔
221
        int xerrno = errno;
×
222

223
        pr_log_debug(DEBUG5, "fpathconf() error for fd %d: %s", dir_fd,
×
224
          strerror(xerrno));
225

226
        errno = xerrno;
×
227
        return -1;
×
228
      }
229

230
      return name_max;
231
    }
232
  }
233

234
  /* Name, then. */
235
  if (dir_name != NULL) {
1✔
236
    res = get_pathconf_name_max(dir_name, &name_max);
1✔
237
    if (res == 0) {
238
      if (name_max < 0) {
1✔
239
        int xerrno = errno;
×
240

241
        pr_log_debug(DEBUG5, "pathconf() error for name '%s': %s", dir_name,
×
242
          strerror(xerrno));
243

244
        errno = xerrno;
×
245
        return -1;
×
246
      }
247

248
      return name_max;
249
    }
250
  }
251

252
  errno = ENOSYS;
×
253
  return -1;
×
254
}
255

256
/* Interpolates a pathname, expanding ~ notation if necessary
257
 */
258
char *dir_interpolate(pool *p, const char *path) {
5✔
259
  char *res = NULL;
5✔
260

261
  if (p == NULL ||
10✔
262
      path == NULL) {
5✔
263
    errno = EINVAL;
2✔
264
    return NULL;
2✔
265
  }
266

267
  if (*path == '~') {
3✔
268
    char *ptr, *user;
269

270
    user = pstrdup(p, path + 1);
2✔
271
    ptr = strchr(user, '/');
2✔
272
    if (ptr != NULL) {
2✔
273
      *ptr++ = '\0';
2✔
274
    }
275

276
    if (user[0] == '\0') {
2✔
277
      user = (char *) session.user;
×
278
    }
279

280
    if (session.user != NULL &&
3✔
281
        strcmp(user, session.user) == 0 &&
2✔
282
        session.user_homedir != NULL) {
1✔
283
      res = pdircat(p, session.user_homedir, ptr, NULL);
1✔
284

285
    } else {
286
      struct passwd *pw;
287

288
      pw = pr_auth_getpwnam(p, user);
1✔
289
      if (pw == NULL) {
1✔
290
        errno = ENOENT;
1✔
291
        return NULL;
1✔
292
      }
293

294
      res = pdircat(p, pw->pw_dir, ptr, NULL);
×
295
    }
296

297
  } else {
298
    res = pstrdup(p, path);
1✔
299
  }
300

301
  return res;
302
}
303

304
/* dir_best_path() creates the "most" fully canonicalized path possible
305
 * (i.e. if path components at the end don't exist, they are ignored).
306
 */
307
char *dir_best_path(pool *p, const char *path) {
9✔
308
  char workpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
9✔
309
  char realpath_buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
9✔
310
  char *target = NULL, *ntarget;
9✔
311
  int fini = 0;
9✔
312

313
  if (p == NULL ||
18✔
314
      path == NULL) {
9✔
315
    errno = EINVAL;
2✔
316
    return NULL;
2✔
317
  }
318

319
  if (*path == '~') {
7✔
320
    if (pr_fs_interpolate(path, workpath, sizeof(workpath)-1) != 1) {
×
321
      if (pr_fs_dircat(workpath, sizeof(workpath), pr_fs_getcwd(), path) < 0) {
×
322
        return NULL;
323
      }
324
    }
325

326
  } else {
327
    if (pr_fs_dircat(workpath, sizeof(workpath), pr_fs_getcwd(), path) < 0) {
7✔
328
      return NULL;
329
    }
330
  }
331

332
  pr_fs_clean_path(pstrdup(p, workpath), workpath, sizeof(workpath)-1);
7✔
333

334
  while (!fini && *workpath) {
26✔
335
    pr_signals_handle();
18✔
336

337
    if (pr_fs_resolve_path(workpath, realpath_buf,
18✔
338
        sizeof(realpath_buf)-1, 0) != -1) {
339
      break;
340
    }
341

342
    ntarget = strrchr(workpath, '/');
12✔
343
    if (ntarget) {
12✔
344
      if (target) {
12✔
345
        if (pr_fs_dircat(workpath, sizeof(workpath), workpath, target) < 0) {
5✔
346
          return NULL;
347
        }
348
      }
349

350
      target = ntarget;
12✔
351
      *target++ = '\0';
12✔
352

353
    } else {
354
      fini++;
355
    }
356
  }
357

358
  if (!fini && *workpath) {
7✔
359
    if (target) {
6✔
360
      if (pr_fs_dircat(workpath, sizeof(workpath), realpath_buf, target) < 0) {
6✔
361
        return NULL;
362
      }
363

364
    } else {
365
      sstrncpy(workpath, realpath_buf, sizeof(workpath));
×
366
    }
367

368
  } else {
369
    if (pr_fs_dircat(workpath, sizeof(workpath), "/", target) < 0) {
1✔
370
      return NULL;
371
    }
372
  }
373

374
  return pstrdup(p, workpath);
7✔
375
}
376

377
char *dir_canonical_path(pool *p, const char *path) {
3✔
378
  char buf[PR_TUNABLE_PATH_MAX + 1]  = {'\0'};
3✔
379
  char work[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
3✔
380

381
  if (p == NULL ||
6✔
382
      path == NULL) {
3✔
383
    errno = EINVAL;
2✔
384
    return NULL;
2✔
385
  }
386

387
  if (*path == '~') {
1✔
388
    if (pr_fs_interpolate(path, work, sizeof(work)-1) != 1) {
×
389
      if (pr_fs_dircat(work, sizeof(work), pr_fs_getcwd(), path) < 0) {
×
390
        return NULL;
391
      }
392
    }
393

394
  } else {
395
    if (pr_fs_dircat(work, sizeof(work), pr_fs_getcwd(), path) < 0) {
1✔
396
      return NULL;
397
    }
398
  }
399

400
  pr_fs_clean_path(work, buf, sizeof(buf)-1);
1✔
401
  return pstrdup(p, buf);
1✔
402
}
403

404
char *dir_canonical_vpath(pool *p, const char *path) {
3✔
405
  char buf[PR_TUNABLE_PATH_MAX + 1]  = {'\0'};
3✔
406
  char work[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
3✔
407

408
  if (p == NULL ||
6✔
409
      path == NULL) {
3✔
410
    errno = EINVAL;
2✔
411
    return NULL;
2✔
412
  }
413

414
  if (*path == '~') {
1✔
415
    if (pr_fs_interpolate(path, work, sizeof(work)-1) != 1) {
×
416
      if (pr_fs_dircat(work, sizeof(work), pr_fs_getvwd(), path) < 0) {
×
417
        return NULL;
418
      }
419
    }
420

421
  } else {
422
    if (pr_fs_dircat(work, sizeof(work), pr_fs_getvwd(), path) < 0) {
1✔
423
      return NULL;
424
    }
425
  }
426

427
  pr_fs_clean_path(work, buf, sizeof(buf)-1);
1✔
428
  return pstrdup(p, buf);
1✔
429
}
430

431
/* Performs chroot-aware handling of symlinks. */
432
int dir_readlink(pool *p, const char *path, char *buf, size_t bufsz,
26✔
433
    int flags) {
434
  int is_abs_dst, clean_flags, len, res = -1;
26✔
435
  size_t chroot_pathlen = 0, adj_pathlen = 0;
26✔
436
  char *dst_path, *adj_path;
437
  pool *tmp_pool;
438

439
  if (p == NULL ||
52✔
440
      path == NULL ||
50✔
441
      buf == NULL) {
442
    errno = EINVAL;
3✔
443
    return -1;
3✔
444
  }
445

446
  if (bufsz == 0) {
23✔
447
    return 0;
448
  }
449

450
  len = pr_fsio_readlink(path, buf, bufsz);
22✔
451
  if (len < 0) {
22✔
452
    return -1;
453
  }
454

455
  pr_trace_msg("fsio", 9,
20✔
456
    "dir_readlink() read link '%.*s' for path '%s'", (int) len, buf, path);
457

458
  if (len == 0 ||
40✔
459
      (size_t) len == bufsz) {
20✔
460
    /* If we read nothing in, OR if the given buffer was completely
461
     * filled WITHOUT terminating NUL, there's really nothing we can/should
462
     * be doing.
463
     */
464
    return len;
465
  }
466

467
  is_abs_dst = FALSE;
19✔
468
  if (*buf == '/') {
19✔
469
    is_abs_dst = TRUE;
5✔
470
  }
471

472
  if (session.chroot_path != NULL) {
19✔
473
    chroot_pathlen = strlen(session.chroot_path);
14✔
474
  }
475

476
  if (chroot_pathlen <= 1) {
14✔
477
    char *ptr;
478

479
    if (is_abs_dst == TRUE ||
11✔
480
        !(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) {
5✔
481
      return len;
482
    }
483

484
    /* Since we have a relative destination path, we will concat it
485
     * with the source path's directory, then clean up that path.
486
     */
487
    ptr = strrchr(path, '/');
2✔
488
    if (ptr != NULL &&
4✔
489
        ptr != path) {
2✔
490
      char *parent_dir;
491

492
      tmp_pool = make_sub_pool(p);
2✔
493
      pr_pool_tag(tmp_pool, "dir_readlink pool");
2✔
494

495
      parent_dir = pstrndup(tmp_pool, path, (ptr - path));
2✔
496
      dst_path = pdircat(tmp_pool, parent_dir, buf, NULL);
2✔
497

498
      adj_pathlen = bufsz + 1;
2✔
499
      adj_path = pcalloc(tmp_pool, adj_pathlen);
2✔
500

501
      res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, 0);
2✔
502
      if (res == 0) {
2✔
503
        pr_trace_msg("fsio", 19,
2✔
504
          "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path);
505
        dst_path = adj_path;
2✔
506
      }
507

508
      pr_trace_msg("fsio", 19,
2✔
509
        "adjusted relative symlink path '%s', yielding '%s'", buf, dst_path);
510

511
      memset(buf, '\0', bufsz);
2✔
512
      sstrncpy(buf, dst_path, bufsz);
2✔
513
      len = strlen(buf);
2✔
514
      destroy_pool(tmp_pool);
2✔
515
    }
516

517
    return len;
518
  }
519

520
  if (is_abs_dst == FALSE) {
13✔
521
    /* If we are to ignore relative destination paths, return now. */
522
    if (!(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) {
9✔
523
      return len;
524
    }
525
  }
526

527
  if (is_abs_dst == TRUE &&
10✔
528
      (size_t) len < chroot_pathlen) {
529
    /* If the destination path length is shorter than the chroot path,
530
     * AND the destination path is absolute, then by definition it CANNOT
531
     * point within the chroot.
532
     */
533
    return len;
534
  }
535

536
  tmp_pool = make_sub_pool(p);
9✔
537
  pr_pool_tag(tmp_pool, "dir_readlink pool");
9✔
538

539
  dst_path = pstrdup(tmp_pool, buf);
9✔
540
  if (is_abs_dst == FALSE) {
9✔
541
    char *ptr;
542

543
    /* Since we have a relative destination path, we will concat it
544
     * with the source path's directory, then clean up that path.
545
     */
546

547
    ptr = strrchr(path, '/');
6✔
548
    if (ptr != NULL) {
6✔
549
      if (ptr != path) {
6✔
550
        char *parent_dir;
551

552
        parent_dir = pstrndup(tmp_pool, path, (ptr - path));
6✔
553
        dst_path = pdircat(tmp_pool, parent_dir, dst_path, NULL);
6✔
554

555
      } else {
556
        dst_path = pdircat(tmp_pool, "/", dst_path, NULL);
×
557
      }
558
    }
559
  }
560

561
  adj_pathlen = bufsz + 1;
9✔
562
  adj_path = pcalloc(tmp_pool, adj_pathlen);
9✔
563

564
  clean_flags = PR_FSIO_CLEAN_PATH_FL_MAKE_ABS_PATH;
9✔
565
  res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, clean_flags);
9✔
566
  if (res == 0) {
9✔
567
    pr_trace_msg("fsio", 19,
9✔
568
      "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path);
569
    dst_path = adj_path;
9✔
570

571
    memset(buf, '\0', bufsz);
9✔
572
    sstrncpy(buf, dst_path, bufsz);
9✔
573
    len = strlen(dst_path);
9✔
574
  }
575

576
  if (strncmp(dst_path, session.chroot_path, chroot_pathlen) == 0 &&
12✔
577
      *(dst_path + chroot_pathlen) == '/') {
3✔
578
    char *ptr;
579

580
    ptr = dst_path + chroot_pathlen;
2✔
581

582
    if (is_abs_dst == FALSE &&
2✔
583
        res == 0) {
584
      /* If we originally had a relative destination path, AND we cleaned
585
       * that adjusted path, then we should try to re-adjust the path
586
       * back to being a relative path.  Within reason.
587
       */
588
      ptr = pstrcat(tmp_pool, ".", ptr, NULL);
1✔
589
    }
590

591
    /* Since we are making the destination path shorter, the given buffer
592
     * (which was big enough for the original destination path) should
593
     * always be large enough for this adjusted, shorter version.  Right?
594
     */
595
    pr_trace_msg("fsio", 19,
2✔
596
      "adjusted symlink path '%s' for chroot '%s', yielding '%s'",
597
      dst_path, session.chroot_path, ptr);
598

599
    memset(buf, '\0', bufsz);
2✔
600
    sstrncpy(buf, ptr, bufsz);
2✔
601
    len = strlen(buf);
2✔
602
  }
603

604
  destroy_pool(tmp_pool);
9✔
605
  return len;
9✔
606
}
607

608
/* dir_realpath() is needed to properly dereference symlinks (getcwd() may
609
 * not work if permissions cause problems somewhere up the tree).
610
 */
611
char *dir_realpath(pool *p, const char *path) {
6✔
612
  char buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
6✔
613

614
  if (p == NULL ||
12✔
615
      path == NULL) {
6✔
616
    errno = EINVAL;
2✔
617
    return NULL;
2✔
618
  }
619

620
  if (pr_fs_resolve_partial(path, buf, sizeof(buf)-1, 0) < 0) {
4✔
621
    return NULL;
622
  }
623

624
  return pstrdup(p, buf);
1✔
625
}
626

627
/* Takes a directory and returns its absolute version.  ~username references
628
 * are appropriately interpolated.  "Absolute" includes a _full_ reference
629
 * based on the root directory, not upon a chrooted dir.
630
 */
631
char *dir_abs_path(pool *p, const char *path, int interpolate) {
35✔
632
  char *res = NULL;
35✔
633

634
  if (p == NULL ||
70✔
635
      path == NULL) {
35✔
636
    errno = EINVAL;
2✔
637
    return NULL;
2✔
638
  }
639

640
  if (interpolate) {
33✔
641
    char buf[PR_TUNABLE_PATH_MAX+1];
642

643
    memset(buf, '\0', sizeof(buf));
33✔
644
    switch (pr_fs_interpolate(path, buf, sizeof(buf)-1)) {
33✔
645
      case -1:
×
646
        return NULL;
×
647

648
      case 0:
649
        /* Do nothing; path exists */
650
        break;
651

652
      case 1:
33✔
653
        /* Interpolation occurred; make a copy of the interpolated path. */
654
        path = pstrdup(p, buf);
33✔
655
        break;
33✔
656
    }
657
  }
658

659
  if (*path != '/') {
33✔
660
    if (session.chroot_path) {
19✔
661
      res = pdircat(p, session.chroot_path, pr_fs_getcwd(), path, NULL);
×
662

663
    } else {
664
      res = pdircat(p, pr_fs_getcwd(), path, NULL);
19✔
665
    }
666

667
  } else {
668
    if (session.chroot_path) {
14✔
669
      if (strncmp(path, session.chroot_path,
×
670
          strlen(session.chroot_path)) != 0) {
671
        res = pdircat(p, session.chroot_path, path, NULL);
×
672

673
      } else {
674
        res = pstrdup(p, path);
×
675
      }
676

677
    } else {
678
      res = pstrdup(p, path);
14✔
679
    }
680
  }
681

682
  return res;
683
}
684

685
/* Return the mode (including the file type) of the file pointed to by symlink
686
 * PATH, or 0 if it doesn't exist. Catch symlink loops using LAST_INODE and
687
 * RCOUNT.
688
 */
689
static mode_t get_symlink_mode(pool *p, const char *path, ino_t last_inode,
2✔
690
    int rcount) {
691
  char buf[PR_TUNABLE_PATH_MAX + 1];
692
  struct stat st;
693
  int i;
694

695
  if (++rcount >= PR_FSIO_MAX_LINK_COUNT) {
2✔
696
    errno = ELOOP;
×
697
    return 0;
×
698
  }
699

700
  memset(buf, '\0', sizeof(buf));
2✔
701

702
  if (p != NULL) {
2✔
703
    i = dir_readlink(p, path, buf, sizeof(buf)-1,
1✔
704
      PR_DIR_READLINK_FL_HANDLE_REL_PATH);
705

706
  } else {
707
    i = pr_fsio_readlink(path, buf, sizeof(buf)-1);
1✔
708
  }
709

710
  if (i < 0) {
2✔
711
    return (mode_t) 0;
712
  }
UNCOV
713
  buf[i] = '\0';
×
714

715
  pr_fs_clear_cache2(buf);
×
716
  if (pr_fsio_lstat(buf, &st) >= 0) {
×
UNCOV
717
    if (st.st_ino > 0 &&
×
718
        (ino_t) st.st_ino == last_inode) {
719
      errno = ELOOP;
×
UNCOV
720
      return 0;
×
721
    }
722

723
    if (S_ISLNK(st.st_mode)) {
×
UNCOV
724
      return get_symlink_mode(p, buf, (ino_t) st.st_ino, rcount);
×
725
    }
726

727
    return st.st_mode;
728
  }
729

730
  return 0;
731
}
732

733
mode_t symlink_mode2(pool *p, const char *path) {
5✔
734
  if (path == NULL) {
5✔
735
    errno = EINVAL;
3✔
736
    return 0;
3✔
737
  }
738

739
  return get_symlink_mode(p, path, (ino_t) 0, 0);
2✔
740
}
741

742
mode_t symlink_mode(const char *path) {
2✔
743
  return symlink_mode2(NULL, path);
2✔
744
}
745

746
mode_t file_mode2(pool *p, const char *path) {
24✔
747
  struct stat st;
748
  mode_t mode = 0;
24✔
749

750
  if (path == NULL) {
24✔
751
    errno = EINVAL;
12✔
752
    return mode;
12✔
753
  }
754

755
  pr_fs_clear_cache2(path);
12✔
756
  if (pr_fsio_lstat(path, &st) >= 0) {
12✔
757
    if (S_ISLNK(st.st_mode)) {
12✔
758
      mode = get_symlink_mode(p, path, (ino_t) 0, 0);
×
UNCOV
759
      if (mode == 0) {
×
760
        /* a dangling symlink, but it exists to rename or delete. */
UNCOV
761
        mode = st.st_mode;
×
762
      }
763

764
    } else {
765
      mode = st.st_mode;
766
    }
767
  }
768

769
  return mode;
770
}
771

772
mode_t file_mode(const char *path) {
2✔
773
  return file_mode2(NULL, path);
2✔
774
}
775

776
/* If flags == 1, fail unless PATH is an existing directory.
777
 * If flags == 0, fail unless PATH is an existing non-directory.
778
 * If flags == -1, fail unless PATH exists; the caller doesn't care whether
779
 * PATH is a file or a directory.
780
 */
781
static int path_exists(pool *p, const char *path, int flags) {
782
  mode_t mode;
783

784
  mode = file_mode2(p, path);
19✔
785
  if (mode != 0) {
19✔
786
    switch (flags) {
787
      case 1:
4✔
788
        if (!S_ISDIR(mode)) {
4✔
789
          return FALSE;
790
        }
791
        break;
792

793
      case 0:
4✔
794
        if (S_ISDIR(mode)) {
4✔
795
          return FALSE;
796
        }
797
        break;
798

799
      default:
800
        break;
801
    }
802

803
    return TRUE;
804
  }
805

806
  return FALSE;
807
}
808

809
int file_exists2(pool *p, const char *path) {
4✔
810
  return path_exists(p, path, 0);
7✔
811
}
812

813
int file_exists(const char *path) {
3✔
814
  return file_exists2(NULL, path);
3✔
815
}
816

817
int dir_exists2(pool *p, const char *path) {
4✔
818
  return path_exists(p, path, 1);
7✔
819
}
820

821
int dir_exists(const char *path) {
3✔
822
  return dir_exists2(NULL, path);
3✔
823
}
824

825
int exists2(pool *p, const char *path) {
3✔
826
  return path_exists(p, path, -1);
5✔
827
}
828

829
int exists(const char *path) {
2✔
830
  return exists2(NULL, path);
2✔
831
}
832

833
/* safe_token tokenizes a string, and increments the pointer to
834
 * the next non-white space character.  It's "safe" because it
835
 * never returns NULL, only an empty string if no token remains
836
 * in the source string.
837
 */
838
char *safe_token(char **s) {
29✔
839
  char *res = "";
29✔
840

841
  if (s == NULL ||
57✔
842
      !*s) {
28✔
843
    return res;
844
  }
845

846
  while (PR_ISSPACE(**s) && **s) {
33✔
847
    (*s)++;
5✔
848
  }
849

850
  if (**s) {
28✔
851
    res = *s;
852

853
    while (!PR_ISSPACE(**s) && **s) {
80✔
854
      (*s)++;
54✔
855
    }
856

857
    if (**s) {
26✔
858
      *(*s)++ = '\0';
21✔
859
    }
860

861
    while (PR_ISSPACE(**s) && **s) {
26✔
UNCOV
862
      (*s)++;
×
863
    }
864
  }
865

866
  return res;
867
}
868

869
/* Checks for the existence of PR_SHUTMSG_PATH.  deny and disc are
870
 * filled with the times to deny new connections and disconnect
871
 * existing ones.
872
 */
873
int check_shutmsg(pool *p, const char *path, time_t *shut, time_t *deny,
6✔
874
    time_t *disc, char *msg, size_t msg_size) {
875
  FILE *fp;
876
  char *deny_str, *disc_str, *cp, buf[PR_TUNABLE_BUFFER_SIZE+1] = {'\0'};
6✔
877
  char hr[3] = {'\0'}, mn[3] = {'\0'};
6✔
878
  time_t now, shuttime = (time_t) 0;
6✔
879
  struct stat st;
880
  struct tm *tm;
881

882
  if (path == NULL) {
6✔
883
    errno = EINVAL;
1✔
884
    return -1;
1✔
885
  }
886

887
  fp = fopen(path, "r");
5✔
888
  if (fp == NULL) {
5✔
889
    return -1;
890
  }
891

892
  if (fstat(fileno(fp), &st) == 0) {
8✔
893
    if (S_ISDIR(st.st_mode)) {
4✔
894
      fclose(fp);
1✔
895
      errno = EISDIR;
1✔
896
      return -1;
1✔
897
    }
898
  }
899

900
  cp = fgets(buf, sizeof(buf), fp);
3✔
901
  if (cp != NULL) {
3✔
902
    buf[sizeof(buf)-1] = '\0'; CHOP(cp);
3✔
903

904
    /* We use this to fill in dst, timezone, etc */
905
    time(&now);
3✔
906
    tm = pr_localtime(p, &now);
3✔
907
    if (tm == NULL) {
3✔
908
      fclose(fp);
×
UNCOV
909
      return 0;
×
910
    }
911

912
    tm->tm_year = atoi(safe_token(&cp)) - 1900;
6✔
913
    tm->tm_mon = atoi(safe_token(&cp)) - 1;
6✔
914
    tm->tm_mday = atoi(safe_token(&cp));
6✔
915
    tm->tm_hour = atoi(safe_token(&cp));
6✔
916
    tm->tm_min = atoi(safe_token(&cp));
6✔
917
    tm->tm_sec = atoi(safe_token(&cp));
6✔
918

919
    deny_str = safe_token(&cp);
3✔
920
    disc_str = safe_token(&cp);
3✔
921

922
    shuttime = mktime(tm);
3✔
923
    if (shuttime == (time_t) -1) {
3✔
924
      fclose(fp);
×
UNCOV
925
      return 0;
×
926
    }
927

928
    if (deny != NULL) {
3✔
929
      if (strlen(deny_str) == 4) {
1✔
930
        sstrncpy(hr, deny_str, sizeof(hr));
1✔
931
        hr[2] = '\0';
1✔
932
        deny_str += 2;
1✔
933

934
        sstrncpy(mn, deny_str, sizeof(mn));
1✔
935
        mn[2] = '\0';
1✔
936

937
        *deny = shuttime - ((atoi(hr) * 3600) + (atoi(mn) * 60));
2✔
938

939
      } else {
UNCOV
940
        *deny = shuttime;
×
941
      }
942
    }
943

944
    if (disc != NULL) {
3✔
945
      if (strlen(disc_str) == 4) {
1✔
946
        sstrncpy(hr, disc_str, sizeof(hr));
1✔
947
        hr[2] = '\0';
1✔
948
        disc_str += 2;
1✔
949

950
        sstrncpy(mn, disc_str, sizeof(mn));
1✔
951
        mn[2] = '\0';
1✔
952

953
        *disc = shuttime - ((atoi(hr) * 3600) + (atoi(mn) * 60));
2✔
954

955
      } else {
UNCOV
956
        *disc = shuttime;
×
957
      }
958
    }
959

960
    if (fgets(buf, sizeof(buf), fp) && msg) {
3✔
961
      buf[sizeof(buf)-1] = '\0';
1✔
962
      CHOP(buf);
1✔
963
      sstrncpy(msg, buf, msg_size-1);
1✔
964
    }
965
  }
966

967
  fclose(fp);
3✔
968

969
  if (shut != NULL) {
3✔
970
    *shut = shuttime;
1✔
971
  }
972

973
  return 1;
974
}
975

976
#if !defined(PR_USE_OPENSSL) || OPENSSL_VERSION_NUMBER <= 0x000907000L
977
/* "safe" memset() (code borrowed from OpenSSL).  This function should be
978
 * used to clear/scrub sensitive memory areas instead of memset() for the
979
 * reasons mentioned in this BugTraq thread:
980
 *
981
 *  http://online.securityfocus.com/archive/1/298598
982
 */
983

984
static unsigned char memscrub_ctr = 0;
985
#endif
986

987
void pr_memscrub(void *ptr, size_t ptrlen) {
44✔
988
#if defined(PR_USE_OPENSSL) && OPENSSL_VERSION_NUMBER > 0x000907000L
989
  if (ptr == NULL ||
88✔
990
      ptrlen == 0) {
44✔
991
    return;
992
  }
993

994
  /* Just use OpenSSL's function for this.  They have optimized it for
995
   * performance in later OpenSSL releases.
996
   */
997
  OPENSSL_cleanse(ptr, ptrlen);
41✔
998

999
#else
1000
  unsigned char *p;
1001
  size_t loop;
1002

1003
  if (ptr == NULL ||
1004
      ptrlen == 0) {
1005
    return;
1006
  }
1007

1008
  p = ptr;
1009
  loop = ptrlen;
1010

1011
  while (loop--) {
1012
    *(p++) = memscrub_ctr++;
1013
    memscrub_ctr += (17 + (unsigned char)((intptr_t) p & 0xF));
1014
  }
1015

1016
  if (memchr(ptr, memscrub_ctr, ptrlen)) {
1017
    memscrub_ctr += 63;
1018
  }
1019
#endif
1020
}
1021

1022
void pr_getopt_reset(void) {
1✔
1023
#if defined(FREEBSD4) || defined(FREEBSD5) || defined(FREEBSD6) || \
1024
    defined(FREEBSD7) || defined(FREEBSD8) || defined(FREEBSD9) || \
1025
    defined(FREEBSD10) || defined(FREEBSD11) || defined(FREEBSD12) || \
1026
    defined(FREEBSD13) || defined(FREEBSD14) || \
1027
    defined(DARWIN7) || defined(DARWIN8) || defined(DARWIN9) || \
1028
    defined(DARWIN10) || defined(DARWIN11) || defined(DARWIN12) || \
1029
    defined(DARWIN13) || defined(DARWIN14) || defined(DARWIN15) || \
1030
    defined(DARWIN16) || defined(DARWIN17) || defined(DARWIN18) || \
1031
    defined(DARWIN19) || defined(DARWIN20) || defined(DARWIN21) || \
1032
    defined(DARWIN22) || defined(DARWIN23)
1033
  optreset = 1;
1034
  opterr = 1;
1035
  optind = 1;
1036

1037
#elif defined(SOLARIS2) || defined(HPUX11)
1038
  opterr = 0;
1039
  optind = 1;
1040

1041
#else
1042
  opterr = 0;
1✔
1043
  optind = 0;
1✔
1044
#endif /* !FreeBSD, !Mac OSX and !Solaris2 */
1045

1046
  if (pr_env_get(permanent_pool, "POSIXLY_CORRECT") == NULL) {
1✔
1047
    pr_env_set(permanent_pool, "POSIXLY_CORRECT", "1");
1✔
1048
  }
1049
}
1✔
1050

1051
struct tm *pr_gmtime(pool *p, const time_t *now) {
8✔
1052
  struct tm *sys_tm, *dup_tm;
1053

1054
  if (now == NULL) {
8✔
1055
    errno = EINVAL;
1✔
1056
    return NULL;
1✔
1057
  }
1058

1059
#if defined(HAVE_GMTIME_R)
1060
  if (p == NULL) {
7✔
1061
    errno = EINVAL;
3✔
1062
    return NULL;
3✔
1063
  }
1064

1065
  sys_tm = gmtime_r(now, pcalloc(p, sizeof(struct tm)));
4✔
1066
#else
1067
  sys_tm = gmtime(now);
1068
#endif /* HAVE_GMTIME_R */
1069
  if (sys_tm == NULL) {
4✔
1070
    return NULL;
1071
  }
1072

1073
  /* If the caller provided a pool, make a copy of the struct tm using that
1074
   * pool.  Otherwise, return the struct tm as is.
1075
   */
1076
  if (p != NULL) {
1077
    dup_tm = pcalloc(p, sizeof(struct tm));
4✔
1078
    memcpy(dup_tm, sys_tm, sizeof(struct tm));
1079

1080
  } else {
1081
    dup_tm = sys_tm;
1082
  }
1083

1084
  return dup_tm;
4✔
1085
}
1086

1087
struct tm *pr_localtime(pool *p, const time_t *now) {
4,943✔
1088
  struct tm *sys_tm, *dup_tm;
1089

1090
#ifdef HAVE_TZNAME
1091
  char *tzname_dup[2];
1092

1093
  /* The localtime(3) function has a nasty habit of changing the tzname
1094
   * global variable as a side-effect.  This can cause problems, as when
1095
   * the process has become chrooted, and localtime(3) sets/changes
1096
   * tzname wrong.  (For more information on the tzname global variable,
1097
   * see the tzset(3) man page.)
1098
   *
1099
   * The best way to deal with this issue (which is especially prominent
1100
   * on systems running glibc-2.3 or later, which is particularly ill-behaved
1101
   * in a chrooted environment, as it assumes the ability to find system
1102
   * timezone files at paths which are no longer valid within the chroot)
1103
   * is to set the TZ environment variable explicitly, before starting
1104
   * proftpd.  You can also use the SetEnv configuration directive within
1105
   * the proftpd.conf to set the TZ environment variable, e.g.:
1106
   *
1107
   *  SetEnv TZ PST
1108
   *
1109
   * To try to help sites which fail to do this, the tzname global variable
1110
   * will be copied prior to the localtime(3) call, and the copy restored
1111
   * after the call.  (Note that calling the ctime(3) and mktime(3)
1112
   * functions also causes a similar overwriting/setting of the tzname
1113
   * environment variable.)
1114
   *
1115
   * This hack is also used in the lib/pr-syslog.c code, to work around
1116
   * mktime(3) antics.
1117
   */
1118
  memcpy(&tzname_dup, tzname, sizeof(tzname_dup));
4,943✔
1119
#endif /* HAVE_TZNAME */
1120

1121
  if (now == NULL) {
4,943✔
1122
    errno = EINVAL;
1✔
1123
    return NULL;
1✔
1124
  }
1125

1126
#if defined(HAVE_LOCALTIME_R)
1127
  if (p == NULL) {
4,942✔
1128
    errno = EINVAL;
2✔
1129
    return NULL;
2✔
1130
  }
1131

1132
  sys_tm = localtime_r(now, pcalloc(p, sizeof(struct tm)));
4,940✔
1133
#else
1134
  sys_tm = localtime(now);
1135
#endif /* HAVE_LOCALTIME_R */
1136
  if (sys_tm == NULL) {
4,940✔
1137
    return NULL;
1138
  }
1139

1140
  if (p != NULL) {
1141
    /* If the caller provided a pool, make a copy of the returned
1142
     * struct tm, allocated out of that pool.
1143
     */
1144
    dup_tm = pcalloc(p, sizeof(struct tm));
4,940✔
1145
    memcpy(dup_tm, sys_tm, sizeof(struct tm));
1146

1147
  } else {
1148

1149
    /* Other callers do not require pool-allocated copies, and instead
1150
     * are happy with the struct tm as is.
1151
     */
1152
    dup_tm = sys_tm;
1153
  }
1154

1155
#if defined(HAVE_TZNAME)
1156
  /* Restore the old tzname values prior to returning. */
1157
  memcpy(tzname, tzname_dup, sizeof(tzname_dup));
4,940✔
1158
#endif /* HAVE_TZNAME */
1159

1160
  return dup_tm;
4,940✔
1161
}
1162

1163
const char *pr_strtime(time_t t) {
1✔
1164
  return pr_strtime2(t, FALSE);
1✔
1165
}
1166

1167
const char *pr_strtime2(time_t t, int use_gmtime) {
1✔
1168
  return pr_strtime3(NULL, t, use_gmtime);
2✔
1169
}
1170

1171
const char *pr_strtime3(pool *p, time_t t, int use_gmtime) {
12✔
1172
  static char buf[64];
1173
  static char *mons[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
1174
    "Aug", "Sep", "Oct", "Nov", "Dec" };
1175
  static char *days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
1176
  struct tm *tr;
1177

1178
  memset(buf, '\0', sizeof(buf));
12✔
1179

1180
  if (use_gmtime) {
12✔
1181
    tr = pr_gmtime(p, &t);
3✔
1182

1183
  } else {
1184
    tr = pr_localtime(p, &t);
9✔
1185
  }
1186

1187
  if (tr == NULL) {
12✔
1188
    return NULL;
1189
  }
1190

1191
  pr_snprintfl(__FILE__, __LINE__, buf, sizeof(buf),
27✔
1192
    "%s %s %02d %02d:%02d:%02d %d", days[tr->tm_wday], mons[tr->tm_mon],
18✔
1193
    tr->tm_mday, tr->tm_hour, tr->tm_min, tr->tm_sec, tr->tm_year + 1900);
9✔
1194
  buf[sizeof(buf)-1] = '\0';
9✔
1195

1196
  if (p != NULL) {
9✔
1197
    return pstrdup(p, buf);
9✔
1198
  }
1199

1200
  return buf;
1201
}
1202

1203
int pr_timeval2millis(struct timeval *tv, uint64_t *millis) {
21✔
1204
  if (tv == NULL ||
42✔
1205
      millis == NULL) {
21✔
1206
    errno = EINVAL;
3✔
1207
    return -1;
3✔
1208
  }
1209

1210
  /* Make sure to use 64-bit multiplication to avoid overflow errors,
1211
   * as much as we can.
1212
   */
1213
  *millis = (tv->tv_sec * (uint64_t) 1000) + (tv->tv_usec / (uint64_t) 1000);
18✔
1214
  return 0;
18✔
1215
}
1216

1217
int pr_gettimeofday_millis(uint64_t *millis) {
14✔
1218
  struct timeval tv;
1219

1220
  if (gettimeofday(&tv, NULL) < 0) {
14✔
1221
    return -1;
1222
  }
1223

1224
  if (pr_timeval2millis(&tv, millis) < 0) {
14✔
1225
    return -1;
1226
  }
1227

1228
  return 0;
13✔
1229
}
1230

1231
int pr_vsnprintfl(const char *file, int lineno, char *buf, size_t bufsz,
15,221✔
1232
    const char *fmt, va_list msg) {
1233
  int res, xerrno = 0;
15,221✔
1234

1235
  if (buf == NULL ||
30,442✔
1236
      fmt == NULL) {
15,221✔
1237
    errno = EINVAL;
4✔
1238
    return -1;
4✔
1239
  }
1240

1241
  if (bufsz == 0) {
15,217✔
1242
    return 0;
1243
  }
1244

1245
  res = vsnprintf(buf, bufsz, fmt, msg);
15,215✔
1246
  xerrno = errno;
15,215✔
1247

1248
  if (res < 0) {
15,215✔
1249
    /* Unexpected error. */
1250

1251
#ifdef EOVERFLOW
1252
    if (xerrno == EOVERFLOW) {
×
UNCOV
1253
      xerrno = ENOSPC;
×
1254
    }
1255
#endif /* EOVERFLOW */
1256

1257
  } else if ((size_t) res >= bufsz) {
15,215✔
1258
    /* Buffer too small. */
1259
    xerrno = ENOSPC;
7✔
1260
    res = -1;
7✔
1261
  }
1262

1263
  /* We are mostly concerned with tracking down the locations of truncated
1264
   * buffers, hence the stacktrace logging only for these conditions.
1265
   */
1266
  if (res < 0 &&
30,430✔
1267
      xerrno == ENOSPC) {
15,215✔
1268
    if (file != NULL &&
14✔
1269
        lineno > 0) {
7✔
1270
      pr_log_pri(PR_LOG_WARNING,
2✔
1271
        "%s:%d: error writing format string '%s' into %lu-byte buffer: %s",
1272
        file, lineno, fmt, (unsigned long) bufsz, strerror(xerrno));
1273

1274
    } else {
1275
      pr_log_pri(PR_LOG_WARNING,
5✔
1276
        "error writing format string '%s' into %lu-byte buffer: %s", fmt,
1277
        (unsigned long) bufsz, strerror(xerrno));
1278
    }
1279

1280
    pr_log_stacktrace(-1, NULL);
7✔
1281
  }
1282

1283
  errno = xerrno;
15,215✔
1284
  return res;
15,215✔
1285
}
1286

1287
int pr_vsnprintf(char *buf, size_t bufsz, const char *fmt, va_list msg) {
5,094✔
1288
  return pr_vsnprintfl(NULL, -1, buf, bufsz, fmt, msg);
5,094✔
1289
}
1290

1291
int pr_snprintfl(const char *file, int lineno, char *buf, size_t bufsz,
15✔
1292
    const char *fmt, ...) {
1293
  va_list msg;
1294
  int res;
1295

1296
  va_start(msg, fmt);
15✔
1297
  res = pr_vsnprintfl(file, lineno, buf, bufsz, fmt, msg);
15✔
1298
  va_end(msg);
15✔
1299

1300
  return res;
15✔
1301
}
1302

1303
int pr_snprintf(char *buf, size_t bufsz, const char *fmt, ...) {
10,112✔
1304
  va_list msg;
1305
  int res;
1306

1307
  va_start(msg, fmt);
10,112✔
1308
  res = pr_vsnprintfl(NULL, -1, buf, bufsz, fmt, msg);
10,112✔
1309
  va_end(msg);
10,112✔
1310

1311
  return res;
10,112✔
1312
}
1313

1314
/* Substitute any appearance of the %u variable in the given string with
1315
 * the value.
1316
 */
1317
const char *path_subst_uservar(pool *path_pool, const char **path) {
11✔
1318
  const char *new_path = NULL, *substr_path = NULL;
11✔
1319
  char *substr = NULL;
11✔
1320
  size_t user_len = 0;
11✔
1321

1322
  /* Sanity check. */
1323
  if (path_pool == NULL ||
22✔
1324
      path == NULL ||
20✔
1325
      !*path) {
9✔
1326
    errno = EINVAL;
3✔
1327
    return NULL;
3✔
1328
  }
1329

1330
  /* If no %u string present, do nothing. */
1331
  if (strstr(*path, "%u") == NULL) {
8✔
1332
    return *path;
1333
  }
1334

1335
  /* Same if there is no user set yet. */
1336
  if (session.user == NULL) {
7✔
1337
    return *path;
1338
  }
1339

1340
  user_len = strlen(session.user);
7✔
1341

1342
  /* First, deal with occurrences of "%u[index]" strings.  Note that
1343
   * with this syntax, the '[' and ']' characters become invalid in paths,
1344
   * but only if that '[' appears after a "%u" string -- certainly not
1345
   * a common phenomenon (I hope).  This means that in the future, an escape
1346
   * mechanism may be needed in this function.  Caveat emptor.
1347
   */
1348

1349
  substr_path = *path;
7✔
1350
  substr = substr_path ? strstr(substr_path, "%u[") : NULL;
7✔
1351
  while (substr != NULL) {
18✔
1352
    long i = 0;
9✔
1353
    char *substr_end = NULL, *substr_dup = NULL, *endp = NULL;
9✔
1354
    char ref_char[2] = {'\0', '\0'};
9✔
1355

1356
    pr_signals_handle();
9✔
1357

1358
    /* Now, find the closing ']'. If not found, it is a syntax error;
1359
     * continue on without processing this occurrence.
1360
     */
1361
    substr_end = strchr(substr, ']');
9✔
1362
    if (substr_end == NULL) {
9✔
1363
      /* Just end here. */
1364
      break;
1365
    }
1366

1367
    /* Make a copy of the entire substring. */
1368
    substr_dup = pstrdup(path_pool, substr);
8✔
1369

1370
    /* The substr_end variable (used as an index) should work here, too
1371
     * (trying to obtain the entire substring).
1372
     */
1373
    substr_dup[substr_end - substr + 1] = '\0';
8✔
1374

1375
    /* Advance the substring pointer by three characters, so that it is
1376
     * pointing at the character after the '['.
1377
     */
1378
    substr += 3;
8✔
1379

1380
    /* If the closing ']' is the next character after the opening '[', it
1381
     * is a syntax error.
1382
     */
1383
    if (*substr == ']') {
8✔
1384
      substr_path = *path;
1✔
1385
      break;
1✔
1386
    }
1387

1388
    /* Temporarily set the ']' to '\0', to make it easy for the string
1389
     * scanning below.
1390
     */
1391
    *substr_end = '\0';
7✔
1392

1393
    /* Scan the index string into a number, watching for bad strings. */
1394
    i = strtol(substr, &endp, 10);
7✔
1395
    if (endp && *endp) {
7✔
1396
      *substr_end = ']';
1✔
1397
      pr_trace_msg("auth", 3,
1✔
1398
        "invalid index number syntax found in '%s', ignoring", substr);
1399
      return *path;
4✔
1400
    }
1401

1402
    /* Make sure that index is within bounds. */
1403
    if (i < 0 ||
11✔
1404
        (size_t) i > user_len - 1) {
5✔
1405

1406
      /* Put the closing ']' back. */
1407
      *substr_end = ']';
2✔
1408

1409
      if (i < 0) {
2✔
1410
        pr_trace_msg("auth", 3,
1✔
1411
          "out-of-bounds index number (%ld) found in '%s', ignoring", i,
1412
          substr);
1413

1414
      } else {
1415
        pr_trace_msg("auth", 3,
1✔
1416
          "out-of-bounds index number (%ld > %lu) found in '%s', ignoring", i,
1417
          (unsigned long) user_len-1, substr);
1418
      }
1419

1420
      return *path;
2✔
1421
    }
1422

1423
    ref_char[0] = session.user[i];
4✔
1424

1425
    /* Put the closing ']' back. */
1426
    *substr_end = ']';
4✔
1427

1428
    /* Now, to substitute the whole "%u[index]" substring with the
1429
     * referenced character/string.
1430
     */
1431
    substr_path = sreplace(path_pool, substr_path, substr_dup, ref_char, NULL);
4✔
1432
    substr = substr_path ? strstr(substr_path, "%u[") : NULL;
4✔
1433
  }
1434

1435
  /* Check for any bare "%u", and handle those if present. */
1436
  if (substr_path &&
8✔
1437
      strstr(substr_path, "%u") != NULL) {
4✔
1438
    new_path = sreplace(path_pool, substr_path, "%u", session.user, NULL);
4✔
1439

1440
  } else {
1441
    new_path = substr_path;
1442
  }
1443

1444
  return new_path;
1445
}
1446

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc