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_REMOVE(e);
279 apr_bucket_destroy(e);
288 * Canonicalise ftp URLs.
290 static int proxy_ftp_canon(request_rec *r, char *url)
292 char *user, *password, *host, *path, *parms, *strp, sport[7];
293 apr_pool_t *p = r->pool;
295 apr_port_t port, def_port;
298 if (strncasecmp(url, "ftp:", 4) == 0) {
304 def_port = apr_uri_port_of_scheme("ftp");
306 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "canonicalising URL %s", url);
309 err = ap_proxy_canon_netloc(p, &url, &user, &password, &host, &port);
311 return HTTP_BAD_REQUEST;
312 if (user != NULL && !ftp_check_string(user))
313 return HTTP_BAD_REQUEST;
314 if (password != NULL && !ftp_check_string(password))
315 return HTTP_BAD_REQUEST;
317 /* now parse path/parameters args, according to rfc1738 */
319 * N.B. if this isn't a true proxy request, then the URL path (but not
320 * query args) has already been decoded. This gives rise to the problem
321 * of a ; being decoded into the path.
323 strp = strchr(url, ';');
326 parms = ap_proxy_canonenc(p, strp, strlen(strp), enc_parm, 0,
329 return HTTP_BAD_REQUEST;
334 path = ap_proxy_canonenc(p, url, strlen(url), enc_path, 0, r->proxyreq);
336 return HTTP_BAD_REQUEST;
337 if (!ftp_check_string(path))
338 return HTTP_BAD_REQUEST;
340 if (r->proxyreq && r->args != NULL) {
342 strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_parm, 1, r->proxyreq);
344 return HTTP_BAD_REQUEST;
345 parms = apr_pstrcat(p, parms, "?", strp, NULL);
348 strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_fpath, 1, r->proxyreq);
350 return HTTP_BAD_REQUEST;
351 path = apr_pstrcat(p, path, "?", strp, NULL);
356 /* now, rebuild URL */
358 if (port != def_port)
359 apr_snprintf(sport, sizeof(sport), ":%d", port);
363 if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
364 host = apr_pstrcat(p, "[", host, "]", NULL);
366 r->filename = apr_pstrcat(p, "proxy:ftp://", (user != NULL) ? user : "",
367 (password != NULL) ? ":" : "",
368 (password != NULL) ? password : "",
369 (user != NULL) ? "@" : "", host, sport, "/", path,
370 (parms[0] != '\0') ? ";" : "", parms, NULL);
375 /* we chop lines longer than 80 characters */
376 #define MAX_LINE_LEN 80
379 * Reads response lines, returns both the ftp status code and
380 * remembers the response message in the supplied buffer
382 static int ftp_getrc_msg(conn_rec *ftp_ctrl, apr_bucket_brigade *bb, char *msgbuf, int msglen)
385 char response[MAX_LINE_LEN];
387 char *mb = msgbuf, *me = &msgbuf[msglen];
391 if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
395 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
398 if (!apr_isdigit(response[0]) || !apr_isdigit(response[1]) ||
399 !apr_isdigit(response[2]) || (response[3] != ' ' && response[3] != '-'))
402 status = 100 * response[0] + 10 * response[1] + response[2] - 111 * '0';
404 mb = apr_cpystrn(mb, response + 4, me - mb);
406 if (response[3] == '-') {
407 memcpy(buff, response, 3);
410 if (APR_SUCCESS != (rv = ftp_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
413 mb = apr_cpystrn(mb, response + (' ' == response[0] ? 1 : 4), me - mb);
414 } while (memcmp(response, buff, 4) != 0);
420 /* this is a filter that turns a raw ASCII directory listing into pretty HTML */
422 /* ideally, mod_proxy should simply send the raw directory list up the filter
423 * stack to mod_autoindex, which in theory should turn the raw ascii into
424 * pretty html along with all the bells and whistles it provides...
426 * all in good time...! :)
430 apr_bucket_brigade *in;
431 char buffer[MAX_STRING_LEN];
437 /* fallback regex for ls -s1; ($0..$2) == 3 */
438 #define LS_REG_PATTERN "^ *([0-9]+) +([^ ]+)$"
439 #define LS_REG_MATCH 3
440 static ap_regex_t *ls_regex;
442 static apr_status_t proxy_send_dir_filter(ap_filter_t *f,
443 apr_bucket_brigade *in)
445 request_rec *r = f->r;
446 conn_rec *c = r->connection;
447 apr_pool_t *p = r->pool;
448 apr_bucket_brigade *out = apr_brigade_create(p, c->bucket_alloc);
452 char *dir, *path, *reldir, *site, *str, *type;
454 const char *pwd = apr_table_get(r->notes, "Directory-PWD");
455 const char *readme = apr_table_get(r->notes, "Directory-README");
457 proxy_dir_ctx_t *ctx = f->ctx;
460 f->ctx = ctx = apr_pcalloc(p, sizeof(*ctx));
461 ctx->in = apr_brigade_create(p, c->bucket_alloc);
466 /* combine the stored and the new */
467 APR_BRIGADE_CONCAT(ctx->in, in);
469 if (HEADER == ctx->state) {
471 /* basedir is either "", or "/%2f" for the "squid %2f hack" */
472 const char *basedir = ""; /* By default, path is relative to the $HOME dir */
473 char *wildcard = NULL;
477 * In the reverse proxy case we need to construct our site string
478 * via ap_construct_url. For non anonymous sites apr_uri_unparse would
479 * only supply us with 'username@' which leads to the construction of
480 * an invalid base href later on. Losing the username part of the URL
481 * is no problem in the reverse proxy case as the browser sents the
482 * credentials anyway once entered.
484 if (r->proxyreq == PROXYREQ_REVERSE) {
485 site = ap_construct_url(p, "", r);
488 /* Save "scheme://site" prefix without password */
489 site = apr_uri_unparse(p, &f->r->parsed_uri,
490 APR_URI_UNP_OMITPASSWORD |
491 APR_URI_UNP_OMITPATHINFO);
494 /* ... and path without query args */
495 path = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITSITEPART | APR_URI_UNP_OMITQUERY);
497 /* If path began with /%2f, change the basedir */
498 if (strncasecmp(path, "/%2f", 4) == 0) {
502 /* Strip off a type qualifier. It is ignored for dir listings */
503 if ((type = strstr(path, ";type=")) != NULL)
506 (void)decodeenc(path);
508 while (path[1] == '/') /* collapse multiple leading slashes to one */
511 reldir = strrchr(path, '/');
512 if (reldir != NULL && ftp_check_globbingchars(reldir)) {
513 wildcard = &reldir[1];
514 reldir[0] = '\0'; /* strip off the wildcard suffix */
517 /* Copy path, strip (all except the last) trailing slashes */
518 /* (the trailing slash is needed for the dir component loop below) */
519 path = dir = apr_pstrcat(p, path, "/", NULL);
520 for (n = strlen(path); n > 1 && path[n - 1] == '/' && path[n - 2] == '/'; --n)
523 /* Add a link to the root directory (if %2f hack was used) */
524 str = (basedir[0] != '\0') ? "<a href=\"/%2f/\">%2f</a>/" : "";
526 /* print "ftp://host/" */
527 escpath = ap_escape_html(p, path);
528 str = apr_psprintf(p, DOCTYPE_HTML_3_2
529 "<html>\n <head>\n <title>%s%s%s</title>\n"
530 "<base href=\"%s%s%s\">\n"
532 " <body>\n <h2>Directory of "
533 "<a href=\"/\">%s</a>/%s",
534 ap_escape_html(p, site), basedir, escpath,
535 ap_escape_uri(p, site), basedir, escpath,
536 ap_escape_uri(p, site), str);
538 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
539 p, c->bucket_alloc));
541 for (dir = path+1; (dir = strchr(dir, '/')) != NULL; )
544 if ((reldir = strrchr(path+1, '/'))==NULL) {
549 /* print "path/" component */
550 str = apr_psprintf(p, "<a href=\"%s%s/\">%s</a>/", basedir,
551 ap_escape_uri(p, path),
552 ap_escape_html(p, reldir));
556 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
560 if (wildcard != NULL) {
561 wildcard = ap_escape_html(p, wildcard);
562 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(wildcard,
567 /* If the caller has determined the current directory, and it differs */
568 /* from what the client requested, then show the real name */
569 if (pwd == NULL || strncmp(pwd, path, strlen(pwd)) == 0) {
570 str = apr_psprintf(p, "</h2>\n\n <hr />\n\n<pre>");
573 str = apr_psprintf(p, "</h2>\n\n(%s)\n\n <hr />\n\n<pre>",
574 ap_escape_html(p, pwd));
576 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
577 p, c->bucket_alloc));
581 str = apr_psprintf(p, "%s\n</pre>\n\n<hr />\n\n<pre>\n",
582 ap_escape_html(p, readme));
584 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
589 /* make sure page intro gets sent out */
590 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
591 if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
594 apr_brigade_cleanup(out);
599 /* loop through each line of directory */
600 while (BODY == ctx->state) {
604 ap_regmatch_t re_result[LS_REG_MATCH];
606 /* get a complete line */
607 /* if the buffer overruns - throw data away */
608 while (!found && !APR_BRIGADE_EMPTY(ctx->in)) {
609 char *pos, *response;
613 e = APR_BRIGADE_FIRST(ctx->in);
614 if (APR_BUCKET_IS_EOS(e)) {
618 if (APR_SUCCESS != (rv = apr_bucket_read(e, (const char **)&response, &len, APR_BLOCK_READ))) {
621 pos = memchr(response, APR_ASCII_LF, len);
623 if ((response + len) != (pos + 1)) {
624 len = pos - response + 1;
625 apr_bucket_split(e, pos - response + 1);
629 max = sizeof(ctx->buffer) - strlen(ctx->buffer) - 1;
634 /* len+1 to leave space for the trailing nil char */
635 apr_cpystrn(ctx->buffer+strlen(ctx->buffer), response, len+1);
637 APR_BUCKET_REMOVE(e);
638 apr_bucket_destroy(e);
641 /* EOS? jump to footer */
647 /* not complete? leave and try get some more */
653 apr_size_t n = strlen(ctx->buffer);
654 if (ctx->buffer[n-1] == CRLF[1]) /* strip trailing '\n' */
655 ctx->buffer[--n] = '\0';
656 if (ctx->buffer[n-1] == CRLF[0]) /* strip trailing '\r' if present */
657 ctx->buffer[--n] = '\0';
661 if (ctx->buffer[0] == 'l' && (filename = strstr(ctx->buffer, " -> ")) != NULL) {
662 char *link_ptr = filename;
666 } while (filename[0] != ' ' && filename > ctx->buffer);
667 if (filename > ctx->buffer)
668 *(filename++) = '\0';
669 *(link_ptr++) = '\0';
670 str = apr_psprintf(p, "%s <a href=\"%s\">%s %s</a>\n",
671 ap_escape_html(p, ctx->buffer),
672 ap_escape_uri(p, filename),
673 ap_escape_html(p, filename),
674 ap_escape_html(p, link_ptr));
677 /* a directory/file? */
678 else if (ctx->buffer[0] == 'd' || ctx->buffer[0] == '-' || ctx->buffer[0] == 'l' || apr_isdigit(ctx->buffer[0])) {
680 char *searchptr = NULL;
682 if (apr_isdigit(ctx->buffer[0])) { /* handle DOS dir */
683 searchptr = strchr(ctx->buffer, '<');
684 if (searchptr != NULL)
686 searchptr = strchr(ctx->buffer, '>');
687 if (searchptr != NULL)
691 filename = strrchr(ctx->buffer, ' ');
692 if (filename == NULL) {
693 /* Line is broken. Ignore it. */
694 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01034)
695 "proxy_ftp: could not parse line %s",
697 /* erase buffer for next time around */
699 continue; /* while state is BODY */
701 *(filename++) = '\0';
703 /* handle filenames with spaces in 'em */
704 if (!strcmp(filename, ".") || !strcmp(filename, "..") || firstfile) {
706 searchidx = filename - ctx->buffer;
708 else if (searchidx != 0 && ctx->buffer[searchidx] != 0) {
710 ctx->buffer[searchidx - 1] = '\0';
711 filename = &ctx->buffer[searchidx];
714 /* Append a slash to the HREF link for directories */
715 if (!strcmp(filename, ".") || !strcmp(filename, "..") || ctx->buffer[0] == 'd') {
716 str = apr_psprintf(p, "%s <a href=\"%s/\">%s</a>\n",
717 ap_escape_html(p, ctx->buffer),
718 ap_escape_uri(p, filename),
719 ap_escape_html(p, filename));
722 str = apr_psprintf(p, "%s <a href=\"%s\">%s</a>\n",
723 ap_escape_html(p, ctx->buffer),
724 ap_escape_uri(p, filename),
725 ap_escape_html(p, filename));
728 /* Try a fallback for listings in the format of "ls -s1" */
729 else if (0 == ap_regexec(ls_regex, ctx->buffer, LS_REG_MATCH, re_result, 0)) {
731 * We don't need to check for rm_eo == rm_so == -1 here since ls_regex
732 * is such that $2 cannot be unset if we have a match.
734 filename = apr_pstrndup(p, &ctx->buffer[re_result[2].rm_so], re_result[2].rm_eo - re_result[2].rm_so);
736 str = apr_pstrcat(p, ap_escape_html(p, apr_pstrndup(p, ctx->buffer, re_result[2].rm_so)),
737 "<a href=\"", ap_escape_uri(p, filename), "\">",
738 ap_escape_html(p, filename), "</a>\n", NULL);
741 strcat(ctx->buffer, "\n"); /* re-append the newline */
742 str = ap_escape_html(p, ctx->buffer);
745 /* erase buffer for next time around */
748 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
750 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
751 if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
754 apr_brigade_cleanup(out);
758 if (FOOTER == ctx->state) {
759 str = apr_psprintf(p, "</pre>\n\n <hr />\n\n %s\n\n </body>\n</html>\n", ap_psignature("", r));
760 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
762 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
763 APR_BRIGADE_INSERT_TAIL(out, apr_bucket_eos_create(c->bucket_alloc));
764 if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
767 apr_brigade_destroy(out);
773 /* Parse EPSV reply and return port, or zero on error. */
774 static apr_port_t parse_epsv_reply(const char *reply)
780 /* Reply syntax per RFC 2428: "229 blah blah (|||port|)" where '|'
781 * can be any character in ASCII from 33-126, obscurely. Verify
783 p = ap_strchr_c(reply, '(');
784 if (p == NULL || !p[1] || p[1] != p[2] || p[1] != p[3]
790 port = strtol(p + 4, &ep, 10);
791 if (errno || port < 1 || port > 65535 || ep[0] != p[1] || ep[1] != ')') {
795 return (apr_port_t)port;
799 * Generic "send FTP command to server" routine, using the control socket.
800 * Returns the FTP returncode (3 digit code)
801 * Allows for tracing the FTP protocol (in LogLevel debug)
804 proxy_ftp_command(const char *cmd, request_rec *r, conn_rec *ftp_ctrl,
805 apr_bucket_brigade *bb, char **pmessage)
809 char message[HUGE_STRING_LEN];
811 /* If cmd == NULL, we retrieve the next ftp response line */
813 conn_rec *c = r->connection;
814 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(cmd, strlen(cmd), r->pool, c->bucket_alloc));
815 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(c->bucket_alloc));
816 ap_pass_brigade(ftp_ctrl->output_filters, bb);
818 /* strip off the CRLF for logging */
819 apr_cpystrn(message, cmd, sizeof(message));
820 if ((crlf = strchr(message, '\r')) != NULL ||
821 (crlf = strchr(message, '\n')) != NULL)
823 if (strncmp(message,"PASS ", 5) == 0)
824 strcpy(&message[5], "****");
825 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;
982 apr_status_t uerr = APR_SUCCESS;
983 apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
984 char *buf, *connectname;
985 apr_port_t connectport;
986 char *ftpmessage = NULL;
987 char *path, *strp, *type_suffix, *cwd = NULL;
990 /* char *account = NULL; how to supply an account in a URL? */
991 const char *password = NULL;
995 char xfer_type = 'A'; /* after ftp login, the default is ASCII */
997 #if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
998 apr_time_t mtime = 0L;
1000 proxy_ftp_dir_conf *fdconf = ap_get_module_config(r->per_dir_config,
1003 /* stuff for PASV mode */
1004 int connect = 0, use_port = 0;
1005 char dates[APR_RFC822_DATE_LEN];
1007 apr_pool_t *address_pool;
1009 /* is this for us? */
1011 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1012 "declining URL %s - proxyhost %s specified:", url,
1014 return DECLINED; /* proxy connections are via HTTP */
1016 if (strncasecmp(url, "ftp:", 4)) {
1017 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1018 "declining URL %s - not ftp:", url);
1019 return DECLINED; /* only interested in FTP */
1021 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "serving URL %s", url);
1025 * I: Who Do I Connect To? -----------------------
1027 * Break up the URL to determine the host to connect to
1030 /* we only support GET and HEAD */
1031 if (r->method_number != M_GET)
1032 return HTTP_NOT_IMPLEMENTED;
1034 /* We break the URL into host, port, path-search */
1035 if (r->parsed_uri.hostname == NULL) {
1036 if (APR_SUCCESS != apr_uri_parse(p, url, &uri)) {
1037 return ap_proxyerror(r, HTTP_BAD_REQUEST,
1038 apr_psprintf(p, "URI cannot be parsed: %s", url));
1040 connectname = uri.hostname;
1041 connectport = uri.port;
1042 path = apr_pstrdup(p, uri.path);
1045 connectname = r->parsed_uri.hostname;
1046 connectport = r->parsed_uri.port;
1047 path = apr_pstrdup(p, r->parsed_uri.path);
1049 if (connectport == 0) {
1050 connectport = apr_uri_port_of_scheme("ftp");
1052 path = (path != NULL && path[0] != '\0') ? &path[1] : "";
1054 type_suffix = strchr(path, ';');
1055 if (type_suffix != NULL)
1056 *(type_suffix++) = '\0';
1058 if (type_suffix != NULL && strncmp(type_suffix, "type=", 5) == 0
1059 && apr_isalpha(type_suffix[5])) {
1060 /* "type=d" forces a dir listing.
1061 * The other types (i|a|e) are directly used for the ftp TYPE command
1063 if ( ! (dirlisting = (apr_tolower(type_suffix[5]) == 'd')))
1064 xfer_type = apr_toupper(type_suffix[5]);
1066 /* Check valid types, rather than ignoring invalid types silently: */
1067 if (strchr("AEI", xfer_type) == NULL)
1068 return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
1069 "ftp proxy supports only types 'a', 'i', or 'e': \"",
1070 type_suffix, "\" is invalid.", NULL));
1073 /* make binary transfers the default */
1079 * The "Authorization:" header must be checked first. We allow the user
1080 * to "override" the URL-coded user [ & password ] in the Browsers'
1081 * User&Password Dialog. NOTE that this is only marginally more secure
1082 * than having the password travel in plain as part of the URL, because
1083 * Basic Auth simply uuencodes the plain text password. But chances are
1084 * still smaller that the URL is logged regularly.
1086 if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
1087 && strcasecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
1088 && (password = ap_pbase64decode(r->pool, password))[0] != ':') {
1089 /* Check the decoded string for special characters. */
1090 if (!ftp_check_string(password)) {
1091 return ap_proxyerror(r, HTTP_BAD_REQUEST,
1092 "user credentials contained invalid character");
1095 * Note that this allocation has to be made from r->connection->pool
1096 * because it has the lifetime of the connection. The other
1097 * allocations are temporary and can be tossed away any time.
1099 user = ap_getword_nulls(r->connection->pool, &password, ':');
1100 r->ap_auth_type = "Basic";
1101 r->user = r->parsed_uri.user = user;
1103 else if ((user = r->parsed_uri.user) != NULL) {
1104 user = apr_pstrdup(p, user);
1106 if ((password = r->parsed_uri.password) != NULL) {
1107 char *tmp = apr_pstrdup(p, password);
1114 password = "apache-proxy@";
1117 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01036)
1118 "connecting %s to %s:%d", url, connectname, connectport);
1120 if (worker->s->is_address_reusable) {
1121 if (!worker->cp->addr) {
1122 if ((err = PROXY_THREAD_LOCK(worker->balancer)) != APR_SUCCESS) {
1123 ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(01037) "lock");
1124 return HTTP_INTERNAL_SERVER_ERROR;
1127 connect_addr = worker->cp->addr;
1128 address_pool = worker->cp->pool;
1131 address_pool = r->pool;
1133 /* do a DNS lookup for the destination host */
1135 err = apr_sockaddr_info_get(&(connect_addr),
1136 connectname, APR_UNSPEC,
1139 if (worker->s->is_address_reusable && !worker->cp->addr) {
1140 worker->cp->addr = connect_addr;
1141 if ((uerr = PROXY_THREAD_UNLOCK(worker->balancer)) != APR_SUCCESS) {
1142 ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(01038) "unlock");
1146 * get all the possible IP addresses for the destname and loop through
1147 * them until we get a successful connection
1149 if (APR_SUCCESS != err) {
1150 return ap_proxyerror(r, HTTP_GATEWAY_TIME_OUT, apr_pstrcat(p,
1151 "DNS lookup failure for: ",
1152 connectname, NULL));
1155 /* check if ProxyBlock directive on this host */
1156 if (OK != ap_proxy_checkproxyblock(r, conf, connectname, connect_addr)) {
1157 return ap_proxyerror(r, HTTP_FORBIDDEN,
1158 "Connect to remote machine blocked");
1161 /* create space for state information */
1162 backend = (proxy_conn_rec *) ap_get_module_config(c->conn_config, &proxy_ftp_module);
1164 status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server);
1168 ap_proxy_release_connection("FTP", backend, r->server);
1172 /* TODO: see if ftp could use determine_connection */
1173 backend->addr = connect_addr;
1174 ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
1179 * II: Make the Connection -----------------------
1181 * We have determined who to connect to. Now make the connection.
1185 if (ap_proxy_connect_backend("FTP", backend, worker, r->server)) {
1186 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01039)
1187 "an error occurred creating a new connection to %pI (%s)",
1188 connect_addr, connectname);
1189 proxy_ftp_cleanup(r, backend);
1190 return HTTP_SERVICE_UNAVAILABLE;
1193 if (!backend->connection) {
1194 status = ap_proxy_connection_create("FTP", backend, c, r->server);
1196 proxy_ftp_cleanup(r, backend);
1201 /* Use old naming */
1202 origin = backend->connection;
1203 sock = backend->sock;
1205 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
1206 "control connection complete");
1210 * III: Send Control Request -------------------------
1212 * Log into the ftp server, send the username & password, change to the
1213 * correct directory...
1217 /* possible results: */
1218 /* 120 Service ready in nnn minutes. */
1219 /* 220 Service ready for new user. */
1220 /* 421 Service not available, closing control connection. */
1221 rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
1223 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1224 "Error reading from remote server");
1226 else if (rc == 421) {
1227 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1228 "Error reading from remote server");
1230 else if (rc == 120) {
1232 * RFC2616 states: 14.37 Retry-After
1234 * The Retry-After response-header field can be used with a 503 (Service
1235 * Unavailable) response to indicate how long the service is expected
1236 * to be unavailable to the requesting client. [...] The value of
1237 * this field can be either an HTTP-date or an integer number of
1238 * seconds (in decimal) after the time of the response. Retry-After
1239 * = "Retry-After" ":" ( HTTP-date | delta-seconds )
1241 char *secs_str = ftpmessage;
1244 /* Look for a number, preceded by whitespace */
1246 if ((secs_str==ftpmessage || apr_isspace(secs_str[-1])) &&
1247 apr_isdigit(secs_str[0]))
1249 if (*secs_str != '\0') {
1250 secs = atol(secs_str);
1251 apr_table_addn(r->headers_out, "Retry-After",
1252 apr_psprintf(p, "%lu", (unsigned long)(60 * secs)));
1254 return ftp_proxyerror(r, backend, HTTP_SERVICE_UNAVAILABLE, ftpmessage);
1256 else if (rc != 220) {
1257 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1260 rc = proxy_ftp_command(apr_pstrcat(p, "USER ", user, CRLF, NULL),
1261 r, origin, bb, &ftpmessage);
1262 /* possible results; 230, 331, 332, 421, 500, 501, 530 */
1263 /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */
1264 /* 230 User logged in, proceed. */
1265 /* 331 User name okay, need password. */
1266 /* 332 Need account for login. */
1267 /* 421 Service not available, closing control connection. */
1268 /* 500 Syntax error, command unrecognized. */
1269 /* (This may include errors such as command line too long.) */
1270 /* 501 Syntax error in parameters or arguments. */
1271 /* 530 Not logged in. */
1273 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1274 "Error reading from remote server");
1276 else if (rc == 421) {
1277 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1278 "Error reading from remote server");
1280 else if (rc == 530) {
1281 proxy_ftp_cleanup(r, backend);
1282 return ftp_unauthorized(r, 1); /* log it: user name guessing
1285 else if (rc != 230 && rc != 331) {
1286 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1289 if (rc == 331) { /* send password */
1290 if (password == NULL) {
1291 proxy_ftp_cleanup(r, backend);
1292 return ftp_unauthorized(r, 0);
1295 rc = proxy_ftp_command(apr_pstrcat(p, "PASS ", password, CRLF, NULL),
1296 r, origin, bb, &ftpmessage);
1297 /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */
1298 /* 230 User logged in, proceed. */
1299 /* 332 Need account for login. */
1300 /* 421 Service not available, closing control connection. */
1301 /* 500 Syntax error, command unrecognized. */
1302 /* 501 Syntax error in parameters or arguments. */
1303 /* 503 Bad sequence of commands. */
1304 /* 530 Not logged in. */
1306 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1307 "Error reading from remote server");
1309 else if (rc == 421) {
1310 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1311 "Error reading from remote server");
1313 else if (rc == 332) {
1314 return ftp_proxyerror(r, backend, HTTP_UNAUTHORIZED,
1315 apr_pstrcat(p, "Need account for login: ", ftpmessage, NULL));
1317 /* @@@ questionable -- we might as well return a 403 Forbidden here */
1318 else if (rc == 530) {
1319 proxy_ftp_cleanup(r, backend);
1320 return ftp_unauthorized(r, 1); /* log it: passwd guessing
1323 else if (rc != 230 && rc != 202) {
1324 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1327 apr_table_set(r->notes, "Directory-README", ftpmessage);
1330 /* Special handling for leading "%2f": this enforces a "cwd /"
1331 * out of the $HOME directory which was the starting point after login
1333 if (strncasecmp(path, "%2f", 3) == 0) {
1335 while (*path == '/') /* skip leading '/' (after root %2f) */
1338 rc = proxy_ftp_command("CWD /" CRLF, r, origin, bb, &ftpmessage);
1340 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1341 "Error reading from remote server");
1343 else if (rc == 421) {
1344 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1345 "Error reading from remote server");
1350 * set the directory (walk directory component by component): this is
1351 * what we must do if we don't know the OS type of the remote machine
1354 strp = strchr(path, '/');
1359 decodeenc(path); /* Note! This decodes a %2f -> "/" */
1361 if (strchr(path, '/')) { /* are there now any '/' characters? */
1362 return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
1363 "Use of /%2f is only allowed at the base directory");
1366 /* NOTE: FTP servers do globbing on the path.
1367 * So we need to escape the URI metacharacters.
1368 * We use a special glob-escaping routine to escape globbing chars.
1369 * We could also have extended gen_test_char.c with a special T_ESCAPE_FTP_PATH
1371 rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1372 ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1373 r, origin, bb, &ftpmessage);
1375 /* responses: 250, 421, 500, 501, 502, 530, 550 */
1376 /* 250 Requested file action okay, completed. */
1377 /* 421 Service not available, closing control connection. */
1378 /* 500 Syntax error, command unrecognized. */
1379 /* 501 Syntax error in parameters or arguments. */
1380 /* 502 Command not implemented. */
1381 /* 530 Not logged in. */
1382 /* 550 Requested action not taken. */
1384 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1385 "Error reading from remote server");
1387 else if (rc == 421) {
1388 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1389 "Error reading from remote server");
1391 else if (rc == 550) {
1392 return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1394 else if (rc != 250) {
1395 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1402 * IV: Make Data Connection? -------------------------
1404 * Try EPSV, if that fails... try PASV, if that fails... try PORT.
1406 /* this temporarily switches off EPSV/PASV */
1409 /* set up data connection - EPSV */
1411 apr_port_t data_port;
1414 * The EPSV command replaces PASV where both IPV4 and IPV6 is
1415 * supported. Only the port is returned, the IP address is always the
1416 * same as that on the control connection. Example: Entering Extended
1417 * Passive Mode (|||6446|)
1419 rc = proxy_ftp_command("EPSV" CRLF,
1420 r, origin, bb, &ftpmessage);
1421 /* possible results: 227, 421, 500, 501, 502, 530 */
1422 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1423 /* 421 Service not available, closing control connection. */
1424 /* 500 Syntax error, command unrecognized. */
1425 /* 501 Syntax error in parameters or arguments. */
1426 /* 502 Command not implemented. */
1427 /* 530 Not logged in. */
1429 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1430 "Error reading from remote server");
1432 else if (rc == 421) {
1433 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1434 "Error reading from remote server");
1436 else if (rc != 229 && rc != 500 && rc != 501 && rc != 502) {
1437 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1439 else if (rc == 229) {
1440 /* Parse the port out of the EPSV reply. */
1441 data_port = parse_epsv_reply(ftpmessage);
1444 apr_sockaddr_t *remote_addr, epsv_addr;
1446 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
1447 "EPSV contacting remote host on port %d", data_port);
1449 /* Retrieve the client's address. */
1450 rv = apr_socket_addr_get(&remote_addr, APR_REMOTE, sock);
1451 if (rv == APR_SUCCESS) {
1452 /* Take a shallow copy of the server address to
1453 * modify; the _addr_get function gives back a
1454 * pointer to the socket's internal structure.
1455 * This is awkward given current APR network
1457 epsv_addr = *remote_addr;
1458 epsv_addr.port = data_port;
1460 if (epsv_addr.family == APR_INET6) {
1461 epsv_addr.sa.sin6.sin6_port = htons(data_port);
1466 epsv_addr.sa.sin.sin_port = htons(data_port);
1468 rv = apr_socket_create(&data_sock, epsv_addr.family, SOCK_STREAM, 0, r->pool);
1471 if (rv != APR_SUCCESS) {
1472 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01040)
1473 "could not establish socket for client data connection");
1474 proxy_ftp_cleanup(r, backend);
1475 return HTTP_INTERNAL_SERVER_ERROR;
1478 if (conf->recv_buffer_size > 0
1479 && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
1480 conf->recv_buffer_size))) {
1481 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01041)
1482 "apr_socket_opt_set(SO_RCVBUF): Failed to "
1483 "set ProxyReceiveBufferSize, using default");
1486 rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
1487 if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
1488 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01042)
1489 "apr_socket_opt_set(APR_TCP_NODELAY): "
1493 rv = apr_socket_connect(data_sock, &epsv_addr);
1494 if (rv != APR_SUCCESS) {
1495 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01043)
1496 "EPSV attempt to connect to %pI failed - "
1497 "Firewall/NAT?", &epsv_addr);
1498 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1499 apr_psprintf(r->pool,
1500 "EPSV attempt to connect to %pI failed - firewall/NAT?",
1504 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
1505 "connected data socket to %pI", &epsv_addr);
1512 /* set up data connection - PASV */
1514 rc = proxy_ftp_command("PASV" CRLF,
1515 r, origin, bb, &ftpmessage);
1516 /* possible results: 227, 421, 500, 501, 502, 530 */
1517 /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1518 /* 421 Service not available, closing control connection. */
1519 /* 500 Syntax error, command unrecognized. */
1520 /* 501 Syntax error in parameters or arguments. */
1521 /* 502 Command not implemented. */
1522 /* 530 Not logged in. */
1524 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1525 "Error reading from remote server");
1527 else if (rc == 421) {
1528 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1529 "Error reading from remote server");
1531 else if (rc != 227 && rc != 502) {
1532 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1534 else if (rc == 227) {
1535 unsigned int h0, h1, h2, h3, p0, p1;
1539 /* FIXME: Check PASV against RFC1123 */
1542 pstr = apr_strtok(pstr, " ", &tok_cntx); /* separate result code */
1544 if (*(pstr + strlen(pstr) + 1) == '=') {
1545 pstr += strlen(pstr) + 2;
1548 pstr = apr_strtok(NULL, "(", &tok_cntx); /* separate address &
1551 pstr = apr_strtok(NULL, ")", &tok_cntx);
1555 /* FIXME: Only supports IPV4 - fix in RFC2428 */
1557 if (pstr != NULL && (sscanf(pstr,
1558 "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) {
1560 apr_sockaddr_t *pasv_addr;
1561 apr_port_t pasvport = (p1 << 8) + p0;
1562 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01044)
1563 "PASV contacting host %d.%d.%d.%d:%d",
1564 h3, h2, h1, h0, pasvport);
1566 if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1567 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01045)
1568 "error creating PASV socket");
1569 proxy_ftp_cleanup(r, backend);
1570 return HTTP_INTERNAL_SERVER_ERROR;
1573 if (conf->recv_buffer_size > 0
1574 && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
1575 conf->recv_buffer_size))) {
1576 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01046)
1577 "apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
1580 rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
1581 if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
1582 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01047)
1583 "apr_socket_opt_set(APR_TCP_NODELAY): "
1587 /* make the connection */
1588 apr_sockaddr_info_get(&pasv_addr, apr_psprintf(p, "%d.%d.%d.%d", h3, h2, h1, h0), connect_addr->family, pasvport, 0, p);
1589 rv = apr_socket_connect(data_sock, pasv_addr);
1590 if (rv != APR_SUCCESS) {
1591 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01048)
1592 "PASV attempt to connect to %pI failed - Firewall/NAT?", pasv_addr);
1593 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1594 apr_psprintf(r->pool,
1595 "PASV attempt to connect to %pI failed - firewall/NAT?",
1606 /* set up data connection - PORT */
1608 apr_sockaddr_t *local_addr;
1610 apr_port_t local_port;
1611 unsigned int h0, h1, h2, h3, p0, p1;
1613 if ((rv = apr_socket_create(&local_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1614 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01049)
1615 "error creating local socket");
1616 proxy_ftp_cleanup(r, backend);
1617 return HTTP_INTERNAL_SERVER_ERROR;
1619 apr_socket_addr_get(&local_addr, APR_LOCAL, sock);
1620 local_port = local_addr->port;
1621 apr_sockaddr_ip_get(&local_ip, local_addr);
1623 if ((rv = apr_socket_opt_set(local_sock, APR_SO_REUSEADDR, one))
1625 #ifndef _OSD_POSIX /* BS2000 has this option "always on" */
1626 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01050)
1627 "error setting reuseaddr option");
1628 proxy_ftp_cleanup(r, backend);
1629 return HTTP_INTERNAL_SERVER_ERROR;
1630 #endif /* _OSD_POSIX */
1633 apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
1635 if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
1636 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01051)
1637 "error binding to ftp data socket %pI", local_addr);
1638 proxy_ftp_cleanup(r, backend);
1639 return HTTP_INTERNAL_SERVER_ERROR;
1642 /* only need a short queue */
1643 if ((rv = apr_socket_listen(local_sock, 2)) != APR_SUCCESS) {
1644 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01052)
1645 "error listening to ftp data socket %pI", local_addr);
1646 proxy_ftp_cleanup(r, backend);
1647 return HTTP_INTERNAL_SERVER_ERROR;
1650 /* FIXME: Sent PORT here */
1652 if (local_ip && (sscanf(local_ip,
1653 "%d.%d.%d.%d", &h3, &h2, &h1, &h0) == 4)) {
1654 p1 = (local_port >> 8);
1655 p0 = (local_port & 0xFF);
1657 rc = proxy_ftp_command(apr_psprintf(p, "PORT %d,%d,%d,%d,%d,%d" CRLF, h3, h2, h1, h0, p1, p0),
1658 r, origin, bb, &ftpmessage);
1659 /* possible results: 200, 421, 500, 501, 502, 530 */
1660 /* 200 Command okay. */
1661 /* 421 Service not available, closing control connection. */
1662 /* 500 Syntax error, command unrecognized. */
1663 /* 501 Syntax error in parameters or arguments. */
1664 /* 502 Command not implemented. */
1665 /* 530 Not logged in. */
1667 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1668 "Error reading from remote server");
1670 else if (rc == 421) {
1671 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1672 "Error reading from remote server");
1674 else if (rc != 200) {
1675 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1678 /* signal that we must use the EPRT/PORT loop */
1683 * The EPRT command replaces PORT where both IPV4 and IPV6 is supported. The first
1684 * number (1,2) indicates the protocol type. Examples:
1685 * EPRT |1|132.235.1.2|6275|
1686 * EPRT |2|1080::8:800:200C:417A|5282|
1688 return ftp_proxyerror(r, backend, HTTP_NOT_IMPLEMENTED,
1689 "Connect to IPV6 ftp server using EPRT not supported. Enable EPSV.");
1695 * V: Set The Headers -------------------
1697 * Get the size of the request, set up the environment for HTTP.
1700 /* set request; "path" holds last path component */
1701 len = decodeenc(path);
1703 if (strchr(path, '/')) { /* are there now any '/' characters? */
1704 return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
1705 "Use of /%2f is only allowed at the base directory");
1708 /* If len == 0 then it must be a directory (you can't RETR nothing)
1709 * Also, don't allow to RETR by wildcard. Instead, create a dirlisting,
1710 * unless ProxyFtpListOnWildcard is off.
1712 if (len == 0 || (ftp_check_globbingchars(path) && fdconf->ftp_list_on_wildcard)) {
1716 /* (from FreeBSD ftpd):
1717 * SIZE is not in RFC959, but Postel has blessed it and
1718 * it will be in the updated RFC.
1720 * Return size of file in a format suitable for
1721 * using with RESTART (we just count bytes).
1723 /* from draft-ietf-ftpext-mlst-14.txt:
1725 * change depending on the current STRUcture, MODE and TYPE of the data
1726 * connection, or a data connection which would be created were one
1727 * created now. Thus, the result of the SIZE command is dependent on
1728 * the currently established STRU, MODE and TYPE parameters.
1730 /* Therefore: switch to binary if the user did not specify ";type=a" */
1731 ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
1732 rc = proxy_ftp_command(apr_pstrcat(p, "SIZE ",
1733 ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1734 r, origin, bb, &ftpmessage);
1736 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1737 "Error reading from remote server");
1739 else if (rc == 421) {
1740 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1741 "Error reading from remote server");
1743 else if (rc == 213) {/* Size command ok */
1745 for (j = 0; apr_isdigit(ftpmessage[j]); j++)
1747 ftpmessage[j] = '\0';
1748 if (ftpmessage[0] != '\0')
1749 size = ftpmessage; /* already pstrdup'ed: no copy necessary */
1751 else if (rc == 550) { /* Not a regular file */
1752 ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
1753 "SIZE shows this is a directory");
1755 rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1756 ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1757 r, origin, bb, &ftpmessage);
1758 /* possible results: 250, 421, 500, 501, 502, 530, 550 */
1759 /* 250 Requested file action okay, completed. */
1760 /* 421 Service not available, closing control connection. */
1761 /* 500 Syntax error, command unrecognized. */
1762 /* 501 Syntax error in parameters or arguments. */
1763 /* 502 Command not implemented. */
1764 /* 530 Not logged in. */
1765 /* 550 Requested action not taken. */
1767 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1768 "Error reading from remote server");
1770 else if (rc == 421) {
1771 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1772 "Error reading from remote server");
1774 else if (rc == 550) {
1775 return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1777 else if (rc != 250) {
1778 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1785 cwd = ftp_get_PWD(r, origin, bb);
1787 apr_table_set(r->notes, "Directory-PWD", cwd);
1791 ftp_set_TYPE('A', r, origin, bb, NULL);
1792 /* If the current directory contains no slash, we are talking to
1793 * a non-unix ftp system. Try LIST instead of "LIST -lag", it
1794 * should return a long listing anyway (unlike NLST).
1795 * Some exotic FTP servers might choke on the "-lag" switch.
1797 /* Note that we do not escape the path here, to allow for
1798 * queries like: ftp://user@host/apache/src/server/http_*.c
1801 buf = apr_pstrcat(p, "LIST ", path, CRLF, NULL);
1802 else if (cwd == NULL || strchr(cwd, '/') != NULL)
1803 buf = "LIST -lag" CRLF;
1808 /* switch to binary if the user did not specify ";type=a" */
1809 ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
1810 #if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1811 /* from draft-ietf-ftpext-mlst-14.txt:
1812 * The FTP command, MODIFICATION TIME (MDTM), can be used to determine
1813 * when a file in the server NVFS was last modified. <..>
1814 * The syntax of a time value is:
1815 * time-val = 14DIGIT [ "." 1*DIGIT ] <..>
1816 * Symbolically, a time-val may be viewed as
1817 * YYYYMMDDHHMMSS.sss
1818 * The "." and subsequent digits ("sss") are optional. <..>
1819 * Time values are always represented in UTC (GMT)
1821 rc = proxy_ftp_command(apr_pstrcat(p, "MDTM ", ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1822 r, origin, bb, &ftpmessage);
1823 /* then extract the Last-Modified time from it (YYYYMMDDhhmmss or YYYYMMDDhhmmss.xxx GMT). */
1833 if (6 == sscanf(ftpmessage, "%4[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]",
1834 time_val.YYYY, time_val.MM, time_val.DD, time_val.hh, time_val.mm, time_val.ss)) {
1836 memset (&tms, '\0', sizeof tms);
1837 tms.tm_year = atoi(time_val.YYYY) - 1900;
1838 tms.tm_mon = atoi(time_val.MM) - 1;
1839 tms.tm_mday = atoi(time_val.DD);
1840 tms.tm_hour = atoi(time_val.hh);
1841 tms.tm_min = atoi(time_val.mm);
1842 tms.tm_sec = atoi(time_val.ss);
1843 #ifdef HAVE_TIMEGM /* Does system have timegm()? */
1844 mtime = timegm(&tms);
1845 mtime *= APR_USEC_PER_SEC;
1846 #elif HAVE_GMTOFF /* does struct tm have a member tm_gmtoff? */
1847 /* mktime will subtract the local timezone, which is not what we want.
1848 * Add it again because the MDTM string is GMT
1850 mtime = mktime(&tms);
1851 mtime += tms.tm_gmtoff;
1852 mtime *= APR_USEC_PER_SEC;
1858 #endif /* USE_MDTM */
1859 /* FIXME: Handle range requests - send REST */
1860 buf = apr_pstrcat(p, "RETR ", ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL);
1862 rc = proxy_ftp_command(buf, r, origin, bb, &ftpmessage);
1863 /* rc is an intermediate response for the LIST or RETR commands */
1866 * RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530,
1867 * 550 NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502,
1870 /* 110 Restart marker reply. */
1871 /* 125 Data connection already open; transfer starting. */
1872 /* 150 File status okay; about to open data connection. */
1873 /* 226 Closing data connection. */
1874 /* 250 Requested file action okay, completed. */
1875 /* 421 Service not available, closing control connection. */
1876 /* 425 Can't open data connection. */
1877 /* 426 Connection closed; transfer aborted. */
1878 /* 450 Requested file action not taken. */
1879 /* 451 Requested action aborted. Local error in processing. */
1880 /* 500 Syntax error, command unrecognized. */
1881 /* 501 Syntax error in parameters or arguments. */
1882 /* 530 Not logged in. */
1883 /* 550 Requested action not taken. */
1885 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1886 "Error reading from remote server");
1888 else if (rc == 421) {
1889 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1890 "Error reading from remote server");
1892 else if (rc == 550) {
1893 ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
1894 "RETR failed, trying LIST instead");
1896 /* Directory Listings should always be fetched in ASCII mode */
1898 ftp_set_TYPE('A', r, origin, bb, NULL);
1900 rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1901 ftp_escape_globbingchars(p, path, fdconf), CRLF, NULL),
1902 r, origin, bb, &ftpmessage);
1903 /* possible results: 250, 421, 500, 501, 502, 530, 550 */
1904 /* 250 Requested file action okay, completed. */
1905 /* 421 Service not available, closing control connection. */
1906 /* 500 Syntax error, command unrecognized. */
1907 /* 501 Syntax error in parameters or arguments. */
1908 /* 502 Command not implemented. */
1909 /* 530 Not logged in. */
1910 /* 550 Requested action not taken. */
1912 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1913 "Error reading from remote server");
1915 else if (rc == 421) {
1916 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1917 "Error reading from remote server");
1919 else if (rc == 550) {
1920 return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1922 else if (rc != 250) {
1923 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1926 /* Update current directory after CWD */
1927 cwd = ftp_get_PWD(r, origin, bb);
1929 apr_table_set(r->notes, "Directory-PWD", cwd);
1932 /* See above for the "LIST" vs. "LIST -lag" discussion. */
1933 rc = proxy_ftp_command((cwd == NULL || strchr(cwd, '/') != NULL)
1934 ? "LIST -lag" CRLF : "LIST" CRLF,
1935 r, origin, bb, &ftpmessage);
1937 /* rc is an intermediate response for the LIST command (125 transfer starting, 150 opening data connection) */
1939 return ftp_proxyerror(r, backend, HTTP_GATEWAY_TIME_OUT,
1940 "Error reading from remote server");
1942 else if (rc == 421) {
1943 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1944 "Error reading from remote server");
1947 if (rc != 125 && rc != 150 && rc != 226 && rc != 250) {
1948 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1951 r->status = HTTP_OK;
1952 r->status_line = "200 OK";
1954 apr_rfc822_date(dates, r->request_time);
1955 apr_table_setn(r->headers_out, "Date", dates);
1956 apr_table_setn(r->headers_out, "Server", ap_get_server_description());
1958 /* set content-type */
1960 ap_set_content_type(r, apr_pstrcat(p, "text/html;charset=",
1961 fdconf->ftp_directory_charset ?
1962 fdconf->ftp_directory_charset :
1963 "ISO-8859-1", NULL));
1966 if (xfer_type != 'A' && size != NULL) {
1967 /* We "trust" the ftp server to really serve (size) bytes... */
1968 apr_table_setn(r->headers_out, "Content-Length", size);
1969 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1970 "Content-Length set to %s", size);
1973 if (r->content_type) {
1974 apr_table_setn(r->headers_out, "Content-Type", r->content_type);
1975 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1976 "Content-Type set to %s", r->content_type);
1979 #if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1981 char datestr[APR_RFC822_DATE_LEN];
1982 apr_rfc822_date(datestr, mtime);
1983 apr_table_set(r->headers_out, "Last-Modified", datestr);
1984 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1985 "Last-Modified set to %s", datestr);
1987 #endif /* USE_MDTM */
1989 /* If an encoding has been set by mistake, delete it.
1990 * @@@ FIXME (e.g., for ftp://user@host/file*.tar.gz,
1991 * @@@ the encoding is currently set to x-gzip)
1993 if (dirlisting && r->content_encoding != NULL)
1994 r->content_encoding = NULL;
1996 /* set content-encoding (not for dir listings, they are uncompressed)*/
1997 if (r->content_encoding != NULL && r->content_encoding[0] != '\0') {
1998 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
1999 "Content-Encoding set to %s", r->content_encoding);
2000 apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
2003 /* wait for connection */
2006 rv = apr_socket_accept(&data_sock, local_sock, r->pool);
2007 if (APR_STATUS_IS_EINTR(rv)) {
2010 else if (rv == APR_SUCCESS) {
2014 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01053)
2015 "failed to accept data connection");
2016 proxy_ftp_cleanup(r, backend);
2017 return HTTP_GATEWAY_TIME_OUT;
2022 /* the transfer socket is now open, create a new connection */
2023 data = ap_run_create_connection(p, r->server, data_sock, r->connection->id,
2024 r->connection->sbh, c->bucket_alloc);
2027 * the peer reset the connection already; ap_run_create_connection() closed
2030 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01054)
2031 "an error occurred creating the transfer connection");
2032 proxy_ftp_cleanup(r, backend);
2033 return HTTP_INTERNAL_SERVER_ERROR;
2037 * We do not do SSL over the data connection, even if the virtual host we
2038 * are in might have SSL enabled
2040 ap_proxy_ssl_disable(data);
2041 /* set up the connection filters */
2042 rc = ap_run_pre_connection(data, data_sock);
2043 if (rc != OK && rc != DONE) {
2044 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01055)
2045 "pre_connection setup failed (%d)", rc);
2047 proxy_ftp_cleanup(r, backend);
2052 * VI: Receive the Response ------------------------
2054 * Get response from the remote ftp socket, and pass it up the filter chain.
2061 /* insert directory filter */
2062 ap_add_output_filter("PROXY_SEND_DIR", NULL, r, r->connection);
2066 if (!r->header_only) {
2070 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "start body send");
2072 /* read the body, pass it to the output filters */
2073 while (ap_get_brigade(data->input_filters,
2077 conf->io_buffer_size) == APR_SUCCESS) {
2080 apr_off_t readbytes;
2081 apr_brigade_length(bb, 0, &readbytes);
2082 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(01056)
2083 "proxy: readbytes: %#x", readbytes);
2087 if (APR_BRIGADE_EMPTY(bb)) {
2091 /* found the last brigade? */
2092 if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
2093 /* if this is the last brigade, cleanup the
2094 * backend connection first to prevent the
2095 * backend server from hanging around waiting
2096 * for a slow client to eat these bytes
2098 ap_flush_conn(data);
2100 apr_socket_close(data_sock);
2103 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01057)
2104 "data connection closed");
2105 /* signal that we must leave */
2109 /* if no EOS yet, then we must flush */
2110 if (FALSE == finish) {
2111 e = apr_bucket_flush_create(c->bucket_alloc);
2112 APR_BRIGADE_INSERT_TAIL(bb, e);
2115 /* try send what we read */
2116 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
2118 /* Ack! Phbtt! Die! User aborted! */
2122 /* make sure we always clean up after ourselves */
2123 apr_brigade_cleanup(bb);
2125 /* if we are done, leave */
2126 if (TRUE == finish) {
2130 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "end body send");
2134 ap_flush_conn(data);
2135 apr_socket_close(data_sock);
2136 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01058) "data connection closed");
2139 /* Retrieve the final response for the RETR or LIST commands */
2140 proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
2141 apr_brigade_cleanup(bb);
2144 * VII: Clean Up -------------
2146 * If there are no KeepAlives, or if the connection has been signalled to
2147 * close, close the socket and clean up
2151 proxy_ftp_command("QUIT" CRLF, r, origin, bb, &ftpmessage);
2152 /* responses: 221, 500 */
2153 /* 221 Service closing control connection. */
2154 /* 500 Syntax error, command unrecognized. */
2155 ap_flush_conn(origin);
2156 proxy_ftp_cleanup(r, backend);
2158 apr_brigade_destroy(bb);
2162 static void ap_proxy_ftp_register_hook(apr_pool_t *p)
2165 proxy_hook_scheme_handler(proxy_ftp_handler, NULL, NULL, APR_HOOK_MIDDLE);
2166 proxy_hook_canon_handler(proxy_ftp_canon, NULL, NULL, APR_HOOK_MIDDLE);
2168 ap_register_output_filter("PROXY_SEND_DIR", proxy_send_dir_filter,
2169 NULL, AP_FTYPE_RESOURCE);
2170 /* Compile the output format of "ls -s1" as a fallback for non-unix ftp listings */
2171 ls_regex = ap_pregcomp(p, LS_REG_PATTERN, AP_REG_EXTENDED);
2172 ap_assert(ls_regex != NULL);
2175 static const command_rec proxy_ftp_cmds[] =
2177 AP_INIT_FLAG("ProxyFtpListOnWildcard", set_ftp_list_on_wildcard, NULL,
2178 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."),
2179 AP_INIT_FLAG("ProxyFtpEscapeWildcards", set_ftp_escape_wildcards, NULL,
2180 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."),
2181 AP_INIT_TAKE1("ProxyFtpDirCharset", set_ftp_directory_charset, NULL,
2182 RSRC_CONF|ACCESS_CONF, "Define the character set for proxied FTP listings"),
2187 AP_DECLARE_MODULE(proxy_ftp) = {
2188 STANDARD20_MODULE_STUFF,
2189 create_proxy_ftp_dir_config,/* create per-directory config structure */
2190 merge_proxy_ftp_dir_config, /* merge per-directory config structures */
2191 NULL, /* create per-server config structure */
2192 NULL, /* merge per-server config structures */
2193 proxy_ftp_cmds, /* command apr_table_t */
2194 ap_proxy_ftp_register_hook /* register hooks */