"Fossies" - the Fresh Open Source Software Archive

Member "mod_ftp-0.9.6/modules/ftp/ftp_commands.c" (21 Sep 2009, 104727 Bytes) of package /linux/www/apache_httpd_modules/old/mod_ftp-0.9.6-beta.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ftp_commands.c" see the Fossies "Dox" file reference documentation.

    1 /* Licensed to the Apache Software Foundation (ASF) under one or more
    2  * contributor license agreements.  See the NOTICE file distributed with
    3  * this work for additional information regarding copyright ownership.
    4  * The ASF licenses this file to You under the Apache License, Version 2.0
    5  * (the "License"); you may not use this file except in compliance with
    6  * the License.  You may obtain a copy of the License at
    7  *
    8  *     http://www.apache.org/licenses/LICENSE-2.0
    9  *
   10  * Unless required by applicable law or agreed to in writing, software
   11  * distributed under the License is distributed on an "AS IS" BASIS,
   12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13  * See the License for the specific language governing permissions and
   14  * limitations under the License.
   15  */
   16 
   17 /*
   18  * Original Copyright (c) 2005 Covalent Technologies
   19  *
   20  * FTP Protocol module for Apache 2.0
   21  */
   22 
   23 #define CORE_PRIVATE
   24 #include "mod_ftp.h"
   25 #include "ftp_internal.h"
   26 #include "apr_version.h"
   27 #include "apr_network_io.h"
   28 #include "http_vhost.h"
   29 
   30 /* seem to need this for LONG_MAX */
   31 #if APR_HAVE_LIMITS_H
   32 #include <limits.h>
   33 #endif
   34 
   35 /* Wish APR had one of these */
   36 #if defined(WIN32) || defined(USE_WINSOCK)
   37 #define FTP_APR_EADDRINUSE (APR_OS_START_SYSERR + WSAEADDRINUSE)
   38 #else
   39 #define FTP_APR_EADDRINUSE EADDRINUSE
   40 #endif                          /* WIN32 */
   41 
   42 extern ap_filter_rec_t *ftp_count_size_filter_handle;
   43 extern ap_filter_rec_t *ftp_content_length_filter_handle;
   44 
   45 static apr_hash_t *FTPMethodHash;
   46 static apr_pool_t *FTPMethodPool;
   47 static const char *FTPHelpText;
   48 static apr_size_t  FTPHelpTextLen;
   49 static const char *FTPFeatText;
   50 static apr_size_t  FTPFeatTextLen;
   51 
   52 /*
   53  * The FTP command structure contains useful information about the FTP
   54  * handler.  This information is filled out when a command is registered
   55  * using ftp_hook_cmd(), which also puts the handler into the global hash.
   56  */
   57 typedef struct ftp_cmd_entry
   58 {
   59     const char *key;             /* The key, e.g. "DELE" */
   60     ftp_hook_fn *pf;             /* Pointer to the handler */
   61     const char *alias;           /* The aliased command e.g. "CDUP" */
   62     int order;                   /* Handler ordering */
   63     int flags;                   /* Flags for this command.  See FTP_CMD_ */
   64     const char *help;            /* Help string for this command */
   65     struct ftp_cmd_entry *next;  /* Pointer to the next handler */
   66 } ftp_cmd_entry;
   67 
   68 
   69 FTP_DECLARE(void) ftp_hook_cmd_any(const char *key, ftp_hook_fn *pf,
   70                                    const char *alias, int order,
   71                                    int flags, const char *help)
   72 {
   73     ftp_cmd_entry *cmd, *curr;
   74 
   75     cmd = apr_pcalloc(FTPMethodPool, sizeof(ftp_cmd_entry));
   76 
   77     /* Duplicate for storage into the hash */
   78     key = apr_pstrdup(FTPMethodPool, key);
   79     help = apr_pstrdup(FTPMethodPool, help);
   80 
   81     cmd->key = key;
   82     cmd->pf = pf;
   83     cmd->alias = alias;
   84     cmd->flags = flags;
   85     cmd->order = order;
   86     cmd->help = help;
   87 
   88     if (!FTPMethodHash) {
   89         /*
   90          * Should never get here.  If a user tries to load an extension
   91          * module before the core FTP module is loaded, they should get an
   92          * undefined symbol.
   93          */
   94         fprintf(stderr, "Could not process registration for %s.", key);
   95         return;
   96     }
   97 
   98     curr = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
   99 
  100     if (curr) {
  101         if (curr->order > cmd->order) {
  102             cmd->next = curr;
  103             apr_hash_set(FTPMethodHash, key, APR_HASH_KEY_STRING, cmd);
  104         }
  105         else {
  106             while (curr->next && (curr->order < cmd->order)) {
  107                 curr = curr->next;
  108             }
  109             cmd->next = curr->next;
  110             curr->next = cmd;
  111         }
  112     }
  113     else {
  114         apr_hash_set(FTPMethodHash, key, APR_HASH_KEY_STRING, cmd);
  115     }
  116 
  117     /*
  118      * Only limit commands that are implemented and access checked (not
  119      * aliases).  PASS is a special case, it is verified after invocation,
  120      * login isn't needed prior to processing.
  121      */
  122     if (pf && (flags & FTP_NEED_LOGIN)) {
  123         ap_method_register(FTPMethodPool, key);
  124     }
  125     ap_method_register(FTPMethodPool, "PASS");
  126 }
  127 
  128 static int ftp_run_handler(request_rec *r, struct ftp_cmd_entry *cmd,
  129                            const char *arg)
  130 {
  131     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  132     request_rec *rr;
  133     int res;
  134 
  135     /*
  136      * Set the authorization header and the user name for all requests.  This
  137      * information may not be needed for all   requests, but is required for
  138      * logging purposes.
  139      */
  140     ftp_set_authorization(r);
  141 
  142     /*
  143      * Run the header parser hook for mod_setenv.  For conditional logging,
  144      * etc
  145      */
  146     ap_run_header_parser(r);
  147 
  148     /*
  149      * Run a subrequest before the command is run.  This allows us to check
  150      * if a command is allowed for the user's current location
  151      */
  152     if ((res = ftp_set_uri(r, fc->cwd))) {
  153         return res;
  154     }
  155 
  156     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
  157 
  158     /*
  159      * XXX - JKS
  160      * 
  161      * This will check authz for the *CURRENT* location.  For commands where the
  162      * URI can change, the command handler will need to rerun the auth/authz
  163      * checks to ensure the user also has permission for the new URI.
  164      */
  165     if ((rr->status == HTTP_UNAUTHORIZED &&
  166          (cmd->flags & FTP_NEED_LOGIN)) ||
  167         ((rr->status == HTTP_FORBIDDEN) &&
  168          (cmd->flags & FTP_NEED_LOGIN))) {
  169         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOTALLOWED,
  170                                  ftp_escape_control_text(r->method, r->pool));
  171         ap_destroy_sub_req(rr);
  172         return FTP_REPLY_FILE_NOT_FOUND;
  173     }
  174     ap_destroy_sub_req(rr);
  175 
  176     /* Ok, this method is allowed, run through the list */
  177     res = cmd->pf(r, arg);
  178     if (res != DECLINED) {
  179         return res;
  180     }
  181 
  182     if (cmd->next) {
  183         cmd = cmd->next;
  184         if (cmd->pf) {
  185             return ftp_run_handler(r, cmd, arg);
  186         }
  187     }
  188     return (cmd->flags & FTP_EXTENSIBLE)
  189             ? FTP_REPLY_COMMAND_NOT_IMPL_PARAM
  190             : FTP_REPLY_COMMAND_NOT_IMPLEMENTED;
  191 }
  192 
  193 const char *ftp_get_cmd_alias(const char *key)
  194 {
  195     ftp_cmd_entry *cmd;
  196 
  197     if (!FTPMethodHash) {
  198         return key;
  199     }
  200 
  201     cmd = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
  202 
  203     if (cmd && cmd->alias) {
  204         return (const char *) cmd->alias;
  205     }
  206 
  207     return key;
  208 }
  209 
  210 void ftp_cmd_finalize(apr_pool_t *pool, apr_pool_t *ptemp)
  211 {
  212     ftp_cmd_entry *cmd, *basecmd;
  213     apr_hash_index_t *hi;
  214     void *val;
  215     int i;
  216 
  217     FTPHelpText = apr_psprintf(ptemp, "%d-%s", FTP_REPLY_HELP_MESSAGE,
  218                                "The following commands are recognized "
  219                                "(* =>'s unimplemented).");
  220 
  221     FTPFeatText = apr_psprintf(ptemp, "%d-%s", FTP_REPLY_SYSTEM_STATUS,
  222                                "Extensions supported");
  223 
  224     for (hi = apr_hash_first(ptemp, FTPMethodHash), i = 0; hi;
  225          hi = apr_hash_next(hi), i++)
  226     {
  227         apr_hash_this(hi, NULL, NULL, &val);
  228         cmd = (struct ftp_cmd_entry *) val;
  229 
  230         if (cmd->alias)
  231             basecmd = apr_hash_get(FTPMethodHash, cmd->alias, APR_HASH_KEY_STRING);
  232         else
  233             basecmd = cmd;
  234 
  235         if (!(cmd->flags & FTP_NO_HELP))
  236             FTPHelpText = apr_psprintf(ptemp, "%s%s   %c%-4s",
  237                                        FTPHelpText, (i % 8) ? "" : CRLF,
  238                                        (basecmd->pf) ? ' ' : '*', cmd->key);
  239         else
  240             --i;
  241 
  242         if (cmd->flags & FTP_NEW_FEAT)
  243             FTPFeatText = apr_pstrcat(ptemp, FTPFeatText, CRLF " ", 
  244                                       cmd->key, NULL);
  245     }
  246 
  247     FTPHelpText = apr_pstrcat(pool, FTPHelpText, CRLF, NULL);
  248     FTPHelpTextLen = strlen(FTPHelpText);
  249 
  250     FTPFeatText = apr_pstrcat(pool, FTPFeatText, CRLF, NULL);
  251     FTPFeatTextLen = strlen(FTPFeatText);
  252 }
  253 
  254 /* ftp_parse2: Parse a FTP request that is expected to have 2 arguments.
  255  *
  256  * Arguments: pool - Pool to allocate from
  257  *            cmd  - The complete request (e.g. GET /foo.html)
  258  *            a1   - The first argument
  259  *            a2   - The second argument
  260  *
  261  * Returns: 1 on error or 0 on success, a1 and a2 are modified to point
  262  *          to the correct values.
  263  */
  264 static int ftp_parse2(apr_pool_t *pool, const char *cmd,
  265                       char **a1, char **a2, int keepws)
  266 {
  267     if (keepws)
  268     {
  269         const char *save = cmd;
  270         while (*cmd && *cmd != ' ') ++cmd;
  271         *a1 = apr_pstrndup(pool, save, cmd - save);
  272         if (*cmd && *cmd == ' ') ++cmd;
  273         *a2 = apr_pstrdup(pool, cmd);
  274         if (!*a1 || !*a2)
  275             return 1;
  276     }
  277     else
  278     {
  279         char *fix;
  280         *a1 = ap_getword(pool, &cmd, ' ');
  281         *a2 = apr_pstrdup(pool, cmd);
  282         if (!*a1 || !*a2)
  283             return 1;
  284         fix = strchr(*a2, '\0');
  285         while (fix > *a2 && *(fix - 1) == ' ')
  286             *(--fix) = '\0';
  287     }
  288     return 0;
  289 }
  290 
  291 int ftp_run_cmd(request_rec *r, const char *key)
  292 {
  293     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  294     ftp_cmd_entry *cmd;
  295     char *method, *arg = NULL;
  296     int res;
  297 
  298     if (!FTPMethodHash) {
  299         return FTP_REPLY_LOCAL_ERROR;
  300     }
  301 
  302     cmd = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
  303 
  304     if (cmd) {
  305 
  306         /*
  307          * Commands must have been traslated to their real equivilants back
  308          * in ftp_read_request_line, by the ftp_get_cmd_alias function above.
  309          * 
  310          * Note: recursive aliases are unsupported
  311          */
  312         if (cmd->pf == NULL) {
  313             return (cmd->flags & FTP_EXTENSIBLE)
  314                     ? FTP_REPLY_COMMAND_NOT_IMPL_PARAM
  315                     : FTP_REPLY_COMMAND_NOT_IMPLEMENTED;
  316         }
  317 
  318         if ((cmd->flags & FTP_NEED_LOGIN) && !fc->logged_in) {
  319             fc->response_notes = "Please log in with USER and PASS";
  320             return FTP_REPLY_NOT_LOGGED_IN;
  321         }
  322         else {
  323             res = ftp_parse2(r->pool, r->the_request,
  324                              &method, &arg, cmd->flags & FTP_KEEP_WHITESPACE);
  325             if (res || (!(cmd->flags & FTP_TAKE0) && !*arg)
  326                     || (!(cmd->flags & FTP_TAKE1) && *arg))
  327                 return FTP_REPLY_SYNTAX_ERROR;
  328 
  329             return ftp_run_handler(r, cmd, arg);
  330         }
  331     }
  332 
  333     return FTP_REPLY_COMMAND_UNRECOGNIZED;
  334 }
  335 
  336 int ftp_cmd_abort_data(const char *key)
  337 {
  338     ftp_cmd_entry *cmd;
  339 
  340     cmd = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
  341 
  342     /* Return true if cmd should abort an active data connection */
  343     return (cmd && (cmd->flags & FTP_DATA_INTR));
  344 }
  345 
  346 /* Begin definition of our command handlers. */
  347 static int ftp_cmd_abor(request_rec *r, const char *arg)
  348 {
  349     r->the_request = apr_pstrdup(r->pool, "ABOR");
  350     r->method = apr_pstrdup(r->pool, "ABOR");
  351     apr_table_setn(r->subprocess_env, "ftp_transfer_ok", "0");
  352     return FTP_REPLY_TRANSFER_ABORTED;
  353 }
  354 
  355 static int ftp_cmd_auth(request_rec *r, const char *arg)
  356 {
  357     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  358 
  359     if (!ftp_have_ssl() || (!fc->ssl_input_ctx || !fc->ssl_output_ctx)) {
  360         fc->response_notes = "AUTH mechanism not available";
  361         return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
  362     }
  363 
  364     /*
  365      * RFC 2228 states these arguments are case insensitive.
  366      * draft-murray-auth-ftp-ssl-06.txt defined these 4 AUTH mechanisms. TLS
  367      * or TLS-C  will encrypt the control connection, leaving the data
  368      * channel clear.  SSL or TLS-P will encrypt both the control and data
  369      * connections. As it evolved to publication, all but "TLS" were dropped
  370      * from RFC4217, as RFC 2228 previously insisted that PROT defaults to
  371      * 'C'lear text.
  372      */
  373     if ((strcasecmp(arg, "SSL") == 0) ||
  374         (strcasecmp(arg, "TLS-P") == 0)) {
  375 
  376         fc->prot = FTP_PROT_PRIVATE;
  377         fc->auth = FTP_AUTH_SSL;
  378     }
  379     else if ((strcasecmp(arg, "TLS") == 0) ||
  380              (strcasecmp(arg, "TLS-C") == 0)) {
  381 
  382         fc->prot = FTP_PROT_CLEAR;
  383         fc->auth = FTP_AUTH_TLS;
  384     }
  385     else {
  386         fc->response_notes = "AUTH mechanism not supported";
  387         return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
  388     }
  389 
  390     return FTP_REPLY_SECURITY_EXCHANGE_DONE;
  391 }
  392 
  393 /* XXX: RPM 6/11/2002
  394  * This needs rewriting.  Too many special cases.
  395  */
  396 static int ftp_change_dir(request_rec *r, const char *arg)
  397 {
  398     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  399     ftp_dir_config *dconf;
  400     request_rec *rr;
  401     conn_rec *c = r->connection;
  402     int res, response;
  403 
  404     if ((res = ftp_set_uri(r, arg))) {
  405         return res;
  406     }
  407 
  408     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
  409 
  410     if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
  411         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
  412                                  ftp_escape_control_text(r->parsed_uri.path,
  413                                                          r->pool));
  414         ap_destroy_sub_req(rr);
  415         return FTP_REPLY_FILE_NOT_FOUND;
  416     }
  417 
  418     dconf = ftp_get_module_config(rr->per_dir_config);
  419 
  420     /* Handle special case where the URI is / */
  421     if (r->uri[0] == '/' && !r->uri[1]) {
  422         apr_cpystrn(fc->cwd, r->uri, APR_PATH_MAX + 1);
  423 
  424         if (dconf->readme) {
  425 
  426             /*
  427              * We do not inherit readme messages.  If this readme was not
  428              * specifically meant for us, we skip it.  The exception to the
  429              * rule is if FTPReadmeMessage was placed in the global server
  430              * configuration.
  431              */
  432             if (!dconf->path ||
  433                 !strncmp(dconf->path, r->filename,
  434                          strlen(r->filename) - 1)) {
  435 
  436                 if (dconf->readme_isfile) {
  437                     ftp_show_file(c->output_filters, r->pool,
  438                                   FTP_REPLY_COMPLETED, fc,
  439                                   dconf->readme);
  440                 }
  441                 else {
  442                     char outbuf[BUFSIZ];
  443 
  444                     ftp_message_generate(fc, dconf->readme, outbuf,
  445                                          sizeof(outbuf));
  446                     ftp_reply(fc, c->output_filters, r->pool,
  447                               FTP_REPLY_COMPLETED, 1, outbuf);
  448                 }
  449             }
  450         }
  451 
  452         ap_destroy_sub_req(rr);
  453         return FTP_REPLY_COMPLETED;
  454     }
  455 
  456 
  457     /* Check access permissions for the new directory. */
  458     if (!((rr->status == HTTP_OK) || (rr->status == HTTP_MOVED_PERMANENTLY))) {
  459         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
  460                                  ftp_escape_control_text(r->parsed_uri.path,
  461                                                          r->pool));
  462         ap_destroy_sub_req(rr);
  463         return FTP_REPLY_FILE_NOT_FOUND;
  464     }
  465 
  466     if (rr->finfo.filetype != 0) {
  467         if (rr->finfo.filetype != APR_DIR) {
  468             fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOT_A_DIR,
  469                                      ftp_escape_control_text(r->parsed_uri.path,
  470                                                              r->pool));
  471             response = FTP_REPLY_FILE_NOT_FOUND;
  472         }
  473         else {
  474             apr_cpystrn(fc->cwd, r->parsed_uri.path, APR_PATH_MAX + 1);
  475 
  476             if (dconf->readme) {
  477                 /*
  478                  * We do not inherit readme messages.  If this readme was not
  479                  * specifically meant for us, we skip it.  The exception to
  480                  * the rule is if FTPReadmeMessage was placed in the global
  481                  * server configuration.
  482                  */
  483                 if (!dconf->path ||
  484                     !strncmp(dconf->path, r->filename,
  485                              strlen(r->filename) - 1)) {
  486 
  487                     if (dconf->readme_isfile) {
  488                         ftp_show_file(c->output_filters, r->pool,
  489                                       FTP_REPLY_COMPLETED, fc,
  490                                       dconf->readme);
  491                     }
  492                     else {
  493                         char outbuf[BUFSIZ];
  494 
  495                         ftp_message_generate(fc, dconf->readme, outbuf,
  496                                              sizeof(outbuf));
  497                         ftp_reply(fc, c->output_filters, r->pool,
  498                                   FTP_REPLY_COMPLETED, 1, outbuf);
  499                     }
  500                 }
  501             }
  502             response = FTP_REPLY_COMPLETED;
  503         }
  504     }
  505     else {
  506         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
  507                                  ftp_escape_control_text(r->parsed_uri.path,
  508                                                          r->pool));
  509         response = FTP_REPLY_FILE_NOT_FOUND;
  510     }
  511 
  512     ap_destroy_sub_req(rr);
  513     return response;
  514 }
  515 
  516 static int ftp_cmd_cdup(request_rec *r, const char *arg)
  517 {
  518     return ftp_change_dir(r, "..");
  519 }
  520 
  521 static int ftp_cmd_cwd(request_rec *r, const char *arg)
  522 {
  523     return ftp_change_dir(r, arg);
  524 }
  525 
  526 static int ftp_cmd_dele(request_rec *r, const char *arg)
  527 {
  528     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  529     request_rec *rr;
  530     apr_status_t rv;
  531     int res, response;
  532 
  533     if ((res = ftp_set_uri(r, arg))) {
  534         return res;
  535     }
  536 
  537     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
  538 
  539     /*
  540      * If the user does not have permission to view the file, do not let them
  541      * delete it.
  542      */
  543     if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
  544         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
  545                                  ftp_escape_control_text(r->parsed_uri.path,
  546                                                          r->pool));
  547         response = FTP_REPLY_FILE_NOT_FOUND;
  548     }
  549     /* We do have permission, and the file exists.  Try to remove. */
  550     else if (rr->finfo.filetype == APR_DIR) {
  551 
  552         rv = apr_dir_remove(r->filename, r->pool);
  553 
  554         if (rv != APR_SUCCESS) {
  555             char error_str[120];
  556             char *err = apr_strerror(rv, error_str, sizeof(error_str));
  557             fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
  558                                      ftp_escape_control_text(err, r->pool));
  559             response = FTP_REPLY_FILE_NOT_FOUND;
  560         }
  561         else {
  562             response = FTP_REPLY_COMPLETED;
  563         }
  564     }
  565     else if (rr->finfo.filetype == APR_REG) {
  566 
  567         rv = apr_file_remove(r->filename, r->pool);
  568 
  569         if (rv != APR_SUCCESS) {
  570             /* Call to apr_file_remove failed */
  571             char error_str[120];
  572             char *err = apr_strerror(rv, error_str, sizeof(error_str));
  573             fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
  574                                      ftp_escape_control_text(err, r->pool));
  575             response = FTP_REPLY_FILE_NOT_FOUND;
  576         }
  577         else {
  578             response = FTP_REPLY_COMPLETED;
  579         }
  580     }
  581     else {
  582         /* File does not exist */
  583         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
  584                                  ftp_escape_control_text(r->parsed_uri.path,
  585                                                          r->pool));
  586         response = FTP_REPLY_FILE_NOT_FOUND;
  587     }
  588     ap_destroy_sub_req(rr);
  589     return response;
  590 }
  591 
  592 static int ftp_cmd_feat(request_rec *r, const char *arg)
  593 {
  594     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  595     conn_rec *c = r->connection;
  596     apr_bucket_brigade *bb;
  597     apr_bucket *b;
  598 
  599     bb = apr_brigade_create(r->pool, c->bucket_alloc);
  600     b = apr_bucket_immortal_create(FTPFeatText, FTPFeatTextLen, c->bucket_alloc);
  601     APR_BRIGADE_INSERT_TAIL(bb, b);
  602     fc->traffic += FTPFeatTextLen;
  603 
  604     b = apr_bucket_flush_create(c->bucket_alloc);
  605     APR_BRIGADE_INSERT_TAIL(bb, b);
  606     ap_pass_brigade(c->output_filters, bb);
  607 
  608     fc->response_notes = "End";
  609     return FTP_REPLY_SYSTEM_STATUS;
  610 }
  611 
  612 static int ftp_cmd_help(request_rec *r, const char *arg)
  613 {
  614     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  615     conn_rec *c = r->connection;
  616     apr_bucket_brigade *bb;
  617     apr_bucket *b;
  618     ftp_cmd_entry *cmd;
  619     char *method;
  620 
  621     if (*arg) {
  622         method = ftp_toupper(r->pool, arg);
  623         cmd = apr_hash_get(FTPMethodHash, method, APR_HASH_KEY_STRING);
  624 
  625         if (cmd) {
  626             fc->response_notes = apr_psprintf(r->pool, FTP_MSG_HELP_SYNTAX,
  627                                               arg, cmd->help);
  628             return FTP_REPLY_HELP_MESSAGE;
  629         }
  630         else {
  631             fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOTIMPL,
  632                                      ftp_escape_control_text(arg, r->pool));
  633             return FTP_REPLY_COMMAND_NOT_IMPLEMENTED;
  634         }
  635     }
  636 
  637     /* given no argument, pre-prepared HELP message */
  638     bb = apr_brigade_create(r->pool, c->bucket_alloc);
  639     b = apr_bucket_immortal_create(FTPHelpText, FTPHelpTextLen, c->bucket_alloc);
  640     APR_BRIGADE_INSERT_TAIL(bb, b);
  641     fc->traffic += FTPHelpTextLen;
  642 
  643     b = apr_bucket_flush_create(c->bucket_alloc);
  644     APR_BRIGADE_INSERT_TAIL(bb, b);
  645     ap_pass_brigade(c->output_filters, bb);
  646 
  647     fc->response_notes = apr_psprintf(r->pool, FTP_MSG_HELP,
  648                              ftp_escape_control_text(r->server->server_admin,
  649                                                      r->pool));
  650     return FTP_REPLY_HELP_MESSAGE;
  651 }
  652 
  653 static int common_list(request_rec *r, const char *arg, int is_list)
  654 {
  655     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  656     conn_rec *c = r->connection;
  657     ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
  658     conn_rec *cdata;
  659     request_rec *rr;
  660     apr_bucket_brigade *bb;
  661     apr_bucket *b;
  662     struct ftp_direntry *direntry, *dirp;
  663     char *word, *buf = NULL, *pattern;
  664     int dashl = 0;
  665     apr_status_t rv;
  666     apr_size_t nbytes;
  667     char *varg = apr_pstrdup(r->pool, arg);
  668     const char *test, *sl;
  669     int res;
  670     int decend = 0;
  671 
  672     while (*varg == '-')
  673     {
  674         if (ftp_parse2(r->pool, varg, &word, &varg, FTP_KEEP_WHITESPACE)) {
  675             varg = word;
  676             break;
  677         }
  678         /* More Cowbell!  TODO: expand the accepted dash patterns */
  679         if (ap_strchr(word, 'l')) {
  680             dashl = 1;
  681         }
  682         /* -- 'end of dash-opts' by convention allows patterns like '-*' */
  683         if (ap_strchr(word + 1, '-')) {
  684             break;
  685         }
  686     }
  687 
  688     /* Special FTPOption that maps NLST directly to LIST */
  689     if (is_list && (fsc->options & FTP_OPT_LISTISNLST) && !dashl) {
  690         is_list = 0;
  691     }
  692     else
  693     /* Special FTPOption that maps NLST directly to LIST */
  694     if (!is_list && ((fsc->options & FTP_OPT_NLSTISLIST) || dashl)) {
  695         is_list = 1;
  696     }
  697 
  698     arg = varg;
  699 
  700     if (is_list && (ap_strchr_c(arg, '*') != NULL))
  701     {
  702         /* Prevent DOS attacks, only allow one segment to have a wildcard */
  703         int found = 0;          /* The number of segments with a wildcard */
  704         const char *pos = arg;  /* Pointer for iterating over the string */
  705 
  706         while (*pos != '\0') {
  707             if (*pos == '*') {
  708                 /*
  709                  * We found a wildcard in this segment.  Increment the count
  710                  * and move the pointer to the beginning of the next segment
  711                  */
  712                 found++;
  713                 while (*pos != '\0' && *pos != '/') {
  714                     pos++;
  715                 }
  716             }
  717             else {
  718                 /* Nothing here, move on */
  719                 pos++;
  720             }
  721         }
  722 
  723         /* In the future this can be configurable */
  724         if (found > 1) {
  725             ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, 0,
  726                          "Ignoring directory listing request for %s", arg);
  727             fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
  728                                      ftp_escape_control_text(arg, r->pool));
  729             return FTP_REPLY_FILE_NOT_FOUND;
  730         }
  731     }
  732 
  733     if ((res = ftp_set_uri(r, arg))) {
  734         return res;
  735     }
  736 
  737     /* Need to run intermediate subrequest to check for redirect */
  738     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
  739     if (rr->status == HTTP_MOVED_PERMANENTLY)
  740     {
  741         ap_parse_uri(r, apr_pstrcat(r->pool, rr->uri, "/", NULL));
  742 
  743         /* Rerun the subrequest with the new uri */
  744         ap_destroy_sub_req(rr);
  745         rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
  746     }
  747 
  748     if (rr->status != HTTP_OK) {
  749         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
  750                                  ftp_escape_control_text(r->parsed_uri.path,
  751                                                          r->pool));
  752         ap_destroy_sub_req(rr);
  753         return FTP_REPLY_FILE_NOT_FOUND;
  754     }
  755 
  756     ap_destroy_sub_req(rr);
  757 
  758     if (arg[0] == '\0') {
  759         pattern = apr_pstrcat(r->pool, r->filename, "/*", NULL);
  760     }
  761     else {
  762         pattern = r->filename;
  763     }
  764 
  765 #ifdef FTP_DEBUG
  766     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Pattern: %s", pattern);
  767 #endif
  768 
  769     /* Construct the sorted array of directory contents */
  770     if ((direntry = ftp_direntry_get(r, pattern)) == NULL) {
  771         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
  772                                  ftp_escape_control_text(arg, r->pool));
  773         return FTP_REPLY_FILE_NOT_FOUND;
  774     }
  775 
  776     fc->response_notes = FTP_MSG_OPENASCII;
  777     ftp_send_response(r, FTP_REPLY_FILE_STATUS_OK);
  778 
  779     if (!(cdata = ftp_open_dataconn(r, 1))) {
  780         return FTP_REPLY_CANNOT_OPEN_DATACONN;
  781     }
  782 
  783     if (is_list)
  784     {
  785         /*
  786          * We have the directory tree, and its time to print it to the client.
  787          * At this point we could have one of three things.
  788          *
  789          * 1. One entry, a directory.  Just print the contents
  790          * 2. No directories.  Just print the contents.
  791          * 3. Mixture.  Print files first, then directories.
  792          */
  793 
  794         /* Handle case 1 */
  795         if (direntry->child && !direntry->next) {
  796             direntry = direntry->child;
  797         }
  798         else {
  799             /* Handle case 2 & 3 */
  800             for (dirp = direntry; dirp; dirp = dirp->next) {
  801                 if (dirp->child)
  802                     decend = 1;
  803             }
  804         }
  805     }
  806 
  807     bb = apr_brigade_create(r->pool, c->bucket_alloc);
  808 
  809     /* Print the directory listing, skipping directories as needed */
  810     for (dirp = direntry; dirp; dirp = dirp->next)
  811     {
  812         if (dirp->modestring[0] == 'd')
  813         {
  814             if (is_list && decend) 
  815                 continue;
  816             else if (!is_list && !(fsc->options & FTP_OPT_NLSTSHOWDIRS))
  817                 continue;
  818         }
  819 
  820         for (test = dirp->name; (sl = ap_strchr_c(test, '/')); test = sl + 1)
  821              /* noop */ ;
  822 
  823         if (!strcmp(".", test)) {
  824             continue;
  825         }
  826 
  827         if (is_list)
  828             buf = apr_psprintf(r->pool,
  829                                "%10s%5d %-8s %-8s%9" APR_OFF_T_FMT " %s %s"
  830                                CRLF, dirp->modestring, dirp->nlink,
  831                                dirp->username, dirp->groupname,
  832                                dirp->size, dirp->datestring, dirp->name);
  833         else
  834             buf = apr_psprintf(r->pool, "%s" CRLF, dirp->name);
  835 
  836         nbytes = strlen(buf);
  837         rv = apr_brigade_write(bb, ap_filter_flush, cdata->output_filters,
  838                                buf, nbytes);
  839         fc->traffic += nbytes;
  840     }
  841 
  842     if (is_list && decend) {
  843         for (dirp = direntry; dirp; dirp = dirp->next) {
  844             if (dirp->modestring[0] == 'd' && dirp->child)
  845             {
  846                 ftp_direntry *decend;
  847 
  848                 /* Iterate through the sub directory */
  849                 buf = apr_psprintf(r->pool, CRLF "%s:" CRLF, dirp->name);
  850                 nbytes = strlen(buf);
  851                 rv = apr_brigade_write(bb, ap_filter_flush,
  852                                        cdata->output_filters, buf, nbytes);
  853                 fc->traffic += nbytes;
  854 
  855                 buf = apr_pstrcat(r->pool, "total 0" CRLF, NULL);
  856                 nbytes = strlen(buf);
  857                 rv = apr_brigade_write(bb, ap_filter_flush,
  858                                        cdata->output_filters, buf, nbytes);
  859                 fc->traffic += nbytes;
  860 
  861                 for (decend = dirp->child; decend; decend = decend->next)
  862                 {
  863                     for (test = dirp->name; (sl = ap_strchr_c(test, '/'));
  864                          test = sl + 1)
  865                          /* noop */ ;
  866 
  867                     if (!strcmp(".", test)) {
  868                         continue;
  869                     }
  870 
  871                     buf = apr_psprintf(r->pool, "%10s%5d %-8s %-8s%9"
  872                                        APR_OFF_T_FMT " %s %s" CRLF,
  873                                        decend->modestring, decend->nlink,
  874                                        decend->username, decend->groupname,
  875                                        decend->size, decend->datestring,
  876                                        decend->name);
  877                     nbytes = strlen(buf);
  878                     rv = apr_brigade_write(bb, ap_filter_flush,
  879                                            cdata->output_filters, buf,
  880                                            nbytes);
  881                     fc->traffic += nbytes;
  882                 }
  883             }
  884         }
  885     }
  886 
  887     /* If the brigade is empty, just send an eos */
  888     if (APR_BRIGADE_EMPTY(bb)) {
  889         b = apr_bucket_eos_create(c->bucket_alloc);
  890         APR_BRIGADE_INSERT_TAIL(bb, b);
  891     }
  892 
  893     /* Flush the brigade down the filter chain */
  894     b = apr_bucket_flush_create(cdata->bucket_alloc);
  895     APR_BRIGADE_INSERT_TAIL(bb, b);
  896     ap_pass_brigade(cdata->output_filters, bb);
  897 
  898     ap_lingering_close(cdata);
  899     fc->datasock = NULL;
  900     fc->transfers++;
  901 
  902     if (cdata->aborted)
  903         return FTP_REPLY_TRANSFER_ABORTED;
  904     else
  905         return FTP_REPLY_DATA_CLOSE;
  906 }
  907 
  908 static int ftp_cmd_list(request_rec *r, const char *arg)
  909 {
  910     return common_list(r, arg, 1);
  911 }
  912 
  913 static int ftp_cmd_nlst(request_rec *r, const char *arg)
  914 {
  915     return common_list(r, arg, 0);
  916 }
  917 
  918 static int ftp_cmd_mdtm(request_rec *r, const char *arg)
  919 {
  920     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  921     int res;
  922     request_rec *rr;
  923     apr_time_exp_t t;
  924 
  925     if ((res = ftp_set_uri(r, arg))) {
  926         return res;
  927     }
  928 
  929     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
  930 
  931     if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
  932         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
  933                                  ftp_escape_control_text(r->parsed_uri.path,
  934                                                          r->pool));
  935         ap_destroy_sub_req(rr);
  936         return FTP_REPLY_FILE_NOT_FOUND;
  937     }
  938     apr_time_exp_lt(&t, rr->finfo.mtime);
  939     fc->response_notes = apr_psprintf(r->pool,
  940                                       "%04d%02d%02d%02d%02d%02d",
  941                                       1900 + t.tm_year, t.tm_mon + 1,
  942                                       t.tm_mday, t.tm_hour, t.tm_min,
  943                                       t.tm_sec);
  944     ap_destroy_sub_req(rr);
  945     return FTP_REPLY_FILE_STATUS;
  946 }
  947 
  948 static int ftp_cmd_mkd(request_rec *r, const char *arg)
  949 {
  950     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
  951     apr_status_t rv;
  952     int res;
  953     request_rec *rr;
  954     ftp_dir_config *dconf;
  955     apr_fileperms_t dirperms;
  956 
  957     if ((res = ftp_set_uri(r, arg))) {
  958         return res;
  959     }
  960 
  961     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
  962 
  963     if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
  964         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
  965                                  ftp_escape_control_text(r->parsed_uri.path,
  966                                                          r->pool));
  967         ap_destroy_sub_req(rr);
  968         return FTP_REPLY_FILE_NOT_FOUND;
  969     }
  970 
  971     dconf = ftp_get_module_config(rr->per_dir_config);
  972     dirperms = dconf->dirperms;
  973     ap_destroy_sub_req(rr);
  974 
  975     if (dirperms == APR_OS_DEFAULT)
  976         dirperms = FTP_DEFAULT_UMASK;
  977 
  978     /*
  979      * In the config phase, ->fileperms was a negative umask. for operation,
  980      * exchange this with a positive protections to pass to the apr_dir_make
  981      * protection flag.
  982      */
  983     dirperms = (APR_UREAD | APR_UWRITE | APR_UEXECUTE |
  984                 APR_GREAD | APR_GWRITE | APR_GEXECUTE |
  985                 APR_WREAD | APR_WWRITE | APR_WEXECUTE)
  986         & ~dirperms;
  987     rv = apr_dir_make(r->filename, dirperms, r->pool);
  988 
  989     if (rv != APR_SUCCESS) {
  990         char error_str[120];
  991         char *err = apr_strerror(rv, error_str, sizeof(error_str));
  992         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED, 
  993                                  ftp_escape_control_text(err, r->pool));
  994         return FTP_REPLY_FILE_NOT_FOUND;
  995     }
  996     else {
  997         apr_file_perms_set(r->filename, dirperms);
  998         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_DIR_CREAT,
  999                                  ftp_escape_control_text(r->parsed_uri.path,
 1000                                                          r->pool));
 1001         return FTP_REPLY_PATH_CREATED;
 1002     }
 1003 }
 1004 
 1005 static int ftp_cmd_mode(request_rec *r, const char *arg)
 1006 {
 1007     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1008 
 1009     if (*arg && !arg[1]) {
 1010         switch (toupper(*arg)) {
 1011         case 'S':
 1012             fc->response_notes = "Mode set to S";
 1013             return FTP_REPLY_COMMAND_OK;
 1014         }
 1015     }
 1016     fc->response_notes = apr_psprintf(r->pool, "Mode %s not implemented", 
 1017                              ftp_escape_control_text(arg, r->pool));
 1018     return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
 1019 }
 1020 
 1021 static int ftp_cmd_noop(request_rec *r, const char *arg)
 1022 {
 1023     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1024 
 1025     fc->response_notes = apr_psprintf(r->pool, FTP_MSG_SUCCESS, r->method);
 1026     return FTP_REPLY_COMMAND_OK;
 1027 }
 1028 
 1029 static int ftp_cmd_pass(request_rec *r, const char *arg)
 1030 {
 1031     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1032     conn_rec *c = r->connection;
 1033     ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
 1034     core_server_config *ftpcore = NULL;
 1035     char *userdir = NULL;
 1036     ftp_dir_config *dconf;
 1037     request_rec *rr;
 1038     char *userpass;
 1039     server_rec *ftpserver;
 1040     apr_status_t rv;
 1041     char *tmppath;
 1042 
 1043     if (fc->user == ftp_unknown_username) {
 1044         return FTP_REPLY_BAD_SEQUENCE;
 1045     }
 1046 
 1047     apr_cpystrn(fc->cwd, "/", APR_PATH_MAX + 1);
 1048 
 1049     /*
 1050      * By this point we have already extracted the arguments, so rewrite the
 1051      * original request to not include the password
 1052      */
 1053     r->the_request = apr_pstrdup(r->pool, "PASS xxx");
 1054 
 1055     userpass = apr_psprintf(r->pool, "%s:%s", fc->user, arg);
 1056     fc->authorization = apr_psprintf(fc->login_pool, "Basic %s",
 1057                                      ap_pbase64encode(r->pool, userpass));
 1058 
 1059     /*
 1060      * This is normally set in the dispatcher, but since we just read the
 1061      * password, we need to reset the auth header and r->user
 1062      */
 1063     ftp_set_authorization(r);
 1064 
 1065     /* Prepare to overwrite ap_document_root */
 1066     if (fsc->jailuser || fsc->docrootenv) {
 1067         int i;
 1068         ap_conf_vector_t *conf_vector;
 1069         module *modp;
 1070 
 1071         ftpserver = apr_pcalloc(fc->login_pool, sizeof(*ftpserver));
 1072         memcpy(ftpserver, r->server, sizeof(*ftpserver));
 1073 
 1074         /*
 1075          * We need to count the number of modules in order to know how big an
 1076          * array to allocate.  There is a variable in the server for this,
 1077          * but it isn't exported, so we are stuck doing this.  I guess this
 1078          * could be a static variable that we compute once, but it shouldn't
 1079          * be that expensive to do it this way.
 1080          */
 1081         for (i = 0; ap_loaded_modules[i]; i++);
 1082         conf_vector = apr_pcalloc(fc->login_pool, sizeof(void *) * i);
 1083 
 1084         for (modp = ap_top_module; modp; modp = modp->next) {
 1085             /*
 1086              * This is a hack.  Basically, to keep the user in thier own
 1087              * directory, we are re-writing the DocumentRoot when the user
 1088              * logs in.  To do this, we copy the entire server_rec for this
 1089              * Vhost, and then we copy the whole module_config for that
 1090              * server.  For the core module, we don't just copy the pointer,
 1091              * instead we create a new core_server_config, copy the old one
 1092              * to it, and change the docroot.  Yuck.
 1093              */
 1094             if (modp == &core_module) {
 1095                 core_server_config *core;
 1096 
 1097                 ftpcore = apr_pcalloc(fc->login_pool, sizeof(*ftpcore));
 1098                 core = ap_get_module_config(r->server->module_config, modp);
 1099                 *ftpcore = *core;
 1100                 ap_set_module_config(conf_vector, modp, ftpcore);
 1101             }
 1102             else {
 1103                 ap_set_module_config(conf_vector, modp,
 1104                       ap_get_module_config(r->server->module_config, modp));
 1105             }
 1106         }
 1107         ftpserver->module_config = (ap_conf_vector_t *) conf_vector;
 1108         fc->orig_server = r->server = ftpserver;
 1109     }
 1110 
 1111     if (fsc->docrootenv) {
 1112         const char *docroot;
 1113 
 1114         /*
 1115          * Invoke a request that forces auth to occur, Then check for an
 1116          * fsc->docrootenv that translates to a valid envvar.
 1117          */
 1118         rr = ap_sub_req_method_uri(r->method, "/", r, NULL);
 1119         docroot = apr_table_get(rr->subprocess_env, fsc->docrootenv);
 1120 
 1121         if (!docroot || !*docroot) {
 1122             ap_log_perror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0,
 1123                           r->pool,
 1124                   "Warning: Document Root variable from FTPDocRootEnv [%s] "
 1125                        "was empty or not found, using default DocumentRoot",
 1126                           fsc->docrootenv);
 1127         }
 1128         else if (((rv = apr_filepath_merge(&tmppath, NULL, docroot,
 1129                                            APR_FILEPATH_TRUENAME |
 1130                                            APR_FILEPATH_NOTRELATIVE,
 1131                                            fc->login_pool)) != APR_SUCCESS)
 1132                  || (rv = APR_ENOTDIR, !ap_is_directory(r->pool, tmppath))) {
 1133             ap_log_perror(APLOG_MARK, APLOG_WARNING, rv,
 1134                           r->pool,
 1135                       "Warning: Document Root [%s] from FTPDocRootEnv [%s] "
 1136                  "is invalid or does not exist, using default DocumentRoot",
 1137                           docroot, fsc->docrootenv);
 1138         }
 1139         else {
 1140             ftpcore->ap_document_root = tmppath;
 1141         }
 1142 
 1143         ap_destroy_sub_req(rr);
 1144     }
 1145 
 1146     /* Check for a configured home directory */
 1147     if (fsc->homedir) {
 1148 
 1149         apr_filepath_merge(&userdir, fsc->homedir, fc->user, 0, r->pool);
 1150 
 1151         if (userdir) {
 1152 
 1153             rr = ap_sub_req_method_uri(r->method, userdir, r, NULL);
 1154 
 1155             if (rr->finfo.filetype == APR_DIR &&
 1156                 (rr->status == HTTP_OK ||
 1157                  rr->status == HTTP_MOVED_PERMANENTLY)) {
 1158                 tmppath = apr_pstrcat(r->pool, userdir, "/", NULL);
 1159                 apr_cpystrn(fc->cwd, tmppath, APR_PATH_MAX + 1);
 1160             }
 1161             else if (rr->status == HTTP_OK &&
 1162                      rr->finfo.filetype == APR_NOFILE) {
 1163 
 1164                 /* See if we should create the directory automatically */
 1165                 if (fsc->options & FTP_OPT_CREATEHOMEDIRS) {
 1166 
 1167                     if (rr->finfo.filetype == APR_NOFILE && rr->filename
 1168                         && ap_is_directory(rr->pool,
 1169                            ap_make_dirstr_parent(rr->pool, rr->filename))) {
 1170 
 1171                         rv = apr_dir_make(rr->filename, APR_OS_DEFAULT,
 1172                                           r->pool);
 1173 
 1174                         if (rv != APR_SUCCESS) {
 1175                             ap_log_error(APLOG_MARK, APLOG_ERR, rv,
 1176                                          r->server, "Couldn't "
 1177                                          "create home directory (%s) for "
 1178                                          "user %s",
 1179                                          rr->filename, fc->user);
 1180                         }
 1181                         else {
 1182                             /* The new directory was created. */
 1183                             tmppath = apr_pstrcat(r->pool, userdir, "/", NULL);
 1184                             apr_cpystrn(fc->cwd, tmppath, APR_PATH_MAX + 1);
 1185                             ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO,
 1186                                          0, r->server, "Created home "
 1187                                          "directory (%s) for user %s",
 1188                                          rr->filename, fc->user);
 1189                         }
 1190                     }
 1191                     else {
 1192                         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO,
 1193                                      0, r->server,
 1194                                      "Home directory for user %s not "
 1195                                      "found.  Using \"/\"", fc->user);
 1196 
 1197                     }
 1198                 }
 1199                 else {
 1200                     ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0,
 1201                                  r->server, "Home directory for "
 1202                                  "user %s not found.  Using \"/\"",
 1203                                  fc->user);
 1204                 }
 1205             }
 1206             else {
 1207 
 1208                 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0,
 1209                              r->server, "Permission denied entering"
 1210                              " home directory for user %s.  Using \"/\"",
 1211                              fc->user);
 1212             }
 1213 
 1214             ap_destroy_sub_req(rr);
 1215         }
 1216     }
 1217 
 1218     if (fsc->jailuser) {
 1219         /*
 1220          * If we didn't set cwd the Jailed user can't be allowed to log in.
 1221          * The cwd was set to userdir if it existed or was created above, but
 1222          * we must ignore the trailing slash.
 1223          */
 1224         if (!userdir || (strncmp(userdir, fc->cwd, strlen(userdir)) != 0)) {
 1225             goto pass_try_again;
 1226         }
 1227         ftpcore->ap_document_root = apr_pstrcat(fc->login_pool,
 1228                                   ftpcore->ap_document_root, userdir, NULL);
 1229         apr_cpystrn(fc->cwd, "/", APR_PATH_MAX + 1);
 1230     }
 1231 
 1232     /* Our document_root and cwd are now constructed for the user... */
 1233     rr = ap_sub_req_method_uri(r->method, fc->cwd, r, NULL);
 1234 
 1235     dconf = ftp_get_module_config(rr->per_dir_config);
 1236 
 1237     if (rr->status == HTTP_OK) {
 1238         /*
 1239          * We now must iterate over the entire scoreboard checking to see if
 1240          * we have another server that can handle an incoming request. If we
 1241          * don't see another server in the ready state, that means we are the
 1242          * last available server, and must send the client a message that the
 1243          * server is full.
 1244          * 
 1245          * For those wondering, there is a race condition here that could cause
 1246          * a client to be put in the accept queue, but we should be able to
 1247          * recover from this once a client disconnects.
 1248          * 
 1249          * This has a few side effects: - We can really only service Max - 1 FTP
 1250          * sessions concurrently.
 1251          * 
 1252          * - If a hybid server is heavily loaded, such that all servers are busy
 1253          * serving HTTP traffic, it is possible to starve FTP requests, since
 1254          * when we check the scoreboard, all servers will be busy.
 1255          */
 1256         ftp_loginlimit_t limrv;
 1257 
 1258         if ((fsc->options & FTP_OPT_CHECKMAXCLIENTS) &&
 1259             ftp_check_maxclients(r)) {
 1260             ap_destroy_sub_req(rr);
 1261             fc->response_notes = FTP_MSG_SESSIONLIMIT;
 1262             fc->close_connection = 1;
 1263 
 1264             return FTP_REPLY_SERVICE_NOT_AVAILABLE;
 1265         }
 1266 
 1267         limrv = ftp_limitlogin_check(fc->user, r);
 1268         /* I love switch */
 1269         switch (limrv) {
 1270         case FTP_LIMIT_HIT_PERSERVER:
 1271             ap_destroy_sub_req(rr);
 1272             ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
 1273                          r->server, "FTP per server login limit hit for %s",
 1274                          fc->user);
 1275 
 1276             fc->response_notes = FTP_MSG_SESSIONLIMIT;
 1277             fc->close_connection = 1;
 1278             return FTP_REPLY_SERVICE_NOT_AVAILABLE;
 1279 
 1280         case FTP_LIMIT_HIT_PERUSER:
 1281             ap_destroy_sub_req(rr);
 1282             ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
 1283                          r->server, "FTP per user login limit hit for %s",
 1284                          fc->user);
 1285 
 1286             fc->response_notes = FTP_MSG_SESSIONLIMIT;
 1287             fc->close_connection = 1;
 1288             return FTP_REPLY_SERVICE_NOT_AVAILABLE;
 1289 
 1290         case FTP_LIMIT_HIT_PERIP:
 1291             ap_destroy_sub_req(rr);
 1292             ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
 1293                          r->server, "FTP per IP login limit hit for %s",
 1294                          fc->user);
 1295 
 1296             fc->response_notes = FTP_MSG_SESSIONLIMIT;
 1297             fc->close_connection = 1;
 1298             return FTP_REPLY_SERVICE_NOT_AVAILABLE;
 1299 
 1300         case FTP_LIMIT_ERROR:
 1301             ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
 1302                          r->server, "Error accessing DB for user %s; no login limit in effect",
 1303                          fc->user);
 1304             break;
 1305 
 1306         case FTP_LIMIT_OK:
 1307         default:
 1308             break;
 1309         }
 1310 
 1311         ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
 1312                      r->server, "FTP LOGIN FROM %s as %s",
 1313                      c->remote_ip, fc->user);
 1314 
 1315         if (dconf->readme) {
 1316             if (dconf->readme_isfile) {
 1317                 ftp_show_file(c->output_filters, r->pool,
 1318                               FTP_REPLY_USER_LOGGED_IN, fc,
 1319                               dconf->readme);
 1320             }
 1321             else {
 1322                 char outbuf[BUFSIZ];
 1323 
 1324                 ftp_message_generate(fc, dconf->readme, outbuf,
 1325                                      sizeof(outbuf));
 1326                 ftp_reply(fc, c->output_filters, r->pool,
 1327                           FTP_REPLY_USER_LOGGED_IN, 1, outbuf);
 1328             }
 1329         }
 1330 
 1331         fc->logged_in = 1;
 1332         ap_destroy_sub_req(rr);
 1333         return FTP_REPLY_USER_LOGGED_IN;
 1334     }
 1335 
 1336     ap_destroy_sub_req(rr);
 1337 
 1338     /*
 1339      * Here we return the generic FTP_REPLY_NOT_LOGGED_IN error (530), but it
 1340      * may have been because of a varity of reasons.  If the return status is
 1341      * 401 HTTP_UNAUTHORIZED, then the username/password combination was
 1342      * incorrect.  If we get a HTTP_FORBIDDEN, it is likely that the access
 1343      * checkers failed because the location was configured with 'deny from
 1344      * all'.
 1345      */
 1346     if (strcmp(fc->user, "anonymous") == 0) {
 1347         ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0,
 1348                      r->server,
 1349                      "ANONYMOUS FTP LOGIN REFUSED FROM %s",
 1350                      c->remote_ip);
 1351     }
 1352     else {
 1353         ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0,
 1354                      r->server, "FTP LOGIN REFUSED FROM %s, %s",
 1355                      c->remote_ip, fc->user);
 1356     }
 1357 pass_try_again:
 1358     if (++fc->login_attempts == fsc->max_login_attempts) {
 1359         ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0,
 1360                      r->server, "repeated login failures from %s",
 1361                      c->remote_ip);
 1362 
 1363         fc->response_notes = "Maximum login attempts reached, closing connection.";
 1364         fc->close_connection = 1;
 1365 
 1366         return FTP_REPLY_SERVICE_NOT_AVAILABLE;
 1367     }
 1368 
 1369     /*
 1370      * Sleep one second for every failed login attempt.  This is a common
 1371      * practice among FTP servers to prevent DOS attacks
 1372      */
 1373     apr_sleep(fc->login_attempts * APR_USEC_PER_SEC);
 1374 
 1375     fc->user = ftp_unknown_username;
 1376     fc->authorization = NULL;
 1377     fc->response_notes = "Please log in with USER and PASS";
 1378     return FTP_REPLY_NOT_LOGGED_IN;
 1379 }
 1380 
 1381 static int init_pasv_socket(request_rec *r, int bindfamily,
 1382                                 const char *bindaddr)
 1383 {
 1384     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1385     ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
 1386     apr_sockaddr_t *sa;
 1387     apr_socket_t *s;
 1388     apr_status_t rv;
 1389     apr_port_t port;
 1390     int tries = 0;
 1391 
 1392     rv = apr_sockaddr_info_get(&sa, bindaddr, bindfamily, 0, 0, fc->data_pool);
 1393 
 1394     if (!sa || rv) {
 1395         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 1396                       "Couldn't resolve local socket address %s (%d)"
 1397                       " (ftp, apr or socket stack bug?)",
 1398                       bindaddr, bindfamily);
 1399         return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1400     }
 1401 
 1402 #if APR_MAJOR_VERSION < 1
 1403     rv = apr_socket_create_ex(&s, sa->family, SOCK_STREAM,
 1404                               APR_PROTO_TCP, fc->data_pool);
 1405 #else
 1406     rv = apr_socket_create(&s, sa->family, SOCK_STREAM,
 1407                            APR_PROTO_TCP, fc->data_pool);
 1408 #endif
 1409     if (rv != APR_SUCCESS) {
 1410         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
 1411                      "Couldn't create passive socket");
 1412         return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1413     }
 1414 
 1415     rv = apr_socket_opt_set(s, APR_SO_LINGER,
 1416                             APR_MAX_SECS_TO_LINGER);
 1417     if (rv != APR_SUCCESS) {
 1418         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
 1419                      "Couldn't set APR_SO_LINGER socket option");
 1420         apr_socket_close(s);
 1421         return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1422     }
 1423 
 1424     rv = apr_socket_opt_set(s, APR_SO_REUSEADDR, 1);
 1425     if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
 1426         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
 1427                      "Couldn't set APR_SO_REUSEADDR socket option");
 1428         apr_socket_close(s);
 1429         return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1430     }
 1431 
 1432     /*
 1433      * XXX: should pick an arbitrary port within the acceptable range, but
 1434      * dodge the throttle timer on the first reloop (init tries = -1?)
 1435      */
 1436     port = fsc->pasv_min;
 1437     while (1) {
 1438         /* Magic here when port == 0, accepts an ephemeral port assignment */
 1439         sa->port = port;
 1440         sa->sa.sin.sin_port = htons(port);
 1441         rv = apr_socket_bind(s, sa);
 1442         if (rv == APR_SUCCESS) {
 1443             break;
 1444         }
 1445 
 1446         if (rv != FTP_APR_EADDRINUSE || tries >= FTP_MAX_TRIES) {
 1447             ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
 1448                          "Couldn't bind to passive socket");
 1449             apr_socket_close(s);
 1450             return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1451         }
 1452 
 1453         /*
 1454          * If we are using a range, increment the port, and continue.  If we
 1455          * have reached the top of the range start over at the beginning
 1456          */
 1457         if (port != 0) {
 1458             if (port < fsc->pasv_max) {
 1459                 ++port;
 1460                 continue;
 1461             }
 1462             port = fsc->pasv_min;
 1463             /* Fall through into throttle */
 1464         }
 1465 
 1466         /*
 1467          * Under high load we may have many sockets in TIME_WAIT, causing
 1468          * bind to fail with EADDRINUSE.  In these conditions we throttle the
 1469          * system by sleeping before we attempt to bind again within our
 1470          * acceptable port range
 1471          */
 1472         ++tries;
 1473         ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
 1474                      "Couldn't find port within range for passive "
 1475                    "connection.  Restarting at %d (retry %d)", port, tries);
 1476         apr_sleep(tries * APR_USEC_PER_SEC);
 1477     }
 1478 
 1479     rv = apr_socket_listen(s, 1);
 1480     if (rv != APR_SUCCESS) {
 1481         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
 1482                      "Couldn't listen on passive socket");
 1483         apr_socket_close(s);
 1484         return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1485     }
 1486 
 1487     fc->csock = s;
 1488     fc->passive_created = apr_time_now();
 1489     return 0;
 1490 }
 1491 
 1492 
 1493 static int ftp_cmd_epsv(request_rec *r, const char *arg)
 1494 {
 1495     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1496     conn_rec *c = r->connection;
 1497     ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
 1498     int res;
 1499     apr_sockaddr_t *sa;
 1500     const char *addr;
 1501     int family = 0;
 1502 
 1503     if (strcasecmp(arg, "ALL") == 0) {
 1504         /* A contract to never respond to other data connection methods */
 1505         fc->all_epsv = 1;
 1506         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_SUCCESS, r->method);
 1507         return FTP_REPLY_COMMAND_OK;
 1508     }
 1509 
 1510     ftp_reset_dataconn(fc);
 1511 
 1512     /*
 1513      * Why don't pasv_bindaddr/bindfamily appear below? These directives
 1514      * provide an override which the client is informed of, while EPSV only
 1515      * informs the client of the port to use, not a family, and never an
 1516      * address. FTPEPSVIgnoreFamily should offer sufficient customization.
 1517      */
 1518     if (!*arg || ((arg[0] == '1' || arg[0] == '2') && !arg[1]
 1519                   && fsc->epsv_ignore_family)) {
 1520 #if APR_HAVE_IPV6
 1521         if (c->local_addr->family == AF_INET6 &&
 1522             IN6_IS_ADDR_V4MAPPED((struct in6_addr *)
 1523                                  c->local_addr->ipaddr_ptr)) {
 1524             /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
 1525             addr = c->local_ip;
 1526             family = APR_INET;
 1527         }
 1528         else
 1529 #endif
 1530         {
 1531             addr = c->local_ip;
 1532             family = c->local_addr->family;
 1533         }
 1534     }
 1535     else if (arg[0] == '1' && !arg[1]) {
 1536         if (c->local_addr->family == AF_INET
 1537 #if APR_HAVE_IPV6
 1538             || (c->local_addr->family == AF_INET6 &&
 1539                 IN6_IS_ADDR_V4MAPPED((struct in6_addr *)
 1540                                      c->local_addr->ipaddr_ptr))
 1541 #endif
 1542             ) {
 1543             /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
 1544             addr = c->local_ip;
 1545             family = APR_INET;
 1546         }
 1547         else {
 1548             return FTP_REPLY_BAD_PROTOCOL;
 1549         }
 1550     }
 1551 #if APR_HAVE_IPV6
 1552     else if (arg[0] == '2' && !arg[1]) {
 1553         family = AF_INET6;
 1554         if (c->local_addr->family == AF_INET6 &&
 1555             IN6_IS_ADDR_V4MAPPED((struct in6_addr *)
 1556                                  c->local_addr->ipaddr_ptr)) {
 1557             /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
 1558             addr = c->local_ip;
 1559             family = APR_INET;
 1560         }
 1561         else if (c->local_addr->family == AF_INET6) {
 1562             addr = c->local_ip;
 1563             family = AF_INET6;
 1564         }
 1565         else {
 1566             return FTP_REPLY_BAD_PROTOCOL;
 1567         }
 1568     }
 1569 #endif
 1570     else {
 1571         return FTP_REPLY_BAD_PROTOCOL;
 1572     }
 1573 
 1574     if ((res = init_pasv_socket(r, family, addr))) {
 1575         return res;
 1576     }
 1577 
 1578     apr_socket_addr_get(&sa, APR_LOCAL, fc->csock);
 1579     fc->response_notes = apr_psprintf(r->pool, 
 1580                                       "Entering Extended Passive Mode (|||%u|)",
 1581                                       sa->port);
 1582 
 1583     return FTP_REPLY_EXTENDED_PASSIVE_MODE;
 1584 }
 1585 
 1586 
 1587 static int ftp_cmd_pasv(request_rec *r, const char *arg)
 1588 {
 1589     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1590     conn_rec *c = r->connection;
 1591     ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
 1592     int res;
 1593     apr_sockaddr_t *sa;
 1594     apr_socket_t *s;
 1595     char *report_addr, *period;
 1596     apr_port_t port;
 1597     short high, low;
 1598     const char *addr;
 1599     int family;
 1600 
 1601     ftp_reset_dataconn(fc);
 1602 
 1603     if (fc->all_epsv) {
 1604         fc->response_notes = "Restricted by EPSV ALL";
 1605         return FTP_REPLY_BAD_SEQUENCE;
 1606     }
 1607 
 1608     if (fsc->pasv_bindaddr) {
 1609         addr = fsc->pasv_bindaddr;
 1610         family = fsc->pasv_bindfamily;
 1611     }
 1612 #if APR_HAVE_IPV6
 1613     else if (c->local_addr->family == AF_INET6 &&
 1614       IN6_IS_ADDR_V4MAPPED((struct in6_addr *) c->local_addr->ipaddr_ptr)) {
 1615         /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
 1616         addr = c->local_ip;
 1617         family = APR_INET;
 1618     }
 1619 #endif
 1620     else {
 1621         addr = c->local_ip;
 1622         family = c->local_addr->family;
 1623     }
 1624 
 1625     if ((res = init_pasv_socket(r, family, addr))) {
 1626         return res;
 1627     }
 1628     s = fc->csock;
 1629 
 1630     apr_socket_addr_get(&sa, APR_LOCAL, s);
 1631 
 1632     /*
 1633      * pasv_addr is an ADVERTISED address, not related to the true address.
 1634      * It should always be evaluated first for the benefit of servers hiding
 1635      * behind load balancers, vpn's, nat, etc.
 1636      */
 1637     if (fsc->pasv_addr) {
 1638         report_addr = apr_pstrdup(r->pool, fsc->pasv_addr);
 1639     }
 1640     else if (fsc->pasv_bindaddr && (fsc->pasv_bindfamily == APR_INET)) {
 1641         report_addr = apr_pstrdup(r->pool, fsc->pasv_bindaddr);
 1642     }
 1643     else if ((c->local_addr->family == AF_INET)
 1644 #if APR_HAVE_IPV6
 1645              || (c->local_addr->family == AF_INET6
 1646                  && IN6_IS_ADDR_V4MAPPED((struct in6_addr *) c->local_addr->ipaddr_ptr))
 1647 #endif
 1648         ) {
 1649         /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
 1650         report_addr = apr_pstrdup(r->pool, c->local_ip);
 1651     }
 1652     else {
 1653         /*
 1654          * a bogus answer, which will not be translated below, wherein
 1655          * clients can choose to connect back to the original, same address.
 1656          * Suggested as an early solution at http://cr.yp.to/ftp/retr.html
 1657          */
 1658         report_addr = "127,555,555,555";
 1659     }
 1660 
 1661     /* Translate x.x.x.x to x,x,x,x */
 1662     while ((period = strstr(report_addr, ".")) != NULL) {
 1663         *period = ',';
 1664     }
 1665 
 1666     port = sa->port;
 1667     high = ((port & 0xff00) >> 8);
 1668     low = (port & 0x00ff);
 1669     fc->response_notes = apr_psprintf(r->pool,
 1670                                       "Entering Passive Mode (%s,%u,%u)",
 1671                                       report_addr, high, low);
 1672     return FTP_REPLY_PASSIVE_MODE;
 1673 }
 1674 
 1675 static int ftp_cmd_pbsz(request_rec *r, const char *arg)
 1676 {
 1677     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1678     char *endp;
 1679 
 1680     /*
 1681      * Check if we have completed the security exchange. Note, some
 1682      * clients still send PBSZ even with Implicit SSL. So we
 1683      * allow this misbehavior...
 1684      */
 1685     if (fc->auth == FTP_AUTH_NONE) {
 1686         return FTP_REPLY_BAD_SEQUENCE;
 1687     }
 1688 
 1689     fc->pbsz = strtol(arg, &endp, 10);
 1690     /*
 1691      * Return 501 if we were unable to parse the argument or if there was a
 1692      * possibility of an overflow
 1693      */
 1694     if (((*arg == '\0') || (*endp != '\0')) || fc->pbsz < 0
 1695         || fc->pbsz == LONG_MAX) {
 1696         fc->response_notes = "Could not parse PBSZ argument";
 1697         return FTP_REPLY_SYNTAX_ERROR;
 1698     }
 1699 
 1700     fc->response_notes = apr_psprintf(r->pool, "PBSZ Command OK. "
 1701                                       "Protection buffer size set to %d",
 1702                                       fc->pbsz);
 1703     return FTP_REPLY_COMMAND_OK;
 1704 }
 1705 
 1706 static int get_outbound_port(apr_sockaddr_t **sa_rv, request_rec *r,
 1707                              apr_int32_t family)
 1708 {
 1709     conn_rec *c = r->connection;
 1710     ftp_connection *fc = ftp_get_module_config(c->conn_config);
 1711     ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
 1712     apr_sockaddr_t *sa;
 1713     apr_socket_t *s;
 1714     apr_status_t rv;
 1715     apr_port_t local_port;
 1716 
 1717     if (fsc->active_min == -1) {
 1718         local_port = 0;
 1719     }
 1720     else if (fsc->active_max == fsc->active_min) {
 1721         local_port = fsc->active_min;
 1722     }
 1723     else {
 1724         local_port = fsc->active_min +
 1725             (apr_port_t) (rand() % (fsc->active_max -
 1726                                     fsc->active_min + 1));
 1727     }
 1728 
 1729     if (c->local_addr->family == family) {
 1730         /* Shortcut; duplicate the contents */
 1731         sa = apr_palloc(fc->data_pool, sizeof(apr_sockaddr_t));
 1732         memcpy(sa, c->local_addr, sizeof(apr_sockaddr_t));
 1733         sa->next = NULL;
 1734         if (sa->family == APR_INET)
 1735             sa->ipaddr_ptr = &(sa->sa.sin.sin_addr);
 1736 #if APR_HAVE_IPV6
 1737         else if (sa->family == APR_INET6)
 1738             sa->ipaddr_ptr = &(sa->sa.sin6.sin6_addr);
 1739 #endif
 1740         sa->sa.sin.sin_port = htons(local_port);
 1741     }
 1742     else {
 1743         /* Long way around, create a fresh sa, attempt to use 
 1744          * the original port, before falling back on the nul adapter
 1745          * TODO: this could use a config option to specify the
 1746          * preferred interface/local address
 1747          */
 1748         rv = apr_sockaddr_info_get(&sa, c->local_ip, family,
 1749                                    local_port, 0, fc->data_pool);
 1750         if (!sa || rv) {
 1751             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
 1752                           "Couldn't resolve explicit local socket address %s "
 1753                           "(apr or socket stack bug?)  Retrying", c->local_ip);
 1754             rv = apr_sockaddr_info_get(&sa, NULL, APR_INET,
 1755                                        local_port, 0, fc->data_pool);
 1756         }
 1757 
 1758         if (!sa || rv) {
 1759             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 1760                           "Couldn't resolve emphemeral local socket address "
 1761                           "(apr or socket stack bug?)  Giving up");
 1762             return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1763         }
 1764     }
 1765 
 1766 #ifdef HAVE_FTP_LOWPORTD
 1767     if ((local_port > 0) && (local_port < 1024)) {
 1768         /*
 1769          * Here's the case of low numbered port creation; we have spun off
 1770          * a worker to serve socket fd's through a unix domain socket via the
 1771          * ftp_request_lowport client.
 1772          */
 1773         rv = ftp_request_lowport(&s, r, sa, fc->data_pool);
 1774 
 1775         if (rv != APR_SUCCESS) {
 1776             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 1777                           "Request socket failed from FTP low port daemon");
 1778             return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1779         }
 1780     }
 1781     else
 1782 #endif
 1783     {
 1784 #if APR_MAJOR_VERSION < 1
 1785         rv = apr_socket_create_ex(&s, family, SOCK_STREAM, APR_PROTO_TCP,
 1786                                   fc->data_pool);
 1787 #else
 1788         rv = apr_socket_create(&s, family, SOCK_STREAM, APR_PROTO_TCP,
 1789                                fc->data_pool);
 1790 #endif
 1791 
 1792         if (rv != APR_SUCCESS) {
 1793             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 1794                           "Couldn't create socket");
 1795             return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1796         }
 1797 
 1798         apr_socket_opt_set(s, APR_SO_REUSEADDR, 1);
 1799 
 1800         rv = apr_socket_bind(s, sa);
 1801 
 1802         if (rv != APR_SUCCESS) {
 1803 #ifdef EACCES
 1804             if (sa->port < 1024 && rv == EACCES)
 1805                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 1806                               "Couldn't bind to low numbered port (<1024).  "
 1807                               "See FTPActiveRange directive");
 1808             else
 1809 #endif
 1810                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 1811                               "Couldn't bind to socket");
 1812             apr_socket_close(s);
 1813             return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1814         }
 1815     }
 1816 
 1817     *sa_rv = sa;
 1818     fc->csock = s;
 1819     fc->response_notes = apr_psprintf(r->pool, FTP_MSG_SUCCESS, r->method);
 1820     return FTP_REPLY_COMMAND_OK;
 1821 }
 1822 
 1823 static int ftp_cmd_eprt(request_rec *r, const char *arg)
 1824 {
 1825     conn_rec *c = r->connection;
 1826     ftp_connection *fc = ftp_get_module_config(c->conn_config);
 1827     ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
 1828     apr_sockaddr_t *sa;
 1829     apr_status_t rv;
 1830     char *arg_tok, *ip_addr;
 1831     apr_int32_t family;
 1832     apr_port_t port;
 1833     int res;
 1834 
 1835     ftp_reset_dataconn(fc);
 1836 
 1837     if (fc->all_epsv) {
 1838         fc->response_notes = "Restricted by EPSV ALL";
 1839         return FTP_REPLY_BAD_SEQUENCE;
 1840     }
 1841 
 1842     arg_tok = apr_pstrdup(fc->data_pool, arg);
 1843     if ((res = ftp_eprt_decode(&family, &ip_addr, &port, arg_tok))
 1844         != FTP_REPLY_COMMAND_OK) {
 1845         fc->response_notes = "Invalid EPRT request";
 1846         return res;
 1847     }
 1848 
 1849     rv = apr_sockaddr_info_get(&fc->clientsa, ip_addr, family,
 1850                                port, 0, fc->data_pool);
 1851     if (!fc->clientsa || rv) {
 1852         ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
 1853                      "Unable to resolve EPRT address of |%d|%s|%d|",
 1854 #if APR_HAVE_IPV6
 1855                      (family == APR_INET6) ? 2 : 1, ip_addr, port);
 1856 #else
 1857                      1, ip_addr, port);
 1858 #endif
 1859         fc->response_notes = "Invalid EPRT command, unable to resolve request";
 1860         return FTP_REPLY_SYNTAX_ERROR;
 1861     }
 1862 
 1863     /*
 1864      * Open data connection only if the EPRT connection is to the client's IP
 1865      * address. All other EPRT connection requests are denied, unless
 1866      * disabled using:
 1867      * 
 1868      * FTPOptions AllowProxyPORT
 1869      * 
 1870      * We must canonicalize the IP address first to compare it to our own idea
 1871      * of the client's IP address.
 1872      */
 1873     if (!(fsc->options & FTP_OPT_ALLOWPROXYPORT)) {
 1874         char *test_ip = "(unknown)";
 1875         if (apr_sockaddr_ip_get(&test_ip, fc->clientsa) != APR_SUCCESS
 1876             || (strcasecmp(test_ip, c->remote_ip) != 0)) {
 1877             ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
 1878                          "Rejected EPRT data connection request to %s "
 1879                          "(doesn't match the client IP %s and "
 1880                          "not configured to AllowProxyPORT)",
 1881                          test_ip, c->remote_ip);
 1882             fc->response_notes = "Invalid EPRT command, proxy EPRT is"
 1883                                  " not permitted";
 1884             return FTP_REPLY_SYNTAX_ERROR;
 1885         }
 1886     }
 1887 
 1888     return get_outbound_port(&sa, r, family);
 1889 }
 1890 
 1891 static int ftp_cmd_port(request_rec *r, const char *arg)
 1892 {
 1893     conn_rec *c = r->connection;
 1894     ftp_connection *fc = ftp_get_module_config(c->conn_config);
 1895     ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
 1896     apr_sockaddr_t *sa;
 1897     apr_status_t rv;
 1898     apr_port_t port;
 1899     char *ip_addr, tc;
 1900     int res, val[6];
 1901 
 1902     ftp_reset_dataconn(fc);
 1903 
 1904     if (fc->all_epsv) {
 1905         fc->response_notes = "Restricted by EPSV ALL";
 1906         return FTP_REPLY_BAD_SEQUENCE;
 1907     }
 1908 
 1909     if ((res = sscanf(arg, "%d,%d,%d,%d,%d,%d%c", &val[0], &val[1], &val[2],
 1910                       &val[3], &val[4], &val[5], &tc)) != 6) {
 1911         fc->response_notes = "Invalid PORT request";
 1912         return FTP_REPLY_SYNTAX_ERROR;
 1913     }
 1914 
 1915     ip_addr = apr_psprintf(fc->data_pool, "%d.%d.%d.%d",
 1916                            val[0], val[1], val[2], val[3]);
 1917 
 1918     port = ((val[4] << 8) + val[5]);
 1919 
 1920     /*
 1921      * Open data connection only if the PORT connection is to the client's IP
 1922      * address. All other PORT connection requests are denied, unless
 1923      * enabled using:
 1924      * 
 1925      * FTPOptions AllowProxyPORT
 1926      */
 1927     if (!(fsc->options & FTP_OPT_ALLOWPROXYPORT)) {
 1928         if (strcasecmp(ip_addr, c->remote_ip) != 0) {
 1929             ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
 1930                          "Rejected PORT data connection request to %s "
 1931                          "(doesn't match the client IP %s and "
 1932                          "not configured to AllowProxyPORT)",
 1933                          ip_addr, c->remote_ip);
 1934             fc->response_notes = "Invalid PORT command, proxy PORT is"
 1935                                  " not permitted";
 1936             return FTP_REPLY_SYNTAX_ERROR;
 1937         }
 1938     }
 1939 
 1940     rv = apr_sockaddr_info_get(&fc->clientsa, ip_addr, APR_INET,
 1941                                port, 0, fc->data_pool);
 1942 
 1943     if (!fc->clientsa || rv) {
 1944         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 1945                       "Couldn't resolve remote socket address %s"
 1946                       " (apr or socket stack bug?)", ip_addr);
 1947         return FTP_REPLY_CANNOT_OPEN_DATACONN;
 1948     }
 1949 
 1950     return get_outbound_port(&sa, r, APR_INET);
 1951 }
 1952 
 1953 static int ftp_cmd_prot(request_rec *r, const char *arg)
 1954 {
 1955     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1956 
 1957     /*
 1958      * Return 503 if the user has not done a AUTH command yet. Although
 1959      * RFC2228 and RFC4217 are very explicit that PBSZ must preceed PROT,
 1960      * it's entirely worthless in the context of TLS, and not even worth
 1961      * enforcing.
 1962      */
 1963     if (fc->auth == FTP_AUTH_NONE) {
 1964         return FTP_REPLY_BAD_SEQUENCE;
 1965     }
 1966 
 1967     switch (*arg) {
 1968     case 'C':
 1969         fc->response_notes = "PROT Command OK. Using clear data channel";
 1970         fc->prot = FTP_PROT_CLEAR;
 1971         return FTP_REPLY_COMMAND_OK;
 1972     case 'S':
 1973         return FTP_REPLY_PROT_NOT_SUPPORTED;
 1974     case 'E':
 1975         return FTP_REPLY_PROT_NOT_SUPPORTED;
 1976     case 'P':
 1977         fc->response_notes = "PROT Command OK. Using private data channel";
 1978         fc->prot = FTP_PROT_PRIVATE;
 1979         return FTP_REPLY_COMMAND_OK;
 1980     }
 1981 
 1982     /* We don't understand */
 1983     fc->response_notes = "PROT argument not understood.";
 1984     return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
 1985 }
 1986 
 1987 static int ftp_cmd_pwd(request_rec *r, const char *arg)
 1988 {
 1989     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1990 
 1991     fc->response_notes = apr_psprintf(r->pool, FTP_MSG_DIR_CUR, 
 1992                              ftp_escape_control_text(fc->cwd, r->pool));
 1993     return FTP_REPLY_PATH_CREATED;
 1994 }
 1995 
 1996 static int ftp_cmd_quit(request_rec *r, const char *arg)
 1997 {
 1998     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 1999 
 2000     fc->close_connection = 1;
 2001     return FTP_REPLY_CONTROL_CLOSE;
 2002 }
 2003 
 2004 static int ftp_cmd_rest(request_rec *r, const char *arg)
 2005 {
 2006     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2007     char *endp;
 2008 
 2009 #if APR_MAJOR_VERSION < 1
 2010     fc->restart_point = apr_atoi64(arg);
 2011     endp = (char *) arg + strspn(arg, "0123456789");
 2012     if (
 2013 #else
 2014     apr_status_t rv = apr_strtoff(&(fc->restart_point), arg, &endp, 10);
 2015     if ((rv != APR_SUCCESS) ||
 2016 #endif
 2017         (*arg == '\0') || (*endp != '\0') || (fc->restart_point < 0)) {
 2018         fc->response_notes = "REST requires a non-negative integer value";
 2019         return FTP_REPLY_SYNTAX_ERROR;
 2020     }
 2021 
 2022     fc->response_notes = apr_psprintf(r->pool, "Restarting at %" APR_OFF_T_FMT
 2023                                       ". Send STORE or RETRIEVE to initiate "
 2024                                       "transfer.", fc->restart_point);
 2025     return FTP_REPLY_PENDING;
 2026 }
 2027 
 2028 static int ftp_cmd_retr(request_rec *r, const char *arg)
 2029 {
 2030     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2031     request_rec *rr;
 2032     conn_rec *c = r->connection;
 2033     conn_rec *cdata;
 2034     ap_filter_t *f, *rinput, *routput, *rinput_proto, *routput_proto;
 2035     int res;
 2036 
 2037     /* Put a note in the env table for logging */
 2038     /*
 2039      * This envar determines whether or not the command being
 2040      * processed is one which causes a file to be uploaded or
 2041      * downloaded ("transferred"). This allows a logfile to be
 2042      * created of just transfers attempts. The success or failure
 2043      * of the transfer is determined by the ftp_transfer_ok envar
 2044      */
 2045     apr_table_setn(r->subprocess_env, "do_transfer_log", "1");
 2046 
 2047     if ((res = ftp_set_uri(r, arg))) {
 2048         return res;
 2049     }
 2050 
 2051     /* If anything fails in the subrequest, simply return permission denied */
 2052     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
 2053     if (rr->status != HTTP_OK) {
 2054         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2055                                  ftp_escape_control_text(arg, r->pool));
 2056         ap_destroy_sub_req(rr);
 2057         return FTP_REPLY_FILE_NOT_FOUND;
 2058     }
 2059     ap_destroy_sub_req(rr);
 2060 
 2061     ftp_reply(fc, c->output_filters, r->pool, FTP_REPLY_FILE_STATUS_OK, 0,
 2062               apr_pstrcat(r->pool, "Opening ",
 2063                           (fc->type == TYPE_A) ? "ASCII" : "BINARY",
 2064                           " mode data connection for ", 
 2065                           ftp_escape_control_text(arg, r->pool), NULL));
 2066 
 2067     if (!(cdata = ftp_open_dataconn(r, 1))) {
 2068         return FTP_REPLY_CANNOT_OPEN_DATACONN;
 2069     }
 2070 
 2071     /* Save the original filters */
 2072     rinput = r->input_filters;
 2073     rinput_proto = r->proto_input_filters;
 2074     routput = r->output_filters;
 2075     routput_proto = r->proto_output_filters;
 2076 
 2077     r->proto_input_filters = cdata->input_filters;
 2078     r->input_filters = r->proto_input_filters;
 2079     r->proto_output_filters = cdata->output_filters;
 2080     r->output_filters = r->proto_output_filters;
 2081 
 2082     ap_add_input_filter_handle(ftp_input_filter_handle, NULL, r, r->connection);
 2083 
 2084     r->connection = cdata;
 2085 
 2086     /*
 2087      * If we are sending a ASCII file, we need to run the CRLF filter.
 2088      * Running the CRLF filter will result in a bunch of buckets, so we
 2089      * should also add the COALESCE filter to put everything back into a
 2090      * single bucket.
 2091      */
 2092     if (fc->type == TYPE_A) {
 2093         fc->filter_mask += FTP_NEED_CRLF;
 2094     }
 2095 
 2096     if (fc->restart_point > 0) {
 2097         fc->filter_mask += FTP_NEED_BYTERANGE;
 2098     }
 2099 
 2100     rr = ap_sub_req_method_uri("GET", r->uri, r, NULL);
 2101     if (rr->status != HTTP_OK) {
 2102         /*
 2103          * Should never get here since we have already run this subrequest,
 2104          * but better safe than sorry
 2105          */
 2106         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2107                                  ftp_escape_control_text(arg, r->pool));
 2108         res = FTP_REPLY_FILE_NOT_FOUND;
 2109         goto clean_up;
 2110     }
 2111 
 2112     /* If this is a resume request, set the "Range: {start}-" header */
 2113     if (fc->restart_point > 0) {
 2114         apr_table_setn(rr->headers_in, "Range",
 2115                        apr_psprintf(r->pool, "bytes=%" APR_OFF_T_FMT "-",
 2116                                     fc->restart_point));
 2117 
 2118         /* Byterange requires that we are not assbackwards (HTTP/0.9) */
 2119         rr->assbackwards = 0;
 2120     }
 2121 
 2122     /*
 2123      * Filter manipulation.  We remove the subrequest filter so that the EOS
 2124      * buckets stay intact.  We also add the content length filter so that
 2125      * we can record the bytes_sent
 2126      */
 2127     for (f = rr->output_filters; f; f = f->next) {
 2128         if (strcasecmp(f->frec->name, "SUBREQ_CORE") == 0) {
 2129             ap_remove_output_filter(f);
 2130         }
 2131     }
 2132 
 2133     /* Need content length rr->bytes_sent */
 2134     ap_add_output_filter_handle(ftp_content_length_filter_handle, NULL,
 2135                                 rr, rr->connection);
 2136 
 2137     if ((res = ap_run_sub_req(rr)) != OK) {
 2138         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
 2139                                  ftp_escape_control_text(arg, r->pool));
 2140         res = FTP_REPLY_FILE_NOT_FOUND;
 2141     }
 2142 
 2143     else {
 2144         res = FTP_REPLY_DATA_CLOSE;
 2145     }
 2146 
 2147     fc->restart_point = 0;
 2148     fc->traffic += rr->bytes_sent;
 2149     fc->bytes += rr->bytes_sent;
 2150     fc->files += 1;
 2151     fc->transfers += 1;
 2152 
 2153     /*
 2154      * We use a subrequest here in an weird way, which causes some
 2155      * information to be lost.  Here we hack around that by setting values in
 2156      * r->main, but in the future, we may just want to do away with running
 2157      * the subrequest, and run the main request.
 2158      * 
 2159      * There may be other values we need to save here.
 2160      */
 2161     rr->main->sent_bodyct = 1;
 2162 
 2163     /* Check to make sure the connection wasnt aborted */
 2164     if (rr->connection->aborted || cdata->aborted) {
 2165         rr->main->bytes_sent = 0;
 2166         res = FTP_REPLY_TRANSFER_ABORTED;
 2167         rr->main->connection->aborted = 0;
 2168     }
 2169     else {
 2170         rr->main->bytes_sent = rr->bytes_sent;
 2171     }
 2172 
 2173 clean_up:
 2174     ap_destroy_sub_req(rr);
 2175 
 2176     /* Replace the filters and connection */
 2177     r->input_filters = rinput;
 2178     r->proto_input_filters = rinput_proto;
 2179     r->output_filters = routput;
 2180     r->proto_output_filters = routput_proto;
 2181     r->connection = c;
 2182 
 2183     /* Close the data connection, send confirmation, and return  */
 2184     ap_lingering_close(cdata);
 2185     fc->datasock = NULL;
 2186     fc->filter_mask = 0;
 2187 
 2188     return res;
 2189 }
 2190 
 2191 static int ftp_cmd_rnfr(request_rec *r, const char *arg)
 2192 {
 2193     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2194     request_rec *rr;
 2195     apr_status_t res;
 2196     int response;
 2197 
 2198     if ((res = ftp_set_uri(r, arg))) {
 2199         return res;
 2200     }
 2201 
 2202     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
 2203 
 2204     if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
 2205         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2206                                  ftp_escape_control_text(r->parsed_uri.path,
 2207                                                          r->pool));
 2208         response = FTP_REPLY_FILE_NOT_FOUND;
 2209     }
 2210     else if (rr->finfo.filetype != APR_NOFILE) {
 2211         fc->response_notes = "File exists, ready for destination name";
 2212         apr_cpystrn(fc->rename_from, r->filename, APR_PATH_MAX + 1);
 2213         response = FTP_REPLY_PENDING;
 2214     }
 2215     else {
 2216         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
 2217                                  ftp_escape_control_text(r->parsed_uri.path,
 2218                                                          r->pool));
 2219         response = FTP_REPLY_FILE_NOT_FOUND;
 2220     }
 2221 
 2222     ap_destroy_sub_req(rr);
 2223 
 2224     return response;
 2225 }
 2226 
 2227 static int ftp_cmd_rnto(request_rec *r, const char *arg)
 2228 {
 2229     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2230     apr_status_t rv;
 2231     int response, res;
 2232     request_rec *rr;
 2233 
 2234     if ((res = ftp_set_uri(r, arg))) {
 2235         fc->rename_from[0] = '\0';
 2236         return res;
 2237     }
 2238 
 2239     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
 2240 
 2241     if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
 2242         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2243                                  ftp_escape_control_text(r->parsed_uri.path,
 2244                                                          r->pool));
 2245         ap_destroy_sub_req(rr);
 2246         return FTP_REPLY_FILE_NOT_FOUND;
 2247     }
 2248 
 2249     ap_destroy_sub_req(rr);
 2250 
 2251     /* RNTO *must* be preceeded by RNFR */
 2252     if (fc->rename_from[0] == '\0') {
 2253         return FTP_REPLY_BAD_SEQUENCE;
 2254     }
 2255 
 2256     rv = apr_file_rename(fc->rename_from, r->filename, r->pool);
 2257 
 2258     if (rv != APR_SUCCESS) {
 2259         response = FTP_REPLY_LOCAL_ERROR;
 2260     }
 2261     else {
 2262         response = FTP_REPLY_COMPLETED;
 2263     }
 2264 
 2265     fc->rename_from[0] = '\0';
 2266     return response;
 2267 }
 2268 
 2269 static int ftp_cmd_rmd(request_rec *r, const char *arg)
 2270 {
 2271     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2272     apr_status_t rv, res;
 2273     request_rec *rr;
 2274 
 2275     if ((res = ftp_set_uri(r, arg))) {
 2276         return res;
 2277     }
 2278 
 2279     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
 2280 
 2281     if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
 2282         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2283                                  ftp_escape_control_text(r->parsed_uri.path,
 2284                                                          r->pool));
 2285         ap_destroy_sub_req(rr);
 2286         return FTP_REPLY_FILE_NOT_FOUND;
 2287     }
 2288     ap_destroy_sub_req(rr);
 2289 
 2290     rv = apr_dir_remove(r->filename, r->pool);
 2291 
 2292     if (rv != APR_SUCCESS) {
 2293         char error_str[120];
 2294         char *err = apr_strerror(rv, error_str, sizeof(error_str));
 2295         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2296                                  ftp_escape_control_text(err, r->pool));
 2297         return FTP_REPLY_FILE_NOT_FOUND;
 2298     }
 2299     else {
 2300         return FTP_REPLY_COMPLETED;
 2301     }
 2302 }
 2303 
 2304 static int ftp_cmd_size(request_rec *r, const char *arg)
 2305 {
 2306     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2307     int res, response;
 2308     request_rec *rr;
 2309 
 2310     if ((res = ftp_set_uri(r, arg))) {
 2311         return res;
 2312     }
 2313 
 2314     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
 2315 
 2316     if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
 2317         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2318                                  ftp_escape_control_text(r->parsed_uri.path, r->pool));
 2319         return FTP_REPLY_FILE_NOT_FOUND;
 2320     }
 2321 
 2322     if (rr->finfo.filetype == 0) {
 2323         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
 2324                                  ftp_escape_control_text(arg, r->pool));
 2325         response = FTP_REPLY_FILE_NOT_FOUND;
 2326     }
 2327     else if (rr->finfo.filetype != APR_REG) {
 2328         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOTPLAIN,
 2329                                  ftp_escape_control_text(arg, r->pool));
 2330         response = FTP_REPLY_FILE_NOT_FOUND;
 2331     }
 2332     else {
 2333         /*
 2334          * XXX: big bug - going back to the beginning.  We must compute size
 2335          * rather than stating the file... this could be done trivially with
 2336          * a request to a null 'data' port which simply counts up the bytes.
 2337          * Will be implemented as a special-case of the ftp_core_data
 2338          * connection endpoint-filter.
 2339          * See RFC3659 - especially with respect to TYPE A/I
 2340          */
 2341         fc->response_notes = apr_psprintf(r->pool, "%" APR_OFF_T_FMT,
 2342                                           rr->finfo.size);
 2343         response = FTP_REPLY_FILE_STATUS;
 2344     }
 2345 
 2346     ap_destroy_sub_req(rr);
 2347     return response;
 2348 }
 2349 
 2350 static int ftp_get_client_block(conn_rec *c, ap_input_mode_t mode,
 2351                                 apr_bucket_brigade *bb,
 2352                                 char *buffer, apr_size_t bufsiz,
 2353                                 apr_size_t *len)
 2354 {
 2355     apr_size_t total;
 2356     apr_status_t rv;
 2357     apr_bucket *b;
 2358     const char *tempbuf;
 2359 
 2360     if (APR_BRIGADE_EMPTY(bb)) {
 2361         if ((rv = ap_get_brigade(c->input_filters, bb, mode,
 2362                                  APR_BLOCK_READ, bufsiz)) != APR_SUCCESS) {
 2363             apr_brigade_destroy(bb);
 2364             return rv;
 2365         }
 2366     }
 2367 
 2368     b = APR_BRIGADE_FIRST(bb);
 2369     if (APR_BUCKET_IS_EOS(b)) { /* From previous invocation */
 2370         apr_bucket_delete(b);
 2371         return APR_EOF;
 2372     }
 2373 
 2374     total = 0;
 2375     while (total < bufsiz
 2376            && b != APR_BRIGADE_SENTINEL(bb)
 2377            && !APR_BUCKET_IS_EOS(b)) {
 2378         apr_size_t len_read;
 2379         apr_bucket *old;
 2380 
 2381         if ((rv = apr_bucket_read(b, &tempbuf, &len_read,
 2382                                   APR_BLOCK_READ)) != APR_SUCCESS) {
 2383             return rv;
 2384         }
 2385         if (total + len_read > bufsiz) {
 2386             len_read = bufsiz - total;
 2387             apr_bucket_split(b, bufsiz - total);
 2388         }
 2389         memcpy(buffer, tempbuf, len_read);
 2390         buffer += len_read;
 2391         total += len_read;
 2392 
 2393         old = b;
 2394         b = APR_BUCKET_NEXT(b);
 2395         apr_bucket_delete(old);
 2396     }
 2397 
 2398     *len = total;
 2399     return APR_SUCCESS;
 2400 }
 2401 
 2402 /*
 2403  * This handles all STOR and APPE requests
 2404  */
 2405 static int ftp_cmd_stor(request_rec *r, const char *arg)
 2406 {
 2407     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2408     conn_rec *c = r->connection;
 2409     conn_rec *cdata;
 2410     ftp_dir_config *dconf;
 2411     apr_file_t *file;
 2412     apr_status_t rv, res;
 2413     apr_bucket_brigade *bb;
 2414     apr_int32_t openflag;
 2415     char *buffer;
 2416     apr_off_t total = 0;
 2417     apr_size_t len;
 2418     request_rec *rr;
 2419     int clientstatus = APR_SUCCESS;
 2420     int status = FTP_REPLY_DATA_CLOSE;
 2421 #ifndef WIN32
 2422     int seen_cr = 0;
 2423 #endif
 2424     apr_fileperms_t creatperms;
 2425 #ifdef HAVE_FCHMOD
 2426     mode_t fixmode;
 2427     apr_finfo_t finfo;
 2428     int fd;
 2429 #endif
 2430 
 2431     apr_table_setn(r->subprocess_env, "do_transfer_log", "1");
 2432 
 2433     if ((res = ftp_set_uri(r, arg))) {
 2434         return res;
 2435     }
 2436 
 2437     rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
 2438 
 2439     if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
 2440         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2441                                  ftp_escape_control_text(r->parsed_uri.path,
 2442                                                          r->pool));
 2443         ap_destroy_sub_req(rr);
 2444         return FTP_REPLY_FILE_NOT_FOUND;
 2445     }
 2446 
 2447     dconf = ftp_get_module_config(rr->per_dir_config);
 2448     creatperms = dconf->fileperms;
 2449     ap_destroy_sub_req(rr);
 2450 
 2451     if (creatperms == APR_OS_DEFAULT)
 2452         creatperms = FTP_DEFAULT_UMASK;
 2453 
 2454     /*
 2455      * In the config phase, ->creatperms was a negative umask. for operation,
 2456      * exchange this with a positive protections to pass to the apr_file_open
 2457      * protection flag.
 2458      */
 2459     creatperms = (APR_UREAD | APR_UWRITE |
 2460                   APR_GREAD | APR_GWRITE |
 2461                   APR_WREAD | APR_WWRITE)
 2462         & ~creatperms;
 2463 
 2464 #ifdef HAVE_FCHMOD
 2465     fixmode = ftp_unix_perms2mode(creatperms);
 2466     creatperms = 0;
 2467 #endif
 2468 
 2469     /*
 2470      * For the remainder of the operation, (openflag & APR_APPEND) reflects
 2471      * this was an append operation and we have no need to truncate.
 2472      * Presume, if not append and our restart point is zero, that
 2473      * open(O_TRUNC) is supported and preferable.
 2474      */
 2475     openflag = APR_WRITE | APR_CREATE;
 2476     if (strcmp(r->method, "APPE") == 0) {
 2477         openflag |= APR_APPEND;
 2478     }
 2479     else if (fc->restart_point == 0) {
 2480         openflag |= APR_TRUNCATE;
 2481     }
 2482 
 2483     rv = apr_file_open(&file, r->filename, openflag, creatperms, r->pool);
 2484 
 2485     /* File permissions deny server write access */
 2486     if (rv != APR_SUCCESS) {
 2487         char error_str[120];
 2488         char *err = apr_strerror(rv, error_str, sizeof(error_str));
 2489         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2490                                  ftp_escape_control_text(err, r->pool));
 2491         fc->restart_point = 0;
 2492         return FTP_REPLY_FILE_NOT_FOUND;
 2493     }
 2494 
 2495 #ifdef HAVE_FCHMOD
 2496     /*
 2497      * If we opened an existing file, we have nothing to do later in the code
 2498      * (leave fd as -1). If we created the file with perms zero, grab the fd
 2499      * and creatperms to adjust the perms when finished.
 2500      */
 2501     if (apr_file_info_get(&finfo, APR_FINFO_PROT, file)
 2502         != APR_SUCCESS) {
 2503         fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
 2504                                           "Unable to perform file upload; "
 2505                                           "failed to get fileinfo");
 2506         fc->restart_point = 0;
 2507         return FTP_REPLY_FILE_NOT_FOUND;
 2508     }
 2509     if (finfo.protection) {
 2510         fd = -1;
 2511     }
 2512     else {
 2513         apr_os_file_get(&fd, file);
 2514     }
 2515 #endif
 2516 
 2517     /*
 2518      * Once the file is opened, non-append/non-truncate, we need to space
 2519      * forward to the restart point.  Unset the restart pointer once we have
 2520      * grabbed it to space forward.
 2521      */
 2522     if (!(openflag & (APR_APPEND | APR_TRUNCATE))) {
 2523         apr_off_t restart = fc->restart_point;
 2524         if (apr_file_seek(file, APR_SET, &restart) != APR_SUCCESS) {
 2525             fc->response_notes = "Requested action not taken: invalid REST "
 2526                                  "parameter; failed to skip to restart point";
 2527 #ifdef HAVE_FCHMOD
 2528             if (fd != -1)
 2529                 fchmod(fd, fixmode);
 2530 #endif
 2531             fc->restart_point = 0;
 2532             return FTP_REPLY_INVALID_REST_PARAM;
 2533         }
 2534     }
 2535     fc->restart_point = 0;
 2536 
 2537     ftp_reply(fc, c->output_filters, r->pool, FTP_REPLY_FILE_STATUS_OK, 0,
 2538               apr_pstrcat(r->pool, "Opening ",
 2539                           (fc->type == TYPE_A) ? "ASCII" : "BINARY",
 2540                           " mode data connection for ", 
 2541                           ftp_escape_control_text(arg, r->pool), NULL));
 2542 
 2543     /*
 2544      * Open the data connection to the client
 2545      */
 2546     if (!(cdata = ftp_open_dataconn(r, 0))) {
 2547 #ifdef HAVE_FCHMOD
 2548         if (fd != -1)
 2549             fchmod(fd, fixmode);
 2550 #endif
 2551         return FTP_REPLY_CANNOT_OPEN_DATACONN;
 2552     }
 2553 
 2554     bb = apr_brigade_create(r->pool, c->bucket_alloc);
 2555     buffer = apr_palloc(r->pool, AP_IOBUFSIZE);
 2556 
 2557     while ((clientstatus = ftp_get_client_block(cdata,
 2558                               fc->type == TYPE_A ? AP_MODE_GETLINE : AP_MODE_READBYTES,
 2559                               bb, buffer, AP_IOBUFSIZE, &len)) == APR_SUCCESS) {
 2560 #ifndef WIN32
 2561         /*
 2562          * If we are in ASCII mode, translate to our own internal form. This
 2563          * means CRLF for windows and plain LF for Unicies
 2564          */
 2565         if ((fc->type == TYPE_A) && (len > 0)) {
 2566             /*
 2567              * The last read may have been incomplete if we had activity on
 2568              * the control connection.  Watch out for trailing CR's from the
 2569              * last write, and back up the file a byte if one had been
 2570              * written for this now-leading LF.
 2571              */
 2572             if (seen_cr && (*buffer == APR_ASCII_LF)) {
 2573                 apr_off_t off = -1;
 2574                 apr_file_seek(file, APR_CUR, &off);
 2575                 --total;
 2576             }
 2577 
 2578             /*
 2579              * We may reach EOF without an ending newline.  Make sure we
 2580              * don't truncate any important data
 2581              */
 2582             if ((len > 1) && (*(buffer + len - 2) == APR_ASCII_CR)) {
 2583                 *(buffer + len - 2) = APR_ASCII_LF;
 2584                 len--;
 2585             }
 2586             else {
 2587                 /* Now note any trailing CR on this write. */
 2588                 seen_cr = (*(buffer + len - 1) == APR_ASCII_CR);
 2589             }
 2590         }
 2591 
 2592 #endif                          /* WIN32 */
 2593 
 2594         rv = apr_file_write(file, buffer, &len);
 2595         if (rv != APR_SUCCESS) {
 2596             char error_str[120];
 2597             char *err = apr_strerror(rv, error_str, sizeof(error_str));
 2598             ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
 2599                          "Error writing uploaded file");
 2600             fc->response_notes = ftp_escape_control_text(err, r->pool);
 2601             status = FTP_REPLY_LOCAL_ERROR;
 2602             break;
 2603         }
 2604 
 2605         total += len;
 2606     }
 2607 
 2608     if ((clientstatus != APR_SUCCESS && !APR_STATUS_IS_EOF(clientstatus))
 2609         || (cdata->aborted)) {
 2610         status = FTP_REPLY_TRANSFER_ABORTED;
 2611         cdata->aborted = 1;
 2612     }
 2613 
 2614     fc->traffic += total;
 2615     fc->bytes += total;
 2616     fc->files += 1;
 2617     fc->transfers += 1;
 2618 
 2619     /* Keep track of bytes sent for logging purposes. */
 2620     r->sent_bodyct = 1;
 2621     r->bytes_sent = total;
 2622 
 2623     /*
 2624      * Once we have finished this upload - we need to reset the file perms to
 2625      * no longer hold a lock on the uploaded file...
 2626      */
 2627 #ifdef HAVE_FCHMOD
 2628     if (fd != -1)
 2629         fchmod(fd, fixmode);
 2630 #endif
 2631 
 2632     /*
 2633      * and truncate anything beyond the end of the most recent upload
 2634      */
 2635     if (!(openflag & (APR_APPEND | APR_TRUNCATE))) {
 2636         /* use apr_file_seek to do apr_file_tell's... */
 2637         apr_off_t totsize = 0;
 2638         apr_off_t restart = 0;
 2639         if ((rv = apr_file_seek(file, APR_CUR, &restart)) != APR_SUCCESS) {
 2640             ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
 2641                          "STOR resume: failed to determine current position for truncation");
 2642         }
 2643         else if ((rv = apr_file_seek(file, APR_END, &totsize)) != APR_SUCCESS) {
 2644             ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
 2645                          "STOR resume: failed to determine current file size for truncation");
 2646         }
 2647         else if (totsize <= restart) {
 2648             ;                   /* noop - nothing to truncate */
 2649         }
 2650         else if ((rv = apr_file_trunc(file, restart)) != APR_SUCCESS) {
 2651             ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
 2652                  "STOR resume: failed to truncate remaining file contents");
 2653         }
 2654     }
 2655 
 2656     rv = apr_file_close(file);
 2657     if (rv != APR_SUCCESS) {
 2658         ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
 2659                      "STOR/APPE: failed to close file");
 2660     }
 2661 
 2662     ap_lingering_close(cdata);
 2663     fc->datasock = NULL;
 2664 
 2665     return status;
 2666 }
 2667 
 2668 static int ftp_cmd_stru(request_rec *r, const char *arg)
 2669 {
 2670     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2671 
 2672     if (*arg && !arg[1]) {
 2673         switch (toupper(*arg)) {
 2674         case 'F':
 2675             fc->response_notes = "Structure set to F";
 2676             return FTP_REPLY_COMMAND_OK;
 2677         }
 2678     }
 2679     fc->response_notes = apr_psprintf(r->pool, "Structure %s not implemented",
 2680                              ftp_escape_control_text(arg, r->pool));
 2681 
 2682     return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
 2683 }
 2684 
 2685 static int ftp_cmd_syst(request_rec *r, const char *arg)
 2686 {
 2687     return FTP_REPLY_SYSTEM_TYPE;
 2688 }
 2689 
 2690 static int ftp_cmd_type(request_rec *r, const char *arg)
 2691 {
 2692     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2693 
 2694     if (*arg && !arg[1])
 2695     {
 2696         switch (toupper(*arg)) {
 2697         case 'A':
 2698             fc->type = TYPE_A;
 2699             fc->response_notes = "Type set to A";
 2700             return FTP_REPLY_COMMAND_OK;
 2701         case 'I':
 2702             fc->type = TYPE_I;
 2703             fc->response_notes = "Type set to I";
 2704             return FTP_REPLY_COMMAND_OK;
 2705         }
 2706     }
 2707     else if (!strcasecmp(arg, "A N")) {
 2708         /*
 2709          * Accept Ascii Non-Print
 2710          */
 2711         fc->type = TYPE_A;
 2712         fc->response_notes = "Type set to A N";
 2713         return FTP_REPLY_COMMAND_OK;
 2714     }
 2715     else if (!strcasecmp(arg, "L 8")) {
 2716         /*
 2717          * Treat Local 8 as indistinguishible from Image for httpd platforms
 2718          */
 2719         fc->type = TYPE_I;
 2720         fc->response_notes = "Type set to L 8";
 2721         return FTP_REPLY_COMMAND_OK;
 2722     }
 2723 
 2724     fc->response_notes = apr_psprintf(r->pool, "Type %s not implemented",
 2725                              ftp_escape_control_text(arg, r->pool));
 2726 
 2727     return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
 2728 }
 2729 
 2730 static int ftp_cmd_user(request_rec *r, const char *arg)
 2731 {
 2732     ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
 2733     conn_rec *c = r->connection;
 2734     ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
 2735     apr_time_t prev_timeout;
 2736     apr_status_t rv;
 2737     char *username;
 2738     char *hostname;
 2739 
 2740     /* Implicit logout */
 2741     if (fc->logged_in) {
 2742         ftp_limitlogin_loggedout(c);
 2743     }
 2744     fc->logged_in = 0;
 2745     r->server = fc->orig_server = c->base_server;
 2746     r->per_dir_config = r->server->lookup_defaults;
 2747     r->hostname = fc->host = NULL;
 2748     apr_pool_clear(fc->login_pool);
 2749 
 2750     fc->user = username = apr_pstrdup(fc->login_pool, arg);
 2751 
 2752     /*
 2753      * Identify virtual host (user@{hostname}) for named vhost lookup, and
 2754      * split from user name if so configured.
 2755      */
 2756     if ((hostname = ap_strchr(username, '@')) != NULL) {
 2757         /*
 2758          * Toggle to the Host:-based vhost's timeout mode to process this
 2759          * login request
 2760          */
 2761         if (fsc->options & FTP_OPT_VHOST_BY_USER) {
 2762             r->hostname = hostname + 1;
 2763 
 2764             ap_update_vhost_from_headers(r);
 2765 
 2766             fc->host = r->hostname;
 2767             fc->orig_server = r->server;
 2768         }
 2769     }
 2770 
 2771     /* we may have switched to another server */
 2772     fsc = ftp_get_module_config(r->server->module_config);
 2773     r->per_dir_config = r->server->lookup_defaults;
 2774 
 2775     /*
 2776      * Now that we switched virtual hosts, it's time to determine if the
 2777      * username fc->user's "@{hostname}" should be discarded
 2778      */
 2779     if ((hostname != NULL) && (fsc->options & FTP_OPT_STRIP_HOSTNAME))
 2780         *hostname = '\0';
 2781 
 2782     /*
 2783      * We have nominally 'logged out', and also potentially changed virtual
 2784      * host contexts; reset to the proper timeout_login
 2785      */
 2786     rv = apr_socket_timeout_get(fc->cntlsock, &prev_timeout);
 2787     if (rv != APR_SUCCESS || prev_timeout != fsc->timeout_login) {
 2788         rv = apr_socket_timeout_set(fc->cntlsock,
 2789                                     fsc->timeout_login * APR_USEC_PER_SEC);
 2790         if (rv != APR_SUCCESS)
 2791             ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
 2792                          "Couldn't set SO_TIMEOUT socket option");
 2793     }
 2794 
 2795     if ((fsc->options & FTP_OPT_REQUIRESSL) && !fc->is_secure) {
 2796         r->hostname = fc->host = NULL;
 2797         fc->user = ftp_unknown_username;
 2798         fc->authorization = NULL;
 2799         r->server = fc->orig_server = c->base_server;
 2800         r->per_dir_config = r->server->lookup_defaults;
 2801         fc->response_notes = "This server requires the use of SSL";
 2802         return FTP_REPLY_NOT_LOGGED_IN;
 2803     }
 2804 
 2805     /* TODO: these should really be configurable */
 2806     if ((strcmp(fc->user, "anonymous") == 0) ||
 2807         (strncmp(fc->user, "anonymous@", 10) == 0) ||
 2808         (strcmp(fc->user, "ftp") == 0) ||
 2809         (strncmp(fc->user, "ftp@", 4) == 0)) {
 2810         fc->response_notes = "Guest login ok, type your email address"
 2811                              " as the password";
 2812         /* force this for limit and access control */
 2813         fc->user = "anonymous";
 2814     }
 2815     else {
 2816         fc->response_notes = apr_psprintf(r->pool, "Password required for %s",
 2817                                  ftp_escape_control_text(fc->user, r->pool));
 2818     }
 2819 
 2820     return FTP_REPLY_USER_OK;
 2821 }
 2822 
 2823 void ftp_register_core_cmds(apr_pool_t *p)
 2824 {
 2825 
 2826     /*
 2827      * Create the FTP command hash.  All external modules will need to make
 2828      * sure that their register hooks callbacks are called after the FTP
 2829      * module
 2830      */
 2831 
 2832     FTPMethodHash = apr_hash_make(p);
 2833     FTPMethodPool = p;          /* The config pool */
 2834 
 2835     /* Register all of our core command handlers */
 2836 
 2837     ftp_hook_cmd("ABOR", ftp_cmd_abor, FTP_HOOK_LAST,
 2838                  FTP_NEED_LOGIN | FTP_TAKE0 | FTP_DATA_INTR,
 2839                  "(abort operation)");
 2840 
 2841     ftp_hook_cmd("ACCT", NULL, FTP_HOOK_LAST,
 2842                  FTP_NEED_LOGIN | FTP_TAKE0,
 2843                  "(specify account)");
 2844 
 2845     ftp_hook_cmd("ALLO", NULL, FTP_HOOK_LAST,
 2846                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2847                  "allocate storage (vacuously)");
 2848 
 2849     ftp_hook_cmd("APPE", ftp_cmd_stor, FTP_HOOK_LAST,
 2850                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2851                  "<sp> file-name");
 2852 
 2853     /* AUTH does not require a user to be logged in */
 2854     ftp_hook_cmd("AUTH", ftp_cmd_auth, FTP_HOOK_LAST,
 2855                  FTP_TAKE1,
 2856                  "<sp> mechanism-name");
 2857 
 2858     /* XXX: Advertise only if configured! */
 2859     ftp_feat_advert("AUTH TLS");
 2860 
 2861     ftp_hook_cmd("CDUP", ftp_cmd_cdup, FTP_HOOK_LAST,
 2862                  FTP_NEED_LOGIN | FTP_TAKE0,
 2863                  "change to parent directory");
 2864 
 2865     ftp_hook_cmd("CWD", ftp_cmd_cwd, FTP_HOOK_LAST,
 2866                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2867                  "[ <sp> directory-name ]");
 2868 
 2869     ftp_hook_cmd("DELE", ftp_cmd_dele, FTP_HOOK_LAST,
 2870                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2871                  "<sp> file-name");
 2872 
 2873     ftp_hook_cmd("EPRT", ftp_cmd_eprt, FTP_HOOK_LAST,
 2874                  FTP_NEED_LOGIN | FTP_TAKE1 | FTP_NEW_FEAT,
 2875                  "<sp> <d>af<d>addr<d>port<d>");
 2876 
 2877     ftp_hook_cmd("EPSV", ftp_cmd_epsv, FTP_HOOK_LAST,
 2878                  FTP_NEED_LOGIN | FTP_TAKE0 | FTP_TAKE1 | FTP_NEW_FEAT,
 2879                  "[ <sp> af|ALL ]");
 2880 
 2881     ftp_hook_cmd("FEAT", ftp_cmd_feat, FTP_HOOK_LAST,
 2882                  FTP_TAKE0,
 2883                  "show server features");
 2884 
 2885     ftp_hook_cmd("HELP", ftp_cmd_help, FTP_HOOK_LAST,
 2886                  FTP_TAKE0 | FTP_TAKE1,
 2887                  "[ <sp> <string> ]");
 2888 
 2889     ftp_hook_cmd("LANG", NULL, FTP_HOOK_LAST,
 2890                  FTP_TAKE1,
 2891                  "<sp> lang-code");
 2892 
 2893     ftp_hook_cmd("LIST", ftp_cmd_list, FTP_HOOK_LAST,
 2894                  FTP_NEED_LOGIN | FTP_TAKE0 | FTP_TAKE1_PATH,
 2895                  "[ <sp> path-name ]");
 2896 
 2897     ftp_hook_cmd("MDTM", ftp_cmd_mdtm, FTP_HOOK_LAST,
 2898                  FTP_NEED_LOGIN | FTP_TAKE1_PATH | FTP_NEW_FEAT,
 2899                  "<sp> path-name");
 2900 
 2901     ftp_hook_cmd("MKD", ftp_cmd_mkd, FTP_HOOK_LAST,
 2902                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2903                  "<sp> path-name");
 2904 
 2905     ftp_hook_cmd("MODE", ftp_cmd_mode, FTP_HOOK_LAST,
 2906                  FTP_NEED_LOGIN | FTP_TAKE1,
 2907                  "<sp> [ S | B | C ] (specify transfer mode)");
 2908 
 2909     ftp_hook_cmd("NLST", ftp_cmd_nlst, FTP_HOOK_LAST,
 2910                  FTP_NEED_LOGIN | FTP_TAKE0 | FTP_TAKE1_PATH,
 2911                  "[ <sp> path-name ]");
 2912 
 2913     ftp_hook_cmd("NOOP", ftp_cmd_noop, FTP_HOOK_LAST,
 2914                  FTP_TAKE0,
 2915                  "");
 2916 
 2917     ftp_hook_cmd("OPTS", NULL, FTP_HOOK_LAST,
 2918                  FTP_NEED_LOGIN | FTP_TAKE1 | FTP_EXTENSIBLE,
 2919                  "<sp> command [ <sp> options ]");
 2920 
 2921     /* Pass must be handled literally to ensure leading, trailing spaces 
 2922      * or empty string are recognized
 2923      */
 2924     ftp_hook_cmd("PASS", ftp_cmd_pass, FTP_HOOK_LAST,
 2925                  FTP_TAKE0 | FTP_TAKE1_PATH,
 2926                  "<sp> password");
 2927 
 2928     ftp_hook_cmd("PASV", ftp_cmd_pasv, FTP_HOOK_LAST,
 2929                  FTP_NEED_LOGIN | FTP_TAKE0,
 2930                  "(set server in passive mode)");
 2931 
 2932     /* PBSZ does not require a user to be logged in */
 2933     ftp_hook_cmd("PBSZ", ftp_cmd_pbsz, FTP_HOOK_LAST,
 2934                  FTP_TAKE1 | FTP_NEW_FEAT,
 2935                  "<sp> decimal-integer");
 2936 
 2937     ftp_hook_cmd("PORT", ftp_cmd_port, FTP_HOOK_LAST,
 2938                  FTP_NEED_LOGIN | FTP_TAKE1,
 2939                  "<sp> b0, b1, b1, b3, b4, b5");
 2940 
 2941     /* PROT does not require a user to be logged in */
 2942     ftp_hook_cmd("PROT", ftp_cmd_prot, FTP_HOOK_LAST,
 2943                  FTP_TAKE1 | FTP_NEW_FEAT,
 2944                  "<sp> prot-code");
 2945 
 2946     ftp_hook_cmd("PWD", ftp_cmd_pwd, FTP_HOOK_LAST,
 2947                  FTP_NEED_LOGIN | FTP_TAKE0,
 2948                  "(return current directory)");
 2949 
 2950     ftp_hook_cmd("QUIT", ftp_cmd_quit, FTP_HOOK_LAST,
 2951                  FTP_TAKE0 | FTP_DATA_INTR,
 2952                  "(terminate service)");
 2953 
 2954     ftp_hook_cmd("REIN", NULL, FTP_HOOK_LAST,
 2955                  FTP_NEED_LOGIN | FTP_TAKE0 | FTP_DATA_INTR,
 2956                  "(reinitialize server)");
 2957 
 2958     ftp_hook_cmd("REST", ftp_cmd_rest, FTP_HOOK_LAST,
 2959                  FTP_NEED_LOGIN | FTP_TAKE1,
 2960                  "<sp> offset (restart command)");
 2961 
 2962     ftp_feat_advert("REST STREAM");
 2963 
 2964     ftp_hook_cmd("RETR", ftp_cmd_retr, FTP_HOOK_LAST,
 2965                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2966                  "<sp> file-name");
 2967 
 2968     ftp_hook_cmd("RMD", ftp_cmd_rmd, FTP_HOOK_LAST,
 2969                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2970                  "<sp> path-name");
 2971 
 2972     ftp_hook_cmd("RNFR", ftp_cmd_rnfr, FTP_HOOK_LAST,
 2973                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2974                  "<sp> file-name");
 2975 
 2976     ftp_hook_cmd("RNTO", ftp_cmd_rnto, FTP_HOOK_LAST,
 2977                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2978                  "<sp> file-name");
 2979 
 2980     ftp_hook_cmd("SITE", NULL, FTP_HOOK_LAST,
 2981                  FTP_NEED_LOGIN | FTP_TAKE1 | FTP_EXTENSIBLE,
 2982                  "<sp> site-cmd [ <sp> arguments ]");
 2983 
 2984     ftp_hook_cmd("SIZE", ftp_cmd_size, FTP_HOOK_LAST,
 2985                  FTP_NEED_LOGIN | FTP_TAKE1_PATH | FTP_NEW_FEAT,
 2986                  "<sp> path-name");
 2987 
 2988     ftp_hook_cmd("SMNT", NULL, FTP_HOOK_LAST,
 2989                  FTP_NEED_LOGIN | FTP_TAKE0,
 2990                  "(structure mount)");
 2991 
 2992     ftp_hook_cmd("STAT", NULL, FTP_HOOK_LAST,
 2993                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2994                  "[ <sp> path-name ]");
 2995 
 2996     ftp_hook_cmd("STOR", ftp_cmd_stor, FTP_HOOK_LAST,
 2997                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 2998                  "<sp> file-name");
 2999 
 3000     ftp_hook_cmd("STOU", NULL, FTP_HOOK_LAST,
 3001                  FTP_NEED_LOGIN | FTP_TAKE1_PATH,
 3002                  "<sp> file-name");
 3003 
 3004     ftp_hook_cmd("STRU", ftp_cmd_stru, FTP_HOOK_LAST,
 3005                  FTP_NEED_LOGIN | FTP_TAKE1,
 3006                  "<sp> [ F | R | P ] (specify file structure)");
 3007 
 3008     ftp_hook_cmd("SYST", ftp_cmd_syst, FTP_HOOK_LAST,
 3009                  FTP_NEED_LOGIN | FTP_TAKE0,
 3010                  "(get type of operating system)");
 3011 
 3012     /* RFC3659 TVFS advertising simply insists that '/' is not a filename
 3013      * character, but a path delimiter in a tree structured filesystem.
 3014      * That's us, advertise it;
 3015      */
 3016     ftp_feat_advert("TVFS");
 3017 
 3018     ftp_hook_cmd("TYPE", ftp_cmd_type, FTP_HOOK_LAST,
 3019                  FTP_NEED_LOGIN | FTP_TAKE1,
 3020                  "<sp> [ A [ fmt ] | E [ fmt ] | I | L size ]");
 3021 
 3022     ftp_hook_cmd("USER", ftp_cmd_user, FTP_HOOK_LAST,
 3023                  FTP_TAKE1,
 3024                  "<sp> username");
 3025 
 3026     /* RFC2640 offers UTF-8; it does not insist all octets are UTF-8,
 3027      * even for arbitrary file names.  This reflects 'most unix' today.
 3028      * Because we want to permit admins not to advertise support,
 3029      * this is handled in post-config; see mod_ftp.c.
 3030      */
 3031     /* ftp_feat_advert("UTF8"); */
 3032 
 3033     /* Unadvertised, deprecated RFC775 X-flavor CDUP */
 3034     ftp_hook_cmd_alias("XCUP", "CDUP", FTP_HOOK_LAST,
 3035                        FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE0,
 3036                        "change to parent directory");
 3037 
 3038     /* Unadvertised, deprecated RFC542 X-flavor CWD */
 3039     ftp_hook_cmd_alias("XCWD", "CWD", FTP_HOOK_LAST,
 3040                        FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE1,
 3041                        "[ <sp> directory-name ]");
 3042 
 3043     /* Unadvertised, deprecated RFC775 X-flavor MKD */
 3044     ftp_hook_cmd_alias("XMKD", "MKD", FTP_HOOK_LAST,
 3045                        FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE1,
 3046                        "<sp> path-name");
 3047 
 3048     /* Unadvertised, deprecated RFC775 X-flavor PWD */
 3049     ftp_hook_cmd_alias("XPWD", "PWD", FTP_HOOK_LAST,
 3050                        FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE0,
 3051                        "(return current directory)");
 3052 
 3053     /* Unadvertised, deprecated RFC775 X-flavor RMD */
 3054     ftp_hook_cmd_alias("XRMD", "RMD", FTP_HOOK_LAST,
 3055                        FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE1,
 3056                        "<sp> path-name");
 3057 
 3058 }