michael@0: /* michael@0: Copyright (c) 2007, Adobe Systems, Incorporated michael@0: All rights reserved. michael@0: michael@0: Redistribution and use in source and binary forms, with or without michael@0: modification, are permitted provided that the following conditions are michael@0: met: michael@0: michael@0: * Redistributions of source code must retain the above copyright michael@0: notice, this list of conditions and the following disclaimer. michael@0: michael@0: * Redistributions in binary form must reproduce the above copyright michael@0: notice, this list of conditions and the following disclaimer in the michael@0: documentation and/or other materials provided with the distribution. michael@0: michael@0: * Neither the name of Adobe Systems, Network Resonance nor the names of its michael@0: contributors may be used to endorse or promote products derived from michael@0: this software without specific prior written permission. michael@0: michael@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: michael@0: michael@0: static char *RCSSTRING __UNUSED__="$Id: stun_codec.c,v 1.2 2008/04/28 18:21:30 ekr Exp $"; michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #ifdef WIN32 michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #else /* UNIX */ michael@0: #include michael@0: #endif /* end UNIX */ michael@0: #include michael@0: #include michael@0: michael@0: #include "nr_api.h" michael@0: #include "stun.h" michael@0: #include "byteorder.h" michael@0: #include "r_crc32.h" michael@0: #include "nr_crypto.h" michael@0: #include "mbslen.h" michael@0: michael@0: #define NR_STUN_IPV4_FAMILY 0x01 michael@0: #define NR_STUN_IPV6_FAMILY 0x02 michael@0: michael@0: #define SKIP_ATTRIBUTE_DECODE -1 michael@0: michael@0: static int nr_stun_find_attr_info(UINT2 type, nr_stun_attr_info **info); michael@0: michael@0: static int nr_stun_fix_attribute_ordering(nr_stun_message *msg); michael@0: michael@0: static int nr_stun_encode_htons(UINT2 data, int buflen, UCHAR *buf, int *offset); michael@0: static int nr_stun_encode_htonl(UINT4 data, int buflen, UCHAR *buf, int *offset); michael@0: static int nr_stun_encode_htonll(UINT8 data, int buflen, UCHAR *buf, int *offset); michael@0: static int nr_stun_encode(UCHAR *data, int length, int buflen, UCHAR *buf, int *offset); michael@0: michael@0: static int nr_stun_decode_htons(UCHAR *buf, int buflen, int *offset, UINT2 *data); michael@0: static int nr_stun_decode_htonl(UCHAR *buf, int buflen, int *offset, UINT4 *data); michael@0: static int nr_stun_decode_htonll(UCHAR *buf, int buflen, int *offset, UINT8 *data); michael@0: static int nr_stun_decode(int length, UCHAR *buf, int buflen, int *offset, UCHAR *data); michael@0: michael@0: static int nr_stun_attr_string_illegal(nr_stun_attr_info *attr_info, int len, void *data, int max_bytes, int max_chars); michael@0: michael@0: static int nr_stun_attr_error_code_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data); michael@0: static int nr_stun_attr_nonce_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data); michael@0: static int nr_stun_attr_realm_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data); michael@0: static int nr_stun_attr_server_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data); michael@0: static int nr_stun_attr_username_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data); michael@0: static int michael@0: nr_stun_attr_codec_fingerprint_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data); michael@0: michael@0: michael@0: int michael@0: nr_stun_encode_htons(UINT2 data, int buflen, UCHAR *buf, int *offset) michael@0: { michael@0: UINT2 d = htons(data); michael@0: michael@0: if (*offset + sizeof(d) >= buflen) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd >= %d", *offset, sizeof(d), buflen); michael@0: return R_BAD_DATA; michael@0: } michael@0: michael@0: memcpy(&buf[*offset], &d, sizeof(d)); michael@0: *offset += sizeof(d); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nr_stun_encode_htonl(UINT4 data, int buflen, UCHAR *buf, int *offset) michael@0: { michael@0: UINT4 d = htonl(data); michael@0: michael@0: if (*offset + sizeof(d) > buflen) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen); michael@0: return R_BAD_DATA; michael@0: } michael@0: michael@0: memcpy(&buf[*offset], &d, sizeof(d)); michael@0: *offset += sizeof(d); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nr_stun_encode_htonll(UINT8 data, int buflen, UCHAR *buf, int *offset) michael@0: { michael@0: UINT8 d = nr_htonll(data); michael@0: michael@0: if (*offset + sizeof(d) > buflen) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen); michael@0: return R_BAD_DATA; michael@0: } michael@0: michael@0: memcpy(&buf[*offset], &d, sizeof(d)); michael@0: *offset += sizeof(d); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nr_stun_encode(UCHAR *data, int length, int buflen, UCHAR *buf, int *offset) michael@0: { michael@0: if (*offset + length > buflen) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %d > %d", *offset, length, buflen); michael@0: return R_BAD_DATA; michael@0: } michael@0: michael@0: memcpy(&buf[*offset], data, length); michael@0: *offset += length; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: michael@0: int michael@0: nr_stun_decode_htons(UCHAR *buf, int buflen, int *offset, UINT2 *data) michael@0: { michael@0: UINT2 d; michael@0: michael@0: if (*offset + sizeof(d) > buflen) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen); michael@0: return R_BAD_DATA; michael@0: } michael@0: michael@0: memcpy(&d, &buf[*offset], sizeof(d)); michael@0: *offset += sizeof(d); michael@0: *data = htons(d); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nr_stun_decode_htonl(UCHAR *buf, int buflen, int *offset, UINT4 *data) michael@0: { michael@0: UINT4 d; michael@0: michael@0: if (*offset + sizeof(d) > buflen) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen); michael@0: return R_BAD_DATA; michael@0: } michael@0: michael@0: memcpy(&d, &buf[*offset], sizeof(d)); michael@0: *offset += sizeof(d); michael@0: *data = htonl(d); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nr_stun_decode_htonll(UCHAR *buf, int buflen, int *offset, UINT8 *data) michael@0: { michael@0: UINT8 d; michael@0: michael@0: if (*offset + sizeof(d) > buflen) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen); michael@0: return R_BAD_DATA; michael@0: } michael@0: michael@0: memcpy(&d, &buf[*offset], sizeof(d)); michael@0: *offset += sizeof(d); michael@0: *data = nr_htonll(d); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nr_stun_decode(int length, UCHAR *buf, int buflen, int *offset, UCHAR *data) michael@0: { michael@0: if (*offset + length > buflen) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %d > %d", *offset, length, buflen); michael@0: return R_BAD_DATA; michael@0: } michael@0: michael@0: memcpy(data, &buf[*offset], length); michael@0: *offset += length; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int michael@0: nr_stun_attr_string_illegal(nr_stun_attr_info *attr_info, int len, void *data, int max_bytes, int max_chars) michael@0: { michael@0: int _status; michael@0: char *s = data; michael@0: size_t nchars; michael@0: michael@0: if (len > max_bytes) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "%s is too large: %d bytes", attr_info->name, len); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: if (max_chars >= 0) { michael@0: if (mbslen(s, &nchars)) { michael@0: /* who knows what to do, just assume everything is working ok */ michael@0: } michael@0: else if (nchars > max_chars) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "%s is too large: %zd characters", attr_info->name, nchars); michael@0: ABORT(R_FAILED); michael@0: } michael@0: } michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: int michael@0: nr_stun_attr_error_code_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data) michael@0: { michael@0: int r,_status; michael@0: nr_stun_attr_error_code *ec = data; michael@0: michael@0: if (ec->number < 300 || ec->number > 699) michael@0: ABORT(R_FAILED); michael@0: michael@0: if ((r=nr_stun_attr_string_illegal(attr_info, strlen(ec->reason), ec->reason, NR_STUN_MAX_ERROR_CODE_REASON_BYTES, NR_STUN_MAX_ERROR_CODE_REASON_CHARS))) michael@0: ABORT(r); michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: int michael@0: nr_stun_attr_nonce_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data) michael@0: { michael@0: return nr_stun_attr_string_illegal(attr_info, attrlen, data, NR_STUN_MAX_NONCE_BYTES, NR_STUN_MAX_NONCE_CHARS); michael@0: } michael@0: michael@0: int michael@0: nr_stun_attr_realm_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data) michael@0: { michael@0: return nr_stun_attr_string_illegal(attr_info, attrlen, data, NR_STUN_MAX_REALM_BYTES, NR_STUN_MAX_REALM_CHARS); michael@0: } michael@0: michael@0: int michael@0: nr_stun_attr_server_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data) michael@0: { michael@0: return nr_stun_attr_string_illegal(attr_info, attrlen, data, NR_STUN_MAX_SERVER_BYTES, NR_STUN_MAX_SERVER_CHARS); michael@0: } michael@0: michael@0: int michael@0: nr_stun_attr_username_illegal(nr_stun_attr_info *attr_info, int attrlen, void *data) michael@0: { michael@0: return nr_stun_attr_string_illegal(attr_info, attrlen, data, NR_STUN_MAX_USERNAME_BYTES, -1); michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_UCHAR_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %u", msg, attr_info->name, *(UCHAR*)data); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_UCHAR_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: int start = offset; michael@0: UINT4 tmp = *((UCHAR *)data); michael@0: tmp <<= 24; michael@0: michael@0: if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(sizeof(UINT4) , buflen, buf, &offset) michael@0: || nr_stun_encode_htonl(tmp , buflen, buf, &offset)) michael@0: return R_FAILED; michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_UCHAR_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: UINT4 tmp; michael@0: michael@0: if (attrlen != sizeof(UINT4)) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Integer is illegal size: %d", attrlen); michael@0: return R_FAILED; michael@0: } michael@0: michael@0: if (nr_stun_decode_htonl(buf, buflen, &offset, &tmp)) michael@0: return R_FAILED; michael@0: michael@0: *((UCHAR *)data) = (tmp >> 24) & 0xff; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_UCHAR = { michael@0: "UCHAR", michael@0: nr_stun_attr_codec_UCHAR_print, michael@0: nr_stun_attr_codec_UCHAR_encode, michael@0: nr_stun_attr_codec_UCHAR_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_UINT4_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %u", msg, attr_info->name, *(UINT4*)data); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_UINT4_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: int start = offset; michael@0: michael@0: if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(sizeof(UINT4) , buflen, buf, &offset) michael@0: || nr_stun_encode_htonl(*(UINT4*)data , buflen, buf, &offset)) michael@0: return R_FAILED; michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_UINT4_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: if (attrlen != sizeof(UINT4)) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Integer is illegal size: %d", attrlen); michael@0: return R_FAILED; michael@0: } michael@0: michael@0: if (nr_stun_decode_htonl(buf, buflen, &offset, (UINT4*)data)) michael@0: return R_FAILED; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_UINT4 = { michael@0: "UINT4", michael@0: nr_stun_attr_codec_UINT4_print, michael@0: nr_stun_attr_codec_UINT4_encode, michael@0: nr_stun_attr_codec_UINT4_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_UINT8_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %llu", msg, attr_info->name, *(UINT8*)data); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_UINT8_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: int start = offset; michael@0: michael@0: if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(sizeof(UINT8) , buflen, buf, &offset) michael@0: || nr_stun_encode_htonll(*(UINT8*)data , buflen, buf, &offset)) michael@0: return R_FAILED; michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_UINT8_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: if (attrlen != sizeof(UINT8)) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Integer is illegal size: %d", attrlen); michael@0: return R_FAILED; michael@0: } michael@0: michael@0: if (nr_stun_decode_htonll(buf, buflen, &offset, (UINT8*)data)) michael@0: return R_FAILED; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_UINT8 = { michael@0: "UINT8", michael@0: nr_stun_attr_codec_UINT8_print, michael@0: nr_stun_attr_codec_UINT8_encode, michael@0: nr_stun_attr_codec_UINT8_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_addr_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %s", msg, attr_info->name, ((nr_transport_addr*)data)->as_string); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_addr_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: int r,_status; michael@0: int start = offset; michael@0: nr_transport_addr *addr = data; michael@0: UCHAR pad = '\0'; michael@0: UCHAR family; michael@0: michael@0: if ((r=nr_stun_encode_htons(attr_info->type, buflen, buf, &offset))) michael@0: ABORT(r); michael@0: michael@0: switch (addr->ip_version) { michael@0: case NR_IPV4: michael@0: family = NR_STUN_IPV4_FAMILY; michael@0: if (nr_stun_encode_htons(8 , buflen, buf, &offset) michael@0: || nr_stun_encode(&pad, 1 , buflen, buf, &offset) michael@0: || nr_stun_encode(&family, 1 , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(ntohs(addr->u.addr4.sin_port), buflen, buf, &offset) michael@0: || nr_stun_encode_htonl(ntohl(addr->u.addr4.sin_addr.s_addr), buflen, buf, &offset)) michael@0: ABORT(R_FAILED); michael@0: break; michael@0: michael@0: case NR_IPV6: michael@0: assert(0); michael@0: ABORT(R_INTERNAL); michael@0: break; michael@0: default: michael@0: assert(0); michael@0: ABORT(R_INTERNAL); michael@0: break; michael@0: } michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_addr_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: int _status; michael@0: UCHAR pad; michael@0: UCHAR family; michael@0: UINT2 port; michael@0: UINT4 addr4; michael@0: nr_transport_addr *result = data; michael@0: michael@0: if (nr_stun_decode(1, buf, buflen, &offset, &pad) michael@0: || nr_stun_decode(1, buf, buflen, &offset, &family)) michael@0: ABORT(R_FAILED); michael@0: michael@0: switch (family) { michael@0: case NR_STUN_IPV4_FAMILY: michael@0: if (attrlen != 8) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Illegal attribute length: %d", attrlen); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: if (nr_stun_decode_htons(buf, buflen, &offset, &port) michael@0: || nr_stun_decode_htonl(buf, buflen, &offset, &addr4)) michael@0: ABORT(R_FAILED); michael@0: michael@0: if (nr_ip4_port_to_transport_addr(addr4, port, IPPROTO_UDP, result)) michael@0: ABORT(R_FAILED); michael@0: break; michael@0: michael@0: case NR_STUN_IPV6_FAMILY: michael@0: if (attrlen != 16) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Illegal attribute length: %d", attrlen); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "IPv6 not supported"); michael@0: #ifdef NDEBUG michael@0: ABORT(SKIP_ATTRIBUTE_DECODE); michael@0: #else michael@0: UNIMPLEMENTED; michael@0: #endif /* NDEBUG */ michael@0: break; michael@0: michael@0: default: michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Illegal address family: %d", family); michael@0: ABORT(R_FAILED); michael@0: break; michael@0: } michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_addr = { michael@0: "addr", michael@0: nr_stun_attr_codec_addr_print, michael@0: nr_stun_attr_codec_addr_encode, michael@0: nr_stun_attr_codec_addr_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_data_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: nr_stun_attr_data *d = data; michael@0: r_dump(NR_LOG_STUN, LOG_DEBUG, attr_info->name, (char*)d->data, d->length); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_data_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: nr_stun_attr_data *d = data; michael@0: int start = offset; michael@0: michael@0: if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(d->length , buflen, buf, &offset) michael@0: || nr_stun_encode(d->data, d->length , buflen, buf, &offset)) michael@0: return R_FAILED; michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_data_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: int _status; michael@0: nr_stun_attr_data *result = data; michael@0: michael@0: /* -1 because it is going to be null terminated just to be safe */ michael@0: if (attrlen >= (sizeof(result->data) - 1)) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Too much data: %d bytes", attrlen); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: if (nr_stun_decode(attrlen, buf, buflen, &offset, result->data)) michael@0: ABORT(R_FAILED); michael@0: michael@0: result->length = attrlen; michael@0: result->data[attrlen] = '\0'; /* just to be nice */ michael@0: michael@0: _status=0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_data = { michael@0: "data", michael@0: nr_stun_attr_codec_data_print, michael@0: nr_stun_attr_codec_data_encode, michael@0: nr_stun_attr_codec_data_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_error_code_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: nr_stun_attr_error_code *error_code = data; michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %d %s", michael@0: msg, attr_info->name, error_code->number, michael@0: error_code->reason); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_error_code_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: nr_stun_attr_error_code *error_code = data; michael@0: int start = offset; michael@0: int length = strlen(error_code->reason); michael@0: UCHAR pad[2] = { 0 }; michael@0: UCHAR class = error_code->number / 100; michael@0: UCHAR number = error_code->number % 100; michael@0: michael@0: if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(4 + length , buflen, buf, &offset) michael@0: || nr_stun_encode(pad, 2 , buflen, buf, &offset) michael@0: || nr_stun_encode(&class, 1 , buflen, buf, &offset) michael@0: || nr_stun_encode(&number, 1 , buflen, buf, &offset) michael@0: || nr_stun_encode((UCHAR*)error_code->reason, length, buflen, buf, &offset)) michael@0: return R_FAILED; michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_error_code_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: int _status; michael@0: nr_stun_attr_error_code *result = data; michael@0: UCHAR pad[2]; michael@0: UCHAR class; michael@0: UCHAR number; michael@0: int size_reason; michael@0: michael@0: if (nr_stun_decode(2, buf, buflen, &offset, pad) michael@0: || nr_stun_decode(1, buf, buflen, &offset, &class) michael@0: || nr_stun_decode(1, buf, buflen, &offset, &number)) michael@0: ABORT(R_FAILED); michael@0: michael@0: result->number = (class * 100) + number; michael@0: michael@0: size_reason = attrlen - 4; michael@0: michael@0: /* -1 because the string will be null terminated */ michael@0: if (size_reason > (sizeof(result->reason) - 1)) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Reason is too large, truncating"); michael@0: /* don't fail, but instead truncate the reason */ michael@0: size_reason = sizeof(result->reason) - 1; michael@0: } michael@0: michael@0: if (nr_stun_decode(size_reason, buf, buflen, &offset, (UCHAR*)result->reason)) michael@0: ABORT(R_FAILED); michael@0: result->reason[size_reason] = '\0'; michael@0: michael@0: _status=0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_error_code = { michael@0: "error_code", michael@0: nr_stun_attr_codec_error_code_print, michael@0: nr_stun_attr_codec_error_code_encode, michael@0: nr_stun_attr_codec_error_code_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_fingerprint_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: nr_stun_attr_fingerprint *fingerprint = data; michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %08x", msg, attr_info->name, fingerprint->checksum); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_fingerprint_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: UINT4 checksum; michael@0: nr_stun_attr_fingerprint *fingerprint = data; michael@0: nr_stun_message_header *header = (nr_stun_message_header*)buf; michael@0: michael@0: /* the length must include the FINGERPRINT attribute when computing michael@0: * the fingerprint */ michael@0: header->length = ntohs(header->length); michael@0: header->length += 8; /* Fingerprint */ michael@0: header->length = htons(header->length); michael@0: michael@0: if (r_crc32((char*)buf, offset, &checksum)) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Unable to compute fingerprint"); michael@0: return R_FAILED; michael@0: } michael@0: michael@0: fingerprint->checksum = checksum ^ 0x5354554e; michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Computed FINGERPRINT %08x", fingerprint->checksum); michael@0: michael@0: fingerprint->valid = 1; michael@0: return nr_stun_attr_codec_UINT4.encode(attr_info, &fingerprint->checksum, offset, buflen, buf, attrlen); michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_fingerprint_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: int r,_status; michael@0: nr_stun_attr_fingerprint *fingerprint = data; michael@0: nr_stun_message_header *header = (nr_stun_message_header*)buf; michael@0: int length; michael@0: UINT4 checksum; michael@0: michael@0: if ((r=nr_stun_attr_codec_UINT4.decode(attr_info, attrlen, buf, offset, buflen, &fingerprint->checksum))) michael@0: ABORT(r); michael@0: michael@0: offset -= 4; /* rewind to before the length and type fields */ michael@0: michael@0: /* the length must include the FINGERPRINT attribute when computing michael@0: * the fingerprint */ michael@0: length = offset; /* right before FINGERPRINT */ michael@0: length -= sizeof(*header); /* remove header length */ michael@0: length += 8; /* add length of Fingerprint */ michael@0: header->length = htons(length); michael@0: michael@0: /* make sure FINGERPRINT is final attribute in message */ michael@0: assert(length + sizeof(*header) == buflen); michael@0: michael@0: if (r_crc32((char*)buf, offset, &checksum)) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Unable to compute fingerprint"); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: fingerprint->valid = (fingerprint->checksum == (checksum ^ 0x5354554e)); michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Computed FINGERPRINT %08x", (checksum ^ 0x5354554e)); michael@0: if (! fingerprint->valid) michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Invalid FINGERPRINT %08x", fingerprint->checksum); michael@0: michael@0: _status=0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_fingerprint = { michael@0: "fingerprint", michael@0: nr_stun_attr_codec_fingerprint_print, michael@0: nr_stun_attr_codec_fingerprint_encode, michael@0: nr_stun_attr_codec_fingerprint_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_flag_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: on", msg, attr_info->name); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_flag_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: int start = offset; michael@0: michael@0: if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(0 , buflen, buf, &offset)) michael@0: return R_FAILED; michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_flag_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: if (attrlen != 0) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Illegal flag length: %d", attrlen); michael@0: return R_FAILED; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_flag = { michael@0: "flag", michael@0: nr_stun_attr_codec_flag_print, michael@0: nr_stun_attr_codec_flag_encode, michael@0: nr_stun_attr_codec_flag_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_message_integrity_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: nr_stun_attr_message_integrity *integrity = data; michael@0: r_dump(NR_LOG_STUN, LOG_DEBUG, attr_info->name, (char*)integrity->hash, sizeof(integrity->hash)); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_compute_message_integrity(UCHAR *buf, int offset, UCHAR *password, int passwordlen, UCHAR *computedHMAC) michael@0: { michael@0: int r,_status; michael@0: UINT2 hold; michael@0: UINT2 length; michael@0: nr_stun_message_header *header; michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Computing MESSAGE-INTEGRITY"); michael@0: michael@0: header = (nr_stun_message_header*)buf; michael@0: hold = header->length; michael@0: michael@0: /* adjust the length of the message */ michael@0: length = offset; michael@0: length -= sizeof(*header); michael@0: length += 24; /* for MESSAGE-INTEGRITY attribute */ michael@0: header->length = htons(length); michael@0: michael@0: if ((r=nr_crypto_hmac_sha1((UCHAR*)password, passwordlen, michael@0: buf, offset, computedHMAC))) michael@0: ABORT(r); michael@0: michael@0: r_dump(NR_LOG_STUN, LOG_DEBUG, "Computed MESSAGE-INTEGRITY ", (char*)computedHMAC, 20); michael@0: michael@0: _status=0; michael@0: abort: michael@0: header->length = hold; michael@0: return _status; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_message_integrity_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: int start = offset; michael@0: nr_stun_attr_message_integrity *integrity = data; michael@0: michael@0: if (nr_stun_compute_message_integrity(buf, offset, integrity->password, integrity->passwordlen, integrity->hash)) michael@0: return R_FAILED; michael@0: michael@0: if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(sizeof(integrity->hash) , buflen, buf, &offset) michael@0: || nr_stun_encode(integrity->hash, sizeof(integrity->hash) , buflen, buf, &offset)) michael@0: return R_FAILED; michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_message_integrity_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: int _status; michael@0: int start; michael@0: nr_stun_attr_message_integrity *result = data; michael@0: UCHAR computedHMAC[20]; michael@0: michael@0: result->valid = 0; michael@0: michael@0: if (attrlen != 20) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "%s must be 20 bytes, not %d", attr_info->name, attrlen); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: start = offset - 4; /* rewind to before the length and type fields */ michael@0: if (start < 0) michael@0: ABORT(R_INTERNAL); michael@0: michael@0: if (nr_stun_decode(attrlen, buf, buflen, &offset, result->hash)) michael@0: ABORT(R_FAILED); michael@0: michael@0: if (result->unknown_user) { michael@0: result->valid = 0; michael@0: } michael@0: else { michael@0: if (nr_stun_compute_message_integrity(buf, start, result->password, result->passwordlen, computedHMAC)) michael@0: ABORT(R_FAILED); michael@0: michael@0: assert(sizeof(computedHMAC) == sizeof(result->hash)); michael@0: michael@0: result->valid = (memcmp(computedHMAC, result->hash, 20) == 0); michael@0: } michael@0: michael@0: _status=0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_message_integrity = { michael@0: "message_integrity", michael@0: nr_stun_attr_codec_message_integrity_print, michael@0: nr_stun_attr_codec_message_integrity_encode, michael@0: nr_stun_attr_codec_message_integrity_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_noop_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: return SKIP_ATTRIBUTE_DECODE; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_noop = { michael@0: "NOOP", michael@0: 0, /* ignore, never print these attributes */ michael@0: 0, /* ignore, never encode these attributes */ michael@0: nr_stun_attr_codec_noop_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_quoted_string_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %s", michael@0: msg, attr_info->name, (char*)data); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_quoted_string_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: //TODO: !nn! syntax check, conversion if not quoted already? michael@0: //We'll just restrict this in the API -- EKR michael@0: return nr_stun_attr_codec_string.encode(attr_info, data, offset, buflen, buf, attrlen); michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_quoted_string_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: //TODO: !nn! I don't see any need to unquote this but we may michael@0: //find one later -- EKR michael@0: return nr_stun_attr_codec_string.decode(attr_info, attrlen, buf, offset, buflen, data); michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_quoted_string = { michael@0: "quoted_string", michael@0: nr_stun_attr_codec_quoted_string_print, michael@0: nr_stun_attr_codec_quoted_string_encode, michael@0: nr_stun_attr_codec_quoted_string_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_string_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %s", michael@0: msg, attr_info->name, (char*)data); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_string_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: int start = offset; michael@0: char *str = data; michael@0: int length = strlen(str); michael@0: michael@0: if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(length , buflen, buf, &offset) michael@0: || nr_stun_encode((UCHAR*)str, length , buflen, buf, &offset)) michael@0: return R_FAILED; michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_string_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: int _status; michael@0: char *result = data; michael@0: michael@0: /* actual enforcement of the specific string size happens elsewhere */ michael@0: if (attrlen >= NR_STUN_MAX_STRING_SIZE) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "String is too large: %d bytes", attrlen); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: if (nr_stun_decode(attrlen, buf, buflen, &offset, (UCHAR*)result)) michael@0: ABORT(R_FAILED); michael@0: result[attrlen] = '\0'; /* just to be nice */ michael@0: michael@0: if (strlen(result) != attrlen) { michael@0: /* stund 0.96 sends a final null in the Server attribute, so michael@0: * only error if the null appears anywhere else in a string */ michael@0: if (strlen(result) != attrlen-1) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Error in string: %zd/%d", strlen(result), attrlen); michael@0: ABORT(R_FAILED); michael@0: } michael@0: } michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_string = { michael@0: "string", michael@0: nr_stun_attr_codec_string_print, michael@0: nr_stun_attr_codec_string_encode, michael@0: nr_stun_attr_codec_string_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_unknown_attributes_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: nr_stun_attr_unknown_attributes *unknown_attributes = data; michael@0: char type[9]; michael@0: char str[64 + (NR_STUN_MAX_UNKNOWN_ATTRIBUTES * sizeof(type))]; michael@0: int i; michael@0: michael@0: snprintf(str, sizeof(str), "%s %s:", msg, attr_info->name); michael@0: for (i = 0; i < unknown_attributes->num_attributes; ++i) { michael@0: snprintf(type, sizeof(type), "%s 0x%04x", ((i>0)?",":""), unknown_attributes->attribute[i]); michael@0: strlcat(str, type, sizeof(str)); michael@0: } michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s", str); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_unknown_attributes_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: int _status; michael@0: int start = offset; michael@0: nr_stun_attr_unknown_attributes *unknown_attributes = data; michael@0: int length = (2 * unknown_attributes->num_attributes); michael@0: int i; michael@0: michael@0: if (unknown_attributes->num_attributes > NR_STUN_MAX_UNKNOWN_ATTRIBUTES) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Too many UNKNOWN-ATTRIBUTES: %d", unknown_attributes->num_attributes); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset) michael@0: || nr_stun_encode_htons(length , buflen, buf, &offset)) michael@0: ABORT(R_FAILED); michael@0: michael@0: for (i = 0; i < unknown_attributes->num_attributes; ++i) { michael@0: if (nr_stun_encode_htons(unknown_attributes->attribute[i], buflen, buf, &offset)) michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: *attrlen = offset - start; michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_unknown_attributes_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: int _status; michael@0: nr_stun_attr_unknown_attributes *unknown_attributes = data; michael@0: int i; michael@0: UINT2 *a; michael@0: michael@0: if ((attrlen % 4) != 0) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attribute is illegal size: %d", attrlen); michael@0: ABORT(R_REJECTED); michael@0: } michael@0: michael@0: unknown_attributes->num_attributes = attrlen / 2; michael@0: michael@0: if (unknown_attributes->num_attributes > NR_STUN_MAX_UNKNOWN_ATTRIBUTES) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Too many UNKNOWN-ATTRIBUTES: %d", unknown_attributes->num_attributes); michael@0: ABORT(R_REJECTED); michael@0: } michael@0: michael@0: for (i = 0; i < unknown_attributes->num_attributes; ++i) { michael@0: a = &(unknown_attributes->attribute[i]); michael@0: if (nr_stun_decode_htons(buf, buflen, &offset, a)) michael@0: return R_FAILED; michael@0: } michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_unknown_attributes = { michael@0: "unknown_attributes", michael@0: nr_stun_attr_codec_unknown_attributes_print, michael@0: nr_stun_attr_codec_unknown_attributes_encode, michael@0: nr_stun_attr_codec_unknown_attributes_decode michael@0: }; michael@0: michael@0: static int michael@0: nr_stun_attr_codec_xor_mapped_address_print(nr_stun_attr_info *attr_info, char *msg, void *data) michael@0: { michael@0: nr_stun_attr_xor_mapped_address *xor_mapped_address = data; michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %s (unmasked) %s (masked)", michael@0: msg, attr_info->name, michael@0: xor_mapped_address->unmasked.as_string, michael@0: xor_mapped_address->masked.as_string); michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_xor_mapped_address_encode(nr_stun_attr_info *attr_info, void *data, int offset, int buflen, UCHAR *buf, int *attrlen) michael@0: { michael@0: nr_stun_attr_xor_mapped_address *xor_mapped_address = data; michael@0: nr_stun_message_header *header = (nr_stun_message_header*)buf; michael@0: UINT4 magic_cookie; michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Unmasked XOR-MAPPED-ADDRESS = %s", xor_mapped_address->unmasked.as_string); michael@0: michael@0: /* this needs to be the magic cookie in the header and not michael@0: * the MAGIC_COOKIE constant because if we're talking to michael@0: * older servers (that don't have a magic cookie) they use michael@0: * message ID for this */ michael@0: magic_cookie = ntohl(header->magic_cookie); michael@0: michael@0: nr_stun_xor_mapped_address(magic_cookie, &xor_mapped_address->unmasked, &xor_mapped_address->masked); michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Masked XOR-MAPPED-ADDRESS = %s", xor_mapped_address->masked.as_string); michael@0: michael@0: if (nr_stun_attr_codec_addr.encode(attr_info, &xor_mapped_address->masked, offset, buflen, buf, attrlen)) michael@0: return R_FAILED; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static int michael@0: nr_stun_attr_codec_xor_mapped_address_decode(nr_stun_attr_info *attr_info, int attrlen, UCHAR *buf, int offset, int buflen, void *data) michael@0: { michael@0: int r,_status; michael@0: nr_stun_attr_xor_mapped_address *xor_mapped_address = data; michael@0: nr_stun_message_header *header = (nr_stun_message_header*)buf; michael@0: UINT4 magic_cookie; michael@0: michael@0: if ((r=nr_stun_attr_codec_addr.decode(attr_info, attrlen, buf, offset, buflen, &xor_mapped_address->masked))) michael@0: ABORT(r); michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Masked XOR-MAPPED-ADDRESS = %s", xor_mapped_address->masked.as_string); michael@0: michael@0: /* this needs to be the magic cookie in the header and not michael@0: * the MAGIC_COOKIE constant because if we're talking to michael@0: * older servers (that don't have a magic cookie) they use michael@0: * message ID for this */ michael@0: magic_cookie = ntohl(header->magic_cookie); michael@0: michael@0: nr_stun_xor_mapped_address(magic_cookie, &xor_mapped_address->masked, &xor_mapped_address->unmasked); michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Unmasked XOR-MAPPED-ADDRESS = %s", xor_mapped_address->unmasked.as_string); michael@0: michael@0: _status = 0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_xor_mapped_address = { michael@0: "xor_mapped_address", michael@0: nr_stun_attr_codec_xor_mapped_address_print, michael@0: nr_stun_attr_codec_xor_mapped_address_encode, michael@0: nr_stun_attr_codec_xor_mapped_address_decode michael@0: }; michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_old_xor_mapped_address = { michael@0: "xor_mapped_address", michael@0: nr_stun_attr_codec_xor_mapped_address_print, michael@0: 0, /* never encode this type */ michael@0: nr_stun_attr_codec_xor_mapped_address_decode michael@0: }; michael@0: michael@0: nr_stun_attr_codec nr_stun_attr_codec_xor_peer_address = { michael@0: "xor_peer_address", michael@0: nr_stun_attr_codec_xor_mapped_address_print, michael@0: nr_stun_attr_codec_xor_mapped_address_encode, michael@0: nr_stun_attr_codec_xor_mapped_address_decode michael@0: }; michael@0: michael@0: #define NR_ADD_STUN_ATTRIBUTE(type, name, codec, illegal) \ michael@0: { (type), (name), &(codec), illegal }, michael@0: michael@0: #define NR_ADD_STUN_ATTRIBUTE_IGNORE(type, name) \ michael@0: { (type), (name), &nr_stun_attr_codec_noop, 0 }, michael@0: michael@0: michael@0: static nr_stun_attr_info attrs[] = { michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_ALTERNATE_SERVER, "ALTERNATE-SERVER", nr_stun_attr_codec_addr, 0) michael@0: #ifdef USE_STUND_0_96 michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_OLD_CHANGE_REQUEST, "CHANGE-REQUEST", nr_stun_attr_codec_UINT4, 0) michael@0: #endif michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_ERROR_CODE, "ERROR-CODE", nr_stun_attr_codec_error_code, nr_stun_attr_error_code_illegal) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_FINGERPRINT, "FINGERPRINT", nr_stun_attr_codec_fingerprint, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_MAPPED_ADDRESS, "MAPPED-ADDRESS", nr_stun_attr_codec_addr, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_MESSAGE_INTEGRITY, "MESSAGE-INTEGRITY", nr_stun_attr_codec_message_integrity, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_NONCE, "NONCE", nr_stun_attr_codec_quoted_string, nr_stun_attr_nonce_illegal) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_REALM, "REALM", nr_stun_attr_codec_quoted_string, nr_stun_attr_realm_illegal) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_SERVER, "SERVER", nr_stun_attr_codec_string, nr_stun_attr_server_illegal) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_UNKNOWN_ATTRIBUTES, "UNKNOWN-ATTRIBUTES", nr_stun_attr_codec_unknown_attributes, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_USERNAME, "USERNAME", nr_stun_attr_codec_string, nr_stun_attr_username_illegal) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_XOR_MAPPED_ADDRESS, "XOR-MAPPED-ADDRESS", nr_stun_attr_codec_xor_mapped_address, 0) michael@0: michael@0: #ifdef USE_ICE michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_ICE_CONTROLLED, "ICE-CONTROLLED", nr_stun_attr_codec_UINT8, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_ICE_CONTROLLING, "ICE-CONTROLLING", nr_stun_attr_codec_UINT8, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_PRIORITY, "PRIORITY", nr_stun_attr_codec_UINT4, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_USE_CANDIDATE, "USE-CANDIDATE", nr_stun_attr_codec_flag, 0) michael@0: #endif michael@0: michael@0: #ifdef USE_TURN michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_DATA, "DATA", nr_stun_attr_codec_data, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_LIFETIME, "LIFETIME", nr_stun_attr_codec_UINT4, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_XOR_RELAY_ADDRESS, "XOR-RELAY-ADDRESS", nr_stun_attr_codec_xor_mapped_address, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_XOR_PEER_ADDRESS, "XOR-PEER-ADDRESS", nr_stun_attr_codec_xor_peer_address, 0) michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_REQUESTED_TRANSPORT, "REQUESTED-TRANSPORT", nr_stun_attr_codec_UCHAR, 0) michael@0: #endif /* USE_TURN */ michael@0: michael@0: /* for backwards compatibilty */ michael@0: NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_OLD_XOR_MAPPED_ADDRESS, "Old XOR-MAPPED-ADDRESS", nr_stun_attr_codec_old_xor_mapped_address, 0) michael@0: #ifdef USE_RFC_3489_BACKWARDS_COMPATIBLE michael@0: NR_ADD_STUN_ATTRIBUTE_IGNORE(NR_STUN_ATTR_OLD_RESPONSE_ADDRESS, "RESPONSE-ADDRESS") michael@0: NR_ADD_STUN_ATTRIBUTE_IGNORE(NR_STUN_ATTR_OLD_SOURCE_ADDRESS, "SOURCE-ADDRESS") michael@0: NR_ADD_STUN_ATTRIBUTE_IGNORE(NR_STUN_ATTR_OLD_CHANGED_ADDRESS, "CHANGED-ADDRESS") michael@0: NR_ADD_STUN_ATTRIBUTE_IGNORE(NR_STUN_ATTR_OLD_PASSWORD, "PASSWORD") michael@0: #endif /* USE_RFC_3489_BACKWARDS_COMPATIBLE */ michael@0: }; michael@0: michael@0: michael@0: int michael@0: nr_stun_find_attr_info(UINT2 type, nr_stun_attr_info **info) michael@0: { michael@0: int _status; michael@0: int i; michael@0: michael@0: *info = 0; michael@0: for (i = 0; i < sizeof(attrs)/sizeof(*attrs); ++i) { michael@0: if (type == attrs[i].type) { michael@0: *info = &attrs[i]; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (*info == 0) michael@0: ABORT(R_NOT_FOUND); michael@0: michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: int michael@0: nr_stun_fix_attribute_ordering(nr_stun_message *msg) michael@0: { michael@0: nr_stun_message_attribute *message_integrity; michael@0: nr_stun_message_attribute *fingerprint; michael@0: michael@0: /* 2nd to the last */ michael@0: if (nr_stun_message_has_attribute(msg, NR_STUN_ATTR_MESSAGE_INTEGRITY, &message_integrity)) { michael@0: TAILQ_REMOVE(&msg->attributes, message_integrity, entry); michael@0: TAILQ_INSERT_TAIL(&msg->attributes, message_integrity, entry); michael@0: } michael@0: michael@0: /* last */ michael@0: if (nr_stun_message_has_attribute(msg, NR_STUN_ATTR_FINGERPRINT, &fingerprint)) { michael@0: TAILQ_REMOVE(&msg->attributes, fingerprint, entry); michael@0: TAILQ_INSERT_TAIL(&msg->attributes, fingerprint, entry); michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: #ifdef SANITY_CHECKS michael@0: static void sanity_check_encoding_stuff(nr_stun_message *msg) michael@0: { michael@0: nr_stun_message_attribute *attr = 0; michael@0: int padding_bytes; michael@0: int l; michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Starting to sanity check encoding"); michael@0: michael@0: l = 0; michael@0: TAILQ_FOREACH(attr, &msg->attributes, entry) { michael@0: padding_bytes = 0; michael@0: if ((attr->length % 4) != 0) { michael@0: padding_bytes = 4 - (attr->length % 4); michael@0: } michael@0: assert(attr->length == (attr->encoding_length - (4 + padding_bytes))); michael@0: assert(((void*)attr->encoding) == (msg->buffer + 20 + l)); michael@0: l += attr->encoding_length; michael@0: assert((l % 4) == 0); michael@0: } michael@0: assert(l == msg->header.length); michael@0: } michael@0: #endif /* SANITY_CHECKS */ michael@0: michael@0: michael@0: int michael@0: nr_stun_encode_message(nr_stun_message *msg) michael@0: { michael@0: int r,_status; michael@0: int length_offset; michael@0: int length_offset_hold; michael@0: nr_stun_attr_info *attr_info; michael@0: nr_stun_message_attribute *attr; michael@0: int padding_bytes; michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Encoding STUN message"); michael@0: michael@0: nr_stun_fix_attribute_ordering(msg); michael@0: michael@0: msg->name = nr_stun_msg_type(msg->header.type); michael@0: msg->length = 0; michael@0: msg->header.length = 0; michael@0: michael@0: if ((r=nr_stun_encode_htons(msg->header.type, sizeof(msg->buffer), msg->buffer, &msg->length))) michael@0: ABORT(r); michael@0: if (msg->name) michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Encoded MsgType: %s", msg->name); michael@0: else michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Encoded MsgType: 0x%03x", msg->header.type); michael@0: michael@0: /* grab the offset to be used later to re-write the header length field */ michael@0: length_offset_hold = msg->length; michael@0: michael@0: if ((r=nr_stun_encode_htons(msg->header.length, sizeof(msg->buffer), msg->buffer, &msg->length))) michael@0: ABORT(r); michael@0: michael@0: if ((r=nr_stun_encode_htonl(msg->header.magic_cookie, sizeof(msg->buffer), msg->buffer, &msg->length))) michael@0: ABORT(r); michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Encoded Cookie: %08x", msg->header.magic_cookie); michael@0: michael@0: if ((r=nr_stun_encode((UCHAR*)(&msg->header.id), sizeof(msg->header.id), sizeof(msg->buffer), msg->buffer, &msg->length))) michael@0: ABORT(r); michael@0: r_dump(NR_LOG_STUN, LOG_DEBUG, "Encoded ID", (void*)&msg->header.id, sizeof(msg->header.id)); michael@0: michael@0: TAILQ_FOREACH(attr, &msg->attributes, entry) { michael@0: if ((r=nr_stun_find_attr_info(attr->type, &attr_info))) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Unrecognized attribute: 0x%04x", attr->type); michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: attr_info->name = attr_info->name; michael@0: attr->type_name = attr_info->codec->name; michael@0: attr->encoding = (nr_stun_encoded_attribute*)&msg->buffer[msg->length]; michael@0: michael@0: if (attr_info->codec->encode != 0) { michael@0: if ((r=attr_info->codec->encode(attr_info, &attr->u, msg->length, sizeof(msg->buffer), msg->buffer, &attr->encoding_length))) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Unable to encode %s", attr_info->name); michael@0: ABORT(r); michael@0: } michael@0: michael@0: msg->length += attr->encoding_length; michael@0: attr->length = attr->encoding_length - 4; /* -4 for type and length fields */ michael@0: michael@0: if (attr_info->illegal) { michael@0: if ((r=attr_info->illegal(attr_info, attr->length, &attr->u))) michael@0: ABORT(r); michael@0: } michael@0: michael@0: attr_info->codec->print(attr_info, "Encoded", &attr->u); michael@0: michael@0: if ((attr->length % 4) == 0) { michael@0: padding_bytes = 0; michael@0: } michael@0: else { michael@0: padding_bytes = 4 - (attr->length % 4); michael@0: nr_stun_encode((UCHAR*)"\0\0\0\0", padding_bytes, sizeof(msg->buffer), msg->buffer, &msg->length); michael@0: attr->encoding_length += padding_bytes; michael@0: } michael@0: michael@0: msg->header.length += attr->encoding_length; michael@0: length_offset = length_offset_hold; michael@0: (void)nr_stun_encode_htons(msg->header.length, sizeof(msg->buffer), msg->buffer, &length_offset); michael@0: } michael@0: } michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Encoded Length: %d", msg->header.length); michael@0: michael@0: assert(msg->length < NR_STUN_MAX_MESSAGE_SIZE); michael@0: michael@0: #ifdef SANITY_CHECKS michael@0: sanity_check_encoding_stuff(msg); michael@0: #endif /* SANITY_CHECKS */ michael@0: michael@0: _status=0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: michael@0: int michael@0: nr_stun_decode_message(nr_stun_message *msg, int (*get_password)(void *arg, nr_stun_message *msg, Data **password), void *arg) michael@0: { michael@0: int r,_status; michael@0: int offset; michael@0: int size; michael@0: int padding_bytes; michael@0: nr_stun_message_attribute *attr; michael@0: nr_stun_attr_info *attr_info; michael@0: Data *password; michael@0: michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Parsing STUN message of %d bytes", msg->length); michael@0: michael@0: if (!TAILQ_EMPTY(&msg->attributes)) michael@0: ABORT(R_BAD_ARGS); michael@0: michael@0: if (sizeof(nr_stun_message_header) > msg->length) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Message too small"); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: memcpy(&msg->header, msg->buffer, sizeof(msg->header)); michael@0: msg->header.type = ntohs(msg->header.type); michael@0: msg->header.length = ntohs(msg->header.length); michael@0: msg->header.magic_cookie = ntohl(msg->header.magic_cookie); michael@0: michael@0: msg->name = nr_stun_msg_type(msg->header.type); michael@0: michael@0: if (msg->name) michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Parsed MsgType: %s", msg->name); michael@0: else michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Parsed MsgType: 0x%03x", msg->header.type); michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Parsed Length: %d", msg->header.length); michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Parsed Cookie: %08x", msg->header.magic_cookie); michael@0: r_dump(NR_LOG_STUN, LOG_DEBUG, "Parsed ID", (void*)&msg->header.id, sizeof(msg->header.id)); michael@0: michael@0: if (msg->header.length + sizeof(msg->header) != msg->length) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Inconsistent message header length: %d/%d", michael@0: msg->header.length, msg->length); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: size = msg->header.length; michael@0: michael@0: if ((size % 4) != 0) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Illegal message size: %d", msg->header.length); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: offset = sizeof(msg->header); michael@0: michael@0: while (size > 0) { michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "size = %d", size); michael@0: michael@0: if (size < 4) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Illegal message length: %d", size); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: if ((r=nr_stun_message_attribute_create(msg, &attr))) michael@0: ABORT(R_NO_MEMORY); michael@0: michael@0: attr->encoding = (nr_stun_encoded_attribute*)&msg->buffer[offset]; michael@0: attr->type = ntohs(attr->encoding->type); michael@0: attr->length = ntohs(attr->encoding->length); michael@0: attr->encoding_length = attr->length + 4; michael@0: michael@0: if ((attr->length % 4) != 0) { michael@0: padding_bytes = 4 - (attr->length % 4); michael@0: attr->encoding_length += padding_bytes; michael@0: } michael@0: michael@0: if ((attr->encoding_length) > size) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Attribute length larger than remaining message size: %d/%d", attr->encoding_length, size); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: if ((r=nr_stun_find_attr_info(attr->type, &attr_info))) { michael@0: if (attr->type <= 0x7FFF) michael@0: ++msg->comprehension_required_unknown_attributes; michael@0: else michael@0: ++msg->comprehension_optional_unknown_attributes; michael@0: r_log(NR_LOG_STUN, LOG_INFO, "Unrecognized attribute: 0x%04x", attr->type); michael@0: } michael@0: else { michael@0: attr_info->name = attr_info->name; michael@0: attr->type_name = attr_info->codec->name; michael@0: michael@0: if (attr->type == NR_STUN_ATTR_MESSAGE_INTEGRITY) { michael@0: if (get_password && get_password(arg, msg, &password) == 0) { michael@0: if (password->len > sizeof(attr->u.message_integrity.password)) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Password too long: %d bytes", password->len); michael@0: ABORT(R_FAILED); michael@0: } michael@0: michael@0: memcpy(attr->u.message_integrity.password, password->data, password->len); michael@0: attr->u.message_integrity.passwordlen = password->len; michael@0: } michael@0: else { michael@0: /* set to user "not found" */ michael@0: attr->u.message_integrity.unknown_user = 1; michael@0: } michael@0: } michael@0: else if (attr->type == NR_STUN_ATTR_OLD_XOR_MAPPED_ADDRESS) { michael@0: attr->type = NR_STUN_ATTR_XOR_MAPPED_ADDRESS; michael@0: r_log(NR_LOG_STUN, LOG_INFO, "Translating obsolete XOR-MAPPED-ADDRESS type"); michael@0: } michael@0: michael@0: if ((r=attr_info->codec->decode(attr_info, attr->length, msg->buffer, offset+4, msg->length, &attr->u))) { michael@0: if (r == SKIP_ATTRIBUTE_DECODE) { michael@0: r_log(NR_LOG_STUN, LOG_INFO, "Skipping %s", attr_info->name); michael@0: } michael@0: else { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Unable to parse %s", attr_info->name); michael@0: } michael@0: michael@0: attr->invalid = 1; michael@0: } michael@0: else { michael@0: attr_info->codec->print(attr_info, "Parsed", &attr->u); michael@0: michael@0: #ifdef USE_STUN_PEDANTIC michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "Before pedantic attr_info checks"); michael@0: if (attr_info->illegal) { michael@0: if ((r=attr_info->illegal(attr_info, attr->length, &attr->u))) { michael@0: r_log(NR_LOG_STUN, LOG_WARNING, "Failed pedantic attr_info checks"); michael@0: ABORT(r); michael@0: } michael@0: } michael@0: r_log(NR_LOG_STUN, LOG_DEBUG, "After pedantic attr_info checks"); michael@0: #endif /* USE_STUN_PEDANTIC */ michael@0: } michael@0: } michael@0: michael@0: offset += attr->encoding_length; michael@0: size -= attr->encoding_length; michael@0: } michael@0: michael@0: #ifdef SANITY_CHECKS michael@0: sanity_check_encoding_stuff(msg); michael@0: #endif /* SANITY_CHECKS */ michael@0: michael@0: _status=0; michael@0: abort: michael@0: return _status; michael@0: } michael@0: