/*
* ProFTPD - FTP server daemon
* Copyright (c) 1997, 1998 Public Flood Software
* Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
* Copyright (c) 2001-2006 The ProFTPD Project team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
* and other respective copyright holders give permission to link this program
* with OpenSSL, and distribute the resulting executable, without including
* the source code for OpenSSL in the source distribution.
*/
/* Core FTPD module
* $Id: mod_core.c,v 1.277 2006/02/21 06:55:38 castaglia Exp $
*/
#include "conf.h"
#include "privs.h"
#include <ctype.h>
#include <sys/resource.h>
#include <signal.h>
#ifdef HAVE_REGEX_H
# include <regex.h>
#endif
extern module site_module;
extern xaset_t *server_list;
/* From src/main.c */
extern unsigned long max_connects;
extern unsigned int max_connect_interval;
/* From modules/mod_site.c */
extern modret_t *site_dispatch(cmd_rec*);
/* From src/dirtree.c */
extern array_header *server_defines;
/* For bytes-retrieving directives */
#define PR_BYTES_BAD_UNITS -1
#define PR_BYTES_BAD_FORMAT -2
module core_module;
char AddressCollisionCheck = TRUE;
static int core_scrub_timer_id;
static ssize_t get_num_bytes(char *nbytes_str) {
ssize_t nbytes = 0;
unsigned long inb;
char units, junk;
int result;
/* Scan in the given argument, checking for the leading number-of-bytes
* as well as a trailing G, M, K, or B (case-insensitive). The junk
* variable is catch arguments like "2g2" or "number-letter-whatever".
*
* NOTE: There is no portable way to scan in an ssize_t, so we do unsigned
* long and cast it. This probably places a 32-bit limit on rlimit values.
*/
if ((result = sscanf(nbytes_str, "%lu%c%c", &inb, &units, &junk)) == 2) {
if (units != 'G' && units != 'g' &&
units != 'M' && units != 'm' &&
units != 'K' && units != 'k' &&
units != 'B' && units != 'b')
return PR_BYTES_BAD_UNITS;
nbytes = (ssize_t)inb;
/* Calculate the actual bytes, multiplying by the given units. Doing
* it this way means that <math.h> and -lm aren't required.
*/
if (units == 'G' || units == 'g')
nbytes *= (1024 * 1024 * 1024);
if (units == 'M' || units == 'm')
nbytes *= (1024 * 1024);
if (units == 'K' || units == 'k')
nbytes *= 1024;
/* Silently ignore units of 'B' and 'b', as they don't affect
* the requested number of bytes anyway.
*/
/* NB: should we check for a maximum numeric value of calculated bytes?
* Probably not, as it varies (int to rlim_t) from platform to
* platform)...at least, not yet.
*/
return nbytes;
} else if (result == 1) {
/* No units given. Return the number of bytes as is. */
return (ssize_t) inb;
}
/* Default return value: the given argument was badly formatted.
*/
return PR_BYTES_BAD_FORMAT;
}
static void scrub_scoreboard(void *data) {
int fd = -1;
off_t curr_offset = 0;
struct flock lock;
pr_scoreboard_entry_t entry;
pr_log_debug(DEBUG9, "scrubbing scoreboard");
/* Manually open the scoreboard. It won't hurt if the process already
* has a descriptor opened on the scoreboard file.
*/
PRIVS_ROOT
fd = open(pr_get_scoreboard(), O_RDWR);
PRIVS_RELINQUISH
if (fd < 0) {
pr_log_debug(DEBUG1, "unable to scrub ScoreboardFile '%s': %s",
pr_get_scoreboard(), strerror(errno));
return;
}
/* Lock the entire scoreboard. */
lock.l_type = F_WRLCK;
lock.l_whence = 0;
lock.l_start = 0;
lock.l_len = 0;
while (fcntl(fd, F_SETLKW, &lock) < 0) {
if (errno == EINTR) {
pr_signals_handle();
continue;
} else
return;
}
/* Skip past the scoreboard header. */
curr_offset = lseek(fd, sizeof(pr_scoreboard_header_t), SEEK_SET);
memset(&entry, 0, sizeof(entry));
PRIVS_ROOT
while (read(fd, &entry, sizeof(entry)) == sizeof(entry)) {
/* Check to see if the PID in this entry is valid. If not, erase
* the slot.
*/
if (entry.sce_pid &&
kill(entry.sce_pid, 0) < 0 &&
errno == ESRCH) {
/* OK, the recorded PID is no longer valid. */
pr_log_debug(DEBUG9, "scrubbing scoreboard slot for PID %u",
(unsigned int) entry.sce_pid);
/* Rewind to the start of this slot. */
lseek(fd, curr_offset, SEEK_SET);
memset(&entry, 0, sizeof(entry));
while (write(fd, &entry, sizeof(entry)) != sizeof(entry)) {
if (errno == EINTR) {
pr_signals_handle();
continue;
} else
pr_log_debug(DEBUG0, "error scrubbing scoreboard: %s",
strerror(errno));
}
}
/* Mark the current offset. */
curr_offset = lseek(fd, 0, SEEK_CUR);
}
PRIVS_RELINQUISH
/* Release the scoreboard. */
lock.l_type = F_UNLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
while (fcntl(fd, F_SETLK, &lock) < 0) {
if (errno == EINTR) {
pr_signals_handle();
continue;
}
}
/* Don't need the descriptor anymore. */
close(fd);
}
static int core_scrub_scoreboard_cb(CALLBACK_FRAME) {
/* Always return 1 when leaving this function, to make sure the timer
* gets called again.
*/
scrub_scoreboard(NULL);
return 1;
}
MODRET start_ifdefine(cmd_rec *cmd) {
unsigned int ifdefine_ctx_count = 1;
unsigned char not_define = FALSE, defined = FALSE;
char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}, *config_line = NULL;
CHECK_ARGS(cmd, 1);
if (*(cmd->argv[1]) == '!') {
not_define = TRUE;
(cmd->argv[1])++;
}
defined = define_exists(cmd->argv[1]);
/* Return now if we don't need to consume the <IfDefine> section
* configuration lines.
*/
if ((!not_define && defined) || (not_define && !defined)) {
pr_log_debug(DEBUG3, "%s: using '%s%s' section at line %u", cmd->argv[0],
not_define ? "!" : "", cmd->argv[1], pr_parser_get_lineno());
return HANDLED(cmd);
} else
pr_log_debug(DEBUG3, "%s: skipping '%s%s' section at line %u", cmd->argv[0],
not_define ? "!" : "", cmd->argv[1], pr_parser_get_lineno());
/* Rather than communicating with parse_config_file() via some global
* variable/flag the need to skip configuration lines, if the requested
* module condition is not TRUE, read in the lines here (effectively
* preventing them from being parsed) up to and including the closing
* directive.
*/
while (ifdefine_ctx_count && (config_line = pr_parser_read_line(buf,
sizeof(buf))) != NULL) {
if (strncasecmp(config_line, "<IfDefine", 9) == 0)
ifdefine_ctx_count++;
if (strcasecmp(config_line, "</IfDefine>") == 0)
ifdefine_ctx_count--;
}
/* If there are still unclosed <IfDefine> sections, signal an error.
*/
if (ifdefine_ctx_count)
CONF_ERROR(cmd, "unclosed <IfDefine> context");
return HANDLED(cmd);
}
/* As with Apache, there is no way of cleanly checking whether an
* <IfDefine> section is properly closed. Extra </IfDefine> directives
* will be silently ignored.
*/
MODRET end_ifdefine(cmd_rec *cmd) {
return HANDLED(cmd);
}
MODRET start_ifmodule(cmd_rec *cmd) {
unsigned int ifmodule_ctx_count = 1;
unsigned char not_module = FALSE, found_module = FALSE;
char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}, *config_line = NULL;
CHECK_ARGS(cmd, 1);
if (*(cmd->argv[1]) == '!') {
not_module = TRUE;
(cmd->argv[1])++;
}
found_module = pr_module_exists(cmd->argv[1]);
/* Return now if we don't need to consume the <IfModule> section
* configuration lines.
*/
if ((!not_module && found_module) || (not_module && !found_module)) {
pr_log_debug(DEBUG3, "%s: using '%s%s' section at line %u", cmd->argv[0],
not_module ? "!" : "", cmd->argv[1], pr_parser_get_lineno());
return HANDLED(cmd);
} else
pr_log_debug(DEBUG3, "%s: skipping '%s%s' section at line %u", cmd->argv[0],
not_module ? "!" : "", cmd->argv[1], pr_parser_get_lineno());
/* Rather than communicating with parse_config_file() via some global
* variable/flag the need to skip configuration lines, if the requested
* module condition is not TRUE, read in the lines here (effectively
* preventing them from being parsed) up to and including the closing
* directive.
*/
while (ifmodule_ctx_count && (config_line = pr_parser_read_line(buf,
sizeof(buf))) != NULL) {
char *bufp;
/* Advance past any leading whitespace. */
for (bufp = config_line; *bufp && isspace((int) *bufp); bufp++);
if (strncasecmp(bufp, "<IfModule", 9) == 0)
ifmodule_ctx_count++;
if (strcasecmp(bufp, "</IfModule>") == 0)
ifmodule_ctx_count--;
}
/* If there are still unclosed <IfModule> sections, signal an error.
*/
if (ifmodule_ctx_count)
CONF_ERROR(cmd, "unclosed <IfModule> context");
return HANDLED(cmd);
}
/* As with Apache, there is no way of cleanly checking whether an
* <IfModule> section is properly closed. Extra </IfModule> directives
* will be silently ignored.
*/
MODRET end_ifmodule(cmd_rec *cmd) {
return HANDLED(cmd);
}
/* Syntax: Define parameter
*
* Configuration file equivalent of the -D command-line option for
* specifying an <IfDefine> value.
*
* It is suggested the RLimitMemory (a good idea to use anyway) be
* used if this directive is present, to prevent Defines was being
* used by a malicious local user in a .ftpaccess file.
*/
MODRET set_define(cmd_rec *cmd) {
/* Make sure there's at least one parameter; any others are ignored */
CHECK_ARGS(cmd, 1);
/* This directive can occur in any context, so no need for the
* CHECK_CONF macro.
*/
/* If this is the first such definition, allocate an array_header
* for the definitions. Note that this uses the permanent_pool
* rather than the containing server's pool so that defined parameters
* are properly globally visible.
*/
if (!server_defines)
server_defines = make_array(permanent_pool, 0, sizeof(char *));
*((char **) push_array(server_defines)) = pstrdup(permanent_pool,
cmd->argv[1]);
return HANDLED(cmd);
}
MODRET add_include(cmd_rec *cmd) {
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_ANON|CONF_GLOBAL|CONF_DIR);
/* Make sure the given path is a valid path. */
if (pr_fs_valid_path(cmd->argv[1]) < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unable to use path for configuration file '", cmd->argv[1], "'", NULL));
}
if (parse_config_path(cmd->tmp_pool, cmd->argv[1]) == -1) {
if (errno != EINVAL)
pr_log_pri(PR_LOG_WARNING, "warning: unable to include '%s': %s",
cmd->argv[1], strerror(errno));
else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error including '", cmd->argv[1],
"': ", strerror(errno), NULL));
}
}
return HANDLED(cmd);
}
MODRET set_debuglevel(cmd_rec *cmd) {
config_rec *c = NULL;
int debuglevel = -1;
char *endp = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
/* Make sure the parameter is a valid number. */
debuglevel = strtol(cmd->argv[1], &endp, 10);
if (endp && *endp)
CONF_ERROR(cmd, "not a valid number");
/* Make sure the number is within the valid debug level range. */
if (debuglevel < 0 || debuglevel > 10)
CONF_ERROR(cmd, "invalid debug level configured");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[0]) = debuglevel;
return HANDLED(cmd);
}
MODRET set_defaultaddress(cmd_rec *cmd) {
pr_netaddr_t *main_addr = NULL;
array_header *addrs = NULL;
if (cmd->argc-1 < 1)
CONF_ERROR(cmd, "wrong number of parameters");
CHECK_CONF(cmd, CONF_ROOT);
main_addr = pr_netaddr_get_addr(main_server->pool, cmd->argv[1], &addrs);
if (main_addr == NULL)
return ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
(cmd->argv)[0], ": unable to resolve \"", cmd->argv[1], "\"",
NULL));
pr_log_pri(PR_LOG_INFO, "setting default address to %s",
pr_netaddr_get_ipstr(main_addr));
main_server->ServerAddress = pr_netaddr_get_ipstr(main_addr);
main_server->addr = main_addr;
if (addrs) {
register unsigned int i;
pr_netaddr_t **elts = addrs->elts;
/* For every additional address, implicitly add a bind record. */
for (i = 0; i < addrs->nelts; i++) {
const char *ipstr = pr_netaddr_get_ipstr(elts[i]);
#ifdef PR_USE_IPV6
char ipbuf[INET6_ADDRSTRLEN];
if (pr_netaddr_get_family(elts[i]) == AF_INET) {
/* Create the bind record using the IPv4-mapped IPv6 version of
* this address.
*/
snprintf(ipbuf, sizeof(ipbuf), "::ffff:%s", ipstr);
ipstr = ipbuf;
}
#endif /* PR_USE_IPV6 */
add_config_param_str("_bind", 1, ipstr);
}
}
/* Handle multiple addresses in a DefaultAddres directive. We do
* this by adding bind directives to the server_rec created for the
* first address.
*/
if (cmd->argc-1 > 1) {
register unsigned int i;
for (i = 2; i < cmd->argc; i++) {
pr_netaddr_t *addr;
addrs = NULL;
addr = pr_netaddr_get_addr(cmd->tmp_pool, cmd->argv[i], &addrs);
if (addr == NULL)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error resolving '",
cmd->argv[i], "': ", strerror(errno), NULL));
add_config_param_str("_bind", 1, pr_netaddr_get_ipstr(addr));
if (addrs) {
register unsigned int j;
pr_netaddr_t **elts = addrs->elts;
/* For every additional address, implicitly add a bind record. */
for (j = 0; j < addrs->nelts; j++)
add_config_param_str("_bind", 1, pr_netaddr_get_ipstr(elts[j]));
}
}
}
return HANDLED(cmd);
}
MODRET set_servername(cmd_rec *cmd) {
server_rec *s = cmd->server;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
s->ServerName = pstrdup(s->pool,cmd->argv[1]);
return HANDLED(cmd);
}
MODRET set_servertype(cmd_rec *cmd) {
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
if (!strcasecmp(cmd->argv[1], "inetd"))
ServerType = SERVER_INETD;
else if (!strcasecmp(cmd->argv[1], "standalone"))
ServerType = SERVER_STANDALONE;
else
CONF_ERROR(cmd,"type must be either 'inetd' or 'standalone'");
return HANDLED(cmd);
}
MODRET set_setenv(cmd_rec *cmd) {
#ifdef HAVE_SETENV
int ctxt_type;
CHECK_ARGS(cmd, 2);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
add_config_param_str(cmd->argv[0], 2, cmd->argv[1], cmd->argv[2]);
/* In addition, if this is the "server config" context, set the
* environ variable now. If there was a <Daemon> context, that would
* be a more appropriate place for configuring parse-time environ
* variables.
*/
ctxt_type = (cmd->config && cmd->config->config_type != CONF_PARAM ?
cmd->config->config_type : cmd->server->config_type ?
cmd->server->config_type : CONF_ROOT);
if (ctxt_type == CONF_ROOT) {
if (setenv(cmd->argv[1], cmd->argv[2], 1) < 0)
pr_log_debug(DEBUG1, "%s: unable to set environ variable '%s': %s",
cmd->argv[0], cmd->argv[1], strerror(errno));
}
return HANDLED(cmd);
#else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", cmd->argv[0],
" directive cannot be used on this system, as it does not have the "
"setenv() function", NULL));
#endif /* HAVE_SETENV */
}
MODRET add_transferlog(cmd_rec *cmd) {
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
c = add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
c->flags |= CF_MERGEDOWN;
return HANDLED(cmd);
}
MODRET set_wtmplog(cmd_rec *cmd) {
int bool = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
if (strcasecmp(cmd->argv[1], "NONE") == 0)
bool = 0;
else
bool = get_boolean(cmd, 1);
if (bool != -1) {
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = bool;
c->flags |= CF_MERGEDOWN;
} else
CONF_ERROR(cmd, "expected boolean argument, or \"NONE\"");
return HANDLED(cmd);
}
MODRET set_serveradmin(cmd_rec *cmd) {
server_rec *s = cmd->server;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
s->ServerAdmin = pstrdup(s->pool, cmd->argv[1]);
return HANDLED(cmd);
}
MODRET set_usereversedns(cmd_rec *cmd) {
int bool = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
ServerUseReverseDNS = bool;
return HANDLED(cmd);
}
MODRET set_satisfy(cmd_rec *cmd) {
int satisfy = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_CLASS);
if (strcasecmp(cmd->argv[1], "any") == 0)
satisfy = PR_CLASS_SATISFY_ANY;
else if (strcasecmp(cmd->argv[1], "all") == 0)
satisfy = PR_CLASS_SATISFY_ALL;
else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "invalid parameter: '",
cmd->argv[1], "'", NULL));
if (pr_class_set_satisfy(satisfy) < 0)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error setting Satisfy: ",
strerror(errno), NULL));
return HANDLED(cmd);
}
MODRET set_scoreboardfile(cmd_rec *cmd) {
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
if (pr_set_scoreboard(cmd->argv[1]) < 0)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unable to use '",
cmd->argv[1], "': ", strerror(errno), NULL));
return HANDLED(cmd);
}
MODRET set_serverport(cmd_rec *cmd) {
server_rec *s = cmd->server;
int port;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
port = atoi(cmd->argv[1]);
if (port < 0 || port > 65535)
CONF_ERROR(cmd,"value must be between 0 and 65535");
s->ServerPort = port;
return HANDLED(cmd);
}
MODRET set_pidfile(cmd_rec *cmd) {
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
return HANDLED(cmd);
}
MODRET set_sysloglevel(cmd_rec *cmd) {
config_rec *c = NULL;
int level = 0;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
level = pr_log_str2sysloglevel(cmd->argv[1]);
if (level < 0)
CONF_ERROR(cmd, "SyslogLevel requires level keyword: one of "
"emerg/alert/crit/error/warn/notice/info/debug");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[0]) = level;
return HANDLED(cmd);
}
MODRET set_serverident(cmd_rec *cmd) {
int bool = -1;
config_rec *c = NULL;
if (cmd->argc < 2 || cmd->argc > 3)
CONF_ERROR(cmd, "wrong number of parameters");
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
if (bool && cmd->argc == 3) {
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = !bool;
c->argv[1] = pstrdup(c->pool, cmd->argv[2]);
} else {
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = !bool;
}
return HANDLED(cmd);
}
MODRET set_defaultserver(cmd_rec *cmd) {
int bool = -1;
server_rec *s = NULL;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
if (!bool)
return HANDLED(cmd);
/* DefaultServer is not allowed if already set somewhere */
for (s = (server_rec *) server_list->xas_list; s; s = s->next)
if (find_config(s->conf, CONF_PARAM, cmd->argv[0], FALSE))
CONF_ERROR(cmd, "DefaultServer has already been set");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = bool;
return HANDLED(cmd);
}
MODRET set_masqueradeaddress(cmd_rec *cmd) {
config_rec *c = NULL;
pr_netaddr_t *masq_addr = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
/* We can only masquerade as one address, so we don't need to know if the
* given name might map to multiple addresses.
*/
masq_addr = pr_netaddr_get_addr(cmd->server->pool, cmd->argv[1], NULL);
if (masq_addr == NULL)
return ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0],
": unable to resolve \"", cmd->argv[1], "\"", NULL));
c = add_config_param(cmd->argv[0], 2, (void *) masq_addr, NULL);
c->argv[1] = pstrdup(c->pool, cmd->argv[1]);
return HANDLED(cmd);
}
MODRET set_maxinstances(cmd_rec *cmd) {
int max;
char *endp;
CHECK_ARGS(cmd,1);
CHECK_CONF(cmd,CONF_ROOT);
if (!strcasecmp(cmd->argv[1],"none"))
max = 0;
else {
max = (int)strtol(cmd->argv[1],&endp,10);
if ((endp && *endp) || max < 1)
CONF_ERROR(cmd, "argument must be 'none' or a number greater than 0");
}
ServerMaxInstances = max;
return HANDLED(cmd);
}
/* usage: MaxConnectionRate rate [interval] */
MODRET set_maxconnrate(cmd_rec *cmd) {
long conn_max = 0L;
char *endp = NULL;
if (cmd->argc-1 < 1 || cmd->argc-1 > 2)
CONF_ERROR(cmd, "wrong number of parameters");
CHECK_CONF(cmd, CONF_ROOT);
conn_max = strtol(cmd->argv[1], &endp, 10);
if (endp && *endp)
CONF_ERROR(cmd, "invalid connection rate");
if (conn_max < 0)
CONF_ERROR(cmd, "connection rate must be positive");
max_connects = conn_max;
/* If the optional interval parameter is given, parse it. */
if (cmd->argc-1 == 2) {
max_connect_interval = atoi(cmd->argv[2]);
if (max_connect_interval < 1)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
": interval must be greater than zero", NULL));
}
return HANDLED(cmd);
}
MODRET set_timeoutidle(cmd_rec *cmd) {
int timeout = -1;
char *endp = NULL;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
timeout = (int) strtol(cmd->argv[1], &endp, 10);
if ((endp && *endp) || timeout < 0 || timeout > 65535)
CONF_ERROR(cmd, "timeout values must be between 0 and 65535");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = timeout;
return HANDLED(cmd);
}
MODRET set_timeoutlinger(cmd_rec *cmd) {
long timeout = -1;
char *endp = NULL;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
timeout = strtol(cmd->argv[1], &endp, 10);
if ((endp && *endp) || timeout < 0 || timeout > 65535)
CONF_ERROR(cmd, "timeout values must be between 0 and 65535");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(long));
*((long *) c->argv[0]) = timeout;
return HANDLED(cmd);
}
MODRET set_socketbindtight(cmd_rec *cmd) {
int bool = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
SocketBindTight = bool;
return HANDLED(cmd);
}
/* NOTE: at some point in the future, SocketBindTight should be folded
* into this SocketOptions directive handler.
*/
MODRET set_socketoptions(cmd_rec *cmd) {
register unsigned int i = 0;
/* Make sure we have the right number of parameters. */
if ((cmd->argc-1) % 2 != 0)
CONF_ERROR(cmd, "bad number of parameters");
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
for (i = 1; i < cmd->argc; i++) {
int value = 0;
if (strcasecmp(cmd->argv[i], "maxseg") == 0) {
value = atoi(cmd->argv[++i]);
/* As per the tcp(7) man page, sizes larger than the interface MTU
* will be ignored, and will have no effect.
*/
if (value < 0)
CONF_ERROR(cmd, "maxseg size must be greater than 0");
cmd->server->tcp_mss_len = value;
} else if (strcasecmp(cmd->argv[i], "rcvbuf") == 0) {
value = atoi(cmd->argv[++i]);
if (value < 1024)
CONF_ERROR(cmd, "rcvbuf size must be greater than or equal to 1024");
cmd->server->tcp_rcvbuf_len = value;
cmd->server->tcp_rcvbuf_override = TRUE;
} else if (strcasecmp(cmd->argv[i], "sndbuf") == 0) {
value = atoi(cmd->argv[++i]);
if (value < 1024)
CONF_ERROR(cmd, "sndbuf size must be greater than or equal to 1024");
cmd->server->tcp_sndbuf_len = value;
cmd->server->tcp_sndbuf_override = TRUE;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown socket option: '",
cmd->argv[i], "'", NULL));
}
}
return HANDLED(cmd);
}
MODRET set_multilinerfc2228(cmd_rec *cmd) {
int bool = -1;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
MultilineRFC2228 = bool;
return HANDLED(cmd);
}
MODRET set_identlookups(cmd_rec *cmd) {
int bool = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = bool;
return HANDLED(cmd);
}
MODRET set_tcpbacklog(cmd_rec *cmd) {
int backlog;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
backlog = atoi(cmd->argv[1]);
if (backlog < 1 || backlog > 255)
CONF_ERROR(cmd, "parameter must be a number between 1 and 255");
tcpBackLog = backlog;
return HANDLED(cmd);
}
MODRET set_tcpnodelay(cmd_rec *cmd) {
int bool = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = bool;
return HANDLED(cmd);
}
MODRET set_user(cmd_rec *cmd) {
struct passwd *pw = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
/* 1.1.7, no longer force user/group lookup inside <Anonymous>
* it's now defered until authentication occurs.
*/
if (!cmd->config || cmd->config->config_type != CONF_ANON) {
pw = pr_auth_getpwnam(cmd->tmp_pool, cmd->argv[1]);
if (pw == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "Unknown user '",
cmd->argv[1], "'", NULL));
}
}
if (pw) {
config_rec *c = add_config_param("UserID", 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(uid_t));
*((uid_t *) c->argv[0]) = pw->pw_uid;
}
add_config_param_str("UserName", 1, cmd->argv[1]);
return HANDLED(cmd);
}
MODRET add_from(cmd_rec *cmd) {
int cargc;
char **cargv;
CHECK_CONF(cmd, CONF_CLASS);
cargc = cmd->argc-1;
cargv = cmd->argv;
while (cargc && *(cargv + 1)) {
if (strcasecmp("all", *(cargv + 1)) == 0 ||
strcasecmp("none", *(cargv + 1)) == 0) {
pr_netacl_t *acl = pr_netacl_create(cmd->tmp_pool, *(cargv + 1));
if (pr_class_add_acl(acl) < 0)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error adding rule '",
*(cargv + 1), "': ", strerror(errno), NULL));
cargc = 0;
}
break;
}
/* Parse each parameter into a netacl. */
while (cargc-- && *(++cargv)) {
char *ent = NULL;
char *str = pstrdup(cmd->tmp_pool, *cargv);
while ((ent = get_token(&str, ",")) != NULL) {
if (*ent) {
pr_netacl_t *acl;
if (strcasecmp(ent, "all") == 0 ||
strcasecmp(ent, "none") == 0) {
cargc = 0;
break;
}
acl = pr_netacl_create(cmd->tmp_pool, ent);
if (pr_class_add_acl(acl) < 0)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error adding rule '", ent,
"': ", strerror(errno), NULL));
}
}
}
return HANDLED(cmd);
}
MODRET set_group(cmd_rec *cmd) {
struct group *grp = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
if (!cmd->config || cmd->config->config_type != CONF_ANON) {
grp = pr_auth_getgrnam(cmd->tmp_pool, cmd->argv[1]);
if (grp == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "Unknown group '",
cmd->argv[1], "'", NULL));
}
}
if (grp) {
config_rec *c = add_config_param("GroupID", 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(gid_t));
*((gid_t *) c->argv[0]) = grp->gr_gid;
}
add_config_param_str("GroupName", 1, cmd->argv[1]);
return HANDLED(cmd);
}
MODRET set_umask(cmd_rec *cmd) {
config_rec *c;
char *endp;
mode_t tmp_umask;
CHECK_VARARGS(cmd, 1, 2);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|
CONF_DIR|CONF_DYNDIR);
tmp_umask = (mode_t) strtol(cmd->argv[1], &endp, 8);
if (endp && *endp)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", cmd->argv[1],
"' is not a valid umask", NULL));
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(mode_t));
*((mode_t *) c->argv[0]) = tmp_umask;
c->flags |= CF_MERGEDOWN;
/* Have we specified a directory umask as well?
*/
if (CHECK_HASARGS(cmd, 2)) {
/* allocate space for another mode_t. Don't worry -- the previous
* pointer was recorded in the Umask config_rec
*/
tmp_umask = (mode_t) strtol(cmd->argv[2], &endp, 8);
if (endp && *endp)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", cmd->argv[2],
"' is not a valid umask", NULL));
c = add_config_param("DirUmask", 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(mode_t));
*((mode_t *) c->argv[0]) = tmp_umask;
c->flags |= CF_MERGEDOWN;
}
return HANDLED(cmd);
}
MODRET set_unsetenv(cmd_rec *cmd) {
#ifdef HAVE_UNSETENV
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
return HANDLED(cmd);
#else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", cmd->argv[0],
" directive cannot be used on this system, as it does not have the "
"unsetenv() function", NULL));
#endif /* HAVE_UNSETENV */
}
MODRET set_rlimitcpu(cmd_rec *cmd) {
#ifdef RLIMIT_CPU
/* Make sure the directive has between 1 and 3 parameters */
if (cmd->argc-1 < 1 || cmd->argc-1 > 3)
CONF_ERROR(cmd, "wrong number of parameters");
/* The context check for this directive depends on the first parameter.
* For backwards compatibility, this parameter may be a number, or it
* may be "daemon", "session", or "none". If it happens to be
* "daemon", then this directive should be in the CONF_ROOT context only.
* Otherwise, it can appear in the full range of server contexts.
*/
if (strcmp(cmd->argv[1], "daemon") == 0) {
CHECK_CONF(cmd, CONF_ROOT);
} else {
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
}
/* Handle the newer format, which uses "daemon" or "session" or "none"
* as the first parameter.
*/
if (strcmp(cmd->argv[1], "daemon") == 0 ||
strcmp(cmd->argv[1], "session") == 0) {
config_rec *c = NULL;
struct rlimit *rlim = pcalloc(cmd->server->pool, sizeof(struct rlimit));
/* Retrieve the current values */
if (getrlimit(RLIMIT_CPU, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_CPU): %s",
strerror(errno));
if (strcasecmp("max", cmd->argv[2]) == 0)
rlim->rlim_cur = RLIM_INFINITY;
else {
/* Check that the non-max argument is a number, and error out if not.
*/
char *tmp = NULL;
unsigned long num = strtoul(cmd->argv[2], &tmp, 10);
if (tmp && *tmp)
CONF_ERROR(cmd, "badly formatted argument");
rlim->rlim_cur = num;
}
/* Handle the optional "hard limit" parameter, if present. */
if (cmd->argc-1 == 3) {
if (strcasecmp("max", cmd->argv[3]) == 0)
rlim->rlim_max = RLIM_INFINITY;
else {
/* Check that the non-max argument is a number, and error out if not.
*/
char *tmp = NULL;
unsigned long num = strtoul(cmd->argv[3], &tmp, 10);
if (tmp && *tmp)
CONF_ERROR(cmd, "badly formatted argument");
rlim->rlim_max = num;
}
}
c = add_config_param(cmd->argv[0], 2, (void *) rlim, NULL);
c->argv[1] = pstrdup(c->pool, cmd->argv[1]);
/* Handle the older format, which will have a number as the first
* parameter.
*/
} else {
struct rlimit *rlim = pcalloc(cmd->server->pool, sizeof(struct rlimit));
/* Retrieve the current values */
if (getrlimit(RLIMIT_CPU, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_CPU): %s",
strerror(errno));
if (strcasecmp("max", cmd->argv[1]) == 0)
rlim->rlim_cur = RLIM_INFINITY;
else {
/* Check that the non-max argument is a number, and error out if not.
*/
char *tmp = NULL;
long num = strtol(cmd->argv[1], &tmp, 10);
if (tmp && *tmp)
CONF_ERROR(cmd, "badly formatted argument");
rlim->rlim_cur = num;
}
/* Handle the optional "hard limit" parameter, if present. */
if (cmd->argc-1 == 2) {
if (!strcasecmp("max", cmd->argv[2]))
rlim->rlim_max = RLIM_INFINITY;
else {
/* Check that the non-max argument is a number, and error out if not.
*/
char *tmp = NULL;
long num = strtol(cmd->argv[2], &tmp, 10);
if (tmp && *tmp)
CONF_ERROR(cmd, "badly formatted argument");
rlim->rlim_max = num;
}
}
add_config_param(cmd->argv[0], 2, (void *) rlim, NULL);
}
return HANDLED(cmd);
#else
CONF_ERROR(cmd, "RLimitCPU is not supported on this platform");
#endif
}
MODRET set_rlimitmemory(cmd_rec *cmd) {
#if defined(RLIMIT_AS) || defined(RLIMIT_DATA) || defined(RLIMIT_VMEM)
/* Make sure the directive has between 1 and 3 parameters */
if (cmd->argc-1 < 1 || cmd->argc-1 > 3)
CONF_ERROR(cmd, "wrong number of parameters");
/* The context check for this directive depends on the first parameter.
* For backwards compatibility, this parameter may be a number, or it
* may be "daemon", "session", or "none". If it happens to be
* "daemon", then this directive should be in the CONF_ROOT context only.
* Otherwise, it can appear in the full range of server contexts.
*/
if (strcmp(cmd->argv[1], "daemon") == 0) {
CHECK_CONF(cmd, CONF_ROOT);
} else {
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
}
/* Handle the newer format, which uses "daemon" or "session" or "none"
* as the first parameter.
*/
if (strcmp(cmd->argv[1], "daemon") == 0 ||
strcmp(cmd->argv[1], "session") == 0) {
config_rec *c = NULL;
struct rlimit *rlim = pcalloc(cmd->server->pool, sizeof(struct rlimit));
/* Retrieve the current values */
#if defined(RLIMIT_AS)
if (getrlimit(RLIMIT_AS, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_AS): %s",
strerror(errno));
#elif defined(RLIMIT_DATA)
if (getrlimit(RLIMIT_DATA, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_DATA): %s",
strerror(errno));
#elif defined(RLIMIT_VMEM)
if (getrlimit(RLIMIT_VMEM, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_VMEM): %s",
strerror(errno));
#endif
if (strcasecmp("max", cmd->argv[2]) == 0)
rlim->rlim_cur = RLIM_INFINITY;
else
rlim->rlim_cur = get_num_bytes(cmd->argv[2]);
/* Check for bad return values. */
if (rlim->rlim_cur == PR_BYTES_BAD_UNITS)
CONF_ERROR(cmd, "unknown units used");
if (rlim->rlim_cur == PR_BYTES_BAD_FORMAT)
CONF_ERROR(cmd, "badly formatted parameter");
/* Handle the optional "hard limit" parameter, if present. */
if (cmd->argc-1 == 3) {
if (strcasecmp("max", cmd->argv[3]) == 0)
rlim->rlim_max = RLIM_INFINITY;
else
rlim->rlim_cur = get_num_bytes(cmd->argv[3]);
/* Check for bad return values. */
if (rlim->rlim_cur == PR_BYTES_BAD_UNITS)
CONF_ERROR(cmd, "unknown units used");
if (rlim->rlim_cur == PR_BYTES_BAD_FORMAT)
CONF_ERROR(cmd, "badly formatted parameter");
}
c = add_config_param(cmd->argv[0], 2, (void *) rlim, NULL);
c->argv[1] = pstrdup(c->pool, cmd->argv[1]);
/* Handle the older format, which will have a number as the first
* parameter.
*/
} else {
struct rlimit *rlim = pcalloc(cmd->server->pool, sizeof(struct rlimit));
/* Retrieve the current values */
#if defined(RLIMIT_AS)
if (getrlimit(RLIMIT_AS, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_AS): %s",
strerror(errno));
#elif defined(RLIMIT_DATA)
if (getrlimit(RLIMIT_DATA, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_DATA): %s",
strerror(errno));
#elif defined(RLIMIT_VMEM)
if (getrlimit(RLIMIT_VMEM, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_VMEM): %s",
strerror(errno));
#endif
if (strcasecmp("max", cmd->argv[1]) == 0)
rlim->rlim_cur = RLIM_INFINITY;
else
rlim->rlim_cur = get_num_bytes(cmd->argv[1]);
/* Check for bad return values. */
if (rlim->rlim_cur == PR_BYTES_BAD_UNITS)
CONF_ERROR(cmd, "unknown units used");
if (rlim->rlim_cur == PR_BYTES_BAD_FORMAT)
CONF_ERROR(cmd, "badly formatted parameter");
/* Handle the optional "hard limit" parameter, if present. */
if (cmd->argc-1 == 2) {
if (strcasecmp("max", cmd->argv[2]) == 0)
rlim->rlim_max = RLIM_INFINITY;
else
rlim->rlim_cur = get_num_bytes(cmd->argv[2]);
/* Check for bad return values. */
if (rlim->rlim_cur == PR_BYTES_BAD_UNITS)
CONF_ERROR(cmd, "unknown units used");
if (rlim->rlim_cur == PR_BYTES_BAD_FORMAT)
CONF_ERROR(cmd, "badly formatted parameter");
}
add_config_param(cmd->argv[0], 2, (void *) rlim, NULL);
}
return HANDLED(cmd);
#else
CONF_ERROR(cmd, "RLimitMemory is not supported on this platform");
#endif
}
MODRET set_rlimitopenfiles(cmd_rec *cmd) {
#if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE)
/* Make sure the directive has between 1 and 3 parameters */
if (cmd->argc-1 < 1 || cmd->argc-1 > 3)
CONF_ERROR(cmd, "wrong number of parameters");
/* The context check for this directive depends on the first parameter.
* For backwards compatibility, this parameter may be a number, or it
* may be "daemon", "session", or "none". If it happens to be
* "daemon", then this directive should be in the CONF_ROOT context only.
* Otherwise, it can appear in the full range of server contexts.
*/
if (strcmp(cmd->argv[1], "daemon") == 0) {
CHECK_CONF(cmd, CONF_ROOT);
} else {
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
}
/* Handle the newer format, which uses "daemon" or "session" or "none"
* as the first parameter.
*/
if (strcmp(cmd->argv[1], "daemon") == 0 ||
strcmp(cmd->argv[1], "session") == 0) {
config_rec *c = NULL;
struct rlimit *rlim = pcalloc(cmd->server->pool, sizeof(struct rlimit));
/* Retrieve the current values */
#if defined(RLIMIT_NOFILE)
if (getrlimit(RLIMIT_NOFILE, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_NOFILE): %s",
strerror(errno));
#elif defined(RLIMIT_OFILE)
if (getrlimit(RLIMIT_OFILE, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_OFILE): %s",
strerror(errno));
#endif
if (strcasecmp("max", cmd->argv[2]) == 0)
rlim->rlim_cur = sysconf(_SC_OPEN_MAX);
else {
/* Check that the non-max argument is a number, and error out if not.
*/
char *tmp = NULL;
long num = strtol(cmd->argv[2], &tmp, 10);
if (tmp && *tmp)
CONF_ERROR(cmd, "badly formatted argument");
rlim->rlim_cur = num;
}
/* Handle the optional "hard limit" parameter, if present. */
if (cmd->argc-1 == 3) {
if (strcasecmp("max", cmd->argv[3]) == 0)
rlim->rlim_max = sysconf(_SC_OPEN_MAX);
else {
/* Check that the non-max argument is a number, and error out if not.
*/
char *tmp = NULL;
long num = strtol(cmd->argv[3], &tmp, 10);
if (tmp && *tmp)
CONF_ERROR(cmd, "badly formatted argument");
rlim->rlim_max = num;
}
}
c = add_config_param(cmd->argv[0], 2, (void *) rlim, NULL);
c->argv[1] = pstrdup(c->pool, cmd->argv[1]);
/* Handle the older format, which will have a number as the first
* parameter.
*/
} else {
struct rlimit *rlim = pcalloc(cmd->server->pool, sizeof(struct rlimit));
/* Retrieve the current values */
#if defined(RLIMIT_NOFILE)
if (getrlimit(RLIMIT_NOFILE, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_NOFILE): %s",
strerror(errno));
#elif defined(RLIMIT_OFILE)
if (getrlimit(RLIMIT_OFILE, rlim) == -1)
pr_log_pri(PR_LOG_ERR, "error: getrlimit(RLIMIT_OFILE): %s",
strerror(errno));
#endif
if (strcasecmp("max", cmd->argv[1]) == 0)
rlim->rlim_cur = sysconf(_SC_OPEN_MAX);
else {
/* Check that the non-max argument is a number, and error out if not.
*/
char *tmp = NULL;
long num = strtol(cmd->argv[1], &tmp, 10);
if (tmp && *tmp)
CONF_ERROR(cmd, "badly formatted argument");
rlim->rlim_cur = num;
}
/* Handle the optional "hard limit" parameter, if present. */
if (cmd->argc-1 == 2) {
if (strcasecmp("max", cmd->argv[2]) == 0)
rlim->rlim_max = sysconf(_SC_OPEN_MAX);
else {
/* Check that the non-max argument is a number, and error out if not.
*/
char *tmp = NULL;
long num = strtol(cmd->argv[2], &tmp, 10);
if (tmp && *tmp)
CONF_ERROR(cmd, "badly formatted argument");
rlim->rlim_max = num;
}
}
add_config_param(cmd->argv[0], 2, (void *) rlim, NULL);
}
return HANDLED(cmd);
#else
CONF_ERROR(cmd, "RLimitOpenFiles is not supported on this platform");
#endif
}
MODRET set_syslogfacility(cmd_rec *cmd) {
int i;
struct {
char *name;
int facility;
} factable[] = {
{ "AUTH", LOG_AUTHPRIV },
{ "AUTHPRIV", LOG_AUTHPRIV },
#ifdef HAVE_LOG_FTP
{ "FTP", LOG_FTP },
#endif
#ifdef HAVE_LOG_CRON
{ "CRON", LOG_CRON },
#endif
{ "DAEMON", LOG_DAEMON },
{ "KERN", LOG_KERN },
{ "LOCAL0", LOG_LOCAL0 },
{ "LOCAL1", LOG_LOCAL1 },
{ "LOCAL2", LOG_LOCAL2 },
{ "LOCAL3", LOG_LOCAL3 },
{ "LOCAL4", LOG_LOCAL4 },
{ "LOCAL5", LOG_LOCAL5 },
{ "LOCAL6", LOG_LOCAL6 },
{ "LOCAL7", LOG_LOCAL7 },
{ "LPR", LOG_LPR },
{ "MAIL", LOG_MAIL },
{ "NEWS", LOG_NEWS },
{ "USER", LOG_USER },
{ "UUCP", LOG_UUCP },
{ NULL, 0 } };
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
for (i = 0; factable[i].name; i++) {
if (strcasecmp(cmd->argv[1], factable[i].name) == 0) {
log_closesyslog();
log_setfacility(factable[i].facility);
pr_signals_block();
switch (log_opensyslog(NULL)) {
case -1:
pr_signals_unblock();
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to open syslog: ",
strerror(errno), NULL));
break;
case PR_LOG_WRITABLE_DIR:
pr_signals_unblock();
CONF_ERROR(cmd,
"you are attempting to log to a world writeable directory");
break;
case PR_LOG_SYMLINK:
pr_signals_unblock();
CONF_ERROR(cmd, "you are attempting to log to a symbolic link");
break;
default:
break;
}
pr_signals_unblock();
return HANDLED(cmd);
}
}
CONF_ERROR(cmd, "argument must be a valid syslog facility");
}
MODRET set_timesgmt(cmd_rec *cmd) {
int bool = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = bool;
c->flags |= CF_MERGEDOWN;
return HANDLED(cmd);
}
MODRET set_regex(cmd_rec *cmd, char *param, char *type) {
#if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP)
regex_t *preg = NULL;
config_rec *c = NULL;
int res = 0;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|CONF_DIR|
CONF_DYNDIR);
pr_log_debug(DEBUG4, "%s: compiling %s regex '%s'", cmd->argv[0], type,
cmd->argv[1]);
preg = pr_regexp_alloc();
res = regcomp(preg, cmd->argv[1], REG_EXTENDED|REG_NOSUB);
if (res != 0) {
char errstr[200] = {'\0'};
regerror(res, preg, errstr, sizeof(errstr));
pr_regexp_free(preg);
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", cmd->argv[1], "' failed regex "
"compilation: ", errstr, NULL));
}
c = add_config_param(param, 1, preg);
c->flags |= CF_MERGEDOWN;
return HANDLED(cmd);
#else /* no regular expression support at the moment */
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", param, " directive cannot be "
"used on this system, as you do not have POSIX compliant regex support",
NULL));
#endif
}
MODRET set_allowfilter(cmd_rec *cmd) {
return set_regex(cmd, cmd->argv[0], "allow");
}
MODRET set_denyfilter(cmd_rec *cmd) {
return set_regex(cmd, cmd->argv[0], "deny");
}
MODRET set_passiveports(cmd_rec *cmd) {
int pasv_min_port, pasv_max_port;
config_rec *c = NULL;
CHECK_ARGS(cmd, 2);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
pasv_min_port = atoi(cmd->argv[1]);
pasv_max_port = atoi(cmd->argv[2]);
/* Sanity check */
if (pasv_min_port <= 0 || pasv_min_port > 65535)
CONF_ERROR(cmd, "min port must be allowable port number");
if (pasv_max_port <= 0 || pasv_max_port > 65535)
CONF_ERROR(cmd, "max port must be allowable port number");
if (pasv_min_port < 1024 || pasv_max_port < 1024)
CONF_ERROR(cmd, "port numbers must be above 1023");
if (pasv_max_port < pasv_min_port)
CONF_ERROR(cmd, "min port must be equal to or less than max port");
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = pasv_min_port;
c->argv[1] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[1]) = pasv_max_port;
return HANDLED(cmd);
}
MODRET set_pathallowfilter(cmd_rec *cmd) {
return set_regex(cmd, cmd->argv[0], "allow");
}
MODRET set_pathdenyfilter(cmd_rec *cmd) {
return set_regex(cmd, cmd->argv[0], "deny");
}
MODRET set_allowforeignaddress(cmd_rec *cmd) {
int bool = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
if ((bool = get_boolean(cmd,1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = bool;
c->flags |= CF_MERGEDOWN;
return HANDLED(cmd);
}
MODRET set_commandbuffersize(cmd_rec *cmd) {
int size = 0;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
/* NOTE: need to add checks for maximum possible sizes, negative sizes. */
size = atoi(cmd->argv[1]);
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = size;
return HANDLED(cmd);
}
MODRET set_cdpath(cmd_rec *cmd) {
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
c = add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
c->flags |= CF_MERGEDOWN;
return HANDLED(cmd);
}
MODRET add_directory(cmd_rec *cmd) {
config_rec *c;
char *dir,*rootdir = NULL;
int flags = 0;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
dir = cmd->argv[1];
if (*dir != '/' &&
*dir != '~' &&
(!cmd->config ||
cmd->config->config_type != CONF_ANON))
CONF_ERROR(cmd, "relative path not allowed in non-<Anonymous> sections");
/* If in anonymous mode, and path is relative, just cat anon root
* and relative path.
*
* Note: This is no longer necessary, because we don't interpolate anonymous
* directories at run-time.
*/
if (cmd->config &&
cmd->config->config_type == CONF_ANON &&
*dir != '/' &&
*dir != '~') {
if (strcmp(dir, "*") != 0)
dir = pdircat(cmd->tmp_pool, "/", dir, NULL);
rootdir = cmd->config->name;
} else
flags |= CF_DEFER;
/* Check to see that there isn't already a config for this directory,
* but only if we're not in an <Anonymous> section. Due to the way
* in which later <Directory> checks are done, <Directory> blocks inside
* <Anonymous> sections are handled differently than outside, probably
* overriding their outside counterparts (if necessary). This is
* probably OK, as this overriding only takes effect for the <Anonymous>
* user.
*/
if (!check_context(cmd, CONF_ANON) &&
find_config(cmd->server->conf, CONF_DIR, dir, FALSE) != NULL)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
cmd->argv[0], ": <Directory> section already configured for '",
cmd->argv[1], "'", NULL));
/* Check for any expandable variables, and mark this config_rec for
* deferred resolution if present
*/
if (strstr(dir, "%u") &&
!(flags & CF_DEFER))
flags |= CF_DEFER;
c = pr_parser_config_ctxt_open(dir);
c->argc = 2;
c->argv = pcalloc(c->pool, 3 * sizeof(void *));
if (rootdir)
c->argv[1] = pstrdup(c->pool, rootdir);
c->config_type = CONF_DIR;
c->flags |= flags;
if (!(c->flags & CF_DEFER))
pr_log_debug(DEBUG2,
"<Directory %s>: adding section for resolved path '%s'", cmd->argv[1],
dir);
else
pr_log_debug(DEBUG2,
"<Directory %s>: deferring resolution of path", cmd->argv[1]);
return HANDLED(cmd);
}
MODRET set_hidefiles(cmd_rec *cmd) {
#if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP)
regex_t *regexp = NULL;
config_rec *c = NULL;
int res;
unsigned int precedence = 0;
unsigned char inverted = FALSE;
int ctxt = (cmd->config && cmd->config->config_type != CONF_PARAM ?
cmd->config->config_type : cmd->server->config_type ?
cmd->server->config_type : CONF_ROOT);
/* This directive must have either 1, or 3, arguments */
if (cmd->argc-1 != 1 && cmd->argc-1 != 3)
CONF_ERROR(cmd, "wrong number of parameters");
CHECK_CONF(cmd, CONF_DIR|CONF_DYNDIR);
/* Set the precedence for this config_rec based on its configuration
* context.
*/
if (ctxt & CONF_DIR)
precedence = 1;
else
precedence = 2;
/* Check for a "none" argument, which is used to nullify inherited
* HideFiles configurations from parent directories.
*/
if (!strcasecmp(cmd->argv[1], "none")) {
pr_log_debug(DEBUG4, "setting %s to NULL", cmd->argv[0]);
c = add_config_param(cmd->argv[0], 1, NULL);
c->flags |= CF_MERGEDOWN_MULTI;
return HANDLED(cmd);
}
/* Check for a leading '!' prefix, signifying regex negation */
if (*cmd->argv[1] == '!') {
inverted = TRUE;
cmd->argv[1]++;
}
regexp = pr_regexp_alloc();
if ((res = regcomp(regexp, cmd->argv[1], REG_EXTENDED|REG_NOSUB)) != 0) {
char errstr[200] = {'\0'};
regerror(res, regexp, errstr, sizeof(errstr));
pr_regexp_free(regexp);
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", cmd->argv[1],
"' failed regex compilation: ", errstr, NULL));
}
/* If the directive was used with 3 arguments, then the optional
* classifiers, and classifier expression, were used. Make sure that
* a valid classifier was used.
*/
if (cmd->argc-1 == 3) {
if (!strcmp(cmd->argv[2], "user") ||
!strcmp(cmd->argv[2], "group") ||
!strcmp(cmd->argv[2], "class")) {
/* no-op */
} else
return ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool, cmd->argv[0],
": unknown classifier used: '", cmd->argv[2], "'", NULL));
}
if (cmd->argc-1 == 1) {
c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(regex_t *));
*((regex_t **) c->argv[0]) = regexp;
c->argv[1] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[1]) = inverted;
c->argv[2] = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[2]) = precedence;
} else if (cmd->argc-1 == 3) {
array_header *acl = NULL;
int argc = cmd->argc - 3;
char **argv = cmd->argv + 2;
acl = pr_expr_create(cmd->tmp_pool, &argc, argv);
c = add_config_param(cmd->argv[0], 0);
c->argc = argc + 4;
/* Add 5 to argc for the argv of the config_rec: one for the
* regexp, one for the 'inverted' value, one for the precedence,
* one for the classifier, and one for the terminating NULL
*/
c->argv = pcalloc(c->pool, ((argc + 5) * sizeof(char *)));
/* Capture the config_rec's argv pointer for doing the by-hand
* population.
*/
argv = (char **) c->argv;
/* Copy in the regexp. */
*argv = pcalloc(c->pool, sizeof(regex_t *));
*((regex_t **) *argv++) = regexp;
/* Copy in the 'inverted' flag */
*argv = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) *argv++) = inverted;
/* Copy in the precedence. */
*argv = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) *argv++) = precedence;
/* Copy in the expression classifier */
*argv++ = pstrdup(c->pool, cmd->argv[2]);
/* now, copy in the expression arguments */
if (argc && acl) {
while (argc--) {
*argv++ = pstrdup(c->pool, *((char **) acl->elts));
acl->elts = ((char **) acl->elts) + 1;
}
}
/* don't forget the terminating NULL */
*argv = NULL;
}
c->flags |= CF_MERGEDOWN_MULTI;
return HANDLED(cmd);
#else /* no regular expression support at the moment */
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The HideFiles directive cannot be "
"used on this system, as you do not have POSIX compliant regex support",
NULL));
#endif
}
MODRET set_hidenoaccess(cmd_rec *cmd) {
int bool = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ANON|CONF_DIR);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = bool;
c->flags |= CF_MERGEDOWN;
return HANDLED(cmd);
}
MODRET set_hideuser(cmd_rec *cmd) {
config_rec *c = NULL;
char *user = NULL;
struc