diff -rupN pjproject-2.5.5/aconfigure pjproject-2.5.5-new/aconfigure --- pjproject-2.5.5/aconfigure 2016-07-20 09:03:49.000000000 +0200 +++ pjproject-2.5.5-new/aconfigure 2017-01-05 14:15:08.696523621 +0100 @@ -640,6 +640,8 @@ ac_no_opencore_amrnb libcrypto_present libssl_present openssl_h_present +libgnutls_present +gnutls_h_present ac_no_ssl ac_webrtc_ldflags ac_webrtc_cflags @@ -1491,8 +1493,8 @@ Optional Features: package and samples location using IPPROOT and IPPSAMPLES env var or with --with-ipp and --with-ipp-samples options - --disable-ssl Exclude SSL support the build (default: autodetect) - + --enable-ssl=backend Select 'gnutls' or 'openssl' (default) to provide + SSL support (autodetect) --disable-opencore-amr Exclude OpenCORE AMR support from the build (default: autodetect) @@ -7826,33 +7828,160 @@ fi # Check whether --enable-ssl was given. if test "${enable_ssl+set}" = set; then : - enableval=$enable_ssl; - if test "$enable_ssl" = "no"; then - ac_no_ssl=1 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if SSL support is disabled... yes" >&5 + enableval=$enable_ssl; if test "x$enableval" = "xgnutls"; then + ssl_backend="gnutls" + else + ssl_backend="openssl" + + fi + +fi + + +if test "x$enable_ssl" = "xno"; then + ac_no_ssl=1 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if SSL support is disabled... yes" >&5 $as_echo "Checking if SSL support is disabled... yes" >&6; } - fi +else + if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then + CFLAGS="$CFLAGS -I$with_ssl/include" + LDFLAGS="$LDFLAGS -L$with_ssl/lib" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using SSL prefix... $with_ssl" >&5 +$as_echo "Using SSL prefix... $with_ssl" >&6; } + fi + if test "x$ssl_backend" = "xgnutls"; then + 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. */ - { $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for OpenSSL installations.." >&5 +/* 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; } + # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK + #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) + $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h + + $as_echo "#define PJ_HAS_TLS_SOCK 1" >>confdefs.h + + 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 + 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" - LDFLAGS="$LDFLAGS -L$with_ssl/lib" - { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using SSL prefix... $with_ssl" >&5 -$as_echo "Using SSL prefix... $with_ssl" >&6; } - fi - ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default" + 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 fi - { $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 : $as_echo_n "(cached) " >&6 @@ -7892,7 +8021,7 @@ if test "x$ac_cv_lib_crypto_ERR_load_BIO libcrypto_present=1 && LIBS="-lcrypto $LIBS" fi - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_library_init in -lssl" >&5 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_library_init in -lssl" >&5 $as_echo_n "checking for SSL_library_init in -lssl... " >&6; } if ${ac_cv_lib_ssl_SSL_library_init+:} false; then : $as_echo_n "(cached) " >&6 @@ -7932,22 +8061,23 @@ if test "x$ac_cv_lib_ssl_SSL_library_ini libssl_present=1 && LIBS="-lssl $LIBS" fi - 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 + 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; } - # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK - #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) - $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; } - fi - + # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK + #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) + $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h + + $as_echo "#define PJ_HAS_TLS_SOCK 0" >>confdefs.h + + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ** No OpenSSL libraries found, disabling SSL support **" >&5 +$as_echo "** No OpenSSL libraries found, disabling SSL support **" >&6; } + fi + fi fi - # Check whether --with-opencore-amrnb was given. if test "${with_opencore_amrnb+set}" = set; then : withval=$with_opencore_amrnb; as_fn_error $? "This option is obsolete and replaced by --with-opencore-amr=DIR" "$LINENO" 5 diff -rupN pjproject-2.5.5/aconfigure.ac pjproject-2.5.5-new/aconfigure.ac --- pjproject-2.5.5/aconfigure.ac 2016-07-20 09:03:49.000000000 +0200 +++ pjproject-2.5.5-new/aconfigure.ac 2017-01-05 14:15:08.696523621 +0100 @@ -1566,38 +1566,77 @@ fi dnl # Include SSL support AC_SUBST(ac_no_ssl) -AC_ARG_ENABLE(ssl, - AS_HELP_STRING([--disable-ssl], - [Exclude SSL support the build (default: autodetect)]) - , - [ - if test "$enable_ssl" = "no"; then - [ac_no_ssl=1] - AC_MSG_RESULT([Checking if SSL support is disabled... yes]) - fi - ], - [ - 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" - LDFLAGS="$LDFLAGS -L$with_ssl/lib" - AC_MSG_RESULT([Using SSL prefix... $with_ssl]) +AC_ARG_ENABLE([ssl], + AS_HELP_STRING([--enable-ssl[=backend]], + [Select 'gnutls' or 'openssl' (default) to provide SSL support (autodetect)]), + [ if test "x$enableval" = "xgnutls"; then + [ssl_backend="gnutls"] + else + [ssl_backend="openssl"] + 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_library_init,[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]) - # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK - #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 **]) - fi - ]) + ]) + +if test "x$enable_ssl" = "xno"; then + [ac_no_ssl=1] + AC_MSG_RESULT([Checking if SSL support is disabled... yes]) +else + if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then + CFLAGS="$CFLAGS -I$with_ssl/include" + LDFLAGS="$LDFLAGS -L$with_ssl/lib" + AC_MSG_RESULT([Using SSL prefix... $with_ssl]) + fi + if test "x$ssl_backend" = "xgnutls"; then + 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]) + # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK + #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) + AC_DEFINE(PJ_HAS_SSL_SOCK, 1) + AC_DEFINE(PJ_HAS_TLS_SOCK, 1) + else + AC_MSG_RESULT([** No GnuTLS libraries found, disabling SSL support **]) + fi + else + 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_library_init,[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]) + # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK + #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) + AC_DEFINE(PJ_HAS_SSL_SOCK, 1) + AC_DEFINE(PJ_HAS_TLS_SOCK, 0) + else + AC_MSG_RESULT([** No OpenSSL libraries found, disabling SSL support **]) + fi + fi +fi dnl # Obsolete option --with-opencore-amrnb AC_ARG_WITH(opencore-amrnb, diff -rupN pjproject-2.5.5/pjlib/build/Makefile pjproject-2.5.5-new/pjlib/build/Makefile --- pjproject-2.5.5/pjlib/build/Makefile 2014-02-04 08:39:55.000000000 +0100 +++ pjproject-2.5.5-new/pjlib/build/Makefile 2017-01-05 14:15:08.696523621 +0100 @@ -35,7 +35,7 @@ export PJLIB_OBJS += $(OS_OBJS) $(M_OBJS guid.o hash.o ip_helper_generic.o list.o lock.o log.o os_time_common.o \ 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 sock_qos_bsd.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) export PJLIB_CXXFLAGS += $(_CXXFLAGS) diff -rupN pjproject-2.5.5/pjlib/include/pj/compat/os_auto.h.in pjproject-2.5.5-new/pjlib/include/pj/compat/os_auto.h.in --- pjproject-2.5.5/pjlib/include/pj/compat/os_auto.h.in 2016-02-25 05:54:17.000000000 +0100 +++ pjproject-2.5.5-new/pjlib/include/pj/compat/os_auto.h.in 2017-01-05 14:15:08.696523621 +0100 @@ -209,6 +209,9 @@ #ifndef PJ_HAS_SSL_SOCK #undef PJ_HAS_SSL_SOCK #endif +#ifndef PJ_HAS_TLS_SOCK +#undef PJ_HAS_TLS_SOCK +#endif #endif /* __PJ_COMPAT_OS_AUTO_H__ */ diff -rupN pjproject-2.5.5/pjlib/include/pj/config.h pjproject-2.5.5-new/pjlib/include/pj/config.h --- pjproject-2.5.5/pjlib/include/pj/config.h 2017-01-05 14:15:08.693523650 +0100 +++ pjproject-2.5.5-new/pjlib/include/pj/config.h 2017-01-05 14:15:08.697523611 +0100 @@ -861,13 +861,15 @@ /** * Enable secure socket. For most platforms, this is implemented using - * OpenSSL, so this will require OpenSSL to be installed. For Symbian + * OpenSSL, so this will require OpenSSL or GnuTLS to be installed. For Symbian * platform, this is implemented natively using CSecureSocket. * * Default: 0 (for now) */ #ifndef PJ_HAS_SSL_SOCK # define PJ_HAS_SSL_SOCK 0 + // When set to 1 secure sockets will use the GnuTLS backend +# define PJ_HAS_TLS_SOCK 0 #endif diff -rupN pjproject-2.5.5/pjlib/include/pj/ssl_sock.h pjproject-2.5.5-new/pjlib/include/pj/ssl_sock.h --- pjproject-2.5.5/pjlib/include/pj/ssl_sock.h 2016-01-27 10:55:13.000000000 +0100 +++ pjproject-2.5.5-new/pjlib/include/pj/ssl_sock.h 2017-01-05 14:15:08.697523611 +0100 @@ -184,6 +184,11 @@ typedef struct pj_ssl_cert_info { 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; diff -rupN pjproject-2.5.5/pjlib/src/pj/ssl_sock_common.c pjproject-2.5.5-new/pjlib/src/pj/ssl_sock_common.c --- pjproject-2.5.5/pjlib/src/pj/ssl_sock_common.c 2015-12-11 05:59:56.000000000 +0100 +++ pjproject-2.5.5-new/pjlib/src/pj/ssl_sock_common.c 2017-01-05 14:15:08.697523611 +0100 @@ -35,7 +35,12 @@ PJ_DEF(void) pj_ssl_sock_param_default(p param->async_cnt = 1; param->concurrency = -1; param->whole_data = PJ_TRUE; +#if defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 1 + // 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; #endif diff -rupN pjproject-2.5.5/pjlib/src/pj/ssl_sock_gtls.c pjproject-2.5.5-new/pjlib/src/pj/ssl_sock_gtls.c --- pjproject-2.5.5/pjlib/src/pj/ssl_sock_gtls.c 1970-01-01 01:00:00.000000000 +0100 +++ pjproject-2.5.5-new/pjlib/src/pj/ssl_sock_gtls.c 2017-01-05 14:15:08.698523601 +0100 @@ -0,0 +1,2887 @@ +/* $Id$ */ +/* + * Copyright (C) 2014-2016 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if GNUTLS_VERSION_NUMBER < 0x030306 && !defined(_MSC_VER) +#include +#endif + +#include + +/* Only build when PJ_HAS_SSL_SOCK and PJ_HAS_TLS_SOCK are enabled */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \ + defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK != 0 + +#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 +#include +#include + +#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 = 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 = {(const 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_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(¶m); + status = pj_ssl_sock_create(pool, ¶m, &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, ¶m->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 */ diff -rupN pjproject-2.5.5/pjlib/src/pj/ssl_sock_ossl.c pjproject-2.5.5-new/pjlib/src/pj/ssl_sock_ossl.c --- pjproject-2.5.5/pjlib/src/pj/ssl_sock_ossl.c 2016-06-29 08:23:20.000000000 +0200 +++ pjproject-2.5.5-new/pjlib/src/pj/ssl_sock_ossl.c 2017-01-05 14:15:08.698523601 +0100 @@ -32,8 +32,10 @@ #include -/* 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 PJ_HAS_TLS_SOCK is + * disabled (meaning GnuTLS is off) */ +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \ + defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 0 #define THIS_FILE "ssl_sock_ossl.c" diff -rupN pjproject-2.5.5/pjmedia/src/pjmedia/transport_srtp.c pjproject-2.5.5-new/pjmedia/src/pjmedia/transport_srtp.c --- pjproject-2.5.5/pjmedia/src/pjmedia/transport_srtp.c 2016-06-10 06:04:09.000000000 +0200 +++ pjproject-2.5.5-new/pjmedia/src/pjmedia/transport_srtp.c 2017-01-05 14:15:08.698523601 +0100 @@ -30,7 +30,8 @@ #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 && \ + defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 0 # include /* Suppress compile warning of OpenSSL deprecation (OpenSSL is deprecated @@ -1113,7 +1114,8 @@ static pj_status_t generate_crypto_attr_ key_ok = PJ_TRUE; -#if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) +#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \ + defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 0 /* Include OpenSSL libraries for MSVC */ # ifdef _MSC_VER