diff options
Diffstat (limited to 'APACHE_1_3_42/src/modules/proxy/proxy_ftp.c')
-rw-r--r-- | APACHE_1_3_42/src/modules/proxy/proxy_ftp.c | 1393 |
1 files changed, 1393 insertions, 0 deletions
diff --git a/APACHE_1_3_42/src/modules/proxy/proxy_ftp.c b/APACHE_1_3_42/src/modules/proxy/proxy_ftp.c new file mode 100644 index 0000000000..01a6a0112e --- /dev/null +++ b/APACHE_1_3_42/src/modules/proxy/proxy_ftp.c @@ -0,0 +1,1393 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* FTP routines for Apache proxy */ + +#include "mod_proxy.h" +#include "http_main.h" +#include "http_log.h" +#include "http_core.h" + +#define AUTODETECT_PWD + +/* + * Decodes a '%' escaped string, and returns the number of characters + */ +static int decodeenc(char *x) +{ + int i, j, ch; + + if (x[0] == '\0') + return 0; /* special case for no characters */ + for (i = 0, j = 0; x[i] != '\0'; i++, j++) { +/* decode it if not already done */ + ch = x[i]; + if (ch == '%' && ap_isxdigit(x[i + 1]) && ap_isxdigit(x[i + 2])) { + ch = ap_proxy_hex2c(&x[i + 1]); + i += 2; + } + x[j] = ch; + } + x[j] = '\0'; + return j; +} + +/* + * checks an encoded ftp string for bad characters, namely, CR, LF or + * non-ascii character + */ +static int ftp_check_string(const char *x) +{ + int i, ch; + + for (i = 0; x[i] != '\0'; i++) { + ch = x[i]; + if (ch == '%' && ap_isxdigit(x[i + 1]) && ap_isxdigit(x[i + 2])) { + ch = ap_proxy_hex2c(&x[i + 1]); + i += 2; + } + if (ch == CR || ch == LF || (OS_ASC(ch) & 0x80)) + return 0; + } + return 1; +} + +/* + * Canonicalise ftp URLs. + */ +int ap_proxy_ftp_canon(request_rec *r, char *url) +{ + char *user, *password, *host, *path, *parms, *strp, sport[7]; + pool *p = r->pool; + const char *err; + int port; + + port = DEFAULT_FTP_PORT; + err = ap_proxy_canon_netloc(p, &url, &user, &password, &host, &port); + if (err) + return HTTP_BAD_REQUEST; + if (user != NULL && !ftp_check_string(user)) + return HTTP_BAD_REQUEST; + if (password != NULL && !ftp_check_string(password)) + return HTTP_BAD_REQUEST; + +/* now parse path/parameters args, according to rfc1738 */ +/* N.B. if this isn't a true proxy request, then the URL path + * (but not query args) has already been decoded. + * This gives rise to the problem of a ; being decoded into the + * path. + */ + strp = strchr(url, ';'); + if (strp != NULL) { + *(strp++) = '\0'; + parms = ap_proxy_canonenc(p, strp, strlen(strp), enc_parm, + r->proxyreq); + if (parms == NULL) + return HTTP_BAD_REQUEST; + } + else + parms = ""; + + path = ap_proxy_canonenc(p, url, strlen(url), enc_path, r->proxyreq); + if (path == NULL) + return HTTP_BAD_REQUEST; + if (!ftp_check_string(path)) + return HTTP_BAD_REQUEST; + + if (r->proxyreq == NOT_PROXY && r->args != NULL) { + if (strp != NULL) { + strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_parm, STD_PROXY); + if (strp == NULL) + return HTTP_BAD_REQUEST; + parms = ap_pstrcat(p, parms, "?", strp, NULL); + } + else { + strp = ap_proxy_canonenc(p, r->args, strlen(r->args), enc_fpath, STD_PROXY); + if (strp == NULL) + return HTTP_BAD_REQUEST; + path = ap_pstrcat(p, path, "?", strp, NULL); + } + r->args = NULL; + } + +/* now, rebuild URL */ + + if (port != DEFAULT_FTP_PORT) + ap_snprintf(sport, sizeof(sport), ":%d", port); + else + sport[0] = '\0'; + + r->filename = ap_pstrcat(p, "proxy:ftp://", (user != NULL) ? user : "", + (password != NULL) ? ":" : "", + (password != NULL) ? password : "", + (user != NULL) ? "@" : "", host, sport, "/", path, + (parms[0] != '\0') ? ";" : "", parms, NULL); + + return OK; +} + +/* + * Returns the ftp status code; + * or -1 on I/O error, 0 on data error + */ +static int ftp_getrc(BUFF *ctrl) +{ + int len, status; + char linebuff[100], buff[5]; + + len = ap_bgets(linebuff, sizeof linebuff, ctrl); + if (len == -1) + return -1; +/* check format */ + if (len < 5 || !ap_isdigit(linebuff[0]) || !ap_isdigit(linebuff[1]) || + !ap_isdigit(linebuff[2]) || (linebuff[3] != ' ' && linebuff[3] != '-')) + status = 0; + else + status = 100 * linebuff[0] + 10 * linebuff[1] + linebuff[2] - 111 * '0'; + + if (linebuff[len - 1] != '\n') { + (void)ap_bskiplf(ctrl); + } + +/* skip continuation lines */ + if (linebuff[3] == '-') { + memcpy(buff, linebuff, 3); + buff[3] = ' '; + do { + len = ap_bgets(linebuff, sizeof linebuff, ctrl); + if (len == -1) + return -1; + if (linebuff[len - 1] != '\n') { + (void)ap_bskiplf(ctrl); + } + } while (memcmp(linebuff, buff, 4) != 0); + } + + return status; +} + +/* + * Like ftp_getrc but returns both the ftp status code and + * remembers the response message in the supplied buffer + */ +static int ftp_getrc_msg(BUFF *ctrl, char *msgbuf, int msglen) +{ + int len, status; + char linebuff[100], buff[5]; + char *mb = msgbuf, *me = &msgbuf[msglen]; + + len = ap_bgets(linebuff, sizeof linebuff, ctrl); + if (len == -1) + return -1; + if (len < 5 || !ap_isdigit(linebuff[0]) || !ap_isdigit(linebuff[1]) || + !ap_isdigit(linebuff[2]) || (linebuff[3] != ' ' && linebuff[3] != '-')) + status = 0; + else + status = 100 * linebuff[0] + 10 * linebuff[1] + linebuff[2] - 111 * '0'; + + mb = ap_cpystrn(mb, linebuff + 4, me - mb); + + if (linebuff[len - 1] != '\n') + (void)ap_bskiplf(ctrl); + + if (linebuff[3] == '-') { + memcpy(buff, linebuff, 3); + buff[3] = ' '; + do { + len = ap_bgets(linebuff, sizeof linebuff, ctrl); + if (len == -1) + return -1; + if (linebuff[len - 1] != '\n') { + (void)ap_bskiplf(ctrl); + } + mb = ap_cpystrn(mb, linebuff + 4, me - mb); + } while (memcmp(linebuff, buff, 4) != 0); + } + return status; +} + +static long int send_dir(BUFF *data, request_rec *r, cache_req *c, char *cwd) +{ + char *buf, *buf2; + size_t buf_size; + char *filename; + int searchidx = 0; + char *searchptr = NULL; + int firstfile = 1; + unsigned long total_bytes_sent = 0; + register int n; + conn_rec *con = r->connection; + pool *p = r->pool; + char *dir, *path, *reldir, *site, *type = NULL; + char *basedir = ""; /* By default, path is relative to the $HOME + * dir */ + + /* create default sized buffers for the stuff below */ + buf_size = IOBUFSIZE; + buf = ap_palloc(r->pool, buf_size); + buf2 = ap_palloc(r->pool, buf_size); + + /* Save "scheme://site" prefix without password */ + site = ap_unparse_uri_components(p, &r->parsed_uri, UNP_OMITPASSWORD | UNP_OMITPATHINFO); + /* ... and path without query args */ + path = ap_unparse_uri_components(p, &r->parsed_uri, UNP_OMITSITEPART | UNP_OMITQUERY); + + /* If path began with /%2f, change the basedir */ + if (strncasecmp(path, "/%2f", 4) == 0) { + basedir = "/%2f"; + } + + /* Strip off a type qualifier. It is ignored for dir listings */ + if ((type = strstr(path, ";type=")) != NULL) + *type++ = '\0'; + + (void)decodeenc(path); + + while (path[1] == '/') /* collapse multiple leading slashes to one */ + ++path; + + /* Copy path, strip (all except the last) trailing slashes */ + /* (the trailing slash is needed for the dir component loop below) */ + path = dir = ap_pstrcat(r->pool, path, "/", NULL); + for (n = strlen(path); n > 1 && path[n - 1] == '/' && path[n - 2] == '/'; --n) + path[n - 1] = '\0'; + + /* print "ftp://host/" */ + n = ap_snprintf(buf, buf_size, DOCTYPE_HTML_3_2 + "<html><head><title>%s%s%s</title>\n" + "<base href=\"%s%s%s\"></head>\n" + "<body><h2>Directory of " + "<a href=\"/\">%s</a>/", + site, basedir, ap_escape_html(p, path), + site, basedir, ap_escape_uri(p, path), + site); + total_bytes_sent += ap_proxy_bputs2(buf, con->client, c); + + /* Add a link to the root directory (if %2f hack was used) */ + if (basedir[0] != '\0') { + total_bytes_sent += ap_proxy_bputs2("<a href=\"/%2f/\">%2f</a>/", con->client, c); + } + + for (dir = path + 1; (dir = strchr(dir, '/')) != NULL;) { + *dir = '\0'; + if ((reldir = strrchr(path + 1, '/')) == NULL) { + reldir = path + 1; + } + else + ++reldir; + /* print "path/" component */ + ap_snprintf(buf, buf_size, "<a href=\"%s%s/\">%s</a>/", + basedir, + ap_escape_uri(p, path), + ap_escape_html(p, reldir)); + total_bytes_sent += ap_proxy_bputs2(buf, con->client, c); + *dir = '/'; + while (*dir == '/') + ++dir; + } + + /* If the caller has determined the current directory, and it differs */ + /* from what the client requested, then show the real name */ + if (cwd == NULL || strncmp(cwd, path, strlen(cwd)) == 0) { + ap_snprintf(buf, buf_size, "</h2>\n<hr /><pre>"); + } + else { + ap_snprintf(buf, buf_size, "</h2>\n(%s)\n<hr /><pre>", + ap_escape_html(p, cwd)); + } + total_bytes_sent += ap_proxy_bputs2(buf, con->client, c); + + while (!con->aborted) { + n = ap_bgets(buf, buf_size, data); + if (n == -1) { /* input error */ + if (c != NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req, + "proxy: error reading from %s", c->url); + c = ap_proxy_cache_error(c); + } + break; + } + if (n == 0) + break; /* EOF */ + + if (buf[n - 1] == '\n') /* strip trailing '\n' */ + buf[--n] = '\0'; + if (buf[n - 1] == '\r') /* strip trailing '\r' if present */ + buf[--n] = '\0'; + + /* Handle unix-style symbolic link */ + if (buf[0] == 'l' && (filename = strstr(buf, " -> ")) != NULL) { + char *link_ptr = filename; + + do { + filename--; + } while (filename[0] != ' ' && filename > buf); + if (filename != buf) + *(filename++) = '\0'; + *(link_ptr++) = '\0'; + ap_snprintf(buf2, buf_size, "%s <a href=\"%s\">%s %s</a>\n", + ap_escape_html(p, buf), + ap_escape_uri(p, filename), + ap_escape_html(p, filename), + ap_escape_html(p, link_ptr)); + ap_cpystrn(buf, buf2, buf_size); + n = strlen(buf); + } + /* Handle unix style or DOS style directory */ + else if (buf[0] == 'd' || buf[0] == '-' || buf[0] == 'l' || ap_isdigit(buf[0])) { + if (ap_isdigit(buf[0])) { /* handle DOS dir */ + searchptr = strchr(buf, '<'); + if (searchptr != NULL) + *searchptr = '['; + searchptr = strchr(buf, '>'); + if (searchptr != NULL) + *searchptr = ']'; + } + + filename = strrchr(buf, ' '); + *(filename++) = 0; + + /* handle filenames with spaces in 'em */ + if (!strcmp(filename, ".") || !strcmp(filename, "..") || firstfile) { + firstfile = 0; + searchidx = filename - buf; + } + else if (searchidx != 0 && buf[searchidx] != 0) { + *(--filename) = ' '; + buf[searchidx - 1] = 0; + filename = &buf[searchidx]; + } + + /* Special handling for '.' and '..': append slash to link */ + if (!strcmp(filename, ".") || !strcmp(filename, "..") || buf[0] == 'd') { + ap_snprintf(buf2, buf_size, "%s <a href=\"%s/\">%s</a>\n", + ap_escape_html(p, buf), ap_escape_uri(p, filename), + ap_escape_html(p, filename)); + } + else { + ap_snprintf(buf2, buf_size, "%s <a href=\"%s\">%s</a>\n", + ap_escape_html(p, buf), + ap_escape_uri(p, filename), + ap_escape_html(p, filename)); + } + ap_cpystrn(buf, buf2, buf_size); + n = strlen(buf); + } + /* else??? What about other OS's output formats? */ + else { + strcat(buf, "\n"); /* re-append the newline char */ + ap_cpystrn(buf, ap_escape_html(p, buf), buf_size); + } + + total_bytes_sent += ap_proxy_bputs2(buf, con->client, c); + + ap_reset_timeout(r); /* reset timeout after successfule write */ + } + + total_bytes_sent += ap_proxy_bputs2("</pre><hr />\n", con->client, c); + total_bytes_sent += ap_proxy_bputs2(ap_psignature("", r), con->client, c); + total_bytes_sent += ap_proxy_bputs2("</body></html>\n", con->client, c); + + ap_bclose(data); + + ap_bflush(con->client); + + return total_bytes_sent; +} + +/* Common routine for failed authorization (i.e., missing or wrong password) + * to an ftp service. This causes most browsers to retry the request + * with username and password (which was presumably queried from the user) + * supplied in the Authorization: header. + * Note that we "invent" a realm name which consists of the + * ftp://user@host part of the reqest (sans password -if supplied but invalid-) + */ +static int ftp_unauthorized(request_rec *r, int log_it) +{ + r->proxyreq = NOT_PROXY; + /* + * Log failed requests if they supplied a password (log username/password + * guessing attempts) + */ + if (log_it) + ap_log_rerror(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO, r, + "proxy: missing or failed auth to %s", + ap_unparse_uri_components(r->pool, + &r->parsed_uri, UNP_OMITPATHINFO)); + + ap_table_setn(r->err_headers_out, "WWW-Authenticate", + ap_pstrcat(r->pool, "Basic realm=\"", + ap_unparse_uri_components(r->pool, &r->parsed_uri, + UNP_OMITPASSWORD | UNP_OMITPATHINFO), + "\"", NULL)); + + return HTTP_UNAUTHORIZED; +} + +/* Set ftp server to TYPE {A,I,E} before transfer of a directory or file */ +static int ftp_set_TYPE(request_rec *r, BUFF *ctrl, char xfer_type) +{ + static char old_type[2] = {'A', '\0'}; /* After logon, mode is ASCII */ + int ret = HTTP_OK; + int rc; + + if (xfer_type == old_type[0]) + return ret; + + /* set desired type */ + old_type[0] = xfer_type; + ap_bvputs(ctrl, "TYPE ", old_type, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: TYPE %s", old_type); + +/* responses: 200, 421, 500, 501, 504, 530 */ + /* 200 Command okay. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 504 Command not implemented for that parameter. */ + /* 530 Not logged in. */ + rc = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", rc); + if (rc == -1 || rc == 421) { + ap_kill_timeout(r); + ret = ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server"); + } + else if (rc != 200 && rc != 504) { + ap_kill_timeout(r); + ret = ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Unable to set transfer type"); + } +/* Allow not implemented */ + else if (rc == 504) + /* ignore it silently */ ; + + return ret; +} + +/* Common cleanup routine: close open BUFFers or sockets, and return an error */ +static int ftp_cleanup_and_return(request_rec *r, BUFF *ctrl, BUFF *data, int csock, int dsock, int rc) +{ + if (ctrl != NULL) + ap_bclose(ctrl); + else if (csock != -1) + ap_pclosesocket(r->pool, csock); + + if (data != NULL) + ap_bclose(data); + else if (dsock != -1) + ap_pclosesocket(r->pool, dsock); + + ap_kill_timeout(r); + + return rc; +} + +/* + * Handles direct access of ftp:// URLs + * Original (Non-PASV) version from + * Troy Morrison <spiffnet@zoom.com> + * PASV added by Chuck + */ +int ap_proxy_ftp_handler(request_rec *r, cache_req *c, char *url) +{ + char *desthost, *path, *strp, *parms; + char *strp2; + char *cwd = NULL; + char *user = NULL; +/* char *account = NULL; how to supply an account in a URL? */ + const char *password = NULL; + const char *err; + int destport, i, j, len, rc, nocache = 0; + int csd = 0, sock = -1, dsock = -1; + struct sockaddr_in server; + struct hostent server_hp; + struct in_addr destaddr; + table *resp_hdrs; + BUFF *ctrl = NULL; + BUFF *data = NULL; + pool *p = r->pool; + char *destportstr = NULL; + const char *urlptr = NULL; + int one = 1; + NET_SIZE_T clen; + char xfer_type = 'A'; /* after ftp login, the default is ASCII */ + int get_dirlisting = 0; + + void *sconf = r->server->module_config; + proxy_server_conf *conf = + (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + struct noproxy_entry *npent = (struct noproxy_entry *) conf->noproxies->elts; + struct nocache_entry *ncent = (struct nocache_entry *) conf->nocaches->elts; + +/* stuff for PASV mode */ + unsigned int presult, h0, h1, h2, h3, p0, p1; + unsigned int paddr; + unsigned short pport; + struct sockaddr_in data_addr; + int pasvmode = 0; + char pasv[64]; + char *pstr; + +/* stuff for responses */ + char resp[MAX_STRING_LEN]; + char *size = NULL; + +/* we only support GET and HEAD */ + + if (r->method_number != M_GET) + return HTTP_NOT_IMPLEMENTED; + +/* We break the URL into host, port, path-search */ + + urlptr = strstr(url, "://"); + if (urlptr == NULL) + return HTTP_BAD_REQUEST; + urlptr += 3; + destport = 21; + strp = strchr(urlptr, '/'); + if (strp == NULL) { + desthost = ap_pstrdup(p, urlptr); + urlptr = "/"; + } + else { + char *q = ap_palloc(p, strp - urlptr + 1); + memcpy(q, urlptr, strp - urlptr); + q[strp - urlptr] = '\0'; + urlptr = strp; + desthost = q; + } + + strp2 = strchr(desthost, ':'); + if (strp2 != NULL) { + *(strp2++) = '\0'; + if (ap_isdigit(*strp2)) { + destport = atoi(strp2); + destportstr = strp2; + } + } + path = strchr(urlptr, '/')+1; + + /* + * The "Authorization:" header must be checked first. We allow the user + * to "override" the URL-coded user [ & password ] in the Browsers' + * User&Password Dialog. NOTE that this is only marginally more secure + * than having the password travel in plain as part of the URL, because + * Basic Auth simply uuencodes the plain text password. But chances are + * still smaller that the URL is logged regularly. + */ + if ((password = ap_table_get(r->headers_in, "Authorization")) != NULL + && strcasecmp(ap_getword(r->pool, &password, ' '), "Basic") == 0 + && (password = ap_pbase64decode(r->pool, password))[0] != ':') { + /* + * Note that this allocation has to be made from r->connection->pool + * because it has the lifetime of the connection. The other + * allocations are temporary and can be tossed away any time. + */ + user = ap_getword_nulls(r->connection->pool, &password, ':'); + r->connection->ap_auth_type = "Basic"; + r->connection->user = r->parsed_uri.user = user; + nocache = 1; /* This resource only accessible with + * username/password */ + } + else if ((user = r->parsed_uri.user) != NULL) { + user = ap_pstrdup(p, user); + decodeenc(user); + if ((password = r->parsed_uri.password) != NULL) { + char *tmp = ap_pstrdup(p, password); + decodeenc(tmp); + password = tmp; + } + nocache = 1; /* This resource only accessible with + * username/password */ + } + else { + user = "anonymous"; + password = "apache_proxy@"; + } + + /* check if ProxyBlock directive on this host */ + destaddr.s_addr = ap_inet_addr(desthost); + for (i = 0; i < conf->noproxies->nelts; i++) { + if (destaddr.s_addr == npent[i].addr.s_addr || + (npent[i].name != NULL && + (npent[i].name[0] == '*' || strstr(desthost, npent[i].name) != NULL))) + return ap_proxyerror(r, HTTP_FORBIDDEN, + "Connect to remote machine blocked"); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: connect to %s:%d", desthost, destport); + + parms = strchr(url, ';'); + if (parms != NULL) + *(parms++) = '\0'; + + memset(&server, 0, sizeof(struct sockaddr_in)); + server.sin_family = AF_INET; + server.sin_port = htons((unsigned short)destport); + err = ap_proxy_host2addr(desthost, &server_hp); + if (err != NULL) + return ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, err); + + sock = ap_psocket_ex(p, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + if (sock == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: error creating socket"); + return HTTP_INTERNAL_SERVER_ERROR; + } + +#if !defined(TPF) && !defined(BEOS) + if (conf->recv_buffer_size > 0 + && setsockopt(sock, SOL_SOCKET, SO_RCVBUF, + (const char *)&conf->recv_buffer_size, sizeof(int)) + == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "setsockopt(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default"); + } +#endif + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, + sizeof(one)) == -1) { +#ifndef _OSD_POSIX /* BS2000 has this option "always on" */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: error setting reuseaddr option: setsockopt(SO_REUSEADDR)"); + ap_pclosesocket(p, sock); + return HTTP_INTERNAL_SERVER_ERROR; +#endif /* _OSD_POSIX */ + } + +#ifdef SINIX_D_RESOLVER_BUG + { + struct in_addr *ip_addr = (struct in_addr *)*server_hp.h_addr_list; + + for (; ip_addr->s_addr != 0; ++ip_addr) { + memcpy(&server.sin_addr, ip_addr, sizeof(struct in_addr)); + i = ap_proxy_doconnect(sock, &server, r); + if (i == 0) + break; + } + } +#else + j = 0; + while (server_hp.h_addr_list[j] != NULL) { + memcpy(&server.sin_addr, server_hp.h_addr_list[j], + sizeof(struct in_addr)); + i = ap_proxy_doconnect(sock, &server, r); + if (i == 0) + break; + j++; + } +#endif + if (i == -1) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, ap_pstrcat(r->pool, + "Could not connect to remote machine: ", + strerror(errno), NULL))); + } + + /* record request_time for HTTP/1.1 age calculation */ + c->req_time = time(NULL); + + ctrl = ap_bcreate(p, B_RDWR | B_SOCKET); + ap_bpushfd(ctrl, sock, sock); +/* shouldn't we implement telnet control options here? */ + +#ifdef CHARSET_EBCDIC + ap_bsetflag(ctrl, B_ASCII2EBCDIC | B_EBCDIC2ASCII, 1); +#endif /* CHARSET_EBCDIC */ + + /* possible results: */ + /* 120 Service ready in nnn minutes. */ + /* 220 Service ready for new user. */ + /* 421 Service not available, closing control connection. */ + ap_hard_timeout("proxy ftp", r); + i = ftp_getrc_msg(ctrl, resp, sizeof resp); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + } +#if 0 + if (i == 120) { + /* + * RFC2068 states: 14.38 Retry-After + * + * The Retry-After response-header field can be used with a 503 (Service + * Unavailable) response to indicate how long the service is expected + * to be unavailable to the requesting client. The value of this + * field can be either an HTTP-date or an integer number of seconds + * (in decimal) after the time of the response. Retry-After = + * "Retry-After" ":" ( HTTP-date | delta-seconds ) + */ +/**INDENT** Error@756: Unbalanced parens */ + ap_set_header("Retry-After", ap_psprintf(p, "%u", 60 * wait_mins); + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE, resp)); + } +#endif + if (i != 220) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, resp)); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: connected."); + + ap_bvputs(ctrl, "USER ", user, CRLF, NULL); + ap_bflush(ctrl); /* capture any errors */ + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: USER %s", user); + + /* possible results; 230, 331, 332, 421, 500, 501, 530 */ + /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */ + /* 230 User logged in, proceed. */ + /* 331 User name okay, need password. */ + /* 332 Need account for login. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* (This may include errors such as command line too long.) */ + /* 501 Syntax error in parameters or arguments. */ + /* 530 Not logged in. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + } + if (i == 530) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ftp_unauthorized(r, 1)); + } + if (i != 230 && i != 331) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + + if (i == 331) { /* send password */ + if (password == NULL) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ftp_unauthorized(r, 0)); + } + ap_bvputs(ctrl, "PASS ", password, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PASS %s", password); + /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */ + /* 230 User logged in, proceed. */ + /* 332 Need account for login. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 503 Bad sequence of commands. */ + /* 530 Not logged in. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + } + if (i == 332) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_UNAUTHORIZED, + "Need account for login")); + } + /* @@@ questionable -- we might as well return a 403 Forbidden here */ + if (i == 530) /* log it: passwd guessing attempt? */ + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ftp_unauthorized(r, 1)); + if (i != 230 && i != 202) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + + /* + * Special handling for leading "%2f": this enforces a "cwd /" out of the + * $HOME directory which was the starting point after login + */ + if (strncasecmp(path, "%2f", 3) == 0) { + path += 3; + while (*path == '/') /* skip leading '/' (after root %2f) */ + ++path; + ap_bputs("CWD /" CRLF, ctrl); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: CWD /"); + + /* possible results: 250, 421, 500, 501, 502, 530, 550 */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + else if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + else if (i != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + +/* set the directory (walk directory component by component): + * this is what we must do if we don't know the OS type of the remote + * machine + */ + for (; (strp = strchr(path, '/')) != NULL; path = strp + 1) { + char *slash = strp; + + *slash = '\0'; + + /* Skip multiple '/' (or trailing '/') to avoid 500 errors */ + while (strp[1] == '/') + ++strp; + if (strp[1] == '\0') + break; + + len = decodeenc(path); /* Note! This decodes a %2f -> "/" */ + if (strchr(path, '/')) /* were there any '/' characters? */ + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_REQUEST, + "Use of %2F is only allowed at the base directory")); + + ap_bvputs(ctrl, "CWD ", path, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: CWD %s", path); + *slash = '/'; + +/* responses: 250, 421, 500, 501, 502, 530, 550 */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (i == 500 || i == 501) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_REQUEST, + "Syntax error in filename (reported by ftp server)")); + if (i != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + + if (parms != NULL && strncmp(parms, "type=", 5) == 0 + && ap_isalpha(parms[5])) { + /* + * "type=d" forces a dir listing. The other types (i|a|e) are + * directly used for the ftp TYPE command + */ + if (!(get_dirlisting = (parms[5] == 'd'))) + xfer_type = ap_toupper(parms[5]); + + /* Check valid types, rather than ignoring invalid types silently: */ + if (strchr("AEI", xfer_type) == NULL) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_REQUEST, ap_pstrcat(r->pool, + "ftp proxy supports only types 'a', 'i', or 'e': \"", + parms, "\" is invalid.", NULL))); + } + else { + /* make binary transfers the default */ + xfer_type = 'I'; + } + +/* try to set up PASV data connection first */ + dsock = ap_psocket_ex(p, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + if (dsock == -1) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: error creating PASV socket")); + } + +#if !defined (TPF) && !defined(BEOS) + if (conf->recv_buffer_size) { + if (setsockopt(dsock, SOL_SOCKET, SO_RCVBUF, + (const char *)&conf->recv_buffer_size, sizeof(int)) == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "setsockopt(SO_RCVBUF): Failed to set ProxyReceiveBufferSize, using default"); + } + } +#endif + + ap_bputs("PASV" CRLF, ctrl); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PASV command issued"); +/* possible results: 227, 421, 500, 501, 502, 530 */ + /* 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + + i = ap_bgets(pasv, sizeof(pasv), ctrl); + if (i == -1 || i == 421) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: PASV: control connection is toast")); + } + else { + pasv[i - 1] = '\0'; + pstr = strtok(pasv, " "); /* separate result code */ + if (pstr != NULL) { + presult = atoi(pstr); + if (*(pstr + strlen(pstr) + 1) == '=') + pstr += strlen(pstr) + 2; + else { + pstr = strtok(NULL, "("); /* separate address & port + * params */ + if (pstr != NULL) + pstr = strtok(NULL, ")"); + } + } + else + presult = atoi(pasv); + + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", presult); + + if (presult == 227 && pstr != NULL && (sscanf(pstr, + "%d,%d,%d,%d,%d,%d", &h3, &h2, &h1, &h0, &p1, &p0) == 6)) { + /* pardon the parens, but it makes gcc happy */ + paddr = (((((h3 << 8) + h2) << 8) + h1) << 8) + h0; + pport = (p1 << 8) + p0; + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: contacting host %d.%d.%d.%d:%d", + h3, h2, h1, h0, pport); + data_addr.sin_family = AF_INET; + data_addr.sin_addr.s_addr = htonl(paddr); + data_addr.sin_port = htons(pport); + i = ap_proxy_doconnect(dsock, &data_addr, r); + + if (i == -1) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + ap_pstrcat(r->pool, + "Could not connect to remote machine: ", + strerror(errno), NULL))); + } + pasvmode = 1; + } + else { + ap_pclosesocket(p, dsock); /* and try the regular way */ + dsock = -1; + } + } + + if (!pasvmode) { /* set up data connection */ + clen = sizeof(struct sockaddr_in); + if (getsockname(sock, (struct sockaddr *)&server, &clen) < 0) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: error getting socket address")); + } + + dsock = ap_psocket_ex(p, PF_INET, SOCK_STREAM, IPPROTO_TCP, 1); + if (dsock == -1) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: error creating socket")); + } + + if (setsockopt(dsock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, + sizeof(one)) == -1) { +#ifndef _OSD_POSIX /* BS2000 has this option "always on" */ + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + "proxy: error setting reuseaddr option")); +#endif /* _OSD_POSIX */ + } + + if (bind(dsock, (struct sockaddr *)&server, + sizeof(struct sockaddr_in)) == -1) { + + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_INTERNAL_SERVER_ERROR, + ap_psprintf(p, "proxy: error binding to ftp data socket %s:%d", + inet_ntoa(server.sin_addr), server.sin_port))); + } + listen(dsock, 2); /* only need a short queue */ + } + +/* set request; "path" holds last path component */ + len = decodeenc(path); + if (strchr(path, '/')) /* were there any '/' characters? */ + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_REQUEST, + "Use of %2F is only allowed at the base directory")); + + /* TM - if len == 0 then it must be a directory (you can't RETR nothing) */ + + if (len == 0) { + get_dirlisting = 1; + } + else { + ap_bvputs(ctrl, "SIZE ", path, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: SIZE %s", path); + i = ftp_getrc_msg(ctrl, resp, sizeof resp); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d with response %s", i, resp); + if (i != 500) { /* Size command not recognized */ + if (i == 550) { /* Not a regular file */ + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: SIZE shows this is a directory"); + get_dirlisting = 1; + ap_bvputs(ctrl, "CWD ", path, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: CWD %s", path); + + /* possible results: 250, 421, 500, 501, 502, 530, 550 */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (i != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + path = ""; + len = 0; + } + else if (i == 213) {/* Size command ok */ + for (j = 0; j < sizeof resp && ap_isdigit(resp[j]); j++); + resp[j] = '\0'; + if (resp[0] != '\0') + size = ap_pstrdup(p, resp); + } + } + } + +#ifdef AUTODETECT_PWD + ap_bvputs(ctrl, "PWD", CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PWD"); +/* responses: 257, 500, 501, 502, 421, 550 */ + /* 257 "<directory-name>" <commentary> */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 550 Requested action not taken. */ + i = ftp_getrc_msg(ctrl, resp, sizeof resp); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PWD returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (i == 257) { + const char *dirp = resp; + cwd = ap_getword_conf(r->pool, &dirp); + } +#endif /* AUTODETECT_PWD */ + + if (get_dirlisting) { + if (len != 0) + ap_bvputs(ctrl, "LIST ", path, CRLF, NULL); + else + ap_bputs("LIST -lag" CRLF, ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: LIST %s", (len == 0 ? "" : path)); + } + else { + ftp_set_TYPE(r, ctrl, xfer_type); + ap_bvputs(ctrl, "RETR ", path, CRLF, NULL); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: RETR %s", path); + } + ap_bflush(ctrl); +/* RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530, 550 + NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502, 530 */ + /* 110 Restart marker reply. */ + /* 125 Data connection already open; transfer starting. */ + /* 150 File status okay; about to open data connection. */ + /* 226 Closing data connection. */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 425 Can't open data connection. */ + /* 426 Connection closed; transfer aborted. */ + /* 450 Requested file action not taken. */ + /* 451 Requested action aborted. Local error in processing. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + rc = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", rc); + if (rc == -1 || rc == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (rc == 550) { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: RETR failed, trying LIST instead"); + get_dirlisting = 1; + ftp_set_TYPE(r, ctrl, 'A'); /* directories must be transferred in + * ASCII */ + + ap_bvputs(ctrl, "CWD ", path, CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: CWD %s", path); + /* possible results: 250, 421, 500, 501, 502, 530, 550 */ + /* 250 Requested file action okay, completed. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 530 Not logged in. */ + /* 550 Requested action not taken. */ + rc = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", rc); + if (rc == -1 || rc == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (rc == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (rc != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + +#ifdef AUTODETECT_PWD + ap_bvputs(ctrl, "PWD", CRLF, NULL); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PWD"); +/* responses: 257, 500, 501, 502, 421, 550 */ + /* 257 "<directory-name>" <commentary> */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + /* 550 Requested action not taken. */ + i = ftp_getrc_msg(ctrl, resp, sizeof resp); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: PWD returned status %d", i); + if (i == -1 || i == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + if (i == 550) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_NOT_FOUND); + if (i == 257) { + const char *dirp = resp; + cwd = ap_getword_conf(r->pool, &dirp); + } +#endif /* AUTODETECT_PWD */ + + ap_bputs("LIST -lag" CRLF, ctrl); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: LIST -lag"); + rc = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", rc); + if (rc == -1 || rc == 421) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server")); + } + ap_kill_timeout(r); + if (rc != 125 && rc != 150 && rc != 226 && rc != 250) + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + + r->status = HTTP_OK; + r->status_line = "200 OK"; + + resp_hdrs = ap_make_table(p, 2); + c->hdrs = resp_hdrs; + + ap_table_setn(resp_hdrs, "Date", ap_gm_timestr_822(r->pool, r->request_time)); + ap_table_setn(resp_hdrs, "Server", ap_get_server_version()); + + if (get_dirlisting) { + ap_table_setn(resp_hdrs, "Content-Type", "text/html"); +#ifdef CHARSET_EBCDIC + r->ebcdic.conv_out = 1; /* server-generated */ +#endif + } + else { +#ifdef CHARSET_EBCDIC + r->ebcdic.conv_out = 0; /* do not convert what we read from the ftp + * server */ +#endif + if (r->content_type != NULL) { + ap_table_setn(resp_hdrs, "Content-Type", r->content_type); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: Content-Type set to %s", r->content_type); + } + else { + ap_table_setn(resp_hdrs, "Content-Type", ap_default_type(r)); + } + if (xfer_type != 'A' && size != NULL) { + /* We "trust" the ftp server to really serve (size) bytes... */ + ap_table_set(resp_hdrs, "Content-Length", size); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: Content-Length set to %s", size); + } + } + if (r->content_encoding != NULL && r->content_encoding[0] != '\0') { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: Content-Encoding set to %s", r->content_encoding); + ap_table_setn(resp_hdrs, "Content-Encoding", r->content_encoding); + } + +/* check if NoCache directive on this host */ + if (nocache == 0) { + for (i = 0; i < conf->nocaches->nelts; i++) { + if (destaddr.s_addr == ncent[i].addr.s_addr || + (ncent[i].name != NULL && + (ncent[i].name[0] == '*' || + strstr(desthost, ncent[i].name) != NULL))) { + nocache = 1; + break; + } + } + } + + i = ap_proxy_cache_update(c, resp_hdrs, 0, nocache); + + if (i != DECLINED) { + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, i); + } + + if (!pasvmode) { /* wait for connection */ + ap_hard_timeout("proxy ftp data connect", r); + clen = sizeof(struct sockaddr_in); + do + csd = accept(dsock, (struct sockaddr *)&server, &clen); + while (csd == -1 && errno == EINTR); + if (csd == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "proxy: failed to accept data connection"); + if (c != NULL) + c = ap_proxy_cache_error(c); + return ftp_cleanup_and_return(r, ctrl, data, sock, dsock, + HTTP_BAD_GATEWAY); + } + data = ap_bcreate(p, B_RDWR | B_SOCKET); + ap_bpushfd(data, csd, -1); + ap_kill_timeout(r); + } + else { + data = ap_bcreate(p, B_RDWR | B_SOCKET); + ap_bpushfd(data, dsock, dsock); + } + + ap_hard_timeout("proxy receive", r); + + /* send response */ + /* write status line and headers to the cache file */ + ap_proxy_write_headers(c, ap_pstrcat(p, "HTTP/1.1 ", r->status_line, NULL), resp_hdrs); + + /* Setup the headers for our client from upstreams response-headers */ + ap_overlap_tables(r->headers_out, resp_hdrs, AP_OVERLAP_TABLES_SET); + /* Add X-Cache header */ + ap_table_setn(r->headers_out, "X-Cache", + ap_pstrcat(r->pool, "MISS from ", + ap_get_server_name(r), NULL)); + /* The Content-Type of this response is the upstream one. */ + r->content_type = ap_table_get(r->headers_out, "Content-Type"); + /* finally output the headers to the client */ + ap_send_http_header(r); + +#ifdef CHARSET_EBCDIC + ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, r->ebcdic.conv_out); +#endif +/* send body */ + if (!r->header_only) { + if (!get_dirlisting) { +/* we need to set this for ap_proxy_send_fb()... */ + if (c != NULL) + c->cache_completion = 0; + ap_proxy_send_fb(data, r, c, -1, 0, 0, conf->io_buffer_size); + } + else { + send_dir(data, r, c, cwd); + } + /* ap_proxy_send_fb() closes the socket */ + data = NULL; + dsock = -1; + + /* + * We checked for 125||150||226||250 above. See if another rc is + * pending, and fetch it: + */ + if (rc == 125 || rc == 150) + rc = ftp_getrc(ctrl); + } + else { +/* abort the transfer: we send the header only */ + ap_bputs("ABOR" CRLF, ctrl); + ap_bflush(ctrl); + if (data != NULL) { + ap_bclose(data); + data = NULL; + dsock = -1; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: ABOR"); +/* responses: 225, 226, 421, 500, 501, 502 */ + /* 225 Data connection open; no transfer in progress. */ + /* 226 Closing data connection. */ + /* 421 Service not available, closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + /* 501 Syntax error in parameters or arguments. */ + /* 502 Command not implemented. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: returned status %d", i); + } + + ap_kill_timeout(r); + ap_proxy_cache_tidy(c); + +/* finish */ + ap_bputs("QUIT" CRLF, ctrl); + ap_bflush(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: QUIT"); +/* responses: 221, 500 */ + /* 221 Service closing control connection. */ + /* 500 Syntax error, command unrecognized. */ + i = ftp_getrc(ctrl); + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "FTP: QUIT: status %d", i); + + ap_bclose(ctrl); + + ap_rflush(r); /* flush before garbage collection */ + + ap_proxy_garbage_coll(r); + + return OK; +} |