Coverage Report

Created: 2024-02-11 06:31

/src/proftpd/src/parser.c
Line
Count
Source (jump to first uncovered line)
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
0
#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
0
#define PARSER_CONFIG_SRC_POOL_SZ 512
64
65
static struct config_src *parser_sources = NULL;
66
67
/* Private functions
68
 */
69
70
0
static struct config_src *add_config_source(pr_fh_t *fh) {
71
0
  pool *p;
72
0
  struct config_src *cs;
73
74
0
  p = pr_pool_create_sz(parser_pool, PARSER_CONFIG_SRC_POOL_SZ);
75
0
  pr_pool_tag(p, "configuration source pool");
76
77
0
  cs = pcalloc(p, sizeof(struct config_src));
78
0
  cs->cs_pool = p;
79
0
  cs->cs_next = NULL;
80
0
  cs->cs_fh = fh;
81
0
  cs->cs_lineno = 0;
82
83
0
  if (parser_sources == NULL) {
84
0
    parser_sources = cs;
85
86
0
  } else {
87
0
    cs->cs_next = parser_sources;
88
0
    parser_sources = cs;
89
0
  }
90
91
0
  return cs;
92
0
}
93
94
0
static char *get_config_word(pool *p, char *word) {
95
0
  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
0
  wordlen = strlen(word);
103
0
  if (wordlen > 7) {
104
0
    char *ptr = NULL;
105
106
0
    pr_trace_msg(trace_channel, 27, "word '%s' long enough for environment "
107
0
      "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
0
    ptr = strstr(word, "%{env:");
115
0
    while (ptr != NULL) {
116
0
      char *env, *key, *ptr2, *var;
117
0
      unsigned int keylen;
118
119
0
      pr_signals_handle();
120
121
0
      ptr2 = strchr(ptr + 6, '}');
122
0
      if (ptr2 == NULL) {
123
        /* No terminating marker; continue on to the next potential
124
         * variable in the word.
125
         */
126
0
        ptr2 = ptr + 6;
127
0
        ptr = strstr(ptr2, "%{env:");
128
0
        continue;
129
0
      }
130
131
0
      keylen = (ptr2 - ptr - 6);
132
0
      var = pstrndup(p, ptr, (ptr2 - ptr) + 1);
133
134
0
      key = pstrndup(p, ptr + 6, keylen);
135
136
0
      pr_trace_msg(trace_channel, 17,
137
0
        "word '%s' uses environment variable '%s'", word, key);
138
0
      env = pr_env_get(p, key);
139
0
      if (env == NULL) {
140
        /* No value in the environment; continue on to the next potential
141
         * variable in the word.
142
         */
143
0
        pr_trace_msg(trace_channel, 17, "no value found for environment "
144
0
          "variable '%s' for word '%s', ignoring", key, word);
145
146
0
        word = (char *) sreplace(p, word, var, "", NULL);
147
0
        ptr = strstr(word, "%{env:");
148
0
        continue;
149
0
      }
150
151
0
      pr_trace_msg(trace_channel, 17,
152
0
        "resolved environment variable '%s' to value '%s' for word '%s'", key,
153
0
        env, word);
154
155
0
      word = (char *) sreplace(p, word, var, env, NULL);
156
0
      ptr = strstr(word, "%{env:");
157
0
    }
158
159
0
  } else {
160
0
    pr_trace_msg(trace_channel, 27, "word '%s' not long enough for environment "
161
0
      "variable (%lu < minimum required 7)", word, (unsigned long) wordlen);
162
0
  }
163
164
0
  return pstrdup(p, word);
165
0
}
166
167
0
static void remove_config_source(void) {
168
0
  struct config_src *cs = parser_sources;
169
170
0
  if (cs != NULL) {
171
0
    parser_sources = cs->cs_next;
172
0
    destroy_pool(cs->cs_pool);
173
0
  }
174
0
}
175
176
/* Public API
177
 */
178
179
0
int pr_parser_cleanup(void) {
180
0
  if (parser_pool != NULL) {
181
0
    if (parser_servstack->nelts > 1 ||
182
0
        (parser_curr_config && *parser_curr_config)) {
183
0
      errno = EPERM;
184
0
      return -1;
185
0
    }
186
187
0
    destroy_pool(parser_pool);
188
0
    parser_pool = NULL;
189
0
  }
190
191
0
  parser_servstack = NULL;
192
0
  parser_curr_server = NULL;
193
194
0
  parser_confstack = NULL;
195
0
  parser_curr_config = NULL;
196
197
  /* Reset the SID counter. */
198
0
  parser_sid = 1;
199
200
0
  return 0;
201
0
}
202
203
0
config_rec *pr_parser_config_ctxt_close(int *empty) {
204
0
  config_rec *c = *parser_curr_config;
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
0
  if (parser_curr_config == (config_rec **) parser_confstack->elts) {
212
0
    if (c != NULL &&
213
0
        (!c->subset || !c->subset->xas_list)) {
214
0
      xaset_remove(c->set, (xasetmember_t *) c);
215
0
      destroy_pool(c->pool);
216
217
0
      if (empty) {
218
0
        *empty = TRUE;
219
0
      }
220
0
    }
221
222
0
    if (*parser_curr_config) {
223
0
      *parser_curr_config = NULL;
224
0
    }
225
226
0
    return NULL;
227
0
  }
228
229
0
  if (c != NULL &&
230
0
      (!c->subset || !c->subset->xas_list)) {
231
0
    xaset_remove(c->set, (xasetmember_t *) c);
232
0
    destroy_pool(c->pool);
233
234
0
    if (empty) {
235
0
      *empty = TRUE;
236
0
    }
237
0
  }
238
239
0
  parser_curr_config--;
240
0
  parser_confstack->nelts--;
241
242
0
  return *parser_curr_config;
243
0
}
244
245
0
config_rec *pr_parser_config_ctxt_get(void) {
246
0
  if (parser_curr_config) {
247
0
    return *parser_curr_config;
248
0
  }
249
250
0
  errno = ENOENT;
251
0
  return NULL;
252
0
}
253
254
0
config_rec *pr_parser_config_ctxt_open(const char *name) {
255
0
  config_rec *c = NULL, *parent = *parser_curr_config;
256
0
  pool *c_pool = NULL, *parent_pool = NULL;
257
0
  xaset_t **set = NULL;
258
259
0
  if (name == NULL) {
260
0
    errno = EINVAL;
261
0
    return NULL;
262
0
  }
263
264
0
  if (parent != NULL) {
265
0
    parent_pool = parent->pool;
266
0
    set = &parent->subset;
267
268
0
  } else {
269
0
    parent_pool = (*parser_curr_server)->pool;
270
0
    set = &(*parser_curr_server)->conf;
271
0
  }
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
0
  if (strcasecmp(name, "<Global>") == 0) {
281
0
    if (global_config_pool == NULL) {
282
0
      global_config_pool = make_sub_pool(permanent_pool);
283
0
      pr_pool_tag(global_config_pool, "<Global> Pool");
284
0
    }
285
286
0
    parent_pool = global_config_pool;
287
0
  }
288
289
0
  c_pool = make_sub_pool(parent_pool);
290
0
  pr_pool_tag(c_pool, "sub-config pool");
291
292
0
  c = (config_rec *) pcalloc(c_pool, sizeof(config_rec));
293
294
0
  if (!*set) {
295
0
    pool *set_pool = make_sub_pool(parent_pool);
296
0
    *set = xaset_create(set_pool, NULL);
297
0
    (*set)->pool = set_pool;
298
0
  }
299
300
0
  xaset_insert(*set, (xasetmember_t *) c);
301
302
0
  c->pool = c_pool;
303
0
  c->set = *set;
304
0
  c->parent = parent;
305
0
  c->name = pstrdup(c->pool, name);
306
307
0
  if (parent != NULL) {
308
0
    if (parent->config_type == CONF_DYNDIR) {
309
0
      c->flags |= CF_DYNAMIC;
310
0
    }
311
0
  }
312
313
0
  (void) pr_parser_config_ctxt_push(c);
314
0
  return c;
315
0
}
316
317
0
int pr_parser_config_ctxt_push(config_rec *c) {
318
0
  if (c == NULL) {
319
0
    errno = EINVAL;
320
0
    return -1;
321
0
  }
322
323
0
  if (parser_confstack == NULL) {
324
0
    errno = EPERM;
325
0
    return -1;
326
0
  }
327
328
0
  if (!*parser_curr_config) {
329
0
    *parser_curr_config = c;
330
331
0
  } else {
332
0
    parser_curr_config = (config_rec **) push_array(parser_confstack);
333
0
    *parser_curr_config = c;
334
0
  }
335
336
0
  return 0;
337
0
}
338
339
0
unsigned int pr_parser_get_lineno(void) {
340
0
  return parser_curr_lineno;
341
0
}
342
343
/* Return an array of all supported/known configuration directives. */
344
0
static array_header *get_all_directives(pool *p) {
345
0
  array_header *names;
346
0
  conftable *tab;
347
0
  int idx;
348
0
  unsigned int hash;
349
350
0
  names = make_array(p, 1, sizeof(const char *));
351
352
0
  idx = -1;
353
0
  hash = 0;
354
0
  tab = pr_stash_get_symbol2(PR_SYM_CONF, NULL, NULL, &idx, &hash);
355
0
  while (idx != -1) {
356
0
    pr_signals_handle();
357
358
0
    if (tab != NULL) {
359
0
      *((const char **) push_array(names)) = pstrdup(p, tab->directive);
360
361
0
    } else {
362
0
      idx++;
363
0
    }
364
365
0
    tab = pr_stash_get_symbol2(PR_SYM_CONF, NULL, tab, &idx, &hash);
366
0
  }
367
368
0
  return names;
369
0
}
370
371
int pr_parser_parse_file(pool *p, const char *path, config_rec *start,
372
0
    int flags) {
373
0
  pr_fh_t *fh;
374
0
  struct stat st;
375
0
  struct config_src *cs;
376
0
  cmd_rec *cmd;
377
0
  pool *tmp_pool;
378
0
  char *buf, *report_path;
379
0
  size_t bufsz;
380
381
0
  if (path == NULL) {
382
0
    errno = EINVAL;
383
0
    return -1;
384
0
  }
385
386
0
  if (parser_servstack == NULL) {
387
0
    errno = EPERM;
388
0
    return -1;
389
0
  }
390
391
0
  tmp_pool = make_sub_pool(p ? p : permanent_pool);
392
0
  pr_pool_tag(tmp_pool, "parser file pool");
393
394
0
  report_path = (char *) path;
395
0
  if (session.chroot_path) {
396
0
    report_path = pdircat(tmp_pool, session.chroot_path, path, NULL);
397
0
  }
398
399
0
  if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
400
0
    pr_trace_msg(trace_channel, 3, "parsing '%s' configuration", report_path);
401
0
  }
402
403
0
  fh = pr_fsio_open(path, O_RDONLY);
404
0
  if (fh == NULL) {
405
0
    int xerrno = errno;
406
407
0
    destroy_pool(tmp_pool);
408
409
0
    errno = xerrno;
410
0
    return -1;
411
0
  }
412
413
  /* Stat the opened file to determine the optimal buffer size for IO. */
414
0
  memset(&st, 0, sizeof(st));
415
0
  if (pr_fsio_fstat(fh, &st) < 0) {
416
0
    int xerrno = errno;
417
418
0
    pr_fsio_close(fh);
419
0
    destroy_pool(tmp_pool);
420
421
0
    errno = xerrno;
422
0
    return -1;
423
0
  }
424
425
0
  if (S_ISDIR(st.st_mode)) {
426
0
    pr_fsio_close(fh);
427
0
    destroy_pool(tmp_pool);
428
429
0
    errno = EISDIR;
430
0
    return -1;
431
0
  }
432
433
  /* Advise the platform that we will be only reading this file
434
   * sequentially.
435
   */
436
0
  pr_fs_fadvise(PR_FH_FD(fh), 0, 0, PR_FS_FADVISE_SEQUENTIAL);
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
0
  if (st.st_mode & S_IWOTH) {
444
0
    pr_log_pri(PR_LOG_WARNING, "warning: config file '%s' is world-writable",
445
0
     path);
446
0
  }
447
448
0
  fh->fh_iosz = st.st_blksize;
449
450
  /* Push the configuration information onto the stack of configuration
451
   * sources.
452
   */
453
0
  cs = add_config_source(fh);
454
455
0
  if (start != NULL) {
456
0
    (void) pr_parser_config_ctxt_push(start);
457
0
  }
458
459
0
  bufsz = PR_TUNABLE_PARSER_BUFFER_SIZE;
460
0
  buf = pcalloc(tmp_pool, bufsz + 1);
461
462
0
  while (pr_parser_read_line(buf, bufsz) != NULL) {
463
0
    pool *parsed_pool;
464
0
    pr_parsed_line_t *parsed_line;
465
466
0
    pr_signals_handle();
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
0
    parsed_pool = make_sub_pool(tmp_pool);
473
0
    parsed_line = pcalloc(parsed_pool, sizeof(pr_parsed_line_t));
474
0
    parsed_line->text = pstrdup(parsed_pool, buf);
475
0
    parsed_line->source_file = report_path;
476
0
    parsed_line->source_lineno = cs->cs_lineno;
477
478
0
    cmd = pr_parser_parse_line(tmp_pool, buf, 0);
479
0
    if (cmd == NULL) {
480
0
      destroy_pool(parsed_pool);
481
0
      continue;
482
0
    }
483
484
    /* Generate an event about the parsed line of text, for any interested
485
     * parties.
486
     */
487
0
    parsed_line->cmd = cmd;
488
0
    pr_event_generate("core.parsed-line", parsed_line);
489
0
    destroy_pool(parsed_pool);
490
491
0
    if (cmd->argc) {
492
0
      conftable *conftab;
493
0
      char found = FALSE;
494
495
0
      cmd->server = *parser_curr_server;
496
0
      cmd->config = *parser_curr_config;
497
498
0
      conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], NULL,
499
0
        &cmd->stash_index, &cmd->stash_hash);
500
0
      while (conftab != NULL) {
501
0
        modret_t *mr;
502
503
0
        pr_signals_handle();
504
505
0
        cmd->argv[0] = conftab->directive;
506
507
0
        pr_trace_msg(trace_channel, 7,
508
0
          "dispatching directive '%s' to module mod_%s", conftab->directive,
509
0
          conftab->m->name);
510
511
0
        mr = pr_module_call(conftab->m, conftab->handler, cmd);
512
0
        if (mr != NULL) {
513
0
          if (MODRET_ISERROR(mr)) {
514
0
            if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
515
0
              pr_log_pri(PR_LOG_WARNING, "fatal: %s on line %u of '%s'",
516
0
                MODRET_ERRMSG(mr), cs->cs_lineno, report_path);
517
0
              destroy_pool(tmp_pool);
518
0
              errno = EPERM;
519
0
              return -1;
520
0
            }
521
522
0
            pr_log_pri(PR_LOG_WARNING, "warning: %s on line %u of '%s'",
523
0
              MODRET_ERRMSG(mr), cs->cs_lineno, report_path);
524
0
          }
525
0
        }
526
527
0
        if (!MODRET_ISDECLINED(mr)) {
528
0
          found = TRUE;
529
0
        }
530
531
0
        conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], conftab,
532
0
          &cmd->stash_index, &cmd->stash_hash);
533
0
      }
534
535
0
      if (cmd->tmp_pool != NULL) {
536
0
        destroy_pool(cmd->tmp_pool);
537
0
        cmd->tmp_pool = NULL;
538
0
      }
539
540
0
      if (found == FALSE) {
541
0
        register unsigned int i;
542
0
        char *name;
543
0
        size_t namelen;
544
0
        int non_ascii = FALSE;
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
0
        name = cmd->argv[0];
559
0
        namelen = strlen(name);
560
561
0
        for (i = 0; i < namelen; i++) {
562
0
          if (!isascii((int) name[i])) {
563
0
            non_ascii = TRUE;
564
0
            break;
565
0
          }
566
0
        }
567
568
0
        if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
569
0
          pr_log_pri(PR_LOG_WARNING, "fatal: unknown configuration directive "
570
0
            "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path);
571
0
          if (non_ascii) {
572
0
            pr_log_pri(PR_LOG_WARNING, "fatal: malformed directive name "
573
0
              "'%s' (contains non-ASCII characters)", name);
574
575
0
          } else {
576
0
            array_header *directives, *similars;
577
578
0
            directives = get_all_directives(tmp_pool);
579
0
            similars = pr_str_get_similars(tmp_pool, name, directives, 0,
580
0
              PR_STR_FL_IGNORE_CASE);
581
0
            if (similars != NULL &&
582
0
                similars->nelts > 0) {
583
0
              unsigned int nelts;
584
0
              const char **names, *msg;
585
586
0
              names = similars->elts;
587
0
              nelts = similars->nelts;
588
0
              if (nelts > 4) {
589
0
                nelts = 4;
590
0
              }
591
592
0
              msg = "fatal: Did you mean:";
593
594
0
              if (nelts == 1) {
595
0
                msg = pstrcat(tmp_pool, msg, " ", names[0], NULL);
596
597
0
              } else {
598
0
                for (i = 0; i < nelts; i++) {
599
0
                  msg = pstrcat(tmp_pool, msg, "\n  ", names[i], NULL);
600
0
                }
601
0
              }
602
603
0
              pr_log_pri(PR_LOG_WARNING, "%s", msg);
604
0
            }
605
0
          }
606
607
0
          destroy_pool(tmp_pool);
608
0
          errno = EPERM;
609
0
          return -1;
610
0
        }
611
612
0
        pr_log_pri(PR_LOG_WARNING, "warning: unknown configuration directive "
613
0
          "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path);
614
0
        if (non_ascii) {
615
0
          pr_log_pri(PR_LOG_WARNING, "warning: malformed directive name "
616
0
            "'%s' (contains non-ASCII characters)", name);
617
0
        }
618
0
      }
619
0
    }
620
621
0
    destroy_pool(cmd->pool);
622
0
    memset(buf, '\0', bufsz);
623
0
  }
624
625
  /* Pop this configuration stream from the stack. */
626
0
  remove_config_source();
627
628
0
  pr_fsio_close(fh);
629
630
0
  destroy_pool(tmp_pool);
631
0
  return 0;
632
0
}
633
634
0
cmd_rec *pr_parser_parse_line(pool *p, const char *text, size_t text_len) {
635
0
  register unsigned int i;
636
0
  char *arg = "", *ptr, *word = NULL;
637
0
  cmd_rec *cmd = NULL;
638
0
  pool *sub_pool = NULL;
639
0
  array_header *arr = NULL;
640
641
0
  if (p == NULL ||
642
0
      text == NULL) {
643
0
    errno = EINVAL;
644
0
    return NULL;
645
0
  }
646
647
0
  if (text_len == 0) {
648
0
    text_len = strlen(text);
649
0
  }
650
651
0
  if (text_len == 0) {
652
0
    errno = ENOENT;
653
0
    return NULL;
654
0
  }
655
656
0
  ptr = (char *) text;
657
658
  /* Build a new pool for the command structure and array */
659
0
  sub_pool = make_sub_pool(p);
660
0
  pr_pool_tag(sub_pool, "parser cmd subpool");
661
662
0
  cmd = pcalloc(sub_pool, sizeof(cmd_rec));
663
0
  cmd->pool = sub_pool;
664
0
  cmd->stash_index = -1;
665
0
  cmd->stash_hash = 0;
666
667
  /* Add each word to the array */
668
0
  arr = make_array(cmd->pool, 4, sizeof(char **));
669
0
  while ((word = pr_str_get_word(&ptr, 0)) != NULL) {
670
0
    char *ptr2;
671
672
0
    pr_signals_handle();
673
0
    ptr2 = get_config_word(cmd->pool, word);
674
0
    *((char **) push_array(arr)) = ptr2;
675
0
    cmd->argc++;
676
0
  }
677
678
  /* Terminate the array with a NULL. */
679
0
  *((char **) push_array(arr)) = NULL;
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
0
  cmd->argv = (void **) arr->elts;
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
0
  if (cmd->argc &&
699
0
      *((char *) cmd->argv[0]) == '<') {
700
0
    char *cp;
701
0
    size_t cp_len;
702
703
0
    cp = cmd->argv[cmd->argc-1];
704
0
    cp_len = strlen(cp);
705
706
0
    if (*(cp + cp_len-1) == '>' &&
707
0
        cmd->argc > 1) {
708
709
0
      if (strcmp(cp, ">") == 0) {
710
0
        cmd->argv[cmd->argc-1] = NULL;
711
0
        cmd->argc--;
712
713
0
      } else {
714
0
        *(cp + cp_len-1) = '\0';
715
0
      }
716
717
0
      cp = cmd->argv[0];
718
0
      cp_len = strlen(cp);
719
0
      if (*(cp + cp_len-1) != '>') {
720
0
        cmd->argv[0] = pstrcat(cmd->pool, cp, ">", NULL);
721
0
      }
722
0
    }
723
0
  }
724
725
0
  if (cmd->argc < 2) {
726
0
    arg = pstrdup(cmd->pool, arg);
727
0
  }
728
729
0
  for (i = 1; i < cmd->argc; i++) {
730
0
    arg = pstrcat(cmd->pool, arg, *arg ? " " : "", cmd->argv[i], NULL);
731
0
  }
732
733
0
  cmd->arg = arg;
734
0
  return cmd;
735
0
}
736
737
0
int pr_parser_prepare(pool *p, xaset_t **parsed_servers) {
738
739
0
  if (p == NULL) {
740
0
    if (parser_pool == NULL) {
741
0
      parser_pool = make_sub_pool(permanent_pool);
742
0
      pr_pool_tag(parser_pool, "Parser Pool");
743
0
    }
744
745
0
    p = parser_pool;
746
0
  }
747
748
0
  if (parsed_servers == NULL) {
749
0
    parser_server_list = &server_list;
750
751
0
  } else {
752
0
    parser_server_list = parsed_servers;
753
0
  }
754
755
0
  parser_servstack = make_array(p, 1, sizeof(server_rec *));
756
0
  parser_curr_server = (server_rec **) push_array(parser_servstack);
757
0
  *parser_curr_server = main_server;
758
759
0
  parser_confstack = make_array(p, 10, sizeof(config_rec *));
760
0
  parser_curr_config = (config_rec **) push_array(parser_confstack);
761
0
  *parser_curr_config = NULL;
762
763
0
  return 0;
764
0
}
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
0
char *pr_parser_read_line(char *buf, size_t bufsz) {
774
0
  struct config_src *cs;
775
776
  /* Always use the config stream at the top of the stack. */
777
0
  cs = parser_sources;
778
779
0
  if (buf == NULL ||
780
0
      cs == NULL) {
781
0
    errno = EINVAL;
782
0
    return NULL;
783
0
  }
784
785
0
  if (cs->cs_fh == NULL) {
786
0
    errno = EPERM;
787
0
    return NULL;
788
0
  }
789
790
0
  parser_curr_lineno = cs->cs_lineno;
791
792
  /* Check for error conditions. */
793
794
0
  while ((pr_fsio_getline(buf, bufsz, cs->cs_fh, &(cs->cs_lineno))) != NULL) {
795
0
    int have_eol = FALSE;
796
0
    char *bufp = NULL;
797
0
    size_t buflen;
798
799
0
    pr_signals_handle();
800
801
0
    buflen = strlen(buf);
802
0
    parser_curr_lineno = cs->cs_lineno;
803
804
    /* Trim off the trailing newline, if present. */
805
0
    if (buflen &&
806
0
        buf[buflen - 1] == '\n') {
807
0
      have_eol = TRUE;
808
0
      buf[buflen-1] = '\0';
809
0
      buflen--;
810
0
    }
811
812
0
    if (buflen &&
813
0
        buf[buflen - 1] == '\r') {
814
0
      buf[buflen-1] = '\0';
815
0
      buflen--;
816
0
    }
817
818
0
    if (have_eol == FALSE) {
819
0
      pr_log_pri(PR_LOG_WARNING,
820
0
        "warning: handling possibly truncated configuration data at "
821
0
        "line %u of '%s'", cs->cs_lineno, cs->cs_fh->fh_path);
822
0
    }
823
824
    /* Advance past any leading whitespace. */
825
0
    for (bufp = buf; *bufp && PR_ISSPACE(*bufp); bufp++) {
826
0
    }
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
0
    if (*bufp == '#' || !*bufp) {
833
0
      continue;
834
0
    }
835
836
    /* Copy the value of bufp back into the pointer passed in
837
     * and return it.
838
     */
839
0
    buf = bufp;
840
841
0
    return buf;
842
0
  }
843
844
0
  return NULL;
845
0
}
846
847
0
server_rec *pr_parser_server_ctxt_close(void) {
848
0
  if (!parser_curr_server) {
849
0
    errno = ENOENT;
850
0
    return NULL;
851
0
  }
852
853
  /* Disallow underflows. */
854
0
  if (parser_curr_server == (server_rec **) parser_servstack->elts) {
855
0
    errno = EPERM;
856
0
    return NULL;
857
0
  }
858
859
0
  parser_curr_server--;
860
0
  parser_servstack->nelts--;
861
862
0
  return *parser_curr_server;
863
0
}
864
865
0
server_rec *pr_parser_server_ctxt_get(void) {
866
0
  if (parser_curr_server) {
867
0
    return *parser_curr_server;
868
0
  }
869
870
0
  errno = ENOENT;
871
0
  return NULL;
872
0
}
873
874
0
int pr_parser_server_ctxt_push(server_rec *s) {
875
0
  if (s == NULL) {
876
0
    errno = EINVAL;
877
0
    return -1;
878
0
  }
879
880
0
  if (parser_servstack == NULL) {
881
0
    errno = EPERM;
882
0
    return -1;
883
0
  }
884
885
0
  parser_curr_server = (server_rec **) push_array(parser_servstack);
886
0
  *parser_curr_server = s;
887
888
0
  return 0;
889
0
}
890
891
0
server_rec *pr_parser_server_ctxt_open(const char *addrstr) {
892
0
  server_rec *s;
893
0
  pool *p;
894
895
0
  p = make_sub_pool(permanent_pool);
896
0
  pr_pool_tag(p, "<VirtualHost> Pool");
897
898
0
  s = (server_rec *) pcalloc(p, sizeof(server_rec));
899
0
  s->pool = p;
900
0
  s->config_type = CONF_VIRTUAL;
901
0
  s->sid = ++parser_sid;
902
0
  s->notes = pr_table_nalloc(p, 0, 8);
903
904
  /* TCP port reuse is disabled by default. */
905
0
  s->tcp_reuse_port = -1;
906
907
  /* TCP KeepAlive is enabled by default, with the system defaults. */
908
0
  s->tcp_keepalive = palloc(s->pool, sizeof(struct tcp_keepalive));
909
0
  s->tcp_keepalive->keepalive_enabled = TRUE;
910
0
  s->tcp_keepalive->keepalive_idle = -1;
911
0
  s->tcp_keepalive->keepalive_count = -1;
912
0
  s->tcp_keepalive->keepalive_intvl = -1;
913
914
  /* Have to make sure it ends up on the end of the chain, otherwise
915
   * main_server becomes useless.
916
   */
917
0
  xaset_insert_end(*parser_server_list, (xasetmember_t *) s);
918
0
  s->set = *parser_server_list;
919
0
  if (addrstr) {
920
0
    s->ServerAddress = pstrdup(s->pool, addrstr);
921
0
  }
922
923
  /* Default server port */
924
0
  s->ServerPort = pr_inet_getservport(s->pool, "ftp", "tcp");
925
926
0
  (void) pr_parser_server_ctxt_push(s);
927
0
  return s;
928
0
}
929
930
0
unsigned long pr_parser_set_include_opts(unsigned long opts) {
931
0
  unsigned long prev_opts;
932
933
0
  prev_opts = parser_include_opts;
934
0
  parser_include_opts = opts;
935
936
0
  return prev_opts;
937
0
}
938
939
static const char *tmpfile_patterns[] = {
940
  "*~",
941
  "*.sw?",
942
  NULL
943
};
944
945
0
static int is_tmp_file(const char *file) {
946
0
  register unsigned int i;
947
948
0
  for (i = 0; tmpfile_patterns[i]; i++) {
949
0
    if (pr_fnmatch(tmpfile_patterns[i], file, PR_FNM_PERIOD) == 0) {
950
0
      return TRUE;
951
0
    }
952
0
  }
953
954
0
  return FALSE;
955
0
}
956
957
0
static int config_filename_cmp(const void *a, const void *b) {
958
0
  return strcmp(*((char **) a), *((char **) b));
959
0
}
960
961
static int parse_wildcard_config_path(pool *p, const char *path,
962
0
    unsigned int depth) {
963
0
  register unsigned int i;
964
0
  int res, xerrno;
965
0
  pool *tmp_pool;
966
0
  array_header *globbed_dirs = NULL;
967
0
  const char *component = NULL, *parent_path = NULL, *suffix_path = NULL;
968
0
  struct stat st;
969
0
  size_t path_len, component_len;
970
0
  char *name_pattern = NULL;
971
0
  void *dirh = NULL;
972
0
  struct dirent *dent = NULL;
973
974
0
  if (depth > PR_PARSER_INCLUDE_MAX_DEPTH) {
975
0
    pr_log_pri(PR_LOG_WARNING, "error: resolving wildcard pattern in '%s' "
976
0
      "exceeded maximum filesystem depth (%u)", path,
977
0
      (unsigned int) PR_PARSER_INCLUDE_MAX_DEPTH);
978
0
    errno = EINVAL;
979
0
    return -1;
980
0
  }
981
982
0
  path_len = strlen(path);
983
0
  if (path_len < 2) {
984
0
    pr_trace_msg(trace_channel, 7, "path '%s' too short to be wildcard path",
985
0
      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
0
    errno = EINVAL;
991
0
    return -1;
992
0
  }
993
994
0
  tmp_pool = make_sub_pool(p);
995
0
  pr_pool_tag(tmp_pool, "Include sub-pool");
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
0
  parent_path = pstrdup(tmp_pool, "/");
1004
0
  component = path + 1;
1005
1006
0
  while (TRUE) {
1007
0
    int last_component = FALSE;
1008
0
    char *ptr;
1009
1010
0
    pr_signals_handle();
1011
1012
0
    ptr = strchr(component, '/');
1013
0
    if (ptr != NULL) {
1014
0
      component_len = ptr - component;
1015
1016
0
    } else {
1017
0
      component_len = strlen(component);
1018
0
      last_component = TRUE;
1019
0
    }
1020
1021
0
    if (memchr(component, (int) '*', component_len) != NULL ||
1022
0
        memchr(component, (int) '?', component_len) != NULL ||
1023
0
        memchr(component, (int) '[', component_len) != NULL) {
1024
1025
0
      name_pattern = pstrndup(tmp_pool, component, component_len);
1026
1027
0
      if (ptr != NULL) {
1028
0
        suffix_path = pstrdup(tmp_pool, ptr + 1);
1029
0
      }
1030
1031
0
      break;
1032
0
    }
1033
1034
0
    parent_path = pdircat(tmp_pool, parent_path,
1035
0
      pstrndup(tmp_pool, component, component_len), NULL);
1036
1037
0
    if (last_component == TRUE) {
1038
0
      break;
1039
0
    }
1040
1041
0
    component = ptr + 1;
1042
0
  }
1043
1044
0
  if (name_pattern == NULL) {
1045
0
    pr_trace_msg(trace_channel, 4,
1046
0
      "unable to process invalid, non-globbed path '%s'", path);
1047
0
    errno = ENOENT;
1048
0
    return -1;
1049
0
  }
1050
1051
0
  pr_trace_msg(trace_channel, 19, "generated globbed name pattern '%s/%s'",
1052
0
    parent_path, name_pattern);
1053
1054
0
  pr_fs_clear_cache2(parent_path);
1055
0
  res = pr_fsio_lstat(parent_path, &st);
1056
0
  xerrno = errno;
1057
1058
0
  if (res < 0) {
1059
0
    pr_log_pri(PR_LOG_WARNING,
1060
0
      "error: failed to check configuration path '%s': %s", parent_path,
1061
0
      strerror(xerrno));
1062
1063
0
    destroy_pool(tmp_pool);
1064
0
    errno = xerrno;
1065
0
    return -1;
1066
0
  }
1067
1068
0
  if (S_ISLNK(st.st_mode) &&
1069
0
      !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) {
1070
0
    pr_log_pri(PR_LOG_WARNING,
1071
0
      "error: cannot read configuration path '%s': Symbolic link", parent_path);
1072
0
    destroy_pool(tmp_pool);
1073
0
    errno = ENOTDIR;
1074
0
    return -1;
1075
0
  }
1076
1077
0
  pr_log_pri(PR_LOG_DEBUG,
1078
0
    "processing configuration directory '%s' using pattern '%s', suffix '%s'",
1079
0
    parent_path, name_pattern, suffix_path);
1080
1081
0
  dirh = pr_fsio_opendir(parent_path);
1082
0
  if (dirh == NULL) {
1083
0
    pr_log_pri(PR_LOG_WARNING,
1084
0
      "error: unable to open configuration directory '%s': %s", parent_path,
1085
0
      strerror(errno));
1086
0
    destroy_pool(tmp_pool);
1087
0
    errno = EINVAL;
1088
0
    return -1;
1089
0
  }
1090
1091
0
  globbed_dirs = make_array(tmp_pool, 0, sizeof(char *));
1092
1093
0
  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
1094
0
    pr_signals_handle();
1095
1096
0
    if (strcmp(dent->d_name, ".") == 0 ||
1097
0
        strcmp(dent->d_name, "..") == 0) {
1098
0
      continue;
1099
0
    }
1100
1101
0
    if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) {
1102
0
      if (is_tmp_file(dent->d_name) == TRUE) {
1103
0
        pr_trace_msg(trace_channel, 19,
1104
0
          "ignoring temporary file '%s' found in directory '%s'", dent->d_name,
1105
0
          parent_path);
1106
0
        continue;
1107
0
      }
1108
0
    }
1109
1110
0
    if (pr_fnmatch(name_pattern, dent->d_name, PR_FNM_PERIOD) == 0) {
1111
0
      pr_trace_msg(trace_channel, 17,
1112
0
        "matched '%s/%s' path with wildcard pattern '%s/%s'", parent_path,
1113
0
        dent->d_name, parent_path, name_pattern);
1114
1115
0
      *((char **) push_array(globbed_dirs)) = pdircat(tmp_pool, parent_path,
1116
0
        dent->d_name, suffix_path, NULL);
1117
0
    }
1118
0
  }
1119
1120
0
  pr_fsio_closedir(dirh);
1121
1122
0
  if (globbed_dirs->nelts == 0) {
1123
0
    pr_log_pri(PR_LOG_WARNING,
1124
0
      "error: no matches found for wildcard directory '%s'", path);
1125
0
    destroy_pool(tmp_pool);
1126
0
    errno = ENOENT;
1127
0
    return -1;
1128
0
  }
1129
1130
0
  depth++;
1131
1132
0
  qsort((void *) globbed_dirs->elts, globbed_dirs->nelts, sizeof(char *),
1133
0
    config_filename_cmp);
1134
1135
0
  for (i = 0; i < globbed_dirs->nelts; i++) {
1136
0
    const char *globbed_dir;
1137
1138
0
    globbed_dir = ((const char **) globbed_dirs->elts)[i];
1139
0
    res = parse_config_path2(p, globbed_dir, depth);
1140
0
    if (res < 0) {
1141
0
      xerrno = errno;
1142
1143
0
      pr_trace_msg(trace_channel, 7, "error parsing wildcard path '%s': %s",
1144
0
        globbed_dir, strerror(xerrno));
1145
1146
0
      destroy_pool(tmp_pool);
1147
0
      errno = xerrno;
1148
0
      return -1;
1149
0
    }
1150
0
  }
1151
1152
0
  destroy_pool(tmp_pool);
1153
0
  return 0;
1154
0
}
1155
1156
0
int parse_config_path2(pool *p, const char *path, unsigned int depth) {
1157
0
  struct stat st;
1158
0
  int have_glob;
1159
0
  void *dirh;
1160
0
  struct dirent *dent;
1161
0
  array_header *file_list;
1162
0
  char *dup_path, *ptr;
1163
0
  pool *tmp_pool;
1164
1165
0
  if (p == NULL ||
1166
0
      path == NULL ||
1167
0
      (depth > PR_PARSER_INCLUDE_MAX_DEPTH)) {
1168
0
    errno = EINVAL;
1169
0
    return -1;
1170
0
  }
1171
1172
0
  if (pr_fs_valid_path(path) < 0) {
1173
0
    errno = EINVAL;
1174
0
    return -1;
1175
0
  }
1176
1177
0
  have_glob = pr_str_is_fnmatch(path);
1178
0
  if (have_glob) {
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
0
    if (*path != '/') {
1184
0
      have_glob = FALSE;
1185
0
    }
1186
0
  }
1187
1188
0
  pr_fs_clear_cache2(path);
1189
1190
0
  if (have_glob) {
1191
0
    pr_trace_msg(trace_channel, 19, "parsing '%s' as a globbed path", path);
1192
0
  }
1193
1194
0
  if (!have_glob &&
1195
0
      pr_fsio_lstat(path, &st) < 0) {
1196
0
    return -1;
1197
0
  }
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
0
  if (have_glob == FALSE &&
1203
0
      (S_ISLNK(st.st_mode) ||
1204
0
       !S_ISDIR(st.st_mode))) {
1205
0
    int res, xerrno;
1206
1207
0
    PRIVS_ROOT
1208
0
    res = pr_parser_parse_file(p, path, NULL, 0);
1209
0
    xerrno = errno;
1210
0
    PRIVS_RELINQUISH
1211
1212
0
    errno = xerrno;
1213
0
    return res;
1214
0
  }
1215
1216
0
  tmp_pool = make_sub_pool(p);
1217
0
  pr_pool_tag(tmp_pool, "Include sub-pool");
1218
1219
  /* Handle the glob/directory. */
1220
0
  dup_path = pstrdup(tmp_pool, path);
1221
1222
0
  ptr = strrchr(dup_path, '/');
1223
1224
0
  if (have_glob) {
1225
0
    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
0
    *ptr = '\0';
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
0
    have_glob_dir = pr_str_is_fnmatch(dup_path);
1237
0
    if (have_glob_dir) {
1238
0
      const char *glob_dir;
1239
1240
0
      if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_WILDCARDS) {
1241
0
        pr_log_pri(PR_LOG_WARNING, "error: wildcard patterns not allowed in "
1242
0
          "configuration directory name '%s'", dup_path);
1243
0
        destroy_pool(tmp_pool);
1244
0
        errno = EINVAL;
1245
0
        return -1;
1246
0
      }
1247
1248
0
      *ptr = '/';
1249
0
      glob_dir = pstrdup(p, dup_path);
1250
0
      destroy_pool(tmp_pool);
1251
1252
0
      return parse_wildcard_config_path(p, glob_dir, depth);
1253
0
    }
1254
1255
0
    ptr++;
1256
1257
    /* Check the directory component. */
1258
0
    pr_fs_clear_cache2(dup_path);
1259
0
    if (pr_fsio_lstat(dup_path, &st) < 0) {
1260
0
      int xerrno = errno;
1261
1262
0
      pr_log_pri(PR_LOG_WARNING,
1263
0
        "error: failed to check configuration path '%s': %s", dup_path,
1264
0
        strerror(xerrno));
1265
1266
0
      destroy_pool(tmp_pool);
1267
0
      errno = xerrno;
1268
0
      return -1;
1269
0
    }
1270
1271
0
    if (S_ISLNK(st.st_mode) &&
1272
0
        !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) {
1273
0
      pr_log_pri(PR_LOG_WARNING,
1274
0
        "error: cannot read configuration path '%s': Symbolic link", path);
1275
0
      destroy_pool(tmp_pool);
1276
0
      errno = ENOTDIR;
1277
0
      return -1;
1278
0
    }
1279
1280
0
    if (have_glob_dir == FALSE &&
1281
0
        pr_str_is_fnmatch(ptr) == FALSE) {
1282
0
      pr_log_pri(PR_LOG_WARNING,
1283
0
        "error: wildcard pattern required for file '%s'", ptr);
1284
0
      destroy_pool(tmp_pool);
1285
0
      errno = EINVAL;
1286
0
      return -1;
1287
0
    }
1288
0
  }
1289
1290
0
  pr_trace_msg(trace_channel, 3, "processing configuration directory '%s'",
1291
0
    dup_path);
1292
1293
0
  dirh = pr_fsio_opendir(dup_path);
1294
0
  if (dirh == NULL) {
1295
0
    pr_log_pri(PR_LOG_WARNING,
1296
0
      "error: unable to open configuration directory '%s': %s", dup_path,
1297
0
      strerror(errno));
1298
0
    destroy_pool(tmp_pool);
1299
0
    errno = EINVAL;
1300
0
    return -1;
1301
0
  }
1302
1303
0
  file_list = make_array(tmp_pool, 0, sizeof(char *));
1304
1305
0
  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
1306
0
    pr_signals_handle();
1307
1308
0
    if (strcmp(dent->d_name, ".") == 0 ||
1309
0
        strcmp(dent->d_name, "..") == 0) {
1310
0
      continue;
1311
0
    }
1312
1313
0
    if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) {
1314
0
      if (is_tmp_file(dent->d_name) == TRUE) {
1315
0
        pr_trace_msg(trace_channel, 19,
1316
0
          "ignoring temporary file '%s' found in directory '%s'", dent->d_name,
1317
0
          dup_path);
1318
0
        continue;
1319
0
      }
1320
0
    }
1321
1322
0
    if (have_glob == FALSE ||
1323
0
        (ptr != NULL &&
1324
0
         pr_fnmatch(ptr, dent->d_name, PR_FNM_PERIOD) == 0)) {
1325
0
      *((char **) push_array(file_list)) = pdircat(tmp_pool, dup_path,
1326
0
        dent->d_name, NULL);
1327
0
    }
1328
0
  }
1329
1330
0
  pr_fsio_closedir(dirh);
1331
1332
0
  if (file_list->nelts) {
1333
0
    register unsigned int i;
1334
1335
0
    qsort((void *) file_list->elts, file_list->nelts, sizeof(char *),
1336
0
      config_filename_cmp);
1337
1338
0
    for (i = 0; i < file_list->nelts; i++) {
1339
0
      int res, xerrno;
1340
0
      char *file;
1341
1342
0
      file = ((char **) file_list->elts)[i];
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
0
      PRIVS_ROOT
1350
0
      res = pr_parser_parse_file(tmp_pool, file, NULL, 0);
1351
0
      xerrno = errno;
1352
0
      PRIVS_RELINQUISH
1353
1354
0
      if (res < 0) {
1355
0
        pr_log_pri(PR_LOG_WARNING,
1356
0
          "error: unable to parse file '%s': %s", file, strerror(xerrno));
1357
0
        pr_log_pri(PR_LOG_WARNING, "%s",
1358
0
          "error: check `proftpd --configtest -d10` for details");
1359
1360
0
        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
0
        errno = EINVAL;
1367
0
        return -1;
1368
0
      }
1369
0
    }
1370
0
  }
1371
1372
0
  destroy_pool(tmp_pool);
1373
0
  return 0;
1374
0
}
1375
1376
0
int parse_config_path(pool *p, const char *path) {
1377
0
  return parse_config_path2(p, path, 0);
1378
0
}