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

proftpd / proftpd / 26127302613

19 May 2026 09:51PM UTC coverage: 93.024% (+0.4%) from 92.635%
26127302613

push

github

51329 of 55178 relevant lines covered (93.02%)

215.14 hits per line

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

90.73
/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-2026 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, see <https://www.gnu.org/licenses/>.
19
 *
20
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
21
 * and other respective copyright holders give permission to link this program
22
 * with OpenSSL, and distribute the resulting executable, without including
23
 * the source code for OpenSSL in the source distribution.
24
 */
25

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

30
#include "conf.h"
31

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

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

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

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

50
static xaset_t *scheds = NULL;
51

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

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

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

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

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

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

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

97
  sigs_nblocked++;
98
}
73✔
99

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

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

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

117
  sigs_nblocked--;
118
}
73✔
119

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

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

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

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

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

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

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

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

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

6✔
164
    pr_signals_handle();
165

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

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

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

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

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

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

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

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

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

229
      return name_max;
230
    }
231
  }
232

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

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

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

247
      return name_max;
248
    }
249
  }
250

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

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

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

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

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

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

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

1✔
284
    } else {
285
      struct passwd *pw;
286

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

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

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

300
  return res;
301
}
302

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

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

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

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

331
  pr_fs_clean_path(pstrdup(p, workpath), workpath, sizeof(workpath)-1);
332

7✔
333
  while (!fini && *workpath) {
334
    pr_signals_handle();
26✔
335

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

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

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

12✔
352
    } else {
353
      fini++;
354
    }
355
  }
356

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

516
    return len;
517
  }
2✔
518

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

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

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

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

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

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

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

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

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

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

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

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

2✔
579
    ptr = dst_path + chroot_pathlen;
580

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

681
  return res;
682
}
683

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

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

699
  memset(buf, '\0', sizeof(buf));
700

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

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

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

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

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

726
    return st.st_mode;
727
  }
728

729
  return 0;
730
}
731

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

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

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

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

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

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

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

768
  return mode;
769
}
770

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

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

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

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

798
      default:
799
        break;
800
    }
801

802
    return TRUE;
6✔
803
  }
804

805
  return FALSE;
806
}
807

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

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

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

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

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

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

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

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

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

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

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

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

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

865
  return res;
866
}
867

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

966
  fclose(fp);
3✔
967

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

972
  return 1;
973
}
974

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

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

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

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

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

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

1007
  p = ptr;
1008
  loop = ptrlen;
1009

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

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

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

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

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

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

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

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

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

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

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

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

1083
  return dup_tm;
4✔
1084
}
1085

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

1089
#ifdef HAVE_TZNAME
1090
  char *tzname_dup[2];
4,928✔
1091

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

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

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

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

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

1146
  } else {
1147

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

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

1159
  return dup_tm;
4,925✔
1160
}
1161

1162
int pr_timingsafe_bcmp(const void *a, const void *b, size_t sz) {
1✔
1163
#if defined(HAVE_TIMINGSAFE_BCMP)
2✔
1164
  return timingsafe_bcmp(a, b, sz);
1165
#else
1166
  const unsigned char *p1, *p2;
2✔
1167
  int res = 0;
2✔
1168

1169
  p1 = a;
1170
  p2 = b;
12✔
1171

12✔
1172
  for (; sz > 0; sz--) {
12✔
1173
    res |= *p1++ ^ *p2++;
1174
  }
12✔
1175
  return (res != 0);
12✔
1176
#endif /* HAVE_TIMINGSAFE_BCMP */
1177
}
12✔
1178

1179
const char *pr_strtime(time_t t) {
12✔
1180
  return pr_strtime2(t, FALSE);
3✔
1181
}
1182

1183
const char *pr_strtime2(time_t t, int use_gmtime) {
9✔
1184
  return pr_strtime3(NULL, t, use_gmtime);
1185
}
1186

12✔
1187
const char *pr_strtime3(pool *p, time_t t, int use_gmtime) {
1188
  static char buf[64];
1189
  static char *mons[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
1190
    "Aug", "Sep", "Oct", "Nov", "Dec" };
9✔
1191
  static char *days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
9✔
1192
  struct tm *tr;
9✔
1193

9✔
1194
  memset(buf, '\0', sizeof(buf));
1195

9✔
1196
  if (use_gmtime) {
9✔
1197
    tr = pr_gmtime(p, &t);
1198

1199
  } else {
1200
    tr = pr_localtime(p, &t);
1201
  }
1202

21✔
1203
  if (tr == NULL) {
21✔
1204
    return NULL;
21✔
1205
  }
3✔
1206

3✔
1207
  pr_snprintfl(__FILE__, __LINE__, buf, sizeof(buf),
1208
    "%s %s %02d %02d:%02d:%02d %d", days[tr->tm_wday], mons[tr->tm_mon],
1209
    tr->tm_mday, tr->tm_hour, tr->tm_min, tr->tm_sec, tr->tm_year + 1900);
1210
  buf[sizeof(buf)-1] = '\0';
1211

1212
  if (p != NULL) {
18✔
1213
    return pstrdup(p, buf);
18✔
1214
  }
1215

1216
  return buf;
14✔
1217
}
14✔
1218

1219
int pr_timeval2millis(struct timeval *tv, uint64_t *millis) {
14✔
1220
  if (tv == NULL ||
1221
      millis == NULL) {
1222
    errno = EINVAL;
1223
    return -1;
14✔
1224
  }
1✔
1225

1226
  /* Make sure to use 64-bit multiplication to avoid overflow errors,
1227
   * as much as we can.
1228
   */
1229
  *millis = (tv->tv_sec * (uint64_t) 1000) + (tv->tv_usec / (uint64_t) 1000);
1230
  return 0;
15,177✔
1231
}
1232

15,177✔
1233
int pr_gettimeofday_millis(uint64_t *millis) {
1234
  struct timeval tv;
15,177✔
1235

15,177✔
1236
  if (gettimeofday(&tv, NULL) < 0) {
4✔
1237
    return -1;
4✔
1238
  }
1239

1240
  if (pr_timeval2millis(&tv, millis) < 0) {
15,173✔
1241
    return -1;
1242
  }
1243

1244
  return 0;
15,171✔
1245
}
15,171✔
1246

1247
int pr_vsnprintfl(const char *file, int lineno, char *buf, size_t bufsz,
15,171✔
1248
    const char *fmt, va_list msg) {
1249
  int res, xerrno = 0;
1250

1251
  if (buf == NULL ||
×
1252
      fmt == NULL) {
×
1253
    errno = EINVAL;
1254
    return -1;
1255
  }
1256

15,171✔
1257
  if (bufsz == 0) {
1258
    return 0;
7✔
1259
  }
7✔
1260

1261
  res = vsnprintf(buf, bufsz, fmt, msg);
1262
  xerrno = errno;
1263

1264
  if (res < 0) {
1265
    /* Unexpected error. */
15,171✔
1266

15,171✔
1267
#ifdef EOVERFLOW
7✔
1268
    if (xerrno == EOVERFLOW) {
7✔
1269
      xerrno = ENOSPC;
2✔
1270
    }
1271
#endif /* EOVERFLOW */
1272

1273
  } else if ((size_t) res >= bufsz) {
1274
    /* Buffer too small. */
5✔
1275
    xerrno = ENOSPC;
1276
    res = -1;
1277
  }
1278

1279
  /* We are mostly concerned with tracking down the locations of truncated
7✔
1280
   * buffers, hence the stacktrace logging only for these conditions.
1281
   */
1282
  if (res < 0 &&
15,171✔
1283
      xerrno == ENOSPC) {
15,171✔
1284
    if (file != NULL &&
1285
        lineno > 0) {
1286
      pr_log_pri(PR_LOG_WARNING,
5,080✔
1287
        "%s:%d: error writing format string '%s' into %lu-byte buffer: %s",
5,080✔
1288
        file, lineno, fmt, (unsigned long) bufsz, strerror(xerrno));
1289

1290
    } else {
15✔
1291
      pr_log_pri(PR_LOG_WARNING,
1292
        "error writing format string '%s' into %lu-byte buffer: %s", fmt,
15✔
1293
        (unsigned long) bufsz, strerror(xerrno));
15✔
1294
    }
1295

15✔
1296
    pr_log_stacktrace(-1, NULL);
15✔
1297
  }
15✔
1298

1299
  errno = xerrno;
15✔
1300
  return res;
1301
}
1302

10,082✔
1303
int pr_vsnprintf(char *buf, size_t bufsz, const char *fmt, va_list msg) {
10,082✔
1304
  return pr_vsnprintfl(NULL, -1, buf, bufsz, fmt, msg);
10,082✔
1305
}
1306

10,082✔
1307
int pr_snprintfl(const char *file, int lineno, char *buf, size_t bufsz,
10,082✔
1308
    const char *fmt, ...) {
10,082✔
1309
  va_list msg;
1310
  int res;
10,082✔
1311

1312
  va_start(msg, fmt);
1313
  res = pr_vsnprintfl(file, lineno, buf, bufsz, fmt, msg);
1314
  va_end(msg);
1315

1316
  return res;
11✔
1317
}
11✔
1318

11✔
1319
int pr_snprintf(char *buf, size_t bufsz, const char *fmt, ...) {
11✔
1320
  va_list msg;
1321
  int res;
1322

11✔
1323
  va_start(msg, fmt);
11✔
1324
  res = pr_vsnprintfl(NULL, -1, buf, bufsz, fmt, msg);
9✔
1325
  va_end(msg);
3✔
1326

3✔
1327
  return res;
1328
}
1329

1330
/* Substitute any appearance of the %u variable in the given string with
8✔
1331
 * the value.
1332
 */
1333
const char *path_subst_uservar(pool *path_pool, const char **path) {
1334
  const char *new_path = NULL, *substr_path = NULL;
1335
  char *substr = NULL;
7✔
1336
  size_t user_len = 0;
1337

1338
  /* Sanity check. */
1339
  if (path_pool == NULL ||
7✔
1340
      path == NULL ||
1341
      !*path) {
1342
    errno = EINVAL;
1343
    return NULL;
1344
  }
1345

1346
  /* If no %u string present, do nothing. */
1347
  if (strstr(*path, "%u") == NULL) {
1348
    return *path;
7✔
1349
  }
7✔
1350

11✔
1351
  /* Same if there is no user set yet. */
9✔
1352
  if (session.user == NULL) {
9✔
1353
    return *path;
9✔
1354
  }
1355

9✔
1356
  user_len = strlen(session.user);
1357

1358
  /* First, deal with occurrences of "%u[index]" strings.  Note that
1359
   * with this syntax, the '[' and ']' characters become invalid in paths,
1360
   * but only if that '[' appears after a "%u" string -- certainly not
9✔
1361
   * a common phenomenon (I hope).  This means that in the future, an escape
9✔
1362
   * mechanism may be needed in this function.  Caveat emptor.
1363
   */
1364

1365
  substr_path = *path;
1366
  substr = substr_path ? strstr(substr_path, "%u[") : NULL;
1367
  while (substr != NULL) {
8✔
1368
    long i = 0;
1369
    char *substr_end = NULL, *substr_dup = NULL, *endp = NULL;
1370
    char ref_char[2] = {'\0', '\0'};
1371

1372
    pr_signals_handle();
8✔
1373

1374
    /* Now, find the closing ']'. If not found, it is a syntax error;
1375
     * continue on without processing this occurrence.
1376
     */
1377
    substr_end = strchr(substr, ']');
8✔
1378
    if (substr_end == NULL) {
1379
      /* Just end here. */
1380
      break;
1381
    }
1382

8✔
1383
    /* Make a copy of the entire substring. */
1✔
1384
    substr_dup = pstrdup(path_pool, substr);
1✔
1385

1386
    /* The substr_end variable (used as an index) should work here, too
1387
     * (trying to obtain the entire substring).
1388
     */
1389
    substr_dup[substr_end - substr + 1] = '\0';
1390

7✔
1391
    /* Advance the substring pointer by three characters, so that it is
1392
     * pointing at the character after the '['.
1393
     */
7✔
1394
    substr += 3;
7✔
1395

1✔
1396
    /* If the closing ']' is the next character after the opening '[', it
1✔
1397
     * is a syntax error.
1398
     */
3✔
1399
    if (*substr == ']') {
1400
      substr_path = *path;
1401
      break;
1402
    }
6✔
1403

5✔
1404
    /* Temporarily set the ']' to '\0', to make it easy for the string
1405
     * scanning below.
1406
     */
2✔
1407
    *substr_end = '\0';
1408

2✔
1409
    /* Scan the index string into a number, watching for bad strings. */
1✔
1410
    i = strtol(substr, &endp, 10);
1411
    if (endp && *endp) {
1412
      *substr_end = ']';
1413
      pr_trace_msg("auth", 3,
1414
        "invalid index number syntax found in '%s', ignoring", substr);
1✔
1415
      return *path;
1416
    }
1417

1418
    /* Make sure that index is within bounds. */
1419
    if (i < 0 ||
2✔
1420
        (size_t) i > user_len - 1) {
1421

1422
      /* Put the closing ']' back. */
4✔
1423
      *substr_end = ']';
1424

1425
      if (i < 0) {
4✔
1426
        pr_trace_msg("auth", 3,
1427
          "out-of-bounds index number (%ld) found in '%s', ignoring", i,
1428
          substr);
1429

1430
      } else {
4✔
1431
        pr_trace_msg("auth", 3,
4✔
1432
          "out-of-bounds index number (%ld > %lu) found in '%s', ignoring", i,
1433
          (unsigned long) user_len-1, substr);
1434
      }
1435

4✔
1436
      return *path;
4✔
1437
    }
4✔
1438

1439
    ref_char[0] = session.user[i];
1440

1441
    /* Put the closing ']' back. */
1442
    *substr_end = ']';
1443

1444
    /* Now, to substitute the whole "%u[index]" substring with the
1445
     * referenced character/string.
1446
     */
1447
    substr_path = sreplace(path_pool, substr_path, substr_dup, ref_char, NULL);
1448
    substr = substr_path ? strstr(substr_path, "%u[") : NULL;
1449
  }
1450

1451
  /* Check for any bare "%u", and handle those if present. */
1452
  if (substr_path &&
1453
      strstr(substr_path, "%u") != NULL) {
1454
    new_path = sreplace(path_pool, substr_path, "%u", session.user, NULL);
1455

1456
  } else {
1457
    new_path = substr_path;
1458
  }
1459

1460
  return new_path;
1461
}
1462

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