Blame modules/proxy/mod_proxy_ftp.c

Packit 90a5c9
/* Licensed to the Apache Software Foundation (ASF) under one or more
Packit 90a5c9
 * contributor license agreements.  See the NOTICE file distributed with
Packit 90a5c9
 * this work for additional information regarding copyright ownership.
Packit 90a5c9
 * The ASF licenses this file to You under the Apache License, Version 2.0
Packit 90a5c9
 * (the "License"); you may not use this file except in compliance with
Packit 90a5c9
 * the License.  You may obtain a copy of the License at
Packit 90a5c9
 *
Packit 90a5c9
 *     http://www.apache.org/licenses/LICENSE-2.0
Packit 90a5c9
 *
Packit 90a5c9
 * Unless required by applicable law or agreed to in writing, software
Packit 90a5c9
 * distributed under the License is distributed on an "AS IS" BASIS,
Packit 90a5c9
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Packit 90a5c9
 * See the License for the specific language governing permissions and
Packit 90a5c9
 * limitations under the License.
Packit 90a5c9
 */
Packit 90a5c9
Packit 90a5c9
/* FTP routines for Apache proxy */
Packit 90a5c9
Packit 90a5c9
#define APR_WANT_BYTEFUNC
Packit 90a5c9
#include "mod_proxy.h"
Packit 90a5c9
#if APR_HAVE_TIME_H
Packit 90a5c9
#include <time.h>
Packit 90a5c9
#endif
Packit 90a5c9
#include "apr_version.h"
Packit 90a5c9
Packit 90a5c9
#if (APR_MAJOR_VERSION < 1)
Packit 90a5c9
#undef apr_socket_create
Packit 90a5c9
#define apr_socket_create apr_socket_create_ex
Packit 90a5c9
#endif
Packit 90a5c9
Packit 90a5c9
#define AUTODETECT_PWD
Packit 90a5c9
/* Automatic timestamping (Last-Modified header) based on MDTM is used if:
Packit 90a5c9
 * 1) the FTP server supports the MDTM command and
Packit 90a5c9
 * 2) HAVE_TIMEGM (preferred) or HAVE_GMTOFF is available at compile time
Packit 90a5c9
 */
Packit 90a5c9
#define USE_MDTM
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
module AP_MODULE_DECLARE_DATA proxy_ftp_module;
Packit 90a5c9
Packit 90a5c9
typedef struct {
Packit 90a5c9
    int ftp_list_on_wildcard;
Packit 90a5c9
    int ftp_list_on_wildcard_set;
Packit 90a5c9
    int ftp_escape_wildcards;
Packit 90a5c9
    int ftp_escape_wildcards_set;
Packit 90a5c9
    const char *ftp_directory_charset;
Packit 90a5c9
} proxy_ftp_dir_conf;
Packit 90a5c9
Packit 90a5c9
static void *create_proxy_ftp_dir_config(apr_pool_t *p, char *dummy)
Packit 90a5c9
{
Packit 90a5c9
    proxy_ftp_dir_conf *new =
Packit 90a5c9
        (proxy_ftp_dir_conf *) apr_pcalloc(p, sizeof(proxy_ftp_dir_conf));
Packit 90a5c9
Packit 90a5c9
    /* Put these in the dir config so they work inside <Location> */
Packit 90a5c9
    new->ftp_list_on_wildcard = 1;
Packit 90a5c9
    new->ftp_escape_wildcards = 1;
Packit 90a5c9
Packit 90a5c9
    return (void *) new;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void *merge_proxy_ftp_dir_config(apr_pool_t *p, void *basev, void *addv)
Packit 90a5c9
{
Packit 90a5c9
    proxy_ftp_dir_conf *new = (proxy_ftp_dir_conf *) apr_pcalloc(p, sizeof(proxy_ftp_dir_conf));
Packit 90a5c9
    proxy_ftp_dir_conf *add = (proxy_ftp_dir_conf *) addv;
Packit 90a5c9
    proxy_ftp_dir_conf *base = (proxy_ftp_dir_conf *) basev;
Packit 90a5c9
Packit 90a5c9
    /* Put these in the dir config so they work inside <Location> */
Packit 90a5c9
    new->ftp_list_on_wildcard = add->ftp_list_on_wildcard_set ?
Packit 90a5c9
                                add->ftp_list_on_wildcard :
Packit 90a5c9
                                base->ftp_list_on_wildcard;
Packit 90a5c9
    new->ftp_list_on_wildcard_set = add->ftp_list_on_wildcard_set ?
Packit 90a5c9
                                1 :
Packit 90a5c9
                                base->ftp_list_on_wildcard_set;
Packit 90a5c9
    new->ftp_escape_wildcards = add->ftp_escape_wildcards_set ?
Packit 90a5c9
                                add->ftp_escape_wildcards :
Packit 90a5c9
                                base->ftp_escape_wildcards;
Packit 90a5c9
    new->ftp_escape_wildcards_set = add->ftp_escape_wildcards_set ?
Packit 90a5c9
                                1 :
Packit 90a5c9
                                base->ftp_escape_wildcards_set;
Packit 90a5c9
    new->ftp_directory_charset = add->ftp_directory_charset ?
Packit 90a5c9
                                 add->ftp_directory_charset :
Packit 90a5c9
                                 base->ftp_directory_charset;
Packit 90a5c9
    return new;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static const char *set_ftp_list_on_wildcard(cmd_parms *cmd, void *dconf,
Packit 90a5c9
                                            int flag)
Packit 90a5c9
{
Packit 90a5c9
    proxy_ftp_dir_conf *conf = dconf;
Packit 90a5c9
Packit 90a5c9
    conf->ftp_list_on_wildcard = flag;
Packit 90a5c9
    conf->ftp_list_on_wildcard_set = 1;
Packit 90a5c9
    return NULL;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static const char *set_ftp_escape_wildcards(cmd_parms *cmd, void *dconf,
Packit 90a5c9
                                            int flag)
Packit 90a5c9
{
Packit 90a5c9
    proxy_ftp_dir_conf *conf = dconf;
Packit 90a5c9
Packit 90a5c9
    conf->ftp_escape_wildcards = flag;
Packit 90a5c9
    conf->ftp_escape_wildcards_set = 1;
Packit 90a5c9
    return NULL;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static const char *set_ftp_directory_charset(cmd_parms *cmd, void *dconf,
Packit 90a5c9
                                             const char *arg)
Packit 90a5c9
{
Packit 90a5c9
    proxy_ftp_dir_conf *conf = dconf;
Packit 90a5c9
Packit 90a5c9
    conf->ftp_directory_charset = arg;
Packit 90a5c9
    return NULL;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * Decodes a '%' escaped string, and returns the number of characters
Packit 90a5c9
 */
Packit 90a5c9
static int decodeenc(char *x)
Packit 90a5c9
{
Packit 90a5c9
    int i, j, ch;
Packit 90a5c9
Packit 90a5c9
    if (x[0] == '\0')
Packit 90a5c9
        return 0;               /* special case for no characters */
Packit 90a5c9
    for (i = 0, j = 0; x[i] != '\0'; i++, j++) {
Packit 90a5c9
        /* decode it if not already done */
Packit 90a5c9
        ch = x[i];
Packit 90a5c9
        if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
Packit 90a5c9
            ch = ap_proxy_hex2c(&x[i + 1]);
Packit 90a5c9
            i += 2;
Packit 90a5c9
        }
Packit 90a5c9
        x[j] = ch;
Packit 90a5c9
    }
Packit 90a5c9
    x[j] = '\0';
Packit 90a5c9
    return j;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * Escape the globbing characters in a path used as argument to
Packit 90a5c9
 * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
Packit 90a5c9
 * ftpd assumes '\\' as a quoting character to escape special characters.
Packit 90a5c9
 * Just returns the original string if ProxyFtpEscapeWildcards has been
Packit 90a5c9
 * configured "off".
Packit 90a5c9
 * Returns: escaped string
Packit 90a5c9
 */
Packit 90a5c9
#define FTP_GLOBBING_CHARS "*?[{~"
Packit 90a5c9
static const char *ftp_escape_globbingchars(apr_pool_t *p, const char *path, proxy_ftp_dir_conf *dconf)
Packit 90a5c9
{
Packit 90a5c9
    char *ret;
Packit 90a5c9
    char *d;
Packit 90a5c9
Packit 90a5c9
    if (!dconf->ftp_escape_wildcards) {
Packit 90a5c9
        return path;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    ret = apr_palloc(p, 2*strlen(path)+sizeof(""));
Packit 90a5c9
    for (d = ret; *path; ++path) {
Packit 90a5c9
        if (strchr(FTP_GLOBBING_CHARS, *path) != NULL)
Packit 90a5c9
            *d++ = '\\';
Packit 90a5c9
        *d++ = *path;
Packit 90a5c9
    }
Packit 90a5c9
    *d = '\0';
Packit 90a5c9
    return ret;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * Check for globbing characters in a path used as argument to
Packit 90a5c9
 * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
Packit 90a5c9
 * ftpd assumes '\\' as a quoting character to escape special characters.
Packit 90a5c9
 * Returns: 0 (no globbing chars, or all globbing chars escaped), 1 (globbing chars)
Packit 90a5c9
 */
Packit 90a5c9
static int ftp_check_globbingchars(const char *path)
Packit 90a5c9
{
Packit 90a5c9
    for ( ; *path; ++path) {
Packit 90a5c9
        if (*path == '\\')
Packit 90a5c9
            ++path;
Packit 90a5c9
        if (*path != '\0' && strchr(FTP_GLOBBING_CHARS, *path) != NULL)
Packit 90a5c9
            return TRUE;
Packit 90a5c9
    }
Packit 90a5c9
    return FALSE;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * checks an encoded ftp string for bad characters, namely, CR, LF or
Packit 90a5c9
 * non-ascii character
Packit 90a5c9
 */
Packit 90a5c9
static int ftp_check_string(const char *x)
Packit 90a5c9
{
Packit 90a5c9
    int i, ch = 0;
Packit 90a5c9
#if APR_CHARSET_EBCDIC
Packit 90a5c9
    char buf[1];
Packit 90a5c9
#endif
Packit 90a5c9
Packit 90a5c9
    for (i = 0; x[i] != '\0'; i++) {
Packit 90a5c9
        ch = x[i];
Packit 90a5c9
        if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
Packit 90a5c9
            ch = ap_proxy_hex2c(&x[i + 1]);
Packit 90a5c9
            i += 2;
Packit 90a5c9
        }
Packit 90a5c9
#if !APR_CHARSET_EBCDIC
Packit 90a5c9
        if (ch == '\015' || ch == '\012' || (ch & 0x80))
Packit 90a5c9
#else                           /* APR_CHARSET_EBCDIC */
Packit 90a5c9
        if (ch == '\r' || ch == '\n')
Packit 90a5c9
            return 0;
Packit 90a5c9
        buf[0] = ch;
Packit 90a5c9
        ap_xlate_proto_to_ascii(buf, 1);
Packit 90a5c9
        if (buf[0] & 0x80)
Packit 90a5c9
#endif                          /* APR_CHARSET_EBCDIC */
Packit 90a5c9
            return 0;
Packit 90a5c9
    }
Packit 90a5c9
    return 1;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * converts a series of buckets into a string
Packit 90a5c9
 * XXX: BillS says this function performs essentially the same function as
Packit 90a5c9
 * ap_rgetline() in protocol.c. Deprecate this function and use ap_rgetline()
Packit 90a5c9
 * instead? I think ftp_string_read() will not work properly on non ASCII
Packit 90a5c9
 * (EBCDIC) machines either.
Packit 90a5c9
 */
Packit 90a5c9
static apr_status_t ftp_string_read(conn_rec *c, apr_bucket_brigade *bb,
Packit 38e228
        char *buff, apr_size_t bufflen, int *eos, apr_size_t *outlen)
Packit 90a5c9
{
Packit 90a5c9
    apr_bucket *e;
Packit 90a5c9
    apr_status_t rv;
Packit 90a5c9
    char *pos = buff;
Packit 90a5c9
    char *response;
Packit 90a5c9
    int found = 0;
Packit 90a5c9
    apr_size_t len;
Packit 90a5c9
Packit 90a5c9
    /* start with an empty string */
Packit 90a5c9
    buff[0] = 0;
Packit 90a5c9
    *eos = 0;
Packit 38e228
    *outlen = 0;
Packit 90a5c9
Packit 90a5c9
    /* loop through each brigade */
Packit 90a5c9
    while (!found) {
Packit 90a5c9
        /* get brigade from network one line at a time */
Packit 90a5c9
        if (APR_SUCCESS != (rv = ap_get_brigade(c->input_filters, bb,
Packit 90a5c9
                                                AP_MODE_GETLINE,
Packit 90a5c9
                                                APR_BLOCK_READ,
Packit 90a5c9
                                                0))) {
Packit 90a5c9
            return rv;
Packit 90a5c9
        }
Packit 90a5c9
        /* loop through each bucket */
Packit 90a5c9
        while (!found) {
Packit 90a5c9
            if (*eos || APR_BRIGADE_EMPTY(bb)) {
Packit 90a5c9
                /* The connection aborted or timed out */
Packit 90a5c9
                return APR_ECONNABORTED;
Packit 90a5c9
            }
Packit 90a5c9
            e = APR_BRIGADE_FIRST(bb);
Packit 90a5c9
            if (APR_BUCKET_IS_EOS(e)) {
Packit 90a5c9
                *eos = 1;
Packit 90a5c9
            }
Packit 90a5c9
            else {
Packit 90a5c9
                if (APR_SUCCESS != (rv = apr_bucket_read(e,
Packit 90a5c9
                                                         (const char **)&response,
Packit 90a5c9
                                                         &len,
Packit 90a5c9
                                                         APR_BLOCK_READ))) {
Packit 90a5c9
                    return rv;
Packit 90a5c9
                }
Packit 90a5c9
                /*
Packit 90a5c9
                 * is string LF terminated?
Packit 90a5c9
                 * XXX: This check can be made more efficient by simply checking
Packit 90a5c9
                 * if the last character in the 'response' buffer is an ASCII_LF.
Packit 90a5c9
                 * See ap_rgetline() for an example.
Packit 90a5c9
                 */
Packit 90a5c9
                if (memchr(response, APR_ASCII_LF, len)) {
Packit 90a5c9
                    found = 1;
Packit 90a5c9
                }
Packit 90a5c9
                /* concat strings until buff is full - then throw the data away */
Packit 90a5c9
                if (len > ((bufflen-1)-(pos-buff))) {
Packit 90a5c9
                    len = (bufflen-1)-(pos-buff);
Packit 90a5c9
                }
Packit 90a5c9
                if (len > 0) {
Packit 90a5c9
                    memcpy(pos, response, len);
Packit 90a5c9
                    pos += len;
Packit 38e228
                    *outlen += len;
Packit 90a5c9
                }
Packit 90a5c9
            }
Packit 90a5c9
            apr_bucket_delete(e);
Packit 90a5c9
        }
Packit 90a5c9
        *pos = '\0';
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    return APR_SUCCESS;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * Canonicalise ftp URLs.
Packit 90a5c9
 */
Packit 90a5c9
static int proxy_ftp_canon(request_rec *r, char *url)
Packit 90a5c9
{
Packit 90a5c9
    char *user, *password, *host, *path, *parms, *strp, sport[7];
Packit 90a5c9
    apr_pool_t *p = r->pool;
Packit 90a5c9
    const char *err;
Packit 90a5c9
    apr_port_t port, def_port;
Packit 90a5c9
Packit 90a5c9
    /* */
Packit 90a5c9
    if (strncasecmp(url, "ftp:", 4) == 0) {
Packit 90a5c9
        url += 4;
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        return DECLINED;
Packit 90a5c9
    }
Packit 90a5c9
    def_port = apr_uri_port_of_scheme("ftp");
Packit 90a5c9
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
Packit 90a5c9
Packit 90a5c9
    port = def_port;
Packit 90a5c9
    err = ap_proxy_canon_netloc(p, &url, &user, &password, &host, &port);
Packit 90a5c9
    if (err)
Packit 90a5c9
        return HTTP_BAD_REQUEST;
Packit 90a5c9
    if (user != NULL && !ftp_check_string(user))
Packit 90a5c9
        return HTTP_BAD_REQUEST;
Packit 90a5c9
    if (password != NULL && !ftp_check_string(password))
Packit 90a5c9
        return HTTP_BAD_REQUEST;
Packit 90a5c9
Packit 90a5c9
    /* now parse path/parameters args, according to rfc1738 */
Packit 90a5c9
    /*
Packit 90a5c9
     * N.B. if this isn't a true proxy request, then the URL path (but not
Packit 90a5c9
     * query args) has already been decoded. This gives rise to the problem
Packit 90a5c9
     * of a ; being decoded into the path.
Packit 90a5c9
     */
Packit 90a5c9
    strp = strchr(url, ';');
Packit 90a5c9
    if (strp != NULL) {
Packit 90a5c9
        *(strp++) = '\0';
Packit 90a5c9
        parms = ap_proxy_canonenc(p, strp, strlen(strp), enc_parm, 0,
Packit 90a5c9
                                  r->proxyreq);
Packit 90a5c9
        if (parms == NULL)
Packit 90a5c9
            return HTTP_BAD_REQUEST;
Packit 90a5c9
    }
Packit 90a5c9
    else
Packit 90a5c9
        parms = "";
Packit 90a5c9
Packit 90a5c9
    path = ap_proxy_canonenc(p, url, strlen(url), enc_path, 0, r->proxyreq);
Packit 90a5c9
    if (path == NULL)
Packit 90a5c9
        return HTTP_BAD_REQUEST;
Packit 90a5c9
    if (!ftp_check_string(path))
Packit 90a5c9
        return HTTP_BAD_REQUEST;
Packit 90a5c9
Packit 90a5c9
    if (r->proxyreq && r->args != NULL) {
Packit 90a5c9
        if (strp != NULL) {
Packit 90a5c9
            strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_parm, 1, r->proxyreq);
Packit 90a5c9
            if (strp == NULL)
Packit 90a5c9
                return HTTP_BAD_REQUEST;
Packit 90a5c9
            parms = apr_pstrcat(p, parms, "?", strp, NULL);
Packit 90a5c9
        }
Packit 90a5c9
        else {
Packit 90a5c9
            strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_fpath, 1, r->proxyreq);
Packit 90a5c9
            if (strp == NULL)
Packit 90a5c9
                return HTTP_BAD_REQUEST;
Packit 90a5c9
            path = apr_pstrcat(p, path, "?", strp, NULL);
Packit 90a5c9
        }
Packit 90a5c9
        r->args = NULL;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
/* now, rebuild URL */
Packit 90a5c9
Packit 90a5c9
    if (port != def_port)
Packit 90a5c9
        apr_snprintf(sport, sizeof(sport), ":%d", port);
Packit 90a5c9
    else
Packit 90a5c9
        sport[0] = '\0';
Packit 90a5c9
Packit 90a5c9
    if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
Packit 90a5c9
        host = apr_pstrcat(p, "[", host, "]", NULL);
Packit 90a5c9
    }
Packit 90a5c9
    r->filename = apr_pstrcat(p, "proxy:ftp://", (user != NULL) ? user : "",
Packit 90a5c9
                              (password != NULL) ? ":" : "",
Packit 90a5c9
                              (password != NULL) ? password : "",
Packit 90a5c9
                          (user != NULL) ? "@" : "", host, sport, "/", path,
Packit 90a5c9
                              (parms[0] != '\0') ? ";" : "", parms, NULL);
Packit 90a5c9
Packit 90a5c9
    return OK;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/* we chop lines longer than 80 characters */
Packit 90a5c9
#define MAX_LINE_LEN 80
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * Reads response lines, returns both the ftp status code and
Packit 90a5c9
 * remembers the response message in the supplied buffer
Packit 90a5c9
 */
Packit 90a5c9
static int ftp_getrc_msg(conn_rec *ftp_ctrl, apr_bucket_brigade *bb, char *msgbuf, int msglen)
Packit 90a5c9
{
Packit 90a5c9
    int status;
Packit 90a5c9
    char response[MAX_LINE_LEN];
Packit 90a5c9
    char buff[5];
Packit 90a5c9
    char *mb = msgbuf, *me = &msgbuf[msglen];
Packit 90a5c9
    apr_status_t rv;
Packit 38e228
    apr_size_t nread;
Packit 38e228
    
Packit 90a5c9
    int eos;
Packit 90a5c9
Packit 38e228
    if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos, &nread))) {
Packit 90a5c9
        return -1;
Packit 90a5c9
    }
Packit 90a5c9
/*
Packit 90a5c9
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(03233)
Packit 90a5c9
                 "<%s", response);
Packit 90a5c9
*/
Packit 38e228
    if (nread < 4) { 
Packit 38e228
        ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, APLOGNO(10229) "Malformed FTP response '%s'", response);
Packit 38e228
        *mb = '\0';
Packit 38e228
        return -1;
Packit 38e228
    }
Packit 38e228
Packit 90a5c9
    if (!apr_isdigit(response[0]) || !apr_isdigit(response[1]) ||
Packit 38e228
        !apr_isdigit(response[2]) || (response[3] != ' ' && response[3] != '-'))
Packit 90a5c9
        status = 0;
Packit 90a5c9
    else
Packit 90a5c9
        status = 100 * response[0] + 10 * response[1] + response[2] - 111 * '0';
Packit 90a5c9
Packit 90a5c9
    mb = apr_cpystrn(mb, response + 4, me - mb);
Packit 90a5c9
Packit 38e228
    if (response[3] == '-') { /* multi-line reply "123-foo\nbar\n123 baz" */
Packit 90a5c9
        memcpy(buff, response, 3);
Packit 90a5c9
        buff[3] = ' ';
Packit 90a5c9
        do {
Packit 38e228
            if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos, &nread))) {
Packit 90a5c9
                return -1;
Packit 90a5c9
            }
Packit 90a5c9
            mb = apr_cpystrn(mb, response + (' ' == response[0] ? 1 : 4), me - mb);
Packit 90a5c9
        } while (memcmp(response, buff, 4) != 0);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    return status;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/* this is a filter that turns a raw ASCII directory listing into pretty HTML */
Packit 90a5c9
Packit 90a5c9
/* ideally, mod_proxy should simply send the raw directory list up the filter
Packit 90a5c9
 * stack to mod_autoindex, which in theory should turn the raw ascii into
Packit 90a5c9
 * pretty html along with all the bells and whistles it provides...
Packit 90a5c9
 *
Packit 90a5c9
 * all in good time...! :)
Packit 90a5c9
 */
Packit 90a5c9
Packit 90a5c9
typedef struct {
Packit 90a5c9
    apr_bucket_brigade *in;
Packit 90a5c9
    char buffer[MAX_STRING_LEN];
Packit 90a5c9
    enum {
Packit 90a5c9
        HEADER, BODY, FOOTER
Packit 90a5c9
    }    state;
Packit 90a5c9
}      proxy_dir_ctx_t;
Packit 90a5c9
Packit 90a5c9
/* fallback regex for ls -s1;  ($0..$2) == 3 */
Packit 90a5c9
#define LS_REG_PATTERN "^ *([0-9]+) +([^ ]+)$"
Packit 90a5c9
#define LS_REG_MATCH   3
Packit 90a5c9
static ap_regex_t *ls_regex;
Packit 90a5c9
Packit 90a5c9
static apr_status_t proxy_send_dir_filter(ap_filter_t *f,
Packit 90a5c9
                                          apr_bucket_brigade *in)
Packit 90a5c9
{
Packit 90a5c9
    request_rec *r = f->r;
Packit 90a5c9
    conn_rec *c = r->connection;
Packit 90a5c9
    apr_pool_t *p = r->pool;
Packit 90a5c9
    apr_bucket_brigade *out = apr_brigade_create(p, c->bucket_alloc);
Packit 90a5c9
    apr_status_t rv;
Packit 90a5c9
Packit 90a5c9
    int n;
Packit 90a5c9
    char *dir, *path, *reldir, *site, *str, *type;
Packit 90a5c9
Packit 90a5c9
    const char *pwd = apr_table_get(r->notes, "Directory-PWD");
Packit 90a5c9
    const char *readme = apr_table_get(r->notes, "Directory-README");
Packit 90a5c9
Packit 90a5c9
    proxy_dir_ctx_t *ctx = f->ctx;
Packit 90a5c9
Packit 90a5c9
    if (!ctx) {
Packit 90a5c9
        f->ctx = ctx = apr_pcalloc(p, sizeof(*ctx));
Packit 90a5c9
        ctx->in = apr_brigade_create(p, c->bucket_alloc);
Packit 90a5c9
        ctx->buffer[0] = 0;
Packit 90a5c9
        ctx->state = HEADER;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* combine the stored and the new */
Packit 90a5c9
    APR_BRIGADE_CONCAT(ctx->in, in);
Packit 90a5c9
Packit 90a5c9
    if (HEADER == ctx->state) {
Packit 90a5c9
Packit 90a5c9
        /* basedir is either "", or "/%2f" for the "squid %2f hack" */
Packit 90a5c9
        const char *basedir = "";  /* By default, path is relative to the $HOME dir */
Packit 90a5c9
        char *wildcard = NULL;
Packit 90a5c9
        const char *escpath;
Packit 90a5c9
Packit 90a5c9
        /*
Packit 90a5c9
         * In the reverse proxy case we need to construct our site string
Packit 90a5c9
         * via ap_construct_url. For non anonymous sites apr_uri_unparse would
Packit 90a5c9
         * only supply us with 'username@' which leads to the construction of
Packit 90a5c9
         * an invalid base href later on. Losing the username part of the URL
Packit 90a5c9
         * is no problem in the reverse proxy case as the browser sents the
Packit 90a5c9
         * credentials anyway once entered.
Packit 90a5c9
         */
Packit 90a5c9
        if (r->proxyreq == PROXYREQ_REVERSE) {
Packit 90a5c9
            site = ap_construct_url(p, "", r);
Packit 90a5c9
        }
Packit 90a5c9
        else {
Packit 90a5c9
            /* Save "scheme://site" prefix without password */
Packit 90a5c9
            site = apr_uri_unparse(p, &f->r->parsed_uri,
Packit 90a5c9
                                   APR_URI_UNP_OMITPASSWORD |
Packit 90a5c9
                                   APR_URI_UNP_OMITPATHINFO);
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* ... and path without query args */
Packit 90a5c9
        path = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITSITEPART | APR_URI_UNP_OMITQUERY);
Packit 90a5c9
Packit 90a5c9
        /* If path began with /%2f, change the basedir */
Packit 90a5c9
        if (strncasecmp(path, "/%2f", 4) == 0) {
Packit 90a5c9
            basedir = "/%2f";
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* Strip off a type qualifier. It is ignored for dir listings */
Packit 90a5c9
        if ((type = strstr(path, ";type=")) != NULL)
Packit 90a5c9
            *type++ = '\0';
Packit 90a5c9
Packit 90a5c9
        (void)decodeenc(path);
Packit 90a5c9
Packit 90a5c9
        while (path[1] == '/') /* collapse multiple leading slashes to one */
Packit 90a5c9
            ++path;
Packit 90a5c9
Packit 90a5c9
        reldir = strrchr(path, '/');
Packit 90a5c9
        if (reldir != NULL && ftp_check_globbingchars(reldir)) {
Packit 90a5c9
            wildcard = &reldir[1];
Packit 90a5c9
            reldir[0] = '\0'; /* strip off the wildcard suffix */
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* Copy path, strip (all except the last) trailing slashes */
Packit 90a5c9
        /* (the trailing slash is needed for the dir component loop below) */
Packit 90a5c9
        path = dir = apr_pstrcat(p, path, "/", NULL);
Packit 90a5c9
        for (n = strlen(path); n > 1 && path[n - 1] == '/' && path[n - 2] == '/'; --n)
Packit 90a5c9
            path[n - 1] = '\0';
Packit 90a5c9
Packit 90a5c9
        /* Add a link to the root directory (if %2f hack was used) */
Packit 90a5c9
        str = (basedir[0] != '\0') ? "%2f/" : "";
Packit 90a5c9
Packit 90a5c9
        /* print "ftp://host/" */
Packit 90a5c9
        escpath = ap_escape_html(p, path);
Packit 90a5c9
        str = apr_psprintf(p, DOCTYPE_HTML_3_2
Packit 90a5c9
                "<html>\n <head>\n  <title>%s%s%s</title>\n"
Packit 90a5c9
                "<base href=\"%s%s%s\">\n"
Packit 90a5c9
                " </head>\n"
Packit 90a5c9
                " <body>\n  

Directory of "

Packit 90a5c9
                "%s/%s",
Packit 90a5c9
                ap_escape_html(p, site), basedir, escpath,
Packit 90a5c9
                ap_escape_uri(p, site), basedir, escpath,
Packit 90a5c9
                ap_escape_uri(p, site), str);
Packit 90a5c9
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
Packit 90a5c9
                                                          p, c->bucket_alloc));
Packit 90a5c9
Packit 90a5c9
        for (dir = path+1; (dir = strchr(dir, '/')) != NULL; )
Packit 90a5c9
        {
Packit 90a5c9
            *dir = '\0';
Packit 90a5c9
            if ((reldir = strrchr(path+1, '/'))==NULL) {
Packit 90a5c9
                reldir = path+1;
Packit 90a5c9
            }
Packit 90a5c9
            else
Packit 90a5c9
                ++reldir;
Packit 90a5c9
            /* print "path/" component */
Packit 90a5c9
            str = apr_psprintf(p, "%s/", basedir,
Packit 90a5c9
                        ap_escape_uri(p, path),
Packit 90a5c9
                        ap_escape_html(p, reldir));
Packit 90a5c9
            *dir = '/';
Packit 90a5c9
            while (*dir == '/')
Packit 90a5c9
              ++dir;
Packit 90a5c9
            APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
Packit 90a5c9
                                                           strlen(str), p,
Packit 90a5c9
                                                           c->bucket_alloc));
Packit 90a5c9
        }
Packit 90a5c9
        if (wildcard != NULL) {
Packit 90a5c9
            wildcard = ap_escape_html(p, wildcard);
Packit 90a5c9
            APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(wildcard,
Packit 90a5c9
                                                           strlen(wildcard), p,
Packit 90a5c9
                                                           c->bucket_alloc));
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* If the caller has determined the current directory, and it differs */
Packit 90a5c9
        /* from what the client requested, then show the real name */
Packit 90a5c9
        if (pwd == NULL || strncmp(pwd, path, strlen(pwd)) == 0) {
Packit 90a5c9
            str = apr_psprintf(p, "\n\n  
\n\n
");
Packit 90a5c9
        }
Packit 90a5c9
        else {
Packit 90a5c9
            str = apr_psprintf(p, "\n\n(%s)\n\n  
\n\n
",
Packit 90a5c9
                               ap_escape_html(p, pwd));
Packit 90a5c9
        }
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
Packit 90a5c9
                                                           p, c->bucket_alloc));
Packit 90a5c9
Packit 90a5c9
        /* print README */
Packit 90a5c9
        if (readme) {
Packit 90a5c9
            str = apr_psprintf(p, "%s\n\n\n
\n\n
\n",
Packit 90a5c9
                               ap_escape_html(p, readme));
Packit 90a5c9
Packit 90a5c9
            APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
Packit 90a5c9
                                                           strlen(str), p,
Packit 90a5c9
                                                           c->bucket_alloc));
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* make sure page intro gets sent out */
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
Packit 90a5c9
        if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
Packit 90a5c9
            return rv;
Packit 90a5c9
        }
Packit 90a5c9
        apr_brigade_cleanup(out);
Packit 90a5c9
Packit 90a5c9
        ctx->state = BODY;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* loop through each line of directory */
Packit 90a5c9
    while (BODY == ctx->state) {
Packit 90a5c9
        char *filename;
Packit 90a5c9
        int found = 0;
Packit 90a5c9
        int eos = 0;
Packit 90a5c9
        ap_regmatch_t re_result[LS_REG_MATCH];
Packit 90a5c9
Packit 90a5c9
        /* get a complete line */
Packit 90a5c9
        /* if the buffer overruns - throw data away */
Packit 90a5c9
        while (!found && !APR_BRIGADE_EMPTY(ctx->in)) {
Packit 90a5c9
            char *pos, *response;
Packit 90a5c9
            apr_size_t len, max;
Packit 90a5c9
            apr_bucket *e;
Packit 90a5c9
Packit 90a5c9
            e = APR_BRIGADE_FIRST(ctx->in);
Packit 90a5c9
            if (APR_BUCKET_IS_EOS(e)) {
Packit 90a5c9
                eos = 1;
Packit 90a5c9
                break;
Packit 90a5c9
            }
Packit 90a5c9
            if (APR_SUCCESS != (rv = apr_bucket_read(e, (const char **)&response, &len, APR_BLOCK_READ))) {
Packit 90a5c9
                return rv;
Packit 90a5c9
            }
Packit 90a5c9
            pos = memchr(response, APR_ASCII_LF, len);
Packit 90a5c9
            if (pos != NULL) {
Packit 90a5c9
                if ((response + len) != (pos + 1)) {
Packit 90a5c9
                    len = pos - response + 1;
Packit 90a5c9
                    apr_bucket_split(e, pos - response + 1);
Packit 90a5c9
                }
Packit 90a5c9
                found = 1;
Packit 90a5c9
            }
Packit 90a5c9
            max = sizeof(ctx->buffer) - strlen(ctx->buffer) - 1;
Packit 90a5c9
            if (len > max) {
Packit 90a5c9
                len = max;
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
            /* len+1 to leave space for the trailing nil char */
Packit 90a5c9
            apr_cpystrn(ctx->buffer+strlen(ctx->buffer), response, len+1);
Packit 90a5c9
Packit 90a5c9
            apr_bucket_delete(e);
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* EOS? jump to footer */
Packit 90a5c9
        if (eos) {
Packit 90a5c9
            ctx->state = FOOTER;
Packit 90a5c9
            break;
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* not complete? leave and try get some more */
Packit 90a5c9
        if (!found) {
Packit 90a5c9
            return APR_SUCCESS;
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        {
Packit 90a5c9
            apr_size_t n = strlen(ctx->buffer);
Packit 90a5c9
            if (ctx->buffer[n-1] == CRLF[1])  /* strip trailing '\n' */
Packit 90a5c9
                ctx->buffer[--n] = '\0';
Packit 90a5c9
            if (ctx->buffer[n-1] == CRLF[0])  /* strip trailing '\r' if present */
Packit 90a5c9
                ctx->buffer[--n] = '\0';
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* a symlink? */
Packit 90a5c9
        if (ctx->buffer[0] == 'l' && (filename = strstr(ctx->buffer, " -> ")) != NULL) {
Packit 90a5c9
            char *link_ptr = filename;
Packit 90a5c9
Packit 90a5c9
            do {
Packit 90a5c9
                filename--;
Packit 90a5c9
            } while (filename[0] != ' ' && filename > ctx->buffer);
Packit 90a5c9
            if (filename > ctx->buffer)
Packit 90a5c9
                *(filename++) = '\0';
Packit 90a5c9
            *(link_ptr++) = '\0';
Packit 90a5c9
            str = apr_psprintf(p, "%s %s %s\n",
Packit 90a5c9
                               ap_escape_html(p, ctx->buffer),
Packit 90a5c9
                               ap_escape_uri(p, filename),
Packit 90a5c9
                               ap_escape_html(p, filename),
Packit 90a5c9
                               ap_escape_html(p, link_ptr));
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* a directory/file? */
Packit 90a5c9
        else if (ctx->buffer[0] == 'd' || ctx->buffer[0] == '-' || ctx->buffer[0] == 'l' || apr_isdigit(ctx->buffer[0])) {
Packit 90a5c9
            int searchidx = 0;
Packit 90a5c9
            char *searchptr = NULL;
Packit 90a5c9
            int firstfile = 1;
Packit 90a5c9
            if (apr_isdigit(ctx->buffer[0])) {  /* handle DOS dir */
Packit 90a5c9
                searchptr = strchr(ctx->buffer, '<');
Packit 90a5c9
                if (searchptr != NULL)
Packit 90a5c9
                    *searchptr = '[';
Packit 90a5c9
                searchptr = strchr(ctx->buffer, '>');
Packit 90a5c9
                if (searchptr != NULL)
Packit 90a5c9
                    *searchptr = ']';
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
            filename = strrchr(ctx->buffer, ' ');
Packit 90a5c9
            if (filename == NULL) {
Packit 90a5c9
                /* Line is broken.  Ignore it. */
Packit 90a5c9
                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01034)
Packit 90a5c9
                              "proxy_ftp: could not parse line %s",
Packit 90a5c9
                              ctx->buffer);
Packit 90a5c9
                /* erase buffer for next time around */
Packit 90a5c9
                ctx->buffer[0] = 0;
Packit 90a5c9
                continue;  /* while state is BODY */
Packit 90a5c9
            }
Packit 90a5c9
            *(filename++) = '\0';
Packit 90a5c9
Packit 90a5c9
            /* handle filenames with spaces in 'em */
Packit 90a5c9
            if (!strcmp(filename, ".") || !strcmp(filename, "..") || firstfile) {
Packit 90a5c9
                firstfile = 0;
Packit 90a5c9
                searchidx = filename - ctx->buffer;
Packit 90a5c9
            }
Packit 90a5c9
            else if (searchidx != 0 && ctx->buffer[searchidx] != 0) {
Packit 90a5c9
                *(--filename) = ' ';
Packit 90a5c9
                ctx->buffer[searchidx - 1] = '\0';
Packit 90a5c9
                filename = &ctx->buffer[searchidx];
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
            /* Append a slash to the HREF link for directories */
Packit 90a5c9
            if (!strcmp(filename, ".") || !strcmp(filename, "..") || ctx->buffer[0] == 'd') {
Packit 90a5c9
                str = apr_psprintf(p, "%s %s\n",
Packit 90a5c9
                                   ap_escape_html(p, ctx->buffer),
Packit 90a5c9
                                   ap_escape_uri(p, filename),
Packit 90a5c9
                                   ap_escape_html(p, filename));
Packit 90a5c9
            }
Packit 90a5c9
            else {
Packit 90a5c9
                str = apr_psprintf(p, "%s %s\n",
Packit 90a5c9
                                   ap_escape_html(p, ctx->buffer),
Packit 90a5c9
                                   ap_escape_uri(p, filename),
Packit 90a5c9
                                   ap_escape_html(p, filename));
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
        /* Try a fallback for listings in the format of "ls -s1" */
Packit 90a5c9
        else if (0 == ap_regexec(ls_regex, ctx->buffer, LS_REG_MATCH, re_result, 0)) {
Packit 90a5c9
            /*
Packit 90a5c9
             * We don't need to check for rm_eo == rm_so == -1 here since ls_regex
Packit 90a5c9
             * is such that $2 cannot be unset if we have a match.
Packit 90a5c9
             */
Packit 90a5c9
            filename = apr_pstrndup(p, &ctx->buffer[re_result[2].rm_so], re_result[2].rm_eo - re_result[2].rm_so);
Packit 90a5c9
Packit 90a5c9
            str = apr_pstrcat(p, ap_escape_html(p, apr_pstrndup(p, ctx->buffer, re_result[2].rm_so)),
Packit 90a5c9
                              "",
Packit 90a5c9
                              ap_escape_html(p, filename), "\n", NULL);
Packit 90a5c9
        }
Packit 90a5c9
        else {
Packit 90a5c9
            strcat(ctx->buffer, "\n"); /* re-append the newline */
Packit 90a5c9
            str = ap_escape_html(p, ctx->buffer);
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* erase buffer for next time around */
Packit 90a5c9
        ctx->buffer[0] = 0;
Packit 90a5c9
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
Packit 90a5c9
                                                            c->bucket_alloc));
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
Packit 90a5c9
        if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
Packit 90a5c9
            return rv;
Packit 90a5c9
        }
Packit 90a5c9
        apr_brigade_cleanup(out);
Packit 90a5c9
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    if (FOOTER == ctx->state) {
Packit 90a5c9
        str = apr_psprintf(p, "\n\n  
\n\n %s\n\n </body>\n</html>\n", ap_psignature("", r));
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
Packit 90a5c9
                                                            c->bucket_alloc));
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(out, apr_bucket_eos_create(c->bucket_alloc));
Packit 90a5c9
        if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
Packit 90a5c9
            return rv;
Packit 90a5c9
        }
Packit 90a5c9
        apr_brigade_destroy(out);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    return APR_SUCCESS;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/* Parse EPSV reply and return port, or zero on error. */
Packit 90a5c9
static apr_port_t parse_epsv_reply(const char *reply)
Packit 90a5c9
{
Packit 90a5c9
    const char *p;
Packit 90a5c9
    char *ep;
Packit 90a5c9
    long port;
Packit 90a5c9
Packit 90a5c9
    /* Reply syntax per RFC 2428: "229 blah blah (|||port|)" where '|'
Packit 90a5c9
     * can be any character in ASCII from 33-126, obscurely.  Verify
Packit 90a5c9
     * the syntax. */
Packit 90a5c9
    p = ap_strchr_c(reply, '(');
Packit 90a5c9
    if (p == NULL || !p[1] || p[1] != p[2] || p[1] != p[3]
Packit 90a5c9
        || p[4] == p[1]) {
Packit 90a5c9
        return 0;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    errno = 0;
Packit 90a5c9
    port = strtol(p + 4, &ep, 10);
Packit 90a5c9
    if (errno || port < 1 || port > 65535 || ep[0] != p[1] || ep[1] != ')') {
Packit 90a5c9
        return 0;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    return (apr_port_t)port;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * Generic "send FTP command to server" routine, using the control socket.
Packit 90a5c9
 * Returns the FTP returncode (3 digit code)
Packit 90a5c9
 * Allows for tracing the FTP protocol (in LogLevel debug)
Packit 90a5c9
 */
Packit 90a5c9
static int
Packit 90a5c9
proxy_ftp_command(const char *cmd, request_rec *r, conn_rec *ftp_ctrl,
Packit 90a5c9
                  apr_bucket_brigade *bb, char **pmessage)
Packit 90a5c9
{
Packit 90a5c9
    char *crlf;
Packit 90a5c9
    int rc;
Packit 90a5c9
    char message[HUGE_STRING_LEN];
Packit 90a5c9
Packit 90a5c9
    /* If cmd == NULL, we retrieve the next ftp response line */
Packit 90a5c9
    if (cmd != NULL) {
Packit 90a5c9
        conn_rec *c = r->connection;
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(cmd, strlen(cmd), r->pool, c->bucket_alloc));
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(c->bucket_alloc));
Packit 90a5c9
        ap_pass_brigade(ftp_ctrl->output_filters, bb);
Packit 90a5c9
Packit 90a5c9
        /* strip off the CRLF for logging */
Packit 90a5c9
        apr_cpystrn(message, cmd, sizeof(message));
Packit 90a5c9
        if ((crlf = strchr(message, '\r')) != NULL ||
Packit 90a5c9
            (crlf = strchr(message, '\n')) != NULL)
Packit 90a5c9
            *crlf = '\0';
Packit 90a5c9
        if (strncmp(message,"PASS ", 5) == 0)
Packit 90a5c9
            strcpy(&message[5], "****");
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, ">%s", message);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    rc = ftp_getrc_msg(ftp_ctrl, bb, message, sizeof message);
Packit 90a5c9
    if (rc == -1 || rc == 421)
Packit 90a5c9
        strcpy(message,"<unable to read result>");
Packit 90a5c9
    if ((crlf = strchr(message, '\r')) != NULL ||
Packit 90a5c9
        (crlf = strchr(message, '\n')) != NULL)
Packit 90a5c9
        *crlf = '\0';
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "<%3.3u %s", rc, message);
Packit 90a5c9
Packit 90a5c9
    if (pmessage != NULL)
Packit 90a5c9
        *pmessage = apr_pstrdup(r->pool, message);
Packit 90a5c9
Packit 90a5c9
    return rc;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/* Set ftp server to TYPE {A,I,E} before transfer of a directory or file */
Packit 90a5c9
static int ftp_set_TYPE(char xfer_type, request_rec *r, conn_rec *ftp_ctrl,
Packit 90a5c9
                  apr_bucket_brigade *bb, char **pmessage)
Packit 90a5c9
{
Packit 90a5c9
    char old_type[2] = { 'A', '\0' }; /* After logon, mode is ASCII */
Packit 90a5c9
    int ret = HTTP_OK;
Packit 90a5c9
    int rc;
Packit 90a5c9
Packit 90a5c9
    /* set desired type */
Packit 90a5c9
    old_type[0] = xfer_type;
Packit 90a5c9
Packit 90a5c9
    rc = proxy_ftp_command(apr_pstrcat(r->pool, "TYPE ", old_type, CRLF, NULL),
Packit 90a5c9
                           r, ftp_ctrl, bb, pmessage);
Packit 90a5c9
/* responses: 200, 421, 500, 501, 504, 530 */
Packit 90a5c9
    /* 200 Command okay. */
Packit 90a5c9
    /* 421 Service not available, closing control connection. */
Packit 90a5c9
    /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
    /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
    /* 504 Command not implemented for that parameter. */
Packit 90a5c9
    /* 530 Not logged in. */
Packit 90a5c9
    if (rc == -1 || rc == 421) {
Packit 90a5c9
        ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
Packit 90a5c9
                             "Error reading from remote server");
Packit 90a5c9
    }
Packit 90a5c9
    else if (rc != 200 && rc != 504) {
Packit 90a5c9
        ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
Packit 90a5c9
                             "Unable to set transfer type");
Packit 90a5c9
    }
Packit 90a5c9
/* Allow not implemented */
Packit 90a5c9
    else if (rc == 504) {
Packit 90a5c9
        /* ignore it silently */
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    return ret;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
/* Return the current directory which we have selected on the FTP server, or NULL */
Packit 90a5c9
static char *ftp_get_PWD(request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
Packit 90a5c9
{
Packit 90a5c9
    char *cwd = NULL;
Packit 90a5c9
    char *ftpmessage = NULL;
Packit 90a5c9
Packit 90a5c9
    /* responses: 257, 500, 501, 502, 421, 550 */
Packit 90a5c9
    /* 257 "<directory-name>" <commentary> */
Packit 90a5c9
    /* 421 Service not available, closing control connection. */
Packit 90a5c9
    /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
    /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
    /* 502 Command not implemented. */
Packit 90a5c9
    /* 550 Requested action not taken. */
Packit 90a5c9
    switch (proxy_ftp_command("PWD" CRLF, r, ftp_ctrl, bb, &ftpmessage)) {
Packit 90a5c9
        case -1:
Packit 90a5c9
        case 421:
Packit 90a5c9
        case 550:
Packit 90a5c9
            ap_proxyerror(r, HTTP_BAD_GATEWAY,
Packit 90a5c9
                             "Failed to read PWD on ftp server");
Packit 90a5c9
            break;
Packit 90a5c9
Packit 90a5c9
        case 257: {
Packit 90a5c9
            const char *dirp = ftpmessage;
Packit 90a5c9
            cwd = ap_getword_conf(r->pool, &dirp);
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return cwd;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
/* Common routine for failed authorization (i.e., missing or wrong password)
Packit 90a5c9
 * to an ftp service. This causes most browsers to retry the request
Packit 90a5c9
 * with username and password (which was presumably queried from the user)
Packit 90a5c9
 * supplied in the Authorization: header.
Packit 90a5c9
 * Note that we "invent" a realm name which consists of the
Packit 90a5c9
 * ftp://user@host part of the reqest (sans password -if supplied but invalid-)
Packit 90a5c9
 */
Packit 90a5c9
static int ftp_unauthorized(request_rec *r, int log_it)
Packit 90a5c9
{
Packit 90a5c9
    r->proxyreq = PROXYREQ_NONE;
Packit 90a5c9
    /*
Packit 90a5c9
     * Log failed requests if they supplied a password (log username/password
Packit 90a5c9
     * guessing attempts)
Packit 90a5c9
     */
Packit 90a5c9
    if (log_it)
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01035)
Packit 90a5c9
                      "missing or failed auth to %s",
Packit 90a5c9
                      apr_uri_unparse(r->pool,
Packit 90a5c9
                                 &r->parsed_uri, APR_URI_UNP_OMITPATHINFO));
Packit 90a5c9
Packit 90a5c9
    apr_table_setn(r->err_headers_out, "WWW-Authenticate",
Packit 90a5c9
                   apr_pstrcat(r->pool, "Basic realm=\"",
Packit 90a5c9
                               apr_uri_unparse(r->pool, &r->parsed_uri,
Packit 90a5c9
                       APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO),
Packit 90a5c9
                               "\"", NULL));
Packit 90a5c9
Packit 90a5c9
    return HTTP_UNAUTHORIZED;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static
Packit 90a5c9
apr_status_t proxy_ftp_cleanup(request_rec *r, proxy_conn_rec *backend)
Packit 90a5c9
{
Packit 90a5c9
Packit 90a5c9
    backend->close = 1;
Packit 90a5c9
    ap_set_module_config(r->connection->conn_config, &proxy_ftp_module, NULL);
Packit 90a5c9
    ap_proxy_release_connection("FTP", backend, r->server);
Packit 90a5c9
Packit 90a5c9
    return OK;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static
Packit 90a5c9
int ftp_proxyerror(request_rec *r, proxy_conn_rec *conn, int statuscode, const char *message)
Packit 90a5c9
{
Packit 90a5c9
    proxy_ftp_cleanup(r, conn);
Packit 90a5c9
    return ap_proxyerror(r, statuscode, message);
Packit 90a5c9
}
Packit 90a5c9
/*
Packit 90a5c9
 * Handles direct access of ftp:// URLs
Packit 90a5c9
 * Original (Non-PASV) version from
Packit 90a5c9
 * Troy Morrison <spiffnet@zoom.com>
Packit 90a5c9
 * PASV added by Chuck
Packit 90a5c9
 * Filters by [Graham Leggett <minfrin@sharp.fm>]
Packit 90a5c9
 */
Packit 90a5c9
static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
Packit 90a5c9
                             proxy_server_conf *conf, char *url,
Packit 90a5c9
                             const char *proxyhost, apr_port_t proxyport)
Packit 90a5c9
{
Packit 90a5c9
    apr_pool_t *p = r->pool;
Packit 90a5c9
    conn_rec *c = r->connection;
Packit 90a5c9
    proxy_conn_rec *backend;
Packit 90a5c9
    apr_socket_t *sock, *local_sock, *data_sock = NULL;
Packit 90a5c9
    apr_sockaddr_t *connect_addr = NULL;
Packit 90a5c9
    apr_status_t rv;
Packit 90a5c9
    conn_rec *origin, *data = NULL;
Packit 90a5c9
    apr_status_t err = APR_SUCCESS;
Packit 90a5c9
    apr_status_t uerr = APR_SUCCESS;
Packit 90a5c9
    apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
Packit 90a5c9
    char *buf, *connectname;
Packit 90a5c9
    apr_port_t connectport;
Packit 90a5c9
    char *ftpmessage = NULL;
Packit 90a5c9
    char *path, *strp, *type_suffix, *cwd = NULL;
Packit 90a5c9
    apr_uri_t uri;
Packit 90a5c9
    char *user = NULL;
Packit 90a5c9
/*    char *account = NULL; how to supply an account in a URL? */
Packit 90a5c9
    const char *password = NULL;
Packit 90a5c9
    int len, rc;
Packit 90a5c9
    int one = 1;
Packit 90a5c9
    char *size = NULL;
Packit 90a5c9
    char xfer_type = 'A'; /* after ftp login, the default is ASCII */
Packit 90a5c9
    int  dirlisting = 0;
Packit 90a5c9
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
Packit 90a5c9
    apr_time_t mtime = 0L;
Packit 90a5c9
#endif
Packit 90a5c9
    proxy_ftp_dir_conf *fdconf = ap_get_module_config(r->per_dir_config,
Packit 90a5c9
                                                      &proxy_ftp_module);
Packit 90a5c9
Packit 90a5c9
    /* stuff for PASV mode */
Packit 90a5c9
    int connect = 0, use_port = 0;
Packit 90a5c9
    char dates[APR_RFC822_DATE_LEN];
Packit 90a5c9
    int status;
Packit 90a5c9
    apr_pool_t *address_pool;
Packit 90a5c9
Packit 90a5c9
    /* is this for us? */
Packit 90a5c9
    if (proxyhost) {
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
Packit 90a5c9
                      "declining URL %s - proxyhost %s specified:", url,
Packit 90a5c9
                      proxyhost);
Packit 90a5c9
        return DECLINED;        /* proxy connections are via HTTP */
Packit 90a5c9
    }
Packit 90a5c9
    if (strncasecmp(url, "ftp:", 4)) {
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
Packit 90a5c9
                      "declining URL %s - not ftp:", url);
Packit 90a5c9
        return DECLINED;        /* only interested in FTP */
Packit 90a5c9
    }
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "serving URL %s", url);
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * I: Who Do I Connect To? -----------------------
Packit 90a5c9
     *
Packit 90a5c9
     * Break up the URL to determine the host to connect to
Packit 90a5c9
     */
Packit 90a5c9
Packit 90a5c9
    /* we only support GET and HEAD */
Packit 90a5c9
    if (r->method_number != M_GET)
Packit 90a5c9
        return HTTP_NOT_IMPLEMENTED;
Packit 90a5c9
Packit 90a5c9
    /* We break the URL into host, port, path-search */
Packit 90a5c9
    if (r->parsed_uri.hostname == NULL) {
Packit 90a5c9
        if (APR_SUCCESS != apr_uri_parse(p, url, &uri)) {
Packit f2471b
            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO() 
Packit f2471b
                          "URI cannot be parsed: %s", url);
Packit f2471b
            return ap_proxyerror(r, HTTP_BAD_REQUEST, "URI cannot be parsed");
Packit 90a5c9
        }
Packit 90a5c9
        connectname = uri.hostname;
Packit 90a5c9
        connectport = uri.port;
Packit 90a5c9
        path = apr_pstrdup(p, uri.path);
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        connectname = r->parsed_uri.hostname;
Packit 90a5c9
        connectport = r->parsed_uri.port;
Packit 90a5c9
        path = apr_pstrdup(p, r->parsed_uri.path);
Packit 90a5c9
    }
Packit 90a5c9
    if (connectport == 0) {
Packit 90a5c9
        connectport = apr_uri_port_of_scheme("ftp");
Packit 90a5c9
    }
Packit 90a5c9
    path = (path != NULL && path[0] != '\0') ? &path[1] : "";
Packit 90a5c9
Packit 90a5c9
    type_suffix = strchr(path, ';');
Packit 90a5c9
    if (type_suffix != NULL)
Packit 90a5c9
        *(type_suffix++) = '\0';
Packit 90a5c9
Packit 90a5c9
    if (type_suffix != NULL && strncmp(type_suffix, "type=", 5) == 0
Packit 90a5c9
        && apr_isalpha(type_suffix[5])) {
Packit 90a5c9
        /* "type=d" forces a dir listing.
Packit 90a5c9
         * The other types (i|a|e) are directly used for the ftp TYPE command
Packit 90a5c9
         */
Packit 90a5c9
        if ( ! (dirlisting = (apr_tolower(type_suffix[5]) == 'd')))
Packit 90a5c9
            xfer_type = apr_toupper(type_suffix[5]);
Packit 90a5c9
Packit 90a5c9
        /* Check valid types, rather than ignoring invalid types silently: */
Packit 90a5c9
        if (strchr("AEI", xfer_type) == NULL)
Packit 90a5c9
            return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
Packit 90a5c9
                                    "ftp proxy supports only types 'a', 'i', or 'e': \"",
Packit 90a5c9
                                    type_suffix, "\" is invalid.", NULL));
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        /* make binary transfers the default */
Packit 90a5c9
        xfer_type = 'I';
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * The "Authorization:" header must be checked first. We allow the user
Packit 90a5c9
     * to "override" the URL-coded user [ & password ] in the Browsers'
Packit 90a5c9
     * User&Password Dialog. NOTE that this is only marginally more secure
Packit 90a5c9
     * than having the password travel in plain as part of the URL, because
Packit 90a5c9
     * Basic Auth simply uuencodes the plain text password. But chances are
Packit 90a5c9
     * still smaller that the URL is logged regularly.
Packit 90a5c9
     */
Packit 90a5c9
    if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
Packit 90a5c9
        && strcasecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
Packit 90a5c9
        && (password = ap_pbase64decode(r->pool, password))[0] != ':') {
Packit 90a5c9
        /* Check the decoded string for special characters. */
Packit 90a5c9
        if (!ftp_check_string(password)) {
Packit 90a5c9
            return ap_proxyerror(r, HTTP_BAD_REQUEST,
Packit 90a5c9
                                 "user credentials contained invalid character");
Packit 90a5c9
        }
Packit 90a5c9
        /*
Packit 90a5c9
         * Note that this allocation has to be made from r->connection->pool
Packit 90a5c9
         * because it has the lifetime of the connection.  The other
Packit 90a5c9
         * allocations are temporary and can be tossed away any time.
Packit 90a5c9
         */
Packit 90a5c9
        user = ap_getword_nulls(r->connection->pool, &password, ':');
Packit 90a5c9
        r->ap_auth_type = "Basic";
Packit 90a5c9
        r->user = r->parsed_uri.user = user;
Packit 90a5c9
    }
Packit 90a5c9
    else if ((user = r->parsed_uri.user) != NULL) {
Packit 90a5c9
        user = apr_pstrdup(p, user);
Packit 90a5c9
        decodeenc(user);
Packit 90a5c9
        if ((password = r->parsed_uri.password) != NULL) {
Packit 90a5c9
            char *tmp = apr_pstrdup(p, password);
Packit 90a5c9
            decodeenc(tmp);
Packit 90a5c9
            password = tmp;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        user = "anonymous";
Packit 90a5c9
        password = "apache-proxy@";
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01036)
Packit 90a5c9
                  "connecting %s to %s:%d", url, connectname, connectport);
Packit 90a5c9
Packit 90a5c9
    if (worker->s->is_address_reusable) {
Packit 90a5c9
        if (!worker->cp->addr) {
Packit 90a5c9
            if ((err = PROXY_THREAD_LOCK(worker->balancer)) != APR_SUCCESS) {
Packit 90a5c9
                ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(01037) "lock");
Packit 90a5c9
                return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
        connect_addr = worker->cp->addr;
Packit 90a5c9
        address_pool = worker->cp->pool;
Packit 90a5c9
    }
Packit 90a5c9
    else
Packit 90a5c9
        address_pool = r->pool;
Packit 90a5c9
Packit 90a5c9
    /* do a DNS lookup for the destination host */
Packit 90a5c9
    if (!connect_addr)
Packit 90a5c9
        err = apr_sockaddr_info_get(&(connect_addr),
Packit 90a5c9
                                    connectname, APR_UNSPEC,
Packit 90a5c9
                                    connectport, 0,
Packit 90a5c9
                                    address_pool);
Packit 90a5c9
    if (worker->s->is_address_reusable && !worker->cp->addr) {
Packit 90a5c9
        worker->cp->addr = connect_addr;
Packit 90a5c9
        if ((uerr = PROXY_THREAD_UNLOCK(worker->balancer)) != APR_SUCCESS) {
Packit 90a5c9
            ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(01038) "unlock");
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    /*
Packit 90a5c9
     * get all the possible IP addresses for the destname and loop through
Packit 90a5c9
     * them until we get a successful connection
Packit 90a5c9
     */
Packit 90a5c9
    if (APR_SUCCESS != err) {
Packit 90a5c9
        return ap_proxyerror(r, HTTP_BAD_GATEWAY, apr_pstrcat(p,
Packit 90a5c9
                                                 "DNS lookup failure for: ",
Packit 90a5c9
                                                        connectname, NULL));
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* check if ProxyBlock directive on this host */
Packit 90a5c9
    if (OK != ap_proxy_checkproxyblock2(r, conf, connectname, connect_addr)) {
Packit 90a5c9
        return ap_proxyerror(r, HTTP_FORBIDDEN,
Packit 90a5c9
                             "Connect to remote machine blocked");
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* create space for state information */
Packit 90a5c9
    backend = (proxy_conn_rec *) ap_get_module_config(c->conn_config, &proxy_ftp_module);
Packit 90a5c9
    if (!backend) {
Packit 90a5c9
        status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server);
Packit 90a5c9
        if (status != OK) {
Packit 90a5c9
            if (backend) {
Packit 90a5c9
                backend->close = 1;
Packit 90a5c9
                ap_proxy_release_connection("FTP", backend, r->server);
Packit 90a5c9
            }
Packit 90a5c9
            return status;
Packit 90a5c9
        }
Packit 90a5c9
        /* TODO: see if ftp could use determine_connection */
Packit 90a5c9
        backend->addr = connect_addr;
Packit 90a5c9
        ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * II: Make the Connection -----------------------
Packit 90a5c9
     *
Packit 90a5c9
     * We have determined who to connect to. Now make the connection.
Packit 90a5c9
     */
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
    if (ap_proxy_connect_backend("FTP", backend, worker, r->server)) {
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01039)
Packit 90a5c9
                      "an error occurred creating a new connection to %pI (%s)",
Packit 90a5c9
                      connect_addr, connectname);
Packit 90a5c9
        proxy_ftp_cleanup(r, backend);
Packit 90a5c9
        return HTTP_SERVICE_UNAVAILABLE;
Packit 90a5c9
    }
Packit 90a5c9
Packit f02de7
    status = ap_proxy_connection_create_ex("FTP", backend, r);
Packit f02de7
    if (status != OK) {
Packit f02de7
        proxy_ftp_cleanup(r, backend);
Packit f02de7
        return status;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* Use old naming */
Packit 90a5c9
    origin = backend->connection;
Packit 90a5c9
    sock = backend->sock;
Packit 90a5c9
Packit 90a5c9
    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
Packit 90a5c9
                  "control connection complete");
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * III: Send Control Request -------------------------
Packit 90a5c9
     *
Packit 90a5c9
     * Log into the ftp server, send the username & password, change to the
Packit 90a5c9
     * correct directory...
Packit 90a5c9
     */
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
    /* possible results: */
Packit 90a5c9
    /* 120 Service ready in nnn minutes. */
Packit 90a5c9
    /* 220 Service ready for new user. */
Packit 90a5c9
    /* 421 Service not available, closing control connection. */
Packit 90a5c9
    rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
Packit 90a5c9
    if (rc == -1 || rc == 421) {
Packit 90a5c9
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
Packit 90a5c9
    }
Packit 90a5c9
    if (rc == 120) {
Packit 90a5c9
        /*
Packit 90a5c9
         * RFC2616 states: 14.37 Retry-After
Packit 90a5c9
         *
Packit 90a5c9
         * The Retry-After response-header field can be used with a 503 (Service
Packit 90a5c9
         * Unavailable) response to indicate how long the service is expected
Packit 90a5c9
         * to be unavailable to the requesting client. [...] The value of
Packit 90a5c9
         * this field can be either an HTTP-date or an integer number of
Packit 90a5c9
         * seconds (in decimal) after the time of the response. Retry-After
Packit 90a5c9
         * = "Retry-After" ":" ( HTTP-date | delta-seconds )
Packit 90a5c9
         */
Packit 90a5c9
        char *secs_str = ftpmessage;
Packit 90a5c9
        time_t secs;
Packit 90a5c9
Packit 90a5c9
        /* Look for a number, preceded by whitespace */
Packit 90a5c9
        while (*secs_str)
Packit 90a5c9
            if ((secs_str==ftpmessage || apr_isspace(secs_str[-1])) &&
Packit 90a5c9
                apr_isdigit(secs_str[0]))
Packit 90a5c9
                break;
Packit 90a5c9
        if (*secs_str != '\0') {
Packit 90a5c9
            secs = atol(secs_str);
Packit 90a5c9
            apr_table_addn(r->headers_out, "Retry-After",
Packit 90a5c9
                           apr_psprintf(p, "%lu", (unsigned long)(60 * secs)));
Packit 90a5c9
        }
Packit 90a5c9
        return ftp_proxyerror(r, backend, HTTP_SERVICE_UNAVAILABLE, ftpmessage);
Packit 90a5c9
    }
Packit 90a5c9
    if (rc != 220) {
Packit 90a5c9
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    rc = proxy_ftp_command(apr_pstrcat(p, "USER ", user, CRLF, NULL),
Packit 90a5c9
                           r, origin, bb, &ftpmessage);
Packit 90a5c9
    /* possible results; 230, 331, 332, 421, 500, 501, 530 */
Packit 90a5c9
    /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */
Packit 90a5c9
    /* 230 User logged in, proceed. */
Packit 90a5c9
    /* 331 User name okay, need password. */
Packit 90a5c9
    /* 332 Need account for login. */
Packit 90a5c9
    /* 421 Service not available, closing control connection. */
Packit 90a5c9
    /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
    /* (This may include errors such as command line too long.) */
Packit 90a5c9
    /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
    /* 530 Not logged in. */
Packit 90a5c9
    if (rc == -1 || rc == 421) {
Packit 90a5c9
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
Packit 90a5c9
    }
Packit 90a5c9
    if (rc == 530) {
Packit 90a5c9
        proxy_ftp_cleanup(r, backend);
Packit 90a5c9
        return ftp_unauthorized(r, 1);  /* log it: user name guessing
Packit 90a5c9
                                         * attempt? */
Packit 90a5c9
    }
Packit 90a5c9
    if (rc != 230 && rc != 331) {
Packit 90a5c9
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    if (rc == 331) {            /* send password */
Packit 90a5c9
        if (password == NULL) {
Packit 90a5c9
            proxy_ftp_cleanup(r, backend);
Packit 90a5c9
            return ftp_unauthorized(r, 0);
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        rc = proxy_ftp_command(apr_pstrcat(p, "PASS ", password, CRLF, NULL),
Packit 90a5c9
                           r, origin, bb, &ftpmessage);
Packit 90a5c9
        /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */
Packit 90a5c9
        /* 230 User logged in, proceed. */
Packit 90a5c9
        /* 332 Need account for login. */
Packit 90a5c9
        /* 421 Service not available, closing control connection. */
Packit 90a5c9
        /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
        /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
        /* 503 Bad sequence of commands. */
Packit 90a5c9
        /* 530 Not logged in. */
Packit 90a5c9
        if (rc == -1 || rc == 421) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                  "Error reading from remote server");
Packit 90a5c9
        }
Packit 90a5c9
        if (rc == 332) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_UNAUTHORIZED,
Packit 90a5c9
                  apr_pstrcat(p, "Need account for login: ", ftpmessage, NULL));
Packit 90a5c9
        }
Packit 90a5c9
        /* @@@ questionable -- we might as well return a 403 Forbidden here */
Packit 90a5c9
        if (rc == 530) {
Packit 90a5c9
            proxy_ftp_cleanup(r, backend);
Packit 90a5c9
            return ftp_unauthorized(r, 1);      /* log it: passwd guessing
Packit 90a5c9
                                                 * attempt? */
Packit 90a5c9
        }
Packit 90a5c9
        if (rc != 230 && rc != 202) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    apr_table_set(r->notes, "Directory-README", ftpmessage);
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
    /* Special handling for leading "%2f": this enforces a "cwd /"
Packit 90a5c9
     * out of the $HOME directory which was the starting point after login
Packit 90a5c9
     */
Packit 90a5c9
    if (strncasecmp(path, "%2f", 3) == 0) {
Packit 90a5c9
        path += 3;
Packit 90a5c9
        while (*path == '/') /* skip leading '/' (after root %2f) */
Packit 90a5c9
            ++path;
Packit 90a5c9
Packit 90a5c9
        rc = proxy_ftp_command("CWD /" CRLF, r, origin, bb, &ftpmessage);
Packit 90a5c9
        if (rc == -1 || rc == 421)
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                  "Error reading from remote server");
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * set the directory (walk directory component by component): this is
Packit 90a5c9
     * what we must do if we don't know the OS type of the remote machine
Packit 90a5c9
     */
Packit 90a5c9
    for (;;) {
Packit 90a5c9
        strp = strchr(path, '/');
Packit 90a5c9
        if (strp == NULL)
Packit 90a5c9
            break;
Packit 90a5c9
        *strp = '\0';
Packit 90a5c9
Packit 90a5c9
        decodeenc(path); /* Note! This decodes a %2f -> "/" */
Packit 90a5c9
Packit 90a5c9
        if (strchr(path, '/')) { /* are there now any '/' characters? */
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
Packit 90a5c9
                                  "Use of /%2f is only allowed at the base directory");
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* NOTE: FTP servers do globbing on the path.
Packit 90a5c9
         * So we need to escape the URI metacharacters.
Packit 90a5c9
         * We use a special glob-escaping routine to escape globbing chars.
Packit 90a5c9
         * We could also have extended gen_test_char.c with a special T_ESCAPE_FTP_PATH
Packit 90a5c9
         */
Packit 90a5c9
        rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
Packit 90a5c9
                           ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
Packit 90a5c9
                           r, origin, bb, &ftpmessage);
Packit 90a5c9
        *strp = '/';
Packit 90a5c9
        /* responses: 250, 421, 500, 501, 502, 530, 550 */
Packit 90a5c9
        /* 250 Requested file action okay, completed. */
Packit 90a5c9
        /* 421 Service not available, closing control connection. */
Packit 90a5c9
        /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
        /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
        /* 502 Command not implemented. */
Packit 90a5c9
        /* 530 Not logged in. */
Packit 90a5c9
        /* 550 Requested action not taken. */
Packit 90a5c9
        if (rc == -1 || rc == 421) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                  "Error reading from remote server");
Packit 90a5c9
        }
Packit 90a5c9
        if (rc == 550) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
Packit 90a5c9
        }
Packit 90a5c9
        if (rc != 250) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        path = strp + 1;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * IV: Make Data Connection? -------------------------
Packit 90a5c9
     *
Packit 90a5c9
     * Try EPSV, if that fails... try PASV, if that fails... try PORT.
Packit 90a5c9
     */
Packit 90a5c9
/* this temporarily switches off EPSV/PASV */
Packit 90a5c9
/*goto bypass;*/
Packit 90a5c9
Packit 90a5c9
    /* set up data connection - EPSV */
Packit 90a5c9
    {
Packit 90a5c9
        apr_port_t data_port;
Packit 90a5c9
Packit 90a5c9
        /*
Packit 90a5c9
         * The EPSV command replaces PASV where both IPV4 and IPV6 is
Packit 90a5c9
         * supported. Only the port is returned, the IP address is always the
Packit 90a5c9
         * same as that on the control connection. Example: Entering Extended
Packit 90a5c9
         * Passive Mode (|||6446|)
Packit 90a5c9
         */
Packit 90a5c9
        rc = proxy_ftp_command("EPSV" CRLF,
Packit 90a5c9
                           r, origin, bb, &ftpmessage);
Packit 90a5c9
        /* possible results: 227, 421, 500, 501, 502, 530 */
Packit 90a5c9
        /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
Packit 90a5c9
        /* 421 Service not available, closing control connection. */
Packit 90a5c9
        /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
        /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
        /* 502 Command not implemented. */
Packit 90a5c9
        /* 530 Not logged in. */
Packit 90a5c9
        if (rc == -1 || rc == 421) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                  "Error reading from remote server");
Packit 90a5c9
        }
Packit 90a5c9
        if (rc != 229 && rc != 500 && rc != 501 && rc != 502) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
        }
Packit 90a5c9
        else if (rc == 229) {
Packit 90a5c9
            /* Parse the port out of the EPSV reply. */
Packit 90a5c9
            data_port = parse_epsv_reply(ftpmessage);
Packit 90a5c9
Packit 90a5c9
            if (data_port) {
Packit 90a5c9
                apr_sockaddr_t *remote_addr, epsv_addr;
Packit 90a5c9
Packit 90a5c9
                ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
Packit 90a5c9
                              "EPSV contacting remote host on port %d", data_port);
Packit 90a5c9
Packit 90a5c9
                /* Retrieve the client's address. */
Packit 90a5c9
                rv = apr_socket_addr_get(&remote_addr, APR_REMOTE, sock);
Packit 90a5c9
                if (rv == APR_SUCCESS) {
Packit 90a5c9
                    /* Take a shallow copy of the server address to
Packit 90a5c9
                     * modify; the _addr_get function gives back a
Packit 90a5c9
                     * pointer to the socket's internal structure.
Packit 90a5c9
                     * This is awkward given current APR network
Packit 90a5c9
                     * interfaces. */
Packit 90a5c9
                    epsv_addr = *remote_addr;
Packit 90a5c9
                    epsv_addr.port = data_port;
Packit 90a5c9
#if APR_HAVE_IPV6
Packit 90a5c9
                    if (epsv_addr.family == APR_INET6) {
Packit 90a5c9
                        epsv_addr.sa.sin6.sin6_port = htons(data_port);
Packit 90a5c9
                    }
Packit 90a5c9
                    else
Packit 90a5c9
#endif
Packit 90a5c9
                    {
Packit 90a5c9
                        epsv_addr.sa.sin.sin_port = htons(data_port);
Packit 90a5c9
                    }
Packit 90a5c9
                    rv = apr_socket_create(&data_sock, epsv_addr.family, SOCK_STREAM, 0, r->pool);
Packit 90a5c9
                }
Packit 90a5c9
Packit 90a5c9
                if (rv != APR_SUCCESS) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01040) 
Packit 90a5c9
                                  "could not establish socket for client data connection");
Packit 90a5c9
                    proxy_ftp_cleanup(r, backend);
Packit 90a5c9
                    return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
                }
Packit 90a5c9
Packit 90a5c9
                if (conf->recv_buffer_size > 0
Packit 90a5c9
                        && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
Packit 90a5c9
                                                    conf->recv_buffer_size))) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01041)
Packit 90a5c9
                                  "apr_socket_opt_set(SO_RCVBUF): Failed to "
Packit 90a5c9
                                  "set ProxyReceiveBufferSize, using default");
Packit 90a5c9
                }
Packit 90a5c9
Packit 90a5c9
                rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
Packit 90a5c9
                if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01042)
Packit 90a5c9
                                  "apr_socket_opt_set(APR_TCP_NODELAY): "
Packit 90a5c9
                                  "Failed to set");
Packit 90a5c9
                }
Packit 90a5c9
Packit 90a5c9
                rv = apr_socket_connect(data_sock, &epsv_addr);
Packit 90a5c9
                if (rv != APR_SUCCESS) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01043)
Packit 90a5c9
                                  "EPSV attempt to connect to %pI failed - "
Packit 90a5c9
                                  "Firewall/NAT?", &epsv_addr);
Packit 90a5c9
                    return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
Packit 90a5c9
                                                                           "EPSV attempt to connect to %pI failed - firewall/NAT?", &epsv_addr));
Packit 90a5c9
                }
Packit 90a5c9
                else {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
Packit 90a5c9
                                  "connected data socket to %pI", &epsv_addr);
Packit 90a5c9
                    connect = 1;
Packit 90a5c9
                }
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* set up data connection - PASV */
Packit 90a5c9
    if (!connect) {
Packit 90a5c9
        rc = proxy_ftp_command("PASV" CRLF,
Packit 90a5c9
                           r, origin, bb, &ftpmessage);
Packit 90a5c9
        /* possible results: 227, 421, 500, 501, 502, 530 */
Packit 90a5c9
        /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
Packit 90a5c9
        /* 421 Service not available, closing control connection. */
Packit 90a5c9
        /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
        /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
        /* 502 Command not implemented. */
Packit 90a5c9
        /* 530 Not logged in. */
Packit 90a5c9
        if (rc == -1 || rc == 421) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                  "Error reading from remote server");
Packit 90a5c9
        }
Packit 90a5c9
        if (rc != 227 && rc != 502) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
        }
Packit 90a5c9
        else if (rc == 227) {
Packit 90a5c9
            unsigned int h0, h1, h2, h3, p0, p1;
Packit 90a5c9
            char *pstr;
Packit 90a5c9
            char *tok_cntx;
Packit 90a5c9
Packit 90a5c9
/* FIXME: Check PASV against RFC1123 */
Packit 90a5c9
Packit 90a5c9
            pstr = ftpmessage;
Packit 90a5c9
            pstr = apr_strtok(pstr, " ", &tok_cntx);    /* separate result code */
Packit 90a5c9
            if (pstr != NULL) {
Packit 90a5c9
                if (*(pstr + strlen(pstr) + 1) == '=') {
Packit 90a5c9
                    pstr += strlen(pstr) + 2;
Packit 90a5c9
                }
Packit 90a5c9
                else {
Packit 90a5c9
                    pstr = apr_strtok(NULL, "(", &tok_cntx);    /* separate address &
Packit 90a5c9
                                                                 * port params */
Packit 90a5c9
                    if (pstr != NULL)
Packit 90a5c9
                        pstr = apr_strtok(NULL, ")", &tok_cntx);
Packit 90a5c9
                }
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
/* FIXME: Only supports IPV4 - fix in RFC2428 */
Packit 90a5c9
Packit 90a5c9
            if (pstr != NULL && (sscanf(pstr,
Packit 90a5c9
                 "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) {
Packit 90a5c9
Packit 90a5c9
                apr_sockaddr_t *pasv_addr;
Packit 90a5c9
                apr_port_t pasvport = (p1 << 8) + p0;
Packit 90a5c9
                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01044)
Packit 90a5c9
                              "PASV contacting host %d.%d.%d.%d:%d",
Packit 90a5c9
                              h3, h2, h1, h0, pasvport);
Packit 90a5c9
Packit 90a5c9
                if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01045)
Packit 90a5c9
                                  "error creating PASV socket");
Packit 90a5c9
                    proxy_ftp_cleanup(r, backend);
Packit 90a5c9
                    return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
                }
Packit 90a5c9
Packit 90a5c9
                if (conf->recv_buffer_size > 0
Packit 90a5c9
                        && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
Packit 90a5c9
                                                    conf->recv_buffer_size))) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01046)
Packit 90a5c9
                                  "apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
Packit 90a5c9
                }
Packit 90a5c9
Packit 90a5c9
                rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
Packit 90a5c9
                if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01047)
Packit 90a5c9
                                  "apr_socket_opt_set(APR_TCP_NODELAY): "
Packit 90a5c9
                                  "Failed to set");
Packit 90a5c9
                }
Packit 90a5c9
Packit 90a5c9
                /* make the connection */
Packit 90a5c9
                apr_sockaddr_info_get(&pasv_addr, apr_psprintf(p, "%d.%d.%d.%d", h3, h2, h1, h0), connect_addr->family, pasvport, 0, p);
Packit 90a5c9
                rv = apr_socket_connect(data_sock, pasv_addr);
Packit 90a5c9
                if (rv != APR_SUCCESS) {
Packit 90a5c9
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01048)
Packit 90a5c9
                                  "PASV attempt to connect to %pI failed - Firewall/NAT?", pasv_addr);
Packit 90a5c9
                    return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
Packit 90a5c9
                                                                           "PASV attempt to connect to %pI failed - firewall/NAT?", pasv_addr));
Packit 90a5c9
                }
Packit 90a5c9
                else {
Packit 90a5c9
                    connect = 1;
Packit 90a5c9
                }
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
/*bypass:*/
Packit 90a5c9
Packit 90a5c9
    /* set up data connection - PORT */
Packit 90a5c9
    if (!connect) {
Packit 90a5c9
        apr_sockaddr_t *local_addr;
Packit 90a5c9
        char *local_ip;
Packit 90a5c9
        apr_port_t local_port;
Packit 90a5c9
        unsigned int h0, h1, h2, h3, p0, p1;
Packit 90a5c9
Packit 90a5c9
        if ((rv = apr_socket_create(&local_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
Packit 90a5c9
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01049)
Packit 90a5c9
                          "error creating local socket");
Packit 90a5c9
            proxy_ftp_cleanup(r, backend);
Packit 90a5c9
            return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
        }
Packit 90a5c9
        apr_socket_addr_get(&local_addr, APR_LOCAL, sock);
Packit 90a5c9
        local_port = local_addr->port;
Packit 90a5c9
        apr_sockaddr_ip_get(&local_ip, local_addr);
Packit 90a5c9
Packit 90a5c9
        if ((rv = apr_socket_opt_set(local_sock, APR_SO_REUSEADDR, one))
Packit 90a5c9
                != APR_SUCCESS) {
Packit 90a5c9
#ifndef _OSD_POSIX              /* BS2000 has this option "always on" */
Packit 90a5c9
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01050)
Packit 90a5c9
                          "error setting reuseaddr option");
Packit 90a5c9
            proxy_ftp_cleanup(r, backend);
Packit 90a5c9
            return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
#endif                          /* _OSD_POSIX */
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
Packit 90a5c9
Packit 90a5c9
        if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
Packit 90a5c9
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01051)
Packit 90a5c9
                          "error binding to ftp data socket %pI", local_addr);
Packit 90a5c9
            proxy_ftp_cleanup(r, backend);
Packit 90a5c9
            return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* only need a short queue */
Packit 90a5c9
        if ((rv = apr_socket_listen(local_sock, 2)) != APR_SUCCESS) {
Packit 90a5c9
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01052)
Packit 90a5c9
                          "error listening to ftp data socket %pI", local_addr);
Packit 90a5c9
            proxy_ftp_cleanup(r, backend);
Packit 90a5c9
            return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
/* FIXME: Sent PORT here */
Packit 90a5c9
Packit 90a5c9
        if (local_ip && (sscanf(local_ip,
Packit 90a5c9
                                "%d.%d.%d.%d", &h3, &h2, &h1, &h0) == 4)) {
Packit 90a5c9
            p1 = (local_port >> 8);
Packit 90a5c9
            p0 = (local_port & 0xFF);
Packit 90a5c9
Packit 90a5c9
            rc = proxy_ftp_command(apr_psprintf(p, "PORT %d,%d,%d,%d,%d,%d" CRLF, h3, h2, h1, h0, p1, p0),
Packit 90a5c9
                           r, origin, bb, &ftpmessage);
Packit 90a5c9
            /* possible results: 200, 421, 500, 501, 502, 530 */
Packit 90a5c9
            /* 200 Command okay. */
Packit 90a5c9
            /* 421 Service not available, closing control connection. */
Packit 90a5c9
            /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
            /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
            /* 502 Command not implemented. */
Packit 90a5c9
            /* 530 Not logged in. */
Packit 90a5c9
            if (rc == -1 || rc == 421) {
Packit 90a5c9
                return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                      "Error reading from remote server");
Packit 90a5c9
            }
Packit 90a5c9
            if (rc != 200) {
Packit 90a5c9
                return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
            /* signal that we must use the EPRT/PORT loop */
Packit 90a5c9
            use_port = 1;
Packit 90a5c9
        }
Packit 90a5c9
        else {
Packit 90a5c9
/* IPV6 FIXME:
Packit 90a5c9
 * The EPRT command replaces PORT where both IPV4 and IPV6 is supported. The first
Packit 90a5c9
 * number (1,2) indicates the protocol type. Examples:
Packit 90a5c9
 *   EPRT |1|132.235.1.2|6275|
Packit 90a5c9
 *   EPRT |2|1080::8:800:200C:417A|5282|
Packit 90a5c9
 */
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_NOT_IMPLEMENTED,
Packit 90a5c9
                                  "Connect to IPV6 ftp server using EPRT not supported. Enable EPSV.");
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * V: Set The Headers -------------------
Packit 90a5c9
     *
Packit 90a5c9
     * Get the size of the request, set up the environment for HTTP.
Packit 90a5c9
     */
Packit 90a5c9
Packit 90a5c9
    /* set request; "path" holds last path component */
Packit 90a5c9
    len = decodeenc(path);
Packit 90a5c9
Packit 90a5c9
    if (strchr(path, '/')) { /* are there now any '/' characters? */
Packit 90a5c9
       return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
Packit 90a5c9
                             "Use of /%2f is only allowed at the base directory");
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* If len == 0 then it must be a directory (you can't RETR nothing)
Packit 90a5c9
     * Also, don't allow to RETR by wildcard. Instead, create a dirlisting,
Packit 90a5c9
     * unless ProxyFtpListOnWildcard is off.
Packit 90a5c9
     */
Packit 90a5c9
    if (len == 0 || (ftp_check_globbingchars(path) && fdconf->ftp_list_on_wildcard)) {
Packit 90a5c9
        dirlisting = 1;
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        /* (from FreeBSD ftpd):
Packit 90a5c9
         * SIZE is not in RFC959, but Postel has blessed it and
Packit 90a5c9
         * it will be in the updated RFC.
Packit 90a5c9
         *
Packit 90a5c9
         * Return size of file in a format suitable for
Packit 90a5c9
         * using with RESTART (we just count bytes).
Packit 90a5c9
         */
Packit 90a5c9
        /* from draft-ietf-ftpext-mlst-14.txt:
Packit 90a5c9
         * This value will
Packit 90a5c9
         * change depending on the current STRUcture, MODE and TYPE of the data
Packit 90a5c9
         * connection, or a data connection which would be created were one
Packit 90a5c9
         * created now.  Thus, the result of the SIZE command is dependent on
Packit 90a5c9
         * the currently established STRU, MODE and TYPE parameters.
Packit 90a5c9
         */
Packit 90a5c9
        /* Therefore: switch to binary if the user did not specify ";type=a" */
Packit 90a5c9
        ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
Packit 90a5c9
        rc = proxy_ftp_command(apr_pstrcat(p, "SIZE ",
Packit 90a5c9
                           ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
Packit 90a5c9
                           r, origin, bb, &ftpmessage);
Packit 90a5c9
        if (rc == -1 || rc == 421) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                  "Error reading from remote server");
Packit 90a5c9
        }
Packit 90a5c9
        else if (rc == 213) {/* Size command ok */
Packit 90a5c9
            int j;
Packit 90a5c9
            for (j = 0; apr_isdigit(ftpmessage[j]); j++)
Packit 90a5c9
                ;
Packit 90a5c9
            ftpmessage[j] = '\0';
Packit 90a5c9
            if (ftpmessage[0] != '\0')
Packit 90a5c9
                 size = ftpmessage; /* already pstrdup'ed: no copy necessary */
Packit 90a5c9
        }
Packit 90a5c9
        else if (rc == 550) {    /* Not a regular file */
Packit 90a5c9
            ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
Packit 90a5c9
                          "SIZE shows this is a directory");
Packit 90a5c9
            dirlisting = 1;
Packit 90a5c9
            rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
Packit 90a5c9
                           ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
Packit 90a5c9
                           r, origin, bb, &ftpmessage);
Packit 90a5c9
            /* possible results: 250, 421, 500, 501, 502, 530, 550 */
Packit 90a5c9
            /* 250 Requested file action okay, completed. */
Packit 90a5c9
            /* 421 Service not available, closing control connection. */
Packit 90a5c9
            /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
            /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
            /* 502 Command not implemented. */
Packit 90a5c9
            /* 530 Not logged in. */
Packit 90a5c9
            /* 550 Requested action not taken. */
Packit 90a5c9
            if (rc == -1 || rc == 421) {
Packit 90a5c9
                return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                      "Error reading from remote server");
Packit 90a5c9
            }
Packit 90a5c9
            if (rc == 550) {
Packit 90a5c9
                return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
Packit 90a5c9
            }
Packit 90a5c9
            if (rc != 250) {
Packit 90a5c9
                return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
            }
Packit 90a5c9
            path = "";
Packit 90a5c9
            len = 0;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    cwd = ftp_get_PWD(r, origin, bb);
Packit 90a5c9
    if (cwd != NULL) {
Packit 90a5c9
        apr_table_set(r->notes, "Directory-PWD", cwd);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    if (dirlisting) {
Packit 90a5c9
        ftp_set_TYPE('A', r, origin, bb, NULL);
Packit 90a5c9
        /* If the current directory contains no slash, we are talking to
Packit 90a5c9
         * a non-unix ftp system. Try LIST instead of "LIST -lag", it
Packit 90a5c9
         * should return a long listing anyway (unlike NLST).
Packit 90a5c9
         * Some exotic FTP servers might choke on the "-lag" switch.
Packit 90a5c9
         */
Packit 90a5c9
        /* Note that we do not escape the path here, to allow for
Packit 90a5c9
         * queries like: ftp://user@host/apache/src/server/http_*.c
Packit 90a5c9
         */
Packit 90a5c9
        if (len != 0)
Packit 90a5c9
            buf = apr_pstrcat(p, "LIST ", path, CRLF, NULL);
Packit 90a5c9
        else if (cwd == NULL || strchr(cwd, '/') != NULL)
Packit 90a5c9
            buf = "LIST -lag" CRLF;
Packit 90a5c9
        else
Packit 90a5c9
            buf = "LIST" CRLF;
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        /* switch to binary if the user did not specify ";type=a" */
Packit 90a5c9
        ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
Packit 90a5c9
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
Packit 90a5c9
        /* from draft-ietf-ftpext-mlst-14.txt:
Packit 90a5c9
         *   The FTP command, MODIFICATION TIME (MDTM), can be used to determine
Packit 90a5c9
         *   when a file in the server NVFS was last modified.     <..>
Packit 90a5c9
         *   The syntax of a time value is:
Packit 90a5c9
         *           time-val       = 14DIGIT [ "." 1*DIGIT ]      <..>
Packit 90a5c9
         *     Symbolically, a time-val may be viewed as
Packit 90a5c9
         *           YYYYMMDDHHMMSS.sss
Packit 90a5c9
         *     The "." and subsequent digits ("sss") are optional. <..>
Packit 90a5c9
         *     Time values are always represented in UTC (GMT)
Packit 90a5c9
         */
Packit 90a5c9
        rc = proxy_ftp_command(apr_pstrcat(p, "MDTM ", ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
Packit 90a5c9
                               r, origin, bb, &ftpmessage);
Packit 90a5c9
        /* then extract the Last-Modified time from it (YYYYMMDDhhmmss or YYYYMMDDhhmmss.xxx GMT). */
Packit 90a5c9
        if (rc == 213) {
Packit 90a5c9
            struct {
Packit 90a5c9
                char YYYY[4+1];
Packit 90a5c9
                char MM[2+1];
Packit 90a5c9
                char DD[2+1];
Packit 90a5c9
                char hh[2+1];
Packit 90a5c9
                char mm[2+1];
Packit 90a5c9
                char ss[2+1];
Packit 90a5c9
            } time_val;
Packit 90a5c9
            if (6 == sscanf(ftpmessage, "%4[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]",
Packit 90a5c9
                time_val.YYYY, time_val.MM, time_val.DD, time_val.hh, time_val.mm, time_val.ss)) {
Packit 90a5c9
                struct tm tms;
Packit 90a5c9
                memset (&tms, '\0', sizeof tms);
Packit 90a5c9
                tms.tm_year = atoi(time_val.YYYY) - 1900;
Packit 90a5c9
                tms.tm_mon  = atoi(time_val.MM)   - 1;
Packit 90a5c9
                tms.tm_mday = atoi(time_val.DD);
Packit 90a5c9
                tms.tm_hour = atoi(time_val.hh);
Packit 90a5c9
                tms.tm_min  = atoi(time_val.mm);
Packit 90a5c9
                tms.tm_sec  = atoi(time_val.ss);
Packit 90a5c9
#ifdef HAVE_TIMEGM /* Does system have timegm()? */
Packit 90a5c9
                mtime = timegm(&tms);
Packit 90a5c9
                mtime *= APR_USEC_PER_SEC;
Packit 90a5c9
#elif HAVE_GMTOFF /* does struct tm have a member tm_gmtoff? */
Packit 90a5c9
                /* mktime will subtract the local timezone, which is not what we want.
Packit 90a5c9
                 * Add it again because the MDTM string is GMT
Packit 90a5c9
                 */
Packit 90a5c9
                mtime = mktime(&tms);
Packit 90a5c9
                mtime += tms.tm_gmtoff;
Packit 90a5c9
                mtime *= APR_USEC_PER_SEC;
Packit 90a5c9
#else
Packit 90a5c9
                mtime = 0L;
Packit 90a5c9
#endif
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
#endif /* USE_MDTM */
Packit 90a5c9
/* FIXME: Handle range requests - send REST */
Packit 90a5c9
        buf = apr_pstrcat(p, "RETR ", ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL);
Packit 90a5c9
    }
Packit 90a5c9
    rc = proxy_ftp_command(buf, r, origin, bb, &ftpmessage);
Packit 90a5c9
    /* rc is an intermediate response for the LIST or RETR commands */
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530,
Packit 90a5c9
     * 550 NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502,
Packit 90a5c9
     * 530
Packit 90a5c9
     */
Packit 90a5c9
    /* 110 Restart marker reply. */
Packit 90a5c9
    /* 125 Data connection already open; transfer starting. */
Packit 90a5c9
    /* 150 File status okay; about to open data connection. */
Packit 90a5c9
    /* 226 Closing data connection. */
Packit 90a5c9
    /* 250 Requested file action okay, completed. */
Packit 90a5c9
    /* 421 Service not available, closing control connection. */
Packit 90a5c9
    /* 425 Can't open data connection. */
Packit 90a5c9
    /* 426 Connection closed; transfer aborted. */
Packit 90a5c9
    /* 450 Requested file action not taken. */
Packit 90a5c9
    /* 451 Requested action aborted. Local error in processing. */
Packit 90a5c9
    /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
    /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
    /* 530 Not logged in. */
Packit 90a5c9
    /* 550 Requested action not taken. */
Packit 90a5c9
    if (rc == -1 || rc == 421) {
Packit 90a5c9
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                              "Error reading from remote server");
Packit 90a5c9
    }
Packit 90a5c9
    if (rc == 550) {
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
Packit 90a5c9
                      "RETR failed, trying LIST instead");
Packit 90a5c9
Packit 90a5c9
        /* Directory Listings should always be fetched in ASCII mode */
Packit 90a5c9
        dirlisting = 1;
Packit 90a5c9
        ftp_set_TYPE('A', r, origin, bb, NULL);
Packit 90a5c9
Packit 90a5c9
        rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
Packit 90a5c9
                               ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
Packit 90a5c9
                               r, origin, bb, &ftpmessage);
Packit 90a5c9
        /* possible results: 250, 421, 500, 501, 502, 530, 550 */
Packit 90a5c9
        /* 250 Requested file action okay, completed. */
Packit 90a5c9
        /* 421 Service not available, closing control connection. */
Packit 90a5c9
        /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
        /* 501 Syntax error in parameters or arguments. */
Packit 90a5c9
        /* 502 Command not implemented. */
Packit 90a5c9
        /* 530 Not logged in. */
Packit 90a5c9
        /* 550 Requested action not taken. */
Packit 90a5c9
        if (rc == -1 || rc == 421) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                  "Error reading from remote server");
Packit 90a5c9
        }
Packit 90a5c9
        if (rc == 550) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
Packit 90a5c9
        }
Packit 90a5c9
        if (rc != 250) {
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* Update current directory after CWD */
Packit 90a5c9
        cwd = ftp_get_PWD(r, origin, bb);
Packit 90a5c9
        if (cwd != NULL) {
Packit 90a5c9
            apr_table_set(r->notes, "Directory-PWD", cwd);
Packit 90a5c9
        }
Packit 90a5c9
Packit 90a5c9
        /* See above for the "LIST" vs. "LIST -lag" discussion. */
Packit 90a5c9
        rc = proxy_ftp_command((cwd == NULL || strchr(cwd, '/') != NULL)
Packit 90a5c9
                               ? "LIST -lag" CRLF : "LIST" CRLF,
Packit 90a5c9
                               r, origin, bb, &ftpmessage);
Packit 90a5c9
Packit 90a5c9
        /* rc is an intermediate response for the LIST command (125 transfer starting, 150 opening data connection) */
Packit 90a5c9
        if (rc == -1 || rc == 421)
Packit 90a5c9
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
Packit 90a5c9
                                  "Error reading from remote server");
Packit 90a5c9
    }
Packit 90a5c9
    if (rc != 125 && rc != 150 && rc != 226 && rc != 250) {
Packit 90a5c9
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    r->status = HTTP_OK;
Packit 90a5c9
    r->status_line = "200 OK";
Packit 90a5c9
Packit 90a5c9
    apr_rfc822_date(dates, r->request_time);
Packit 90a5c9
    apr_table_setn(r->headers_out, "Date", dates);
Packit 90a5c9
    apr_table_setn(r->headers_out, "Server", ap_get_server_description());
Packit 90a5c9
Packit 90a5c9
    /* set content-type */
Packit 90a5c9
    if (dirlisting) {
Packit 90a5c9
        ap_set_content_type(r, apr_pstrcat(p, "text/html;charset=",
Packit 90a5c9
                                           fdconf->ftp_directory_charset ?
Packit 90a5c9
                                           fdconf->ftp_directory_charset :
Packit 90a5c9
                                           "ISO-8859-1",  NULL));
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        if (xfer_type != 'A' && size != NULL) {
Packit 90a5c9
            /* We "trust" the ftp server to really serve (size) bytes... */
Packit 90a5c9
            apr_table_setn(r->headers_out, "Content-Length", size);
Packit 90a5c9
            ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
Packit 90a5c9
                          "Content-Length set to %s", size);
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    if (r->content_type) {
Packit 90a5c9
        apr_table_setn(r->headers_out, "Content-Type", r->content_type);
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
Packit 90a5c9
                      "Content-Type set to %s", r->content_type);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
Packit 90a5c9
    if (mtime != 0L) {
Packit 90a5c9
        char datestr[APR_RFC822_DATE_LEN];
Packit 90a5c9
        apr_rfc822_date(datestr, mtime);
Packit 90a5c9
        apr_table_set(r->headers_out, "Last-Modified", datestr);
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
Packit 90a5c9
                      "Last-Modified set to %s", datestr);
Packit 90a5c9
    }
Packit 90a5c9
#endif /* USE_MDTM */
Packit 90a5c9
Packit 90a5c9
    /* If an encoding has been set by mistake, delete it.
Packit 90a5c9
     * @@@ FIXME (e.g., for ftp://user@host/file*.tar.gz,
Packit 90a5c9
     * @@@        the encoding is currently set to x-gzip)
Packit 90a5c9
     */
Packit 90a5c9
    if (dirlisting && r->content_encoding != NULL)
Packit 90a5c9
        r->content_encoding = NULL;
Packit 90a5c9
Packit 90a5c9
    /* set content-encoding (not for dir listings, they are uncompressed)*/
Packit 90a5c9
    if (r->content_encoding != NULL && r->content_encoding[0] != '\0') {
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
Packit 90a5c9
                      "Content-Encoding set to %s", r->content_encoding);
Packit 90a5c9
        apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* wait for connection */
Packit 90a5c9
    if (use_port) {
Packit 90a5c9
        for (;;) {
Packit 90a5c9
            rv = apr_socket_accept(&data_sock, local_sock, r->pool);
Packit 90a5c9
            if (APR_STATUS_IS_EINTR(rv)) {
Packit 90a5c9
                continue;
Packit 90a5c9
            }
Packit 90a5c9
            else if (rv == APR_SUCCESS) {
Packit 90a5c9
                break;
Packit 90a5c9
            }
Packit 90a5c9
            else {
Packit 90a5c9
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01053)
Packit 90a5c9
                              "failed to accept data connection");
Packit 90a5c9
                proxy_ftp_cleanup(r, backend);
Packit 90a5c9
                return HTTP_BAD_GATEWAY;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* the transfer socket is now open, create a new connection */
Packit 90a5c9
    data = ap_run_create_connection(p, r->server, data_sock, r->connection->id,
Packit 90a5c9
                                    r->connection->sbh, c->bucket_alloc);
Packit 90a5c9
    if (!data) {
Packit 90a5c9
        /*
Packit 90a5c9
         * the peer reset the connection already; ap_run_create_connection() closed
Packit 90a5c9
         * the socket
Packit 90a5c9
         */
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01054)
Packit 90a5c9
                      "an error occurred creating the transfer connection");
Packit 90a5c9
        proxy_ftp_cleanup(r, backend);
Packit 90a5c9
        return HTTP_INTERNAL_SERVER_ERROR;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * We do not do SSL over the data connection, even if the virtual host we
Packit 90a5c9
     * are in might have SSL enabled
Packit 90a5c9
     */
Packit 90a5c9
    ap_proxy_ssl_engine(data, r->per_dir_config, 0);
Packit 90a5c9
    /* set up the connection filters */
Packit 90a5c9
    rc = ap_run_pre_connection(data, data_sock);
Packit 90a5c9
    if (rc != OK && rc != DONE) {
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01055)
Packit 90a5c9
                      "pre_connection setup failed (%d)", rc);
Packit 90a5c9
        data->aborted = 1;
Packit 90a5c9
        proxy_ftp_cleanup(r, backend);
Packit 90a5c9
        return rc;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * VI: Receive the Response ------------------------
Packit 90a5c9
     *
Packit 90a5c9
     * Get response from the remote ftp socket, and pass it up the filter chain.
Packit 90a5c9
     */
Packit 90a5c9
Packit 90a5c9
    /* send response */
Packit 90a5c9
    r->sent_bodyct = 1;
Packit 90a5c9
Packit 90a5c9
    if (dirlisting) {
Packit 90a5c9
        /* insert directory filter */
Packit 90a5c9
        ap_add_output_filter("PROXY_SEND_DIR", NULL, r, r->connection);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* send body */
Packit 90a5c9
    if (!r->header_only) {
Packit 90a5c9
        apr_bucket *e;
Packit 90a5c9
        int finish = FALSE;
Packit 90a5c9
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "start body send");
Packit 90a5c9
Packit 90a5c9
        /* read the body, pass it to the output filters */
Packit 90a5c9
        while (ap_get_brigade(data->input_filters,
Packit 90a5c9
                              bb,
Packit 90a5c9
                              AP_MODE_READBYTES,
Packit 90a5c9
                              APR_BLOCK_READ,
Packit 90a5c9
                              conf->io_buffer_size) == APR_SUCCESS) {
Packit 90a5c9
#if DEBUGGING
Packit 90a5c9
            {
Packit 90a5c9
                apr_off_t readbytes;
Packit 90a5c9
                apr_brigade_length(bb, 0, &readbytes);
Packit 90a5c9
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01056)
Packit 90a5c9
                             "proxy: readbytes: %#x", readbytes);
Packit 90a5c9
            }
Packit 90a5c9
#endif
Packit 90a5c9
            /* sanity check */
Packit 90a5c9
            if (APR_BRIGADE_EMPTY(bb)) {
Packit 90a5c9
                break;
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
            /* found the last brigade? */
Packit 90a5c9
            if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
Packit 90a5c9
                /* if this is the last brigade, cleanup the
Packit 90a5c9
                 * backend connection first to prevent the
Packit 90a5c9
                 * backend server from hanging around waiting
Packit 90a5c9
                 * for a slow client to eat these bytes
Packit 90a5c9
                 */
Packit 90a5c9
                ap_flush_conn(data);
Packit 90a5c9
                if (data_sock) {
Packit 90a5c9
                    apr_socket_close(data_sock);
Packit 90a5c9
                }
Packit 90a5c9
                data_sock = NULL;
Packit 90a5c9
                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01057)
Packit 90a5c9
                              "data connection closed");
Packit 90a5c9
                /* signal that we must leave */
Packit 90a5c9
                finish = TRUE;
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
            /* if no EOS yet, then we must flush */
Packit 90a5c9
            if (FALSE == finish) {
Packit 90a5c9
                e = apr_bucket_flush_create(c->bucket_alloc);
Packit 90a5c9
                APR_BRIGADE_INSERT_TAIL(bb, e);
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
            /* try send what we read */
Packit 90a5c9
            if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
Packit 90a5c9
                || c->aborted) {
Packit 90a5c9
                /* Ack! Phbtt! Die! User aborted! */
Packit 90a5c9
                finish = TRUE;
Packit 90a5c9
            }
Packit 90a5c9
Packit 90a5c9
            /* make sure we always clean up after ourselves */
Packit 90a5c9
            apr_brigade_cleanup(bb);
Packit 90a5c9
Packit 90a5c9
            /* if we are done, leave */
Packit 90a5c9
            if (TRUE == finish) {
Packit 90a5c9
                break;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "end body send");
Packit 90a5c9
Packit 90a5c9
    }
Packit 90a5c9
    if (data_sock) {
Packit 90a5c9
        ap_flush_conn(data);
Packit 90a5c9
        apr_socket_close(data_sock);
Packit 90a5c9
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01058) "data connection closed");
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* Retrieve the final response for the RETR or LIST commands */
Packit 90a5c9
    proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
Packit 90a5c9
    apr_brigade_cleanup(bb);
Packit 90a5c9
Packit 90a5c9
    /*
Packit 90a5c9
     * VII: Clean Up -------------
Packit 90a5c9
     *
Packit 90a5c9
     * If there are no KeepAlives, or if the connection has been signalled to
Packit 90a5c9
     * close, close the socket and clean up
Packit 90a5c9
     */
Packit 90a5c9
Packit 90a5c9
    /* finish */
Packit 90a5c9
    proxy_ftp_command("QUIT" CRLF, r, origin, bb, &ftpmessage);
Packit 90a5c9
    /* responses: 221, 500 */
Packit 90a5c9
    /* 221 Service closing control connection. */
Packit 90a5c9
    /* 500 Syntax error, command unrecognized. */
Packit 90a5c9
    ap_flush_conn(origin);
Packit 90a5c9
    proxy_ftp_cleanup(r, backend);
Packit 90a5c9
Packit 90a5c9
    apr_brigade_destroy(bb);
Packit 90a5c9
    return OK;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void ap_proxy_ftp_register_hook(apr_pool_t *p)
Packit 90a5c9
{
Packit 90a5c9
    /* hooks */
Packit 90a5c9
    proxy_hook_scheme_handler(proxy_ftp_handler, NULL, NULL, APR_HOOK_MIDDLE);
Packit 90a5c9
    proxy_hook_canon_handler(proxy_ftp_canon, NULL, NULL, APR_HOOK_MIDDLE);
Packit 90a5c9
    /* filters */
Packit 90a5c9
    ap_register_output_filter("PROXY_SEND_DIR", proxy_send_dir_filter,
Packit 90a5c9
                              NULL, AP_FTYPE_RESOURCE);
Packit 90a5c9
    /* Compile the output format of "ls -s1" as a fallback for non-unix ftp listings */
Packit 90a5c9
    ls_regex = ap_pregcomp(p, LS_REG_PATTERN, AP_REG_EXTENDED);
Packit 90a5c9
    ap_assert(ls_regex != NULL);
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static const command_rec proxy_ftp_cmds[] =
Packit 90a5c9
{
Packit 90a5c9
    AP_INIT_FLAG("ProxyFtpListOnWildcard", set_ftp_list_on_wildcard, NULL,
Packit 90a5c9
     RSRC_CONF|ACCESS_CONF, "Whether wildcard characters in a path cause mod_proxy_ftp to list the files instead of trying to get them. Defaults to on."),
Packit 90a5c9
    AP_INIT_FLAG("ProxyFtpEscapeWildcards", set_ftp_escape_wildcards, NULL,
Packit 90a5c9
     RSRC_CONF|ACCESS_CONF, "Whether the proxy should escape wildcards in paths before sending them to the FTP server.  Defaults to on, but most FTP servers will need it turned off if you need to manage paths that contain wildcard characters."),
Packit 90a5c9
    AP_INIT_TAKE1("ProxyFtpDirCharset", set_ftp_directory_charset, NULL,
Packit 90a5c9
     RSRC_CONF|ACCESS_CONF, "Define the character set for proxied FTP listings"),
Packit 90a5c9
    {NULL}
Packit 90a5c9
};
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
AP_DECLARE_MODULE(proxy_ftp) = {
Packit 90a5c9
    STANDARD20_MODULE_STUFF,
Packit 90a5c9
    create_proxy_ftp_dir_config,/* create per-directory config structure */
Packit 90a5c9
    merge_proxy_ftp_dir_config, /* merge per-directory config structures */
Packit 90a5c9
    NULL,                       /* create per-server config structure */
Packit 90a5c9
    NULL,                       /* merge per-server config structures */
Packit 90a5c9
    proxy_ftp_cmds,             /* command apr_table_t */
Packit 90a5c9
    ap_proxy_ftp_register_hook  /* register hooks */
Packit 90a5c9
};