summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.symbian/pjlib.mmp2
-rw-r--r--build.symbian/pjsip.mmp1
-rw-r--r--build.symbian/symbian_ua.mmp2
-rw-r--r--pjlib/include/pj/ssl_sock.h652
-rw-r--r--pjlib/src/pj/ssl_sock_common.c43
-rw-r--r--pjlib/src/pj/ssl_sock_symbian.cpp1049
-rw-r--r--pjsip-apps/src/symbian_ua/ua.cpp29
-rw-r--r--pjsip/src/pjsip/sip_transport_tls.c1317
8 files changed, 3088 insertions, 7 deletions
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 <pj/ioqueue.h>
+#include <pj/sock.h>
+
+
+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 <pj/ssl_sock.h>
+#include <pj/string.h>
+
+/*
+ * 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 <pj/ssl_sock.h>
+#include <pj/compat/socket.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/pool.h>
+#include <pj/sock.h>
+#include <pj/string.h>
+
+#include "os_symbian.h"
+#include <securesocket.h>
+#include <x509cert.h>
+#include <e32des8.h>
+
+#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, &param->ciphers);
+ pj_strdup_with_null(pool, &ssock->servername, &param->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 "<sip:100@pjsip.lab>"
//
// Account
@@ -52,9 +52,11 @@
//#define SIP_PROXY "<sip:192.168.0.8;lr>"
//
-// 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 <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/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;
+ 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 */