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