]> granicus.if.org Git - apache/blob - modules/proxy/mod_proxy_ftp.c
* Construct a site in the reverse proxy case when there is non.
[apache] / modules / proxy / mod_proxy_ftp.c
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /* FTP routines for Apache proxy */
18
19 #include "mod_proxy.h"
20 #if APR_HAVE_TIME_H
21 #include <time.h>
22 #endif
23 #include "apr_version.h"
24
25 #if (APR_MAJOR_VERSION < 1)
26 #undef apr_socket_create
27 #define apr_socket_create apr_socket_create_ex
28 #endif
29
30 #define AUTODETECT_PWD
31 /* Automatic timestamping (Last-Modified header) based on MDTM is used if:
32  * 1) the FTP server supports the MDTM command and
33  * 2) HAVE_TIMEGM (preferred) or HAVE_GMTOFF is available at compile time
34  */
35 #define USE_MDTM
36
37
38 module AP_MODULE_DECLARE_DATA proxy_ftp_module;
39
40 /*
41  * Decodes a '%' escaped string, and returns the number of characters
42  */
43 static int decodeenc(char *x)
44 {
45     int i, j, ch;
46
47     if (x[0] == '\0')
48         return 0;               /* special case for no characters */
49     for (i = 0, j = 0; x[i] != '\0'; i++, j++) {
50         /* decode it if not already done */
51         ch = x[i];
52         if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
53             ch = ap_proxy_hex2c(&x[i + 1]);
54             i += 2;
55         }
56         x[j] = ch;
57     }
58     x[j] = '\0';
59     return j;
60 }
61
62 /*
63  * Escape the globbing characters in a path used as argument to
64  * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
65  * ftpd assumes '\\' as a quoting character to escape special characters.
66  * Returns: escaped string
67  */
68 #define FTP_GLOBBING_CHARS "*?[{~"
69 static char *ftp_escape_globbingchars(apr_pool_t *p, const char *path)
70 {
71     char *ret = apr_palloc(p, 2*strlen(path)+sizeof(""));
72     char *d;
73     for (d = ret; *path; ++path) {
74         if (strchr(FTP_GLOBBING_CHARS, *path) != NULL)
75             *d++ = '\\';
76         *d++ = *path;
77     }
78     *d = '\0';
79     return ret;
80 }
81
82 /*
83  * Check for globbing characters in a path used as argument to
84  * the FTP commands (SIZE, CWD, RETR, MDTM, ...).
85  * ftpd assumes '\\' as a quoting character to escape special characters.
86  * Returns: 0 (no globbing chars, or all globbing chars escaped), 1 (globbing chars)
87  */
88 static int ftp_check_globbingchars(const char *path)
89 {
90     for ( ; *path; ++path) {
91         if (*path == '\\')
92         ++path;
93         if (*path != '\0' && strchr(FTP_GLOBBING_CHARS, *path) != NULL)
94             return TRUE;
95     }
96     return FALSE;
97 }
98
99 /*
100  * checks an encoded ftp string for bad characters, namely, CR, LF or
101  * non-ascii character
102  */
103 static int ftp_check_string(const char *x)
104 {
105     int i, ch = 0;
106 #if APR_CHARSET_EBCDIC
107     char buf[1];
108 #endif
109
110     for (i = 0; x[i] != '\0'; i++) {
111         ch = x[i];
112         if (ch == '%' && apr_isxdigit(x[i + 1]) && apr_isxdigit(x[i + 2])) {
113             ch = ap_proxy_hex2c(&x[i + 1]);
114             i += 2;
115         }
116 #if !APR_CHARSET_EBCDIC
117         if (ch == '\015' || ch == '\012' || (ch & 0x80))
118 #else                           /* APR_CHARSET_EBCDIC */
119         if (ch == '\r' || ch == '\n')
120             return 0;
121         buf[0] = ch;
122         ap_xlate_proto_to_ascii(buf, 1);
123         if (buf[0] & 0x80)
124 #endif                          /* APR_CHARSET_EBCDIC */
125             return 0;
126     }
127     return 1;
128 }
129
130 /*
131  * Canonicalise ftp URLs.
132  */
133 static int proxy_ftp_canon(request_rec *r, char *url)
134 {
135     char *user, *password, *host, *path, *parms, *strp, sport[7];
136     apr_pool_t *p = r->pool;
137     const char *err;
138     apr_port_t port, def_port;
139
140     /* */
141     if (strncasecmp(url, "ftp:", 4) == 0) {
142         url += 4;
143     }
144     else {
145         return DECLINED;
146     }
147     def_port = apr_uri_port_of_scheme("ftp");
148
149     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
150                  "proxy: FTP: canonicalising URL %s", url);
151
152     port = def_port;
153     err = ap_proxy_canon_netloc(p, &url, &user, &password, &host, &port);
154     if (err)
155         return HTTP_BAD_REQUEST;
156     if (user != NULL && !ftp_check_string(user))
157         return HTTP_BAD_REQUEST;
158     if (password != NULL && !ftp_check_string(password))
159         return HTTP_BAD_REQUEST;
160
161     /* now parse path/parameters args, according to rfc1738 */
162     /*
163      * N.B. if this isn't a true proxy request, then the URL path (but not
164      * query args) has already been decoded. This gives rise to the problem
165      * of a ; being decoded into the path.
166      */
167     strp = strchr(url, ';');
168     if (strp != NULL) {
169         *(strp++) = '\0';
170         parms = ap_proxy_canonenc(p, strp, strlen(strp), enc_parm, 0,
171                                   r->proxyreq);
172         if (parms == NULL)
173             return HTTP_BAD_REQUEST;
174     }
175     else
176         parms = "";
177
178     path = ap_proxy_canonenc(p, url, strlen(url), enc_path, 0, r->proxyreq);
179     if (path == NULL)
180         return HTTP_BAD_REQUEST;
181     if (!ftp_check_string(path))
182         return HTTP_BAD_REQUEST;
183
184     if (r->proxyreq && r->args != NULL) {
185         if (strp != NULL) {
186             strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_parm, 1, r->proxyreq);
187             if (strp == NULL)
188                 return HTTP_BAD_REQUEST;
189             parms = apr_pstrcat(p, parms, "?", strp, NULL);
190         }
191         else {
192             strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_fpath, 1, r->proxyreq);
193             if (strp == NULL)
194                 return HTTP_BAD_REQUEST;
195             path = apr_pstrcat(p, path, "?", strp, NULL);
196         }
197         r->args = NULL;
198     }
199
200 /* now, rebuild URL */
201
202     if (port != def_port)
203         apr_snprintf(sport, sizeof(sport), ":%d", port);
204     else
205         sport[0] = '\0';
206
207     if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
208         host = apr_pstrcat(p, "[", host, "]", NULL);
209     }
210     r->filename = apr_pstrcat(p, "proxy:ftp://", (user != NULL) ? user : "",
211                               (password != NULL) ? ":" : "",
212                               (password != NULL) ? password : "",
213                           (user != NULL) ? "@" : "", host, sport, "/", path,
214                               (parms[0] != '\0') ? ";" : "", parms, NULL);
215
216     return OK;
217 }
218
219 /* we chop lines longer than 80 characters */
220 #define MAX_LINE_LEN 80
221
222 /*
223  * Reads response lines, returns both the ftp status code and
224  * remembers the response message in the supplied buffer
225  */
226 static int ftp_getrc_msg(conn_rec *ftp_ctrl, apr_bucket_brigade *bb, char *msgbuf, int msglen)
227 {
228     int status;
229     char response[MAX_LINE_LEN];
230     char buff[5];
231     char *mb = msgbuf, *me = &msgbuf[msglen];
232     apr_status_t rv;
233     int eos;
234
235     if (APR_SUCCESS != (rv = ap_proxy_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
236         return -1;
237     }
238 /*
239     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
240                  "proxy: <FTP: %s", response);
241 */
242     if (!apr_isdigit(response[0]) || !apr_isdigit(response[1]) ||
243     !apr_isdigit(response[2]) || (response[3] != ' ' && response[3] != '-'))
244         status = 0;
245     else
246         status = 100 * response[0] + 10 * response[1] + response[2] - 111 * '0';
247
248     mb = apr_cpystrn(mb, response + 4, me - mb);
249
250     if (response[3] == '-') {
251         memcpy(buff, response, 3);
252         buff[3] = ' ';
253         do {
254             if (APR_SUCCESS != (rv = ap_proxy_string_read(ftp_ctrl, bb, response, sizeof(response), &eos))) {
255                 return -1;
256             }
257             mb = apr_cpystrn(mb, response + (' ' == response[0] ? 1 : 4), me - mb);
258         } while (memcmp(response, buff, 4) != 0);
259     }
260
261     return status;
262 }
263
264 /* this is a filter that turns a raw ASCII directory listing into pretty HTML */
265
266 /* ideally, mod_proxy should simply send the raw directory list up the filter
267  * stack to mod_autoindex, which in theory should turn the raw ascii into
268  * pretty html along with all the bells and whistles it provides...
269  *
270  * all in good time...! :)
271  */
272
273 typedef struct {
274     apr_bucket_brigade *in;
275     char buffer[MAX_STRING_LEN];
276     enum {
277         HEADER, BODY, FOOTER
278     }    state;
279 }      proxy_dir_ctx_t;
280
281 /* fallback regex for ls -s1;  ($0..$2) == 3 */
282 #define LS_REG_PATTERN "^ *([0-9]+) +([^ ]+)$"
283 #define LS_REG_MATCH   3
284
285 static apr_status_t proxy_send_dir_filter(ap_filter_t *f,
286                                           apr_bucket_brigade *in)
287 {
288     request_rec *r = f->r;
289     conn_rec *c = r->connection;
290     apr_pool_t *p = r->pool;
291     apr_bucket_brigade *out = apr_brigade_create(p, c->bucket_alloc);
292     apr_status_t rv;
293
294     register int n;
295     char *dir, *path, *reldir, *site, *str, *type;
296
297     const char *pwd = apr_table_get(r->notes, "Directory-PWD");
298     const char *readme = apr_table_get(r->notes, "Directory-README");
299
300     proxy_dir_ctx_t *ctx = f->ctx;
301
302     if (!ctx) {
303         f->ctx = ctx = apr_pcalloc(p, sizeof(*ctx));
304         ctx->in = apr_brigade_create(p, c->bucket_alloc);
305         ctx->buffer[0] = 0;
306         ctx->state = HEADER;
307     }
308
309     /* combine the stored and the new */
310     APR_BRIGADE_CONCAT(ctx->in, in);
311
312     if (HEADER == ctx->state) {
313
314         /* basedir is either "", or "/%2f" for the "squid %2f hack" */
315         const char *basedir = "";  /* By default, path is relative to the $HOME dir */
316         char *wildcard = NULL;
317         const char *escpath;
318
319         /* Save "scheme://site" prefix without password */
320         site = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO);
321         /*
322          * In the reverse proxy case we usually have no site. So contruct
323          * one.
324          */
325         if ((*site == '\0') && (r->proxyreq == PROXYREQ_REVERSE)) {
326             site = ap_construct_url(p, "", r);
327         }
328         /* ... and path without query args */
329         path = apr_uri_unparse(p, &f->r->parsed_uri, APR_URI_UNP_OMITSITEPART | APR_URI_UNP_OMITQUERY);
330
331         /* If path began with /%2f, change the basedir */
332         if (strncasecmp(path, "/%2f", 4) == 0) {
333             basedir = "/%2f";
334         }
335
336         /* Strip off a type qualifier. It is ignored for dir listings */
337         if ((type = strstr(path, ";type=")) != NULL)
338             *type++ = '\0';
339
340         (void)decodeenc(path);
341
342         while (path[1] == '/') /* collapse multiple leading slashes to one */
343             ++path;
344
345         reldir = strrchr(path, '/');
346         if (reldir != NULL && ftp_check_globbingchars(reldir)) {
347             wildcard = &reldir[1];
348             reldir[0] = '\0'; /* strip off the wildcard suffix */
349         }
350
351         /* Copy path, strip (all except the last) trailing slashes */
352         /* (the trailing slash is needed for the dir component loop below) */
353         path = dir = apr_pstrcat(p, path, "/", NULL);
354         for (n = strlen(path); n > 1 && path[n - 1] == '/' && path[n - 2] == '/'; --n)
355             path[n - 1] = '\0';
356
357         /* Add a link to the root directory (if %2f hack was used) */
358         str = (basedir[0] != '\0') ? "<a href=\"/%2f/\">%2f</a>/" : "";
359
360         /* print "ftp://host/" */
361         escpath = ap_escape_html(p, path);
362         str = apr_psprintf(p, DOCTYPE_HTML_3_2
363                 "<html>\n <head>\n  <title>%s%s%s</title>\n"
364                 "<base href=\"%s%s%s\">\n"
365                 " </head>\n"
366                 " <body>\n  <h2>Directory of "
367                 "<a href=\"/\">%s</a>/%s",
368                 site, basedir, escpath, site, basedir, escpath, site, str);
369
370         APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
371                                                           p, c->bucket_alloc));
372
373         for (dir = path+1; (dir = strchr(dir, '/')) != NULL; )
374         {
375             *dir = '\0';
376             if ((reldir = strrchr(path+1, '/'))==NULL) {
377                 reldir = path+1;
378             }
379             else
380                 ++reldir;
381             /* print "path/" component */
382             str = apr_psprintf(p, "<a href=\"%s%s/\">%s</a>/", basedir,
383                         ap_escape_uri(p, path),
384                         ap_escape_html(p, reldir));
385             *dir = '/';
386             while (*dir == '/')
387               ++dir;
388             APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
389                                                            strlen(str), p,
390                                                            c->bucket_alloc));
391         }
392         if (wildcard != NULL) {
393             APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(wildcard,
394                                                            strlen(wildcard), p,
395                                                            c->bucket_alloc));
396         }
397
398         /* If the caller has determined the current directory, and it differs */
399         /* from what the client requested, then show the real name */
400         if (pwd == NULL || strncmp(pwd, path, strlen(pwd)) == 0) {
401             str = apr_psprintf(p, "</h2>\n\n  <hr />\n\n<pre>");
402         }
403         else {
404             str = apr_psprintf(p, "</h2>\n\n(%s)\n\n  <hr />\n\n<pre>",
405                                ap_escape_html(p, pwd));
406         }
407         APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str),
408                                                            p, c->bucket_alloc));
409
410         /* print README */
411         if (readme) {
412             str = apr_psprintf(p, "%s\n</pre>\n\n<hr />\n\n<pre>\n",
413                                ap_escape_html(p, readme));
414
415             APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str,
416                                                            strlen(str), p,
417                                                            c->bucket_alloc));
418         }
419
420         /* make sure page intro gets sent out */
421         APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
422         if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
423             return rv;
424         }
425         apr_brigade_cleanup(out);
426
427         ctx->state = BODY;
428     }
429
430     /* loop through each line of directory */
431     while (BODY == ctx->state) {
432         char *filename;
433         int found = 0;
434         int eos = 0;
435
436         ap_regex_t *re = NULL;
437         ap_regmatch_t re_result[LS_REG_MATCH];
438
439         /* Compile the output format of "ls -s1" as a fallback for non-unix ftp listings */
440         re = ap_pregcomp(p, LS_REG_PATTERN, AP_REG_EXTENDED);
441         ap_assert(re != NULL);
442
443         /* get a complete line */
444         /* if the buffer overruns - throw data away */
445         while (!found && !APR_BRIGADE_EMPTY(ctx->in)) {
446             char *pos, *response;
447             apr_size_t len, max;
448             apr_bucket *e;
449
450             e = APR_BRIGADE_FIRST(ctx->in);
451             if (APR_BUCKET_IS_EOS(e)) {
452                 eos = 1;
453                 break;
454             }
455             if (APR_SUCCESS != (rv = apr_bucket_read(e, (const char **)&response, &len, APR_BLOCK_READ))) {
456                 return rv;
457             }
458             pos = memchr(response, APR_ASCII_LF, len);
459             if (pos != NULL) {
460                 if ((response + len) != (pos + 1)) {
461                     len = pos - response + 1;
462                     apr_bucket_split(e, pos - response + 1);
463                 }
464                 found = 1;
465             }
466             max = sizeof(ctx->buffer) - strlen(ctx->buffer) - 1;
467             if (len > max) {
468                 len = max;
469             }
470
471             /* len+1 to leave space for the trailing nil char */
472             apr_cpystrn(ctx->buffer+strlen(ctx->buffer), response, len+1);
473
474             APR_BUCKET_REMOVE(e);
475             apr_bucket_destroy(e);
476         }
477
478         /* EOS? jump to footer */
479         if (eos) {
480             ctx->state = FOOTER;
481             break;
482         }
483
484         /* not complete? leave and try get some more */
485         if (!found) {
486             return APR_SUCCESS;
487         }
488
489         {
490             apr_size_t n = strlen(ctx->buffer);
491             if (ctx->buffer[n-1] == CRLF[1])  /* strip trailing '\n' */
492                 ctx->buffer[--n] = '\0';
493             if (ctx->buffer[n-1] == CRLF[0])  /* strip trailing '\r' if present */
494                 ctx->buffer[--n] = '\0';
495         }
496
497         /* a symlink? */
498         if (ctx->buffer[0] == 'l' && (filename = strstr(ctx->buffer, " -> ")) != NULL) {
499             char *link_ptr = filename;
500
501             do {
502                 filename--;
503             } while (filename[0] != ' ' && filename > ctx->buffer);
504             if (filename > ctx->buffer)
505                 *(filename++) = '\0';
506             *(link_ptr++) = '\0';
507             str = apr_psprintf(p, "%s <a href=\"%s\">%s %s</a>\n",
508                                ap_escape_html(p, ctx->buffer),
509                                ap_escape_uri(p, filename),
510                                ap_escape_html(p, filename),
511                                ap_escape_html(p, link_ptr));
512         }
513
514         /* a directory/file? */
515         else if (ctx->buffer[0] == 'd' || ctx->buffer[0] == '-' || ctx->buffer[0] == 'l' || apr_isdigit(ctx->buffer[0])) {
516             int searchidx = 0;
517             char *searchptr = NULL;
518             int firstfile = 1;
519             if (apr_isdigit(ctx->buffer[0])) {  /* handle DOS dir */
520                 searchptr = strchr(ctx->buffer, '<');
521                 if (searchptr != NULL)
522                     *searchptr = '[';
523                 searchptr = strchr(ctx->buffer, '>');
524                 if (searchptr != NULL)
525                     *searchptr = ']';
526             }
527
528             filename = strrchr(ctx->buffer, ' ');
529             if (filename == NULL) {
530                 /* Line is broken.  Ignore it. */
531                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
532                              "proxy_ftp: could not parse line %s", ctx->buffer);
533                 /* erase buffer for next time around */
534                 ctx->buffer[0] = 0;
535                 continue;  /* while state is BODY */
536             }
537             *(filename++) = '\0';
538
539             /* handle filenames with spaces in 'em */
540             if (!strcmp(filename, ".") || !strcmp(filename, "..") || firstfile) {
541                 firstfile = 0;
542                 searchidx = filename - ctx->buffer;
543             }
544             else if (searchidx != 0 && ctx->buffer[searchidx] != 0) {
545                 *(--filename) = ' ';
546                 ctx->buffer[searchidx - 1] = '\0';
547                 filename = &ctx->buffer[searchidx];
548             }
549
550             /* Append a slash to the HREF link for directories */
551             if (!strcmp(filename, ".") || !strcmp(filename, "..") || ctx->buffer[0] == 'd') {
552                 str = apr_psprintf(p, "%s <a href=\"%s/\">%s</a>\n",
553                                    ap_escape_html(p, ctx->buffer),
554                                    ap_escape_uri(p, filename),
555                                    ap_escape_html(p, filename));
556             }
557             else {
558                 str = apr_psprintf(p, "%s <a href=\"%s\">%s</a>\n",
559                                    ap_escape_html(p, ctx->buffer),
560                                    ap_escape_uri(p, filename),
561                                    ap_escape_html(p, filename));
562             }
563         }
564         /* Try a fallback for listings in the format of "ls -s1" */
565         else if (0 == ap_regexec(re, ctx->buffer, LS_REG_MATCH, re_result, 0)) {
566
567             filename = apr_pstrndup(p, &ctx->buffer[re_result[2].rm_so], re_result[2].rm_eo - re_result[2].rm_so);
568
569             str = apr_pstrcat(p, ap_escape_html(p, apr_pstrndup(p, ctx->buffer, re_result[2].rm_so)),
570                               "<a href=\"", ap_escape_uri(p, filename), "\">",
571                               ap_escape_html(p, filename), "</a>\n", NULL);
572         }
573         else {
574             strcat(ctx->buffer, "\n"); /* re-append the newline */
575             str = ap_escape_html(p, ctx->buffer);
576         }
577
578         /* erase buffer for next time around */
579         ctx->buffer[0] = 0;
580
581         APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
582                                                             c->bucket_alloc));
583         APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
584         if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
585             return rv;
586         }
587         apr_brigade_cleanup(out);
588
589     }
590
591     if (FOOTER == ctx->state) {
592         str = apr_psprintf(p, "</pre>\n\n  <hr />\n\n  %s\n\n </body>\n</html>\n", ap_psignature("", r));
593         APR_BRIGADE_INSERT_TAIL(out, apr_bucket_pool_create(str, strlen(str), p,
594                                                             c->bucket_alloc));
595         APR_BRIGADE_INSERT_TAIL(out, apr_bucket_flush_create(c->bucket_alloc));
596         APR_BRIGADE_INSERT_TAIL(out, apr_bucket_eos_create(c->bucket_alloc));
597         if (APR_SUCCESS != (rv = ap_pass_brigade(f->next, out))) {
598             return rv;
599         }
600         apr_brigade_destroy(out);
601     }
602
603     return APR_SUCCESS;
604 }
605
606 /*
607  * Generic "send FTP command to server" routine, using the control socket.
608  * Returns the FTP returncode (3 digit code)
609  * Allows for tracing the FTP protocol (in LogLevel debug)
610  */
611 static int
612 proxy_ftp_command(const char *cmd, request_rec *r, conn_rec *ftp_ctrl,
613                   apr_bucket_brigade *bb, char **pmessage)
614 {
615     char *crlf;
616     int rc;
617     char message[HUGE_STRING_LEN];
618
619     /* If cmd == NULL, we retrieve the next ftp response line */
620     if (cmd != NULL) {
621         conn_rec *c = r->connection;
622         APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(cmd, strlen(cmd), r->pool, c->bucket_alloc));
623         APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(c->bucket_alloc));
624         ap_pass_brigade(ftp_ctrl->output_filters, bb);
625
626         /* strip off the CRLF for logging */
627         apr_cpystrn(message, cmd, sizeof(message));
628         if ((crlf = strchr(message, '\r')) != NULL ||
629             (crlf = strchr(message, '\n')) != NULL)
630             *crlf = '\0';
631         if (strncmp(message,"PASS ", 5) == 0)
632             strcpy(&message[5], "****");
633         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
634                      "proxy:>FTP: %s", message);
635     }
636
637     rc = ftp_getrc_msg(ftp_ctrl, bb, message, sizeof message);
638     if (rc == -1 || rc == 421)
639         strcpy(message,"<unable to read result>");
640     if ((crlf = strchr(message, '\r')) != NULL ||
641         (crlf = strchr(message, '\n')) != NULL)
642         *crlf = '\0';
643     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
644                  "proxy:<FTP: %3.3u %s", rc, message);
645
646     if (pmessage != NULL)
647         *pmessage = apr_pstrdup(r->pool, message);
648
649     return rc;
650 }
651
652 /* Set ftp server to TYPE {A,I,E} before transfer of a directory or file */
653 static int ftp_set_TYPE(char xfer_type, request_rec *r, conn_rec *ftp_ctrl,
654                   apr_bucket_brigade *bb, char **pmessage)
655 {
656     char old_type[2] = { 'A', '\0' }; /* After logon, mode is ASCII */
657     int ret = HTTP_OK;
658     int rc;
659
660     /* set desired type */
661     old_type[0] = xfer_type;
662
663     rc = proxy_ftp_command(apr_pstrcat(r->pool, "TYPE ", old_type, CRLF, NULL),
664                            r, ftp_ctrl, bb, pmessage);
665 /* responses: 200, 421, 500, 501, 504, 530 */
666     /* 200 Command okay. */
667     /* 421 Service not available, closing control connection. */
668     /* 500 Syntax error, command unrecognized. */
669     /* 501 Syntax error in parameters or arguments. */
670     /* 504 Command not implemented for that parameter. */
671     /* 530 Not logged in. */
672     if (rc == -1 || rc == 421) {
673         ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
674                              "Error reading from remote server");
675     }
676     else if (rc != 200 && rc != 504) {
677         ret = ap_proxyerror(r, HTTP_BAD_GATEWAY,
678                              "Unable to set transfer type");
679     }
680 /* Allow not implemented */
681     else if (rc == 504)
682         /* ignore it silently */;
683
684     return ret;
685 }
686
687
688 /* Return the current directory which we have selected on the FTP server, or NULL */
689 static char *ftp_get_PWD(request_rec *r, conn_rec *ftp_ctrl, apr_bucket_brigade *bb)
690 {
691     char *cwd = NULL;
692     char *ftpmessage = NULL;
693
694     /* responses: 257, 500, 501, 502, 421, 550 */
695     /* 257 "<directory-name>" <commentary> */
696     /* 421 Service not available, closing control connection. */
697     /* 500 Syntax error, command unrecognized. */
698     /* 501 Syntax error in parameters or arguments. */
699     /* 502 Command not implemented. */
700     /* 550 Requested action not taken. */
701     switch (proxy_ftp_command("PWD" CRLF, r, ftp_ctrl, bb, &ftpmessage)) {
702         case -1:
703         case 421:
704         case 550:
705             ap_proxyerror(r, HTTP_BAD_GATEWAY,
706                              "Failed to read PWD on ftp server");
707             break;
708
709         case 257: {
710             const char *dirp = ftpmessage;
711             cwd = ap_getword_conf(r->pool, &dirp);
712         }
713     }
714     return cwd;
715 }
716
717
718 /* Common routine for failed authorization (i.e., missing or wrong password)
719  * to an ftp service. This causes most browsers to retry the request
720  * with username and password (which was presumably queried from the user)
721  * supplied in the Authorization: header.
722  * Note that we "invent" a realm name which consists of the
723  * ftp://user@host part of the reqest (sans password -if supplied but invalid-)
724  */
725 static int ftp_unauthorized(request_rec *r, int log_it)
726 {
727     r->proxyreq = PROXYREQ_NONE;
728     /*
729      * Log failed requests if they supplied a password (log username/password
730      * guessing attempts)
731      */
732     if (log_it)
733         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
734                       "proxy: missing or failed auth to %s",
735                       apr_uri_unparse(r->pool,
736                                  &r->parsed_uri, APR_URI_UNP_OMITPATHINFO));
737
738     apr_table_setn(r->err_headers_out, "WWW-Authenticate",
739                    apr_pstrcat(r->pool, "Basic realm=\"",
740                                apr_uri_unparse(r->pool, &r->parsed_uri,
741                        APR_URI_UNP_OMITPASSWORD | APR_URI_UNP_OMITPATHINFO),
742                                "\"", NULL));
743
744     return HTTP_UNAUTHORIZED;
745 }
746
747 static
748 apr_status_t proxy_ftp_cleanup(request_rec *r, proxy_conn_rec *backend)
749 {
750
751     backend->close = 1;
752     ap_set_module_config(r->connection->conn_config, &proxy_ftp_module, NULL);
753     ap_proxy_release_connection("FTP", backend, r->server);
754
755     return OK;
756 }
757
758 static
759 int ftp_proxyerror(request_rec *r, proxy_conn_rec *conn, int statuscode, const char *message)
760 {
761     proxy_ftp_cleanup(r, conn);
762     return ap_proxyerror(r, statuscode, message);
763 }
764 /*
765  * Handles direct access of ftp:// URLs
766  * Original (Non-PASV) version from
767  * Troy Morrison <spiffnet@zoom.com>
768  * PASV added by Chuck
769  * Filters by [Graham Leggett <minfrin@sharp.fm>]
770  */
771 static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
772                              proxy_server_conf *conf, char *url,
773                              const char *proxyhost, apr_port_t proxyport)
774 {
775     apr_pool_t *p = r->pool;
776     conn_rec *c = r->connection;
777     proxy_conn_rec *backend;
778     apr_socket_t *sock, *local_sock, *data_sock = NULL;
779     apr_sockaddr_t *connect_addr = NULL;
780     apr_status_t rv;
781     conn_rec *origin, *data = NULL;
782     apr_status_t err = APR_SUCCESS;
783     apr_status_t uerr = APR_SUCCESS;
784     apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
785     char *buf, *connectname;
786     apr_port_t connectport;
787     char buffer[MAX_STRING_LEN];
788     char *ftpmessage = NULL;
789     char *path, *strp, *type_suffix, *cwd = NULL;
790     apr_uri_t uri;
791     char *user = NULL;
792 /*    char *account = NULL; how to supply an account in a URL? */
793     const char *password = NULL;
794     int len, rc;
795     int one = 1;
796     char *size = NULL;
797     char xfer_type = 'A'; /* after ftp login, the default is ASCII */
798     int  dirlisting = 0;
799 #if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
800     apr_time_t mtime = 0L;
801 #endif
802
803     /* stuff for PASV mode */
804     int connect = 0, use_port = 0;
805     char dates[APR_RFC822_DATE_LEN];
806     int status;
807     apr_pool_t *address_pool;
808
809     /* is this for us? */
810     if (proxyhost) {
811         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
812                      "proxy: FTP: declining URL %s - proxyhost %s specified:", url, proxyhost);
813         return DECLINED;        /* proxy connections are via HTTP */
814     }
815     if (strncasecmp(url, "ftp:", 4)) {
816         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
817                      "proxy: FTP: declining URL %s - not ftp:", url);
818         return DECLINED;        /* only interested in FTP */
819     }
820     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
821                  "proxy: FTP: serving URL %s", url);
822
823
824     /*
825      * I: Who Do I Connect To? -----------------------
826      *
827      * Break up the URL to determine the host to connect to
828      */
829
830     /* we only support GET and HEAD */
831     if (r->method_number != M_GET)
832         return HTTP_NOT_IMPLEMENTED;
833
834     /* We break the URL into host, port, path-search */
835     if (r->parsed_uri.hostname == NULL) {
836         if (APR_SUCCESS != apr_uri_parse(p, url, &uri)) {
837             return ap_proxyerror(r, HTTP_BAD_REQUEST,
838                 apr_psprintf(p, "URI cannot be parsed: %s", url));
839         }
840         connectname = uri.hostname;
841         connectport = uri.port;
842         path = apr_pstrdup(p, uri.path);
843     }
844     else {
845         connectname = r->parsed_uri.hostname;
846         connectport = r->parsed_uri.port;
847         path = apr_pstrdup(p, r->parsed_uri.path);
848     }
849     if (connectport == 0) {
850         connectport = apr_uri_port_of_scheme("ftp");
851     }
852     path = (path != NULL && path[0] != '\0') ? &path[1] : "";
853
854     type_suffix = strchr(path, ';');
855     if (type_suffix != NULL)
856         *(type_suffix++) = '\0';
857
858     if (type_suffix != NULL && strncmp(type_suffix, "type=", 5) == 0
859         && apr_isalpha(type_suffix[5])) {
860         /* "type=d" forces a dir listing.
861          * The other types (i|a|e) are directly used for the ftp TYPE command
862          */
863         if ( ! (dirlisting = (apr_tolower(type_suffix[5]) == 'd')))
864             xfer_type = apr_toupper(type_suffix[5]);
865
866         /* Check valid types, rather than ignoring invalid types silently: */
867         if (strchr("AEI", xfer_type) == NULL)
868             return ap_proxyerror(r, HTTP_BAD_REQUEST, apr_pstrcat(r->pool,
869                                     "ftp proxy supports only types 'a', 'i', or 'e': \"",
870                                     type_suffix, "\" is invalid.", NULL));
871     }
872     else {
873         /* make binary transfers the default */
874         xfer_type = 'I';
875     }
876
877
878     /*
879      * The "Authorization:" header must be checked first. We allow the user
880      * to "override" the URL-coded user [ & password ] in the Browsers'
881      * User&Password Dialog. NOTE that this is only marginally more secure
882      * than having the password travel in plain as part of the URL, because
883      * Basic Auth simply uuencodes the plain text password. But chances are
884      * still smaller that the URL is logged regularly.
885      */
886     if ((password = apr_table_get(r->headers_in, "Authorization")) != NULL
887         && strcasecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0
888         && (password = ap_pbase64decode(r->pool, password))[0] != ':') {
889         /*
890          * Note that this allocation has to be made from r->connection->pool
891          * because it has the lifetime of the connection.  The other
892          * allocations are temporary and can be tossed away any time.
893          */
894         user = ap_getword_nulls(r->connection->pool, &password, ':');
895         r->ap_auth_type = "Basic";
896         r->user = r->parsed_uri.user = user;
897     }
898     else if ((user = r->parsed_uri.user) != NULL) {
899         user = apr_pstrdup(p, user);
900         decodeenc(user);
901         if ((password = r->parsed_uri.password) != NULL) {
902             char *tmp = apr_pstrdup(p, password);
903             decodeenc(tmp);
904             password = tmp;
905         }
906     }
907     else {
908         user = "anonymous";
909         password = "apache-proxy@";
910     }
911
912     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
913        "proxy: FTP: connecting %s to %s:%d", url, connectname, connectport);
914
915     if (worker->is_address_reusable) {
916         if (!worker->cp->addr) {
917             if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) {
918                 ap_log_error(APLOG_MARK, APLOG_ERR, err, r->server,
919                              "proxy: FTP: lock");
920                 return HTTP_INTERNAL_SERVER_ERROR;
921             }
922         }
923         connect_addr = worker->cp->addr;
924         address_pool = worker->cp->pool;
925     }
926     else
927         address_pool = r->pool;
928
929     /* do a DNS lookup for the destination host */
930     if (!connect_addr)
931         err = apr_sockaddr_info_get(&(connect_addr),
932                                     connectname, APR_UNSPEC,
933                                     connectport, 0,
934                                     address_pool);
935     if (worker->is_address_reusable && !worker->cp->addr) {
936         worker->cp->addr = connect_addr;
937         if ((uerr = PROXY_THREAD_UNLOCK(worker)) != APR_SUCCESS) {
938             ap_log_error(APLOG_MARK, APLOG_ERR, uerr, r->server,
939                          "proxy: FTP: unlock");
940         }
941     }
942     /*
943      * get all the possible IP addresses for the destname and loop through
944      * them until we get a successful connection
945      */
946     if (APR_SUCCESS != err) {
947         return ap_proxyerror(r, HTTP_BAD_GATEWAY, apr_pstrcat(p,
948                                                  "DNS lookup failure for: ",
949                                                         connectname, NULL));
950     }
951
952     /* check if ProxyBlock directive on this host */
953     if (OK != ap_proxy_checkproxyblock(r, conf, connect_addr)) {
954         return ap_proxyerror(r, HTTP_FORBIDDEN,
955                              "Connect to remote machine blocked");
956     }
957
958     /* create space for state information */
959     backend = (proxy_conn_rec *) ap_get_module_config(c->conn_config, &proxy_ftp_module);
960     if (!backend) {
961         status = ap_proxy_acquire_connection("FTP", &backend, worker, r->server);
962         if (status != OK) {
963             if (backend) {
964                 backend->close = 1;
965                 ap_proxy_release_connection("FTP", backend, r->server);
966             }
967             return status;
968         }
969         /* TODO: see if ftp could use determine_connection */
970         backend->addr = connect_addr;
971         backend->r = r;
972         ap_set_module_config(c->conn_config, &proxy_ftp_module, backend);
973     }
974
975
976     /*
977      * II: Make the Connection -----------------------
978      *
979      * We have determined who to connect to. Now make the connection.
980      */
981
982
983     if (ap_proxy_connect_backend("FTP", backend, worker, r->server)) {
984         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
985                      "proxy: FTP: an error occurred creating a new connection to %pI (%s)",
986                      connect_addr, connectname);
987         proxy_ftp_cleanup(r, backend);
988         return HTTP_SERVICE_UNAVAILABLE;
989     }
990
991     if (!backend->connection) {
992         status = ap_proxy_connection_create("FTP", backend, c, r->server);
993         if (status != OK) {
994             proxy_ftp_cleanup(r, backend);
995             return status;
996         }
997     }
998
999     /* Use old naming */
1000     origin = backend->connection;
1001     sock = backend->sock;
1002
1003     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1004                  "proxy: FTP: control connection complete");
1005
1006
1007     /*
1008      * III: Send Control Request -------------------------
1009      *
1010      * Log into the ftp server, send the username & password, change to the
1011      * correct directory...
1012      */
1013
1014
1015     /* possible results: */
1016     /* 120 Service ready in nnn minutes. */
1017     /* 220 Service ready for new user. */
1018     /* 421 Service not available, closing control connection. */
1019     rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
1020     if (rc == -1 || rc == 421) {
1021         return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
1022     }
1023     if (rc == 120) {
1024         /*
1025          * RFC2616 states: 14.37 Retry-After
1026          *
1027          * The Retry-After response-header field can be used with a 503 (Service
1028          * Unavailable) response to indicate how long the service is expected
1029          * to be unavailable to the requesting client. [...] The value of
1030          * this field can be either an HTTP-date or an integer number of
1031          * seconds (in decimal) after the time of the response. Retry-After
1032          * = "Retry-After" ":" ( HTTP-date | delta-seconds )
1033          */
1034         char *secs_str = ftpmessage;
1035         time_t secs;
1036
1037         /* Look for a number, preceded by whitespace */
1038         while (*secs_str)
1039             if ((secs_str==ftpmessage || apr_isspace(secs_str[-1])) &&
1040                 apr_isdigit(secs_str[0]))
1041                 break;
1042         if (*secs_str != '\0') {
1043             secs = atol(secs_str);
1044             apr_table_add(r->headers_out, "Retry-After",
1045                           apr_psprintf(p, "%lu", (unsigned long)(60 * secs)));
1046         }
1047         return ftp_proxyerror(r, backend, HTTP_SERVICE_UNAVAILABLE, ftpmessage);
1048     }
1049     if (rc != 220) {
1050         return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1051     }
1052
1053     rc = proxy_ftp_command(apr_pstrcat(p, "USER ", user, CRLF, NULL),
1054                            r, origin, bb, &ftpmessage);
1055     /* possible results; 230, 331, 332, 421, 500, 501, 530 */
1056     /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */
1057     /* 230 User logged in, proceed. */
1058     /* 331 User name okay, need password. */
1059     /* 332 Need account for login. */
1060     /* 421 Service not available, closing control connection. */
1061     /* 500 Syntax error, command unrecognized. */
1062     /* (This may include errors such as command line too long.) */
1063     /* 501 Syntax error in parameters or arguments. */
1064     /* 530 Not logged in. */
1065     if (rc == -1 || rc == 421) {
1066         return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, "Error reading from remote server");
1067     }
1068     if (rc == 530) {
1069         proxy_ftp_cleanup(r, backend);
1070         return ftp_unauthorized(r, 1);  /* log it: user name guessing
1071                                          * attempt? */
1072     }
1073     if (rc != 230 && rc != 331) {
1074         return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1075     }
1076
1077     if (rc == 331) {            /* send password */
1078         if (password == NULL) {
1079             proxy_ftp_cleanup(r, backend);
1080             return ftp_unauthorized(r, 0);
1081         }
1082
1083         rc = proxy_ftp_command(apr_pstrcat(p, "PASS ", password, CRLF, NULL),
1084                            r, origin, bb, &ftpmessage);
1085         /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */
1086         /* 230 User logged in, proceed. */
1087         /* 332 Need account for login. */
1088         /* 421 Service not available, closing control connection. */
1089         /* 500 Syntax error, command unrecognized. */
1090         /* 501 Syntax error in parameters or arguments. */
1091         /* 503 Bad sequence of commands. */
1092         /* 530 Not logged in. */
1093         if (rc == -1 || rc == 421) {
1094             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1095                                   "Error reading from remote server");
1096         }
1097         if (rc == 332) {
1098             return ftp_proxyerror(r, backend, HTTP_UNAUTHORIZED,
1099                   apr_pstrcat(p, "Need account for login: ", ftpmessage, NULL));
1100         }
1101         /* @@@ questionable -- we might as well return a 403 Forbidden here */
1102         if (rc == 530) {
1103             proxy_ftp_cleanup(r, backend);
1104             return ftp_unauthorized(r, 1);      /* log it: passwd guessing
1105                                                  * attempt? */
1106         }
1107         if (rc != 230 && rc != 202) {
1108             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1109         }
1110     }
1111     apr_table_set(r->notes, "Directory-README", ftpmessage);
1112
1113
1114     /* Special handling for leading "%2f": this enforces a "cwd /"
1115      * out of the $HOME directory which was the starting point after login
1116      */
1117     if (strncasecmp(path, "%2f", 3) == 0) {
1118         path += 3;
1119         while (*path == '/') /* skip leading '/' (after root %2f) */
1120             ++path;
1121
1122         rc = proxy_ftp_command("CWD /" CRLF, r, origin, bb, &ftpmessage);
1123         if (rc == -1 || rc == 421)
1124             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1125                                   "Error reading from remote server");
1126     }
1127
1128     /*
1129      * set the directory (walk directory component by component): this is
1130      * what we must do if we don't know the OS type of the remote machine
1131      */
1132     for (;;) {
1133         strp = strchr(path, '/');
1134         if (strp == NULL)
1135             break;
1136         *strp = '\0';
1137
1138         len = decodeenc(path); /* Note! This decodes a %2f -> "/" */
1139
1140         if (strchr(path, '/')) { /* are there now any '/' characters? */
1141             return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
1142                                   "Use of /%2f is only allowed at the base directory");
1143         }
1144
1145         /* NOTE: FTP servers do globbing on the path.
1146          * So we need to escape the URI metacharacters.
1147          * We use a special glob-escaping routine to escape globbing chars.
1148          * We could also have extended gen_test_char.c with a special T_ESCAPE_FTP_PATH
1149          */
1150         rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1151                            ftp_escape_globbingchars(p, path), CRLF, NULL),
1152                            r, origin, bb, &ftpmessage);
1153         *strp = '/';
1154         /* responses: 250, 421, 500, 501, 502, 530, 550 */
1155         /* 250 Requested file action okay, completed. */
1156         /* 421 Service not available, closing control connection. */
1157         /* 500 Syntax error, command unrecognized. */
1158         /* 501 Syntax error in parameters or arguments. */
1159         /* 502 Command not implemented. */
1160         /* 530 Not logged in. */
1161         /* 550 Requested action not taken. */
1162         if (rc == -1 || rc == 421) {
1163             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1164                                   "Error reading from remote server");
1165         }
1166         if (rc == 550) {
1167             return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1168         }
1169         if (rc != 250) {
1170             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1171         }
1172
1173         path = strp + 1;
1174     }
1175
1176     /*
1177      * IV: Make Data Connection? -------------------------
1178      *
1179      * Try EPSV, if that fails... try PASV, if that fails... try PORT.
1180      */
1181 /* this temporarily switches off EPSV/PASV */
1182 /*goto bypass;*/
1183
1184     /* set up data connection - EPSV */
1185     {
1186         apr_sockaddr_t *data_addr;
1187         char *data_ip;
1188         apr_port_t data_port;
1189
1190         /*
1191          * The EPSV command replaces PASV where both IPV4 and IPV6 is
1192          * supported. Only the port is returned, the IP address is always the
1193          * same as that on the control connection. Example: Entering Extended
1194          * Passive Mode (|||6446|)
1195          */
1196         rc = proxy_ftp_command("EPSV" CRLF,
1197                            r, origin, bb, &ftpmessage);
1198         /* possible results: 227, 421, 500, 501, 502, 530 */
1199         /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1200         /* 421 Service not available, closing control connection. */
1201         /* 500 Syntax error, command unrecognized. */
1202         /* 501 Syntax error in parameters or arguments. */
1203         /* 502 Command not implemented. */
1204         /* 530 Not logged in. */
1205         if (rc == -1 || rc == 421) {
1206             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1207                                   "Error reading from remote server");
1208         }
1209         if (rc != 229 && rc != 500 && rc != 501 && rc != 502) {
1210             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1211         }
1212         else if (rc == 229) {
1213             char *pstr;
1214             char *tok_cntx;
1215
1216             pstr = ftpmessage;
1217             pstr = apr_strtok(pstr, " ", &tok_cntx);    /* separate result code */
1218             if (pstr != NULL) {
1219                 if (*(pstr + strlen(pstr) + 1) == '=') {
1220                     pstr += strlen(pstr) + 2;
1221                 }
1222                 else {
1223                     pstr = apr_strtok(NULL, "(", &tok_cntx);    /* separate address &
1224                                                                  * port params */
1225                     if (pstr != NULL)
1226                         pstr = apr_strtok(NULL, ")", &tok_cntx);
1227                 }
1228             }
1229
1230             if (pstr) {
1231                 apr_sockaddr_t *epsv_addr;
1232                 data_port = atoi(pstr + 3);
1233
1234                 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1235                        "proxy: FTP: EPSV contacting remote host on port %d",
1236                              data_port);
1237
1238                 if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1239                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1240                                   "proxy: FTP: error creating EPSV socket");
1241                     proxy_ftp_cleanup(r, backend);
1242                     return HTTP_INTERNAL_SERVER_ERROR;
1243                 }
1244
1245 #if !defined (TPF) && !defined(BEOS)
1246                 if (conf->recv_buffer_size > 0
1247                         && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
1248                                                     conf->recv_buffer_size))) {
1249                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1250                                   "proxy: FTP: apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
1251                 }
1252 #endif
1253
1254                 rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
1255                 if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
1256                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1257                                  "apr_socket_opt_set(APR_TCP_NODELAY): Failed to set");
1258                 }
1259
1260                 /* make the connection */
1261                 apr_socket_addr_get(&data_addr, APR_REMOTE, sock);
1262                 apr_sockaddr_ip_get(&data_ip, data_addr);
1263                 apr_sockaddr_info_get(&epsv_addr, data_ip, connect_addr->family, data_port, 0, p);
1264                 rv = apr_socket_connect(data_sock, epsv_addr);
1265                 if (rv != APR_SUCCESS) {
1266                     ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1267                                  "proxy: FTP: EPSV attempt to connect to %pI failed - Firewall/NAT?", epsv_addr);
1268                     return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
1269                                                                            "EPSV attempt to connect to %pI failed - firewall/NAT?", epsv_addr));
1270                 }
1271                 else {
1272                     connect = 1;
1273                 }
1274             }
1275             else {
1276                 /* and try the regular way */
1277                 apr_socket_close(data_sock);
1278             }
1279         }
1280     }
1281
1282     /* set up data connection - PASV */
1283     if (!connect) {
1284         rc = proxy_ftp_command("PASV" CRLF,
1285                            r, origin, bb, &ftpmessage);
1286         /* possible results: 227, 421, 500, 501, 502, 530 */
1287         /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */
1288         /* 421 Service not available, closing control connection. */
1289         /* 500 Syntax error, command unrecognized. */
1290         /* 501 Syntax error in parameters or arguments. */
1291         /* 502 Command not implemented. */
1292         /* 530 Not logged in. */
1293         if (rc == -1 || rc == 421) {
1294             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1295                                   "Error reading from remote server");
1296         }
1297         if (rc != 227 && rc != 502) {
1298             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1299         }
1300         else if (rc == 227) {
1301             unsigned int h0, h1, h2, h3, p0, p1;
1302             char *pstr;
1303             char *tok_cntx;
1304
1305 /* FIXME: Check PASV against RFC1123 */
1306
1307             pstr = ftpmessage;
1308             pstr = apr_strtok(pstr, " ", &tok_cntx);    /* separate result code */
1309             if (pstr != NULL) {
1310                 if (*(pstr + strlen(pstr) + 1) == '=') {
1311                     pstr += strlen(pstr) + 2;
1312                 }
1313                 else {
1314                     pstr = apr_strtok(NULL, "(", &tok_cntx);    /* separate address &
1315                                                                  * port params */
1316                     if (pstr != NULL)
1317                         pstr = apr_strtok(NULL, ")", &tok_cntx);
1318                 }
1319             }
1320
1321 /* FIXME: Only supports IPV4 - fix in RFC2428 */
1322
1323             if (pstr != NULL && (sscanf(pstr,
1324                  "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) {
1325
1326                 apr_sockaddr_t *pasv_addr;
1327                 apr_port_t pasvport = (p1 << 8) + p0;
1328                 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1329                           "proxy: FTP: PASV contacting host %d.%d.%d.%d:%d",
1330                              h3, h2, h1, h0, pasvport);
1331
1332                 if ((rv = apr_socket_create(&data_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1333                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1334                                   "proxy: error creating PASV socket");
1335                     proxy_ftp_cleanup(r, backend);
1336                     return HTTP_INTERNAL_SERVER_ERROR;
1337                 }
1338
1339 #if !defined (TPF) && !defined(BEOS)
1340                 if (conf->recv_buffer_size > 0
1341                         && (rv = apr_socket_opt_set(data_sock, APR_SO_RCVBUF,
1342                                                     conf->recv_buffer_size))) {
1343                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1344                                   "proxy: FTP: apr_socket_opt_set(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default");
1345                 }
1346 #endif
1347
1348                 rv = apr_socket_opt_set(data_sock, APR_TCP_NODELAY, 1);
1349                 if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
1350                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1351                                  "apr_socket_opt_set(APR_TCP_NODELAY): Failed to set");
1352                 }
1353
1354                 /* make the connection */
1355                 apr_sockaddr_info_get(&pasv_addr, apr_psprintf(p, "%d.%d.%d.%d", h3, h2, h1, h0), connect_addr->family, pasvport, 0, p);
1356                 rv = apr_socket_connect(data_sock, pasv_addr);
1357                 if (rv != APR_SUCCESS) {
1358                     ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1359                                  "proxy: FTP: PASV attempt to connect to %pI failed - Firewall/NAT?", pasv_addr);
1360                     return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, apr_psprintf(r->pool,
1361                                                                            "PASV attempt to connect to %pI failed - firewall/NAT?", pasv_addr));
1362                 }
1363                 else {
1364                     connect = 1;
1365                 }
1366             }
1367             else {
1368                 /* and try the regular way */
1369                 apr_socket_close(data_sock);
1370             }
1371         }
1372     }
1373 /*bypass:*/
1374
1375     /* set up data connection - PORT */
1376     if (!connect) {
1377         apr_sockaddr_t *local_addr;
1378         char *local_ip;
1379         apr_port_t local_port;
1380         unsigned int h0, h1, h2, h3, p0, p1;
1381
1382         if ((rv = apr_socket_create(&local_sock, connect_addr->family, SOCK_STREAM, 0, r->pool)) != APR_SUCCESS) {
1383             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1384                           "proxy: FTP: error creating local socket");
1385             proxy_ftp_cleanup(r, backend);
1386             return HTTP_INTERNAL_SERVER_ERROR;
1387         }
1388         apr_socket_addr_get(&local_addr, APR_LOCAL, sock);
1389         local_port = local_addr->port;
1390         apr_sockaddr_ip_get(&local_ip, local_addr);
1391
1392         if ((rv = apr_socket_opt_set(local_sock, APR_SO_REUSEADDR, one))
1393                 != APR_SUCCESS) {
1394 #ifndef _OSD_POSIX              /* BS2000 has this option "always on" */
1395             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1396                           "proxy: FTP: error setting reuseaddr option");
1397             proxy_ftp_cleanup(r, backend);
1398             return HTTP_INTERNAL_SERVER_ERROR;
1399 #endif                          /* _OSD_POSIX */
1400         }
1401
1402         apr_sockaddr_info_get(&local_addr, local_ip, APR_UNSPEC, local_port, 0, r->pool);
1403
1404         if ((rv = apr_socket_bind(local_sock, local_addr)) != APR_SUCCESS) {
1405             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1406             "proxy: FTP: error binding to ftp data socket %pI", local_addr);
1407             proxy_ftp_cleanup(r, backend);
1408             return HTTP_INTERNAL_SERVER_ERROR;
1409         }
1410
1411         /* only need a short queue */
1412         if ((rv = apr_socket_listen(local_sock, 2)) != APR_SUCCESS) {
1413             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1414                           "proxy: FTP: error listening to ftp data socket %pI", local_addr);
1415             proxy_ftp_cleanup(r, backend);
1416             return HTTP_INTERNAL_SERVER_ERROR;
1417         }
1418
1419 /* FIXME: Sent PORT here */
1420
1421         if (local_ip && (sscanf(local_ip,
1422                                 "%d.%d.%d.%d", &h3, &h2, &h1, &h0) == 4)) {
1423             p1 = (local_port >> 8);
1424             p0 = (local_port & 0xFF);
1425
1426             rc = proxy_ftp_command(apr_psprintf(p, "PORT %d,%d,%d,%d,%d,%d" CRLF, h3, h2, h1, h0, p1, p0),
1427                            r, origin, bb, &ftpmessage);
1428             /* possible results: 200, 421, 500, 501, 502, 530 */
1429             /* 200 Command okay. */
1430             /* 421 Service not available, closing control connection. */
1431             /* 500 Syntax error, command unrecognized. */
1432             /* 501 Syntax error in parameters or arguments. */
1433             /* 502 Command not implemented. */
1434             /* 530 Not logged in. */
1435             if (rc == -1 || rc == 421) {
1436                 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1437                                       "Error reading from remote server");
1438             }
1439             if (rc != 200) {
1440                 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, buffer);
1441             }
1442
1443             /* signal that we must use the EPRT/PORT loop */
1444             use_port = 1;
1445         }
1446         else {
1447 /* IPV6 FIXME:
1448  * The EPRT command replaces PORT where both IPV4 and IPV6 is supported. The first
1449  * number (1,2) indicates the protocol type. Examples:
1450  *   EPRT |1|132.235.1.2|6275|
1451  *   EPRT |2|1080::8:800:200C:417A|5282|
1452  */
1453             return ftp_proxyerror(r, backend, HTTP_NOT_IMPLEMENTED,
1454                                   "Connect to IPV6 ftp server using EPRT not supported. Enable EPSV.");
1455         }
1456     }
1457
1458
1459     /*
1460      * V: Set The Headers -------------------
1461      *
1462      * Get the size of the request, set up the environment for HTTP.
1463      */
1464
1465     /* set request; "path" holds last path component */
1466     len = decodeenc(path);
1467
1468     if (strchr(path, '/')) { /* are there now any '/' characters? */
1469        return ftp_proxyerror(r, backend, HTTP_BAD_REQUEST,
1470                              "Use of /%2f is only allowed at the base directory");
1471     }
1472
1473     /* If len == 0 then it must be a directory (you can't RETR nothing)
1474      * Also, don't allow to RETR by wildcard. Instead, create a dirlisting
1475      */
1476     if (len == 0 || ftp_check_globbingchars(path)) {
1477         dirlisting = 1;
1478     }
1479     else {
1480         /* (from FreeBSD ftpd):
1481          * SIZE is not in RFC959, but Postel has blessed it and
1482          * it will be in the updated RFC.
1483          *
1484          * Return size of file in a format suitable for
1485          * using with RESTART (we just count bytes).
1486          */
1487         /* from draft-ietf-ftpext-mlst-14.txt:
1488          * This value will
1489          * change depending on the current STRUcture, MODE and TYPE of the data
1490          * connection, or a data connection which would be created were one
1491          * created now.  Thus, the result of the SIZE command is dependent on
1492          * the currently established STRU, MODE and TYPE parameters.
1493          */
1494         /* Therefore: switch to binary if the user did not specify ";type=a" */
1495         ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
1496         rc = proxy_ftp_command(apr_pstrcat(p, "SIZE ",
1497                            ftp_escape_globbingchars(p, path), CRLF, NULL),
1498                            r, origin, bb, &ftpmessage);
1499         if (rc == -1 || rc == 421) {
1500             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1501                                   "Error reading from remote server");
1502         }
1503         else if (rc == 213) {/* Size command ok */
1504             int j;
1505             for (j = 0; apr_isdigit(ftpmessage[j]); j++)
1506                 ;
1507             ftpmessage[j] = '\0';
1508             if (ftpmessage[0] != '\0')
1509                  size = ftpmessage; /* already pstrdup'ed: no copy necessary */
1510         }
1511         else if (rc == 550) {    /* Not a regular file */
1512             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1513                              "proxy: FTP: SIZE shows this is a directory");
1514             dirlisting = 1;
1515             rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1516                            ftp_escape_globbingchars(p, path), CRLF, NULL),
1517                            r, origin, bb, &ftpmessage);
1518             /* possible results: 250, 421, 500, 501, 502, 530, 550 */
1519             /* 250 Requested file action okay, completed. */
1520             /* 421 Service not available, closing control connection. */
1521             /* 500 Syntax error, command unrecognized. */
1522             /* 501 Syntax error in parameters or arguments. */
1523             /* 502 Command not implemented. */
1524             /* 530 Not logged in. */
1525             /* 550 Requested action not taken. */
1526             if (rc == -1 || rc == 421) {
1527                 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1528                                       "Error reading from remote server");
1529             }
1530             if (rc == 550) {
1531                 return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1532             }
1533             if (rc != 250) {
1534                 return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1535             }
1536             path = "";
1537             len = 0;
1538         }
1539     }
1540
1541     cwd = ftp_get_PWD(r, origin, bb);
1542     if (cwd != NULL) {
1543         apr_table_set(r->notes, "Directory-PWD", cwd);
1544     }
1545
1546     if (dirlisting) {
1547         ftp_set_TYPE('A', r, origin, bb, NULL);
1548         /* If the current directory contains no slash, we are talking to
1549          * a non-unix ftp system. Try LIST instead of "LIST -lag", it
1550          * should return a long listing anyway (unlike NLST).
1551          * Some exotic FTP servers might choke on the "-lag" switch.
1552          */
1553         /* Note that we do not escape the path here, to allow for
1554          * queries like: ftp://user@host/apache/src/server/http_*.c
1555          */
1556         if (len != 0)
1557             buf = apr_pstrcat(p, "LIST ", path, CRLF, NULL);
1558         else if (cwd == NULL || strchr(cwd, '/') != NULL)
1559             buf = apr_pstrcat(p, "LIST -lag", CRLF, NULL);
1560         else
1561             buf = "LIST" CRLF;
1562     }
1563     else {
1564         /* switch to binary if the user did not specify ";type=a" */
1565         ftp_set_TYPE(xfer_type, r, origin, bb, &ftpmessage);
1566 #if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1567         /* from draft-ietf-ftpext-mlst-14.txt:
1568          *   The FTP command, MODIFICATION TIME (MDTM), can be used to determine
1569          *   when a file in the server NVFS was last modified.     <..>
1570          *   The syntax of a time value is:
1571          *           time-val       = 14DIGIT [ "." 1*DIGIT ]      <..>
1572          *     Symbolically, a time-val may be viewed as
1573          *           YYYYMMDDHHMMSS.sss
1574          *     The "." and subsequent digits ("sss") are optional. <..>
1575          *     Time values are always represented in UTC (GMT)
1576          */
1577         rc = proxy_ftp_command(apr_pstrcat(p, "MDTM ", ftp_escape_globbingchars(p, path), CRLF, NULL),
1578                                r, origin, bb, &ftpmessage);
1579         /* then extract the Last-Modified time from it (YYYYMMDDhhmmss or YYYYMMDDhhmmss.xxx GMT). */
1580         if (rc == 213) {
1581         struct {
1582             char YYYY[4+1];
1583         char MM[2+1];
1584         char DD[2+1];
1585         char hh[2+1];
1586         char mm[2+1];
1587         char ss[2+1];
1588         } time_val;
1589         if (6 == sscanf(ftpmessage, "%4[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]%2[0-9]",
1590             time_val.YYYY, time_val.MM, time_val.DD, time_val.hh, time_val.mm, time_val.ss)) {
1591                 struct tm tms;
1592         memset (&tms, '\0', sizeof tms);
1593         tms.tm_year = atoi(time_val.YYYY) - 1900;
1594         tms.tm_mon  = atoi(time_val.MM)   - 1;
1595         tms.tm_mday = atoi(time_val.DD);
1596         tms.tm_hour = atoi(time_val.hh);
1597         tms.tm_min  = atoi(time_val.mm);
1598         tms.tm_sec  = atoi(time_val.ss);
1599 #ifdef HAVE_TIMEGM /* Does system have timegm()? */
1600         mtime = timegm(&tms);
1601         mtime *= APR_USEC_PER_SEC;
1602 #elif HAVE_GMTOFF /* does struct tm have a member tm_gmtoff? */
1603                 /* mktime will subtract the local timezone, which is not what we want.
1604          * Add it again because the MDTM string is GMT
1605          */
1606         mtime = mktime(&tms);
1607         mtime += tms.tm_gmtoff;
1608         mtime *= APR_USEC_PER_SEC;
1609 #else
1610         mtime = 0L;
1611 #endif
1612             }
1613     }
1614 #endif /* USE_MDTM */
1615 /* FIXME: Handle range requests - send REST */
1616         buf = apr_pstrcat(p, "RETR ", ftp_escape_globbingchars(p, path), CRLF, NULL);
1617     }
1618     rc = proxy_ftp_command(buf, r, origin, bb, &ftpmessage);
1619     /* rc is an intermediate response for the LIST or RETR commands */
1620
1621     /*
1622      * RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530,
1623      * 550 NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502,
1624      * 530
1625      */
1626     /* 110 Restart marker reply. */
1627     /* 125 Data connection already open; transfer starting. */
1628     /* 150 File status okay; about to open data connection. */
1629     /* 226 Closing data connection. */
1630     /* 250 Requested file action okay, completed. */
1631     /* 421 Service not available, closing control connection. */
1632     /* 425 Can't open data connection. */
1633     /* 426 Connection closed; transfer aborted. */
1634     /* 450 Requested file action not taken. */
1635     /* 451 Requested action aborted. Local error in processing. */
1636     /* 500 Syntax error, command unrecognized. */
1637     /* 501 Syntax error in parameters or arguments. */
1638     /* 530 Not logged in. */
1639     /* 550 Requested action not taken. */
1640     if (rc == -1 || rc == 421) {
1641         return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1642                               "Error reading from remote server");
1643     }
1644     if (rc == 550) {
1645         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1646                      "proxy: FTP: RETR failed, trying LIST instead");
1647
1648         /* Directory Listings should always be fetched in ASCII mode */
1649         dirlisting = 1;
1650         ftp_set_TYPE('A', r, origin, bb, NULL);
1651
1652         rc = proxy_ftp_command(apr_pstrcat(p, "CWD ",
1653                                ftp_escape_globbingchars(p, path), CRLF, NULL),
1654                                r, origin, bb, &ftpmessage);
1655         /* possible results: 250, 421, 500, 501, 502, 530, 550 */
1656         /* 250 Requested file action okay, completed. */
1657         /* 421 Service not available, closing control connection. */
1658         /* 500 Syntax error, command unrecognized. */
1659         /* 501 Syntax error in parameters or arguments. */
1660         /* 502 Command not implemented. */
1661         /* 530 Not logged in. */
1662         /* 550 Requested action not taken. */
1663         if (rc == -1 || rc == 421) {
1664             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1665                                   "Error reading from remote server");
1666         }
1667         if (rc == 550) {
1668             return ftp_proxyerror(r, backend, HTTP_NOT_FOUND, ftpmessage);
1669         }
1670         if (rc != 250) {
1671             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1672         }
1673
1674         /* Update current directory after CWD */
1675         cwd = ftp_get_PWD(r, origin, bb);
1676         if (cwd != NULL) {
1677             apr_table_set(r->notes, "Directory-PWD", cwd);
1678         }
1679
1680         /* See above for the "LIST" vs. "LIST -lag" discussion. */
1681         rc = proxy_ftp_command((cwd == NULL || strchr(cwd, '/') != NULL)
1682                                ? "LIST -lag" CRLF : "LIST" CRLF,
1683                                r, origin, bb, &ftpmessage);
1684
1685         /* rc is an intermediate response for the LIST command (125 transfer starting, 150 opening data connection) */
1686         if (rc == -1 || rc == 421)
1687             return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY,
1688                                   "Error reading from remote server");
1689     }
1690     if (rc != 125 && rc != 150 && rc != 226 && rc != 250) {
1691         return ftp_proxyerror(r, backend, HTTP_BAD_GATEWAY, ftpmessage);
1692     }
1693
1694     r->status = HTTP_OK;
1695     r->status_line = "200 OK";
1696
1697     apr_rfc822_date(dates, r->request_time);
1698     apr_table_setn(r->headers_out, "Date", dates);
1699     apr_table_setn(r->headers_out, "Server", ap_get_server_banner());
1700
1701     /* set content-type */
1702     if (dirlisting) {
1703         proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
1704                                                      &proxy_module);
1705
1706         ap_set_content_type(r, apr_pstrcat(p, "text/html;charset=",
1707                                            dconf->ftp_directory_charset ?
1708                                            dconf->ftp_directory_charset :
1709                                            "ISO-8859-1",  NULL));
1710     }
1711     else {
1712         if (r->content_type) {
1713             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1714                      "proxy: FTP: Content-Type set to %s", r->content_type);
1715         }
1716         else {
1717             ap_set_content_type(r, ap_default_type(r));
1718         }
1719         if (xfer_type != 'A' && size != NULL) {
1720             /* We "trust" the ftp server to really serve (size) bytes... */
1721             apr_table_setn(r->headers_out, "Content-Length", size);
1722             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1723                          "proxy: FTP: Content-Length set to %s", size);
1724         }
1725     }
1726     apr_table_setn(r->headers_out, "Content-Type", r->content_type);
1727     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1728                  "proxy: FTP: Content-Type set to %s", r->content_type);
1729
1730 #if defined(USE_MDTM) && (defined(HAVE_TIMEGM) || defined(HAVE_GMTOFF))
1731     if (mtime != 0L) {
1732         char datestr[APR_RFC822_DATE_LEN];
1733         apr_rfc822_date(datestr, mtime);
1734         apr_table_set(r->headers_out, "Last-Modified", datestr);
1735         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1736                  "proxy: FTP: Last-Modified set to %s", datestr);
1737     }
1738 #endif /* USE_MDTM */
1739
1740     /* If an encoding has been set by mistake, delete it.
1741      * @@@ FIXME (e.g., for ftp://user@host/file*.tar.gz,
1742      * @@@        the encoding is currently set to x-gzip)
1743      */
1744     if (dirlisting && r->content_encoding != NULL)
1745         r->content_encoding = NULL;
1746
1747     /* set content-encoding (not for dir listings, they are uncompressed)*/
1748     if (r->content_encoding != NULL && r->content_encoding[0] != '\0') {
1749         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1750              "proxy: FTP: Content-Encoding set to %s", r->content_encoding);
1751         apr_table_setn(r->headers_out, "Content-Encoding", r->content_encoding);
1752     }
1753
1754     /* wait for connection */
1755     if (use_port) {
1756         for (;;) {
1757             rv = apr_socket_accept(&data_sock, local_sock, r->pool);
1758             if (rv == APR_EINTR) {
1759                 continue;
1760             }
1761             else if (rv == APR_SUCCESS) {
1762                 break;
1763             }
1764             else {
1765                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1766                             "proxy: FTP: failed to accept data connection");
1767                 proxy_ftp_cleanup(r, backend);
1768                 return HTTP_BAD_GATEWAY;
1769             }
1770         }
1771     }
1772
1773     /* the transfer socket is now open, create a new connection */
1774     data = ap_run_create_connection(p, r->server, data_sock, r->connection->id,
1775                                     r->connection->sbh, c->bucket_alloc);
1776     if (!data) {
1777         /*
1778          * the peer reset the connection already; ap_run_create_connection() closed
1779          * the socket
1780          */
1781         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1782           "proxy: FTP: an error occurred creating the transfer connection");
1783         proxy_ftp_cleanup(r, backend);
1784         return HTTP_INTERNAL_SERVER_ERROR;
1785     }
1786
1787     /*
1788      * We do not do SSL over the data connection, even if the virtual host we
1789      * are in might have SSL enabled
1790      */
1791     ap_proxy_ssl_disable(data);
1792     /* set up the connection filters */
1793     rc = ap_run_pre_connection(data, data_sock);
1794     if (rc != OK && rc != DONE) {
1795         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1796                      "proxy: FTP: pre_connection setup failed (%d)",
1797                      rc);
1798         data->aborted = 1;
1799         proxy_ftp_cleanup(r, backend);
1800         return rc;
1801     }
1802
1803     /*
1804      * VI: Receive the Response ------------------------
1805      *
1806      * Get response from the remote ftp socket, and pass it up the filter chain.
1807      */
1808
1809     /* send response */
1810     r->sent_bodyct = 1;
1811
1812     if (dirlisting) {
1813         /* insert directory filter */
1814         ap_add_output_filter("PROXY_SEND_DIR", NULL, r, r->connection);
1815     }
1816
1817     /* send body */
1818     if (!r->header_only) {
1819         apr_bucket *e;
1820         int finish = FALSE;
1821
1822         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1823                      "proxy: FTP: start body send");
1824
1825         /* read the body, pass it to the output filters */
1826         while (ap_get_brigade(data->input_filters,
1827                               bb,
1828                               AP_MODE_READBYTES,
1829                               APR_BLOCK_READ,
1830                               conf->io_buffer_size) == APR_SUCCESS) {
1831 #if DEBUGGING
1832             {
1833                 apr_off_t readbytes;
1834                 apr_brigade_length(bb, 0, &readbytes);
1835                 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
1836                              r->server, "proxy (PID %d): readbytes: %#x",
1837                              getpid(), readbytes);
1838             }
1839 #endif
1840             /* sanity check */
1841             if (APR_BRIGADE_EMPTY(bb)) {
1842                 apr_brigade_cleanup(bb);
1843                 break;
1844             }
1845
1846             /* found the last brigade? */
1847             if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
1848                 /* if this is the last brigade, cleanup the
1849                  * backend connection first to prevent the
1850                  * backend server from hanging around waiting
1851                  * for a slow client to eat these bytes
1852                  */
1853                 ap_flush_conn(data);
1854                 apr_socket_close(data_sock);
1855                 data_sock = NULL;
1856                 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1857                              "proxy: FTP: data connection closed");
1858                 /* signal that we must leave */
1859                 finish = TRUE;
1860             }
1861
1862             /* if no EOS yet, then we must flush */
1863             if (FALSE == finish) {
1864                 e = apr_bucket_flush_create(c->bucket_alloc);
1865                 APR_BRIGADE_INSERT_TAIL(bb, e);
1866             }
1867
1868             /* try send what we read */
1869             if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS
1870                 || c->aborted) {
1871                 /* Ack! Phbtt! Die! User aborted! */
1872                 finish = TRUE;
1873             }
1874
1875             /* make sure we always clean up after ourselves */
1876             apr_brigade_cleanup(bb);
1877
1878             /* if we are done, leave */
1879             if (TRUE == finish) {
1880                 break;
1881             }
1882         }
1883         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1884                      "proxy: FTP: end body send");
1885
1886     }
1887     if (data_sock) {
1888         ap_flush_conn(data);
1889         apr_socket_close(data_sock);
1890         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1891                      "proxy: FTP: data connection closed");
1892     }
1893
1894     /* Retrieve the final response for the RETR or LIST commands */
1895     rc = proxy_ftp_command(NULL, r, origin, bb, &ftpmessage);
1896     apr_brigade_cleanup(bb);
1897
1898     /*
1899      * VII: Clean Up -------------
1900      *
1901      * If there are no KeepAlives, or if the connection has been signalled to
1902      * close, close the socket and clean up
1903      */
1904
1905     /* finish */
1906     rc = proxy_ftp_command("QUIT" CRLF,
1907                            r, origin, bb, &ftpmessage);
1908     /* responses: 221, 500 */
1909     /* 221 Service closing control connection. */
1910     /* 500 Syntax error, command unrecognized. */
1911     ap_flush_conn(origin);
1912     proxy_ftp_cleanup(r, backend);
1913
1914     apr_brigade_destroy(bb);
1915     return OK;
1916 }
1917
1918 static void ap_proxy_ftp_register_hook(apr_pool_t *p)
1919 {
1920     /* hooks */
1921     proxy_hook_scheme_handler(proxy_ftp_handler, NULL, NULL, APR_HOOK_MIDDLE);
1922     proxy_hook_canon_handler(proxy_ftp_canon, NULL, NULL, APR_HOOK_MIDDLE);
1923     /* filters */
1924     ap_register_output_filter("PROXY_SEND_DIR", proxy_send_dir_filter,
1925                               NULL, AP_FTYPE_RESOURCE);
1926 }
1927
1928 module AP_MODULE_DECLARE_DATA proxy_ftp_module = {
1929     STANDARD20_MODULE_STUFF,
1930     NULL,                       /* create per-directory config structure */
1931     NULL,                       /* merge per-directory config structures */
1932     NULL,                       /* create per-server config structure */
1933     NULL,                       /* merge per-server config structures */
1934     NULL,                       /* command apr_table_t */
1935     ap_proxy_ftp_register_hook  /* register hooks */
1936 };