Related to ticket #485: huge changeset to update STUN relating to managing authentication. See the ticket for the details

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1877 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjnath/build/Makefile b/pjnath/build/Makefile
index 319893e..393562f 100644
--- a/pjnath/build/Makefile
+++ b/pjnath/build/Makefile
@@ -38,7 +38,7 @@
 # Defines for building test application
 #
 export PJNATH_TEST_SRCDIR = ../src/pjnath-test
-export PJNATH_TEST_OBJS += ice_test.o stun.o test.o
+export PJNATH_TEST_OBJS += ice_test.o stun.o sess_auth.o test.o
 export PJNATH_TEST_CFLAGS += $(_CFLAGS)
 export PJNATH_TEST_LDFLAGS += $(_LDFLAGS)
 export PJNATH_TEST_EXE:=../bin/pjnath-test-$(TARGET_NAME)$(HOST_EXE)
diff --git a/pjnath/build/pjnath_test.dsp b/pjnath/build/pjnath_test.dsp
index cabea6a..0c6b862 100644
--- a/pjnath/build/pjnath_test.dsp
+++ b/pjnath/build/pjnath_test.dsp
@@ -95,6 +95,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE="..\src\pjnath-test\sess_auth.c"

+# End Source File

+# Begin Source File

+

 SOURCE="..\src\pjnath-test\stun.c"

 # End Source File

 # Begin Source File

diff --git a/pjnath/include/pjnath/stun_auth.h b/pjnath/include/pjnath/stun_auth.h
index 840e473..249b062 100644
--- a/pjnath/include/pjnath/stun_auth.h
+++ b/pjnath/include/pjnath/stun_auth.h
@@ -39,6 +39,29 @@
  */
 
 /**
+ * Type of authentication.
+ */
+typedef enum pj_stun_auth_type
+{ 
+    /**
+     * No authentication.
+     */
+    PJ_STUN_AUTH_NONE = 0,
+
+    /**
+     * Authentication using short term credential.
+     */
+    PJ_STUN_AUTH_SHORT_TERM = 1,
+
+    /**
+     * Authentication using long term credential.
+     */
+    PJ_STUN_AUTH_LONG_TERM = 2
+
+} pj_stun_auth_type;
+
+
+/**
  * Type of authentication data in the credential.
  */
 typedef enum pj_stun_auth_cred_type
@@ -62,6 +85,26 @@
 
 
 /**
+ * Type of encoding applied to the password stored in the credential.
+ */
+typedef enum pj_stun_passwd_type
+{
+    /**
+     * Plain text password.
+     */
+    PJ_STUN_PASSWD_PLAIN    = 0,
+
+    /**
+     * Hashed password, valid for long term credential only. The hash value
+     * of the password is calculated as MD5(USERNAME ":" REALM ":" PASSWD)
+     * with all quotes removed from the username and realm values.
+     */
+    PJ_STUN_PASSWD_HASHED = 1
+
+} pj_stun_passwd_type;
+
+
+/**
  * This structure contains the descriptions needed to perform server side
  * authentication. Depending on the \a type set in the structure, application
  * may specify a static username/password combination, or to have callbacks
@@ -89,31 +132,29 @@
 	    /** 
 	     * If not-empty, it indicates that this is a long term credential.
 	     */
-	    pj_str_t	  realm;
+	    pj_str_t		realm;
 
 	    /** 
 	     * The username of the credential.
 	     */
-	    pj_str_t	  username;
+	    pj_str_t		username;
 
 	    /**
 	     * Data type to indicate the type of password in the \a data field.
-	     * Value zero indicates that the data contains a plaintext
-	     * password.
 	     */
-	    int		  data_type;
+	    pj_stun_passwd_type	data_type;
 
 	    /** 
 	     * The data, which depends depends on the value of \a data_type
 	     * field. When \a data_type is zero, this field will contain the
 	     * plaintext password.
 	     */
-	    pj_str_t	  data;
+	    pj_str_t		data;
 
 	    /** 
 	     * Optional NONCE.
 	     */
-	    pj_str_t	  nonce;
+	    pj_str_t		nonce;
 
 	} static_cred;
 
@@ -156,7 +197,7 @@
 				    pj_str_t *nonce);
 
 	    /**
-	     * Get the credential to be put in outgoing message.
+	     * Get the credential to be put in outgoing request.
 	     *
 	     * @param msg	The outgoing message where the credential is
 	     *			to be applied.
@@ -186,7 +227,7 @@
 				    pj_str_t *realm,
 				    pj_str_t *username,
 				    pj_str_t *nonce,
-				    int *data_type,
+				    pj_stun_passwd_type *data_type,
 				    pj_str_t *data);
 
 	    /**
@@ -217,7 +258,7 @@
 				        const pj_str_t *realm,
 				        const pj_str_t *username,
 					pj_pool_t *pool,
-					int *data_type,
+					pj_stun_passwd_type *data_type,
 					pj_str_t *data);
 
 	    /**
@@ -250,6 +291,40 @@
 
 
 /**
+ * This structure contains the credential information that is found and
+ * used to authenticate incoming requests. Application may use this
+ * information when generating authentication for the outgoing response.
+ */
+typedef struct pj_stun_req_cred_info
+{
+    /**
+     * The REALM value found in the incoming request. If short term 
+     * credential is used, the value will be empty.
+     */
+    pj_str_t	realm;
+
+    /**
+     * The USERNAME value found in the incoming request.
+     */
+    pj_str_t	username;
+
+    /**
+     * Optional NONCE.
+     */
+    pj_str_t	nonce;
+
+    /**
+     * Authentication key that was used to authenticate the incoming 
+     * request. This key is created with #pj_stun_create_key(), and
+     * it can be used to encode the credential of the outgoing
+     * response.
+     */
+    pj_str_t	auth_key;
+
+} pj_stun_req_cred_info;
+
+
+/**
  * Duplicate authentication credential.
  *
  * @param pool		Pool to be used to allocate memory.
@@ -260,6 +335,40 @@
 				      pj_stun_auth_cred *dst,
 				      const pj_stun_auth_cred *src);
 
+/**
+ * Duplicate request credential.
+ *
+ * @param pool		Pool to be used to allocate memory.
+ * @param dst		Destination credential.
+ * @param src		Source credential.
+ */
+PJ_DECL(void) pj_stun_req_cred_info_dup(pj_pool_t *pool,
+					pj_stun_req_cred_info *dst,
+					const pj_stun_req_cred_info *src);
+
+
+/**
+ * Create authentication key to be used for encoding the message with
+ * MESSAGE-INTEGRITY. If short term credential is used (i.e. the realm
+ * argument is NULL or empty), the key will be copied from the password.
+ * If long term credential is used, the key will be calculated from the
+ * MD5 hash of the realm, username, and password.
+ *
+ * @param pool		Pool to allocate memory for the key.
+ * @param key		String to receive the key.
+ * @param realm		The realm of the credential, if long term credential
+ *			is to be used. If short term credential is wanted,
+ *			application can put NULL or empty string here.
+ * @param username	The username.
+ * @param data_type	Password encoding.
+ * @param data		The password.
+ */
+PJ_DECL(void) pj_stun_create_key(pj_pool_t *pool,
+				 pj_str_t *key,
+				 const pj_str_t *realm,
+				 const pj_str_t *username,
+				 pj_stun_passwd_type data_type,
+				 const pj_str_t *data);
 
 /**
  * Verify credential in the STUN request. Note that before calling this
@@ -277,9 +386,9 @@
  *			the message.
  * @param pool		If response is to be created, then memory will
  *			be allocated from this pool.
- * @param auth_key	Optional pointer to receive authentication key to
- *			calculate MESSAGE-INTEGRITY of the response, if
- *			the response needs to be authenticated.
+ * @param info		Optional pointer to receive authentication information
+ *			found in the request and the credential that is used
+ *			to authenticate the request.
  * @param p_response	Optional pointer to receive the response message
  *			then the credential in the request fails to
  *			authenticate.
@@ -294,7 +403,7 @@
 					          const pj_stun_msg *msg,
 					          pj_stun_auth_cred *cred,
 					          pj_pool_t *pool,
-						  pj_str_t *auth_key,
+						  pj_stun_req_cred_info *info,
 					          pj_stun_msg **p_response);
 
 
diff --git a/pjnath/include/pjnath/stun_msg.h b/pjnath/include/pjnath/stun_msg.h
index cde21fa..31e4033 100644
--- a/pjnath/include/pjnath/stun_msg.h
+++ b/pjnath/include/pjnath/stun_msg.h
@@ -1225,6 +1225,17 @@
 					pj_stun_msg **p_msg);
 
 /**
+ * Clone a STUN message with all of its attributes.
+ *
+ * @param pool		Pool to allocate memory for the new message.
+ * @param msg		The message to be cloned.
+ *
+ * @return		The duplicate message.
+ */
+PJ_DECL(pj_stun_msg*) pj_stun_msg_clone(pj_pool_t *pool,
+					const pj_stun_msg *msg);
+
+/**
  * Create STUN response message. 
  *
  * @param pool		Pool to create the mesage.
@@ -1300,30 +1311,6 @@
 					const pj_str_t *key,
 				        unsigned *p_msg_len);
 
-
-/**
- * Create authentication key to be used for encoding the message with
- * MESSAGE-INTEGRITY. If short term credential is used (i.e. the realm
- * argument is NULL or empty), the key will be copied from the password.
- * If long term credential is used, the key will be calculated from the
- * MD5 hash of the realm, username, and password.
- *
- * @param pool		Pool to allocate memory for the key.
- * @param key		String to receive the key.
- * @param realm		The realm of the credential, if long term credential
- *			is to be used. If short term credential is wanted,
- *			application can put NULL or empty string here.
- * @param username	The username.
- * @param passwd	The clear text password.
- */
-PJ_DECL(void) pj_stun_create_key(pj_pool_t *pool,
-				 pj_str_t *key,
-				 const pj_str_t *realm,
-				 const pj_str_t *username,
-				 const pj_str_t *passwd);
-
-
-
 /**
  * Check that the PDU is potentially a valid STUN message. This function
  * is useful when application needs to multiplex STUN packets with other
@@ -1417,6 +1404,18 @@
 
 
 /**
+ * Clone a STUN attribute.
+ *
+ * @param pool		Pool to allocate memory.
+ * @param attr		Attribute to clone.
+ *
+ * @return		Duplicate attribute.
+ */
+PJ_DECL(pj_stun_attr_hdr*) pj_stun_attr_clone(pj_pool_t *pool,
+					      const pj_stun_attr_hdr *attr);
+
+
+/**
  * Create a generic STUN IP address attribute. The \a addr_len and
  * \a addr parameters specify whether the address is IPv4 or IPv4
  * address.
diff --git a/pjnath/include/pjnath/stun_session.h b/pjnath/include/pjnath/stun_session.h
index b28763a..187064e 100644
--- a/pjnath/include/pjnath/stun_session.h
+++ b/pjnath/include/pjnath/stun_session.h
@@ -46,6 +46,9 @@
 /** Forward declaration for pj_stun_tx_data */
 typedef struct pj_stun_tx_data pj_stun_tx_data;
 
+/** Forward declaration for pj_stun_rx_data */
+typedef struct pj_stun_rx_data pj_stun_rx_data;
+
 /** Forward declaration for pj_stun_session */
 typedef struct pj_stun_session pj_stun_session;
 
@@ -85,7 +88,7 @@
      * @param sess	    The STUN session.
      * @param pkt	    Pointer to the original STUN packet.
      * @param pkt_len	    Length of the STUN packet.
-     * @param msg	    The parsed STUN request.
+     * @param rdata	    Data containing incoming request message.
      * @param src_addr	    Source address of the packet.
      * @param src_addr_len  Length of the source address.
      *
@@ -96,7 +99,7 @@
     pj_status_t (*on_rx_request)(pj_stun_session *sess,
 				 const pj_uint8_t *pkt,
 				 unsigned pkt_len,
-				 const pj_stun_msg *msg,
+				 const pj_stun_rx_data *rdata,
 				 const pj_sockaddr_t *src_addr,
 				 unsigned src_addr_len);
 
@@ -144,6 +147,25 @@
 
 
 /**
+ * This structure describes incoming request message.
+ */
+struct pj_stun_rx_data
+{
+    /**
+     * The parsed request message.
+     */
+    pj_stun_msg		    *msg;
+
+    /**
+     * Credential information that is found and used to authenticate 
+     * incoming request. Application may use this information when 
+     * generating  authentication for the outgoing response.
+     */
+    pj_stun_req_cred_info   info;
+};
+
+
+/**
  * This structure describe the outgoing STUN transmit data to carry the
  * message to be sent.
  */
@@ -161,7 +183,7 @@
     pj_uint32_t		 msg_magic;	/**< Message magic.		    */
     pj_uint8_t		 msg_key[12];	/**< Message/transaction key.	    */
 
-    pj_str_t		 auth_key;	/**< Auth key.			    */
+    pj_stun_req_cred_info auth_info;	/**< Credential info		    */
 
     void		*pkt;		/**< The STUN packet.		    */
     unsigned		 max_len;	/**< Length of packet buffer.	    */
@@ -259,13 +281,15 @@
  * again with NULL as the argument.
  *
  * @param sess	    The STUN session instance.
+ * @param auth_type Type of authentication.
  * @param cred	    The credential to be used by this session. If NULL
  *		    is specified, authentication will be disabled.
  *
  * @return	    PJ_SUCCESS on success, or the appropriate error code.
  */
-PJ_DECL(void) pj_stun_session_set_credential(pj_stun_session *sess,
-					     const pj_stun_auth_cred *cred);
+PJ_DECL(pj_status_t) pj_stun_session_set_credential(pj_stun_session *sess,
+						pj_stun_auth_type auth_type,
+						const pj_stun_auth_cred *cred);
 
 /**
  * Create a STUN request message. After the message has been successfully
@@ -328,7 +352,7 @@
  * @return	    PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pj_stun_session_create_res(pj_stun_session *sess,
-						const pj_stun_msg *req,
+						const pj_stun_rx_data *rdata,
 						unsigned err_code,
 						const pj_str_t *err_msg,
 						pj_stun_tx_data **p_tdata);
@@ -362,7 +386,7 @@
  * Create and send STUN response message.
  *
  * @param sess	    The STUN session instance.
- * @param req	    The STUN request message to be responded.
+ * @param rdata	    The STUN request message to be responded.
  * @param err_code  Error code to be set in the response, if error response
  *		    is to be created, according to pj_stun_status enumeration.
  *		    This argument MUST be zero if successful response is
@@ -382,7 +406,7 @@
  * @return	    PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pj_stun_session_respond(pj_stun_session *sess, 
-					     const pj_stun_msg *req,
+					     const pj_stun_rx_data *rdata,
 					     unsigned code, 
 					     const char *err_msg,
 					     pj_bool_t cache, 
diff --git a/pjnath/src/pjnath-test/sess_auth.c b/pjnath/src/pjnath-test/sess_auth.c
new file mode 100644
index 0000000..86ac5d8
--- /dev/null
+++ b/pjnath/src/pjnath-test/sess_auth.c
@@ -0,0 +1,1125 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "test.h"
+
+#define THIS_FILE   "sess_auth.c"
+
+#define REALM	    "STUN session test"
+#define USERNAME    "theusername"
+#define PASSWORD    "thepassword"
+#define NONCE	    "thenonce"
+
+
+/* STUN config */
+static pj_stun_config stun_cfg;
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// SERVER PART
+//
+
+
+/* Server instance */
+static struct server
+{
+    pj_pool_t		*pool;
+    pj_sockaddr		 addr;
+    pj_stun_session	*sess;
+
+    pj_bool_t		 responding;
+    unsigned		 recv_count;
+    pj_stun_auth_type	 auth_type;
+
+    pj_sock_t		 sock;
+
+    pj_bool_t		 quit;
+    pj_thread_t		*thread;
+} *server;
+
+
+static pj_status_t server_send_msg(pj_stun_session *sess,
+				   const void *pkt,
+				   pj_size_t pkt_size,
+				   const pj_sockaddr_t *dst_addr,
+				   unsigned addr_len)
+{
+    pj_ssize_t len = pkt_size;
+
+    PJ_UNUSED_ARG(sess);
+
+    return pj_sock_sendto(server->sock, pkt, &len, 0, dst_addr, addr_len);
+}
+
+static pj_status_t server_on_rx_request(pj_stun_session *sess,
+					const pj_uint8_t *pkt,
+					unsigned pkt_len,
+					const pj_stun_rx_data *rdata,
+					const pj_sockaddr_t *src_addr,
+					unsigned src_addr_len)
+{
+    PJ_UNUSED_ARG(pkt);
+    PJ_UNUSED_ARG(pkt_len);
+
+    return pj_stun_session_respond(sess, rdata, 0, NULL, PJ_TRUE, 
+				   src_addr, src_addr_len);
+}
+
+
+static pj_status_t server_get_auth(void *user_data,
+				   pj_pool_t *pool,
+				   pj_str_t *realm,
+				   pj_str_t *nonce)
+{
+    PJ_UNUSED_ARG(user_data);
+    PJ_UNUSED_ARG(pool);
+
+    if (server->auth_type == PJ_STUN_AUTH_SHORT_TERM) {
+	realm->slen = nonce->slen = 0;
+    } else {
+	*realm = pj_str(REALM);
+	*nonce = pj_str(NONCE);
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_status_t server_get_password( const pj_stun_msg *msg,
+					void *user_data, 
+				        const pj_str_t *realm,
+				        const pj_str_t *username,
+					pj_pool_t *pool,
+					pj_stun_passwd_type *data_type,
+					pj_str_t *data)
+{
+    PJ_UNUSED_ARG(msg);
+    PJ_UNUSED_ARG(user_data);
+    PJ_UNUSED_ARG(pool);
+
+    if (server->auth_type == PJ_STUN_AUTH_SHORT_TERM) {
+	if (realm && realm->slen) {
+	    PJ_LOG(4,(THIS_FILE, "    server expecting short term"));
+	    return -1;
+	}
+    } else {
+	if (realm==NULL || realm->slen==0) {
+	    PJ_LOG(4,(THIS_FILE, "    realm not present"));
+	    return -1;
+	}
+    }
+
+    if (pj_strcmp2(username, USERNAME) != 0) {
+	PJ_LOG(4,(THIS_FILE, "    wrong username"));
+	return -1;
+    }
+
+    *data_type = PJ_STUN_PASSWD_PLAIN;
+    *data = pj_str(PASSWORD);
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_bool_t server_verify_nonce(const pj_stun_msg *msg,
+				     void *user_data,
+				     const pj_str_t *realm,
+				     const pj_str_t *username,
+				     const pj_str_t *nonce)
+{
+    PJ_UNUSED_ARG(msg);
+    PJ_UNUSED_ARG(user_data);
+    PJ_UNUSED_ARG(realm);
+    PJ_UNUSED_ARG(username);
+
+    if (pj_strcmp2(nonce, NONCE) != 0)
+	return PJ_FALSE;
+
+    return PJ_TRUE;
+}
+
+
+static int server_thread(void *unused)
+{
+    PJ_UNUSED_ARG(unused);
+
+    while (!server->quit) {
+	pj_fd_set_t readset;
+	pj_time_val delay = {0, 10};
+
+	PJ_FD_ZERO(&readset);
+	PJ_FD_SET(server->sock, &readset);
+
+	if (pj_sock_select(server->sock, &readset, NULL, NULL, &delay)==1 &&
+	    PJ_FD_ISSET(server->sock, &readset)) 
+	{
+	    char pkt[1000];
+	    pj_ssize_t len;
+	    pj_status_t status;
+	    pj_sockaddr src_addr;
+	    int src_addr_len;
+
+	    len = sizeof(pkt);
+	    src_addr_len = sizeof(src_addr);
+
+	    status = pj_sock_recvfrom(server->sock, pkt, &len, 0, &src_addr, &src_addr_len);
+	    if (status != PJ_SUCCESS)
+		continue;
+
+	    /* Increment server's receive count */
+	    server->recv_count++;
+
+	    /* Only pass to server if we allow to respond */
+	    if (!server->responding)
+		continue;
+
+	    pj_stun_session_on_rx_pkt(server->sess, pkt, len, 
+				      PJ_STUN_CHECK_PACKET | PJ_STUN_IS_DATAGRAM,
+				      NULL, &src_addr, src_addr_len);
+	}
+    }
+
+    return 0;
+}
+
+
+/* Destroy server */
+static void destroy_server(void)
+{
+    if (server->thread) {
+	server->quit = PJ_TRUE;
+	pj_thread_join(server->thread);
+	pj_thread_destroy(server->thread);
+    }
+
+    if (server->sock) {
+	pj_sock_close(server->sock);
+    }
+
+    if (server->sess) {
+	pj_stun_session_destroy(server->sess);
+    }
+
+    pj_pool_release(server->pool);
+    server = NULL;
+}
+
+/* Instantiate standard server */
+static int create_std_server(pj_stun_auth_type auth_type,
+			     pj_bool_t responding)
+{
+    pj_pool_t *pool;
+    pj_stun_session_cb sess_cb;
+    pj_stun_auth_cred cred;
+    pj_status_t status;
+    
+    /* Create server */
+    pool = pj_pool_create(mem, "server", 1000, 1000, NULL);
+    server = PJ_POOL_ZALLOC_T(pool, struct server);
+    server->pool = pool;
+    server->auth_type = auth_type;
+    server->responding = responding;
+
+    /* Create STUN session */
+    pj_bzero(&sess_cb, sizeof(sess_cb));
+    sess_cb.on_rx_request = &server_on_rx_request;
+    sess_cb.on_send_msg = &server_send_msg;
+    status = pj_stun_session_create(&stun_cfg, "server", &sess_cb, PJ_FALSE, &server->sess);
+    if (status != PJ_SUCCESS) {
+	destroy_server();
+	return -10;
+    }
+
+    /* Configure credential */
+    pj_bzero(&cred, sizeof(cred));
+    cred.type = PJ_STUN_AUTH_CRED_DYNAMIC;
+    cred.data.dyn_cred.get_auth = &server_get_auth;
+    cred.data.dyn_cred.get_password = &server_get_password;
+    cred.data.dyn_cred.verify_nonce = &server_verify_nonce;
+    status = pj_stun_session_set_credential(server->sess, auth_type, &cred);
+    if (status != PJ_SUCCESS) {
+	destroy_server();
+	return -20;
+    }
+
+    /* Create socket */
+    status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &server->sock);
+    if (status != PJ_SUCCESS) {
+	destroy_server();
+	return -30;
+    }
+
+    /* Bind */
+    pj_sockaddr_in_init(&server->addr.ipv4, NULL, 0);
+    status = pj_sock_bind(server->sock, &server->addr, pj_sockaddr_get_len(&server->addr));
+    if (status != PJ_SUCCESS) {
+	destroy_server();
+	return -40;
+    } else {
+	/* Get the bound IP address */
+	int namelen = sizeof(server->addr);
+	pj_sockaddr addr;
+
+	status = pj_sock_getsockname(server->sock, &server->addr, &namelen);
+	if (status != PJ_SUCCESS) {
+	    destroy_server();
+	    return -43;
+	}
+
+	status = pj_gethostip(pj_AF_INET(), &addr);
+	if (status != PJ_SUCCESS) {
+	    destroy_server();
+	    return -45;
+	}
+
+	pj_sockaddr_copy_addr(&server->addr, &addr);
+    }
+
+
+    /* Create worker thread */
+    status = pj_thread_create(pool, "server", &server_thread, 0, 0, 0, &server->thread);
+    if (status != PJ_SUCCESS) {
+	destroy_server();
+	return -30;
+    }
+
+    return 0;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// CLIENT PART
+//
+
+static struct client
+{
+    pj_pool_t	    *pool;
+    pj_stun_session *sess;
+    pj_sem_t	    *test_complete;
+    pj_sock_t	     sock;
+
+    pj_bool_t	     responding;
+    unsigned	     recv_count;
+
+    pj_status_t	     response_status;
+    pj_stun_msg	    *response;
+
+    pj_bool_t	     quit;
+    pj_thread_t	    *thread;
+} *client;
+
+
+static pj_status_t client_send_msg(pj_stun_session *sess,
+				   const void *pkt,
+				   pj_size_t pkt_size,
+				   const pj_sockaddr_t *dst_addr,
+				   unsigned addr_len)
+{
+    pj_ssize_t len = pkt_size;
+
+    PJ_UNUSED_ARG(sess);
+
+    return pj_sock_sendto(client->sock, pkt, &len, 0, dst_addr, addr_len);
+}
+
+
+static void client_on_request_complete( pj_stun_session *sess,
+					pj_status_t status,
+					pj_stun_tx_data *tdata,
+					const pj_stun_msg *response,
+					const pj_sockaddr_t *src_addr,
+					unsigned src_addr_len)
+{
+    PJ_UNUSED_ARG(sess);
+    PJ_UNUSED_ARG(tdata);
+    PJ_UNUSED_ARG(src_addr);
+    PJ_UNUSED_ARG(src_addr_len);
+
+    client->response_status = status;
+    if (response)
+	client->response = pj_stun_msg_clone(client->pool, response);
+
+    pj_sem_post(client->test_complete);
+}
+
+
+static int client_thread(void *unused)
+{
+    PJ_UNUSED_ARG(unused);
+
+    while (!client->quit) {
+	pj_fd_set_t readset;
+	pj_time_val delay = {0, 10};
+
+	/* Also poll the timer heap */
+	pj_timer_heap_poll(stun_cfg.timer_heap, NULL);
+
+	/* Poll client socket */
+	PJ_FD_ZERO(&readset);
+	PJ_FD_SET(client->sock, &readset);
+
+	if (pj_sock_select(client->sock, &readset, NULL, NULL, &delay)==1 &&
+	    PJ_FD_ISSET(client->sock, &readset)) 
+	{
+	    char pkt[1000];
+	    pj_ssize_t len;
+	    pj_status_t status;
+	    pj_sockaddr src_addr;
+	    int src_addr_len;
+
+	    len = sizeof(pkt);
+	    src_addr_len = sizeof(src_addr);
+
+	    status = pj_sock_recvfrom(client->sock, pkt, &len, 0, &src_addr, &src_addr_len);
+	    if (status != PJ_SUCCESS)
+		continue;
+
+	    /* Increment client's receive count */
+	    client->recv_count++;
+
+	    /* Only pass to client if we allow to respond */
+	    if (!client->responding)
+		continue;
+
+	    pj_stun_session_on_rx_pkt(client->sess, pkt, len, 
+				      PJ_STUN_CHECK_PACKET | PJ_STUN_IS_DATAGRAM,
+				      NULL, &src_addr, src_addr_len);
+	}
+ 
+    }
+
+    return 0;
+}
+
+
+static void destroy_client_server(void)
+{
+    if (client->thread) {
+	client->quit = 1;
+	pj_thread_join(client->thread);
+	pj_thread_destroy(client->thread);
+    }
+
+    if (client->sess)
+	pj_stun_session_destroy(client->sess);
+
+    if (client->sock)
+	pj_sock_close(client->sock);
+
+    if (client->test_complete)
+	pj_sem_destroy(client->test_complete);
+
+    if (server)
+	destroy_server();
+}
+
+static int run_client_test(const char *title,
+
+			   pj_bool_t server_responding,
+			   pj_stun_auth_type server_auth_type,
+
+			   pj_stun_auth_type client_auth_type,
+			   const char *realm,
+			   const char *username,
+			   const char *nonce,
+			   const char *password,
+			   pj_bool_t dummy_mi,
+
+			   pj_stun_status expected_error,
+			   pj_stun_status expected_code,
+			   const char *expected_realm,
+			   const char *expected_nonce,
+			   
+			   int (*more_check)(void))
+{
+    pj_pool_t *pool;
+    pj_stun_session_cb sess_cb;
+    pj_stun_auth_cred cred;
+    pj_stun_tx_data *tdata;
+    pj_status_t status;
+    int rc = 0;
+    
+    PJ_LOG(3,(THIS_FILE, "   %s test", title));
+
+    /* Create client */
+    pool = pj_pool_create(mem, "client", 1000, 1000, NULL);
+    client = PJ_POOL_ZALLOC_T(pool, struct client);
+    client->pool = pool;
+    client->responding = PJ_TRUE;
+
+    /* Create STUN session */
+    pj_bzero(&sess_cb, sizeof(sess_cb));
+    sess_cb.on_request_complete = &client_on_request_complete;
+    sess_cb.on_send_msg = &client_send_msg;
+    status = pj_stun_session_create(&stun_cfg, "client", &sess_cb, PJ_FALSE, &client->sess);
+    if (status != PJ_SUCCESS) {
+	destroy_client_server();
+	return -200;
+    }
+
+    /* Create semaphore */
+    status = pj_sem_create(pool, "client", 0, 1, &client->test_complete);
+    if (status != PJ_SUCCESS) {
+	destroy_client_server();
+	return -205;
+    }
+
+    /* Create client socket */
+    status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &client->sock);
+    if (status != PJ_SUCCESS) {
+	destroy_client_server();
+	return -210;
+    }
+
+    /* Bind client socket */
+    status = pj_sock_bind_in(client->sock, 0, 0);
+    if (status != PJ_SUCCESS) {
+	destroy_client_server();
+	return -220;
+    }
+
+    /* Create client thread */
+    status = pj_thread_create(pool, "client", &client_thread, NULL, 0, 0, &client->thread);
+    if (status != PJ_SUCCESS) {
+	destroy_client_server();
+	return -230;
+    }
+
+    /* Initialize credential */
+    pj_bzero(&cred, sizeof(cred));
+    cred.type = PJ_STUN_AUTH_CRED_STATIC;
+    if (realm) cred.data.static_cred.realm = pj_str((char*)realm);
+    if (username) cred.data.static_cred.username = pj_str((char*)username);
+    if (nonce) cred.data.static_cred.nonce = pj_str((char*)nonce);
+    if (password) cred.data.static_cred.data = pj_str((char*)password);
+    cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
+    status = pj_stun_session_set_credential(client->sess, client_auth_type, &cred);
+    if (status != PJ_SUCCESS) {
+	destroy_client_server();
+	return -240;
+    }
+
+    /* Create the server */
+    status = create_std_server(server_auth_type, server_responding);
+    if (status != 0) {
+	destroy_client_server();
+	return status;
+    }
+
+    /* Create request */
+    status = pj_stun_session_create_req(client->sess, PJ_STUN_BINDING_REQUEST, 
+					PJ_STUN_MAGIC, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	destroy_client_server();
+	return -250;
+    }
+
+    /* Add our own attributes if client authentication is set to none */
+    if (client_auth_type == PJ_STUN_AUTH_NONE) {
+	pj_str_t tmp;
+	if (realm)
+	    pj_stun_msg_add_string_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_REALM, pj_cstr(&tmp, realm));
+	if (username)
+	    pj_stun_msg_add_string_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_USERNAME, pj_cstr(&tmp, username));
+	if (nonce)
+	    pj_stun_msg_add_string_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_NONCE, pj_cstr(&tmp, nonce));
+	if (password) {
+	    // ignored
+	}
+	if (dummy_mi) {
+	    pj_stun_msgint_attr *mi;
+
+	    pj_stun_msgint_attr_create(tdata->pool, &mi);
+	    pj_stun_msg_add_attr(tdata->msg, &mi->hdr);
+	}
+	   
+    }
+
+    /* Send the request */
+    status = pj_stun_session_send_msg(client->sess, PJ_FALSE, &server->addr,
+				      pj_sockaddr_get_len(&server->addr), tdata);
+    if (status != PJ_SUCCESS) {
+	destroy_client_server();
+	return -270;
+    }
+
+    /* Wait until test complete */
+    pj_sem_wait(client->test_complete);
+
+
+    /* Verify response */
+    if (expected_error) {
+	if (expected_code != client->response_status) {
+	    char e1[PJ_ERR_MSG_SIZE], e2[PJ_ERR_MSG_SIZE];
+
+	    pj_strerror(expected_code, e1, sizeof(e1));
+	    pj_strerror(client->response_status, e2, sizeof(e2));
+
+	    PJ_LOG(3,(THIS_FILE, "    err: expecting %d (%s) but got %d (%s) response",
+		      expected_code, e1, client->response_status, e2));
+	    rc = -500;
+	} 
+
+    } else {
+	int res_code = 0;
+	pj_stun_realm_attr *arealm;
+	pj_stun_nonce_attr *anonce;
+
+	if (client->response_status != 0) {
+	    PJ_LOG(3,(THIS_FILE, "    err: expecting successful operation but got error %d", 
+		      client->response_status));
+	    rc = -600;
+	    goto done;
+	} 
+
+	if (PJ_STUN_IS_ERROR_RESPONSE(client->response->hdr.type)) {
+	    pj_stun_errcode_attr *aerr = NULL;
+
+	    aerr = (pj_stun_errcode_attr*)
+		   pj_stun_msg_find_attr(client->response, 
+					 PJ_STUN_ATTR_ERROR_CODE, 0);
+	    if (aerr == NULL) {
+		PJ_LOG(3,(THIS_FILE, "    err: received error response without ERROR-CODE"));
+		rc = -610;
+		goto done;
+	    }
+
+	    res_code = aerr->err_code;
+	} else {
+	    res_code = 0;
+	}
+
+	/* Check that code matches */
+	if (expected_code != res_code) {
+	    PJ_LOG(3,(THIS_FILE, "    err: expecting response code %d but got %d",
+		      expected_code, res_code));
+	    rc = -620;
+	    goto done;
+	}
+
+	/* Find REALM and NONCE attributes */
+	arealm = (pj_stun_realm_attr*)
+	         pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_REALM, 0);
+	anonce = (pj_stun_nonce_attr*)
+	         pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_NONCE, 0);
+
+	if (expected_realm) {
+	    if (arealm == NULL) {
+		PJ_LOG(3,(THIS_FILE, "    err: expecting REALM in esponse"));
+		rc = -630;
+		goto done;
+	    }
+	    if (pj_strcmp2(&arealm->value, expected_realm)!=0) {
+		PJ_LOG(3,(THIS_FILE, "    err: REALM mismatch in response"));
+		rc = -640;
+		goto done;
+	    }
+	} else {
+	    if (arealm != NULL) {
+		PJ_LOG(3,(THIS_FILE, "    err: non expecting REALM in response"));
+		rc = -650;
+		goto done;
+	    }
+	}
+
+	if (expected_nonce) {
+	    if (anonce == NULL) {
+		PJ_LOG(3,(THIS_FILE, "    err: expecting NONCE in esponse"));
+		rc = -660;
+		goto done;
+	    }
+	    if (pj_strcmp2(&anonce->value, expected_nonce)!=0) {
+		PJ_LOG(3,(THIS_FILE, "    err: NONCE mismatch in response"));
+		rc = -670;
+		goto done;
+	    }
+	} else {
+	    if (anonce != NULL) {
+		PJ_LOG(3,(THIS_FILE, "    err: non expecting NONCE in response"));
+		rc = -680;
+		goto done;
+	    }
+	}
+    }
+
+    /* Our tests are okay so far. Let caller do some more tests if
+     * it wants to.
+     */
+    if (rc==0 && more_check) {
+	rc = (*more_check)();
+    }
+
+
+done:
+    destroy_client_server();
+    return rc;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// More verification
+//
+
+/* Retransmission test */
+static int retransmit_check(void)
+{
+    if (server->recv_count != PJ_STUN_MAX_TRANSMIT_COUNT)
+	return -700;
+    if (client->recv_count != 0)
+	return -710;
+
+    return 0;
+}
+
+static int long_term_check1(void)
+{
+    /* SHOULD NOT contain USERNAME or MESSAGE-INTEGRITY */
+    if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_USERNAME, 0))
+	return -800;
+    if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0))
+	return -800;
+
+    return 0;
+}
+
+static int long_term_check2(void)
+{
+    /* response SHOULD NOT include a USERNAME, NONCE, REALM or 
+     * MESSAGE-INTEGRITY attribute. 
+     */
+    if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_USERNAME, 0))
+	return -900;
+    if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_NONCE, 0))
+	return -910;
+    if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_REALM, 0))
+	return -920;
+    if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0))
+	return -930;
+
+    return 0;
+}
+
+static int long_term_check3(void)
+{
+    /* response SHOULD NOT include a USERNAME, NONCE, and REALM */
+    if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_USERNAME, 0))
+	return -1000;
+    if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_NONCE, 0))
+	return -1010;
+    if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_REALM, 0))
+	return -1020;
+
+    return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// TEST MAIN
+//
+
+
+int sess_auth_test(void)
+{
+    pj_pool_t *pool;
+    int rc;
+
+    PJ_LOG(3,(THIS_FILE, "  STUN session authentication test"));
+
+    /* Init STUN config */
+    pj_stun_config_init(&stun_cfg, mem, 0, NULL, NULL);
+
+    /* Create pool and timer heap */
+    pool = pj_pool_create(mem, "authtest", 200, 200, NULL);
+    if (pj_timer_heap_create(pool, 20, &stun_cfg.timer_heap)) {
+	pj_pool_release(pool);
+	return -5;
+    }
+
+    /* Basic retransmission test */
+    rc = run_client_test("Retransmission",  // title
+			 PJ_FALSE,	    // server responding
+			 PJ_STUN_AUTH_NONE, // server auth
+			 PJ_STUN_AUTH_NONE, // client auth
+			 NULL,		    // realm
+			 NULL,		    // username
+			 NULL,		    // nonce
+			 NULL,		    // password
+			 PJ_FALSE,	    // dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJNATH_ESTUNTIMEDOUT,// expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 &retransmit_check  // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /*
+     * Short term credential.
+     * draft-ietf-behave-rfc3489bis-15#section-10.1.2
+     */
+
+    /*
+     * If the message does not contain both a MESSAGE-INTEGRITY and a
+     * USERNAME attribute, If the message is a request, the server MUST
+     * reject the request with an error response.  This response MUST
+     * use an error code of 400 (Bad Request).
+     */
+    rc = run_client_test("Missing MESSAGE-INTEGRITY (short term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_SHORT_TERM, // server auth
+			 PJ_STUN_AUTH_NONE, // client auth
+			 NULL,		    // realm
+			 NULL,		    // username
+			 NULL,		    // nonce
+			 NULL,		    // password
+			 PJ_FALSE,	    // dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(400),// expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 NULL		    // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* If the USERNAME does not contain a username value currently valid
+     * within the server: If the message is a request, the server MUST 
+     * reject the request with an error response.  This response MUST use
+     * an error code of 401 (Unauthorized).
+     */
+    rc = run_client_test("USERNAME mismatch (short term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_SHORT_TERM, // server auth
+			 PJ_STUN_AUTH_SHORT_TERM, // client auth
+			 NULL,		    // realm
+			 "anotheruser",	    // username
+			 NULL,		    // nonce
+			 "anotherpass",	    // password
+			 PJ_FALSE,	    // dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(401),// expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 NULL		    // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* Using the password associated with the username, compute the value
+     * for the message-integrity as described in Section 15.4.  If the
+     * resulting value does not match the contents of the MESSAGE-
+     * INTEGRITY attribute:
+     *
+     * - If the message is a request, the server MUST reject the request
+     *   with an error response.  This response MUST use an error code
+     *   of 401 (Unauthorized).
+     */
+    rc = run_client_test("MESSAGE-INTEGRITY mismatch (short term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_SHORT_TERM, // server auth
+			 PJ_STUN_AUTH_SHORT_TERM, // client auth
+			 NULL,		    // realm
+			 USERNAME,	    // username
+			 NULL,		    // nonce
+			 "anotherpass",	    // password
+			 PJ_FALSE,	    // dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(401),// expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 NULL		    // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* USERNAME is not present, server must respond with 400 (Bad
+     * Request).
+     */
+    rc = run_client_test("Missing USERNAME (short term)",// title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_SHORT_TERM, // server auth
+			 PJ_STUN_AUTH_NONE, // client auth
+			 NULL,		    // realm
+			 NULL,		    // username
+			 NULL,		    // nonce
+			 NULL,		    // password
+			 PJ_TRUE,	    // dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(400),	    // expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 NULL		    // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* Successful short term authentication */
+    rc = run_client_test("Successful scenario (short term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_SHORT_TERM, // server auth
+			 PJ_STUN_AUTH_SHORT_TERM, // client auth
+			 NULL,		    // realm
+			 USERNAME,	    // username
+			 NULL,		    // nonce
+			 PASSWORD,	    // password
+			 PJ_FALSE,	    // dummy MI
+			 PJ_FALSE,	    // expected error
+			 PJ_SUCCESS,	    // expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 NULL		    // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /*
+     * (our own) Extended tests for long term credential
+     */
+
+    /* When server wants to use short term credential, but request has
+     * REALM, reject with .... 401 ???
+     */
+    rc = run_client_test("Unwanted REALM (short term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_SHORT_TERM, // server auth
+			 PJ_STUN_AUTH_NONE, // client auth
+			 REALM,		    // realm
+			 USERNAME,	    // username
+			 NULL,		    // nonce
+			 PASSWORD,	    // password
+			 PJ_TRUE,	    // dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(401),	    // expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 &long_term_check2  // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+
+    /*
+     * Long term credential.
+     * draft-ietf-behave-rfc3489bis-15#section-10.2.2
+     */
+
+    /* If the message does not contain a MESSAGE-INTEGRITY attribute, the
+     * server MUST generate an error response with an error code of 401
+     * (Unauthorized).  This response MUST include a REALM value.  It is
+     * RECOMMENDED that the REALM value be the domain name of the
+     * provider of the STUN server.  The response MUST include a NONCE,
+     * selected by the server.  The response SHOULD NOT contain a
+     * USERNAME or MESSAGE-INTEGRITY attribute.
+     */
+    rc = run_client_test("Missing M-I (long term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_LONG_TERM, // server auth
+			 PJ_STUN_AUTH_NONE, // client auth
+			 NULL,		    // client realm
+			 NULL,		    // client username
+			 NULL,		    // client nonce
+			 NULL,		    // client password
+			 PJ_FALSE,	    // client dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(401), // expected code
+			 REALM,		    // expected realm
+			 NONCE,		    // expected nonce
+			 &long_term_check1  // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* If the message contains a MESSAGE-INTEGRITY attribute, but is
+     * missing the USERNAME, REALM or NONCE attributes, the server MUST
+     * generate an error response with an error code of 400 (Bad
+     * Request).  This response SHOULD NOT include a USERNAME, NONCE,
+     * REALM or MESSAGE-INTEGRITY attribute.
+     */
+    /* Missing USERNAME */
+    rc = run_client_test("Missing USERNAME (long term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_LONG_TERM, // server auth
+			 PJ_STUN_AUTH_NONE, // client auth
+			 REALM,		    // client realm
+			 NULL,		    // client username
+			 NONCE,		    // client nonce
+			 PASSWORD,	    // client password
+			 PJ_TRUE,	    // client dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(400), // expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 &long_term_check2  // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* Missing REALM */
+    rc = run_client_test("Missing REALM (long term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_LONG_TERM, // server auth
+			 PJ_STUN_AUTH_NONE, // client auth
+			 NULL,		    // client realm
+			 USERNAME,	    // client username
+			 NONCE,		    // client nonce
+			 PASSWORD,	    // client password
+			 PJ_TRUE,	    // client dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(400), // expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 &long_term_check2  // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* Missing NONCE */
+    rc = run_client_test("Missing NONCE (long term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_LONG_TERM, // server auth
+			 PJ_STUN_AUTH_NONE, // client auth
+			 REALM,		    // client realm
+			 USERNAME,	    // client username
+			 NULL,		    // client nonce
+			 PASSWORD,	    // client password
+			 PJ_TRUE,	    // client dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(400), // expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 &long_term_check2  // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* If the NONCE is no longer valid, the server MUST generate an error
+     * response with an error code of 438 (Stale Nonce).  This response
+     * MUST include a NONCE and REALM attribute and SHOULD NOT incude the
+     * USERNAME or MESSAGE-INTEGRITY attribute.  Servers can invalidate
+     * nonces in order to provide additional security.  See Section 4.3
+     * of [RFC2617] for guidelines.    
+     */
+    // how??
+
+    /* If the username in the USERNAME attribute is not valid, the server
+     * MUST generate an error response with an error code of 401
+     * (Unauthorized).  This response MUST include a REALM value.  It is
+     * RECOMMENDED that the REALM value be the domain name of the
+     * provider of the STUN server.  The response MUST include a NONCE,
+     * selected by the server.  The response SHOULD NOT contain a
+     * USERNAME or MESSAGE-INTEGRITY attribute.
+     */
+    rc = run_client_test("Invalid username (long term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_LONG_TERM, // server auth
+			 PJ_STUN_AUTH_LONG_TERM, // client auth
+			 REALM,		    // client realm
+			 "anotheruser",	    // client username
+			 "a nonce",	    // client nonce
+			 "somepassword",    // client password
+			 PJ_FALSE,	    // client dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(401), // expected code
+			 REALM,		    // expected realm
+			 NONCE,		    // expected nonce
+			 &long_term_check1  // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* Successful long term authentication */
+    rc = run_client_test("Successful scenario (long term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_LONG_TERM, // server auth
+			 PJ_STUN_AUTH_LONG_TERM, // client auth
+			 REALM,		    // client realm
+			 USERNAME,	    // client username
+			 "anothernonce",    // client nonce
+			 PASSWORD,	    // client password
+			 PJ_FALSE,	    // client dummy MI
+			 PJ_FALSE,	    // expected error
+			 0,		    // expected code
+			 NULL,		    // expected realm
+			 NULL,		    // expected nonce
+			 &long_term_check3  // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /*
+     * (our own) Extended tests for long term credential
+     */
+
+    /* If REALM doesn't match, server must respond with 401
+     */
+    rc = run_client_test("Invalid REALM (long term)",  // title
+			 PJ_TRUE,	    // server responding
+			 PJ_STUN_AUTH_LONG_TERM, // server auth
+			 PJ_STUN_AUTH_LONG_TERM, // client auth
+			 "anotherrealm",    // client realm
+			 USERNAME,	    // client username
+			 NONCE,		    // client nonce
+			 PASSWORD,	    // client password
+			 PJ_FALSE,	    // client dummy MI
+			 PJ_TRUE,	    // expected error
+			 PJ_STATUS_FROM_STUN_CODE(401), // expected code
+			 REALM,		    // expected realm
+			 NONCE,		    // expected nonce
+			 &long_term_check1  // more check
+			 );
+    if (rc != 0) {
+	goto done;
+    }
+
+    /* Invalid HMAC */
+
+    /* Valid static short term, without NONCE */
+
+    /* Valid static short term, WITH NONCE */
+
+    /* Valid static long term (with NONCE */
+
+    /* Valid dynamic short term (without NONCE) */
+
+    /* Valid dynamic short term (with NONCE) */
+
+    /* Valid dynamic long term (with NONCE) */
+
+
+done:
+    pj_timer_heap_destroy(stun_cfg.timer_heap);
+    pj_pool_release(pool);
+    return rc;
+}
diff --git a/pjnath/src/pjnath-test/stun.c b/pjnath/src/pjnath-test/stun.c
index c0d7438..1b11f89 100644
--- a/pjnath/src/pjnath-test/stun.c
+++ b/pjnath/src/pjnath-test/stun.c
@@ -269,7 +269,7 @@
 	    continue;
 
 	/* Try to encode message */
-	pj_stun_create_key(pool, &key, NULL, &USERNAME, &PASSWORD);
+	pj_stun_create_key(pool, &key, NULL, &USERNAME, PJ_STUN_PASSWD_PLAIN, &PASSWORD);
 	status = pj_stun_msg_encode(msg, buf, sizeof(buf), 0, &key, &len);
 	if (status != PJ_SUCCESS) {
 	    PJ_LOG(1,(THIS_FILE, "    encode error: %s", err(status)));
@@ -408,55 +408,6 @@
     return 0;
 }
 
-static int auth_test(void)
-{
-    /* REALM and USERNAME is present, but MESSAGE-INTEGRITY is not present.
-     * For short term, must with reply 401 without REALM.
-     * For long term, must reply with 401 with REALM.
-     */
-
-    /* USERNAME is not present, server must respond with 432 (Missing
-     * Username).
-     */
-
-    /* If long term credential is wanted and REALM is not present, server 
-     * must respond with 434 (Missing Realm) 
-     */
-
-    /* If REALM doesn't match, server must respond with 434 (Missing Realm)
-     * too, containing REALM and NONCE attribute.
-     */
-
-    /* When long term authentication is wanted and NONCE is NOT present,
-     * server must respond with 435 (Missing Nonce), containing REALM and
-     * NONCE attribute.
-     */
-
-    /* Simulate 438 (Stale Nonce) */
-    
-    /* Simulate 436 (Unknown Username) */
-
-    /* When server wants to use short term credential, but request has
-     * REALM, reject with .... ???
-     */
-
-    /* Invalid HMAC */
-
-    /* Valid static short term, without NONCE */
-
-    /* Valid static short term, WITH NONCE */
-
-    /* Valid static long term (with NONCE */
-
-    /* Valid dynamic short term (without NONCE) */
-
-    /* Valid dynamic short term (with NONCE) */
-
-    /* Valid dynamic long term (with NONCE) */
-
-    return 0;
-}
-
 typedef struct test_vector test_vector;
 
 static pj_stun_msg* create_msgint1(pj_pool_t *pool, test_vector *v);
@@ -614,7 +565,7 @@
 	    pj_str_t s1, s2;
 
 	    pj_stun_create_key(pool, &key, NULL, pj_cstr(&s1, v->username), 
-			       pj_cstr(&s2, v->password));
+			       PJ_STUN_PASSWD_PLAIN, pj_cstr(&s2, v->password));
 	    pj_stun_msg_encode(msg, buf, sizeof(buf), 0, &key, &len);
 
 	} else {
@@ -751,10 +702,6 @@
     if (rc != 0)
 	goto on_return;
 
-    rc = auth_test();
-    if (rc != 0)
-	goto on_return;
-
     rc = fingerprint_test_vector();
     if (rc != 0)
 	goto on_return;
diff --git a/pjnath/src/pjnath-test/test.c b/pjnath/src/pjnath-test/test.c
index fc1c124..312c1ea 100644
--- a/pjnath/src/pjnath-test/test.c
+++ b/pjnath/src/pjnath-test/test.c
@@ -68,6 +68,7 @@
 
 #if INCLUDE_STUN_TEST
     DO_TEST(stun_test());
+    DO_TEST(sess_auth_test());
 #endif
 
 #if INCLUDE_ICE_TEST
diff --git a/pjnath/src/pjnath-test/test.h b/pjnath/src/pjnath-test/test.h
index 39ad411..713f3da 100644
--- a/pjnath/src/pjnath-test/test.h
+++ b/pjnath/src/pjnath-test/test.h
@@ -23,9 +23,10 @@
 #define INCLUDE_STUN_TEST	    1
 #define INCLUDE_ICE_TEST	    1
 
-extern int stun_test(void);
-extern int ice_test(void);
-extern int test_main(void);
+int stun_test(void);
+int sess_auth_test(void);
+int ice_test(void);
+int test_main(void);
 
 extern void app_perror(const char *title, pj_status_t rc);
 extern pj_pool_factory *mem;
diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c
index 71ff41c..5ecac38 100644
--- a/pjnath/src/pjnath/ice_session.c
+++ b/pjnath/src/pjnath/ice_session.c
@@ -120,7 +120,7 @@
 static pj_status_t on_stun_rx_request(pj_stun_session *sess,
 				      const pj_uint8_t *pkt,
 				      unsigned pkt_len,
-				      const pj_stun_msg *msg,
+				      const pj_stun_rx_data *rdata,
 				      const pj_sockaddr_t *src_addr,
 				      unsigned src_addr_len);
 static void on_stun_request_complete(pj_stun_session *stun_sess,
@@ -147,14 +147,14 @@
 				      pj_str_t *realm,
 				      pj_str_t *username,
 				      pj_str_t *nonce,
-				      int *data_type,
+				      pj_stun_passwd_type *data_type,
 				      pj_str_t *data);
 static pj_status_t stun_auth_get_password(const pj_stun_msg *msg,
 					  void *user_data, 
 					  const pj_str_t *realm,
 					  const pj_str_t *username,
 					  pj_pool_t *pool,
-					  int *data_type,
+					  pj_stun_passwd_type *data_type,
 					  pj_str_t *data);
 
 
@@ -232,7 +232,8 @@
     auth_cred.data.dyn_cred.get_cred = &stun_auth_get_cred;
     auth_cred.data.dyn_cred.get_password = &stun_auth_get_password;
     auth_cred.data.dyn_cred.user_data = comp->stun_sess;
-    pj_stun_session_set_credential(comp->stun_sess, &auth_cred);
+    pj_stun_session_set_credential(comp->stun_sess, PJ_STUN_AUTH_SHORT_TERM,
+				   &auth_cred);
 
     return PJ_SUCCESS;
 }
@@ -446,7 +447,7 @@
 				      pj_str_t *realm,
 				      pj_str_t *username,
 				      pj_str_t *nonce,
-				      int *data_type,
+				      pj_stun_passwd_type *data_type,
 				      pj_str_t *data)
 {
     pj_stun_session *sess = (pj_stun_session *)user_data;
@@ -461,12 +462,12 @@
 	 * incoming requests.
 	 */
 	*username = ice->rx_uname;
-	*data_type = 0;
+	*data_type = PJ_STUN_PASSWD_PLAIN;
 	*data = ice->rx_pass;
     }
     else {
 	*username = ice->tx_uname;
-	*data_type = 0;
+	*data_type = PJ_STUN_PASSWD_PLAIN;
 	*data = ice->tx_pass;
     }
 
@@ -479,7 +480,7 @@
 					  const pj_str_t *realm,
 					  const pj_str_t *username,
 					  pj_pool_t *pool,
-					  int *data_type,
+					  pj_stun_passwd_type *data_type,
 					  pj_str_t *data)
 {
     pj_stun_session *sess = (pj_stun_session *)user_data;
@@ -496,7 +497,7 @@
 	/* Verify username */
 	if (pj_strcmp(username, &ice->tx_uname) != 0)
 	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
-	*data_type = 0;
+	*data_type = PJ_STUN_PASSWD_PLAIN;
 	*data = ice->tx_pass;
 
     } else {
@@ -521,7 +522,7 @@
 	if (pj_strcmp(&ufrag, &ice->rx_ufrag) != 0)
 	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
 
-	*data_type = 0;
+	*data_type = PJ_STUN_PASSWD_PLAIN;
 	*data = ice->rx_pass;
 
     }
@@ -1903,11 +1904,12 @@
 static pj_status_t on_stun_rx_request(pj_stun_session *sess,
 				      const pj_uint8_t *pkt,
 				      unsigned pkt_len,
-				      const pj_stun_msg *msg,
+				      const pj_stun_rx_data *rdata,
 				      const pj_sockaddr_t *src_addr,
 				      unsigned src_addr_len)
 {
     stun_data *sd;
+    const pj_stun_msg *msg = rdata->msg;
     pj_ice_sess *ice;
     pj_stun_priority_attr *prio_attr;
     pj_stun_use_candidate_attr *uc_attr;
@@ -1921,7 +1923,7 @@
 
     /* Reject any requests except Binding request */
     if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) {
-	status = pj_stun_session_create_res(sess, msg, 
+	status = pj_stun_session_create_res(sess, rdata, 
 					    PJ_STUN_SC_BAD_REQUEST,
 					    NULL, &tdata);
 	if (status != PJ_SUCCESS)
@@ -1992,7 +1994,7 @@
 	    pj_ice_sess_change_role(ice, PJ_ICE_SESS_ROLE_CONTROLLED);
 	} else {
 	    /* Generate 487 response */
-	    status = pj_stun_session_create_res(sess, msg, 
+	    status = pj_stun_session_create_res(sess, rdata, 
 					        PJ_STUN_SC_ROLE_CONFLICT,
 						NULL, &tdata);
 	    if (status == PJ_SUCCESS) {
@@ -2008,7 +2010,7 @@
     {
 	if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) {
 	    /* Generate 487 response */
-	    status = pj_stun_session_create_res(sess, msg, 
+	    status = pj_stun_session_create_res(sess, rdata, 
 					        PJ_STUN_SC_ROLE_CONFLICT,
 						NULL, &tdata);
 	    if (status == PJ_SUCCESS) {
@@ -2028,7 +2030,7 @@
     /* 
      * First send response to this request 
      */
-    status = pj_stun_session_create_res(sess, msg, 0, NULL, &tdata);
+    status = pj_stun_session_create_res(sess, rdata, 0, NULL, &tdata);
     if (status != PJ_SUCCESS) {
 	pj_mutex_unlock(ice->mutex);
 	return status;
diff --git a/pjnath/src/pjnath/stun_auth.c b/pjnath/src/pjnath/stun_auth.c
index 4a6024f..4ef2959 100644
--- a/pjnath/src/pjnath/stun_auth.c
+++ b/pjnath/src/pjnath/stun_auth.c
@@ -19,9 +19,11 @@
 #include <pjnath/stun_auth.h>
 #include <pjnath/errno.h>
 #include <pjlib-util/hmac_sha1.h>
+#include <pjlib-util/md5.h>
 #include <pjlib-util/sha1.h>
 #include <pj/assert.h>
 #include <pj/log.h>
+#include <pj/pool.h>
 #include <pj/string.h>
 
 #define THIS_FILE   "stun_auth.c"
@@ -53,6 +55,99 @@
 }
 
 
+/*
+ * Duplicate request credential.
+ */
+PJ_DEF(void) pj_stun_req_cred_info_dup( pj_pool_t *pool,
+					pj_stun_req_cred_info *dst,
+					const pj_stun_req_cred_info *src)
+{
+    pj_strdup(pool, &dst->realm, &src->realm);
+    pj_strdup(pool, &dst->username, &src->username);
+    pj_strdup(pool, &dst->nonce, &src->nonce);
+    pj_strdup(pool, &dst->auth_key, &src->auth_key);
+}
+
+
+/* Calculate HMAC-SHA1 key for long term credential, by getting
+ * MD5 digest of username, realm, and password. 
+ */
+static void calc_md5_key(pj_uint8_t digest[16],
+			 const pj_str_t *realm,
+			 const pj_str_t *username,
+			 const pj_str_t *passwd)
+{
+    /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking
+     * the MD5 hash of the result of concatenating the following five
+     * fields: (1) The username, with any quotes and trailing nulls
+     * removed, (2) A single colon, (3) The realm, with any quotes and
+     * trailing nulls removed, (4) A single colon, and (5) The 
+     * password, with any trailing nulls removed.
+     */
+    pj_md5_context ctx;
+    pj_str_t s;
+
+    pj_md5_init(&ctx);
+
+#define REMOVE_QUOTE(s)	if (s.slen && *s.ptr=='"') \
+			    s.ptr++, s.slen--; \
+			if (s.slen && s.ptr[s.slen-1]=='"') \
+			    s.slen--;
+
+    /* Add username */
+    s = *username;
+    REMOVE_QUOTE(s);
+    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
+
+    /* Add single colon */
+    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
+
+    /* Add realm */
+    s = *realm;
+    REMOVE_QUOTE(s);
+    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
+
+#undef REMOVE_QUOTE
+
+    /* Another colon */
+    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
+
+    /* Add password */
+    pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, passwd->slen);
+
+    /* Done */
+    pj_md5_final(&ctx, digest);
+}
+
+
+/*
+ * Create authentication key to be used for encoding the message with
+ * MESSAGE-INTEGRITY. 
+ */
+PJ_DEF(void) pj_stun_create_key(pj_pool_t *pool,
+				pj_str_t *key,
+				const pj_str_t *realm,
+				const pj_str_t *username,
+				pj_stun_passwd_type data_type,
+				const pj_str_t *data)
+{
+    PJ_ASSERT_ON_FAIL(pool && key && username && data, return);
+
+    if (realm && realm->slen) {
+	if (data_type == PJ_STUN_PASSWD_PLAIN) {
+	    key->ptr = (char*) pj_pool_alloc(pool, 16);
+	    calc_md5_key((pj_uint8_t*)key->ptr, realm, username, data);
+	    key->slen = 16;
+	} else {
+	    pj_strdup(pool, key, data);
+	}
+    } else {
+	pj_assert(data_type == PJ_STUN_PASSWD_PLAIN);
+	pj_strdup(pool, key, data);
+    }
+}
+
+
 PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
 {
     return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]);
@@ -86,8 +181,8 @@
     if (rc != PJ_SUCCESS)
 	return rc;
 
-
-    if (realm && realm->slen) {
+    /* SHOULD NOT add REALM, NONCE, USERNAME, and M-I on 400 response */
+    if (err_code!=400 && realm && realm->slen) {
 	rc = pj_stun_msg_add_string_attr(pool, response,
 					 PJ_STUN_ATTR_REALM, 
 					 realm);
@@ -101,7 +196,7 @@
 	}
     }
 
-    if (nonce && nonce->slen) {
+    if (err_code!=400 && nonce && nonce->slen) {
 	rc = pj_stun_msg_add_string_attr(pool, response,
 					 PJ_STUN_ATTR_NONCE, 
 					 nonce);
@@ -121,20 +216,20 @@
 					         const pj_stun_msg *msg,
 					         pj_stun_auth_cred *cred,
 					         pj_pool_t *pool,
-						 pj_str_t *auth_key,
+						 pj_stun_req_cred_info *p_info,
 					         pj_stun_msg **p_response)
 {
-    pj_str_t realm, nonce, password;
+    pj_stun_req_cred_info tmp_info;
     const pj_stun_msgint_attr *amsgi;
     unsigned i, amsgi_pos;
     pj_bool_t has_attr_beyond_mi;
     const pj_stun_username_attr *auser;
-    pj_bool_t username_ok;
     const pj_stun_realm_attr *arealm;
     const pj_stun_realm_attr *anonce;
     pj_hmac_sha1_context ctx;
     pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
-    pj_str_t key;
+    pj_stun_status err_code;
+    const char *err_text = NULL;
     pj_status_t status;
 
     /* msg and credential MUST be specified */
@@ -149,18 +244,24 @@
     if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
 	p_response = NULL;
 
+    if (p_info == NULL)
+	p_info = &tmp_info;
+
+    pj_bzero(p_info, sizeof(pj_stun_req_cred_info));
+
     /* Get realm and nonce from credential */
-    realm.slen = nonce.slen = 0;
+    p_info->realm.slen = p_info->nonce.slen = 0;
     if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
-	realm = cred->data.static_cred.realm;
-	nonce = cred->data.static_cred.nonce;
+	p_info->realm = cred->data.static_cred.realm;
+	p_info->nonce = cred->data.static_cred.nonce;
     } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
 	status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data,
-					      pool, &realm, &nonce);
+					      pool, &p_info->realm, 
+					      &p_info->nonce);
 	if (status != PJ_SUCCESS)
 	    return status;
     } else {
-	pj_assert(!"Unexpected");
+	pj_assert(!"Invalid credential type");
 	return PJ_EBUG;
     }
 
@@ -184,14 +285,9 @@
 	   for short term, and 401 for long term.
 	   The rule has been changed from rfc3489bis-06
 	*/
-	int code;
-
-	code = realm.slen ? PJ_STUN_SC_UNAUTHORIZED : PJ_STUN_SC_BAD_REQUEST;
-	if (p_response) {
-	    create_challenge(pool, msg, code, NULL,
-			     &realm, &nonce, p_response);
-	}
-	return PJ_STATUS_FROM_STUN_CODE(code);
+	err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED : 
+		    PJ_STUN_SC_BAD_REQUEST;
+	goto on_auth_failed;
     }
 
     /* Next check that USERNAME is present */
@@ -202,48 +298,67 @@
 	   for both short and long term, since M-I is present.
 	   The rule has been changed from rfc3489bis-06
 	*/
-	int code = PJ_STUN_SC_BAD_REQUEST;
-	if (p_response) {
-	    create_challenge(pool, msg, code, "Missing USERNAME",
-			     &realm, &nonce, p_response);
-	}
-	return PJ_STATUS_FROM_STUN_CODE(code);
+	err_code = PJ_STUN_SC_BAD_REQUEST;
+	err_text = "Missing USERNAME";
+	goto on_auth_failed;
     }
 
     /* Get REALM, if any */
     arealm = (const pj_stun_realm_attr*)
 	     pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
 
+    /* Reject with 400 if we have long term credential and the request
+     * is missing REALM attribute.
+     */
+    if (p_info->realm.slen && arealm==NULL) {
+	err_code = PJ_STUN_SC_BAD_REQUEST;
+	err_text = "Missing REALM";
+	goto on_auth_failed;
+    }
+
     /* Check if username match */
     if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
+	pj_bool_t username_ok;
 	username_ok = !pj_strcmp(&auser->value, 
 				 &cred->data.static_cred.username);
-	password = cred->data.static_cred.data;
+	if (username_ok) {
+	    pj_strdup(pool, &p_info->username, 
+		      &cred->data.static_cred.username);
+	    pj_stun_create_key(pool, &p_info->auth_key, &p_info->realm,
+			       &auser->value, cred->data.static_cred.data_type,
+			       &cred->data.static_cred.data);
+	} else {
+	    /* Username mismatch */
+	    /* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should 
+	     * return 401 
+	     */
+	    err_code = PJ_STUN_SC_UNAUTHORIZED;
+	    goto on_auth_failed;
+	}
     } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
-	int data_type = 0;
+	pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN;
+	pj_str_t password;
 	pj_status_t rc;
+
 	rc = cred->data.dyn_cred.get_password(msg, 
 					      cred->data.dyn_cred.user_data,
 					      (arealm?&arealm->value:NULL),
 					      &auser->value, pool,
 					      &data_type, &password);
-	username_ok = (rc == PJ_SUCCESS);
+	if (rc == PJ_SUCCESS) {
+	    pj_strdup(pool, &p_info->username, &auser->value);
+	    pj_stun_create_key(pool, &p_info->auth_key, 
+			       (arealm?&arealm->value:NULL), &auser->value, 
+			       data_type, &password);
+	} else {
+	    err_code = PJ_STUN_SC_UNAUTHORIZED;
+	    goto on_auth_failed;
+	}
     } else {
-	username_ok = PJ_TRUE;
-	password.slen = 0;
+	pj_assert(!"Invalid credential type");
+	return PJ_EBUG;
     }
 
-    if (!username_ok) {
-	/* Username mismatch */
-	/* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should 
-	 * return 401 
-	 */
-	if (p_response) {
-	    create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, NULL,
-			     &realm, &nonce, p_response);
-	}
-	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
-    }
 
 
     /* Get NONCE attribute */
@@ -251,40 +366,33 @@
 	     pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0);
 
     /* Check for long term/short term requirements. */
-    if (realm.slen != 0 && arealm == NULL) {
+    if (p_info->realm.slen != 0 && arealm == NULL) {
 	/* Long term credential is required and REALM is not present */
-	if (p_response) {
-	    create_challenge(pool, msg, PJ_STUN_SC_BAD_REQUEST, 
-			     "Missing REALM",
-			     &realm, &nonce, p_response);
-	}
-	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
+	err_code = PJ_STUN_SC_BAD_REQUEST;
+	err_text = "Missing REALM";
+	goto on_auth_failed;
 
-    } else if (realm.slen != 0 && arealm != NULL) {
+    } else if (p_info->realm.slen != 0 && arealm != NULL) {
 	/* We want long term, and REALM is present */
 
 	/* NONCE must be present. */
-	if (anonce == NULL && nonce.slen) {
-	    if (p_response) {
-		create_challenge(pool, msg, PJ_STUN_SC_BAD_REQUEST, 
-				 "Missing NONCE", &realm, &nonce, p_response);
-	    }
-	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
+	if (anonce == NULL && p_info->nonce.slen) {
+	    err_code = PJ_STUN_SC_BAD_REQUEST;
+	    err_text = "Missing NONCE";
+	    goto on_auth_failed;
 	}
 
 	/* Verify REALM matches */
-	if (pj_stricmp(&arealm->value, &realm)) {
+	if (pj_stricmp(&arealm->value, &p_info->realm)) {
 	    /* REALM doesn't match */
-	    if (p_response) {
-		create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, 
-				 "Invalid REALM", &realm, &nonce, p_response);
-	    }
-	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
+	    err_code = PJ_STUN_SC_UNAUTHORIZED;
+	    err_text = "Invalid REALM";
+	    goto on_auth_failed;
 	}
 
 	/* Valid case, will validate the message integrity later */
 
-    } else if (realm.slen == 0 && arealm != NULL) {
+    } else if (p_info->realm.slen == 0 && arealm != NULL) {
 	/* We want to use short term credential, but client uses long
 	 * term credential. The draft doesn't mention anything about
 	 * switching between long term and short term.
@@ -293,16 +401,14 @@
 	/* For now just accept the credential, anyway it will probably
 	 * cause wrong message integrity value later.
 	 */
-    } else if (realm.slen==0 && arealm == NULL) {
+    } else if (p_info->realm.slen==0 && arealm == NULL) {
 	/* Short term authentication is wanted, and one is supplied */
 
 	/* Application MAY request NONCE to be supplied */
-	if (nonce.slen != 0) {
-	    if (p_response) {
-		create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, 
-				 "NONCE required", &realm, &nonce, p_response);
-	    }
-	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
+	if (p_info->nonce.slen != 0) {
+	    err_code = PJ_STUN_SC_UNAUTHORIZED;
+	    err_text = "NONCE required";
+	    goto on_auth_failed;
 	}
     }
 
@@ -321,31 +427,22 @@
 	} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
 	    ok = PJ_TRUE;
 	} else {
-	    if (nonce.slen) {
-		ok = !pj_strcmp(&anonce->value, &nonce);
+	    if (p_info->nonce.slen) {
+		ok = !pj_strcmp(&anonce->value, &p_info->nonce);
 	    } else {
 		ok = PJ_TRUE;
 	    }
 	}
 
 	if (!ok) {
-	    if (p_response) {
-		create_challenge(pool, msg, PJ_STUN_SC_STALE_NONCE, 
-				 NULL, &realm, &nonce, p_response);
-	    }
-	    if (auth_key) {
-		pj_stun_create_key(pool, auth_key, &realm, 
-				   &auser->value, &password);
-	    }
-	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_STALE_NONCE);
+	    err_code = PJ_STUN_SC_STALE_NONCE;
+	    goto on_auth_failed;
 	}
     }
 
-    /* Calculate key */
-    pj_stun_create_key(pool, &key, &realm, &auser->value, &password);
-
     /* Now calculate HMAC of the message. */
-    pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key.ptr, key.slen);
+    pj_hmac_sha1_init(&ctx, (pj_uint8_t*)p_info->auth_key.ptr, 
+		      p_info->auth_key.slen);
 
 #if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
     /* Pre rfc3489bis-06 style of calculation */
@@ -382,15 +479,20 @@
     if (pj_memcmp(amsgi->hmac, digest, 20)) {
 	/* HMAC value mismatch */
 	/* According to rfc3489bis-10 Sec 10.1.2 we should return 401 */
-	if (p_response) {
-	    create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED,
-			     NULL, &realm, &nonce, p_response);
-	}
-	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
+	err_code = PJ_STUN_SC_UNAUTHORIZED;
+	err_text = "MESSAGE-INTEGRITY mismatch";
+	goto on_auth_failed;
     }
 
     /* Everything looks okay! */
     return PJ_SUCCESS;
+
+on_auth_failed:
+    if (p_response) {
+	create_challenge(pool, msg, err_code, err_text,
+			 &p_info->realm, &p_info->nonce, p_response);
+    }
+    return PJ_STATUS_FROM_STUN_CODE(err_code);
 }
 
 
@@ -425,11 +527,10 @@
     switch (err_attr->err_code) {
     case PJ_STUN_SC_BAD_REQUEST:	    /* 400 (Bad Request)	    */
     case PJ_STUN_SC_UNAUTHORIZED:	    /* 401 (Unauthorized)	    */
-    //case PJ_STUN_SC_STALE_CREDENTIALS:    /* 430 (Stale Credential)	    */
-    //case PJ_STUN_SC_MISSING_USERNAME:	    /* 432 (Missing Username)	    */
-    //case PJ_STUN_SC_MISSING_REALM:	    /* 434 (Missing Realm)	    */
-    //case PJ_STUN_SC_UNKNOWN_USERNAME:	    /* 436 (Unknown Username)	    */
-    //case PJ_STUN_SC_INTEGRITY_CHECK_FAILURE:/* 431 (Integrity Check Fail) */
+
+    /* Due to the way this response is generated here, we can't really
+     * authenticate 420 (Unknown Attribute) response			    */
+    case PJ_STUN_SC_UNKNOWN_ATTRIBUTE:
 	return PJ_FALSE;
     default:
 	return PJ_TRUE;
diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c
index 3e01d4a..716683f 100644
--- a/pjnath/src/pjnath/stun_msg.c
+++ b/pjnath/src/pjnath/stun_msg.c
@@ -20,7 +20,6 @@
 #include <pjnath/errno.h>
 #include <pjlib-util/crc32.h>
 #include <pjlib-util/hmac_sha1.h>
-#include <pjlib-util/md5.h>
 #include <pj/assert.h>
 #include <pj/log.h>
 #include <pj/os.h>
@@ -90,6 +89,7 @@
 			       void **p_attr);
     pj_status_t (*encode_attr)(const void *a, pj_uint8_t *buf, 
 			       unsigned len, unsigned *printed);
+    void*       (*clone_attr)(pj_pool_t *pool, const void *src);
 };
 
 static pj_status_t decode_sockaddr_attr(pj_pool_t *pool, 
@@ -101,47 +101,55 @@
 static pj_status_t encode_sockaddr_attr(const void *a, pj_uint8_t *buf, 
 				       unsigned len, 
 				       unsigned *printed);
+static void*       clone_sockaddr_attr(pj_pool_t *pool, const void *src);
 static pj_status_t decode_string_attr(pj_pool_t *pool, 
 				      const pj_uint8_t *buf, 
 				      void **p_attr);
 static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf, 
 				      unsigned len, unsigned *printed);
+static void*       clone_string_attr(pj_pool_t *pool, const void *src);
 static pj_status_t decode_msgint_attr(pj_pool_t *pool, 
 				      const pj_uint8_t *buf,
 				      void **p_attr);
 static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf, 
 				      unsigned len, unsigned *printed);
+static void*       clone_msgint_attr(pj_pool_t *pool, const void *src);
 static pj_status_t decode_errcode_attr(pj_pool_t *pool, 
 				       const pj_uint8_t *buf,
 				       void **p_attr);
 static pj_status_t encode_errcode_attr(const void *a, pj_uint8_t *buf, 
 				       unsigned len, unsigned *printed);
+static void*       clone_errcode_attr(pj_pool_t *pool, const void *src);
 static pj_status_t decode_unknown_attr(pj_pool_t *pool, 
 				       const pj_uint8_t *buf, 
 				       void **p_attr);
 static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf, 
 				       unsigned len, unsigned *printed);
+static void*       clone_unknown_attr(pj_pool_t *pool, const void *src);
 static pj_status_t decode_uint_attr(pj_pool_t *pool, 
 				    const pj_uint8_t *buf, 
 				    void **p_attr);
 static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf, 
 				    unsigned len, unsigned *printed);
+static void*       clone_uint_attr(pj_pool_t *pool, const void *src);
 static pj_status_t decode_uint64_attr(pj_pool_t *pool, 
 				      const pj_uint8_t *buf, 
 				      void **p_attr);
 static pj_status_t encode_uint64_attr(const void *a, pj_uint8_t *buf, 
 				      unsigned len, unsigned *printed);
+static void*       clone_uint64_attr(pj_pool_t *pool, const void *src);
 static pj_status_t decode_binary_attr(pj_pool_t *pool, 
 				      const pj_uint8_t *buf,
 				      void **p_attr);
 static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf, 
 				      unsigned len, unsigned *printed);
+static void*       clone_binary_attr(pj_pool_t *pool, const void *src);
 static pj_status_t decode_empty_attr(pj_pool_t *pool, 
 				     const pj_uint8_t *buf, 
 				     void **p_attr);
 static pj_status_t encode_empty_attr(const void *a, pj_uint8_t *buf, 
 				     unsigned len, unsigned *printed);
-
+static void*       clone_empty_attr(pj_pool_t *pool, const void *src);
 
 static struct attr_desc mandatory_attr_desc[] = 
 {
@@ -149,235 +157,274 @@
 	/* type zero */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* PJ_STUN_ATTR_MAPPED_ADDR, */
 	"MAPPED-ADDRESS",
 	&decode_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_RESPONSE_ADDR, */
 	"RESPONSE-ADDRESS",
 	&decode_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_CHANGE_REQUEST, */
 	"CHANGE-REQUEST",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* PJ_STUN_ATTR_SOURCE_ADDR, */
 	"SOURCE-ADDRESS",
 	&decode_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_CHANGED_ADDR, */
 	"CHANGED-ADDRESS",
 	&decode_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_USERNAME, */
 	"USERNAME",
 	&decode_string_attr,
-	&encode_string_attr
+	&encode_string_attr,
+	&clone_string_attr
     },
     {
 	/* PJ_STUN_ATTR_PASSWORD, */
 	"PASSWORD",
 	&decode_string_attr,
-	&encode_string_attr
+	&encode_string_attr,
+	&clone_string_attr
     },
     {
 	/* PJ_STUN_ATTR_MESSAGE_INTEGRITY, */
 	"MESSAGE-INTEGRITY",
 	&decode_msgint_attr,
-	&encode_msgint_attr
+	&encode_msgint_attr,
+	&clone_msgint_attr
     },
     {
 	/* PJ_STUN_ATTR_ERROR_CODE, */
 	"ERROR-CODE",
 	&decode_errcode_attr,
-	&encode_errcode_attr
+	&encode_errcode_attr,
+	&clone_errcode_attr
     },
     {
 	/* PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, */
 	"UNKNOWN-ATTRIBUTES",
 	&decode_unknown_attr,
-	&encode_unknown_attr
+	&encode_unknown_attr,
+	&clone_unknown_attr
     },
     {
 	/* PJ_STUN_ATTR_REFLECTED_FROM, */
 	"REFLECTED-FROM",
 	&decode_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_CHANNEL_NUMBER (0x000C) */
 	"CHANNEL-NUMBER",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* PJ_STUN_ATTR_LIFETIME, */
 	"LIFETIME",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* ID 0x000E is not assigned */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* PJ_STUN_ATTR_MAGIC_COOKIE */
 	"MAGIC-COOKIE",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* PJ_STUN_ATTR_BANDWIDTH, */
 	"BANDWIDTH",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* ID 0x0011 is not assigned */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* PJ_STUN_ATTR_PEER_ADDRESS, */
 	"PEER-ADDRESS",
 	&decode_xored_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_DATA, */
 	"DATA",
 	&decode_binary_attr,
-	&encode_binary_attr
+	&encode_binary_attr,
+	&clone_binary_attr
     },
     {
 	/* PJ_STUN_ATTR_REALM, */
 	"REALM",
 	&decode_string_attr,
-	&encode_string_attr
+	&encode_string_attr,
+	&clone_string_attr
     },
     {
 	/* PJ_STUN_ATTR_NONCE, */
 	"NONCE",
 	&decode_string_attr,
-	&encode_string_attr
+	&encode_string_attr,
+	&clone_string_attr
     },
     {
 	/* PJ_STUN_ATTR_RELAY_ADDRESS, */
 	"RELAY-ADDRESS",
 	&decode_xored_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_REQUESTED_ADDR_TYPE, */
 	"REQUESTED-ADDRESS-TYPE",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* PJ_STUN_ATTR_REQUESTED_PROPS, */
 	"REQUESTED-PROPS",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* PJ_STUN_ATTR_REQUESTED_TRANSPORT, */
 	"REQUESTED-TRANSPORT",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* ID 0x001A is not assigned */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* ID 0x001B is not assigned */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* ID 0x001C is not assigned */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* ID 0x001D is not assigned */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* ID 0x001E is not assigned */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* ID 0x001F is not assigned */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* PJ_STUN_ATTR_XOR_MAPPED_ADDRESS, */
 	"XOR-MAPPED-ADDRESS",
 	&decode_xored_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_TIMER_VAL, */
 	"TIMER-VAL",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* PJ_STUN_ATTR_RESERVATION_TOKEN, */
 	"RESERVATION-TOKEN",
 	&decode_uint64_attr,
-	&encode_uint64_attr
+	&encode_uint64_attr,
+	&clone_uint64_attr
     },
     {
 	/* PJ_STUN_ATTR_XOR_REFLECTED_FROM, */
 	"XOR-REFLECTED-FROM",
 	&decode_xored_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_PRIORITY, */
 	"PRIORITY",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* PJ_STUN_ATTR_USE_CANDIDATE, */
 	"USE-CANDIDATE",
 	&decode_empty_attr,
-	&encode_empty_attr
+	&encode_empty_attr,
+	&clone_empty_attr
     },
     {
 	/* PJ_STUN_ATTR_XOR_INTERNAL_ADDR, */
 	"XOR-INTERNAL-ADDRESS",
 	&decode_xored_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
 
     /* Sentinel */
@@ -385,6 +432,7 @@
 	/* PJ_STUN_ATTR_END_MANDATORY_ATTR */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     }
 };
@@ -395,61 +443,71 @@
 	/* ID 0x8021 is not assigned */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* PJ_STUN_ATTR_SERVER, */
 	"SERVER",
 	&decode_string_attr,
-	&encode_string_attr
+	&encode_string_attr,
+	&clone_string_attr
     },
     {
 	/* PJ_STUN_ATTR_ALTERNATE_SERVER, */
 	"ALTERNATE-SERVER",
 	&decode_sockaddr_attr,
-	&encode_sockaddr_attr
+	&encode_sockaddr_attr,
+	&clone_sockaddr_attr
     },
     {
 	/* PJ_STUN_ATTR_REFRESH_INTERVAL, */
 	"REFRESH-INTERVAL",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* ID 0x8025 is not assigned*/
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* PADDING, 0x8026 */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* CACHE-TIMEOUT, 0x8027 */
 	NULL,
 	NULL,
+	NULL,
 	NULL
     },
     {
 	/* PJ_STUN_ATTR_FINGERPRINT, */
 	"FINGERPRINT",
 	&decode_uint_attr,
-	&encode_uint_attr
+	&encode_uint_attr,
+	&clone_uint_attr
     },
     {
 	/* PJ_STUN_ATTR_ICE_CONTROLLED, */
 	"ICE-CONTROLLED",
 	&decode_uint64_attr,
-	&encode_uint64_attr
+	&encode_uint64_attr,
+	&clone_uint64_attr
     },
     {
 	/* PJ_STUN_ATTR_ICE_CONTROLLING, */
 	"ICE-CONTROLLING",
 	&decode_uint64_attr,
-	&encode_uint64_attr
+	&encode_uint64_attr,
+	&clone_uint64_attr
     }
 };
 
@@ -826,6 +884,13 @@
 }
 
 
+static void* clone_sockaddr_attr(pj_pool_t *pool, const void *src)
+{
+    pj_stun_sockaddr_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_sockaddr_attr);
+    pj_memcpy(dst, src, sizeof(pj_stun_sockaddr_attr));
+    return (void*)dst;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 /*
  * STUN generic string attribute
@@ -934,6 +999,17 @@
 }
 
 
+static void* clone_string_attr(pj_pool_t *pool, const void *src)
+{
+    const pj_stun_string_attr *asrc = (const pj_stun_string_attr*)src;
+    pj_stun_string_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_string_attr);
+
+    pj_memcpy(dst, src, sizeof(pj_stun_attr_hdr));
+    pj_strdup(pool, &dst->value, &asrc->value);
+
+    return (void*)dst;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 /*
  * STUN empty attribute (used by USE-CANDIDATE).
@@ -1018,6 +1094,15 @@
 }
 
 
+static void* clone_empty_attr(pj_pool_t *pool, const void *src)
+{
+    pj_stun_empty_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_empty_attr);
+
+    pj_memcpy(dst, src, sizeof(pj_stun_empty_attr));
+
+    return (void*) dst;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 /*
  * STUN generic 32bit integer attribute.
@@ -1103,6 +1188,16 @@
     return PJ_SUCCESS;
 }
 
+
+static void* clone_uint_attr(pj_pool_t *pool, const void *src)
+{
+    pj_stun_uint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint_attr);
+
+    pj_memcpy(dst, src, sizeof(pj_stun_uint_attr));
+
+    return (void*)dst;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 
 /*
@@ -1189,6 +1284,16 @@
 }
 
 
+static void* clone_uint64_attr(pj_pool_t *pool, const void *src)
+{
+    pj_stun_uint64_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint64_attr);
+
+    pj_memcpy(dst, src, sizeof(pj_stun_uint64_attr));
+
+    return (void*)dst;
+}
+
+
 //////////////////////////////////////////////////////////////////////////////
 /*
  * STUN MESSAGE-INTEGRITY attribute.
@@ -1271,6 +1376,16 @@
     return PJ_SUCCESS;
 }
 
+
+static void* clone_msgint_attr(pj_pool_t *pool, const void *src)
+{
+    pj_stun_msgint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_msgint_attr);
+
+    pj_memcpy(dst, src, sizeof(pj_stun_msgint_attr));
+
+    return (void*) dst;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 /*
  * STUN ERROR-CODE
@@ -1381,6 +1496,18 @@
     return PJ_SUCCESS;
 }
 
+
+static void* clone_errcode_attr(pj_pool_t *pool, const void *src)
+{
+    const pj_stun_errcode_attr *asrc = (const pj_stun_errcode_attr*)src;
+    pj_stun_errcode_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_errcode_attr);
+
+    pj_memcpy(dst, src, sizeof(pj_stun_errcode_attr));
+    pj_strdup(pool, &dst->reason, &asrc->reason);
+
+    return (void*)dst;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 /*
  * STUN UNKNOWN-ATTRIBUTES attribute
@@ -1499,6 +1626,15 @@
 }
 
 
+static void* clone_unknown_attr(pj_pool_t *pool, const void *src)
+{
+    pj_stun_unknown_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_unknown_attr);
+
+    pj_memcpy(dst, src, sizeof(pj_stun_unknown_attr));
+    
+    return (void*)dst;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 /*
  * STUN generic binary attribute
@@ -1597,6 +1733,21 @@
 }
 
 
+static void* clone_binary_attr(pj_pool_t *pool, const void *src)
+{
+    const pj_stun_binary_attr *asrc = (const pj_stun_binary_attr*)src;
+    pj_stun_binary_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_binary_attr);
+
+    pj_memcpy(dst, src, sizeof(pj_stun_binary_attr));
+
+    if (asrc->length) {
+	dst->data = (pj_uint8_t*) pj_pool_alloc(pool, asrc->length);
+	pj_memcpy(dst->data, asrc->data, asrc->length);
+    }
+
+    return (void*)dst;
+}
+
 //////////////////////////////////////////////////////////////////////////////
 
 /*
@@ -1641,6 +1792,31 @@
 
 
 /*
+ * Clone a STUN message with all of its attributes.
+ */
+PJ_DEF(pj_stun_msg*) pj_stun_msg_clone( pj_pool_t *pool,
+					const pj_stun_msg *src)
+{
+    pj_stun_msg *dst;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(pool && src, NULL);
+
+    dst = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
+    pj_memcpy(dst, src, sizeof(pj_stun_msg));
+
+    /* Duplicate the attributes */
+    for (i=0, dst->attr_count=0; i<src->attr_count; ++i) {
+	dst->attr[dst->attr_count] = pj_stun_attr_clone(pool, src->attr[i]);
+	if (dst->attr[dst->attr_count])
+	    ++dst->attr_count;
+    }
+
+    return dst;
+}
+
+
+/*
  * Add STUN attribute to STUN message.
  */
 PJ_DEF(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg,
@@ -1981,79 +2157,6 @@
     return PJ_SUCCESS;
 }
 
-/* Calculate HMAC-SHA1 key for long term credential, by getting
- * MD5 digest of username, realm, and password. 
- */
-static void calc_md5_key(pj_uint8_t digest[16],
-			 const pj_str_t *realm,
-			 const pj_str_t *username,
-			 const pj_str_t *passwd)
-{
-    /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking
-     * the MD5 hash of the result of concatenating the following five
-     * fields: (1) The username, with any quotes and trailing nulls
-     * removed, (2) A single colon, (3) The realm, with any quotes and
-     * trailing nulls removed, (4) A single colon, and (5) The 
-     * password, with any trailing nulls removed.
-     */
-    pj_md5_context ctx;
-    pj_str_t s;
-
-    pj_md5_init(&ctx);
-
-#define REMOVE_QUOTE(s)	if (s.slen && *s.ptr=='"') \
-			    s.ptr++, s.slen--; \
-			if (s.slen && s.ptr[s.slen-1]=='"') \
-			    s.slen--;
-
-    /* Add username */
-    s = *username;
-    REMOVE_QUOTE(s);
-    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
-
-    /* Add single colon */
-    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
-
-    /* Add realm */
-    s = *realm;
-    REMOVE_QUOTE(s);
-    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
-
-#undef REMOVE_QUOTE
-
-    /* Another colon */
-    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
-
-    /* Add password */
-    pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, passwd->slen);
-
-    /* Done */
-    pj_md5_final(&ctx, digest);
-}
-
-
-/*
- * Create authentication key to be used for encoding the message with
- * MESSAGE-INTEGRITY. 
- */
-PJ_DEF(void) pj_stun_create_key(pj_pool_t *pool,
-				pj_str_t *key,
-				const pj_str_t *realm,
-				const pj_str_t *username,
-				const pj_str_t *passwd)
-{
-    PJ_ASSERT_ON_FAIL(pool && key && username && passwd, return);
-
-    if (realm && realm->slen) {
-	key->ptr = (char*) pj_pool_alloc(pool, 16);
-	calc_md5_key((pj_uint8_t*)key->ptr, realm, username, passwd);
-	key->slen = 16;
-    } else {
-	pj_strdup(pool, key, passwd);
-    }
-}
-
-
 /*
 static char *print_binary(const pj_uint8_t *data, unsigned data_len)
 {
@@ -2207,7 +2310,7 @@
 	/* MESSAGE-INTEGRITY must be the last attribute in the message, or
 	 * the last attribute before FINGERPRINT.
 	 */
-	if (i < msg->attr_count-2) {
+	if (msg->attr_count>1 && i < msg->attr_count-2) {
 	    /* Should not happen for message generated by us */
 	    pj_assert(PJ_FALSE);
 	    return PJNATH_ESTUNMSGINTPOS;
@@ -2298,3 +2401,20 @@
     return NULL;
 }
 
+
+/*
+ * Clone a STUN attribute.
+ */
+PJ_DEF(pj_stun_attr_hdr*) pj_stun_attr_clone( pj_pool_t *pool,
+					      const pj_stun_attr_hdr *attr)
+{
+    const struct attr_desc *adesc;
+
+    /* Get the attribute descriptor */
+    adesc = find_attr_desc(attr->type);
+    PJ_ASSERT_RETURN(adesc != NULL, NULL);
+
+    return (pj_stun_attr_hdr*) (*adesc->clone_attr)(pool, attr);
+}
+
+
diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c
index 29dc3e5..02b017c 100644
--- a/pjnath/src/pjnath/stun_session.c
+++ b/pjnath/src/pjnath/stun_session.c
@@ -17,6 +17,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
 #include <pjnath/stun_session.h>
+#include <pjnath/errno.h>
 #include <pjlib.h>
 
 struct pj_stun_session
@@ -29,7 +30,14 @@
     void		*user_data;
 
     pj_bool_t		 use_fingerprint;
-    pj_stun_auth_cred	*cred;
+
+    char		 dump_buf[1000];
+
+    pj_stun_auth_type	 auth_type;
+    pj_stun_auth_cred	 cred;
+    int			 auth_retry;
+    pj_str_t		 next_nonce;
+
     pj_str_t		 srv_name;
 
     pj_stun_tx_data	 pending_request_list;
@@ -125,39 +133,6 @@
     return PJ_SUCCESS;
 }
 
-static pj_status_t create_request_tdata(pj_stun_session *sess,
-					unsigned msg_type,
-					pj_uint32_t magic,
-					const pj_uint8_t tsx_id[12],
-					pj_stun_tx_data **p_tdata)
-{
-    pj_status_t status;
-    pj_stun_tx_data *tdata;
-
-    status = create_tdata(sess, &tdata);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    /* Create STUN message */
-    status = pj_stun_msg_create(tdata->pool, msg_type,  magic, 
-				tsx_id, &tdata->msg);
-    if (status != PJ_SUCCESS) {
-	pj_pool_release(tdata->pool);
-	return status;
-    }
-
-    /* copy the request's transaction ID as the transaction key. */
-    pj_assert(sizeof(tdata->msg_key)==sizeof(tdata->msg->hdr.tsx_id));
-    tdata->msg_magic = tdata->msg->hdr.magic;
-    pj_memcpy(tdata->msg_key, tdata->msg->hdr.tsx_id,
-	      sizeof(tdata->msg->hdr.tsx_id));
-
-    *p_tdata = tdata;
-
-    return PJ_SUCCESS;
-}
-
-
 static void stun_tsx_on_destroy(pj_stun_client_tsx *tsx)
 {
     pj_stun_tx_data *tdata;
@@ -215,55 +190,13 @@
     pj_stun_msg_destroy_tdata(tdata->sess, tdata);
 }
 
-static pj_status_t get_key(pj_stun_session *sess, pj_pool_t *pool,
-			   const pj_stun_msg *msg, pj_str_t *auth_key)
-{
-    if (sess->cred == NULL) {
-	auth_key->slen = 0;
-	return PJ_SUCCESS;
-    } else if (sess->cred->type == PJ_STUN_AUTH_CRED_STATIC) {
-	pj_stun_create_key(pool, auth_key, 
-			   &sess->cred->data.static_cred.realm,
-			   &sess->cred->data.static_cred.username,
-			   &sess->cred->data.static_cred.data);
-	return PJ_SUCCESS;
-    } else if (sess->cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
-	pj_str_t realm, username, nonce;
-	pj_str_t *password;
-	void *user_data = sess->cred->data.dyn_cred.user_data;
-	int data_type = 0;
-	pj_status_t status;
-
-	realm.slen = username.slen = nonce.slen = 0;
-	password = PJ_POOL_ZALLOC_T(pool, pj_str_t);
-	status = (*sess->cred->data.dyn_cred.get_cred)(msg, user_data, pool,
-						       &realm, &username,
-						       &nonce, &data_type,
-						       password);
-	if (status != PJ_SUCCESS)
-	    return status;
-
-	pj_stun_create_key(pool, auth_key, 
-			   &realm, &username, password);
-
-	return PJ_SUCCESS;
-
-    } else {
-	pj_assert(!"Unknown credential type");
-	return PJ_EBUG;
-    }
-}
-
 static pj_status_t apply_msg_options(pj_stun_session *sess,
 				     pj_pool_t *pool,
+				     const pj_stun_req_cred_info *auth_info,
 				     pj_stun_msg *msg)
 {
     pj_status_t status = 0;
-    pj_bool_t need_auth;
-    pj_str_t realm, username, nonce, password;
-    int data_type = 0;
-
-    realm.slen = username.slen = nonce.slen = password.slen = 0;
+    pj_str_t realm, username, nonce, auth_key;
 
     /* The server SHOULD include a SERVER attribute in all responses */
     if (sess->srv_name.slen && PJ_STUN_IS_RESPONSE(msg->hdr.type)) {
@@ -271,32 +204,16 @@
 				    &sess->srv_name);
     }
 
-    need_auth = pj_stun_auth_valid_for_msg(msg);
-
-    if (sess->cred && sess->cred->type == PJ_STUN_AUTH_CRED_STATIC &&
-	need_auth)
-    {
-	realm = sess->cred->data.static_cred.realm;
-	username = sess->cred->data.static_cred.username;
-	data_type = sess->cred->data.static_cred.data_type;
-	password = sess->cred->data.static_cred.data;
-	nonce = sess->cred->data.static_cred.nonce;
-
-    } else if (sess->cred && sess->cred->type == PJ_STUN_AUTH_CRED_DYNAMIC &&
-	       need_auth) 
-    {
-	void *user_data = sess->cred->data.dyn_cred.user_data;
-
-	status = (*sess->cred->data.dyn_cred.get_cred)(msg, user_data, pool,
-						       &realm, &username,
-						       &nonce, &data_type,
-						       &password);
-	if (status != PJ_SUCCESS)
-	    return status;
+    if (pj_stun_auth_valid_for_msg(msg) && auth_info) {
+	realm = auth_info->realm;
+	username = auth_info->username;
+	nonce = auth_info->nonce;
+	auth_key = auth_info->auth_key;
+    } else {
+	realm.slen = username.slen = nonce.slen = auth_key.slen = 0;
     }
 
-
-    /* Create and add USERNAME attribute for */
+    /* Create and add USERNAME attribute if needed */
     if (username.slen && PJ_STUN_IS_REQUEST(msg->hdr.type)) {
 	status = pj_stun_msg_add_string_attr(pool, msg,
 					     PJ_STUN_ATTR_USERNAME,
@@ -323,7 +240,7 @@
     }
 
     /* Add MESSAGE-INTEGRITY attribute */
-    if (username.slen && need_auth) {
+    if (username.slen && auth_key.slen) {
 	status = pj_stun_msg_add_msgint_attr(pool, msg);
 	PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
     }
@@ -339,6 +256,105 @@
     return PJ_SUCCESS;
 }
 
+static pj_status_t handle_auth_challenge(pj_stun_session *sess,
+					 const pj_stun_tx_data *request,
+					 const pj_stun_msg *response,
+					 const pj_sockaddr_t *src_addr,
+					 unsigned src_addr_len,
+					 pj_bool_t *notify_user)
+{
+    const pj_stun_errcode_attr *ea;
+
+    *notify_user = PJ_TRUE;
+
+    if (sess->auth_type != PJ_STUN_AUTH_LONG_TERM)
+	return PJ_SUCCESS;
+    
+    if (!PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type))
+	return PJ_SUCCESS;
+
+    ea = (const pj_stun_errcode_attr*)
+	 pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0);
+    if (!ea) {
+	PJ_LOG(4,(SNAME(sess), "Invalid error response: no ERROR-CODE"
+		  " attribute"));
+	*notify_user = PJ_FALSE;
+	return PJNATH_EINSTUNMSG;
+    }
+
+    if (ea->err_code == PJ_STUN_SC_UNAUTHORIZED || 
+	ea->err_code == PJ_STUN_SC_STALE_NONCE)
+    {
+	const pj_stun_nonce_attr *anonce;
+	pj_stun_tx_data *tdata;
+	unsigned i;
+	pj_status_t status;
+
+	anonce = (const pj_stun_nonce_attr*)
+		 pj_stun_msg_find_attr(response, PJ_STUN_ATTR_NONCE, 0);
+	if (!anonce) {
+	    PJ_LOG(4,(SNAME(sess), "Invalid response: missing NONCE"));
+	    *notify_user = PJ_FALSE;
+	    return PJNATH_EINSTUNMSG;
+	}
+
+	/* Bail out if we've supplied the correct nonce */
+	if (pj_strcmp(&anonce->value, &sess->next_nonce)==0) {
+	    return PJ_SUCCESS;
+	}
+
+	/* Bail out if we've tried too many */
+	if (++sess->auth_retry > 3) {
+	    PJ_LOG(4,(SNAME(sess), "Error: authentication failed (too "
+		      "many retries)"));
+	    return PJ_STATUS_FROM_STUN_CODE(401);
+	}
+
+	/* Save next_nonce */
+	pj_strdup(sess->pool, &sess->next_nonce, &anonce->value);
+
+	/* Create new request */
+	status = pj_stun_session_create_req(sess, request->msg->hdr.type,
+					    request->msg->hdr.magic,
+					    NULL, &tdata);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Duplicate all the attributes in the old request, except
+	 * USERNAME, REALM, M-I, and NONCE 
+	 */
+	for (i=0; i<request->msg->attr_count; ++i) {
+	    const pj_stun_attr_hdr *asrc = request->msg->attr[i];
+
+	    if (asrc->type == PJ_STUN_ATTR_USERNAME ||
+		asrc->type == PJ_STUN_ATTR_REALM ||
+		asrc->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY ||
+		asrc->type == PJ_STUN_ATTR_NONCE)
+	    {
+		continue;
+	    }
+
+	    tdata->msg->attr[tdata->msg->attr_count++] = 
+		pj_stun_attr_clone(tdata->pool, asrc);
+	}
+
+	/* Will retry the request with authentication, no need to
+	 * notify user.
+	 */
+	*notify_user = PJ_FALSE;
+
+	PJ_LOG(4,(SNAME(sess), "Retrying request with new authentication"));
+
+	/* Retry the request */
+	status = pj_stun_session_send_msg(sess, PJ_TRUE, src_addr, 
+					  src_addr_len, tdata);
+
+    } else {
+	sess->auth_retry = 0;
+    }
+
+    return PJ_SUCCESS;
+}
 
 static void stun_tsx_on_complete(pj_stun_client_tsx *tsx,
 				 pj_status_t status, 
@@ -346,14 +362,21 @@
 				 const pj_sockaddr_t *src_addr,
 				 unsigned src_addr_len)
 {
+    pj_stun_session *sess;
+    pj_bool_t notify_user = PJ_TRUE;
     pj_stun_tx_data *tdata;
 
     tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx);
+    sess = tdata->sess;
 
-    if (tdata->sess->cb.on_request_complete) {
-	(*tdata->sess->cb.on_request_complete)(tdata->sess, status, tdata, 
-					       response, 
-					       src_addr, src_addr_len);
+    /* Handle authentication challenge */
+    handle_auth_challenge(sess, tdata, response, src_addr,
+		          src_addr_len, &notify_user);
+
+    if (notify_user && sess->cb.on_request_complete) {
+	(*sess->cb.on_request_complete)(sess, status, tdata, 
+					response, 
+					src_addr, src_addr_len);
     }
 }
 
@@ -488,20 +511,65 @@
     return PJ_SUCCESS;
 }
 
-PJ_DEF(void) pj_stun_session_set_credential(pj_stun_session *sess,
-					    const pj_stun_auth_cred *cred)
+PJ_DEF(pj_status_t) pj_stun_session_set_credential(pj_stun_session *sess,
+						 pj_stun_auth_type auth_type,
+						 const pj_stun_auth_cred *cred)
 {
-    PJ_ASSERT_ON_FAIL(sess, return);
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+
+    sess->auth_type = auth_type;
     if (cred) {
-	if (!sess->cred)
-	    sess->cred = PJ_POOL_ALLOC_T(sess->pool, pj_stun_auth_cred);
-	pj_stun_auth_cred_dup(sess->pool, sess->cred, cred);
+	pj_stun_auth_cred_dup(sess->pool, &sess->cred, cred);
     } else {
-	sess->cred = NULL;
+	sess->auth_type = PJ_STUN_AUTH_NONE;
+	pj_bzero(&sess->cred, sizeof(sess->cred));
     }
+
+    return PJ_SUCCESS;
 }
 
 
+static pj_status_t get_auth(pj_stun_session *sess,
+			    pj_stun_tx_data *tdata)
+{
+    if (sess->cred.type == PJ_STUN_AUTH_CRED_STATIC) {
+	tdata->auth_info.realm = sess->cred.data.static_cred.realm;
+	tdata->auth_info.username = sess->cred.data.static_cred.username;
+	tdata->auth_info.nonce = sess->cred.data.static_cred.nonce;
+
+	pj_stun_create_key(tdata->pool, &tdata->auth_info.auth_key, 
+			   &tdata->auth_info.realm,
+			   &tdata->auth_info.username,
+			   sess->cred.data.static_cred.data_type,
+			   &sess->cred.data.static_cred.data);
+
+    } else if (sess->cred.type == PJ_STUN_AUTH_CRED_DYNAMIC) {
+	pj_str_t password;
+	void *user_data = sess->cred.data.dyn_cred.user_data;
+	pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN;
+	pj_status_t rc;
+
+	rc = (*sess->cred.data.dyn_cred.get_cred)(tdata->msg, user_data, 
+						  tdata->pool,
+						  &tdata->auth_info.realm, 
+						  &tdata->auth_info.username,
+						  &tdata->auth_info.nonce, 
+						  &data_type, &password);
+	if (rc != PJ_SUCCESS)
+	    return rc;
+
+	pj_stun_create_key(tdata->pool, &tdata->auth_info.auth_key, 
+			   &tdata->auth_info.realm, &tdata->auth_info.username,
+			   data_type, &password);
+
+    } else {
+	pj_assert(!"Unknown credential type");
+	return PJ_EBUG;
+    }
+
+    return PJ_SUCCESS;
+}
+
 PJ_DEF(pj_status_t) pj_stun_session_create_req(pj_stun_session *sess,
 					       int method,
 					       pj_uint32_t magic,
@@ -513,10 +581,56 @@
 
     PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL);
 
-    status = create_request_tdata(sess, method, magic, tsx_id, &tdata);
+    status = create_tdata(sess, &tdata);
     if (status != PJ_SUCCESS)
 	return status;
 
+    /* Create STUN message */
+    status = pj_stun_msg_create(tdata->pool, method,  magic, 
+				tsx_id, &tdata->msg);
+    if (status != PJ_SUCCESS) {
+	pj_pool_release(tdata->pool);
+	return status;
+    }
+
+    /* copy the request's transaction ID as the transaction key. */
+    pj_assert(sizeof(tdata->msg_key)==sizeof(tdata->msg->hdr.tsx_id));
+    tdata->msg_magic = tdata->msg->hdr.magic;
+    pj_memcpy(tdata->msg_key, tdata->msg->hdr.tsx_id,
+	      sizeof(tdata->msg->hdr.tsx_id));
+
+    
+    /* Get authentication information for the request */
+    if (sess->auth_type == PJ_STUN_AUTH_NONE) {
+	/* No authentication */
+
+    } else if (sess->auth_type == PJ_STUN_AUTH_SHORT_TERM) {
+	/* MUST put authentication in request */
+	status = get_auth(sess, tdata);
+	if (status != PJ_SUCCESS) {
+	    pj_pool_release(tdata->pool);
+	    return status;
+	}
+
+    } else if (sess->auth_type == PJ_STUN_AUTH_LONG_TERM) {
+	/* Only put authentication information if we've received
+	 * response from server.
+	 */
+	if (sess->next_nonce.slen != 0) {
+	    status = get_auth(sess, tdata);
+	    if (status != PJ_SUCCESS) {
+		pj_pool_release(tdata->pool);
+		return status;
+	    }
+	    tdata->auth_info.nonce = sess->next_nonce;
+	}
+
+    } else {
+	pj_assert(!"Invalid authentication type");
+	pj_pool_release(tdata->pool);
+	return PJ_EBUG;
+    }
+
     *p_tdata = tdata;
     return PJ_SUCCESS;
 }
@@ -551,7 +665,7 @@
  * Create a STUN response message.
  */
 PJ_DEF(pj_status_t) pj_stun_session_create_res( pj_stun_session *sess,
-						const pj_stun_msg *req,
+						const pj_stun_rx_data *rdata,
 						unsigned err_code,
 						const pj_str_t *err_msg,
 						pj_stun_tx_data **p_tdata)
@@ -564,17 +678,21 @@
 	return status;
 
     /* Create STUN response message */
-    status = pj_stun_msg_create_response(tdata->pool, req, err_code, err_msg,
-					 &tdata->msg);
+    status = pj_stun_msg_create_response(tdata->pool, rdata->msg, 
+					 err_code, err_msg, &tdata->msg);
     if (status != PJ_SUCCESS) {
 	pj_pool_release(tdata->pool);
 	return status;
     }
 
     /* copy the request's transaction ID as the transaction key. */
-    pj_assert(sizeof(tdata->msg_key)==sizeof(req->hdr.tsx_id));
-    tdata->msg_magic = req->hdr.magic;
-    pj_memcpy(tdata->msg_key, req->hdr.tsx_id, sizeof(req->hdr.tsx_id));
+    pj_assert(sizeof(tdata->msg_key)==sizeof(rdata->msg->hdr.tsx_id));
+    tdata->msg_magic = rdata->msg->hdr.magic;
+    pj_memcpy(tdata->msg_key, rdata->msg->hdr.tsx_id, 
+	      sizeof(rdata->msg->hdr.tsx_id));
+
+    /* copy the credential found in the request */
+    pj_stun_req_cred_info_dup(tdata->pool, &tdata->auth_info, &rdata->info);
 
     *p_tdata = tdata;
 
@@ -586,29 +704,18 @@
 static void dump_tx_msg(pj_stun_session *sess, const pj_stun_msg *msg,
 			unsigned pkt_size, const pj_sockaddr_t *addr)
 {
-    const char *dst_name;
-    int dst_port;
-    const pj_sockaddr *dst = (const pj_sockaddr*)addr;
-    char buf[800];
+    char dst_name[80];
     
-    if (dst->addr.sa_family == pj_AF_INET()) {
-	dst_name = pj_inet_ntoa(dst->ipv4.sin_addr);
-	dst_port = pj_ntohs(dst->ipv4.sin_port);
-    } else if (dst->addr.sa_family == pj_AF_INET6()) {
-	dst_name = "IPv6";
-	dst_port = pj_ntohs(dst->ipv6.sin6_port);
-    } else {
-	LOG_ERR_(sess, "Invalid address family", PJ_EINVAL);
-	return;
-    }
+    pj_sockaddr_print(addr, dst_name, sizeof(dst_name), 3);
 
     PJ_LOG(5,(SNAME(sess), 
-	      "TX %d bytes STUN message to %s:%d:\n"
+	      "TX %d bytes STUN message to %s:\n"
 	      "--- begin STUN message ---\n"
 	      "%s"
 	      "--- end of STUN message ---\n",
-	      pkt_size, dst_name, dst_port,
-	      pj_stun_msg_dump(msg, buf, sizeof(buf), NULL)));
+	      pkt_size, dst_name, 
+	      pj_stun_msg_dump(msg, sess->dump_buf, sizeof(sess->dump_buf), 
+			       NULL)));
 
 }
 
@@ -631,7 +738,8 @@
     pj_lock_acquire(sess->lock);
 
     /* Apply options */
-    status = apply_msg_options(sess, tdata->pool, tdata->msg);
+    status = apply_msg_options(sess, tdata->pool, &tdata->auth_info, 
+			       tdata->msg);
     if (status != PJ_SUCCESS) {
 	pj_stun_msg_destroy_tdata(sess, tdata);
 	pj_lock_release(sess->lock);
@@ -639,18 +747,10 @@
 	return status;
     }
 
-    status = get_key(sess, tdata->pool, tdata->msg, &tdata->auth_key);
-    if (status != PJ_SUCCESS) {
-	pj_stun_msg_destroy_tdata(sess, tdata);
-	pj_lock_release(sess->lock);
-	LOG_ERR_(sess, "Error getting creadential's key", status);
-	return status;
-    }
-
     /* Encode message */
     status = pj_stun_msg_encode(tdata->msg, (pj_uint8_t*)tdata->pkt, 
     				tdata->max_len, 0, 
-    				&tdata->auth_key,
+    				&tdata->auth_info.auth_key,
 				&tdata->pkt_size);
     if (status != PJ_SUCCESS) {
 	pj_stun_msg_destroy_tdata(sess, tdata);
@@ -742,7 +842,7 @@
  * Create and send STUN response message.
  */
 PJ_DEF(pj_status_t) pj_stun_session_respond( pj_stun_session *sess, 
-					     const pj_stun_msg *req,
+					     const pj_stun_rx_data *rdata,
 					     unsigned code, 
 					     const char *errmsg,
 					     pj_bool_t cache, 
@@ -753,7 +853,7 @@
     pj_str_t reason;
     pj_stun_tx_data *tdata;
 
-    status = pj_stun_session_create_res(sess, req, code, 
+    status = pj_stun_session_create_res(sess, rdata, code, 
 					(errmsg?pj_cstr(&reason,errmsg):NULL), 
 					&tdata);
     if (status != PJ_SUCCESS)
@@ -813,7 +913,7 @@
 /* Send response */
 static pj_status_t send_response(pj_stun_session *sess, 
 				 pj_pool_t *pool, pj_stun_msg *response,
-				 const pj_str_t *auth_key,
+				 const pj_stun_req_cred_info *auth_info,
 				 pj_bool_t retransmission,
 				 const pj_sockaddr_t *addr, unsigned addr_len)
 {
@@ -823,7 +923,7 @@
 
     /* Apply options */
     if (!retransmission) {
-	status = apply_msg_options(sess, pool, response);
+	status = apply_msg_options(sess, pool, auth_info, response);
 	if (status != PJ_SUCCESS)
 	    return status;
     }
@@ -834,7 +934,7 @@
 
     /* Encode */
     status = pj_stun_msg_encode(response, out_pkt, out_max_len, 0, 
-				auth_key, &out_len);
+				&auth_info->auth_key, &out_len);
     if (status != PJ_SUCCESS) {
 	LOG_ERR_(sess, "Error encoding message", status);
 	return status;
@@ -853,23 +953,26 @@
 static pj_status_t authenticate_req(pj_stun_session *sess,
 				    const pj_uint8_t *pkt,
 				    unsigned pkt_len,
-				    const pj_stun_msg *msg,
+				    pj_stun_rx_data *rdata,
 				    pj_pool_t *tmp_pool,
 				    const pj_sockaddr_t *src_addr,
 				    unsigned src_addr_len)
 {
     pj_stun_msg *response;
-    pj_str_t auth_key;
     pj_status_t status;
 
-    if (PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type) || sess->cred == NULL)
+    if (PJ_STUN_IS_ERROR_RESPONSE(rdata->msg->hdr.type) || 
+	sess->auth_type == PJ_STUN_AUTH_NONE)
+    {
 	return PJ_SUCCESS;
+    }
 
-    status = pj_stun_authenticate_request(pkt, pkt_len, msg, sess->cred,
-				          tmp_pool, &auth_key, &response);
+    status = pj_stun_authenticate_request(pkt, pkt_len, rdata->msg, 
+					  &sess->cred, tmp_pool, &rdata->info,
+					  &response);
     if (status != PJ_SUCCESS && response != NULL) {
 	PJ_LOG(5,(SNAME(sess), "Message authentication failed"));
-	send_response(sess, tmp_pool, response, &auth_key, PJ_FALSE, 
+	send_response(sess, tmp_pool, response, &rdata->info, PJ_FALSE, 
 		      src_addr, src_addr_len);
     }
 
@@ -897,14 +1000,18 @@
 	return PJ_SUCCESS;
     }
 
+    if (sess->auth_type == PJ_STUN_AUTH_NONE)
+	options |= PJ_STUN_NO_AUTHENTICATE;
+
     /* Authenticate the message, unless PJ_STUN_NO_AUTHENTICATE
      * is specified in the option.
      */
-    if ((options & PJ_STUN_NO_AUTHENTICATE) == 0 && tdata->auth_key.slen != 0
-	&& pj_stun_auth_valid_for_msg(msg))
+    if ((options & PJ_STUN_NO_AUTHENTICATE) == 0 && 
+	tdata->auth_info.auth_key.slen != 0 && 
+	pj_stun_auth_valid_for_msg(msg))
     {
 	status = pj_stun_authenticate_response(pkt, pkt_len, msg, 
-					       &tdata->auth_key);
+					       &tdata->auth_info.auth_key);
 	if (status != PJ_SUCCESS) {
 	    PJ_LOG(5,(SNAME(sess), 
 		      "Response authentication failed"));
@@ -962,7 +1069,7 @@
 	PJ_LOG(5,(SNAME(sess), 
 		 "Request retransmission, sending cached response"));
 
-	send_response(sess, tmp_pool, t->msg, &t->auth_key, PJ_TRUE, 
+	send_response(sess, tmp_pool, t->msg, &t->auth_info, PJ_TRUE, 
 		      src_addr, src_addr_len);
 	return PJ_SUCCESS;
     }
@@ -976,18 +1083,26 @@
 				       pj_pool_t *tmp_pool,
 				       const pj_uint8_t *in_pkt,
 				       unsigned in_pkt_len,
-				       const pj_stun_msg *msg,
+				       pj_stun_msg *msg,
 				       const pj_sockaddr_t *src_addr,
 				       unsigned src_addr_len)
 {
+    pj_stun_rx_data rdata;
     pj_status_t status;
 
+    /* Init rdata */
+    rdata.msg = msg;
+    pj_bzero(&rdata.info, sizeof(rdata.info));
+
+    if (sess->auth_type == PJ_STUN_AUTH_NONE)
+	options |= PJ_STUN_NO_AUTHENTICATE;
+
     /* Authenticate the message, unless PJ_STUN_NO_AUTHENTICATE
      * is specified in the option.
      */
     if ((options & PJ_STUN_NO_AUTHENTICATE) == 0) {
 	status = authenticate_req(sess, (const pj_uint8_t*) in_pkt, in_pkt_len,
-				  msg, tmp_pool, src_addr, src_addr_len);
+				  &rdata, tmp_pool, src_addr, src_addr_len);
 	if (status != PJ_SUCCESS) {
 	    return status;
 	}
@@ -995,14 +1110,16 @@
 
     /* Distribute to handler, or respond with Bad Request */
     if (sess->cb.on_rx_request) {
-	status = (*sess->cb.on_rx_request)(sess, in_pkt, in_pkt_len, msg,
+	status = (*sess->cb.on_rx_request)(sess, in_pkt, in_pkt_len, &rdata,
 					   src_addr, src_addr_len);
     } else {
+	pj_str_t err_text;
 	pj_stun_msg *response;
 
+	err_text = pj_str("Callback is not set to handle request");
 	status = pj_stun_msg_create_response(tmp_pool, msg, 
-					     PJ_STUN_SC_BAD_REQUEST, NULL,
-					     &response);
+					     PJ_STUN_SC_BAD_REQUEST, 
+					     &err_text, &response);
 	if (status == PJ_SUCCESS && response) {
 	    status = send_response(sess, tmp_pool, response, 
 				   NULL, PJ_FALSE, src_addr, src_addr_len);
diff --git a/pjnath/src/pjnath/turn_session.c b/pjnath/src/pjnath/turn_session.c
index 7b772dc..e5540b0 100644
--- a/pjnath/src/pjnath/turn_session.c
+++ b/pjnath/src/pjnath/turn_session.c
@@ -521,7 +521,7 @@
 
     pj_lock_acquire(sess->lock);
 
-    pj_stun_session_set_credential(sess->stun, cred);
+    pj_stun_session_set_credential(sess->stun, PJ_STUN_AUTH_LONG_TERM, cred);
 
     pj_lock_release(sess->lock);
 
diff --git a/pjnath/src/pjturn-srv/allocation.c b/pjnath/src/pjturn-srv/allocation.c
index da35266..14ed228 100644
--- a/pjnath/src/pjturn-srv/allocation.c
+++ b/pjnath/src/pjturn-srv/allocation.c
@@ -72,7 +72,7 @@
 static pj_status_t stun_on_rx_request(pj_stun_session *sess,
 				      const pj_uint8_t *pkt,
 				      unsigned pkt_len,
-				      const pj_stun_msg *msg,
+				      const pj_stun_rx_data *rdata,
 				      const pj_sockaddr_t *src_addr,
 				      unsigned src_addr_len);
 static pj_status_t stun_on_rx_indication(pj_stun_session *sess,
@@ -97,10 +97,11 @@
 /* Parse ALLOCATE request */
 static pj_status_t parse_allocate_req(alloc_request *cfg,
 				      pj_stun_session *sess,
-				      const pj_stun_msg *req,
+				      const pj_stun_rx_data *rdata,
 				      const pj_sockaddr_t *src_addr,
 				      unsigned src_addr_len)
 {
+    const pj_stun_msg *req = rdata->msg;
     pj_stun_bandwidth_attr *attr_bw;
     pj_stun_req_transport_attr *attr_req_tp;
     pj_stun_res_token_attr *attr_res_token;
@@ -120,7 +121,8 @@
 
     /* Check if we can satisfy the bandwidth */
     if (cfg->bandwidth > MAX_CLIENT_BANDWIDTH) {
-	pj_stun_session_respond(sess, req, PJ_STUN_SC_ALLOCATION_QUOTA_REACHED,
+	pj_stun_session_respond(sess, rdata, 
+				PJ_STUN_SC_ALLOCATION_QUOTA_REACHED,
 			        "Invalid bandwidth", PJ_TRUE,
 				src_addr, src_addr_len);
 	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_ALLOCATION_QUOTA_REACHED);
@@ -130,7 +132,7 @@
     attr_req_tp = (pj_stun_uint_attr*)
 	          pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_TRANSPORT, 0);
     if (attr_req_tp == NULL) {
-	pj_stun_session_respond(sess, req, PJ_STUN_SC_BAD_REQUEST,
+	pj_stun_session_respond(sess, rdata, PJ_STUN_SC_BAD_REQUEST,
 			        "Missing REQUESTED-TRANSPORT attribute", 
 				PJ_TRUE, src_addr, src_addr_len);
 	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
@@ -140,7 +142,7 @@
 
     /* Can only support UDP for now */
     if (cfg->tp_type != PJ_TURN_TP_UDP) {
-	pj_stun_session_respond(sess, req, PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO,
+	pj_stun_session_respond(sess, rdata, PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO,
 				NULL, PJ_TRUE, src_addr, src_addr_len);
 	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO);
     }
@@ -151,7 +153,7 @@
 					   0);
     if (attr_res_token) {
 	/* We don't support RESERVATION-TOKEN for now */
-	pj_stun_session_respond(sess, req, 
+	pj_stun_session_respond(sess, rdata, 
 				PJ_STUN_SC_BAD_REQUEST,
 				"RESERVATION-TOKEN is not supported", PJ_TRUE, 
 				src_addr, src_addr_len);
@@ -163,7 +165,7 @@
 	       pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_PROPS, 0);
     if (attr_rpp) {
 	/* We don't support REQUESTED-PROPS for now */
-	pj_stun_session_respond(sess, req, 
+	pj_stun_session_respond(sess, rdata, 
 				PJ_STUN_SC_BAD_REQUEST,
 				"REQUESTED-PROPS is not supported", PJ_TRUE, 
 				src_addr, src_addr_len);
@@ -176,7 +178,7 @@
     if (attr_lifetime) {
 	cfg->lifetime = attr_lifetime->value;
 	if (cfg->lifetime < MIN_LIFETIME) {
-	    pj_stun_session_respond(sess, req, PJ_STUN_SC_BAD_REQUEST,
+	    pj_stun_session_respond(sess, rdata, PJ_STUN_SC_BAD_REQUEST,
 				    "LIFETIME too short", PJ_TRUE, 
 				    src_addr, src_addr_len);
 	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
@@ -194,13 +196,13 @@
 /* Respond to ALLOCATE request */
 static pj_status_t send_allocate_response(pj_turn_allocation *alloc,
 					  pj_stun_session *srv_sess,
-					  const pj_stun_msg *msg)
+					  const pj_stun_rx_data *rdata)
 {
     pj_stun_tx_data *tdata;
     pj_status_t status;
 
     /* Respond the original ALLOCATE request */
-    status = pj_stun_session_create_res(srv_sess, msg, 0, NULL, &tdata);
+    status = pj_stun_session_create_res(srv_sess, rdata, 0, NULL, &tdata);
     if (status != PJ_SUCCESS)
 	return status;
 
@@ -284,11 +286,12 @@
 PJ_DEF(pj_status_t) pj_turn_allocation_create(pj_turn_listener *listener,
 					      const pj_sockaddr_t *src_addr,
 					      unsigned src_addr_len,
-					      const pj_stun_msg *msg,
+					      const pj_stun_rx_data *rdata,
 					      pj_stun_session *srv_sess,
 					      pj_turn_allocation **p_alloc)
 {
     pj_turn_srv *srv = listener->server;
+    const pj_stun_msg *msg = rdata->msg;
     pj_pool_t *pool;
     alloc_request req;
     pj_turn_allocation *alloc;
@@ -297,7 +300,7 @@
     pj_status_t status;
 
     /* Parse ALLOCATE request */
-    status = parse_allocate_req(&req, srv_sess, msg, src_addr, src_addr_len);
+    status = parse_allocate_req(&req, srv_sess, rdata, src_addr, src_addr_len);
     if (status != PJ_SUCCESS)
 	return status;
 
@@ -354,7 +357,8 @@
     }
 
     /* Attach authentication credential to STUN session */
-    pj_stun_session_set_credential(alloc->sess, &alloc->cred);
+    pj_stun_session_set_credential(alloc->sess, PJ_STUN_AUTH_LONG_TERM,
+				   &alloc->cred);
 
     /* Create the relay resource */
     status = create_relay(srv, alloc, msg, &req, &alloc->relay);
@@ -366,7 +370,7 @@
     pj_turn_srv_register_allocation(srv, alloc);
 
     /* Respond to ALLOCATE request */
-    status = send_allocate_response(alloc, srv_sess, msg);
+    status = send_allocate_response(alloc, srv_sess, rdata);
     if (status != PJ_SUCCESS)
 	goto on_error;
 
@@ -383,7 +387,7 @@
 on_error:
     /* Send reply to the ALLOCATE request */
     pj_strerror(status, str_tmp, sizeof(str_tmp));
-    pj_stun_session_respond(srv_sess, msg, PJ_STUN_SC_BAD_REQUEST, str_tmp, 
+    pj_stun_session_respond(srv_sess, rdata, PJ_STUN_SC_BAD_REQUEST, str_tmp, 
 			    PJ_TRUE, src_addr, src_addr_len);
 
     /* Cleanup */
@@ -709,13 +713,13 @@
 
 /* Create and send error response */
 static void send_reply_err(pj_turn_allocation *alloc,
-			   const pj_stun_msg *req,
+			   const pj_stun_rx_data *rdata,
 			   pj_bool_t cache, 
 			   int code, const char *errmsg)
 {
     pj_status_t status;
 
-    status = pj_stun_session_respond(alloc->sess, req, code, errmsg, cache,
+    status = pj_stun_session_respond(alloc->sess, rdata, code, errmsg, cache,
 				     &alloc->hkey.clt_addr,
 				     pj_sockaddr_get_len(&alloc->hkey.clt_addr.addr));
     if (status != PJ_SUCCESS) {
@@ -726,13 +730,13 @@
 
 /* Create and send successful response */
 static void send_reply_ok(pj_turn_allocation *alloc,
-		          const pj_stun_msg *req)
+		          const pj_stun_rx_data *rdata)
 {
     pj_status_t status;
     unsigned interval;
     pj_stun_tx_data *tdata;
 
-    status = pj_stun_session_create_res(alloc->sess, req, 0, NULL, &tdata);
+    status = pj_stun_session_create_res(alloc->sess, rdata, 0, NULL, &tdata);
     if (status != PJ_SUCCESS) {
 	alloc_err(alloc, "Error creating STUN success response", status);
 	return;
@@ -1072,10 +1076,11 @@
 static pj_status_t stun_on_rx_request(pj_stun_session *sess,
 				      const pj_uint8_t *pkt,
 				      unsigned pkt_len,
-				      const pj_stun_msg *msg,
+				      const pj_stun_rx_data *rdata,
 				      const pj_sockaddr_t *src_addr,
 				      unsigned src_addr_len)
 {
+    const pj_stun_msg *msg = rdata->msg;
     pj_turn_allocation *alloc;
 
     PJ_UNUSED_ARG(pkt);
@@ -1088,7 +1093,7 @@
     /* Refuse to serve any request if we've been shutdown */
     if (alloc->relay.lifetime == 0) {
 	/* Reject with 437 if we're shutting down */
-	send_reply_err(alloc, msg, PJ_TRUE, 
+	send_reply_err(alloc, rdata, PJ_TRUE, 
 		       PJ_STUN_SC_ALLOCATION_MISMATCH, NULL);
 	return PJ_SUCCESS;
     }
@@ -1115,7 +1120,7 @@
 	    alloc->relay.lifetime = 0;
 
 	    /* Respond first */
-	    send_reply_ok(alloc, msg);
+	    send_reply_ok(alloc, rdata);
 
 	    /* Shutdown allocation */
 	    PJ_LOG(4,(alloc->obj_name, 
@@ -1141,7 +1146,7 @@
 	    resched_timeout(alloc);
 
 	    /* Send reply */
-	    send_reply_ok(alloc, msg);
+	    send_reply_ok(alloc, rdata);
 	}
 
     } else if (msg->hdr.type == PJ_STUN_CHANNEL_BIND_REQUEST) {
@@ -1158,7 +1163,8 @@
 		    pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PEER_ADDR, 0);
 
 	if (!ch_attr || !peer_attr) {
-	    send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_BAD_REQUEST, NULL);
+	    send_reply_err(alloc, rdata, PJ_TRUE, 
+			   PJ_STUN_SC_BAD_REQUEST, NULL);
 	    return PJ_SUCCESS;
 	}
 
@@ -1171,7 +1177,7 @@
 	if (p1) {
 	    if (pj_sockaddr_cmp(&p1->hkey.peer_addr, &peer_attr->sockaddr)) {
 		/* Address mismatch. Send 400 */
-		send_reply_err(alloc, msg, PJ_TRUE, 
+		send_reply_err(alloc, rdata, PJ_TRUE, 
 			       PJ_STUN_SC_BAD_REQUEST, 
 			       "Peer address mismatch");
 		return PJ_SUCCESS;
@@ -1190,7 +1196,7 @@
 	p2 = lookup_permission_by_addr(alloc, &peer_attr->sockaddr,
 				       pj_sockaddr_get_len(&peer_attr->sockaddr));
 	if (p2 && p2->channel != PJ_TURN_INVALID_CHANNEL) {
-	    send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_BAD_REQUEST, 
+	    send_reply_err(alloc, rdata, PJ_TRUE, PJ_STUN_SC_BAD_REQUEST, 
 			   "Peer address already assigned a channel number");
 	    return PJ_SUCCESS;
 	}
@@ -1210,19 +1216,20 @@
 	refresh_permission(p2);
 
 	/* Reply */
-	send_reply_ok(alloc, msg);
+	send_reply_ok(alloc, rdata);
 
 	return PJ_SUCCESS;
 
     } else if (msg->hdr.type == PJ_STUN_ALLOCATE_REQUEST) {
 
 	/* Respond with 437 (section 6.3 turn-07) */
-	send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_ALLOCATION_MISMATCH, NULL);
+	send_reply_err(alloc, rdata, PJ_TRUE, PJ_STUN_SC_ALLOCATION_MISMATCH,
+		       NULL);
 
     } else {
 
 	/* Respond with Bad Request? */
-	send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_BAD_REQUEST, NULL);
+	send_reply_err(alloc, rdata, PJ_TRUE, PJ_STUN_SC_BAD_REQUEST, NULL);
 
     }
 
diff --git a/pjnath/src/pjturn-srv/auth.c b/pjnath/src/pjturn-srv/auth.c
index ad93025..3071221 100644
--- a/pjnath/src/pjturn-srv/auth.c
+++ b/pjnath/src/pjturn-srv/auth.c
@@ -58,29 +58,6 @@
 }
 
 
-PJ_DEF(pj_status_t) pj_turn_get_cred( const pj_stun_msg *msg,
-				      void *user_data,
-				      pj_pool_t *pool,
-				      pj_str_t *realm,
-				      pj_str_t *username,
-				      pj_str_t *nonce,
-				      int *data_type,
-				      pj_str_t *data)
-{
-    PJ_UNUSED_ARG(msg);
-    PJ_UNUSED_ARG(pool);
-    PJ_UNUSED_ARG(user_data);
-
-    *realm = pj_str(g_realm);
-    *username = pj_str(g_cred[0].username);
-    *nonce = pj_str(THE_NONCE);
-    *data_type = 0;
-    *data = pj_str(g_cred[0].passwd);
-
-    return PJ_SUCCESS;
-}
-
-
 /*
  * This function is called by pj_stun_verify_credential() when
  * server needs to challenge the request with 401 response.
diff --git a/pjnath/src/pjturn-srv/auth.h b/pjnath/src/pjturn-srv/auth.h
index d0b5a0e..db92805 100644
--- a/pjnath/src/pjturn-srv/auth.h
+++ b/pjnath/src/pjturn-srv/auth.h
@@ -61,18 +61,6 @@
 				      pj_str_t *nonce);
 
 /**
- * Get credential.
- */
-PJ_DECL(pj_status_t) pj_turn_get_cred(const pj_stun_msg *msg,
-				      void *user_data,
-				      pj_pool_t *pool,
-				      pj_str_t *realm,
-				      pj_str_t *username,
-				      pj_str_t *nonce,
-				      int *data_type,
-				      pj_str_t *data);
-
-/**
  * This function is called to get the password for the specified username.
  * This function is also used to check whether the username is valid.
  *
diff --git a/pjnath/src/pjturn-srv/server.c b/pjnath/src/pjturn-srv/server.c
index 11180db..b22cc53 100644
--- a/pjnath/src/pjturn-srv/server.c
+++ b/pjnath/src/pjturn-srv/server.c
@@ -40,7 +40,7 @@
 static pj_status_t on_rx_stun_request(pj_stun_session *sess,
 				      const pj_uint8_t *pkt,
 				      unsigned pkt_len,
-				      const pj_stun_msg *msg,
+				      const pj_stun_rx_data *rdata,
 				      const pj_sockaddr_t *src_addr,
 				      unsigned src_addr_len);
 
@@ -147,7 +147,6 @@
     srv->core.cred.type = PJ_STUN_AUTH_CRED_DYNAMIC;
     srv->core.cred.data.dyn_cred.user_data = srv;
     srv->core.cred.data.dyn_cred.get_auth = &pj_turn_get_auth;
-    srv->core.cred.data.dyn_cred.get_cred = &pj_turn_get_cred;
     srv->core.cred.data.dyn_cred.get_password = &pj_turn_get_password;
     srv->core.cred.data.dyn_cred.verify_nonce = &pj_turn_verify_nonce;
 
@@ -368,7 +367,8 @@
     }
 
     pj_stun_session_set_user_data(sess, lis);
-    pj_stun_session_set_credential(sess, &srv->core.cred);
+    pj_stun_session_set_credential(sess, PJ_STUN_AUTH_LONG_TERM, 
+				   &srv->core.cred);
 
     srv->core.stun_sess[index] = sess;
     lis->id = index;
@@ -483,9 +483,8 @@
 
 
 /* Respond to STUN request */
-static pj_status_t stun_respond(pj_turn_srv *srv,
-				pj_stun_session *sess, 
-				const pj_stun_msg *req,
+static pj_status_t stun_respond(pj_stun_session *sess, 
+				const pj_stun_rx_data *rdata,
 				unsigned code, 
 				const char *errmsg,
 				pj_bool_t cache, 
@@ -497,114 +496,17 @@
     pj_stun_tx_data *tdata;
 
     /* Create response */
-    status = pj_stun_session_create_res(sess, req, code, 
+    status = pj_stun_session_create_res(sess, rdata, code, 
 					(errmsg?pj_cstr(&reason,errmsg):NULL),
 					&tdata);
     if (status != PJ_SUCCESS)
 	return status;
 
-    /* Store the credential for future lookup. */
-    if (pj_stun_auth_valid_for_msg(tdata->msg)) {
-	pj_turn_srv_put_cred(srv, req, tdata);
-    }
-
     /* Send the response */
     return pj_stun_session_send_msg(sess, cache, dst_addr,  addr_len, tdata);
 }
 
 
-/*
- * Store the credential to put placed for the specified message for
- * future retrieval.
- */
-PJ_DEF(pj_status_t) pj_turn_srv_put_cred(pj_turn_srv *srv,
-				         const pj_stun_msg *req,
-					 pj_stun_tx_data *response)
-{
-    pj_stun_username_attr *user;
-    pj_stun_realm_attr *realm;
-    pj_stun_nonce_attr *nonce;
-    struct saved_cred *saved_cred;
-    pj_status_t status;
-
-    realm = (pj_stun_realm_attr*)
-	    pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REALM, 0);
-    PJ_ASSERT_RETURN(realm != NULL, PJ_EBUG);
-
-    user = (pj_stun_username_attr*)
-	   pj_stun_msg_find_attr(req, PJ_STUN_ATTR_USERNAME, 0);
-    PJ_ASSERT_RETURN(user != NULL, PJ_EBUG);
-
-    nonce = (pj_stun_nonce_attr*)
-	    pj_stun_msg_find_attr(req, PJ_STUN_ATTR_NONCE, 0);
-    PJ_ASSERT_RETURN(nonce != NULL, PJ_EBUG);
-
-    saved_cred = PJ_POOL_ALLOC_T(response->pool, struct saved_cred);
-
-    /* Lookup the password */
-    status = pj_turn_get_password(response->msg, NULL, &realm->value, 
-				  &user->value, response->pool, 
-				  &saved_cred->data_type, 
-				  &saved_cred->data);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    /* Store credential */
-    pj_strdup(response->pool, &saved_cred->username, &user->value);
-    pj_strdup(response->pool, &saved_cred->realm, &realm->value);
-    pj_strdup(response->pool, &saved_cred->nonce, &nonce->value);
-
-    pj_thread_local_set(srv->core.tls_key, response->msg);
-    pj_thread_local_set(srv->core.tls_data, saved_cred);
-
-    return PJ_SUCCESS;
-}
-
-
-/**
- * Retrieve previously stored credential for the specified message.
- */
-PJ_DEF(pj_status_t) pj_turn_srv_get_cred(const pj_stun_msg *msg,
-					 void *user_data,
-					 pj_pool_t *pool,
-					 pj_str_t *realm,
-					 pj_str_t *username,
-					 pj_str_t *nonce,
-					 int *data_type,
-					 pj_str_t *data)
-{
-    pj_turn_srv *srv;
-    const pj_stun_msg *saved_msg;
-    struct saved_cred *saved_cred;
-
-    PJ_UNUSED_ARG(pool);
-
-    srv = (pj_turn_srv*)user_data;
-
-    /* Lookup stored message and make sure it's for the same message */
-    saved_msg = (const pj_stun_msg*)
-	        pj_thread_local_get(srv->core.tls_key);
-    PJ_ASSERT_RETURN(saved_msg==msg, PJ_ENOTFOUND);
-
-    /* Lookup saved credential */
-    saved_cred = (struct saved_cred*) 
-		 pj_thread_local_get(srv->core.tls_data);
-    PJ_ASSERT_RETURN(saved_cred != NULL, PJ_ENOTFOUND);
-
-
-    *realm = saved_cred->realm;
-    *username = saved_cred->username;
-    *nonce = saved_cred->nonce;
-    *data_type = saved_cred->data_type;
-    *data = saved_cred->data;
-
-
-    /* Don't clear saved_cred as this may be called more than once */
-
-    return PJ_SUCCESS;
-}
-
-
 /* Callback from our own STUN session when incoming request arrives.
  * This function is triggered by pj_stun_session_on_rx_pkt() call in
   * pj_turn_srv_on_rx_pkt() function below.
@@ -612,11 +514,12 @@
 static pj_status_t on_rx_stun_request(pj_stun_session *sess,
 				      const pj_uint8_t *pkt,
 				      unsigned pkt_len,
-				      const pj_stun_msg *msg,
+				      const pj_stun_rx_data *rdata,
 				      const pj_sockaddr_t *src_addr,
 				      unsigned src_addr_len)
 {
     pj_turn_listener *listener;
+    const pj_stun_msg *msg = rdata->msg;
     pj_turn_srv *srv;
     pj_turn_allocation *alloc;
     pj_status_t status;
@@ -629,7 +532,7 @@
 
     /* Respond any requests other than ALLOCATE with 437 response */
     if (msg->hdr.type != PJ_STUN_ALLOCATE_REQUEST) {
-	stun_respond(srv, sess, msg, PJ_STUN_SC_ALLOCATION_MISMATCH,
+	stun_respond(sess, rdata, PJ_STUN_SC_ALLOCATION_MISMATCH,
 		     NULL, PJ_FALSE, src_addr, src_addr_len);
 	return PJ_SUCCESS;
     }
@@ -638,7 +541,7 @@
      * in this function.
      */
     status = pj_turn_allocation_create(listener, src_addr, src_addr_len,
-				       msg, sess, &alloc);
+				       rdata, sess, &alloc);
     if (status != PJ_SUCCESS) {
 	/* STUN response has been sent, no need to reply here */
 	return PJ_SUCCESS;
diff --git a/pjnath/src/pjturn-srv/turn.h b/pjnath/src/pjturn-srv/turn.h
index 871db42..2eb9925 100644
--- a/pjnath/src/pjturn-srv/turn.h
+++ b/pjnath/src/pjturn-srv/turn.h
@@ -204,7 +204,7 @@
 PJ_DECL(pj_status_t) pj_turn_allocation_create(pj_turn_listener *listener,
 					      const pj_sockaddr_t *src_addr,
 					      unsigned src_addr_len,
-					      const pj_stun_msg *msg,
+					      const pj_stun_rx_data *rdata,
 					      pj_stun_session *srv_sess,
 					      pj_turn_allocation **p_alloc);
 /**
@@ -459,26 +459,5 @@
 				   pj_turn_pkt *pkt);
 
 
-/**
- * Store the credential to put placed for the specified message for
- * future retrieval.
- */
-PJ_DECL(pj_status_t) pj_turn_srv_put_cred(pj_turn_srv *srv,
-					  const pj_stun_msg *request,
-					  pj_stun_tx_data *response);
-
-/**
- * Retrieve previously stored credential for the specified message.
- */
-PJ_DECL(pj_status_t) pj_turn_srv_get_cred(const pj_stun_msg *msg,
-					  void *user_data,
-					  pj_pool_t *pool,
-					  pj_str_t *realm,
-					  pj_str_t *username,
-					  pj_str_t *nonce,
-					  int *data_type,
-					  pj_str_t *data);
-
-
 #endif	/* __PJ_TURN_SRV_TURN_H__ */