<commit_before>/*
 * Flexisip, a flexible SIP proxy server with media capabilities.
 * Copyright (C) 2018  Belledonne Communications SARL, All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <algorithm>
#include <cstring>
#include <sstream>
#include <stdexcept>

#include "log/logmanager.hh"

#include "external-auth-module.hh"

using namespace std;

ExternalAuthModule::ExternalAuthModule(su_root_t *root, const std::string &domain, const std::string &algo) : FlexisipAuthModuleBase(root, domain, algo) {
	mEngine = nth_engine_create(root, TAG_END());
}

ExternalAuthModule::ExternalAuthModule(su_root_t *root, const std::string &domain, const std::string &algo, int nonceExpire) : FlexisipAuthModuleBase(root, domain, algo, nonceExpire) {
	mEngine = nth_engine_create(root, TAG_END());
}

ExternalAuthModule::~ExternalAuthModule() {
	nth_engine_destroy(mEngine);
}

void ExternalAuthModule::checkAuthHeader(FlexisipAuthStatus &as, msg_auth_t *credentials, auth_challenger_t const *ach) {
	try {
		auto &externalAs = dynamic_cast<ExternalAuthModule::Status &>(as);
		map<string, string> params = extractParameters(externalAs, *credentials);

		string uri;
		try {
			uri = mUriFormater.format(params);
		} catch (const invalid_argument &e) {
			ostringstream os;
			os << "cannot format HTTP URI: " << e.what();
			throw runtime_error(os.str());
		}

		auto *ctx = new HttpRequestCtx({*this, as});

		nth_client_t *request = nth_client_tcreate(mEngine,
			onHttpResponseCb,
			reinterpret_cast<nth_client_magic_t *>(ctx),
			http_method_get,
			"GET",
			URL_STRING_MAKE(uri.c_str()),
			TAG_END()
		);
		if (request == nullptr) {
			ostringstream os;
			os << "HTTP request for '" << uri << "' has failed";
			delete ctx;
			throw runtime_error(os.str());
		}
		SLOGD << "HTTP request [" << request << "] to '" << uri << "' successfully sent";
		as.status(100);
	} catch (const runtime_error &e) {
		SLOGE << e.what();
		onError(as);
	}
}

void ExternalAuthModule::loadPassword(const FlexisipAuthStatus &as) {
}

std::map<std::string, std::string> ExternalAuthModule::extractParameters(const Status &as, const msg_auth_t &credentials) const {
	map<string, string> params;

	for (int i = 0; credentials.au_params[i] != nullptr; i++) {
		const char *param = credentials.au_params[i];
		const char *equal = strchr(const_cast<char *>(param), '=');
		string key(param, equal-param);
		string value = equal+1;
		params[move(key)] = move(value);
	}

	params["scheme"] = credentials.au_scheme;
	params["method"] = as.method();
	params["from"] = as.fromHeader();
	params["sip-instance"] = as.sipInstance();
	params["domain"] = as.domain();
	return params;
}

void ExternalAuthModule::onHttpResponse(FlexisipAuthStatus &as, nth_client_t *request, const http_t *http) {
	shared_ptr<RequestSipEvent> ev;
	try {
		int sipCode = 0;
		string phrase;
		string reasonHeaderValue;
		string pAssertedIdentity;
		ostringstream os;

		if (http == nullptr) {
			os << "HTTP server responds with code " << nth_client_status(request);
			throw runtime_error(os.str());
		}

		int status = http->http_status->st_status;
		SLOGD << "HTTP response received [" << status << "]: " << endl << http->http_payload;
		if (status != 200) {
			os << "unhandled HTTP status code [" << status << "]";
			throw runtime_error(os.str());
		}

		string httpBody = toString(http->http_payload);
		if (httpBody.empty()) {
			os << "HTTP server answered with an empty body";
			throw runtime_error(os.str());
		}

		try {
			map<string, string> kv = parseHttpBody(httpBody);
			sipCode = stoi(kv["Status"]);
			phrase = move(kv["Phrase"]);
			reasonHeaderValue = move(kv["Reason"]);
			pAssertedIdentity = move(kv["P-Asserted-Identity"]);
		} catch (const logic_error &e) {
			os << "error while parsing HTTP body: " << e.what();
			throw runtime_error(os.str());
		}

		if (!validSipCode(sipCode) || reasonHeaderValue.empty()) {
			os << "invalid SIP code or reason";
			throw runtime_error(os.str());
		}

		auto &httpAuthStatus = dynamic_cast<ExternalAuthModule::Status &>(as);
		httpAuthStatus.status(sipCode == 200 ? 0 : sipCode);
		httpAuthStatus.phrase(su_strdup(as.home(), phrase.c_str()));
		httpAuthStatus.reason(reasonHeaderValue);
		httpAuthStatus.pAssertedIdentity(pAssertedIdentity);
		finish(as);
	} catch (const runtime_error &e) {
		SLOGE << "HTTP request [" << request << "]: " << e.what();
		onError(as);
	}
}

std::map<std::string, std::string> ExternalAuthModule::parseHttpBody(const std::string &body) const {<commit_msg>Fix memory leak in the external authentication module<commit_after>/*
 * Flexisip, a flexible SIP proxy server with media capabilities.
 * Copyright (C) 2018  Belledonne Communications SARL, All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <algorithm>
#include <cstring>
#include <sstream>
#include <stdexcept>

#include "log/logmanager.hh"

#include "external-auth-module.hh"

using namespace std;

ExternalAuthModule::ExternalAuthModule(su_root_t *root, const std::string &domain, const std::string &algo) : FlexisipAuthModuleBase(root, domain, algo) {
	mEngine = nth_engine_create(root, TAG_END());
}

ExternalAuthModule::ExternalAuthModule(su_root_t *root, const std::string &domain, const std::string &algo, int nonceExpire) : FlexisipAuthModuleBase(root, domain, algo, nonceExpire) {
	mEngine = nth_engine_create(root, TAG_END());
}

ExternalAuthModule::~ExternalAuthModule() {
	nth_engine_destroy(mEngine);
}

void ExternalAuthModule::checkAuthHeader(FlexisipAuthStatus &as, msg_auth_t *credentials, auth_challenger_t const *ach) {
	try {
		auto &externalAs = dynamic_cast<ExternalAuthModule::Status &>(as);
		map<string, string> params = extractParameters(externalAs, *credentials);

		string uri;
		try {
			uri = mUriFormater.format(params);
		} catch (const invalid_argument &e) {
			ostringstream os;
			os << "cannot format HTTP URI: " << e.what();
			throw runtime_error(os.str());
		}

		auto *ctx = new HttpRequestCtx({*this, as});

		nth_client_t *request = nth_client_tcreate(mEngine,
			onHttpResponseCb,
			reinterpret_cast<nth_client_magic_t *>(ctx),
			http_method_get,
			"GET",
			URL_STRING_MAKE(uri.c_str()),
			TAG_END()
		);
		if (request == nullptr) {
			ostringstream os;
			os << "HTTP request for '" << uri << "' has failed";
			delete ctx;
			throw runtime_error(os.str());
		}
		SLOGD << "HTTP request [" << request << "] to '" << uri << "' successfully sent";
		as.status(100);
	} catch (const runtime_error &e) {
		SLOGE << e.what();
		onError(as);
	}
}

void ExternalAuthModule::loadPassword(const FlexisipAuthStatus &as) {
}

std::map<std::string, std::string> ExternalAuthModule::extractParameters(const Status &as, const msg_auth_t &credentials) const {
	map<string, string> params;

	for (int i = 0; credentials.au_params[i] != nullptr; i++) {
		const char *param = credentials.au_params[i];
		const char *equal = strchr(const_cast<char *>(param), '=');
		string key(param, equal-param);
		string value = equal+1;
		params[move(key)] = move(value);
	}

	params["scheme"] = credentials.au_scheme;
	params["method"] = as.method();
	params["from"] = as.fromHeader();
	params["sip-instance"] = as.sipInstance();
	params["domain"] = as.domain();
	return params;
}

void ExternalAuthModule::onHttpResponse(FlexisipAuthStatus &as, nth_client_t *request, const http_t *http) {
	shared_ptr<RequestSipEvent> ev;
	try {
		int sipCode = 0;
		string phrase;
		string reasonHeaderValue;
		string pAssertedIdentity;
		ostringstream os;

		if (http == nullptr) {
			os << "HTTP server responds with code " << nth_client_status(request);
			throw runtime_error(os.str());
		}

		int status = http->http_status->st_status;
		SLOGD << "HTTP response received [" << status << "]: " << endl << http->http_payload;
		if (status != 200) {
			os << "unhandled HTTP status code [" << status << "]";
			throw runtime_error(os.str());
		}

		string httpBody = toString(http->http_payload);
		if (httpBody.empty()) {
			os << "HTTP server answered with an empty body";
			throw runtime_error(os.str());
		}

		try {
			map<string, string> kv = parseHttpBody(httpBody);
			sipCode = stoi(kv["Status"]);
			phrase = move(kv["Phrase"]);
			reasonHeaderValue = move(kv["Reason"]);
			pAssertedIdentity = move(kv["P-Asserted-Identity"]);
		} catch (const logic_error &e) {
			os << "error while parsing HTTP body: " << e.what();
			throw runtime_error(os.str());
		}

		if (!validSipCode(sipCode) || reasonHeaderValue.empty()) {
			os << "invalid SIP code or reason";
			throw runtime_error(os.str());
		}

		auto &httpAuthStatus = dynamic_cast<ExternalAuthModule::Status &>(as);
		httpAuthStatus.status(sipCode == 200 ? 0 : sipCode);
		httpAuthStatus.phrase(su_strdup(as.home(), phrase.c_str()));
		httpAuthStatus.reason(reasonHeaderValue);
		httpAuthStatus.pAssertedIdentity(pAssertedIdentity);
		finish(as);
	} catch (const runtime_error &e) {
		SLOGE << "HTTP request [" << request << "]: " << e.what();
		onError(as);
	} catch (...) {
		if (request) nth_client_destroy(request);
		throw;
	}
	if (request) nth_client_destroy(request);
}

std::map<std::string, std::string> ExternalAuthModule::parseHttpBody(const std::string &body) const {
{'fraction_non_alphanumeric': 0.1393748207628334, 'fraction_numerical': 0.0034413535990823058, 'mean_word_length': 4.831661092530657, 'pattern_counts': {'":': 0, '<': 149, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6076471', 'n_tokens_mistral': 3667, 'n_tokens_neox': 3364, 'n_words': 899}