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