/* * This file is part of the Sofia-SIP package * * Copyright (C) 2005 Nokia Corporation. * * Contact: Pekka Pessi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /**@internal * @file stun_common.c * @brief * * @author Tat Chan * @author Kai Vehmanen * * @date Created: Fri Oct 3 13:40:41 2003 ppessi * */ #include "config.h" #ifdef USE_TURN #include "../turn/turn_common.h" #undef STUN_A_LAST_MANDATORY #define STUN_A_LAST_MANDATORY TURN_LARGEST_ATTRIBUTE #endif #include "stun_internal.h" #include #include #include #if HAVE_FUNC #elif HAVE_FUNCTION #define __func__ __FUNCTION__ #else #define __func__ "stun_common" #endif const char stun_400_Bad_request[] = "Bad Request", stun_401_Unauthorized[] = "Unauthorized", stun_420_Unknown_attribute[] = "Unknown Attribute", stun_430_Stale_credentials[] = "Stale Credentials", stun_431_Integrity_check_failure[] = "Integrity Check Failure", stun_432_Missing_username[] = "Missing Username", stun_433_Use_tls[] = "Use TLS", #ifdef USE_TURN turn_434_Missing_realm[] = "Missing Realm", turn_435_Missing_nonce[] = "Missing Nonce", turn_436_Unknown_username[] = "Unknown Username", turn_437_No_binding[] = "No Binding", turn_439_Illegal_port[] = "Illegal Port", #endif stun_500_Server_error[] = "Server Error", stun_600_Global_failure[] = "Global Failure"; #define set16(b, offset, value) \ (((b)[(offset) + 0] = ((value) >> 8) & 255), \ ((b)[(offset) + 1] = (value) & 255)) #define get16(b, offset) \ (((b)[(offset) + 0] << 8) | \ ((b)[(offset) + 1] << 0)) int stun_parse_message(stun_msg_t *msg) { unsigned len; int i; unsigned char *p; /* parse header first */ p = msg->enc_buf.data; msg->stun_hdr.msg_type = get16(p, 0); msg->stun_hdr.msg_len = get16(p, 2); memcpy(msg->stun_hdr.tran_id, p + 4, STUN_TID_BYTES); SU_DEBUG_5(("%s: Parse STUN message: Length = %d\n", __func__, msg->stun_hdr.msg_len)); /* parse attributes */ len = msg->stun_hdr.msg_len; p = msg->enc_buf.data + 20; msg->stun_attr = NULL; while (len > 0) { i = stun_parse_attribute(msg, p); if (i <= 0) { SU_DEBUG_3(("%s: Error parsing attribute.\n", __func__)); return -1; } p += i; len -= i; } return 0; } int stun_parse_attribute(stun_msg_t *msg, unsigned char *p) { int len; uint16_t attr_type; stun_attr_t *attr, *next; attr_type = get16(p, 0); len = get16(p, 2); SU_DEBUG_5(("%s: received attribute: Type %02X, Length %d - %s\n", __func__, attr_type, len, stun_attr_phrase(attr_type))); if (attr_type > STUN_A_LAST_MANDATORY && attr_type < STUN_A_OPTIONAL) { return -1; } attr = (stun_attr_t *)calloc(1, sizeof(stun_attr_t)); if (!attr) return -1; attr->next = NULL; attr->attr_type = attr_type; p += 4; switch (attr->attr_type) { case MAPPED_ADDRESS: case RESPONSE_ADDRESS: case SOURCE_ADDRESS: case CHANGED_ADDRESS: case REFLECTED_FROM: #ifdef USE_TURN case TURN_ALTERNATE_SERVER: case TURN_DESTINATION_ADDRESS: case TURN_SOURCE_ADDRESS: #endif if (stun_parse_attr_address(attr, p, len) < 0) { free(attr); return -1; } break; case ERROR_CODE: if (stun_parse_attr_error_code(attr, p, len) <0) { free(attr); return -1; } break; case UNKNOWN_ATTRIBUTES: if(stun_parse_attr_unknown_attributes(attr, p, len) <0) { free(attr); return -1; } break; case CHANGE_REQUEST: #ifdef USE_TURN case TURN_LIFETIME: case TURN_MAGIC_COOKIE: case TURN_BANDWIDTH: #endif if (stun_parse_attr_uint32(attr, p, len) <0) { free(attr); return -1; } break; case USERNAME: case PASSWORD: case STUN_A_REALM: case STUN_A_NONCE: #ifdef USE_TURN case TURN_DATA: case TURN_NONCE: #endif if (stun_parse_attr_buffer(attr, p, len) <0) { free(attr); return -1; } break; default: /* just copy as is */ attr->pattr = NULL; attr->enc_buf.size = len; attr->enc_buf.data = (unsigned char *) malloc(len); memcpy(attr->enc_buf.data, p, len); break; } /* skip to end of list */ if(msg->stun_attr==NULL) { msg->stun_attr = attr; } else { next = msg->stun_attr; while(next->next!=NULL) { next = next->next; } next->next = attr; } return len+4; } int stun_parse_attr_address(stun_attr_t *attr, const unsigned char *p, unsigned len) { su_sockaddr_t *addr; int addrlen; char ipaddr[SU_ADDRSIZE + 2]; if (len != 8) { return -1; } addrlen = sizeof(su_sockaddr_t); addr = (su_sockaddr_t *) malloc(addrlen); if (*(p+1) == 1) { /* expected value for IPv4 */ addr->su_sin.sin_family = AF_INET; } else { free(addr); return -1; } memcpy(&addr->su_sin.sin_port, p + 2, 2); memcpy(&addr->su_sin.sin_addr.s_addr, p + 4, 4); SU_DEBUG_5(("%s: address attribute: %s:%d\n", __func__, su_inet_ntop(addr->su_family, SU_ADDR(addr), ipaddr, sizeof(ipaddr)), (unsigned) ntohs(addr->su_sin.sin_port))); attr->pattr = addr; stun_init_buffer(&attr->enc_buf); return 0; } int stun_parse_attr_error_code(stun_attr_t *attr, const unsigned char *p, unsigned len) { uint32_t tmp; stun_attr_errorcode_t *error; memcpy(&tmp, p, sizeof(uint32_t)); tmp = ntohl(tmp); error = (stun_attr_errorcode_t *) malloc(sizeof(*error)); error->code = (tmp & STUN_EC_CLASS)*100 + (tmp & STUN_EC_NUM); error->phrase = (char *) malloc(len-3); strncpy(error->phrase, (char*)p+4, len-4); error->phrase[len - 4] = '\0'; attr->pattr = error; stun_init_buffer(&attr->enc_buf); return 0; } int stun_parse_attr_uint32(stun_attr_t *attr, const unsigned char *p, unsigned len) { uint32_t tmp; stun_attr_changerequest_t *cr; cr = (stun_attr_changerequest_t *) malloc(sizeof(*cr)); memcpy(&tmp, p, sizeof(uint32_t)); cr->value = ntohl(tmp); attr->pattr = cr; stun_init_buffer(&attr->enc_buf); return 0; } int stun_parse_attr_buffer(stun_attr_t *attr, const unsigned char *p, unsigned len) { stun_buffer_t *buf; buf = (stun_buffer_t *) malloc(sizeof(stun_buffer_t)); buf->size = len; buf->data = (unsigned char *) malloc(len); memcpy(buf->data, p, len); attr->pattr = buf; stun_init_buffer(&attr->enc_buf); return 0; } int stun_parse_attr_unknown_attributes(stun_attr_t *attr, const unsigned char *p, unsigned len) { return 0; } /** scan thru attribute list and return the next requested attr */ stun_attr_t *stun_get_attr(stun_attr_t *attr, uint16_t attr_type) { stun_attr_t *p; for (p = attr; p != NULL; p = p->next) { if (p->attr_type == attr_type) break; } return p; } void stun_init_buffer(stun_buffer_t *p) { p->data = NULL; p->size = 0; } int stun_free_buffer(stun_buffer_t *p) { if (p->data) free(p->data), p->data = NULL; p->size = 0; return 0; } int stun_copy_buffer(stun_buffer_t *p, stun_buffer_t *p2) { stun_free_buffer(p); /* clean up existing data */ p->size = p2->size; p->data = (unsigned char *) malloc(p->size); memcpy(p->data, p2->data, p->size); return p->size; } const char *stun_response_phrase(int status) { if (status <100 || status >600) return NULL; switch (status) { case STUN_400_BAD_REQUEST: return stun_400_Bad_request; case STUN_401_UNAUTHORIZED: return stun_401_Unauthorized; case STUN_420_UNKNOWN_ATTRIBUTE: return stun_420_Unknown_attribute; case STUN_430_STALE_CREDENTIALS: return stun_430_Stale_credentials; case STUN_431_INTEGRITY_CHECK_FAILURE: return stun_431_Integrity_check_failure; case STUN_432_MISSING_USERNAME: return stun_432_Missing_username; case STUN_433_USE_TLS: return stun_433_Use_tls; #ifdef USE_TURN case TURN_MISSING_REALM: return turn_434_Missing_realm; case TURN_MISSING_NONCE: return turn_435_Missing_nonce; case TURN_UNKNOWN_USERNAME: return turn_436_Unknown_username; case TURN_NO_BINDING: return turn_437_No_binding; case TURN_ILLEGAL_PORT: return turn_439_Illegal_port; #endif case STUN_500_SERVER_ERROR: return stun_500_Server_error; case STUN_600_GLOBAL_FAILURE: return stun_600_Global_failure; } return "Response"; } /** The set of functions encodes the corresponding attribute to * network format, and save the result to the enc_buf. Return the * size of the buffer. */ /* This function is used to encode any attribute of the form ADDRESS */ int stun_encode_address(stun_attr_t *attr) { stun_attr_sockaddr_t *a; uint16_t tmp; a = (stun_attr_sockaddr_t *)attr->pattr; if (stun_encode_type_len(attr, 8) < 0) { return -1; } tmp = htons(0x01); /* FAMILY = 0x01 */ memcpy(attr->enc_buf.data+4, &tmp, sizeof(tmp)); memcpy(attr->enc_buf.data+6, &a->sin_port, 2); memcpy(attr->enc_buf.data+8, &a->sin_addr.s_addr, 4); return attr->enc_buf.size; } int stun_encode_uint32(stun_attr_t *attr) { uint32_t tmp; if (stun_encode_type_len(attr, 4) < 0) { return -1; } tmp = htonl(((stun_attr_changerequest_t *) attr->pattr)->value); memcpy(attr->enc_buf.data+4, &tmp, 4); return attr->enc_buf.size; } int stun_encode_error_code(stun_attr_t *attr) { short int class, num; size_t phrase_len, padded; stun_attr_errorcode_t *error; error = (stun_attr_errorcode_t *) attr->pattr; class = error->code / 100; num = error->code % 100; phrase_len = strlen(error->phrase); if (phrase_len + 8 > 65536) phrase_len = 65536 - 8; /* note: align the phrase len (see RFC3489:11.2.9) */ padded = phrase_len + (phrase_len % 4 == 0 ? 0 : 4 - (phrase_len % 4)); /* note: error-code has four octets of headers plus the * reason field -> len+4 octets */ if (stun_encode_type_len(attr, (uint16_t)(padded + 4)) < 0) { return -1; } else { assert(attr->enc_buf.size == padded + 8); memset(attr->enc_buf.data+4, 0, 2); attr->enc_buf.data[6] = class; attr->enc_buf.data[7] = num; /* note: 4 octets of TLV header and 4 octets of error-code header */ memcpy(attr->enc_buf.data+8, error->phrase, phrase_len); memset(attr->enc_buf.data + 8 + phrase_len, 0, padded - phrase_len); } return attr->enc_buf.size; } int stun_encode_buffer(stun_attr_t *attr) { stun_buffer_t *a; a = (stun_buffer_t *)attr->pattr; assert(a->size < 65536); if (stun_encode_type_len(attr, (uint16_t)a->size) < 0) { return -1; } memcpy(attr->enc_buf.data+4, a->data, a->size); return attr->enc_buf.size; } #if defined(HAVE_OPENSSL) int stun_encode_message_integrity(stun_attr_t *attr, unsigned char *buf, int len, stun_buffer_t *pwd) { int padded_len; unsigned int dig_len; unsigned char *padded_text = NULL; void *sha1_hmac; if (stun_encode_type_len(attr, 20) < 0) { return -1; } /* zero padding */ if (len % 64 != 0) { padded_len = len + (64 - (len % 64)); padded_text = (unsigned char *) malloc(padded_len); memcpy(padded_text, buf, len); memset(padded_text + len, 0, padded_len - len); sha1_hmac = HMAC(EVP_sha1(), pwd->data, pwd->size, padded_text, padded_len, NULL, &dig_len); } else { sha1_hmac = HMAC(EVP_sha1(), pwd->data, pwd->size, buf, len, NULL, &dig_len); } assert(dig_len == 20); memcpy(attr->enc_buf.data + 4, sha1_hmac, 20); free(padded_text); return attr->enc_buf.size; } #else int stun_encode_message_integrity(stun_attr_t *attr, unsigned char *buf, int len, stun_buffer_t *pwd) { return 0; } #endif /* HAVE_OPENSSL */ /** this function allocates the enc_buf, fills in type, length */ int stun_encode_type_len(stun_attr_t *attr, uint16_t len) { uint16_t tmp; attr->enc_buf.data = (unsigned char *) malloc(len + 4); memset(attr->enc_buf.data, 0, len + 4); tmp = htons(attr->attr_type); memcpy(attr->enc_buf.data, &tmp, 2); tmp = htons(len); memcpy(attr->enc_buf.data + 2, &tmp, 2); attr->enc_buf.size = len + 4; return 0; } /** * Validate the message integrity based on given * STUN password 'pwd'. The received content should be * in msg->enc_buf. */ int stun_validate_message_integrity(stun_msg_t *msg, stun_buffer_t *pwd) { #if defined(HAVE_OPENSSL) int padded_len, len; unsigned int dig_len; unsigned char dig[20]; /* received sha1 digest */ unsigned char *padded_text; #endif /* password NULL so shared-secret not established and messege integrity checks can be skipped */ if (pwd->data == NULL) return 0; /* otherwise the check must match */ #if defined(HAVE_OPENSSL) /* message integrity not received */ if (stun_get_attr(msg->stun_attr, MESSAGE_INTEGRITY) == NULL) { SU_DEBUG_5(("%s: error: message integrity missing.\n", __func__)); return -1; } /* zero padding */ len = msg->enc_buf.size - 24; padded_len = len + (len % 64 == 0 ? 0 : 64 - (len % 64)); padded_text = (unsigned char *) malloc(padded_len); memset(padded_text, 0, padded_len); memcpy(padded_text, msg->enc_buf.data, len); memcpy(dig, HMAC(EVP_sha1(), pwd->data, pwd->size, padded_text, padded_len, NULL, &dig_len), 20); if (memcmp(dig, msg->enc_buf.data + msg->enc_buf.size - 20, 20) != 0) { /* does not match, but try the test server's password */ if (memcmp(msg->enc_buf.data+msg->enc_buf.size-20, "hmac-not-implemented", 20) != 0) { SU_DEBUG_5(("%s: error: message digest problem.\n", __func__)); return -1; } } else { SU_DEBUG_5(("%s: message integrity validated.\n", __func__)); } free(padded_text); return 0; #else /* HAVE_OPENSSL */ return -1; #endif } void debug_print(stun_buffer_t *buf) { unsigned i; for(i = 0; i < buf->size/4; i++) { SU_DEBUG_9(("%02x %02x %02x %02x\n", *(buf->data + i*4), *(buf->data + i*4 +1), *(buf->data + i*4 +2), *(buf->data + i*4 +3))); if (i == 4) SU_DEBUG_9(("---------------------\n")); } SU_DEBUG_9(("\n")); } int stun_init_message(stun_msg_t *msg) { msg->stun_hdr.msg_type = 0; msg->stun_hdr.msg_len = 0; msg->stun_attr = NULL; stun_init_buffer(&msg->enc_buf); return 0; } int stun_free_message(stun_msg_t *msg) { stun_attr_t *p, *p2; /* clearing header */ memset(&msg->stun_hdr, 0, sizeof msg->stun_hdr); /* clearing attr */ p = msg->stun_attr; while(p) { if(p->pattr) { switch(p->attr_type) { case USERNAME: case PASSWORD: #ifdef USE_TURN case TURN_DATA: case TURN_NONCE: #endif stun_free_buffer(p->pattr); break; default: free(p->pattr); } } stun_free_buffer(&p->enc_buf); p2 = p->next; free(p); p = p2; } msg->stun_attr = NULL; /* clearing buffer */ stun_free_buffer(&msg->enc_buf); return 0; } int stun_send_message(su_socket_t s, su_sockaddr_t *to_addr, stun_msg_t *msg, stun_buffer_t *pwd) { int err; char ipaddr[SU_ADDRSIZE + 2]; stun_attr_t **a, *b; stun_encode_message(msg, pwd); err = su_sendto(s, msg->enc_buf.data, msg->enc_buf.size, 0, to_addr, SU_SOCKADDR_SIZE(to_addr)); free(msg->enc_buf.data), msg->enc_buf.data = NULL; msg->enc_buf.size = 0; for (a = &msg->stun_attr; *a;) { if ((*a)->pattr) free((*a)->pattr), (*a)->pattr = NULL; if ((*a)->enc_buf.data) free((*a)->enc_buf.data), (*a)->enc_buf.data = NULL; b = *a; b = b->next; free(*a); *a = NULL; *a = b; } if (err > 0) { su_inet_ntop(to_addr->su_family, SU_ADDR(to_addr), ipaddr, sizeof(ipaddr)); SU_DEBUG_5(("%s: message sent to %s:%u\n", __func__, ipaddr, ntohs(to_addr->su_port))); debug_print(&msg->enc_buf); } else STUN_ERROR(errno, sendto); return err; } /** Send a STUN message. * This will convert the stun_msg_t to the binary format based on the * spec */ int stun_encode_message(stun_msg_t *msg, stun_buffer_t *pwd) { int z = -1, len, buf_len = 0; unsigned char *buf; stun_attr_t *attr, *msg_int=NULL; if (msg->enc_buf.data == NULL) { /* convert msg to binary format */ /* convert attributes to binary format for transmission */ len = 0; for (attr = msg->stun_attr; attr ; attr = attr->next) { switch(attr->attr_type) { case RESPONSE_ADDRESS: case MAPPED_ADDRESS: case SOURCE_ADDRESS: case CHANGED_ADDRESS: case REFLECTED_FROM: #ifdef USE_TURN case TURN_ALTERNATE_SERVER: case TURN_DESTINATION_ADDRESS: case TURN_SOURCE_ADDRESS: #endif z = stun_encode_address(attr); break; case CHANGE_REQUEST: #ifdef USE_TURN case TURN_LIFETIME: case TURN_MAGIC_COOKIE: case TURN_BANDWIDTH: #endif z = stun_encode_uint32(attr); break; case USERNAME: case PASSWORD: #ifdef USE_TURN case TURN_REALM: case TURN_NONCE: case TURN_DATA: #endif z = stun_encode_buffer(attr); break; case MESSAGE_INTEGRITY: msg_int = attr; z = 24; break; case ERROR_CODE: z = stun_encode_error_code(attr); default: break; } if(z < 0) return z; len += z; } msg->stun_hdr.msg_len = len; buf_len = 20 + msg->stun_hdr.msg_len; buf = (unsigned char *) malloc(buf_len); /* convert to binary format for transmission */ set16(buf, 0, msg->stun_hdr.msg_type); set16(buf, 2, msg->stun_hdr.msg_len); memcpy(buf + 4, msg->stun_hdr.tran_id, STUN_TID_BYTES); len = 20; /* attaching encoded attributes */ attr = msg->stun_attr; while(attr) { /* attach only if enc_buf is not null */ if(attr->enc_buf.data && attr->attr_type != MESSAGE_INTEGRITY) { memcpy(buf+len, (void *)attr->enc_buf.data, attr->enc_buf.size); len += attr->enc_buf.size; } attr = attr->next; } if (msg_int) { /* compute message integrity */ if(stun_encode_message_integrity(msg_int, buf, len, pwd)!=24) { free(buf); return -1; } memcpy(buf+len, (void *)msg_int->enc_buf.data, msg_int->enc_buf.size); } /* save binary buffer for future reference */ if (msg->enc_buf.data) free(msg->enc_buf.data); msg->enc_buf.data = buf; msg->enc_buf.size = buf_len; } return 0; } #include #include #include char *stun_determine_ip_address(int family) { char *local_ip_address; su_localinfo_t *li = NULL, hints[1] = {{ LI_CANONNAME|LI_NUMERIC }}; int error; size_t address_size; struct sockaddr_in *sa = NULL; su_sockaddr_t *temp; hints->li_family = family; hints->li_canonname = getenv("HOSTADDRESS"); if ((error = su_getlocalinfo(hints, &li)) < 0) { SU_DEBUG_5(("%s: stun_determine_ip_address, su_getlocalinfo: %s\n", __func__, su_gli_strerror(error))); return NULL; } temp = li->li_addr; sa = &temp->su_sin; address_size = strlen(inet_ntoa(sa->sin_addr)); local_ip_address = malloc(address_size + 1); strcpy(local_ip_address, (char *) inet_ntoa(sa->sin_addr)); /* otherwise? */ su_freelocalinfo(li); return local_ip_address; } const char *stun_attr_phrase(uint16_t type) { switch(type) { case MAPPED_ADDRESS: return "MAPPED-ADDRESS"; case RESPONSE_ADDRESS: return "RESPONSE-ADDRESS"; case CHANGE_REQUEST: return "CHANGE-REQUEST"; case SOURCE_ADDRESS: return "SOURCE-ADDRESS"; case CHANGED_ADDRESS: return "CHANGED-ADDRESS"; case USERNAME: return "USERNAME"; case PASSWORD: return "PASSWORD"; case MESSAGE_INTEGRITY: return "MESSAGE-INTEGRITY"; case ERROR_CODE: return "ERROR-CODE"; case UNKNOWN_ATTRIBUTES: return "UNKNOWN-ATTRIBUTES"; case REFLECTED_FROM: return "REFLECTED-FROM"; case STUN_A_ALTERNATE_SERVER: case STUN_A_ALTERNATE_SERVER_DEP: return "ALTERNATE-SERVER"; case STUN_A_REALM: return "REALM"; case STUN_A_NONCE: return "NONCE"; case STUN_A_XOR_MAPPED_ADDRESS: return "XOR-MAPPED-ADDRESS"; #ifdef USE_TURN case TURN_REALM: return "REALM"; case TURN_LIFETIME: return "LIFETIME"; case TURN_ALTERNATE_SERVER: return "ALTERNATE_SERVER"; case TURN_MAGIC_COOKIE: return "MAGIC_COOKIE"; case TURN_BANDWIDTH: return "BANDWIDTH"; case TURN_DESTINATION_ADDRESS: return "DESTINATION_ADDRESS"; case TURN_SOURCE_ADDRESS: return "SOURCE_ADDRESS"; case TURN_DATA: return "DATA"; case TURN_NONCE: return "NONCE"; #endif default: return "Attribute undefined"; } }