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