diff --git a/TODO b/TODO index 30f923edb..3013e18f6 100644 --- a/TODO +++ b/TODO @@ -166,3 +166,5 @@ TODO for 1.3 - check all copyrights - fix Makefile.bsd - separate inline functions to put them in files covered by GPL + - implement HTTP status 414 - request URI too long + diff --git a/doc/http-parsing.txt b/doc/http-parsing.txt new file mode 100644 index 000000000..21c3fc583 --- /dev/null +++ b/doc/http-parsing.txt @@ -0,0 +1,214 @@ +--- Relevant portions of RFC2616 --- + +OCTET = +CHAR = +UPALPHA = +LOALPHA = +ALPHA = UPALPHA | LOALPHA +DIGIT = +CTL = +CR = +LF = +SP = +HT = +<"> = +CRLF = CR LF +LWS = [CRLF] 1*( SP | HT ) +TEXT = +HEX = "A" | "B" | "C" | "D" | "E" | "F" + | "a" | "b" | "c" | "d" | "e" | "f" | DIGIT +separators = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT +token = 1* + +quoted-pair = "\" CHAR +ctext = +qdtext = > +quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) +comment = "(" *( ctext | quoted-pair | comment ) ")" + + + + + +4 HTTP Message +4.1 Message Types + +HTTP messages consist of requests from client to server and responses from +server to client. Request (section 5) and Response (section 6) messages use the +generic message format of RFC 822 [9] for transferring entities (the payload of +the message). Both types of message consist of : + + - a start-line + - zero or more header fields (also known as "headers") + - an empty line (i.e., a line with nothing preceding the CRLF) indicating the + end of the header fields + - and possibly a message-body. + + +HTTP-message = Request | Response + +start-line = Request-Line | Status-Line +generic-message = start-line + *(message-header CRLF) + CRLF + [ message-body ] + +In the interest of robustness, servers SHOULD ignore any empty line(s) received +where a Request-Line is expected. In other words, if the server is reading the +protocol stream at the beginning of a message and receives a CRLF first, it +should ignore the CRLF. + + +4.2 Message headers + +- Each header field consists of a name followed by a colon (":") and the field + value. +- Field names are case-insensitive. +- The field value MAY be preceded by any amount of LWS, though a single SP is + preferred. +- Header fields can be extended over multiple lines by preceding each extra + line with at least one SP or HT. + + +message-header = field-name ":" [ field-value ] +field-name = token +field-value = *( field-content | LWS ) +field-content = + + +The field-content does not include any leading or trailing LWS occurring before +the first non-whitespace character of the field-value or after the last +non-whitespace character of the field-value. Such leading or trailing LWS MAY +be removed without changing the semantics of the field value. Any LWS that +occurs between field-content MAY be replaced with a single SP before +interpreting the field value or forwarding the message downstream. + + +=> format des headers = 1*(CHAR & !ctl & !sep) ":" *(OCTET & (!ctl | LWS)) +=> les regex de matching de headers s'appliquent sur field-content, et peuvent + utiliser field-value comme espace de travail (mais de préférence après le + premier SP). + +(19.3) The line terminator for message-header fields is the sequence CRLF. +However, we recommend that applications, when parsing such headers, recognize +a single LF as a line terminator and ignore the leading CR. + + + + + +message-body = entity-body + | + + + +5 Request + +Request = Request-Line + *(( general-header + | request-header + | entity-header ) CRLF) + CRLF + [ message-body ] + + + +5.1 Request line + +The elements are separated by SP characters. No CR or LF is allowed except in +the final CRLF sequence. + +Request-Line = Method SP Request-URI SP HTTP-Version CRLF + +(19.3) Clients SHOULD be tolerant in parsing the Status-Line and servers +tolerant when parsing the Request-Line. In particular, they SHOULD accept any +amount of SP or HT characters between fields, even though only a single SP is +required. + +4.5 General headers +Apply to MESSAGE. + +general-header = Cache-Control + | Connection + | Date + | Pragma + | Trailer + | Transfer-Encoding + | Upgrade + | Via + | Warning + +General-header field names can be extended reliably only in combination with a +change in the protocol version. However, new or experimental header fields may +be given the semantics of general header fields if all parties in the +communication recognize them to be general-header fields. Unrecognized header +fields are treated as entity-header fields. + + + + +5.3 Request Header Fields + +The request-header fields allow the client to pass additional information about +the request, and about the client itself, to the server. These fields act as +request modifiers, with semantics equivalent to the parameters on a programming +language method invocation. + +request-header = Accept + | Accept-Charset + | Accept-Encoding + | Accept-Language + | Authorization + | Expect + | From + | Host + | If-Match + | If-Modified-Since + | If-None-Match + | If-Range + | If-Unmodified-Since + | Max-Forwards + | Proxy-Authorization + | Range + | Referer + | TE + | User-Agent + +Request-header field names can be extended reliably only in combination with a +change in the protocol version. However, new or experimental header fields MAY +be given the semantics of request-header fields if all parties in the +communication recognize them to be request-header fields. Unrecognized header +fields are treated as entity-header fields. + + + +7.1 Entity header fields + +Entity-header fields define metainformation about the entity-body or, if no +body is present, about the resource identified by the request. Some of this +metainformation is OPTIONAL; some might be REQUIRED by portions of this +specification. + +entity-header = Allow + | Content-Encoding + | Content-Language + | Content-Length + | Content-Location + | Content-MD5 + | Content-Range + | Content-Type + | Expires + | Last-Modified + | extension-header +extension-header = message-header + +The extension-header mechanism allows additional entity-header fields to be +defined without changing the protocol, but these fields cannot be assumed to be +recognizable by the recipient. Unrecognized header fields SHOULD be ignored by +the recipient and MUST be forwarded by transparent proxies. + diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 0005bf78c..beffcfe4e 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -27,6 +27,13 @@ #include #include +/* + * some macros used for the request parsing. + * from RFC2616: + * CTL = + */ +static inline int IS_CTL(const unsigned char x) { return (x < 32)||(x == 127);} + int event_accept(int fd); int process_session(struct task *t); @@ -39,6 +46,10 @@ void srv_close_with_err(struct session *t, int err, int finst, int status, int msglen, const char *msg); int produce_content(struct session *s); +void debug_hdr(const char *dir, struct session *t, const char *start, const char *end); +void get_srv_from_appsession(struct session *t, const char *begin, const char *end); +void apply_filters_to_session(struct session *t, struct buffer *req, struct hdr_exp *exp); +void manage_client_side_cookies(struct session *t, struct buffer *req); #endif /* _PROTO_PROTO_HTTP_H */ diff --git a/include/types/proto_http.h b/include/types/proto_http.h index 3cedc1e31..6e6472e6f 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -51,6 +51,21 @@ #define SV_STCLOSE 6 +/* Possible states while parsing HTTP messages (request|response) */ +#define HTTP_PA_EMPTY 0 /* leading LF, before start line */ +#define HTTP_PA_START 1 /* inside start line */ +#define HTTP_PA_STRT_LF 2 /* LF after start line */ +#define HTTP_PA_HEADER 3 /* inside a header */ +#define HTTP_PA_HDR_LF 4 /* LF after a header */ +#define HTTP_PA_HDR_LWS 5 /* LWS after a header */ +#define HTTP_PA_LFLF 6 /* after double LF/CRLF at the end of headers */ +#define HTTP_PA_ERROR 7 /* syntax error in the message */ +#define HTTP_PA_CR_SKIP 0x10 /* ORed with other values when a CR was skipped */ +#define HTTP_PA_LF_EXP 0x20 /* ORed with other values when a CR is seen and + * an LF is expected before entering the + * designated state. */ + + #endif /* _TYPES_PROTO_HTTP_H */ /* diff --git a/include/types/session.h b/include/types/session.h index f5e949054..5a8710acc 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -122,6 +122,7 @@ struct session { char **req_cap; /* array of captured request headers (may be NULL) */ char **rsp_cap; /* array of captured response headers (may be NULL) */ struct hdr_idx hdr_idx; /* array of header indexes (max: MAX_HTTP_HDR) */ + int hdr_state; /* where we are in the current header parsing */ struct chunk req_line; /* points to first line */ struct chunk auth_hdr; /* points to 'Authorization:' header */ struct { diff --git a/src/client.c b/src/client.c index b86783067..0ce2392e3 100644 --- a/src/client.c +++ b/src/client.c @@ -161,6 +161,7 @@ int event_accept(int fd) { s->cli_state = (p->mode == PR_MODE_HTTP) ? CL_STHEADERS : CL_STDATA; /* no HTTP headers for non-HTTP proxies */ s->srv_state = SV_STIDLE; + s->hdr_state = HTTP_PA_EMPTY; /* at the very beginning of the request */ s->req = s->rep = NULL; /* will be allocated later */ s->cli_fd = cfd; diff --git a/src/proto_http.c b/src/proto_http.c index 124594b5f..ea46fbd2d 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -44,12 +44,16 @@ #include #include #include +#include #include #include #include #include +#define DEBUG_PARSE_NO_SPEEDUP +#undef DEBUG_PARSE_NO_SPEEDUP + /* This is used by remote monitoring */ const char *HTTP_200 = "HTTP/1.0 200 OK\r\n" @@ -218,9 +222,10 @@ int process_cli(struct session *t) int c = t->cli_state; struct buffer *req = t->req; struct buffer *rep = t->rep; - int method_checked = 0; - appsess *asession_temp = NULL; - appsess local_asession; + int delete_header = 0; + + int cur_hdr, cur_idx; + char *ptr; #ifdef DEBUG_FULL fprintf(stderr,"process_cli: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%d.%d,%d.%d\n", @@ -234,45 +239,601 @@ int process_cli(struct session *t) //MY_FD_ISSET(t->srv_fd, StaticReadEvent), MY_FD_ISSET(t->srv_fd, StaticWriteEvent) //); if (c == CL_STHEADERS) { - /* now parse the partial (or complete) headers */ - while (req->lr < req->r) { /* this loop only sees one header at each iteration */ + /* + * Now parse the partial (or complete) lines. + * We will check the request syntax, and also join multi-line + * headers. An index of all the lines will be elaborated while + * parsing. + * + * For the parsing, we use a 10 states FSM. + * + * RFC2616 requires that both LF and CRLF are recognized as + * line breaks, but that any other combination is an error. + * To avoid duplicating all the states above to check for CR, + * we use a special bit HTTP_PA_LF_EXP that we 'OR' with the + * state we will switch to if the LF is seen, so that we know + * whether there's a pending CR or not. We can check it + * globally since all CR followed by anything but LF are + * errors. Each state is entered with the first character is + * has to process at req->lr. We also have HTTP_PA_CR_SKIP + * indicating that a CR has been seen on current line and + * skipped. + * + * Here is the information we currently have : + * req->h = beginning of current line or header + * req->lr = first non-visited byte + * req->r = end of data + */ + + while (req->lr < req->r) { char *ptr; - int delete_header; - char *request_line = NULL; - - ptr = req->lr; + int parse; - /* look for the end of the current header */ - while (ptr < req->r && *ptr != '\n' && *ptr != '\r') - ptr++; - - if (ptr == req->h) { /* empty line, end of headers */ - int line, len; +#ifdef DEBUG_FULL + fprintf(stderr, "WHL: hdr_st=0x%02x, hdr_used=%d hdr_tail=%d hdr_last=%d, h=%p, lr=%p, r=%p\n", + t->hdr_state, t->hdr_idx.used, t->hdr_idx.tail, t->hdr_idx.last, req->h, req->lr, req->r); +#endif - /* - * first, let's check that it's not a leading empty line, in - * which case we'll ignore and remove it (according to RFC2616). - */ - if (req->h == req->data) { - /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */ - if (ptr > req->r - 2) { - /* this is a partial header, let's wait for more to come */ - req->lr = ptr; - break; - } + if (t->hdr_state & HTTP_PA_LF_EXP) { + if (*req->lr != '\n') { + t->hdr_state = HTTP_PA_ERROR; + break; + } + t->hdr_state &= ~HTTP_PA_LF_EXP; + } - /* 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')) - req->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */ - else - req->lr = ptr + 2; /* \r\n or \n\r */ - /* ignore empty leading lines */ - buffer_replace2(req, req->h, req->lr, NULL, 0); - req->h = req->lr; + parse = t->hdr_state & ~HTTP_PA_CR_SKIP;; + + if (parse == HTTP_PA_EMPTY) { + /* leading empty lines */ + + if (*req->lr == '\n') { + req->lr ++; + t->hdr_state = HTTP_PA_EMPTY; continue; } + else if (*req->lr == '\r') { + req->lr ++; + t->hdr_state = HTTP_PA_EMPTY | HTTP_PA_CR_SKIP | HTTP_PA_LF_EXP; + continue; + } + +#if PARSE_PRESERVE_EMPTY_LINES + /* only skip empty leading lines, don't remove them */ + t->hdr_idx.v[0].len = req->lr - req->h; +#else + /* remove empty leading lines, as recommended by + * RFC2616. This takes a lot of time because we + * must move all the buffer backwards, but this + * is rarely needed. The method above will be + * cleaner when we'll be able to start sending + * the request from any place in the buffer. + */ + buffer_replace2(req, req->h, req->lr, NULL, 0); +#endif + req->h = req->lr; + + t->hdr_state = HTTP_PA_START; +#ifndef DEBUG_PARSE_NO_SPEEDUP + /* we know that we still have one char available */ + goto parse_start; +#else + continue; +#endif + + } else if (parse == HTTP_PA_START) { + parse_start: + /* Start line */ + while (req->lr < req->r && !IS_CTL(*req->lr)) + req->lr++; + if (req->lr == req->r) + break; + /* we have a CTL char */ + if (*req->lr == '\r') { + req->lr++; + t->hdr_state = HTTP_PA_STRT_LF | HTTP_PA_CR_SKIP | HTTP_PA_LF_EXP; + continue; + } + else if (*req->lr == '\n') { + t->hdr_state = HTTP_PA_STRT_LF; +#ifndef DEBUG_PARSE_NO_SPEEDUP + /* we know that we still have one char available */ + goto parse_strt_lf; +#else + continue; +#endif + } + t->hdr_state = HTTP_PA_ERROR; + break; + + } else if (parse == HTTP_PA_STRT_LF) { + parse_strt_lf: + /* The LF validating the request line */ + + ptr = req->lr; + if (t->hdr_state & HTTP_PA_CR_SKIP) + ptr--; /* Get back to the CR */ + + /* We have the complete start line between + * req->h and ptr (excluded). lr points to + * the LF. + */ + + /* FIXME: insert a REQUESTURI hook here. */ + + + /* 1: we might have to print this header */ + if ((global.mode & MODE_DEBUG) && + (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) + debug_hdr("clireq", t, req->h, ptr); + + + /* 1: save a pointer to the first line as the request */ + if (t->req_line.len < 0) { + t->req_line.str = req->h; + t->req_line.len = ptr - req->h; + } + + /* 2: maybe we have to copy the original REQURI for the logs ? */ + if (t->logs.logwait & LW_REQ) { + /* we have a complete HTTP request that we must log */ + if ((t->logs.uri = pool_alloc(requri)) != NULL) { + int urilen = ptr - req->h; + + if (urilen >= REQURI_LEN) + urilen = REQURI_LEN - 1; + memcpy(t->logs.uri, req->h, urilen); + t->logs.uri[urilen] = 0; + + if (!(t->logs.logwait &= ~LW_REQ)) + sess_log(t); + } else { + Alert("HTTP logging : out of memory.\n"); + } + } + + /* 3: reference this line as the start line */ + if (hdr_idx_add(ptr - req->h, req->lr - ptr, + &t->hdr_idx, t->hdr_idx.tail) < 0) { + t->hdr_state = HTTP_PA_ERROR; + break; + } + + req->lr++; + req->h = req->lr; + /* in fact, a state is missing here, we should + * be able to distinguish between an empty line + * and a header. + */ + t->hdr_state = HTTP_PA_HEADER; + continue; + + } else if (parse == HTTP_PA_HEADER) { + parse_inside_hdr: + /* Inside a non-empty header */ + + delete_header = 0; + while (req->lr < req->r && !IS_CTL(*req->lr)) + req->lr++; + if (req->lr == req->r) + break; + + /* we have a CTL char */ + if (*req->lr == '\r') { + t->hdr_state = HTTP_PA_HDR_LF | HTTP_PA_CR_SKIP | HTTP_PA_LF_EXP; + req->lr++; + continue; + } + else if (*req->lr == '\n') { + t->hdr_state = HTTP_PA_HDR_LF; +#ifndef DEBUG_PARSE_NO_SPEEDUP + goto parse_hdr_lf; +#else + continue; +#endif + } + t->hdr_state = HTTP_PA_ERROR; + break; + + } else if (parse == HTTP_PA_HDR_LF) { + parse_hdr_lf: + /* The LF validating last header, but it + * may also be an LWS, in which case we will + * need more data to know if we can close this + * header or not. However, we must check right + * now if this LF/CRLF closes an empty line, in + * which case it means the end of the request. + */ + ptr = req->lr; + if (t->hdr_state & HTTP_PA_CR_SKIP) + ptr--; /* Get back to the CR */ + + if (ptr == req->h) { + /* We have found the end of the headers. + * req->h points to the ending LF/CRLF, + * and req->lr points to the first byte + * after the LF, so it is easy to append + * anything there. + */ + t->hdr_state = HTTP_PA_LFLF; +#ifndef DEBUG_PARSE_NO_SPEEDUP + goto parse_lflf; +#else + continue; +#endif + //req->lr++; + //break; + } + + if (req->lr + 1 >= req->r) /* LF, ?? */ + break; + req->lr++; + + /* Right now, we *know* that there is one char + * available at req->lr. + */ + + if (*req->lr == ' ' || *req->lr == '\t') { + /* We have an LWS, we will replace the + * CR and LF with spaces as RFC2616 + * allows it. now points to the + * first space char of the LWS part. + */ + for (;ptr < req->lr; ptr++) + *ptr = ' '; + + t->hdr_state = HTTP_PA_HDR_LWS; +#ifndef DEBUG_PARSE_NO_SPEEDUP + goto parse_hdr_lws; +#else + continue; +#endif + } + + /********************************************** + * We now have one complete header between * + * req->h and ptr, with a possible CR at ptr, * + * everything ending before req->lr. Some very* + * early processing can be applied. * + **********************************************/ + + /* + * FIXME: insert a REQHEADER hook here. + * For instance, we could check the header's + * syntax such as forbidding the leading space + * in the first header (Apache also has the same problem) + */ + + + /* 1: we might have to print this header */ + if ((global.mode & MODE_DEBUG) && + (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) + debug_hdr("clihdr", t, req->h, ptr); + + + /* 2: maybe we have to copy this header for the logs ? */ + if (t->logs.logwait & LW_REQHDR) { + /* FIXME: we must *search* the value after the ':' and not + * consider that it's necessary after one single space.*/ + struct cap_hdr *h; + int len; + for (h = t->fi->req_cap; h; h = h->next) { + if ((h->namelen + 2 <= ptr - req->h) && + (req->h[h->namelen] == ':') && + (strncasecmp(req->h, h->name, h->namelen) == 0)) { + if (t->req_cap[h->index] == NULL) + t->req_cap[h->index] = pool_alloc_from(h->pool, h->len + 1); + + if (t->req_cap[h->index] == NULL) { + Alert("HTTP capture : out of memory.\n"); + continue; + } + + len = ptr - (req->h + h->namelen + 2); + if (len > h->len) + len = h->len; + + memcpy(t->req_cap[h->index], req->h + h->namelen + 2, len); + t->req_cap[h->index][len]=0; + } + } + } + + + /* 3: We might need to remove "connection:" */ + if (!delete_header && (t->fe->options & PR_O_HTTP_CLOSE) + && (strncasecmp(req->h, "Connection:", 11) == 0)) { + delete_header = 1; + } + + + /* 4: we might also need the 'Authorization:' header */ + if (!delete_header && + t->auth_hdr.len < 0 && t->fi->uri_auth != NULL && + ptr > req->h + 14 && !strncasecmp("Authorization:", req->h, 14)) { + t->auth_hdr.str = req->h; + t->auth_hdr.len = ptr - req->h; + } + + + + + /* OK, that's enough processing for the first step. + * Now either we index this header or we remove it. + */ + + if (!delete_header) { + /* we insert it into the index */ + if (hdr_idx_add(ptr - req->h, req->lr - ptr - 1, + &t->hdr_idx, t->hdr_idx.tail) < 0) { + t->hdr_state = HTTP_PA_ERROR; + break; + } + } else { + /* we remove it */ + delete_header = 0; + buffer_replace2(req, req->h, req->lr, NULL, 0); + /* WARNING: ptr is not valid anymore, since the + * header may have been deleted or truncated ! */ + } + + /* In any case, we set the next header pointer + * to the next line. + */ + req->h = req->lr; + +#ifndef DEBUG_PARSE_NO_SPEEDUP + /* + * We know that at least one character remains. + * It is interesting to directly branch to the + * matching state. + */ + ptr = req->lr; + if (IS_CTL(*req->lr)) { + if (*ptr == '\r') { + req->lr++; + t->hdr_state = HTTP_PA_LFLF | HTTP_PA_LF_EXP; + continue; + } + else if (*ptr == '\n') { + t->hdr_state = HTTP_PA_LFLF; + goto parse_lflf; + } + else { + t->hdr_state = HTTP_PA_ERROR; + break; + } + } + t->hdr_state = HTTP_PA_HEADER; + goto parse_inside_hdr; +#else + t->hdr_state = HTTP_PA_HEADER; + continue; +#endif + + } else if (parse == HTTP_PA_HDR_LWS) { + parse_hdr_lws: + /* Inside an LWS. We just replace tabs with + * spaces and fall back to the HEADER state + * at the first non-space character + */ + + while (req->lr < req->r) { + if (*req->lr == '\t') + *req->lr = ' '; + else if (*req->lr != ' ') { + t->hdr_state = HTTP_PA_HEADER; +#ifndef DEBUG_PARSE_NO_SPEEDUP + goto parse_inside_hdr; +#else + break; +#endif + } + req->lr++; + } + continue; + + } else if (parse == HTTP_PA_LFLF) { + parse_lflf: + req->lr ++; + /* req->h points to either CR or CRLF, and + * req->lr points to 1 char after LF. + */ + + /* + * FIXME: insert a hook here for the end of the headers + */ + break; + } else if (parse == HTTP_PA_ERROR) { + break; + } + + } /* end of the "while(req->lr < req->r)" loop */ + +#ifdef DEBUG_FULL + fprintf(stderr, "END: hdr_st=0x%02x, hdr_used=%d hdr_tail=%d hdr_last=%d, h=%p, lr=%p, r=%p\n", + t->hdr_state, t->hdr_idx.used, t->hdr_idx.tail, t->hdr_idx.last, req->h, req->lr, req->r); +#endif + + /* + * Now, let's catch bad requests. + */ + + if (t->hdr_state == HTTP_PA_ERROR) { + t->logs.status = 400; + client_retnclose(t, t->fe->errmsg.len400, t->fe->errmsg.msg400); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + + + /* + * Now we quickly check if we have found a full request. + * If not so, we check the FD and buffer states before leaving. + * A full request is indicated by the fact that we have seen + * the double LF/CRLF, so the state is HTTP_PA_LFLF. + * + */ + + if (t->hdr_state != HTTP_PA_LFLF) { /* Request not complete yet */ + + /* 1: 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. We + * must terminate it now. + */ + if (req->l >= req->rlim - req->data) { + /* FIXME: check if hdr_state & mask < HTTP_PA_HEADER, + * and return Status 414 Request URI too long instead. + */ + t->logs.status = 400; + client_retnclose(t, t->fe->errmsg.len400, t->fe->errmsg.msg400); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + /* 2: have we encountered a read error or a close ? */ + else if (req->flags & (BF_READ_ERROR | BF_READ_NULL)) { + /* read error, or last read : give up. */ + tv_eternity(&req->rex); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLICL; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + /* 3: has the read timeout expired ? */ + else if (tv_cmp2_ms(&req->rex, &now) <= 0) { + /* read timeout : give up with an error message. */ + t->logs.status = 408; + client_retnclose(t, t->fe->errmsg.len408, t->fe->errmsg.msg408); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLITO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + /* 4: do we need to re-enable the read socket ? */ + else if (! MY_FD_ISSET(t->cli_fd, StaticReadEvent)) { + /* fd in StaticReadEvent was disabled, perhaps because of a previous buffer + * full. We cannot loop here since stream_sock_read will disable it only if + * req->l == rlim-data + */ + MY_FD_SET(t->cli_fd, StaticReadEvent); + if (t->fe->clitimeout) + tv_delayfrom(&req->rex, &now, t->fe->clitimeout); + else + tv_eternity(&req->rex); + } + return t->cli_state != CL_STHEADERS; + } + + + /**************************************************************** + * More interesting part now : we know that we have a complete * + * request which at least looks like HTTP. We have an indicator * + * of each header's length, so we can parse them quickly. * + ****************************************************************/ + + + /* + * 1: the appsession cookie was looked up very early in 1.2, + * so let's do the same now. + */ + + /* It needs to look into the URI */ + if (t->be->appsession_name) { + req->h = req->data + t->hdr_idx.v[0].len; /* start of the URI */ + ptr = req->h + t->hdr_idx.v[t->hdr_idx.v[0].next].len; /* end of the URI */ + get_srv_from_appsession(t, req->h, ptr); + } + + + /* + * 2: 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. + */ + + + /* try headers regexps */ + if (t->fi->req_exp != NULL) + apply_filters_to_session(t, req, t->fi->req_exp); + + /* + * 3: Now we can work with the cookies. + */ + + if (!(t->flags & (SN_CLDENY|SN_CLTARPIT))) + manage_client_side_cookies(t, req); + + + +#if TEST + /* example: dump each line */ + + fprintf(stderr, "t->flags=0x%08x\n", t->flags & (SN_CLALLOW|SN_CLDENY|SN_CLTARPIT)); + + fprintf(stderr, "req->h=%d\n", req->h - req->data); + req->h = req->data + t->hdr_idx.v[0].len; + cur_hdr = 0; + + cur_idx = t->hdr_idx.v[0].next; + cur_hdr = 1; + + while (cur_hdr < t->hdr_idx.used) { + ptr = req->h + t->hdr_idx.v[cur_idx].len + t->hdr_idx.v[cur_idx].cr + 1; + fprintf(stderr, "lr=%d r=%d hdr=%d idx=%d adr=%d..%d len=%d cr=%d data:\n", + req->lr - req->data, req->r - req->data, + cur_hdr, cur_idx, + req->h - req->data, + req->h - req->data + t->hdr_idx.v[cur_idx].len + t->hdr_idx.v[cur_idx].cr, + t->hdr_idx.v[cur_idx].len, + t->hdr_idx.v[cur_idx].cr); + write(2, req->h, ptr - req->h); + + req->h = ptr; + cur_idx = t->hdr_idx.v[cur_idx].next; + cur_hdr++; + } +#endif + + + + /**************** debugging ***************/ + t->logs.status = 400; + client_retnclose(t, t->fe->errmsg.len400, t->fe->errmsg.msg400); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + + + + + +#if 0 + + + + char *ptr; + char *request_line = NULL; + + if (ptr == req->h) { /* empty line, end of headers */ + int line, len; /* we can only get here after an end of headers */ /* we'll have something else to do here : add new headers ... */ @@ -469,594 +1030,20 @@ int process_cli(struct session *t) goto process_data; } - /* To get a complete header line, we need the ending \r\n, \n\r, - * \r or \n, possibly followed by a white space or tab indicating - * that the header goes on next line. */ - if (ptr > req->r - 3) { - /* this is a partial header, let's wait for more to come */ - req->lr = ptr; - break; - } - /* 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')) - req->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */ - else - req->lr = ptr + 2; /* \r\n or \n\r */ + /*******************************************/ - /* Now, try to detect multi-line headers. From RFC 2616 : - * HTTP/1.1 header field values can be folded onto multiple lines if the - * continuation line begins with a space or horizontal tab. All linear - * white space, including folding, has the same semantics as SP. A - * recipient MAY replace any linear white space with a single SP before - * interpreting the field value or forwarding the message downstream. - * - * LWS = [CRLF] 1*( SP | HT ) - */ - if (req->lr < req->r && - (*req->lr == ' ' || *req->lr == '\t')) { - /* we are allowed to replace the \r\n with spaces */ - while (ptr < req->lr) - *ptr++ = ' '; - /* now look for end of LWS */ - do { - req->lr++; - } while (req->lr < req->r && (*req->lr == ' ' || *req->lr == '\t')); - - /* continue processing on the same header */ - continue; - } - /* - * now we know that we have a full header ; we can do whatever - * we want with these pointers : - * req->h = beginning of header - * ptr = end of header (first \r or \n) - * req->lr = beginning of next line (next rep->h) - * req->r = end of data (not used at this stage) - */ - if (!method_checked && (t->be->appsession_name != NULL) && - ((memcmp(req->h, "GET ", 4) == 0) || (memcmp(req->h, "POST ", 4) == 0)) && - ((request_line = memchr(req->h, ';', req->lr - req->h)) != NULL)) { - - /* skip ; */ - request_line++; - - /* look if we have a jsessionid */ - - if (strncasecmp(request_line, t->be->appsession_name, t->be->appsession_name_len) == 0) { - - /* skip jsessionid= */ - request_line += t->be->appsession_name_len + 1; - - /* First try if we allready have an appsession */ - asession_temp = &local_asession; - - if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { - Alert("Not enough memory process_cli():asession_temp->sessid:calloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough Memory process_cli():asession_temp->sessid:calloc().\n"); - return 0; - } - - /* Copy the sessionid */ - memcpy(asession_temp->sessid, request_line, t->be->appsession_len); - asession_temp->sessid[t->be->appsession_len] = 0; - asession_temp->serverid = NULL; - - /* only do insert, if lookup fails */ - if (chtbl_lookup(&(t->be->htbl_proxy), (void *)&asession_temp)) { - if ((asession_temp = pool_alloc(appsess)) == NULL) { - Alert("Not enough memory process_cli():asession:calloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); - return 0; - } - asession_temp->sessid = local_asession.sessid; - asession_temp->serverid = local_asession.serverid; - chtbl_insert(&(t->be->htbl_proxy), (void *) asession_temp); - } /* end if (chtbl_lookup()) */ - else { - /*free wasted memory;*/ - pool_free_to(apools.sessid, local_asession.sessid); - } - - tv_delayfrom(&asession_temp->expire, &now, t->be->appsession_timeout); - asession_temp->request_count++; - -#if defined(DEBUG_HASH) - print_table(&(t->proxy->htbl_proxy)); #endif - if (asession_temp->serverid == NULL) { - Alert("Found Application Session without matching server.\n"); - } else { - struct server *srv = t->be->srv; - while (srv) { - if (strcmp(srv->id, asession_temp->serverid) == 0) { - if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) { - /* we found the server and it's usable */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; - t->srv = srv; - break; - } else { - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_DOWN; - } - } /* end if (strcmp()) */ - srv = srv->next; - }/* end while(srv) */ - }/* end else of if (asession_temp->serverid == NULL) */ - }/* end if (strncasecmp(request_line,t->proxy->appsession_name,apssesion_name_len) == 0) */ - else { - //fprintf(stderr,">>>>>>>>>>>>>>>>>>>>>>NO SESSION\n"); - } - method_checked = 1; - } /* end if (!method_checked ...) */ - else{ - //printf("No Methode-Header with Session-String\n"); - } - - if (t->logs.logwait & LW_REQ) { - /* we have a complete HTTP request that we must log */ - int urilen; - - if ((t->logs.uri = pool_alloc(requri)) == NULL) { - Alert("HTTP logging : out of memory.\n"); - t->logs.status = 500; - client_retnclose(t, t->fe->errmsg.len500, t->fe->errmsg.msg500); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - - urilen = ptr - req->h; - if (urilen >= REQURI_LEN) - urilen = REQURI_LEN - 1; - memcpy(t->logs.uri, req->h, urilen); - t->logs.uri[urilen] = 0; - - if (!(t->logs.logwait &= ~LW_REQ)) - sess_log(t); - } - else if (t->logs.logwait & LW_REQHDR) { - struct cap_hdr *h; - int len; - for (h = t->fi->req_cap; h; h = h->next) { - if ((h->namelen + 2 <= ptr - req->h) && - (req->h[h->namelen] == ':') && - (strncasecmp(req->h, h->name, h->namelen) == 0)) { - - if (t->req_cap[h->index] == NULL) - t->req_cap[h->index] = pool_alloc_from(h->pool, h->len + 1); - - len = ptr - (req->h + h->namelen + 2); - if (len > h->len) - len = h->len; - - memcpy(t->req_cap[h->index], req->h + h->namelen + 2, len); - t->req_cap[h->index][len]=0; - } - } - - } - - delete_header = 0; - - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - int len, max; - len = sprintf(trash, "%08x:%s.clihdr[%04x:%04x]: ", t->uniq_id, t->be->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); - max = ptr - req->h; - UBOUND(max, sizeof(trash) - len - 1); - len += strlcpy2(trash + len, req->h, max + 1); - trash[len++] = '\n'; - write(1, trash, len); - } - /* remove "connection: " if needed */ - if (!delete_header && (t->fe->options & PR_O_HTTP_CLOSE) - && (strncasecmp(req->h, "Connection: ", 12) == 0)) { - delete_header = 1; - } - /* try headers regexps */ - if (!delete_header && t->fi->req_exp != NULL - && !(t->flags & SN_CLDENY)) { - struct hdr_exp *exp; - char term; - - term = *ptr; - *ptr = '\0'; - exp = t->fi->req_exp; - do { - if (regexec(exp->preg, req->h, MAX_MATCH, pmatch, 0) == 0) { - switch (exp->action) { - case ACT_ALLOW: - if (!(t->flags & (SN_CLDENY | SN_CLTARPIT))) - t->flags |= SN_CLALLOW; - break; - case ACT_REPLACE: - if (!(t->flags & (SN_CLDENY | SN_CLTARPIT))) { - int len = exp_replace(trash, req->h, exp->replace, pmatch); - ptr += buffer_replace2(req, req->h, ptr, trash, len); - } - break; - case ACT_REMOVE: - if (!(t->flags & (SN_CLDENY | SN_CLTARPIT))) - delete_header = 1; - break; - case ACT_DENY: - if (!(t->flags & (SN_CLALLOW | SN_CLTARPIT))) - t->flags |= SN_CLDENY; - break; - case ACT_TARPIT: - if (!(t->flags & (SN_CLALLOW | SN_CLDENY))) - t->flags |= SN_CLTARPIT; - break; - case ACT_PASS: /* we simply don't deny this one */ - break; - } - break; - } - } while ((exp = exp->next) != NULL); - *ptr = term; /* restore the string terminator */ - } - - /* Now look for cookies. Conforming to RFC2109, we have to support - * attributes whose name begin with a '$', and associate them with - * the right cookie, if we want to delete this cookie. - * So there are 3 cases for each cookie read : - * 1) it's a special attribute, beginning with a '$' : ignore it. - * 2) it's a server id cookie that we *MAY* want to delete : save - * some pointers on it (last semi-colon, beginning of cookie...) - * 3) it's an application cookie : we *MAY* have to delete a previous - * "special" cookie. - * At the end of loop, if a "special" cookie remains, we may have to - * remove it. If no application cookie persists in the header, we - * *MUST* delete it - */ - if (!delete_header && - (t->be->cookie_name != NULL || t->be->appsession_name !=NULL || t->fi->capture_name != NULL) - && !(t->flags & (SN_CLDENY|SN_CLTARPIT)) && (ptr >= req->h + 8) - && (strncasecmp(req->h, "Cookie: ", 8) == 0)) { - char *p1, *p2, *p3, *p4; - char *del_colon, *del_cookie, *colon; - int app_cookies; - - p1 = req->h + 8; /* first char after 'Cookie: ' */ - colon = p1; - /* del_cookie == NULL => nothing to be deleted */ - del_colon = del_cookie = NULL; - app_cookies = 0; - - while (p1 < ptr) { - /* skip spaces and colons, but keep an eye on these ones */ - while (p1 < ptr) { - if (*p1 == ';' || *p1 == ',') - colon = p1; - else if (!isspace((int)*p1)) - break; - p1++; - } - - if (p1 == ptr) - break; - - /* p1 is at the beginning of the cookie name */ - p2 = p1; - while (p2 < ptr && *p2 != '=') - p2++; - - if (p2 == ptr) - break; - - p3 = p2 + 1; /* skips the '=' sign */ - if (p3 == ptr) - break; - - p4 = p3; - while (p4 < ptr && !isspace((int)*p4) && *p4 != ';' && *p4 != ',') - p4++; - - /* here, we have the cookie name between p1 and p2, - * and its value between p3 and p4. - * we can process it : - * - * Cookie: NAME=VALUE; - * | || || | - * | || || +--> p4 - * | || |+-------> p3 - * | || +--------> p2 - * | |+------------> p1 - * | +-------------> colon - * +--------------------> req->h - */ - - if (*p1 == '$') { - /* skip this one */ - } - else { - /* first, let's see if we want to capture it */ - if (t->fi->capture_name != NULL && - t->logs.cli_cookie == NULL && - (p4 - p1 >= t->fi->capture_namelen) && - memcmp(p1, t->fi->capture_name, t->fi->capture_namelen) == 0) { - int log_len = p4 - p1; - - if ((t->logs.cli_cookie = pool_alloc(capture)) == NULL) { - Alert("HTTP logging : out of memory.\n"); - } else { - if (log_len > t->fi->capture_len) - log_len = t->fi->capture_len; - memcpy(t->logs.cli_cookie, p1, log_len); - t->logs.cli_cookie[log_len] = 0; - } - } - - if ((p2 - p1 == t->be->cookie_len) && (t->be->cookie_name != NULL) && - (memcmp(p1, t->be->cookie_name, p2 - p1) == 0)) { - /* Cool... it's the right one */ - struct server *srv = t->be->srv; - char *delim; - - /* if we're in cookie prefix mode, we'll search the delimitor so that we - * have the server ID betweek p3 and delim, and the original cookie between - * delim+1 and p4. Otherwise, delim==p4 : - * - * Cookie: NAME=SRV~VALUE; - * | || || | | - * | || || | +--> p4 - * | || || +--------> delim - * | || |+-----------> p3 - * | || +------------> p2 - * | |+----------------> p1 - * | +-----------------> colon - * +------------------------> req->h - */ - - if (t->be->options & PR_O_COOK_PFX) { - for (delim = p3; delim < p4; delim++) - if (*delim == COOKIE_DELIM) - break; - } - else - delim = p4; + /****************************************************************/ - /* Here, we'll look for the first running server which supports the cookie. - * This allows to share a same cookie between several servers, for example - * to dedicate backup servers to specific servers only. - * However, to prevent clients from sticking to cookie-less backup server - * when they have incidentely learned an empty cookie, we simply ignore - * empty cookies and mark them as invalid. - */ - if (delim == p3) - srv = NULL; - while (srv) { - if ((srv->cklen == delim - p3) && !memcmp(p3, srv->cookie, delim - p3)) { - if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) { - /* we found the server and it's usable */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; - t->srv = srv; - break; - } else { - /* we found a server, but it's down */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_DOWN; - } - } - srv = srv->next; - } - - if (!srv && !(t->flags & SN_CK_DOWN)) { - /* no server matched this cookie */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_INVALID; - } - - /* depending on the cookie mode, we may have to either : - * - delete the complete cookie if we're in insert+indirect mode, so that - * the server never sees it ; - * - remove the server id from the cookie value, and tag the cookie as an - * application cookie so that it does not get accidentely removed later, - * if we're in cookie prefix mode - */ - if ((t->be->options & PR_O_COOK_PFX) && (delim != p4)) { - buffer_replace2(req, p3, delim + 1, NULL, 0); - p4 -= (delim + 1 - p3); - ptr -= (delim + 1 - p3); - del_cookie = del_colon = NULL; - app_cookies++; /* protect the header from deletion */ - } - else if (del_cookie == NULL && - (t->be->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND)) { - del_cookie = p1; - del_colon = colon; - } - } else { - /* now we know that we must keep this cookie since it's - * not ours. But if we wanted to delete our cookie - * earlier, we cannot remove the complete header, but we - * can remove the previous block itself. - */ - app_cookies++; - - if (del_cookie != NULL) { - buffer_replace2(req, del_cookie, p1, NULL, 0); - p4 -= (p1 - del_cookie); - ptr -= (p1 - del_cookie); - del_cookie = del_colon = NULL; - } - } - - if ((t->be->appsession_name != NULL) && - (memcmp(p1, t->be->appsession_name, p2 - p1) == 0)) { - /* first, let's see if the cookie is our appcookie*/ - - /* Cool... it's the right one */ - - asession_temp = &local_asession; - - if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { - Alert("Not enough memory process_cli():asession->sessid:malloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n"); - return 0; - } - - memcpy(asession_temp->sessid, p3, t->be->appsession_len); - asession_temp->sessid[t->be->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_cli():asession:calloc().\n"); - send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); - return 0; - } - - asession_temp->sessid = local_asession.sessid; - asession_temp->serverid = local_asession.serverid; - chtbl_insert(&(t->be->htbl_proxy), (void *) asession_temp); - } else { - /* free wasted memory */ - pool_free_to(apools.sessid, local_asession.sessid); - } - - if (asession_temp->serverid == NULL) { - Alert("Found Application Session without matching server.\n"); - } else { - struct server *srv = t->be->srv; - while (srv) { - if (strcmp(srv->id, asession_temp->serverid) == 0) { - if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) { - /* we found the server and it's usable */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; - t->srv = srv; - break; - } else { - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_DOWN; - } - } - srv = srv->next; - }/* end while(srv) */ - }/* end else if server == NULL */ - - tv_delayfrom(&asession_temp->expire, &now, t->be->appsession_timeout); - }/* end if ((t->proxy->appsession_name != NULL) ... */ - } - - /* we'll have to look for another cookie ... */ - p1 = p4; - } /* while (p1 < ptr) */ - - /* There's no more cookie on this line. - * We may have marked the last one(s) for deletion. - * We must do this now in two ways : - * - if there is no app cookie, we simply delete the header ; - * - if there are app cookies, we must delete the end of the - * string properly, including the colon/semi-colon before - * the cookie name. - */ - if (del_cookie != NULL) { - if (app_cookies) { - buffer_replace2(req, del_colon, ptr, NULL, 0); - /* WARNING! becomes invalid for now. If some code - * below needs to rely on it before the end of the global - * header loop, we need to correct it with this code : - */ - ptr = del_colon; - } - else - delete_header = 1; - } - } /* end of cookie processing on this header */ - - /* let's look if we have to delete this header */ - if (delete_header && !(t->flags & (SN_CLDENY|SN_CLTARPIT))) { - buffer_replace2(req, req->h, req->lr, NULL, 0); - /* WARNING: ptr is not valid anymore, since the header may have - * been deleted or truncated ! */ - } else { - /* try to catch the first line as the request */ - if (t->req_line.len < 0) { - t->req_line.str = req->h; - t->req_line.len = ptr - req->h; - } - - /* We might also need the 'Authorization: ' header */ - if (t->auth_hdr.len < 0 && - t->fi->uri_auth != NULL && - ptr > req->h + 15 && - !strncasecmp("Authorization: ", req->h, 15)) { - t->auth_hdr.str = req->h; - t->auth_hdr.len = ptr - req->h; - } - } - - req->h = req->lr; - } /* while (req->lr < req->r) */ - - /* end of header processing (even if incomplete) */ - - if ((req->l < req->rlim - req->data) && ! MY_FD_ISSET(t->cli_fd, StaticReadEvent)) { - /* fd in StaticReadEvent was disabled, perhaps because of a previous buffer - * full. We cannot loop here since stream_sock_read will disable it only if - * req->l == rlim-data - */ - MY_FD_SET(t->cli_fd, StaticReadEvent); - if (t->fe->clitimeout) - tv_delayfrom(&req->rex, &now, t->fe->clitimeout); - else - tv_eternity(&req->rex); - } - - /* 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. - */ - if (req->l >= req->rlim - req->data) { - t->logs.status = 400; - client_retnclose(t, t->fe->errmsg.len400, t->fe->errmsg.msg400); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - else if (req->flags & (BF_READ_ERROR | BF_READ_NULL)) { - /* read error, or last read : give up. */ - tv_eternity(&req->rex); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLICL; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - else if (tv_cmp2_ms(&req->rex, &now) <= 0) { - - /* read timeout : give up with an error message. - */ - t->logs.status = 408; - client_retnclose(t, t->fe->errmsg.len408, t->fe->errmsg.msg408); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLITO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - - return t->cli_state != CL_STHEADERS; } else if (c == CL_STDATA) { process_data: @@ -1801,15 +1788,8 @@ int process_srv(struct session *t) delete_header = 0; - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - int len, max; - len = sprintf(trash, "%08x:%s.srvhdr[%04x:%04x]: ", t->uniq_id, t->be->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); - max = ptr - rep->h; - UBOUND(max, sizeof(trash) - len - 1); - len += strlcpy2(trash + len, rep->h, max + 1); - trash[len++] = '\n'; - write(1, trash, len); - } + 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 & PR_O_HTTP_CLOSE) @@ -2898,6 +2878,562 @@ int produce_content(struct session *s) } + +/* + * Apply all the req filters to all headers in buffer of session + */ + +void apply_filters_to_session(struct session *t, struct buffer *req, struct hdr_exp *exp) +{ + /* iterate through the filters in the outer loop */ + while (exp && !(t->flags & (SN_CLDENY|SN_CLTARPIT))) { + char term; + char *cur_ptr, *cur_end, *cur_next; + int cur_idx, old_idx, abort_filt; + + + /* + * The interleaving of transformations and verdicts + * makes it difficult to decide to continue or stop + * the evaluation. + */ + + if ((t->flags & SN_CLALLOW) && + (exp->action == ACT_ALLOW || exp->action == ACT_DENY || + exp->action == ACT_TARPIT || exp->action == ACT_PASS)) { + exp = exp->next; + continue; + } + + /* Iterate through the headers in the inner loop. + * we start with the start line. + */ + old_idx = cur_idx = 0; + cur_next = req->data + t->hdr_idx.v[0].len; + abort_filt = 0; + + while (!abort_filt && (cur_idx = t->hdr_idx.v[cur_idx].next)) { + struct hdr_idx_elem *cur_hdr = &t->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: + if (!(t->flags & (SN_CLDENY | SN_CLTARPIT))) { + t->flags |= SN_CLALLOW; + abort_filt = 1; + } + break; + case ACT_REPLACE: + if (!(t->flags & (SN_CLDENY | SN_CLTARPIT))) { + int len, delta; + len = exp_replace(trash, cur_ptr, exp->replace, pmatch); + delta = buffer_replace2(req, cur_ptr, cur_end, trash, len); + cur_end += delta; + cur_next += delta; + cur_hdr->len += delta; + } + break; + case ACT_REMOVE: + if (!(t->flags & (SN_CLDENY | SN_CLTARPIT))) { + int delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0); + cur_next += delta; + + /* FIXME: this should be a separate function */ + t->hdr_idx.v[old_idx].next = cur_hdr->next; + t->hdr_idx.used--; + cur_hdr->len = 0; + + cur_end = NULL; /* null-term has been rewritten */ + } + break; + case ACT_DENY: + if (!(t->flags & (SN_CLALLOW | SN_CLTARPIT))) { + t->flags |= SN_CLDENY; + abort_filt = 1; + } + break; + case ACT_TARPIT: + if (!(t->flags & (SN_CLALLOW | SN_CLDENY))) { + t->flags |= SN_CLTARPIT; + abort_filt = 1; + } + break; + //case ACT_PASS: /* FIXME: broken as of now. We should mark the header as "ignored". */ + // break; + } + } + if (cur_end) + *cur_end = term; /* restore the string terminator */ + + /* keep the link from this header to next one */ + old_idx = cur_idx; + } + exp = exp->next; + } +} + + + +/* + * Manager client-side cookie + */ +void manage_client_side_cookies(struct session *t, struct buffer *req) +{ + char *p1, *p2, *p3, *p4; + char *del_colon, *del_cookie, *colon; + int app_cookies; + + appsess *asession_temp = NULL; + appsess local_asession; + + char *cur_ptr, *cur_end, *cur_next; + int cur_idx, old_idx, abort_filt; + + if (t->be->cookie_name == NULL && + t->be->appsession_name ==NULL && + t->fi->capture_name != NULL) + return; + + + + /* Iterate through the headers in the inner loop. + * we start with the start line. + */ + old_idx = cur_idx = 0; + cur_next = req->data + t->hdr_idx.v[0].len; + abort_filt = 0; + + while ((cur_idx = t->hdr_idx.v[cur_idx].next)) { + struct hdr_idx_elem *cur_hdr; + + cur_hdr = &t->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 <= 7) || + (strncasecmp(cur_ptr, "Cookie:", 7) != 0)) { + old_idx = cur_idx; + continue; + } + + /* Now look for cookies. Conforming to RFC2109, we have to support + * attributes whose name begin with a '$', and associate them with + * the right cookie, if we want to delete this cookie. + * So there are 3 cases for each cookie read : + * 1) it's a special attribute, beginning with a '$' : ignore it. + * 2) it's a server id cookie that we *MAY* want to delete : save + * some pointers on it (last semi-colon, beginning of cookie...) + * 3) it's an application cookie : we *MAY* have to delete a previous + * "special" cookie. + * At the end of loop, if a "special" cookie remains, we may have to + * remove it. If no application cookie persists in the header, we + * *MUST* delete it + */ + + + p1 = cur_ptr + 7; /* first char after 'Cookie:' */ + if (isspace((int)*p1)) /* try to get the first space with it */ + p1++; + + colon = p1; + /* del_cookie == NULL => nothing to be deleted */ + del_colon = del_cookie = NULL; + app_cookies = 0; + + while (p1 < cur_end) { + /* skip spaces and colons, but keep an eye on these ones */ + while (p1 < cur_end) { + if (*p1 == ';' || *p1 == ',') + colon = p1; + else if (!isspace((int)*p1)) + break; + p1++; + } + + if (p1 == cur_end) + break; + + /* p1 is at the beginning of the cookie name */ + p2 = p1; + while (p2 < cur_end && *p2 != '=') + p2++; + + if (p2 == cur_end) + break; + + p3 = p2 + 1; /* skips the '=' sign */ + if (p3 == cur_end) + break; + + p4 = p3; + while (p4 < cur_end && !isspace((int)*p4) && *p4 != ';' && *p4 != ',') + p4++; + + /* here, we have the cookie name between p1 and p2, + * and its value between p3 and p4. + * we can process it : + * + * Cookie: NAME=VALUE; + * | || || | + * | || || +--> p4 + * | || |+-------> p3 + * | || +--------> p2 + * | |+------------> p1 + * | +-------------> colon + * +--------------------> cur_ptr + */ + + if (*p1 == '$') { + /* skip this one */ + } + else { + /* first, let's see if we want to capture it */ + if (t->fi->capture_name != NULL && + t->logs.cli_cookie == NULL && + (p4 - p1 >= t->fi->capture_namelen) && + memcmp(p1, t->fi->capture_name, t->fi->capture_namelen) == 0) { + int log_len = p4 - p1; + + if ((t->logs.cli_cookie = pool_alloc(capture)) == NULL) { + Alert("HTTP logging : out of memory.\n"); + } else { + if (log_len > t->fi->capture_len) + log_len = t->fi->capture_len; + memcpy(t->logs.cli_cookie, p1, log_len); + t->logs.cli_cookie[log_len] = 0; + } + } + + if ((p2 - p1 == t->be->cookie_len) && (t->be->cookie_name != NULL) && + (memcmp(p1, t->be->cookie_name, p2 - p1) == 0)) { + /* Cool... it's the right one */ + struct server *srv = t->be->srv; + char *delim; + + /* if we're in cookie prefix mode, we'll search the delimitor so that we + * have the server ID betweek p3 and delim, and the original cookie between + * delim+1 and p4. Otherwise, delim==p4 : + * + * Cookie: NAME=SRV~VALUE; + * | || || | | + * | || || | +--> p4 + * | || || +--------> delim + * | || |+-----------> p3 + * | || +------------> p2 + * | |+----------------> p1 + * | +-----------------> colon + * +------------------------> cur_ptr + */ + + if (t->be->options & PR_O_COOK_PFX) { + for (delim = p3; delim < p4; delim++) + if (*delim == COOKIE_DELIM) + break; + } + else + delim = p4; + + + /* Here, we'll look for the first running server which supports the cookie. + * This allows to share a same cookie between several servers, for example + * to dedicate backup servers to specific servers only. + * However, to prevent clients from sticking to cookie-less backup server + * when they have incidentely learned an empty cookie, we simply ignore + * empty cookies and mark them as invalid. + */ + if (delim == p3) + srv = NULL; + + while (srv) { + if ((srv->cklen == delim - p3) && !memcmp(p3, srv->cookie, delim - p3)) { + if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) { + /* we found the server and it's usable */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; + t->srv = srv; + break; + } else { + /* we found a server, but it's down */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_DOWN; + } + } + srv = srv->next; + } + + if (!srv && !(t->flags & SN_CK_DOWN)) { + /* no server matched this cookie */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_INVALID; + } + + /* depending on the cookie mode, we may have to either : + * - delete the complete cookie if we're in insert+indirect mode, so that + * the server never sees it ; + * - remove the server id from the cookie value, and tag the cookie as an + * application cookie so that it does not get accidentely removed later, + * if we're in cookie prefix mode + */ + if ((t->be->options & PR_O_COOK_PFX) && (delim != p4)) { + int delta; /* negative */ + + delta = buffer_replace2(req, p3, delim + 1, NULL, 0); + p4 += delta; + cur_end += delta; + cur_next += delta; + cur_hdr->len += delta; + + del_cookie = del_colon = NULL; + app_cookies++; /* protect the header from deletion */ + } + else if (del_cookie == NULL && + (t->be->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND)) { + del_cookie = p1; + del_colon = colon; + } + } else { + /* now we know that we must keep this cookie since it's + * not ours. But if we wanted to delete our cookie + * earlier, we cannot remove the complete header, but we + * can remove the previous block itself. + */ + app_cookies++; + + if (del_cookie != NULL) { + int delta; /* negative */ + + delta = buffer_replace2(req, del_cookie, p1, NULL, 0); + p4 += delta; + cur_end += delta; + cur_next += delta; + cur_hdr->len += delta; + del_cookie = del_colon = NULL; + } + } + + if ((t->be->appsession_name != NULL) && + (memcmp(p1, t->be->appsession_name, p2 - p1) == 0)) { + /* first, let's see if the cookie is our appcookie*/ + + /* Cool... it's the right one */ + + asession_temp = &local_asession; + + if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { + Alert("Not enough memory process_cli():asession->sessid:malloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n"); + return; + } + + memcpy(asession_temp->sessid, p3, t->be->appsession_len); + asession_temp->sessid[t->be->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) { + /* free previously allocated memory */ + pool_free_to(apools.sessid, local_asession.sessid); + Alert("Not enough memory process_cli():asession:calloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); + return; + } + + asession_temp->sessid = local_asession.sessid; + asession_temp->serverid = local_asession.serverid; + chtbl_insert(&(t->be->htbl_proxy), (void *) asession_temp); + } else { + /* free previously allocated memory */ + pool_free_to(apools.sessid, local_asession.sessid); + } + + if (asession_temp->serverid == NULL) { + Alert("Found Application Session without matching server.\n"); + } else { + struct server *srv = t->be->srv; + while (srv) { + if (strcmp(srv->id, asession_temp->serverid) == 0) { + if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) { + /* we found the server and it's usable */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; + t->srv = srv; + break; + } else { + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_DOWN; + } + } + srv = srv->next; + }/* end while(srv) */ + }/* end else if server == NULL */ + + tv_delayfrom(&asession_temp->expire, &now, t->be->appsession_timeout); + }/* end if ((t->proxy->appsession_name != NULL) ... */ + } + + /* we'll have to look for another cookie ... */ + p1 = p4; + } /* while (p1 < cur_end) */ + + /* There's no more cookie on this line. + * We may have marked the last one(s) for deletion. + * We must do this now in two ways : + * - if there is no app cookie, we simply delete the header ; + * - if there are app cookies, we must delete the end of the + * string properly, including the colon/semi-colon before + * the cookie name. + */ + if (del_cookie != NULL) { + int delta; + if (app_cookies) { + delta = buffer_replace2(req, del_colon, cur_end, NULL, 0); + cur_end = del_colon; + cur_next += delta; + cur_hdr->len += delta; + } else { + delta = buffer_replace2(req, cur_ptr, cur_next, NULL, 0); + cur_next += delta; + + /* FIXME: this should be a separate function */ + t->hdr_idx.v[old_idx].next = cur_hdr->next; + t->hdr_idx.used--; + cur_hdr->len = 0; + } + } + + /* keep the link from this header to next one */ + old_idx = cur_idx; + } /* end of cookie processing on this header */ +} + + + +/* + * Try to retrieve a known appsession in the URI, then the associated server. + * If the server is found, it's assigned to the session. + */ + +void get_srv_from_appsession(struct session *t, const char *begin, const char *end) +{ + appsess *asession_temp = NULL; + appsess local_asession; + char *request_line; + + if (t->be->appsession_name == NULL || + (memcmp(begin, "GET ", 4) != 0 && memcmp(begin, "POST ", 5) != 0) || + (request_line = memchr(begin, ';', end - begin)) == NULL || + ((1 + t->be->appsession_name_len + 1 + t->be->appsession_len) > (end - request_line))) + return; + + /* skip ';' */ + request_line++; + + /* look if we have a jsessionid */ + if (strncasecmp(request_line, t->be->appsession_name, t->be->appsession_name_len) != 0) + return; + + /* skip jsessionid= */ + request_line += t->be->appsession_name_len + 1; + + /* First try if we already have an appsession */ + asession_temp = &local_asession; + + if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { + Alert("Not enough memory process_cli():asession_temp->sessid:calloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough Memory process_cli():asession_temp->sessid:calloc().\n"); + return; + } + + /* Copy the sessionid */ + memcpy(asession_temp->sessid, request_line, t->be->appsession_len); + asession_temp->sessid[t->be->appsession_len] = 0; + asession_temp->serverid = NULL; + + /* only do insert, if lookup fails */ + if (chtbl_lookup(&(t->be->htbl_proxy), (void *)&asession_temp)) { + if ((asession_temp = pool_alloc(appsess)) == NULL) { + /* free previously allocated memory */ + pool_free_to(apools.sessid, local_asession.sessid); + Alert("Not enough memory process_cli():asession:calloc().\n"); + send_log(t->be, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); + return; + } + asession_temp->sessid = local_asession.sessid; + asession_temp->serverid = local_asession.serverid; + chtbl_insert(&(t->be->htbl_proxy), (void *) asession_temp); + } + else { + /* free previously allocated memory */ + pool_free_to(apools.sessid, local_asession.sessid); + } + + tv_delayfrom(&asession_temp->expire, &now, t->be->appsession_timeout); + asession_temp->request_count++; + +#if defined(DEBUG_HASH) + print_table(&(t->proxy->htbl_proxy)); +#endif + if (asession_temp->serverid == NULL) { + Alert("Found Application Session without matching server.\n"); + } else { + struct server *srv = t->be->srv; + while (srv) { + if (strcmp(srv->id, asession_temp->serverid) == 0) { + if (srv->state & SRV_RUNNING || t->be->options & PR_O_PERSIST) { + /* we found the server and it's usable */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; + t->srv = srv; + break; + } else { + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_DOWN; + } + } + srv = srv->next; + } + } +} + + +/* + * Print a debug line with a header + */ +void debug_hdr(const char *dir, struct session *t, const char *start, const char *end) +{ + int len, max; + len = sprintf(trash, "%08x:%s.%s[%04x:%04x]: ", t->uniq_id, t->be->id, + dir, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); + max = end - start; + UBOUND(max, sizeof(trash) - len - 1); + len += strlcpy2(trash + len, start, max + 1); + trash[len++] = '\n'; + write(1, trash, len); +} + + /* * Local variables: * c-indent-level: 8