/*
* 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-2005 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.
*/
/* Data transfer module for ProFTPD
*
* $Id: mod_xfer.c,v 1.191 2005/12/19 18:59:22 castaglia Exp $
*/
#include "conf.h"
#include "privs.h"
#include <signal.h>
#ifdef HAVE_SYS_SENDFILE_H
#include <sys/sendfile.h>
#endif
#ifdef HAVE_REGEX_H
#include <regex.h>
#endif
extern module auth_module;
extern pid_t mpid;
/* Variables for this module */
static pr_fh_t *retr_fh = NULL;
static pr_fh_t *stor_fh = NULL;
static unsigned char have_prot = FALSE;
static unsigned char use_sendfile = TRUE;
/* Transfer rate variables */
static long double xfer_rate_kbps = 0.0, xfer_rate_bps = 0.0;
static off_t xfer_rate_freebytes = 0.0;
static unsigned char have_xfer_rate = FALSE;
static unsigned int xfer_rate_scoreboard_updates = 0;
/* Transfer rate functions */
static void xfer_rate_lookup(cmd_rec *);
static unsigned char xfer_rate_parse_cmdlist(config_rec *, char *);
static void xfer_rate_sigmask(unsigned char);
static void xfer_rate_throttle(off_t, unsigned int);
module xfer_module;
static int xfer_errno;
static unsigned long find_max_nbytes(char *directive) {
config_rec *c = NULL;
unsigned int ctxt_precedence = 0;
unsigned char have_user_limit, have_group_limit, have_class_limit,
have_all_limit;
unsigned long max_nbytes = 0UL;
have_user_limit = have_group_limit = have_class_limit =
have_all_limit = FALSE;
c = find_config(CURRENT_CONF, CONF_PARAM, directive, FALSE);
while (c) {
/* This check is for more than three arguments: one argument is the
* classifier (i.e. "user", "group", or "class"), one argument is
* the precedence, one is the number of bytes; the remaining arguments
* are the individual items in the configured expression.
*/
if (c->argc > 3) {
if (strcmp(c->argv[2], "user") == 0) {
if (pr_expr_eval_user_or((char **) &c->argv[3])) {
if (*((unsigned int *) c->argv[1]) > ctxt_precedence) {
/* Set the context precedence */
ctxt_precedence = *((unsigned int *) c->argv[1]);
max_nbytes = *((unsigned long *) c->argv[0]);
have_group_limit = have_class_limit = have_all_limit = FALSE;
have_user_limit = TRUE;
}
}
} else if (strcmp(c->argv[2], "group") == 0) {
if (pr_expr_eval_group_or((char **) &c->argv[3])) {
if (*((unsigned int *) c->argv[1]) > ctxt_precedence) {
/* Set the context precedence */
ctxt_precedence = *((unsigned int *) c->argv[1]);
max_nbytes = *((unsigned long *) c->argv[0]);
have_user_limit = have_class_limit = have_all_limit = FALSE;
have_group_limit = TRUE;
}
}
} else if (strcmp(c->argv[2], "class") == 0) {
if (pr_expr_eval_class_or((char **) &c->argv[3])) {
if (*((unsigned int *) c->argv[1]) > ctxt_precedence) {
/* Set the context precedence */
ctxt_precedence = *((unsigned int *) c->argv[1]);
max_nbytes = *((unsigned long *) c->argv[0]);
have_user_limit = have_group_limit = have_all_limit = FALSE;
have_class_limit = TRUE;
}
}
}
} else {
if (*((unsigned int *) c->argv[1]) > ctxt_precedence) {
/* Set the context precedence. */
ctxt_precedence = *((unsigned int *) c->argv[1]);
max_nbytes = *((unsigned long *) c->argv[0]);
have_user_limit = have_group_limit = have_class_limit = FALSE;
have_all_limit = TRUE;
}
}
c = find_config_next(c, c->next, CONF_PARAM, directive, FALSE);
}
/* Print out some nice debugging information. */
if (max_nbytes > 0UL &&
(have_user_limit || have_group_limit ||
have_class_limit || have_all_limit)) {
pr_log_debug(DEBUG5, "%s (%lu bytes) in effect for %s",
directive, max_nbytes,
have_user_limit ? "user " : have_group_limit ? "group " :
have_class_limit ? "class " : "all");
}
return max_nbytes;
}
static unsigned long parse_max_nbytes(char *nbytes_str, char *units_str) {
long res;
unsigned long nbytes;
char *endp = NULL;
float units_factor = 0.0;
/* clear any previous local errors */
xfer_errno = 0;
/* first, check the given units to determine the correct mulitplier
*/
if (!strcasecmp("Gb", units_str)) {
units_factor = 1024.0 * 1024.0 * 1024.0;
} else if (!strcasecmp("Mb", units_str)) {
units_factor = 1024.0 * 1024.0;
} else if (!strcasecmp("Kb", units_str)) {
units_factor = 1024.0;
} else if (!strcasecmp("b", units_str)) {
units_factor = 1.0;
} else {
xfer_errno = EINVAL;
return 0;
}
/* make sure a number was given */
if (!isdigit((int) *nbytes_str)) {
xfer_errno = EINVAL;
return 0;
}
/* knowing the factor, now convert the given number string to a real
* number
*/
res = strtol(nbytes_str, &endp, 10);
if (errno == ERANGE) {
xfer_errno = ERANGE;
return 0;
}
if (endp && *endp) {
xfer_errno = EINVAL;
return 0;
}
/* don't bother to apply the factor if that will cause the number to
* overflow
*/
if (res > (ULONG_MAX / units_factor)) {
xfer_errno = ERANGE;
return 0;
}
nbytes = (unsigned long) res * units_factor;
return nbytes;
}
static void _log_transfer(char direction, char abort_flag) {
struct timeval end_time;
char *fullpath = NULL;
memset(&end_time, '\0', sizeof(end_time));
if (session.xfer.start_time.tv_sec != 0) {
gettimeofday(&end_time, NULL);
end_time.tv_sec -= session.xfer.start_time.tv_sec;
if (end_time.tv_usec >= session.xfer.start_time.tv_usec)
end_time.tv_usec -= session.xfer.start_time.tv_usec;
else {
end_time.tv_usec = 1000000L - (session.xfer.start_time.tv_usec -
end_time.tv_usec);
end_time.tv_sec--;
}
}
fullpath = dir_abs_path(session.xfer.p, session.xfer.path, TRUE);
if ((session.sf_flags & SF_ANON) != 0) {
xferlog_write(end_time.tv_sec, session.c->remote_name,
session.xfer.total_bytes, fullpath,
(session.sf_flags & SF_ASCII ? 'a' : 'b'), direction,
'a', session.anon_user, abort_flag);
} else {
xferlog_write(end_time.tv_sec, session.c->remote_name,
session.xfer.total_bytes, fullpath,
(session.sf_flags & SF_ASCII ? 'a' : 'b'), direction,
'r', session.user, abort_flag);
}
pr_log_debug(DEBUG1, "Transfer %s %" PR_LU " bytes in %ld.%02lu seconds",
abort_flag == 'c' ? "completed:" : "aborted after",
(pr_off_t) session.xfer.total_bytes, (long) end_time.tv_sec,
(unsigned long)(end_time.tv_usec / 10000));
}
/* Code borrowed from src/dirtree.c's get_word() -- modified to separate
* words on commas as well as spaces.
*/
static char *get_cmd_from_list(char **list) {
char *res = NULL, *dst = NULL;
unsigned char quote_mode = FALSE;
while (**list && isspace((int) **list))
(*list)++;
if (!**list)
return NULL;
res = dst = *list;
if (**list == '\"') {
quote_mode = TRUE;
(*list)++;
}
while (**list && **list != ',' &&
(quote_mode ? (**list != '\"') : (!isspace((int) **list)))) {
if (**list == '\\' && quote_mode) {
/* escaped char */
if (*((*list) + 1))
*dst = *(++(*list));
}
*dst++ = **list;
++(*list);
}
if (**list)
(*list)++;
*dst = '\0';
return res;
}
static void xfer_rate_lookup(cmd_rec *cmd) {
config_rec *c = NULL;
char *xfer_cmd = NULL;
unsigned char have_user_rate = FALSE, have_group_rate = FALSE,
have_class_rate = FALSE, have_all_rate = FALSE;
unsigned int precedence = 0;
/* Make sure the variables are (re)initialized */
xfer_rate_kbps = xfer_rate_bps = 0.0;
xfer_rate_freebytes = 0;
xfer_rate_scoreboard_updates = 0;
have_xfer_rate = FALSE;
c = find_config(CURRENT_CONF, CONF_PARAM, "TransferRate", FALSE);
/* Note: need to cycle through all the matching config_recs, and using
* the information from the current config_rec only if it matches
* the target *and* has a higher precedence than any of the previously
* found config_recs.
*/
while (c) {
char **cmdlist = (char **) c->argv[0];
unsigned char matched_cmd = FALSE;
/* Does this TransferRate apply to the current command? Note: this
* could be made more efficient by using bitmasks rather than string
* comparisons.
*/
for (xfer_cmd = *cmdlist; xfer_cmd; xfer_cmd = *(cmdlist++)) {
if (!strcasecmp(xfer_cmd, cmd->argv[0])) {
matched_cmd = TRUE;
break;
}
}
/* No -- continue on to the next TransferRate. */
if (!matched_cmd) {
c = find_config_next(c, c->next, CONF_PARAM, "TransferRate", FALSE);
continue;
}
if (c->argc > 4) {
if (strcmp(c->argv[4], "user") == 0) {
if (pr_expr_eval_user_or((char **) &c->argv[5]) &&
*((unsigned int *) c->argv[3]) > precedence) {
/* Set the precedence. */
precedence = *((unsigned int *) c->argv[3]);
xfer_rate_kbps = *((long double *) c->argv[1]);
xfer_rate_freebytes = *((off_t *) c->argv[2]);
have_xfer_rate = TRUE;
have_user_rate = TRUE;
have_group_rate = have_class_rate = have_all_rate = FALSE;
}
} else if (strcmp(c->argv[4], "group") == 0) {
if (pr_expr_eval_group_and((char **) &c->argv[5]) &&
*((unsigned int *) c->argv[3]) > precedence) {
/* Set the precedence. */
precedence = *((unsigned int *) c->argv[3]);
xfer_rate_kbps = *((long double *) c->argv[1]);
xfer_rate_freebytes = *((off_t *) c->argv[2]);
have_xfer_rate = TRUE;
have_group_rate = TRUE;
have_user_rate = have_class_rate = have_all_rate = FALSE;
}
} else if (strcmp(c->argv[4], "class") == 0) {
if (pr_expr_eval_class_or((char **) &c->argv[5]) &&
*((unsigned int *) c->argv[3]) > precedence) {
/* Set the precedence. */
precedence = *((unsigned int *) c->argv[3]);
xfer_rate_kbps = *((long double *) c->argv[1]);
xfer_rate_freebytes = *((off_t *) c->argv[2]);
have_xfer_rate = TRUE;
have_class_rate = TRUE;
have_user_rate = have_group_rate = have_all_rate = FALSE;
}
}
} else {
if (*((unsigned int *) c->argv[3]) > precedence) {
/* Set the precedence. */
precedence = *((unsigned int *) c->argv[3]);
xfer_rate_kbps = *((long double *) c->argv[1]);
xfer_rate_freebytes = *((off_t *) c->argv[2]);
have_xfer_rate = TRUE;
have_all_rate = TRUE;
have_user_rate = have_group_rate = have_class_rate = FALSE;
}
}
c = find_config_next(c, c->next, CONF_PARAM, "TransferRate", FALSE);
}
/* Print out a helpful debugging message. */
if (have_xfer_rate) {
pr_log_debug(DEBUG3, "TransferRate (%.3Lf KB/s, %" PR_LU
" bytes free) in effect%s", xfer_rate_kbps,
(pr_off_t) xfer_rate_freebytes,
have_user_rate ? " for current user" :
have_group_rate ? " for current group" :
have_class_rate ? " for current class" : "");
/* Convert the configured Kbps to bytes per usec, for use later.
* The 1024.0 factor converts for Kbytes to bytes, and the
* 1000000.0 factor converts from secs to usecs.
*/
xfer_rate_bps = xfer_rate_kbps * 1024.0;
}
}
static unsigned char xfer_rate_parse_cmdlist(config_rec *c, char *cmdlist) {
char *cmd = NULL;
array_header *cmds = NULL;
/* Allocate an array_header. */
cmds = make_array(c->pool, 0, sizeof(char *));
/* Add each command to the array, checking for invalid commands or
* duplicates.
*/
while ((cmd = get_cmd_from_list(&cmdlist)) != NULL) {
/* Is the given command a valid one for this directive? */
if (strcasecmp(cmd, C_APPE) && strcasecmp(cmd, C_RETR) &&
strcasecmp(cmd, C_STOR) && strcasecmp(cmd, C_STOU)) {
pr_log_debug(DEBUG0, "invalid TransferRate command: %s", cmd);
return FALSE;
}
*((char **) push_array(cmds)) = pstrdup(c->pool, cmd);
}
/* Terminate the array with a NULL. */
*((char **) push_array(cmds)) = NULL;
/* Store the array of commands in the config_rec. */
c->argv[0] = (void *) cmds->elts;
return TRUE;
}
/* Very similar to the {block,unblock}_signals() function, this masks most
* of the same signals -- except for TERM. This allows a throttling process
* to be killed by the admin.
*/
static void xfer_rate_sigmask(unsigned char block) {
static sigset_t sig_set;
if (block) {
sigemptyset(&sig_set);
sigaddset(&sig_set, SIGCHLD);
sigaddset(&sig_set, SIGUSR1);
sigaddset(&sig_set, SIGINT);
sigaddset(&sig_set, SIGQUIT);
sigaddset(&sig_set, SIGURG);
#ifdef SIGIO
sigaddset(&sig_set, SIGIO);
#endif /* SIGIO */
#ifdef SIGBUS
sigaddset(&sig_set, SIGBUS);
#endif /* SIGBUS */
sigaddset(&sig_set, SIGHUP);
while (sigprocmask(SIG_BLOCK, &sig_set, NULL) < 0) {
if (errno == EINTR) {
pr_signals_handle();
continue;
}
break;
}
} else {
while (sigprocmask(SIG_UNBLOCK, &sig_set, NULL) < 0) {
if (errno == EINTR) {
pr_signals_handle();
continue;
}
break;
}
}
}
/* Returns the difference, in milliseconds, between the given timeval and
* now.
*/
static long xfer_rate_since(struct timeval *then) {
struct timeval now;
gettimeofday(&now, NULL);
return (((now.tv_sec - then->tv_sec) * 1000L) +
((now.tv_usec - then->tv_usec) / 1000L));
}
static void xfer_rate_throttle(off_t xferlen, unsigned int xfer_ending) {
long ideal = 0, elapsed = 0;
off_t orig_xferlen = xferlen;
/* Calculate the time interval since the transfer of data started. */
elapsed = xfer_rate_since(&session.xfer.start_time);
/* Perform no throttling if no throttling has been configured. */
if (!have_xfer_rate) {
xfer_rate_scoreboard_updates++;
if (xfer_ending ||
xfer_rate_scoreboard_updates % PR_TUNABLE_XFER_SCOREBOARD_UPDATES == 0) {
/* Update the scoreboard. */
pr_scoreboard_update_entry(getpid(),
PR_SCORE_XFER_LEN, orig_xferlen,
PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed,
NULL);
xfer_rate_scoreboard_updates = 0;
}
return;
}
/* Give credit for any configured freebytes. */
if (xferlen && xfer_rate_freebytes) {
if (xferlen > xfer_rate_freebytes)
/* Decrement the number of bytes transferred by the freebytes, so that
* any throttling does not take into account the freebytes.
*/
xferlen -= xfer_rate_freebytes;
else {
xfer_rate_scoreboard_updates++;
/* The number of bytes transferred is less than the freebytes. Just
* update the scoreboard -- no throttling needed.
*/
if (xfer_ending ||
xfer_rate_scoreboard_updates % PR_TUNABLE_XFER_SCOREBOARD_UPDATES == 0) {
pr_scoreboard_update_entry(getpid(),
PR_SCORE_XFER_LEN, orig_xferlen,
PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed,
NULL);
xfer_rate_scoreboard_updates = 0;
}
return;
}
}
ideal = xferlen * 1000L / xfer_rate_bps;
if (ideal > elapsed) {
struct timeval tv;
/* Setup for the select. We use select() instead of usleep() because it
* seems to be far more portable across platforms.
*
* ideal and elapsed are in milleconds, but tv_usec will be microseconds,
* so be sure to convert properly.
*/
tv.tv_usec = (ideal - elapsed) * 1000;
tv.tv_sec = tv.tv_usec / 1000000L;
tv.tv_usec = tv.tv_usec % 1000000L;
pr_log_debug(DEBUG7, "transferring too fast, delaying %ld sec%s, %ld usecs",
(long int) tv.tv_sec, tv.tv_sec == 1 ? "" : "s", (long int) tv.tv_usec);
/* No interruptions, please... */
xfer_rate_sigmask(TRUE);
if (select(0, NULL, NULL, NULL, &tv) < 0)
pr_log_pri(PR_LOG_WARNING, "warning: unable to throttle bandwidth: %s",
strerror(errno));
xfer_rate_sigmask(FALSE);
pr_signals_handle();
/* Update the scoreboard. */
pr_scoreboard_update_entry(getpid(),
PR_SCORE_XFER_LEN, orig_xferlen,
PR_SCORE_XFER_ELAPSED, (unsigned long) ideal,
NULL);
} else {
/* Update the scoreboard. */
pr_scoreboard_update_entry(getpid(),
PR_SCORE_XFER_LEN, orig_xferlen,
PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed,
NULL);
}
return;
}
static int transmit_normal(char *buf, long bufsz) {
long sz = pr_fsio_read(retr_fh, buf, bufsz);
if (sz <= 0)
return 0;
return pr_data_xfer(buf, sz);
}
#ifdef HAVE_SENDFILE
static int transmit_sendfile(off_t count, off_t *offset,
pr_sendfile_t *retval) {
/* We don't use sendfile() if:
* - We're using bandwidth throttling.
* - We're transmitting an ASCII file.
* - We're using RFC2228 data channel protection
* - There's no data left to transmit.
* - UseSendfile is set to off.
*/
if (have_xfer_rate ||
!(session.xfer.file_size - count) ||
(session.sf_flags & (SF_ASCII|SF_ASCII_OVERRIDE)) ||
have_prot ||
!use_sendfile)
return 0;
retry:
*retval = pr_data_sendfile(PR_FH_FD(retr_fh), offset,
session.xfer.file_size - count);
if (*retval == -1) {
switch (errno) {
case EAGAIN:
case EINTR:
/* Interrupted call, or the other side wasn't ready yet. */
pr_signals_handle();
goto retry;
case EPIPE:
case ECONNRESET:
case ETIMEDOUT:
case EHOSTUNREACH:
/* Other side broke the connection. */
break;
#ifdef ENOSYS
case ENOSYS:
#endif /* ENOSYS */
case EINVAL:
/* No sendfile support, apparently. Try it the normal way. */
return 0;
break;
default:
pr_log_pri(PR_LOG_ERR, "transmit_sendfile() error, "
"reverting to normal data transmission: [%d] %s", errno,
strerror(errno));
return 0;
}
}
return 1;
}
#endif /* HAVE_SENDFILE */
/* Note: the count and offset arguments are only for the benefit of
* transmit_sendfile(), if sendfile support is enabled. The transmit_normal()
* function only needs/uses buf and bufsz.
*/
static long transmit_data(off_t count, off_t *offset, char *buf, long bufsz) {
long res;
#ifdef TCP_CORK
int on = 1;
socklen_t len = sizeof(int);
#endif /* TCP_CORK */
#ifdef SOL_TCP
int tcp_level = SOL_TCP;
#else
int tcp_level = IPPROTO_TCP;
#endif /* SOL_TCP */
#ifdef HAVE_SENDFILE
pr_sendfile_t retval;
#endif /* HAVE_SENDFILE */
#ifdef TCP_CORK
/* Note: TCP_CORK is a Linuxism, introduced with the 2.4 kernel. It
* has effects similar to BSD's TCP_NOPUSH option.
*/
if (setsockopt(PR_NETIO_FD(session.d->outstrm), tcp_level, TCP_CORK, &on,
len) < 0)
pr_log_pri(PR_LOG_NOTICE, "error setting TCP_CORK: %s", strerror(errno));
#endif /* TCP_CORK */
#ifdef HAVE_SENDFILE
if (transmit_sendfile(count, offset, &retval) == 0)
res = transmit_normal(buf, bufsz);
else
res = (long) retval;
#else
res = transmit_normal(buf, bufsz);
#endif /* HAVE_SENDFILE */
#ifdef TCP_CORK
on = 0;
if (setsockopt(PR_NETIO_FD(session.d->outstrm), tcp_level, TCP_CORK, &on,
len) < 0)
pr_log_pri(PR_LOG_NOTICE, "error setting TCP_CORK: %s", strerror(errno));
#endif /* TCP_CORK */
return res;
}
static void stor_chown(void) {
struct stat st;
char *xfer_path = NULL;
if (session.xfer.xfer_type == STOR_HIDDEN)
xfer_path = session.xfer.path_hidden;
else
xfer_path = session.xfer.path;
/* session.fsgid defaults to -1, so chown(2) won't chgrp unless specifically
* requested via GroupOwner.
*/
if ((session.fsuid != (uid_t) -1) && xfer_path) {
int err = 0, iserr = 0;
PRIVS_ROOT
if (pr_fsio_chown(xfer_path, session.fsuid, session.fsgid) == -1) {
iserr++;
err = errno;
}
PRIVS_RELINQUISH
if (iserr)
pr_log_pri(PR_LOG_WARNING, "chown(%s) as root failed: %s", xfer_path,
strerror(err));
else {
if (session.fsgid != (gid_t) -1)
pr_log_debug(DEBUG2, "root chown(%s) to uid %lu, gid %lu successful",
xfer_path, (unsigned long) session.fsuid,
(unsigned long) session.fsgid);
else
pr_log_debug(DEBUG2, "root chown(%s) to uid %lu successful", xfer_path,
(unsigned long) session.fsuid);
pr_fs_clear_cache();
pr_fsio_stat(xfer_path, &st);
/* The chmod happens after the chown because chown will remove
* the S{U,G}ID bits on some files (namely, directories); the subsequent
* chmod is used to restore those dropped bits. This makes it
* necessary to use root privs when doing the chmod as well (at least
* in the case of chown'ing the file via root privs) in order to insure
* that the mode can be set (a file might be being "given away", and if
* root privs aren't used, the chmod() will fail because the old owner/
* session user doesn't have the necessary privileges to do so).
*/
iserr = 0;
PRIVS_ROOT
if (pr_fsio_chmod(xfer_path, st.st_mode) < 0)
iserr++;
PRIVS_RELINQUISH
if (iserr)
pr_log_debug(DEBUG0, "root chmod(%s) to %04o failed: %s", xfer_path,
(unsigned int) st.st_mode, strerror(errno));
else
pr_log_debug(DEBUG2, "root chmod(%s) to %04o successful", xfer_path,
(unsigned int) st.st_mode);
}
} else if ((session.fsgid != (gid_t) -1) && xfer_path) {
if (pr_fsio_chown(xfer_path, (uid_t) -1, session.fsgid) == -1)
pr_log_pri(PR_LOG_WARNING, "chown(%s) failed: %s", xfer_path,
strerror(errno));
else {
pr_log_debug(DEBUG2, "chown(%s) to gid %lu successful", xfer_path,
(unsigned long) session.fsgid);
pr_fs_clear_cache();
pr_fsio_stat(xfer_path, &st);
if (pr_fsio_chmod(xfer_path, st.st_mode) < 0)
pr_log_debug(DEBUG0, "chmod(%s) to %04o failed: %s", xfer_path,
(unsigned int) st.st_mode, strerror(errno));
}
}
}
static void retr_abort(void) {
/* Isn't necessary to send anything here, just cleanup */
if (retr_fh) {
pr_fsio_close(retr_fh);
retr_fh = NULL;
}
_log_transfer('o', 'i');
}
static void retr_complete(void) {
pr_fsio_close(retr_fh);
retr_fh = NULL;
}
static void stor_abort(void) {
unsigned char *delete_stores = NULL;
if (stor_fh) {
if (pr_fsio_close(stor_fh) != 0)
pr_log_pri(PR_LOG_NOTICE, "notice: error closing '%s': %s",
stor_fh->fh_path, strerror(errno));
stor_fh = NULL;
}
if (session.xfer.xfer_type == STOR_HIDDEN) {
/* If hidden stor aborted, remove only hidden file, not real one */
if (session.xfer.path_hidden)
pr_fsio_unlink(session.xfer.path_hidden);
} else if (session.xfer.path) {
if ((delete_stores = get_param_ptr(CURRENT_CONF, "DeleteAbortedStores",
FALSE)) != NULL && *delete_stores == TRUE)
pr_fsio_unlink(session.xfer.path);
}
_log_transfer('i', 'i');
}
static int stor_complete(void) {
int res = 0;
if (pr_fsio_close(stor_fh) != 0) {
pr_log_pri(PR_LOG_NOTICE, "notice: error closing '%s': %s",
stor_fh->fh_path, strerror(errno));
res = -1;
}
stor_fh = NULL;
return res;
}
static int get_hidden_store_path(cmd_rec *cmd, char *path) {
char *c = NULL, *hidden_path;
int dotcount = 0, foundslash = 0, basenamestart = 0, maxlen;
/* We have to also figure out the temporary hidden file name for receiving
* this transfer. Length is +5 due to .in. prepended and "." at end.
*/
/* Figure out where the basename starts */
for (c = path; *c; ++c) {
if (*c == '/') {
foundslash = 1;
basenamestart = dotcount = 0;
} else if (*c == '.') {
++dotcount;
/* Keep track of leading dots, ... is normal, . and .. are special.
* So if we exceed ".." it becomes a normal file. Retroactively consider
* this the possible start of the basename.
*/
if ((dotcount > 2) &&
!basenamestart)
basenamestart = ((unsigned long) c - (unsigned long) path) - dotcount;
} else {
/* We found a nonslash, nondot character; if this is the first time
* we found one since the last slash, remember this as the possible
* start of the basename.
*/
if (!basenamestart)
basenamestart = ((unsigned long) c - (unsigned long) path) - dotcount;
}
}
if (!basenamestart) {
/* This probably shouldn't happen */
pr_response_add_err(R_451, "%s: Bad file name", path);
return -1;
}
/* Add five for the ".in." and "." characters, plus one for a terminating
* NUL.
*/
maxlen = strlen(path) + 6;
if (maxlen > PR_TUNABLE_PATH_MAX) {
/* This probably shouldn't happen */
pr_response_add_err(R_451, "%s: File name too long", path);
return -1;
}
if (pr_table_add(cmd->notes, "mod_xfer.store-hidden-path", NULL, 0) < 0)
pr_log_pri(PR_LOG_NOTICE,
"notice: error adding 'mod_xfer.store-hidden-path': %s",
strerror(errno));
if (!foundslash) {
/* Simple local file name */
hidden_path = pstrcat(cmd->tmp_pool, ".in.", path, ".", NULL);
pr_log_pri(PR_LOG_DEBUG, "HiddenStore: local path, will rename %s to %s",
hidden_path, path);
} else {
/* Complex relative path or absolute path */
hidden_path = pstrndup(cmd->pool, path, maxlen);
hidden_path[basenamestart] = '\0';
hidden_path = pstrcat(cmd->pool, hidden_path, ".in.",
path + basenamestart, ".", NULL);
pr_log_pri(PR_LOG_DEBUG, "HiddenStore: complex path, will rename %s to %s",
hidden_path, path);
}
if (file_mode(hidden_path)) {
pr_log_debug(DEBUG3, "HiddenStore path '%s' already exists",
hidden_path);
pr_response_add_err(R_550, "%s: Temporary hidden file %s already exists",
cmd->arg, hidden_path);
return -1;
}
if (pr_table_set(cmd->notes, "mod_xfer.store-hidden-path",
hidden_path, 0) < 0)
pr_log_pri(PR_LOG_NOTICE,
"notice: error setting 'mod_xfer.store-hidden-path': %s",
strerror(errno));
session.xfer.xfer_type = STOR_HIDDEN;
return 0;
}
MODRET xfer_post_prot(cmd_rec *cmd) {
CHECK_CMD_ARGS(cmd, 2);
if (strcmp(cmd->argv[1], "C") != 0)
have_prot = TRUE;
else
have_prot = FALSE;
return DECLINED(cmd);
}
/* This is a PRE_CMD handler that checks security, etc, and places the full
* filename to receive in cmd->notes, under the key 'mod_xfer.store-path'.
* Note that we CANNOT use cmd->tmp_pool for this, as tmp_pool only lasts for
* the duration of this function.
*/
MODRET xfer_pre_stor(cmd_rec *cmd) {
char *dir;
mode_t fmode;
unsigned char *hidden_stores = NULL, *allow_overwrite = NULL,
*allow_restart = NULL;
if (cmd->argc < 2) {
pr_response_add_err(R_500, "'%s' not understood", get_full_cmd(cmd));
return ERROR(cmd);
}
dir = dir_best_path(cmd->tmp_pool, cmd->arg);
if (!dir || !dir_check(cmd->tmp_pool, cmd->argv[0], cmd->group, dir, NULL)) {
pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(errno));
return ERROR(cmd);
}
fmode = file_mode(dir);
allow_overwrite = get_param_ptr(CURRENT_CONF, "AllowOverwrite", FALSE);
if (fmode && (session.xfer.xfer_type != STOR_APPEND) &&
(!allow_overwrite || *allow_overwrite == FALSE)) {
pr_log_debug(DEBUG6, "AllowOverwrite denied permission for %s", cmd->arg);
pr_response_add_err(R_550, "%s: Overwrite permission denied", cmd->arg);
return ERROR(cmd);
}
if (fmode && !S_ISREG(fmode) && !S_ISFIFO(fmode)) {
pr_response_add_err(R_550, "%s: Not a regular file", cmd->arg);
return ERROR(cmd);
}
/* If restarting, check permissions on this directory, if
* AllowStoreRestart is set, permit it
*/
allow_restart = get_param_ptr(CURRENT_CONF, "AllowStoreRestart", FALSE);
if (fmode &&
(session.restart_pos || (session.xfer.xfer_type == STOR_APPEND)) &&
(!allow_restart || *allow_restart == FALSE)) {
pr_response_add_err(R_451, "%s: Append/Restart not permitted, try again",
cmd->arg);
session.restart_pos = 0L;
session.xfer.xfer_type = STOR_DEFAULT;
return ERROR(cmd);
}
/* Otherwise everthing is good */
if (pr_table_add(cmd->notes, "mod_xfer.store-path",
pstrdup(cmd->pool, dir), 0) < 0)
pr_log_pri(PR_LOG_NOTICE, "notice: error adding 'mod_xfer.store-path': %s",
strerror(errno));
if ((hidden_stores = get_param_ptr(CURRENT_CONF, "HiddenStores",
FALSE)) != NULL && *hidden_stores == TRUE) {
if (get_hidden_store_path(cmd, dir) < 0)
return ERROR(cmd);
}
return HANDLED(cmd);
}
/* xfer_pre_stou() is a PRE_CMD handler that changes the uploaded filename
* to a unique one, after making the requisite security and authorization
* checks.
*/
MODRET xfer_pre_stou(cmd_rec *cmd) {
config_rec *c = NULL;
char *prefix = "ftp", *filename = NULL;
int tmpfd;
mode_t mode;
unsigned char *allow_overwrite = NULL;
/* Some FTP clients are "broken" in that they will send a filename
* along with STOU. Technically this violates RFC959, but for now, just
* ignore that filename. Stupid client implementors.
*/
if (cmd->argc > 2) {
pr_response_add_err(R_500, "'%s' not understood.", get_full_cmd(cmd));
return ERROR(cmd);
}
/* Watch for STOU preceded by REST, which makes no sense.
*
* REST: session.restart_pos > 0
*/
if (session.restart_pos) {
pr_response_add_err(R_550, "STOU incompatible with REST");
return ERROR(cmd);
}
/* Generate the filename to be stored, depending on the configured
* unique filename prefix.
*/
if ((c = find_config(CURRENT_CONF, CONF_PARAM, "StoreUniquePrefix",
FALSE)) != NULL)
prefix = c->argv[0];
/* Now, construct the unique filename using the cmd_rec's pool, the
* prefix, and mkstemp().
*/
filename = pstrcat(cmd->pool, prefix, "XXXXXX", NULL);
tmpfd = mkstemp(filename);
if (tmpfd < 0) {
pr_log_pri(PR_LOG_ERR, "error: unable to use mkstemp(): %s",
strerror(errno));
/* If we can't guarantee a unique filename, refuse the command. */
pr_response_add_err(R_450, "%s: unable to generate unique filename",
cmd->argv[0]);
return ERROR(cmd);
} else {
cmd->arg = filename;
/* Close the unique file. This introduces a small race condition
* between the time this function returns, and the STOU CMD handler
* opens the unique file, but this may have to do, as closing that
* race would involve some major restructuring.
*/
close(tmpfd);
}
/* It's OK to reuse the char * pointer for filename. */
filename = dir_best_path(cmd->tmp_pool, cmd->arg);
if (!filename || !dir_check(cmd->tmp_pool, cmd->argv[0], cmd->group,
filename, NULL)) {
/* Do not forget to delete the file created by mkstemp(3) if there is
* an error.
*/
(void) pr_fsio_unlink(cmd->arg);
pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(errno));
return ERROR(cmd);
}
mode = file_mode(filename);
/* Note: this case should never happen: how one can be appending to
* a supposedly unique filename? Should probably be removed...
*/
allow_overwrite = get_param_ptr(CURRENT_CONF, "AllowOverwrite", FALSE);
if (mode && session.xfer.xfer_type != STOR_APPEND &&
(!allow_overwrite || *allow_overwrite == FALSE)) {
pr_log_debug(DEBUG6, "AllowOverwrite denied permission for %s", cmd->arg);
pr_response_add_err(R_550, "%s: Overwrite permission denied", cmd->arg);
return ERROR(cmd);
}
/* Not likely to _not_ be a regular file, but just to be certain... */
if (mode && !S_ISREG(mode)) {
(void) pr_fsio_unlink(cmd->arg);
pr_response_add_err(R_550, "%s: Not a regular file", cmd->arg);
return ERROR(cmd);
}
/* Otherwise everthing is good */
if (pr_table_add(cmd->notes, "mod_xfer.store-path",
pstrdup(cmd->pool, filename), 0) < 0)
pr_log_pri(PR_LOG_NOTICE, "notice: error adding 'mod_xfer.store-path': %s",
strerror(errno));
session.xfer.xfer_type = STOR_UNIQUE;
return HANDLED(cmd);
}
/* xfer_post_stou() is a POST_CMD handler that changes the mode of the
* STOU file from 0600, which is what mkstemp() makes it, to 0666,
* the default for files uploaded via STOR. This is to prevent users
* from being surprised.
*/
MODRET xfer_post_stou(cmd_rec *cmd) {
/* This is the same mode as used in src/fs.c. Should probably be
* available as a macro.
*/
mode_t mode = 0666;
if (pr_fsio_chmod(cmd->arg, mode) < 0) {
/* Not much to do but log the error. */
pr_log_pri(PR_LOG_ERR, "error: unable to chmod '%s': %s", cmd->arg,
strerror(errno));
}
return HANDLED(cmd);
}
/* xfer_pre_appe() is the PRE_CMD handler for the APPE command, which
* simply sets xfer_type to STOR_APPEND and calls xfer_pre_stor().
*/
MODRET xfer_pre_appe(cmd_rec *cmd) {
session.xfer.xfer_type = STOR_APPEND;
session.restart_pos = 0L;
return xfer_pre_stor(cmd);
}
MODRET xfer_stor(cmd_rec *cmd) {
char *dir;
char *lbuf;
int bufsz,len;
off_t nbytes_stored, nbytes_max_store = 0;
unsigned char have_limit = FALSE;
struct stat st;
off_t curr_pos = 0;
#if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP)
regex_t *preg;
int ret;
#endif /* REGEX */
/* This function sets static module variables for later throttling */
xfer_rate_lookup(cmd);
session.xfer.path = pr_table_get(cmd->notes, "mod_xfer.store-path", NULL);
session.xfer.path_hidden = pr_table_get(cmd->notes,
"mod_xfer.store-hidden-path", NULL);
dir = session.xfer.path;
#if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP)
preg = (regex_t *) get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE);
if (preg) {
ret = regexec(preg, cmd->arg, 0, NULL, 0);
if (ret != 0) {
pr_log_debug(DEBUG2, "'%s' denied by PathAllowFilter", cmd->arg);
pr_response_add_err(R_550, "%s: Forbidden filename", cmd->arg);
return ERROR(cmd);
} else {
char errmsg[200];
regerror(ret, preg, errmsg, sizeof(errmsg));
pr_log_debug(DEBUG8, "'%s' allowed by PathAllowFilter (%s)", cmd->arg,
errmsg);
}
}
preg = (regex_t *) get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE);
if (preg) {
ret = regexec(preg, cmd->arg, 0, NULL, 0);
if (ret == 0) {
pr_log_debug(DEBUG2, "'%s' denied by PathDenyFilter", cmd->arg);
pr_response_add_err(R_550, "%s: Forbidden filename", cmd->arg);
return ERROR(cmd);
} else {
char errmsg[200];
regerror(ret, preg, errmsg, sizeof(errmsg));
pr_log_debug(DEBUG8, "'%s' allowed by PathDenyFilter (%s)", cmd->arg,
errmsg);
}
}
#endif /* REGEX */
/* Make sure the proper current working directory is set in the FSIO
* layer, so that the proper FS can be used for the open().
*/
pr_fs_setcwd(pr_fs_getcwd());
if (session.xfer.xfer_type == STOR_HIDDEN)
stor_fh = pr_fsio_open(session.xfer.path_hidden,
O_WRONLY|(session.restart_pos ? 0 : O_CREAT|O_EXCL));
else if (session.xfer.xfer_type == STOR_APPEND) {
stor_fh = pr_fsio_open(session.xfer.path, O_CREAT|O_WRONLY);
if (stor_fh)
if (pr_fsio_lseek(stor_fh, 0, SEEK_END) == (off_t) -1) {
(void) pr_fsio_close(stor_fh);
stor_fh = NULL;
}
}
else /* Normal session */
stor_fh = pr_fsio_open(dir,
O_WRONLY|(session.restart_pos ? 0 : O_TRUNC|O_CREAT));
if (stor_fh && session.restart_pos) {
int xerrno = 0;
if (pr_fsio_lseek(stor_fh, session.restart_pos, SEEK_SET) == -1)
xerrno = errno;
else if (pr_fsio_stat(dir, &st) == -1)
xerrno = errno;
if (xerrno) {
(void) pr_fsio_close(stor_fh);
errno = xerrno;
stor_fh = NULL;
}
/* Make sure that the requested offset is valid (within the size of the
* file being resumed.
*/
if (stor_fh && session.restart_pos > st.st_size) {
pr_response_add_err(R_554, "%s: invalid REST argument", cmd->arg);
(void) pr_fsio_close(stor_fh);
stor_fh = NULL;
return ERROR(cmd);
}
curr_pos = session.restart_pos;
session.restart_pos = 0L;
}
if (!stor_fh) {
pr_log_debug(DEBUG4, "unable to open '%s' for writing: %s", cmd->arg,
strerror(errno));
pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(errno));
return ERROR(cmd);
}
/* Perform the actual transfer now */
pr_data_init(cmd->arg, PR_NETIO_IO_RD);
session.xfer.file_size = curr_pos;
/* First, make sure the uploaded file has the requested ownership. */
stor_chown();
if (pr_data_open(cmd->arg, NULL, PR_NETIO_IO_RD, 0) < 0) {
stor_abort();
pr_data_abort(0, TRUE);
return ERROR(cmd);
}
/* Initialize the number of bytes stored */
nbytes_stored = 0;
/* Retrieve the number of bytes to store, maximum, if present.
* This check is needed during the pr_data_xfer() loop, below, because
* the size of the file being uploaded isn't known in advance
*/
if ((nbytes_max_store = find_max_nbytes("MaxStoreFileSize")) == 0UL)
have_limit = FALSE;
else
have_limit = TRUE;
/* Check the MaxStoreFileSize, and abort now if zero. */
if (have_limit &&
nbytes_max_store == 0) {
pr_log_pri(PR_LOG_INFO, "MaxStoreFileSize (%" PR_LU " byte%s) reached: "
"aborting transfer of '%s'", (pr_off_t) nbytes_max_store,
nbytes_max_store != 1 ? "s" : "", dir);
/* Abort the transfer. */
stor_abort();
/* Set errno to EDQOUT (or the most appropriate alternative). */
#if defined(EDQUOT)
pr_data_abort(EDQUOT, FALSE);
#elif defined(EFBIG)
pr_data_abort(EFBIG, FALSE);
#else
pr_data_abort(EPERM, FALSE);
#endif
return ERROR(cmd);
}
bufsz = (main_server->tcp_rcvbuf_len > 0 ? main_server->tcp_rcvbuf_len :
PR_TUNABLE_XFER_BUFFER_SIZE);
lbuf = (char *) palloc(cmd->tmp_pool, bufsz);
while ((len = pr_data_xfer(lbuf, bufsz)) > 0) {
int res;
if (XFER_ABORTED)
break;
nbytes_stored += len;
/* Double-check the current number of bytes stored against the
* MaxStoreFileSize, if configured.
*/
if (have_limit &&
nbytes_stored > nbytes_max_store) {
pr_log_pri(PR_LOG_INFO, "MaxStoreFileSize (%" PR_LU " bytes) reached: "
"aborting transfer of '%s'", (pr_off_t) nbytes_max_store, dir);
/* Unlink the file being written. */
pr_fsio_unlink(dir);
/* Abort the transfer. */
stor_abort();
/* Set errno to EDQOUT (or the most appropriate alternative). */
#if defined(EDQUOT)
pr_data_abort(EDQUOT, FALSE);
#elif defined(EFBIG)
pr_data_abort(EFBIG, FALSE);
#else
pr_data_abort(EPERM, FALSE);
#endif
return ERROR(cmd);
}
res = pr_fsio_write(stor_fh, lbuf, len);
if (res != len) {
int xerrno = EIO;
if (res < 0)
xerrno = errno;
stor_abort();
pr_data_abort(xerrno, FALSE);
return ERROR(cmd);
}
/* If no throttling is configured, this does nothing. */
xfer_rate_throttle(nbytes_stored, FALSE);
}
if (XFER_ABORTED) {
stor_abort();
pr_data_abort(0, 0);
return ERROR(cmd);
} else if (len < 0) {
stor_abort();
pr_data_abort(PR_NETIO_ERRNO(session.d->instrm), FALSE);
return ERROR(cmd);
} else {
/* If no throttling is configured, this does nothing. */
xfer_rate_throttle(nbytes_stored, TRUE);
if (stor_complete() < 0) {
/* Check errno for EDQOUT (or the most appropriate alternative).
* (I hate the fact that FTP has a special response code just for
* this, and that clients actually expect it. Special cases are
* stupid.)
*/
#if defined(EDQUOT)
if (errno == EDQUOT) {
pr_response_add_err(R_552, "%s: %s", session.xfer.path,
strerror(errno));
return ERROR(cmd);
}
#elif defined(EFBIG)
if (errno == EFBIG) {
pr_response_add_err(R_552, "%s: %s", session.xfer.path,
strerror(errno));
return ERROR(cmd);
}
#endif
pr_response_add_err(R_550, "%s: %s", session.xfer.path,
strerror(errno));
return ERROR(cmd);
}
if (session.xfer.path &&
session.xfer.path_hidden) {
if (pr_fsio_rename(session.xfer.path_hidden, session.xfer.path) != 0) {
/* This should only fail on a race condition with a chmod/chown
* or if STOR_APPEND is on and the permissions are squirrely.
* The poor user will have to re-upload, but we've got more important
* problems to worry about and this failure should be fairly rare.
*/
pr_log_pri(PR_LOG_WARNING, "Rename of %s to %s failed: %s.",
session.xfer.path_hidden, session.xfer.path, strerror(errno));
pr_response_add_err(R_550, "%s: Rename of hidden file %s failed: %s",
session.xfer.path, session.xfer.path_hidden, strerror(errno));
pr_fsio_unlink(session.xfer.path_hidden);
return ERROR(cmd);
}
}
pr_data_close(FALSE);
}
return HANDLED(cmd);
}
MODRET xfer_rest(cmd_rec *cmd) {
off_t pos;
char *endp = NULL;
unsigned char *hidden_stores = NULL;
if (cmd->argc != 2) {
pr_response_add_err(R_500, "'%s' not understood", get_full_cmd(cmd));
return ERROR(cmd);
}
/* If we're using HiddenStores, then REST won't work. */
if ((hidden_stores = get_param_ptr(CURRENT_CONF, "HiddenStores",
FALSE)) != NULL && *hidden_stores == TRUE) {
pr_response_add_err(R_501, "REST not compatible with server configuration");
return ERROR(cmd);
}
#ifdef HAVE_STRTOULL
pos = strtoull(cmd->argv[1], &endp, 10);
#else
pos = strtoul(cmd->argv[1], &endp, 10);
#endif /* HAVE_STRTOULL */
if (endp &&
*endp) {
pr_response_add_err(R_501,
"REST requires a value greater than or equal to 0");
return ERROR(cmd);
}
session.restart_pos = pos;
/* Refuse the command if we're in ASCII mode, and the restart position
* is anything other than zero.
*
* Ideally, we would refuse the REST command when in ASCII mode regardless
* of position. However, some (IMHO, stupid) clients "test" the FTP
* server by sending "REST 0" to see if the server supports REST, without
* regard to the transfer type. This, then, is a hack to handle such
* clients.
*/
if ((session.sf_flags & SF_ASCII) &&
pos != 0) {
pr_log_debug(DEBUG5, "%s not allowed in ASCII mode", cmd->argv[0]);
pr_response_add_err(R_501,
"%s: Resuming transfers not allowed in ASCII mode", cmd->argv[0]);
return ERROR(cmd);
}
pr_response_add(R_350, "Restarting at %" PR_LU ". Send STORE or RETRIEVE to "
"initiate transfer", (pr_off_t) pos);
return HANDLED(cmd);
}
/* This is a PRE_CMD handler that checks security, etc, and places the full
* filename to send in cmd->notes (note that we CANNOT use cmd->tmp_pool
* for this, as tmp_pool only lasts for the duration of this function).
*/
MODRET xfer_pre_retr(cmd_rec *cmd) {
char *dir = NULL;
mode_t fmode;
unsigned char *allow_restart = NULL;
if (cmd->argc < 2) {
pr_response_add_err(R_500, "'%s' not understood", get_full_cmd(cmd));
return ERROR(cmd);
}
dir = dir_realpath(cmd->tmp_pool, cmd->arg);
if (!dir ||
!dir_check(cmd->tmp_pool, cmd->argv[0], cmd->group, dir, NULL)) {
pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(errno));
return ERROR(cmd);
}
fmode = file_mode(dir);
if (!S_ISREG(fmode)) {
if (!fmode)
pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(errno));
else
pr_response_add_err(R_550, "%s: Not a regular file", cmd->arg);
return ERROR(cmd);
}
/* If restart is on, check to see if AllowRestartRetrieve is off, in
* which case we disallow the transfer and clear restart_pos.
*/
allow_restart = get_param_ptr(CURRENT_CONF, "AllowRetrieveRestart", FALSE);
if (session.restart_pos &&
(allow_restart && *allow_restart == FALSE)) {
pr_response_add_err(R_451, "%s: Restart not permitted, try again",
cmd->arg);
session.restart_pos = 0L;
return ERROR(cmd);
}
/* Otherwise everthing is good */
if (pr_table_add(cmd->notes, "mod_xfer.retr-path",
pstrdup(cmd->pool, dir), 0) < 0)
pr_log_pri(PR_LOG_NOTICE, "notice: error adding 'mod_xfer.retr-path': %s",
strerror(errno));
return HANDLED(cmd);
}
MODRET xfer_retr(cmd_rec *cmd) {
char *dir = NULL, *lbuf;
struct stat st;
off_t nbytes_max_retrieve = 0;
unsigned char have_limit = FALSE;
long bufsz, len = 0;
off_t curr_pos = 0, nbytes_sent = 0, cnt_steps = 0, cnt_next = 0;
/* This function sets static module variables for later potential
* throttling of the transfer.
*/
xfer_rate_lookup(cmd);
dir = pr_table_get(cmd->notes, "mod_xfer.retr-path", NULL);
retr_fh = pr_fsio_open(dir, O_RDONLY);
if (retr_fh == NULL) {
/* Error opening the file. */
pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(errno));
return ERROR(cmd);
}
if (pr_fsio_stat(dir, &st) < 0) {
/* Error stat'ing the file. */
int xerrno = errno;
pr_fsio_close(retr_fh);
errno = xerrno;
retr_fh = NULL;
pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(errno));
return ERROR(cmd);
}
if (session.restart_pos) {
/* Make sure that the requested offset is valid (within the size of the
* file being resumed.
*/
if (session.restart_pos > st.st_size) {
pr_response_add_err(R_554, "%s: invalid REST argument", cmd->arg);
pr_fsio_close(retr_fh);
retr_fh = NULL;
return ERROR(cmd);
}
if (pr_fsio_lseek(retr_fh, session.restart_pos,
SEEK_SET) == (off_t) -1) {
int xerrno = errno;
pr_fsio_close(retr_fh);
errno = xerrno;
retr_fh = NULL;
}
curr_pos = session.restart_pos;
session.restart_pos = 0L;
}
/* Send the data */
pr_data_init(cmd->arg, PR_NETIO_IO_WR);
session.xfer.path = dir;
session.xfer.file_size = st.st_size;
cnt_steps = session.xfer.file_size / 100;
if (cnt_steps == 0)
cnt_steps = 1;
if (pr_data_open(cmd->arg, NULL, PR_NETIO_IO_WR, st.st_size - curr_pos) < 0) {
retr_abort();
pr_data_abort(0, TRUE);
return ERROR(cmd);
}
/* Retrieve the number of bytes to retrieve, maximum, if present */
if ((nbytes_max_retrieve = find_max_nbytes("MaxRetrieveFileSize")) == 0UL)
have_limit = FALSE;
else
have_limit = TRUE;
/* Check the MaxRetrieveFileSize. If it is zero, or if the size
* of the file being retrieved is greater than the MaxRetrieveFileSize,
* then signal an error and abort the transfer now.
*/
if (have_limit &&
((nbytes_max_retrieve == 0) || (st.st_size > nbytes_max_retrieve))) {
pr_log_pri(PR_LOG_INFO, "MaxRetrieveFileSize (%" PR_LU " byte%s) reached: "
"aborting transfer of '%s'", (pr_off_t) nbytes_max_retrieve,
nbytes_max_retrieve != 1 ? "s" : "", dir);
/* Abort the transfer. */
retr_abort();
/* Set errno to EPERM ("Operation not permitted") */
pr_data_abort(EPERM, FALSE);
return ERROR(cmd);
}
bufsz = (main_server->tcp_sndbuf_len > 0 ? main_server->tcp_sndbuf_len :
PR_TUNABLE_XFER_BUFFER_SIZE);
lbuf = (char *) palloc(cmd->tmp_pool, bufsz);
nbytes_sent = curr_pos;
pr_scoreboard_update_entry(getpid(),
PR_SCORE_XFER_SIZE, session.xfer.file_size,
PR_SCORE_XFER_DONE, 0,
NULL);
while (nbytes_sent != session.xfer.file_size) {
if (XFER_ABORTED)
break;
len = transmit_data(nbytes_sent, &curr_pos, lbuf, bufsz);
if (len == 0)
break;
if (len < 0) {
retr_abort();
pr_data_abort(PR_NETIO_ERRNO(session.d->outstrm), FALSE);
return ERROR(cmd);
}
nbytes_sent += len;
if ((nbytes_sent / cnt_steps) != cnt_next) {
cnt_next = nbytes_sent / cnt_steps;
pr_scoreboard_update_entry(getpid(),
PR_SCORE_XFER_DONE, nbytes_sent,
NULL);
}
/* If no throttling is configured, this simply updates the scoreboard.
* In this case, we want to use session.xfer.total_bytes, rather than
* nbytes_sent, as the latter incorporates a REST position and the
* former does not. (When handling STOR, this is not an issue: different
* end-of-loop conditions).
*/
xfer_rate_throttle(session.xfer.total_bytes, FALSE);
}
if (XFER_ABORTED) {
retr_abort();
pr_data_abort(0, 0);
return ERROR(cmd);
} else if (len < 0) {
retr_abort();
pr_data_abort(errno, FALSE);
return ERROR(cmd);
} else {
/* If no throttling is configured, this simply updates the scoreboard.
* In this case, we want to use session.xfer.total_bytes, rather than
* nbytes_sent, as the latter incorporates a REST position and the
* former does not. (When handling STOR, this is not an issue: different
* end-of-loop conditions).
*/
xfer_rate_throttle(session.xfer.total_bytes, TRUE);
retr_complete();
pr_data_close(FALSE);
}
return HANDLED(cmd);
}
MODRET xfer_abor(cmd_rec *cmd) {
if (cmd->argc != 1) {
pr_response_add_err(R_500, "'%s' not understood", get_full_cmd(cmd));
return ERROR(cmd);
}
pr_data_abort(0, FALSE);
pr_data_reset();
pr_data_cleanup();
pr_response_add(R_226, "Abort successful");
return HANDLED(cmd);
}
MODRET xfer_type(cmd_rec *cmd) {
if (cmd->argc < 2 || cmd->argc > 3) {
pr_response_add_err(R_500, "'%s' not understood", get_full_cmd(cmd));
return ERROR(cmd);
}
cmd->argv[1][0] = toupper(cmd->argv[1][0]);
if (!strcmp(cmd->argv[1], "A") ||
(cmd->argc == 3 && !strcmp(cmd->argv[1], "L") &&
!strcmp(cmd->argv[2], "7"))) {
/* TYPE A(SCII) or TYPE L 7. */
session.sf_flags |= SF_ASCII;
} else if (!strcmp(cmd->argv[1], "I") ||
(cmd->argc == 3 && !strcmp(cmd->argv[1], "L") &&
!strcmp(cmd->argv[2], "8"))) {
/* TYPE I(MAGE) or TYPE L 8. */
session.sf_flags &= (SF_ALL^SF_ASCII);
} else {
pr_response_add_err(R_500, "'%s' not understood", get_full_cmd(cmd));
return ERROR(cmd);
}
pr_response_add(R_200, "Type set to %s", cmd->argv[1]);
return HANDLED(cmd);
}
MODRET xfer_stru(cmd_rec *cmd) {
if (cmd->argc != 2) {
pr_response_add_err(R_501, "'%s' not understood", get_full_cmd(cmd));
return ERROR(cmd);
}
cmd->argv[1][0] = toupper(cmd->argv[1][0]);
switch ((int) cmd->argv[1][0]) {
case 'F':
/* Should 202 be returned instead??? */
pr_response_add(R_200, "Structure set to F.");
return HANDLED(cmd);
break;
case 'R':
/* Accept R but with no operational difference from F???
* R is required in minimum implementations by RFC-959, 5.1.
* RFC-1123, 4.1.2.13, amends this to only apply to servers whose file
* systems support record structures, but also suggests that such a
* server "may still accept files with STRU R, recording the byte stream
* literally." Another configurable choice, perhaps?
*
* NOTE: wu-ftpd does not so accept STRU R.
*/
/* FALLTHROUGH */
case 'P':
/* RFC-1123 recommends against implementing P. */
pr_response_add_err(R_504, "'%s' unsupported structure type.",
get_full_cmd(cmd));
return ERROR(cmd);
break;
default:
pr_response_add_err(R_501, "'%s' unrecognized structure type.",
get_full_cmd(cmd));
return ERROR(cmd);
break;
}
}
MODRET xfer_mode(cmd_rec *cmd) {
if (cmd->argc != 2) {
pr_response_add_err(R_501, "'%s' not understood", get_full_cmd(cmd));
return ERROR(cmd);
}
cmd->argv[1][0] = toupper(cmd->argv[1][0]);
switch ((int) cmd->argv[1][0]) {
case 'S':
/* Should 202 be returned instead??? */
pr_response_add(R_200, "Mode set to S.");
return HANDLED(cmd);
break;
case 'B':
/* FALLTHROUGH */
case 'C':
pr_response_add_err(R_504, "'%s' unsupported transfer mode.",
get_full_cmd(cmd));
return ERROR(cmd);
break;
default:
pr_response_add_err(R_501, "'%s' unrecognized transfer mode.",
get_full_cmd(cmd));
return ERROR(cmd);
break;
}
}
MODRET xfer_allo(cmd_rec *cmd) {
pr_response_add(R_202, "No storage allocation necessary.");
return HANDLED(cmd);
}
MODRET xfer_smnt(cmd_rec *cmd) {
pr_response_add(R_502, "SMNT command not implemented.");
return HANDLED(cmd);
}
MODRET xfer_err_cleanup(cmd_rec *cmd) {
if (session.xfer.p)
destroy_pool(session.xfer.p);
memset(&session.xfer, '\0', sizeof(session.xfer));
/* Don't forget to clear any possible REST parameter as well. */
session.restart_pos = 0;
return DECLINED(cmd);
}
MODRET xfer_log_stor(cmd_rec *cmd) {
_log_transfer('i', 'c');
/* Increment the byte counter. */
session.total_bytes_in += session.xfer.total_bytes;
/* Increment the file counters. */
session.total_files_in++;
session.total_files_xfer++;
pr_data_cleanup();
/* Don't forget to clear any possible REST parameter as well. */
session.restart_pos = 0;
return DECLINED(cmd);
}
MODRET xfer_log_retr(cmd_rec *cmd) {
_log_transfer('o', 'c');
/* Increment the byte counter. */
session.total_bytes_out += session.xfer.total_bytes;
/* Increment the file counters. */
session.total_files_out++;
session.total_files_xfer++;
pr_data_cleanup();
/* Don't forget to clear any possible REST parameter as well. */
session.restart_pos = 0;
return DECLINED(cmd);
}
static int noxfer_timeout_cb(CALLBACK_FRAME) {
if (session.sf_flags & SF_XFER)
/* Transfer in progress, ignore this timeout */
return 1;
pr_event_generate("core.timeout-no-transfer", NULL);
pr_response_send_async(R_421, "No Transfer Timeout (%d seconds): closing "
"control connection.", TimeoutNoXfer);
pr_timer_remove(TIMER_IDLE, ANY_MODULE);
pr_timer_remove(TIMER_LOGIN, ANY_MODULE);
/* If this timeout is encountered and we are expecting a passive transfer,
* add some logging that suggests things to check and possibly fix
* (e.g. network/firewall rules).
*/
if (session.sf_flags & SF_PASSIVE) {
pr_log_pri(PR_LOG_INFO,
"Passive data transfer failed, possibly due to network issues");
pr_log_pri(PR_LOG_INFO,
"Check your PassivePorts and MasqueradeAddress settings,");
pr_log_pri(PR_LOG_INFO,
"and any router, NAT, and firewall rules in the network path.");
}
session_exit(PR_LOG_NOTICE, "FTP no transfer timeout, disconnected", 0, NULL);
return 0;
}
/* Configuration handlers
*/
MODRET set_allowoverwrite(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|
CONF_DIR|CONF_DYNDIR);
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]) = (unsigned char) bool;
c->flags |= CF_MERGEDOWN;
return HANDLED(cmd);
}
MODRET set_allowrestart(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|
CONF_DIR|CONF_DYNDIR);
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_deleteabortedstores(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|
CONF_DIR|CONF_DYNDIR);
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_hiddenstores(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|CONF_DIR);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
c = add_config_param("HiddenStores", 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_maxfilesize(cmd_rec *cmd) {
config_rec *c = NULL;
unsigned long nbytes;
unsigned int precedence = 0;
int ctxt = (cmd->config && cmd->config->config_type != CONF_PARAM ?
cmd->config->config_type : cmd->server->config_type ?
cmd->server->config_type : CONF_ROOT);
if (cmd->argc-1 == 1) {
if (strcmp(cmd->argv[1], "*") != 0)
CONF_ERROR(cmd, "incorrect number of parameters");
} else if (cmd->argc-1 != 2 && cmd->argc-1 != 4)
CONF_ERROR(cmd, "incorrect number of parameters");
CHECK_CONF(cmd, CONF_ROOT|CONF_ANON|CONF_VIRTUAL|CONF_GLOBAL|CONF_DIR|
CONF_DYNDIR);
/* Set the precedence for this config_rec based on its configuration
* context.
*/
if (ctxt & CONF_GLOBAL)
precedence = 1;
/* These will never appear simultaneously */
else if (ctxt & CONF_ROOT || ctxt & CONF_VIRTUAL)
precedence = 2;
else if (ctxt & CONF_ANON)
precedence = 3;
else if (ctxt & CONF_DIR)
precedence = 4;
else if (ctxt & CONF_DYNDIR)
precedence = 5;
/* If the directive was used with four arguments, it means the optional
* classifiers and expression were used. Make sure the classifier is a valid
* one.
*/
if (cmd->argc-1 == 4) {
if (!strcmp(cmd->argv[3], "user") ||
!strcmp(cmd->argv[3], "group") ||
!strcmp(cmd->argv[3], "class")) {
/* no-op */
} else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown classifier used: '",
cmd->argv[3], "'", NULL));
}
if (cmd->argc-1 == 1) {
/* Do nothing here -- the "*" (the only parameter allowed if there is
* only a single parameter given) signifies an unlimited size, which is
* what the server provides by default.
*/
nbytes = 0UL;
} else {
/* Pass the cmd_rec off to see what number of bytes was
* requested/configured.
*/
if ((nbytes = parse_max_nbytes(cmd->argv[1], cmd->argv[2])) == 0) {
char ulong_max[80] = {'\0'};
sprintf(ulong_max, "%lu", (unsigned long) ULONG_MAX);
if (xfer_errno == EINVAL)
CONF_ERROR(cmd, "invalid parameters");
if (xfer_errno == ERANGE)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"number of bytes must be between 0 and ", ulong_max, NULL));
}
}
if (cmd->argc-1 == 1 || cmd->argc-1 == 2) {
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
*((unsigned long *) c->argv[0]) = nbytes;
c->argv[1] = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[1]) = precedence;
} else {
array_header *acl = NULL;
int argc = cmd->argc - 4;
char **argv = cmd->argv + 3;
acl = pr_expr_create(cmd->tmp_pool, &argc, argv);
c = add_config_param(cmd->argv[0], 0);
c->argc = argc + 3;
c->argv = pcalloc(c->pool, ((argc + 4) * sizeof(char *)));
argv = (char **) c->argv;
/* Copy in the configured bytes */
*argv = pcalloc(c->pool, sizeof(unsigned long));
*((unsigned long *) *argv++) = nbytes;
/* Copy in the precedence */
*argv = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) *argv++) = precedence;
/* Copy in the classifier. */
*argv++ = pstrdup(c->pool, cmd->argv[3]);
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);
}
MODRET set_storeuniqueprefix(cmd_rec *cmd) {
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|
CONF_DIR|CONF_DYNDIR);
/* make sure there are no slashes in the prefix */
if (strchr(cmd->argv[1], '/') != NULL)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "no slashes allowed in prefix: '",
cmd->argv[1], "'", NULL));
c = add_config_param_str(cmd->argv[0], 1, (void *) cmd->argv[1]);
c->flags |= CF_MERGEDOWN;
return HANDLED(cmd);
}
MODRET set_timeoutnoxfer(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_timeoutstalled(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);
}
/* usage: TransferRate cmds kbps[:free-bytes] ["user"|"group"|"class"
* expression]
*/
MODRET set_transferrate(cmd_rec *cmd) {
config_rec *c = NULL;
char *tmp = NULL, *endp = NULL;
long double rate = 0.0;
off_t freebytes = 0;
unsigned int precedence = 0;
int ctxt = (cmd->config && cmd->config->config_type != CONF_PARAM ?
cmd->config->config_type : cmd->server->config_type ?
cmd->server->config_type : CONF_ROOT);
/* Must have two or four parameters */
if (cmd->argc-1 != 2 && cmd->argc-1 != 4)
CONF_ERROR(cmd, "wrong number of parameters");
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|
CONF_DIR|CONF_DYNDIR);
/* Set the precedence for this config_rec based on its configuration
* context.
*/
if (ctxt & CONF_GLOBAL)
precedence = 1;
/* These will never appear simultaneously */
else if (ctxt & CONF_ROOT || ctxt & CONF_VIRTUAL)
precedence = 2;
else if (ctxt & CONF_ANON)
precedence = 3;
else if (ctxt & CONF_DIR)
precedence = 4;
/* Note: by tweaking this value to be lower than the precedence for
* <Directory> appearances of this directive, I can effectively cause
* any .ftpaccess appearances not to override...
*/
else if (ctxt & CONF_DYNDIR)
precedence = 5;
/* Check for a valid classifier. */
if (cmd->argc-1 > 2) {
if (!strcmp(cmd->argv[3], "user") ||
!strcmp(cmd->argv[3], "group") ||
!strcmp(cmd->argv[3], "class"))
/* do nothing */
;
else
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown classifier requested: '",
cmd->argv[3], "'", NULL));
}
if ((tmp = strchr(cmd->argv[2], ':')) != NULL)
*tmp = '\0';
/* Parse the 'kbps' part. Ideally, we'd be using strtold(3) rather than
* strtod(3) here, but FreeBSD doesn't have strtold(3). Yay. Portability.
*/
rate = (long double) strtod(cmd->argv[2], &endp);
if (rate < 0.0)
CONF_ERROR(cmd, "rate must be greater than zero");
if (endp && *endp)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "invalid number: '",
cmd->argv[2], "'", NULL));
/* Parse any 'free-bytes' part */
if (tmp) {
cmd->argv[2] = ++tmp;
if ((freebytes = strtoul(cmd->argv[2], &endp, 10)) < 0)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"negative values not allowed: '", cmd->argv[2], "'", NULL));
if (endp && *endp)
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "invalid number: '",
cmd->argv[2], "'", NULL));
}
/* Construct the config_rec */
if (cmd->argc-1 == 2) {
c = add_config_param(cmd->argv[0], 4, NULL, NULL, NULL, NULL);
/* Parse the command list. */
if (!xfer_rate_parse_cmdlist(c, cmd->argv[1]))
CONF_ERROR(cmd, "error with command list");
c->argv[1] = pcalloc(c->pool, sizeof(long double));
*((long double *) c->argv[1]) = rate;
c->argv[2] = pcalloc(c->pool, sizeof(off_t));
*((off_t *) c->argv[2]) = freebytes;
c->argv[3] = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[3]) = precedence;
} else {
array_header *acl = NULL;
int argc = cmd->argc - 4;
char **argv = cmd->argv + 3;
acl = pr_expr_create(cmd->tmp_pool, &argc, argv);
c = add_config_param(cmd->argv[0], 0);
/* Parse the command list. */
/* The five additional slots are for: cmd-list, bps, free-bytes,
* precedence, user/group/class.
*/
c->argc = argc + 5;
c->argv = pcalloc(c->pool, ((c->argc + 1) * sizeof(char *)));
argv = (char **) c->argv;
if (!xfer_rate_parse_cmdlist(c, cmd->argv[1]))
CONF_ERROR(cmd, "error with command list");
/* Note: the command list is at index 0, hence this increment. */
argv++;
*argv = pcalloc(c->pool, sizeof(long double));
*((long double *) *argv++) = rate;
*argv = pcalloc(c->pool, sizeof(off_t));
*((unsigned long *) *argv++) = freebytes;
*argv = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) *argv++) = precedence;
*argv++ = pstrdup(c->pool, cmd->argv[3]);
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);
}
/* usage: UseSendfile on|off */
MODRET set_usesendfile(cmd_rec *cmd) {
int bool = -1;
config_rec *c;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
bool = get_boolean(cmd, 1);
if (bool == -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);
}
/* Event handlers
*/
static void xfer_sigusr2_ev(const void *event_data, void *user_data) {
/* Only do this if we're currently involved in a data transfer.
* This is a hack put in to support mod_shaper's antics.
*/
if (strcmp(session.curr_cmd, C_APPE) == 0 ||
strcmp(session.curr_cmd, C_RETR) == 0 ||
strcmp(session.curr_cmd, C_STOR) == 0 ||
strcmp(session.curr_cmd, C_STOU) == 0) {
pool *p = make_sub_pool(session.pool);
cmd_rec *cmd = pr_cmd_alloc(p, 1, session.curr_cmd);
/* Rescan the config tree for TransferRates, picking up any possible
* changes.
*/
pr_log_debug(DEBUG2, "rechecking TransferRates");
xfer_rate_lookup(cmd);
destroy_pool(p);
}
return;
}
/* Events handlers
*/
static void xfer_exit_ev(const void *event_data, void *user_data) {
if (session.sf_flags & SF_XFER) {
if (session.xfer.direction == PR_NETIO_IO_RD)
/* An upload is occurring... */
stor_abort();
else
/* A download is occurring... */
retr_abort();
}
return;
}
/* Initialization routines
*/
static int xfer_init(void) {
/* Add the commands handled by this module to the HELP list. */
pr_help_add(C_TYPE, "<sp> type-code (A, I, L 7, L 8)", TRUE);
pr_help_add(C_STRU, "is not implemented (always F)", TRUE);
pr_help_add(C_MODE, "is not implemented (always S)", TRUE);
pr_help_add(C_RETR, "<sp> pathname", TRUE);
pr_help_add(C_STOR, "<sp> pathname", TRUE);
pr_help_add(C_STOU, "(store unique filename)", TRUE);
pr_help_add(C_APPE, "<sp> pathname", TRUE);
pr_help_add(C_REST, "<sp> byte-count", TRUE);
pr_help_add(C_ABOR, "(abort current operation)", TRUE);
return 0;
}
static int xfer_sess_init(void) {
config_rec *c = NULL;
/* Check for a server-specific TimeoutNoTransfer */
if ((c = find_config(main_server->conf, CONF_PARAM, "TimeoutNoTransfer",
FALSE)) != NULL)
TimeoutNoXfer = *((int *) c->argv[0]);
/* Setup TimeoutNoXfer timer */
if (TimeoutNoXfer)
pr_timer_add(TimeoutNoXfer, TIMER_NOXFER, &xfer_module, noxfer_timeout_cb);
/* Check for a server-specific TimeoutStalled */
if ((c = find_config(main_server->conf, CONF_PARAM, "TimeoutStalled",
FALSE)) != NULL)
TimeoutStalled = *((int *) c->argv[0]);
/* Note: timers for handling TimeoutStalled timeouts are handled in the
* data transfer routines, not here.
*/
/* Check for UseSendfile. */
c = find_config(main_server->conf, CONF_PARAM, "UseSendfile", FALSE);
if (c)
use_sendfile = *((unsigned char *) c->argv[0]);
/* Exit handler for HiddenStores cleanup */
pr_event_register(&xfer_module, "core.exit", xfer_exit_ev, NULL);
pr_event_register(&xfer_module, "core.signal.USR2", xfer_sigusr2_ev,
NULL);
return 0;
}
/* Module API tables
*/
static conftable xfer_conftab[] = {
{ "AllowOverwrite", set_allowoverwrite, NULL },
{ "AllowRetrieveRestart", set_allowrestart, NULL },
{ "AllowStoreRestart", set_allowrestart, NULL },
{ "DeleteAbortedStores", set_deleteabortedstores, NULL },
{ "HiddenStor", set_hiddenstores, NULL },
{ "HiddenStores", set_hiddenstores, NULL },
{ "MaxRetrieveFileSize", set_maxfilesize, NULL },
{ "MaxStoreFileSize", set_maxfilesize, NULL },
{ "StoreUniquePrefix", set_storeuniqueprefix, NULL },
{ "TimeoutNoTransfer", set_timeoutnoxfer, NULL },
{ "TimeoutStalled", set_timeoutstalled, NULL },
{ "TransferRate", set_transferrate, NULL },
{ "UseSendfile", set_usesendfile, NULL },
{ NULL }
};
static cmdtable xfer_cmdtab[] = {
{ CMD, C_TYPE, G_NONE, xfer_type, TRUE, FALSE, CL_MISC },
{ CMD, C_STRU, G_NONE, xfer_stru, TRUE, FALSE, CL_MISC },
{ CMD, C_MODE, G_NONE, xfer_mode, TRUE, FALSE, CL_MISC },
{ CMD, C_ALLO, G_NONE, xfer_allo, TRUE, FALSE, CL_MISC },
{ CMD, C_SMNT, G_NONE, xfer_smnt, TRUE, FALSE, CL_MISC },
{ PRE_CMD, C_RETR, G_READ, xfer_pre_retr, TRUE, FALSE },
{ CMD, C_RETR, G_READ, xfer_retr, TRUE, FALSE, CL_READ },
{ LOG_CMD, C_RETR, G_NONE, xfer_log_retr, FALSE, FALSE },
{ LOG_CMD_ERR, C_RETR,G_NONE, xfer_err_cleanup, FALSE, FALSE },
{ PRE_CMD, C_STOR, G_WRITE, xfer_pre_stor, TRUE, FALSE },
{ CMD, C_STOR, G_WRITE, xfer_stor, TRUE, FALSE, CL_WRITE },
{ LOG_CMD, C_STOR, G_NONE, xfer_log_stor, FALSE, FALSE },
{ LOG_CMD_ERR, C_STOR,G_NONE, xfer_err_cleanup, FALSE, FALSE },
{ PRE_CMD, C_STOU, G_WRITE, xfer_pre_stou, TRUE, FALSE },
{ CMD, C_STOU, G_WRITE, xfer_stor, TRUE, FALSE, CL_WRITE },
{ POST_CMD,C_STOU, G_WRITE, xfer_post_stou,FALSE, FALSE },
{ LOG_CMD, C_STOU, G_NONE, xfer_log_stor, FALSE, FALSE },
{ LOG_CMD_ERR, C_STOU,G_NONE, xfer_err_cleanup, FALSE, FALSE },
{ PRE_CMD, C_APPE, G_WRITE, xfer_pre_appe, TRUE, FALSE },
{ CMD, C_APPE, G_WRITE, xfer_stor, TRUE, FALSE, CL_WRITE },
{ LOG_CMD, C_APPE, G_NONE, xfer_log_stor, FALSE, FALSE },
{ LOG_CMD_ERR, C_APPE,G_NONE, xfer_err_cleanup, FALSE, FALSE },
{ CMD, C_ABOR, G_NONE, xfer_abor, TRUE, TRUE, CL_MISC },
{ CMD, C_REST, G_NONE, xfer_rest, TRUE, FALSE, CL_MISC },
{ POST_CMD,C_PROT, G_NONE, xfer_post_prot,FALSE, FALSE },
{ 0,NULL }
};
module xfer_module = {
NULL, NULL,
/* Module API version */
0x20,
/* Module name */
"xfer",
/* Module configuration directive table */
xfer_conftab,
/* Module command handler table */
xfer_cmdtab,
/* Module authentication handler table */
NULL,
/* Module initialization function */
xfer_init,
/* Session initialization function */
xfer_sess_init
};
Last Updated: Thu Feb 23 11:07:09 2006
HTML generated by tj's src2html script