From a15645d4357fadaf36b2ce34229dde1c11269cbd Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 18 Mar 2007 16:22:39 +0100 Subject: [PATCH] [MAJOR] completed the HTTP response processing. Now the response is correctly processed in the backend first then in the frontend. It has followed intensive tests to catch regressions, and everything seems OK now, but the code is young anyway. --- include/proto/proto_http.h | 5 +- src/proto_http.c | 1678 ++++++++++++++++++++++-------------- 2 files changed, 1054 insertions(+), 629 deletions(-) diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 324a5776fa..297c5b9367 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -2,7 +2,7 @@ include/proto/proto_http.h This file contains HTTP protocol definitions. - Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + Copyright (C) 2000-2007 Willy Tarreau - w@1wt.eu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -75,7 +75,10 @@ void get_srv_from_appsession(struct session *t, const char *begin, int len); int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hdr_exp *exp); int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_exp *exp); int apply_filters_to_request(struct session *t, struct buffer *req, struct hdr_exp *exp); +int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_exp *exp); void manage_client_side_cookies(struct session *t, struct buffer *req); +void manage_server_side_cookies(struct session *t, struct buffer *rtr); +void check_response_for_cacheability(struct session *t, struct buffer *rtr); int stats_check_uri_auth(struct session *t, struct proxy *backend); void init_proto_http(); diff --git a/src/proto_http.c b/src/proto_http.c index f967c526dd..651ceb683a 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -1,7 +1,7 @@ /* * HTTP protocol analyzer * - * Copyright 2000-2006 Willy Tarreau + * Copyright 2000-2007 Willy Tarreau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -570,7 +570,7 @@ void capture_headers(char *som, struct hdr_idx *idx, /* - * This function parses a response line between and , starting with + * This function parses a status line between and , starting with * parser state . Only states HTTP_MSG_RPVER, HTTP_MSG_RPVER_SP, * HTTP_MSG_RPCODE, HTTP_MSG_RPCODE_SP and HTTP_MSG_RPREASON are handled. Others * will give undefined results. @@ -588,7 +588,7 @@ void capture_headers(char *som, struct hdr_idx *idx, * within its state machine and use the same macros, hence the need for same * labels and variable names. */ -const char *http_parse_rspline(struct http_msg *msg, const char *msg_buf, int state, +const char *http_parse_stsline(struct http_msg *msg, const char *msg_buf, int state, const char *ptr, const char *end, char **ret_ptr, int *ret_state) { @@ -924,7 +924,7 @@ void http_msg_analyzer(struct buffer *buf, struct http_msg *msg, struct hdr_idx case HTTP_MSG_RPCODE: case HTTP_MSG_RPCODE_SP: case HTTP_MSG_RPREASON: - ptr = (char *)http_parse_rspline(msg, buf->data, state, ptr, end, + ptr = (char *)http_parse_stsline(msg, buf->data, state, ptr, end, &buf->lr, &msg->msg_state); if (unlikely(!ptr)) return; @@ -1635,6 +1635,7 @@ int process_cli(struct session *t) if (hdr_idx_add(17, 1, &txn->hdr_idx, txn->hdr_idx.tail) < 0) goto return_bad_req; + t->flags |= SN_CONN_CLOSED; } /************************************************************* @@ -1983,13 +1984,10 @@ int process_cli(struct session *t) */ int process_srv(struct session *t) { - struct http_txn *txn = &t->txn; int s = t->srv_state; int c = t->cli_state; struct buffer *req = t->req; struct buffer *rep = t->rep; - appsess *asession_temp = NULL; - appsess local_asession; int conn_err; #ifdef DEBUG_FULL @@ -2201,507 +2199,62 @@ int process_srv(struct session *t) if (t->srv) t->srv->cum_sess++; rep->rlim = rep->data + BUFSIZE - MAXREWRITE; /* rewrite needed */ + t->txn.rsp.msg_state = HTTP_MSG_RPBEFORE; + /* reset hdr_idx which was already initialized by the request. + * right now, the http parser does it. + * hdr_idx_init(&t->txn.hdr_idx); + */ } tv_eternity(&req->cex); return 1; } } else if (s == SV_STHEADERS) { /* receiving server headers */ - /* now parse the partial (or complete) headers */ - while (rep->lr < rep->r) { /* this loop only sees one header at each iteration */ - char *ptr; - int delete_header; + /* + * Now parse the partial (or complete) lines. + * We will check the response syntax, and also join multi-line + * headers. An index of all the lines will be elaborated while + * parsing. + * + * For the parsing, we use a 28 states FSM. + * + * Here is the information we currently have : + * rep->data + req->som = beginning of response + * rep->data + req->eoh = end of processed headers / start of current one + * rep->data + req->eol = end of current header or line (LF or CRLF) + * rep->lr = first non-visited byte + * rep->r = end of data + */ - ptr = rep->lr; + int cur_idx; + struct http_txn *txn = &t->txn; + struct http_msg *msg = &txn->rsp; + struct proxy *cur_proxy; - /* look for the end of the current header */ - while (ptr < rep->r && *ptr != '\n' && *ptr != '\r') - ptr++; - - if (ptr == rep->h) { - int line, len; + if (likely(rep->lr < rep->r)) + http_msg_analyzer(rep, msg, &txn->hdr_idx); - /* we can only get here after an end of headers */ + /* 1: we might have to print this header in debug mode */ + if (unlikely((global.mode & MODE_DEBUG) && + (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) && + (msg->msg_state == HTTP_MSG_BODY || msg->msg_state == HTTP_MSG_ERROR))) { + char *eol, *sol; - /* first, we'll block if security checks have caught nasty things */ - if (t->flags & SN_CACHEABLE) { - if ((t->flags & SN_CACHE_COOK) && - (t->flags & SN_SCK_ANY) && - (t->be->beprm->options & PR_O_CHK_CACHE)) { + sol = rep->data + msg->som; + eol = sol + msg->sl.rq.l; + debug_hdr("srvrep", t, sol, eol); - /* we're in presence of a cacheable response containing - * a set-cookie header. We'll block it as requested by - * the 'checkcache' option, and send an alert. - */ - tv_eternity(&rep->rex); - tv_eternity(&req->wex); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_secu++; - } - t->be->beprm->denied_resp++; - t->srv_state = SV_STCLOSE; - t->logs.status = 502; - client_return(t, error_message(t, HTTP_ERR_502)); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; + sol += hdr_idx_first_pos(&txn->hdr_idx); + cur_idx = hdr_idx_first_idx(&txn->hdr_idx); - Alert("Blocking cacheable cookie in response from instance %s, server %s.\n", t->be->beprm->id, t->srv->id); - send_log(t->be, LOG_ALERT, "Blocking cacheable cookie in response from instance %s, server %s.\n", t->be->beprm->id, t->srv->id); - - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->be->beprm)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - } - - /* next, we'll block if an 'rspideny' or 'rspdeny' filter matched */ - if (t->flags & SN_SVDENY) { - tv_eternity(&rep->rex); - tv_eternity(&req->wex); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_secu++; - } - t->be->beprm->denied_resp++; - t->srv_state = SV_STCLOSE; - t->logs.status = 502; - client_return(t, error_message(t, HTTP_ERR_502)); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->be->beprm)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - - /* we'll have something else to do here : add new headers ... */ - - if ((t->srv) && !(t->flags & SN_DIRECT) && (t->be->beprm->options & PR_O_COOK_INS) && - (!(t->be->beprm->options & PR_O_COOK_POST) || (txn->meth == HTTP_METH_POST))) { - /* the server is known, it's not the one the client requested, we have to - * insert a set-cookie here, except if we want to insert only on POST - * requests and this one isn't. Note that servers which don't have cookies - * (eg: some backup servers) will return a full cookie removal request. - */ - len = sprintf(trash, "Set-Cookie: %s=%s; path=/\r\n", - t->be->beprm->cookie_name, - t->srv->cookie ? t->srv->cookie : "; Expires=Thu, 01-Jan-1970 00:00:01 GMT"); - - t->flags |= SN_SCK_INSERTED; - - /* Here, we will tell an eventual cache on the client side that we don't - * want it to cache this reply because HTTP/1.0 caches also cache cookies ! - * Some caches understand the correct form: 'no-cache="set-cookie"', but - * others don't (eg: apache <= 1.3.26). So we use 'private' instead. - */ - if (t->be->beprm->options & PR_O_COOK_NOC) - //len += sprintf(newhdr + len, "Cache-control: no-cache=\"set-cookie\"\r\n"); - len += sprintf(trash + len, "Cache-control: private\r\n"); - - if (rep->data + rep->l < rep->h) - /* The data has been stolen, we will crash cleanly instead of corrupting memory */ - *(int *)0 = 0; - buffer_replace2(rep, rep->h, rep->h, trash, len); - } - - /* headers to be added */ - /* FIXME: we should add headers from BE then from FE */ - for (line = 0; line < t->be->fiprm->nb_rspadd; line++) { - len = sprintf(trash, "%s\r\n", t->be->fiprm->rsp_add[line]); - buffer_replace2(rep, rep->h, rep->h, trash, len); - } - - /* add a "connection: close" line if needed */ - if ((t->fe->options | t->be->beprm->options) & PR_O_HTTP_CLOSE) - buffer_replace2(rep, rep->h, rep->h, "Connection: close\r\n", 19); - - t->srv_state = SV_STDATA; - rep->rlim = rep->data + BUFSIZE; /* no more rewrite needed */ - t->logs.t_data = tv_diff(&t->logs.tv_accept, &now); - - /* client connection already closed or option 'httpclose' required : - * we close the server's outgoing connection right now. - */ - if ((req->l == 0) && - (c == CL_STSHUTR || c == CL_STCLOSE || t->be->beprm->options & PR_O_FORCE_CLO)) { - MY_FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&req->wex); - - /* We must ensure that the read part is still alive when switching - * to shutw */ - MY_FD_SET(t->srv_fd, StaticReadEvent); - if (t->be->beprm->srvtimeout) - tv_delayfrom(&rep->rex, &now, t->be->beprm->srvtimeout); - - shutdown(t->srv_fd, SHUT_WR); - t->srv_state = SV_STSHUTW; - } - -#ifdef CONFIG_HAP_TCPSPLICE - if ((t->fe->options & t->be->beprm->options) & PR_O_TCPSPLICE) { - /* TCP splicing supported by both FE and BE */ - tcp_splice_splicefd(t->cli_fd, t->srv_fd, 0); - } -#endif - /* if the user wants to log as soon as possible, without counting - bytes from the server, then this is the right moment. */ - if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) { - t->logs.t_close = t->logs.t_data; /* to get a valid end date */ - t->logs.bytes_in = rep->h - rep->data; - sess_log(t); - } - break; + while (cur_idx) { + eol = sol + txn->hdr_idx.v[cur_idx].len; + debug_hdr("srvhdr", t, sol, eol); + sol = eol + txn->hdr_idx.v[cur_idx].cr + 1; + cur_idx = txn->hdr_idx.v[cur_idx].next; } + } - /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */ - if (ptr > rep->r - 2) { - /* this is a partial header, let's wait for more to come */ - rep->lr = ptr; - break; - } - - // fprintf(stderr,"h=%p, ptr=%p, lr=%p, r=%p, *h=", rep->h, ptr, rep->lr, rep->r); - // write(2, rep->h, ptr - rep->h); fprintf(stderr,"\n"); - - /* now we know that *ptr is either \r or \n, - * and that there are at least 1 char after it. - */ - if ((ptr[0] == ptr[1]) || (ptr[1] != '\r' && ptr[1] != '\n')) - rep->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */ - else - rep->lr = ptr + 2; /* \r\n or \n\r */ - - /* - * now we know that we have a full header ; we can do whatever - * we want with these pointers : - * rep->h = beginning of header - * ptr = end of header (first \r or \n) - * rep->lr = beginning of next line (next rep->h) - * rep->r = end of data (not used at this stage) - */ - - - if (t->logs.status == -1) { - t->logs.logwait &= ~LW_RESP; - t->logs.status = atoi(rep->h + 9); - switch (t->logs.status) { - case 200: - case 203: - case 206: - case 300: - case 301: - case 410: - /* RFC2616 @13.4: - * "A response received with a status code of - * 200, 203, 206, 300, 301 or 410 MAY be stored - * by a cache (...) unless a cache-control - * directive prohibits caching." - * - * RFC2616 @9.5: POST method : - * "Responses to this method are not cacheable, - * unless the response includes appropriate - * Cache-Control or Expires header fields." - */ - if (!(txn->meth == HTTP_METH_POST) && (t->be->beprm->options & PR_O_CHK_CACHE)) - t->flags |= SN_CACHEABLE | SN_CACHE_COOK; - break; - default: - break; - } - } - else if (t->logs.logwait & LW_RSPHDR) { - struct cap_hdr *h; - int len; - for (h = t->fe->fiprm->rsp_cap; h; h = h->next) { - if ((h->namelen + 2 <= ptr - rep->h) && - (rep->h[h->namelen] == ':') && - (strncasecmp(rep->h, h->name, h->namelen) == 0)) { - if (txn->rsp.cap[h->index] == NULL) - txn->rsp.cap[h->index] = - pool_alloc_from(h->pool, h->len + 1); - - if (txn->rsp.cap[h->index] == NULL) { - Alert("HTTP capture : out of memory.\n"); - continue; - } - - len = ptr - (rep->h + h->namelen + 2); - if (len > h->len) - len = h->len; - - memcpy(txn->rsp.cap[h->index], rep->h + h->namelen + 2, len); - txn->rsp.cap[h->index][len]=0; - } - } - - } - - delete_header = 0; - - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) - debug_hdr("srvhdr", t, rep->h, ptr); - - /* remove "connection: " if needed */ - if (!delete_header && - ((t->fe->options | t->be->beprm->options) & PR_O_HTTP_CLOSE) && - (strncasecmp(rep->h, "Connection: ", 12) == 0)) { - delete_header = 1; - } - - /* try headers regexps */ - if (!delete_header && t->be->fiprm->rsp_exp != NULL - && !(t->flags & SN_SVDENY)) { - struct hdr_exp *exp; - char term; - - term = *ptr; - *ptr = '\0'; - exp = t->be->fiprm->rsp_exp; - do { - if (regexec(exp->preg, rep->h, MAX_MATCH, pmatch, 0) == 0) { - switch (exp->action) { - case ACT_ALLOW: - if (!(t->flags & SN_SVDENY)) - t->flags |= SN_SVALLOW; - break; - case ACT_REPLACE: - if (!(t->flags & SN_SVDENY)) { - int len = exp_replace(trash, rep->h, exp->replace, pmatch); - ptr += buffer_replace2(rep, rep->h, ptr, trash, len); - } - break; - case ACT_REMOVE: - if (!(t->flags & SN_SVDENY)) - delete_header = 1; - break; - case ACT_DENY: - if (!(t->flags & SN_SVALLOW)) - t->flags |= SN_SVDENY; - break; - case ACT_PASS: /* we simply don't deny this one */ - break; - } - break; - } - } while ((exp = exp->next) != NULL); - *ptr = term; /* restore the string terminator */ - } - - /* check for cache-control: or pragma: headers */ - if (!delete_header && (t->flags & SN_CACHEABLE)) { - if (strncasecmp(rep->h, "Pragma: no-cache", 16) == 0) - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - else if (strncasecmp(rep->h, "Cache-control: ", 15) == 0) { - if (strncasecmp(rep->h + 15, "no-cache", 8) == 0) { - if (rep->h + 23 == ptr || rep->h[23] == ',') - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - else { - if (strncasecmp(rep->h + 23, "=\"set-cookie", 12) == 0 - && (rep->h[35] == '"' || rep->h[35] == ',')) - t->flags &= ~SN_CACHE_COOK; - } - } else if ((strncasecmp(rep->h + 15, "private", 7) == 0 && - (rep->h + 22 == ptr || rep->h[22] == ',')) - || (strncasecmp(rep->h + 15, "no-store", 8) == 0 && - (rep->h + 23 == ptr || rep->h[23] == ','))) { - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - } else if (strncasecmp(rep->h + 15, "max-age=0", 9) == 0 && - (rep->h + 24 == ptr || rep->h[24] == ',')) { - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - } else if (strncasecmp(rep->h + 15, "s-maxage=0", 10) == 0 && - (rep->h + 25 == ptr || rep->h[25] == ',')) { - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - } else if (strncasecmp(rep->h + 15, "public", 6) == 0 && - (rep->h + 21 == ptr || rep->h[21] == ',')) { - t->flags |= SN_CACHEABLE | SN_CACHE_COOK; - } - } - } - - /* check for server cookies */ - if (!delete_header /*&& (t->proxy->options & PR_O_COOK_ANY)*/ - && (t->be->beprm->cookie_name != NULL || - t->be->fiprm->capture_name != NULL || - t->be->beprm->appsession_name !=NULL) - && (strncasecmp(rep->h, "Set-Cookie: ", 12) == 0)) { - char *p1, *p2, *p3, *p4; - - t->flags |= SN_SCK_ANY; - - p1 = rep->h + 12; /* first char after 'Set-Cookie: ' */ - - while (p1 < ptr) { /* in fact, we'll break after the first cookie */ - while (p1 < ptr && (isspace((int)*p1))) - p1++; - - if (p1 == ptr || *p1 == ';') /* end of cookie */ - break; - - /* p1 is at the beginning of the cookie name */ - p2 = p1; - - while (p2 < ptr && *p2 != '=' && *p2 != ';') - p2++; - - if (p2 == ptr || *p2 == ';') /* next cookie */ - break; - - p3 = p2 + 1; /* skips the '=' sign */ - if (p3 == ptr) - break; - - p4 = p3; - while (p4 < ptr && !isspace((int)*p4) && *p4 != ';') - p4++; - - /* here, we have the cookie name between p1 and p2, - * and its value between p3 and p4. - * we can process it. - */ - - /* first, let's see if we want to capture it */ - if (t->be->fiprm->capture_name != NULL && - t->logs.srv_cookie == NULL && - (p4 - p1 >= t->be->fiprm->capture_namelen) && - memcmp(p1, t->be->fiprm->capture_name, t->be->fiprm->capture_namelen) == 0) { - int log_len = p4 - p1; - - if ((t->logs.srv_cookie = pool_alloc(capture)) == NULL) { - Alert("HTTP logging : out of memory.\n"); - } - - if (log_len > t->be->fiprm->capture_len) - log_len = t->be->fiprm->capture_len; - memcpy(t->logs.srv_cookie, p1, log_len); - t->logs.srv_cookie[log_len] = 0; - } - - if ((p2 - p1 == t->be->beprm->cookie_len) && (t->be->beprm->cookie_name != NULL) && - (memcmp(p1, t->be->beprm->cookie_name, p2 - p1) == 0)) { - /* Cool... it's the right one */ - t->flags |= SN_SCK_SEEN; - - /* If the cookie is in insert mode on a known server, we'll delete - * this occurrence because we'll insert another one later. - * We'll delete it too if the "indirect" option is set and we're in - * a direct access. */ - if (((t->srv) && (t->be->beprm->options & PR_O_COOK_INS)) || - ((t->flags & SN_DIRECT) && (t->be->beprm->options & PR_O_COOK_IND))) { - /* this header must be deleted */ - delete_header = 1; - t->flags |= SN_SCK_DELETED; - } - else if ((t->srv) && (t->srv->cookie) && - (t->be->beprm->options & PR_O_COOK_RW)) { - /* replace bytes p3->p4 with the cookie name associated - * with this server since we know it. - */ - buffer_replace2(rep, p3, p4, t->srv->cookie, t->srv->cklen); - t->flags |= SN_SCK_INSERTED | SN_SCK_DELETED; - } - else if ((t->srv) && (t->srv->cookie) && - (t->be->beprm->options & PR_O_COOK_PFX)) { - /* insert the cookie name associated with this server - * before existing cookie, and insert a delimitor between them.. - */ - buffer_replace2(rep, p3, p3, t->srv->cookie, t->srv->cklen + 1); - p3[t->srv->cklen] = COOKIE_DELIM; - t->flags |= SN_SCK_INSERTED | SN_SCK_DELETED; - } - break; - } - - /* first, let's see if the cookie is our appcookie*/ - if ((t->be->beprm->appsession_name != NULL) && - (memcmp(p1, t->be->beprm->appsession_name, p2 - p1) == 0)) { - - /* Cool... it's the right one */ - - size_t server_id_len = strlen(t->srv->id) + 1; - asession_temp = &local_asession; - - if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { - Alert("Not enought Memory process_srv():asession->sessid:malloc().\n"); - send_log(t->be, LOG_ALERT, "Not enought Memory process_srv():asession->sessid:malloc().\n"); - } - memcpy(asession_temp->sessid, p3, t->be->beprm->appsession_len); - asession_temp->sessid[t->be->beprm->appsession_len] = 0; - asession_temp->serverid = NULL; - - /* only do insert, if lookup fails */ - if (chtbl_lookup(&(t->be->htbl_proxy), (void *) &asession_temp) != 0) { - if ((asession_temp = pool_alloc(appsess)) == NULL) { - Alert("Not enought Memory process_srv():asession:calloc().\n"); - send_log(t->be, LOG_ALERT, "Not enought Memory process_srv():asession:calloc().\n"); - return 0; - } - asession_temp->sessid = local_asession.sessid; - asession_temp->serverid = local_asession.serverid; - chtbl_insert(&(t->be->beprm->htbl_proxy), (void *) asession_temp); - }/* end if (chtbl_lookup()) */ - else { - /* free wasted memory */ - pool_free_to(apools.sessid, local_asession.sessid); - } /* end else from if (chtbl_lookup()) */ - - if (asession_temp->serverid == NULL) { - if ((asession_temp->serverid = pool_alloc_from(apools.serverid, apools.ser_msize)) == NULL) { - Alert("Not enought Memory process_srv():asession->sessid:malloc().\n"); - send_log(t->be, LOG_ALERT, "Not enought Memory process_srv():asession->sessid:malloc().\n"); - } - asession_temp->serverid[0] = '\0'; - } - - if (asession_temp->serverid[0] == '\0') - memcpy(asession_temp->serverid,t->srv->id,server_id_len); - - tv_delayfrom(&asession_temp->expire, &now, t->be->beprm->appsession_timeout); - -#if defined(DEBUG_HASH) - print_table(&(t->be->beprm->htbl_proxy)); -#endif - break; - }/* end if ((t->proxy->appsession_name != NULL) ... */ - else { - // fprintf(stderr,"Ignoring unknown cookie : "); - // write(2, p1, p2-p1); - // fprintf(stderr," = "); - // write(2, p3, p4-p3); - // fprintf(stderr,"\n"); - } - break; /* we don't want to loop again since there cannot be another cookie on the same line */ - } /* we're now at the end of the cookie value */ - } /* end of cookie processing */ - - /* check for any set-cookie in case we check for cacheability */ - if (!delete_header && !(t->flags & SN_SCK_ANY) && - (t->be->beprm->options & PR_O_CHK_CACHE) && - (strncasecmp(rep->h, "Set-Cookie: ", 12) == 0)) { - t->flags |= SN_SCK_ANY; - } - - /* let's look if we have to delete this header */ - if (delete_header && !(t->flags & SN_SVDENY)) - buffer_replace2(rep, rep->h, rep->lr, "", 0); - - rep->h = rep->lr; - } /* while (rep->lr < rep->r) */ - - /* end of header processing (even if incomplete) */ if ((rep->l < rep->rlim - rep->data) && ! MY_FD_ISSET(t->srv_fd, StaticReadEvent)) { /* fd in StaticReadEvent was disabled, perhaps because of a previous buffer @@ -2715,147 +2268,487 @@ int process_srv(struct session *t) tv_eternity(&rep->rex); } - /* read error, write error */ - if (req->flags & BF_WRITE_ERROR || rep->flags & BF_READ_ERROR) { - tv_eternity(&rep->rex); - tv_eternity(&req->wex); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_resp++; - } - t->be->failed_resp++; - t->srv_state = SV_STCLOSE; - t->logs.status = 502; - client_return(t, error_message(t, HTTP_ERR_502)); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVCL; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->be->beprm)) - task_wakeup(&rq, t->srv->queue_mgt); - return 1; - } - /* end of client write or end of server read. - * since we are in header mode, if there's no space left for headers, we - * won't be able to free more later, so the session will never terminate. + /* + * Now we quickly check if we have found a full valid response. + * If not so, we check the FD and buffer states before leaving. + * A full response is indicated by the fact that we have seen + * the double LF/CRLF, so the state is HTTP_MSG_BODY. Invalid + * responses are checked first. + * + * Depending on whether the client is still there or not, we + * may send an error response back or not. Note that normally + * we should only check for HTTP status there, and check I/O + * errors somewhere else. */ - else if (rep->flags & BF_READ_NULL || c == CL_STSHUTW || c == CL_STCLOSE || rep->l >= rep->rlim - rep->data) { - MY_FD_CLR(t->srv_fd, StaticReadEvent); - tv_eternity(&rep->rex); - shutdown(t->srv_fd, SHUT_RD); - t->srv_state = SV_STSHUTR; - //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); - return 1; - } - /* read timeout : return a 504 to the client. - */ - else if (MY_FD_ISSET(t->srv_fd, StaticReadEvent) && tv_cmp2_ms(&rep->rex, &now) <= 0) { - tv_eternity(&rep->rex); - tv_eternity(&req->wex); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_resp++; - } - t->be->failed_resp++; - t->srv_state = SV_STCLOSE; - t->logs.status = 504; - client_return(t, error_message(t, HTTP_ERR_504)); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->be->beprm)) - task_wakeup(&rq, t->srv->queue_mgt); - return 1; - } - /* last client read and buffer empty */ - /* FIXME!!! here, we don't want to switch to SHUTW if the - * client shuts read too early, because we may still have - * some work to do on the headers. - * The side-effect is that if the client completely closes its - * connection during SV_STHEADER, the connection to the server - * is kept until a response comes back or the timeout is reached. - */ - else if ((/*c == CL_STSHUTR ||*/ c == CL_STCLOSE) && (req->l == 0)) { - MY_FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&req->wex); + if (unlikely(msg->msg_state != HTTP_MSG_BODY)) { - /* We must ensure that the read part is still alive when switching - * to shutw */ - MY_FD_SET(t->srv_fd, StaticReadEvent); - if (t->be->beprm->srvtimeout) - tv_delayfrom(&rep->rex, &now, t->be->beprm->srvtimeout); - - shutdown(t->srv_fd, SHUT_WR); - t->srv_state = SV_STSHUTW; - return 1; - } - /* write timeout */ - /* FIXME!!! here, we don't want to switch to SHUTW if the - * client shuts read too early, because we may still have - * some work to do on the headers. - */ - else if (MY_FD_ISSET(t->srv_fd, StaticWriteEvent) && tv_cmp2_ms(&req->wex, &now) <= 0) { - MY_FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&req->wex); - shutdown(t->srv_fd, SHUT_WR); - /* We must ensure that the read part is still alive when switching - * to shutw */ - MY_FD_SET(t->srv_fd, StaticReadEvent); - if (t->be->beprm->srvtimeout) - tv_delayfrom(&rep->rex, &now, t->be->beprm->srvtimeout); - - /* We must ensure that the read part is still alive when switching - * to shutw */ - MY_FD_SET(t->srv_fd, StaticReadEvent); - if (t->be->beprm->srvtimeout) - tv_delayfrom(&rep->rex, &now, t->be->beprm->srvtimeout); - - t->srv_state = SV_STSHUTW; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; - return 1; - } - - if (req->l == 0) { - if (MY_FD_ISSET(t->srv_fd, StaticWriteEvent)) { - MY_FD_CLR(t->srv_fd, StaticWriteEvent); /* stop writing */ + /* Invalid response, or read error or write error */ + if (unlikely((msg->msg_state == HTTP_MSG_ERROR) || + (req->flags & BF_WRITE_ERROR) || + (rep->flags & BF_READ_ERROR))) { + tv_eternity(&rep->rex); tv_eternity(&req->wex); - } - } - else { /* client buffer not empty */ - if (! MY_FD_ISSET(t->srv_fd, StaticWriteEvent)) { - MY_FD_SET(t->srv_fd, StaticWriteEvent); /* restart writing */ - if (t->be->beprm->srvtimeout) { - tv_delayfrom(&req->wex, &now, t->be->beprm->srvtimeout); - /* FIXME: to prevent the server from expiring read timeouts during writes, - * we refresh it. */ - rep->rex = req->wex; + fd_delete(t->srv_fd); + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_resp++; } - else + t->be->failed_resp++; + t->srv_state = SV_STCLOSE; + t->logs.status = 502; + client_return(t, error_message(t, HTTP_ERR_502)); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVCL; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_H; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (t->srv && may_dequeue_tasks(t->srv, t->be->beprm)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + + /* end of client write or end of server read. + * since we are in header mode, if there's no space left for headers, we + * won't be able to free more later, so the session will never terminate. + */ + else if (unlikely(rep->flags & BF_READ_NULL || + c == CL_STSHUTW || c == CL_STCLOSE || + rep->l >= rep->rlim - rep->data)) { + MY_FD_CLR(t->srv_fd, StaticReadEvent); + tv_eternity(&rep->rex); + shutdown(t->srv_fd, SHUT_RD); + t->srv_state = SV_STSHUTR; + //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); + return 1; + } + + /* read timeout : return a 504 to the client. + */ + else if (unlikely(MY_FD_ISSET(t->srv_fd, StaticReadEvent) && + tv_cmp2_ms(&rep->rex, &now) <= 0)) { + tv_eternity(&rep->rex); + tv_eternity(&req->wex); + fd_delete(t->srv_fd); + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_resp++; + } + t->be->failed_resp++; + t->srv_state = SV_STCLOSE; + t->logs.status = 504; + client_return(t, error_message(t, HTTP_ERR_504)); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVTO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_H; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (t->srv && may_dequeue_tasks(t->srv, t->be->beprm)) + task_wakeup(&rq, t->srv->queue_mgt); + return 1; + } + + /* last client read and buffer empty */ + /* FIXME!!! here, we don't want to switch to SHUTW if the + * client shuts read too early, because we may still have + * some work to do on the headers. + * The side-effect is that if the client completely closes its + * connection during SV_STHEADER, the connection to the server + * is kept until a response comes back or the timeout is reached. + */ + else if (unlikely((/*c == CL_STSHUTR ||*/ c == CL_STCLOSE) && + (req->l == 0))) { + MY_FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&req->wex); + + /* We must ensure that the read part is still + * alive when switching to shutw */ + MY_FD_SET(t->srv_fd, StaticReadEvent); + if (t->be->beprm->srvtimeout) + tv_delayfrom(&rep->rex, &now, t->be->beprm->srvtimeout); + + shutdown(t->srv_fd, SHUT_WR); + t->srv_state = SV_STSHUTW; + return 1; + } + + /* write timeout */ + /* FIXME!!! here, we don't want to switch to SHUTW if the + * client shuts read too early, because we may still have + * some work to do on the headers. + */ + else if (unlikely(MY_FD_ISSET(t->srv_fd, StaticWriteEvent) && + tv_cmp2_ms(&req->wex, &now) <= 0)) { + MY_FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&req->wex); + shutdown(t->srv_fd, SHUT_WR); + /* We must ensure that the read part is still alive + * when switching to shutw */ + MY_FD_SET(t->srv_fd, StaticReadEvent); + if (t->be->beprm->srvtimeout) + tv_delayfrom(&rep->rex, &now, t->be->beprm->srvtimeout); + + t->srv_state = SV_STSHUTW; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVTO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_H; + return 1; + } + + /* + * And now the non-error cases. + */ + + /* Data remaining in the request buffer. + * This happens during the first pass here, and during + * long posts. + */ + else if (likely(req->l)) { + if (! MY_FD_ISSET(t->srv_fd, StaticWriteEvent)) { + MY_FD_SET(t->srv_fd, StaticWriteEvent); /* restart writing */ + if (t->be->beprm->srvtimeout) { + tv_delayfrom(&req->wex, &now, t->be->beprm->srvtimeout); + /* FIXME: to prevent the server from expiring read timeouts during writes, + * we refresh it. */ + rep->rex = req->wex; + } + else + tv_eternity(&req->wex); + } + } + + /* nothing left in the request buffer */ + else { + if (MY_FD_ISSET(t->srv_fd, StaticWriteEvent)) { + MY_FD_CLR(t->srv_fd, StaticWriteEvent); /* stop writing */ tv_eternity(&req->wex); + } + } + + return t->srv_state != SV_STHEADERS; + } + + + /***************************************************************** + * More interesting part now : we know that we have a complete * + * response which at least looks like HTTP. We have an indicator * + * of each header's length, so we can parse them quickly. * + ****************************************************************/ + + /* + * 1: get the status code and check for cacheability. + */ + + t->logs.logwait &= ~LW_RESP; + t->logs.status = strl2ui(rep->data + msg->sl.st.c, msg->sl.st.c_l); + + switch (t->logs.status) { + case 200: + case 203: + case 206: + case 300: + case 301: + case 410: + /* RFC2616 @13.4: + * "A response received with a status code of + * 200, 203, 206, 300, 301 or 410 MAY be stored + * by a cache (...) unless a cache-control + * directive prohibits caching." + * + * RFC2616 @9.5: POST method : + * "Responses to this method are not cacheable, + * unless the response includes appropriate + * Cache-Control or Expires header fields." + */ + if (likely(txn->meth != HTTP_METH_POST) && + unlikely(t->be->beprm->options & PR_O_CHK_CACHE)) + t->flags |= SN_CACHEABLE | SN_CACHE_COOK; + break; + default: + break; + } + + /* + * 2: we may need to capture headers + */ + if (unlikely((t->logs.logwait & LW_RSPHDR) && t->fe->fiprm->rsp_cap)) + capture_headers(rep->data + msg->som, &txn->hdr_idx, + txn->rsp.cap, t->fe->fiprm->rsp_cap); + + /* + * 3: we will have to evaluate the filters. + * As opposed to version 1.2, now they will be evaluated in the + * filters order and not in the header order. This means that + * each filter has to be validated among all headers. + * + * Filters are tried with ->be first, then with ->fe if it is + * different from ->be. + */ + + t->flags &= ~SN_CONN_CLOSED; /* prepare for inspection */ + + cur_proxy = t->be; + while (1) { + struct proxy *rule_set = cur_proxy->fiprm; + + /* try headers filters */ + if (rule_set->rsp_exp != NULL) { + if (apply_filters_to_response(t, rep, rule_set->rsp_exp) < 0) { + return_bad_resp: + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_resp++; + } + cur_proxy->beprm->failed_resp++; + return_srv_prx_502: + tv_eternity(&rep->rex); + tv_eternity(&req->wex); + fd_delete(t->srv_fd); + t->srv_state = SV_STCLOSE; + t->logs.status = 502; + client_return(t, error_message(t, HTTP_ERR_502)); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_H; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (t->srv && may_dequeue_tasks(t->srv, cur_proxy->beprm)) + task_wakeup(&rq, t->srv->queue_mgt); + return 1; + } + } + + /* has the response been denied ? */ + if (t->flags & SN_SVDENY) { + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_secu++; + } + cur_proxy->beprm->denied_resp++; + goto return_srv_prx_502; + } + + /* We might have to check for "Connection:" */ + if (((t->fe->options | t->be->beprm->options) & PR_O_HTTP_CLOSE) && + !(t->flags & SN_CONN_CLOSED)) { + char *cur_ptr, *cur_end, *cur_next; + int cur_idx, old_idx, delta; + struct hdr_idx_elem *cur_hdr; + + cur_next = rep->data + txn->rsp.som + hdr_idx_first_pos(&txn->hdr_idx); + old_idx = 0; + + while ((cur_idx = txn->hdr_idx.v[old_idx].next)) { + cur_hdr = &txn->hdr_idx.v[cur_idx]; + cur_ptr = cur_next; + cur_end = cur_ptr + cur_hdr->len; + cur_next = cur_end + cur_hdr->cr + 1; + + if (strncasecmp(cur_ptr, "Connection:", 11) == 0) { + /* 3 possibilities : + * - we have already set Connection: close, + * so we remove this line. + * - we have not yet set Connection: close, + * but this line indicates close. We leave + * it untouched and set the flag. + * - we have not yet set Connection: close, + * and this line indicates non-close. We + * replace it. + */ + if (t->flags & SN_CONN_CLOSED) { + delta = buffer_replace2(rep, cur_ptr, cur_next, NULL, 0); + txn->rsp.eoh += delta; + cur_next += delta; + txn->hdr_idx.v[old_idx].next = cur_hdr->next; + txn->hdr_idx.used--; + cur_hdr->len = 0; + } else { + if (cur_ptr + 17 > cur_end || + !http_is_lws[(unsigned char)*(cur_ptr+17)] || + strncasecmp(cur_ptr+11, " close", 6)) { + delta = buffer_replace2(rep, cur_ptr+11, cur_end, + " close", 6); + cur_next += delta; + cur_hdr->len += delta; + txn->rsp.eoh += delta; + } + t->flags |= SN_CONN_CLOSED; + } + } + old_idx = cur_idx; + } + } + + /* add response headers from the rule sets in the same order */ + for (cur_idx = 0; cur_idx < rule_set->nb_rspadd; cur_idx++) { + int len; + + len = sprintf(trash, "%s\r\n", rule_set->rsp_add[cur_idx]); + len = buffer_replace2(rep, rep->data + txn->rsp.eoh, + rep->data + txn->rsp.eoh, trash, len); + txn->rsp.eoh += len; + + if (unlikely(hdr_idx_add(len - 2, 1, &txn->hdr_idx, txn->hdr_idx.tail) < 0)) + goto return_bad_resp; + } + + /* check whether we're already working on the frontend */ + if (cur_proxy == t->fe) + break; + cur_proxy = t->fe; + } + + /* + * 4: check for server cookie. + */ + manage_server_side_cookies(t, rep); + + /* + * 5: add server cookie in the response if needed + */ + if ((t->srv) && !(t->flags & SN_DIRECT) && (t->be->beprm->options & PR_O_COOK_INS) && + (!(t->be->beprm->options & PR_O_COOK_POST) || (txn->meth == HTTP_METH_POST))) { + int len; + + /* the server is known, it's not the one the client requested, we have to + * insert a set-cookie here, except if we want to insert only on POST + * requests and this one isn't. Note that servers which don't have cookies + * (eg: some backup servers) will return a full cookie removal request. + */ + len = sprintf(trash, "Set-Cookie: %s=%s; path=/\r\n", + t->be->beprm->cookie_name, + t->srv->cookie ? t->srv->cookie : "; Expires=Thu, 01-Jan-1970 00:00:01 GMT"); + + len = buffer_replace2(rep, rep->data + txn->rsp.eoh, rep->data + txn->rsp.eoh, trash, len); + txn->rsp.eoh += len; + + if (hdr_idx_add(len - 2, 1, &txn->hdr_idx, txn->hdr_idx.tail) < 0) + goto return_bad_resp; + + t->flags |= SN_SCK_INSERTED; + + /* Here, we will tell an eventual cache on the client side that we don't + * want it to cache this reply because HTTP/1.0 caches also cache cookies ! + * Some caches understand the correct form: 'no-cache="set-cookie"', but + * others don't (eg: apache <= 1.3.26). So we use 'private' instead. + */ + if (t->be->beprm->options & PR_O_COOK_NOC) { + //len += sprintf(newhdr + len, "Cache-control: no-cache=\"set-cookie\"\r\n"); + len = sprintf(trash, "Cache-control: private\r\n"); + + len = buffer_replace2(rep, rep->data + txn->rsp.eoh, + rep->data + txn->rsp.eoh, trash, len); + txn->rsp.eoh += len; + if (hdr_idx_add(len - 2, 1, &txn->hdr_idx, txn->hdr_idx.tail) < 0) + goto return_bad_resp; } } - /* be nice with the client side which would like to send a complete header - * FIXME: COMPLETELY BUGGY !!! not all headers may be processed because the client - * would read all remaining data at once ! The client should not write past rep->lr - * when the server is in header state. + + /* + * 6: check for cache-control or pragma headers. */ - //return header_processed; - return t->srv_state != SV_STHEADERS; + check_response_for_cacheability(t, rep); + + + /* + * 7: check if result will be cacheable with a cookie. + * We'll block the response if security checks have caught + * nasty things such as a cacheable cookie. + */ + if (((t->flags & (SN_CACHEABLE | SN_CACHE_COOK | SN_SCK_ANY)) == + (SN_CACHEABLE | SN_CACHE_COOK | SN_SCK_ANY)) && + (t->be->beprm->options & PR_O_CHK_CACHE)) { + + /* we're in presence of a cacheable response containing + * a set-cookie header. We'll block it as requested by + * the 'checkcache' option, and send an alert. + */ + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_secu++; + } + t->be->beprm->denied_resp++; + + Alert("Blocking cacheable cookie in response from instance %s, server %s.\n", + t->be->beprm->id, t->srv?t->srv->id:""); + send_log(t->be, LOG_ALERT, + "Blocking cacheable cookie in response from instance %s, server %s.\n", + t->be->beprm->id, t->srv?t->srv->id:""); + goto return_srv_prx_502; + } + + /* + * 8: add "Connection: close" if needed and not yet set. + */ + if (((t->fe->options | t->be->beprm->options) & PR_O_HTTP_CLOSE) && + !(t->flags & SN_CONN_CLOSED)) { + int len; + len = buffer_replace2(rep, rep->data + txn->rsp.eoh, + rep->data + txn->rsp.eoh, "Connection: close\r\n", 19); + txn->rsp.eoh += len; + + if (hdr_idx_add(17, 1, &txn->hdr_idx, txn->hdr_idx.tail) < 0) + goto return_bad_resp; + t->flags |= SN_CONN_CLOSED; + } + + + /************************************************************* + * OK, that's finished for the headers. We have done what we * + * could. Let's switch to the DATA state. * + ************************************************************/ + + t->srv_state = SV_STDATA; + rep->rlim = rep->data + BUFSIZE; /* no more rewrite needed */ + t->logs.t_data = tv_diff(&t->logs.tv_accept, &now); + + /* client connection already closed or option 'forceclose' required : + * we close the server's outgoing connection right now. + */ + if ((req->l == 0) && + (c == CL_STSHUTR || c == CL_STCLOSE || t->be->beprm->options & PR_O_FORCE_CLO)) { + MY_FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&req->wex); + + /* We must ensure that the read part is still alive when switching + * to shutw */ + MY_FD_SET(t->srv_fd, StaticReadEvent); + if (t->be->beprm->srvtimeout) + tv_delayfrom(&rep->rex, &now, t->be->beprm->srvtimeout); + + shutdown(t->srv_fd, SHUT_WR); + t->srv_state = SV_STSHUTW; + } + +#ifdef CONFIG_HAP_TCPSPLICE + if ((t->fe->options & t->be->beprm->options) & PR_O_TCPSPLICE) { + /* TCP splicing supported by both FE and BE */ + tcp_splice_splicefd(t->cli_fd, t->srv_fd, 0); + } +#endif + /* if the user wants to log as soon as possible, without counting + bytes from the server, then this is the right moment. */ + if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) { + t->logs.t_close = t->logs.t_data; /* to get a valid end date */ + t->logs.bytes_in = rep->h - rep->data; + sess_log(t); + } + + /* Note: we must not try to cheat by jumping directly to DATA, + * otherwise we would not let the client side wake up. + */ + + return 1; } else if (s == SV_STDATA) { /* read or write error */ @@ -3135,7 +3028,8 @@ int process_srv(struct session *t) else { /* SV_STCLOSE : nothing to do */ if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { int len; - len = sprintf(trash, "%08x:%s.srvcls[%04x:%04x]\n", t->uniq_id, t->be->beprm->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); + len = sprintf(trash, "%08x:%s.srvcls[%04x:%04x]\n", + t->uniq_id, t->be->beprm->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); write(1, trash, len); } return 0; @@ -3657,6 +3551,8 @@ int produce_content_stats_proxy(struct session *s, struct proxy *px) /* Iterate the same filter through all request headers. * Returns 1 if this filter can be stopped upon return, otherwise 0. + * Since it can manage the switch to another backend, it updates the per-proxy + * DENY stats. */ int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hdr_exp *exp) { @@ -3785,6 +3681,8 @@ int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hd /* Apply the filter to the request line. * Returns 0 if nothing has been done, 1 if the filter has been applied, * or -1 if a replacement resulted in an invalid request line. + * Since it can manage the switch to another backend, it updates the per-proxy + * DENY stats. */ int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_exp *exp) { @@ -3897,7 +3795,8 @@ int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_e /* * Apply all the req filters to all headers in buffer of session . * Returns 0 if everything is alright, or -1 in case a replacement lead to an - * unparsable request. + * unparsable request. Since it can manage the switch to another backend, it + * updates the per-proxy DENY stats. */ int apply_filters_to_request(struct session *t, struct buffer *req, struct hdr_exp *exp) { @@ -3935,6 +3834,7 @@ int apply_filters_to_request(struct session *t, struct buffer *req, struct hdr_e } + /* * Manager client-side cookie */ @@ -3952,8 +3852,8 @@ void manage_client_side_cookies(struct session *t, struct buffer *req) int cur_idx, old_idx; if (t->be->beprm->cookie_name == NULL && - t->be->beprm->appsession_name ==NULL && - t->be->fiprm->capture_name != NULL) + t->be->beprm->appsession_name == NULL && + t->be->fiprm->capture_name == NULL) return; /* Iterate through the headers. @@ -4276,6 +4176,528 @@ void manage_client_side_cookies(struct session *t, struct buffer *req) } +/* Iterate the same filter through all response headers contained in . + * Returns 1 if this filter can be stopped upon return, otherwise 0. + */ +int apply_filter_to_resp_headers(struct session *t, struct buffer *rtr, struct hdr_exp *exp) +{ + char term; + char *cur_ptr, *cur_end, *cur_next; + int cur_idx, old_idx, last_hdr; + struct http_txn *txn = &t->txn; + struct hdr_idx_elem *cur_hdr; + int len, delta; + + last_hdr = 0; + + cur_next = rtr->data + txn->rsp.som + hdr_idx_first_pos(&txn->hdr_idx); + old_idx = 0; + + while (!last_hdr) { + if (unlikely(t->flags & SN_SVDENY)) + return 1; + else if (unlikely(t->flags & SN_SVALLOW) && + (exp->action == ACT_ALLOW || + exp->action == ACT_DENY)) + return 0; + + cur_idx = txn->hdr_idx.v[old_idx].next; + if (!cur_idx) + break; + + cur_hdr = &txn->hdr_idx.v[cur_idx]; + cur_ptr = cur_next; + cur_end = cur_ptr + cur_hdr->len; + cur_next = cur_end + cur_hdr->cr + 1; + + /* Now we have one header between cur_ptr and cur_end, + * and the next header starts at cur_next. + */ + + /* The annoying part is that pattern matching needs + * that we modify the contents to null-terminate all + * strings before testing them. + */ + + term = *cur_end; + *cur_end = '\0'; + + if (regexec(exp->preg, cur_ptr, MAX_MATCH, pmatch, 0) == 0) { + switch (exp->action) { + case ACT_ALLOW: + t->flags |= SN_SVALLOW; + last_hdr = 1; + break; + + case ACT_DENY: + t->flags |= SN_SVDENY; + last_hdr = 1; + break; + + case ACT_REPLACE: + len = exp_replace(trash, cur_ptr, exp->replace, pmatch); + delta = buffer_replace2(rtr, cur_ptr, cur_end, trash, len); + /* FIXME: if the user adds a newline in the replacement, the + * index will not be recalculated for now, and the new line + * will not be counted as a new header. + */ + + cur_end += delta; + cur_next += delta; + cur_hdr->len += delta; + txn->rsp.eoh += delta; + break; + + case ACT_REMOVE: + delta = buffer_replace2(rtr, cur_ptr, cur_next, NULL, 0); + cur_next += delta; + + /* FIXME: this should be a separate function */ + txn->rsp.eoh += delta; + txn->hdr_idx.v[old_idx].next = cur_hdr->next; + txn->hdr_idx.used--; + cur_hdr->len = 0; + cur_end = NULL; /* null-term has been rewritten */ + break; + + } + } + if (cur_end) + *cur_end = term; /* restore the string terminator */ + + /* keep the link from this header to next one in case of later + * removal of next header. + */ + old_idx = cur_idx; + } + return 0; +} + + +/* Apply the filter to the status line in the response buffer . + * Returns 0 if nothing has been done, 1 if the filter has been applied, + * or -1 if a replacement resulted in an invalid status line. + */ +int apply_filter_to_sts_line(struct session *t, struct buffer *rtr, struct hdr_exp *exp) +{ + char term; + char *cur_ptr, *cur_end; + int done; + struct http_txn *txn = &t->txn; + int len, delta; + + + if (unlikely(t->flags & SN_SVDENY)) + return 1; + else if (unlikely(t->flags & SN_SVALLOW) && + (exp->action == ACT_ALLOW || + exp->action == ACT_DENY)) + return 0; + else if (exp->action == ACT_REMOVE) + return 0; + + done = 0; + + cur_ptr = rtr->data + txn->rsp.som; + cur_end = cur_ptr + txn->rsp.sl.rq.l; + + /* Now we have the status line between cur_ptr and cur_end */ + + /* The annoying part is that pattern matching needs + * that we modify the contents to null-terminate all + * strings before testing them. + */ + + term = *cur_end; + *cur_end = '\0'; + + if (regexec(exp->preg, cur_ptr, MAX_MATCH, pmatch, 0) == 0) { + switch (exp->action) { + case ACT_ALLOW: + t->flags |= SN_SVALLOW; + done = 1; + break; + + case ACT_DENY: + t->flags |= SN_SVDENY; + done = 1; + break; + + case ACT_REPLACE: + *cur_end = term; /* restore the string terminator */ + len = exp_replace(trash, cur_ptr, exp->replace, pmatch); + delta = buffer_replace2(rtr, cur_ptr, cur_end, trash, len); + /* FIXME: if the user adds a newline in the replacement, the + * index will not be recalculated for now, and the new line + * will not be counted as a new header. + */ + + txn->rsp.eoh += delta; + cur_end += delta; + + cur_end = (char *)http_parse_stsline(&txn->rsp, rtr->data, + HTTP_MSG_RQMETH, + cur_ptr, cur_end + 1, + NULL, NULL); + if (unlikely(!cur_end)) + return -1; + + /* we have a full respnse and we know that we have either a CR + * or an LF at . + */ + t->logs.status = strl2ui(rtr->data + txn->rsp.sl.st.c, txn->rsp.sl.st.c_l); + hdr_idx_set_start(&txn->hdr_idx, txn->rsp.sl.rq.l, *cur_end == '\r'); + /* there is no point trying this regex on headers */ + return 1; + } + } + *cur_end = term; /* restore the string terminator */ + return done; +} + + + +/* + * Apply all the resp filters to all headers in buffer of session . + * Returns 0 if everything is alright, or -1 in case a replacement lead to an + * unparsable response. + */ +int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_exp *exp) +{ + /* iterate through the filters in the outer loop */ + while (exp && !(t->flags & SN_SVDENY)) { + int ret; + + /* + * The interleaving of transformations and verdicts + * makes it difficult to decide to continue or stop + * the evaluation. + */ + + if ((t->flags & SN_SVALLOW) && + (exp->action == ACT_ALLOW || exp->action == ACT_DENY || + exp->action == ACT_PASS)) { + exp = exp->next; + continue; + } + + /* Apply the filter to the status line. */ + ret = apply_filter_to_sts_line(t, rtr, exp); + if (unlikely(ret < 0)) + return -1; + + if (likely(ret == 0)) { + /* The filter did not match the response, it can be + * iterated through all headers. + */ + apply_filter_to_resp_headers(t, rtr, exp); + } + exp = exp->next; + } + return 0; +} + + + +/* + * Manager server-side cookies + */ +void manage_server_side_cookies(struct session *t, struct buffer *rtr) +{ + struct http_txn *txn = &t->txn; + char *p1, *p2, *p3, *p4; + + appsess *asession_temp = NULL; + appsess local_asession; + + char *cur_ptr, *cur_end, *cur_next; + int cur_idx, old_idx, delta; + + if (t->be->beprm->cookie_name == NULL && + t->be->beprm->appsession_name == NULL && + t->be->fiprm->capture_name == NULL && + !(t->be->beprm->options & PR_O_CHK_CACHE)) + return; + + /* Iterate through the headers. + * we start with the start line. + */ + old_idx = 0; + cur_next = rtr->data + txn->rsp.som + hdr_idx_first_pos(&txn->hdr_idx); + + while ((cur_idx = txn->hdr_idx.v[old_idx].next)) { + struct hdr_idx_elem *cur_hdr; + + cur_hdr = &txn->hdr_idx.v[cur_idx]; + cur_ptr = cur_next; + cur_end = cur_ptr + cur_hdr->len; + cur_next = cur_end + cur_hdr->cr + 1; + + /* We have one full header between cur_ptr and cur_end, and the + * next header starts at cur_next. We're only interested in + * "Cookie:" headers. + */ + + if ((cur_end - cur_ptr <= 11) || + (strncasecmp(cur_ptr, "Set-Cookie:", 11) != 0)) { + old_idx = cur_idx; + continue; + } + + /* OK, right now we know we have a set-cookie at cur_ptr */ + t->flags |= SN_SCK_ANY; + + + /* maybe we only wanted to see if there was a set-cookie */ + if (t->be->beprm->cookie_name == NULL && + t->be->beprm->appsession_name == NULL && + t->be->fiprm->capture_name == NULL) + return; + + p1 = cur_ptr + 11; /* first char after 'Set-Cookie:' */ + + while (p1 < cur_end) { /* in fact, we'll break after the first cookie */ + while (p1 < cur_end && (isspace((int)*p1))) + p1++; + + if (p1 == cur_end || *p1 == ';') /* end of cookie */ + break; + + /* p1 is at the beginning of the cookie name */ + p2 = p1; + + while (p2 < cur_end && *p2 != '=' && *p2 != ';') + p2++; + + if (p2 == cur_end || *p2 == ';') /* next cookie */ + break; + + p3 = p2 + 1; /* skip the '=' sign */ + if (p3 == cur_end) + break; + + p4 = p3; + while (p4 < cur_end && !isspace((int)*p4) && *p4 != ';') + p4++; + + /* here, we have the cookie name between p1 and p2, + * and its value between p3 and p4. + * we can process it. + */ + + /* first, let's see if we want to capture it */ + if (t->be->fiprm->capture_name != NULL && + t->logs.srv_cookie == NULL && + (p4 - p1 >= t->be->fiprm->capture_namelen) && + memcmp(p1, t->be->fiprm->capture_name, t->be->fiprm->capture_namelen) == 0) { + int log_len = p4 - p1; + + if ((t->logs.srv_cookie = pool_alloc(capture)) == NULL) { + Alert("HTTP logging : out of memory.\n"); + } + + if (log_len > t->be->fiprm->capture_len) + log_len = t->be->fiprm->capture_len; + memcpy(t->logs.srv_cookie, p1, log_len); + t->logs.srv_cookie[log_len] = 0; + } + + /* now check if we need to process it for persistence */ + if ((p2 - p1 == t->be->beprm->cookie_len) && (t->be->beprm->cookie_name != NULL) && + (memcmp(p1, t->be->beprm->cookie_name, p2 - p1) == 0)) { + /* Cool... it's the right one */ + t->flags |= SN_SCK_SEEN; + + /* If the cookie is in insert mode on a known server, we'll delete + * this occurrence because we'll insert another one later. + * We'll delete it too if the "indirect" option is set and we're in + * a direct access. */ + if (((t->srv) && (t->be->beprm->options & PR_O_COOK_INS)) || + ((t->flags & SN_DIRECT) && (t->be->beprm->options & PR_O_COOK_IND))) { + /* this header must be deleted */ + delta = buffer_replace2(rtr, cur_ptr, cur_next, NULL, 0); + txn->hdr_idx.v[old_idx].next = cur_hdr->next; + txn->hdr_idx.used--; + cur_hdr->len = 0; + cur_next += delta; + txn->rsp.eoh += delta; + + t->flags |= SN_SCK_DELETED; + } + else if ((t->srv) && (t->srv->cookie) && + (t->be->beprm->options & PR_O_COOK_RW)) { + /* replace bytes p3->p4 with the cookie name associated + * with this server since we know it. + */ + delta = buffer_replace2(rtr, p3, p4, t->srv->cookie, t->srv->cklen); + cur_hdr->len += delta; + cur_next += delta; + txn->rsp.eoh += delta; + + t->flags |= SN_SCK_INSERTED | SN_SCK_DELETED; + } + else if ((t->srv) && (t->srv->cookie) && + (t->be->beprm->options & PR_O_COOK_PFX)) { + /* insert the cookie name associated with this server + * before existing cookie, and insert a delimitor between them.. + */ + delta = buffer_replace2(rtr, p3, p3, t->srv->cookie, t->srv->cklen + 1); + cur_hdr->len += delta; + cur_next += delta; + txn->rsp.eoh += delta; + + p3[t->srv->cklen] = COOKIE_DELIM; + t->flags |= SN_SCK_INSERTED | SN_SCK_DELETED; + } + } + /* next, let's see if the cookie is our appcookie */ + else if ((t->be->beprm->appsession_name != NULL) && + (memcmp(p1, t->be->beprm->appsession_name, p2 - p1) == 0)) { + + /* Cool... it's the right one */ + + size_t server_id_len = strlen(t->srv->id) + 1; + asession_temp = &local_asession; + + if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { + Alert("Not enough Memory process_srv():asession->sessid:malloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession->sessid:malloc().\n"); + return; + } + memcpy(asession_temp->sessid, p3, t->be->beprm->appsession_len); + asession_temp->sessid[t->be->beprm->appsession_len] = 0; + asession_temp->serverid = NULL; + + /* only do insert, if lookup fails */ + if (chtbl_lookup(&(t->be->htbl_proxy), (void *) &asession_temp) != 0) { + if ((asession_temp = pool_alloc(appsess)) == NULL) { + Alert("Not enough Memory process_srv():asession:calloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession:calloc().\n"); + return; + } + asession_temp->sessid = local_asession.sessid; + asession_temp->serverid = local_asession.serverid; + chtbl_insert(&(t->be->beprm->htbl_proxy), (void *) asession_temp); + }/* end if (chtbl_lookup()) */ + else { + /* free wasted memory */ + pool_free_to(apools.sessid, local_asession.sessid); + } /* end else from if (chtbl_lookup()) */ + + if (asession_temp->serverid == NULL) { + if ((asession_temp->serverid = pool_alloc_from(apools.serverid, apools.ser_msize)) == NULL) { + Alert("Not enough Memory process_srv():asession->sessid:malloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough Memory process_srv():asession->sessid:malloc().\n"); + return; + } + asession_temp->serverid[0] = '\0'; + } + + if (asession_temp->serverid[0] == '\0') + memcpy(asession_temp->serverid, t->srv->id, server_id_len); + + tv_delayfrom(&asession_temp->expire, &now, t->be->beprm->appsession_timeout); + +#if defined(DEBUG_HASH) + print_table(&(t->be->beprm->htbl_proxy)); +#endif + }/* end if ((t->proxy->appsession_name != NULL) ... */ + break; /* we don't want to loop again since there cannot be another cookie on the same line */ + } /* we're now at the end of the cookie value */ + + /* keep the link from this header to next one */ + old_idx = cur_idx; + } /* end of cookie processing on this header */ +} + + + +/* + * Check if response is cacheable or not. Updates t->flags. + */ +void check_response_for_cacheability(struct session *t, struct buffer *rtr) +{ + struct http_txn *txn = &t->txn; + char *p1, *p2; + + char *cur_ptr, *cur_end, *cur_next; + int cur_idx; + + if (!t->flags & SN_CACHEABLE) + return; + + /* Iterate through the headers. + * we start with the start line. + */ + cur_idx = 0; + cur_next = rtr->data + txn->rsp.som + hdr_idx_first_pos(&txn->hdr_idx); + + while ((cur_idx = txn->hdr_idx.v[cur_idx].next)) { + struct hdr_idx_elem *cur_hdr; + + cur_hdr = &txn->hdr_idx.v[cur_idx]; + cur_ptr = cur_next; + cur_end = cur_ptr + cur_hdr->len; + cur_next = cur_end + cur_hdr->cr + 1; + + /* We have one full header between cur_ptr and cur_end, and the + * next header starts at cur_next. We're only interested in + * "Cookie:" headers. + */ + + if ((cur_end - cur_ptr >= 16) && + strncasecmp(cur_ptr, "Pragma: no-cache", 16) == 0) { + t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; + return; + } + + if ((cur_end - cur_ptr <= 14) || + (strncasecmp(cur_ptr, "Cache-control:", 14) != 0)) + continue; + + /* OK, right now we know we have a cache-control header at cur_ptr */ + + p1 = cur_ptr + 14; /* first char after 'cache-control:' */ + + while (p1 < cur_end && (isspace((int)*p1))) + p1++; + + if (p1 >= cur_end) /* no more info */ + continue; + + /* p1 is at the beginning of the value */ + p2 = p1; + + while (p2 < cur_end && *p2 != '=' && *p2 != ',' && !isspace((int)*p2)) + p2++; + + /* we have a complete value between p1 and p2 */ + if (p2 < cur_end && *p2 == '=') { + /* we have something of the form no-cache="set-cookie" */ + if ((cur_end - p1 >= 21) && + strncasecmp(p1, "no-cache=\"set-cookie", 20) == 0 + && (p1[20] == '"' || p1[20] == ',')) + t->flags &= ~SN_CACHE_COOK; + continue; + } + + /* OK, so we know that either p2 points to the end of string or to a comma */ + if (((p2 - p1 == 7) && strncasecmp(p1, "private", 7) == 0) || + ((p2 - p1 == 8) && strncasecmp(p1, "no-store", 8) == 0) || + ((p2 - p1 == 9) && strncasecmp(p1, "max-age=0", 9) == 0) || + ((p2 - p1 == 10) && strncasecmp(p1, "s-maxage=0", 10) == 0)) { + t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; + return; + } + + if ((p2 - p1 == 6) && strncasecmp(p1, "public", 6) == 0) { + t->flags |= SN_CACHEABLE | SN_CACHE_COOK; + continue; + } + } +} + + /* * Try to retrieve a known appsession in the URI, then the associated server. * If the server is found, it's assigned to the session.