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