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 ...]