/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
#define AUTODETECT_PWD /* Automatic timestamping (Last-Modified header) based on MDTM is used if: * 1) the FTP server supports the MDTM command and * 2) HAVE_TIMEGM (preferred) or HAVE_GMTOFF is available at compile time
*/ #define USE_MDTM
module AP_MODULE_DECLARE_DATA proxy_ftp_module;
typedefstruct { int ftp_list_on_wildcard; int ftp_list_on_wildcard_set; int ftp_escape_wildcards; int ftp_escape_wildcards_set; constchar *ftp_directory_charset;
} proxy_ftp_dir_conf;
/* * Decodes a '%' escaped string, and returns the number of characters
*/ staticint decodeenc(char *x)
{ int i, j, ch;
if (x[0] == '\0') return 0; /* special case for no characters */ for (i = 0, j = 0; x[i] != '\0'; i++, j++) { /* decode it if not already done */
ch = x[i]; if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
ch = ap_proxy_hex2c(&x[i + 1]);
i += 2;
}
x[j] = ch;
}
x[j] = '\0'; return j;
}
/* * Escape the globbing characters in a path used as argument to * the FTP commands (SIZE, CWD, RETR, MDTM, ...). * ftpd assumes '\\' as a quoting character to escape special characters. * Just returns the original string if ProxyFtpEscapeWildcards has been * configured "off". * Returns: escaped string
*/ #define FTP_GLOBBING_CHARS "*?[{~" staticconstchar *ftp_escape_globbingchars(apr_pool_t *p, constchar *path, proxy_ftp_dir_conf *dconf)
{ char *ret; char *d;
if (!dconf->ftp_escape_wildcards) { return path;
}
ret = apr_palloc(p, 2*strlen(path)+sizeof("")); for (d = ret; *path; ++path) { if (strchr(FTP_GLOBBING_CHARS, *path) != NULL)
*d++ = '\\';
*d++ = *path;
}
*d = '\0'; return ret;
}
/* * Check for globbing characters in a path used as argument to * the FTP commands (SIZE, CWD, RETR, MDTM, ...). * ftpd assumes '\\' as a quoting character to escape special characters. * Returns: 0 (no globbing chars, or all globbing chars escaped), 1 (globbing chars)
*/ staticint ftp_check_globbingchars(constchar *path)
{ for ( ; *path; ++path) { if (*path == '\\')
++path; if (*path != '\0' && strchr(FTP_GLOBBING_CHARS, *path) != NULL) returnTRUE;
} returnFALSE;
}
/* * checks an encoded ftp string for bad characters, namely, CR, LF or * non-ascii character
*/ staticint ftp_check_string(constchar *x)
{ int i, ch = 0; #if APR_CHARSET_EBCDIC char buf[1]; #endif
/* * converts a series of buckets into a string * XXX: BillS says this function performs essentially the same function as * ap_rgetline() in protocol.c. Deprecate this function and use ap_rgetline() * instead? I think ftp_string_read() will not work properly on non ASCII * (EBCDIC) machines either.
*/ static apr_status_t ftp_string_read(conn_rec *c, apr_bucket_brigade *bb, char *buff, apr_size_t bufflen, int *eos, apr_size_t *outlen)
{
apr_bucket *e;
apr_status_t rv; char *pos = buff; char *response; int found = 0;
apr_size_t len;
/* start with an empty string */
buff[0] = 0;
*eos = 0;
*outlen = 0;
/* loop through each brigade */ while (!found) { /* get brigade from network one line at a time */ if (APR_SUCCESS != (rv = ap_get_brigade(c->input_filters, bb,
AP_MODE_GETLINE,
APR_BLOCK_READ,
0))) { return rv;
} /* loop through each bucket */ while (!found) { if (*eos || APR_BRIGADE_EMPTY(bb)) { /* The connection aborted or timed out */ return APR_ECONNABORTED;
}
e = APR_BRIGADE_FIRST(bb); if (APR_BUCKET_IS_EOS(e)) {
*eos = 1;
} else { if (APR_SUCCESS != (rv = apr_bucket_read(e,
(constchar **)&response,
&len,
APR_BLOCK_READ))) { return rv;
} /* * is string LF terminated? * XXX: This check can be made more efficient by simply checking * if the last character in the 'response' buffer is an ASCII_LF. * See ap_rgetline() for an example.
*/ if (memchr(response, APR_ASCII_LF, len)) {
found = 1;
} /* concat strings until buff is full - then throw the data away */ if (len > ((bufflen-1)-(pos-buff))) {
len = (bufflen-1)-(pos-buff);
} if (len > 0) {
memcpy(pos, response, len);
pos += len;
*outlen += len;
}
}
apr_bucket_delete(e);
}
*pos = '\0';
}
port = def_port;
err = ap_proxy_canon_netloc(p, &url, &user, &password, &host, &port); if (err) return HTTP_BAD_REQUEST; if (user != NULL && !ftp_check_string(user)) return HTTP_BAD_REQUEST; if (password != NULL && !ftp_check_string(password)) return HTTP_BAD_REQUEST;
/* now parse path/parameters args, according to rfc1738 */ /* * N.B. if this isn't a true proxy request, then the URL path (but not * query args) has already been decoded. This gives rise to the problem * of a ; being decoded into the path.
*/
strp = strchr(url, ';'); if (strp != NULL) {
*(strp++) = '\0';
parms = ap_proxy_canonenc(p, strp, strlen(strp), enc_parm, 0,
r->proxyreq); if (parms == NULL) return HTTP_BAD_REQUEST;
} else
parms = "";
path = ap_proxy_canonenc_ex(p, url, strlen(url), enc_path, flags,
r->proxyreq); if (path == NULL) return HTTP_BAD_REQUEST; if (!ftp_check_string(path)) return HTTP_BAD_REQUEST;
/* this is a filter that turns a raw ASCII directory listing into pretty HTML */
/* ideally, mod_proxy should simply send the raw directory list up the filter * stack to mod_autoindex, which in theory should turn the raw ascii into * pretty html along with all the bells and whistles it provides... * * all in good time...! :)
*/
/* combine the stored and the new */
APR_BRIGADE_CONCAT(ctx->in, in);
if (HEADER == ctx->state) {
/* basedir is either "", or "/%2f" for the "squid %2f hack" */ constchar *basedir = ""; /* By default, path is relative to the $HOME dir */ char *wildcard = NULL; constchar *escpath;
/* * In the reverse proxy case we need to construct our site string * via ap_construct_url. For non anonymous sites apr_uri_unparse would * only supply us with 'username@' which leads to the construction of * an invalid base href later on. Losing the username part of the URL * is no problem in the reverse proxy case as the browser sents the * credentials anyway once entered.
*/ if (r->proxyreq == PROXYREQ_REVERSE) {
site = ap_construct_url(p, "", r);
} else { /* Save "scheme://site" prefix without password */
site = apr_uri_unparse(p, &f->r->parsed_uri,
APR_URI_UNP_OMITPASSWORD |
APR_URI_UNP_OMITPATHINFO);
}
/* ... and path without query args */
path = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITSITEPART | APR_URI_UNP_OMITQUERY);
/* If path began with /%2f, change the basedir */ if (ap_cstr_casecmpn(path, "/%2f", 4) == 0) {
basedir = "/%2f";
}
/* Strip off a type qualifier. It is ignored for dir listings */ if ((type = strstr(path, ";type=")) != NULL)
*type++ = '\0';
(void)decodeenc(path);
while (path[1] == '/') /* collapse multiple leading slashes to one */
++path;
reldir = strrchr(path, '/'); if (reldir != NULL && ftp_check_globbingchars(reldir)) {
wildcard = &reldir[1];
reldir[0] = '\0'; /* strip off the wildcard suffix */
}
/* Copy path, strip (all except the last) trailing slashes */ /* (the trailing slash is needed for the dir component loop below) */
path = dir = apr_pstrcat(p, path, "/", NULL); for (n = strlen(path); n > 1 && path[n - 1] == '/' && path[n - 2] == '/'; --n)
path[n - 1] = '\0';
/* Add a link to the root directory (if %2f hack was used) */
str = (basedir[0] != '\0') ? "/%2f/\">%2f/" : "";
/* If the caller has determined the current directory, and it differs */ /* from what the client requested, then show the real name */ if (pwd == NULL || strncmp(pwd, path, strlen(pwd)) == 0) {
str = apr_psprintf(p, "\n\n \n\n
/* make sure page intro gets sent out */
APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc)); if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) { return rv;
}
apr_brigade_cleanup(out);
ctx->state = BODY;
}
/* loop through each line of directory */ while (BODY == ctx->state) { char *filename; int found = 0; int eos = 0;
ap_regmatch_t re_result[LS_REG_MATCH];
/* get a complete line */ /* if the buffer overruns - throw data away */ while (!found && !APR_BRIGADE_EMPTY(ctx->in)) { char *pos, *response;
apr_size_t len, max;
apr_bucket *e;
e = APR_BRIGADE_FIRST(ctx->in); if (APR_BUCKET_IS_EOS(e)) {
eos = 1; break;
} if (APR_SUCCESS != (rv = apr_bucket_read(e, (constchar **)&response, &len, APR_BLOCK_READ))) { return rv;
}
pos = memchr(response, APR_ASCII_LF, len); if (pos != NULL) { if ((response + len) != (pos + 1)) {
len = pos - response + 1;
apr_bucket_split(e, pos - response + 1);
}
found = 1;
}
max = sizeof(ctx->buffer) - strlen(ctx->buffer) - 1; if (len > max) {
len = max;
}
/* len+1 to leave space for the trailing nil char */
apr_cpystrn(ctx->buffer+strlen(ctx->buffer), response, len+1);
apr_bucket_delete(e);
}
/* EOS? jump to footer */ if (eos) {
ctx->state = FOOTER; break;
}
/* not complete? leave and try get some more */ if (!found) { return APR_SUCCESS;
}
{
apr_size_t n = strlen(ctx->buffer); if (ctx->buffer[n-1] == CRLF[1]) /* strip trailing '\n' */
ctx->buffer[--n] = '\0'; if (ctx->buffer[n-1] == CRLF[0]) /* strip trailing '\r' if present */
ctx->buffer[--n] = '\0';
}
/* a directory/file? */ elseif (ctx->buffer[0] == 'd' || ctx->buffer[0] == '-' || ctx->buffer[0] == 'l' || apr_isdigit(ctx->buffer[0])) { int searchidx = 0; char *searchptr = NULL; int firstfile = 1; if (apr_isdigit(ctx->buffer[0])) { /* handle DOS dir */
searchptr = strchr(ctx->buffer, '<'); if (searchptr != NULL)
*searchptr = '[';
searchptr = strchr(ctx->buffer, '>'); if (searchptr != NULL)
*searchptr = ']';
}
filename = strrchr(ctx->buffer, ' '); if (filename == NULL) { /* Line is broken. Ignore it. */
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01034) "proxy_ftp: could not parse line %s",
ctx->buffer); /* erase buffer for next time around */
ctx->buffer[0] = 0; continue; /* while state is BODY */
}
*(filename++) = '\0';
/* Append a slash to the HREF link for directories */ if (!strcmp(filename, ".") || !strcmp(filename, "..") || ctx->buffer[0] == 'd') {
str = apr_psprintf(p, "%s %s/\">%s\n",
ap_escape_html(p, ctx->buffer),
ap_escape_uri(p, filename),
ap_escape_html(p, filename));
} else {
str = apr_psprintf(p, "%s %s\">%s\n",
ap_escape_html(p, ctx->buffer),
ap_escape_uri(p, filename),
ap_escape_html(p, filename));
}
} /* Try a fallback for listings in the format of "ls -s1" */ elseif (0 == ap_regexec(ls_regex, ctx->buffer, LS_REG_MATCH, re_result, 0)) { /* * We don't need to check for rm_eo == rm_so == -1 here since ls_regex * is such that $2 cannot be unset if we have a match.
*/
filename = apr_pstrndup(p, &ctx->buffer[re_result[2].rm_so], re_result[2].rm_eo - re_result[2].rm_so);
/* Parse EPSV reply and return port, or zero on error. */ static apr_port_t parse_epsv_reply(constchar *reply)
{ constchar *p; char *ep; long port;
/* Reply syntax per RFC 2428: "229 blah blah (|||port|)" where '|' * can be any character in ASCII from 33-126, obscurely. Verify
* the syntax. */
p = ap_strchr_c(reply, '('); if (p == NULL || !p[1] || p[1] != p[2] || p[1] != p[3]
|| p[4] == p[1]) { return 0;
}
errno = 0;
port = strtol(p + 4, &ep, 10); if (errno || port < 1 || port > 65535 || ep[0] != p[1] || ep[1] != ')') { return 0;
}
return (apr_port_t)port;
}
/* * Generic "send FTP command to server" routine, using the control socket. * Returns the FTP returncode (3 digit code) * Allows for tracing the FTP protocol (in LogLevel debug)
*/ staticint
proxy_ftp_command(constchar *cmd, request_rec *r, conn_rec *ftp_ctrl,
apr_bucket_brigade *bb, char **pmessage)
{ char *crlf; int rc; char message[HUGE_STRING_LEN];
/* If cmd == NULL, we retrieve the next ftp response line */ if (cmd != NULL) {
conn_rec *c = r->connection;
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(cmd, strlen(cmd), r->pool, c->bucket_alloc));
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(c->bucket_alloc));
ap_pass_brigade(ftp_ctrl->output_filters, bb);
if (APLOGrtrace2(r)) { /* strip off the CRLF for logging */
apr_cpystrn(message, cmd, sizeof(message)); if ((crlf = strchr(message, '\r')) != NULL ||
(crlf = strchr(message, '\n')) != NULL)
*crlf = '\0'; if (strncmp(message,"PASS ", 5) == 0)
strcpy(&message[5], "****");
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, ">%s", message);
}
}
if (pmessage != NULL)
*pmessage = apr_pstrdup(r->pool, message);
return rc;
}
/* Set ftp server to TYPE {A,I,E} before transfer of a directory or file */ staticint ftp_set_TYPE(char xfer_type, request_rec *r, conn_rec *ftp_ctrl,
apr_bucket_brigade *bb, char **pmessage)
{ char old_type[2] = { 'A', '\0' }; /* After logon, mode is ASCII */ int ret = HTTP_OK; int rc;
/* set desired type */
old_type[0] = xfer_type;
rc = proxy_ftp_command(apr_pstrcat(r->pool, "TYPE ", old_type, CRLF, NULL),
r, ftp_ctrl, bb, pmessage); /* responses: 200, 421, 500, 501, 504, 530 */ /* 200 Command okay. */ /* 421 Service not available, closing control connection. */ /* 500 Syntax error, command unrecognized. */ /* 501 Syntax error in parameters or arguments. */ /* 504 Command not implemented for that parameter. */ /* 530 Not logged in. */ if (rc == -1 || rc == 421) {
ret = ap_proxyerror(r, HTTP_BAD_GATEWAY, "Error reading from remote server");
} elseif (rc != 200 && rc != 504) {
ret = ap_proxyerror(r, HTTP_BAD_GATEWAY, "Unable to set transfer type");
} /* Allow not implemented */ elseif (rc == 504) { /* ignore it silently */
}
return ret;
}
/* Return the current directory which we have selected on the FTP server, or NULL */ staticchar *ftp_get_PWD(request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
{ char *cwd = NULL; char *ftpmessage = NULL;
/* responses: 257, 500, 501, 502, 421, 550 */ /* 257 "<directory-name>" <commentary> */ /* 421 Service not available, closing control connection. */ /* 500 Syntax error, command unrecognized. */ /* 501 Syntax error in parameters or arguments. */ /* 502 Command not implemented. */ /* 550 Requested action not taken. */ switch (proxy_ftp_command("PWD" CRLF, r, ftp_ctrl, bb, &ftpmessage)) { case -1: case 421: case 550:
ap_proxyerror(r, HTTP_BAD_GATEWAY, "Failed to read PWD on ftp server"); break;
/* Common routine for failed authorization (i.e., missing or wrong password) * to an ftp service. This causes most browsers to retry the request * with username and password (which was presumably queried from the user) * supplied in the Authorization: header. * Note that we "invent" a realm name which consists of the * ftp://user@host part of the request (sans password -if supplied but invalid-)
*/ staticint ftp_unauthorized(request_rec *r, int log_it)
{
r->proxyreq = PROXYREQ_NONE; /* * Log failed requests if they supplied a password (log username/password * guessing attempts)
*/ if (log_it)
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01035) "missing or failed auth to %s",
apr_uri_unparse(r->pool,
&r->parsed_uri, APR_URI_UNP_OMITPATHINFO));
if (type_suffix != NULL && strncmp(type_suffix, "type=", 5) == 0
&& apr_isalpha(type_suffix[5])) { /* "type=d" forces a dir listing. * The other types (i|a|e) are directly used for the ftp TYPE command
*/ if ( ! (dirlisting = (apr_tolower(type_suffix[5]) == 'd')))
xfer_type = apr_toupper(type_suffix[5]);
/* Check valid types, rather than ignoring invalid types silently: */ if (strchr("AEI", xfer_type) == NULL) return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool, "ftp proxy supports only types 'a', 'i', or 'e': \"",
type_suffix, "\" is invalid.", NULL));
} else { /* make binary transfers the default */
xfer_type = 'I';
}
/* * The "Authorization:" header must be checked first. We allow the user * to "override" the URL-coded user [ & password ] in the Browsers' * User&Password Dialog. NOTE that this is only marginally more secure * than having the password travel in plain as part of the URL, because * Basic Auth simply uuencodes the plain text password. But chances are * still smaller that the URL is logged regularly.
*/ if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
&& ap_cstr_casecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
&& (password = ap_pbase64decode(r->pool, password))[0] != ':') { /* Check the decoded string for special characters. */ if (!ftp_check_string(password)) { return ap_proxyerror(r, HTTP_BAD_REQUEST, "user credentials contained invalid character");
} /* * Note that this allocation has to be made from r->connection->pool * because it has the lifetime of the connection. The other * allocations are temporary and can be tossed away any time.
*/
user = ap_getword_nulls(r->connection->pool, &password, ':');
r->ap_auth_type = "Basic";
r->user = r->parsed_uri.user = user;
} elseif ((user = r->parsed_uri.user) != NULL) {
user = apr_pstrdup(p, user);
decodeenc(user); if ((password = r->parsed_uri.password) != NULL) { char *tmp = apr_pstrdup(p, password);
decodeenc(tmp);
password = tmp;
}
} else {
user = "anonymous";
password = "apache-proxy@";
}
/* create space for state information */
backend = ap_get_module_config(c->conn_config, &proxy_ftp_module); if (!backend) {
status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server); if (status != OK) { if (backend) {
backend->close = 1;
ap_proxy_release_connection("FTP", backend, r->server);
} return status;
}
ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
}
/* * get all the possible IP addresses for the destname and loop through * them until we get a successful connection
*/
err = ap_proxy_determine_address("FTP", backend, connectname, connectport,
0, r, r->server); if (APR_SUCCESS != err) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error resolving backend address");
}
/* check if ProxyBlock directive on this host */ if (OK != ap_proxy_checkproxyblock2(r, conf, connectname, backend->addr)) { return ftp_proxyerror(r, backend, HTTP_FORBIDDEN, "Connect to remote machine blocked");
}
/* * II: Make the Connection ----------------------- * * We have determined who to connect to. Now make the connection.
*/
/* * III: Send Control Request ------------------------- * * Log into the ftp server, send the username & password, change to the * correct directory...
*/
bb = apr_brigade_create(p, c->bucket_alloc);
/* possible results: */ /* 120 Service ready in nnn minutes. */ /* 220 Service ready for new user. */ /* 421 Service not available, closing control connection. */
rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage); if (rc == -1 || rc == 421) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
} if (rc == 120) { /* * RFC2616 states: 14.37 Retry-After * * The Retry-After response-header field can be used with a 503 (Service * Unavailable) response to indicate how long the service is expected * to be unavailable to the requesting client. [...] The value of * this field can be either an HTTP-date or an integer number of * seconds (in decimal) after the time of the response. Retry-After * = "Retry-After" ":" ( HTTP-date | delta-seconds )
*/ char *secs_str = ftpmessage;
time_t secs;
/* Look for a number, preceded by whitespace */ while (*secs_str) if ((secs_str==ftpmessage || apr_isspace(secs_str[-1])) &&
apr_isdigit(secs_str[0])) break; if (*secs_str != '\0') {
secs = atol(secs_str);
apr_table_addn(r->headers_out, "Retry-After",
apr_psprintf(p, "%lu", (unsignedlong)(60 * secs)));
} return ftp_proxyerror(r, backend, HTTP_SERVICE_UNAVAILABLE, ftpmessage);
} if (rc != 220) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
}
rc = proxy_ftp_command(apr_pstrcat(p, "USER ", user, CRLF, NULL),
r, origin, bb, &ftpmessage); /* possible results; 230, 331, 332, 421, 500, 501, 530 */ /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */ /* 230 User logged in, proceed. */ /* 331 User name okay, need password. */ /* 332 Need account for login. */ /* 421 Service not available, closing control connection. */ /* 500 Syntax error, command unrecognized. */ /* (This may include errors such as command line too long.) */ /* 501 Syntax error in parameters or arguments. */ /* 530 Not logged in. */ if (rc == -1 || rc == 421) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
} if (rc == 530) {
proxy_ftp_cleanup(r, backend); return ftp_unauthorized(r, 1); /* log it: user name guessing
* attempt? */
} if (rc != 230 && rc != 331) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
}
if (rc == 331) { /* send password */ if (password == NULL) {
proxy_ftp_cleanup(r, backend); return ftp_unauthorized(r, 0);
}
rc = proxy_ftp_command(apr_pstrcat(p, "PASS ", password, CRLF, NULL),
r, origin, bb, &ftpmessage); /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */ /* 230 User logged in, proceed. */ /* 332 Need account for login. */ /* 421 Service not available, closing control connection. */ /* 500 Syntax error, command unrecognized. */ /* 501 Syntax error in parameters or arguments. */ /* 503 Bad sequence of commands. */ /* 530 Not logged in. */ if (rc == -1 || rc == 421) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
} if (rc == 332) { return ftp_proxyerror(r, backend, HTTP_UNAUTHORIZED,
apr_pstrcat(p, "Need account for login: ", ftpmessage, NULL));
} /* @@@ questionable -- we might as well return a 403 Forbidden here */ if (rc == 530) {
proxy_ftp_cleanup(r, backend); return ftp_unauthorized(r, 1); /* log it: passwd guessing
* attempt? */
} if (rc != 230 && rc != 202) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
}
}
apr_table_set(r->notes, "Directory-README", ftpmessage);
/* Special handling for leading "%2f": this enforces a "cwd /" * out of the $HOME directory which was the starting point after login
*/ if (ap_cstr_casecmpn(path, "%2f", 3) == 0) {
path += 3; while (*path == '/') /* skip leading '/' (after root %2f) */
++path;
/* * set the directory (walk directory component by component): this is * what we must do if we don't know the OS type of the remote machine
*/ for (;;) {
strp = strchr(path, '/'); if (strp == NULL) break;
*strp = '\0';
decodeenc(path); /* Note! This decodes a %2f -> "/" */
if (strchr(path, '/')) { /* are there now any '/' characters? */ return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST, "Use of /%2f is only allowed at the base directory");
}
/* NOTE: FTP servers do globbing on the path. * So we need to escape the URI metacharacters. * We use a special glob-escaping routine to escape globbing chars. * We could also have extended gen_test_char.c with a special T_ESCAPE_FTP_PATH
*/
rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
r, origin, bb, &ftpmessage);
*strp = '/'; /* responses: 250, 421, 500, 501, 502, 530, 550 */ /* 250 Requested file action okay, completed. */ /* 421 Service not available, closing control connection. */ /* 500 Syntax error, command unrecognized. */ /* 501 Syntax error in parameters or arguments. */ /* 502 Command not implemented. */ /* 530 Not logged in. */ /* 550 Requested action not taken. */ if (rc == -1 || rc == 421) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
} if (rc == 550) { return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
} if (rc != 250) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
}
path = strp + 1;
}
/* * IV: Make Data Connection? ------------------------- * * Try EPSV, if that fails... try PASV, if that fails... try PORT.
*/ /* this temporarily switches off EPSV/PASV */ /*goto bypass;*/
/* set up data connection - EPSV */
{
apr_port_t data_port;
/* * The EPSV command replaces PASV where both IPV4 and IPV6 is * supported. Only the port is returned, the IP address is always the * same as that on the control connection. Example: Entering Extended * Passive Mode (|||6446|)
*/
rc = proxy_ftp_command("EPSV" CRLF,
r, origin, bb, &ftpmessage); /* possible results: 227, 421, 500, 501, 502, 530 */ /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */ /* 421 Service not available, closing control connection. */ /* 500 Syntax error, command unrecognized. */ /* 501 Syntax error in parameters or arguments. */ /* 502 Command not implemented. */ /* 530 Not logged in. */ if (rc == -1 || rc == 421) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
} if (rc != 229 && rc != 500 && rc != 501 && rc != 502) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
} elseif (rc == 229) { /* Parse the port out of the EPSV reply. */
data_port = parse_epsv_reply(ftpmessage);
if (data_port) {
apr_sockaddr_t *remote_addr, epsv_addr;
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "EPSV contacting remote host on port %d", data_port);
/* Retrieve the client's address. */
rv = apr_socket_addr_get(&remote_addr, APR_REMOTE, sock); if (rv == APR_SUCCESS) { /* Take a shallow copy of the server address to * modify; the _addr_get function gives back a * pointer to the socket's internal structure. * This is awkward given current APR network
* interfaces. */
epsv_addr = *remote_addr;
epsv_addr.port = data_port; #if APR_HAVE_IPV6 if (epsv_addr.family == APR_INET6) {
epsv_addr.sa.sin6.sin6_port = htons(data_port);
} else #endif
{
epsv_addr.sa.sin.sin_port = htons(data_port);
}
rv = apr_socket_create(&data_sock, epsv_addr.family, SOCK_STREAM, 0, r->pool);
}
if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01040) "could not establish socket for client data connection");
proxy_ftp_cleanup(r, backend); return HTTP_INTERNAL_SERVER_ERROR;
}
if (conf->recv_buffer_size > 0
&& (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
conf->recv_buffer_size))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01041) "apr_socket_opt_set(SO_RCVBUF): Failed to " "set ProxyReceiveBufferSize, using default");
}
/* set up data connection - PORT */ if (!connect) {
apr_sockaddr_t *local_addr; char *local_ip;
apr_port_t local_port; unsignedint h0, h1, h2, h3, p0, p1;
if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01051) "error binding to ftp data socket %pI", local_addr);
proxy_ftp_cleanup(r, backend); return HTTP_INTERNAL_SERVER_ERROR;
}
/* only need a short queue */ if ((rv = apr_socket_listen(local_sock, 2)) != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01052) "error listening to ftp data socket %pI", local_addr);
proxy_ftp_cleanup(r, backend); return HTTP_INTERNAL_SERVER_ERROR;
}
rc = proxy_ftp_command(apr_psprintf(p, "PORT %d,%d,%d,%d,%d,%d" CRLF, h3, h2, h1, h0, p1, p0),
r, origin, bb, &ftpmessage); /* possible results: 200, 421, 500, 501, 502, 530 */ /* 200 Command okay. */ /* 421 Service not available, closing control connection. */ /* 500 Syntax error, command unrecognized. */ /* 501 Syntax error in parameters or arguments. */ /* 502 Command not implemented. */ /* 530 Not logged in. */ if (rc == -1 || rc == 421) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
} if (rc != 200) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
}
/* signal that we must use the EPRT/PORT loop */
use_port = 1;
} else { /* IPV6 FIXME: * The EPRT command replaces PORT where both IPV4 and IPV6 is supported. The first * number (1,2) indicates the protocol type. Examples: * EPRT |1|132.235.1.2|6275| * EPRT |2|1080::8:800:200C:417A|5282|
*/ return ftp_proxyerror(r, backend, HTTP_NOT_IMPLEMENTED, "Connect to IPV6 ftp server using EPRT not supported. Enable EPSV.");
}
}
/* * V: Set The Headers ------------------- * * Get the size of the request, set up the environment for HTTP.
*/
/* set request; "path" holds last path component */
len = decodeenc(path);
if (strchr(path, '/')) { /* are there now any '/' characters? */ return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST, "Use of /%2f is only allowed at the base directory");
}
/* If len == 0 then it must be a directory (you can't RETR nothing) * Also, don't allow to RETR by wildcard. Instead, create a dirlisting, * unless ProxyFtpListOnWildcard is off.
*/ if (len == 0 || (ftp_check_globbingchars(path) && fdconf->ftp_list_on_wildcard)) {
dirlisting = 1;
} else { /* (from FreeBSD ftpd): * SIZE is not in RFC959, but Postel has blessed it and * it will be in the updated RFC. * * Return size of file in a format suitable for * using with RESTART (we just count bytes).
*/ /* from draft-ietf-ftpext-mlst-14.txt: * This value will * change depending on the current STRUcture, MODE and TYPE of the data * connection, or a data connection which would be created were one * created now. Thus, the result of the SIZE command is dependent on * the currently established STRU, MODE and TYPE parameters.
*/ /* Therefore: switch to binary if the user did not specify ";type=a" */
ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
rc = proxy_ftp_command(apr_pstrcat(p, "SIZE ",
ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
r, origin, bb, &ftpmessage); if (rc == -1 || rc == 421) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
} elseif (rc == 213) {/* Size command ok */ int j; for (j = 0; apr_isdigit(ftpmessage[j]); j++)
;
ftpmessage[j] = '\0'; if (ftpmessage[0] != '\0')
size = ftpmessage; /* already pstrdup'ed: no copy necessary */
} elseif (rc == 550) { /* Not a regular file */
ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "SIZE shows this is a directory");
dirlisting = 1;
rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
r, origin, bb, &ftpmessage); /* possible results: 250, 421, 500, 501, 502, 530, 550 */ /* 250 Requested file action okay, completed. */ /* 421 Service not available, closing control connection. */ /* 500 Syntax error, command unrecognized. */ /* 501 Syntax error in parameters or arguments. */ /* 502 Command not implemented. */ /* 530 Not logged in. */ /* 550 Requested action not taken. */ if (rc == -1 || rc == 421) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
} if (rc == 550) { return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
} if (rc != 250) { return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
}
path = "";
len = 0;
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.46Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.