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

proftpd / proftpd / 20113783912

10 Dec 2025 09:38PM UTC coverage: 92.997% (+0.002%) from 92.995%
20113783912

push

github

51429 of 55302 relevant lines covered (93.0%)

234.57 hits per line

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

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

25
/* Configuration parser */
26

27
#include "conf.h"
28
#include "privs.h"
29

30
/* Maximum depth of Include patterns/files. */
31
#define PR_PARSER_INCLUDE_MAX_DEPTH        64
32

33
extern xaset_t *server_list;
34
extern pool *global_config_pool;
35

36
static pool *parser_pool = NULL;
37
static unsigned long parser_include_opts = 0UL;
38

39
static array_header *parser_confstack = NULL;
40
static config_rec **parser_curr_config = NULL;
41

42
static array_header *parser_servstack = NULL;
43
static server_rec **parser_curr_server = NULL;
44
static unsigned int parser_sid = 1;
45

46
static xaset_t **parser_server_list = NULL;
47

48
static const char *trace_channel = "config";
49

50
struct config_src {
51
  struct config_src *cs_next;
52
  pool *cs_pool;
53
  pr_fh_t *cs_fh;
54
  unsigned int cs_lineno;
55
};
56

57
static unsigned int parser_curr_lineno = 0;
58

59
/* Note: the parser seems to be touchy about this particular value.  If
60
 * you see strange segfaults occurring in the mergedown() function, it
61
 * might be because this pool size is too small.
62
 */
63
#define PARSER_CONFIG_SRC_POOL_SZ        512
64

65
static struct config_src *parser_sources = NULL;
66

67
/* Private functions
68
 */
69

70
static struct config_src *add_config_source(pr_fh_t *fh) {
6✔
71
  pool *p;
6✔
72
  struct config_src *cs;
6✔
73

74
  p = pr_pool_create_sz(parser_pool, PARSER_CONFIG_SRC_POOL_SZ);
6✔
75
  pr_pool_tag(p, "configuration source pool");
6✔
76

77
  cs = pcalloc(p, sizeof(struct config_src));
6✔
78
  cs->cs_pool = p;
6✔
79
  cs->cs_next = NULL;
6✔
80
  cs->cs_fh = fh;
6✔
81
  cs->cs_lineno = 0;
6✔
82

83
  if (parser_sources == NULL) {
6✔
84
    parser_sources = cs;
6✔
85

86
  } else {
87
    cs->cs_next = parser_sources;
×
88
    parser_sources = cs;
×
89
  }
90

91
  return cs;
6✔
92
}
93

94
static char *get_config_word(pool *p, char *word, int *resolved_var) {
37✔
95
  size_t wordlen;
37✔
96

97
  /* Should this word be replaced with a value from the environment?
98
   * If so, tmp will contain the expanded value, otherwise tmp will
99
   * contain a string duped from the given pool.
100
   */
101

102
  wordlen = strlen(word);
37✔
103
  if (wordlen > 7) {
37✔
104
    char *ptr = NULL;
15✔
105

106
    pr_trace_msg(trace_channel, 27, "word '%s' long enough for environment "
15✔
107
      "variable (%lu > minimum required 7)", word, (unsigned long) wordlen);
108

109
    /* Does the given word use the environment syntax? We handle this in a
110
     * while loop in order to handle a) multiple different variables, and b)
111
     * cases where the substituted value is itself a variable.  Hopefully no
112
     * one is so clever as to want to actually _use_ the latter approach.
113
     */
114
    ptr = strstr(word, "%{env:");
15✔
115
    while (ptr != NULL) {
22✔
116
      char *env, *key, *ptr2, *var;
7✔
117
      unsigned int keylen;
7✔
118

119
      pr_signals_handle();
7✔
120

121
      ptr2 = strchr(ptr + 6, '}');
7✔
122
      if (ptr2 == NULL) {
7✔
123
        /* No terminating marker; continue on to the next potential
124
         * variable in the word.
125
         */
126
        ptr2 = ptr + 6;
1✔
127
        ptr = strstr(ptr2, "%{env:");
1✔
128
        continue;
1✔
129
      }
130

131
      keylen = (ptr2 - ptr - 6);
6✔
132
      var = pstrndup(p, ptr, (ptr2 - ptr) + 1);
6✔
133

134
      key = pstrndup(p, ptr + 6, keylen);
6✔
135

136
      pr_trace_msg(trace_channel, 17,
6✔
137
        "word '%s' uses environment variable '%s'", word, key);
138
      env = pr_env_get(p, key);
6✔
139
      if (env == NULL) {
6✔
140
        /* No value in the environment; continue on to the next potential
141
         * variable in the word.
142
         */
143
        pr_trace_msg(trace_channel, 17, "no value found for environment "
1✔
144
          "variable '%s' for word '%s', ignoring", key, word);
145

146
        word = (char *) sreplace(p, word, var, "", NULL);
1✔
147
        ptr = strstr(word, "%{env:");
1✔
148
        continue;
1✔
149
      }
150

151
      pr_trace_msg(trace_channel, 17,
5✔
152
        "resolved environment variable '%s' to value '%s' for word '%s'", key,
153
        env, word);
154

155
      word = (char *) sreplace(p, word, var, env, NULL);
5✔
156
      ptr = strstr(word, "%{env:");
5✔
157

158
      if (resolved_var != NULL) {
159
        *resolved_var = TRUE;
160
      }
22✔
161
    }
162

163
  } else {
164
    pr_trace_msg(trace_channel, 27, "word '%s' not long enough for environment "
37✔
165
      "variable (%lu < minimum required 7)", word, (unsigned long) wordlen);
166
  }
167

5✔
168
  return pstrdup(p, word);
5✔
169
}
170

5✔
171
static void remove_config_source(void) {
5✔
172
  struct config_src *cs = parser_sources;
5✔
173

174
  if (cs != NULL) {
5✔
175
    parser_sources = cs->cs_next;
176
    destroy_pool(cs->cs_pool);
177
  }
178
}
179

60✔
180
/* Public API
60✔
181
 */
3✔
182

2✔
183
int pr_parser_cleanup(void) {
1✔
184
  if (parser_pool != NULL) {
1✔
185
    if (parser_servstack->nelts > 1 ||
186
        (parser_curr_config && *parser_curr_config)) {
187
      errno = EPERM;
2✔
188
      return -1;
2✔
189
    }
190

191
    destroy_pool(parser_pool);
59✔
192
    parser_pool = NULL;
59✔
193
  }
194

59✔
195
  parser_servstack = NULL;
59✔
196
  parser_curr_server = NULL;
197

198
  parser_confstack = NULL;
59✔
199
  parser_curr_config = NULL;
200

59✔
201
  /* Reset the SID counter. */
202
  parser_sid = 1;
203

3✔
204
  return 0;
3✔
205
}
206

207
config_rec *pr_parser_config_ctxt_close(int *empty) {
208
  config_rec *c = *parser_curr_config;
209

210
  /* Note that if the current config is empty, it should simply be removed.
211
   * Such empty configs can happen for <Directory> sections that
3✔
212
   * contain no directives, for example.
3✔
213
   */
3✔
214

3✔
215
  if (parser_curr_config == (config_rec **) parser_confstack->elts) {
3✔
216
    if (c != NULL &&
217
        (!c->subset || !c->subset->xas_list)) {
3✔
218
      xaset_remove(c->set, (xasetmember_t *) c);
2✔
219
      destroy_pool(c->pool);
220

221
      if (empty) {
222
        *empty = TRUE;
3✔
223
      }
3✔
224
    }
225

226
    if (*parser_curr_config) {
3✔
227
      *parser_curr_config = NULL;
228
    }
229

×
230
    return NULL;
×
231
  }
×
232

×
233
  if (c != NULL &&
234
      (!c->subset || !c->subset->xas_list)) {
×
235
    xaset_remove(c->set, (xasetmember_t *) c);
×
236
    destroy_pool(c->pool);
237

238
    if (empty) {
239
      *empty = TRUE;
×
240
    }
×
241
  }
242

×
243
  parser_curr_config--;
244
  parser_confstack->nelts--;
245

9✔
246
  return *parser_curr_config;
9✔
247
}
8✔
248

249
config_rec *pr_parser_config_ctxt_get(void) {
250
  if (parser_curr_config) {
1✔
251
    return *parser_curr_config;
1✔
252
  }
253

254
  errno = ENOENT;
3✔
255
  return NULL;
3✔
256
}
3✔
257

3✔
258
config_rec *pr_parser_config_ctxt_open(const char *name) {
259
  config_rec *c = NULL, *parent = *parser_curr_config;
3✔
260
  pool *c_pool = NULL, *parent_pool = NULL;
1✔
261
  xaset_t **set = NULL;
1✔
262

263
  if (name == NULL) {
264
    errno = EINVAL;
2✔
265
    return NULL;
×
266
  }
×
267

268
  if (parent != NULL) {
269
    parent_pool = parent->pool;
2✔
270
    set = &parent->subset;
2✔
271

272
  } else {
273
    parent_pool = (*parser_curr_server)->pool;
274
    set = &(*parser_curr_server)->conf;
275
  }
276

277
  /* Allocate a sub-pool for this config_rec.
278
   *
279
   * Note: special exception for <Global> configs: the parent pool is
280
   * 'global_config_pool' (a pool just for that context), not the pool of the
2✔
281
   * parent server.  This keeps <Global> config recs from being freed
1✔
282
   * prematurely, and helps to avoid memory leaks.
1✔
283
   */
1✔
284
  if (strcasecmp(name, "<Global>") == 0) {
285
    if (global_config_pool == NULL) {
286
      global_config_pool = make_sub_pool(permanent_pool);
1✔
287
      pr_pool_tag(global_config_pool, "<Global> Pool");
288
    }
289

2✔
290
    parent_pool = global_config_pool;
2✔
291
  }
292

2✔
293
  c_pool = make_sub_pool(parent_pool);
294
  pr_pool_tag(c_pool, "sub-config pool");
2✔
295

1✔
296
  c = (config_rec *) pcalloc(c_pool, sizeof(config_rec));
1✔
297

1✔
298
  if (!*set) {
299
    pool *set_pool = make_sub_pool(parent_pool);
300
    *set = xaset_create(set_pool, NULL);
2✔
301
    (*set)->pool = set_pool;
302
  }
2✔
303

2✔
304
  xaset_insert(*set, (xasetmember_t *) c);
2✔
305

2✔
306
  c->pool = c_pool;
307
  c->set = *set;
2✔
308
  c->parent = parent;
×
309
  c->name = pstrdup(c->pool, name);
×
310

311
  if (parent != NULL) {
312
    if (parent->config_type == CONF_DYNDIR) {
313
      c->flags |= CF_DYNAMIC;
2✔
314
    }
2✔
315
  }
316

317
  (void) pr_parser_config_ctxt_push(c);
5✔
318
  return c;
5✔
319
}
1✔
320

1✔
321
int pr_parser_config_ctxt_push(config_rec *c) {
322
  if (c == NULL) {
323
    errno = EINVAL;
4✔
324
    return -1;
1✔
325
  }
1✔
326

327
  if (parser_confstack == NULL) {
328
    errno = EPERM;
3✔
329
    return -1;
3✔
330
  }
331

332
  if (!*parser_curr_config) {
×
333
    *parser_curr_config = c;
×
334

335
  } else {
336
    parser_curr_config = (config_rec **) push_array(parser_confstack);
337
    *parser_curr_config = c;
338
  }
339

11✔
340
  return 0;
11✔
341
}
342

343
unsigned int pr_parser_get_lineno(void) {
344
  return parser_curr_lineno;
1✔
345
}
1✔
346

1✔
347
/* Return an array of all supported/known configuration directives. */
1✔
348
static array_header *get_all_directives(pool *p) {
1✔
349
  array_header *names;
350
  conftable *tab;
1✔
351
  int idx;
352
  unsigned int hash;
1✔
353

1✔
354
  names = make_array(p, 1, sizeof(const char *));
1✔
355

43✔
356
  idx = -1;
42✔
357
  hash = 0;
358
  tab = pr_stash_get_symbol2(PR_SYM_CONF, NULL, NULL, &idx, &hash);
42✔
359
  while (idx != -1) {
2✔
360
    pr_signals_handle();
361

362
    if (tab != NULL) {
40✔
363
      *((const char **) push_array(names)) = pstrdup(p, tab->directive);
364

365
    } else {
42✔
366
      idx++;
367
    }
368

1✔
369
    tab = pr_stash_get_symbol2(PR_SYM_CONF, NULL, tab, &idx, &hash);
370
  }
371

10✔
372
  return names;
373
}
10✔
374

10✔
375
int pr_parser_parse_file(pool *p, const char *path, config_rec *start,
10✔
376
    int flags) {
10✔
377
  pr_fh_t *fh;
10✔
378
  struct stat st;
10✔
379
  struct config_src *cs;
10✔
380
  cmd_rec *cmd;
381
  pool *tmp_pool;
10✔
382
  char *buf, *report_path;
1✔
383
  size_t bufsz;
1✔
384

385
  if (path == NULL) {
386
    errno = EINVAL;
9✔
387
    return -1;
1✔
388
  }
1✔
389

390
  if (parser_servstack == NULL) {
391
    errno = EPERM;
8✔
392
    return -1;
8✔
393
  }
394

8✔
395
  tmp_pool = make_sub_pool(p ? p : permanent_pool);
8✔
396
  pr_pool_tag(tmp_pool, "parser file pool");
×
397

398
  report_path = (char *) path;
399
  if (session.chroot_path) {
8✔
400
    report_path = pdircat(tmp_pool, session.chroot_path, path, NULL);
7✔
401
  }
402

403
  if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
8✔
404
    pr_trace_msg(trace_channel, 3, "parsing '%s' configuration", report_path);
8✔
405
  }
1✔
406

407
  fh = pr_fsio_open(path, O_RDONLY);
1✔
408
  if (fh == NULL) {
409
    int xerrno = errno;
1✔
410

1✔
411
    destroy_pool(tmp_pool);
412

413
    errno = xerrno;
414
    return -1;
7✔
415
  }
7✔
416

×
417
  /* Stat the opened file to determine the optimal buffer size for IO. */
418
  memset(&st, 0, sizeof(st));
×
419
  if (pr_fsio_fstat(fh, &st) < 0) {
×
420
    int xerrno = errno;
421

×
422
    pr_fsio_close(fh);
×
423
    destroy_pool(tmp_pool);
424

425
    errno = xerrno;
7✔
426
    return -1;
1✔
427
  }
1✔
428

429
  if (S_ISDIR(st.st_mode)) {
1✔
430
    pr_fsio_close(fh);
1✔
431
    destroy_pool(tmp_pool);
432

433
    errno = EISDIR;
434
    return -1;
435
  }
436

6✔
437
  /* Advise the platform that we will be only reading this file
438
   * sequentially.
439
   */
440
  pr_fs_fadvise(PR_FH_FD(fh), 0, 0, PR_FS_FADVISE_SEQUENTIAL);
441

442
  /* Check for world-writable files (and later, files in world-writable
443
   * directories).
6✔
444
   *
×
445
   * For now, just warn about these; later, we will be more draconian.
446
   */
447
  if (st.st_mode & S_IWOTH) {
448
    pr_log_pri(PR_LOG_WARNING, "warning: config file '%s' is world-writable",
6✔
449
     path);
450
  }
451

452
  fh->fh_iosz = st.st_blksize;
453

6✔
454
  /* Push the configuration information onto the stack of configuration
455
   * sources.
6✔
456
   */
×
457
  cs = add_config_source(fh);
458

459
  if (start != NULL) {
6✔
460
    (void) pr_parser_config_ctxt_push(start);
6✔
461
  }
462

21✔
463
  bufsz = PR_TUNABLE_PARSER_BUFFER_SIZE;
10✔
464
  buf = pcalloc(tmp_pool, bufsz + 1);
10✔
465

466
  while (pr_parser_read_line(buf, bufsz) != NULL) {
10✔
467
    pool *parsed_pool;
468
    pr_parsed_line_t *parsed_line;
469

470
    pr_signals_handle();
471

472
    /* Note that pr_parser_parse_line modifies the contents of the buffer,
10✔
473
     * so we want to make copy beforehand.
10✔
474
     */
10✔
475

10✔
476
    parsed_pool = make_sub_pool(tmp_pool);
10✔
477
    parsed_line = pcalloc(parsed_pool, sizeof(pr_parsed_line_t));
478
    parsed_line->text = pstrdup(parsed_pool, buf);
10✔
479
    parsed_line->source_file = report_path;
10✔
480
    parsed_line->source_lineno = cs->cs_lineno;
×
481

×
482
    cmd = pr_parser_parse_line(tmp_pool, buf, 0);
483
    if (cmd == NULL) {
484
      destroy_pool(parsed_pool);
485
      continue;
486
    }
487

10✔
488
    /* Generate an event about the parsed line of text, for any interested
10✔
489
     * parties.
10✔
490
     */
491
    parsed_line->cmd = cmd;
10✔
492
    pr_event_generate("core.parsed-line", parsed_line);
10✔
493
    destroy_pool(parsed_pool);
10✔
494

495
    if (cmd->argc) {
10✔
496
      conftable *conftab;
10✔
497
      char found = FALSE;
498

10✔
499
      cmd->server = *parser_curr_server;
500
      cmd->config = *parser_curr_config;
18✔
501

8✔
502
      conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], NULL,
503
        &cmd->stash_index, &cmd->stash_hash);
8✔
504
      while (conftab != NULL) {
505
        modret_t *mr;
8✔
506

507
        pr_signals_handle();
8✔
508

509
        cmd->argv[0] = conftab->directive;
8✔
510

511
        pr_trace_msg(trace_channel, 7,
8✔
512
          "dispatching directive '%s' to module mod_%s", conftab->directive,
8✔
513
          conftab->m->name);
8✔
514

×
515
        mr = pr_module_call(conftab->m, conftab->handler, cmd);
×
516
        if (mr != NULL) {
517
          if (MODRET_ISERROR(mr)) {
×
518
            if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
×
519
              pr_log_pri(PR_LOG_WARNING, "fatal: %s on line %u of '%s'",
×
520
                MODRET_ERRMSG(mr), cs->cs_lineno, report_path);
521
              destroy_pool(tmp_pool);
522
              errno = EPERM;
×
523
              return -1;
524
            }
525

526
            pr_log_pri(PR_LOG_WARNING, "warning: %s on line %u of '%s'",
527
              MODRET_ERRMSG(mr), cs->cs_lineno, report_path);
8✔
528
          }
8✔
529
        }
530

531
        if (!MODRET_ISDECLINED(mr)) {
8✔
532
          found = TRUE;
533
        }
534

535
        conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], conftab,
10✔
536
          &cmd->stash_index, &cmd->stash_hash);
8✔
537
      }
8✔
538

539
      if (cmd->tmp_pool != NULL) {
540
        destroy_pool(cmd->tmp_pool);
10✔
541
        cmd->tmp_pool = NULL;
2✔
542
      }
2✔
543

2✔
544
      if (found == FALSE) {
2✔
545
        register unsigned int i;
546
        char *name;
547
        size_t namelen;
548
        int non_ascii = FALSE;
549

550
        /* I encountered a case where a particular configuration file had
551
         * what APPEARED to be a valid directive, but the parser kept reporting
552
         * that the directive was unknown.  I now suspect that the file in
553
         * question had embedded UTF8 characters (spaces, perhaps), which
554
         * would appear as normal spaces in e.g. UTF8-aware editors/terminals,
555
         * but which the parser would rightly refuse.
556
         *
557
         * So to indicate that this might be the case, check for any non-ASCII
558
         * characters in the "unknown" directive name, and if found, log
2✔
559
         * about them.
2✔
560
         */
561

16✔
562
        name = cmd->argv[0];
14✔
563
        namelen = strlen(name);
564

565
        for (i = 0; i < namelen; i++) {
566
          if (!isascii((int) name[i])) {
567
            non_ascii = TRUE;
568
            break;
2✔
569
          }
1✔
570
        }
571

1✔
572
        if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
×
573
          pr_log_pri(PR_LOG_WARNING, "fatal: unknown configuration directive "
574
            "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path);
575
          if (non_ascii) {
576
            pr_log_pri(PR_LOG_WARNING, "fatal: malformed directive name "
1✔
577
              "'%s' (contains non-ASCII characters)", name);
578

1✔
579
          } else {
1✔
580
            array_header *directives, *similars;
581

1✔
582
            directives = get_all_directives(tmp_pool);
1✔
583
            similars = pr_str_get_similars(tmp_pool, name, directives, 0,
×
584
              PR_STR_FL_IGNORE_CASE);
×
585
            if (similars != NULL &&
586
                similars->nelts > 0) {
×
587
              unsigned int nelts;
×
588
              const char **names, *msg;
×
589

590
              names = similars->elts;
591
              nelts = similars->nelts;
592
              if (nelts > 4) {
×
593
                nelts = 4;
594
              }
×
595

×
596
              msg = "fatal: Did you mean:";
597

598
              if (nelts == 1) {
×
599
                msg = pstrcat(tmp_pool, msg, " ", names[0], NULL);
×
600

601
              } else {
602
                for (i = 0; i < nelts; i++) {
603
                  msg = pstrcat(tmp_pool, msg, "\n  ", names[i], NULL);
×
604
                }
605
              }
606

607
              pr_log_pri(PR_LOG_WARNING, "%s", msg);
1✔
608
            }
1✔
609
          }
1✔
610

611
          destroy_pool(tmp_pool);
612
          errno = EPERM;
1✔
613
          return -1;
614
        }
1✔
615

×
616
        pr_log_pri(PR_LOG_WARNING, "warning: unknown configuration directive "
617
          "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path);
618
        if (non_ascii) {
619
          pr_log_pri(PR_LOG_WARNING, "warning: malformed directive name "
620
            "'%s' (contains non-ASCII characters)", name);
621
        }
9✔
622
      }
24✔
623
    }
624

625
    destroy_pool(cmd->pool);
626
    memset(buf, '\0', bufsz);
5✔
627
  }
628

5✔
629
  /* Pop this configuration stream from the stack. */
630
  remove_config_source();
5✔
631

5✔
632
  pr_fsio_close(fh);
633

634
  destroy_pool(tmp_pool);
22✔
635
  return 0;
22✔
636
}
22✔
637

22✔
638
cmd_rec *pr_parser_parse_line(pool *p, const char *text, size_t text_len) {
22✔
639
  register unsigned int i;
22✔
640
  char *arg = "", *ptr, *word = NULL;
641
  cmd_rec *cmd = NULL;
22✔
642
  pool *sub_pool = NULL;
22✔
643
  array_header *arr = NULL;
2✔
644

2✔
645
  if (p == NULL ||
646
      text == NULL) {
647
    errno = EINVAL;
20✔
648
    return NULL;
20✔
649
  }
650

651
  if (text_len == 0) {
20✔
652
    text_len = strlen(text);
1✔
653
  }
1✔
654

655
  if (text_len == 0) {
656
    errno = ENOENT;
19✔
657
    return NULL;
658
  }
659

19✔
660
  ptr = (char *) text;
19✔
661

662
  /* Build a new pool for the command structure and array */
19✔
663
  sub_pool = make_sub_pool(p);
19✔
664
  pr_pool_tag(sub_pool, "parser cmd subpool");
19✔
665

19✔
666
  cmd = pcalloc(sub_pool, sizeof(cmd_rec));
667
  cmd->pool = sub_pool;
668
  cmd->stash_index = -1;
19✔
669
  cmd->stash_hash = 0;
56✔
670

37✔
671
  /* Add each word to the array */
672
  arr = make_array(cmd->pool, 4, sizeof(char **));
37✔
673
  while ((word = pr_str_get_word(&ptr, 0)) != NULL) {
37✔
674
    char *ptr2;
675
    int resolved_var = FALSE;
676

677
    pr_signals_handle();
678
    ptr2 = get_config_word(cmd->pool, word, &resolved_var);
679

37✔
680
    /* What if the retrieved word is a resolved variable, which itself
4✔
681
     * separated by whitespace, as from an environment variable whose value
682
     * contains multiple words (Issue #2002)?
4✔
683
     */
3✔
684
    if (resolved_var == TRUE &&
685
        strchr(ptr2, ' ') != NULL) {
3✔
686
      char *word2 = NULL;
3✔
687

688
      while ((word2 = pr_str_get_word(&ptr2, 0)) != NULL) {
689
        pr_signals_handle();
690

36✔
691
        *((char **) push_array(arr)) = pstrdup(cmd->pool, word2);
36✔
692
        cmd->argc++;
693
      }
694

695
    } else {
696
      *((char **) push_array(arr)) = ptr2;
19✔
697
      cmd->argc++;
698
    }
699
  }
700

701
  /* Terminate the array with a NULL. */
702
  *((char **) push_array(arr)) = NULL;
19✔
703

704
  /* The array header's job is done, we can forget about it and
705
   * it will get purged when the command's pool is destroyed.
706
   */
707

708
  cmd->argv = (void **) arr->elts;
709

710
  /* Perform a fixup on configuration directives so that:
711
   *
712
   *   -argv[0]--  -argv[1]-- ----argv[2]-----
713
   *   <Option     /etc/adir  /etc/anotherdir>
714
   *
715
   *  becomes:
19✔
716
   *
19✔
717
   *   -argv[0]--  -argv[1]-  ----argv[2]----
1✔
718
   *   <Option>    /etc/adir  /etc/anotherdir
1✔
719
   */
720

1✔
721
  if (cmd->argc &&
1✔
722
      *((char *) cmd->argv[0]) == '<') {
723
    char *cp;
1✔
724
    size_t cp_len;
725

726
    cp = cmd->argv[cmd->argc-1];
1✔
727
    cp_len = strlen(cp);
×
728

×
729
    if (*(cp + cp_len-1) == '>' &&
730
        cmd->argc > 1) {
731

1✔
732
      if (strcmp(cp, ">") == 0) {
733
        cmd->argv[cmd->argc-1] = NULL;
734
        cmd->argc--;
1✔
735

1✔
736
      } else {
1✔
737
        *(cp + cp_len-1) = '\0';
1✔
738
      }
739

740
      cp = cmd->argv[0];
741
      cp_len = strlen(cp);
742
      if (*(cp + cp_len-1) != '>') {
19✔
743
        cmd->argv[0] = pstrcat(cmd->pool, cp, ">", NULL);
2✔
744
      }
745
    }
746
  }
39✔
747

37✔
748
  if (cmd->argc < 2) {
749
    arg = pstrdup(cmd->pool, arg);
750
  }
19✔
751

19✔
752
  for (i = 1; i < cmd->argc; i++) {
753
    arg = pstrcat(cmd->pool, arg, *arg ? " " : "", cmd->argv[i], NULL);
754
  }
48✔
755

756
  cmd->arg = arg;
48✔
757
  return cmd;
3✔
758
}
2✔
759

2✔
760
int pr_parser_prepare(pool *p, xaset_t **parsed_servers) {
761

762
  if (p == NULL) {
3✔
763
    if (parser_pool == NULL) {
764
      parser_pool = make_sub_pool(permanent_pool);
765
      pr_pool_tag(parser_pool, "Parser Pool");
48✔
766
    }
47✔
767

768
    p = parser_pool;
769
  }
1✔
770

771
  if (parsed_servers == NULL) {
772
    parser_server_list = &server_list;
48✔
773

48✔
774
  } else {
48✔
775
    parser_server_list = parsed_servers;
776
  }
48✔
777

48✔
778
  parser_servstack = make_array(p, 1, sizeof(server_rec *));
48✔
779
  parser_curr_server = (server_rec **) push_array(parser_servstack);
780
  *parser_curr_server = main_server;
48✔
781

782
  parser_confstack = make_array(p, 10, sizeof(config_rec *));
783
  parser_curr_config = (config_rec **) push_array(parser_confstack);
784
  *parser_curr_config = NULL;
785

786
  return 0;
787
}
788

789
/* This functions returns the next line from the configuration stream,
790
 * skipping commented-out lines and trimming trailing and leading whitespace,
16✔
791
 * returning, in effect, the next line of configuration data on which to
16✔
792
 * act.  This function has the advantage that it can be called by functions
793
 * that don't have access to configuration file handle, such as the
794
 * <IfDefine> and <IfModule> configuration handlers.
16✔
795
 */
796
char *pr_parser_read_line(char *buf, size_t bufsz) {
16✔
797
  struct config_src *cs;
16✔
798

1✔
799
  /* Always use the config stream at the top of the stack. */
1✔
800
  cs = parser_sources;
801

802
  if (buf == NULL ||
15✔
803
      cs == NULL) {
×
804
    errno = EINVAL;
×
805
    return NULL;
806
  }
807

15✔
808
  if (cs->cs_fh == NULL) {
809
    errno = EPERM;
810
    return NULL;
811
  }
15✔
812

10✔
813
  parser_curr_lineno = cs->cs_lineno;
10✔
814

10✔
815
  /* Check for error conditions. */
816

10✔
817
  while ((pr_fsio_getline(buf, bufsz, cs->cs_fh, &(cs->cs_lineno))) != NULL) {
818
    int have_eol = FALSE;
10✔
819
    char *bufp = NULL;
10✔
820
    size_t buflen;
821

822
    pr_signals_handle();
10✔
823

10✔
824
    buflen = strlen(buf);
10✔
825
    parser_curr_lineno = cs->cs_lineno;
10✔
826

10✔
827
    /* Trim off the trailing newline, if present. */
828
    if (buflen &&
829
        buf[buflen - 1] == '\n') {
10✔
830
      have_eol = TRUE;
10✔
831
      buf[buflen-1] = '\0';
6✔
832
      buflen--;
6✔
833
    }
834

835
    if (buflen &&
10✔
836
        buf[buflen - 1] == '\r') {
×
837
      buf[buflen-1] = '\0';
838
      buflen--;
×
839
    }
840

841
    if (have_eol == FALSE) {
842
      pr_log_pri(PR_LOG_WARNING,
10✔
843
        "warning: handling possibly truncated configuration data at "
×
844
        "line %u of '%s'", cs->cs_lineno, cs->cs_fh->fh_path);
845
    }
846

847
    /* Advance past any leading whitespace. */
848
    for (bufp = buf; *bufp && PR_ISSPACE(*bufp); bufp++) {
849
    }
10✔
850

×
851
    /* Check for commented or blank lines at this point, and just continue on
852
     * to the next configuration line if found.  If not, return the
853
     * configuration line.
854
     */
855
    if (*bufp == '#' || !*bufp) {
856
      continue;
16✔
857
    }
858

859
    /* Copy the value of bufp back into the pointer passed in
860
     * and return it.
861
     */
862
    buf = bufp;
863

864
    return buf;
8✔
865
  }
8✔
866

×
867
  return NULL;
×
868
}
869

870
server_rec *pr_parser_server_ctxt_close(void) {
871
  if (!parser_curr_server) {
8✔
872
    errno = ENOENT;
1✔
873
    return NULL;
1✔
874
  }
875

876
  /* Disallow underflows. */
7✔
877
  if (parser_curr_server == (server_rec **) parser_servstack->elts) {
7✔
878
    errno = EPERM;
879
    return NULL;
7✔
880
  }
881

882
  parser_curr_server--;
27✔
883
  parser_servstack->nelts--;
27✔
884

21✔
885
  return *parser_curr_server;
886
}
887

6✔
888
server_rec *pr_parser_server_ctxt_get(void) {
6✔
889
  if (parser_curr_server) {
890
    return *parser_curr_server;
891
  }
14✔
892

14✔
893
  errno = ENOENT;
1✔
894
  return NULL;
1✔
895
}
896

897
int pr_parser_server_ctxt_push(server_rec *s) {
13✔
898
  if (s == NULL) {
1✔
899
    errno = EINVAL;
1✔
900
    return -1;
901
  }
902

12✔
903
  if (parser_servstack == NULL) {
12✔
904
    errno = EPERM;
905
    return -1;
12✔
906
  }
907

908
  parser_curr_server = (server_rec **) push_array(parser_servstack);
11✔
909
  *parser_curr_server = s;
11✔
910

11✔
911
  return 0;
912
}
11✔
913

11✔
914
server_rec *pr_parser_server_ctxt_open(const char *addrstr) {
915
  server_rec *s;
11✔
916
  pool *p;
11✔
917

11✔
918
  p = make_sub_pool(permanent_pool);
11✔
919
  pr_pool_tag(p, "<VirtualHost> Pool");
11✔
920

921
  s = (server_rec *) pcalloc(p, sizeof(server_rec));
922
  s->pool = p;
11✔
923
  s->config_type = CONF_VIRTUAL;
924
  s->sid = ++parser_sid;
925
  s->notes = pr_table_nalloc(p, 0, 8);
11✔
926

11✔
927
  /* TCP port reuse is disabled by default. */
11✔
928
  s->tcp_reuse_port = -1;
11✔
929

11✔
930
  /* TCP KeepAlive is enabled by default, with the system defaults. */
931
  s->tcp_keepalive = palloc(s->pool, sizeof(struct tcp_keepalive));
932
  s->tcp_keepalive->keepalive_enabled = TRUE;
933
  s->tcp_keepalive->keepalive_idle = -1;
934
  s->tcp_keepalive->keepalive_count = -1;
11✔
935
  s->tcp_keepalive->keepalive_intvl = -1;
11✔
936

11✔
937
  /* Have to make sure it ends up on the end of the chain, otherwise
11✔
938
   * main_server becomes useless.
939
   */
940
  xaset_insert_end(*parser_server_list, (xasetmember_t *) s);
941
  s->set = *parser_server_list;
11✔
942
  if (addrstr) {
943
    s->ServerAddress = pstrdup(s->pool, addrstr);
11✔
944
  }
11✔
945

946
  /* Default server port */
947
  s->ServerPort = pr_inet_getservport(s->pool, "ftp", "tcp");
6✔
948

6✔
949
  (void) pr_parser_server_ctxt_push(s);
950
  return s;
6✔
951
}
6✔
952

953
unsigned long pr_parser_set_include_opts(unsigned long opts) {
6✔
954
  unsigned long prev_opts;
955

956
  prev_opts = parser_include_opts;
957
  parser_include_opts = opts;
958

959
  return prev_opts;
960
}
961

962
static const char *tmpfile_patterns[] = {
30✔
963
  "*~",
30✔
964
  "*.sw?",
965
  NULL
86✔
966
};
58✔
967

968
static int is_tmp_file(const char *file) {
969
  register unsigned int i;
970

971
  for (i = 0; tmpfile_patterns[i]; i++) {
972
    if (pr_fnmatch(tmpfile_patterns[i], file, PR_FNM_PERIOD) == 0) {
973
      return TRUE;
974
    }
×
975
  }
×
976

977
  return FALSE;
978
}
1✔
979

980
static int config_filename_cmp(const void *a, const void *b) {
1✔
981
  return strcmp(*((char **) a), *((char **) b));
1✔
982
}
1✔
983

1✔
984
static int parse_wildcard_config_path(pool *p, const char *path,
1✔
985
    unsigned int depth) {
1✔
986
  register unsigned int i;
1✔
987
  int res, xerrno;
1✔
988
  pool *tmp_pool;
1✔
989
  array_header *globbed_dirs = NULL;
1✔
990
  const char *component = NULL, *parent_path = NULL, *suffix_path = NULL;
991
  struct stat st;
1✔
992
  size_t path_len, component_len;
×
993
  char *name_pattern = NULL;
994
  void *dirh = NULL;
995
  struct dirent *dent = NULL;
×
996

×
997
  if (depth > PR_PARSER_INCLUDE_MAX_DEPTH) {
998
    pr_log_pri(PR_LOG_WARNING, "error: resolving wildcard pattern in '%s' "
999
      "exceeded maximum filesystem depth (%u)", path,
1✔
1000
      (unsigned int) PR_PARSER_INCLUDE_MAX_DEPTH);
1✔
1001
    errno = EINVAL;
×
1002
    return -1;
1003
  }
1004

1005
  path_len = strlen(path);
1006
  if (path_len < 2) {
1007
    pr_trace_msg(trace_channel, 7, "path '%s' too short to be wildcard path",
×
1008
      path);
×
1009

1010
    /* The first character must be a slash, and we need at least one more
1011
     * character in the path as a glob character.
1✔
1012
     */
1✔
1013
    errno = EINVAL;
1014
    return -1;
1015
  }
1016

1017
  tmp_pool = make_sub_pool(p);
1018
  pr_pool_tag(tmp_pool, "Include sub-pool");
1019

1020
  /* We need to find the first component of the path which contains glob
1✔
1021
   * characters.  We then use the path up to the previous component as the
1✔
1022
   * parent directory to open, and the glob-bearing component as the filter
1023
   * for directories within the parent.
1✔
1024
   */
1✔
1025

1✔
1026
  parent_path = pstrdup(tmp_pool, "/");
1027
  component = path + 1;
1✔
1028

1029
  while (TRUE) {
1✔
1030
    int last_component = FALSE;
1✔
1031
    char *ptr;
1✔
1032

1033
    pr_signals_handle();
1034

×
1035
    ptr = strchr(component, '/');
×
1036
    if (ptr != NULL) {
1037
      component_len = ptr - component;
1038

1✔
1039
    } else {
×
1040
      component_len = strlen(component);
×
1041
      last_component = TRUE;
1042
    }
1✔
1043

1044
    if (memchr(component, (int) '*', component_len) != NULL ||
1✔
1045
        memchr(component, (int) '?', component_len) != NULL ||
1✔
1046
        memchr(component, (int) '[', component_len) != NULL) {
1047

1048
      name_pattern = pstrndup(tmp_pool, component, component_len);
1049

1050
      if (ptr != NULL) {
1051
        suffix_path = pstrdup(tmp_pool, ptr + 1);
×
1052
      }
1053

1054
      break;
×
1055
    }
1056

1057
    parent_path = pdircat(tmp_pool, parent_path,
1058
      pstrndup(tmp_pool, component, component_len), NULL);
×
1059

1060
    if (last_component == TRUE) {
1061
      break;
1✔
1062
    }
×
1063

1064
    component = ptr + 1;
×
1065
  }
×
1066

1067
  if (name_pattern == NULL) {
1068
    pr_trace_msg(trace_channel, 4,
1✔
1069
      "unable to process invalid, non-globbed path '%s'", path);
1070
    errno = ENOENT;
1071
    return -1;
1✔
1072
  }
1✔
1073

1✔
1074
  pr_trace_msg(trace_channel, 19, "generated globbed name pattern '%s/%s'",
1075
    parent_path, name_pattern);
1✔
1076

×
1077
  pr_fs_clear_cache2(parent_path);
1078
  res = pr_fsio_lstat(parent_path, &st);
1079
  xerrno = errno;
1080

×
1081
  if (res < 0) {
×
1082
    pr_log_pri(PR_LOG_WARNING,
×
1083
      "error: failed to check configuration path '%s': %s", parent_path,
1084
      strerror(xerrno));
1085

1✔
1086
    destroy_pool(tmp_pool);
×
1087
    errno = xerrno;
×
1088
    return -1;
1089
  }
×
1090

×
1091
  if (S_ISLNK(st.st_mode) &&
×
1092
      !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) {
1093
    pr_log_pri(PR_LOG_WARNING,
1094
      "error: cannot read configuration path '%s': Symbolic link", parent_path);
1✔
1095
    destroy_pool(tmp_pool);
1096
    errno = ENOTDIR;
1097
    return -1;
1098
  }
1✔
1099

1✔
1100
  pr_log_pri(PR_LOG_DEBUG,
×
1101
    "processing configuration directory '%s' using pattern '%s', suffix '%s'",
1102
    parent_path, name_pattern, suffix_path);
1103

×
1104
  dirh = pr_fsio_opendir(parent_path);
×
1105
  if (dirh == NULL) {
×
1106
    pr_log_pri(PR_LOG_WARNING,
1107
      "error: unable to open configuration directory '%s': %s", parent_path,
1108
      strerror(errno));
1✔
1109
    destroy_pool(tmp_pool);
1110
    errno = EINVAL;
29✔
1111
    return -1;
28✔
1112
  }
1113

28✔
1114
  globbed_dirs = make_array(tmp_pool, 0, sizeof(char *));
27✔
1115

2✔
1116
  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
1117
    pr_signals_handle();
1118

26✔
1119
    if (strcmp(dent->d_name, ".") == 0 ||
26✔
1120
        strcmp(dent->d_name, "..") == 0) {
×
1121
      continue;
1122
    }
1123

×
1124
    if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) {
1125
      if (is_tmp_file(dent->d_name) == TRUE) {
1126
        pr_trace_msg(trace_channel, 19,
1127
          "ignoring temporary file '%s' found in directory '%s'", dent->d_name,
26✔
1128
          parent_path);
1✔
1129
        continue;
1130
      }
1131
    }
1132

1✔
1133
    if (pr_fnmatch(name_pattern, dent->d_name, PR_FNM_PERIOD) == 0) {
1134
      pr_trace_msg(trace_channel, 17,
1135
        "matched '%s/%s' path with wildcard pattern '%s/%s'", parent_path,
1136
        dent->d_name, parent_path, name_pattern);
1137

1✔
1138
      *((char **) push_array(globbed_dirs)) = pdircat(tmp_pool, parent_path,
1139
        dent->d_name, suffix_path, NULL);
1✔
1140
    }
×
1141
  }
1142

×
1143
  pr_fsio_closedir(dirh);
×
1144

×
1145
  if (globbed_dirs->nelts == 0) {
1146
    pr_log_pri(PR_LOG_WARNING,
1147
      "error: no matches found for wildcard directory '%s'", path);
1✔
1148
    destroy_pool(tmp_pool);
1149
    errno = ENOENT;
1✔
1150
    return -1;
1151
  }
1152

3✔
1153
  depth++;
1✔
1154

1155
  qsort((void *) globbed_dirs->elts, globbed_dirs->nelts, sizeof(char *),
1✔
1156
    config_filename_cmp);
1✔
1157

1✔
1158
  for (i = 0; i < globbed_dirs->nelts; i++) {
×
1159
    const char *globbed_dir;
1160

×
1161
    globbed_dir = ((const char **) globbed_dirs->elts)[i];
1162
    res = parse_config_path2(p, globbed_dir, depth);
1163
    if (res < 0) {
×
1164
      xerrno = errno;
×
1165

×
1166
      pr_trace_msg(trace_channel, 7, "error parsing wildcard path '%s': %s",
1167
        globbed_dir, strerror(xerrno));
1168

1169
      destroy_pool(tmp_pool);
1✔
1170
      errno = xerrno;
1✔
1171
      return -1;
1172
    }
1173
  }
15✔
1174

15✔
1175
  destroy_pool(tmp_pool);
15✔
1176
  return 0;
15✔
1177
}
15✔
1178

15✔
1179
int parse_config_path2(pool *p, const char *path, unsigned int depth) {
15✔
1180
  struct stat st;
15✔
1181
  int have_glob;
1182
  void *dirh;
15✔
1183
  struct dirent *dent;
15✔
1184
  array_header *file_list;
1185
  char *dup_path, *ptr;
3✔
1186
  pool *tmp_pool;
3✔
1187

1188
  if (p == NULL ||
1189
      path == NULL ||
12✔
1190
      (depth > PR_PARSER_INCLUDE_MAX_DEPTH)) {
1✔
1191
    errno = EINVAL;
1✔
1192
    return -1;
1193
  }
1194

11✔
1195
  if (pr_fs_valid_path(path) < 0) {
11✔
1196
    errno = EINVAL;
1197
    return -1;
1198
  }
1199

1200
  have_glob = pr_str_is_fnmatch(path);
7✔
1201
  if (have_glob) {
×
1202
    /* Even though the path may be valid, it also may not be a filesystem
1203
     * path; consider custom FSIO modules.  Thus if the path does not start
1204
     * with a slash, it should not be treated as having globs.
1205
     */
11✔
1206
    if (*path != '/') {
1207
      have_glob = FALSE;
11✔
1208
    }
7✔
1209
  }
1210

1211
  pr_fs_clear_cache2(path);
15✔
1212

4✔
1213
  if (have_glob) {
1214
    pr_trace_msg(trace_channel, 19, "parsing '%s' as a globbed path", path);
1215
  }
1216

1217
  if (!have_glob &&
1218
      pr_fsio_lstat(path, &st) < 0) {
1219
    return -1;
10✔
1220
  }
3✔
1221

1222
  /* If path is not a glob pattern, and is a symlink OR is not a directory,
1✔
1223
   * then use the normal parsing function for the file.
1224
   */
1✔
1225
  if (have_glob == FALSE &&
1✔
1226
      (S_ISLNK(st.st_mode) ||
1✔
1227
       !S_ISDIR(st.st_mode))) {
1✔
1228
    int res, xerrno;
1229

1✔
1230
    PRIVS_ROOT
1✔
1231
    res = pr_parser_parse_file(p, path, NULL, 0);
1232
    xerrno = errno;
1233
    PRIVS_RELINQUISH
9✔
1234

9✔
1235
    errno = xerrno;
1236
    return res;
1237
  }
9✔
1238

1239
  tmp_pool = make_sub_pool(p);
9✔
1240
  pr_pool_tag(tmp_pool, "Include sub-pool");
1241

9✔
1242
  /* Handle the glob/directory. */
7✔
1243
  dup_path = pstrdup(tmp_pool, path);
1244

1245
  ptr = strrchr(dup_path, '/');
1246

1247
  if (have_glob) {
1248
    int have_glob_dir;
7✔
1249

1250
    /* Note that we know, by definition, that ptr CANNOT be null here; dup_path
1251
     * is a duplicate of path, and the first character (if nothing else) of
1252
     * path MUST be a slash, per earlier checks.
1253
     */
7✔
1254
    *ptr = '\0';
7✔
1255

3✔
1256
    /* We just changed ptr, thus we DO need to check whether the now-modified
1257
     * path contains fnmatch(3) characters again.
3✔
1258
     */
2✔
1259
    have_glob_dir = pr_str_is_fnmatch(dup_path);
1260
    if (have_glob_dir) {
2✔
1261
      const char *glob_dir;
2✔
1262

2✔
1263
      if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_WILDCARDS) {
1264
        pr_log_pri(PR_LOG_WARNING, "error: wildcard patterns not allowed in "
1265
          "configuration directory name '%s'", dup_path);
1✔
1266
        destroy_pool(tmp_pool);
1✔
1267
        errno = EINVAL;
1✔
1268
        return -1;
1269
      }
1✔
1270

1271
      *ptr = '/';
1272
      glob_dir = pstrdup(p, dup_path);
4✔
1273
      destroy_pool(tmp_pool);
1274

1275
      return parse_wildcard_config_path(p, glob_dir, depth);
4✔
1276
    }
4✔
1277

×
1278
    ptr++;
1279

×
1280
    /* Check the directory component. */
1281
    pr_fs_clear_cache2(dup_path);
1282
    if (pr_fsio_lstat(dup_path, &st) < 0) {
1283
      int xerrno = errno;
×
1284

×
1285
      pr_log_pri(PR_LOG_WARNING,
×
1286
        "error: failed to check configuration path '%s': %s", dup_path,
1287
        strerror(xerrno));
1288

4✔
1289
      destroy_pool(tmp_pool);
×
1290
      errno = xerrno;
×
1291
      return -1;
1292
    }
×
1293

×
1294
    if (S_ISLNK(st.st_mode) &&
×
1295
        !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) {
1296
      pr_log_pri(PR_LOG_WARNING,
1297
        "error: cannot read configuration path '%s': Symbolic link", path);
4✔
1298
      destroy_pool(tmp_pool);
4✔
1299
      errno = ENOTDIR;
×
1300
      return -1;
1301
    }
×
1302

×
1303
    if (have_glob_dir == FALSE &&
×
1304
        pr_str_is_fnmatch(ptr) == FALSE) {
1305
      pr_log_pri(PR_LOG_WARNING,
1306
        "error: wildcard pattern required for file '%s'", ptr);
1307
      destroy_pool(tmp_pool);
6✔
1308
      errno = EINVAL;
1309
      return -1;
1310
    }
6✔
1311
  }
6✔
1312

×
1313
  pr_trace_msg(trace_channel, 3, "processing configuration directory '%s'",
1314
    dup_path);
×
1315

×
1316
  dirh = pr_fsio_opendir(dup_path);
×
1317
  if (dirh == NULL) {
×
1318
    pr_log_pri(PR_LOG_WARNING,
1319
      "error: unable to open configuration directory '%s': %s", dup_path,
1320
      strerror(errno));
6✔
1321
    destroy_pool(tmp_pool);
1322
    errno = EINVAL;
23✔
1323
    return -1;
17✔
1324
  }
1325

17✔
1326
  file_list = make_array(tmp_pool, 0, sizeof(char *));
11✔
1327

12✔
1328
  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
1329
    pr_signals_handle();
1330

5✔
1331
    if (strcmp(dent->d_name, ".") == 0 ||
4✔
1332
        strcmp(dent->d_name, "..") == 0) {
2✔
1333
      continue;
1334
    }
1335

2✔
1336
    if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) {
1337
      if (is_tmp_file(dent->d_name) == TRUE) {
1338
        pr_trace_msg(trace_channel, 19,
1339
          "ignoring temporary file '%s' found in directory '%s'", dent->d_name,
3✔
1340
          dup_path);
3✔
1341
        continue;
3✔
1342
      }
3✔
1343
    }
1344

1345
    if (have_glob == FALSE ||
1346
        (ptr != NULL &&
1347
         pr_fnmatch(ptr, dent->d_name, PR_FNM_PERIOD) == 0)) {
6✔
1348
      *((char **) push_array(file_list)) = pdircat(tmp_pool, dup_path,
1349
        dent->d_name, NULL);
6✔
1350
    }
3✔
1351
  }
1352

3✔
1353
  pr_fsio_closedir(dirh);
1354

1355
  if (file_list->nelts) {
9✔
1356
    register unsigned int i;
3✔
1357

3✔
1358
    qsort((void *) file_list->elts, file_list->nelts, sizeof(char *),
1359
      config_filename_cmp);
3✔
1360

1361
    for (i = 0; i < file_list->nelts; i++) {
1362
      int res, xerrno;
1363
      char *file;
1364

1365
      file = ((char **) file_list->elts)[i];
1366

3✔
1367
      /* Make sure we always parse the files with root privs.  The
3✔
1368
       * previously parsed file might have had root privs relinquished
3✔
1369
       * (e.g. by its directive handlers), but when we first start up,
3✔
1370
       * we have root privs.  See Bug#3855.
1371
       */
3✔
1372
      PRIVS_ROOT
×
1373
      res = pr_parser_parse_file(tmp_pool, file, NULL, 0);
1374
      xerrno = errno;
×
1375
      PRIVS_RELINQUISH
1376

1377
      if (res < 0) {
×
1378
        pr_log_pri(PR_LOG_WARNING,
1379
          "error: unable to parse file '%s': %s", file, strerror(xerrno));
1380
        pr_log_pri(PR_LOG_WARNING, "%s",
1381
          "error: check `proftpd --configtest -d10` for details");
1382

1383
        destroy_pool(tmp_pool);
×
1384

×
1385
        /* Any error other than EINVAL is logged as a warning, but ignored,
1386
         * by the Include directive handler.  Thus we always return EINVAL
1387
         * here to halt further parsing (Issue #1721).
1388
         */
1389
        errno = EINVAL;
6✔
1390
        return -1;
6✔
1391
      }
1392
    }
1393
  }
1✔
1394

1✔
1395
  destroy_pool(tmp_pool);
1396
  return 0;
1397
}
1398

1399
int parse_config_path(pool *p, const char *path) {
1400
  return parse_config_path2(p, path, 0);
1401
}
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