diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-12-25 06:43:59 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-12-25 06:43:59 +0000 |
commit | d6a9dded719756a7a873f1f9a6c1d201859b0ee6 (patch) | |
tree | 9db7148d26d53dedb2c91dc8bbb5da20c163faa0 /pjsip | |
parent | 94aaa0ce2af8bf9799752bb6cb6c9989d60bc612 (diff) |
Major TLS work (ticket #3): asynchronous socket, rather complete TLS options, and pjsua integration. The TLS support should work in both client and server mode.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@861 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip')
-rw-r--r-- | pjsip/include/pjsip/sip_config.h | 28 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_errno.h | 70 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_transport_tcp.h | 9 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_transport_tls.h | 142 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua.h | 15 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_errno.c | 15 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_transport.c | 18 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_transport_tls_ossl.c | 2489 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 40 |
9 files changed, 2117 insertions, 709 deletions
diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h index a66dc37e..6b13ee0a 100644 --- a/pjsip/include/pjsip/sip_config.h +++ b/pjsip/include/pjsip/sip_config.h @@ -200,6 +200,18 @@ /** + * The TCP incoming connection backlog number to be set in accept(). + * + * Default: 5 + * + * @see PJSIP_TLS_TRANSPORT_BACKLOG + */ +#ifndef PJSIP_TCP_TRANSPORT_BACKLOG +# define PJSIP_TCP_TRANSPORT_BACKLOG 5 +#endif + + +/** * This macro specifies whether full DNS resolution should be used. * When enabled, #pjsip_resolve() will perform asynchronous DNS SRV and * A (or AAAA, when IPv6 is supported) resolution to resolve the SIP @@ -216,6 +228,8 @@ * it should also exclude dns.o and resolve.o from PJLIB-UTIL. * * Default: 1 (enabled) + * + * @see PJSIP_MAX_RESOLVED_ADDRESSES */ #ifndef PJSIP_HAS_RESOLVER # define PJSIP_HAS_RESOLVER 1 @@ -228,6 +242,8 @@ * 32 bytes of stack memory. * * Default: 8 + * + * @see PJSIP_HAS_RESOLVER */ #ifndef PJSIP_MAX_RESOLVED_ADDRESSES # define PJSIP_MAX_RESOLVED_ADDRESSES 8 @@ -245,6 +261,18 @@ #endif +/** + * The TLS pending incoming connection backlog number to be set in accept(). + * + * Default: 5 + * + * @see PJSIP_TCP_TRANSPORT_BACKLOG + */ +#ifndef PJSIP_TLS_TRANSPORT_BACKLOG +# define PJSIP_TLS_TRANSPORT_BACKLOG 5 +#endif + + /* Endpoint. */ #define PJSIP_MAX_TIMER_COUNT (2*PJSIP_MAX_TSX_COUNT + 2*PJSIP_MAX_DIALOG_COUNT) diff --git a/pjsip/include/pjsip/sip_errno.h b/pjsip/include/pjsip/sip_errno.h index 92ca57b6..03d21ec2 100644 --- a/pjsip/include/pjsip/sip_errno.h +++ b/pjsip/include/pjsip/sip_errno.h @@ -397,6 +397,76 @@ PJ_BEGIN_DECL #define PJSIP_ESESSIONSTATE (PJSIP_ERRNO_START_PJSIP+141) /* 171141 */ +/************************************************************ + * TLS TRANSPORT ERRORS + ***********************************************************/ +/** + * @hideinitializer + * Unknown TLS error + */ +#define PJSIP_TLS_EUNKNOWN (PJSIP_ERRNO_START_PJSIP+160) /* 171160 */ +/** + * @hideinitializer + * Invalid SSL protocol method. + */ +#define PJSIP_TLS_EINVMETHOD (PJSIP_ERRNO_START_PJSIP+161) /* 171161 */ +/** + * @hideinitializer + * Error loading/verifying SSL CA list file. + */ +#define PJSIP_TLS_ECACERT (PJSIP_ERRNO_START_PJSIP+162) /* 171162 */ +/** + * @hideinitializer + * Error loading SSL certificate chain file. + */ +#define PJSIP_TLS_ECERTFILE (PJSIP_ERRNO_START_PJSIP+163) /* 171163 */ +/** + * @hideinitializer + * Error adding private key from SSL certificate file. + */ +#define PJSIP_TLS_EKEYFILE (PJSIP_ERRNO_START_PJSIP+164) /* 171164 */ +/** + * @hideinitializer + * Error setting SSL cipher list. + */ +#define PJSIP_TLS_ECIPHER (PJSIP_ERRNO_START_PJSIP+165) /* 171165 */ +/** + * @hideinitializer + * Error creating SSL context. + */ +#define PJSIP_TLS_ECTX (PJSIP_ERRNO_START_PJSIP+166) /* 171166 */ +/** + * @hideinitializer + * Error creating SSL connection object. + */ +#define PJSIP_TLS_ESSLCONN (PJSIP_ERRNO_START_PJSIP+167) /* 171167 */ +/** + * @hideinitializer + * Unknown error when performing SSL connect(). + */ +#define PJSIP_TLS_ECONNECT (PJSIP_ERRNO_START_PJSIP+168) /* 171168 */ +/** + * @hideinitializer + * Unknown error when performing SSL accept(). + */ +#define PJSIP_TLS_EACCEPT (PJSIP_ERRNO_START_PJSIP+169) /* 171169 */ +/** + * @hideinitializer + * Unknown error when sending SSL data + */ +#define PJSIP_TLS_ESEND (PJSIP_ERRNO_START_PJSIP+170) /* 171170 */ +/** + * @hideinitializer + * Unknown error when reading SSL data + */ +#define PJSIP_TLS_EREAD (PJSIP_ERRNO_START_PJSIP+171) /* 171171 */ +/** + * @hideinitializer + * SSL negotiation has exceeded the maximum configured timeout. + */ +#define PJSIP_TLS_ETIMEDOUT (PJSIP_ERRNO_START_PJSIP+172) /* 171172 */ + + PJ_END_DECL diff --git a/pjsip/include/pjsip/sip_transport_tcp.h b/pjsip/include/pjsip/sip_transport_tcp.h index db308d7d..09ab157f 100644 --- a/pjsip/include/pjsip/sip_transport_tcp.h +++ b/pjsip/include/pjsip/sip_transport_tcp.h @@ -38,15 +38,6 @@ PJ_BEGIN_DECL */ /** - * The TCP incoming connection backlog number. - * Default: 5 - */ -#ifndef PJSIP_TCP_TRANSPORT_BACKLOG -# define PJSIP_TCP_TRANSPORT_BACKLOG 5 -#endif - - -/** * Register support for SIP TCP transport by creating TCP listener on * the specified address and port. This function will create an * instance of SIP TCP transport factory and register it to the diff --git a/pjsip/include/pjsip/sip_transport_tls.h b/pjsip/include/pjsip/sip_transport_tls.h index bef81acf..adf7342c 100644 --- a/pjsip/include/pjsip/sip_transport_tls.h +++ b/pjsip/include/pjsip/sip_transport_tls.h @@ -25,6 +25,8 @@ */ #include <pjsip/sip_transport.h> +#include <pj/string.h> + PJ_BEGIN_DECL @@ -37,6 +39,138 @@ PJ_BEGIN_DECL * the transport to the framework. */ +/** SSL protocol method constants. */ +typedef enum pjsip_ssl_method +{ + PJSIP_SSL_DEFAULT_METHOD = 0, /**< Default protocol method. */ + PJSIP_TLSV1_METHOD = 1, /**< Use SSLv1 method. */ + PJSIP_SSLV2_METHOD = 2, /**< Use SSLv2 method. */ + PJSIP_SSLV3_METHOD = 3, /**< Use SSLv3 method. */ + PJSIP_SSLV23_METHOD = 23 /**< Use SSLv23 method. */ +} pjsip_ssl_method; + + +/** + * TLS transport settings. + */ +typedef struct pjsip_tls_setting +{ + /** + * Certificate of Authority (CA) list file. + */ + pj_str_t ca_list_file; + + /** + * Public endpoint certificate file, which will be used as client- + * side certificate for outgoing TLS connection, and server-side + * certificate for incoming TLS connection. + */ + pj_str_t cert_file; + + /** + * Optional private key of the endpoint certificate to be used. + */ + pj_str_t privkey_file; + + /** + * Password to open private key. + */ + pj_str_t password; + + /** + * TLS protocol method from #pjsip_ssl_method, which can be: + * - PJSIP_SSL_DEFAULT_METHOD(0): default (which will use SSLv23) + * - PJSIP_TLSV1_METHOD(1): TLSv1 + * - PJSIP_SSLV2_METHOD(2): TLSv2 + * - PJSIP_SSLV3_METHOD(3): TLSv3 + * - PJSIP_SSLV23_METHOD(23): TLSv23 + * + * Default is PJSIP_SSL_DEFAULT_METHOD (0), which will use SSLv23 + * protocol method. + */ + int method; + + /** + * TLS cipher list string in OpenSSL format. If empty, then default + * cipher list of the backend will be used. + */ + pj_str_t ciphers; + + /** + * When PJSIP is acting as a client (outgoing TLS connections), + * it will always receive a certificate from the peer. + * If \a verify_server is disabled (set to zero), PJSIP will not + * verifiy the certificate and allows TLS connections to servers + * which do not present a valid certificate. + * If \a tls_verify_server is non-zero, PJSIP verifies the server + * certificate and will close the TLS connection if the server + * certificate is not valid. + * + * This setting corresponds to OpenSSL SSL_VERIFY_PEER flag. + * Default value is zero. + */ + pj_bool_t verify_server; + + /** + * When acting as server (incoming TLS connections), setting + * \a verify_client to non-zero will cause the transport to activate + * peer verification upon receiving incoming TLS connection. + * + * This setting corresponds to OpenSSL SSL_VERIFY_PEER flag. + * Default value is zero. + */ + pj_bool_t verify_client; + + /** + * When acting as server (incoming TLS connections), reject inocming + * connection if client doesn't have a valid certificate. + * + * This setting corresponds to SSL_VERIFY_FAIL_IF_NO_PEER_CERT flag. + * Default value is zero. + */ + pj_bool_t require_client_cert; + + /** + * TLS negotiation timeout to be applied for both outgoing and + * incoming connection. If both sec and msec member is set to zero, + * the SSL negotiation doesn't have a timeout. + */ + pj_time_val timeout; + +} pjsip_tls_setting; + + +/** + * Initialize TLS setting with default values. + * + * @param tls_opt The TLS setting to be initialized. + */ +PJ_INLINE(void) pjsip_tls_setting_default(pjsip_tls_setting *tls_opt) +{ + pj_memset(tls_opt, 0, sizeof(*tls_opt)); +} + + +/** + * Copy TLS setting. + * + * @param pool The pool to duplicate strings etc. + * @param dst Destination structure. + * @param src Source structure. + */ +PJ_INLINE(void) pjsip_tls_setting_copy(pj_pool_t *pool, + pjsip_tls_setting *dst, + const pjsip_tls_setting *src) +{ + pj_memcpy(dst, src, sizeof(*dst)); + pj_strdup_with_null(pool, &dst->ca_list_file, &src->ca_list_file); + pj_strdup_with_null(pool, &dst->cert_file, &src->cert_file); + pj_strdup_with_null(pool, &dst->privkey_file, &src->privkey_file); + pj_strdup_with_null(pool, &dst->password, &src->password); + pj_strdup_with_null(pool, &dst->ciphers, &src->ciphers); +} + + /** * Register support for SIP TLS transport by creating TLS listener on * the specified address and port. This function will create an @@ -44,9 +178,7 @@ PJ_BEGIN_DECL * transport manager. * * @param endpt The SIP endpoint. - * @param keyfile Path to keys and certificate file. - * @param password Password to open the private key. - * @param ca_list_file Path to Certificate of Authority file. + * @param opt Optional TLS settings. * @param local Optional local address to bind, or specify the * address to bind the server socket to. Both IP * interface address and port fields are optional. @@ -71,9 +203,7 @@ PJ_BEGIN_DECL * the appropriate error code. */ PJ_DECL(pj_status_t) pjsip_tls_transport_start(pjsip_endpoint *endpt, - const pj_str_t *keyfile, - const pj_str_t *password, - const pj_str_t *ca_list_file, + const pjsip_tls_setting *opt, const pj_sockaddr_in *local, const pjsip_host_port *a_name, unsigned async_cnt, diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index a964c49f..eda850a5 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -841,19 +841,9 @@ typedef struct pjsua_transport_config pjsua_stun_config stun_config; /** - * TLS root CA file path (only used for TLS transport). + * TLS settings. */ - pj_str_t tls_ca_file; - - /** - * TLS client key path (only used for TLS transport). - */ - pj_str_t tls_key_file; - - /** - * TLS password (only used for TLS transport). - */ - pj_str_t tls_password; + pjsip_tls_setting tls_setting; } pjsua_transport_config; @@ -866,6 +856,7 @@ typedef struct pjsua_transport_config PJ_INLINE(void) pjsua_transport_config_default(pjsua_transport_config *cfg) { pj_bzero(cfg, sizeof(*cfg)); + pjsip_tls_setting_default(&cfg->tls_setting); } diff --git a/pjsip/src/pjsip/sip_errno.c b/pjsip/src/pjsip/sip_errno.c index c32481c8..e9f126f5 100644 --- a/pjsip/src/pjsip/sip_errno.c +++ b/pjsip/src/pjsip/sip_errno.c @@ -108,6 +108,21 @@ static const struct /* Invite session. */ PJ_BUILD_ERR( PJSIP_ESESSIONTERMINATED, "INVITE session already terminated" ), PJ_BUILD_ERR( PJSIP_ESESSIONSTATE, "Invalid INVITE session state" ), + + /* SSL errors */ + PJ_BUILD_ERR( PJSIP_TLS_EUNKNOWN, "Unknown TLS error" ), + PJ_BUILD_ERR( PJSIP_TLS_EINVMETHOD, "Invalid SSL protocol method" ), + PJ_BUILD_ERR( PJSIP_TLS_ECACERT, "Error loading/verifying SSL CA list file"), + PJ_BUILD_ERR( PJSIP_TLS_ECERTFILE, "Error loading SSL certificate chain file"), + PJ_BUILD_ERR( PJSIP_TLS_EKEYFILE, "Error adding private key from SSL certificate file"), + PJ_BUILD_ERR( PJSIP_TLS_ECIPHER, "Error setting SSL cipher list"), + PJ_BUILD_ERR( PJSIP_TLS_ECTX, "Error creating SSL context"), + PJ_BUILD_ERR( PJSIP_TLS_ESSLCONN, "Error creating SSL connection object"), + PJ_BUILD_ERR( PJSIP_TLS_ECONNECT, "Unknown error when performing SSL connect()"), + PJ_BUILD_ERR( PJSIP_TLS_EACCEPT, "Unknown error when performing SSL accept()"), + PJ_BUILD_ERR( PJSIP_TLS_ESEND, "Unknown error when sending SSL data"), + PJ_BUILD_ERR( PJSIP_TLS_EREAD, "Unknown error when reading SSL data"), + PJ_BUILD_ERR( PJSIP_TLS_ETIMEDOUT, "SSL negotiation has timed out"), }; diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c index 6f53d6ee..8fd80d52 100644 --- a/pjsip/src/pjsip/sip_transport.c +++ b/pjsip/src/pjsip/sip_transport.c @@ -1289,6 +1289,7 @@ PJ_DEF(void) pjsip_tpmgr_dump_transports(pjsip_tpmgr *mgr) #if PJ_LOG_MAX_LEVEL >= 3 pj_hash_iterator_t itr_val; pj_hash_iterator_t *itr; + pjsip_tpfactory *factory; pj_lock_acquire(mgr->lock); @@ -1297,6 +1298,18 @@ PJ_DEF(void) pjsip_tpmgr_dump_transports(pjsip_tpmgr *mgr) pj_atomic_get(mgr->tdata_counter))); #endif + PJ_LOG(3, (THIS_FILE, " Dumping listeners:")); + factory = mgr->factory_list.next; + while (factory != &mgr->factory_list) { + PJ_LOG(3, (THIS_FILE, " %s %s:%.*s:%d", + factory->obj_name, + factory->type_name, + (int)factory->addr_name.host.slen, + factory->addr_name.host.ptr, + (int)factory->addr_name.port)); + factory = factory->next; + } + itr = pj_hash_first(mgr->table, &itr_val); if (itr) { PJ_LOG(3, (THIS_FILE, " Dumping transports:")); @@ -1304,10 +1317,11 @@ PJ_DEF(void) pjsip_tpmgr_dump_transports(pjsip_tpmgr *mgr) do { pjsip_transport *t = pj_hash_this(mgr->table, itr); - PJ_LOG(3, (THIS_FILE, " %s %s (refcnt=%d)", + PJ_LOG(3, (THIS_FILE, " %s %s (refcnt=%d%s)", t->obj_name, t->info, - pj_atomic_get(t->ref_cnt))); + pj_atomic_get(t->ref_cnt), + (t->idle_timer.id ? " [idle]" : ""))); itr = pj_hash_next(mgr->table, itr); } while (itr); diff --git a/pjsip/src/pjsip/sip_transport_tls_ossl.c b/pjsip/src/pjsip/sip_transport_tls_ossl.c index 0e69ed79..97d820f7 100644 --- a/pjsip/src/pjsip/sip_transport_tls_ossl.c +++ b/pjsip/src/pjsip/sip_transport_tls_ossl.c @@ -16,30 +16,42 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <pjsip/sip_transport.h> +#include <pjsip/sip_transport_tls.h> #include <pjsip/sip_endpoint.h> #include <pjsip/sip_errno.h> +#include <pj/compat/socket.h> #include <pj/addr_resolv.h> #include <pj/assert.h> +#include <pj/ioqueue.h> #include <pj/lock.h> #include <pj/log.h> #include <pj/os.h> #include <pj/pool.h> +#include <pj/compat/socket.h> #include <pj/sock_select.h> #include <pj/string.h> -#include <pj/compat/socket.h> /* Only build when PJSIP_HAS_TLS_TRANSPORT is enabled */ #if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0 -/* OpenSSL headers */ +/* + * Include OpenSSL headers + */ #include <openssl/bio.h> #include <openssl/ssl.h> #include <openssl/err.h> +/* + * With VisualC++, it's not possible to dynamically link against some + * libraries when some macros are defined (unlike "make" based build + * system where this can be easily manipulated). + * + * So for VisualC++ IDE, include OpenSSL libraries in the linking by + * using the #pragma lib construct below. + */ #ifdef _MSC_VER # ifdef _DEBUG # pragma comment( lib, "libeay32MTd") @@ -50,156 +62,210 @@ # endif #endif + +#define THIS_FILE "transport_tls_ossl.c" + +#define MAX_ASYNC_CNT 16 +#define POOL_LIS_INIT 4000 +#define POOL_LIS_INC 4001 +#define POOL_TP_INIT 4000 +#define POOL_TP_INC 4002 + + + /** - * @hideinitializer - * Unable to read SSL certificate file. - */ -#define PJSIP_TLS_ECERTFILE PJ_EUNKNOWN -/** - * @hideinitializer - * Unable to read SSL private key file. - */ -#define PJSIP_TLS_EKEYFILE PJ_EUNKNOWN -/** - * @hideinitializer - * Error creating SSL context. - */ -#define PJSIP_TLS_ECTX PJ_EUNKNOWN -/** - * @hideinitializer - * Unable to list SSL CA list. - */ -#define PJSIP_TLS_ECALIST PJ_EUNKNOWN -/** - * @hideinitializer - * SSL connect() error - */ -#define PJSIP_TLS_ECONNECT PJ_EUNKNOWN -/** - * @hideinitializer - * Error sending SSL data + * Get the number of descriptors in the set. This is defined in sock_select.c + * This function will only return the number of sockets set from PJ_FD_SET + * operation. When the set is modified by other means (such as by select()), + * the count will not be reflected here. + * + * That's why don't export this function in the header file, to avoid + * misunderstanding. + * + * @param fdsetp The descriptor set. + * + * @return Number of descriptors in the set. */ -#define PJSIP_TLS_ESEND PJ_EUNKNOWN +PJ_DECL(pj_size_t) PJ_FD_COUNT(const pj_fd_set_t *fdsetp); + -#define THIS_FILE "tls_ssl" +struct tls_listener; +struct tls_transport; /* - * TLS transport factory/listener. + * This structure is "descendant" of pj_ioqueue_op_key_t, and it is used to + * track pending/asynchronous accept() operation. TLS transport may have + * more than one pending accept() operations, depending on the value of + * async_cnt. + */ +struct pending_accept +{ + pj_ioqueue_op_key_t op_key; + struct tls_listener *listener; + unsigned index; + pj_pool_t *pool; + pj_sock_t new_sock; + int addr_len; + pj_sockaddr_in local_addr; + pj_sockaddr_in remote_addr; +}; + + +/* + * This is the TLS listener, which is a "descendant" of pjsip_tpfactory (the + * SIP transport factory). */ struct tls_listener { - pjsip_tpfactory base; - pjsip_endpoint *endpt; - pjsip_tpmgr *tpmgr; + pjsip_tpfactory factory; + pj_bool_t is_registered; + pjsip_tls_setting setting; + pjsip_endpoint *endpt; + pjsip_tpmgr *tpmgr; + pj_sock_t sock; + pj_ioqueue_key_t *key; + unsigned async_cnt; + struct pending_accept *accept_op[MAX_ASYNC_CNT]; + + SSL_CTX *ctx; +}; - pj_bool_t is_registered; - SSL_CTX *ctx; - pj_str_t password; +/* + * This structure is used to keep delayed transmit operation in a list. + * A delayed transmission occurs when application sends tx_data when + * the TLS connect/establishment is still in progress. These delayed + * transmission will be "flushed" once the socket is connected (either + * successfully or with errors). + */ +struct delayed_tdata +{ + PJ_DECL_LIST_MEMBER(struct delayed_tdata); + pjsip_tx_data_op_key *tdata_op_key; }; /* - * TLS transport. + * This structure describes the TLS transport, and it's descendant of + * pjsip_transport. */ struct tls_transport { - pjsip_transport base; - - pj_sock_t sock; - SSL *ssl; + pjsip_transport base; + pj_bool_t is_server; + struct tls_listener *listener; + pj_bool_t is_registered; + pj_bool_t is_closing; + pj_status_t close_reason; + pj_sock_t sock; + pj_ioqueue_key_t *key; + pj_bool_t has_pending_connect; + + /* SSL connection */ + SSL *ssl; + pj_bool_t ssl_shutdown_called; + + /* TLS transport can only have one rdata! + * Otherwise chunks of incoming PDU may be received on different + * buffer. + */ + pjsip_rx_data rdata; - pjsip_rx_data rdata; - pj_bool_t quitting; - pj_thread_t *thread; + /* Pending transmission list. */ + struct delayed_tdata delayed_list; }; -/* - * TLS factory callbacks. +/**************************************************************************** + * PROTOTYPES */ + +/* This callback is called when pending accept() operation completes. */ +static void on_accept_complete( pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_sock_t sock, + pj_status_t status); + +/* This callback is called by transport manager to destroy listener */ +static pj_status_t lis_destroy(pjsip_tpfactory *factory); + +/* This callback is called by transport manager to create transport */ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, pjsip_tpmgr *mgr, pjsip_endpoint *endpt, const pj_sockaddr *rem_addr, int addr_len, pjsip_transport **transport); -static pj_status_t lis_destroy(pjsip_tpfactory *factory); - -/* - * TLS transport callback. - */ -static pj_status_t tls_tp_send_msg(pjsip_transport *transport, - pjsip_tx_data *tdata, - const pj_sockaddr_t *rem_addr, - int addr_len, - void *token, - void (*callback)(pjsip_transport *transport, - void *token, - pj_ssize_t sent_bytes)); -static pj_status_t tls_tp_do_shutdown(pjsip_transport *transport); -static pj_status_t tls_tp_destroy(pjsip_transport *transport); +/* Common function to create and initialize transport */ +static pj_status_t tls_create(struct tls_listener *listener, + pj_pool_t *pool, + pj_sock_t sock, pj_bool_t is_server, + const pj_sockaddr_in *local, + const pj_sockaddr_in *remote, + struct tls_transport **p_tls); - - -/* - * Static vars. +/**************************************************************************** + * SSL FUNCTIONS */ -static int tls_init_count; /* ssl_report_error() */ -static void ssl_report_error(int level, const char *sender, +static void ssl_report_error(const char *sender, int level, + pj_status_t status, const char *format, ...) { - va_list arg; - unsigned long ssl_err; + va_list marker; - va_start(arg, format); - ssl_err = ERR_get_error(); + va_start(marker, format); - if (ssl_err == 0) { - pj_log(sender, level, format, arg); - } else { - char err_format[512]; + if (status != PJ_SUCCESS) { + char err_format[PJ_ERR_MSG_SIZE + 512]; int len; len = pj_ansi_snprintf(err_format, sizeof(err_format), "%s: ", format); - ERR_error_string(ssl_err, err_format+len); + pj_strerror(status, err_format+len, sizeof(err_format)-len); - pj_log(sender, level, err_format, arg); - } + pj_log(sender, level, err_format, marker); - va_end(arg); -} + } else { + unsigned long ssl_err; + ssl_err = ERR_get_error(); -/* Initialize OpenSSL */ -static pj_status_t init_openssl(void) -{ - if (++tls_init_count != 1) - return PJ_SUCCESS; - - SSL_library_init(); - SSL_load_error_strings(); + if (ssl_err == 0) { + pj_log(sender, level, format, marker); + } else { + char err_format[512]; + int len; - ERR_load_BIO_strings(); - OpenSSL_add_all_algorithms(); + len = pj_ansi_snprintf(err_format, sizeof(err_format), + "%s: ", format); + ERR_error_string(ssl_err, err_format+len); + + pj_log(sender, level, err_format, marker); + } + } - return PJ_SUCCESS; + va_end(marker); } -/* Shutdown OpenSSL */ -static void shutdown_openssl(void) + +static void sockaddr_to_host_port( pj_pool_t *pool, + pjsip_host_port *host_port, + const pj_sockaddr_in *addr ) { - if (--tls_init_count != 0) - return; + enum { M = 48 }; + host_port->host.ptr = pj_pool_alloc(pool, M); + host_port->host.slen = pj_ansi_snprintf( host_port->host.ptr, M, "%s", + pj_inet_ntoa(addr->sin_addr)); + host_port->port = pj_ntohs(addr->sin_port); } + /* SSL password callback. */ static int password_cb(char *buf, int num, int rwflag, void *user_data) { @@ -207,367 +273,226 @@ static int password_cb(char *buf, int num, int rwflag, void *user_data) PJ_UNUSED_ARG(rwflag); - if(num < lis->password.slen+1) + if(num < lis->setting.password.slen+1) return 0; - pj_memcpy(buf, lis->password.ptr, lis->password.slen); - return lis->password.slen; + pj_memcpy(buf, lis->setting.password.ptr, lis->setting.password.slen); + return lis->setting.password.slen; } -/* Create and initialize new SSL context */ -static pj_status_t initialize_ctx(struct tls_listener *lis, - const char *keyfile, - const char *ca_list_file, - SSL_CTX **p_ctx) -{ - SSL_METHOD *meth; - SSL_CTX *ctx; - - *p_ctx = NULL; - - /* Create SSL context*/ - meth = SSLv23_method(); - ctx = SSL_CTX_new(meth); - if (ctx == NULL) - return PJSIP_TLS_ECTX; - - /* Load the CAs we trust*/ - if (ca_list_file && *ca_list_file) { - if(!(SSL_CTX_load_verify_locations(ctx, ca_list_file, 0))) { - ssl_report_error(2, lis->base.obj_name, - "Error loading/verifying CA list file '%s'", - ca_list_file); - SSL_CTX_free(ctx); - return PJSIP_TLS_ECALIST; - } - } - - - /* Load our keys and certificates */ - if (keyfile && *keyfile) { - if(!(SSL_CTX_use_certificate_chain_file(ctx, keyfile))) { - ssl_report_error(2, lis->base.obj_name, - "Error loading keys and certificate file '%s'", - keyfile); - SSL_CTX_free(ctx); - return PJSIP_TLS_EKEYFILE; - } +/* OpenSSL library initialization counter */ +static int openssl_init_count; - /* Set password callback */ - SSL_CTX_set_default_passwd_cb(ctx, password_cb); - SSL_CTX_set_default_passwd_cb_userdata(ctx, lis); +/* Initialize OpenSSL */ +static pj_status_t init_openssl(void) +{ + if (++openssl_init_count != 1) + return PJ_SUCCESS; - if(!(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM))) { - ssl_report_error(2, lis->base.obj_name, - "Error loading private key file '%s'", - keyfile); - SSL_CTX_free(ctx); - return PJSIP_TLS_EKEYFILE; - } - } + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); -#if (OPENSSL_VERSION_NUMBER < 0x00905100L) - SSL_CTX_set_verify_depth(ctx,1); -#endif - - *p_ctx = ctx; return PJ_SUCCESS; } -/* Destroy SSL context */ -static void destroy_ctx(SSL_CTX *ctx) -{ - SSL_CTX_free(ctx); -} - - -/* Check that the common name matches the host name*/ -#if 0 -static void check_cert(SSL *ssl, char *host) +/* Shutdown OpenSSL */ +static void shutdown_openssl(void) { - X509 *peer; - char peer_CN[256]; - - if(SSL_get_verify_result(ssl)!=X509_V_OK) - berr_exit("Certificate doesn't verify"); - - /* Check the cert chain. The chain length is automatically checked - * by OpenSSL when we set the verify depth in the ctx - */ - - /* Check the common name */ - peer = SSL_get_peer_certificate(ssl); - X509_NAME_get_text_by_NID( X509_get_subject_name(peer), - NID_commonName, peer_CN, 256); - - if(strcasecmp(peer_CN,host)) { - err_exit("Common name doesn't match host name"); - } + if (--openssl_init_count != 0) + return; } -#endif - -/* - * Public function to create TLS listener. - */ -PJ_DEF(pj_status_t) pjsip_tls_transport_start(pjsip_endpoint *endpt, - const pj_str_t *prm_keyfile, - const pj_str_t *prm_password, - const pj_str_t *prm_ca_list_file, - const pj_sockaddr_in *local, - const pjsip_host_port *a_name, - unsigned async_cnt, - pjsip_tpfactory **p_factory) +/* Create and initialize new SSL context */ +static pj_status_t create_ctx( struct tls_listener *lis, SSL_CTX **p_ctx) { - struct tls_listener *lis = NULL; - pj_pool_t *pool = NULL; - char str_keyfile[256], *keyfile; - char str_ca_list_file[256], *ca_list_file; - char str_password[128], *password; - pj_status_t status; - - PJ_LOG(5,(THIS_FILE, "Creating TLS listener")); + struct pjsip_tls_setting *opt = &lis->setting; + char *lis_name = lis->factory.obj_name; + SSL_METHOD *ssl_method; + SSL_CTX *ctx; + int mode, rc; + + *p_ctx = NULL; - /* Sanity check */ - PJ_ASSERT_RETURN(endpt, PJ_EINVAL); - - /* Unused arguments */ - PJ_UNUSED_ARG(async_cnt); - -#define COPY_STRING(dstbuf, dst, src) \ - if (src) { \ - PJ_ASSERT_RETURN(src->slen < sizeof(dstbuf), PJ_ENAMETOOLONG); \ - pj_memcpy(dstbuf, src->ptr, src->slen); \ - dstbuf[src->slen] = '\0'; \ - dst = dstbuf; \ - } else { \ - dst = NULL; \ + /* Make sure OpenSSL library has been initialized */ + init_openssl(); + + /* Determine SSL method to use */ + switch (opt->method) { + case PJSIP_SSL_DEFAULT_METHOD: + case PJSIP_SSLV23_METHOD: + ssl_method = SSLv23_method(); + break; + case PJSIP_TLSV1_METHOD: + ssl_method = TLSv1_method(); + break; + case PJSIP_SSLV2_METHOD: + ssl_method = SSLv2_method(); + break; + case PJSIP_SSLV3_METHOD: + ssl_method = SSLv3_method(); + break; + default: + ssl_report_error(lis_name, 4, PJSIP_TLS_EINVMETHOD, + "Error creating SSL context"); + return PJSIP_TLS_EINVMETHOD; } - /* Copy strings */ - COPY_STRING(str_keyfile, keyfile, prm_keyfile); - COPY_STRING(str_ca_list_file, ca_list_file, prm_ca_list_file); - COPY_STRING(str_password, password, prm_password); - - /* Verify that address given in a_name (if any) is valid */ - if (a_name && a_name->host.slen) { - pj_sockaddr_in tmp; - - status = pj_sockaddr_in_init(&tmp, &a_name->host, - (pj_uint16_t)a_name->port); - if (status != PJ_SUCCESS || tmp.sin_addr.s_addr == PJ_INADDR_ANY || - tmp.sin_addr.s_addr == PJ_INADDR_NONE) - { - /* Invalid address */ - return PJ_EINVAL; - } + /* Create SSL context for the listener */ + ctx = SSL_CTX_new(ssl_method); + if (ctx == NULL) { + ssl_report_error(lis_name, 4, PJ_SUCCESS, + "Error creating SSL context"); + return PJSIP_TLS_ECTX; } - /* Initialize OpenSSL */ - status = init_openssl(); - if (status != PJ_SUCCESS) - return status; - - - /* Create the listener struct. */ - pool = pjsip_endpt_create_pool(endpt, "tlslis", 4000, 4000); - lis = pj_pool_zalloc(pool, sizeof(*lis)); - lis->base.pool = pool; - - /* Save password */ - pj_strdup2_with_null(pool, &lis->password, password); - - - /* Create OpenSSL context */ - status = initialize_ctx(lis, keyfile, ca_list_file, &lis->ctx); - if (status != PJ_SUCCESS) - goto on_error; - /* Initialize listener. */ - pj_ansi_snprintf(lis->base.obj_name, sizeof(lis->base.obj_name), - "%s", "tlslis"); - pj_lock_create_recursive_mutex(pool, "tlslis", &lis->base.lock); - lis->base.type = PJSIP_TRANSPORT_TLS; - lis->base.type_name = "tls"; - lis->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS); - lis->base.create_transport = &lis_create_transport; - lis->base.destroy = &lis_destroy; - - /* Keep endpoint and transport manager instance */ - lis->endpt = endpt; - lis->tpmgr = pjsip_endpt_get_tpmgr(endpt); - - /* Determine the exported name */ - if (a_name) { - pj_strdup(pool, &lis->base.addr_name.host, &a_name->host); - lis->base.addr_name.port = a_name->port; - } else { - pj_in_addr ip_addr; - const char *str_ip_addr; + /* Load CA list if one is specified. */ + if (opt->ca_list_file.slen) { - /* Get default IP interface for the host */ - status = pj_gethostip(&ip_addr); - if (status != PJ_SUCCESS) - goto on_error; + /* Can only take a NULL terminated filename in the setting */ + pj_assert(opt->ca_list_file.ptr[opt->ca_list_file.slen] == '\0'); - /* Set publicized host */ - str_ip_addr = pj_inet_ntoa(ip_addr); - pj_strdup2(pool, &lis->base.addr_name.host, str_ip_addr); + rc = SSL_CTX_load_verify_locations(ctx, opt->ca_list_file.ptr, NULL); - /* Set publicized port */ - if (local) { - lis->base.addr_name.port = pj_ntohs(local->sin_port); - } else { - lis->base.addr_name.port = - pjsip_transport_get_default_port_for_type(PJSIP_TRANSPORT_TLS); + if (rc != 1) { + ssl_report_error(lis_name, 4, PJ_SUCCESS, + "Error loading/verifying CA list file '%.*s'", + (int)opt->ca_list_file.slen, + opt->ca_list_file.ptr); + SSL_CTX_free(ctx); + return PJSIP_TLS_ECACERT; } - } -#if 0 - if (local) { - pj_memcpy(&lis->base.local_addr, local, sizeof(pj_sockaddr_in)); - pj_strdup2(pool, &lis->base.addr_name.host, - pj_inet_ntoa(((pj_sockaddr_in*)local)->sin_addr)); - lis->base.addr_name.port = pj_ntohs(((pj_sockaddr_in*)local)->sin_port); - } else { - int port; - port = pjsip_transport_get_default_port_for_type(PJSIP_TRANSPORT_TLS); - pj_sockaddr_in_init(&lis->base.local_addr, NULL, port); - pj_strdup(pool, &lis->base.addr_name.host, pj_gethostname()); - lis->base.addr_name.port = port; + PJ_LOG(5,(lis_name, "TLS CA file successfully loaded from '%.*s'", + (int)opt->ca_list_file.slen, + opt->ca_list_file.ptr)); } -#endif - + + /* Set password callback */ + SSL_CTX_set_default_passwd_cb(ctx, password_cb); + SSL_CTX_set_default_passwd_cb_userdata(ctx, lis); - /* Register listener to transport manager. */ - status = pjsip_tpmgr_register_tpfactory(lis->tpmgr, &lis->base); - if (status != PJ_SUCCESS) - goto on_error; - lis->is_registered = PJ_TRUE; + /* Load certificate if one is specified */ + if (opt->cert_file.slen) { + /* Can only take a NULL terminated filename in the setting */ + pj_assert(opt->cert_file.ptr[opt->cert_file.slen] == '\0'); - /* Done */ - if (p_factory) - *p_factory = &lis->base; + /* Load certificate chain from file into ctx */ + rc = SSL_CTX_use_certificate_chain_file(ctx, opt->cert_file.ptr); - PJ_LOG(4,(lis->base.obj_name, "TLS listener started at %.*s;%d", - (int)lis->base.addr_name.host.slen, - lis->base.addr_name.host.ptr, - lis->base.addr_name.port)); - return PJ_SUCCESS; + if(rc != 1) { + ssl_report_error(lis_name, 4, PJ_SUCCESS, + "Error loading certificate chain file '%.*s'", + (int)opt->cert_file.slen, + opt->cert_file.ptr); + SSL_CTX_free(ctx); + return PJSIP_TLS_ECERTFILE; + } -on_error: - if (lis) { - lis_destroy(&lis->base); - } else if (pool) { - pj_pool_release(pool); - shutdown_openssl(); - } else { - shutdown_openssl(); + PJ_LOG(5,(lis_name, "TLS certificate successfully loaded from '%.*s'", + (int)opt->cert_file.slen, + opt->cert_file.ptr)); } - return status; -} + /* Load private key if one is specified */ + if (opt->privkey_file.slen) { -/* Transport worker thread */ -static int PJ_THREAD_FUNC tls_worker_thread(void *arg) -{ - struct tls_transport *tls_tp = (struct tls_transport *)arg; - pjsip_rx_data *rdata = &tls_tp->rdata; + /* Can only take a NULL terminated filename in the setting */ + pj_assert(opt->privkey_file.ptr[opt->privkey_file.slen] == '\0'); - while (!tls_tp->quitting) { - pj_fd_set_t rd_set; - pj_time_val timeout; - int len; - pj_size_t size_eaten; + /* Adds the first private key found in file to ctx */ + rc = SSL_CTX_use_PrivateKey_file(ctx, opt->privkey_file.ptr, + SSL_FILETYPE_PEM); - PJ_FD_ZERO(&rd_set); - PJ_FD_SET(tls_tp->sock, &rd_set); + if(rc != 1) { + ssl_report_error(lis_name, 4, PJ_SUCCESS, + "Error adding private key from '%.*s'", + (int)opt->privkey_file.slen, + opt->privkey_file.ptr); + SSL_CTX_free(ctx); + return PJSIP_TLS_EKEYFILE; + } - timeout.sec = 1; - timeout.msec = 0; + PJ_LOG(5,(lis_name, "TLS private key successfully loaded from '%.*s'", + (int)opt->privkey_file.slen, + opt->privkey_file.ptr)); + } - len = pj_sock_select(tls_tp->sock, &rd_set, NULL, NULL, &timeout); - if (len < 1) - continue; - /* Start blocking read to SSL socket */ - len = SSL_read(tls_tp->ssl, - rdata->pkt_info.packet + rdata->pkt_info.len, - sizeof(rdata->pkt_info.packet) - rdata->pkt_info.len); + /* SSL verification options */ + if (lis->setting.verify_client || lis->setting.verify_server) { + mode = SSL_VERIFY_PEER; + if (lis->setting.require_client_cert) + mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + } else { + mode = SSL_VERIFY_NONE; + } + SSL_CTX_set_verify(ctx, mode, NULL); - switch (SSL_get_error(tls_tp->ssl, len)) { - case SSL_ERROR_NONE: - rdata->pkt_info.len += len; - rdata->pkt_info.zero = 0; - pj_gettimeofday(&rdata->pkt_info.timestamp); + PJ_LOG(5,(lis_name, "TLS verification mode set to %d", mode)); - /* Report to transport manager. - * The transport manager will tell us how many bytes of the packet - * have been processed (as valid SIP message). - */ - size_eaten = - pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, - rdata); - - pj_assert(size_eaten <= (pj_size_t)rdata->pkt_info.len); + /* Optionally set cipher list if one is specified */ + if (opt->ciphers.slen) { + /* Can only take a NULL terminated cipher list in the setting */ + pj_assert(opt->cert_file.ptr[opt->cert_file.slen] == '\0'); - /* Move unprocessed data to the front of the buffer */ - if (size_eaten>0 && size_eaten<(pj_size_t)rdata->pkt_info.len) { - pj_memmove(rdata->pkt_info.packet, - rdata->pkt_info.packet + size_eaten, - rdata->pkt_info.len - size_eaten); - } - - rdata->pkt_info.len -= size_eaten; - break; - - case SSL_ERROR_ZERO_RETURN: - PJ_LOG(4,(tls_tp->base.obj_name, "SSL transport shutdodwn by remote")); - if (!tls_tp->quitting) - pjsip_transport_shutdown(&tls_tp->base); - goto done; - - case SSL_ERROR_SYSCALL: - PJ_LOG(2,(tls_tp->base.obj_name, "SSL Error: Premature close")); - if (!tls_tp->quitting) - pjsip_transport_shutdown(&tls_tp->base); - goto done; - - default: - PJ_LOG(2,(tls_tp->base.obj_name, "SSL read problem")); - if (!tls_tp->quitting) - pjsip_transport_shutdown(&tls_tp->base); - goto done; + rc = SSL_CTX_set_cipher_list(ctx, opt->ciphers.ptr); + if (rc != 1) { + ssl_report_error(lis_name, 4, PJ_SUCCESS, + "Error setting cipher list '%.*s'", + (int)opt->ciphers.slen, + opt->ciphers.ptr); + SSL_CTX_free(ctx); + return PJSIP_TLS_ECIPHER; } + + PJ_LOG(5,(lis_name, "TLS ciphers set to '%.*s'", + (int)opt->ciphers.slen, + opt->ciphers.ptr)); } -done: - return 0; + /* Done! */ + + *p_ctx = ctx; + return PJ_SUCCESS; } +/* Destroy SSL context */ +static void destroy_ctx(SSL_CTX *ctx) +{ + SSL_CTX_free(ctx); -PJ_DECL(pj_size_t) PJ_FD_COUNT(const pj_fd_set_t *fdsetp); + /* Potentially shutdown OpenSSL library if this is the last + * context exists. + */ + shutdown_openssl(); +} /* * Perform SSL_connect upon completion of socket connect() */ -static pj_status_t perform_ssl_connect(SSL *ssl, pj_sock_t sock) +static pj_status_t ssl_connect(struct tls_transport *tls) { + SSL *ssl = tls->ssl; int status; if (SSL_is_init_finished (ssl)) return PJ_SUCCESS; - SSL_set_fd(ssl, (int)sock); + if (SSL_get_fd(ssl) < 0) + SSL_set_fd(ssl, (int)tls->sock); - if (!SSL_in_connect_init (ssl)) - SSL_set_connect_state (ssl); + if (!SSL_in_connect_init(ssl)) + SSL_set_connect_state(ssl); + + PJ_LOG(5,(tls->base.obj_name, "Starting SSL_connect() negotiation")); do { /* These handle sets are used to set up for whatever SSL_connect @@ -584,17 +509,19 @@ static pj_status_t perform_ssl_connect(SSL *ssl, pj_sock_t sock) case SSL_ERROR_NONE: /* Success */ status = 0; + PJ_LOG(5,(tls->base.obj_name, + "SSL_connect() negotiation completes successfully")); break; case SSL_ERROR_WANT_WRITE: /* Wait for more activity */ - PJ_FD_SET(sock, &wr_set); + PJ_FD_SET(tls->sock, &wr_set); status = 1; break; case SSL_ERROR_WANT_READ: /* Wait for more activity */ - PJ_FD_SET(sock, &rd_set); + PJ_FD_SET(tls->sock, &rd_set); status = 1; break; @@ -605,8 +532,7 @@ static pj_status_t perform_ssl_connect(SSL *ssl, pj_sock_t sock) */ PJ_LOG(4,(THIS_FILE, "SSL connect() failed, remote has" "shutdown connection.")); - status = -1; - break; + return PJ_STATUS_FROM_OS(OSERR_ENOTCONN); case SSL_ERROR_SYSCALL: /* On some platforms (e.g. MS Windows) OpenSSL does not @@ -627,9 +553,9 @@ static pj_status_t perform_ssl_connect(SSL *ssl, pj_sock_t sock) */ status = 1; /* Wait for more activity */ if (SSL_want_write (ssl)) - PJ_FD_SET(sock, &wr_set); + PJ_FD_SET(tls->sock, &wr_set); else if (SSL_want_read (ssl)) - PJ_FD_SET(sock, &rd_set); + PJ_FD_SET(tls->sock, &rd_set); else status = -1; /* Doesn't want anything - bail out */ } @@ -637,21 +563,35 @@ static pj_status_t perform_ssl_connect(SSL *ssl, pj_sock_t sock) status = -1; } break; - + default: - ssl_report_error(4, THIS_FILE, "SSL_connect() error"); + ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS, + "SSL_connect() error"); status = -1; break; } if (status == 1) { + pj_time_val timeout, *p_timeout; + /* Must have at least one handle to wait for at this point. */ - pj_assert(PJ_FD_COUNT(&rd_set) == 1 || - PJ_FD_COUNT(&wr_set) == 1); + pj_assert(PJ_FD_COUNT(&rd_set) == 1 || PJ_FD_COUNT(&wr_set) == 1); + /* This will block the whole stack!!! */ + PJ_TODO(SUPPORT_SSL_ASYNCHRONOUS_CONNECT); + + if (tls->listener->setting.timeout.sec == 0 && + tls->listener->setting.timeout.msec == 0) + { + p_timeout = NULL; + } else { + timeout = tls->listener->setting.timeout; + p_timeout = &timeout; + } + /* Block indefinitely if timeout pointer is zero. */ - status = pj_sock_select(FD_SETSIZE, &rd_set, &wr_set, - NULL, NULL); + status = pj_sock_select(tls->sock+1, &rd_set, &wr_set, + NULL, p_timeout); /* 0 is timeout, so we're done. * -1 is error, so we're done. @@ -659,7 +599,9 @@ static pj_status_t perform_ssl_connect(SSL *ssl, pj_sock_t sock) */ if (status >= 1) status = 1; - else /* Timeout or socket failure */ + else if (status == 0) + return PJSIP_TLS_ETIMEDOUT; + else status = -1; } @@ -670,407 +612,1602 @@ static pj_status_t perform_ssl_connect(SSL *ssl, pj_sock_t sock) /* - * Create a new TLS transport. The TLS role can be a server or a client, - * depending on whether socket is valid. + * Perform SSL_accept() on the newly established incoming TLS connection. */ -static pj_status_t tls_create_transport(struct tls_listener *lis, - pj_sock_t sock, - const pj_sockaddr_in *rem_addr, - struct tls_transport **p_tp) +static pj_status_t ssl_accept(struct tls_transport *tls) { - struct tls_transport *tls_tp = NULL; - pj_pool_t *pool = NULL; - char dst_str[80]; - int len; - pj_status_t status; + SSL *ssl = tls->ssl; + int rc; + + if (SSL_is_init_finished (ssl)) + return PJ_SUCCESS; + + if (!SSL_in_accept_init(ssl)) + SSL_set_accept_state(ssl); - /* Build remote address */ - PJ_ASSERT_RETURN(rem_addr->sin_family==PJ_AF_INET, PJ_EINVAL); + PJ_LOG(5,(tls->base.obj_name, "Starting SSL_accept() negotiation")); - /* sock must not be zero (should be either a valid socket or - * PJ_INVALID_SOCKET. + /* Repeat retrying SSL_accept() procedure until it completes either + * successfully or with failure. */ - PJ_ASSERT_RETURN(sock==PJ_INVALID_SOCKET || sock > 0, PJ_EINVAL); + do { - /* - * Create the transport - */ - pool = pjsip_endpt_create_pool(lis->endpt, "tls", 4000, 4000); - tls_tp = pj_pool_zalloc(pool, sizeof(*tls_tp)); - tls_tp->sock = sock; - tls_tp->base.pool = pool; - - len = pj_ansi_snprintf(tls_tp->base.obj_name, - sizeof(tls_tp->base.obj_name), - "tls%p", tls_tp); - if (len < 1 || len >= sizeof(tls_tp->base.obj_name)) { - status = PJ_ENAMETOOLONG; - goto on_error; - } + /* These handle sets are used to set up for whatever SSL_accept says + * it wants next. They're reset on each pass around the loop. + */ + pj_fd_set_t rd_set; + pj_fd_set_t wr_set; + + PJ_FD_ZERO(&rd_set); + PJ_FD_ZERO(&wr_set); - /* Print destination address. */ - len = pj_ansi_snprintf(dst_str, sizeof(dst_str), "%s:%d", - pj_inet_ntoa(rem_addr->sin_addr), - pj_ntohs(rem_addr->sin_port)); + rc = SSL_accept (ssl); - PJ_LOG(5,(lis->base.obj_name, "Creating TLS transport to %s", dst_str)); + switch (SSL_get_error (ssl, rc)) { + case SSL_ERROR_NONE: + /* Success! */ + rc = 0; + PJ_LOG(5,(tls->base.obj_name, + "SSL_accept() negotiation completes successfully")); + break; + + case SSL_ERROR_WANT_WRITE: + PJ_FD_SET(tls->sock, &wr_set); + rc = 1; /* Wait for more activity */ + break; + + case SSL_ERROR_WANT_READ: + PJ_FD_SET(tls->sock, &rd_set); + rc = 1; /* Need to read more data */ + break; + case SSL_ERROR_ZERO_RETURN: + /* The peer has notified us that it is shutting down via the SSL + * "close_notify" message so we need to shutdown, too. + */ + PJ_LOG(4,(tls->base.obj_name, + "Incoming SSL connection closed prematurely by client")); + return PJ_STATUS_FROM_OS(OSERR_ENOTCONN); - /* Transport info */ - tls_tp->base.endpt = lis->endpt; - tls_tp->base.tpmgr = lis->tpmgr; - tls_tp->base.type_name = (char*)pjsip_transport_get_type_name(PJSIP_TRANSPORT_TLS); - tls_tp->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS); - tls_tp->base.info = pj_pool_alloc(pool, len + 5); - pj_ansi_snprintf(tls_tp->base.info, len + 5, "TLS:%s", dst_str); + case SSL_ERROR_SYSCALL: + /* Explicitly check for EWOULDBLOCK since it doesn't get converted + * to an SSL_ERROR_WANT_{READ,WRITE} on some platforms. + * If SSL_accept failed outright, though, don't bother checking + * more. This can happen if the socket gets closed during the + * handshake. + */ + if (pj_get_netos_error()==PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) + && rc==-1) + { + /* Although the SSL_ERROR_WANT_READ/WRITE isn't getting set + * correctly, the read/write state should be valid. Use that + * to decide what to do. + */ + rc = 1; /* Wait for more activity */ + if (SSL_want_write(ssl)) + PJ_FD_SET(tls->sock, &wr_set); + else if (SSL_want_read(ssl)) + PJ_FD_SET(tls->sock, &rd_set); + else { + /* Doesn't want anything - bail out */ + return PJ_STATUS_FROM_OS(OSERR_ENOTCONN); + } + } + else { + return PJSIP_TLS_EUNKNOWN; + } + break; + + default: + ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS, + "Error calling SSL_accept()"); + return pj_get_netos_error() ? pj_get_netos_error() : + PJSIP_TLS_EUNKNOWN; + } + + if (rc == 1) { - /* Reference counter */ - status = pj_atomic_create(pool, 0, &tls_tp->base.ref_cnt); - if (status != PJ_SUCCESS) - goto on_error; - - /* Lock */ - status = pj_lock_create_recursive_mutex(pool, "tls", &tls_tp->base.lock); - if (status != PJ_SUCCESS) - goto on_error; + pj_time_val timeout, *p_timeout; - /* Transport key */ - tls_tp->base.key.type = PJSIP_TRANSPORT_TLS; - pj_memcpy(&tls_tp->base.key.rem_addr, rem_addr, sizeof(*rem_addr)); + /* Must have at least one handle to wait for at this point. */ + pj_assert(PJ_FD_COUNT(&rd_set) == 1 || PJ_FD_COUNT(&wr_set) == 1); + + if (tls->listener->setting.timeout.sec == 0 && + tls->listener->setting.timeout.msec == 0) + { + p_timeout = NULL; + } else { + timeout = tls->listener->setting.timeout; + p_timeout = &timeout; + } - pj_strdup(pool, &tls_tp->base.local_name.host, &lis->base.addr_name.host); - tls_tp->base.local_name.port = lis->base.addr_name.port; + rc = pj_sock_select(tls->sock+1, &rd_set, &wr_set, NULL, + p_timeout); + + if (rc >= 1) + rc = 1; + else if (rc == 0) + return PJSIP_TLS_ETIMEDOUT; + else + return pj_get_netos_error(); + } + + } while (rc == 1 && !SSL_is_init_finished(ssl)); + + return (rc == -1 ? PJSIP_TLS_EUNKNOWN : PJ_SUCCESS); +} - pj_strdup2(pool, &tls_tp->base.remote_name.host, dst_str); - tls_tp->base.remote_name.port = pj_ntohs(rem_addr->sin_port); - /* Initialize transport callback */ - tls_tp->base.send_msg = &tls_tp_send_msg; - tls_tp->base.do_shutdown = &tls_tp_do_shutdown; - tls_tp->base.destroy = &tls_tp_destroy; +/* Send outgoing data with SSL connection */ +static pj_status_t ssl_write(struct tls_transport *tls, + pjsip_tx_data *tdata) +{ + int size = tdata->buf.cur - tdata->buf.start; + int sent = 0; + do { + const int fragment_sent = SSL_write(tls->ssl, + tdata->buf.start + sent, + size - sent); + + switch( SSL_get_error(tls->ssl, fragment_sent)) { + case SSL_ERROR_NONE: + sent += fragment_sent; + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* For now, we can't handle situation where WANT_READ/WANT_WRITE + * is raised after some data has been sent, since we don't have + * mechanism to keep track of how many bytes have been sent + * inside the individual tdata. + */ + pj_assert(sent == 0); + PJ_TODO(PARTIAL_SSL_SENT); + return PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK); + + case SSL_ERROR_ZERO_RETURN: + /* The peer has notified us that it is shutting down via the SSL + * "close_notify" message. Tell the transport manager that it + * shouldn't use this transport any more and return ENOTCONN + * to caller. + */ + + /* It is safe to call this multiple times. */ + pjsip_transport_shutdown(&tls->base); - /* Connect SSL */ - if (sock == PJ_INVALID_SOCKET) { + return PJ_STATUS_FROM_OS(OSERR_ENOTCONN); + + case SSL_ERROR_SYSCALL: + if (fragment_sent == 0) { + /* An EOF occured but the SSL "close_notify" message was not + * sent. Shutdown the transport and return ENOTCONN. + */ - /* Create socket */ - status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_STREAM, 0, &sock); - if (status != PJ_SUCCESS) - goto on_error; + /* It is safe to call this multiple times. */ + pjsip_transport_shutdown(&tls->base); - /* Save the socket */ - tls_tp->sock = sock; + return PJ_STATUS_FROM_OS(OSERR_ENOTCONN); + } + + /* Other error */ + return pj_get_netos_error(); + + default: + ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS, + "Error sending %s with SSL_write()", + pjsip_tx_data_get_info(tdata)); + return pj_get_netos_error() ? pj_get_netos_error() + : PJSIP_TLS_ESEND; + } + + } while (sent < size); - /* TODO: asynchronous connect() */ - PJ_TODO(TLS_ASYNC_CONNECT); + return PJ_SUCCESS; +} - /* Connect socket */ - status = pj_sock_connect(sock, rem_addr, sizeof(*rem_addr)); - if (status != PJ_SUCCESS) - goto on_error; - /* Create SSL object and BIO */ - tls_tp->ssl = SSL_new(lis->ctx); - SSL_set_verify (tls_tp->ssl, 0, 0); +/* Read data from SSL connection */ +static pj_status_t ssl_read(struct tls_transport *tls) +{ + pjsip_rx_data *rdata = &tls->rdata; - /* Connect SSL */ - status = perform_ssl_connect(tls_tp->ssl, sock); - if (status != PJ_SUCCESS) - goto on_error; + int bytes_read, max_size; - /* TODO: check server cert. */ - PJ_TODO(TLS_CHECK_SERVER_CERT); -#if 0 - check_cert(ssl,host); -#endif + max_size = sizeof(rdata->pkt_info.packet) - rdata->pkt_info.len; + bytes_read = SSL_read(tls->ssl, + rdata->pkt_info.packet+rdata->pkt_info.len, + max_size); - } else { - /* - * This is a server side TLS socket. + switch (SSL_get_error(tls->ssl, bytes_read)) { + case SSL_ERROR_NONE: + /* Data successfully read */ + rdata->pkt_info.len += bytes_read; + return PJ_SUCCESS; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK); + + case SSL_ERROR_ZERO_RETURN: + /* The peer has notified us that it is shutting down via the SSL + * "close_notify" message. */ - PJ_TODO(TLS_IMPLEMENT_SERVER); - status = PJ_ENOTSUP; + pjsip_transport_shutdown(&tls->base); + return PJ_STATUS_FROM_OS(OSERR_ENOTCONN); + + case SSL_ERROR_SYSCALL: + if (bytes_read == 0) { + /* An EOF occured but the SSL "close_notify" message was not + * sent. + */ + pjsip_transport_shutdown(&tls->base); + return PJ_STATUS_FROM_OS(OSERR_ENOTCONN); + } + + /* Other error */ + return pj_get_netos_error(); + + default: + ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS, + "Error reading data with SSL_read()"); + return pj_get_netos_error() ? pj_get_netos_error() + : PJSIP_TLS_EREAD; + } + + /* Should not reach here */ +} + + +/**************************************************************************** + * The TLS listener/transport factory. + */ + +/* + * This is the public API to create, initialize, register, and start the + * TLS listener. + */ +PJ_DEF(pj_status_t) pjsip_tls_transport_start( pjsip_endpoint *endpt, + const pjsip_tls_setting *opt, + const pj_sockaddr_in *local, + const pjsip_host_port *a_name, + unsigned async_cnt, + pjsip_tpfactory **p_factory) +{ + pj_pool_t *pool; + struct tls_listener *listener; + pj_ioqueue_callback listener_cb; + pj_sockaddr_in *listener_addr; + int addr_len; + unsigned i; + pj_status_t status; + + /* Sanity check */ + PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL); + + /* Verify that address given in a_name (if any) is valid */ + if (a_name && a_name->host.slen) { + pj_sockaddr_in tmp; + + status = pj_sockaddr_in_init(&tmp, &a_name->host, + (pj_uint16_t)a_name->port); + if (status != PJ_SUCCESS || tmp.sin_addr.s_addr == PJ_INADDR_ANY || + tmp.sin_addr.s_addr == PJ_INADDR_NONE) + { + /* Invalid address */ + return PJ_EINVAL; + } + } + + pool = pjsip_endpt_create_pool(endpt, "tlslis", POOL_LIS_INIT, + POOL_LIS_INC); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + + listener = pj_pool_zalloc(pool, sizeof(struct tls_listener)); + listener->factory.pool = pool; + listener->factory.type = PJSIP_TRANSPORT_TLS; + listener->factory.type_name = "tls"; + listener->factory.flag = + pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS); + listener->sock = PJ_INVALID_SOCKET; + + /* Create object name */ + pj_ansi_snprintf(listener->factory.obj_name, + sizeof(listener->factory.obj_name), + "tls%p", listener); + + /* Create duplicate of TLS settings */ + if (opt) + pjsip_tls_setting_copy(pool, &listener->setting, opt); + else + pjsip_tls_setting_default(&listener->setting); + + /* Initialize SSL context to be used by this listener */ + status = create_ctx(listener, &listener->ctx); + if (status != PJ_SUCCESS) + goto on_error; + + status = pj_lock_create_recursive_mutex(pool, "tlslis", + &listener->factory.lock); + if (status != PJ_SUCCESS) goto on_error; + + + /* Create and bind socket */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_STREAM, 0, &listener->sock); + if (status != PJ_SUCCESS) + goto on_error; + + listener_addr = (pj_sockaddr_in*)&listener->factory.local_addr; + if (local) { + pj_memcpy(listener_addr, local, sizeof(pj_sockaddr_in)); + } else { + pj_sockaddr_in_init(listener_addr, NULL, 0); } - /* Initialize local address */ - tls_tp->base.addr_len = sizeof(tls_tp->base.local_addr); - status = pj_sock_getsockname(tls_tp->sock, &tls_tp->base.local_addr, - &tls_tp->base.addr_len); + status = pj_sock_bind(listener->sock, listener_addr, + sizeof(pj_sockaddr_in)); if (status != PJ_SUCCESS) goto on_error; + /* Retrieve the bound address */ + addr_len = sizeof(pj_sockaddr_in); + status = pj_sock_getsockname(listener->sock, listener_addr, &addr_len); + if (status != PJ_SUCCESS) + goto on_error; - /* - * Create rdata + /* If published host/IP is specified, then use that address as the + * listener advertised address. */ - pool = pjsip_endpt_create_pool(lis->endpt, - "rtd%p", - PJSIP_POOL_RDATA_LEN, - PJSIP_POOL_RDATA_INC); - if (!pool) { - status = PJ_ENOMEM; + if (a_name && a_name->host.slen) { + /* Copy the address */ + listener->factory.addr_name = *a_name; + pj_strdup(listener->factory.pool, &listener->factory.addr_name.host, + &a_name->host); + listener->factory.addr_name.port = a_name->port; + + } else { + /* No published address is given, use the bound address */ + + /* If the address returns 0.0.0.0, use the default + * interface address as the transport's address. + */ + if (listener_addr->sin_addr.s_addr == 0) { + pj_in_addr hostip; + + status = pj_gethostip(&hostip); + if (status != PJ_SUCCESS) + goto on_error; + + listener_addr->sin_addr = hostip; + } + + /* Save the address name */ + sockaddr_to_host_port(listener->factory.pool, + &listener->factory.addr_name, listener_addr); + } + + /* If port is zero, get the bound port */ + if (listener->factory.addr_name.port == 0) { + listener->factory.addr_name.port = pj_ntohs(listener_addr->sin_port); + } + + /* Start listening to the address */ + status = pj_sock_listen(listener->sock, PJSIP_TLS_TRANSPORT_BACKLOG); + if (status != PJ_SUCCESS) goto on_error; + + + /* Register socket to ioqeuue */ + pj_bzero(&listener_cb, sizeof(listener_cb)); + listener_cb.on_accept_complete = &on_accept_complete; + status = pj_ioqueue_register_sock(pool, pjsip_endpt_get_ioqueue(endpt), + listener->sock, listener, + &listener_cb, &listener->key); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register to transport manager */ + listener->endpt = endpt; + listener->tpmgr = pjsip_endpt_get_tpmgr(endpt); + listener->factory.create_transport = lis_create_transport; + listener->factory.destroy = lis_destroy; + listener->is_registered = PJ_TRUE; + status = pjsip_tpmgr_register_tpfactory(listener->tpmgr, + &listener->factory); + if (status != PJ_SUCCESS) { + listener->is_registered = PJ_FALSE; + goto on_error; + } + + + /* Start pending accept() operations */ + if (async_cnt > MAX_ASYNC_CNT) async_cnt = MAX_ASYNC_CNT; + listener->async_cnt = async_cnt; + + for (i=0; i<async_cnt; ++i) { + pj_pool_t *pool; + + pool = pjsip_endpt_create_pool(endpt, "tlss%p", POOL_TP_INIT, + POOL_TP_INIT); + if (!pool) { + status = PJ_ENOMEM; + goto on_error; + } + + listener->accept_op[i] = pj_pool_zalloc(pool, + sizeof(struct pending_accept)); + pj_ioqueue_op_key_init(&listener->accept_op[i]->op_key, + sizeof(listener->accept_op[i]->op_key)); + listener->accept_op[i]->pool = pool; + listener->accept_op[i]->listener = listener; + listener->accept_op[i]->index = i; + + on_accept_complete(listener->key, &listener->accept_op[i]->op_key, + listener->sock, PJ_EPENDING); + } + + PJ_LOG(4,(listener->factory.obj_name, + "SIP TLS listener ready for incoming connections at %.*s:%d", + (int)listener->factory.addr_name.host.slen, + listener->factory.addr_name.host.ptr, + listener->factory.addr_name.port)); + + /* Return the pointer to user */ + if (p_factory) *p_factory = &listener->factory; + + return PJ_SUCCESS; + +on_error: + lis_destroy(&listener->factory); + return status; +} + + +/* This callback is called by transport manager to destroy listener */ +static pj_status_t lis_destroy(pjsip_tpfactory *factory) +{ + struct tls_listener *listener = (struct tls_listener *)factory; + unsigned i; + + if (listener->is_registered) { + pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory); + listener->is_registered = PJ_FALSE; + } + + if (listener->key) { + pj_ioqueue_unregister(listener->key); + listener->key = NULL; + listener->sock = PJ_INVALID_SOCKET; + } + + if (listener->sock != PJ_INVALID_SOCKET) { + pj_sock_close(listener->sock); + listener->sock = PJ_INVALID_SOCKET; + } + + if (listener->factory.lock) { + pj_lock_destroy(listener->factory.lock); + listener->factory.lock = NULL; + } + + for (i=0; i<PJ_ARRAY_SIZE(listener->accept_op); ++i) { + if (listener->accept_op[i] && listener->accept_op[i]->pool) { + pj_pool_t *pool = listener->accept_op[i]->pool; + listener->accept_op[i]->pool = NULL; + pj_pool_release(pool); + } + } + + if (listener->ctx) { + destroy_ctx(listener->ctx); + listener->ctx = NULL; + } + + if (listener->factory.pool) { + pj_pool_t *pool = listener->factory.pool; + + PJ_LOG(4,(listener->factory.obj_name, "SIP TLS listener destroyed")); + + listener->factory.pool = NULL; + pj_pool_release(pool); } - tls_tp->rdata.tp_info.pool = pool; + + return PJ_SUCCESS; +} + + +/***************************************************************************/ +/* + * TLS Transport + */ + +/* + * Prototypes. + */ +/* Called by transport manager to send message */ +static pj_status_t tls_send_msg(pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + void (*callback)(pjsip_transport *transport, + void *token, + pj_ssize_t sent_bytes)); + +/* Called by transport manager to shutdown */ +static pj_status_t tls_shutdown(pjsip_transport *transport); + +/* Called by transport manager to destroy transport */ +static pj_status_t tls_destroy_transport(pjsip_transport *transport); + +/* Utility to destroy transport */ +static pj_status_t tls_destroy(pjsip_transport *transport, + pj_status_t reason); + +/* Callback from ioqueue on incoming packet */ +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read); + +/* Callback from ioqueue when packet is sent */ +static void on_write_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_sent); + +/* Callback from ioqueue when connect completes */ +static void on_connect_complete(pj_ioqueue_key_t *key, + pj_status_t status); + + +/* + * Common function to create TLS transport, called when pending accept() and + * pending connect() complete. + */ +static pj_status_t tls_create( struct tls_listener *listener, + pj_pool_t *pool, + pj_sock_t sock, pj_bool_t is_server, + const pj_sockaddr_in *local, + const pj_sockaddr_in *remote, + struct tls_transport **p_tls) +{ + struct tls_transport *tls; + pj_ioqueue_t *ioqueue; + pj_ioqueue_callback tls_callback; + int rc; + pj_status_t status; + + + PJ_ASSERT_RETURN(sock != PJ_INVALID_SOCKET, PJ_EINVAL); + + + if (pool == NULL) { + pool = pjsip_endpt_create_pool(listener->endpt, "tls", + POOL_TP_INIT, POOL_TP_INC); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + } /* - * Initialize rdata + * Create and initialize basic transport structure. */ - tls_tp->rdata.tp_info.transport = &tls_tp->base; - tls_tp->rdata.tp_info.tp_data = tls_tp; - tls_tp->rdata.tp_info.op_key.rdata = &tls_tp->rdata; - pj_ioqueue_op_key_init(&tls_tp->rdata.tp_info.op_key.op_key, - sizeof(pj_ioqueue_op_key_t)); + tls = pj_pool_zalloc(pool, sizeof(*tls)); + tls->sock = sock; + tls->is_server = is_server; + tls->listener = listener; + pj_list_init(&tls->delayed_list); + tls->base.pool = pool; + + pj_ansi_snprintf(tls->base.obj_name, PJ_MAX_OBJ_NAME, + (is_server ? "tlss%p" :"tlsc%p"), tls); + + /* Initialize transport reference counter to 1 */ + status = pj_atomic_create(pool, 1, &tls->base.ref_cnt); + if (status != PJ_SUCCESS) { + goto on_error; + } - tls_tp->rdata.pkt_info.src_addr = tls_tp->base.key.rem_addr; - tls_tp->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in); - rem_addr = (pj_sockaddr_in*) &tls_tp->base.key.rem_addr; - pj_ansi_strcpy(tls_tp->rdata.pkt_info.src_name, - pj_inet_ntoa(rem_addr->sin_addr)); - tls_tp->rdata.pkt_info.src_port = pj_ntohs(rem_addr->sin_port); + status = pj_lock_create_recursive_mutex(pool, "tls", &tls->base.lock); + if (status != PJ_SUCCESS) { + goto on_error; + } - /* Register transport to transport manager */ - status = pjsip_transport_register(lis->tpmgr, &tls_tp->base); + tls->base.key.type = PJSIP_TRANSPORT_TLS; + pj_memcpy(&tls->base.key.rem_addr, remote, sizeof(pj_sockaddr_in)); + tls->base.type_name = "tls"; + tls->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS); + + tls->base.info = pj_pool_alloc(pool, 64); + pj_ansi_snprintf(tls->base.info, 64, "TLS to %s:%d", + pj_inet_ntoa(remote->sin_addr), + (int)pj_ntohs(remote->sin_port)); + + tls->base.addr_len = sizeof(pj_sockaddr_in); + pj_memcpy(&tls->base.local_addr, local, sizeof(pj_sockaddr_in)); + sockaddr_to_host_port(pool, &tls->base.local_name, local); + sockaddr_to_host_port(pool, &tls->base.remote_name, remote); + + tls->base.endpt = listener->endpt; + tls->base.tpmgr = listener->tpmgr; + tls->base.send_msg = &tls_send_msg; + tls->base.do_shutdown = &tls_shutdown; + tls->base.destroy = &tls_destroy_transport; + + /* Create SSL connection object */ + tls->ssl = SSL_new(listener->ctx); + if (tls->ssl == NULL) { + ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS, + "Error creating SSL connection object"); + status = PJSIP_TLS_ESSLCONN; + goto on_error; + } + + /* Associate network socket with SSL connection object */ + rc = SSL_set_fd(tls->ssl, (int)sock); + if (rc != 1) { + ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS, + "Error calling SSL_set_fd"); + status = PJSIP_TLS_ESSLCONN; + goto on_error; + } + + /* Register socket to ioqueue */ + pj_bzero(&tls_callback, sizeof(pj_ioqueue_callback)); + tls_callback.on_read_complete = &on_read_complete; + tls_callback.on_write_complete = &on_write_complete; + tls_callback.on_connect_complete = &on_connect_complete; + + ioqueue = pjsip_endpt_get_ioqueue(listener->endpt); + status = pj_ioqueue_register_sock(pool, ioqueue, sock, + tls, &tls_callback, &tls->key); if (status != PJ_SUCCESS) { goto on_error; } - /* Create worker thread to receive packets */ - status = pj_thread_create(pool, "tlsthread", &tls_worker_thread, - tls_tp, PJ_THREAD_DEFAULT_STACK_SIZE, 0, - &tls_tp->thread); + /* Register transport to transport manager */ + status = pjsip_transport_register(listener->tpmgr, &tls->base); if (status != PJ_SUCCESS) { - pjsip_transport_destroy(&tls_tp->base); - return status; + goto on_error; } - /* Done */ - *p_tp = tls_tp; + tls->is_registered = PJ_TRUE; - PJ_LOG(4,(tls_tp->base.obj_name, "TLS transport created, remote=%s", - dst_str)); + /* Done setting up basic transport. */ + *p_tls = tls; + + PJ_LOG(4,(tls->base.obj_name, "TLS %s transport created", + (tls->is_server ? "server" : "client"))); return PJ_SUCCESS; on_error: - if (tls_tp) - tls_tp_destroy(&tls_tp->base); - else if (pool) { + tls_destroy(&tls->base, status); + return status; +} + + +/* Flush all delayed transmision once the socket is connected. + * Return non-zero if pending transmission list is empty after + * the function returns. + */ +static pj_bool_t tls_flush_pending_tx(struct tls_transport *tls) +{ + pj_bool_t empty; + + pj_lock_acquire(tls->base.lock); + while (!pj_list_empty(&tls->delayed_list)) { + struct delayed_tdata *pending_tx; + pjsip_tx_data *tdata; + pj_ioqueue_op_key_t *op_key; + pj_ssize_t size; + pj_status_t status; + + pending_tx = tls->delayed_list.next; + + tdata = pending_tx->tdata_op_key->tdata; + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + /* send the txdata */ + status = ssl_write(tls, tdata); + + /* On EWOULDBLOCK, suspend further transmissions */ + if (status == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK)) { + break; + } + + /* tdata has been transmitted (successfully or with failure). + * In any case, remove it from pending transmission list. + */ + pj_list_erase(pending_tx); + + /* Notify callback */ + if (status == PJ_SUCCESS) + size = tdata->buf.cur - tdata->buf.start; + else + size = -status; + + on_write_complete(tls->key, op_key, size); + + } + + empty = pj_list_empty(&tls->delayed_list); + + pj_lock_release(tls->base.lock); + + return empty; +} + + +/* Called by transport manager to destroy transport */ +static pj_status_t tls_destroy_transport(pjsip_transport *transport) +{ + struct tls_transport *tls = (struct tls_transport*)transport; + + /* Transport would have been unregistered by now since this callback + * is called by transport manager. + */ + tls->is_registered = PJ_FALSE; + + return tls_destroy(transport, tls->close_reason); +} + + +/* Destroy TLS transport */ +static pj_status_t tls_destroy(pjsip_transport *transport, + pj_status_t reason) +{ + struct tls_transport *tls = (struct tls_transport*)transport; + + if (tls->close_reason == 0) + tls->close_reason = reason; + + if (tls->is_registered) { + tls->is_registered = PJ_FALSE; + pjsip_transport_destroy(transport); + + /* pjsip_transport_destroy will recursively call this function + * again. + */ + return PJ_SUCCESS; + } + + /* Mark transport as closing */ + ++tls->is_closing; + + /* Cancel all delayed transmits */ + while (!pj_list_empty(&tls->delayed_list)) { + struct delayed_tdata *pending_tx; + pj_ioqueue_op_key_t *op_key; + + pending_tx = tls->delayed_list.next; + pj_list_erase(pending_tx); + + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + on_write_complete(tls->key, op_key, -reason); + } + + if (tls->rdata.tp_info.pool) { + pj_pool_release(tls->rdata.tp_info.pool); + tls->rdata.tp_info.pool = NULL; + } + + if (tls->key) { + pj_ioqueue_unregister(tls->key); + tls->key = NULL; + tls->sock = PJ_INVALID_SOCKET; + } + + if (tls->sock != PJ_INVALID_SOCKET) { + pj_sock_close(tls->sock); + tls->sock = PJ_INVALID_SOCKET; + } + + if (tls->base.lock) { + pj_lock_destroy(tls->base.lock); + tls->base.lock = NULL; + } + + if (tls->base.ref_cnt) { + pj_atomic_destroy(tls->base.ref_cnt); + tls->base.ref_cnt = NULL; + } + + if (tls->ssl) { + SSL_free(tls->ssl); + tls->ssl = NULL; + } + + if (tls->base.pool) { + pj_pool_t *pool; + + if (reason != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(reason, errmsg, sizeof(errmsg)); + PJ_LOG(4,(tls->base.obj_name, + "TLS transport destroyed with reason %d: %s", + reason, errmsg)); + + } else { + + PJ_LOG(4,(tls->base.obj_name, + "TLS transport destroyed normally")); + + } + + pool = tls->base.pool; + tls->base.pool = NULL; pj_pool_release(pool); - if (sock != PJ_INVALID_SOCKET) pj_sock_close(sock); } - return status; + + return PJ_SUCCESS; } /* - * Callback from transport manager to create a new (outbound) TLS transport. + * This utility function creates receive data buffers and start + * asynchronous recv() operations from the socket. It is called after + * accept() or connect() operation complete. + */ +static pj_status_t tls_start_read(struct tls_transport *tls) +{ + pj_pool_t *pool; + pj_ssize_t size; + pj_sockaddr_in *rem_addr; + pj_status_t status; + + /* Init rdata */ + pool = pjsip_endpt_create_pool(tls->listener->endpt, + "rtd%p", + PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_INC); + if (!pool) { + ssl_report_error(tls->base.obj_name, 4, PJ_ENOMEM, + "Unable to create pool for listener rxdata"); + return PJ_ENOMEM; + } + + tls->rdata.tp_info.pool = pool; + + tls->rdata.tp_info.transport = &tls->base; + tls->rdata.tp_info.tp_data = tls; + tls->rdata.tp_info.op_key.rdata = &tls->rdata; + pj_ioqueue_op_key_init(&tls->rdata.tp_info.op_key.op_key, + sizeof(pj_ioqueue_op_key_t)); + + tls->rdata.pkt_info.src_addr = tls->base.key.rem_addr; + tls->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in); + rem_addr = (pj_sockaddr_in*) &tls->base.key.rem_addr; + pj_ansi_strcpy(tls->rdata.pkt_info.src_name, + pj_inet_ntoa(rem_addr->sin_addr)); + tls->rdata.pkt_info.src_port = pj_ntohs(rem_addr->sin_port); + + /* Here's the real trick with OpenSSL. + * Since asynchronous socket operation with OpenSSL uses select() like + * mechanism, it's not really compatible with PJLIB's ioqueue. So to + * make them "talk" together, we simulate select() by using MSG_PEEK + * when we call pj_ioqueue_recv(). + */ + size = 1; + status = pj_ioqueue_recv(tls->key, &tls->rdata.tp_info.op_key.op_key, + tls->rdata.pkt_info.packet, &size, + PJ_IOQUEUE_ALWAYS_ASYNC | PJ_MSG_PEEK); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + ssl_report_error(tls->base.obj_name, 4, status, + "ioqueue_recv() error"); + return status; + } + + return PJ_SUCCESS; +} + + + +/* This callback is called by transport manager for the TLS factory + * to create outgoing transport to the specified destination. */ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, pjsip_tpmgr *mgr, pjsip_endpoint *endpt, const pj_sockaddr *rem_addr, int addr_len, - pjsip_transport **transport) + pjsip_transport **p_transport) { + struct tls_listener *listener; + struct tls_transport *tls; + pj_sock_t sock; + pj_sockaddr_in local_addr; pj_status_t status; - struct tls_transport *tls_tp; - /* Check address */ + /* Sanity checks */ + PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr && + addr_len && p_transport, PJ_EINVAL); + + /* Check that address is a sockaddr_in */ PJ_ASSERT_RETURN(rem_addr->sa_family == PJ_AF_INET && addr_len == sizeof(pj_sockaddr_in), PJ_EINVAL); - PJ_UNUSED_ARG(mgr); - PJ_UNUSED_ARG(endpt); - /* addr_len is not used on Release build */ - PJ_UNUSED_ARG(addr_len); + listener = (struct tls_listener*)factory; + + + /* Create socket */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_STREAM, 0, &sock); + if (status != PJ_SUCCESS) + return status; + + /* Bind to any port */ + status = pj_sock_bind_in(sock, 0, 0); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + /* Get the local port */ + addr_len = sizeof(pj_sockaddr_in); + status = pj_sock_getsockname(sock, &local_addr, &addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + /* Initially set the address from the listener's address */ + local_addr.sin_addr.s_addr = + ((pj_sockaddr_in*)&listener->factory.local_addr)->sin_addr.s_addr; - /* Create TLS transport */ - status = tls_create_transport((struct tls_listener*)factory, - PJ_INVALID_SOCKET, - (const pj_sockaddr_in*)rem_addr, - &tls_tp); + /* Create the transport descriptor */ + status = tls_create(listener, NULL, sock, PJ_FALSE, &local_addr, + (pj_sockaddr_in*)rem_addr, &tls); if (status != PJ_SUCCESS) return status; + + /* Start asynchronous connect() operation */ + tls->has_pending_connect = PJ_TRUE; + status = pj_ioqueue_connect(tls->key, rem_addr, sizeof(pj_sockaddr_in)); + if (status == PJ_SUCCESS) { + + /* Immediate socket connect() ! */ + tls->has_pending_connect = PJ_FALSE; + + /* Perform SSL_connect() */ + status = ssl_connect(tls); + if (status != PJ_SUCCESS) { + tls_destroy(&tls->base, status); + return status; + } + + } else if (status != PJ_EPENDING) { + tls_destroy(&tls->base, status); + return status; + } + + /* Update (again) local address, just in case local address currently + * set is different now that asynchronous connect() is started. + */ + addr_len = sizeof(pj_sockaddr_in); + if (pj_sock_getsockname(tls->sock, &local_addr, &addr_len)==PJ_SUCCESS) { + pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tls->base.local_addr; + + /* Some systems (like old Win32 perhaps) may not set local address + * properly before socket is fully connected. + */ + if (tp_addr->sin_addr.s_addr != local_addr.sin_addr.s_addr && + local_addr.sin_addr.s_addr != 0) + { + tp_addr->sin_addr.s_addr = local_addr.sin_addr.s_addr; + tp_addr->sin_port = local_addr.sin_port; + sockaddr_to_host_port(tls->base.pool, &tls->base.local_name, + &local_addr); + } + } + + if (tls->has_pending_connect) { + PJ_LOG(4,(tls->base.obj_name, + "TLS transport %.*s:%d is connecting to %.*s:%d...", + (int)tls->base.local_name.host.slen, + tls->base.local_name.host.ptr, + tls->base.local_name.port, + (int)tls->base.remote_name.host.slen, + tls->base.remote_name.host.ptr, + tls->base.remote_name.port)); + } + /* Done */ - *transport = &tls_tp->base; + *p_transport = &tls->base; + return PJ_SUCCESS; } + /* - * Callback from transport manager to destroy TLS listener. + * This callback is called by ioqueue when pending accept() operation has + * completed. */ -static pj_status_t lis_destroy(pjsip_tpfactory *factory) +static void on_accept_complete( pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_sock_t sock, + pj_status_t status) { - struct tls_listener *lis = (struct tls_listener *) factory; + struct tls_listener *listener; + struct tls_transport *tls; + struct pending_accept *accept_op; + int err_cnt = 0; - PJ_LOG(4,(factory->obj_name, "TLS listener shutting down..")); + listener = pj_ioqueue_get_user_data(key); + accept_op = (struct pending_accept*) op_key; - if (lis->is_registered) { - pjsip_tpmgr_unregister_tpfactory(lis->tpmgr, &lis->base); - lis->is_registered = PJ_FALSE; - } + /* + * Loop while there is immediate connection or when there is error. + */ + do { + if (status == PJ_EPENDING) { + /* + * This can only happen when this function is called during + * initialization to kick off asynchronous accept(). + */ - if (lis->base.lock) { - pj_lock_destroy(lis->base.lock); - lis->base.lock = NULL; - } + } else if (status != PJ_SUCCESS) { + + /* + * Error in accept(). + */ + ssl_report_error(listener->factory.obj_name, 4, status, + "Error in asynchronous accept() completion"); + + /* + * Prevent endless accept() error loop by limiting the + * number of consecutive errors. Once the number of errors + * is equal to maximum, we treat this as permanent error, and + * we stop the accept() operation. + */ + ++err_cnt; + if (err_cnt >= 10) { + PJ_LOG(1, (listener->factory.obj_name, + "Too many errors, listener stopping")); + } + + } else { + pj_pool_t *pool; + struct pending_accept *new_op; + + if (sock == PJ_INVALID_SOCKET) { + sock = accept_op->new_sock; + } + + if (sock == PJ_INVALID_SOCKET) { + pj_assert(!"Should not happen. status should be error"); + goto next_accept; + } + + PJ_LOG(4,(listener->factory.obj_name, + "TLS listener %.*s:%d: got incoming TCP connection " + "from %s:%d, sock=%d", + (int)listener->factory.addr_name.host.slen, + listener->factory.addr_name.host.ptr, + listener->factory.addr_name.port, + pj_inet_ntoa(accept_op->remote_addr.sin_addr), + pj_ntohs(accept_op->remote_addr.sin_port), + sock)); + + /* Create new accept_opt */ + pool = pjsip_endpt_create_pool(listener->endpt, "tlss%p", + POOL_TP_INIT, POOL_TP_INC); + new_op = pj_pool_zalloc(pool, sizeof(struct pending_accept)); + new_op->pool = pool; + new_op->listener = listener; + new_op->index = accept_op->index; + pj_ioqueue_op_key_init(&new_op->op_key, sizeof(new_op->op_key)); + listener->accept_op[accept_op->index] = new_op; + + /* + * Incoming connections! + * Create TLS transport for the new socket. + */ + status = tls_create( listener, accept_op->pool, sock, PJ_TRUE, + &accept_op->local_addr, + &accept_op->remote_addr, &tls); + if (status == PJ_SUCCESS) { + /* Complete SSL_accept() */ + status = ssl_accept(tls); + } + + if (status == PJ_SUCCESS) { + /* Start asynchronous read from the socket */ + status = tls_start_read(tls); + } + + if (status != PJ_SUCCESS) { + ssl_report_error(tls->base.obj_name, 4, status, + "Error creating incoming TLS transport"); + pjsip_transport_shutdown(&tls->base); + } + + accept_op = new_op; + } - if (lis->ctx) { - destroy_ctx(lis->ctx); - lis->ctx = NULL; +next_accept: + /* + * Start the next asynchronous accept() operation. + */ + accept_op->addr_len = sizeof(pj_sockaddr_in); + accept_op->new_sock = PJ_INVALID_SOCKET; + + status = pj_ioqueue_accept(listener->key, + &accept_op->op_key, + &accept_op->new_sock, + &accept_op->local_addr, + &accept_op->remote_addr, + &accept_op->addr_len); + + /* + * Loop while we have immediate connection or when there is error. + */ + + } while (status != PJ_EPENDING); +} + + +/* + * Callback from ioqueue when packet is sent. + */ +static void on_write_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_sent) +{ + struct tls_transport *tls = pj_ioqueue_get_user_data(key); + pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key; + + tdata_op_key->tdata = NULL; + + /* Check for error/closure */ + if (bytes_sent <= 0) { + pj_status_t status; + + ssl_report_error(tls->base.obj_name, 4, -bytes_sent, + "TLS send() error"); + + status = (bytes_sent == 0) ? PJ_STATUS_FROM_OS(OSERR_ENOTCONN) : + -bytes_sent; + if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; + pjsip_transport_shutdown(&tls->base); } - if (lis->base.pool) { - pj_pool_t *pool = lis->base.pool; - lis->base.pool = NULL; - pj_pool_release(pool); + if (tdata_op_key->callback) { + /* + * Notify sip_transport.c that packet has been sent. + */ + tdata_op_key->callback(&tls->base, tdata_op_key->token, bytes_sent); } +} - /* Shutdown OpenSSL */ - shutdown_openssl(); - return PJ_SUCCESS; +/* Add tdata to pending list */ +static void add_pending_tx(struct tls_transport *tls, + pjsip_tx_data *tdata) +{ + struct delayed_tdata *delayed_tdata; + + delayed_tdata = pj_pool_alloc(tdata->pool, + sizeof(*delayed_tdata)); + delayed_tdata->tdata_op_key = &tdata->op_key; + pj_list_push_back(&tls->delayed_list, delayed_tdata); } -/* - * Function to be called by transport manager to send SIP message. +/* + * This callback is called by transport manager to send SIP message */ -static pj_status_t tls_tp_send_msg(pjsip_transport *transport, - pjsip_tx_data *tdata, - const pj_sockaddr_t *rem_addr, - int addr_len, - void *token, - void (*callback)(pjsip_transport *transport, - void *token, - pj_ssize_t sent_bytes)) +static pj_status_t tls_send_msg(pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + void (*callback)(pjsip_transport *transport, + void *token, + pj_ssize_t sent_bytes)) { - struct tls_transport *tls_tp = (struct tls_transport*) transport; - int bytes_sent; - - /* This is a connection oriented protocol, so rem_addr is not used */ - PJ_UNUSED_ARG(rem_addr); - PJ_UNUSED_ARG(addr_len); + struct tls_transport *tls = (struct tls_transport*)transport; + pj_ssize_t size; + pj_bool_t delayed = PJ_FALSE; + pj_status_t status = PJ_SUCCESS; - /* Data written immediately, no need to call callback */ - PJ_UNUSED_ARG(callback); - PJ_UNUSED_ARG(token); + /* Sanity check */ + PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL); - /* Write to TLS */ - bytes_sent = SSL_write (tls_tp->ssl, tdata->buf.start, - tdata->buf.cur - tdata->buf.start); + /* Check that there's no pending operation associated with the tdata */ + PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX); - switch (SSL_get_error (tls_tp->ssl, bytes_sent)) { - case SSL_ERROR_NONE: - pj_assert(bytes_sent == tdata->buf.cur - tdata->buf.start); - return PJ_SUCCESS; - - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - return PJ_RETURN_OS_ERROR(OSERR_EWOULDBLOCK); - - case SSL_ERROR_ZERO_RETURN: - /* The peer has notified us that it is shutting down via the SSL - * "close_notify" message so we need to shutdown, too. + /* Check the address is supported */ + PJ_ASSERT_RETURN(rem_addr && addr_len==sizeof(pj_sockaddr_in), PJ_EINVAL); + + + + /* Init op key. */ + tdata->op_key.tdata = tdata; + tdata->op_key.token = token; + tdata->op_key.callback = callback; + + /* If asynchronous connect() has not completed yet, just put the + * transmit data in the pending transmission list since we can not + * use the socket yet. + */ + if (tls->has_pending_connect) { + + /* + * Looks like connect() is still in progress. Check again (this time + * with holding the lock) to be sure. */ - pj_assert(bytes_sent == tdata->buf.cur - tdata->buf.start); - SSL_shutdown (tls_tp->ssl); - pjsip_transport_shutdown(transport); - return PJ_SUCCESS; - - case SSL_ERROR_SYSCALL: - if (bytes_sent == 0) { - /* An EOF occured but the SSL "close_notify" message was not - * sent. This is a protocol error, but we ignore it. + pj_lock_acquire(tls->base.lock); + + if (tls->has_pending_connect) { + /* + * connect() is still in progress. Put the transmit data to + * the delayed list. */ - pjsip_transport_shutdown(transport); - return 0; + add_pending_tx(tls, tdata); + status = PJ_EPENDING; + + /* Prevent ssl_write() to be called below */ + delayed = PJ_TRUE; } - return pj_get_netos_error(); - - default: - /* Reset errno to prevent previous values (e.g. EWOULDBLOCK) - * from being associated with fatal SSL errors. + + pj_lock_release(tls->base.lock); + } + + if (!delayed) { + + pj_bool_t no_pending_tx; + + /* Make sure that we've flushed pending tx first so that + * stream is in order. */ - pj_set_netos_error(0); - ssl_report_error(4, transport->obj_name, "SSL_write error"); - return PJSIP_TLS_ESEND; + no_pending_tx = tls_flush_pending_tx(tls); + + /* Send data immediately with SSL_write() if we don't have + * pending data in our list. + */ + if (no_pending_tx) { + + status = ssl_write(tls, tdata); + + /* On EWOULDBLOCK, put this tdata in the list */ + if (status == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK)) { + add_pending_tx(tls, tdata); + status = PJ_EPENDING; + } + + if (status != PJ_EPENDING) { + /* Not pending (could be immediate success or error) */ + tdata->op_key.tdata = NULL; + + /* Shutdown transport on closure/errors */ + if (status != PJ_SUCCESS) { + size = -status; + + ssl_report_error(tls->base.obj_name, 4, status, + "TLS send() error"); + + if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; + pjsip_transport_shutdown(&tls->base); + } + } + + } else { + /* We have pending data in our list, so queue the txdata + * in the pending tx list. + */ + add_pending_tx(tls, tdata); + status = PJ_EPENDING; + } } + + return status; } -/* - * Instruct the transport to initiate graceful shutdown procedure. +/* + * This callback is called by transport manager to shutdown transport. + * This normally is only used by UDP transport. */ -static pj_status_t tls_tp_do_shutdown(pjsip_transport *transport) +static pj_status_t tls_shutdown(pjsip_transport *transport) { - PJ_LOG(4,(transport->obj_name, "TLS transport marked for shutdown..")); + struct tls_transport *tls = (struct tls_transport*)transport; + + /* Shutdown SSL */ + if (!tls->ssl_shutdown_called) { + /* Release our reference counter and shutdown SSL */ + pjsip_transport_dec_ref(transport); + SSL_shutdown(tls->ssl); + tls->ssl_shutdown_called = PJ_TRUE; - /* Nothing to do for TLS */ - PJ_UNUSED_ARG(transport); + PJ_LOG(4,(transport->obj_name, "TLS transport shutdown")); + } return PJ_SUCCESS; } -/* - * Forcefully destroy this transport. + +/* + * Callback from ioqueue that an incoming data is received from the socket. */ -static pj_status_t tls_tp_destroy(pjsip_transport *transport) +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read_unused) { - struct tls_transport *tls_tp = (struct tls_transport*) transport; + enum { MAX_IMMEDIATE_PACKET = 10 }; + pjsip_rx_data_op_key *rdata_op_key = (pjsip_rx_data_op_key*) op_key; + pjsip_rx_data *rdata = rdata_op_key->rdata; + struct tls_transport *tls = + (struct tls_transport*)rdata->tp_info.transport; + int i; + pj_status_t status; + + /* Don't do anything if transport is closing. */ + if (tls->is_closing) { + tls->is_closing++; + return; + } - PJ_LOG(4,(transport->obj_name, "Destroying TLS transport..")); - if (tls_tp->thread) { - tls_tp->quitting = PJ_TRUE; - SSL_shutdown(tls_tp->ssl); + /* Recall that we use MSG_PEEK when calling ioqueue_recv(), so + * when this callback is called, data has not actually been read + * from socket buffer. + */ - pj_thread_join(tls_tp->thread); - pj_thread_destroy(tls_tp->thread); - tls_tp->thread = NULL; - } + for (i=0;; ++i) { + pj_uint32_t flags; + + /* Read data from SSL connection */ + status = ssl_read(tls); + + if (status == PJ_SUCCESS) { + /* + * We have packet! + */ + pj_size_t size_eaten; + + /* Init pkt_info part. */ + rdata->pkt_info.zero = 0; + pj_gettimeofday(&rdata->pkt_info.timestamp); + + /* Report to transport manager. + * The transport manager will tell us how many bytes of the packet + * have been processed (as valid SIP message). + */ + size_eaten = + pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, + rdata); + + pj_assert(size_eaten <= (pj_size_t)rdata->pkt_info.len); + + /* Move unprocessed data to the front of the buffer */ + if (size_eaten>0 && size_eaten<(pj_size_t)rdata->pkt_info.len) { + pj_memmove(rdata->pkt_info.packet, + rdata->pkt_info.packet + size_eaten, + rdata->pkt_info.len - size_eaten); + } + + rdata->pkt_info.len -= size_eaten; + + } else if (status == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK)) { + + /* Ignore EWOULDBLOCK error (?) */ + + } else { + + /* For other errors, treat as transport being closed */ + ssl_report_error(tls->base.obj_name, 4, status, + "Error reading SSL stream"); + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + if (tls->close_reason==PJ_SUCCESS) + tls->close_reason = status; + pjsip_transport_shutdown(&tls->base); + + return; + } + + /* Reset pool. */ + pj_pool_reset(rdata->tp_info.pool); + + /* If we have pending data in SSL buffer, read it. */ + if (SSL_pending(tls->ssl)) { + + /* Check that we have enough space in buffer */ + if (rdata->pkt_info.len >= PJSIP_MAX_PKT_LEN-1) { + PJ_LOG(4,(tls->base.obj_name, + "Incoming packet dropped from tls:%.*s:%d " + "because it's too big (%d bytes)", + (int)tls->base.remote_name.host.slen, + tls->base.remote_name.host.ptr, + tls->base.remote_name.port, + rdata->pkt_info.len)); + rdata->pkt_info.len = 0; + } + + continue; + } + + /* If we've reached maximum number of packets received on a single + * poll, force the next reading to be asynchronous. + */ + if (i >= MAX_IMMEDIATE_PACKET) { + /* Receive quota reached. Force ioqueue_recv() to + * return PJ_EPENDING + */ + flags = PJ_IOQUEUE_ALWAYS_ASYNC; + } else { + flags = 0; + } + + /* Read next packet from the network. Remember, we need to use + * MSG_PEEK or otherwise the packet will be eaten by us! + */ + bytes_read_unused = 1; + status = pj_ioqueue_recv(key, op_key, + rdata->pkt_info.packet+rdata->pkt_info.len, + &bytes_read_unused, flags | PJ_MSG_PEEK); + + if (status == PJ_SUCCESS) { - if (tls_tp->ssl) { - int rc; - rc = SSL_shutdown(tls_tp->ssl); - if (rc == 0) { - pj_sock_shutdown(tls_tp->sock, PJ_SD_BOTH); - SSL_shutdown(tls_tp->ssl); + /* Continue loop. */ + pj_assert(i < MAX_IMMEDIATE_PACKET); + + } else if (status == PJ_EPENDING) { + break; + + } else { + /* Socket error */ + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; + pjsip_transport_shutdown(&tls->base); + + return; } + } +} + + +/* + * Callback from ioqueue when asynchronous connect() operation completes. + */ +static void on_connect_complete(pj_ioqueue_key_t *key, + pj_status_t status) +{ + struct tls_transport *tls; + pj_sockaddr_in addr; + int addrlen; + + tls = pj_ioqueue_get_user_data(key); + + /* Check connect() status */ + if (status != PJ_SUCCESS) { + + /* Mark that pending connect() operation has completed. */ + tls->has_pending_connect = PJ_FALSE; + + ssl_report_error(tls->base.obj_name, 4, status, + "Error connecting to %.*s:%d", + (int)tls->base.remote_name.host.slen, + tls->base.remote_name.host.ptr, + tls->base.remote_name.port); + + /* Cancel all delayed transmits */ + while (!pj_list_empty(&tls->delayed_list)) { + struct delayed_tdata *pending_tx; + pj_ioqueue_op_key_t *op_key; - SSL_free(tls_tp->ssl); - tls_tp->ssl = NULL; - tls_tp->sock = PJ_INVALID_SOCKET; + pending_tx = tls->delayed_list.next; + pj_list_erase(pending_tx); - } else if (tls_tp->sock != PJ_INVALID_SOCKET) { - pj_sock_close(tls_tp->sock); - tls_tp->sock = PJ_INVALID_SOCKET; + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + on_write_complete(tls->key, op_key, -status); + } + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; + pjsip_transport_shutdown(&tls->base); + return; } - if (tls_tp->base.lock) { - pj_lock_destroy(tls_tp->base.lock); - tls_tp->base.lock = NULL; + PJ_LOG(4,(tls->base.obj_name, + "TCP transport %.*s:%d is connected to %.*s:%d", + (int)tls->base.local_name.host.slen, + tls->base.local_name.host.ptr, + tls->base.local_name.port, + (int)tls->base.remote_name.host.slen, + tls->base.remote_name.host.ptr, + tls->base.remote_name.port)); + + + /* Update (again) local address, just in case local address currently + * set is different now that the socket is connected (could happen + * on some systems, like old Win32 probably?). + */ + addrlen = sizeof(pj_sockaddr_in); + if (pj_sock_getsockname(tls->sock, &addr, &addrlen)==PJ_SUCCESS) { + pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tls->base.local_addr; + + if (tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr) { + tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr; + tp_addr->sin_port = addr.sin_port; + sockaddr_to_host_port(tls->base.pool, &tls->base.local_name, + tp_addr); + } } - if (tls_tp->base.ref_cnt) { - pj_atomic_destroy(tls_tp->base.ref_cnt); - tls_tp->base.ref_cnt = NULL; + /* Perform SSL_connect() */ + status = ssl_connect(tls); + if (status != PJ_SUCCESS) { + + /* Cancel all delayed transmits */ + while (!pj_list_empty(&tls->delayed_list)) { + struct delayed_tdata *pending_tx; + pj_ioqueue_op_key_t *op_key; + + pending_tx = tls->delayed_list.next; + pj_list_erase(pending_tx); + + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + on_write_complete(tls->key, op_key, -status); + } + + if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; + pjsip_transport_shutdown(&tls->base); + return; } - if (tls_tp->base.pool) { - pj_pool_t *pool = tls_tp->base.pool; - tls_tp->base.pool = NULL; - pj_pool_release(pool); + /* Mark that pending connect() operation has completed. */ + tls->has_pending_connect = PJ_FALSE; + + /* Start pending read */ + status = tls_start_read(tls); + if (status != PJ_SUCCESS) { + + /* Cancel all delayed transmits */ + while (!pj_list_empty(&tls->delayed_list)) { + struct delayed_tdata *pending_tx; + pj_ioqueue_op_key_t *op_key; + + pending_tx = tls->delayed_list.next; + pj_list_erase(pending_tx); + + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + on_write_complete(tls->key, op_key, -status); + } + + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; + pjsip_transport_shutdown(&tls->base); + return; } - PJ_LOG(4,(THIS_FILE, "TLS transport destroyed")); - return PJ_SUCCESS; + /* Flush all pending send operations */ + tls_flush_pending_tx(tls); } - #endif /* PJSIP_HAS_TLS_TRANSPORT */ diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index ee1439fd..21864833 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -1049,13 +1049,45 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, /* * Create TLS transport. */ + /* + * Create TCP transport. + */ + pjsua_transport_config config; + pjsip_host_port a_name; pjsip_tpfactory *tls; + pj_sockaddr_in local_addr; + + /* Supply default config if it's not specified */ + if (cfg == NULL) { + pjsua_transport_config_default(&config); + config.port = 5061; + cfg = &config; + } + + /* Init local address */ + pj_sockaddr_in_init(&local_addr, 0, 0); + + if (cfg->port) + local_addr.sin_port = pj_htons((pj_uint16_t)cfg->port); + + if (cfg->bound_addr.slen) { + status = pj_sockaddr_in_set_str_addr(&local_addr,&cfg->bound_addr); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to resolve transport bound address", + status); + goto on_return; + } + } + + /* Init published name */ + pj_bzero(&a_name, sizeof(pjsip_host_port)); + if (cfg->public_addr.slen) + a_name.host = cfg->public_addr; status = pjsip_tls_transport_start(pjsua_var.endpt, - &cfg->tls_key_file, - &cfg->tls_password, - &cfg->tls_ca_file, - NULL, NULL, 1, &tls); + &cfg->tls_setting, + &local_addr, &a_name, 1, &tls); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error creating SIP TLS listener", status); |