diff options
author | Mario Preksavec <mario at slackware dot hr> | 2017-08-09 01:00:33 +0100 |
---|---|---|
committer | Willy Sudiarto Raharjo <willysr@slackbuilds.org> | 2017-08-12 06:57:39 +0700 |
commit | 9d5aad10de6ae86ea2f5c4e5cb569e397fe30d0d (patch) | |
tree | fe6b846084e9d250390542ecf220b42b5fbdd5d9 /libraries/pjproject-ring | |
parent | 784db0a4db3ba05675e087a0740fd2bcd413d3b3 (diff) |
libraries/pjproject-ring: Added (Multimedia Communication Library).
Signed-off-by: David Spencer <idlemoor@slackbuilds.org>
Diffstat (limited to 'libraries/pjproject-ring')
24 files changed, 7290 insertions, 0 deletions
diff --git a/libraries/pjproject-ring/README b/libraries/pjproject-ring/README new file mode 100644 index 0000000000000..9507d7350de5f --- /dev/null +++ b/libraries/pjproject-ring/README @@ -0,0 +1,8 @@ +PJSIP is a free and open source multimedia communication library written +in C language implementing standard based protocols such as SIP, SDP, +RTP, STUN, TURN, and ICE. It combines signaling protocol (SIP) with rich +multimedia framework and NAT traversal functionality into high level API +that is portable and suitable for almost any type of systems ranging from +desktops, embedded systems, to mobile handsets. + +This package includes patches from Ring project. diff --git a/libraries/pjproject-ring/patches/add_dtls_transport.patch b/libraries/pjproject-ring/patches/add_dtls_transport.patch new file mode 100644 index 0000000000000..b7f9f296e7943 --- /dev/null +++ b/libraries/pjproject-ring/patches/add_dtls_transport.patch @@ -0,0 +1,63 @@ +--- a/pjsip/src/pjsip/sip_transport.c ++++ b/pjsip/src/pjsip/sip_transport.c +@@ -183,6 +183,13 @@ + PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE + }, + { ++ PJSIP_TRANSPORT_DTLS, ++ 5061, ++ {"DTLS", 4}, ++ "DTLS transport", ++ PJSIP_TRANSPORT_SECURE ++ }, ++ { + PJSIP_TRANSPORT_SCTP, + 5060, + {"SCTP", 4}, +@@ -224,6 +231,13 @@ + "TLS IPv6 transport", + PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE + }, ++ { ++ PJSIP_TRANSPORT_DTLS6, ++ 5061, ++ {"DTLS", 4}, ++ "DTLS IPv6 transport", ++ PJSIP_TRANSPORT_SECURE ++ }, + }; + + static void tp_state_callback(pjsip_transport *tp, +@@ -249,7 +263,7 @@ + */ + PJ_DEF(pj_status_t) pjsip_transport_register_type( unsigned tp_flag, + const char *tp_name, +- int def_port, ++ int def_port, + int *p_tp_type) + { + unsigned i; +--- a/pjsip/include/pjsip/sip_types.h ++++ b/pjsip/include/pjsip/sip_types.h +@@ -73,6 +73,9 @@ + /** TLS. */ + PJSIP_TRANSPORT_TLS, + ++ /** DTLS. */ ++ PJSIP_TRANSPORT_DTLS, ++ + /** SCTP. */ + PJSIP_TRANSPORT_SCTP, + +@@ -95,7 +98,10 @@ + PJSIP_TRANSPORT_TCP6 = PJSIP_TRANSPORT_TCP + PJSIP_TRANSPORT_IPV6, + + /** TLS over IPv6 */ +- PJSIP_TRANSPORT_TLS6 = PJSIP_TRANSPORT_TLS + PJSIP_TRANSPORT_IPV6 ++ PJSIP_TRANSPORT_TLS6 = PJSIP_TRANSPORT_TLS + PJSIP_TRANSPORT_IPV6, ++ ++ /** DTLS over IPv6 */ ++ PJSIP_TRANSPORT_DTLS6 = PJSIP_TRANSPORT_DTLS + PJSIP_TRANSPORT_IPV6 + + } pjsip_transport_type_e; + diff --git a/libraries/pjproject-ring/patches/android.patch b/libraries/pjproject-ring/patches/android.patch new file mode 100644 index 0000000000000..01621b9886f7f --- /dev/null +++ b/libraries/pjproject-ring/patches/android.patch @@ -0,0 +1,49 @@ +--- a/pjlib/include/pj/config.h ++++ b/pjlib/include/pj/config.h +@@ -293,6 +293,21 @@ + */ + #include <pj/config_site.h> + ++#undef PJ_ANDROID ++#define PJ_ANDROID 0 ++#define PJ_JNI_HAS_JNI_ONLOAD 0 ++#undef PJ_HAS_FLOATING_POINT ++#define PJ_HAS_FLOATING_POINT 0 ++#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 ++#define PJMEDIA_AUDIO_DEV_HAS_WMME 0 ++#define PJMEDIA_AUDIO_DEV_HAS_OPENSL 0 ++#define PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI 0 ++#define PJMEDIA_HAS_L16_CODEC 0 ++#define PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY 5 ++#define PJMEDIA_VID_DEV_INFO_FMT_CNT 128 ++#define PJSIP_MAX_TSX_COUNT 31 ++#define PJSIP_MAX_DIALOG_COUNT 31 ++ + /******************************************************************** + * PJLIB Features. + */ +--- a/aconfigure ++++ b/aconfigure +@@ -5809,6 +5809,3 @@ + case $target in +- *android*) +- ac_os_objs="$ac_os_objs guid_android.o" +- ;; + *) + if test "$ac_has_uuid_lib" = "1" -a "$ac_has_uuid_h" = "1"; then +--- a/aconfigure.ac ++++ b/aconfigure.ac +@@ -457,6 +457,3 @@ + case $target in +- *android*) +- ac_os_objs="$ac_os_objs guid_android.o" +- ;; + *) + if test "$ac_has_uuid_lib" = "1" -a "$ac_has_uuid_h" = "1"; then +--- a/pjlib/src/pj/os_timestamp_posix.c ++++ b/pjlib/src/pj/os_timestamp_posix.c +@@ -163,3 +163,3 @@ + +-#elif defined(__ANDROID__) ++#elif defined(PJ_ANDROID) && PJ_ANDROID +
\ No newline at end of file diff --git a/libraries/pjproject-ring/patches/endianness.patch b/libraries/pjproject-ring/patches/endianness.patch new file mode 100644 index 0000000000000..84b9499448f65 --- /dev/null +++ b/libraries/pjproject-ring/patches/endianness.patch @@ -0,0 +1,19 @@ +diff --git a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h +index 10f86fd..4ace1bc 100644 +--- a/pjlib/include/pj/config.h ++++ b/pjlib/include/pj/config.h +@@ -245,7 +245,13 @@ + # define PJ_M_NAME "armv4" + # define PJ_HAS_PENTIUM 0 + # if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN +-# error Endianness must be declared for this processor ++# if defined(__GNUC__) ++# include <endian.h> ++# define PJ_IS_LITTLE_ENDIAN __BYTE_ORDER__ == __LITTLE_ENDIAN__ ++# define PJ_IS_BIG_ENDIAN __BYTE_ORDER__ == __BIG_ENDIAN__ ++# else ++# error Endianness must be declared for this processor ++# endif + # endif + + #elif defined (PJ_M_POWERPC) || defined(__powerpc) || defined(__powerpc__) || \ diff --git a/libraries/pjproject-ring/patches/fix_base64.patch b/libraries/pjproject-ring/patches/fix_base64.patch new file mode 100644 index 0000000000000..70bdd5054df89 --- /dev/null +++ b/libraries/pjproject-ring/patches/fix_base64.patch @@ -0,0 +1,20 @@ +--- a/pjlib-util/src/pjlib-util/base64.c 2017-05-03 10:29:07.200417026 -0400 ++++ b/pjlib-util/src/pjlib-util/base64.c 2017-05-03 10:28:30.344335390 -0400 +@@ -131,7 +131,7 @@ + + PJ_ASSERT_RETURN(input && out && out_len, PJ_EINVAL); + +- while (buf[len-1] == '=' && len) ++ while (len && buf[len-1] == '=') + --len; + + PJ_ASSERT_RETURN(*out_len >= PJ_BASE64_TO_BASE256_LEN(len), +@@ -161,7 +161,7 @@ + out[j++] = (pj_uint8_t)(((c[2] & 0x03)<<6) | (c[3] & 0x3F)); + } + +- pj_assert(j < *out_len); ++ pj_assert(j <= *out_len); + *out_len = j; + + return PJ_SUCCESS; diff --git a/libraries/pjproject-ring/patches/fix_ioqueue_ipv6_sendto.patch b/libraries/pjproject-ring/patches/fix_ioqueue_ipv6_sendto.patch new file mode 100644 index 0000000000000..bc53fd2cb4a82 --- /dev/null +++ b/libraries/pjproject-ring/patches/fix_ioqueue_ipv6_sendto.patch @@ -0,0 +1,19 @@ +--- a/pjlib/src/pj/ioqueue_common_abs.c 2015-11-05 23:18:46.000000000 -0500 ++++ b/pjlib/src/pj/ioqueue_common_abs.c 2016-10-21 13:49:09.183662433 -0400 +@@ -1048,5 +1048,6 @@ + * Check that address storage can hold the address parameter. + */ +- PJ_ASSERT_RETURN(addrlen <= (int)sizeof(pj_sockaddr_in), PJ_EBUG); ++ PJ_ASSERT_RETURN((((pj_sockaddr*)addr)->addr.sa_family == pj_AF_INET() && addrlen <= (int)sizeof(pj_sockaddr_in)) || ++ (((pj_sockaddr*)addr)->addr.sa_family == pj_AF_INET6() && addrlen <= (int)sizeof(pj_sockaddr_in6)), PJ_EBUG); + + /* +--- a/pjlib/src/pj/ioqueue_common_abs.h 2013-02-21 06:18:36.000000000 -0500 ++++ b/pjlib/src/pj/ioqueue_common_abs.h 2016-10-21 14:04:04.148928591 -0400 +@@ -64,5 +64,5 @@ + pj_ssize_t written; + unsigned flags; +- pj_sockaddr_in rmt_addr; ++ pj_sockaddr rmt_addr; + int rmt_addrlen; + }; diff --git a/libraries/pjproject-ring/patches/fix_turn_fallback.patch b/libraries/pjproject-ring/patches/fix_turn_fallback.patch new file mode 100644 index 0000000000000..50229dd01ccf7 --- /dev/null +++ b/libraries/pjproject-ring/patches/fix_turn_fallback.patch @@ -0,0 +1,53 @@ +--- a/pjnath/src/pjnath/turn_session.c 2016-09-19 18:21:09.073614574 -0400 ++++ b/pjnath/src/pjnath/turn_session.c 2016-09-19 18:21:30.648631620 -0400 +@@ -653,3 +653,3 @@ + +- cnt = PJ_TURN_MAX_DNS_SRV_CNT; ++ cnt = 1; + ai = (pj_addrinfo*) +--- a/pjnath/src/pjnath/ice_strans.c 2016-09-19 18:36:04.180104330 -0400 ++++ b/pjnath/src/pjnath/ice_strans.c 2016-09-19 18:37:10.614136809 -0400 +@@ -1304,2 +1304,5 @@ + ++ if (!comp->turn[n].sock) ++ continue; ++ + /* Gather remote addresses for this component */ +@@ -1995,4 +1998,37 @@ + sess_init_update(comp->ice_st); + ++ } else if ((old_state == PJ_TURN_STATE_RESOLVING || old_state == PJ_TURN_STATE_ALLOCATING) && ++ new_state >= PJ_TURN_STATE_DEALLOCATING) ++ { ++ pj_ice_sess_cand *cand = NULL; ++ unsigned i; ++ ++ /* DNS resolution has failed! */ ++ ++comp->turn[tp_idx].err_cnt; ++ ++ /* Unregister ourself from the TURN relay */ ++ pj_turn_sock_set_user_data(turn_sock, NULL); ++ comp->turn[tp_idx].sock = NULL; ++ ++ /* Wait until initialization completes */ ++ pj_grp_lock_acquire(comp->ice_st->grp_lock); ++ ++ /* Find relayed candidate in the component */ ++ for (i=0; i<comp->cand_cnt; ++i) { ++ if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED && ++ comp->cand_list[i].transport_id == data->transport_id) ++ { ++ cand = &comp->cand_list[i]; ++ break; ++ } ++ } ++ pj_assert(cand != NULL); ++ ++ pj_grp_lock_release(comp->ice_st->grp_lock); ++ ++ cand->status = old_state == PJ_TURN_STATE_RESOLVING ? PJ_ERESOLVE : PJ_EINVALIDOP; ++ ++ sess_init_update(comp->ice_st); ++ + } else if (new_state >= PJ_TURN_STATE_DEALLOCATING) { + pj_turn_session_info info; diff --git a/libraries/pjproject-ring/patches/gnutls.patch b/libraries/pjproject-ring/patches/gnutls.patch new file mode 100644 index 0000000000000..e8b26657ea8a8 --- /dev/null +++ b/libraries/pjproject-ring/patches/gnutls.patch @@ -0,0 +1,3295 @@ +Copyright (c) 2014-2017 Savoir-faire Linux Inc. + +ssl_sock: add gnutls backend + +This backend is mutually exclusive with the OpenSSL one, but completely +compatible, and conformant to the PJSIP API. Also avoids any license issues +when linking statically. + +The configure script is updated to select either OpenSSL or GnuTLS +with --enable-ssl[='...'] and a new symbol (PJ_HAS_TLS_SOCK) is introduced +to identify which backend is in use. + +Written by +Vittorio Giovara <vittorio.giovara@savoirfairelinux.com> +Philippe Proulx <philippe.proulx@savoirfairelinux.com> and +Adrien BĂ©raud <adrien.beraud@savoirfairelinux.com> +on behalf of Savoir-faire Linux. + +--- +diff -ru a/aconfigure b/aconfigure +--- a/aconfigure 2017-01-25 06:23:08.000000000 -0500 ++++ b/aconfigure 2017-06-08 13:51:11.146810527 -0400 +@@ -644,6 +644,8 @@ + libcrypto_present + libssl_present + openssl_h_present ++libgnutls_present ++gnutls_h_present + ac_ssl_has_aes_gcm + ac_no_ssl + ac_openh264_ldflags +@@ -1482,8 +1484,8 @@ + 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) + +@@ -7787,17 +7789,149 @@ + + # 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. */ ++ ++/* 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" +@@ -7971,11 +8105,10 @@ + { $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 +- ++ 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 -ru a/aconfigure.ac b/aconfigure.ac +--- a/aconfigure.ac 2017-01-25 06:23:08.000000000 -0500 ++++ b/aconfigure.ac 2017-06-08 13:28:17.138135490 -0400 +@@ -1533,18 +1533,59 @@ + dnl # Include SSL support + AC_SUBST(ac_no_ssl) + AC_SUBST(ac_ssl_has_aes_gcm,0) +-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_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 ]) ++ ++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..]) ++ + if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then + CFLAGS="$CFLAGS -I$with_ssl/include" + LDFLAGS="$LDFLAGS -L$with_ssl/lib" +@@ -1578,7 +1619,8 @@ + else + AC_MSG_RESULT([** OpenSSL libraries not found, disabling SSL support **]) + fi +- ]) ++ fi ++fi + + dnl # Obsolete option --with-opencore-amrnb + AC_ARG_WITH(opencore-amrnb, +diff -ru a/pjlib/build/Makefile b/pjlib/build/Makefile +--- a/pjlib/build/Makefile 2016-10-05 05:52:39.000000000 -0400 ++++ b/pjlib/build/Makefile 2017-06-08 13:30:20.702138591 -0400 +@@ -35,7 +35,7 @@ + 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 \ +- 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 -ru a/pjlib/include/pj/compat/os_auto.h.in b/pjlib/include/pj/compat/os_auto.h.in +--- a/pjlib/include/pj/compat/os_auto.h.in 2017-01-24 00:36:50.000000000 -0500 ++++ b/pjlib/include/pj/compat/os_auto.h.in 2017-06-08 13:31:04.976064779 -0400 +@@ -219,6 +219,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 -ru a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h +--- a/pjlib/include/pj/config.h 2017-01-25 21:29:59.000000000 -0500 ++++ b/pjlib/include/pj/config.h 2017-06-08 13:34:27.642149351 -0400 +@@ -888,7 +888,7 @@ + + /** + * Enable secure socket. For most platforms, this is implemented using +- * OpenSSL, so this will require OpenSSL to be installed. For Symbian ++ * OpenSSL or GnuTLS, so this will require OpenSSL or GnuTLS to be installed. For Symbian + * platform, this is implemented natively using CSecureSocket. + * + * Default: 0 (for now) +@@ -896,6 +896,10 @@ + #ifndef PJ_HAS_SSL_SOCK + # define PJ_HAS_SSL_SOCK 0 + #endif ++// When set to 1 secure sockets will use the GnuTLS backend than OpenSSL ++#ifndef PJ_HAS_TLS_SOCK ++# define PJ_HAS_TLS_SOCK 0 ++#endif + + + /** +diff -ru a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h +--- a/pjlib/include/pj/ssl_sock.h 2016-10-27 03:58:01.000000000 -0400 ++++ b/pjlib/include/pj/ssl_sock.h 2017-06-08 13:36:16.448510381 -0400 +@@ -184,6 +184,10 @@ + 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 -ru a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c +--- a/pjlib/src/pj/ssl_sock_common.c 2016-10-27 03:58:01.000000000 -0400 ++++ b/pjlib/src/pj/ssl_sock_common.c 2017-06-08 13:37:17.171037628 -0400 +@@ -35,7 +35,12 @@ + 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 --git a/pjlib/src/pj/ssl_sock_gtls.c b/pjlib/src/pj/ssl_sock_gtls.c +new file mode 100644 +index 0000000..37bcaba +--- /dev/null ++++ b/pjlib/src/pj/ssl_sock_gtls.c +@@ -0,0 +1,2877 @@ ++/* $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 <pj/ssl_sock.h> ++#include <pj/activesock.h> ++#include <pj/compat/socket.h> ++#include <pj/assert.h> ++#include <pj/errno.h> ++#include <pj/list.h> ++#include <pj/lock.h> ++#include <pj/log.h> ++#include <pj/math.h> ++#include <pj/os.h> ++#include <pj/pool.h> ++#include <pj/string.h> ++#include <pj/timer.h> ++#include <pj/file_io.h> ++ ++#if GNUTLS_VERSION_NUMBER < 0x030306 && !defined(_MSC_VER) ++#include <dirent.h> ++#endif ++ ++#include <errno.h> ++ ++/* Only build when PJ_HAS_SSL_SOCK and 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 <gnutls/gnutls.h> ++#include <gnutls/x509.h> ++#include <gnutls/abstract.h> ++ ++#ifdef _MSC_VER ++# pragma comment( lib, "libgnutls") ++#endif ++ ++ ++/* TLS state enumeration. */ ++enum tls_connection_state { ++ TLS_STATE_NULL, ++ TLS_STATE_HANDSHAKING, ++ TLS_STATE_ESTABLISHED ++}; ++ ++/* Internal timer types. */ ++enum timer_id { ++ TIMER_NONE, ++ TIMER_HANDSHAKE_TIMEOUT, ++ TIMER_CLOSE ++}; ++ ++/* Structure of SSL socket read buffer. */ ++typedef struct read_data_t { ++ void *data; ++ pj_size_t len; ++} read_data_t; ++ ++/* ++ * Get the offset of pointer to read-buffer of SSL socket from read-buffer ++ * of active socket. Note that both SSL socket and active socket employ ++ * different but correlated read-buffers (as much as async_cnt for each), ++ * and to make it easier/faster to find corresponding SSL socket's read-buffer ++ * from known active socket's read-buffer, the pointer of corresponding ++ * SSL socket's read-buffer is stored right after the end of active socket's ++ * read-buffer. ++ */ ++#define OFFSET_OF_READ_DATA_PTR(ssock, asock_rbuf) \ ++ (read_data_t**) \ ++ ((pj_int8_t *)(asock_rbuf) + \ ++ ssock->param.read_buffer_size) ++ ++/* Structure of SSL socket write data. */ ++typedef struct write_data_t { ++ PJ_DECL_LIST_MEMBER(struct write_data_t); ++ pj_ioqueue_op_key_t key; ++ pj_size_t record_len; ++ pj_ioqueue_op_key_t *app_key; ++ pj_size_t plain_data_len; ++ pj_size_t data_len; ++ unsigned flags; ++ union { ++ char content[1]; ++ const char *ptr; ++ } data; ++} write_data_t; ++ ++ ++/* Structure of SSL socket write buffer (circular buffer). */ ++typedef struct send_buf_t { ++ char *buf; ++ pj_size_t max_len; ++ char *start; ++ pj_size_t len; ++} send_buf_t; ++ ++ ++/* Circular buffer object */ ++typedef struct circ_buf_t { ++ pj_size_t cap; /* maximum number of elements (must be power of 2) */ ++ pj_size_t readp; /* index of oldest element */ ++ pj_size_t writep; /* index at which to write new element */ ++ pj_size_t size; /* number of elements */ ++ pj_uint8_t *buf; /* data buffer */ ++ pj_pool_t *pool; /* where new allocations will take place */ ++} circ_buf_t; ++ ++ ++/* Secure socket structure definition. */ ++struct pj_ssl_sock_t { ++ pj_pool_t *pool; ++ pj_ssl_sock_t *parent; ++ pj_ssl_sock_param param; ++ pj_ssl_sock_param newsock_param; ++ pj_ssl_cert_t *cert; ++ ++ pj_ssl_cert_info local_cert_info; ++ pj_ssl_cert_info remote_cert_info; ++ ++ pj_bool_t is_server; ++ enum tls_connection_state connection_state; ++ pj_ioqueue_op_key_t handshake_op_key; ++ pj_timer_entry timer; ++ pj_status_t verify_status; ++ ++ int last_err; ++ ++ pj_sock_t sock; ++ pj_activesock_t *asock; ++ ++ pj_sockaddr local_addr; ++ pj_sockaddr rem_addr; ++ int addr_len; ++ ++ pj_bool_t read_started; ++ pj_size_t read_size; ++ pj_uint32_t read_flags; ++ void **asock_rbuf; ++ read_data_t *ssock_rbuf; ++ ++ write_data_t write_pending; /* list of pending writes */ ++ write_data_t write_pending_empty; /* cache for write_pending */ ++ pj_bool_t flushing_write_pend; /* flag of flushing is ongoing */ ++ send_buf_t send_buf; ++ write_data_t send_pending; /* list of pending write to network */ ++ ++ gnutls_session_t session; ++ gnutls_certificate_credentials_t xcred; ++ ++ circ_buf_t circ_buf_input; ++ pj_lock_t *circ_buf_input_mutex; ++ ++ circ_buf_t circ_buf_output; ++ pj_lock_t *circ_buf_output_mutex; ++ ++ int tls_init_count; /* library initialization counter */ ++}; ++ ++ ++/* Certificate/credential structure definition. */ ++struct pj_ssl_cert_t { ++ pj_str_t CA_file; ++ pj_str_t CA_path; ++ pj_str_t cert_file; ++ pj_str_t privkey_file; ++ pj_str_t privkey_pass; ++}; ++ ++/* GnuTLS available ciphers */ ++static unsigned tls_available_ciphers; ++ ++/* Array of id/names for available ciphers */ ++static struct tls_ciphers_t { ++ pj_ssl_cipher id; ++ const char *name; ++} tls_ciphers[MAX_CIPHERS]; ++ ++/* Last error reported somehow */ ++static int tls_last_error; ++ ++ ++/* ++ ******************************************************************* ++ * Circular buffer functions. ++ ******************************************************************* ++ */ ++ ++static pj_status_t circ_init(pj_pool_factory *factory, ++ circ_buf_t *cb, pj_size_t cap) ++{ ++ cb->cap = cap; ++ cb->readp = 0; ++ cb->writep = 0; ++ cb->size = 0; ++ ++ /* Initial pool holding the buffer elements */ ++ cb->pool = pj_pool_create(factory, "tls-circ%p", cap, cap, NULL); ++ if (!cb->pool) ++ return PJ_ENOMEM; ++ ++ /* Allocate circular buffer */ ++ cb->buf = pj_pool_alloc(cb->pool, cap); ++ if (!cb->buf) { ++ pj_pool_release(cb->pool); ++ return PJ_ENOMEM; ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++static void circ_deinit(circ_buf_t *cb) ++{ ++ if (cb->pool) { ++ pj_pool_release(cb->pool); ++ cb->pool = NULL; ++ } ++} ++ ++static pj_bool_t circ_empty(const circ_buf_t *cb) ++{ ++ return cb->size == 0; ++} ++ ++static pj_size_t circ_size(const circ_buf_t *cb) ++{ ++ return cb->size; ++} ++ ++static pj_size_t circ_avail(const circ_buf_t *cb) ++{ ++ return cb->cap - cb->size; ++} ++ ++static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) ++{ ++ pj_size_t size_after = cb->cap - cb->readp; ++ pj_size_t tbc = PJ_MIN(size_after, len); ++ pj_size_t rem = len - tbc; ++ ++ pj_memcpy(dst, cb->buf + cb->readp, tbc); ++ pj_memcpy(dst + tbc, cb->buf, rem); ++ ++ cb->readp += len; ++ cb->readp &= (cb->cap - 1); ++ ++ cb->size -= len; ++} ++ ++static pj_status_t circ_write(circ_buf_t *cb, ++ const pj_uint8_t *src, pj_size_t len) ++{ ++ /* Overflow condition: resize */ ++ if (len > circ_avail(cb)) { ++ /* Minimum required capacity */ ++ pj_size_t min_cap = len + cb->size; ++ ++ /* Next 32-bit power of two */ ++ min_cap--; ++ min_cap |= min_cap >> 1; ++ min_cap |= min_cap >> 2; ++ min_cap |= min_cap >> 4; ++ min_cap |= min_cap >> 8; ++ min_cap |= min_cap >> 16; ++ min_cap++; ++ ++ /* Create a new pool to hold a bigger buffer, using the same factory */ ++ pj_pool_t *pool = pj_pool_create(cb->pool->factory, "tls-circ%p", ++ min_cap, min_cap, NULL); ++ if (!pool) ++ return PJ_ENOMEM; ++ ++ /* Allocate our new buffer */ ++ pj_uint8_t *buf = pj_pool_alloc(pool, min_cap); ++ if (!buf) { ++ pj_pool_release(pool); ++ return PJ_ENOMEM; ++ } ++ ++ /* Save old size, which we shall restore after the next read */ ++ pj_size_t old_size = cb->size; ++ ++ /* Copy old data into beginning of new buffer */ ++ circ_read(cb, buf, cb->size); ++ ++ /* Restore old size now */ ++ cb->size = old_size; ++ ++ /* Release the previous pool */ ++ pj_pool_release(cb->pool); ++ ++ /* Update circular buffer members */ ++ cb->pool = pool; ++ cb->buf = buf; ++ cb->readp = 0; ++ cb->writep = cb->size; ++ cb->cap = min_cap; ++ } ++ ++ pj_size_t size_after = cb->cap - cb->writep; ++ pj_size_t tbc = PJ_MIN(size_after, len); ++ pj_size_t rem = len - tbc; ++ ++ pj_memcpy(cb->buf + cb->writep, src, tbc); ++ pj_memcpy(cb->buf, src + tbc, rem); ++ ++ cb->writep += len; ++ cb->writep &= (cb->cap - 1); ++ ++ cb->size += len; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* ++ ******************************************************************* ++ * Static/internal functions. ++ ******************************************************************* ++ */ ++ ++/* Convert from GnuTLS error to pj_status_t. */ ++static pj_status_t tls_status_from_err(pj_ssl_sock_t *ssock, int err) ++{ ++ pj_status_t status; ++ ++ switch (err) { ++ case GNUTLS_E_SUCCESS: ++ status = PJ_SUCCESS; ++ break; ++ case GNUTLS_E_MEMORY_ERROR: ++ status = PJ_ENOMEM; ++ break; ++ case GNUTLS_E_LARGE_PACKET: ++ status = PJ_ETOOBIG; ++ break; ++ case GNUTLS_E_NO_CERTIFICATE_FOUND: ++ status = PJ_ENOTFOUND; ++ break; ++ case GNUTLS_E_SESSION_EOF: ++ status = PJ_EEOF; ++ break; ++ case GNUTLS_E_HANDSHAKE_TOO_LARGE: ++ status = PJ_ETOOBIG; ++ break; ++ case GNUTLS_E_EXPIRED: ++ status = PJ_EGONE; ++ break; ++ case GNUTLS_E_TIMEDOUT: ++ status = PJ_ETIMEDOUT; ++ break; ++ case GNUTLS_E_PREMATURE_TERMINATION: ++ status = PJ_ECANCELLED; ++ break; ++ case GNUTLS_E_INTERNAL_ERROR: ++ case GNUTLS_E_UNIMPLEMENTED_FEATURE: ++ status = PJ_EBUG; ++ break; ++ case GNUTLS_E_AGAIN: ++ case GNUTLS_E_INTERRUPTED: ++ case GNUTLS_E_REHANDSHAKE: ++ status = PJ_EPENDING; ++ break; ++ case GNUTLS_E_TOO_MANY_EMPTY_PACKETS: ++ case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS: ++ case GNUTLS_E_RECORD_LIMIT_REACHED: ++ status = PJ_ETOOMANY; ++ break; ++ case GNUTLS_E_UNSUPPORTED_VERSION_PACKET: ++ case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM: ++ case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE: ++ case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE: ++ case GNUTLS_E_X509_UNSUPPORTED_EXTENSION: ++ case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION: ++ status = PJ_ENOTSUP; ++ break; ++ case GNUTLS_E_INVALID_SESSION: ++ case GNUTLS_E_INVALID_REQUEST: ++ case GNUTLS_E_INVALID_PASSWORD: ++ case GNUTLS_E_ILLEGAL_PARAMETER: ++ case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION: ++ case GNUTLS_E_UNEXPECTED_PACKET: ++ case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: ++ case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET: ++ case GNUTLS_E_UNWANTED_ALGORITHM: ++ case GNUTLS_E_USER_ERROR: ++ status = PJ_EINVAL; ++ break; ++ default: ++ status = PJ_EUNKNOWN; ++ break; ++ } ++ ++ /* Not thread safe */ ++ tls_last_error = err; ++ if (ssock) ++ ssock->last_err = err; ++ return status; ++} ++ ++ ++/* Get error string from GnuTLS using tls_last_error */ ++static pj_str_t tls_strerror(pj_status_t status, ++ char *buf, pj_size_t bufsize) ++{ ++ pj_str_t errstr; ++ const char *tmp = gnutls_strerror(tls_last_error); ++ ++#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) ++ if (tmp) { ++ pj_ansi_strncpy(buf, tmp, bufsize); ++ errstr = pj_str(buf); ++ return errstr; ++ } ++#endif /* PJ_HAS_ERROR_STRING */ ++ ++ errstr.ptr = buf; ++ errstr.slen = pj_ansi_snprintf(buf, bufsize, "GnuTLS error %d: %s", ++ tls_last_error, tmp); ++ if (errstr.slen < 1 || errstr.slen >= (int) bufsize) ++ errstr.slen = bufsize - 1; ++ ++ return errstr; ++} ++ ++ ++/* 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); ++ ++ /* 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/rand.h> ++pj_status_t pj_ssl_sock_ossl_test_send_buf(pj_pool_t *pool) ++{ ++ enum { MAX_CHUNK_NUM = 20 }; ++ unsigned chunk_size, chunk_cnt, i; ++ write_data_t *wdata[MAX_CHUNK_NUM] = {0}; ++ pj_time_val now; ++ pj_ssl_sock_t *ssock = NULL; ++ pj_ssl_sock_param param; ++ pj_status_t status; ++ ++ pj_gettimeofday(&now); ++ pj_srand((unsigned)now.sec); ++ ++ pj_ssl_sock_param_default(¶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 -ru a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c +--- a/pjlib/src/pj/ssl_sock_ossl.c 2017-01-24 00:41:05.000000000 -0500 ++++ b/pjlib/src/pj/ssl_sock_ossl.c 2017-06-08 13:42:15.188809557 -0400 +@@ -32,8 +32,10 @@ + #include <pj/timer.h> + + +-/* 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 -ru a/pjmedia/src/pjmedia/transport_srtp.c b/pjmedia/src/pjmedia/transport_srtp.c +--- a/pjmedia/src/pjmedia/transport_srtp.c 2017-01-10 23:38:29.000000000 -0500 ++++ b/pjmedia/src/pjmedia/transport_srtp.c 2017-06-08 13:43:29.727001721 -0400 +@@ -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 <openssl/rand.h> + + /* Suppress compile warning of OpenSSL deprecation (OpenSSL is deprecated +@@ -1147,7 +1148,8 @@ + 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 + diff --git a/libraries/pjproject-ring/patches/ice_config.patch b/libraries/pjproject-ring/patches/ice_config.patch new file mode 100644 index 0000000000000..0bc2e432927a7 --- /dev/null +++ b/libraries/pjproject-ring/patches/ice_config.patch @@ -0,0 +1,27 @@ +--- a/pjnath/include/pjnath/config.h ++++ b/pjnath/include/pjnath/config.h +@@ -233,3 +233,3 @@ + #ifndef PJ_ICE_MAX_CAND +-# define PJ_ICE_MAX_CAND 16 ++# define PJ_ICE_MAX_CAND 256 + #endif +@@ -243,3 +243,3 @@ + #ifndef PJ_ICE_ST_MAX_CAND +-# define PJ_ICE_ST_MAX_CAND 8 ++# define PJ_ICE_ST_MAX_CAND 32 + #endif +@@ -254,3 +254,3 @@ + #ifndef PJ_ICE_MAX_STUN +-# define PJ_ICE_MAX_STUN 2 ++# define PJ_ICE_MAX_STUN 3 + #endif +@@ -274,3 +274,3 @@ + #ifndef PJ_ICE_COMP_BITS +-# define PJ_ICE_COMP_BITS 1 ++# define PJ_ICE_COMP_BITS 2 + #endif +@@ -325,3 +325,3 @@ + #ifndef PJ_ICE_MAX_CHECKS +-# define PJ_ICE_MAX_CHECKS 32 ++# define PJ_ICE_MAX_CHECKS 150 + #endif diff --git a/libraries/pjproject-ring/patches/intptr_t.patch b/libraries/pjproject-ring/patches/intptr_t.patch new file mode 100644 index 0000000000000..8994a991c08aa --- /dev/null +++ b/libraries/pjproject-ring/patches/intptr_t.patch @@ -0,0 +1,11 @@ +--- pjproject/pjsip/src/pjsua2/endpoint.cpp.orig 2014-09-05 16:39:29.708512865 -0400 ++++ pjproject/pjsip/src/pjsua2/endpoint.cpp 2014-09-05 16:39:00.084513427 -0400 +@@ -489,7 +489,7 @@ + LogEntry entry; + entry.level = level; + entry.msg = string(data, len); +- entry.threadId = (long)pj_thread_this(); ++ entry.threadId = (intptr_t)pj_thread_this(); + entry.threadName = string(pj_thread_get_name(pj_thread_this())); + + ep.utilLogWrite(entry); diff --git a/libraries/pjproject-ring/patches/ipv6.patch b/libraries/pjproject-ring/patches/ipv6.patch new file mode 100644 index 0000000000000..8b42fbbda1bd8 --- /dev/null +++ b/libraries/pjproject-ring/patches/ipv6.patch @@ -0,0 +1,11 @@ +--- a/pjlib/include/pj/config.h ++++ b/pjlib/include/pj/config.h +@@ -549,7 +549,7 @@ + * Default: 0 (disabled, for now) + */ + #ifndef PJ_HAS_IPV6 +-# define PJ_HAS_IPV6 0 ++# define PJ_HAS_IPV6 1 + #endif + + /** diff --git a/libraries/pjproject-ring/patches/isblank.patch b/libraries/pjproject-ring/patches/isblank.patch new file mode 100644 index 0000000000000..98f6f1e827e69 --- /dev/null +++ b/libraries/pjproject-ring/patches/isblank.patch @@ -0,0 +1,8 @@ +--- pjproject/pjlib/include/pj/compat/ctype.h 2016-10-06 16:39:29.708512865 -0400 ++++ pjproject/pjlib/include/pj/compat/ctype.h 2016-10-06 17:39:00.084513427 -0400 +@@ -43,5 +43,2 @@ + +-#ifndef isblank +-# define isblank(c) (c==' ' || c=='\t') +-#endif + diff --git a/libraries/pjproject-ring/patches/multiple_listeners.patch b/libraries/pjproject-ring/patches/multiple_listeners.patch new file mode 100644 index 0000000000000..89590107c889e --- /dev/null +++ b/libraries/pjproject-ring/patches/multiple_listeners.patch @@ -0,0 +1,66 @@ +diff --git a/pjproject/pjsip/src/pjsip/sip_transport.c b/pjproject_new/pjsip/src/pjsip/sip_transport.c +index 4b2cc1a..22e3603 100644 +--- a/pjsip/src/pjsip/sip_transport.c ++++ b/pjsip/src/pjsip/sip_transport.c +@@ -1248,22 +1248,22 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_register_tpfactory( pjsip_tpmgr *mgr, + + pj_lock_acquire(mgr->lock); + +- /* Check that no factory with the same type has been registered. */ ++ /* Check that no factory with the same type and bound address has been registered. */ + status = PJ_SUCCESS; + for (p=mgr->factory_list.next; p!=&mgr->factory_list; p=p->next) { +- if (p->type == tpf->type) { +- status = PJSIP_ETYPEEXISTS; +- break; +- } +- if (p == tpf) { +- status = PJ_EEXISTS; +- break; +- } ++ if (p->type == tpf->type && !pj_sockaddr_cmp(&tpf->local_addr, &p->local_addr)) { ++ status = PJSIP_ETYPEEXISTS; ++ break; ++ } ++ if (p == tpf) { ++ status = PJ_EEXISTS; ++ break; ++ } + } + + if (status != PJ_SUCCESS) { +- pj_lock_release(mgr->lock); +- return status; ++ pj_lock_release(mgr->lock); ++ return status; + } + + pj_list_insert_before(&mgr->factory_list, tpf); +@@ -2047,13 +2047,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + pj_memcpy(&key.rem_addr, remote, addr_len); + + transport = (pjsip_transport*) +- pj_hash_get(mgr->table, &key, key_len, NULL); +- ++ pj_hash_get(mgr->table, &key, key_len, NULL); ++ unsigned flag = pjsip_transport_get_flag_from_type(type); + if (transport == NULL) { +- unsigned flag = pjsip_transport_get_flag_from_type(type); + const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote; + +- + /* Ignore address for loop transports. */ + if (type == PJSIP_TRANSPORT_LOOP || + type == PJSIP_TRANSPORT_LOOP_DGRAM) +@@ -2135,6 +2135,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + } + + } else { ++ /* Make sure we don't use another factory than the one given if secure flag is set */ ++ if (flag & PJSIP_TRANSPORT_SECURE) { ++ TRACE_((THIS_FILE, "Can't create new TLS transport with no provided suitable TLS listener.")); ++ return PJSIP_ETPNOTSUITABLE; ++ } + + /* Find factory with type matches the destination type */ + factory = mgr->factory_list.next; diff --git a/libraries/pjproject-ring/patches/notestsapps.patch b/libraries/pjproject-ring/patches/notestsapps.patch new file mode 100644 index 0000000000000..8bf957b3680a4 --- /dev/null +++ b/libraries/pjproject-ring/patches/notestsapps.patch @@ -0,0 +1,106 @@ +diff --git a/Makefile b/Makefile +index 33a4e6b..a486eb7 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ include build/host-$(HOST_NAME).mak + include version.mak + + LIB_DIRS = pjlib/build pjlib-util/build pjnath/build third_party/build pjmedia/build pjsip/build +-DIRS = $(LIB_DIRS) pjsip-apps/build $(EXTRA_DIRS) ++DIRS = $(LIB_DIRS) $(EXTRA_DIRS) + + ifdef MINSIZE + MAKE_FLAGS := MINSIZE=1 +diff --git a/pjlib-util/build/Makefile b/pjlib-util/build/Makefile +index cb601cb..862a78a 100644 +--- a/pjlib-util/build/Makefile ++++ b/pjlib-util/build/Makefile +@@ -54,7 +54,6 @@ export UTIL_TEST_OBJS += xml.o encryption.o stun.o resolver_test.o test.o \ + export UTIL_TEST_CFLAGS += $(_CFLAGS) + export UTIL_TEST_CXXFLAGS += $(_CXXFLAGS) + export UTIL_TEST_LDFLAGS += $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS) +-export UTIL_TEST_EXE:=pjlib-util-test-$(TARGET_NAME)$(HOST_EXE) + + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT +diff --git a/pjlib/build/Makefile b/pjlib/build/Makefile +index 1e64950..a75fa65 100644 +--- a/pjlib/build/Makefile ++++ b/pjlib/build/Makefile +@@ -56,7 +56,6 @@ export TEST_OBJS += activesock.o atomic.o echo_clt.o errno.o exception.o \ + export TEST_CFLAGS += $(_CFLAGS) + export TEST_CXXFLAGS += $(_CXXFLAGS) + export TEST_LDFLAGS += $(PJLIB_LDLIB) $(_LDFLAGS) +-export TEST_EXE := pjlib-test-$(TARGET_NAME)$(HOST_EXE) + + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT +diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile +index 8012cb7..2ca283a 100644 +--- a/pjmedia/build/Makefile ++++ b/pjmedia/build/Makefile +@@ -165,7 +165,6 @@ export PJMEDIA_TEST_LDFLAGS += $(PJMEDIA_CODEC_LDLIB) \ + $(PJLIB_UTIL_LDLIB) \ + $(PJNATH_LDLIB) \ + $(_LDFLAGS) +-export PJMEDIA_TEST_EXE:=pjmedia-test-$(TARGET_NAME)$(HOST_EXE) + + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT +diff --git a/pjnath/build/Makefile b/pjnath/build/Makefile +index 1bc08b5..109f79b 100644 +--- a/pjnath/build/Makefile ++++ b/pjnath/build/Makefile +@@ -54,7 +54,6 @@ export PJNATH_TEST_OBJS += ice_test.o stun.o sess_auth.o server.o concur_test.o + export PJNATH_TEST_CFLAGS += $(_CFLAGS) + export PJNATH_TEST_CXXFLAGS += $(_CXXFLAGS) + export PJNATH_TEST_LDFLAGS += $(PJNATH_LDLIB) $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS) +-export PJNATH_TEST_EXE:=pjnath-test-$(TARGET_NAME)$(HOST_EXE) + + + ############################################################################### +@@ -65,7 +64,6 @@ export PJTURN_CLIENT_OBJS += client_main.o + export PJTURN_CLIENT_CFLAGS += $(_CFLAGS) + export PJTURN_CLIENT_CXXFLAGS += $(_CXXFLAGS) + export PJTURN_CLIENT_LDFLAGS += $(PJNATH_LDLIB) $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS) +-export PJTURN_CLIENT_EXE:=pjturn-client-$(TARGET_NAME)$(HOST_EXE) + + ############################################################################### + # Defines for building TURN server application +@@ -76,7 +74,6 @@ export PJTURN_SRV_OBJS += allocation.o auth.o listener_udp.o \ + export PJTURN_SRV_CFLAGS += $(_CFLAGS) + export PJTURN_SRV_CXXFLAGS += $(_CXXFLAGS) + export PJTURN_SRV_LDFLAGS += $(PJNATH_LDLIB) $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS) +-export PJTURN_SRV_EXE:=pjturn-srv-$(TARGET_NAME)$(HOST_EXE) + + + +diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile +index d2a5c2a..7e2ec60 100644 +--- a/pjsip/build/Makefile ++++ b/pjsip/build/Makefile +@@ -140,7 +140,7 @@ export PJSUA2_LIB_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ + account.o endpoint.o json.o persistent.o types.o \ + siptypes.o call.o presence.o media.o + export PJSUA2_LIB_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS) +-export PJSUA2_LIB_CXXFLAGS = $(PJSUA2_LIB_CFLAGS) ++export PJSUA2_LIB_CXXFLAGS = $(_CXXFLAGS) $(PJ_VIDEO_CFLAGS) + export PJSUA2_LIB_LDFLAGS += $(PJSUA_LIB_LDLIB) \ + $(PJSIP_UA_LDLIB) \ + $(PJSIP_SIMPLE_LDLIB) \ +@@ -165,7 +165,6 @@ export PJSUA2_TEST_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ + export PJSUA2_TEST_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS) + export PJSUA2_TEST_CXXFLAGS = $(PJSUA2_LIB_CFLAGS) + export PJSUA2_TEST_LDFLAGS += $(PJ_LDXXFLAGS) $(PJ_LDXXLIBS) $(LDFLAGS) +-export PJSUA2_TEST_EXE := pjsua2-test-$(TARGET_NAME)$(HOST_EXE) + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT + +@@ -195,7 +194,6 @@ export TEST_LDFLAGS += $(PJSIP_LDLIB) \ + $(PJLIB_UTIL_LDLIB) \ + $(PJNATH_LDLIB) \ + $(_LDFLAGS) +-export TEST_EXE := pjsip-test-$(TARGET_NAME)$(HOST_EXE) + + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT diff --git a/libraries/pjproject-ring/patches/pj_ice_sess.patch b/libraries/pjproject-ring/patches/pj_ice_sess.patch new file mode 100644 index 0000000000000..bd040ffe3e87d --- /dev/null +++ b/libraries/pjproject-ring/patches/pj_ice_sess.patch @@ -0,0 +1,22 @@ +--- a/pjnath/include/pjnath/ice_strans.h ++++ b/pjnath/include/pjnath/ice_strans.h +@@ -845,6 +845,8 @@ PJ_DECL(pj_status_t) pj_ice_strans_sendt + int dst_addr_len); + + ++PJ_DECL(pj_ice_sess *) pj_ice_strans_get_ice_sess(pj_ice_strans *ice_st); ++ + /** + * @} + */ +--- a/pjnath/src/pjnath/ice_strans.c ++++ b/pjnath/src/pjnath/ice_strans.c +@@ -1243,6 +1243,11 @@ PJ_DEF(pj_status_t) pj_ice_strans_sendto + return PJ_EINVALIDOP; + } + ++PJ_DECL(pj_ice_sess *) pj_ice_strans_get_ice_sess( pj_ice_strans *ice_st ) ++{ ++ return ice_st->ice; ++} ++ diff --git a/libraries/pjproject-ring/patches/pj_uwp.patch b/libraries/pjproject-ring/patches/pj_uwp.patch new file mode 100644 index 0000000000000..2e7f0382667c4 --- /dev/null +++ b/libraries/pjproject-ring/patches/pj_uwp.patch @@ -0,0 +1,203 @@ +--- a/pjlib-util/build/pjlib_util.vcxproj
++++ b/pjlib-util/build/pjlib_util.vcxproj
+@@ -327,7 +327,7 @@
+ </Midl>
+ <ClCompile>
+ <AdditionalIncludeDirectories>../include;../../pjlib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+- <PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
++ <PreprocessorDefinitions>PJ_OS_HAS_CHECK_STACK=0;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderOutputFile />
+ </ClCompile>
+ <Lib>
+--- a/pjlib/build/pjlib.vcxproj
++++ b/pjlib/build/pjlib.vcxproj
+@@ -327,8 +327,8 @@
+ <TargetEnvironment>X64</TargetEnvironment>
+ </Midl>
+ <ClCompile>
+- <AdditionalIncludeDirectories>../include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+- <PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
++ <AdditionalIncludeDirectories>..\..\..\include;../include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
++ <PreprocessorDefinitions>_WIN32_WINNT=0x0A00;PJ_TERM_HAS_COLOR=0;PJ_OS_HAS_CHECK_STACK=0;WIN32_NATIVE;PJ_HAS_SSL_SOCK;PJ_HAS_TLS_SOCK;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderOutputFile />
+ </ClCompile>
+ </ItemDefinitionGroup>
+@@ -620,6 +620,7 @@
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+ </ClCompile>
+ <ClCompile Include="..\src\pj\ip_helper_win32.c">
++ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(API_Family)'!='WinDesktop'">true</ExcludedFromBuild>
+ </ClCompile>
+ <ClCompile Include="..\src\pj\ip_helper_generic.c">
+@@ -851,6 +852,7 @@
+ <ClCompile Include="..\src\pj\sock_select.c" />
+ <ClCompile Include="..\src\pj\ssl_sock_common.c" />
+ <ClCompile Include="..\src\pj\ssl_sock_dump.c" />
++ <ClCompile Include="..\src\pj\ssl_sock_gtls.c" />
+ <ClCompile Include="..\src\pj\ssl_sock_ossl.c" />
+ <ClCompile Include="..\src\pj\string.c" />
+ <ClCompile Include="..\src\pj\symbols.c">
+--- a/pjlib/include/pj/compat/string.h
++++ b/pjlib/include/pj/compat/string.h
+@@ -43,7 +43,7 @@
+ # include <stdlib.h>
+ #endif
+
+-#if defined(_MSC_VER)
++#if defined(PJ_WIN32)
+ # define strcasecmp _stricmp
+ # define strncasecmp _strnicmp
+ # define snprintf _snprintf
+--- /dev/null
++++ b/pjlib/include/pj/config_site.h
+@@ -0,0 +1,8 @@
++#include "config_site_sample.h"
++
++#undef PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
++#undef PJMEDIA_AUDIO_DEV_HAS_WMME
++
++#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0
++#define PJMEDIA_AUDIO_DEV_HAS_WMME 0
++#define PJMEDIA_RESAMPLE_IMP PJMEDIA_RESAMPLE_SPEEX
+\ No newline at end of file
+--- a/pjlib/src/pj/file_access_win32.c
++++ b/pjlib/src/pj/file_access_win32.c
+@@ -79,7 +79,7 @@ static HANDLE WINAPI create_file(LPCTSTR filename, DWORD desired_access,
+ return CreateFile2(filename, desired_access, share_mode,
+ creation_disposition, NULL);
+ #else
+- return CreateFile(filename, desired_access, share_mode,
++ return CreateFile2(filename, desired_access, share_mode,
+ security_attributes, creation_disposition,
+ flags_and_attributes, template_file);
+ #endif
+--- a/pjlib/src/pj/file_io_win32.c
++++ b/pjlib/src/pj/file_io_win32.c
+@@ -129,7 +129,7 @@ PJ_DEF(pj_status_t) pj_file_open( pj_pool_t *pool,
+ dwDesiredAccess, dwShareMode, dwCreationDisposition,
+ NULL);
+ #else
+- hFile = CreateFile(PJ_STRING_TO_NATIVE(pathname,
++ hFile = CreateFile2(PJ_STRING_TO_NATIVE(pathname,
+ wpathname, sizeof(wpathname)),
+ dwDesiredAccess, dwShareMode, NULL,
+ dwCreationDisposition, dwFlagsAndAttributes, NULL);
+--- a/pjmedia/build/pjmedia.vcxproj
++++ b/pjmedia/build/pjmedia.vcxproj
+@@ -325,7 +325,7 @@
+ </Midl>
+ <ClCompile>
+ <AdditionalIncludeDirectories>../include;../../pjlib/include;../../pjlib-util/include;../../pjnath/include;../../third_party/portaudio/include;../../third_party/speex/include;../../third_party/build/srtp;../../third_party/srtp/include;../../third_party/srtp/crypto/include;../../third_party/yuv/include;../../third_party/webrtc/src;../..;$(DXSDK_DIR)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+- <PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
++ <PreprocessorDefinitions>PJ_OS_HAS_CHECK_STACK=0;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderOutputFile />
+ </ClCompile>
+ </ItemDefinitionGroup>
+--- a/pjnath/build/pjnath.vcxproj
++++ b/pjnath/build/pjnath.vcxproj
+@@ -410,7 +410,7 @@
+ </Midl>
+ <ClCompile>
+ <AdditionalIncludeDirectories>../include;../../pjlib/include;../../pjlib-util/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+- <PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
++ <PreprocessorDefinitions>PJ_OS_HAS_CHECK_STACK=0;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderOutputFile />
+ </ClCompile>
+ </ItemDefinitionGroup>
+--- a/pjsip/build/pjsip_core.vcxproj
++++ b/pjsip/build/pjsip_core.vcxproj
+@@ -327,7 +327,7 @@
+ </Midl>
+ <ClCompile>
+ <AdditionalIncludeDirectories>../include;../../pjlib/include;../../pjlib-util/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+- <PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
++ <PreprocessorDefinitions>PJ_OS_HAS_CHECK_STACK=0;PJ_HAS_SSL_SOCK;_LIB;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderOutputFile />
+ </ClCompile>
+ <Lib>
+--- a/pjsip/build/pjsip_simple.vcxproj
++++ b/pjsip/build/pjsip_simple.vcxproj
+@@ -362,7 +362,7 @@
+ </Midl>
+ <ClCompile>
+ <AdditionalIncludeDirectories>../include;../../pjlib-util/include;../../pjlib/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+- <PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
++ <PreprocessorDefinitions>PJ_OS_HAS_CHECK_STACK=0;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderOutputFile />
+ </ClCompile>
+ <Lib>
+--- a/pjsip/build/pjsip_ua.vcxproj
++++ b/pjsip/build/pjsip_ua.vcxproj
+@@ -327,7 +327,7 @@
+ </Midl>
+ <ClCompile>
+ <AdditionalIncludeDirectories>../include;../../pjlib/include;../../pjlib-util/include;../../pjmedia/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+- <PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
++ <PreprocessorDefinitions>PJ_OS_HAS_CHECK_STACK=0;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderOutputFile />
+ </ClCompile>
+ <Lib>
+--- a/pjsip/build/pjsua2_lib.vcxproj
++++ b/pjsip/build/pjsua2_lib.vcxproj
+@@ -364,7 +364,7 @@
+ </Midl>
+ <ClCompile>
+ <AdditionalIncludeDirectories>../include;../../pjmedia/include;../../pjlib-util/include;../../pjlib/include;../../pjnath/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+- <PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
++ <PreprocessorDefinitions>PJ_OS_HAS_CHECK_STACK=0;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderOutputFile />
+ </ClCompile>
+ <Lib>
+--- a/pjsip/build/pjsua_lib.vcxproj
++++ b/pjsip/build/pjsua_lib.vcxproj
+@@ -362,7 +362,7 @@
+ </Midl>
+ <ClCompile>
+ <AdditionalIncludeDirectories>../include;../../pjmedia/include;../../pjlib-util/include;../../pjlib/include;../../pjnath/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+- <PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
++ <PreprocessorDefinitions>PJ_OS_HAS_CHECK_STACK=0;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PrecompiledHeaderOutputFile />
+ </ClCompile>
+ <Lib>
+--- a/pjsip/src/pjsip/sip_dialog.c
++++ b/pjsip/src/pjsip/sip_dialog.c
+@@ -882,7 +882,7 @@ PJ_DEF(void) pjsip_dlg_inc_lock(pjsip_dialog *dlg)
+ PJ_LOG(6,(dlg->obj_name, "Entering pjsip_dlg_inc_lock(), sess_count=%d",
+ dlg->sess_count));
+
+- pj_mutex_lock(dlg->mutex_);
++ pj_mutex_trylock(dlg->mutex_);
+ dlg->sess_count++;
+
+ PJ_LOG(6,(dlg->obj_name, "Leaving pjsip_dlg_inc_lock(), sess_count=%d",
+--- a/pjlib/build/pjlib.vcxproj
++++ b/pjlib/build/pjlib.vcxproj
+@@ -617,7 +617,7 @@
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release-Static|x64'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
+- <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
++ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
+ </ClCompile>
+ <ClCompile Include="..\src\pj\ip_helper_win32.c">
+ <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+--- a/pjlib/src/pj/os_core_win32.c
++++ b/pjlib/src/pj/os_core_win32.c
+@@ -1424,10 +1424,10 @@ PJ_DEF(pj_status_t) pj_event_pulse(pj_event_t *event)
+
+ PJ_LOG(6, (event->obj_name, "Pulsing event"));
+
+- if (PulseEvent(event->hEvent))
++ //if (PulseEvent(event->hEvent))
+ return PJ_SUCCESS;
+- else
+- return PJ_RETURN_OS_ERROR(GetLastError());
++ //else
++ //return PJ_RETURN_OS_ERROR(GetLastError());
+ #endif
+ }
+
+--
+2.10.2.windows.1
+
diff --git a/libraries/pjproject-ring/patches/pj_uwp_fix_turn_fallback.patch b/libraries/pjproject-ring/patches/pj_uwp_fix_turn_fallback.patch new file mode 100644 index 0000000000000..5d7b44b9f5c75 --- /dev/null +++ b/libraries/pjproject-ring/patches/pj_uwp_fix_turn_fallback.patch @@ -0,0 +1,51 @@ +--- a/pjnath/src/pjnath/turn_session.c ++++ b/pjnath/src/pjnath/turn_session.c +@@ -653,3 +653,3 @@ + +- cnt = PJ_TURN_MAX_DNS_SRV_CNT; ++ cnt = 1; + ai = (pj_addrinfo*) +--- a/pjnath/src/pjnath/ice_strans.c ++++ b/pjnath/src/pjnath/ice_strans.c +@@ -2078,6 +2078,38 @@ static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state, + } + + sess_init_update(comp->ice_st); ++ } else if ((old_state == PJ_TURN_STATE_RESOLVING || old_state == PJ_TURN_STATE_ALLOCATING) && ++ new_state >= PJ_TURN_STATE_DEALLOCATING) ++ { ++ pj_ice_sess_cand *cand = NULL; ++ unsigned i; ++ ++ /* DNS resolution has failed! */ ++ ++comp->turn[tp_idx].err_cnt; ++ ++ /* Unregister ourself from the TURN relay */ ++ pj_turn_sock_set_user_data(turn_sock, NULL); ++ comp->turn[tp_idx].sock = NULL; ++ ++ /* Wait until initialization completes */ ++ pj_grp_lock_acquire(comp->ice_st->grp_lock); ++ ++ /* Find relayed candidate in the component */ ++ for (i=0; i<comp->cand_cnt; ++i) { ++ if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED && ++ comp->cand_list[i].transport_id == data->transport_id) ++ { ++ cand = &comp->cand_list[i]; ++ break; ++ } ++ } ++ pj_assert(cand != NULL); ++ ++ pj_grp_lock_release(comp->ice_st->grp_lock); ++ ++ cand->status = old_state == PJ_TURN_STATE_RESOLVING ? PJ_ERESOLVE : PJ_EINVALIDOP; ++ ++ sess_init_update(comp->ice_st); + + } else if (new_state >= PJ_TURN_STATE_DEALLOCATING) { + pj_turn_session_info info; +-- +2.8.1.windows.1 + diff --git a/libraries/pjproject-ring/patches/pj_uwp_gnutls.patch b/libraries/pjproject-ring/patches/pj_uwp_gnutls.patch new file mode 100644 index 0000000000000..9f20025be439b --- /dev/null +++ b/libraries/pjproject-ring/patches/pj_uwp_gnutls.patch @@ -0,0 +1,3004 @@ +Copyright (c) 2014-2017 Savoir-faire Linux Inc. + +ssl_sock: add gnutls backend + +This backend is mutually exclusive with the OpenSSL one, but completely +compatible, and conformant to the PJSIP API. Also avoids any license issues +when linking statically. + +The configure script is updated to select either OpenSSL or GnuTLS +with --enable-ssl[='...'] and a new symbol (PJ_HAS_TLS_SOCK) is introduced +to identify which backend is in use. + +Written by +Vittorio Giovara <vittorio.giovara@savoirfairelinux.com> +Philippe Proulx <philippe.proulx@savoirfairelinux.com> and +Adrien BĂ©raud <adrien.beraud@savoirfairelinux.com> +on behalf of Savoir-faire Linux. +--- a/pjlib/include/pj/compat/os_auto.h.in ++++ b/pjlib/include/pj/compat/os_auto.h.in +@@ -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__ */ +--- a/pjlib/include/pj/config.h ++++ b/pjlib/include/pj/config.h +@@ -855,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 + + +--- a/pjlib/include/pj/ssl_sock.h ++++ b/pjlib/include/pj/ssl_sock.h +@@ -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; + + +--- a/pjlib/src/pj/ssl_sock_common.c ++++ b/pjlib/src/pj/ssl_sock_common.c +@@ -35,7 +35,12 @@ PJ_DEF(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param) + 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 +--- /dev/null ++++ b/pjlib/src/pj/ssl_sock_gtls.c +@@ -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 <pj/ssl_sock.h> ++#include <pj/activesock.h> ++#include <pj/compat/socket.h> ++#include <pj/assert.h> ++#include <pj/errno.h> ++#include <pj/list.h> ++#include <pj/lock.h> ++#include <pj/log.h> ++#include <pj/math.h> ++#include <pj/os.h> ++#include <pj/pool.h> ++#include <pj/string.h> ++#include <pj/timer.h> ++#include <pj/file_io.h> ++ ++#if GNUTLS_VERSION_NUMBER < 0x030306 && !defined(_MSC_VER) ++#include <dirent.h> ++#endif ++ ++#include <errno.h> ++ ++/* Only build when PJ_HAS_SSL_SOCK and 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 <gnutls/gnutls.h> ++#include <gnutls/x509.h> ++#include <gnutls/abstract.h> ++ ++#ifdef _MSC_VER ++# pragma comment( lib, "libgnutls") ++#endif ++ ++ ++/* TLS state enumeration. */ ++enum tls_connection_state { ++ TLS_STATE_NULL, ++ TLS_STATE_HANDSHAKING, ++ TLS_STATE_ESTABLISHED ++}; ++ ++/* Internal timer types. */ ++enum timer_id { ++ TIMER_NONE, ++ TIMER_HANDSHAKE_TIMEOUT, ++ TIMER_CLOSE ++}; ++ ++/* Structure of SSL socket read buffer. */ ++typedef struct read_data_t { ++ void *data; ++ pj_size_t len; ++} read_data_t; ++ ++/* ++ * Get the offset of pointer to read-buffer of SSL socket from read-buffer ++ * of active socket. Note that both SSL socket and active socket employ ++ * different but correlated read-buffers (as much as async_cnt for each), ++ * and to make it easier/faster to find corresponding SSL socket's read-buffer ++ * from known active socket's read-buffer, the pointer of corresponding ++ * SSL socket's read-buffer is stored right after the end of active socket's ++ * read-buffer. ++ */ ++#define OFFSET_OF_READ_DATA_PTR(ssock, asock_rbuf) \ ++ (read_data_t**) \ ++ ((pj_int8_t *)(asock_rbuf) + \ ++ ssock->param.read_buffer_size) ++ ++/* Structure of SSL socket write data. */ ++typedef struct write_data_t { ++ PJ_DECL_LIST_MEMBER(struct write_data_t); ++ pj_ioqueue_op_key_t key; ++ pj_size_t record_len; ++ pj_ioqueue_op_key_t *app_key; ++ pj_size_t plain_data_len; ++ pj_size_t data_len; ++ unsigned flags; ++ union { ++ char content[1]; ++ const char *ptr; ++ } data; ++} write_data_t; ++ ++ ++/* Structure of SSL socket write buffer (circular buffer). */ ++typedef struct send_buf_t { ++ char *buf; ++ pj_size_t max_len; ++ char *start; ++ pj_size_t len; ++} send_buf_t; ++ ++ ++/* Circular buffer object */ ++typedef struct circ_buf_t { ++ pj_size_t cap; /* maximum number of elements (must be power of 2) */ ++ pj_size_t readp; /* index of oldest element */ ++ pj_size_t writep; /* index at which to write new element */ ++ pj_size_t size; /* number of elements */ ++ pj_uint8_t *buf; /* data buffer */ ++ pj_pool_t *pool; /* where new allocations will take place */ ++} circ_buf_t; ++ ++ ++/* Secure socket structure definition. */ ++struct pj_ssl_sock_t { ++ pj_pool_t *pool; ++ pj_ssl_sock_t *parent; ++ pj_ssl_sock_param param; ++ pj_ssl_sock_param newsock_param; ++ pj_ssl_cert_t *cert; ++ ++ pj_ssl_cert_info local_cert_info; ++ pj_ssl_cert_info remote_cert_info; ++ ++ pj_bool_t is_server; ++ enum tls_connection_state connection_state; ++ pj_ioqueue_op_key_t handshake_op_key; ++ pj_timer_entry timer; ++ pj_status_t verify_status; ++ ++ int last_err; ++ ++ pj_sock_t sock; ++ pj_activesock_t *asock; ++ ++ pj_sockaddr local_addr; ++ pj_sockaddr rem_addr; ++ int addr_len; ++ ++ pj_bool_t read_started; ++ pj_size_t read_size; ++ pj_uint32_t read_flags; ++ void **asock_rbuf; ++ read_data_t *ssock_rbuf; ++ ++ write_data_t write_pending; /* list of pending writes */ ++ write_data_t write_pending_empty; /* cache for write_pending */ ++ pj_bool_t flushing_write_pend; /* flag of flushing is ongoing */ ++ send_buf_t send_buf; ++ write_data_t send_pending; /* list of pending write to network */ ++ ++ gnutls_session_t session; ++ gnutls_certificate_credentials_t xcred; ++ ++ circ_buf_t circ_buf_input; ++ pj_lock_t *circ_buf_input_mutex; ++ ++ circ_buf_t circ_buf_output; ++ pj_lock_t *circ_buf_output_mutex; ++ ++ int tls_init_count; /* library initialization counter */ ++}; ++ ++ ++/* Certificate/credential structure definition. */ ++struct pj_ssl_cert_t { ++ pj_str_t CA_file; ++ pj_str_t CA_path; ++ pj_str_t cert_file; ++ pj_str_t privkey_file; ++ pj_str_t privkey_pass; ++}; ++ ++/* GnuTLS available ciphers */ ++static unsigned tls_available_ciphers; ++ ++/* Array of id/names for available ciphers */ ++static struct tls_ciphers_t { ++ pj_ssl_cipher id; ++ const char *name; ++} tls_ciphers[MAX_CIPHERS]; ++ ++/* Last error reported somehow */ ++static int tls_last_error; ++ ++ ++/* ++ ******************************************************************* ++ * Circular buffer functions. ++ ******************************************************************* ++ */ ++ ++static pj_status_t circ_init(pj_pool_factory *factory, ++ circ_buf_t *cb, pj_size_t cap) ++{ ++ cb->cap = cap; ++ cb->readp = 0; ++ cb->writep = 0; ++ cb->size = 0; ++ ++ /* Initial pool holding the buffer elements */ ++ cb->pool = pj_pool_create(factory, "tls-circ%p", cap, cap, NULL); ++ if (!cb->pool) ++ return PJ_ENOMEM; ++ ++ /* Allocate circular buffer */ ++ cb->buf = pj_pool_alloc(cb->pool, cap); ++ if (!cb->buf) { ++ pj_pool_release(cb->pool); ++ return PJ_ENOMEM; ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++static void circ_deinit(circ_buf_t *cb) ++{ ++ if (cb->pool) { ++ pj_pool_release(cb->pool); ++ cb->pool = NULL; ++ } ++} ++ ++static pj_bool_t circ_empty(const circ_buf_t *cb) ++{ ++ return cb->size == 0; ++} ++ ++static pj_size_t circ_size(const circ_buf_t *cb) ++{ ++ return cb->size; ++} ++ ++static pj_size_t circ_avail(const circ_buf_t *cb) ++{ ++ return cb->cap - cb->size; ++} ++ ++static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) ++{ ++ pj_size_t size_after = cb->cap - cb->readp; ++ pj_size_t tbc = PJ_MIN(size_after, len); ++ pj_size_t rem = len - tbc; ++ ++ pj_memcpy(dst, cb->buf + cb->readp, tbc); ++ pj_memcpy(dst + tbc, cb->buf, rem); ++ ++ cb->readp += len; ++ cb->readp &= (cb->cap - 1); ++ ++ cb->size -= len; ++} ++ ++static pj_status_t circ_write(circ_buf_t *cb, ++ const pj_uint8_t *src, pj_size_t len) ++{ ++ /* Overflow condition: resize */ ++ if (len > circ_avail(cb)) { ++ /* Minimum required capacity */ ++ pj_size_t min_cap = len + cb->size; ++ ++ /* Next 32-bit power of two */ ++ min_cap--; ++ min_cap |= min_cap >> 1; ++ min_cap |= min_cap >> 2; ++ min_cap |= min_cap >> 4; ++ min_cap |= min_cap >> 8; ++ min_cap |= min_cap >> 16; ++ min_cap++; ++ ++ /* Create a new pool to hold a bigger buffer, using the same factory */ ++ pj_pool_t *pool = pj_pool_create(cb->pool->factory, "tls-circ%p", ++ min_cap, min_cap, NULL); ++ if (!pool) ++ return PJ_ENOMEM; ++ ++ /* Allocate our new buffer */ ++ pj_uint8_t *buf = pj_pool_alloc(pool, min_cap); ++ if (!buf) { ++ pj_pool_release(pool); ++ return PJ_ENOMEM; ++ } ++ ++ /* Save old size, which we shall restore after the next read */ ++ pj_size_t old_size = cb->size; ++ ++ /* Copy old data into beginning of new buffer */ ++ circ_read(cb, buf, cb->size); ++ ++ /* Restore old size now */ ++ cb->size = old_size; ++ ++ /* Release the previous pool */ ++ pj_pool_release(cb->pool); ++ ++ /* Update circular buffer members */ ++ cb->pool = pool; ++ cb->buf = buf; ++ cb->readp = 0; ++ cb->writep = cb->size; ++ cb->cap = min_cap; ++ } ++ ++ pj_size_t size_after = cb->cap - cb->writep; ++ pj_size_t tbc = PJ_MIN(size_after, len); ++ pj_size_t rem = len - tbc; ++ ++ pj_memcpy(cb->buf + cb->writep, src, tbc); ++ pj_memcpy(cb->buf, src + tbc, rem); ++ ++ cb->writep += len; ++ cb->writep &= (cb->cap - 1); ++ ++ cb->size += len; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* ++ ******************************************************************* ++ * Static/internal functions. ++ ******************************************************************* ++ */ ++ ++/* Convert from GnuTLS error to pj_status_t. */ ++static pj_status_t tls_status_from_err(pj_ssl_sock_t *ssock, int err) ++{ ++ pj_status_t status; ++ ++ switch (err) { ++ case GNUTLS_E_SUCCESS: ++ status = PJ_SUCCESS; ++ break; ++ case GNUTLS_E_MEMORY_ERROR: ++ status = PJ_ENOMEM; ++ break; ++ case GNUTLS_E_LARGE_PACKET: ++ status = PJ_ETOOBIG; ++ break; ++ case GNUTLS_E_NO_CERTIFICATE_FOUND: ++ status = PJ_ENOTFOUND; ++ break; ++ case GNUTLS_E_SESSION_EOF: ++ status = PJ_EEOF; ++ break; ++ case GNUTLS_E_HANDSHAKE_TOO_LARGE: ++ status = PJ_ETOOBIG; ++ break; ++ case GNUTLS_E_EXPIRED: ++ status = PJ_EGONE; ++ break; ++ case GNUTLS_E_TIMEDOUT: ++ status = PJ_ETIMEDOUT; ++ break; ++ case GNUTLS_E_PREMATURE_TERMINATION: ++ status = PJ_ECANCELLED; ++ break; ++ case GNUTLS_E_INTERNAL_ERROR: ++ case GNUTLS_E_UNIMPLEMENTED_FEATURE: ++ status = PJ_EBUG; ++ break; ++ case GNUTLS_E_AGAIN: ++ case GNUTLS_E_INTERRUPTED: ++ case GNUTLS_E_REHANDSHAKE: ++ status = PJ_EPENDING; ++ break; ++ case GNUTLS_E_TOO_MANY_EMPTY_PACKETS: ++ case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS: ++ case GNUTLS_E_RECORD_LIMIT_REACHED: ++ status = PJ_ETOOMANY; ++ break; ++ case GNUTLS_E_UNSUPPORTED_VERSION_PACKET: ++ case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM: ++ case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE: ++ case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE: ++ case GNUTLS_E_X509_UNSUPPORTED_EXTENSION: ++ case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION: ++ status = PJ_ENOTSUP; ++ break; ++ case GNUTLS_E_INVALID_SESSION: ++ case GNUTLS_E_INVALID_REQUEST: ++ case GNUTLS_E_INVALID_PASSWORD: ++ case GNUTLS_E_ILLEGAL_PARAMETER: ++ case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION: ++ case GNUTLS_E_UNEXPECTED_PACKET: ++ case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: ++ case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET: ++ case GNUTLS_E_UNWANTED_ALGORITHM: ++ case GNUTLS_E_USER_ERROR: ++ status = PJ_EINVAL; ++ break; ++ default: ++ status = PJ_EUNKNOWN; ++ break; ++ } ++ ++ /* Not thread safe */ ++ tls_last_error = err; ++ if (ssock) ++ ssock->last_err = err; ++ return status; ++} ++ ++ ++/* Get error string from GnuTLS using tls_last_error */ ++static pj_str_t tls_strerror(pj_status_t status, ++ char *buf, pj_size_t bufsize) ++{ ++ pj_str_t errstr; ++ const char *tmp = gnutls_strerror(tls_last_error); ++ ++#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) ++ if (tmp) { ++ pj_ansi_strncpy(buf, tmp, bufsize); ++ errstr = pj_str(buf); ++ return errstr; ++ } ++#endif /* PJ_HAS_ERROR_STRING */ ++ ++ errstr.ptr = buf; ++ errstr.slen = pj_ansi_snprintf(buf, bufsize, "GnuTLS error %d: %s", ++ tls_last_error, tmp); ++ if (errstr.slen < 1 || errstr.slen >= (int) bufsize) ++ errstr.slen = bufsize - 1; ++ ++ return errstr; ++} ++ ++ ++/* GnuTLS way of reporting internal operations. */ ++static void tls_print_logs(int level, const char* msg) ++{ ++ PJ_LOG(3, (THIS_FILE, "GnuTLS [%d]: %s", level, msg)); ++} ++ ++ ++/* Initialize GnuTLS. */ ++static pj_status_t tls_init(void) ++{ ++ /* Register error subsystem */ ++ pj_status_t status = pj_register_strerror(PJ_ERRNO_START_USER + ++ PJ_ERRNO_SPACE_SIZE * 6, ++ PJ_ERRNO_SPACE_SIZE, ++ &tls_strerror); ++ pj_assert(status == PJ_SUCCESS); ++ ++ /* Init GnuTLS library */ ++ int ret = gnutls_global_init(); ++ if (ret < 0) ++ return tls_status_from_err(NULL, ret); ++ ++ gnutls_global_set_log_level(GNUTLS_LOG_LEVEL); ++ gnutls_global_set_log_function(tls_print_logs); ++ ++ /* Init available ciphers */ ++ if (!tls_available_ciphers) { ++ unsigned int i; ++ ++ for (i = 0; ; i++) { ++ unsigned char id[2]; ++ const char *suite = 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/rand.h> ++pj_status_t pj_ssl_sock_ossl_test_send_buf(pj_pool_t *pool) ++{ ++ enum { MAX_CHUNK_NUM = 20 }; ++ unsigned chunk_size, chunk_cnt, i; ++ write_data_t *wdata[MAX_CHUNK_NUM] = {0}; ++ pj_time_val now; ++ pj_ssl_sock_t *ssock = NULL; ++ pj_ssl_sock_param param; ++ pj_status_t status; ++ ++ pj_gettimeofday(&now); ++ pj_srand((unsigned)now.sec); ++ ++ pj_ssl_sock_param_default(¶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 */ +--- a/pjlib/src/pj/ssl_sock_ossl.c ++++ b/pjlib/src/pj/ssl_sock_ossl.c +@@ -32,8 +32,10 @@ + #include <pj/timer.h> + + +-/* 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" + +--- a/pjmedia/src/pjmedia/transport_srtp.c ++++ b/pjmedia/src/pjmedia/transport_srtp.c +@@ -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 <openssl/rand.h> + + /* Suppress compile warning of OpenSSL deprecation (OpenSSL is deprecated +@@ -1113,7 +1114,8 @@ static pj_status_t generate_crypto_attr_value(pj_pool_t *pool, + 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 diff --git a/libraries/pjproject-ring/patches/pj_uwp_ice_sess.patch b/libraries/pjproject-ring/patches/pj_uwp_ice_sess.patch new file mode 100644 index 0000000000000..ce4e0b4898a44 --- /dev/null +++ b/libraries/pjproject-ring/patches/pj_uwp_ice_sess.patch @@ -0,0 +1,25 @@ +--- a/pjnath/include/pjnath/ice_strans.h
++++ b/pjnath/include/pjnath/ice_strans.h
+@@ -930,6 +930,7 @@
+ int dst_addr_len);
+
+
++PJ_DECL(pj_ice_sess *) pj_ice_strans_get_ice_sess(pj_ice_strans *ice_st);
+ /**
+ * @}
+ */
+--- a/pjnath/src/pjnath/ice_strans.c
++++ b/pjnath/src/pjnath/ice_strans.c
+@@ -1465,6 +1468,11 @@
+ return PJ_EINVALIDOP;
+ }
+
++PJ_DECL(pj_ice_sess *) pj_ice_strans_get_ice_sess( pj_ice_strans *ice_st )
++{
++ return ice_st->ice;
++}
++
+ /*
+ * Callback called by ICE session when ICE processing is complete, either
+ * successfully or with failure.
+
\ No newline at end of file diff --git a/libraries/pjproject-ring/patches/pj_uwp_multiple_listeners.patch b/libraries/pjproject-ring/patches/pj_uwp_multiple_listeners.patch new file mode 100644 index 0000000000000..893bcfbdca8bd --- /dev/null +++ b/libraries/pjproject-ring/patches/pj_uwp_multiple_listeners.patch @@ -0,0 +1,64 @@ +--- a/pjsip/src/pjsip/sip_transport.c ++++ b/pjsip/src/pjsip/sip_transport.c +@@ -1248,22 +1248,22 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_register_tpfactory( pjsip_tpmgr *mgr, + + pj_lock_acquire(mgr->lock); + +- /* Check that no factory with the same type has been registered. */ ++ /* Check that no factory with the same type and bound address has been registered. */ + status = PJ_SUCCESS; + for (p=mgr->factory_list.next; p!=&mgr->factory_list; p=p->next) { +- if (p->type == tpf->type) { +- status = PJSIP_ETYPEEXISTS; +- break; +- } +- if (p == tpf) { +- status = PJ_EEXISTS; +- break; +- } ++ if (p->type == tpf->type && !pj_sockaddr_cmp(&tpf->local_addr, &p->local_addr)) { ++ status = PJSIP_ETYPEEXISTS; ++ break; ++ } ++ if (p == tpf) { ++ status = PJ_EEXISTS; ++ break; ++ } + } + + if (status != PJ_SUCCESS) { +- pj_lock_release(mgr->lock); +- return status; ++ pj_lock_release(mgr->lock); ++ return status; + } + + pj_list_insert_before(&mgr->factory_list, tpf); +@@ -2047,13 +2047,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + pj_memcpy(&key.rem_addr, remote, addr_len); + + transport = (pjsip_transport*) +- pj_hash_get(mgr->table, &key, key_len, NULL); +- ++ pj_hash_get(mgr->table, &key, key_len, NULL); ++ unsigned flag = pjsip_transport_get_flag_from_type(type); + if (transport == NULL) { +- unsigned flag = pjsip_transport_get_flag_from_type(type); + const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote; + +- + /* Ignore address for loop transports. */ + if (type == PJSIP_TRANSPORT_LOOP || + type == PJSIP_TRANSPORT_LOOP_DGRAM) +@@ -2135,6 +2135,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + } + + } else { ++ /* Make sure we don't use another factory than the one given if secure flag is set */ ++ if (flag & PJSIP_TRANSPORT_SECURE) { ++ TRACE_((THIS_FILE, "Can't create new TLS transport with no provided suitable TLS listener.")); ++ return PJSIP_ETPNOTSUITABLE; ++ } + + /* Find factory with type matches the destination type */ + factory = mgr->factory_list.next; diff --git a/libraries/pjproject-ring/patches/pj_win.patch b/libraries/pjproject-ring/patches/pj_win.patch new file mode 100644 index 0000000000000..c1c4c78f196dc --- /dev/null +++ b/libraries/pjproject-ring/patches/pj_win.patch @@ -0,0 +1,11 @@ +--- pjproject/pjlib/include/pj/compat/string.h.orig 2014-09-08 16:17:36.471214560 -0400 ++++ pjproject/pjlib/include/pj/compat/string.h 2014-09-08 16:09:22.095207141 -0400 +@@ -43,7 +43,7 @@ + # include <stdlib.h> + #endif + +-#if defined(_MSC_VER) ++#if defined(PJ_WIN32) + # define strcasecmp _stricmp + # define strncasecmp _strnicmp + # define snprintf _snprintf diff --git a/libraries/pjproject-ring/pjproject-ring.SlackBuild b/libraries/pjproject-ring/pjproject-ring.SlackBuild new file mode 100644 index 0000000000000..d8bf842e34fbe --- /dev/null +++ b/libraries/pjproject-ring/pjproject-ring.SlackBuild @@ -0,0 +1,126 @@ +#!/bin/sh + +# Slackware build script for pjproject-ring + +# Copyright 2017 Mario Preksavec, Zagreb, Croatia +# All rights reserved. +# +# Redistribution and use of this script, with or without modification, is +# permitted provided that the following conditions are met: +# +# 1. Redistributions of this script must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +PRGNAM=pjproject-ring +VERSION=${VERSION:-2.6} +BUILD=${BUILD:-1} +TAG=${TAG:-_SBo} + +SRCNAM=pjproject + +if [ -z "$ARCH" ]; then + case "$( uname -m )" in + i?86) ARCH=i586 ;; + arm*) ARCH=arm ;; + *) ARCH=$( uname -m ) ;; + esac +fi + +CWD=$(pwd) +TMP=${TMP:-/tmp/SBo} +PKG=$TMP/package-$PRGNAM +OUTPUT=${OUTPUT:-/tmp} + +if [ "$ARCH" = "i586" ]; then + SLKCFLAGS="-O2 -march=i586 -mtune=i686" + LIBDIRSUFFIX="" +elif [ "$ARCH" = "i686" ]; then + SLKCFLAGS="-O2 -march=i686 -mtune=i686" + LIBDIRSUFFIX="" +elif [ "$ARCH" = "x86_64" ]; then + SLKCFLAGS="-O2 -fPIC" + LIBDIRSUFFIX="64" +else + SLKCFLAGS="-O2" + LIBDIRSUFFIX="" +fi + +set -e + +rm -rf $PKG +mkdir -p $TMP $PKG $OUTPUT +cd $TMP +rm -rf $SRCNAM-$VERSION +tar xvf $CWD/$SRCNAM-$VERSION.tar.bz2 +cd $SRCNAM-$VERSION +chown -R root:root . +find -L . \ + \( -perm 777 -o -perm 775 -o -perm 750 -o -perm 711 -o -perm 555 \ + -o -perm 511 \) -exec chmod 755 {} \; -o \ + \( -perm 666 -o -perm 664 -o -perm 640 -o -perm 600 -o -perm 444 \ + -o -perm 440 -o -perm 400 \) -exec chmod 644 {} \; + +for patch in gnutls notestsapps fix_base64 ipv6 ice_config \ + multiple_listeners pj_ice_sess fix_turn_fallback \ + fix_ioqueue_ipv6_sendto add_dtls_transport; do + patch -p1 -l <$CWD/patches/$patch.patch +done + +PJFLAGS="-DPJ_ICE_MAX_CAND=256 + -DPJ_ICE_MAX_CHECKS=150 + -DPJ_ICE_COMP_BITS=2 + -DPJ_ICE_MAX_STUN=3 + -DPJSIP_MAX_PKT_LEN=8000 + -DPJ_ICE_ST_MAX_CAND=32" + +CFLAGS="$SLKCFLAGS -g $PJFLAGS" \ +CXXFLAGS="$SLKCFLAGS -g $PJFLAGS -std=gnu++11" \ +./aconfigure \ + --prefix=/usr \ + --libdir=/usr/lib${LIBDIRSUFFIX} \ + --disable-oss \ + --disable-sound \ + --disable-video \ + --enable-ext-sound \ + --disable-speex-aec \ + --disable-g711-codec \ + --disable-l16-codec \ + --disable-gsm-codec \ + --disable-g722-codec \ + --disable-g7221-codec \ + --disable-speex-codec \ + --disable-ilbc-codec \ + --disable-opencore-amr \ + --disable-silk \ + --disable-sdl \ + --disable-ffmpeg \ + --disable-v4l2 \ + --disable-openh264 \ + --disable-resample \ + --disable-libwebrtc \ + --enable-ssl=gnutls \ + --build=$ARCH-slackware-linux + +make +make install DESTDIR=$PKG + +mkdir -p $PKG/usr/doc/$PRGNAM-$VERSION +cp -a COPYING README-RTEMS README.txt $PKG/usr/doc/$PRGNAM-$VERSION +cat $CWD/$PRGNAM.SlackBuild > $PKG/usr/doc/$PRGNAM-$VERSION/$PRGNAM.SlackBuild + +mkdir -p $PKG/install +cat $CWD/slack-desc > $PKG/install/slack-desc + +cd $PKG +/sbin/makepkg -l y -c n $OUTPUT/$PRGNAM-$VERSION-$ARCH-$BUILD$TAG.${PKGTYPE:-tgz} diff --git a/libraries/pjproject-ring/pjproject-ring.info b/libraries/pjproject-ring/pjproject-ring.info new file mode 100644 index 0000000000000..44051473ec081 --- /dev/null +++ b/libraries/pjproject-ring/pjproject-ring.info @@ -0,0 +1,10 @@ +PRGNAM="pjproject-ring" +VERSION="2.6" +HOMEPAGE="http://www.pjsip.org/" +DOWNLOAD="http://www.pjsip.org/release/2.6/pjproject-2.6.tar.bz2" +MD5SUM="c347a672679e7875ce572e18517884b2" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +REQUIRES="" +MAINTAINER="Mario Preksavec" +EMAIL="mario at slackware dot hr" diff --git a/libraries/pjproject-ring/slack-desc b/libraries/pjproject-ring/slack-desc new file mode 100644 index 0000000000000..2e5b6f9a28bad --- /dev/null +++ b/libraries/pjproject-ring/slack-desc @@ -0,0 +1,19 @@ +# HOW TO EDIT THIS FILE: +# The "handy ruler" below makes it easier to edit a package description. +# Line up the first '|' above the ':' following the base package name, and +# the '|' on the right side marks the last column you can put a character in. +# You must make exactly 11 lines for the formatting to be correct. It's also +# customary to leave one space after the ':' except on otherwise blank lines. + + |-----handy-ruler------------------------------------------------------| +pjproject-ring: pjproject-ring (Multimedia Communication Library) +pjproject-ring: +pjproject-ring: PJSIP is a free and open source multimedia communication library +pjproject-ring: written in C language implementing standard based protocols such as +pjproject-ring: SIP, SDP, RTP, STUN, TURN, and ICE. +pjproject-ring: +pjproject-ring: This package includes patches from Ring project. +pjproject-ring: +pjproject-ring: Homepage: http://www.pjsip.org/ +pjproject-ring: +pjproject-ring: |