summaryrefslogtreecommitdiff
path: root/APACHE_1_3_42/src/modules/proxy/proxy_ftp.c
diff options
context:
space:
mode:
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.c1393
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;
+}