Coverage Report

Created: 2026-02-23 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proftpd/src/support.c
Line
Count
Source
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
0
static void mask_signals(unsigned char block) {
56
0
  static sigset_t mask_sigset;
57
58
0
  if (block) {
59
0
    sigemptyset(&mask_sigset);
60
61
0
    sigaddset(&mask_sigset, SIGTERM);
62
0
    sigaddset(&mask_sigset, SIGCHLD);
63
0
    sigaddset(&mask_sigset, SIGUSR1);
64
0
    sigaddset(&mask_sigset, SIGINT);
65
0
    sigaddset(&mask_sigset, SIGQUIT);
66
0
    sigaddset(&mask_sigset, SIGALRM);
67
0
#ifdef SIGIO
68
0
    sigaddset(&mask_sigset, SIGIO);
69
0
#endif
70
0
#ifdef SIGBUS
71
0
    sigaddset(&mask_sigset, SIGBUS);
72
0
#endif
73
0
    sigaddset(&mask_sigset, SIGHUP);
74
75
0
    if (sigprocmask(SIG_BLOCK, &mask_sigset, NULL) < 0) {
76
0
      pr_log_pri(PR_LOG_NOTICE,
77
0
        "unable to block signal set: %s", strerror(errno));
78
0
    }
79
80
0
  } else {
81
0
    if (sigprocmask(SIG_UNBLOCK, &mask_sigset, NULL) < 0) {
82
0
      pr_log_pri(PR_LOG_NOTICE,
83
0
        "unable to unblock signal set: %s", strerror(errno));
84
0
    }
85
0
  }
86
0
}
87
88
0
void pr_signals_block(void) {
89
0
  if (sigs_nblocked == 0) {
90
0
    mask_signals(TRUE);
91
0
    pr_trace_msg("signal", 5, "signals blocked");
92
93
0
  } else {
94
0
    pr_trace_msg("signal", 9, "signals already blocked (block count = %u)",
95
0
      sigs_nblocked);
96
0
  }
97
98
0
  sigs_nblocked++;
99
0
}
100
101
0
void pr_signals_unblock(void) {
102
0
  if (sigs_nblocked == 0) {
103
0
    pr_trace_msg("signal", 5, "signals already unblocked");
104
0
    pr_signals_handle();
105
0
    return;
106
0
  }
107
108
0
  if (sigs_nblocked == 1) {
109
0
    mask_signals(FALSE);
110
0
    pr_trace_msg("signal", 5, "signals unblocked");
111
0
    pr_signals_handle();
112
113
0
  } else {
114
0
    pr_trace_msg("signal", 9, "signals already unblocked (block count = %u)",
115
0
      sigs_nblocked);
116
0
  }
117
118
0
  sigs_nblocked--;
119
0
}
120
121
void schedule(void (*cb)(void *, void *, void *, void *), int nloops,
122
0
    void *arg1, void *arg2, void *arg3, void *arg4) {
123
0
  pool *p, *sub_pool;
124
0
  sched_t *s;
125
126
0
  if (cb == NULL ||
127
0
      nloops < 0) {
128
0
    return;
129
0
  }
130
131
0
  if (scheds == NULL) {
132
0
    p = make_sub_pool(permanent_pool);
133
0
    pr_pool_tag(p, "Schedules Pool");
134
0
    scheds = xaset_create(p, NULL);
135
136
0
  } else {
137
0
    p = scheds->pool;
138
0
  }
139
140
0
  sub_pool = make_sub_pool(p);
141
0
  pr_pool_tag(sub_pool, "schedule pool");
142
143
0
  s = pcalloc(sub_pool, sizeof(sched_t));
144
0
  s->pool = sub_pool;
145
0
  s->cb = cb;
146
0
  s->arg1 = arg1;
147
0
  s->arg2 = arg2;
148
0
  s->arg3 = arg3;
149
0
  s->arg4 = arg4;
150
0
  s->nloops = nloops;
151
0
  xaset_insert(scheds, (xasetmember_t *) s);
152
0
}
153
154
0
void run_schedule(void) {
155
0
  sched_t *s, *snext;
156
157
0
  if (scheds == NULL ||
158
0
      scheds->xas_list == NULL) {
159
0
    return;
160
0
  }
161
162
0
  for (s = (sched_t *) scheds->xas_list; s; s = snext) {
163
0
    snext = s->next;
164
165
0
    pr_signals_handle();
166
167
0
    if (s->nloops-- <= 0) {
168
0
      s->cb(s->arg1, s->arg2, s->arg3, s->arg4);
169
0
      xaset_remove(scheds, (xasetmember_t *) s);
170
0
      destroy_pool(s->pool);
171
0
    }
172
0
  }
173
0
}
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
0
static int get_fpathconf_name_max(int fd, long *name_max) {
187
0
#if defined(HAVE_FPATHCONF)
188
0
  *name_max = fpathconf(fd, _PC_NAME_MAX);
189
0
  return 0;
190
#else
191
  errno = ENOSYS;
192
  return -1;
193
#endif /* HAVE_FPATHCONF */
194
0
}
195
196
0
static int get_pathconf_name_max(char *dir, long *name_max) {
197
0
#if defined(HAVE_PATHCONF)
198
0
  *name_max = pathconf(dir, _PC_NAME_MAX);
199
0
  return 0;
200
#else
201
  errno = ENOSYS;
202
  return -1;
203
#endif /* HAVE_PATHCONF */
204
0
}
205
206
0
long get_name_max(char *dir_name, int dir_fd) {
207
0
  int res;
208
0
  long name_max = 0;
209
210
0
  if (dir_name == NULL &&
211
0
      dir_fd < 0) {
212
0
    errno = EINVAL;
213
0
    return -1;
214
0
  }
215
216
  /* Try the fd first. */
217
0
  if (dir_fd >= 0) {
218
0
    res = get_fpathconf_name_max(dir_fd, &name_max);
219
0
    if (res == 0) {
220
0
      if (name_max < 0) {
221
0
        int xerrno = errno;
222
223
0
        pr_log_debug(DEBUG5, "fpathconf() error for fd %d: %s", dir_fd,
224
0
          strerror(xerrno));
225
226
0
        errno = xerrno;
227
0
        return -1;
228
0
      }
229
230
0
      return name_max;
231
0
    }
232
0
  }
233
234
  /* Name, then. */
235
0
  if (dir_name != NULL) {
236
0
    res = get_pathconf_name_max(dir_name, &name_max);
237
0
    if (res == 0) {
238
0
      if (name_max < 0) {
239
0
        int xerrno = errno;
240
241
0
        pr_log_debug(DEBUG5, "pathconf() error for name '%s': %s", dir_name,
242
0
          strerror(xerrno));
243
244
0
        errno = xerrno;
245
0
        return -1;
246
0
      }
247
248
0
      return name_max;
249
0
    }
250
0
  }
251
252
0
  errno = ENOSYS;
253
0
  return -1;
254
0
}
255
256
/* Interpolates a pathname, expanding ~ notation if necessary
257
 */
258
0
char *dir_interpolate(pool *p, const char *path) {
259
0
  char *res = NULL;
260
261
0
  if (p == NULL ||
262
0
      path == NULL) {
263
0
    errno = EINVAL;
264
0
    return NULL;
265
0
  }
266
267
0
  if (*path == '~') {
268
0
    char *ptr, *user;
269
270
0
    user = pstrdup(p, path + 1);
271
0
    ptr = strchr(user, '/');
272
0
    if (ptr != NULL) {
273
0
      *ptr++ = '\0';
274
0
    }
275
276
0
    if (user[0] == '\0') {
277
0
      user = (char *) session.user;
278
0
    }
279
280
0
    if (session.user != NULL &&
281
0
        strcmp(user, session.user) == 0 &&
282
0
        session.user_homedir != NULL) {
283
0
      res = pdircat(p, session.user_homedir, ptr, NULL);
284
285
0
    } else {
286
0
      struct passwd *pw;
287
288
0
      pw = pr_auth_getpwnam(p, user);
289
0
      if (pw == NULL) {
290
0
        errno = ENOENT;
291
0
        return NULL;
292
0
      }
293
294
0
      res = pdircat(p, pw->pw_dir, ptr, NULL);
295
0
    }
296
297
0
  } else {
298
0
    res = pstrdup(p, path);
299
0
  }
300
301
0
  return res;
302
0
}
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
0
char *dir_best_path(pool *p, const char *path) {
308
0
  char workpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
309
0
  char realpath_buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
310
0
  char *target = NULL, *ntarget;
311
0
  int fini = 0;
312
313
0
  if (p == NULL ||
314
0
      path == NULL) {
315
0
    errno = EINVAL;
316
0
    return NULL;
317
0
  }
318
319
0
  if (*path == '~') {
320
0
    if (pr_fs_interpolate(path, workpath, sizeof(workpath)-1) != 1) {
321
0
      if (pr_fs_dircat(workpath, sizeof(workpath), pr_fs_getcwd(), path) < 0) {
322
0
        return NULL;
323
0
      }
324
0
    }
325
326
0
  } else {
327
0
    if (pr_fs_dircat(workpath, sizeof(workpath), pr_fs_getcwd(), path) < 0) {
328
0
      return NULL;
329
0
    }
330
0
  }
331
332
0
  pr_fs_clean_path(pstrdup(p, workpath), workpath, sizeof(workpath)-1);
333
334
0
  while (!fini && *workpath) {
335
0
    pr_signals_handle();
336
337
0
    if (pr_fs_resolve_path(workpath, realpath_buf,
338
0
        sizeof(realpath_buf)-1, 0) != -1) {
339
0
      break;
340
0
    }
341
342
0
    ntarget = strrchr(workpath, '/');
343
0
    if (ntarget) {
344
0
      if (target) {
345
0
        if (pr_fs_dircat(workpath, sizeof(workpath), workpath, target) < 0) {
346
0
          return NULL;
347
0
        }
348
0
      }
349
350
0
      target = ntarget;
351
0
      *target++ = '\0';
352
353
0
    } else {
354
0
      fini++;
355
0
    }
356
0
  }
357
358
0
  if (!fini && *workpath) {
359
0
    if (target) {
360
0
      if (pr_fs_dircat(workpath, sizeof(workpath), realpath_buf, target) < 0) {
361
0
        return NULL;
362
0
      }
363
364
0
    } else {
365
0
      sstrncpy(workpath, realpath_buf, sizeof(workpath));
366
0
    }
367
368
0
  } else {
369
0
    if (pr_fs_dircat(workpath, sizeof(workpath), "/", target) < 0) {
370
0
      return NULL;
371
0
    }
372
0
  }
373
374
0
  return pstrdup(p, workpath);
375
0
}
376
377
0
char *dir_canonical_path(pool *p, const char *path) {
378
0
  char buf[PR_TUNABLE_PATH_MAX + 1]  = {'\0'};
379
0
  char work[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
380
381
0
  if (p == NULL ||
382
0
      path == NULL) {
383
0
    errno = EINVAL;
384
0
    return NULL;
385
0
  }
386
387
0
  if (*path == '~') {
388
0
    if (pr_fs_interpolate(path, work, sizeof(work)-1) != 1) {
389
0
      if (pr_fs_dircat(work, sizeof(work), pr_fs_getcwd(), path) < 0) {
390
0
        return NULL;
391
0
      }
392
0
    }
393
394
0
  } else {
395
0
    if (pr_fs_dircat(work, sizeof(work), pr_fs_getcwd(), path) < 0) {
396
0
      return NULL;
397
0
    }
398
0
  }
399
400
0
  pr_fs_clean_path(work, buf, sizeof(buf)-1);
401
0
  return pstrdup(p, buf);
402
0
}
403
404
0
char *dir_canonical_vpath(pool *p, const char *path) {
405
0
  char buf[PR_TUNABLE_PATH_MAX + 1]  = {'\0'};
406
0
  char work[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
407
408
0
  if (p == NULL ||
409
0
      path == NULL) {
410
0
    errno = EINVAL;
411
0
    return NULL;
412
0
  }
413
414
0
  if (*path == '~') {
415
0
    if (pr_fs_interpolate(path, work, sizeof(work)-1) != 1) {
416
0
      if (pr_fs_dircat(work, sizeof(work), pr_fs_getvwd(), path) < 0) {
417
0
        return NULL;
418
0
      }
419
0
    }
420
421
0
  } else {
422
0
    if (pr_fs_dircat(work, sizeof(work), pr_fs_getvwd(), path) < 0) {
423
0
      return NULL;
424
0
    }
425
0
  }
426
427
0
  pr_fs_clean_path(work, buf, sizeof(buf)-1);
428
0
  return pstrdup(p, buf);
429
0
}
430
431
/* Performs chroot-aware handling of symlinks. */
432
int dir_readlink(pool *p, const char *path, char *buf, size_t bufsz,
433
0
    int flags) {
434
0
  int is_abs_dst, clean_flags, len, res = -1;
435
0
  size_t chroot_pathlen = 0, adj_pathlen = 0;
436
0
  char *dst_path, *adj_path;
437
0
  pool *tmp_pool;
438
439
0
  if (p == NULL ||
440
0
      path == NULL ||
441
0
      buf == NULL) {
442
0
    errno = EINVAL;
443
0
    return -1;
444
0
  }
445
446
0
  if (bufsz == 0) {
447
0
    return 0;
448
0
  }
449
450
0
  len = pr_fsio_readlink(path, buf, bufsz);
451
0
  if (len < 0) {
452
0
    return -1;
453
0
  }
454
455
0
  pr_trace_msg("fsio", 9,
456
0
    "dir_readlink() read link '%.*s' for path '%s'", (int) len, buf, path);
457
458
0
  if (len == 0 ||
459
0
      (size_t) len == bufsz) {
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
0
    return len;
465
0
  }
466
467
0
  is_abs_dst = FALSE;
468
0
  if (*buf == '/') {
469
0
    is_abs_dst = TRUE;
470
0
  }
471
472
0
  if (session.chroot_path != NULL) {
473
0
    chroot_pathlen = strlen(session.chroot_path);
474
0
  }
475
476
0
  if (chroot_pathlen <= 1) {
477
0
    char *ptr;
478
479
0
    if (is_abs_dst == TRUE ||
480
0
        !(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) {
481
0
      return len;
482
0
    }
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
0
    ptr = strrchr(path, '/');
488
0
    if (ptr != NULL &&
489
0
        ptr != path) {
490
0
      char *parent_dir;
491
492
0
      tmp_pool = make_sub_pool(p);
493
0
      pr_pool_tag(tmp_pool, "dir_readlink pool");
494
495
0
      parent_dir = pstrndup(tmp_pool, path, (ptr - path));
496
0
      dst_path = pdircat(tmp_pool, parent_dir, buf, NULL);
497
498
0
      adj_pathlen = bufsz + 1;
499
0
      adj_path = pcalloc(tmp_pool, adj_pathlen);
500
501
0
      res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, 0);
502
0
      if (res == 0) {
503
0
        pr_trace_msg("fsio", 19,
504
0
          "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path);
505
0
        dst_path = adj_path;
506
0
      }
507
508
0
      pr_trace_msg("fsio", 19,
509
0
        "adjusted relative symlink path '%s', yielding '%s'", buf, dst_path);
510
511
0
      memset(buf, '\0', bufsz);
512
0
      sstrncpy(buf, dst_path, bufsz);
513
0
      len = strlen(buf);
514
0
      destroy_pool(tmp_pool);
515
0
    }
516
517
0
    return len;
518
0
  }
519
520
0
  if (is_abs_dst == FALSE) {
521
    /* If we are to ignore relative destination paths, return now. */
522
0
    if (!(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) {
523
0
      return len;
524
0
    }
525
0
  }
526
527
0
  if (is_abs_dst == TRUE &&
528
0
      (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
0
    return len;
534
0
  }
535
536
0
  tmp_pool = make_sub_pool(p);
537
0
  pr_pool_tag(tmp_pool, "dir_readlink pool");
538
539
0
  dst_path = pstrdup(tmp_pool, buf);
540
0
  if (is_abs_dst == FALSE) {
541
0
    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
0
    ptr = strrchr(path, '/');
548
0
    if (ptr != NULL) {
549
0
      if (ptr != path) {
550
0
        char *parent_dir;
551
552
0
        parent_dir = pstrndup(tmp_pool, path, (ptr - path));
553
0
        dst_path = pdircat(tmp_pool, parent_dir, dst_path, NULL);
554
555
0
      } else {
556
0
        dst_path = pdircat(tmp_pool, "/", dst_path, NULL);
557
0
      }
558
0
    }
559
0
  }
560
561
0
  adj_pathlen = bufsz + 1;
562
0
  adj_path = pcalloc(tmp_pool, adj_pathlen);
563
564
0
  clean_flags = PR_FSIO_CLEAN_PATH_FL_MAKE_ABS_PATH;
565
0
  res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, clean_flags);
566
0
  if (res == 0) {
567
0
    pr_trace_msg("fsio", 19,
568
0
      "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path);
569
0
    dst_path = adj_path;
570
571
0
    memset(buf, '\0', bufsz);
572
0
    sstrncpy(buf, dst_path, bufsz);
573
0
    len = strlen(dst_path);
574
0
  }
575
576
0
  if (strncmp(dst_path, session.chroot_path, chroot_pathlen) == 0 &&
577
0
      *(dst_path + chroot_pathlen) == '/') {
578
0
    char *ptr;
579
580
0
    ptr = dst_path + chroot_pathlen;
581
582
0
    if (is_abs_dst == FALSE &&
583
0
        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
0
      ptr = pstrcat(tmp_pool, ".", ptr, NULL);
589
0
    }
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
0
    pr_trace_msg("fsio", 19,
596
0
      "adjusted symlink path '%s' for chroot '%s', yielding '%s'",
597
0
      dst_path, session.chroot_path, ptr);
598
599
0
    memset(buf, '\0', bufsz);
600
0
    sstrncpy(buf, ptr, bufsz);
601
0
    len = strlen(buf);
602
0
  }
603
604
0
  destroy_pool(tmp_pool);
605
0
  return len;
606
0
}
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
0
char *dir_realpath(pool *p, const char *path) {
612
0
  char buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
613
614
0
  if (p == NULL ||
615
0
      path == NULL) {
616
0
    errno = EINVAL;
617
0
    return NULL;
618
0
  }
619
620
0
  if (pr_fs_resolve_partial(path, buf, sizeof(buf)-1, 0) < 0) {
621
0
    return NULL;
622
0
  }
623
624
0
  return pstrdup(p, buf);
625
0
}
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
0
char *dir_abs_path(pool *p, const char *path, int interpolate) {
632
0
  char *res = NULL;
633
634
0
  if (p == NULL ||
635
0
      path == NULL) {
636
0
    errno = EINVAL;
637
0
    return NULL;
638
0
  }
639
640
0
  if (interpolate) {
641
0
    char buf[PR_TUNABLE_PATH_MAX+1];
642
643
0
    memset(buf, '\0', sizeof(buf));
644
0
    switch (pr_fs_interpolate(path, buf, sizeof(buf)-1)) {
645
0
      case -1:
646
0
        return NULL;
647
648
0
      case 0:
649
        /* Do nothing; path exists */
650
0
        break;
651
652
0
      case 1:
653
        /* Interpolation occurred; make a copy of the interpolated path. */
654
0
        path = pstrdup(p, buf);
655
0
        break;
656
0
    }
657
0
  }
658
659
0
  if (*path != '/') {
660
0
    if (session.chroot_path) {
661
0
      res = pdircat(p, session.chroot_path, pr_fs_getcwd(), path, NULL);
662
663
0
    } else {
664
0
      res = pdircat(p, pr_fs_getcwd(), path, NULL);
665
0
    }
666
667
0
  } else {
668
0
    if (session.chroot_path) {
669
0
      if (strncmp(path, session.chroot_path,
670
0
          strlen(session.chroot_path)) != 0) {
671
0
        res = pdircat(p, session.chroot_path, path, NULL);
672
673
0
      } else {
674
0
        res = pstrdup(p, path);
675
0
      }
676
677
0
    } else {
678
0
      res = pstrdup(p, path);
679
0
    }
680
0
  }
681
682
0
  return res;
683
0
}
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,
690
0
    int rcount) {
691
0
  char buf[PR_TUNABLE_PATH_MAX + 1];
692
0
  struct stat st;
693
0
  int i;
694
695
0
  if (++rcount >= PR_FSIO_MAX_LINK_COUNT) {
696
0
    errno = ELOOP;
697
0
    return 0;
698
0
  }
699
700
0
  memset(buf, '\0', sizeof(buf));
701
702
0
  if (p != NULL) {
703
0
    i = dir_readlink(p, path, buf, sizeof(buf)-1,
704
0
      PR_DIR_READLINK_FL_HANDLE_REL_PATH);
705
706
0
  } else {
707
0
    i = pr_fsio_readlink(path, buf, sizeof(buf)-1);
708
0
  }
709
710
0
  if (i < 0) {
711
0
    return (mode_t) 0;
712
0
  }
713
0
  buf[i] = '\0';
714
715
0
  pr_fs_clear_cache2(buf);
716
0
  if (pr_fsio_lstat(buf, &st) >= 0) {
717
0
    if (st.st_ino > 0 &&
718
0
        (ino_t) st.st_ino == last_inode) {
719
0
      errno = ELOOP;
720
0
      return 0;
721
0
    }
722
723
0
    if (S_ISLNK(st.st_mode)) {
724
0
      return get_symlink_mode(p, buf, (ino_t) st.st_ino, rcount);
725
0
    }
726
727
0
    return st.st_mode;
728
0
  }
729
730
0
  return 0;
731
0
}
732
733
0
mode_t symlink_mode2(pool *p, const char *path) {
734
0
  if (path == NULL) {
735
0
    errno = EINVAL;
736
0
    return 0;
737
0
  }
738
739
0
  return get_symlink_mode(p, path, (ino_t) 0, 0);
740
0
}
741
742
0
mode_t symlink_mode(const char *path) {
743
0
  return symlink_mode2(NULL, path);
744
0
}
745
746
0
mode_t file_mode2(pool *p, const char *path) {
747
0
  struct stat st;
748
0
  mode_t mode = 0;
749
750
0
  if (path == NULL) {
751
0
    errno = EINVAL;
752
0
    return mode;
753
0
  }
754
755
0
  pr_fs_clear_cache2(path);
756
0
  if (pr_fsio_lstat(path, &st) >= 0) {
757
0
    if (S_ISLNK(st.st_mode)) {
758
0
      mode = get_symlink_mode(p, path, (ino_t) 0, 0);
759
0
      if (mode == 0) {
760
  /* a dangling symlink, but it exists to rename or delete. */
761
0
  mode = st.st_mode;
762
0
      }
763
764
0
    } else {
765
0
      mode = st.st_mode;
766
0
    }
767
0
  }
768
769
0
  return mode;
770
0
}
771
772
0
mode_t file_mode(const char *path) {
773
0
  return file_mode2(NULL, path);
774
0
}
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
0
static int path_exists(pool *p, const char *path, int flags) {
782
0
  mode_t mode;
783
784
0
  mode = file_mode2(p, path);
785
0
  if (mode != 0) {
786
0
    switch (flags) {
787
0
      case 1:
788
0
        if (!S_ISDIR(mode)) {
789
0
          return FALSE;
790
0
        }
791
0
        break;
792
793
0
      case 0:
794
0
        if (S_ISDIR(mode)) {
795
0
          return FALSE;
796
0
        }
797
0
        break;
798
799
0
      default:
800
0
        break;
801
0
    }
802
803
0
    return TRUE;
804
0
  }
805
806
0
  return FALSE;
807
0
}
808
809
0
int file_exists2(pool *p, const char *path) {
810
0
  return path_exists(p, path, 0);
811
0
}
812
813
0
int file_exists(const char *path) {
814
0
  return file_exists2(NULL, path);
815
0
}
816
817
0
int dir_exists2(pool *p, const char *path) {
818
0
  return path_exists(p, path, 1);
819
0
}
820
821
0
int dir_exists(const char *path) {
822
0
  return dir_exists2(NULL, path);
823
0
}
824
825
0
int exists2(pool *p, const char *path) {
826
0
  return path_exists(p, path, -1);
827
0
}
828
829
0
int exists(const char *path) {
830
0
  return exists2(NULL, path);
831
0
}
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
0
char *safe_token(char **s) {
839
0
  char *res = "";
840
841
0
  if (s == NULL ||
842
0
      !*s) {
843
0
    return res;
844
0
  }
845
846
0
  while (PR_ISSPACE(**s) && **s) {
847
0
    (*s)++;
848
0
  }
849
850
0
  if (**s) {
851
0
    res = *s;
852
853
0
    while (!PR_ISSPACE(**s) && **s) {
854
0
      (*s)++;
855
0
    }
856
857
0
    if (**s) {
858
0
      *(*s)++ = '\0';
859
0
    }
860
861
0
    while (PR_ISSPACE(**s) && **s) {
862
0
      (*s)++;
863
0
    }
864
0
  }
865
866
0
  return res;
867
0
}
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,
874
0
    time_t *disc, char *msg, size_t msg_size) {
875
0
  FILE *fp;
876
0
  char *deny_str, *disc_str, *cp, buf[PR_TUNABLE_BUFFER_SIZE+1] = {'\0'};
877
0
  char hr[3] = {'\0'}, mn[3] = {'\0'};
878
0
  time_t now, shuttime = (time_t) 0;
879
0
  struct stat st;
880
0
  struct tm *tm;
881
882
0
  if (path == NULL) {
883
0
    errno = EINVAL;
884
0
    return -1;
885
0
  }
886
887
0
  fp = fopen(path, "r");
888
0
  if (fp == NULL) {
889
0
    return -1;
890
0
  }
891
892
0
  if (fstat(fileno(fp), &st) == 0) {
893
0
    if (S_ISDIR(st.st_mode)) {
894
0
      fclose(fp);
895
0
      errno = EISDIR;
896
0
      return -1;
897
0
    }
898
0
  }
899
900
0
  cp = fgets(buf, sizeof(buf), fp);
901
0
  if (cp != NULL) {
902
0
    buf[sizeof(buf)-1] = '\0'; CHOP(cp);
903
904
    /* We use this to fill in dst, timezone, etc */
905
0
    time(&now);
906
0
    tm = pr_localtime(p, &now);
907
0
    if (tm == NULL) {
908
0
      fclose(fp);
909
0
      return 0;
910
0
    }
911
912
0
    tm->tm_year = atoi(safe_token(&cp)) - 1900;
913
0
    tm->tm_mon = atoi(safe_token(&cp)) - 1;
914
0
    tm->tm_mday = atoi(safe_token(&cp));
915
0
    tm->tm_hour = atoi(safe_token(&cp));
916
0
    tm->tm_min = atoi(safe_token(&cp));
917
0
    tm->tm_sec = atoi(safe_token(&cp));
918
919
0
    deny_str = safe_token(&cp);
920
0
    disc_str = safe_token(&cp);
921
922
0
    shuttime = mktime(tm);
923
0
    if (shuttime == (time_t) -1) {
924
0
      fclose(fp);
925
0
      return 0;
926
0
    }
927
928
0
    if (deny != NULL) {
929
0
      if (strlen(deny_str) == 4) {
930
0
        sstrncpy(hr, deny_str, sizeof(hr));
931
0
        hr[2] = '\0';
932
0
        deny_str += 2;
933
934
0
        sstrncpy(mn, deny_str, sizeof(mn));
935
0
        mn[2] = '\0';
936
937
0
        *deny = shuttime - ((atoi(hr) * 3600) + (atoi(mn) * 60));
938
939
0
      } else {
940
0
        *deny = shuttime;
941
0
      }
942
0
    }
943
944
0
    if (disc != NULL) {
945
0
      if (strlen(disc_str) == 4) {
946
0
        sstrncpy(hr, disc_str, sizeof(hr));
947
0
        hr[2] = '\0';
948
0
        disc_str += 2;
949
950
0
        sstrncpy(mn, disc_str, sizeof(mn));
951
0
        mn[2] = '\0';
952
953
0
        *disc = shuttime - ((atoi(hr) * 3600) + (atoi(mn) * 60));
954
955
0
      } else {
956
0
        *disc = shuttime;
957
0
      }
958
0
    }
959
960
0
    if (fgets(buf, sizeof(buf), fp) && msg) {
961
0
      buf[sizeof(buf)-1] = '\0';
962
0
      CHOP(buf);
963
0
      sstrncpy(msg, buf, msg_size-1);
964
0
    }
965
0
  }
966
967
0
  fclose(fp);
968
969
0
  if (shut != NULL) {
970
0
    *shut = shuttime;
971
0
  }
972
973
0
  return 1;
974
0
}
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
0
void pr_memscrub(void *ptr, size_t ptrlen) {
988
#if defined(PR_USE_OPENSSL) && OPENSSL_VERSION_NUMBER > 0x000907000L
989
  if (ptr == NULL ||
990
      ptrlen == 0) {
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);
998
999
#else
1000
0
  unsigned char *p;
1001
0
  size_t loop;
1002
1003
0
  if (ptr == NULL ||
1004
0
      ptrlen == 0) {
1005
0
    return;
1006
0
  }
1007
1008
0
  p = ptr;
1009
0
  loop = ptrlen;
1010
1011
0
  while (loop--) {
1012
0
    *(p++) = memscrub_ctr++;
1013
0
    memscrub_ctr += (17 + (unsigned char)((intptr_t) p & 0xF));
1014
0
  }
1015
1016
0
  if (memchr(ptr, memscrub_ctr, ptrlen)) {
1017
0
    memscrub_ctr += 63;
1018
0
  }
1019
0
#endif
1020
0
}
1021
1022
0
void pr_getopt_reset(void) {
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
0
  opterr = 0;
1043
0
  optind = 0;
1044
0
#endif /* !FreeBSD, !Mac OSX and !Solaris2 */
1045
1046
0
  if (pr_env_get(permanent_pool, "POSIXLY_CORRECT") == NULL) {
1047
0
    pr_env_set(permanent_pool, "POSIXLY_CORRECT", "1");
1048
0
  }
1049
0
}
1050
1051
0
struct tm *pr_gmtime(pool *p, const time_t *now) {
1052
0
  struct tm *sys_tm, *dup_tm;
1053
1054
0
  if (now == NULL) {
1055
0
    errno = EINVAL;
1056
0
    return NULL;
1057
0
  }
1058
1059
0
#if defined(HAVE_GMTIME_R)
1060
0
  if (p == NULL) {
1061
0
    errno = EINVAL;
1062
0
    return NULL;
1063
0
  }
1064
1065
0
  sys_tm = gmtime_r(now, pcalloc(p, sizeof(struct tm)));
1066
#else
1067
  sys_tm = gmtime(now);
1068
#endif /* HAVE_GMTIME_R */
1069
0
  if (sys_tm == NULL) {
1070
0
    return NULL;
1071
0
  }
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
0
  if (p != NULL) {
1077
0
    dup_tm = pcalloc(p, sizeof(struct tm));
1078
0
    memcpy(dup_tm, sys_tm, sizeof(struct tm));
1079
1080
0
  } else {
1081
0
    dup_tm = sys_tm;
1082
0
  }
1083
1084
0
  return dup_tm;
1085
0
}
1086
1087
0
struct tm *pr_localtime(pool *p, const time_t *now) {
1088
0
  struct tm *sys_tm, *dup_tm;
1089
1090
0
#ifdef HAVE_TZNAME
1091
0
  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
0
  memcpy(&tzname_dup, tzname, sizeof(tzname_dup));
1119
0
#endif /* HAVE_TZNAME */
1120
1121
0
  if (now == NULL) {
1122
0
    errno = EINVAL;
1123
0
    return NULL;
1124
0
  }
1125
1126
0
#if defined(HAVE_LOCALTIME_R)
1127
0
  if (p == NULL) {
1128
0
    errno = EINVAL;
1129
0
    return NULL;
1130
0
  }
1131
1132
0
  sys_tm = localtime_r(now, pcalloc(p, sizeof(struct tm)));
1133
#else
1134
  sys_tm = localtime(now);
1135
#endif /* HAVE_LOCALTIME_R */
1136
0
  if (sys_tm == NULL) {
1137
0
    return NULL;
1138
0
  }
1139
1140
0
  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
0
    dup_tm = pcalloc(p, sizeof(struct tm));
1145
0
    memcpy(dup_tm, sys_tm, sizeof(struct tm));
1146
1147
0
  } else {
1148
1149
    /* Other callers do not require pool-allocated copies, and instead
1150
     * are happy with the struct tm as is.
1151
     */
1152
0
    dup_tm = sys_tm;
1153
0
  }
1154
1155
0
#if defined(HAVE_TZNAME)
1156
  /* Restore the old tzname values prior to returning. */
1157
0
  memcpy(tzname, tzname_dup, sizeof(tzname_dup));
1158
0
#endif /* HAVE_TZNAME */
1159
1160
0
  return dup_tm;
1161
0
}
1162
1163
0
const char *pr_strtime(time_t t) {
1164
0
  return pr_strtime2(t, FALSE);
1165
0
}
1166
1167
0
const char *pr_strtime2(time_t t, int use_gmtime) {
1168
0
  return pr_strtime3(NULL, t, use_gmtime);
1169
0
}
1170
1171
0
const char *pr_strtime3(pool *p, time_t t, int use_gmtime) {
1172
0
  static char buf[64];
1173
0
  static char *mons[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
1174
0
    "Aug", "Sep", "Oct", "Nov", "Dec" };
1175
0
  static char *days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
1176
0
  struct tm *tr;
1177
1178
0
  memset(buf, '\0', sizeof(buf));
1179
1180
0
  if (use_gmtime) {
1181
0
    tr = pr_gmtime(p, &t);
1182
1183
0
  } else {
1184
0
    tr = pr_localtime(p, &t);
1185
0
  }
1186
1187
0
  if (tr == NULL) {
1188
0
    return NULL;
1189
0
  }
1190
1191
0
  pr_snprintfl(__FILE__, __LINE__, buf, sizeof(buf),
1192
0
    "%s %s %02d %02d:%02d:%02d %d", days[tr->tm_wday], mons[tr->tm_mon],
1193
0
    tr->tm_mday, tr->tm_hour, tr->tm_min, tr->tm_sec, tr->tm_year + 1900);
1194
0
  buf[sizeof(buf)-1] = '\0';
1195
1196
0
  if (p != NULL) {
1197
0
    return pstrdup(p, buf);
1198
0
  }
1199
1200
0
  return buf;
1201
0
}
1202
1203
0
int pr_timeval2millis(struct timeval *tv, uint64_t *millis) {
1204
0
  if (tv == NULL ||
1205
0
      millis == NULL) {
1206
0
    errno = EINVAL;
1207
0
    return -1;
1208
0
  }
1209
1210
  /* Make sure to use 64-bit multiplication to avoid overflow errors,
1211
   * as much as we can.
1212
   */
1213
0
  *millis = (tv->tv_sec * (uint64_t) 1000) + (tv->tv_usec / (uint64_t) 1000);
1214
0
  return 0;
1215
0
}
1216
1217
0
int pr_gettimeofday_millis(uint64_t *millis) {
1218
0
  struct timeval tv;
1219
1220
0
  if (gettimeofday(&tv, NULL) < 0) {
1221
0
    return -1;
1222
0
  }
1223
1224
0
  if (pr_timeval2millis(&tv, millis) < 0) {
1225
0
    return -1;
1226
0
  }
1227
1228
0
  return 0;
1229
0
}
1230
1231
int pr_vsnprintfl(const char *file, int lineno, char *buf, size_t bufsz,
1232
0
    const char *fmt, va_list msg) {
1233
0
  int res, xerrno = 0;
1234
1235
0
  if (buf == NULL ||
1236
0
      fmt == NULL) {
1237
0
    errno = EINVAL;
1238
0
    return -1;
1239
0
  }
1240
1241
0
  if (bufsz == 0) {
1242
0
    return 0;
1243
0
  }
1244
1245
0
  res = vsnprintf(buf, bufsz, fmt, msg);
1246
0
  xerrno = errno;
1247
1248
0
  if (res < 0) {
1249
    /* Unexpected error. */
1250
1251
0
#ifdef EOVERFLOW
1252
0
    if (xerrno == EOVERFLOW) {
1253
0
      xerrno = ENOSPC;
1254
0
    }
1255
0
#endif /* EOVERFLOW */
1256
1257
0
  } else if ((size_t) res >= bufsz) {
1258
    /* Buffer too small. */
1259
0
    xerrno = ENOSPC;
1260
0
    res = -1;
1261
0
  }
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
0
  if (res < 0 &&
1267
0
      xerrno == ENOSPC) {
1268
0
    if (file != NULL &&
1269
0
        lineno > 0) {
1270
0
      pr_log_pri(PR_LOG_WARNING,
1271
0
        "%s:%d: error writing format string '%s' into %lu-byte buffer: %s",
1272
0
        file, lineno, fmt, (unsigned long) bufsz, strerror(xerrno));
1273
1274
0
    } else {
1275
0
      pr_log_pri(PR_LOG_WARNING,
1276
0
        "error writing format string '%s' into %lu-byte buffer: %s", fmt,
1277
0
        (unsigned long) bufsz, strerror(xerrno));
1278
0
    }
1279
1280
0
    pr_log_stacktrace(-1, NULL);
1281
0
  }
1282
1283
0
  errno = xerrno;
1284
0
  return res;
1285
0
}
1286
1287
0
int pr_vsnprintf(char *buf, size_t bufsz, const char *fmt, va_list msg) {
1288
0
  return pr_vsnprintfl(NULL, -1, buf, bufsz, fmt, msg);
1289
0
}
1290
1291
int pr_snprintfl(const char *file, int lineno, char *buf, size_t bufsz,
1292
0
    const char *fmt, ...) {
1293
0
  va_list msg;
1294
0
  int res;
1295
1296
0
  va_start(msg, fmt);
1297
0
  res = pr_vsnprintfl(file, lineno, buf, bufsz, fmt, msg);
1298
0
  va_end(msg);
1299
1300
0
  return res;
1301
0
}
1302
1303
0
int pr_snprintf(char *buf, size_t bufsz, const char *fmt, ...) {
1304
0
  va_list msg;
1305
0
  int res;
1306
1307
0
  va_start(msg, fmt);
1308
0
  res = pr_vsnprintfl(NULL, -1, buf, bufsz, fmt, msg);
1309
0
  va_end(msg);
1310
1311
0
  return res;
1312
0
}
1313
1314
/* Substitute any appearance of the %u variable in the given string with
1315
 * the value.
1316
 */
1317
0
const char *path_subst_uservar(pool *path_pool, const char **path) {
1318
0
  const char *new_path = NULL, *substr_path = NULL;
1319
0
  char *substr = NULL;
1320
0
  size_t user_len = 0;
1321
1322
  /* Sanity check. */
1323
0
  if (path_pool == NULL ||
1324
0
      path == NULL ||
1325
0
      !*path) {
1326
0
    errno = EINVAL;
1327
0
    return NULL;
1328
0
  }
1329
1330
  /* If no %u string present, do nothing. */
1331
0
  if (strstr(*path, "%u") == NULL) {
1332
0
    return *path;
1333
0
  }
1334
1335
  /* Same if there is no user set yet. */
1336
0
  if (session.user == NULL) {
1337
0
    return *path;
1338
0
  }
1339
1340
0
  user_len = strlen(session.user);
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
0
  substr_path = *path;
1350
0
  substr = substr_path ? strstr(substr_path, "%u[") : NULL;
1351
0
  while (substr != NULL) {
1352
0
    long i = 0;
1353
0
    char *substr_end = NULL, *substr_dup = NULL, *endp = NULL;
1354
0
    char ref_char[2] = {'\0', '\0'};
1355
1356
0
    pr_signals_handle();
1357
1358
    /* Now, find the closing ']'. If not found, it is a syntax error;
1359
     * continue on without processing this occurrence.
1360
     */
1361
0
    substr_end = strchr(substr, ']');
1362
0
    if (substr_end == NULL) {
1363
      /* Just end here. */
1364
0
      break;
1365
0
    }
1366
1367
    /* Make a copy of the entire substring. */
1368
0
    substr_dup = pstrdup(path_pool, substr);
1369
1370
    /* The substr_end variable (used as an index) should work here, too
1371
     * (trying to obtain the entire substring).
1372
     */
1373
0
    substr_dup[substr_end - substr + 1] = '\0';
1374
1375
    /* Advance the substring pointer by three characters, so that it is
1376
     * pointing at the character after the '['.
1377
     */
1378
0
    substr += 3;
1379
1380
    /* If the closing ']' is the next character after the opening '[', it
1381
     * is a syntax error.
1382
     */
1383
0
    if (*substr == ']') {
1384
0
      substr_path = *path;
1385
0
      break;
1386
0
    }
1387
1388
    /* Temporarily set the ']' to '\0', to make it easy for the string
1389
     * scanning below.
1390
     */
1391
0
    *substr_end = '\0';
1392
1393
    /* Scan the index string into a number, watching for bad strings. */
1394
0
    i = strtol(substr, &endp, 10);
1395
0
    if (endp && *endp) {
1396
0
      *substr_end = ']';
1397
0
      pr_trace_msg("auth", 3,
1398
0
        "invalid index number syntax found in '%s', ignoring", substr);
1399
0
      return *path;
1400
0
    }
1401
1402
    /* Make sure that index is within bounds. */
1403
0
    if (i < 0 ||
1404
0
        (size_t) i > user_len - 1) {
1405
1406
      /* Put the closing ']' back. */
1407
0
      *substr_end = ']';
1408
1409
0
      if (i < 0) {
1410
0
        pr_trace_msg("auth", 3,
1411
0
          "out-of-bounds index number (%ld) found in '%s', ignoring", i,
1412
0
          substr);
1413
1414
0
      } else {
1415
0
        pr_trace_msg("auth", 3,
1416
0
          "out-of-bounds index number (%ld > %lu) found in '%s', ignoring", i,
1417
0
          (unsigned long) user_len-1, substr);
1418
0
      }
1419
1420
0
      return *path;
1421
0
    }
1422
1423
0
    ref_char[0] = session.user[i];
1424
1425
    /* Put the closing ']' back. */
1426
0
    *substr_end = ']';
1427
1428
    /* Now, to substitute the whole "%u[index]" substring with the
1429
     * referenced character/string.
1430
     */
1431
0
    substr_path = sreplace(path_pool, substr_path, substr_dup, ref_char, NULL);
1432
0
    substr = substr_path ? strstr(substr_path, "%u[") : NULL;
1433
0
  }
1434
1435
  /* Check for any bare "%u", and handle those if present. */
1436
0
  if (substr_path &&
1437
0
      strstr(substr_path, "%u") != NULL) {
1438
0
    new_path = sreplace(path_pool, substr_path, "%u", session.user, NULL);
1439
1440
0
  } else {
1441
0
    new_path = substr_path;
1442
0
  }
1443
1444
0
  return new_path;
1445
0
}
1446