Index: pjproject/trunk/aconfigure
===================================================================
--- pjproject/trunk/aconfigure	(revision 5724)
+++ pjproject/trunk/aconfigure	(revision 5725)
@@ -643,7 +643,10 @@
 ac_no_opencore_amrwb
 ac_no_opencore_amrnb
+libgnutls_present
+gnutls_h_present
 libcrypto_present
 libssl_present
 openssl_h_present
+ac_ssl_backend
 ac_ssl_has_aes_gcm
 ac_no_ssl
@@ -759,5 +762,4 @@
 oldincludedir
 includedir
-runstatedir
 localstatedir
 sharedstatedir
@@ -820,4 +822,5 @@
 with_ipp_arch
 with_ssl
+with_gnutls
 enable_ssl
 with_opencore_amrnb
@@ -884,5 +887,4 @@
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
-runstatedir='${localstatedir}/run'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
@@ -1137,13 +1139,4 @@
     silent=yes ;;
 
-  -runstatedir | --runstatedir | --runstatedi | --runstated \
-  | --runstate | --runstat | --runsta | --runst | --runs \
-  | --run | --ru | --r)
-    ac_prev=runstatedir ;;
-  -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
-  | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
-  | --run=* | --ru=* | --r=*)
-    runstatedir=$ac_optarg ;;
-
   -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
     ac_prev=sbindir ;;
@@ -1283,5 +1276,5 @@
 		datadir sysconfdir sharedstatedir localstatedir includedir \
 		oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
-		libdir localedir mandir runstatedir
+		libdir localedir mandir
 do
   eval ac_val=\$$ac_var
@@ -1436,5 +1429,4 @@
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
-  --runstatedir=DIR       modifiable per-process data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
@@ -1555,5 +1547,9 @@
   --with-ipp-arch=ARCH    Specify the Intel IPP ARCH suffix, e.g. "64" or
                           "em64t. Default is blank for IA32"
-  --with-ssl=DIR          Specify alternate libssl prefix
+  --with-ssl=DIR          Specify alternate SSL library prefix. This option
+                          will try to find OpenSSL first, then if not found,
+                          GnuTLS. To skip OpenSSL finding, use --with-gnutls
+                          option instead.
+  --with-gnutls=DIR       Specify alternate GnuTLS prefix
   --with-opencore-amrnb=DIR
                           This option is obsolete and replaced by
@@ -7860,5 +7856,15 @@
 
 
-if test "x$ac_cross_compile" != "x" -a "x$with_ssl" = "xno"; then
+
+# Check whether --with-gnutls was given.
+if test "${with_gnutls+set}" = set; then :
+  withval=$with_gnutls;
+else
+  with_gnutls=no
+
+fi
+
+
+if test "x$ac_cross_compile" != "x" -a "x$with_ssl" = "xno" -a "x$with_gnutls" = "xno"; then
     enable_ssl=no
 fi
@@ -7866,4 +7872,5 @@
 
 ac_ssl_has_aes_gcm=0
+
 
 # Check whether --enable-ssl was given.
@@ -7878,6 +7885,4 @@
 else
 
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for OpenSSL installations.." >&5
-$as_echo "checking for OpenSSL installations.." >&6; }
                 if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
                     CFLAGS="$CFLAGS -I$with_ssl/include"
@@ -7887,7 +7892,12 @@
                 fi
 
-
-
-		ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
+	        if test "x$with_gnutls" = "xno"; then
+
+		    { $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for OpenSSL installations.." >&5
+$as_echo "checking for OpenSSL installations.." >&6; }
+
+
+
+		    ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default"
 if test "x$ac_cv_header_openssl_ssl_h" = xyes; then :
   openssl_h_present=1
@@ -7895,5 +7905,5 @@
 
 
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ERR_load_BIO_strings in -lcrypto" >&5
+		    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ERR_load_BIO_strings in -lcrypto" >&5
 $as_echo_n "checking for ERR_load_BIO_strings in -lcrypto... " >&6; }
 if ${ac_cv_lib_crypto_ERR_load_BIO_strings+:} false; then :
@@ -7935,5 +7945,5 @@
 fi
 
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_CTX_new in -lssl" >&5
+		    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_CTX_new in -lssl" >&5
 $as_echo_n "checking for SSL_CTX_new in -lssl... " >&6; }
 if ${ac_cv_lib_ssl_SSL_CTX_new+:} false; then :
@@ -7975,5 +7985,5 @@
 fi
 
-		if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then
+		    if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then
 	        	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL library found, SSL support enabled" >&5
 $as_echo "OpenSSL library found, SSL support enabled" >&6; }
@@ -8051,8 +8061,143 @@
 			$as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h
 
-		else
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ** OpenSSL libraries not found, disabling SSL support **" >&5
-$as_echo "** OpenSSL libraries not found, disabling SSL support **" >&6; }
+			$as_echo "#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_OPENSSL" >>confdefs.h
+
+			ac_ssl_backend="openssl"
+		    else
+			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ** OpenSSL libraries not found **" >&5
+$as_echo "** OpenSSL libraries not found **" >&6; }
+		    fi
+
 		fi
+
+    		if test "x$ac_ssl_backend" = "x"; then
+
+		    if test "x$with_gnutls" != "xno" -a "x$with_gnutls" != "x"; then
+        	        CFLAGS="$CFLAGS -I$with_gnutls/include"
+        	        LDFLAGS="$LDFLAGS -L$with_gnutls/lib"
+        	        { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using GnuTLS prefix... $with_gnutls" >&5
+$as_echo "Using GnuTLS prefix... $with_gnutls" >&6; }
+    		    fi
+
+        	    for ac_prog in $host-pkg-config pkg-config "python pkgconfig.py"
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$PKG_CONFIG"; then
+  ac_cv_prog_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_PKG_CONFIG="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+PKG_CONFIG=$ac_cv_prog_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$PKG_CONFIG" && break
+done
+test -n "$PKG_CONFIG" || PKG_CONFIG="none"
+
+        	    { $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for GnuTLS installations.." >&5
+$as_echo "checking for GnuTLS installations.." >&6; }
+
+
+        	    ac_fn_c_check_header_mongrel "$LINENO" "gnutls/gnutls.h" "ac_cv_header_gnutls_gnutls_h" "$ac_includes_default"
+if test "x$ac_cv_header_gnutls_gnutls_h" = xyes; then :
+  gnutls_h_present=1
+fi
+
+
+
+        	    if test "$PKG_CONFIG" != "none"; then
+            	    	if $PKG_CONFIG --exists gnutls; then
+                	    LIBS="$LIBS `$PKG_CONFIG --libs gnutls`"
+                	    libgnutls_present=1
+            		else
+                	    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gnutls_certificate_set_x509_system_trust in -lgnutls" >&5
+$as_echo_n "checking for gnutls_certificate_set_x509_system_trust in -lgnutls... " >&6; }
+if ${ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lgnutls  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char gnutls_certificate_set_x509_system_trust ();
+int
+main ()
+{
+return gnutls_certificate_set_x509_system_trust ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust=yes
+else
+  ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust" >&5
+$as_echo "$ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust" >&6; }
+if test "x$ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust" = xyes; then :
+  libgnutls_present=1 &&
+                              LIBS="$LIBS -lgnutls"
+fi
+
+            		fi
+        	    else
+            		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: *** Warning: neither pkg-config nor python is available, disabling gnutls. ***" >&5
+$as_echo "*** Warning: neither pkg-config nor python is available, disabling gnutls. ***" >&6; }
+        	    fi
+
+        	    if test "x$gnutls_h_present" = "x1" -a "x$libgnutls_present" = "x1"; then
+           		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: GnuTLS library found, SSL support enabled" >&5
+$as_echo "GnuTLS library found, SSL support enabled" >&6; }
+			$as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h
+
+			$as_echo "#define PJ_SSL_SOCK_IMP PJ_SSL_SOCK_IMP_GNUTLS" >>confdefs.h
+
+            		ac_ssl_backend="gnutls"
+        	    else
+            		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ** No GnuTLS libraries found, disabling SSL support **" >&5
+$as_echo "** No GnuTLS libraries found, disabling SSL support **" >&6; }
+        	    fi
+
+        	fi
 
 fi
Index: pjproject/trunk/aconfigure.ac
===================================================================
--- pjproject/trunk/aconfigure.ac	(revision 5724)
+++ pjproject/trunk/aconfigure.ac	(revision 5725)
@@ -1542,11 +1542,21 @@
 AC_ARG_WITH(ssl,
     AS_HELP_STRING([--with-ssl=DIR],
-		   [Specify alternate libssl prefix]),
+		   [Specify alternate SSL library prefix. This option will try
+		    to find OpenSSL first, then if not found, GnuTLS. To skip
+		    OpenSSL finding, use --with-gnutls option instead.]),
     [],
     [with_ssl=no]
     )
 
+dnl # GnuTLS alt prefix
+AC_ARG_WITH(gnutls,
+    AS_HELP_STRING([--with-gnutls=DIR],
+		   [Specify alternate GnuTLS prefix]),
+    [],
+    [with_gnutls=no]
+    )
+
 dnl # Do not use default SSL installation if we are cross-compiling
-if test "x$ac_cross_compile" != "x" -a "x$with_ssl" = "xno"; then
+if test "x$ac_cross_compile" != "x" -a "x$with_ssl" = "xno" -a "x$with_gnutls" = "xno"; then
     enable_ssl=no
 fi
@@ -1555,4 +1565,5 @@
 AC_SUBST(ac_no_ssl)
 AC_SUBST(ac_ssl_has_aes_gcm,0)
+AC_SUBST(ac_ssl_backend)
 AC_ARG_ENABLE(ssl,
 	      AS_HELP_STRING([--disable-ssl],
@@ -1566,5 +1577,4 @@
 	      ],
 	      [
-		AC_MSG_RESULT([checking for OpenSSL installations..])
                 if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
                     CFLAGS="$CFLAGS -I$with_ssl/include"
@@ -1572,11 +1582,15 @@
                     AC_MSG_RESULT([Using SSL prefix... $with_ssl])
                 fi
-		AC_SUBST(openssl_h_present)
-		AC_SUBST(libssl_present)
-		AC_SUBST(libcrypto_present)
-		AC_CHECK_HEADER(openssl/ssl.h,[openssl_h_present=1])
-		AC_CHECK_LIB(crypto,ERR_load_BIO_strings,[libcrypto_present=1 && LIBS="-lcrypto $LIBS"])
-		AC_CHECK_LIB(ssl,SSL_CTX_new,[libssl_present=1 && LIBS="-lssl $LIBS"])
-		if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then
+
+	        if test "x$with_gnutls" = "xno"; then
+
+		    AC_MSG_RESULT([checking for OpenSSL installations..])
+		    AC_SUBST(openssl_h_present)
+		    AC_SUBST(libssl_present)
+		    AC_SUBST(libcrypto_present)
+		    AC_CHECK_HEADER(openssl/ssl.h,[openssl_h_present=1])
+		    AC_CHECK_LIB(crypto,ERR_load_BIO_strings,[libcrypto_present=1 && LIBS="-lcrypto $LIBS"])
+		    AC_CHECK_LIB(ssl,SSL_CTX_new,[libssl_present=1 && LIBS="-lssl $LIBS"])
+		    if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then
 	        	AC_MSG_RESULT([OpenSSL library found, SSL support enabled])
 			
@@ -1599,7 +1613,52 @@
 			#AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1)
 			AC_DEFINE(PJ_HAS_SSL_SOCK, 1)
-		else
-			AC_MSG_RESULT([** OpenSSL libraries not found, disabling SSL support **])
+			AC_DEFINE(PJ_SSL_SOCK_IMP, PJ_SSL_SOCK_IMP_OPENSSL)
+			ac_ssl_backend="openssl"
+		    else
+			AC_MSG_RESULT([** OpenSSL libraries not found **])
+		    fi
+		
 		fi
+		
+    		if test "x$ac_ssl_backend" = "x"; then
+
+		    if test "x$with_gnutls" != "xno" -a "x$with_gnutls" != "x"; then
+        	        CFLAGS="$CFLAGS -I$with_gnutls/include"
+        	        LDFLAGS="$LDFLAGS -L$with_gnutls/lib"
+        	        AC_MSG_RESULT([Using GnuTLS prefix... $with_gnutls])
+    		    fi
+
+        	    AC_CHECK_PROGS(PKG_CONFIG,
+                       $host-pkg-config pkg-config "python pkgconfig.py",
+                       none)
+        	    AC_MSG_RESULT([checking for GnuTLS installations..])
+        	    AC_SUBST(gnutls_h_present)
+        	    AC_SUBST(libgnutls_present)
+        	    AC_CHECK_HEADER(gnutls/gnutls.h, [gnutls_h_present=1])
+
+        	    if test "$PKG_CONFIG" != "none"; then
+            	    	if $PKG_CONFIG --exists gnutls; then
+                	    LIBS="$LIBS `$PKG_CONFIG --libs gnutls`"
+                	    libgnutls_present=1
+            		else
+                	    AC_CHECK_LIB(gnutls,
+                             gnutls_certificate_set_x509_system_trust,
+                             [libgnutls_present=1 &&
+                              LIBS="$LIBS -lgnutls"])
+            		fi
+        	    else
+            		AC_MSG_RESULT([*** Warning: neither pkg-config nor python is available, disabling gnutls. ***])
+        	    fi
+
+        	    if test "x$gnutls_h_present" = "x1" -a "x$libgnutls_present" = "x1"; then
+           		AC_MSG_RESULT([GnuTLS library found, SSL support enabled])
+			AC_DEFINE(PJ_HAS_SSL_SOCK, 1)
+			AC_DEFINE(PJ_SSL_SOCK_IMP, PJ_SSL_SOCK_IMP_GNUTLS)
+            		ac_ssl_backend="gnutls"
+        	    else
+            		AC_MSG_RESULT([** No GnuTLS libraries found, disabling SSL support **])
+        	    fi
+        	
+        	fi
 	      ])
 
Index: pjproject/trunk/pjlib/build/Makefile
===================================================================
--- pjproject/trunk/pjlib/build/Makefile	(revision 5724)
+++ pjproject/trunk/pjlib/build/Makefile	(revision 5725)
@@ -36,5 +36,5 @@
 	os_info.o pool.o pool_buf.o pool_caching.o pool_dbg.o rand.o \
 	rbtree.o sock_common.o sock_qos_common.o \
-	ssl_sock_common.o ssl_sock_ossl.o ssl_sock_dump.o \
+	ssl_sock_common.o ssl_sock_ossl.o ssl_sock_gtls.o ssl_sock_dump.o \
 	string.o timer.o types.o
 export PJLIB_CFLAGS += $(_CFLAGS)
Index: pjproject/trunk/pjlib/include/pj/compat/os_auto.h.in
===================================================================
--- pjproject/trunk/pjlib/include/pj/compat/os_auto.h.in	(revision 5724)
+++ pjproject/trunk/pjlib/include/pj/compat/os_auto.h.in	(revision 5725)
@@ -220,4 +220,7 @@
 #undef PJ_HAS_SSL_SOCK
 #endif
+#ifndef PJ_SSL_SOCK_IMP
+#   define PJ_SSL_SOCK_IMP	PJ_SSL_SOCK_IMP_NONE
+#endif
 
 
Index: pjproject/trunk/pjlib/include/pj/config.h
===================================================================
--- pjproject/trunk/pjlib/include/pj/config.h	(revision 5724)
+++ pjproject/trunk/pjlib/include/pj/config.h	(revision 5725)
@@ -913,6 +913,7 @@
 /**
  * Enable secure socket. For most platforms, this is implemented using
- * OpenSSL, so this will require OpenSSL to be installed. For Symbian
- * platform, this is implemented natively using CSecureSocket.
+ * OpenSSL or GnuTLS, so this will require one of those libraries to
+ * be installed. For Symbian platform, this is implemented natively
+ * using CSecureSocket.
  *
  * Default: 0 (for now)
@@ -920,4 +921,26 @@
 #ifndef PJ_HAS_SSL_SOCK
 #  define PJ_HAS_SSL_SOCK	    0
+#endif
+
+
+/*
+ * Secure socket implementation.
+ * Select one of these implementations in PJ_SSL_SOCK_IMP.
+ */
+#define PJ_SSL_SOCK_IMP_NONE 	    0	/**< Disable SSL socket.    */
+#define PJ_SSL_SOCK_IMP_OPENSSL	    1	/**< Using OpenSSL.	    */
+#define PJ_SSL_SOCK_IMP_GNUTLS      2	/**< Using GnuTLS.	    */
+
+
+/**
+ * Select which SSL socket implementation to use. Currently pjlib supports
+ * PJ_SSL_SOCK_IMP_OPENSSL, which uses OpenSSL, and PJ_SSL_SOCK_IMP_GNUTLS,
+ * which uses GnuTLS. Setting this to PJ_SSL_SOCK_IMP_NONE will disable
+ * secure socket.
+ *
+ * Default is PJ_SSL_SOCK_IMP_NONE
+ */
+#ifndef PJ_SSL_SOCK_IMP
+#   define PJ_SSL_SOCK_IMP		    PJ_SSL_SOCK_IMP_NONE
 #endif
 
Index: pjproject/trunk/pjlib/include/pj/ssl_sock.h
===================================================================
--- pjproject/trunk/pjlib/include/pj/ssl_sock.h	(revision 5724)
+++ pjproject/trunk/pjlib/include/pj/ssl_sock.h	(revision 5725)
@@ -184,4 +184,9 @@
     pj_str_t raw;		    /**< Raw certificate in PEM format, only
 					 available for remote certificate. */
+
+    struct {
+        unsigned    	cnt;        /**< # of entry     */
+        pj_str_t       *cert_raw;
+    } raw_chain;
 
 } pj_ssl_cert_info;
Index: pjproject/trunk/pjlib/src/pj/ssl_sock_common.c
===================================================================
--- pjproject/trunk/pjlib/src/pj/ssl_sock_common.c	(revision 5724)
+++ pjproject/trunk/pjlib/src/pj/ssl_sock_common.c	(revision 5725)
@@ -36,5 +36,10 @@
     param->concurrency = -1;
     param->whole_data = PJ_TRUE;
+#if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_GNUTLS)
+    /* GnuTLS is allowed to send bigger chunks.*/
+    param->send_buffer_size = 65536;
+#else
     param->send_buffer_size = 8192;
+#endif
 #if !defined(PJ_SYMBIAN) || PJ_SYMBIAN==0
     param->read_buffer_size = 1500;
Index: pjproject/trunk/pjlib/src/pj/ssl_sock_gtls.c
===================================================================
--- pjproject/trunk/pjlib/src/pj/ssl_sock_gtls.c	(revision 5725)
+++ pjproject/trunk/pjlib/src/pj/ssl_sock_gtls.c	(revision 5725)
@@ -0,0 +1,2911 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2018-2018 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2014-2017 Savoir-faire Linux.
+ * (https://www.savoirfairelinux.com)
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <pj/ssl_sock.h>
+#include <pj/activesock.h>
+#include <pj/compat/socket.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/list.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/timer.h>
+#include <pj/file_io.h>
+
+#if GNUTLS_VERSION_NUMBER < 0x030306 && !defined(_MSC_VER)
+#   include <dirent.h>
+#endif
+
+#include <errno.h>
+
+/* Only build when PJ_HAS_SSL_SOCK and the implementation is GnuTLS. */
+#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \
+    (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_GNUTLS)
+
+#define THIS_FILE               "ssl_sock_gtls.c"
+
+/* Workaround for ticket #985 */
+#define DELAYED_CLOSE_TIMEOUT   200
+
+/* Maximum ciphers */
+#define MAX_CIPHERS             100
+
+/* Standard trust locations */
+#define TRUST_STORE_FILE1 "/etc/ssl/certs/ca-certificates.crt"
+#define TRUST_STORE_FILE2 "/etc/ssl/certs/ca-bundle.crt"
+
+/* Debugging output level for GnuTLS only */
+#define GNUTLS_LOG_LEVEL 0
+
+/* GnuTLS includes */
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <gnutls/abstract.h>
+
+#ifdef _MSC_VER
+#  pragma comment( lib, "libgnutls")
+#endif
+
+
+/* TLS state enumeration. */
+enum tls_connection_state {
+    TLS_STATE_NULL,
+    TLS_STATE_HANDSHAKING,
+    TLS_STATE_ESTABLISHED
+};
+
+/* Internal timer types. */
+enum timer_id {
+    TIMER_NONE,
+    TIMER_HANDSHAKE_TIMEOUT,
+    TIMER_CLOSE
+};
+
+/* Structure of SSL socket read buffer. */
+typedef struct read_data_t {
+    void *data;
+    pj_size_t len;
+} read_data_t;
+
+/*
+ * Get the offset of pointer to read-buffer of SSL socket from read-buffer
+ * of active socket. Note that both SSL socket and active socket employ
+ * different but correlated read-buffers (as much as async_cnt for each),
+ * and to make it easier/faster to find corresponding SSL socket's read-buffer
+ * from known active socket's read-buffer, the pointer of corresponding
+ * SSL socket's read-buffer is stored right after the end of active socket's
+ * read-buffer.
+ */
+#define OFFSET_OF_READ_DATA_PTR(ssock, asock_rbuf) \
+                                        (read_data_t**) \
+                                        ((pj_int8_t *)(asock_rbuf) + \
+                                        ssock->param.read_buffer_size)
+
+/* Structure of SSL socket write data. */
+typedef struct write_data_t {
+    PJ_DECL_LIST_MEMBER(struct write_data_t);
+    pj_ioqueue_op_key_t  key;
+    pj_size_t            record_len;
+    pj_ioqueue_op_key_t *app_key;
+    pj_size_t            plain_data_len;
+    pj_size_t            data_len;
+    unsigned             flags;
+    union {
+        char             content[1];
+        const char      *ptr;
+    } data;
+} write_data_t;
+
+
+/* Structure of SSL socket write buffer (circular buffer). */
+typedef struct send_buf_t {
+    char                *buf;
+    pj_size_t            max_len;
+    char                *start;
+    pj_size_t            len;
+} send_buf_t;
+
+
+/* Circular buffer object */
+typedef struct circ_buf_t {
+    pj_size_t      cap;    /* maximum number of elements (must be power of 2) */
+    pj_size_t      readp;  /* index of oldest element */
+    pj_size_t      writep; /* index at which to write new element  */
+    pj_size_t      size;   /* number of elements */
+    pj_uint8_t    *buf;    /* data buffer */
+    pj_pool_t     *pool;   /* where new allocations will take place */
+} circ_buf_t;
+
+
+/* Secure socket structure definition. */
+struct pj_ssl_sock_t {
+    pj_pool_t            *pool;
+    pj_ssl_sock_t        *parent;
+    pj_ssl_sock_param     param;
+    pj_ssl_sock_param     newsock_param;
+    pj_ssl_cert_t        *cert;
+
+    pj_ssl_cert_info      local_cert_info;
+    pj_ssl_cert_info      remote_cert_info;
+
+    pj_bool_t             is_server;
+    enum tls_connection_state connection_state;
+    pj_ioqueue_op_key_t   handshake_op_key;
+    pj_timer_entry        timer;
+    pj_status_t           verify_status;
+
+    int                   last_err;
+
+    pj_sock_t             sock;
+    pj_activesock_t      *asock;
+
+    pj_sockaddr           local_addr;
+    pj_sockaddr           rem_addr;
+    int                   addr_len;
+
+    pj_bool_t             read_started;
+    pj_size_t             read_size;
+    pj_uint32_t           read_flags;
+    void                **asock_rbuf;
+    read_data_t          *ssock_rbuf;
+
+    write_data_t          write_pending;       /* list of pending writes */
+    write_data_t          write_pending_empty; /* cache for write_pending */
+    pj_bool_t             flushing_write_pend; /* flag of flushing is ongoing */
+    send_buf_t            send_buf;
+    write_data_t          send_pending; /* list of pending write to network */
+
+    gnutls_session_t      session;
+    gnutls_certificate_credentials_t xcred;
+
+    circ_buf_t            circ_buf_input;
+    pj_lock_t            *circ_buf_input_mutex;
+
+    circ_buf_t            circ_buf_output;
+    pj_lock_t            *circ_buf_output_mutex;
+
+    int                   tls_init_count; /* library initialization counter */
+};
+
+
+/* Certificate/credential structure definition. */
+struct pj_ssl_cert_t {
+    pj_str_t CA_file;
+    pj_str_t CA_path;
+    pj_str_t cert_file;
+    pj_str_t privkey_file;
+    pj_str_t privkey_pass;
+};
+
+/* GnuTLS available ciphers */
+static unsigned tls_available_ciphers;
+
+/* Array of id/names for available ciphers */
+static struct tls_ciphers_t {
+    pj_ssl_cipher id;
+    const char *name;
+} tls_ciphers[MAX_CIPHERS];
+
+/* Last error reported somehow */
+static int tls_last_error;
+
+
+/*
+ *******************************************************************
+ * Circular buffer functions.
+ *******************************************************************
+ */
+
+static pj_status_t circ_init(pj_pool_factory *factory,
+                             circ_buf_t *cb, pj_size_t cap)
+{
+    cb->cap    = cap;
+    cb->readp  = 0;
+    cb->writep = 0;
+    cb->size   = 0;
+
+    /* Initial pool holding the buffer elements */
+    cb->pool = pj_pool_create(factory, "tls-circ%p", cap, cap, NULL);
+    if (!cb->pool)
+        return PJ_ENOMEM;
+
+    /* Allocate circular buffer */
+    cb->buf = pj_pool_alloc(cb->pool, cap);
+    if (!cb->buf) {
+        pj_pool_release(cb->pool);
+        return PJ_ENOMEM;
+    }
+
+    return PJ_SUCCESS;
+}
+
+static void circ_deinit(circ_buf_t *cb)
+{
+    if (cb->pool) {
+        pj_pool_release(cb->pool);
+        cb->pool = NULL;
+    }
+}
+
+static pj_bool_t circ_empty(const circ_buf_t *cb)
+{
+    return cb->size == 0;
+}
+
+static pj_size_t circ_size(const circ_buf_t *cb)
+{
+    return cb->size;
+}
+
+static pj_size_t circ_avail(const circ_buf_t *cb)
+{
+    return cb->cap - cb->size;
+}
+
+static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len)
+{
+    pj_size_t size_after = cb->cap - cb->readp;
+    pj_size_t tbc = PJ_MIN(size_after, len);
+    pj_size_t rem = len - tbc;
+
+    pj_memcpy(dst, cb->buf + cb->readp, tbc);
+    pj_memcpy(dst + tbc, cb->buf, rem);
+
+    cb->readp += len;
+    cb->readp &= (cb->cap - 1);
+
+    cb->size -= len;
+}
+
+static pj_status_t circ_write(circ_buf_t *cb,
+                              const pj_uint8_t *src, pj_size_t len)
+{
+    /* Overflow condition: resize */
+    if (len > circ_avail(cb)) {
+        /* Minimum required capacity */
+        pj_size_t min_cap = len + cb->size;
+
+        /* Next 32-bit power of two */
+        min_cap--;
+        min_cap |= min_cap >> 1;
+        min_cap |= min_cap >> 2;
+        min_cap |= min_cap >> 4;
+        min_cap |= min_cap >> 8;
+        min_cap |= min_cap >> 16;
+        min_cap++;
+
+        /* Create a new pool to hold a bigger buffer, using the same factory */
+        pj_pool_t *pool = pj_pool_create(cb->pool->factory, "tls-circ%p",
+                                         min_cap, min_cap, NULL);
+        if (!pool)
+            return PJ_ENOMEM;
+
+        /* Allocate our new buffer */
+        pj_uint8_t *buf = pj_pool_alloc(pool, min_cap);
+        if (!buf) {
+            pj_pool_release(pool);
+            return PJ_ENOMEM;
+        }
+
+        /* Save old size, which we shall restore after the next read */
+        pj_size_t old_size = cb->size;
+
+        /* Copy old data into beginning of new buffer */
+        circ_read(cb, buf, cb->size);
+
+        /* Restore old size now */
+        cb->size = old_size;
+
+        /* Release the previous pool */
+        pj_pool_release(cb->pool);
+
+        /* Update circular buffer members */
+        cb->pool = pool;
+        cb->buf = buf;
+        cb->readp = 0;
+        cb->writep = cb->size;
+        cb->cap = min_cap;
+    }
+
+    pj_size_t size_after = cb->cap - cb->writep;
+    pj_size_t tbc = PJ_MIN(size_after, len);
+    pj_size_t rem = len - tbc;
+
+    pj_memcpy(cb->buf + cb->writep, src, tbc);
+    pj_memcpy(cb->buf, src + tbc, rem);
+
+    cb->writep += len;
+    cb->writep &= (cb->cap - 1);
+
+    cb->size += len;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ *******************************************************************
+ * Static/internal functions.
+ *******************************************************************
+ */
+
+/* Convert from GnuTLS error to pj_status_t. */
+static pj_status_t tls_status_from_err(pj_ssl_sock_t *ssock, int err)
+{
+    pj_status_t status;
+
+    switch (err) {
+    case GNUTLS_E_SUCCESS:
+        status = PJ_SUCCESS;
+        break;
+    case GNUTLS_E_MEMORY_ERROR:
+        status = PJ_ENOMEM;
+        break;
+    case GNUTLS_E_LARGE_PACKET:
+        status = PJ_ETOOBIG;
+        break;
+    case GNUTLS_E_NO_CERTIFICATE_FOUND:
+        status = PJ_ENOTFOUND;
+        break;
+    case GNUTLS_E_SESSION_EOF:
+        status = PJ_EEOF;
+        break;
+    case GNUTLS_E_HANDSHAKE_TOO_LARGE:
+        status = PJ_ETOOBIG;
+        break;
+    case GNUTLS_E_EXPIRED:
+        status = PJ_EGONE;
+        break;
+    case GNUTLS_E_TIMEDOUT:
+        status = PJ_ETIMEDOUT;
+        break;
+    case GNUTLS_E_PREMATURE_TERMINATION:
+        status = PJ_ECANCELLED;
+        break;
+    case GNUTLS_E_INTERNAL_ERROR:
+    case GNUTLS_E_UNIMPLEMENTED_FEATURE:
+        status = PJ_EBUG;
+        break;
+    case GNUTLS_E_AGAIN:
+    case GNUTLS_E_INTERRUPTED:
+    case GNUTLS_E_REHANDSHAKE:
+        status = PJ_EPENDING;
+        break;
+    case GNUTLS_E_TOO_MANY_EMPTY_PACKETS:
+    case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS:
+    case GNUTLS_E_RECORD_LIMIT_REACHED:
+        status = PJ_ETOOMANY;
+        break;
+    case GNUTLS_E_UNSUPPORTED_VERSION_PACKET:
+    case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM:
+    case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE:
+    case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE:
+    case GNUTLS_E_X509_UNSUPPORTED_EXTENSION:
+    case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION:
+        status = PJ_ENOTSUP;
+        break;
+    case GNUTLS_E_INVALID_SESSION:
+    case GNUTLS_E_INVALID_REQUEST:
+    case GNUTLS_E_INVALID_PASSWORD:
+    case GNUTLS_E_ILLEGAL_PARAMETER:
+    case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION:
+    case GNUTLS_E_UNEXPECTED_PACKET:
+    case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
+    case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET:
+    case GNUTLS_E_UNWANTED_ALGORITHM:
+    case GNUTLS_E_USER_ERROR:
+        status = PJ_EINVAL;
+        break;
+    default:
+        status = PJ_EUNKNOWN;
+        break;
+    }
+
+    /* Not thread safe */
+    tls_last_error = err;
+    if (ssock)
+        ssock->last_err = err;
+    return status;
+}
+
+
+/* Get error string from GnuTLS using tls_last_error */
+static pj_str_t tls_strerror(pj_status_t status,
+                             char *buf, pj_size_t bufsize)
+{
+    pj_str_t errstr;
+    const char *tmp = gnutls_strerror(tls_last_error);
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+    if (tmp) {
+        pj_ansi_strncpy(buf, tmp, bufsize);
+        errstr = pj_str(buf);
+        return errstr;
+    }
+#endif /* PJ_HAS_ERROR_STRING */
+
+    errstr.ptr = buf;
+    errstr.slen = pj_ansi_snprintf(buf, bufsize, "GnuTLS error %d: %s",
+                                   tls_last_error, tmp);
+    if (errstr.slen < 1 || errstr.slen >= (int) bufsize)
+        errstr.slen = bufsize - 1;
+
+    return errstr;
+}
+
+
+/* GnuTLS way of reporting internal operations. */
+static void tls_print_logs(int level, const char* msg)
+{
+    PJ_LOG(3, (THIS_FILE, "GnuTLS [%d]: %s", level, msg));
+}
+
+
+/* Initialize GnuTLS. */
+static pj_status_t tls_init(void)
+{
+    /* Register error subsystem */
+    pj_status_t status = pj_register_strerror(PJ_ERRNO_START_USER +
+                                              PJ_ERRNO_SPACE_SIZE * 6,
+                                              PJ_ERRNO_SPACE_SIZE,
+                                              &tls_strerror);
+    pj_assert(status == PJ_SUCCESS);
+
+    /* Init GnuTLS library */
+    int ret = gnutls_global_init();
+    if (ret < 0)
+        return tls_status_from_err(NULL, ret);
+
+    gnutls_global_set_log_level(GNUTLS_LOG_LEVEL);
+    gnutls_global_set_log_function(tls_print_logs);
+
+    /* Init available ciphers */
+    if (!tls_available_ciphers) {
+        unsigned int i;
+
+        for (i = 0; ; i++) {
+            unsigned char id[2];
+            const char *suite;
+            
+            suite = gnutls_cipher_suite_info(i, (unsigned char *)id,
+                                             NULL, NULL, NULL, NULL);
+            tls_ciphers[i].id = 0;
+            /* usually the array size is bigger than the number of available
+             * ciphers anyway, so by checking here we can exit the loop as soon
+             * as either all ciphers have been added or the array is full */
+            if (suite && i < PJ_ARRAY_SIZE(tls_ciphers)) {
+                tls_ciphers[i].id = (pj_ssl_cipher)
+                    (pj_uint32_t) ((id[0] << 8) | id[1]);
+                tls_ciphers[i].name = suite;
+            } else
+                break;
+        }
+
+        tls_available_ciphers = i;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* Shutdown GnuTLS */
+static void tls_deinit(void)
+{
+    gnutls_global_deinit();
+}
+
+
+/* Callback invoked every time a certificate has to be validated. */
+static int tls_cert_verify_cb(gnutls_session_t session)
+{
+    pj_ssl_sock_t *ssock;
+    unsigned int status;
+    int ret;
+
+    /* Get SSL socket instance */
+    ssock = (pj_ssl_sock_t *)gnutls_session_get_ptr(session);
+    pj_assert(ssock);
+
+    /* Support only x509 format */
+    ret = gnutls_certificate_type_get(session) != GNUTLS_CRT_X509;
+    if (ret < 0) {
+        ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT;
+        return GNUTLS_E_CERTIFICATE_ERROR;
+    }
+
+    /* Store verification status */
+    ret = gnutls_certificate_verify_peers2(session, &status);
+    if (ret < 0) {
+        ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN;
+        return GNUTLS_E_CERTIFICATE_ERROR;
+    }
+    if (ssock->param.verify_peer) {
+    if (status & GNUTLS_CERT_INVALID) {
+        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
+            ssock->verify_status |= PJ_SSL_CERT_EISSUER_NOT_FOUND;
+        else if (status & GNUTLS_CERT_EXPIRED ||
+                 status & GNUTLS_CERT_NOT_ACTIVATED)
+            ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD;
+        else if (status & GNUTLS_CERT_SIGNER_NOT_CA ||
+                 status & GNUTLS_CERT_INSECURE_ALGORITHM)
+            ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED;
+        else if (status & GNUTLS_CERT_UNEXPECTED_OWNER ||
+                 status & GNUTLS_CERT_MISMATCH)
+            ssock->verify_status |= PJ_SSL_CERT_EISSUER_MISMATCH;
+        else if (status & GNUTLS_CERT_REVOKED)
+            ssock->verify_status |= PJ_SSL_CERT_EREVOKED;
+        else
+            ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN;
+
+        return GNUTLS_E_CERTIFICATE_ERROR;
+    }
+
+    /* When verification is not requested just return ok here, however
+     * applications can still get the verification status. */
+        gnutls_x509_crt_t cert;
+        unsigned int cert_list_size;
+        const gnutls_datum_t *cert_list;
+        int ret;
+
+        ret = gnutls_x509_crt_init(&cert);
+        if (ret < 0)
+            goto out;
+
+        cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+        if (cert_list == NULL) {
+            ret = GNUTLS_E_NO_CERTIFICATE_FOUND;
+            goto out;
+        }
+
+        /* TODO: verify whole chain perhaps? */
+        ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
+        if (ret < 0)
+            ret = gnutls_x509_crt_import(cert, &cert_list[0],
+                                         GNUTLS_X509_FMT_PEM);
+        if (ret < 0) {
+            ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT;
+            goto out;
+        }
+        ret = gnutls_x509_crt_check_hostname(cert,
+        				     ssock->param.server_name.ptr);
+        if (ret < 0)
+            goto out;
+
+        gnutls_x509_crt_deinit(cert);
+
+        /* notify GnuTLS to continue handshake normally */
+        return GNUTLS_E_SUCCESS;
+
+out:
+        tls_last_error = ret;
+        ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN;
+        return GNUTLS_E_CERTIFICATE_ERROR;
+    }
+
+    return GNUTLS_E_SUCCESS;
+}
+
+
+/* gnutls_handshake() and gnutls_record_send() will call this function to
+ * send/write (encrypted) data */
+static ssize_t tls_data_push(gnutls_transport_ptr_t ptr,
+                             const void *data, size_t len)
+{
+    pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr;
+
+    pj_lock_acquire(ssock->circ_buf_output_mutex);
+    if (circ_write(&ssock->circ_buf_output, data, len) != PJ_SUCCESS) {
+        pj_lock_release(ssock->circ_buf_output_mutex);
+
+        gnutls_transport_set_errno(ssock->session, ENOMEM);
+        return -1;
+    }
+
+    pj_lock_release(ssock->circ_buf_output_mutex);
+
+    return len;
+}
+
+
+/* gnutls_handshake() and gnutls_record_recv() will call this function to
+ * receive/read (encrypted) data */
+static ssize_t tls_data_pull(gnutls_transport_ptr_t ptr,
+                             void *data, pj_size_t len)
+{
+    pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr;
+
+    pj_lock_acquire(ssock->circ_buf_input_mutex);
+
+    if (circ_empty(&ssock->circ_buf_input)) {
+        pj_lock_release(ssock->circ_buf_input_mutex);
+
+        /* Data buffers not yet filled */
+        gnutls_transport_set_errno(ssock->session, EAGAIN);
+        return -1;
+    }
+
+    pj_size_t circ_buf_size = circ_size(&ssock->circ_buf_input);
+    pj_size_t read_size = PJ_MIN(circ_buf_size, len);
+
+    circ_read(&ssock->circ_buf_input, data, read_size);
+
+    pj_lock_release(ssock->circ_buf_input_mutex);
+
+    return read_size;
+}
+
+
+/* Append a string to the priority string, only once. */
+static pj_status_t tls_str_append_once(pj_str_t *dst, pj_str_t *src)
+{
+    if (pj_strstr(dst, src) == NULL) {
+        /* Check buffer size */
+        if (dst->slen + src->slen + 3 > 1024)
+            return PJ_ETOOMANY;
+
+        pj_strcat2(dst, ":+");
+        pj_strcat(dst, src);
+    }
+    return PJ_SUCCESS;
+}
+
+
+/* Generate priority string with user preference order. */
+static pj_status_t tls_priorities_set(pj_ssl_sock_t *ssock)
+{
+    char buf[1024];
+    char priority_buf[256];
+    pj_str_t cipher_list;
+    pj_str_t compression = pj_str("COMP-NULL");
+    pj_str_t server = pj_str(":%SERVER_PRECEDENCE");
+    int i, j, ret;
+    pj_str_t priority;
+    const char *err;
+
+    pj_strset(&cipher_list, buf, 0);
+    pj_strset(&priority, priority_buf, 0);
+
+    /* For each level, enable only the requested protocol */
+    pj_strcat2(&priority, "NORMAL:");
+    if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_2) {
+        pj_strcat2(&priority, "+VERS-TLS1.2:");
+    }
+    if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1_1) {
+        pj_strcat2(&priority, "+VERS-TLS1.1:");
+    }
+    if (ssock->param.proto & PJ_SSL_SOCK_PROTO_TLS1) {
+        pj_strcat2(&priority, "+VERS-TLS1.0:");
+    }
+    pj_strcat2(&priority, "-VERS-SSL3.0:");
+    pj_strcat2(&priority, "%LATEST_RECORD_VERSION");
+
+    pj_strcat(&cipher_list, &priority);
+    for (i = 0; i < ssock->param.ciphers_num; i++) {
+        for (j = 0; ; j++) {
+            pj_ssl_cipher c;
+            const char *suite;
+            unsigned char id[2];
+            gnutls_protocol_t proto;
+            gnutls_kx_algorithm_t kx;
+            gnutls_mac_algorithm_t mac;
+            gnutls_cipher_algorithm_t algo;
+
+            suite = gnutls_cipher_suite_info(j, (unsigned char *)id,
+                                             &kx, &algo, &mac, &proto);
+            if (!suite)
+                break;
+
+            c = (pj_ssl_cipher) (pj_uint32_t) ((id[0] << 8) | id[1]);
+            if (ssock->param.ciphers[i] == c) {
+                char temp[256];
+                pj_str_t cipher_entry;
+
+                /* Protocol version */
+                pj_strset(&cipher_entry, temp, 0);
+                pj_strcat2(&cipher_entry, "VERS-");
+                pj_strcat2(&cipher_entry, gnutls_protocol_get_name(proto));
+                ret = tls_str_append_once(&cipher_list, &cipher_entry);
+                if (ret != PJ_SUCCESS)
+                    return ret;
+
+                /* Cipher */
+                pj_strset(&cipher_entry, temp, 0);
+                pj_strcat2(&cipher_entry, gnutls_cipher_get_name(algo));
+                ret = tls_str_append_once(&cipher_list, &cipher_entry);
+                if (ret != PJ_SUCCESS)
+                    return ret;
+
+                /* Mac */
+                pj_strset(&cipher_entry, temp, 0);
+                pj_strcat2(&cipher_entry, gnutls_mac_get_name(mac));
+                ret = tls_str_append_once(&cipher_list, &cipher_entry);
+                if (ret != PJ_SUCCESS)
+                    return ret;
+
+                /* Key exchange */
+                pj_strset(&cipher_entry, temp, 0);
+                pj_strcat2(&cipher_entry, gnutls_kx_get_name(kx));
+                ret = tls_str_append_once(&cipher_list, &cipher_entry);
+                if (ret != PJ_SUCCESS)
+                    return ret;
+
+                /* Compression is always disabled */
+                /* Signature is level-default */
+                break;
+            }
+        }
+    }
+
+    /* Disable compression, it's a TLS-only extension after all */
+    tls_str_append_once(&cipher_list, &compression);
+
+    /* Server will be the one deciding which crypto to use */
+    if (ssock->is_server) {
+        if (cipher_list.slen + server.slen + 1 > sizeof(buf))
+            return PJ_ETOOMANY;
+        else
+            pj_strcat(&cipher_list, &server);
+    }
+
+    /* End the string and print it */
+    cipher_list.ptr[cipher_list.slen] = '\0';
+    PJ_LOG(5, (ssock->pool->obj_name, "Priority string: %s", cipher_list.ptr));
+
+    /* Set our priority string */
+    ret = gnutls_priority_set_direct(ssock->session,
+                                        cipher_list.ptr, &err);
+    if (ret < 0) {
+        tls_last_error = GNUTLS_E_INVALID_REQUEST;
+        return PJ_EINVAL;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* Load root CA file or load the installed ones. */
+static pj_status_t tls_trust_set(pj_ssl_sock_t *ssock)
+{
+    int ntrusts = 0;
+    int err;
+
+    err = gnutls_certificate_set_x509_system_trust(ssock->xcred);
+    if (err > 0)
+        ntrusts += err;
+    err = gnutls_certificate_set_x509_trust_file(ssock->xcred,
+                                                 TRUST_STORE_FILE1,
+                                                 GNUTLS_X509_FMT_PEM);
+    if (err > 0)
+        ntrusts += err;
+
+    err = gnutls_certificate_set_x509_trust_file(ssock->xcred,
+                                                 TRUST_STORE_FILE2,
+                                                 GNUTLS_X509_FMT_PEM);
+    if (err > 0)
+        ntrusts += err;
+
+    if (ntrusts > 0)
+        return PJ_SUCCESS;
+    else if (!ntrusts)
+        return PJ_ENOTFOUND;
+    else
+        return PJ_EINVAL;
+}
+
+#if GNUTLS_VERSION_NUMBER < 0x030306
+
+#ifdef _POSIX_PATH_MAX
+#   define GNUTLS_PATH_MAX _POSIX_PATH_MAX
+#else
+#   define GNUTLS_PATH_MAX 256
+#endif
+
+static int gnutls_certificate_set_x509_trust_dir(
+		gnutls_certificate_credentials_t cred,
+		const char *dirname, unsigned type)
+{
+    DIR *dirp;
+    struct dirent *d;
+    int ret;
+    int r = 0;
+    char path[GNUTLS_PATH_MAX];
+#ifndef _WIN32
+    struct dirent e;
+#endif
+
+    dirp = opendir(dirname);
+    if (dirp != NULL) {
+        do {
+#ifdef _WIN32
+            d = readdir(dirp);
+            if (d != NULL) {
+#else
+            ret = readdir_r(dirp, &e, &d);
+            if (ret == 0 && d != NULL
+#ifdef _DIRENT_HAVE_D_TYPE
+                && (d->d_type == DT_REG || d->d_type == DT_LNK ||
+                    d->d_type == DT_UNKNOWN)
+#endif
+            ) {
+#endif
+                snprintf(path, sizeof(path), "%s/%s",
+                     	 dirname, d->d_name);
+
+                ret = gnutls_certificate_set_x509_trust_file(cred, path, type);
+                if (ret >= 0)
+                    r += ret;
+            }
+        } while (d != NULL);
+        closedir(dirp);
+    }
+
+    return r;
+}
+
+#endif
+
+/* Create and initialize new GnuTLS context and instance */
+static pj_status_t tls_open(pj_ssl_sock_t *ssock)
+{
+    pj_ssl_cert_t *cert;
+    pj_status_t status;
+    int ret;
+
+    pj_assert(ssock);
+
+    cert = ssock->cert;
+
+    /* Even if reopening is harmless, having one instance only simplifies
+     * deallocating it later on */
+    if (!ssock->tls_init_count) {
+        ssock->tls_init_count++;
+        ret = tls_init();
+        if (ret < 0)
+            return ret;
+    } else
+        return PJ_SUCCESS;
+
+    /* Start this socket session */
+    ret = gnutls_init(&ssock->session, ssock->is_server ? GNUTLS_SERVER
+                                                        : GNUTLS_CLIENT);
+    if (ret < 0)
+        goto out;
+
+    /* Set the ssock object to be retrieved by transport (send/recv) and by
+     * user data from this session */
+    gnutls_transport_set_ptr(ssock->session,
+                             (gnutls_transport_ptr_t) (uintptr_t) ssock);
+    gnutls_session_set_ptr(ssock->session,
+                           (gnutls_transport_ptr_t) (uintptr_t) ssock);
+
+    /* Initialize input circular buffer */
+    status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, 512);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    /* Initialize output circular buffer */
+    status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, 512);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    /* Set the callback that allows GnuTLS to PUSH and PULL data
+     * TO and FROM the transport layer */
+    gnutls_transport_set_push_function(ssock->session, tls_data_push);
+    gnutls_transport_set_pull_function(ssock->session, tls_data_pull);
+
+    /* Determine which cipher suite to support */
+    status = tls_priorities_set(ssock);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    /* Allocate credentials for handshaking and transmission */
+    ret = gnutls_certificate_allocate_credentials(&ssock->xcred);
+    if (ret < 0)
+        goto out;
+    gnutls_certificate_set_verify_function(ssock->xcred, tls_cert_verify_cb);
+
+    /* Load system trust file(s) */
+    status = tls_trust_set(ssock);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    /* Load user-provided CA, certificate and key if available */
+    if (cert) {
+        /* Load CA if one is specified. */
+        if (cert->CA_file.slen) {
+            ret = gnutls_certificate_set_x509_trust_file(ssock->xcred,
+                                                         cert->CA_file.ptr,
+                                                         GNUTLS_X509_FMT_PEM);
+            if (ret < 0)
+                ret = gnutls_certificate_set_x509_trust_file(
+                		ssock->xcred,
+                                cert->CA_file.ptr,
+                                GNUTLS_X509_FMT_DER);
+            if (ret < 0)
+                goto out;
+        }
+        if (cert->CA_path.slen) {
+            ret = gnutls_certificate_set_x509_trust_dir(ssock->xcred,
+                                                         cert->CA_path.ptr,
+                                                         GNUTLS_X509_FMT_PEM);
+            if (ret < 0)
+                ret = gnutls_certificate_set_x509_trust_dir(
+                		ssock->xcred,
+                                cert->CA_path.ptr,
+                                GNUTLS_X509_FMT_DER);
+            if (ret < 0)
+                goto out;
+        }
+
+        /* Load certificate, key and pass if one is specified */
+        if (cert->cert_file.slen && cert->privkey_file.slen) {
+            const char *prikey_file = cert->privkey_file.ptr;
+            const char *prikey_pass = cert->privkey_pass.slen
+                                    ? cert->privkey_pass.ptr
+                                    : NULL;
+            ret = gnutls_certificate_set_x509_key_file2(ssock->xcred,
+                                                        cert->cert_file.ptr,
+                                                        prikey_file,
+                                                        GNUTLS_X509_FMT_PEM,
+                                                        prikey_pass,
+                                                        0);
+            if (ret != GNUTLS_E_SUCCESS)
+                ret = gnutls_certificate_set_x509_key_file2(ssock->xcred,
+                                                            cert->cert_file.ptr,
+                                                            prikey_file,
+                                                            GNUTLS_X509_FMT_DER,
+                                                            prikey_pass,
+                                                            0);
+            if (ret < 0)
+                goto out;
+        }
+    }
+
+    /* Require client certificate if asked */
+    if (ssock->is_server && ssock->param.require_client_cert)
+        gnutls_certificate_server_set_request(ssock->session,
+                                              GNUTLS_CERT_REQUIRE);
+
+    /* Finally set credentials for this session */
+    ret = gnutls_credentials_set(ssock->session,
+                                 GNUTLS_CRD_CERTIFICATE, ssock->xcred);
+    if (ret < 0)
+        goto out;
+
+    ret = GNUTLS_E_SUCCESS;
+out:
+    return tls_status_from_err(ssock, ret);
+}
+
+
+/* Destroy GnuTLS credentials and session. */
+static void tls_close(pj_ssl_sock_t *ssock)
+{
+    if (ssock->session) {
+        gnutls_bye(ssock->session, GNUTLS_SHUT_RDWR);
+        gnutls_deinit(ssock->session);
+        ssock->session = NULL;
+    }
+
+    if (ssock->xcred) {
+        gnutls_certificate_free_credentials(ssock->xcred);
+        ssock->xcred = NULL;
+    }
+
+    /* Free GnuTLS library */
+    if (ssock->tls_init_count) {
+        ssock->tls_init_count--;
+        tls_deinit();
+    }
+
+    /* Destroy circular buffers */
+    circ_deinit(&ssock->circ_buf_input);
+    circ_deinit(&ssock->circ_buf_output);
+}
+
+
+/* Reset socket state. */
+static void tls_sock_reset(pj_ssl_sock_t *ssock)
+{
+    ssock->connection_state = TLS_STATE_NULL;
+
+    tls_close(ssock);
+
+    if (ssock->asock) {
+        pj_activesock_close(ssock->asock);
+        ssock->asock = NULL;
+        ssock->sock = PJ_INVALID_SOCKET;
+    }
+    if (ssock->sock != PJ_INVALID_SOCKET) {
+        pj_sock_close(ssock->sock);
+        ssock->sock = PJ_INVALID_SOCKET;
+    }
+
+    ssock->last_err = tls_last_error = GNUTLS_E_SUCCESS;
+}
+
+
+/* Get Common Name field string from a general name string */
+static void tls_cert_get_cn(const pj_str_t *gen_name, pj_str_t *cn)
+{
+    pj_str_t CN_sign = {"CN=", 3};
+    char *p, *q;
+
+    pj_bzero(cn, sizeof(cn));
+
+    p = pj_strstr(gen_name, &CN_sign);
+    if (!p)
+        return;
+
+    p += 3; /* shift pointer to value part */
+    pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr));
+    q = pj_strchr(cn, ',');
+    if (q)
+        cn->slen = q - p;
+}
+
+
+/* Get certificate info; in case the certificate info is already populated,
+ * this function will check if the contents need updating by inspecting the
+ * issuer and the serial number. */
+static void tls_cert_get_info(pj_pool_t *pool, pj_ssl_cert_info *ci,
+			      gnutls_x509_crt_t cert)
+{
+    pj_bool_t update_needed;
+    char buf[512] = { 0 };
+    size_t bufsize = sizeof(buf);
+    pj_uint8_t serial_no[64] = { 0 }; /* should be >= sizeof(ci->serial_no) */
+    size_t serialsize = sizeof(serial_no);
+    size_t len = sizeof(buf);
+    int i, ret, seq = 0;
+    pj_ssl_cert_name_type type;
+
+    pj_assert(pool && ci && cert);
+
+    /* Get issuer */
+    gnutls_x509_crt_get_issuer_dn(cert, buf, &bufsize);
+
+    /* Get serial no */
+    gnutls_x509_crt_get_serial(cert, serial_no, &serialsize);
+
+    /* Check if the contents need to be updated */
+    update_needed = pj_strcmp2(&ci->issuer.info, buf) ||
+                    pj_memcmp(ci->serial_no, serial_no, serialsize);
+    if (!update_needed)
+        return;
+
+    /* Update cert info */
+
+    pj_bzero(ci, sizeof(pj_ssl_cert_info));
+
+    /* Version */
+    ci->version = gnutls_x509_crt_get_version(cert);
+
+    /* Issuer */
+    pj_strdup2(pool, &ci->issuer.info, buf);
+    tls_cert_get_cn(&ci->issuer.info, &ci->issuer.cn);
+
+    /* Serial number */
+    pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no));
+
+    /* Subject */
+    bufsize = sizeof(buf);
+    gnutls_x509_crt_get_dn(cert, buf, &bufsize);
+    pj_strdup2(pool, &ci->subject.info, buf);
+    tls_cert_get_cn(&ci->subject.info, &ci->subject.cn);
+
+    /* Validity */
+    ci->validity.end.sec = gnutls_x509_crt_get_expiration_time(cert);
+    ci->validity.start.sec = gnutls_x509_crt_get_activation_time(cert);
+    ci->validity.gmt = 0;
+
+    /* Subject Alternative Name extension */
+    if (ci->version >= 3) {
+        char out[256] = { 0 };
+        /* Get the number of all alternate names so that we can allocate
+         * the correct number of bytes in subj_alt_name */
+        while (gnutls_x509_crt_get_subject_alt_name(cert, seq, out, &len,
+                                                    NULL) !=
+               GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+        {
+            seq++;
+        }
+
+        ci->subj_alt_name.entry = pj_pool_calloc(pool, seq,
+                                	sizeof(*ci->subj_alt_name.entry));
+        if (!ci->subj_alt_name.entry) {
+            tls_last_error = GNUTLS_E_MEMORY_ERROR;
+            return;
+        }
+
+        /* Now populate the alternative names */
+        for (i = 0; i < seq; i++) {
+            len = sizeof(out) - 1;
+            ret = gnutls_x509_crt_get_subject_alt_name(cert, i, out,
+            					       &len, NULL);
+
+            switch (ret) {
+            case GNUTLS_SAN_IPADDRESS:
+                type = PJ_SSL_CERT_NAME_IP;
+                pj_inet_ntop2(len == sizeof(pj_in6_addr) ? pj_AF_INET6()
+                                                         : pj_AF_INET(),
+                              out, buf, sizeof(buf));
+                break;
+            case GNUTLS_SAN_URI:
+                type = PJ_SSL_CERT_NAME_URI;
+                break;
+            case GNUTLS_SAN_RFC822NAME:
+                type = PJ_SSL_CERT_NAME_RFC822;
+                break;
+            case GNUTLS_SAN_DNSNAME:
+                type = PJ_SSL_CERT_NAME_DNS;
+                break;
+            default:
+                type = PJ_SSL_CERT_NAME_UNKNOWN;
+                break;
+            }
+
+            if (len && type != PJ_SSL_CERT_NAME_UNKNOWN) {
+                ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type;
+                pj_strdup2(pool,
+                	&ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name,
+                        type == PJ_SSL_CERT_NAME_IP ? buf : out);
+                ci->subj_alt_name.cnt++;
+            }
+        }
+        /* TODO: if no DNS alt. names were found, we could check against
+         * the commonName as per RFC3280. */
+    }
+}
+
+static void tls_cert_get_chain_raw(pj_pool_t *pool, pj_ssl_cert_info *ci,
+				   const gnutls_datum_t *certs,
+				   size_t certs_num)
+{
+    size_t i=0;
+    ci->raw_chain.cert_raw = pj_pool_calloc(pool, certs_num,
+    					sizeof(*ci->raw_chain.cert_raw));
+    ci->raw_chain.cnt = certs_num;
+    for (i=0; i < certs_num; ++i) {
+        const pj_str_t crt_raw = {(char*)certs[i].data,
+        			  (pj_ssize_t)certs[i].size};
+        pj_strdup(pool, ci->raw_chain.cert_raw+i, &crt_raw);
+    }
+}
+
+/* Update local & remote certificates info. This function should be
+ * called after handshake or renegotiation successfully completed. */
+static void tls_cert_update(pj_ssl_sock_t *ssock)
+{
+    gnutls_x509_crt_t cert = NULL;
+    const gnutls_datum_t *us;
+    const gnutls_datum_t *certs;
+    unsigned int certslen = 0;
+    int ret = GNUTLS_CERT_INVALID;
+
+    pj_assert(ssock->connection_state == TLS_STATE_ESTABLISHED);
+
+    /* Get active local certificate */
+    us = gnutls_certificate_get_ours(ssock->session);
+    if (!us)
+        goto us_out;
+
+    ret = gnutls_x509_crt_init(&cert);
+    if (ret < 0)
+        goto us_out;
+    ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_DER);
+    if (ret < 0)
+        ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_PEM);
+    if (ret < 0)
+        goto us_out;
+
+    tls_cert_get_info(ssock->pool, &ssock->local_cert_info, cert);
+    tls_cert_get_chain_raw(ssock->pool, &ssock->local_cert_info, us, 1);
+
+us_out:
+    tls_last_error = ret;
+    if (cert)
+        gnutls_x509_crt_deinit(cert);
+    else
+        pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info));
+
+    cert = NULL;
+
+    /* Get active remote certificate */
+    certs = gnutls_certificate_get_peers(ssock->session, &certslen);
+    if (certs == NULL || certslen == 0)
+        goto peer_out;
+
+    ret = gnutls_x509_crt_init(&cert);
+    if (ret < 0)
+        goto peer_out;
+
+    ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_PEM);
+    if (ret < 0)
+        ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER);
+    if (ret < 0)
+        goto peer_out;
+
+    tls_cert_get_info(ssock->pool, &ssock->remote_cert_info, cert);
+    tls_cert_get_chain_raw(ssock->pool, &ssock->remote_cert_info, certs,
+    			   certslen);
+
+peer_out:
+    tls_last_error = ret;
+    if (cert)
+        gnutls_x509_crt_deinit(cert);
+    else
+        pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info));
+}
+
+
+/* When handshake completed:
+ * - notify application
+ * - if handshake failed, reset SSL state
+ * - return PJ_FALSE when SSL socket instance is destroyed by application. */
+static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock,
+                                       pj_status_t status)
+{
+    pj_bool_t ret = PJ_TRUE;
+
+    /* Cancel handshake timer */
+    if (ssock->timer.id == TIMER_HANDSHAKE_TIMEOUT) {
+        pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer);
+        ssock->timer.id = TIMER_NONE;
+    }
+
+    /* Update certificates info on successful handshake */
+    if (status == PJ_SUCCESS)
+        tls_cert_update(ssock);
+
+    /* Accepting */
+    if (ssock->is_server) {
+        if (status != PJ_SUCCESS) {
+            /* Handshake failed in accepting, destroy our self silently. */
+
+            char errmsg[PJ_ERR_MSG_SIZE];
+            char buf[PJ_INET6_ADDRSTRLEN + 10];
+
+            pj_strerror(status, errmsg, sizeof(errmsg));
+            PJ_LOG(3, (ssock->pool->obj_name,
+                       "Handshake failed in accepting %s: %s",
+                       pj_sockaddr_print(&ssock->rem_addr, buf,sizeof(buf),3),
+                       errmsg));
+
+            /* Workaround for ticket #985 */
+#if (defined(PJ_WIN32)&& PJ_WIN32 != 0) || (defined(PJ_WIN64)&& PJ_WIN64 != 0)
+            if (ssock->param.timer_heap) {
+                pj_time_val interval = {0, DELAYED_CLOSE_TIMEOUT};
+
+                tls_sock_reset(ssock);
+
+                ssock->timer.id = TIMER_CLOSE;
+                pj_time_val_normalize(&interval);
+                if (pj_timer_heap_schedule(ssock->param.timer_heap,
+                                           &ssock->timer, &interval) != 0)
+                {
+                    ssock->timer.id = TIMER_NONE;
+                    pj_ssl_sock_close(ssock);
+                }
+            } else
+#endif /* PJ_WIN32 */
+            {
+                pj_ssl_sock_close(ssock);
+            }
+
+            return PJ_FALSE;
+        }
+        /* Notify application the newly accepted SSL socket */
+        if (ssock->param.cb.on_accept_complete)
+            ret = (*ssock->param.cb.on_accept_complete)
+                      (ssock->parent, ssock, (pj_sockaddr_t*)&ssock->rem_addr,
+                       pj_sockaddr_get_len((pj_sockaddr_t*)&ssock->rem_addr));
+
+    } else { /* Connecting */
+        /* On failure, reset SSL socket state first, as app may try to
+         * reconnect in the callback. */
+        if (status != PJ_SUCCESS) {
+            /* Server disconnected us, possibly due to negotiation failure */
+            tls_sock_reset(ssock);
+        }
+        if (ssock->param.cb.on_connect_complete) {
+
+            ret = (*ssock->param.cb.on_connect_complete)(ssock, status);
+        }
+    }
+
+    return ret;
+}
+
+static write_data_t *alloc_send_data(pj_ssl_sock_t *ssock, pj_size_t len)
+{
+    send_buf_t *send_buf = &ssock->send_buf;
+    pj_size_t avail_len, skipped_len = 0;
+    char *reg1, *reg2;
+    pj_size_t reg1_len, reg2_len;
+    write_data_t *p;
+
+    /* Check buffer availability */
+    avail_len = send_buf->max_len - send_buf->len;
+    if (avail_len < len)
+        return NULL;
+
+    /* If buffer empty, reset start pointer and return it */
+    if (send_buf->len == 0) {
+        send_buf->start = send_buf->buf;
+        send_buf->len   = len;
+        p = (write_data_t*)send_buf->start;
+        goto init_send_data;
+    }
+
+    /* Free space may be wrapped/splitted into two regions, so let's
+     * analyze them if any region can hold the write data. */
+    reg1 = send_buf->start + send_buf->len;
+    if (reg1 >= send_buf->buf + send_buf->max_len)
+        reg1 -= send_buf->max_len;
+        reg1_len = send_buf->max_len - send_buf->len;
+    if (reg1 + reg1_len > send_buf->buf + send_buf->max_len) {
+        reg1_len = send_buf->buf + send_buf->max_len - reg1;
+        reg2 = send_buf->buf;
+        reg2_len = send_buf->start - send_buf->buf;
+    } else {
+        reg2 = NULL;
+        reg2_len = 0;
+    }
+
+    /* More buffer availability check, note that the write data must be in
+     * a contigue buffer. */
+    avail_len = PJ_MAX(reg1_len, reg2_len);
+    if (avail_len < len)
+    return NULL;
+
+    /* Get the data slot */
+    if (reg1_len >= len) {
+        p = (write_data_t*)reg1;
+    } else {
+        p = (write_data_t*)reg2;
+        skipped_len = reg1_len;
+    }
+
+    /* Update buffer length */
+    send_buf->len += len + skipped_len;
+
+init_send_data:
+    /* Init the new send data */
+    pj_bzero(p, sizeof(*p));
+    pj_list_init(p);
+    pj_list_push_back(&ssock->send_pending, p);
+
+    return p;
+}
+
+static void free_send_data(pj_ssl_sock_t *ssock, write_data_t *wdata)
+{
+    send_buf_t *buf = &ssock->send_buf;
+    write_data_t *spl = &ssock->send_pending;
+
+    pj_assert(!pj_list_empty(&ssock->send_pending));
+
+    /* Free slot from the buffer */
+    if (spl->next == wdata && spl->prev == wdata) {
+    /* This is the only data, reset the buffer */
+    buf->start = buf->buf;
+    buf->len = 0;
+    } else if (spl->next == wdata) {
+    /* This is the first data, shift start pointer of the buffer and
+     * adjust the buffer length.
+     */
+    buf->start = (char*)wdata->next;
+    if (wdata->next > wdata) {
+        buf->len -= ((char*)wdata->next - buf->start);
+    } else {
+        /* Overlapped */
+        pj_size_t right_len, left_len;
+        right_len = buf->buf + buf->max_len - (char*)wdata;
+        left_len  = (char*)wdata->next - buf->buf;
+        buf->len -= (right_len + left_len);
+    }
+    } else if (spl->prev == wdata) {
+    /* This is the last data, just adjust the buffer length */
+    if (wdata->prev < wdata) {
+        pj_size_t jump_len;
+        jump_len = (char*)wdata -
+               ((char*)wdata->prev + wdata->prev->record_len);
+        buf->len -= (wdata->record_len + jump_len);
+    } else {
+        /* Overlapped */
+        pj_size_t right_len, left_len;
+        right_len = buf->buf + buf->max_len -
+            ((char*)wdata->prev + wdata->prev->record_len);
+        left_len  = (char*)wdata + wdata->record_len - buf->buf;
+        buf->len -= (right_len + left_len);
+    }
+    }
+    /* For data in the middle buffer, just do nothing on the buffer. The slot
+     * will be freed later when freeing the first/last data. */
+
+    /* Remove the data from send pending list */
+    pj_list_erase(wdata);
+}
+
+#if 0
+/* Just for testing send buffer alloc/free */
+#include <pj/rand.h>
+pj_status_t pj_ssl_sock_ossl_test_send_buf(pj_pool_t *pool)
+{
+    enum { MAX_CHUNK_NUM = 20 };
+    unsigned chunk_size, chunk_cnt, i;
+    write_data_t *wdata[MAX_CHUNK_NUM] = {0};
+    pj_time_val now;
+    pj_ssl_sock_t *ssock = NULL;
+    pj_ssl_sock_param param;
+    pj_status_t status;
+
+    pj_gettimeofday(&now);
+    pj_srand((unsigned)now.sec);
+
+    pj_ssl_sock_param_default(&param);
+    status = pj_ssl_sock_create(pool, &param, &ssock);
+    if (status != PJ_SUCCESS) {
+        return status;
+    }
+
+    if (ssock->send_buf.max_len == 0) {
+        ssock->send_buf.buf = (char *)
+                              pj_pool_alloc(ssock->pool,
+                                            ssock->param.send_buffer_size);
+        ssock->send_buf.max_len = ssock->param.send_buffer_size;
+        ssock->send_buf.start = ssock->send_buf.buf;
+        ssock->send_buf.len = 0;
+    }
+
+    chunk_size = ssock->param.send_buffer_size / MAX_CHUNK_NUM / 2;
+    chunk_cnt = 0;
+    for (i = 0; i < MAX_CHUNK_NUM; i++) {
+        wdata[i] = alloc_send_data(ssock, pj_rand() % chunk_size + 321);
+        if (wdata[i])
+            chunk_cnt++;
+        else
+            break;
+    }
+
+    while (chunk_cnt) {
+        i = pj_rand() % MAX_CHUNK_NUM;
+        if (wdata[i]) {
+            free_send_data(ssock, wdata[i]);
+            wdata[i] = NULL;
+            chunk_cnt--;
+        }
+    }
+
+    if (ssock->send_buf.len != 0)
+        status = PJ_EBUG;
+
+    pj_ssl_sock_close(ssock);
+    return status;
+}
+#endif
+
+/* Flush write circular buffer to network socket. */
+static pj_status_t flush_circ_buf_output(pj_ssl_sock_t *ssock,
+                                         pj_ioqueue_op_key_t *send_key,
+                                         pj_size_t orig_len, unsigned flags)
+{
+    pj_ssize_t len;
+    write_data_t *wdata;
+    pj_size_t needed_len;
+    pj_status_t status;
+
+    pj_lock_acquire(ssock->circ_buf_output_mutex);
+
+    /* Check if there is data in the circular buffer, flush it if any */
+    if (circ_empty(&ssock->circ_buf_output)) {
+        pj_lock_release(ssock->circ_buf_output_mutex);
+
+        return PJ_SUCCESS;
+    }
+
+    len = circ_size(&ssock->circ_buf_output);
+
+    /* Calculate buffer size needed, and align it to 8 */
+    needed_len = len + sizeof(write_data_t);
+    needed_len = ((needed_len + 7) >> 3) << 3;
+
+    /* Allocate buffer for send data */
+    wdata = alloc_send_data(ssock, needed_len);
+    if (wdata == NULL) {
+        pj_lock_release(ssock->circ_buf_output_mutex);
+        return PJ_ENOMEM;
+    }
+
+    /* Copy the data and set its properties into the send data */
+    pj_ioqueue_op_key_init(&wdata->key, sizeof(pj_ioqueue_op_key_t));
+    wdata->key.user_data = wdata;
+    wdata->app_key = send_key;
+    wdata->record_len = needed_len;
+    wdata->data_len = len;
+    wdata->plain_data_len = orig_len;
+    wdata->flags = flags;
+    circ_read(&ssock->circ_buf_output, (pj_uint8_t *)&wdata->data, len);
+
+    /* Ticket #1573: Don't hold mutex while calling PJLIB socket send(). */
+    pj_lock_release(ssock->circ_buf_output_mutex);
+
+    /* Send it */
+    if (ssock->param.sock_type == pj_SOCK_STREAM()) {
+        status = pj_activesock_send(ssock->asock, &wdata->key,
+                                    wdata->data.content, &len,
+                                    flags);
+    } else {
+        status = pj_activesock_sendto(ssock->asock, &wdata->key,
+                                      wdata->data.content, &len,
+                                      flags,
+                                      (pj_sockaddr_t*)&ssock->rem_addr,
+                                      ssock->addr_len);
+    }
+
+    if (status != PJ_EPENDING) {
+        /* When the sending is not pending, remove the wdata from send
+         * pending list. */
+        pj_lock_acquire(ssock->circ_buf_output_mutex);
+        free_send_data(ssock, wdata);
+        pj_lock_release(ssock->circ_buf_output_mutex);
+    }
+
+    return status;
+}
+
+static void on_timer(pj_timer_heap_t *th, struct pj_timer_entry *te)
+{
+    pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)te->user_data;
+    int timer_id = te->id;
+
+    te->id = TIMER_NONE;
+
+    PJ_UNUSED_ARG(th);
+
+    switch (timer_id) {
+    case TIMER_HANDSHAKE_TIMEOUT:
+        PJ_LOG(1, (ssock->pool->obj_name, "TLS timeout after %d.%ds",
+                   ssock->param.timeout.sec, ssock->param.timeout.msec));
+
+        on_handshake_complete(ssock, PJ_ETIMEDOUT);
+        break;
+    case TIMER_CLOSE:
+        pj_ssl_sock_close(ssock);
+        break;
+    default:
+        pj_assert(!"Unknown timer");
+        break;
+    }
+}
+
+
+/* Try to perform an asynchronous handshake */
+static pj_status_t tls_try_handshake(pj_ssl_sock_t *ssock)
+{
+    int ret;
+    pj_status_t status;
+
+    /* Perform SSL handshake */
+    ret = gnutls_handshake(ssock->session);
+
+    status = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    if (ret == GNUTLS_E_SUCCESS) {
+        /* System are GO */
+        ssock->connection_state = TLS_STATE_ESTABLISHED;
+        status = PJ_SUCCESS;
+    } else if (!gnutls_error_is_fatal(ret)) {
+        /* Non fatal error, retry later (busy or again) */
+        status = PJ_EPENDING;
+    } else {
+        /* Fatal error invalidates session, no fallback */
+        status = PJ_EINVAL;
+    }
+
+    tls_last_error = ret;
+
+    return status;
+}
+
+
+/*
+ *******************************************************************
+ * Active socket callbacks.
+ *******************************************************************
+ */
+
+/* PJ_TRUE asks the socket to read more data, PJ_FALSE takes it off the queue */
+static pj_bool_t asock_on_data_read(pj_activesock_t *asock, void *data,
+                                    pj_size_t size, pj_status_t status,
+                                    pj_size_t *remainder)
+{
+    pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)
+                           pj_activesock_get_user_data(asock);
+
+    pj_size_t app_remainder = 0;
+
+    if (data && size > 0) {
+        /* Push data into input circular buffer (for GnuTLS) */
+        pj_lock_acquire(ssock->circ_buf_input_mutex);
+        circ_write(&ssock->circ_buf_input, data, size);
+        pj_lock_release(ssock->circ_buf_input_mutex);
+    }
+
+    /* Check if SSL handshake hasn't finished yet */
+    if (ssock->connection_state == TLS_STATE_HANDSHAKING) {
+        pj_bool_t ret = PJ_TRUE;
+
+        if (status == PJ_SUCCESS)
+            status = tls_try_handshake(ssock);
+
+        /* Not pending is either success or failed */
+        if (status != PJ_EPENDING)
+            ret = on_handshake_complete(ssock, status);
+
+        return ret;
+    }
+
+    /* See if there is any decrypted data for the application */
+    if (ssock->read_started) {
+        do {
+            /* Get read data structure at the end of the data */
+            read_data_t *app_read_data =
+            	*(OFFSET_OF_READ_DATA_PTR(ssock, data));
+            int app_data_size = (int)(ssock->read_size - app_read_data->len);
+
+            /* Decrypt received data using GnuTLS (will read our input
+             * circular buffer) */
+            int decrypted_size = gnutls_record_recv(ssock->session,
+                                	((read_data_t *)app_read_data->data) +
+                                         app_read_data->len,
+                                         app_data_size);
+
+            if (decrypted_size > 0 || status != PJ_SUCCESS) {
+                if (ssock->param.cb.on_data_read) {
+                    pj_bool_t ret;
+                    app_remainder = 0;
+
+                    if (decrypted_size > 0)
+                        app_read_data->len += decrypted_size;
+
+                    ret = (*ssock->param.cb.on_data_read)(ssock,
+                                                          app_read_data->data,
+                                                          app_read_data->len,
+                                                          status,
+                                                          &app_remainder);
+
+                    if (!ret) {
+                        /* We've been destroyed */
+                        return PJ_FALSE;
+                    }
+
+                    /* Application may have left some data to be consumed
+                     * later as remainder */
+                    app_read_data->len = app_remainder;
+                }
+
+                /* Active socket signalled connection closed/error, this has
+                 * been signalled to the application along with any remaining
+                 * buffer. So, let's just reset SSL socket now.  */
+                if (status != PJ_SUCCESS) {
+                    tls_sock_reset(ssock);
+                    return PJ_FALSE;
+                }
+            } else if (decrypted_size == 0) {
+                /* Nothing more to read */
+
+                return PJ_TRUE;
+            } else if (decrypted_size == GNUTLS_E_AGAIN ||
+                       decrypted_size == GNUTLS_E_INTERRUPTED) {
+                return PJ_TRUE;
+            } else if (decrypted_size == GNUTLS_E_REHANDSHAKE) {
+                /* Seems like we are renegotiating */
+                pj_status_t try_handshake_status = tls_try_handshake(ssock);
+
+                /* Not pending is either success or failed */
+                if (try_handshake_status != PJ_EPENDING) {
+                    if (!on_handshake_complete(ssock, try_handshake_status)) {
+                        return PJ_FALSE;
+                    }
+                }
+
+                if (try_handshake_status != PJ_SUCCESS &&
+                    try_handshake_status != PJ_EPENDING) {
+                    return PJ_FALSE;
+                }
+            } else if (!gnutls_error_is_fatal(decrypted_size)) {
+                /* non-fatal error, let's just continue */
+            } else {
+                return PJ_FALSE;
+            }
+        } while (PJ_TRUE);
+    }
+
+    return PJ_TRUE;
+}
+
+
+/* Callback every time new data is available from the active socket */
+static pj_bool_t asock_on_data_sent(pj_activesock_t *asock,
+                                    pj_ioqueue_op_key_t *send_key,
+                                    pj_ssize_t sent)
+{
+    pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock);
+
+    PJ_UNUSED_ARG(send_key);
+    PJ_UNUSED_ARG(sent);
+
+    if (ssock->connection_state == TLS_STATE_HANDSHAKING) {
+        /* Initial handshaking */
+        pj_status_t status = tls_try_handshake(ssock);
+
+        /* Not pending is either success or failed */
+        if (status != PJ_EPENDING)
+            return on_handshake_complete(ssock, status);
+
+    } else if (send_key != &ssock->handshake_op_key) {
+        /* Some data has been sent, notify application */
+        write_data_t *wdata = (write_data_t*)send_key->user_data;
+        if (ssock->param.cb.on_data_sent) {
+            pj_bool_t ret;
+            pj_ssize_t sent_len;
+
+            sent_len = sent > 0 ? wdata->plain_data_len : sent;
+
+            ret = (*ssock->param.cb.on_data_sent)(ssock, wdata->app_key,
+                                                  sent_len);
+            if (!ret) {
+                /* We've been destroyed */
+                return PJ_FALSE;
+            }
+        }
+
+        /* Update write buffer state */
+        pj_lock_acquire(ssock->circ_buf_output_mutex);
+        free_send_data(ssock, wdata);
+        pj_lock_release(ssock->circ_buf_output_mutex);
+    } else {
+        /* SSL re-negotiation is on-progress, just do nothing */
+        /* FIXME: check if this is valid for GnuTLS too */
+    }
+
+    return PJ_TRUE;
+}
+
+
+/* Callback every time a new connection has been accepted (server) */
+static pj_bool_t asock_on_accept_complete(pj_activesock_t *asock,
+                                          pj_sock_t newsock,
+                                          const pj_sockaddr_t *src_addr,
+                                          int src_addr_len)
+{
+    pj_ssl_sock_t *ssock_parent = (pj_ssl_sock_t *)
+                                  pj_activesock_get_user_data(asock);
+
+    pj_ssl_sock_t *ssock;
+    pj_activesock_cb asock_cb;
+    pj_activesock_cfg asock_cfg;
+    unsigned int i;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(src_addr_len);
+
+    /* Create new SSL socket instance */
+    status = pj_ssl_sock_create(ssock_parent->pool,
+    				&ssock_parent->newsock_param,
+                                &ssock);
+    if (status != PJ_SUCCESS)
+        goto on_return;
+
+    /* Update new SSL socket attributes */
+    ssock->sock = newsock;
+    ssock->parent = ssock_parent;
+    ssock->is_server = PJ_TRUE;
+    if (ssock_parent->cert) {
+        status = pj_ssl_sock_set_certificate(ssock, ssock->pool,
+                                             ssock_parent->cert);
+        if (status != PJ_SUCCESS)
+            goto on_return;
+    }
+
+    /* Apply QoS, if specified */
+    status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type,
+                                &ssock->param.qos_params, 1,
+                                ssock->pool->obj_name, NULL);
+    if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error)
+        goto on_return;
+
+    /* Update local address */
+    ssock->addr_len = src_addr_len;
+    status = pj_sock_getsockname(ssock->sock, &ssock->local_addr,
+                                 &ssock->addr_len);
+    if (status != PJ_SUCCESS) {
+        /* This fails on few envs, e.g: win IOCP, just tolerate this and
+         * use parent local address instead.
+         */
+        pj_sockaddr_cp(&ssock->local_addr, &ssock_parent->local_addr);
+    }
+
+    /* Set remote address */
+    pj_sockaddr_cp(&ssock->rem_addr, src_addr);
+
+    /* Create SSL context */
+    status = tls_open(ssock);
+    if (status != PJ_SUCCESS)
+        goto on_return;
+
+    /* Prepare read buffer */
+    ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool,
+                                                ssock->param.async_cnt,
+                                                sizeof(void*));
+    if (!ssock->asock_rbuf)
+        return PJ_ENOMEM;
+
+    for (i = 0; i < ssock->param.async_cnt; ++i) {
+        ssock->asock_rbuf[i] = (void *)pj_pool_alloc(
+                                            ssock->pool,
+                                            ssock->param.read_buffer_size +
+                                            sizeof(read_data_t*));
+        if (!ssock->asock_rbuf[i])
+            return PJ_ENOMEM;
+    }
+
+    /* Create active socket */
+    pj_activesock_cfg_default(&asock_cfg);
+    asock_cfg.async_cnt = ssock->param.async_cnt;
+    asock_cfg.concurrency = ssock->param.concurrency;
+    asock_cfg.whole_data = PJ_TRUE;
+
+    pj_bzero(&asock_cb, sizeof(asock_cb));
+    asock_cb.on_data_read = asock_on_data_read;
+    asock_cb.on_data_sent = asock_on_data_sent;
+
+    status = pj_activesock_create(ssock->pool,
+                                  ssock->sock,
+                                  ssock->param.sock_type,
+                                  &asock_cfg,
+                                  ssock->param.ioqueue,
+                                  &asock_cb,
+                                  ssock,
+                                  &ssock->asock);
+
+    if (status != PJ_SUCCESS)
+        goto on_return;
+
+    /* Start reading */
+    status = pj_activesock_start_read2(ssock->asock, ssock->pool,
+                                       (unsigned)ssock->param.read_buffer_size,
+                                       ssock->asock_rbuf,
+                                       PJ_IOQUEUE_ALWAYS_ASYNC);
+    if (status != PJ_SUCCESS)
+        goto on_return;
+
+    /* Prepare write/send state */
+    pj_assert(ssock->send_buf.max_len == 0);
+    ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool,
+                                                ssock->param.send_buffer_size);
+    if (!ssock->send_buf.buf)
+        return PJ_ENOMEM;
+
+    ssock->send_buf.max_len = ssock->param.send_buffer_size;
+    ssock->send_buf.start = ssock->send_buf.buf;
+    ssock->send_buf.len = 0;
+
+    /* Start handshake timer */
+    if (ssock->param.timer_heap &&
+        (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0)) {
+        pj_assert(ssock->timer.id == TIMER_NONE);
+        ssock->timer.id = TIMER_HANDSHAKE_TIMEOUT;
+        status = pj_timer_heap_schedule(ssock->param.timer_heap,
+                                        &ssock->timer,
+                                        &ssock->param.timeout);
+        if (status != PJ_SUCCESS)
+            ssock->timer.id = TIMER_NONE;
+    }
+
+    /* Start SSL handshake */
+    ssock->connection_state = TLS_STATE_HANDSHAKING;
+
+    status = tls_try_handshake(ssock);
+
+on_return:
+    if (ssock && status != PJ_EPENDING)
+        on_handshake_complete(ssock, status);
+
+    /* Must return PJ_TRUE whatever happened, as active socket must
+     * continue listening.
+     */
+    return PJ_TRUE;
+}
+
+
+/* Callback every time a new connection has been completed (client) */
+static pj_bool_t asock_on_connect_complete (pj_activesock_t *asock,
+                                            pj_status_t status)
+{
+    pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)
+                           pj_activesock_get_user_data(asock);
+
+    unsigned int i;
+    int ret;
+
+    if (status != PJ_SUCCESS)
+        goto on_return;
+
+    /* Update local address */
+    ssock->addr_len = sizeof(pj_sockaddr);
+    status = pj_sock_getsockname(ssock->sock, &ssock->local_addr,
+                                 &ssock->addr_len);
+    if (status != PJ_SUCCESS)
+        goto on_return;
+
+    /* Create SSL context */
+    status = tls_open(ssock);
+    if (status != PJ_SUCCESS)
+        goto on_return;
+
+    /* Prepare read buffer */
+    ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool,
+                                                ssock->param.async_cnt,
+                                                sizeof(void *));
+    if (!ssock->asock_rbuf)
+        return PJ_ENOMEM;
+
+    for (i = 0; i < ssock->param.async_cnt; ++i) {
+        ssock->asock_rbuf[i] = (void *)pj_pool_alloc(
+                                            ssock->pool,
+                                            ssock->param.read_buffer_size +
+                                            sizeof(read_data_t *));
+        if (!ssock->asock_rbuf[i])
+            return PJ_ENOMEM;
+    }
+
+    /* Start read */
+    status = pj_activesock_start_read2(ssock->asock, ssock->pool,
+                                       (unsigned)ssock->param.read_buffer_size,
+                                       ssock->asock_rbuf,
+                                       PJ_IOQUEUE_ALWAYS_ASYNC);
+    if (status != PJ_SUCCESS)
+        goto on_return;
+
+    /* Prepare write/send state */
+    pj_assert(ssock->send_buf.max_len == 0);
+    ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool,
+                                                ssock->param.send_buffer_size);
+    if (!ssock->send_buf.buf)
+        return PJ_ENOMEM;
+
+    ssock->send_buf.max_len = ssock->param.send_buffer_size;
+    ssock->send_buf.start = ssock->send_buf.buf;
+    ssock->send_buf.len = 0;
+
+    /* Set server name to connect */
+    if (ssock->param.server_name.slen) {
+        /* Server name is null terminated already */
+        ret = gnutls_server_name_set(ssock->session, GNUTLS_NAME_DNS,
+                                     ssock->param.server_name.ptr,
+                                     ssock->param.server_name.slen);
+        if (ret < 0) {
+            PJ_LOG(3, (ssock->pool->obj_name,
+                       "gnutls_server_name_set() failed: %s",
+                       gnutls_strerror(ret)));
+        }
+    }
+
+    /* Start handshake */
+    ssock->connection_state = TLS_STATE_HANDSHAKING;
+
+    status = tls_try_handshake(ssock);
+    if (status != PJ_EPENDING)
+        goto on_return;
+
+    return PJ_TRUE;
+
+on_return:
+    return on_handshake_complete(ssock, status);
+}
+
+static void tls_ciphers_fill(void)
+{
+     if (!tls_available_ciphers) {
+         tls_init();
+         tls_deinit();
+     }
+}
+
+/*
+ *******************************************************************
+ * API
+ *******************************************************************
+ */
+
+/* Load credentials from files. */
+PJ_DEF(pj_status_t) pj_ssl_cert_load_from_files(pj_pool_t *pool,
+                                                const pj_str_t *CA_file,
+                                                const pj_str_t *cert_file,
+                                                const pj_str_t *privkey_file,
+                                                const pj_str_t *privkey_pass,
+                                                pj_ssl_cert_t **p_cert)
+{
+    return pj_ssl_cert_load_from_files2(pool, CA_file, NULL, cert_file,
+                    privkey_file, privkey_pass, p_cert);
+}
+
+/* Load credentials from files. */
+PJ_DECL(pj_status_t) pj_ssl_cert_load_from_files2(
+                        pj_pool_t *pool,
+                        const pj_str_t *CA_file,
+                        const pj_str_t *CA_path,
+                        const pj_str_t *cert_file,
+                        const pj_str_t *privkey_file,
+                        const pj_str_t *privkey_pass,
+                        pj_ssl_cert_t **p_cert)
+{
+    pj_ssl_cert_t *cert;
+
+    PJ_ASSERT_RETURN(pool && (CA_file || CA_path) && cert_file &&
+             privkey_file,
+             PJ_EINVAL);
+
+    cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t);
+    if (CA_file) {
+        pj_strdup_with_null(pool, &cert->CA_file, CA_file);
+    }
+    if (CA_path) {
+        pj_strdup_with_null(pool, &cert->CA_path, CA_path);
+    }
+    pj_strdup_with_null(pool, &cert->cert_file, cert_file);
+    pj_strdup_with_null(pool, &cert->privkey_file, privkey_file);
+    pj_strdup_with_null(pool, &cert->privkey_pass, privkey_pass);
+
+    *p_cert = cert;
+
+    return PJ_SUCCESS;
+}
+
+/* Store credentials. */
+PJ_DECL(pj_status_t) pj_ssl_sock_set_certificate(pj_ssl_sock_t *ssock,
+                                                 pj_pool_t *pool,
+                                                 const pj_ssl_cert_t *cert)
+{
+    pj_ssl_cert_t *cert_;
+
+    PJ_ASSERT_RETURN(ssock && pool && cert, PJ_EINVAL);
+
+    cert_ = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t);
+    pj_memcpy(cert_, cert, sizeof(cert));
+    pj_strdup_with_null(pool, &cert_->CA_file, &cert->CA_file);
+    pj_strdup_with_null(pool, &cert_->CA_path, &cert->CA_path);
+    pj_strdup_with_null(pool, &cert_->cert_file, &cert->cert_file);
+    pj_strdup_with_null(pool, &cert_->privkey_file, &cert->privkey_file);
+    pj_strdup_with_null(pool, &cert_->privkey_pass, &cert->privkey_pass);
+
+    ssock->cert = cert_;
+
+    return PJ_SUCCESS;
+}
+
+
+/* Get available ciphers. */
+PJ_DEF(pj_status_t) pj_ssl_cipher_get_availables(pj_ssl_cipher ciphers[],
+                                                 unsigned *cipher_num)
+{
+    unsigned int i;
+
+    PJ_ASSERT_RETURN(ciphers && cipher_num, PJ_EINVAL);
+
+    tls_ciphers_fill();
+
+    if (!tls_available_ciphers) {
+        *cipher_num = 0;
+        return PJ_ENOTFOUND;
+    }
+
+    *cipher_num = PJ_MIN(*cipher_num, tls_available_ciphers);
+
+    for (i = 0; i < *cipher_num; ++i)
+        ciphers[i] = tls_ciphers[i].id;
+
+    return PJ_SUCCESS;
+}
+
+
+/* Get cipher name string. */
+PJ_DEF(const char *)pj_ssl_cipher_name(pj_ssl_cipher cipher)
+{
+    unsigned int i;
+
+    tls_ciphers_fill();
+
+    for (i = 0; i < tls_available_ciphers; ++i) {
+        if (cipher == tls_ciphers[i].id)
+            return tls_ciphers[i].name;
+    }
+
+    return NULL;
+}
+
+
+/* Get cipher identifier. */
+PJ_DEF(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name)
+{
+    unsigned int i;
+
+    tls_ciphers_fill();
+
+    for (i = 0; i < tls_available_ciphers; ++i) {
+        if (!pj_ansi_stricmp(tls_ciphers[i].name, cipher_name))
+            return tls_ciphers[i].id;
+    }
+
+    return PJ_TLS_UNKNOWN_CIPHER;
+}
+
+
+/* Check if the specified cipher is supported by the TLS backend. */
+PJ_DEF(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher)
+{
+    unsigned int i;
+
+    tls_ciphers_fill();
+
+    for (i = 0; i < tls_available_ciphers; ++i) {
+        if (cipher == tls_ciphers[i].id)
+            return PJ_TRUE;
+    }
+
+    return PJ_FALSE;
+}
+
+/* Create SSL socket instance. */
+PJ_DEF(pj_status_t) pj_ssl_sock_create(pj_pool_t *pool,
+                                       const pj_ssl_sock_param *param,
+                                       pj_ssl_sock_t **p_ssock)
+{
+    pj_ssl_sock_t *ssock;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(pool && param && p_ssock, PJ_EINVAL);
+    PJ_ASSERT_RETURN(param->sock_type == pj_SOCK_STREAM(), PJ_ENOTSUP);
+
+    pool = pj_pool_create(pool->factory, "tls%p", 512, 512, NULL);
+
+    /* Create secure socket */
+    ssock = PJ_POOL_ZALLOC_T(pool, pj_ssl_sock_t);
+    ssock->pool = pool;
+    ssock->sock = PJ_INVALID_SOCKET;
+    ssock->connection_state = TLS_STATE_NULL;
+    pj_list_init(&ssock->write_pending);
+    pj_list_init(&ssock->write_pending_empty);
+    pj_list_init(&ssock->send_pending);
+    pj_timer_entry_init(&ssock->timer, 0, ssock, &on_timer);
+    pj_ioqueue_op_key_init(&ssock->handshake_op_key,
+                           sizeof(pj_ioqueue_op_key_t));
+
+    /* Create secure socket mutex */
+    status = pj_lock_create_recursive_mutex(pool, pool->obj_name,
+                                            &ssock->circ_buf_output_mutex);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    /* Create input circular buffer mutex */
+    status = pj_lock_create_simple_mutex(pool, pool->obj_name,
+                                         &ssock->circ_buf_input_mutex);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    /* Create output circular buffer mutex */
+    status = pj_lock_create_simple_mutex(pool, pool->obj_name,
+                                         &ssock->circ_buf_output_mutex);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    /* Init secure socket param */
+    ssock->param = *param;
+    ssock->param.read_buffer_size = ((ssock->param.read_buffer_size + 7) >> 3)
+    				     << 3;
+
+    if (param->ciphers_num > 0) {
+        unsigned int i;
+        ssock->param.ciphers = (pj_ssl_cipher *)
+                               pj_pool_calloc(pool, param->ciphers_num,
+                                              sizeof(pj_ssl_cipher));
+        if (!ssock->param.ciphers)
+            return PJ_ENOMEM;
+
+        for (i = 0; i < param->ciphers_num; ++i)
+            ssock->param.ciphers[i] = param->ciphers[i];
+    }
+
+    /* Server name must be null-terminated */
+    pj_strdup_with_null(pool, &ssock->param.server_name, &param->server_name);
+
+    /* Finally */
+    *p_ssock = ssock;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Close the secure socket. This will unregister the socket from the
+ * ioqueue and ultimately close the socket.
+ */
+PJ_DEF(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock)
+{
+    pj_pool_t *pool;
+
+    PJ_ASSERT_RETURN(ssock, PJ_EINVAL);
+
+    if (!ssock->pool)
+        return PJ_SUCCESS;
+
+    if (ssock->timer.id != TIMER_NONE) {
+        pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer);
+        ssock->timer.id = TIMER_NONE;
+    }
+
+    tls_sock_reset(ssock);
+
+    pj_lock_destroy(ssock->circ_buf_output_mutex);
+    pj_lock_destroy(ssock->circ_buf_input_mutex);
+
+    pool = ssock->pool;
+    ssock->pool = NULL;
+    if (pool)
+        pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+
+/* Associate arbitrary data with the secure socket. */
+PJ_DEF(pj_status_t) pj_ssl_sock_set_user_data(pj_ssl_sock_t *ssock,
+                                              void *user_data)
+{
+    PJ_ASSERT_RETURN(ssock, PJ_EINVAL);
+
+    ssock->param.user_data = user_data;
+    return PJ_SUCCESS;
+}
+
+
+/* Retrieve the user data previously associated with this secure socket. */
+PJ_DEF(void *)pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock)
+{
+    PJ_ASSERT_RETURN(ssock, NULL);
+
+    return ssock->param.user_data;
+}
+
+
+/* Retrieve the local address and port used by specified SSL socket. */
+PJ_DEF(pj_status_t) pj_ssl_sock_get_info (pj_ssl_sock_t *ssock,
+                                          pj_ssl_sock_info *info)
+{
+    pj_bzero(info, sizeof(*info));
+
+    /* Established flag */
+    info->established = (ssock->connection_state == TLS_STATE_ESTABLISHED);
+
+    /* Protocol */
+    info->proto = ssock->param.proto;
+
+    /* Local address */
+    pj_sockaddr_cp(&info->local_addr, &ssock->local_addr);
+
+    if (info->established) {
+        int i;
+        gnutls_cipher_algorithm_t lookup;
+        gnutls_cipher_algorithm_t cipher;
+
+        /* Current cipher */
+        cipher = gnutls_cipher_get(ssock->session);
+        for (i = 0; ; i++) {
+            unsigned char id[2];
+            const char *suite = gnutls_cipher_suite_info(i,(unsigned char *)id,
+                                                         NULL, &lookup, NULL,
+                                                         NULL);
+            if (suite) {
+                if (lookup == cipher) {
+                    info->cipher = (pj_uint32_t) ((id[0] << 8) | id[1]);
+                    break;
+                }
+            } else
+                break;
+        }
+
+        /* Remote address */
+        pj_sockaddr_cp(&info->remote_addr, &ssock->rem_addr);
+
+        /* Certificates info */
+        info->local_cert_info = &ssock->local_cert_info;
+        info->remote_cert_info = &ssock->remote_cert_info;
+
+        /* Verification status */
+        info->verify_status = ssock->verify_status;
+    }
+
+    /* Last known GnuTLS error code */
+    info->last_native_err = ssock->last_err;
+
+    return PJ_SUCCESS;
+}
+
+
+/* Starts read operation on this secure socket. */
+PJ_DEF(pj_status_t) pj_ssl_sock_start_read(pj_ssl_sock_t *ssock,
+                                           pj_pool_t *pool,
+                                           unsigned buff_size,
+                                           pj_uint32_t flags)
+{
+    void **readbuf;
+    unsigned int i;
+
+    PJ_ASSERT_RETURN(ssock && pool && buff_size, PJ_EINVAL);
+    PJ_ASSERT_RETURN(ssock->connection_state == TLS_STATE_ESTABLISHED,
+                     PJ_EINVALIDOP);
+
+    readbuf = (void**) pj_pool_calloc(pool, ssock->param.async_cnt,
+                                      sizeof(void *));
+    if (!readbuf)
+        return PJ_ENOMEM;
+
+    for (i = 0; i < ssock->param.async_cnt; ++i) {
+        readbuf[i] = pj_pool_alloc(pool, buff_size);
+        if (!readbuf[i])
+            return PJ_ENOMEM;
+    }
+
+    return pj_ssl_sock_start_read2(ssock, pool, buff_size, readbuf, flags);
+}
+
+
+/*
+ * Same as #pj_ssl_sock_start_read(), except that the application
+ * supplies the buffers for the read operation so that the acive socket
+ * does not have to allocate the buffers.
+ */
+PJ_DEF(pj_status_t) pj_ssl_sock_start_read2 (pj_ssl_sock_t *ssock,
+                                             pj_pool_t *pool,
+                                             unsigned buff_size,
+                                             void *readbuf[],
+                                             pj_uint32_t flags)
+{
+    unsigned int i;
+
+    PJ_ASSERT_RETURN(ssock && pool && buff_size && readbuf, PJ_EINVAL);
+    PJ_ASSERT_RETURN(ssock->connection_state == TLS_STATE_ESTABLISHED,
+                     PJ_EINVALIDOP);
+
+    /* Create SSL socket read buffer */
+    ssock->ssock_rbuf = (read_data_t*)pj_pool_calloc(pool,
+                                                     ssock->param.async_cnt,
+                                                     sizeof(read_data_t));
+    if (!ssock->ssock_rbuf)
+        return PJ_ENOMEM;
+
+    /* Store SSL socket read buffer pointer in the activesock read buffer */
+    for (i = 0; i < ssock->param.async_cnt; ++i) {
+        read_data_t **p_ssock_rbuf =
+                        OFFSET_OF_READ_DATA_PTR(ssock, ssock->asock_rbuf[i]);
+
+        ssock->ssock_rbuf[i].data = readbuf[i];
+        ssock->ssock_rbuf[i].len = 0;
+
+        *p_ssock_rbuf = &ssock->ssock_rbuf[i];
+    }
+
+    ssock->read_size = buff_size;
+    ssock->read_started = PJ_TRUE;
+    ssock->read_flags = flags;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Same as pj_ssl_sock_start_read(), except that this function is used
+ * only for datagram sockets, and it will trigger \a on_data_recvfrom()
+ * callback instead.
+ */
+PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom (pj_ssl_sock_t *ssock,
+                                                pj_pool_t *pool,
+                                                unsigned buff_size,
+                                                pj_uint32_t flags)
+{
+    PJ_UNUSED_ARG(ssock);
+    PJ_UNUSED_ARG(pool);
+    PJ_UNUSED_ARG(buff_size);
+    PJ_UNUSED_ARG(flags);
+
+    return PJ_ENOTSUP;
+}
+
+
+/*
+ * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom()
+ * operation takes the buffer from the argument rather than creating
+ * new ones.
+ */
+PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom2 (pj_ssl_sock_t *ssock,
+                                                 pj_pool_t *pool,
+                                                 unsigned buff_size,
+                                                 void *readbuf[],
+                                                 pj_uint32_t flags)
+{
+    PJ_UNUSED_ARG(ssock);
+    PJ_UNUSED_ARG(pool);
+    PJ_UNUSED_ARG(buff_size);
+    PJ_UNUSED_ARG(readbuf);
+    PJ_UNUSED_ARG(flags);
+
+    return PJ_ENOTSUP;
+}
+
+
+/*
+ * Write the plain data to GnuTLS, it will be encrypted by gnutls_record_send()
+ * and sent via tls_data_push. Note that re-negotitation may be on progress, so
+ * sending data should be delayed until re-negotiation is completed.
+ */
+static pj_status_t tls_write(pj_ssl_sock_t *ssock,
+                             pj_ioqueue_op_key_t *send_key,
+                             const void *data, pj_ssize_t size, unsigned flags)
+{
+    pj_status_t status;
+    int nwritten;
+    pj_ssize_t total_written = 0;
+
+    /* Ask GnuTLS to encrypt our plaintext now. GnuTLS will use the push
+     * callback to actually write the encrypted bytes into our output circular
+     * buffer. GnuTLS may refuse to "send" everything at once, but since we are
+     * not really sending now, we will just call it again now until it succeeds
+     * (or fails in a fatal way). */
+    while (total_written < size) {
+        /* Try encrypting using GnuTLS */
+        nwritten = gnutls_record_send(ssock->session,
+        			      ((read_data_t *)data) + total_written,
+                                      size);
+
+        if (nwritten > 0) {
+            /* Good, some data was encrypted and written */
+            total_written += nwritten;
+        } else {
+            /* Normally we would have to retry record_send but our internal
+             * state has not changed, so we have to ask for more data first.
+             * We will just try again later, although this should never happen.
+             */
+            return tls_status_from_err(ssock, nwritten);
+        }
+    }
+
+    /* All encrypted data is written to the output circular buffer;
+     * now send it on the socket (or notify problem). */
+    if (total_written == size)
+        status = flush_circ_buf_output(ssock, send_key, size, flags);
+    else
+        status = PJ_ENOMEM;
+
+    return status;
+}
+
+
+/* Flush delayed data sending in the write pending list. */
+static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock)
+{
+    /* Check for another ongoing flush */
+    if (ssock->flushing_write_pend) {
+        return PJ_EBUSY;
+    }
+
+    pj_lock_acquire(ssock->circ_buf_output_mutex);
+
+    /* Again, check for another ongoing flush */
+    if (ssock->flushing_write_pend) {
+        pj_lock_release(ssock->circ_buf_output_mutex);
+        return PJ_EBUSY;
+    }
+
+    /* Set ongoing flush flag */
+    ssock->flushing_write_pend = PJ_TRUE;
+
+    while (!pj_list_empty(&ssock->write_pending)) {
+        write_data_t *wp;
+        pj_status_t status;
+
+        wp = ssock->write_pending.next;
+
+        /* Ticket #1573: Don't hold mutex while calling socket send. */
+        pj_lock_release(ssock->circ_buf_output_mutex);
+
+        status = tls_write(ssock, &wp->key, wp->data.ptr,
+                           wp->plain_data_len, wp->flags);
+        if (status != PJ_SUCCESS) {
+            /* Reset ongoing flush flag first. */
+            ssock->flushing_write_pend = PJ_FALSE;
+            return status;
+        }
+
+        pj_lock_acquire(ssock->circ_buf_output_mutex);
+        pj_list_erase(wp);
+        pj_list_push_back(&ssock->write_pending_empty, wp);
+    }
+
+    /* Reset ongoing flush flag */
+    ssock->flushing_write_pend = PJ_FALSE;
+
+    pj_lock_release(ssock->circ_buf_output_mutex);
+
+    return PJ_SUCCESS;
+}
+
+
+/* Sending is delayed, push back the sending data into pending list. */
+static pj_status_t delay_send(pj_ssl_sock_t *ssock,
+                              pj_ioqueue_op_key_t *send_key,
+                              const void *data, pj_ssize_t size,
+                              unsigned flags)
+{
+    write_data_t *wp;
+
+    pj_lock_acquire(ssock->circ_buf_output_mutex);
+
+    /* Init write pending instance */
+    if (!pj_list_empty(&ssock->write_pending_empty)) {
+        wp = ssock->write_pending_empty.next;
+        pj_list_erase(wp);
+    } else {
+        wp = PJ_POOL_ZALLOC_T(ssock->pool, write_data_t);
+    }
+
+    wp->app_key = send_key;
+    wp->plain_data_len = size;
+    wp->data.ptr = data;
+    wp->flags = flags;
+
+    pj_list_push_back(&ssock->write_pending, wp);
+
+    pj_lock_release(ssock->circ_buf_output_mutex);
+
+    /* Must return PJ_EPENDING */
+    return PJ_EPENDING;
+}
+
+
+/**
+ * Send data using the socket.
+ */
+PJ_DEF(pj_status_t) pj_ssl_sock_send(pj_ssl_sock_t *ssock,
+                                     pj_ioqueue_op_key_t *send_key,
+                                     const void *data, pj_ssize_t *size,
+                                     unsigned flags)
+{
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(ssock && data && size && (*size > 0), PJ_EINVAL);
+    PJ_ASSERT_RETURN(ssock->connection_state==TLS_STATE_ESTABLISHED,
+                     PJ_EINVALIDOP);
+
+    /* Flush delayed send first. Sending data might be delayed when
+     * re-negotiation is on-progress. */
+    status = flush_delayed_send(ssock);
+    if (status == PJ_EBUSY) {
+        /* Re-negotiation or flushing is on progress, delay sending */
+        status = delay_send(ssock, send_key, data, *size, flags);
+        goto on_return;
+    } else if (status != PJ_SUCCESS) {
+        goto on_return;
+    }
+
+    /* Write data to SSL */
+    status = tls_write(ssock, send_key, data, *size, flags);
+    if (status == PJ_EBUSY) {
+        /* Re-negotiation is on progress, delay sending */
+        status = delay_send(ssock, send_key, data, *size, flags);
+    }
+
+on_return:
+    return status;
+}
+
+
+/**
+ * Send datagram using the socket.
+ */
+PJ_DEF(pj_status_t) pj_ssl_sock_sendto (pj_ssl_sock_t *ssock,
+                                        pj_ioqueue_op_key_t *send_key,
+                                        const void *data, pj_ssize_t *size,
+                                        unsigned flags,
+                                        const pj_sockaddr_t *addr, int addr_len)
+{
+    PJ_UNUSED_ARG(ssock);
+    PJ_UNUSED_ARG(send_key);
+    PJ_UNUSED_ARG(data);
+    PJ_UNUSED_ARG(size);
+    PJ_UNUSED_ARG(flags);
+    PJ_UNUSED_ARG(addr);
+    PJ_UNUSED_ARG(addr_len);
+
+    return PJ_ENOTSUP;
+}
+
+/**
+ * Starts asynchronous socket accept() operations on this secure socket.
+ */
+PJ_DEF(pj_status_t) pj_ssl_sock_start_accept (pj_ssl_sock_t *ssock,
+                          pj_pool_t *pool,
+                          const pj_sockaddr_t *localaddr,
+                          int addr_len)
+{
+    return pj_ssl_sock_start_accept2(ssock, pool, localaddr, addr_len,
+                         &ssock->param);
+}
+
+/**
+ * Starts asynchronous socket accept() operations on this secure socket.
+ */
+PJ_DEF(pj_status_t)
+pj_ssl_sock_start_accept2 (pj_ssl_sock_t *ssock,
+                           pj_pool_t *pool,
+                           const pj_sockaddr_t *localaddr,
+                           int addr_len,
+                           const pj_ssl_sock_param *newsock_param)
+{
+    pj_activesock_cb asock_cb;
+    pj_activesock_cfg asock_cfg;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(ssock && pool && localaddr && addr_len, PJ_EINVAL);
+
+    /* Verify new socket parameters */
+    if (newsock_param->grp_lock != ssock->param.grp_lock ||
+        newsock_param->sock_af != ssock->param.sock_af ||
+        newsock_param->sock_type != ssock->param.sock_type)
+    {
+        return PJ_EINVAL;
+    }
+
+    /* Create socket */
+    status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0,
+                            &ssock->sock);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    /* Apply SO_REUSEADDR */
+    if (ssock->param.reuse_addr) {
+        int enabled = 1;
+        status = pj_sock_setsockopt(ssock->sock, pj_SOL_SOCKET(),
+                                    pj_SO_REUSEADDR(),
+                                    &enabled, sizeof(enabled));
+        if (status != PJ_SUCCESS) {
+            PJ_PERROR(4,(ssock->pool->obj_name, status,
+                         "Warning: error applying SO_REUSEADDR"));
+        }
+    }
+
+    /* Apply QoS, if specified */
+    status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type,
+                                &ssock->param.qos_params, 2,
+                                ssock->pool->obj_name, NULL);
+    if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error)
+        goto on_error;
+
+    /* Bind socket */
+    status = pj_sock_bind(ssock->sock, localaddr, addr_len);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    /* Start listening to the address */
+    status = pj_sock_listen(ssock->sock, PJ_SOMAXCONN);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    /* Create active socket */
+    pj_activesock_cfg_default(&asock_cfg);
+    asock_cfg.async_cnt = ssock->param.async_cnt;
+    asock_cfg.concurrency = ssock->param.concurrency;
+    asock_cfg.whole_data = PJ_TRUE;
+
+    pj_bzero(&asock_cb, sizeof(asock_cb));
+    asock_cb.on_accept_complete = asock_on_accept_complete;
+
+    status = pj_activesock_create(pool,
+                                  ssock->sock,
+                                  ssock->param.sock_type,
+                                  &asock_cfg,
+                                  ssock->param.ioqueue,
+                                  &asock_cb,
+                                  ssock,
+                                  &ssock->asock);
+
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    /* Start accepting */
+    pj_ssl_sock_param_copy(pool, &ssock->newsock_param, newsock_param);
+    status = pj_activesock_start_accept(ssock->asock, pool);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    /* Update local address */
+    ssock->addr_len = addr_len;
+    status = pj_sock_getsockname(ssock->sock, &ssock->local_addr,
+                                 &ssock->addr_len);
+    if (status != PJ_SUCCESS)
+        pj_sockaddr_cp(&ssock->local_addr, localaddr);
+
+    ssock->is_server = PJ_TRUE;
+
+    return PJ_SUCCESS;
+
+on_error:
+    tls_sock_reset(ssock);
+    return status;
+}
+
+
+/**
+ * Starts asynchronous socket connect() operation.
+ */
+PJ_DECL(pj_status_t) pj_ssl_sock_start_connect(pj_ssl_sock_t *ssock,
+                                               pj_pool_t *pool,
+                                               const pj_sockaddr_t *localaddr,
+                                               const pj_sockaddr_t *remaddr,
+                                               int addr_len)
+{
+    pj_activesock_cb asock_cb;
+    pj_activesock_cfg asock_cfg;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len,
+                     PJ_EINVAL);
+
+    /* Create socket */
+    status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0,
+                            &ssock->sock);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    /* Apply QoS, if specified */
+    status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type,
+                                &ssock->param.qos_params, 2,
+                                ssock->pool->obj_name, NULL);
+    if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error)
+        goto on_error;
+
+    /* Bind socket */
+    status = pj_sock_bind(ssock->sock, localaddr, addr_len);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    /* Create active socket */
+    pj_activesock_cfg_default(&asock_cfg);
+    asock_cfg.async_cnt = ssock->param.async_cnt;
+    asock_cfg.concurrency = ssock->param.concurrency;
+    asock_cfg.whole_data = PJ_TRUE;
+
+    pj_bzero(&asock_cb, sizeof(asock_cb));
+    asock_cb.on_connect_complete = asock_on_connect_complete;
+    asock_cb.on_data_read = asock_on_data_read;
+    asock_cb.on_data_sent = asock_on_data_sent;
+
+    status = pj_activesock_create(pool,
+                                  ssock->sock,
+                                  ssock->param.sock_type,
+                                  &asock_cfg,
+                                  ssock->param.ioqueue,
+                                  &asock_cb,
+                                  ssock,
+                                  &ssock->asock);
+
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    /* Save remote address */
+    pj_sockaddr_cp(&ssock->rem_addr, remaddr);
+
+    /* Start timer */
+    if (ssock->param.timer_heap &&
+        (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0))
+    {
+        pj_assert(ssock->timer.id == TIMER_NONE);
+        ssock->timer.id = TIMER_HANDSHAKE_TIMEOUT;
+        status = pj_timer_heap_schedule(ssock->param.timer_heap,
+                                        &ssock->timer,
+                                        &ssock->param.timeout);
+        if (status != PJ_SUCCESS)
+            ssock->timer.id = TIMER_NONE;
+    }
+
+    status = pj_activesock_start_connect(ssock->asock, pool, remaddr,
+                                         addr_len);
+
+    if (status == PJ_SUCCESS)
+        asock_on_connect_complete(ssock->asock, PJ_SUCCESS);
+    else if (status != PJ_EPENDING)
+        goto on_error;
+
+    /* Update local address */
+    ssock->addr_len = addr_len;
+    status = pj_sock_getsockname(ssock->sock, &ssock->local_addr,
+                                 &ssock->addr_len);
+    /* Note that we may not get an IP address here. This can
+     * happen for example on Windows, where getsockname()
+     * would return 0.0.0.0 if socket has just started the
+     * async connect. In this case, just leave the local
+     * address with 0.0.0.0 for now; it will be updated
+     * once the socket is established.
+     */
+
+    /* Update socket state */
+    ssock->is_server = PJ_FALSE;
+
+    return PJ_EPENDING;
+
+on_error:
+    tls_sock_reset(ssock);
+    return status;
+}
+
+
+PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock)
+{
+    int status;
+
+    /* Nothing established yet */
+    PJ_ASSERT_RETURN(ssock->connection_state == TLS_STATE_ESTABLISHED,
+                     PJ_EINVALIDOP);
+
+    /* Cannot renegotiate; we're a client */
+    /* FIXME: in fact maybe that's not true */
+    PJ_ASSERT_RETURN(!ssock->is_server, PJ_EINVALIDOP);
+
+    /* First call gnutls_rehandshake() to see if this is even possible */
+    status = gnutls_rehandshake(ssock->session);
+
+    if (status == GNUTLS_E_SUCCESS) {
+        /* Rehandshake is possible, so try a GnuTLS handshake now. The eventual
+         * gnutls_record_recv() calls could return a few specific values during
+         * this state:
+         *
+         *   - GNUTLS_E_REHANDSHAKE: rehandshake message processing
+         *   - GNUTLS_E_WARNING_ALERT_RECEIVED: client does not wish to
+         *                                      renegotiate
+         */
+        ssock->connection_state = TLS_STATE_HANDSHAKING;
+        status = tls_try_handshake(ssock);
+
+        return status;
+    } else {
+        return tls_status_from_err(ssock, status);
+    }
+}
+
+#endif /* PJ_HAS_SSL_SOCK */
Index: pjproject/trunk/pjlib/src/pj/ssl_sock_ossl.c
===================================================================
--- pjproject/trunk/pjlib/src/pj/ssl_sock_ossl.c	(revision 5724)
+++ pjproject/trunk/pjlib/src/pj/ssl_sock_ossl.c	(revision 5725)
@@ -33,6 +33,9 @@
 
 
-/* Only build when PJ_HAS_SSL_SOCK is enabled */
-#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK!=0
+/* Only build when PJ_HAS_SSL_SOCK is enabled and when the backend is
+ * OpenSSL.
+ */
+#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \
+    (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL)
 
 #define THIS_FILE		"ssl_sock_ossl.c"
Index: pjproject/trunk/pjmedia/src/pjmedia/transport_srtp.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/transport_srtp.c	(revision 5724)
+++ pjproject/trunk/pjmedia/src/pjmedia/transport_srtp.c	(revision 5725)
@@ -32,5 +32,6 @@
 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
 
-#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0)
+#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \
+    (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL)
 #  include <openssl/rand.h>
 #  include <openssl/opensslv.h>
Index: pjproject/trunk/pjmedia/src/pjmedia/transport_srtp_sdes.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/transport_srtp_sdes.c	(revision 5724)
+++ pjproject/trunk/pjmedia/src/pjmedia/transport_srtp_sdes.c	(revision 5725)
@@ -123,5 +123,6 @@
 
 	do {
-#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0)
+#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) && \
+    (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL)
 	    int err = RAND_bytes((unsigned char*)key,
 				 crypto_suites[cs_idx].cipher_key_len);
