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

proftpd / proftpd / 14136377169

28 Mar 2025 07:21PM UTC coverage: 92.663% (-0.4%) from 93.027%
14136377169

push

github

Castaglia
To aid in debugging of unexpectedly malfunctioning authorized SSH keys, add trace logging of any lines of text which are not formatted as expected.

47143 of 50876 relevant lines covered (92.66%)

237.38 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