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

proftpd / proftpd / 14581522647

21 Apr 2025 09:13PM UTC coverage: 92.667% (-0.4%) from 93.03%
14581522647

push

github

Castaglia
Adding a regression test for Bug#4417.

47172 of 50905 relevant lines covered (92.67%)

250.83 hits per line

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

79.97
/src/parser.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2004-2023 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;
72
  struct config_src *cs;
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) {
35✔
95
  size_t wordlen;
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);
35✔
103
  if (wordlen > 7) {
35✔
104
    char *ptr = NULL;
14✔
105

106
    pr_trace_msg(trace_channel, 27, "word '%s' long enough for environment "
14✔
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:");
14✔
115
    while (ptr != NULL) {
34✔
116
      char *env, *key, *ptr2, *var;
117
      unsigned int keylen;
118

119
      pr_signals_handle();
6✔
120

121
      ptr2 = strchr(ptr + 6, '}');
6✔
122
      if (ptr2 == NULL) {
6✔
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);
5✔
132
      var = pstrndup(p, ptr, (ptr2 - ptr) + 1);
5✔
133

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

136
      pr_trace_msg(trace_channel, 17,
5✔
137
        "word '%s' uses environment variable '%s'", word, key);
138
      env = pr_env_get(p, key);
5✔
139
      if (env == NULL) {
5✔
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,
4✔
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);
4✔
156
      ptr = strstr(word, "%{env:");
4✔
157
    }
158

159
  } else {
160
    pr_trace_msg(trace_channel, 27, "word '%s' not long enough for environment "
21✔
161
      "variable (%lu < minimum required 7)", word, (unsigned long) wordlen);
162
  }
163

164
  return pstrdup(p, word);
35✔
165
}
166

167
static void remove_config_source(void) {
168
  struct config_src *cs = parser_sources;
5✔
169

170
  if (cs != NULL) {
5✔
171
    parser_sources = cs->cs_next;
5✔
172
    destroy_pool(cs->cs_pool);
5✔
173
  }
174
}
175

176
/* Public API
177
 */
178

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

187
    destroy_pool(parser_pool);
2✔
188
    parser_pool = NULL;
2✔
189
  }
190

191
  parser_servstack = NULL;
59✔
192
  parser_curr_server = NULL;
59✔
193

194
  parser_confstack = NULL;
59✔
195
  parser_curr_config = NULL;
59✔
196

197
  /* Reset the SID counter. */
198
  parser_sid = 1;
59✔
199

200
  return 0;
59✔
201
}
202

203
config_rec *pr_parser_config_ctxt_close(int *empty) {
3✔
204
  config_rec *c = *parser_curr_config;
3✔
205

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

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

217
      if (empty) {
3✔
218
        *empty = TRUE;
2✔
219
      }
220
    }
221

222
    if (*parser_curr_config) {
3✔
223
      *parser_curr_config = NULL;
3✔
224
    }
225

226
    return NULL;
227
  }
228

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

234
    if (empty) {
×
235
      *empty = TRUE;
×
236
    }
237
  }
238

239
  parser_curr_config--;
×
240
  parser_confstack->nelts--;
×
241

242
  return *parser_curr_config;
×
243
}
244

245
config_rec *pr_parser_config_ctxt_get(void) {
9✔
246
  if (parser_curr_config) {
9✔
247
    return *parser_curr_config;
8✔
248
  }
249

250
  errno = ENOENT;
1✔
251
  return NULL;
1✔
252
}
253

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

259
  if (name == NULL) {
3✔
260
    errno = EINVAL;
1✔
261
    return NULL;
1✔
262
  }
263

264
  if (parent != NULL) {
2✔
265
    parent_pool = parent->pool;
×
266
    set = &parent->subset;
×
267

268
  } else {
269
    parent_pool = (*parser_curr_server)->pool;
2✔
270
    set = &(*parser_curr_server)->conf;
2✔
271
  }
272

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

286
    parent_pool = global_config_pool;
1✔
287
  }
288

289
  c_pool = make_sub_pool(parent_pool);
2✔
290
  pr_pool_tag(c_pool, "sub-config pool");
2✔
291

292
  c = (config_rec *) pcalloc(c_pool, sizeof(config_rec));
2✔
293

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

300
  xaset_insert(*set, (xasetmember_t *) c);
2✔
301

302
  c->pool = c_pool;
2✔
303
  c->set = *set;
2✔
304
  c->parent = parent;
2✔
305
  c->name = pstrdup(c->pool, name);
2✔
306

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

313
  (void) pr_parser_config_ctxt_push(c);
2✔
314
  return c;
2✔
315
}
316

317
int pr_parser_config_ctxt_push(config_rec *c) {
5✔
318
  if (c == NULL) {
5✔
319
    errno = EINVAL;
1✔
320
    return -1;
1✔
321
  }
322

323
  if (parser_confstack == NULL) {
4✔
324
    errno = EPERM;
1✔
325
    return -1;
1✔
326
  }
327

328
  if (!*parser_curr_config) {
3✔
329
    *parser_curr_config = c;
3✔
330

331
  } else {
332
    parser_curr_config = (config_rec **) push_array(parser_confstack);
×
333
    *parser_curr_config = c;
×
334
  }
335

336
  return 0;
337
}
338

339
unsigned int pr_parser_get_lineno(void) {
10✔
340
  return parser_curr_lineno;
10✔
341
}
342

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

350
  names = make_array(p, 1, sizeof(const char *));
1✔
351

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

358
    if (tab != NULL) {
42✔
359
      *((const char **) push_array(names)) = pstrdup(p, tab->directive);
2✔
360

361
    } else {
362
      idx++;
40✔
363
    }
364

365
    tab = pr_stash_get_symbol2(PR_SYM_CONF, NULL, tab, &idx, &hash);
42✔
366
  }
367

368
  return names;
1✔
369
}
370

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

381
  if (path == NULL) {
10✔
382
    errno = EINVAL;
1✔
383
    return -1;
1✔
384
  }
385

386
  if (parser_servstack == NULL) {
9✔
387
    errno = EPERM;
1✔
388
    return -1;
1✔
389
  }
390

391
  tmp_pool = make_sub_pool(p ? p : permanent_pool);
8✔
392
  pr_pool_tag(tmp_pool, "parser file pool");
8✔
393

394
  report_path = (char *) path;
8✔
395
  if (session.chroot_path) {
8✔
396
    report_path = pdircat(tmp_pool, session.chroot_path, path, NULL);
×
397
  }
398

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

403
  fh = pr_fsio_open(path, O_RDONLY);
8✔
404
  if (fh == NULL) {
8✔
405
    int xerrno = errno;
1✔
406

407
    destroy_pool(tmp_pool);
1✔
408

409
    errno = xerrno;
1✔
410
    return -1;
1✔
411
  }
412

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

418
    pr_fsio_close(fh);
×
419
    destroy_pool(tmp_pool);
×
420

421
    errno = xerrno;
×
422
    return -1;
×
423
  }
424

425
  if (S_ISDIR(st.st_mode)) {
7✔
426
    pr_fsio_close(fh);
1✔
427
    destroy_pool(tmp_pool);
1✔
428

429
    errno = EISDIR;
1✔
430
    return -1;
1✔
431
  }
432

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

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

448
  fh->fh_iosz = st.st_blksize;
6✔
449

450
  /* Push the configuration information onto the stack of configuration
451
   * sources.
452
   */
453
  cs = add_config_source(fh);
6✔
454

455
  if (start != NULL) {
6✔
456
    (void) pr_parser_config_ctxt_push(start);
×
457
  }
458

459
  bufsz = PR_TUNABLE_PARSER_BUFFER_SIZE;
6✔
460
  buf = pcalloc(tmp_pool, bufsz + 1);
6✔
461

462
  while (pr_parser_read_line(buf, bufsz) != NULL) {
21✔
463
    pool *parsed_pool;
464
    pr_parsed_line_t *parsed_line;
465

466
    pr_signals_handle();
10✔
467

468
    /* Note that pr_parser_parse_line modifies the contents of the buffer,
469
     * so we want to make copy beforehand.
470
     */
471

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

478
    cmd = pr_parser_parse_line(tmp_pool, buf, 0);
10✔
479
    if (cmd == NULL) {
10✔
480
      destroy_pool(parsed_pool);
×
481
      continue;
×
482
    }
483

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

491
    if (cmd->argc) {
10✔
492
      conftable *conftab;
493
      char found = FALSE;
10✔
494

495
      cmd->server = *parser_curr_server;
10✔
496
      cmd->config = *parser_curr_config;
10✔
497

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

503
        pr_signals_handle();
8✔
504

505
        cmd->argv[0] = conftab->directive;
8✔
506

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

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

522
            pr_log_pri(PR_LOG_WARNING, "warning: %s on line %u of '%s'",
×
523
              MODRET_ERRMSG(mr), cs->cs_lineno, report_path);
524
          }
525
        }
526

527
        if (!MODRET_ISDECLINED(mr)) {
8✔
528
          found = TRUE;
8✔
529
        }
530

531
        conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], conftab,
8✔
532
          &cmd->stash_index, &cmd->stash_hash);
533
      }
534

535
      if (cmd->tmp_pool != NULL) {
10✔
536
        destroy_pool(cmd->tmp_pool);
8✔
537
        cmd->tmp_pool = NULL;
8✔
538
      }
539

540
      if (found == FALSE) {
10✔
541
        register unsigned int i;
542
        char *name;
543
        size_t namelen;
544
        int non_ascii = FALSE;
2✔
545

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

558
        name = cmd->argv[0];
2✔
559
        namelen = strlen(name);
2✔
560

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

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

575
          } else {
576
            array_header *directives, *similars;
577

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

586
              names = similars->elts;
×
587
              nelts = similars->nelts;
×
588
              if (nelts > 4) {
×
589
                nelts = 4;
590
              }
591

592
              msg = "fatal: Did you mean:";
×
593

594
              if (nelts == 1) {
×
595
                msg = pstrcat(tmp_pool, msg, " ", names[0], NULL);
×
596

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

603
              pr_log_pri(PR_LOG_WARNING, "%s", msg);
×
604
            }
605
          }
606

607
          destroy_pool(tmp_pool);
1✔
608
          errno = EPERM;
1✔
609
          return -1;
1✔
610
        }
611

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

621
    destroy_pool(cmd->pool);
9✔
622
    memset(buf, '\0', bufsz);
623
  }
624

625
  /* Pop this configuration stream from the stack. */
626
  remove_config_source();
5✔
627

628
  pr_fsio_close(fh);
5✔
629

630
  destroy_pool(tmp_pool);
5✔
631
  return 0;
5✔
632
}
633

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

641
  if (p == NULL ||
42✔
642
      text == NULL) {
21✔
643
    errno = EINVAL;
2✔
644
    return NULL;
2✔
645
  }
646

647
  if (text_len == 0) {
19✔
648
    text_len = strlen(text);
19✔
649
  }
650

651
  if (text_len == 0) {
19✔
652
    errno = ENOENT;
1✔
653
    return NULL;
1✔
654
  }
655

656
  ptr = (char *) text;
18✔
657

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

662
  cmd = pcalloc(sub_pool, sizeof(cmd_rec));
18✔
663
  cmd->pool = sub_pool;
18✔
664
  cmd->stash_index = -1;
18✔
665
  cmd->stash_hash = 0;
18✔
666

667
  /* Add each word to the array */
668
  arr = make_array(cmd->pool, 4, sizeof(char **));
18✔
669
  while ((word = pr_str_get_word(&ptr, 0)) != NULL) {
71✔
670
    char *ptr2;
671

672
    pr_signals_handle();
35✔
673
    ptr2 = get_config_word(cmd->pool, word);
35✔
674
    *((char **) push_array(arr)) = ptr2;
35✔
675
    cmd->argc++;
35✔
676
  }
677

678
  /* Terminate the array with a NULL. */
679
  *((char **) push_array(arr)) = NULL;
18✔
680

681
  /* The array header's job is done, we can forget about it and
682
   * it will get purged when the command's pool is destroyed.
683
   */
684

685
  cmd->argv = (void **) arr->elts;
18✔
686

687
  /* Perform a fixup on configuration directives so that:
688
   *
689
   *   -argv[0]--  -argv[1]-- ----argv[2]-----
690
   *   <Option     /etc/adir  /etc/anotherdir>
691
   *
692
   *  becomes:
693
   *
694
   *   -argv[0]--  -argv[1]-  ----argv[2]----
695
   *   <Option>    /etc/adir  /etc/anotherdir
696
   */
697

698
  if (cmd->argc &&
36✔
699
      *((char *) cmd->argv[0]) == '<') {
18✔
700
    char *cp;
701
    size_t cp_len;
702

703
    cp = cmd->argv[cmd->argc-1];
1✔
704
    cp_len = strlen(cp);
1✔
705

706
    if (*(cp + cp_len-1) == '>' &&
1✔
707
        cmd->argc > 1) {
708

709
      if (strcmp(cp, ">") == 0) {
1✔
710
        cmd->argv[cmd->argc-1] = NULL;
×
711
        cmd->argc--;
×
712

713
      } else {
714
        *(cp + cp_len-1) = '\0';
1✔
715
      }
716

717
      cp = cmd->argv[0];
1✔
718
      cp_len = strlen(cp);
1✔
719
      if (*(cp + cp_len-1) != '>') {
1✔
720
        cmd->argv[0] = pstrcat(cmd->pool, cp, ">", NULL);
1✔
721
      }
722
    }
723
  }
724

725
  if (cmd->argc < 2) {
18✔
726
    arg = pstrdup(cmd->pool, arg);
2✔
727
  }
728

729
  for (i = 1; i < cmd->argc; i++) {
17✔
730
    arg = pstrcat(cmd->pool, arg, *arg ? " " : "", cmd->argv[i], NULL);
17✔
731
  }
732

733
  cmd->arg = arg;
18✔
734
  return cmd;
18✔
735
}
736

737
int pr_parser_prepare(pool *p, xaset_t **parsed_servers) {
48✔
738

739
  if (p == NULL) {
48✔
740
    if (parser_pool == NULL) {
3✔
741
      parser_pool = make_sub_pool(permanent_pool);
2✔
742
      pr_pool_tag(parser_pool, "Parser Pool");
2✔
743
    }
744

745
    p = parser_pool;
3✔
746
  }
747

748
  if (parsed_servers == NULL) {
48✔
749
    parser_server_list = &server_list;
47✔
750

751
  } else {
752
    parser_server_list = parsed_servers;
1✔
753
  }
754

755
  parser_servstack = make_array(p, 1, sizeof(server_rec *));
48✔
756
  parser_curr_server = (server_rec **) push_array(parser_servstack);
48✔
757
  *parser_curr_server = main_server;
48✔
758

759
  parser_confstack = make_array(p, 10, sizeof(config_rec *));
48✔
760
  parser_curr_config = (config_rec **) push_array(parser_confstack);
48✔
761
  *parser_curr_config = NULL;
48✔
762

763
  return 0;
48✔
764
}
765

766
/* This functions returns the next line from the configuration stream,
767
 * skipping commented-out lines and trimming trailing and leading whitespace,
768
 * returning, in effect, the next line of configuration data on which to
769
 * act.  This function has the advantage that it can be called by functions
770
 * that don't have access to configuration file handle, such as the
771
 * <IfDefine> and <IfModule> configuration handlers.
772
 */
773
char *pr_parser_read_line(char *buf, size_t bufsz) {
16✔
774
  struct config_src *cs;
775

776
  /* Always use the config stream at the top of the stack. */
777
  cs = parser_sources;
16✔
778

779
  if (buf == NULL ||
32✔
780
      cs == NULL) {
16✔
781
    errno = EINVAL;
1✔
782
    return NULL;
1✔
783
  }
784

785
  if (cs->cs_fh == NULL) {
15✔
786
    errno = EPERM;
×
787
    return NULL;
×
788
  }
789

790
  parser_curr_lineno = cs->cs_lineno;
15✔
791

792
  /* Check for error conditions. */
793

794
  while ((pr_fsio_getline(buf, bufsz, cs->cs_fh, &(cs->cs_lineno))) != NULL) {
30✔
795
    int have_eol = FALSE;
10✔
796
    char *bufp = NULL;
10✔
797
    size_t buflen;
798

799
    pr_signals_handle();
10✔
800

801
    buflen = strlen(buf);
10✔
802
    parser_curr_lineno = cs->cs_lineno;
10✔
803

804
    /* Trim off the trailing newline, if present. */
805
    if (buflen &&
20✔
806
        buf[buflen - 1] == '\n') {
10✔
807
      have_eol = TRUE;
10✔
808
      buf[buflen-1] = '\0';
10✔
809
      buflen--;
10✔
810
    }
811

812
    if (buflen &&
20✔
813
        buf[buflen - 1] == '\r') {
10✔
814
      buf[buflen-1] = '\0';
6✔
815
      buflen--;
6✔
816
    }
817

818
    if (have_eol == FALSE) {
10✔
819
      pr_log_pri(PR_LOG_WARNING,
×
820
        "warning: handling possibly truncated configuration data at "
821
        "line %u of '%s'", cs->cs_lineno, cs->cs_fh->fh_path);
×
822
    }
823

824
    /* Advance past any leading whitespace. */
825
    for (bufp = buf; *bufp && PR_ISSPACE(*bufp); bufp++) {
10✔
826
    }
827

828
    /* Check for commented or blank lines at this point, and just continue on
829
     * to the next configuration line if found.  If not, return the
830
     * configuration line.
831
     */
832
    if (*bufp == '#' || !*bufp) {
10✔
833
      continue;
×
834
    }
835

836
    /* Copy the value of bufp back into the pointer passed in
837
     * and return it.
838
     */
839
    buf = bufp;
840

841
    return buf;
842
  }
843

844
  return NULL;
845
}
846

847
server_rec *pr_parser_server_ctxt_close(void) {
8✔
848
  if (!parser_curr_server) {
8✔
849
    errno = ENOENT;
×
850
    return NULL;
×
851
  }
852

853
  /* Disallow underflows. */
854
  if (parser_curr_server == (server_rec **) parser_servstack->elts) {
8✔
855
    errno = EPERM;
1✔
856
    return NULL;
1✔
857
  }
858

859
  parser_curr_server--;
7✔
860
  parser_servstack->nelts--;
7✔
861

862
  return *parser_curr_server;
7✔
863
}
864

865
server_rec *pr_parser_server_ctxt_get(void) {
27✔
866
  if (parser_curr_server) {
27✔
867
    return *parser_curr_server;
21✔
868
  }
869

870
  errno = ENOENT;
6✔
871
  return NULL;
6✔
872
}
873

874
int pr_parser_server_ctxt_push(server_rec *s) {
14✔
875
  if (s == NULL) {
14✔
876
    errno = EINVAL;
1✔
877
    return -1;
1✔
878
  }
879

880
  if (parser_servstack == NULL) {
13✔
881
    errno = EPERM;
1✔
882
    return -1;
1✔
883
  }
884

885
  parser_curr_server = (server_rec **) push_array(parser_servstack);
12✔
886
  *parser_curr_server = s;
12✔
887

888
  return 0;
12✔
889
}
890

891
server_rec *pr_parser_server_ctxt_open(const char *addrstr) {
11✔
892
  server_rec *s;
893
  pool *p;
894

895
  p = make_sub_pool(permanent_pool);
11✔
896
  pr_pool_tag(p, "<VirtualHost> Pool");
11✔
897

898
  s = (server_rec *) pcalloc(p, sizeof(server_rec));
11✔
899
  s->pool = p;
11✔
900
  s->config_type = CONF_VIRTUAL;
11✔
901
  s->sid = ++parser_sid;
11✔
902
  s->notes = pr_table_nalloc(p, 0, 8);
11✔
903

904
  /* TCP port reuse is disabled by default. */
905
  s->tcp_reuse_port = -1;
11✔
906

907
  /* TCP KeepAlive is enabled by default, with the system defaults. */
908
  s->tcp_keepalive = palloc(s->pool, sizeof(struct tcp_keepalive));
11✔
909
  s->tcp_keepalive->keepalive_enabled = TRUE;
11✔
910
  s->tcp_keepalive->keepalive_idle = -1;
11✔
911
  s->tcp_keepalive->keepalive_count = -1;
11✔
912
  s->tcp_keepalive->keepalive_intvl = -1;
11✔
913

914
  /* Have to make sure it ends up on the end of the chain, otherwise
915
   * main_server becomes useless.
916
   */
917
  xaset_insert_end(*parser_server_list, (xasetmember_t *) s);
11✔
918
  s->set = *parser_server_list;
11✔
919
  if (addrstr) {
11✔
920
    s->ServerAddress = pstrdup(s->pool, addrstr);
11✔
921
  }
922

923
  /* Default server port */
924
  s->ServerPort = pr_inet_getservport(s->pool, "ftp", "tcp");
11✔
925

926
  (void) pr_parser_server_ctxt_push(s);
11✔
927
  return s;
11✔
928
}
929

930
unsigned long pr_parser_set_include_opts(unsigned long opts) {
6✔
931
  unsigned long prev_opts;
932

933
  prev_opts = parser_include_opts;
6✔
934
  parser_include_opts = opts;
6✔
935

936
  return prev_opts;
6✔
937
}
938

939
static const char *tmpfile_patterns[] = {
940
  "*~",
941
  "*.sw?",
942
  NULL
943
};
944

945
static int is_tmp_file(const char *file) {
29✔
946
  register unsigned int i;
947

948
  for (i = 0; tmpfile_patterns[i]; i++) {
83✔
949
    if (pr_fnmatch(tmpfile_patterns[i], file, PR_FNM_PERIOD) == 0) {
56✔
950
      return TRUE;
951
    }
952
  }
953

954
  return FALSE;
955
}
956

957
static int config_filename_cmp(const void *a, const void *b) {
×
958
  return strcmp(*((char **) a), *((char **) b));
×
959
}
960

961
static int parse_wildcard_config_path(pool *p, const char *path,
1✔
962
    unsigned int depth) {
963
  register unsigned int i;
964
  int res, xerrno;
965
  pool *tmp_pool;
966
  array_header *globbed_dirs = NULL;
1✔
967
  const char *component = NULL, *parent_path = NULL, *suffix_path = NULL;
1✔
968
  struct stat st;
969
  size_t path_len, component_len;
970
  char *name_pattern = NULL;
1✔
971
  void *dirh = NULL;
1✔
972
  struct dirent *dent = NULL;
1✔
973

974
  if (depth > PR_PARSER_INCLUDE_MAX_DEPTH) {
1✔
975
    pr_log_pri(PR_LOG_WARNING, "error: resolving wildcard pattern in '%s' "
×
976
      "exceeded maximum filesystem depth (%u)", path,
977
      (unsigned int) PR_PARSER_INCLUDE_MAX_DEPTH);
978
    errno = EINVAL;
×
979
    return -1;
×
980
  }
981

982
  path_len = strlen(path);
1✔
983
  if (path_len < 2) {
1✔
984
    pr_trace_msg(trace_channel, 7, "path '%s' too short to be wildcard path",
×
985
      path);
986

987
    /* The first character must be a slash, and we need at least one more
988
     * character in the path as a glob character.
989
     */
990
    errno = EINVAL;
×
991
    return -1;
×
992
  }
993

994
  tmp_pool = make_sub_pool(p);
1✔
995
  pr_pool_tag(tmp_pool, "Include sub-pool");
1✔
996

997
  /* We need to find the first component of the path which contains glob
998
   * characters.  We then use the path up to the previous component as the
999
   * parent directory to open, and the glob-bearing component as the filter
1000
   * for directories within the parent.
1001
   */
1002

1003
  parent_path = pstrdup(tmp_pool, "/");
1✔
1004
  component = path + 1;
1✔
1005

1006
  while (TRUE) {
×
1007
    int last_component = FALSE;
1✔
1008
    char *ptr;
1009

1010
    pr_signals_handle();
1✔
1011

1012
    ptr = strchr(component, '/');
1✔
1013
    if (ptr != NULL) {
1✔
1014
      component_len = ptr - component;
1✔
1015

1016
    } else {
1017
      component_len = strlen(component);
×
1018
      last_component = TRUE;
×
1019
    }
1020

1021
    if (memchr(component, (int) '*', component_len) != NULL ||
1✔
1022
        memchr(component, (int) '?', component_len) != NULL ||
×
1023
        memchr(component, (int) '[', component_len) != NULL) {
×
1024

1025
      name_pattern = pstrndup(tmp_pool, component, component_len);
1✔
1026

1027
      if (ptr != NULL) {
1✔
1028
        suffix_path = pstrdup(tmp_pool, ptr + 1);
1✔
1029
      }
1030

1031
      break;
1032
    }
1033

1034
    parent_path = pdircat(tmp_pool, parent_path,
×
1035
      pstrndup(tmp_pool, component, component_len), NULL);
1036

1037
    if (last_component == TRUE) {
×
1038
      break;
1039
    }
1040

1041
    component = ptr + 1;
×
1042
  }
1043

1044
  if (name_pattern == NULL) {
1✔
1045
    pr_trace_msg(trace_channel, 4,
×
1046
      "unable to process invalid, non-globbed path '%s'", path);
1047
    errno = ENOENT;
×
1048
    return -1;
×
1049
  }
1050

1051
  pr_trace_msg(trace_channel, 19, "generated globbed name pattern '%s/%s'",
1✔
1052
    parent_path, name_pattern);
1053

1054
  pr_fs_clear_cache2(parent_path);
1✔
1055
  res = pr_fsio_lstat(parent_path, &st);
1✔
1056
  xerrno = errno;
1✔
1057

1058
  if (res < 0) {
1✔
1059
    pr_log_pri(PR_LOG_WARNING,
×
1060
      "error: failed to check configuration path '%s': %s", parent_path,
1061
      strerror(xerrno));
1062

1063
    destroy_pool(tmp_pool);
×
1064
    errno = xerrno;
×
1065
    return -1;
×
1066
  }
1067

1068
  if (S_ISLNK(st.st_mode) &&
1✔
1069
      !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) {
×
1070
    pr_log_pri(PR_LOG_WARNING,
×
1071
      "error: cannot read configuration path '%s': Symbolic link", parent_path);
1072
    destroy_pool(tmp_pool);
×
1073
    errno = ENOTDIR;
×
1074
    return -1;
×
1075
  }
1076

1077
  pr_log_pri(PR_LOG_DEBUG,
1✔
1078
    "processing configuration directory '%s' using pattern '%s', suffix '%s'",
1079
    parent_path, name_pattern, suffix_path);
1080

1081
  dirh = pr_fsio_opendir(parent_path);
1✔
1082
  if (dirh == NULL) {
1✔
1083
    pr_log_pri(PR_LOG_WARNING,
×
1084
      "error: unable to open configuration directory '%s': %s", parent_path,
1085
      strerror(errno));
1086
    destroy_pool(tmp_pool);
×
1087
    errno = EINVAL;
×
1088
    return -1;
×
1089
  }
1090

1091
  globbed_dirs = make_array(tmp_pool, 0, sizeof(char *));
1✔
1092

1093
  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
29✔
1094
    pr_signals_handle();
27✔
1095

1096
    if (strcmp(dent->d_name, ".") == 0 ||
53✔
1097
        strcmp(dent->d_name, "..") == 0) {
26✔
1098
      continue;
2✔
1099
    }
1100

1101
    if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) {
25✔
1102
      if (is_tmp_file(dent->d_name) == TRUE) {
25✔
1103
        pr_trace_msg(trace_channel, 19,
×
1104
          "ignoring temporary file '%s' found in directory '%s'", dent->d_name,
1105
          parent_path);
1106
        continue;
×
1107
      }
1108
    }
1109

1110
    if (pr_fnmatch(name_pattern, dent->d_name, PR_FNM_PERIOD) == 0) {
25✔
1111
      pr_trace_msg(trace_channel, 17,
1✔
1112
        "matched '%s/%s' path with wildcard pattern '%s/%s'", parent_path,
1113
        dent->d_name, parent_path, name_pattern);
1114

1115
      *((char **) push_array(globbed_dirs)) = pdircat(tmp_pool, parent_path,
1✔
1116
        dent->d_name, suffix_path, NULL);
1117
    }
1118
  }
1119

1120
  pr_fsio_closedir(dirh);
1✔
1121

1122
  if (globbed_dirs->nelts == 0) {
1✔
1123
    pr_log_pri(PR_LOG_WARNING,
×
1124
      "error: no matches found for wildcard directory '%s'", path);
1125
    destroy_pool(tmp_pool);
×
1126
    errno = ENOENT;
×
1127
    return -1;
×
1128
  }
1129

1130
  depth++;
1✔
1131

1132
  qsort((void *) globbed_dirs->elts, globbed_dirs->nelts, sizeof(char *),
1✔
1133
    config_filename_cmp);
1134

1135
  for (i = 0; i < globbed_dirs->nelts; i++) {
2✔
1136
    const char *globbed_dir;
1137

1138
    globbed_dir = ((const char **) globbed_dirs->elts)[i];
1✔
1139
    res = parse_config_path2(p, globbed_dir, depth);
1✔
1140
    if (res < 0) {
1✔
1141
      xerrno = errno;
×
1142

1143
      pr_trace_msg(trace_channel, 7, "error parsing wildcard path '%s': %s",
×
1144
        globbed_dir, strerror(xerrno));
1145

1146
      destroy_pool(tmp_pool);
×
1147
      errno = xerrno;
×
1148
      return -1;
×
1149
    }
1150
  }
1151

1152
  destroy_pool(tmp_pool);
1✔
1153
  return 0;
1✔
1154
}
1155

1156
int parse_config_path2(pool *p, const char *path, unsigned int depth) {
15✔
1157
  struct stat st;
1158
  int have_glob;
1159
  void *dirh;
1160
  struct dirent *dent;
1161
  array_header *file_list;
1162
  char *dup_path, *ptr;
1163
  pool *tmp_pool;
1164

1165
  if (p == NULL ||
30✔
1166
      path == NULL ||
28✔
1167
      (depth > PR_PARSER_INCLUDE_MAX_DEPTH)) {
1168
    errno = EINVAL;
3✔
1169
    return -1;
3✔
1170
  }
1171

1172
  if (pr_fs_valid_path(path) < 0) {
12✔
1173
    errno = EINVAL;
1✔
1174
    return -1;
1✔
1175
  }
1176

1177
  have_glob = pr_str_is_fnmatch(path);
11✔
1178
  if (have_glob) {
11✔
1179
    /* Even though the path may be valid, it also may not be a filesystem
1180
     * path; consider custom FSIO modules.  Thus if the path does not start
1181
     * with a slash, it should not be treated as having globs.
1182
     */
1183
    if (*path != '/') {
7✔
1184
      have_glob = FALSE;
×
1185
    }
1186
  }
1187

1188
  pr_fs_clear_cache2(path);
11✔
1189

1190
  if (have_glob) {
11✔
1191
    pr_trace_msg(trace_channel, 19, "parsing '%s' as a globbed path", path);
7✔
1192
  }
1193

1194
  if (!have_glob &&
15✔
1195
      pr_fsio_lstat(path, &st) < 0) {
4✔
1196
    return -1;
1197
  }
1198

1199
  /* If path is not a glob pattern, and is a symlink OR is not a directory,
1200
   * then use the normal parsing function for the file.
1201
   */
1202
  if (have_glob == FALSE &&
13✔
1203
      (S_ISLNK(st.st_mode) ||
3✔
1204
       !S_ISDIR(st.st_mode))) {
1205
    int res, xerrno;
1206

1207
    PRIVS_ROOT
1✔
1208
    res = pr_parser_parse_file(p, path, NULL, 0);
1✔
1209
    xerrno = errno;
1✔
1210
    PRIVS_RELINQUISH
1✔
1211

1212
    errno = xerrno;
1✔
1213
    return res;
1✔
1214
  }
1215

1216
  tmp_pool = make_sub_pool(p);
9✔
1217
  pr_pool_tag(tmp_pool, "Include sub-pool");
9✔
1218

1219
  /* Handle the glob/directory. */
1220
  dup_path = pstrdup(tmp_pool, path);
9✔
1221

1222
  ptr = strrchr(dup_path, '/');
9✔
1223

1224
  if (have_glob) {
9✔
1225
    int have_glob_dir;
1226

1227
    /* Note that we know, by definition, that ptr CANNOT be null here; dup_path
1228
     * is a duplicate of path, and the first character (if nothing else) of
1229
     * path MUST be a slash, per earlier checks.
1230
     */
1231
    *ptr = '\0';
7✔
1232

1233
    /* We just changed ptr, thus we DO need to check whether the now-modified
1234
     * path contains fnmatch(3) characters again.
1235
     */
1236
    have_glob_dir = pr_str_is_fnmatch(dup_path);
7✔
1237
    if (have_glob_dir) {
7✔
1238
      const char *glob_dir;
1239

1240
      if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_WILDCARDS) {
3✔
1241
        pr_log_pri(PR_LOG_WARNING, "error: wildcard patterns not allowed in "
2✔
1242
          "configuration directory name '%s'", dup_path);
1243
        destroy_pool(tmp_pool);
2✔
1244
        errno = EINVAL;
2✔
1245
        return -1;
2✔
1246
      }
1247

1248
      *ptr = '/';
1✔
1249
      glob_dir = pstrdup(p, dup_path);
1✔
1250
      destroy_pool(tmp_pool);
1✔
1251

1252
      return parse_wildcard_config_path(p, glob_dir, depth);
1✔
1253
    }
1254

1255
    ptr++;
4✔
1256

1257
    /* Check the directory component. */
1258
    pr_fs_clear_cache2(dup_path);
4✔
1259
    if (pr_fsio_lstat(dup_path, &st) < 0) {
4✔
1260
      int xerrno = errno;
×
1261

1262
      pr_log_pri(PR_LOG_WARNING,
×
1263
        "error: failed to check configuration path '%s': %s", dup_path,
1264
        strerror(xerrno));
1265

1266
      destroy_pool(tmp_pool);
×
1267
      errno = xerrno;
×
1268
      return -1;
×
1269
    }
1270

1271
    if (S_ISLNK(st.st_mode) &&
4✔
1272
        !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) {
×
1273
      pr_log_pri(PR_LOG_WARNING,
×
1274
        "error: cannot read configuration path '%s': Symbolic link", path);
1275
      destroy_pool(tmp_pool);
×
1276
      errno = ENOTDIR;
×
1277
      return -1;
×
1278
    }
1279

1280
    if (have_glob_dir == FALSE &&
4✔
1281
        pr_str_is_fnmatch(ptr) == FALSE) {
4✔
1282
      pr_log_pri(PR_LOG_WARNING,
×
1283
        "error: wildcard pattern required for file '%s'", ptr);
1284
      destroy_pool(tmp_pool);
×
1285
      errno = EINVAL;
×
1286
      return -1;
×
1287
    }
1288
  }
1289

1290
  pr_trace_msg(trace_channel, 3, "processing configuration directory '%s'",
6✔
1291
    dup_path);
1292

1293
  dirh = pr_fsio_opendir(dup_path);
6✔
1294
  if (dirh == NULL) {
6✔
1295
    pr_log_pri(PR_LOG_WARNING,
×
1296
      "error: unable to open configuration directory '%s': %s", dup_path,
1297
      strerror(errno));
×
1298
    destroy_pool(tmp_pool);
×
1299
    errno = EINVAL;
×
1300
    return -1;
×
1301
  }
1302

1303
  file_list = make_array(tmp_pool, 0, sizeof(char *));
6✔
1304

1305
  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
29✔
1306
    pr_signals_handle();
17✔
1307

1308
    if (strcmp(dent->d_name, ".") == 0 ||
28✔
1309
        strcmp(dent->d_name, "..") == 0) {
11✔
1310
      continue;
12✔
1311
    }
1312

1313
    if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) {
5✔
1314
      if (is_tmp_file(dent->d_name) == TRUE) {
4✔
1315
        pr_trace_msg(trace_channel, 19,
2✔
1316
          "ignoring temporary file '%s' found in directory '%s'", dent->d_name,
1317
          dup_path);
1318
        continue;
2✔
1319
      }
1320
    }
1321

1322
    if (have_glob == FALSE ||
3✔
1323
        (ptr != NULL &&
3✔
1324
         pr_fnmatch(ptr, dent->d_name, PR_FNM_PERIOD) == 0)) {
3✔
1325
      *((char **) push_array(file_list)) = pdircat(tmp_pool, dup_path,
3✔
1326
        dent->d_name, NULL);
1327
    }
1328
  }
1329

1330
  pr_fsio_closedir(dirh);
6✔
1331

1332
  if (file_list->nelts) {
6✔
1333
    register unsigned int i;
1334

1335
    qsort((void *) file_list->elts, file_list->nelts, sizeof(char *),
3✔
1336
      config_filename_cmp);
1337

1338
    for (i = 0; i < file_list->nelts; i++) {
6✔
1339
      int res, xerrno;
1340
      char *file;
1341

1342
      file = ((char **) file_list->elts)[i];
3✔
1343

1344
      /* Make sure we always parse the files with root privs.  The
1345
       * previously parsed file might have had root privs relinquished
1346
       * (e.g. by its directive handlers), but when we first start up,
1347
       * we have root privs.  See Bug#3855.
1348
       */
1349
      PRIVS_ROOT
3✔
1350
      res = pr_parser_parse_file(tmp_pool, file, NULL, 0);
3✔
1351
      xerrno = errno;
3✔
1352
      PRIVS_RELINQUISH
3✔
1353

1354
      if (res < 0) {
3✔
1355
        pr_log_pri(PR_LOG_WARNING,
×
1356
          "error: unable to parse file '%s': %s", file, strerror(xerrno));
1357
        pr_log_pri(PR_LOG_WARNING, "%s",
×
1358
          "error: check `proftpd --configtest -d10` for details");
1359

1360
        destroy_pool(tmp_pool);
×
1361

1362
        /* Any error other than EINVAL is logged as a warning, but ignored,
1363
         * by the Include directive handler.  Thus we always return EINVAL
1364
         * here to halt further parsing (Issue #1721).
1365
         */
1366
        errno = EINVAL;
×
1367
        return -1;
×
1368
      }
1369
    }
1370
  }
1371

1372
  destroy_pool(tmp_pool);
6✔
1373
  return 0;
6✔
1374
}
1375

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