Ticket #1032:
 - Initial version of server domain name verification:
   - Updated SSL certificate info, especially identities info
   - Updated verification mechanism as in the specifications in ticket desc.
   - Added server domain name info in pjsip_tx_data.
   - Added alternative API for acquiring transport and creating transport of transport factory to include pjsip_tx_data param.
   - Server identity match criteria:
     - full host name match
     - wild card not accepted
     - if identity is URI, it must be SIP/SIPS URI
 - Initial version of transport state notifications:
   - Added new API to set transport state callback in PJSIP and PJSUA.
   - Defined states: connected/disconnected, accepted/rejected, verification errors.
 - Minors: 
   - Updated SSL socket test: dump verification result, test of requiring client cert, and few minors.
   - Updated test cert to include subjectAltName extensions.
   - Added SSL certificate dump function.
   - Updated max number of socket async operations in Symbian sample apps (RSocketServ::Connect()) to 32 (was default 8).




git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@3106 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/build.symbian/pjlib.mmp b/build.symbian/pjlib.mmp
index 65ffcc5..3765e0f 100644
--- a/build.symbian/pjlib.mmp
+++ b/build.symbian/pjlib.mmp
@@ -49,6 +49,8 @@
 SOURCE		pool_caching.c
 SOURCE		rand.c
 SOURCE		rbtree.c
+SOURCE		ssl_sock_common.c
+SOURCE		ssl_sock_dump.c
 SOURCE		sock_common.c
 SOURCE		sock_qos_common.c
 SOURCE		types.c
@@ -72,7 +74,6 @@
 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
diff --git a/build.symbian/pjlib_test.mmp b/build.symbian/pjlib_test.mmp
index e48a332..65b654c 100644
--- a/build.symbian/pjlib_test.mmp
+++ b/build.symbian/pjlib_test.mmp
@@ -69,7 +69,9 @@
 SYSTEMINCLUDE	\epoc32\include
 SYSTEMINCLUDE	\epoc32\include\libc
 
-LIBRARY		esock.lib insock.lib charconv.lib euser.lib estlib.lib securesocket.lib 
+LIBRARY		esock.lib insock.lib charconv.lib euser.lib estlib.lib 
+LIBRARY		securesocket.lib x509.lib crypto.lib x500.lib 
+ 
 #ifdef WINSCW
 STATICLIBRARY   eexe.lib ecrt0.lib
 #endif
diff --git a/build.symbian/symbian_ua.mmp b/build.symbian/symbian_ua.mmp
index 9514cde..fc5c8ae 100644
--- a/build.symbian/symbian_ua.mmp
+++ b/build.symbian/symbian_ua.mmp
@@ -71,7 +71,8 @@
 	STATICLIBRARY   eexe.lib ecrt0.lib
 #endif
 
-LIBRARY			esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib securesocket.lib 
+LIBRARY			esock.lib insock.lib charconv.lib euser.lib estlib.lib commdb.lib apengine.lib 
+LIBRARY			securesocket.lib x509.lib crypto.lib x500.lib 
 
 // The default 8KB seems to be insufficient with all bells and
 // whistles turned on
diff --git a/pjlib/build/Makefile b/pjlib/build/Makefile
index a3482e0..1e0e88f 100644
--- a/pjlib/build/Makefile
+++ b/pjlib/build/Makefile
@@ -26,7 +26,8 @@
 	guid.o hash.o ip_helper_generic.o list.o lock.o log.o os_time_common.o \
 	pool.o pool_buf.o pool_caching.o pool_dbg.o rand.o \
 	rbtree.o sock_common.o sock_qos_common.o sock_qos_bsd.o \
-	ssl_sock_common.o ssl_sock_ossl.o string.o timer.o types.o
+	ssl_sock_common.o ssl_sock_ossl.o ssl_sock_dump.o \
+	string.o timer.o types.o
 export PJLIB_CFLAGS += $(_CFLAGS)
 
 ###############################################################################
diff --git a/pjlib/build/cacert.pem b/pjlib/build/cacert.pem
index 0a2ee45..cfce092 100644
--- a/pjlib/build/cacert.pem
+++ b/pjlib/build/cacert.pem
@@ -1,21 +1,14 @@
 -----BEGIN CERTIFICATE-----
-MIIDXzCCAkegAwIBAgIJAPqAwYU5OQLXMA0GCSqGSIb3DQEBBQUAMEYxCzAJBgNV
-BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMQ4wDAYDVQQKDAVwanNpcDESMBAG
-A1UEAwwJcGpzaXAubGFiMB4XDTA5MTAyMjE3MTczN1oXDTE5MTAyMDE3MTczN1ow
-RjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoMBXBq
-c2lwMRIwEAYDVQQDDAlwanNpcC5sYWIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
-ggEKAoIBAQDWWvnL+oSC0Q6OwGLt2YuhXTEzIVv3B+SGQ7tajB6H3WXVeq+1NmU9
-Yzca33g4FRrU7n4smYmKLzm1aniBhNmJjA+t+gbyizKnLMaCLoG52tUoULcANGKU
-aGwlmvZFugDn2eVg6UfUfRzEGbV3q3a/PzSsOEPwsMeF3YMQJPhkoyPQLtWgUXgP
-89Nyq3XjGGtw/qmUgQjE8a6/P0yXc+myI0hmApmZ9nB3YmlB5W3q6WoU2gGhLXf4
-12rH/LgdnPhM4ijS554Kv9EcUDdQTTrm6bYg66tj+qTet7DolUOlTZ3vKpuCK3tt
-eK9CbNPVzsMsB3yCALSLzQ347pIwfLaJAgMBAAGjUDBOMB0GA1UdDgQWBBRE/VNp
-kNQmLEXKQ+NM4bOVj95zYTAfBgNVHSMEGDAWgBRE/VNpkNQmLEXKQ+NM4bOVj95z
-YTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCj/gzJKTOZDEBD+zr7
-lvbVctiYE9o8ObxZQsnl/6zI2V9H/2yc1sqQyjBupzw6c37ehvk30yIyUfD3ts87
-2xaJ5VgtgUI3FI5DQ+ASyQXmDawUEmXIGqHb2gDrXBQLd6uMpvNDNW+7TouuniyA
-F12JUCNITeXaVJ0c8d4A9J9DlszBfYUzI45yIQu1gbpnpH74Sp/hG77EMxrRau+x
-EFFmV7gAmkCgOBnXm8jTKqNre/GfLfO7w2xoLsubSLnK46U3iLGBIJJRVGu3UQuN
-k1o7CiIKf0SSWj1bQI99ipTj8obBKRqj1nSbgKF/U6FIfd8DGcVvbJCSAG2czzyA
-5tdA
+MIICNDCCAZ2gAwIBAgIJAIa9mZggMk2WMA0GCSqGSIb3DQEBBAUAMDMxEjAQBgNV
+BAMTCXBqc2lwLmxhYjEdMBsGCSqGSIb3DQEJARYOdGVzdEBwanNpcC5sYWIwHhcN
+MTAwMjEwMDkwNTQ0WhcNMjAwMjA4MDkwNTQ0WjAzMRIwEAYDVQQDEwlwanNpcC5s
+YWIxHTAbBgkqhkiG9w0BCQEWDnRlc3RAcGpzaXAubGFiMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQDI9T0Pf+1gKOTOAGEpZ481Q6xfm5vz6n1+6udxzQtfPKlQ
+rPD5x5im2u3tmy6ABxZeY5tCdeikBPiGlc5bRIRng6KM8kidkg3gEhwhRUxHCMWb
+mBpkz7rFERf/pWAOCqYCiy1RT8QrK+XOFoFdJhdF85UPDEUw+pHEsYetTDs9RQID
+AQABo1AwTjBMBgNVHREERTBDgglwanNpcC5sYWKCDXNpcC5wanNpcC5sYWKBDnRl
+c3RAcGpzaXAubGFihhFzaXA6c2lwLnBqc2lwLmxhYocEfwAAATANBgkqhkiG9w0B
+AQQFAAOBgQCLPl/WF1QvjT36kVLH0nxfHwDOJuAzlh6nv9rYBviOLw9FTEMgW6hA
+oG55YSdVjTnMynTMOH/kVp4Vxlk46A8neE+/LI8RPh6lJh52vb+iPAtBpsQoq06T
++u4DfJcN8Y/jy+QAn78jryKjwKuZWfuWny9gxsLWMUbH5Bc6v6wfQQ==
 -----END CERTIFICATE-----
diff --git a/pjlib/build/pjlib.dsp b/pjlib/build/pjlib.dsp
index 6977f8a..d63165e 100644
--- a/pjlib/build/pjlib.dsp
+++ b/pjlib/build/pjlib.dsp
@@ -340,6 +340,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE=..\src\pj\ssl_sock_dump.c

+# End Source File

+# Begin Source File

+

 SOURCE=..\src\pj\ssl_sock_ossl.c

 # End Source File

 # Begin Source File

diff --git a/pjlib/build/pjlib.vcproj b/pjlib/build/pjlib.vcproj
index d151dfb..9c3941b 100644
--- a/pjlib/build/pjlib.vcproj
+++ b/pjlib/build/pjlib.vcproj
@@ -5649,6 +5649,10 @@
 				>

 			</File>

 			<File

+				RelativePath="..\src\pj\ssl_sock_dump.c"

+				>

+			</File>

+			<File

 				RelativePath="..\src\pj\ssl_sock_ossl.c"

 				>

 			</File>

diff --git a/pjlib/build/privkey.pem b/pjlib/build/privkey.pem
index 3241be2..44c4b4e 100644
--- a/pjlib/build/privkey.pem
+++ b/pjlib/build/privkey.pem
@@ -1,27 +1,15 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIIEpQIBAAKCAQEA1lr5y/qEgtEOjsBi7dmLoV0xMyFb9wfkhkO7Woweh91l1Xqv
-tTZlPWM3Gt94OBUa1O5+LJmJii85tWp4gYTZiYwPrfoG8osypyzGgi6BudrVKFC3
-ADRilGhsJZr2RboA59nlYOlH1H0cxBm1d6t2vz80rDhD8LDHhd2DECT4ZKMj0C7V
-oFF4D/PTcqt14xhrcP6plIEIxPGuvz9Ml3PpsiNIZgKZmfZwd2JpQeVt6ulqFNoB
-oS13+Ndqx/y4HZz4TOIo0ueeCr/RHFA3UE065um2IOurY/qk3rew6JVDpU2d7yqb
-git7bXivQmzT1c7DLAd8ggC0i80N+O6SMHy2iQIDAQABAoIBAQCAke7Ujz2d7WDq
-9LAh8+NRdUFGZtLvd9d1RPkCVZsWaRBknIL5kVfmGzV5M+K62MXQRACAJdOeg7b8
-fpErNpD4dH8PHjG+lwlZxnyGpvh+jqhd1xP81m7ujzeW0ry2k9tpNYPkveespyJy
-6Oy0i67dBT9FsTXnD1GNlJDBRTuLuEkTTBqbn/2s3+gUfChJ4HPmYMeO9HU4PcfM
-yUsHatBiIkXiCKdDZVMDr5AUVD0Wo3uHPGJ8ZreURAjH+ldG09+/EsVoPberbrDZ
-ZxJ70VKG+ZZTY8HZr9OsZhDZDrHiw9PdG7Hvg7bCvv+gDzZ/z8F+7YHjRjmD5Tp5
-Ex5hDco1AoGBAPfdfzwmqb79AXwYH0HZkkl2EXpzbR9LRgvWlOMSN3GlZhusvGQR
-up6iGk9QnmoEtQS2IAuK4JT3r+yoM/20Nadq5ZUpkZ49SHuZ6+eZpotv3Fh8Oay8
-TAi2vBGM7EQPUOjPOWMRaYGBz3FT/GvUGPTeQ8jYt1gy8F18+A8xD8pTAoGBAN1j
-7+yTly+M47U6mIUXcwoelaS4f/kMcwKHO0O182S4ktfjzc3TpQbHm68ws1rB3iFZ
-SFOP/d04tVxZqPBhN2SpXRHGqTJxXthdTbu3scLMedlf4jY11SRiHX4PDnoBQ1GJ
-NpdkMoex25Fw3AqSVpP61zo8sJkqpqjFfeQDbfgzAoGBAKyGx1ZmDwc6cjsfSzp5
-p+JsRVQ3XcBHk9UPooi/mEoJd55RyLvav0xFxwxoMCvZZOqHnpyKKTJniVOv7Khu
-NF55AJ6n1Y0QWRB3ngWSJKOv0+7fYQHD+yShlRyeO6JQCuBRxT8Y0phrc6oNbIjd
-lBV1VDdL6aqBol9gagWg/72zAoGBAK1rAx1F3z+YFSZ459AZNjvPCVkmTNhBMDXi
-yEGZ3TYgfqYuA6AfET3mTcVFWLjW87EbxtPuDuWi7i2Q7gydmk53fDfYbeDdfXXu
-YF2S3uPAWBI2UXQ1ZuhBEukT0jsvkhPkb6bXDd3NLDkZNsPxLXBtJPqxX4QbLME3
-Mg3RweqRAoGAJ4iXP2b4XWhg17qpDtpn8nhFxLNdhxdaDSKRL8oKQLteds3wS0fi
-ZlaU1P9a3ygTpNquKlmdLJTQEDAVjV5DDlrQAPxtSSytHulNzXRMQFaeydar3Ssv
-J07BPdQs6JEgV071rGGzBcL8ulo7qCdnGxU6GmhLkS4MBbTuqR6jmgU=
+MIICXgIBAAKBgQDI9T0Pf+1gKOTOAGEpZ481Q6xfm5vz6n1+6udxzQtfPKlQrPD5
+x5im2u3tmy6ABxZeY5tCdeikBPiGlc5bRIRng6KM8kidkg3gEhwhRUxHCMWbmBpk
+z7rFERf/pWAOCqYCiy1RT8QrK+XOFoFdJhdF85UPDEUw+pHEsYetTDs9RQIDAQAB
+AoGAGV+1xQY/H7wqH8S2f/begzg3RJ8uUt8R13urm5frTqwnKNOdXbyRDshn8G9+
+sJW0gliLWxnuNP+Xrc6ujqGZIguK/yAxJ3LprAN2Ay1lW2ONyZNMquBeIY5Txhyy
+SnU7U+NQYgA3+w9T7O7YQ575TTDm2gri558jIx8t55Wo9sUCQQDtjfGZ3sYXwpxR
+MvtdtfwDxSKhf6glT6dn7/37KITBZXFy6Eb/tHrEEUuwR46g30vTd2JElCB+QExu
+4sZDt813AkEA2I/WXdGVRXtHzVivf3AnqWyXfrfAAXlBmEkgPyIPwE1+mxeNxkU7
+TRn0MOqAfbQW4+GRIYCKSBLodRnRq2iKIwJBAJLYa8DyNQH7CyYmnbwQAvlRo1ax
+0v89ff6CHD5ljar/SmH9s+XdawZIqsENet13KyhNZDGAX5WrqZPiGy1BMYECQQC1
+FREawfUfdEZF3rJgzVdcxACpZNyYXtwKipr8L28cTbBf3wIdmCZOAjW98VgfxEaf
+pi3E5ca7HZRi1oQL4A4hAkEA5koHCQYl+5PDjbLtxl0VyVCpmT9BrcZ99MS+ZEaW
+2+HpKIhXrEFxePQaWbCaW7gjKmKUwC0qqu0moedqJC3mzg==
 -----END RSA PRIVATE KEY-----
diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h
index 9c39214..f9fc69b 100644
--- a/pjlib/include/pj/ssl_sock.h
+++ b/pjlib/include/pj/ssl_sock.h
@@ -60,17 +60,126 @@
 typedef struct pj_ssl_cert_t pj_ssl_cert_t;
 
 
+typedef enum pj_ssl_cert_verify_flag_t
+{
+    /**
+     * No error in verification.
+     */
+    PJ_SSL_CERT_ESUCCESS				= 0,
+
+    /**
+     * The issuer certificate cannot be found.
+     */
+    PJ_SSL_CERT_EISSUER_NOT_FOUND			= (1 << 0),
+
+    /**
+     * The certificate is untrusted.
+     */
+    PJ_SSL_CERT_EUNTRUSTED				= (1 << 1),
+
+    /**
+     * The certificate has expired or not yet valid.
+     */
+    PJ_SSL_CERT_EVALIDITY_PERIOD			= (1 << 2),
+
+    /**
+     * One or more fields of the certificate cannot be decoded due to
+     * invalid format.
+     */
+    PJ_SSL_CERT_EINVALID_FORMAT				= (1 << 3),
+
+    /**
+     * The certificate cannot be used for the specified purpose.
+     */
+    PJ_SSL_CERT_EINVALID_PURPOSE			= (1 << 4),
+
+    /**
+     * The issuer info in the certificate does not match to the (candidate) 
+     * issuer certificate, e.g: issuer name not match to subject name
+     * of (candidate) issuer certificate.
+     */
+    PJ_SSL_CERT_EISSUER_MISMATCH			= (1 << 5),
+
+    /**
+     * The CRL certificate cannot be found or cannot be read properly.
+     */
+    PJ_SSL_CERT_ECRL_FAILURE				= (1 << 6),
+
+    /**
+     * The certificate has been revoked.
+     */
+    PJ_SSL_CERT_EREVOKED				= (1 << 7),
+
+    /**
+     * The certificate chain length is too long.
+     */
+    PJ_SSL_CERT_ECHAIN_TOO_LONG				= (1 << 8),
+
+    /**
+     * The server identity does not match to any identities specified in 
+     * the certificate, e.g: subjectAltName extension, subject common name.
+     * This flag will only be set by application as SSL socket does not 
+     * perform server identity verification.
+     */
+    PJ_SSL_CERT_EIDENTITY_NOT_MATCH			= (1 << 30),
+
+    /**
+     * Unknown verification error.
+     */
+    PJ_SSL_CERT_EUNKNOWN				= (1 << 31)
+
+} pj_ssl_cert_verify_flag_t;
+
+
+typedef enum pj_ssl_cert_name_type
+{
+    PJ_SSL_CERT_NAME_UNKNOWN = 0,
+    PJ_SSL_CERT_NAME_RFC822,
+    PJ_SSL_CERT_NAME_DNS,
+    PJ_SSL_CERT_NAME_URI,
+    PJ_SSL_CERT_NAME_IP
+} pj_ssl_cert_name_type;
+
 /**
  * Describe structure of certificate info.
  */
 typedef struct pj_ssl_cert_info {
-    pj_str_t	subject;	    /**< Subject.		*/
-    pj_str_t	issuer;		    /**< Issuer.		*/
-    unsigned	version;	    /**< Certificate version.	*/
-    pj_time_val	validity_start;	    /**< Validity start.	*/
-    pj_time_val	validity_end;	    /**< Validity end.		*/
-    pj_bool_t	validity_use_gmt;   /**< Flag if validity date/time 
-					 use GMT.		*/
+
+    unsigned	version;	    /**< Certificate version	*/
+
+    pj_uint8_t	serial_no[20];	    /**< Serial number, array of
+				         octets, first index is
+					 MSB			*/
+
+    struct {
+        pj_str_t	cn;	    /**< Common name		*/
+        pj_str_t	info;	    /**< One line subject, fields
+					 are separated by slash */
+    } subject;			    /**< Subject		*/
+
+    struct {
+        pj_str_t	cn;	    /**< Common name		*/
+        pj_str_t	info;	    /**< One line subject, fields
+					 are separated by slash.*/
+    } issuer;			    /**< Issuer			*/
+
+    struct {
+	pj_time_val	start;	    /**< Validity start		*/
+	pj_time_val	end;	    /**< Validity end		*/
+	pj_bool_t	gmt;	    /**< Flag if validity date/time 
+					 use GMT		*/
+    } validity;			    /**< Validity		*/
+
+    struct {
+	unsigned	cnt;	    /**< # of entry		*/
+	struct {
+	    pj_ssl_cert_name_type type;
+				    /**< Name type		*/
+	    pj_str_t	name;	    /**< The name		*/
+	} *entry;		    /**< Subject alt name entry */
+    } subj_alt_name;		    /**< Subject alternative
+					 name extension		*/
+
 } pj_ssl_cert_info;
 
 
@@ -93,6 +202,39 @@
 						 pj_ssl_cert_t **p_cert);
 
 
+/**
+ * Dump SSL certificate info.
+ *
+ * @param ci		The certificate info.
+ * @param prefix	Prefix string for each line.
+ * @param buf		The buffer where certificate info will be printed on.
+ * @param buf_size	The buffer size.
+ *
+ * @return		PJ_SUCCESS when successful.
+ */
+PJ_DECL(pj_status_t) pj_ssl_cert_info_dump(const pj_ssl_cert_info *ci,
+					   const char *prefix,
+					   char *buf,
+					   pj_size_t buf_size);
+
+
+/**
+ * Get SSL certificate verification error messages from verification status.
+ *
+ * @param verify_status	The SSL certificate verification status.
+ * @param error_strings	Array of strings to receive the verification error 
+ *			messages.
+ * @param count		On input it specifies maximum error messages should be
+ *			retrieved. On output it specifies the number of error
+ *			messages retrieved.
+ *
+ * @return		PJ_SUCCESS when successful.
+ */
+PJ_DECL(pj_status_t) pj_ssl_cert_verify_error_st(pj_uint32_t verify_status, 
+						 const char *error_strings[],
+						 unsigned *count);
+
+
 /** 
  * Cipher suites enumeration.
  */
@@ -363,13 +505,18 @@
     /**
      * Describes active local certificate info.
      */
-    pj_ssl_cert_info local_cert_info;
+    pj_ssl_cert_info *local_cert_info;
    
     /**
      * Describes active remote certificate info.
      */
-    pj_ssl_cert_info remote_cert_info;
-   
+    pj_ssl_cert_info *remote_cert_info;
+
+    /**
+     * Status of peer certificate verification.
+     */
+    pj_uint32_t		verify_status;
+
 } pj_ssl_sock_info;
 
 
@@ -523,11 +670,11 @@
     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.
+     * Server name indication. When secure socket is acting as client 
+     * (perform outgoing connection) and the server may host multiple
+     * 'virtual' servers at a single underlying network address, setting
+     * this will allow client to tell the server a name of the server
+     * it is contacting.
      *
      * Default value is zero/not-set.
      */
diff --git a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c
index 127099c..988a8b2 100644
--- a/pjlib/src/pj/ssl_sock_common.c
+++ b/pjlib/src/pj/ssl_sock_common.c
@@ -17,6 +17,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
 #include <pj/ssl_sock.h>
+#include <pj/assert.h>
 #include <pj/errno.h>
 #include <pj/string.h>
 
@@ -128,6 +129,7 @@
 }
 
 
+/* Get cipher name string */
 PJ_DEF(const char*) pj_ssl_cipher_name(pj_ssl_cipher cipher)
 {
     unsigned i, n;
@@ -140,3 +142,96 @@
 
     return NULL;
 }
+
+
+
+
+PJ_DEF(pj_status_t) pj_ssl_cert_verify_error_st(pj_uint32_t verify_status, 
+						const char *error_strings[],
+						unsigned *count)
+{
+    unsigned i = 0, shift_idx = 0;
+    unsigned unknown = 0;
+    pj_uint32_t errs;
+
+    PJ_ASSERT_RETURN(error_strings && count, PJ_EINVAL);
+
+    if (verify_status == PJ_SSL_CERT_ESUCCESS && *count) {
+	error_strings[0] = "OK";
+	*count = 1;
+	return PJ_SUCCESS;
+    }
+
+    errs = verify_status;
+
+    while (errs && i < *count) {
+	pj_uint32_t err;
+	const char *p = NULL;
+
+	if ((errs & 1) == 0) {
+	    shift_idx++;
+	    errs >>= 1;
+	    continue;
+	}
+
+	err = (1 << shift_idx);
+
+	switch (err) {
+	case PJ_SSL_CERT_EISSUER_NOT_FOUND:
+	    p = "The issuer certificate cannot be found";
+	    break;
+	case PJ_SSL_CERT_EUNTRUSTED:
+	    p = "The certificate is untrusted";
+	    break;
+	case PJ_SSL_CERT_EVALIDITY_PERIOD:
+	    p = "The certificate has expired or not yet valid";
+	    break;
+	case PJ_SSL_CERT_EINVALID_FORMAT:
+	    p = "One or more fields of the certificate cannot be decoded "
+		"due to invalid format";
+	    break;
+	case PJ_SSL_CERT_EISSUER_MISMATCH:
+	    p = "The issuer info in the certificate does not match to the "
+		"(candidate) issuer certificate";
+	    break;
+	case PJ_SSL_CERT_ECRL_FAILURE:
+	    p = "The CRL certificate cannot be found or cannot be read "
+		"properly";
+	    break;
+	case PJ_SSL_CERT_EREVOKED:
+	    p = "The certificate has been revoked";
+	    break;
+	case PJ_SSL_CERT_EINVALID_PURPOSE:
+	    p = "The certificate or CA certificate cannot be used for the "
+		"specified purpose";
+	    break;
+	case PJ_SSL_CERT_ECHAIN_TOO_LONG:
+	    p = "The certificate chain length is too long";
+	    break;
+	case PJ_SSL_CERT_EIDENTITY_NOT_MATCH:
+	    p = "The server identity does not match to any identities "
+		"specified in the certificate";
+	    break;
+	case PJ_SSL_CERT_EUNKNOWN:
+	default:
+	    unknown++;
+	    break;
+	}
+	
+	/* Set error string */
+	if (p)
+	    error_strings[i++] = p;
+
+	/* Next */
+	shift_idx++;
+	errs >>= 1;
+    }
+
+    /* Unknown error */
+    if (unknown && i < *count)
+	error_strings[i++] = "Unknown verification error";
+
+    *count = i;
+
+    return PJ_SUCCESS;
+}
diff --git a/pjlib/src/pj/ssl_sock_dump.c b/pjlib/src/pj/ssl_sock_dump.c
new file mode 100644
index 0000000..45a6f7e
--- /dev/null
+++ b/pjlib/src/pj/ssl_sock_dump.c
@@ -0,0 +1,147 @@
+/* $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/errno.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+
+/* Only build when PJ_HAS_SSL_SOCK is enabled */
+#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK!=0
+
+#define THIS_FILE	"ssl_sock_dump.c"
+
+#define CHECK_BUF_LEN()							\
+    if ((len < 0) || ((p+=len) >= end)) {				\
+	*(p-1) = '\0';							\
+	return PJ_ETOOSMALL;						\
+    }
+
+PJ_DEF(pj_status_t) pj_ssl_cert_info_dump(const pj_ssl_cert_info *ci,
+					  const char *prefix,
+					  char *buf,
+					  pj_size_t buf_size)
+{
+    const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+    pj_parsed_time pt1;
+    pj_parsed_time pt2;
+    unsigned i;
+    int len = 0;
+    char *p, *end;
+
+    p = buf;
+    end = buf + buf_size;
+
+    pj_time_decode(&ci->validity.start, &pt1);
+    pj_time_decode(&ci->validity.end, &pt2);
+
+    /* Version */
+    len = pj_ansi_snprintf(p, end-p, "%sVersion    : v%d\n", 
+			   prefix, ci->version);
+    CHECK_BUF_LEN();
+    
+    /* Serial number */
+    len = pj_ansi_snprintf(p, end-p, "%sSerial     : ", prefix);
+    CHECK_BUF_LEN();
+
+    for (i = 0; i < sizeof(ci->serial_no) && !ci->serial_no[i]; ++i);
+    for (; i < sizeof(ci->serial_no); ++i) {
+	len = pj_ansi_snprintf(p, end-p, "%02X ", ci->serial_no[i]);
+	CHECK_BUF_LEN();
+    }
+    *(p-1) = '\n';
+    
+    /* Subject */
+    len = pj_ansi_snprintf( p, end-p, "%sSubject    : %.*s\n", prefix,
+			    ci->subject.cn.slen, 
+			    ci->subject.cn.ptr);
+    CHECK_BUF_LEN();
+    len = pj_ansi_snprintf( p, end-p, "%s             %.*s\n", prefix,
+			    ci->subject.info.slen,
+			    ci->subject.info.ptr);
+    CHECK_BUF_LEN();
+
+    /* Issuer */
+    len = pj_ansi_snprintf( p, end-p, "%sIssuer     : %.*s\n", prefix,
+			    ci->issuer.cn.slen,
+			    ci->issuer.cn.ptr);
+    CHECK_BUF_LEN();
+    len = pj_ansi_snprintf( p, end-p, "%s             %.*s\n", prefix,
+			    ci->issuer.info.slen,
+			    ci->issuer.info.ptr);
+    CHECK_BUF_LEN();
+
+    /* Validity period */
+    len = pj_ansi_snprintf( p, end-p, "%sValid from : %s %4d-%02d-%02d "
+			    "%02d:%02d:%02d.%03d %s\n", prefix,
+			    wdays[pt1.wday], pt1.year, pt1.mon+1, pt1.day,
+			    pt1.hour, pt1.min, pt1.sec, pt1.msec,
+			    (ci->validity.gmt? "GMT":""));
+    CHECK_BUF_LEN();
+
+    len = pj_ansi_snprintf( p, end-p, "%sValid to   : %s %4d-%02d-%02d "
+			    "%02d:%02d:%02d.%03d %s\n", prefix,
+			    wdays[pt2.wday], pt2.year, pt2.mon+1, pt2.day,
+			    pt2.hour, pt2.min, pt2.sec, pt2.msec,
+			    (ci->validity.gmt? "GMT":""));
+    CHECK_BUF_LEN();
+
+    /* Subject alternative name extension */
+    if (ci->subj_alt_name.cnt) {
+	unsigned i;
+
+	len = pj_ansi_snprintf(p, end-p, "%ssubjectAltName extension\n", 
+			       prefix);
+	CHECK_BUF_LEN();
+
+	for (i = 0; i < ci->subj_alt_name.cnt; ++i) {
+	    const char *type = NULL;
+
+	    switch(ci->subj_alt_name.entry[i].type) {
+	    case PJ_SSL_CERT_NAME_RFC822:
+		type = "MAIL";
+		break;
+	    case PJ_SSL_CERT_NAME_DNS:
+		type = " DNS";
+		break;
+	    case PJ_SSL_CERT_NAME_URI:
+		type = " URI";
+		break;
+	    case PJ_SSL_CERT_NAME_IP:
+		type = "  IP";
+		break;
+	    default:
+		break;
+	    }
+	    if (type) {
+		len = pj_ansi_snprintf( p, end-p, "%s      %s : %.*s\n", prefix, 
+					type, 
+					ci->subj_alt_name.entry[i].name.slen, 
+					ci->subj_alt_name.entry[i].name.ptr);
+		CHECK_BUF_LEN();
+	    }
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+#endif  /* PJ_HAS_SSL_SOCK */
+
diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c
index 3b0e86f..7fe0c43 100644
--- a/pjlib/src/pj/ssl_sock_ossl.c
+++ b/pjlib/src/pj/ssl_sock_ossl.c
@@ -45,6 +45,7 @@
 #include <openssl/bio.h>
 #include <openssl/ssl.h>
 #include <openssl/err.h>
+#include <openssl/x509v3.h>
 
 
 #ifdef _MSC_VER
@@ -152,6 +153,7 @@
     enum ssl_state	  ssl_state;
     pj_ioqueue_op_key_t	  handshake_op_key;
     pj_timer_entry	  timer;
+    pj_status_t		  verify_status;
 
     pj_sock_t		  sock;
     pj_activesock_t	 *asock;
@@ -207,13 +209,17 @@
 
 #define PJ_SSL_ERRNO_SPACE_SIZE		PJ_ERRNO_SPACE_SIZE
 
-#define GET_SSL_STATUS(status) { \
-    unsigned long e = ERR_get_error();\
-    status = ERR_GET_LIB(e)*300 + ERR_GET_REASON(e);\
+#define STATUS_FROM_SSL_ERR(err, status) { \
+    status = ERR_GET_LIB(err)*300 + ERR_GET_REASON(err);\
     pj_assert(status < PJ_SSL_ERRNO_SPACE_SIZE);\
     if (status) status += PJ_SSL_ERRNO_START;\
 }
 
+#define GET_SSL_STATUS(status) { \
+    unsigned long e = ERR_get_error();\
+    STATUS_FROM_SSL_ERR(e, status);\
+}
+
 /*
  * Get error string of OpenSSL.
  */
@@ -235,7 +241,11 @@
 
     {
 	const char *tmp = NULL;
-	tmp = ERR_reason_error_string(ssl_err);
+
+	if (ssl_err >= 300)
+	    tmp = ERR_reason_error_string(ssl_err);
+	else
+	    tmp = X509_verify_cert_error_string(ssl_err);
 
 	if (tmp) {
 	    pj_ansi_strncpy(buf, tmp, bufsize);
@@ -263,6 +273,9 @@
 static pj_ssl_cipher openssl_ciphers[100];
 static unsigned openssl_cipher_num;
 
+/* OpenSSL application data index */
+static int sslsock_idx;
+
 
 /* Initialize OpenSSL */
 static pj_status_t init_openssl(void)
@@ -329,6 +342,9 @@
 	openssl_cipher_num = n;
     }
 
+    /* Create OpenSSL application data index for SSL socket */
+    sslsock_idx = SSL_get_ex_new_index(0, "SSL socket", NULL, NULL, NULL);
+
     return PJ_SUCCESS;
 }
 
@@ -355,8 +371,107 @@
 }
 
 
-/* Create and initialize new SSL context */
-static pj_status_t create_ssl_ctx(pj_ssl_sock_t *ssock, SSL_CTX **p_ctx)
+/* SSL password callback. */
+static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
+{
+    pj_ssl_sock_t *ssock;
+    SSL *ossl_ssl;
+    int err;
+
+    /* Get SSL instance */
+    ossl_ssl = X509_STORE_CTX_get_ex_data(x509_ctx, 
+				    SSL_get_ex_data_X509_STORE_CTX_idx());
+    pj_assert(ossl_ssl);
+
+    /* Get SSL socket instance */
+    ssock = SSL_get_ex_data(ossl_ssl, sslsock_idx);
+    pj_assert(ssock);
+
+    /* Store verification status */
+    err = X509_STORE_CTX_get_error(x509_ctx);
+    switch (err) {
+    case X509_V_OK:
+	break;
+
+    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+	ssock->verify_status |= PJ_SSL_CERT_EISSUER_NOT_FOUND;
+	break;
+
+    case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+    case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+    case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
+    case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
+	ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT;
+	break;
+
+    case X509_V_ERR_CERT_NOT_YET_VALID:
+    case X509_V_ERR_CERT_HAS_EXPIRED:
+	ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD;
+	break;
+
+    case X509_V_ERR_UNABLE_TO_GET_CRL:
+    case X509_V_ERR_CRL_NOT_YET_VALID:
+    case X509_V_ERR_CRL_HAS_EXPIRED:
+    case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
+    case X509_V_ERR_CRL_SIGNATURE_FAILURE:
+    case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
+    case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
+	ssock->verify_status |= PJ_SSL_CERT_ECRL_FAILURE;
+	break;	
+
+    case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+    case X509_V_ERR_CERT_UNTRUSTED:
+    case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+	ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED;
+	break;	
+
+    case X509_V_ERR_CERT_SIGNATURE_FAILURE:
+    case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+    case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
+    case X509_V_ERR_AKID_SKID_MISMATCH:
+    case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
+    case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
+	ssock->verify_status |= PJ_SSL_CERT_EISSUER_MISMATCH;
+	break;
+
+    case X509_V_ERR_CERT_REVOKED:
+	ssock->verify_status |= PJ_SSL_CERT_EREVOKED;
+	break;	
+
+    case X509_V_ERR_INVALID_PURPOSE:
+    case X509_V_ERR_CERT_REJECTED:
+    case X509_V_ERR_INVALID_CA:
+	ssock->verify_status |= PJ_SSL_CERT_EINVALID_PURPOSE;
+	break;
+
+    case X509_V_ERR_CERT_CHAIN_TOO_LONG: /* not really used */
+    case X509_V_ERR_PATH_LENGTH_EXCEEDED:
+	ssock->verify_status |= PJ_SSL_CERT_ECHAIN_TOO_LONG;
+	break;
+
+    /* Unknown errors */
+    case X509_V_ERR_OUT_OF_MEM:
+    default:
+	ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN;
+	break;
+    }
+
+    /* When verification is not requested just return ok here, however
+     * application can still get the verification status.
+     */
+    if (PJ_FALSE == ssock->param.verify_peer)
+	preverify_ok = 1;
+
+    return preverify_ok;
+}
+
+/* Setting SSL sock cipher list */
+static pj_status_t set_cipher_list(pj_ssl_sock_t *ssock);
+
+
+/* Create and initialize new SSL context and instance */
+static pj_status_t create_ssl(pj_ssl_sock_t *ssock)
 {
     SSL_METHOD *ssl_method;
     SSL_CTX *ctx;
@@ -364,7 +479,7 @@
     int mode, rc;
     pj_status_t status;
         
-    pj_assert(ssock && p_ctx);
+    pj_assert(ssock);
 
     cert = ssock->cert;
 
@@ -393,7 +508,7 @@
 	return PJ_EINVAL;
     }
 
-    /* Create SSL context for the listener */
+    /* Create SSL context */
     ctx = SSL_CTX_new(ssl_method);
     if (ctx == NULL) {
 	GET_SSL_STATUS(status);
@@ -455,29 +570,55 @@
 	}
     }
 
-
-    /* SSL verification options */
-    if (ssock->param.verify_peer) {
-	mode = SSL_VERIFY_PEER;
-    } else {
-	mode = SSL_VERIFY_NONE;
+    /* Create SSL instance */
+    ssock->ossl_ctx = ctx;
+    ssock->ossl_ssl = SSL_new(ssock->ossl_ctx);
+    if (ssock->ossl_ssl == NULL) {
+	GET_SSL_STATUS(status);
+	return status;
     }
 
+    /* Set SSL sock as application data of SSL instance */
+    SSL_set_ex_data(ssock->ossl_ssl, sslsock_idx, ssock);
+
+    /* SSL verification options */
+    mode = SSL_VERIFY_PEER;
     if (ssock->is_server && ssock->param.require_client_cert)
-	mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_PEER;
+	mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
 
-    SSL_CTX_set_verify(ctx, mode, NULL);
+    SSL_set_verify(ssock->ossl_ssl, mode, &verify_cb);
 
-    *p_ctx = ctx;
+    /* Set cipher list */
+    status = set_cipher_list(ssock);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Setup SSL BIOs */
+    ssock->ossl_rbio = BIO_new(BIO_s_mem());
+    ssock->ossl_wbio = BIO_new(BIO_s_mem());
+    BIO_set_close(ssock->ossl_rbio, BIO_CLOSE);
+    BIO_set_close(ssock->ossl_wbio, BIO_CLOSE);
+    SSL_set_bio(ssock->ossl_ssl, ssock->ossl_rbio, ssock->ossl_wbio);
 
     return PJ_SUCCESS;
 }
 
 
-/* Destroy SSL context */
-static void destroy_ssl_ctx(SSL_CTX *ctx)
+/* Destroy SSL context and instance */
+static void destroy_ssl(pj_ssl_sock_t *ssock)
 {
-    SSL_CTX_free(ctx);
+    /* Destroy SSL instance */
+    if (ssock->ossl_ssl) {
+	SSL_shutdown(ssock->ossl_ssl);
+	SSL_free(ssock->ossl_ssl); /* this will also close BIOs */
+	ssock->ossl_ssl = NULL;
+    }
+
+    /* Destroy SSL context */
+    if (ssock->ossl_ctx) {
+	SSL_CTX_free(ssock->ossl_ctx);
+	ssock->ossl_ctx = NULL;
+    }
 
     /* Potentially shutdown OpenSSL library if this is the last
      * context exists.
@@ -491,15 +632,8 @@
 {
     ssock->ssl_state = SSL_STATE_NULL;
 
-    if (ssock->ossl_ssl) {
-	SSL_shutdown(ssock->ossl_ssl);
-	SSL_free(ssock->ossl_ssl); /* this will also close BIOs */
-	ssock->ossl_ssl = NULL;
-    }
-    if (ssock->ossl_ctx) {
-	destroy_ssl_ctx(ssock->ossl_ctx);
-	ssock->ossl_ctx = NULL;
-    }
+    destroy_ssl(ssock);
+
     if (ssock->asock) {
 	pj_activesock_close(ssock->asock);
 	ssock->asock = NULL;
@@ -639,42 +773,143 @@
 }
 
 
-/* Get certificate info from OpenSSL X509 */
+/* Get Common Name field string from a general name string */
+static void get_cn_from_gen_name(const pj_str_t *gen_name, pj_str_t *cn)
+{
+    pj_str_t CN_sign = {"/CN=", 4};
+    char *p, *q;
+
+    pj_bzero(cn, sizeof(cn));
+
+    p = pj_strstr(gen_name, &CN_sign);
+    if (!p)
+	return;
+
+    p += 4; /* shift pointer to value part */
+    pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr));
+    q = pj_strchr(cn, '/');
+    if (q)
+	cn->slen = q - p;
+}
+
+
+/* Get certificate info from OpenSSL X509, in case the certificate info
+ * hal already populated, this function will check if the contents need 
+ * to be updated by inspecting the issuer and the serial number.
+ */
 static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci, X509 *x)
 {
-    pj_ssl_cert_info info;
-    char buf1[256];
-    char buf2[256];
+    pj_bool_t update_needed;
+    char buf[512];
+    pj_uint8_t serial_no[64] = {0}; /* should be >= sizeof(ci->serial_no) */
+    pj_uint8_t *p;
+    unsigned len;
+    GENERAL_NAMES *names = NULL;
 
-    pj_assert(pool && ci);
+    pj_assert(pool && ci && x);
 
-    if (!x) {
-	pj_bzero(ci, sizeof(pj_ssl_cert_info));
+    /* Get issuer */
+    X509_NAME_oneline(X509_get_issuer_name(x), buf, sizeof(buf));
+
+    /* Get serial no */
+    p = (pj_uint8_t*) M_ASN1_STRING_data(X509_get_serialNumber(x));
+    len = M_ASN1_STRING_length(X509_get_serialNumber(x));
+    if (len > sizeof(ci->serial_no)) 
+	len = sizeof(ci->serial_no);
+    pj_memcpy(serial_no + sizeof(ci->serial_no) - len, p, len);
+
+    /* Check if the contents need to be updated. */
+    update_needed = pj_strcmp2(&ci->issuer.info, buf) || 
+	            pj_memcmp(ci->serial_no, serial_no, sizeof(ci->serial_no));
+    if (!update_needed)
 	return;
-    }
 
-    pj_bzero(&info, sizeof(info));
+    /* Update cert info */
 
-    /* Populate cert info */
-    info.subject = pj_str(X509_NAME_oneline(X509_get_subject_name(x),buf1,
-					    sizeof(buf1)));
-    info.issuer = pj_str(X509_NAME_oneline(X509_get_issuer_name(x), buf2,
-					   sizeof(buf2)));
-    info.version = X509_get_version(x) + 1;
-    parse_ossl_asn1_time(&info.validity_start, &info.validity_use_gmt,
+    pj_bzero(ci, sizeof(pj_ssl_cert_info));
+
+    /* Version */
+    ci->version = X509_get_version(x) + 1;
+
+    /* Issuer */
+    pj_strdup2(pool, &ci->issuer.info, buf);
+    get_cn_from_gen_name(&ci->issuer.info, &ci->issuer.cn);
+
+    /* Serial number */
+    pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no));
+
+    /* Subject */
+    pj_strdup2(pool, &ci->subject.info, 
+	       X509_NAME_oneline(X509_get_subject_name(x),
+				 buf, sizeof(buf)));
+    get_cn_from_gen_name(&ci->subject.info, &ci->subject.cn);
+
+    /* Validity */
+    parse_ossl_asn1_time(&ci->validity.start, &ci->validity.gmt,
 			 X509_get_notBefore(x));
-    parse_ossl_asn1_time(&info.validity_end, &info.validity_use_gmt,
+    parse_ossl_asn1_time(&ci->validity.end, &ci->validity.gmt,
 			 X509_get_notAfter(x));
 
-    /* Update certificate info */
-    if (pj_strcmp(&ci->subject, &info.subject))
-	pj_strdup(pool, &ci->subject, &info.subject);
-    if (pj_strcmp(&ci->issuer, &info.issuer))
-	pj_strdup(pool, &ci->issuer, &info.issuer);
-    ci->version = info.version;
-    ci->validity_start = info.validity_start;
-    ci->validity_end = info.validity_end;
-    ci->validity_use_gmt = info.validity_use_gmt;
+    /* Subject Alternative Name extension */
+    if (ci->version >= 3) {
+	names = (GENERAL_NAMES*) X509_get_ext_d2i(x, NID_subject_alt_name,
+						  NULL, NULL);
+    }
+    if (names) {
+        unsigned i, cnt;
+
+        cnt = sk_GENERAL_NAME_num(names);
+	ci->subj_alt_name.entry = pj_pool_calloc(pool, cnt, 
+					    sizeof(*ci->subj_alt_name.entry));
+
+        for (i = 0; i < cnt; ++i) {
+	    unsigned char *p = 0;
+	    pj_ssl_cert_name_type type = PJ_SSL_CERT_NAME_UNKNOWN;
+            const GENERAL_NAME *name;
+	    
+	    name = sk_GENERAL_NAME_value(names, i);
+
+            switch (name->type) {
+                case GEN_EMAIL:
+                    len = ASN1_STRING_to_UTF8(&p, name->d.ia5);
+		    type = PJ_SSL_CERT_NAME_RFC822;
+                    break;
+                case GEN_DNS:
+                    len = ASN1_STRING_to_UTF8(&p, name->d.ia5);
+		    type = PJ_SSL_CERT_NAME_DNS;
+                    break;
+                case GEN_URI:
+                    len = ASN1_STRING_to_UTF8(&p, name->d.ia5);
+		    type = PJ_SSL_CERT_NAME_URI;
+                    break;
+                case GEN_IPADD:
+		    p = ASN1_STRING_data(name->d.ip);
+		    len = ASN1_STRING_length(name->d.ip);
+		    type = PJ_SSL_CERT_NAME_IP;
+                    break;
+		default:
+		    break;
+            }
+
+	    if (p && len && type != PJ_SSL_CERT_NAME_UNKNOWN) {
+		ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type;
+		if (type == PJ_SSL_CERT_NAME_IP) {
+		    int af = pj_AF_INET();
+		    if (len == sizeof(pj_in6_addr)) af = pj_AF_INET6();
+		    pj_inet_ntop2(af, p, buf, sizeof(buf));
+		    pj_strdup2(pool, 
+		          &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name,
+		          buf);
+		} else {
+		    pj_strdup2(pool, 
+			  &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, 
+			  (char*)p);
+		    OPENSSL_free(p);
+		}
+		ci->subj_alt_name.cnt++;
+	    }
+        }
+    }
 }
 
 
@@ -689,14 +924,22 @@
 
     /* Active local certificate */
     x = SSL_get_certificate(ssock->ossl_ssl);
-    get_cert_info(ssock->pool, &ssock->local_cert_info, x);
-    /* Don't free local's X509! */
+    if (x) {
+	get_cert_info(ssock->pool, &ssock->local_cert_info, x);
+	/* Don't free local's X509! */
+    } else {
+	pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info));
+    }
 
     /* Active remote certificate */
     x = SSL_get_peer_certificate(ssock->ossl_ssl);
-    get_cert_info(ssock->pool, &ssock->remote_cert_info, x);
-    /* Free peer's X509 */
-    X509_free(x);
+    if (x) {
+	get_cert_info(ssock->pool, &ssock->remote_cert_info, x);
+	/* Free peer's X509 */
+	X509_free(x);
+    } else {
+	pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info));
+    }
 }
 
 
@@ -1231,29 +1474,10 @@
     pj_sockaddr_cp(&ssock->rem_addr, src_addr);
 
     /* Create SSL context */
-    status = create_ssl_ctx(ssock, &ssock->ossl_ctx);
+    status = create_ssl(ssock);
     if (status != PJ_SUCCESS)
 	goto on_return;
 
-    /* Create SSL instance */
-    ssock->ossl_ssl = SSL_new(ssock->ossl_ctx);
-    if (ssock->ossl_ssl == NULL) {
-	GET_SSL_STATUS(status);
-	goto on_return;
-    }
-
-    /* Set cipher list */
-    status = set_cipher_list(ssock);
-    if (status != PJ_SUCCESS)
-	goto on_return;
-
-    /* Setup SSL BIOs */
-    ssock->ossl_rbio = BIO_new(BIO_s_mem());
-    ssock->ossl_wbio = BIO_new(BIO_s_mem());
-    BIO_set_close(ssock->ossl_rbio, BIO_CLOSE);
-    BIO_set_close(ssock->ossl_wbio, BIO_CLOSE);
-    SSL_set_bio(ssock->ossl_ssl, ssock->ossl_rbio, ssock->ossl_wbio);
-
     /* Prepare read buffer */
     ssock->asock_rbuf = (void**)pj_pool_calloc(ssock->pool, 
 					       ssock->param.async_cnt,
@@ -1351,29 +1575,10 @@
 	goto on_return;
 
     /* Create SSL context */
-    status = create_ssl_ctx(ssock, &ssock->ossl_ctx);
+    status = create_ssl(ssock);
     if (status != PJ_SUCCESS)
 	goto on_return;
 
-    /* Create SSL instance */
-    ssock->ossl_ssl = SSL_new(ssock->ossl_ctx);
-    if (ssock->ossl_ssl == NULL) {
-	GET_SSL_STATUS(status);
-	goto on_return;
-    }
-
-    /* Set cipher list */
-    status = set_cipher_list(ssock);
-    if (status != PJ_SUCCESS)
-	goto on_return;
-
-    /* Setup SSL BIOs */
-    ssock->ossl_rbio = BIO_new(BIO_s_mem());
-    ssock->ossl_wbio = BIO_new(BIO_s_mem());
-    BIO_set_close(ssock->ossl_rbio, BIO_CLOSE);
-    BIO_set_close(ssock->ossl_wbio, BIO_CLOSE);
-    SSL_set_bio(ssock->ossl_ssl, ssock->ossl_rbio, ssock->ossl_wbio);
-
     /* Prepare read buffer */
     ssock->asock_rbuf = (void**)pj_pool_calloc(ssock->pool, 
 					       ssock->param.async_cnt,
@@ -1651,9 +1856,9 @@
     pj_sockaddr_cp(&info->local_addr, &ssock->local_addr);
     
     if (info->established) {
-	/* Current cipher */
 	const SSL_CIPHER *cipher;
 
+	/* Current cipher */
 	cipher = SSL_get_current_cipher(ssock->ossl_ssl);
 	info->cipher = (cipher->id & 0x00FFFFFF);
 
@@ -1661,8 +1866,11 @@
 	pj_sockaddr_cp(&info->remote_addr, &ssock->rem_addr);
 
 	/* Certificates info */
-	info->local_cert_info = ssock->local_cert_info;
-	info->remote_cert_info = ssock->remote_cert_info;
+	info->local_cert_info = &ssock->local_cert_info;
+	info->remote_cert_info = &ssock->remote_cert_info;
+
+	/* Verification status */
+	info->verify_status = ssock->verify_status;
     }
 
     return PJ_SUCCESS;
diff --git a/pjlib/src/pj/ssl_sock_symbian.cpp b/pjlib/src/pj/ssl_sock_symbian.cpp
index efcf170..ab808f6 100644
--- a/pjlib/src/pj/ssl_sock_symbian.cpp
+++ b/pjlib/src/pj/ssl_sock_symbian.cpp
@@ -127,6 +127,11 @@
 	    return securesock_->CurrentCipherSuite(cipher);
 	return KErrNotFound;
     }
+    const CX509Certificate *GetPeerCert() {
+	if (securesock_)
+	    return securesock_->ServerCert();
+	return NULL;
+    }
 
 private:
     enum ssl_state	 state_;
@@ -409,6 +414,7 @@
  */
 struct pj_ssl_sock_t
 {
+    pj_pool_t		*pool;
     pj_ssl_sock_cb	 cb;
     void		*user_data;
     
@@ -434,9 +440,132 @@
     unsigned		 ciphers_num;
     pj_ssl_cipher	*ciphers;
     pj_str_t		 servername;
+    pj_ssl_cert_info	 remote_cert_info;
 };
 
 
+static pj_str_t get_cert_name(pj_pool_t *pool,
+                              const CX500DistinguishedName &name)
+{
+    TInt i;
+    char buf[1024];
+    TUint8 *p;
+    TInt l = sizeof(buf);
+    
+    p = (TUint8*)buf;
+    for(i = 0; i < name.Count(); ++i) {
+	const CX520AttributeTypeAndValue &attr = name.Element(i);
+
+	/* Print element separator */
+	*p++ = '/';
+	if (0 == --l) break;
+
+	/* Print the type. */
+	TPtr8 type(p, l);
+	type.Copy(attr.Type());
+	p += type.Length();
+	l -= type.Length();
+	if (0 >= --l) break;
+
+	/* Print equal sign */
+	*p++ = '=';
+	if (0 == --l) break;
+	
+	/* Print the value. Let's just get the raw data here */
+	TPtr8 value(p, l);
+	value.Copy(attr.EncodedValue().Mid(2));
+	p += value.Length();
+	l -= value.Length();
+	if (0 >= --l) break;
+    }
+    
+    pj_str_t src, res;
+    pj_strset(&src, buf, sizeof(buf) - l);
+    pj_strdup(pool, &res, &src);
+    
+    return res;
+}
+                            
+/* Get certificate info from CX509Certificate.
+ */
+static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci,
+                          const CX509Certificate *x)
+{
+    unsigned len;
+    
+    pj_assert(pool && ci && x);
+    
+    pj_bzero(ci, sizeof(*ci));
+    
+    /* Version */
+    ci->version = x->Version();
+    
+    /* Serial number */
+    len = x->SerialNumber().Length();
+    if (len > sizeof(ci->serial_no)) 
+	len = sizeof(ci->serial_no);
+    pj_memcpy(ci->serial_no + sizeof(ci->serial_no) - len, 
+              x->SerialNumber().Ptr(), len);
+    
+    /* Subject */
+    {
+	HBufC *subject = NULL;
+	TRAPD(err, subject = x->SubjectL());
+	if (err == KErrNone) {
+	    TPtr16 ptr16(subject->Des());
+	    len = ptr16.Length();
+	    TPtr8 ptr8((TUint8*)pj_pool_alloc(pool, len), len);
+	    ptr8.Copy(ptr16);
+	    pj_strset(&ci->subject.cn, (char*)ptr8.Ptr(), ptr8.Length());
+	}
+	ci->subject.info = get_cert_name(pool, x->SubjectName());
+    }
+
+    /* Issuer */
+    {
+	HBufC *issuer = NULL;
+	TRAPD(err, issuer = x->IssuerL());
+	if (err == KErrNone) {
+	    TPtr16 ptr16(issuer->Des());
+	    len = ptr16.Length();
+	    TPtr8 ptr8((TUint8*)pj_pool_alloc(pool, len), len);
+	    ptr8.Copy(ptr16);
+	    pj_strset(&ci->issuer.cn, (char*)ptr8.Ptr(), ptr8.Length());
+	}
+	ci->issuer.info = get_cert_name(pool, x->IssuerName());
+    }
+    
+    /* Validity */
+    const CValidityPeriod &valid_period = x->ValidityPeriod();
+    TTime base_time(TDateTime(1970, EJanuary, 0, 0, 0, 0, 0));
+    TTimeIntervalSeconds tmp_sec;
+    valid_period.Start().SecondsFrom(base_time, tmp_sec);
+    ci->validity.start.sec = tmp_sec.Int(); 
+    valid_period.Finish().SecondsFrom(base_time, tmp_sec);
+    ci->validity.end.sec = tmp_sec.Int();
+}
+
+
+/* Update certificates info. This function should be called after handshake
+ * or renegotiation successfully completed.
+ */
+static void update_certs_info(pj_ssl_sock_t *ssock)
+{
+    const CX509Certificate *x;
+
+    pj_assert(ssock && ssock->sock &&
+              ssock->sock->GetState() == CPjSSLSocket::SSL_STATE_ESTABLISHED);
+        
+    /* Active remote certificate */
+    x = ssock->sock->GetPeerCert();
+    if (x) {
+	get_cert_info(ssock->pool, &ssock->remote_cert_info, x);
+    } else {
+	pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info));
+    }
+}
+
+
 /*
  * Get cipher list supported by SSL/TLS backend.
  */
@@ -504,6 +633,7 @@
     ssock->write_state.start = ssock->write_state.buf;
     
     /* Init secure socket */
+    ssock->pool = pool;
     ssock->sock_af = param->sock_af;
     ssock->sock_type = param->sock_type;
     ssock->cb = param->cb;
@@ -643,7 +773,10 @@
 
 	/* Remote address */
         pj_sockaddr_cp((pj_sockaddr_t*)&info->remote_addr, 
-    		   (pj_sockaddr_t*)&ssock->rem_addr);
+    		       (pj_sockaddr_t*)&ssock->rem_addr);
+        
+        /* Certificates info */
+        info->remote_cert_info = &ssock->remote_cert_info;
     }
 
     /* Protocol */
@@ -728,14 +861,20 @@
 	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);
-	}
     }
     
+    /* Connection closed or something goes wrong */
     if (status != PJ_SUCCESS && status != PJ_EPENDING) {
-	/* Connection closed or something goes wrong */
+	/* Notify error */
+	if (ssock->cb.on_data_read) {
+	    pj_bool_t ret = (*ssock->cb.on_data_read)(ssock, NULL, 0, 
+						      status, NULL);
+	    if (!ret) {
+		/* We've been destroyed */
+		return;
+	    }
+	}
+	
 	delete ssock->read_state.read_buf;
 	delete ssock->read_state.orig_buf;
 	ssock->read_state.read_buf = NULL;
@@ -1001,6 +1140,7 @@
     status = (err == KErrNone)? PJ_SUCCESS : PJ_RETURN_OS_ERROR(err);
     if (status == PJ_SUCCESS) {
 	ssock->established = PJ_TRUE;
+	update_certs_info(ssock);
     } else {
 	delete ssock->sock;
 	ssock->sock = NULL;
diff --git a/pjlib/src/pjlib-test/ssl_sock.c b/pjlib/src/pjlib-test/ssl_sock.c
index 8302e8f..6e0d451 100644
--- a/pjlib/src/pjlib-test/ssl_sock.c
+++ b/pjlib/src/pjlib-test/ssl_sock.c
@@ -22,7 +22,7 @@
 
 
 #define CERT_DIR		    "../build/"
-#define CERT_CA_FILE		    NULL
+#define CERT_CA_FILE		    CERT_DIR "cacert.pem"
 #define CERT_FILE		    CERT_DIR "cacert.pem"
 #define CERT_PRIVKEY_FILE	    CERT_DIR "privkey.pem"
 #define CERT_PRIVKEY_PASS	    ""
@@ -83,26 +83,40 @@
     struct send_key send_key;	    /* send op key			    */
 };
 
-static void dump_cert_info(const char *prefix, const pj_ssl_cert_info *ci)
+static void dump_ssl_info(const pj_ssl_sock_info *si)
 {
-    const char *wdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
-    pj_parsed_time pt1;
-    pj_parsed_time pt2;
+    const char *tmp_st;
 
-    pj_time_decode(&ci->validity_start, &pt1);
-    pj_time_decode(&ci->validity_end, &pt2);
+    /* Print cipher name */
+    tmp_st = pj_ssl_cipher_name(si->cipher);
+    if (tmp_st == NULL)
+	tmp_st = "[Unknown]";
+    PJ_LOG(3, ("", ".....Cipher: %s", tmp_st));
 
-    PJ_LOG(3, ("", "%sSubject    : %.*s", prefix, ci->subject.slen, ci->subject.ptr));
-    PJ_LOG(3, ("", "%sIssuer     : %.*s", prefix, ci->issuer.slen, ci->issuer.ptr));
-    PJ_LOG(3, ("", "%sVersion    : v%d", prefix, ci->version));
-    PJ_LOG(3, ("", "%sValid from : %s %4d-%02d-%02d %02d:%02d:%02d.%03d %s", 
-		   prefix, wdays[pt1.wday], pt1.year, pt1.mon+1, pt1.day,
-		   pt1.hour, pt1.min, pt1.sec, pt1.msec,
-		   (ci->validity_use_gmt? "GMT":"")));
-    PJ_LOG(3, ("", "%sValid to   : %s %4d-%02d-%02d %02d:%02d:%02d.%03d %s", 
-		   prefix, wdays[pt2.wday], pt2.year, pt2.mon+1, pt2.day,
-		   pt2.hour, pt2.min, pt2.sec, pt2.msec,
-		   (ci->validity_use_gmt? "GMT":"")));
+    /* Print remote certificate info and verification result */
+    if (si->remote_cert_info && si->remote_cert_info->subject.info.slen) 
+    {
+	char buf[2048];
+	const char *verif_msgs[32];
+	unsigned verif_msg_cnt;
+
+	/* Dump remote TLS certificate info */
+	PJ_LOG(3, ("", ".....Remote certificate info:"));
+	pj_ssl_cert_info_dump(si->remote_cert_info, "  ", buf, sizeof(buf));
+	PJ_LOG(3,("", "\n%s", buf));
+
+	/* Dump remote TLS certificate verification result */
+	verif_msg_cnt = PJ_ARRAY_SIZE(verif_msgs);
+	pj_ssl_cert_verify_error_st(si->verify_status,
+				    verif_msgs, &verif_msg_cnt);
+	PJ_LOG(3,("", ".....Remote certificate verification result: %s",
+		  (verif_msg_cnt == 1? verif_msgs[0]:"")));
+	if (verif_msg_cnt > 1) {
+	    unsigned i;
+	    for (i = 0; i < verif_msg_cnt; ++i)
+		PJ_LOG(3,("", "..... - %s", verif_msgs[i]));
+	}
+    }
 }
 
 
@@ -130,25 +144,8 @@
     pj_sockaddr_print((pj_sockaddr_t*)&info.remote_addr, buf2, sizeof(buf2), 1);
     PJ_LOG(3, ("", "...Connected %s -> %s!", buf1, buf2));
 
-    if (st->is_verbose) {
-	const char *tmp_st;
-
-	/* Print cipher name */
-	tmp_st = pj_ssl_cipher_name(info.cipher);
-	if (tmp_st == NULL)
-	    tmp_st = "[Unknown]";
-	PJ_LOG(3, ("", ".....Cipher: %s", tmp_st));
-
-	/* Print certificates info */
-	if (info.local_cert_info.subject.slen) {
-	    PJ_LOG(3, ("", ".....Local certificate info:"));
-	    dump_cert_info(".......", &info.local_cert_info);
-	}
-	if (info.remote_cert_info.subject.slen) {
-	    PJ_LOG(3, ("", ".....Remote certificate info:"));
-	    dump_cert_info(".......", &info.remote_cert_info);
-	}
-    }
+    if (st->is_verbose)
+	dump_ssl_info(&info);
 
     /* Start reading data */
     read_buf[0] = st->read_buf;
@@ -198,6 +195,8 @@
 				   pj_ssl_sock_get_user_data(ssock);
     struct test_state *st;
     void *read_buf[1];
+    pj_ssl_sock_info info;
+    char buf[64];
     pj_status_t status;
 
     PJ_UNUSED_ARG(src_addr_len);
@@ -207,37 +206,18 @@
     *st = *parent_st;
     pj_ssl_sock_set_user_data(newsock, st);
 
-    if (st->is_verbose) {
-	pj_ssl_sock_info info;
-	char buf[64];
-	const char *tmp_st;
-
-	status = pj_ssl_sock_get_info(newsock, &info);
-	if (status != PJ_SUCCESS) {
-	    app_perror("...ERROR pj_ssl_sock_get_info()", status);
-	    goto on_return;
-	}
-
-	pj_sockaddr_print(src_addr, buf, sizeof(buf), 1);
-	PJ_LOG(3, ("", "...Accepted connection from %s", buf));
-
-	/* Print cipher name */
-	tmp_st = pj_ssl_cipher_name(info.cipher);
-	if (tmp_st == NULL)
-	    tmp_st = "[Unknown]";
-	PJ_LOG(3, ("", ".....Cipher: %s", tmp_st));
-
-	/* Print certificates info */
-	if (info.local_cert_info.subject.slen) {
-	    PJ_LOG(3, ("", ".....Local certificate info:"));
-	    dump_cert_info(".......", &info.local_cert_info);
-	}
-	if (info.remote_cert_info.subject.slen) {
-	    PJ_LOG(3, ("", ".....Remote certificate info:"));
-	    dump_cert_info(".......", &info.remote_cert_info);
-	}
+    status = pj_ssl_sock_get_info(newsock, &info);
+    if (status != PJ_SUCCESS) {
+	app_perror("...ERROR pj_ssl_sock_get_info()", status);
+	goto on_return;
     }
 
+    pj_sockaddr_print(src_addr, buf, sizeof(buf), 1);
+    PJ_LOG(3, ("", "...Accepted connection from %s", buf));
+
+    if (st->is_verbose)
+	dump_ssl_info(&info);
+
     /* Start reading data */
     read_buf[0] = st->read_buf;
     status = pj_ssl_sock_start_read2(newsock, st->pool, sizeof(st->read_buf), (void**)read_buf, 0);
@@ -460,6 +440,7 @@
     param.timer_heap = timer;
     param.timeout.sec = 0;
     param.timeout.msec = ms_timeout;
+    param.proto = PJ_SSL_SOCK_PROTO_SSL23;
     pj_time_val_normalize(&param.timeout);
 
     status = pj_ssl_sock_create(pool, &param, &ssock);
@@ -512,7 +493,8 @@
 
 
 static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto,
-		     pj_ssl_cipher srv_cipher, pj_ssl_cipher cli_cipher)
+		     pj_ssl_cipher srv_cipher, pj_ssl_cipher cli_cipher,
+		     pj_bool_t req_client_cert, pj_bool_t client_provide_cert)
 {
     pj_pool_t *pool = NULL;
     pj_ioqueue_t *ioqueue = NULL;
@@ -533,21 +515,6 @@
 	goto on_return;
     }
 
-    /* Set cert */
-    {
-	pj_str_t tmp1, tmp2, tmp3, tmp4;
-
-	status = pj_ssl_cert_load_from_files(pool, 
-					     pj_strset2(&tmp1, (char*)CERT_CA_FILE), 
-					     pj_strset2(&tmp2, (char*)CERT_FILE), 
-					     pj_strset2(&tmp3, (char*)CERT_PRIVKEY_FILE), 
-					     pj_strset2(&tmp4, (char*)CERT_PRIVKEY_PASS), 
-					     &cert);
-	if (status != PJ_SUCCESS) {
-	    goto on_return;
-	}
-    }
-
     pj_ssl_sock_param_default(&param);
     param.cb.on_accept_complete = &ssl_on_accept_complete;
     param.cb.on_connect_complete = &ssl_on_connect_complete;
@@ -562,10 +529,11 @@
 	pj_sockaddr_init(PJ_AF_INET, &addr, pj_strset2(&tmp_st, "127.0.0.1"), 0);
     }
 
-    /* SERVER */
+    /* === SERVER === */
     param.proto = srv_proto;
     param.user_data = &state_serv;
     param.ciphers_num = (srv_cipher == -1)? 0 : 1;
+    param.require_client_cert = req_client_cert;
     ciphers[0] = srv_cipher;
 
     state_serv.pool = pool;
@@ -578,9 +546,24 @@
 	goto on_return;
     }
 
-    status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert);
-    if (status != PJ_SUCCESS) {
-	goto on_return;
+    /* Set server cert */
+    {
+	pj_str_t tmp1, tmp2, tmp3, tmp4;
+
+	status = pj_ssl_cert_load_from_files(pool, 
+					     pj_strset2(&tmp1, (char*)CERT_CA_FILE), 
+					     pj_strset2(&tmp2, (char*)CERT_FILE), 
+					     pj_strset2(&tmp3, (char*)CERT_PRIVKEY_FILE), 
+					     pj_strset2(&tmp4, (char*)CERT_PRIVKEY_PASS), 
+					     &cert);
+	if (status != PJ_SUCCESS) {
+	    goto on_return;
+	}
+
+	status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert);
+	if (status != PJ_SUCCESS) {
+	    goto on_return;
+	}
     }
 
     status = pj_ssl_sock_start_accept(ssock_serv, pool, &addr, pj_sockaddr_get_len(&addr));
@@ -596,7 +579,7 @@
 	pj_sockaddr_cp(&listen_addr, &info.local_addr);
     }
 
-    /* CLIENT */
+    /* === CLIENT === */
     param.proto = cli_proto;
     param.user_data = &state_cli;
     param.ciphers_num = (cli_cipher == -1)? 0 : 1;
@@ -625,6 +608,28 @@
 	goto on_return;
     }
 
+    /* Set cert for client */
+    {
+
+	if (!client_provide_cert) {
+	    pj_str_t tmp1, tmp2;
+
+	    pj_strset2(&tmp1, (char*)CERT_CA_FILE);
+	    pj_strset2(&tmp2, NULL);
+	    status = pj_ssl_cert_load_from_files(pool, 
+						 &tmp1, &tmp2, &tmp2, &tmp2,
+						 &cert);
+	    if (status != PJ_SUCCESS) {
+		goto on_return;
+	    }
+	}
+
+	status = pj_ssl_sock_set_certificate(ssock_cli, pool, cert);
+	if (status != PJ_SUCCESS) {
+	    goto on_return;
+	}
+    }
+
     status = pj_ssl_sock_start_connect(ssock_cli, pool, &addr, &listen_addr, pj_sockaddr_get_len(&addr));
     if (status == PJ_SUCCESS) {
 	ssl_on_connect_complete(ssock_cli, PJ_SUCCESS);
@@ -1013,6 +1018,9 @@
     ssock_cli = pj_pool_calloc(pool, clients, sizeof(pj_ssl_sock_t*));
     state_cli = pj_pool_calloc(pool, clients, sizeof(struct test_state));
 
+    /* Get start timestamp */
+    pj_gettimeofday(&start);
+
     /* Setup clients */
     for (i = 0; i < clients; ++i) {
 	param.user_data = &state_cli[i];
@@ -1064,9 +1072,6 @@
 	}
     }
 
-    /* Get start timestamp */
-    pj_gettimeofday(&start);
-
     /* Wait until everything has been sent/received or error */
     while (clients_num)
     {
@@ -1150,28 +1155,46 @@
 
     PJ_LOG(3,("", "..echo test w/ TLSv1 and TLS_RSA_WITH_DES_CBC_SHA cipher"));
     ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1, PJ_SSL_SOCK_PROTO_TLS1, 
-		    TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA);
+		    TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA, 
+		    PJ_FALSE, PJ_FALSE);
     if (ret != 0)
 	return ret;
 
     PJ_LOG(3,("", "..echo test w/ SSLv23 and TLS_RSA_WITH_AES_256_CBC_SHA cipher"));
     ret = echo_test(PJ_SSL_SOCK_PROTO_SSL23, PJ_SSL_SOCK_PROTO_SSL23, 
-		    TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA);
+		    TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA,
+		    PJ_FALSE, PJ_FALSE);
     if (ret != 0)
 	return ret;
 
     PJ_LOG(3,("", "..echo test w/ incompatible proto"));
     ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1, PJ_SSL_SOCK_PROTO_SSL3, 
-		    TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA);
+		    TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA,
+		    PJ_FALSE, PJ_FALSE);
     if (ret == 0)
 	return PJ_EBUG;
 
     PJ_LOG(3,("", "..echo test w/ incompatible ciphers"));
     ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, 
-		    TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA);
+		    TLS_RSA_WITH_DES_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA,
+		    PJ_FALSE, PJ_FALSE);
     if (ret == 0)
 	return PJ_EBUG;
 
+    PJ_LOG(3,("", "..echo test w/ client cert required but not provided"));
+    ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, 
+		    TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA,
+		    PJ_TRUE, PJ_FALSE);
+    if (ret == 0)
+	return PJ_EBUG;
+
+    PJ_LOG(3,("", "..echo test w/ client cert required and provided"));
+    ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, 
+		    TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA,
+		    PJ_TRUE, PJ_TRUE);
+    if (ret != 0)
+	return ret;
+
     PJ_LOG(3,("", "..client non-SSL (handshake timeout 5 secs)"));
     ret = client_non_ssl(5000);
     if (ret != 0)
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index 42ce012..d3cb8c8 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -2788,6 +2788,85 @@
 
 
 /*
+ * Transport status notification
+ */
+static pj_bool_t on_transport_state(pjsip_transport *tp, pj_uint32_t state,
+				    const pjsip_transport_state_info *info)
+{
+    char host_port[128];
+
+    pj_ansi_snprintf(host_port, sizeof(host_port), "[%.*s:%d]",
+		     (int)tp->remote_name.host.slen,
+		     tp->remote_name.host.ptr,
+		     tp->remote_name.port);
+
+    if (state & PJSIP_TP_STATE_CONNECTED) {
+	PJ_LOG(3,(THIS_FILE, "SIP transport %s is connected to %s", 
+		 tp->type_name, host_port));
+    } 
+    else if (state & PJSIP_TP_STATE_ACCEPTED) {
+	PJ_LOG(3,(THIS_FILE, "SIP transport %s accepted %s",
+		 tp->type_name, host_port));
+    } 
+    else if (state & PJSIP_TP_STATE_DISCONNECTED) {
+	char buf[100];
+
+	snprintf(buf, sizeof(buf), "SIP transport %s is disconnected from %s",
+		 tp->type_name, host_port);
+	pjsua_perror(THIS_FILE, buf, info->status);
+    }
+    else if (state & PJSIP_TP_STATE_REJECTED) {
+	char buf[100];
+
+	snprintf(buf, sizeof(buf), "SIP transport %s rejected %s",
+		 tp->type_name, host_port);
+	pjsua_perror(THIS_FILE, buf, info->status);
+    }
+
+#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
+
+    if (!pj_ansi_stricmp(tp->type_name, "tls") && info->ext_info &&
+	(state == PJSIP_TP_STATE_CONNECTED || 
+	 (state & PJSIP_TP_STATE_TLS_VERIF_ERROR)))
+    {
+	pjsip_tls_state_info *tls_info = (pjsip_tls_state_info*)info->ext_info;
+	pj_ssl_sock_info *ssl_sock_info = (pj_ssl_sock_info*)
+					  tls_info->ssl_sock_info;
+	char buf[2048];
+	const char *verif_msgs[32];
+	unsigned verif_msg_cnt;
+
+	/* Dump server TLS certificate */
+	pj_ssl_cert_info_dump(ssl_sock_info->remote_cert_info, "  ",
+			      buf, sizeof(buf));
+	PJ_LOG(4,(THIS_FILE, "TLS cert info of %s:\n%s", host_port, buf));
+
+	/* Dump server TLS certificate verification result */
+	verif_msg_cnt = PJ_ARRAY_SIZE(verif_msgs);
+	pj_ssl_cert_verify_error_st(ssl_sock_info->verify_status,
+				    verif_msgs, &verif_msg_cnt);
+	PJ_LOG(3,(THIS_FILE, "TLS cert verification result of %s : %s",
+			     host_port,
+			     (verif_msg_cnt == 1? verif_msgs[0]:"")));
+	if (verif_msg_cnt > 1) {
+	    unsigned i;
+	    for (i = 0; i < verif_msg_cnt; ++i)
+		PJ_LOG(3,(THIS_FILE, "- %s", verif_msgs[i]));
+	}
+
+	if (state & PJSIP_TP_STATE_TLS_VERIF_ERROR && 
+	    !app_config.udp_cfg.tls_setting.verify_server) 
+	{
+	    PJ_LOG(3,(THIS_FILE, "PJSUA is configured to ignore TLS cert "
+				 "verification errors"));
+	}
+    }
+
+#endif
+    return PJ_TRUE;
+}
+
+/*
  * Print buddy list.
  */
 static void print_buddy_list(void)
@@ -4383,6 +4462,7 @@
     app_config.cfg.cb.on_call_replaced = &on_call_replaced;
     app_config.cfg.cb.on_nat_detect = &on_nat_detect;
     app_config.cfg.cb.on_mwi_info = &on_mwi_info;
+    app_config.cfg.cb.on_transport_state = &on_transport_state;
 
     /* Set sound device latency */
     if (app_config.capture_lat > 0)
diff --git a/pjsip-apps/src/symbian_ua/ua.cpp b/pjsip-apps/src/symbian_ua/ua.cpp
index 64e49ae..620e75a 100644
--- a/pjsip-apps/src/symbian_ua/ua.cpp
+++ b/pjsip-apps/src/symbian_ua/ua.cpp
@@ -270,6 +270,63 @@
 			 (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
 }
 
+/*
+ * Transport status notification
+ */
+static pj_bool_t on_transport_state(pjsip_transport *tp, pj_uint32_t state,
+				    const pjsip_transport_state_info *info)
+{
+    char host_port[128];
+
+    pj_ansi_snprintf(host_port, sizeof(host_port), "[%.*s:%d]",
+		     (int)tp->remote_name.host.slen,
+		     tp->remote_name.host.ptr,
+		     tp->remote_name.port);
+
+    if (state & PJSIP_TP_STATE_CONNECTED) {
+	PJ_LOG(3,(THIS_FILE, "SIP transport %s is connected to %s", 
+		 tp->type_name, host_port));
+    } 
+    else if (state & PJSIP_TP_STATE_ACCEPTED) {
+	PJ_LOG(3,(THIS_FILE, "SIP transport %s accepted %s",
+		 tp->type_name, host_port));
+    } 
+    else if (state & PJSIP_TP_STATE_DISCONNECTED) {
+	char buf[100];
+
+	snprintf(buf, sizeof(buf), "SIP transport %s is disconnected from %s",
+		 tp->type_name, host_port);
+	pjsua_perror(THIS_FILE, buf, info->status);
+    }
+    else if (state & PJSIP_TP_STATE_REJECTED) {
+	char buf[100];
+
+	snprintf(buf, sizeof(buf), "SIP transport %s rejected %s",
+		 tp->type_name, host_port);
+	pjsua_perror(THIS_FILE, buf, info->status);
+    }
+
+#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
+
+    if (!pj_ansi_stricmp(tp->type_name, "tls") && info->ext_info &&
+	(state == PJSIP_TP_STATE_CONNECTED || 
+	 (state & PJSIP_TP_STATE_TLS_VERIF_ERROR)))
+    {
+	pjsip_tls_state_info *tls_info = (pjsip_tls_state_info*)info->ext_info;
+	pj_ssl_sock_info *ssl_sock_info = (pj_ssl_sock_info*)
+					  tls_info->ssl_sock_info;
+	char buf[2048];
+
+	/* Dump server TLS certificate */
+	pj_ssl_cert_info_dump(ssl_sock_info->remote_cert_info, "  ",
+			      buf, sizeof(buf));
+	PJ_LOG(4,(THIS_FILE, "TLS cert info of %s:\n%s", host_port, buf));
+    }
+
+#endif
+    return PJ_TRUE;
+}
+
 
 //#include<e32debug.h>
 
@@ -330,6 +387,7 @@
     cfg.cb.on_call_transfer_status = &on_call_transfer_status;
     cfg.cb.on_call_replaced = &on_call_replaced;
     cfg.cb.on_nat_detect = &on_nat_detect;
+    cfg.cb.on_transport_state = &on_transport_state;
     
     if (SIP_PROXY) {
 	    cfg.outbound_proxy_cnt = 1;
@@ -1054,7 +1112,7 @@
     SelectIAP();
     
     // Initialize RSocketServ
-    if ((err=aSocketServer.Connect()) != KErrNone)
+    if ((err=aSocketServer.Connect(32)) != KErrNone)
     	return PJ_STATUS_FROM_OS(err);
     
     // Open up a connection
diff --git a/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp b/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
index f5a7931..0f964ac 100644
--- a/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
+++ b/pjsip-apps/src/symbian_ua_gui/group/symbian_ua_gui.mmp
@@ -41,6 +41,7 @@
 LIBRARY			etext.lib gdi.lib egul.lib insock.lib

 LIBRARY			ecom.lib inetprotutil.lib http.lib esock.lib

 LIBRARY			charconv.lib estlib.lib 

+LIBRARY			securesocket.lib x509.lib crypto.lib x500.lib

 

 // Ordering static libs based on dependencies, most to least dependent,

 // this could be necessary for some SDKs, e.g: S60 3rd MR

diff --git a/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp b/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp
index f722336..bf3f7a3 100644
--- a/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp
+++ b/pjsip-apps/src/symbian_ua_gui/src/symbian_ua.cpp
@@ -263,7 +263,7 @@
     pj_status_t status;

     

     // Initialize RSocketServ

-    if ((err=aSocketServer.Connect()) != KErrNone)

+    if ((err=aSocketServer.Connect(32)) != KErrNone)

     	return PJ_STATUS_FROM_OS(err);

     

     // Open up a connection

diff --git a/pjsip/include/pjsip/sip_endpoint.h b/pjsip/include/pjsip/sip_endpoint.h
index 1938e9d..2abcf4a 100644
--- a/pjsip/include/pjsip/sip_endpoint.h
+++ b/pjsip/include/pjsip/sip_endpoint.h
@@ -372,6 +372,35 @@
 			       pjsip_transport **p_tp);
 
 
+/**
+ * Find a SIP transport suitable for sending SIP message to the specified
+ * address by also considering the outgoing SIP message data. If transport 
+ * selector ("sel") is set, then the function will check if the transport 
+ * selected is suitable to send requests to the specified address.
+ *
+ * @see pjsip_tpmgr_acquire_transport
+ *
+ * @param endpt	    The SIP endpoint instance.
+ * @param type	    The type of transport to be acquired.
+ * @param remote    The remote address to send message to.
+ * @param addr_len  Length of the remote address.
+ * @param sel	    Optional pointer to transport selector instance which is
+ *		    used to find explicit transport, if required.
+ * @param tdata	    Optional pointer to SIP message data to be sent.
+ * @param p_tp	    Pointer to receive the transport instance, if one is found.
+ *
+ * @return	    PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) 
+pjsip_endpt_acquire_transport2(pjsip_endpoint *endpt,
+			       pjsip_transport_type_e type,
+			       const pj_sockaddr_t *remote,
+			       int addr_len,
+			       const pjsip_tpselector *sel,
+			       pjsip_tx_data *tdata,
+			       pjsip_transport **p_tp);
+
+
 /*****************************************************************************
  *
  * Capabilities Management
diff --git a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h
index 6592fc9..9f6534c 100644
--- a/pjsip/include/pjsip/sip_transport.h
+++ b/pjsip/include/pjsip/sip_transport.h
@@ -540,6 +540,10 @@
      */
     struct
     {
+	/** Server name. 
+	 */
+	pj_str_t		 name;
+
 	/** Server addresses resolved. 
 	 */
 	pjsip_server_addresses   addr;
@@ -689,6 +693,11 @@
     long		    type;
 
     /**
+     * Hash of host name.
+     */
+    pj_uint32_t		    hname;
+
+    /**
      * Destination address.
      */
     pj_sockaddr		    rem_addr;
@@ -918,7 +927,8 @@
     pjsip_host_port	    addr_name;	    /**< Published name.	*/
 
     /**
-     * Create new outbound connection.
+     * Create new outbound connection suitable for sending SIP message
+     * to specified remote address.
      * Note that the factory is responsible for both creating the
      * transport and registering it to the transport manager.
      */
@@ -930,6 +940,21 @@
 				    pjsip_transport **transport);
 
     /**
+     * Create new outbound connection suitable for sending SIP message
+     * to specified remote address by also considering outgoing SIP 
+     * message data.
+     * Note that the factory is responsible for both creating the
+     * transport and registering it to the transport manager.
+     */
+    pj_status_t (*create_transport2)(pjsip_tpfactory *factory,
+				     pjsip_tpmgr *mgr,
+				     pjsip_endpoint *endpt,
+				     const pj_sockaddr *rem_addr,
+				     int addr_len,
+				     pjsip_tx_data *tdata,
+				     pjsip_transport **transport);
+
+    /**
      * Destroy the listener.
      */
     pj_status_t (*destroy)(pjsip_tpfactory *factory);
@@ -1100,6 +1125,34 @@
 						   pjsip_transport **tp);
 
 /**
+ * Find suitable transport for sending SIP message to specified remote 
+ * destination by also considering the outgoing SIP message. If no suitable 
+ * transport is found, a new one will be created.
+ *
+ * This is an internal function since normally application doesn't have access
+ * to transport manager. Application should use pjsip_endpt_acquire_transport()
+ * instead.
+ *
+ * @param mgr	    The transport manager instance.
+ * @param type	    The type of transport to be acquired.
+ * @param remote    The remote address to send message to.
+ * @param addr_len  Length of the remote address.
+ * @param sel	    Optional pointer to transport selector instance which is
+ *		    used to find explicit transport, if required.
+ * @param tdata	    Optional pointer to data to be sent.
+ * @param tp	    Pointer to receive the transport instance, if one is found.
+ *
+ * @return	    PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
+						    pjsip_transport_type_e type,
+						    const pj_sockaddr_t *remote,
+						    int addr_len,
+						    const pjsip_tpselector *sel,
+						    pjsip_tx_data *tdata,
+						    pjsip_transport **tp);
+
+/**
  * Type of callback to receive notification when message or raw data
  * has been sent.
  *
@@ -1187,6 +1240,94 @@
 					  void *token,
 					  pjsip_tp_send_callback cb);
 
+
+/**
+ * Enumeration of transport state types.
+ */
+typedef enum pjsip_transport_state_type {
+
+    /** Transport connected.	*/
+    PJSIP_TP_STATE_CONNECTED	    = (1 << 0),
+
+    /** Transport accepted.	*/
+    PJSIP_TP_STATE_ACCEPTED	    = (1 << 1),
+
+    /** Transport disconnected.	*/
+    PJSIP_TP_STATE_DISCONNECTED	    = (1 << 2),
+
+    /** Incoming connection rejected.	*/
+    PJSIP_TP_STATE_REJECTED	    = (1 << 3),
+
+    /** TLS verification error.	*/
+    PJSIP_TP_STATE_TLS_VERIF_ERROR  = (1 << 8)
+
+} pjsip_transport_state_type;
+
+
+/**
+ * Structure of transport state info.
+ */
+typedef struct pjsip_transport_state_info {
+    /**
+     * The last error code related to the transport state.
+     */
+    pj_status_t		 status;
+    
+    /**
+     * Optional extended info, the content is specific for each transport type.
+     */
+    void		*ext_info;
+} pjsip_transport_state_info;
+
+
+/**
+ * Type of callback to receive transport state notifications, such as
+ * transport connected, disconnected or TLS verification error.
+ *
+ * @param tp		The transport instance.
+ * @param state		The transport state, this may contain single or 
+ *			combination of transport state types defined in
+ *			#pjsip_transport_state_type.
+ * @param info		The transport state info.
+ *
+ * @return		When TLS verification fails and peer verification in
+ *			#pjsip_tls_setting is not set, application may return
+ *			PJ_TRUE to ignore the verification result and continue
+ *			using the transport. On other cases, this return value
+ *			is currently not used and will be ignored.
+ */
+typedef pj_bool_t (*pjsip_tp_state_callback)(
+				    pjsip_transport *tp,
+				    pj_uint32_t state,
+				    const pjsip_transport_state_info *info);
+
+
+/**
+ * Setting callback of transport state notification. The caller will be
+ * notified whenever the state of transport is changed. The type of
+ * events are defined in #pjsip_transport_state_type.
+ * 
+ * @param mgr	    Transport manager.
+ * @param cb	    Callback to be called to notify caller about transport 
+ *		    status changing.
+ *
+ * @return	    PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsip_tpmgr_set_status_cb(pjsip_tpmgr *mgr,
+					       pjsip_tp_state_callback *cb);
+
+
+/**
+ * Getting the callback of transport state notification.
+ * 
+ * @param mgr	    Transport manager.
+ *
+ * @return	    The transport state callback or NULL if it is not set.
+ */
+PJ_DECL(pjsip_tp_state_callback*) pjsip_tpmgr_get_status_cb(
+					       const pjsip_tpmgr *mgr);
+
+
 /**
  * @}
  */
diff --git a/pjsip/include/pjsip/sip_transport_tls.h b/pjsip/include/pjsip/sip_transport_tls.h
index 8c41e16..f97414b 100644
--- a/pjsip/include/pjsip/sip_transport_tls.h
+++ b/pjsip/include/pjsip/sip_transport_tls.h
@@ -26,6 +26,7 @@
  */
 
 #include <pjsip/sip_transport.h>
+#include <pj/ssl_sock.h>
 #include <pj/string.h>
 #include <pj/sock_qos.h>
 
@@ -121,27 +122,44 @@
     pj_str_t	server_name;
 
     /**
-     * When PJSIP is acting as a client (outgoing TLS connections), 
-     * it will always receive a certificate from the peer. 
-     * If \a verify_server is disabled (set to zero), PJSIP will not 
-     * verifiy the certificate and allows TLS connections to servers 
-     * which do not present a valid certificate. 
-     * If \a tls_verify_server is non-zero, PJSIP verifies the server 
-     * certificate and will close the TLS connection if the server 
-     * certificate is not valid.
+     * Specifies the action when verification of server TLS certificate
+     * resulting errors:
+     * - If \a verify_server is disabled (set to PJ_FALSE), TLS transport 
+     *   will just notify the application via #pjsip_tp_state_callback with
+     *   state (PJSIP_TP_STATE_CONNECTED | PJSIP_TP_STATE_TLS_VERIF_ERROR)
+     *   whenever there is any TLS verification error, the return value of 
+     *   the callback will be used to decide whether transport should be 
+     *   shutdown.
+     * - If \a verify_server is enabled (set to PJ_TRUE), TLS transport 
+     *   will be shutdown and application will be notified with state
+     *   (PJSIP_TP_STATE_DISCONNECTED | PJSIP_TP_STATE_TLS_VERIF_ERROR)
+     *   whenever there is any TLS verification error.
      *
-     * This setting corresponds to OpenSSL SSL_VERIFY_PEER flag.
-     * Default value is zero.
+     * When the verification resulting success, application will be notified
+     * via #pjsip_tp_state_callback with state PJSIP_TP_STATE_CONNECTED.
+     *
+     * Default value is PJ_FALSE.
      */
     pj_bool_t	verify_server;
 
     /**
-     * When acting as server (incoming TLS connections), setting
-     * \a verify_client to non-zero will cause the transport to activate
-     * peer verification upon receiving incoming TLS connection.
+     * Specifies the action when verification of server TLS certificate
+     * resulting errors:
+     * - If \a verify_client is disabled (set to PJ_FALSE), TLS transport 
+     *   will just notify the application via #pjsip_tp_state_callback with
+     *   state (PJSIP_TP_STATE_ACCEPTED | PJSIP_TP_STATE_TLS_VERIF_ERROR)
+     *   whenever there is any TLS verification error, the return value of 
+     *   the callback will be used to decide whether transport should be 
+     *   shutdown.
+     * - If \a verify_client is enabled (set to PJ_TRUE), TLS transport 
+     *   will be shutdown and application will be notified with state
+     *   (PJSIP_TP_STATE_REJECTED | PJSIP_TP_STATE_TLS_VERIF_ERROR)
+     *   whenever there is any TLS verification error.
      *
-     * This setting corresponds to OpenSSL SSL_VERIFY_PEER flag.
-     * Default value is zero.
+     * When the verification resulting success, application will be notified
+     * via #pjsip_tp_state_callback with state PJSIP_TP_STATE_ACCEPTED.
+     *
+     * Default value is PJ_FALSE.
      */
     pj_bool_t	verify_client;
 
@@ -150,7 +168,7 @@
      * connection if client doesn't have a valid certificate.
      *
      * This setting corresponds to SSL_VERIFY_FAIL_IF_NO_PEER_CERT flag.
-     * Default value is zero.
+     * Default value is PJ_FALSE.
      */
     pj_bool_t	require_client_cert;
 
@@ -191,6 +209,20 @@
 
 
 /**
+ * This structure defines transport state extended info specifically for
+ * TLS transport.
+ */
+typedef struct pjsip_tls_state_info
+{
+    /**
+     * SSL socket info.
+     */
+    pj_ssl_sock_info	*ssl_sock_info;
+
+} pjsip_tls_state_info;
+
+
+/**
  * Initialize TLS setting with default values.
  *
  * @param tls_opt   The TLS setting to be initialized.
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index ecafb25..8d4d0a9 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -843,6 +843,25 @@
      */
     void (*on_mwi_info)(pjsua_acc_id acc_id, pjsua_mwi_info *mwi_info);
 
+    /**
+     * This callback is called when transport state is changed. See also
+     * #pjsip_tp_state_callback.
+     *
+     * @param tp	The transport instance.
+     * @param state	The transport state, this may contain single or 
+     *			combination of transport state types defined in
+     *			#pjsip_transport_state_type.
+     * @param info	The transport state info.
+     *
+     * @return		When TLS verification fails and peer verification in
+     *			#pjsip_tls_setting is not set, application may return
+     *			PJ_TRUE to ignore the verification result and continue
+     *			using the transport. On other cases, this return value
+     *			is currently not used and will be ignored.
+     */
+    pj_bool_t (*on_transport_state)(pjsip_transport *tp, pj_uint32_t state,
+				    const pjsip_transport_state_info *info);
+
 } pjsua_callback;
 
 
diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c
index a686182..e0fe4f0 100644
--- a/pjsip/src/pjsip/sip_endpoint.c
+++ b/pjsip/src/pjsip/sip_endpoint.c
@@ -1075,6 +1075,22 @@
 
 
 /*
+ * Find/create transport.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport2(pjsip_endpoint *endpt,
+						   pjsip_transport_type_e type,
+						   const pj_sockaddr_t *remote,
+						   int addr_len,
+						   const pjsip_tpselector *sel,
+						   pjsip_tx_data *tdata,
+						   pjsip_transport **transport)
+{
+    return pjsip_tpmgr_acquire_transport2(endpt->transport_mgr, type, remote, 
+					  addr_len, sel, tdata, transport);
+}
+
+
+/*
  * Report error.
  */
 PJ_DEF(void) pjsip_endpt_log_error(  pjsip_endpoint *endpt,
diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c
index f5d7ee5..92fbaf4 100644
--- a/pjsip/src/pjsip/sip_transport.c
+++ b/pjsip/src/pjsip/sip_transport.c
@@ -89,6 +89,8 @@
 #endif
     void           (*on_rx_msg)(pjsip_endpoint*, pj_status_t, pjsip_rx_data*);
     pj_status_t	   (*on_tx_msg)(pjsip_endpoint*, pjsip_tx_data*);
+    pjsip_tp_state_callback *tp_state_cb;
+    void *tp_state_user_data;
 };
 
 
@@ -864,7 +866,7 @@
     /* 
      * Register to hash table (see Trac ticket #42).
      */
-    key_len = sizeof(tp->key.type) + tp->addr_len;
+    key_len = sizeof(tp->key.type) + sizeof(tp->key.hname) + tp->addr_len;
     pj_lock_acquire(mgr->lock);
 
     /* If entry already occupied, unregister previous entry */
@@ -914,7 +916,7 @@
     /*
      * Unregister from hash table (see Trac ticket #42).
      */
-    key_len = sizeof(tp->key.type) + tp->addr_len;
+    key_len = sizeof(tp->key.type) + sizeof(tp->key.hname) + tp->addr_len;
     hval = 0;
     entry = pj_hash_get(mgr->table, &tp->key, key_len, &hval);
     if (entry == (void*)tp)
@@ -1502,6 +1504,24 @@
 						  const pjsip_tpselector *sel,
 						  pjsip_transport **tp)
 {
+    return pjsip_tpmgr_acquire_transport2(mgr, type, remote, addr_len, sel,
+					  NULL, tp);
+}
+
+/*
+ * pjsip_tpmgr_acquire_transport2()
+ *
+ * Get transport suitable to communicate to remote. Create a new one
+ * if necessary.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
+						   pjsip_transport_type_e type,
+						   const pj_sockaddr_t *remote,
+						   int addr_len,
+						   const pjsip_tpselector *sel,
+						   pjsip_tx_data *tdata,
+						   pjsip_transport **tp)
+{
     pjsip_tpfactory *factory;
     pj_status_t status;
 
@@ -1571,19 +1591,43 @@
 	int key_len;
 	pjsip_transport *transport;
 
+	/*
+	 * Find factory that can create such transport.
+	 */
+	factory = mgr->factory_list.next;
+	while (factory != &mgr->factory_list) {
+	    if (factory->type == type)
+		break;
+	    factory = factory->next;
+	}
+	if (factory == &mgr->factory_list)
+	    factory = NULL;
+
 	pj_bzero(&key, sizeof(key));
-	key_len = sizeof(key.type) + addr_len;
+	key_len = sizeof(key.type) + sizeof(key.hname) + addr_len;
 
 	/* First try to get exact destination. */
 	key.type = type;
 	pj_memcpy(&key.rem_addr, remote, addr_len);
+	if (factory && factory->create_transport2 && 
+	    tdata && tdata->dest_info.name.slen)
+	{
+	    /* Only include hostname hash in the key when the factory support
+	     * create_transport2() and tdata is supplied.
+	     */
+	    key.hname = pj_hash_calc_tolower(0, 
+				    (char*)tdata->dest_info.name.ptr,
+				    &tdata->dest_info.name);
+	}
 
 	transport = (pjsip_transport*)
 		    pj_hash_get(mgr->table, &key, key_len, NULL);
+
 	if (transport == NULL) {
 	    unsigned flag = pjsip_transport_get_flag_from_type(type);
 	    const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote;
 
+
 	    /* Ignore address for loop transports. */
 	    if (type == PJSIP_TRANSPORT_LOOP ||
 		     type == PJSIP_TRANSPORT_LOOP_DGRAM)
@@ -1591,7 +1635,7 @@
 		pj_sockaddr *addr = &key.rem_addr;
 
 		pj_bzero(addr, addr_len);
-		key_len = sizeof(key.type) + addr_len;
+		key_len = sizeof(key.type) + sizeof(key.hname) + addr_len;
 		transport = (pjsip_transport*) 
 			    pj_hash_get(mgr->table, &key, key_len, NULL);
 	    }
@@ -1604,7 +1648,7 @@
 		pj_bzero(addr, addr_len);
 		addr->addr.sa_family = remote_addr->addr.sa_family;
 
-		key_len = sizeof(key.type) + addr_len;
+		key_len = sizeof(key.type) + sizeof(key.hname) + addr_len;
 		transport = (pjsip_transport*)
 			    pj_hash_get(mgr->table, &key, key_len, NULL);
 	    }
@@ -1624,31 +1668,28 @@
 
 	/*
 	 * Transport not found!
-	 * Find factory that can create such transport.
 	 */
-	factory = mgr->factory_list.next;
-	while (factory != &mgr->factory_list) {
-	    if (factory->type == type)
-		break;
-	    factory = factory->next;
-	}
-
-	if (factory == &mgr->factory_list) {
+	if (NULL == factory) {
 	    /* No factory can create the transport! */
 	    pj_lock_release(mgr->lock);
 	    TRACE_((THIS_FILE, "No suitable factory was found either"));
 	    return PJSIP_EUNSUPTRANSPORT;
 	}
-
     }
 
     
     TRACE_((THIS_FILE, "Creating new transport from factory"));
 
     /* Request factory to create transport. */
-    status = factory->create_transport(factory, mgr, mgr->endpt,
-				       (const pj_sockaddr*) remote, addr_len,
-				       tp);
+    if (factory->create_transport2) {
+	status = factory->create_transport2(factory, mgr, mgr->endpt,
+					    (const pj_sockaddr*) remote, 
+					    addr_len, tdata, tp);
+    } else {
+	status = factory->create_transport(factory, mgr, mgr->endpt,
+					   (const pj_sockaddr*) remote, 
+					   addr_len, tp);
+    }
     if (status == PJ_SUCCESS) {
 	PJ_ASSERT_ON_FAIL(tp!=NULL, 
 	    {pj_lock_release(mgr->lock); return PJ_EBUG;});
@@ -1711,3 +1752,20 @@
 #endif
 }
 
+PJ_DEF(pj_status_t) pjsip_tpmgr_set_status_cb(pjsip_tpmgr *mgr,
+					      pjsip_tp_state_callback *cb)
+{
+    PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+    mgr->tp_state_cb = cb;
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pjsip_tp_state_callback*) pjsip_tpmgr_get_status_cb(
+					      const pjsip_tpmgr *mgr)
+{
+    PJ_ASSERT_RETURN(mgr, NULL);
+
+    return mgr->tp_state_cb;
+}
diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c
index 56a4fe8..a7c7eeb 100644
--- a/pjsip/src/pjsip/sip_transport_tcp.c
+++ b/pjsip/src/pjsip/sip_transport_tcp.c
@@ -166,6 +166,36 @@
     host_port->port = pj_sockaddr_get_port(addr);
 }
 
+
+static void tcp_init_shutdown(struct tcp_transport *tcp, pj_status_t status)
+{
+    pjsip_tp_state_callback *state_cb;
+
+    if (tcp->close_reason == PJ_SUCCESS)
+	tcp->close_reason = status;
+
+    if (tcp->base.is_shutdown)
+	return;
+
+    /* Notify application of transport disconnected state */
+    state_cb = pjsip_tpmgr_get_status_cb(tcp->base.tpmgr);
+    if (state_cb) {
+	pjsip_transport_state_info state_info;
+
+	pj_bzero(&state_info, sizeof(state_info));
+	state_info.status = tcp->close_reason;
+	(*state_cb)(&tcp->base, PJSIP_TP_STATE_DISCONNECTED, &state_info);
+    }
+
+    /* We can not destroy the transport since high level objects may
+     * still keep reference to this transport. So we can only 
+     * instruct transport manager to gracefully start the shutdown
+     * procedure for this transport.
+     */
+    pjsip_transport_shutdown(&tcp->base);
+}
+
+
 /*
  * Initialize pjsip_tcp_transport_cfg structure with default values.
  */
@@ -921,6 +951,7 @@
     struct tcp_listener *listener;
     struct tcp_transport *tcp;
     char addr[PJ_INET6_ADDRSTRLEN+10];
+    pjsip_tp_state_callback *state_cb;
     pj_status_t status;
 
     PJ_UNUSED_ARG(src_addr_len);
@@ -966,6 +997,15 @@
 		tcp->ka_timer.id = PJ_TRUE;
 		pj_gettimeofday(&tcp->last_activity);
 	    }
+
+	    /* Notify application of transport state accepted */
+	    state_cb = pjsip_tpmgr_get_status_cb(tcp->base.tpmgr);
+	    if (state_cb) {
+		pjsip_transport_state_info state_info;
+            
+		pj_bzero(&state_info, sizeof(state_info));
+		(*state_cb)(&tcp->base, PJSIP_TP_STATE_ACCEPTED, &state_info);
+	    }
 	}
     }
 
@@ -1013,8 +1053,8 @@
 
 	status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) :
 				     -bytes_sent;
-	if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status;
-	pjsip_transport_shutdown(&tcp->base);
+
+	tcp_init_shutdown(tcp, status);
 
 	return PJ_FALSE;
     }
@@ -1109,8 +1149,8 @@
 
 		if (status == PJ_SUCCESS) 
 		    status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
-		if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status;
-		pjsip_transport_shutdown(&tcp->base);
+
+		tcp_init_shutdown(tcp, status);
 	    }
 	}
     }
@@ -1199,14 +1239,7 @@
 	/* Transport is closed */
 	PJ_LOG(4,(tcp->base.obj_name, "TCP 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 (tcp->close_reason==PJ_SUCCESS) 
-	    tcp->close_reason = status;
-	pjsip_transport_shutdown(&tcp->base);
+	tcp_init_shutdown(tcp, status);
 
 	return PJ_FALSE;
 
@@ -1229,6 +1262,8 @@
     pj_sockaddr_in addr;
     int addrlen;
 
+    pjsip_tp_state_callback *state_cb;
+
     tcp = (struct tcp_transport*) pj_activesock_get_user_data(asock);
 
     /* Mark that pending connect() operation has completed. */
@@ -1252,14 +1287,7 @@
 	    on_data_sent(tcp->asock, 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 (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status;
-	pjsip_transport_shutdown(&tcp->base);
-	return PJ_FALSE;
+	tcp_init_shutdown(tcp, status);
     }
 
     PJ_LOG(4,(tcp->base.obj_name, 
@@ -1293,16 +1321,19 @@
     /* Start pending read */
     status = tcp_start_read(tcp);
     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 (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status;
-	pjsip_transport_shutdown(&tcp->base);
+	tcp_init_shutdown(tcp, status);
 	return PJ_FALSE;
     }
 
+    /* Notify application of transport state connected */
+    state_cb = pjsip_tpmgr_get_status_cb(tcp->base.tpmgr);
+    if (state_cb) {
+	pjsip_transport_state_info state_info;
+    
+	pj_bzero(&state_info, sizeof(state_info));
+	(*state_cb)(&tcp->base, PJSIP_TP_STATE_CONNECTED, &state_info);
+    }
+
     /* Flush all pending send operations */
     tcp_flush_pending_tx(tcp);
 
@@ -1358,7 +1389,7 @@
     if (status != PJ_SUCCESS && status != PJ_EPENDING) {
 	tcp_perror(tcp->base.obj_name, 
 		   "Error sending keep-alive packet", status);
-	pjsip_transport_shutdown(&tcp->base);
+	tcp_init_shutdown(tcp, status);
 	return;
     }
 
diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c
index ab96ecd..a135c43 100644
--- a/pjsip/src/pjsip/sip_transport_tls.c
+++ b/pjsip/src/pjsip/sip_transport_tls.c
@@ -24,6 +24,7 @@
 #include <pj/addr_resolv.h>
 #include <pj/ssl_sock.h>
 #include <pj/assert.h>
+#include <pj/hash.h>
 #include <pj/lock.h>
 #include <pj/log.h>
 #include <pj/os.h>
@@ -80,12 +81,14 @@
 {
     pjsip_transport	     base;
     pj_bool_t		     is_server;
+    pj_str_t		     remote_name;
 
     pj_bool_t		     is_registered;
     pj_bool_t		     is_closing;
     pj_status_t		     close_reason;
     pj_ssl_sock_t	    *ssock;
     pj_bool_t		     has_pending_connect;
+    pj_bool_t		     verify_server;
 
     /* Keep-alive timer. */
     pj_timer_entry	     ka_timer;
@@ -135,6 +138,7 @@
 					pjsip_endpoint *endpt,
 					const pj_sockaddr *rem_addr,
 					int addr_len,
+					pjsip_tx_data *tdata,
 					pjsip_transport **transport);
 
 /* Common function to create and initialize transport */
@@ -144,6 +148,7 @@
 			      pj_bool_t is_server,
 			      const pj_sockaddr_in *local,
 			      const pj_sockaddr_in *remote,
+			      const pj_str_t *remote_name,
 			      struct tls_transport **p_tls);
 
 
@@ -169,6 +174,34 @@
 }
 
 
+static void tls_init_shutdown(struct tls_transport *tls, pj_status_t status)
+{
+    pjsip_tp_state_callback *state_cb;
+
+    if (tls->close_reason == PJ_SUCCESS)
+	tls->close_reason = status;
+
+    if (tls->base.is_shutdown)
+	return;
+
+    /* Notify application of transport disconnected state */
+    state_cb = pjsip_tpmgr_get_status_cb(tls->base.tpmgr);
+    if (state_cb) {
+	pjsip_transport_state_info state_info;
+
+	pj_bzero(&state_info, sizeof(state_info));
+	state_info.status = tls->close_reason;
+	(*state_cb)(&tls->base, PJSIP_TP_STATE_DISCONNECTED, &state_info);
+    }
+
+    /* We can not destroy the transport since high level objects may
+     * still keep reference to this transport. So we can only 
+     * instruct transport manager to gracefully start the shutdown
+     * procedure for this transport.
+     */
+    pjsip_transport_shutdown(&tls->base);
+}
+
 
 /****************************************************************************
  * The TLS listener/transport factory.
@@ -243,10 +276,10 @@
     ssock_param.async_cnt = async_cnt;
     ssock_param.ioqueue = pjsip_endpt_get_ioqueue(endpt);
     ssock_param.require_client_cert = listener->tls_setting.require_client_cert;
-    ssock_param.server_name = 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;
+    ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket
+					 * due to verification error */
     if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN)
 	ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN;
     if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN)
@@ -371,7 +404,7 @@
     /* Register to transport manager */
     listener->endpt = endpt;
     listener->tpmgr = pjsip_endpt_get_tpmgr(endpt);
-    listener->factory.create_transport = lis_create_transport;
+    listener->factory.create_transport2 = lis_create_transport;
     listener->factory.destroy = lis_destroy;
     listener->is_registered = PJ_TRUE;
     status = pjsip_tpmgr_register_tpfactory(listener->tpmgr,
@@ -480,6 +513,7 @@
 			       pj_bool_t is_server,
 			       const pj_sockaddr_in *local,
 			       const pj_sockaddr_in *remote,
+			       const pj_str_t *remote_name,
 			       struct tls_transport **p_tls)
 {
     struct tls_transport *tls;
@@ -501,6 +535,7 @@
      */
     tls = PJ_POOL_ZALLOC_T(pool, struct tls_transport);
     tls->is_server = is_server;
+    tls->verify_server = listener->tls_setting.verify_server;
     pj_list_init(&tls->delayed_list);
     tls->base.pool = pool;
 
@@ -517,8 +552,13 @@
 	goto on_error;
     }
 
+    if (remote_name)
+	pj_strdup(pool, &tls->remote_name, remote_name);
+
     tls->base.key.type = PJSIP_TRANSPORT_TLS;
     pj_memcpy(&tls->base.key.rem_addr, remote, sizeof(pj_sockaddr_in));
+    tls->base.key.hname = pj_hash_calc_tolower(0, (char*)tls->remote_name.ptr,
+					       &tls->remote_name);
     tls->base.type_name = "tls";
     tls->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS);
 
@@ -769,6 +809,7 @@
 					pjsip_endpoint *endpt,
 					const pj_sockaddr *rem_addr,
 					int addr_len,
+					pjsip_tx_data *tdata,
 					pjsip_transport **p_transport)
 {
     struct tls_listener *listener;
@@ -777,6 +818,7 @@
     pj_ssl_sock_t *ssock;
     pj_ssl_sock_param ssock_param;
     pj_sockaddr_in local_addr;
+    pj_str_t remote_name;
     pj_status_t status;
 
     /* Sanity checks */
@@ -794,6 +836,12 @@
 				   POOL_TP_INIT, POOL_TP_INC);
     PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
 
+    /* Get remote host name from tdata */
+    if (tdata)
+	remote_name = tdata->dest_info.name;
+    else
+	pj_bzero(&remote_name, sizeof(remote_name));
+
     /* Build SSL socket param */
     pj_ssl_sock_param_default(&ssock_param);
     ssock_param.cb.on_connect_complete = &on_connect_complete;
@@ -801,12 +849,12 @@
     ssock_param.cb.on_data_sent = &on_data_sent;
     ssock_param.async_cnt = 1;
     ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt);
-    PJ_TODO(set_proper_servername_based_on_target);
     PJ_TODO(synchronize_tls_cipher_type_with_ssl_sock_cipher_type);
-    ssock_param.server_name = listener->tls_setting.server_name;
+    ssock_param.server_name = remote_name;
     ssock_param.timeout = listener->tls_setting.timeout;
     ssock_param.user_data = NULL; /* pending, must be set later */
-    ssock_param.verify_peer = listener->tls_setting.verify_server;
+    ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket
+					 * due to verification error */
     if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN)
 	ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN;
     if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN)
@@ -850,7 +898,7 @@
 
     /* Create the transport descriptor */
     status = tls_create(listener, pool, ssock, PJ_FALSE, &local_addr, 
-			(pj_sockaddr_in*)rem_addr, &tls);
+			(pj_sockaddr_in*)rem_addr, &remote_name, &tls);
     if (status != PJ_SUCCESS)
 	return status;
 
@@ -928,9 +976,13 @@
 {
     struct tls_listener *listener;
     struct tls_transport *tls;
+    pj_ssl_sock_info ssl_info;
     char addr[PJ_INET6_ADDRSTRLEN+10];
     pj_status_t status;
 
+    pjsip_tp_state_callback *state_cb;
+    pj_bool_t tls_verif_ignored;
+
     PJ_UNUSED_ARG(src_addr_len);
 
     listener = (struct tls_listener*) pj_ssl_sock_get_user_data(ssock);
@@ -946,32 +998,84 @@
 	      pj_sockaddr_print(src_addr, addr, sizeof(addr), 3),
 	      new_ssock));
 
+    /* Retrieve SSL socket info, close the socket if this is failed
+     * as the SSL socket info availability is rather critical here.
+     */
+    status = pj_ssl_sock_get_info(new_ssock, &ssl_info);
+    if (status != PJ_SUCCESS) {
+	pj_ssl_sock_close(new_ssock);
+	return PJ_TRUE;
+    }
+
     /* 
      * Incoming connection!
      * Create TLS transport for the new socket.
      */
     status = tls_create( listener, NULL, new_ssock, PJ_TRUE,
 			 (const pj_sockaddr_in*)&listener->factory.local_addr,
-			 (const pj_sockaddr_in*)src_addr, &tls);
+			 (const pj_sockaddr_in*)src_addr, NULL, &tls);
     
-    if (status == PJ_SUCCESS) {
-	/* Set the "pending" SSL socket user data */
-	pj_ssl_sock_set_user_data(new_ssock, tls);
+    if (status != PJ_SUCCESS)
+	return PJ_TRUE;
 
-	status = tls_start_read(tls);
-	if (status != PJ_SUCCESS) {
-	    PJ_LOG(3,(tls->base.obj_name, "New transport cancelled"));
-	    tls_destroy(&tls->base, status);
+    /* Set the "pending" SSL socket user data */
+    pj_ssl_sock_set_user_data(new_ssock, tls);
+
+    tls_verif_ignored = !listener->tls_setting.verify_client;
+
+    /* Notify transport state to application */
+    state_cb = pjsip_tpmgr_get_status_cb(tls->base.tpmgr);
+    if (state_cb) {
+	pjsip_transport_state_info state_info;
+	pjsip_tls_state_info tls_info;
+	pj_uint32_t tp_state = 0;
+
+	/* Init transport state notification callback */
+	pj_bzero(&tls_info, sizeof(tls_info));
+	pj_bzero(&state_info, sizeof(state_info));
+
+	/* Set transport state based on verification status */
+	if (ssl_info.verify_status) {
+	    state_info.status = PJSIP_TLS_EACCEPT;
+	    tp_state |= PJSIP_TP_STATE_TLS_VERIF_ERROR;
+	    if (listener->tls_setting.verify_client)
+		tp_state |= PJSIP_TP_STATE_REJECTED;
+	    else
+		tp_state |= PJSIP_TP_STATE_ACCEPTED;
 	} 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);
-	    }
+	    tp_state |= PJSIP_TP_STATE_ACCEPTED;
+	}
+
+	tls_info.ssl_sock_info = &ssl_info;
+	state_info.ext_info = &tls_info;
+
+	tls_verif_ignored = (*state_cb)(&tls->base, tp_state, &state_info);
+    }
+
+    /* Transport should be destroyed when there is TLS verification error
+     * and application doesn't want to ignore it.
+     */
+    if (ssl_info.verify_status && 
+	(listener->tls_setting.verify_client || !tls_verif_ignored))
+    {
+	tls_destroy(&tls->base, PJSIP_TLS_EACCEPT);
+	return PJ_TRUE;
+    }
+
+    status = tls_start_read(tls);
+    if (status != PJ_SUCCESS) {
+	PJ_LOG(3,(tls->base.obj_name, "New transport cancelled"));
+	tls_init_shutdown(tls, status);
+	tls_destroy(&tls->base, status);
+    } else {
+	/* Start keep-alive timer */
+	if (PJSIP_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);
 	}
     }
 
@@ -1019,8 +1123,8 @@
 
 	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);
+
+	tls_init_shutdown(tls, status);
 
 	return PJ_FALSE;
     }
@@ -1115,8 +1219,8 @@
 
 		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);
+
+		tls_init_shutdown(tls, status);
 	    }
 	}
     }
@@ -1204,15 +1308,8 @@
 
 	/* 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);
+
+	tls_init_shutdown(tls, status);
 
 	return PJ_FALSE;
 
@@ -1232,8 +1329,12 @@
 				     pj_status_t status)
 {
     struct tls_transport *tls;
-    pj_ssl_sock_info info;
-    
+    pj_ssl_sock_info ssl_info;
+    pj_sockaddr_in addr, *tp_addr;
+
+    pjsip_tp_state_callback *state_cb;
+    pj_bool_t tls_verif_ignored;
+
     tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock);
 
     /* Check connect() status */
@@ -1254,36 +1355,130 @@
 	    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;
+	goto on_error;
     }
 
+    /* Retrieve SSL socket info, shutdown the transport if this is failed
+     * as the SSL socket info availability is rather critical here.
+     */
+    status = pj_ssl_sock_get_info(tls->ssock, &ssl_info);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
     /* Update (again) local address, just in case local address currently
      * set is different now that the socket is connected (could happen
      * on some systems, like old Win32 probably?).
      */
-
-    /* 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);
-	}
+    tp_addr = (pj_sockaddr_in*)&tls->base.local_addr;
+    pj_sockaddr_cp((pj_sockaddr_t*)&addr, 
+		   (pj_sockaddr_t*)&ssl_info.local_addr);
+    if (tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr) {
+	tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr;
+	tp_addr->sin_port = addr.sin_port;
+	sockaddr_to_host_port(tls->base.pool, &tls->base.local_name,
+			      tp_addr);
     }
 
+    /* Server identity verification based on server certificate. */
+    if (ssl_info.remote_cert_info->version) {
+	pj_str_t *remote_name;
+	pj_ssl_cert_info *serv_cert = ssl_info.remote_cert_info;
+	pj_bool_t matched = PJ_FALSE;
+	unsigned i;
+
+	/* Remote name may be hostname or IP address */
+	if (tls->remote_name.slen)
+	    remote_name = &tls->remote_name;
+	else
+	    remote_name = &tls->base.remote_name.host;
+
+	/* Start matching remote name with SubjectAltName fields of 
+	 * server certificate.
+	 */
+	for (i = 0; i < serv_cert->subj_alt_name.cnt && !matched; ++i) {
+	    pj_str_t *cert_name = &serv_cert->subj_alt_name.entry[i].name;
+
+	    switch (serv_cert->subj_alt_name.entry[i].type) {
+	    case PJ_SSL_CERT_NAME_DNS:
+	    case PJ_SSL_CERT_NAME_IP:
+		matched = !pj_stricmp(remote_name, cert_name);
+		break;
+	    case PJ_SSL_CERT_NAME_URI:
+		if (pj_strnicmp2(cert_name, "sip:", 4) == 0 ||
+		    pj_strnicmp2(cert_name, "sips:", 5) == 0)
+		{
+		    pj_str_t host_part;
+		    char *p;
+
+		    p = pj_strchr(cert_name, ':') + 1;
+		    pj_strset(&host_part, p, cert_name->slen - 
+					     (p - cert_name->ptr));
+		    matched = !pj_stricmp(remote_name, &host_part);
+		}
+		break;
+	    default:
+		break;
+	    }
+	}
+    	
+	/* When still not matched or no SubjectAltName fields in server
+	 * certificate, try with Common Name of Subject field.
+	 */
+	if (!matched) {
+	    matched = !pj_stricmp(remote_name, &serv_cert->subject.cn);
+	}
+
+	if (!matched)
+	    ssl_info.verify_status |= PJ_SSL_CERT_EIDENTITY_NOT_MATCH;
+    }
+
+    tls_verif_ignored = !tls->verify_server;
+
+    /* Notify transport state to application */
+    state_cb = pjsip_tpmgr_get_status_cb(tls->base.tpmgr);
+    if (state_cb) {
+	pjsip_transport_state_info state_info;
+	pjsip_tls_state_info tls_info;
+	pj_uint32_t tp_state = 0;
+
+	/* Init transport state notification callback */
+	pj_bzero(&state_info, sizeof(state_info));
+	pj_bzero(&tls_info, sizeof(tls_info));
+
+	/* Set transport state info */
+	state_info.ext_info = &tls_info;
+	tls_info.ssl_sock_info = &ssl_info;
+
+	/* Set transport state based on verification status */
+	if (ssl_info.verify_status) {
+	    state_info.status = PJSIP_TLS_ECONNECT;
+	    tp_state |= PJSIP_TP_STATE_TLS_VERIF_ERROR;
+	    if (tls->verify_server)
+		tp_state |= PJSIP_TP_STATE_DISCONNECTED;
+	    else
+		tp_state |= PJSIP_TP_STATE_CONNECTED;
+	} else {
+	    tp_state |= PJSIP_TP_STATE_CONNECTED;
+	}
+
+	tls_verif_ignored = (*state_cb)(&tls->base, tp_state, &state_info);
+    }
+
+    /* Transport should be shutdown when there is TLS verification error
+     * and application doesn't want to ignore it.
+     */
+    if (ssl_info.verify_status && 
+	(tls->verify_server || !tls_verif_ignored))
+    {
+	if (tls->close_reason == PJ_SUCCESS) 
+	    tls->close_reason = PJSIP_TLS_ECONNECT;
+	pjsip_transport_shutdown(&tls->base);
+	return PJ_FALSE;
+    }
+
+    /* Mark that pending connect() operation has completed. */
+    tls->has_pending_connect = PJ_FALSE;
+
     PJ_LOG(4,(tls->base.obj_name, 
 	      "TLS transport %.*s:%d is connected to %.*s:%d",
 	      (int)tls->base.local_name.host.slen,
@@ -1293,21 +1488,10 @@
 	      tls->base.remote_name.host.ptr,
 	      tls->base.remote_name.port));
 
-    /* Mark that pending connect() operation has completed. */
-    tls->has_pending_connect = PJ_FALSE;
-
     /* Start pending read */
     status = tls_start_read(tls);
-    if (status != PJ_SUCCESS) {
-	/* 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;
-    }
+    if (status != PJ_SUCCESS)
+	goto on_error;
 
     /* Flush all pending send operations */
     tls_flush_pending_tx(tls);
@@ -1322,6 +1506,11 @@
     }
 
     return PJ_TRUE;
+
+on_error:
+    tls_init_shutdown(tls, status);
+
+    return PJ_FALSE;
 }
 
 
@@ -1365,7 +1554,8 @@
     if (status != PJ_SUCCESS && status != PJ_EPENDING) {
 	tls_perror(tls->base.obj_name, 
 		   "Error sending keep-alive packet", status);
-	pjsip_transport_shutdown(&tls->base);
+
+	tls_init_shutdown(tls, status);
 	return;
     }
 
diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c
index 5bd0f6b..3b1bc10 100644
--- a/pjsip/src/pjsip/sip_util.c
+++ b/pjsip/src/pjsip/sip_util.c
@@ -779,6 +779,10 @@
 	    pjsip_hdr_clone(cancel_tdata->pool, req_tdata->saved_strict_route);
     }
 
+    /* Copy the destination host name from the original request */
+    pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name,
+	      &req_tdata->dest_info.name);
+
     /* Finally copy the destination info from the original request */
     pj_memcpy(&cancel_tdata->dest_info, &req_tdata->dest_info,
 	      sizeof(req_tdata->dest_info));
@@ -1134,11 +1138,12 @@
 	cur_addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len;
 
 	/* Acquire transport. */
-	status = pjsip_endpt_acquire_transport( stateless_data->endpt,
+	status = pjsip_endpt_acquire_transport2(stateless_data->endpt,
 						cur_addr_type,
 						cur_addr,
 						cur_addr_len,
 						&tdata->tp_sel,
+						tdata,
 						&stateless_data->cur_transport);
 	if (status != PJ_SUCCESS) {
 	    sent = -status;
@@ -1319,6 +1324,9 @@
      * proceed to sending the request directly.
      */
     if (tdata->dest_info.addr.count == 0) {
+	/* Copy the destination host name to TX data */
+	pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);
+
 	pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, stateless_data,
 			     &stateless_send_resolver_callback);
     } else {
@@ -1466,6 +1474,9 @@
 	pjsip_tpselector_add_ref(sraw_data->sel);
     }
 
+    /* Copy the destination host name to TX data */
+    pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);
+
     /* Resolve destination host.
      * The processing then resumed when the resolving callback is called.
      */
@@ -1622,11 +1633,12 @@
     /* Only handle the first address resolved. */
 
     /* Acquire transport. */
-    status = pjsip_endpt_acquire_transport( send_state->endpt, 
+    status = pjsip_endpt_acquire_transport2(send_state->endpt, 
 					    addr->entry[0].type,
 					    &addr->entry[0].addr,
 					    addr->entry[0].addr_len,
 					    &send_state->tdata->tp_sel,
+					    send_state->tdata,
 					    &send_state->cur_transport);
     if (status != PJ_SUCCESS) {
 	if (send_state->app_cb) {
@@ -1702,6 +1714,10 @@
 	    return status;
 	}
     } else {
+	/* Copy the destination host name to TX data */
+	pj_strdup(tdata->pool, &tdata->dest_info.name, 
+		  &res_addr->dst_host.addr.host);
+
 	pjsip_endpt_resolve(endpt, tdata->pool, &res_addr->dst_host, 
 			    send_state, &send_response_resolver_cb);
 	return PJ_SUCCESS;
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 52d436e..8870b87 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -1810,9 +1810,6 @@
 	/*
 	 * Create TLS transport.
 	 */
-	/*
-	 * Create TCP transport.
-	 */
 	pjsua_transport_config config;
 	pjsip_host_port a_name;
 	pjsip_tpfactory *tls;
@@ -1867,6 +1864,11 @@
 	goto on_return;
     }
 
+    /* Set transport state callback */
+    if (pjsua_var.ua_cfg.cb.on_transport_state) {
+	pjsip_tpmgr_set_status_cb(pjsip_endpt_get_tpmgr(pjsua_var.endpt),
+				  &pjsua_var.ua_cfg.cb.on_transport_state);
+    }
 
     /* Return the ID */
     if (p_id) *p_id = id;