From 6bac97c07d7f6eb3015a2b5fe2869b0560a9594a Mon Sep 17 00:00:00 2001
From: Todd Short <tshort@akamai.com>
Date: Wed, 1 Apr 2015 11:56:42 -0400
Subject: [PATCH 2/4] Add IPv4/IPv6:port-based client cache

Update client cache to use IPv4/v6 addresses via sockaddr_storage.
Add unit tests for client cache

(cherry picked from commit 5f6500c4da4746e32db601edcfc0dff20d6f1aa3)

Conflicts:
	include/openssl/ssl.h
	ssl/Makefile
	test/Makefile
---
 include/openssl/ssl.h    |  26 ++
 ssl/Makefile             |  28 +-
 ssl/ssl_client_cache.c   | 729 +++++++++++++++++++++++++++++++++++++++++++++++
 ssl/ssl_lib.c            |   7 +
 ssl/ssl_sess.c           |   1 +
 test/Makefile            |  41 ++-
 test/client_cache_test.c | 612 +++++++++++++++++++++++++++++++++++++++
 7 files changed, 1438 insertions(+), 6 deletions(-)
 create mode 100644 ssl/ssl_client_cache.c
 create mode 100644 test/client_cache_test.c

diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 32d2640..7736506 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -1536,6 +1536,7 @@ __owur long SSL_SESSION_get_time(const SSL_SESSION *s);
 __owur long SSL_SESSION_set_time(SSL_SESSION *s, long t);
 __owur long SSL_SESSION_get_timeout(const SSL_SESSION *s);
 __owur long SSL_SESSION_set_timeout(SSL_SESSION *s, long t);
+__owur int SSL_SESSION_set_timeout_update_cache(const SSL *s, long t);
 __owur int SSL_SESSION_has_ticket(const SSL_SESSION *s);
 __owur unsigned long SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *s);
 void SSL_SESSION_get0_ticket(const SSL_SESSION *s, unsigned char **tick,
@@ -2010,6 +2011,31 @@ void SSL_CTX_share_session_cache(SSL_CTX *a, SSL_CTX *b);
 /* The int argument is 1 for read buffers, 0 for write buffers */
 void SSL3_BUFFER_set_mem_functions(void* (*m)(int, size_t), void(*f)(int, size_t, void*));
 
+/* Support for client cache */
+# ifdef OPENSSL_SYS_WINDOWS
+#  include <winsock.h>
+# else
+#  include <sys/socket.h>
+# endif
+
+/* IPv4 legacy functions */
+void SSL_set_remote_addr(SSL *s, unsigned int addr);
+void SSL_set_remote_port(SSL *s, unsigned int port);
+unsigned int SSL_get_remote_addr(const SSL *s);
+unsigned int SSL_get_remote_port(const SSL *s);
+
+/* IPv4/6 versions */
+int SSL_set_remote_addr_ex(SSL *s, struct sockaddr_storage* addr);
+int SSL_get_remote_addr_ex(const SSL *s, struct sockaddr_storage* addr);
+void SSL_SESSION_copy_remote_addr(SSL_SESSION*, SSL*);
+SSL_SESSION *ssl_sess_dup(SSL_SESSION *sess_from);
+int    SSL_SESSION_client_cmp(const void *data1, const void *data2);
+
+#define MUST_HAVE_APP_DATA 0x1
+#define MUST_COPY_SESSION  0x2
+int     SSL_get_prev_client_session(SSL *s, int flags);
+int     SSL_CTX_set_client_session_cache(SSL_CTX *ctx);
+
 /* BEGIN ERROR CODES */
 /*
  * The following lines are auto generated by the script mkerr.pl. Any changes
diff --git a/ssl/Makefile b/ssl/Makefile
index 00b5cf7..57df6ad 100644
--- a/ssl/Makefile
+++ b/ssl/Makefile
@@ -26,7 +26,8 @@ LIBSRC=	\
 	ssl_ciph.c ssl_stat.c ssl_rsa.c \
 	ssl_asn1.c ssl_txt.c ssl_algs.c ssl_conf.c ssl_bucket.c \
 	bio_ssl.c ssl_err.c t1_reneg.c tls_srp.c t1_trce.c ssl_utst.c \
-	record/ssl3_buffer.c record/ssl3_record.c record/dtls1_bitmap.c
+	record/ssl3_buffer.c record/ssl3_record.c record/dtls1_bitmap.c \
+	ssl_client_cache.c
 LIBOBJ= \
 	s3_srvr.o  s3_clnt.o  s3_lib.o  s3_enc.o record/rec_layer_s3.o \
 	s3_both.o s3_cbc.o s3_msg.o \
@@ -37,7 +38,8 @@ LIBOBJ= \
 	ssl_ciph.o ssl_stat.o ssl_rsa.o \
 	ssl_asn1.o ssl_txt.o ssl_algs.o ssl_conf.o ssl_bucket.o \
 	bio_ssl.o ssl_err.o t1_reneg.o tls_srp.o t1_trce.o ssl_utst.o \
-	record/ssl3_buffer.o record/ssl3_record.o record/dtls1_bitmap.o
+	record/ssl3_buffer.o record/ssl3_record.o record/dtls1_bitmap.o \
+	ssl_client_cache.o
 
 SRC= $(LIBSRC)
 
@@ -587,6 +589,28 @@ ssl_ciph.o: ../include/openssl/ssl2.h ../include/openssl/ssl3.h
 ssl_ciph.o: ../include/openssl/stack.h ../include/openssl/symhacks.h
 ssl_ciph.o: ../include/openssl/tls1.h ../include/openssl/x509.h
 ssl_ciph.o: ../include/openssl/x509_vfy.h record/record.h ssl_ciph.c ssl_locl.h
+ssl_client_cache.o: ../e_os.h ../include/openssl/asn1.h
+ssl_client_cache.o: ../include/openssl/bio.h ../include/openssl/buffer.h
+ssl_client_cache.o: ../include/openssl/comp.h ../include/openssl/crypto.h
+ssl_client_cache.o: ../include/openssl/dsa.h ../include/openssl/dtls1.h
+ssl_client_cache.o: ../include/openssl/e_os2.h ../include/openssl/ec.h
+ssl_client_cache.o: ../include/openssl/ecdh.h ../include/openssl/ecdsa.h
+ssl_client_cache.o: ../include/openssl/engine.h ../include/openssl/err.h
+ssl_client_cache.o: ../include/openssl/evp.h ../include/openssl/hmac.h
+ssl_client_cache.o: ../include/openssl/lhash.h ../include/openssl/obj_mac.h
+ssl_client_cache.o: ../include/openssl/objects.h
+ssl_client_cache.o: ../include/openssl/opensslconf.h
+ssl_client_cache.o: ../include/openssl/opensslv.h ../include/openssl/ossl_typ.h
+ssl_client_cache.o: ../include/openssl/pem.h ../include/openssl/pem2.h
+ssl_client_cache.o: ../include/openssl/pkcs7.h ../include/openssl/pqueue.h
+ssl_client_cache.o: ../include/openssl/rand.h ../include/openssl/rsa.h
+ssl_client_cache.o: ../include/openssl/safestack.h ../include/openssl/sha.h
+ssl_client_cache.o: ../include/openssl/srtp.h ../include/openssl/ssl.h
+ssl_client_cache.o: ../include/openssl/ssl2.h ../include/openssl/ssl3.h
+ssl_client_cache.o: ../include/openssl/stack.h ../include/openssl/symhacks.h
+ssl_client_cache.o: ../include/openssl/tls1.h ../include/openssl/x509.h
+ssl_client_cache.o: ../include/openssl/x509_vfy.h record/record.h
+ssl_client_cache.o: ssl_client_cache.c ssl_locl.h
 ssl_conf.o: ../e_os.h ../include/openssl/asn1.h ../include/openssl/bio.h
 ssl_conf.o: ../include/openssl/buffer.h ../include/openssl/comp.h
 ssl_conf.o: ../include/openssl/conf.h ../include/openssl/crypto.h
diff --git a/ssl/ssl_client_cache.c b/ssl/ssl_client_cache.c
new file mode 100644
index 0000000..5d91183
--- /dev/null
+++ b/ssl/ssl_client_cache.c
@@ -0,0 +1,729 @@
+/**
+ * @file   ssl/ssl_client_cache.c
+ * @brief  This file contains client-cache-specific changes to OpenSSL
+ *
+ * Copyright (C) 2015 Akamai Technologies
+ * All rights reserved.
+ *
+ * This file includes Akamai-specific changes to OpenSSL. These
+ * changes were originally spread in various OpenSSL files, such
+ * as ssl_lib.c and ssl_sess.c.
+ *
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <openssl/lhash.h>
+#include <openssl/rand.h>
+#ifndef OPENSSL_NO_ENGINE
+#include <openssl/engine.h>
+#endif
+#include "ssl_locl.h"
+#ifndef OPENSSL_SYS_WINDOWS
+#include <netinet/in.h>
+#endif
+
+static volatile int SSL_SESSION_SOCKADDR_IDX = -1; /**< EX_DATA index for SSL_SESSION sockaddr data */
+static volatile int SSL_SOCKADDR_IDX = -1;         /**< EX_DATA index for SSL sockaddr data */
+
+/**
+ * @brief Allocates sockaddr for EX_DATA
+ * This function is used to add sockaddr data to be put into an OpenSSL data-structure's EX_DATA
+ * It is assigned via CRYPTO_get_ex_new_index.
+ *
+ * @see CRYPTO_get_ex_new_index
+ *
+ * @param @c parent The data structure the sockaddr is for, it is generic so it can't be used to add the ex_data
+ * @param @c ptr The existing value of the sockaddr (invariably NULL)
+ * @param @c ad The EX_DATA structure for adding the sockaddr
+ * @param @c idx The index used for adding sockaddr to @c ad, passed to CRYPTO_get_ex_new_index
+ * @param @c argl @c long value passed to CRYPTO_get_ex_new_index
+ * @param @c argp pointer value passed to CRYPTO_get_ex_new_index
+ * @return 1 on success, 0 on failure
+ */
+int ssl_sockaddr_new(void* parent, void* ptr, CRYPTO_EX_DATA* ad,
+                     int idx, long argl, void* argp)
+{
+    struct sockaddr_storage* saddr = OPENSSL_malloc(sizeof(*saddr));
+    if (saddr == NULL)
+        return 0;
+    memset(saddr, 0, sizeof(*saddr));
+    return CRYPTO_set_ex_data(ad, idx, saddr);
+}
+
+/**
+ * @brief Frees sockaddr from EX_DATA
+ * This function is used to free sockaddr data that is in an OpenSSL data-structure's EX_DATA
+ * It is assigned via CRYPTO_get_ex_new_index.
+ *
+ * @see CRYPTO_get_ex_new_index
+ *
+ * @param @c parent The data structure the sockaddr is for, it is generic so it can't be used to add the ex_data
+ * @param @c ptr The existing value of the sockaddr (to be freed)
+ * @param @c ad The EX_DATA structure for adding the sockaddr
+ * @param @c idx The index used for adding sockaddr to @c ad, passed to CRYPTO_get_ex_new_index
+ * @param @c argl @c long value passed to CRYPTO_get_ex_new_index
+ * @param @c argp pointer value passed to CRYPTO_get_ex_new_index
+ * @return @c void
+ */
+void ssl_sockaddr_free(void* parent, void* ptr, CRYPTO_EX_DATA* ad,
+                       int idx, long arlg, void* argp)
+{
+    if (ptr)
+        OPENSSL_free(ptr);
+    CRYPTO_set_ex_data(ad, idx, NULL);
+}
+
+/**
+ * @brief Duplicates sockaddr from EX_DATA
+ * This function is used to duplicate data that is in an OpenSSL data-structure's EX_DATA
+ * It is assigned via CRYPTO_get_ex_new_index. This function passes in the address of value
+ * of the original EX_DATA (be it a pointer or integral type). The caller will take the value
+ * from the @p from EX_DATA and assign it to the @p to EX_DATA. For integral types, nothing
+ * really needs to be done. For pointers, we can't have two EX_DATA's pointing to the same
+ * memory location. So, we need to get the new data, copy from the original data (@p from_d),
+ * then assign the new data's address into @p from_d so that value is copied into the
+ * EX_DATA of @p to. It's a little convoluted, but it's how OpenSSL works.
+ *
+ * @see CRYPTO_get_ex_new_index
+ *
+ * @param @c to The EX_DATA structure that has the original data
+ * @param @c from The EX_DATA structure that has the new ata
+ * @param @c from_d The address of the original data, this is an input/output parameter
+ * @param @c idx The index used for adding sockaddr to @c ad, passed to CRYPTO_get_ex_new_index
+ * @param @c argl @c long value passed to CRYPTO_get_ex_new_index
+ * @param @c argp pointer value passed to CRYPTO_get_ex_new_index
+ * @return 1 on success, 0 on failure
+ */
+int ssl_sockaddr_dup(CRYPTO_EX_DATA* to, CRYPTO_EX_DATA* from, void* from_d,
+                     int idx, long arlg, void* argp)
+{
+    /**
+     * from_d is actually the address of the pointer put into the ex_data,
+     * we want a different pointer put into the destination
+     **/
+    struct sockaddr_storage** orig = (struct sockaddr_storage**)from_d;
+    struct sockaddr_storage* new = CRYPTO_get_ex_data(to, idx);
+    if (orig == NULL)
+        return 0;
+    if (*orig == NULL) {
+        *orig = new;
+        return 1;
+    }
+    if (new == NULL)
+        return 0;
+    memcpy(new, *orig, sizeof(*new));
+    *orig = new;
+    return 1;
+}
+
+/**
+ * @brief Legacy IPv4 function to assign an IPv4 address to an SSL structure
+ * This function saves the given @p addr into the OpenSSL SSL @p s.
+ * The byte-order doesn't matter; the get routines will return the value as
+ * it was originally given. It is up to the caller to keep the byte-order
+ * consistent.
+ *
+ * @param @c s The SSL structure to assign the IPv4 address
+ * @param @c addr The IPv4 to assign, byte-order doesn't matter
+ * @return @c void
+ */
+void SSL_set_remote_addr(SSL *s, unsigned int addr)
+{
+    struct sockaddr_storage* sstorage = SSL_get_ex_data(s, SSL_SOCKADDR_IDX);
+    if (sstorage != NULL) {
+        struct sockaddr_in* sin = (struct sockaddr_in*)sstorage;
+        sin->sin_family = AF_INET;
+        sin->sin_addr.s_addr = addr;
+    }
+}
+
+/**
+ * @brief Legacy IPv4 function to assign a port to an SSL structure
+ * This function saves the given @p port into the OpenSSL SSL @p s.
+ * The byte-order doesn't matter; the get routines will return the value as
+ * it was originally given. It is up to the caller to keep the byte-order
+ * consistent. Only the lower 16-bits are significant.
+ *
+ * @param @c s The SSL structure to assign the port
+ * @param @c port The port, byte-order doesn't matter, 16-bits are significant
+ * @return @c void
+ */
+void SSL_set_remote_port(SSL *s, unsigned int port)
+{
+    struct sockaddr_storage* sstorage = SSL_get_ex_data(s, SSL_SOCKADDR_IDX);
+    if (sstorage != NULL) {
+        if (sstorage->ss_family == AF_INET6) {
+            struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sstorage;
+            sin6->sin6_port = port;
+        } else {
+            struct sockaddr_in* sin = (struct sockaddr_in*)sstorage;
+            sin->sin_family = AF_INET; /* may not be initialized */
+            sin->sin_port = port;
+        }
+    }
+}
+
+/**
+ * @brief Legacy IPv4 function to retreive an IPv4 address from an SSL structure
+ * This function returns the saved IPv4 address from the OpenSSL SSL @p s.
+ * The byte-order doesn't matter; the get routines will return the value as
+ * it was originally given. It is up to the caller to keep the byte-order
+ * consistent.
+ *
+ * @param @c s The SSL structure from which to retrieve the IPv4 address
+ * @return @<unsigned int> IPv4 address
+ */
+unsigned int SSL_get_remote_addr(const SSL *s)
+{
+    struct sockaddr_storage* sstorage = SSL_get_ex_data(s, SSL_SOCKADDR_IDX);
+    if (sstorage != NULL && sstorage->ss_family == AF_INET) {
+        struct sockaddr_in* sin = (struct sockaddr_in*)sstorage;
+        return sin->sin_addr.s_addr;
+    }
+    return 0;
+}
+
+/**
+ * @brief Legacy IPv4 function to retrieve an port from an SSL structure
+ * This function returns the saved port from the OpenSSL SSL @p s.
+ * The byte-order doesn't matter; this routines will return the value as
+ * it was originally set,  is up to the caller to keep the byte-order
+ * consistent. Only the lower 16-bits are significant.
+ *
+ * @param @c s The SSL structure from which to retreive the port
+ * @return @<unsigned int@> 16-bit port value
+ */
+unsigned int SSL_get_remote_port(const SSL *s)
+{
+    struct sockaddr_storage* sstorage = SSL_get_ex_data(s, SSL_SOCKADDR_IDX);
+    if (sstorage != NULL) {
+        if (sstorage->ss_family == AF_INET6) {
+            struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sstorage;
+            return sin6->sin6_port;
+        } else if (sstorage->ss_family == AF_INET) {
+            struct sockaddr_in* sin = (struct sockaddr_in*)sstorage;
+            return sin->sin_port;
+        }
+    }
+    return 0;
+}
+
+/**
+ * @brief IPv4/v6 function to assign an address and port to an SSL structure
+ * This function saves a copy of the given @p addr into the OpenSSL SSL @p s.
+ * The byte-order doesn't matter; the get routines will return the value as
+ * it was originally given. It is up to the caller to keep the byte-order
+ * consistent. The @c ss_family field of @p addr must be assigned to either
+ * AF_INET or AF_INET6. If set to AF_INET, then a @c sockaddr_in structure
+ * must be passed in. If set to AF_INET6, then a @c sockaddr_in6 structure
+ * must be passed in. Both the address and the port are set in the same
+ * function call.
+ *
+ * @param @c s The SSL structure to assign the address/port.
+ * @param @c addr The address/port to be assigned.
+ * @return 0 on success, -1 on failure
+ */
+int SSL_set_remote_addr_ex(SSL *s, struct sockaddr_storage* addr)
+{
+    struct sockaddr_storage* sstorage = SSL_get_ex_data(s, SSL_SOCKADDR_IDX);
+    if (addr != NULL && sstorage != NULL) {
+        if (addr->ss_family == AF_INET6) {
+            memcpy(sstorage, addr, sizeof(struct sockaddr_in6));
+            return 0;
+        } else if (addr->ss_family == AF_INET) {
+            memcpy(sstorage, addr, sizeof(struct sockaddr_in));
+            return 0;
+        }
+    }
+    return -1;
+}
+
+/**
+ * @brief IPv4/v6 function to retrieve an address and port from an SSL structure
+ * This function copies the address/port of the given @p s into the @p addr.
+ * The byte-order doesn't matter; the get routines will return the value as
+ * it was originally given. It is up to the caller to keep the byte-order
+ * consistent. The caller must pass in a @<struct sockaddr_storage@>.
+ * The @c ss_family field of @p addr will be assigned to either AF_INET or
+ * AF_INET6. Both the address and the port are returned in the same function
+ * call.
+ *
+ * @param @c s The SSL structure to assign the address/port.
+ * @param @c addr Output parameter to receive the address/port.
+ * @return 0 on success, -1 on failure
+ */
+int SSL_get_remote_addr_ex(const SSL *s, struct sockaddr_storage* addr)
+{
+    struct sockaddr_storage* sstorage = SSL_get_ex_data(s, SSL_SOCKADDR_IDX);
+    if (addr != NULL && sstorage != NULL) {
+        if (sstorage->ss_family == AF_INET6)
+            memcpy(addr, sstorage, sizeof(struct sockaddr_in6));
+        else if (sstorage->ss_family == AF_INET)
+            memcpy(addr, sstorage, sizeof(struct sockaddr_in));
+        else
+            addr->ss_family = 0;
+        return 0;
+    }
+    return -1;
+}
+
+/**
+ * @brief Copies a sockaddr from an SSL to an SSL_SESSION
+ * This function copies the address/port from an SSL structure into an
+ * SSL_SESSION structure. Called in SSL_get_prev_client_session() and
+ * ssl_get_new_session().
+ *
+ * @see SSL_get_prev_client_session
+ * @see ssl_get_new_session
+ *
+ * @param @c ss Destination SSL_SESSION
+ * @param @c s Source SSL
+ * @return @c void
+ */
+void SSL_SESSION_copy_remote_addr(SSL_SESSION* ss, SSL* s)
+{
+    /* Looks weird, but it's right: grab the desintation, and then
+       copy to the destination. */
+    void* p = SSL_SESSION_get_ex_data(ss, SSL_SESSION_SOCKADDR_IDX);
+    if (p)
+        SSL_get_remote_addr_ex(s, p);
+}
+
+/**
+ * @brief Assigns an SSL_SESSION based on an SSL's client address
+ * This function is designed to find an SSL_SESSION with which to
+ * establish a new connection as a client. It is similar to the
+ * the server-side SSL_get_prev_session() function, but instead
+ * of searching for a session ID, the search is for the server's
+ * address and port.
+ * This function will search for an SSL_SESSION based on SSL @p s
+ * IP address specified via SSL_set_remote_addr_ex. When found,
+ * it assigns the SSL_SESSION to the SSL structure, based on the
+ * passed @p flags. This function does not work until the SSL_CTX
+ * that was used to create the SSL is configured with
+ * SSL_CTX_set_client_session_cache().
+ *
+ * @see SSL_get_prev_session
+ * @see SSL_CTX_set_client_session_cache
+ * @see SSL_set_remote_addr_ex
+ *
+ * @param @c s SSL structure
+ * @param @c flags May include MUST_COPY_SESSION and/or MUST_HAVE_APP_DATA
+ * @return @c int 1 = found, 0 = not found, -1 = error
+ */
+int SSL_get_prev_client_session(SSL *s, int flags)
+{
+    /* This is used only by clients.
+     * It's a replica of ssl_get_prev_session() with some modifications.
+     * Call it after finishing with SSL_new(), and either:
+     ** SSL_set_remote_addr() and SSL_set_remote_port()
+     * OR
+     ** SSL_set_remote_addr_ex()
+     * to attach a previous session (if any) to the SSL structure.
+     * This originally used ssl->ctx, but was changed to ssl->session_ctx
+     */
+
+    SSL_SESSION *ret=NULL, *ss=NULL, data;
+    int fatal = 0;
+
+    /* initialize the EX_DATA */
+    memset(&data, 0, sizeof(SSL_SESSION));
+    CRYPTO_new_ex_data(CRYPTO_EX_INDEX_SSL_SESSION, &data, &data.ex_data);
+    SSL_SESSION_copy_remote_addr(&data, s);
+    data.sid_ctx_length = s->sid_ctx_length;
+
+    if (s->sid_ctx_length)
+        memcpy(data.sid_ctx, s->sid_ctx, s->sid_ctx_length);
+
+    /* we don't support get_session_cb() on the client side because that
+     * takes the session ID rather than the server address & port
+     * + we don't need it
+     */
+
+    CRYPTO_r_lock(CRYPTO_LOCK_SSL_CTX);
+    /* was originally ctx, but should probably be session_ctx */
+    ret=(SSL_SESSION *)lh_retrieve((_LHASH *)s->session_ctx->sessions,(char *)&data);
+    /* clean up the EX_DATA */
+    CRYPTO_free_ex_data(CRYPTO_EX_INDEX_SSL_SESSION, &data, &data.ex_data);
+    if (ret != NULL) {
+        /* don't allow other threads to destroy it */
+        CRYPTO_add(&ret->references,1,CRYPTO_LOCK_SSL_SESSION);
+    }
+    CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
+
+    if (ret == NULL)
+        goto err;
+
+    if ((flags & MUST_HAVE_APP_DATA) && !SSL_SESSION_get_app_data(ret)) {
+        s->session_ctx->stats.sess_miss++;
+        SSL_CTX_remove_session(s->session_ctx,ret);
+        goto err;
+    }
+
+    if ((long)(ret->time+ret->timeout) < (long)time(NULL)) { /* timeout */
+        s->session_ctx->stats.sess_timeout++;
+        /* remove it from the cache */
+        SSL_CTX_remove_session(s->session_ctx,ret);
+        goto err;
+    }
+
+    /*
+     * At this point we are going to use the session from cache.
+     * This function needs to be reentrant, and should prohibit giving out
+     * the same session instance from cache to different threads simultaneously.
+     * Thus, we normally create a copy of the cached session.
+     * Important !!! Mark s->hit field with 1 if cached session is used,
+     * so it is not attempted to be put back in cache.
+     */
+
+    if (flags & MUST_COPY_SESSION) {
+        ss = ssl_sess_dup(ret);
+        if (ss == NULL) {
+            /* we could not duplicate the session we got from cache */
+            SSL_CTX_remove_session(s->session_ctx,ret);
+            fatal = -1;
+            goto err;
+        }
+    } else
+        ss = ret;
+
+    if (! SSL_set_session(s, ss)) {
+        SSL_CTX_remove_session(s->session_ctx,ret);
+        if (flags & MUST_COPY_SESSION)
+	    SSL_SESSION_free(ss);
+        fatal = -1;
+        goto err;
+    }
+
+    s->hit = 1;
+
+    CRYPTO_add(&ss->references,-1,CRYPTO_LOCK_SSL_SESSION); /* SSL_set_session() incremented it again */
+    if (flags & MUST_COPY_SESSION)
+        SSL_SESSION_free(ret); /* This would usually just decrease its ref counter we incremented above. */
+    return(1);
+
+ err:
+    if (ret != NULL)
+        SSL_SESSION_free(ret);
+    return fatal;
+}
+
+/**
+ * @brief Updates the SSL_SESSION timeout of an SSL.
+ * This function will update an SSL_SESSION in the SSL_CTX
+ * cache, based on the SSL_SESSION of an SSL structure.
+ * This function works if the SSL_SESSION actually present
+ * in the cache, or is a copy.This does not update the
+ * last-used time of a session, just the session time-out.
+ *
+ * Note: if the session exists on the SSL, 1 is returned whether
+ * or not the session is updated on the SSL_CTX cache.
+ *
+ * @param @c s SSL structure
+ * @param @c t Timeout period in seconds.
+ * @return @c int 1 = if session exists in SSL, 0 otherwise.
+ */
+int SSL_SESSION_set_timeout_update_cache(const SSL *s, long t)
+{
+  SSL_SESSION *ss, *ret;
+
+  if (s == NULL)
+    return 0;
+
+  ss = s->session;
+
+  if (ss == NULL)
+    return 0;
+
+  ss->timeout = t;
+
+  CRYPTO_r_lock(CRYPTO_LOCK_SSL_CTX);
+  ret =(SSL_SESSION *)lh_retrieve((_LHASH *)s->session_ctx->sessions, ss);
+  if ((ret != NULL) && (ret != ss)) {
+     /* our session is a copy of the one in cache */
+     ret->timeout = t;
+  }
+  CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
+
+  return 1;
+}
+
+/**
+ * @brief Utility to duplicate memory
+ * Given an address and length, this function will allocate
+ * memory and copy @p size bytes from @p src into it.
+ * This is similar to a strdup-type function.
+ *
+ * @param @c src Source to duplicate
+ * @param @c size Number of bytes to duplicate
+ * @return @c void* A pointer to the duplicated memory, NULL on failure
+ */
+void* OPENSSL_memdup(const void* src, size_t size)
+{
+    void* ret = NULL;
+    if (src != NULL && size != 0) {
+        ret = OPENSSL_malloc(size);
+        if (ret)
+            memcpy(ret, src, size);
+    }
+    return ret;
+}
+
+/**
+ * @brief Utility to an SSL_SESSION structure
+ * Given an SSL_SESSION structure, this function copy all
+ * data and duplicate all dynamically allocated fields.
+ *
+ * @param @c sess_from SSL_SESSION to duplicate
+ * @return @c void* A pointer to the duplicated SSL_SESSION, NULL on failure
+ */
+SSL_SESSION *ssl_sess_dup(SSL_SESSION *sess_from)
+{
+    SSL_SESSION *ss;
+
+    if (sess_from == NULL)
+        return NULL;
+
+    ss = (SSL_SESSION *)OPENSSL_malloc(sizeof(SSL_SESSION));
+    if (ss == NULL) {
+        SSLerr(SSL_F_SSL_SESSION_NEW,ERR_R_MALLOC_FAILURE);
+        return NULL;
+    }
+
+    /* copy all the data in one fell swoop, then update appropriately */
+    memcpy(ss, sess_from, sizeof(SSL_SESSION));
+
+    /* Local fields which are specific to that instance */
+    ss->references = 1;
+    ss->prev = NULL;
+    ss->next = NULL;
+    CRYPTO_new_ex_data(CRYPTO_EX_INDEX_SSL_SESSION, ss, &ss->ex_data);
+
+    /*
+     * Copying the fields from existing session.
+     * IMPORTANT !!!
+     * Once per-instance locks are implemented, we should take here
+     * a read lock over the existing session we are copying from, to prohibit it from
+     * changing while we are copying.
+     */
+
+    (void)CRYPTO_dup_ex_data(CRYPTO_EX_INDEX_SSL, &ss->ex_data, &sess_from->ex_data);
+
+#ifndef OPENSSL_NO_PSK
+    if (sess_from->psk_identity_hint != NULL)
+        ss->psk_identity_hint = BUF_strdup(sess_from->psk_identity_hint);
+    if (sess_from->psk_identity != NULL)
+        ss->psk_identity = BUF_strdup(sess_from->psk_identity);
+#endif
+
+    /*
+     * We will share the cert with original session, but bump up the ref counter,
+     * preventing cert destruction. It does not seem like cert structures can be updated after creation.
+     */
+    if (sess_from->sess_cert != NULL)
+        CRYPTO_add(&sess_from->sess_cert->references, 1, CRYPTO_LOCK_SSL_SESS_CERT);
+
+    if (sess_from->peer != NULL)
+        CRYPTO_add(&sess_from->peer->references, 1, CRYPTO_LOCK_X509);
+
+    if (sess_from->ciphers != NULL)
+        ss->ciphers = sk_SSL_CIPHER_dup(sess_from->ciphers);
+
+#ifndef OPENSSL_NO_TLSEXT
+    if (sess_from->tlsext_hostname)
+        ss->tlsext_hostname = BUF_strdup(sess_from->tlsext_hostname);
+
+#ifndef OPENSSL_NO_EC
+    ss->tlsext_ecpointformatlist = OPENSSL_memdup(sess_from->tlsext_ecpointformatlist,
+                                                  sess_from->tlsext_ecpointformatlist_length);
+    ss->tlsext_ellipticcurvelist = OPENSSL_memdup(sess_from->tlsext_ellipticcurvelist,
+                                                  sess_from->tlsext_ellipticcurvelist_length);
+#endif /* OPENSSL_NO_EC */
+    /* RFC4507 info */
+    ss->tlsext_tick = OPENSSL_memdup(sess_from->tlsext_tick,
+                                     sess_from->tlsext_ticklen);
+#endif
+
+#ifndef OPENSSL_NO_SRP
+    if (sess_from->srp_username != NULL)
+        ss->srp_username = BUF_strdup(sess_from->srp_username);
+#endif
+
+    /* End of copied fields */
+
+    return ss;
+
+}
+
+/**
+ * @brief Generate a hash from a client SSL_SESSION
+ * Given an SSL_SESSION structure, this function generates
+ * a hash of the address/port value for an lhash structure.
+ * Used as a hash key generation callback for the client
+ * session cache.
+ *
+ * @see SSL_CTX_set_client_session_cache
+ *
+ * @param @c data SSL_SESSION to hash
+ * @return @<unsigned long@> hash value
+ */
+unsigned long SSL_SESSION_client_hash(const void *data)
+{
+    unsigned long hash = 0;
+    const SSL_SESSION *a = (const SSL_SESSION*)data;
+    struct sockaddr_storage* sstorage = SSL_SESSION_get_ex_data(a, SSL_SESSION_SOCKADDR_IDX);
+
+    /* There's usually just 1 sid_ctx (sometimes 2) so considering only its */
+    /* length (but not its content) should be sufficient here */
+    hash ^= a->sid_ctx_length;
+    if (sstorage != NULL) {
+        hash ^= sstorage->ss_family;
+        if (sstorage->ss_family == AF_INET) {
+            struct sockaddr_in* sin = (struct sockaddr_in*)sstorage;
+            hash ^= sin->sin_addr.s_addr;
+            hash ^= sin->sin_port;
+        } else if (sstorage->ss_family == AF_INET6) {
+            struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sstorage;
+#if defined(OPENSSL_SYS_LINUX) || defined(OPENSSL_SYS_MACOSX)
+# ifdef OPENSSL_SYS_MACOSX
+#  define s6_addr32 __u6_addr.__u6_addr32
+# endif
+            hash ^= sin6->sin6_addr.s6_addr32[0];
+            hash ^= sin6->sin6_addr.s6_addr32[1];
+            hash ^= sin6->sin6_addr.s6_addr32[2];
+            hash ^= sin6->sin6_addr.s6_addr32[3];
+#else
+            /* Windows, (and BSDs?) do not have s6_addr32 */
+            int i;
+            for (i = 0; i < 16; i++) {
+                /* take each byte and shift it over by a bit */
+                hash ^= sin6->sin6_addr.s6_addr[i] << i;
+            }
+#endif
+            hash ^= sin6->sin6_port;
+        }
+    }
+    return hash;
+}
+
+/**
+ * @brief Compare two client SSL_SESSIONs
+ * Given an SSL_SESSION structure, this function compares
+ * the address/port value of two SSL_SESSION structures.
+ * Used as a hash key compatiston callback for the client
+ * session cache.
+ * Does not behave like strcmp/memcmp; only does equality.
+ *
+ * @see SSL_CTX_set_client_session_cache
+ *
+ * @param @c data1 first SSL_SESSION to compare
+ * @param @c data2 second SSL_SESSION to compare
+ * @return @c int 0 if equal, 1 if non-equal
+ */
+int SSL_SESSION_client_cmp(const void *data1, const void *data2)
+{
+    SSL_SESSION *a = (SSL_SESSION*)data1;
+    SSL_SESSION *b = (SSL_SESSION*)data2;
+    struct sockaddr_storage* sstorage_a;
+    struct sockaddr_storage* sstorage_b;
+    if (a == b)
+        return 0; /* same object, so they must be equal */
+    if (a->sid_ctx_length != b->sid_ctx_length)
+        return 1; /* cannot be equal */
+    sstorage_a = (struct sockaddr_storage*)SSL_SESSION_get_ex_data(a, SSL_SESSION_SOCKADDR_IDX);
+    if (sstorage_a == NULL)
+        return 1; /* cannot be equal */
+    sstorage_b = (struct sockaddr_storage*)SSL_SESSION_get_ex_data(b, SSL_SESSION_SOCKADDR_IDX);
+    if (sstorage_b == NULL)
+        return 1; /* cannot be equal */
+    if (sstorage_a->ss_family != sstorage_b->ss_family)
+        return 1; /* cannot be equal */
+    if (sstorage_a->ss_family == AF_INET) {
+        struct sockaddr_in* sin_a = (struct sockaddr_in*)sstorage_a;
+        struct sockaddr_in* sin_b = (struct sockaddr_in*)sstorage_b;
+        if (sin_a->sin_addr.s_addr != sin_b->sin_addr.s_addr)
+            return 1; /* cannot be equal */
+        if (sin_a->sin_port != sin_b->sin_port)
+            return 1; /* cannot be equal */
+    } else if (sstorage_a->ss_family == AF_INET6) {
+        struct sockaddr_in6* sin6_a = (struct sockaddr_in6*)sstorage_a;
+        struct sockaddr_in6* sin6_b = (struct sockaddr_in6*)sstorage_b;
+        if (memcmp(&sin6_a->sin6_addr, &sin6_b->sin6_addr, sizeof(struct in6_addr)))
+            return 1; /* cannot be equal */
+        if (sin6_a->sin6_port != sin6_b->sin6_port)
+            return 1; /* cannot be equal */
+    } else
+        return 1; /* not set cannot be equal */
+
+    if (a->sid_ctx_length > 0 && memcmp(a->sid_ctx, b->sid_ctx, a->sid_ctx_length))
+        return 1; /* they are not equal */
+
+    return 0; /* they are equal */
+}
+
+/**
+ * @brief Update an SSL_CTX structure to cache client sessions
+ * Given an SSL_CTX structure, clear out the cache, and override
+ * the default cache settings to use client-mode caching based
+ * on the IP address/port combination.
+ * Initializes the SSL_SOCKADDR_IDX and SSL_SESSION_SOCKADDR_IDX
+ * as needed.
+ * For debugging/testing purposes, the structure names may be
+ * passed to CRYPTO_get_ex_new_index() to display allocation
+ * and free information.
+ *
+ * @param @c ctx SSL_CTX to update
+ * @return @c int 1 on success, 0 on failure
+ */
+int SSL_CTX_set_client_session_cache(SSL_CTX *ctx)
+{
+    /*
+     * Zero is a valid index value, BUT is used for "app_data",
+     * and it's never initially reserved for that purpose. So,
+     * always make a fake reservation that will eat up 0 (if
+     * something has already reserved 0, this will leave an
+     * unused hole, oh well. Remove the extra new_index()s
+     * when/if this is fixed.
+     */
+    if (SSL_SOCKADDR_IDX == -1) {
+        CRYPTO_w_lock(CRYPTO_LOCK_SSL);
+        if (SSL_SOCKADDR_IDX == -1) {
+            SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+            SSL_SOCKADDR_IDX = SSL_get_ex_new_index(0, NULL,
+                                                    ssl_sockaddr_new,
+                                                    ssl_sockaddr_dup,
+                                                    ssl_sockaddr_free);
+        }
+        CRYPTO_w_unlock(CRYPTO_LOCK_SSL);
+    }
+    if (SSL_SESSION_SOCKADDR_IDX == -1) {
+        CRYPTO_w_lock(CRYPTO_LOCK_SSL_SESSION);
+        if (SSL_SESSION_SOCKADDR_IDX == -1) {
+            SSL_SESSION_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+            SSL_SESSION_SOCKADDR_IDX =
+                SSL_SESSION_get_ex_new_index(0, NULL,
+                                             ssl_sockaddr_new,
+                                             ssl_sockaddr_dup,
+                                             ssl_sockaddr_free);
+        }
+        CRYPTO_w_unlock(CRYPTO_LOCK_SSL_SESSION);
+    }
+    CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
+    if (ctx->sessions)
+        lh_SSL_SESSION_free(ctx->sessions);
+
+    /* Force client-side caching */
+    ctx->session_cache_mode |= SSL_SESS_CACHE_CLIENT;
+    ctx->session_cache_mode &= ~SSL_SESS_CACHE_SERVER;
+
+    if ((ctx->sessions=(LHASH_OF(SSL_SESSION)*)lh_new(SSL_SESSION_client_hash,SSL_SESSION_client_cmp))) {
+        CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
+        return 1;
+    }
+
+    CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
+
+    SSLerr(SSL_F_SSL_CTX_NEW,ERR_R_MALLOC_FAILURE);
+    return(0);
+}
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index efed001..790db75 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -452,6 +452,13 @@ int SSL_has_matching_session_id(const SSL *ssl, const unsigned char *id,
 
     if (id_len > sizeof r.session_id)
         return 0;
+    /*
+     * SSL_SESSION r may be completely uninitialized, if we override
+     * the hashing functions to look at EX_DATA, then the code will
+     * crash without this memeset()
+     */
+    memset(&r, 0, sizeof(r));
+
 
     r.ssl_version = ssl->version;
     r.session_id_length = id_len;
diff --git a/ssl/ssl_sess.c b/ssl/ssl_sess.c
index 092b5e6..dbc3828 100644
--- a/ssl/ssl_sess.c
+++ b/ssl/ssl_sess.c
@@ -398,6 +398,7 @@ int ssl_get_new_session(SSL *s, int session)
         SSL_SESSION_free(ss);
         return 0;
     }
+    SSL_SESSION_copy_remote_addr(ss, s);
     memcpy(ss->sid_ctx, s->sid_ctx, s->sid_ctx_length);
     ss->sid_ctx_length = s->sid_ctx_length;
     s->session = ss;
diff --git a/test/Makefile b/test/Makefile
index 86a251f..2492741 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -67,6 +67,7 @@ SRPTEST=	srptest
 V3NAMETEST=	v3nametest
 HEARTBEATTEST=  heartbeat_test
 CONSTTIMETEST=  constant_time_test
+CLIENTCACHETEST= client_cache_test
 
 TESTS=		alltests
 
@@ -82,7 +83,7 @@ EXE=	$(BNTEST)$(EXE_EXT) $(ECTEST)$(EXE_EXT)  $(ECDSATEST)$(EXE_EXT) $(ECDHTEST)
 	$(EVPTEST)$(EXE_EXT) $(EVPEXTRATEST)$(EXE_EXT) $(IGETEST)$(EXE_EXT) \
 	$(JPAKETEST)$(EXE_EXT) $(SRPTEST)$(EXE_EXT) $(V3NAMETEST)$(EXE_EXT) \
 	$(HEARTBEATTEST)$(EXE_EXT) $(P5_CRPT2_TEST)$(EXE_EXT) \
-	$(CONSTTIMETEST)$(EXE_EXT)
+	$(CONSTTIMETEST)$(EXE_EXT) $(CLIENTCACHETEST)$(EXE_EXT)
 
 # $(METHTEST)$(EXE_EXT)
 
@@ -96,7 +97,7 @@ OBJ=	$(BNTEST).o $(ECTEST).o  $(ECDSATEST).o $(ECDHTEST).o $(IDEATEST).o \
 	$(BFTEST).o  $(SSLTEST).o  $(DSATEST).o  $(EXPTEST).o $(RSATEST).o \
 	$(EVPTEST).o $(EVPEXTRATEST).o $(IGETEST).o $(JPAKETEST).o $(V3NAMETEST).o \
 	$(GOST2814789TEST).o $(HEARTBEATTEST).o $(P5_CRPT2_TEST).o \
-	$(CONSTTIMETEST).o testutil.o
+	$(CONSTTIMETEST).o testutil.o $(CLIENTCACHETEST).o
 
 SRC=	$(BNTEST).c $(ECTEST).c  $(ECDSATEST).c $(ECDHTEST).c $(IDEATEST).c \
 	$(MD2TEST).c  $(MD4TEST).c $(MD5TEST).c \
@@ -107,7 +108,7 @@ SRC=	$(BNTEST).c $(ECTEST).c  $(ECDSATEST).c $(ECDHTEST).c $(IDEATEST).c \
 	$(BFTEST).c  $(SSLTEST).c $(DSATEST).c   $(EXPTEST).c $(RSATEST).c \
 	$(EVPTEST).c $(EVPEXTRATEST).c $(IGETEST).c $(JPAKETEST).c $(V3NAMETEST).c \
 	$(GOST2814789TEST).c $(HEARTBEATTEST).c $(P5_CRPT2_TEST).c \
-	$(CONSTTIMETEST).c testutil.c
+	$(CONSTTIMETEST).c testutil.c $(CLIENTCACHETEST).c
 
 HEADER=	testutil.h
 
@@ -147,7 +148,7 @@ alltests: \
 	test_ss test_ca test_engine test_evp test_evp_extra test_ssl test_tsa \
 	test_ige test_jpake test_srp test_cms test_v3name test_ocsp \
 	test_gost2814789 test_heartbeat test_p5_crpt2 \
-	test_constant_time
+	test_constant_time test_client_cache
 
 test_evp: $(EVPTEST)$(EXE_EXT) evptests.txt
 	@echo $(START) $@
@@ -392,6 +393,10 @@ test_constant_time: $(CONSTTIMETEST)$(EXE_EXT)
 	@echo $(START) $@
 	../util/shlib_wrap.sh ./$(CONSTTIMETEST)
 
+test_client_cache: $(CLIENTCACHETEST)$(EXE_EXT)
+	@echo "Test client caching"
+	../util/shlib_wrap.sh ./$(CLIENTCACHETEST)
+
 update: local_depend
 	@if [ -z "$(THIS)" ]; then $(MAKE) -f $(TOP)/Makefile reflect THIS=$@; fi
 
@@ -576,6 +581,9 @@ $(HEARTBEATTEST)$(EXE_EXT): $(HEARTBEATTEST).o $(DLIBCRYPTO) testutil.o
 $(CONSTTIMETEST)$(EXE_EXT): $(CONSTTIMETEST).o
 	@target=$(CONSTTIMETEST) $(BUILD_CMD)
 
+$(CLIENTCACHETEST)$(EXE_EXT): $(CLIENTCACHETEST).o
+	@target=$(CLIENTCACHETEST) $(BUILD_CMD)
+
 #$(AESTEST).o: $(AESTEST).c
 #	$(CC) -c $(CFLAGS) -DINTERMEDIATE_VALUE_KAT -DTRACE_KAT_MCT $(AESTEST).c
 
@@ -609,6 +617,31 @@ bntest.o: ../include/openssl/symhacks.h ../include/openssl/x509.h
 bntest.o: ../include/openssl/x509_vfy.h bntest.c
 casttest.o: ../e_os.h ../include/openssl/cast.h ../include/openssl/e_os2.h
 casttest.o: ../include/openssl/opensslconf.h casttest.c
+client_cache_test.o: ../e_os.h ../include/openssl/asn1.h
+client_cache_test.o: ../include/openssl/bio.h ../include/openssl/bn.h
+client_cache_test.o: ../include/openssl/buffer.h ../include/openssl/comp.h
+client_cache_test.o: ../include/openssl/conf.h ../include/openssl/crypto.h
+client_cache_test.o: ../include/openssl/dh.h ../include/openssl/dsa.h
+client_cache_test.o: ../include/openssl/dtls1.h ../include/openssl/e_os2.h
+client_cache_test.o: ../include/openssl/ec.h ../include/openssl/ecdh.h
+client_cache_test.o: ../include/openssl/ecdsa.h ../include/openssl/engine.h
+client_cache_test.o: ../include/openssl/err.h ../include/openssl/evp.h
+client_cache_test.o: ../include/openssl/hmac.h ../include/openssl/lhash.h
+client_cache_test.o: ../include/openssl/obj_mac.h ../include/openssl/objects.h
+client_cache_test.o: ../include/openssl/opensslconf.h
+client_cache_test.o: ../include/openssl/opensslv.h
+client_cache_test.o: ../include/openssl/ossl_typ.h ../include/openssl/pem.h
+client_cache_test.o: ../include/openssl/pem2.h ../include/openssl/pkcs7.h
+client_cache_test.o: ../include/openssl/pqueue.h ../include/openssl/rand.h
+client_cache_test.o: ../include/openssl/rsa.h ../include/openssl/safestack.h
+client_cache_test.o: ../include/openssl/sha.h ../include/openssl/srp.h
+client_cache_test.o: ../include/openssl/srtp.h ../include/openssl/ssl.h
+client_cache_test.o: ../include/openssl/ssl2.h ../include/openssl/ssl3.h
+client_cache_test.o: ../include/openssl/stack.h ../include/openssl/symhacks.h
+client_cache_test.o: ../include/openssl/tls1.h ../include/openssl/x509.h
+client_cache_test.o: ../include/openssl/x509_vfy.h ../include/openssl/x509v3.h
+client_cache_test.o: ../ssl/record/record.h ../ssl/ssl_locl.h
+client_cache_test.o: client_cache_test.c
 constant_time_test.o: ../e_os.h ../include/internal/constant_time_locl.h
 constant_time_test.o: ../include/openssl/e_os2.h
 constant_time_test.o: ../include/openssl/opensslconf.h constant_time_test.c
diff --git a/test/client_cache_test.c b/test/client_cache_test.c
new file mode 100644
index 0000000..6913813
--- /dev/null
+++ b/test/client_cache_test.c
@@ -0,0 +1,612 @@
+132/* ssl/client_cache_test.c */
+/*
+ * Copyright (C) 2015 Akamai Technologies
+ * All rights reserved.
+ *
+ * This file contains routines to test routines in ssl_client_cache.c
+ * Specifically:
+ * - Caching of IPv4/IPv6 server addresses for client connections
+ */
+
+#define _BSD_SOURCE 1		/* Or gethostname won't be declared properly
+				   on Linux and GNU platforms. */
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#define USE_SOCKETS
+#include "e_os.h"
+
+#ifdef OPENSSL_SYS_VMS
+#define _XOPEN_SOURCE 500	/* Or isascii won't be declared properly on
+				   VMS (at least with DECompHP C).  */
+#endif
+
+#include <ctype.h>
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include "../ssl/ssl_locl.h"
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#ifndef OPENSSL_NO_ENGINE
+#include <openssl/engine.h>
+#endif
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#ifndef OPENSSL_NO_RSA
+#include <openssl/rsa.h>
+#endif
+#ifndef OPENSSL_NO_DSA
+#include <openssl/dsa.h>
+#endif
+#ifndef OPENSSL_NO_DH
+#include <openssl/dh.h>
+#endif
+#ifndef OPENSSL_NO_SRP
+#include <openssl/srp.h>
+#endif
+#include <openssl/bn.h>
+
+#define _XOPEN_SOURCE_EXTENDED	1 /* Or gethostname won't be declared properly
+				     on Compaq platforms (at least with DEC C).
+				     Do not try to put it earlier, or IPv6 includes
+				     get screwed...
+				  */
+
+#ifdef OPENSSL_SYS_WINDOWS
+#include <winsock.h>
+#else
+#include OPENSSL_UNISTD
+#include <netinet/in.h>
+#endif
+
+
+static int verbose = 0;
+static int debug = 0;
+static BIO* bio_stdout = NULL;
+
+static const char rnd_seed[] = "string to make the random number generator think it has entropy";
+
+
+#ifndef HEXDUMP_COLS
+#define HEXDUMP_COLS 16
+#endif
+
+void hexdump(void *mem, size_t len)
+{
+    unsigned char* ptr = mem;
+    size_t i, j;
+
+    for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
+        /* print offset */
+        if (i % HEXDUMP_COLS == 0)
+            printf("0x%06x: ", (unsigned int)i);
+
+        /* print hex data */
+        if (i < len)
+            printf("%02x ", 0xFF & ptr[i]);
+        else
+            /* end of block, just aligning for ASCII dump */
+            printf("   ");
+
+        /* print ASCII dump */
+        if (i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
+            for (j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
+                if (j >= len) {
+                    /* end of block, not really printing */
+                    printf(" ");
+                } else if(isprint(ptr[j])) {
+                    /* printable char */
+                    printf("%c", 0xFF & ptr[j]);
+                } else {
+                    /* other char */
+                    printf(".");
+                }
+            }
+            printf("\n");
+        }
+    }
+}
+
+static void sv_usage(void)
+{
+    fprintf(stderr,"usage: akamaitest [args ...]\n");
+    fprintf(stderr,"\n");
+    fprintf(stderr," -v            - more output\n");
+    fprintf(stderr," -d            - debug output\n");
+}
+
+static void lock_dbg_cb(int mode, int type, const char *file, int line)
+{
+    static int modes[CRYPTO_NUM_LOCKS]; /* = {0, 0, ... } */
+    const char *errstr = NULL;
+    int rw;
+
+    rw = mode & (CRYPTO_READ|CRYPTO_WRITE);
+    if (!((rw == CRYPTO_READ) || (rw == CRYPTO_WRITE))) {
+        errstr = "invalid mode";
+        goto err;
+    }
+
+    if (type < 0 || type >= CRYPTO_NUM_LOCKS) {
+        errstr = "type out of bounds";
+        goto err;
+    }
+
+    if (mode & CRYPTO_LOCK) {
+        if (modes[type]) {
+            errstr = "already locked";
+            /* must not happen in a single-threaded program
+             * (would deadlock) */
+            goto err;
+        }
+
+        modes[type] = rw;
+    } else if (mode & CRYPTO_UNLOCK) {
+        if (!modes[type]) {
+            errstr = "not locked";
+            goto err;
+        }
+
+        if (modes[type] != rw) {
+            errstr = (rw == CRYPTO_READ) ?
+                "CRYPTO_r_unlock on write lock" :
+                "CRYPTO_w_unlock on read lock";
+        }
+
+        modes[type] = 0;
+    } else {
+        errstr = "invalid mode";
+        goto err;
+    }
+
+ err:
+    if (errstr)
+        printf("openssl (lock_dbg_cb): %s (mode=%d, type=%d) at %s:%d\n",
+                errstr, mode, type, file, line);
+}
+
+static int check_session_count(SSL_CTX* ctx, long expected_count)
+{
+    long sess_count = SSL_CTX_sess_number(ctx);
+    if (sess_count != expected_count) {
+        printf("wrong number of sessions: %ld expected %ld\n",
+                   sess_count, expected_count);
+        return 0;
+    }
+    return 1;
+}
+
+static int search_ip(SSL* ssl, struct sockaddr_storage* sstorage, const char* name, int flags, int expect_found)
+{
+    int err;
+    if (SSL_set_remote_addr_ex(ssl, sstorage) != 0)
+        printf("SSL_get_remote_addr_ex() failed for IPv6\n");
+    SSL_set_session(ssl, NULL);
+    err = SSL_get_prev_client_session(ssl, flags);
+    if (err == -1) {
+        printf("%s fatal\n", name);
+        return 0;
+    }
+    if (err == 1) {
+        if (expect_found) {
+            printf("%s found (OK)\n", name);
+            return 1;
+        }
+        printf("%s found (not OK)\n", name);
+        return 0;
+    }
+    if (err == 0) {
+        if (expect_found) {
+            printf("%s not found (not OK)\n", name);
+            return 0;
+        }
+        printf("%s not found (OK)\n", name);
+        return 1;
+    }
+    printf("%s unknown error\n", name);
+    return 0;
+}
+
+static int check_ip4(SSL* ssl, struct sockaddr_in* sin4, int expect_success)
+{
+    struct sockaddr_storage sstorage;
+    unsigned int ip4_result;
+    unsigned int port_result;
+
+    ip4_result = SSL_get_remote_addr(ssl);
+    if (ip4_result != sin4->sin_addr.s_addr && expect_success) {
+        printf("SSL_get_remote_addr() failure: 0x%X expected 0x%X\n",
+                   ip4_result, sin4->sin_addr.s_addr);
+        return 0;
+    }
+
+    port_result = SSL_get_remote_port(ssl);
+    if (port_result != sin4->sin_port && expect_success) {
+        printf("SSL_get_remote_port() failure: %d expected %d\n",
+                   port_result, sin4->sin_port);
+        return 0;
+    }
+
+    if (SSL_get_remote_addr_ex(ssl, &sstorage) != 0) {
+        printf("SSL_get_remote_addr_ex() returned failure\n");
+        /* can't continue */
+        return 0;
+    }
+    if (sstorage.ss_family != AF_INET && expect_success) {
+        printf("SSL_get_remote_addr_ex() family failure: %d expected %d\n",
+                   sstorage.ss_family, AF_INET);
+        return 0;
+    }
+    ip4_result = ((struct sockaddr_in*)&sstorage)->sin_addr.s_addr;
+    if (ip4_result != sin4->sin_addr.s_addr && expect_success) {
+        printf("SSL_get_remote_addr_ex() address failure: 0x%X expected 0x%X\n",
+                   ip4_result, sin4->sin_addr.s_addr);
+        return 0;
+    }
+    port_result = ((struct sockaddr_in*)&sstorage)->sin_port;
+    if (port_result != sin4->sin_port && expect_success) {
+        printf("SSL_get_remote_addr_ex() port failure: %d expected %d\n",
+                   port_result, sin4->sin_port);
+        return 0;
+    }
+    return 1;
+}
+
+static void print_ip6(struct in6_addr* ip6)
+{
+    int i;
+    for (i = 0; i < sizeof(struct in6_addr); i++) {
+        if (i)
+            printf(":");
+        printf("%2.2X", (unsigned char)ip6->s6_addr[i]);
+    }
+}
+
+static int check_ip6(SSL* ssl, struct sockaddr_in6* sin6, int expect_success)
+{
+    struct sockaddr_storage sstorage;
+    struct in6_addr* ip6_result;
+    unsigned int port_result;
+
+    if (SSL_get_remote_addr_ex(ssl, &sstorage) != 0) {
+        printf("SSL_get_remote_addr_ex() returned failure\n");
+        /* can't continue */
+        return 0;
+    }
+    if (sstorage.ss_family != AF_INET6 && expect_success) {
+        printf("SSL_get_remote_addr_ex() family failure: %d expected %d\n",
+                   sstorage.ss_family, AF_INET6);
+        return 0;
+    }
+    ip6_result = &((struct sockaddr_in6*)&sstorage)->sin6_addr;
+    if (memcmp(ip6_result, &sin6->sin6_addr, sizeof(struct in6_addr)) && expect_success) {
+        printf("SSL_get_remote_addr_ex() address failure: ");
+        print_ip6(ip6_result);
+        printf(" expected ");
+        print_ip6(&sin6->sin6_addr);
+        printf("\n");
+        return 0;
+    }
+    port_result = ((struct sockaddr_in6*)&sstorage)->sin6_port;
+    if (port_result != sin6->sin6_port && expect_success) {
+        printf("SSL_get_remote_addr_ex() port failure: %d expected %d\n",
+                   port_result, sin6->sin6_port);
+        return 0;
+    }
+    return 1;
+}
+
+int add_ip_to_cache(SSL_CTX* ctx, struct sockaddr_storage* sstorage)
+{
+    int ret = 0;
+    int err;
+    SSL* ssl = SSL_new(ctx);
+    if (ssl == NULL) {
+        printf("add_ip_to_cache() failed to allocate SSL\n");
+        return 0;
+    }
+    if (SSL_set_remote_addr_ex(ssl, sstorage) != 0) {
+        printf("SSL_set_remote_addr_ex() returned failure\n");
+        goto end;
+    }
+
+    /* create the session in the SSL - copies SSL's address */
+    if (!ssl_get_new_session(ssl, 1)) {
+        printf("ssl_get_new_session() returned failure\n");
+        goto end;
+    }
+
+    if (ssl->tlsext_ticket_expected) {
+        printf("tlsext_ticket_exptected is true\n");
+        goto end;
+    }
+
+    if (ssl->session == NULL) {
+        printf("no session\n");
+        goto end;
+    }
+
+    if (ssl->session->session_id_length == 0) {
+        printf("no session length\n");
+        goto end;
+    }
+
+    if (ssl->session_ctx != ctx) {
+        printf("ssl->session_ctx=%p != ctx=%p\n", ssl->session_ctx, ctx);
+        goto end;
+    }
+
+    /* ssl_update_cache() not working as expected, use this */
+    SSL_CTX_add_session(ctx, ssl->session);
+
+    SSL_SESSION_set_timeout_update_cache(ssl, 1);
+
+    SSL_set_session(ssl, NULL);
+
+    /* make sure it's there! */
+    if (SSL_set_remote_addr_ex(ssl, sstorage) != 0) {
+        printf("SSL_get_remote_addr_ex() failed\n");
+    }
+    err = SSL_get_prev_client_session(ssl, 0);
+    if (err != 1) {
+        printf("SSL_get_prev_client_session() error: %d\n", err);
+        goto end;
+    }
+
+    ret = 1;
+ end:
+    SSL_free(ssl);
+    return ret;
+}
+
+void random_ip6(struct sockaddr_in6* sin6)
+{
+    RAND_bytes((void*)&sin6->sin6_addr, sizeof(sin6->sin6_addr));
+    RAND_bytes((void*)&sin6->sin6_port, sizeof(sin6->sin6_port));
+    sin6->sin6_family = AF_INET6;
+}
+
+void zero_ip6(struct sockaddr_in6* sin6)
+{
+    memset(sin6, 0, sizeof(struct sockaddr_in6));
+    sin6->sin6_family = AF_INET6;
+}
+
+void random_ip4(struct sockaddr_in* sin4)
+{
+    RAND_bytes((void*)&sin4->sin_addr, sizeof(sin4->sin_addr));
+    RAND_bytes((void*)&sin4->sin_port, sizeof(sin4->sin_port));
+    sin4->sin_family = AF_INET;
+}
+
+void zero_ip4(struct sockaddr_in* sin4)
+{
+    memset(sin4, 0, sizeof(struct sockaddr_in));
+    sin4->sin_family = AF_INET;
+}
+
+int main(int argc, char *argv[])
+{
+    int badop=0;
+    int ret=1;
+    SSL_CTX *ctx = NULL;
+    SSL *ssl = NULL;
+    char *debug_mem;
+    int session_id_context = 0;
+    struct sockaddr_in6 sin6;
+    struct sockaddr_in6 sin6a;
+    struct sockaddr_in  sin4;
+    struct sockaddr_in  sin4a;
+
+    CRYPTO_set_locking_callback(lock_dbg_cb);
+
+    /* enable memory leak checking unless explicitly disabled */
+    debug_mem = getenv("OPENSSL_DEBUG_MEMORY");
+    if (debug_mem && strcmp(debug_mem, "on") == 0) {
+        CRYPTO_malloc_debug_init();
+        CRYPTO_set_mem_debug_options(V_CRYPTO_MDEBUG_ALL);
+    } else {
+        /* OPENSSL_DEBUG_MEMORY=off */
+        CRYPTO_set_mem_debug_functions(0, 0, 0, 0, 0);
+    }
+    CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
+
+    RAND_seed(rnd_seed, sizeof rnd_seed);
+
+    bio_stdout=BIO_new_fp(stdout,BIO_NOCLOSE|BIO_FP_TEXT);
+
+    argc--;
+    argv++;
+
+    while (argc >= 1) {
+        if (strcmp(*argv, "-v") == 0)
+            verbose = 1;
+        else if (strcmp(*argv, "-d") == 0)
+            debug = 1;
+        else {
+            fprintf(stderr, "unknown option %s\n", *argv);
+            badop = 1;
+            break;
+        }
+        argc--;
+        argv++;
+    }
+    if (badop) {
+        sv_usage();
+        goto end;
+    }
+
+    SSL_library_init();
+    SSL_load_error_strings();
+
+    ctx = SSL_CTX_new(SSLv3_method());
+    if (ctx == NULL) {
+        ERR_print_errors(bio_stdout);
+        goto end;
+    }
+
+    SSL_CTX_set_session_id_context(ctx, (void *)&session_id_context, sizeof session_id_context);
+
+    /* This is where the magic begins */
+    SSL_CTX_set_client_session_cache(ctx);
+
+    ssl = SSL_new(ctx);
+
+    /* We really don't care if the values are host or network in this test,
+       just as long as they match the expected result! */
+
+    printf("Testing get/set interface\n");
+
+    printf("Testing with nothing set\n");
+    random_ip4(&sin4);
+    random_ip6(&sin6);
+
+    if (!check_ip4(ssl, &sin4, 0))
+        goto end;
+
+    if (!check_ip6(ssl, &sin6, 0))
+        goto end;
+
+    printf("Testing legacy API\n");
+    random_ip4(&sin4);
+    zero_ip6(&sin6);
+
+    SSL_set_remote_addr(ssl, sin4.sin_addr.s_addr);
+    SSL_set_remote_port(ssl, sin4.sin_port);
+
+    if (!check_ip4(ssl, &sin4, 1))
+        goto end;
+
+    if (!check_ip6(ssl, &sin6, 0))
+        goto end;
+
+    printf("Testing IPv4 with new API\n");
+    random_ip4(&sin4);
+    zero_ip6(&sin6);
+
+    SSL_set_remote_addr_ex(ssl, (struct sockaddr_storage*)&sin4);
+
+    if (!check_ip4(ssl, &sin4, 1))
+        goto end;
+
+    if (!check_ip6(ssl, &sin6, 0))
+        goto end;
+
+    printf("Testing IPv6 with new API\n");
+    random_ip6(&sin6);
+    zero_ip4(&sin4);
+
+    SSL_set_remote_addr_ex(ssl, (struct sockaddr_storage*)&sin6);
+
+    if (!check_ip4(ssl, &sin4, 0))
+        goto end;
+
+    if (!check_ip6(ssl, &sin6, 1))
+        goto end;
+
+    /* Set the port on an IPv6 address using legacy */
+    SSL_set_remote_port(ssl, sin4.sin_port);
+
+    if (!check_ip6(ssl, &sin6, 0))
+        goto end;
+
+    sin6.sin6_port = sin4.sin_port;
+    if (!check_ip6(ssl, &sin6, 1))
+        goto end;
+
+    printf("Adding entries to cache... ");
+    printf("1");
+    random_ip6(&sin6);
+    if (add_ip_to_cache(ctx, (struct sockaddr_storage*)&sin6) == 0)
+        goto end;
+    printf("2");
+    random_ip4(&sin4);
+    if (add_ip_to_cache(ctx, (struct sockaddr_storage*)&sin4) == 0)
+        goto end;
+    printf("3");
+    random_ip6(&sin6);
+    if (add_ip_to_cache(ctx, (struct sockaddr_storage*)&sin6) == 0)
+        goto end;
+    printf("4");
+    random_ip4(&sin4);
+    if (add_ip_to_cache(ctx, (struct sockaddr_storage*)&sin4) == 0)
+        goto end;
+    printf("\n");
+
+    /* Added four sessions, so this should be four */
+    if (!check_session_count(ctx, 4))
+        goto end;
+
+    /* Check to see if those last two addresses are in the database */
+
+    if (search_ip(ssl, (struct sockaddr_storage*)&sin6, "IPv6", MUST_COPY_SESSION, 1) == 0)
+        goto end;
+
+    if (search_ip(ssl, (struct sockaddr_storage*)&sin4, "IPv4", 0, 1) == 0)
+        goto end;
+
+    /* Try to remove one of them and search for it */
+    SSL_CTX_remove_session(ctx, SSL_get0_session(ssl));
+
+    if (search_ip(ssl, (struct sockaddr_storage*)&sin4, "removed IPv4", 0, 0) == 0)
+        goto end;
+
+    /* Deleted one, there should be three */
+    if (!check_session_count(ctx, 3))
+        goto end;
+
+    /* Try a random IPv6, which should not be found */
+    random_ip6(&sin6a);
+
+    if (search_ip(ssl, (struct sockaddr_storage*)&sin6a, "random IPv6", 0, 0) == 0)
+        goto end;
+
+    /* Try a random IPv4, which should not be found */
+    random_ip4(&sin4a);
+
+    if (search_ip(ssl, (struct sockaddr_storage*)&sin4a, "random IPv4", 0, 0) == 0)
+        goto end;
+
+    /* Try to remove another one of them and search for it */
+    if (search_ip(ssl, (struct sockaddr_storage*)&sin6, "IPv6", 0, 1) == 0)
+        goto end;
+
+    /* Try again with MUST_HAVE_APP_DATA (which it doesn't), it will be
+       found, but then removed because of the lack of app-data */
+    if (search_ip(ssl, (struct sockaddr_storage*)&sin6, "no app-data IPv6", MUST_HAVE_APP_DATA, 0) == 0)
+        goto end;
+
+    if (search_ip(ssl, (struct sockaddr_storage*)&sin6, "removed IPv6", 0, 0) == 0)
+        goto end;
+
+    /* Deleted another one, there should be two */
+    if (!check_session_count(ctx, 2))
+        goto end;
+
+    ret = 0; /* SUCCESS! */
+
+end:
+    SSL_free(ssl);
+    SSL_CTX_free(ctx);
+#ifndef OPENSSL_NO_ENGINE
+    ENGINE_cleanup();
+#endif
+    CRYPTO_cleanup_all_ex_data();
+    ERR_free_strings();
+    ERR_remove_thread_state(NULL);
+    EVP_cleanup();
+    CRYPTO_mem_leaks(bio_stdout);
+    if (bio_stdout != NULL)
+        BIO_free(bio_stdout);
+    EXIT(ret);
+    return ret;
+}
-- 
2.3.2 (Apple Git-55)

