authdb.cc 9.14 KB
Newer Older
1
/*
2 3
	Flexisip, a flexible SIP proxy server with media capabilities.
	Copyright (C) 2010-2015  Belledonne Communications SARL, All rights reserved.
4

5 6 7 8
	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.
9

10 11 12 13
	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.
14

15 16
	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/>.
17 18 19
*/

#include "authdb.hh"
20
#include "utils/digest.hh"
21

22
using namespace std;
23 24 25 26 27 28 29 30 31 32 33

namespace flexisip {

void AuthDbBackend::ListenerToFunctionWrapper::onResult(AuthDbResult result, const std::string &passwd) {
	delete this;
}

void AuthDbBackend::ListenerToFunctionWrapper::onResult(AuthDbResult result, const std::vector<passwd_algo_t> &passwd) {
	if (mCb) mCb(result, passwd);
	delete this;
}
Yann Diorcet's avatar
Yann Diorcet committed
34

35
unique_ptr<AuthDbBackend> AuthDbBackend::sUnique;
36

37 38 39
AuthDbListener::~AuthDbListener(){
}

40
class FixedAuthDb : public AuthDbBackend {
Mickaël Turnel's avatar
Mickaël Turnel committed
41
public:
42 43
	FixedAuthDb() {
	}
44

45
	void getUserWithPhoneFromBackend(const string & phone, const string &domain, AuthDbListener *listener) override {
46 47
		if (listener) listener->onResult(PASSWORD_FOUND, "user@domain.com");
	}
48 49 50 51 52
	void getPasswordFromBackend(const string &id, const string &domain,
										const string &authid, AuthDbListener *listener) override {
		if (listener) {
			listener->onResult(PASSWORD_FOUND, {{"fixed", "CLRTXT"}});
		}
53
	}
54
	static void declareConfig(GenericStruct *mc){};
55 56
};

57 58
AuthDbBackend &AuthDbBackend::get() {
	if (sUnique == nullptr) {
59 60 61
		GenericStruct *cr = GenericManager::get()->getRoot();
		GenericStruct *ma = cr->get<GenericStruct>("module::Authentication");
		const string &impl = ma->get<ConfigString>("db-implementation")->read();
Guillaume Beraudo's avatar
Guillaume Beraudo committed
62
		if (impl == "fixed") {
63
			sUnique.reset(new FixedAuthDb());
Yann Diorcet's avatar
Yann Diorcet committed
64
		} else if (impl == "file") {
65
			sUnique.reset(new FileAuthDb());
66
#if ENABLE_SOCI
67
		} else if (impl == "soci") {
68
			sUnique.reset(new SociAuthDB());
Guillaume Beraudo's avatar
Guillaume Beraudo committed
69
#endif
70
		}
71 72
	}

73
	return *sUnique;
74 75
}

76
AuthDbBackend::AuthDbBackend() {
77 78 79
	GenericStruct *cr = GenericManager::get()->getRoot();
	GenericStruct *ma = cr->get<GenericStruct>("module::Authentication");
	list<string> domains = ma->get<ConfigStringList>("auth-domains")->read();
80 81 82
	mCacheExpire = ma->get<ConfigInt>("cache-expire")->read();
}

83
AuthDbBackend::~AuthDbBackend() {
84 85
}

86 87 88 89 90 91 92
void AuthDbBackend::declareConfig(GenericStruct *mc) {
	FileAuthDb::declareConfig(mc);
#if ENABLE_SOCI
	SociAuthDB::declareConfig(mc);
#endif
}

93 94
//c++ style wrapper around sofia-sip 'url_unescape'
//Avoids creating a temporary buffer for the unescaped string
95
string AuthDbBackend::urlUnescape(const std::string &str) {
Erwan Croze's avatar
Erwan Croze committed
96 97 98
	vector<char> unescaped(str.size() + 1);
	url_unescape(unescaped.data(), str.c_str());
	return unescaped.data();
99 100
}

101
string AuthDbBackend::createPasswordKey(const string &user, const string &auth_username) {
102
	ostringstream key;
103 104
	string unescapedUser = urlUnescape(user);
	string unescapedUsername = urlUnescape(auth_username);
Simon Morlat's avatar
Simon Morlat committed
105

106
	key << unescapedUser << "#" << unescapedUsername;
107
	return key.str();
108 109
}

110
AuthDbBackend::CacheResult AuthDbBackend::getCachedPassword(const string &key, const string &domain, vector<passwd_algo_t> &pass) {
Mickaël Turnel's avatar
Mickaël Turnel committed
111 112 113 114 115
	time_t now = getCurrentTime();
	auto &passwords = mCachedPasswords[domain];
	unique_lock<mutex> lck(mCachedPasswordMutex);
	auto it = passwords.find(key);
	if (it != passwords.end()) {
116
		pass = it->second.pass;
Mickaël Turnel's avatar
Mickaël Turnel committed
117 118 119 120 121 122 123 124
		if (now < it->second.expire_date) {
			return VALID_PASS_FOUND;
		} else {
			passwords.erase(it);
			return EXPIRED_PASS_FOUND;
		}
	}
	return NO_PASS_FOUND;
125 126
}

127
void AuthDbBackend::clearCache() {
128 129 130
	mCachedPasswords.clear();
}

131
bool AuthDbBackend::cachePassword(const string &key, const string &domain, const vector<passwd_algo_t> &pass, int expires) {
132
	if (pass.empty()) throw invalid_argument("empty password list");
Mickaël Turnel's avatar
Mickaël Turnel committed
133 134 135 136 137 138 139 140 141 142 143 144 145
	time_t now = getCurrentTime();
	map<string, CachedPassword> &passwords = mCachedPasswords[domain];
	unique_lock<mutex> lck(mCachedPasswordMutex);
	map<string, CachedPassword>::iterator it = passwords.find(key);
	if (expires == -1)
		expires = mCacheExpire;
	if (it != passwords.end()) {
		it->second.pass = pass;
		it->second.expire_date = now + expires;
	} else {
		passwords.insert(make_pair(key, CachedPassword(pass, now + expires)));
	}
	return true;
146 147
}

148
bool AuthDbBackend::cacheUserWithPhone(const string &phone, const string &domain, const string &user) {
149
	unique_lock<mutex> lck(mCachedUserWithPhoneMutex);
150

151 152 153 154
	if (!phone.empty()) {
		ostringstream ostr;
		ostr<<phone<< "@"<< domain << ";user=phone";
		mPhone2User[ostr.str()] = user;
155
	}
156 157 158
	ostringstream ostr;
	ostr << user << "@" << domain;
	mPhone2User[ostr.str()] = user;
159 160 161
	return true;
}

162
void AuthDbBackend::getPassword(const std::string &user, const std::string &domain, const std::string &auth_username, AuthDbListener *listener) {
Mickaël Turnel's avatar
Mickaël Turnel committed
163
	// Check for usable cached password
164
	string key = createPasswordKey(user, auth_username);
165
	vector<passwd_algo_t> pass;
166
	switch (getCachedPassword(key, domain, pass)) {
Mickaël Turnel's avatar
Mickaël Turnel committed
167 168 169 170 171 172 173 174 175 176 177 178 179
		case VALID_PASS_FOUND:
			if (listener) listener->onResult(AuthDbResult::PASSWORD_FOUND, pass);
			return;
		case EXPIRED_PASS_FOUND:
			// Might check here if connection is failing
			// If it is the case use fallback password and
			// return AuthDbResult::PASSWORD_FOUND;
			break;
		case NO_PASS_FOUND:
			break;
	}

	// if we reach here, password wasn't cached: we have to grab the password from the actual backend
180
	getPasswordFromBackend(user, domain, auth_username, listener);
181
}
182

183 184 185
void AuthDbBackend::getPassword(const std::string &user, const std::string &domain, const std::string &auth_username, const ResultCb &cb) {
	auto *listener = new ListenerToFunctionWrapper(cb);
	getPassword(user, domain, auth_username, listener);
186
}
187 188 189

void AuthDbBackend::createCachedAccount(const string &user, const string &host, const string &auth_username, const vector<passwd_algo_t> &password,
										int expires, const string & phone_alias) {
Mickaël Turnel's avatar
Mickaël Turnel committed
190 191 192 193 194
	if (!user.empty() && !host.empty()) {
		string key = createPasswordKey(user, auth_username);
		cachePassword(key, host, password, expires);
		cacheUserWithPhone(phone_alias, host, user);
	}
195 196
}

197 198
void AuthDbBackend::createAccount(const string & user, const string & host, const string &auth_username, const string &password,
										int expires, const string & phone_alias) {
Mickaël Turnel's avatar
Mickaël Turnel committed
199
	// Password here is in mod clrtxt. Calcul passmd5 and passsha256 before createCachedAccount.
200 201 202 203 204 205 206 207 208 209
	vector<passwd_algo_t> pass;
	passwd_algo_t clrtxt, md5, sha256;

	clrtxt.pass = password;
	clrtxt.algo = "CLRTXT";
	pass.push_back(clrtxt);

	string input;
	input = user+":"+host+":"+clrtxt.pass;

210
	md5.pass = Md5().compute<string>(input);
211 212 213
	md5.algo = "MD5";
	pass.push_back(md5);

214
	sha256.pass = Sha256().compute<string>(input);
215 216 217
	sha256.algo = "SHA-256";
	pass.push_back(sha256);

218
	createCachedAccount(user, host, auth_username, pass, expires, phone_alias);
219
}
220 221 222 223

AuthDbBackend::CacheResult AuthDbBackend::getCachedUserWithPhone(const string &phone, const string &domain, string &user) {
	unique_lock<mutex> lck(mCachedUserWithPhoneMutex);
	auto it = mPhone2User.find(phone + "@" + domain);
224 225 226
	if (it == mPhone2User.end()) {
		it = mPhone2User.find(phone + "@" + domain + ";user=phone");
	}
227 228 229 230 231 232 233
	if (it != mPhone2User.end()) {
		user.assign(it->second);
		return VALID_PASS_FOUND;
	}
	return NO_PASS_FOUND;
}

234
void AuthDbBackend::getUserWithPhone(const string & phone, const string & domain, AuthDbListener *listener) {
235 236
	// Check for usable cached password
	string user;
237
	switch (getCachedUserWithPhone(phone, domain, user)) {
238 239 240 241 242 243 244 245 246 247 248
		case VALID_PASS_FOUND:
			if (listener) listener->onResult(AuthDbResult::PASSWORD_FOUND, user);
			return;
		case EXPIRED_PASS_FOUND:
		case NO_PASS_FOUND:
			break;
	}

	// if we reach here, password wasn't cached: we have to grab the password from the actual backend
	getUserWithPhoneFromBackend(phone, domain, listener);
}
249

250
void AuthDbBackend::getUsersWithPhone(list<tuple<string,string,AuthDbListener*>> & creds) {
251 252
	list<tuple<string,string,AuthDbListener*>> needed_creds;
	for (tuple<string,string,AuthDbListener*> cred : creds) {
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
		// Check for usable cached password
		string user;
		string phone = std::get<0>(cred);
		string domain = std::get<1>(cred);
		AuthDbListener* cred_listener = std::get<2>(cred);
		switch (getCachedUserWithPhone(phone, domain, user)) {
			case VALID_PASS_FOUND:
				if (cred_listener) cred_listener->onResult(AuthDbResult::PASSWORD_FOUND, user);
				break;
			case EXPIRED_PASS_FOUND:
			case NO_PASS_FOUND:
				needed_creds.push_back(cred);
				break;
		}
	}
268 269 270 271
	if (!needed_creds.empty()){
		// if we reach here, password wasn't cached: we have to grab the password from the actual backend
		getUsersWithPhonesFromBackend(needed_creds);
	}
272 273
}

274
void AuthDbBackend::getUsersWithPhonesFromBackend(list<tuple<string,string,AuthDbListener*>> &creds) {
275
	for(tuple<string,string,AuthDbListener*> cred : creds) {
276 277 278 279 280 281
		string phone = std::get<0>(cred);
		string domain = std::get<1>(cred);
		AuthDbListener* l = std::get<2>(cred);
		getUserWithPhoneFromBackend(phone,domain, l);
	}
}
282 283

} // namespace flexisip