Lukas Gradl <lgr...@openmailbox.org> writes: > As I said I will post two patches later (pjproject & libring). Any help > on those or some of the missing inputs is of course greatly appreciated!
Here they are! pjproject still fails to build one of its components (pjmedia) because of a missing include (resamplesubs.h). I almost have a package providing that header. I am not super happy with the fact that this is a special package dedicated to libring. There are a lot of patches that ship with libring's bundeled version of pjproject. These patches are nice (e.g. gnutls instead of openssl), but they fail on the most recent release 2.5.1 of pjproject and I have no idea in what way they affect other software that might want to use pjproject (I think Asterisk would be an example of this). I will try to remove some of the constraints on pjproject that are imposed by libring without breaking anything, but first I want to get it to build and we might end up needing another seperate definition for a "normal" pjproject. The patch for libring is mostly just to keep track of dependencies right now; it fails during 'configure (when it is looking for pjproject :-)) Any comments are very much appreciated! Thank you! Best, Lukas
>From ecc5faebb7586e4d8e080c9cd08ea0367157c63c Mon Sep 17 00:00:00 2001 From: Lukas Gradl <lgr...@openmailbox.org> Date: Wed, 22 Jun 2016 00:31:43 -0500 Subject: [PATCH] gnu: Add libring. * gnu/packages/telephony.scm (libring): New variable. --- gnu/packages/telephony.scm | 84 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/gnu/packages/telephony.scm b/gnu/packages/telephony.scm index e4668cf..35a18c7 100644 --- a/gnu/packages/telephony.scm +++ b/gnu/packages/telephony.scm @@ -24,16 +24,28 @@ #:use-module (gnu packages) #:use-module (gnu packages audio) #:use-module (gnu packages autotools) + #:use-module (gnu packages base) + #:use-module (gnu packages compression) + #:use-module (gnu packages crypto) #:use-module (gnu packages gnupg) + #:use-module (gnu packages libupnp) #:use-module (gnu packages linux) + #:use-module (gnu packages multiprecision) + #:use-module (gnu packages nettle) + #:use-module (gnu packages pcre) #:use-module (gnu packages pkg-config) #:use-module (gnu packages pulseaudio) #:use-module (gnu packages python) + #:use-module (gnu packages readline) + #:use-module (gnu packages serialization) #:use-module (gnu packages tls) + #:use-module (gnu packages video) + #:use-module (gnu packages web) #:use-module (gnu packages xiph) #:use-module (guix licenses) #:use-module (guix packages) #:use-module (guix download) + #:use-module (guix git-download) #:use-module (guix build-system gnu)) (define-public commoncpp @@ -358,3 +370,75 @@ Initiation Protocol (SIP) and a multimedia framework. This package is intended for use with libring. There are several custom patches, most notably the use of gnutls instead of openssl for encryption.") (license gpl2+))) + + + +(define-public libring + (let ((commit "3dbedf53e9cceebb1eed155b5143026f6d253cb8")) ; tagged 2.2.0 + (package + (name "libring") + (version (string-append "2.2.0-1." (string-take commit 7))) + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://gerrit-ring.savoirfairelinux.com/ring-daemon.git") + (commit commit))) + (file-name (string-append name "-" version "-checkout")) + (modules '((guix build utils))) + (snippet + '(begin + (delete-file-recursively "contrib"))) + (sha256 + (base32 + "1xnvkv0h24zr1dcmp7djjhqgzvrwic242jy4hb3m5qv71azvcsqg")))) + (build-system gnu-build-system) + (inputs + `(("ffmpeg" ,ffmpeg) + ("flac" ,flac) + ("libgcrypt" ,libgcrypt) + ("gmp" ,gmp) + ; ("gnutls" ,gnutls) ; Maybe not needed as already used by inputs + ("libgpg-error" ,libgpg-error) + ; ("gsm" ,gsm) ; not found + ("libiax2" ,libiax2) + ("libiconv" ,libiconv) + ("jack" ,jack-1) + ; ("msgpack" ,msgpack) ; maybe not needed as used by inputs (opendht) + ; ("nettle" ,nettle) ; maybe not needed (only certain proprietary OSes?) + ("libogg" ,libogg) + ("opendht" ,opendht) + ("opus" ,opus) + ("pcre" ,pcre) + ; ("pjproject" ,pjproject-for-libring) ; not found, WIP + ; ("pkg-static" ,pkg-static) ; not found + ("portaudio" ,portaudio) + ("libsamplerate" ,libsamplerate) + ("libsndfile" ,libsndfile) + ("speex" ,speex) + ; ("speexdsp" ,speexdsp) + ("libupnp" ,libupnp) + ; ("uuid" ,uuid) ; not found, maybe not needed (only certain proprietary OSes?) + ("libvorbis" ,libvorbis) + ("libvpx" ,libvpx) + ("libx264" ,libx264) + ; ("yaml-cpp" ,yaml-cpp) ;not found + ("zlib" ,zlib))) ; maybe not needed as used by inputs + (native-inputs + `(("autoconf" ,autoconf) + ("pkg-config" ,pkg-config) + ("automake" ,automake) + ("libtool" ,libtool))) + (arguments + `(#:phases + (modify-phases %standard-phases + (add-before 'configure 'autoconf + (lambda _ + (zero? (system* "autoreconf" "-vfi"))))))) + (synopsis "Distributed multimedia communications platform") + (description "Ring is a secure and distributed voice, video and chat +communication platform that requires no centralized server and leaves the +power of privacy in the hands of the user. It supports the SIP and IAX +protocols, as well as decentralized calling using P2P-DHT.") + (home-page "https://ring.cx/") + (license gpl3+)))) -- 2.7.4
>From 909dbecc19da3a65a91e3d45750be63d400760fb Mon Sep 17 00:00:00 2001 From: Lukas Gradl <lgr...@openmailbox.org> Date: Wed, 22 Jun 2016 00:21:21 -0500 Subject: [PATCH] gnu: Add pjproject. * gnu/packages/patches/pjproject-endianness.patch: New file. * gnu/packages/patches/pjproject-errno.patch: New file. * gnu/packages/patches/pjproject-ice-config.patch: New file. * gnu/packages/patches/pjproject-ice-sess.patch: New file. * gnu/packages/patches/pjproject-ipv6.patch: New file. * gnu/packages/patches/pjproject-multiple-listeners.patch: New file. * gnu/packages/patches/pjproject-no-test-apps.patch: New file. * gnu/packages/patches/pjproject-use-gnutls.patch: New file. * gnu/local.mk (dist_patch_DATA): Add them. * gnu/packages/telephony.scm (pjproject): New variable. --- gnu/local.mk | 8 + gnu/packages/patches/pjproject-endianness.patch | 19 + gnu/packages/patches/pjproject-errno.patch | 13 + gnu/packages/patches/pjproject-ice-config.patch | 23 + gnu/packages/patches/pjproject-ice-sess.patch | 22 + gnu/packages/patches/pjproject-ipv6.patch | 11 + .../patches/pjproject-multiple-listeners.patch | 66 + gnu/packages/patches/pjproject-no-test-apps.patch | 97 + gnu/packages/patches/pjproject-use-gnutls.patch | 3380 ++++++++++++++++++++ gnu/packages/telephony.scm | 106 + 10 files changed, 3745 insertions(+) create mode 100644 gnu/packages/patches/pjproject-endianness.patch create mode 100644 gnu/packages/patches/pjproject-errno.patch create mode 100644 gnu/packages/patches/pjproject-ice-config.patch create mode 100644 gnu/packages/patches/pjproject-ice-sess.patch create mode 100644 gnu/packages/patches/pjproject-ipv6.patch create mode 100644 gnu/packages/patches/pjproject-multiple-listeners.patch create mode 100644 gnu/packages/patches/pjproject-no-test-apps.patch create mode 100644 gnu/packages/patches/pjproject-use-gnutls.patch diff --git a/gnu/local.mk b/gnu/local.mk index 86b56d4..784afc7 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -682,6 +682,14 @@ dist_patch_DATA = \ %D%/packages/patches/pinball-src-deps.patch \ %D%/packages/patches/pinball-system-ltdl.patch \ %D%/packages/patches/pingus-sdl-libs-config.patch \ + %D%/packages/patches/pjproject-endianness.patch \ + %D%/packages/patches/pjproject-errno.patch \ + %D%/packages/patches/pjproject-ice-config.patch \ + %D%/packages/patches/pjproject-ice-sess.patch \ + %D%/packages/patches/pjproject-ipv6.patch \ + %D%/packages/patches/pjproject-multiple-listeners.patch \ + %D%/packages/patches/pjproject-no-test-apps.patch \ + %D%/packages/patches/pjproject-use-gnutls.patch \ %D%/packages/patches/plink-1.07-unclobber-i.patch \ %D%/packages/patches/plotutils-libpng-jmpbuf.patch \ %D%/packages/patches/polkit-drop-test.patch \ diff --git a/gnu/packages/patches/pjproject-endianness.patch b/gnu/packages/patches/pjproject-endianness.patch new file mode 100644 index 0000000..84b9499 --- /dev/null +++ b/gnu/packages/patches/pjproject-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/gnu/packages/patches/pjproject-errno.patch b/gnu/packages/patches/pjproject-errno.patch new file mode 100644 index 0000000..357fb19 --- /dev/null +++ b/gnu/packages/patches/pjproject-errno.patch @@ -0,0 +1,13 @@ +--- pjproject/pjlib/include/pj/errno.h 2013-04-04 23:02:19.000000000 -0400 ++++ pjproject/pjlib/include/pj/errno.h 2015-02-27 12:21:24.171567801 -0500 +@@ -432,6 +432,11 @@ + * Socket is stopped + */ + #define PJ_ESOCKETSTOP (PJ_ERRNO_START_STATUS + 24)/* 70024 */ ++/** ++ * @hideinitializer ++ * There is no data available right now, try again later. ++ */ ++#define PJ_EAGAIN (PJ_ERRNO_START_STATUS + 25)/* 70025 */ + + /** @} */ /* pj_errnum */ diff --git a/gnu/packages/patches/pjproject-ice-config.patch b/gnu/packages/patches/pjproject-ice-config.patch new file mode 100644 index 0000000..e958c0d --- /dev/null +++ b/gnu/packages/patches/pjproject-ice-config.patch @@ -0,0 +1,23 @@ +--- a/pjnath/include/pjnath/config.h ++++ b/pjnath/include/pjnath/config.h +@@ -231,5 +231,5 @@ + * Default: 16 + */ + #ifndef PJ_ICE_MAX_CAND +-# define PJ_ICE_MAX_CAND 16 ++# define PJ_ICE_MAX_CAND 32 + #endif +@@ -250,5 +250,5 @@ + * the maximum number of components (PJ_ICE_MAX_COMP) value. + */ + #ifndef PJ_ICE_COMP_BITS +-# define PJ_ICE_COMP_BITS 1 ++# define PJ_ICE_COMP_BITS 2 + #endif +@@ -301,5 +301,5 @@ + * Default: 32 + */ + #ifndef PJ_ICE_MAX_CHECKS +-# define PJ_ICE_MAX_CHECKS 32 ++# define PJ_ICE_MAX_CHECKS 150 + #endif diff --git a/gnu/packages/patches/pjproject-ice-sess.patch b/gnu/packages/patches/pjproject-ice-sess.patch new file mode 100644 index 0000000..bd040ff --- /dev/null +++ b/gnu/packages/patches/pjproject-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/gnu/packages/patches/pjproject-ipv6.patch b/gnu/packages/patches/pjproject-ipv6.patch new file mode 100644 index 0000000..8b42fbb --- /dev/null +++ b/gnu/packages/patches/pjproject-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/gnu/packages/patches/pjproject-multiple-listeners.patch b/gnu/packages/patches/pjproject-multiple-listeners.patch new file mode 100644 index 0000000..9bd58ee --- /dev/null +++ b/gnu/packages/patches/pjproject-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 +@@ -1179,22 +1179,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); +@@ -1909,13 +1909,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) +@@ -1954,6 +1952,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + return PJ_SUCCESS; + } + ++ if (flag & PJSIP_TRANSPORT_SECURE) { ++ TRACE_((THIS_FILE, "Can't create new TLS transport with no provided suitable TLS listener.")); ++ return PJSIP_ETPNOTSUITABLE; ++ } ++ + /* + * Transport not found! + * Find factory that can create such transport. diff --git a/gnu/packages/patches/pjproject-no-test-apps.patch b/gnu/packages/patches/pjproject-no-test-apps.patch new file mode 100644 index 0000000..662f1db --- /dev/null +++ b/gnu/packages/patches/pjproject-no-test-apps.patch @@ -0,0 +1,97 @@ +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 +@@ -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/gnu/packages/patches/pjproject-use-gnutls.patch b/gnu/packages/patches/pjproject-use-gnutls.patch new file mode 100644 index 0000000..00bdd95 --- /dev/null +++ b/gnu/packages/patches/pjproject-use-gnutls.patch @@ -0,0 +1,3380 @@ +From 5cb3786d107bdea372ab45e6c35f882fd867d5e0 Mon Sep 17 00:00:00 2001 +From: Vittorio Giovara <vittorio.giov...@savoirfairelinux.com> +Date: Mon, 9 Jun 2014 14:20:55 -0400 +Subject: [PATCH 1/1] 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.giov...@savoirfairelinux.com> +Philippe Proulx <philippe.pro...@savoirfairelinux.com> and +Adrien Béraud <adrien.ber...@savoirfairelinux.com> +on behalf of Savoir-Faire Linux. +--- + {a => b}/aconfigure | 194 +- + {a => b}/aconfigure.ac | 109 +- + {a => b}/pjlib/build/Makefile | 2 +- + {a => b}/pjlib/include/pj/compat/os_auto.h.in | 3 + + {a => b}/pjlib/include/pj/config.h | 4 +- + {a => b}/pjlib/include/pj/ssl_sock.h | 5 + + {a => b}/pjlib/src/pj/ssl_sock_common.c | 5 + + /dev/null => b/pjlib/src/pj/ssl_sock_gtls.c | 2867 +++++++++++++++++++++++++ + {a => b}/pjlib/src/pj/ssl_sock_ossl.c | 6 +- + 9 files changed, 3124 insertions(+), 71 deletions(-) + +diff --git a/aconfigure b/aconfigure +index 084ab0a..d4f4639 100755 +--- a/aconfigure ++++ b/aconfigure +@@ -637,6 +637,8 @@ ac_no_opencore_amrnb + libcrypto_present + libssl_present + openssl_h_present ++libgnutls_present ++gnutls_h_present + ac_no_ssl + ac_libyuv_ldflags + ac_libyuv_cflags +@@ -1469,8 +1471,8 @@ Optional Features: + package and samples location using IPPROOT and + IPPSAMPLES env var or with --with-ipp and + --with-ipp-samples options +- --disable-ssl Exclude SSL support the build (default: autodetect) +- ++ --enable-ssl=backend Select 'gnutls' or 'openssl' (default) to provide ++ SSL support (autodetect) + --disable-opencore-amr Exclude OpenCORE AMR support from the build + (default: autodetect) + +@@ -7644,33 +7646,160 @@ fi + + # Check whether --enable-ssl was given. + if test "${enable_ssl+set}" = set; then : +- enableval=$enable_ssl; +- if test "$enable_ssl" = "no"; then +- ac_no_ssl=1 +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if SSL support is disabled... yes" >&5 ++ enableval=$enable_ssl; if test "x$enableval" = "xgnutls"; then ++ ssl_backend="gnutls" ++ else ++ ssl_backend="openssl" ++ ++ fi ++ ++fi ++ ++ ++if test "x$enable_ssl" = "xno"; then ++ ac_no_ssl=1 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if SSL support is disabled... yes" >&5 + $as_echo "Checking if SSL support is disabled... yes" >&6; } +- fi ++else ++ if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then ++ CFLAGS="$CFLAGS -I$with_ssl/include" ++ LDFLAGS="$LDFLAGS -L$with_ssl/lib" ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using SSL prefix... $with_ssl" >&5 ++$as_echo "Using SSL prefix... $with_ssl" >&6; } ++ fi ++ if test "x$ssl_backend" = "xgnutls"; then ++ for ac_prog in $host-pkg-config pkg-config "python pkgconfig.py" ++do ++ # Extract the first word of "$ac_prog", so it can be a program name with args. ++set dummy $ac_prog; ac_word=$2 ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 ++$as_echo_n "checking for $ac_word... " >&6; } ++if ${ac_cv_prog_PKG_CONFIG+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ if test -n "$PKG_CONFIG"; then ++ ac_cv_prog_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test. ++else ++as_save_IFS=$IFS; IFS=$PATH_SEPARATOR ++for as_dir in $PATH ++do ++ IFS=$as_save_IFS ++ test -z "$as_dir" && as_dir=. ++ for ac_exec_ext in '' $ac_executable_extensions; do ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ++ ac_cv_prog_PKG_CONFIG="$ac_prog" ++ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 ++ break 2 ++ fi ++done ++ done ++IFS=$as_save_IFS + ++fi ++fi ++PKG_CONFIG=$ac_cv_prog_PKG_CONFIG ++if test -n "$PKG_CONFIG"; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 ++$as_echo "$PKG_CONFIG" >&6; } + else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 ++$as_echo "no" >&6; } ++fi ++ + +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for OpenSSL installations.." >&5 ++ 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" +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using SSL prefix... $with_ssl" >&5 +-$as_echo "Using SSL prefix... $with_ssl" >&6; } +- fi + + + +- ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default" ++ ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default" + if test "x$ac_cv_header_openssl_ssl_h" = xyes; then : + openssl_h_present=1 + fi + + +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ERR_load_BIO_strings in -lcrypto" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ERR_load_BIO_strings in -lcrypto" >&5 + $as_echo_n "checking for ERR_load_BIO_strings in -lcrypto... " >&6; } + if ${ac_cv_lib_crypto_ERR_load_BIO_strings+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -7710,7 +7839,7 @@ if test "x$ac_cv_lib_crypto_ERR_load_BIO_strings" = xyes; then : + libcrypto_present=1 && LIBS="-lcrypto -ldl -lz $LIBS" + fi + +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_library_init in -lssl" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_library_init in -lssl" >&5 + $as_echo_n "checking for SSL_library_init in -lssl... " >&6; } + if ${ac_cv_lib_ssl_SSL_library_init+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -7750,14 +7879,16 @@ if test "x$ac_cv_lib_ssl_SSL_library_init" = xyes; then : + libssl_present=1 && LIBS="-lssl $LIBS" + fi + +- if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL library found, SSL support enabled" >&5 ++ if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL library found, SSL support enabled" >&5 + $as_echo "OpenSSL library found, SSL support enabled" >&6; } +- # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK +- #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) +- $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h ++ # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK ++ #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) ++ $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h ++ ++ $as_echo "#define PJ_HAS_TLS_SOCK 0" >>confdefs.h + +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSLv2_method in -lssl" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSLv2_method in -lssl" >&5 + $as_echo_n "checking for SSLv2_method in -lssl... " >&6; } + if ${ac_cv_lib_ssl_SSLv2_method+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -7797,18 +7928,17 @@ if test "x$ac_cv_lib_ssl_SSLv2_method" = xyes; then : + libssl_no_ssl2=1 + fi + +- if test "x$libssl_no_ssl2" != "x1"; then +- CFLAGS="$CFLAGS -DOPENSSL_NO_SSL2=1" +- fi +- else +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: ** OpenSSL libraries not found, disabling SSL support **" >&5 +-$as_echo "** OpenSSL libraries not found, disabling SSL support **" >&6; } +- fi +- ++ if test "x$libssl_no_ssl2" != "x1"; then ++ CFLAGS="$CFLAGS -DOPENSSL_NO_SSL2=1" ++ fi ++ else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ** No OpenSSL libraries found, disabling SSL support **" >&5 ++$as_echo "** No OpenSSL libraries found, disabling SSL support **" >&6; } ++ fi ++ fi + fi + + +- + # Check whether --with-opencore-amrnb was given. + if test "${with_opencore_amrnb+set}" = set; then : + withval=$with_opencore_amrnb; as_fn_error $? "This option is obsolete and replaced by --with-opencore-amr=DIR" "$LINENO" 5 +diff --git a/aconfigure.ac b/aconfigure.ac +index 67cf24f..c6cbf82 100644 +--- a/aconfigure.ac ++++ b/aconfigure.ac +@@ -1512,42 +1512,81 @@ fi + + dnl # Include SSL support + AC_SUBST(ac_no_ssl) +-AC_ARG_ENABLE(ssl, +- AC_HELP_STRING([--disable-ssl], +- [Exclude SSL support the build (default: autodetect)]) +- , +- [ +- if test "$enable_ssl" = "no"; then +- [ac_no_ssl=1] +- AC_MSG_RESULT([Checking if SSL support is disabled... yes]) +- fi +- ], +- [ +- AC_MSG_RESULT([checking for OpenSSL installations..]) +- if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then +- CFLAGS="$CFLAGS -I$with_ssl/include" +- LDFLAGS="$LDFLAGS -L$with_ssl/lib" +- AC_MSG_RESULT([Using SSL prefix... $with_ssl]) ++AC_ARG_ENABLE([ssl], ++ AC_HELP_STRING([--enable-ssl[=backend]], ++ [Select 'gnutls' or 'openssl' (default) to provide SSL support (autodetect)]), ++ [ if test "x$enableval" = "xgnutls"; then ++ [ssl_backend="gnutls"] ++ else ++ [ssl_backend="openssl"] ++ + fi +- AC_SUBST(openssl_h_present) +- AC_SUBST(libssl_present) +- AC_SUBST(libcrypto_present) +- AC_CHECK_HEADER(openssl/ssl.h,[openssl_h_present=1]) +- AC_CHECK_LIB(crypto,ERR_load_BIO_strings,[libcrypto_present=1 && LIBS="-lcrypto -ldl -lz $LIBS"],,-ldl -lz) +- AC_CHECK_LIB(ssl,SSL_library_init,[libssl_present=1 && LIBS="-lssl $LIBS"]) +- if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then +- AC_MSG_RESULT([OpenSSL library found, SSL support enabled]) +- # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK +- #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) +- AC_DEFINE(PJ_HAS_SSL_SOCK, 1) +- AC_CHECK_LIB(ssl,SSLv2_method,[libssl_no_ssl2=1]) +- if test "x$libssl_no_ssl2" != "x1"; then +- CFLAGS="$CFLAGS -DOPENSSL_NO_SSL2=1" +- fi +- else +- AC_MSG_RESULT([** OpenSSL libraries not found, disabling SSL support **]) +- fi +- ]) ++ ]) ++ ++if test "x$enable_ssl" = "xno"; then ++ [ac_no_ssl=1] ++ AC_MSG_RESULT([Checking if SSL support is disabled... yes]) ++else ++ if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then ++ CFLAGS="$CFLAGS -I$with_ssl/include" ++ LDFLAGS="$LDFLAGS -L$with_ssl/lib" ++ AC_MSG_RESULT([Using SSL prefix... $with_ssl]) ++ fi ++ if test "x$ssl_backend" = "xgnutls"; then ++ AC_CHECK_PROGS(PKG_CONFIG, ++ $host-pkg-config pkg-config "python pkgconfig.py", ++ none) ++ AC_MSG_RESULT([checking for GnuTLS installations..]) ++ AC_SUBST(gnutls_h_present) ++ AC_SUBST(libgnutls_present) ++ AC_CHECK_HEADER(gnutls/gnutls.h, [gnutls_h_present=1]) ++ ++ if test "$PKG_CONFIG" != "none"; then ++ if $PKG_CONFIG --exists gnutls; then ++ LIBS="$LIBS `$PKG_CONFIG --libs gnutls`" ++ libgnutls_present=1 ++ else ++ AC_CHECK_LIB(gnutls, ++ gnutls_certificate_set_x509_system_trust, ++ [libgnutls_present=1 && ++ LIBS="$LIBS -lgnutls"]) ++ fi ++ else ++ AC_MSG_RESULT([*** Warning: neither pkg-config nor python is available, disabling gnutls. ***]) ++ fi ++ ++ if test "x$gnutls_h_present" = "x1" -a "x$libgnutls_present" = "x1"; then ++ AC_MSG_RESULT([GnuTLS library found, SSL support enabled]) ++ # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK ++ #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) ++ AC_DEFINE(PJ_HAS_SSL_SOCK, 1) ++ AC_DEFINE(PJ_HAS_TLS_SOCK, 1) ++ else ++ AC_MSG_RESULT([** No GnuTLS libraries found, disabling SSL support **]) ++ fi ++ else ++ AC_MSG_RESULT([checking for OpenSSL installations..]) ++ AC_SUBST(openssl_h_present) ++ AC_SUBST(libssl_present) ++ AC_SUBST(libcrypto_present) ++ AC_CHECK_HEADER(openssl/ssl.h, [openssl_h_present=1]) ++ AC_CHECK_LIB(crypto,ERR_load_BIO_strings,[libcrypto_present=1 && LIBS="-lcrypto -ldl -lz $LIBS"],,-ldl -lz) ++ AC_CHECK_LIB(ssl,SSL_library_init,[libssl_present=1 && LIBS="-lssl $LIBS"]) ++ if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then ++ AC_MSG_RESULT([OpenSSL library found, SSL support enabled]) ++ # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK ++ #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) ++ AC_DEFINE(PJ_HAS_SSL_SOCK, 1) ++ AC_DEFINE(PJ_HAS_TLS_SOCK, 0) ++ AC_CHECK_LIB(ssl,SSLv2_method,[libssl_no_ssl2=1]) ++ if test "x$libssl_no_ssl2" != "x1"; then ++ CFLAGS="$CFLAGS -DOPENSSL_NO_SSL2=1" ++ fi ++ else ++ AC_MSG_RESULT([** No OpenSSL libraries found, disabling SSL support **]) ++ fi ++ fi ++fi + + dnl # Obsolete option --with-opencore-amrnb + AC_ARG_WITH(opencore-amrnb, +diff --git a/pjlib/build/Makefile b/pjlib/build/Makefile +index 1e64950..e650a31 100644 +--- a/pjlib/build/Makefile ++++ b/pjlib/build/Makefile +@@ -35,7 +35,7 @@ export PJLIB_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ + guid.o hash.o ip_helper_generic.o list.o lock.o log.o os_time_common.o \ + os_info.o pool.o pool_buf.o pool_caching.o pool_dbg.o rand.o \ + rbtree.o sock_common.o sock_qos_common.o sock_qos_bsd.o \ +- ssl_sock_common.o ssl_sock_ossl.o ssl_sock_dump.o \ ++ ssl_sock_common.o ssl_sock_ossl.o ssl_sock_gtls.o ssl_sock_dump.o \ + string.o timer.o types.o + export PJLIB_CFLAGS += $(_CFLAGS) + export PJLIB_CXXFLAGS += $(_CXXFLAGS) +diff --git a/pjlib/include/pj/compat/os_auto.h.in b/pjlib/include/pj/compat/os_auto.h.in +index 18df2bf..9295740 100644 +--- a/pjlib/include/pj/compat/os_auto.h.in ++++ b/pjlib/include/pj/compat/os_auto.h.in +@@ -206,6 +206,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 --git a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h +index 08116cd..6d042fd 100644 +--- a/pjlib/include/pj/config.h ++++ b/pjlib/include/pj/config.h +@@ -854,13 +854,15 @@ + + /** + * Enable secure socket. For most platforms, this is implemented using +- * OpenSSL, so this will require OpenSSL to be installed. For Symbian ++ * OpenSSL, so this will require OpenSSL or GnuTLS to be installed. For Symbian + * platform, this is implemented natively using CSecureSocket. + * + * Default: 0 (for now) + */ + #ifndef PJ_HAS_SSL_SOCK + # define PJ_HAS_SSL_SOCK 0 ++ // When set to 1 secure sockets will use the GnuTLS backend ++# define PJ_HAS_TLS_SOCK 0 + #endif + + +diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h +index 161bcf3..0b8d1fc 100644 +--- a/pjlib/include/pj/ssl_sock.h ++++ b/pjlib/include/pj/ssl_sock.h +@@ -181,6 +181,11 @@ typedef struct pj_ssl_cert_info { + } subj_alt_name; /**< Subject alternative + name extension */ + ++ struct { ++ unsigned cnt; /**< # of entry */ ++ pj_str_t* cert_raw; ++ } raw_chain; ++ + } pj_ssl_cert_info; + + +diff --git a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c +index 913efee..ac7f683 100644 +--- a/pjlib/src/pj/ssl_sock_common.c ++++ b/pjlib/src/pj/ssl_sock_common.c +@@ -34,7 +34,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 +diff --git b/pjlib/src/pj/ssl_sock_gtls.c b/pjlib/src/pj/ssl_sock_gtls.c +new file mode 100644 +index 0000000..5a383d7 +--- /dev/null ++++ b/pjlib/src/pj/ssl_sock_gtls.c +@@ -0,0 +1,2867 @@ ++/* $Id$ */ ++/* ++ * Copyright (C) 2014-2015 Savoir-Faire Linux. (http://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 ++#include <dirent.h> ++#endif ++ ++/* 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, "gnutls") ++#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_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, PJ_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, PJ_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:"); ++ } ++ if (ssock->param.proto & PJ_SSL_SOCK_PROTO_SSL3) { ++ pj_strcat2(&priority, "+VERS-SSL3.0:"); ++ } else { ++ 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, ++ 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->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, 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) ++{ ++ pj_activesock_cb asock_cb; ++ pj_activesock_cfg asock_cfg; ++ pj_status_t status; ++ ++ PJ_ASSERT_RETURN(ssock && pool && localaddr && 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 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 */ ++ 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 --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c +index 513d754..d1db3f0 100644 +--- a/pjlib/src/pj/ssl_sock_ossl.c ++++ b/pjlib/src/pj/ssl_sock_ossl.c +@@ -31,8 +31,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 --git a/gnu/packages/telephony.scm b/gnu/packages/telephony.scm index b27dd1f..e4668cf 100644 --- a/gnu/packages/telephony.scm +++ b/gnu/packages/telephony.scm @@ -22,11 +22,15 @@ (define-module (gnu packages telephony) #:use-module (gnu packages) + #:use-module (gnu packages audio) #:use-module (gnu packages autotools) #:use-module (gnu packages gnupg) #:use-module (gnu packages linux) #:use-module (gnu packages pkg-config) + #:use-module (gnu packages pulseaudio) + #:use-module (gnu packages python) #:use-module (gnu packages tls) + #:use-module (gnu packages xiph) #:use-module (guix licenses) #:use-module (guix packages) #:use-module (guix download) @@ -252,3 +256,105 @@ Voice-over-IP (VoIP) communications.") ;; covered under the 'GPL'. ;; The package as a whole is distributed under the LGPL 2.0. (license (list lgpl2.0 public-domain gpl2+))))) + + +(define-public pjproject-for-libring + (package + (name "pjproject") + (version "2.4") + (source + (origin + (method url-fetch) + (uri (string-append + "http://www.pjsip.org/release/" + version "/" name "-" version ".tar.bz2")) + (modules '((guix build utils))) + (patches (search-patches "pjproject-errno.patch" + "pjproject-endianness.patch" + "pjproject-use-gnutls.patch" + ; "pjproject-no-test-apps.patch" breaks tests + "pjproject-ipv6.patch" + "pjproject-ice-config.patch" + "pjproject-multiple-listeners.patch" + "pjproject-ice-sess.patch")) + ;; All these patches are distributed with the libring source code by + ;; the Ring project. + (snippet + '(begin + (delete-file-recursively "third_party") + (substitute* "aconfigure.ac" + (("third_party/build/os-auto.mak") "") + (("third_party/build/portaudio/os-auto.mak") "")) + (substitute* "pjmedia/src/pjmedia/resample_libsamplerate.c" + (("../../third_party/libsamplerate/src/samplerate.h") + "samplerate.h")) + (substitute* "pjmedia/src/pjmedia/resample_resample.c" + (("third_party/resample/include/resamplesubs.h") + "resamplesubs.h")) + (substitute* "Makefile" + (("third_party/build") "")))) + (sha256 + (base32 + "0n90n1p41svf23d4fag8jqbjnv82fz14z6zchb8j1kldvap1b00h")))) + ;"1jvxhny268nannz242rj1yfh2j1iyhn62sfdvm9zych8gbnkp9n5")))) version 2.5.1 + (build-system gnu-build-system) + (inputs + `(("libsrtp" ,libsrtp) + ("portaudio" ,portaudio) + ("opus" ,opus) + ("speex" ,speex) + ("libsamplerate" ,libsamplerate) + ("gnutls" ,gnutls) + ("alsa-lib" ,alsa-lib))) + (native-inputs + `(("autoconf" ,autoconf) + ("automake" ,automake) + ("python" ,python) + ("pkg-config" ,pkg-config) + ("libtool" ,libtool))) + (arguments + `(#:test-target + "selftest" + #:make-flags + (list (string-append "CFLAGS+=" + "-DPJ_ICE_MAX_CAND=32" " " + "-DPJ_ICE_MAX_CHECKS=150" " " + "-DPJMEDIA_AUDIO_DEV_HAS_WMME=0" " " + "-DPJ_ICE_COMP_BITS=2")) + #:configure-flags + (list "--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-ilbc-codec" + "--disable-opencore-amr" + "--disable-sdl" + "--disable-ffmpeg" + "--disable-v4l2" + "--enable-ssl=gnutls" + "--enable-libsamplerate" + ; "--with-external-speex" + "--disable-speex-codec" + "--with-external-pa" + "--with-external-srtp") + #:phases + (modify-phases %standard-phases + (add-before 'patch-source-shebangs 'autoconf + (lambda _ + (zero? + (system* "autoconf" "-v" "-f" "-i" "-o" + "aconfigure" "aconfigure.ac"))))))) + (home-page "www.pjsip.org") + (synopsis "Session Initiation Protocol (SIP) stack") + (description "PJProject provides an implementation of the Session +Initiation Protocol (SIP) and a multimedia framework. + +This package is intended for use with libring. There are several custom +patches, most notably the use of gnutls instead of openssl for encryption.") + (license gpl2+))) -- 2.7.4