CVE-2020-7670: Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling') (CWE-444)
value: agoo prior to 2.14.0 allows request smuggling attacks where agoo is used as a backend and a frontend proxy also being vulnerable. HTTP pipelining issues and request smuggling attacks might be possible due to incorrect Content-Length and Transfer encoding header parsing. It is possible to conduct HTTP request smuggling attacks where `agoo` is used as part of a chain of backend servers due to insufficient `Content-Length` and `Transfer Encoding` parsing.
CWE description: The product acts as an intermediary HTTP agent
         (such as a proxy or firewall) in the data flow between two
         entities such as a client and server, but it does not
         interpret malformed HTTP requests or responses in ways that
         are consistent with how the messages will be processed by
         those entities that are at the ultimate destination.
CVSS v3 base score: 7.5
Published: 2020-06-10T16:15Z
Repository: https://github.com/ohler55/agoo
Language: C
Files changed: CHANGELOG.md, ext/agoo/con.c, ext/agoo/con.h, ext/agoo/http.c, ext/agoo/req.h, ext/agoo/request.c, ext/agoo/server.c, ext/agoo/websocket.c, lib/agoo/version.rb, test/base_handler_test.rb, test/rack_handler_test.rb
Commit date: 2020-11-07 19:07:47 -0500
Commit message: Remote addr (#99)

* REMOTE_ADDR added

* Ready for merge
Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 91dab4b..617acbe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,19 @@
 # Changelog
 
 All changes to the Agoo gem are documented here. Releases follow semantic versioning.
 
+## [2.14.0] - 2020-11-07
+
+### Added
+
+- REMOTE_ADDR element added to requests/env argument to `call()`.
+
+- Added check for multiple Content-Length headers.
+
+- Multiple occurrances of a header are now passed to the Rack `call()` method as an array.
+
 ## [2.13.0] - 2020-07-05
 
 ### Added
 
 - Agoo::Server.use() added. It works similar to the Rack use() function.
diff --git a/ext/agoo/con.c b/ext/agoo/con.c
index d59144a..c430e24 100644
--- a/ext/agoo/con.c
+++ b/ext/agoo/con.c
@@ -61,10 +61,26 @@ agoo_con_create(agooErr err, int sock, uint64_t id, agooBind b) {
     agooCon	c;
 
     if (NULL == (c = (agooCon)AGOO_CALLOC(1, sizeof(struct _agooCon)))) {
 	AGOO_ERR_MEM(err, "Connection");
     } else {
+	// It would be better to get this information in server.c after
+	// accept() but that does not work on macOS so instead a call to
+	// getpeername() is used instead.
+	struct sockaddr_storage	addr;
+	socklen_t		len = sizeof(addr);
+
+	getpeername(sock, (struct sockaddr*)&addr, &len);
+	if (addr.ss_family == AF_INET) {
+	    struct sockaddr_in	*s = (struct sockaddr_in*)&addr;
+
+	    inet_ntop(AF_INET, &s->sin_addr, c->remote, sizeof(c->remote));
+	} else {
+	    struct sockaddr_in6	*s = (struct sockaddr_in6*)&addr;
+
+	    inet_ntop(AF_INET6, &s->sin6_addr, c->remote, sizeof(c->remote));
+	}
 	c->sock = sock;
 	c->id = id;
 	c->timeout = dtime() + CON_TIMEOUT;
 	c->bind = b;
 	c->loop = NULL;
@@ -435,10 +451,11 @@ con_header_read(agooCon c, size_t *mlenp) {
     }
     c->req->msg[mlen] = '\0';
     c->req->method = method;
     c->req->upgrade = AGOO_UP_NONE;
     c->req->up = NULL;
+    memcpy(c->req->remote, c->remote, sizeof(c->remote));
     c->req->path.start = c->req->msg + (path.start - c->buf);
     c->req->path.len = (int)(path.end - path.start);
     c->req->query.start = c->req->msg + (query - c->buf);
     c->req->query.len = (int)(qend - query);
     c->req->query.start[c->req->query.len] = '\0';
diff --git a/ext/agoo/con.h b/ext/agoo/con.h
index 4e69812..c23ac96 100644
--- a/ext/agoo/con.h
+++ b/ext/agoo/con.h
@@ -1,10 +1,11 @@
 // Copyright (c) 2018, Peter Ohler, All rights reserved.
 
 #ifndef AGOO_CON_H
 #define AGOO_CON_H
 
+#include <arpa/inet.h>
 #include <poll.h>
 #include <pthread.h>
 #include <stdbool.h>
 #include <stdint.h>
 #ifdef HAVE_OPENSSL_SSL_H
@@ -43,10 +44,11 @@ typedef struct _agooCon {
     struct _agooCon		*next;
     int				sock;
     struct _agooBind		*bind;
     struct pollfd		*pp;
     uint64_t			id;
+    char			remote[INET6_ADDRSTRLEN];
     char			buf[MAX_HEADER_SIZE];
     size_t			bcnt;
 
     ssize_t			mcnt;  // how much has been read so far
     ssize_t			wcnt;  // how much has been written
diff --git a/ext/agoo/http.c b/ext/agoo/http.c
index 28096c6..a551740 100644
--- a/ext/agoo/http.c
+++ b/ext/agoo/http.c
@@ -24,11 +24,11 @@ typedef struct _cache {
 } *Cache;
 
 struct _cache		key_cache;
 
 // The rack spec indicates the characters (),/:;<=>?@[]{} are invalid which
-// clearly is not consisten with RFC7230 so stick with the RFC.
+// clearly is not consistent with RFC7230 so stick with the RFC.
 static char		header_value_chars[256] = "\
 xxxxxxxxxxoxxxxxxxxxxxxxxxxxxxxx\
 oooooooooooooooooooooooooooooooo\
 oooooooooooooooooooooooooooooooo\
 ooooooooooooooooooooooooooooooox\
@@ -48,11 +48,10 @@ static const char	*header_keys[] = {
     "Accept-Charset",
     "Accept-Datetime",
     "Accept-Encoding",
     "Accept-Features",
     "Accept-Language",
-    "Accept-Language",
     "Accept-Patch",
     "Accept-Post",
     "Accept-Ranges",
     "Access-Control",
     "Access-Control-Allow-Credentials",
@@ -72,11 +71,10 @@ static const char	*header_keys[] = {
     "Apparently-To",
     "Apply-To-Redirect-Ref",
     "Approved",
     "Archive",
     "Archived-At",
-    "Archived-At",
     "Article-Names",
     "Article-Updates",
     "Authentication-Control",
     "Authentication-Info",
     "Authentication-Results",
@@ -97,40 +95,31 @@ static const char	*header_keys[] = {
     "Cancel-Key",
     "Cancel-Lock",
     "Cc",
     "Close",
     "Comments",
-    "Comments",
     "Compliance",
     "Connection",
     "Content-Alternative",
     "Content-Base",
-    "Content-Base",
     "Content-Description",
     "Content-Disposition",
-    "Content-Disposition",
     "Content-Duration",
     "Content-Encoding",
     "Content-ID",
-    "Content-ID",
     "Content-Identifier",
     "Content-Language",
-    "Content-Language",
     "Content-Length",
     "Content-Location",
-    "Content-Location",
-    "Content-MD5",
     "Content-MD5",
     "Content-Range",
     "Content-Return",
     "Content-Script-Type",
     "Content-Style-Type",
     "Content-Transfer-Encoding",
-    "Content-Transfer-Encoding",
     "Content-Translation-Type",
     "Content-Type",
-    "Content-Type",
     "Content-Version",
     "Content-features",
     "Control",
     "Conversion",
     "Conversion-With-Loss",
@@ -139,12 +128,11 @@ static const char	*header_keys[] = {
     "Cost",
     "DASL",
     "DAV",
     "DKIM-Signature",
     "DL-Expansion-History",
-    "Date",
-    "Date",
+    "DNT",
     "Date",
     "Date-Received",
     "Default-Style",
     "Deferred-Delivery",
     "Delivery-Date",
@@ -180,28 +168,24 @@ static const char	*header_keys[] = {
     "Downgraded-Resent-To",
     "Downgraded-Return-Path",
     "Downgraded-Sender",
     "Downgraded-To",
     "EDIINT-Features",
-    "EDIINT-Features",
     "ETag",
     "Eesst-Version",
     "Encoding",
     "Encrypted",
     "Errors-To",
     "Expect",
     "Expires",
-    "Expires",
-    "Expires",
     "Expiry-Date",
     "Ext",
     "Followup-To",
     "Form-Sub",
     "Forwarded",
     "From",
-    "From",
-    "From",
+    "Front-End-Https",
     "Generate-Delivery-Report",
     "GetProfile",
     "HTTP2-Settings",
     "Hobareg",
     "Host",
@@ -217,14 +201,12 @@ static const char	*header_keys[] = {
     "In-Reply-To",
     "Incomplete-Copy",
     "Injection-Date",
     "Injection-Info",
     "Jabber-ID",
-    "Jabber-ID",
     "Keep-Alive",
     "Keywords",
-    "Keywords",
     "Label",
     "Language",
     "Last-Modified",
     "Latest-Delivery-Time",
     "Lines",
@@ -238,11 +220,10 @@ static const char	*header_keys[] = {
     "List-Unsubscribe",
     "List-Unsubscribe-Post",
     "Location",
     "Lock-Token",
     "MIME-Version",
-    "MIME-Version",
     "MMHS-Acp127-Message-Identifier",
     "MMHS-Authorizing-Users",
     "MMHS-Codress-Message-Indicator",
     "MMHS-Copy-Precedence",
     "MMHS-Exempted-Address",
@@ -260,12 +241,10 @@ static const char	*header_keys[] = {
     "Man",
     "Max-Forwards",
     "Memento-Datetime",
     "Message-Context",
     "Message-ID",
-    "Message-ID",
-    "Message-ID",
     "Message-Type",
     "Meter",
     "Method-Check",
     "Method-Check-Expires",
     "NNTP-Posting-Date",
@@ -277,11 +256,10 @@ static const char	*header_keys[] = {
     "Opt",
     "Optional",
     "Optional-WWW-Authenticate",
     "Ordering-Type",
     "Organization",
-    "Organization",
     "Origin",
     "Original-Encoded-Information-Types",
     "Original-From",
     "Original-Message-ID",
     "Original-Recipient",
@@ -290,11 +268,10 @@ static const char	*header_keys[] = {
     "Originator-Return-Address",
     "Overwrite",
     "P3P",
     "PEP",
     "PICS-Label",
-    "PICS-Label",
     "Path",
     "Pep-Info",
     "Position",
     "Posting-Version",
     "Pragma",
@@ -309,27 +286,26 @@ static const char	*header_keys[] = {
     "Protocol-Query",
     "Protocol-Request",
     "Proxy-Authenticate",
     "Proxy-Authentication-Info",
     "Proxy-Authorization",
+    "Proxy-Connection",
     "Proxy-Features",
     "Proxy-Instruction",
     "Public",
     "Public-Key-Pins",
     "Public-Key-Pins-Report-Only",
     "Range",
     "Received",
     "Received-SPF",
     "Redirect-Ref",
     "References",
-    "References",
     "Referer",
     "Referer-Root",
     "Relay-Version",
     "Reply-By",
     "Reply-To",
-    "Reply-To",
     "Require-Recipient-Valid-Since",
     "Resent-Bcc",
     "Resent-Cc",
     "Resent-Date",
     "Resent-From",
@@ -343,10 +319,11 @@ static const char	*header_keys[] = {
     "Return-Path",
     "SIO-Label",
     "SIO-Label-History",
     "SLUG",
     "Safe",
+    "Save-Data",
     "Schedule-Reply",
     "Schedule-Tag",
     "Sec-WebSocket-Accept",
     "Sec-WebSocket-Extensions",
     "Sec-WebSocket-Key",
@@ -365,15 +342,13 @@ static const char	*header_keys[] = {
     "Solicitation",
     "Status-URI",
     "Strict-Transport-Security",
     "SubOK",
     "Subject",
-    "Subject",
     "Subst",
     "Summary",
     "Supersedes",
-    "Supersedes",
     "Surrogate-Capability",
     "Surrogate-Control",
     "TCN",
     "TE",
     "TTL",
@@ -388,37 +363,47 @@ static const char	*header_keys[] = {
     "UA-Pixels",
     "UA-Resolution",
     "UA-Windowpixels",
     "URI",
     "Upgrade",
+    "Upgrade-Insecure-Requests",
     "Urgency",
     "User-Agent",
-    "User-Agent",
     "VBR-Info",
     "Variant-Vary",
     "Vary",
     "Version",
     "Via",
     "WWW-Authenticate",
     "Want-Digest",
     "Warning",
-    "X-Archived-At",
+    "X-ATT-DeviceId",
     "X-Archived-At",
     "X-Content-Type-Options",
+    "X-Correlation-ID",
+    "X-Csrf-Token",
     "X-Device-Accept",
     "X-Device-Accept-Charset",
     "X-Device-Accept-Encoding",
     "X-Device-Accept-Language",
     "X-Device-User-Agent",
+    "X-Forwarded-For",
+    "X-Forwarded-Host",
+    "X-Forwarded-Proto",
     "X-Frame-Options",
+    "X-Http-Method-Override",
     "X-Mittente",
     "X-PGP-Sig",
+    "X-Request-ID",
+    "X-Requested-With",
     "X-Ricevuta",
     "X-Riferimento-Message-ID",
     "X-TipoRicevuta",
     "X-Trasporto",
+    "X-UIDH",
     "X-VerificaSicurezza",
+    "X-Wap-Profile",
     "X-XSS-Protection",
     "X400-Content-Identifier",
     "X400-Content-Return",
     "X400-Content-Type",
     "X400-MTS-Identifier",
@@ -467,11 +452,11 @@ static void
 key_set(const char *key) {
     int		len = (int)strlen(key);
     int64_t	h = calc_hash(key, &len);
     Slot	*bucket = get_bucketp(h);
     Slot	s;
-    
+
     if (NULL != (s = (Slot)AGOO_MALLOC(sizeof(struct _slot)))) {
 	s->hash = h;
 	s->klen = len;
 	s->key = key;
 	s->next = *bucket;
@@ -480,11 +465,11 @@ key_set(const char *key) {
 }
 
 void
 agoo_http_init() {
     const char	**kp = header_keys;
-    
+
     memset(&key_cache, 0, sizeof(struct _cache));
     for (; NULL != *kp; kp++) {
 	key_set(*kp);
     }
 }
@@ -548,11 +533,11 @@ agoo_http_header_ok(agooErr err, const char *key, int klen, const char *value, i
 }
 
 const char*
 agoo_http_code_message(int code) {
     const char	*msg = "";
-    
+
     switch (code) {
     case 100:	msg = "Continue";				break;
     case 101:	msg = "Switching Protocols";			break;
     case 102:	msg = "Processing";				break;
     case 200:	msg = "OK";					break;
diff --git a/ext/agoo/req.h b/ext/agoo/req.h
index 0446845..75d23d0 100644
--- a/ext/agoo/req.h
+++ b/ext/agoo/req.h
@@ -1,10 +1,11 @@
 // Copyright (c) 2018, Peter Ohler, All rights reserved.
 
 #ifndef AGOO_REQ_H
 #define AGOO_REQ_H
 
+#include <arpa/inet.h>
 #include <stdint.h>
 
 #include "hook.h"
 #include "kinds.h"
 
@@ -30,10 +31,11 @@ typedef struct _agooReq {
     struct _agooUpgraded	*up;
     struct _agooStr		path;
     struct _agooStr		query;
     struct _agooStr		header;
     struct _agooStr		body;
+    char			remote[INET6_ADDRSTRLEN];
     void			*env;
     agooHook			hook;
     size_t			mlen;   // allocated msg length
     char			msg[8]; // expanded to be full message
 } *agooReq;
diff --git a/ext/agoo/request.c b/ext/agoo/request.c
index 602c057..7172a70 100644
--- a/ext/agoo/request.c
+++ b/ext/agoo/request.c
@@ -41,10 +41,11 @@ static VALUE	rack_multithread_val = Qundef;
 static VALUE	rack_run_once_val = Qundef;
 static VALUE	rack_upgrade_val = Qundef;
 static VALUE	rack_url_scheme_val = Qundef;
 static VALUE	rack_version_val = Qundef;
 static VALUE	rack_version_val_val = Qundef;
+static VALUE	remote_addr_val = Qundef;
 static VALUE	request_method_val = Qundef;
 static VALUE	script_name_val = Qundef;
 static VALUE	server_name_val = Qundef;
 static VALUE	server_port_val = Qundef;
 static VALUE	slash_val = Qundef;
@@ -94,10 +95,31 @@ req_method(agooReq r) {
 static VALUE
 method(VALUE self) {
     return req_method((agooReq)DATA_PTR(self));
 }
 
+static VALUE
+req_remote_addr(agooReq r) {
+
+    if (NULL == r) {
+	rb_raise(rb_eArgError, "Request is no longer valid.");
+    }
+    return rb_str_new(r->remote, strlen(r->remote));
+}
+
+/* Document-method: remote_addr
+ *
+ * call-seq: remote_addr()
+ *
+ * Returns the remote address.
+ */
+static VALUE
+remote_addr(VALUE self) {
+    return req_remote_addr((agooReq)DATA_PTR(self));
+}
+
+
 static VALUE
 req_script_name(agooReq r) {
     // The logic is a bit tricky here and for path_info. If the HTTP path is /
     // then the script_name must be empty and the path_info will be /. All
     // other cases are handled with the full path in script_name and path_info
@@ -364,18 +386,33 @@ rack_run_once(VALUE self) {
     return Qfalse;
 }
 
 static void
 add_header_value(VALUE hh, const char *key, int klen, const char *val, int vlen) {
+    VALUE	v;
+
     if (sizeof(content_type) - 1 == klen && 0 == strncasecmp(key, content_type, sizeof(content_type) - 1)) {
-	rb_hash_aset(hh, content_type_val, rb_str_new(val, vlen));
+	if (Qnil == (v = rb_hash_lookup2(hh, content_type_val, Qnil))) {
+	    rb_hash_aset(hh, content_type_val, rb_str_new(val, vlen));
+	} else {
+	    volatile VALUE	a = rb_ary_new();
+
+	    rb_ary_push(a, v);
+	    rb_ary_push(a, rb_str_new(val, vlen));
+	    rb_hash_aset(hh, content_type_val, a);
+	}
     } else if (sizeof(content_length) - 1 == klen && 0 == strncasecmp(key, content_length, sizeof(content_length) - 1)) {
-	rb_hash_aset(hh, content_length_val, rb_str_new(val, vlen));
+	if (Qnil == (v = rb_hash_lookup2(hh, content_length_val, Qnil))) {
+	    rb_hash_aset(hh, content_length_val, rb_str_new(val, vlen));
+	} else {
+	    rb_raise(rb_eArgError, "Multiple Content-Length headers.");
+	}
     } else {
 	char		hkey[1024];
 	char		*k = hkey;
 	volatile VALUE	sval = rb_str_new(val, vlen);
+	volatile VALUE	kval;
 
 	strcpy(hkey, "HTTP_");
 	k = hkey + 5;
 	if ((int)(sizeof(hkey) - 5) <= klen) {
 	    klen = sizeof(hkey) - 6;
@@ -390,11 +427,20 @@ add_header_value(VALUE hh, const char *key, int klen, const char *val, int vlen)
 		*k = '_';
 	    } else {
 		*k = toupper(*k);
 	    }
 	}
-	rb_hash_aset(hh, rb_str_new(hkey, klen + 5), sval);
+	kval = rb_str_new(hkey, klen + 5);
+	if (Qnil == (v = rb_hash_lookup2(hh, kval, Qnil))) {
+	    rb_hash_aset(hh, kval, sval);
+	} else {
+	    volatile VALUE	a = rb_ary_new();
+
+	    rb_ary_push(a, v);
+	    rb_ary_push(a, sval);
+	    rb_hash_aset(hh, kval, a);
+	}
     }
 }
 
 static void
 fill_headers(agooReq r, VALUE hash) {
@@ -543,12 +589,13 @@ request_env(agooReq req, VALUE self) {
 
 	rb_hash_aset(env, request_method_val, req_method(req));
 	rb_hash_aset(env, script_name_val, req_script_name(req));
 	rb_hash_aset(env, path_info_val, req_path_info(req));
 	rb_hash_aset(env, query_string_val, req_query_string(req));
-	rb_hash_aset(env, server_name_val, req_server_name(req));
+	rb_hash_aset(env, remote_addr_val, req_remote_addr(req));
 	rb_hash_aset(env, server_port_val, req_server_port(req));
+	rb_hash_aset(env, server_name_val, req_server_name(req));
 	fill_headers(req, env);
 	rb_hash_aset(env, rack_version_val, rack_version_val_val);
 	rb_hash_aset(env, rack_url_scheme_val, req_rack_url_scheme(req));
 	rb_hash_aset(env, rack_input_val, req_rack_input(req));
 	rb_hash_aset(env, rack_errors_val, req_rack_errors(req));
@@ -661,10 +708,11 @@ request_init(VALUE mod) {
     rb_define_method(req_class, "script_name", script_name, 0);
     rb_define_method(req_class, "path_info", path_info, 0);
     rb_define_method(req_class, "query_string", query_string, 0);
     rb_define_method(req_class, "server_name", server_name, 0);
     rb_define_method(req_class, "server_port", server_port, 0);
+    rb_define_method(req_class, "remote_addr", remote_addr, 0);
     rb_define_method(req_class, "rack_version", rack_version, 0);
     rb_define_method(req_class, "rack_url_scheme", rack_url_scheme, 0);
     rb_define_method(req_class, "rack_input", rack_input, 0);
     rb_define_method(req_class, "rack_errors", rack_errors, 0);
     rb_define_method(req_class, "rack_multithread", rack_multithread, 0);
@@ -711,10 +759,11 @@ request_init(VALUE mod) {
     rack_multithread_val = rb_str_new_cstr("rack.multithread");	rb_gc_register_address(&rack_multithread_val);
     rack_run_once_val = rb_str_new_cstr("rack.run_once");	rb_gc_register_address(&rack_run_once_val);
     rack_upgrade_val = rb_str_new_cstr("rack.upgrade?");	rb_gc_register_address(&rack_upgrade_val);
     rack_url_scheme_val = rb_str_new_cstr("rack.url_scheme");	rb_gc_register_address(&rack_url_scheme_val);
     rack_version_val = rb_str_new_cstr("rack.version");		rb_gc_register_address(&rack_version_val);
+    remote_addr_val = rb_str_new_cstr("REMOTE_ADDR");		rb_gc_register_address(&remote_addr_val);
     request_method_val = rb_str_new_cstr("REQUEST_METHOD");	rb_gc_register_address(&request_method_val);
     script_name_val = rb_str_new_cstr("SCRIPT_NAME");		rb_gc_register_address(&script_name_val);
     server_name_val = rb_str_new_cstr("SERVER_NAME");		rb_gc_register_address(&server_name_val);
     server_port_val = rb_str_new_cstr("SERVER_PORT");		rb_gc_register_address(&server_port_val);
     slash_val = rb_str_new_cstr("/");				rb_gc_register_address(&slash_val);
diff --git a/ext/agoo/server.c b/ext/agoo/server.c
index 5891f00..c09c0b8 100644
--- a/ext/agoo/server.c
+++ b/ext/agoo/server.c
@@ -158,12 +158,12 @@ listen_loop(void *x) {
 #endif
 		    fcntl(client_sock, F_SETFL, O_NONBLOCK);
 		    //fcntl(client_sock, F_SETFL, FNDELAY);
 		    setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
 		    setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
-		    agoo_log_cat(&agoo_con_cat, "Server with pid %d accepted connection %llu on %s [%d]",
-				 getpid(), (unsigned long long)cnt, b->id, con->sock);
+		    agoo_log_cat(&agoo_con_cat, "Server with pid %d accepted connection %llu on %s [%d] from %s",
+				 getpid(), (unsigned long long)cnt, b->id, con->sock, con->remote);
 
 		    con_cnt = atomic_fetch_add(&agoo_server.con_cnt, 1);
 		    if (agoo_server.loop_max > agoo_server.loop_cnt && agoo_server.loop_cnt * LOOP_UP < con_cnt) {
 			add_con_loop();
 		    }
diff --git a/ext/agoo/websocket.c b/ext/agoo/websocket.c
index 590f7c2..ef5ef72 100644
--- a/ext/agoo/websocket.c
+++ b/ext/agoo/websocket.c
@@ -187,10 +187,11 @@ agoo_ws_create_req(agooCon c, long mlen) {
     c->req->msg[mlen] = '\0';
     c->req->mlen = mlen;
     c->req->method = (AGOO_WS_OP_BIN == op) ? AGOO_ON_BIN : AGOO_ON_MSG;
     c->req->upgrade = AGOO_UP_NONE;
     c->req->up = c->up;
+    memcpy(c->req->remote, c->remote, sizeof(c->remote));
     c->req->res = NULL;
     if (c->up->on_msg) {
 	c->req->hook = agoo_hook_create(AGOO_NONE, NULL, c->up->ctx, PUSH_HOOK, &agoo_server.eval_queue);
     }
     return false;
diff --git a/lib/agoo/version.rb b/lib/agoo/version.rb
index a17de2f..c0b8d6a 100644
--- a/lib/agoo/version.rb
+++ b/lib/agoo/version.rb
@@ -1,5 +1,5 @@
 
 module Agoo
   # Agoo version.
-  VERSION = '2.13.0'
+  VERSION = '2.14.0'
 end
diff --git a/test/base_handler_test.rb b/test/base_handler_test.rb
index bdbe6f6..a318546 100755
--- a/test/base_handler_test.rb
+++ b/test/base_handler_test.rb
@@ -80,11 +80,11 @@ class BaseHandlerTest < Minitest::Test
 
   Minitest.after_run {
     GC.start
     Agoo::shutdown
   }
-  
+
   def test_eval
     uri = URI('http://localhost:6470/tellme?a=1')
     req = Net::HTTP::Get.new(uri)
     # Set the headers the way we want them.
     req['Accept-Encoding'] = '*'
@@ -130,26 +130,26 @@ class BaseHandlerTest < Minitest::Test
     req = Net::HTTP::Post.new(uri)
     # Set the headers the way we want them.
     req['Accept-Encoding'] = '*'
     req['Accept'] = 'application/json'
     req['User-Agent'] = 'Ruby'
-    
+
     res = Net::HTTP.start(uri.hostname, uri.port) { |h|
       h.request(req)
     }
     assert_equal(Net::HTTPNoContent, res.class)
   end
-  
+
   def test_put
     uri = URI('http://localhost:6470/makeme')
     req = Net::HTTP::Put.new(uri)
     # Set the headers the way we want them.
     req['Accept-Encoding'] = '*'
     req['Accept'] = 'application/json'
     req['User-Agent'] = 'Ruby'
     req.body = 'hello'
-    
+
     res = Net::HTTP.start(uri.hostname, uri.port) { |h|
       h.request(req)
     }
     assert_equal(Net::HTTPCreated, res.class)
     assert_equal('hello', res.body)
diff --git a/test/rack_handler_test.rb b/test/rack_handler_test.rb
index 7ef6f0b..5f47291 100755
--- a/test/rack_handler_test.rb
+++ b/test/rack_handler_test.rb
@@ -103,10 +103,11 @@ size=big
       "HTTP_USER_AGENT" => "Ruby",
       "HTTP_HOST" => "localhost:6467",
       "PATH_INFO" => "/tellme",
       "QUERY_STRING" => "a=1",
       "REQUEST_METHOD" => "GET",
+      "REMOTE_ADDR" => "127.0.0.1",
       "SCRIPT_NAME" => "",
       "SERVER_NAME" => "localhost",
       "SERVER_PORT" => "6467",
       "rack.errors" => nil,
       "rack.input" => nil,

Vulnerable code: // clearly is not consisten with RFC7230 so stick with the RFC.
    "Accept-Language",
    "Archived-At",
    "Comments",
    "Content-Base",
    "Content-Disposition",
    "Content-ID",
    "Content-Language",
    "Content-Location",
    "Content-MD5",
    "Content-Transfer-Encoding",
    "Content-Type",
    "Date",
    "Date",
    "EDIINT-Features",
    "Expires",
    "Expires",
    "From",
    "From",
    "Jabber-ID",
    "Keywords",
    "MIME-Version",
    "Message-ID",
    "Message-ID",
    "Organization",
    "PICS-Label",
    "References",
    "Reply-To",
    "Subject",
    "Supersedes",
    "User-Agent",
    "X-Archived-At",
    
    
    
	rb_hash_aset(hh, content_type_val, rb_str_new(val, vlen));
	rb_hash_aset(hh, content_length_val, rb_str_new(val, vlen));
	rb_hash_aset(hh, rb_str_new(hkey, klen + 5), sval);
	rb_hash_aset(env, server_name_val, req_server_name(req));
		    agoo_log_cat(&agoo_con_cat, "Server with pid %d accepted connection %llu on %s [%d]",
				 getpid(), (unsigned long long)cnt, b->id, con->sock);
  VERSION = '2.13.0'
  
    
  
    
Fixed code: ## [2.14.0] - 2020-11-07

### Added

- REMOTE_ADDR element added to requests/env argument to `call()`.

- Added check for multiple Content-Length headers.

- Multiple occurrances of a header are now passed to the Rack `call()` method as an array.

	// It would be better to get this information in server.c after
	// accept() but that does not work on macOS so instead a call to
	// getpeername() is used instead.
	struct sockaddr_storage	addr;
	socklen_t		len = sizeof(addr);

	getpeername(sock, (struct sockaddr*)&addr, &len);
	if (addr.ss_family == AF_INET) {
	    struct sockaddr_in	*s = (struct sockaddr_in*)&addr;

	    inet_ntop(AF_INET, &s->sin_addr, c->remote, sizeof(c->remote));
	} else {
	    struct sockaddr_in6	*s = (struct sockaddr_in6*)&addr;

	    inet_ntop(AF_INET6, &s->sin6_addr, c->remote, sizeof(c->remote));
	}
    memcpy(c->req->remote, c->remote, sizeof(c->remote));
#include <arpa/inet.h>
    char			remote[INET6_ADDRSTRLEN];
// clearly is not consistent with RFC7230 so stick with the RFC.
    "DNT",
    "Front-End-Https",
    "Proxy-Connection",
    "Save-Data",
    "Upgrade-Insecure-Requests",
    "X-ATT-DeviceId",
    "X-Correlation-ID",
    "X-Csrf-Token",
    "X-Forwarded-For",
    "X-Forwarded-Host",
    "X-Forwarded-Proto",
    "X-Http-Method-Override",
    "X-Request-ID",
    "X-Requested-With",
    "X-UIDH",
    "X-Wap-Profile",



#include <arpa/inet.h>
    char			remote[INET6_ADDRSTRLEN];
static VALUE	remote_addr_val = Qundef;
static VALUE
req_remote_addr(agooReq r) {

    if (NULL == r) {
	rb_raise(rb_eArgError, "Request is no longer valid.");
    }
    return rb_str_new(r->remote, strlen(r->remote));
}

/* Document-method: remote_addr
 *
 * call-seq: remote_addr()
 *
 * Returns the remote address.
 */
static VALUE
remote_addr(VALUE self) {
    return req_remote_addr((agooReq)DATA_PTR(self));
}


    VALUE	v;

	if (Qnil == (v = rb_hash_lookup2(hh, content_type_val, Qnil))) {
	    rb_hash_aset(hh, content_type_val, rb_str_new(val, vlen));
	} else {
	    volatile VALUE	a = rb_ary_new();

	    rb_ary_push(a, v);
	    rb_ary_push(a, rb_str_new(val, vlen));
	    rb_hash_aset(hh, content_type_val, a);
	}
	if (Qnil == (v = rb_hash_lookup2(hh, content_length_val, Qnil))) {
	    rb_hash_aset(hh, content_length_val, rb_str_new(val, vlen));
	} else {
	    rb_raise(rb_eArgError, "Multiple Content-Length headers.");
	}
	volatile VALUE	kval;
	kval = rb_str_new(hkey, klen + 5);
	if (Qnil == (v = rb_hash_lookup2(hh, kval, Qnil))) {
	    rb_hash_aset(hh, kval, sval);
	} else {
	    volatile VALUE	a = rb_ary_new();

	    rb_ary_push(a, v);
	    rb_ary_push(a, sval);
	    rb_hash_aset(hh, kval, a);
	}
	rb_hash_aset(env, remote_addr_val, req_remote_addr(req));
	rb_hash_aset(env, server_name_val, req_server_name(req));
    rb_define_method(req_class, "remote_addr", remote_addr, 0);
    remote_addr_val = rb_str_new_cstr("REMOTE_ADDR");		rb_gc_register_address(&remote_addr_val);
		    agoo_log_cat(&agoo_con_cat, "Server with pid %d accepted connection %llu on %s [%d] from %s",
				 getpid(), (unsigned long long)cnt, b->id, con->sock, con->remote);
    memcpy(c->req->remote, c->remote, sizeof(c->remote));
  VERSION = '2.14.0'




      "REMOTE_ADDR" => "127.0.0.1",