You are viewing a plain text version of this content. The canonical link for it is here.
Posted to mod_ftp-commits@incubator.apache.org by ji...@apache.org on 2005/10/06 13:17:04 UTC
svn commit: r306631 [4/8] - in /incubator/mod_ftp/trunk: ./ conf/ docs/
include/ modules/ patches/ src/ tests/ tests/conf/ tests/logs/ tests/tests/
utils/ utils/ftp_proxy/ utils/static_build/ utils/stresstest/
Added: incubator/mod_ftp/trunk/src/ftp_commands.c
URL: http://svn.apache.org/viewcvs/incubator/mod_ftp/trunk/src/ftp_commands.c?rev=306631&view=auto
==============================================================================
--- incubator/mod_ftp/trunk/src/ftp_commands.c (added)
+++ incubator/mod_ftp/trunk/src/ftp_commands.c Thu Oct 6 06:16:28 2005
@@ -0,0 +1,2640 @@
+/* Copyright 1999-2005 The Apache Software Foundation or its licensors, as
+ * applicable.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Original Copyright (c) Covalent Technologies 2001-2005
+ *
+ * FTP Protocol module for Apache 2.0
+ */
+
+#include "mod_ftp.h"
+#include "ftp_config.h"
+
+/* seem to need this for LONG_MAX */
+#include <limits.h>
+#if APR_HAVE_UNISTD_H
+/* Required for geteuid and seteuid */
+#include <unistd.h>
+#endif
+
+/* Wish APR had one of these */
+#ifdef WIN32
+#define FTP_APR_EADDRINUSE (APR_OS_START_SYSERR + WSAEADDRINUSE)
+#else
+#define FTP_APR_EADDRINUSE EADDRINUSE
+#endif /* WIN32 */
+
+extern ap_filter_rec_t *ftp_content_length_filter_handle;
+
+static apr_hash_t *FTPMethodHash;
+static apr_pool_t *FTPMethodPool;
+
+FTP_DECLARE(void) ftp_hook_cmd_any(const char *key, ftp_hook_fn *pf,
+ const char *alias, int order,
+ int flags, const char *help)
+{
+ ftp_cmd_entry *cmd, *curr;
+
+ cmd = apr_pcalloc(FTPMethodPool, sizeof(ftp_cmd_entry));
+
+ /* Duplicate for storage into the hash */
+ key = apr_pstrdup(FTPMethodPool, key);
+ help = apr_pstrdup(FTPMethodPool, help);
+
+ cmd->key = key;
+ cmd->pf = pf;
+ cmd->alias = alias;
+ cmd->flags = flags;
+ cmd->order = order;
+ cmd->help = help;
+
+ if (!FTPMethodHash) {
+ /* Should never get here. If a user tries to load an extension
+ * module before the core FTP module is loaded, they should get
+ * an undefined symbol.
+ */
+ fprintf(stderr, "Could not process registration for %s.", key);
+ return;
+ }
+
+ curr = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
+
+ if (curr) {
+ if (curr->order > cmd->order) {
+ cmd->next = curr;
+ apr_hash_set(FTPMethodHash, key, APR_HASH_KEY_STRING, cmd);
+ }
+ else {
+ while (curr->next && (curr->order < cmd->order)) {
+ curr = curr->next;
+ }
+ cmd->next = curr->next;
+ curr->next = cmd;
+ }
+ }
+ else {
+ apr_hash_set(FTPMethodHash, key, APR_HASH_KEY_STRING, cmd);
+ }
+
+ /* Only limit commands that are implemented (not aliases) */
+ if (pf) {
+ ap_method_register(FTPMethodPool, key);
+ }
+}
+
+static int ftp_run_handler(request_rec *r, struct ftp_cmd_entry *cmd,
+ const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ request_rec *rr;
+ int res;
+
+ /* Set the authorization header and the user name for all
+ * requests. This information may not be needed for all
+ * requests, but is required for logging purposes.
+ */
+ ftp_set_authorization(r);
+
+ /* Run the header parser hook for mod_setenv. For conditional
+ * logging, etc */
+ ap_run_header_parser(r);
+
+ /* Run a subrequest before the command is run. This allows us to
+ * check if a command is allowed for the user's current location */
+ if ((res = ftp_set_uri(r, fc->cwd))) {
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ /* XXX - JKS
+ *
+ * This will check authz for the *CURRENT* location. For commands
+ * where the URI can change, the command handler will need to rerun
+ * the auth/authz checks to ensure the user also has permission for
+ * the new URI.
+ */
+ if ((rr->status == HTTP_UNAUTHORIZED &&
+ (cmd->flags & FTP_NEED_LOGIN)) ||
+ ((rr->status == HTTP_FORBIDDEN) &&
+ (cmd->flags & FTP_NEED_LOGIN))) {
+ fc->response_notes = apr_psprintf(r->pool,
+ FTP_MSG_NOTALLOWED,
+ r->method);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ ap_destroy_sub_req(rr);
+
+ /* Ok, this method is allowed, run through the list */
+ res = cmd->pf(r, arg);
+ if (res != DECLINED) {
+ return res;
+ }
+ else if (res == DECLINED && cmd->next) {
+ return ftp_run_handler(r, cmd->next, arg);
+ }
+ else {
+ return FTP_REPLY_COMMAND_UNRECOGNIZED;
+ }
+}
+
+const char * ftp_get_cmd_alias(const char *key)
+{
+ ftp_cmd_entry *cmd;
+
+ if (!FTPMethodHash) {
+ return key;
+ }
+
+ cmd = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
+
+ if (cmd && cmd->alias) {
+ return (const char *) cmd->alias;
+ }
+
+ return key;
+}
+
+/* ftp_parse2: Parse a FTP request that is expected to have 2 arguments.
+ *
+ * Arguments: pool - Pool to allocate from
+ * cmd - The complete request (e.g. GET /foo.html)
+ * a1 - The first argument
+ * a2 - The second argument
+ *
+ * Returns: 1 on error or 0 on success, a1 and a2 are modified to point
+ * to the correct values.
+ */
+int ftp_parse2(apr_pool_t *pool, const char *cmd, char **a1, char **a2)
+{
+ *a1 = ap_getword_white(pool, &cmd);
+ *a2 = apr_pstrdup(pool, cmd);
+ if(!*a1 || !*a2) {
+ return 1;
+ }
+ return 0;
+}
+
+int ftp_run_cmd(request_rec *r, const char *key)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ ftp_cmd_entry *cmd;
+ char *method, *arg = NULL;
+ int res;
+
+ if (!FTPMethodHash) {
+ return FTP_REPLY_LOCAL_ERROR;
+ }
+
+ cmd = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
+
+ if (cmd) {
+
+ /* Commands must have been traslated to their real
+ * equivilants back in ftp_read_request_line, by the
+ * ftp_get_cmd_alias function above.
+ *
+ * XXX: recursive aliases are unsupported
+ */
+ if (cmd->pf == NULL) {
+ return FTP_REPLY_COMMAND_UNRECOGNIZED;
+ }
+
+ if ((cmd->flags & FTP_NEED_LOGIN) && !fc->logged_in) {
+ fc->response_notes = apr_pstrdup(r->pool, "Please log in with"
+ " USER and PASS");
+ return FTP_REPLY_NOT_LOGGED_IN;
+ }
+ else {
+ res = ftp_parse2(r->pool, r->the_request,
+ &method, &arg);
+ if ((!(cmd->flags & FTP_TAKE0) && !arg) || res)
+ return FTP_REPLY_LOCAL_ERROR;
+
+ return ftp_run_handler(r, cmd, arg);
+ }
+ }
+
+ /* The command was not found, but we may have received an ABOR,
+ adjusted in case the A was OOB */
+ if (!strcmp(key, "BOR")) {
+ cmd = apr_hash_get(FTPMethodHash, "ABOR", APR_HASH_KEY_STRING);
+ return ftp_run_handler(r, cmd, arg);
+ }
+
+ return FTP_REPLY_COMMAND_UNRECOGNIZED;
+}
+
+int ftp_cmd_abort_data(const char *key)
+{
+ ftp_cmd_entry *cmd;
+
+ cmd = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
+
+ /* Return true if cmd should abort an active data connection */
+ return (cmd && (cmd->flags & FTP_DATA_INTR));
+}
+
+/* Begin definition of our command handlers. */
+static int ftp_cmd_abor(request_rec *r, const char *arg)
+{
+ r->the_request = apr_pstrdup(r->pool, "ABOR");
+ r->method = apr_pstrdup(r->pool, "ABOR");
+ apr_table_setn(r->subprocess_env, "ftp_transfer_ok", "0");
+ return FTP_REPLY_TRANSFER_ABORTED;
+}
+
+static int ftp_cmd_auth(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+
+ if (!ftp_have_ssl() || (!fc->ssl_input_ctx || !fc->ssl_output_ctx)) {
+ fc->response_notes = apr_pstrdup(r->pool,
+ "AUTH mechanism not available");
+ return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
+ }
+
+ /* RFC 2228 states these arguments are case insensitive.
+ * draft-murray-auth-ftp-ssl-06.txt defines these 4 AUTH mechanisms.
+ * TLS or TLS-C will encrypt the control connection, leaving the
+ * data channel clear. SSL or TLS-P will encrypt both the control
+ * and data connections. */
+ if ((strcasecmp(arg, "SSL") == 0) ||
+ (strcasecmp(arg, "TLS-P") == 0)) {
+
+ fc->prot = FTP_PROT_PRIVATE;
+ fc->auth = FTP_AUTH_SSL;
+ }
+ else if ((strcasecmp(arg, "TLS") == 0) ||
+ (strcasecmp(arg, "TLS-C") == 0)) {
+
+ fc->prot = FTP_PROT_CLEAR;
+ fc->auth = FTP_AUTH_TLS;
+ }
+ else {
+ fc->response_notes = apr_pstrdup(r->pool,
+ "AUTH mechanism not supported");
+ return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
+ }
+
+ return FTP_REPLY_SECURITY_EXCHANGE_DONE;
+}
+
+/* XXX: RPM 6/11/2002
+ * This needs rewriting. Too many special cases.
+ */
+static int ftp_change_dir(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ ftp_dir_config *dconf;
+ request_rec *rr;
+ conn_rec *c = r->connection;
+ int res, response;
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+ dconf = ftp_get_module_config(rr->per_dir_config);
+
+ /* Handle special case where the URI is / */
+ if (r->uri[0] == '/' && !r->uri[1]) {
+ fc->cwd = apr_pstrdup(c->pool, r->uri);
+
+ if (dconf->readme) {
+
+ /* We do not inherit readme messages. If this readme was
+ * not specifically meant for us, we skip it. The exception
+ * to the rule is if FTPReadmeMessage was placed in the
+ * global server configuration.
+ */
+ if (!dconf->path ||
+ !strncmp(dconf->path, r->filename,
+ strlen(r->filename) - 1)) {
+
+ if (dconf->readme_isfile) {
+ ftp_show_file(c->output_filters, r->pool,
+ FTP_REPLY_COMPLETED, fc,
+ dconf->readme);
+ }
+ else {
+ char outbuf[BUFSIZ];
+
+ ftp_message_generate(fc, dconf->readme, outbuf,
+ sizeof(outbuf));
+ ftp_reply(fc, c->output_filters, r->pool,
+ FTP_REPLY_COMPLETED, 1, outbuf);
+ }
+ }
+ }
+
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_COMPLETED;
+ }
+
+
+ /* Check access permissions for the new directory. */
+ if (!((rr->status == HTTP_OK) || (rr->status == HTTP_MOVED_PERMANENTLY))) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+ if (rr->finfo.filetype != 0) {
+ if (rr->finfo.filetype != APR_DIR) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOT_A_DIR,
+ r->parsed_uri.path);
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ }
+ else {
+ fc->cwd = apr_pstrdup(c->pool, r->parsed_uri.path);
+
+ if (dconf->readme) {
+ /* We do not inherit readme messages. If this readme was
+ * not specifically meant for us, we skip it. The exception
+ * to the rule is if FTPReadmeMessage was placed in the
+ * global server configuration.
+ */
+ if (!dconf->path ||
+ !strncmp(dconf->path, r->filename,
+ strlen(r->filename) - 1)) {
+
+ if (dconf->readme_isfile) {
+ ftp_show_file(c->output_filters, r->pool,
+ FTP_REPLY_COMPLETED, fc,
+ dconf->readme);
+ }
+ else {
+ char outbuf[BUFSIZ];
+
+ ftp_message_generate(fc, dconf->readme, outbuf,
+ sizeof(outbuf));
+ ftp_reply(fc, c->output_filters, r->pool,
+ FTP_REPLY_COMPLETED, 1, outbuf);
+ }
+ }
+ }
+ response = FTP_REPLY_COMPLETED;
+ }
+ }
+ else {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
+ r->parsed_uri.path);
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+ ap_destroy_sub_req(rr);
+ return response;
+}
+
+static int ftp_cmd_cdup(request_rec *r, const char *arg)
+{
+ return ftp_change_dir(r, "..");
+}
+
+static int ftp_cmd_cwd(request_rec *r, const char *arg)
+{
+ return ftp_change_dir(r, arg);
+}
+
+static int ftp_cmd_dele(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ request_rec *rr;
+ apr_status_t rv;
+ int res, response;
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ /* If the user does not have permission to view the file, do
+ * not let them delete it.
+ */
+ if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ }
+ /* We do have permission, and the file exists. Try to remove. */
+ else if (rr->finfo.filetype == APR_DIR) {
+
+ rv = apr_dir_remove(r->filename, r->pool);
+
+ if (rv != APR_SUCCESS) {
+ char error_str[120];
+ char *err = apr_strerror(rv, error_str, sizeof(error_str));
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ err);
+
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ } else {
+ response = FTP_REPLY_COMPLETED;
+ }
+ }
+ else if (rr->finfo.filetype == APR_REG) {
+
+ rv = apr_file_remove(r->filename, r->pool);
+
+ if (rv != APR_SUCCESS) {
+ /* Call to apr_file_remove failed */
+ char error_str[120];
+ char *err = apr_strerror(rv, error_str, sizeof(error_str));
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ err);
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ }
+ else {
+ response = FTP_REPLY_COMPLETED;
+ }
+ }
+ else {
+ /* File does not exist */
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
+ r->parsed_uri.path);
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ }
+ ap_destroy_sub_req(rr);
+ return response;
+}
+
+static int ftp_cmd_feat(request_rec *r, const char *arg)
+{
+ return FTP_REPLY_SYSTEM_STATUS;
+}
+
+static int ftp_cmd_help(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ conn_rec *c = r->connection;
+ ftp_cmd_entry *cmd;
+ int i;
+ char *method, *buf = "";
+
+ if (*arg) {
+ method = ftp_toupper(r->pool, arg);
+ cmd = apr_hash_get(FTPMethodHash, method, APR_HASH_KEY_STRING);
+
+ if (cmd) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_HELP_SYNTAX,
+ arg, cmd->help);
+ return FTP_REPLY_HELP_MESSAGE;
+ }
+ else {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOTIMPL, arg);
+ return FTP_REPLY_COMMAND_NOT_IMPLEMENTED;
+ }
+ } else {
+ apr_status_t rv;
+ apr_bucket_brigade *bb;
+ apr_hash_index_t *hi;
+ ftp_cmd_entry *cmd;
+ apr_size_t nbytes;
+ apr_bucket *b;
+ void *val;
+ char supported;
+
+ buf = apr_psprintf(r->pool, "%d-%s" CRLF, FTP_REPLY_HELP_MESSAGE,
+ "The following commands are recognized "
+ "(* =>'s unimplemented).");
+
+ for (hi = apr_hash_first(r->pool, FTPMethodHash), i = 0; hi;
+ hi = apr_hash_next(hi), i++) {
+
+ apr_hash_this(hi, NULL, NULL, &val);
+ cmd = (struct ftp_cmd_entry *)val;
+
+ if (cmd->alias) {
+ ftp_cmd_entry *basecmd;
+ basecmd = apr_hash_get(FTPMethodHash, cmd->alias, APR_HASH_KEY_STRING);
+
+ if (basecmd && basecmd->pf)
+ supported = ' ';
+ else
+ supported = '*';
+ }
+ else if (cmd->pf)
+ supported = ' ';
+ else
+ supported = '*';
+
+ method = apr_psprintf(r->pool, " %c%-4s",
+ supported, cmd->key);
+
+ buf = apr_pstrcat(r->pool, buf, method, NULL);
+
+ if (((i + 1) % 8) == 0 )
+ buf = apr_pstrcat(r->pool, buf, CRLF, NULL);
+ }
+
+ buf = apr_pstrcat(r->pool, buf, CRLF, NULL);
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ nbytes = strlen(buf);
+ rv = apr_brigade_write(bb, ap_filter_flush, r->output_filters,
+ buf, nbytes);
+ fc->traffic += nbytes;
+
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ ap_pass_brigade(c->output_filters, bb);
+
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_HELP,
+ c->base_server->server_admin);
+ return FTP_REPLY_HELP_MESSAGE;
+ }
+}
+
+static int ftp_cmd_nlst(request_rec *r, const char *arg);
+
+static int ftp_cmd_list(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ conn_rec *c = r->connection;
+ ftp_server_config *fsc =
+ ftp_get_module_config(c->base_server->module_config);
+ conn_rec *cdata;
+ request_rec *rr;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ int res, decend = 0;
+ struct ftp_direntry *direntry, *dirp;
+ char *word, *buf = NULL, *pattern;
+ int dashl = 0;
+ apr_status_t rv;
+ apr_size_t nbytes;
+ char *argcopy = apr_pstrdup(r->pool, arg);
+ const char *test, *sl;
+
+ while ((word = ap_getword_white(r->pool, &arg)) != NULL) {
+ if(word[0] != '-')
+ break;
+ if (ap_strchr(word, 'l')) {
+ dashl = 1;
+ }
+ }
+ arg = word;
+
+ /* Special FTPOption that maps NLST directly to LIST */
+ if ((fsc->options & FTP_OPT_LISTISNLST) && (!dashl)) {
+ return ftp_cmd_nlst(r, argcopy);
+ }
+
+#ifdef FTP_NO_GLOB
+ /* There is no need to check for abuses of * when globbing is not
+ * enabled. */
+#else
+ if (ap_strchr_c(arg, '*') != NULL) {
+ /* Prevent DOS attacks, only allow one segment to have a wildcard */
+ int found = 0; /* The number of segments with a wildcard */
+ const char *pos = arg; /* Pointer for iterating over the string */
+
+ while (*pos != '\0') {
+ if (*pos == '*') {
+ /* We found a wildcard in this segment. Increment the count
+ * and move the pointer to the beginning of the next
+ * segment */
+ found++;
+ while (*pos != '\0' && *pos != '/') {
+ pos++;
+ }
+ } else {
+ /* Nothing here, move on */
+ pos++;
+ }
+ }
+
+ /* In the future this can be configurable */
+ if (found > 1) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, 0,
+ "Ignoring directory listing request for %s", arg);
+ fc->response_notes = apr_psprintf(r->pool,
+ FTP_MSG_PERM_DENIED, arg);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ }
+#endif /* FTP_NO_GLOB */
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ /* Need to run intermediate subrequest to check for redirect */
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+ if (rr->status == HTTP_MOVED_PERMANENTLY) {
+ ap_parse_uri(r, apr_pstrcat(r->pool, rr->uri, "/", NULL));
+
+ /* Rerun the subrequest with the new uri */
+ ap_destroy_sub_req(rr);
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+ }
+
+ if (rr->status != HTTP_OK) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+ ap_destroy_sub_req(rr);
+
+ if(arg[0] == '\0') {
+ pattern = apr_pstrcat(r->pool, r->filename, "/*", NULL);
+ }
+ else {
+ pattern = r->filename;
+ }
+
+ /* Construct the sorted array of directory contents */
+#ifdef FTP_NO_GLOB
+ if((direntry = ftp_direntry_get(r)) == NULL) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE, arg);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+#else
+ if((direntry = ftp_direntry_get_glob(r, pattern)) == NULL) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE, arg);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+#endif /* FTP_NO_GLOB */
+
+ fc->response_notes = FTP_MSG_OPENASCII;
+ ftp_send_response(r, FTP_REPLY_FILE_STATUS_OK);
+
+ if ( !(cdata = ftp_open_dataconn(r, 1)) ) {
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+
+ /*
+ * We have the directory tree, and its time to print it to the client.
+ * At this point we could have one of three things.
+ *
+ * 1. One entry, a directory. Just print the contents
+ * 2. No directories. Just print the contents.
+ * 3. Mixture. Print files first, then directories.
+ */
+
+ /* Handle case 1 */
+ if (direntry->child && !direntry->next) {
+ direntry = direntry->child;
+ }
+ else {
+ /* Handle case 2 & 3 */
+ for (dirp = direntry; dirp; dirp=dirp->next) {
+ if (dirp->child)
+ decend = 1;
+ }
+ }
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ /* Print the directory listing, skipping directories if needed */
+ for (dirp = direntry; dirp; dirp = dirp->next) {
+ if (dirp->modestring[0] == 'd' && decend) {
+ continue;
+ }
+
+ for (test = dirp->name; sl = strchr(test, '/'); test = sl + 1)
+ /* noop */ ;
+
+ if (!strcmp(".", test)) {
+ continue;
+ }
+
+ buf = apr_psprintf(r->pool,
+ "%10s%5d %-8s %-8s%9" APR_OFF_T_FMT " %s %s"
+ CRLF, dirp->modestring, dirp->nlink,
+ dirp->username, dirp->groupname,
+ dirp->size, dirp->datestring, dirp->name);
+ nbytes = strlen(buf);
+ rv = apr_brigade_write(bb, ap_filter_flush, cdata->output_filters,
+ buf, nbytes);
+ fc->traffic += nbytes;
+ }
+
+ if (decend) {
+ for (dirp = direntry; dirp; dirp=dirp->next) {
+ if (dirp->modestring[0] == 'd' && dirp->child) {
+ ftp_direntry *decend;
+
+ /* Iterate through the sub directory */
+ buf = apr_psprintf(r->pool, CRLF "%s:" CRLF, dirp->name);
+ nbytes = strlen(buf);
+ rv = apr_brigade_write(bb, ap_filter_flush,
+ cdata->output_filters, buf, nbytes);
+ fc->traffic += nbytes;
+
+ buf = apr_pstrcat(r->pool, "total 0" CRLF, NULL);
+ nbytes = strlen(buf);
+ rv = apr_brigade_write(bb, ap_filter_flush,
+ cdata->output_filters, buf, nbytes);
+ fc->traffic += nbytes;
+
+ for (decend = dirp->child; decend; decend = decend->next) {
+ for (test = dirp->name; sl = strchr(test, '/'); test = sl + 1)
+ /* noop */ ;
+
+ if (!strcmp(".", test)) {
+ continue;
+ }
+
+ buf = apr_psprintf(r->pool, "%10s%5d %-8s %-8s%9"
+ APR_OFF_T_FMT " %s %s" CRLF,
+ decend->modestring, decend->nlink,
+ decend->username, decend->groupname,
+ decend->size, decend->datestring,
+ decend->name);
+ nbytes = strlen(buf);
+ rv = apr_brigade_write(bb, ap_filter_flush,
+ cdata->output_filters, buf,
+ nbytes);
+ fc->traffic += nbytes;
+ }
+ }
+ }
+ }
+
+ /* Flush the brigade down the filter chain */
+ b = apr_bucket_flush_create(cdata->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ ap_pass_brigade(cdata->output_filters, bb);
+
+ ap_lingering_close(cdata);
+ fc->datasock = NULL;
+ fc->transfers++;
+
+ if (cdata->aborted)
+ return FTP_REPLY_TRANSFER_ABORTED;
+ else
+ return FTP_REPLY_DATA_CLOSE;
+}
+
+static int ftp_cmd_mdtm(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ int res;
+ request_rec *rr;
+ apr_time_exp_t t;
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ apr_explode_localtime(&t, rr->finfo.mtime);
+ fc->response_notes = apr_psprintf(r->pool,
+ "%04d%02d%02d%02d%02d%02d",
+ 1900 + t.tm_year, t.tm_mon + 1,
+ t.tm_mday, t.tm_hour, t.tm_min,
+ t.tm_sec);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_STATUS;
+}
+
+static int ftp_cmd_mkd(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ apr_status_t rv;
+ int res;
+ request_rec *rr;
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ ap_destroy_sub_req(rr);
+
+ rv = apr_dir_make(r->filename, APR_OS_DEFAULT, r->pool);
+
+ if (rv != APR_SUCCESS) {
+ char error_str[120];
+ char *err = apr_strerror(rv, error_str, sizeof(error_str));
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED, err);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ else {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_DIR_CREAT,
+ r->parsed_uri.path);
+ return FTP_REPLY_PATH_CREATED;
+ }
+}
+
+static int ftp_cmd_nlst(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ conn_rec *c = r->connection;
+ ftp_server_config *fsc =
+ ftp_get_module_config(c->base_server->module_config);
+ conn_rec *cdata;
+ request_rec *rr;
+ int res;
+ struct ftp_direntry *direntry, *dirp;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ char *word, *buf = NULL, *pattern;
+ apr_status_t rv;
+ apr_size_t nbytes;
+ char *argcopy = apr_pstrdup(r->pool, arg);
+ int dashl = 0;
+ const char *test, *sl;
+
+ while ((word = ap_getword_white(r->pool, &arg)) != NULL) {
+ if(word[0] != '-')
+ break;
+ if (ap_strchr(word, 'l')) {
+ dashl = 1;
+ }
+ }
+ arg = word;
+
+ /* Special FTPOption that maps NLST directly to LIST */
+ if (fsc->options & FTP_OPT_NLSTISLIST || (dashl)) {
+ return ftp_cmd_list(r, argcopy);
+ }
+
+ while ((word = ap_getword_white(r->pool, &arg)) != NULL) {
+ if (word[0] != '-')
+ break;
+ }
+ arg = word;
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ /* Run intermediate subrequest to check for a redirect */
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+ if (rr->status == HTTP_MOVED_PERMANENTLY) {
+ ap_parse_uri(r, apr_pstrcat(r->pool, rr->uri, "/", NULL));
+
+ /* Rerun the subrequest */
+ ap_destroy_sub_req(rr);
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+ }
+
+ if (rr->status != HTTP_OK) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ ap_destroy_sub_req(rr);
+
+ if (arg[0] == '\0') {
+ pattern = apr_pstrcat(r->pool, r->filename, "/*", NULL);
+ }
+ else {
+ pattern = r->filename;
+ }
+
+ /* Construct the sorted array of directory contents */
+#ifdef FTP_NO_GLOB
+ if((direntry = ftp_direntry_get(r)) == NULL) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE, arg);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+#else
+ if((direntry = ftp_direntry_get_glob(r, pattern)) == NULL) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE, arg);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+#endif /* FTP_NO_GLOB */
+
+ fc->response_notes = FTP_MSG_OPENASCII;
+ ftp_send_response(r, FTP_REPLY_FILE_STATUS_OK);
+
+ if (!(cdata = ftp_open_dataconn(r, 1))) {
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ for (dirp = direntry; dirp; dirp = dirp->next) {
+ for (test = dirp->name; sl = strchr(test, '/'); test = sl + 1)
+ /* noop */ ;
+
+ if (!strcmp(".", test)) {
+ continue;
+ }
+
+ if ((fsc->options & FTP_OPT_NLSTSHOWDIRS) ||
+ dirp->modestring[0] != 'd') {
+ buf = apr_psprintf(r->pool, "%s" CRLF, dirp->name);
+ nbytes = strlen(buf);
+ rv = apr_brigade_write(bb, ap_filter_flush,
+ cdata->output_filters, buf, nbytes);
+ fc->traffic += nbytes;
+ }
+ }
+
+ /* If the brigade is empty, just send an eos */
+ if (APR_BRIGADE_EMPTY(bb)) {
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
+
+ /* Flush the brigade down the filter chain */
+ b = apr_bucket_flush_create(cdata->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ ap_pass_brigade(cdata->output_filters, bb);
+
+ ap_lingering_close(cdata);
+ fc->datasock = NULL;
+
+ fc->transfers++;
+
+ if (cdata->aborted)
+ return FTP_REPLY_TRANSFER_ABORTED;
+ else
+ return FTP_REPLY_DATA_CLOSE;
+}
+
+static int ftp_cmd_noop(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_SUCCESS, r->method);
+ return FTP_REPLY_COMMAND_OK;
+}
+
+static int ftp_cmd_pass(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ core_server_config *ftpcore = NULL;
+ char *userdir = NULL;
+ ftp_dir_config *dconf;
+ conn_rec *c = r->connection;
+ request_rec *rr;
+ char *userpass;
+ server_rec *ftpserver;
+ apr_status_t rv;
+
+ ftp_server_config *fsc =
+ ftp_get_module_config(c->base_server->module_config);
+
+ /* Reset the possibly mauled ap_document_root and our cwd
+ * with each attempt to finish logging in.
+ */
+ c->base_server = fc->orig_server;
+ fc->cwd = "/";
+
+ /* By this point we have already extracted the arguments, so rewrite
+ * the original request to not include the password */
+ r->the_request = apr_pstrdup(r->pool, "PASS xxx");
+
+ userpass = apr_psprintf(r->pool, "%s:%s", fc->user, arg);
+ fc->authorization = apr_psprintf(c->pool, "Basic %s",
+ ap_pbase64encode(r->pool, userpass));
+
+ /* This is normally set in the dispatcher, but since we just read
+ * the password, we need to reset the auth header and r->user
+ */
+ ftp_set_authorization(r);
+
+ /* Prepare to overwrite ap_document_root */
+ if (fsc->jailuser || fsc->docrootenv) {
+ int i;
+ ap_conf_vector_t *conf_vector;
+ module *modp;
+
+ ftpserver = apr_pcalloc(c->pool, sizeof(*ftpserver));
+ memcpy(ftpserver, c->base_server, sizeof(*ftpserver));
+
+ /* We need to count the number of modules in order to know
+ * how big an array to allocate. There is a variable in
+ * the server for this, but it isn't exported, so we are
+ * stuck doing this. I guess this could be a static variable
+ * that we compute once, but it shouldn't be that expensive
+ * to do it this way.
+ */
+ for (i = 0; ap_loaded_modules[i]; i++);
+ conf_vector = apr_pcalloc(c->pool, sizeof(void *) * i);
+
+ for (modp = ap_top_module; modp; modp = modp->next) {
+ /* This is a hack. Basically, to keep the user in thier
+ * own directory, we are re-writing the DocumentRoot when
+ * the user logs in. To do this, we copy the entire
+ * server_rec for this Vhost, and then we copy the whole
+ * module_config for that server. For the core module,
+ * we don't just copy the pointer, instead we create a new
+ * core_server_config, copy the old one to it, and change
+ * the docroot. Yuck.
+ */
+ if (modp == &core_module) {
+ core_server_config *core;
+
+ ftpcore = apr_pcalloc(c->pool, sizeof(*ftpcore));
+ core =
+ ap_get_module_config(c->base_server->module_config,
+ modp);
+ *ftpcore = *core;
+ ap_set_module_config(conf_vector, modp, ftpcore);
+ }
+ else {
+ ap_set_module_config(conf_vector, modp,
+ ap_get_module_config(c->base_server->module_config,
+ modp));
+ }
+ }
+ ftpserver->module_config = (ap_conf_vector_t *)conf_vector;
+ r->server = ftpserver;
+ c->base_server = ftpserver;
+ }
+
+ if (fsc->docrootenv) {
+ const char *docroot;
+ char *newroot;
+
+ /* Invoke a request that forces auth to occur,
+ * Then check for an fsc->docrootenv that translates
+ * to a valid envvar.
+ */
+ rr = ap_sub_req_method_uri(r->method, "/", r, NULL);
+ docroot = apr_table_get(rr->subprocess_env, fsc->docrootenv);
+
+ if (!docroot || !*docroot) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0,
+ r->pool,
+ "Warning: Document Root variable from FTPDocRootEnv [%s] "
+ "was empty or not found, using default DocumentRoot",
+ fsc->docrootenv);
+ }
+ else if (((rv = apr_filepath_merge(&newroot, NULL, docroot,
+ APR_FILEPATH_TRUENAME
+ | APR_FILEPATH_NOTRELATIVE,
+ c->pool)) != APR_SUCCESS)
+ || (rv = APR_ENOTDIR, !ap_is_directory(r->pool, newroot))) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, rv,
+ r->pool,
+ "Warning: Document Root [%s] from FTPDocRootEnv [%s] "
+ "is invalid or does not exist, using default DocumentRoot",
+ docroot, fsc->docrootenv);
+ }
+ else {
+ ftpcore->ap_document_root = newroot;
+ }
+
+ ap_destroy_sub_req(rr);
+ }
+
+ /* Check for a configured home directory */
+ if (fsc->homedir) {
+
+ apr_filepath_merge(&userdir, fsc->homedir, fc->user, 0, r->pool);
+
+ if (userdir) {
+
+ rr = ap_sub_req_method_uri(r->method, userdir, r, NULL);
+
+ if (rr->finfo.filetype == APR_DIR &&
+ (rr->status == HTTP_OK ||
+ rr->status == HTTP_MOVED_PERMANENTLY)) {
+
+ fc->cwd = apr_pstrcat(c->pool, userdir, "/", NULL);
+ }
+ else if (rr->status == HTTP_OK &&
+ rr->finfo.filetype == APR_NOFILE) {
+
+ /* See if we should create the directory automatically */
+ if (fsc->options & FTP_OPT_CREATEHOMEDIRS) {
+
+ if (rr->finfo.filetype == APR_NOFILE && rr->filename
+ && ap_is_directory(rr->pool,
+ ap_make_dirstr_parent(rr->pool, rr->filename))) {
+
+ rv = apr_dir_make(rr->filename, APR_OS_DEFAULT,
+ r->pool);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv,
+ c->base_server, "Couldn't "
+ "create home directory (%s) for "
+ "user %s",
+ rr->filename, fc->user);
+ }
+ else {
+ /* The new directory was created. */
+ fc->cwd = apr_pstrcat(c->pool, userdir, "/", NULL);
+ ap_log_error(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO,
+ 0, c->base_server, "Created home "
+ "directory (%s) for user %s",
+ rr->filename, fc->user);
+ }
+ } else {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO,
+ 0, c->base_server,
+ "Home directory for user %s not "
+ "found. Using \"/\"", fc->user);
+
+ }
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
+ c->base_server, "Home directory for "
+ "user %s not found. Using \"/\"",
+ fc->user);
+ }
+ } else {
+
+ ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
+ c->base_server, "Permission denied entering"
+ " home directory for user %s. Using \"/\"",
+ fc->user);
+ }
+
+ ap_destroy_sub_req(rr);
+ }
+ }
+
+ if (fsc->jailuser) {
+ /* If we didn't set cwd the Jailed user can't be allowed to log in.
+ * The cwd was set to userdir if it existed or was created above,
+ * but we must ignore the trailing slash.
+ */
+ if (!userdir || (strncmp(userdir, fc->cwd, strlen(userdir)) != 0)) {
+ goto pass_try_again;
+ }
+ ftpcore->ap_document_root = apr_pstrcat(c->pool,
+ ftpcore->ap_document_root, userdir, NULL);
+ fc->cwd = apr_pstrdup(c->pool, "/");
+ }
+
+ /* Our document_root and cwd are now constructed for the user... */
+ rr = ap_sub_req_method_uri(r->method, fc->cwd, r, NULL);
+
+ dconf = ftp_get_module_config(rr->per_dir_config);
+
+ if (rr->status == HTTP_OK) {
+ /* We now must iterate over the entire scoreboard checking to see
+ * if we have another server that can handle an incoming request.
+ * If we don't see another server in the ready state, that means
+ * we are the last available server, and must send the client a
+ * message that the server is full.
+ *
+ * For those wondering, there is a race condition here that could
+ * cause a client to be put in the accept queue, but we should be
+ * able to recover from this once a client disconnects.
+ *
+ * This has a few side effects:
+ * - We can really only service Max - 1 FTP sessions concurrently.
+ *
+ * - If a hybid server is heavily loaded, such that all servers
+ * are busy serving HTTP traffic, it is possible to starve FTP
+ * requests, since when we check the scoreboard, all servers will
+ * be busy.
+ */
+ ftp_loginlimit_t limrv;
+
+ if ((fsc->options & FTP_OPT_CHECKMAXCLIENTS) &&
+ ftp_check_maxclients(r)) {
+ ap_destroy_sub_req(rr);
+ fc->response_notes = apr_pstrdup(r->pool,
+ "Maximum number of concurrent "
+ "sessions reached, closing connection.");
+ fc->close_connection = 1;
+
+ return FTP_REPLY_SERVICE_NOT_AVAILABLE;
+ }
+
+ limrv = ftp_limitlogin_check(fc->user, r);
+ /* I love switch */
+ switch (limrv) {
+ case FTP_LIMIT_HIT_PERSERVER:
+ ap_destroy_sub_req(rr);
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0,
+ c->base_server, "FTP per server login limit hit for %s",
+ fc->user);
+
+ fc->response_notes = apr_pstrdup(r->pool,
+ "Maximum number of concurrent "
+ "sessions reached, closing connection.");
+ fc->close_connection = 1;
+ return FTP_REPLY_SERVICE_NOT_AVAILABLE;
+
+ case FTP_LIMIT_HIT_PERUSER:
+ ap_destroy_sub_req(rr);
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0,
+ c->base_server, "FTP per user login limit hit for %s",
+ fc->user);
+
+ fc->response_notes = apr_pstrdup(r->pool,
+ "Maximum number of concurrent "
+ "sessions reached, closing connection.");
+ fc->close_connection = 1;
+ return FTP_REPLY_SERVICE_NOT_AVAILABLE;
+
+ case FTP_LIMIT_HIT_PERIP:
+ ap_destroy_sub_req(rr);
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0,
+ c->base_server, "FTP per IP login limit hit for %s",
+ fc->user);
+
+ fc->response_notes = apr_pstrdup(r->pool,
+ "Maximum number of concurrent "
+ "sessions reached, closing connection.");
+ fc->close_connection = 1;
+ return FTP_REPLY_SERVICE_NOT_AVAILABLE;
+
+ case FTP_LIMIT_ERROR:
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0,
+ c->base_server, "Error accessing DB for user %s; no login limit in effect",
+ fc->user);
+ break;
+
+ case FTP_LIMIT_OK:
+ default:
+ break;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0,
+ c->base_server, "FTP LOGIN FROM %s as %s",
+ c->remote_ip, fc->user);
+
+ if (dconf->readme) {
+ if (dconf->readme_isfile) {
+ ftp_show_file(c->output_filters, r->pool,
+ FTP_REPLY_USER_LOGGED_IN, fc,
+ dconf->readme);
+ }
+ else {
+ char outbuf[BUFSIZ];
+
+ ftp_message_generate(fc, dconf->readme, outbuf,
+ sizeof(outbuf));
+ ftp_reply(fc, c->output_filters, r->pool,
+ FTP_REPLY_USER_LOGGED_IN, 1, outbuf);
+ }
+ }
+
+ fc->logged_in = 1;
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_USER_LOGGED_IN;
+ }
+
+ ap_destroy_sub_req(rr);
+
+ /* Here we return the generic FTP_REPLY_NOT_LOGGED_IN error (530),
+ * but it may have been because of a varity of reasons. If the
+ * return status is 401 HTTP_UNAUTHORIZED, then the username/password
+ * combination was incorrect. If we get a HTTP_FORBIDDEN, it is
+ * likely that the access checkers failed because the location
+ * was configured with 'deny from all'.
+ */
+ if ((strcmp(fc->user, "anonymous") == 0) ||
+ strcmp(fc->user, "guest") == 0) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0,
+ c->base_server,
+ "ANONYMOUS FTP LOGIN REFUSED FROM %s",
+ c->remote_ip);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0,
+ c->base_server, "FTP LOGIN REFUSED FROM %s, %s",
+ c->remote_ip, fc->user);
+ }
+pass_try_again:
+ if (++fc->login_attempts == fsc->max_login_attempts) {
+ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0,
+ c->base_server, "repeated login failures from %s",
+ c->remote_ip);
+
+ fc->response_notes = apr_pstrdup(r->pool,
+ "Maximum login attempts reached, "
+ "closing connection.");
+ fc->close_connection = 1;
+
+ return FTP_REPLY_SERVICE_NOT_AVAILABLE;
+ }
+
+ /* Sleep one second for every failed login attempt. This is
+ * a common practice among FTP servers to prevent DOS attacks */
+ apr_sleep(fc->login_attempts * APR_USEC_PER_SEC);
+
+ fc->response_notes = apr_pstrdup(r->pool,
+ "Please log in with USER and PASS");
+ return FTP_REPLY_NOT_LOGGED_IN;
+}
+
+static int ftp_cmd_pasv(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ conn_rec *c = r->connection;
+ ftp_server_config *fsc =
+ ftp_get_module_config(c->base_server->module_config);
+ apr_sockaddr_t *sa;
+ apr_socket_t *s;
+ apr_port_t port;
+ apr_status_t rv;
+ char *a, *period;
+ short high, low;
+ int tries, found_port;
+
+ if (fc->csock) {
+ apr_socket_close(fc->csock);
+ fc->csock = NULL;
+ fc->passive_created = -1;
+ }
+
+ rv = apr_socket_create(&s, APR_INET, SOCK_STREAM, c->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server,
+ "Couldn't create passive socket");
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+
+ rv = apr_socket_opt_set(s, APR_SO_LINGER,
+ MAX_SECS_TO_LINGER);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server,
+ "Couldn't set APR_SO_LINGER socket option");
+ apr_socket_close(s);
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+ rv = apr_socket_opt_set(s, APR_SO_REUSEADDR, 1);
+ if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server,
+ "Couldn't set APR_SO_REUSEADDR socket option");
+ apr_socket_close(s);
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+
+ apr_socket_addr_get(&sa, APR_LOCAL, s);
+
+ if (fsc->pasv_bindaddr) {
+ apr_sockaddr_ip_set(sa, fsc->pasv_bindaddr);
+ }
+ else {
+ apr_sockaddr_ip_set(sa, c->local_ip);
+ }
+
+ found_port = 0;
+ port = fsc->pasv_min;
+ while (!found_port) {
+
+ apr_sockaddr_port_set(sa, port); /* Magic here when port = 0 */
+
+ rv = apr_bind(s, sa);
+ if (rv == APR_SUCCESS) {
+ found_port = 1;
+ break;
+ }
+
+ /* If we are using a range, increment the port, and continue. If
+ * we have reached the top of the range start over at the
+ * beginning
+ */
+ if (port != 0) {
+ if (port == fsc->pasv_max) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server,
+ "Couldn't find port within range for passive "
+ "connection. Restarting at %d", fsc->pasv_min);
+ apr_socket_close(s);
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+ port++;
+ continue;
+ }
+
+ /* Otherwise, we are using a random port, loop attempting to bind.
+ * Under high load we may have many sockets in TIME_WAIT, causing
+ * bind to fail with EADDRINUSE. In these conditions we throttle
+ * the system by sleeping before we attempt to bind again
+ */
+ for (tries = 0;; tries++) {
+ rv = apr_bind(s, sa);
+ if (rv == APR_SUCCESS) {
+ found_port = 1;
+ break;
+ }
+ if (rv != FTP_APR_EADDRINUSE ||
+ tries >= FTP_MAX_TRIES) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server,
+ "Couldn't bind to passive socket");
+ apr_socket_close(s);
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+ apr_sleep(tries * APR_USEC_PER_SEC);
+ }
+ }
+
+ rv = apr_listen(s, 1);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server,
+ "Couldn't listen on passive socket");
+ apr_socket_close(s);
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+
+ apr_socket_addr_get(&sa, APR_LOCAL, s);
+ if (fsc->pasv_bindaddr) {
+ a = apr_pstrdup(r->pool, fsc->pasv_bindaddr);
+ }
+ else if (fsc->pasv_addr) {
+ a = apr_pstrdup(r->pool, fsc->pasv_addr);
+ }
+ else {
+ apr_sockaddr_ip_get(&a, sa);
+ }
+
+ /* Translate x.x.x.x to x,x,x,x */
+ while ((period = strstr(a, ".")) != NULL) {
+ *period = ',';
+ }
+
+ apr_sockaddr_port_get(&port, sa);
+ high = ((port & 0xff00) >> 8);
+ low = (port & 0x00ff);
+ fc->response_notes = apr_psprintf(r->pool,
+ "Entering Passive Mode (%s,%u,%u)",
+ a, high, low);
+ fc->csock = s;
+ fc->passive_created = apr_time_now();
+
+ return FTP_REPLY_PASSIVE_MODE;
+}
+
+static int ftp_cmd_pbsz(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ char *endp;
+
+ /* Check if we have completed the security exchange */
+ if (fc->auth == FTP_AUTH_NONE) {
+ return FTP_REPLY_BAD_SEQUENCE;
+ }
+
+ fc->pbsz = strtol(arg, &endp, 10);
+ /* Return 501 if we were unable to parse the argument or if
+ * there was a possibility of an overflow */
+ if (((*arg == '\0') || (*endp != '\0')) || fc->pbsz < 0
+ || fc->pbsz == LONG_MAX) {
+ fc->response_notes = apr_pstrdup(r->pool, "Could not parse PBSZ "
+ "argument");
+ return FTP_REPLY_SYNTAX_ERROR;
+ }
+
+ fc->response_notes = apr_psprintf(r->pool, "PBSZ Command OK. "
+ "Protection buffer size set to %d",
+ fc->pbsz);
+ return FTP_REPLY_COMMAND_OK;
+}
+
+static int ftp_cmd_port(request_rec *r, const char *arg)
+{
+ ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ conn_rec *c = r->connection;
+ apr_sockaddr_t *sa;
+ apr_socket_t *s;
+ apr_status_t rv;
+ char *request, *ip_addr;
+ short port;
+#if !defined(WIN32) && !defined(HPUX)
+ uid_t user = 0;
+#endif
+ int res, val[6];
+
+ if (fc->csock) {
+ apr_socket_close(fc->csock);
+ fc->csock = NULL;
+ fc->passive_created = -1;
+ }
+
+ request = apr_pstrdup(r->pool, arg);
+ if ((res = sscanf(request, "%d,%d,%d,%d,%d,%d", &val[0], &val[1], &val[2],
+ &val[3], &val[4], &val[5])) != 6) {
+ fc->response_notes = apr_pstrdup(r->pool, "Invalid PORT request");
+ return FTP_REPLY_SYNTAX_ERROR;
+ }
+
+ ip_addr = apr_psprintf(r->pool, "%d.%d.%d.%d",
+ val[0], val[1], val[2], val[3]);
+
+ port = ((val[4] << 8) + val[5]);
+
+ /* Open data connection only if the PORT connection is to the client's IP address.
+ * All other PORT connection requests are denied, unless disabled using:
+ *
+ * FTPOptions AllowProxyPORT
+ */
+ if (!(fsc->options & FTP_OPT_ALLOWPROXYPORT)) {
+ if (strcasecmp(ip_addr, c->remote_ip) != 0) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server,
+ "PORT data connection request to %s "
+ "doesn't match the client IP %s",
+ ip_addr, c->remote_ip);
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server,
+ "PORT data connection request denied, "
+ "not configured to AllowProxyPORT");
+ fc->response_notes = apr_pstrdup(r->pool, "Invalid PORT command, proxy PORT is not permitted");
+ return FTP_REPLY_SYNTAX_ERROR;
+ }
+ }
+
+#if !defined(WIN32) && !defined(HPUX)
+ user = geteuid();
+ if ((fsc->active_min != -1) && (fsc->active_min < 1024)) {
+ if (seteuid(0) != 0) {
+ /* For safety, switch back to user, ignore result */
+ seteuid(user);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
+ "Unable to switch to root priviledges.");
+ }
+ }
+#endif
+
+ rv = apr_socket_create(&s, APR_INET, SOCK_STREAM, c->pool);
+ if (rv != APR_SUCCESS) {
+#if !defined(WIN32) && !defined(HPUX)
+ if ((fsc->active_min != -1) && (fsc->active_min < 1024)) {
+ seteuid(user);
+ }
+#endif
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server,
+ "Couldn't create socket");
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+
+ apr_socket_addr_get(&sa, APR_LOCAL, s);
+
+ if (fsc->active_min != -1) {
+ short port_num = fsc->active_min +
+ ((int) (rand() % (fsc->active_max - fsc->active_min + 1)));
+ apr_sockaddr_port_set(sa, port_num);
+ }
+ else {
+ apr_sockaddr_port_set(sa, 0);
+ }
+
+ apr_sockaddr_ip_set(sa, c->local_ip);
+ apr_socket_opt_set(s, APR_SO_REUSEADDR, 1);
+ rv = apr_bind(s, sa);
+
+#if !defined(WIN32) && !defined(HPUX)
+ if ((fsc->active_min != -1) && (fsc->active_min < 1024)) {
+ seteuid(user);
+ }
+#endif
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server,
+ "Couldn't bind to socket");
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+
+ apr_socket_addr_get(&fc->clientsa, APR_LOCAL, s);
+ apr_sockaddr_port_set(fc->clientsa, port);
+ apr_sockaddr_ip_set(fc->clientsa, ip_addr);
+
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_SUCCESS, r->method);
+ fc->passive_created = -1; /* Redundant but just for clarity */
+ fc->csock = s;
+ return FTP_REPLY_COMMAND_OK;
+}
+
+static int ftp_cmd_prot(request_rec *r, const char *arg)
+{
+ ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+
+ /* Return 503 if the user has not done a PRBZ command yet */
+ if (fc->auth == FTP_AUTH_NONE && !fsc->implicit_ssl) {
+ return FTP_REPLY_BAD_SEQUENCE;
+ }
+
+ switch (*arg) {
+ case 'C':
+ fc->response_notes = apr_pstrdup(r->pool, "PROT Command OK. "
+ "Using clear data channel");
+ fc->prot = FTP_PROT_CLEAR;
+ return FTP_REPLY_COMMAND_OK;
+ case 'S':
+ return FTP_REPLY_PROT_NOT_SUPPORTED;
+ case 'E':
+ return FTP_REPLY_PROT_NOT_SUPPORTED;
+ case 'P':
+ fc->response_notes = apr_pstrdup(r->pool, "PROT Command OK. "
+ "Using private data channel");
+ fc->prot = FTP_PROT_PRIVATE;
+ return FTP_REPLY_COMMAND_OK;
+ default:
+ /* We don't understand */
+ fc->response_notes = apr_pstrdup(r->pool, "PROT argument not "
+ "understood.");
+ return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
+ }
+ /* NOT REACHED */
+}
+
+static int ftp_cmd_pwd(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_DIR_CUR, fc->cwd);
+ return FTP_REPLY_PATH_CREATED;
+}
+
+static int ftp_cmd_quit(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+
+ fc->close_connection = 1;
+ return FTP_REPLY_CONTROL_CLOSE;
+}
+
+static int ftp_cmd_rest(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ conn_rec *c = r->connection;
+ char *endp;
+
+ /* XXX: shortcoming, cannot restart > ~2GB. Must be solved in
+ * APR, or we need to use
+ * int len;
+ * res = sscanf(arg,"%"APR_OFF_T_FMT"%n", &fc->restart_point, &len);
+ * end = arg + len;
+ * and test that res == 2. Dunno how portable or safe this gross
+ * hack would be in real life.
+ */
+ fc->restart_point = strtol(arg, &endp, 10);
+ if (((*arg == '\0') || (*endp != '\0')) || fc->restart_point < 0) {
+ fc->response_notes = apr_pstrdup(r->pool, "REST requires a an "
+ "integer value greater than zero");
+ return FTP_REPLY_SYNTAX_ERROR;
+ }
+
+ /* Check overflow condition */
+ if (fc->restart_point == LONG_MAX) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0,
+ c->base_server,
+ "Client attempted an invalid restart point");
+ /* XXX: possible overflow, continue gracefully? Many other FTP
+ * client do not check overflow conditions in the REST command.
+ */
+ }
+ fc->response_notes = apr_psprintf(r->pool, "Restarting at %" APR_OFF_T_FMT
+ ". Send STORE or RETRIEVE to initiate "
+ "transfer.", fc->restart_point);
+ return FTP_REPLY_PENDING;
+}
+
+static int ftp_cmd_retr(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ request_rec *rr;
+ conn_rec *c = r->connection;
+ conn_rec *cdata;
+ const char *rfile;
+ ap_filter_t *f, *rinput, *routput, *rinput_proto, *routput_proto;
+ ftp_server_config *fsc;
+ int res;
+
+ fsc = ftp_get_module_config(c->base_server->module_config);
+ rfile = arg;
+
+ /* Put a note in the env table for logging */
+ /*
+ * This envar determines whether or not the command being
+ * processed is one which causes a file to be uploaded or
+ * downloaded ("transferred"). This allows a logfile to be
+ * created of just transfers attempts. The success or failure
+ * of the transfer is determined by the ftp_transfer_ok envar
+ */
+ apr_table_setn(r->subprocess_env, "do_transfer_log", "1");
+
+ if ((res = ftp_set_uri(r, rfile))) {
+ return res;
+ }
+
+ /* If anything fails in the subrequest, simply return permission denied */
+ rr = ap_sub_req_method_uri(r->method, rfile, r, NULL);
+ if (rr->status != HTTP_OK){
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ rfile);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ ap_destroy_sub_req(rr);
+
+ ftp_reply(fc, fc->connection->output_filters, r->pool,
+ FTP_REPLY_FILE_STATUS_OK, 0,
+ apr_pstrcat(r->pool, "Opening ",
+ (fc->type == TYPE_A) ? "ASCII" : "BINARY",
+ " mode data connection for ", arg, NULL));
+
+ if ( !(cdata = ftp_open_dataconn(r, 1)) ) {
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+
+ /* Save the original filters */
+ rinput = r->input_filters;
+ rinput_proto = r->proto_input_filters;
+ routput = r->output_filters;
+ routput_proto = r->proto_output_filters;
+
+ r->proto_input_filters = cdata->input_filters;
+ r->input_filters = r->proto_input_filters;
+ r->proto_output_filters = cdata->output_filters;
+ r->output_filters = r->proto_output_filters;
+
+ ap_add_input_filter_handle(ftp_input_filter_handle, NULL, r, r->connection);
+
+ r->connection = cdata;
+
+ /*
+ * If we are sending a ASCII file, we need to run the CRLF filter.
+ * Running the CRLF filter will result in a bunch of buckets, so we
+ * should also add the COALESCE filter to put everything back into a
+ * single bucket.
+ */
+ if (fc->type == TYPE_A) {
+ fc->filter_mask += FTP_NEED_CRLF;
+ }
+
+ if (fc->restart_point > 0) {
+ fc->filter_mask += FTP_NEED_BYTERANGE;
+ }
+
+ fc->sending_file = apr_pstrdup(r->pool, arg);
+ fc->in_sending = 0;
+
+ rr = ap_sub_req_method_uri("GET", rfile, r, NULL);
+ if (rr->status != HTTP_OK) {
+ /* Should never get here since we have already run this
+ * subrequest, but better safe than sorry */
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ rfile);
+ res = FTP_REPLY_FILE_NOT_FOUND;
+ goto clean_up;
+ }
+
+ /* If this is a resume request, set the "Range: {start}-" header */
+ if (fc->restart_point > 0) {
+ apr_table_setn(rr->headers_in, "Range",
+ apr_psprintf(r->pool, "bytes=%" APR_OFF_T_FMT "-",
+ fc->restart_point));
+
+ /* Byterange requires that we are not assbackwards (HTTP/0.9) */
+ rr->assbackwards = 0;
+ }
+
+ /* Filter manipulation. We remove the subrequest filter so that
+ * the EOS buckets stay in tact. We also add the content length
+ * filter so that we can record the bytes_sent */
+ for (f = rr->output_filters; f; f = f->next) {
+ if (strcasecmp(f->frec->name, "SUBREQ_CORE") == 0) {
+ ap_remove_output_filter(f);
+ }
+ }
+
+ /* Need content length rr->bytes_sent */
+ ap_add_output_filter_handle(ftp_content_length_filter_handle, NULL,
+ rr, rr->connection);
+
+ if ((res = ap_run_sub_req(rr)) != OK){
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE, arg);
+ res = FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+ else {
+ res = FTP_REPLY_DATA_CLOSE;
+ }
+
+ fc->restart_point = 0;
+ fc->traffic += rr->bytes_sent;
+ fc->bytes += rr->bytes_sent;
+ fc->files += 1;
+ fc->transfers += 1;
+
+ /* We use a subrequest here in an weird way, which causes some
+ * information to be lost. Here we hack around that by setting
+ * values in r->main, but in the future, we may just want to do
+ * away with running the subrequest, and run the main request.
+ *
+ * There may be other values we need to save here.
+ */
+ rr->main->sent_bodyct = 1;
+
+ /* Check to make sure the connection wasnt aborted */
+ if (rr->connection->aborted || cdata->aborted) {
+ rr->main->bytes_sent = 0;
+ res = FTP_REPLY_TRANSFER_ABORTED;
+ rr->main->connection->aborted = 0;
+ } else {
+ rr->main->bytes_sent = rr->bytes_sent;
+ }
+
+clean_up:
+ ap_destroy_sub_req(rr);
+
+ /* Replace the filters and connection */
+ r->input_filters = rinput;
+ r->proto_input_filters = rinput_proto;
+ r->output_filters = routput;
+ r->proto_output_filters = routput_proto;
+ r->connection = c;
+
+ /* Close the data connection, send confirmation, and return */
+ ap_lingering_close(cdata);
+ fc->datasock = NULL;
+ fc->sending_file = NULL;
+ fc->filter_mask = 0;
+
+ return res;
+}
+
+static int ftp_cmd_rnfr(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ request_rec *rr;
+ conn_rec *c = r->connection;
+ apr_status_t res;
+ int response;
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ }
+ else if (rr->finfo.filetype != APR_NOFILE) {
+ fc->response_notes = apr_pstrdup(r->pool, "File exists, ready for "
+ "destination name");
+ fc->rename_from = apr_pstrdup(c->pool, r->filename);
+ response = FTP_REPLY_PENDING;
+ }
+ else {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
+ r->parsed_uri.path);
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+ ap_destroy_sub_req(rr);
+
+ return response;
+}
+
+static int ftp_cmd_rnto(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ apr_status_t rv;
+ int response, res;
+ request_rec *rr;
+
+ if ((res = ftp_set_uri(r, arg))) {
+ fc->rename_from = NULL;
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+ ap_destroy_sub_req(rr);
+
+ /* RNTO *must* be preceeded by RNFR */
+ if (fc->rename_from == NULL) {
+ return FTP_REPLY_BAD_SEQUENCE;
+ }
+
+ rv = apr_file_rename(fc->rename_from, r->filename, r->pool);
+
+ if (rv != APR_SUCCESS) {
+ response = FTP_REPLY_LOCAL_ERROR;
+ }
+ else {
+ response = FTP_REPLY_COMPLETED;
+ }
+
+ fc->rename_from = NULL;
+ return response;
+}
+
+static int ftp_cmd_rmd(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ apr_status_t rv, res;
+ request_rec *rr;
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ ap_destroy_sub_req(rr);
+
+ rv = apr_dir_remove(r->filename, r->pool);
+
+ if (rv != APR_SUCCESS) {
+ char error_str[120];
+ char *err = apr_strerror(rv, error_str, sizeof(error_str));
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED, err);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ else {
+ return FTP_REPLY_COMPLETED;
+ }
+}
+
+static int ftp_cmd_size(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ int res, response;
+ request_rec *rr;
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+ if (rr->finfo.filetype == 0) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE, arg);
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ }
+ else if (rr->finfo.filetype != APR_REG) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOTPLAIN, arg);
+ response = FTP_REPLY_FILE_NOT_FOUND;
+ }
+ else {
+ /* XXX: big bug - going back to the beginning. We should compute
+ * size rather than stating the file... this could be done trivially
+ * with a request to a null 'data' port which simply counts up the
+ * bytes. Will be implemented as a special-case of the ftp_core_data
+ * connection endpoint-filter.
+ */
+ fc->response_notes = apr_psprintf(r->pool, "%" APR_OFF_T_FMT,
+ rr->finfo.size);
+ response = FTP_REPLY_FILE_STATUS;
+ }
+
+ ap_destroy_sub_req(rr);
+ return response;
+}
+
+static int ftp_get_client_block(conn_rec *c, ap_input_mode_t mode,
+ apr_bucket_brigade *bb,
+ char *buffer, apr_size_t bufsiz, apr_size_t *len)
+{
+ apr_size_t total;
+ apr_status_t rv;
+ apr_bucket *b;
+ const char *tempbuf;
+
+ if (APR_BRIGADE_EMPTY(bb)) {
+ if ((rv = ap_get_brigade(c->input_filters, bb, mode,
+ APR_BLOCK_READ, bufsiz)) != APR_SUCCESS) {
+ apr_brigade_destroy(bb);
+ return rv;
+ }
+ }
+
+ b = APR_BRIGADE_FIRST(bb);
+ if (APR_BUCKET_IS_EOS(b)) { /* From previous invocation */
+ apr_bucket_delete(b);
+ return APR_EOF;
+ }
+
+ total = 0;
+ while (total < bufsiz
+ && b != APR_BRIGADE_SENTINEL(bb)
+ && !APR_BUCKET_IS_EOS(b)) {
+ apr_size_t len_read;
+ apr_bucket *old;
+
+ if ((rv = apr_bucket_read(b, &tempbuf, &len_read,
+ APR_BLOCK_READ)) != APR_SUCCESS) {
+ return rv;
+ }
+ if (total + len_read > bufsiz) {
+ len_read = bufsiz - total;
+ apr_bucket_split(b, bufsiz - total);
+ }
+ memcpy(buffer, tempbuf, len_read);
+ buffer += len_read;
+ total += len_read;
+
+ old = b;
+ b = APR_BUCKET_NEXT(b);
+ apr_bucket_delete(old);
+ }
+
+ *len = total;
+ return APR_SUCCESS;
+}
+
+/*
+ * This handles all STOR and APPE requests
+ */
+static int ftp_cmd_stor(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ conn_rec *c = r->connection;
+ conn_rec *cdata;
+ ftp_server_config *fsc = ftp_get_module_config(c->base_server->module_config);
+ apr_file_t *file;
+ apr_status_t rv, res;
+ apr_bucket_brigade *bb;
+ apr_int32_t openflag;
+ const char *reply;
+ char *buffer;
+ apr_size_t total = 0, len;
+ request_rec *rr;
+ int clientstatus = APR_SUCCESS;
+ int status = FTP_REPLY_DATA_CLOSE;
+#ifndef WIN32
+ int seen_cr = 0;
+#endif
+#ifdef HAVE_FCHMOD
+ apr_fileperms_t creatperms = 0;
+ apr_finfo_t finfo;
+ int fd;
+#else
+ apr_fileperms_t creatperms = fsc->fileperms;
+#endif
+
+ apr_table_setn(r->subprocess_env, "do_transfer_log", "1");
+
+ if ((res = ftp_set_uri(r, arg))) {
+ return res;
+ }
+
+ rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
+
+ if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ r->parsed_uri.path);
+ ap_destroy_sub_req(rr);
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+ ap_destroy_sub_req(rr);
+
+ /* For the remainder of the operation, (openflag & APR_APPEND)
+ * reflects this was an append operation and we have no need
+ * to truncate. Presume, if not append and our restart point
+ * is zero, that open(O_TRUNC) is supported and preferable.
+ */
+ openflag = APR_WRITE|APR_CREATE;
+ if (strcmp(r->method, "APPE") == 0) {
+ openflag |= APR_APPEND;
+ }
+ else if (fc->restart_point == 0) {
+ openflag |= APR_TRUNCATE;
+ }
+
+ rv = apr_file_open(&file, r->filename, openflag, creatperms, r->pool);
+
+ /* File permissions deny server write access */
+ if (rv != APR_SUCCESS) {
+ char error_str[120];
+ char *err = apr_strerror(rv, error_str, sizeof(error_str));
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ err);
+ fc->restart_point = 0;
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+
+#ifdef HAVE_FCHMOD
+ /* If we opened an existing file, we have nothing to
+ * do later in the code (leave fd as -1).
+ * If we created the file with perms zero, grab the
+ * fd and creatperms to adjust the perms when finished.
+ */
+ if (apr_file_info_get(&finfo, APR_FINFO_PROT, file)
+ != APR_SUCCESS) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ "Unable to perform file upload; "
+ "failed to get fileinfo");
+ fc->restart_point = 0;
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ if (finfo.protection) {
+ fd = -1;
+ }
+ else {
+ apr_os_file_get(&fd, file);
+ creatperms = fsc->fileperms;
+ }
+#endif
+
+ /* Once the file is opened, non-append/non-truncate, we need to space
+ * forward to the restart point. Unset the restart pointer once we
+ * have grabbed it to space forward.
+ */
+ if (!(openflag & (APR_APPEND | APR_TRUNCATE))) {
+ apr_off_t restart = fc->restart_point;
+ if (apr_file_seek(file, APR_SET, &restart) != APR_SUCCESS) {
+ fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
+ "Unable to perform file upload; "
+ "failed to skip to restart point");
+#ifdef HAVE_FCHMOD
+ if (fd != -1) fchmod(fd, ftp_unix_perms2mode(creatperms));
+#endif
+ fc->restart_point = 0;
+ return FTP_REPLY_FILE_NOT_FOUND;
+ }
+ }
+ fc->restart_point = 0;
+
+ reply = apr_psprintf(r->pool, "Opening %s mode data connection for %s",
+ fc->type == TYPE_A ? "ASCII" : "BINARY", arg);
+ ftp_reply(fc, c->output_filters, r->pool, FTP_REPLY_FILE_STATUS_OK,
+ 0, reply);
+
+ /* Open the data connection to the client
+ */
+ if ( !(cdata = ftp_open_dataconn(r, 0)) ) {
+#ifdef HAVE_FCHMOD
+ if (fd != -1) fchmod(fd, ftp_unix_perms2mode(creatperms));
+#endif
+ return FTP_REPLY_CANNOT_OPEN_DATACONN;
+ }
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ buffer = apr_palloc(r->pool, AP_IOBUFSIZE);
+
+ while ((clientstatus = ftp_get_client_block(cdata, fc->type == TYPE_A
+ ? AP_MODE_GETLINE : AP_MODE_READBYTES,
+ bb, buffer, AP_IOBUFSIZE, &len)) ==
+ APR_SUCCESS) {
+#ifndef WIN32
+ /* If we are in ASCII mode, translate to our own internal form.
+ * This means CRLF for windows and plain LF for Unicies */
+ if ((fc->type == TYPE_A) && (len > 0)) {
+ /*
+ * The last read may have been incomplete if we had activity
+ * on the control connection. Watch out for trailing CR's
+ * from the last write, and back up the file a byte if one
+ * had been written for this now-leading LF. */
+ if (seen_cr && (*buffer == APR_ASCII_LF)) {
+ apr_off_t off = -1;
+ apr_file_seek(file, APR_CUR, &off);
+ --total;
+ }
+
+ /* We may reach EOF without an ending newline. Make sure we
+ * don't truncate any important data */
+ if ((len > 1) && (*(buffer + len - 2) == APR_ASCII_CR)) {
+ *(buffer + len - 2) = APR_ASCII_LF;
+ len--;
+ }
+ else {
+ /* Now note any trailing CR on this write. */
+ seen_cr = (*(buffer + len - 1) == APR_ASCII_CR);
+ }
+ }
+
+#endif /* WIN32 */
+
+ rv = apr_file_write(file, buffer, &len);
+ if (rv != APR_SUCCESS) {
+ char error_str[120];
+ char *err = apr_strerror(rv, error_str, sizeof(error_str));
+ fc->response_notes = apr_pstrdup(r->pool, err);
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server,
+ "Error writing uploaded file");
+
+ status = FTP_REPLY_LOCAL_ERROR;
+ break;
+ }
+
+ total += len;
+ }
+
+ if ( (clientstatus != APR_SUCCESS && !APR_STATUS_IS_EOF(clientstatus))
+ || (cdata->aborted) ) {
+ status = FTP_REPLY_TRANSFER_ABORTED;
+ cdata->aborted = 1;
+ }
+
+ fc->traffic += total;
+ fc->bytes += total;
+ fc->files += 1;
+ fc->transfers += 1;
+
+ /* Keep track of bytes sent for logging purposes. */
+ r->sent_bodyct = 1;
+ r->bytes_sent = total;
+
+ /* Once we have finished this upload - we need to reset the file perms
+ * to no longer hold a lock on the uploaded file...
+ */
+#ifdef HAVE_FCHMOD
+ if (fd != -1) fchmod(fd, ftp_unix_perms2mode(creatperms));
+#endif
+
+ /* and truncate anything beyond the end of the most recent upload
+ */
+ if (!(openflag & (APR_APPEND | APR_TRUNCATE))) {
+ /* use apr_file_seek to do apr_file_tell's... */
+ apr_off_t totsize = 0;
+ apr_off_t restart = 0;
+ if ((rv = apr_file_seek(file, APR_CUR, &restart)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, c->base_server,
+ "STOR resume: failed to determine current position for truncation");
+ }
+ else if ((rv = apr_file_seek(file, APR_END, &totsize)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, c->base_server,
+ "STOR resume: failed to determine current file size for truncation");
+ }
+ else if (totsize <= restart) {
+ ; /* noop - nothing to truncate */
+ }
+ else if ((rv = apr_file_trunc(file, restart)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, c->base_server,
+ "STOR resume: failed to truncate remaining file contents");
+ }
+ }
+
+ rv = apr_file_close(file);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, c->base_server,
+ "STOR/APPE: failed to close file");
+ }
+
+ ap_lingering_close(cdata);
+ fc->datasock = NULL;
+
+ return status;
+}
+
+static int ftp_cmd_syst(request_rec *r, const char *arg)
+{
+ return FTP_REPLY_SYSTEM_TYPE;
+}
+
+static int ftp_cmd_type(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+
+ if (*arg && !arg[1]){
+ switch (*arg) {
+ case 'A':
+ fc->type = TYPE_A;
+ fc->response_notes = apr_pstrdup(r->pool, "Type set to A");
+ return FTP_REPLY_COMMAND_OK;
+ case 'I':
+ fc->type = TYPE_I;
+ fc->response_notes = apr_pstrdup(r->pool, "Type set to I.");
+ return FTP_REPLY_COMMAND_OK;
+ case 'E':
+ case 'L':
+ fc->response_notes =
+ apr_pstrcat(r->pool, "Type ", arg, " not implemented", NULL);
+ return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
+ }
+ }
+ fc->response_notes = apr_psprintf(r->pool, "Unrecognized TYPE %s", arg);
+ return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
+}
+
+static int ftp_cmd_user(request_rec *r, const char *arg)
+{
+ ftp_connection *fc = ftp_get_module_config(r->request_config);
+ conn_rec *c = r->connection;
+ ftp_server_config *fsc =
+ ftp_get_module_config(c->base_server->module_config);
+
+ /* Implicit logout */
+ if (fc->logged_in) {
+ ftp_limitlogin_loggedout(fc->user, c);
+ }
+ fc->logged_in = 0;
+
+ fc->user = apr_pstrdup(c->pool, arg);
+
+ if ((fsc->options & FTP_OPT_REQUIRESSL) && !fc->is_secure) {
+ fc->response_notes = apr_pstrdup(r->pool,
+ "This server requires the use of "
+ "SSL");
+ return FTP_REPLY_NOT_LOGGED_IN;
+ }
+
+ if ((strcmp(fc->user, "anonymous") == 0) ||
+ strcmp(fc->user, "guest") == 0) {
+ fc->response_notes = apr_pstrdup(r->pool,
+ "Guest login ok, type your email "
+ "address as the password");
+ fc->user = apr_pstrdup(c->pool, "anonymous"); /* force this for limit control */
+ }
+ else {
+ fc->response_notes = apr_psprintf(r->pool, "Password required for %s",
+ fc->user);
+ }
+
+ return FTP_REPLY_USER_OK;
+}
+
+void ftp_register_core_cmds(apr_pool_t *p) {
+
+ /* Create the FTP command hash. All external modules will need
+ * to make sure that their register hooks callbacks are called
+ * after the FTP module
+ */
+
+ FTPMethodHash = apr_hash_make(p);
+ FTPMethodPool = p; /* The config pool */
+
+ /* Register all of our core command handlers */
+
+ ftp_hook_cmd("ABOR", ftp_cmd_abor, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0|FTP_DATA_INTR,
+ "(abort operation)");
+
+ ftp_hook_cmd("ACCT", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(specify account)");
+
+ ftp_hook_cmd("ALLO", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "allocate storage (vacuously)");
+
+ ftp_hook_cmd("APPE", ftp_cmd_stor, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "<sp> file-name");
+
+ /* AUTH does not require a user to be logged in */
+ ftp_hook_cmd("AUTH", ftp_cmd_auth, FTP_HOOK_LAST,
+ FTP_TAKE1,
+ "<sp> mechanism-name");
+
+ ftp_hook_cmd("CDUP", ftp_cmd_cdup, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "change to parent directory");
+
+ ftp_hook_cmd("CWD", ftp_cmd_cwd, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "[ <sp> directory-name ]");
+
+ ftp_hook_cmd("DELE", ftp_cmd_dele, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "<sp> file-name");
+
+ ftp_hook_cmd("EPRT", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "<sp> |af|addr|port|");
+
+ ftp_hook_cmd("EPSV", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "[ <sp> af|ALL ]");
+
+ ftp_hook_cmd("FEAT", ftp_cmd_feat, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "show server features");
+
+ ftp_hook_cmd("HELP", ftp_cmd_help, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "[ <sp> <string> ]");
+
+ ftp_hook_cmd("LIST", ftp_cmd_list, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0|FTP_TAKE1,
+ "[ <sp> path-name ]");
+
+ ftp_hook_cmd("LPRT", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "<sp> af, hal, h1, h2, h3,..., pal, p1, p2...");
+
+ ftp_hook_cmd("LPSV", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(set server in passive mode)");
+
+ ftp_hook_cmd("MAIL", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(mail to user)");
+
+ ftp_hook_cmd("MDTM", ftp_cmd_mdtm, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "<sp> path-name");
+
+ ftp_hook_cmd("MKD", ftp_cmd_mkd, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "<sp> path-name");
+
+ ftp_hook_cmd("MLFL", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(mail file)");
+
+ ftp_hook_cmd("MODE", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(specify transfer mode)");
+
+ ftp_hook_cmd("MSAM", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(mail send to terminal and mailbox)");
+
+ ftp_hook_cmd("MSND", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(mail send to terminal)");
+
+ ftp_hook_cmd("MSOM", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(mail send to terminal or mailbox");
+
+ ftp_hook_cmd("NLST", ftp_cmd_nlst, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0|FTP_TAKE1,
+ "[ <sp> path-name ]");
+
+ ftp_hook_cmd("NOOP", ftp_cmd_noop, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "");
+
+ ftp_hook_cmd("PASS", ftp_cmd_pass, FTP_HOOK_LAST,
+ FTP_TAKE1,
+ "<sp> password");
+
+ ftp_hook_cmd("PASV", ftp_cmd_pasv, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(set server in passive mode)");
+
+ /* PBSZ does not require a user to be logged in */
+ ftp_hook_cmd("PBSZ", ftp_cmd_pbsz, FTP_HOOK_LAST,
+ FTP_TAKE1,
+ "<sp> decimal-integer");
+
+ ftp_hook_cmd("PORT", ftp_cmd_port, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE1,
+ "<sp> b0, b1, b1, b3, b4, b5");
+
+ /* PROT does not require a user to be logged in */
+ ftp_hook_cmd("PROT", ftp_cmd_prot, FTP_HOOK_LAST,
+ FTP_TAKE1,
+ "<sp> prot-code");
+
+ ftp_hook_cmd("PWD", ftp_cmd_pwd, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0,
+ "(return current directory)");
+
+ ftp_hook_cmd("QUIT", ftp_cmd_quit, FTP_HOOK_LAST,
+ FTP_TAKE0|FTP_DATA_INTR,
+ "(terminate service)");
+
+ ftp_hook_cmd("REIN", NULL, FTP_HOOK_LAST,
+ FTP_NEED_LOGIN|FTP_TAKE0|FTP_DATA_INTR,
+ "(reinitialize server)");
+
[... 101 lines stripped ...]