/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 | } |