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

proftpd / proftpd / 28042938174

23 Jun 2026 05:02PM UTC coverage: 92.464% (-0.6%) from 93.027%
28042938174

push

github

web-flow
It is possible for a client to say that it wishes to use the "sk-ssh-ed25519@openssh.com" algorithm for SSH user publickey authentication, but to _actually_ provide an "ssh-ed25519" public key.

The issue is that, when verifying the type of the public key provided by the client, we do not enforce that the provided key algorith matches the expected public key algorithm.

Thanks to Fabian Wahle of Hap Security for reporting this issue.

48661 of 52627 relevant lines covered (92.46%)

234.31 hits per line

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

80.26
/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) {
6✔
70
  pool *p;
71
  struct config_src *cs;
72

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

76
  cs = pcalloc(p, sizeof(struct config_src));
6✔
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

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

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

90
  return cs;
6✔
91
}
92

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

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);
37✔
102
  if (wordlen > 7) {
37✔
103
    char *ptr = NULL;
15✔
104

105
    pr_trace_msg(trace_channel, 27, "word '%s' long enough for environment "
15✔
106
      "variable (%lu > minimum required 7)", word, (unsigned long) wordlen);
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:");
15✔
114
    while (ptr != NULL) {
37✔
115
      char *env, *key, *ptr2, *var;
116
      unsigned int keylen;
117

118
      pr_signals_handle();
7✔
119

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

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

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

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

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

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

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

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

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

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

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

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

179
/* Public API
180
 */
181

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

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

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

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

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

203
  return 0;
59✔
204
}
205

206
config_rec *pr_parser_config_ctxt_close(int *empty) {
3✔
207
  config_rec *c = *parser_curr_config;
3✔
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.
212
   */
213

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

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

225
    if (*parser_curr_config) {
3✔
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;
×
246
}
247

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

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

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

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

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

271
  } else {
272
    parent_pool = (*parser_curr_server)->pool;
2✔
273
    set = &(*parser_curr_server)->conf;
2✔
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
281
   * prematurely, and helps to avoid memory leaks.
282
   */
283
  if (strcasecmp(name, "<Global>") == 0) {
2✔
284
    if (global_config_pool == NULL) {
1✔
285
      global_config_pool = make_sub_pool(permanent_pool);
1✔
286
      pr_pool_tag(global_config_pool, "<Global> Pool");
1✔
287
    }
288

289
    parent_pool = global_config_pool;
1✔
290
  }
291

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

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

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

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

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

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

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

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

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

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

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

339
  return 0;
340
}
341

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

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

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

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

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

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

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

371
  return names;
1✔
372
}
373

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

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

389
  if (parser_servstack == NULL) {
9✔
390
    errno = EPERM;
1✔
391
    return -1;
1✔
392
  }
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;
8✔
398
  if (session.chroot_path) {
8✔
399
    report_path = pdircat(tmp_pool, session.chroot_path, path, NULL);
×
400
  }
401

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

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

410
    destroy_pool(tmp_pool);
1✔
411

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

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

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

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

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

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

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

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

451
  fh->fh_iosz = st.st_blksize;
6✔
452

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

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

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

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

469
    pr_signals_handle();
10✔
470

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

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);
10✔
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);
10✔
482
    if (cmd == NULL) {
10✔
483
      destroy_pool(parsed_pool);
×
484
      continue;
×
485
    }
486

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

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

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

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

506
        pr_signals_handle();
8✔
507

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

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

514
        mr = pr_module_call(conftab->m, conftab->handler, cmd);
8✔
515
        if (mr != NULL) {
8✔
516
          if (MODRET_ISERROR(mr)) {
8✔
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
          }
528
        }
529

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

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

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

543
      if (found == FALSE) {
10✔
544
        register unsigned int i;
545
        char *name;
546
        size_t namelen;
547
        int non_ascii = FALSE;
2✔
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.
559
         */
560

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

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

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

578
          } else {
579
            array_header *directives, *similars;
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 &&
2✔
585
                similars->nelts > 0) {
1✔
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
            }
608
          }
609

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

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

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

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

631
  pr_fsio_close(fh);
5✔
632

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

767
    p = parser_pool;
3✔
768
  }
769

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

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

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

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

785
  return 0;
48✔
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
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
 */
795
char *pr_parser_read_line(char *buf, size_t bufsz) {
16✔
796
  struct config_src *cs;
797

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

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

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

812
  parser_curr_lineno = cs->cs_lineno;
15✔
813

814
  /* Check for error conditions. */
815

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

821
    pr_signals_handle();
10✔
822

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

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

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

840
    if (have_eol == FALSE) {
10✔
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++) {
10✔
848
    }
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) {
10✔
855
      continue;
×
856
    }
857

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

863
    return buf;
864
  }
865

866
  return NULL;
867
}
868

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

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

881
  parser_curr_server--;
7✔
882
  parser_servstack->nelts--;
7✔
883

884
  return *parser_curr_server;
7✔
885
}
886

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

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

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

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

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

910
  return 0;
12✔
911
}
912

913
server_rec *pr_parser_server_ctxt_open(const char *addrstr) {
11✔
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

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

926
  /* TCP port reuse is disabled by default. */
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));
11✔
931
  s->tcp_keepalive->keepalive_enabled = TRUE;
11✔
932
  s->tcp_keepalive->keepalive_idle = -1;
11✔
933
  s->tcp_keepalive->keepalive_count = -1;
11✔
934
  s->tcp_keepalive->keepalive_intvl = -1;
11✔
935

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

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

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

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

955
  prev_opts = parser_include_opts;
6✔
956
  parser_include_opts = opts;
6✔
957

958
  return prev_opts;
6✔
959
}
960

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

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

970
  for (i = 0; tmpfile_patterns[i]; i++) {
83✔
971
    if (pr_fnmatch(tmpfile_patterns[i], file, PR_FNM_PERIOD) == 0) {
56✔
972
      return TRUE;
973
    }
974
  }
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

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;
1✔
989
  const char *component = NULL, *parent_path = NULL, *suffix_path = NULL;
1✔
990
  struct stat st;
991
  size_t path_len, component_len;
992
  char *name_pattern = NULL;
1✔
993
  void *dirh = NULL;
1✔
994
  struct dirent *dent = NULL;
1✔
995

996
  if (depth > PR_PARSER_INCLUDE_MAX_DEPTH) {
1✔
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

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

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

1016
  tmp_pool = make_sub_pool(p);
1✔
1017
  pr_pool_tag(tmp_pool, "Include sub-pool");
1✔
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
1022
   * for directories within the parent.
1023
   */
1024

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

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

1032
    pr_signals_handle();
1✔
1033

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

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

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

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

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

1053
      break;
1054
    }
1055

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

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

1063
    component = ptr + 1;
×
1064
  }
1065

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

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

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

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

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

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

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

1103
  dirh = pr_fsio_opendir(parent_path);
1✔
1104
  if (dirh == NULL) {
1✔
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;
×
1111
  }
1112

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

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

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

1123
    if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) {
25✔
1124
      if (is_tmp_file(dent->d_name) == TRUE) {
25✔
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
    }
1131

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

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

1142
  pr_fsio_closedir(dirh);
1✔
1143

1144
  if (globbed_dirs->nelts == 0) {
1✔
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

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

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

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

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

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

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

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

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

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

1199
  have_glob = pr_str_is_fnmatch(path);
11✔
1200
  if (have_glob) {
11✔
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
1203
     * with a slash, it should not be treated as having globs.
1204
     */
1205
    if (*path != '/') {
7✔
1206
      have_glob = FALSE;
×
1207
    }
1208
  }
1209

1210
  pr_fs_clear_cache2(path);
11✔
1211

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

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

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.
1223
   */
1224
  if (have_glob == FALSE &&
13✔
1225
      (S_ISLNK(st.st_mode) ||
3✔
1226
       !S_ISDIR(st.st_mode))) {
1227
    int res, xerrno;
1228

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

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

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

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

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

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

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

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

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

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

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

1277
    ptr++;
4✔
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;
×
1291
    }
1292

1293
    if (S_ISLNK(st.st_mode) &&
4✔
1294
        !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) {
×
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 &&
4✔
1303
        pr_str_is_fnmatch(ptr) == FALSE) {
4✔
1304
      pr_log_pri(PR_LOG_WARNING,
×
1305
        "error: wildcard pattern required for file '%s'", ptr);
1306
      destroy_pool(tmp_pool);
×
1307
      errno = EINVAL;
×
1308
      return -1;
×
1309
    }
1310
  }
1311

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

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

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

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

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

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

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

1352
  pr_fsio_closedir(dirh);
6✔
1353

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

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

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

1364
      file = ((char **) file_list->elts)[i];
3✔
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
3✔
1372
      res = pr_parser_parse_file(tmp_pool, file, NULL, 0);
3✔
1373
      xerrno = errno;
3✔
1374
      PRIVS_RELINQUISH
3✔
1375

1376
      if (res < 0) {
3✔
1377
        pr_log_pri(PR_LOG_WARNING,
×
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);
6✔
1395
  return 0;
6✔
1396
}
1397

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