/// Delete the data for the specified namespace and key. Writes the data /// unconditionally, so CAS is not needed. Store::Status BaseMemcachedStore::delete_data(const std::string& table, const std::string& key, SAS::TrailId trail) { TRC_DEBUG("Deleting key %s from table %s", key.c_str(), table.c_str()); // Construct the fully qualified key. std::string fqkey = table + "\\\\" + key; // Delete from the read replicas - read replicas are a superset of the write replicas const std::vector<memcached_st*>& replicas = get_replicas(fqkey, Op::READ); TRC_DEBUG("Deleting from the %d read replicas for key %s", replicas.size(), fqkey.c_str()); if (_tombstone_lifetime == 0) { delete_without_tombstone(fqkey, replicas, trail); } else { delete_with_tombstone(fqkey, replicas, trail); } return Status::OK; }
// Generate a WWW-Authenticate header. This has the format: // WWW-Authenticate: Digest realm="<home domain>", // qop="auth", // nonce="<nonce>", // opaque="<opaque>", // [stale=TRUE] void HTTPDigestAuthenticate::generate_www_auth_header(std::string& www_auth_header, bool include_stale, AuthStore::Digest* digest) { www_auth_header = "Digest"; www_auth_header.append(" realm=\"").append(_home_domain).append("\""); www_auth_header.append(",qop=\"").append("auth").append("\""); www_auth_header.append(",nonce=\"").append(digest->_nonce).append("\""); www_auth_header.append(",opaque=\"").append(digest->_opaque).append("\""); if (include_stale) { www_auth_header.append(",stale=TRUE"); } TRC_DEBUG("WWW-Authenticate header generated: %s", www_auth_header.c_str()); TRC_DEBUG("Raising correlating marker with opaque value = %s", digest->_opaque.c_str()); SAS::Marker corr(_trail, MARKED_ID_GENERIC_CORRELATOR, 0); corr.add_var_param(digest->_opaque); // The marker should be trace-scoped, and should not reactivate any trail // groups SAS::report_marker(corr, SAS::Marker::Scope::Trace, false); }
ConnectionTracker::~ConnectionTracker() { for (std::map<pjsip_transport *, pjsip_tp_state_listener_key *>::iterator it = _connection_listeners.begin(); it != _connection_listeners.end(); ++it) { TRC_DEBUG("Stop listening on connection %p", it->first); pjsip_transport_remove_state_listener(it->first, it->second, (void *)this); } pthread_mutex_destroy(&_lock); }
SessionStore::Session* SessionStore::get_session_data(const std::string& call_id, const role_of_node_t role, const node_functionality_t function, SAS::TrailId trail) { std::string key = create_key(call_id, role, function); TRC_DEBUG("Retrieving session data for %s", key.c_str()); Session* session = NULL; std::string data; uint64_t cas; Store::Status status = _store->get_data("session", key, data, cas, trail); if (status == Store::Status::OK && !data.empty()) { // Retrieved the data, so deserialize it. TRC_DEBUG("Retrieved record, CAS = %ld", cas); session = deserialize_session(data); if (session != NULL) { session->_cas = cas; } else { // Could not deserialize the record. Treat it as not found. TRC_INFO("Failed to deserialize record"); SAS::Event event(trail, SASEvent::SESSION_DESERIALIZATION_FAILED, 0); event.add_var_param(call_id); event.add_var_param(data); SAS::report_event(event); } } return session; }
void BaseResolver::untested(const AddrInfo& ai) { std::string ai_str = ai.to_string(); TRC_DEBUG("%s returned untested", ai_str.c_str()); pthread_mutex_lock(&_hosts_lock); Hosts::iterator i = _hosts.find(ai); if (i != _hosts.end()) { i->second.untested(pthread_self()); } pthread_mutex_unlock(&_hosts_lock); }
std::vector<std::string> SubscriberManager::subscriptions_to_remove(const Bindings& orig_bindings, const Subscriptions& orig_subscriptions, const Bindings& bindings_to_update, const std::vector<std::string> binding_ids_to_remove) { std::vector<std::string> subscription_ids_to_remove; std::set<std::string> missing_uris; // Store off the contact URIs of bindings to be removed. Any subscriptions // sharing any of these contact URIs will be removed. for (std::string binding_id : binding_ids_to_remove) { Bindings::const_iterator b = orig_bindings.find(binding_id); if (b != orig_bindings.end()) { missing_uris.insert(b->second->_uri); } } // Store off the original contact URI of bindings where the contact is about // to be changed. Any subscriptions that share any of the original contact // URIs will be removed. for (BindingPair bp : bindings_to_update) { Bindings::const_iterator b = orig_bindings.find(bp.first); if ((b != orig_bindings.end()) && (b->second->_uri != bp.second->_uri)) { missing_uris.insert(b->second->_uri); } } // Loop over the subscriptions. If any have the same contact as one of the // missing URIs, the subscription should be removed. for (SubscriptionPair sp : orig_subscriptions) { if (missing_uris.find(sp.second->_req_uri) != missing_uris.end()) { TRC_DEBUG("Subscription %s is being removed because the binding that shares" " its contact URI %s is being removed or changing contact URI", sp.first.c_str(), sp.second->_req_uri.c_str()); subscription_ids_to_remove.push_back(sp.first); } } return subscription_ids_to_remove; }
HTTPCode SubscriberManager::deregister_with_hss(const std::string& aor_id, const std::string& dereg_reason, const std::string& server_name, HSSConnection::irs_info& irs_info, SAS::TrailId trail) { TRC_DEBUG("All bindings removed for AoR %s - deregister with HSS", aor_id.c_str()); HSSConnection::irs_query irs_query; irs_query._public_id = aor_id; irs_query._req_type = dereg_reason; irs_query._server_name = server_name; return get_subscriber_state(irs_query, irs_info, trail); }
void ConnectionTracker::unquiesce() { TRC_DEBUG("Unquiesce connections"); pthread_mutex_lock(&_lock); // It is not possible to "un-shutdown" a pjsip transport. All connections // that were previously active will eventually be closed. Instead we just // clear the quiescing flag so we won't shut down any new connections that are // established. // // Note it is illegal to call this method if we're not quiescing. assert(_quiescing); _quiescing = PJ_FALSE; pthread_mutex_unlock(&_lock); }
void BaseMemcachedStore::delete_with_tombstone(const std::string& fqkey, const std::vector<memcached_st*>& replicas, SAS::TrailId trail) { if (trail != 0) { SAS::Event event(trail, SASEvent::MEMCACHED_DELETE, 0); event.add_var_param(fqkey); event.add_static_param(_tombstone_lifetime); SAS::report_event(event); } const char* key_ptr = fqkey.data(); const size_t key_len = fqkey.length(); // Calculate a timestamp (least-significant 32 bits of milliseconds since the // epoch) for the current time. We store this in the flags field to allow us // to resolve conflicts when resynchronizing between memcached servers. struct timespec ts; (void)clock_gettime(CLOCK_REALTIME, &ts); uint32_t flags = (uint32_t)((ts.tv_sec * 1000) + (ts.tv_nsec / 1000000)); // Calculate the vbucket for this key. int vbucket = vbucket_for_key(fqkey); for (size_t ii = 0; ii < replicas.size(); ++ii) { TRC_DEBUG("Attempt write tombstone to replica %d (connection %p)", ii, replicas[ii]); memcached_return_t rc = memcached_set_vb(replicas[ii], key_ptr, key_len, _binary ? vbucket : 0, TOMBSTONE.data(), TOMBSTONE.length(), _tombstone_lifetime, flags); if (!memcached_success(rc)) { log_delete_failure(fqkey, ii, replicas.size(), trail, 1); } } }
void DNSEnumService::parse_naptr_reply(const struct ares_naptr_reply* naptr_reply, std::vector<DNSEnumService::Rule>& rules) { for (const struct ares_naptr_reply* record = naptr_reply; record != NULL; record = record->next) { TRC_DEBUG("Got NAPTR record: %u %u \"%s\" \"%s\" \"%s\" %s", record->order, record->preference, record->service, record->flags, record->regexp, record->replacement); if ((strcasecmp((char*)record->service, "e2u+sip") == 0) || (strcasecmp((char*)record->service, "e2u+pstn:sip") == 0) || (strcasecmp((char*)record->service, "e2u+pstn:tel") == 0)) { boost::regex regex; std::string replace; bool terminal = false; if (!EnumService::parse_regex_replace(std::string((char*)record->regexp), regex, replace)) { TRC_WARNING("DNS ENUM record contains unparseable regular expression: %s", record->regexp); // As above, we don't give up totally here. continue; } // The only valid flag is u. If we see any other flags, we must ignore // the whole record (according to RFC 3761, 2.4.1). if (strcasecmp((char*)record->flags, "u") == 0) { terminal = true; } else if (strcmp((char*)record->flags, "") != 0) { TRC_WARNING("DNS ENUM record contains unknown flags: %s", record->flags); // Note that we don't give up totally here. If we end up with an empty // list, we'll break out then. Otherwise, we'll just try and push on. continue; } rules.push_back(Rule(regex, replace, terminal, record->order, record->preference)); } } std::sort(rules.begin(), rules.end(), DNSEnumService::Rule::compare_order_preference); }
/// Performs an HSS LIR query. int ICSCFLIRouter::hss_query() { int status_code = PJSIP_SC_OK; // If we've already done one query we must force the HSS to return // capabilities this time. std::string auth_type = (_hss_rsp.scscf.empty()) ? "" : "CAPAB"; TRC_DEBUG("Perform LIR - impu %s, originating %s, auth_type %s", _impu.c_str(), (_originating) ? "true" : "false", (auth_type != "") ? auth_type.c_str() : "None"); rapidjson::Document* rsp = NULL; HTTPCode rc =_hss->get_location_data(_impu, _originating, auth_type, rsp, _trail); if (rc == HTTP_NOT_FOUND) { // HSS returned not found, so reject the request with a 404. status_code = PJSIP_SC_NOT_FOUND; } else if ((rc != HTTP_OK) || (rsp == NULL)) { // HSS failed to respond or responded with invalid data, so reject the // request with a 480. // LCOV_EXCL_START status_code = PJSIP_SC_TEMPORARILY_UNAVAILABLE; // LCOV_EXCL_STOP } else { // HSS returned a well-formed response, so parse it. status_code = parse_hss_response(rsp, auth_type == "CAPAB"); } delete rsp; return status_code; }
void accumulate_internal(CurrentAndPrevious<ContinuousStatistics>& data, uint32_t sample) { struct timespec now; clock_gettime(CLOCK_REALTIME_COARSE, &now); ContinuousStatistics* current_data = data.get_current(now); TRC_DEBUG("Accumulating sample %uui into continuous accumulator statistic", sample); current_data->count++; // Compute the updated sum and sqsum based on the previous values, dependent on // how long since an update happened. Additionally update the sum of squares as a // rolling total, and update the time of the last update. Also maintain a // current value held, that can be used if the period ends. uint64_t time_since_last_update = ((now.tv_sec * 1000) + (now.tv_nsec / 1000000)) - (current_data->time_last_update_ms.load()); uint32_t current_value = current_data->current_value.load(); current_data->time_last_update_ms = (now.tv_sec * 1000) + (now.tv_nsec / 1000000); current_data->sum += current_value * time_since_last_update; current_data->sqsum += current_value * current_value * time_since_last_update; current_data->current_value = sample; // Update the low- and high-water marks. In each case, we get the current // value, decide whether a change is required and then atomically swap it // if so, repeating if it was changed in the meantime. Note that // compare_exchange_weak loads the current value into the expected value // parameter (lwm or hwm below) if the compare fails. uint_fast64_t lwm = current_data->lwm.load(); while ((sample < lwm) && (!current_data->lwm.compare_exchange_weak(lwm, sample))) { // Do nothing. } uint_fast64_t hwm = current_data->hwm.load(); while ((sample > hwm) && (!current_data->hwm.compare_exchange_weak(hwm, sample))) { // Do nothing. } };
/// Add rows to the table for a given string index (one row per time period /// that we are tracking. void create_and_add_rows(std::string string_index) { // This is going to mutate the maps so get the write lock first. pthread_rwlock_wrlock(&_table_lock); // Check that the rows don't already exist. We only call this function if // the rows don't exist, but it's possible that they've been added // inbetween us checking that they don't exist and getting the write lock. if (_five_second.count(string_index) != 0) { // The rows already exist. We don't release the RW lock until we've // completely finished adding all the rows so existence in the 5s map // means that all the rows are fully created. Just return. TRC_DEBUG("Tried to add new rows but another thread beat us to it"); pthread_rwlock_unlock(&_table_lock); return; } // Create CurrentAndPrevious views of EventStatisticAccumulator to // accumulate statistics for these rows at each of 5s and 5m scopes. _five_second[string_index] = new CurrentAndPrevious<EventStatisticAccumulator>(5000); _five_minute[string_index] = new CurrentAndPrevious<EventStatisticAccumulator>(300000); // Now add the actual rows to the table, referencing the // EventStatisticAccumulator views we've just created. Note that we create // the rows with an abitrary internal key (using a _table_rows counter to // generate unique keys) as we don't need to be able to look them up again // in future. We don't need this because we don't currently support // removing rows from the table. If we need to add this in future then we // would need to start keying the rows off a tuple of string index and time // scope. this->add(_table_rows++, new TimeAndStringBasedEventRow(TimePeriodIndexes::scopePrevious5SecondPeriod, string_index, new TimeAndStringBasedEventRow::PreviousView((_five_second)[string_index]))); this->add(_table_rows++, new TimeAndStringBasedEventRow(TimePeriodIndexes::scopeCurrent5MinutePeriod, string_index, new TimeAndStringBasedEventRow::CurrentView((_five_minute)[string_index]))); this->add(_table_rows++, new TimeAndStringBasedEventRow(TimePeriodIndexes::scopePrevious5MinutePeriod, string_index, new TimeAndStringBasedEventRow::PreviousView((_five_minute)[string_index]))); pthread_rwlock_unlock(&_table_lock); }
void BaseResolver::success(const AddrInfo& ai) { if (Log::enabled(Log::DEBUG_LEVEL)) { std::string ai_str = ai.to_string(); TRC_DEBUG("Successful response from %s", ai_str.c_str()); } pthread_mutex_lock(&_hosts_lock); Hosts::iterator i = _hosts.find(ai); if (i != _hosts.end()) { i->second.success(); } pthread_mutex_unlock(&_hosts_lock); }
SIPResolver::SIPResolver(DnsCachedResolver* dns_client, int blacklist_duration) : BaseResolver(dns_client) { TRC_DEBUG("Creating SIP resolver"); // Create the NAPTR cache. std::map<std::string, int> naptr_services; naptr_services["SIP+D2U"] = IPPROTO_UDP; naptr_services["SIP+D2T"] = IPPROTO_TCP; create_naptr_cache(naptr_services); // Create the SRV cache. create_srv_cache(); // Create the blacklist. create_blacklist(blacklist_duration); TRC_STATUS("Created SIP resolver"); }
// Pop a specific timer, if required pass the timer on to the replication layer to // reset the timer for another pop, otherwise destroy the timer record. void TimerHandler::pop(Timer* timer) { // Tombstones are reaped when they pop. if (timer->is_tombstone()) { TRC_DEBUG("Discarding expired tombstone"); delete timer; timer = NULL; return; } // Increment the timer's sequence before sending the callback. timer->sequence_number++; // Update the timer in case it has out of date configuration timer->update_cluster_information(); // The callback borrows of the timer at this point. _callback->perform(timer); timer = NULL; }
static void sas_log_tx_msg(pjsip_tx_data *tdata) { // For outgoing messages always use the trail identified in the module data SAS::TrailId trail = get_trail(tdata); if (trail == DONT_LOG_TO_SAS) { TRC_DEBUG("Skipping SAS logging for OPTIONS response"); return; } else if (trail != 0) { // Raise SAS markers on initial requests only - responses in the same // transaction will have the same trail ID so don't need additional markers if (tdata->msg->type == PJSIP_REQUEST_MSG) { PJUtils::report_sas_to_from_markers(trail, tdata->msg); PJUtils::mark_sas_call_branch_ids(trail, NULL, tdata->msg); } // Log the message event. SAS::Event event(trail, SASEvent::TX_SIP_MSG, 0); event.add_static_param(pjsip_transport_get_type_from_flag(tdata->tp_info.transport->flag)); event.add_static_param(tdata->tp_info.dst_port); event.add_var_param(tdata->tp_info.dst_name); event.add_compressed_param((int)(tdata->buf.cur - tdata->buf.start), tdata->buf.start, &SASEvent::PROFILE_SIP); SAS::report_event(event); } else { TRC_ERROR("Transmitting message with no SAS trail identifier\n%.*s", (int)(tdata->buf.cur - tdata->buf.start), tdata->buf.start); } }
Store::Status AuthStore::set_digest(const std::string& impi, const std::string& nonce, const AuthStore::Digest* digest, SAS::TrailId trail) { std::string key = impi + '\\' + nonce; std::string data = serialize_digest(digest); TRC_DEBUG("Set digest for %s\n%s", key.c_str(), data.c_str()); Store::Status status = _data_store->set_data("AuthStore", key, data, digest->_cas, _expiry, trail); if (status != Store::Status::OK) { // LCOV_EXCL_START - Store used in UTs doesn't fail TRC_ERROR("Failed to write digest for key %s", key.c_str()); SAS::Event event(trail, SASEvent::AUTHSTORE_SET_FAILURE, 0); event.add_var_param(key); event.add_var_param(data); SAS::report_event(event); // LCOV_EXCL_STOP } else { SAS::Event event(trail, SASEvent::AUTHSTORE_SET_SUCCESS, 0); event.add_var_param(key); event.add_var_param(data); SAS::report_event(event); } return status; }
std::string JSONEnumService::lookup_uri_from_user(const std::string &user, SAS::TrailId trail) const { std::string uri; TRC_DEBUG("Translating URI via JSON ENUM lookup"); if (user.empty()) { TRC_INFO("No dial string supplied, so don't do ENUM lookup"); return std::string(); } std::string aus = user_to_aus(user); struct NumberPrefix* pfix = prefix_match(aus); if (pfix == NULL) { TRC_INFO("No matching number range %s from ENUM lookup", user.c_str()); return uri; } // Apply the regular expression to the user string to generate a new // URI. try { uri = boost::regex_replace(aus, pfix->match, pfix->replace); } catch(...) // LCOV_EXCL_START Only throws if expression too complex or similar hard-to-hit conditions { TRC_ERROR("Failed to translate number with regex"); return uri; // LCOV_EXCL_STOP } TRC_INFO("Number %s found, translated URI = %s", user.c_str(), uri.c_str()); return uri; }
void ICSCFSproutletTsx::add_p_profile_header(const std::string& wildcard, pjsip_msg* req) { // The wildcard can contain characters that aren't valid in // URIs (e.g. square brackets) - escape these before attempting to // create a URI. pjsip_routing_hdr* ppk = identity_hdr_create(get_pool(req), STR_P_PROFILE_KEY); pjsip_uri* wildcard_uri = PJUtils::uri_from_string( PJUtils::escape_string_for_uri(wildcard), get_pool(req)); if (wildcard_uri) { TRC_DEBUG("Adding a P-Profile-Key header for %s", wildcard.c_str()); ppk->name_addr.uri = wildcard_uri; pjsip_msg_add_hdr(req, (pjsip_hdr*)ppk); } else { TRC_ERROR("Invalid wildcard returned on LIA: %s", wildcard.c_str()); } }
void ConnectionPool::init() { // Create an initial set of connections. for (int ii = 0; ii < _num_connections; ++ii) { create_connection(ii); } if (_recycle_period != 0) { // Spawn a thread to recycle connections pj_status_t status = pj_thread_create(_pool, "recycler", &recycle_thread, (void*)this, 0, 0, &_recycler); if (status != PJ_SUCCESS) { TRC_ERROR("Error creating recycler thread, %s", PJUtils::pj_status_to_string(status).c_str()); } } TRC_DEBUG("Started %d connections to %.*s:%d", _num_connections, _target.host.slen, _target.host.ptr, _target.port); }
/// Add P-Charging headers on incoming out-of-dialog/dialog initiating requests static void proxy_add_p_charging_header(pjsip_tx_data *tdata) { TRC_DEBUG("Add P-Charging headers"); std::string cdf_domain = PJUtils::pj_str_to_string(&stack_data.cdf_domain); if (cdf_domain != "") { // Add the P-Charging-Function-Addresses. The value of the CDF is passed in // as a parameter in bono - if this isn't present then don't set these // headers. pjsip_p_c_f_a_hdr* p_c_f_a = pjsip_p_c_f_a_hdr_create(tdata->pool); pjsip_param* new_param = (pjsip_param*) pj_pool_alloc(tdata->pool, sizeof(pjsip_param)); new_param->name = STR_CCF; new_param->value = stack_data.cdf_domain; pj_list_insert_before(&p_c_f_a->ccf, new_param); pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)p_c_f_a); // Add the P-Charging-Vector Id. The icid-value is the Call-ID, and the // icid-generated-at is the bono hostname - it must be unique to the node that // generates it. pjsip_cid_hdr* call_id = (pjsip_cid_hdr*)pjsip_msg_find_hdr_by_name(tdata->msg, &STR_CALL_ID, NULL); std::string c_id = PJUtils::pj_str_to_string(&call_id->id); c_id.erase(std::remove(c_id.begin(), c_id.end(), '@'), c_id.end()); c_id.erase(std::remove(c_id.begin(), c_id.end(), '"'), c_id.end()); pjsip_p_c_v_hdr* p_c_v = pjsip_p_c_v_hdr_create(tdata->pool); pj_strdup2(tdata->pool, &p_c_v->icid, c_id.c_str()); p_c_v->icid_gen_addr = stack_data.public_host; pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)p_c_v); } }
bool BaseResolver::NAPTRCacheFactory::parse_regex_replace(const std::string& regex_replace, boost::regex& regex, std::string& replace) { bool success = false; // Split the regular expression into the match and replace sections. RFC3402 // says any character other than 1-9 or i can be the delimiter, but // recommends / or !. We just use the first character and reject if it // doesn't neatly split the regex into two. std::vector<std::string> match_replace; Utils::split_string(regex_replace, regex_replace[0], match_replace); if (match_replace.size() == 2) { TRC_DEBUG("Split regex into match=%s, replace=%s", match_replace[0].c_str(), match_replace[1].c_str()); try { regex.assign(match_replace[0]); replace = match_replace[1]; success = true; } // LCOV_EXCL_START catch (...) { success = false; } // LCOV_EXCL_STOP } else { success = false; } return success; }
//--------------------------------------------------------------------------------------- std::string Templater::generate() //--------------------------------------------------------------------------------------- { TRC_DEBUG_FUNC_ENTER(0U, ""); std::size_t tagOpenBracket = mContent.find(Templater::MACRO_TAG); std::size_t tagCloseBracket = mContent.find(Templater::MACRO_TAG, tagOpenBracket+1); while( std::string::npos != tagOpenBracket && std::string::npos != tagCloseBracket) { std::string token = mContent.substr( tagOpenBracket + Templater::MACRO_TAG.length(), tagCloseBracket - tagOpenBracket - Templater::MACRO_TAG.length() ); std::size_t lastModifiedSymbol = tagCloseBracket + 1; auto iter = mMacroses.find(token); if (iter != mMacroses.end()) { TRC_DEBUG(0U, "macro recognized: '%s' -> '%s'", token.c_str(), iter->second.c_str()); mContent.replace( mContent.begin() + tagOpenBracket, mContent.begin() + tagCloseBracket + Templater::MACRO_TAG.length(), iter->second); lastModifiedSymbol = tagOpenBracket + iter->second.length(); } tagOpenBracket = mContent.find(Templater::MACRO_TAG, lastModifiedSymbol + 1); tagCloseBracket = mContent.find(Templater::MACRO_TAG, tagOpenBracket + 1); } TRC_DEBUG_FUNC_EXIT(0U); return mContent; }
JSONEnumService::JSONEnumService(std::string configuration) { // Check whether the file exists. struct stat s; if ((stat(configuration.c_str(), &s) != 0) && (errno == ENOENT)) { TRC_STATUS("No ENUM configuration (file %s does not exist)", configuration.c_str()); CL_SPROUT_ENUM_FILE_MISSING.log(configuration.c_str()); return; } TRC_STATUS("Loading ENUM configuration from %s", configuration.c_str()); // Read from the file std::ifstream fs(configuration.c_str()); std::string enum_str((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>()); if (enum_str == "") { // LCOV_EXCL_START TRC_ERROR("Failed to read ENUM configuration data from %s", configuration.c_str()); CL_SPROUT_ENUM_FILE_EMPTY.log(configuration.c_str()); return; // LCOV_EXCL_STOP } // Now parse the document rapidjson::Document doc; doc.Parse<0>(enum_str.c_str()); if (doc.HasParseError()) { TRC_ERROR("Failed to read ENUM configuration data: %s\nError: %s", enum_str.c_str(), rapidjson::GetParseError_En(doc.GetParseError())); CL_SPROUT_ENUM_FILE_INVALID.log(configuration.c_str()); return; } try { JSON_ASSERT_CONTAINS(doc, "number_blocks"); JSON_ASSERT_ARRAY(doc["number_blocks"]); const rapidjson::Value& nb_arr = doc["number_blocks"]; for (rapidjson::Value::ConstValueIterator nb_it = nb_arr.Begin(); nb_it != nb_arr.End(); ++nb_it) { try { std::string prefix; JSON_GET_STRING_MEMBER(*nb_it, "prefix", prefix); std::string regex; JSON_GET_STRING_MEMBER(*nb_it, "regex", regex); // Entry is well-formed, so add it. TRC_DEBUG("Found valid number prefix block %s", prefix.c_str()); NumberPrefix *pfix = new NumberPrefix; pfix->prefix = prefix; if (parse_regex_replace(regex, pfix->match, pfix->replace)) { _number_prefixes.push_back(pfix); TRC_STATUS(" Adding number prefix %s, regex=%s", pfix->prefix.c_str(), regex.c_str()); } else { TRC_WARNING("Badly formed regular expression in ENUM number block %s", regex.c_str()); delete pfix; } } catch (JsonFormatError err) { // Badly formed number block. TRC_WARNING("Badly formed ENUM number block (hit error at %s:%d)", err._file, err._line); CL_SPROUT_ENUM_FILE_INVALID.log(configuration.c_str()); } } } catch (JsonFormatError err) { TRC_ERROR("Badly formed ENUM configuration data - missing number_blocks object"); CL_SPROUT_ENUM_FILE_INVALID.log(configuration.c_str()); } }
std::string DNSEnumService::lookup_uri_from_user(const std::string& user, SAS::TrailId trail) const { if (user.empty()) { TRC_INFO("No dial string supplied, so don't do ENUM lookup"); return std::string(); } // Log starting ENUM processing. SAS::Event event(trail, SASEvent::ENUM_START, 0); event.add_var_param(user); SAS::report_event(event); // Determine the Application Unique String (AUS) from the user. This is // used to form the first key, and also as the input into the regular // expressions. std::string aus = user_to_aus(user); std::string string = aus; // Get the resolver to use. This comes from thread-local data. DNSResolver* resolver = get_resolver(); // Spin round until we've finished (successfully or otherwise) or we've done // the maximum number of queries. bool complete = false; bool failed = false; bool server_failed = false; int dns_queries = 0; while ((!complete) && (!failed) && (dns_queries < MAX_DNS_QUERIES)) { // Translate the key into a domain and issue a query for it. std::string domain = key_to_domain(string); struct ares_naptr_reply* naptr_reply = NULL; int status = resolver->perform_naptr_query(domain, naptr_reply, trail); if (status == ARES_SUCCESS) { // Parse the reply into a sorted list of rules. std::vector<Rule> rules; parse_naptr_reply(naptr_reply, rules); // Now spin through the rules, looking for the first match. std::vector<DNSEnumService::Rule>::const_iterator rule; for (rule = rules.begin(); rule != rules.end(); ++rule) { if (rule->matches(string)) { // We found a match, so apply the regular expression to the AUS (not // the previous string - this is what ENUM mandates). If this was a // terminal rule, we now have a SIP URI and we're finished. // Otherwise, the output of the regular expression is used as the // next key. try { string = rule->replace(aus, trail); complete = rule->is_terminal(); } catch(...) // LCOV_EXCL_START Only throws if expression too complex or similar hard-to-hit conditions { TRC_ERROR("Failed to translate number with regex"); failed = true; // LCOV_EXCL_STOP } break; } } // If we didn't find a match (and so hit the end of the list), consider // this a failure. failed = failed || (rule == rules.end()); } else if (status == ARES_ENOTFOUND) { // Our DNS query failed, so give up, but this is not an ENUM server issue - // we just tried to look up an unknown name. failed = true; } else { // Our DNS query failed. Give up, and track an ENUM server failure. failed = true; server_failed = true; } // Free off the NAPTR reply if we have one. if (naptr_reply != NULL) { resolver->free_naptr_reply(naptr_reply); naptr_reply = NULL; } dns_queries++; } // Log that we've finished processing (and whether it was successful or not). if (complete) { TRC_DEBUG("Enum lookup completes: %s", string.c_str()); SAS::Event event(trail, SASEvent::ENUM_COMPLETE, 0); event.add_var_param(user); event.add_var_param(string); SAS::report_event(event); } else { TRC_WARNING("Enum lookup did not complete for user %s", user.c_str()); SAS::Event event(trail, SASEvent::ENUM_INCOMPLETE, 0); event.add_var_param(user); SAS::report_event(event); // On failure, we must return an empty (rather than incomplete) string. string = std::string(""); } // Report state of last communication attempt (which may potentially set/clear // an associated alarm). if (_comm_monitor) { if (server_failed) { _comm_monitor->inform_failure(); } else { _comm_monitor->inform_success(); } } return string; }
// Determine the type of a URI. // // Parameters: // // - uri - the URI to classify // - prefer_sip - for ambiguous URIs like sip:[email protected] (which could be a global phone // number or just a SIP URI), prefer to interpret it as SIP // - check_np - check for the presence of Number Portability parameters and // classify accordingly // URIClass URIClassifier::classify_uri(const pjsip_uri* uri, bool prefer_sip, bool check_np) { URIClass ret = URIClass::UNKNOWN; // First, check to see if this URI has number portability data - this takes priority bool has_rn = false; bool has_npdi = false; if (check_np) { if (PJSIP_URI_SCHEME_IS_TEL(uri)) { // If the URI is a tel URI, pull out the information from the other_params has_rn = (pjsip_param_find(&((pjsip_tel_uri*)uri)->other_param, &STR_RN) != NULL); has_npdi = (pjsip_param_find(&((pjsip_tel_uri*)uri)->other_param, &STR_NPDI) != NULL); } else if (PJSIP_URI_SCHEME_IS_SIP(uri)) { // If the URI is a tel URI, pull out the information from the userinfo_params has_rn = (pjsip_param_find(&((pjsip_sip_uri*)uri)->userinfo_param, &STR_RN) != NULL); has_npdi = (pjsip_param_find(&((pjsip_sip_uri*)uri)->userinfo_param, &STR_NPDI) != NULL); } } if (has_rn) { if (has_npdi) { ret = FINAL_NP_DATA; } else { ret = NP_DATA; } } // No number portability data else if (PJSIP_URI_SCHEME_IS_TEL(uri)) { // TEL URIs can only represent phone numbers - decide if it's a global (E.164) number or not pjsip_tel_uri* tel_uri = (pjsip_tel_uri*)uri; std::string user = PJUtils::pj_str_to_string(&tel_uri->number); boost::match_results<std::string::const_iterator> results; if (boost::regex_match(user, results, CHARS_ALLOWED_IN_GLOBAL_NUM)) { ret = GLOBAL_PHONE_NUMBER; } else { ret = enforce_global ? LOCAL_PHONE_NUMBER : GLOBAL_PHONE_NUMBER; } } else if (PJSIP_URI_SCHEME_IS_SIP(uri)) { pjsip_sip_uri* sip_uri = (pjsip_sip_uri*)uri; pj_str_t host = sip_uri->host; bool home_domain = is_home_domain(host); bool local_to_node = is_local_name(host); bool is_gruu = (pjsip_param_find(&((pjsip_sip_uri*)uri)->other_param, &STR_GR) != NULL); bool treat_number_as_phone = !enforce_user_phone && !prefer_sip; TRC_DEBUG("home domain: %s, local_to_node: %s, is_gruu: %s, enforce_user_phone: %s, prefer_sip: %s, treat_number_as_phone: %s", home_domain ? "true" : "false", local_to_node ? "true" : "false", is_gruu ? "true" : "false", enforce_user_phone ? "true" : "false", prefer_sip ? "true" : "false", treat_number_as_phone ? "true" : "false"); bool classified = false; // SIP URI that's 'really' a phone number - apply the same logic as for TEL URIs if ((!pj_strcmp(&((pjsip_sip_uri*)uri)->user_param, &STR_USER_PHONE) || (home_domain && treat_number_as_phone && !is_gruu))) { // Get the user part minus any parameters. std::string user = PJUtils::pj_str_to_string(&sip_uri->user); if (!user.empty()) { std::vector<std::string> user_tokens; Utils::split_string(user, ';', user_tokens, 0, true); boost::match_results<std::string::const_iterator> results; if (boost::regex_match(user_tokens[0], results, CHARS_ALLOWED_IN_GLOBAL_NUM)) { ret = GLOBAL_PHONE_NUMBER; classified = true; } else if (boost::regex_match(user_tokens[0], results, CHARS_ALLOWED_IN_LOCAL_NUM)) { ret = enforce_global ? LOCAL_PHONE_NUMBER : GLOBAL_PHONE_NUMBER; classified = true; } } } if (!classified) { // Not a phone number - classify it based on domain ret = (home_domain) ? HOME_DOMAIN_SIP_URI : ((local_to_node) ? NODE_LOCAL_SIP_URI : OFFNET_SIP_URI); } } std::string uri_str = PJUtils::uri_to_string(PJSIP_URI_IN_OTHER, uri); TRC_DEBUG("Classified URI %s as %d", uri_str.c_str(), (int)ret); return ret; }
// Check if the response from the client matches the stored digest // The logic is: // HA1 is the digest returned from Homestead. // HA2 = MD5(method : uri), e.g. MD5(GET:/org.projectclearwater.call-list/users/<IMPU>/call-list.xml) // response = MD5(HA1 : nonce : nonce_count (provided by client) : cnonce : qop : HA2) HTTPCode HTTPDigestAuthenticate::check_if_matches(AuthStore::Digest* digest, std::string& www_auth_header, Response* response) { HTTPCode rc = HTTP_OK; if (digest->_opaque != response->_opaque) { TRC_DEBUG("The opaque value in the request (%s) doesn't match the stored value (%s)", response->_opaque.c_str(), digest->_opaque.c_str()); return HTTP_BAD_REQUEST; } else if (response->_realm != digest->_realm) { TRC_DEBUG("Request not targeted at the stored domain. Target: %s, Realm: %s", response->_realm.c_str(), digest->_realm.c_str()); return HTTP_BAD_REQUEST; } unsigned char ha2[Utils::MD5_HASH_SIZE]; unsigned char ha2_hex[Utils::HEX_HASH_SIZE + 1]; MD5_CTX Md5Ctx; MD5_Init(&Md5Ctx); MD5_Update(&Md5Ctx, _method.c_str(), strlen(_method.c_str())); MD5_Update(&Md5Ctx, ":", 1); MD5_Update(&Md5Ctx, response->_uri.c_str(), strlen(response->_uri.c_str())); MD5_Final(ha2, &Md5Ctx); Utils::hashToHex(ha2, ha2_hex); unsigned char resp[Utils::MD5_HASH_SIZE]; unsigned char resp_hex[Utils::HEX_HASH_SIZE + 1]; MD5_Init(&Md5Ctx); MD5_Update(&Md5Ctx, digest->_ha1.c_str(), strlen(digest->_ha1.c_str())); MD5_Update(&Md5Ctx, ":", 1); MD5_Update(&Md5Ctx, response->_nonce.c_str(), strlen(response->_nonce.c_str())); MD5_Update(&Md5Ctx, ":", 1); MD5_Update(&Md5Ctx, response->_nc.c_str(), strlen(response->_nc.c_str())); MD5_Update(&Md5Ctx, ":", 1); MD5_Update(&Md5Ctx, response->_cnonce.c_str(), strlen(response->_cnonce.c_str())); MD5_Update(&Md5Ctx, ":", 1); MD5_Update(&Md5Ctx, response->_qop.c_str(), strlen(response->_qop.c_str())); MD5_Update(&Md5Ctx, ":", 1); MD5_Update(&Md5Ctx, ha2_hex, Utils::HEX_HASH_SIZE); MD5_Final(resp, &Md5Ctx); Utils::hashToHex(resp, resp_hex); std::string stored_response((const char*) resp_hex); if (response->_response == stored_response) { TRC_DEBUG("Client response matches stored digest"); // If the nonce count supplied isn't an int then this will return 0, // and the nonce will be treated as stale. uint32_t client_count = atoi(response->_nc.c_str()); // Nonce count is stale. Request the digest from Homestead again. if (client_count < digest->_nonce_count) { TRC_DEBUG("Client response's nonce count is too low"); SAS::Event event(_trail, SASEvent::AUTHENTICATION_OUT_OF_DATE, 0); SAS::report_event(event); rc = request_digest_and_store(www_auth_header, true, response); } else if (_impu != digest->_impu) { TRC_DEBUG("Request's IMPU doesn't match stored IMPU. Target: %s, Stored: %s", _impu.c_str(), digest->_impu.c_str()); SAS::Event event(_trail, SASEvent::AUTHENTICATION_WRONG_IMPU, 0); event.add_var_param(_impu); event.add_var_param(digest->_impu); SAS::report_event(event); rc = request_digest_and_store(www_auth_header, true, response); } else { // Authentication successful. Increment the stored nonce count digest->_nonce_count++; Store::Status store_rc = _auth_store->set_digest(_impi, digest->_nonce, digest, _trail); TRC_DEBUG("Updating nonce count - store returned %d", store_rc); if (store_rc == Store::DATA_CONTENTION) { // The write to the store failed due to a CAS mismatch. This means that // the digest has already been used to authenticate another request, so // the authentication on this request is stale. Rechallenge. TRC_DEBUG("Failed to update nonce count - rechallenge"); SAS::Event event(_trail, SASEvent::AUTHENTICATION_OUT_OF_DATE, 1); SAS::report_event(event); rc = request_digest_and_store(www_auth_header, true, response); } else { // The nonce count was either updated successfully (in which case we // accept the request) or the store failed (in which case we're not sure // what to do for the best, and accepting the request is sensible // default behaviour). TRC_DEBUG("Authentication accepted"); SAS::Event event(_trail, SASEvent::AUTHENTICATION_ACCEPTED, 0); SAS::report_event(event); _stat_auth_success_count->increment(); } } } else { // Digest doesn't match - reject the request TRC_DEBUG("Client response doesn't match stored digest"); SAS::Event event(_trail, SASEvent::AUTHENTICATION_REJECTED, 0); SAS::report_event(event); _stat_auth_failure_count->increment(); rc = HTTP_FORBIDDEN; } return rc; }
HTTPCode HTTPDigestAuthenticate::parse_auth_header(std::string auth_header, bool& auth_info, Response* response) { HTTPCode rc = HTTP_OK; std::string digest = "Digest"; size_t digest_pos = auth_header.find(digest); if (digest_pos != 0) { TRC_DEBUG("Authorization header doesn't contain Digest credentials"); return HTTP_BAD_REQUEST; } std::string rest_of_header = auth_header.substr(digest.length()); // Split the request on the delimiter ',' std::vector<std::string> response_params; Utils::split_string(rest_of_header, ',', response_params, 0, true, true); // Then split by = and sort through array. std::map<std::string, std::string> response_key_values; std::vector<std::string> temp_value; for (std::vector<std::string>::iterator ii = response_params.begin(); ii != response_params.end(); ++ii) { std::string parameter(ii->c_str()); Utils::split_string(parameter, '=', temp_value, 2, true); // Strip quotes off befores storing int val_len = temp_value[1].length(); if (val_len > 1 && temp_value[1][0] == '"' && temp_value[1][val_len - 1] == '"') { temp_value[1] = temp_value[1].substr(1, val_len - 2); } response_key_values.insert(std::pair<std::string, std::string>(temp_value[0], temp_value[1])); temp_value.clear(); } std::map<std::string, std::string>::iterator username_entry = response_key_values.find("username"); std::map<std::string, std::string>::iterator realm_entry = response_key_values.find("realm"); std::map<std::string, std::string>::iterator nonce_entry = response_key_values.find("nonce"); std::map<std::string, std::string>::iterator uri_entry = response_key_values.find("uri"); std::map<std::string, std::string>::iterator qop_entry = response_key_values.find("qop"); std::map<std::string, std::string>::iterator nc_entry = response_key_values.find("nc"); std::map<std::string, std::string>::iterator cnonce_entry = response_key_values.find("cnonce"); std::map<std::string, std::string>::iterator response_entry = response_key_values.find("response"); std::map<std::string, std::string>::iterator opaque_entry = response_key_values.find("opaque"); // A valid Authorization header must contain a username. // It must then either contain all of a realm, nonce, uri, qop, nc, // cnonce, response and opaque values, or none of the above. // It can contain other parameters; these aren't validated. if ((username_entry != response_key_values.end()) && (realm_entry != response_key_values.end()) && (nonce_entry != response_key_values.end()) && (uri_entry != response_key_values.end()) && (qop_entry != response_key_values.end()) && (nc_entry != response_key_values.end()) && (cnonce_entry != response_key_values.end()) && (response_entry != response_key_values.end()) && (opaque_entry != response_key_values.end())) { TRC_DEBUG("Authorization header valid and complete"); _impi = username_entry->second; auth_info = true; response->set_members(username_entry->second, realm_entry->second, nonce_entry->second, uri_entry->second, qop_entry->second, nc_entry->second, cnonce_entry->second, response_entry->second, opaque_entry->second); TRC_DEBUG("Raising correlating marker with opaque value = %s", opaque_entry->second.c_str()); SAS::Marker corr(_trail, MARKED_ID_GENERIC_CORRELATOR, 0); corr.add_var_param(opaque_entry->second); // The marker should be trace-scoped, and should not reactivate any trail // groups SAS::report_marker(corr, SAS::Marker::Scope::Trace, false); } else if ((username_entry != response_key_values.end()) && (realm_entry == response_key_values.end()) && (nonce_entry == response_key_values.end()) && (uri_entry == response_key_values.end()) && (qop_entry == response_key_values.end()) && (nc_entry == response_key_values.end()) && (cnonce_entry == response_key_values.end()) && (response_entry == response_key_values.end()) && (opaque_entry == response_key_values.end())) { TRC_DEBUG("Authorization header valid and minimal"); _impi = username_entry->second; auth_info = false; } else { TRC_DEBUG("Authorization header not valid"); rc = HTTP_BAD_REQUEST; } return rc; }
void ConnectionPool::transport_state_update(pjsip_transport* tp, pjsip_transport_state state) { // Transport state has changed. pthread_mutex_lock(&_tp_hash_lock); std::map<pjsip_transport*, int>::const_iterator i = _tp_map.find(tp); if (i != _tp_map.end()) { int hash_slot = i->second; if ((state == PJSIP_TP_STATE_CONNECTED) && (!_tp_hash[hash_slot].connected)) { // New connection has connected successfully, so update the statistics. TRC_DEBUG("Transport %s in slot %d has connected", tp->obj_name, hash_slot); _tp_hash[hash_slot].connected = PJ_TRUE; ++_active_connections; increment_connection_count(tp); if (_recycle_period > 0) { // Compute a TTL for the connection. To avoid all the recycling being // synchronized we set the TTL to the specified average recycle time // perturbed by a random factor. int ttl = _recycle_period + (rand() % (2 * _recycle_margin)) - _recycle_margin; _tp_hash[hash_slot].recycle_time = time(NULL) + ttl; } else { // Connection recycling is disabled. _tp_hash[hash_slot].recycle_time = 0; } } else if ((state == PJSIP_TP_STATE_DISCONNECTED) || (state == PJSIP_TP_STATE_DESTROYED)) { // Either a connection has failed or been shutdown, or a new connection // failed to connect. TRC_DEBUG("Transport %s in slot %d has failed", tp->obj_name, hash_slot); if (_tp_hash[hash_slot].connected) { // A connection has failed, so update the statistics. --_active_connections; decrement_connection_count(tp); } else { // Failed to establish a connection to this server, so blacklist // it so we steer clear of it for a while. We don't blacklist // if an existing connection fails as this may be a transient error // or even a disconnect triggered by an inactivity timeout . AddrInfo server; server.transport = IPPROTO_TCP; server.port = pj_sockaddr_get_port(&tp->key.rem_addr); server.address.af = tp->key.rem_addr.addr.sa_family; if (server.address.af == AF_INET) { server.address.addr.ipv4.s_addr = tp->key.rem_addr.ipv4.sin_addr.s_addr; } else { memcpy((char*)&server.address.addr.ipv6, (char*)&tp->key.rem_addr.ipv6.sin6_addr, sizeof(struct in6_addr)); } PJUtils::blacklist_server(server); } // Don't listen for any more state changes on this connection (but note // it's illegal to call any methods on the transport once it's entered the // `destroyed` state). if (state != PJSIP_TP_STATE_DESTROYED) { pjsip_transport_remove_state_listener(tp, _tp_hash[hash_slot].listener_key, (void *)this); } // Remove the transport from the hash and the map. _tp_hash[hash_slot].tp = NULL; _tp_hash[hash_slot].listener_key = NULL; _tp_hash[hash_slot].connected = PJ_FALSE; _tp_map.erase(tp); // Remove our reference to the transport. pjsip_transport_dec_ref(tp); } } pthread_mutex_unlock(&_tp_hash_lock); }