/* Copyright (C) 2005-2009 Michel de Boer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "request.h" #include "util.h" #include "parse_ctrl.h" #include "protocol.h" #include "milenage.h" #include "audits/memman.h" #include #include using namespace UCOMMON_NAMESPACE; // AKAv1-MD5 algorithm specific helpers #define B64_ENC_SZ(x) (4 * ((x + 2) / 3)) #define B64_DEC_SZ(x) (3 * ((x + 3) / 4)) int b64_enc(const u8 * src, u8 * dst, int len) { static char tbl[64] = { 'A','B','C','D','E','F','G','H', 'I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X', 'Y','Z','a','b','c','d','e','f', 'g','h','i','j','k','l','m','n', 'o','p','q','r','s','t','u','v', 'w','x','y','z','0','1','2','3', '4','5','6','7','8','9','+','/' }; u8 * dst0 = dst; int i, v, div = len / 3, mod = len % 3; for (i = 0; i < div * 3; i += 3) { v = (src[i+0] & 0xfc) >> 2; *(dst++) = tbl[v]; v = (src[i+0] & 0x03) << 4; v |= (src[i+1] & 0xf0) >> 4; *(dst++) = tbl[v]; v = (src[i+1] & 0x0f) << 2; v |= (src[i+2] & 0xc0) >> 6; *(dst++) = tbl[v]; v = src[i+2] & 0x3f; *(dst++) = tbl[v]; } if (mod == 1) { v = (src[i+0] & 0xfc) >> 2; *(dst++) = tbl[v]; v = (src[i+0] & 0x03) << 4; *(dst++) = tbl[v]; *(dst++) = '='; *(dst++) = '='; } else if (mod == 2) { v = (src[i+0] & 0xfc) >> 2; *(dst++) = tbl[v]; v = (src[i+0] & 0x03) << 4; v |= (src[i+1] & 0xf0) >> 4; *(dst++) = tbl[v]; v = (src[i+1] & 0x0f) << 2; *(dst++) = tbl[v]; *(dst++) = '='; } return dst - dst0; } static int b64_val(u8 x) { if (x >= 'A' && x <= 'Z') return x - 'A'; else if (x >= 'a' && x <= 'z') return x - 'a' + 26; else if (x >= '0' && x <= '9') return x - '0' + 52; else if (x == '+') return 62; else if (x == '/') return 63; //else if (x == '=') return -1; } int b64_dec(const u8 * src, u8 * dst, int len) { u8 * dst0 = dst; int i, x1, x2, x3, x4; if (len % 4) return 0; for (i=0; i+4 < len; i += 4) { x1 = b64_val(*(src++)); x2 = b64_val(*(src++)); x3 = b64_val(*(src++)); x4 = b64_val(*(src++)); *(dst++) = (x1 << 2) | ((x2 & 0x30) >> 4); *(dst++) = ((x2 & 0x0F) << 4) | ((x3 & 0x3C) >> 2); *(dst++) = ((x3 & 0x03) << 6) | (x4 & 0x3F); } if (len) { x1 = b64_val(*(src++)); x2 = b64_val(*(src++)); x3 = b64_val(*(src++)); x4 = b64_val(*(src++)); *(dst++) = (x1 << 2) | ((x2 & 0x30) >> 4); if (x3 != -1) { *(dst++) = ((x2 & 0x0F) << 4) | ((x3 & 0x3C) >> 2); if (x4 != -1) *(dst++) = ((x3 & 0x03) << 6) | (x4 & 0x3F); } } return dst - dst0; } #define HASH_HEX_LEN 32 #define HASH_LEN 16 // authentication with AKAv1-MD5 algorithm (RFC 3310) bool t_request::authorize_akav1_md5(const t_digest_challenge &dchlg, const std::string &username, const std::string &passwd, uint8 *op, uint8 *amf, unsigned long nc, const std::string &cnonce, const std::string &qop, std::string &resp, std::string &fail_reason) const { u8 nonce64[B64_DEC_SZ(dchlg.nonce.size())]; int len = b64_dec((const u8 *)dchlg.nonce.c_str(), nonce64, dchlg.nonce.size()); u8 rnd[AKA_RANDLEN]; u8 sqnxoraka[AKA_SQNLEN]; u8 sqn[AKA_SQNLEN]; u8 k[AKA_KLEN]; u8 res[AKA_RESLEN]; u8 ck[AKA_CKLEN]; u8 ik[AKA_IKLEN]; u8 ak[AKA_AKLEN]; int i; if (len < AKA_RANDLEN+AKA_AUTNLEN) { fail_reason = "nonce base64 data too short (need 32 bytes)"; return false; } memset(rnd, 0, AKA_RANDLEN); memset(sqnxoraka, 0, AKA_SQNLEN); memset(k, 0, AKA_KLEN); memcpy(rnd, nonce64, AKA_RANDLEN); memcpy(sqnxoraka, nonce64 + AKA_RANDLEN, AKA_SQNLEN); memcpy(k, passwd.c_str(), passwd.size()); f2345(k, rnd, res, ck, ik, ak, op); for (i=0; i < AKA_SQNLEN; i++) sqn[i] = sqnxoraka[i] ^ ak[i]; std::string res_str = std::string((char *)res, AKA_RESLEN); return authorize_md5(dchlg, username, res_str, nc, cnonce, qop, resp, fail_reason); } // authentication with MD5 algorithm bool t_request::authorize_md5(const t_digest_challenge &dchlg, const std::string &username, const std::string &passwd, unsigned long nc, const std::string &cnonce, const std::string &qop, std::string &resp, std::string &fail_reason) const { std::string A1, A2; // RFC 2617 3.2.2.2 A1 = username + ":" + dchlg.realm + ":" + passwd; // RFC 2617 3.2.2.3 if (cmp_nocase(qop, QOP_AUTH) == 0 || qop == "") { A2 = method2str(method, unknown_method) + ":" + uri.encode(); } else { A2 = method2str(method, unknown_method) + ":" + uri.encode(); A2 += ":"; if (body) { digest_t MD5body("md5"); MD5body.puts(body->encode().c_str()); A2 += std::string(MD5body.str()); } else { digest_t MD5body("md5"); MD5body.puts(""); A2 += std::string(MD5body.str()); } } // RFC 2716 3.2.2.1 // Caculate digest digest_t MD5A1("md5"); digest_t MD5A2("md5"); MD5A1.puts(A1.c_str()); MD5A2.puts(A2.c_str()); std::string x; if (cmp_nocase(qop, QOP_AUTH) == 0 || cmp_nocase(qop, QOP_AUTH_INT) == 0) { x = std::string(MD5A1.str()); x += ":"; x += dchlg.nonce + ":"; x += int2str(nc, "%08x") + ":"; x += cnonce + ":"; x += qop + ":"; x += std::string(MD5A2.str()); } else { x = std::string(MD5A1.str()); x += ":"; x += dchlg.nonce + ":"; x += std::string(MD5A2.str()); } digest_t digest("md5"); digest.puts(x.c_str()); resp = std::string(digest.str()); return true; } bool t_request::authorize(const t_challenge &chlg, t_user *user_config, const std::string &username, const std::string &passwd, unsigned long nc, const std::string &cnonce, t_credentials &cr, std::string &fail_reason) const { // Only Digest authentication is supported if (cmp_nocase(chlg.auth_scheme, AUTH_DIGEST) != 0) { fail_reason = "Authentication scheme " + chlg.auth_scheme; fail_reason += " not supported."; return false; } const t_digest_challenge &dchlg = chlg.digest_challenge; std::string qop = ""; // Determine QOP // If both auth and auth-int are supported by the server, then // choose auth to avoid problems with SIP ALGs. A SIP ALG rewrites // the body of a message, thereby breaking auth-int authentication. if (!dchlg.qop_options.empty()) { const list::const_iterator i = find( dchlg.qop_options.begin(), dchlg.qop_options.end(), QOP_AUTH_INT); const list::const_iterator j = find( dchlg.qop_options.begin(), dchlg.qop_options.end(), QOP_AUTH); if (j != dchlg.qop_options.end()) qop = QOP_AUTH; else { if (i != dchlg.qop_options.end()) qop = QOP_AUTH_INT; else { fail_reason = "Non of the qop values are supported."; return false; } } } bool ret = false; std::string resp; if (cmp_nocase(dchlg.algorithm, ALG_MD5) == 0) { ret = authorize_md5(dchlg, username, passwd, nc, cnonce, qop, resp, fail_reason); } else if (cmp_nocase(dchlg.algorithm, ALG_AKAV1_MD5) == 0) { uint8 aka_op[AKA_OPLEN]; uint8 aka_amf[AKA_AMFLEN]; user_config->get_auth_aka_op(aka_op); user_config->get_auth_aka_amf(aka_amf); ret = authorize_akav1_md5(dchlg, username, passwd, aka_op, aka_amf, nc, cnonce, qop, resp, fail_reason); } else { fail_reason = "Authentication algorithm " + dchlg.algorithm; fail_reason += " not supported."; return false; } if (!ret) return false; // Create credentials cr.auth_scheme = AUTH_DIGEST; t_digest_response &dr = cr.digest_response; dr.dresponse = resp; dr.username = username; dr.realm = dchlg.realm; dr.nonce = dchlg.nonce; dr.digest_uri = uri; dr.algorithm = dchlg.algorithm; dr.opaque = dchlg.opaque; // RFC 2617 3.2.2 if (qop != "") { dr.message_qop = qop; dr.cnonce = cnonce; dr.nonce_count = nc; } return true; } t_request::t_request() : t_sip_message(), transport_specified(false), method(METHOD_UNKNOWN) { } t_request::t_request(const t_request &r) : t_sip_message(r), destinations(r.destinations), transport_specified(r.transport_specified), uri(r.uri), method(r.method), unknown_method(r.unknown_method) { } t_request::t_request(const t_method m) : t_sip_message() { method = m; } void t_request::set_method(const std::string &s) { method = str2method(s); if (method == METHOD_UNKNOWN) { unknown_method = s; } } std::string t_request::encode(bool add_content_length) { std::string s; s = method2str(method, unknown_method) + ' ' + uri.encode(); s += " SIP/"; s += version; s += CRLF; s += t_sip_message::encode(add_content_length); return s; } list t_request::encode_env(void) { std::string s; list l = t_sip_message::encode_env(); s = "SIPREQUEST_METHOD="; s += method2str(method, unknown_method); l.push_back(s); s = "SIPREQUEST_URI="; s += uri.encode(); l.push_back(s); return l; } t_sip_message *t_request::copy(void) const { t_sip_message *m = new t_request(*this); MEMMAN_NEW(m); return m; } void t_request::set_route(const t_url &target_uri, const list &route_set) { // RFC 3261 12.2.1.1 if (route_set.empty()) { uri = target_uri; } else { if (route_set.front().uri.get_lr()) { // Loose routing uri = target_uri; for (list::const_iterator i = route_set.begin(); i != route_set.end(); ++i) { hdr_route.add_route(*i); } hdr_route.route_to_first_route = true; } else { // Strict routing uri = route_set.front().uri; for (list::const_iterator i = route_set.begin(); i != route_set.end(); ++i) { if (i != route_set.begin()) { hdr_route.add_route(*i); } } // Add target uri to the route list t_route route; route.uri = target_uri; hdr_route.add_route(route); } } } t_response *t_request::create_response(int code, std::string reason) const { t_response *r; r = new t_response(code, reason); MEMMAN_NEW(r); r->src_ip_port_request = src_ip_port; r->hdr_from = hdr_from; r->hdr_call_id = hdr_call_id; r->hdr_cseq = hdr_cseq; r->hdr_via = hdr_via; r->hdr_to = hdr_to; // Create a to-tag if none was present in the request // NOTE: 100 Trying should not get a to-tag if (hdr_to.tag.size() == 0 && code != R_100_TRYING) { r->hdr_to.set_tag(NEW_TAG); } // Server SET_HDR_SERVER(r->hdr_server); return r; } bool t_request::is_valid(bool &fatal, std::string &reason) const { if (!t_sip_message::is_valid(fatal, reason)) return false; fatal = false; if (t_parser::check_max_forwards && !hdr_max_forwards.is_populated()) { reason = "Max-Forwards header missing"; return false; } // RFC 3261 8.1.1.5 // The CSeq method must match the request method. if (hdr_cseq.method != method) { reason = "CSeq method does not match request method"; return false; } switch(method) { case INVITE: if (!hdr_contact.is_populated()) { reason = "Contact header missing"; return false; } break; case PRACK: // RFC 3262 7.1 if (!hdr_rack.is_populated()) { reason = "RAck header missing"; return false; } break; case SUBSCRIBE: // RFC 3265 7.1, 7.2 if (!hdr_contact.is_populated()) { reason = "Contact header missing"; return false; } if (!hdr_event.is_populated()) { reason = "Event header missing"; return false; } break; case NOTIFY: // RFC 3265 7.1, 7.2 if (!hdr_contact.is_populated()) { reason = "Contact header missing"; return false; } if (!hdr_event.is_populated()) { reason = "Event header missing"; return false; } // RFC 3265 7.2 // Subscription-State header is mandatory // As an exception Twinkle allows an unsollicited NOTIFY for MWI // without a Subscription-State header. Asterisk sends // unsollicited NOTIFY requests. if (!hdr_to.tag.empty() || hdr_event.event_type != SIP_EVENT_MSG_SUMMARY) { if (!hdr_subscription_state.is_populated()) { reason = "Subscription-State header missing"; return false; } } // The Subscription-State header is mandatory. // However, Asterisk uses an expired draft for sending // unsollicitied NOTIFY messages without a Subscription-State // header. As Asterisk is popular, Twinkle allows this. break; case REFER: // RFC 3515 2.4.1 if (!hdr_refer_to.is_populated()) { reason = "Refer-To header missing"; return false; } break; default: break; } if (hdr_replaces.is_populated()) { // RFC 3891 3 if (method != INVITE) { reason = "Replaces header not allowed"; return false; } } return true; } void t_request::add_destinations(const t_user &user_profile, const t_url &dst_uri) { t_url dest; if (dst_uri.get_scheme() == "tel") { // Send a tel-URI to the configure domain for the user. dest = "sip:" + user_profile.get_domain(); } else { dest = dst_uri; } if ((user_profile.get_sip_transport() == SIP_TRANS_AUTO || user_profile.get_sip_transport() == SIP_TRANS_TCP) && (dest.get_transport().empty() || cmp_nocase(dest.get_transport(), "tcp") == 0)) { list l = dest.get_h_ip_srv("tcp"); destinations.insert(destinations.end(), l.begin(), l.end()); } // Add UDP destinations after TCP, so UDP will be used as a fallback // for large messages, when TCP fails. If the message is not large, // then the TCP destinations will be removed later. // NOTE: If a message is larger than 64K, it cannot be sent via UDP if ((user_profile.get_sip_transport() == SIP_TRANS_AUTO || user_profile.get_sip_transport() == SIP_TRANS_UDP) && (dest.get_transport().empty() || cmp_nocase(dest.get_transport(), "udp") == 0) && (get_encoded_size() < 65536)) { list l = dest.get_h_ip_srv("udp"); destinations.insert(destinations.end(), l.begin(), l.end()); } transport_specified = !dest.get_transport().empty(); } void t_request::calc_destinations(const t_user &user_profile) { destinations.clear(); // Send a REGISTER to the registrar if provisioned. if (method == REGISTER && user_profile.get_use_registrar()) { add_destinations(user_profile, user_profile.get_registrar()); return; } // Bypass the proxy for an out-of-dialog SUBSCRIBE if provisioned. if (method == SUBSCRIBE && hdr_to.tag.empty()) { if (hdr_event.event_type == SIP_EVENT_MSG_SUMMARY) { if (!user_profile.get_mwi_via_proxy()) { // Take Request-URI add_destinations(user_profile, uri); return; } } } if (!user_profile.get_use_outbound_proxy() || (hdr_to.tag != "" && !user_profile.get_all_requests_to_proxy())) { // A mid dialog request will go to the host in the contact // header (put in the request-URI in this request) or route list // specified in the final response of the invite (the Route-header in // this request). // Note that an ACK for a failed INVITE (3XX-6XX) will be // sent by the transaction layer to the ipaddr/port of the // INVITE. if (hdr_route.is_populated() && hdr_route.route_to_first_route) { // Take URI from first route-header t_url &u = hdr_route.route_list.front().uri; add_destinations(user_profile, u); } else { // Take Request-URI add_destinations(user_profile, uri); } } // Send request to outbound proxy if configured if (user_profile.get_use_outbound_proxy()) { if (user_profile.get_non_resolvable_to_proxy() && !destinations.empty()) { // The destination has been resolved, so do not // use the outbound proxy in this case. return; } if (user_profile.get_all_requests_to_proxy() || hdr_to.tag == "") { // All requests should go to the proxy. // Override destination by the outbound proxy address. destinations.clear(); add_destinations(user_profile, user_profile.get_outbound_proxy()); } } } void t_request::get_destination(t_ip_port &ip_port, const t_user &user_profile) { if (destinations.empty()) calc_destinations(user_profile); // RFC 3261 18.1.1 // If the message size is larger than 1300 then the message must be // sent over TCP. // If the destination URI indicated an explicit transport, then the // destination calculation picked the possible destinations already. // The size cannot influence this calculation anymore. if (user_profile.get_sip_transport() == SIP_TRANS_AUTO && !destinations.empty() && destinations.front().transport == "tcp" && get_encoded_size() <= user_profile.get_sip_transport_udp_threshold() && !transport_specified) { // The message can be sent over UDP. Remove all TCP destinations. while (!destinations.empty() && destinations.front().transport == "tcp") { destinations.pop_front(); } } get_current_destination(ip_port); } void t_request::get_current_destination(t_ip_port &ip_port) { if (destinations.empty()) { // No destinations could be found. ip_port.transport = "udp"; ip_port.ipaddr = 0; ip_port.port = 0; } else { // Return first destination ip_port = destinations.front(); } } bool t_request::next_destination(void) { if (destinations.size() <= 1) return false; // Remove current destination destinations.pop_front(); return true; } void t_request::set_destination(const t_ip_port &ip_port) { destinations.clear(); destinations.push_back(ip_port); } bool t_request::www_authorize(const t_challenge &chlg, t_user *user_config, const std::string &username, const std::string &passwd, unsigned long nc, const std::string &cnonce, t_credentials &cr, std::string &fail_reason) { if (!authorize(chlg, user_config, username, passwd, nc, cnonce, cr, fail_reason)) { return false; } hdr_authorization.add_credentials(cr); return true; } bool t_request::proxy_authorize(const t_challenge &chlg, t_user *user_config, const std::string &username, const std::string &passwd, unsigned long nc, const std::string &cnonce, t_credentials &cr, std::string &fail_reason) { if (!authorize(chlg, user_config, username, passwd, nc, cnonce, cr, fail_reason)) { return false; } hdr_proxy_authorization.add_credentials(cr); return true; } void t_request::calc_local_ip(void) { t_ip_port dst; get_current_destination(dst); if (dst.ipaddr != 0) { local_ip_ = get_src_ip4_address_for_dst(dst.ipaddr); } } bool t_request::is_registration_request(void) const { if (method != REGISTER) return false; if (hdr_expires.is_populated() && hdr_expires.time > 0) return true; if (hdr_contact.is_populated() && !hdr_contact.contact_list.empty() && hdr_contact.contact_list.front().is_expires_present() && hdr_contact.contact_list.front().get_expires() > 0) { return true; } return false; } bool t_request::is_de_registration_request(void) const { if (method != REGISTER) return false; if (hdr_expires.is_populated() && hdr_expires.time == 0) return true; if (hdr_contact.is_populated() && !hdr_contact.contact_list.empty() && hdr_contact.contact_list.front().is_expires_present() && hdr_contact.contact_list.front().get_expires() == 0) { return true; } return false; }