From 600ea59362df8c53ff23a6c5985eaf20b5460f5a Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Thu, 27 Aug 2009 19:55:13 +0000 Subject: Ticket #957: Initial version of TLS transport for Symbian, includes: - Secure socket, generic abstraction and Symbian implementation (using CSecureSocket). - Initial rewriting of SIP TLS transport. - Updated symbian_ua.mmp to support SIP transport TLS (experimental). git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2913 74dad513-b988-da41-8d7b-12977e46ad98 --- build.symbian/pjlib.mmp | 2 + build.symbian/pjsip.mmp | 1 + build.symbian/symbian_ua.mmp | 2 +- pjlib/include/pj/ssl_sock.h | 652 +++++++++++++++++ pjlib/src/pj/ssl_sock_common.c | 43 ++ pjlib/src/pj/ssl_sock_symbian.cpp | 1049 ++++++++++++++++++++++++++++ pjsip-apps/src/symbian_ua/ua.cpp | 29 +- pjsip/src/pjsip/sip_transport_tls.c | 1317 +++++++++++++++++++++++++++++++++++ 8 files changed, 3088 insertions(+), 7 deletions(-) create mode 100644 pjlib/include/pj/ssl_sock.h create mode 100644 pjlib/src/pj/ssl_sock_common.c create mode 100644 pjlib/src/pj/ssl_sock_symbian.cpp create mode 100644 pjsip/src/pjsip/sip_transport_tls.c diff --git a/build.symbian/pjlib.mmp b/build.symbian/pjlib.mmp index ec4a9902..1385b974 100644 --- a/build.symbian/pjlib.mmp +++ b/build.symbian/pjlib.mmp @@ -71,6 +71,8 @@ SOURCE os_timestamp_common.c SOURCE os_time_unix.c SOURCE os_timestamp_posix.c SOURCE pool_policy_new.cpp +SOURCE ssl_sock_common.c +SOURCE ssl_sock_symbian.cpp SOURCE sock_symbian.cpp SOURCE sock_select_symbian.cpp SOURCE timer_symbian.cpp diff --git a/build.symbian/pjsip.mmp b/build.symbian/pjsip.mmp index 785a799f..b74f4de6 100644 --- a/build.symbian/pjsip.mmp +++ b/build.symbian/pjsip.mmp @@ -51,6 +51,7 @@ SOURCE sip_transport_wrap.cpp SOURCE sip_transport_loop.c SOURCE sip_transport_tcp.c SOURCE sip_transport_udp.c +SOURCE sip_transport_tls.c SOURCE sip_ua_layer.c SOURCE sip_uri.c SOURCE sip_util_wrap.cpp diff --git a/build.symbian/symbian_ua.mmp b/build.symbian/symbian_ua.mmp index 8ef9f73f..60310aec 100644 --- a/build.symbian/symbian_ua.mmp +++ b/build.symbian/symbian_ua.mmp @@ -69,7 +69,7 @@ STATICLIBRARY libresample.lib STATICLIBRARY eexe.lib ecrt0.lib #endif -LIBRARY esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib +LIBRARY esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib securesocket.lib // The default 8KB seems to be insufficient with all bells and // whistles turned on diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h new file mode 100644 index 00000000..9836a3d6 --- /dev/null +++ b/pjlib/include/pj/ssl_sock.h @@ -0,0 +1,652 @@ +/* $Id$ */ +/* + * Copyright (C) 2009 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 + */ +#ifndef __PJ_SSL_SOCK_H__ +#define __PJ_SSL_SOCK_H__ + +/** + * @file ssl_sock.h + * @brief Secure socket + */ + +#include +#include + + +PJ_BEGIN_DECL + +/** + * @defgroup PJ_SSL_SOCK Secure socket I/O + * @brief Secure socket provides security on socket operation using standard + * security protocols such as SSL and TLS. + * @ingroup PJ_IO + * @{ + * + * Secure socket wraps normal socket and applies security features, i.e: + * privacy and data integrity, on the socket traffic, using standard security + * protocols such as SSL and TLS. + * + * Secure socket employs active socket operations, which is similar to (and + * described more detail) in \ref PJ_ACTIVESOCK. + */ + +/** + * Opaque declaration of certificate or endpoint credentials. This may contains + * certificate, private key, and trusted Certificate Authorities lists. + */ +typedef struct pj_ssl_cert_t pj_ssl_cert_t; + +/** + * This opaque structure describes the secure socket. + */ +typedef struct pj_ssl_sock_t pj_ssl_sock_t; + +/** + * This structure contains the callbacks to be called by the secure socket. + */ +typedef struct pj_ssl_sock_cb +{ + /** + * This callback is called when a data arrives as the result of + * pj_ssl_sock_start_read(). + * + * @param ssock The secure socket. + * @param data The buffer containing the new data, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument may be NULL. + * @param size The length of data in the buffer. + * @param status The status of the read operation. This may contain + * non-PJ_SUCCESS for example when the TCP connection + * has been closed. In this case, the buffer may + * contain left over data from previous callback which + * the application may want to process. + * @param remainder If application wishes to leave some data in the + * buffer (common for TCP applications), it should + * move the remainder data to the front part of the + * buffer and set the remainder length here. The value + * of this parameter will be ignored for datagram + * sockets. + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + 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); + /** + * This callback is called when a packet arrives as the result of + * pj_ssl_sock_start_recvfrom(). + * + * @param ssock The secure socket. + * @param data The buffer containing the packet, if any. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to NULL. + * @param size The length of packet in the buffer. If + * the status argument is non-PJ_SUCCESS, this + * argument will be set to zero. + * @param src_addr Source address of the packet. + * @param addr_len Length of the source address. + * @param status This contains + * + * @return PJ_TRUE if further read is desired, and PJ_FALSE + * when application no longer wants to receive data. + * Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_data_recvfrom)(pj_ssl_sock_t *ssock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status); + + /** + * This callback is called when data has been sent. + * + * @param ssock The secure socket. + * @param send_key Key associated with the send operation. + * @param sent If value is positive non-zero it indicates the + * number of data sent. When the value is negative, + * it contains the error code which can be retrieved + * by negating the value (i.e. status=-sent). + * + * @return Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + 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 when new connection arrives as the result + * of pj_ssl_sock_start_accept(). + * + * @param ssock The secure socket. + * @param newsock The new incoming secure socket. + * @param src_addr The source address of the connection. + * @param addr_len Length of the source address. + * + * @return PJ_TRUE if further accept() is desired, and PJ_FALSE + * when application no longer wants to accept incoming + * connection. Application may destroy the secure socket + * in the callback and return PJ_FALSE here. + */ + pj_bool_t (*on_accept_complete)(pj_ssl_sock_t *ssock, + pj_ssl_sock_t *newsock, + const pj_sockaddr_t *src_addr, + int src_addr_len); + + /** + * This callback is called when pending connect operation has been + * completed. + * + * @param ssock The secure socket. + * @param status The connection result. If connection has been + * successfully established, the status will contain + * PJ_SUCCESS. + * + * @return Application may destroy the secure socket in the + * callback and return PJ_FALSE here. + */ + pj_bool_t (*on_connect_complete)(pj_ssl_sock_t *ssock, + pj_status_t status); + +} pj_ssl_sock_cb; + + +/** + * Enumeration of secure socket protocol types. + */ +typedef enum pj_ssl_sock_proto +{ + PJ_SSL_SOCK_PROTO_DEFAULT, /**< Default protocol of backend. */ + PJ_SSL_SOCK_PROTO_TLS1, /**< TLSv1.0 protocol. */ + PJ_SSL_SOCK_PROTO_SSL2, /**< SSLv2.0 protocol. */ + PJ_SSL_SOCK_PROTO_SSL3, /**< SSLv3.0 protocol. */ + PJ_SSL_SOCK_PROTO_SSL23, /**< SSLv3.0 but can roll back to + SSLv2.0. */ + PJ_SSL_SOCK_PROTO_DTLS1 /**< DTLSv1.0 protocol. */ +} pj_ssl_sock_proto; + + +/** + * Definition of secure socket info structure. + */ +typedef struct pj_ssl_sock_info +{ + /** + * Describes whether secure socket connection is established, i.e: TLS/SSL + * handshaking has been done successfully. + */ + pj_bool_t established; + /** + * Describes secure socket protocol being used. + */ + pj_ssl_sock_proto proto; + /** + * Describes cipher suite being used, this can be known only when + * connection is established. + */ + pj_str_t cipher; + /** + * Describes local address. + */ + pj_sockaddr local_addr; + /** + * Describes remote address. + */ + pj_sockaddr remote_addr; + +} pj_ssl_sock_info; + +/** + * Definition of secure socket creation parameters. + */ +typedef struct pj_ssl_sock_param +{ + /** + * Specifies socket address family, either pj_AF_INET() and pj_AF_INET6(). + * + * Default is pj_AF_INET(). + */ + int sock_af; + + /** + * Specify socket type, either pj_SOCK_DGRAM() or pj_SOCK_STREAM(). + * + * Default is pj_SOCK_STREAM(). + */ + int sock_type; + + /** + * Specify the ioqueue to use. Secure socket uses the ioqueue to perform + * active socket operations, see \ref PJ_ACTIVESOCK for more detail. + */ + pj_ioqueue_t *ioqueue; + + /** + * Specify secure socket callbacks, see #pj_ssl_sock_cb. + */ + pj_ssl_sock_cb cb; + + /** + * Specify secure socket user data. + */ + void *user_data; + + /** + * Specify security protocol to use, see #pj_ssl_sock_proto. + * + * Default is PJ_SSL_SOCK_PROTO_DEFAULT. + */ + pj_ssl_sock_proto proto; + + /** + * Number of concurrent asynchronous operations that is to be supported + * by the secure socket. This value only affects socket receive and + * accept operations -- the secure socket will issue one or more + * asynchronous read and accept operations based on the value of this + * field. Setting this field to more than one will allow more than one + * incoming data or incoming connections to be processed simultaneously + * on multiprocessor systems, when the ioqueue is polled by more than + * one threads. + * + * The default value is 1. + */ + unsigned async_cnt; + + /** + * The ioqueue concurrency to be forced on the socket when it is + * registered to the ioqueue. See #pj_ioqueue_set_concurrency() for more + * info about ioqueue concurrency. + * + * When this value is -1, the concurrency setting will not be forced for + * this socket, and the socket will inherit the concurrency setting of + * the ioqueue. When this value is zero, the secure socket will disable + * concurrency for the socket. When this value is +1, the secure socket + * will enable concurrency for the socket. + * + * The default value is -1. + */ + int concurrency; + + /** + * If this option is specified, the secure socket will make sure that + * asynchronous send operation with stream oriented socket will only + * call the callback after all data has been sent. This means that the + * secure socket will automatically resend the remaining data until + * all data has been sent. + * + * Please note that when this option is specified, it is possible that + * error is reported after partial data has been sent. Also setting + * this will disable the ioqueue concurrency for the socket. + * + * Default value is 1. + */ + pj_bool_t whole_data; + + /** + * Specify buffer size for delayed send operation. This setting is only + * applied for some platforms that restrict more than one outstanding + * send operation at a time, e.g: Symbian. So delaying/buffering send + * mechanism is used to allow application to send data anytime without + * worrying about current outstanding send operations. + * + * Default value is 0, except for Symbian 8192 bytes. + */ + pj_size_t send_buffer_size; + + /** + * Cipher list string. If empty, then default cipher list of the backend + * will be used. + */ + pj_str_t ciphers; + + /** + * Security negotiation timeout. If this is set to zero (both sec and + * msec), the negotiation doesn't have a timeout. + * + * Default value is zero. + */ + pj_time_val timeout; + + /** + * Specify whether endpoint should verify peer certificate. + * + * Default value is PJ_FALSE. + */ + pj_bool_t verify_peer; + + /** + * When secure socket is acting as server (handles incoming connection), + * it will require the client to provide certificate. + * + * Default value is PJ_FALSE. + */ + pj_bool_t require_client_cert; + + /** + * When secure socket is acting as client (perform outgoing connection) + * and it needs to verify server name (e.g: host or domain name) by + * matching it to the name specified in the server certificate. This + * setting is useful when the server is hosting multiple domains for + * the same listening socket. + * + * Default value is zero/not-set. + */ + pj_str_t servername; + +} pj_ssl_sock_param; + + +/** + * Initialize the secure socket parameters for its creation with + * the default values. + * + * @param param The parameter to be initialized. + */ +PJ_DECL(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param); + + +/** + * Create secure socket instance. + * + * @param pool The pool for allocating secure socket instance. + * @param param The secure socket parameter, see #pj_ssl_sock_param. + * @param p_ssock Pointer to secure socket instance to be created. + * + * @return PJ_SUCCESS when successful. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_create(pj_pool_t *pool, + const pj_ssl_sock_param *param, + pj_ssl_sock_t **p_ssock); + + +/** + * Set secure socket certificate or credentials. Credentials may include + * certificate, private key and trusted Certification Authorities list. + * Normally, server socket must provide certificate (and private key). + * Socket client may also need to provide certificate in case requested + * by the server. + * + * @param ssock The secure socket instance. + * @param pool The pool. + * @param cert The endpoint certificate/credentials, see + * #pj_ssl_cert_t. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_set_certificate( + pj_ssl_sock_t *ssock, + pj_pool_t *pool, + const pj_ssl_cert_t *cert); + + +/** + * Close and destroy the secure socket. + * + * @param ssock The secure socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock); + + +/** + * Associate arbitrary data with the secure socket. Application may + * inspect this data in the callbacks and associate it with higher + * level processing. + * + * @param ssock The secure socket. + * @param user_data The user data to be associated with the secure + * socket. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_set_user_data(pj_ssl_sock_t *ssock, + void *user_data); + +/** + * Retrieve the user data previously associated with this secure + * socket. + * + * @param ssock The secure socket. + * + * @return The user data. + */ +PJ_DECL(void*) pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock); + + +/** + * Retrieve the local address and port used by specified secure socket. + * + * @param ssock The secure socket. + * @param info The info buffer to be set, see #pj_ssl_sock_info. + * + * @return PJ_SUCCESS on successful. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_get_info(pj_ssl_sock_t *ssock, + pj_ssl_sock_info *info); + + +/** + * Starts read operation on this secure socket. This function will create + * \a async_cnt number of buffers (the \a async_cnt parameter was given + * in \a pj_ssl_sock_create() function) where each buffer is \a buff_size + * long. The buffers are allocated from the specified \a pool. Once the + * buffers are created, it then issues \a async_cnt number of asynchronous + * \a recv() operations to the socket and returns back to caller. Incoming + * data on the socket will be reported back to application via the + * \a on_data_read() callback. + * + * Application only needs to call this function once to initiate read + * operations. Further read operations will be done automatically by the + * secure socket when \a on_data_read() callback returns non-zero. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_start_read(pj_ssl_sock_t *ssock, + pj_pool_t *pool, + unsigned buff_size, + pj_uint32_t flags); + +/** + * Same as #pj_ssl_sock_start_read(), except that the application + * supplies the buffers for the read operation so that the acive socket + * does not have to allocate the buffers. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recv(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_start_read2(pj_ssl_sock_t *ssock, + pj_pool_t *pool, + unsigned buff_size, + void *readbuf[], + pj_uint32_t flags); + +/** + * Same as pj_ssl_sock_start_read(), except that this function is used + * only for datagram sockets, and it will trigger \a on_data_recvfrom() + * callback instead. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_start_recvfrom(pj_ssl_sock_t *ssock, + pj_pool_t *pool, + unsigned buff_size, + pj_uint32_t flags); + +/** + * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom() + * operation takes the buffer from the argument rather than creating + * new ones. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate buffers for incoming data. + * @param buff_size The size of each buffer, in bytes. + * @param readbuf Array of packet buffers, each has buff_size size. + * @param flags Flags to be given to pj_ioqueue_recvfrom(). + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_start_recvfrom2(pj_ssl_sock_t *ssock, + pj_pool_t *pool, + unsigned buff_size, + void *readbuf[], + pj_uint32_t flags); + +/** + * Send data using the socket. + * + * @param ssock The secure socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_send(pj_ssl_sock_t *ssock, + pj_ioqueue_op_key_t *send_key, + const void *data, + pj_ssize_t *size, + unsigned flags); + +/** + * Send datagram using the socket. + * + * @param ssock The secure socket. + * @param send_key The operation key to send the data, which is useful + * if application wants to submit multiple pending + * send operations and want to track which exact data + * has been sent in the \a on_data_sent() callback. + * @param data The data to be sent. This data must remain valid + * until the data has been sent. + * @param size The size of the data. + * @param flags Flags to be given to pj_ioqueue_send(). + * @param addr The destination address. + * @param addr_len Length of buffer containing destination address. + * + * @return PJ_SUCCESS if data has been sent immediately, or + * PJ_EPENDING if data cannot be sent immediately. In + * this case the \a on_data_sent() callback will be + * called when data is actually sent. Any other return + * value indicates error condition. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_sendto(pj_ssl_sock_t *ssock, + pj_ioqueue_op_key_t *send_key, + const void *data, + pj_ssize_t *size, + unsigned flags, + const pj_sockaddr_t *addr, + int addr_len); + + +/** + * Starts asynchronous socket accept() operations on this secure socket. + * This function will issue \a async_cnt number of asynchronous \a accept() + * operations to the socket and returns back to caller. Incoming + * connection on the socket will be reported back to application via the + * \a on_accept_complete() callback. + * + * Application only needs to call this function once to initiate accept() + * operations. Further accept() operations will be done automatically by + * the secure socket when \a on_accept_complete() callback returns non-zero. + * + * @param ssock The secure socket. + * @param pool Pool used to allocate some internal data for the + * operation. + * @param localaddr Local address to bind on. + * @param addr_len Length of buffer containing local address. + * + * @return PJ_SUCCESS if the operation has been successful, + * or the appropriate error code on failure. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_start_accept(pj_ssl_sock_t *ssock, + pj_pool_t *pool, + const pj_sockaddr_t *local_addr, + int addr_len); + + +/** + * Starts asynchronous socket connect() operation and SSL/TLS handshaking + * for this socket. Once the connection is done (either successfully or not), + * the \a on_connect_complete() callback will be called. + * + * @param ssock The secure socket. + * @param pool The pool to allocate some internal data for the + * operation. + * @param localaddr Local address. + * @param remaddr Remote address. + * @param addr_len Length of buffer containing above addresses. + * + * @return PJ_SUCCESS if connection can be established immediately + * or PJ_EPENDING if connection cannot be established + * immediately. In this case the \a on_connect_complete() + * callback will be called when connection is complete. + * Any other return value indicates error condition. + */ +PJ_DECL(pj_status_t) pj_ssl_sock_start_connect(pj_ssl_sock_t *ssock, + pj_pool_t *pool, + const pj_sockaddr_t *localaddr, + const pj_sockaddr_t *remaddr, + int addr_len); + + +/** + * @} + */ + +PJ_END_DECL + +#endif /* __PJ_SSL_SOCK_H__ */ diff --git a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c new file mode 100644 index 00000000..bcb249fb --- /dev/null +++ b/pjlib/src/pj/ssl_sock_common.c @@ -0,0 +1,43 @@ +/* $Id$ */ +/* + * Copyright (C) 2009 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 +#include + +/* + * Initialize the SSL socket configuration with the default values. + */ +PJ_DECL(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param) +{ + pj_bzero(param, sizeof(*param)); + + /* Socket config */ + param->sock_af = PJ_AF_INET; + param->sock_type = pj_SOCK_STREAM(); + param->async_cnt = 1; + param->concurrency = -1; + param->whole_data = PJ_TRUE; +#if PJ_SYMBIAN + param->send_buffer_size = 8192; +#endif + + /* Security config */ + param->proto = PJ_SSL_SOCK_PROTO_DEFAULT; +} + + diff --git a/pjlib/src/pj/ssl_sock_symbian.cpp b/pjlib/src/pj/ssl_sock_symbian.cpp new file mode 100644 index 00000000..41d3e159 --- /dev/null +++ b/pjlib/src/pj/ssl_sock_symbian.cpp @@ -0,0 +1,1049 @@ +/* $Id$ */ +/* + * Copyright (C) 2009 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 +#include +#include +#include +#include +#include +#include + +#include "os_symbian.h" +#include +#include +#include + +#define THIS_FILE "ssl_sock_symbian.cpp" + +typedef void (*CPjSSLSocket_cb)(int err, void *key); + +class CPjSSLSocketReader : public CActive +{ +public: + static CPjSSLSocketReader *NewL(CSecureSocket &sock) + { + CPjSSLSocketReader *self = new (ELeave) + CPjSSLSocketReader(sock); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + + ~CPjSSLSocketReader() { + Cancel(); + } + + /* Asynchronous read from the socket. */ + int Read(CPjSSLSocket_cb cb, void *key, TPtr8 &data, TUint flags) + { + PJ_ASSERT_RETURN(!IsActive(), PJ_EBUSY); + + cb_ = cb; + key_ = key; + sock_.RecvOneOrMore(data, iStatus, len_received_); + SetActive(); + + return PJ_EPENDING; + } + +private: + CSecureSocket &sock_; + CPjSSLSocket_cb cb_; + void *key_; + TSockXfrLength len_received_; /* not really useful? */ + + void DoCancel() { + sock_.CancelAll(); + } + + void RunL() { + (*cb_)(iStatus.Int(), key_); + } + + CPjSSLSocketReader(CSecureSocket &sock) : + CActive(0), sock_(sock), cb_(NULL), key_(NULL) + {} + + void ConstructL() { + CActiveScheduler::Add(this); + } +}; + +class CPjSSLSocket : public CActive +{ +public: + enum ssl_state { + SSL_STATE_NULL, + SSL_STATE_CONNECTING, + SSL_STATE_HANDSHAKING, + SSL_STATE_ESTABLISHED + }; + + static CPjSSLSocket *NewL(const TDesC8 &ssl_proto) { + CPjSSLSocket *self = new (ELeave) CPjSSLSocket(); + CleanupStack::PushL(self); + self->ConstructL(ssl_proto); + CleanupStack::Pop(self); + return self; + } + + ~CPjSSLSocket() { + Cancel(); + CleanupSubObjects(); + } + + int Connect(CPjSSLSocket_cb cb, void *key, const TInetAddr &local_addr, + const TInetAddr &rem_addr, + const TDesC8 &servername = TPtrC8(NULL,0)); + int Send(CPjSSLSocket_cb cb, void *key, const TDesC8 &aDesc, TUint flags); + int SendSync(const TDesC8 &aDesc, TUint flags); + + CPjSSLSocketReader* GetReader(); + enum ssl_state GetState() const { return state_; } + const TInetAddr* GetLocalAddr() const { return &local_addr_; } + int GetCipher(TDes8 &cipher) const { + if (securesock_) + return securesock_->CurrentCipherSuite(cipher); + return KErrNotFound; + } + +private: + enum ssl_state state_; + pj_sock_t sock_; + CSecureSocket *securesock_; + bool is_connected_; + CPjSSLSocketReader *reader_; + TBuf<32> ssl_proto_; + TInetAddr rem_addr_; + TPtrC8 servername_; + TInetAddr local_addr_; + TSockXfrLength sent_len_; + + CPjSSLSocket_cb cb_; + void *key_; + + void DoCancel(); + void RunL(); + + CPjSSLSocket() : + CActive(0), state_(SSL_STATE_NULL), sock_(PJ_INVALID_SOCKET), + securesock_(NULL), + is_connected_(false), reader_(NULL), + cb_(NULL), key_(NULL) + {} + + void ConstructL(const TDesC8 &ssl_proto) { + ssl_proto_.Copy(ssl_proto); + CActiveScheduler::Add(this); + } + + void CleanupSubObjects() { + delete reader_; + reader_ = NULL; + if (securesock_) { + securesock_->Close(); + delete securesock_; + securesock_ = NULL; + } + if (sock_ != PJ_INVALID_SOCKET) { + delete (CPjSocket*)sock_; + sock_ = PJ_INVALID_SOCKET; + } + } +}; + +int CPjSSLSocket::Connect(CPjSSLSocket_cb cb, void *key, + const TInetAddr &local_addr, + const TInetAddr &rem_addr, + const TDesC8 &servername) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(state_ == SSL_STATE_NULL, PJ_EINVALIDOP); + + status = pj_sock_socket(rem_addr.Family(), pj_SOCK_STREAM(), 0, &sock_); + if (status != PJ_SUCCESS) + return status; + + RSocket &rSock = ((CPjSocket*)sock_)->Socket(); + + local_addr_ = local_addr; + + if (!local_addr_.IsUnspecified()) { + TInt err = rSock.Bind(local_addr_); + if (err != KErrNone) + return PJ_RETURN_OS_ERROR(err); + } + + cb_ = cb; + key_ = key; + rem_addr_ = rem_addr; + servername_.Set(servername); + state_ = SSL_STATE_CONNECTING; + + rSock.Connect(rem_addr_, iStatus); + SetActive(); + + rSock.LocalName(local_addr_); + + return PJ_EPENDING; +} + +int CPjSSLSocket::Send(CPjSSLSocket_cb cb, void *key, const TDesC8 &aDesc, + TUint flags) +{ + PJ_UNUSED_ARG(flags); + + PJ_ASSERT_RETURN(state_ == SSL_STATE_ESTABLISHED, PJ_EINVALIDOP); + + if (IsActive()) + return PJ_EBUSY; + + cb_ = cb; + key_ = key; + + securesock_->Send(aDesc, iStatus, sent_len_); + SetActive(); + + return PJ_EPENDING; +} + +int CPjSSLSocket::SendSync(const TDesC8 &aDesc, TUint flags) +{ + PJ_UNUSED_ARG(flags); + + PJ_ASSERT_RETURN(state_ == SSL_STATE_ESTABLISHED, PJ_EINVALIDOP); + + TRequestStatus reqStatus; + securesock_->Send(aDesc, reqStatus, sent_len_); + User::WaitForRequest(reqStatus); + + return PJ_RETURN_OS_ERROR(reqStatus.Int()); +} + +CPjSSLSocketReader* CPjSSLSocket::GetReader() +{ + PJ_ASSERT_RETURN(state_ == SSL_STATE_ESTABLISHED, NULL); + + if (reader_) + return reader_; + + TRAPD(err, reader_ = CPjSSLSocketReader::NewL(*securesock_)); + if (err != KErrNone) + return NULL; + + return reader_; +} + +void CPjSSLSocket::DoCancel() +{ + /* Operation to be cancelled depends on current state */ + switch (state_) { + case SSL_STATE_CONNECTING: + { + RSocket &rSock = ((CPjSocket*)sock_)->Socket(); + rSock.CancelConnect(); + + CleanupSubObjects(); + + state_ = SSL_STATE_NULL; + } + break; + case SSL_STATE_HANDSHAKING: + { + securesock_->CancelHandshake(); + securesock_->Close(); + + CleanupSubObjects(); + + state_ = SSL_STATE_NULL; + } + break; + case SSL_STATE_ESTABLISHED: + securesock_->CancelSend(); + break; + default: + break; + } +} + +void CPjSSLSocket::RunL() +{ + switch (state_) { + case SSL_STATE_CONNECTING: + if (iStatus != KErrNone) { + CleanupSubObjects(); + state_ = SSL_STATE_NULL; + /* Dispatch connect failure notification */ + if (cb_) (*cb_)(iStatus.Int(), key_); + } else { + RSocket &rSock = ((CPjSocket*)sock_)->Socket(); + + /* Get local addr */ + rSock.LocalName(local_addr_); + + /* Prepare and start handshake */ + securesock_ = CSecureSocket::NewL(rSock, ssl_proto_); + securesock_->SetDialogMode(EDialogModeAttended); + if (servername_.Length() > 0) + securesock_->SetOpt(KSoSSLDomainName, KSolInetSSL, + servername_); + securesock_->FlushSessionCache(); + securesock_->StartClientHandshake(iStatus); + SetActive(); + state_ = SSL_STATE_HANDSHAKING; + } + break; + case SSL_STATE_HANDSHAKING: + if (iStatus == KErrNone) { + state_ = SSL_STATE_ESTABLISHED; + } else { + state_ = SSL_STATE_NULL; + CleanupSubObjects(); + } + /* Dispatch connect status notification */ + if (cb_) (*cb_)(iStatus.Int(), key_); + break; + case SSL_STATE_ESTABLISHED: + /* Dispatch data sent notification */ + if (cb_) (*cb_)(iStatus.Int(), key_); + break; + default: + pj_assert(0); + break; + } +} + +typedef void (*CPjTimer_cb)(void *user_data); + +class CPjTimer : public CActive +{ +public: + CPjTimer(const pj_time_val *delay, CPjTimer_cb cb, void *user_data) : + CActive(0), cb_(cb), user_data_(user_data) + { + CActiveScheduler::Add(this); + + rtimer_.CreateLocal(); + pj_int32_t interval = PJ_TIME_VAL_MSEC(*delay) * 1000; + if (interval < 0) { + interval = 0; + } + rtimer_.After(iStatus, interval); + SetActive(); + } + + ~CPjTimer() { Cancel(); } + +private: + RTimer rtimer_; + CPjTimer_cb cb_; + void *user_data_; + + void RunL() { if (cb_) (*cb_)(user_data_); } + void DoCancel() { rtimer_.Cancel(); } +}; + +/* + * Structure of recv/read state. + */ +typedef struct read_state_t { + TPtr8 *read_buf; + TPtr8 *orig_buf; + pj_uint32_t flags; +} read_state_t; + +/* + * Structure of send/write data. + */ +typedef struct write_data_t { + pj_size_t len; + pj_ioqueue_op_key_t *key; + pj_size_t data_len; + char data[1]; +} write_data_t; + +/* + * Structure of send/write state. + */ +typedef struct write_state_t { + char *buf; + pj_size_t max_len; + char *start; + pj_size_t len; + write_data_t *current_data; + TPtrC8 send_ptr; +} write_state_t; + +/* + * Secure socket structure definition. + */ +struct pj_ssl_sock_t +{ + pj_ssl_sock_cb cb; + void *user_data; + + pj_bool_t established; + write_state_t write_state; + read_state_t read_state; + CPjTimer *connect_timer; + + CPjSSLSocket *sock; + int sock_af; + int sock_type; + pj_sockaddr local_addr; + pj_sockaddr rem_addr; + + pj_ssl_sock_proto proto; + pj_time_val timeout; + pj_str_t ciphers; + pj_str_t servername; +}; + + +/* + * Create SSL socket instance. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_create (pj_pool_t *pool, + const pj_ssl_sock_param *param, + pj_ssl_sock_t **p_ssock) +{ + pj_ssl_sock_t *ssock; + + PJ_ASSERT_RETURN(param->async_cnt == 1, PJ_EINVAL); + PJ_ASSERT_RETURN(pool && param && p_ssock, PJ_EINVAL); + + /* Allocate secure socket */ + ssock = PJ_POOL_ZALLOC_T(pool, pj_ssl_sock_t); + + /* Allocate write buffer */ + ssock->write_state.buf = (char*)pj_pool_alloc(pool, + param->send_buffer_size); + ssock->write_state.max_len = param->send_buffer_size; + ssock->write_state.start = ssock->write_state.buf; + + /* Init secure socket */ + ssock->sock_af = param->sock_af; + ssock->sock_type = param->sock_type; + ssock->cb = param->cb; + ssock->user_data = param->user_data; + pj_strdup_with_null(pool, &ssock->ciphers, ¶m->ciphers); + pj_strdup_with_null(pool, &ssock->servername, ¶m->servername); + + /* Finally */ + *p_ssock = ssock; + + return PJ_SUCCESS; +} + +/* + * Set SSL socket credential. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_set_certificate( + pj_ssl_sock_t *ssock, + pj_pool_t *pool, + const pj_ssl_cert_t *cert) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(cert); + return PJ_ENOTSUP; +} + +/* + * Close the SSL socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock) +{ + PJ_ASSERT_RETURN(ssock, PJ_EINVAL); + + delete ssock->connect_timer; + ssock->connect_timer = NULL; + + delete ssock->sock; + ssock->sock = NULL; + + delete ssock->read_state.read_buf; + delete ssock->read_state.orig_buf; + ssock->read_state.read_buf = NULL; + ssock->read_state.orig_buf = NULL; + + return PJ_SUCCESS; +} + + +/* + * Associate arbitrary data with the SSL socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_set_user_data (pj_ssl_sock_t *ssock, + void *user_data) +{ + PJ_ASSERT_RETURN(ssock, PJ_EINVAL); + + ssock->user_data = user_data; + + return PJ_SUCCESS; +} + + +/* + * Retrieve the user data previously associated with this SSL + * socket. + */ +PJ_DEF(void*) pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock) +{ + PJ_ASSERT_RETURN(ssock, NULL); + + return ssock->user_data; +} + + +/* + * Retrieve the local address and port used by specified SSL socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_get_info (pj_ssl_sock_t *ssock, + pj_ssl_sock_info *info) +{ + const char *cipher_names[0x1B] = { + "TLS_RSA_WITH_NULL_MD5", + "TLS_RSA_WITH_NULL_SHA", + "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "TLS_RSA_WITH_RC4_128_MD5", + "TLS_RSA_WITH_RC4_128_SHA", + "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "TLS_RSA_WITH_IDEA_CBC_SHA", + "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "TLS_RSA_WITH_DES_CBC_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", + "TLS_DH_DSS_WITH_DES_CBC_SHA", + "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", + "TLS_DH_RSA_WITH_DES_CBC_SHA", + "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "TLS_DHE_DSS_WITH_DES_CBC_SHA", + "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "TLS_DHE_RSA_WITH_DES_CBC_SHA", + "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "TLS_DH_anon_WITH_RC4_128_MD5", + "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "TLS_DH_anon_WITH_DES_CBC_SHA", + "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA" + }; + + PJ_ASSERT_RETURN(ssock && info, PJ_EINVAL); + + pj_bzero(info, sizeof(*info)); + + info->established = ssock->established; + + /* Local address */ + if (ssock->sock) { + const TInetAddr* local_addr_ = ssock->sock->GetLocalAddr(); + int addrlen = sizeof(pj_sockaddr); + pj_status_t status; + + status = PjSymbianOS::Addr2pj(*local_addr_, info->local_addr, &addrlen); + if (status != PJ_SUCCESS) + return status; + } else { + pj_sockaddr_cp(&info->local_addr, &ssock->local_addr); + } + + /* Remote address */ + pj_sockaddr_cp((pj_sockaddr_t*)&info->remote_addr, + (pj_sockaddr_t*)&ssock->rem_addr); + + /* Cipher suite */ + if (info->established) { + TBuf8<8> cipher; + if (ssock->sock->GetCipher(cipher) == KErrNone) { + TLex8 lex(cipher); + TUint cipher_code = cipher[1]; + if (cipher_code>=1 && cipher_code<=0x1B) + info->cipher = pj_str((char*)cipher_names[cipher_code-1]); + } + } + + /* Protocol */ + info->proto = ssock->proto; + + return PJ_SUCCESS; +} + + +/* + * Starts read operation on this SSL socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_start_read (pj_ssl_sock_t *ssock, + pj_pool_t *pool, + unsigned buff_size, + pj_uint32_t flags) +{ + PJ_ASSERT_RETURN(ssock && pool && buff_size, PJ_EINVAL); + PJ_ASSERT_RETURN(ssock->established, PJ_EINVALIDOP); + + /* Reading is already started */ + if (ssock->read_state.orig_buf) { + return PJ_SUCCESS; + } + + void *readbuf[1]; + readbuf[0] = pj_pool_alloc(pool, buff_size); + return pj_ssl_sock_start_read2(ssock, pool, buff_size, readbuf, flags); +} + +static void read_cb(int err, void *key) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)key; + pj_status_t status; + + status = (err == KErrNone)? PJ_SUCCESS : PJ_RETURN_OS_ERROR(err); + + /* Check connection status */ + if (err == KErrEof || !PjSymbianOS::Instance()->IsConnectionUp() || + !ssock->established) + { + status = PJ_EEOF; + } + + /* Notify data arrival */ + if (ssock->cb.on_data_read) { + pj_size_t remainder = 0; + char *data = (char*)ssock->read_state.orig_buf->Ptr(); + pj_size_t data_len = ssock->read_state.read_buf->Length() + + ssock->read_state.read_buf->Ptr() - + ssock->read_state.orig_buf->Ptr(); + + if (data_len > 0) { + /* Notify received data */ + pj_bool_t ret = (*ssock->cb.on_data_read)(ssock, data, data_len, + status, &remainder); + if (!ret) { + /* We've been destroyed */ + return; + } + + /* Calculate available data for next READ operation */ + if (remainder > 0) { + pj_size_t data_maxlen = ssock->read_state.orig_buf->MaxLength(); + + /* There is some data left unconsumed by application, we give + * smaller buffer for next READ operation. + */ + ssock->read_state.read_buf->Set((TUint8*)data+remainder, 0, + data_maxlen - remainder); + } else { + /* Give all buffer for next READ operation. + */ + ssock->read_state.read_buf->Set(*ssock->read_state.orig_buf); + } + } + } + + if (status == PJ_SUCCESS) { + /* Perform the "next" READ operation */ + CPjSSLSocketReader *reader = ssock->sock->GetReader(); + ssock->read_state.read_buf->SetLength(0); + status = reader->Read(&read_cb, ssock, *ssock->read_state.read_buf, + ssock->read_state.flags); + if (status != PJ_EPENDING) { + /* Notify error */ + (*ssock->cb.on_data_read)(ssock, NULL, 0, status, NULL); + } + } + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + /* Connection closed or something goes wrong */ + delete ssock->read_state.read_buf; + delete ssock->read_state.orig_buf; + ssock->read_state.read_buf = NULL; + ssock->read_state.orig_buf = NULL; + ssock->established = PJ_FALSE; + } +} + +/* + * Same as #pj_ssl_sock_start_read(), except that the application + * supplies the buffers for the read operation so that the acive socket + * does not have to allocate the buffers. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_start_read2 (pj_ssl_sock_t *ssock, + pj_pool_t *pool, + unsigned buff_size, + void *readbuf[], + pj_uint32_t flags) +{ + PJ_ASSERT_RETURN(ssock && buff_size && readbuf, PJ_EINVAL); + PJ_ASSERT_RETURN(ssock->established, PJ_EINVALIDOP); + + /* Return failure if access point is marked as down by app. */ + PJ_SYMBIAN_CHECK_CONNECTION(); + + /* Reading is already started */ + if (ssock->read_state.orig_buf) { + return PJ_SUCCESS; + } + + PJ_UNUSED_ARG(pool); + + /* Get reader instance */ + CPjSSLSocketReader *reader = ssock->sock->GetReader(); + if (!reader) + return PJ_ENOMEM; + + /* We manage two buffer pointers here: + * 1. orig_buf keeps the orginal buffer address (and its max length). + * 2. read_buf provides buffer for READ operation, mind that there may be + * some remainder data left by application. + */ + ssock->read_state.read_buf = new TPtr8((TUint8*)readbuf[0], 0, buff_size); + ssock->read_state.orig_buf = new TPtr8((TUint8*)readbuf[0], 0, buff_size); + ssock->read_state.flags = flags; + + pj_status_t status; + status = reader->Read(&read_cb, ssock, *ssock->read_state.read_buf, + ssock->read_state.flags); + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + delete ssock->read_state.read_buf; + delete ssock->read_state.orig_buf; + ssock->read_state.read_buf = NULL; + ssock->read_state.orig_buf = NULL; + + return status; + } + + return PJ_SUCCESS; +} + +/* + * Same as pj_ssl_sock_start_read(), except that this function is used + * only for datagram sockets, and it will trigger \a on_data_recvfrom() + * callback instead. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom (pj_ssl_sock_t *ssock, + pj_pool_t *pool, + unsigned buff_size, + pj_uint32_t flags) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(buff_size); + PJ_UNUSED_ARG(flags); + return PJ_ENOTSUP; +} + +/* + * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom() + * operation takes the buffer from the argument rather than creating + * new ones. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom2 (pj_ssl_sock_t *ssock, + pj_pool_t *pool, + unsigned buff_size, + void *readbuf[], + pj_uint32_t flags) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(buff_size); + PJ_UNUSED_ARG(readbuf); + PJ_UNUSED_ARG(flags); + return PJ_ENOTSUP; +} + +static void send_cb(int err, void *key) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)key; + write_state_t *st = &ssock->write_state; + + /* Check connection status */ + if (err != KErrNone || !PjSymbianOS::Instance()->IsConnectionUp() || + !ssock->established) + { + ssock->established = PJ_FALSE; + return; + } + + /* Remove sent data from buffer */ + st->start += st->current_data->len; + st->len -= st->current_data->len; + + /* Reset current outstanding send */ + st->current_data = NULL; + + /* Let's check if there is pending data to send */ + if (st->len) { + write_data_t *wdata = (write_data_t*)st->start; + pj_status_t status; + + st->send_ptr.Set((TUint8*)wdata->data, (TInt)wdata->data_len); + st->current_data = wdata; + status = ssock->sock->Send(&send_cb, ssock, st->send_ptr, 0); + if (status != PJ_EPENDING) { + ssock->established = PJ_FALSE; + st->len = 0; + return; + } + } +} + +/* + * Send data using the socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_send (pj_ssl_sock_t *ssock, + pj_ioqueue_op_key_t *send_key, + const void *data, + pj_ssize_t *size, + unsigned flags) +{ + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(ssock && data && size, PJ_EINVAL); + PJ_ASSERT_RETURN(ssock->write_state.max_len == 0 || + ssock->write_state.max_len >= (pj_size_t)*size, + PJ_ETOOSMALL); + + /* Check connection status */ + if (!PjSymbianOS::Instance()->IsConnectionUp() || !ssock->established) + { + ssock->established = PJ_FALSE; + return PJ_ECANCELLED; + } + + write_state_t *st = &ssock->write_state; + + /* Synchronous mode */ + if (st->max_len == 0) { + st->send_ptr.Set((TUint8*)data, (TInt)*size); + return ssock->sock->SendSync(st->send_ptr, flags); + } + + /* CSecureSocket only allows one outstanding send operation, so + * we use buffering mechanism to allow application to perform send + * operations at any time. + */ + + pj_size_t avail_len = st->max_len - st->len; + pj_size_t needed_len = *size + sizeof(write_data_t) - 1; + + /* Align needed_len to be multiplication of 4 */ + needed_len = ((needed_len + 3) >> 2) << 2; + + /* Block until there is buffer slot available! */ + while (needed_len >= avail_len) { + pj_symbianos_poll(-1, -1); + avail_len = st->max_len - st->len; + } + + /* Ok, make sure the new data will not get wrapped */ + if (st->start + st->len + needed_len > st->buf + st->max_len) { + /* Align buffer left */ + pj_memmove(st->buf, st->start, st->len); + st->start = st->buf; + } + + /* Push back the send data into the buffer */ + write_data_t *wdata = (write_data_t*)(st->start + st->len); + + wdata->len = needed_len; + wdata->key = send_key; + wdata->data_len = (pj_size_t)*size; + pj_memcpy(wdata->data, data, *size); + st->len += needed_len; + + /* If no outstanding send, send it */ + if (st->current_data == NULL) { + pj_status_t status; + + wdata = (write_data_t*)st->start; + st->current_data = wdata; + st->send_ptr.Set((TUint8*)wdata->data, (TInt)wdata->data_len); + status = ssock->sock->Send(&send_cb, ssock, st->send_ptr, flags); + + if (status != PJ_EPENDING) { + *size = -status; + return status; + } + } + + return PJ_SUCCESS; +} + +/* + * Send datagram using the socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_sendto (pj_ssl_sock_t *ssock, + pj_ioqueue_op_key_t *send_key, + const void *data, + pj_ssize_t *size, + unsigned flags, + const pj_sockaddr_t *addr, + int addr_len) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(send_key); + PJ_UNUSED_ARG(data); + PJ_UNUSED_ARG(size); + PJ_UNUSED_ARG(flags); + PJ_UNUSED_ARG(addr); + PJ_UNUSED_ARG(addr_len); + return PJ_ENOTSUP; +} + +/* + * Starts asynchronous socket accept() operations on this SSL socket. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_start_accept (pj_ssl_sock_t *ssock, + pj_pool_t *pool, + const pj_sockaddr_t *local_addr, + int addr_len) +{ + PJ_UNUSED_ARG(ssock); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(local_addr); + PJ_UNUSED_ARG(addr_len); + + return PJ_ENOTSUP; +} + +static void connect_cb(int err, void *key) +{ + pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)key; + pj_status_t status; + + if (ssock->connect_timer) { + delete ssock->connect_timer; + ssock->connect_timer = NULL; + } + + status = (err == KErrNone)? PJ_SUCCESS : PJ_RETURN_OS_ERROR(err); + if (status == PJ_SUCCESS) { + ssock->established = PJ_TRUE; + } else { + delete ssock->sock; + ssock->sock = NULL; + } + + if (ssock->cb.on_connect_complete) { + pj_bool_t ret = (*ssock->cb.on_connect_complete)(ssock, status); + if (!ret) { + /* We've been destroyed */ + return; + } + } +} + +static void connect_timer_cb(void *key) +{ + connect_cb(KErrTimedOut, key); +} + +/* + * Starts asynchronous socket connect() operation and SSL/TLS handshaking + * for this socket. Once the connection is done (either successfully or not), + * the \a on_connect_complete() callback will be called. + */ +PJ_DEF(pj_status_t) pj_ssl_sock_start_connect (pj_ssl_sock_t *ssock, + pj_pool_t *pool, + const pj_sockaddr_t *localaddr, + const pj_sockaddr_t *remaddr, + int addr_len) +{ + CPjSSLSocket *sock = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len, + PJ_EINVAL); + + /* Check connection status */ + PJ_SYMBIAN_CHECK_CONNECTION(); + + if (ssock->sock != NULL) { + CPjSSLSocket::ssl_state state = ssock->sock->GetState(); + switch (state) { + case CPjSSLSocket::SSL_STATE_ESTABLISHED: + return PJ_SUCCESS; + default: + return PJ_EPENDING; + } + } + + /* Set SSL protocol */ + TPtrC8 proto; + + if (ssock->proto == PJ_SSL_SOCK_PROTO_DEFAULT) + ssock->proto = PJ_SSL_SOCK_PROTO_TLS1; + + /* CSecureSocket only support TLS1.0 and SSL3.0 */ + switch(ssock->proto) { + case PJ_SSL_SOCK_PROTO_TLS1: + proto.Set((const TUint8*)"TLS1.0", 6); + break; + case PJ_SSL_SOCK_PROTO_SSL3: + proto.Set((const TUint8*)"SSL3.0", 6); + break; + default: + return PJ_ENOTSUP; + } + + /* Prepare addresses */ + TInetAddr localaddr_, remaddr_; + status = PjSymbianOS::pj2Addr(*(pj_sockaddr*)localaddr, addr_len, + localaddr_); + if (status != PJ_SUCCESS) + return status; + + status = PjSymbianOS::pj2Addr(*(pj_sockaddr*)remaddr, addr_len, + remaddr_); + if (status != PJ_SUCCESS) + return status; + + pj_sockaddr_cp((pj_sockaddr_t*)&ssock->rem_addr, remaddr); + + /* Init SSL engine */ + TRAPD(err, sock = CPjSSLSocket::NewL(proto)); + if (err != KErrNone) + return PJ_ENOMEM; + + if (ssock->timeout.sec != 0 || ssock->timeout.msec != 0) { + ssock->connect_timer = new CPjTimer(&ssock->timeout, + &connect_timer_cb, ssock); + } + + /* Convert server name to Symbian descriptor */ + TPtrC8 servername_((TUint8*)ssock->servername.ptr, + ssock->servername.slen); + + /* Try to connect */ + status = sock->Connect(&connect_cb, ssock, localaddr_, remaddr_, + servername_); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + delete sock; + return status; + } + + ssock->sock = sock; + return status; +} + diff --git a/pjsip-apps/src/symbian_ua/ua.cpp b/pjsip-apps/src/symbian_ua/ua.cpp index 2b36b709..d4a5c3ec 100644 --- a/pjsip-apps/src/symbian_ua/ua.cpp +++ b/pjsip-apps/src/symbian_ua/ua.cpp @@ -35,7 +35,7 @@ // // Destination URI (to make call, or to subscribe presence) // -#define SIP_DST_URI "sip:100@pjsip.lab" +#define SIP_DST_URI "" // // Account @@ -52,9 +52,11 @@ //#define SIP_PROXY "" // -// Set to 1 if TCP is desired (experimental) +// SIP transports // -#define ENABLE_SIP_TCP 0 +#define ENABLE_SIP_UDP 1 +#define ENABLE_SIP_TCP 0 // experimental +#define ENABLE_SIP_TLS 0 // experimental // // Configure nameserver if DNS SRV is to be used with both SIP @@ -373,11 +375,13 @@ static pj_status_t app_startup() pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), &codec_id, PJMEDIA_CODEC_PRIO_DISABLED); } + - /* Add UDP transport. */ pjsua_transport_config tcfg; pjsua_transport_id tid; +#if ENABLE_SIP_UDP + /* Add UDP transport. */ pjsua_transport_config_default(&tcfg); tcfg.port = SIP_PORT; status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &tcfg, &tid); @@ -386,9 +390,10 @@ static pj_status_t app_startup() pjsua_destroy(); return status; } - - /* Add TCP transport */ +#endif + #if ENABLE_SIP_TCP + /* Add TCP transport */ pjsua_transport_config_default(&tcfg); tcfg.port = SIP_PORT; status = pjsua_transport_create(PJSIP_TRANSPORT_TCP, &tcfg, &tid); @@ -399,6 +404,18 @@ static pj_status_t app_startup() } #endif +#if ENABLE_SIP_TLS + /* Add TLS transport */ + pjsua_transport_config_default(&tcfg); + tcfg.port = SIP_PORT + 1; + status = pjsua_transport_create(PJSIP_TRANSPORT_TLS, &tcfg, &tid); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating TLS transport", status); + pjsua_destroy(); + return status; + } +#endif + /* Add account for the transport */ pjsua_acc_add_local(tid, PJ_TRUE, &g_acc_id); diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c new file mode 100644 index 00000000..2d58f751 --- /dev/null +++ b/pjsip/src/pjsip/sip_transport_tls.c @@ -0,0 +1,1317 @@ +/* $Id$ */ +/* + * Copyright (C) 2009 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + 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_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; + + /* 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_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, + 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); +} + + + +/**************************************************************************** + * 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.ciphers = listener->tls_setting.ciphers; + ssock_param.ioqueue = pjsip_endpt_get_ioqueue(endpt); + ssock_param.require_client_cert = listener->tls_setting.require_client_cert; + ssock_param.servername = listener->tls_setting.server_name; + ssock_param.timeout = listener->tls_setting.timeout; + ssock_param.user_data = listener; + ssock_param.verify_peer = listener->tls_setting.verify_client; + + 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); + } + + /* 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_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; + } + + PJ_LOG(4,(listener->factory.obj_name, + "SIP TLS listener is ready%s at %.*s:%d", + (has_listener?" for incoming connections":""), + (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; + + 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, + struct tls_transport **p_tls) +{ + struct tls_transport *tls; + const pj_str_t ka_pkt = PJSIP_TCP_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; + 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; + } + + 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); + 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; + + 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_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_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); + + /* 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.ciphers = listener->tls_setting.ciphers; + ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt); + PJ_TODO(SET_PROPER_SERVERNAME_BASED_ON_TARGET); + ssock_param.servername = listener->tls_setting.server_name; + ssock_param.timeout = listener->tls_setting.timeout; + ssock_param.user_data = NULL; /* pending, must be set later */ + ssock_param.verify_peer = listener->tls_setting.verify_server; + + 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; + + /* 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, &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 (again) 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_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tls->base.local_addr; + + pj_assert(pj_sockaddr_get_len((pj_sockaddr_t*)&info.local_addr) <= + sizeof(local_addr)); + pj_sockaddr_cp((pj_sockaddr_t*)&local_addr, (pj_sockaddr_t*)&info.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); + } + } + + 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; + char addr[PJ_INET6_ADDRSTRLEN+10]; + 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)); + + /* + * 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, &tls); + if (status == PJ_SUCCESS) { + status = tls_start_read(tls); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(tls->base.obj_name, "New transport cancelled")); + tls_destroy(&tls->base, status); + } else { + /* Start keep-alive timer */ + if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = {PJSIP_TCP_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; + if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; + pjsip_transport_shutdown(&tls->base); + + 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); + if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; + pjsip_transport_shutdown(&tls->base); + } + } + } + + 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")); + + /* 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_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 info; + + tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock); + + /* Mark that pending connect() operation has completed. */ + tls->has_pending_connect = PJ_FALSE; + + /* 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); + } + + /* 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_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)); + + + /* 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?). + */ + + /* Retrieve the bound address */ + status = pj_ssl_sock_get_info(tls->ssock, &info); + if (status == PJ_SUCCESS) { + pj_sockaddr_in addr; + pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tls->base.local_addr; + + pj_sockaddr_cp((pj_sockaddr_t*)&addr, (pj_sockaddr_t*)&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); + } + } + + /* Start pending read */ + status = tls_start_read(tls); + if (status != PJ_SUCCESS) { + /* 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_FALSE; + } + + /* Flush all pending send operations */ + tls_flush_pending_tx(tls); + + /* Start keep-alive timer */ + if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = { PJSIP_TCP_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; +} + +/* 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_TCP_KEEP_ALIVE_INTERVAL) { + /* There has been activity, so don't send keep-alive */ + delay.sec = PJSIP_TCP_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); + pjsip_transport_shutdown(&tls->base); + return; + } + + /* Register next keep-alive */ + delay.sec = PJSIP_TCP_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 */ -- cgit v1.2.3