diff options
Diffstat (limited to 'pjlib-util/src/pjlib-util')
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(¶m->method, (char*)http_method_names[HTTP_GET]); + pj_strset2(¶m->version, (char*)HTTP_1_0); + param->timeout.msec = PJ_HTTP_DEFAULT_TIMEOUT; + pj_time_val_normalize(¶m->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" |