/* * mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2013-2014, Grasshopper * * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * * The Initial Developer of the Original Code is Grasshopper * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * Chris Rienzo * * iks_helpers.c -- iksemel helpers * */ #include "iks_helpers.h" #include #include #include #undef XMPP_ERROR #define XMPP_ERROR(def_name, name, type) \ const struct xmpp_error def_name##_val = { name, type }; \ const struct xmpp_error *def_name = &def_name##_val; #include "xmpp_errors.def" /** * Create a event * @param name the event name * @param namespace the event namespace * @param from * @param to * @return the event XML node */ iks *iks_new_presence(const char *name, const char *namespace, const char *from, const char *to) { iks *event = iks_new("presence"); iks *x; /* iks makes copies of attrib name and value */ iks_insert_attrib(event, "from", from); iks_insert_attrib(event, "to", to); x = iks_insert(event, name); if (!zstr(namespace)) { iks_insert_attrib(x, "xmlns", namespace); } return event; } /** * Create error response from request * @param req the request * @param from * @param to * @param err the XMPP stanza error * @return the error response */ iks *iks_new_error(iks *req, const struct xmpp_error *err) { iks *response = iks_copy(req); iks *x; /* */ iks_insert_attrib(response, "from", iks_find_attrib(req, "to")); iks_insert_attrib(response, "to", iks_find_attrib(req, "from")); iks_insert_attrib(response, "type", "error"); /* */ x = iks_insert(response, "error"); iks_insert_attrib(x, "type", err->type); /* e.g. */ x = iks_insert(x, err->name); iks_insert_attrib(x, "xmlns", IKS_NS_XMPP_STANZAS); return response; } /** * Create error response from request * @param req the request * @param from * @param to * @param err the XMPP stanza error * @param detail_text optional text to include in message * @return the error response */ iks *iks_new_error_detailed(iks *req, const struct xmpp_error *err, const char *detail_text) { iks *reply = iks_new_error(req, err); if (!zstr(detail_text)) { iks *error = iks_find(reply, "error"); iks *text = iks_insert(error, "text"); iks_insert_attrib(text, "xml:lang", "en"); iks_insert_attrib(text, "xmlns", IKS_NS_XMPP_STANZAS); iks_insert_cdata(text, detail_text, strlen(detail_text)); } return reply; } /** * Create error response from request * @param req the request * @param from * @param to * @param err the XMPP stanza error * @param detail_text_format format string * @param ... * @return the error response */ iks *iks_new_error_detailed_printf(iks *req, const struct xmpp_error *err, const char *detail_text_format, ...) { iks *reply = NULL; char *data; va_list ap; int ret; va_start(ap, detail_text_format); ret = switch_vasprintf(&data, detail_text_format, ap); va_end(ap); if (ret == -1) { return NULL; } reply = iks_new_error_detailed(req, err, data); free(data); return reply; } /** * Create result response from request * @param iq the request * @return the result response */ iks *iks_new_iq_result(iks *iq) { iks *response = iks_new("iq"); iks_insert_attrib(response, "from", iks_find_attrib(iq, "to")); iks_insert_attrib(response, "to", iks_find_attrib(iq, "from")); iks_insert_attrib(response, "type", "result"); iks_insert_attrib(response, "id", iks_find_attrib(iq, "id")); return response; } /** * Get attribute value of node, returning empty string if non-existent or not set. * @param xml the XML node to search * @param attrib the Attribute name * @return the attribute value */ const char *iks_find_attrib_soft(iks *xml, const char *attrib) { char *value = iks_find_attrib(xml, attrib); return zstr(value) ? "" : value; } /** * Get attribute value of node, returning default value if missing. The default value * is set in the node if missing. * @param xml the XML node to search * @param attrib the Attribute name * @return the attribute value */ const char *iks_find_attrib_default(iks *xml, const char *attrib, const char *def) { char *value = iks_find_attrib(xml, attrib); if (!value) { iks_insert_attrib(xml, attrib, def); return def; } return value; } /** * Get attribute integer value of node * @param xml the XML node to search * @param attrib the Attribute name * @return the attribute value */ int iks_find_int_attrib(iks *xml, const char *attrib) { return atoi(iks_find_attrib_soft(xml, attrib)); } /** * Get attribute boolean value of node * @param xml the XML node to search * @param attrib the Attribute name * @return the attribute value */ int iks_find_bool_attrib(iks *xml, const char *attrib) { return switch_true(iks_find_attrib_soft(xml, attrib)); } /** * Get attribute double value of node * @param xml the XML node to search * @param attrib the Attribute name * @return the attribute value */ double iks_find_decimal_attrib(iks *xml, const char *attrib) { return atof(iks_find_attrib_soft(xml, attrib)); } /** * Get attribute character value of node * @param xml the XML node to search * @param attrib the Attribute name * @return the attribute value */ char iks_find_char_attrib(iks *xml, const char *attrib) { return iks_find_attrib_soft(xml, attrib)[0]; } /** * Convert iksemel XML node type to string * @param type the XML node type * @return the string value of type or "UNKNOWN" */ const char *iks_node_type_to_string(int type) { switch(type) { case IKS_NODE_START: return "NODE_START"; case IKS_NODE_NORMAL: return "NODE_NORMAL"; case IKS_NODE_ERROR: return "NODE_ERROR"; case IKS_NODE_STOP: return "NODE_START"; default: return "NODE_UNKNOWN"; } } /** * Convert iksemel error code to string * @param err the iksemel error code * @return the string value of error or "UNKNOWN" */ const char *iks_net_error_to_string(int err) { switch (err) { case IKS_OK: return "OK"; case IKS_NOMEM: return "NOMEM"; case IKS_BADXML: return "BADXML"; case IKS_HOOK: return "HOOK"; case IKS_NET_NODNS: return "NET_NODNS"; case IKS_NET_NOSOCK: return "NET_NOSOCK"; case IKS_NET_NOCONN: return "NET_NOCONN"; case IKS_NET_RWERR: return "NET_RWERR"; case IKS_NET_NOTSUPP: return "NET_NOTSUPP"; case IKS_NET_TLSFAIL: return "NET_TLSFAIL"; case IKS_NET_DROPPED: return "NET_DROPPED"; case IKS_NET_UNKNOWN: return "NET_UNKNOWN"; default: return "UNKNOWN"; } } /** * Insert attribute using format string * @param xml node to insert attribute into * @param name of attribute * @param fmt format string * @param ... format string args */ iks *iks_insert_attrib_printf(iks *xml, const char *name, const char *fmt, ...) { iks *node; char *data; va_list ap; int ret; va_start(ap, fmt); ret = switch_vasprintf(&data, fmt, ap); va_end(ap); if (ret == -1) { return NULL; } node = iks_insert_attrib(xml, name, data); free(data); return node; } /** * @param value to match * @param rule to check * @return true if value is one of the comma-separated values in rule */ int value_matches(const char *value, const char *rule) { if (rule && *rule && value && *value && !strchr(value, ',')) { const char *begin = strstr(rule, value); const char *end = begin + strlen(value); if (!begin) { return 0; } if ((begin == rule || *(begin - 1) == ',') && (*end == ',' || *end == '\0')) { return 1; } /* substring matched... try farther down the string */ return value_matches(value, end); } return 0; } /** * Validate boolean * @param value * @return SWTICH_TRUE if boolean */ int iks_attrib_is_bool(const char *value) { if (value && *value && (!strcasecmp("true", value) || !strcasecmp("false", value))) { return SWITCH_TRUE; } return SWITCH_FALSE; } /** * Validate integer * @param value * @return SWTICH_TRUE if not negative */ int iks_attrib_is_not_negative(const char *value) { if (value && *value && switch_is_number(value)) { int value_i = atoi(value); if (value_i >= 0) { return SWITCH_TRUE; } } return SWITCH_FALSE; } /** * Validate integer * @param value * @return SWTICH_TRUE if positive */ int iks_attrib_is_positive(const char *value) { if (value && *value && switch_is_number(value)) { int value_i = atoi(value); if (value_i > 0) { return SWITCH_TRUE; } } return SWITCH_FALSE; } /** * Validate integer * @param value * @return SWTICH_TRUE if positive or -1 */ int iks_attrib_is_positive_or_neg_one(const char *value) { if (value && *value && switch_is_number(value)) { int value_i = atoi(value); if (value_i == -1 || value_i > 0) { return SWITCH_TRUE; } } return SWITCH_FALSE; } /** * Validate string * @param value * @return SWTICH_TRUE */ int iks_attrib_is_any(const char *value) { return SWITCH_TRUE; } /** * Validate decimal * @param value * @return SWTICH_TRUE if 0.0 <= x <= 1.0 */ int iks_attrib_is_decimal_between_zero_and_one(const char *value) { if (value && *value && switch_is_number(value)) { double value_d = atof(value); if (value_d >= 0.0 && value_d <= 1.0) { return SWITCH_TRUE; } } return SWITCH_FALSE; } /** * Validate dtmf digit * @param value * @return SWITCH_TRUE if 0-9,a,b,c,d,A,B,C,D,*,# */ int iks_attrib_is_dtmf_digit(const char *value) { if (value && *value && strlen(value) == 1) { switch (*value) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'A': case 'a': case 'B': case 'b': case 'C': case 'c': case 'D': case 'd': case '*': case '#': return SWITCH_TRUE; } } return SWITCH_FALSE; } /** * @param fn to evaluate attribute * @param attrib to evaluate * @return true if not set or is valid */ int validate_optional_attrib(iks_attrib_validation_function fn, const char *attrib) { if (!attrib || !*attrib) { return SWITCH_TRUE; } return fn(attrib); } #define IKS_SHA256_HEX_DIGEST_LENGTH ((SHA256_DIGEST_LENGTH * 2) + 1) /** * Convert hash to a hex string. * @param hash hash to convert * @param str buffer to store hash - this buffer must be hashlen * 2 + 1 in size. */ static void iks_hash_to_hex_string(unsigned char *hash, int hashlen, unsigned char *str) { static const char *HEX = "0123456789abcdef"; int i; /* convert to hex string with in-place algorithm */ for (i = hashlen - 1; i >= 0; i--) { str[i * 2 + 1] = HEX[hash[i] & 0x0f]; str[i * 2] = HEX[(hash[i] >> 4) & 0x0f]; } str[hashlen * 2] = '\0'; } /** * Generate SHA-256 hash of value as hex string * @param data to hash * @param datalen length of data to hash * @return hash as a hex string */ static void iks_sha256_hex_string(const unsigned char *data, int datalen, unsigned char *hash) { /* hash data */ SHA256(data, datalen, hash); iks_hash_to_hex_string(hash, SHA256_DIGEST_LENGTH, hash); } /** * Generate HMAC SHA-256 * @param key the key * @param keylen length of key * @param message the message * @param messagelen length of message * @param hash buffer to store the hash - must be IKS_SHA256_HEX_DIGEST_LENGTH */ static void iks_hmac_sha256_hex_string(const unsigned char *key, int keylen, const unsigned char *message, int messagelen, unsigned char *hash) { unsigned int hash_len = SHA256_DIGEST_LENGTH; HMAC(EVP_sha256(), key, keylen, message, messagelen, hash, &hash_len); iks_hash_to_hex_string(hash, SHA256_DIGEST_LENGTH, hash); } /** * Generate server dialback key. free() the returned value * @param secret originating server shared secret * @param receiving_server domain * @param originating_server domain * @param stream_id stream ID * @return the dialback key */ char *iks_server_dialback_key(const char *secret, const char *receiving_server, const char *originating_server, const char *stream_id) { if (!zstr(secret) && !zstr(receiving_server) && !zstr(originating_server) && !zstr(stream_id)) { unsigned char secret_hash[IKS_SHA256_HEX_DIGEST_LENGTH]; unsigned char *message = NULL; unsigned char *dialback_key = malloc(sizeof(unsigned char) * IKS_SHA256_HEX_DIGEST_LENGTH); iks_sha256_hex_string((unsigned char *)secret, strlen(secret), secret_hash); message = (unsigned char *)switch_mprintf("%s %s %s", receiving_server, originating_server, stream_id); iks_hmac_sha256_hex_string(secret_hash, strlen((char *)secret_hash), message, strlen((char *)message), dialback_key); free(message); return (char *)dialback_key; } return NULL; } /** * Print base 64 encoded SHA-1 hash * @param sha hash to print * @param buf to store baes 64 encoded hash */ void iks_sha_print_base64(iksha *sha, char *buf) { int i; char hex_digit[3] = { 0 }; char hex_buf[SHA_1_HASH_BUF_SIZE]; unsigned char bin_buf[SHA_1_HASH_BUF_SIZE / 2]; iks_sha_print(sha, hex_buf); /* convert hex string to octets */ for (i = 0; i < SHA_1_HASH_BUF_SIZE; i += 2) { hex_digit[0] = hex_buf[i]; hex_digit[1] = hex_buf[i + 1]; bin_buf[i / 2] = strtol(hex_digit, NULL, 16); } switch_b64_encode(bin_buf, SHA_1_HASH_BUF_SIZE / 2, (unsigned char *)buf, SHA_1_HASH_BUF_SIZE); } /* For Emacs: * Local Variables: * mode:c * indent-tabs-mode:t * tab-width:4 * c-basic-offset:4 * End: * For VIM: * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet */