1 /*
2  * ProFTPD - FTP server daemon
3  * Copyright (c) 1997, 1998 Public Flood Software
4  * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
5  * Copyright (c) 2001-2020 The ProFTPD Project team
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20  *
21  * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
22  * and other respective copyright holders give permission to link this program
23  * with OpenSSL, and distribute the resulting executable, without including
24  * the source code for OpenSSL in the source distribution.
25  */
26 
27 /* Core FTPD module */
28 
29 #include "conf.h"
30 #include "privs.h"
31 #include "error.h"
32 
33 #include <ctype.h>
34 
35 extern module *loaded_modules;
36 extern module site_module;
37 extern xaset_t *server_list;
38 
39 /* From src/main.c */
40 extern unsigned long max_connects;
41 extern unsigned int max_connect_interval;
42 
43 /* From modules/mod_site.c */
44 extern modret_t *site_dispatch(cmd_rec*);
45 
46 /* For bytes-retrieving directives */
47 #define PR_BYTES_BAD_UNITS	-1
48 #define PR_BYTES_BAD_FORMAT	-2
49 
50 /* Maximum number of parameters for OPTS commands (see Bug#3870). */
51 #define PR_OPTS_MAX_PARAM_COUNT		8
52 
53 module core_module;
54 char AddressCollisionCheck = TRUE;
55 
56 static int core_scrub_timer_id = -1;
57 static pr_fh_t *displayquit_fh = NULL;
58 
59 #ifdef PR_USE_TRACE
60 static const char *trace_log = NULL;
61 #endif /* PR_USE_TRACE */
62 
63 /* Necessary prototypes. */
64 static void core_exit_ev(const void *, void *);
65 static int core_sess_init(void);
66 static void reset_server_auth_order(void);
67 
68 /* These are for handling any configured MaxCommandRate. */
69 static unsigned long core_cmd_count = 0UL;
70 static unsigned long core_max_cmds = 0UL;
71 static unsigned int core_max_cmd_interval = 1;
72 static time_t core_max_cmd_ts = 0;
73 
core_exceeded_cmd_rate(cmd_rec * cmd)74 static unsigned long core_exceeded_cmd_rate(cmd_rec *cmd) {
75   unsigned long res = 0;
76   long over = 0;
77   time_t now;
78 
79   if (core_max_cmds == 0) {
80     return 0;
81   }
82 
83   core_cmd_count++;
84 
85   over = core_cmd_count - core_max_cmds;
86   if (over > 0) {
87     /* Determine the delay, in ms.
88      *
89      * The value for this delay must be a value which will cause the command
90      * rate to not be exceeded.  For example, if the config is:
91      *
92      *  MaxCommandRate 200 1
93      *
94      * it means a maximum of 200 commands a sec.  This works out to a command
95      * every 5 ms, maximum:
96      *
97      *  1 sec * 1000 ms / 200 cmds per sec = 5 ms
98      *
99      * That 5 ms, then, would be the delay to use. For each command over the
100      * maximum number of commands per interval, we add this delay factor.
101      * This means that the more over the limit the session is, the longer the
102      * delay.
103      */
104 
105     res = (unsigned long) (((core_max_cmd_interval * 1000) / core_max_cmds) * over);
106   }
107 
108   now = time(NULL);
109   if (core_max_cmd_ts > 0) {
110     if ((now - core_max_cmd_ts) > core_max_cmd_interval) {
111       /* If it's been longer than the MaxCommandRate interval, reset the
112        * command counter.
113        */
114       core_cmd_count = 0;
115       core_max_cmd_ts = now;
116     }
117 
118   } else {
119     core_max_cmd_ts = now;
120   }
121 
122   return res;
123 }
124 
core_idle_timeout_cb(CALLBACK_FRAME)125 static int core_idle_timeout_cb(CALLBACK_FRAME) {
126   int timeout;
127 
128   timeout = pr_data_get_timeout(PR_DATA_TIMEOUT_IDLE);
129 
130   /* We don't want to quit in the middle of a transfer */
131   if (session.sf_flags & SF_XFER) {
132     pr_trace_msg("timer", 4,
133       "TimeoutIdle (%d %s) reached, but data transfer in progress, ignoring",
134       timeout, timeout != 1 ? "seconds" : "second");
135 
136     /* Restart the timer. */
137     return 1;
138   }
139 
140   pr_event_generate("core.timeout-idle", NULL);
141 
142   pr_response_send_async(R_421,
143     _("Idle timeout (%d seconds): closing control connection"), timeout);
144 
145   pr_timer_remove(PR_TIMER_LOGIN, ANY_MODULE);
146   pr_timer_remove(PR_TIMER_NOXFER, ANY_MODULE);
147 
148   pr_log_pri(PR_LOG_INFO, "%s", "Client session idle timeout, disconnected");
149   pr_session_disconnect(&core_module, PR_SESS_DISCONNECT_TIMEOUT,
150     "TimeoutIdle");
151   return 0;
152 }
153 
154 /* If the environment variable being set/unset is locale-related, then we need
155  * to call setlocale(3) again.
156  *
157  * Note: We deliberately set LC_NUMERIC to "C", regardless of configuration.
158  * Failure to do so will cause problems with formatting of e.g. floats in
159  * SQL query strings.
160  */
core_handle_locale_env(const char * env_name)161 static void core_handle_locale_env(const char *env_name) {
162 #if defined(PR_USE_NLS) && defined(HAVE_LOCALE_H)
163   register unsigned int i;
164   const char *locale_envs[] = {
165     "LC_ALL",
166     "LC_COLLATE",
167     "LC_CTYPE",
168     "LC_MESSAGES",
169     "LC_MONETARY",
170     "LC_NUMERIC",
171     "LC_TIME",
172     "LANG",
173     NULL
174   };
175 
176   for (i = 0; locale_envs[i] != NULL; i++) {
177     if (strcmp(env_name, locale_envs[i]) == 0) {
178       if (setlocale(LC_ALL, "") != NULL) {
179         setlocale(LC_NUMERIC, "C");
180       }
181     }
182   }
183 #endif /* PR_USE_NLS and HAVE_LOCALE_H */
184 }
185 
core_scrub_scoreboard_cb(CALLBACK_FRAME)186 static int core_scrub_scoreboard_cb(CALLBACK_FRAME) {
187   /* Always return 1 when leaving this function, to make sure the timer
188    * gets called again.
189    */
190   pr_scoreboard_scrub();
191 
192   return 1;
193 }
194 
start_ifdefine(cmd_rec * cmd)195 MODRET start_ifdefine(cmd_rec *cmd) {
196   unsigned int ifdefine_ctx_count = 1;
197   unsigned char not_define = FALSE, defined = FALSE;
198   char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}, *config_line = NULL, *ptr;
199 
200   CHECK_ARGS(cmd, 1);
201 
202   ptr = cmd->argv[1];
203   if (*ptr == '!') {
204     not_define = TRUE;
205     ptr++;
206   }
207 
208   defined = pr_define_exists(ptr);
209 
210   /* Return now if we don't need to consume the <IfDefine> section
211    * configuration lines.
212    */
213   if ((!not_define && defined) ||
214       (not_define && !defined)) {
215     pr_log_debug(DEBUG3, "%s: using '%s%s' section at line %u",
216       (char *) cmd->argv[0], not_define ? "!" : "", (char *) cmd->argv[1],
217       pr_parser_get_lineno());
218     return PR_HANDLED(cmd);
219   }
220 
221   pr_log_debug(DEBUG3, "%s: skipping '%s%s' section at line %u",
222     (char *) cmd->argv[0], not_define ? "!" : "", (char *) cmd->argv[1],
223     pr_parser_get_lineno());
224 
225   /* Rather than communicating with parse_config_file() via some global
226    * variable/flag the need to skip configuration lines, if the requested
227    * module condition is not TRUE, read in the lines here (effectively
228    * preventing them from being parsed) up to and including the closing
229    * directive.
230    */
231   while (ifdefine_ctx_count && (config_line = pr_parser_read_line(buf,
232       sizeof(buf))) != NULL) {
233 
234     if (strncasecmp(config_line, "<IfDefine", 9) == 0) {
235       ifdefine_ctx_count++;
236 
237     } else if (strcasecmp(config_line, "</IfDefine>") == 0) {
238       ifdefine_ctx_count--;
239     }
240   }
241 
242   /* If there are still unclosed <IfDefine> sections, signal an error.
243    */
244   if (ifdefine_ctx_count) {
245     CONF_ERROR(cmd, "unclosed <IfDefine> context");
246   }
247 
248   return PR_HANDLED(cmd);
249 }
250 
251 /* As with Apache, there is no way of cleanly checking whether an
252  * <IfDefine> section is properly closed.  Extra </IfDefine> directives
253  * will be silently ignored.
254  */
end_ifdefine(cmd_rec * cmd)255 MODRET end_ifdefine(cmd_rec *cmd) {
256   return PR_HANDLED(cmd);
257 }
258 
start_ifmodule(cmd_rec * cmd)259 MODRET start_ifmodule(cmd_rec *cmd) {
260   unsigned int ifmodule_ctx_count = 1;
261   unsigned char not_module = FALSE, found_module = FALSE;
262   char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}, *config_line = NULL, *ptr;
263 
264   CHECK_ARGS(cmd, 1);
265 
266   ptr = cmd->argv[1];
267   if (*ptr == '!') {
268     not_module = TRUE;
269     ptr++;
270   }
271 
272   found_module = pr_module_exists(ptr);
273 
274   /* Return now if we don't need to consume the <IfModule> section
275    * configuration lines.
276    */
277   if ((!not_module && found_module) ||
278       (not_module && !found_module)) {
279     pr_log_debug(DEBUG3, "%s: using '%s%s' section at line %u",
280       (char *) cmd->argv[0], not_module ? "!" : "", (char *) cmd->argv[1],
281       pr_parser_get_lineno());
282     return PR_HANDLED(cmd);
283   }
284 
285   pr_log_debug(DEBUG3, "%s: skipping '%s%s' section at line %u",
286     (char *) cmd->argv[0], not_module ? "!" : "", (char *) cmd->argv[1],
287     pr_parser_get_lineno());
288 
289   /* Rather than communicating with parse_config_file() via some global
290    * variable/flag the need to skip configuration lines, if the requested
291    * module condition is not TRUE, read in the lines here (effectively
292    * preventing them from being parsed) up to and including the closing
293    * directive.
294    */
295   while (ifmodule_ctx_count && (config_line = pr_parser_read_line(buf,
296       sizeof(buf))) != NULL) {
297     char *bufp;
298 
299     pr_signals_handle();
300 
301     /* Advance past any leading whitespace. */
302     for (bufp = config_line; *bufp && PR_ISSPACE(*bufp); bufp++);
303 
304     if (strncasecmp(bufp, "<IfModule", 9) == 0) {
305       ifmodule_ctx_count++;
306 
307     } else if (strcasecmp(bufp, "</IfModule>") == 0) {
308       ifmodule_ctx_count--;
309     }
310   }
311 
312   /* If there are still unclosed <IfModule> sections, signal an error. */
313   if (ifmodule_ctx_count) {
314     CONF_ERROR(cmd, "unclosed <IfModule> context");
315   }
316 
317   return PR_HANDLED(cmd);
318 }
319 
320 /* As with Apache, there is no way of cleanly checking whether an
321  * <IfModule> section is properly closed.  Extra </IfModule> directives
322  * will be silently ignored.
323  */
end_ifmodule(cmd_rec * cmd)324 MODRET end_ifmodule(cmd_rec *cmd) {
325   return PR_HANDLED(cmd);
326 }
327 
328 /* Syntax: Define parameter
329  *
330  * Configuration file equivalent of the -D command-line option for
331  * specifying an <IfDefine> value.
332  *
333  * It is suggested the RLimitMemory (a good idea to use anyway) be
334  * used if this directive is present, to prevent Defines was being
335  * used by a malicious local user in a .ftpaccess file.
336  */
set_define(cmd_rec * cmd)337 MODRET set_define(cmd_rec *cmd) {
338 
339   /* Make sure there's at least one parameter; any others are ignored */
340   CHECK_ARGS(cmd, 1);
341 
342   /* This directive can occur in any context, so no need for the
343    * CHECK_CONF macro.
344    */
345 
346   pr_define_add(cmd->argv[1], FALSE);
347   return PR_HANDLED(cmd);
348 }
349 
350 /* usage: Include path|pattern */
set_include(cmd_rec * cmd)351 MODRET set_include(cmd_rec *cmd) {
352   int allowed_ctxs, parent_ctx, res, xerrno;
353 
354   CHECK_ARGS(cmd, 1);
355 
356   /* If we are not currently in a .ftpaccess context, then we allow Include
357    * in a <Limit> section.  Otherwise, a .ftpaccess file could contain a
358    * <Limit>, and that <Limit> could include e.g. itself, leading to a loop.
359    */
360 
361   allowed_ctxs = CONF_ROOT|CONF_VIRTUAL|CONF_ANON|CONF_GLOBAL|CONF_DIR;
362 
363   parent_ctx = CONF_ROOT;
364   if (cmd->config != NULL &&
365       cmd->config->parent != NULL) {
366     parent_ctx = cmd->config->parent->config_type;
367   }
368 
369   if (parent_ctx != CONF_DYNDIR) {
370     allowed_ctxs |= CONF_LIMIT;
371   }
372 
373   CHECK_CONF(cmd, allowed_ctxs);
374 
375   /* Make sure the given path is a valid path. */
376 
377   PRIVS_ROOT
378   res = pr_fs_valid_path(cmd->argv[1]);
379   PRIVS_RELINQUISH
380 
381   if (res < 0) {
382     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
383       "unable to use path for configuration file '", cmd->argv[1], "'", NULL));
384   }
385 
386   PRIVS_ROOT
387   res = parse_config_path(cmd->tmp_pool, cmd->argv[1]);
388   xerrno = errno;
389   PRIVS_RELINQUISH
390 
391   if (res < 0) {
392     if (xerrno != EINVAL) {
393       pr_log_pri(PR_LOG_WARNING, "warning: unable to include '%s': %s",
394         (char *) cmd->argv[1], strerror(xerrno));
395 
396     } else {
397       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error including '",
398         (char *) cmd->argv[1], "': ", strerror(xerrno), NULL));
399     }
400   }
401 
402   return PR_HANDLED(cmd);
403 }
404 
405 /* usage: IncludeOptions opt1 ... */
set_includeoptions(cmd_rec * cmd)406 MODRET set_includeoptions(cmd_rec *cmd) {
407   register unsigned int i;
408   unsigned long opts = 0UL;
409 
410   if (cmd->argc-1 == 0) {
411     CONF_ERROR(cmd, "wrong number of parameters");
412   }
413 
414   CHECK_CONF(cmd, CONF_ROOT);
415 
416   for (i = 1; i < cmd->argc; i++) {
417     if (strcmp(cmd->argv[i], "AllowSymlinks") == 0) {
418       opts |= PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS;
419 
420     } else if (strcmp(cmd->argv[i], "IgnoreTempFiles") == 0) {
421       opts |= PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES;
422 
423     } else if (strcmp(cmd->argv[i], "IgnoreWildcards") == 0) {
424       opts |= PR_PARSER_INCLUDE_OPT_IGNORE_WILDCARDS;
425 
426     } else {
427       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown IncludeOption '",
428         cmd->argv[i], "'", NULL));
429     }
430   }
431 
432   (void) pr_parser_set_include_opts(opts);
433   return PR_HANDLED(cmd);
434 }
435 
set_debuglevel(cmd_rec * cmd)436 MODRET set_debuglevel(cmd_rec *cmd) {
437   config_rec *c = NULL;
438   int debuglevel = -1;
439   char *endp = NULL;
440 
441   CHECK_ARGS(cmd, 1);
442   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
443 
444   /* Make sure the parameter is a valid number. */
445   debuglevel = strtol(cmd->argv[1], &endp, 10);
446 
447   if (endp && *endp)
448     CONF_ERROR(cmd, "not a valid number");
449 
450   /* Make sure the number is within the valid debug level range. */
451   if (debuglevel < 0 || debuglevel > 10)
452     CONF_ERROR(cmd, "invalid debug level configured");
453 
454   c = add_config_param(cmd->argv[0], 1, NULL);
455   c->argv[0] = pcalloc(c->pool, sizeof(int));
456   *((int *) c->argv[0]) = debuglevel;
457 
458   return PR_HANDLED(cmd);
459 }
460 
set_defaultaddress(cmd_rec * cmd)461 MODRET set_defaultaddress(cmd_rec *cmd) {
462   const char *name, *main_ipstr;
463   const pr_netaddr_t *main_addr = NULL;
464   array_header *addrs = NULL;
465   unsigned int addr_flags = PR_NETADDR_GET_ADDR_FL_INCL_DEVICE;
466 
467   if (cmd->argc-1 < 1) {
468     CONF_ERROR(cmd, "wrong number of parameters");
469   }
470 
471   CHECK_CONF(cmd, CONF_ROOT);
472 
473   name = cmd->argv[1];
474   main_addr = pr_netaddr_get_addr2(main_server->pool, name, &addrs, addr_flags);
475   if (main_addr == NULL) {
476     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to resolve '", name, "'",
477       NULL));
478   }
479 
480   /* If the given name is a DNS name, automatically add a ServerAlias
481    * directive.
482    */
483   if (pr_netaddr_is_v4(name) == FALSE &&
484       pr_netaddr_is_v6(name) == FALSE) {
485     add_config_param_str("ServerAlias", 1, name);
486   }
487 
488   main_server->ServerAddress = main_ipstr = pr_netaddr_get_ipstr(main_addr);
489   main_server->addr = main_addr;
490 
491   if (addrs != NULL) {
492     register unsigned int i;
493     pr_netaddr_t **elts = addrs->elts;
494 
495     /* For every additional address, implicitly add a bind record. */
496     for (i = 0; i < addrs->nelts; i++) {
497       const char *ipstr;
498 
499       ipstr = pr_netaddr_get_ipstr(elts[i]);
500 
501       /* Skip duplicate addresses. */
502       if (strcmp(main_ipstr, ipstr) == 0) {
503         continue;
504       }
505 
506 #ifdef PR_USE_IPV6
507       if (pr_netaddr_use_ipv6()) {
508         char *ipbuf;
509 
510         ipbuf = pcalloc(cmd->tmp_pool, INET6_ADDRSTRLEN + 1);
511         if (pr_netaddr_get_family(elts[i]) == AF_INET) {
512           /* Create the bind record using the IPv4-mapped IPv6 version of
513            * this address.
514            */
515           pr_snprintf(ipbuf, INET6_ADDRSTRLEN, "::ffff:%s", ipstr);
516           ipstr = ipbuf;
517         }
518       }
519 #endif /* PR_USE_IPV6 */
520 
521       add_config_param_str("_bind_", 1, ipstr);
522     }
523   }
524 
525   /* Handle multiple addresses in a DefaultAddress directive.  We do
526    * this by adding bind directives to the server_rec created for the
527    * first address.
528    */
529   if (cmd->argc-1 > 1) {
530     register unsigned int i;
531     char *addrs_str = (char *) pr_netaddr_get_ipstr(main_addr);
532 
533     for (i = 2; i < cmd->argc; i++) {
534       const char *addr_ipstr;
535       const pr_netaddr_t *addr;
536       addrs = NULL;
537 
538       addr = pr_netaddr_get_addr2(cmd->tmp_pool, cmd->argv[i], &addrs,
539         addr_flags);
540       if (addr == NULL) {
541         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error resolving '",
542           cmd->argv[i], "': ", strerror(errno), NULL));
543       }
544 
545       addr_ipstr = pr_netaddr_get_ipstr(addr);
546       add_config_param_str("_bind_", 1, addr_ipstr);
547 
548       /* If the given name is a DNS name, automatically add a ServerAlias
549        * directive.
550        */
551       if (pr_netaddr_is_v4(cmd->argv[i]) == FALSE &&
552           pr_netaddr_is_v6(cmd->argv[i]) == FALSE) {
553         add_config_param_str("ServerAlias", 1, cmd->argv[i]);
554       }
555 
556       addrs_str = pstrcat(cmd->tmp_pool, addrs_str, ", ", addr_ipstr, NULL);
557 
558       if (addrs != NULL) {
559         register unsigned int j;
560         pr_netaddr_t **elts = addrs->elts;
561 
562         /* For every additional address, implicitly add a bind record. */
563         for (j = 0; j < addrs->nelts; j++) {
564           const char *ipstr;
565 
566           ipstr = pr_netaddr_get_ipstr(elts[j]);
567 
568           /* Skip duplicate addresses. */
569           if (strcmp(addr_ipstr, ipstr) == 0) {
570             continue;
571           }
572 
573           add_config_param_str("_bind_", 1, ipstr);
574         }
575       }
576     }
577 
578     pr_log_debug(DEBUG3, "setting default addresses to %s", addrs_str);
579 
580   } else {
581     pr_log_debug(DEBUG3, "setting default address to %s", main_ipstr);
582   }
583 
584   return PR_HANDLED(cmd);
585 }
586 
set_servername(cmd_rec * cmd)587 MODRET set_servername(cmd_rec *cmd) {
588   server_rec *s = cmd->server;
589 
590   CHECK_ARGS(cmd, 1);
591   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
592 
593   s->ServerName = pstrdup(s->pool,cmd->argv[1]);
594   return PR_HANDLED(cmd);
595 }
596 
set_servertype(cmd_rec * cmd)597 MODRET set_servertype(cmd_rec *cmd) {
598   CHECK_ARGS(cmd, 1);
599   CHECK_CONF(cmd, CONF_ROOT);
600 
601   if (strcasecmp(cmd->argv[1], "inetd") == 0)
602     ServerType = SERVER_INETD;
603 
604   else if (strcasecmp(cmd->argv[1], "standalone") == 0)
605     ServerType = SERVER_STANDALONE;
606 
607   else
608     CONF_ERROR(cmd,"type must be either 'inetd' or 'standalone'");
609 
610   return PR_HANDLED(cmd);
611 }
612 
set_setenv(cmd_rec * cmd)613 MODRET set_setenv(cmd_rec *cmd) {
614   int ctxt_type;
615 
616   CHECK_ARGS(cmd, 2);
617   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
618 
619   add_config_param_str(cmd->argv[0], 2, cmd->argv[1], cmd->argv[2]);
620 
621   /* In addition, if this is the "server config" context, set the
622    * environment variable now.  If there was a <Daemon> context, that would
623    * be a more appropriate place for configuring parse-time environ
624    * variables.
625    */
626   ctxt_type = (cmd->config && cmd->config->config_type != CONF_PARAM ?
627      cmd->config->config_type : cmd->server->config_type ?
628      cmd->server->config_type : CONF_ROOT);
629 
630   if (ctxt_type == CONF_ROOT) {
631     if (pr_env_set(cmd->server->pool, cmd->argv[1], cmd->argv[2]) < 0) {
632       pr_log_debug(DEBUG1, "%s: unable to set environment variable '%s': %s",
633         (char *) cmd->argv[0], (char *) cmd->argv[1], strerror(errno));
634 
635     } else {
636       core_handle_locale_env(cmd->argv[1]);
637     }
638   }
639 
640   return PR_HANDLED(cmd);
641 }
642 
add_transferlog(cmd_rec * cmd)643 MODRET add_transferlog(cmd_rec *cmd) {
644   config_rec *c = NULL;
645 
646   CHECK_ARGS(cmd, 1);
647   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
648 
649   c = add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
650   c->flags |= CF_MERGEDOWN;
651 
652   return PR_HANDLED(cmd);
653 }
654 
set_serveradmin(cmd_rec * cmd)655 MODRET set_serveradmin(cmd_rec *cmd) {
656   server_rec *s = cmd->server;
657 
658   CHECK_ARGS(cmd, 1);
659   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
660 
661   s->ServerAdmin = pstrdup(s->pool, cmd->argv[1]);
662   return PR_HANDLED(cmd);
663 }
664 
665 /* usage: UseIPv6 on|off */
set_useipv6(cmd_rec * cmd)666 MODRET set_useipv6(cmd_rec *cmd) {
667 #ifdef PR_USE_IPV6
668   int bool = -1;
669 
670   CHECK_ARGS(cmd, 1);
671   CHECK_CONF(cmd, CONF_ROOT);
672 
673   bool = get_boolean(cmd, 1);
674   if (bool == -1)
675     CONF_ERROR(cmd, "expected Boolean parameter");
676 
677   if (bool == 0) {
678     pr_log_debug(DEBUG2, "disabling runtime support for IPv6 connections");
679     pr_netaddr_disable_ipv6();
680 
681   } else {
682     pr_netaddr_enable_ipv6();
683   }
684 
685   return PR_HANDLED(cmd);
686 #else
687   CONF_ERROR(cmd,
688     "Use of the UseIPv6 directive requires IPv6 support (--enable-ipv6)");
689 #endif /* PR_USE_IPV6 */
690 }
691 
set_usereversedns(cmd_rec * cmd)692 MODRET set_usereversedns(cmd_rec *cmd) {
693   int bool = -1;
694 
695   CHECK_ARGS(cmd, 1);
696   CHECK_CONF(cmd, CONF_ROOT);
697 
698   bool = get_boolean(cmd, 1);
699   if (bool == -1)
700     CONF_ERROR(cmd, "expected Boolean parameter");
701 
702   ServerUseReverseDNS = bool;
703   pr_netaddr_set_reverse_dns(bool);
704 
705   return PR_HANDLED(cmd);
706 }
707 
set_satisfy(cmd_rec * cmd)708 MODRET set_satisfy(cmd_rec *cmd) {
709   int satisfy = -1;
710 
711   CHECK_ARGS(cmd, 1);
712   CHECK_CONF(cmd, CONF_CLASS);
713 
714   if (strcasecmp(cmd->argv[1], "any") == 0) {
715     satisfy = PR_CLASS_SATISFY_ANY;
716 
717   } else if (strcasecmp(cmd->argv[1], "all") == 0) {
718     satisfy = PR_CLASS_SATISFY_ALL;
719 
720   } else {
721     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "invalid parameter: '",
722       cmd->argv[1], "'", NULL));
723   }
724 
725   if (pr_class_set_satisfy(satisfy) < 0) {
726     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error setting Satisfy: ",
727       strerror(errno), NULL));
728   }
729 
730   return PR_HANDLED(cmd);
731 }
732 
733 /* usage: ScoreboardFile path */
set_scoreboardfile(cmd_rec * cmd)734 MODRET set_scoreboardfile(cmd_rec *cmd) {
735   CHECK_ARGS(cmd, 1);
736   CHECK_CONF(cmd, CONF_ROOT);
737 
738   if (pr_set_scoreboard(cmd->argv[1]) < 0) {
739     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", cmd->argv[1],
740       "': ", strerror(errno), NULL));
741   }
742 
743   return PR_HANDLED(cmd);
744 }
745 
746 /* usage: ScoreboardMutex path */
set_scoreboardmutex(cmd_rec * cmd)747 MODRET set_scoreboardmutex(cmd_rec *cmd) {
748   CHECK_ARGS(cmd, 1);
749   CHECK_CONF(cmd, CONF_ROOT);
750 
751   if (pr_set_scoreboard_mutex(cmd->argv[1]) < 0) {
752     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", cmd->argv[1],
753       "': ", strerror(errno), NULL));
754   }
755 
756   return PR_HANDLED(cmd);
757 }
758 
759 /* usage: ScoreboardScrub "on"|"off"|secs */
set_scoreboardscrub(cmd_rec * cmd)760 MODRET set_scoreboardscrub(cmd_rec *cmd) {
761   int bool = -1, nsecs = 0;
762   config_rec *c;
763 
764   CHECK_ARGS(cmd, 1);
765   CHECK_CONF(cmd, CONF_ROOT);
766 
767   bool = get_boolean(cmd, 1);
768   if (bool == -1) {
769     /* If this is the case, try handling the parameter as the number of
770      * seconds, as the scrub frequency.
771      */
772     nsecs = atoi(cmd->argv[1]);
773     if (nsecs <= 0) {
774       CONF_ERROR(cmd, "number must be greater than zero");
775     }
776   }
777 
778   if (nsecs > 0) {
779     c = add_config_param(cmd->argv[0], 2, NULL, NULL);
780     c->argv[0] = pcalloc(c->pool, sizeof(int));
781     *((int *) c->argv[0]) = TRUE;
782     c->argv[1] = pcalloc(c->pool, sizeof(int));
783     *((int *) c->argv[1]) = nsecs;
784 
785   } else {
786     c = add_config_param(cmd->argv[0], 1, NULL);
787     c->argv[0] = pcalloc(c->pool, sizeof(int));
788     *((int *) c->argv[0]) = bool;
789   }
790 
791   return PR_HANDLED(cmd);
792 }
793 
set_serverport(cmd_rec * cmd)794 MODRET set_serverport(cmd_rec *cmd) {
795   server_rec *s = cmd->server;
796   int port;
797 
798   CHECK_ARGS(cmd, 1);
799   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
800 
801   port = atoi(cmd->argv[1]);
802   if (port < 0 ||
803       port > 65535) {
804     CONF_ERROR(cmd, "value must be between 0 and 65535");
805   }
806 
807   s->ServerPort = port;
808   return PR_HANDLED(cmd);
809 }
810 
set_pidfile(cmd_rec * cmd)811 MODRET set_pidfile(cmd_rec *cmd) {
812   CHECK_ARGS(cmd, 1);
813   CHECK_CONF(cmd, CONF_ROOT);
814 
815   if (pr_pidfile_set(cmd->argv[1]) < 0) {
816     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to set PidFile '",
817       cmd->argv[1], "': ", strerror(errno), NULL));
818   }
819 
820   return PR_HANDLED(cmd);
821 }
822 
set_sysloglevel(cmd_rec * cmd)823 MODRET set_sysloglevel(cmd_rec *cmd) {
824   config_rec *c = NULL;
825   int level = 0;
826 
827   CHECK_ARGS(cmd, 1);
828   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
829 
830   level = pr_log_str2sysloglevel(cmd->argv[1]);
831   if (level < 0) {
832     CONF_ERROR(cmd, "SyslogLevel requires level keyword: one of "
833       "emerg/alert/crit/error/warn/notice/info/debug");
834   }
835 
836   c = add_config_param(cmd->argv[0], 1, NULL);
837   c->argv[0] = pcalloc(c->pool, sizeof(int));
838   *((int *) c->argv[0]) = level;
839 
840   return PR_HANDLED(cmd);
841 }
842 
843 /* usage: ServerAlias hostname [hostname ...] */
set_serveralias(cmd_rec * cmd)844 MODRET set_serveralias(cmd_rec *cmd) {
845   register unsigned int i;
846 
847   if (cmd->argc < 2) {
848     CONF_ERROR(cmd, "wrong number of parameters");
849   }
850 
851   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
852 
853   for (i = 1; i < cmd->argc; i++) {
854     add_config_param_str(cmd->argv[0], 1, cmd->argv[i]);
855   }
856 
857   return PR_HANDLED(cmd);
858 }
859 
860 /* usage: ServerIdent off|on [name] */
set_serverident(cmd_rec * cmd)861 MODRET set_serverident(cmd_rec *cmd) {
862   int ident_on = -1;
863   config_rec *c = NULL;
864 
865   if (cmd->argc < 2 ||
866       cmd->argc > 3) {
867     CONF_ERROR(cmd, "wrong number of parameters");
868   }
869 
870   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
871 
872   ident_on = get_boolean(cmd, 1);
873   if (ident_on == -1) {
874     CONF_ERROR(cmd, "expected Boolean parameter");
875   }
876 
877   if (ident_on == TRUE &&
878       cmd->argc == 3) {
879     c = add_config_param(cmd->argv[0], 2, NULL, NULL);
880     c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
881     *((unsigned char *) c->argv[0]) = ident_on;
882     c->argv[1] = pstrdup(c->pool, cmd->argv[2]);
883 
884   } else {
885     c = add_config_param(cmd->argv[0], 1, NULL);
886     c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
887     *((unsigned char *) c->argv[0]) = ident_on;
888   }
889 
890   return PR_HANDLED(cmd);
891 }
892 
set_defaultserver(cmd_rec * cmd)893 MODRET set_defaultserver(cmd_rec *cmd) {
894   int bool = -1;
895   server_rec *s = NULL;
896   config_rec *c = NULL;
897 
898   CHECK_ARGS(cmd, 1);
899   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
900 
901   bool = get_boolean(cmd, 1);
902   if (bool == -1) {
903     CONF_ERROR(cmd, "expected Boolean parameter");
904   }
905 
906   if (!bool) {
907     return PR_HANDLED(cmd);
908   }
909 
910   /* DefaultServer is not allowed if already set somewhere */
911   for (s = (server_rec *) server_list->xas_list; s; s = s->next) {
912     if (find_config(s->conf, CONF_PARAM, cmd->argv[0], FALSE)) {
913       CONF_ERROR(cmd, "DefaultServer has already been set");
914     }
915   }
916 
917   c = add_config_param(cmd->argv[0], 1, NULL);
918   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
919   *((unsigned char *) c->argv[0]) = bool;
920 
921   return PR_HANDLED(cmd);
922 }
923 
set_masqueradeaddress(cmd_rec * cmd)924 MODRET set_masqueradeaddress(cmd_rec *cmd) {
925   config_rec *c = NULL;
926   const char *name;
927   size_t namelen;
928   const pr_netaddr_t *masq_addr = NULL;
929   unsigned int addr_flags = PR_NETADDR_GET_ADDR_FL_INCL_DEVICE;
930 
931   CHECK_ARGS(cmd, 1);
932   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
933 
934   /* We can only masquerade as one address, so we don't need to know if the
935    * given name might map to multiple addresses.
936    */
937   name = cmd->argv[1];
938   namelen = strlen(name);
939   if (namelen == 0) {
940     /* Guard against empty names here. */
941     CONF_ERROR(cmd, "missing required name parameter");
942   }
943 
944   masq_addr = pr_netaddr_get_addr2(cmd->server->pool, name, NULL, addr_flags);
945   if (masq_addr == NULL) {
946     /* If the requested name cannot be resolved because it is not known AT
947      * THIS TIME, then do not fail to start the server.  We will simply try
948      * again later (Bug#4104).
949      */
950     if (errno != ENOENT) {
951       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to resolve '", name, "'",
952         NULL));
953     }
954   }
955 
956   c = add_config_param(cmd->argv[0], 2, (void *) masq_addr, NULL);
957   c->argv[1] = pstrdup(c->pool, cmd->argv[1]);
958 
959   return PR_HANDLED(cmd);
960 }
961 
set_maxinstances(cmd_rec * cmd)962 MODRET set_maxinstances(cmd_rec *cmd) {
963   long max_instances;
964   char *endp;
965 
966   CHECK_ARGS(cmd, 1);
967   CHECK_CONF(cmd, CONF_ROOT);
968 
969   if (strcasecmp(cmd->argv[1], "none") == 0) {
970     max_instances = 0UL;
971 
972   } else {
973     max_instances = strtol(cmd->argv[1], &endp, 10);
974 
975     if ((endp && *endp) ||
976         max_instances < 1) {
977       CONF_ERROR(cmd, "argument must be 'none' or a number greater than 0");
978     }
979   }
980 
981   ServerMaxInstances = max_instances;
982   return PR_HANDLED(cmd);
983 }
984 
985 /* usage: MaxCommandRate rate [interval] */
set_maxcommandrate(cmd_rec * cmd)986 MODRET set_maxcommandrate(cmd_rec *cmd) {
987   config_rec *c;
988   long cmd_max = 0L;
989   unsigned int max_cmd_interval = 1;
990   char *endp = NULL;
991 
992   if (cmd->argc-1 < 1 ||
993       cmd->argc-1 > 2) {
994     CONF_ERROR(cmd, "wrong number of parameters");
995   }
996 
997   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
998 
999   cmd_max = strtol(cmd->argv[1], &endp, 10);
1000 
1001   if (endp && *endp) {
1002     CONF_ERROR(cmd, "invalid command rate");
1003   }
1004 
1005   if (cmd_max < 0) {
1006     CONF_ERROR(cmd, "command rate must be positive");
1007   }
1008 
1009   /* If the optional interval parameter is given, parse it. */
1010   if (cmd->argc-1 == 2) {
1011     max_cmd_interval = atoi(cmd->argv[2]);
1012 
1013     if (max_cmd_interval < 1) {
1014       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1015         "interval must be greater than zero", NULL));
1016     }
1017   }
1018 
1019   c = add_config_param(cmd->argv[0], 2, NULL, NULL);
1020   c->argv[0] = palloc(c->pool, sizeof(unsigned long));
1021   *((unsigned long *) c->argv[0]) = cmd_max;
1022   c->argv[1] = palloc(c->pool, sizeof(unsigned int));
1023   *((unsigned int *) c->argv[1]) = max_cmd_interval;
1024 
1025   return PR_HANDLED(cmd);
1026 }
1027 
1028 
1029 /* usage: MaxConnectionRate rate [interval] */
set_maxconnrate(cmd_rec * cmd)1030 MODRET set_maxconnrate(cmd_rec *cmd) {
1031   long conn_max = 0L;
1032   char *endp = NULL;
1033 
1034   if (cmd->argc-1 < 1 ||
1035       cmd->argc-1 > 2) {
1036     CONF_ERROR(cmd, "wrong number of parameters");
1037   }
1038   CHECK_CONF(cmd, CONF_ROOT);
1039 
1040   conn_max = strtol(cmd->argv[1], &endp, 10);
1041 
1042   if (endp && *endp) {
1043     CONF_ERROR(cmd, "invalid connection rate");
1044   }
1045 
1046   if (conn_max < 0) {
1047     CONF_ERROR(cmd, "connection rate must be positive");
1048   }
1049 
1050   max_connects = conn_max;
1051 
1052   /* If the optional interval parameter is given, parse it. */
1053   if (cmd->argc-1 == 2) {
1054     max_connect_interval = atoi(cmd->argv[2]);
1055 
1056     if (max_connect_interval < 1) {
1057       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1058         "interval must be greater than zero", NULL));
1059     }
1060   }
1061 
1062   return PR_HANDLED(cmd);
1063 }
1064 
set_timeoutidle(cmd_rec * cmd)1065 MODRET set_timeoutidle(cmd_rec *cmd) {
1066   int timeout = -1;
1067   config_rec *c = NULL;
1068 
1069   CHECK_ARGS(cmd, 1);
1070   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
1071 
1072   if (pr_str_get_duration(cmd->argv[1], &timeout) < 0) {
1073     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing timeout value '",
1074       cmd->argv[1], "': ", strerror(errno), NULL));
1075   }
1076 
1077   c = add_config_param(cmd->argv[0], 1, NULL);
1078   c->argv[0] = pcalloc(c->pool, sizeof(int));
1079   *((int *) c->argv[0]) = timeout;
1080   c->flags |= CF_MERGEDOWN;
1081 
1082   return PR_HANDLED(cmd);
1083 }
1084 
set_timeoutlinger(cmd_rec * cmd)1085 MODRET set_timeoutlinger(cmd_rec *cmd) {
1086   int timeout = -1;
1087   config_rec *c = NULL;
1088 
1089   CHECK_ARGS(cmd, 1);
1090   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1091 
1092   if (pr_str_get_duration(cmd->argv[1], &timeout) < 0) {
1093     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing timeout value '",
1094       cmd->argv[1], "': ", strerror(errno), NULL));
1095   }
1096 
1097   c = add_config_param(cmd->argv[0], 1, NULL);
1098   c->argv[0] = pcalloc(c->pool, sizeof(int));
1099   *((int *) c->argv[0]) = timeout;
1100 
1101   return PR_HANDLED(cmd);
1102 }
1103 
set_socketbindtight(cmd_rec * cmd)1104 MODRET set_socketbindtight(cmd_rec *cmd) {
1105   int bool = -1;
1106   CHECK_ARGS(cmd, 1);
1107   CHECK_CONF(cmd, CONF_ROOT);
1108 
1109   bool = get_boolean(cmd, 1);
1110   if (bool == -1) {
1111     CONF_ERROR(cmd, "expected Boolean parameter");
1112   }
1113 
1114   SocketBindTight = bool;
1115   return PR_HANDLED(cmd);
1116 }
1117 
1118 /* NOTE: at some point in the future, SocketBindTight should be folded
1119  * into this SocketOptions directive handler.
1120  */
set_socketoptions(cmd_rec * cmd)1121 MODRET set_socketoptions(cmd_rec *cmd) {
1122   register unsigned int i = 0;
1123 
1124   /* Make sure we have the right number of parameters. */
1125   if ((cmd->argc-1) % 2 != 0)
1126    CONF_ERROR(cmd, "bad number of parameters");
1127 
1128   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
1129 
1130   for (i = 1; i < cmd->argc; i++) {
1131     int value = 0;
1132 
1133     if (strcasecmp(cmd->argv[i], "maxseg") == 0) {
1134       value = atoi(cmd->argv[++i]);
1135 
1136       /* As per the tcp(7) man page, sizes larger than the interface MTU
1137        * will be ignored, and will have no effect.
1138        */
1139 
1140       if (value < 0) {
1141         CONF_ERROR(cmd, "maxseg size must be greater than 0");
1142       }
1143 
1144       cmd->server->tcp_mss_len = value;
1145 
1146     } else if (strcasecmp(cmd->argv[i], "rcvbuf") == 0) {
1147       value = atoi(cmd->argv[++i]);
1148 
1149       if (value < 1024) {
1150         CONF_ERROR(cmd, "rcvbuf size must be greater than or equal to 1024");
1151       }
1152 
1153       cmd->server->tcp_rcvbuf_len = value;
1154       cmd->server->tcp_rcvbuf_override = TRUE;
1155 
1156     } else if (strcasecmp(cmd->argv[i], "sndbuf") == 0) {
1157       value = atoi(cmd->argv[++i]);
1158 
1159       if (value < 1024) {
1160         CONF_ERROR(cmd, "sndbuf size must be greater than or equal to 1024");
1161       }
1162 
1163       cmd->server->tcp_sndbuf_len = value;
1164       cmd->server->tcp_sndbuf_override = TRUE;
1165 
1166     /* SocketOption keepalive off
1167      * SocketOption keepalive on
1168      * SocketOption keepalive 7200:9:75
1169      */
1170     } else if (strcasecmp(cmd->argv[i], "keepalive") == 0) {
1171       int b;
1172 
1173       b = get_boolean(cmd, i+1);
1174       if (b == -1) {
1175 #if defined(TCP_KEEPIDLE) || defined(TCP_KEEPCNT) || defined(TCP_KEEPINTVL)
1176         char *keepalive_spec, *ptr, *ptr2;
1177         int idle, count, intvl;
1178 
1179         /* Parse the given keepalive-spec */
1180         keepalive_spec = cmd->argv[i+1];
1181 
1182         ptr = strchr(keepalive_spec, ':');
1183         if (ptr == NULL) {
1184           CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1185             "badly formatted TCP keepalive spec '", cmd->argv[i+1], "'", NULL));
1186         }
1187 
1188         *ptr = '\0';
1189         idle = atoi(keepalive_spec);
1190 
1191         keepalive_spec = ptr + 1;
1192         ptr2 = strchr(keepalive_spec, ':');
1193         if (ptr2 == NULL) {
1194           *ptr = ':';
1195           CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1196             "badly formatted TCP keepalive spec '", cmd->argv[i+1], "'", NULL));
1197         }
1198 
1199         *ptr2 = '\0';
1200         count = atoi(keepalive_spec);
1201 
1202         keepalive_spec = ptr2 + 1;
1203         intvl = atoi(keepalive_spec);
1204 
1205         if (idle < 1) {
1206           char val_str[33];
1207 
1208           memset(val_str, '\0', sizeof(val_str));
1209           pr_snprintf(val_str, sizeof(val_str)-1, "%d", idle);
1210 
1211           CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1212             "badly formatted TCP keepalive spec: idle time '", val_str,
1213             "' cannot be less than 1", NULL));
1214         }
1215 
1216         if (count < 1) {
1217           char val_str[33];
1218 
1219           memset(val_str, '\0', sizeof(val_str));
1220           pr_snprintf(val_str, sizeof(val_str)-1, "%d", count);
1221 
1222           CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1223             "badly formatted TCP keepalive spec: count '", val_str,
1224             "' cannot be less than 1", NULL));
1225         }
1226 
1227         if (intvl < 1) {
1228           char val_str[33];
1229 
1230           memset(val_str, '\0', sizeof(val_str));
1231           pr_snprintf(val_str, sizeof(val_str)-1, "%d", intvl);
1232 
1233           CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1234             "badly formatted TCP keepalive spec: interval time '", val_str,
1235             "' cannot be less than 1", NULL));
1236         }
1237 
1238         cmd->server->tcp_keepalive->keepalive_enabled = TRUE;
1239         cmd->server->tcp_keepalive->keepalive_idle = idle;
1240         cmd->server->tcp_keepalive->keepalive_count = count;
1241         cmd->server->tcp_keepalive->keepalive_intvl = intvl;
1242 #else
1243         cmd->server->tcp_keepalive->keepalive_enabled = TRUE;
1244         pr_log_debug(DEBUG0,
1245           "%s: platform does not support fine-grained TCP keepalive control, "
1246           "using \"keepalive on\"", (char *) cmd->argv[0]);
1247 #endif /* No TCP_KEEPIDLE, TCP_KEEPCNT, or TCP_KEEPINTVL */
1248 
1249       } else {
1250         cmd->server->tcp_keepalive->keepalive_enabled = b;
1251       }
1252 
1253       /* Don't forget to increment the iterator. */
1254       i++;
1255 
1256     } else {
1257       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown socket option: '",
1258         cmd->argv[i], "'", NULL));
1259     }
1260   }
1261 
1262   return PR_HANDLED(cmd);
1263 }
1264 
set_multilinerfc2228(cmd_rec * cmd)1265 MODRET set_multilinerfc2228(cmd_rec *cmd) {
1266   int bool;
1267   config_rec *c;
1268 
1269   CHECK_ARGS(cmd, 1);
1270   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1271 
1272   bool = get_boolean(cmd, 1);
1273   if (bool == -1) {
1274     CONF_ERROR(cmd, "expected Boolean parameter");
1275   }
1276 
1277   c = add_config_param(cmd->argv[0], 1, NULL);
1278   c->argv[0] = pcalloc(c->pool, sizeof(int));
1279   *((int *) c->argv[0]) = bool;
1280 
1281   return PR_HANDLED(cmd);
1282 }
1283 
set_tcpbacklog(cmd_rec * cmd)1284 MODRET set_tcpbacklog(cmd_rec *cmd) {
1285   int backlog;
1286 
1287   CHECK_ARGS(cmd, 1);
1288   CHECK_CONF(cmd, CONF_ROOT);
1289 
1290   backlog = atoi(cmd->argv[1]);
1291 
1292   if (backlog < 1 ||
1293       backlog > 255) {
1294     CONF_ERROR(cmd, "parameter must be a number between 1 and 255");
1295   }
1296 
1297 #ifdef SOMAXCONN
1298   if (backlog > SOMAXCONN) {
1299     char str[32];
1300 
1301     memset(str, '\0', sizeof(str));
1302     pr_snprintf(str, sizeof(str)-1, "%u", (unsigned int) SOMAXCONN);
1303 
1304     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1305       "parameter must be less than SOMAXCONN (", str, ")", NULL));
1306   }
1307 #endif
1308 
1309   tcpBackLog = backlog;
1310   return PR_HANDLED(cmd);
1311 }
1312 
set_tcpnodelay(cmd_rec * cmd)1313 MODRET set_tcpnodelay(cmd_rec *cmd) {
1314   int bool = -1;
1315   config_rec *c = NULL;
1316 
1317   CHECK_ARGS(cmd, 1);
1318   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1319 
1320   bool = get_boolean(cmd, 1);
1321   if (bool == -1) {
1322     CONF_ERROR(cmd, "expected Boolean parameter");
1323   }
1324 
1325   c = add_config_param(cmd->argv[0], 1, NULL);
1326   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
1327   *((unsigned char *) c->argv[0]) = bool;
1328 
1329   return PR_HANDLED(cmd);
1330 }
1331 
set_user(cmd_rec * cmd)1332 MODRET set_user(cmd_rec *cmd) {
1333   struct passwd *pw = NULL;
1334 
1335   CHECK_ARGS(cmd, 1);
1336   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
1337 
1338   /* 1.1.7, no longer force user/group lookup inside <Anonymous>
1339    * it's now deferred until authentication occurs.
1340    */
1341 
1342   if (!cmd->config || cmd->config->config_type != CONF_ANON) {
1343     pw = pr_auth_getpwnam(cmd->tmp_pool, cmd->argv[1]);
1344     if (pw == NULL) {
1345       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "Unknown user '",
1346         cmd->argv[1], "'", NULL));
1347     }
1348   }
1349 
1350   if (pw) {
1351     config_rec *c = add_config_param("UserID", 1, NULL);
1352     c->argv[0] = pcalloc(c->pool, sizeof(uid_t));
1353     *((uid_t *) c->argv[0]) = pw->pw_uid;
1354   }
1355 
1356   add_config_param_str("UserName", 1, cmd->argv[1]);
1357   return PR_HANDLED(cmd);
1358 }
1359 
add_from(cmd_rec * cmd)1360 MODRET add_from(cmd_rec *cmd) {
1361   int cargc;
1362   void **cargv;
1363 
1364   CHECK_CONF(cmd, CONF_CLASS);
1365 
1366   cargc = cmd->argc-1;
1367   cargv = cmd->argv;
1368 
1369   while (cargc && *(cargv + 1)) {
1370     char *from;
1371 
1372     from = *(((char **) cargv) + 1);
1373 
1374     if (strcasecmp(from, "all") == 0 ||
1375         strcasecmp(from, "none") == 0) {
1376       pr_netacl_t *acl = pr_netacl_create(cmd->tmp_pool, from);
1377       if (acl == NULL) {
1378         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "bad ACL definition '", from,
1379           "': ", strerror(errno), NULL));
1380       }
1381 
1382       pr_trace_msg("netacl", 9, "'%s' parsed into netacl '%s'", from,
1383         pr_netacl_get_str(cmd->tmp_pool, acl));
1384 
1385       if (pr_class_add_acl(acl) < 0) {
1386         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error adding rule '", from,
1387           "': ", strerror(errno), NULL));
1388       }
1389 
1390       cargc = 0;
1391     }
1392 
1393     break;
1394   }
1395 
1396   /* Parse each parameter into a netacl. */
1397   while (cargc-- && *(++cargv)) {
1398     char *ent = NULL, *str;
1399 
1400     str = pstrdup(cmd->tmp_pool, *((char **) cargv));
1401 
1402     while ((ent = pr_str_get_token(&str, ",")) != NULL) {
1403       if (*ent) {
1404         pr_netacl_t *acl;
1405 
1406         pr_signals_handle();
1407 
1408         if (strcasecmp(ent, "all") == 0 ||
1409             strcasecmp(ent, "none") == 0) {
1410            cargc = 0;
1411            break;
1412          }
1413 
1414         acl = pr_netacl_create(cmd->tmp_pool, ent);
1415         if (acl == NULL) {
1416           CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "bad ACL definition '",
1417             ent, "': ", strerror(errno), NULL));
1418         }
1419 
1420         pr_trace_msg("netacl", 9, "'%s' parsed into netacl '%s'", ent,
1421           pr_netacl_get_str(cmd->tmp_pool, acl));
1422 
1423         if (pr_class_add_acl(acl) < 0) {
1424           CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error adding rule '", ent,
1425             "': ", strerror(errno), NULL));
1426         }
1427       }
1428     }
1429   }
1430 
1431   return PR_HANDLED(cmd);
1432 }
1433 
1434 /* usage: FSCachePolicy on|off|size {count} [maxAge {age}] */
set_fscachepolicy(cmd_rec * cmd)1435 MODRET set_fscachepolicy(cmd_rec *cmd) {
1436   register unsigned int i;
1437   config_rec *c;
1438 
1439   if (cmd->argc != 2 &&
1440       cmd->argc != 5) {
1441     CONF_ERROR(cmd, "wrong number of parameters");
1442   }
1443 
1444   if (cmd->argc == 2) {
1445     int engine;
1446 
1447     engine = get_boolean(cmd, 1);
1448     if (engine == -1) {
1449       CONF_ERROR(cmd, "expected Boolean parameter");
1450     }
1451 
1452     c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
1453     c->argv[0] = palloc(c->pool, sizeof(int));
1454     *((int *) c->argv[0]) = engine;
1455     c->argv[1] = palloc(c->pool, sizeof(unsigned int));
1456     *((unsigned int *) c->argv[1]) = PR_TUNABLE_FS_STATCACHE_SIZE;
1457     c->argv[2] = palloc(c->pool, sizeof(unsigned int));
1458     *((unsigned int *) c->argv[2]) = PR_TUNABLE_FS_STATCACHE_MAX_AGE;
1459 
1460     return PR_HANDLED(cmd);
1461   }
1462 
1463   c = add_config_param_str(cmd->argv[0], 3, NULL, NULL, NULL);
1464   c->argv[0] = palloc(c->pool, sizeof(int));
1465   *((int *) c->argv[0]) = TRUE;
1466   c->argv[1] = palloc(c->pool, sizeof(unsigned int));
1467   *((unsigned int *) c->argv[1]) = PR_TUNABLE_FS_STATCACHE_SIZE;
1468   c->argv[2] = palloc(c->pool, sizeof(unsigned int));
1469   *((unsigned int *) c->argv[2]) = PR_TUNABLE_FS_STATCACHE_MAX_AGE;
1470 
1471   for (i = 1; i < cmd->argc; i++) {
1472     if (strncasecmp(cmd->argv[i], "size", 5) == 0) {
1473       int size;
1474 
1475       i++;
1476       size = atoi(cmd->argv[i]);
1477       if (size < 1) {
1478         CONF_ERROR(cmd, "size parameter must be greater than 1");
1479       }
1480 
1481       *((unsigned int *) c->argv[1]) = size;
1482 
1483     } else if (strncasecmp(cmd->argv[i], "maxAge", 7) == 0) {
1484       int max_age;
1485 
1486       i++;
1487       max_age = atoi(cmd->argv[i]);
1488       if (max_age < 1) {
1489         CONF_ERROR(cmd, "maxAge parameter must be greater than 1");
1490       }
1491 
1492       *((unsigned int *) c->argv[2]) = max_age;
1493 
1494     } else {
1495       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown FSCachePolicy: ",
1496         cmd->argv[i], NULL));
1497     }
1498   }
1499 
1500   return PR_HANDLED(cmd);
1501 }
1502 
1503 /* usage: FSOptions opt1 opt2 ... */
set_fsoptions(cmd_rec * cmd)1504 MODRET set_fsoptions(cmd_rec *cmd) {
1505   register unsigned int i;
1506   config_rec *c;
1507 
1508   unsigned long opts = 0UL;
1509 
1510   if (cmd->argc-1 == 0) {
1511     CONF_ERROR(cmd, "wrong number of parameters");
1512   }
1513 
1514   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1515 
1516   c = add_config_param(cmd->argv[0], 1, NULL);
1517 
1518   for (i = 1; i < cmd->argc; i++) {
1519     if (strcmp(cmd->argv[i], "IgnoreExtendedAttributes") == 0) {
1520       opts |= PR_FSIO_OPT_IGNORE_XATTR;
1521 
1522     } else {
1523       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown FSOption '",
1524         cmd->argv[i], "'", NULL));
1525     }
1526   }
1527 
1528   c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
1529   *((unsigned long *) c->argv[0]) = opts;
1530 
1531   return PR_HANDLED(cmd);
1532 }
1533 
set_group(cmd_rec * cmd)1534 MODRET set_group(cmd_rec *cmd) {
1535   struct group *grp = NULL;
1536 
1537   CHECK_ARGS(cmd, 1);
1538   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
1539 
1540   if (!cmd->config || cmd->config->config_type != CONF_ANON) {
1541     grp = pr_auth_getgrnam(cmd->tmp_pool, cmd->argv[1]);
1542     if (grp == NULL) {
1543       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "Unknown group '",
1544         cmd->argv[1], "'", NULL));
1545     }
1546   }
1547 
1548   if (grp) {
1549     config_rec *c = add_config_param("GroupID", 1, NULL);
1550     c->argv[0] = pcalloc(c->pool, sizeof(gid_t));
1551     *((gid_t *) c->argv[0]) = grp->gr_gid;
1552   }
1553 
1554   add_config_param_str("GroupName", 1, cmd->argv[1]);
1555   return PR_HANDLED(cmd);
1556 }
1557 
1558 /* usage: Trace ["session"] channel1:level1 ... */
set_trace(cmd_rec * cmd)1559 MODRET set_trace(cmd_rec *cmd) {
1560 #ifdef PR_USE_TRACE
1561   register unsigned int i;
1562   int per_session = FALSE;
1563   unsigned int idx = 1;
1564 
1565   if (cmd->argc-1 < 1) {
1566     CONF_ERROR(cmd, "wrong number of parameters");
1567   }
1568   CHECK_CONF(cmd, CONF_ROOT);
1569 
1570   /* Look for the optional "session" keyword, which will indicate that these
1571    * Trace settings are to be applied to a session process only.
1572    */
1573   if (strncmp(cmd->argv[1], "session", 8) == 0) {
1574 
1575     /* If this is the only parameter, it's a config error. */
1576     if (cmd->argc == 2) {
1577       CONF_ERROR(cmd, "wrong number of parameters");
1578     }
1579 
1580     per_session = TRUE;
1581     idx = 2;
1582   }
1583 
1584   if (!per_session) {
1585     for (i = idx; i < cmd->argc; i++) {
1586       char *channel, *ptr;
1587       int min_level, max_level, res;
1588 
1589       ptr = strchr(cmd->argv[i], ':');
1590       if (ptr == NULL) {
1591         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "badly formatted parameter: '",
1592           cmd->argv[i], "'", NULL));
1593       }
1594 
1595       channel = cmd->argv[i];
1596       *ptr = '\0';
1597 
1598       res = pr_trace_parse_levels(ptr + 1, &min_level, &max_level);
1599       if (res < 0) {
1600         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing level \"",
1601           ptr + 1, "\" for channel '", channel, "': ", strerror(errno), NULL));
1602       }
1603 
1604       if (pr_trace_set_levels(channel, min_level, max_level) < 0) {
1605         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error setting level \"",
1606           ptr + 1, "\" for channel '", channel, "': ", strerror(errno), NULL));
1607       }
1608 
1609       *ptr = ':';
1610     }
1611 
1612   } else {
1613     register unsigned int j = 0;
1614     config_rec *c;
1615 
1616     /* Do a syntax check of the configured trace channels/levels, and store
1617      * them in a config rec for later handling.
1618      */
1619 
1620     c = add_config_param(cmd->argv[0], 0);
1621     c->argc = cmd->argc - 2;
1622     c->argv = pcalloc(c->pool, ((c->argc + 1) * sizeof(void *)));
1623 
1624     for (i = idx; i < cmd->argc; i++) {
1625       char *ptr;
1626 
1627       ptr = strchr(cmd->argv[i], ':');
1628       if (ptr == NULL) {
1629         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "badly formatted parameter: '",
1630           cmd->argv[i], "'", NULL));
1631       }
1632 
1633       c->argv[j++] = pstrdup(c->pool, cmd->argv[i]);
1634     }
1635   }
1636 
1637   return PR_HANDLED(cmd);
1638 #else
1639   CONF_ERROR(cmd,
1640     "Use of the Trace directive requires trace support (--enable-trace)");
1641 #endif /* PR_USE_TRACE */
1642 }
1643 
1644 /* usage: TraceLog path */
set_tracelog(cmd_rec * cmd)1645 MODRET set_tracelog(cmd_rec *cmd) {
1646 #ifdef PR_USE_TRACE
1647   if (cmd->argc-1 != 1) {
1648     CONF_ERROR(cmd, "wrong number of parameters");
1649   }
1650   CHECK_CONF(cmd, CONF_ROOT);
1651 
1652   if (pr_fs_valid_path(cmd->argv[1]) < 0) {
1653     CONF_ERROR(cmd, "must be an absolute path");
1654   }
1655 
1656   trace_log = pstrdup(cmd->server->pool, cmd->argv[1]);
1657   if (pr_trace_set_file(trace_log) < 0) {
1658     if (errno == EPERM) {
1659       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error using TraceLog '",
1660         trace_log, "': directory is symlink or is world-writable", NULL));
1661 
1662     } else {
1663       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error using TraceLog '",
1664         trace_log, "': ", strerror(errno), NULL));
1665     }
1666   }
1667 
1668   return PR_HANDLED(cmd);
1669 #else
1670   CONF_ERROR(cmd,
1671     "Use of the TraceLog directive requires trace support (--enable-trace)");
1672 #endif /* PR_USE_TRACE */
1673 }
1674 
1675 /* usage: TraceOptions opt1 ... optN */
set_traceoptions(cmd_rec * cmd)1676 MODRET set_traceoptions(cmd_rec *cmd) {
1677 #ifdef PR_USE_TRACE
1678   register unsigned int i;
1679   int ctx;
1680   config_rec *c;
1681   unsigned long trace_opts = PR_TRACE_OPT_DEFAULT;
1682 
1683   if (cmd->argc < 2) {
1684     CONF_ERROR(cmd, "wrong number of parameters");
1685   }
1686 
1687   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1688 
1689   for (i = 1; i < cmd->argc; i++) {
1690     char action, *opt;
1691 
1692     opt = cmd->argv[i];
1693     action = *opt;
1694 
1695     if (action != '-' &&
1696         action != '+') {
1697       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "bad TraceOption: '", opt, "'",
1698         NULL));
1699     }
1700 
1701     opt++;
1702 
1703     if (strcasecmp(opt, "ConnIPs") == 0) {
1704       switch (action) {
1705         case '-':
1706           trace_opts &= ~PR_TRACE_OPT_LOG_CONN_IPS;
1707           break;
1708 
1709         case '+':
1710           trace_opts |= PR_TRACE_OPT_LOG_CONN_IPS;
1711           break;
1712       }
1713 
1714     } else if (strcasecmp(opt, "Timestamp") == 0) {
1715       switch (action) {
1716         case '-':
1717           trace_opts &= ~PR_TRACE_OPT_USE_TIMESTAMP;
1718           break;
1719 
1720         case '+':
1721           trace_opts |= PR_TRACE_OPT_USE_TIMESTAMP;
1722           break;
1723       }
1724 
1725     } else if (strcasecmp(opt, "TimestampMillis") == 0) {
1726       switch (action) {
1727         case '-':
1728           trace_opts &= ~PR_TRACE_OPT_USE_TIMESTAMP_MILLIS;
1729           break;
1730 
1731         case '+':
1732           trace_opts |= PR_TRACE_OPT_USE_TIMESTAMP_MILLIS;
1733           break;
1734       }
1735 
1736     } else {
1737       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown TraceOption: '",
1738         opt, "'", NULL));
1739     }
1740   }
1741 
1742   c = add_config_param(cmd->argv[0], 1, NULL);
1743   c->argv[0] = palloc(c->pool, sizeof(unsigned long));
1744   *((unsigned long *) c->argv[0]) = trace_opts;
1745 
1746   ctx = (cmd->config && cmd->config->config_type != CONF_PARAM ?
1747     cmd->config->config_type : cmd->server->config_type ?
1748     cmd->server->config_type : CONF_ROOT);
1749 
1750   if (ctx == CONF_ROOT) {
1751     /* If we're the "server config" context, set the TraceOptions here,
1752      * too.  This will apply these TraceOptions to the daemon process.
1753      */
1754     if (pr_trace_set_options(trace_opts) < 0) {
1755       pr_log_debug(DEBUG6, "%s: error setting TraceOptions (%lu): %s",
1756         (char *) cmd->argv[0], trace_opts, strerror(errno));
1757     }
1758   }
1759 
1760   return PR_HANDLED(cmd);
1761 
1762 #else
1763   CONF_ERROR(cmd,
1764     "Use of the TraceOptions directive requires trace support (--enable-trace)");
1765 #endif /* PR_USE_TRACE */
1766 }
1767 
set_umask(cmd_rec * cmd)1768 MODRET set_umask(cmd_rec *cmd) {
1769   config_rec *c;
1770   char *endp;
1771   mode_t tmp_umask;
1772 
1773   CHECK_VARARGS(cmd, 1, 2);
1774   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|
1775     CONF_DIR|CONF_DYNDIR);
1776 
1777   tmp_umask = (mode_t) strtol(cmd->argv[1], &endp, 8);
1778 
1779   if (endp && *endp) {
1780     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", cmd->argv[1],
1781       "' is not a valid umask", NULL));
1782   }
1783 
1784   c = add_config_param(cmd->argv[0], 1, NULL);
1785   c->argv[0] = pcalloc(c->pool, sizeof(mode_t));
1786   *((mode_t *) c->argv[0]) = tmp_umask;
1787   c->flags |= CF_MERGEDOWN;
1788 
1789   /* Have we specified a directory umask as well?
1790    */
1791   if (CHECK_HASARGS(cmd, 2)) {
1792 
1793     /* allocate space for another mode_t.  Don't worry -- the previous
1794      * pointer was recorded in the Umask config_rec
1795      */
1796     tmp_umask = (mode_t) strtol(cmd->argv[2], &endp, 8);
1797 
1798     if (endp && *endp) {
1799       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", cmd->argv[2],
1800         "' is not a valid umask", NULL));
1801     }
1802 
1803     c = add_config_param("DirUmask", 1, NULL);
1804     c->argv[0] = pcalloc(c->pool, sizeof(mode_t));
1805     *((mode_t *) c->argv[0]) = tmp_umask;
1806     c->flags |= CF_MERGEDOWN;
1807   }
1808 
1809   return PR_HANDLED(cmd);
1810 }
1811 
set_unsetenv(cmd_rec * cmd)1812 MODRET set_unsetenv(cmd_rec *cmd) {
1813   int ctxt_type;
1814 
1815   CHECK_ARGS(cmd, 1);
1816   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1817 
1818   add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
1819 
1820   /* In addition, if this is the "server config" context, unset the
1821    * environment variable now.  If there was a <Daemon> context, that would
1822    * be a more appropriate place for configuring parse-time environment
1823    * variables.
1824    */
1825   ctxt_type = (cmd->config && cmd->config->config_type != CONF_PARAM ?
1826     cmd->config->config_type : cmd->server->config_type ?
1827     cmd->server->config_type : CONF_ROOT);
1828 
1829   if (ctxt_type == CONF_ROOT) {
1830     if (pr_env_unset(cmd->server->pool, cmd->argv[1]) < 0) {
1831       pr_log_debug(DEBUG1, "%s: unable to unset environment variable '%s': %s",
1832         (char *) cmd->argv[0], (char *) cmd->argv[1], strerror(errno));
1833 
1834     } else {
1835       core_handle_locale_env(cmd->argv[1]);
1836     }
1837   }
1838 
1839   return PR_HANDLED(cmd);
1840 }
1841 
1842 /* usage: ProcessTitles "terse"|"verbose" */
set_processtitles(cmd_rec * cmd)1843 MODRET set_processtitles(cmd_rec *cmd) {
1844   CHECK_ARGS(cmd, 1);
1845   CHECK_CONF(cmd, CONF_ROOT);
1846 
1847   if (strcasecmp(cmd->argv[1], "terse") != 0 &&
1848       strcasecmp(cmd->argv[1], "verbose") != 0) {
1849     CONF_ERROR(cmd, "unknown parameter");
1850   }
1851 
1852   add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
1853   return PR_HANDLED(cmd);
1854 }
1855 
1856 /* usage: Protocols protocol1 ... protocolN */
set_protocols(cmd_rec * cmd)1857 MODRET set_protocols(cmd_rec *cmd) {
1858   register unsigned int i;
1859   config_rec *c;
1860   array_header *list;
1861 
1862   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1863 
1864   if (cmd->argc < 2) {
1865     CONF_ERROR(cmd, "wrong number of parameters");
1866   }
1867 
1868   c = add_config_param(cmd->argv[0], 1, NULL);
1869 
1870   list = make_array(c->pool, 0, sizeof(char *));
1871   for (i = 1; i < cmd->argc; i++) {
1872     *((char **) push_array(list)) = pstrdup(c->pool, cmd->argv[i]);
1873   }
1874 
1875   c->argv[0] = list;
1876   c->flags |= CF_MULTI;
1877 
1878   return PR_HANDLED(cmd);
1879 }
1880 
1881 /* usage: RegexOptions [MatchLimit limit] [MatchLimitRecursion limit]
1882  */
set_regexoptions(cmd_rec * cmd)1883 MODRET set_regexoptions(cmd_rec *cmd) {
1884   config_rec *c;
1885   unsigned long match_limit = 0, match_limit_recursion = 0;
1886   register unsigned int i;
1887 
1888   if (cmd->argc < 3) {
1889     CONF_ERROR(cmd, "Wrong number of parameters");
1890 
1891   } else {
1892     int npairs;
1893 
1894     /* Make sure we have an even number of args for the key/value pairs. */
1895     npairs = cmd->argc - 1;
1896     if (npairs % 2 != 0) {
1897       CONF_ERROR(cmd, "Wrong number of parameters");
1898     }
1899   }
1900 
1901   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1902 
1903   /* XXX If more limits/options are supported, switch to using a table
1904    * for storing the key/value pairs.
1905    */
1906 
1907   for (i = 1; i < cmd->argc; i++) {
1908     if (strncmp(cmd->argv[i], "MatchLimit", 11) == 0) {
1909       char *ptr = NULL;
1910 
1911       match_limit = strtoul(cmd->argv[i+1], &ptr, 10);
1912       if (ptr && *ptr) {
1913         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "bad MatchLimit value: ",
1914           cmd->argv[i+1], NULL));
1915       }
1916 
1917       /* Don't forget to advance i past the value. */
1918       i += 2;
1919 
1920     } else if (strncmp(cmd->argv[i], "MatchLimitRecursion", 20) == 0) {
1921       char *ptr = NULL;
1922 
1923       match_limit_recursion = strtoul(cmd->argv[i+1], &ptr, 10);
1924       if (ptr && *ptr) {
1925         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1926           "bad MatchLimitRecursion value: ", cmd->argv[i+1], NULL));
1927       }
1928 
1929       /* Don't forget to advance i past the value. */
1930       i += 2;
1931 
1932     } else {
1933       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown RegexOptions option: '",
1934         cmd->argv[i], "'", NULL));
1935     }
1936   }
1937 
1938   c = add_config_param(cmd->argv[0], 2, NULL, NULL);
1939   c->argv[0] = palloc(c->pool, sizeof(unsigned long));
1940   *((unsigned long *) c->argv[0]) = match_limit;
1941   c->argv[1] = palloc(c->pool, sizeof(unsigned long));
1942   *((unsigned long *) c->argv[1]) = match_limit_recursion;
1943 
1944   return PR_HANDLED(cmd);
1945 }
1946 
set_syslogfacility(cmd_rec * cmd)1947 MODRET set_syslogfacility(cmd_rec *cmd) {
1948   int i;
1949   struct {
1950     char *name;
1951     int facility;
1952   } factable[] = {
1953   { "AUTH",		LOG_AUTHPRIV		},
1954   { "AUTHPRIV",		LOG_AUTHPRIV		},
1955 #ifdef HAVE_LOG_FTP
1956   { "FTP",		LOG_FTP			},
1957 #endif
1958 #ifdef HAVE_LOG_CRON
1959   { "CRON",		LOG_CRON		},
1960 #endif
1961   { "DAEMON",		LOG_DAEMON		},
1962   { "KERN",		LOG_KERN		},
1963   { "LOCAL0",		LOG_LOCAL0		},
1964   { "LOCAL1",		LOG_LOCAL1		},
1965   { "LOCAL2",		LOG_LOCAL2		},
1966   { "LOCAL3",		LOG_LOCAL3		},
1967   { "LOCAL4",		LOG_LOCAL4		},
1968   { "LOCAL5",		LOG_LOCAL5		},
1969   { "LOCAL6",		LOG_LOCAL6		},
1970   { "LOCAL7",		LOG_LOCAL7		},
1971   { "LPR",		LOG_LPR			},
1972   { "MAIL",		LOG_MAIL		},
1973   { "NEWS",		LOG_NEWS		},
1974   { "USER",		LOG_USER		},
1975   { "UUCP",		LOG_UUCP		},
1976   { NULL,		0			} };
1977 
1978   CHECK_ARGS(cmd, 1);
1979   CHECK_CONF(cmd, CONF_ROOT);
1980 
1981   for (i = 0; factable[i].name; i++) {
1982     if (strcasecmp(cmd->argv[1], factable[i].name) == 0) {
1983       log_closesyslog();
1984       log_setfacility(factable[i].facility);
1985 
1986       pr_signals_block();
1987       switch (log_opensyslog(NULL)) {
1988         case -1:
1989           pr_signals_unblock();
1990           CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to open syslog: ",
1991             strerror(errno), NULL));
1992           break;
1993 
1994         case PR_LOG_WRITABLE_DIR:
1995           pr_signals_unblock();
1996           CONF_ERROR(cmd,
1997             "you are attempting to log to a world-writable directory");
1998           break;
1999 
2000         case PR_LOG_SYMLINK:
2001           pr_signals_unblock();
2002           CONF_ERROR(cmd, "you are attempting to log to a symbolic link");
2003           break;
2004 
2005         default:
2006           break;
2007       }
2008       pr_signals_unblock();
2009 
2010       return PR_HANDLED(cmd);
2011     }
2012   }
2013 
2014   CONF_ERROR(cmd, "argument must be a valid syslog facility");
2015 }
2016 
set_timesgmt(cmd_rec * cmd)2017 MODRET set_timesgmt(cmd_rec *cmd) {
2018   int bool = -1;
2019   config_rec *c = NULL;
2020 
2021   CHECK_ARGS(cmd, 1);
2022   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
2023 
2024   bool = get_boolean(cmd, 1);
2025   if (bool == -1) {
2026     CONF_ERROR(cmd, "expected Boolean parameter");
2027   }
2028 
2029   c = add_config_param(cmd->argv[0], 1, NULL);
2030   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
2031   *((unsigned char *) c->argv[0]) = bool;
2032 
2033   c->flags |= CF_MERGEDOWN;
2034   return PR_HANDLED(cmd);
2035 }
2036 
set_regex(cmd_rec * cmd,char * param,char * type)2037 MODRET set_regex(cmd_rec *cmd, char *param, char *type) {
2038 #ifdef PR_USE_REGEX
2039   pr_regex_t *pre = NULL;
2040   config_rec *c = NULL;
2041   int regex_flags = REG_EXTENDED|REG_NOSUB, res = 0;
2042 
2043   if (cmd->argc-1 < 1 ||
2044       cmd->argc-1 > 2) {
2045     CONF_ERROR(cmd, "bad number of parameters");
2046   }
2047 
2048   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|CONF_DIR|
2049     CONF_DYNDIR);
2050 
2051   /* Make sure that, if present, the flags parameter is correctly formatted. */
2052   if (cmd->argc-1 == 2) {
2053     int flags = 0;
2054 
2055     /* We need to parse the flags parameter here, to see if any flags which
2056      * affect the compilation of the regex (e.g. NC) are present.
2057      */
2058 
2059     flags = pr_filter_parse_flags(cmd->tmp_pool, cmd->argv[2]);
2060     if (flags < 0) {
2061       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
2062         "badly formatted flags parameter: '", cmd->argv[2], "'", NULL));
2063     }
2064 
2065     if (flags == 0) {
2066       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
2067         "unknown filter flags '", cmd->argv[2], "'", NULL));
2068     }
2069 
2070     regex_flags |= flags;
2071   }
2072 
2073   pr_log_debug(DEBUG4, "%s: compiling %s regex '%s'", (char *) cmd->argv[0],
2074     type, (char *) cmd->argv[1]);
2075   pre = pr_regexp_alloc(&core_module);
2076 
2077   res = pr_regexp_compile(pre, cmd->argv[1], regex_flags);
2078   if (res != 0) {
2079     char errstr[200] = {'\0'};
2080 
2081     pr_regexp_error(res, pre, errstr, sizeof(errstr));
2082     pr_regexp_free(NULL, pre);
2083 
2084     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", (char *) cmd->argv[1],
2085       "' failed regex compilation: ", errstr, NULL));
2086   }
2087 
2088   c = add_config_param(param, 1, pre);
2089   c->flags |= CF_MERGEDOWN;
2090   return PR_HANDLED(cmd);
2091 
2092 #else /* no regular expression support at the moment */
2093   CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", param, " directive cannot be "
2094     "used on this system, as you do not have POSIX compliant regex support",
2095     NULL));
2096 #endif
2097 }
2098 
set_allowdenyfilter(cmd_rec * cmd)2099 MODRET set_allowdenyfilter(cmd_rec *cmd) {
2100 #ifdef PR_USE_REGEX
2101   pr_regex_t *pre = NULL;
2102   config_rec *c = NULL;
2103   int regex_flags = REG_EXTENDED|REG_NOSUB, res = 0;
2104 
2105   if (cmd->argc-1 < 1 ||
2106       cmd->argc-1 > 2) {
2107     CONF_ERROR(cmd, "bad number of parameters");
2108   }
2109 
2110   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|CONF_DIR|
2111     CONF_DYNDIR|CONF_LIMIT);
2112 
2113   /* Make sure that, if present, the flags parameter is correctly formatted. */
2114   if (cmd->argc-1 == 2) {
2115     int flags = 0;
2116 
2117     /* We need to parse the flags parameter here, to see if any flags which
2118      * affect the compilation of the regex (e.g. NC) are present.
2119      */
2120 
2121     flags = pr_filter_parse_flags(cmd->tmp_pool, cmd->argv[2]);
2122     if (flags < 0) {
2123       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
2124         "badly formatted flags parameter: '", cmd->argv[2], "'", NULL));
2125     }
2126 
2127     if (flags == 0) {
2128       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
2129         "unknown filter flags '", cmd->argv[2], "'", NULL));
2130     }
2131 
2132     regex_flags |= flags;
2133   }
2134 
2135   pr_log_debug(DEBUG4, "%s: compiling regex '%s'", (char *) cmd->argv[0],
2136     (char *) cmd->argv[1]);
2137   pre = pr_regexp_alloc(&core_module);
2138 
2139   res = pr_regexp_compile(pre, cmd->argv[1], regex_flags);
2140   if (res != 0) {
2141     char errstr[200] = {'\0'};
2142 
2143     pr_regexp_error(res, pre, errstr, sizeof(errstr));
2144     pr_regexp_free(NULL, pre);
2145 
2146     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", (char *) cmd->argv[1],
2147       "' failed regex compilation: ", errstr, NULL));
2148   }
2149 
2150   c = add_config_param(cmd->argv[0], 1, pre);
2151   c->flags |= CF_MERGEDOWN;
2152   return PR_HANDLED(cmd);
2153 
2154 #else /* no regular expression support at the moment */
2155   CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", cmd->argv[0],
2156     " directive cannot be used on this system, as you do not have POSIX "
2157     "compliant regex support", NULL));
2158 #endif
2159 }
2160 
set_passiveports(cmd_rec * cmd)2161 MODRET set_passiveports(cmd_rec *cmd) {
2162   int pasv_min_port, pasv_max_port;
2163   config_rec *c = NULL;
2164 
2165   CHECK_ARGS(cmd, 2);
2166   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
2167 
2168   pasv_min_port = atoi(cmd->argv[1]);
2169   pasv_max_port = atoi(cmd->argv[2]);
2170 
2171   /* Sanity check */
2172   if (pasv_min_port <= 0 ||
2173       pasv_min_port > 65535) {
2174     CONF_ERROR(cmd, "min port must be allowable port number");
2175   }
2176 
2177   if (pasv_max_port <= 0 ||
2178       pasv_max_port > 65535) {
2179     CONF_ERROR(cmd, "max port must be allowable port number");
2180   }
2181 
2182   if (pasv_min_port < 1024 ||
2183       pasv_max_port < 1024) {
2184     CONF_ERROR(cmd, "port numbers must be above 1023");
2185   }
2186 
2187   if (pasv_max_port <= pasv_min_port) {
2188     CONF_ERROR(cmd, "min port must be less than max port");
2189   }
2190 
2191   c = add_config_param(cmd->argv[0], 2, NULL, NULL);
2192   c->argv[0] = pcalloc(c->pool, sizeof(int));
2193   *((int *) c->argv[0]) = pasv_min_port;
2194   c->argv[1] = pcalloc(c->pool, sizeof(int));
2195   *((int *) c->argv[1]) = pasv_max_port;
2196 
2197   return PR_HANDLED(cmd);
2198 }
2199 
set_pathallowfilter(cmd_rec * cmd)2200 MODRET set_pathallowfilter(cmd_rec *cmd) {
2201   return set_regex(cmd, cmd->argv[0], "allow");
2202 }
2203 
set_pathdenyfilter(cmd_rec * cmd)2204 MODRET set_pathdenyfilter(cmd_rec *cmd) {
2205   return set_regex(cmd, cmd->argv[0], "deny");
2206 }
2207 
2208 /* usage: AllowForeignAddress on|off|class */
set_allowforeignaddress(cmd_rec * cmd)2209 MODRET set_allowforeignaddress(cmd_rec *cmd) {
2210   int bool = -1;
2211   config_rec *c = NULL;
2212   char *class_name = NULL;
2213 
2214   CHECK_ARGS(cmd, 1);
2215   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
2216 
2217   bool = get_boolean(cmd, 1);
2218   if (bool == -1) {
2219     /* Not a boolean?  Assume it's a <Class> name, then. */
2220     class_name = cmd->argv[1];
2221   }
2222 
2223   c = add_config_param(cmd->argv[0], 2, NULL, NULL);
2224   c->argv[0] = pcalloc(c->pool, sizeof(int));
2225   *((int *) c->argv[0]) = bool;
2226   c->argv[1] = pstrdup(c->pool, class_name);
2227 
2228   c->flags |= CF_MERGEDOWN;
2229   return PR_HANDLED(cmd);
2230 }
2231 
set_commandbuffersize(cmd_rec * cmd)2232 MODRET set_commandbuffersize(cmd_rec *cmd) {
2233   size_t size = 0;
2234   off_t nbytes = 0;
2235   config_rec *c = NULL;
2236   const char *units = NULL;
2237 
2238   if (cmd->argc < 2 || cmd->argc > 3) {
2239     CONF_ERROR(cmd, "wrong number of parameters")
2240   }
2241 
2242   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
2243 
2244   if (cmd->argc == 3) {
2245     units = cmd->argv[2];
2246   }
2247 
2248   if (pr_str_get_nbytes(cmd->argv[1], units, &nbytes) < 0) {
2249     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to parse: ",
2250       cmd->argv[1], " ", units ? units : "", ": ", strerror(errno), NULL));
2251   }
2252 
2253   if (nbytes > PR_TUNABLE_CMD_BUFFER_SIZE) {
2254     char max[1024];
2255 
2256     pr_snprintf(max, sizeof(max)-1, "%lu", (unsigned long)
2257       PR_TUNABLE_CMD_BUFFER_SIZE);
2258     max[sizeof(max)-1] = '\0';
2259 
2260     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "size ", cmd->argv[1],
2261       units ? units : "", "exceeds max size ", max, NULL));
2262   }
2263 
2264   /* Possible truncation here, but only for an absurdly large size. */
2265   size = (size_t) nbytes;
2266 
2267   c = add_config_param(cmd->argv[0], 1, NULL);
2268   c->argv[0] = pcalloc(c->pool, sizeof(size_t));
2269   *((size_t *) c->argv[0]) = size;
2270 
2271   return PR_HANDLED(cmd);
2272 }
2273 
set_cdpath(cmd_rec * cmd)2274 MODRET set_cdpath(cmd_rec *cmd) {
2275   config_rec *c = NULL;
2276 
2277   CHECK_ARGS(cmd, 1);
2278   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
2279 
2280   c = add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
2281   c->flags |= CF_MERGEDOWN;
2282 
2283   return PR_HANDLED(cmd);
2284 }
2285 
add_directory(cmd_rec * cmd)2286 MODRET add_directory(cmd_rec *cmd) {
2287   config_rec *c;
2288   char *dir, *rootdir = NULL;
2289   int flags = 0;
2290 
2291   CHECK_ARGS(cmd, 1);
2292   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
2293 
2294   dir = cmd->argv[1];
2295 
2296   if (*dir != '/' &&
2297       *dir != '~' &&
2298       (!cmd->config ||
2299        cmd->config->config_type != CONF_ANON)) {
2300     CONF_ERROR(cmd, "relative path not allowed in non-<Anonymous> sections");
2301   }
2302 
2303   /* If in anonymous mode, and path is relative, just cat anon root
2304    * and relative path.
2305    *
2306    * Note: This is no longer necessary, because we don't interpolate anonymous
2307    * directories at run-time.
2308    */
2309   if (cmd->config &&
2310       cmd->config->config_type == CONF_ANON &&
2311       *dir != '/' &&
2312       *dir != '~') {
2313     if (strncmp(dir, "*", 2) != 0) {
2314       dir = pdircat(cmd->tmp_pool, "/", dir, NULL);
2315     }
2316     rootdir = cmd->config->name;
2317 
2318   } else {
2319     if (pr_fs_valid_path(dir) < 0) {
2320       /* Not an absolute path; mark it for deferred resolution. */
2321       flags |= CF_DEFER;
2322     }
2323   }
2324 
2325   /* Check to see that there isn't already a config for this directory,
2326    * but only if we're not in an <Anonymous> section.  Due to the way
2327    * in which later <Directory> checks are done, <Directory> blocks inside
2328    * <Anonymous> sections are handled differently than outside, probably
2329    * overriding their outside counterparts (if necessary).  This is
2330    * probably OK, as this overriding only takes effect for the <Anonymous>
2331    * user.
2332    */
2333 
2334   if (!check_context(cmd, CONF_ANON) &&
2335       find_config(cmd->server->conf, CONF_DIR, dir, FALSE) != NULL) {
2336     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
2337       "<Directory> section already configured for '", cmd->argv[1], "'", NULL));
2338   }
2339 
2340   /* Check for any expandable variables, and mark this config_rec for
2341    * deferred resolution if present
2342    */
2343   if (strstr(dir, "%u")) {
2344     flags |= CF_DEFER;
2345   }
2346 
2347   c = pr_parser_config_ctxt_open(dir);
2348   c->argc = 2;
2349   c->argv = pcalloc(c->pool, 3 * sizeof(void *));
2350 
2351   /* If we do NOT have rootdir, then do NOT add anything to the argv[1] slot;
2352    * it is intended solely for that particular use case.
2353    */
2354   if (rootdir) {
2355     c->argv[1] = pstrdup(c->pool, rootdir);
2356   }
2357 
2358   c->config_type = CONF_DIR;
2359   c->flags |= flags;
2360 
2361   if (!(c->flags & CF_DEFER)) {
2362     pr_log_debug(DEBUG2, "<Directory %s>: adding section for resolved "
2363       "path '%s'", (char *) cmd->argv[1], dir);
2364 
2365   } else {
2366     pr_log_debug(DEBUG2,
2367       "<Directory %s>: deferring resolution of path", (char *) cmd->argv[1]);
2368   }
2369 
2370   return PR_HANDLED(cmd);
2371 }
2372 
set_hidefiles(cmd_rec * cmd)2373 MODRET set_hidefiles(cmd_rec *cmd) {
2374 #ifdef PR_USE_REGEX
2375   pr_regex_t *pre = NULL;
2376   config_rec *c = NULL;
2377   unsigned int precedence = 0;
2378   unsigned char negated = FALSE, none = FALSE;
2379   char *ptr;
2380 
2381   int ctxt = (cmd->config && cmd->config->config_type != CONF_PARAM ?
2382     cmd->config->config_type : cmd->server->config_type ?
2383     cmd->server->config_type : CONF_ROOT);
2384 
2385   /* This directive must have either 1, or 3, arguments */
2386   if (cmd->argc-1 != 1 &&
2387       cmd->argc-1 != 3) {
2388     CONF_ERROR(cmd, "wrong number of parameters");
2389   }
2390 
2391   CHECK_CONF(cmd, CONF_DIR|CONF_DYNDIR);
2392 
2393   /* Set the precedence for this config_rec based on its configuration
2394    * context.
2395    */
2396   if (ctxt & CONF_DIR) {
2397     precedence = 1;
2398 
2399   } else {
2400     precedence = 2;
2401   }
2402 
2403   /* Check for a leading '!' prefix, signifying regex negation */
2404   ptr = cmd->argv[1];
2405   if (*ptr == '!') {
2406     negated = TRUE;
2407     ptr++;
2408 
2409   } else {
2410     /* Check for a "none" argument, which is used to nullify inherited
2411      * HideFiles configurations from parent directories.
2412      */
2413     if (strcasecmp(ptr, "none") == 0) {
2414       none = TRUE;
2415     }
2416   }
2417 
2418   if (!none) {
2419     int res;
2420 
2421     pre = pr_regexp_alloc(&core_module);
2422 
2423     res = pr_regexp_compile(pre, ptr, REG_EXTENDED|REG_NOSUB);
2424     if (res != 0) {
2425       char errstr[200] = {'\0'};
2426 
2427       pr_regexp_error(res, pre, errstr, sizeof(errstr));
2428       pr_regexp_free(NULL, pre);
2429 
2430       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", ptr,
2431         "' failed regex compilation: ", errstr, NULL));
2432     }
2433   }
2434 
2435   /* If the directive was used with 3 arguments, then the optional
2436    * classifiers, and classifier expression, were used.  Make sure that
2437    * a valid classifier was used.
2438    */
2439   if (cmd->argc-1 == 3) {
2440     if (strncmp(cmd->argv[2], "user", 5) == 0 ||
2441         strncmp(cmd->argv[2], "group", 6) == 0 ||
2442         strncmp(cmd->argv[2], "class", 6) == 0) {
2443 
2444       /* no-op */
2445 
2446     } else {
2447       return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0],
2448         "unknown classifier used: '", cmd->argv[2], "'", NULL));
2449     }
2450   }
2451 
2452   if (cmd->argc-1 == 1) {
2453     c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
2454     c->argv[0] = pcalloc(c->pool, sizeof(pr_regex_t *));
2455     *((pr_regex_t **) c->argv[0]) = pre;
2456     c->argv[1] = pcalloc(c->pool, sizeof(unsigned char));
2457     *((unsigned char *) c->argv[1]) = negated;
2458     c->argv[2] = pcalloc(c->pool, sizeof(unsigned int));
2459     *((unsigned int *) c->argv[2]) = precedence;
2460 
2461   } else if (cmd->argc-1 == 3) {
2462     array_header *acl = NULL;
2463     unsigned int argc = cmd->argc - 3;
2464     void **argv;
2465 
2466     argv = &(cmd->argv[2]);
2467 
2468     acl = pr_expr_create(cmd->tmp_pool, &argc, (char **) argv);
2469     if (acl == NULL) {
2470       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error creating expression: ",
2471         strerror(errno), NULL));
2472     }
2473 
2474     c = add_config_param(cmd->argv[0], 0);
2475     c->argc = argc + 4;
2476 
2477     /* Add 5 to argc for the argv of the config_rec: one for the
2478      * regexp, one for the 'negated' value, one for the precedence,
2479      * one for the classifier, and one for the terminating NULL
2480      */
2481     c->argv = pcalloc(c->pool, ((argc + 5) * sizeof(void *)));
2482 
2483     /* Capture the config_rec's argv pointer for doing the by-hand
2484      * population.
2485      */
2486     argv = c->argv;
2487 
2488     /* Copy in the regexp. */
2489     *argv = pcalloc(c->pool, sizeof(pr_regex_t *));
2490     *((pr_regex_t **) *argv++) = pre;
2491 
2492     /* Copy in the 'negated' flag */
2493     *argv = pcalloc(c->pool, sizeof(unsigned char));
2494     *((unsigned char *) *argv++) = negated;
2495 
2496     /* Copy in the precedence. */
2497     *argv = pcalloc(c->pool, sizeof(unsigned int));
2498     *((unsigned int *) *argv++) = precedence;
2499 
2500     /* Copy in the expression classifier */
2501     *argv++ = pstrdup(c->pool, cmd->argv[2]);
2502 
2503     /* now, copy in the expression arguments */
2504     if (argc && acl) {
2505       while (argc-- > 0) {
2506         *argv++ = pstrdup(c->pool, *((char **) acl->elts));
2507         acl->elts = ((char **) acl->elts) + 1;
2508       }
2509     }
2510 
2511     /* don't forget the terminating NULL */
2512     *argv = NULL;
2513   }
2514 
2515   c->flags |= CF_MERGEDOWN_MULTI;
2516   return PR_HANDLED(cmd);
2517 
2518 #else /* no regular expression support at the moment */
2519   CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The HideFiles directive cannot be "
2520     "used on this system, as you do not have POSIX compliant regex support",
2521     NULL));
2522 #endif
2523 }
2524 
set_hidenoaccess(cmd_rec * cmd)2525 MODRET set_hidenoaccess(cmd_rec *cmd) {
2526   int bool = -1;
2527   config_rec *c = NULL;
2528 
2529   CHECK_ARGS(cmd, 1);
2530   CHECK_CONF(cmd, CONF_ANON|CONF_DIR);
2531 
2532   bool = get_boolean(cmd, 1);
2533   if (bool == -1) {
2534     CONF_ERROR(cmd, "expected Boolean parameter");
2535   }
2536 
2537   c = add_config_param(cmd->argv[0], 1, NULL);
2538   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
2539   *((unsigned char *) c->argv[0]) = bool;
2540   c->flags |= CF_MERGEDOWN;
2541 
2542   return PR_HANDLED(cmd);
2543 }
2544 
set_hideuser(cmd_rec * cmd)2545 MODRET set_hideuser(cmd_rec *cmd) {
2546   config_rec *c = NULL;
2547   char *user = NULL;
2548   int inverted = FALSE;
2549 
2550   CHECK_ARGS(cmd, 1);
2551   CHECK_CONF(cmd, CONF_ANON|CONF_DIR);
2552 
2553   user = cmd->argv[1];
2554   if (*user == '!') {
2555     inverted = TRUE;
2556     user++;
2557   }
2558 
2559   c = add_config_param(cmd->argv[0], 2, NULL, NULL);
2560   c->argv[0] = pstrdup(c->pool, user);
2561   c->argv[1] = pcalloc(c->pool, sizeof(int));
2562   *((int *) c->argv[1]) = inverted;
2563 
2564   c->flags |= CF_MERGEDOWN;
2565   return PR_HANDLED(cmd);
2566 }
2567 
set_hidegroup(cmd_rec * cmd)2568 MODRET set_hidegroup(cmd_rec *cmd) {
2569   config_rec *c = NULL;
2570   char *group = NULL;
2571   int inverted = FALSE;
2572 
2573   CHECK_ARGS(cmd, 1);
2574   CHECK_CONF(cmd, CONF_ANON|CONF_DIR);
2575 
2576   group = cmd->argv[1];
2577   if (*group == '!') {
2578     inverted = TRUE;
2579     group++;
2580   }
2581 
2582   c = add_config_param(cmd->argv[0], 2, NULL, NULL);
2583   c->argv[0] = pstrdup(c->pool, group);
2584   c->argv[1] = pcalloc(c->pool, sizeof(int));
2585   *((int *) c->argv[1]) = inverted;
2586 
2587   c->flags |= CF_MERGEDOWN;
2588   return PR_HANDLED(cmd);
2589 }
2590 
add_groupowner(cmd_rec * cmd)2591 MODRET add_groupowner(cmd_rec *cmd) {
2592   config_rec *c = NULL;
2593 
2594   CHECK_ARGS(cmd, 1);
2595   CHECK_CONF(cmd, CONF_ANON|CONF_DIR|CONF_DYNDIR);
2596 
2597   c = add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
2598   c->flags |= CF_MERGEDOWN;
2599 
2600   return PR_HANDLED(cmd);
2601 }
2602 
add_userowner(cmd_rec * cmd)2603 MODRET add_userowner(cmd_rec *cmd) {
2604   config_rec *c = NULL;
2605 
2606   CHECK_ARGS(cmd, 1);
2607   CHECK_CONF(cmd, CONF_ANON|CONF_DIR);
2608 
2609   c = add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
2610   c->flags |= CF_MERGEDOWN;
2611 
2612   return PR_HANDLED(cmd);
2613 }
2614 
set_allowoverride(cmd_rec * cmd)2615 MODRET set_allowoverride(cmd_rec *cmd) {
2616   int bool = -1;
2617   config_rec *c = NULL;
2618   unsigned int precedence = 0;
2619 
2620   int ctxt = (cmd->config && cmd->config->config_type != CONF_PARAM ?
2621      cmd->config->config_type : cmd->server->config_type ?
2622      cmd->server->config_type : CONF_ROOT);
2623 
2624   /* This directive must have either 1 argument; the 3 arguments format is
2625    * now deprecated.
2626    */
2627   if (cmd->argc-1 == 3) {
2628     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "Please use mod_ifsession for "
2629       "per-user/group/class conditional configuration", NULL));
2630   }
2631 
2632   CHECK_ARGS(cmd, 1);
2633   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|CONF_DIR);
2634 
2635   bool = get_boolean(cmd, 1);
2636   if (bool == -1) {
2637     CONF_ERROR(cmd, "expected Boolean parameter");
2638   }
2639 
2640   /* Set the precedence for this config_rec based on its configuration
2641    * context.
2642    */
2643   if (ctxt & CONF_GLOBAL) {
2644     precedence = 1;
2645 
2646   /* These will never appear simultaneously */
2647   } else if (ctxt & CONF_ROOT || ctxt & CONF_VIRTUAL) {
2648     precedence = 2;
2649 
2650   } else if (ctxt & CONF_ANON) {
2651     precedence = 3;
2652 
2653   } else if (ctxt & CONF_DIR) {
2654     precedence = 4;
2655   }
2656 
2657   c = add_config_param(cmd->argv[0], 2, NULL, NULL);
2658   c->argv[0] = pcalloc(c->pool, sizeof(int));
2659   *((int *) c->argv[0]) = bool;
2660   c->argv[1] = pcalloc(c->pool, sizeof(unsigned int));
2661   *((unsigned int *) c->argv[1]) = precedence;
2662   c->flags |= CF_MERGEDOWN_MULTI;
2663 
2664   return PR_HANDLED(cmd);
2665 }
2666 
end_directory(cmd_rec * cmd)2667 MODRET end_directory(cmd_rec *cmd) {
2668   int empty_ctxt = FALSE;
2669 
2670   if (cmd->argc > 1) {
2671     CONF_ERROR(cmd, "wrong number of parameters");
2672   }
2673 
2674   CHECK_CONF(cmd, CONF_DIR);
2675 
2676   pr_parser_config_ctxt_close(&empty_ctxt);
2677 
2678   if (empty_ctxt) {
2679     pr_log_debug(DEBUG3, "%s: ignoring empty section", (char *) cmd->argv[0]);
2680   }
2681 
2682   return PR_HANDLED(cmd);
2683 }
2684 
add_anonymous(cmd_rec * cmd)2685 MODRET add_anonymous(cmd_rec *cmd) {
2686   config_rec *c = NULL;
2687   char *dir;
2688 
2689   CHECK_ARGS(cmd, 1);
2690   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
2691 
2692   dir = cmd->argv[1];
2693 
2694   if (*dir != '/' && *dir != '~') {
2695     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "(", dir, ") absolute pathname "
2696       "required", NULL));
2697   }
2698 
2699   if (strchr(dir, '*')) {
2700     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "(", dir, ") wildcards not allowed "
2701       "in pathname", NULL));
2702   }
2703 
2704   if (strncmp(dir, "/", 2) == 0) {
2705     CONF_ERROR(cmd, "'/' not permitted for anonymous root directory");
2706   }
2707 
2708   if (*(dir+strlen(dir)-1) != '/') {
2709     dir = pstrcat(cmd->tmp_pool, dir, "/", NULL);
2710   }
2711 
2712   if (dir == NULL) {
2713     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, cmd->argv[1], ": ",
2714       strerror(errno), NULL));
2715   }
2716 
2717   c = pr_parser_config_ctxt_open(dir);
2718 
2719   c->config_type = CONF_ANON;
2720   return PR_HANDLED(cmd);
2721 }
2722 
end_anonymous(cmd_rec * cmd)2723 MODRET end_anonymous(cmd_rec *cmd) {
2724   int empty_ctxt = FALSE;
2725 
2726   if (cmd->argc > 1) {
2727     CONF_ERROR(cmd, "wrong number of parameters");
2728   }
2729 
2730   CHECK_CONF(cmd, CONF_ANON);
2731 
2732   pr_parser_config_ctxt_close(&empty_ctxt);
2733 
2734   if (empty_ctxt) {
2735     pr_log_debug(DEBUG3, "%s: ignoring empty section", (char *) cmd->argv[0]);
2736   }
2737 
2738   return PR_HANDLED(cmd);
2739 }
2740 
add_class(cmd_rec * cmd)2741 MODRET add_class(cmd_rec *cmd) {
2742   CHECK_ARGS(cmd, 1);
2743   CHECK_CONF(cmd, CONF_ROOT);
2744 
2745   if (pr_class_open(main_server->pool, cmd->argv[1]) < 0) {
2746     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error creating <Class ",
2747       cmd->argv[1], ">: ", strerror(errno), NULL));
2748   }
2749 
2750   return PR_HANDLED(cmd);
2751 }
2752 
end_class(cmd_rec * cmd)2753 MODRET end_class(cmd_rec *cmd) {
2754   if (cmd->argc > 1) {
2755     CONF_ERROR(cmd, "wrong number of parameters");
2756   }
2757 
2758   CHECK_CONF(cmd, CONF_CLASS);
2759 
2760   if (pr_class_close() < 0) {
2761     pr_log_pri(PR_LOG_WARNING, "warning: empty <Class> definition");
2762   }
2763 
2764   return PR_HANDLED(cmd);
2765 }
2766 
add_global(cmd_rec * cmd)2767 MODRET add_global(cmd_rec *cmd) {
2768   config_rec *c = NULL;
2769 
2770   if (cmd->argc-1 != 0) {
2771     CONF_ERROR(cmd, "Too many parameters");
2772   }
2773 
2774   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
2775 
2776   c = pr_parser_config_ctxt_open(cmd->argv[0]);
2777   c->config_type = CONF_GLOBAL;
2778 
2779   return PR_HANDLED(cmd);
2780 }
2781 
end_global(cmd_rec * cmd)2782 MODRET end_global(cmd_rec *cmd) {
2783   int empty_ctxt = FALSE;
2784 
2785   if (cmd->argc > 1) {
2786     CONF_ERROR(cmd, "wrong number of parameters");
2787   }
2788 
2789   CHECK_CONF(cmd, CONF_GLOBAL);
2790 
2791   pr_parser_config_ctxt_close(&empty_ctxt);
2792 
2793   if (empty_ctxt) {
2794     pr_log_debug(DEBUG3, "%s: ignoring empty section", (char *) cmd->argv[0]);
2795   }
2796 
2797   return PR_HANDLED(cmd);
2798 }
2799 
add_limit(cmd_rec * cmd)2800 MODRET add_limit(cmd_rec *cmd) {
2801   register unsigned int i;
2802   config_rec *c = NULL;
2803   int cargc, have_cdup = FALSE, have_xcup = FALSE, have_mkd = FALSE,
2804     have_xmkd = FALSE, have_pwd = FALSE, have_xpwd = FALSE, have_rmd = FALSE,
2805     have_xrmd = FALSE;
2806   void **cargv, **elts;
2807   array_header *list;
2808 
2809   if (cmd->argc < 2) {
2810     CONF_ERROR(cmd, "directive requires one or more commands");
2811   }
2812 
2813   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_DIR|CONF_ANON|CONF_DYNDIR|CONF_GLOBAL);
2814 
2815   c = pr_parser_config_ctxt_open("Limit");
2816   c->config_type = CONF_LIMIT;
2817   cargc = cmd->argc;
2818   cargv = cmd->argv;
2819 
2820   list = make_array(c->pool, c->argc + 1, sizeof(void *));
2821 
2822   while (cargc-- && *(++cargv)) {
2823     char *ent = NULL, *str;
2824 
2825     str = pstrdup(cmd->tmp_pool, *((char **) cargv));
2826     while ((ent = pr_str_get_token(&str, ",")) != NULL) {
2827       pr_signals_handle();
2828 
2829       if (*ent) {
2830         *((char **) push_array(list)) = pstrdup(c->pool, ent);
2831       }
2832     }
2833   }
2834 
2835   /* Now iterate though the list, looking for the following commands:
2836    *
2837    *  CDUP/XCUP
2838    *  MKD/XMKD
2839    *  PWD/XPWD
2840    *  RMD/XRMD
2841    *
2842    * If we see one of these without its counterpart, automatically add
2843    * the counterpart (see Bug#3077).
2844    */
2845 
2846   elts = list->elts;
2847   for (i = 0; i < list->nelts; i++) {
2848     if (strcasecmp(elts[i], C_CDUP) == 0) {
2849       have_cdup = TRUE;
2850 
2851     } else if (strcasecmp(elts[i], C_XCUP) == 0) {
2852       have_xcup = TRUE;
2853 
2854     } else if (strcasecmp(elts[i], C_MKD) == 0) {
2855       have_mkd = TRUE;
2856 
2857     } else if (strcasecmp(elts[i], C_XMKD) == 0) {
2858       have_xmkd = TRUE;
2859 
2860     } else if (strcasecmp(elts[i], C_PWD) == 0) {
2861       have_pwd = TRUE;
2862 
2863     } else if (strcasecmp(elts[i], C_XPWD) == 0) {
2864       have_xpwd = TRUE;
2865 
2866     } else if (strcasecmp(elts[i], C_RMD) == 0) {
2867       have_rmd = TRUE;
2868 
2869     } else if (strcasecmp(elts[i], C_XRMD) == 0) {
2870       have_xrmd = TRUE;
2871     }
2872   }
2873 
2874   if (have_cdup && !have_xcup) {
2875     *((char **) push_array(list)) = pstrdup(c->pool, C_XCUP);
2876   }
2877 
2878   if (!have_cdup && have_xcup) {
2879     *((char **) push_array(list)) = pstrdup(c->pool, C_CDUP);
2880   }
2881 
2882   if (have_mkd && !have_xmkd) {
2883     *((char **) push_array(list)) = pstrdup(c->pool, C_XMKD);
2884   }
2885 
2886   if (!have_mkd && have_xmkd) {
2887     *((char **) push_array(list)) = pstrdup(c->pool, C_MKD);
2888   }
2889 
2890   if (have_pwd && !have_xpwd) {
2891     *((char **) push_array(list)) = pstrdup(c->pool, C_XPWD);
2892   }
2893 
2894   if (!have_pwd && have_xpwd) {
2895     *((char **) push_array(list)) = pstrdup(c->pool, C_PWD);
2896   }
2897 
2898   if (have_rmd && !have_xrmd) {
2899     *((char **) push_array(list)) = pstrdup(c->pool, C_XRMD);
2900   }
2901 
2902   if (!have_rmd && have_xrmd) {
2903     *((char **) push_array(list)) = pstrdup(c->pool, C_RMD);
2904   }
2905 
2906   c->argc = list->nelts;
2907   c->argv = list->elts;
2908 
2909   return PR_HANDLED(cmd);
2910 }
2911 
set_order(cmd_rec * cmd)2912 MODRET set_order(cmd_rec *cmd) {
2913   int order = -1;
2914   char *arg = "";
2915   config_rec *c = NULL;
2916 
2917   if (cmd->argc != 2 &&
2918       cmd->argc != 3) {
2919     CONF_ERROR(cmd, "wrong number of parameters");
2920   }
2921   CHECK_CONF(cmd, CONF_LIMIT);
2922 
2923   if (cmd->argc == 2) {
2924     arg = cmd->argv[1];
2925 
2926   } else {
2927     /* Concatenate our parameters. */
2928     arg = pstrcat(cmd->tmp_pool, arg, (char *) cmd->argv[1],
2929       (char *) cmd->argv[2], NULL);
2930   }
2931 
2932   if (strcasecmp(arg, "allow,deny") == 0) {
2933     order = ORDER_ALLOWDENY;
2934 
2935   } else if (strcasecmp(arg, "deny,allow") == 0) {
2936     order = ORDER_DENYALLOW;
2937 
2938   } else {
2939     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", arg, "': invalid argument",
2940       NULL));
2941   }
2942 
2943   c = add_config_param(cmd->argv[0], 1, NULL);
2944   c->argv[0] = pcalloc(c->pool, sizeof(int));
2945   *((int *) c->argv[0]) = order;
2946 
2947   return PR_HANDLED(cmd);
2948 }
2949 
set_allowdenyusergroupclass(cmd_rec * cmd)2950 MODRET set_allowdenyusergroupclass(cmd_rec *cmd) {
2951   config_rec *c;
2952   void **argv;
2953   unsigned int argc;
2954   int eval_type;
2955   array_header *acl = NULL;
2956 
2957   CHECK_CONF(cmd, CONF_LIMIT);
2958 
2959   if (cmd->argc < 2) {
2960     CONF_ERROR(cmd, "wrong number of parameters");
2961   }
2962 
2963   /* For AllowClass/DenyClass and AllowUser/DenyUser, the default expression
2964    * type is "or".
2965    */
2966   if (strncmp(cmd->argv[0], "AllowClass", 11) == 0 ||
2967       strncmp(cmd->argv[0], "AllowUser", 10) == 0 ||
2968       strncmp(cmd->argv[0], "DenyClass", 10) == 0 ||
2969       strncmp(cmd->argv[0], "DenyUser", 9) == 0) {
2970     eval_type = PR_EXPR_EVAL_OR;
2971 
2972   /* For AllowGroup and DenyGroup, the default expression type is "and". */
2973   } else {
2974     eval_type = PR_EXPR_EVAL_AND;
2975   }
2976 
2977   if (cmd->argc > 2) {
2978     /* Check the first parameter to see if it is an evaluation modifier:
2979      * "and", "or", or "regex".
2980      */
2981     if (strcasecmp(cmd->argv[1], "AND") == 0) {
2982       eval_type = PR_EXPR_EVAL_AND;
2983       argc = cmd->argc-2;
2984       argv = cmd->argv;
2985 
2986     } else if (strcasecmp(cmd->argv[1], "OR") == 0) {
2987       eval_type = PR_EXPR_EVAL_OR;
2988       argc = cmd->argc-2;
2989       argv = cmd->argv+1;
2990 
2991     } else if (strcasecmp(cmd->argv[1], "regex") == 0) {
2992 #ifdef PR_USE_REGEX
2993       pr_regex_t *pre;
2994       int res;
2995 
2996       if (cmd->argc != 3) {
2997         CONF_ERROR(cmd, "wrong number of parameters");
2998       }
2999 
3000       pre = pr_regexp_alloc(&core_module);
3001 
3002       res = pr_regexp_compile_posix(pre, cmd->argv[2], REG_EXTENDED|REG_NOSUB);
3003       if (res != 0) {
3004         char errstr[200] = {'\0'};
3005 
3006         pr_regexp_error(res, pre, errstr, sizeof(errstr));
3007         pr_regexp_free(NULL, pre);
3008 
3009         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", (char *) cmd->argv[2],
3010           "' failed regex compilation: ", errstr, NULL));
3011       }
3012 
3013       c = add_config_param(cmd->argv[0], 2, NULL, NULL);
3014       c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
3015       *((unsigned char *) c->argv[0]) = PR_EXPR_EVAL_REGEX;
3016       c->argv[1] = (void *) pre;
3017       c->flags |= CF_MERGEDOWN_MULTI;
3018 
3019       return PR_HANDLED(cmd);
3020 #else
3021       CONF_ERROR(cmd, "The 'regex' parameter cannot be used on this system, "
3022         "as you do not have POSIX compliant regex support");
3023 #endif /* regex support */
3024 
3025     } else {
3026       argc = cmd->argc-1;
3027       argv = cmd->argv;
3028     }
3029 
3030   } else {
3031     argc = cmd->argc-1;
3032     argv = cmd->argv;
3033   }
3034 
3035   acl = pr_expr_create(cmd->tmp_pool, &argc, (char **) argv);
3036   if (acl == NULL) {
3037     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error creating expression: ",
3038       strerror(errno), NULL));
3039   }
3040 
3041   c = add_config_param(cmd->argv[0], 0);
3042 
3043   c->argc = acl->nelts + 1;
3044   c->argv = pcalloc(c->pool, (c->argc + 1) * sizeof(void *));
3045 
3046   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
3047   *((unsigned char *) c->argv[0]) = eval_type;
3048 
3049   argv = &(c->argv[1]);
3050 
3051   while (acl->nelts-- > 0) {
3052     pr_signals_handle();
3053 
3054     *argv++ = pstrdup(c->pool, *((char **) acl->elts));
3055     acl->elts = ((char **) acl->elts) + 1;
3056   }
3057 
3058   *argv = NULL;
3059 
3060   c->flags |= CF_MERGEDOWN_MULTI;
3061   return PR_HANDLED(cmd);
3062 }
3063 
set_allowdeny(cmd_rec * cmd)3064 MODRET set_allowdeny(cmd_rec *cmd) {
3065   int argc;
3066   void **argv;
3067   pr_netacl_t **aclargv;
3068   array_header *list;
3069   config_rec *c;
3070 
3071   CHECK_CONF(cmd, CONF_LIMIT);
3072 
3073   /* Syntax: allow [from] [all|none]|host|network[,...] */
3074   list = make_array(cmd->tmp_pool, cmd->argc, sizeof(pr_netacl_t *));
3075   argc = cmd->argc-1;
3076   argv = cmd->argv;
3077 
3078   c = add_config_param(cmd->argv[0], 0);
3079 
3080   /* Skip optional "from" keyword. The '!' character is allowed in front of a
3081    * hostmask or IP, but NOT in front of "ALL" or "NONE".
3082    */
3083 
3084   while (argc && *(argv+1)) {
3085     if (strcasecmp("from", *(((char **) argv) + 1)) == 0) {
3086       argv++;
3087       argc--;
3088       continue;
3089 
3090     } else if (strcasecmp("!all", *(((char **) argv) + 1)) == 0 ||
3091                strcasecmp("!none", *(((char **) argv) + 1)) == 0) {
3092       CONF_ERROR(cmd, "the ! negation operator cannot be used with ALL/NONE");
3093 
3094     } else if (strcasecmp("all", *(argv+1)) == 0 ||
3095                strcasecmp("none", *(argv+1)) == 0) {
3096       *((pr_netacl_t **) push_array(list)) =
3097         pr_netacl_create(c->pool, *(argv+1));
3098       argc = 0;
3099     }
3100 
3101     break;
3102   }
3103 
3104   /* Parse any other/remaining rules. */
3105   while (argc-- && *(++argv)) {
3106     char *ent = NULL;
3107     char *s = pstrdup(cmd->tmp_pool, *argv);
3108 
3109     /* Parse the string into comma-delimited entries */
3110     while ((ent = pr_str_get_token(&s, ",")) != NULL) {
3111       if (*ent) {
3112         pr_netacl_t *acl;
3113 
3114         if (strcasecmp(ent, "all") == 0 ||
3115             strcasecmp(ent, "none") == 0) {
3116           list->nelts = 0;
3117           argc = 0;
3118           break;
3119         }
3120 
3121         acl = pr_netacl_create(c->pool, ent);
3122         if (acl == NULL) {
3123           CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "bad ACL definition '",
3124             ent, "': ", strerror(errno), NULL));
3125         }
3126 
3127         pr_trace_msg("netacl", 9, "'%s' parsed into netacl '%s'", ent,
3128           pr_netacl_get_str(cmd->tmp_pool, acl));
3129 
3130         *((pr_netacl_t **) push_array(list)) = acl;
3131       }
3132     }
3133   }
3134 
3135   if (!list->nelts)
3136     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "syntax: ", cmd->argv[0],
3137       " [from] [all|none]|host|network[,...]", NULL));
3138 
3139   c->argc = list->nelts;
3140   c->argv = pcalloc(c->pool, (c->argc+1) * sizeof(pr_netacl_t *));
3141   aclargv = (pr_netacl_t **) c->argv;
3142 
3143   while (list->nelts--) {
3144     *aclargv++ = *((pr_netacl_t **) list->elts);
3145     list->elts = ((pr_netacl_t **) list->elts) + 1;
3146   }
3147   *aclargv = NULL;
3148 
3149   return PR_HANDLED(cmd);
3150 }
3151 
set_denyall(cmd_rec * cmd)3152 MODRET set_denyall(cmd_rec *cmd) {
3153   config_rec *c = NULL;
3154 
3155   if (cmd->argc > 1) {
3156     CONF_ERROR(cmd, "wrong number of parameters");
3157   }
3158 
3159   CHECK_CONF(cmd, CONF_LIMIT|CONF_ANON|CONF_DIR|CONF_DYNDIR);
3160 
3161   c = add_config_param(cmd->argv[0], 1, NULL);
3162   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
3163   *((unsigned char *) c->argv[0]) = TRUE;
3164 
3165   return PR_HANDLED(cmd);
3166 }
3167 
set_allowall(cmd_rec * cmd)3168 MODRET set_allowall(cmd_rec *cmd) {
3169   config_rec *c = NULL;
3170 
3171   if (cmd->argc > 1) {
3172     CONF_ERROR(cmd, "wrong number of parameters");
3173   }
3174 
3175   CHECK_CONF(cmd, CONF_LIMIT|CONF_ANON|CONF_DIR|CONF_DYNDIR);
3176 
3177   c = add_config_param(cmd->argv[0], 1, NULL);
3178   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
3179   *((unsigned char *) c->argv[0]) = TRUE;
3180 
3181   return PR_HANDLED(cmd);
3182 }
3183 
set_authorder(cmd_rec * cmd)3184 MODRET set_authorder(cmd_rec *cmd) {
3185   register unsigned int i = 0;
3186   config_rec *c = NULL;
3187   array_header *module_list = NULL;
3188 
3189   CHECK_ARGS(cmd, 1);
3190   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
3191 
3192   /* Check to see if the directive has already been set */
3193   if (find_config(cmd->server->conf, CONF_PARAM, cmd->argv[0], FALSE)) {
3194     CONF_ERROR(cmd, "AuthOrder has already been configured");
3195   }
3196 
3197   c = add_config_param(cmd->argv[0], 1, NULL);
3198   module_list = make_array(c->pool, 0, sizeof(char *));
3199 
3200   for (i = 1; i < cmd->argc; i++) {
3201     *((char **) push_array(module_list)) = pstrdup(c->pool, cmd->argv[i]);
3202   }
3203   c->argv[0] = (void *) module_list;
3204 
3205   return PR_HANDLED(cmd);
3206 }
3207 
end_limit(cmd_rec * cmd)3208 MODRET end_limit(cmd_rec *cmd) {
3209   int empty_ctxt = FALSE;
3210 
3211   if (cmd->argc > 1) {
3212     CONF_ERROR(cmd, "wrong number of parameters");
3213   }
3214 
3215   CHECK_CONF(cmd, CONF_LIMIT);
3216 
3217   pr_parser_config_ctxt_close(&empty_ctxt);
3218 
3219   if (empty_ctxt) {
3220     pr_log_debug(DEBUG3, "%s: ignoring empty section", (char *) cmd->argv[0]);
3221   }
3222 
3223   return PR_HANDLED(cmd);
3224 }
3225 
set_ignorehidden(cmd_rec * cmd)3226 MODRET set_ignorehidden(cmd_rec *cmd) {
3227   int bool = -1;
3228   config_rec *c = NULL;
3229 
3230   CHECK_ARGS(cmd, 1);
3231   CHECK_CONF(cmd, CONF_LIMIT);
3232 
3233   bool = get_boolean(cmd, 1);
3234   if (bool == -1) {
3235     CONF_ERROR(cmd, "expected Boolean parameter");
3236   }
3237 
3238   c = add_config_param(cmd->argv[0], 1, NULL);
3239   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
3240   *((unsigned char *) c->argv[0]) = bool;
3241 
3242   return PR_HANDLED(cmd);
3243 }
3244 
3245 /* usage: DisplayChdir path [on|off] */
set_displaychdir(cmd_rec * cmd)3246 MODRET set_displaychdir(cmd_rec *cmd) {
3247   config_rec *c = NULL;
3248   int bool = FALSE;
3249 
3250   if (cmd->argc-1 < 1 ||
3251       cmd->argc-1 > 2) {
3252     CONF_ERROR(cmd, "wrong number of parameters");
3253   }
3254 
3255   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|CONF_DIR);
3256 
3257   if (cmd->argc-1 == 2) {
3258     bool = get_boolean(cmd, 2);
3259     if (bool < 0) {
3260       CONF_ERROR(cmd, "expected Boolean parameter");
3261     }
3262   }
3263 
3264   c = add_config_param(cmd->argv[0], 2, NULL, NULL);
3265   c->argv[0] = pstrdup(c->pool, cmd->argv[1]);
3266   c->argv[1] = pcalloc(c->pool, sizeof(int));
3267   *((int *) c->argv[1]) = bool;
3268 
3269   c->flags |= CF_MERGEDOWN;
3270   return PR_HANDLED(cmd);
3271 }
3272 
set_displayconnect(cmd_rec * cmd)3273 MODRET set_displayconnect(cmd_rec *cmd) {
3274   CHECK_ARGS(cmd, 1);
3275   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
3276 
3277   add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
3278   return PR_HANDLED(cmd);
3279 }
3280 
set_displayquit(cmd_rec * cmd)3281 MODRET set_displayquit(cmd_rec *cmd) {
3282   config_rec *c = NULL;
3283 
3284   CHECK_ARGS(cmd, 1);
3285   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
3286 
3287   c = add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
3288   c->flags |= CF_MERGEDOWN;
3289 
3290   return PR_HANDLED(cmd);
3291 }
3292 
add_virtualhost(cmd_rec * cmd)3293 MODRET add_virtualhost(cmd_rec *cmd) {
3294   const char *name, *addr_ipstr;
3295   server_rec *s = NULL;
3296   const pr_netaddr_t *addr = NULL;
3297   array_header *addrs = NULL;
3298   unsigned int addr_flags = PR_NETADDR_GET_ADDR_FL_INCL_DEVICE;
3299 
3300   if (cmd->argc-1 < 1) {
3301     CONF_ERROR(cmd, "wrong number of parameters");
3302   }
3303   CHECK_CONF(cmd, CONF_ROOT);
3304 
3305   name = cmd->argv[1];
3306   s = pr_parser_server_ctxt_open(name);
3307   if (s == NULL) {
3308     CONF_ERROR(cmd, "unable to create virtual server configuration");
3309   }
3310 
3311   /* It's possible for a server to have multiple IP addresses (e.g. a DNS
3312    * name that has both A and AAAA records).  We need to handle that case
3313    * here by looking up all of a server's addresses, and making sure there
3314    * are server_recs for each one.
3315    */
3316 
3317   addr = pr_netaddr_get_addr2(cmd->tmp_pool, name, &addrs, addr_flags);
3318   if (addr == NULL) {
3319     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error resolving '", name, "': ",
3320       strerror(errno), NULL));
3321   }
3322 
3323   /* If the given name is a DNS name, automatically add a ServerAlias
3324    * directive.
3325    */
3326   if (pr_netaddr_is_v4(name) == FALSE &&
3327       pr_netaddr_is_v6(name) == FALSE) {
3328     add_config_param_str("ServerAlias", 1, name);
3329   }
3330 
3331   addr_ipstr = pr_netaddr_get_ipstr(addr);
3332 
3333   if (addrs != NULL) {
3334     register unsigned int i;
3335     pr_netaddr_t **elts = addrs->elts;
3336 
3337     /* For every additional address, implicitly add a bind record. */
3338     for (i = 0; i < addrs->nelts; i++) {
3339       const char *ipstr;
3340 
3341       ipstr = pr_netaddr_get_ipstr(elts[i]);
3342 
3343       /* Skip duplicate addresses. */
3344       if (strcmp(addr_ipstr, ipstr) == 0) {
3345         continue;
3346       }
3347 
3348       add_config_param_str("_bind_", 1, ipstr);
3349     }
3350   }
3351 
3352   /* Handle multiple addresses in a <VirtualHost> directive.  We do
3353    * this by adding bind directives to the server_rec created for the
3354    * first address.
3355    */
3356   if (cmd->argc-1 > 1) {
3357     register unsigned int i;
3358 
3359     for (i = 2; i < cmd->argc; i++) {
3360       addrs = NULL;
3361 
3362       name = cmd->argv[i];
3363       addr = pr_netaddr_get_addr2(cmd->tmp_pool, name, &addrs, addr_flags);
3364       if (addr == NULL) {
3365         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error resolving '", name, "': ",
3366           strerror(errno), NULL));
3367       }
3368 
3369       /* If the given name is a DNS name, automatically add a ServerAlias
3370        * directive.
3371        */
3372       if (pr_netaddr_is_v4(name) == FALSE &&
3373           pr_netaddr_is_v6(name) == FALSE) {
3374         add_config_param_str("ServerAlias", 1, name);
3375       }
3376 
3377       addr_ipstr = pr_netaddr_get_ipstr(addr);
3378       add_config_param_str("_bind_", 1, addr_ipstr);
3379 
3380       if (addrs != NULL) {
3381         register unsigned int j;
3382         pr_netaddr_t **elts = addrs->elts;
3383 
3384         /* For every additional address, implicitly add a bind record. */
3385         for (j = 0; j < addrs->nelts; j++) {
3386           const char *ipstr;
3387 
3388           ipstr = pr_netaddr_get_ipstr(elts[j]);
3389 
3390           /* Skip duplicate addresses. */
3391           if (strcmp(addr_ipstr, ipstr) == 0) {
3392             continue;
3393           }
3394 
3395           add_config_param_str("_bind_", 1, ipstr);
3396         }
3397       }
3398     }
3399   }
3400 
3401   return PR_HANDLED(cmd);
3402 }
3403 
end_virtualhost(cmd_rec * cmd)3404 MODRET end_virtualhost(cmd_rec *cmd) {
3405   server_rec *s = NULL, *next_s = NULL;
3406   const pr_netaddr_t *addr = NULL;
3407   const char *address = NULL;
3408   unsigned int addr_flags = PR_NETADDR_GET_ADDR_FL_INCL_DEVICE;
3409 
3410   if (cmd->argc > 1) {
3411     CONF_ERROR(cmd, "wrong number of parameters");
3412   }
3413 
3414   CHECK_CONF(cmd, CONF_VIRTUAL);
3415 
3416   if (cmd->server->ServerAddress) {
3417     address = cmd->server->ServerAddress;
3418 
3419   } else {
3420     address = pr_netaddr_get_localaddr_str(cmd->tmp_pool);
3421   }
3422 
3423   /* Any additional addresses associated with the configured address have
3424    * already been handled, so we can ignore them here.
3425    */
3426   addr = pr_netaddr_get_addr2(cmd->tmp_pool, address, NULL, addr_flags);
3427   if (addr == NULL) {
3428     /* This bad server context will be removed in fixup_servers(), after
3429      * the parsing has completed, so we need do nothing else here.
3430      */
3431     pr_log_pri(PR_LOG_WARNING,
3432       "warning: unable to determine IP address of '%s'", address);
3433   }
3434 
3435   if (AddressCollisionCheck) {
3436     /* Check if this server's address/port combination is already being used. */
3437     for (s = (server_rec *) server_list->xas_list; addr && s; s = next_s) {
3438       next_s = s->next;
3439 
3440       /* Have to resort to duplicating some of fixup_servers()'s functionality
3441        * here, to do this check The Right Way(tm).
3442        */
3443       if (s != cmd->server) {
3444         const char *serv_addrstr = NULL;
3445         const pr_netaddr_t *serv_addr = NULL;
3446 
3447         if (s->addr) {
3448           serv_addr = s->addr;
3449 
3450         } else {
3451           serv_addrstr = s->ServerAddress ? s->ServerAddress :
3452             pr_netaddr_get_localaddr_str(cmd->tmp_pool);
3453 
3454           serv_addr = pr_netaddr_get_addr2(cmd->tmp_pool, serv_addrstr, NULL,
3455             addr_flags);
3456         }
3457 
3458         if (serv_addr == NULL) {
3459           pr_log_pri(PR_LOG_WARNING,
3460             "warning: unable to determine IP address of '%s'", serv_addrstr);
3461 
3462         } else if (pr_netaddr_cmp(addr, serv_addr) == 0 &&
3463             cmd->server->ServerPort == s->ServerPort) {
3464           config_rec *c;
3465 
3466           /* If this server has a ServerAlias, it means it's a named vhost and
3467            * can be used for name-based virtual hosting.  Which, in turn, means
3468            * that this collision is expected, even wanted.
3469            */
3470           c = find_config(cmd->server->conf, CONF_PARAM, "ServerAlias", FALSE);
3471           if (c == NULL) {
3472             pr_log_pri(PR_LOG_WARNING,
3473               "warning: \"%s\" address/port (%s:%d) already in use by \"%s\"",
3474               cmd->server->ServerName ? cmd->server->ServerName : "ProFTPD",
3475               pr_netaddr_get_ipstr(addr), cmd->server->ServerPort,
3476               s->ServerName ? s->ServerName : "ProFTPD");
3477 
3478             if (xaset_remove(server_list, (xasetmember_t *) cmd->server) == 1) {
3479               destroy_pool(cmd->server->pool);
3480             }
3481           }
3482         }
3483 
3484         continue;
3485       }
3486     }
3487   }
3488 
3489   if (pr_parser_server_ctxt_close() == NULL) {
3490     CONF_ERROR(cmd, "must have matching <VirtualHost> directive");
3491   }
3492 
3493   return PR_HANDLED(cmd);
3494 }
3495 
3496 #ifdef PR_USE_REGEX
regex_filters(cmd_rec * cmd)3497 MODRET regex_filters(cmd_rec *cmd) {
3498   pr_regex_t *allow_regex = NULL, *deny_regex = NULL;
3499 
3500   /* Don't apply the filter checks to passwords (arguments to the PASS
3501    * command).
3502    */
3503   if (strcasecmp(cmd->argv[0], C_PASS) == 0) {
3504     return PR_DECLINED(cmd);
3505   }
3506 
3507   /* Check for an AllowFilter */
3508   allow_regex = get_param_ptr(CURRENT_CONF, "AllowFilter", FALSE);
3509   if (allow_regex != NULL &&
3510       cmd->arg != NULL &&
3511       pr_regexp_exec(allow_regex, cmd->arg, 0, NULL, 0, 0, 0) != 0) {
3512     pr_log_debug(DEBUG2, "'%s %s' denied by AllowFilter", (char *) cmd->argv[0],
3513       cmd->arg);
3514     pr_response_add_err(R_550, _("%s: Forbidden command argument"), cmd->arg);
3515 
3516     pr_cmd_set_errno(cmd, EACCES);
3517     errno = EACCES;
3518     return PR_ERROR(cmd);
3519   }
3520 
3521   /* Check for a DenyFilter */
3522   deny_regex = get_param_ptr(CURRENT_CONF, "DenyFilter", FALSE);
3523   if (deny_regex != NULL &&
3524       cmd->arg != NULL &&
3525       pr_regexp_exec(deny_regex, cmd->arg, 0, NULL, 0, 0, 0) == 0) {
3526     pr_log_debug(DEBUG2, "'%s %s' denied by DenyFilter", (char *) cmd->argv[0],
3527       cmd->arg);
3528     pr_response_add_err(R_550, _("%s: Forbidden command argument"), cmd->arg);
3529 
3530     pr_cmd_set_errno(cmd, EACCES);
3531     errno = EACCES;
3532     return PR_ERROR(cmd);
3533   }
3534 
3535   return PR_DECLINED(cmd);
3536 }
3537 #endif /* regex support */
3538 
core_pre_any(cmd_rec * cmd)3539 MODRET core_pre_any(cmd_rec *cmd) {
3540   unsigned long cmd_delay = 0;
3541   const char *rnfr_path = NULL;
3542 
3543   /* Check for an exceeded MaxCommandRate. */
3544   cmd_delay = core_exceeded_cmd_rate(cmd);
3545   if (cmd_delay > 0) {
3546     struct timeval tv;
3547 
3548     pr_event_generate("core.max-command-rate", NULL);
3549 
3550     pr_log_pri(PR_LOG_NOTICE,
3551       "MaxCommandRate (%lu cmds/%u %s) exceeded, injecting processing delay "
3552       "of %lu ms", core_max_cmds, core_max_cmd_interval,
3553       core_max_cmd_interval == 1 ? "sec" : "secs", cmd_delay);
3554 
3555     pr_trace_msg("command", 8, "MaxCommandRate exceeded, delaying for %lu ms",
3556       cmd_delay);
3557 
3558     tv.tv_sec = (cmd_delay / 1000);
3559     tv.tv_usec = (cmd_delay - (tv.tv_sec * 1000)) * 1000;
3560 
3561     pr_signals_block();
3562     (void) select(0, NULL, NULL, NULL, &tv);
3563     pr_signals_unblock();
3564   }
3565 
3566   /* Make sure that any command immediately following an RNFR command which
3567    * is NOT the RNTO command is rejected (see Bug#3829).
3568    *
3569    * Make exception for the following commands:
3570    *
3571    *  HELP
3572    *  NOOP
3573    *  QUIT
3574    *  STAT
3575    *
3576    *  and RFC 2228 commands.
3577    */
3578   rnfr_path = pr_table_get(session.notes, "mod_core.rnfr-path", NULL);
3579   if (rnfr_path != NULL) {
3580     if (pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) != 0 &&
3581         pr_cmd_cmp(cmd, PR_CMD_HELP_ID) != 0 &&
3582         pr_cmd_cmp(cmd, PR_CMD_NOOP_ID) != 0 &&
3583         pr_cmd_cmp(cmd, PR_CMD_QUIT_ID) != 0 &&
3584         pr_cmd_cmp(cmd, PR_CMD_STAT_ID) != 0) {
3585       int reject_cmd = TRUE;
3586 
3587       /* Perform additional checks if an RFC 2228 auth mechanism (TLS, GSSAPI)
3588        * has been negotiated/used.
3589        */
3590       if (session.rfc2228_mech != NULL) {
3591         if (pr_cmd_cmp(cmd, PR_CMD_CCC_ID) == 0 ||
3592             pr_cmd_cmp(cmd, PR_CMD_CONF_ID) == 0 ||
3593             pr_cmd_cmp(cmd, PR_CMD_ENC_ID) == 0 ||
3594             pr_cmd_cmp(cmd, PR_CMD_MIC_ID) == 0 ||
3595             pr_cmd_cmp(cmd, PR_CMD_PBSZ_ID) == 0 ||
3596             pr_cmd_cmp(cmd, PR_CMD_PROT_ID) == 0) {
3597           reject_cmd = FALSE;
3598         }
3599       }
3600 
3601       if (reject_cmd) {
3602         pr_log_debug(DEBUG3,
3603           "RNFR followed immediately by %s rather than RNTO, rejecting command",
3604           (char *) cmd->argv[0]);
3605         pr_response_add_err(R_501, _("Bad sequence of commands"));
3606 
3607         pr_cmd_set_errno(cmd, EPERM);
3608         errno = EPERM;
3609         return PR_ERROR(cmd);
3610       }
3611     }
3612   }
3613 
3614   return PR_DECLINED(cmd);
3615 }
3616 
core_quit(cmd_rec * cmd)3617 MODRET core_quit(cmd_rec *cmd) {
3618   int flags = PR_DISPLAY_FL_SEND_NOW;
3619 
3620   if (displayquit_fh) {
3621     if (pr_display_fh(displayquit_fh, NULL, R_221, flags) < 0) {
3622       pr_log_debug(DEBUG6, "unable to display DisplayQuit file '%s': %s",
3623         displayquit_fh->fh_path, strerror(errno));
3624     }
3625 
3626     pr_fsio_close(displayquit_fh);
3627     displayquit_fh = NULL;
3628 
3629   } else {
3630     char *display;
3631 
3632     display = get_param_ptr(TOPLEVEL_CONF, "DisplayQuit", FALSE);
3633     if (display) {
3634       if (pr_display_file(display, NULL, R_221, flags) < 0) {
3635         int xerrno = errno;
3636 
3637         pr_log_debug(DEBUG6, "unable to display DisplayQuit file '%s': %s",
3638           display, strerror(xerrno));
3639 
3640         if (xerrno == ENOENT) {
3641           /* No file found?  Send our normal fairwell, then. */
3642           pr_response_send(R_221, "%s", _("Goodbye."));
3643         }
3644       }
3645 
3646     } else {
3647       pr_response_send(R_221, "%s", _("Goodbye."));
3648     }
3649   }
3650 
3651   /* The LOG_CMD handler for QUIT is responsible for actually ending
3652    * the session.
3653    */
3654 
3655   return PR_HANDLED(cmd);
3656 }
3657 
core_log_quit(cmd_rec * cmd)3658 MODRET core_log_quit(cmd_rec *cmd) {
3659 
3660 #ifndef PR_DEVEL_NO_DAEMON
3661   pr_session_disconnect(&core_module, PR_SESS_DISCONNECT_CLIENT_QUIT, NULL);
3662 #endif /* PR_DEVEL_NO_DAEMON */
3663 
3664   /* Even though pr_session_end() does not return, this is necessary to avoid
3665    * compiler warnings.
3666    */
3667   return PR_HANDLED(cmd);
3668 }
3669 
core_pwd(cmd_rec * cmd)3670 MODRET core_pwd(cmd_rec *cmd) {
3671   CHECK_CMD_ARGS(cmd, 1);
3672 
3673   if (!dir_check(cmd->tmp_pool, cmd, cmd->group, session.vwd, NULL)) {
3674     int xerrno = EACCES;
3675 
3676     pr_response_add_err(R_550, "%s: %s", (char *) cmd->argv[0],
3677       strerror(xerrno));
3678 
3679     pr_cmd_set_errno(cmd, xerrno);
3680     errno = xerrno;
3681     return PR_ERROR(cmd);
3682   }
3683 
3684   pr_response_add(R_257, _("\"%s\" is the current directory"),
3685     quote_dir(cmd->tmp_pool, pr_fs_encode_path(cmd->tmp_pool, session.vwd)));
3686 
3687   return PR_HANDLED(cmd);
3688 }
3689 
core_pasv(cmd_rec * cmd)3690 MODRET core_pasv(cmd_rec *cmd) {
3691   unsigned int port = 0;
3692   char *addrstr = NULL, *tmp = NULL;
3693   config_rec *c = NULL;
3694   const pr_netaddr_t *bind_addr;
3695   const char *proto;
3696 
3697   if (session.sf_flags & SF_EPSV_ALL) {
3698     pr_response_add_err(R_500, _("Illegal PASV command, EPSV ALL in effect"));
3699 
3700     pr_cmd_set_errno(cmd, EPERM);
3701     errno = EPERM;
3702     return PR_ERROR(cmd);
3703   }
3704 
3705   CHECK_CMD_ARGS(cmd, 1);
3706 
3707   /* Returning 501 is the best we can do.  It would be nicer if RFC959 allowed
3708    * 550 as a possible response.
3709    */
3710   if (!dir_check(cmd->tmp_pool, cmd, cmd->group, session.cwd, NULL)) {
3711     int xerrno = EPERM;
3712 
3713     pr_log_debug(DEBUG8, "PASV denied by <Limit> configuration");
3714     pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[0],
3715       strerror(xerrno));
3716 
3717     pr_cmd_set_errno(cmd, xerrno);
3718     errno = xerrno;
3719     return PR_ERROR(cmd);
3720   }
3721 
3722   /* If we already have a passive listen data connection open, kill it. */
3723   if (session.d) {
3724     pr_inet_close(session.d->pool, session.d);
3725     session.d = NULL;
3726   }
3727 
3728   if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(session.c->remote_addr)) {
3729 
3730 #ifdef PR_USE_IPV6
3731     if (pr_netaddr_use_ipv6()) {
3732       /* Make sure that the family is NOT IPv6, even though the family of the
3733        * local and remote ends match.  The PASV command cannot be used for
3734        * IPv6 addresses (Bug#3745).
3735        *
3736        * However, SOME clients and ALGs ARE able to properly handle the
3737        * PASV response, even for an IPv6 address.  So we relax this code
3738        * to merely warn about possible incompatibilities, rather than
3739        * rejecting the command outright.
3740        */
3741       if (pr_netaddr_get_family(session.c->local_addr) == AF_INET6) {
3742         pr_log_pri(PR_LOG_INFO,
3743           "sending a PASV response for an IPv6 address '%s'; some FTP clients "
3744           "may have interoperability issues with this response",
3745           pr_netaddr_get_ipstr(session.c->local_addr));
3746         pr_log_pri(PR_LOG_INFO, "%s", "please configure your FTP client "
3747           "to use the IPv6-compatible EPSV/EPRT commands");
3748       }
3749     }
3750 #endif /* PR_USE_IPV6 */
3751 
3752     bind_addr = session.c->local_addr;
3753 
3754   } else {
3755     /* In this scenario, the server has an IPv6 socket, but the remote client
3756      * is an IPv4 (or IPv4-mapped IPv6) peer.
3757      */
3758     bind_addr = pr_netaddr_v6tov4(cmd->pool, session.c->local_addr);
3759   }
3760 
3761   c = find_config(main_server->conf, CONF_PARAM, "PassivePorts", FALSE);
3762   if (c != NULL) {
3763     int pasv_min_port = *((int *) c->argv[0]);
3764     int pasv_max_port = *((int *) c->argv[1]);
3765 
3766     session.d = pr_inet_create_conn_portrange(session.pool, bind_addr,
3767       pasv_min_port, pasv_max_port);
3768     if (session.d == NULL) {
3769       /* If not able to open a passive port in the given range, default to
3770        * normal behavior (using INPORT_ANY), and log the failure.  This
3771        * indicates a too-small range configuration.
3772        */
3773       pr_log_pri(PR_LOG_WARNING,
3774         "unable to find open port in PassivePorts range %d-%d: "
3775         "defaulting to INPORT_ANY (consider defining a larger PassivePorts "
3776         "range)", pasv_min_port, pasv_max_port);
3777     }
3778   }
3779 
3780   /* Open up the connection and pass it back. */
3781   if (session.d == NULL) {
3782     session.d = pr_inet_create_conn(session.pool, -1, bind_addr, INPORT_ANY,
3783       FALSE);
3784   }
3785 
3786   if (session.d == NULL) {
3787     pr_response_add_err(R_425,
3788       _("Unable to build data connection: Internal error"));
3789 
3790     pr_cmd_set_errno(cmd, EINVAL);
3791     errno = EINVAL;
3792     return PR_ERROR(cmd);
3793   }
3794 
3795   /* Make sure that necessary socket options are set on the socket prior
3796    * to the call to listen(2).
3797    */
3798   pr_inet_set_proto_opts(session.pool, session.d, main_server->tcp_mss_len, 0,
3799     IPTOS_THROUGHPUT, 1);
3800   pr_inet_generate_socket_event("core.data-listen", main_server,
3801     session.d->local_addr, session.d->listen_fd);
3802 
3803   pr_inet_set_block(session.pool, session.d);
3804   if (pr_inet_listen(session.pool, session.d, 1, 0) < 0) {
3805     int xerrno = errno;
3806 
3807     pr_response_add_err(R_425, "%s: %s", (char *) cmd->argv[0],
3808       strerror(xerrno));
3809 
3810     pr_cmd_set_errno(cmd, xerrno);
3811     errno = xerrno;
3812     return PR_ERROR(cmd);
3813   }
3814 
3815   session.d->instrm = pr_netio_open(session.pool, PR_NETIO_STRM_DATA,
3816     session.d->listen_fd, PR_NETIO_IO_RD);
3817 
3818   /* Now tell the client our address/port */
3819   port = session.data_port = session.d->local_port;
3820   session.sf_flags |= SF_PASSIVE;
3821 
3822   addrstr = (char *) pr_netaddr_get_ipstr(session.d->local_addr);
3823 
3824   /* Check for a MasqueradeAddress configuration record, and return that
3825    * addr if appropriate.  Note that if TLSMasqueradeAddress is configured AND
3826    * this is an FTPS session, TLSMasqueradeAddress will take precedence;
3827    * see Bug#3862.
3828    */
3829   proto = pr_session_get_protocol(0);
3830   if (strncmp(proto, "ftps", 5) == 0) {
3831     c = find_config(main_server->conf, CONF_PARAM, "TLSMasqueradeAddress",
3832       FALSE);
3833     if (c != NULL) {
3834       addrstr = (char *) pr_netaddr_get_ipstr(c->argv[0]);
3835 
3836     } else {
3837       c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress",
3838         FALSE);
3839       if (c != NULL) {
3840         if (c->argv[0] != NULL) {
3841           addrstr = (char *) pr_netaddr_get_ipstr(c->argv[0]);
3842         }
3843       }
3844     }
3845 
3846   } else {
3847     c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE);
3848     if (c != NULL) {
3849       if (c->argv[0] != NULL) {
3850         addrstr = (char *) pr_netaddr_get_ipstr(c->argv[0]);
3851       }
3852     }
3853   }
3854 
3855   /* Fixup the address string for the PASV response. */
3856   tmp = strrchr(addrstr, ':');
3857   if (tmp) {
3858     addrstr = tmp + 1;
3859   }
3860 
3861   for (tmp = addrstr; *tmp; tmp++) {
3862     if (*tmp == '.') {
3863       *tmp = ',';
3864     }
3865   }
3866 
3867   pr_log_debug(DEBUG1, "Entering Passive Mode (%s,%u,%u).", addrstr,
3868     (port >> 8) & 255, port & 255);
3869 
3870   /* Note: this response is specifically NOT localised because clients
3871    * assume this particular text.  Nice, huh?
3872    */
3873   pr_response_add(R_227, "Entering Passive Mode (%s,%u,%u).", addrstr,
3874     (port >> 8) & 255, port & 255);
3875 
3876   return PR_HANDLED(cmd);
3877 }
3878 
core_port(cmd_rec * cmd)3879 MODRET core_port(cmd_rec *cmd) {
3880   const pr_netaddr_t *listen_addr = NULL, *port_addr = NULL;
3881   char *port_info;
3882 #ifdef PR_USE_IPV6
3883   char buf[INET6_ADDRSTRLEN] = {'\0'};
3884 #else
3885   char buf[INET_ADDRSTRLEN] = {'\0'};
3886 #endif /* PR_USE_IPV6 */
3887   unsigned int h1, h2, h3, h4, p1, p2;
3888   unsigned short port;
3889   int allow_foreign_addr = FALSE, *root_revoke = NULL;
3890   config_rec *c;
3891   const char *proto;
3892 
3893   if (session.sf_flags & SF_EPSV_ALL) {
3894     pr_response_add_err(R_500, _("Illegal PORT command, EPSV ALL in effect"));
3895 
3896     pr_cmd_set_errno(cmd, EPERM);
3897     errno = EPERM;
3898     return PR_ERROR(cmd);
3899   }
3900 
3901   CHECK_CMD_ARGS(cmd, 2);
3902 
3903   /* Returning 501 is the best we can do.  It would be nicer if RFC959 allowed
3904    * 550 as a possible response.
3905    */
3906   if (!dir_check(cmd->tmp_pool, cmd, cmd->group, session.cwd, NULL)) {
3907     int xerrno = EPERM;
3908 
3909     pr_log_debug(DEBUG8, "PORT denied by <Limit> configuration");
3910     pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[0],
3911       strerror(xerrno));
3912 
3913     pr_cmd_set_errno(cmd, xerrno);
3914     errno = xerrno;
3915     return PR_ERROR(cmd);
3916   }
3917 
3918   /* Block active transfers (the PORT command) if RootRevoke is in effect
3919    * and the server's port is below 1024 (binding to the data port in this
3920    * case would require root privs, which will have been dropped).
3921    *
3922    * A RootRevoke value of 0 indicates 'false', 1 indicates 'true', and
3923    * 2 indicates 'NonCompliantActiveTransfer'.  We only block active transfers
3924    * for a RootRevoke value of 1.
3925    */
3926   root_revoke = get_param_ptr(TOPLEVEL_CONF, "RootRevoke", FALSE);
3927   if (root_revoke != NULL &&
3928       *root_revoke == 1 &&
3929       session.c->local_port < 1024) {
3930     pr_log_debug(DEBUG0, "RootRevoke in effect, unable to bind to local "
3931       "port %d for active transfer", session.c->local_port-1);
3932     pr_response_add_err(R_500, _("Unable to service PORT commands"));
3933 
3934     pr_cmd_set_errno(cmd, EPERM);
3935     errno = EPERM;
3936     return PR_ERROR(cmd);
3937   }
3938 
3939   /* Format is h1,h2,h3,h4,p1,p2 (ASCII in network order) */
3940   port_info = cmd->argv[1];
3941   if (sscanf(port_info, "%u,%u,%u,%u,%u,%u", &h1, &h2, &h3, &h4, &p1,
3942       &p2) != 6) {
3943     pr_log_debug(DEBUG2, "PORT '%s' is not syntactically valid", port_info);
3944     pr_response_add_err(R_501, _("Illegal PORT command"));
3945 
3946     pr_cmd_set_errno(cmd, EPERM);
3947     errno = EPERM;
3948     return PR_ERROR(cmd);
3949   }
3950 
3951   if (h1 > 255 || h2 > 255 || h3 > 255 || h4 > 255 || p1 > 255 || p2 > 255 ||
3952       (h1|h2|h3|h4) == 0 || (p1|p2) == 0) {
3953     pr_log_debug(DEBUG2, "PORT '%s' has invalid value(s)", cmd->arg);
3954     pr_response_add_err(R_501, _("Illegal PORT command"));
3955 
3956     pr_cmd_set_errno(cmd, EPERM);
3957     errno = EPERM;
3958     return PR_ERROR(cmd);
3959   }
3960   port = ((p1 << 8) | p2);
3961 
3962 #ifdef PR_USE_IPV6
3963   if (pr_netaddr_use_ipv6()) {
3964     if (pr_netaddr_get_family(session.c->remote_addr) == AF_INET6) {
3965       pr_snprintf(buf, sizeof(buf), "::ffff:%u.%u.%u.%u", h1, h2, h3, h4);
3966 
3967     } else {
3968       pr_snprintf(buf, sizeof(buf), "%u.%u.%u.%u", h1, h2, h3, h4);
3969     }
3970 
3971   } else
3972 #endif /* PR_USE_IPV6 */
3973   pr_snprintf(buf, sizeof(buf), "%u.%u.%u.%u", h1, h2, h3, h4);
3974   buf[sizeof(buf)-1] = '\0';
3975 
3976   port_addr = pr_netaddr_get_addr(cmd->tmp_pool, buf, NULL);
3977   if (port_addr == NULL) {
3978     pr_log_debug(DEBUG1, "error getting sockaddr for '%s': %s", buf,
3979       strerror(errno));
3980     pr_response_add_err(R_501, _("Illegal PORT command"));
3981 
3982     pr_cmd_set_errno(cmd, EPERM);
3983     errno = EPERM;
3984     return PR_ERROR(cmd);
3985   }
3986 
3987   /* If we are NOT listening on an RFC1918 address, BUT the client HAS
3988    * sent us an RFC1918 address in its PORT command (which we know to not be
3989    * routable), then ignore that address, and use the client's remote address.
3990    */
3991   listen_addr = session.c->local_addr;
3992 
3993   proto = pr_session_get_protocol(0);
3994   if (strncmp(proto, "ftps", 5) == 0) {
3995     c = find_config(main_server->conf, CONF_PARAM, "TLSMasqueradeAddress",
3996       FALSE);
3997     if (c != NULL) {
3998       listen_addr = c->argv[0];
3999 
4000     } else {
4001       c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress",
4002         FALSE);
4003       if (c != NULL) {
4004         if (c->argv[0] != NULL) {
4005           listen_addr = c->argv[0];
4006         }
4007       }
4008     }
4009 
4010   } else {
4011     c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE);
4012     if (c != NULL) {
4013       if (c->argv[0] != NULL) {
4014         listen_addr = c->argv[0];
4015       }
4016     }
4017   }
4018 
4019   if (pr_netaddr_is_rfc1918(listen_addr) != TRUE &&
4020       pr_netaddr_is_rfc1918(session.c->remote_addr) != TRUE &&
4021       pr_netaddr_is_rfc1918(port_addr) == TRUE) {
4022     const char *rfc1918_ipstr;
4023 
4024     rfc1918_ipstr = pr_netaddr_get_ipstr(port_addr);
4025     port_addr = pr_netaddr_dup(cmd->tmp_pool, session.c->remote_addr);
4026     pr_log_debug(DEBUG1, "client sent RFC1918 address '%s' in PORT command, "
4027       "ignoring it and using '%s'", rfc1918_ipstr,
4028       pr_netaddr_get_ipstr(port_addr));
4029   }
4030 
4031   pr_netaddr_set_family(&session.data_addr, pr_netaddr_get_family(port_addr));
4032   pr_netaddr_set_port(&session.data_addr, htons(port));
4033 
4034   /* Make sure that the address specified matches the address from which
4035    * the control connection is coming.
4036    */
4037 
4038   c = find_config(TOPLEVEL_CONF, CONF_PARAM, "AllowForeignAddress", FALSE);
4039   if (c != NULL) {
4040     int allowed;
4041 
4042     allowed = *((int *) c->argv[0]);
4043     switch (allowed) {
4044       case TRUE:
4045         allow_foreign_addr = TRUE;
4046         break;
4047 
4048       case FALSE:
4049         break;
4050 
4051       default: {
4052         char *class_name;
4053         const pr_class_t *cls;
4054 
4055         class_name = c->argv[1];
4056         cls = pr_class_find(class_name);
4057         if (cls != NULL) {
4058           if (pr_class_satisfied(cmd->tmp_pool, cls, port_addr) == TRUE) {
4059             allow_foreign_addr = TRUE;
4060 
4061           } else {
4062             pr_log_debug(DEBUG8, "<Class> '%s' not satisfied by foreign "
4063               "address '%s'", class_name, pr_netaddr_get_ipstr(port_addr));
4064           }
4065 
4066         } else {
4067           pr_log_debug(DEBUG8, "<Class> '%s' not found for filtering "
4068             "AllowForeignAddress", class_name);
4069         }
4070       }
4071     }
4072   }
4073 
4074   if (allow_foreign_addr == FALSE) {
4075     const pr_netaddr_t *remote_addr = session.c->remote_addr;
4076 
4077 #ifdef PR_USE_IPV6
4078     if (pr_netaddr_use_ipv6()) {
4079       /* We can only compare the PORT-given address against the remote client
4080        * address if the remote client address is an IPv4-mapped IPv6 address.
4081        */
4082       if (pr_netaddr_get_family(remote_addr) == AF_INET6 &&
4083           pr_netaddr_is_v4mappedv6(remote_addr) != TRUE) {
4084         pr_log_pri(PR_LOG_WARNING,
4085           "Refused PORT %s (IPv4/IPv6 address mismatch)", cmd->arg);
4086         pr_response_add_err(R_500, _("Illegal PORT command"));
4087 
4088         pr_cmd_set_errno(cmd, EPERM);
4089         errno = EPERM;
4090         return PR_ERROR(cmd);
4091       }
4092     }
4093 #endif /* PR_USE_IPV6 */
4094 
4095     if (pr_netaddr_cmp(port_addr, remote_addr) != 0) {
4096       pr_log_pri(PR_LOG_WARNING, "Refused PORT %s (address mismatch)",
4097         cmd->arg);
4098       pr_response_add_err(R_500, _("Illegal PORT command"));
4099 
4100       pr_cmd_set_errno(cmd, EPERM);
4101       errno = EPERM;
4102       return PR_ERROR(cmd);
4103     }
4104   }
4105 
4106   /* Additionally, make sure that the port number used is a "high numbered"
4107    * port, to avoid bounce attacks.  For remote Windows machines, the
4108    * port numbers mean little.  However, there are also quite a few Unix
4109    * machines out there for whom the port number matters...
4110    */
4111 
4112   if (port < 1024) {
4113     pr_log_pri(PR_LOG_WARNING,
4114       "Refused PORT %s (port %d below 1024, possible bounce attack)", cmd->arg,
4115       port);
4116     pr_response_add_err(R_500, _("Illegal PORT command"));
4117 
4118     pr_cmd_set_errno(cmd, EPERM);
4119     errno = EPERM;
4120     return PR_ERROR(cmd);
4121   }
4122 
4123   memcpy(&session.data_addr, port_addr, sizeof(session.data_addr));
4124   session.data_port = port;
4125   session.sf_flags &= (SF_ALL^SF_PASSIVE);
4126 
4127   /* If we already have a data connection open, kill it. */
4128   if (session.d != NULL) {
4129     pr_inet_close(session.d->pool, session.d);
4130     session.d = NULL;
4131   }
4132 
4133   session.sf_flags |= SF_PORT;
4134   pr_response_add(R_200, _("PORT command successful"));
4135 
4136   return PR_HANDLED(cmd);
4137 }
4138 
core_eprt(cmd_rec * cmd)4139 MODRET core_eprt(cmd_rec *cmd) {
4140   const pr_netaddr_t *listen_addr = NULL;
4141   pr_netaddr_t na;
4142   int family = 0;
4143   unsigned short port = 0;
4144   int allow_foreign_addr = FALSE, *root_revoke = NULL;
4145   char delim = '\0', *argstr = pstrdup(cmd->tmp_pool, cmd->argv[1]);
4146   char *tmp = NULL;
4147   config_rec *c;
4148   const char *proto;
4149 
4150   if (session.sf_flags & SF_EPSV_ALL) {
4151     pr_response_add_err(R_500, _("Illegal EPRT command, EPSV ALL in effect"));
4152 
4153     pr_cmd_set_errno(cmd, EPERM);
4154     errno = EPERM;
4155     return PR_ERROR(cmd);
4156   }
4157 
4158   CHECK_CMD_ARGS(cmd, 2);
4159 
4160   /* Returning 501 is the best we can do.  It would be nicer if RFC959 allowed
4161    * 550 as a possible response.
4162    */
4163   if (!dir_check(cmd->tmp_pool, cmd, cmd->group, session.cwd, NULL)) {
4164     int xerrno = EPERM;
4165 
4166     pr_log_debug(DEBUG8, "EPRT denied by <Limit> configuration");
4167     pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[0],
4168       strerror(xerrno));
4169 
4170     pr_cmd_set_errno(cmd, xerrno);
4171     errno = xerrno;
4172     return PR_ERROR(cmd);
4173   }
4174 
4175   /* Initialize the netaddr. */
4176   pr_netaddr_clear(&na);
4177 
4178   /* Block active transfers (the EPRT command) if RootRevoke is in effect
4179    * and the server's port is below 1024 (binding to the data port in this
4180    * case would require root privs, which will have been dropped.
4181    *
4182    * A RootRevoke value of 0 indicates 'false', 1 indicates 'true', and
4183    * 2 indicates 'NonCompliantActiveTransfer'.  We only block active transfers
4184    * for a RootRevoke value of 1.
4185    */
4186   root_revoke = get_param_ptr(TOPLEVEL_CONF, "RootRevoke", FALSE);
4187   if (root_revoke != NULL &&
4188       *root_revoke == 1 &&
4189       session.c->local_port < 1024) {
4190     pr_log_debug(DEBUG0, "RootRevoke in effect, unable to bind to local "
4191       "port %d for active transfer", session.c->local_port-1);
4192     pr_response_add_err(R_500, _("Unable to service EPRT commands"));
4193 
4194     pr_cmd_set_errno(cmd, EPERM);
4195     errno = EPERM;
4196     return PR_ERROR(cmd);
4197   }
4198 
4199   /* Format is <d>proto<d>ip address<d>port<d> (ASCII in network order),
4200    * where <d> is an arbitrary delimiter character.
4201    */
4202   delim = *argstr++;
4203 
4204   /* atoi() will happily any trailing non-numeric characters, so feeding
4205    * the parameter string won't hurt.
4206    */
4207   family = atoi(argstr);
4208 
4209   switch (family) {
4210     case 1:
4211       break;
4212 
4213 #ifdef PR_USE_IPV6
4214     case 2:
4215       if (pr_netaddr_use_ipv6())
4216         break;
4217 #endif /* PR_USE_IPV6 */
4218 
4219     default:
4220 #ifdef PR_USE_IPV6
4221       if (pr_netaddr_use_ipv6()) {
4222         pr_response_add_err(R_522,
4223           _("Network protocol not supported, use (1,2)"));
4224 
4225       } else {
4226         pr_response_add_err(R_522,
4227           _("Network protocol not supported, use (1)"));
4228       }
4229 #else
4230       pr_response_add_err(R_522, _("Network protocol not supported, use (1)"));
4231 #endif /* PR_USE_IPV6 */
4232 
4233       pr_cmd_set_errno(cmd, EINVAL);
4234       errno = EINVAL;
4235       return PR_ERROR(cmd);
4236   }
4237 
4238   /* Now, skip past those numeric characters that atoi() used. */
4239   while (PR_ISDIGIT(*argstr)) {
4240     argstr++;
4241   }
4242 
4243   /* If the next character is not the delimiter, it's a badly formatted
4244    * parameter.
4245    */
4246   if (*argstr == delim) {
4247     argstr++;
4248 
4249   } else {
4250     pr_response_add_err(R_501, _("Illegal EPRT command"));
4251 
4252     pr_cmd_set_errno(cmd, EPERM);
4253     errno = EPERM;
4254     return PR_ERROR(cmd);
4255   }
4256 
4257   tmp = strchr(argstr, delim);
4258   if (tmp == NULL) {
4259     pr_log_debug(DEBUG3, "badly formatted EPRT argument: '%s'",
4260       (char *) cmd->argv[1]);
4261     pr_response_add_err(R_501, _("Illegal EPRT command"));
4262 
4263     pr_cmd_set_errno(cmd, EPERM);
4264     errno = EPERM;
4265     return PR_ERROR(cmd);
4266   }
4267 
4268   /* Twiddle the string so that just the address portion will be processed
4269    * by pr_inet_pton().
4270    */
4271   *tmp = '\0';
4272 
4273   memset(&na, 0, sizeof(na));
4274 
4275   /* Use pr_inet_pton() to translate the address string into the address
4276    * value.
4277    */
4278   switch (family) {
4279     case 1: {
4280       struct sockaddr *sa = NULL;
4281 
4282       pr_netaddr_set_family(&na, AF_INET);
4283       sa = pr_netaddr_get_sockaddr(&na);
4284       if (sa)
4285         sa->sa_family = AF_INET;
4286       if (pr_inet_pton(AF_INET, argstr, pr_netaddr_get_inaddr(&na)) <= 0) {
4287         pr_log_debug(DEBUG2, "error converting IPv4 address '%s': %s",
4288           argstr, strerror(errno));
4289         pr_response_add_err(R_501, _("Illegal EPRT command"));
4290 
4291         pr_cmd_set_errno(cmd, EPERM);
4292         errno = EPERM;
4293         return PR_ERROR(cmd);
4294       }
4295       break;
4296     }
4297 
4298     case 2: {
4299       struct sockaddr *sa = NULL;
4300 
4301       pr_netaddr_set_family(&na, AF_INET6);
4302       sa = pr_netaddr_get_sockaddr(&na);
4303       if (sa)
4304         sa->sa_family = AF_INET6;
4305       if (pr_inet_pton(AF_INET6, argstr, pr_netaddr_get_inaddr(&na)) <= 0) {
4306         pr_log_debug(DEBUG2, "error converting IPv6 address '%s': %s",
4307           argstr, strerror(errno));
4308         pr_response_add_err(R_501, _("Illegal EPRT command"));
4309 
4310         pr_cmd_set_errno(cmd, EPERM);
4311         errno = EPERM;
4312         return PR_ERROR(cmd);
4313       }
4314       break;
4315     }
4316   }
4317 
4318   /* Advance past the address portion of the argument. */
4319   argstr = ++tmp;
4320 
4321   port = atoi(argstr);
4322 
4323   while (PR_ISDIGIT(*argstr)) {
4324     argstr++;
4325   }
4326 
4327   /* If the next character is not the delimiter, it's a badly formatted
4328    * parameter.
4329    */
4330   if (*argstr != delim) {
4331     pr_log_debug(DEBUG3, "badly formatted EPRT argument: '%s'",
4332       (char *) cmd->argv[1]);
4333     pr_response_add_err(R_501, _("Illegal EPRT command"));
4334 
4335     pr_cmd_set_errno(cmd, EPERM);
4336     errno = EPERM;
4337     return PR_ERROR(cmd);
4338   }
4339 
4340   /* If we are NOT listening on an RFC1918 address, BUT the client HAS
4341    * sent us an RFC1918 address in its PORT command (which we know to not be
4342    * routable), then ignore that address, and use the client's remote address.
4343    */
4344   listen_addr = session.c->local_addr;
4345 
4346   proto = pr_session_get_protocol(0);
4347   if (strncmp(proto, "ftps", 5) == 0) {
4348     c = find_config(main_server->conf, CONF_PARAM, "TLSMasqueradeAddress",
4349       FALSE);
4350     if (c != NULL) {
4351       listen_addr = c->argv[0];
4352 
4353     } else {
4354       c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress",
4355         FALSE);
4356       if (c != NULL) {
4357         if (c->argv[0] != NULL) {
4358           listen_addr = c->argv[0];
4359         }
4360       }
4361     }
4362 
4363   } else {
4364     c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE);
4365     if (c != NULL) {
4366       if (c->argv[0] != NULL) {
4367         listen_addr = c->argv[0];
4368       }
4369     }
4370   }
4371 
4372   if (pr_netaddr_is_rfc1918(listen_addr) != TRUE &&
4373       pr_netaddr_is_rfc1918(session.c->remote_addr) != TRUE &&
4374       pr_netaddr_is_rfc1918(&na) == TRUE) {
4375     const char *rfc1918_ipstr;
4376 
4377     rfc1918_ipstr = pr_netaddr_get_ipstr(&na);
4378 
4379     pr_netaddr_clear(&na);
4380     pr_netaddr_set_family(&na, pr_netaddr_get_family(session.c->remote_addr));
4381     pr_netaddr_set_sockaddr(&na,
4382       pr_netaddr_get_sockaddr(session.c->remote_addr));
4383 
4384     pr_log_debug(DEBUG1, "client sent RFC1918 address '%s' in EPRT command, "
4385       "ignoring it and using '%s'", rfc1918_ipstr, pr_netaddr_get_ipstr(&na));
4386   }
4387 
4388   /* Make sure that the address specified matches the address from which
4389    * the control connection is coming.
4390    */
4391 
4392   c = find_config(TOPLEVEL_CONF, CONF_PARAM, "AllowForeignAddress", FALSE);
4393   if (c != NULL) {
4394     int allowed;
4395 
4396     allowed = *((int *) c->argv[0]);
4397     switch (allowed) {
4398       case TRUE:
4399         allow_foreign_addr = TRUE;
4400         break;
4401 
4402       case FALSE:
4403         break;
4404 
4405       default: {
4406         char *class_name;
4407         const pr_class_t *cls;
4408 
4409         class_name = c->argv[1];
4410         cls = pr_class_find(class_name);
4411         if (cls != NULL) {
4412           if (pr_class_satisfied(cmd->tmp_pool, cls, &na) == TRUE) {
4413             allow_foreign_addr = TRUE;
4414 
4415           } else {
4416             pr_log_debug(DEBUG8, "<Class> '%s' not satisfied by foreign "
4417               "address '%s'", class_name, pr_netaddr_get_ipstr(&na));
4418           }
4419 
4420         } else {
4421           pr_log_debug(DEBUG8, "<Class> '%s' not found for filtering "
4422             "AllowForeignAddress", class_name);
4423         }
4424       }
4425     }
4426   }
4427 
4428   if (allow_foreign_addr == FALSE) {
4429     if (pr_netaddr_cmp(&na, session.c->remote_addr) != 0 || !port) {
4430       pr_log_pri(PR_LOG_WARNING, "Refused EPRT %s (address mismatch)",
4431         cmd->arg);
4432       pr_response_add_err(R_500, _("Illegal EPRT command"));
4433 
4434       pr_cmd_set_errno(cmd, EPERM);
4435       errno = EPERM;
4436       return PR_ERROR(cmd);
4437     }
4438   }
4439 
4440   /* Additionally, make sure that the port number used is a "high numbered"
4441    * port, to avoid bounce attacks.  For remote Windows machines, the
4442    * port numbers mean little.  However, there are also quite a few Unix
4443    * machines out there for whom the port number matters...
4444    */
4445 
4446   if (port < 1024) {
4447     pr_log_pri(PR_LOG_WARNING,
4448       "Refused EPRT %s (port %d below 1024, possible bounce attack)", cmd->arg,
4449       port);
4450     pr_response_add_err(R_500, _("Illegal EPRT command"));
4451 
4452     pr_cmd_set_errno(cmd, EPERM);
4453     errno = EPERM;
4454     return PR_ERROR(cmd);
4455   }
4456 
4457   /* Make sure we're using network byte order. */
4458   pr_netaddr_set_port(&na, htons(port));
4459 
4460   switch (family) {
4461     case 1:
4462       pr_netaddr_set_family(&session.data_addr, AF_INET);
4463       break;
4464 
4465     case 2:
4466       pr_netaddr_set_family(&session.data_addr, AF_INET6);
4467       break;
4468   }
4469 
4470   pr_netaddr_set_sockaddr(&session.data_addr, pr_netaddr_get_sockaddr(&na));
4471   pr_netaddr_set_port(&session.data_addr, pr_netaddr_get_port(&na));
4472   session.data_port = port;
4473   session.sf_flags &= (SF_ALL^SF_PASSIVE);
4474 
4475   /* If we already have a data connection open, kill it. */
4476   if (session.d) {
4477     pr_inet_close(session.d->pool, session.d);
4478     session.d = NULL;
4479   }
4480 
4481   session.sf_flags |= SF_PORT;
4482   pr_response_add(R_200, _("EPRT command successful"));
4483 
4484   return PR_HANDLED(cmd);
4485 }
4486 
core_epsv(cmd_rec * cmd)4487 MODRET core_epsv(cmd_rec *cmd) {
4488   char *addrstr = "";
4489   char *endp = NULL, *arg = NULL;
4490   int family = 0;
4491   int epsv_min_port = 1024, epsv_max_port = 65535;
4492   config_rec *c = NULL;
4493   const pr_netaddr_t *bind_addr;
4494 
4495   CHECK_CMD_MIN_ARGS(cmd, 1);
4496 
4497   /* Returning 501 is the best we can do.  It would be nicer if RFC959 allowed
4498    * 550 as a possible response.
4499    */
4500   if (!dir_check(cmd->tmp_pool, cmd, cmd->group, session.cwd, NULL)) {
4501     int xerrno = EPERM;
4502 
4503     pr_log_debug(DEBUG8, "EPSV denied by <Limit> configuration");
4504     pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[0],
4505       strerror(xerrno));
4506 
4507     pr_cmd_set_errno(cmd, xerrno);
4508     errno = xerrno;
4509     return PR_ERROR(cmd);
4510   }
4511 
4512   if (cmd->argc-1 == 1) {
4513     arg = pstrdup(cmd->tmp_pool, cmd->argv[1]);
4514   }
4515 
4516   if (arg && strcasecmp(arg, "all") == 0) {
4517     session.sf_flags |= SF_EPSV_ALL;
4518     pr_response_add(R_200, _("EPSV ALL command successful"));
4519 
4520     pr_cmd_set_errno(cmd, EPERM);
4521     errno = EPERM;
4522     return PR_HANDLED(cmd);
4523   }
4524 
4525   /* If the optional parameter was given, determine the address family from
4526    * that.  If not, determine the family from the control connection address
4527    * family.
4528    */
4529   if (arg) {
4530     family = strtol(arg, &endp, 10);
4531 
4532     if (endp && *endp) {
4533       pr_response_add_err(R_501, _("%s: unknown network protocol"),
4534         (char *) cmd->argv[0]);
4535 
4536       pr_cmd_set_errno(cmd, EINVAL);
4537       errno = EINVAL;
4538       return PR_ERROR(cmd);
4539     }
4540 
4541   } else {
4542 
4543     switch (pr_netaddr_get_family(session.c->local_addr)) {
4544       case AF_INET:
4545         family = 1;
4546         break;
4547 
4548 #ifdef PR_USE_IPV6
4549       case AF_INET6:
4550         if (pr_netaddr_use_ipv6()) {
4551           family = 2;
4552           break;
4553         }
4554 #endif /* PR_USE_IPV6 */
4555 
4556       default:
4557         family = 0;
4558         break;
4559     }
4560   }
4561 
4562   switch (family) {
4563     case 1:
4564       break;
4565 
4566 #ifdef PR_USE_IPV6
4567     case 2:
4568       if (pr_netaddr_use_ipv6())
4569         break;
4570 #endif /* PR_USE_IPV6 */
4571 
4572     default:
4573 #ifdef PR_USE_IPV6
4574       if (pr_netaddr_use_ipv6()) {
4575         pr_response_add_err(R_522,
4576           _("Network protocol not supported, use (1,2)"));
4577 
4578       } else {
4579         pr_response_add_err(R_522,
4580           _("Network protocol not supported, use (1)"));
4581       }
4582 #else
4583       pr_response_add_err(R_522, _("Network protocol not supported, use (1)"));
4584 #endif /* PR_USE_IPV6 */
4585 
4586       pr_cmd_set_errno(cmd, EINVAL);
4587       errno = EINVAL;
4588       return PR_ERROR(cmd);
4589   }
4590 
4591   /* If we already have a passive listen data connection open, kill it. */
4592   if (session.d) {
4593     pr_inet_close(session.d->pool, session.d);
4594     session.d = NULL;
4595   }
4596 
4597   if (pr_netaddr_get_family(session.c->local_addr) == pr_netaddr_get_family(session.c->remote_addr)) {
4598     bind_addr = session.c->local_addr;
4599 
4600   } else {
4601     /* In this scenario, the server has an IPv6 socket, but the remote client
4602      * is an IPv4 (or IPv4-mapped IPv6) peer.
4603      */
4604     bind_addr = pr_netaddr_v6tov4(cmd->pool, session.c->local_addr);
4605   }
4606 
4607   c = find_config(main_server->conf, CONF_PARAM, "PassivePorts", FALSE);
4608   if (c != NULL) {
4609     epsv_min_port = *((int *) c->argv[0]);
4610     epsv_max_port = *((int *) c->argv[1]);
4611   }
4612 
4613   /* We always use the portrange variant of inet_create_conn() here,
4614    * since it seems that some Unix kernels have issues when choosing a
4615    * random port number for IPv6 sockets (see Bug #2900).  By using the
4616    * portrange variant, proftpd, and not the kernel, will be the one
4617    * choosing the port number.  We really only need to do this for EPSV
4618    * and not PASV since only the EPSV command can be used for IPv6
4619    * connections; using PASV means IPv4 connections, and Unix kernels
4620    * have more predictable behavior for choosing random IPv4 socket ports.
4621    */
4622 
4623   session.d = pr_inet_create_conn_portrange(session.pool, bind_addr,
4624     epsv_min_port, epsv_max_port);
4625   if (session.d == NULL) {
4626     /* If not able to open a passive port in the given range, default to
4627      * normal behavior (using INPORT_ANY), and log the failure.  This
4628      * indicates a too-small range configuration.
4629      */
4630     pr_log_pri(PR_LOG_WARNING, "unable to find open port in PassivePorts "
4631       "range %d-%d: defaulting to INPORT_ANY (consider defining a larger "
4632       "PassivePorts range)", epsv_min_port, epsv_max_port);
4633 
4634     session.d = pr_inet_create_conn(session.pool, -1, bind_addr, INPORT_ANY,
4635       FALSE);
4636   }
4637 
4638   if (session.d == NULL) {
4639     pr_response_add_err(R_425,
4640       _("Unable to build data connection: Internal error"));
4641 
4642     pr_cmd_set_errno(cmd, EINVAL);
4643     errno = EINVAL;
4644     return PR_ERROR(cmd);
4645   }
4646 
4647   /* Make sure that necessary socket options are set on the socket prior
4648    * to the call to listen(2).
4649    */
4650   pr_inet_set_proto_opts(session.pool, session.d, main_server->tcp_mss_len, 0,
4651     IPTOS_THROUGHPUT, 1);
4652   pr_inet_generate_socket_event("core.data-listen", main_server,
4653     session.d->local_addr, session.d->listen_fd);
4654 
4655   pr_inet_set_block(session.pool, session.d);
4656   if (pr_inet_listen(session.pool, session.d, 1, 0) < 0) {
4657     int xerrno = errno;
4658 
4659     pr_response_add_err(R_425, "%s: %s", (char *) cmd->argv[0],
4660       strerror(xerrno));
4661 
4662     pr_cmd_set_errno(cmd, xerrno);
4663     errno = xerrno;
4664     return PR_ERROR(cmd);
4665   }
4666 
4667   session.d->instrm = pr_netio_open(session.pool, PR_NETIO_STRM_DATA,
4668     session.d->listen_fd, PR_NETIO_IO_RD);
4669 
4670   /* Now tell the client our address/port. */
4671   session.data_port = session.d->local_port;
4672   session.sf_flags |= SF_PASSIVE;
4673 
4674   /* Note: what about masquerading IPv6 addresses?  It seems that RFC2428,
4675    * which defines the EPSV command, does not explicitly handle the
4676    * case where the server may wish to return a network address in its
4677    * EPSV response.  The assumption is that in an IPv6 environment, there
4678    * will be no need for NAT, and hence no need for masquerading.  This
4679    * may be true in an ideal world, but I think it more likely that current
4680    * clients will simply use EPSV, rather than PASV, in existing IPv4 networks.
4681    *
4682    * Disable the honoring of MasqueradeAddress for EPSV until this can
4683    * be officially determined (Bug#2369).  See also Bug#3862.
4684    */
4685 #if 0
4686   c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE);
4687   if (c != NULL) {
4688    addrstr = (char *) pr_netaddr_get_ipstr(c->argv[0]);
4689   }
4690 #endif
4691 
4692   pr_log_debug(DEBUG1, "Entering Extended Passive Mode (||%s|%u|)",
4693     addrstr, (unsigned int) session.data_port);
4694   pr_response_add(R_229, "Entering Extended Passive Mode (||%s|%u|)",
4695     addrstr, (unsigned int) session.data_port);
4696 
4697   return PR_HANDLED(cmd);
4698 }
4699 
core_help(cmd_rec * cmd)4700 MODRET core_help(cmd_rec *cmd) {
4701   if (cmd->argc == 1) {
4702     pr_help_add_response(cmd, NULL);
4703 
4704   } else {
4705     char *cp;
4706 
4707     for (cp = cmd->argv[1]; *cp; cp++) {
4708       *cp = toupper(*cp);
4709     }
4710 
4711     if (strcasecmp(cmd->argv[1], C_SITE) == 0) {
4712       return pr_module_call(&site_module, site_dispatch, cmd);
4713     }
4714 
4715     if (pr_help_add_response(cmd, cmd->argv[1]) == 0) {
4716       return PR_HANDLED(cmd);
4717     }
4718 
4719     pr_response_add_err(R_502, _("Unknown command '%s'"),
4720       (char *) cmd->argv[1]);
4721 
4722     pr_cmd_set_errno(cmd, EINVAL);
4723     errno = EINVAL;
4724     return PR_ERROR(cmd);
4725   }
4726 
4727   return PR_HANDLED(cmd);
4728 }
4729 
core_host(cmd_rec * cmd)4730 MODRET core_host(cmd_rec *cmd) {
4731   const char *local_ipstr;
4732   char *host;
4733   size_t hostlen;
4734   server_rec *named_server;
4735   int found_ipv6 = FALSE;
4736 
4737   if (cmd->argc != 2) {
4738     pr_response_add_err(R_500, _("'%s' not understood"), (char *) cmd->argv[0]);
4739 
4740     pr_cmd_set_errno(cmd, EINVAL);
4741     errno = EINVAL;
4742     return PR_ERROR(cmd);
4743   }
4744 
4745   if (session.user != NULL) {
4746     pr_log_debug(DEBUG0,
4747       "HOST '%s' command received after login, refusing HOST command",
4748       (char *) cmd->argv[1]);
4749 
4750     /* Per HOST spec, HOST after successful USER/PASS is not allowed. */
4751     pr_response_add_err(R_503, _("Bad sequence of commands"));
4752 
4753     pr_cmd_set_errno(cmd, EPERM);
4754     errno = EPERM;
4755     return PR_ERROR(cmd);
4756   }
4757 
4758   if (!dir_check(cmd->tmp_pool, cmd, cmd->group, session.cwd, NULL)) {
4759     int xerrno = EACCES;
4760 
4761     pr_response_add_err(R_504, "%s: %s", (char *) cmd->argv[1],
4762       strerror(xerrno));
4763 
4764     pr_cmd_set_errno(cmd, xerrno);
4765     errno = xerrno;
4766     return PR_ERROR(cmd);
4767   }
4768 
4769   /* Should there be a limit on the number of HOST commands that a client
4770    * can send?
4771    *
4772    * In practice, this will be limited by the TimeoutLogin time interval;
4773    * a client can send as many HOST commands as it wishes, as long as it
4774    * successfully authenticates in that time.
4775    */
4776 
4777   /* If the user has already authenticated or negotiated a RFC2228 mechanism,
4778    * then the HOST command is too late.
4779    */
4780   if (session.rfc2228_mech != NULL &&
4781       pr_table_get(session.notes, "mod_tls.sni", NULL) == NULL) {
4782     pr_log_debug(DEBUG0, "HOST '%s' command received after client has "
4783       "requested RFC2228 protection (%s), refusing HOST command",
4784       (char *) cmd->argv[1], session.rfc2228_mech);
4785 
4786     pr_response_add_err(R_503, _("Bad sequence of commands"));
4787 
4788     pr_cmd_set_errno(cmd, EPERM);
4789     errno = EPERM;
4790     return PR_ERROR(cmd);
4791   }
4792 
4793   host = cmd->argv[1];
4794   hostlen = strlen(host);
4795 
4796   if (host[0] == '[') {
4797     /* Check for any literal IPv6 hostnames. Per HOST spec, these IPv6
4798      * addresses MUST be enclosed within square brackets.
4799      */
4800 
4801     if (pr_netaddr_use_ipv6()) {
4802       if (host[hostlen-1] != ']') {
4803         pr_response_add_err(R_501, _("%s: Invalid IPv6 address provided"),
4804           host);
4805 
4806         pr_cmd_set_errno(cmd, EINVAL);
4807         errno = EINVAL;
4808         return PR_ERROR(cmd);
4809       }
4810 
4811       host = pstrndup(cmd->tmp_pool, host + 1, hostlen - 2);
4812       hostlen = hostlen - 2;
4813 
4814       if (pr_netaddr_is_v6(host) != TRUE) {
4815         pr_log_debug(DEBUG0,
4816           "Client-sent hostname '%s' is not a valid IPv6 address, "
4817           "refusing HOST command", host);
4818 
4819         pr_response_add_err(R_501, _("%s: Invalid IPv6 address provided"),
4820           (char *) cmd->argv[1]);
4821 
4822         pr_cmd_set_errno(cmd, EINVAL);
4823         errno = EINVAL;
4824         return PR_ERROR(cmd);
4825       }
4826 
4827       found_ipv6 = TRUE;
4828 
4829     } else {
4830       pr_response_add_err(R_501, _("%s: Invalid hostname provided"),
4831         host);
4832 
4833       pr_cmd_set_errno(cmd, EINVAL);
4834       errno = EINVAL;
4835       return PR_ERROR(cmd);
4836     }
4837   }
4838 
4839   local_ipstr = pr_netaddr_get_ipstr(session.c->local_addr);
4840 
4841   if (pr_netaddr_is_v4(host) == TRUE) {
4842     if (pr_netaddr_is_v4(local_ipstr) == TRUE) {
4843       if (strncmp(host, local_ipstr, hostlen) != 0) {
4844         /* The client connected to an IP address, but requested a different IP
4845          * address in its HOST command.  That won't work.
4846          */
4847         pr_log_debug(DEBUG0, "HOST '%s' requested, but client connected to "
4848           "IPv4 address '%s', refusing HOST command", host, local_ipstr);
4849         pr_response_add_err(R_504, _("%s: Unknown hostname provided"),
4850           (char *) cmd->argv[1]);
4851 
4852         pr_cmd_set_errno(cmd, EPERM);
4853         errno = EPERM;
4854         return PR_ERROR(cmd);
4855       }
4856     }
4857 
4858     (void) pr_table_remove(session.notes, "mod_core.host", NULL);
4859     if (pr_table_add_dup(session.notes, "mod_core.host", host, 0) < 0) {
4860       pr_trace_msg("command", 3,
4861         "error stashing 'mod_core.host' in session.notes: %s", strerror(errno));
4862     }
4863 
4864     /* No need to send the banner information again, since we didn't actually
4865      * change the virtual host used by the client.
4866      */
4867     pr_response_add(R_220, _("HOST command successful"));
4868     return PR_HANDLED(cmd);
4869 
4870   } else if (pr_netaddr_is_v6(host) == TRUE) {
4871     if (pr_netaddr_is_v6(local_ipstr) == TRUE) {
4872 
4873       if (found_ipv6 == FALSE) {
4874         /* The client sent us an IPv6 address WITHOUT the '[...]' notation,
4875          * which is a syntax error.
4876          */
4877         pr_log_debug(DEBUG0, "Client-sent hostname '%s' is an IPv6 address, "
4878           "but did not have required [] notation, refusing HOST command",
4879           host);
4880 
4881         pr_response_add_err(R_501, _("%s: Invalid IPv6 address provided"),
4882           host);
4883 
4884         pr_cmd_set_errno(cmd, EINVAL);
4885         errno = EINVAL;
4886         return PR_ERROR(cmd);
4887       }
4888 
4889       if (strncmp(host, local_ipstr, hostlen) != 0) {
4890         /* The client connected to an IP address, but requested a different IP
4891          * address in its HOST command.  That won't work.
4892          */
4893         pr_log_debug(DEBUG0, "HOST '%s' requested, but client connected to "
4894           "IPv6 address '%s', refusing HOST command", host, local_ipstr);
4895         pr_response_add_err(R_504, _("%s: Unknown hostname provided"),
4896           (char *) cmd->argv[1]);
4897 
4898         pr_cmd_set_errno(cmd, EPERM);
4899         errno = EPERM;
4900         return PR_ERROR(cmd);
4901       }
4902     }
4903 
4904     (void) pr_table_remove(session.notes, "mod_core.host", NULL);
4905     if (pr_table_add_dup(session.notes, "mod_core.host", host, 0) < 0) {
4906       pr_trace_msg("command", 3,
4907         "error stashing 'mod_core.host' in session.notes: %s", strerror(errno));
4908     }
4909 
4910     /* No need to send the banner information again, since we didn't actually
4911      * change the virtual host used by the client.
4912      */
4913     pr_response_add(R_220, _("HOST command successful"));
4914     return PR_HANDLED(cmd);
4915   }
4916 
4917   /* If we reach this point, the hostname is probably a DNS name.  See if we
4918    * have a matching namebind based on the current IP address/port.
4919    */
4920 
4921   if (strchr(host, ':') != NULL) {
4922     /* Hostnames cannot contain colon characters. */
4923     pr_response_add_err(R_501, _("%s: Invalid hostname provided"), host);
4924 
4925     pr_cmd_set_errno(cmd, EINVAL);
4926     errno = EINVAL;
4927     return PR_ERROR(cmd);
4928   }
4929 
4930   named_server = pr_namebind_get_server(host, main_server->addr,
4931     session.c->local_port);
4932   if (named_server == NULL) {
4933     pr_log_debug(DEBUG0, "Unknown host '%s' requested on %s#%d, "
4934       "refusing HOST command", host, local_ipstr, main_server->ServerPort);
4935 
4936     pr_response_add_err(R_504, _("%s: Unknown hostname provided"),
4937       (char *) cmd->argv[1]);
4938 
4939     pr_cmd_set_errno(cmd, ENOENT);
4940     errno = ENOENT;
4941     return PR_ERROR(cmd);
4942   }
4943 
4944   if (session.rfc2228_mech != NULL &&
4945       strncmp(session.rfc2228_mech, "TLS", 4) == 0) {
4946     const char *sni = NULL;
4947 
4948     /* If the TLS client used the SNI extension, ensure that the SNI name
4949      * matches the HOST name, per RFC 7151, Section 3.2.2.  Otherwise, we
4950      * reject the HOST command.
4951      */
4952     sni = pr_table_get(session.notes, "mod_tls.sni", NULL);
4953     if (sni != NULL) {
4954       if (strcasecmp(sni, host) != 0) {
4955         pr_log_debug(DEBUG0, "HOST '%s' requested, but client connected via "
4956           "TLS to SNI '%s', refusing HOST command", host, sni);
4957         pr_response_add_err(R_504, _("%s: Unknown hostname provided"),
4958           (char *) cmd->argv[1]);
4959 
4960         pr_cmd_set_errno(cmd, EPERM);
4961         errno = EPERM;
4962         return PR_ERROR(cmd);
4963       }
4964     }
4965   }
4966 
4967   (void) pr_table_remove(session.notes, "mod_core.host", NULL);
4968   if (pr_table_add_dup(session.notes, "mod_core.host", host, 0) < 0) {
4969     pr_trace_msg("command", 3,
4970       "error stashing 'mod_core.host' in session.notes: %s", strerror(errno));
4971   }
4972 
4973   if (named_server != main_server) {
4974     /* Set a session flag indicating that the main_server pointer changed. */
4975     pr_log_debug(DEBUG0,
4976       "Changing to server '%s' (ServerAlias %s) due to HOST command",
4977       named_server->ServerName, host);
4978     session.prev_server = main_server;
4979     main_server = named_server;
4980 
4981     pr_event_generate("core.session-reinit", named_server);
4982   }
4983 
4984   /* XXX Ultimately, if HOST is successful, we change the main_server pointer
4985    * to point to the named server_rec.
4986    *
4987    * Check *every single sess_init* function, since there are MANY things
4988    * which currently happen at sess_init, based on the main_server pointer,
4989    * that will need to be re-done in a HOST POST_CMD handler.  This includes
4990    * AuthOrder, timeouts, etc etc.  (Unfortunately, POST_CMD handlers cannot
4991    * fail the given command; for modules which then need to end the
4992    * connection, they'll need to use pr_session_disconnect().)
4993    *
4994    * Modules implementing post_host handlers:
4995    *   mod_core
4996    *
4997    * Modules implementing 'sess-reinit' event handlers:
4998    *   mod_auth
4999    *   mod_auth_file
5000    *   mod_auth_unix
5001    *   mod_ban
5002    *   mod_cap
5003    *   mod_copy
5004    *   mod_deflate
5005    *   mod_delay
5006    *   mod_dnsbl
5007    *   mod_exec
5008    *   mod_facts
5009    *   mod_ident
5010    *   mod_ldap
5011    *   mod_log
5012    *   mod_log_forensic
5013    *   mod_memcache
5014    *   mod_qos
5015    *   mod_quotatab
5016    *   mod_radius
5017    *   mod_rewrite
5018    *   mod_site_misc
5019    *   mod_sql
5020    *   mod_sql_passwd
5021    *   mod_tls
5022    *   mod_wrap
5023    *   mod_wrap2
5024    *   mod_xfer
5025    *
5026    * Modules that MIGHT need a session-reinit listener:
5027    *   mod_ratio
5028    *   mod_snmp
5029    *
5030    * Modules that DO NOT NEED a session-reinit listener:
5031    *   mod_auth_pam
5032    *   mod_ctrls_admin
5033    *   mod_dynmasq
5034    *   mod_ifsession
5035    *   mod_ifversion
5036    *   mod_load
5037    *   mod_readme
5038    *   mod_sftp (HOST command is FTP only)
5039    *   mod_sftp_pam
5040    *   mod_sftp_sql
5041    *   mod_shaper
5042    *   mod_sql_mysql
5043    *   mod_sql_postgres
5044    *   mod_sql_odbc
5045    *   mod_sql_sqlite
5046    *   mod_tls_fscache
5047    *   mod_tls_memcache
5048    *   mod_tls_shmcache
5049    *   mod_unique_id
5050    */
5051 
5052   /* XXX Will this function need to use pr_response_add(), rather than
5053    * pr_response_send(), in order to accommodate the delaying of sending the
5054    * response until after POST_CMD/LOG_CMD handlers have run (and thus allowing
5055    * module e.g. mod_tls to send an error response in the POST_CMD handler,
5056    * and close the connection)?
5057    */
5058 
5059   pr_session_send_banner(main_server, 0);
5060   return PR_HANDLED(cmd);
5061 }
5062 
core_post_host(cmd_rec * cmd)5063 MODRET core_post_host(cmd_rec *cmd) {
5064 
5065   /* If the HOST command changed the main_server pointer, reinitialize
5066    * ourselves.
5067    */
5068   if (session.prev_server != NULL) {
5069     int res;
5070     config_rec *c;
5071 
5072     /* Reset the FS options */
5073     (void) pr_fsio_set_options(0UL);
5074 
5075     /* Remove the TimeoutIdle timer. */
5076     (void) pr_timer_remove(PR_TIMER_IDLE, ANY_MODULE);
5077 
5078     /* Restore the original TimeoutLinger value. */
5079     pr_data_set_linger(PR_TUNABLE_TIMEOUTLINGER);
5080 
5081     /* Restore original DebugLevel, but only if set via directive, not
5082      * via the command-line.
5083      */
5084     c = find_config(session.prev_server->conf, CONF_PARAM, "DebugLevel", FALSE);
5085     if (c != NULL) {
5086       pr_log_setdebuglevel(DEBUG0);
5087     }
5088 
5089     /* Restore the original RegexOptions values. */
5090     pr_regexp_set_limits(0, 0);
5091 
5092     /* Remove any configured SetEnvs. */
5093     c = find_config(session.prev_server->conf, CONF_PARAM, "SetEnv", FALSE);
5094     while (c) {
5095       pr_signals_handle();
5096 
5097       if (pr_env_unset(session.pool, c->argv[0]) < 0) {
5098         pr_log_debug(DEBUG0, "unable to unset environment variable '%s': %s",
5099           (char *) c->argv[0], strerror(errno));
5100       }
5101 
5102       c = find_config_next(c, c->next, CONF_PARAM, "SetEnv", FALSE);
5103     }
5104 
5105     /* Restore original AuthOrder. */
5106     reset_server_auth_order();
5107 
5108 #ifdef PR_USE_TRACE
5109     /* XXX Restore original Trace settings. */
5110 
5111     /* Restore original TraceOptions settings. */
5112     (void) pr_trace_set_options(PR_TRACE_OPT_DEFAULT);
5113 
5114 #endif /* PR_USE_TRACE */
5115 
5116     /* Remove the variables set via pr_var_set(). */
5117     (void) pr_var_delete("%{bytes_xfer}");
5118     (void) pr_var_delete("%{total_bytes_in}");
5119     (void) pr_var_delete("%{total_bytes_out}");
5120     (void) pr_var_delete("%{total_bytes_xfer}");
5121     (void) pr_var_delete("%{total_files_in}");
5122     (void) pr_var_delete("%{total_files_out}");
5123     (void) pr_var_delete("%{total_files_xfer}");
5124 
5125     /* Reset the DisplayQuit file. */
5126     if (displayquit_fh != NULL) {
5127       pr_fsio_close(displayquit_fh);
5128       displayquit_fh = NULL;
5129     }
5130 
5131     /* Restore the original ProcessTitles setting. */
5132     pr_proctitle_set_static_str(NULL);
5133 
5134     res = core_sess_init();
5135     if (res < 0) {
5136       pr_session_disconnect(&core_module,
5137         PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
5138     }
5139   }
5140 
5141   return PR_DECLINED(cmd);
5142 }
5143 
core_clnt(cmd_rec * cmd)5144 MODRET core_clnt(cmd_rec *cmd) {
5145   pr_response_add(R_200, _("OK"));
5146   return PR_HANDLED(cmd);
5147 }
5148 
core_syst(cmd_rec * cmd)5149 MODRET core_syst(cmd_rec *cmd) {
5150   pr_response_add(R_215, "UNIX Type: L8");
5151   return PR_HANDLED(cmd);
5152 }
5153 
core_chgrp(cmd_rec * cmd,const char * path,uid_t uid,gid_t gid)5154 int core_chgrp(cmd_rec *cmd, const char *path, uid_t uid, gid_t gid) {
5155   char *cmd_name;
5156 
5157   cmd_name = cmd->argv[0];
5158   pr_cmd_set_name(cmd, "SITE_CHGRP");
5159   if (!dir_check(cmd->tmp_pool, cmd, G_WRITE, path, NULL)) {
5160     pr_log_debug(DEBUG7, "SITE CHGRP command denied by <Limit> config");
5161     pr_cmd_set_name(cmd, cmd_name);
5162 
5163     errno = EACCES;
5164     return -1;
5165   }
5166   pr_cmd_set_name(cmd, cmd_name);
5167 
5168   return pr_fsio_lchown(path, uid, gid);
5169 }
5170 
core_chmod(cmd_rec * cmd,const char * path,mode_t mode)5171 int core_chmod(cmd_rec *cmd, const char *path, mode_t mode) {
5172   char *cmd_name;
5173 
5174   cmd_name = cmd->argv[0];
5175   pr_cmd_set_name(cmd, "SITE_CHMOD");
5176   if (!dir_check(cmd->tmp_pool, cmd, G_WRITE, path, NULL)) {
5177     pr_log_debug(DEBUG7, "SITE CHMOD command denied by <Limit> config");
5178     pr_cmd_set_name(cmd, cmd_name);
5179 
5180     errno = EACCES;
5181     return -1;
5182   }
5183   pr_cmd_set_name(cmd, cmd_name);
5184 
5185   return pr_fsio_chmod(path, mode);
5186 }
5187 
core_chdir(cmd_rec * cmd,char * ndir)5188 MODRET core_chdir(cmd_rec *cmd, char *ndir) {
5189   char *dir, *orig_dir, *cdir;
5190   int xerrno = 0;
5191   config_rec *c = NULL, *cdpath;
5192   unsigned char show_symlinks = TRUE, *ptr = NULL;
5193   struct stat st;
5194 
5195   orig_dir = ndir;
5196 
5197   ptr = get_param_ptr(TOPLEVEL_CONF, "ShowSymlinks", FALSE);
5198   if (ptr != NULL) {
5199     show_symlinks = *ptr;
5200   }
5201 
5202   if (show_symlinks) {
5203     int use_cdpath = FALSE;
5204 
5205     dir = dir_realpath(cmd->tmp_pool, ndir);
5206     if (dir == NULL) {
5207       use_cdpath = TRUE;
5208     }
5209 
5210     if (!use_cdpath) {
5211       int allowed_access = TRUE;
5212 
5213       allowed_access = dir_check_full(cmd->tmp_pool, cmd, cmd->group, dir,
5214         NULL);
5215       if (!allowed_access) {
5216         use_cdpath = TRUE;
5217       }
5218     }
5219 
5220     if (use_cdpath == FALSE &&
5221         pr_fsio_chdir(dir, 0) < 0) {
5222       xerrno = errno;
5223       use_cdpath = TRUE;
5224     }
5225 
5226     if (use_cdpath) {
5227       for (cdpath = find_config(main_server->conf, CONF_PARAM, "CDPath", TRUE);
5228           cdpath != NULL;
5229           cdpath = find_config_next(cdpath, cdpath->next, CONF_PARAM, "CDPath", TRUE)) {
5230         cdir = palloc(cmd->tmp_pool, strlen(cdpath->argv[0]) + strlen(ndir) + 2);
5231         pr_snprintf(cdir, strlen(cdpath->argv[0]) + strlen(ndir) + 2,
5232                  "%s%s%s", (char *) cdpath->argv[0],
5233                  ((char *) cdpath->argv[0])[strlen(cdpath->argv[0]) - 1] == '/' ? "" : "/",
5234                  ndir);
5235         dir = dir_realpath(cmd->tmp_pool, cdir);
5236 
5237         if (dir &&
5238             dir_check_full(cmd->tmp_pool, cmd, cmd->group, dir, NULL) &&
5239             pr_fsio_chdir(dir, 0) == 0) {
5240           break;
5241         }
5242       }
5243 
5244       if (cdpath == FALSE) {
5245         if (xerrno == 0) {
5246           xerrno = errno;
5247         }
5248 
5249         pr_response_add_err(R_550, "%s: %s", orig_dir, strerror(xerrno));
5250 
5251         pr_cmd_set_errno(cmd, xerrno);
5252         errno = xerrno;
5253         return PR_ERROR(cmd);
5254       }
5255     }
5256 
5257   } else {
5258     int use_cdpath = FALSE;
5259 
5260     /* Virtualize the chdir */
5261     ndir = dir_canonical_vpath(cmd->tmp_pool, ndir);
5262     dir = dir_realpath(cmd->tmp_pool, ndir);
5263 
5264     if (!dir) {
5265       use_cdpath = TRUE;
5266     }
5267 
5268     if (!use_cdpath) {
5269       int allowed_access = TRUE;
5270 
5271       allowed_access = dir_check_full(cmd->tmp_pool, cmd, cmd->group, dir,
5272         NULL);
5273       if (!allowed_access)
5274         use_cdpath = TRUE;
5275     }
5276 
5277     if (!use_cdpath &&
5278         pr_fsio_chdir_canon(ndir, 1) < 0) {
5279       use_cdpath = TRUE;
5280     }
5281 
5282     if (use_cdpath) {
5283       for (cdpath = find_config(main_server->conf, CONF_PARAM, "CDPath", TRUE);
5284           cdpath != NULL;
5285           cdpath = find_config_next(cdpath, cdpath->next, CONF_PARAM, "CDPath", TRUE)) {
5286         cdir = palloc(cmd->tmp_pool, strlen(cdpath->argv[0]) + strlen(ndir) + 2);
5287         pr_snprintf(cdir, strlen(cdpath->argv[0]) + strlen(ndir) + 2,
5288                  "%s%s%s", (char *) cdpath->argv[0],
5289                 ((char *)cdpath->argv[0])[strlen(cdpath->argv[0]) - 1] == '/' ? "" : "/",
5290                 ndir);
5291         ndir = dir_canonical_vpath(cmd->tmp_pool, cdir);
5292         dir = dir_realpath(cmd->tmp_pool, ndir);
5293 
5294         if (dir &&
5295             dir_check_full(cmd->tmp_pool, cmd, cmd->group, dir, NULL) &&
5296             pr_fsio_chdir_canon(ndir, 1) != -1) {
5297           break;
5298         }
5299       }
5300 
5301       if (cdpath == NULL) {
5302         if (xerrno == 0) {
5303           xerrno = errno;
5304         }
5305 
5306         pr_response_add_err(R_550, "%s: %s", orig_dir, strerror(xerrno));
5307 
5308         pr_cmd_set_errno(cmd, xerrno);
5309         errno = xerrno;
5310         return PR_ERROR(cmd);
5311       }
5312     }
5313   }
5314 
5315   sstrncpy(session.cwd, pr_fs_getcwd(), sizeof(session.cwd));
5316   sstrncpy(session.vwd, pr_fs_getvwd(), sizeof(session.vwd));
5317 
5318   pr_scoreboard_entry_update(session.pid,
5319     PR_SCORE_CWD, session.cwd,
5320     NULL);
5321 
5322   if (session.dir_config) {
5323     c = find_config(session.dir_config->subset, CONF_PARAM, "DisplayChdir",
5324       FALSE);
5325   }
5326 
5327   if (c == NULL &&
5328       session.anon_config != NULL) {
5329     c = find_config(session.anon_config->subset, CONF_PARAM, "DisplayChdir",
5330       FALSE);
5331   }
5332 
5333   if (c == NULL) {
5334     c = find_config(cmd->server->conf, CONF_PARAM, "DisplayChdir", FALSE);
5335   }
5336 
5337   if (c != NULL) {
5338     time_t prev = 0;
5339 
5340     char *display = c->argv[0];
5341     int bool = *((int *) c->argv[1]);
5342 
5343     if (bool) {
5344 
5345       /* XXX Get rid of this CONF_USERDATA instance; it's the only
5346        * occurrence of it in the source.  Use the session.notes table instead.
5347        */
5348       c = find_config(cmd->server->conf, CONF_USERDATA, session.cwd, FALSE);
5349       if (!c) {
5350         time(&prev);
5351         c = pr_config_add_set(&cmd->server->conf, session.cwd, 0);
5352         c->config_type = CONF_USERDATA;
5353         c->argc = 1;
5354         c->argv = pcalloc(c->pool, sizeof(void **) * 2);
5355         c->argv[0] = palloc(c->pool, sizeof(time_t));
5356         *((time_t *) c->argv[0]) = prev;
5357         prev = (time_t) 0L;
5358 
5359       } else {
5360         prev = *((time_t *) c->argv[0]);
5361 
5362         /* Update the timestamp stored for this directory. */
5363         *((time_t *) c->argv[0]) = time(NULL);
5364       }
5365     }
5366 
5367     if (pr_fsio_stat(display, &st) != -1 &&
5368         !S_ISDIR(st.st_mode) &&
5369         (bool ? st.st_mtime > prev : TRUE)) {
5370 
5371       if (pr_display_file(display, session.cwd, R_250, 0) < 0) {
5372         pr_log_debug(DEBUG3, "error displaying '%s': %s", display,
5373           strerror(errno));
5374       }
5375     }
5376   }
5377 
5378   pr_response_add(R_250, _("%s command successful"), (char *) cmd->argv[0]);
5379   return PR_HANDLED(cmd);
5380 }
5381 
core_rmd(cmd_rec * cmd)5382 MODRET core_rmd(cmd_rec *cmd) {
5383   int res;
5384   char *decoded_path, *dir;
5385   pr_error_t *err = NULL;
5386 
5387   CHECK_CMD_MIN_ARGS(cmd, 2);
5388 
5389   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
5390     FSIO_DECODE_FL_TELL_ERRORS);
5391   if (decoded_path == NULL) {
5392     int xerrno = errno;
5393 
5394     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
5395       strerror(xerrno));
5396     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
5397       cmd->arg);
5398 
5399     pr_cmd_set_errno(cmd, xerrno);
5400     errno = xerrno;
5401     return PR_ERROR(cmd);
5402   }
5403 
5404   dir = decoded_path;
5405 
5406   res = pr_filter_allow_path(CURRENT_CONF, dir);
5407   switch (res) {
5408     case 0:
5409       break;
5410 
5411     case PR_FILTER_ERR_FAILS_ALLOW_FILTER:
5412       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathAllowFilter",
5413         (char *) cmd->argv[0], dir);
5414       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
5415 
5416       pr_cmd_set_errno(cmd, EPERM);
5417       errno = EPERM;
5418       return PR_ERROR(cmd);
5419 
5420     case PR_FILTER_ERR_FAILS_DENY_FILTER:
5421       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathDenyFilter",
5422         (char *) cmd->argv[0], dir);
5423       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
5424 
5425       pr_cmd_set_errno(cmd, EPERM);
5426       errno = EPERM;
5427       return PR_ERROR(cmd);
5428   }
5429 
5430   dir = dir_canonical_path(cmd->tmp_pool, dir);
5431   if (dir == NULL) {
5432     int xerrno = EINVAL;
5433 
5434     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5435 
5436     pr_cmd_set_errno(cmd, xerrno);
5437     errno = xerrno;
5438     return PR_ERROR(cmd);
5439   }
5440 
5441   if (!dir_check_canon(cmd->tmp_pool, cmd, cmd->group, dir, NULL)) {
5442     int xerrno = EACCES;
5443 
5444     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5445 
5446     pr_cmd_set_errno(cmd, xerrno);
5447     errno = xerrno;
5448     return PR_ERROR(cmd);
5449   }
5450 
5451   res = pr_fsio_rmdir_with_error(cmd->pool, dir, &err);
5452   if (res < 0) {
5453     int xerrno = errno;
5454 
5455     pr_error_set_where(err, &core_module, __FILE__, __LINE__ - 4);
5456     pr_error_set_why(err, pstrcat(cmd->pool, "remove directory '", dir, "'",
5457       NULL));
5458 
5459     (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
5460       "error removing directory '%s': %s", (char *) cmd->argv[0], session.user,
5461       pr_uid2str(cmd->tmp_pool, session.uid),
5462       pr_gid2str(cmd->tmp_pool, session.gid), dir, strerror(xerrno));
5463 
5464     if (err != NULL) {
5465       pr_log_debug(DEBUG9, "%s", pr_error_strerror(err, 0));
5466       pr_error_destroy(err);
5467       err = NULL;
5468 
5469     } else {
5470       pr_log_debug(DEBUG9, "error removing directory '%s': %s", dir,
5471         strerror(xerrno));
5472     }
5473 
5474     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5475 
5476     pr_cmd_set_errno(cmd, xerrno);
5477     errno = xerrno;
5478     return PR_ERROR(cmd);
5479   }
5480 
5481   pr_response_add(R_250, _("%s command successful"), (char *) cmd->argv[0]);
5482   return PR_HANDLED(cmd);
5483 }
5484 
core_mkd(cmd_rec * cmd)5485 MODRET core_mkd(cmd_rec *cmd) {
5486   int res;
5487   char *decoded_path, *dir;
5488 
5489   CHECK_CMD_MIN_ARGS(cmd, 2);
5490 
5491   /* XXX Why is there a check to prevent the creation of any directory
5492    * name containing an asterisk?
5493    */
5494   if (strchr(cmd->arg, '*')) {
5495     pr_response_add_err(R_550, _("%s: Invalid directory name"), cmd->arg);
5496 
5497     pr_cmd_set_errno(cmd, EINVAL);
5498     errno = EINVAL;
5499     return PR_ERROR(cmd);
5500   }
5501 
5502   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
5503     FSIO_DECODE_FL_TELL_ERRORS);
5504   if (decoded_path == NULL) {
5505     int xerrno = errno;
5506 
5507     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
5508       strerror(xerrno));
5509     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
5510       cmd->arg);
5511 
5512     pr_cmd_set_errno(cmd, xerrno);
5513     errno = xerrno;
5514     return PR_ERROR(cmd);
5515   }
5516 
5517   dir = decoded_path;
5518 
5519   res = pr_filter_allow_path(CURRENT_CONF, dir);
5520   switch (res) {
5521     case 0:
5522       break;
5523 
5524     case PR_FILTER_ERR_FAILS_ALLOW_FILTER:
5525       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathAllowFilter",
5526         (char *) cmd->argv[0], dir);
5527       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
5528 
5529       pr_cmd_set_errno(cmd, EPERM);
5530       errno = EPERM;
5531       return PR_ERROR(cmd);
5532 
5533     case PR_FILTER_ERR_FAILS_DENY_FILTER:
5534       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathDenyFilter",
5535         (char *) cmd->argv[0], dir);
5536       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
5537 
5538       pr_cmd_set_errno(cmd, EPERM);
5539       errno = EPERM;
5540       return PR_ERROR(cmd);
5541   }
5542 
5543   dir = dir_canonical_path(cmd->tmp_pool, dir);
5544   if (dir == NULL) {
5545     int xerrno = EINVAL;
5546 
5547     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5548 
5549     pr_cmd_set_errno(cmd, xerrno);
5550     errno = xerrno;
5551     return PR_ERROR(cmd);
5552   }
5553 
5554   if (!dir_check_canon(cmd->tmp_pool, cmd, cmd->group, dir, NULL)) {
5555     int xerrno = EACCES;
5556 
5557     pr_log_debug(DEBUG8, "%s command denied by <Limit> config",
5558       (char *) cmd->argv[0]);
5559     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5560 
5561     pr_cmd_set_errno(cmd, xerrno);
5562     errno = xerrno;
5563     return PR_ERROR(cmd);
5564   }
5565 
5566   if (pr_fsio_smkdir(cmd->tmp_pool, dir, 0777, session.fsuid,
5567       session.fsgid) < 0) {
5568     int xerrno = errno;
5569 
5570     (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
5571       "error making directory '%s': %s", (char *) cmd->argv[0], session.user,
5572       pr_uid2str(cmd->tmp_pool, session.uid),
5573       pr_gid2str(cmd->tmp_pool, session.gid), dir, strerror(xerrno));
5574 
5575     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5576 
5577     pr_cmd_set_errno(cmd, xerrno);
5578     errno = xerrno;
5579     return PR_ERROR(cmd);
5580   }
5581 
5582   pr_response_add(R_257, _("\"%s\" - Directory successfully created"),
5583     quote_dir(cmd->tmp_pool, dir));
5584 
5585   return PR_HANDLED(cmd);
5586 }
5587 
core_cwd(cmd_rec * cmd)5588 MODRET core_cwd(cmd_rec *cmd) {
5589   char *decoded_path;
5590   CHECK_CMD_MIN_ARGS(cmd, 2);
5591 
5592   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
5593     FSIO_DECODE_FL_TELL_ERRORS);
5594   if (decoded_path == NULL) {
5595     int xerrno = errno;
5596 
5597     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
5598       strerror(xerrno));
5599     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
5600       cmd->arg);
5601 
5602     pr_cmd_set_errno(cmd, xerrno);
5603     errno = xerrno;
5604     return PR_ERROR(cmd);
5605   }
5606 
5607   return core_chdir(cmd, decoded_path);
5608 }
5609 
core_cdup(cmd_rec * cmd)5610 MODRET core_cdup(cmd_rec *cmd) {
5611   CHECK_CMD_ARGS(cmd, 1);
5612   return core_chdir(cmd, "..");
5613 }
5614 
5615 /* Returns the modification time of a file, as per RFC3659. */
core_mdtm(cmd_rec * cmd)5616 MODRET core_mdtm(cmd_rec *cmd) {
5617   char *decoded_path, *path;
5618   struct stat st;
5619 
5620   CHECK_CMD_MIN_ARGS(cmd, 2);
5621 
5622   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
5623     FSIO_DECODE_FL_TELL_ERRORS);
5624   if (decoded_path == NULL) {
5625     int xerrno = errno;
5626 
5627     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
5628       strerror(xerrno));
5629     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
5630       cmd->arg);
5631 
5632     pr_cmd_set_errno(cmd, xerrno);
5633     errno = xerrno;
5634     return PR_ERROR(cmd);
5635   }
5636 
5637   path = decoded_path;
5638 
5639   pr_fs_clear_cache2(path);
5640   path = dir_realpath(cmd->tmp_pool, decoded_path);
5641   if (!path ||
5642       !dir_check(cmd->tmp_pool, cmd, cmd->group, path, NULL) ||
5643       pr_fsio_stat(path, &st) == -1) {
5644     int xerrno = errno;
5645 
5646     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5647 
5648     pr_cmd_set_errno(cmd, xerrno);
5649     errno = xerrno;
5650     return PR_ERROR(cmd);
5651 
5652   } else {
5653     if (!S_ISREG(st.st_mode)) {
5654       pr_response_add_err(R_550, _("%s: not a plain file"), cmd->arg);
5655 
5656       pr_cmd_set_errno(cmd, EINVAL);
5657       errno = EINVAL;
5658       return PR_ERROR(cmd);
5659 
5660     } else {
5661       char buf[16];
5662       struct tm *tm;
5663 
5664       memset(buf, '\0', sizeof(buf));
5665 
5666       tm = pr_gmtime(cmd->tmp_pool, &st.st_mtime);
5667       if (tm != NULL) {
5668         pr_snprintf(buf, sizeof(buf), "%04d%02d%02d%02d%02d%02d",
5669           tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour,
5670           tm->tm_min, tm->tm_sec);
5671 
5672       } else {
5673         pr_snprintf(buf, sizeof(buf), "00000000000000");
5674       }
5675 
5676       pr_response_add(R_213, "%s", buf);
5677     }
5678   }
5679 
5680   return PR_HANDLED(cmd);
5681 }
5682 
core_size(cmd_rec * cmd)5683 MODRET core_size(cmd_rec *cmd) {
5684   char *decoded_path, *path;
5685   struct stat st;
5686 
5687   CHECK_CMD_MIN_ARGS(cmd, 2);
5688 
5689   /* The PR_ALLOW_ASCII_MODE_SIZE macro should ONLY be defined at compile time,
5690    * e.g. using:
5691    *
5692    *  $ ./configure CPPFLAGS=-DPR_ALLOW_ASCII_MODE_SIZE ...
5693    *
5694    * Define this macro if you want proftpd to handle a SIZE command while in
5695    * ASCII mode.  Note, however, that ProFTPD will NOT properly calculate
5696    * CRLF sequences EVEN if this macro is defined: ProFTPD will always return
5697    * the number of bytes on disk for the requested file, even if the number of
5698    * bytes transferred when that file is downloaded is different.  Thus this
5699    * behavior will not comply with RFC 3659, Section 4.  Caveat emptor.
5700    */
5701 #ifndef PR_ALLOW_ASCII_MODE_SIZE
5702   /* Refuse the command if we're in ASCII mode. */
5703   if (session.sf_flags & SF_ASCII) {
5704     pr_log_debug(DEBUG5, "%s not allowed in ASCII mode", (char *) cmd->argv[0]);
5705     pr_response_add_err(R_550, _("%s not allowed in ASCII mode"),
5706       (char *) cmd->argv[0]);
5707 
5708     pr_cmd_set_errno(cmd, EPERM);
5709     errno = EPERM;
5710     return PR_ERROR(cmd);
5711   }
5712 #endif /* PR_ALLOW_ASCII_MODE_SIZE */
5713 
5714   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
5715     FSIO_DECODE_FL_TELL_ERRORS);
5716   if (decoded_path == NULL) {
5717     int xerrno = errno;
5718 
5719     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
5720       strerror(xerrno));
5721     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
5722       cmd->arg);
5723 
5724     pr_cmd_set_errno(cmd, xerrno);
5725     errno = xerrno;
5726     return PR_ERROR(cmd);
5727   }
5728 
5729   pr_fs_clear_cache2(decoded_path);
5730   path = dir_realpath(cmd->tmp_pool, decoded_path);
5731   if (path != NULL) {
5732     pr_fs_clear_cache2(path);
5733   }
5734 
5735   if (path == NULL ||
5736       !dir_check(cmd->tmp_pool, cmd, cmd->group, path, NULL) ||
5737       pr_fsio_stat(path, &st) == -1) {
5738     int xerrno = errno;
5739 
5740     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5741 
5742     pr_cmd_set_errno(cmd, xerrno);
5743     errno = xerrno;
5744     return PR_ERROR(cmd);
5745 
5746   } else {
5747     if (!S_ISREG(st.st_mode)) {
5748       pr_response_add_err(R_550, _("%s: not a regular file"), cmd->arg);
5749 
5750       pr_cmd_set_errno(cmd, EINVAL);
5751       errno = EINVAL;
5752       return PR_ERROR(cmd);
5753 
5754     } else {
5755       pr_response_add(R_213, "%" PR_LU, (pr_off_t) st.st_size);
5756     }
5757   }
5758 
5759   return PR_HANDLED(cmd);
5760 }
5761 
core_dele(cmd_rec * cmd)5762 MODRET core_dele(cmd_rec *cmd) {
5763   int res;
5764   char *decoded_path, *path, *fullpath;
5765   struct stat st;
5766   pr_error_t *err = NULL;
5767 
5768   CHECK_CMD_MIN_ARGS(cmd, 2);
5769 
5770   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
5771     FSIO_DECODE_FL_TELL_ERRORS);
5772   if (decoded_path == NULL) {
5773     int xerrno = errno;
5774 
5775     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
5776       strerror(xerrno));
5777     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
5778       cmd->arg);
5779 
5780     pr_cmd_set_errno(cmd, xerrno);
5781     errno = xerrno;
5782     return PR_ERROR(cmd);
5783   }
5784 
5785   path = decoded_path;
5786 
5787   res = pr_filter_allow_path(CURRENT_CONF, path);
5788   switch (res) {
5789     case 0:
5790       break;
5791 
5792     case PR_FILTER_ERR_FAILS_ALLOW_FILTER:
5793       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathAllowFilter",
5794         (char *) cmd->argv[0], path);
5795       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
5796 
5797       pr_cmd_set_errno(cmd, EPERM);
5798       errno = EPERM;
5799       return PR_ERROR(cmd);
5800 
5801     case PR_FILTER_ERR_FAILS_DENY_FILTER:
5802       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathDenyFilter",
5803         (char *) cmd->argv[0], path);
5804       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
5805 
5806       pr_cmd_set_errno(cmd, EPERM);
5807       errno = EPERM;
5808       return PR_ERROR(cmd);
5809   }
5810 
5811   /* If told to delete a symlink, don't delete the file it points to!  */
5812   path = dir_canonical_path(cmd->tmp_pool, path);
5813   if (path == NULL) {
5814     int xerrno = ENOENT;
5815 
5816     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5817 
5818     pr_cmd_set_errno(cmd, xerrno);
5819     errno = xerrno;
5820     return PR_ERROR(cmd);
5821   }
5822 
5823   if (!dir_check(cmd->tmp_pool, cmd, cmd->group, path, NULL)) {
5824     int xerrno = errno;
5825 
5826     pr_log_debug(DEBUG7, "deleting '%s' denied by <Limit> configuration", path);
5827     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5828 
5829     pr_cmd_set_errno(cmd, xerrno);
5830     errno = xerrno;
5831     return PR_ERROR(cmd);
5832   }
5833 
5834   /* Stat the path, before it is deleted, so that the size of the file
5835    * being deleted can be logged.  Note that unlink() doesn't follow symlinks,
5836    * so we need to use lstat(), not stat(), lest we log the wrong size.
5837    */
5838   memset(&st, 0, sizeof(st));
5839   pr_fs_clear_cache2(path);
5840   res = pr_fsio_lstat_with_error(cmd->tmp_pool, path, &st, &err);
5841   if (res < 0) {
5842     int xerrno = errno;
5843 
5844     pr_error_set_where(err, &core_module, __FILE__, __LINE__ - 4);
5845     pr_error_set_why(err, pstrcat(cmd->pool, "check file '", path, "'", NULL));
5846 
5847     if (err != NULL) {
5848       pr_log_debug(DEBUG3, "%s", pr_error_strerror(err, 0));
5849       pr_error_destroy(err);
5850       err = NULL;
5851 
5852     } else {
5853       pr_log_debug(DEBUG3, "unable to lstat '%s': %s", path, strerror(xerrno));
5854     }
5855 
5856     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5857 
5858     pr_cmd_set_errno(cmd, xerrno);
5859     errno = xerrno;
5860     return PR_ERROR(cmd);
5861   }
5862 
5863 #ifdef EISDIR
5864   /* If the path is a directory, try to return a good error message (e.g.
5865    * EISDIR).
5866    */
5867   if (S_ISDIR(st.st_mode)) {
5868     int xerrno = EISDIR;
5869 
5870     (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
5871       "error deleting '%s': %s", (char *) cmd->argv[0], session.user,
5872       pr_uid2str(cmd->tmp_pool, session.uid),
5873       pr_gid2str(cmd->tmp_pool, session.gid), path, strerror(xerrno));
5874 
5875     pr_log_debug(DEBUG3, "error deleting '%s': %s", path, strerror(xerrno));
5876     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5877 
5878     pr_cmd_set_errno(cmd, xerrno);
5879     errno = xerrno;
5880     return PR_ERROR(cmd);
5881   }
5882 #endif /* !EISDIR */
5883 
5884   res = pr_fsio_unlink_with_error(cmd->pool, path, &err);
5885   if (res < 0) {
5886     int xerrno = errno;
5887 
5888     pr_error_set_where(err, &core_module, __FILE__, __LINE__ - 4);
5889     pr_error_set_why(err, pstrcat(cmd->pool, "delete file '", path, "'", NULL));
5890 
5891     (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
5892       "error deleting '%s': %s", (char *) cmd->argv[0], session.user,
5893       pr_uid2str(cmd->tmp_pool, session.uid),
5894       pr_gid2str(cmd->tmp_pool, session.gid), path, strerror(xerrno));
5895 
5896     if (err != NULL) {
5897       pr_log_debug(DEBUG3, "%s", pr_error_strerror(err, 0));
5898       pr_error_destroy(err);
5899       err = NULL;
5900 
5901     } else {
5902       pr_log_debug(DEBUG3, "error deleting '%s': %s", path, strerror(xerrno));
5903     }
5904 
5905     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
5906 
5907     pr_cmd_set_errno(cmd, xerrno);
5908     errno = xerrno;
5909     return PR_ERROR(cmd);
5910   }
5911 
5912   fullpath = dir_abs_path(cmd->tmp_pool, path, TRUE);
5913 
5914   if (session.sf_flags & SF_ANON) {
5915     xferlog_write(0, session.c->remote_name, st.st_size, fullpath,
5916       (session.sf_flags & SF_ASCII ? 'a' : 'b'), 'd', 'a', session.anon_user,
5917       'c', "_");
5918 
5919   } else {
5920     xferlog_write(0, session.c->remote_name, st.st_size, fullpath,
5921       (session.sf_flags & SF_ASCII ? 'a' : 'b'), 'd', 'r', session.user, 'c',
5922       "_");
5923   }
5924 
5925   pr_response_add(R_250, _("%s command successful"), (char *) cmd->argv[0]);
5926   return PR_HANDLED(cmd);
5927 }
5928 
core_rnto(cmd_rec * cmd)5929 MODRET core_rnto(cmd_rec *cmd) {
5930   int res;
5931   char *decoded_path, *path;
5932   unsigned char *allow_overwrite = NULL;
5933   struct stat st;
5934   pr_error_t *err = NULL;
5935 
5936   CHECK_CMD_MIN_ARGS(cmd, 2);
5937 
5938   if (!session.xfer.path) {
5939     if (session.xfer.p) {
5940       destroy_pool(session.xfer.p);
5941       memset(&session.xfer, '\0', sizeof(session.xfer));
5942     }
5943 
5944     pr_response_add_err(R_503, _("Bad sequence of commands"));
5945 
5946     pr_cmd_set_errno(cmd, EPERM);
5947     errno = EPERM;
5948     return PR_ERROR(cmd);
5949   }
5950 
5951   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
5952     FSIO_DECODE_FL_TELL_ERRORS);
5953   if (decoded_path == NULL) {
5954     int xerrno = errno;
5955 
5956     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
5957       strerror(xerrno));
5958     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
5959       cmd->arg);
5960 
5961     pr_cmd_set_errno(cmd, xerrno);
5962     errno = xerrno;
5963     return PR_ERROR(cmd);
5964   }
5965 
5966   path = decoded_path;
5967 
5968   res = pr_filter_allow_path(CURRENT_CONF, path);
5969   switch (res) {
5970     case 0:
5971       break;
5972 
5973     case PR_FILTER_ERR_FAILS_ALLOW_FILTER:
5974       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathAllowFilter",
5975         (char *) cmd->argv[0], path);
5976       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
5977 
5978       pr_cmd_set_errno(cmd, EPERM);
5979       errno = EPERM;
5980       return PR_ERROR(cmd);
5981 
5982     case PR_FILTER_ERR_FAILS_DENY_FILTER:
5983       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathDenyFilter",
5984         (char *) cmd->argv[0], path);
5985       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
5986 
5987       pr_cmd_set_errno(cmd, EPERM);
5988       errno = EPERM;
5989       return PR_ERROR(cmd);
5990   }
5991 
5992   path = dir_canonical_path(cmd->tmp_pool, path);
5993 
5994   allow_overwrite = get_param_ptr(CURRENT_CONF, "AllowOverwrite", FALSE);
5995 
5996   /* Deny the rename if AllowOverwrites are not allowed, and the destination
5997    * rename file already exists.
5998    */
5999   pr_fs_clear_cache2(path);
6000   if ((!allow_overwrite || *allow_overwrite == FALSE) &&
6001       pr_fsio_stat(path, &st) == 0) {
6002     pr_log_debug(DEBUG6, "AllowOverwrite denied permission for %s", path);
6003     pr_response_add_err(R_550, _("%s: Rename permission denied"), cmd->arg);
6004 
6005     pr_cmd_set_errno(cmd, EACCES);
6006     errno = EACCES;
6007     return PR_ERROR(cmd);
6008   }
6009 
6010   if (!path ||
6011       !dir_check_canon(cmd->tmp_pool, cmd, cmd->group, path, NULL)) {
6012     pr_response_add_err(R_550, _("%s: %s"), cmd->arg, strerror(EPERM));
6013 
6014     pr_cmd_set_errno(cmd, EPERM);
6015     errno = EPERM;
6016     return PR_ERROR(cmd);
6017   }
6018 
6019   res = pr_fsio_rename_with_error(cmd->pool, session.xfer.path, path, &err);
6020   if (res < 0) {
6021     int xerrno = errno;
6022 
6023     pr_error_set_where(err, &core_module, __FILE__, __LINE__ - 4);
6024     pr_error_set_why(err, pstrcat(cmd->pool, "rename '", session.xfer.path,
6025       "' to '", path, "'", NULL));
6026 
6027     if (xerrno == EISDIR) {
6028       /* In this case, the client has requested that a directory be renamed
6029        * across mount points.  The pr_fs_copy_file() function can't handle
6030        * copying directories; it only knows about files.  (This could be
6031        * fixed to work later, e.g. using code from the mod_copy module.)
6032        *
6033        * For now, error out now with a more informative error message to the
6034        * client.
6035        */
6036 
6037       (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
6038         "error copying '%s' to '%s': %s (previous error was '%s')",
6039         (char *) cmd->argv[0], session.user,
6040         pr_uid2str(cmd->tmp_pool, session.uid),
6041         pr_gid2str(cmd->tmp_pool, session.gid), session.xfer.path, path,
6042         strerror(xerrno), strerror(EXDEV));
6043 
6044       pr_log_debug(DEBUG4,
6045         "Cannot rename directory '%s' across a filesystem mount point",
6046         session.xfer.path);
6047 
6048       if (err != NULL) {
6049         pr_error_destroy(err);
6050         err = NULL;
6051       }
6052 
6053       /* Use EPERM, rather than EISDIR, to get slightly more informative
6054        * error messages.
6055        */
6056       xerrno = EPERM;
6057 
6058       pr_response_add_err(R_550, _("%s: %s"), cmd->arg, strerror(xerrno));
6059 
6060       pr_cmd_set_errno(cmd, xerrno);
6061       errno = xerrno;
6062       return PR_ERROR(cmd);
6063     }
6064 
6065     if (xerrno != EXDEV) {
6066       (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
6067         "error renaming '%s' to '%s': %s", (char *) cmd->argv[0], session.user,
6068         pr_uid2str(cmd->tmp_pool, session.uid),
6069         pr_gid2str(cmd->tmp_pool, session.gid), session.xfer.path, path,
6070         strerror(xerrno));
6071 
6072       if (err != NULL) {
6073         pr_log_debug(DEBUG9, "%s", pr_error_strerror(err, 0));
6074         pr_error_destroy(err);
6075         err = NULL;
6076       }
6077 
6078       pr_response_add_err(R_550, _("%s: %s"), cmd->arg, strerror(xerrno));
6079 
6080       pr_cmd_set_errno(cmd, xerrno);
6081       errno = xerrno;
6082       return PR_ERROR(cmd);
6083     }
6084 
6085     /* In this case, we'll need to manually copy the file from the source
6086      * to the destination paths.
6087      */
6088     if (pr_fs_copy_file(session.xfer.path, path) < 0) {
6089       xerrno = errno;
6090 
6091       (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): "
6092         "error copying '%s' to '%s': %s", (char *) cmd->argv[0], session.user,
6093         pr_uid2str(cmd->tmp_pool, session.uid),
6094         pr_gid2str(cmd->tmp_pool, session.gid), session.xfer.path, path,
6095         strerror(xerrno));
6096 
6097       pr_response_add_err(R_550, _("Rename %s: %s"), cmd->arg,
6098         strerror(xerrno));
6099 
6100       pr_cmd_set_errno(cmd, xerrno);
6101       errno = xerrno;
6102       return PR_ERROR(cmd);
6103     }
6104 
6105     /* Once copied, unlink the original file. */
6106     res = pr_fsio_unlink_with_error(cmd->pool, session.xfer.path, &err);
6107     if (res < 0) {
6108       xerrno = errno;
6109 
6110       pr_error_set_where(err, &core_module, __FILE__, __LINE__ - 4);
6111       pr_error_set_why(err, pstrcat(cmd->pool, "delete file '",
6112         session.xfer.path, "'", NULL));
6113 
6114       if (err != NULL) {
6115         pr_log_debug(DEBUG0, "%s", pr_error_strerror(err, 0));
6116         pr_error_destroy(err);
6117         err = NULL;
6118 
6119       } else {
6120         pr_log_debug(DEBUG0, "error deleting '%s': %s", session.xfer.path,
6121           strerror(xerrno));
6122       }
6123     }
6124   }
6125 
6126   /* Change the xfer path to the name of the destination file, for logging. */
6127   session.xfer.path = pstrdup(session.xfer.p, path);
6128 
6129   pr_response_add(R_250, _("Rename successful"));
6130   return PR_HANDLED(cmd);
6131 }
6132 
core_rnto_cleanup(cmd_rec * cmd)6133 MODRET core_rnto_cleanup(cmd_rec *cmd) {
6134   if (session.xfer.p)
6135     destroy_pool(session.xfer.p);
6136 
6137   memset(&session.xfer, '\0', sizeof(session.xfer));
6138 
6139   pr_table_remove(session.notes, "mod_core.rnfr-path", NULL);
6140   return PR_DECLINED(cmd);
6141 }
6142 
core_rnfr(cmd_rec * cmd)6143 MODRET core_rnfr(cmd_rec *cmd) {
6144   int res;
6145   char *decoded_path, *path;
6146 
6147   CHECK_CMD_MIN_ARGS(cmd, 2);
6148 
6149   decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
6150     FSIO_DECODE_FL_TELL_ERRORS);
6151   if (decoded_path == NULL) {
6152     int xerrno = errno;
6153 
6154     pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
6155       strerror(xerrno));
6156     pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
6157       cmd->arg);
6158 
6159     pr_cmd_set_errno(cmd, xerrno);
6160     errno = xerrno;
6161     return PR_ERROR(cmd);
6162   }
6163 
6164   path = decoded_path;
6165 
6166   res = pr_filter_allow_path(CURRENT_CONF, path);
6167   switch (res) {
6168     case 0:
6169       break;
6170 
6171     case PR_FILTER_ERR_FAILS_ALLOW_FILTER:
6172       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathAllowFilter",
6173         (char *) cmd->argv[0], path);
6174       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
6175 
6176       pr_cmd_set_errno(cmd, EPERM);
6177       errno = EPERM;
6178       return PR_ERROR(cmd);
6179 
6180     case PR_FILTER_ERR_FAILS_DENY_FILTER:
6181       pr_log_pri(PR_LOG_NOTICE, "'%s %s' denied by PathDenyFilter",
6182         (char *) cmd->argv[0], path);
6183       pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg);
6184 
6185       pr_cmd_set_errno(cmd, EPERM);
6186       errno = EPERM;
6187       return PR_ERROR(cmd);
6188   }
6189 
6190   /* Allow renaming a symlink, even a dangling one. */
6191   path = dir_canonical_path(cmd->tmp_pool, path);
6192 
6193   if (path == NULL ||
6194       !dir_check(cmd->tmp_pool, cmd, cmd->group, path, NULL) ||
6195       !exists2(cmd->tmp_pool, path)) {
6196     int xerrno = errno;
6197 
6198     pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
6199 
6200     pr_cmd_set_errno(cmd, xerrno);
6201     errno = xerrno;
6202     return PR_ERROR(cmd);
6203   }
6204 
6205   /* We store the path in session.xfer.path */
6206   if (session.xfer.p) {
6207     destroy_pool(session.xfer.p);
6208     memset(&session.xfer, '\0', sizeof(session.xfer));
6209   }
6210 
6211   session.xfer.p = make_sub_pool(session.pool);
6212   pr_pool_tag(session.xfer.p, "session xfer pool");
6213 
6214   session.xfer.path = pstrdup(session.xfer.p, path);
6215 
6216   pr_table_add(session.notes, "mod_core.rnfr-path",
6217     pstrdup(session.xfer.p, session.xfer.path), 0);
6218 
6219   pr_response_add(R_350,
6220     _("File or directory exists, ready for destination name"));
6221   return PR_HANDLED(cmd);
6222 }
6223 
core_noop(cmd_rec * cmd)6224 MODRET core_noop(cmd_rec *cmd) {
6225   if (!dir_check(cmd->tmp_pool, cmd, cmd->group, session.vwd, NULL)) {
6226     int xerrno = EPERM;
6227 
6228     pr_response_add_err(R_550, "%s", strerror(xerrno));
6229 
6230     pr_cmd_set_errno(cmd, xerrno);
6231     errno = xerrno;
6232     return PR_ERROR(cmd);
6233   }
6234 
6235   pr_response_add(R_200, _("NOOP command successful"));
6236   return PR_HANDLED(cmd);
6237 }
6238 
feat_cmp(const void * a,const void * b)6239 static int feat_cmp(const void *a, const void *b) {
6240   return strcasecmp(*((const char **) a), *((const char **) b));
6241 }
6242 
core_feat(cmd_rec * cmd)6243 MODRET core_feat(cmd_rec *cmd) {
6244   register unsigned int i;
6245   const char *feat = NULL;
6246   array_header *feats = NULL;
6247 
6248   CHECK_CMD_ARGS(cmd, 1);
6249 
6250   if (!dir_check(cmd->tmp_pool, cmd, cmd->group, session.vwd, NULL)) {
6251     int xerrno = EPERM;
6252 
6253     pr_log_debug(DEBUG3, "%s command denied by <Limit> configuration",
6254       (char *) cmd->argv[0]);
6255     pr_response_add_err(R_550, "%s: %s", (char *) cmd->argv[0],
6256       strerror(xerrno));
6257 
6258     pr_cmd_set_errno(cmd, xerrno);
6259     errno = xerrno;
6260     return PR_ERROR(cmd);
6261   }
6262 
6263   feat = pr_feat_get();
6264   if (feat == NULL) {
6265     pr_response_add(R_211, _("No features supported"));
6266     return PR_HANDLED(cmd);
6267   }
6268 
6269   feats = make_array(cmd->tmp_pool, 0, sizeof(char **));
6270 
6271   while (feat != NULL) {
6272     pr_signals_handle();
6273     *((char **) push_array(feats)) = pstrdup(cmd->tmp_pool, feat);
6274     feat = pr_feat_get_next();
6275   }
6276 
6277   /* Sort the features, for a prettier output. */
6278   qsort(feats->elts, feats->nelts, sizeof(char *), feat_cmp);
6279 
6280   pr_response_add(R_211, "%s", _("Features:"));
6281   for (i = 0; i < feats->nelts; i++) {
6282     pr_response_add(R_DUP, "%s", ((const char **) feats->elts)[i]);
6283   }
6284   pr_response_add(R_DUP, _("End"));
6285 
6286   return PR_HANDLED(cmd);
6287 }
6288 
core_opts(cmd_rec * cmd)6289 MODRET core_opts(cmd_rec *cmd) {
6290   register unsigned int i;
6291   int res;
6292   char *arg = "";
6293   cmd_rec *subcmd;
6294 
6295   CHECK_CMD_MIN_ARGS(cmd, 2);
6296 
6297   /* Impose a maximum number of allowed arguments, to prevent malicious
6298    * clients from trying to do Bad Things(tm).  See Bug#3870.
6299    */
6300   if ((cmd->argc-1) > PR_OPTS_MAX_PARAM_COUNT) {
6301     int xerrno = EINVAL;
6302 
6303     pr_log_debug(DEBUG2,
6304       "OPTS command with too many parameters (%d), rejecting", cmd->argc-1);
6305     pr_response_add_err(R_550, "%s: %s", (char *) cmd->argv[0],
6306       strerror(xerrno));
6307 
6308     pr_cmd_set_errno(cmd, xerrno);
6309     errno = xerrno;
6310     return PR_ERROR(cmd);
6311   }
6312 
6313   subcmd = pr_cmd_alloc(cmd->tmp_pool, cmd->argc-1, NULL);
6314   subcmd->argv[0] = pstrcat(cmd->tmp_pool, "OPTS_", cmd->argv[1], NULL);
6315   subcmd->group = cmd->group;
6316 
6317   if (!dir_check(cmd->tmp_pool, subcmd, subcmd->group, session.vwd, NULL)) {
6318     int xerrno = EACCES;
6319 
6320     pr_log_debug(DEBUG7, "OPTS %s denied by <Limit> configuration",
6321       (char *) cmd->argv[1]);
6322     pr_response_add_err(R_550, "%s %s: %s", (char *) cmd->argv[0],
6323       (char *) cmd->argv[1], strerror(xerrno));
6324 
6325     pr_cmd_set_errno(cmd, xerrno);
6326     errno = xerrno;
6327     return PR_ERROR(cmd);
6328   }
6329 
6330   for (i = 2; i < cmd->argc; i++) {
6331     subcmd->argv[i-1] = cmd->argv[i];
6332 
6333     arg = pstrcat(cmd->tmp_pool, arg, *arg ? " " : "", cmd->argv[i], NULL);
6334   }
6335 
6336   subcmd->arg = arg;
6337 
6338   res = pr_cmd_dispatch(subcmd);
6339   if (res < 0) {
6340     return PR_ERROR(cmd);
6341   }
6342 
6343   return PR_HANDLED(cmd);
6344 }
6345 
core_post_pass(cmd_rec * cmd)6346 MODRET core_post_pass(cmd_rec *cmd) {
6347   config_rec *c;
6348 
6349   c = find_config(TOPLEVEL_CONF, CONF_PARAM, "TimeoutIdle", FALSE);
6350   if (c != NULL) {
6351     int prev_timeout, timeout;
6352 
6353     prev_timeout = pr_data_get_timeout(PR_DATA_TIMEOUT_IDLE);
6354     timeout = *((int *) c->argv[0]);
6355 
6356     if (timeout != prev_timeout) {
6357       pr_data_set_timeout(PR_DATA_TIMEOUT_IDLE, timeout);
6358 
6359       /* Remove the old timer, and add a new one with the changed
6360        * timeout value.
6361        */
6362       pr_timer_remove(PR_TIMER_IDLE, &core_module);
6363 
6364       if (timeout > 0) {
6365         pr_timer_add(timeout, PR_TIMER_IDLE, &core_module, core_idle_timeout_cb,
6366           "TimeoutIdle");
6367       }
6368     }
6369   }
6370 
6371 #ifdef PR_USE_TRACE
6372   /* Handle any user/group-specific Trace settings. */
6373   c = find_config(main_server->conf, CONF_PARAM, "Trace", FALSE);
6374   if (c != NULL) {
6375     register unsigned int i;
6376 
6377     for (i = 0; i < c->argc; i++) {
6378       char *channel, *ptr;
6379       int min_level, max_level, res;
6380 
6381       pr_signals_handle();
6382 
6383       channel = c->argv[i];
6384       ptr = strchr(channel, ':');
6385       if (ptr == NULL) {
6386         pr_log_debug(DEBUG6, "skipping badly formatted '%s' setting",
6387           channel);
6388         continue;
6389       }
6390 
6391       *ptr = '\0';
6392 
6393       res = pr_trace_parse_levels(ptr + 1, &min_level, &max_level);
6394       if (res == 0) {
6395         res = pr_trace_set_levels(channel, min_level, max_level);
6396         *ptr = ':';
6397 
6398         if (res < 0) {
6399           pr_log_debug(DEBUG6, "%s: error setting levels %d-%d for "
6400             "channel '%s': %s", c->name, min_level, max_level, channel,
6401             strerror(errno));
6402         }
6403 
6404       } else {
6405         pr_log_debug(DEBUG6, "%s: error parsing level '%s' for channel '%s': "
6406           "%s", c->name, ptr + 1, channel, strerror(errno));
6407       }
6408     }
6409   }
6410 
6411   /* Handle any user/group-specific TraceOptions settings. */
6412   c = find_config(main_server->conf, CONF_PARAM, "TraceOptions", FALSE);
6413   if (c != NULL) {
6414     unsigned long trace_opts;
6415 
6416     trace_opts = *((unsigned long *) c->argv[0]);
6417     if (pr_trace_set_options(trace_opts) < 0) {
6418       pr_log_debug(DEBUG6, "%s: error setting TraceOptions (%lu): %s",
6419         c->name, trace_opts, strerror(errno));
6420     }
6421   }
6422 #endif /* PR_USE_TRACE */
6423 
6424   /* If "SocketOptions keepalive off" is in effect, disable TCP keepalives
6425    * for the control connection as well (Bug#4340).
6426    */
6427   if (main_server->tcp_keepalive->keepalive_enabled == FALSE) {
6428     int keepalive = 0;
6429 
6430     pr_trace_msg("inet", 17, "disabling SO_KEEPALIVE on socket fd %d",
6431       session.c->wfd);
6432     if (setsockopt(session.c->wfd, SOL_SOCKET, SO_KEEPALIVE, (void *)
6433         &keepalive, sizeof(int)) < 0) {
6434       pr_log_pri(PR_LOG_NOTICE,
6435         "error setting SO_KEEPALIVE on socket fd %d: %s", session.c->wfd,
6436         strerror(errno));
6437 
6438     } else {
6439       pr_trace_msg("inet", 15, "disabled SO_KEEPALIVE on socket fd %d",
6440         session.c->wfd);
6441     }
6442   }
6443 
6444   /* Look for a configured MaxCommandRate. */
6445   c = find_config(main_server->conf, CONF_PARAM, "MaxCommandRate", FALSE);
6446   if (c) {
6447     core_cmd_count = 0UL;
6448     core_max_cmds = *((unsigned long *) c->argv[0]);
6449     core_max_cmd_interval = *((unsigned int *) c->argv[1]);
6450     core_max_cmd_ts = 0;
6451   }
6452 
6453   /* Configure the statcache to start caching for the authenticated session. */
6454   pr_fs_statcache_reset();
6455   c = find_config(main_server->conf, CONF_PARAM, "FSCachePolicy", FALSE);
6456   if (c != NULL) {
6457     int engine;
6458     unsigned int size, max_age;
6459 
6460     engine = *((int *) c->argv[0]);
6461     size = *((unsigned int *) c->argv[1]);
6462     max_age = *((unsigned int *) c->argv[2]);
6463 
6464     if (engine) {
6465       pr_fs_statcache_set_policy(size, max_age, 0);
6466 
6467     } else {
6468       pr_fs_statcache_set_policy(0, 0, 0);
6469     }
6470 
6471   } else {
6472     /* Set the default statcache policy. */
6473     pr_fs_statcache_set_policy(PR_TUNABLE_FS_STATCACHE_SIZE,
6474       PR_TUNABLE_FS_STATCACHE_MAX_AGE, 0);
6475   }
6476 
6477   /* Register an exit handler here, for clearing the statcache. */
6478   pr_event_register(&core_module, "core.exit", core_exit_ev, NULL);
6479 
6480   /* Note: we MUST return HANDLED here, not DECLINED, to indicate that at
6481    * least one POST_CMD handler of the PASS command succeeded.  Since
6482    * mod_core is always the last module to which commands are dispatched,
6483    * we can rest assured that we are not causing problems for any other
6484    * PASS POST_CMD handlers by returning HANDLED here.
6485    */
6486   return PR_HANDLED(cmd);
6487 }
6488 
6489 /* Configuration directive handlers
6490  */
6491 
set_deferwelcome(cmd_rec * cmd)6492 MODRET set_deferwelcome(cmd_rec *cmd) {
6493   int bool = -1;
6494   config_rec *c = NULL;
6495 
6496   CHECK_ARGS(cmd, 1);
6497   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
6498 
6499   bool = get_boolean(cmd, 1);
6500   if (bool == -1) {
6501     CONF_ERROR(cmd, "expected Boolean parameter");
6502   }
6503 
6504   c = add_config_param(cmd->argv[0], 1, NULL);
6505   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
6506   *((unsigned char *) c->argv[0]) = bool;
6507 
6508   return PR_HANDLED(cmd);
6509 }
6510 
6511 /* Variable handlers
6512  */
6513 
core_get_sess_bytes_str(void * data,size_t datasz)6514 static const char *core_get_sess_bytes_str(void *data, size_t datasz) {
6515   char buf[256];
6516   off_t bytes = *((off_t *) data);
6517 
6518   memset(buf, '\0', sizeof(buf));
6519   pr_snprintf(buf, sizeof(buf)-1, "%" PR_LU, (pr_off_t) bytes);
6520 
6521   return pstrdup(session.pool, buf);
6522 }
6523 
core_get_sess_files_str(void * data,size_t datasz)6524 static const char *core_get_sess_files_str(void *data, size_t datasz) {
6525   char buf[256];
6526   unsigned int files = *((unsigned int *) data);
6527 
6528   memset(buf, '\0', sizeof(buf));
6529   pr_snprintf(buf, sizeof(buf)-1, "%u", files);
6530 
6531   return pstrdup(session.pool, buf);
6532 }
6533 
core_get_xfer_bytes_str(void * data,size_t datasz)6534 static const char *core_get_xfer_bytes_str(void *data, size_t datasz) {
6535   char buf[256];
6536   off_t bytes = *((off_t *) data);
6537 
6538   memset(buf, '\0', sizeof(buf));
6539   pr_snprintf(buf, sizeof(buf)-1, "%" PR_LU, (pr_off_t) bytes);
6540 
6541   return pstrdup(session.pool, buf);
6542 }
6543 
6544 /* Event handlers
6545  */
6546 
core_exit_ev(const void * event_data,void * user_data)6547 static void core_exit_ev(const void *event_data, void *user_data) {
6548   pr_fs_statcache_free();
6549 }
6550 
core_restart_ev(const void * event_data,void * user_data)6551 static void core_restart_ev(const void *event_data, void *user_data) {
6552   pr_fs_statcache_reset();
6553   pr_scoreboard_scrub();
6554 
6555 #ifdef PR_USE_TRACE
6556   if (trace_log) {
6557     (void) pr_trace_set_levels(PR_TRACE_DEFAULT_CHANNEL, -1, -1);
6558     pr_trace_set_file(NULL);
6559     trace_log = NULL;
6560   }
6561 #endif /* PR_USE_TRACE */
6562 }
6563 
core_startup_ev(const void * event_data,void * user_data)6564 static void core_startup_ev(const void *event_data, void *user_data) {
6565 
6566   /* Add a scoreboard-scrubbing timer.
6567    *
6568    * Note that we do this only for standalone proftpd daemons, not for
6569    * inetd-run daemons.  There is no "master"/"daemon" process for
6570    * inetd-run proftpd processes, which means that _all_ processes scrub
6571    * the scoreboard (which greatly increases lock contention, particularly
6572    * under high numbers of simultaneous connections), or that _no_
6573    * processes scrub the scoreboard (which increases the chance of stale/bad
6574    * scoreboard data).
6575    */
6576   if (ServerType == SERVER_STANDALONE) {
6577     int scrub_scoreboard = TRUE;
6578     int scrub_interval = PR_TUNABLE_SCOREBOARD_SCRUB_TIMER;
6579     config_rec *c;
6580 
6581     c = find_config(main_server->conf, CONF_PARAM, "ScoreboardScrub", FALSE);
6582     if (c) {
6583       scrub_scoreboard = *((int *) c->argv[0]);
6584 
6585       if (c->argc == 2) {
6586         scrub_interval = *((int *) c->argv[1]);
6587       }
6588     }
6589 
6590     if (scrub_scoreboard) {
6591       core_scrub_timer_id = pr_timer_add(scrub_interval, -1,
6592         &core_module, core_scrub_scoreboard_cb, "scoreboard scrubbing");
6593     }
6594   }
6595 }
6596 
6597 /* Initialization/finalization routines
6598  */
6599 
core_init(void)6600 static int core_init(void) {
6601   /* Set the default (i.e. FTP) command handler. */
6602   pr_cmd_set_handler(NULL);
6603 
6604   /* Add the commands handled by this module to the HELP list. */
6605   pr_help_add(C_CWD,  _("<sp> pathname"), TRUE);
6606   pr_help_add(C_XCWD, _("<sp> pathname"), TRUE);
6607   pr_help_add(C_CDUP, _("(up one directory)"), TRUE);
6608   pr_help_add(C_XCUP, _("(up one directory)"), TRUE);
6609   pr_help_add(C_SMNT, _("is not implemented"), FALSE);
6610   pr_help_add(C_QUIT, _("(close control connection)"), TRUE);
6611   pr_help_add(C_PORT, _("<sp> h1,h2,h3,h4,p1,p2"), TRUE);
6612   pr_help_add(C_PASV, _("(returns address/port)"), TRUE);
6613   pr_help_add(C_EPRT, _("<sp> |proto|addr|port|"), TRUE);
6614   pr_help_add(C_EPSV, _("(returns port |||port|)"), TRUE);
6615   pr_help_add(C_ALLO, _("<sp> size"), TRUE);
6616   pr_help_add(C_RNFR, _("<sp> pathname"), TRUE);
6617   pr_help_add(C_RNTO, _("<sp> pathname"), TRUE);
6618   pr_help_add(C_DELE, _("<sp> pathname"), TRUE);
6619   pr_help_add(C_MDTM, _("<sp> pathname"), TRUE);
6620   pr_help_add(C_RMD, _("<sp> pathname"), TRUE);
6621   pr_help_add(C_XRMD, _("<sp> pathname"), TRUE);
6622   pr_help_add(C_MKD, _("<sp> pathname"), TRUE);
6623   pr_help_add(C_XMKD, _("<sp> pathname"), TRUE);
6624   pr_help_add(C_PWD, _("(returns current working directory)"), TRUE);
6625   pr_help_add(C_XPWD, _("(returns current working directory)"), TRUE);
6626   pr_help_add(C_SIZE, _("<sp> pathname"), TRUE);
6627   pr_help_add(C_SYST, _("(returns system type)"), TRUE);
6628   pr_help_add(C_HELP, _("[<sp> command]"), TRUE);
6629   pr_help_add(C_NOOP, _("(no operation)"), TRUE);
6630   pr_help_add(C_FEAT, _("(returns feature list)"), TRUE);
6631   pr_help_add(C_OPTS, _("<sp> command [<sp> options]"), TRUE);
6632   pr_help_add(C_HOST, _("<cp> hostname"), TRUE);
6633   pr_help_add(C_CLNT, _("<cp> client-info"), TRUE);
6634   pr_help_add(C_AUTH, _("<sp> base64-data"), FALSE);
6635   pr_help_add(C_CCC, _("(clears protection level)"), FALSE);
6636   pr_help_add(C_CONF, _("<sp> base64-data"), FALSE);
6637   pr_help_add(C_ENC, _("<sp> base64-data"), FALSE);
6638   pr_help_add(C_MIC, _("<sp> base64-data"), FALSE);
6639   pr_help_add(C_PBSZ, _("<sp> protection buffer size"), FALSE);
6640   pr_help_add(C_PROT, _("<sp> protection code"), FALSE);
6641 
6642   /* Add the additional features implemented by this module into the
6643    * list, to be displayed in response to a FEAT command.
6644    */
6645   pr_feat_add(C_CLNT);
6646   pr_feat_add(C_EPRT);
6647   pr_feat_add(C_EPSV);
6648   pr_feat_add(C_MDTM);
6649   pr_feat_add("REST STREAM");
6650   pr_feat_add(C_SIZE);
6651   pr_feat_add(C_HOST);
6652 
6653   pr_event_register(&core_module, "core.restart", core_restart_ev, NULL);
6654   pr_event_register(&core_module, "core.startup", core_startup_ev, NULL);
6655 
6656   return 0;
6657 }
6658 
6659 static const char *auth_syms[] = {
6660   "setpwent", "endpwent", "setgrent", "endgrent", "getpwent", "getgrent",
6661   "getpwnam", "getgrnam", "getpwuid", "getgrgid", "auth", "check",
6662   "uid2name", "gid2name", "name2uid", "name2gid", "getgroups", NULL
6663 };
6664 
reset_server_auth_order(void)6665 static void reset_server_auth_order(void) {
6666   config_rec *c = NULL;
6667 
6668   c = find_config(session.prev_server->conf, CONF_PARAM, "AuthOrder", FALSE);
6669   if (c != NULL) {
6670     register unsigned int i;
6671     unsigned int module_pri = 0;
6672     module *m;
6673 
6674     /* There was an AuthOrder applying to the previous server_rec, which
6675      * means we need to reset the default AuthOrder symbols.
6676      */
6677 
6678     /* Delete all auth syms. */
6679     for (i = 0; auth_syms[i] != NULL; i++) {
6680       pr_stash_remove_symbol(PR_SYM_AUTH, auth_syms[i], NULL);
6681     }
6682 
6683     /* Reload all modules' auth syms. Be sure to reset the module
6684      * priority while doing so.
6685      */
6686     for (m = loaded_modules; m; m = m->next) {
6687       if (pr_module_load_authtab(m) < 0) {
6688         pr_log_debug(DEBUG0,
6689           "error reloading auth symbols for module 'mod_%s.c': %s", m->name,
6690           strerror(errno));
6691       }
6692 
6693       m->priority = module_pri++;
6694     }
6695   }
6696 }
6697 
set_server_auth_order(void)6698 static void set_server_auth_order(void) {
6699   config_rec *c = NULL;
6700 
6701   c = find_config(main_server->conf, CONF_PARAM, "AuthOrder", FALSE);
6702   if (c != NULL) {
6703     array_header *module_list = (array_header *) c->argv[0];
6704     unsigned int modulec = 0;
6705     char **modulev = NULL;
6706     register unsigned int i = 0;
6707 
6708     pr_log_debug(DEBUG3, "AuthOrder in effect, resetting auth module order");
6709 
6710     modulec = module_list->nelts;
6711     modulev = (char **) module_list->elts;
6712 
6713     /* First, delete all auth symbols. */
6714     for (i = 0; auth_syms[i] != NULL; i++) {
6715       pr_stash_remove_symbol(PR_SYM_AUTH, auth_syms[i], NULL);
6716     }
6717 
6718     /* Now, cycle through the list of configured modules, re-adding their
6719      * auth symbols, in the order in which they appear.
6720      */
6721 
6722     for (i = 0; i < modulec; i++) {
6723       module *m;
6724       int required = FALSE;
6725 
6726       /* Check for the trailing '*', indicating a required auth module. */
6727       if (modulev[i][strlen(modulev[i])-1] == '*') {
6728         required = TRUE;
6729         modulev[i][strlen(modulev[i])-1] = '\0';
6730       }
6731 
6732       m = pr_module_get(modulev[i]);
6733 
6734       if (m) {
6735         if (m->authtable) {
6736           authtable *authtab;
6737 
6738           /* Twiddle the module's priority field before insertion into the
6739            * symbol table, as the insertion operation does so based on that
6740            * priority.  This has no effect other than during symbol
6741            * insertion.
6742            */
6743           m->priority = modulec - i;
6744 
6745           for (authtab = m->authtable; authtab->name; authtab++) {
6746             authtab->m = m;
6747 
6748             if (required) {
6749               authtab->auth_flags |= PR_AUTH_FL_REQUIRED;
6750             }
6751 
6752             pr_stash_add_symbol(PR_SYM_AUTH, authtab);
6753           }
6754 
6755         } else {
6756           pr_log_debug(DEBUG0, "AuthOrder: warning: module '%s' is not a valid "
6757             "auth module (no auth handlers), authentication may fail",
6758             modulev[i]);
6759         }
6760 
6761       } else {
6762         pr_log_debug(DEBUG0, "AuthOrder: warning: module '%s' not loaded",
6763           modulev[i]);
6764       }
6765     }
6766 
6767     /* NOTE: the master conf/cmd/auth tables/arrays should ideally be
6768      * rebuilt after this symbol shuffling, but it's not necessary at this
6769      * point.
6770      */
6771   }
6772 }
6773 
core_sess_init(void)6774 static int core_sess_init(void) {
6775   int timeout_idle;
6776   char *displayquit = NULL;
6777   config_rec *c = NULL;
6778   unsigned int *debug_level = NULL;
6779   unsigned long fs_opts = 0UL;
6780 
6781   init_auth();
6782 
6783   c = find_config(main_server->conf, CONF_PARAM, "MultilineRFC2228", FALSE);
6784   if (c != NULL) {
6785     session.multiline_rfc2228 = *((int *) c->argv[0]);
6786   }
6787 
6788   /* Start the idle timer. */
6789 
6790   c = find_config(main_server->conf, CONF_PARAM, "TimeoutIdle", FALSE);
6791   if (c != NULL) {
6792     int timeout = *((int *) c->argv[0]);
6793     pr_data_set_timeout(PR_DATA_TIMEOUT_IDLE, timeout);
6794   }
6795 
6796   timeout_idle = pr_data_get_timeout(PR_DATA_TIMEOUT_IDLE);
6797   if (timeout_idle) {
6798     pr_timer_add(timeout_idle, PR_TIMER_IDLE, &core_module,
6799       core_idle_timeout_cb, "TimeoutIdle");
6800   }
6801 
6802   /* Check for a server-specific TimeoutLinger */
6803   c = find_config(main_server->conf, CONF_PARAM, "TimeoutLinger", FALSE);
6804   if (c != NULL) {
6805     long timeout;
6806 
6807     timeout = (long) *((int *) c->argv[0]);
6808     pr_data_set_linger(timeout);
6809   }
6810 
6811   /* Check for a configured DebugLevel. */
6812   debug_level = get_param_ptr(main_server->conf, "DebugLevel", FALSE);
6813   if (debug_level != NULL) {
6814     pr_log_setdebuglevel(*debug_level);
6815   }
6816 
6817   c = find_config(main_server->conf, CONF_PARAM, "FSOptions", FALSE);
6818   while (c != NULL) {
6819     unsigned long opts = 0;
6820 
6821     pr_signals_handle();
6822 
6823     opts = *((unsigned long *) c->argv[0]);
6824     fs_opts |= opts;
6825 
6826     c = find_config_next(c, c->next, CONF_PARAM, "FSOptions", FALSE);
6827   }
6828 
6829   (void) pr_fsio_set_options(fs_opts);
6830 
6831   /* Check for any server-specific RegexOptions */
6832   c = find_config(main_server->conf, CONF_PARAM, "RegexOptions", FALSE);
6833   if (c != NULL) {
6834     unsigned long match_limit, match_limit_recursion;
6835 
6836     match_limit = *((unsigned long *) c->argv[0]);
6837     match_limit_recursion = *((unsigned long *) c->argv[1]);
6838 
6839     pr_trace_msg("regexp", 4,
6840       "using regex options: match limit = %lu, match limit recursion = %lu",
6841       match_limit, match_limit_recursion);
6842 
6843     pr_regexp_set_limits(match_limit, match_limit_recursion);
6844   }
6845 
6846   /* Check for configured SetEnvs. */
6847   c = find_config(main_server->conf, CONF_PARAM, "SetEnv", FALSE);
6848 
6849   while (c) {
6850     if (pr_env_set(session.pool, c->argv[0], c->argv[1]) < 0) {
6851       pr_log_debug(DEBUG1, "unable to set environment variable '%s': %s",
6852         (char *) c->argv[0], strerror(errno));
6853 
6854     } else {
6855       core_handle_locale_env(c->argv[0]);
6856     }
6857 
6858     c = find_config_next(c, c->next, CONF_PARAM, "SetEnv", FALSE);
6859   }
6860 
6861   /* Check for configured UnsetEnvs. */
6862   c = find_config(main_server->conf, CONF_PARAM, "UnsetEnv", FALSE);
6863 
6864   while (c) {
6865     if (pr_env_unset(session.pool, c->argv[0]) < 0) {
6866       pr_log_debug(DEBUG1, "unable to unset environment variable '%s': %s",
6867         (char *) c->argv[0], strerror(errno));
6868 
6869     } else {
6870       core_handle_locale_env(c->argv[0]);
6871     }
6872 
6873     c = find_config_next(c, c->next, CONF_PARAM, "UnsetEnv", FALSE);
6874   }
6875 
6876   set_server_auth_order();
6877 
6878 #ifdef PR_USE_TRACE
6879   /* Handle any session-specific Trace settings. */
6880   c = find_config(main_server->conf, CONF_PARAM, "Trace", FALSE);
6881   if (c != NULL) {
6882     register unsigned int i;
6883 
6884     for (i = 0; i < c->argc; i++) {
6885       char *channel, *ptr;
6886       int min_level, max_level, res;
6887 
6888       pr_signals_handle();
6889 
6890       channel = c->argv[i];
6891 
6892       ptr = strchr(channel, ':');
6893       if (ptr == NULL) {
6894         pr_log_debug(DEBUG6, "skipping badly formatted '%s' setting",
6895           channel);
6896         continue;
6897       }
6898 
6899       *ptr = '\0';
6900 
6901       res = pr_trace_parse_levels(ptr + 1, &min_level, &max_level);
6902       if (res == 0) {
6903         res = pr_trace_set_levels(channel, min_level, max_level);
6904         *ptr = ':';
6905 
6906         if (res < 0) {
6907           pr_log_debug(DEBUG6, "%s: error setting levels %d-%d for "
6908             "channel '%s': %s", c->name, min_level, max_level, channel,
6909             strerror(errno));
6910         }
6911 
6912       } else {
6913         pr_log_debug(DEBUG6, "%s: error parsing level '%s' for channel '%s': "
6914           "%s", c->name, ptr + 1, channel, strerror(errno));
6915       }
6916     }
6917   }
6918 
6919   /* Handle any session-specific TraceOptions settings. */
6920   c = find_config(main_server->conf, CONF_PARAM, "TraceOptions", FALSE);
6921   if (c != NULL) {
6922     unsigned long trace_opts;
6923 
6924     trace_opts = *((unsigned long *) c->argv[0]);
6925     if (pr_trace_set_options(trace_opts) < 0) {
6926       pr_log_debug(DEBUG6, "%s: error setting TraceOptions (%lu): %s",
6927         c->name, trace_opts, strerror(errno));
6928     }
6929   }
6930 #endif /* PR_USE_TRACE */
6931 
6932   if (ServerType == SERVER_STANDALONE) {
6933     pr_timer_remove(core_scrub_timer_id, &core_module);
6934 
6935   } else if (ServerType == SERVER_INETD) {
6936 
6937     /* If we're running as 'ServerType inetd', scrub the scoreboard here.
6938      * For standalone ServerTypes, the scoreboard scrubber will handle
6939      * things itself.
6940      */
6941 
6942     c = find_config(main_server->conf, CONF_PARAM, "ScoreboardScrub", FALSE);
6943     if (c) {
6944       if (*((int *) c->argv[0]) == TRUE) {
6945         pr_scoreboard_scrub();
6946       }
6947     }
6948   }
6949 
6950   /* Set some Variable entries for Display files. */
6951 
6952   if (pr_var_set(session.pool, "%{bytes_xfer}",
6953       "Number of bytes transferred in this transfer", PR_VAR_TYPE_FUNC,
6954       (void *) core_get_xfer_bytes_str, &session.xfer.total_bytes,
6955       sizeof(off_t *)) < 0) {
6956     pr_log_debug(DEBUG6, "error setting %%{bytes_fer} variable: %s",
6957       strerror(errno));
6958   }
6959 
6960   if (pr_var_set(session.pool, "%{total_bytes_in}",
6961       "Number of bytes uploaded during a session", PR_VAR_TYPE_FUNC,
6962       (void *) core_get_sess_bytes_str, &session.total_bytes_in,
6963       sizeof(off_t *)) < 0) {
6964     pr_log_debug(DEBUG6, "error setting %%{total_bytes_in} variable: %s",
6965       strerror(errno));
6966   }
6967 
6968   if (pr_var_set(session.pool, "%{total_bytes_out}",
6969       "Number of bytes downloaded during a session", PR_VAR_TYPE_FUNC,
6970       (void *) core_get_sess_bytes_str, &session.total_bytes_out,
6971       sizeof(off_t *)) < 0) {
6972     pr_log_debug(DEBUG6, "error setting %%{total_bytes_out} variable: %s",
6973       strerror(errno));
6974   }
6975 
6976   if (pr_var_set(session.pool, "%{total_bytes_xfer}",
6977       "Number of bytes transferred during a session", PR_VAR_TYPE_FUNC,
6978       (void *) core_get_sess_bytes_str, &session.total_bytes,
6979       sizeof(off_t *)) < 0) {
6980     pr_log_debug(DEBUG6, "error setting %%{total_bytes_fer} variable: %s",
6981       strerror(errno));
6982   }
6983 
6984   if (pr_var_set(session.pool, "%{total_files_in}",
6985       "Number of files uploaded during a session", PR_VAR_TYPE_FUNC,
6986       (void *) core_get_sess_files_str, &session.total_files_in,
6987       sizeof(unsigned int *)) < 0) {
6988     pr_log_debug(DEBUG6, "error setting %%{total_files_in} variable: %s",
6989       strerror(errno));
6990   }
6991 
6992   if (pr_var_set(session.pool, "%{total_files_out}",
6993       "Number of files downloaded during a session", PR_VAR_TYPE_FUNC,
6994       (void *) core_get_sess_files_str, &session.total_files_out,
6995       sizeof(unsigned int *)) < 0) {
6996     pr_log_debug(DEBUG6, "error setting %%{total_files_out} variable: %s",
6997       strerror(errno));
6998   }
6999 
7000   if (pr_var_set(session.pool, "%{total_files_xfer}",
7001       "Number of files transferred during a session", PR_VAR_TYPE_FUNC,
7002       (void *) core_get_sess_files_str, &session.total_files_xfer,
7003       sizeof(unsigned int *)) < 0) {
7004     pr_log_debug(DEBUG6, "error setting %%{total_files_xfer} variable: %s",
7005       strerror(errno));
7006   }
7007 
7008   /* Look for a DisplayQuit file which has an absolute path.  If we
7009    * find one, open a filehandle, such that that file can be displayed
7010    * even if the session is chrooted.  DisplayQuit files with
7011    * relative paths will be handled after chroot, preserving the old
7012    * behavior.
7013    */
7014   displayquit = get_param_ptr(TOPLEVEL_CONF, "DisplayQuit", FALSE);
7015   if (displayquit &&
7016       *displayquit == '/') {
7017     struct stat st;
7018 
7019     displayquit_fh = pr_fsio_open(displayquit, O_RDONLY);
7020     if (displayquit_fh == NULL) {
7021       pr_log_debug(DEBUG6, "unable to open DisplayQuit file '%s': %s",
7022         displayquit, strerror(errno));
7023 
7024     } else {
7025       if (pr_fsio_fstat(displayquit_fh, &st) < 0) {
7026         pr_log_debug(DEBUG6, "unable to stat DisplayQuit file '%s': %s",
7027           displayquit, strerror(errno));
7028         pr_fsio_close(displayquit_fh);
7029         displayquit_fh = NULL;
7030 
7031       } else {
7032         if (S_ISDIR(st.st_mode)) {
7033           errno = EISDIR;
7034           pr_log_debug(DEBUG6, "unable to use DisplayQuit file '%s': %s",
7035             displayquit, strerror(errno));
7036           pr_fsio_close(displayquit_fh);
7037           displayquit_fh = NULL;
7038         }
7039       }
7040     }
7041   }
7042 
7043   /* Check for any ProcessTitles setting. */
7044   c = find_config(main_server->conf, CONF_PARAM, "ProcessTitles", FALSE);
7045   if (c) {
7046     char *verbosity;
7047 
7048     verbosity = c->argv[0];
7049     if (strcasecmp(verbosity, "terse") == 0) {
7050       pr_proctitle_set_static_str("proftpd: processing connection");
7051     }
7052   }
7053 
7054   return 0;
7055 }
7056 
7057 /* Module API tables
7058  */
7059 
7060 static conftable core_conftab[] = {
7061   { "<Anonymous>",		add_anonymous,			NULL },
7062   { "</Anonymous>",		end_anonymous,			NULL },
7063   { "<Class>",			add_class,			NULL },
7064   { "</Class>",			end_class,			NULL },
7065   { "<Directory>",		add_directory,			NULL },
7066   { "</Directory>",		end_directory,			NULL },
7067   { "<Global>",			add_global,			NULL },
7068   { "</Global>",		end_global,			NULL },
7069   { "<IfDefine>",		start_ifdefine,			NULL },
7070   { "</IfDefine>",		end_ifdefine,			NULL },
7071   { "<IfModule>",		start_ifmodule,			NULL },
7072   { "</IfModule>",		end_ifmodule,			NULL },
7073   { "<Limit>",			add_limit,			NULL },
7074   { "</Limit>", 		end_limit, 			NULL },
7075   { "<VirtualHost>",		add_virtualhost,		NULL },
7076   { "</VirtualHost>",		end_virtualhost,		NULL },
7077   { "Allow",			set_allowdeny,			NULL },
7078   { "AllowAll",			set_allowall,			NULL },
7079   { "AllowClass",		set_allowdenyusergroupclass,	NULL },
7080   { "AllowFilter",		set_allowdenyfilter,		NULL },
7081   { "AllowForeignAddress",	set_allowforeignaddress,	NULL },
7082   { "AllowGroup",		set_allowdenyusergroupclass,	NULL },
7083   { "AllowOverride",		set_allowoverride,		NULL },
7084   { "AllowUser",		set_allowdenyusergroupclass,	NULL },
7085   { "AuthOrder",		set_authorder,			NULL },
7086   { "CDPath",			set_cdpath,			NULL },
7087   { "CommandBufferSize",	set_commandbuffersize,		NULL },
7088   { "DebugLevel",		set_debuglevel,			NULL },
7089   { "DefaultAddress",		set_defaultaddress,		NULL },
7090   { "DefaultServer",		set_defaultserver,		NULL },
7091   { "DeferWelcome",		set_deferwelcome,		NULL },
7092   { "Define",			set_define,			NULL },
7093   { "Deny",			set_allowdeny,			NULL },
7094   { "DenyAll",			set_denyall,			NULL },
7095   { "DenyClass",		set_allowdenyusergroupclass,	NULL },
7096   { "DenyFilter",		set_allowdenyfilter,		NULL },
7097   { "DenyGroup",		set_allowdenyusergroupclass,	NULL },
7098   { "DenyUser",			set_allowdenyusergroupclass,	NULL },
7099   { "DisplayChdir",		set_displaychdir,		NULL },
7100   { "DisplayConnect",		set_displayconnect,		NULL },
7101   { "DisplayQuit",		set_displayquit,		NULL },
7102   { "From",			add_from,			NULL },
7103   { "FSCachePolicy",		set_fscachepolicy,		NULL },
7104   { "FSOptions",		set_fsoptions,			NULL },
7105   { "Group",			set_group, 			NULL },
7106   { "GroupOwner",		add_groupowner,			NULL },
7107   { "HideFiles",		set_hidefiles,			NULL },
7108   { "HideGroup",		set_hidegroup,			NULL },
7109   { "HideNoAccess",		set_hidenoaccess,		NULL },
7110   { "HideUser",			set_hideuser,			NULL },
7111   { "IgnoreHidden",		set_ignorehidden,		NULL },
7112   { "Include",			set_include,	 		NULL },
7113   { "IncludeOptions",		set_includeoptions, 		NULL },
7114   { "MasqueradeAddress",	set_masqueradeaddress,		NULL },
7115   { "MaxCommandRate",		set_maxcommandrate,		NULL },
7116   { "MaxConnectionRate",	set_maxconnrate,		NULL },
7117   { "MaxInstances",		set_maxinstances,		NULL },
7118   { "MultilineRFC2228",		set_multilinerfc2228,		NULL },
7119   { "Order",			set_order,			NULL },
7120   { "PassivePorts",		set_passiveports,		NULL },
7121   { "PathAllowFilter",		set_pathallowfilter,		NULL },
7122   { "PathDenyFilter",		set_pathdenyfilter,		NULL },
7123   { "PidFile",			set_pidfile,	 		NULL },
7124   { "Port",			set_serverport, 		NULL },
7125   { "ProcessTitles",		set_processtitles,		NULL },
7126   { "Protocols",		set_protocols,			NULL },
7127   { "RegexOptions",		set_regexoptions,		NULL },
7128   { "Satisfy",			set_satisfy,			NULL },
7129   { "ScoreboardFile",		set_scoreboardfile,		NULL },
7130   { "ScoreboardMutex",		set_scoreboardmutex,		NULL },
7131   { "ScoreboardScrub",		set_scoreboardscrub,		NULL },
7132   { "ServerAdmin",		set_serveradmin,		NULL },
7133   { "ServerAlias",		set_serveralias,		NULL },
7134   { "ServerIdent",		set_serverident,		NULL },
7135   { "ServerName",		set_servername, 		NULL },
7136   { "ServerType",		set_servertype,			NULL },
7137   { "SetEnv",			set_setenv,			NULL },
7138   { "SocketBindTight",		set_socketbindtight,		NULL },
7139   { "SocketOptions",		set_socketoptions,		NULL },
7140   { "SyslogFacility",		set_syslogfacility,		NULL },
7141   { "SyslogLevel",		set_sysloglevel,		NULL },
7142   { "TimeoutIdle",		set_timeoutidle,		NULL },
7143   { "TimeoutLinger",		set_timeoutlinger,		NULL },
7144   { "TimesGMT",			set_timesgmt,			NULL },
7145   { "Trace",			set_trace,			NULL },
7146   { "TraceLog",			set_tracelog,			NULL },
7147   { "TraceOptions",		set_traceoptions,		NULL },
7148   { "TransferLog",		add_transferlog,		NULL },
7149   { "Umask",			set_umask,			NULL },
7150   { "UnsetEnv",			set_unsetenv,			NULL },
7151   { "UseIPv6",			set_useipv6,			NULL },
7152   { "UseReverseDNS",		set_usereversedns,		NULL },
7153   { "User",			set_user,			NULL },
7154   { "UserOwner",		add_userowner,			NULL },
7155   { "TCPBackLog",		set_tcpbacklog,			NULL },
7156   { "TCPNoDelay",		set_tcpnodelay,			NULL },
7157 
7158   { NULL, NULL, NULL }
7159 };
7160 
7161 static cmdtable core_cmdtab[] = {
7162 #ifdef PR_USE_REGEX
7163   { PRE_CMD, C_ANY, G_NONE,  regex_filters, FALSE, FALSE, CL_NONE },
7164 #endif
7165   { PRE_CMD, C_ANY, G_NONE, core_pre_any,FALSE, FALSE, CL_NONE },
7166   { CMD, C_HELP, G_NONE,  core_help,	FALSE,	FALSE, CL_INFO },
7167   { CMD, C_PORT, G_NONE,  core_port,	TRUE,	FALSE, CL_MISC },
7168   { CMD, C_PASV, G_NONE,  core_pasv,	TRUE,	FALSE, CL_MISC },
7169   { CMD, C_EPRT, G_NONE,  core_eprt,    TRUE,	FALSE, CL_MISC },
7170   { CMD, C_EPSV, G_NONE,  core_epsv,	TRUE,	FALSE, CL_MISC },
7171   { CMD, C_SYST, G_NONE,  core_syst,	FALSE,	FALSE, CL_INFO },
7172   { CMD, C_PWD,	 G_DIRS,  core_pwd,	TRUE,	FALSE, CL_INFO|CL_DIRS },
7173   { CMD, C_XPWD, G_DIRS,  core_pwd,	TRUE,	FALSE, CL_INFO|CL_DIRS },
7174   { CMD, C_CWD,	 G_DIRS,  core_cwd,	TRUE,	FALSE, CL_DIRS },
7175   { CMD, C_XCWD, G_DIRS,  core_cwd,	TRUE,	FALSE, CL_DIRS },
7176   { CMD, C_MKD,	 G_WRITE, core_mkd,	TRUE,	FALSE, CL_DIRS|CL_WRITE },
7177   { CMD, C_XMKD, G_WRITE, core_mkd,	TRUE,	FALSE, CL_DIRS|CL_WRITE },
7178   { CMD, C_RMD,	 G_WRITE, core_rmd,	TRUE,	FALSE, CL_DIRS|CL_WRITE },
7179   { CMD, C_XRMD, G_WRITE, core_rmd,	TRUE,	FALSE, CL_DIRS|CL_WRITE },
7180   { CMD, C_CDUP, G_DIRS,  core_cdup,	TRUE,	FALSE, CL_DIRS },
7181   { CMD, C_XCUP, G_DIRS,  core_cdup,	TRUE,	FALSE, CL_DIRS },
7182   { CMD, C_DELE, G_WRITE, core_dele,	TRUE,	FALSE, CL_WRITE },
7183   { CMD, C_MDTM, G_DIRS,  core_mdtm,	TRUE,	FALSE, CL_INFO|CL_DIRS },
7184   { CMD, C_RNFR, G_WRITE, core_rnfr,	TRUE,	FALSE, CL_MISC|CL_WRITE },
7185   { CMD, C_RNTO, G_WRITE, core_rnto,	TRUE,	FALSE, CL_MISC|CL_WRITE },
7186   { LOG_CMD,     C_RNTO, G_NONE, core_rnto_cleanup, TRUE, FALSE, CL_NONE },
7187   { LOG_CMD_ERR, C_RNTO, G_NONE, core_rnto_cleanup, TRUE, FALSE, CL_NONE },
7188   { CMD, C_SIZE, G_READ,  core_size,	TRUE,	FALSE, CL_INFO },
7189   { CMD, C_QUIT, G_NONE,  core_quit,	FALSE,	FALSE,  CL_INFO },
7190   { LOG_CMD, 	 C_QUIT, G_NONE, core_log_quit, FALSE, FALSE },
7191   { LOG_CMD_ERR, C_QUIT, G_NONE, core_log_quit, FALSE, FALSE },
7192   { CMD, C_NOOP, G_NONE,  core_noop,	FALSE,	FALSE,  CL_MISC },
7193   { CMD, C_FEAT, G_NONE,  core_feat,	FALSE,	FALSE,  CL_INFO },
7194   { CMD, C_OPTS, G_NONE,  core_opts,    FALSE,	FALSE,	CL_MISC },
7195   { CMD, C_HOST, G_NONE,  core_host,    FALSE,	FALSE,	CL_MISC },
7196   { POST_CMD, C_PASS, G_NONE, core_post_pass, FALSE, FALSE },
7197   { CMD, C_HOST, G_NONE,  core_host,	FALSE,	FALSE,	CL_AUTH },
7198   { POST_CMD, C_HOST, G_NONE, core_post_host, FALSE, FALSE },
7199   { CMD, C_CLNT, G_NONE,  core_clnt,	FALSE,	FALSE,	CL_INFO },
7200 
7201   { 0, NULL }
7202 };
7203 
7204 module core_module = {
7205   NULL, NULL,
7206 
7207   /* Module API version */
7208   0x20,
7209 
7210   /* Module name */
7211   "core",
7212 
7213   /* Module configuration directive table */
7214   core_conftab,
7215 
7216   /* Module command handler table */
7217   core_cmdtab,
7218 
7219   /* Module authentication handler table */
7220   NULL,
7221 
7222   /* Module initialization function */
7223   core_init,
7224 
7225   /* Session initialization function */
7226   core_sess_init
7227 };
7228