summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip/sip_transport_tls.c
diff options
context:
space:
mode:
authorDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
committerDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
commitf3ab456a17af1c89a6e3be4d20c5944853df1cb0 (patch)
treed00e1a332cd038a6d906a1ea0ac91e1a4458e617 /pjsip/src/pjsip/sip_transport_tls.c
Import pjproject-2.0.1
Diffstat (limited to 'pjsip/src/pjsip/sip_transport_tls.c')
-rw-r--r--pjsip/src/pjsip/sip_transport_tls.c1610
1 files changed, 1610 insertions, 0 deletions
diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c
new file mode 100644
index 0000000..878b6db
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_tls.c
@@ -0,0 +1,1610 @@
+/* $Id: sip_transport_tls.c 4146 2012-05-30 06:35:59Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <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/ssl_sock.h>
+#include <pj/assert.h>
+#include <pj/hash.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
+
+#define THIS_FILE "sip_transport_tls.c"
+
+#define MAX_ASYNC_CNT 16
+#define POOL_LIS_INIT 512
+#define POOL_LIS_INC 512
+#define POOL_TP_INIT 512
+#define POOL_TP_INC 512
+
+struct tls_listener;
+struct tls_transport;
+
+/*
+ * Definition of TLS/SSL transport listener, and it's descendant of
+ * pjsip_tpfactory.
+ */
+struct tls_listener
+{
+ pjsip_tpfactory factory;
+ pj_bool_t is_registered;
+ pjsip_endpoint *endpt;
+ pjsip_tpmgr *tpmgr;
+ pj_ssl_sock_t *ssock;
+ pj_ssl_cert_t *cert;
+ pjsip_tls_setting tls_setting;
+};
+
+
+/*
+ * 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/SSL transport, and it's descendant of pjsip_transport.
+ */
+struct tls_transport
+{
+ pjsip_transport base;
+ pj_bool_t is_server;
+ pj_str_t remote_name;
+
+ pj_bool_t is_registered;
+ pj_bool_t is_closing;
+ pj_status_t close_reason;
+ pj_ssl_sock_t *ssock;
+ pj_bool_t has_pending_connect;
+ pj_bool_t verify_server;
+
+ /* Keep-alive timer. */
+ pj_timer_entry ka_timer;
+ pj_time_val last_activity;
+ pjsip_tx_data_op_key ka_op_key;
+ pj_str_t ka_pkt;
+
+ /* TLS transport can only have one rdata!
+ * Otherwise chunks of incoming PDU may be received on different
+ * buffer.
+ */
+ pjsip_rx_data rdata;
+
+ /* Pending transmission list. */
+ struct delayed_tdata delayed_list;
+};
+
+
+/****************************************************************************
+ * PROTOTYPES
+ */
+
+/* This callback is called when pending accept() operation completes. */
+static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock,
+ pj_ssl_sock_t *new_ssock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len);
+
+/* Callback on incoming data */
+static pj_bool_t on_data_read(pj_ssl_sock_t *ssock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder);
+
+/* Callback when packet is sent */
+static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock,
+ pj_ioqueue_op_key_t *send_key,
+ pj_ssize_t sent);
+
+/* 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_tx_data *tdata,
+ 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_ssl_sock_t *ssock,
+ pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ const pj_str_t *remote_name,
+ struct tls_transport **p_tls);
+
+
+static void tls_perror(const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
+}
+
+
+static void sockaddr_to_host_port( pj_pool_t *pool,
+ pjsip_host_port *host_port,
+ const pj_sockaddr_in *addr )
+{
+ host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4);
+ pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 2);
+ host_port->host.slen = pj_ansi_strlen(host_port->host.ptr);
+ host_port->port = pj_sockaddr_get_port(addr);
+}
+
+
+static void tls_init_shutdown(struct tls_transport *tls, pj_status_t status)
+{
+ pjsip_tp_state_callback state_cb;
+
+ if (tls->close_reason == PJ_SUCCESS)
+ tls->close_reason = status;
+
+ if (tls->base.is_shutdown)
+ return;
+
+ /* Prevent immediate transport destroy by application, as transport
+ * state notification callback may be stacked and transport instance
+ * must remain valid at any point in the callback.
+ */
+ pjsip_transport_add_ref(&tls->base);
+
+ /* Notify application of transport disconnected state */
+ state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+ pjsip_tls_state_info tls_info;
+ pj_ssl_sock_info ssl_info;
+
+ /* Init transport state info */
+ pj_bzero(&state_info, sizeof(state_info));
+ state_info.status = tls->close_reason;
+
+ if (tls->ssock &&
+ pj_ssl_sock_get_info(tls->ssock, &ssl_info) == PJ_SUCCESS)
+ {
+ pj_bzero(&tls_info, sizeof(tls_info));
+ tls_info.ssl_sock_info = &ssl_info;
+ state_info.ext_info = &tls_info;
+ }
+
+ (*state_cb)(&tls->base, PJSIP_TP_STATE_DISCONNECTED, &state_info);
+ }
+
+ /* 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.
+ */
+ pjsip_transport_shutdown(&tls->base);
+
+ /* Now, it is ok to destroy the transport. */
+ pjsip_transport_dec_ref(&tls->base);
+}
+
+
+/****************************************************************************
+ * 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_ssl_sock_param ssock_param;
+ pj_sockaddr_in *listener_addr;
+ pj_bool_t has_listener;
+ 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_T(pool, 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);
+
+ pj_ansi_strcpy(listener->factory.obj_name, "tlslis");
+
+ if (opt)
+ pjsip_tls_setting_copy(pool, &listener->tls_setting, opt);
+ else
+ pjsip_tls_setting_default(&listener->tls_setting);
+
+ status = pj_lock_create_recursive_mutex(pool, "tlslis",
+ &listener->factory.lock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (async_cnt > MAX_ASYNC_CNT)
+ async_cnt = MAX_ASYNC_CNT;
+
+ /* Build SSL socket param */
+ pj_ssl_sock_param_default(&ssock_param);
+ ssock_param.cb.on_accept_complete = &on_accept_complete;
+ ssock_param.cb.on_data_read = &on_data_read;
+ ssock_param.cb.on_data_sent = &on_data_sent;
+ ssock_param.async_cnt = async_cnt;
+ ssock_param.ioqueue = pjsip_endpt_get_ioqueue(endpt);
+ ssock_param.require_client_cert = listener->tls_setting.require_client_cert;
+ ssock_param.timeout = listener->tls_setting.timeout;
+ ssock_param.user_data = listener;
+ ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket
+ * due to verification error */
+ if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN;
+ if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN;
+ ssock_param.ciphers_num = listener->tls_setting.ciphers_num;
+ ssock_param.ciphers = listener->tls_setting.ciphers;
+ ssock_param.qos_type = listener->tls_setting.qos_type;
+ ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error;
+ pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params,
+ sizeof(ssock_param.qos_params));
+
+ has_listener = PJ_FALSE;
+
+ switch(listener->tls_setting.method) {
+ case PJSIP_TLSV1_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1;
+ break;
+ case PJSIP_SSLV2_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2;
+ break;
+ case PJSIP_SSLV3_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3;
+ break;
+ case PJSIP_SSLV23_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23;
+ break;
+ default:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT;
+ break;
+ }
+
+ /* Create SSL socket */
+ status = pj_ssl_sock_create(pool, &ssock_param, &listener->ssock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ listener_addr = (pj_sockaddr_in*)&listener->factory.local_addr;
+ if (local) {
+ pj_sockaddr_cp((pj_sockaddr_t*)listener_addr,
+ (const pj_sockaddr_t*)local);
+ } else {
+ pj_sockaddr_in_init(listener_addr, NULL, 0);
+ }
+
+ /* Check if certificate/CA list for SSL socket is set */
+ if (listener->tls_setting.cert_file.slen ||
+ listener->tls_setting.ca_list_file.slen)
+ {
+ status = pj_ssl_cert_load_from_files(pool,
+ &listener->tls_setting.ca_list_file,
+ &listener->tls_setting.cert_file,
+ &listener->tls_setting.privkey_file,
+ &listener->tls_setting.password,
+ &listener->cert);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_ssl_sock_set_certificate(listener->ssock, pool,
+ listener->cert);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Start accepting incoming connections. Note that some TLS/SSL backends
+ * may not support for SSL socket server.
+ */
+ has_listener = PJ_FALSE;
+
+ status = pj_ssl_sock_start_accept(listener->ssock, pool,
+ (pj_sockaddr_t*)listener_addr,
+ pj_sockaddr_get_len((pj_sockaddr_t*)listener_addr));
+ if (status == PJ_SUCCESS || status == PJ_EPENDING) {
+ pj_ssl_sock_info info;
+ has_listener = PJ_TRUE;
+
+ /* Retrieve the bound address */
+ status = pj_ssl_sock_get_info(listener->ssock, &info);
+ if (status == PJ_SUCCESS)
+ pj_sockaddr_cp(listener_addr, (pj_sockaddr_t*)&info.local_addr);
+ } else if (status != PJ_ENOTSUP) {
+ goto on_error;
+ }
+
+ /* If published host/IP is specified, then use that address as the
+ * listener advertised address.
+ */
+ 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_sockaddr hostip;
+
+ status = pj_gethostip(pj_AF_INET(), &hostip);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ listener_addr->sin_addr.s_addr = hostip.ipv4.sin_addr.s_addr;
+ }
+
+ /* 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);
+ }
+
+ pj_ansi_snprintf(listener->factory.obj_name,
+ sizeof(listener->factory.obj_name),
+ "tlslis:%d", listener->factory.addr_name.port);
+
+ /* Register to transport manager */
+ listener->endpt = endpt;
+ listener->tpmgr = pjsip_endpt_get_tpmgr(endpt);
+ listener->factory.create_transport2 = 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;
+ }
+
+ if (has_listener) {
+ PJ_LOG(4,(listener->factory.obj_name,
+ "SIP TLS listener is 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));
+ } else {
+ PJ_LOG(4,(listener->factory.obj_name, "SIP TLS is ready "
+ "(client only)"));
+ }
+
+ /* 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;
+
+ if (listener->is_registered) {
+ pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory);
+ listener->is_registered = PJ_FALSE;
+ }
+
+ if (listener->ssock) {
+ pj_ssl_sock_close(listener->ssock);
+ listener->ssock = NULL;
+ }
+
+ if (listener->factory.lock) {
+ pj_lock_destroy(listener->factory.lock);
+ listener->factory.lock = 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);
+ }
+
+ 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,
+ pjsip_transport_callback callback);
+
+/* 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 when connect completes */
+static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock,
+ pj_status_t status);
+
+/* TLS keep-alive timer callback */
+static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e);
+
+/*
+ * 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_ssl_sock_t *ssock,
+ pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ const pj_str_t *remote_name,
+ struct tls_transport **p_tls)
+{
+ struct tls_transport *tls;
+ const pj_str_t ka_pkt = PJSIP_TLS_KEEP_ALIVE_DATA;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(listener && ssock && local && remote && p_tls, 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);
+ }
+
+ /*
+ * Create and initialize basic transport structure.
+ */
+ tls = PJ_POOL_ZALLOC_T(pool, struct tls_transport);
+ tls->is_server = is_server;
+ tls->verify_server = listener->tls_setting.verify_server;
+ 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);
+
+ status = pj_atomic_create(pool, 0, &tls->base.ref_cnt);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ status = pj_lock_create_recursive_mutex(pool, "tls", &tls->base.lock);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ if (remote_name)
+ pj_strdup(pool, &tls->remote_name, remote_name);
+
+ 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 = (char*) 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);
+ tls->base.dir = is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING;
+
+ /* Set initial local address */
+ if (!pj_sockaddr_has_addr(local)) {
+ pj_sockaddr_cp(&tls->base.local_addr,
+ &listener->factory.local_addr);
+ } else {
+ pj_sockaddr_cp(&tls->base.local_addr, local);
+ }
+
+ sockaddr_to_host_port(pool, &tls->base.local_name,
+ (pj_sockaddr_in*)&tls->base.local_addr);
+ if (tls->remote_name.slen) {
+ tls->base.remote_name.host = tls->remote_name;
+ tls->base.remote_name.port = pj_sockaddr_in_get_port(remote);
+ } else {
+ 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;
+
+ tls->ssock = ssock;
+
+ /* Register transport to transport manager */
+ status = pjsip_transport_register(listener->tpmgr, &tls->base);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ tls->is_registered = PJ_TRUE;
+
+ /* Initialize keep-alive timer */
+ tls->ka_timer.user_data = (void*)tls;
+ tls->ka_timer.cb = &tls_keep_alive_timer;
+ pj_ioqueue_op_key_init(&tls->ka_op_key.key, sizeof(pj_ioqueue_op_key_t));
+ pj_strdup(tls->base.pool, &tls->ka_pkt, &ka_pkt);
+
+ /* 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:
+ tls_destroy(&tls->base, status);
+ return status;
+}
+
+
+/* Flush all delayed transmision once the socket is connected. */
+static void tls_flush_pending_tx(struct tls_transport *tls)
+{
+ 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;
+ pj_list_erase(pending_tx);
+
+ tdata = pending_tx->tdata_op_key->tdata;
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ /* send! */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_ssl_sock_send(tls->ssock, op_key, tdata->buf.start,
+ &size, 0);
+
+ if (status != PJ_EPENDING) {
+ on_data_sent(tls->ssock, op_key, size);
+ }
+ }
+ pj_lock_release(tls->base.lock);
+}
+
+
+/* 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 = PJ_TRUE;
+
+ /* Stop keep-alive timer. */
+ if (tls->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer);
+ tls->ka_timer.id = PJ_FALSE;
+ }
+
+ /* 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_data_sent(tls->ssock, 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->ssock) {
+ pj_ssl_sock_close(tls->ssock);
+ tls->ssock = NULL;
+ }
+ 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->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);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * 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;
+ void *readbuf[1];
+ pj_status_t status;
+
+ /* Init rdata */
+ pool = pjsip_endpt_create_pool(tls->base.endpt,
+ "rtd%p",
+ PJSIP_POOL_RDATA_LEN,
+ PJSIP_POOL_RDATA_INC);
+ if (!pool) {
+ tls_perror(tls->base.obj_name, "Unable to create pool", PJ_ENOMEM);
+ 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);
+
+ size = sizeof(tls->rdata.pkt_info.packet);
+ readbuf[0] = tls->rdata.pkt_info.packet;
+ status = pj_ssl_sock_start_read2(tls->ssock, tls->base.pool, size,
+ readbuf, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ PJ_LOG(4, (tls->base.obj_name,
+ "pj_ssl_sock_start_read() error, status=%d",
+ status));
+ 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_tx_data *tdata,
+ pjsip_transport **p_transport)
+{
+ struct tls_listener *listener;
+ struct tls_transport *tls;
+ pj_pool_t *pool;
+ pj_ssl_sock_t *ssock;
+ pj_ssl_sock_param ssock_param;
+ pj_sockaddr_in local_addr;
+ pj_str_t remote_name;
+ pj_status_t status;
+
+ /* 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->addr.sa_family == pj_AF_INET() &&
+ addr_len == sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+
+ listener = (struct tls_listener*)factory;
+
+ pool = pjsip_endpt_create_pool(listener->endpt, "tls",
+ POOL_TP_INIT, POOL_TP_INC);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ /* Get remote host name from tdata */
+ if (tdata)
+ remote_name = tdata->dest_info.name;
+ else
+ pj_bzero(&remote_name, sizeof(remote_name));
+
+ /* Build SSL socket param */
+ pj_ssl_sock_param_default(&ssock_param);
+ ssock_param.cb.on_connect_complete = &on_connect_complete;
+ ssock_param.cb.on_data_read = &on_data_read;
+ ssock_param.cb.on_data_sent = &on_data_sent;
+ ssock_param.async_cnt = 1;
+ ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt);
+ ssock_param.server_name = remote_name;
+ ssock_param.timeout = listener->tls_setting.timeout;
+ ssock_param.user_data = NULL; /* pending, must be set later */
+ ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket
+ * due to verification error */
+ if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN;
+ if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN;
+ ssock_param.ciphers_num = listener->tls_setting.ciphers_num;
+ ssock_param.ciphers = listener->tls_setting.ciphers;
+ ssock_param.qos_type = listener->tls_setting.qos_type;
+ ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error;
+ pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params,
+ sizeof(ssock_param.qos_params));
+
+ switch(listener->tls_setting.method) {
+ case PJSIP_TLSV1_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1;
+ break;
+ case PJSIP_SSLV2_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2;
+ break;
+ case PJSIP_SSLV3_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3;
+ break;
+ case PJSIP_SSLV23_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23;
+ break;
+ default:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT;
+ break;
+ }
+
+ status = pj_ssl_sock_create(pool, &ssock_param, &ssock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Apply SSL certificate */
+ if (listener->cert) {
+ status = pj_ssl_sock_set_certificate(ssock, pool, listener->cert);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Initially set bind address to PJ_INADDR_ANY port 0 */
+ pj_sockaddr_in_init(&local_addr, NULL, 0);
+
+ /* Create the transport descriptor */
+ status = tls_create(listener, pool, ssock, PJ_FALSE, &local_addr,
+ (pj_sockaddr_in*)rem_addr, &remote_name, &tls);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set the "pending" SSL socket user data */
+ pj_ssl_sock_set_user_data(tls->ssock, tls);
+
+ /* Start asynchronous connect() operation */
+ tls->has_pending_connect = PJ_TRUE;
+ status = pj_ssl_sock_start_connect(tls->ssock, tls->base.pool,
+ (pj_sockaddr_t*)&local_addr,
+ (pj_sockaddr_t*)rem_addr,
+ addr_len);
+ if (status == PJ_SUCCESS) {
+ on_connect_complete(tls->ssock, PJ_SUCCESS);
+ } else if (status != PJ_EPENDING) {
+ tls_destroy(&tls->base, status);
+ return status;
+ }
+
+ if (tls->has_pending_connect) {
+ pj_ssl_sock_info info;
+
+ /* Update local address, just in case local address currently set is
+ * different now that asynchronous connect() is started.
+ */
+
+ /* Retrieve the bound address */
+ status = pj_ssl_sock_get_info(tls->ssock, &info);
+ if (status == PJ_SUCCESS) {
+ pj_uint16_t new_port;
+
+ new_port = pj_sockaddr_get_port((pj_sockaddr_t*)&info.local_addr);
+
+ if (pj_sockaddr_has_addr((pj_sockaddr_t*)&info.local_addr)) {
+ /* Update sockaddr */
+ pj_sockaddr_cp((pj_sockaddr_t*)&tls->base.local_addr,
+ (pj_sockaddr_t*)&info.local_addr);
+ } else if (new_port && new_port != pj_sockaddr_get_port(
+ (pj_sockaddr_t*)&tls->base.local_addr))
+ {
+ /* Update port only */
+ pj_sockaddr_set_port(&tls->base.local_addr,
+ new_port);
+ }
+
+ sockaddr_to_host_port(tls->base.pool, &tls->base.local_name,
+ (pj_sockaddr_in*)&tls->base.local_addr);
+ }
+
+ 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 */
+ *p_transport = &tls->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This callback is called by SSL socket when pending accept() operation
+ * has completed.
+ */
+static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock,
+ pj_ssl_sock_t *new_ssock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len)
+{
+ struct tls_listener *listener;
+ struct tls_transport *tls;
+ pj_ssl_sock_info ssl_info;
+ char addr[PJ_INET6_ADDRSTRLEN+10];
+ pjsip_tp_state_callback state_cb;
+ pj_bool_t is_shutdown;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(src_addr_len);
+
+ listener = (struct tls_listener*) pj_ssl_sock_get_user_data(ssock);
+
+ PJ_ASSERT_RETURN(new_ssock, PJ_TRUE);
+
+ PJ_LOG(4,(listener->factory.obj_name,
+ "TLS listener %.*s:%d: got incoming TLS connection "
+ "from %s, sock=%d",
+ (int)listener->factory.addr_name.host.slen,
+ listener->factory.addr_name.host.ptr,
+ listener->factory.addr_name.port,
+ pj_sockaddr_print(src_addr, addr, sizeof(addr), 3),
+ new_ssock));
+
+ /* Retrieve SSL socket info, close the socket if this is failed
+ * as the SSL socket info availability is rather critical here.
+ */
+ status = pj_ssl_sock_get_info(new_ssock, &ssl_info);
+ if (status != PJ_SUCCESS) {
+ pj_ssl_sock_close(new_ssock);
+ return PJ_TRUE;
+ }
+
+ /*
+ * Incoming connection!
+ * Create TLS transport for the new socket.
+ */
+ status = tls_create( listener, NULL, new_ssock, PJ_TRUE,
+ (const pj_sockaddr_in*)&listener->factory.local_addr,
+ (const pj_sockaddr_in*)src_addr, NULL, &tls);
+
+ if (status != PJ_SUCCESS)
+ return PJ_TRUE;
+
+ /* Set the "pending" SSL socket user data */
+ pj_ssl_sock_set_user_data(new_ssock, tls);
+
+ /* Prevent immediate transport destroy as application may access it
+ * (getting info, etc) in transport state notification callback.
+ */
+ pjsip_transport_add_ref(&tls->base);
+
+ /* If there is verification error and verification is mandatory, shutdown
+ * and destroy the transport.
+ */
+ if (ssl_info.verify_status && listener->tls_setting.verify_client) {
+ if (tls->close_reason == PJ_SUCCESS)
+ tls->close_reason = PJSIP_TLS_ECERTVERIF;
+ pjsip_transport_shutdown(&tls->base);
+ }
+
+ /* Notify transport state to application */
+ state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+ pjsip_tls_state_info tls_info;
+ pjsip_transport_state tp_state;
+
+ /* Init transport state info */
+ pj_bzero(&tls_info, sizeof(tls_info));
+ pj_bzero(&state_info, sizeof(state_info));
+ tls_info.ssl_sock_info = &ssl_info;
+ state_info.ext_info = &tls_info;
+
+ /* Set transport state based on verification status */
+ if (ssl_info.verify_status && listener->tls_setting.verify_client)
+ {
+ tp_state = PJSIP_TP_STATE_DISCONNECTED;
+ state_info.status = PJSIP_TLS_ECERTVERIF;
+ } else {
+ tp_state = PJSIP_TP_STATE_CONNECTED;
+ state_info.status = PJ_SUCCESS;
+ }
+
+ (*state_cb)(&tls->base, tp_state, &state_info);
+ }
+
+ /* Release transport reference. If transport is shutting down, it may
+ * get destroyed here.
+ */
+ is_shutdown = tls->base.is_shutdown;
+ pjsip_transport_dec_ref(&tls->base);
+ if (is_shutdown)
+ return PJ_TRUE;
+
+
+ status = tls_start_read(tls);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(tls->base.obj_name, "New transport cancelled"));
+ tls_init_shutdown(tls, status);
+ tls_destroy(&tls->base, status);
+ } else {
+ /* Start keep-alive timer */
+ if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = {PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0};
+ pjsip_endpt_schedule_timer(listener->endpt,
+ &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tls->last_activity);
+ }
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when packet is sent.
+ */
+static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_sent)
+{
+ struct tls_transport *tls = (struct tls_transport*)
+ pj_ssl_sock_get_user_data(ssock);
+ pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key;
+
+ /* Note that op_key may be the op_key from keep-alive, thus
+ * it will not have tdata etc.
+ */
+
+ tdata_op_key->tdata = NULL;
+
+ if (tdata_op_key->callback) {
+ /*
+ * Notify sip_transport.c that packet has been sent.
+ */
+ if (bytes_sent == 0)
+ bytes_sent = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tdata_op_key->callback(&tls->base, tdata_op_key->token, bytes_sent);
+
+ /* Mark last activity time */
+ pj_gettimeofday(&tls->last_activity);
+
+ }
+
+ /* Check for error/closure */
+ if (bytes_sent <= 0) {
+ pj_status_t status;
+
+ PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d",
+ bytes_sent));
+
+ status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) :
+ -bytes_sent;
+
+ tls_init_shutdown(tls, status);
+
+ return PJ_FALSE;
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * This callback is called by transport manager to send SIP 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,
+ pjsip_transport_callback callback)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+ pj_ssize_t size;
+ pj_bool_t delayed = PJ_FALSE;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL);
+
+ /* Check that there's no pending operation associated with the tdata */
+ PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX);
+
+ /* 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_lock_acquire(tls->base.lock);
+
+ if (tls->has_pending_connect) {
+ struct delayed_tdata *delayed_tdata;
+
+ /*
+ * connect() is still in progress. Put the transmit data to
+ * the delayed list.
+ */
+ delayed_tdata = PJ_POOL_ALLOC_T(tdata->pool,
+ struct delayed_tdata);
+ delayed_tdata->tdata_op_key = &tdata->op_key;
+
+ pj_list_push_back(&tls->delayed_list, delayed_tdata);
+ status = PJ_EPENDING;
+
+ /* Prevent pj_ioqueue_send() to be called below */
+ delayed = PJ_TRUE;
+ }
+
+ pj_lock_release(tls->base.lock);
+ }
+
+ if (!delayed) {
+ /*
+ * Transport is ready to go. Send the packet to ioqueue to be
+ * sent asynchronously.
+ */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_ssl_sock_send(tls->ssock,
+ (pj_ioqueue_op_key_t*)&tdata->op_key,
+ tdata->buf.start, &size, 0);
+
+ if (status != PJ_EPENDING) {
+ /* Not pending (could be immediate success or error) */
+ tdata->op_key.tdata = NULL;
+
+ /* Shutdown transport on closure/errors */
+ if (size <= 0) {
+
+ PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d",
+ size));
+
+ if (status == PJ_SUCCESS)
+ status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tls_init_shutdown(tls, status);
+ }
+ }
+ }
+
+ return status;
+}
+
+
+/*
+ * This callback is called by transport manager to shutdown transport.
+ */
+static pj_status_t tls_shutdown(pjsip_transport *transport)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+
+ /* Stop keep-alive timer. */
+ if (tls->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer);
+ tls->ka_timer.id = PJ_FALSE;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Callback from ioqueue that an incoming data is received from the socket.
+ */
+static pj_bool_t on_data_read(pj_ssl_sock_t *ssock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
+{
+ enum { MAX_IMMEDIATE_PACKET = 10 };
+ struct tls_transport *tls;
+ pjsip_rx_data *rdata;
+
+ PJ_UNUSED_ARG(data);
+
+ tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock);
+ rdata = &tls->rdata;
+
+ /* Don't do anything if transport is closing. */
+ if (tls->is_closing) {
+ tls->is_closing++;
+ return PJ_FALSE;
+ }
+
+ /* Houston, we have packet! Report the packet to transport manager
+ * to be parsed.
+ */
+ if (status == PJ_SUCCESS) {
+ pj_size_t size_eaten;
+
+ /* Mark this as an activity */
+ pj_gettimeofday(&tls->last_activity);
+
+ pj_assert((void*)rdata->pkt_info.packet == data);
+
+ /* Init pkt_info part. */
+ rdata->pkt_info.len = size;
+ 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 */
+ *remainder = size - size_eaten;
+ if (*remainder > 0 && *remainder != size) {
+ pj_memmove(rdata->pkt_info.packet,
+ rdata->pkt_info.packet + size_eaten,
+ *remainder);
+ }
+
+ } else {
+
+ /* Transport is closed */
+ PJ_LOG(4,(tls->base.obj_name, "TLS connection closed"));
+
+ tls_init_shutdown(tls, status);
+
+ return PJ_FALSE;
+
+ }
+
+ /* Reset pool. */
+ pj_pool_reset(rdata->tp_info.pool);
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when asynchronous connect() operation completes.
+ */
+static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock,
+ pj_status_t status)
+{
+ struct tls_transport *tls;
+ pj_ssl_sock_info ssl_info;
+ pj_sockaddr_in addr, *tp_addr;
+ pjsip_tp_state_callback state_cb;
+ pj_bool_t is_shutdown;
+
+ tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock);
+
+ /* Check connect() status */
+ if (status != PJ_SUCCESS) {
+
+ tls_perror(tls->base.obj_name, "TLS connect() error", status);
+
+ /* 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_data_sent(tls->ssock, op_key, -status);
+ }
+
+ goto on_error;
+ }
+
+ /* Retrieve SSL socket info, shutdown the transport if this is failed
+ * as the SSL socket info availability is rather critical here.
+ */
+ status = pj_ssl_sock_get_info(tls->ssock, &ssl_info);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* 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?).
+ */
+ tp_addr = (pj_sockaddr_in*)&tls->base.local_addr;
+ pj_sockaddr_cp((pj_sockaddr_t*)&addr,
+ (pj_sockaddr_t*)&ssl_info.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);
+ }
+
+ /* Server identity verification based on server certificate. */
+ if (ssl_info.remote_cert_info->version) {
+ pj_str_t *remote_name;
+ pj_ssl_cert_info *serv_cert = ssl_info.remote_cert_info;
+ pj_bool_t matched = PJ_FALSE;
+ unsigned i;
+
+ /* Remote name may be hostname or IP address */
+ if (tls->remote_name.slen)
+ remote_name = &tls->remote_name;
+ else
+ remote_name = &tls->base.remote_name.host;
+
+ /* Start matching remote name with SubjectAltName fields of
+ * server certificate.
+ */
+ for (i = 0; i < serv_cert->subj_alt_name.cnt && !matched; ++i) {
+ pj_str_t *cert_name = &serv_cert->subj_alt_name.entry[i].name;
+
+ switch (serv_cert->subj_alt_name.entry[i].type) {
+ case PJ_SSL_CERT_NAME_DNS:
+ case PJ_SSL_CERT_NAME_IP:
+ matched = !pj_stricmp(remote_name, cert_name);
+ break;
+ case PJ_SSL_CERT_NAME_URI:
+ if (pj_strnicmp2(cert_name, "sip:", 4) == 0 ||
+ pj_strnicmp2(cert_name, "sips:", 5) == 0)
+ {
+ pj_str_t host_part;
+ char *p;
+
+ p = pj_strchr(cert_name, ':') + 1;
+ pj_strset(&host_part, p, cert_name->slen -
+ (p - cert_name->ptr));
+ matched = !pj_stricmp(remote_name, &host_part);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* When still not matched or no SubjectAltName fields in server
+ * certificate, try with Common Name of Subject field.
+ */
+ if (!matched) {
+ matched = !pj_stricmp(remote_name, &serv_cert->subject.cn);
+ }
+
+ if (!matched)
+ ssl_info.verify_status |= PJ_SSL_CERT_EIDENTITY_NOT_MATCH;
+ }
+
+ /* Prevent immediate transport destroy as application may access it
+ * (getting info, etc) in transport state notification callback.
+ */
+ pjsip_transport_add_ref(&tls->base);
+
+ /* If there is verification error and verification is mandatory, shutdown
+ * and destroy the transport.
+ */
+ if (ssl_info.verify_status && tls->verify_server) {
+ if (tls->close_reason == PJ_SUCCESS)
+ tls->close_reason = PJSIP_TLS_ECERTVERIF;
+ pjsip_transport_shutdown(&tls->base);
+ }
+
+ /* Notify transport state to application */
+ state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+ pjsip_tls_state_info tls_info;
+ pjsip_transport_state tp_state;
+
+ /* Init transport state info */
+ pj_bzero(&state_info, sizeof(state_info));
+ pj_bzero(&tls_info, sizeof(tls_info));
+ state_info.ext_info = &tls_info;
+ tls_info.ssl_sock_info = &ssl_info;
+
+ /* Set transport state based on verification status */
+ if (ssl_info.verify_status && tls->verify_server)
+ {
+ tp_state = PJSIP_TP_STATE_DISCONNECTED;
+ state_info.status = PJSIP_TLS_ECERTVERIF;
+ } else {
+ tp_state = PJSIP_TP_STATE_CONNECTED;
+ state_info.status = PJ_SUCCESS;
+ }
+
+ (*state_cb)(&tls->base, tp_state, &state_info);
+ }
+
+ /* Release transport reference. If transport is shutting down, it may
+ * get destroyed here.
+ */
+ is_shutdown = tls->base.is_shutdown;
+ pjsip_transport_dec_ref(&tls->base);
+ if (is_shutdown)
+ return PJ_FALSE;
+
+
+ /* Mark that pending connect() operation has completed. */
+ tls->has_pending_connect = PJ_FALSE;
+
+ PJ_LOG(4,(tls->base.obj_name,
+ "TLS 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));
+
+ /* Start pending read */
+ status = tls_start_read(tls);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Flush all pending send operations */
+ tls_flush_pending_tx(tls);
+
+ /* Start keep-alive timer */
+ if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = { PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0 };
+ pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tls->last_activity);
+ }
+
+ return PJ_TRUE;
+
+on_error:
+ tls_init_shutdown(tls, status);
+
+ return PJ_FALSE;
+}
+
+
+/* Transport keep-alive timer callback */
+static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e)
+{
+ struct tls_transport *tls = (struct tls_transport*) e->user_data;
+ pj_time_val delay;
+ pj_time_val now;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(th);
+
+ tls->ka_timer.id = PJ_TRUE;
+
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, tls->last_activity);
+
+ if (now.sec > 0 && now.sec < PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+ /* There has been activity, so don't send keep-alive */
+ delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL - now.sec;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+ return;
+ }
+
+ PJ_LOG(5,(tls->base.obj_name, "Sending %d byte(s) keep-alive to %.*s:%d",
+ (int)tls->ka_pkt.slen, (int)tls->base.remote_name.host.slen,
+ tls->base.remote_name.host.ptr,
+ tls->base.remote_name.port));
+
+ /* Send the data */
+ size = tls->ka_pkt.slen;
+ status = pj_ssl_sock_send(tls->ssock, &tls->ka_op_key.key,
+ tls->ka_pkt.ptr, &size, 0);
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ tls_perror(tls->base.obj_name,
+ "Error sending keep-alive packet", status);
+
+ tls_init_shutdown(tls, status);
+ return;
+ }
+
+ /* Register next keep-alive */
+ delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+}
+
+#endif /* PJSIP_HAS_TLS_TRANSPORT */