/* Licensed to the Apache Software Foundation (ASF) under one or more *contributorlicenseagreements.SeetheNOTICEfiledistributedwith *thisworkforadditionalinformationregardingcopyrightownership. *TheASFlicensesthisfiletoYouundertheApacheLicense,Version2.0 *(the"License");youmaynotusethisfileexceptincompliancewith *theLicense.YoumayobtainacopyoftheLicenseat * *http://www.apache.org/licenses/LICENSE-2.0 * *Unlessrequiredbyapplicablelaworagreedtoinwriting,software *distributedundertheLicenseisdistributedonan"ASIS"BASIS, *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied. *SeetheLicenseforthespecificlanguagegoverningpermissionsand *limitationsundertheLicense.
*/
#define AUTODETECT_PWD /* Automatic timestamping (Last-Modified header) based on MDTM is used if: *1)theFTPserversupportstheMDTMcommandand *2)HAVE_TIMEGM(preferred)orHAVE_GMTOFFisavailableatcompiletime
*/ #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;
/* 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;
} /* *isstringLFterminated? *XXX:Thischeckcanbemademoreefficientbysimplychecking *ifthelastcharacterinthe'response'bufferisanASCII_LF. *Seeap_rgetline()foranexample.
*/ 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';
}
/* 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 *stacktomod_autoindex,whichintheoryshouldturntherawasciiinto *prettyhtmlalongwithallthebellsandwhistlesitprovides... * *allingoodtime...!:)
*/
/* 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;
/* *Inthereverseproxycaseweneedtoconstructoursitestring *viaap_construct_url.Fornonanonymoussitesapr_uri_unparsewould *onlysupplyuswith'username@'whichleadstotheconstructionof *aninvalidbasehreflateron.LosingtheusernamepartoftheURL *isnoprobleminthereverseproxycaseasthebrowsersentsthe *credentialsanywayonceentered.
*/ 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') ? "<a href=\"/%2f/\">%2f</a>/" : "";
/* 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, "</h2>\n\n <hr />\n\n<pre>");
} else {
str = apr_psprintf(p, "</h2>\n\n(%s)\n\n <hr />\n\n<pre>",
ap_escape_html(p, pwd));
}
APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
p, c->bucket_alloc));
/* 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';
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: case421: case550:
ap_proxyerror(r, HTTP_BAD_GATEWAY, "Failed to read PWD on ftp server"); break;
/* 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) { /* *RFC2616states:14.37Retry-After * *TheRetry-Afterresponse-headerfieldcanbeusedwitha503(Service *Unavailable)responsetoindicatehowlongtheserviceisexpected *tobeunavailabletotherequestingclient.[...]Thevalueof *thisfieldcanbeeitheranHTTP-dateoranintegernumberof *seconds(indecimal)afterthetimeoftheresponse.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 /" *outofthe$HOMEdirectorywhichwasthestartingpointafterlogin
*/ if (ap_cstr_casecmpn(path, "%2f", 3) == 0) {
path += 3; while (*path == '/') /* skip leading '/' (after root %2f) */
++path;
/* *setthedirectory(walkdirectorycomponentbycomponent):thisis *whatwemustdoifwedon'tknowtheOStypeoftheremotemachine
*/ 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. *SoweneedtoescapetheURImetacharacters. *Weuseaspecialglob-escapingroutinetoescapeglobbingchars. *Wecouldalsohaveextendedgen_test_char.cwithaspecialT_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:MakeDataConnection?------------------------- * *TryEPSV,ifthatfails...tryPASV,ifthatfails...tryPORT.
*/ /* this temporarily switches off EPSV/PASV */ /*goto bypass;*/
/* set up data connection - EPSV */
{
apr_port_t data_port;
/* *TheEPSVcommandreplacesPASVwherebothIPV4andIPV6is *supported.Onlytheportisreturned,theIPaddressisalwaysthe *sameasthatonthecontrolconnection.Example:EnteringExtended *PassiveMode(|||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_getfunctiongivesbacka *pointertothesocket'sinternalstructure. *ThisisawkwardgivencurrentAPRnetwork
* 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: *TheEPRTcommandreplacesPORTwherebothIPV4andIPV6issupported.Thefirst *number(1,2)indicatestheprotocoltype.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.");
}
}
/* 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'tallowtoRETRbywildcard.Instead,createadirlisting, *unlessProxyFtpListOnWildcardisoff.
*/ if (len == 0 || (ftp_check_globbingchars(path) && fdconf->ftp_list_on_wildcard)) {
dirlisting = 1;
} else { /* (from FreeBSD ftpd): *SIZEisnotinRFC959,butPostelhasblesseditand *itwillbeintheupdatedRFC. * *Returnsizeoffileinaformatsuitablefor *usingwithRESTART(wejustcountbytes).
*/ /* from draft-ietf-ftpext-mlst-14.txt: *Thisvaluewill *changedependingonthecurrentSTRUcture,MODEandTYPEofthedata *connection,oradataconnectionwhichwouldbecreatedwereone *creatednow.Thus,theresultoftheSIZEcommandisdependenton *thecurrentlyestablishedSTRU,MODEandTYPEparameters.
*/ /* 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;
}
}
/* set content-type */ if (dirlisting) {
ap_set_content_type_ex(r, apr_pstrcat(p, "text/html;charset=",
fdconf->ftp_directory_charset ?
fdconf->ftp_directory_charset : "ISO-8859-1", NULL), 1);
} else { if (xfer_type != 'A' && size != NULL) { /* We "trust" the ftp server to really serve (size) bytes... */
apr_table_setn(r->headers_out, "Content-Length", size);
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "Content-Length set to %s", size);
}
} if (r->content_type) {
apr_table_setn(r->headers_out, "Content-Type", r->content_type);
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "Content-Type set to %s", r->content_type);
}
#ifdefined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF)) if (mtime != 0L) { char datestr[APR_RFC822_DATE_LEN];
apr_rfc822_date(datestr, mtime);
apr_table_set(r->headers_out, "Last-Modified", datestr);
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "Last-Modified set to %s", datestr);
} #endif/* USE_MDTM */
/* If an encoding has been set by mistake, delete it. *@@@FIXME(e.g.,forftp://user@host/file*.tar.gz, *@@@theencodingiscurrentlysettox-gzip)
*/ if (dirlisting && r->content_encoding != NULL)
r->content_encoding = NULL;
/* set content-encoding (not for dir listings, they are uncompressed)*/ if (r->content_encoding != NULL && r->content_encoding[0] != '\0') {
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "Content-Encoding set to %s", r->content_encoding);
apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
}
/* wait for connection */ if (use_port) { for (;;) {
rv = apr_socket_accept(&data_sock, local_sock, r->pool); if (APR_STATUS_IS_EINTR(rv)) { continue;
} elseif (rv == APR_SUCCESS) { break;
} else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01053) "failed to accept data connection");
proxy_ftp_cleanup(r, backend); return HTTP_BAD_GATEWAY;
}
}
}
/* the transfer socket is now open, create a new connection */
data = ap_run_create_connection(p, r->server, data_sock, r->connection->id,
r->connection->sbh, c->bucket_alloc); if (!data) { /* *thepeerresettheconnectionalready;ap_run_create_connection()closed *thesocket
*/
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01054) "an error occurred creating the transfer connection");
proxy_ftp_cleanup(r, backend); return HTTP_INTERNAL_SERVER_ERROR;
}
/* *WedonotdoSSLoverthedataconnection,evenifthevirtualhostwe *areinmighthaveSSLenabled
*/
ap_proxy_ssl_engine(data, r->per_dir_config, 0); /* set up the connection filters */
rc = ap_run_pre_connection(data, data_sock); if (rc != OK && rc != DONE) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01055) "pre_connection setup failed (%d)", rc);
data->aborted = 1;
proxy_ftp_cleanup(r, backend); return rc;
}
/* send body */ if (!r->header_only) {
apr_bucket *e; int finish = FALSE;
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "start body send");
/* read the body, pass it to the output filters */ while (ap_get_brigade(data->input_filters,
bb,
AP_MODE_READBYTES,
APR_BLOCK_READ,
conf->io_buffer_size) == APR_SUCCESS) { #if DEBUGGING
{
apr_off_t readbytes;
apr_brigade_length(bb, 0, &readbytes);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01056) "proxy: readbytes: %#x", readbytes);
} #endif /* sanity check */ if (APR_BRIGADE_EMPTY(bb)) { break;
}
/* found the last brigade? */ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) { /* if this is the last brigade, cleanup the *backendconnectionfirsttopreventthe *backendserverfromhangingaroundwaiting *foraslowclienttoeatthesebytes
*/
ap_flush_conn(data); if (data_sock) {
apr_socket_close(data_sock);
}
data_sock = NULL;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01057) "data connection closed"); /* signal that we must leave */
finish = TRUE;
}
/* if no EOS yet, then we must flush */ if (FALSE == finish) {
e = apr_bucket_flush_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
}
/* try send what we read */ if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
|| c->aborted) { /* Ack! Phbtt! Die! User aborted! */
finish = TRUE;
}
/* make sure we always clean up after ourselves */
apr_brigade_cleanup(bb);
/* if we are done, leave */ if (TRUE == finish) { break;
}
}
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "end body send");
staticvoid ap_proxy_ftp_register_hook(apr_pool_t *p)
{ /* hooks */
proxy_hook_scheme_handler(proxy_ftp_handler, NULL, NULL, APR_HOOK_MIDDLE);
proxy_hook_canon_handler(proxy_ftp_canon, NULL, NULL, APR_HOOK_MIDDLE); /* filters */
ap_register_output_filter("PROXY_SEND_DIR", proxy_send_dir_filter,
NULL, AP_FTYPE_RESOURCE); /* Compile the output format of "ls -s1" as a fallback for non-unix ftp listings */
ls_regex = ap_pregcomp(p, LS_REG_PATTERN, AP_REG_EXTENDED);
ap_assert(ls_regex != NULL);
}
staticconst command_rec proxy_ftp_cmds[] =
{
AP_INIT_FLAG("ProxyFtpListOnWildcard", set_ftp_list_on_wildcard, NULL,
RSRC_CONF|ACCESS_CONF, "Whether wildcard characters in a path cause mod_proxy_ftp to list the files instead of trying to get them. Defaults to on."),
AP_INIT_FLAG("ProxyFtpEscapeWildcards", set_ftp_escape_wildcards, NULL,
RSRC_CONF|ACCESS_CONF, "Whether the proxy should escape wildcards in paths before sending them to the FTP server. Defaults to on, but most FTP servers will need it turned off if you need to manage paths that contain wildcard characters."),
AP_INIT_TAKE1("ProxyFtpDirCharset", set_ftp_directory_charset, NULL,
RSRC_CONF|ACCESS_CONF, "Define the character set for proxied FTP listings"),
{NULL}
};
¤ 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.70Bemerkung:
(vorverarbeitet am 2026-09-28)
¤
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 und die Messung sind noch experimentell.