mirror of https://github.com/sipwise/sems.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
549 lines
14 KiB
549 lines
14 KiB
/*
|
|
* Copyright (C) 2002-2003 Fhg Fokus
|
|
* Copyright (C) 2006 iptego GmbH
|
|
*
|
|
* This file is part of SEMS, a free SIP media server.
|
|
*
|
|
* SEMS 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 released under
|
|
* the GPL with the additional exemption that compiling, linking,
|
|
* and/or using OpenSSL is allowed.
|
|
*
|
|
* For a license to use the SEMS software under conditions
|
|
* other than those described here, or to purchase support for this
|
|
* software, please contact iptel.org by e-mail at the following addresses:
|
|
* info@iptel.org
|
|
*
|
|
* SEMS 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "UACAuth.h"
|
|
#include "AmSipMsg.h"
|
|
#include "AmUtils.h"
|
|
#include "AmSipHeaders.h"
|
|
|
|
#include <map>
|
|
|
|
#include <cctype>
|
|
#include <algorithm>
|
|
|
|
#include "md5.h"
|
|
|
|
using std::string;
|
|
|
|
|
|
#define MOD_NAME "uac_auth"
|
|
|
|
EXPORT_SESSION_EVENT_HANDLER_FACTORY(UACAuthFactory, MOD_NAME);
|
|
EXPORT_PLUGIN_CLASS_FACTORY(UACAuthFactory, MOD_NAME);
|
|
|
|
UACAuthFactory* UACAuthFactory::_instance=0;
|
|
|
|
UACAuthFactory* UACAuthFactory::instance()
|
|
{
|
|
if(!_instance)
|
|
_instance = new UACAuthFactory(MOD_NAME);
|
|
return _instance;
|
|
}
|
|
|
|
void UACAuthFactory::invoke(const string& method, const AmArg& args, AmArg& ret)
|
|
{
|
|
if(method == "getHandler"){
|
|
CredentialHolder* c = dynamic_cast<CredentialHolder*>(args.get(0).asObject());
|
|
DialogControl* cc = dynamic_cast<DialogControl*>(args.get(1).asObject());
|
|
|
|
if ((c!=NULL)&&(cc!=NULL)) {
|
|
AmArg handler;
|
|
handler.setBorrowedPointer(getHandler(cc->getDlg(), c));
|
|
ret.push(handler);
|
|
} else {
|
|
ERROR("wrong types in call to getHandler. (c=%ld, cc= %ld)\n",
|
|
(unsigned long)c, (unsigned long)cc);
|
|
}
|
|
}
|
|
else
|
|
throw AmDynInvoke::NotImplemented(method);
|
|
}
|
|
|
|
|
|
int UACAuthFactory::onLoad()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bool UACAuthFactory::onInvite(const AmSipRequest& req, AmConfigReader& conf)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
AmSessionEventHandler* UACAuthFactory::getHandler(AmSession* s)
|
|
{
|
|
CredentialHolder* c = dynamic_cast<CredentialHolder*>(s);
|
|
if (c != NULL) {
|
|
return getHandler(&s->dlg, c);
|
|
} else {
|
|
DBG("no credentials for new session. not enabling auth session handler.\n");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
AmSessionEventHandler* UACAuthFactory::getHandler(AmSipDialog* dlg, CredentialHolder* c) {
|
|
return new UACAuth(dlg, c->getCredentials());
|
|
}
|
|
|
|
UACAuth::UACAuth(AmSipDialog* dlg,
|
|
UACAuthCred* cred)
|
|
: dlg(dlg),
|
|
credential(cred),
|
|
AmSessionEventHandler(),
|
|
nonce_count(0),
|
|
nonce_reuse(false)
|
|
{
|
|
}
|
|
|
|
bool UACAuth::process(AmEvent* ev)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool UACAuth::onSipEvent(AmSipEvent* ev)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool UACAuth::onSipRequest(const AmSipRequest& req)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool UACAuth::onSipReply(const AmSipReply& reply, int old_dlg_status, const string& trans_method)
|
|
{
|
|
bool processed = false;
|
|
if (reply.code==407 || reply.code==401) {
|
|
DBG("SIP reply with code %d cseq %d .\n", reply.code, reply.cseq);
|
|
|
|
std::map<unsigned int, SIPRequestInfo >::iterator ri =
|
|
sent_requests.find(reply.cseq);
|
|
if (ri!= sent_requests.end())
|
|
{
|
|
DBG(" UACAuth - processing with reply code %d \n", reply.code);
|
|
// DBG("realm %s user %s pwd %s ----------------\n",
|
|
// credential->realm.c_str(),
|
|
// credential->user.c_str(),
|
|
// credential->pwd.c_str());
|
|
if (!nonce_reuse &&
|
|
(((reply.code == 401) &&
|
|
getHeader(ri->second.hdrs, SIP_HDR_AUTHORIZATION, true).length()) ||
|
|
((reply.code == 407) &&
|
|
getHeader(ri->second.hdrs, SIP_HDR_PROXY_AUTHORIZATION, true).length()))) {
|
|
DBG("Authorization failed!\n");
|
|
} else {
|
|
nonce_reuse = false;
|
|
|
|
string auth_hdr = (reply.code==407) ?
|
|
getHeader(reply.hdrs, SIP_HDR_PROXY_AUTHENTICATE, true) :
|
|
getHeader(reply.hdrs, SIP_HDR_WWW_AUTHENTICATE, true);
|
|
string result;
|
|
|
|
string auth_uri;
|
|
auth_uri = dlg->remote_uri;
|
|
|
|
if (do_auth(reply.code, auth_hdr,
|
|
ri->second.method,
|
|
auth_uri, ri->second.body, result)) {
|
|
string hdrs = ri->second.hdrs;
|
|
|
|
// strip other auth headers
|
|
if (reply.code == 401) {
|
|
removeHeader(hdrs, SIP_HDR_AUTHORIZATION);
|
|
} else {
|
|
removeHeader(hdrs, SIP_HDR_PROXY_AUTHORIZATION);
|
|
}
|
|
|
|
if (hdrs == "\r\n" || hdrs == "\r" || hdrs == "\n")
|
|
hdrs = result;
|
|
else
|
|
hdrs += result;
|
|
|
|
if (dlg->getStatus() < AmSipDialog::Connected &&
|
|
ri->second.method != SIP_METH_BYE) {
|
|
// reset remote tag so remote party
|
|
// thinks its new dlg
|
|
dlg->remote_tag = "";
|
|
|
|
if (AmConfig::ProxyStickyAuth) {
|
|
// update remote URI to resolved IP
|
|
size_t hpos = dlg->remote_uri.find("@");
|
|
if (hpos != string::npos && reply.remote_ip.length()) {
|
|
dlg->remote_uri = dlg->remote_uri.substr(0, hpos+1) +
|
|
reply.remote_ip + ":"+int2str(reply.remote_port);
|
|
DBG("updated remote URI to '%s'\n", dlg->remote_uri.c_str());
|
|
}
|
|
}
|
|
|
|
}
|
|
// resend request
|
|
if (dlg->sendRequest(ri->second.method,
|
|
ri->second.content_type,
|
|
ri->second.body,
|
|
hdrs, SIP_FLAGS_VERBATIM | SIP_FLAGS_NOAUTH) == 0) {
|
|
processed = true;
|
|
DBG("authenticated request successfully sent.\n");
|
|
// undo SIP dialog status change
|
|
if (dlg->getStatus() != old_dlg_status)
|
|
dlg->setStatus(old_dlg_status);
|
|
} else {
|
|
ERROR("failed to send authenticated request.\n");
|
|
}
|
|
}
|
|
}
|
|
sent_requests.erase(ri);
|
|
}
|
|
} else if (reply.code >= 200) {
|
|
sent_requests.erase(reply.cseq); // now we dont need it any more
|
|
}
|
|
|
|
return processed;
|
|
}
|
|
|
|
bool UACAuth::onSendRequest(const string& method,
|
|
const string& content_type,
|
|
const string& body,
|
|
string& hdrs,
|
|
int flags,
|
|
unsigned int cseq)
|
|
{
|
|
// add authentication header if nonce is already there
|
|
string result;
|
|
if (!(flags & SIP_FLAGS_NOAUTH) &&
|
|
!challenge.nonce.empty() &&
|
|
do_auth(challenge, challenge_code,
|
|
method,
|
|
dlg->remote_uri, body, result)) {
|
|
// add headers
|
|
if (hdrs == "\r\n" || hdrs == "\r" || hdrs == "\n")
|
|
hdrs = result;
|
|
else
|
|
hdrs += result;
|
|
|
|
nonce_reuse = true;
|
|
} else {
|
|
nonce_reuse = false;
|
|
}
|
|
|
|
DBG("adding %d to list of sent requests.\n", cseq);
|
|
sent_requests[cseq] = SIPRequestInfo(method,
|
|
content_type,
|
|
body,
|
|
hdrs);
|
|
return false;
|
|
}
|
|
|
|
|
|
bool UACAuth::onSendReply(const AmSipRequest& req,
|
|
unsigned int code,const string& reason,
|
|
const string& content_type,const string& body,
|
|
string& hdrs,
|
|
int flags)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void w_MD5Update(MD5_CTX *ctx, const string& s) {
|
|
unsigned char a[255];
|
|
if (s.length()>255) {
|
|
ERROR("string too long\n");
|
|
return;
|
|
}
|
|
memcpy(a, s.c_str(), s.length());
|
|
MD5Update(ctx, a, s.length());
|
|
}
|
|
|
|
|
|
string UACAuth::find_attribute(const string& name, const string& header) {
|
|
string res;
|
|
size_t pos1 = header.find(name);
|
|
if (pos1!=string::npos) {
|
|
pos1+=name.length();
|
|
pos1 = header.find_first_not_of(" =\"", pos1);
|
|
if (pos1 != string::npos) {
|
|
size_t pos2 = header.find_first_of(",\"", pos1);
|
|
if (pos2 != string::npos) {
|
|
res = header.substr(pos1, pos2-pos1);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool UACAuth::parse_header(const string& auth_hdr, UACAuthDigestChallenge& challenge) {
|
|
size_t p = auth_hdr.find_first_not_of(' ');
|
|
string method = auth_hdr.substr(p, 6);
|
|
std::transform(method.begin(), method.end(), method.begin(),
|
|
(int(*)(int)) toupper);
|
|
if (method != "DIGEST") {
|
|
ERROR("only Digest auth supported\n");
|
|
return false;
|
|
}
|
|
|
|
// inefficient parsing...TODO: optimize this
|
|
challenge.realm = find_attribute("realm", auth_hdr);
|
|
challenge.nonce = find_attribute("nonce", auth_hdr);
|
|
challenge.opaque = find_attribute("opaque", auth_hdr);
|
|
challenge.algorithm = find_attribute("algorithm", auth_hdr);
|
|
challenge.qop = find_attribute("qop", auth_hdr);
|
|
|
|
return (challenge.realm.length() && challenge.nonce.length());
|
|
}
|
|
|
|
bool UACAuth::do_auth(const unsigned int code, const string& auth_hdr,
|
|
const string& method, const string& uri,
|
|
const string& body, string& result)
|
|
{
|
|
if (!auth_hdr.length()) {
|
|
ERROR("empty auth header.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!parse_header(auth_hdr, challenge)) {
|
|
ERROR("error parsing auth header '%s'\n", auth_hdr.c_str());
|
|
return false;
|
|
}
|
|
|
|
challenge_code = code;
|
|
|
|
return do_auth(challenge, code, method, uri, body, result);
|
|
}
|
|
|
|
|
|
bool UACAuth::do_auth(const UACAuthDigestChallenge& challenge,
|
|
const unsigned int code,
|
|
const string& method, const string& uri,
|
|
const string& body, string& result)
|
|
{
|
|
if ((challenge.algorithm.length()) && (challenge.algorithm != "MD5")) {
|
|
DBG("unsupported algorithm: '%s'\n", challenge.algorithm.c_str());
|
|
return false;
|
|
}
|
|
|
|
DBG("realm='%s', nonce='%s', qop='%s'\n",
|
|
challenge.realm.c_str(),
|
|
challenge.nonce.c_str(),
|
|
challenge.qop.c_str());
|
|
|
|
if (credential->realm.length()
|
|
&& (credential->realm != challenge.realm)) {
|
|
DBG("authentication realm mismatch ('%s' vs '%s').\n",
|
|
credential->realm.c_str(),challenge.realm.c_str());
|
|
}
|
|
|
|
HASHHEX ha1;
|
|
HASHHEX ha2;
|
|
HASHHEX hentity;
|
|
HASHHEX response;
|
|
bool qop_auth=false;
|
|
bool qop_auth_int=false;
|
|
string cnonce;
|
|
string qop_value;
|
|
|
|
if(!challenge.qop.empty()){
|
|
|
|
qop_auth = key_in_list(challenge.qop,"auth");
|
|
qop_auth_int = key_in_list(challenge.qop,"auth-int");
|
|
|
|
if(qop_auth || qop_auth_int) {
|
|
|
|
cnonce = int2hex(get_random(),true);
|
|
|
|
if(challenge.nonce == nonce)
|
|
nonce_count++;
|
|
else
|
|
nonce_count = 1;
|
|
|
|
if(qop_auth_int){
|
|
uac_calc_hentity(body,hentity);
|
|
qop_value = "auth-int";
|
|
}
|
|
else
|
|
qop_value = "auth";
|
|
}
|
|
}
|
|
|
|
/* do authentication */
|
|
uac_calc_HA1( challenge, cnonce, ha1);
|
|
uac_calc_HA2( method, uri, challenge, qop_auth_int ? hentity : NULL, ha2);
|
|
uac_calc_response( ha1, ha2, challenge, cnonce, qop_value, response);
|
|
DBG("calculated response = %s\n", response);
|
|
|
|
// compile auth response
|
|
result = ((code==401) ? SIP_HDR_COLSP(SIP_HDR_AUTHORIZATION) :
|
|
SIP_HDR_COLSP(SIP_HDR_PROXY_AUTHORIZATION));
|
|
|
|
result += "Digest username=\"" + credential->user + "\", "
|
|
"realm=\"" + challenge.realm + "\", "
|
|
"nonce=\"" + challenge.nonce + "\", "
|
|
"uri=\"" + uri + "\", ";
|
|
|
|
if (challenge.opaque.length())
|
|
result += "opaque=\"" + challenge.opaque + "\", ";
|
|
|
|
if (!qop_value.empty())
|
|
result += "qop=" + qop_value + ", "
|
|
"cnonce=\"" + cnonce + "\", "
|
|
"nc=" + int2hex(nonce_count,true) + ", ";
|
|
|
|
result += "response=\"" + string((char*)response) + "\", algorithm=MD5\n";
|
|
|
|
DBG("Auth req hdr: '%s'\n", result.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
// These functions come basically from ser's uac module
|
|
static inline void cvt_hex(HASH bin, HASHHEX hex)
|
|
{
|
|
unsigned short i;
|
|
unsigned char j;
|
|
|
|
for (i = 0; i<HASHLEN; i++)
|
|
{
|
|
j = (bin[i] >> 4) & 0xf;
|
|
if (j <= 9)
|
|
{
|
|
hex[i * 2] = (j + '0');
|
|
} else {
|
|
hex[i * 2] = (j + 'a' - 10);
|
|
}
|
|
|
|
j = bin[i] & 0xf;
|
|
|
|
if (j <= 9)
|
|
{
|
|
hex[i * 2 + 1] = (j + '0');
|
|
} else {
|
|
hex[i * 2 + 1] = (j + 'a' - 10);
|
|
}
|
|
};
|
|
|
|
hex[HASHHEXLEN] = '\0';
|
|
}
|
|
|
|
|
|
/*
|
|
* calculate H(A1)
|
|
*/
|
|
void UACAuth::uac_calc_HA1(const UACAuthDigestChallenge& challenge,
|
|
string cnonce,
|
|
HASHHEX sess_key)
|
|
{
|
|
MD5_CTX Md5Ctx;
|
|
HASH HA1;
|
|
|
|
MD5Init(&Md5Ctx);
|
|
w_MD5Update(&Md5Ctx, credential->user);
|
|
w_MD5Update(&Md5Ctx, ":");
|
|
// use realm from challenge
|
|
w_MD5Update(&Md5Ctx, challenge.realm);
|
|
w_MD5Update(&Md5Ctx, ":");
|
|
w_MD5Update(&Md5Ctx, credential->pwd);
|
|
MD5Final(HA1, &Md5Ctx);
|
|
|
|
// MD5sess ...not supported
|
|
// if ( flags & AUTHENTICATE_MD5SESS )
|
|
// {
|
|
// MD5Init(&Md5Ctx);
|
|
// MD5Update(&Md5Ctx, HA1, HASHLEN);
|
|
// MD5Update(&Md5Ctx, ":", 1);
|
|
// MD5Update(&Md5Ctx, challenge.nonce.c_str(), challenge.nonce.length());
|
|
// MD5Update(&Md5Ctx, ":", 1);
|
|
// MD5Update(&Md5Ctx, cnonce.c_str(), cnonce.length());
|
|
// MD5Final(HA1, &Md5Ctx);
|
|
// };
|
|
cvt_hex(HA1, sess_key);
|
|
}
|
|
|
|
|
|
/*
|
|
* calculate H(A2)
|
|
*/
|
|
void UACAuth::uac_calc_HA2( const string& method, const string& uri,
|
|
const UACAuthDigestChallenge& challenge,
|
|
HASHHEX hentity,
|
|
HASHHEX HA2Hex )
|
|
{
|
|
unsigned char hc[1]; hc[0]=':';
|
|
MD5_CTX Md5Ctx;
|
|
HASH HA2;
|
|
|
|
MD5Init(&Md5Ctx);
|
|
w_MD5Update(&Md5Ctx, method);
|
|
MD5Update(&Md5Ctx, hc, 1);
|
|
w_MD5Update(&Md5Ctx, uri);
|
|
|
|
if ( hentity != 0 )
|
|
{
|
|
MD5Update(&Md5Ctx, hc, 1);
|
|
MD5Update(&Md5Ctx, hentity, HASHHEXLEN);
|
|
};
|
|
|
|
MD5Final(HA2, &Md5Ctx);
|
|
cvt_hex(HA2, HA2Hex);
|
|
}
|
|
|
|
/*
|
|
* calculate H(body)
|
|
*/
|
|
|
|
void UACAuth::uac_calc_hentity( const string& body, HASHHEX hentity )
|
|
{
|
|
MD5_CTX Md5Ctx;
|
|
HASH h;
|
|
|
|
MD5Init(&Md5Ctx);
|
|
w_MD5Update(&Md5Ctx, body);
|
|
MD5Final(h, &Md5Ctx);
|
|
cvt_hex(h,hentity);
|
|
}
|
|
|
|
/*
|
|
* calculate request-digest/response-digest as per HTTP Digest spec
|
|
*/
|
|
void UACAuth::uac_calc_response(HASHHEX ha1, HASHHEX ha2,
|
|
const UACAuthDigestChallenge& challenge, const string& cnonce,
|
|
const string& qop_value, HASHHEX response)
|
|
{
|
|
unsigned char hc[1]; hc[0]=':';
|
|
MD5_CTX Md5Ctx;
|
|
HASH RespHash;
|
|
|
|
MD5Init(&Md5Ctx);
|
|
MD5Update(&Md5Ctx, ha1, HASHHEXLEN);
|
|
MD5Update(&Md5Ctx, hc, 1);
|
|
w_MD5Update(&Md5Ctx, challenge.nonce);
|
|
MD5Update(&Md5Ctx, hc, 1);
|
|
|
|
if (!qop_value.empty()) {
|
|
|
|
w_MD5Update(&Md5Ctx, int2hex(nonce_count,true));
|
|
MD5Update(&Md5Ctx, hc, 1);
|
|
w_MD5Update(&Md5Ctx, cnonce);
|
|
MD5Update(&Md5Ctx, hc, 1);
|
|
w_MD5Update(&Md5Ctx, qop_value);
|
|
MD5Update(&Md5Ctx, hc, 1);
|
|
};
|
|
|
|
MD5Update(&Md5Ctx, ha2, HASHHEXLEN);
|
|
MD5Final(RespHash, &Md5Ctx);
|
|
cvt_hex(RespHash, response);
|
|
}
|