/*
- * $Id: ftp.cc,v 1.381 2006/01/24 17:40:11 wessels Exp $
+ * $Id: ftp.cc,v 1.382 2006/01/25 17:41:23 wessels Exp $
*
* DEBUG: section 9 File Transfer Protocol (FTP)
* AUTHOR: Harvest Derived
#endif
#include "ConnectionDetail.h"
#include "forward.h"
+#include "Server.h"
+#include "MemBuf.h"
+
+#if ICAP_CLIENT
+#include "ICAP/ICAPClientRespmodPrecache.h"
+#include "ICAP/ICAPConfig.h"
+extern ICAPConfig TheICAPConfig;
+static void icapAclCheckDoneWrapper(ICAPServiceRep::Pointer service, void *data);
+#endif
static const char *const crlf = "\r\n";
static char cbuf[1024];
bool put;
bool put_mkdir;
bool listformat_unknown;
+ bool listing_started;
};
class FtpStateData;
typedef void (FTPSM) (FtpStateData *);
-class FtpStateData
+class FtpStateData : public ServerStateData
{
public:
void operator delete (void *);
FtpStateData(FwdState *);
~FtpStateData();
- StoreEntry *entry;
- HttpRequest *request;
char user[MAX_URL];
char password[MAX_URL];
int password_url;
struct
{
int fd;
- char *buf;
- size_t size;
- off_t offset;
+ MemBuf *readBuf;
char *host;
u_short port;
+ bool read_pending;
}
data;
struct _ftp_flags flags;
- FwdState::Pointer fwd;
private:
CBDATA_CLASS(FtpStateData);
public:
+ // these should all be private
void start();
void loginParser(const char *, int escaped);
int restartable();
int checkAuth(const HttpHeader * req_hdr);
void checkUrlpath();
void buildTitleUrl();
+ void writeReplyBody(const char *, int len);
+ void printfReplyBody(const char *fmt, ...);
+ void maybeReadData();
+ void transactionComplete();
+ void processReplyBody();
static PF ftpSocketClosed;
static CNCB ftpPasvCallback;
static HttpReply *ftpAuthRequired(HttpRequest * request, const char *realm);
static void ftpRequestBody(char *buf, ssize_t size, void *data);
static wordlist *ftpParseControlReply(char *, size_t, int *, int *);
+
+#if ICAP_CLIENT
+
+public:
+ void icapAclCheckDone(ICAPServiceRep::Pointer);
+ void takeAdaptedHeaders(HttpReply *);
+ void takeAdaptedBody(MemBuf *);
+ void doneAdapting();
+ void abortAdapting();
+ void icapSpaceAvailable();
+ bool icapAccessCheckPending;
+#endif
+
};
CBDATA_CLASS_INIT(FtpStateData);
delete ftpState;
}
-FtpStateData::FtpStateData(FwdState *theFwdState)
+FtpStateData::FtpStateData(FwdState *theFwdState) : ServerStateData(theFwdState)
{
- request = theFwdState->request;
- entry = theFwdState->entry;
- storeLockObject(entry);
- fwd = theFwdState;
const char *url = storeUrl(entry);
-
debug(9, 3) ("ftpStart: '%s'\n", url);
statCounter.server.all.requests++;
statCounter.server.ftp.requests++;
- request = requestLink(request);
ctrl.fd = theFwdState->server_fd;
data.fd = -1;
size = -1;
storeUnregisterAbort(entry);
- storeUnlockObject(entry);
-
if (reply_hdr) {
memFree(reply_hdr, MEM_8K_BUF);
reply_hdr = NULL;
}
- requestUnlink(request);
-
if (data.fd > -1) {
int fd = data.fd;
data.fd = -1;
ctrl.buf = NULL;
}
- if (data.buf) {
- memFreeBuf(data.size, data.buf);
- data.buf = NULL;
- }
+ if (!data.readBuf->isNull())
+ data.readBuf->clean();
+
+ delete data.readBuf;
if (pathcomps)
wordlistDestroy(&pathcomps);
void
FtpStateData::listingStart()
{
+ debugs(9,3,HERE << "listingStart()");
wordlist *w;
char *dirup;
int i, j, k;
const char *title = title_url.buf();
- storeAppendPrintf(entry, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
- storeAppendPrintf(entry, "<!-- HTML listing generated by Squid %s -->\n",
- version_string);
- storeAppendPrintf(entry, "<!-- %s -->\n", mkrfc1123(squid_curtime));
- storeAppendPrintf(entry, "<HTML><HEAD><TITLE>\n");
+ flags.listing_started = true;
+ printfReplyBody("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
+ printfReplyBody("<!-- HTML listing generated by Squid %s -->\n",
+ version_string);
+ printfReplyBody("<!-- %s -->\n", mkrfc1123(squid_curtime));
+ printfReplyBody("<HTML><HEAD><TITLE>\n");
{
char *t = xstrdup(title);
rfc1738_unescape(t);
- storeAppendPrintf(entry, "FTP Directory: %s\n", html_quote(t));
+ printfReplyBody("FTP Directory: %s\n", html_quote(t));
xfree(t);
}
- storeAppendPrintf(entry, "</TITLE>\n");
- storeAppendPrintf(entry, "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
+ printfReplyBody("</TITLE>\n");
+ printfReplyBody("<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
if (flags.need_base_href)
- storeAppendPrintf(entry, "<BASE HREF=\"%s\">\n",
- html_quote(base_href.buf()));
+ printfReplyBody("<BASE HREF=\"%s\">\n",
+ html_quote(base_href.buf()));
- storeAppendPrintf(entry, "</HEAD><BODY>\n");
+ printfReplyBody("</HEAD><BODY>\n");
if (cwd_message) {
- storeAppendPrintf(entry, "<PRE>\n");
+ printfReplyBody("<PRE>\n");
for (w = cwd_message; w; w = w->next)
- storeAppendPrintf(entry, "%s\n", html_quote(w->key));
+ printfReplyBody("%s\n", html_quote(w->key));
- storeAppendPrintf(entry, "</PRE>\n");
+ printfReplyBody("</PRE>\n");
- storeAppendPrintf(entry, "<HR noshade size=\"1px\">\n");
+ printfReplyBody("<HR noshade size=\"1px\">\n");
wordlistDestroy(&cwd_message);
}
- storeAppendPrintf(entry, "<H2>\n");
- storeAppendPrintf(entry, "FTP Directory: ");
+ printfReplyBody("<H2>\n");
+ printfReplyBody("FTP Directory: ");
/* "ftp://" == 6 characters */
assert(title_url.size() >= 6);
k = 6 + strcspn(&title[6], "/");
for (i = 6, j = 0; title[i]; j = i) {
- storeAppendPrintf(entry, "<A HREF=\"");
+ printfReplyBody("<A HREF=\"");
i += strcspn(&title[i], "/");
if (i > j) {
char *url = xstrdup(title);
url[i] = '\0';
- storeAppendPrintf(entry, "%s", html_quote(url + k));
- storeAppendPrintf(entry, "/");
- storeAppendPrintf(entry, "\">");
+ printfReplyBody("%s", html_quote(url + k));
+ printfReplyBody("/");
+ printfReplyBody("\">");
rfc1738_unescape(url + j);
- storeAppendPrintf(entry, "%s", html_quote(url + j));
+ printfReplyBody("%s", html_quote(url + j));
safe_free(url);
- storeAppendPrintf(entry, "</A>");
+ printfReplyBody("</A>");
}
- storeAppendPrintf(entry, "/");
+ printfReplyBody("/");
if (title[i] == '/')
i++;
if (i == j) {
/* Error guard, or "assert" */
- storeAppendPrintf(entry, "ERROR: Failed to parse URL: %s\n",
- html_quote(title));
+ printfReplyBody("ERROR: Failed to parse URL: %s\n",
+ html_quote(title));
debug(9, 0) ("Failed to parse URL: %s\n", title);
break;
}
}
- storeAppendPrintf(entry, "</H2>\n");
- storeAppendPrintf(entry, "<PRE>\n");
+ printfReplyBody("</H2>\n");
+ printfReplyBody("<PRE>\n");
dirup = htmlifyListEntry("<internal-dirup>");
- storeAppend(entry, dirup, strlen(dirup));
+ writeReplyBody(dirup, strlen(dirup));
flags.html_header_sent = 1;
}
void
FtpStateData::listingFinish()
{
+ debugs(9,3,HERE << "listingFinish()");
storeBuffer(entry);
- storeAppendPrintf(entry, "</PRE>\n");
+ printfReplyBody("</PRE>\n");
if (flags.listformat_unknown && !flags.tried_nlst) {
- storeAppendPrintf(entry, "<A HREF=\"%s/;type=d\">[As plain directory]</A>\n",
- flags.dir_slash ? rfc1738_escape_part(old_filepath) : ".");
+ printfReplyBody("<A HREF=\"%s/;type=d\">[As plain directory]</A>\n",
+ flags.dir_slash ? rfc1738_escape_part(old_filepath) : ".");
} else if (typecode == 'D') {
const char *path = flags.dir_slash ? filepath : ".";
- storeAppendPrintf(entry, "<A HREF=\"%s/\">[As extended directory]</A>\n", html_quote(path));
+ printfReplyBody("<A HREF=\"%s/\">[As extended directory]</A>\n", html_quote(path));
}
- storeAppendPrintf(entry, "<HR noshade size=\"1px\">\n");
- storeAppendPrintf(entry, "<ADDRESS>\n");
- storeAppendPrintf(entry, "Generated %s by %s (%s)\n",
- mkrfc1123(squid_curtime),
- getMyHostname(),
- visible_appname_string);
- storeAppendPrintf(entry, "</ADDRESS></BODY></HTML>\n");
+ printfReplyBody("<HR noshade size=\"1px\">\n");
+ printfReplyBody("<ADDRESS>\n");
+ printfReplyBody("Generated %s by %s (%s)\n",
+ mkrfc1123(squid_curtime),
+ getMyHostname(),
+ visible_appname_string);
+ printfReplyBody("</ADDRESS></BODY></HTML>\n");
}
static const char *Month[] =
void
FtpStateData::parseListing()
{
- char *buf = data.buf;
+ char *buf = data.readBuf->content();
char *sbuf; /* NULL-terminated copy of buf */
char *end;
char *line;
size_t linelen;
size_t usable;
StoreEntry *e = entry;
- size_t len = data.offset;
+ size_t len = data.readBuf->contentSize();
/*
* We need a NULL-terminated buffer for scanning, ick
*/
assert(t != NULL);
- storeAppend(e, t, strlen(t));
- }
-
- assert(usable <= len);
-
- if (usable < len) {
- /* must copy partial line to beginning of buf */
- linelen = len - usable;
-
- if (linelen > 4096)
- linelen = 4096;
+#if ICAP_CLIENT
- xstrncpy(line, end, linelen);
+ if (icap) {
+ if ((int)strlen(t) > icap->potentialSpaceSize()) {
+ debugs(0,0,HERE << "WARNING avoid overwhelming ICAP with data!");
+ usable = s - sbuf;
+ break;
+ }
+ }
- xstrncpy(data.buf, line, data.size);
+#endif
- data.offset = strlen(data.buf);
+ writeReplyBody(t, strlen(t));
}
+ data.readBuf->consume(usable);
memFree(line, MEM_4K_BUF);
xfree(sbuf);
}
FtpStateData::dataReadWrapper(int fd, char *buf, size_t len, comm_err_t errflag, int xerrno, void *data)
{
FtpStateData *ftpState = (FtpStateData *)data;
+ ftpState->data.read_pending = false;
ftpState->dataRead(fd, buf, len, errflag, xerrno);
}
+void
+FtpStateData::maybeReadData()
+{
+ if (data.fd < 0)
+ return;
+
+ if (data.read_pending)
+ return;
+
+ int read_sz = data.readBuf->spaceSize();
+
+#if ICAP_CLIENT
+
+ if (icap) {
+ int icap_space = icap->potentialSpaceSize();
+
+ if (icap_space < read_sz)
+ read_sz = icap_space;
+ }
+
+#endif
+
+ debugs(11,9, HERE << "FTP may read up to " << read_sz << " bytes");
+
+ if (read_sz < 2) // see http.cc
+ return;
+
+ data.read_pending = true;
+
+ debugs(9,5,HERE << "queueing read on FD " << data.fd);
+
+ entry->delayAwareRead(data.fd, data.readBuf->space(), read_sz, dataReadWrapper, this);
+}
+
void
FtpStateData::dataRead(int fd, char *buf, size_t len, comm_err_t errflag, int xerrno)
{
int j;
int bin;
- size_t read_sz;
- debug(9, 5) ("ftpDataRead: FD %d, Read %d bytes\n", fd, (unsigned int)len);
+ debugs(9, 3, HERE << "ftpDataRead: FD " << fd << " Read " << len << " bytes");
if (len > 0) {
kb_incr(&statCounter.server.all.kbytes_in, len);
delayId.bytesIn(len);
#endif
- data.offset += len;
}
if (errflag == COMM_OK && len > 0) {
+ debugs(9,5,HERE << "appended " << len << " bytes to readBuf");
+ data.readBuf->appended(len);
+#if DELAY_POOLS
+
+ DelayId delayId = entry->mem_obj->mostBytesAllowed();
+ delayId.bytesIn(len);
+#endif
+
IOStats.Ftp.reads++;
for (j = len - 1, bin = 0; j; bin++)
IOStats.Ftp.read_hist[bin]++;
}
- if (!flags.http_header_sent && len >= 0) {
- appendSuccessHeader();
-
- if (flags.isdir)
- listingStart();
- }
-
if (errflag != COMM_OK || len < 0) {
debug(50, ignoreErrno(xerrno) ? 3 : 1) ("ftpDataRead: read error: %s\n", xstrerr(xerrno));
if (ignoreErrno(xerrno)) {
/* XXX what about Config.Timeout.read? */
- read_sz = data.size - data.offset;
-#if DELAY_POOLS
-
- read_sz = delayId.bytesWanted(1, read_sz);
-#endif
-
- comm_read(fd, data.buf + data.offset, read_sz, dataReadWrapper, this);
+ maybeReadData();
} else {
if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported) {
fwd->dontRetry(false); /* this is a retryable error */
}
} else if (len == 0) {
dataComplete();
- } else {
- if (flags.isdir) {
- parseListing();
- } else {
- storeAppend(entry, data.buf, len);
- data.offset = 0;
- }
+ }
- storeBufferFlush(entry);
+ processReplyBody();
+}
- /* XXX what about Config.Timeout.read? */
- read_sz = data.size - data.offset;
+void
+FtpStateData::processReplyBody()
+{
+ if (!flags.http_header_sent && data.readBuf->contentSize() >= 0)
+ appendSuccessHeader();
-#if DELAY_POOLS
+#if ICAP_CLIENT
- read_sz = delayId.bytesWanted(1, read_sz);
+ if (icapAccessCheckPending) {
+ debugs(9,3,HERE << "returning from dataRead due to icapAccessCheckPending");
+ return;
+ }
#endif
- comm_read(fd, data.buf + data.offset, read_sz, dataReadWrapper, this);
+ if (flags.isdir && !flags.listing_started)
+ listingStart();
+
+ if (flags.isdir) {
+ parseListing();
+ } else {
+ writeReplyBody(data.readBuf->content(), data.readBuf->contentSize());
+ debugs(9,5,HERE << "consuming " << data.readBuf->contentSize() << " bytes of readBuf");
+ data.readBuf->consume(data.readBuf->contentSize());
}
+
+ storeBufferFlush(entry);
+
+ /* XXX what about Config.Timeout.read? */
+ maybeReadData();
}
/*
ctrl.last_command = xstrdup("Connect to server");
ctrl.buf = (char *)memAllocBuf(4096, &ctrl.size);
ctrl.offset = 0;
- data.buf = (char *)memAllocBuf(SQUID_TCP_SO_RCVBUF, &data.size);
+ data.readBuf = new MemBuf;
+ data.readBuf->init(4096, SQUID_TCP_SO_RCVBUF);
scheduleReadControlReply(0);
}
if (code == 125 || (code == 150 && ftpState->data.host)) {
/* Begin data transfer */
/* XXX what about Config.Timeout.read? */
- assert(ftpState->data.offset == 0);
- ftpState->entry->delayAwareRead(ftpState->data.fd, ftpState->data.buf, ftpState->data.size, FtpStateData::dataReadWrapper, ftpState);
+ ftpState->maybeReadData();
ftpState->state = READING_DATA;
/*
* Cancel the timeout on the Control socket and establish one
/* Begin data transfer */
debug(9, 3) ("ftpReadRetr: reading data channel\n");
/* XXX what about Config.Timeout.read? */
- size_t read_sz = ftpState->data.size - ftpState->data.offset;
-
- ftpState->entry->delayAwareRead(ftpState->data.fd, ftpState->data.buf + ftpState->data.offset, read_sz, FtpStateData::dataReadWrapper, ftpState);
-
+ ftpState->maybeReadData();
ftpState->state = READING_DATA;
/*
* Cancel the timeout on the Control socket and establish one
if (ftpState->flags.html_header_sent)
ftpState->listingFinish();
- ftpState->fwd->unregister(ftpState->ctrl.fd);
-
- ftpState->fwd->complete();
-
- ftpSendQuit(ftpState);
+ ftpState->transactionComplete();
} else { /* != 226 */
debug(9, 1) ("ftpReadTransferDone: Got code %d after reading data\n",
code);
{
FtpStateData *ftpState = (FtpStateData *) data;
debug(9, 3) ("ftpRequestBody: buf=%p size=%d ftpState=%p\n", buf, (int) size, data);
- ftpState->data.offset = size;
if (size > 0) {
/* DataWrite */
if (!err) {
/* Shedule the rest of the request */
- clientReadBody(ftpState->request, ftpState->data.buf, ftpState->data.size, ftpRequestBody, ftpState);
+ clientReadBody(ftpState->request,
+ ftpState->data.readBuf->content(),
+ ftpState->data.readBuf->contentSize(),
+ ftpRequestBody,
+ ftpState);
} else {
debug(9, 1) ("ftpDataWriteCallback: write error: %s\n", xstrerr(xerrno));
ftpState->failed(ERR_WRITE_ERROR, xerrno);
FtpStateData *ftpState = (FtpStateData *) data;
debug(9, 3) ("ftpDataWrite\n");
/* This starts the body transfer */
- clientReadBody(ftpState->request, ftpState->data.buf, ftpState->data.size, ftpRequestBody, ftpState);
+ clientReadBody(ftpState->request,
+ ftpState->data.readBuf->content(),
+ ftpState->data.readBuf->contentSize(),
+ ftpRequestBody,
+ ftpState);
}
static void
const char *filename = NULL;
const char *t = NULL;
StoreEntry *e = entry;
- HttpReply *reply = new HttpReply;
+ HttpReply *newrep = new HttpReply;
+
+ reply = newrep->lock()
+
+ ;
if (flags.http_header_sent)
return;
if (mime_enc)
httpHeaderPutStr(&reply->header, HDR_CONTENT_ENCODING, mime_enc);
+#if ICAP_CLIENT
+
+ if (TheICAPConfig.onoff) {
+ ICAPAccessCheck *icap_access_check = new ICAPAccessCheck(ICAP::methodRespmod,
+ ICAP::pointPreCache,
+ request,
+ reply,
+ icapAclCheckDoneWrapper,
+ this);
+
+ icapAccessCheckPending = true;
+ icap_access_check->check(); // will eventually delete self
+ return;
+ }
+
+#endif
+
storeEntryReplaceObject(e, reply);
storeTimestampsSet(e);
return buf;
}
+
+void
+FtpStateData::printfReplyBody(const char *fmt, ...)
+{
+ va_list args;
+ va_start (args, fmt);
+ static char buf[4096];
+ buf[0] = '\0';
+ vsnprintf(buf, 4096, fmt, args);
+ writeReplyBody(buf, strlen(buf));
+}
+
+/*
+ * Call this when there is data from the origin server
+ * which should be sent to either StoreEntry, or to ICAP...
+ */
+void
+FtpStateData::writeReplyBody(const char *data, int len)
+{
+#if ICAP_CLIENT
+
+ if (icap) {
+ debugs(9,5,HERE << "writing " << len << " bytes to ICAP");
+ icap->sendMoreData (StoreIOBuffer(len, 0, (char*)data));
+ return;
+ }
+
+#endif
+
+ debugs(9,5,HERE << "writing " << len << " bytes to StoreEntry");
+
+ storeAppend(entry, data, len);
+}
+
+
+void
+FtpStateData::transactionComplete()
+{
+ debugs(9,5,HERE << "transactionComplete FD " << ctrl.fd << " this " << this);
+
+ fwd->unregister(ctrl.fd);
+
+ ftpSendQuit(this);
+
+#if ICAP_CLIENT
+
+ if (icap) {
+ icap->doneSending();
+ return;
+ }
+
+#endif
+
+ fwd->complete();
+}
+
+#if ICAP_CLIENT
+
+static void
+icapAclCheckDoneWrapper(ICAPServiceRep::Pointer service, void *data)
+{
+ FtpStateData *ftpState = (FtpStateData *)data;
+ ftpState->icapAclCheckDone(service);
+}
+
+void
+FtpStateData::icapAclCheckDone(ICAPServiceRep::Pointer service)
+{
+ icapAccessCheckPending = false;
+
+ if (service == NULL) {
+ // handle case where no service is selected;
+ debugs(0,0,HERE << "write me");
+ processReplyBody();
+ return;
+ }
+
+ if (doIcap(service) < 0) {
+ /*
+ * XXX Maybe instead of an error page we should
+ * handle the reply normally (without ICAP).
+ */
+ ErrorState *err = errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
+ err->xerrno = errno;
+ err->request = requestLink(request);
+ errorAppendEntry(entry, err);
+ comm_close(ctrl.fd);
+ return;
+ }
+
+ icap->startRespMod(this, request, reply);
+ processReplyBody();
+}
+
+/*
+ * Called by ICAPClientRespmodPrecache when it has space available for us.
+ */
+void
+FtpStateData::icapSpaceAvailable()
+{
+ debug(11,5)("FtpStateData::icapSpaceAvailable() called\n");
+ maybeReadData();
+}
+
+void
+FtpStateData::takeAdaptedHeaders(HttpReply *rep)
+{
+ debug(11,5)("FtpStateData::takeAdaptedHeaders() called\n");
+
+ if (!entry->isAccepting()) {
+ debug(11,5)("\toops, entry is not Accepting!\n");
+ icap->ownerAbort();
+ return;
+ }
+
+ assert (rep);
+ storeEntryReplaceObject(entry, rep);
+ reply->unlock();
+
+ reply = rep->lock()
+
+ ;
+
+ debug(11,5)("FtpStateData::takeAdaptedHeaders() finished\n");
+}
+
+void
+FtpStateData::takeAdaptedBody(MemBuf *buf)
+{
+ debug(11,5)("FtpStateData::takeAdaptedBody() called\n");
+ debug(11,5)("\t%d bytes\n", (int) buf->contentSize());
+
+ if (!entry->isAccepting()) {
+ debug(11,5)("\toops, entry is not Accepting!\n");
+ icap->ownerAbort();
+ return;
+ }
+
+ storeAppend(entry, buf->content(), buf->contentSize());
+ buf->consume(buf->contentSize()); // consume everything written
+}
+
+void
+FtpStateData::doneAdapting()
+{
+ debug(11,5)("FtpStateData::doneAdapting() called\n");
+
+ if (!entry->isAccepting()) {
+ debug(11,5)("\toops, entry is not Accepting!\n");
+ icap->ownerAbort();
+ } else {
+ fwd->complete();
+ }
+
+ /*
+ * ICAP is done, so we don't need this any more.
+ */
+ delete icap;
+
+ cbdataReferenceDone(icap);
+
+ if (ctrl.fd >= 0)
+ comm_close(ctrl.fd);
+ else
+ delete this;
+}
+
+void
+FtpStateData::abortAdapting()
+{
+ debug(11,5)("FtpStateData::abortAdapting() called\n");
+
+ /*
+ * ICAP has given up, we're done with it too
+ */
+ delete icap;
+ cbdataReferenceDone(icap);
+
+ if (entry->isEmpty()) {
+ ErrorState *err;
+ err = errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR);
+ err->request = requestLink((HttpRequest *) request);
+ err->xerrno = errno;
+ fwd->fail(err);
+ fwd->dontRetry(true);
+ }
+
+ if (ctrl.fd >= 0)
+ comm_close(ctrl.fd);
+ else
+ delete this;
+}
+
+#endif