--- /dev/null
+/*
+ * shctx.h - shared context management functions for SSL
+ *
+ * Copyright (C) 2011-2012 EXCELIANCE
+ *
+ * Author: Emeric Brun - emeric@exceliance.fr
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef SHCTX_H
+#define SHCTX_H
+#include <openssl/ssl.h>
+#include <stdint.h>
+
+#ifndef SHSESS_MAX_FOOTER_LEN
+#define SHSESS_MAX_FOOTER_LEN sizeof(uint32_t) \
+ + EVP_MAX_MD_SIZE
+#endif
+
+#ifndef SHSESS_MAX_DATA_LEN
+#define SHSESS_MAX_DATA_LEN 512
+#endif
+
+#ifndef SHCTX_DEFAULT_SIZE
+#define SHCTX_DEFAULT_SIZE 20000
+#endif
+
+#define SHSESS_MAX_ENCODED_LEN SSL_MAX_SSL_SESSION_ID_LENGTH \
+ + SHSESS_MAX_DATA_LEN \
+ + SHSESS_MAX_FOOTER_LEN
+
+
+
+/* Callback called on a new session event:
+ * session contains the sessionid zeros padded to SSL_MAX_SSL_SESSION_ID_LENGTH
+ * followed by ASN1 session encoding.
+ * len is set to SSL_MAX_SSL_SESSION_ID_LENGTH + ASN1 session length
+ * len is always less than SSL_MAX_SSL_SESSION_ID_LENGTH + SHSESS_MAX_DATA_LEN.
+ * Remaining Bytes from len to SHSESS_MAX_ENCODED_LEN can be used to add a footer.
+ * cdate is the creation date timestamp.
+ */
+void shsess_set_new_cbk(void (*func)(unsigned char *session, unsigned int len, long cdate));
+
+/* Add a session into the cache,
+ * session contains the sessionid zeros padded to SSL_MAX_SSL_SESSION_ID_LENGTH
+ * followed by ASN1 session encoding.
+ * len is set to SSL_MAX_SSL_SESSION_ID_LENGTH + ASN1 data length.
+ * if len greater than SHSESS_MAX_ENCODED_LEN, session is not added.
+ * if cdate not 0, on get events session creation date will be reset to cdate */
+void shctx_sess_add(const unsigned char *session, unsigned int session_len, long cdate);
+
+/* Allocate shared memory context.
+ * size is maximum cached sessions.
+ * if set less or equal to 0, SHCTX_DEFAULT_SIZE is used.
+ * Returns: -1 on alloc failure, size if it performs context alloc,
+ * and 0 if cache is already allocated */
+int shared_context_init(int size);
+
+/* Set shared cache callbacks on an ssl context.
+ * Set session cache mode to server and disable openssl internal cache.
+ * Shared context MUST be firstly initialized */
+void shared_context_set_cache(SSL_CTX *ctx);
+
+#endif /* SHCTX_H */
+
--- /dev/null
+/*
+ * shctx.c - shared context management functions for SSL
+ *
+ * Copyright (C) 2011-2012 EXCELIANCE
+ *
+ * Author: Emeric Brun - emeric@exceliance.fr
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <sys/mman.h>
+#ifdef USE_SYSCALL_FUTEX
+#include <unistd.h>
+#include <linux/futex.h>
+#include <sys/syscall.h>
+#else /* USE_SYSCALL_FUTEX */
+#include <pthread.h>
+#endif /* USE_SYSCALL_FUTEX */
+
+#include "ebmbtree.h"
+#include "proto/shctx.h"
+
+struct shared_session {
+ struct ebmb_node key;
+ unsigned char key_data[SSL_MAX_SSL_SESSION_ID_LENGTH];
+ long c_date;
+ int data_len;
+ unsigned char data[SHSESS_MAX_DATA_LEN];
+ struct shared_session *p;
+ struct shared_session *n;
+};
+
+
+struct shared_context {
+#ifdef USE_SYSCALL_FUTEX
+ unsigned int waiters;
+#else /* USE_SYSCALL_FUTEX */
+ pthread_mutex_t mutex;
+#endif
+ struct shared_session active;
+ struct shared_session free;
+};
+
+/* Static shared context */
+static struct shared_context *shctx = NULL;
+
+/* Callbacks */
+static void (*shared_session_new_cbk)(unsigned char *session, unsigned int session_len, long cdate);
+
+
+/* Lock functions */
+#ifdef USE_SYSCALL_FUTEX
+#if defined (__i586__) || defined (__x86_64__)
+static inline unsigned int xchg(unsigned int *ptr, unsigned int x)
+{
+ __asm volatile("lock xchgl %0,%1"
+ : "=r" (x), "+m" (*ptr)
+ : "0" (x)
+ : "memory");
+ return x;
+}
+
+static inline unsigned int cmpxchg(unsigned int *ptr, unsigned int old, unsigned int new)
+{
+ unsigned int ret;
+
+ __asm volatile("lock cmpxchgl %2,%1"
+ : "=a" (ret), "+m" (*ptr)
+ : "r" (new), "0" (old)
+ : "memory");
+ return ret;
+}
+
+static inline unsigned char atomic_dec(unsigned int *ptr)
+{
+ unsigned char ret;
+ __asm volatile("lock decl %0\n"
+ "setne %1\n"
+ : "+m" (*ptr), "=qm" (ret)
+ :
+ : "memory");
+ return ret;
+}
+
+#else /* if no x86_64 or i586 arch: use less optimized gcc >= 4.1 built-ins */
+static inline unsigned int xchg(unsigned int *ptr, unsigned int x)
+{
+ return __sync_lock_test_and_set(ptr, x);
+}
+
+static inline unsigned int cmpxchg(unsigned int *ptr, unsigned int old, unsigned int new)
+{
+ return __sync_val_compare_and_swap(ptr, old, new);
+}
+
+static inline unsigned char atomic_dec(unsigned int *ptr)
+{
+ return __sync_sub_and_fetch(ptr, 1) ? 1 : 0;
+}
+
+#endif
+
+static inline void shared_context_lock(void)
+{
+ unsigned int x;
+
+ x = cmpxchg(&shctx->waiters, 0, 1);
+ if (x) {
+ if (x != 2)
+ x = xchg(&shctx->waiters, 2);
+
+ while (x) {
+ syscall(SYS_futex, &shctx->waiters, FUTEX_WAIT, 2, NULL, 0, 0);
+ x = xchg(&shctx->waiters, 2);
+ }
+ }
+}
+
+static inline void shared_context_unlock(void)
+{
+ if (atomic_dec(&shctx->waiters)) {
+ shctx->waiters = 0;
+ syscall(SYS_futex, &shctx->waiters, FUTEX_WAKE, 1, NULL, 0, 0);
+ }
+}
+
+#else /* USE_SYSCALL_FUTEX */
+
+#define shared_context_lock(v) pthread_mutex_lock(&shctx->mutex)
+#define shared_context_unlock(v) pthread_mutex_unlock(&shctx->mutex)
+
+#endif
+
+/* List Macros */
+
+#define shsess_unset(s) (s)->n->p = (s)->p; \
+ (s)->p->n = (s)->n;
+
+#define shsess_set_free(s) shsess_unset(s) \
+ (s)->p = &shctx->free; \
+ (s)->n = shctx->free.n; \
+ shctx->free.n->p = s; \
+ shctx->free.n = s;
+
+
+#define shsess_set_active(s) shsess_unset(s) \
+ (s)->p = &shctx->active; \
+ (s)->n = shctx->active.n; \
+ shctx->active.n->p = s; \
+ shctx->active.n = s;
+
+
+#define shsess_get_next() (shctx->free.p == shctx->free.n) ? \
+ shctx->active.p : shctx->free.p;
+
+/* Tree Macros */
+
+#define shsess_tree_delete(s) ebmb_delete(&(s)->key);
+
+#define shsess_tree_insert(s) (struct shared_session *)ebmb_insert(&shctx->active.key.node.branches, \
+ &(s)->key, SSL_MAX_SSL_SESSION_ID_LENGTH);
+
+#define shsess_tree_lookup(k) (struct shared_session *)ebmb_lookup(&shctx->active.key.node.branches, \
+ (k), SSL_MAX_SSL_SESSION_ID_LENGTH);
+
+/* Other Macros */
+
+#define shsess_set_key(s,k,l) { memcpy((s)->key_data, (k), (l)); \
+ if ((l) < SSL_MAX_SSL_SESSION_ID_LENGTH) \
+ memset((s)->key_data+(l), 0, SSL_MAX_SSL_SESSION_ID_LENGTH-(l)); };
+
+
+/* SSL context callbacks */
+
+/* SSL callback used on new session creation */
+int shctx_new_cb(SSL *ssl, SSL_SESSION *sess)
+{
+ struct shared_session *shsess;
+ unsigned char *data,*p;
+ unsigned int data_len;
+ unsigned char encsess[SHSESS_MAX_ENCODED_LEN];
+ (void)ssl;
+
+ /* check if session reserved size in aligned buffer is large enougth for the ASN1 encode session */
+ data_len=i2d_SSL_SESSION(sess, NULL);
+ if(data_len > SHSESS_MAX_DATA_LEN)
+ return 0;
+
+ /* process ASN1 session encoding before the lock: lower cost */
+ p = data = encsess+SSL_MAX_SSL_SESSION_ID_LENGTH;
+ i2d_SSL_SESSION(sess, &p);
+
+ shared_context_lock();
+
+ shsess = shsess_get_next();
+
+ shsess_tree_delete(shsess);
+
+ shsess_set_key(shsess, sess->session_id, sess->session_id_length);
+
+ /* it returns the already existing node or current node if none, never returns null */
+ shsess = shsess_tree_insert(shsess);
+
+ /* store ASN1 encoded session into cache */
+ shsess->data_len = data_len;
+ memcpy(shsess->data, data, data_len);
+
+ /* store creation date */
+ shsess->c_date = SSL_SESSION_get_time(sess);
+
+ shsess_set_active(shsess);
+
+ shared_context_unlock();
+
+ if (shared_session_new_cbk) { /* if user level callback is set */
+ /* copy sessionid padded with 0 into the sessionid + data aligned buffer */
+ memcpy(encsess, sess->session_id, sess->session_id_length);
+ if (sess->session_id_length < SSL_MAX_SSL_SESSION_ID_LENGTH)
+ memset(encsess+sess->session_id_length, 0, SSL_MAX_SSL_SESSION_ID_LENGTH-sess->session_id_length);
+
+ shared_session_new_cbk(encsess, SSL_MAX_SSL_SESSION_ID_LENGTH+data_len, SSL_SESSION_get_time(sess));
+ }
+
+ return 0; /* do not increment session reference count */
+}
+
+/* SSL callback used on lookup an existing session cause none found in internal cache */
+SSL_SESSION *shctx_get_cb(SSL *ssl, unsigned char *key, int key_len, int *do_copy)
+{
+ struct shared_session *shsess;
+ unsigned char data[SHSESS_MAX_DATA_LEN], *p;
+ unsigned char tmpkey[SSL_MAX_SSL_SESSION_ID_LENGTH];
+ unsigned int data_len;
+ long cdate;
+ SSL_SESSION *sess;
+ (void)ssl;
+
+ /* allow the session to be freed automatically by openssl */
+ *do_copy = 0;
+
+ /* tree key is zeros padded sessionid */
+ if (key_len < SSL_MAX_SSL_SESSION_ID_LENGTH) {
+ memcpy(tmpkey, key, key_len);
+ memset(tmpkey + key_len, 0, SSL_MAX_SSL_SESSION_ID_LENGTH - key_len);
+ key = tmpkey;
+ }
+
+ /* lock cache */
+ shared_context_lock();
+
+ /* lookup for session */
+ shsess = shsess_tree_lookup(key);
+ if (!shsess) {
+ /* no session found: unlock cache and exit */
+ shared_context_unlock();
+ return NULL;
+ }
+
+ /* backup creation date to reset in session after ASN1 decode */
+ cdate = shsess->c_date;
+
+ /* copy ASN1 session data to decode outside the lock */
+ data_len = shsess->data_len;
+ memcpy(data, shsess->data, shsess->data_len);
+
+ shsess_set_active(shsess);
+
+ shared_context_unlock();
+
+ /* decode ASN1 session */
+ p = data;
+ sess = d2i_SSL_SESSION(NULL, (const unsigned char **)&p, data_len);
+
+ /* reset creation date */
+ if (sess)
+ SSL_SESSION_set_time(sess, cdate);
+
+ return sess;
+}
+
+/* SSL callback used to signal session is no more used in internal cache */
+void shctx_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess)
+{
+ struct shared_session *shsess;
+ unsigned char tmpkey[SSL_MAX_SSL_SESSION_ID_LENGTH];
+ unsigned char *key = sess->session_id;
+ (void)ctx;
+
+ /* tree key is zeros padded sessionid */
+ if (sess->session_id_length < SSL_MAX_SSL_SESSION_ID_LENGTH) {
+ memcpy(tmpkey, sess->session_id, sess->session_id_length);
+ memset(tmpkey+sess->session_id_length, 0, SSL_MAX_SSL_SESSION_ID_LENGTH - sess->session_id_length);
+ key = tmpkey;
+ }
+
+ shared_context_lock();
+
+ /* lookup for session */
+ shsess = shsess_tree_lookup(key);
+ if (shsess) {
+ shsess_set_free(shsess);
+ }
+
+ /* unlock cache */
+ shared_context_unlock();
+}
+
+/* User level function called to add a session to the cache (remote updates) */
+void shctx_sess_add(const unsigned char *encsess, unsigned int len, long cdate)
+{
+ struct shared_session *shsess;
+
+ /* check buffer is at least padded key long + 1 byte
+ and data_len not too long */
+ if ((len <= SSL_MAX_SSL_SESSION_ID_LENGTH)
+ || (len > SHSESS_MAX_DATA_LEN+SSL_MAX_SSL_SESSION_ID_LENGTH))
+ return;
+
+ shared_context_lock();
+
+ shsess = shsess_get_next();
+
+ shsess_tree_delete(shsess);
+
+ shsess_set_key(shsess, encsess, SSL_MAX_SSL_SESSION_ID_LENGTH);
+
+ /* it returns the already existing node or current node if none, never returns null */
+ shsess = shsess_tree_insert(shsess);
+
+ /* store into cache and update earlier on session get events */
+ if (cdate)
+ shsess->c_date = (long)cdate;
+
+ /* copy ASN1 session data into cache */
+ shsess->data_len = len-SSL_MAX_SSL_SESSION_ID_LENGTH;
+ memcpy(shsess->data, encsess+SSL_MAX_SSL_SESSION_ID_LENGTH, shsess->data_len);
+
+ shsess_set_active(shsess);
+
+ shared_context_unlock();
+}
+
+/* Function used to set a callback on new session creation */
+void shsess_set_new_cbk(void (*func)(unsigned char *, unsigned int, long))
+{
+ shared_session_new_cbk = func;
+}
+
+/* Allocate shared memory context.
+ * size is maximum cached sessions.
+ * if set less or equal to 0, SHCTX_DEFAULT_SIZE is used.
+ * Returns: -1 on alloc failure, size if it performs context alloc,
+ * and 0 if cache is already allocated */
+int shared_context_init(int size)
+{
+ int i;
+#ifndef USE_SYSCALL_FUTEX
+ pthread_mutexattr_t attr;
+#endif /* USE_SYSCALL_FUTEX */
+ struct shared_session *prev,*cur;
+
+ if (shctx)
+ return 0;
+
+ if (size<=0)
+ size = SHCTX_DEFAULT_SIZE;
+
+ shctx = (struct shared_context *)mmap(NULL, sizeof(struct shared_context)+(size*sizeof(struct shared_session)),
+ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (!shctx || shctx == MAP_FAILED) {
+ shctx = NULL;
+ return -1;
+ }
+
+#ifdef USE_SYSCALL_FUTEX
+ shctx->waiters = 0;
+#else
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+ pthread_mutex_init(&shctx->mutex, &attr);
+#endif
+ memset(&shctx->active.key, 0, sizeof(struct ebmb_node));
+ memset(&shctx->free.key, 0, sizeof(struct ebmb_node));
+
+ /* No duplicate authorized in tree: */
+ //shctx->active.key.node.branches.b[1] = (void *)1;
+ shctx->active.key.node.branches = EB_ROOT_UNIQUE;
+
+ cur = &shctx->active;
+ cur->n = cur->p = cur;
+
+ cur = &shctx->free;
+ for (i = 0 ; i < size ; i++) {
+ prev = cur;
+ cur = (struct shared_session *)((char *)prev + sizeof(struct shared_session));
+ prev->n = cur;
+ cur->p = prev;
+ }
+ cur->n = &shctx->free;
+ shctx->free.p = cur;
+
+ return size;
+}
+
+
+/* Set session cache mode to server and disable openssl internal cache.
+ * Set shared cache callbacks on an ssl context.
+ * Shared context MUST be firstly initialized */
+void shared_context_set_cache(SSL_CTX *ctx)
+{
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER |
+ SSL_SESS_CACHE_NO_INTERNAL |
+ SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ if (!shctx)
+ return;
+
+ /* Set callbacks */
+ SSL_CTX_sess_set_new_cb(ctx, shctx_new_cb);
+ SSL_CTX_sess_set_get_cb(ctx, shctx_get_cb);
+ SSL_CTX_sess_set_remove_cb(ctx, shctx_remove_cb);
+}