Index: pjproject/trunk/pjnath/src/pjnath/stun_auth.c
===================================================================
--- pjproject/trunk/pjnath/src/pjnath/stun_auth.c	(revision 1869)
+++ pjproject/trunk/pjnath/src/pjnath/stun_auth.c	(revision 1877)
@@ -20,7 +20,9 @@
 #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>
 
@@ -54,4 +56,97 @@
 
 
+/*
+ * 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)
 {
@@ -87,6 +182,6 @@
 	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, 
@@ -102,5 +197,5 @@
     }
 
-    if (nonce && nonce->slen) {
+    if (err_code!=400 && nonce && nonce->slen) {
 	rc = pj_stun_msg_add_string_attr(pool, response,
 					 PJ_STUN_ATTR_NONCE, 
@@ -122,18 +217,18 @@
 					         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;
 
@@ -150,16 +245,22 @@
 	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;
     }
@@ -185,12 +286,7 @@
 	   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;
     }
 
@@ -203,10 +299,7 @@
 	   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;
     }
 
@@ -215,12 +308,37 @@
 	     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,
@@ -228,21 +346,18 @@
 					      &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;
-    }
-
-    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);
-    }
+	pj_assert(!"Invalid credential type");
+	return PJ_EBUG;
+    }
+
 
 
@@ -252,38 +367,31 @@
 
     /* 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);
-
-    } else if (realm.slen != 0 && arealm != NULL) {
+	err_code = PJ_STUN_SC_BAD_REQUEST;
+	err_text = "Missing REALM";
+	goto on_auth_failed;
+
+    } 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
@@ -294,14 +402,12 @@
 	 * 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;
 	}
     }
@@ -322,6 +428,6 @@
 	    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;
@@ -330,21 +436,12 @@
 
 	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);
-	}
-    }
-
-    /* Calculate key */
-    pj_stun_create_key(pool, &key, &realm, &auser->value, &password);
+	    err_code = PJ_STUN_SC_STALE_NONCE;
+	    goto on_auth_failed;
+	}
+    }
 
     /* 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
@@ -383,13 +480,18 @@
 	/* 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);
 }
 
@@ -426,9 +528,8 @@
     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:
