Authentication and RADIUS/PortaOne related changes.

git-svn-id: http://voip.null.ro/svn/yate@740 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2006-04-04 19:06:02 +00:00
parent 27af822dd2
commit 4f78175a12
5 changed files with 120 additions and 40 deletions

View File

@ -20,10 +20,10 @@
;short_number=false
; auth_priority: integer: Priority of the user.auth handler
;auth_priority=50
;auth_priority=70
; acct_priority: integer: Priority of the call.cdr handler
;acct_priority=50
;acct_priority=70
[portabill]

View File

@ -503,6 +503,11 @@ bool SIPEngine::checkUser(const String& username, const String& realm, const Str
return false;
}
bool SIPEngine::checkAuth(bool noUser, const SIPMessage* message, GenObject* userData)
{
return message && noUser && checkUser("","","",message->method,message->uri,"",message,userData);
}
// response = md5(md5(username:realm:password):nonce:md5(method:uri))
void SIPEngine::buildAuth(const String& username, const String& realm, const String& passwd,
const String& nonce, const String& method, const String& uri, String& response)
@ -532,6 +537,7 @@ int SIPEngine::authUser(const SIPMessage* message, String& user, bool proxy, Gen
{
if (!message)
return -1;
bool noUser = true;
const char* hdr = proxy ? "Proxy-Authorization" : "Authorization";
const ObjList* l = &message->header;
for (; l; l = l->next()) {
@ -555,6 +561,7 @@ int SIPEngine::authUser(const SIPMessage* message, String& user, bool proxy, Gen
long age = nonceAge(nonce);
if (age < 0)
continue;
noUser = false;
XDebug(this,DebugAll,"authUser nonce age is %ld",age);
String res(t->getParam("response"));
delQuotes(res);
@ -575,6 +582,9 @@ int SIPEngine::authUser(const SIPMessage* message, String& user, bool proxy, Gen
return age;
}
}
// we got no auth headers - try to authenticate by other means
if (checkAuth(noUser,message,userData))
return 0;
return -1;
}

View File

@ -1022,6 +1022,16 @@ public:
const String& method, const String& uri, const String& response,
const SIPMessage* message, GenObject* userData);
/**
* Authenticate a message by other means than user credentials. By default
* it calls @ref checkUser with empty user credential fields
* @param noUser No plausible user credentials were detected so far
* @param message Message that is to be authenticated
* @param userData Pointer to an optional object passed from @ref authUser
* @return True if message is authenticated, false if verification failed
*/
virtual bool checkAuth(bool noUser, const SIPMessage* message, GenObject* userData);
/**
* Detect the proper credentials for any user in the engine
* @param message Pointer to the message to check

View File

@ -348,7 +348,7 @@ public:
bool addAttribute(const char* attrib, int val);
bool addAttribute(const char* attrib, unsigned char subType, const char* val, bool emptyOk = false);
void addAttributes(NamedList& params, NamedList* list);
bool prepareAttributes(NamedList& params, bool forAcct = false);
bool prepareAttributes(NamedList& params, bool forAcct = true, String* user = 0);
bool returnAttributes(NamedList& params, const ObjList* attributes);
static bool fillRandom(DataBlock& data, int len);
@ -441,6 +441,18 @@ static void portaBillingRoute(NamedList& params, const ObjList* attributes)
route << rsep << tmp;
}
}
else if (tmp.startSkip("CLI:",false)) {
if (tmp) {
Debug(&__plugin,DebugCall,"PortaBilling setting caller '%s'",tmp.c_str());
params.setParam("caller",tmp);
}
}
else if (tmp.startSkip("CompleteNumber:",false)) {
if (tmp) {
Debug(&__plugin,DebugCall,"PortaBilling setting called '%s'",tmp.c_str());
params.setParam("called",tmp);
}
}
}
if (route) {
Debug(&__plugin,DebugCall,"PortaBilling returned route '%s'",route.c_str());
@ -1171,7 +1183,7 @@ void RadiusClient::addAttributes(NamedList& params, NamedList* list)
}
// Find matching NAS section and populate attributes accordingly
bool RadiusClient::prepareAttributes(NamedList& params, bool forAcct)
bool RadiusClient::prepareAttributes(NamedList& params, bool forAcct, String* user)
{
const char* caller = params.getValue("caller");
const char* called = 0;
@ -1187,7 +1199,17 @@ bool RadiusClient::prepareAttributes(NamedList& params, bool forAcct)
if (!called)
called = params.getValue("called");
}
const char* username = params.getValue("username",caller);
const char* username = params.getValue("username");
if (!username)
username = params.getValue("authname");
if (!username) {
if (forAcct)
username = caller;
// we were unable to build an username
// don't even send such a request to PortaOne
if (s_pb_enabled && !username)
return false;
}
Lock lock(s_cfgMutex);
NamedList* nasSect = 0;
String nasName;
@ -1262,6 +1284,8 @@ bool RadiusClient::prepareAttributes(NamedList& params, bool forAcct)
addAttribute("Called-Station-Id",called);
addAttributes(params,nasSect);
addAttributes(params,servSect);
if (user)
*user = username;
return true;
}
@ -1297,37 +1321,54 @@ bool AuthHandler::received(Message& msg)
if (proto.null())
return false;
RadiusClient radclient;
if (!radclient.prepareAttributes(msg,false))
// preserve the actually authenticated username in case we succeed
String user;
if (!radclient.prepareAttributes(msg,false,&user))
return false;
// TODO: process plaintext password
if ((proto == "digest") || (proto == "sip")) {
// mandatory auth parameters
if (!radclient.addAttribute("Digest-Response",msg.getValue("response")))
return false;
if (!(
radclient.addAttribute("Digest-Attributes",Digest_Nonce,msg.getValue("nonce")) &&
radclient.addAttribute("Digest-Attributes",Digest_Method,msg.getValue("method")) &&
radclient.addAttribute("Digest-Attributes",Digest_URI,msg.getValue("uri")) &&
radclient.addAttribute("Digest-Attributes",Digest_UserName,msg.getValue("username"))
))
return false;
// optional auth parameters
radclient.addAttribute("Digest-Attributes",Digest_Realm,msg.getValue("realm"));
radclient.addAttribute("Digest-Attributes",Digest_Algo,msg.getValue("algorithm","MD5"));
radclient.addAttribute("Digest-Attributes",Digest_QOP,msg.getValue("qop"));
ObjList result;
if (radclient.doAuthenticate(&result) != AuthSuccess)
return false;
radclient.returnAttributes(msg,&result);
if (s_pb_enabled)
portaBillingRoute(msg,&result);
// signal we don't return a password
msg.retValue().clear();
return true;
const char* resp = msg.getValue("response");
const char* nonce = msg.getValue("nonce");
const char* method = msg.getValue("method");
const char* uri = msg.getValue("uri");
const char* user = msg.getValue("username");
if (resp && nonce && method && uri && user) {
// mandatory auth parameters
if (!(
radclient.addAttribute("Digest-Response",resp) &&
radclient.addAttribute("Digest-Attributes",Digest_Nonce,nonce) &&
radclient.addAttribute("Digest-Attributes",Digest_Method,method) &&
radclient.addAttribute("Digest-Attributes",Digest_URI,uri) &&
radclient.addAttribute("Digest-Attributes",Digest_UserName,user)
))
return false;
// optional auth parameters
radclient.addAttribute("Digest-Attributes",Digest_Realm,msg.getValue("realm"));
radclient.addAttribute("Digest-Attributes",Digest_Algo,msg.getValue("algorithm","MD5"));
radclient.addAttribute("Digest-Attributes",Digest_QOP,msg.getValue("qop"));
}
}
else {
Debug(&__plugin,DebugMild,"Protocol '%s' not supported!",proto.c_str());
String address = msg.getValue("address");
// suppress any port number - IMHO this is stupid
int sep = address.find(':');
if (sep >= 0)
address = address.substr(0,sep);
radclient.addAttribute("h323-remote-address",address);
ObjList result;
if (radclient.doAuthenticate(&result) != AuthSuccess)
return false;
}
// copy back the username we actually authenticated
if (user)
msg.setParam("username",user);
// and pick whatever other parameters we want to return
radclient.returnAttributes(msg,&result);
if (s_pb_enabled)
portaBillingRoute(msg,&result);
// signal we don't return a password
msg.retValue().clear();
return true;
}
@ -1387,7 +1428,7 @@ bool AcctHandler::received(Message& msg)
return false;
RadiusClient radclient;
if (!radclient.prepareAttributes(msg,true))
if (!radclient.prepareAttributes(msg))
return false;
// create a Cisco-compatible conference ID
@ -1510,8 +1551,8 @@ void RadiusModule::initialize()
m_init = true;
setup();
Engine::install(new AuthHandler(s_cfg.getIntValue("general","auth_priority",50)));
Engine::install(new AcctHandler(s_cfg.getIntValue("general","acct_priority",50)));
Engine::install(new AuthHandler(s_cfg.getIntValue("general","auth_priority",70)));
Engine::install(new AcctHandler(s_cfg.getIntValue("general","acct_priority",70)));
}
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -889,14 +889,18 @@ bool YateSIPEngine::checkUser(const String& username, const String& realm, const
const String& method, const String& uri, const String& response,
const SIPMessage* message, GenObject* userData)
{
NamedList* params = YOBJECT(NamedList,userData);
Message m("user.auth");
m.addParam("protocol","sip");
m.addParam("username",username);
m.addParam("realm",realm);
m.addParam("nonce",nonce);
if (username) {
m.addParam("username",username);
m.addParam("realm",realm);
m.addParam("nonce",nonce);
m.addParam("response",response);
}
m.addParam("method",method);
m.addParam("uri",uri);
m.addParam("response",response);
if (message) {
m.addParam("ip_host",message->getParty()->getPartyAddr());
m.addParam("ip_port",String(message->getParty()->getPartyPort()));
@ -907,7 +911,6 @@ bool YateSIPEngine::checkUser(const String& username, const String& realm, const
}
}
NamedList* params = YOBJECT(NamedList,userData);
if (params) {
const char* str = params->getValue("caller");
if (str)
@ -923,6 +926,22 @@ bool YateSIPEngine::checkUser(const String& username, const String& realm, const
// empty password returned means authentication succeeded
if (m.retValue().null())
return copyAuthParams(params,m);
// check for refusals
if (m.retValue() == "-") {
if (params) {
const char* err = m.getValue("error");
if (err)
params->setParam("error",err);
err = m.getValue("reason");
if (err)
params->setParam("reason",err);
}
return false;
}
// password works only with username
if (!username)
return false;
String res;
buildAuth(username,realm,m.retValue(),nonce,method,uri,res);
if (res == response)