More STUN work

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1030 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjlib-util/include/pjlib-util/errno.h b/pjlib-util/include/pjlib-util/errno.h
index 3f004ca..d33ed1a 100644
--- a/pjlib-util/include/pjlib-util/errno.h
+++ b/pjlib-util/include/pjlib-util/errno.h
@@ -301,18 +301,24 @@
 #define PJLIB_UTIL_ESTUNNOHANDLER   (PJLIB_UTIL_ERRNO_START+116)/* 320116 */
 /**
  * @hideinitializer
- * Invalid STUN MESSAGE-INTEGRITY attribute position in message.
- * STUN MESSAGE-INTEGRITY must be put last in the message, or before
- * FINGERPRINT attribute.
+ * Found non-FINGERPRINT attribute after MESSAGE-INTEGRITY. This is not
+ * valid since MESSAGE-INTEGRITY MUST be the last attribute or the
+ * attribute right before FINGERPRINT before the message.
  */
-#define PJLIB_UTIL_ESTUNMSGINT	    (PJLIB_UTIL_ERRNO_START+117)/* 320117 */
+#define PJLIB_UTIL_ESTUNMSGINTPOS    (PJLIB_UTIL_ERRNO_START+118)/* 320118 */
+/**
+ * @hideinitializer
+ * Found attribute after FINGERPRINT. This is not valid since FINGERPRINT
+ * MUST be the last attribute in the message.
+ */
+#define PJLIB_UTIL_ESTUNFINGERPOS   (PJLIB_UTIL_ERRNO_START+119)/* 320119 */
 /**
  * @hideinitializer
  * Missing STUN USERNAME attribute.
  * When credential is included in the STUN message (MESSAGE-INTEGRITY is
  * present), the USERNAME attribute must be present in the message.
  */
-#define PJLIB_UTIL_ESTUNNOUSERNAME  (PJLIB_UTIL_ERRNO_START+118)/* 320118 */
+#define PJLIB_UTIL_ESTUNNOUSERNAME  (PJLIB_UTIL_ERRNO_START+120)/* 320120 */
 
 
 #define PJ_STATUS_FROM_STUN_CODE(code)	(PJLIB_UTIL_ERRNO_START+code)
diff --git a/pjlib-util/src/pjlib-util/stun_msg.c b/pjlib-util/src/pjlib-util/stun_msg.c
index e8153e1..6954434 100644
--- a/pjlib-util/src/pjlib-util/stun_msg.c
+++ b/pjlib-util/src/pjlib-util/stun_msg.c
@@ -988,7 +988,7 @@
     pj_stun_msg_integrity_attr *attr;
 
     /* Check that struct size is valid */
-    pj_assert(sizeof(pj_stun_msg_integrity_attr)==STUN_MSG_INTEGRITY_LEN);
+    pj_assert(sizeof(pj_stun_msg_integrity_attr)==ATTR_LEN);
 
     /* Create attribute */
     attr = PJ_POOL_ZALLOC_TYPE(pool, pj_stun_msg_integrity_attr);
@@ -1856,9 +1856,7 @@
     /* Print each attribute */
     for (i=0; i<msg->attr_count; ++i) {
 	const struct attr_desc *adesc;
-	const pj_stun_attr_hdr *attr_hdr;
-
-	attr_hdr = msg->attr[i];
+	const pj_stun_attr_hdr *attr_hdr = msg->attr[i];
 
 	if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
 	    pj_assert(amsg_integrity == NULL);
@@ -1891,6 +1889,27 @@
 	buf_size -= printed;
     }
 
+    /* We may have stopped printing attribute because we found
+     * MESSAGE-INTEGRITY or FINGERPRINT. Scan the rest of the
+     * attributes.
+     */
+    for ( ++i; i<msg->attr_count; ++i) {
+	const pj_stun_attr_hdr *attr_hdr = msg->attr[i];
+
+	/* There mustn't any attribute after FINGERPRINT */
+	PJ_ASSERT_RETURN(afingerprint == NULL, PJLIB_UTIL_ESTUNFINGERPOS);
+
+	if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+	    /* There mustn't be MESSAGE-INTEGRITY before */
+	    PJ_ASSERT_RETURN(amsg_integrity == NULL, 
+			     PJLIB_UTIL_ESTUNMSGINTPOS);
+	    amsg_integrity = (pj_stun_msg_integrity_attr*) attr_hdr;
+
+	} else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) {
+	    afingerprint = (pj_stun_fingerprint_attr*) attr_hdr;
+	}
+    }
+
     /* We MUST update the message length in the header NOW before
      * calculating MESSAGE-INTEGRITY and FINGERPRINT. 
      * Note that length is not including the 20 bytes header.
@@ -1921,13 +1940,13 @@
 	if (i < msg->attr_count-2) {
 	    /* Should not happen for message generated by us */
 	    pj_assert(PJ_FALSE);
-	    return PJLIB_UTIL_ESTUNMSGINT;
+	    return PJLIB_UTIL_ESTUNMSGINTPOS;
 
 	} else if (i == msg->attr_count-2)  {
 	    if (msg->attr[i+1]->type != PJ_STUN_ATTR_FINGERPRINT) {
 		/* Should not happen for message generated by us */
 		pj_assert(PJ_FALSE);
-		return PJLIB_UTIL_ESTUNMSGINT;
+		return PJLIB_UTIL_ESTUNMSGINTPOS;
 	    } else {
 		afingerprint = (pj_stun_fingerprint_attr*) msg->attr[i+1];
 	    }
diff --git a/pjlib-util/src/pjlib-util/stun_session.c b/pjlib-util/src/pjlib-util/stun_session.c
index 579f0ac..eea1c2e 100644
--- a/pjlib-util/src/pjlib-util/stun_session.c
+++ b/pjlib-util/src/pjlib-util/stun_session.c
@@ -571,7 +571,7 @@
 
     /* Encode message */
     status = pj_stun_msg_encode(tdata->msg, tdata->pkt, tdata->max_len,
-			        0, NULL, &tdata->pkt_size);
+			        0, password, &tdata->pkt_size);
     if (status != PJ_SUCCESS) {
 	pj_stun_msg_destroy_tdata(sess, tdata);
 	pj_mutex_unlock(sess->mutex);
diff --git a/pjlib-util/src/pjstun-client/client_main.c b/pjlib-util/src/pjstun-client/client_main.c
index 4ef95d8..4f82f74 100644
--- a/pjlib-util/src/pjstun-client/client_main.c
+++ b/pjlib-util/src/pjstun-client/client_main.c
@@ -19,11 +19,35 @@
 #include <pjlib-util.h>
 #include <pjlib.h>
 
-#include <conio.h>
-
 
 #define THIS_FILE	"client_main.c"
 
+static struct global
+{
+    pj_stun_endpoint	*endpt;
+    pj_pool_t		*pool;
+    pj_caching_pool	 cp;
+    pj_timer_heap_t	*th;
+    pj_stun_session	*sess;
+    unsigned		 sess_options;
+    pj_sock_t		 sock;
+    pj_thread_t		*thread;
+    pj_bool_t		 quit;
+
+    pj_sockaddr_in	 dst_addr;  /**< destination addr */
+
+} g;
+
+static struct options
+{
+    char    *dst_addr;
+    char    *dst_port;
+    char    *realm;
+    char    *user_name;
+    char    *password;
+    pj_bool_t use_fingerprint;
+} o;
+
 
 static my_perror(const char *title, pj_status_t status)
 {
@@ -33,20 +57,17 @@
     PJ_LOG(3,(THIS_FILE, "%s: %s", title, errmsg));
 }
 
-static pj_status_t on_send_msg(pj_stun_tx_data *tdata,
+static pj_status_t on_send_msg(pj_stun_session *sess,
 			       const void *pkt,
 			       pj_size_t pkt_size,
-			       unsigned addr_len, 
-			       const pj_sockaddr_t *dst_addr)
+			       const pj_sockaddr_t *dst_addr,
+			       unsigned addr_len)
 {
-    pj_sock_t sock;
     pj_ssize_t len;
     pj_status_t status;
 
-    sock = (pj_sock_t) pj_stun_session_get_user_data(tdata->sess);
-
     len = pkt_size;
-    status = pj_sock_sendto(sock, pkt, &len, 0, dst_addr, addr_len);
+    status = pj_sock_sendto(g.sock, pkt, &len, 0, dst_addr, addr_len);
 
     if (status != PJ_SUCCESS)
 	my_perror("Error sending packet", status);
@@ -54,105 +75,295 @@
     return status;
 }
 
-static void on_bind_response(pj_stun_session *sess, 
-			     pj_status_t status, 
-			     pj_stun_tx_data *request,
-			     const pj_stun_msg *response)
+static void on_request_complete(pj_stun_session *sess,
+			        pj_status_t status,
+			        pj_stun_tx_data *tdata,
+			        const pj_stun_msg *response)
 {
-    my_perror("on_bind_response()", status);
+    if (status == PJ_SUCCESS) {
+	puts("Client transaction completes");
+    } else {
+	my_perror("Client transaction error", status);
+    }
 }
 
-int main()
+static int worker_thread(void *unused)
 {
-    pj_stun_endpoint *endpt = NULL;
-    pj_pool_t *pool = NULL;
-    pj_caching_pool cp;
-    pj_timer_heap_t *th = NULL;
-    pj_stun_session *sess;
-    pj_sock_t sock = PJ_INVALID_SOCKET;
-    pj_sockaddr_in addr;
-    pj_stun_session_cb stun_cb;
-    pj_stun_tx_data *tdata;
-    pj_str_t s;
-    pj_status_t status;
+    PJ_UNUSED_ARG(unused);
 
-    status = pj_init();
-    status = pjlib_util_init();
-
-    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
-    
-    pool = pj_pool_create(&cp.factory, NULL, 1000, 1000, NULL);
-
-    status = pj_timer_heap_create(pool, 1000, &th);
-    pj_assert(status == PJ_SUCCESS);
-
-    status = pj_stun_endpoint_create(&cp.factory, 0, NULL, th, &endpt);
-    pj_assert(status == PJ_SUCCESS);
-
-    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock);
-    pj_assert(status == PJ_SUCCESS);
-
-    status = pj_sockaddr_in_init(&addr, pj_cstr(&s, "127.0.0.1"), PJ_STUN_PORT);
-    pj_assert(status == PJ_SUCCESS);
-
-    pj_memset(&stun_cb, 0, sizeof(stun_cb));
-    stun_cb.on_send_msg = &on_send_msg;
-    stun_cb.on_bind_response = &on_bind_response;
-
-    status = pj_stun_session_create(endpt, NULL, &stun_cb, &sess);
-    pj_assert(status == PJ_SUCCESS);
-
-    pj_stun_session_set_user_data(sess, (void*)sock);
-
-    status = pj_stun_session_create_bind_req(sess, &tdata);
-    pj_assert(status == PJ_SUCCESS);
-
-    status = pj_stun_session_send_msg(sess, 0, sizeof(addr), &addr, tdata);
-    pj_assert(status == PJ_SUCCESS);
-
-    while (1) {
-	pj_fd_set_t rset;
+    while (!g.quit) {
+	pj_time_val timeout =  {0, 50};
+	pj_fd_set_t readset;
 	int n;
-	pj_time_val timeout;
 
-	if (kbhit()) {
-	    if (_getch()==27)
-		break;
-	}
+	pj_timer_heap_poll(g.th, NULL);
 
-	PJ_FD_ZERO(&rset);
-	PJ_FD_SET(sock, &rset);
+	PJ_FD_ZERO(&readset);
+	PJ_FD_SET(g.sock, &readset);
 
-	timeout.sec = 0; timeout.msec = 100;
+	n = pj_sock_select(g.sock+1, &readset, NULL, NULL, &timeout);
+	if (n > 0) {
+	    if (PJ_FD_ISSET(g.sock, &readset)) {
+		char buffer[512];
+		pj_ssize_t len;
+		pj_sockaddr_in addr;
+		int addrlen;
+		pj_status_t rc;
 
-	n = pj_sock_select(FD_SETSIZE, &rset, NULL, NULL, &timeout);
-
-	if (PJ_FD_ISSET(sock, &rset)) {
-	    char pkt[512];
-	    pj_ssize_t len;
-
-	    len = sizeof(pkt);
-	    status = pj_sock_recv(sock, pkt, &len, 0);
-	    if (status == PJ_SUCCESS) {
-		pj_stun_session_on_rx_pkt(sess, pkt, len, NULL);
+		len = sizeof(buffer);
+		addrlen = sizeof(addr);
+		rc = pj_sock_recvfrom(g.sock, buffer, &len, 0, &addr, &addrlen);
+		if (rc == PJ_SUCCESS && len > 0) {
+		    rc = pj_stun_session_on_rx_pkt(g.sess, buffer, len, 
+						   PJ_STUN_IS_DATAGRAM|PJ_STUN_CHECK_PACKET, 
+						   NULL, &addr, addrlen);
+		    if (rc != PJ_SUCCESS)
+			my_perror("Error processing packet", rc);
+		}
 	    }
-	}
-
-	pj_timer_heap_poll(th, NULL);
+	} else if (n < 0)
+	    pj_thread_sleep(50);
     }
 
-on_return:
-    if (sock != PJ_INVALID_SOCKET)
-	pj_sock_close(sock);
-    if (endpt)
-	pj_stun_endpoint_destroy(endpt);
-    if (th)
-	pj_timer_heap_destroy(th);
-    if (pool)
-	pj_pool_release(pool);
-    pj_caching_pool_destroy(&cp);
-
     return 0;
 }
 
+static int init()
+{
+    pj_sockaddr_in addr;
+    pj_stun_session_cb stun_cb;
+    pj_status_t status;
+
+    g.sock = PJ_INVALID_SOCKET;
+
+    status = pj_init();
+    status = pjlib_util_init();
+
+    pj_caching_pool_init(&g.cp, &pj_pool_factory_default_policy, 0);
+
+    if (o.dst_addr) {
+	pj_str_t s;
+	pj_uint16_t port;
+
+	if (o.dst_port)
+	    port = (pj_uint16_t) atoi(o.dst_port);
+	else
+	    port = PJ_STUN_PORT;
+
+	status = pj_sockaddr_in_init(&g.dst_addr, pj_cstr(&s, o.dst_addr), port);
+	if (status != PJ_SUCCESS) {
+	    my_perror("Invalid address", status);
+	    return status;
+	}
+
+	printf("Destination address set to %s:%d\n", o.dst_addr, (int)port);
+    } else {
+	printf("Error: address must be specified\n");
+	return PJ_EINVAL;
+    }
+
+    g.pool = pj_pool_create(&g.cp.factory, NULL, 1000, 1000, NULL);
+
+    status = pj_timer_heap_create(g.pool, 1000, &g.th);
+    pj_assert(status == PJ_SUCCESS);
+
+    status = pj_stun_endpoint_create(&g.cp.factory, 0, NULL, g.th, &g.endpt);
+    pj_assert(status == PJ_SUCCESS);
+
+    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &g.sock);
+    pj_assert(status == PJ_SUCCESS);
+
+    status = pj_sockaddr_in_init(&addr, NULL, 0);
+    pj_assert(status == PJ_SUCCESS);
+
+    pj_memset(&stun_cb, 0, sizeof(stun_cb));
+    stun_cb.on_send_msg = &on_send_msg;
+    stun_cb.on_request_complete = &on_request_complete;
+
+    status = pj_stun_session_create(g.endpt, NULL, &stun_cb, &g.sess);
+    pj_assert(status == PJ_SUCCESS);
+
+    if (o.realm) {
+	pj_str_t r, u, p;
+
+	if (o.user_name == NULL) {
+	    printf("error: username must be specified\n");
+	    return PJ_EINVAL;
+	}
+	if (o.password == NULL)
+	    o.password = "";
+	g.sess_options = PJ_STUN_USE_LONG_TERM_CRED;
+	pj_stun_session_set_long_term_credential(g.sess, pj_cstr(&r, o.realm),
+						 pj_cstr(&u, o.user_name),
+						 pj_cstr(&p, o.password));
+	puts("Using long term credential");
+    } else if (o.user_name) {
+	pj_str_t u, p;
+
+	if (o.password == NULL)
+	    o.password = "";
+	g.sess_options = PJ_STUN_USE_SHORT_TERM_CRED;
+	pj_stun_session_set_short_term_credential(g.sess, 
+						  pj_cstr(&u, o.user_name),
+						  pj_cstr(&p, o.password));
+	puts("Using short term credential");
+    } else {
+	puts("Credential not set");
+    }
+
+    if (o.use_fingerprint)
+	g.sess_options |= PJ_STUN_USE_FINGERPRINT;
+
+    status = pj_thread_create(g.pool, "stun", &worker_thread, NULL, 
+			      0, 0, &g.thread);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    return PJ_SUCCESS;
+}
+
+
+static int shutdown()
+{
+    if (g.thread) {
+	g.quit = 1;
+	pj_thread_join(g.thread);
+	pj_thread_destroy(g.thread);
+	g.thread = NULL;
+    }
+    if (g.sess)
+	pj_stun_session_destroy(g.sess);
+    if (g.endpt)
+	pj_stun_endpoint_destroy(g.endpt);
+    if (g.sock != PJ_INVALID_SOCKET)
+	pj_sock_close(g.sock);
+    if (g.th)
+	pj_timer_heap_destroy(g.th);
+    if (g.pool)
+	pj_pool_release(g.pool);
+
+    pj_pool_factory_dump(&g.cp.factory, PJ_TRUE);
+    pj_caching_pool_destroy(&g.cp);
+
+    return PJ_SUCCESS;
+}
+
+static void menu(void)
+{
+    puts("Menu:");
+    puts("  b      Send Bind request");
+    puts("  q      Quit");
+    puts("");
+    printf("Choice: ");
+}
+
+static void console_main(void)
+{
+    while (!g.quit) {
+	char input[10];
+
+	menu();
+
+	fgets(input, sizeof(input), stdin);
+	
+	switch (input[0]) {
+	case 'b':
+	    {
+		pj_stun_tx_data *tdata;
+		pj_status_t rc;
+
+		rc = pj_stun_session_create_bind_req(g.sess, &tdata);
+		pj_assert(rc == PJ_SUCCESS);
+
+		rc = pj_stun_session_send_msg(g.sess, g.sess_options, 
+					      &g.dst_addr, sizeof(g.dst_addr),
+					      tdata);
+		if (rc != PJ_SUCCESS)
+		    my_perror("Error sending STUN request", rc);
+	    }
+	    break;
+	case 'q':
+	    g.quit = 1;
+	    break;
+	default:
+	    break;
+	}
+    }
+}
+
+
+static void usage(void)
+{
+    puts("Usage: pjstun_client TARGET [OPTIONS]");
+    puts("");
+    puts("where TARGET is \"host[:port]\"");
+    puts("");
+    puts("and OPTIONS:");
+    puts(" --realm, -r       Set realm of the credential");
+    puts(" --username, -u    Set username of the credential");
+    puts(" --password, -p    Set password of the credential");
+    puts(" --fingerprint, -F Use fingerprint for outgoing requests");
+    puts(" --help, -h");
+}
+
+int main(int argc, char *argv[])
+{
+    struct pj_getopt_option long_options[] = {
+	{ "realm",	1, 0, 'r'},
+	{ "username",	1, 0, 'u'},
+	{ "password",	1, 0, 'p'},
+	{ "fingerprint",0, 0, 'F'},
+	{ "help",	0, 0, 'h'}
+    };
+    int c, opt_id;
+    char *pos;
+    pj_status_t status;
+
+    while((c=pj_getopt_long(argc,argv, "r:u:p:hF", long_options, &opt_id))!=-1) {
+	switch (c) {
+	case 'r':
+	    o.realm = pj_optarg;
+	    break;
+	case 'u':
+	    o.user_name = pj_optarg;
+	    break;
+	case 'p':
+	    o.password = pj_optarg;
+	    break;
+	case 'h':
+	    usage();
+	    return 0;
+	case 'F':
+	    o.use_fingerprint = PJ_TRUE;
+	    break;
+	default:
+	    printf("Argument \"%s\" is not valid. Use -h to see help",
+		   argv[pj_optind]);
+	    return 1;
+	}
+    }
+
+    if (pj_optind == argc) {
+	puts("Error: TARGET is needed");
+	return 1;
+    }
+
+    if ((pos=pj_ansi_strchr(argv[pj_optind], ':')) != NULL) {
+	o.dst_addr = argv[pj_optind];
+	*pos = '\0';
+	o.dst_port = pos+1;
+    } else {
+	o.dst_addr = argv[pj_optind];
+    }
+
+    status = init();
+    if (status != PJ_SUCCESS)
+	goto on_return;
+    
+    console_main();
+
+on_return:
+    shutdown();
+    return status ? 1 : 0;
+}
 
diff --git a/pjlib-util/src/pjstun-srv-test/server_main.c b/pjlib-util/src/pjstun-srv-test/server_main.c
index 6c15b5f..a731c0a 100644
--- a/pjlib-util/src/pjstun-srv-test/server_main.c
+++ b/pjlib-util/src/pjstun-srv-test/server_main.c
@@ -123,7 +123,6 @@
 {
     struct service *svc = (struct service *) pj_stun_session_get_user_data(sess);
     pj_stun_tx_data *tdata;
-    pj_stun_auth_policy pol;
     pj_status_t status;
 
     /* Create response */
@@ -131,20 +130,6 @@
     if (status != PJ_SUCCESS)
 	return status;
 
-#if 1
-    pj_memset(&pol, 0, sizeof(pol));
-    pol.type = PJ_STUN_POLICY_STATIC_LONG_TERM;
-    pol.user_data = NULL;
-    pol.data.static_long_term.realm = pj_str("realm");
-    pol.data.static_long_term.username = pj_str("user");
-    pol.data.static_long_term.password = pj_str("password");
-    pol.data.static_long_term.nonce = pj_str("nonce");
-    status = pj_stun_verify_credential(pkt, pkt_len, msg, &pol, tdata->pool, 
-				       &tdata->msg);
-    if (!tdata->msg)
-	return status;
-#endif
-
     /* Create MAPPED-ADDRESS attribute */
     status = pj_stun_msg_add_generic_ip_addr_attr(tdata->pool, tdata->msg,
 						  PJ_STUN_ATTR_MAPPED_ADDR,
@@ -387,15 +372,23 @@
     }
 #else
     pj_status_t status;
-    char line[10];
 
     status = pj_thread_create(server.pool, "stun_server", &worker_thread, NULL,
 			      0, 0, &server.threads[0]);
     if (status != PJ_SUCCESS)
 	return server_perror(THIS_FILE, "create_thread() error", status);
 
-    puts("Press ENTER to quit");
-    fgets(line, sizeof(line), stdin);
+    while (!server.thread_quit_flag) {
+	char line[10];
+
+	printf("Menu:\n"
+	       "  q     Quit\n"
+	       "Choice:");
+
+	fgets(line, sizeof(line), stdin);
+	if (line[0] == 'q')
+	    server.thread_quit_flag = 1;
+    }
 
 #endif