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

proftpd / proftpd / 26182518137

20 May 2026 06:49PM UTC coverage: 93.024% (+0.4%) from 92.635%
26182518137

push

github

51329 of 55178 relevant lines covered (93.02%)

226.63 hits per line

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

81.53
/src/parser.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2004-2026 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, see <https://www.gnu.org/licenses/>.
17
 *
18
 * As a special exemption, The ProFTPD Project team and other respective
19
 * copyright holders give permission to link this program with OpenSSL, and
20
 * distribute the resulting executable, without including the source code
21
 * for OpenSSL in the source distribution.
22
 */
23

24
/* Configuration parser */
25

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

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

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

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

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

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

45
static xaset_t **parser_server_list = NULL;
46

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

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

56
static unsigned int parser_curr_lineno = 0;
57

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

64
static struct config_src *parser_sources = NULL;
65

66
/* Private functions
67
 */
68

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

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

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

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

6✔
85
  } else {
86
    cs->cs_next = parser_sources;
87
    parser_sources = cs;
×
88
  }
×
89

90
  return cs;
91
}
6✔
92

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

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

101
  wordlen = strlen(word);
102
  if (wordlen > 7) {
35✔
103
    char *ptr = NULL;
35✔
104

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

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

6✔
118
      pr_signals_handle();
119

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

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

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

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

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

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

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

4✔
157
      if (resolved_var != NULL) {
158
        *resolved_var = TRUE;
159
      }
160
    }
21✔
161

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

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

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

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

179
/* Public API
60✔
180
 */
60✔
181

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

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

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

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

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

203
  return 0;
3✔
204
}
3✔
205

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

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

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

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

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

229
    return NULL;
×
230
  }
×
231

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

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

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

245
  return *parser_curr_config;
9✔
246
}
9✔
247

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

339
  return 0;
10✔
340
}
10✔
341

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

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

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

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

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

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

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

371
  return names;
10✔
372
}
373

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

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

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

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

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

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

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

1✔
410
    destroy_pool(tmp_pool);
1✔
411

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

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

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

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

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

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

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

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

451
  fh->fh_iosz = st.st_blksize;
452

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

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

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

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

469
    pr_signals_handle();
470

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

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

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

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

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

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

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

8✔
506
        pr_signals_handle();
507

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5✔
631
  pr_fsio_close(fh);
5✔
632

633
  destroy_pool(tmp_pool);
634
  return 0;
21✔
635
}
21✔
636

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

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

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

1✔
654
  if (text_len == 0) {
655
    errno = ENOENT;
656
    return NULL;
18✔
657
  }
658

659
  ptr = (char *) text;
18✔
660

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

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

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

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

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

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

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

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

18✔
700
  /* Terminate the array with a NULL. */
1✔
701
  *((char **) push_array(arr)) = NULL;
1✔
702

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

1✔
707
  cmd->argv = (void **) arr->elts;
708

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

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

725
    cp = cmd->argv[cmd->argc-1];
18✔
726
    cp_len = strlen(cp);
2✔
727

728
    if (*(cp + cp_len-1) == '>' &&
729
        cmd->argc > 1) {
35✔
730

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

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

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

747
  if (cmd->argc < 2) {
748
    arg = pstrdup(cmd->pool, arg);
48✔
749
  }
47✔
750

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

755
  cmd->arg = arg;
48✔
756
  return cmd;
48✔
757
}
48✔
758

759
int pr_parser_prepare(pool *p, xaset_t **parsed_servers) {
48✔
760

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

767
    p = parser_pool;
768
  }
769

770
  if (parsed_servers == NULL) {
771
    parser_server_list = &server_list;
772

773
  } else {
16✔
774
    parser_server_list = parsed_servers;
16✔
775
  }
776

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

16✔
781
  parser_confstack = make_array(p, 10, sizeof(config_rec *));
1✔
782
  parser_curr_config = (config_rec **) push_array(parser_confstack);
1✔
783
  *parser_curr_config = NULL;
784

785
  return 0;
15✔
786
}
×
787

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

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

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

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

812
  parser_curr_lineno = cs->cs_lineno;
10✔
813

10✔
814
  /* Check for error conditions. */
6✔
815

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

821
    pr_signals_handle();
×
822

823
    buflen = strlen(buf);
824
    parser_curr_lineno = cs->cs_lineno;
825

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

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

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

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

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

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

7✔
863
    return buf;
864
  }
865

27✔
866
  return NULL;
27✔
867
}
21✔
868

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

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

13✔
881
  parser_curr_server--;
1✔
882
  parser_servstack->nelts--;
1✔
883

884
  return *parser_curr_server;
885
}
12✔
886

12✔
887
server_rec *pr_parser_server_ctxt_get(void) {
888
  if (parser_curr_server) {
12✔
889
    return *parser_curr_server;
890
  }
891

11✔
892
  errno = ENOENT;
11✔
893
  return NULL;
11✔
894
}
895

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

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

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

11✔
910
  return 0;
11✔
911
}
11✔
912

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

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

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

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

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

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

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

948
  (void) pr_parser_server_ctxt_push(s);
86✔
949
  return s;
58✔
950
}
951

952
unsigned long pr_parser_set_include_opts(unsigned long opts) {
953
  unsigned long prev_opts;
954

955
  prev_opts = parser_include_opts;
956
  parser_include_opts = opts;
957

×
958
  return prev_opts;
×
959
}
960

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

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

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

×
976
  return FALSE;
977
}
978

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

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

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

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

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

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

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

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

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

1032
    pr_signals_handle();
1033

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

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

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

1047
      name_pattern = pstrndup(tmp_pool, component, component_len);
×
1048

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

1053
      break;
1054
    }
1✔
1055

1✔
1056
    parent_path = pdircat(tmp_pool, parent_path,
1✔
1057
      pstrndup(tmp_pool, component, component_len), NULL);
1058

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

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

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

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

1076
  pr_fs_clear_cache2(parent_path);
1077
  res = pr_fsio_lstat(parent_path, &st);
1✔
1078
  xerrno = errno;
1079

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

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

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

2✔
1099
  pr_log_pri(PR_LOG_DEBUG,
1100
    "processing configuration directory '%s' using pattern '%s', suffix '%s'",
1101
    parent_path, name_pattern, suffix_path);
26✔
1102

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

1113
  globbed_dirs = make_array(tmp_pool, 0, sizeof(char *));
1114

1115
  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
1✔
1116
    pr_signals_handle();
1117

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

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

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

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

×
1142
  pr_fsio_closedir(dirh);
1143

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

1152
  depth++;
1✔
1153

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

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

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

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

1168
      destroy_pool(tmp_pool);
3✔
1169
      errno = xerrno;
3✔
1170
      return -1;
1171
    }
1172
  }
12✔
1173

1✔
1174
  destroy_pool(tmp_pool);
1✔
1175
  return 0;
1176
}
1177

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

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

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

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

1✔
1210
  pr_fs_clear_cache2(path);
1✔
1211

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

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

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

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

1234
    errno = xerrno;
1235
    return res;
1236
  }
7✔
1237

7✔
1238
  tmp_pool = make_sub_pool(p);
3✔
1239
  pr_pool_tag(tmp_pool, "Include sub-pool");
1240

3✔
1241
  /* Handle the glob/directory. */
2✔
1242
  dup_path = pstrdup(tmp_pool, path);
1243

2✔
1244
  ptr = strrchr(dup_path, '/');
2✔
1245

2✔
1246
  if (have_glob) {
1247
    int have_glob_dir;
1248

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

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

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

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

×
1274
      return parse_wildcard_config_path(p, glob_dir, depth);
1275
    }
×
1276

×
1277
    ptr++;
×
1278

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

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

1288
      destroy_pool(tmp_pool);
1289
      errno = xerrno;
1290
      return -1;
6✔
1291
    }
1292

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

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

1312
  pr_trace_msg(trace_channel, 3, "processing configuration directory '%s'",
1313
    dup_path);
5✔
1314

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

3✔
1325
  file_list = make_array(tmp_pool, 0, sizeof(char *));
3✔
1326

1327
  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
1328
    pr_signals_handle();
1329

1330
    if (strcmp(dent->d_name, ".") == 0 ||
6✔
1331
        strcmp(dent->d_name, "..") == 0) {
1332
      continue;
6✔
1333
    }
3✔
1334

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

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

3✔
1352
  pr_fsio_closedir(dirh);
3✔
1353

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

1357
    qsort((void *) file_list->elts, file_list->nelts, sizeof(char *),
×
1358
      config_filename_cmp);
1359

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

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

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

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

1382
        destroy_pool(tmp_pool);
1383

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

1394
  destroy_pool(tmp_pool);
1395
  return 0;
1396
}
1397

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