1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* FTP routines for Apache proxy */
19 #define APR_WANT_BYTEFUNC
20 #include "mod_proxy.h"
24 #include "apr_version.h"
26 #if (APR_MAJOR_VERSION < 1)
27 #undef apr_socket_create
28 #define apr_socket_create apr_socket_create_ex
31 #define AUTODETECT_PWD
32 /* Automatic timestamping (Last-Modified header) based on MDTM is used if:
33 * 1) the FTP server supports the MDTM command and
34 * 2) HAVE_TIMEGM (preferred) or HAVE_GMTOFF is available at compile time
39 module AP_MODULE_DECLARE_DATA proxy_ftp_module;
42 int ftp_list_on_wildcard;
43 int ftp_list_on_wildcard_set;
44 int ftp_escape_wildcards;
45 int ftp_escape_wildcards_set;
46 const char *ftp_directory_charset;
49 static void *create_proxy_ftp_dir_config(apr_pool_t *p, char *dummy)
51 proxy_ftp_dir_conf *new =
52 (proxy_ftp_dir_conf *) apr_pcalloc(p, sizeof(proxy_ftp_dir_conf));
54 /* Put these in the dir config so they work inside <Location> */
55 new->ftp_list_on_wildcard = 1;
56 new->ftp_escape_wildcards = 1;
61 static void *merge_proxy_ftp_dir_config(apr_pool_t *p, void *basev, void *addv)
63 proxy_ftp_dir_conf *new = (proxy_ftp_dir_conf *) apr_pcalloc(p, sizeof(proxy_ftp_dir_conf));
64 proxy_ftp_dir_conf *add = (proxy_ftp_dir_conf *) addv;
65 proxy_ftp_dir_conf *base = (proxy_ftp_dir_conf *) basev;
67 /* Put these in the dir config so they work inside <Location> */
68 new->ftp_list_on_wildcard = add->ftp_list_on_wildcard_set ?
69 add->ftp_list_on_wildcard :
70 base->ftp_list_on_wildcard;
71 new->ftp_list_on_wildcard_set = add->ftp_list_on_wildcard_set ?
73 base->ftp_list_on_wildcard_set;
74 new->ftp_escape_wildcards = add->ftp_escape_wildcards_set ?
75 add->ftp_escape_wildcards :
76 base->ftp_escape_wildcards;
77 new->ftp_escape_wildcards_set = add->ftp_escape_wildcards_set ?
79 base->ftp_escape_wildcards_set;
80 new->ftp_directory_charset = add->ftp_directory_charset ?
81 add->ftp_directory_charset :
82 base->ftp_directory_charset;
86 static const char *set_ftp_list_on_wildcard(cmd_parms *cmd, void *dconf,
89 proxy_ftp_dir_conf *conf = dconf;
91 conf->ftp_list_on_wildcard = flag;
92 conf->ftp_list_on_wildcard_set = 1;
96 static const char *set_ftp_escape_wildcards(cmd_parms *cmd, void *dconf,
99 proxy_ftp_dir_conf *conf = dconf;
101 conf->ftp_escape_wildcards = flag;
102 conf->ftp_escape_wildcards_set = 1;
106 static const char *set_ftp_directory_charset(cmd_parms *cmd, void *dconf,
109 proxy_ftp_dir_conf *conf = dconf;
111 conf->ftp_directory_charset = arg;
116 * Decodes a '%' escaped string, and returns the number of characters
118 static int decodeenc(char *x)
123 return 0; /* special case for no characters */
124 for (i = 0, j = 0; x[i] != '\0'; i++, j++) {
125 /* decode it if not already done */
127 if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
128 ch = ap_proxy_hex2c(&x[i + 1]);
138 * Escape the globbing characters in a path used as argument to
139 * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
140 * ftpd assumes '\\' as a quoting character to escape special characters.
141 * Just returns the original string if ProxyFtpEscapeWildcards has been
143 * Returns: escaped string
145 #define FTP_GLOBBING_CHARS "*?[{~"
146 static const char *ftp_escape_globbingchars(apr_pool_t *p, const char *path, proxy_ftp_dir_conf *dconf)
151 if (!dconf->ftp_escape_wildcards) {
155 ret = apr_palloc(p, 2*strlen(path)+sizeof(""));
156 for (d = ret; *path; ++path) {
157 if (strchr(FTP_GLOBBING_CHARS, *path) != NULL)
166 * Check for globbing characters in a path used as argument to
167 * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
168 * ftpd assumes '\\' as a quoting character to escape special characters.
169 * Returns: 0 (no globbing chars, or all globbing chars escaped), 1 (globbing chars)
171 static int ftp_check_globbingchars(const char *path)
173 for ( ; *path; ++path) {
176 if (*path != '\0' && strchr(FTP_GLOBBING_CHARS, *path) != NULL)
183 * checks an encoded ftp string for bad characters, namely, CR, LF or
184 * non-ascii character
186 static int ftp_check_string(const char *x)
189 #if APR_CHARSET_EBCDIC
193 for (i = 0; x[i] != '\0'; i++) {
195 if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
196 ch = ap_proxy_hex2c(&x[i + 1]);
199 #if !APR_CHARSET_EBCDIC
200 if (ch == '\015' || ch == '\012' || (ch & 0x80))
201 #else /* APR_CHARSET_EBCDIC */
202 if (ch == '\r' || ch == '\n')
205 ap_xlate_proto_to_ascii(buf, 1);
207 #endif /* APR_CHARSET_EBCDIC */
214 * converts a series of buckets into a string
215 * XXX: BillS says this function performs essentially the same function as
216 * ap_rgetline() in protocol.c. Deprecate this function and use ap_rgetline()
217 * instead? I think ftp_string_read() will not work properly on non ASCII
218 * (EBCDIC) machines either.
220 static apr_status_t ftp_string_read(conn_rec *c, apr_bucket_brigade *bb,
221 char *buff, apr_size_t bufflen, int *eos)
230 /* start with an empty string */
234 /* loop through each brigade */
236 /* get brigade from network one line at a time */
237 if (APR_SUCCESS != (rv = ap_get_brigade(c->input_filters, bb,
243 /* loop through each bucket */
245 if (*eos || APR_BRIGADE_EMPTY(bb)) {
246 /* The connection aborted or timed out */
247 return APR_ECONNABORTED;
249 e = APR_BRIGADE_FIRST(bb);
250 if (APR_BUCKET_IS_EOS(e)) {
254 if (APR_SUCCESS != (rv = apr_bucket_read(e,
255 (const char **)&response,
261 * is string LF terminated?
262 * XXX: This check can be made more efficient by simply checking
263 * if the last character in the 'response' buffer is an ASCII_LF.
264 * See ap_rgetline() for an example.
266 if (memchr(response, APR_ASCII_LF, len)) {
269 /* concat strings until buff is full - then throw the data away */
270 if (len > ((bufflen-1)-(pos-buff))) {
271 len = (bufflen-1)-(pos-buff);
274 memcpy(pos, response, len);
278 apr_bucket_delete(e);
287 * Canonicalise ftp URLs.
289 static int proxy_ftp_canon(request_rec *r, char *url)
291 char *user, *password, *host, *path, *parms, *strp, sport[7];
292 apr_pool_t *p = r->pool;
294 apr_port_t port, def_port;
297 if (ap_cstr_casecmpn(url, "ftp:", 4) == 0) {
303 def_port = apr_uri_port_of_scheme("ftp");
305 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
308 err = ap_proxy_canon_netloc(p, &url, &user, &password, &host, &port);
310 return HTTP_BAD_REQUEST;
311 if (user != NULL && !ftp_check_string(user))
312 return HTTP_BAD_REQUEST;
313 if (password != NULL && !ftp_check_string(password))
314 return HTTP_BAD_REQUEST;
316 /* now parse path/parameters args, according to rfc1738 */
318 * N.B. if this isn't a true proxy request, then the URL path (but not
319 * query args) has already been decoded. This gives rise to the problem
320 * of a ; being decoded into the path.
322 strp = strchr(url, ';');
325 parms = ap_proxy_canonenc(p, strp, strlen(strp), enc_parm, 0,
328 return HTTP_BAD_REQUEST;
333 path = ap_proxy_canonenc(p, url, strlen(url), enc_path, 0, r->proxyreq);
335 return HTTP_BAD_REQUEST;
336 if (!ftp_check_string(path))
337 return HTTP_BAD_REQUEST;
339 if (r->proxyreq && r->args != NULL) {
341 strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_parm, 1, r->proxyreq);
343 return HTTP_BAD_REQUEST;
344 parms = apr_pstrcat(p, parms, "?", strp, NULL);
347 strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_fpath, 1, r->proxyreq);
349 return HTTP_BAD_REQUEST;
350 path = apr_pstrcat(p, path, "?", strp, NULL);
355 /* now, rebuild URL */
357 if (port != def_port)
358 apr_snprintf(sport, sizeof(sport), ":%d", port);
362 if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
363 host = apr_pstrcat(p, "[", host, "]", NULL);
365 r->filename = apr_pstrcat(p, "proxy:ftp://", (user != NULL) ? user : "",
366 (password != NULL) ? ":" : "",
367 (password != NULL) ? password : "",
368 (user != NULL) ? "@" : "", host, sport, "/", path,
369 (parms[0] != '\0') ? ";" : "", parms, NULL);
374 /* we chop lines longer than 80 characters */
375 #define MAX_LINE_LEN 80
378 * Reads response lines, returns both the ftp status code and
379 * remembers the response message in the supplied buffer
381 static int ftp_getrc_msg(conn_rec *ftp_ctrl, apr_bucket_brigade *bb, char *msgbuf, int msglen)
384 char response[MAX_LINE_LEN];
386 char *mb = msgbuf, *me = &msgbuf[msglen];
390 if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
394 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(03233)
397 if (!apr_isdigit(response[0]) || !apr_isdigit(response[1]) ||
398 !apr_isdigit(response[2]) || (response[3] != ' ' && response[3] != '-'))
401 status = 100 * response[0] + 10 * response[1] + response[2] - 111 * '0';
403 mb = apr_cpystrn(mb, response + 4, me - mb);
405 if (response[3] == '-') {
406 memcpy(buff, response, 3);
409 if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
412 mb = apr_cpystrn(mb, response + (' ' == response[0] ? 1 : 4), me - mb);
413 } while (memcmp(response, buff, 4) != 0);
419 /* this is a filter that turns a raw ASCII directory listing into pretty HTML */
421 /* ideally, mod_proxy should simply send the raw directory list up the filter
422 * stack to mod_autoindex, which in theory should turn the raw ascii into
423 * pretty html along with all the bells and whistles it provides...
425 * all in good time...! :)
429 apr_bucket_brigade *in;
430 char buffer[MAX_STRING_LEN];
436 /* fallback regex for ls -s1; ($0..$2) == 3 */
437 #define LS_REG_PATTERN "^ *([0-9]+) +([^ ]+)$"
438 #define LS_REG_MATCH 3
439 static ap_regex_t *ls_regex;
441 static apr_status_t proxy_send_dir_filter(ap_filter_t *f,
442 apr_bucket_brigade *in)
444 request_rec *r = f->r;
445 conn_rec *c = r->connection;
446 apr_pool_t *p = r->pool;
447 apr_bucket_brigade *out = apr_brigade_create(p, c->bucket_alloc);
451 char *dir, *path, *reldir, *site, *str, *type;
453 const char *pwd = apr_table_get(r->notes, "Directory-PWD");
454 const char *readme = apr_table_get(r->notes, "Directory-README");
456 proxy_dir_ctx_t *ctx = f->ctx;
459 f->ctx = ctx = apr_pcalloc(p, sizeof(*ctx));
460 ctx->in = apr_brigade_create(p, c->bucket_alloc);
465 /* combine the stored and the new */
466 APR_BRIGADE_CONCAT(ctx->in, in);
468 if (HEADER == ctx->state) {
470 /* basedir is either "", or "/%2f" for the "squid %2f hack" */
471 const char *basedir = ""; /* By default, path is relative to the $HOME dir */
472 char *wildcard = NULL;
476 * In the reverse proxy case we need to construct our site string
477 * via ap_construct_url. For non anonymous sites apr_uri_unparse would
478 * only supply us with 'username@' which leads to the construction of
479 * an invalid base href later on. Losing the username part of the URL
480 * is no problem in the reverse proxy case as the browser sents the
481 * credentials anyway once entered.
483 if (r->proxyreq == PROXYREQ_REVERSE) {
484 site = ap_construct_url(p, "", r);
487 /* Save "scheme://site" prefix without password */
488 site = apr_uri_unparse(p, &f->r->parsed_uri,
489 APR_URI_UNP_OMITPASSWORD |
490 APR_URI_UNP_OMITPATHINFO);
493 /* ... and path without query args */
494 path = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITSITEPART | APR_URI_UNP_OMITQUERY);
496 /* If path began with /%2f, change the basedir */
497 if (ap_cstr_casecmpn(path, "/%2f", 4) == 0) {
501 /* Strip off a type qualifier. It is ignored for dir listings */
502 if ((type = strstr(path, ";type=")) != NULL)
505 (void)decodeenc(path);
507 while (path[1] == '/') /* collapse multiple leading slashes to one */
510 reldir = strrchr(path, '/');
511 if (reldir != NULL && ftp_check_globbingchars(reldir)) {
512 wildcard = &reldir[1];
513 reldir[0] = '\0'; /* strip off the wildcard suffix */
516 /* Copy path, strip (all except the last) trailing slashes */
517 /* (the trailing slash is needed for the dir component loop below) */
518 path = dir = apr_pstrcat(p, path, "/", NULL);
519 for (n = strlen(path); n > 1 && path[n - 1] == '/' && path[n - 2] == '/'; --n)
522 /* Add a link to the root directory (if %2f hack was used) */
523 str = (basedir[0] != '\0') ? "<a href=\"/%2f/\">%2f</a>/" : "";
525 /* print "ftp://host/" */
526 escpath = ap_escape_html(p, path);
527 str = apr_psprintf(p, DOCTYPE_HTML_3_2
528 "<html>\n <head>\n <title>%s%s%s</title>\n"
529 "<base href=\"%s%s%s\">\n"
531 " <body>\n <h2>Directory of "
532 "<a href=\"/\">%s</a>/%s",
533 ap_escape_html(p, site), basedir, escpath,
534 ap_escape_uri(p, site), basedir, escpath,
535 ap_escape_uri(p, site), str);
537 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
538 p, c->bucket_alloc));
540 for (dir = path+1; (dir = strchr(dir, '/')) != NULL; )
543 if ((reldir = strrchr(path+1, '/'))==NULL) {
548 /* print "path/" component */
549 str = apr_psprintf(p, "<a href=\"%s%s/\">%s</a>/", basedir,
550 ap_escape_uri(p, path),
551 ap_escape_html(p, reldir));
555 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
559 if (wildcard != NULL) {
560 wildcard = ap_escape_html(p, wildcard);
561 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(wildcard,
566 /* If the caller has determined the current directory, and it differs */
567 /* from what the client requested, then show the real name */
568 if (pwd == NULL || strncmp(pwd, path, strlen(pwd)) == 0) {
569 str = apr_psprintf(p, "</h2>\n\n <hr />\n\n<pre>");
572 str = apr_psprintf(p, "</h2>\n\n(%s)\n\n <hr />\n\n<pre>",
573 ap_escape_html(p, pwd));
575 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
576 p, c->bucket_alloc));
580 str = apr_psprintf(p, "%s\n</pre>\n\n<hr />\n\n<pre>\n",
581 ap_escape_html(p, readme));
583 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
588 /* make sure page intro gets sent out */
589 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
590 if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
593 apr_brigade_cleanup(out);
598 /* loop through each line of directory */
599 while (BODY == ctx->state) {
603 ap_regmatch_t re_result[LS_REG_MATCH];
605 /* get a complete line */
606 /* if the buffer overruns - throw data away */
607 while (!found && !APR_BRIGADE_EMPTY(ctx->in)) {
608 char *pos, *response;
612 e = APR_BRIGADE_FIRST(ctx->in);
613 if (APR_BUCKET_IS_EOS(e)) {
617 if (APR_SUCCESS != (rv = apr_bucket_read(e, (const char **)&response, &len, APR_BLOCK_READ))) {
620 pos = memchr(response, APR_ASCII_LF, len);
622 if ((response + len) != (pos + 1)) {
623 len = pos - response + 1;
624 apr_bucket_split(e, pos - response + 1);
628 max = sizeof(ctx->buffer) - strlen(ctx->buffer) - 1;
633 /* len+1 to leave space for the trailing nil char */
634 apr_cpystrn(ctx->buffer+strlen(ctx->buffer), response, len+1);
636 apr_bucket_delete(e);
639 /* EOS? jump to footer */
645 /* not complete? leave and try get some more */
651 apr_size_t n = strlen(ctx->buffer);
652 if (ctx->buffer[n-1] == CRLF[1]) /* strip trailing '\n' */
653 ctx->buffer[--n] = '\0';
654 if (ctx->buffer[n-1] == CRLF[0]) /* strip trailing '\r' if present */
655 ctx->buffer[--n] = '\0';
659 if (ctx->buffer[0] == 'l' && (filename = strstr(ctx->buffer, " -> ")) != NULL) {
660 char *link_ptr = filename;
664 } while (filename[0] != ' ' && filename > ctx->buffer);
665 if (filename > ctx->buffer)
666 *(filename++) = '\0';
667 *(link_ptr++) = '\0';
668 str = apr_psprintf(p, "%s <a href=\"%s\">%s %s</a>\n",
669 ap_escape_html(p, ctx->buffer),
670 ap_escape_uri(p, filename),
671 ap_escape_html(p, filename),
672 ap_escape_html(p, link_ptr));
675 /* a directory/file? */
676 else if (ctx->buffer[0] == 'd' || ctx->buffer[0] == '-' || ctx->buffer[0] == 'l' || apr_isdigit(ctx->buffer[0])) {
678 char *searchptr = NULL;
680 if (apr_isdigit(ctx->buffer[0])) { /* handle DOS dir */
681 searchptr = strchr(ctx->buffer, '<');
682 if (searchptr != NULL)
684 searchptr = strchr(ctx->buffer, '>');
685 if (searchptr != NULL)
689 filename = strrchr(ctx->buffer, ' ');
690 if (filename == NULL) {
691 /* Line is broken. Ignore it. */
692 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01034)
693 "proxy_ftp: could not parse line %s",
695 /* erase buffer for next time around */
697 continue; /* while state is BODY */
699 *(filename++) = '\0';
701 /* handle filenames with spaces in 'em */
702 if (!strcmp(filename, ".") || !strcmp(filename, "..") || firstfile) {
704 searchidx = filename - ctx->buffer;
706 else if (searchidx != 0 && ctx->buffer[searchidx] != 0) {
708 ctx->buffer[searchidx - 1] = '\0';
709 filename = &ctx->buffer[searchidx];
712 /* Append a slash to the HREF link for directories */
713 if (!strcmp(filename, ".") || !strcmp(filename, "..") || ctx->buffer[0] == 'd') {
714 str = apr_psprintf(p, "%s <a href=\"%s/\">%s</a>\n",
715 ap_escape_html(p, ctx->buffer),
716 ap_escape_uri(p, filename),
717 ap_escape_html(p, filename));
720 str = apr_psprintf(p, "%s <a href=\"%s\">%s</a>\n",
721 ap_escape_html(p, ctx->buffer),
722 ap_escape_uri(p, filename),
723 ap_escape_html(p, filename));
726 /* Try a fallback for listings in the format of "ls -s1" */
727 else if (0 == ap_regexec(ls_regex, ctx->buffer, LS_REG_MATCH, re_result, 0)) {
729 * We don't need to check for rm_eo == rm_so == -1 here since ls_regex
730 * is such that $2 cannot be unset if we have a match.
732 filename = apr_pstrndup(p, &ctx->buffer[re_result[2].rm_so], re_result[2].rm_eo - re_result[2].rm_so);
734 str = apr_pstrcat(p, ap_escape_html(p, apr_pstrndup(p, ctx->buffer, re_result[2].rm_so)),
735 "<a href=\"", ap_escape_uri(p, filename), "\">",
736 ap_escape_html(p, filename), "</a>\n", NULL);
739 strcat(ctx->buffer, "\n"); /* re-append the newline */
740 str = ap_escape_html(p, ctx->buffer);
743 /* erase buffer for next time around */
746 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
748 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
749 if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
752 apr_brigade_cleanup(out);
756 if (FOOTER == ctx->state) {
757 str = apr_psprintf(p, "</pre>\n\n <hr />\n\n %s\n\n </body>\n</html>\n", ap_psignature("", r));
758 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
760 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
761 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_eos_create(c->bucket_alloc));
762 if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
765 apr_brigade_destroy(out);
771 /* Parse EPSV reply and return port, or zero on error. */
772 static apr_port_t parse_epsv_reply(const char *reply)
778 /* Reply syntax per RFC 2428: "229 blah blah (|||port|)" where '|'
779 * can be any character in ASCII from 33-126, obscurely. Verify
781 p = ap_strchr_c(reply, '(');
782 if (p == NULL || !p[1] || p[1] != p[2] || p[1] != p[3]
788 port = strtol(p + 4, &ep, 10);
789 if (errno || port < 1 || port > 65535 || ep[0] != p[1] || ep[1] != ')') {
793 return (apr_port_t)port;
797 * Generic "send FTP command to server" routine, using the control socket.
798 * Returns the FTP returncode (3 digit code)
799 * Allows for tracing the FTP protocol (in LogLevel debug)
802 proxy_ftp_command(const char *cmd, request_rec *r, conn_rec *ftp_ctrl,
803 apr_bucket_brigade *bb, char **pmessage)
807 char message[HUGE_STRING_LEN];
809 /* If cmd == NULL, we retrieve the next ftp response line */
811 conn_rec *c = r->connection;
812 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(cmd, strlen(cmd), r->pool, c->bucket_alloc));
813 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(c->bucket_alloc));
814 ap_pass_brigade(ftp_ctrl->output_filters, bb);
816 if (APLOGrtrace2(r)) {
817 /* strip off the CRLF for logging */
818 apr_cpystrn(message, cmd, sizeof(message));
819 if ((crlf = strchr(message, '\r')) != NULL ||
820 (crlf = strchr(message, '\n')) != NULL)
822 if (strncmp(message,"PASS ", 5) == 0)
823 strcpy(&message[5], "****");
824 ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, ">%s", message);
828 rc = ftp_getrc_msg(ftp_ctrl, bb, message, sizeof(message));
829 if (rc == -1 || rc == 421)
830 strcpy(message,"<unable to read result>");
831 if ((crlf = strchr(message, '\r')) != NULL ||
832 (crlf = strchr(message, '\n')) != NULL)
834 ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "<%3.3u %s", rc, message);
836 if (pmessage != NULL)
837 *pmessage = apr_pstrdup(r->pool, message);
842 /* Set ftp server to TYPE {A,I,E} before transfer of a directory or file */
843 static int ftp_set_TYPE(char xfer_type, request_rec *r, conn_rec *ftp_ctrl,
844 apr_bucket_brigade *bb, char **pmessage)
846 char old_type[2] = { 'A', '\0' }; /* After logon, mode is ASCII */
850 /* set desired type */
851 old_type[0] = xfer_type;
853 rc = proxy_ftp_command(apr_pstrcat(r->pool, "TYPE ", old_type, CRLF, NULL),
854 r, ftp_ctrl, bb, pmessage);
855 /* responses: 200, 421, 500, 501, 504, 530 */
856 /* 200 Command okay. */
857 /* 421 Service not available, closing control connection. */
858 /* 500 Syntax error, command unrecognized. */
859 /* 501 Syntax error in parameters or arguments. */
860 /* 504 Command not implemented for that parameter. */
861 /* 530 Not logged in. */
863 ret = ap_proxyerror(r, HTTP_GATEWAY_TIME_OUT,
864 "Error reading from remote server");
866 else if (rc == 421) {
867 ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
868 "Error reading from remote server");
870 else if (rc != 200 && rc != 504) {
871 ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
872 "Unable to set transfer type");
874 /* Allow not implemented */
875 else if (rc == 504) {
876 /* ignore it silently */
883 /* Return the current directory which we have selected on the FTP server, or NULL */
884 static char *ftp_get_PWD(request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
887 char *ftpmessage = NULL;
889 /* responses: 257, 500, 501, 502, 421, 550 */
890 /* 257 "<directory-name>" <commentary> */
891 /* 421 Service not available, closing control connection. */
892 /* 500 Syntax error, command unrecognized. */
893 /* 501 Syntax error in parameters or arguments. */
894 /* 502 Command not implemented. */
895 /* 550 Requested action not taken. */
896 switch (proxy_ftp_command("PWD" CRLF, r, ftp_ctrl, bb, &ftpmessage)) {
898 ap_proxyerror(r, HTTP_GATEWAY_TIME_OUT,
899 "Failed to read PWD on ftp server");
904 ap_proxyerror(r, HTTP_BAD_GATEWAY,
905 "Failed to read PWD on ftp server");
909 const char *dirp = ftpmessage;
910 cwd = ap_getword_conf(r->pool, &dirp);
917 /* Common routine for failed authorization (i.e., missing or wrong password)
918 * to an ftp service. This causes most browsers to retry the request
919 * with username and password (which was presumably queried from the user)
920 * supplied in the Authorization: header.
921 * Note that we "invent" a realm name which consists of the
922 * ftp://user@host part of the reqest (sans password -if supplied but invalid-)
924 static int ftp_unauthorized(request_rec *r, int log_it)
926 r->proxyreq = PROXYREQ_NONE;
928 * Log failed requests if they supplied a password (log username/password
932 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01035)
933 "missing or failed auth to %s",
934 apr_uri_unparse(r->pool,
935 &r->parsed_uri, APR_URI_UNP_OMITPATHINFO));
937 apr_table_setn(r->err_headers_out, "WWW-Authenticate",
938 apr_pstrcat(r->pool, "Basic realm=\"",
939 apr_uri_unparse(r->pool, &r->parsed_uri,
940 APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO),
943 return HTTP_UNAUTHORIZED;
947 apr_status_t proxy_ftp_cleanup(request_rec *r, proxy_conn_rec *backend)
951 ap_set_module_config(r->connection->conn_config, &proxy_ftp_module, NULL);
952 ap_proxy_release_connection("FTP", backend, r->server);
958 int ftp_proxyerror(request_rec *r, proxy_conn_rec *conn, int statuscode, const char *message)
960 proxy_ftp_cleanup(r, conn);
961 return ap_proxyerror(r, statuscode, message);
964 * Handles direct access of ftp:// URLs
965 * Original (Non-PASV) version from
966 * Troy Morrison <spiffnet@zoom.com>
967 * PASV added by Chuck
968 * Filters by [Graham Leggett <minfrin@sharp.fm>]
970 static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
971 proxy_server_conf *conf, char *url,
972 const char *proxyhost, apr_port_t proxyport)
974 apr_pool_t *p = r->pool;
975 conn_rec *c = r->connection;
976 proxy_conn_rec *backend;
977 apr_socket_t *sock, *local_sock, *data_sock = NULL;
978 apr_sockaddr_t *connect_addr = NULL;
980 conn_rec *origin, *data = NULL;
981 apr_status_t err = APR_SUCCESS;
983 apr_status_t uerr = APR_SUCCESS;
985 apr_bucket_brigade *bb;
986 char *buf, *connectname;
987 apr_port_t connectport;
988 char *ftpmessage = NULL;
989 char *path, *strp, *type_suffix, *cwd = NULL;
992 /* char *account = NULL; how to supply an account in a URL? */
993 const char *password = NULL;
997 char xfer_type = 'A'; /* after ftp login, the default is ASCII */
999 #if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1000 apr_time_t mtime = 0L;
1002 proxy_ftp_dir_conf *fdconf = ap_get_module_config(r->per_dir_config,
1005 /* stuff for PASV mode */
1006 int connect = 0, use_port = 0;
1007 char dates[APR_RFC822_DATE_LEN];
1009 apr_pool_t *address_pool;
1011 /* is this for us? */
1013 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1014 "declining URL %s - proxyhost %s specified:", url,
1016 return DECLINED; /* proxy connections are via HTTP */
1018 if (ap_cstr_casecmpn(url, "ftp:", 4)) {
1019 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1020 "declining URL %s - not ftp:", url);
1021 return DECLINED; /* only interested in FTP */
1023 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "serving URL %s", url);
1027 * I: Who Do I Connect To? -----------------------
1029 * Break up the URL to determine the host to connect to
1032 /* we only support GET and HEAD */
1033 if (r->method_number != M_GET)
1034 return HTTP_NOT_IMPLEMENTED;
1036 /* We break the URL into host, port, path-search */
1037 if (r->parsed_uri.hostname == NULL) {
1038 if (APR_SUCCESS != apr_uri_parse(p, url, &uri)) {
1039 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10189)
1040 "URI cannot be parsed: %s", url);
1041 return ap_proxyerror(r, HTTP_BAD_REQUEST, "URI cannot be parsed");
1043 connectname = uri.hostname;
1044 connectport = uri.port;
1045 path = apr_pstrdup(p, uri.path);
1048 connectname = r->parsed_uri.hostname;
1049 connectport = r->parsed_uri.port;
1050 path = apr_pstrdup(p, r->parsed_uri.path);
1052 if (connectport == 0) {
1053 connectport = apr_uri_port_of_scheme("ftp");
1055 path = (path != NULL && path[0] != '\0') ? &path[1] : "";
1057 type_suffix = strchr(path, ';');
1058 if (type_suffix != NULL)
1059 *(type_suffix++) = '\0';
1061 if (type_suffix != NULL && strncmp(type_suffix, "type=", 5) == 0
1062 && apr_isalpha(type_suffix[5])) {
1063 /* "type=d" forces a dir listing.
1064 * The other types (i|a|e) are directly used for the ftp TYPE command
1066 if ( ! (dirlisting = (apr_tolower(type_suffix[5]) == 'd')))
1067 xfer_type = apr_toupper(type_suffix[5]);
1069 /* Check valid types, rather than ignoring invalid types silently: */
1070 if (strchr("AEI", xfer_type) == NULL)
1071 return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
1072 "ftp proxy supports only types 'a', 'i', or 'e': \"",
1073 type_suffix, "\" is invalid.", NULL));
1076 /* make binary transfers the default */
1082 * The "Authorization:" header must be checked first. We allow the user
1083 * to "override" the URL-coded user [ & password ] in the Browsers'
1084 * User&Password Dialog. NOTE that this is only marginally more secure
1085 * than having the password travel in plain as part of the URL, because
1086 * Basic Auth simply uuencodes the plain text password. But chances are
1087 * still smaller that the URL is logged regularly.
1089 if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
1090 && ap_cstr_casecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
1091 && (password = ap_pbase64decode(r->pool, password))[0] != ':') {
1092 /* Check the decoded string for special characters. */
1093 if (!ftp_check_string(password)) {
1094 return ap_proxyerror(r, HTTP_BAD_REQUEST,
1095 "user credentials contained invalid character");
1098 * Note that this allocation has to be made from r->connection->pool
1099 * because it has the lifetime of the connection. The other
1100 * allocations are temporary and can be tossed away any time.
1102 user = ap_getword_nulls(r->connection->pool, &password, ':');
1103 r->ap_auth_type = "Basic";
1104 r->user = r->parsed_uri.user = user;
1106 else if ((user = r->parsed_uri.user) != NULL) {
1107 user = apr_pstrdup(p, user);
1109 if ((password = r->parsed_uri.password) != NULL) {
1110 char *tmp = apr_pstrdup(p, password);
1117 password = "apache-proxy@";
1120 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01036)
1121 "connecting %s to %s:%d", url, connectname, connectport);
1123 if (worker->s->is_address_reusable) {
1124 if (!worker->cp->addr) {
1126 if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) {
1127 ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(01037) "lock");
1128 return HTTP_INTERNAL_SERVER_ERROR;
1132 connect_addr = worker->cp->addr;
1133 address_pool = worker->cp->pool;
1136 address_pool = r->pool;
1138 /* do a DNS lookup for the destination host */
1140 err = apr_sockaddr_info_get(&(connect_addr),
1141 connectname, APR_UNSPEC,
1144 if (worker->s->is_address_reusable && !worker->cp->addr) {
1145 worker->cp->addr = connect_addr;
1147 if ((uerr = PROXY_THREAD_UNLOCK(worker)) != APR_SUCCESS) {
1148 ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(01038) "unlock");
1153 * get all the possible IP addresses for the destname and loop through
1154 * them until we get a successful connection
1156 if (APR_SUCCESS != err) {
1157 return ap_proxyerror(r, HTTP_GATEWAY_TIME_OUT, apr_pstrcat(p,
1158 "DNS lookup failure for: ",
1159 connectname, NULL));
1162 /* check if ProxyBlock directive on this host */
1163 if (OK != ap_proxy_checkproxyblock(r, conf, connectname, connect_addr)) {
1164 return ap_proxyerror(r, HTTP_FORBIDDEN,
1165 "Connect to remote machine blocked");
1168 /* create space for state information */
1169 backend = (proxy_conn_rec *) ap_get_module_config(c->conn_config, &proxy_ftp_module);
1171 status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server);
1175 ap_proxy_release_connection("FTP", backend, r->server);
1179 /* TODO: see if ftp could use determine_connection */
1180 backend->addr = connect_addr;
1181 ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
1186 * II: Make the Connection -----------------------
1188 * We have determined who to connect to. Now make the connection.
1192 if (ap_proxy_connect_backend("FTP", backend, worker, r->server)) {
1193 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01039)
1194 "an error occurred creating a new connection to %pI (%s)",
1195 connect_addr, connectname);
1196 proxy_ftp_cleanup(r, backend);
1197 return HTTP_SERVICE_UNAVAILABLE;
1200 status = ap_proxy_connection_create_ex("FTP", backend, r);
1202 proxy_ftp_cleanup(r, backend);
1206 /* Use old naming */
1207 origin = backend->connection;
1208 sock = backend->sock;
1210 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
1211 "control connection complete");
1215 * III: Send Control Request -------------------------
1217 * Log into the ftp server, send the username & password, change to the
1218 * correct directory...
1221 bb = apr_brigade_create(p, c->bucket_alloc);
1223 /* possible results: */
1224 /* 120 Service ready in nnn minutes. */
1225 /* 220 Service ready for new user. */
1226 /* 421 Service not available, closing control connection. */
1227 rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
1229 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1230 "Error reading from remote server");
1232 else if (rc == 421) {
1233 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1234 "Error reading from remote server");
1236 else if (rc == 120) {
1238 * RFC2616 states: 14.37 Retry-After
1240 * The Retry-After response-header field can be used with a 503 (Service
1241 * Unavailable) response to indicate how long the service is expected
1242 * to be unavailable to the requesting client. [...] The value of
1243 * this field can be either an HTTP-date or an integer number of
1244 * seconds (in decimal) after the time of the response. Retry-After
1245 * = "Retry-After" ":" ( HTTP-date | delta-seconds )
1247 char *secs_str = ftpmessage;
1250 /* Look for a number, preceded by whitespace */
1252 if ((secs_str==ftpmessage || apr_isspace(secs_str[-1])) &&
1253 apr_isdigit(secs_str[0]))
1255 if (*secs_str != '\0') {
1256 secs = atol(secs_str);
1257 apr_table_addn(r->headers_out, "Retry-After",
1258 apr_psprintf(p, "%lu", (unsigned long)(60 * secs)));
1260 return ftp_proxyerror(r, backend, HTTP_SERVICE_UNAVAILABLE, ftpmessage);
1262 else if (rc != 220) {
1263 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1266 rc = proxy_ftp_command(apr_pstrcat(p, "USER ", user, CRLF, NULL),
1267 r, origin, bb, &ftpmessage);
1268 /* possible results; 230, 331, 332, 421, 500, 501, 530 */
1269 /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */
1270 /* 230 User logged in, proceed. */
1271 /* 331 User name okay, need password. */
1272 /* 332 Need account for login. */
1273 /* 421 Service not available, closing control connection. */
1274 /* 500 Syntax error, command unrecognized. */
1275 /* (This may include errors such as command line too long.) */
1276 /* 501 Syntax error in parameters or arguments. */
1277 /* 530 Not logged in. */
1279 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1280 "Error reading from remote server");
1282 else if (rc == 421) {
1283 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1284 "Error reading from remote server");
1286 else if (rc == 530) {
1287 proxy_ftp_cleanup(r, backend);
1288 return ftp_unauthorized(r, 1); /* log it: user name guessing
1291 else if (rc != 230 && rc != 331) {
1292 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1295 if (rc == 331) { /* send password */
1296 if (password == NULL) {
1297 proxy_ftp_cleanup(r, backend);
1298 return ftp_unauthorized(r, 0);
1301 rc = proxy_ftp_command(apr_pstrcat(p, "PASS ", password, CRLF, NULL),
1302 r, origin, bb, &ftpmessage);
1303 /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */
1304 /* 230 User logged in, proceed. */
1305 /* 332 Need account for login. */
1306 /* 421 Service not available, closing control connection. */
1307 /* 500 Syntax error, command unrecognized. */
1308 /* 501 Syntax error in parameters or arguments. */
1309 /* 503 Bad sequence of commands. */
1310 /* 530 Not logged in. */
1312 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1313 "Error reading from remote server");
1315 else if (rc == 421) {
1316 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1317 "Error reading from remote server");
1319 else if (rc == 332) {
1320 return ftp_proxyerror(r, backend, HTTP_UNAUTHORIZED,
1321 apr_pstrcat(p, "Need account for login: ", ftpmessage, NULL));
1323 /* @@@ questionable -- we might as well return a 403 Forbidden here */
1324 else if (rc == 530) {
1325 proxy_ftp_cleanup(r, backend);
1326 return ftp_unauthorized(r, 1); /* log it: passwd guessing
1329 else if (rc != 230 && rc != 202) {
1330 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1333 apr_table_set(r->notes, "Directory-README", ftpmessage);
1336 /* Special handling for leading "%2f": this enforces a "cwd /"
1337 * out of the $HOME directory which was the starting point after login
1339 if (ap_cstr_casecmpn(path, "%2f", 3) == 0) {
1341 while (*path == '/') /* skip leading '/' (after root %2f) */
1344 rc = proxy_ftp_command("CWD /" CRLF, r, origin, bb, &ftpmessage);
1346 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1347 "Error reading from remote server");
1349 else if (rc == 421) {
1350 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1351 "Error reading from remote server");
1356 * set the directory (walk directory component by component): this is
1357 * what we must do if we don't know the OS type of the remote machine
1360 strp = strchr(path, '/');
1365 decodeenc(path); /* Note! This decodes a %2f -> "/" */
1367 if (strchr(path, '/')) { /* are there now any '/' characters? */
1368 return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
1369 "Use of /%2f is only allowed at the base directory");
1372 /* NOTE: FTP servers do globbing on the path.
1373 * So we need to escape the URI metacharacters.
1374 * We use a special glob-escaping routine to escape globbing chars.
1375 * We could also have extended gen_test_char.c with a special T_ESCAPE_FTP_PATH
1377 rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1378 ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1379 r, origin, bb, &ftpmessage);
1381 /* responses: 250, 421, 500, 501, 502, 530, 550 */
1382 /* 250 Requested file action okay, completed. */
1383 /* 421 Service not available, closing control connection. */
1384 /* 500 Syntax error, command unrecognized. */
1385 /* 501 Syntax error in parameters or arguments. */
1386 /* 502 Command not implemented. */
1387 /* 530 Not logged in. */
1388 /* 550 Requested action not taken. */
1390 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1391 "Error reading from remote server");
1393 else if (rc == 421) {
1394 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1395 "Error reading from remote server");
1397 else if (rc == 550) {
1398 return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1400 else if (rc != 250) {
1401 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1408 * IV: Make Data Connection? -------------------------
1410 * Try EPSV, if that fails... try PASV, if that fails... try PORT.
1412 /* this temporarily switches off EPSV/PASV */
1415 /* set up data connection - EPSV */
1417 apr_port_t data_port;
1420 * The EPSV command replaces PASV where both IPV4 and IPV6 is
1421 * supported. Only the port is returned, the IP address is always the
1422 * same as that on the control connection. Example: Entering Extended
1423 * Passive Mode (|||6446|)
1425 rc = proxy_ftp_command("EPSV" CRLF,
1426 r, origin, bb, &ftpmessage);
1427 /* possible results: 227, 421, 500, 501, 502, 530 */
1428 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1429 /* 421 Service not available, closing control connection. */
1430 /* 500 Syntax error, command unrecognized. */
1431 /* 501 Syntax error in parameters or arguments. */
1432 /* 502 Command not implemented. */
1433 /* 530 Not logged in. */
1435 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1436 "Error reading from remote server");
1438 else if (rc == 421) {
1439 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1440 "Error reading from remote server");
1442 else if (rc != 229 && rc != 500 && rc != 501 && rc != 502) {
1443 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1445 else if (rc == 229) {
1446 /* Parse the port out of the EPSV reply. */
1447 data_port = parse_epsv_reply(ftpmessage);
1450 apr_sockaddr_t *remote_addr, epsv_addr;
1452 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
1453 "EPSV contacting remote host on port %d", data_port);
1455 /* Retrieve the client's address. */
1456 rv = apr_socket_addr_get(&remote_addr, APR_REMOTE, sock);
1457 if (rv == APR_SUCCESS) {
1458 /* Take a shallow copy of the server address to
1459 * modify; the _addr_get function gives back a
1460 * pointer to the socket's internal structure.
1461 * This is awkward given current APR network
1463 epsv_addr = *remote_addr;
1464 epsv_addr.port = data_port;
1466 if (epsv_addr.family == APR_INET6) {
1467 epsv_addr.sa.sin6.sin6_port = htons(data_port);
1472 epsv_addr.sa.sin.sin_port = htons(data_port);
1474 rv = apr_socket_create(&data_sock, epsv_addr.family, SOCK_STREAM, 0, r->pool);
1477 if (rv != APR_SUCCESS) {
1478 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01040)
1479 "could not establish socket for client data connection");
1480 proxy_ftp_cleanup(r, backend);
1481 return HTTP_INTERNAL_SERVER_ERROR;
1484 if (conf->recv_buffer_size > 0
1485 && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
1486 conf->recv_buffer_size))) {
1487 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01041)
1488 "apr_socket_opt_set(SO_RCVBUF): Failed to "
1489 "set ProxyReceiveBufferSize, using default");
1492 rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
1493 if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
1494 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01042)
1495 "apr_socket_opt_set(APR_TCP_NODELAY): "
1499 rv = apr_socket_connect(data_sock, &epsv_addr);
1500 if (rv != APR_SUCCESS) {
1501 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01043)
1502 "EPSV attempt to connect to %pI failed - "
1503 "Firewall/NAT?", &epsv_addr);
1504 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1505 apr_psprintf(r->pool,
1506 "EPSV attempt to connect to %pI failed - firewall/NAT?",
1510 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
1511 "connected data socket to %pI", &epsv_addr);
1518 /* set up data connection - PASV */
1520 rc = proxy_ftp_command("PASV" CRLF,
1521 r, origin, bb, &ftpmessage);
1522 /* possible results: 227, 421, 500, 501, 502, 530 */
1523 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1524 /* 421 Service not available, closing control connection. */
1525 /* 500 Syntax error, command unrecognized. */
1526 /* 501 Syntax error in parameters or arguments. */
1527 /* 502 Command not implemented. */
1528 /* 530 Not logged in. */
1530 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1531 "Error reading from remote server");
1533 else if (rc == 421) {
1534 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1535 "Error reading from remote server");
1537 else if (rc != 227 && rc != 502) {
1538 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1540 else if (rc == 227) {
1541 unsigned int h0, h1, h2, h3, p0, p1;
1545 /* FIXME: Check PASV against RFC1123 */
1548 pstr = apr_strtok(pstr, " ", &tok_cntx); /* separate result code */
1550 if (*(pstr + strlen(pstr) + 1) == '=') {
1551 pstr += strlen(pstr) + 2;
1554 pstr = apr_strtok(NULL, "(", &tok_cntx); /* separate address &
1557 pstr = apr_strtok(NULL, ")", &tok_cntx);
1561 /* FIXME: Only supports IPV4 - fix in RFC2428 */
1563 if (pstr != NULL && (sscanf(pstr,
1564 "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) {
1566 apr_sockaddr_t *pasv_addr;
1567 apr_port_t pasvport = (p1 << 8) + p0;
1568 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01044)
1569 "PASV contacting host %d.%d.%d.%d:%d",
1570 h3, h2, h1, h0, pasvport);
1572 if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1573 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01045)
1574 "error creating PASV socket");
1575 proxy_ftp_cleanup(r, backend);
1576 return HTTP_INTERNAL_SERVER_ERROR;
1579 if (conf->recv_buffer_size > 0
1580 && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
1581 conf->recv_buffer_size))) {
1582 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01046)
1583 "apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
1586 rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
1587 if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
1588 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01047)
1589 "apr_socket_opt_set(APR_TCP_NODELAY): "
1593 /* make the connection */
1594 apr_sockaddr_info_get(&pasv_addr, apr_psprintf(p, "%d.%d.%d.%d", h3, h2, h1, h0), connect_addr->family, pasvport, 0, p);
1595 rv = apr_socket_connect(data_sock, pasv_addr);
1596 if (rv != APR_SUCCESS) {
1597 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01048)
1598 "PASV attempt to connect to %pI failed - Firewall/NAT?", pasv_addr);
1599 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1600 apr_psprintf(r->pool,
1601 "PASV attempt to connect to %pI failed - firewall/NAT?",
1612 /* set up data connection - PORT */
1614 apr_sockaddr_t *local_addr;
1616 apr_port_t local_port;
1617 unsigned int h0, h1, h2, h3, p0, p1;
1619 if ((rv = apr_socket_create(&local_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1620 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01049)
1621 "error creating local socket");
1622 proxy_ftp_cleanup(r, backend);
1623 return HTTP_INTERNAL_SERVER_ERROR;
1625 apr_socket_addr_get(&local_addr, APR_LOCAL, sock);
1626 local_port = local_addr->port;
1627 apr_sockaddr_ip_get(&local_ip, local_addr);
1629 if ((rv = apr_socket_opt_set(local_sock, APR_SO_REUSEADDR, one))
1631 #ifndef _OSD_POSIX /* BS2000 has this option "always on" */
1632 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01050)
1633 "error setting reuseaddr option");
1634 proxy_ftp_cleanup(r, backend);
1635 return HTTP_INTERNAL_SERVER_ERROR;
1636 #endif /* _OSD_POSIX */
1639 apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
1641 if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
1642 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01051)
1643 "error binding to ftp data socket %pI", local_addr);
1644 proxy_ftp_cleanup(r, backend);
1645 return HTTP_INTERNAL_SERVER_ERROR;
1648 /* only need a short queue */
1649 if ((rv = apr_socket_listen(local_sock, 2)) != APR_SUCCESS) {
1650 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01052)
1651 "error listening to ftp data socket %pI", local_addr);
1652 proxy_ftp_cleanup(r, backend);
1653 return HTTP_INTERNAL_SERVER_ERROR;
1656 /* FIXME: Sent PORT here */
1658 if (local_ip && (sscanf(local_ip,
1659 "%d.%d.%d.%d", &h3, &h2, &h1, &h0) == 4)) {
1660 p1 = (local_port >> 8);
1661 p0 = (local_port & 0xFF);
1663 rc = proxy_ftp_command(apr_psprintf(p, "PORT %d,%d,%d,%d,%d,%d" CRLF, h3, h2, h1, h0, p1, p0),
1664 r, origin, bb, &ftpmessage);
1665 /* possible results: 200, 421, 500, 501, 502, 530 */
1666 /* 200 Command okay. */
1667 /* 421 Service not available, closing control connection. */
1668 /* 500 Syntax error, command unrecognized. */
1669 /* 501 Syntax error in parameters or arguments. */
1670 /* 502 Command not implemented. */
1671 /* 530 Not logged in. */
1673 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1674 "Error reading from remote server");
1676 else if (rc == 421) {
1677 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1678 "Error reading from remote server");
1680 else if (rc != 200) {
1681 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1684 /* signal that we must use the EPRT/PORT loop */
1689 * The EPRT command replaces PORT where both IPV4 and IPV6 is supported. The first
1690 * number (1,2) indicates the protocol type. Examples:
1691 * EPRT |1|132.235.1.2|6275|
1692 * EPRT |2|1080::8:800:200C:417A|5282|
1694 return ftp_proxyerror(r, backend, HTTP_NOT_IMPLEMENTED,
1695 "Connect to IPV6 ftp server using EPRT not supported. Enable EPSV.");
1701 * V: Set The Headers -------------------
1703 * Get the size of the request, set up the environment for HTTP.
1706 /* set request; "path" holds last path component */
1707 len = decodeenc(path);
1709 if (strchr(path, '/')) { /* are there now any '/' characters? */
1710 return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
1711 "Use of /%2f is only allowed at the base directory");
1714 /* If len == 0 then it must be a directory (you can't RETR nothing)
1715 * Also, don't allow to RETR by wildcard. Instead, create a dirlisting,
1716 * unless ProxyFtpListOnWildcard is off.
1718 if (len == 0 || (ftp_check_globbingchars(path) && fdconf->ftp_list_on_wildcard)) {
1722 /* (from FreeBSD ftpd):
1723 * SIZE is not in RFC959, but Postel has blessed it and
1724 * it will be in the updated RFC.
1726 * Return size of file in a format suitable for
1727 * using with RESTART (we just count bytes).
1729 /* from draft-ietf-ftpext-mlst-14.txt:
1731 * change depending on the current STRUcture, MODE and TYPE of the data
1732 * connection, or a data connection which would be created were one
1733 * created now. Thus, the result of the SIZE command is dependent on
1734 * the currently established STRU, MODE and TYPE parameters.
1736 /* Therefore: switch to binary if the user did not specify ";type=a" */
1737 ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
1738 rc = proxy_ftp_command(apr_pstrcat(p, "SIZE ",
1739 ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1740 r, origin, bb, &ftpmessage);
1742 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1743 "Error reading from remote server");
1745 else if (rc == 421) {
1746 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1747 "Error reading from remote server");
1749 else if (rc == 213) {/* Size command ok */
1751 for (j = 0; apr_isdigit(ftpmessage[j]); j++)
1753 ftpmessage[j] = '\0';
1754 if (ftpmessage[0] != '\0')
1755 size = ftpmessage; /* already pstrdup'ed: no copy necessary */
1757 else if (rc == 550) { /* Not a regular file */
1758 ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
1759 "SIZE shows this is a directory");
1761 rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1762 ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1763 r, origin, bb, &ftpmessage);
1764 /* possible results: 250, 421, 500, 501, 502, 530, 550 */
1765 /* 250 Requested file action okay, completed. */
1766 /* 421 Service not available, closing control connection. */
1767 /* 500 Syntax error, command unrecognized. */
1768 /* 501 Syntax error in parameters or arguments. */
1769 /* 502 Command not implemented. */
1770 /* 530 Not logged in. */
1771 /* 550 Requested action not taken. */
1773 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1774 "Error reading from remote server");
1776 else if (rc == 421) {
1777 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1778 "Error reading from remote server");
1780 else if (rc == 550) {
1781 return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1783 else if (rc != 250) {
1784 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1791 cwd = ftp_get_PWD(r, origin, bb);
1793 apr_table_set(r->notes, "Directory-PWD", cwd);
1797 ftp_set_TYPE('A', r, origin, bb, NULL);
1798 /* If the current directory contains no slash, we are talking to
1799 * a non-unix ftp system. Try LIST instead of "LIST -lag", it
1800 * should return a long listing anyway (unlike NLST).
1801 * Some exotic FTP servers might choke on the "-lag" switch.
1803 /* Note that we do not escape the path here, to allow for
1804 * queries like: ftp://user@host/apache/src/server/http_*.c
1807 buf = apr_pstrcat(p, "LIST ", path, CRLF, NULL);
1808 else if (cwd == NULL || strchr(cwd, '/') != NULL)
1809 buf = "LIST -lag" CRLF;
1814 /* switch to binary if the user did not specify ";type=a" */
1815 ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
1816 #if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1817 /* from draft-ietf-ftpext-mlst-14.txt:
1818 * The FTP command, MODIFICATION TIME (MDTM), can be used to determine
1819 * when a file in the server NVFS was last modified. <..>
1820 * The syntax of a time value is:
1821 * time-val = 14DIGIT [ "." 1*DIGIT ] <..>
1822 * Symbolically, a time-val may be viewed as
1823 * YYYYMMDDHHMMSS.sss
1824 * The "." and subsequent digits ("sss") are optional. <..>
1825 * Time values are always represented in UTC (GMT)
1827 rc = proxy_ftp_command(apr_pstrcat(p, "MDTM ", ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1828 r, origin, bb, &ftpmessage);
1829 /* then extract the Last-Modified time from it (YYYYMMDDhhmmss or YYYYMMDDhhmmss.xxx GMT). */
1839 if (6 == sscanf(ftpmessage, "%4[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]",
1840 time_val.YYYY, time_val.MM, time_val.DD, time_val.hh, time_val.mm, time_val.ss)) {
1842 memset (&tms, '\0', sizeof tms);
1843 tms.tm_year = atoi(time_val.YYYY) - 1900;
1844 tms.tm_mon = atoi(time_val.MM) - 1;
1845 tms.tm_mday = atoi(time_val.DD);
1846 tms.tm_hour = atoi(time_val.hh);
1847 tms.tm_min = atoi(time_val.mm);
1848 tms.tm_sec = atoi(time_val.ss);
1849 #ifdef HAVE_TIMEGM /* Does system have timegm()? */
1850 mtime = timegm(&tms);
1851 mtime *= APR_USEC_PER_SEC;
1852 #elif HAVE_GMTOFF /* does struct tm have a member tm_gmtoff? */
1853 /* mktime will subtract the local timezone, which is not what we want.
1854 * Add it again because the MDTM string is GMT
1856 mtime = mktime(&tms);
1857 mtime += tms.tm_gmtoff;
1858 mtime *= APR_USEC_PER_SEC;
1864 #endif /* USE_MDTM */
1865 /* FIXME: Handle range requests - send REST */
1866 buf = apr_pstrcat(p, "RETR ", ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL);
1868 rc = proxy_ftp_command(buf, r, origin, bb, &ftpmessage);
1869 /* rc is an intermediate response for the LIST or RETR commands */
1872 * RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530,
1873 * 550 NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502,
1876 /* 110 Restart marker reply. */
1877 /* 125 Data connection already open; transfer starting. */
1878 /* 150 File status okay; about to open data connection. */
1879 /* 226 Closing data connection. */
1880 /* 250 Requested file action okay, completed. */
1881 /* 421 Service not available, closing control connection. */
1882 /* 425 Can't open data connection. */
1883 /* 426 Connection closed; transfer aborted. */
1884 /* 450 Requested file action not taken. */
1885 /* 451 Requested action aborted. Local error in processing. */
1886 /* 500 Syntax error, command unrecognized. */
1887 /* 501 Syntax error in parameters or arguments. */
1888 /* 530 Not logged in. */
1889 /* 550 Requested action not taken. */
1891 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1892 "Error reading from remote server");
1894 else if (rc == 421) {
1895 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1896 "Error reading from remote server");
1898 else if (rc == 550) {
1899 ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
1900 "RETR failed, trying LIST instead");
1902 /* Directory Listings should always be fetched in ASCII mode */
1904 ftp_set_TYPE('A', r, origin, bb, NULL);
1906 rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1907 ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1908 r, origin, bb, &ftpmessage);
1909 /* possible results: 250, 421, 500, 501, 502, 530, 550 */
1910 /* 250 Requested file action okay, completed. */
1911 /* 421 Service not available, closing control connection. */
1912 /* 500 Syntax error, command unrecognized. */
1913 /* 501 Syntax error in parameters or arguments. */
1914 /* 502 Command not implemented. */
1915 /* 530 Not logged in. */
1916 /* 550 Requested action not taken. */
1918 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1919 "Error reading from remote server");
1921 else if (rc == 421) {
1922 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1923 "Error reading from remote server");
1925 else if (rc == 550) {
1926 return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1928 else if (rc != 250) {
1929 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1932 /* Update current directory after CWD */
1933 cwd = ftp_get_PWD(r, origin, bb);
1935 apr_table_set(r->notes, "Directory-PWD", cwd);
1938 /* See above for the "LIST" vs. "LIST -lag" discussion. */
1939 rc = proxy_ftp_command((cwd == NULL || strchr(cwd, '/') != NULL)
1940 ? "LIST -lag" CRLF : "LIST" CRLF,
1941 r, origin, bb, &ftpmessage);
1943 /* rc is an intermediate response for the LIST command (125 transfer starting, 150 opening data connection) */
1945 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1946 "Error reading from remote server");
1948 else if (rc == 421) {
1949 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1950 "Error reading from remote server");
1953 if (rc != 125 && rc != 150 && rc != 226 && rc != 250) {
1954 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1957 r->status = HTTP_OK;
1958 r->status_line = "200 OK";
1960 apr_rfc822_date(dates, r->request_time);
1961 apr_table_setn(r->headers_out, "Date", dates);
1962 apr_table_setn(r->headers_out, "Server", ap_get_server_banner());
1964 /* set content-type */
1966 ap_set_content_type(r, apr_pstrcat(p, "text/html;charset=",
1967 fdconf->ftp_directory_charset ?
1968 fdconf->ftp_directory_charset :
1969 "ISO-8859-1", NULL));
1972 if (xfer_type != 'A' && size != NULL) {
1973 /* We "trust" the ftp server to really serve (size) bytes... */
1974 apr_table_setn(r->headers_out, "Content-Length", size);
1975 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1976 "Content-Length set to %s", size);
1979 if (r->content_type) {
1980 apr_table_setn(r->headers_out, "Content-Type", r->content_type);
1981 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1982 "Content-Type set to %s", r->content_type);
1985 #if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1987 char datestr[APR_RFC822_DATE_LEN];
1988 apr_rfc822_date(datestr, mtime);
1989 apr_table_set(r->headers_out, "Last-Modified", datestr);
1990 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1991 "Last-Modified set to %s", datestr);
1993 #endif /* USE_MDTM */
1995 /* If an encoding has been set by mistake, delete it.
1996 * @@@ FIXME (e.g., for ftp://user@host/file*.tar.gz,
1997 * @@@ the encoding is currently set to x-gzip)
1999 if (dirlisting && r->content_encoding != NULL)
2000 r->content_encoding = NULL;
2002 /* set content-encoding (not for dir listings, they are uncompressed)*/
2003 if (r->content_encoding != NULL && r->content_encoding[0] != '\0') {
2004 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
2005 "Content-Encoding set to %s", r->content_encoding);
2006 apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
2009 /* wait for connection */
2012 rv = apr_socket_accept(&data_sock, local_sock, r->pool);
2013 if (APR_STATUS_IS_EINTR(rv)) {
2016 else if (rv == APR_SUCCESS) {
2020 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01053)
2021 "failed to accept data connection");
2022 proxy_ftp_cleanup(r, backend);
2023 return HTTP_GATEWAY_TIME_OUT;
2028 /* the transfer socket is now open, create a new connection */
2029 data = ap_run_create_connection(p, r->server, data_sock, r->connection->id,
2030 r->connection->sbh, c->bucket_alloc);
2033 * the peer reset the connection already; ap_run_create_connection() closed
2036 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01054)
2037 "an error occurred creating the transfer connection");
2038 proxy_ftp_cleanup(r, backend);
2039 return HTTP_INTERNAL_SERVER_ERROR;
2043 * We do not do SSL over the data connection, even if the virtual host we
2044 * are in might have SSL enabled
2046 ap_proxy_ssl_engine(data, r->per_dir_config, 0);
2047 /* set up the connection filters */
2048 rc = ap_run_pre_connection(data, data_sock);
2049 if (rc != OK && rc != DONE) {
2050 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01055)
2051 "pre_connection setup failed (%d)", rc);
2053 proxy_ftp_cleanup(r, backend);
2058 * VI: Receive the Response ------------------------
2060 * Get response from the remote ftp socket, and pass it up the filter chain.
2067 /* insert directory filter */
2068 ap_add_output_filter("PROXY_SEND_DIR", NULL, r, r->connection);
2072 if (!r->header_only) {
2076 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "start body send");
2078 /* read the body, pass it to the output filters */
2079 while (ap_get_brigade(data->input_filters,
2083 conf->io_buffer_size) == APR_SUCCESS) {
2086 apr_off_t readbytes;
2087 apr_brigade_length(bb, 0, &readbytes);
2088 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01056)
2089 "proxy: readbytes: %#x", readbytes);
2093 if (APR_BRIGADE_EMPTY(bb)) {
2097 /* found the last brigade? */
2098 if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
2099 /* if this is the last brigade, cleanup the
2100 * backend connection first to prevent the
2101 * backend server from hanging around waiting
2102 * for a slow client to eat these bytes
2104 ap_flush_conn(data);
2106 apr_socket_close(data_sock);
2109 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01057)
2110 "data connection closed");
2111 /* signal that we must leave */
2115 /* if no EOS yet, then we must flush */
2116 if (FALSE == finish) {
2117 e = apr_bucket_flush_create(c->bucket_alloc);
2118 APR_BRIGADE_INSERT_TAIL(bb, e);
2121 /* try send what we read */
2122 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
2124 /* Ack! Phbtt! Die! User aborted! */
2128 /* make sure we always clean up after ourselves */
2129 apr_brigade_cleanup(bb);
2131 /* if we are done, leave */
2132 if (TRUE == finish) {
2136 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "end body send");
2140 ap_flush_conn(data);
2141 apr_socket_close(data_sock);
2142 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01058) "data connection closed");
2145 /* Retrieve the final response for the RETR or LIST commands */
2146 proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
2147 apr_brigade_cleanup(bb);
2150 * VII: Clean Up -------------
2152 * If there are no KeepAlives, or if the connection has been signalled to
2153 * close, close the socket and clean up
2157 proxy_ftp_command("QUIT" CRLF, r, origin, bb, &ftpmessage);
2158 /* responses: 221, 500 */
2159 /* 221 Service closing control connection. */
2160 /* 500 Syntax error, command unrecognized. */
2161 ap_flush_conn(origin);
2162 proxy_ftp_cleanup(r, backend);
2164 apr_brigade_destroy(bb);
2168 static void ap_proxy_ftp_register_hook(apr_pool_t *p)
2171 proxy_hook_scheme_handler(proxy_ftp_handler, NULL, NULL, APR_HOOK_MIDDLE);
2172 proxy_hook_canon_handler(proxy_ftp_canon, NULL, NULL, APR_HOOK_MIDDLE);
2174 ap_register_output_filter("PROXY_SEND_DIR", proxy_send_dir_filter,
2175 NULL, AP_FTYPE_RESOURCE);
2176 /* Compile the output format of "ls -s1" as a fallback for non-unix ftp listings */
2177 ls_regex = ap_pregcomp(p, LS_REG_PATTERN, AP_REG_EXTENDED);
2178 ap_assert(ls_regex != NULL);
2181 static const command_rec proxy_ftp_cmds[] =
2183 AP_INIT_FLAG("ProxyFtpListOnWildcard", set_ftp_list_on_wildcard, NULL,
2184 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."),
2185 AP_INIT_FLAG("ProxyFtpEscapeWildcards", set_ftp_escape_wildcards, NULL,
2186 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."),
2187 AP_INIT_TAKE1("ProxyFtpDirCharset", set_ftp_directory_charset, NULL,
2188 RSRC_CONF|ACCESS_CONF, "Define the character set for proxied FTP listings"),
2193 AP_DECLARE_MODULE(proxy_ftp) = {
2194 STANDARD20_MODULE_STUFF,
2195 create_proxy_ftp_dir_config,/* create per-directory config structure */
2196 merge_proxy_ftp_dir_config, /* merge per-directory config structures */
2197 NULL, /* create per-server config structure */
2198 NULL, /* merge per-server config structures */
2199 proxy_ftp_cmds, /* command apr_table_t */
2200 ap_proxy_ftp_register_hook /* register hooks */