This patch for stunnel-4.15 adds support for the X-Forwarded-For header insertion when the option 'xforwardedfor' is set to 'yes'. Unlike some other patches found on the net, this one does reserve some space for the insertion so it does not introduce any buffer overflow. Moreover, it inserts the header after all other headers and not at the top, which respects the order of headers even if the client sets the same header. Willy Tarreau diff -ru stunnel-4.15/doc/stunnel.8 stunnel-4.15_patched/doc/stunnel.8 --- stunnel-4.15/doc/stunnel.8 2005-11-17 11:39:08.000000000 +0100 +++ stunnel-4.15_patched/doc/stunnel.8 2006-07-07 09:13:34.000000000 +0200 @@ -446,6 +446,10 @@ application protocol to negotiate \s-1SSL\s0 .Sp currently supported: cifs, connect, nntp, pop3, smtp +.IP "\fBxforwardedfor\fR = yes | no" 4 +.IX Item "xforwardedfor = yes | no" +append an 'X-Forwarded-For:' HTTP request header providing the +client's IP address to the server. .IP "\fBprotocolCredentials\fR = username:password" 4 .IX Item "protocolCredentials = username:password" credentials for protocol negotiations diff -ru stunnel-4.15/doc/stunnel.fr.8 stunnel-4.15_patched/doc/stunnel.fr.8 --- stunnel-4.15/doc/stunnel.fr.8 2005-01-15 10:15:12.000000000 +0100 +++ stunnel-4.15_patched/doc/stunnel.fr.8 2006-07-07 09:11:55.000000000 +0200 @@ -445,6 +445,10 @@ Négocie avec \s-1SSL\s0 selon le protocole indiqué .Sp Actuellement gérés\ : cifs, nntp, pop3, smtp +.IP "\fBxforwardedfor\fR = yes | no" 4 +.IX Item "xforwardedfor = yes | no" +Ajoute un en-tête 'X-Forwarded-For:' dans la requête HTTP fournissant +au serveur l'adresse IP du client. .IP "\fBpty\fR = yes | no (Unix seulement)" 4 .IX Item "pty = yes | no (Unix seulement)" Alloue un pseudo-terminal pour l'option «\ exec\ » diff -ru stunnel-4.15/src/client.c stunnel-4.15_patched/src/client.c --- stunnel-4.15/src/client.c 2006-03-11 11:27:31.000000000 +0100 +++ stunnel-4.15_patched/src/client.c 2006-07-07 09:11:55.000000000 +0200 @@ -90,6 +90,12 @@ return NULL; } c->opt=opt; + /* some options need space to add some information */ + if (c->opt->option.xforwardedfor) + c->buffsize = BUFFSIZE - BUFF_RESERVED; + else + c->buffsize = BUFFSIZE; + c->crlf_seen=0; c->local_rfd.fd=rfd; c->local_wfd.fd=wfd; return c; @@ -373,6 +379,28 @@ } } +/* Moves all data from the buffer between positions and + * to insert of length . and are updated to their + * new respective values, and the number of characters inserted is returned. + * If is too long, nothing is done and -1 is returned. + * Note that neither nor can be NULL. + */ +static int buffer_insert_with_len(char *buffer, int *start, int *stop, int limit, char *string, int len) { + if (len > limit - *stop) + return -1; + if (*start > *stop) + return -1; + memmove(buffer + *start + len, buffer + *start, *stop - *start); + memcpy(buffer + *start, string, len); + *start += len; + *stop += len; + return len; +} + +static int buffer_insert(char *buffer, int *start, int *stop, int limit, char *string) { + return buffer_insert_with_len(buffer, start, stop, limit, string, strlen(string)); +} + /****************************** some defines for transfer() */ /* is socket/SSL open for read/write? */ #define sock_rd (c->sock_rfd->rd) @@ -409,9 +437,9 @@ /****************************** setup c->fds structure */ s_poll_zero(&c->fds); /* Initialize the structure */ - if(sock_rd && c->sock_ptrsock_ptrbuffsize) /* socket input buffer not full*/ s_poll_add(&c->fds, c->sock_rfd->fd, 1, 0); - if((ssl_rd && c->ssl_ptrssl_ptrbuffsize) || /* SSL input buffer not full */ ((c->sock_ptr || ssl_closing==CL_RETRY) && want_rd)) /* want to SSL_write or SSL_shutdown but read from the * underlying socket needed for the SSL protocol */ @@ -420,7 +448,7 @@ s_poll_add(&c->fds, c->sock_wfd->fd, 0, 1); if(c->sock_ptr || /* socket input buffer not empty */ ssl_closing==CL_INIT /* need to send close_notify */ || - ((c->ssl_ptrssl_ptrbuffsize || ssl_closing==CL_RETRY) && want_wr)) /* want to SSL_read or SSL_shutdown but write to the * underlying socket needed for the SSL protocol */ s_poll_add(&c->fds, c->ssl_wfd->fd, 0, 1); @@ -479,7 +507,7 @@ break; default: memmove(c->ssl_buff, c->ssl_buff+num, c->ssl_ptr-num); - if(c->ssl_ptr==BUFFSIZE) /* buffer was previously full */ + if(c->ssl_ptr>=c->buffsize) /* buffer was previously full */ check_SSL_pending=1; /* check for data buffered by SSL */ c->ssl_ptr-=num; c->sock_bytes+=num; @@ -530,7 +558,7 @@ /****************************** read from socket */ if(sock_rd && sock_can_rd) { num=readsocket(c->sock_rfd->fd, - c->sock_buff+c->sock_ptr, BUFFSIZE-c->sock_ptr); + c->sock_buff+c->sock_ptr, c->buffsize-c->sock_ptr); switch(num) { case -1: parse_socket_error(c, "readsocket"); @@ -546,16 +574,76 @@ } /****************************** read from SSL */ - if(ssl_rd && c->ssl_ptrssl_ptrbuffsize && ( /* input buffer not full */ ssl_can_rd || (want_wr && ssl_can_wr) || /* SSL_read wants to write to the underlying descriptor */ (check_SSL_pending && SSL_pending(c->ssl)) /* write made space from full buffer */ )) { - num=SSL_read(c->ssl, c->ssl_buff+c->ssl_ptr, BUFFSIZE-c->ssl_ptr); + num=SSL_read(c->ssl, c->ssl_buff+c->ssl_ptr, c->buffsize-c->ssl_ptr); switch(err=SSL_get_error(c->ssl, num)) { case SSL_ERROR_NONE: - c->ssl_ptr+=num; + if (c->buffsize != BUFFSIZE) { /* some work left to do */ + int last = c->ssl_ptr; + c->ssl_ptr += num; + + /* Look for end of HTTP headers between last and ssl_ptr. + * To achieve this reliably, we have to count the number of + * successive [CR]LF and to memorize it in case it's spread + * over multiple segments. --WT. + */ + while (last < c->ssl_ptr) { + if (c->ssl_buff[last] == '\n') { + if (++c->crlf_seen == 2) + break; + } else if (last < c->ssl_ptr - 1 && + c->ssl_buff[last] == '\r' && + c->ssl_buff[last+1] == '\n') { + if (++c->crlf_seen == 2) + break; + last++; + } else if (c->ssl_buff[last] != '\r') + /* don't refuse '\r' because we may get a '\n' on next read */ + c->crlf_seen = 0; + last++; + } + if (c->crlf_seen >= 2) { + /* We have all the HTTP headers now. We don't need to + * reserve any space anymore. points to the + * first byte of unread data, and points to the + * exact location where we want to insert our headers, + * which is right before the empty line. + */ + c->buffsize = BUFFSIZE; + + if (c->opt->option.xforwardedfor) { + /* X-Forwarded-For: xxxx \r\n\0 */ + char xforw[17 + IPLEN + 3]; + + /* We will insert our X-Forwarded-For: header here. + * We need to write the IP address, but if we use + * sprintf, it will pad with the terminating 0. + * So we will pass via a temporary buffer allocated + * on the stack. + */ + memcpy(xforw, "X-Forwarded-For: ", 17); + if (getnameinfo(&c->peer_addr.addr[0].sa, + addr_len(c->peer_addr.addr[0]), + xforw + 17, IPLEN, NULL, 0, + NI_NUMERICHOST) == 0) { + strcat(xforw + 17, "\r\n"); + buffer_insert(c->ssl_buff, &last, &c->ssl_ptr, + c->buffsize, xforw); + } + /* last still points to the \r\n and ssl_ptr to the + * end of the buffer, so we may add as many headers + * as wee need to. + */ + } + } + } + else + c->ssl_ptr+=num; watchdog=0; /* reset watchdog */ break; case SSL_ERROR_WANT_WRITE: diff -ru stunnel-4.15/src/common.h stunnel-4.15_patched/src/common.h --- stunnel-4.15/src/common.h 2006-03-10 09:54:57.000000000 +0100 +++ stunnel-4.15_patched/src/common.h 2006-07-07 09:11:55.000000000 +0200 @@ -56,6 +56,9 @@ /* I/O buffer size */ #define BUFFSIZE 16384 +/* maximum space reserved for header insertion in BUFFSIZE */ +#define BUFF_RESERVED 1024 + /* Length of strings (including the terminating '\0' character) */ #define STRLEN 256 diff -ru stunnel-4.15/src/options.c stunnel-4.15_patched/src/options.c --- stunnel-4.15/src/options.c 2006-03-06 14:35:11.000000000 +0100 +++ stunnel-4.15_patched/src/options.c 2006-07-07 09:11:55.000000000 +0200 @@ -681,6 +681,29 @@ break; } + /* xforwardedfor */ + switch(cmd) { + case CMD_INIT: + section->option.xforwardedfor=0; + break; + case CMD_EXEC: + if(strcasecmp(opt, "xforwardedfor")) + break; + if(!strcasecmp(arg, "yes")) + section->option.xforwardedfor=1; + else if(!strcasecmp(arg, "no")) + section->option.xforwardedfor=0; + else + return "argument should be either 'yes' or 'no'"; + return NULL; /* OK */ + case CMD_DEFAULT: + break; + case CMD_HELP: + log_raw("%-15s = yes|no append an HTTP X-Forwarded-For header", + "xforwardedfor"); + break; + } + /* exec */ #ifndef USE_WIN32 switch(cmd) { diff -ru stunnel-4.15/src/prototypes.h stunnel-4.15_patched/src/prototypes.h --- stunnel-4.15/src/prototypes.h 2006-03-06 12:33:43.000000000 +0100 +++ stunnel-4.15_patched/src/prototypes.h 2006-07-07 09:11:55.000000000 +0200 @@ -184,6 +184,7 @@ unsigned int delayed_lookup:1; unsigned int accept:1; unsigned int remote:1; + unsigned int xforwardedfor:1; #ifndef USE_WIN32 unsigned int program:1; unsigned int pty:1; @@ -277,6 +278,8 @@ FD *ssl_rfd, *ssl_wfd; /* Read and write SSL descriptors */ int sock_bytes, ssl_bytes; /* Bytes written to socket and ssl */ s_poll_set fds; /* File descriptors */ + int buffsize; /* current buffer size, may be lower than BUFFSIZE */ + int crlf_seen; /* the number of successive CRLF seen */ } CLI; extern int max_clients;