summaryrefslogtreecommitdiff
path: root/pjlib-util/src/pjlib-util
diff options
context:
space:
mode:
Diffstat (limited to 'pjlib-util/src/pjlib-util')
-rw-r--r--pjlib-util/src/pjlib-util/base64.c170
-rw-r--r--pjlib-util/src/pjlib-util/crc32.c243
-rw-r--r--pjlib-util/src/pjlib-util/dns.c744
-rw-r--r--pjlib-util/src/pjlib-util/dns_dump.c193
-rw-r--r--pjlib-util/src/pjlib-util/dns_server.c554
-rw-r--r--pjlib-util/src/pjlib-util/errno.c174
-rw-r--r--pjlib-util/src/pjlib-util/getopt.c731
-rw-r--r--pjlib-util/src/pjlib-util/hmac_md5.c97
-rw-r--r--pjlib-util/src/pjlib-util/hmac_sha1.c95
-rw-r--r--pjlib-util/src/pjlib-util/http_client.c1654
-rw-r--r--pjlib-util/src/pjlib-util/md5.c266
-rw-r--r--pjlib-util/src/pjlib-util/pcap.c392
-rw-r--r--pjlib-util/src/pjlib-util/resolver.c1588
-rw-r--r--pjlib-util/src/pjlib-util/resolver_wrap.cpp24
-rw-r--r--pjlib-util/src/pjlib-util/scanner.c636
-rw-r--r--pjlib-util/src/pjlib-util/scanner_cis_bitwise.c69
-rw-r--r--pjlib-util/src/pjlib-util/scanner_cis_uint.c46
-rw-r--r--pjlib-util/src/pjlib-util/sha1.c262
-rw-r--r--pjlib-util/src/pjlib-util/srv_resolver.c674
-rw-r--r--pjlib-util/src/pjlib-util/string.c110
-rw-r--r--pjlib-util/src/pjlib-util/stun_simple.c131
-rw-r--r--pjlib-util/src/pjlib-util/stun_simple_client.c335
-rw-r--r--pjlib-util/src/pjlib-util/symbols.c83
-rw-r--r--pjlib-util/src/pjlib-util/xml.c520
-rw-r--r--pjlib-util/src/pjlib-util/xml_wrap.cpp24
25 files changed, 9815 insertions, 0 deletions
diff --git a/pjlib-util/src/pjlib-util/base64.c b/pjlib-util/src/pjlib-util/base64.c
new file mode 100644
index 0000000..d92339c
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/base64.c
@@ -0,0 +1,170 @@
+/* $Id: base64.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/base64.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+
+#define INV -1
+#define PADDING '='
+
+const char base64_char[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '+', '/'
+};
+
+static int base256_char(char c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return (c - 'A');
+ else if (c >= 'a' && c <= 'z')
+ return (c - 'a' + 26);
+ else if (c >= '0' && c <= '9')
+ return (c - '0' + 52);
+ else if (c == '+')
+ return (62);
+ else if (c == '/')
+ return (63);
+ else {
+ /* It *may* happen on bad input, so this is not a good idea.
+ * pj_assert(!"Should not happen as '=' should have been filtered");
+ */
+ return INV;
+ }
+}
+
+
+static void base256to64(pj_uint8_t c1, pj_uint8_t c2, pj_uint8_t c3,
+ int padding, char *output)
+{
+ *output++ = base64_char[c1>>2];
+ *output++ = base64_char[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)];
+ switch (padding) {
+ case 0:
+ *output++ = base64_char[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
+ *output = base64_char[c3 & 0x3F];
+ break;
+ case 1:
+ *output++ = base64_char[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
+ *output = PADDING;
+ break;
+ case 2:
+ default:
+ *output++ = PADDING;
+ *output = PADDING;
+ break;
+ }
+}
+
+
+PJ_DEF(pj_status_t) pj_base64_encode(const pj_uint8_t *input, int in_len,
+ char *output, int *out_len)
+{
+ const pj_uint8_t *pi = input;
+ pj_uint8_t c1, c2, c3;
+ int i = 0;
+ char *po = output;
+
+ PJ_ASSERT_RETURN(input && output && out_len, PJ_EINVAL);
+ PJ_ASSERT_RETURN(*out_len >= PJ_BASE256_TO_BASE64_LEN(in_len),
+ PJ_ETOOSMALL);
+
+ while (i < in_len) {
+ c1 = *pi++;
+ ++i;
+
+ if (i == in_len) {
+ base256to64(c1, 0, 0, 2, po);
+ po += 4;
+ break;
+ } else {
+ c2 = *pi++;
+ ++i;
+
+ if (i == in_len) {
+ base256to64(c1, c2, 0, 1, po);
+ po += 4;
+ break;
+ } else {
+ c3 = *pi++;
+ ++i;
+ base256to64(c1, c2, c3, 0, po);
+ }
+ }
+
+ po += 4;
+ }
+
+ *out_len = po - output;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_base64_decode(const pj_str_t *input,
+ pj_uint8_t *out, int *out_len)
+{
+ const char *buf = input->ptr;
+ int len = input->slen;
+ int i, j, k;
+ int c[4];
+
+ PJ_ASSERT_RETURN(input && out && out_len, PJ_EINVAL);
+
+ while (buf[len-1] == '=' && len)
+ --len;
+
+ PJ_ASSERT_RETURN(*out_len >= PJ_BASE64_TO_BASE256_LEN(len),
+ PJ_ETOOSMALL);
+
+ for (i=0, j=0; i<len; ) {
+ /* Fill up c, silently ignoring invalid characters */
+ for (k=0; k<4 && i<len; ++k) {
+ do {
+ c[k] = base256_char(buf[i++]);
+ } while (c[k]==INV && i<len);
+ }
+
+ if (k<4) {
+ if (k > 1) {
+ out[j++] = (pj_uint8_t)((c[0]<<2) | ((c[1] & 0x30)>>4));
+ if (k > 2) {
+ out[j++] = (pj_uint8_t)
+ (((c[1] & 0x0F)<<4) | ((c[2] & 0x3C)>>2));
+ }
+ }
+ break;
+ }
+
+ out[j++] = (pj_uint8_t)((c[0]<<2) | ((c[1] & 0x30)>>4));
+ out[j++] = (pj_uint8_t)(((c[1] & 0x0F)<<4) | ((c[2] & 0x3C)>>2));
+ out[j++] = (pj_uint8_t)(((c[2] & 0x03)<<6) | (c[3] & 0x3F));
+ }
+
+ pj_assert(j < *out_len);
+ *out_len = j;
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjlib-util/src/pjlib-util/crc32.c b/pjlib-util/src/pjlib-util/crc32.c
new file mode 100644
index 0000000..7986387
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/crc32.c
@@ -0,0 +1,243 @@
+/* $Id: crc32.c 2511 2009-03-13 12:28:00Z bennylp $ */
+/*
+ * This is an implementation of CRC32. See ISO 3309 and ITU-T V.42
+ * for a formal specification
+ *
+ * This file is partly taken from Crypto++ library (http://www.cryptopp.com)
+ * and http://www.di-mgt.com.au/crypto.html#CRC.
+ *
+ * Since the original version of the code is put in public domain,
+ * this file is put on public domain as well.
+ */
+#include <pjlib-util/crc32.h>
+
+
+#define CRC32_NEGL 0xffffffffL
+
+#if defined(PJ_CRC32_HAS_TABLES) && PJ_CRC32_HAS_TABLES!=0
+// crc.cpp - written and placed in the public domain by Wei Dai
+
+/* Table of CRC-32's of all single byte values (made by makecrc.c) */
+#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN != 0
+
+#define CRC32_INDEX(c) (c & 0xff)
+#define CRC32_SHIFTED(c) (c >> 8)
+#define CRC32_SWAP(c) (c)
+
+static const pj_uint32_t crc_tab[] = {
+ 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+ 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+ 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+ 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+ 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+ 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+ 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+ 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+ 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+ 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+ 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+ 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+ 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+ 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+ 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+ 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+ 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+ 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+ 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+ 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+ 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+ 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+ 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+ 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+ 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+ 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+ 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+ 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+ 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+ 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+ 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+ 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+ 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+ 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+ 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+ 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+ 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+ 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+ 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+ 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+ 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+ 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+ 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+ 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+ 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+ 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+ 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+ 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+ 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+ 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+ 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+ 0x2d02ef8dL
+};
+
+
+#elif defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN != 0
+#define CRC32_INDEX(c) (c >> 24)
+#define CRC32_SHIFTED(c) (c << 8)
+#define CRC32_SWAP(c) ((((c) & 0xff000000) >> 24) | \
+ (((c) & 0x00ff0000) >> 8) | \
+ (((c) & 0x0000ff00) << 8) | \
+ (((c) & 0x000000ff) << 24))
+
+static const pj_uint32_t crc_tab[] = {
+ 0x00000000L, 0x96300777L, 0x2c610eeeL, 0xba510999L, 0x19c46d07L,
+ 0x8ff46a70L, 0x35a563e9L, 0xa395649eL, 0x3288db0eL, 0xa4b8dc79L,
+ 0x1ee9d5e0L, 0x88d9d297L, 0x2b4cb609L, 0xbd7cb17eL, 0x072db8e7L,
+ 0x911dbf90L, 0x6410b71dL, 0xf220b06aL, 0x4871b9f3L, 0xde41be84L,
+ 0x7dd4da1aL, 0xebe4dd6dL, 0x51b5d4f4L, 0xc785d383L, 0x56986c13L,
+ 0xc0a86b64L, 0x7af962fdL, 0xecc9658aL, 0x4f5c0114L, 0xd96c0663L,
+ 0x633d0ffaL, 0xf50d088dL, 0xc8206e3bL, 0x5e10694cL, 0xe44160d5L,
+ 0x727167a2L, 0xd1e4033cL, 0x47d4044bL, 0xfd850dd2L, 0x6bb50aa5L,
+ 0xfaa8b535L, 0x6c98b242L, 0xd6c9bbdbL, 0x40f9bcacL, 0xe36cd832L,
+ 0x755cdf45L, 0xcf0dd6dcL, 0x593dd1abL, 0xac30d926L, 0x3a00de51L,
+ 0x8051d7c8L, 0x1661d0bfL, 0xb5f4b421L, 0x23c4b356L, 0x9995bacfL,
+ 0x0fa5bdb8L, 0x9eb80228L, 0x0888055fL, 0xb2d90cc6L, 0x24e90bb1L,
+ 0x877c6f2fL, 0x114c6858L, 0xab1d61c1L, 0x3d2d66b6L, 0x9041dc76L,
+ 0x0671db01L, 0xbc20d298L, 0x2a10d5efL, 0x8985b171L, 0x1fb5b606L,
+ 0xa5e4bf9fL, 0x33d4b8e8L, 0xa2c90778L, 0x34f9000fL, 0x8ea80996L,
+ 0x18980ee1L, 0xbb0d6a7fL, 0x2d3d6d08L, 0x976c6491L, 0x015c63e6L,
+ 0xf4516b6bL, 0x62616c1cL, 0xd8306585L, 0x4e0062f2L, 0xed95066cL,
+ 0x7ba5011bL, 0xc1f40882L, 0x57c40ff5L, 0xc6d9b065L, 0x50e9b712L,
+ 0xeab8be8bL, 0x7c88b9fcL, 0xdf1ddd62L, 0x492dda15L, 0xf37cd38cL,
+ 0x654cd4fbL, 0x5861b24dL, 0xce51b53aL, 0x7400bca3L, 0xe230bbd4L,
+ 0x41a5df4aL, 0xd795d83dL, 0x6dc4d1a4L, 0xfbf4d6d3L, 0x6ae96943L,
+ 0xfcd96e34L, 0x468867adL, 0xd0b860daL, 0x732d0444L, 0xe51d0333L,
+ 0x5f4c0aaaL, 0xc97c0dddL, 0x3c710550L, 0xaa410227L, 0x10100bbeL,
+ 0x86200cc9L, 0x25b56857L, 0xb3856f20L, 0x09d466b9L, 0x9fe461ceL,
+ 0x0ef9de5eL, 0x98c9d929L, 0x2298d0b0L, 0xb4a8d7c7L, 0x173db359L,
+ 0x810db42eL, 0x3b5cbdb7L, 0xad6cbac0L, 0x2083b8edL, 0xb6b3bf9aL,
+ 0x0ce2b603L, 0x9ad2b174L, 0x3947d5eaL, 0xaf77d29dL, 0x1526db04L,
+ 0x8316dc73L, 0x120b63e3L, 0x843b6494L, 0x3e6a6d0dL, 0xa85a6a7aL,
+ 0x0bcf0ee4L, 0x9dff0993L, 0x27ae000aL, 0xb19e077dL, 0x44930ff0L,
+ 0xd2a30887L, 0x68f2011eL, 0xfec20669L, 0x5d5762f7L, 0xcb676580L,
+ 0x71366c19L, 0xe7066b6eL, 0x761bd4feL, 0xe02bd389L, 0x5a7ada10L,
+ 0xcc4add67L, 0x6fdfb9f9L, 0xf9efbe8eL, 0x43beb717L, 0xd58eb060L,
+ 0xe8a3d6d6L, 0x7e93d1a1L, 0xc4c2d838L, 0x52f2df4fL, 0xf167bbd1L,
+ 0x6757bca6L, 0xdd06b53fL, 0x4b36b248L, 0xda2b0dd8L, 0x4c1b0aafL,
+ 0xf64a0336L, 0x607a0441L, 0xc3ef60dfL, 0x55df67a8L, 0xef8e6e31L,
+ 0x79be6946L, 0x8cb361cbL, 0x1a8366bcL, 0xa0d26f25L, 0x36e26852L,
+ 0x95770cccL, 0x03470bbbL, 0xb9160222L, 0x2f260555L, 0xbe3bbac5L,
+ 0x280bbdb2L, 0x925ab42bL, 0x046ab35cL, 0xa7ffd7c2L, 0x31cfd0b5L,
+ 0x8b9ed92cL, 0x1daede5bL, 0xb0c2649bL, 0x26f263ecL, 0x9ca36a75L,
+ 0x0a936d02L, 0xa906099cL, 0x3f360eebL, 0x85670772L, 0x13570005L,
+ 0x824abf95L, 0x147ab8e2L, 0xae2bb17bL, 0x381bb60cL, 0x9b8ed292L,
+ 0x0dbed5e5L, 0xb7efdc7cL, 0x21dfdb0bL, 0xd4d2d386L, 0x42e2d4f1L,
+ 0xf8b3dd68L, 0x6e83da1fL, 0xcd16be81L, 0x5b26b9f6L, 0xe177b06fL,
+ 0x7747b718L, 0xe65a0888L, 0x706a0fffL, 0xca3b0666L, 0x5c0b0111L,
+ 0xff9e658fL, 0x69ae62f8L, 0xd3ff6b61L, 0x45cf6c16L, 0x78e20aa0L,
+ 0xeed20dd7L, 0x5483044eL, 0xc2b30339L, 0x612667a7L, 0xf71660d0L,
+ 0x4d476949L, 0xdb776e3eL, 0x4a6ad1aeL, 0xdc5ad6d9L, 0x660bdf40L,
+ 0xf03bd837L, 0x53aebca9L, 0xc59ebbdeL, 0x7fcfb247L, 0xe9ffb530L,
+ 0x1cf2bdbdL, 0x8ac2bacaL, 0x3093b353L, 0xa6a3b424L, 0x0536d0baL,
+ 0x9306d7cdL, 0x2957de54L, 0xbf67d923L, 0x2e7a66b3L, 0xb84a61c4L,
+ 0x021b685dL, 0x942b6f2aL, 0x37be0bb4L, 0xa18e0cc3L, 0x1bdf055aL,
+ 0x8def022dL
+};
+
+#else
+# error "Endianness not defined"
+#endif
+
+
+PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx)
+{
+ ctx->crc_state = 0;
+}
+
+PJ_DEF(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx,
+ const pj_uint8_t *data,
+ pj_size_t nbytes)
+{
+ pj_uint32_t crc = ctx->crc_state ^ CRC32_NEGL;
+
+ for( ; (((unsigned long)data) & 0x03) && nbytes > 0; --nbytes) {
+ crc = crc_tab[CRC32_INDEX(crc) ^ *data++] ^ CRC32_SHIFTED(crc);
+ }
+
+ while (nbytes >= 4) {
+ crc ^= *(const pj_uint32_t *)data;
+ crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc);
+ crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc);
+ crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc);
+ crc = crc_tab[CRC32_INDEX(crc)] ^ CRC32_SHIFTED(crc);
+ nbytes -= 4;
+ data += 4;
+ }
+
+ while (nbytes--) {
+ crc = crc_tab[CRC32_INDEX(crc) ^ *data++] ^ CRC32_SHIFTED(crc);
+ }
+
+ ctx->crc_state = crc ^ CRC32_NEGL;
+
+ return ctx->crc_state;
+}
+
+PJ_DEF(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx)
+{
+ return CRC32_SWAP(ctx->crc_state);
+}
+
+
+#else
+
+PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx)
+{
+ ctx->crc_state = CRC32_NEGL;
+}
+
+
+PJ_DEF(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx,
+ const pj_uint8_t *octets,
+ pj_size_t len)
+
+{
+ pj_uint32_t crc = ctx->crc_state;
+
+ while (len--) {
+ pj_uint32_t temp;
+ int j;
+
+ temp = (pj_uint32_t)((crc & 0xFF) ^ *octets++);
+ for (j = 0; j < 8; j++)
+ {
+ if (temp & 0x1)
+ temp = (temp >> 1) ^ 0xEDB88320;
+ else
+ temp >>= 1;
+ }
+ crc = (crc >> 8) ^ temp;
+ }
+ ctx->crc_state = crc;
+
+ return crc ^ CRC32_NEGL;
+}
+
+PJ_DEF(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx)
+{
+ ctx->crc_state ^= CRC32_NEGL;
+ return ctx->crc_state;
+}
+
+#endif
+
+
+PJ_DEF(pj_uint32_t) pj_crc32_calc( const pj_uint8_t *data,
+ pj_size_t nbytes)
+{
+ pj_crc32_context ctx;
+
+ pj_crc32_init(&ctx);
+ pj_crc32_update(&ctx, data, nbytes);
+ return pj_crc32_final(&ctx);
+}
+
diff --git a/pjlib-util/src/pjlib-util/dns.c b/pjlib-util/src/pjlib-util/dns.c
new file mode 100644
index 0000000..56e3461
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/dns.c
@@ -0,0 +1,744 @@
+/* $Id: dns.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/dns.h>
+#include <pjlib-util/errno.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/pool.h>
+#include <pj/sock.h>
+#include <pj/string.h>
+
+
+PJ_DEF(const char *) pj_dns_get_type_name(int type)
+{
+ switch (type) {
+ case PJ_DNS_TYPE_A: return "A";
+ case PJ_DNS_TYPE_AAAA: return "AAAA";
+ case PJ_DNS_TYPE_SRV: return "SRV";
+ case PJ_DNS_TYPE_NS: return "NS";
+ case PJ_DNS_TYPE_CNAME: return "CNAME";
+ case PJ_DNS_TYPE_PTR: return "PTR";
+ case PJ_DNS_TYPE_MX: return "MX";
+ case PJ_DNS_TYPE_TXT: return "TXT";
+ case PJ_DNS_TYPE_NAPTR: return "NAPTR";
+ }
+ return "(Unknown)";
+}
+
+
+static void write16(pj_uint8_t *p, pj_uint16_t val)
+{
+ p[0] = (pj_uint8_t)(val >> 8);
+ p[1] = (pj_uint8_t)(val & 0xFF);
+}
+
+
+/**
+ * Initialize a DNS query transaction.
+ */
+PJ_DEF(pj_status_t) pj_dns_make_query( void *packet,
+ unsigned *size,
+ pj_uint16_t id,
+ int qtype,
+ const pj_str_t *name)
+{
+ pj_uint8_t *query, *p = (pj_uint8_t*)packet;
+ const char *startlabel, *endlabel, *endname;
+ unsigned d;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(packet && size && qtype && name, PJ_EINVAL);
+
+ /* Calculate total number of bytes required. */
+ d = sizeof(pj_dns_hdr) + name->slen + 4;
+
+ /* Check that size is sufficient. */
+ PJ_ASSERT_RETURN(*size >= d, PJLIB_UTIL_EDNSQRYTOOSMALL);
+
+ /* Initialize header */
+ pj_assert(sizeof(pj_dns_hdr)==12);
+ pj_bzero(p, sizeof(struct pj_dns_hdr));
+ write16(p+0, id);
+ write16(p+2, (pj_uint16_t)PJ_DNS_SET_RD(1));
+ write16(p+4, (pj_uint16_t)1);
+
+ /* Initialize query */
+ query = p = ((pj_uint8_t*)packet)+sizeof(pj_dns_hdr);
+
+ /* Tokenize name */
+ startlabel = endlabel = name->ptr;
+ endname = name->ptr + name->slen;
+ while (endlabel != endname) {
+ while (endlabel != endname && *endlabel != '.')
+ ++endlabel;
+ *p++ = (pj_uint8_t)(endlabel - startlabel);
+ pj_memcpy(p, startlabel, endlabel-startlabel);
+ p += (endlabel-startlabel);
+ if (endlabel != endname && *endlabel == '.')
+ ++endlabel;
+ startlabel = endlabel;
+ }
+ *p++ = '\0';
+
+ /* Set type */
+ write16(p, (pj_uint16_t)qtype);
+ p += 2;
+
+ /* Set class (IN=1) */
+ write16(p, 1);
+ p += 2;
+
+ /* Done, calculate length */
+ *size = p - (pj_uint8_t*)packet;
+
+ return 0;
+}
+
+
+/* Get a name length (note: name consists of multiple labels and
+ * it may contain pointers when name compression is applied)
+ */
+static pj_status_t get_name_len(int rec_counter, const pj_uint8_t *pkt,
+ const pj_uint8_t *start, const pj_uint8_t *max,
+ int *parsed_len, int *name_len)
+{
+ const pj_uint8_t *p;
+ pj_status_t status;
+
+ /* Limit the number of recursion */
+ if (rec_counter > 10) {
+ /* Too many name recursion */
+ return PJLIB_UTIL_EDNSINNAMEPTR;
+ }
+
+ *name_len = *parsed_len = 0;
+ p = start;
+ while (*p) {
+ if ((*p & 0xc0) == 0xc0) {
+ /* Compression is found! */
+ int ptr_len = 0;
+ int dummy;
+ pj_uint16_t offset;
+
+ /* Get the 14bit offset */
+ pj_memcpy(&offset, p, 2);
+ offset ^= pj_htons((pj_uint16_t)(0xc0 << 8));
+ offset = pj_ntohs(offset);
+
+ /* Check that offset is valid */
+ if (offset >= max - pkt)
+ return PJLIB_UTIL_EDNSINNAMEPTR;
+
+ /* Get the name length from that offset. */
+ status = get_name_len(rec_counter+1, pkt, pkt + offset, max,
+ &dummy, &ptr_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *parsed_len += 2;
+ *name_len += ptr_len;
+
+ return PJ_SUCCESS;
+ } else {
+ unsigned label_len = *p;
+
+ /* Check that label length is valid */
+ if (pkt+label_len > max)
+ return PJLIB_UTIL_EDNSINNAMEPTR;
+
+ p += (label_len + 1);
+ *parsed_len += (label_len + 1);
+
+ if (*p != 0)
+ ++label_len;
+
+ *name_len += label_len;
+
+ if (p >= max)
+ return PJLIB_UTIL_EDNSINSIZE;
+ }
+ }
+ ++p;
+ (*parsed_len)++;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Parse and copy name (note: name consists of multiple labels and
+ * it may contain pointers when compression is applied).
+ */
+static pj_status_t get_name(int rec_counter, const pj_uint8_t *pkt,
+ const pj_uint8_t *start, const pj_uint8_t *max,
+ pj_str_t *name)
+{
+ const pj_uint8_t *p;
+ pj_status_t status;
+
+ /* Limit the number of recursion */
+ if (rec_counter > 10) {
+ /* Too many name recursion */
+ return PJLIB_UTIL_EDNSINNAMEPTR;
+ }
+
+ p = start;
+ while (*p) {
+ if ((*p & 0xc0) == 0xc0) {
+ /* Compression is found! */
+ pj_uint16_t offset;
+
+ /* Get the 14bit offset */
+ pj_memcpy(&offset, p, 2);
+ offset ^= pj_htons((pj_uint16_t)(0xc0 << 8));
+ offset = pj_ntohs(offset);
+
+ /* Check that offset is valid */
+ if (offset >= max - pkt)
+ return PJLIB_UTIL_EDNSINNAMEPTR;
+
+ /* Retrieve the name from that offset. */
+ status = get_name(rec_counter+1, pkt, pkt + offset, max, name);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return PJ_SUCCESS;
+ } else {
+ unsigned label_len = *p;
+
+ /* Check that label length is valid */
+ if (pkt+label_len > max)
+ return PJLIB_UTIL_EDNSINNAMEPTR;
+
+ pj_memcpy(name->ptr + name->slen, p+1, label_len);
+ name->slen += label_len;
+
+ p += label_len + 1;
+ if (*p != 0) {
+ *(name->ptr + name->slen) = '.';
+ ++name->slen;
+ }
+
+ if (p >= max)
+ return PJLIB_UTIL_EDNSINSIZE;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Parse query records. */
+static pj_status_t parse_query(pj_dns_parsed_query *q, pj_pool_t *pool,
+ const pj_uint8_t *pkt, const pj_uint8_t *start,
+ const pj_uint8_t *max, int *parsed_len)
+{
+ const pj_uint8_t *p = start;
+ int name_len, name_part_len;
+ pj_status_t status;
+
+ /* Get the length of the name */
+ status = get_name_len(0, pkt, start, max, &name_part_len, &name_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Allocate memory for the name */
+ q->name.ptr = (char*) pj_pool_alloc(pool, name_len+4);
+ q->name.slen = 0;
+
+ /* Get the name */
+ status = get_name(0, pkt, start, max, &q->name);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ p = (start + name_part_len);
+
+ /* Get the type */
+ pj_memcpy(&q->type, p, 2);
+ q->type = pj_ntohs(q->type);
+ p += 2;
+
+ /* Get the class */
+ pj_memcpy(&q->dnsclass, p, 2);
+ q->dnsclass = pj_ntohs(q->dnsclass);
+ p += 2;
+
+ *parsed_len = (int)(p - start);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Parse RR records */
+static pj_status_t parse_rr(pj_dns_parsed_rr *rr, pj_pool_t *pool,
+ const pj_uint8_t *pkt,
+ const pj_uint8_t *start, const pj_uint8_t *max,
+ int *parsed_len)
+{
+ const pj_uint8_t *p = start;
+ int name_len, name_part_len;
+ pj_status_t status;
+
+ /* Get the length of the name */
+ status = get_name_len(0, pkt, start, max, &name_part_len, &name_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Allocate memory for the name */
+ rr->name.ptr = (char*) pj_pool_alloc(pool, name_len+4);
+ rr->name.slen = 0;
+
+ /* Get the name */
+ status = get_name(0, pkt, start, max, &rr->name);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ p = (start + name_part_len);
+
+ /* Check the size can accomodate next few fields. */
+ if (p+10 > max)
+ return PJLIB_UTIL_EDNSINSIZE;
+
+ /* Get the type */
+ pj_memcpy(&rr->type, p, 2);
+ rr->type = pj_ntohs(rr->type);
+ p += 2;
+
+ /* Get the class */
+ pj_memcpy(&rr->dnsclass, p, 2);
+ rr->dnsclass = pj_ntohs(rr->dnsclass);
+ p += 2;
+
+ /* Class MUST be IN */
+ if (rr->dnsclass != 1)
+ return PJLIB_UTIL_EDNSINCLASS;
+
+ /* Get TTL */
+ pj_memcpy(&rr->ttl, p, 4);
+ rr->ttl = pj_ntohl(rr->ttl);
+ p += 4;
+
+ /* Get rdlength */
+ pj_memcpy(&rr->rdlength, p, 2);
+ rr->rdlength = pj_ntohs(rr->rdlength);
+ p += 2;
+
+ /* Check that length is valid */
+ if (p + rr->rdlength > max)
+ return PJLIB_UTIL_EDNSINSIZE;
+
+ /* Parse some well known records */
+ if (rr->type == PJ_DNS_TYPE_A) {
+ pj_memcpy(&rr->rdata.a.ip_addr, p, 4);
+ p += 4;
+
+ } else if (rr->type == PJ_DNS_TYPE_AAAA) {
+ pj_memcpy(&rr->rdata.aaaa.ip_addr, p, 16);
+ p += 16;
+
+ } else if (rr->type == PJ_DNS_TYPE_CNAME ||
+ rr->type == PJ_DNS_TYPE_NS ||
+ rr->type == PJ_DNS_TYPE_PTR)
+ {
+
+ /* Get the length of the target name */
+ status = get_name_len(0, pkt, p, max, &name_part_len, &name_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Allocate memory for the name */
+ rr->rdata.cname.name.ptr = (char*) pj_pool_alloc(pool, name_len);
+ rr->rdata.cname.name.slen = 0;
+
+ /* Get the name */
+ status = get_name(0, pkt, p, max, &rr->rdata.cname.name);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ p += name_part_len;
+
+ } else if (rr->type == PJ_DNS_TYPE_SRV) {
+
+ /* Priority */
+ pj_memcpy(&rr->rdata.srv.prio, p, 2);
+ rr->rdata.srv.prio = pj_ntohs(rr->rdata.srv.prio);
+ p += 2;
+
+ /* Weight */
+ pj_memcpy(&rr->rdata.srv.weight, p, 2);
+ rr->rdata.srv.weight = pj_ntohs(rr->rdata.srv.weight);
+ p += 2;
+
+ /* Port */
+ pj_memcpy(&rr->rdata.srv.port, p, 2);
+ rr->rdata.srv.port = pj_ntohs(rr->rdata.srv.port);
+ p += 2;
+
+ /* Get the length of the target name */
+ status = get_name_len(0, pkt, p, max, &name_part_len, &name_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Allocate memory for the name */
+ rr->rdata.srv.target.ptr = (char*) pj_pool_alloc(pool, name_len);
+ rr->rdata.srv.target.slen = 0;
+
+ /* Get the name */
+ status = get_name(0, pkt, p, max, &rr->rdata.srv.target);
+ if (status != PJ_SUCCESS)
+ return status;
+ p += name_part_len;
+
+ } else {
+ /* Copy the raw data */
+ rr->data = pj_pool_alloc(pool, rr->rdlength);
+ pj_memcpy(rr->data, p, rr->rdlength);
+
+ p += rr->rdlength;
+ }
+
+ *parsed_len = (int)(p - start);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Parse raw DNS packet into DNS packet structure.
+ */
+PJ_DEF(pj_status_t) pj_dns_parse_packet( pj_pool_t *pool,
+ const void *packet,
+ unsigned size,
+ pj_dns_parsed_packet **p_res)
+{
+ pj_dns_parsed_packet *res;
+ const pj_uint8_t *start, *end;
+ pj_status_t status;
+ unsigned i;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(pool && packet && size && p_res, PJ_EINVAL);
+
+ /* Packet size must be at least as big as the header */
+ if (size < sizeof(pj_dns_hdr))
+ return PJLIB_UTIL_EDNSINSIZE;
+
+ /* Create the structure */
+ res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet);
+
+ /* Copy the DNS header, and convert endianness to host byte order */
+ pj_memcpy(&res->hdr, packet, sizeof(pj_dns_hdr));
+ res->hdr.id = pj_ntohs(res->hdr.id);
+ res->hdr.flags = pj_ntohs(res->hdr.flags);
+ res->hdr.qdcount = pj_ntohs(res->hdr.qdcount);
+ res->hdr.anscount = pj_ntohs(res->hdr.anscount);
+ res->hdr.nscount = pj_ntohs(res->hdr.nscount);
+ res->hdr.arcount = pj_ntohs(res->hdr.arcount);
+
+ /* Mark start and end of payload */
+ start = ((const pj_uint8_t*)packet) + sizeof(pj_dns_hdr);
+ end = ((const pj_uint8_t*)packet) + size;
+
+ /* Parse query records (if any).
+ */
+ if (res->hdr.qdcount) {
+ res->q = (pj_dns_parsed_query*)
+ pj_pool_zalloc(pool, res->hdr.qdcount *
+ sizeof(pj_dns_parsed_query));
+ for (i=0; i<res->hdr.qdcount; ++i) {
+ int parsed_len = 0;
+
+ status = parse_query(&res->q[i], pool, (const pj_uint8_t*)packet,
+ start, end, &parsed_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ start += parsed_len;
+ }
+ }
+
+ /* Parse answer, if any */
+ if (res->hdr.anscount) {
+ res->ans = (pj_dns_parsed_rr*)
+ pj_pool_zalloc(pool, res->hdr.anscount *
+ sizeof(pj_dns_parsed_rr));
+
+ for (i=0; i<res->hdr.anscount; ++i) {
+ int parsed_len;
+
+ status = parse_rr(&res->ans[i], pool, (const pj_uint8_t*)packet,
+ start, end, &parsed_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ start += parsed_len;
+ }
+ }
+
+ /* Parse authoritative NS records, if any */
+ if (res->hdr.nscount) {
+ res->ns = (pj_dns_parsed_rr*)
+ pj_pool_zalloc(pool, res->hdr.nscount *
+ sizeof(pj_dns_parsed_rr));
+
+ for (i=0; i<res->hdr.nscount; ++i) {
+ int parsed_len;
+
+ status = parse_rr(&res->ns[i], pool, (const pj_uint8_t*)packet,
+ start, end, &parsed_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ start += parsed_len;
+ }
+ }
+
+ /* Parse additional RR answer, if any */
+ if (res->hdr.arcount) {
+ res->arr = (pj_dns_parsed_rr*)
+ pj_pool_zalloc(pool, res->hdr.arcount *
+ sizeof(pj_dns_parsed_rr));
+
+ for (i=0; i<res->hdr.arcount; ++i) {
+ int parsed_len;
+
+ status = parse_rr(&res->arr[i], pool, (const pj_uint8_t*)packet,
+ start, end, &parsed_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ start += parsed_len;
+ }
+ }
+
+ /* Looks like everything is okay */
+ *p_res = res;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Perform name compression scheme.
+ * If a name is already in the nametable, when no need to duplicate
+ * the string with the pool, but rather just use the pointer there.
+ */
+static void apply_name_table( unsigned *count,
+ pj_str_t nametable[],
+ const pj_str_t *src,
+ pj_pool_t *pool,
+ pj_str_t *dst)
+{
+ unsigned i;
+
+ /* Scan strings in nametable */
+ for (i=0; i<*count; ++i) {
+ if (pj_stricmp(&nametable[i], src) == 0)
+ break;
+ }
+
+ /* If name is found in nametable, use the pointer in the nametable */
+ if (i != *count) {
+ dst->ptr = nametable[i].ptr;
+ dst->slen = nametable[i].slen;
+ return;
+ }
+
+ /* Otherwise duplicate the string, and insert new name in nametable */
+ pj_strdup(pool, dst, src);
+
+ if (*count < PJ_DNS_MAX_NAMES_IN_NAMETABLE) {
+ nametable[*count].ptr = dst->ptr;
+ nametable[*count].slen = dst->slen;
+
+ ++(*count);
+ }
+}
+
+static void copy_query(pj_pool_t *pool, pj_dns_parsed_query *dst,
+ const pj_dns_parsed_query *src,
+ unsigned *nametable_count,
+ pj_str_t nametable[])
+{
+ pj_memcpy(dst, src, sizeof(*src));
+ apply_name_table(nametable_count, nametable, &src->name, pool, &dst->name);
+}
+
+
+static void copy_rr(pj_pool_t *pool, pj_dns_parsed_rr *dst,
+ const pj_dns_parsed_rr *src,
+ unsigned *nametable_count,
+ pj_str_t nametable[])
+{
+ pj_memcpy(dst, src, sizeof(*src));
+ apply_name_table(nametable_count, nametable, &src->name, pool, &dst->name);
+
+ if (src->data) {
+ dst->data = pj_pool_alloc(pool, src->rdlength);
+ pj_memcpy(dst->data, src->data, src->rdlength);
+ }
+
+ if (src->type == PJ_DNS_TYPE_SRV) {
+ apply_name_table(nametable_count, nametable, &src->rdata.srv.target,
+ pool, &dst->rdata.srv.target);
+ } else if (src->type == PJ_DNS_TYPE_A) {
+ dst->rdata.a.ip_addr.s_addr = src->rdata.a.ip_addr.s_addr;
+ } else if (src->type == PJ_DNS_TYPE_AAAA) {
+ pj_memcpy(&dst->rdata.aaaa.ip_addr, &src->rdata.aaaa.ip_addr,
+ sizeof(pj_in6_addr));
+ } else if (src->type == PJ_DNS_TYPE_CNAME) {
+ pj_strdup(pool, &dst->rdata.cname.name, &src->rdata.cname.name);
+ } else if (src->type == PJ_DNS_TYPE_NS) {
+ pj_strdup(pool, &dst->rdata.ns.name, &src->rdata.ns.name);
+ } else if (src->type == PJ_DNS_TYPE_PTR) {
+ pj_strdup(pool, &dst->rdata.ptr.name, &src->rdata.ptr.name);
+ }
+}
+
+/*
+ * Duplicate DNS packet.
+ */
+PJ_DEF(void) pj_dns_packet_dup(pj_pool_t *pool,
+ const pj_dns_parsed_packet*p,
+ unsigned options,
+ pj_dns_parsed_packet **p_dst)
+{
+ pj_dns_parsed_packet *dst;
+ unsigned nametable_count = 0;
+#if PJ_DNS_MAX_NAMES_IN_NAMETABLE
+ pj_str_t nametable[PJ_DNS_MAX_NAMES_IN_NAMETABLE];
+#else
+ pj_str_t *nametable = NULL;
+#endif
+ unsigned i;
+
+ PJ_ASSERT_ON_FAIL(pool && p && p_dst, return);
+
+ /* Create packet and copy header */
+ *p_dst = dst = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet);
+ pj_memcpy(&dst->hdr, &p->hdr, sizeof(p->hdr));
+
+ /* Initialize section counts in the target packet to zero.
+ * If memory allocation fails during copying process, the target packet
+ * should have a correct section counts.
+ */
+ dst->hdr.qdcount = 0;
+ dst->hdr.anscount = 0;
+ dst->hdr.nscount = 0;
+ dst->hdr.arcount = 0;
+
+
+ /* Copy query section */
+ if (p->hdr.qdcount && (options & PJ_DNS_NO_QD)==0) {
+ dst->q = (pj_dns_parsed_query*)
+ pj_pool_alloc(pool, p->hdr.qdcount *
+ sizeof(pj_dns_parsed_query));
+ for (i=0; i<p->hdr.qdcount; ++i) {
+ copy_query(pool, &dst->q[i], &p->q[i],
+ &nametable_count, nametable);
+ ++dst->hdr.qdcount;
+ }
+ }
+
+ /* Copy answer section */
+ if (p->hdr.anscount && (options & PJ_DNS_NO_ANS)==0) {
+ dst->ans = (pj_dns_parsed_rr*)
+ pj_pool_alloc(pool, p->hdr.anscount *
+ sizeof(pj_dns_parsed_rr));
+ for (i=0; i<p->hdr.anscount; ++i) {
+ copy_rr(pool, &dst->ans[i], &p->ans[i],
+ &nametable_count, nametable);
+ ++dst->hdr.anscount;
+ }
+ }
+
+ /* Copy NS section */
+ if (p->hdr.nscount && (options & PJ_DNS_NO_NS)==0) {
+ dst->ns = (pj_dns_parsed_rr*)
+ pj_pool_alloc(pool, p->hdr.nscount *
+ sizeof(pj_dns_parsed_rr));
+ for (i=0; i<p->hdr.nscount; ++i) {
+ copy_rr(pool, &dst->ns[i], &p->ns[i],
+ &nametable_count, nametable);
+ ++dst->hdr.nscount;
+ }
+ }
+
+ /* Copy additional info section */
+ if (p->hdr.arcount && (options & PJ_DNS_NO_AR)==0) {
+ dst->arr = (pj_dns_parsed_rr*)
+ pj_pool_alloc(pool, p->hdr.arcount *
+ sizeof(pj_dns_parsed_rr));
+ for (i=0; i<p->hdr.arcount; ++i) {
+ copy_rr(pool, &dst->arr[i], &p->arr[i],
+ &nametable_count, nametable);
+ ++dst->hdr.arcount;
+ }
+ }
+}
+
+
+PJ_DEF(void) pj_dns_init_srv_rr( pj_dns_parsed_rr *rec,
+ const pj_str_t *res_name,
+ unsigned dnsclass,
+ unsigned ttl,
+ unsigned prio,
+ unsigned weight,
+ unsigned port,
+ const pj_str_t *target)
+{
+ pj_bzero(rec, sizeof(*rec));
+ rec->name = *res_name;
+ rec->type = PJ_DNS_TYPE_SRV;
+ rec->dnsclass = (pj_uint16_t) dnsclass;
+ rec->ttl = ttl;
+ rec->rdata.srv.prio = (pj_uint16_t) prio;
+ rec->rdata.srv.weight = (pj_uint16_t) weight;
+ rec->rdata.srv.port = (pj_uint16_t) port;
+ rec->rdata.srv.target = *target;
+}
+
+
+PJ_DEF(void) pj_dns_init_cname_rr( pj_dns_parsed_rr *rec,
+ const pj_str_t *res_name,
+ unsigned dnsclass,
+ unsigned ttl,
+ const pj_str_t *name)
+{
+ pj_bzero(rec, sizeof(*rec));
+ rec->name = *res_name;
+ rec->type = PJ_DNS_TYPE_CNAME;
+ rec->dnsclass = (pj_uint16_t) dnsclass;
+ rec->ttl = ttl;
+ rec->rdata.cname.name = *name;
+}
+
+
+PJ_DEF(void) pj_dns_init_a_rr( pj_dns_parsed_rr *rec,
+ const pj_str_t *res_name,
+ unsigned dnsclass,
+ unsigned ttl,
+ const pj_in_addr *ip_addr)
+{
+ pj_bzero(rec, sizeof(*rec));
+ rec->name = *res_name;
+ rec->type = PJ_DNS_TYPE_A;
+ rec->dnsclass = (pj_uint16_t) dnsclass;
+ rec->ttl = ttl;
+ rec->rdata.a.ip_addr = *ip_addr;
+}
+
diff --git a/pjlib-util/src/pjlib-util/dns_dump.c b/pjlib-util/src/pjlib-util/dns_dump.c
new file mode 100644
index 0000000..85c8e85
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/dns_dump.c
@@ -0,0 +1,193 @@
+/* $Id: dns_dump.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/dns.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/string.h>
+
+#define THIS_FILE "dns_dump.c"
+#define LEVEL 3
+
+static const char *spell_ttl(char *buf, int size, unsigned ttl)
+{
+#define DAY (3600*24)
+#define HOUR (3600)
+#define MINUTE (60)
+
+ char *p = buf;
+ int len;
+
+ if (ttl > DAY) {
+ len = pj_ansi_snprintf(p, size, "%dd ", ttl/DAY);
+ if (len < 1)
+ return "-err-";
+ size -= len;
+ p += len;
+ ttl %= DAY;
+ }
+
+ if (ttl > HOUR) {
+ len = pj_ansi_snprintf(p, size, "%dh ", ttl/HOUR);
+ if (len < 1)
+ return "-err-";
+ size -= len;
+ p += len;
+ ttl %= HOUR;
+ }
+
+ if (ttl > MINUTE) {
+ len = pj_ansi_snprintf(p, size, "%dm ", ttl/MINUTE);
+ if (len < 1)
+ return "-err-";
+ size -= len;
+ p += len;
+ ttl %= MINUTE;
+ }
+
+ if (ttl > 0) {
+ len = pj_ansi_snprintf(p, size, "%ds ", ttl);
+ if (len < 1)
+ return "-err-";
+ size -= len;
+ p += len;
+ ttl = 0;
+ }
+
+ *p = '\0';
+ return buf;
+}
+
+
+static void dump_query(unsigned index, const pj_dns_parsed_query *q)
+{
+ PJ_LOG(3,(THIS_FILE, " %d. Name: %.*s",
+ index, (int)q->name.slen, q->name.ptr));
+ PJ_LOG(3,(THIS_FILE, " Type: %s (%d)",
+ pj_dns_get_type_name(q->type), q->type));
+ PJ_LOG(3,(THIS_FILE, " Class: %s (%d)",
+ (q->dnsclass==1 ? "IN" : "<Unknown>"), q->dnsclass));
+}
+
+static void dump_answer(unsigned index, const pj_dns_parsed_rr *rr)
+{
+ const pj_str_t root_name = { "<Root>", 6 };
+ const pj_str_t *name = &rr->name;
+ char ttl_words[32];
+
+ if (name->slen == 0)
+ name = &root_name;
+
+ PJ_LOG(3,(THIS_FILE, " %d. %s record (type=%d)",
+ index, pj_dns_get_type_name(rr->type),
+ rr->type));
+ PJ_LOG(3,(THIS_FILE, " Name: %.*s", (int)name->slen, name->ptr));
+ PJ_LOG(3,(THIS_FILE, " TTL: %u (%s)", rr->ttl,
+ spell_ttl(ttl_words, sizeof(ttl_words), rr->ttl)));
+ PJ_LOG(3,(THIS_FILE, " Data length: %u", rr->rdlength));
+
+ if (rr->type == PJ_DNS_TYPE_SRV) {
+ PJ_LOG(3,(THIS_FILE, " SRV: prio=%d, weight=%d %.*s:%d",
+ rr->rdata.srv.prio, rr->rdata.srv.weight,
+ (int)rr->rdata.srv.target.slen,
+ rr->rdata.srv.target.ptr,
+ rr->rdata.srv.port));
+ } else if (rr->type == PJ_DNS_TYPE_CNAME ||
+ rr->type == PJ_DNS_TYPE_NS ||
+ rr->type == PJ_DNS_TYPE_PTR)
+ {
+ PJ_LOG(3,(THIS_FILE, " Name: %.*s",
+ (int)rr->rdata.cname.name.slen,
+ rr->rdata.cname.name.ptr));
+ } else if (rr->type == PJ_DNS_TYPE_A) {
+ PJ_LOG(3,(THIS_FILE, " IP address: %s",
+ pj_inet_ntoa(rr->rdata.a.ip_addr)));
+ } else if (rr->type == PJ_DNS_TYPE_AAAA) {
+ char addr[PJ_INET6_ADDRSTRLEN];
+ PJ_LOG(3,(THIS_FILE, " IPv6 address: %s",
+ pj_inet_ntop2(pj_AF_INET6(), &rr->rdata.aaaa.ip_addr,
+ addr, sizeof(addr))));
+ }
+}
+
+
+PJ_DEF(void) pj_dns_dump_packet(const pj_dns_parsed_packet *res)
+{
+ unsigned i;
+
+ PJ_ASSERT_ON_FAIL(res != NULL, return);
+
+ /* Header part */
+ PJ_LOG(3,(THIS_FILE, "Domain Name System packet (%s):",
+ (PJ_DNS_GET_QR(res->hdr.flags) ? "response" : "query")));
+ PJ_LOG(3,(THIS_FILE, " Transaction ID: %d", res->hdr.id));
+ PJ_LOG(3,(THIS_FILE,
+ " Flags: opcode=%d, authoritative=%d, truncated=%d, rcode=%d",
+ PJ_DNS_GET_OPCODE(res->hdr.flags),
+ PJ_DNS_GET_AA(res->hdr.flags),
+ PJ_DNS_GET_TC(res->hdr.flags),
+ PJ_DNS_GET_RCODE(res->hdr.flags)));
+ PJ_LOG(3,(THIS_FILE, " Nb of queries: %d", res->hdr.qdcount));
+ PJ_LOG(3,(THIS_FILE, " Nb of answer RR: %d", res->hdr.anscount));
+ PJ_LOG(3,(THIS_FILE, " Nb of authority RR: %d", res->hdr.nscount));
+ PJ_LOG(3,(THIS_FILE, " Nb of additional RR: %d", res->hdr.arcount));
+ PJ_LOG(3,(THIS_FILE, ""));
+
+ /* Dump queries */
+ if (res->hdr.qdcount) {
+ PJ_LOG(3,(THIS_FILE, " Queries:"));
+
+ for (i=0; i<res->hdr.qdcount; ++i) {
+ dump_query(i, &res->q[i]);
+ }
+ PJ_LOG(3,(THIS_FILE, ""));
+ }
+
+ /* Dump answers */
+ if (res->hdr.anscount) {
+ PJ_LOG(3,(THIS_FILE, " Answers RR:"));
+
+ for (i=0; i<res->hdr.anscount; ++i) {
+ dump_answer(i, &res->ans[i]);
+ }
+ PJ_LOG(3,(THIS_FILE, ""));
+ }
+
+ /* Dump NS sections */
+ if (res->hdr.anscount) {
+ PJ_LOG(3,(THIS_FILE, " NS Authority RR:"));
+
+ for (i=0; i<res->hdr.nscount; ++i) {
+ dump_answer(i, &res->ns[i]);
+ }
+ PJ_LOG(3,(THIS_FILE, ""));
+ }
+
+ /* Dump Additional info sections */
+ if (res->hdr.arcount) {
+ PJ_LOG(3,(THIS_FILE, " Additional Info RR:"));
+
+ for (i=0; i<res->hdr.arcount; ++i) {
+ dump_answer(i, &res->arr[i]);
+ }
+ PJ_LOG(3,(THIS_FILE, ""));
+ }
+
+}
+
diff --git a/pjlib-util/src/pjlib-util/dns_server.c b/pjlib-util/src/pjlib-util/dns_server.c
new file mode 100644
index 0000000..2bd21c2
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/dns_server.c
@@ -0,0 +1,554 @@
+/* $Id: dns_server.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/dns_server.h>
+#include <pjlib-util/errno.h>
+#include <pj/activesock.h>
+#include <pj/assert.h>
+#include <pj/list.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "dns_server.c"
+#define MAX_ANS 16
+#define MAX_PKT 1500
+#define MAX_LABEL 32
+
+struct label_tab
+{
+ unsigned count;
+
+ struct {
+ unsigned pos;
+ pj_str_t label;
+ } a[MAX_LABEL];
+};
+
+struct rr
+{
+ PJ_DECL_LIST_MEMBER(struct rr);
+ pj_dns_parsed_rr rec;
+};
+
+
+struct pj_dns_server
+{
+ pj_pool_t *pool;
+ pj_pool_factory *pf;
+ pj_activesock_t *asock;
+ pj_ioqueue_op_key_t send_key;
+ struct rr rr_list;
+};
+
+
+static pj_bool_t on_data_recvfrom(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ const pj_sockaddr_t *src_addr,
+ int addr_len,
+ pj_status_t status);
+
+
+PJ_DEF(pj_status_t) pj_dns_server_create( pj_pool_factory *pf,
+ pj_ioqueue_t *ioqueue,
+ int af,
+ unsigned port,
+ unsigned flags,
+ pj_dns_server **p_srv)
+{
+ pj_pool_t *pool;
+ pj_dns_server *srv;
+ pj_sockaddr sock_addr;
+ pj_activesock_cb sock_cb;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pf && ioqueue && p_srv && flags==0, PJ_EINVAL);
+ PJ_ASSERT_RETURN(af==pj_AF_INET() || af==pj_AF_INET6(), PJ_EINVAL);
+
+ pool = pj_pool_create(pf, "dnsserver", 256, 256, NULL);
+ srv = (pj_dns_server*) PJ_POOL_ZALLOC_T(pool, pj_dns_server);
+ srv->pool = pool;
+ srv->pf = pf;
+ pj_list_init(&srv->rr_list);
+
+ pj_bzero(&sock_addr, sizeof(sock_addr));
+ sock_addr.addr.sa_family = (pj_uint16_t)af;
+ pj_sockaddr_set_port(&sock_addr, (pj_uint16_t)port);
+
+ pj_bzero(&sock_cb, sizeof(sock_cb));
+ sock_cb.on_data_recvfrom = &on_data_recvfrom;
+
+ status = pj_activesock_create_udp(pool, &sock_addr, NULL, ioqueue,
+ &sock_cb, srv, &srv->asock, NULL);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pj_ioqueue_op_key_init(&srv->send_key, sizeof(srv->send_key));
+
+ status = pj_activesock_start_recvfrom(srv->asock, pool, MAX_PKT, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ *p_srv = srv;
+ return PJ_SUCCESS;
+
+on_error:
+ pj_dns_server_destroy(srv);
+ return status;
+}
+
+
+PJ_DEF(pj_status_t) pj_dns_server_destroy(pj_dns_server *srv)
+{
+ PJ_ASSERT_RETURN(srv, PJ_EINVAL);
+
+ if (srv->asock) {
+ pj_activesock_close(srv->asock);
+ srv->asock = NULL;
+ }
+
+ if (srv->pool) {
+ pj_pool_t *pool = srv->pool;
+ srv->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static struct rr* find_rr( pj_dns_server *srv,
+ unsigned dns_class,
+ unsigned type /* pj_dns_type */,
+ const pj_str_t *name)
+{
+ struct rr *r;
+
+ r = srv->rr_list.next;
+ while (r != &srv->rr_list) {
+ if (r->rec.dnsclass == dns_class && r->rec.type == type &&
+ pj_stricmp(&r->rec.name, name)==0)
+ {
+ return r;
+ }
+ r = r->next;
+ }
+
+ return NULL;
+}
+
+
+PJ_DEF(pj_status_t) pj_dns_server_add_rec( pj_dns_server *srv,
+ unsigned count,
+ const pj_dns_parsed_rr rr_param[])
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(srv && count && rr_param, PJ_EINVAL);
+
+ for (i=0; i<count; ++i) {
+ struct rr *rr;
+
+ PJ_ASSERT_RETURN(find_rr(srv, rr_param[i].dnsclass, rr_param[i].type,
+ &rr_param[i].name) == NULL,
+ PJ_EEXISTS);
+
+ rr = (struct rr*) PJ_POOL_ZALLOC_T(srv->pool, struct rr);
+ pj_memcpy(&rr->rec, &rr_param[i], sizeof(pj_dns_parsed_rr));
+
+ pj_list_push_back(&srv->rr_list, rr);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_dns_server_del_rec( pj_dns_server *srv,
+ int dns_class,
+ pj_dns_type type,
+ const pj_str_t *name)
+{
+ struct rr *rr;
+
+ PJ_ASSERT_RETURN(srv && type && name, PJ_EINVAL);
+
+ rr = find_rr(srv, dns_class, type, name);
+ if (!rr)
+ return PJ_ENOTFOUND;
+
+ pj_list_erase(rr);
+
+ return PJ_SUCCESS;
+}
+
+
+static void write16(pj_uint8_t *p, pj_uint16_t val)
+{
+ p[0] = (pj_uint8_t)(val >> 8);
+ p[1] = (pj_uint8_t)(val & 0xFF);
+}
+
+static void write32(pj_uint8_t *p, pj_uint32_t val)
+{
+ val = pj_htonl(val);
+ pj_memcpy(p, &val, 4);
+}
+
+static int print_name(pj_uint8_t *pkt, int size,
+ pj_uint8_t *pos, const pj_str_t *name,
+ struct label_tab *tab)
+{
+ pj_uint8_t *p = pos;
+ const char *endlabel, *endname;
+ unsigned i;
+ pj_str_t label;
+
+ /* Check if name is in the table */
+ for (i=0; i<tab->count; ++i) {
+ if (pj_strcmp(&tab->a[i].label, name)==0)
+ break;
+ }
+
+ if (i != tab->count) {
+ write16(p, (pj_uint16_t)(tab->a[i].pos | (0xc0 << 8)));
+ return 2;
+ } else {
+ if (tab->count < MAX_LABEL) {
+ tab->a[tab->count].pos = (p-pkt);
+ tab->a[tab->count].label.ptr = (char*)(p+1);
+ tab->a[tab->count].label.slen = name->slen;
+ ++tab->count;
+ }
+ }
+
+ endlabel = name->ptr;
+ endname = name->ptr + name->slen;
+
+ label.ptr = (char*)name->ptr;
+
+ while (endlabel != endname) {
+
+ while (endlabel != endname && *endlabel != '.')
+ ++endlabel;
+
+ label.slen = (endlabel - label.ptr);
+
+ if (size < label.slen+1)
+ return -1;
+
+ *p = (pj_uint8_t)label.slen;
+ pj_memcpy(p+1, label.ptr, label.slen);
+
+ size -= (label.slen+1);
+ p += (label.slen+1);
+
+ if (endlabel != endname && *endlabel == '.')
+ ++endlabel;
+ label.ptr = (char*)endlabel;
+ }
+
+ if (size == 0)
+ return -1;
+
+ *p++ = '\0';
+
+ return p-pos;
+}
+
+static int print_rr(pj_uint8_t *pkt, int size, pj_uint8_t *pos,
+ const pj_dns_parsed_rr *rr, struct label_tab *tab)
+{
+ pj_uint8_t *p = pos;
+ int len;
+
+ len = print_name(pkt, size, pos, &rr->name, tab);
+ if (len < 0)
+ return -1;
+
+ p += len;
+ size -= len;
+
+ if (size < 8)
+ return -1;
+
+ pj_assert(rr->dnsclass == 1);
+
+ write16(p+0, (pj_uint16_t)rr->type); /* type */
+ write16(p+2, (pj_uint16_t)rr->dnsclass); /* class */
+ write32(p+4, rr->ttl); /* TTL */
+
+ p += 8;
+ size -= 8;
+
+ if (rr->type == PJ_DNS_TYPE_A) {
+
+ if (size < 6)
+ return -1;
+
+ /* RDLEN is 4 */
+ write16(p, 4);
+
+ /* Address */
+ pj_memcpy(p+2, &rr->rdata.a.ip_addr, 4);
+
+ p += 6;
+ size -= 6;
+
+ } else if (rr->type == PJ_DNS_TYPE_CNAME ||
+ rr->type == PJ_DNS_TYPE_NS ||
+ rr->type == PJ_DNS_TYPE_PTR) {
+
+ if (size < 4)
+ return -1;
+
+ len = print_name(pkt, size-2, p+2, &rr->rdata.cname.name, tab);
+ if (len < 0)
+ return -1;
+
+ write16(p, (pj_uint16_t)len);
+
+ p += (len + 2);
+ size -= (len + 2);
+
+ } else if (rr->type == PJ_DNS_TYPE_SRV) {
+
+ if (size < 10)
+ return -1;
+
+ write16(p+2, rr->rdata.srv.prio); /* Priority */
+ write16(p+4, rr->rdata.srv.weight); /* Weight */
+ write16(p+6, rr->rdata.srv.port); /* Port */
+
+ /* Target */
+ len = print_name(pkt, size-8, p+8, &rr->rdata.srv.target, tab);
+ if (len < 0)
+ return -1;
+
+ /* RDLEN */
+ write16(p, (pj_uint16_t)(len + 6));
+
+ p += (len + 8);
+ size -= (len + 8);
+
+ } else {
+ pj_assert(!"Not supported");
+ return -1;
+ }
+
+ return p-pos;
+}
+
+static int print_packet(const pj_dns_parsed_packet *rec, pj_uint8_t *pkt,
+ int size)
+{
+ pj_uint8_t *p = pkt;
+ struct label_tab tab;
+ int i, len;
+
+ tab.count = 0;
+
+ pj_assert(sizeof(pj_dns_hdr)==12);
+ if (size < (int)sizeof(pj_dns_hdr))
+ return -1;
+
+ /* Initialize header */
+ write16(p+0, rec->hdr.id);
+ write16(p+2, rec->hdr.flags);
+ write16(p+4, rec->hdr.qdcount);
+ write16(p+6, rec->hdr.anscount);
+ write16(p+8, rec->hdr.nscount);
+ write16(p+10, rec->hdr.arcount);
+
+ p = pkt + sizeof(pj_dns_hdr);
+ size -= sizeof(pj_dns_hdr);
+
+ /* Print queries */
+ for (i=0; i<rec->hdr.qdcount; ++i) {
+
+ len = print_name(pkt, size, p, &rec->q[i].name, &tab);
+ if (len < 0)
+ return -1;
+
+ p += len;
+ size -= len;
+
+ if (size < 4)
+ return -1;
+
+ /* Set type */
+ write16(p+0, (pj_uint16_t)rec->q[i].type);
+
+ /* Set class (IN=1) */
+ pj_assert(rec->q[i].dnsclass == 1);
+ write16(p+2, rec->q[i].dnsclass);
+
+ p += 4;
+ }
+
+ /* Print answers */
+ for (i=0; i<rec->hdr.anscount; ++i) {
+ len = print_rr(pkt, size, p, &rec->ans[i], &tab);
+ if (len < 0)
+ return -1;
+
+ p += len;
+ size -= len;
+ }
+
+ /* Print NS records */
+ for (i=0; i<rec->hdr.nscount; ++i) {
+ len = print_rr(pkt, size, p, &rec->ns[i], &tab);
+ if (len < 0)
+ return -1;
+
+ p += len;
+ size -= len;
+ }
+
+ /* Print additional records */
+ for (i=0; i<rec->hdr.arcount; ++i) {
+ len = print_rr(pkt, size, p, &rec->arr[i], &tab);
+ if (len < 0)
+ return -1;
+
+ p += len;
+ size -= len;
+ }
+
+ return p - pkt;
+}
+
+
+static pj_bool_t on_data_recvfrom(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ const pj_sockaddr_t *src_addr,
+ int addr_len,
+ pj_status_t status)
+{
+ pj_dns_server *srv;
+ pj_pool_t *pool;
+ pj_dns_parsed_packet *req;
+ pj_dns_parsed_packet ans;
+ struct rr *rr;
+ pj_ssize_t pkt_len;
+ unsigned i;
+
+ if (status != PJ_SUCCESS)
+ return PJ_TRUE;
+
+ srv = (pj_dns_server*) pj_activesock_get_user_data(asock);
+ pool = pj_pool_create(srv->pf, "dnssrvrx", 512, 256, NULL);
+
+ status = pj_dns_parse_packet(pool, data, size, &req);
+ if (status != PJ_SUCCESS) {
+ char addrinfo[PJ_INET6_ADDRSTRLEN+10];
+ pj_sockaddr_print(src_addr, addrinfo, sizeof(addrinfo), 3);
+ PJ_LOG(4,(THIS_FILE, "Error parsing query from %s", addrinfo));
+ goto on_return;
+ }
+
+ /* Init answer */
+ pj_bzero(&ans, sizeof(ans));
+ ans.hdr.id = req->hdr.id;
+ ans.hdr.qdcount = 1;
+ ans.q = (pj_dns_parsed_query*) PJ_POOL_ALLOC_T(pool, pj_dns_parsed_query);
+ pj_memcpy(ans.q, req->q, sizeof(pj_dns_parsed_query));
+
+ if (req->hdr.qdcount != 1) {
+ ans.hdr.flags = PJ_DNS_SET_RCODE(PJ_DNS_RCODE_FORMERR);
+ goto send_pkt;
+ }
+
+ if (req->q[0].dnsclass != PJ_DNS_CLASS_IN) {
+ ans.hdr.flags = PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NOTIMPL);
+ goto send_pkt;
+ }
+
+ /* Find the record */
+ rr = find_rr(srv, req->q->dnsclass, req->q->type, &req->q->name);
+ if (rr == NULL) {
+ ans.hdr.flags = PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN);
+ goto send_pkt;
+ }
+
+ /* Init answer record */
+ ans.hdr.anscount = 0;
+ ans.ans = (pj_dns_parsed_rr*)
+ pj_pool_calloc(pool, MAX_ANS, sizeof(pj_dns_parsed_rr));
+
+ /* DNS SRV query needs special treatment since it returns multiple
+ * records
+ */
+ if (req->q->type == PJ_DNS_TYPE_SRV) {
+ struct rr *r;
+
+ r = srv->rr_list.next;
+ while (r != &srv->rr_list) {
+ if (r->rec.dnsclass == req->q->dnsclass &&
+ r->rec.type == PJ_DNS_TYPE_SRV &&
+ pj_stricmp(&r->rec.name, &req->q->name)==0 &&
+ ans.hdr.anscount < MAX_ANS)
+ {
+ pj_memcpy(&ans.ans[ans.hdr.anscount], &r->rec,
+ sizeof(pj_dns_parsed_rr));
+ ++ans.hdr.anscount;
+ }
+ r = r->next;
+ }
+ } else {
+ /* Otherwise just copy directly from the server record */
+ pj_memcpy(&ans.ans[ans.hdr.anscount], &rr->rec,
+ sizeof(pj_dns_parsed_rr));
+ ++ans.hdr.anscount;
+ }
+
+ /* For each CNAME entry, add A entry */
+ for (i=0; i<ans.hdr.anscount && ans.hdr.anscount < MAX_ANS; ++i) {
+ if (ans.ans[i].type == PJ_DNS_TYPE_CNAME) {
+ struct rr *r;
+
+ r = find_rr(srv, ans.ans[i].dnsclass, PJ_DNS_TYPE_A,
+ &ans.ans[i].name);
+ pj_memcpy(&ans.ans[ans.hdr.anscount], &r->rec,
+ sizeof(pj_dns_parsed_rr));
+ ++ans.hdr.anscount;
+ }
+ }
+
+send_pkt:
+ pkt_len = print_packet(&ans, (pj_uint8_t*)data, MAX_PKT);
+ if (pkt_len < 1) {
+ PJ_LOG(4,(THIS_FILE, "Error: answer too large"));
+ goto on_return;
+ }
+
+ status = pj_activesock_sendto(srv->asock, &srv->send_key, data, &pkt_len,
+ 0, src_addr, addr_len);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ PJ_LOG(4,(THIS_FILE, "Error sending answer, status=%d", status));
+ goto on_return;
+ }
+
+on_return:
+ pj_pool_release(pool);
+ return PJ_TRUE;
+}
+
diff --git a/pjlib-util/src/pjlib-util/errno.c b/pjlib-util/src/pjlib-util/errno.c
new file mode 100644
index 0000000..36a6b2e
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/errno.c
@@ -0,0 +1,174 @@
+/* $Id: errno.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/errno.h>
+#include <pjlib-util/types.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+
+
+
+/* PJLIB_UTIL's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING!=0
+static const struct
+{
+ int code;
+ const char *msg;
+} err_str[] =
+{
+ /* STUN errors */
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNRESOLVE, "Unable to resolve STUN server" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNINMSGTYPE, "Unknown STUN message type" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNINMSGLEN, "Invalid STUN message length" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNINATTRLEN, "STUN attribute length error" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNINATTRTYPE, "Invalid STUN attribute type" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNININDEX, "Invalid STUN server/socket index" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNNOBINDRES, "No STUN binding response in the message" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNRECVERRATTR, "Received STUN error attribute" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNNOMAP, "No STUN mapped address attribute" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNNOTRESPOND, "Received no response from STUN server" ),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNSYMMETRIC, "Symetric NAT detected by STUN" ),
+
+ /* XML errors */
+ PJ_BUILD_ERR( PJLIB_UTIL_EINXML, "Invalid XML message" ),
+
+ /* DNS errors */
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSQRYTOOSMALL, "DNS query packet buffer is too small"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSINSIZE, "Invalid DNS packet length"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSINCLASS, "Invalid DNS class"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSINNAMEPTR, "Invalid DNS name pointer"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSINNSADDR, "Invalid DNS nameserver address"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSNONS, "No nameserver is in DNS resolver"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSNOWORKINGNS, "No working DNS nameserver"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSNOANSWERREC, "No answer record in the DNS response"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSINANSWER, "Invalid DNS answer"),
+
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_FORMERR, "DNS \"Format error\""),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_SERVFAIL, "DNS \"Server failure\""),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_NXDOMAIN, "DNS \"Name Error\""),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_NOTIMPL, "DNS \"Not Implemented\""),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_REFUSED, "DNS \"Refused\""),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_YXDOMAIN, "DNS \"The name exists\""),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_YXRRSET, "DNS \"The RRset (name, type) exists\""),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_NXRRSET, "DNS \"The RRset (name, type) does not exist\""),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_NOTAUTH, "DNS \"Not authorized\""),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNS_NOTZONE, "DNS \"The zone specified is not a zone\""),
+
+ /* STUN */
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNTOOMANYATTR, "Too many STUN attributes"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNUNKNOWNATTR, "Unknown STUN attribute"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNINADDRLEN, "Invalid STUN socket address length"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNNOTRESPONSE, "Expecting STUN response message"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNINVALIDID, "STUN transaction ID mismatch"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNNOHANDLER, "Unable to find STUN handler for the request"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNMSGINTPOS, "Found non-FINGERPRINT attr. after MESSAGE-INTEGRITY"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNFINGERPOS, "Found STUN attribute after FINGERPRINT"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNNOUSERNAME, "Missing STUN USERNAME attribute"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNMSGINT, "Missing/invalid STUN MESSAGE-INTEGRITY attribute"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNDUPATTR, "Found duplicate STUN attribute"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNNOREALM, "Missing STUN REALM attribute"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNNONCE, "Missing/stale STUN NONCE attribute value"),
+ PJ_BUILD_ERR( PJLIB_UTIL_ESTUNTSXFAILED, "STUN transaction terminates with failure"),
+
+ /* HTTP Client */
+ PJ_BUILD_ERR( PJLIB_UTIL_EHTTPINURL, "Invalid URL format"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EHTTPINPORT, "Invalid URL port number"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EHTTPINCHDR, "Incomplete response header received"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EHTTPINSBUF, "Insufficient buffer"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EHTTPLOST, "Connection lost"),
+};
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+/*
+ * pjlib_util_strerror()
+ */
+pj_str_t pjlib_util_strerror(pj_status_t statcode,
+ char *buf, pj_size_t bufsize )
+{
+ pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+ if (statcode >= PJLIB_UTIL_ERRNO_START &&
+ statcode < PJLIB_UTIL_ERRNO_START + PJ_ERRNO_SPACE_SIZE)
+ {
+ /* Find the error in the table.
+ * Use binary search!
+ */
+ int first = 0;
+ int n = PJ_ARRAY_SIZE(err_str);
+
+ while (n > 0) {
+ int half = n/2;
+ int mid = first + half;
+
+ if (err_str[mid].code < statcode) {
+ first = mid+1;
+ n -= (half+1);
+ } else if (err_str[mid].code > statcode) {
+ n = half;
+ } else {
+ first = mid;
+ break;
+ }
+ }
+
+
+ if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+ pj_str_t msg;
+
+ msg.ptr = (char*)err_str[first].msg;
+ msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ }
+ }
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjlib-util error %d",
+ statcode);
+
+ return errstr;
+}
+
+
+PJ_DEF(pj_status_t) pjlib_util_init(void)
+{
+ pj_status_t status;
+
+ status = pj_register_strerror(PJLIB_UTIL_ERRNO_START,
+ PJ_ERRNO_SPACE_SIZE,
+ &pjlib_util_strerror);
+ pj_assert(status == PJ_SUCCESS);
+
+ return PJ_SUCCESS;
+}
diff --git a/pjlib-util/src/pjlib-util/getopt.c b/pjlib-util/src/pjlib-util/getopt.c
new file mode 100644
index 0000000..845cef7
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/getopt.c
@@ -0,0 +1,731 @@
+/* $Id: getopt.c 3550 2011-05-05 05:33:27Z nanang $ */
+/*
+ * pj_getopt entry points
+ *
+ * modified by Mike Borella <mike_borella@mw.3com.com>
+ */
+
+#include <pjlib-util/getopt.h>
+#include <pj/string.h>
+
+/* Internal only. Users should not call this directly. */
+static
+int _getopt_internal (int argc, char *const *argv,
+ const char *shortopts,
+ const struct pj_getopt_option *longopts, int *longind,
+ int long_only);
+
+/* pj_getopt_long and pj_getopt_long_only entry points for GNU pj_getopt.
+ Copyright (C) 1987,88,89,90,91,92,93,94,96,97 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the GNU C Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA. */
+
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+# define GETOPT_INTERFACE_VERSION 2
+
+
+int
+pj_getopt_long (int argc, char *const *argv, const char *options,
+ const struct pj_getopt_option *long_options, int *opt_index)
+{
+ return _getopt_internal (argc, argv, options, long_options, opt_index, 0);
+}
+
+/* Like pj_getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int
+pj_getopt (int argc, char * const * argv, const char * optstring)
+{
+ return _getopt_internal (argc, argv, optstring,
+ (const struct pj_getopt_option *) 0,
+ (int *) 0,
+ 0);
+}
+
+
+#define _(msgid) (msgid)
+
+/* This version of `pj_getopt' appears to the caller like standard Unix `pj_getopt'
+ but it behaves differently for the user, since it allows the user
+ to intersperse the options with the other arguments.
+
+ As `pj_getopt' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Setting the environment variable POSIXLY_CORRECT disables permutation.
+ Then the behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+/* For communication from `pj_getopt' to the caller.
+ When `pj_getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *pj_optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `pj_getopt'.
+
+ On entry to `pj_getopt', zero means this is the first call; initialize.
+
+ When `pj_getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `pj_optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int pj_optind = 1;
+
+/* Formerly, initialization of pj_getopt depended on pj_optind==0, which
+ causes problems with re-calling pj_getopt as programs generally don't
+ know that. */
+
+int __getopt_initialized = 0;
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+static char *nextchar;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own pj_getopt implementation. */
+
+int pj_optopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters.
+
+ PERMUTE is the default. We permute the contents of ARGV as we scan,
+ so that eventually all the non-options are at the end. This allows options
+ to be given in any order, even with programs that were not written to
+ expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were written
+ to expect options and other ARGV-elements in any order and that care about
+ the ordering of the two. We describe each non-option ARGV-element
+ as if it were the argument of an option with character code 1.
+ Using `-' as the first character of the list of option characters
+ selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `pj_getopt' to return -1 with `pj_optind' != ARGC. */
+
+static enum
+{
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} ordering;
+
+/* Value of POSIXLY_CORRECT environment variable. */
+static char *posixly_correct;
+
+static char *
+my_index (const char *str, int chr)
+{
+ while (*str)
+ {
+ if (*str == chr)
+ return (char *) str;
+ str++;
+ }
+ return 0;
+}
+
+
+/* Handle permutation of arguments. */
+
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+static int first_nonopt;
+static int last_nonopt;
+
+# define SWAP_FLAGS(ch1, ch2)
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,pj_optind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+static void
+exchange (char **argv)
+{
+ int bottom = first_nonopt;
+ int middle = last_nonopt;
+ int top = pj_optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ SWAP_FLAGS (bottom + i, top - (middle - bottom) + i);
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ SWAP_FLAGS (bottom + i, middle + i);
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ first_nonopt += (pj_optind - last_nonopt);
+ last_nonopt = pj_optind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+static const char *_getopt_initialize (int argc, char *const *argv,
+ const char *optstring)
+{
+ PJ_UNUSED_ARG(argc);
+ PJ_UNUSED_ARG(argv);
+
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ first_nonopt = last_nonopt = pj_optind;
+
+ nextchar = NULL;
+
+ //posixly_correct = getenv ("POSIXLY_CORRECT");
+ posixly_correct = NULL;
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-')
+ {
+ ordering = RETURN_IN_ORDER;
+ ++optstring;
+ }
+ else if (optstring[0] == '+')
+ {
+ ordering = REQUIRE_ORDER;
+ ++optstring;
+ }
+ else if (posixly_correct != NULL)
+ ordering = REQUIRE_ORDER;
+ else
+ ordering = PERMUTE;
+
+ return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `pj_getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `pj_getopt' finds another option character, it returns that character,
+ updating `pj_optind' and `nextchar' so that the next call to `pj_getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `pj_getopt' returns -1.
+ Then `pj_optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `pj_opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `pj_optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `pj_optarg', otherwise `pj_optarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `pj_getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ The elements of ARGV aren't really const, because we permute them.
+ But we pretend they're const in the prototype to be compatible
+ with other systems.
+
+ LONGOPTS is a vector of `struct pj_getopt_option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options. */
+
+static int
+_getopt_internal (int argc, char *const *argv, const char *optstring,
+ const struct pj_getopt_option *longopts, int *longind,
+ int long_only)
+{
+ pj_optarg = NULL;
+
+ if (pj_optind == 0 || !__getopt_initialized)
+ {
+ if (pj_optind == 0)
+ pj_optind = 1; /* Don't scan ARGV[0], the program name. */
+ optstring = _getopt_initialize (argc, argv, optstring);
+ __getopt_initialized = 1;
+ }
+
+ /* Test whether ARGV[pj_optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+#define NONOPTION_P (argv[pj_optind][0] != '-' || argv[pj_optind][1] == '\0')
+
+ if (nextchar == NULL || *nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ moved back by the user (who may also have changed the arguments). */
+ if (last_nonopt > pj_optind)
+ last_nonopt = pj_optind;
+ if (first_nonopt > pj_optind)
+ first_nonopt = pj_optind;
+
+ if (ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (first_nonopt != last_nonopt && last_nonopt != pj_optind)
+ exchange ((char **) argv);
+ else if (last_nonopt != pj_optind)
+ first_nonopt = pj_optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (pj_optind < argc && NONOPTION_P)
+ pj_optind++;
+ last_nonopt = pj_optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (pj_optind != argc && !pj_ansi_strcmp(argv[pj_optind], "--"))
+ {
+ pj_optind++;
+
+ if (first_nonopt != last_nonopt && last_nonopt != pj_optind)
+ exchange ((char **) argv);
+ else if (first_nonopt == last_nonopt)
+ first_nonopt = pj_optind;
+ last_nonopt = argc;
+
+ pj_optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (pj_optind == argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (first_nonopt != last_nonopt)
+ pj_optind = first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if (NONOPTION_P)
+ {
+ if (ordering == REQUIRE_ORDER)
+ return -1;
+ pj_optarg = argv[pj_optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ nextchar = (argv[pj_optind] + 1
+ + (longopts != NULL && argv[pj_optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[pj_optind][1] == '-'
+ || (long_only && (argv[pj_optind][2] || !my_index (optstring, argv[pj_optind][1])))))
+ {
+ char *nameend;
+ const struct pj_getopt_option *p;
+ const struct pj_getopt_option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, nextchar, nameend - nextchar))
+ {
+ if ((unsigned int) (nameend - nextchar)
+ == (unsigned int) strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ nextchar += strlen (nextchar);
+ pj_optind++;
+ pj_optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ pj_optind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ pj_optarg = nameend + 1;
+ else
+ {
+ nextchar += strlen (nextchar);
+
+ pj_optopt = pfound->val;
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (pj_optind < argc)
+ pj_optarg = argv[pj_optind++];
+ else
+ {
+ nextchar += strlen (nextchar);
+ pj_optopt = pfound->val;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen (nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not pj_getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[pj_optind][1] == '-'
+ || my_index (optstring, *nextchar) == NULL)
+ {
+ nextchar = (char *) "";
+ pj_optind++;
+ pj_optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *nextchar++;
+ char *temp = my_index (optstring, c);
+
+ /* Increment `pj_optind' when we start to process its last character. */
+ if (*nextchar == '\0')
+ ++pj_optind;
+
+ if (temp == NULL || c == ':')
+ {
+ pj_optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';')
+ {
+ char *nameend;
+ const struct pj_getopt_option *p;
+ const struct pj_getopt_option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0')
+ {
+ pj_optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ pj_optind++;
+ }
+ else if (pj_optind == argc)
+ {
+ pj_optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ }
+ else
+ /* We already incremented `pj_optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ pj_optarg = argv[pj_optind++];
+
+ /* pj_optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (nextchar = nameend = pj_optarg; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, nextchar, nameend - nextchar))
+ {
+ if ((unsigned int) (nameend - nextchar) == strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact)
+ {
+ nextchar += strlen (nextchar);
+ pj_optind++;
+ return '?';
+ }
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ pj_optarg = nameend + 1;
+ else
+ {
+ nextchar += strlen (nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (pj_optind < argc)
+ pj_optarg = argv[pj_optind++];
+ else
+ {
+ nextchar += strlen (nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen (nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*nextchar != '\0')
+ {
+ pj_optarg = nextchar;
+ pj_optind++;
+ }
+ else
+ pj_optarg = NULL;
+ nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0')
+ {
+ pj_optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ pj_optind++;
+ }
+ else if (pj_optind == argc)
+ {
+ pj_optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented `pj_optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ pj_optarg = argv[pj_optind++];
+ nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
diff --git a/pjlib-util/src/pjlib-util/hmac_md5.c b/pjlib-util/src/pjlib-util/hmac_md5.c
new file mode 100644
index 0000000..9d61e39
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/hmac_md5.c
@@ -0,0 +1,97 @@
+/* $Id: hmac_md5.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/hmac_md5.h>
+#include <pj/string.h>
+
+
+PJ_DEF(void) pj_hmac_md5_init(pj_hmac_md5_context *hctx,
+ const pj_uint8_t *key, unsigned key_len)
+{
+ pj_uint8_t k_ipad[64];
+ pj_uint8_t tk[16];
+ int i;
+
+ /* if key is longer than 64 bytes reset it to key=MD5(key) */
+ if (key_len > 64) {
+ pj_md5_context tctx;
+
+ pj_md5_init(&tctx);
+ pj_md5_update(&tctx, key, key_len);
+ pj_md5_final(&tctx, tk);
+
+ key = tk;
+ key_len = 16;
+ }
+
+ /*
+ * HMAC = H(K XOR opad, H(K XOR ipad, text))
+ */
+
+ /* start out by storing key in pads */
+ pj_bzero( k_ipad, sizeof(k_ipad));
+ pj_bzero( hctx->k_opad, sizeof(hctx->k_opad));
+ pj_memcpy( k_ipad, key, key_len);
+ pj_memcpy( hctx->k_opad, key, key_len);
+
+ /* XOR key with ipad and opad values */
+ for (i=0; i<64; i++) {
+ k_ipad[i] ^= 0x36;
+ hctx->k_opad[i] ^= 0x5c;
+ }
+ /*
+ * perform inner MD5
+ */
+ pj_md5_init(&hctx->context);
+ pj_md5_update(&hctx->context, k_ipad, 64);
+
+}
+
+PJ_DEF(void) pj_hmac_md5_update(pj_hmac_md5_context *hctx,
+ const pj_uint8_t *input,
+ unsigned input_len)
+{
+ pj_md5_update(&hctx->context, input, input_len);
+}
+
+PJ_DEF(void) pj_hmac_md5_final(pj_hmac_md5_context *hctx,
+ pj_uint8_t digest[16])
+{
+ pj_md5_final(&hctx->context, digest);
+
+ /*
+ * perform outer MD5
+ */
+ pj_md5_init(&hctx->context);
+ pj_md5_update(&hctx->context, hctx->k_opad, 64);
+ pj_md5_update(&hctx->context, digest, 16);
+ pj_md5_final(&hctx->context, digest);
+}
+
+PJ_DEF(void) pj_hmac_md5( const pj_uint8_t *input, unsigned input_len,
+ const pj_uint8_t *key, unsigned key_len,
+ pj_uint8_t digest[16] )
+{
+ pj_hmac_md5_context ctx;
+
+ pj_hmac_md5_init(&ctx, key, key_len);
+ pj_hmac_md5_update(&ctx, input, input_len);
+ pj_hmac_md5_final(&ctx, digest);
+}
+
diff --git a/pjlib-util/src/pjlib-util/hmac_sha1.c b/pjlib-util/src/pjlib-util/hmac_sha1.c
new file mode 100644
index 0000000..394a01a
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/hmac_sha1.c
@@ -0,0 +1,95 @@
+/* $Id: hmac_sha1.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/hmac_sha1.h>
+#include <pj/string.h>
+
+
+PJ_DEF(void) pj_hmac_sha1_init(pj_hmac_sha1_context *hctx,
+ const pj_uint8_t *key, unsigned key_len)
+{
+ pj_uint8_t k_ipad[64];
+ pj_uint8_t tk[20];
+ unsigned i;
+
+ /* if key is longer than 64 bytes reset it to key=SHA1(key) */
+ if (key_len > 64) {
+ pj_sha1_context tctx;
+
+ pj_sha1_init(&tctx);
+ pj_sha1_update(&tctx, key, key_len);
+ pj_sha1_final(&tctx, tk);
+
+ key = tk;
+ key_len = 20;
+ }
+
+ /*
+ * HMAC = H(K XOR opad, H(K XOR ipad, text))
+ */
+
+ /* start out by storing key in pads */
+ pj_bzero( k_ipad, sizeof(k_ipad));
+ pj_bzero( hctx->k_opad, sizeof(hctx->k_opad));
+ pj_memcpy( k_ipad, key, key_len);
+ pj_memcpy( hctx->k_opad, key, key_len);
+
+ /* XOR key with ipad and opad values */
+ for (i=0; i<64; i++) {
+ k_ipad[i] ^= 0x36;
+ hctx->k_opad[i] ^= 0x5c;
+ }
+ /*
+ * perform inner SHA1
+ */
+ pj_sha1_init(&hctx->context);
+ pj_sha1_update(&hctx->context, k_ipad, 64);
+}
+
+PJ_DEF(void) pj_hmac_sha1_update(pj_hmac_sha1_context *hctx,
+ const pj_uint8_t *input, unsigned input_len)
+{
+ pj_sha1_update(&hctx->context, input, input_len);
+}
+
+PJ_DEF(void) pj_hmac_sha1_final(pj_hmac_sha1_context *hctx,
+ pj_uint8_t digest[20])
+{
+ pj_sha1_final(&hctx->context, digest);
+
+ /*
+ * perform outer SHA1
+ */
+ pj_sha1_init(&hctx->context);
+ pj_sha1_update(&hctx->context, hctx->k_opad, 64);
+ pj_sha1_update(&hctx->context, digest, 20);
+ pj_sha1_final(&hctx->context, digest);
+}
+
+PJ_DEF(void) pj_hmac_sha1(const pj_uint8_t *input, unsigned input_len,
+ const pj_uint8_t *key, unsigned key_len,
+ pj_uint8_t digest[20] )
+{
+ pj_hmac_sha1_context ctx;
+
+ pj_hmac_sha1_init(&ctx, key, key_len);
+ pj_hmac_sha1_update(&ctx, input, input_len);
+ pj_hmac_sha1_final(&ctx, digest);
+}
+
diff --git a/pjlib-util/src/pjlib-util/http_client.c b/pjlib-util/src/pjlib-util/http_client.c
new file mode 100644
index 0000000..6053620
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/http_client.c
@@ -0,0 +1,1654 @@
+/* $Id: http_client.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <pjlib-util/http_client.h>
+#include <pj/activesock.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/errno.h>
+#include <pj/except.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/timer.h>
+#include <pj/rand.h>
+#include <pjlib-util/base64.h>
+#include <pjlib-util/errno.h>
+#include <pjlib-util/md5.h>
+#include <pjlib-util/scanner.h>
+#include <pjlib-util/string.h>
+
+#define THIS_FILE "http_client.c"
+
+#if 0
+ /* Enable some tracing */
+ #define TRACE_(arg) PJ_LOG(3,arg)
+#else
+ #define TRACE_(arg)
+#endif
+
+#define NUM_PROTOCOL 2
+#define HTTP_1_0 "1.0"
+#define HTTP_1_1 "1.1"
+#define CONTENT_LENGTH "Content-Length"
+/* Buffer size for sending/receiving messages. */
+#define BUF_SIZE 2048
+/* Initial data buffer size to store the data in case content-
+ * length is not specified in the server's response.
+ */
+#define INITIAL_DATA_BUF_SIZE 2048
+#define INITIAL_POOL_SIZE 1024
+#define POOL_INCREMENT_SIZE 512
+
+enum http_protocol
+{
+ PROTOCOL_HTTP,
+ PROTOCOL_HTTPS
+};
+
+static const char *http_protocol_names[NUM_PROTOCOL] =
+{
+ "HTTP",
+ "HTTPS"
+};
+
+static const unsigned int http_default_port[NUM_PROTOCOL] =
+{
+ 80,
+ 443
+};
+
+enum http_method
+{
+ HTTP_GET,
+ HTTP_PUT,
+ HTTP_DELETE
+};
+
+static const char *http_method_names[3] =
+{
+ "GET",
+ "PUT",
+ "DELETE"
+};
+
+enum http_state
+{
+ IDLE,
+ CONNECTING,
+ SENDING_REQUEST,
+ SENDING_REQUEST_BODY,
+ REQUEST_SENT,
+ READING_RESPONSE,
+ READING_DATA,
+ READING_COMPLETE,
+ ABORTING,
+};
+
+enum auth_state
+{
+ AUTH_NONE, /* Not authenticating */
+ AUTH_RETRYING, /* New request with auth has been submitted */
+ AUTH_DONE /* Done retrying the request with auth. */
+};
+
+struct pj_http_req
+{
+ pj_str_t url; /* Request URL */
+ pj_http_url hurl; /* Parsed request URL */
+ pj_sockaddr addr; /* The host's socket address */
+ pj_http_req_param param; /* HTTP request parameters */
+ pj_pool_t *pool; /* Pool to allocate memory from */
+ pj_timer_heap_t *timer; /* Timer for timeout management */
+ pj_ioqueue_t *ioqueue; /* Ioqueue to use */
+ pj_http_req_callback cb; /* Callbacks */
+ pj_activesock_t *asock; /* Active socket */
+ pj_status_t error; /* Error status */
+ pj_str_t buffer; /* Buffer to send/receive msgs */
+ enum http_state state; /* State of the HTTP request */
+ enum auth_state auth_state; /* Authentication state */
+ pj_timer_entry timer_entry;/* Timer entry */
+ pj_bool_t resolved; /* Whether URL's host is resolved */
+ pj_http_resp response; /* HTTP response */
+ pj_ioqueue_op_key_t op_key;
+ struct tcp_state
+ {
+ /* Total data sent so far if the data is sent in segments (i.e.
+ * if on_send_data() is not NULL and if param.reqdata.total_size > 0)
+ */
+ pj_size_t tot_chunk_size;
+ /* Size of data to be sent (in a single activesock operation).*/
+ pj_size_t send_size;
+ /* Data size sent so far. */
+ pj_size_t current_send_size;
+ /* Total data received so far. */
+ pj_size_t current_read_size;
+ } tcp_state;
+};
+
+/* Start sending the request */
+static pj_status_t http_req_start_sending(pj_http_req *hreq);
+/* Start reading the response */
+static pj_status_t http_req_start_reading(pj_http_req *hreq);
+/* End the request */
+static pj_status_t http_req_end_request(pj_http_req *hreq);
+/* Parse the header data and populate the header fields with the result. */
+static pj_status_t http_headers_parse(char *hdata, pj_size_t size,
+ pj_http_headers *headers);
+/* Parse the response */
+static pj_status_t http_response_parse(pj_pool_t *pool,
+ pj_http_resp *response,
+ void *data, pj_size_t size,
+ pj_size_t *remainder);
+/* Restart the request with authentication */
+static void restart_req_with_auth(pj_http_req *hreq);
+/* Parse authentication challenge */
+static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input,
+ pj_http_auth_chal *chal);
+
+static pj_uint16_t get_http_default_port(const pj_str_t *protocol)
+{
+ int i;
+
+ for (i = 0; i < NUM_PROTOCOL; i++) {
+ if (!pj_stricmp2(protocol, http_protocol_names[i])) {
+ return (pj_uint16_t)http_default_port[i];
+ }
+ }
+ return 0;
+}
+
+static const char * get_protocol(const pj_str_t *protocol)
+{
+ int i;
+
+ for (i = 0; i < NUM_PROTOCOL; i++) {
+ if (!pj_stricmp2(protocol, http_protocol_names[i])) {
+ return http_protocol_names[i];
+ }
+ }
+
+ /* Should not happen */
+ pj_assert(0);
+ return NULL;
+}
+
+
+/* Syntax error handler for parser. */
+static void on_syntax_error(pj_scanner *scanner)
+{
+ PJ_UNUSED_ARG(scanner);
+ PJ_THROW(PJ_EINVAL); // syntax error
+}
+
+/* Callback when connection is established to the server */
+static pj_bool_t http_on_connect(pj_activesock_t *asock,
+ pj_status_t status)
+{
+ pj_http_req *hreq = (pj_http_req*) pj_activesock_get_user_data(asock);
+
+ if (hreq->state == ABORTING || hreq->state == IDLE)
+ return PJ_FALSE;
+
+ if (status != PJ_SUCCESS) {
+ hreq->error = status;
+ pj_http_req_cancel(hreq, PJ_TRUE);
+ return PJ_FALSE;
+ }
+
+ /* OK, we are connected. Start sending the request */
+ hreq->state = SENDING_REQUEST;
+ http_req_start_sending(hreq);
+ return PJ_TRUE;
+}
+
+static pj_bool_t http_on_data_sent(pj_activesock_t *asock,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t sent)
+{
+ pj_http_req *hreq = (pj_http_req*) pj_activesock_get_user_data(asock);
+
+ PJ_UNUSED_ARG(op_key);
+
+ if (hreq->state == ABORTING || hreq->state == IDLE)
+ return PJ_FALSE;
+
+ if (sent <= 0) {
+ hreq->error = (sent < 0 ? -sent : PJLIB_UTIL_EHTTPLOST);
+ pj_http_req_cancel(hreq, PJ_TRUE);
+ return PJ_FALSE;
+ }
+
+ hreq->tcp_state.current_send_size += sent;
+ TRACE_((THIS_FILE, "\nData sent: %d out of %d bytes",
+ hreq->tcp_state.current_send_size, hreq->tcp_state.send_size));
+ if (hreq->tcp_state.current_send_size == hreq->tcp_state.send_size) {
+ /* Find out whether there is a request body to send. */
+ if (hreq->param.reqdata.total_size > 0 ||
+ hreq->param.reqdata.size > 0)
+ {
+ if (hreq->state == SENDING_REQUEST) {
+ /* Start sending the request body */
+ hreq->state = SENDING_REQUEST_BODY;
+ hreq->tcp_state.tot_chunk_size = 0;
+ pj_assert(hreq->param.reqdata.total_size == 0 ||
+ (hreq->param.reqdata.total_size > 0 &&
+ hreq->param.reqdata.size == 0));
+ } else {
+ /* Continue sending the next chunk of the request body */
+ hreq->tcp_state.tot_chunk_size += hreq->tcp_state.send_size;
+ if (hreq->tcp_state.tot_chunk_size ==
+ hreq->param.reqdata.total_size ||
+ hreq->param.reqdata.total_size == 0)
+ {
+ /* Finish sending all the chunks, start reading
+ * the response.
+ */
+ hreq->state = REQUEST_SENT;
+ http_req_start_reading(hreq);
+ return PJ_TRUE;
+ }
+ }
+ if (hreq->param.reqdata.total_size > 0 &&
+ hreq->cb.on_send_data)
+ {
+ /* Call the callback for the application to provide
+ * the next chunk of data to be sent.
+ */
+ (*hreq->cb.on_send_data)(hreq, &hreq->param.reqdata.data,
+ &hreq->param.reqdata.size);
+ /* Make sure the total data size given by the user does not
+ * exceed what the user originally said.
+ */
+ pj_assert(hreq->tcp_state.tot_chunk_size +
+ hreq->param.reqdata.size <=
+ hreq->param.reqdata.total_size);
+ }
+ http_req_start_sending(hreq);
+ } else {
+ /* No request body, proceed to reading the server's response. */
+ hreq->state = REQUEST_SENT;
+ http_req_start_reading(hreq);
+ }
+ }
+ return PJ_TRUE;
+}
+
+static pj_bool_t http_on_data_read(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
+{
+ pj_http_req *hreq = (pj_http_req*) pj_activesock_get_user_data(asock);
+
+ TRACE_((THIS_FILE, "\nData received: %d bytes", size));
+
+ if (hreq->state == ABORTING || hreq->state == IDLE)
+ return PJ_FALSE;
+
+ if (hreq->state == READING_RESPONSE) {
+ pj_status_t st;
+ pj_size_t rem;
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ hreq->error = status;
+ pj_http_req_cancel(hreq, PJ_TRUE);
+ return PJ_FALSE;
+ }
+
+ /* Parse the response. */
+ st = http_response_parse(hreq->pool, &hreq->response,
+ data, size, &rem);
+ if (st == PJLIB_UTIL_EHTTPINCHDR) {
+ /* If we already use up all our buffer and still
+ * hasn't received the whole header, return error
+ */
+ if (size == BUF_SIZE) {
+ hreq->error = PJ_ETOOBIG; // response header size is too big
+ pj_http_req_cancel(hreq, PJ_TRUE);
+ return PJ_FALSE;
+ }
+ /* Keep the data if we do not get the whole response header */
+ *remainder = size;
+ } else {
+ hreq->state = READING_DATA;
+ if (st != PJ_SUCCESS) {
+ /* Server replied with an invalid (or unknown) response
+ * format. We'll just pass the whole (unparsed) response
+ * to the user.
+ */
+ hreq->response.data = data;
+ hreq->response.size = size - rem;
+ }
+
+ /* If code is 401 or 407, find and parse WWW-Authenticate or
+ * Proxy-Authenticate header
+ */
+ if (hreq->response.status_code == 401 ||
+ hreq->response.status_code == 407)
+ {
+ const pj_str_t STR_WWW_AUTH = { "WWW-Authenticate", 16 };
+ const pj_str_t STR_PROXY_AUTH = { "Proxy-Authenticate", 18 };
+ pj_http_resp *response = &hreq->response;
+ pj_http_headers *hdrs = &response->headers;
+ unsigned i;
+
+ status = PJ_ENOTFOUND;
+ for (i = 0; i < hdrs->count; i++) {
+ if (!pj_stricmp(&hdrs->header[i].name, &STR_WWW_AUTH) ||
+ !pj_stricmp(&hdrs->header[i].name, &STR_PROXY_AUTH))
+ {
+ status = parse_auth_chal(hreq->pool,
+ &hdrs->header[i].value,
+ &response->auth_chal);
+ break;
+ }
+ }
+
+ /* Check if we should perform authentication */
+ if (status == PJ_SUCCESS &&
+ hreq->auth_state == AUTH_NONE &&
+ hreq->response.auth_chal.scheme.slen &&
+ hreq->param.auth_cred.username.slen &&
+ (hreq->param.auth_cred.scheme.slen == 0 ||
+ !pj_stricmp(&hreq->response.auth_chal.scheme,
+ &hreq->param.auth_cred.scheme)) &&
+ (hreq->param.auth_cred.realm.slen == 0 ||
+ !pj_stricmp(&hreq->response.auth_chal.realm,
+ &hreq->param.auth_cred.realm))
+ )
+ {
+ /* Yes, authentication is required and we have been
+ * configured with credential.
+ */
+ restart_req_with_auth(hreq);
+ if (hreq->auth_state == AUTH_RETRYING) {
+ /* We'll be resending the request with auth. This
+ * connection has been closed.
+ */
+ return PJ_FALSE;
+ }
+ }
+ }
+
+ /* We already received the response header, call the
+ * appropriate callback.
+ */
+ if (hreq->cb.on_response)
+ (*hreq->cb.on_response)(hreq, &hreq->response);
+ hreq->response.data = NULL;
+ hreq->response.size = 0;
+
+ if (rem > 0 || hreq->response.content_length == 0)
+ return http_on_data_read(asock, (rem == 0 ? NULL:
+ (char *)data + size - rem),
+ rem, PJ_SUCCESS, NULL);
+ }
+
+ return PJ_TRUE;
+ }
+
+ if (hreq->state != READING_DATA)
+ return PJ_FALSE;
+ if (hreq->cb.on_data_read) {
+ /* If application wishes to receive the data once available, call
+ * its callback.
+ */
+ if (size > 0)
+ (*hreq->cb.on_data_read)(hreq, data, size);
+ } else {
+ if (hreq->response.size == 0) {
+ /* If we know the content length, allocate the data based
+ * on that, otherwise we'll use initial buffer size and grow
+ * it later if necessary.
+ */
+ hreq->response.size = (hreq->response.content_length == -1 ?
+ INITIAL_DATA_BUF_SIZE :
+ hreq->response.content_length);
+ hreq->response.data = pj_pool_alloc(hreq->pool,
+ hreq->response.size);
+ }
+
+ /* If the size of data received exceeds its current size,
+ * grow the buffer by a factor of 2.
+ */
+ if (hreq->tcp_state.current_read_size + size >
+ hreq->response.size)
+ {
+ void *olddata = hreq->response.data;
+
+ hreq->response.data = pj_pool_alloc(hreq->pool,
+ hreq->response.size << 1);
+ pj_memcpy(hreq->response.data, olddata, hreq->response.size);
+ hreq->response.size <<= 1;
+ }
+
+ /* Append the response data. */
+ pj_memcpy((char *)hreq->response.data +
+ hreq->tcp_state.current_read_size, data, size);
+ }
+ hreq->tcp_state.current_read_size += size;
+
+ /* If the total data received so far is equal to the content length
+ * or if it's already EOF.
+ */
+ if ((hreq->response.content_length >=0 &&
+ (pj_ssize_t)hreq->tcp_state.current_read_size >=
+ hreq->response.content_length) ||
+ (status == PJ_EEOF && hreq->response.content_length == -1))
+ {
+ /* Finish reading */
+ http_req_end_request(hreq);
+ hreq->response.size = hreq->tcp_state.current_read_size;
+
+ /* HTTP request is completed, call the callback. */
+ if (hreq->cb.on_complete) {
+ (*hreq->cb.on_complete)(hreq, PJ_SUCCESS, &hreq->response);
+ }
+
+ return PJ_FALSE;
+ }
+
+ /* Error status or premature EOF. */
+ if ((status != PJ_SUCCESS && status != PJ_EPENDING && status != PJ_EEOF)
+ || (status == PJ_EEOF && hreq->response.content_length > -1))
+ {
+ hreq->error = status;
+ pj_http_req_cancel(hreq, PJ_TRUE);
+ return PJ_FALSE;
+ }
+
+ return PJ_TRUE;
+}
+
+/* Callback to be called when query has timed out */
+static void on_timeout( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pj_http_req *hreq = (pj_http_req *) entry->user_data;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ /* Recheck that the request is still not completed, since there is a
+ * slight possibility of race condition (timer elapsed while at the
+ * same time response arrives).
+ */
+ if (hreq->state == READING_COMPLETE) {
+ /* Yeah, we finish on time */
+ return;
+ }
+
+ /* Invalidate id. */
+ hreq->timer_entry.id = 0;
+
+ /* Request timed out. */
+ hreq->error = PJ_ETIMEDOUT;
+ pj_http_req_cancel(hreq, PJ_TRUE);
+}
+
+/* Parse authentication challenge */
+static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input,
+ pj_http_auth_chal *chal)
+{
+ pj_scanner scanner;
+ const pj_str_t REALM_STR = { "realm", 5},
+ NONCE_STR = { "nonce", 5},
+ ALGORITHM_STR = { "algorithm", 9 },
+ STALE_STR = { "stale", 5},
+ QOP_STR = { "qop", 3},
+ OPAQUE_STR = { "opaque", 6};
+ pj_status_t status = PJ_SUCCESS;
+ PJ_USE_EXCEPTION ;
+
+ pj_scan_init(&scanner, input->ptr, input->slen, PJ_SCAN_AUTOSKIP_WS,
+ &on_syntax_error);
+ PJ_TRY {
+ /* Get auth scheme */
+ if (*scanner.curptr == '"') {
+ pj_scan_get_quote(&scanner, '"', '"', &chal->scheme);
+ chal->scheme.ptr++;
+ chal->scheme.slen -= 2;
+ } else {
+ pj_scan_get_until_chr(&scanner, " \t\r\n", &chal->scheme);
+ }
+
+ /* Loop parsing all parameters */
+ for (;;) {
+ const char *end_param = ", \t\r\n;";
+ pj_str_t name, value;
+
+ /* Get pair of parameter name and value */
+ value.ptr = NULL;
+ value.slen = 0;
+ pj_scan_get_until_chr(&scanner, "=, \t\r\n", &name);
+ if (*scanner.curptr == '=') {
+ pj_scan_get_char(&scanner);
+ if (!pj_scan_is_eof(&scanner)) {
+ if (*scanner.curptr == '"' || *scanner.curptr == '\'') {
+ int quote_char = *scanner.curptr;
+ pj_scan_get_quote(&scanner, quote_char, quote_char,
+ &value);
+ value.ptr++;
+ value.slen -= 2;
+ } else if (!strchr(end_param, *scanner.curptr)) {
+ pj_scan_get_until_chr(&scanner, end_param, &value);
+ }
+ }
+ value = pj_str_unescape(pool, &value);
+ }
+
+ if (!pj_stricmp(&name, &REALM_STR)) {
+ chal->realm = value;
+
+ } else if (!pj_stricmp(&name, &NONCE_STR)) {
+ chal->nonce = value;
+
+ } else if (!pj_stricmp(&name, &ALGORITHM_STR)) {
+ chal->algorithm = value;
+
+ } else if (!pj_stricmp(&name, &OPAQUE_STR)) {
+ chal->opaque = value;
+
+ } else if (!pj_stricmp(&name, &QOP_STR)) {
+ chal->qop = value;
+
+ } else if (!pj_stricmp(&name, &STALE_STR)) {
+ chal->stale = value.slen &&
+ (*value.ptr != '0') &&
+ (*value.ptr != 'f') &&
+ (*value.ptr != 'F');
+
+ }
+
+ /* Eat comma */
+ if (!pj_scan_is_eof(&scanner) && *scanner.curptr == ',')
+ pj_scan_get_char(&scanner);
+ else
+ break;
+ }
+
+ }
+ PJ_CATCH_ANY {
+ status = PJ_GET_EXCEPTION();
+ pj_bzero(chal, sizeof(*chal));
+ TRACE_((THIS_FILE, "Error: parsing of auth header failed"));
+ }
+ PJ_END;
+ pj_scan_fini(&scanner);
+ return status;
+}
+
+/* The same as #pj_http_headers_add_elmt() with char * as
+ * its parameters.
+ */
+PJ_DEF(pj_status_t) pj_http_headers_add_elmt2(pj_http_headers *headers,
+ char *name, char *val)
+{
+ pj_str_t f, v;
+ pj_cstr(&f, name);
+ pj_cstr(&v, val);
+ return pj_http_headers_add_elmt(headers, &f, &v);
+}
+
+PJ_DEF(pj_status_t) pj_http_headers_add_elmt(pj_http_headers *headers,
+ pj_str_t *name,
+ pj_str_t *val)
+{
+ PJ_ASSERT_RETURN(headers && name && val, PJ_FALSE);
+ if (headers->count >= PJ_HTTP_HEADER_SIZE)
+ return PJ_ETOOMANY;
+ pj_strassign(&headers->header[headers->count].name, name);
+ pj_strassign(&headers->header[headers->count++].value, val);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t http_response_parse(pj_pool_t *pool,
+ pj_http_resp *response,
+ void *data, pj_size_t size,
+ pj_size_t *remainder)
+{
+ pj_size_t i;
+ char *cptr;
+ char *end_status, *newdata;
+ pj_scanner scanner;
+ pj_str_t s;
+ const pj_str_t STR_CONTENT_LENGTH = { CONTENT_LENGTH, 14 };
+ pj_status_t status;
+
+ PJ_USE_EXCEPTION;
+
+ PJ_ASSERT_RETURN(response, PJ_EINVAL);
+ if (size < 2)
+ return PJLIB_UTIL_EHTTPINCHDR;
+ /* Detect whether we already receive the response's status-line
+ * and its headers. We're looking for a pair of CRLFs. A pair of
+ * LFs is also supported although it is not RFC standard.
+ */
+ cptr = (char *)data;
+ for (i = 1, cptr++; i < size; i++, cptr++) {
+ if (*cptr == '\n') {
+ if (*(cptr - 1) == '\n')
+ break;
+ if (*(cptr - 1) == '\r') {
+ if (i >= 3 && *(cptr - 2) == '\n' && *(cptr - 3) == '\r')
+ break;
+ }
+ }
+ }
+ if (i == size)
+ return PJLIB_UTIL_EHTTPINCHDR;
+ *remainder = size - 1 - i;
+
+ pj_bzero(response, sizeof(response));
+ response->content_length = -1;
+
+ newdata = (char*) pj_pool_alloc(pool, i);
+ pj_memcpy(newdata, data, i);
+
+ /* Parse the status-line. */
+ pj_scan_init(&scanner, newdata, i, 0, &on_syntax_error);
+ PJ_TRY {
+ pj_scan_get_until_ch(&scanner, ' ', &response->version);
+ pj_scan_advance_n(&scanner, 1, PJ_FALSE);
+ pj_scan_get_until_ch(&scanner, ' ', &s);
+ response->status_code = (pj_uint16_t)pj_strtoul(&s);
+ pj_scan_advance_n(&scanner, 1, PJ_FALSE);
+ pj_scan_get_until_ch(&scanner, '\n', &response->reason);
+ if (response->reason.ptr[response->reason.slen-1] == '\r')
+ response->reason.slen--;
+ }
+ PJ_CATCH_ANY {
+ pj_scan_fini(&scanner);
+ return PJ_GET_EXCEPTION();
+ }
+ PJ_END;
+
+ end_status = scanner.curptr;
+ pj_scan_fini(&scanner);
+
+ /* Parse the response headers. */
+ size = i - 2 - (end_status - newdata);
+ if (size > 0) {
+ status = http_headers_parse(end_status + 1, size,
+ &response->headers);
+ } else {
+ status = PJ_SUCCESS;
+ }
+
+ /* Find content-length header field. */
+ for (i = 0; i < response->headers.count; i++) {
+ if (!pj_stricmp(&response->headers.header[i].name,
+ &STR_CONTENT_LENGTH))
+ {
+ response->content_length =
+ pj_strtoul(&response->headers.header[i].value);
+ /* If content length is zero, make sure that it is because the
+ * header value is really zero and not due to parsing error.
+ */
+ if (response->content_length == 0) {
+ if (pj_strcmp2(&response->headers.header[i].value, "0")) {
+ response->content_length = -1;
+ }
+ }
+ break;
+ }
+ }
+
+ return status;
+}
+
+static pj_status_t http_headers_parse(char *hdata, pj_size_t size,
+ pj_http_headers *headers)
+{
+ pj_scanner scanner;
+ pj_str_t s, s2;
+ pj_status_t status;
+ PJ_USE_EXCEPTION;
+
+ PJ_ASSERT_RETURN(headers, PJ_EINVAL);
+
+ pj_scan_init(&scanner, hdata, size, 0, &on_syntax_error);
+
+ /* Parse each line of header field consisting of header field name and
+ * value, separated by ":" and any number of white spaces.
+ */
+ PJ_TRY {
+ do {
+ pj_scan_get_until_chr(&scanner, ":\n", &s);
+ if (*scanner.curptr == ':') {
+ pj_scan_advance_n(&scanner, 1, PJ_TRUE);
+ pj_scan_get_until_ch(&scanner, '\n', &s2);
+ if (s2.ptr[s2.slen-1] == '\r')
+ s2.slen--;
+ status = pj_http_headers_add_elmt(headers, &s, &s2);
+ if (status != PJ_SUCCESS)
+ PJ_THROW(status);
+ }
+ pj_scan_advance_n(&scanner, 1, PJ_TRUE);
+ /* Finish parsing */
+ if (pj_scan_is_eof(&scanner))
+ break;
+ } while (1);
+ }
+ PJ_CATCH_ANY {
+ pj_scan_fini(&scanner);
+ return PJ_GET_EXCEPTION();
+ }
+ PJ_END;
+
+ pj_scan_fini(&scanner);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(void) pj_http_req_param_default(pj_http_req_param *param)
+{
+ pj_assert(param);
+ pj_bzero(param, sizeof(*param));
+ param->addr_family = pj_AF_INET();
+ pj_strset2(&param->method, (char*)http_method_names[HTTP_GET]);
+ pj_strset2(&param->version, (char*)HTTP_1_0);
+ param->timeout.msec = PJ_HTTP_DEFAULT_TIMEOUT;
+ pj_time_val_normalize(&param->timeout);
+ param->max_retries = 3;
+}
+
+/* Get the location of '@' character to indicate the end of
+ * user:passwd part of an URI. If user:passwd part is not
+ * present, NULL will be returned.
+ */
+static char *get_url_at_pos(const char *str, long len)
+{
+ const char *end = str + len;
+ const char *p = str;
+
+ /* skip scheme: */
+ while (p!=end && *p!='/') ++p;
+ if (p!=end && *p=='/') ++p;
+ if (p!=end && *p=='/') ++p;
+ if (p==end) return NULL;
+
+ for (; p!=end; ++p) {
+ switch (*p) {
+ case '/':
+ return NULL;
+ case '@':
+ return (char*)p;
+ }
+ }
+
+ return NULL;
+}
+
+
+PJ_DEF(pj_status_t) pj_http_req_parse_url(const pj_str_t *url,
+ pj_http_url *hurl)
+{
+ pj_scanner scanner;
+ int len = url->slen;
+ PJ_USE_EXCEPTION;
+
+ if (!len) return -1;
+
+ pj_bzero(hurl, sizeof(*hurl));
+ pj_scan_init(&scanner, url->ptr, url->slen, 0, &on_syntax_error);
+
+ PJ_TRY {
+ pj_str_t s;
+
+ /* Exhaust any whitespaces. */
+ pj_scan_skip_whitespace(&scanner);
+
+ /* Parse the protocol */
+ pj_scan_get_until_ch(&scanner, ':', &s);
+ if (!pj_stricmp2(&s, http_protocol_names[PROTOCOL_HTTP])) {
+ pj_strset2(&hurl->protocol,
+ (char*)http_protocol_names[PROTOCOL_HTTP]);
+ } else if (!pj_stricmp2(&s, http_protocol_names[PROTOCOL_HTTPS])) {
+ pj_strset2(&hurl->protocol,
+ (char*)http_protocol_names[PROTOCOL_HTTPS]);
+ } else {
+ PJ_THROW(PJ_ENOTSUP); // unsupported protocol
+ }
+
+ if (pj_scan_strcmp(&scanner, "://", 3)) {
+ PJ_THROW(PJLIB_UTIL_EHTTPINURL); // no "://" after protocol name
+ }
+ pj_scan_advance_n(&scanner, 3, PJ_FALSE);
+
+ if (get_url_at_pos(url->ptr, url->slen)) {
+ /* Parse username and password */
+ pj_scan_get_until_chr(&scanner, ":@", &hurl->username);
+ if (*scanner.curptr == ':') {
+ pj_scan_get_char(&scanner);
+ pj_scan_get_until_chr(&scanner, "@", &hurl->passwd);
+ } else {
+ hurl->passwd.slen = 0;
+ }
+ pj_scan_get_char(&scanner);
+ }
+
+ /* Parse the host and port number (if any) */
+ pj_scan_get_until_chr(&scanner, ":/", &s);
+ pj_strassign(&hurl->host, &s);
+ if (hurl->host.slen==0)
+ PJ_THROW(PJ_EINVAL);
+ if (pj_scan_is_eof(&scanner) || *scanner.curptr == '/') {
+ /* No port number specified */
+ /* Assume default http/https port number */
+ hurl->port = get_http_default_port(&hurl->protocol);
+ pj_assert(hurl->port > 0);
+ } else {
+ pj_scan_advance_n(&scanner, 1, PJ_FALSE);
+ pj_scan_get_until_ch(&scanner, '/', &s);
+ /* Parse the port number */
+ hurl->port = (pj_uint16_t)pj_strtoul(&s);
+ if (!hurl->port)
+ PJ_THROW(PJLIB_UTIL_EHTTPINPORT); // invalid port number
+ }
+
+ if (!pj_scan_is_eof(&scanner)) {
+ hurl->path.ptr = scanner.curptr;
+ hurl->path.slen = scanner.end - scanner.curptr;
+ } else {
+ /* no path, append '/' */
+ pj_cstr(&hurl->path, "/");
+ }
+ }
+ PJ_CATCH_ANY {
+ pj_scan_fini(&scanner);
+ return PJ_GET_EXCEPTION();
+ }
+ PJ_END;
+
+ pj_scan_fini(&scanner);
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(void) pj_http_req_set_timeout(pj_http_req *http_req,
+ const pj_time_val* timeout)
+{
+ pj_memcpy(&http_req->param.timeout, timeout, sizeof(*timeout));
+}
+
+PJ_DEF(pj_status_t) pj_http_req_create(pj_pool_t *pool,
+ const pj_str_t *url,
+ pj_timer_heap_t *timer,
+ pj_ioqueue_t *ioqueue,
+ const pj_http_req_param *param,
+ const pj_http_req_callback *hcb,
+ pj_http_req **http_req)
+{
+ pj_pool_t *own_pool;
+ pj_http_req *hreq;
+ char *at_pos;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && url && timer && ioqueue &&
+ hcb && http_req, PJ_EINVAL);
+
+ *http_req = NULL;
+ own_pool = pj_pool_create(pool->factory, NULL, INITIAL_POOL_SIZE,
+ POOL_INCREMENT_SIZE, NULL);
+ hreq = PJ_POOL_ZALLOC_T(own_pool, struct pj_http_req);
+ if (!hreq)
+ return PJ_ENOMEM;
+
+ /* Initialization */
+ hreq->pool = own_pool;
+ hreq->ioqueue = ioqueue;
+ hreq->timer = timer;
+ hreq->asock = NULL;
+ pj_memcpy(&hreq->cb, hcb, sizeof(*hcb));
+ hreq->state = IDLE;
+ hreq->resolved = PJ_FALSE;
+ hreq->buffer.ptr = NULL;
+ pj_timer_entry_init(&hreq->timer_entry, 0, hreq, &on_timeout);
+
+ /* Initialize parameter */
+ if (param) {
+ pj_memcpy(&hreq->param, param, sizeof(*param));
+ /* TODO: validate the param here
+ * Should we validate the method as well? If yes, based on all HTTP
+ * methods or based on supported methods only? For the later, one
+ * drawback would be that you can't use this if the method is not
+ * officially supported
+ */
+ PJ_ASSERT_RETURN(hreq->param.addr_family==PJ_AF_UNSPEC ||
+ hreq->param.addr_family==PJ_AF_INET ||
+ hreq->param.addr_family==PJ_AF_INET6, PJ_EAFNOTSUP);
+ PJ_ASSERT_RETURN(!pj_strcmp2(&hreq->param.version, HTTP_1_0) ||
+ !pj_strcmp2(&hreq->param.version, HTTP_1_1),
+ PJ_ENOTSUP);
+ pj_time_val_normalize(&hreq->param.timeout);
+ } else {
+ pj_http_req_param_default(&hreq->param);
+ }
+
+ /* Parse the URL */
+ if (!pj_strdup_with_null(hreq->pool, &hreq->url, url)) {
+ pj_pool_release(hreq->pool);
+ return PJ_ENOMEM;
+ }
+ status = pj_http_req_parse_url(&hreq->url, &hreq->hurl);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(hreq->pool);
+ return status; // Invalid URL supplied
+ }
+
+ /* If URL contains username/password, move them to credential and
+ * remove them from the URL.
+ */
+ if ((at_pos=get_url_at_pos(hreq->url.ptr, hreq->url.slen)) != NULL) {
+ pj_str_t tmp;
+ char *user_pos = pj_strchr(&hreq->url, '/');
+ int removed_len;
+
+ /* Save credential first, unescape the string */
+ tmp = pj_str_unescape(hreq->pool, &hreq->hurl.username);;
+ pj_strdup(hreq->pool, &hreq->param.auth_cred.username, &tmp);
+
+ tmp = pj_str_unescape(hreq->pool, &hreq->hurl.passwd);
+ pj_strdup(hreq->pool, &hreq->param.auth_cred.data, &tmp);
+
+ hreq->hurl.username.ptr = hreq->hurl.passwd.ptr = NULL;
+ hreq->hurl.username.slen = hreq->hurl.passwd.slen = 0;
+
+ /* Remove "username:password@" from the URL */
+ pj_assert(user_pos != 0 && user_pos < at_pos);
+ user_pos += 2;
+ removed_len = at_pos + 1 - user_pos;
+ pj_memmove(user_pos, at_pos+1, hreq->url.ptr+hreq->url.slen-at_pos-1);
+ hreq->url.slen -= removed_len;
+
+ /* Need to adjust hostname and path pointers due to memmove*/
+ if (hreq->hurl.host.ptr > user_pos &&
+ hreq->hurl.host.ptr < user_pos + hreq->url.slen)
+ {
+ hreq->hurl.host.ptr -= removed_len;
+ }
+ /* path may come from a string constant, don't shift it if so */
+ if (hreq->hurl.path.ptr > user_pos &&
+ hreq->hurl.path.ptr < user_pos + hreq->url.slen)
+ {
+ hreq->hurl.path.ptr -= removed_len;
+ }
+ }
+
+ *http_req = hreq;
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_bool_t) pj_http_req_is_running(const pj_http_req *http_req)
+{
+ PJ_ASSERT_RETURN(http_req, PJ_FALSE);
+ return (http_req->state != IDLE);
+}
+
+PJ_DEF(void*) pj_http_req_get_user_data(pj_http_req *http_req)
+{
+ PJ_ASSERT_RETURN(http_req, NULL);
+ return http_req->param.user_data;
+}
+
+static pj_status_t start_http_req(pj_http_req *http_req,
+ pj_bool_t notify_on_fail)
+{
+ pj_sock_t sock = PJ_INVALID_SOCKET;
+ pj_status_t status;
+ pj_activesock_cb asock_cb;
+ int retry = 0;
+
+ PJ_ASSERT_RETURN(http_req, PJ_EINVAL);
+ /* Http request is not idle, a request was initiated before and
+ * is still in progress
+ */
+ PJ_ASSERT_RETURN(http_req->state == IDLE, PJ_EBUSY);
+
+ /* Reset few things to make sure restarting works */
+ http_req->error = 0;
+ http_req->response.headers.count = 0;
+ pj_bzero(&http_req->tcp_state, sizeof(http_req->tcp_state));
+
+ if (!http_req->resolved) {
+ /* Resolve the Internet address of the host */
+ status = pj_sockaddr_init(http_req->param.addr_family,
+ &http_req->addr, &http_req->hurl.host,
+ http_req->hurl.port);
+ if (status != PJ_SUCCESS ||
+ !pj_sockaddr_has_addr(&http_req->addr) ||
+ (http_req->param.addr_family==pj_AF_INET() &&
+ http_req->addr.ipv4.sin_addr.s_addr==PJ_INADDR_NONE))
+ {
+ goto on_return;
+ }
+ http_req->resolved = PJ_TRUE;
+ }
+
+ status = pj_sock_socket(http_req->param.addr_family, pj_SOCK_STREAM(),
+ 0, &sock);
+ if (status != PJ_SUCCESS)
+ goto on_return; // error creating socket
+
+ pj_bzero(&asock_cb, sizeof(asock_cb));
+ asock_cb.on_data_read = &http_on_data_read;
+ asock_cb.on_data_sent = &http_on_data_sent;
+ asock_cb.on_connect_complete = &http_on_connect;
+
+ do
+ {
+ pj_sockaddr_in bound_addr;
+ pj_uint16_t port = 0;
+
+ /* If we are using port restriction.
+ * Get a random port within the range
+ */
+ if (http_req->param.source_port_range_start != 0) {
+ port = (pj_uint16_t)
+ (http_req->param.source_port_range_start +
+ (pj_rand() % http_req->param.source_port_range_size));
+ }
+
+ pj_sockaddr_in_init(&bound_addr, NULL, port);
+ status = pj_sock_bind(sock, &bound_addr, sizeof(bound_addr));
+
+ } while (status != PJ_SUCCESS && (retry++ < http_req->param.max_retries));
+
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Unable to bind to the requested port"));
+ pj_sock_close(sock);
+ goto on_return;
+ }
+
+ // TODO: should we set whole data to 0 by default?
+ // or add it in the param?
+ status = pj_activesock_create(http_req->pool, sock, pj_SOCK_STREAM(),
+ NULL, http_req->ioqueue,
+ &asock_cb, http_req, &http_req->asock);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ goto on_return; // error creating activesock
+ }
+
+ /* Schedule timeout timer for the request */
+ pj_assert(http_req->timer_entry.id == 0);
+ http_req->timer_entry.id = 1;
+ status = pj_timer_heap_schedule(http_req->timer, &http_req->timer_entry,
+ &http_req->param.timeout);
+ if (status != PJ_SUCCESS) {
+ http_req->timer_entry.id = 0;
+ goto on_return; // error scheduling timer
+ }
+
+ /* Connect to host */
+ http_req->state = CONNECTING;
+ status = pj_activesock_start_connect(http_req->asock, http_req->pool,
+ (pj_sock_t *)&(http_req->addr),
+ pj_sockaddr_get_len(&http_req->addr));
+ if (status == PJ_SUCCESS) {
+ http_req->state = SENDING_REQUEST;
+ status = http_req_start_sending(http_req);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ } else if (status != PJ_EPENDING) {
+ goto on_return; // error connecting
+ }
+
+ return PJ_SUCCESS;
+
+on_return:
+ http_req->error = status;
+ if (notify_on_fail)
+ pj_http_req_cancel(http_req, PJ_TRUE);
+ else
+ http_req_end_request(http_req);
+
+ return status;
+}
+
+/* Starts an asynchronous HTTP request to the URL specified. */
+PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req)
+{
+ return start_http_req(http_req, PJ_FALSE);
+}
+
+/* Respond to basic authentication challenge */
+static pj_status_t auth_respond_basic(pj_http_req *hreq)
+{
+ /* Basic authentication:
+ * credentials = "Basic" basic-credentials
+ * basic-credentials = base64-user-pass
+ * base64-user-pass = <base64 [4] encoding of user-pass>
+ * user-pass = userid ":" password
+ *
+ * Sample:
+ * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+ */
+ pj_str_t user_pass;
+ pj_http_header_elmt *phdr;
+ int len;
+
+ /* Use send buffer to store userid ":" password */
+ user_pass.ptr = hreq->buffer.ptr;
+ pj_strcpy(&user_pass, &hreq->param.auth_cred.username);
+ pj_strcat2(&user_pass, ":");
+ pj_strcat(&user_pass, &hreq->param.auth_cred.data);
+
+ /* Create Authorization header */
+ phdr = &hreq->param.headers.header[hreq->param.headers.count++];
+ pj_bzero(phdr, sizeof(*phdr));
+ if (hreq->response.status_code == 401)
+ phdr->name = pj_str("Authorization");
+ else
+ phdr->name = pj_str("Proxy-Authorization");
+
+ len = PJ_BASE256_TO_BASE64_LEN(user_pass.slen) + 10;
+ phdr->value.ptr = (char*)pj_pool_alloc(hreq->pool, len);
+ phdr->value.slen = 0;
+
+ pj_strcpy2(&phdr->value, "Basic ");
+ len -= phdr->value.slen;
+ pj_base64_encode((pj_uint8_t*)user_pass.ptr, (int)user_pass.slen,
+ phdr->value.ptr + phdr->value.slen, &len);
+ phdr->value.slen += len;
+
+ return PJ_SUCCESS;
+}
+
+/** Length of digest string. */
+#define MD5_STRLEN 32
+/* A macro just to get rid of type mismatch between char and unsigned char */
+#define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, len)
+
+/* Transform digest to string.
+ * output must be at least PJSIP_MD5STRLEN+1 bytes.
+ *
+ * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
+ */
+static void digest2str(const unsigned char digest[], char *output)
+{
+ int i;
+ for (i = 0; i<16; ++i) {
+ pj_val_to_hex_digit(digest[i], output);
+ output += 2;
+ }
+}
+
+static void auth_create_digest_response(pj_str_t *result,
+ const pj_http_auth_cred *cred,
+ const pj_str_t *nonce,
+ const pj_str_t *nc,
+ const pj_str_t *cnonce,
+ const pj_str_t *qop,
+ const pj_str_t *uri,
+ const pj_str_t *realm,
+ const pj_str_t *method)
+{
+ char ha1[MD5_STRLEN];
+ char ha2[MD5_STRLEN];
+ unsigned char digest[16];
+ pj_md5_context pms;
+
+ pj_assert(result->slen >= MD5_STRLEN);
+
+ TRACE_((THIS_FILE, "Begin creating digest"));
+
+ if (cred->data_type == 0) {
+ /***
+ *** ha1 = MD5(username ":" realm ":" password)
+ ***/
+ pj_md5_init(&pms);
+ MD5_APPEND( &pms, cred->username.ptr, cred->username.slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, realm->ptr, realm->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, cred->data.ptr, cred->data.slen);
+ pj_md5_final(&pms, digest);
+
+ digest2str(digest, ha1);
+
+ } else if (cred->data_type == 1) {
+ pj_assert(cred->data.slen == 32);
+ pj_memcpy( ha1, cred->data.ptr, cred->data.slen );
+ } else {
+ pj_assert(!"Invalid data_type");
+ }
+
+ TRACE_((THIS_FILE, " ha1=%.32s", ha1));
+
+ /***
+ *** ha2 = MD5(method ":" req_uri)
+ ***/
+ pj_md5_init(&pms);
+ MD5_APPEND( &pms, method->ptr, method->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, uri->ptr, uri->slen);
+ pj_md5_final(&pms, digest);
+ digest2str(digest, ha2);
+
+ TRACE_((THIS_FILE, " ha2=%.32s", ha2));
+
+ /***
+ *** When qop is not used:
+ *** response = MD5(ha1 ":" nonce ":" ha2)
+ ***
+ *** When qop=auth is used:
+ *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2)
+ ***/
+ pj_md5_init(&pms);
+ MD5_APPEND( &pms, ha1, MD5_STRLEN);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, nonce->ptr, nonce->slen);
+ if (qop && qop->slen != 0) {
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, nc->ptr, nc->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, cnonce->ptr, cnonce->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, qop->ptr, qop->slen);
+ }
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, ha2, MD5_STRLEN);
+
+ /* This is the final response digest. */
+ pj_md5_final(&pms, digest);
+
+ /* Convert digest to string and store in chal->response. */
+ result->slen = MD5_STRLEN;
+ digest2str(digest, result->ptr);
+
+ TRACE_((THIS_FILE, " digest=%.32s", result->ptr));
+ TRACE_((THIS_FILE, "Digest created"));
+}
+
+/* Find out if qop offer contains "auth" token */
+static pj_bool_t auth_has_qop( pj_pool_t *pool, const pj_str_t *qop_offer)
+{
+ pj_str_t qop;
+ char *p;
+
+ pj_strdup_with_null( pool, &qop, qop_offer);
+ p = qop.ptr;
+ while (*p) {
+ *p = (char)pj_tolower(*p);
+ ++p;
+ }
+
+ p = qop.ptr;
+ while (*p) {
+ if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') {
+ int e = *(p+4);
+ if (e=='"' || e==',' || e==0)
+ return PJ_TRUE;
+ else
+ p += 4;
+ } else {
+ ++p;
+ }
+ }
+
+ return PJ_FALSE;
+}
+
+#define STR_PREC(s) (int)(s).slen, (s).ptr
+
+/* Respond to digest authentication */
+static pj_status_t auth_respond_digest(pj_http_req *hreq)
+{
+ const pj_http_auth_chal *chal = &hreq->response.auth_chal;
+ const pj_http_auth_cred *cred = &hreq->param.auth_cred;
+ pj_http_header_elmt *phdr;
+ char digest_response_buf[MD5_STRLEN];
+ int len;
+ pj_str_t digest_response;
+
+ /* Check algorithm is supported. We only support MD5 */
+ if (chal->algorithm.slen!=0 &&
+ pj_stricmp2(&chal->algorithm, "MD5"))
+ {
+ TRACE_((THIS_FILE, "Error: Unsupported digest algorithm \"%.*s\"",
+ chal->algorithm.slen, chal->algorithm.ptr));
+ return PJ_ENOTSUP;
+ }
+
+ /* Add Authorization header */
+ phdr = &hreq->param.headers.header[hreq->param.headers.count++];
+ pj_bzero(phdr, sizeof(*phdr));
+ if (hreq->response.status_code == 401)
+ phdr->name = pj_str("Authorization");
+ else
+ phdr->name = pj_str("Proxy-Authorization");
+
+ /* Allocate space for the header */
+ len = 8 + /* Digest */
+ 16 + hreq->param.auth_cred.username.slen + /* username= */
+ 12 + chal->realm.slen + /* realm= */
+ 12 + chal->nonce.slen + /* nonce= */
+ 8 + hreq->hurl.path.slen + /* uri= */
+ 16 + /* algorithm=MD5 */
+ 16 + MD5_STRLEN + /* response= */
+ 12 + /* qop=auth */
+ 8 + /* nc=.. */
+ 30 + /* cnonce= */
+ 12 + chal->opaque.slen + /* opaque=".." */
+ 0;
+ phdr->value.ptr = (char*)pj_pool_alloc(hreq->pool, len);
+
+ /* Configure buffer to temporarily store the digest */
+ digest_response.ptr = digest_response_buf;
+ digest_response.slen = MD5_STRLEN;
+
+ if (chal->qop.slen == 0) {
+ const pj_str_t STR_MD5 = { "MD5", 3 };
+
+ /* Server doesn't require quality of protection. */
+ auth_create_digest_response(&digest_response, cred,
+ &chal->nonce, NULL, NULL, NULL,
+ &hreq->hurl.path, &chal->realm,
+ &hreq->param.method);
+
+ len = pj_ansi_snprintf(
+ phdr->value.ptr, len,
+ "Digest username=\"%.*s\", "
+ "realm=\"%.*s\", "
+ "nonce=\"%.*s\", "
+ "uri=\"%.*s\", "
+ "algorithm=%.*s, "
+ "response=\"%.*s\"",
+ STR_PREC(cred->username),
+ STR_PREC(chal->realm),
+ STR_PREC(chal->nonce),
+ STR_PREC(hreq->hurl.path),
+ STR_PREC(STR_MD5),
+ STR_PREC(digest_response));
+ if (len < 0)
+ return PJ_ETOOSMALL;
+ phdr->value.slen = len;
+
+ } else if (auth_has_qop(hreq->pool, &chal->qop)) {
+ /* Server requires quality of protection.
+ * We respond with selecting "qop=auth" protection.
+ */
+ const pj_str_t STR_MD5 = { "MD5", 3 };
+ const pj_str_t qop = pj_str("auth");
+ const pj_str_t nc = pj_str("00000001");
+ const pj_str_t cnonce = pj_str("b39971");
+
+ auth_create_digest_response(&digest_response, cred,
+ &chal->nonce, &nc, &cnonce, &qop,
+ &hreq->hurl.path, &chal->realm,
+ &hreq->param.method);
+ len = pj_ansi_snprintf(
+ phdr->value.ptr, len,
+ "Digest username=\"%.*s\", "
+ "realm=\"%.*s\", "
+ "nonce=\"%.*s\", "
+ "uri=\"%.*s\", "
+ "algorithm=%.*s, "
+ "response=\"%.*s\", "
+ "qop=%.*s, "
+ "nc=%.*s, "
+ "cnonce=\"%.*s\"",
+ STR_PREC(cred->username),
+ STR_PREC(chal->realm),
+ STR_PREC(chal->nonce),
+ STR_PREC(hreq->hurl.path),
+ STR_PREC(STR_MD5),
+ STR_PREC(digest_response),
+ STR_PREC(qop),
+ STR_PREC(nc),
+ STR_PREC(cnonce));
+ if (len < 0)
+ return PJ_ETOOSMALL;
+ phdr->value.slen = len;
+
+ if (chal->opaque.slen) {
+ pj_strcat2(&phdr->value, ", opaque=\"");
+ pj_strcat(&phdr->value, &chal->opaque);
+ pj_strcat2(&phdr->value, "\"");
+ }
+
+ } else {
+ /* Server requires quality protection that we don't support. */
+ TRACE_((THIS_FILE, "Error: Unsupported qop offer %.*s",
+ chal->qop.slen, chal->qop.ptr));
+ return PJ_ENOTSUP;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static void restart_req_with_auth(pj_http_req *hreq)
+{
+ pj_http_auth_chal *chal = &hreq->response.auth_chal;
+ pj_http_auth_cred *cred = &hreq->param.auth_cred;
+ pj_status_t status;
+
+ if (hreq->param.headers.count >= PJ_HTTP_HEADER_SIZE) {
+ TRACE_((THIS_FILE, "Error: no place to put Authorization header"));
+ hreq->auth_state = AUTH_DONE;
+ return;
+ }
+
+ /* If credential specifies specific scheme, make sure they match */
+ if (cred->scheme.slen && pj_stricmp(&chal->scheme, &cred->scheme)) {
+ status = PJ_ENOTSUP;
+ TRACE_((THIS_FILE, "Error: auth schemes mismatch"));
+ goto on_error;
+ }
+
+ /* If credential specifies specific realm, make sure they match */
+ if (cred->realm.slen && pj_stricmp(&chal->realm, &cred->realm)) {
+ status = PJ_ENOTSUP;
+ TRACE_((THIS_FILE, "Error: auth realms mismatch"));
+ goto on_error;
+ }
+
+ if (!pj_stricmp2(&chal->scheme, "basic")) {
+ status = auth_respond_basic(hreq);
+ } else if (!pj_stricmp2(&chal->scheme, "digest")) {
+ status = auth_respond_digest(hreq);
+ } else {
+ TRACE_((THIS_FILE, "Error: unsupported HTTP auth scheme"));
+ status = PJ_ENOTSUP;
+ }
+
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ http_req_end_request(hreq);
+
+ status = start_http_req(hreq, PJ_TRUE);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ hreq->auth_state = AUTH_RETRYING;
+ return;
+
+on_error:
+ hreq->auth_state = AUTH_DONE;
+}
+
+
+/* snprintf() to a pj_str_t struct with an option to append the
+ * result at the back of the string.
+ */
+void str_snprintf(pj_str_t *s, size_t size,
+ pj_bool_t append, const char *format, ...)
+{
+ va_list arg;
+ int retval;
+
+ va_start(arg, format);
+ if (!append)
+ s->slen = 0;
+ size -= s->slen;
+ retval = pj_ansi_vsnprintf(s->ptr + s->slen,
+ size, format, arg);
+ s->slen += ((retval < (int)size) ? retval : size - 1);
+ va_end(arg);
+}
+
+static pj_status_t http_req_start_sending(pj_http_req *hreq)
+{
+ pj_status_t status;
+ pj_str_t pkt;
+ pj_ssize_t len;
+ pj_size_t i;
+
+ PJ_ASSERT_RETURN(hreq->state == SENDING_REQUEST ||
+ hreq->state == SENDING_REQUEST_BODY, PJ_EBUG);
+
+ if (hreq->state == SENDING_REQUEST) {
+ /* Prepare the request data */
+ if (!hreq->buffer.ptr)
+ hreq->buffer.ptr = (char*)pj_pool_alloc(hreq->pool, BUF_SIZE);
+ pj_strassign(&pkt, &hreq->buffer);
+ pkt.slen = 0;
+ /* Start-line */
+ str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%.*s %.*s %s/%.*s\r\n",
+ STR_PREC(hreq->param.method),
+ STR_PREC(hreq->hurl.path),
+ get_protocol(&hreq->hurl.protocol),
+ STR_PREC(hreq->param.version));
+ /* Header field "Host" */
+ str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "Host: %.*s:%d\r\n",
+ STR_PREC(hreq->hurl.host), hreq->hurl.port);
+ if (!pj_strcmp2(&hreq->param.method, http_method_names[HTTP_PUT])) {
+ char buf[16];
+
+ /* Header field "Content-Length" */
+ pj_utoa(hreq->param.reqdata.total_size ?
+ hreq->param.reqdata.total_size:
+ hreq->param.reqdata.size, buf);
+ str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%s: %s\r\n",
+ CONTENT_LENGTH, buf);
+ }
+
+ /* Append user-specified headers */
+ for (i = 0; i < hreq->param.headers.count; i++) {
+ str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%.*s: %.*s\r\n",
+ STR_PREC(hreq->param.headers.header[i].name),
+ STR_PREC(hreq->param.headers.header[i].value));
+ }
+ if (pkt.slen >= BUF_SIZE - 1) {
+ status = PJLIB_UTIL_EHTTPINSBUF;
+ goto on_return;
+ }
+
+ pj_strcat2(&pkt, "\r\n");
+ pkt.ptr[pkt.slen] = 0;
+ TRACE_((THIS_FILE, "%s", pkt.ptr));
+ } else {
+ pkt.ptr = (char*)hreq->param.reqdata.data;
+ pkt.slen = hreq->param.reqdata.size;
+ }
+
+ /* Send the request */
+ len = pj_strlen(&pkt);
+ pj_ioqueue_op_key_init(&hreq->op_key, sizeof(hreq->op_key));
+ hreq->tcp_state.send_size = len;
+ hreq->tcp_state.current_send_size = 0;
+ status = pj_activesock_send(hreq->asock, &hreq->op_key,
+ pkt.ptr, &len, 0);
+
+ if (status == PJ_SUCCESS) {
+ http_on_data_sent(hreq->asock, &hreq->op_key, len);
+ } else if (status != PJ_EPENDING) {
+ goto on_return; // error sending data
+ }
+
+ return PJ_SUCCESS;
+
+on_return:
+ http_req_end_request(hreq);
+ return status;
+}
+
+static pj_status_t http_req_start_reading(pj_http_req *hreq)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(hreq->state == REQUEST_SENT, PJ_EBUG);
+
+ /* Receive the response */
+ hreq->state = READING_RESPONSE;
+ hreq->tcp_state.current_read_size = 0;
+ pj_assert(hreq->buffer.ptr);
+ status = pj_activesock_start_read2(hreq->asock, hreq->pool, BUF_SIZE,
+ (void**)&hreq->buffer.ptr, 0);
+ if (status != PJ_SUCCESS) {
+ /* Error reading */
+ http_req_end_request(hreq);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t http_req_end_request(pj_http_req *hreq)
+{
+ if (hreq->asock) {
+ pj_activesock_close(hreq->asock);
+ hreq->asock = NULL;
+ }
+
+ /* Cancel query timeout timer. */
+ if (hreq->timer_entry.id != 0) {
+ pj_timer_heap_cancel(hreq->timer, &hreq->timer_entry);
+ /* Invalidate id. */
+ hreq->timer_entry.id = 0;
+ }
+
+ hreq->state = IDLE;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_http_req_cancel(pj_http_req *http_req,
+ pj_bool_t notify)
+{
+ http_req->state = ABORTING;
+
+ http_req_end_request(http_req);
+
+ if (notify && http_req->cb.on_complete) {
+ (*http_req->cb.on_complete)(http_req, (!http_req->error?
+ PJ_ECANCELLED: http_req->error), NULL);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_http_req_destroy(pj_http_req *http_req)
+{
+ PJ_ASSERT_RETURN(http_req, PJ_EINVAL);
+
+ /* If there is any pending request, cancel it */
+ if (http_req->state != IDLE) {
+ pj_http_req_cancel(http_req, PJ_FALSE);
+ }
+
+ pj_pool_release(http_req->pool);
+
+ return PJ_SUCCESS;
+}
diff --git a/pjlib-util/src/pjlib-util/md5.c b/pjlib-util/src/pjlib-util/md5.c
new file mode 100644
index 0000000..41545ea
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/md5.c
@@ -0,0 +1,266 @@
+/* $Id: md5.c 1001 2007-02-25 15:38:32Z bennylp $ */
+/*
+ * This is the implementation of MD5 algorithm, based on the code
+ * written by Colin Plumb. This file is put in public domain.
+ */
+#include <pjlib-util/md5.h>
+#include <pj/string.h> /* pj_memcpy */
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN != 0
+#define HIGHFIRST 1
+#endif
+
+#ifndef HIGHFIRST
+#define byteReverse(buf, len) /* Nothing */
+#else
+void byteReverse(unsigned char *buf, unsigned longs);
+
+#ifndef ASM_MD5
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+void byteReverse(unsigned char *buf, unsigned longs)
+{
+ pj_uint32_t t;
+ do {
+ t = (pj_uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+ ((unsigned) buf[1] << 8 | buf[0]);
+ *(pj_uint32_t *) buf = t;
+ buf += 4;
+ } while (--longs);
+}
+#endif
+#endif
+
+static void MD5Transform(pj_uint32_t buf[4], pj_uint32_t const in[16]);
+
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+PJ_DEF(void) pj_md5_init(pj_md5_context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+PJ_DEF(void) pj_md5_update( pj_md5_context *ctx,
+ unsigned char const *buf, unsigned len)
+{
+ pj_uint32_t t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((pj_uint32_t) len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if (t) {
+ unsigned char *p = (unsigned char *) ctx->in + t;
+
+ t = 64 - t;
+ if (len < t) {
+ pj_memcpy(p, buf, len);
+ return;
+ }
+ pj_memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (pj_uint32_t *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ pj_memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (pj_uint32_t *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ pj_memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+PJ_DEF(void) pj_md5_final(pj_md5_context *ctx, unsigned char digest[16])
+{
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ pj_bzero(p, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (pj_uint32_t *) ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ pj_bzero(ctx->in, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ pj_bzero(p, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ((pj_uint32_t *) ctx->in)[14] = ctx->bits[0];
+ ((pj_uint32_t *) ctx->in)[15] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (pj_uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ pj_memcpy(digest, ctx->buf, 16);
+ pj_bzero(ctx, sizeof(ctx)); /* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void MD5Transform(pj_uint32_t buf[4], pj_uint32_t const in[16])
+{
+ register pj_uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+#endif
+
diff --git a/pjlib-util/src/pjlib-util/pcap.c b/pjlib-util/src/pjlib-util/pcap.c
new file mode 100644
index 0000000..65d7dcd
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/pcap.c
@@ -0,0 +1,392 @@
+/* $Id: pcap.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/pcap.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/file_io.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/sock.h>
+#include <pj/string.h>
+
+#if 0
+# define TRACE_(x) PJ_LOG(5,x)
+#else
+# define TRACE_(x)
+#endif
+
+
+#pragma pack(1)
+
+typedef struct pj_pcap_hdr
+{
+ pj_uint32_t magic_number; /* magic number */
+ pj_uint16_t version_major; /* major version number */
+ pj_uint16_t version_minor; /* minor version number */
+ pj_int32_t thiszone; /* GMT to local correction */
+ pj_uint32_t sigfigs; /* accuracy of timestamps */
+ pj_uint32_t snaplen; /* max length of captured packets, in octets */
+ pj_uint32_t network; /* data link type */
+} pj_pcap_hdr;
+
+typedef struct pj_pcap_rec_hdr
+{
+ pj_uint32_t ts_sec; /* timestamp seconds */
+ pj_uint32_t ts_usec; /* timestamp microseconds */
+ pj_uint32_t incl_len; /* number of octets of packet saved in file */
+ pj_uint32_t orig_len; /* actual length of packet */
+} pj_pcap_rec_hdr;
+
+#if 0
+/* gcc insisted on aligning this struct to 32bit on ARM */
+typedef struct pj_pcap_eth_hdr
+{
+ pj_uint8_t dest[6];
+ pj_uint8_t src[6];
+ pj_uint8_t len[2];
+} pj_pcap_eth_hdr;
+#else
+typedef pj_uint8_t pj_pcap_eth_hdr[14];
+#endif
+
+typedef struct pj_pcap_ip_hdr
+{
+ pj_uint8_t v_ihl;
+ pj_uint8_t tos;
+ pj_uint16_t len;
+ pj_uint16_t id;
+ pj_uint16_t flags_fragment;
+ pj_uint8_t ttl;
+ pj_uint8_t proto;
+ pj_uint16_t csum;
+ pj_uint32_t ip_src;
+ pj_uint32_t ip_dst;
+} pj_pcap_ip_hdr;
+
+/* Implementation of pcap file */
+struct pj_pcap_file
+{
+ char obj_name[PJ_MAX_OBJ_NAME];
+ pj_oshandle_t fd;
+ pj_bool_t swap;
+ pj_pcap_hdr hdr;
+ pj_pcap_filter filter;
+};
+
+/* Init default filter */
+PJ_DEF(void) pj_pcap_filter_default(pj_pcap_filter *filter)
+{
+ pj_bzero(filter, sizeof(*filter));
+}
+
+/* Open pcap file */
+PJ_DEF(pj_status_t) pj_pcap_open(pj_pool_t *pool,
+ const char *path,
+ pj_pcap_file **p_file)
+{
+ pj_pcap_file *file;
+ pj_ssize_t sz;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && path && p_file, PJ_EINVAL);
+
+ /* More sanity checks */
+ TRACE_(("pcap", "sizeof(pj_pcap_eth_hdr)=%d",
+ sizeof(pj_pcap_eth_hdr)));
+ PJ_ASSERT_RETURN(sizeof(pj_pcap_eth_hdr)==14, PJ_EBUG);
+ TRACE_(("pcap", "sizeof(pj_pcap_ip_hdr)=%d",
+ sizeof(pj_pcap_ip_hdr)));
+ PJ_ASSERT_RETURN(sizeof(pj_pcap_ip_hdr)==20, PJ_EBUG);
+ TRACE_(("pcap", "sizeof(pj_pcap_udp_hdr)=%d",
+ sizeof(pj_pcap_udp_hdr)));
+ PJ_ASSERT_RETURN(sizeof(pj_pcap_udp_hdr)==8, PJ_EBUG);
+
+ file = PJ_POOL_ZALLOC_T(pool, pj_pcap_file);
+
+ pj_ansi_strcpy(file->obj_name, "pcap");
+
+ status = pj_file_open(pool, path, PJ_O_RDONLY, &file->fd);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Read file pcap header */
+ sz = sizeof(file->hdr);
+ status = pj_file_read(file->fd, &file->hdr, &sz);
+ if (status != PJ_SUCCESS) {
+ pj_file_close(file->fd);
+ return status;
+ }
+
+ /* Check magic number */
+ if (file->hdr.magic_number == 0xa1b2c3d4) {
+ file->swap = PJ_FALSE;
+ } else if (file->hdr.magic_number == 0xd4c3b2a1) {
+ file->swap = PJ_TRUE;
+ file->hdr.network = pj_ntohl(file->hdr.network);
+ } else {
+ /* Not PCAP file */
+ pj_file_close(file->fd);
+ return PJ_EINVALIDOP;
+ }
+
+ TRACE_((file->obj_name, "PCAP file %s opened", path));
+
+ *p_file = file;
+ return PJ_SUCCESS;
+}
+
+/* Close pcap file */
+PJ_DEF(pj_status_t) pj_pcap_close(pj_pcap_file *file)
+{
+ PJ_ASSERT_RETURN(file, PJ_EINVAL);
+ TRACE_((file->obj_name, "PCAP file closed"));
+ return pj_file_close(file->fd);
+}
+
+/* Setup filter */
+PJ_DEF(pj_status_t) pj_pcap_set_filter(pj_pcap_file *file,
+ const pj_pcap_filter *fil)
+{
+ PJ_ASSERT_RETURN(file && fil, PJ_EINVAL);
+ pj_memcpy(&file->filter, fil, sizeof(pj_pcap_filter));
+ return PJ_SUCCESS;
+}
+
+/* Read file */
+static pj_status_t read_file(pj_pcap_file *file,
+ void *buf,
+ pj_ssize_t *sz)
+{
+ pj_status_t status;
+ status = pj_file_read(file->fd, buf, sz);
+ if (status != PJ_SUCCESS)
+ return status;
+ if (*sz == 0)
+ return PJ_EEOF;
+ return PJ_SUCCESS;
+}
+
+static pj_status_t skip(pj_oshandle_t fd, pj_off_t bytes)
+{
+ pj_status_t status;
+ status = pj_file_setpos(fd, bytes, PJ_SEEK_CUR);
+ if (status != PJ_SUCCESS)
+ return status;
+ return PJ_SUCCESS;
+}
+
+
+#define SKIP_PKT() \
+ if (rec_incl > sz_read) { \
+ status = skip(file->fd, rec_incl-sz_read);\
+ if (status != PJ_SUCCESS) \
+ return status; \
+ }
+
+/* Read UDP packet */
+PJ_DEF(pj_status_t) pj_pcap_read_udp(pj_pcap_file *file,
+ pj_pcap_udp_hdr *udp_hdr,
+ pj_uint8_t *udp_payload,
+ pj_size_t *udp_payload_size)
+{
+ PJ_ASSERT_RETURN(file && udp_payload && udp_payload_size, PJ_EINVAL);
+ PJ_ASSERT_RETURN(*udp_payload_size, PJ_EINVAL);
+
+ /* Check data link type in PCAP file header */
+ if ((file->filter.link &&
+ file->hdr.network != (pj_uint32_t)file->filter.link) ||
+ file->hdr.network != PJ_PCAP_LINK_TYPE_ETH)
+ {
+ /* Link header other than Ethernet is not supported for now */
+ return PJ_ENOTSUP;
+ }
+
+ /* Loop until we have the packet */
+ for (;;) {
+ union {
+ pj_pcap_rec_hdr rec;
+ pj_pcap_eth_hdr eth;
+ pj_pcap_ip_hdr ip;
+ pj_pcap_udp_hdr udp;
+ } tmp;
+ unsigned rec_incl;
+ pj_ssize_t sz;
+ unsigned sz_read = 0;
+ pj_status_t status;
+
+ TRACE_((file->obj_name, "Reading packet.."));
+
+ /* Read PCAP packet header */
+ sz = sizeof(tmp.rec);
+ status = read_file(file, &tmp.rec, &sz);
+ if (status != PJ_SUCCESS) {
+ TRACE_((file->obj_name, "read_file() error: %d", status));
+ return status;
+ }
+
+ rec_incl = tmp.rec.incl_len;
+
+ /* Swap byte ordering */
+ if (file->swap) {
+ tmp.rec.incl_len = pj_ntohl(tmp.rec.incl_len);
+ tmp.rec.orig_len = pj_ntohl(tmp.rec.orig_len);
+ tmp.rec.ts_sec = pj_ntohl(tmp.rec.ts_sec);
+ tmp.rec.ts_usec = pj_ntohl(tmp.rec.ts_usec);
+ }
+
+ /* Read link layer header */
+ switch (file->hdr.network) {
+ case PJ_PCAP_LINK_TYPE_ETH:
+ sz = sizeof(tmp.eth);
+ status = read_file(file, &tmp.eth, &sz);
+ break;
+ default:
+ TRACE_((file->obj_name, "Error: link layer not Ethernet"));
+ return PJ_ENOTSUP;
+ }
+
+ if (status != PJ_SUCCESS) {
+ TRACE_((file->obj_name, "Error reading Eth header: %d", status));
+ return status;
+ }
+
+ sz_read += sz;
+
+ /* Read IP header */
+ sz = sizeof(tmp.ip);
+ status = read_file(file, &tmp.ip, &sz);
+ if (status != PJ_SUCCESS) {
+ TRACE_((file->obj_name, "Error reading IP header: %d", status));
+ return status;
+ }
+
+ sz_read += sz;
+
+ /* Skip if IP source mismatch */
+ if (file->filter.ip_src && tmp.ip.ip_src != file->filter.ip_src) {
+ TRACE_((file->obj_name, "IP source %s mismatch, skipping",
+ pj_inet_ntoa(*(pj_in_addr*)&tmp.ip.ip_src)));
+ SKIP_PKT();
+ continue;
+ }
+
+ /* Skip if IP destination mismatch */
+ if (file->filter.ip_dst && tmp.ip.ip_dst != file->filter.ip_dst) {
+ TRACE_((file->obj_name, "IP detination %s mismatch, skipping",
+ pj_inet_ntoa(*(pj_in_addr*)&tmp.ip.ip_dst)));
+ SKIP_PKT();
+ continue;
+ }
+
+ /* Skip if proto mismatch */
+ if (file->filter.proto && tmp.ip.proto != file->filter.proto) {
+ TRACE_((file->obj_name, "IP proto %d mismatch, skipping",
+ tmp.ip.proto));
+ SKIP_PKT();
+ continue;
+ }
+
+ /* Read transport layer header */
+ switch (tmp.ip.proto) {
+ case PJ_PCAP_PROTO_TYPE_UDP:
+ sz = sizeof(tmp.udp);
+ status = read_file(file, &tmp.udp, &sz);
+ if (status != PJ_SUCCESS) {
+ TRACE_((file->obj_name, "Error reading UDP header: %d",status));
+ return status;
+ }
+
+ sz_read += sz;
+
+ /* Skip if source port mismatch */
+ if (file->filter.src_port &&
+ tmp.udp.src_port != file->filter.src_port)
+ {
+ TRACE_((file->obj_name, "UDP src port %d mismatch, skipping",
+ pj_ntohs(tmp.udp.src_port)));
+ SKIP_PKT();
+ continue;
+ }
+
+ /* Skip if destination port mismatch */
+ if (file->filter.dst_port &&
+ tmp.udp.dst_port != file->filter.dst_port)
+ {
+ TRACE_((file->obj_name, "UDP dst port %d mismatch, skipping",
+ pj_ntohs(tmp.udp.dst_port)));
+ SKIP_PKT();
+ continue;
+ }
+
+ /* Copy UDP header if caller wants it */
+ if (udp_hdr) {
+ pj_memcpy(udp_hdr, &tmp.udp, sizeof(*udp_hdr));
+ }
+
+ /* Calculate payload size */
+ sz = pj_ntohs(tmp.udp.len) - sizeof(tmp.udp);
+ break;
+ default:
+ TRACE_((file->obj_name, "Not UDP, skipping"));
+ SKIP_PKT();
+ continue;
+ }
+
+ /* Check if payload fits the buffer */
+ if (sz > (pj_ssize_t)*udp_payload_size) {
+ TRACE_((file->obj_name,
+ "Error: packet too large (%d bytes required)", sz));
+ SKIP_PKT();
+ return PJ_ETOOSMALL;
+ }
+
+ /* Read the payload */
+ status = read_file(file, udp_payload, &sz);
+ if (status != PJ_SUCCESS) {
+ TRACE_((file->obj_name, "Error reading payload: %d", status));
+ return status;
+ }
+
+ sz_read += sz;
+
+ *udp_payload_size = sz;
+
+ // Some layers may have trailer, e.g: link eth2.
+ /* Check that we've read all the packets */
+ //PJ_ASSERT_RETURN(sz_read == rec_incl, PJ_EBUG);
+
+ /* Skip trailer */
+ while (sz_read < rec_incl) {
+ sz = rec_incl - sz_read;
+ status = read_file(file, &tmp.eth, &sz);
+ if (status != PJ_SUCCESS) {
+ TRACE_((file->obj_name, "Error reading trailer: %d", status));
+ return status;
+ }
+ sz_read += sz;
+ }
+
+ return PJ_SUCCESS;
+ }
+
+ /* Does not reach here */
+}
+
+
diff --git a/pjlib-util/src/pjlib-util/resolver.c b/pjlib-util/src/pjlib-util/resolver.c
new file mode 100644
index 0000000..4be5350
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/resolver.c
@@ -0,0 +1,1588 @@
+/* $Id: resolver.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/resolver.h>
+#include <pjlib-util/errno.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/except.h>
+#include <pj/hash.h>
+#include <pj/ioqueue.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/pool_buf.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+#include <pj/sock.h>
+#include <pj/timer.h>
+
+
+#define THIS_FILE "resolver.c"
+
+
+/* Check that maximum DNS nameservers is not too large.
+ * This has got todo with the datatype to index the nameserver in the query.
+ */
+#if PJ_DNS_RESOLVER_MAX_NS > 256
+# error "PJ_DNS_RESOLVER_MAX_NS is too large (max=256)"
+#endif
+
+
+#define RES_HASH_TABLE_SIZE 127 /**< Hash table size (must be 2^n-1 */
+#define PORT 53 /**< Default NS port. */
+#define Q_HASH_TABLE_SIZE 127 /**< Query hash table size */
+#define TIMER_SIZE 127 /**< Initial number of timers. */
+#define MAX_FD 3 /**< Maximum internal sockets. */
+
+#define RES_BUF_SZ PJ_DNS_RESOLVER_RES_BUF_SIZE
+#define UDPSZ PJ_DNS_RESOLVER_MAX_UDP_SIZE
+#define TMP_SZ PJ_DNS_RESOLVER_TMP_BUF_SIZE
+
+
+/* Nameserver state */
+enum ns_state
+{
+ STATE_PROBING,
+ STATE_ACTIVE,
+ STATE_BAD,
+};
+
+static const char *state_names[3] =
+{
+ "Probing",
+ "Active",
+ "Bad"
+};
+
+
+/*
+ * Each nameserver entry.
+ * A name server is identified by its socket address (IP and port).
+ * Each NS will have a flag to indicate whether it's properly functioning.
+ */
+struct nameserver
+{
+ pj_sockaddr_in addr; /**< Server address. */
+
+ enum ns_state state; /**< Nameserver state. */
+ pj_time_val state_expiry; /**< Time set next state. */
+ pj_time_val rt_delay; /**< Response time. */
+
+
+ /* For calculating rt_delay: */
+ pj_uint16_t q_id; /**< Query ID. */
+ pj_time_val sent_time; /**< Time this query is sent. */
+};
+
+
+/* Child query list head
+ * See comments on pj_dns_async_query below.
+ */
+struct query_head
+{
+ PJ_DECL_LIST_MEMBER(pj_dns_async_query);
+};
+
+
+/* Key to look for outstanding query and/or cached response */
+struct res_key
+{
+ pj_uint16_t qtype; /**< Query type. */
+ char name[PJ_MAX_HOSTNAME]; /**< Name being queried */
+};
+
+
+/*
+ * This represents each asynchronous query entry.
+ * This entry will be put in two hash tables, the first one keyed on the DNS
+ * transaction ID to match response with the query, and the second one keyed
+ * on "res_key" structure above to match a new request against outstanding
+ * requests.
+ *
+ * An asynchronous entry may have child entries; child entries are subsequent
+ * queries to the same resource while there is pending query on the same
+ * DNS resource name and type. When a query has child entries, once the
+ * response is received (or error occurs), the response will trigger callback
+ * invocations for all childs entries.
+ *
+ * Note: when application cancels the query, the callback member will be
+ * set to NULL, but for simplicity, the query will be let running.
+ */
+struct pj_dns_async_query
+{
+ PJ_DECL_LIST_MEMBER(pj_dns_async_query); /**< List member. */
+
+ pj_dns_resolver *resolver; /**< The resolver instance. */
+ pj_uint16_t id; /**< Transaction ID. */
+
+ unsigned transmit_cnt; /**< Number of transmissions. */
+
+ struct res_key key; /**< Key to index this query. */
+ pj_hash_entry_buf hbufid; /**< Hash buffer 1 */
+ pj_hash_entry_buf hbufkey; /**< Hash buffer 2 */
+ pj_timer_entry timer_entry; /**< Timer to manage timeouts */
+ unsigned options; /**< Query options. */
+ void *user_data; /**< Application data. */
+ pj_dns_callback *cb; /**< Callback to be called. */
+ struct query_head child_head; /**< Child queries list head. */
+};
+
+
+/* This structure is used to keep cached response entry.
+ * The cache is a hash table keyed on "res_key" structure above.
+ */
+struct cached_res
+{
+ PJ_DECL_LIST_MEMBER(struct cached_res);
+
+ pj_pool_t *pool; /**< Cache's pool. */
+ struct res_key key; /**< Resource key. */
+ pj_hash_entry_buf hbuf; /**< Hash buffer */
+ pj_time_val expiry_time; /**< Expiration time. */
+ pj_dns_parsed_packet *pkt; /**< The response packet. */
+};
+
+
+/* Resolver entry */
+struct pj_dns_resolver
+{
+ pj_str_t name; /**< Resolver instance name for id. */
+
+ /* Internals */
+ pj_pool_t *pool; /**< Internal pool. */
+ pj_mutex_t *mutex; /**< Mutex protection. */
+ pj_bool_t own_timer; /**< Do we own timer? */
+ pj_timer_heap_t *timer; /**< Timer instance. */
+ pj_bool_t own_ioqueue; /**< Do we own ioqueue? */
+ pj_ioqueue_t *ioqueue; /**< Ioqueue instance. */
+ char tmp_pool[TMP_SZ];/**< Temporary pool buffer. */
+
+ /* Socket */
+ pj_sock_t udp_sock; /**< UDP socket. */
+ pj_ioqueue_key_t *udp_key; /**< UDP socket ioqueue key. */
+ unsigned char udp_rx_pkt[UDPSZ];/**< UDP receive buffer. */
+ unsigned char udp_tx_pkt[UDPSZ];/**< UDP receive buffer. */
+ pj_ssize_t udp_len; /**< Length of received packet. */
+ pj_ioqueue_op_key_t udp_op_key; /**< UDP read operation key. */
+ pj_sockaddr_in udp_src_addr; /**< Source address of packet */
+ int udp_addr_len; /**< Source address length. */
+
+ /* Settings */
+ pj_dns_settings settings; /**< Resolver settings. */
+
+ /* Nameservers */
+ unsigned ns_count; /**< Number of name servers. */
+ struct nameserver ns[PJ_DNS_RESOLVER_MAX_NS]; /**< Array of NS. */
+
+ /* Last DNS transaction ID used. */
+ pj_uint16_t last_id;
+
+ /* Hash table for cached response */
+ pj_hash_table_t *hrescache; /**< Cached response in hash table */
+
+ /* Pending asynchronous query, hashed by transaction ID. */
+ pj_hash_table_t *hquerybyid;
+
+ /* Pending asynchronous query, hashed by "res_key" */
+ pj_hash_table_t *hquerybyres;
+
+ /* Query entries free list */
+ struct query_head query_free_nodes;
+};
+
+
+/* Callback from ioqueue when packet is received */
+static void on_read_complete(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read);
+
+/* Callback to be called when query has timed out */
+static void on_timeout( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry);
+
+/* Select which nameserver to use */
+static pj_status_t select_nameservers(pj_dns_resolver *resolver,
+ unsigned *count,
+ unsigned servers[]);
+
+
+/* Initialize DNS settings with default values */
+PJ_DEF(void) pj_dns_settings_default(pj_dns_settings *s)
+{
+ pj_bzero(s, sizeof(pj_dns_settings));
+ s->qretr_delay = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY;
+ s->qretr_count = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT;
+ s->cache_max_ttl = PJ_DNS_RESOLVER_MAX_TTL;
+ s->good_ns_ttl = PJ_DNS_RESOLVER_GOOD_NS_TTL;
+ s->bad_ns_ttl = PJ_DNS_RESOLVER_BAD_NS_TTL;
+}
+
+
+/*
+ * Create the resolver.
+ */
+PJ_DEF(pj_status_t) pj_dns_resolver_create( pj_pool_factory *pf,
+ const char *name,
+ unsigned options,
+ pj_timer_heap_t *timer,
+ pj_ioqueue_t *ioqueue,
+ pj_dns_resolver **p_resolver)
+{
+ pj_pool_t *pool;
+ pj_dns_resolver *resv;
+ pj_ioqueue_callback socket_cb;
+ pj_status_t status;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(pf && p_resolver, PJ_EINVAL);
+
+ if (name == NULL)
+ name = THIS_FILE;
+
+ /* Create and initialize resolver instance */
+ pool = pj_pool_create(pf, name, 4000, 4000, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Create pool and name */
+ resv = PJ_POOL_ZALLOC_T(pool, struct pj_dns_resolver);
+ resv->pool = pool;
+ resv->udp_sock = PJ_INVALID_SOCKET;
+ pj_strdup2_with_null(pool, &resv->name, name);
+
+ /* Create the mutex */
+ status = pj_mutex_create_recursive(pool, name, &resv->mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Timer, ioqueue, and settings */
+ resv->timer = timer;
+ resv->ioqueue = ioqueue;
+ resv->last_id = 1;
+
+ pj_dns_settings_default(&resv->settings);
+ resv->settings.options = options;
+
+ /* Create the timer heap if one is not specified */
+ if (resv->timer == NULL) {
+ status = pj_timer_heap_create(pool, TIMER_SIZE, &resv->timer);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Create the ioqueue if one is not specified */
+ if (resv->ioqueue == NULL) {
+ status = pj_ioqueue_create(pool, MAX_FD, &resv->ioqueue);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Response cache hash table */
+ resv->hrescache = pj_hash_create(pool, RES_HASH_TABLE_SIZE);
+
+ /* Query hash table and free list. */
+ resv->hquerybyid = pj_hash_create(pool, Q_HASH_TABLE_SIZE);
+ resv->hquerybyres = pj_hash_create(pool, Q_HASH_TABLE_SIZE);
+ pj_list_init(&resv->query_free_nodes);
+
+ /* Create the UDP socket */
+ status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &resv->udp_sock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Bind to any address/port */
+ status = pj_sock_bind_in(resv->udp_sock, 0, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register to ioqueue */
+ pj_bzero(&socket_cb, sizeof(socket_cb));
+ socket_cb.on_read_complete = &on_read_complete;
+ status = pj_ioqueue_register_sock(pool, resv->ioqueue, resv->udp_sock,
+ resv, &socket_cb, &resv->udp_key);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pj_ioqueue_op_key_init(&resv->udp_op_key, sizeof(resv->udp_op_key));
+
+ /* Start asynchronous read to the UDP socket */
+ resv->udp_len = sizeof(resv->udp_rx_pkt);
+ resv->udp_addr_len = sizeof(resv->udp_src_addr);
+ status = pj_ioqueue_recvfrom(resv->udp_key, &resv->udp_op_key,
+ resv->udp_rx_pkt, &resv->udp_len,
+ PJ_IOQUEUE_ALWAYS_ASYNC,
+ &resv->udp_src_addr, &resv->udp_addr_len);
+ if (status != PJ_EPENDING)
+ goto on_error;
+
+
+ /* Looks like everything is okay */
+ *p_resolver = resv;
+ return PJ_SUCCESS;
+
+on_error:
+ pj_dns_resolver_destroy(resv, PJ_FALSE);
+ return status;
+}
+
+
+/*
+ * Destroy DNS resolver instance.
+ */
+PJ_DEF(pj_status_t) pj_dns_resolver_destroy( pj_dns_resolver *resolver,
+ pj_bool_t notify)
+{
+ pj_hash_iterator_t it_buf, *it;
+ PJ_ASSERT_RETURN(resolver, PJ_EINVAL);
+
+ if (notify) {
+ /*
+ * Notify pending queries if requested.
+ */
+ it = pj_hash_first(resolver->hquerybyid, &it_buf);
+ while (it) {
+ pj_dns_async_query *q = (pj_dns_async_query *)
+ pj_hash_this(resolver->hquerybyid, it);
+ pj_dns_async_query *cq;
+ if (q->cb)
+ (*q->cb)(q->user_data, PJ_ECANCELLED, NULL);
+
+ cq = q->child_head.next;
+ while (cq != (pj_dns_async_query*)&q->child_head) {
+ if (cq->cb)
+ (*cq->cb)(cq->user_data, PJ_ECANCELLED, NULL);
+ cq = cq->next;
+ }
+ it = pj_hash_next(resolver->hquerybyid, it);
+ }
+ }
+
+ /* Destroy cached entries */
+ it = pj_hash_first(resolver->hrescache, &it_buf);
+ while (it) {
+ struct cached_res *cache;
+
+ cache = (struct cached_res*) pj_hash_this(resolver->hrescache, it);
+ pj_hash_set(NULL, resolver->hrescache, &cache->key,
+ sizeof(cache->key), 0, NULL);
+ pj_pool_release(cache->pool);
+
+ it = pj_hash_first(resolver->hrescache, &it_buf);
+ }
+
+ if (resolver->own_timer && resolver->timer) {
+ pj_timer_heap_destroy(resolver->timer);
+ resolver->timer = NULL;
+ }
+
+ if (resolver->udp_key != NULL) {
+ pj_ioqueue_unregister(resolver->udp_key);
+ resolver->udp_key = NULL;
+ resolver->udp_sock = PJ_INVALID_SOCKET;
+ } else if (resolver->udp_sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(resolver->udp_sock);
+ resolver->udp_sock = PJ_INVALID_SOCKET;
+ }
+
+ if (resolver->own_ioqueue && resolver->ioqueue) {
+ pj_ioqueue_destroy(resolver->ioqueue);
+ resolver->ioqueue = NULL;
+ }
+
+ if (resolver->mutex) {
+ pj_mutex_destroy(resolver->mutex);
+ resolver->mutex = NULL;
+ }
+
+ if (resolver->pool) {
+ pj_pool_t *pool = resolver->pool;
+ resolver->pool = NULL;
+ pj_pool_release(pool);
+ }
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Configure name servers for the DNS resolver.
+ */
+PJ_DEF(pj_status_t) pj_dns_resolver_set_ns( pj_dns_resolver *resolver,
+ unsigned count,
+ const pj_str_t servers[],
+ const pj_uint16_t ports[])
+{
+ unsigned i;
+ pj_time_val now;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(resolver && count && servers, PJ_EINVAL);
+ PJ_ASSERT_RETURN(count < PJ_DNS_RESOLVER_MAX_NS, PJ_EINVAL);
+
+ pj_mutex_lock(resolver->mutex);
+
+ if (count > PJ_DNS_RESOLVER_MAX_NS)
+ count = PJ_DNS_RESOLVER_MAX_NS;
+
+ resolver->ns_count = 0;
+ pj_bzero(resolver->ns, sizeof(resolver->ns));
+
+ pj_gettimeofday(&now);
+
+ for (i=0; i<count; ++i) {
+ struct nameserver *ns = &resolver->ns[i];
+
+ status = pj_sockaddr_in_init(&ns->addr, &servers[i],
+ (pj_uint16_t)(ports ? ports[i] : PORT));
+ if (status != PJ_SUCCESS) {
+ pj_mutex_unlock(resolver->mutex);
+ return PJLIB_UTIL_EDNSINNSADDR;
+ }
+
+ ns->state = STATE_ACTIVE;
+ ns->state_expiry = now;
+ ns->rt_delay.sec = 10;
+ }
+
+ resolver->ns_count = count;
+
+ pj_mutex_unlock(resolver->mutex);
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Modify the resolver settings.
+ */
+PJ_DEF(pj_status_t) pj_dns_resolver_set_settings(pj_dns_resolver *resolver,
+ const pj_dns_settings *st)
+{
+ PJ_ASSERT_RETURN(resolver && st, PJ_EINVAL);
+
+ pj_mutex_lock(resolver->mutex);
+ pj_memcpy(&resolver->settings, st, sizeof(*st));
+ pj_mutex_unlock(resolver->mutex);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the resolver current settings.
+ */
+PJ_DEF(pj_status_t) pj_dns_resolver_get_settings( pj_dns_resolver *resolver,
+ pj_dns_settings *st)
+{
+ PJ_ASSERT_RETURN(resolver && st, PJ_EINVAL);
+
+ pj_mutex_lock(resolver->mutex);
+ pj_memcpy(st, &resolver->settings, sizeof(*st));
+ pj_mutex_unlock(resolver->mutex);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Poll for events from the resolver.
+ */
+PJ_DEF(void) pj_dns_resolver_handle_events(pj_dns_resolver *resolver,
+ const pj_time_val *timeout)
+{
+ PJ_ASSERT_ON_FAIL(resolver, return);
+
+ pj_mutex_lock(resolver->mutex);
+ pj_timer_heap_poll(resolver->timer, NULL);
+ pj_mutex_unlock(resolver->mutex);
+
+ pj_ioqueue_poll(resolver->ioqueue, timeout);
+}
+
+
+/* Get one query node from the free node, if any, or allocate
+ * a new one.
+ */
+static pj_dns_async_query *alloc_qnode(pj_dns_resolver *resolver,
+ unsigned options,
+ void *user_data,
+ pj_dns_callback *cb)
+{
+ pj_dns_async_query *q;
+
+ /* Merge query options with resolver options */
+ options |= resolver->settings.options;
+
+ if (!pj_list_empty(&resolver->query_free_nodes)) {
+ q = resolver->query_free_nodes.next;
+ pj_list_erase(q);
+ pj_bzero(q, sizeof(*q));
+ } else {
+ q = PJ_POOL_ZALLOC_T(resolver->pool, pj_dns_async_query);
+ }
+
+ /* Init query */
+ q->resolver = resolver;
+ q->options = options;
+ q->user_data = user_data;
+ q->cb = cb;
+ pj_list_init(&q->child_head);
+
+ return q;
+}
+
+
+/*
+ * Transmit query.
+ */
+static pj_status_t transmit_query(pj_dns_resolver *resolver,
+ pj_dns_async_query *q)
+{
+ unsigned pkt_size;
+ unsigned i, server_cnt;
+ unsigned servers[PJ_DNS_RESOLVER_MAX_NS];
+ pj_time_val now;
+ pj_str_t name;
+ pj_time_val delay;
+ pj_status_t status;
+
+ /* Create DNS query packet */
+ pkt_size = sizeof(resolver->udp_tx_pkt);
+ name = pj_str(q->key.name);
+ status = pj_dns_make_query(resolver->udp_tx_pkt, &pkt_size,
+ q->id, q->key.qtype, &name);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Select which nameserver(s) to send requests to. */
+ server_cnt = PJ_ARRAY_SIZE(servers);
+ status = select_nameservers(resolver, &server_cnt, servers);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ if (server_cnt == 0) {
+ return PJLIB_UTIL_EDNSNOWORKINGNS;
+ }
+
+ /* Start retransmit/timeout timer for the query */
+ pj_assert(q->timer_entry.id == 0);
+ q->timer_entry.id = 1;
+ q->timer_entry.user_data = q;
+ q->timer_entry.cb = &on_timeout;
+
+ delay.sec = 0;
+ delay.msec = resolver->settings.qretr_delay;
+ pj_time_val_normalize(&delay);
+ status = pj_timer_heap_schedule(resolver->timer, &q->timer_entry, &delay);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Get current time. */
+ pj_gettimeofday(&now);
+
+ /* Send the packet to name servers */
+ for (i=0; i<server_cnt; ++i) {
+ pj_ssize_t sent = (pj_ssize_t) pkt_size;
+ struct nameserver *ns = &resolver->ns[servers[i]];
+
+ pj_sock_sendto(resolver->udp_sock, resolver->udp_tx_pkt, &sent, 0,
+ &resolver->ns[servers[i]].addr, sizeof(pj_sockaddr_in));
+
+ PJ_LOG(4,(resolver->name.ptr,
+ "%s %d bytes to NS %d (%s:%d): DNS %s query for %s",
+ (q->transmit_cnt==0? "Transmitting":"Re-transmitting"),
+ (int)sent, servers[i],
+ pj_inet_ntoa(ns->addr.sin_addr),
+ (int)pj_ntohs(ns->addr.sin_port),
+ pj_dns_get_type_name(q->key.qtype),
+ q->key.name));
+
+ if (ns->q_id == 0) {
+ ns->q_id = q->id;
+ ns->sent_time = now;
+ }
+ }
+
+ ++q->transmit_cnt;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Initialize resource key for hash table lookup.
+ */
+static void init_res_key(struct res_key *key, int type, const pj_str_t *name)
+{
+ unsigned i, len;
+ char *dst = key->name;
+ const char *src = name->ptr;
+
+ pj_bzero(key, sizeof(struct res_key));
+ key->qtype = (pj_uint16_t)type;
+
+ len = name->slen;
+ if (len > PJ_MAX_HOSTNAME) len = PJ_MAX_HOSTNAME;
+
+ /* Copy key, in lowercase */
+ for (i=0; i<len; ++i) {
+ *dst++ = (char)pj_tolower(*src++);
+ }
+}
+
+
+/* Allocate new cache entry */
+static struct cached_res *alloc_entry(pj_dns_resolver *resolver)
+{
+ pj_pool_t *pool;
+ struct cached_res *cache;
+
+ pool = pj_pool_create(resolver->pool->factory, "dnscache",
+ RES_BUF_SZ, 256, NULL);
+ cache = PJ_POOL_ZALLOC_T(pool, struct cached_res);
+ cache->pool = pool;
+
+ return cache;
+}
+
+/* Put unused/expired cached entry to the free list */
+static void free_entry(pj_dns_resolver *resolver, struct cached_res *cache)
+{
+ PJ_UNUSED_ARG(resolver);
+ pj_pool_release(cache->pool);
+}
+
+
+/*
+ * Create and start asynchronous DNS query for a single resource.
+ */
+PJ_DEF(pj_status_t) pj_dns_resolver_start_query( pj_dns_resolver *resolver,
+ const pj_str_t *name,
+ int type,
+ unsigned options,
+ pj_dns_callback *cb,
+ void *user_data,
+ pj_dns_async_query **p_query)
+{
+ pj_time_val now;
+ struct res_key key;
+ struct cached_res *cache;
+ pj_dns_async_query *q;
+ pj_uint32_t hval;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Validate arguments */
+ PJ_ASSERT_RETURN(resolver && name && type, PJ_EINVAL);
+
+ /* Check name is not too long. */
+ PJ_ASSERT_RETURN(name->slen>0 && name->slen < PJ_MAX_HOSTNAME,
+ PJ_ENAMETOOLONG);
+
+ /* Check type */
+ PJ_ASSERT_RETURN(type > 0 && type < 0xFFFF, PJ_EINVAL);
+
+ if (p_query)
+ *p_query = NULL;
+
+ /* Build resource key for looking up hash tables */
+ init_res_key(&key, type, name);
+
+ /* Start working with the resolver */
+ pj_mutex_lock(resolver->mutex);
+
+ /* Get current time. */
+ pj_gettimeofday(&now);
+
+ /* First, check if we have cached response for the specified name/type,
+ * and the cached entry has not expired.
+ */
+ hval = 0;
+ cache = (struct cached_res *) pj_hash_get(resolver->hrescache, &key,
+ sizeof(key), &hval);
+ if (cache) {
+ /* We've found a cached entry. */
+
+ /* Check for expiration */
+ if (PJ_TIME_VAL_GT(cache->expiry_time, now)) {
+
+ /* Log */
+ PJ_LOG(5,(resolver->name.ptr,
+ "Picked up DNS %s record for %.*s from cache, ttl=%d",
+ pj_dns_get_type_name(type),
+ (int)name->slen, name->ptr,
+ (int)(cache->expiry_time.sec - now.sec)));
+
+ /* Map DNS Rcode in the response into PJLIB status name space */
+ status = PJ_DNS_GET_RCODE(cache->pkt->hdr.flags);
+ status = PJ_STATUS_FROM_DNS_RCODE(status);
+
+ /* This cached response is still valid. Just return this
+ * response to caller.
+ */
+ if (cb) {
+ (*cb)(user_data, status, cache->pkt);
+ }
+
+ /* Done. No host resolution is necessary */
+
+ /* Must return PJ_SUCCESS */
+ status = PJ_SUCCESS;
+
+ goto on_return;
+ }
+
+ /* At this point, we have a cached entry, but this entry has expired.
+ * Remove this entry from the cached list.
+ */
+ pj_hash_set(NULL, resolver->hrescache, &key, sizeof(key), 0, NULL);
+
+ /* Store the entry into free nodes */
+ free_entry(resolver, cache);
+
+ /* Must continue with creating a query now */
+ }
+
+ /* Next, check if we have pending query on the same resource */
+ q = (pj_dns_async_query *) pj_hash_get(resolver->hquerybyres, &key,
+ sizeof(key), NULL);
+ if (q) {
+ /* Yes, there's another pending query to the same key.
+ * Just create a new child query and add this query to
+ * pending query's child queries.
+ */
+ pj_dns_async_query *nq;
+
+ nq = alloc_qnode(resolver, options, user_data, cb);
+ pj_list_push_back(&q->child_head, nq);
+
+ /* Done. This child query will be notified once the "parent"
+ * query completes.
+ */
+ status = PJ_SUCCESS;
+ goto on_return;
+ }
+
+ /* There's no pending query to the same key, initiate a new one. */
+ q = alloc_qnode(resolver, options, user_data, cb);
+
+ /* Save the ID and key */
+ /* TODO: dnsext-forgery-resilient: randomize id for security */
+ q->id = resolver->last_id++;
+ if (resolver->last_id == 0)
+ resolver->last_id = 1;
+ pj_memcpy(&q->key, &key, sizeof(struct res_key));
+
+ /* Send the query */
+ status = transmit_query(resolver, q);
+ if (status != PJ_SUCCESS) {
+ pj_list_push_back(&resolver->query_free_nodes, q);
+ goto on_return;
+ }
+
+ /* Add query entry to the hash tables */
+ pj_hash_set_np(resolver->hquerybyid, &q->id, sizeof(q->id),
+ 0, q->hbufid, q);
+ pj_hash_set_np(resolver->hquerybyres, &q->key, sizeof(q->key),
+ 0, q->hbufkey, q);
+
+ if (p_query)
+ *p_query = q;
+
+on_return:
+ pj_mutex_unlock(resolver->mutex);
+ return status;
+}
+
+
+/*
+ * Cancel a pending query.
+ */
+PJ_DEF(pj_status_t) pj_dns_resolver_cancel_query(pj_dns_async_query *query,
+ pj_bool_t notify)
+{
+ pj_dns_callback *cb;
+
+ PJ_ASSERT_RETURN(query, PJ_EINVAL);
+
+ pj_mutex_lock(query->resolver->mutex);
+
+ cb = query->cb;
+ query->cb = NULL;
+
+ if (notify)
+ (*cb)(query->user_data, PJ_ECANCELLED, NULL);
+
+ pj_mutex_unlock(query->resolver->mutex);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * DNS response containing A packet.
+ */
+PJ_DEF(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt,
+ pj_dns_a_record *rec)
+{
+ enum { MAX_SEARCH = 20 };
+ pj_str_t hostname, alias = {NULL, 0}, *resname;
+ unsigned bufstart = 0;
+ unsigned bufleft = sizeof(rec->buf_);
+ unsigned i, ansidx, search_cnt=0;
+
+ PJ_ASSERT_RETURN(pkt && rec, PJ_EINVAL);
+
+ /* Init the record */
+ pj_bzero(rec, sizeof(pj_dns_a_record));
+
+ /* Return error if there's error in the packet. */
+ if (PJ_DNS_GET_RCODE(pkt->hdr.flags))
+ return PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(pkt->hdr.flags));
+
+ /* Return error if there's no query section */
+ if (pkt->hdr.qdcount == 0)
+ return PJLIB_UTIL_EDNSINANSWER;
+
+ /* Return error if there's no answer */
+ if (pkt->hdr.anscount == 0)
+ return PJLIB_UTIL_EDNSNOANSWERREC;
+
+ /* Get the hostname from the query. */
+ hostname = pkt->q[0].name;
+
+ /* Copy hostname to the record */
+ if (hostname.slen > (int)bufleft) {
+ return PJ_ENAMETOOLONG;
+ }
+
+ pj_memcpy(&rec->buf_[bufstart], hostname.ptr, hostname.slen);
+ rec->name.ptr = &rec->buf_[bufstart];
+ rec->name.slen = hostname.slen;
+
+ bufstart += hostname.slen;
+ bufleft -= hostname.slen;
+
+ /* Find the first RR which name matches the hostname */
+ for (ansidx=0; ansidx < pkt->hdr.anscount; ++ansidx) {
+ if (pj_stricmp(&pkt->ans[ansidx].name, &hostname)==0)
+ break;
+ }
+
+ if (ansidx == pkt->hdr.anscount)
+ return PJLIB_UTIL_EDNSNOANSWERREC;
+
+ resname = &hostname;
+
+ /* Keep following CNAME records. */
+ while (pkt->ans[ansidx].type == PJ_DNS_TYPE_CNAME &&
+ search_cnt++ < MAX_SEARCH)
+ {
+ resname = &pkt->ans[ansidx].rdata.cname.name;
+
+ if (!alias.slen)
+ alias = *resname;
+
+ for (i=0; i < pkt->hdr.anscount; ++i) {
+ if (pj_stricmp(resname, &pkt->ans[i].name)==0) {
+ break;
+ }
+ }
+
+ if (i==pkt->hdr.anscount)
+ return PJLIB_UTIL_EDNSNOANSWERREC;
+
+ ansidx = i;
+ }
+
+ if (search_cnt >= MAX_SEARCH)
+ return PJLIB_UTIL_EDNSINANSWER;
+
+ if (pkt->ans[ansidx].type != PJ_DNS_TYPE_A)
+ return PJLIB_UTIL_EDNSINANSWER;
+
+ /* Copy alias to the record, if present. */
+ if (alias.slen) {
+ if (alias.slen > (int)bufleft)
+ return PJ_ENAMETOOLONG;
+
+ pj_memcpy(&rec->buf_[bufstart], alias.ptr, alias.slen);
+ rec->alias.ptr = &rec->buf_[bufstart];
+ rec->alias.slen = alias.slen;
+
+ bufstart += alias.slen;
+ bufleft -= alias.slen;
+ }
+
+ /* Get the IP addresses. */
+ for (i=0; i < pkt->hdr.anscount; ++i) {
+ if (pkt->ans[i].type == PJ_DNS_TYPE_A &&
+ pj_stricmp(&pkt->ans[i].name, resname)==0 &&
+ rec->addr_count < PJ_DNS_MAX_IP_IN_A_REC)
+ {
+ rec->addr[rec->addr_count++].s_addr =
+ pkt->ans[i].rdata.a.ip_addr.s_addr;
+ }
+ }
+
+ if (rec->addr_count == 0)
+ return PJLIB_UTIL_EDNSNOANSWERREC;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Set nameserver state */
+static void set_nameserver_state(pj_dns_resolver *resolver,
+ unsigned index,
+ enum ns_state state,
+ const pj_time_val *now)
+{
+ struct nameserver *ns = &resolver->ns[index];
+ enum ns_state old_state = ns->state;
+
+ ns->state = state;
+ ns->state_expiry = *now;
+
+ if (state == STATE_PROBING)
+ ns->state_expiry.sec += ((resolver->settings.qretr_count + 2) *
+ resolver->settings.qretr_delay) / 1000;
+ else if (state == STATE_ACTIVE)
+ ns->state_expiry.sec += resolver->settings.good_ns_ttl;
+ else
+ ns->state_expiry.sec += resolver->settings.bad_ns_ttl;
+
+ PJ_LOG(5, (resolver->name.ptr, "Nameserver %s:%d state changed %s --> %s",
+ pj_inet_ntoa(ns->addr.sin_addr),
+ (int)pj_ntohs(ns->addr.sin_port),
+ state_names[old_state], state_names[state]));
+}
+
+
+/* Select which nameserver(s) to use. Note this may return multiple
+ * name servers. The algorithm to select which nameservers to be
+ * sent the request to is as follows:
+ * - select the first nameserver that is known to be good for the
+ * last PJ_DNS_RESOLVER_GOOD_NS_TTL interval.
+ * - for all NSes, if last_known_good >= PJ_DNS_RESOLVER_GOOD_NS_TTL,
+ * include the NS to re-check again that the server is still good,
+ * unless the NS is known to be bad in the last PJ_DNS_RESOLVER_BAD_NS_TTL
+ * interval.
+ * - for all NSes, if last_known_bad >= PJ_DNS_RESOLVER_BAD_NS_TTL,
+ * also include the NS to re-check again that the server is still bad.
+ */
+static pj_status_t select_nameservers(pj_dns_resolver *resolver,
+ unsigned *count,
+ unsigned servers[])
+{
+ unsigned i, max_count=*count;
+ int min;
+ pj_time_val now;
+
+ pj_assert(max_count > 0);
+
+ *count = 0;
+ servers[0] = 0xFFFF;
+
+ /* Check that nameservers are configured. */
+ if (resolver->ns_count == 0)
+ return PJLIB_UTIL_EDNSNONS;
+
+ pj_gettimeofday(&now);
+
+ /* Select one Active nameserver with best response time. */
+ for (min=-1, i=0; i<resolver->ns_count; ++i) {
+ struct nameserver *ns = &resolver->ns[i];
+
+ if (ns->state != STATE_ACTIVE)
+ continue;
+
+ if (min == -1)
+ min = i;
+ else if (PJ_TIME_VAL_LT(ns->rt_delay, resolver->ns[min].rt_delay))
+ min = i;
+ }
+ if (min != -1) {
+ servers[0] = min;
+ ++(*count);
+ }
+
+ /* Scan nameservers. */
+ for (i=0; i<resolver->ns_count && *count < max_count; ++i) {
+ struct nameserver *ns = &resolver->ns[i];
+
+ if (PJ_TIME_VAL_LTE(ns->state_expiry, now)) {
+ if (ns->state == STATE_PROBING) {
+ set_nameserver_state(resolver, i, STATE_BAD, &now);
+ } else {
+ set_nameserver_state(resolver, i, STATE_PROBING, &now);
+ if ((int)i != min) {
+ servers[*count] = i;
+ ++(*count);
+ }
+ }
+ } else if (ns->state == STATE_PROBING && (int)i != min) {
+ servers[*count] = i;
+ ++(*count);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Update name server status */
+static void report_nameserver_status(pj_dns_resolver *resolver,
+ const pj_sockaddr_in *ns_addr,
+ const pj_dns_parsed_packet *pkt)
+{
+ unsigned i;
+ int rcode;
+ pj_uint32_t q_id;
+ pj_time_val now;
+ pj_bool_t is_good;
+
+ /* Only mark nameserver as "bad" if it returned non-parseable response or
+ * it returned the following status codes
+ */
+ if (pkt) {
+ rcode = PJ_DNS_GET_RCODE(pkt->hdr.flags);
+ q_id = pkt->hdr.id;
+ } else {
+ rcode = 0;
+ q_id = (pj_uint32_t)-1;
+ }
+
+ if (!pkt || rcode == PJ_DNS_RCODE_SERVFAIL ||
+ rcode == PJ_DNS_RCODE_REFUSED ||
+ rcode == PJ_DNS_RCODE_NOTAUTH)
+ {
+ is_good = PJ_FALSE;
+ } else {
+ is_good = PJ_TRUE;
+ }
+
+
+ /* Mark time */
+ pj_gettimeofday(&now);
+
+ /* Recheck all nameservers. */
+ for (i=0; i<resolver->ns_count; ++i) {
+ struct nameserver *ns = &resolver->ns[i];
+
+ if (ns->addr.sin_addr.s_addr == ns_addr->sin_addr.s_addr &&
+ ns->addr.sin_port == ns_addr->sin_port &&
+ ns->addr.sin_family == ns_addr->sin_family)
+ {
+ if (q_id == ns->q_id) {
+ /* Calculate response time */
+ pj_time_val rt = now;
+ PJ_TIME_VAL_SUB(rt, ns->sent_time);
+ ns->rt_delay = rt;
+ ns->q_id = 0;
+ }
+ set_nameserver_state(resolver, i,
+ (is_good ? STATE_ACTIVE : STATE_BAD), &now);
+ break;
+ }
+ }
+}
+
+
+/* Update response cache */
+static void update_res_cache(pj_dns_resolver *resolver,
+ const struct res_key *key,
+ pj_status_t status,
+ pj_bool_t set_expiry,
+ const pj_dns_parsed_packet *pkt)
+{
+ struct cached_res *cache;
+ pj_uint32_t hval=0, ttl;
+
+ /* If status is unsuccessful, clear the same entry from the cache */
+ if (status != PJ_SUCCESS) {
+ cache = (struct cached_res *) pj_hash_get(resolver->hrescache, key,
+ sizeof(*key), &hval);
+ if (cache)
+ free_entry(resolver, cache);
+ pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL);
+ }
+
+
+ /* Calculate expiration time. */
+ if (set_expiry) {
+ if (pkt->hdr.anscount == 0 || status != PJ_SUCCESS) {
+ /* If we don't have answers for the name, then give a different
+ * ttl value (note: PJ_DNS_RESOLVER_INVALID_TTL may be zero,
+ * which means that invalid names won't be kept in the cache)
+ */
+ ttl = PJ_DNS_RESOLVER_INVALID_TTL;
+
+ } else {
+ /* Otherwise get the minimum TTL from the answers */
+ unsigned i;
+ ttl = 0xFFFFFFFF;
+ for (i=0; i<pkt->hdr.anscount; ++i) {
+ if (pkt->ans[i].ttl < ttl)
+ ttl = pkt->ans[i].ttl;
+ }
+ }
+ } else {
+ ttl = 0xFFFFFFFF;
+ }
+
+ /* Apply maximum TTL */
+ if (ttl > resolver->settings.cache_max_ttl)
+ ttl = resolver->settings.cache_max_ttl;
+
+ /* If TTL is zero, clear the same entry in the hash table */
+ if (ttl == 0) {
+ cache = (struct cached_res *) pj_hash_get(resolver->hrescache, key,
+ sizeof(*key), &hval);
+ if (cache)
+ free_entry(resolver, cache);
+ pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL);
+ return;
+ }
+
+ /* Get a cache response entry */
+ cache = (struct cached_res *) pj_hash_get(resolver->hrescache, key,
+ sizeof(*key), &hval);
+ if (cache == NULL) {
+ cache = alloc_entry(resolver);
+ }
+
+ /* Duplicate the packet.
+ * We don't need to keep the NS and AR sections from the packet,
+ * so exclude from duplication. We do need to keep the Query
+ * section since DNS A parser needs the query section to know
+ * the name being requested.
+ */
+ cache->pkt = NULL;
+ pj_dns_packet_dup(cache->pool, pkt,
+ PJ_DNS_NO_NS | PJ_DNS_NO_AR,
+ &cache->pkt);
+
+ /* Calculate expiration time */
+ if (set_expiry) {
+ pj_gettimeofday(&cache->expiry_time);
+ cache->expiry_time.sec += ttl;
+ } else {
+ cache->expiry_time.sec = 0x7FFFFFFFL;
+ cache->expiry_time.msec = 0;
+ }
+
+ /* Copy key to the cached response */
+ pj_memcpy(&cache->key, key, sizeof(*key));
+
+ /* Update the hash table */
+ pj_hash_set_np(resolver->hrescache, &cache->key, sizeof(*key), hval,
+ cache->hbuf, cache);
+
+}
+
+
+/* Callback to be called when query has timed out */
+static void on_timeout( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pj_dns_resolver *resolver;
+ pj_dns_async_query *q, *cq;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ q = (pj_dns_async_query *) entry->user_data;
+ resolver = q->resolver;
+
+ pj_mutex_lock(resolver->mutex);
+
+ /* Recheck that this query is still pending, since there is a slight
+ * possibility of race condition (timer elapsed while at the same time
+ * response arrives)
+ */
+ if (pj_hash_get(resolver->hquerybyid, &q->id, sizeof(q->id), NULL)==NULL) {
+ /* Yeah, this query is done. */
+ pj_mutex_unlock(resolver->mutex);
+ return;
+ }
+
+ /* Invalidate id. */
+ q->timer_entry.id = 0;
+
+ /* Check to see if we should retransmit instead of time out */
+ if (q->transmit_cnt < resolver->settings.qretr_count) {
+ status = transmit_query(resolver, q);
+ if (status == PJ_SUCCESS) {
+ pj_mutex_unlock(resolver->mutex);
+ return;
+ } else {
+ /* Error occurs */
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(resolver->name.ptr,
+ "Error transmitting request: %s", errmsg));
+
+ /* Let it fallback to timeout section below */
+ }
+ }
+
+ /* Clear hash table entries */
+ pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL);
+ pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL);
+
+ /* Call application callback, if any. */
+ if (q->cb)
+ (*q->cb)(q->user_data, PJ_ETIMEDOUT, NULL);
+
+ /* Call application callback for child queries. */
+ cq = q->child_head.next;
+ while (cq != (void*)&q->child_head) {
+ if (cq->cb)
+ (*cq->cb)(cq->user_data, PJ_ETIMEDOUT, NULL);
+ cq = cq->next;
+ }
+
+ /* Clear data */
+ q->timer_entry.id = 0;
+ q->user_data = NULL;
+
+ /* Put child entries into recycle list */
+ cq = q->child_head.next;
+ while (cq != (void*)&q->child_head) {
+ pj_dns_async_query *next = cq->next;
+ pj_list_push_back(&resolver->query_free_nodes, cq);
+ cq = next;
+ }
+
+ /* Put query entry into recycle list */
+ pj_list_push_back(&resolver->query_free_nodes, q);
+
+ pj_mutex_unlock(resolver->mutex);
+}
+
+
+/* Callback from ioqueue when packet is received */
+static void on_read_complete(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read)
+{
+ pj_dns_resolver *resolver;
+ pj_pool_t *pool = NULL;
+ pj_dns_parsed_packet *dns_pkt;
+ pj_dns_async_query *q;
+ pj_status_t status;
+ PJ_USE_EXCEPTION;
+
+
+ resolver = (pj_dns_resolver *) pj_ioqueue_get_user_data(key);
+ pj_mutex_lock(resolver->mutex);
+
+
+ /* Check for errors */
+ if (bytes_read < 0) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ status = -bytes_read;
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(resolver->name.ptr,
+ "DNS resolver read error from %s:%d: %s",
+ pj_inet_ntoa(resolver->udp_src_addr.sin_addr),
+ pj_ntohs(resolver->udp_src_addr.sin_port),
+ errmsg));
+
+ goto read_next_packet;
+ }
+
+ PJ_LOG(5,(resolver->name.ptr,
+ "Received %d bytes DNS response from %s:%d",
+ (int)bytes_read,
+ pj_inet_ntoa(resolver->udp_src_addr.sin_addr),
+ pj_ntohs(resolver->udp_src_addr.sin_port)));
+
+
+ /* Check for zero packet */
+ if (bytes_read == 0)
+ goto read_next_packet;
+
+ /* Create temporary pool from a fixed buffer */
+ pool = pj_pool_create_on_buf("restmp", resolver->tmp_pool,
+ sizeof(resolver->tmp_pool));
+
+ /* Parse DNS response */
+ status = -1;
+ dns_pkt = NULL;
+ PJ_TRY {
+ status = pj_dns_parse_packet(pool, resolver->udp_rx_pkt,
+ (unsigned)bytes_read, &dns_pkt);
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ }
+ PJ_END;
+
+ /* Update nameserver status */
+ report_nameserver_status(resolver, &resolver->udp_src_addr, dns_pkt);
+
+ /* Handle parse error */
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(3,(resolver->name.ptr,
+ "Error parsing DNS response from %s:%d: %s",
+ pj_inet_ntoa(resolver->udp_src_addr.sin_addr),
+ pj_ntohs(resolver->udp_src_addr.sin_port),
+ errmsg));
+ goto read_next_packet;
+ }
+
+ /* Find the query based on the transaction ID */
+ q = (pj_dns_async_query*)
+ pj_hash_get(resolver->hquerybyid, &dns_pkt->hdr.id,
+ sizeof(dns_pkt->hdr.id), NULL);
+ if (!q) {
+ PJ_LOG(5,(resolver->name.ptr,
+ "DNS response from %s:%d id=%d discarded",
+ pj_inet_ntoa(resolver->udp_src_addr.sin_addr),
+ pj_ntohs(resolver->udp_src_addr.sin_port),
+ (unsigned)dns_pkt->hdr.id));
+ goto read_next_packet;
+ }
+
+ /* Map DNS Rcode in the response into PJLIB status name space */
+ status = PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(dns_pkt->hdr.flags));
+
+ /* Cancel query timeout timer. */
+ pj_assert(q->timer_entry.id != 0);
+ pj_timer_heap_cancel(resolver->timer, &q->timer_entry);
+ q->timer_entry.id = 0;
+
+ /* Clear hash table entries */
+ pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL);
+ pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL);
+
+ /* Workaround for deadlock problem in #1108 */
+ pj_mutex_unlock(resolver->mutex);
+
+ /* Notify applications first, to allow application to modify the
+ * record before it is saved to the hash table.
+ */
+ if (q->cb)
+ (*q->cb)(q->user_data, status, dns_pkt);
+
+ /* If query has subqueries, notify subqueries's application callback */
+ if (!pj_list_empty(&q->child_head)) {
+ pj_dns_async_query *child_q;
+
+ child_q = q->child_head.next;
+ while (child_q != (pj_dns_async_query*)&q->child_head) {
+ if (child_q->cb)
+ (*child_q->cb)(child_q->user_data, status, dns_pkt);
+ child_q = child_q->next;
+ }
+ }
+
+ /* Workaround for deadlock problem in #1108 */
+ pj_mutex_lock(resolver->mutex);
+
+ /* Save/update response cache. */
+ update_res_cache(resolver, &q->key, status, PJ_TRUE, dns_pkt);
+
+ /* Recycle query objects, starting with the child queries */
+ if (!pj_list_empty(&q->child_head)) {
+ pj_dns_async_query *child_q;
+
+ child_q = q->child_head.next;
+ while (child_q != (pj_dns_async_query*)&q->child_head) {
+ pj_dns_async_query *next = child_q->next;
+ pj_list_erase(child_q);
+ pj_list_push_back(&resolver->query_free_nodes, child_q);
+ child_q = next;
+ }
+ }
+ pj_list_push_back(&resolver->query_free_nodes, q);
+
+read_next_packet:
+ if (pool) {
+ /* needed just in case PJ_HAS_POOL_ALT_API is set */
+ pj_pool_release(pool);
+ }
+ bytes_read = sizeof(resolver->udp_rx_pkt);
+ resolver->udp_addr_len = sizeof(resolver->udp_src_addr);
+ status = pj_ioqueue_recvfrom(resolver->udp_key, op_key,
+ resolver->udp_rx_pkt,
+ &bytes_read, PJ_IOQUEUE_ALWAYS_ASYNC,
+ &resolver->udp_src_addr,
+ &resolver->udp_addr_len);
+ if (status != PJ_EPENDING) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(resolver->name.ptr, "DNS resolver ioqueue read error: %s",
+ errmsg));
+
+ pj_assert(!"Unhandled error");
+ }
+
+ pj_mutex_unlock(resolver->mutex);
+}
+
+
+/*
+ * Put the specified DNS packet into DNS cache. This function is mainly used
+ * for testing the resolver, however it can also be used to inject entries
+ * into the resolver.
+ */
+PJ_DEF(pj_status_t) pj_dns_resolver_add_entry( pj_dns_resolver *resolver,
+ const pj_dns_parsed_packet *pkt,
+ pj_bool_t set_ttl)
+{
+ struct res_key key;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(resolver && pkt, PJ_EINVAL);
+
+ /* Packet must be a DNS response */
+ PJ_ASSERT_RETURN(PJ_DNS_GET_QR(pkt->hdr.flags) & 1, PJ_EINVAL);
+
+ /* Make sure there are answers in the packet */
+ PJ_ASSERT_RETURN((pkt->hdr.anscount && pkt->ans) ||
+ (pkt->hdr.qdcount && pkt->q),
+ PJLIB_UTIL_EDNSNOANSWERREC);
+
+ pj_mutex_lock(resolver->mutex);
+
+ /* Build resource key for looking up hash tables */
+ pj_bzero(&key, sizeof(struct res_key));
+ if (pkt->hdr.anscount) {
+ /* Make sure name is not too long. */
+ PJ_ASSERT_RETURN(pkt->ans[0].name.slen < PJ_MAX_HOSTNAME,
+ PJ_ENAMETOOLONG);
+
+ init_res_key(&key, pkt->ans[0].type, &pkt->ans[0].name);
+
+ } else {
+ /* Make sure name is not too long. */
+ PJ_ASSERT_RETURN(pkt->q[0].name.slen < PJ_MAX_HOSTNAME,
+ PJ_ENAMETOOLONG);
+
+ init_res_key(&key, pkt->q[0].type, &pkt->q[0].name);
+ }
+
+ /* Insert entry. */
+ update_res_cache(resolver, &key, PJ_SUCCESS, set_ttl, pkt);
+
+ pj_mutex_unlock(resolver->mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the total number of response in the response cache.
+ */
+PJ_DEF(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver)
+{
+ unsigned count;
+
+ PJ_ASSERT_RETURN(resolver, 0);
+
+ pj_mutex_lock(resolver->mutex);
+ count = pj_hash_count(resolver->hrescache);
+ pj_mutex_unlock(resolver->mutex);
+
+ return count;
+}
+
+
+/*
+ * Dump resolver state to the log.
+ */
+PJ_DEF(void) pj_dns_resolver_dump(pj_dns_resolver *resolver,
+ pj_bool_t detail)
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+ unsigned i;
+ pj_time_val now;
+
+ pj_mutex_lock(resolver->mutex);
+
+ pj_gettimeofday(&now);
+
+ PJ_LOG(3,(resolver->name.ptr, " Dumping resolver state:"));
+
+ PJ_LOG(3,(resolver->name.ptr, " Name servers:"));
+ for (i=0; i<resolver->ns_count; ++i) {
+ const char *state_names[] = { "probing", "active", "bad"};
+ struct nameserver *ns = &resolver->ns[i];
+
+ PJ_LOG(3,(resolver->name.ptr,
+ " NS %d: %s:%d (state=%s until %ds, rtt=%d ms)",
+ i, pj_inet_ntoa(ns->addr.sin_addr),
+ pj_ntohs(ns->addr.sin_port),
+ state_names[ns->state],
+ ns->state_expiry.sec - now.sec,
+ PJ_TIME_VAL_MSEC(ns->rt_delay)));
+ }
+
+ PJ_LOG(3,(resolver->name.ptr, " Nb. of cached responses: %u",
+ pj_hash_count(resolver->hrescache)));
+ if (detail) {
+ pj_hash_iterator_t itbuf, *it;
+ it = pj_hash_first(resolver->hrescache, &itbuf);
+ while (it) {
+ struct cached_res *cache;
+ cache = (struct cached_res*)pj_hash_this(resolver->hrescache, it);
+ PJ_LOG(3,(resolver->name.ptr,
+ " Type %s: %s",
+ pj_dns_get_type_name(cache->key.qtype),
+ cache->key.name));
+ it = pj_hash_next(resolver->hrescache, it);
+ }
+ }
+ PJ_LOG(3,(resolver->name.ptr, " Nb. of pending queries: %u (%u)",
+ pj_hash_count(resolver->hquerybyid),
+ pj_hash_count(resolver->hquerybyres)));
+ if (detail) {
+ pj_hash_iterator_t itbuf, *it;
+ it = pj_hash_first(resolver->hquerybyid, &itbuf);
+ while (it) {
+ struct pj_dns_async_query *q;
+ q = (pj_dns_async_query*) pj_hash_this(resolver->hquerybyid, it);
+ PJ_LOG(3,(resolver->name.ptr,
+ " Type %s: %s",
+ pj_dns_get_type_name(q->key.qtype),
+ q->key.name));
+ it = pj_hash_next(resolver->hquerybyid, it);
+ }
+ }
+ PJ_LOG(3,(resolver->name.ptr, " Nb. of pending query free nodes: %u",
+ pj_list_size(&resolver->query_free_nodes)));
+ PJ_LOG(3,(resolver->name.ptr, " Nb. of timer entries: %u",
+ pj_timer_heap_count(resolver->timer)));
+ PJ_LOG(3,(resolver->name.ptr, " Pool capacity: %d, used size: %d",
+ pj_pool_get_capacity(resolver->pool),
+ pj_pool_get_used_size(resolver->pool)));
+
+ pj_mutex_unlock(resolver->mutex);
+#endif
+}
+
diff --git a/pjlib-util/src/pjlib-util/resolver_wrap.cpp b/pjlib-util/src/pjlib-util/resolver_wrap.cpp
new file mode 100644
index 0000000..b9020e8
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/resolver_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: resolver_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "resolver.c"
diff --git a/pjlib-util/src/pjlib-util/scanner.c b/pjlib-util/src/pjlib-util/scanner.c
new file mode 100644
index 0000000..d8e1c8e
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/scanner.c
@@ -0,0 +1,636 @@
+/* $Id: scanner.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/scanner.h>
+#include <pj/ctype.h>
+#include <pj/string.h>
+#include <pj/except.h>
+#include <pj/os.h>
+#include <pj/errno.h>
+#include <pj/assert.h>
+
+#define PJ_SCAN_IS_SPACE(c) ((c)==' ' || (c)=='\t')
+#define PJ_SCAN_IS_NEWLINE(c) ((c)=='\r' || (c)=='\n')
+#define PJ_SCAN_IS_PROBABLY_SPACE(c) ((c) <= 32)
+#define PJ_SCAN_CHECK_EOF(s) (s != scanner->end)
+
+
+#if defined(PJ_SCANNER_USE_BITWISE) && PJ_SCANNER_USE_BITWISE != 0
+# include "scanner_cis_bitwise.c"
+#else
+# include "scanner_cis_uint.c"
+#endif
+
+
+static void pj_scan_syntax_err(pj_scanner *scanner)
+{
+ (*scanner->callback)(scanner);
+}
+
+
+PJ_DEF(void) pj_cis_add_range(pj_cis_t *cis, int cstart, int cend)
+{
+ /* Can not set zero. This is the requirement of the parser. */
+ pj_assert(cstart > 0);
+
+ while (cstart != cend) {
+ PJ_CIS_SET(cis, cstart);
+ ++cstart;
+ }
+}
+
+PJ_DEF(void) pj_cis_add_alpha(pj_cis_t *cis)
+{
+ pj_cis_add_range( cis, 'a', 'z'+1);
+ pj_cis_add_range( cis, 'A', 'Z'+1);
+}
+
+PJ_DEF(void) pj_cis_add_num(pj_cis_t *cis)
+{
+ pj_cis_add_range( cis, '0', '9'+1);
+}
+
+PJ_DEF(void) pj_cis_add_str( pj_cis_t *cis, const char *str)
+{
+ while (*str) {
+ PJ_CIS_SET(cis, *str);
+ ++str;
+ }
+}
+
+PJ_DEF(void) pj_cis_add_cis( pj_cis_t *cis, const pj_cis_t *rhs)
+{
+ int i;
+ for (i=0; i<256; ++i) {
+ if (PJ_CIS_ISSET(rhs, i))
+ PJ_CIS_SET(cis, i);
+ }
+}
+
+PJ_DEF(void) pj_cis_del_range( pj_cis_t *cis, int cstart, int cend)
+{
+ while (cstart != cend) {
+ PJ_CIS_CLR(cis, cstart);
+ cstart++;
+ }
+}
+
+PJ_DEF(void) pj_cis_del_str( pj_cis_t *cis, const char *str)
+{
+ while (*str) {
+ PJ_CIS_CLR(cis, *str);
+ ++str;
+ }
+}
+
+PJ_DEF(void) pj_cis_invert( pj_cis_t *cis )
+{
+ unsigned i;
+ /* Can not set zero. This is the requirement of the parser. */
+ for (i=1; i<256; ++i) {
+ if (PJ_CIS_ISSET(cis,i))
+ PJ_CIS_CLR(cis,i);
+ else
+ PJ_CIS_SET(cis,i);
+ }
+}
+
+PJ_DEF(void) pj_scan_init( pj_scanner *scanner, char *bufstart, int buflen,
+ unsigned options, pj_syn_err_func_ptr callback )
+{
+ PJ_CHECK_STACK();
+
+ scanner->begin = scanner->curptr = bufstart;
+ scanner->end = bufstart + buflen;
+ scanner->line = 1;
+ scanner->start_line = scanner->begin;
+ scanner->callback = callback;
+ scanner->skip_ws = options;
+
+ if (scanner->skip_ws)
+ pj_scan_skip_whitespace(scanner);
+}
+
+
+PJ_DEF(void) pj_scan_fini( pj_scanner *scanner )
+{
+ PJ_CHECK_STACK();
+ PJ_UNUSED_ARG(scanner);
+}
+
+PJ_DEF(void) pj_scan_skip_whitespace( pj_scanner *scanner )
+{
+ register char *s = scanner->curptr;
+
+ while (PJ_SCAN_IS_SPACE(*s)) {
+ ++s;
+ }
+
+ if (PJ_SCAN_IS_NEWLINE(*s) && (scanner->skip_ws & PJ_SCAN_AUTOSKIP_NEWLINE)) {
+ for (;;) {
+ if (*s == '\r') {
+ ++s;
+ if (*s == '\n') ++s;
+ ++scanner->line;
+ scanner->curptr = scanner->start_line = s;
+ } else if (*s == '\n') {
+ ++s;
+ ++scanner->line;
+ scanner->curptr = scanner->start_line = s;
+ } else if (PJ_SCAN_IS_SPACE(*s)) {
+ do {
+ ++s;
+ } while (PJ_SCAN_IS_SPACE(*s));
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (PJ_SCAN_IS_NEWLINE(*s) && (scanner->skip_ws & PJ_SCAN_AUTOSKIP_WS_HEADER)==PJ_SCAN_AUTOSKIP_WS_HEADER) {
+ /* Check for header continuation. */
+ scanner->curptr = s;
+
+ if (*s == '\r') {
+ ++s;
+ }
+ if (*s == '\n') {
+ ++s;
+ }
+ scanner->start_line = s;
+
+ if (PJ_SCAN_IS_SPACE(*s)) {
+ register char *t = s;
+ do {
+ ++t;
+ } while (PJ_SCAN_IS_SPACE(*t));
+
+ ++scanner->line;
+ scanner->curptr = t;
+ }
+ } else {
+ scanner->curptr = s;
+ }
+}
+
+PJ_DEF(void) pj_scan_skip_line( pj_scanner *scanner )
+{
+ char *s = pj_ansi_strchr(scanner->curptr, '\n');
+ if (!s) {
+ scanner->curptr = scanner->end;
+ } else {
+ scanner->curptr = scanner->start_line = s+1;
+ scanner->line++;
+ }
+}
+
+PJ_DEF(int) pj_scan_peek( pj_scanner *scanner,
+ const pj_cis_t *spec, pj_str_t *out)
+{
+ register char *s = scanner->curptr;
+
+ if (s >= scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return -1;
+ }
+
+ /* Don't need to check EOF with PJ_SCAN_CHECK_EOF(s) */
+ while (pj_cis_match(spec, *s))
+ ++s;
+
+ pj_strset3(out, scanner->curptr, s);
+ return *s;
+}
+
+
+PJ_DEF(int) pj_scan_peek_n( pj_scanner *scanner,
+ pj_size_t len, pj_str_t *out)
+{
+ char *endpos = scanner->curptr + len;
+
+ if (endpos > scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return -1;
+ }
+
+ pj_strset(out, scanner->curptr, len);
+ return *endpos;
+}
+
+
+PJ_DEF(int) pj_scan_peek_until( pj_scanner *scanner,
+ const pj_cis_t *spec,
+ pj_str_t *out)
+{
+ register char *s = scanner->curptr;
+
+ if (s >= scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return -1;
+ }
+
+ while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match( spec, *s))
+ ++s;
+
+ pj_strset3(out, scanner->curptr, s);
+ return *s;
+}
+
+
+PJ_DEF(void) pj_scan_get( pj_scanner *scanner,
+ const pj_cis_t *spec, pj_str_t *out)
+{
+ register char *s = scanner->curptr;
+
+ pj_assert(pj_cis_match(spec,0)==0);
+
+ /* EOF is detected implicitly */
+ if (!pj_cis_match(spec, *s)) {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+
+ do {
+ ++s;
+ } while (pj_cis_match(spec, *s));
+ /* No need to check EOF here (PJ_SCAN_CHECK_EOF(s)) because
+ * buffer is NULL terminated and pj_cis_match(spec,0) should be
+ * false.
+ */
+
+ pj_strset3(out, scanner->curptr, s);
+
+ scanner->curptr = s;
+
+ if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
+ pj_scan_skip_whitespace(scanner);
+ }
+}
+
+
+PJ_DEF(void) pj_scan_get_unescape( pj_scanner *scanner,
+ const pj_cis_t *spec, pj_str_t *out)
+{
+ register char *s = scanner->curptr;
+ char *dst = s;
+
+ pj_assert(pj_cis_match(spec,0)==0);
+
+ /* Must not match character '%' */
+ pj_assert(pj_cis_match(spec,'%')==0);
+
+ /* EOF is detected implicitly */
+ if (!pj_cis_match(spec, *s) && *s != '%') {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+
+ out->ptr = s;
+ do {
+ if (*s == '%') {
+ if (s+3 <= scanner->end && pj_isxdigit(*(s+1)) &&
+ pj_isxdigit(*(s+2)))
+ {
+ *dst = (pj_uint8_t) ((pj_hex_digit_to_val(*(s+1)) << 4) +
+ pj_hex_digit_to_val(*(s+2)));
+ ++dst;
+ s += 3;
+ } else {
+ *dst++ = *s++;
+ *dst++ = *s++;
+ break;
+ }
+ }
+
+ if (pj_cis_match(spec, *s)) {
+ char *start = s;
+ do {
+ ++s;
+ } while (pj_cis_match(spec, *s));
+
+ if (dst != start) pj_memmove(dst, start, s-start);
+ dst += (s-start);
+ }
+
+ } while (*s == '%');
+
+ scanner->curptr = s;
+ out->slen = (dst - out->ptr);
+
+ if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
+ pj_scan_skip_whitespace(scanner);
+ }
+}
+
+
+PJ_DEF(void) pj_scan_get_quote( pj_scanner *scanner,
+ int begin_quote, int end_quote,
+ pj_str_t *out)
+{
+ char beg = (char)begin_quote;
+ char end = (char)end_quote;
+ pj_scan_get_quotes(scanner, &beg, &end, 1, out);
+}
+
+PJ_DEF(void) pj_scan_get_quotes(pj_scanner *scanner,
+ const char *begin_quote, const char *end_quote,
+ int qsize, pj_str_t *out)
+{
+ register char *s = scanner->curptr;
+ int qpair = -1;
+ int i;
+
+ pj_assert(qsize > 0);
+
+ /* Check and eat the begin_quote. */
+ for (i = 0; i < qsize; ++i) {
+ if (*s == begin_quote[i]) {
+ qpair = i;
+ break;
+ }
+ }
+ if (qpair == -1) {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+ ++s;
+
+ /* Loop until end_quote is found.
+ */
+ do {
+ /* loop until end_quote is found. */
+ while (PJ_SCAN_CHECK_EOF(s) && *s != '\n' && *s != end_quote[qpair]) {
+ ++s;
+ }
+
+ /* check that no backslash character precedes the end_quote. */
+ if (*s == end_quote[qpair]) {
+ if (*(s-1) == '\\') {
+ if (s-2 == scanner->begin) {
+ break;
+ } else {
+ char *q = s-2;
+ char *r = s-2;
+
+ while (r != scanner->begin && *r == '\\') {
+ --r;
+ }
+ /* break from main loop if we have odd number of backslashes */
+ if (((unsigned)(q-r) & 0x01) == 1) {
+ ++s;
+ break;
+ }
+ ++s;
+ }
+ } else {
+ /* end_quote is not preceeded by backslash. break now. */
+ break;
+ }
+ } else {
+ /* loop ended by non-end_quote character. break now. */
+ break;
+ }
+ } while (1);
+
+ /* Check and eat the end quote. */
+ if (*s != end_quote[qpair]) {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+ ++s;
+
+ pj_strset3(out, scanner->curptr, s);
+
+ scanner->curptr = s;
+
+ if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
+ pj_scan_skip_whitespace(scanner);
+ }
+}
+
+
+PJ_DEF(void) pj_scan_get_n( pj_scanner *scanner,
+ unsigned N, pj_str_t *out)
+{
+ if (scanner->curptr + N > scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+
+ pj_strset(out, scanner->curptr, N);
+
+ scanner->curptr += N;
+
+ if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
+ pj_scan_skip_whitespace(scanner);
+ }
+}
+
+
+PJ_DEF(int) pj_scan_get_char( pj_scanner *scanner )
+{
+ int chr = *scanner->curptr;
+
+ if (!chr) {
+ pj_scan_syntax_err(scanner);
+ return 0;
+ }
+
+ ++scanner->curptr;
+
+ if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
+ pj_scan_skip_whitespace(scanner);
+ }
+ return chr;
+}
+
+
+PJ_DEF(void) pj_scan_get_newline( pj_scanner *scanner )
+{
+ if (!PJ_SCAN_IS_NEWLINE(*scanner->curptr)) {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+
+ if (*scanner->curptr == '\r') {
+ ++scanner->curptr;
+ }
+ if (*scanner->curptr == '\n') {
+ ++scanner->curptr;
+ }
+
+ ++scanner->line;
+ scanner->start_line = scanner->curptr;
+
+ /**
+ * This probably is a bug, see PROTOS test #2480.
+ * This would cause scanner to incorrectly eat two new lines, e.g.
+ * when parsing:
+ *
+ * Content-Length: 120\r\n
+ * \r\n
+ * <space><space><space>...
+ *
+ * When pj_scan_get_newline() is called to parse the first newline
+ * in the Content-Length header, it will eat the second newline
+ * too because it thinks that it's a header continuation.
+ *
+ * if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
+ * pj_scan_skip_whitespace(scanner);
+ * }
+ */
+}
+
+
+PJ_DEF(void) pj_scan_get_until( pj_scanner *scanner,
+ const pj_cis_t *spec, pj_str_t *out)
+{
+ register char *s = scanner->curptr;
+
+ if (s >= scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+
+ while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match(spec, *s)) {
+ ++s;
+ }
+
+ pj_strset3(out, scanner->curptr, s);
+
+ scanner->curptr = s;
+
+ if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
+ pj_scan_skip_whitespace(scanner);
+ }
+}
+
+
+PJ_DEF(void) pj_scan_get_until_ch( pj_scanner *scanner,
+ int until_char, pj_str_t *out)
+{
+ register char *s = scanner->curptr;
+
+ if (s >= scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+
+ while (PJ_SCAN_CHECK_EOF(s) && *s != until_char) {
+ ++s;
+ }
+
+ pj_strset3(out, scanner->curptr, s);
+
+ scanner->curptr = s;
+
+ if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
+ pj_scan_skip_whitespace(scanner);
+ }
+}
+
+
+PJ_DEF(void) pj_scan_get_until_chr( pj_scanner *scanner,
+ const char *until_spec, pj_str_t *out)
+{
+ register char *s = scanner->curptr;
+ int speclen;
+
+ if (s >= scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+
+ speclen = strlen(until_spec);
+ while (PJ_SCAN_CHECK_EOF(s) && !memchr(until_spec, *s, speclen)) {
+ ++s;
+ }
+
+ pj_strset3(out, scanner->curptr, s);
+
+ scanner->curptr = s;
+
+ if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
+ pj_scan_skip_whitespace(scanner);
+ }
+}
+
+PJ_DEF(void) pj_scan_advance_n( pj_scanner *scanner,
+ unsigned N, pj_bool_t skip_ws)
+{
+ if (scanner->curptr + N > scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return;
+ }
+
+ scanner->curptr += N;
+
+ if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && skip_ws) {
+ pj_scan_skip_whitespace(scanner);
+ }
+}
+
+
+PJ_DEF(int) pj_scan_strcmp( pj_scanner *scanner, const char *s, int len)
+{
+ if (scanner->curptr + len > scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return -1;
+ }
+ return strncmp(scanner->curptr, s, len);
+}
+
+
+PJ_DEF(int) pj_scan_stricmp( pj_scanner *scanner, const char *s, int len)
+{
+ if (scanner->curptr + len > scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return -1;
+ }
+ return pj_ansi_strnicmp(scanner->curptr, s, len);
+}
+
+PJ_DEF(int) pj_scan_stricmp_alnum( pj_scanner *scanner, const char *s,
+ int len)
+{
+ if (scanner->curptr + len > scanner->end) {
+ pj_scan_syntax_err(scanner);
+ return -1;
+ }
+ return strnicmp_alnum(scanner->curptr, s, len);
+}
+
+PJ_DEF(void) pj_scan_save_state( const pj_scanner *scanner,
+ pj_scan_state *state)
+{
+ state->curptr = scanner->curptr;
+ state->line = scanner->line;
+ state->start_line = scanner->start_line;
+}
+
+
+PJ_DEF(void) pj_scan_restore_state( pj_scanner *scanner,
+ pj_scan_state *state)
+{
+ scanner->curptr = state->curptr;
+ scanner->line = state->line;
+ scanner->start_line = state->start_line;
+}
+
+
diff --git a/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c b/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c
new file mode 100644
index 0000000..50754f5
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/scanner_cis_bitwise.c
@@ -0,0 +1,69 @@
+/* $Id: scanner_cis_bitwise.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/*
+ * THIS FILE IS INCLUDED BY scanner.c.
+ * DO NOT COMPILE THIS FILE ALONE!
+ */
+
+PJ_DEF(void) pj_cis_buf_init( pj_cis_buf_t *cis_buf)
+{
+ pj_bzero(cis_buf->cis_buf, sizeof(cis_buf->cis_buf));
+ cis_buf->use_mask = 0;
+}
+
+PJ_DEF(pj_status_t) pj_cis_init(pj_cis_buf_t *cis_buf, pj_cis_t *cis)
+{
+ unsigned i;
+
+ cis->cis_buf = cis_buf->cis_buf;
+
+ for (i=0; i<PJ_CIS_MAX_INDEX; ++i) {
+ if ((cis_buf->use_mask & (1 << i)) == 0) {
+ cis->cis_id = i;
+ cis_buf->use_mask |= (1 << i);
+ return PJ_SUCCESS;
+ }
+ }
+
+ cis->cis_id = PJ_CIS_MAX_INDEX;
+ return PJ_ETOOMANY;
+}
+
+PJ_DEF(pj_status_t) pj_cis_dup( pj_cis_t *new_cis, pj_cis_t *existing)
+{
+ pj_status_t status;
+ unsigned i;
+
+ /* Warning: typecasting here! */
+ status = pj_cis_init((pj_cis_buf_t*)existing->cis_buf, new_cis);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ for (i=0; i<256; ++i) {
+ if (PJ_CIS_ISSET(existing, i))
+ PJ_CIS_SET(new_cis, i);
+ else
+ PJ_CIS_CLR(new_cis, i);
+ }
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjlib-util/src/pjlib-util/scanner_cis_uint.c b/pjlib-util/src/pjlib-util/scanner_cis_uint.c
new file mode 100644
index 0000000..865234a
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/scanner_cis_uint.c
@@ -0,0 +1,46 @@
+/* $Id: scanner_cis_uint.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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
+ */
+
+/*
+ * THIS FILE IS INCLUDED BY scanner.c.
+ * DO NOT COMPILE THIS FILE ALONE!
+ */
+
+
+PJ_DEF(void) pj_cis_buf_init( pj_cis_buf_t *cis_buf)
+{
+ /* Do nothing. */
+ PJ_UNUSED_ARG(cis_buf);
+}
+
+PJ_DEF(pj_status_t) pj_cis_init(pj_cis_buf_t *cis_buf, pj_cis_t *cis)
+{
+ PJ_UNUSED_ARG(cis_buf);
+ pj_bzero(cis->cis_buf, sizeof(cis->cis_buf));
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_cis_dup( pj_cis_t *new_cis, pj_cis_t *existing)
+{
+ pj_memcpy(new_cis, existing, sizeof(pj_cis_t));
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjlib-util/src/pjlib-util/sha1.c b/pjlib-util/src/pjlib-util/sha1.c
new file mode 100644
index 0000000..e006b1a
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/sha1.c
@@ -0,0 +1,262 @@
+/* $Id: sha1.c 3549 2011-05-05 05:10:06Z nanang $ */
+/*
+ * Modified 2/07
+ * By Benny Prijono <benny@prijono.org>
+ * Still 100% Public Domain
+ *
+ * This is the implementation of SHA-1 encryption algorithm based on
+ * Steve Reid work. Modified to work with PJLIB.
+ */
+
+/*
+SHA-1 in C
+By Steve Reid <sreid@sea-to-sky.net>
+100% Public Domain
+
+-----------------
+Modified 7/98
+By James H. Brown <jbrown@burgoyne.com>
+Still 100% Public Domain
+
+Corrected a problem which generated improper hash values on 16 bit machines
+Routine SHA1Update changed from
+ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
+len)
+to
+ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
+long len)
+
+The 'len' parameter was declared an int which works fine on 32 bit machines.
+However, on 16 bit machines an int is too small for the shifts being done
+against
+it. This caused the hash function to generate incorrect values if len was
+greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
+
+Since the file IO in main() reads 16K at a time, any file 8K or larger would
+be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million
+"a"s).
+
+I also changed the declaration of variables i & j in SHA1Update to
+unsigned long from unsigned int for the same reason.
+
+These changes should make no difference to any 32 bit implementations since
+an
+int and a long are the same size in those environments.
+
+--
+I also corrected a few compiler warnings generated by Borland C.
+1. Added #include <process.h> for exit() prototype
+2. Removed unused variable 'j' in SHA1Final
+3. Changed exit(0) to return(0) at end of main.
+
+ALL changes I made can be located by searching for comments containing 'JHB'
+-----------------
+Modified 8/98
+By Steve Reid <sreid@sea-to-sky.net>
+Still 100% public domain
+
+1- Removed #include <process.h> and used return() instead of exit()
+2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
+3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
+
+-----------------
+Modified 4/01
+By Saul Kravitz <Saul.Kravitz@celera.com>
+Still 100% PD
+Modified to run on Compaq Alpha hardware.
+
+-----------------
+Modified 07/2002
+By Ralph Giles <giles@ghostscript.com>
+Still 100% public domain
+modified for use with stdint types, autoconf
+code cleanup, removed attribution comments
+switched SHA1Final() argument order for consistency
+use SHA1_ prefix for public api
+move public api to sha1.h
+*/
+
+/*
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+ A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+ 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+/* #define SHA1HANDSOFF */
+/* blp:
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include "os_types.h"
+
+#include "sha1.h"
+*/
+#include <pjlib-util/sha1.h>
+#include <pj/string.h>
+
+#undef SHA1HANDSOFF
+
+
+static void SHA1_Transform(pj_uint32_t state[5], pj_uint8_t buffer[64]);
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+/* FIXME: can we do this in an endian-proof way? */
+/* #ifdef WORDS_BIGENDIAN */
+#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN != 0
+#define blk0(i) block->l[i]
+#else
+#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+ |(rol(block->l[i],8)&0x00FF00FF))
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+static void SHA1_Transform(pj_uint32_t state[5], pj_uint8_t buffer[64])
+{
+ pj_uint32_t a, b, c, d, e;
+ typedef union {
+ pj_uint8_t c[64];
+ pj_uint32_t l[16];
+ } CHAR64LONG16;
+ CHAR64LONG16* block;
+
+#ifdef SHA1HANDSOFF
+ static pj_uint8_t workspace[64];
+ block = (CHAR64LONG16*)workspace;
+ pj_memcpy(block, buffer, 64);
+#else
+ block = (CHAR64LONG16*)buffer;
+#endif
+
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+}
+
+
+/* SHA1Init - Initialize new context */
+PJ_DEF(void) pj_sha1_init(pj_sha1_context* context)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/* Run your data through this. */
+PJ_DEF(void) pj_sha1_update(pj_sha1_context* context,
+ const pj_uint8_t* data, const pj_size_t len)
+{
+ pj_size_t i, j;
+
+ j = (context->count[0] >> 3) & 63;
+ if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++;
+ context->count[1] += (len >> 29);
+ if ((j + len) > 63) {
+ pj_memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1_Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64) {
+ pj_uint8_t tmp[64];
+ pj_memcpy(tmp, data + i, 64);
+ SHA1_Transform(context->state, tmp);
+ }
+ j = 0;
+ }
+ else i = 0;
+ pj_memcpy(&context->buffer[j], &data[i], len - i);
+
+}
+
+
+/* Add padding and return the message digest. */
+PJ_DEF(void) pj_sha1_final(pj_sha1_context* context,
+ pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE])
+{
+ pj_uint32_t i;
+ pj_uint8_t finalcount[8];
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
+ }
+ pj_sha1_update(context, (pj_uint8_t *)"\200", 1);
+ while ((context->count[0] & 504) != 448) {
+ pj_sha1_update(context, (pj_uint8_t *)"\0", 1);
+ }
+ pj_sha1_update(context, finalcount, 8); /* Should cause a SHA1_Transform() */
+ for (i = 0; i < PJ_SHA1_DIGEST_SIZE; i++) {
+ digest[i] = (pj_uint8_t)
+ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+ }
+
+ /* Wipe variables */
+ i = 0;
+ pj_memset(context->buffer, 0, 64);
+ pj_memset(context->state, 0, 20);
+ pj_memset(context->count, 0, 8);
+ pj_memset(finalcount, 0, 8); /* SWR */
+
+#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite its own static vars */
+ SHA1_Transform(context->state, context->buffer);
+#endif
+}
+
diff --git a/pjlib-util/src/pjlib-util/srv_resolver.c b/pjlib-util/src/pjlib-util/srv_resolver.c
new file mode 100644
index 0000000..8f133cf
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/srv_resolver.c
@@ -0,0 +1,674 @@
+/* $Id: srv_resolver.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/srv_resolver.h>
+#include <pjlib-util/errno.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "srv_resolver.c"
+
+#define ADDR_MAX_COUNT PJ_DNS_MAX_IP_IN_A_REC
+
+struct common
+{
+ pj_dns_type type; /**< Type of this structure.*/
+};
+
+struct srv_target
+{
+ struct common common;
+ pj_dns_srv_async_query *parent;
+ pj_str_t target_name;
+ pj_dns_async_query *q_a;
+ char target_buf[PJ_MAX_HOSTNAME];
+ pj_str_t cname;
+ char cname_buf[PJ_MAX_HOSTNAME];
+ unsigned port;
+ unsigned priority;
+ unsigned weight;
+ unsigned sum;
+ unsigned addr_cnt;
+ pj_in_addr addr[ADDR_MAX_COUNT];
+};
+
+struct pj_dns_srv_async_query
+{
+ struct common common;
+ char *objname;
+
+ pj_dns_type dns_state; /**< DNS type being resolved. */
+ pj_dns_resolver *resolver; /**< Resolver SIP instance. */
+ void *token;
+ pj_dns_async_query *q_srv;
+ pj_dns_srv_resolver_cb *cb;
+ pj_status_t last_error;
+
+ /* Original request: */
+ unsigned option;
+ pj_str_t full_name;
+ pj_str_t domain_part;
+ pj_uint16_t def_port;
+
+ /* SRV records and their resolved IP addresses: */
+ unsigned srv_cnt;
+ struct srv_target srv[PJ_DNS_SRV_MAX_ADDR];
+
+ /* Number of hosts in SRV records that the IP address has been resolved */
+ unsigned host_resolved;
+
+};
+
+
+/* Async resolver callback, forward decl. */
+static void dns_callback(void *user_data,
+ pj_status_t status,
+ pj_dns_parsed_packet *pkt);
+
+
+
+/*
+ * The public API to invoke DNS SRV resolution.
+ */
+PJ_DEF(pj_status_t) pj_dns_srv_resolve( const pj_str_t *domain_name,
+ const pj_str_t *res_name,
+ unsigned def_port,
+ pj_pool_t *pool,
+ pj_dns_resolver *resolver,
+ unsigned option,
+ void *token,
+ pj_dns_srv_resolver_cb *cb,
+ pj_dns_srv_async_query **p_query)
+{
+ int len;
+ pj_str_t target_name;
+ pj_dns_srv_async_query *query_job;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(domain_name && domain_name->slen &&
+ res_name && res_name->slen &&
+ pool && resolver && cb, PJ_EINVAL);
+
+ /* Build full name */
+ len = domain_name->slen + res_name->slen + 2;
+ target_name.ptr = (char*) pj_pool_alloc(pool, len);
+ pj_strcpy(&target_name, res_name);
+ if (res_name->ptr[res_name->slen-1] != '.')
+ pj_strcat2(&target_name, ".");
+ len = target_name.slen;
+ pj_strcat(&target_name, domain_name);
+ target_name.ptr[target_name.slen] = '\0';
+
+
+ /* Build the query_job state */
+ query_job = PJ_POOL_ZALLOC_T(pool, pj_dns_srv_async_query);
+ query_job->common.type = PJ_DNS_TYPE_SRV;
+ query_job->objname = target_name.ptr;
+ query_job->resolver = resolver;
+ query_job->token = token;
+ query_job->cb = cb;
+ query_job->option = option;
+ query_job->full_name = target_name;
+ query_job->domain_part.ptr = target_name.ptr + len;
+ query_job->domain_part.slen = target_name.slen - len;
+ query_job->def_port = (pj_uint16_t)def_port;
+
+ /* Start the asynchronous query_job */
+
+ query_job->dns_state = PJ_DNS_TYPE_SRV;
+
+ PJ_LOG(5, (query_job->objname,
+ "Starting async DNS %s query_job: target=%.*s:%d",
+ pj_dns_get_type_name(query_job->dns_state),
+ (int)target_name.slen, target_name.ptr,
+ def_port));
+
+ status = pj_dns_resolver_start_query(resolver, &target_name,
+ query_job->dns_state, 0,
+ &dns_callback,
+ query_job, &query_job->q_srv);
+ if (status==PJ_SUCCESS && p_query)
+ *p_query = query_job;
+
+ return status;
+}
+
+
+/*
+ * Cancel pending query.
+ */
+PJ_DEF(pj_status_t) pj_dns_srv_cancel_query(pj_dns_srv_async_query *query,
+ pj_bool_t notify)
+{
+ pj_bool_t has_pending = PJ_FALSE;
+ unsigned i;
+
+ if (query->q_srv) {
+ pj_dns_resolver_cancel_query(query->q_srv, PJ_FALSE);
+ query->q_srv = NULL;
+ has_pending = PJ_TRUE;
+ }
+
+ for (i=0; i<query->srv_cnt; ++i) {
+ struct srv_target *srv = &query->srv[i];
+ if (srv->q_a) {
+ pj_dns_resolver_cancel_query(srv->q_a, PJ_FALSE);
+ srv->q_a = NULL;
+ has_pending = PJ_TRUE;
+ }
+ }
+
+ if (has_pending && notify && query->cb) {
+ (*query->cb)(query->token, PJ_ECANCELLED, NULL);
+ }
+
+ return has_pending? PJ_SUCCESS : PJ_EINVALIDOP;
+}
+
+
+#define SWAP(type,ptr1,ptr2) if (ptr1 != ptr2) { \
+ type tmp; \
+ pj_memcpy(&tmp, ptr1, sizeof(type)); \
+ pj_memcpy(ptr1, ptr2, sizeof(type)); \
+ (ptr1)->target_name.ptr = (ptr1)->target_buf;\
+ pj_memcpy(ptr2, &tmp, sizeof(type)); \
+ (ptr2)->target_name.ptr = (ptr2)->target_buf;\
+ } else {}
+
+
+/* Build server entries in the query_job based on received SRV response */
+static void build_server_entries(pj_dns_srv_async_query *query_job,
+ pj_dns_parsed_packet *response)
+{
+ unsigned i;
+
+ /* Save the Resource Records in DNS answer into SRV targets. */
+ query_job->srv_cnt = 0;
+ for (i=0; i<response->hdr.anscount &&
+ query_job->srv_cnt < PJ_DNS_SRV_MAX_ADDR; ++i)
+ {
+ pj_dns_parsed_rr *rr = &response->ans[i];
+ struct srv_target *srv = &query_job->srv[query_job->srv_cnt];
+
+ if (rr->type != PJ_DNS_TYPE_SRV) {
+ PJ_LOG(4,(query_job->objname,
+ "Received non SRV answer for SRV query_job!"));
+ continue;
+ }
+
+ if (rr->rdata.srv.target.slen > PJ_MAX_HOSTNAME) {
+ PJ_LOG(4,(query_job->objname, "Hostname is too long!"));
+ continue;
+ }
+
+ /* Build the SRV entry for RR */
+ pj_bzero(srv, sizeof(*srv));
+ srv->target_name.ptr = srv->target_buf;
+ pj_strncpy(&srv->target_name, &rr->rdata.srv.target,
+ sizeof(srv->target_buf));
+ srv->port = rr->rdata.srv.port;
+ srv->priority = rr->rdata.srv.prio;
+ srv->weight = rr->rdata.srv.weight;
+
+ ++query_job->srv_cnt;
+ }
+
+ if (query_job->srv_cnt == 0) {
+ PJ_LOG(4,(query_job->objname,
+ "Could not find SRV record in DNS answer!"));
+ return;
+ }
+
+ /* First pass:
+ * order the entries based on priority.
+ */
+ for (i=0; i<query_job->srv_cnt-1; ++i) {
+ unsigned min = i, j;
+ for (j=i+1; j<query_job->srv_cnt; ++j) {
+ if (query_job->srv[j].priority < query_job->srv[min].priority)
+ min = j;
+ }
+ SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[min]);
+ }
+
+ /* Second pass:
+ * pick one host among hosts with the same priority, according
+ * to its weight. The idea is when one server fails, client should
+ * contact the next server with higher priority rather than contacting
+ * server with the same priority as the failed one.
+ *
+ * The algorithm for selecting server among servers with the same
+ * priority is described in RFC 2782.
+ */
+ for (i=0; i<query_job->srv_cnt; ++i) {
+ unsigned j, count=1, sum;
+
+ /* Calculate running sum for servers with the same priority */
+ sum = query_job->srv[i].sum = query_job->srv[i].weight;
+ for (j=i+1; j<query_job->srv_cnt &&
+ query_job->srv[j].priority == query_job->srv[i].priority; ++j)
+ {
+ sum += query_job->srv[j].weight;
+ query_job->srv[j].sum = sum;
+ ++count;
+ }
+
+ if (count > 1) {
+ unsigned r;
+
+ /* Elect one random number between zero and the total sum of
+ * weight (inclusive).
+ */
+ r = pj_rand() % (sum + 1);
+
+ /* Select the first server which running sum is greater than or
+ * equal to the random number.
+ */
+ for (j=i; j<i+count; ++j) {
+ if (query_job->srv[j].sum >= r)
+ break;
+ }
+
+ /* Must have selected one! */
+ pj_assert(j != i+count);
+
+ /* Put this entry in front (of entries with same priority) */
+ SWAP(struct srv_target, &query_job->srv[i], &query_job->srv[j]);
+
+ /* Remove all other entries (of the same priority) */
+ while (count > 1) {
+ pj_array_erase(query_job->srv, sizeof(struct srv_target),
+ query_job->srv_cnt, i+1);
+ --count;
+ --query_job->srv_cnt;
+ }
+ }
+ }
+
+ /* Since we've been moving around SRV entries, update the pointers
+ * in target_name.
+ */
+ for (i=0; i<query_job->srv_cnt; ++i) {
+ query_job->srv[i].target_name.ptr = query_job->srv[i].target_buf;
+ }
+
+ /* Check for Additional Info section if A records are available, and
+ * fill in the IP address (so that we won't need to resolve the A
+ * record with another DNS query_job).
+ */
+ for (i=0; i<response->hdr.arcount; ++i) {
+ pj_dns_parsed_rr *rr = &response->arr[i];
+ unsigned j;
+
+ if (rr->type != PJ_DNS_TYPE_A)
+ continue;
+
+ /* Yippeaiyee!! There is an "A" record!
+ * Update the IP address of the corresponding SRV record.
+ */
+ for (j=0; j<query_job->srv_cnt; ++j) {
+ if (pj_stricmp(&rr->name, &query_job->srv[j].target_name)==0) {
+ unsigned cnt = query_job->srv[j].addr_cnt;
+ query_job->srv[j].addr[cnt].s_addr = rr->rdata.a.ip_addr.s_addr;
+ /* Only increment host_resolved once per SRV record */
+ if (query_job->srv[j].addr_cnt == 0)
+ ++query_job->host_resolved;
+ ++query_job->srv[j].addr_cnt;
+ break;
+ }
+ }
+
+ /* Not valid message; SRV entry might have been deleted in
+ * server selection process.
+ */
+ /*
+ if (j == query_job->srv_cnt) {
+ PJ_LOG(4,(query_job->objname,
+ "Received DNS SRV answer with A record, but "
+ "couldn't find matching name (name=%.*s)",
+ (int)rr->name.slen,
+ rr->name.ptr));
+ }
+ */
+ }
+
+ /* Rescan again the name specified in the SRV record to see if IP
+ * address is specified as the target name (unlikely, but well, who
+ * knows..).
+ */
+ for (i=0; i<query_job->srv_cnt; ++i) {
+ pj_in_addr addr;
+
+ if (query_job->srv[i].addr_cnt != 0) {
+ /* IP address already resolved */
+ continue;
+ }
+
+ if (pj_inet_aton(&query_job->srv[i].target_name, &addr) != 0) {
+ query_job->srv[i].addr[query_job->srv[i].addr_cnt++] = addr;
+ ++query_job->host_resolved;
+ }
+ }
+
+ /* Print resolved entries to the log */
+ PJ_LOG(5,(query_job->objname,
+ "SRV query_job for %.*s completed, "
+ "%d of %d total entries selected%c",
+ (int)query_job->full_name.slen,
+ query_job->full_name.ptr,
+ query_job->srv_cnt,
+ response->hdr.anscount,
+ (query_job->srv_cnt ? ':' : ' ')));
+
+ for (i=0; i<query_job->srv_cnt; ++i) {
+ const char *addr;
+
+ if (query_job->srv[i].addr_cnt != 0)
+ addr = pj_inet_ntoa(query_job->srv[i].addr[0]);
+ else
+ addr = "-";
+
+ PJ_LOG(5,(query_job->objname,
+ " %d: SRV %d %d %d %.*s (%s)",
+ i, query_job->srv[i].priority,
+ query_job->srv[i].weight,
+ query_job->srv[i].port,
+ (int)query_job->srv[i].target_name.slen,
+ query_job->srv[i].target_name.ptr,
+ addr));
+ }
+}
+
+
+/* Start DNS A record queries for all SRV records in the query_job structure */
+static pj_status_t resolve_hostnames(pj_dns_srv_async_query *query_job)
+{
+ unsigned i;
+ pj_status_t err=PJ_SUCCESS, status;
+
+ query_job->dns_state = PJ_DNS_TYPE_A;
+ for (i=0; i<query_job->srv_cnt; ++i) {
+ struct srv_target *srv = &query_job->srv[i];
+
+ PJ_LOG(5, (query_job->objname,
+ "Starting async DNS A query_job for %.*s",
+ (int)srv->target_name.slen,
+ srv->target_name.ptr));
+
+ srv->common.type = PJ_DNS_TYPE_A;
+ srv->parent = query_job;
+
+ status = pj_dns_resolver_start_query(query_job->resolver,
+ &srv->target_name,
+ PJ_DNS_TYPE_A, 0,
+ &dns_callback,
+ srv, &srv->q_a);
+ if (status != PJ_SUCCESS) {
+ query_job->host_resolved++;
+ err = status;
+ }
+ }
+
+ return (query_job->host_resolved == query_job->srv_cnt) ? err : PJ_SUCCESS;
+}
+
+/*
+ * This callback is called by PJLIB-UTIL DNS resolver when asynchronous
+ * query_job has completed (successfully or with error).
+ */
+static void dns_callback(void *user_data,
+ pj_status_t status,
+ pj_dns_parsed_packet *pkt)
+{
+ struct common *common = (struct common*) user_data;
+ pj_dns_srv_async_query *query_job;
+ struct srv_target *srv = NULL;
+ unsigned i;
+
+ if (common->type == PJ_DNS_TYPE_SRV) {
+ query_job = (pj_dns_srv_async_query*) common;
+ srv = NULL;
+ } else if (common->type == PJ_DNS_TYPE_A) {
+ srv = (struct srv_target*) common;
+ query_job = srv->parent;
+ } else {
+ pj_assert(!"Unexpected user data!");
+ return;
+ }
+
+ /* Proceed to next stage */
+ if (query_job->dns_state == PJ_DNS_TYPE_SRV) {
+
+ /* We are getting SRV response */
+
+ query_job->q_srv = NULL;
+
+ if (status == PJ_SUCCESS && pkt->hdr.anscount != 0) {
+ /* Got SRV response, build server entry. If A records are available
+ * in additional records section of the DNS response, save them too.
+ */
+ build_server_entries(query_job, pkt);
+
+ } else if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ /* Update query_job last error */
+ query_job->last_error = status;
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(query_job->objname,
+ "DNS SRV resolution failed for %.*s: %s",
+ (int)query_job->full_name.slen,
+ query_job->full_name.ptr,
+ errmsg));
+
+ /* Trigger error when fallback is disabled */
+ if ((query_job->option &
+ (PJ_DNS_SRV_FALLBACK_A | PJ_DNS_SRV_FALLBACK_AAAA)) == 0)
+ {
+ goto on_error;
+ }
+ }
+
+ /* If we can't build SRV record, assume the original target is
+ * an A record and resolve with DNS A resolution.
+ */
+ if (query_job->srv_cnt == 0) {
+ /* Looks like we aren't getting any SRV responses.
+ * Resolve the original target as A record by creating a
+ * single "dummy" srv record and start the hostname resolution.
+ */
+ PJ_LOG(4, (query_job->objname,
+ "DNS SRV resolution failed for %.*s, trying "
+ "resolving A record for %.*s",
+ (int)query_job->full_name.slen,
+ query_job->full_name.ptr,
+ (int)query_job->domain_part.slen,
+ query_job->domain_part.ptr));
+
+ /* Create a "dummy" srv record using the original target */
+ i = query_job->srv_cnt++;
+ pj_bzero(&query_job->srv[i], sizeof(query_job->srv[i]));
+ query_job->srv[i].target_name = query_job->domain_part;
+ query_job->srv[i].priority = 0;
+ query_job->srv[i].weight = 0;
+ query_job->srv[i].port = query_job->def_port;
+ }
+
+
+ /* Resolve server hostnames (DNS A record) for hosts which don't have
+ * A record yet.
+ */
+ if (query_job->host_resolved != query_job->srv_cnt) {
+ status = resolve_hostnames(query_job);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Must return now. Callback may have been called and query_job
+ * may have been destroyed.
+ */
+ return;
+ }
+
+ } else if (query_job->dns_state == PJ_DNS_TYPE_A) {
+
+ /* Clear the outstanding job */
+ srv->q_a = NULL;
+
+ /* Check that we really have answer */
+ if (status==PJ_SUCCESS && pkt->hdr.anscount != 0) {
+ pj_dns_a_record rec;
+
+ /* Parse response */
+ status = pj_dns_parse_a_response(pkt, &rec);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pj_assert(rec.addr_count != 0);
+
+ /* Update CNAME alias, if present. */
+ if (rec.alias.slen) {
+ pj_assert(rec.alias.slen <= (int)sizeof(srv->cname_buf));
+ srv->cname.ptr = srv->cname_buf;
+ pj_strcpy(&srv->cname, &rec.alias);
+ } else {
+ srv->cname.slen = 0;
+ }
+
+ /* Update IP address of the corresponding hostname or CNAME */
+ if (srv->addr_cnt < ADDR_MAX_COUNT) {
+ srv->addr[srv->addr_cnt++].s_addr = rec.addr[0].s_addr;
+
+ PJ_LOG(5,(query_job->objname,
+ "DNS A for %.*s: %s",
+ (int)srv->target_name.slen,
+ srv->target_name.ptr,
+ pj_inet_ntoa(rec.addr[0])));
+ }
+
+ /* Check for multiple IP addresses */
+ for (i=1; i<rec.addr_count && srv->addr_cnt < ADDR_MAX_COUNT; ++i)
+ {
+ srv->addr[srv->addr_cnt++].s_addr = rec.addr[i].s_addr;
+
+ PJ_LOG(5,(query_job->objname,
+ "Additional DNS A for %.*s: %s",
+ (int)srv->target_name.slen,
+ srv->target_name.ptr,
+ pj_inet_ntoa(rec.addr[i])));
+ }
+
+ } else if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ /* Update last error */
+ query_job->last_error = status;
+
+ /* Log error */
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(query_job->objname, "DNS A record resolution failed: %s",
+ errmsg));
+ }
+
+ ++query_job->host_resolved;
+
+ } else {
+ pj_assert(!"Unexpected state!");
+ query_job->last_error = status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Check if all hosts have been resolved */
+ if (query_job->host_resolved == query_job->srv_cnt) {
+ /* Got all answers, build server addresses */
+ pj_dns_srv_record srv_rec;
+
+ srv_rec.count = 0;
+ for (i=0; i<query_job->srv_cnt; ++i) {
+ unsigned j;
+ struct srv_target *srv = &query_job->srv[i];
+
+ srv_rec.entry[srv_rec.count].priority = srv->priority;
+ srv_rec.entry[srv_rec.count].weight = srv->weight;
+ srv_rec.entry[srv_rec.count].port = (pj_uint16_t)srv->port ;
+
+ srv_rec.entry[srv_rec.count].server.name = srv->target_name;
+ srv_rec.entry[srv_rec.count].server.alias = srv->cname;
+ srv_rec.entry[srv_rec.count].server.addr_count = 0;
+
+ pj_assert(srv->addr_cnt <= PJ_DNS_MAX_IP_IN_A_REC);
+
+ for (j=0; j<srv->addr_cnt; ++j) {
+ srv_rec.entry[srv_rec.count].server.addr[j].s_addr =
+ srv->addr[j].s_addr;
+ ++srv_rec.entry[srv_rec.count].server.addr_count;
+ }
+
+ if (srv->addr_cnt > 0) {
+ ++srv_rec.count;
+ if (srv_rec.count == PJ_DNS_SRV_MAX_ADDR)
+ break;
+ }
+ }
+
+ PJ_LOG(5,(query_job->objname,
+ "Server resolution complete, %d server entry(s) found",
+ srv_rec.count));
+
+
+ if (srv_rec.count > 0)
+ status = PJ_SUCCESS;
+ else {
+ status = query_job->last_error;
+ if (status == PJ_SUCCESS)
+ status = PJLIB_UTIL_EDNSNOANSWERREC;
+ }
+
+ /* Call the callback */
+ (*query_job->cb)(query_job->token, status, &srv_rec);
+ }
+
+
+ return;
+
+on_error:
+ /* Check for failure */
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ PJ_UNUSED_ARG(errmsg);
+ PJ_LOG(4,(query_job->objname,
+ "DNS %s record resolution error for '%.*s'."
+ " Err=%d (%s)",
+ pj_dns_get_type_name(query_job->dns_state),
+ (int)query_job->domain_part.slen,
+ query_job->domain_part.ptr,
+ status,
+ pj_strerror(status,errmsg,sizeof(errmsg)).ptr));
+ (*query_job->cb)(query_job->token, status, NULL);
+ return;
+ }
+}
+
+
diff --git a/pjlib-util/src/pjlib-util/string.c b/pjlib-util/src/pjlib-util/string.c
new file mode 100644
index 0000000..e3a7f91
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/string.c
@@ -0,0 +1,110 @@
+/* $Id: string.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/string.h>
+#include <pj/ctype.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+
+PJ_DEF(pj_str_t) pj_str_unescape( pj_pool_t *pool, const pj_str_t *src_str)
+{
+ char *src = src_str->ptr;
+ char *end = src + src_str->slen;
+ pj_str_t dst_str;
+ char *dst;
+
+ if (pj_strchr(src_str, '%')==NULL)
+ return *src_str;
+
+ dst = dst_str.ptr = (char*) pj_pool_alloc(pool, src_str->slen);
+
+ while (src != end) {
+ if (*src == '%' && src < end-2 && pj_isxdigit(*(src+1)) &&
+ pj_isxdigit(*(src+2)))
+ {
+ *dst = (pj_uint8_t) ((pj_hex_digit_to_val(*(src+1)) << 4) +
+ pj_hex_digit_to_val(*(src+2)));
+ ++dst;
+ src += 3;
+ } else {
+ *dst++ = *src++;
+ }
+ }
+ dst_str.slen = dst - dst_str.ptr;
+ return dst_str;
+}
+
+PJ_DEF(pj_str_t*) pj_strcpy_unescape(pj_str_t *dst_str,
+ const pj_str_t *src_str)
+{
+ const char *src = src_str->ptr;
+ const char *end = src + src_str->slen;
+ char *dst = dst_str->ptr;
+
+ while (src != end) {
+ if (*src == '%' && src < end-2) {
+ *dst = (pj_uint8_t) ((pj_hex_digit_to_val(*(src+1)) << 4) +
+ pj_hex_digit_to_val(*(src+2)));
+ ++dst;
+ src += 3;
+ } else {
+ *dst++ = *src++;
+ }
+ }
+ dst_str->slen = dst - dst_str->ptr;
+ return dst_str;
+}
+
+PJ_DEF(pj_ssize_t) pj_strncpy2_escape( char *dst_str, const pj_str_t *src_str,
+ pj_ssize_t max, const pj_cis_t *unres)
+{
+ const char *src = src_str->ptr;
+ const char *src_end = src + src_str->slen;
+ char *dst = dst_str;
+ char *dst_end = dst + max;
+
+ if (max < src_str->slen)
+ return -1;
+
+ while (src != src_end && dst != dst_end) {
+ if (pj_cis_match(unres, *src)) {
+ *dst++ = *src++;
+ } else {
+ if (dst < dst_end-2) {
+ *dst++ = '%';
+ pj_val_to_hex_digit(*src, dst);
+ dst+=2;
+ ++src;
+ } else {
+ break;
+ }
+ }
+ }
+
+ return src==src_end ? dst-dst_str : -1;
+}
+
+PJ_DEF(pj_str_t*) pj_strncpy_escape(pj_str_t *dst_str,
+ const pj_str_t *src_str,
+ pj_ssize_t max, const pj_cis_t *unres)
+{
+ dst_str->slen = pj_strncpy2_escape(dst_str->ptr, src_str, max, unres);
+ return dst_str->slen < 0 ? NULL : dst_str;
+}
+
diff --git a/pjlib-util/src/pjlib-util/stun_simple.c b/pjlib-util/src/pjlib-util/stun_simple.c
new file mode 100644
index 0000000..846e6af
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/stun_simple.c
@@ -0,0 +1,131 @@
+/* $Id: stun_simple.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/stun_simple.h>
+#include <pjlib-util/errno.h>
+#include <pj/pool.h>
+#include <pj/log.h>
+#include <pj/sock.h>
+#include <pj/os.h>
+
+#define THIS_FILE "stun_simple.c"
+
+PJ_DEF(pj_status_t) pjstun_create_bind_req( pj_pool_t *pool,
+ void **msg, pj_size_t *len,
+ pj_uint32_t id_hi,
+ pj_uint32_t id_lo)
+{
+ pjstun_msg_hdr *hdr;
+
+ PJ_CHECK_STACK();
+
+
+ hdr = PJ_POOL_ZALLOC_T(pool, pjstun_msg_hdr);
+ if (!hdr)
+ return PJ_ENOMEM;
+
+ hdr->type = pj_htons(PJSTUN_BINDING_REQUEST);
+ hdr->tsx[2] = pj_htonl(id_hi);
+ hdr->tsx[3] = pj_htonl(id_lo);
+ *msg = hdr;
+ *len = sizeof(pjstun_msg_hdr);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjstun_parse_msg( void *buf, pj_size_t len,
+ pjstun_msg *msg)
+{
+ pj_uint16_t msg_type, msg_len;
+ char *p_attr;
+
+ PJ_CHECK_STACK();
+
+ msg->hdr = (pjstun_msg_hdr*)buf;
+ msg_type = pj_ntohs(msg->hdr->type);
+
+ switch (msg_type) {
+ case PJSTUN_BINDING_REQUEST:
+ case PJSTUN_BINDING_RESPONSE:
+ case PJSTUN_BINDING_ERROR_RESPONSE:
+ case PJSTUN_SHARED_SECRET_REQUEST:
+ case PJSTUN_SHARED_SECRET_RESPONSE:
+ case PJSTUN_SHARED_SECRET_ERROR_RESPONSE:
+ break;
+ default:
+ PJ_LOG(4,(THIS_FILE, "Error: unknown msg type %d", msg_type));
+ return PJLIB_UTIL_ESTUNINMSGTYPE;
+ }
+
+ msg_len = pj_ntohs(msg->hdr->length);
+ if (msg_len != len - sizeof(pjstun_msg_hdr)) {
+ PJ_LOG(4,(THIS_FILE, "Error: invalid msg_len %d (expecting %d)",
+ msg_len, len - sizeof(pjstun_msg_hdr)));
+ return PJLIB_UTIL_ESTUNINMSGLEN;
+ }
+
+ msg->attr_count = 0;
+ p_attr = (char*)buf + sizeof(pjstun_msg_hdr);
+
+ while (msg_len > 0) {
+ pjstun_attr_hdr **attr = &msg->attr[msg->attr_count];
+ pj_uint32_t len;
+ pj_uint16_t attr_type;
+
+ *attr = (pjstun_attr_hdr*)p_attr;
+ len = pj_ntohs((pj_uint16_t) ((*attr)->length)) + sizeof(pjstun_attr_hdr);
+ len = (len + 3) & ~3;
+
+ if (msg_len < len) {
+ PJ_LOG(4,(THIS_FILE, "Error: length mismatch in attr %d",
+ msg->attr_count));
+ return PJLIB_UTIL_ESTUNINATTRLEN;
+ }
+
+ attr_type = pj_ntohs((*attr)->type);
+ if (attr_type > PJSTUN_ATTR_REFLECTED_FROM &&
+ attr_type != PJSTUN_ATTR_XOR_MAPPED_ADDR)
+ {
+ PJ_LOG(5,(THIS_FILE, "Warning: unknown attr type %x in attr %d. "
+ "Attribute was ignored.",
+ attr_type, msg->attr_count));
+ }
+
+ msg_len = (pj_uint16_t)(msg_len - len);
+ p_attr += len;
+ ++msg->attr_count;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(void*) pjstun_msg_find_attr( pjstun_msg *msg, pjstun_attr_type t)
+{
+ int i;
+
+ PJ_CHECK_STACK();
+
+ for (i=0; i<msg->attr_count; ++i) {
+ pjstun_attr_hdr *attr = msg->attr[i];
+ if (pj_ntohs(attr->type) == t)
+ return attr;
+ }
+
+ return 0;
+}
diff --git a/pjlib-util/src/pjlib-util/stun_simple_client.c b/pjlib-util/src/pjlib-util/stun_simple_client.c
new file mode 100644
index 0000000..345d121
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/stun_simple_client.c
@@ -0,0 +1,335 @@
+/* $Id: stun_simple_client.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/stun_simple.h>
+#include <pjlib-util/errno.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/sock_select.h>
+#include <pj/string.h>
+
+
+enum { MAX_REQUEST = 4 };
+static int stun_timer[] = {500, 500, 500, 500 };
+#define STUN_MAGIC 0x2112A442
+
+#define THIS_FILE "stun_client.c"
+#define LOG_ADDR(addr) pj_inet_ntoa(addr.sin_addr), pj_ntohs(addr.sin_port)
+
+#define TRACE_(x) PJ_LOG(6,x)
+
+PJ_DEF(pj_status_t) pjstun_get_mapped_addr( pj_pool_factory *pf,
+ int sock_cnt, pj_sock_t sock[],
+ const pj_str_t *srv1, int port1,
+ const pj_str_t *srv2, int port2,
+ pj_sockaddr_in mapped_addr[])
+{
+ unsigned srv_cnt;
+ pj_sockaddr_in srv_addr[2];
+ int i, send_cnt = 0, nfds;
+ pj_pool_t *pool;
+ struct query_rec {
+ struct {
+ pj_uint32_t mapped_addr;
+ pj_uint32_t mapped_port;
+ } srv[2];
+ } *rec;
+ void *out_msg;
+ pj_size_t out_msg_len;
+ int wait_resp = 0;
+ pj_status_t status;
+
+ PJ_CHECK_STACK();
+
+ TRACE_((THIS_FILE, "Entering pjstun_get_mapped_addr()"));
+
+ /* Create pool. */
+ pool = pj_pool_create(pf, "stun%p", 400, 400, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+
+ /* Allocate client records */
+ rec = (struct query_rec*) pj_pool_calloc(pool, sock_cnt, sizeof(*rec));
+ if (!rec) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+
+ TRACE_((THIS_FILE, " Memory allocated."));
+
+ /* Create the outgoing BIND REQUEST message template */
+ status = pjstun_create_bind_req( pool, &out_msg, &out_msg_len,
+ pj_rand(), pj_rand());
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ TRACE_((THIS_FILE, " Binding request created."));
+
+ /* Resolve servers. */
+ status = pj_sockaddr_in_init(&srv_addr[0], srv1, (pj_uint16_t)port1);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ srv_cnt = 1;
+
+ if (srv2 && port2) {
+ status = pj_sockaddr_in_init(&srv_addr[1], srv2, (pj_uint16_t)port2);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (srv_addr[1].sin_addr.s_addr != srv_addr[0].sin_addr.s_addr &&
+ srv_addr[1].sin_port != srv_addr[0].sin_port)
+ {
+ srv_cnt++;
+ }
+ }
+
+ TRACE_((THIS_FILE, " Server initialized, using %d server(s)", srv_cnt));
+
+ /* Init mapped addresses to zero */
+ pj_memset(mapped_addr, 0, sock_cnt * sizeof(pj_sockaddr_in));
+
+ /* We need these many responses */
+ wait_resp = sock_cnt * srv_cnt;
+
+ TRACE_((THIS_FILE, " Done initialization."));
+
+#if defined(PJ_SELECT_NEEDS_NFDS) && PJ_SELECT_NEEDS_NFDS!=0
+ nfds = -1;
+ for (i=0; i<sock_cnt; ++i) {
+ if (sock[i] > nfds) {
+ nfds = sock[i];
+ }
+ }
+#else
+ nfds = PJ_IOQUEUE_MAX_HANDLES-1;
+#endif
+
+ /* Main retransmission loop. */
+ for (send_cnt=0; send_cnt<MAX_REQUEST; ++send_cnt) {
+ pj_time_val next_tx, now;
+ pj_fd_set_t r;
+ int select_rc;
+
+ PJ_FD_ZERO(&r);
+
+ /* Send messages to servers that has not given us response. */
+ for (i=0; i<sock_cnt && status==PJ_SUCCESS; ++i) {
+ unsigned j;
+ for (j=0; j<srv_cnt && status==PJ_SUCCESS; ++j) {
+ pjstun_msg_hdr *msg_hdr = (pjstun_msg_hdr*) out_msg;
+ pj_ssize_t sent_len;
+
+ if (rec[i].srv[j].mapped_port != 0)
+ continue;
+
+ /* Modify message so that we can distinguish response. */
+ msg_hdr->tsx[2] = pj_htonl(i);
+ msg_hdr->tsx[3] = pj_htonl(j);
+
+ /* Send! */
+ sent_len = out_msg_len;
+ status = pj_sock_sendto(sock[i], out_msg, &sent_len, 0,
+ (pj_sockaddr_t*)&srv_addr[j],
+ sizeof(pj_sockaddr_in));
+ }
+ }
+
+ /* All requests sent.
+ * The loop below will wait for responses until all responses have
+ * been received (i.e. wait_resp==0) or timeout occurs, which then
+ * we'll go to the next retransmission iteration.
+ */
+ TRACE_((THIS_FILE, " Request(s) sent, counter=%d", send_cnt));
+
+ /* Calculate time of next retransmission. */
+ pj_gettimeofday(&next_tx);
+ next_tx.sec += (stun_timer[send_cnt]/1000);
+ next_tx.msec += (stun_timer[send_cnt]%1000);
+ pj_time_val_normalize(&next_tx);
+
+ for (pj_gettimeofday(&now), select_rc=1;
+ status==PJ_SUCCESS && select_rc>=1 && wait_resp>0
+ && PJ_TIME_VAL_LT(now, next_tx);
+ pj_gettimeofday(&now))
+ {
+ pj_time_val timeout;
+
+ timeout = next_tx;
+ PJ_TIME_VAL_SUB(timeout, now);
+
+ for (i=0; i<sock_cnt; ++i) {
+ PJ_FD_SET(sock[i], &r);
+ }
+
+ select_rc = pj_sock_select(nfds+1, &r, NULL, NULL, &timeout);
+ TRACE_((THIS_FILE, " select() rc=%d", select_rc));
+ if (select_rc < 1)
+ continue;
+
+ for (i=0; i<sock_cnt; ++i) {
+ int sock_idx, srv_idx;
+ pj_ssize_t len;
+ pjstun_msg msg;
+ pj_sockaddr_in addr;
+ int addrlen = sizeof(addr);
+ pjstun_mapped_addr_attr *attr;
+ char recv_buf[128];
+
+ if (!PJ_FD_ISSET(sock[i], &r))
+ continue;
+
+ len = sizeof(recv_buf);
+ status = pj_sock_recvfrom( sock[i], recv_buf,
+ &len, 0,
+ (pj_sockaddr_t*)&addr,
+ &addrlen);
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_LOG(4,(THIS_FILE, "recvfrom() error ignored: %s",
+ pj_strerror(status, errmsg,sizeof(errmsg)).ptr));
+
+ /* Ignore non-PJ_SUCCESS status.
+ * It possible that other SIP entity is currently
+ * sending SIP request to us, and because SIP message
+ * is larger than STUN, we could get EMSGSIZE when
+ * we call recvfrom().
+ */
+ status = PJ_SUCCESS;
+ continue;
+ }
+
+ status = pjstun_parse_msg(recv_buf, len, &msg);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_LOG(4,(THIS_FILE, "STUN parsing error ignored: %s",
+ pj_strerror(status, errmsg,sizeof(errmsg)).ptr));
+
+ /* Also ignore non-successful parsing. This may not
+ * be STUN response at all. See the comment above.
+ */
+ status = PJ_SUCCESS;
+ continue;
+ }
+
+ sock_idx = pj_ntohl(msg.hdr->tsx[2]);
+ srv_idx = pj_ntohl(msg.hdr->tsx[3]);
+
+ if (sock_idx<0 || sock_idx>=sock_cnt || sock_idx!=i ||
+ srv_idx<0 || srv_idx>=2)
+ {
+ status = PJLIB_UTIL_ESTUNININDEX;
+ continue;
+ }
+
+ if (pj_ntohs(msg.hdr->type) != PJSTUN_BINDING_RESPONSE) {
+ status = PJLIB_UTIL_ESTUNNOBINDRES;
+ continue;
+ }
+
+ if (rec[sock_idx].srv[srv_idx].mapped_port != 0) {
+ /* Already got response */
+ continue;
+ }
+
+ /* From this part, we consider the packet as a valid STUN
+ * response for our request.
+ */
+ --wait_resp;
+
+ if (pjstun_msg_find_attr(&msg, PJSTUN_ATTR_ERROR_CODE) != NULL) {
+ status = PJLIB_UTIL_ESTUNRECVERRATTR;
+ continue;
+ }
+
+ attr = (pjstun_mapped_addr_attr*)
+ pjstun_msg_find_attr(&msg, PJSTUN_ATTR_MAPPED_ADDR);
+ if (!attr) {
+ attr = (pjstun_mapped_addr_attr*)
+ pjstun_msg_find_attr(&msg, PJSTUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr || attr->family != 1) {
+ status = PJLIB_UTIL_ESTUNNOMAP;
+ continue;
+ }
+ }
+
+ rec[sock_idx].srv[srv_idx].mapped_addr = attr->addr;
+ rec[sock_idx].srv[srv_idx].mapped_port = attr->port;
+ if (pj_ntohs(attr->hdr.type) == PJSTUN_ATTR_XOR_MAPPED_ADDR) {
+ rec[sock_idx].srv[srv_idx].mapped_addr ^= pj_htonl(STUN_MAGIC);
+ rec[sock_idx].srv[srv_idx].mapped_port ^= pj_htons(STUN_MAGIC >> 16);
+ }
+ }
+ }
+
+ /* The best scenario is if all requests have been replied.
+ * Then we don't need to go to the next retransmission iteration.
+ */
+ if (wait_resp <= 0)
+ break;
+ }
+
+ TRACE_((THIS_FILE, " All responses received, calculating result.."));
+
+ for (i=0; i<sock_cnt && status==PJ_SUCCESS; ++i) {
+ if (srv_cnt == 1) {
+ mapped_addr[i].sin_family = pj_AF_INET();
+ mapped_addr[i].sin_addr.s_addr = rec[i].srv[0].mapped_addr;
+ mapped_addr[i].sin_port = (pj_uint16_t)rec[i].srv[0].mapped_port;
+
+ if (rec[i].srv[0].mapped_addr == 0 || rec[i].srv[0].mapped_port == 0) {
+ status = PJLIB_UTIL_ESTUNNOTRESPOND;
+ break;
+ }
+ } else if (rec[i].srv[0].mapped_addr == rec[i].srv[1].mapped_addr &&
+ rec[i].srv[0].mapped_port == rec[i].srv[1].mapped_port)
+ {
+ mapped_addr[i].sin_family = pj_AF_INET();
+ mapped_addr[i].sin_addr.s_addr = rec[i].srv[0].mapped_addr;
+ mapped_addr[i].sin_port = (pj_uint16_t)rec[i].srv[0].mapped_port;
+
+ if (rec[i].srv[0].mapped_addr == 0 || rec[i].srv[0].mapped_port == 0) {
+ status = PJLIB_UTIL_ESTUNNOTRESPOND;
+ break;
+ }
+ } else {
+ status = PJLIB_UTIL_ESTUNSYMMETRIC;
+ break;
+ }
+ }
+
+ TRACE_((THIS_FILE, " Pool usage=%d of %d", pj_pool_get_used_size(pool),
+ pj_pool_get_capacity(pool)));
+
+ pj_pool_release(pool);
+
+ TRACE_((THIS_FILE, " Done."));
+ return status;
+
+on_error:
+ if (pool) pj_pool_release(pool);
+ return status;
+}
+
diff --git a/pjlib-util/src/pjlib-util/symbols.c b/pjlib-util/src/pjlib-util/symbols.c
new file mode 100644
index 0000000..8f4af53
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/symbols.c
@@ -0,0 +1,83 @@
+/* $Id: symbols.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib.h>
+#include <pjlib-util.h>
+
+/*
+ * md5.h
+ */
+PJ_EXPORT_SYMBOL(md5_init)
+PJ_EXPORT_SYMBOL(md5_append)
+PJ_EXPORT_SYMBOL(md5_finish)
+
+/*
+ * scanner.h
+ */
+PJ_EXPORT_SYMBOL(pj_cs_init)
+PJ_EXPORT_SYMBOL(pj_cs_set)
+PJ_EXPORT_SYMBOL(pj_cs_add_range)
+PJ_EXPORT_SYMBOL(pj_cs_add_alpha)
+PJ_EXPORT_SYMBOL(pj_cs_add_num)
+PJ_EXPORT_SYMBOL(pj_cs_add_str)
+PJ_EXPORT_SYMBOL(pj_cs_del_range)
+PJ_EXPORT_SYMBOL(pj_cs_del_str)
+PJ_EXPORT_SYMBOL(pj_cs_invert)
+PJ_EXPORT_SYMBOL(pj_scan_init)
+PJ_EXPORT_SYMBOL(pj_scan_fini)
+PJ_EXPORT_SYMBOL(pj_scan_peek)
+PJ_EXPORT_SYMBOL(pj_scan_peek_n)
+PJ_EXPORT_SYMBOL(pj_scan_peek_until)
+PJ_EXPORT_SYMBOL(pj_scan_get)
+PJ_EXPORT_SYMBOL(pj_scan_get_quote)
+PJ_EXPORT_SYMBOL(pj_scan_get_n)
+PJ_EXPORT_SYMBOL(pj_scan_get_char)
+PJ_EXPORT_SYMBOL(pj_scan_get_newline)
+PJ_EXPORT_SYMBOL(pj_scan_get_until)
+PJ_EXPORT_SYMBOL(pj_scan_get_until_ch)
+PJ_EXPORT_SYMBOL(pj_scan_get_until_chr)
+PJ_EXPORT_SYMBOL(pj_scan_advance_n)
+PJ_EXPORT_SYMBOL(pj_scan_strcmp)
+PJ_EXPORT_SYMBOL(pj_scan_stricmp)
+PJ_EXPORT_SYMBOL(pj_scan_skip_whitespace)
+PJ_EXPORT_SYMBOL(pj_scan_save_state)
+PJ_EXPORT_SYMBOL(pj_scan_restore_state)
+
+/*
+ * stun.h
+ */
+PJ_EXPORT_SYMBOL(pj_stun_create_bind_req)
+PJ_EXPORT_SYMBOL(pj_stun_parse_msg)
+PJ_EXPORT_SYMBOL(pj_stun_msg_find_attr)
+PJ_EXPORT_SYMBOL(pj_stun_get_mapped_addr)
+PJ_EXPORT_SYMBOL(pj_stun_get_err_msg)
+
+/*
+ * xml.h
+ */
+PJ_EXPORT_SYMBOL(pj_xml_parse)
+PJ_EXPORT_SYMBOL(pj_xml_print)
+PJ_EXPORT_SYMBOL(pj_xml_add_node)
+PJ_EXPORT_SYMBOL(pj_xml_add_attr)
+PJ_EXPORT_SYMBOL(pj_xml_find_node)
+PJ_EXPORT_SYMBOL(pj_xml_find_next_node)
+PJ_EXPORT_SYMBOL(pj_xml_find_attr)
+PJ_EXPORT_SYMBOL(pj_xml_find)
+
+
diff --git a/pjlib-util/src/pjlib-util/xml.c b/pjlib-util/src/pjlib-util/xml.c
new file mode 100644
index 0000000..c961596
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/xml.c
@@ -0,0 +1,520 @@
+/* $Id: xml.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib-util/xml.h>
+#include <pjlib-util/scanner.h>
+#include <pj/except.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#define EX_SYNTAX_ERROR 12
+#define THIS_FILE "xml.c"
+
+static void on_syntax_error(struct pj_scanner *scanner)
+{
+ PJ_UNUSED_ARG(scanner);
+ PJ_THROW(EX_SYNTAX_ERROR);
+}
+
+static pj_xml_node *alloc_node( pj_pool_t *pool )
+{
+ pj_xml_node *node;
+
+ node = PJ_POOL_ZALLOC_T(pool, pj_xml_node);
+ pj_list_init( &node->attr_head );
+ pj_list_init( &node->node_head );
+
+ return node;
+}
+
+static pj_xml_attr *alloc_attr( pj_pool_t *pool )
+{
+ return PJ_POOL_ZALLOC_T(pool, pj_xml_attr);
+}
+
+/* This is a recursive function! */
+static pj_xml_node *xml_parse_node( pj_pool_t *pool, pj_scanner *scanner)
+{
+ pj_xml_node *node;
+ pj_str_t end_name;
+
+ PJ_CHECK_STACK();
+
+ if (*scanner->curptr != '<')
+ on_syntax_error(scanner);
+
+ /* Handle Processing Instructino (PI) construct (i.e. "<?") */
+ if (*scanner->curptr == '<' && *(scanner->curptr+1) == '?') {
+ pj_scan_advance_n(scanner, 2, PJ_FALSE);
+ for (;;) {
+ pj_str_t dummy;
+ pj_scan_get_until_ch(scanner, '?', &dummy);
+ if (*scanner->curptr=='?' && *(scanner->curptr+1)=='>') {
+ pj_scan_advance_n(scanner, 2, PJ_TRUE);
+ break;
+ } else {
+ pj_scan_advance_n(scanner, 1, PJ_FALSE);
+ }
+ }
+ return xml_parse_node(pool, scanner);
+ }
+
+ /* Handle comments construct (i.e. "<!") */
+ if (pj_scan_strcmp(scanner, "<!", 2) == 0) {
+ pj_scan_advance_n(scanner, 2, PJ_FALSE);
+ for (;;) {
+ pj_str_t dummy;
+ pj_scan_get_until_ch(scanner, '>', &dummy);
+ if (pj_scan_strcmp(scanner, ">", 1) == 0) {
+ pj_scan_advance_n(scanner, 1, PJ_TRUE);
+ break;
+ } else {
+ pj_scan_advance_n(scanner, 1, PJ_FALSE);
+ }
+ }
+ return xml_parse_node(pool, scanner);
+ }
+
+ /* Alloc node. */
+ node = alloc_node(pool);
+
+ /* Get '<' */
+ pj_scan_get_char(scanner);
+
+ /* Get node name. */
+ pj_scan_get_until_chr( scanner, " />\t\r\n", &node->name);
+
+ /* Get attributes. */
+ while (*scanner->curptr != '>' && *scanner->curptr != '/') {
+ pj_xml_attr *attr = alloc_attr(pool);
+
+ pj_scan_get_until_chr( scanner, "=> \t\r\n", &attr->name);
+ if (*scanner->curptr == '=') {
+ pj_scan_get_char( scanner );
+ pj_scan_get_quotes(scanner, "\"'", "\"'", 2, &attr->value);
+ /* remove quote characters */
+ ++attr->value.ptr;
+ attr->value.slen -= 2;
+ }
+
+ pj_list_push_back( &node->attr_head, attr );
+ }
+
+ if (*scanner->curptr == '/') {
+ pj_scan_get_char(scanner);
+ if (pj_scan_get_char(scanner) != '>')
+ on_syntax_error(scanner);
+ return node;
+ }
+
+ /* Enclosing bracket. */
+ if (pj_scan_get_char(scanner) != '>')
+ on_syntax_error(scanner);
+
+ /* Sub nodes. */
+ while (*scanner->curptr == '<' && *(scanner->curptr+1) != '/') {
+ pj_xml_node *sub_node = xml_parse_node(pool, scanner);
+ pj_list_push_back( &node->node_head, sub_node );
+ }
+
+ /* Content. */
+ if (!pj_scan_is_eof(scanner) && *scanner->curptr != '<') {
+ pj_scan_get_until_ch(scanner, '<', &node->content);
+ }
+
+ /* Enclosing node. */
+ if (pj_scan_get_char(scanner) != '<' || pj_scan_get_char(scanner) != '/')
+ on_syntax_error(scanner);
+
+ pj_scan_get_until_chr(scanner, " \t>", &end_name);
+
+ /* Compare name. */
+ if (pj_stricmp(&node->name, &end_name) != 0)
+ on_syntax_error(scanner);
+
+ /* Enclosing '>' */
+ if (pj_scan_get_char(scanner) != '>')
+ on_syntax_error(scanner);
+
+ return node;
+}
+
+PJ_DEF(pj_xml_node*) pj_xml_parse( pj_pool_t *pool, char *msg, pj_size_t len)
+{
+ pj_xml_node *node = NULL;
+ pj_scanner scanner;
+ PJ_USE_EXCEPTION;
+
+ if (!msg || !len || !pool)
+ return NULL;
+
+ pj_scan_init( &scanner, msg, len,
+ PJ_SCAN_AUTOSKIP_WS|PJ_SCAN_AUTOSKIP_NEWLINE,
+ &on_syntax_error);
+ PJ_TRY {
+ node = xml_parse_node(pool, &scanner);
+ }
+ PJ_CATCH_ANY {
+ PJ_LOG(4,(THIS_FILE, "Syntax error parsing XML in line %d column %d",
+ scanner.line, pj_scan_get_col(&scanner)));
+ }
+ PJ_END;
+ pj_scan_fini( &scanner );
+ return node;
+}
+
+/* This is a recursive function. */
+static int xml_print_node( const pj_xml_node *node, int indent,
+ char *buf, pj_size_t len )
+{
+ int i;
+ char *p = buf;
+ pj_xml_attr *attr;
+ pj_xml_node *sub_node;
+
+#define SIZE_LEFT() ((int)(len - (p-buf)))
+
+ PJ_CHECK_STACK();
+
+ /* Print name. */
+ if (SIZE_LEFT() < node->name.slen + indent + 5)
+ return -1;
+ for (i=0; i<indent; ++i)
+ *p++ = ' ';
+ *p++ = '<';
+ pj_memcpy(p, node->name.ptr, node->name.slen);
+ p += node->name.slen;
+
+ /* Print attributes. */
+ attr = node->attr_head.next;
+ while (attr != &node->attr_head) {
+
+ if (SIZE_LEFT() < attr->name.slen + attr->value.slen + 4)
+ return -1;
+
+ *p++ = ' ';
+
+ /* Attribute name. */
+ pj_memcpy(p, attr->name.ptr, attr->name.slen);
+ p += attr->name.slen;
+
+ /* Attribute value. */
+ if (attr->value.slen) {
+ *p++ = '=';
+ *p++ = '"';
+ pj_memcpy(p, attr->value.ptr, attr->value.slen);
+ p += attr->value.slen;
+ *p++ = '"';
+ }
+
+ attr = attr->next;
+ }
+
+ /* Check for empty node. */
+ if (node->content.slen==0 &&
+ node->node_head.next==(pj_xml_node*)&node->node_head)
+ {
+ *p++ = ' ';
+ *p++ = '/';
+ *p++ = '>';
+ return p-buf;
+ }
+
+ /* Enclosing '>' */
+ if (SIZE_LEFT() < 1) return -1;
+ *p++ = '>';
+
+ /* Print sub nodes. */
+ sub_node = node->node_head.next;
+ while (sub_node != (pj_xml_node*)&node->node_head) {
+ int printed;
+
+ if (SIZE_LEFT() < indent + 3)
+ return -1;
+ //*p++ = '\r';
+ *p++ = '\n';
+
+ printed = xml_print_node(sub_node, indent + 1, p, SIZE_LEFT());
+ if (printed < 0)
+ return -1;
+
+ p += printed;
+ sub_node = sub_node->next;
+ }
+
+ /* Content. */
+ if (node->content.slen) {
+ if (SIZE_LEFT() < node->content.slen) return -1;
+ pj_memcpy(p, node->content.ptr, node->content.slen);
+ p += node->content.slen;
+ }
+
+ /* Enclosing node. */
+ if (node->node_head.next != (pj_xml_node*)&node->node_head) {
+ if (SIZE_LEFT() < node->name.slen + 5 + indent)
+ return -1;
+ //*p++ = '\r';
+ *p++ = '\n';
+ for (i=0; i<indent; ++i)
+ *p++ = ' ';
+ } else {
+ if (SIZE_LEFT() < node->name.slen + 3)
+ return -1;
+ }
+ *p++ = '<';
+ *p++ = '/';
+ pj_memcpy(p, node->name.ptr, node->name.slen);
+ p += node->name.slen;
+ *p++ = '>';
+
+#undef SIZE_LEFT
+
+ return p - buf;
+}
+
+PJ_DEF(int) pj_xml_print(const pj_xml_node *node, char *buf, pj_size_t len,
+ pj_bool_t include_prolog)
+{
+ int prolog_len = 0;
+ int printed;
+
+ if (!node || !buf || !len)
+ return 0;
+
+ if (include_prolog) {
+ pj_str_t prolog = {"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", 39};
+ if ((int)len < prolog.slen)
+ return -1;
+ pj_memcpy(buf, prolog.ptr, prolog.slen);
+ prolog_len = prolog.slen;
+ }
+
+ printed = xml_print_node(node, 0, buf+prolog_len, len-prolog_len) + prolog_len;
+ if (printed > 0 && len-printed >= 1) {
+ buf[printed++] = '\n';
+ }
+ return printed;
+}
+
+PJ_DEF(pj_xml_node*) pj_xml_node_new(pj_pool_t *pool, const pj_str_t *name)
+{
+ pj_xml_node *node = alloc_node(pool);
+ pj_strdup(pool, &node->name, name);
+ return node;
+}
+
+PJ_DEF(pj_xml_attr*) pj_xml_attr_new( pj_pool_t *pool, const pj_str_t *name,
+ const pj_str_t *value)
+{
+ pj_xml_attr *attr = alloc_attr(pool);
+ pj_strdup( pool, &attr->name, name);
+ pj_strdup( pool, &attr->value, value);
+ return attr;
+}
+
+PJ_DEF(void) pj_xml_add_node( pj_xml_node *parent, pj_xml_node *node )
+{
+ pj_list_push_back(&parent->node_head, node);
+}
+
+PJ_DEF(void) pj_xml_add_attr( pj_xml_node *node, pj_xml_attr *attr )
+{
+ pj_list_push_back(&node->attr_head, attr);
+}
+
+PJ_DEF(pj_xml_node*) pj_xml_find_node(const pj_xml_node *parent,
+ const pj_str_t *name)
+{
+ const pj_xml_node *node = parent->node_head.next;
+
+ PJ_CHECK_STACK();
+
+ while (node != (void*)&parent->node_head) {
+ if (pj_stricmp(&node->name, name) == 0)
+ return (pj_xml_node*)node;
+ node = node->next;
+ }
+ return NULL;
+}
+
+PJ_DEF(pj_xml_node*) pj_xml_find_node_rec(const pj_xml_node *parent,
+ const pj_str_t *name)
+{
+ const pj_xml_node *node = parent->node_head.next;
+
+ PJ_CHECK_STACK();
+
+ while (node != (void*)&parent->node_head) {
+ pj_xml_node *found;
+ if (pj_stricmp(&node->name, name) == 0)
+ return (pj_xml_node*)node;
+ found = pj_xml_find_node_rec(node, name);
+ if (found)
+ return (pj_xml_node*)found;
+ node = node->next;
+ }
+ return NULL;
+}
+
+PJ_DEF(pj_xml_node*) pj_xml_find_next_node( const pj_xml_node *parent,
+ const pj_xml_node *node,
+ const pj_str_t *name)
+{
+ PJ_CHECK_STACK();
+
+ node = node->next;
+ while (node != (void*)&parent->node_head) {
+ if (pj_stricmp(&node->name, name) == 0)
+ return (pj_xml_node*)node;
+ node = node->next;
+ }
+ return NULL;
+}
+
+
+PJ_DEF(pj_xml_attr*) pj_xml_find_attr( const pj_xml_node *node,
+ const pj_str_t *name,
+ const pj_str_t *value)
+{
+ const pj_xml_attr *attr = node->attr_head.next;
+ while (attr != (void*)&node->attr_head) {
+ if (pj_stricmp(&attr->name, name)==0) {
+ if (value) {
+ if (pj_stricmp(&attr->value, value)==0)
+ return (pj_xml_attr*)attr;
+ } else {
+ return (pj_xml_attr*)attr;
+ }
+ }
+ attr = attr->next;
+ }
+ return NULL;
+}
+
+
+
+PJ_DEF(pj_xml_node*) pj_xml_find( const pj_xml_node *parent,
+ const pj_str_t *name,
+ const void *data,
+ pj_bool_t (*match)(const pj_xml_node *,
+ const void*))
+{
+ const pj_xml_node *node = (const pj_xml_node *)parent->node_head.next;
+
+ if (!name && !match)
+ return NULL;
+
+ while (node != (const pj_xml_node*) &parent->node_head) {
+ if (name) {
+ if (pj_stricmp(&node->name, name)!=0) {
+ node = node->next;
+ continue;
+ }
+ }
+ if (match) {
+ if (match(node, data))
+ return (pj_xml_node*)node;
+ } else {
+ return (pj_xml_node*)node;
+ }
+
+ node = node->next;
+ }
+ return NULL;
+}
+
+PJ_DEF(pj_xml_node*) pj_xml_find_rec( const pj_xml_node *parent,
+ const pj_str_t *name,
+ const void *data,
+ pj_bool_t (*match)(const pj_xml_node*,
+ const void*))
+{
+ const pj_xml_node *node = (const pj_xml_node *)parent->node_head.next;
+
+ if (!name && !match)
+ return NULL;
+
+ while (node != (const pj_xml_node*) &parent->node_head) {
+ pj_xml_node *found;
+
+ if (name) {
+ if (pj_stricmp(&node->name, name)==0) {
+ if (match) {
+ if (match(node, data))
+ return (pj_xml_node*)node;
+ } else {
+ return (pj_xml_node*)node;
+ }
+ }
+
+ } else if (match) {
+ if (match(node, data))
+ return (pj_xml_node*)node;
+ }
+
+ found = pj_xml_find_rec(node, name, data, match);
+ if (found)
+ return found;
+
+ node = node->next;
+ }
+ return NULL;
+}
+
+PJ_DEF(pj_xml_node*) pj_xml_clone( pj_pool_t *pool, const pj_xml_node *rhs)
+{
+ pj_xml_node *node;
+ const pj_xml_attr *r_attr;
+ const pj_xml_node *child;
+
+ node = alloc_node(pool);
+
+ pj_strdup(pool, &node->name, &rhs->name);
+ pj_strdup(pool, &node->content, &rhs->content);
+
+ /* Clone all attributes */
+ r_attr = rhs->attr_head.next;
+ while (r_attr != &rhs->attr_head) {
+
+ pj_xml_attr *attr;
+
+ attr = alloc_attr(pool);
+ pj_strdup(pool, &attr->name, &r_attr->name);
+ pj_strdup(pool, &attr->value, &r_attr->value);
+
+ pj_list_push_back(&node->attr_head, attr);
+
+ r_attr = r_attr->next;
+ }
+
+ /* Clone all child nodes. */
+ child = rhs->node_head.next;
+ while (child != (pj_xml_node*) &rhs->node_head) {
+ pj_xml_node *new_child;
+
+ new_child = pj_xml_clone(pool, child);
+ pj_list_push_back(&node->node_head, new_child);
+
+ child = child->next;
+ }
+
+ return node;
+}
diff --git a/pjlib-util/src/pjlib-util/xml_wrap.cpp b/pjlib-util/src/pjlib-util/xml_wrap.cpp
new file mode 100644
index 0000000..1478542
--- /dev/null
+++ b/pjlib-util/src/pjlib-util/xml_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: xml_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "xml.c"