void create_challenge(pjsip_digest_credential* credentials, pj_bool_t stale, std::string resync, pjsip_rx_data* rdata, pjsip_tx_data* tdata) { // Get the public and private identities from the request. std::string impi; std::string impu; std::string nonce; PJUtils::get_impi_and_impu(rdata, impi, impu); // Set up the authorization type, following Annex P.4 of TS 33.203. Currently // only support AKA and SIP Digest, so only implement the subset of steps // required to distinguish between the two. std::string auth_type; if (credentials != NULL) { pjsip_param* integrity = pjsip_param_find(&credentials->other_param, &STR_INTEGRITY_PROTECTED); if ((integrity != NULL) && ((pj_stricmp(&integrity->value, &STR_YES) == 0) || (pj_stricmp(&integrity->value, &STR_NO) == 0))) { // Authentication scheme is AKA. auth_type = "aka"; } } // Get the Authentication Vector from the HSS. rapidjson::Document* av = NULL; HTTPCode http_code = hss->get_auth_vector(impi, impu, auth_type, resync, av, get_trail(rdata)); if ((av != NULL) && (!verify_auth_vector(av, impi, get_trail(rdata)))) { // Authentication Vector is badly formed. delete av; av = NULL; } if (av != NULL) { // Retrieved a valid authentication vector, so generate the challenge. TRC_DEBUG("Valid AV - generate challenge"); char buf[16]; pj_str_t random; random.ptr = buf; random.slen = sizeof(buf); pjsip_www_authenticate_hdr* hdr; if (rdata->msg_info.msg->line.req.method.id == PJSIP_REGISTER_METHOD) { TRC_DEBUG("Create WWW-Authenticate header"); hdr = pjsip_www_authenticate_hdr_create(tdata->pool); } else { TRC_DEBUG("Create Proxy-Authenticate header"); hdr = pjsip_proxy_authenticate_hdr_create(tdata->pool); } // Set up common fields for Digest and AKA cases (both are considered // Digest authentication). hdr->scheme = STR_DIGEST; if (av->HasMember("aka")) { // AKA authentication. TRC_DEBUG("Add AKA information"); SAS::Event event(get_trail(rdata), SASEvent::AUTHENTICATION_CHALLENGE_AKA, 0); SAS::report_event(event); rapidjson::Value& aka = (*av)["aka"]; // Use default realm for AKA as not specified in the AV. pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &aka_realm); hdr->challenge.digest.algorithm = STR_AKAV1_MD5; if ((aka.HasMember("challenge")) && (aka["challenge"].IsString())) { nonce = aka["challenge"].GetString(); } pj_strdup2(tdata->pool, &hdr->challenge.digest.nonce, nonce.c_str()); pj_create_random_string(buf, sizeof(buf)); pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random); hdr->challenge.digest.qop = STR_AUTH; hdr->challenge.digest.stale = stale; // Add the cryptography key parameter. pjsip_param* ck_param = (pjsip_param*)pj_pool_alloc(tdata->pool, sizeof(pjsip_param)); ck_param->name = STR_CK; std::string cryptkey = ""; if ((aka.HasMember("cryptkey")) && (aka["cryptkey"].IsString())) { cryptkey = aka["cryptkey"].GetString(); } std::string ck = "\"" + cryptkey + "\""; pj_strdup2(tdata->pool, &ck_param->value, ck.c_str()); pj_list_insert_before(&hdr->challenge.digest.other_param, ck_param); // Add the integrity key parameter. pjsip_param* ik_param = (pjsip_param*)pj_pool_alloc(tdata->pool, sizeof(pjsip_param)); ik_param->name = STR_IK; std::string integritykey = ""; if ((aka.HasMember("integritykey")) && (aka["integritykey"].IsString())) { integritykey = aka["integritykey"].GetString(); } std::string ik = "\"" + integritykey + "\""; pj_strdup2(tdata->pool, &ik_param->value, ik.c_str()); pj_list_insert_before(&hdr->challenge.digest.other_param, ik_param); } else { // Digest authentication. TRC_DEBUG("Add Digest information"); SAS::Event event(get_trail(rdata), SASEvent::AUTHENTICATION_CHALLENGE_DIGEST, 0); SAS::report_event(event); rapidjson::Value& digest = (*av)["digest"]; std::string realm = ""; if ((digest.HasMember("realm")) && (digest["realm"].IsString())) { realm = digest["realm"].GetString(); } std::string qop = ""; if ((digest.HasMember("qop")) && (digest["qop"].IsString())) { qop = digest["qop"].GetString(); } pj_strdup2(tdata->pool, &hdr->challenge.digest.realm, realm.c_str()); hdr->challenge.digest.algorithm = STR_MD5; pj_create_random_string(buf, sizeof(buf)); nonce.assign(buf, sizeof(buf)); pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random); pj_create_random_string(buf, sizeof(buf)); pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random); pj_strdup2(tdata->pool, &hdr->challenge.digest.qop, qop.c_str()); hdr->challenge.digest.stale = stale; } // Add the header to the message. pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); // Store the branch parameter in memcached for correlation purposes pjsip_via_hdr* via_hdr = (pjsip_via_hdr*)pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_VIA, NULL); std::string branch = (via_hdr != NULL) ? PJUtils::pj_str_to_string(&via_hdr->branch_param) : ""; rapidjson::Value branch_value; branch_value.SetString(branch.c_str(), (*av).GetAllocator()); (*av).AddMember("branch", branch_value, (*av).GetAllocator()); // Write the authentication vector (as a JSON string) into the AV store. TRC_DEBUG("Write AV to store"); uint64_t cas = 0; Store::Status status = av_store->set_av(impi, nonce, av, cas, get_trail(rdata)); if (status == Store::OK) { // We've written the AV into the store, so need to set a Chronos // timer so that an AUTHENTICATION_TIMEOUT SAR is sent to the // HSS when it expires. std::string timer_id; std::string chronos_body = "{\"impi\": \"" + impi + "\", \"impu\": \"" + impu +"\", \"nonce\": \"" + nonce +"\"}"; TRC_DEBUG("Sending %s to Chronos to set AV timer", chronos_body.c_str()); chronos->send_post(timer_id, 30, "/authentication-timeout", chronos_body, get_trail(rdata)); } delete av; } else { // If we couldn't get the AV because a downstream node is overloaded then don't return // a 4xx error to the client. if ((http_code == HTTP_SERVER_UNAVAILABLE) || (http_code == HTTP_GATEWAY_TIMEOUT)) { TRC_DEBUG("Downstream node is overloaded or unresponsive, unable to get Authentication vector"); tdata->msg->line.status.code = PJSIP_SC_SERVER_TIMEOUT; tdata->msg->line.status.reason = *pjsip_get_status_text(PJSIP_SC_SERVER_TIMEOUT); SAS::Event event(get_trail(rdata), SASEvent::AUTHENTICATION_FAILED_OVERLOAD, 0); SAS::report_event(event); } else { TRC_DEBUG("Failed to get Authentication vector"); tdata->msg->line.status.code = PJSIP_SC_FORBIDDEN; tdata->msg->line.status.reason = *pjsip_get_status_text(PJSIP_SC_FORBIDDEN); SAS::Event event(get_trail(rdata), SASEvent::AUTHENTICATION_FAILED_NO_AV, 0); SAS::report_event(event); } pjsip_tx_data_invalidate_msg(tdata); } }
pj_status_t init_stack(const std::string& system_name, const std::string& sas_address, int pcscf_trusted_port, int pcscf_untrusted_port, int scscf_port, int icscf_port, const std::string& local_host, const std::string& public_host, const std::string& home_domain, const std::string& additional_home_domains, const std::string& scscf_uri, const std::string& alias_hosts, SIPResolver* sipresolver, int num_pjsip_threads, int record_routing_model, const int default_session_expires, const int max_session_expires, QuiescingManager *quiescing_mgr_arg, const std::string& cdf_domain) { pj_status_t status; pj_sockaddr pri_addr; pj_sockaddr addr_list[16]; unsigned addr_cnt = PJ_ARRAY_SIZE(addr_list); unsigned i; // Set up the vectors of threads. The threads don't get created until // start_pjsip_threads is called. pjsip_threads.resize(num_pjsip_threads); // Get ports and host names specified on options. If local host was not // specified, use the host name returned by pj_gethostname. char* local_host_cstr = strdup(local_host.c_str()); char* public_host_cstr = strdup(public_host.c_str()); char* home_domain_cstr = strdup(home_domain.c_str()); char* scscf_uri_cstr; if (scscf_uri.empty()) { // Create a default S-CSCF URI using the localhost and S-CSCF port. std::string tmp_scscf_uri = "sip:" + local_host + ":" + std::to_string(scscf_port) + ";transport=TCP"; scscf_uri_cstr = strdup(tmp_scscf_uri.c_str()); } else { // Use the specified URI. scscf_uri_cstr = strdup(scscf_uri.c_str()); } // This is only set on Bono nodes (it's the empty string otherwise) char* cdf_domain_cstr = strdup(cdf_domain.c_str()); // Copy port numbers to stack data. stack_data.pcscf_trusted_port = pcscf_trusted_port; stack_data.pcscf_untrusted_port = pcscf_untrusted_port; stack_data.scscf_port = scscf_port; stack_data.icscf_port = icscf_port; stack_data.sipresolver = sipresolver; // Copy other functional options to stack data. stack_data.default_session_expires = default_session_expires; stack_data.max_session_expires = max_session_expires; // Work out local and public hostnames and cluster domain names. stack_data.local_host = (local_host != "") ? pj_str(local_host_cstr) : *pj_gethostname(); stack_data.public_host = (public_host != "") ? pj_str(public_host_cstr) : stack_data.local_host; stack_data.default_home_domain = (home_domain != "") ? pj_str(home_domain_cstr) : stack_data.local_host; stack_data.scscf_uri = pj_str(scscf_uri_cstr); stack_data.cdf_domain = pj_str(cdf_domain_cstr); // Build a set of home domains stack_data.home_domains = std::unordered_set<std::string>(); stack_data.home_domains.insert(PJUtils::pj_str_to_string(&stack_data.default_home_domain)); if (additional_home_domains != "") { std::list<std::string> domains; Utils::split_string(additional_home_domains, ',', domains, 0, true); stack_data.home_domains.insert(domains.begin(), domains.end()); } // Set up the default address family. This is IPv4 unless our local host is an IPv6 address. stack_data.addr_family = AF_INET; struct in6_addr dummy_addr; if (inet_pton(AF_INET6, local_host_cstr, &dummy_addr) == 1) { LOG_DEBUG("Local host is an IPv6 address - enabling IPv6 mode"); stack_data.addr_family = AF_INET6; } stack_data.record_route_on_every_hop = false; stack_data.record_route_on_initiation_of_originating = false; stack_data.record_route_on_initiation_of_terminating = false; stack_data.record_route_on_completion_of_originating = false; stack_data.record_route_on_completion_of_terminating = false; stack_data.record_route_on_diversion = false; if (scscf_port != 0) { switch (record_routing_model) { case 1: stack_data.record_route_on_initiation_of_originating = true; stack_data.record_route_on_completion_of_terminating = true; break; case 2: stack_data.record_route_on_initiation_of_originating = true; stack_data.record_route_on_initiation_of_terminating = true; stack_data.record_route_on_completion_of_originating = true; stack_data.record_route_on_completion_of_terminating = true; stack_data.record_route_on_diversion = true; break; case 3: stack_data.record_route_on_every_hop = true; stack_data.record_route_on_initiation_of_originating = true; stack_data.record_route_on_initiation_of_terminating = true; stack_data.record_route_on_completion_of_originating = true; stack_data.record_route_on_completion_of_terminating = true; stack_data.record_route_on_diversion = true; break; default: LOG_ERROR("Record-Route setting should be 1, 2, or 3, is %d. Defaulting to Record-Route on every hop.", record_routing_model); stack_data.record_route_on_every_hop = true; } } std::string system_name_sas = system_name; std::string system_type_sas = (pcscf_trusted_port != 0) ? "bono" : "sprout"; // Initialize SAS logging. if (system_name_sas == "") { system_name_sas = std::string(stack_data.local_host.ptr, stack_data.local_host.slen); } SAS::init(system_name, system_type_sas, SASEvent::CURRENT_RESOURCE_BUNDLE, sas_address, sas_write); // Initialise PJSIP and all the associated resources. status = init_pjsip(); // Initialize the PJUtils module. PJUtils::init(); // Create listening transports for the ports whichtrusted and untrusted ports. stack_data.pcscf_trusted_tcp_factory = NULL; if (stack_data.pcscf_trusted_port != 0) { status = start_transports(stack_data.pcscf_trusted_port, stack_data.local_host, &stack_data.pcscf_trusted_tcp_factory); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); } stack_data.pcscf_untrusted_tcp_factory = NULL; if (stack_data.pcscf_untrusted_port != 0) { status = start_transports(stack_data.pcscf_untrusted_port, stack_data.public_host, &stack_data.pcscf_untrusted_tcp_factory); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); } stack_data.scscf_tcp_factory = NULL; if (stack_data.scscf_port != 0) { status = start_transports(stack_data.scscf_port, stack_data.public_host, &stack_data.scscf_tcp_factory); if (status == PJ_SUCCESS) { CL_SPROUT_S_CSCF_AVAIL.log(stack_data.scscf_port); } else { CL_SPROUT_S_CSCF_INIT_FAIL2.log(stack_data.scscf_port); } PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); } stack_data.icscf_tcp_factory = NULL; if (stack_data.icscf_port != 0) { status = start_transports(stack_data.icscf_port, stack_data.public_host, &stack_data.icscf_tcp_factory); if (status == PJ_SUCCESS) { CL_SPROUT_I_CSCF_AVAIL.log(stack_data.icscf_port); } else { CL_SPROUT_I_CSCF_INIT_FAIL2.log(stack_data.icscf_port); } PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); } // List all names matching local endpoint. // Note that PJLIB version 0.6 and newer has a function to // enumerate local IP interface (pj_enum_ip_interface()), so // by using it would be possible to list all IP interfaces in // this host. // The first address is important since this would be the one // to be added in Record-Route. stack_data.name[stack_data.name_cnt] = stack_data.local_host; stack_data.name_cnt++; if (strcmp(local_host_cstr, public_host_cstr)) { stack_data.name[stack_data.name_cnt] = stack_data.public_host; stack_data.name_cnt++; } if ((scscf_port != 0) && (!scscf_uri.empty())) { // S-CSCF enabled with a specified URI, so add host name from the URI to hostnames. pjsip_sip_uri* uri = (pjsip_sip_uri*)PJUtils::uri_from_string(scscf_uri, stack_data.pool); if (uri != NULL) { stack_data.name[stack_data.name_cnt] = uri->host; stack_data.name_cnt++; } } if (pj_gethostip(pj_AF_INET(), &pri_addr) == PJ_SUCCESS) { pj_strdup2(stack_data.pool, &stack_data.name[stack_data.name_cnt], pj_inet_ntoa(pri_addr.ipv4.sin_addr)); stack_data.name_cnt++; } // Get the rest of IP interfaces. if (pj_enum_ip_interface(pj_AF_INET(), &addr_cnt, addr_list) == PJ_SUCCESS) { for (i = 0; i < addr_cnt; ++i) { if (addr_list[i].ipv4.sin_addr.s_addr == pri_addr.ipv4.sin_addr.s_addr) { continue; } pj_strdup2(stack_data.pool, &stack_data.name[stack_data.name_cnt], pj_inet_ntoa(addr_list[i].ipv4.sin_addr)); stack_data.name_cnt++; } } // Note that we no longer consider 127.0.0.1 and localhost as aliases. // Parse the list of alias host names. stack_data.aliases = std::unordered_set<std::string>(); if (alias_hosts != "") { std::list<std::string> aliases; Utils::split_string(alias_hosts, ',', aliases, 0, true); stack_data.aliases.insert(aliases.begin(), aliases.end()); for (std::unordered_set<std::string>::iterator it = stack_data.aliases.begin(); it != stack_data.aliases.end(); ++it) { pj_strdup2(stack_data.pool, &stack_data.name[stack_data.name_cnt], it->c_str()); stack_data.name_cnt++; } } LOG_STATUS("Local host aliases:"); for (i = 0; i < stack_data.name_cnt; ++i) { LOG_STATUS(" %.*s", (int)stack_data.name[i].slen, stack_data.name[i].ptr); } // Set up the Last Value Cache, accumulators and counters. std::string process_name; if ((stack_data.pcscf_trusted_port != 0) && (stack_data.pcscf_untrusted_port != 0)) { process_name = "bono"; } else { process_name = "sprout"; } stack_data.stats_aggregator = new LastValueCache(num_known_stats, known_statnames, process_name); if (quiescing_mgr_arg != NULL) { quiescing_mgr = quiescing_mgr_arg; // Create an instance of the stack quiesce handler. This acts as a glue // class between the stack modulem connections tracker, and the quiescing // manager. stack_quiesce_handler = new StackQuiesceHandler(); // Create a new connection tracker, and register the quiesce handler with // it. connection_tracker = new ConnectionTracker(stack_quiesce_handler); // Register the quiesce handler with the quiescing manager (the former // implements the connection handling interface). quiescing_mgr->register_conns_handler(stack_quiesce_handler); pjsip_endpt_register_module(stack_data.endpt, &mod_connection_tracking); } return status; }
pj_status_t user_lookup(pj_pool_t *pool, const pjsip_auth_lookup_cred_param *param, pjsip_cred_info *cred_info, void* av_param) { const pj_str_t* acc_name = ¶m->acc_name; const pj_str_t* realm = ¶m->realm; const pjsip_rx_data* rdata = param->rdata; SAS::TrailId trail = get_trail(rdata); pj_status_t status = PJSIP_EAUTHACCNOTFOUND; // Get the impi and the nonce. There must be an authorization header otherwise // PJSIP wouldn't have called this method. std::string impi = PJUtils::pj_str_to_string(acc_name); pjsip_digest_credential* credentials = get_credentials(rdata); std::string nonce = PJUtils::pj_str_to_string(&credentials->nonce); // Get the Authentication Vector from the store. rapidjson::Document* av = (rapidjson::Document*)av_param; if (av == NULL) { TRC_WARNING("Received an authentication request for %s with nonce %s, but no matching AV found", impi.c_str(), nonce.c_str()); } if ((av != NULL) && (!verify_auth_vector(av, impi, trail))) { // Authentication vector is badly formed. av = NULL; // LCOV_EXCL_LINE } if (av != NULL) { pj_cstr(&cred_info->scheme, "digest"); pj_strdup(pool, &cred_info->username, acc_name); if (av->HasMember("aka")) { pjsip_param* auts_param = pjsip_param_find(&credentials->other_param, &STR_AUTS); // AKA authentication. The response in the AV must be used as a // plain-text password for the MD5 Digest computation. Convert the text // into binary as this is what PJSIP is expecting. If we find the 'auts' // parameter, then leave the response as the empty string in accordance // with RFC 3310. std::string response = ""; if (((*av)["aka"].HasMember("response")) && ((*av)["aka"]["response"].IsString()) && auts_param == NULL) { response = (*av)["aka"]["response"].GetString(); } std::string xres; for (size_t ii = 0; ii < response.length(); ii += 2) { xres.push_back((char)(pj_hex_digit_to_val(response[ii]) * 16 + pj_hex_digit_to_val(response[ii+1]))); } cred_info->data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; pj_strdup2(pool, &cred_info->data, xres.c_str()); TRC_DEBUG("Found AKA XRES = %.*s", cred_info->data.slen, cred_info->data.ptr); // Use default realm as it isn't specified in the AV. pj_strdup(pool, &cred_info->realm, realm); status = PJ_SUCCESS; } else if (av->HasMember("digest")) { std::string digest_realm = ""; if (((*av)["digest"].HasMember("realm")) && ((*av)["digest"]["realm"].IsString())) { digest_realm = (*av)["digest"]["realm"].GetString(); } if (pj_strcmp2(realm, digest_realm.c_str()) == 0) { // Digest authentication, so ha1 field is hashed password. cred_info->data_type = PJSIP_CRED_DATA_DIGEST; std::string digest_ha1 = ""; if (((*av)["digest"].HasMember("ha1")) && ((*av)["digest"]["ha1"].IsString())) { digest_ha1 = (*av)["digest"]["ha1"].GetString(); } pj_strdup2(pool, &cred_info->data, digest_ha1.c_str()); cred_info->realm = *realm; TRC_DEBUG("Found Digest HA1 = %.*s", cred_info->data.slen, cred_info->data.ptr); status = PJ_SUCCESS; } else { // These credentials are for a different realm, so no credentials were // actually provided for us to check. status = PJSIP_EAUTHNOAUTH; } } correlate_branch_from_av(av, trail); } return status; }