View | Details | Raw Unified | Return to bug 41676
Collapse All | Expand All

(-)mod_proxy_ftp.c (-923 / +1145 lines)
Lines 34-42 Link Here
34
 */
34
 */
35
#define USE_MDTM
35
#define USE_MDTM
36
36
37
/* The state machine states.*/
38
enum ftp_states {
39
    ftp_connect = 0,    /* Init socket connection (wait for welcome)*/
40
    ftp_send_user,
41
    ftp_send_pass,
42
    ftp_choose_action,
43
    ftp_check_type,
44
    ftp_check_trans_mode,
45
    ftp_check_transfer,
46
    ftp_connect_data_port,
47
    ftp_send_retr,
48
    ftp_send_stor,
49
    ftp_send_list,
50
    ftp_start_response,
51
    ftp_transfer_stream,
52
    ftp_finish_response,
53
    ftp_cleanup,
54
    ftp_error
55
};
37
56
57
typedef struct {
58
    char *ip;
59
    int port;
60
    apr_socket_t *sock;
61
    apr_socket_t *local_sock;
62
} data_conn;
63
 
64
65
/* The local data is kept different from the normal ftp_conn_data
66
 * for the future where we will be able to reuse the connection to
67
 * an ftp server. The data here is relevant for a single request
68
 * while the data in ftp_conn_data is relevant for a full connection
69
 * period.(may span multiple requests).*/
70
typedef struct {
71
    proxy_server_conf *conf;
72
    apr_sockaddr_t *data_addr;
73
    data_conn data;
74
    int rc;
75
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
76
    apr_time_t mtime;
77
#endif
78
79
} ftp_local_data;
80
81
/* modes of data connection (EPORT is not implemented right now) */
82
enum ftp_connection_modes {
83
    EPSV = 0,
84
    PASV,
85
    PORT,
86
    EPORT
87
};
88
89
/*The possible actions that the user can specify Currently STOR is not
90
 * implemented but is meant for the immediate future. We should perhaps
91
 * look at allowing arbitrary actions either in a header or in the url*/
92
enum ftp_action {
93
    LISTING = 0,
94
    RETR,
95
    STOR
96
};
97
98
typedef struct {
99
    enum ftp_connection_modes mode;
100
    char xfer_type;
101
    enum ftp_states state;
102
    proxy_conn_rec *p_conn;
103
    const char* user;
104
    const char* password;
105
    char* msg;
106
    char* size;
107
    char* path;
108
    char* url;
109
    apr_uri_t *uri;
110
    enum ftp_action action;
111
    ftp_local_data cur;             /* The data that change in each request*/ 
112
} ftp_conn_data;
113
114
38
module AP_MODULE_DECLARE_DATA proxy_ftp_module;
115
module AP_MODULE_DECLARE_DATA proxy_ftp_module;
39
116
117
const char *proxy_function = "FTP";
40
/*
118
/*
41
 * Decodes a '%' escaped string, and returns the number of characters
119
 * Decodes a '%' escaped string, and returns the number of characters
42
 */
120
 */
Lines 632-703 Link Here
632
    return rc;
710
    return rc;
633
}
711
}
634
712
635
/* Set ftp server to TYPE {A,I,E} before transfer of a directory or file */
636
static int ftp_set_TYPE(char xfer_type, request_rec *r, conn_rec *ftp_ctrl,
637
                  apr_bucket_brigade *bb, char **pmessage)
638
{
639
    char old_type[2] = { 'A', '\0' }; /* After logon, mode is ASCII */
640
    int ret = HTTP_OK;
641
    int rc;
642
643
    /* set desired type */
644
    old_type[0] = xfer_type;
645
646
    rc = proxy_ftp_command(apr_pstrcat(r->pool, "TYPE ", old_type, CRLF, NULL),
647
                           r, ftp_ctrl, bb, pmessage);
648
/* responses: 200, 421, 500, 501, 504, 530 */
649
    /* 200 Command okay. */
650
    /* 421 Service not available, closing control connection. */
651
    /* 500 Syntax error, command unrecognized. */
652
    /* 501 Syntax error in parameters or arguments. */
653
    /* 504 Command not implemented for that parameter. */
654
    /* 530 Not logged in. */
655
    if (rc == -1 || rc == 421) {
656
        ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
657
                             "Error reading from remote server");
658
    }
659
    else if (rc != 200 && rc != 504) {
660
        ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
661
                             "Unable to set transfer type");
662
    }
663
/* Allow not implemented */
664
    else if (rc == 504)
665
        /* ignore it silently */;
666
667
    return ret;
668
}
669
670
671
/* Return the current directory which we have selected on the FTP server, or NULL */
672
static char *ftp_get_PWD(request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
673
{
674
    char *cwd = NULL;
675
    char *ftpmessage = NULL;
676
677
    /* responses: 257, 500, 501, 502, 421, 550 */
678
    /* 257 "<directory-name>" <commentary> */
679
    /* 421 Service not available, closing control connection. */
680
    /* 500 Syntax error, command unrecognized. */
681
    /* 501 Syntax error in parameters or arguments. */
682
    /* 502 Command not implemented. */
683
    /* 550 Requested action not taken. */
684
    switch (proxy_ftp_command("PWD" CRLF, r, ftp_ctrl, bb, &ftpmessage)) {
685
        case -1:
686
        case 421:
687
        case 550:
688
            ap_proxyerror(r, HTTP_BAD_GATEWAY,
689
                             "Failed to read PWD on ftp server");
690
            break;
691
692
        case 257: {
693
            const char *dirp = ftpmessage;
694
            cwd = ap_getword_conf(r->pool, &dirp);
695
        }
696
    }
697
    return cwd;
698
}
699
700
701
/* Common routine for failed authorization (i.e., missing or wrong password)
713
/* Common routine for failed authorization (i.e., missing or wrong password)
702
 * to an ftp service. This causes most browsers to retry the request
714
 * to an ftp service. This causes most browsers to retry the request
703
 * with username and password (which was presumably queried from the user)
715
 * with username and password (which was presumably queried from the user)
Lines 714-845 Link Here
714
     */
726
     */
715
    if (log_it)
727
    if (log_it)
716
        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
728
        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
717
                      "proxy: missing or failed auth to %s",
729
                "proxy: missing or failed auth to %s",
718
                      apr_uri_unparse(r->pool,
730
                apr_uri_unparse(r->pool,
719
                                 &r->parsed_uri, APR_URI_UNP_OMITPATHINFO));
731
                    &r->parsed_uri, APR_URI_UNP_OMITPATHINFO));
720
732
721
    apr_table_setn(r->err_headers_out, "WWW-Authenticate",
733
    apr_table_setn(r->err_headers_out, "WWW-Authenticate",
722
                   apr_pstrcat(r->pool, "Basic realm=\"",
734
            apr_pstrcat(r->pool, "Basic realm=\"",
723
                               apr_uri_unparse(r->pool, &r->parsed_uri,
735
                apr_uri_unparse(r->pool, &r->parsed_uri,
724
                       APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO),
736
                    APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO),
725
                               "\"", NULL));
737
                "\"", NULL));
726
727
    return HTTP_UNAUTHORIZED;
728
}
738
}
729
739
730
static
740
static
731
apr_status_t proxy_ftp_cleanup(request_rec *r, proxy_conn_rec *backend)
741
int proxyerror(request_rec *r, proxy_conn_rec *conn, int statuscode, const char *message)
732
{
742
{
733
743
    conn->close = 1;
734
    backend->close = 1;
735
    ap_set_module_config(r->connection->conn_config, &proxy_ftp_module, NULL);
744
    ap_set_module_config(r->connection->conn_config, &proxy_ftp_module, NULL);
736
    ap_proxy_release_connection("FTP", backend, r->server);
745
    ap_proxy_release_connection(proxy_function, conn, r->server);
737
746
738
    return OK;
747
    return ap_proxyerror(r, statuscode, message);
739
}
748
}
740
749
741
static
750
static
742
int ftp_proxyerror(request_rec *r, proxy_conn_rec *conn, int statuscode, const char *message)
751
void parse_ftp_auth(ftp_conn_data *ftp_data, request_rec *r)
743
{
752
{
744
    proxy_ftp_cleanup(r, conn);
753
    /*    char *account = NULL; how to supply an account in a URL? */
745
    return ap_proxyerror(r, statuscode, message);
746
}
747
/*
748
 * Handles direct access of ftp:// URLs
749
 * Original (Non-PASV) version from
750
 * Troy Morrison <spiffnet@zoom.com>
751
 * PASV added by Chuck
752
 * Filters by [Graham Leggett <minfrin@sharp.fm>]
753
 */
754
static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
755
                             proxy_server_conf *conf, char *url,
756
                             const char *proxyhost, apr_port_t proxyport)
757
{
758
    apr_pool_t *p = r->pool;
759
    conn_rec *c = r->connection;
760
    proxy_conn_rec *backend;
761
    apr_socket_t *sock, *local_sock, *data_sock = NULL;
762
    apr_sockaddr_t *connect_addr = NULL;
763
    apr_status_t rv;
764
    conn_rec *origin, *data = NULL;
765
    apr_status_t err = APR_SUCCESS;
766
    apr_status_t uerr = APR_SUCCESS;
767
    apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
768
    char *buf, *connectname;
769
    apr_port_t connectport;
770
    char buffer[MAX_STRING_LEN];
771
    char *ftpmessage = NULL;
772
    char *path, *strp, *type_suffix, *cwd = NULL;
773
    apr_uri_t uri;
774
    char *user = NULL;
754
    char *user = NULL;
775
/*    char *account = NULL; how to supply an account in a URL? */
776
    const char *password = NULL;
755
    const char *password = NULL;
777
    int len, rc;
756
    
778
    int one = 1;
757
    /*
779
    char *size = NULL;
758
     * The "Authorization:" header must be checked first. We allow the user
780
    char xfer_type = 'A'; /* after ftp login, the default is ASCII */
759
     * to "override" the URL-coded user [ & password ] in the Browsers'
781
    int  dirlisting = 0;
760
     * User&Password Dialog. NOTE that this is only marginally more secure
782
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
761
     * than having the password travel in plain as part of the URL, because
783
    apr_time_t mtime = 0L;
762
     * Basic Auth simply uuencodes the plain text password. But chances are
784
#endif
763
     * still smaller that the URL is logged regularly.
785
764
     */
786
    /* stuff for PASV mode */
765
    if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
787
    int connect = 0, use_port = 0;
766
            && strcasecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
788
    char dates[APR_RFC822_DATE_LEN];
767
            && (password = ap_pbase64decode(r->pool, password))[0] != ':') {
789
    int status;
768
        /*
790
    apr_pool_t *address_pool;
769
         * Note that this allocation has to be made from p_conn->pool
791
770
         * because it has the lifetime of the connection.  The other
792
    /* is this for us? */
771
         * allocations are temporary and can be tossed away any time.
793
    if (proxyhost) {
772
         */
794
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
773
        user = ap_getword_nulls(ftp_data->p_conn->pool, &password, ':');
795
                     "proxy: FTP: declining URL %s - proxyhost %s specified:", url, proxyhost);
774
        r->ap_auth_type = "Basic";
796
        return DECLINED;        /* proxy connections are via HTTP */
775
        r->user = r->parsed_uri.user = user;
797
    }
776
    }
798
    if (strncasecmp(url, "ftp:", 4)) {
777
    else if ((user = r->parsed_uri.user) != NULL) {
799
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
778
        user = apr_pstrdup(ftp_data->p_conn->pool, user);
800
                     "proxy: FTP: declining URL %s - not ftp:", url);
779
        decodeenc(user);
801
        return DECLINED;        /* only interested in FTP */
780
        if ((password = r->parsed_uri.password) != NULL) {
781
            char *tmp = apr_pstrdup(ftp_data->p_conn->pool, password);
782
            decodeenc(tmp);
783
            password = tmp;
784
        }
802
    }
785
    }
786
    else {
787
        user = "anonymous";
788
        password = "apache-proxy@";
789
    }
790
    ftp_data->user = user;
791
    ftp_data->password = password;
792
}
793
794
static
795
void ftp_debug(request_rec *r, char* fn, char* msg)
796
{
803
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
797
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
804
                 "proxy: FTP: serving URL %s", url);
798
            "proxy:<FTP: [%s] %s", fn, msg);
799
}
805
800
801
static
802
void set_ftp_error(ftp_conn_data* ftp_data, int rc, char* msg)
803
{
804
    ftp_data->cur.rc = rc;
805
    ftp_data->msg = msg;
806
    ftp_data->state = ftp_error;
807
}
806
808
807
    /*
809
static
808
     * I: Who Do I Connect To? -----------------------
810
int on_init(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
809
     *
811
{
810
     * Break up the URL to determine the host to connect to
812
    char *path = 0;
811
     */
813
    char xfer_type = 'A'; /* after ftp login, the default is ASCII */
814
    int  dirlisting = 0;
815
    apr_pool_t *p = r->pool;
816
    char *type_suffix = 0;
812
817
813
    /* we only support GET and HEAD */
818
    path = ftp_data->uri->path;
814
    if (r->method_number != M_GET)
815
        return HTTP_NOT_IMPLEMENTED;
816
819
817
    /* We break the URL into host, port, path-search */
818
    if (r->parsed_uri.hostname == NULL) {
819
        if (APR_SUCCESS != apr_uri_parse(p, url, &uri)) {
820
            return ap_proxyerror(r, HTTP_BAD_REQUEST,
821
                apr_psprintf(p, "URI cannot be parsed: %s", url));
822
        }
823
        connectname = uri.hostname;
824
        connectport = uri.port;
825
        path = apr_pstrdup(p, uri.path);
826
    }
827
    else {
828
        connectname = r->parsed_uri.hostname;
829
        connectport = r->parsed_uri.port;
830
        path = apr_pstrdup(p, r->parsed_uri.path);
831
    }
832
    if (connectport == 0) {
833
        connectport = apr_uri_port_of_scheme("ftp");
834
    }
835
    path = (path != NULL && path[0] != '\0') ? &path[1] : "";
820
    path = (path != NULL && path[0] != '\0') ? &path[1] : "";
821
    ftp_data->path = apr_pstrdup(p, path);
836
822
837
    type_suffix = strchr(path, ';');
823
    type_suffix = strchr(path, ';');
838
    if (type_suffix != NULL)
824
    if (type_suffix != NULL)
839
        *(type_suffix++) = '\0';
825
        *(type_suffix++) = '\0';
840
826
841
    if (type_suffix != NULL && strncmp(type_suffix, "type=", 5) == 0
827
    if (type_suffix != NULL && strncmp(type_suffix, "type=", 5) == 0
842
        && apr_isalpha(type_suffix[5])) {
828
            && apr_isalpha(type_suffix[5])) {
843
        /* "type=d" forces a dir listing.
829
        /* "type=d" forces a dir listing.
844
         * The other types (i|a|e) are directly used for the ftp TYPE command
830
         * The other types (i|a|e) are directly used for the ftp TYPE command
845
         */
831
         */
Lines 849-1666 Link Here
849
        /* Check valid types, rather than ignoring invalid types silently: */
835
        /* Check valid types, rather than ignoring invalid types silently: */
850
        if (strchr("AEI", xfer_type) == NULL)
836
        if (strchr("AEI", xfer_type) == NULL)
851
            return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
837
            return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
852
                                    "ftp proxy supports only types 'a', 'i', or 'e': \"",
838
                        "ftp proxy supports only types 'a', 'i', or 'e': \"",
853
                                    type_suffix, "\" is invalid.", NULL));
839
                        type_suffix, "\" is invalid.", NULL));
854
    }
840
    }
855
    else {
841
    else {
856
        /* make binary transfers the default */
842
        /* make binary transfers the default */
857
        xfer_type = 'I';
843
        xfer_type = 'I';
858
    }
844
    }
845
    ftp_data->xfer_type = xfer_type;
846
    return OK;
847
}
859
848
849
/* Possible results:
850
 * 120 Service ready in nnn minutes.
851
 * 220 server.com FTP server (Version vvv) ready.
852
 * 421 Service not available, closing control connection.
853
 * */
854
static
855
int on_connect(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
856
{
857
    char *msg = NULL;
858
    int rc = proxy_ftp_command(0, r, ftp_ctrl, bb, &msg);
859
    ftp_debug(r, "on_connect", msg);
860
    switch (rc / 100) {
861
        case 1:
862
            /*
863
             * RFC2616 states: 14.37 Retry-After
864
             *
865
             * The Retry-After response-header field can be used with a 503 (Service
866
             * Unavailable) response to indicate how long the service is expected
867
             * to be unavailable to the requesting client. [...] The value of
868
             * this field can be either an HTTP-date or an integer number of
869
             * seconds (in decimal) after the time of the response. Retry-After
870
             * = "Retry-After" ":" ( HTTP-date | delta-seconds )
871
             */
872
            {
873
                char *secs_str = msg;
874
                time_t secs;
860
875
861
    /*
876
                /* Look for a number, preceded by whitespace */ 
862
     * The "Authorization:" header must be checked first. We allow the user
877
                while (*secs_str)
863
     * to "override" the URL-coded user [ & password ] in the Browsers'
878
                    if ((secs_str==msg || apr_isspace(secs_str[-1])) &&
864
     * User&Password Dialog. NOTE that this is only marginally more secure
879
                            apr_isdigit(secs_str[0]))
865
     * than having the password travel in plain as part of the URL, because
880
                        break;
866
     * Basic Auth simply uuencodes the plain text password. But chances are
881
                if (*secs_str != '\0') {
867
     * still smaller that the URL is logged regularly.
882
                    secs = atol(secs_str);
868
     */
883
                    apr_table_add(r->headers_out, "Retry-After",
869
    if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
884
                            apr_psprintf(r->connection->pool, "%lu", (unsigned long)(60 * secs)));
870
        && strcasecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
885
                }
871
        && (password = ap_pbase64decode(r->pool, password))[0] != ':') {
886
            }
872
        /*
887
            set_ftp_error(ftp_data, rc, msg);
873
         * Note that this allocation has to be made from r->connection->pool
888
            return HTTP_SERVICE_UNAVAILABLE;
874
         * because it has the lifetime of the connection.  The other
889
875
         * allocations are temporary and can be tossed away any time.
890
        case 2:
876
         */
891
            ftp_data->state = ftp_send_user;
877
        user = ap_getword_nulls(r->connection->pool, &password, ':');
892
            return OK;
878
        r->ap_auth_type = "Basic";
893
879
        r->user = r->parsed_uri.user = user;
894
        default:
895
            set_ftp_error(ftp_data, rc, msg);
896
            return HTTP_BAD_GATEWAY;
880
    }
897
    }
881
    else if ((user = r->parsed_uri.user) != NULL) {
898
}
882
        user = apr_pstrdup(p, user);
883
        decodeenc(user);
884
        if ((password = r->parsed_uri.password) != NULL) {
885
            char *tmp = apr_pstrdup(p, password);
886
            decodeenc(tmp);
887
            password = tmp;
888
        }
889
    }
890
    else {
891
        user = "anonymous";
892
        password = "apache-proxy@";
893
    }
894
899
895
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
900
/* possible results; 230, 331, 332, 421, 500, 501, 530
896
       "proxy: FTP: connecting %s to %s:%d", url, connectname, connectport);
901
 * states: 1 - error, 2 - success; 3 - send password, 4,5 fail
902
 * 230 User logged in, proceed.
903
 * 331 User name okay, need password.
904
 * 332 Need account for login.
905
 * 421 Service not available, closing control connection.
906
 * 500 Syntax error, command unrecognized.
907
 * (This may include errors such as command line too long.)
908
 * 501 Syntax error in parameters or arguments.
909
 * 530 Not logged in. 
910
 **/
911
static
912
int send_user(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
913
{
914
    int rc;
915
    char *msg = NULL;
897
916
898
    if (worker->is_address_reusable) {
917
    parse_ftp_auth(ftp_data, r);
899
        if (!worker->cp->addr) {
918
    rc = proxy_ftp_command(apr_pstrcat(r->connection->pool, "USER ", ftp_data->user, CRLF, NULL),
900
            if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) {
919
            r, ftp_ctrl, bb, &msg);
901
                ap_log_error(APLOG_MARK, APLOG_ERR, err, r->server,
920
    ftp_debug(r, "user", msg);
902
                             "proxy: FTP: lock");
921
    switch (rc / 100) {
903
                return HTTP_INTERNAL_SERVER_ERROR;
922
        case 2:
923
            apr_table_set(r->notes, "Directory-README", msg);
924
            ftp_data->state = ftp_choose_action;
925
            return OK;
926
        case 3:
927
            ftp_data->state = ftp_send_pass;
928
            return OK;
929
        case 5:
930
            if (rc  == 530 ) {
931
                ftp_unauthorized(r, 1);  /* log it: user name guessing attempt?*/
932
                return HTTP_UNAUTHORIZED;
904
            }
933
            }
905
        }
934
        default:
906
        connect_addr = worker->cp->addr;
935
            set_ftp_error(ftp_data, rc, msg);
907
        address_pool = worker->cp->pool;
936
            return HTTP_BAD_GATEWAY;
908
    }
937
    }
909
    else
938
}
910
        address_pool = r->pool;
911
939
912
    /* do a DNS lookup for the destination host */
940
/* possible results 202, 230, 332, 421, 500, 501, 503, 530
913
    if (!connect_addr)
941
 * 230 User logged in, proceed.
914
        err = apr_sockaddr_info_get(&(connect_addr),
942
 * 332 Need account for login.
915
                                    connectname, APR_UNSPEC,
943
 * 421 Service not available, closing control connection.
916
                                    connectport, 0,
944
 * 500 Syntax error, command unrecognized.
917
                                    address_pool);
945
 * 501 Syntax error in parameters or arguments.
918
    if (worker->is_address_reusable && !worker->cp->addr) {
946
 * 503 Bad sequence of commands.
919
        worker->cp->addr = connect_addr;
947
 * 530 Not logged in. */
920
        if ((uerr = PROXY_THREAD_UNLOCK(worker)) != APR_SUCCESS) {
948
static
921
            ap_log_error(APLOG_MARK, APLOG_ERR, uerr, r->server,
949
int send_pass(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
922
                         "proxy: FTP: unlock");
950
{
923
        }
951
    int rc;
924
    }
952
    char *msg = NULL;
925
    /*
926
     * get all the possible IP addresses for the destname and loop through
927
     * them until we get a successful connection
928
     */
929
    if (APR_SUCCESS != err) {
930
        return ap_proxyerror(r, HTTP_BAD_GATEWAY, apr_pstrcat(p,
931
                                                 "DNS lookup failure for: ",
932
                                                        connectname, NULL));
933
    }
934
953
935
    /* check if ProxyBlock directive on this host */
954
    rc = proxy_ftp_command(apr_pstrcat(r->connection->pool, "PASS ", ftp_data->password, CRLF, NULL),
936
    if (OK != ap_proxy_checkproxyblock(r, conf, connect_addr)) {
955
            r, ftp_ctrl, bb, &msg);
937
        return ap_proxyerror(r, HTTP_FORBIDDEN,
956
    ftp_debug(r, "pass", msg);
938
                             "Connect to remote machine blocked");
957
    switch (rc / 100) {
939
    }
958
        case 2:
940
959
            apr_table_set(r->notes, "Directory-README", msg);
941
    /* create space for state information */
960
            ftp_data->state = ftp_choose_action;
942
    backend = (proxy_conn_rec *) ap_get_module_config(c->conn_config, &proxy_ftp_module);
961
            return OK;
943
    if (!backend) {
962
        case 3:
944
        status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server);
963
            apr_pstrcat(r->connection->pool, "Need account for login: ", msg, NULL);
945
        if (status != OK) {
964
            return HTTP_UNAUTHORIZED;
946
            if (backend) {
965
        case 5:
947
                backend->close = 1;
966
            if (rc == 530) {
948
                ap_proxy_release_connection("FTP", backend, r->server);
967
                ftp_unauthorized(r, 1);  /* log it: user name guessing attempt?*/
968
                return HTTP_UNAUTHORIZED;
949
            }
969
            }
950
            return status;
970
        default:
951
        }
971
            set_ftp_error(ftp_data, rc, msg);
952
        /* TODO: see if ftp could use determine_connection */
972
            return HTTP_BAD_GATEWAY;
953
        backend->addr = connect_addr;
954
        ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
955
    }
973
    }
974
}
956
975
976
/* possible results: 250, 421, 500, 501, 502, 530, 550
977
 * 250 Requested file action okay, completed.
978
 * 421 Service not available, closing control connection.
979
 * 500 Syntax error, command unrecognized.
980
 * 501 Syntax error in parameters or arguments.
981
 * 502 Command not implemented.
982
 * 530 Not logged in.
983
 * 550 Requested action not taken. */
957
984
958
    /*
985
/* (from FreeBSD ftpd):
959
     * II: Make the Connection -----------------------
986
 * SIZE is not in RFC959, but Postel has blessed it and
960
     *
987
 * it will be in the updated RFC.
961
     * We have determined who to connect to. Now make the connection.
988
 *
962
     */
989
 * Return size of file in a format suitable for
990
 * using with RESTART (we just count bytes).
991
 */
992
/* from draft-ietf-ftpext-mlst-14.txt:
993
 * This value will
994
 * change depending on the current STRUcture, MODE and TYPE of the data
995
 * connection, or a data connection which would be created were one
996
 * created now.  Thus, the result of the SIZE command is dependent on
997
 * the currently established STRU, MODE and TYPE parameters.
998
 */
963
999
1000
static
1001
int send_size(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1002
{
1003
    int rc;
1004
    char *msg = NULL;
964
1005
965
    if (ap_proxy_connect_backend("FTP", backend, worker, r->server)) {
1006
    rc = proxy_ftp_command(apr_pstrcat(r->connection->pool, "SIZE ", ftp_data->path, CRLF, NULL),
966
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1007
            r, ftp_ctrl, bb, &msg);
967
                     "proxy: FTP: an error occurred creating a new connection to %pI (%s)",
1008
    ftp_debug(r, "size", msg);
968
                     connect_addr, connectname);
1009
    switch (rc / 100) {
969
        proxy_ftp_cleanup(r, backend);
1010
        case 2:
970
        return HTTP_SERVICE_UNAVAILABLE;
1011
            {
1012
                int j;
1013
                for (j = 0; apr_isdigit(msg[j]); j++);
1014
                msg[j] = '\0';
1015
                if (msg[0] != '\0')
1016
                    ftp_data->size = msg;
1017
            }
1018
            ftp_data->action = RETR;
1019
            ftp_data->state = ftp_check_type;
1020
            return OK;
1021
        case 5:
1022
            if (rc == 550) {
1023
                ftp_data->action = LISTING;
1024
                return OK;
1025
            }
1026
        default:
1027
            set_ftp_error(ftp_data, rc, msg);
1028
            return HTTP_BAD_GATEWAY;
971
    }
1029
    }
1030
}
972
1031
973
    if (!backend->connection) {
1032
/* responses: 250, 421, 500, 501, 502, 530, 550
974
        status = ap_proxy_connection_create("FTP", backend, c, r->server);
1033
 * 250 Requested file action okay, completed.
975
        if (status != OK) {
1034
 * 421 Service not available, closing control connection.
976
            proxy_ftp_cleanup(r, backend);
1035
 * 500 Syntax error, command unrecognized.
977
            return status;
1036
 * 501 Syntax error in parameters or arguments.
978
        }
1037
 * 502 Command not implemented.
1038
 * 530 Not logged in.
1039
 * 550 Requested action not taken. */
1040
static
1041
int send_cwd(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1042
{
1043
    int rc;
1044
    char *msg = NULL;
1045
1046
    rc = proxy_ftp_command(apr_pstrcat(r->connection->pool, "CWD ", ftp_data->path, CRLF, NULL),
1047
            r, ftp_ctrl, bb, &msg);
1048
    ftp_debug(r, "cwd", msg);
1049
    switch (rc / 100) {
1050
        case 2:
1051
            ftp_data->state = ftp_check_type;
1052
            ftp_data->action = LISTING;
1053
            return OK;
1054
        case 5:
1055
            if (rc == 550) {
1056
                ftp_data->action = RETR;
1057
                return OK;
1058
            }
1059
        default:
1060
            set_ftp_error(ftp_data, rc, msg);
1061
            return HTTP_BAD_GATEWAY;
979
    }
1062
    }
1063
}
980
1064
981
    /* Use old naming */
1065
static
982
    origin = backend->connection;
1066
int choose_action(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
983
    sock = backend->sock;
1067
{
1068
    char* path = ftp_data->path;
1069
    enum ftp_action action = LISTING; /* May be*/
1070
    int fail = 1;
1071
    int len = 0;
1072
    int rc = 0;
1073
    /* Special handling for leading "%2f": this enforces a "cwd /"
1074
     * out of the $HOME directory which was the starting point after login
1075
     */
1076
    if (strncasecmp(path, "%2f", 3) == 0) {
1077
        path += 2;
1078
        path[0] = '/';
1079
    }
984
1080
985
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1081
    decodeenc(path); /* Note! This decodes a %2f -> "/" */
986
                 "proxy: FTP: control connection complete");
987
1082
988
1083
    /* NOTE: FTP servers do globbing on the path.
989
    /*
1084
     * So we need to escape the URI metacharacters.
990
     * III: Send Control Request -------------------------
1085
     * We use a special glob-escaping routine to escape globbing chars.
991
     *
1086
     * We could also have extended gen_test_char.c with a special T_ESCAPE_FTP_PATH
992
     * Log into the ftp server, send the username & password, change to the
993
     * correct directory...
994
     */
1087
     */
1088
    path = ftp_escape_globbingchars(r->connection->pool, path);
1089
    ftp_data->path = path;
1090
    ftp_data->state = ftp_check_type;
995
1091
1092
    if (r-> method_number == M_PUT) {
1093
        ftp_data->action = STOR;
1094
        set_ftp_error(ftp_data, rc, "Not implemented");
1095
        return HTTP_BAD_REQUEST; /* Not implemented*/
1096
    }
996
1097
997
    /* possible results: */
1098
998
    /* 120 Service ready in nnn minutes. */
1099
    len = strlen(ftp_data->path);
999
    /* 220 Service ready for new user. */
1100
    
1000
    /* 421 Service not available, closing control connection. */
1101
    /* If len == 0 then it must be a directory (you can't RETR nothing)*/
1001
    rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
1102
    if (!len) {
1002
    if (rc == -1 || rc == 421) {
1103
        ftp_data->action = LISTING;
1003
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
1104
        return OK;
1004
    }
1105
    }
1005
    if (rc == 120) {
1006
        /*
1007
         * RFC2616 states: 14.37 Retry-After
1008
         *
1009
         * The Retry-After response-header field can be used with a 503 (Service
1010
         * Unavailable) response to indicate how long the service is expected
1011
         * to be unavailable to the requesting client. [...] The value of
1012
         * this field can be either an HTTP-date or an integer number of
1013
         * seconds (in decimal) after the time of the response. Retry-After
1014
         * = "Retry-After" ":" ( HTTP-date | delta-seconds )
1015
         */
1016
        char *secs_str = ftpmessage;
1017
        time_t secs;
1018
1106
1019
        /* Look for a number, preceded by whitespace */
1107
    /* Do we look like a file?*/
1020
        while (*secs_str)
1108
    if (path[len - 1] != '/')
1021
            if ((secs_str==ftpmessage || apr_isspace(secs_str[-1])) &&
1109
        action = RETR; /*May be.*/
1022
                apr_isdigit(secs_str[0]))
1110
    else {
1023
                break;
1111
        /* No files with an '/' end possible*/
1024
        if (*secs_str != '\0') {
1112
        ftp_data->action = LISTING;
1025
            secs = atol(secs_str);
1113
        return send_cwd(ftp_data, r, ftp_ctrl, bb);
1026
            apr_table_add(r->headers_out, "Retry-After",
1027
                          apr_psprintf(p, "%lu", (unsigned long)(60 * secs)));
1028
        }
1029
        return ftp_proxyerror(r, backend, HTTP_SERVICE_UNAVAILABLE, ftpmessage);
1030
    }
1114
    }
1031
    if (rc != 220) {
1115
1032
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1116
     /* Also, don't allow to RETR by wildcard. Instead, create a dirlisting */
1117
    if (ftp_check_globbingchars(ftp_data->path)) {
1118
        ftp_data->action = LISTING;
1119
        return send_cwd(ftp_data, r, ftp_ctrl, bb);
1033
    }
1120
    }
1034
1121
1035
    rc = proxy_ftp_command(apr_pstrcat(p, "USER ", user, CRLF, NULL),
1122
1036
                           r, origin, bb, &ftpmessage);
1123
    /* verify our previous guesses */
1037
    /* possible results; 230, 331, 332, 421, 500, 501, 530 */
1124
    switch (action) {
1038
    /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */
1125
        case LISTING:
1039
    /* 230 User logged in, proceed. */
1126
            return send_cwd(ftp_data, r, ftp_ctrl, bb);
1040
    /* 331 User name okay, need password. */
1127
        case RETR:
1041
    /* 332 Need account for login. */
1128
            return send_size(ftp_data, r, ftp_ctrl, bb);
1042
    /* 421 Service not available, closing control connection. */
1129
        default:
1043
    /* 500 Syntax error, command unrecognized. */
1130
            set_ftp_error(ftp_data, rc, "Not implemented");
1044
    /* (This may include errors such as command line too long.) */
1131
            return HTTP_BAD_REQUEST; /* Not implemented*/
1045
    /* 501 Syntax error in parameters or arguments. */
1046
    /* 530 Not logged in. */
1047
    if (rc == -1 || rc == 421) {
1048
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
1049
    }
1132
    }
1050
    if (rc == 530) {
1133
}
1051
        proxy_ftp_cleanup(r, backend);
1134
1052
        return ftp_unauthorized(r, 1);  /* log it: user name guessing
1135
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1053
                                         * attempt? */
1136
/* from draft-ietf-ftpext-mlst-14.txt:
1137
 *   The FTP command, MODIFICATION TIME (MDTM), can be used to determine
1138
 *   when a file in the server NVFS was last modified.     <..>
1139
 *   The syntax of a time value is:
1140
 *           time-val       = 14DIGIT [ "." 1*DIGIT ]      <..>
1141
 *     Symbolically, a time-val may be viewed as
1142
 *           YYYYMMDDHHMMSS.sss
1143
 *     The "." and subsequent digits ("sss") are optional. <..>
1144
 *     Time values are always represented in UTC (GMT)
1145
 */
1146
static
1147
int send_mdtm(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1148
{
1149
    int rc;
1150
    char *msg = NULL;
1151
    apr_time_t mtime = 0L;
1152
    rc = proxy_ftp_command(apr_pstrcat(r->connection->pool, "MDTM ", ftp_data->path, CRLF, NULL),
1153
            r, ftp_ctrl, bb, &msg);
1154
    ftp_debug(r, "mdtm", msg);
1155
    /* then extract the Last-Modified time from it (YYYYMMDDhhmmss or YYYYMMDDhhmmss.xxx GMT). */
1156
    switch (rc / 100) {
1157
        case 2:
1158
            struct {
1159
                char YYYY[4+1];
1160
                char MM[2+1];
1161
                char DD[2+1];
1162
                char hh[2+1];
1163
                char mm[2+1];
1164
                char ss[2+1];
1165
            } time_val;
1166
            if (6 == sscanf(ftpmessage, "%4[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]",
1167
                        time_val.YYYY, time_val.MM, time_val.DD, time_val.hh, time_val.mm, time_val.ss)) {
1168
                struct tm tms;
1169
                memset (&tms, '\0', sizeof tms);
1170
                tms.tm_year = atoi(time_val.YYYY) - 1900;
1171
                tms.tm_mon  = atoi(time_val.MM)   - 1;
1172
                tms.tm_mday = atoi(time_val.DD);
1173
                tms.tm_hour = atoi(time_val.hh);
1174
                tms.tm_min  = atoi(time_val.mm);
1175
                tms.tm_sec  = atoi(time_val.ss);
1176
#ifdef HAVE_TIMEGM /* Does system have timegm()? */
1177
                mtime = timegm(&tms);
1178
                mtime *= APR_USEC_PER_SEC;
1179
#elif HAVE_GMTOFF /* does struct tm have a member tm_gmtoff? */
1180
                /* mktime will subtract the local timezone, which is not what we want.
1181
                 * Add it again because the MDTM string is GMT
1182
                 */
1183
                mtime = mktime(&tms);
1184
                mtime += tms.tm_gmtoff;
1185
                mtime *= APR_USEC_PER_SEC;
1186
#else
1187
                mtime = 0L;
1188
#endif
1189
            }
1190
        default:
1191
            mtime = 0L;
1054
    }
1192
    }
1055
    if (rc != 230 && rc != 331) {
1193
    ftp_data->cur.mtime = mtime;
1056
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1194
    return OK;
1195
} 
1196
#endif
1197
1198
1199
1200
/* responses: 221, 500
1201
 * 221 Service closing control connection.
1202
 * 500 Syntax error, command unrecognized. */
1203
static
1204
int send_quit(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1205
{
1206
    int rc;
1207
    char *msg = NULL;
1208
1209
    rc = proxy_ftp_command("QUIT" CRLF,
1210
            r, ftp_ctrl, bb, &msg);
1211
    ftp_debug(r, "quit", msg);
1212
    switch (rc / 100) {
1213
        default:
1214
            ftp_data->msg = msg;
1215
            return rc;
1057
    }
1216
    }
1217
}
1058
1218
1059
    if (rc == 331) {            /* send password */
1219
static
1060
        if (password == NULL) {
1220
int send_stor(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1061
            proxy_ftp_cleanup(r, backend);
1221
{
1062
            return ftp_unauthorized(r, 0);
1222
    int rc;
1063
        }
1223
    char *msg = NULL;
1064
1224
1065
        rc = proxy_ftp_command(apr_pstrcat(p, "PASS ", password, CRLF, NULL),
1225
    rc = proxy_ftp_command(apr_pstrcat(r->connection->pool, "STOR ", ftp_data->path, CRLF, NULL),
1066
                           r, origin, bb, &ftpmessage);
1226
            r, ftp_ctrl, bb, &msg);
1067
        /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */
1227
    ftp_debug(r, "stor", msg);
1068
        /* 230 User logged in, proceed. */
1228
    switch (rc / 100) {
1069
        /* 332 Need account for login. */
1229
        case 1:
1070
        /* 421 Service not available, closing control connection. */
1230
            ftp_data->state = ftp_start_response;
1071
        /* 500 Syntax error, command unrecognized. */
1231
            return OK;
1072
        /* 501 Syntax error in parameters or arguments. */
1232
        default:
1073
        /* 503 Bad sequence of commands. */
1233
            set_ftp_error(ftp_data, rc, msg);
1074
        /* 530 Not logged in. */
1234
            return HTTP_BAD_GATEWAY;
1075
        if (rc == -1 || rc == 421) {
1076
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1077
                                  "Error reading from remote server");
1078
        }
1079
        if (rc == 332) {
1080
            return ftp_proxyerror(r, backend, HTTP_UNAUTHORIZED,
1081
                  apr_pstrcat(p, "Need account for login: ", ftpmessage, NULL));
1082
        }
1083
        /* @@@ questionable -- we might as well return a 403 Forbidden here */
1084
        if (rc == 530) {
1085
            proxy_ftp_cleanup(r, backend);
1086
            return ftp_unauthorized(r, 1);      /* log it: passwd guessing
1087
                                                 * attempt? */
1088
        }
1089
        if (rc != 230 && rc != 202) {
1090
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1091
        }
1092
    }
1235
    }
1093
    apr_table_set(r->notes, "Directory-README", ftpmessage);
1236
}
1237
/* intermediate response for the LIST or RETR commands
1094
1238
1239
 * RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530,
1240
 * 550 NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502,
1241
 * 530
1095
1242
1096
    /* Special handling for leading "%2f": this enforces a "cwd /"
1243
 * 110 Restart marker reply.
1097
     * out of the $HOME directory which was the starting point after login
1244
 * 125 Data connection already open; transfer starting.
1098
     */
1245
 * 150 File status okay; about to open data connection.
1099
    if (strncasecmp(path, "%2f", 3) == 0) {
1246
 * 226 Closing data connection.
1100
        path += 3;
1247
 * 250 Requested file action okay, completed.
1101
        while (*path == '/') /* skip leading '/' (after root %2f) */
1248
 * 421 Service not available, closing control connection.
1102
            ++path;
1249
 * 425 Can't open data connection.
1250
 * 426 Connection closed; transfer aborted.
1251
 * 450 Requested file action not taken.
1252
 * 451 Requested action aborted. Local error in processing.
1253
 * 500 Syntax error, command unrecognized.
1254
 * 501 Syntax error in parameters or arguments.
1255
 * 530 Not logged in.
1256
 * 550 Requested action not taken. */
1257
static
1258
int send_retr(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1259
{
1260
    int rc;
1261
    char *msg = NULL;
1103
1262
1104
        rc = proxy_ftp_command("CWD /" CRLF, r, origin, bb, &ftpmessage);
1263
    rc = proxy_ftp_command(apr_pstrcat(r->connection->pool, "RETR ", ftp_data->path, CRLF, NULL),
1105
        if (rc == -1 || rc == 421)
1264
            r, ftp_ctrl, bb, &msg);
1106
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1265
    ftp_debug(r, "retr", msg);
1107
                                  "Error reading from remote server");
1266
    switch (rc / 100) {
1267
        case 1:
1268
            ftp_data->state = ftp_start_response;
1269
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1270
            send_mdtm(ftp_data, r, ftp_ctrl, bb);
1271
#endif
1272
            return OK;
1273
        default:
1274
            set_ftp_error(ftp_data, rc, msg);
1275
            return HTTP_BAD_GATEWAY;
1108
    }
1276
    }
1277
}
1109
1278
1110
    /*
1111
     * set the directory (walk directory component by component): this is
1112
     * what we must do if we don't know the OS type of the remote machine
1113
     */
1114
    for (;;) {
1115
        strp = strchr(path, '/');
1116
        if (strp == NULL)
1117
            break;
1118
        *strp = '\0';
1119
1279
1120
        len = decodeenc(path); /* Note! This decodes a %2f -> "/" */
1280
/* intermediate response for the LIST command 
1281
 * 125 transfer starting, 
1282
 * 150 opening data connection */
1283
static
1284
int send_list(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1285
{
1286
    int rc;
1287
    char *msg = NULL;
1121
1288
1122
        if (strchr(path, '/')) { /* are there now any '/' characters? */
1289
    rc = proxy_ftp_command("LIST" CRLF,
1123
            return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
1290
            r, ftp_ctrl, bb, &msg);
1124
                                  "Use of /%2f is only allowed at the base directory");
1291
    ftp_debug(r, "list", msg);
1125
        }
1292
    switch (rc / 100) {
1293
        case 1:
1294
            ftp_data->state = ftp_start_response;
1295
            return OK;
1296
        default:
1297
            set_ftp_error(ftp_data, rc, msg);
1298
            return HTTP_BAD_GATEWAY;
1299
    }
1300
}
1126
1301
1127
        /* NOTE: FTP servers do globbing on the path.
1302
static
1128
         * So we need to escape the URI metacharacters.
1303
int send_type(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1129
         * We use a special glob-escaping routine to escape globbing chars.
1304
{
1130
         * We could also have extended gen_test_char.c with a special T_ESCAPE_FTP_PATH
1305
    int rc;
1131
         */
1306
    char *msg = NULL;
1132
        rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1307
    char xtype[2] = {'A', 0};
1133
                           ftp_escape_globbingchars(p, path), CRLF, NULL),
1308
    xtype[0] = ftp_data->xfer_type;
1134
                           r, origin, bb, &ftpmessage);
1135
        *strp = '/';
1136
        /* responses: 250, 421, 500, 501, 502, 530, 550 */
1137
        /* 250 Requested file action okay, completed. */
1138
        /* 421 Service not available, closing control connection. */
1139
        /* 500 Syntax error, command unrecognized. */
1140
        /* 501 Syntax error in parameters or arguments. */
1141
        /* 502 Command not implemented. */
1142
        /* 530 Not logged in. */
1143
        /* 550 Requested action not taken. */
1144
        if (rc == -1 || rc == 421) {
1145
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1146
                                  "Error reading from remote server");
1147
        }
1148
        if (rc == 550) {
1149
            return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1150
        }
1151
        if (rc != 250) {
1152
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1153
        }
1154
1309
1155
        path = strp + 1;
1310
    rc = proxy_ftp_command(apr_pstrcat(r->connection->pool, "TYPE ", xtype, CRLF, NULL),
1311
            r, ftp_ctrl, bb, &msg);
1312
    ftp_debug(r, "type", msg);
1313
    switch (rc / 100) {
1314
        case 2:
1315
            ftp_data->state = ftp_check_trans_mode;
1316
            return OK;
1317
        default:
1318
            set_ftp_error(ftp_data, rc, msg);
1319
            return HTTP_BAD_GATEWAY;
1156
    }
1320
    }
1321
}
1157
1322
1158
    /*
1323
static
1159
     * IV: Make Data Connection? -------------------------
1324
int check_type(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1160
     *
1325
{
1161
     * Try EPSV, if that fails... try PASV, if that fails... try PORT.
1326
    /*There are no binary list retrivals. Force xfer_type to ASCII if it is a list*/
1162
     */
1327
    if (ftp_data->action == LISTING)
1163
/* this temporarily switches off EPSV/PASV */
1328
        ftp_data->xfer_type = 'A';
1164
/*goto bypass;*/
1329
    /* Check if the type in ftp_data is same as that of the cur,
1330
     * if it is we do not have to send a type*/
1331
    if (ftp_data->xfer_type ==  'A') {
1332
        ftp_data->state = ftp_check_trans_mode;
1333
        return OK;
1334
    }
1165
1335
1166
    /* set up data connection - EPSV */
1336
    return send_type(ftp_data, r, ftp_ctrl, bb);
1167
    {
1337
}
1168
        apr_sockaddr_t *data_addr;
1169
        char *data_ip;
1170
        apr_port_t data_port;
1171
1338
1172
        /*
1173
         * The EPSV command replaces PASV where both IPV4 and IPV6 is
1174
         * supported. Only the port is returned, the IP address is always the
1175
         * same as that on the control connection. Example: Entering Extended
1176
         * Passive Mode (|||6446|)
1177
         */
1178
        rc = proxy_ftp_command("EPSV" CRLF,
1179
                           r, origin, bb, &ftpmessage);
1180
        /* possible results: 227, 421, 500, 501, 502, 530 */
1181
        /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1182
        /* 421 Service not available, closing control connection. */
1183
        /* 500 Syntax error, command unrecognized. */
1184
        /* 501 Syntax error in parameters or arguments. */
1185
        /* 502 Command not implemented. */
1186
        /* 530 Not logged in. */
1187
        if (rc == -1 || rc == 421) {
1188
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1189
                                  "Error reading from remote server");
1190
        }
1191
        if (rc != 229 && rc != 500 && rc != 501 && rc != 502) {
1192
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1193
        }
1194
        else if (rc == 229) {
1195
            char *pstr;
1196
            char *tok_cntx;
1197
1339
1198
            pstr = ftpmessage;
1340
/*
1199
            pstr = apr_strtok(pstr, " ", &tok_cntx);    /* separate result code */
1341
 * The EPSV command replaces PASV where both IPV4 and IPV6 is
1200
            if (pstr != NULL) {
1342
 * supported. Only the port is returned, the IP address is always the
1201
                if (*(pstr + strlen(pstr) + 1) == '=') {
1343
 * same as that on the control connection. Example: Entering Extended
1202
                    pstr += strlen(pstr) + 2;
1344
 * Passive Mode (|||6446|)
1203
                }
1345
 *
1204
                else {
1346
 * possible results: 227, 421, 500, 501, 502, 530
1205
                    pstr = apr_strtok(NULL, "(", &tok_cntx);    /* separate address &
1347
 * 229 Entering Extended Passive Mode (port).
1206
                                                                 * port params */
1348
 * 421 Service not available, closing control connection.
1207
                    if (pstr != NULL)
1349
 * 500 Syntax error, command unrecognized.
1208
                        pstr = apr_strtok(NULL, ")", &tok_cntx);
1350
 * 501 Syntax error in parameters or arguments.
1209
                }
1351
 * 502 Command not implemented.
1210
            }
1352
 * 530 Not logged in. */
1211
1353
1212
            if (pstr) {
1354
static
1213
                apr_sockaddr_t *epsv_addr;
1355
int send_epsv(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1214
                data_port = atoi(pstr + 3);
1356
{
1357
    int rc;
1358
    char *msg = NULL;
1359
    
1360
    rc = proxy_ftp_command("EPSV" CRLF,
1361
            r, ftp_ctrl, bb, &msg);
1362
    ftp_debug(r, "epsv", msg);
1363
    switch (rc / 100) {
1364
        case 2:
1365
            {
1366
                char *pstr;
1367
                char *tok_cntx;
1368
                pstr = msg;
1369
                pstr = apr_strtok(pstr, " ", &tok_cntx);    /* separate result code */
1370
                if (pstr != NULL) {
1371
                    if (*(pstr + strlen(pstr) + 1) == '=')
1372
                        pstr += strlen(pstr) + 2;
1373
                    else {
1374
                        pstr = apr_strtok(NULL, "(", &tok_cntx);    /* separate address &
1375
                                                                     * port params */
1376
                        if (pstr != NULL)
1377
                            pstr = apr_strtok(NULL, ")", &tok_cntx);
1378
                    }
1379
                    if (pstr != 0 && strlen(pstr) > 3 ) {
1380
                        ftp_data->cur.data.port = atoi(pstr + 3);
1215
1381
1216
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1382
                        apr_sockaddr_t *data_addr;
1217
                       "proxy: FTP: EPSV contacting remote host on port %d",
1383
                        char *data_ip;
1218
                             data_port);
1384
                        apr_socket_addr_get(&data_addr, APR_REMOTE, ftp_data->p_conn->sock);
1385
                        apr_sockaddr_ip_get(&data_ip, data_addr);
1386
                        ftp_data->cur.data.ip = data_ip;
1219
1387
1220
                if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1388
                        ftp_data->mode = EPSV;
1221
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1389
                        ftp_data->state = ftp_connect_data_port;
1222
                                  "proxy: FTP: error creating EPSV socket");
1390
                        return OK;
1223
                    proxy_ftp_cleanup(r, backend);
1391
                    }
1224
                    return HTTP_INTERNAL_SERVER_ERROR;
1225
                }
1392
                }
1393
            }
1226
1394
1227
#if !defined (TPF) && !defined(BEOS)
1395
            msg = "Invalid epassive response.";
1228
                if (conf->recv_buffer_size > 0
1229
                        && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
1230
                                                    conf->recv_buffer_size))) {
1231
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1232
                                  "proxy: FTP: apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
1233
                }
1234
#endif
1235
1396
1236
                /* make the connection */
1397
        default:
1237
                apr_socket_addr_get(&data_addr, APR_REMOTE, sock);
1398
            set_ftp_error(ftp_data, rc, msg);
1238
                apr_sockaddr_ip_get(&data_ip, data_addr);
1399
            return HTTP_BAD_GATEWAY;
1239
                apr_sockaddr_info_get(&epsv_addr, data_ip, connect_addr->family, data_port, 0, p);
1240
                rv = apr_socket_connect(data_sock, epsv_addr);
1241
                if (rv != APR_SUCCESS) {
1242
                    ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1243
                                 "proxy: FTP: EPSV attempt to connect to %pI failed - Firewall/NAT?", epsv_addr);
1244
                    return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
1245
                                                                           "EPSV attempt to connect to %pI failed - firewall/NAT?", epsv_addr));
1246
                }
1247
                else {
1248
                    connect = 1;
1249
                }
1250
            }
1251
            else {
1252
                /* and try the regular way */
1253
                apr_socket_close(data_sock);
1254
            }
1255
        }
1256
    }
1400
    }
1401
}
1257
1402
1258
    /* set up data connection - PASV */
1403
static
1259
    if (!connect) {
1404
int send_pasv(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1260
        rc = proxy_ftp_command("PASV" CRLF,
1405
{
1261
                           r, origin, bb, &ftpmessage);
1406
    int rc;
1262
        /* possible results: 227, 421, 500, 501, 502, 530 */
1407
    char *msg = NULL;
1263
        /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1408
    
1264
        /* 421 Service not available, closing control connection. */
1409
    rc = proxy_ftp_command("PASV" CRLF,
1265
        /* 500 Syntax error, command unrecognized. */
1410
            r, ftp_ctrl, bb, &msg);
1266
        /* 501 Syntax error in parameters or arguments. */
1411
    ftp_debug(r, "pasv", msg);
1267
        /* 502 Command not implemented. */
1412
    switch (rc / 100) {
1268
        /* 530 Not logged in. */
1413
        case 2:
1269
        if (rc == -1 || rc == 421) {
1414
            {
1270
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1415
                unsigned int h0, h1, h2, h3, p0, p1;
1271
                                  "Error reading from remote server");
1416
                char *pstr;
1272
        }
1417
                char *tok_cntx;
1273
        if (rc != 227 && rc != 502) {
1274
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1275
        }
1276
        else if (rc == 227) {
1277
            unsigned int h0, h1, h2, h3, p0, p1;
1278
            char *pstr;
1279
            char *tok_cntx;
1280
1418
1281
/* FIXME: Check PASV against RFC1123 */
1419
                /*TODO: Check PASV against RFC1123 */
1282
1420
1283
            pstr = ftpmessage;
1421
                pstr = msg;
1284
            pstr = apr_strtok(pstr, " ", &tok_cntx);    /* separate result code */
1422
                pstr = apr_strtok(pstr, " ", &tok_cntx);    /* separate result code */
1285
            if (pstr != NULL) {
1423
                if (pstr != NULL) {
1286
                if (*(pstr + strlen(pstr) + 1) == '=') {
1424
                    if (*(pstr + strlen(pstr) + 1) == '=') {
1287
                    pstr += strlen(pstr) + 2;
1425
                        pstr += strlen(pstr) + 2;
1426
                    }
1427
                    else {
1428
                        pstr = apr_strtok(NULL, "(", &tok_cntx);    /* separate address &
1429
                                                                     * port params */
1430
                        if (pstr != NULL)
1431
                            pstr = apr_strtok(NULL, ")", &tok_cntx);
1432
                    }
1433
1434
                    if (pstr != NULL && (sscanf(pstr, "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) {
1435
                        ftp_data->cur.data.port = (p1 << 8) + p0;
1436
                        char* data_ip = apr_psprintf(r->connection->pool, "%d.%d.%d.%d", h3, h2, h1, h0);
1437
                        ftp_data->cur.data.ip = data_ip;
1438
1439
                        ftp_data->mode = PASV;
1440
                        ftp_data->state = ftp_connect_data_port;
1441
                        return OK;
1442
                    }
1288
                }
1443
                }
1289
                else {
1290
                    pstr = apr_strtok(NULL, "(", &tok_cntx);    /* separate address &
1291
                                                                 * port params */
1292
                    if (pstr != NULL)
1293
                        pstr = apr_strtok(NULL, ")", &tok_cntx);
1294
                }
1295
            }
1444
            }
1296
1445
1297
/* FIXME: Only supports IPV4 - fix in RFC2428 */
1446
            msg = "Invalid passive response.";
1298
1447
1299
            if (pstr != NULL && (sscanf(pstr,
1448
        default:
1300
                 "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) {
1449
            set_ftp_error(ftp_data, rc, msg);
1450
            return HTTP_BAD_GATEWAY;
1451
    }
1452
}
1301
1453
1302
                apr_sockaddr_t *pasv_addr;
1303
                apr_port_t pasvport = (p1 << 8) + p0;
1304
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1305
                          "proxy: FTP: PASV contacting host %d.%d.%d.%d:%d",
1306
                             h3, h2, h1, h0, pasvport);
1307
1454
1308
                if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1455
static
1309
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1456
int setup_local_port(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1310
                                  "proxy: error creating PASV socket");
1457
{
1311
                    proxy_ftp_cleanup(r, backend);
1458
    int rv = 0;
1312
                    return HTTP_INTERNAL_SERVER_ERROR;
1459
    apr_sockaddr_t *local_addr;
1313
                }
1460
    char *local_ip;
1461
    apr_port_t local_port;
1462
    apr_socket_t *local_sock, *sock;
1463
    apr_sockaddr_t *connect_addr = ftp_data->p_conn->addr;
1314
1464
1315
#if !defined (TPF) && !defined(BEOS)
1465
    if ((rv = apr_socket_create(&local_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1316
                if (conf->recv_buffer_size > 0
1466
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1317
                        && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
1467
                "proxy: FTP: error creating local socket");
1318
                                                    conf->recv_buffer_size))) {
1468
        return HTTP_INTERNAL_SERVER_ERROR;
1319
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1469
    }
1320
                                  "proxy: FTP: apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
1470
    apr_socket_addr_get(&local_addr, APR_LOCAL, sock);
1321
                }
1471
    local_port = local_addr->port;
1322
#endif
1472
    apr_sockaddr_ip_get(&local_ip, local_addr);
1323
1473
1324
                /* make the connection */
1474
    if ((rv = apr_socket_opt_set(local_sock, APR_SO_REUSEADDR, 1))
1325
                apr_sockaddr_info_get(&pasv_addr, apr_psprintf(p, "%d.%d.%d.%d", h3, h2, h1, h0), connect_addr->family, pasvport, 0, p);
1475
            != APR_SUCCESS) {
1326
                rv = apr_socket_connect(data_sock, pasv_addr);
1476
#ifndef _OSD_POSIX              /* BS2000 has this option "always on" */
1327
                if (rv != APR_SUCCESS) {
1477
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1328
                    ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1478
                "proxy: FTP: error setting reuseaddr option");
1329
                                 "proxy: FTP: PASV attempt to connect to %pI failed - Firewall/NAT?", pasv_addr);
1479
        return HTTP_INTERNAL_SERVER_ERROR;
1330
                    return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
1480
#endif                          /* _OSD_POSIX */
1331
                                                                           "PASV attempt to connect to %pI failed - firewall/NAT?", pasv_addr));
1332
                }
1333
                else {
1334
                    connect = 1;
1335
                }
1336
            }
1337
            else {
1338
                /* and try the regular way */
1339
                apr_socket_close(data_sock);
1340
            }
1341
        }
1342
    }
1481
    }
1343
/*bypass:*/
1344
1482
1345
    /* set up data connection - PORT */
1483
    apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
1346
    if (!connect) {
1347
        apr_sockaddr_t *local_addr;
1348
        char *local_ip;
1349
        apr_port_t local_port;
1350
        unsigned int h0, h1, h2, h3, p0, p1;
1351
1484
1352
        if ((rv = apr_socket_create(&local_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1485
    if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
1353
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1486
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1354
                          "proxy: FTP: error creating local socket");
1487
                "proxy: FTP: error binding to ftp data socket %pI", local_addr);
1355
            proxy_ftp_cleanup(r, backend);
1488
        return HTTP_INTERNAL_SERVER_ERROR;
1356
            return HTTP_INTERNAL_SERVER_ERROR;
1489
    }
1357
        }
1358
        apr_socket_addr_get(&local_addr, APR_LOCAL, sock);
1359
        local_port = local_addr->port;
1360
        apr_sockaddr_ip_get(&local_ip, local_addr);
1361
1490
1362
        if ((rv = apr_socket_opt_set(local_sock, APR_SO_REUSEADDR, one))
1491
    /* only need a short queue */
1363
                != APR_SUCCESS) {
1492
    if ((rv = apr_socket_listen(local_sock, 2)) != APR_SUCCESS) {
1364
#ifndef _OSD_POSIX              /* BS2000 has this option "always on" */
1493
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1365
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1494
                "proxy: FTP: error listening to ftp data socket %pI", local_addr);
1366
                          "proxy: FTP: error setting reuseaddr option");
1495
        return HTTP_INTERNAL_SERVER_ERROR;
1367
            proxy_ftp_cleanup(r, backend);
1496
    }
1368
            return HTTP_INTERNAL_SERVER_ERROR;
1369
#endif                          /* _OSD_POSIX */
1370
        }
1371
1497
1372
        apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
1498
    ftp_data->cur.data.ip = local_ip;
1499
    ftp_data->cur.data.port = local_port;
1500
    ftp_data->cur.data.local_sock = local_sock;
1501
    return 0;
1502
}
1373
1503
1374
        if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
1504
/* possible results: 200, 421, 500, 501, 502, 530
1375
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1505
 * 200 Command okay.
1376
            "proxy: FTP: error binding to ftp data socket %pI", local_addr);
1506
 * 421 Service not available, closing control connection.
1377
            proxy_ftp_cleanup(r, backend);
1507
 * 500 Syntax error, command unrecognized.
1378
            return HTTP_INTERNAL_SERVER_ERROR;
1508
 * 501 Syntax error in parameters or arguments.
1379
        }
1509
 * 502 Command not implemented.
1510
 * 530 Not logged in. */
1511
static
1512
int send_port(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1513
{
1514
    unsigned int h0, h1, h2, h3, p0, p1;
1515
    char* msg;
1380
1516
1381
        /* only need a short queue */
1517
    int rc = setup_local_port(ftp_data, r, ftp_ctrl, bb);
1382
        if ((rv = apr_socket_listen(local_sock, 2)) != APR_SUCCESS) {
1518
    if (rc) {
1383
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1519
        ftp_data->msg = "Unable to establish server connection.";
1384
                          "proxy: FTP: error listening to ftp data socket %pI", local_addr);
1520
        return rc;
1385
            proxy_ftp_cleanup(r, backend);
1521
    }
1386
            return HTTP_INTERNAL_SERVER_ERROR;
1522
    char* local_ip = ftp_data->cur.data.ip;
1387
        }
1523
    int local_port = ftp_data->cur.data.port;
1524
    if (local_ip && (sscanf(local_ip, "%d.%d.%d.%d", &h3, &h2, &h1, &h0) == 4)) {
1525
        p1 = (local_port >> 8);
1526
        p0 = (local_port & 0xFF);
1527
    } else {
1528
        /* IPV6 FIXME:
1529
         * The EPRT command replaces PORT where both IPV4 and IPV6 is supported. The first
1530
         * number (1,2) indicates the protocol type. Examples:
1531
         *   EPRT |1|132.235.1.2|6275|
1532
         *   EPRT |2|1080::8:800:200C:417A|5282|
1533
         */
1534
        ftp_data->msg = "Connect to IPV6 ftp server using EPRT not supported. Enable EPSV.";
1535
        return HTTP_NOT_IMPLEMENTED;
1536
    }
1388
1537
1389
/* FIXME: Sent PORT here */
1390
1538
1391
        if (local_ip && (sscanf(local_ip,
1539
    rc = proxy_ftp_command(apr_psprintf(r->connection->pool, "PORT %d,%d,%d,%d,%d,%d" CRLF, h3, h2, h1, h0, p1, p0),
1392
                                "%d.%d.%d.%d", &h3, &h2, &h1, &h0) == 4)) {
1540
            r, ftp_ctrl, bb, &msg);
1393
            p1 = (local_port >> 8);
1541
    switch ( rc / 100) {
1394
            p0 = (local_port & 0xFF);
1542
        case 2:
1543
            ftp_data->mode = PORT;
1544
            ftp_data->state = ftp_check_transfer;
1545
            return OK;
1395
1546
1396
            rc = proxy_ftp_command(apr_psprintf(p, "PORT %d,%d,%d,%d,%d,%d" CRLF, h3, h2, h1, h0, p1, p0),
1547
        default:
1397
                           r, origin, bb, &ftpmessage);
1548
            set_ftp_error(ftp_data, rc, msg);
1398
            /* possible results: 200, 421, 500, 501, 502, 530 */
1549
            return HTTP_BAD_GATEWAY;
1399
            /* 200 Command okay. */
1550
    }
1400
            /* 421 Service not available, closing control connection. */
1551
}
1401
            /* 500 Syntax error, command unrecognized. */
1402
            /* 501 Syntax error in parameters or arguments. */
1403
            /* 502 Command not implemented. */
1404
            /* 530 Not logged in. */
1405
            if (rc == -1 || rc == 421) {
1406
                return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1407
                                      "Error reading from remote server");
1408
            }
1409
            if (rc != 200) {
1410
                return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, buffer);
1411
            }
1412
1552
1413
            /* signal that we must use the EPRT/PORT loop */
1553
static
1414
            use_port = 1;
1554
int check_trans_mode(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1415
        }
1555
{
1416
        else {
1556
    int rc;
1417
/* IPV6 FIXME:
1557
    switch (ftp_data->mode) {
1418
 * The EPRT command replaces PORT where both IPV4 and IPV6 is supported. The first
1558
        case EPSV:
1419
 * number (1,2) indicates the protocol type. Examples:
1559
            rc = send_epsv(ftp_data, r, ftp_ctrl, bb);
1420
 *   EPRT |1|132.235.1.2|6275|
1560
            if (rc == OK)
1421
 *   EPRT |2|1080::8:800:200C:417A|5282|
1561
                break;
1422
 */
1562
        case PASV:
1423
            return ftp_proxyerror(r, backend, HTTP_NOT_IMPLEMENTED,
1563
            rc = send_pasv(ftp_data, r, ftp_ctrl, bb);
1424
                                  "Connect to IPV6 ftp server using EPRT not supported. Enable EPSV.");
1564
            if (rc == OK)
1425
        }
1565
                break;
1566
        case PORT:
1567
            rc = send_port(ftp_data, r, ftp_ctrl, bb);
1568
            if (rc == OK)
1569
                break;
1570
        default:
1571
            return rc;
1426
    }
1572
    }
1573
    return OK; 
1574
}
1427
1575
1428
1576
1429
    /*
1577
static
1430
     * V: Set The Headers -------------------
1578
int connect_data_port(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1431
     *
1579
{
1432
     * Get the size of the request, set up the environment for HTTP.
1580
    apr_socket_t *data_sock = NULL;
1433
     */
1581
    apr_sockaddr_t *connect_addr = ftp_data->p_conn->addr;
1434
1582
1435
    /* set request; "path" holds last path component */
1583
    int data_port = ftp_data->cur.data.port;
1436
    len = decodeenc(path);
1584
    int rv;
1437
1585
1438
    if (strchr(path, '/')) { /* are there now any '/' characters? */
1586
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1439
       return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
1587
            "proxy: FTP: EPSV contacting remote host on port %d",
1440
                             "Use of /%2f is only allowed at the base directory");
1588
            data_port);
1589
1590
    if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1591
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1592
                "proxy: FTP: error creating EPSV socket");
1593
        return HTTP_INTERNAL_SERVER_ERROR;
1441
    }
1594
    }
1442
1595
1443
    /* If len == 0 then it must be a directory (you can't RETR nothing)
1596
#if !defined (TPF) && !defined(BEOS)
1444
     * Also, don't allow to RETR by wildcard. Instead, create a dirlisting
1597
    if (ftp_data->cur.conf->recv_buffer_size > 0
1445
     */
1598
            && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
1446
    if (len == 0 || ftp_check_globbingchars(path)) {
1599
                    ftp_data->cur.conf->recv_buffer_size))) {
1447
        dirlisting = 1;
1600
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1601
                "proxy: FTP: apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
1448
    }
1602
    }
1449
    else {
1603
#endif
1450
        /* (from FreeBSD ftpd):
1604
    {
1451
         * SIZE is not in RFC959, but Postel has blessed it and
1605
        /* make the connection */
1452
         * it will be in the updated RFC.
1606
        apr_sockaddr_t *d_addr;
1453
         *
1607
        apr_sockaddr_info_get(&d_addr, ftp_data->cur.data.ip, connect_addr->family, data_port, 0, ftp_data->p_conn->pool);
1454
         * Return size of file in a format suitable for
1608
        rv = apr_socket_connect(data_sock, d_addr);
1455
         * using with RESTART (we just count bytes).
1609
        if (rv != APR_SUCCESS) {
1456
         */
1610
            ftp_data->msg = apr_psprintf(r->pool,
1457
        /* from draft-ietf-ftpext-mlst-14.txt:
1611
                    "EPSV attempt to connect to %pI failed - firewall/NAT?", d_addr);
1458
         * This value will
1612
            ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, ftp_data->msg);
1459
         * change depending on the current STRUcture, MODE and TYPE of the data
1613
            return HTTP_BAD_GATEWAY;
1460
         * connection, or a data connection which would be created were one
1461
         * created now.  Thus, the result of the SIZE command is dependent on
1462
         * the currently established STRU, MODE and TYPE parameters.
1463
         */
1464
        /* Therefore: switch to binary if the user did not specify ";type=a" */
1465
        ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
1466
        rc = proxy_ftp_command(apr_pstrcat(p, "SIZE ",
1467
                           ftp_escape_globbingchars(p, path), CRLF, NULL),
1468
                           r, origin, bb, &ftpmessage);
1469
        if (rc == -1 || rc == 421) {
1470
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1471
                                  "Error reading from remote server");
1472
        }
1614
        }
1473
        else if (rc == 213) {/* Size command ok */
1474
            int j;
1475
            for (j = 0; apr_isdigit(ftpmessage[j]); j++)
1476
                ;
1477
            ftpmessage[j] = '\0';
1478
            if (ftpmessage[0] != '\0')
1479
                 size = ftpmessage; /* already pstrdup'ed: no copy necessary */
1480
        }
1481
        else if (rc == 550) {    /* Not a regular file */
1482
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1483
                             "proxy: FTP: SIZE shows this is a directory");
1484
            dirlisting = 1;
1485
            rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1486
                           ftp_escape_globbingchars(p, path), CRLF, NULL),
1487
                           r, origin, bb, &ftpmessage);
1488
            /* possible results: 250, 421, 500, 501, 502, 530, 550 */
1489
            /* 250 Requested file action okay, completed. */
1490
            /* 421 Service not available, closing control connection. */
1491
            /* 500 Syntax error, command unrecognized. */
1492
            /* 501 Syntax error in parameters or arguments. */
1493
            /* 502 Command not implemented. */
1494
            /* 530 Not logged in. */
1495
            /* 550 Requested action not taken. */
1496
            if (rc == -1 || rc == 421) {
1497
                return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1498
                                      "Error reading from remote server");
1499
            }
1500
            if (rc == 550) {
1501
                return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1502
            }
1503
            if (rc != 250) {
1504
                return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1505
            }
1506
            path = "";
1507
            len = 0;
1508
        }
1509
    }
1615
    }
1616
    ftp_data->cur.data.sock = data_sock;
1617
    ftp_data->state = ftp_check_transfer;
1618
    return OK;
1619
}
1510
1620
1511
    cwd = ftp_get_PWD(r, origin, bb);
1512
    if (cwd != NULL) {
1513
        apr_table_set(r->notes, "Directory-PWD", cwd);
1514
    }
1515
1621
1516
    if (dirlisting) {
1517
        ftp_set_TYPE('A', r, origin, bb, NULL);
1518
        /* If the current directory contains no slash, we are talking to
1519
         * a non-unix ftp system. Try LIST instead of "LIST -lag", it
1520
         * should return a long listing anyway (unlike NLST).
1521
         * Some exotic FTP servers might choke on the "-lag" switch.
1522
         */
1523
        /* Note that we do not escape the path here, to allow for
1524
         * queries like: ftp://user@host/apache/src/server/http_*.c
1525
         */
1526
        if (len != 0)
1527
            buf = apr_pstrcat(p, "LIST ", path, CRLF, NULL);
1528
        else if (cwd == NULL || strchr(cwd, '/') != NULL)
1529
            buf = apr_pstrcat(p, "LIST -lag", CRLF, NULL);
1530
        else
1531
            buf = "LIST" CRLF;
1532
    }
1533
    else {
1534
        /* switch to binary if the user did not specify ";type=a" */
1535
        ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
1536
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1537
        /* from draft-ietf-ftpext-mlst-14.txt:
1538
         *   The FTP command, MODIFICATION TIME (MDTM), can be used to determine
1539
         *   when a file in the server NVFS was last modified.     <..>
1540
         *   The syntax of a time value is:
1541
         *           time-val       = 14DIGIT [ "." 1*DIGIT ]      <..>
1542
         *     Symbolically, a time-val may be viewed as
1543
         *           YYYYMMDDHHMMSS.sss
1544
         *     The "." and subsequent digits ("sss") are optional. <..>
1545
         *     Time values are always represented in UTC (GMT)
1546
         */
1547
        rc = proxy_ftp_command(apr_pstrcat(p, "MDTM ", ftp_escape_globbingchars(p, path), CRLF, NULL),
1548
                               r, origin, bb, &ftpmessage);
1549
        /* then extract the Last-Modified time from it (YYYYMMDDhhmmss or YYYYMMDDhhmmss.xxx GMT). */
1550
        if (rc == 213) {
1551
        struct {
1552
            char YYYY[4+1];
1553
        char MM[2+1];
1554
        char DD[2+1];
1555
        char hh[2+1];
1556
        char mm[2+1];
1557
        char ss[2+1];
1558
        } time_val;
1559
        if (6 == sscanf(ftpmessage, "%4[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]",
1560
            time_val.YYYY, time_val.MM, time_val.DD, time_val.hh, time_val.mm, time_val.ss)) {
1561
                struct tm tms;
1562
        memset (&tms, '\0', sizeof tms);
1563
        tms.tm_year = atoi(time_val.YYYY) - 1900;
1564
        tms.tm_mon  = atoi(time_val.MM)   - 1;
1565
        tms.tm_mday = atoi(time_val.DD);
1566
        tms.tm_hour = atoi(time_val.hh);
1567
        tms.tm_min  = atoi(time_val.mm);
1568
        tms.tm_sec  = atoi(time_val.ss);
1569
#ifdef HAVE_TIMEGM /* Does system have timegm()? */
1570
        mtime = timegm(&tms);
1571
        mtime *= APR_USEC_PER_SEC;
1572
#elif HAVE_GMTOFF /* does struct tm have a member tm_gmtoff? */
1573
                /* mktime will subtract the local timezone, which is not what we want.
1574
         * Add it again because the MDTM string is GMT
1575
         */
1576
        mtime = mktime(&tms);
1577
        mtime += tms.tm_gmtoff;
1578
        mtime *= APR_USEC_PER_SEC;
1579
#else
1580
        mtime = 0L;
1581
#endif
1582
            }
1583
    }
1584
#endif /* USE_MDTM */
1585
/* FIXME: Handle range requests - send REST */
1586
        buf = apr_pstrcat(p, "RETR ", ftp_escape_globbingchars(p, path), CRLF, NULL);
1587
    }
1588
    rc = proxy_ftp_command(buf, r, origin, bb, &ftpmessage);
1589
    /* rc is an intermediate response for the LIST or RETR commands */
1590
1622
1591
    /*
1623
static
1592
     * RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530,
1624
int check_transfer(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1593
     * 550 NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502,
1625
{
1594
     * 530
1626
    switch (ftp_data->action) {
1595
     */
1627
        case STOR:
1596
    /* 110 Restart marker reply. */
1628
            ftp_data->state = ftp_send_stor;
1597
    /* 125 Data connection already open; transfer starting. */
1629
            break;
1598
    /* 150 File status okay; about to open data connection. */
1630
        case LISTING:
1599
    /* 226 Closing data connection. */
1631
            ftp_data->state = ftp_send_list;
1600
    /* 250 Requested file action okay, completed. */
1632
            break;
1601
    /* 421 Service not available, closing control connection. */
1633
        default:
1602
    /* 425 Can't open data connection. */
1634
        case RETR:
1603
    /* 426 Connection closed; transfer aborted. */
1635
            ftp_data->state = ftp_send_retr;
1604
    /* 450 Requested file action not taken. */
1636
            break;
1605
    /* 451 Requested action aborted. Local error in processing. */
1606
    /* 500 Syntax error, command unrecognized. */
1607
    /* 501 Syntax error in parameters or arguments. */
1608
    /* 530 Not logged in. */
1609
    /* 550 Requested action not taken. */
1610
    if (rc == -1 || rc == 421) {
1611
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1612
                              "Error reading from remote server");
1613
    }
1637
    }
1614
    if (rc == 550) {
1638
    return OK;
1615
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1639
}
1616
                     "proxy: FTP: RETR failed, trying LIST instead");
1617
1640
1618
        /* Directory Listings should always be fetched in ASCII mode */
1641
static
1619
        dirlisting = 1;
1642
int start_response(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1620
        ftp_set_TYPE('A', r, origin, bb, NULL);
1643
{
1644
    char *msg = NULL;
1645
    char dates[APR_RFC822_DATE_LEN];
1621
1646
1622
        rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1647
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1623
                               ftp_escape_globbingchars(p, path), CRLF, NULL),
1648
    apr_time_t mtime = ftp_data->mtime;
1624
                               r, origin, bb, &ftpmessage);
1649
#endif
1625
        /* possible results: 250, 421, 500, 501, 502, 530, 550 */
1626
        /* 250 Requested file action okay, completed. */
1627
        /* 421 Service not available, closing control connection. */
1628
        /* 500 Syntax error, command unrecognized. */
1629
        /* 501 Syntax error in parameters or arguments. */
1630
        /* 502 Command not implemented. */
1631
        /* 530 Not logged in. */
1632
        /* 550 Requested action not taken. */
1633
        if (rc == -1 || rc == 421) {
1634
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1635
                                  "Error reading from remote server");
1636
        }
1637
        if (rc == 550) {
1638
            return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1639
        }
1640
        if (rc != 250) {
1641
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1642
        }
1643
1650
1644
        /* Update current directory after CWD */
1645
        cwd = ftp_get_PWD(r, origin, bb);
1646
        if (cwd != NULL) {
1647
            apr_table_set(r->notes, "Directory-PWD", cwd);
1648
        }
1649
1650
        /* See above for the "LIST" vs. "LIST -lag" discussion. */
1651
        rc = proxy_ftp_command((cwd == NULL || strchr(cwd, '/') != NULL)
1652
                               ? "LIST -lag" CRLF : "LIST" CRLF,
1653
                               r, origin, bb, &ftpmessage);
1654
1655
        /* rc is an intermediate response for the LIST command (125 transfer starting, 150 opening data connection) */
1656
        if (rc == -1 || rc == 421)
1657
            return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1658
                                  "Error reading from remote server");
1659
    }
1660
    if (rc != 125 && rc != 150 && rc != 226 && rc != 250) {
1661
        return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1662
    }
1663
1664
    r->status = HTTP_OK;
1651
    r->status = HTTP_OK;
1665
    r->status_line = "200 OK";
1652
    r->status_line = "200 OK";
1666
1653
Lines 1669-1695 Link Here
1669
    apr_table_setn(r->headers_out, "Server", ap_get_server_banner());
1656
    apr_table_setn(r->headers_out, "Server", ap_get_server_banner());
1670
1657
1671
    /* set content-type */
1658
    /* set content-type */
1672
    if (dirlisting) {
1659
    if (ftp_data->action == LISTING) {
1673
        ap_set_content_type(r, "text/html");
1660
        ap_set_content_type(r, "text/html");
1674
    }
1661
    }
1675
    else {
1662
    else {
1676
        if (r->content_type) {
1663
        if (r->content_type) {
1677
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1664
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1678
                     "proxy: FTP: Content-Type set to %s", r->content_type);
1665
                    "proxy: FTP: Content-Type set to %s", r->content_type);
1679
        }
1666
        }
1680
        else {
1667
        else {
1681
            ap_set_content_type(r, ap_default_type(r));
1668
            ap_set_content_type(r, ap_default_type(r));
1682
        }
1669
        }
1683
        if (xfer_type != 'A' && size != NULL) {
1670
        if (ftp_data->xfer_type != 'A' ) {
1684
            /* We "trust" the ftp server to really serve (size) bytes... */
1671
            apr_table_setn(r->headers_out, "Content-Length", ftp_data->size);
1685
            apr_table_setn(r->headers_out, "Content-Length", size);
1686
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1672
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1687
                         "proxy: FTP: Content-Length set to %s", size);
1673
                    "proxy: FTP: Content-Length set to %s", ftp_data->size);
1688
        }
1674
        }
1689
    }
1675
    }
1690
    apr_table_setn(r->headers_out, "Content-Type", r->content_type);
1676
    apr_table_setn(r->headers_out, "Content-Type", r->content_type);
1691
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1677
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1692
                 "proxy: FTP: Content-Type set to %s", r->content_type);
1678
            "proxy: FTP: Content-Type set to %s", r->content_type);
1693
1679
1694
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1680
#if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1695
    if (mtime != 0L) {
1681
    if (mtime != 0L) {
Lines 1697-1703 Link Here
1697
        apr_rfc822_date(datestr, mtime);
1683
        apr_rfc822_date(datestr, mtime);
1698
        apr_table_set(r->headers_out, "Last-Modified", datestr);
1684
        apr_table_set(r->headers_out, "Last-Modified", datestr);
1699
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1685
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1700
                 "proxy: FTP: Last-Modified set to %s", datestr);
1686
                "proxy: FTP: Last-Modified set to %s", datestr);
1701
    }
1687
    }
1702
#endif /* USE_MDTM */
1688
#endif /* USE_MDTM */
1703
1689
Lines 1705-1722 Link Here
1705
     * @@@ FIXME (e.g., for ftp://user@host/file*.tar.gz,
1691
     * @@@ FIXME (e.g., for ftp://user@host/file*.tar.gz,
1706
     * @@@        the encoding is currently set to x-gzip)
1692
     * @@@        the encoding is currently set to x-gzip)
1707
     */
1693
     */
1708
    if (dirlisting && r->content_encoding != NULL)
1694
    if ((ftp_data->action == LISTING) && r->content_encoding != NULL)
1709
        r->content_encoding = NULL;
1695
        r->content_encoding = NULL;
1710
1696
1711
    /* set content-encoding (not for dir listings, they are uncompressed)*/
1697
    /* set content-encoding (not for dir listings, they are uncompressed)*/
1712
    if (r->content_encoding != NULL && r->content_encoding[0] != '\0') {
1698
    if (r->content_encoding != NULL && r->content_encoding[0] != '\0') {
1713
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1699
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1714
             "proxy: FTP: Content-Encoding set to %s", r->content_encoding);
1700
                "proxy: FTP: Content-Encoding set to %s", r->content_encoding);
1715
        apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
1701
        apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
1716
    }
1702
    }
1717
1703
1704
    ftp_data->state = ftp_transfer_stream;
1705
    return OK;
1706
}
1707
1708
static
1709
int transfer_stream(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1710
{
1711
    int rc;
1712
    char *msg = NULL;
1713
1714
    apr_status_t rv;
1715
    apr_socket_t *local_sock = ftp_data->cur.data.local_sock;
1716
    apr_socket_t *data_sock = ftp_data->cur.data.sock;
1717
    conn_rec *c = r->connection;
1718
    char dates[APR_RFC822_DATE_LEN];
1719
    conn_rec *data = NULL;
1720
1718
    /* wait for connection */
1721
    /* wait for connection */
1719
    if (use_port) {
1722
    if (ftp_data->mode == PORT) {
1720
        for (;;) {
1723
        for (;;) {
1721
            rv = apr_socket_accept(&data_sock, local_sock, r->pool);
1724
            rv = apr_socket_accept(&data_sock, local_sock, r->pool);
1722
            if (rv == APR_EINTR) {
1725
            if (rv == APR_EINTR) {
Lines 1727-1750 Link Here
1727
            }
1730
            }
1728
            else {
1731
            else {
1729
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1732
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1730
                            "proxy: FTP: failed to accept data connection");
1733
                        "proxy: FTP: failed to accept data connection");
1731
                proxy_ftp_cleanup(r, backend);
1732
                return HTTP_BAD_GATEWAY;
1734
                return HTTP_BAD_GATEWAY;
1733
            }
1735
            }
1734
        }
1736
        }
1735
    }
1737
    }
1736
1738
1737
    /* the transfer socket is now open, create a new connection */
1739
    /* the transfer socket is now open, create a new connection */
1738
    data = ap_run_create_connection(p, r->server, data_sock, r->connection->id,
1740
    data = ap_run_create_connection(r->pool, r->server, data_sock, r->connection->id,
1739
                                    r->connection->sbh, c->bucket_alloc);
1741
            r->connection->sbh, c->bucket_alloc);
1740
    if (!data) {
1742
    if (!data) {
1741
        /*
1743
        /*
1742
         * the peer reset the connection already; ap_run_create_connection() closed
1744
         * the peer reset the connection already; ap_run_create_connection() closed
1743
         * the socket
1745
         * the socket
1744
         */
1746
         */
1745
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1747
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1746
          "proxy: FTP: an error occurred creating the transfer connection");
1748
                "proxy: FTP: an error occurred creating the transfer connection");
1747
        proxy_ftp_cleanup(r, backend);
1748
        return HTTP_INTERNAL_SERVER_ERROR;
1749
        return HTTP_INTERNAL_SERVER_ERROR;
1749
    }
1750
    }
1750
1751
Lines 1752-1761 Link Here
1752
    rc = ap_run_pre_connection(data, data_sock);
1753
    rc = ap_run_pre_connection(data, data_sock);
1753
    if (rc != OK && rc != DONE) {
1754
    if (rc != OK && rc != DONE) {
1754
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1755
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1755
                     "proxy: FTP: pre_connection setup failed (%d)",
1756
                "proxy: FTP: pre_connection setup failed (%d)",
1756
                     rc);
1757
                rc);
1757
        data->aborted = 1;
1758
        data->aborted = 1;
1758
        proxy_ftp_cleanup(r, backend);
1759
        return rc;
1759
        return rc;
1760
    }
1760
    }
1761
1761
Lines 1768-1774 Link Here
1768
    /* send response */
1768
    /* send response */
1769
    r->sent_bodyct = 1;
1769
    r->sent_bodyct = 1;
1770
1770
1771
    if (dirlisting) {
1771
    if (ftp_data->action == LISTING) {
1772
        /* insert directory filter */
1772
        /* insert directory filter */
1773
        ap_add_output_filter("PROXY_SEND_DIR", NULL, r, r->connection);
1773
        ap_add_output_filter("PROXY_SEND_DIR", NULL, r, r->connection);
1774
    }
1774
    }
Lines 1779-1799 Link Here
1779
        int finish = FALSE;
1779
        int finish = FALSE;
1780
1780
1781
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1781
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1782
                     "proxy: FTP: start body send");
1782
                "proxy: FTP: start body send");
1783
1783
1784
        /* read the body, pass it to the output filters */
1784
        /* read the body, pass it to the output filters */
1785
        while (ap_get_brigade(data->input_filters,
1785
        while (ap_get_brigade(data->input_filters,
1786
                              bb,
1786
                    bb,
1787
                              AP_MODE_READBYTES,
1787
                    AP_MODE_READBYTES,
1788
                              APR_BLOCK_READ,
1788
                    APR_BLOCK_READ,
1789
                              conf->io_buffer_size) == APR_SUCCESS) {
1789
                    ftp_data->cur.conf->io_buffer_size) == APR_SUCCESS) {
1790
#if DEBUGGING
1790
#if DEBUGGING
1791
            {
1791
            {
1792
                apr_off_t readbytes;
1792
                apr_off_t readbytes;
1793
                apr_brigade_length(bb, 0, &readbytes);
1793
                apr_brigade_length(bb, 0, &readbytes);
1794
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
1794
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
1795
                             r->server, "proxy (PID %d): readbytes: %#x",
1795
                        r->server, "proxy (PID %d): readbytes: %#x",
1796
                             getpid(), readbytes);
1796
                        getpid(), readbytes);
1797
            }
1797
            }
1798
#endif
1798
#endif
1799
            /* sanity check */
1799
            /* sanity check */
Lines 1813-1819 Link Here
1813
                apr_socket_close(data_sock);
1813
                apr_socket_close(data_sock);
1814
                data_sock = NULL;
1814
                data_sock = NULL;
1815
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1815
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1816
                             "proxy: FTP: data connection closed");
1816
                        "proxy: FTP: data connection closed");
1817
                /* signal that we must leave */
1817
                /* signal that we must leave */
1818
                finish = TRUE;
1818
                finish = TRUE;
1819
            }
1819
            }
Lines 1826-1832 Link Here
1826
1826
1827
            /* try send what we read */
1827
            /* try send what we read */
1828
            if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
1828
            if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
1829
                || c->aborted) {
1829
                    || c->aborted) {
1830
                /* Ack! Phbtt! Die! User aborted! */
1830
                /* Ack! Phbtt! Die! User aborted! */
1831
                finish = TRUE;
1831
                finish = TRUE;
1832
            }
1832
            }
Lines 1840-1857 Link Here
1840
            }
1840
            }
1841
        }
1841
        }
1842
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1842
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1843
                     "proxy: FTP: end body send");
1843
                "proxy: FTP: end body send");
1844
1844
1845
    }
1845
    }
1846
    if (data_sock) {
1847
        ap_flush_conn(data);
1848
        apr_socket_close(data_sock);
1849
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1850
                     "proxy: FTP: data connection closed");
1851
    }
1852
1846
1847
    ap_flush_conn(data);
1848
    ftp_data->state = ftp_finish_response;
1849
    return OK;
1850
}
1851
1852
static
1853
int finish_response(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1854
{
1855
    int rc;
1856
    char* msg;
1857
    apr_socket_close(ftp_data->cur.data.sock);
1858
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1859
            "proxy: FTP: data connection closed");
1860
1853
    /* Retrieve the final response for the RETR or LIST commands */
1861
    /* Retrieve the final response for the RETR or LIST commands */
1854
    rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
1862
    rc = proxy_ftp_command(NULL, r, ftp_ctrl, bb, &msg);
1863
    ftp_data->state = ftp_cleanup;
1864
    return OK;
1865
}
1866
1867
1868
static
1869
int cleanup(ftp_conn_data *ftp_data, request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
1870
{
1871
    int rc;
1855
    apr_brigade_cleanup(bb);
1872
    apr_brigade_cleanup(bb);
1856
1873
1857
    /*
1874
    /*
Lines 1860-1879 Link Here
1860
     * If there are no KeepAlives, or if the connection has been signalled to
1877
     * If there are no KeepAlives, or if the connection has been signalled to
1861
     * close, close the socket and clean up
1878
     * close, close the socket and clean up
1862
     */
1879
     */
1880
    rc = send_quit(ftp_data, r, ftp_ctrl, bb);
1881
    ftp_data->p_conn->close = 1;
1863
1882
1864
    /* finish */
1883
    ap_flush_conn(ftp_ctrl);
1865
    rc = proxy_ftp_command("QUIT" CRLF,
1866
                           r, origin, bb, &ftpmessage);
1867
    /* responses: 221, 500 */
1868
    /* 221 Service closing control connection. */
1869
    /* 500 Syntax error, command unrecognized. */
1870
    ap_flush_conn(origin);
1871
    proxy_ftp_cleanup(r, backend);
1872
1873
    apr_brigade_destroy(bb);
1884
    apr_brigade_destroy(bb);
1885
    ap_set_module_config(r->connection->conn_config, &proxy_ftp_module, NULL);
1874
    return OK;
1886
    return OK;
1875
}
1887
}
1876
1888
1889
static
1890
ftp_conn_data* init_ftp_data(proxy_conn_rec* p_conn)
1891
{
1892
    ftp_conn_data *ftp_data = apr_pcalloc(p_conn->pool, sizeof(ftp_conn_data));
1893
    ftp_data->xfer_type = 'A';
1894
    ftp_data->path = "";
1895
    ftp_data->user = "anonymous";
1896
    ftp_data->password = "apache-proxy@";
1897
    ftp_data->p_conn = p_conn;
1898
    return ftp_data;
1899
}
1900
1901
static
1902
apr_status_t ap_proxy_ftp_request(apr_pool_t *p, request_rec *r,
1903
                                   proxy_conn_rec *p_conn, conn_rec *origin,
1904
                                   proxy_server_conf *conf,
1905
                                   apr_uri_t *uri,
1906
                                   char *url, char *server_portstr)
1907
{
1908
    int rc = 0;
1909
    apr_bucket_brigade *bb = apr_brigade_create(p, r->connection->bucket_alloc);
1910
1911
    /* see if we have any ftp state info (ie if this conn was initialized before)
1912
     * if not, alloc data that needs to be alive for the entire length of persistant
1913
     * connection.
1914
     */
1915
1916
    ftp_conn_data *ftp_data;
1917
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1918
            "proxy: FTP: create new connection %s", url);
1919
    ftp_data = init_ftp_data(p_conn);
1920
    ftp_data->cur.conf = conf;
1921
1922
    ftp_data->url = url;
1923
    ftp_data->uri = uri;
1924
1925
    /* parse the path first*/
1926
    rc = on_init(ftp_data, r, origin, bb);
1927
1928
    /* State machine. */
1929
    for(;;) {
1930
        switch (ftp_data->state) {
1931
            case ftp_connect:
1932
                /* The socket will already be connected, just fetch the welcome message*/
1933
                rc = on_connect(ftp_data, r, origin, bb);
1934
                break;
1935
            case ftp_send_user:
1936
                rc = send_user(ftp_data, r, origin, bb);
1937
                break;
1938
            case ftp_send_pass:
1939
                rc = send_pass(ftp_data, r, origin, bb);
1940
                break;
1941
            case ftp_choose_action:
1942
                rc = choose_action(ftp_data, r, origin, bb);
1943
                break;
1944
            case ftp_check_type:
1945
                rc = check_type(ftp_data, r, origin, bb);
1946
                break;
1947
            case ftp_check_trans_mode:
1948
                rc = check_trans_mode(ftp_data, r, origin, bb);
1949
                break;
1950
            case ftp_connect_data_port:
1951
                rc = connect_data_port(ftp_data, r, origin, bb);
1952
                break;
1953
            case ftp_check_transfer:
1954
                rc = check_transfer(ftp_data, r, origin, bb);
1955
                break;
1956
            case ftp_send_retr:
1957
                rc = send_retr(ftp_data, r, origin, bb);
1958
                break;
1959
            case ftp_send_stor:
1960
                rc = send_stor(ftp_data, r, origin, bb);
1961
                break;
1962
            case ftp_send_list:
1963
                rc = send_list(ftp_data, r, origin, bb);
1964
                break;
1965
            case ftp_start_response:
1966
                rc = start_response(ftp_data, r, origin, bb);
1967
                break;
1968
            case ftp_transfer_stream:
1969
                rc = transfer_stream(ftp_data, r, origin, bb);
1970
                break;
1971
            case ftp_finish_response:
1972
                rc = finish_response(ftp_data, r, origin, bb);
1973
                break;
1974
            case ftp_cleanup:
1975
                cleanup(ftp_data, r, origin, bb);
1976
                return OK;
1977
1978
            default:
1979
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1980
                        "proxy: FTP: Unknown switch [%d] last rc:[%d]", ftp_data->state, ftp_data->cur.rc);
1981
            case ftp_error:
1982
                proxyerror(r, p_conn, rc, ftp_data->msg);
1983
        }
1984
1985
        /* break if there was an error*/
1986
        if (rc != OK)
1987
            break;
1988
    }
1989
1990
    return rc;
1991
}
1992
1993
static
1994
apr_status_t ap_proxy_ftp_cleanup(request_rec *r,
1995
                                   proxy_conn_rec *backend)
1996
{
1997
    ap_proxy_release_connection(proxy_function, backend, r->server);
1998
    return OK;
1999
}
2000
2001
/*
2002
 * Handles direct access of ftp:// URLs
2003
 * Original (Non-PASV) version from
2004
 * Troy Morrison <spiffnet@zoom.com>
2005
 * PASV added by Chuck
2006
 * Filters by [Graham Leggett <minfrin@sharp.fm>]
2007
 */
2008
static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
2009
                             proxy_server_conf *conf,
2010
                             char *url, const char *proxyname,
2011
                             apr_port_t proxyport)
2012
{
2013
    int status;
2014
    char server_portstr[32];
2015
    char *scheme;
2016
    const char *u;
2017
    proxy_conn_rec *backend = NULL;
2018
2019
    /* Note: Memory pool allocation.
2020
     * A downstream keepalive connection is always connected to the existence
2021
     * (or not) of an upstream keepalive connection. If this is not done then
2022
     * load balancing against multiple backend servers breaks (one backend
2023
     * server ends up taking 100% of the load), and the risk is run of
2024
     * downstream keepalive connections being kept open unnecessarily. This
2025
     * keeps webservers busy and ties up resources.
2026
     *
2027
     * As a result, we allocate all sockets out of the upstream connection
2028
     * pool, and when we want to reuse a socket, we check first whether the
2029
     * connection ID of the current upstream connection is the same as that
2030
     * of the connection when the socket was opened.
2031
     */
2032
    apr_pool_t *p = r->connection->pool;
2033
    conn_rec *c = r->connection;
2034
    apr_uri_t *uri = apr_palloc(r->connection->pool, sizeof(*uri));
2035
2036
    /* find the scheme */
2037
    u = strchr(url, ':');
2038
    if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0')
2039
        return DECLINED;
2040
    scheme = apr_pstrndup(c->pool, url, u - url);
2041
    /* scheme is lowercase */
2042
    ap_str_tolower(scheme);
2043
    /* is it for us? */
2044
    if (strcmp(scheme, "ftp") || proxyname) {
2045
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
2046
                "proxy: FTP: declining URL %s", url);
2047
        return DECLINED; /* only interested in direct FTP */
2048
    }
2049
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
2050
            "proxy: FTP: serving URL %s", url);
2051
2052
2053
    /* create space for state information */
2054
    if ((status = ap_proxy_acquire_connection(proxy_function, &backend,
2055
                    worker, r->server)) != OK)
2056
        goto cleanup;
2057
2058
2059
    /* Step One: Determine Who To Connect To */
2060
    if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend,
2061
                    uri, &url, 0,
2062
                    0, server_portstr,
2063
                    sizeof(server_portstr))) != OK)
2064
        goto cleanup;
2065
2066
    /* Step Two: Make the Connection */
2067
    if (ap_proxy_connect_backend(proxy_function, backend, worker, r->server)) {
2068
        status = HTTP_SERVICE_UNAVAILABLE;
2069
        goto cleanup;
2070
    }
2071
2072
    /* Step Three: Create conn_rec */
2073
    if (!backend->connection) {
2074
        if ((status = ap_proxy_connection_create(proxy_function, backend,
2075
                        c, r->server)) != OK)
2076
            goto cleanup;
2077
    }
2078
2079
    /* Step Four: Send the Request */
2080
    if ((status = ap_proxy_ftp_request(p, r, backend, backend->connection,
2081
                    conf, uri, url, server_portstr)) != OK)
2082
        goto cleanup;
2083
2084
    /* Step Five: Receive the Response -- is included in previous step */
2085
2086
    /* Step Six: Clean Up */
2087
2088
cleanup:
2089
    if (backend) {
2090
        if (status != OK)
2091
            backend->close = 1;
2092
        ap_proxy_ftp_cleanup(r, backend);
2093
    }
2094
    return status;
2095
2096
}
2097
1877
static void ap_proxy_ftp_register_hook(apr_pool_t *p)
2098
static void ap_proxy_ftp_register_hook(apr_pool_t *p)
1878
{
2099
{
1879
    /* hooks */
2100
    /* hooks */
Lines 1893-1895 Link Here
1893
    NULL,                       /* command apr_table_t */
2114
    NULL,                       /* command apr_table_t */
1894
    ap_proxy_ftp_register_hook  /* register hooks */
2115
    ap_proxy_ftp_register_hook  /* register hooks */
1895
};
2116
};
2117

Return to bug 41676