From 812439e0ae085a37238dc97f2921c732a962d222 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Mon, 29 Oct 2012 20:03:21 +0100 Subject: [PATCH 01/10] b/f: sst: use proper delimiter for supported list support for SST in the remote would not be understood properly in some cases thanks to Jon Bonilla for reporting --- core/plug-in/session_timer/SessionTimer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/plug-in/session_timer/SessionTimer.cpp b/core/plug-in/session_timer/SessionTimer.cpp index a91668cc..eafd9fa1 100644 --- a/core/plug-in/session_timer/SessionTimer.cpp +++ b/core/plug-in/session_timer/SessionTimer.cpp @@ -271,7 +271,7 @@ void SessionTimer::updateTimer(AmSession* s, const AmSipRequest& req) { remote_timer_aware = key_in_list(getHeader(req.hdrs, SIP_HDR_SUPPORTED, SIP_HDR_SUPPORTED_COMPACT), - TIMER_OPTION_TAG, true); + TIMER_OPTION_TAG); // determine session interval string sess_expires_hdr = getHeader(req.hdrs, SIP_HDR_SESSION_EXPIRES, From e6d156f741aec62143341a10a9d90cb3fce290ba Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Thu, 9 Aug 2012 18:36:04 +0200 Subject: [PATCH 02/10] b/f: when resending request from SST to 422, send VERBATIM --- core/plug-in/session_timer/SessionTimer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/plug-in/session_timer/SessionTimer.cpp b/core/plug-in/session_timer/SessionTimer.cpp index eafd9fa1..054ccc32 100644 --- a/core/plug-in/session_timer/SessionTimer.cpp +++ b/core/plug-in/session_timer/SessionTimer.cpp @@ -103,7 +103,7 @@ bool SessionTimer::onSipReply(const AmSipReply& reply, int old_dlg_status, unsigned int new_cseq = s->dlg.cseq; // resend request with interval i_minse if (s->dlg.sendRequest(orig_req.method,orig_req.content_type, - orig_req.body, orig_req.hdrs) == 0) { + orig_req.body, orig_req.hdrs, SIP_FLAGS_VERBATIM) == 0) { DBG("request with new Session Interval %u successfully sent.\n", i_minse); // undo SIP dialog status change if (s->dlg.getStatus() != old_dlg_status) From 0aaa62c81279f5c8c0489c54ff5a2b2bb7da11cf Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Thu, 29 Nov 2012 21:15:20 +0100 Subject: [PATCH 03/10] xmlrpc2di: configurable log level for XMLRPC calls / parameters --- apps/xmlrpc2di/XMLRPC2DI.cpp | 20 ++++++++++++-------- apps/xmlrpc2di/XMLRPC2DI.h | 2 ++ apps/xmlrpc2di/etc/xmlrpc2di.conf | 5 ++++- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/xmlrpc2di/XMLRPC2DI.cpp b/apps/xmlrpc2di/XMLRPC2DI.cpp index 16722559..6cf8478b 100644 --- a/apps/xmlrpc2di/XMLRPC2DI.cpp +++ b/apps/xmlrpc2di/XMLRPC2DI.cpp @@ -47,6 +47,8 @@ unsigned int XMLRPC2DI::ServerRetryAfter = 10; bool XMLRPC2DI::DebugServerParams = false; bool XMLRPC2DI::DebugServerResult = false; +unsigned int XMLRPC2DI::debug_log_level = 3; + double XMLRPC2DI::ServerTimeout = -1; XMLRPC2DI* XMLRPC2DI::instance() @@ -81,6 +83,8 @@ int XMLRPC2DI::load() { DebugServerResult = cfg.getParameter("debug_server_result", "no") == "yes"; DebugServerParams = cfg.getParameter("debug_server_params", "no") == "yes"; + debug_log_level = cfg.getParameterInt("debug_log_level", 3); + XmlRpcServer* s; bool multi_threaded = false; unsigned int threads = 0; @@ -538,7 +542,7 @@ void XMLRPC2DIServerDIMethod::execute(XmlRpcValue& params, XmlRpcValue& result) string fact_name = params[0]; string fct_name = params[1]; - DBG("XMLRPC2DI: factory '%s' function '%s'\n", + _LOG(XMLRPC2DI::debug_log_level, "XMLRPC2DI: factory '%s' function '%s'\n", fact_name.c_str(), fct_name.c_str()); // get args @@ -546,7 +550,7 @@ void XMLRPC2DIServerDIMethod::execute(XmlRpcValue& params, XmlRpcValue& result) XMLRPC2DIServer::xmlrpcval2amargarray(params, args, 2); if (XMLRPC2DI::DebugServerParams) { - DBG(" params: <%s>\n", AmArg::print(args).c_str()); + _LOG(XMLRPC2DI::debug_log_level, " params: <%s>\n", AmArg::print(args).c_str()); } AmDynInvokeFactory* di_f = AmPlugIn::instance()->getFactory4Di(fact_name); @@ -562,7 +566,7 @@ void XMLRPC2DIServerDIMethod::execute(XmlRpcValue& params, XmlRpcValue& result) if (XMLRPC2DI::DebugServerResult) { - DBG(" result: <%s>\n", AmArg::print(ret).c_str()); + _LOG(XMLRPC2DI::debug_log_level, " result: <%s>\n", AmArg::print(ret).c_str()); } XMLRPC2DIServer::amarg2xmlrpcval(ret, result); @@ -705,19 +709,19 @@ void DIMethodProxy::execute(XmlRpcValue& params, AmArg args, ret; - DBG("XMLRPC2DI '%s': function '%s'\n", - server_method_name.c_str(), - di_method_name.c_str()); + _LOG(XMLRPC2DI::debug_log_level, "XMLRPC2DI '%s': function '%s'\n", + server_method_name.c_str(), + di_method_name.c_str()); XMLRPC2DIServer::xmlrpcval2amarg(params, args); if (XMLRPC2DI::DebugServerParams) { - DBG(" params: <%s>\n", AmArg::print(args).c_str()); + _LOG(XMLRPC2DI::debug_log_level, " params: <%s>\n", AmArg::print(args).c_str()); } di->invoke(di_method_name, args, ret); if (XMLRPC2DI::DebugServerResult) { - DBG(" result: <%s>\n", AmArg::print(ret).c_str()); + _LOG(XMLRPC2DI::debug_log_level, " result: <%s>\n", AmArg::print(ret).c_str()); } XMLRPC2DIServer::amarg2xmlrpcval(ret, result); diff --git a/apps/xmlrpc2di/XMLRPC2DI.h b/apps/xmlrpc2di/XMLRPC2DI.h index fdc03b84..f0807fd2 100644 --- a/apps/xmlrpc2di/XMLRPC2DI.h +++ b/apps/xmlrpc2di/XMLRPC2DI.h @@ -192,6 +192,8 @@ class XMLRPC2DI static bool DebugServerParams; static bool DebugServerResult; + + static unsigned int debug_log_level; }; #endif diff --git a/apps/xmlrpc2di/etc/xmlrpc2di.conf b/apps/xmlrpc2di/etc/xmlrpc2di.conf index 500b9e9f..3f20f64f 100644 --- a/apps/xmlrpc2di/etc/xmlrpc2di.conf +++ b/apps/xmlrpc2di/etc/xmlrpc2di.conf @@ -38,4 +38,7 @@ xmlrpc_port=8090 # # print result of XMLRPC server calls into debug log [yes|no] # debug_server_result=yes -# \ No newline at end of file +# +# log level to use, default: 3 (debug) +# +#debug_log_level=2 From d4054b48cf64020a0b3d28a49ca692281b316ff5 Mon Sep 17 00:00:00 2001 From: Vladimir Broz Date: Mon, 4 Mar 2013 19:58:19 +0100 Subject: [PATCH 04/10] patch provided by A. Pogrebennyk - no build errors when using CI system was: 082f06f5 ... and not installing default config files Note: proper targets should be used (e.g. install-bin) if only binaries should be installed, or alternatively the pkg script should remove installed cfg files afterwards. install target installs the complete package --- apps/Makefile | 6 +++--- core/plug-in/Makefile.app_module | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/Makefile b/apps/Makefile index 394a78fd..80196de4 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -58,11 +58,11 @@ clean: .PHONY: modules modules: - -@for r in $(app_modules) "" ; do \ - if [ -n "$$r" ]; then \ + @for r in $(app_modules) "" ; do \ + if [ -n "$$r" -a -f "$$r"/Makefile ]; then \ echo "" ; \ echo "" ; \ - COREPATH=../$(COREPATH) $(MAKE) -C $$r all; \ + COREPATH=../$(COREPATH) $(MAKE) -C $$r all || exit 1; \ fi ; \ done diff --git a/core/plug-in/Makefile.app_module b/core/plug-in/Makefile.app_module index ad8587d0..03c3ff9e 100644 --- a/core/plug-in/Makefile.app_module +++ b/core/plug-in/Makefile.app_module @@ -27,12 +27,12 @@ depends = $(srcs:.cpp=.d) .PHONY: all all: $(extra_target) - -@$(MAKE) deps && \ + @$(MAKE) deps && \ $(MAKE) $(lib_full_name) .PHONY: module_package module_package: $(extra_target) - -@$(MAKE) deps && \ + @$(MAKE) deps && \ $(MAKE) $(lib_name) From 3930ca276eb1275304e1f52e4ef6d8e3d820b7a7 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Mon, 15 Jul 2013 12:47:43 +0200 Subject: [PATCH 05/10] sbc: b/f: reply 100 Trying to in-dlg msgs which are to be relayed Conflicts: apps/sbc/SBC.cpp --- apps/sbc/SBC.cpp | 10 ++++++++++ core/AmSipHeaders.h | 1 + 2 files changed, 11 insertions(+) diff --git a/apps/sbc/SBC.cpp b/apps/sbc/SBC.cpp index d6245a61..4b2bc5d6 100644 --- a/apps/sbc/SBC.cpp +++ b/apps/sbc/SBC.cpp @@ -858,6 +858,11 @@ void SBCDialog::onSipRequest(const AmSipRequest& req) { } } + if (fwd) { + DBG("replying 100 Trying of %s msg to be fwd'ed\n", req.method.c_str()); + dlg.reply(req, 100, SIP_REPLY_TRYING); + } + AmB2BCallerSession::onSipRequest(req); } @@ -1221,6 +1226,11 @@ void SBCCalleeSession::onSipRequest(const AmSipRequest& req) { } } + if (fwd) { + DBG("replying 100 Trying of %s msg to be fwd'ed\n", req.method.c_str()); + dlg.reply(req, 100, SIP_REPLY_TRYING); + } + AmB2BCalleeSession::onSipRequest(req); } diff --git a/core/AmSipHeaders.h b/core/AmSipHeaders.h index 911a3a44..78b39c39 100644 --- a/core/AmSipHeaders.h +++ b/core/AmSipHeaders.h @@ -62,5 +62,6 @@ #define SIP_REPLY_EXTENSION_REQUIRED "Extension Required" #define SIP_REPLY_LOOP_DETECTED "Loop Detected" #define SIP_REPLY_NOT_EXIST "Call Leg/Transaction Does Not Exist" +#define SIP_REPLY_TRYING "Trying" #endif /* __AMSIPHEADERS_H__ */ From 1bc63b19e22d34a9ad1b76bb51193a0d93c77654 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebennyk Date: Tue, 5 Mar 2013 11:49:27 +0100 Subject: [PATCH 06/10] make build fail on errors; restore use_threadpool --- Makefile | 4 ++-- Makefile.defs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 1d023400..a5cecf04 100644 --- a/Makefile +++ b/Makefile @@ -23,11 +23,11 @@ clean: .PHONY: modules modules: - -@for r in $(subdirs) "" ; do \ + @for r in $(subdirs) "" ; do \ if [ -n "$$r" ]; then \ echo "" ; \ echo "making $$r" ; \ - $(MAKE) -C $$r all; \ + $(MAKE) -C $$r all || exit 1; \ fi ; \ done diff --git a/Makefile.defs b/Makefile.defs index 9379c0f7..24b9091b 100644 --- a/Makefile.defs +++ b/Makefile.defs @@ -21,7 +21,7 @@ endif #version number VERSION = 1 PATCHLEVEL = 4 -SUBLEVEL = 3 +SUBLEVEL = 3 ifneq ($(SCM_REV),) RELEASE = $(SCM_REV) @@ -50,7 +50,7 @@ CPPFLAGS += -D_DEBUG \ # if compiled without thread pool support, every # session will have its own thread. # -#USE_THREADPOOL = yes +USE_THREADPOOL = yes # compile with spandsp DTMF detection? see soft-switch.org # this needs a fairly new version of spandsp - tested with 0.0.4pre11 From c82430ca8c76656e2026376bb779457c6e92cc98 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Wed, 17 Jul 2013 15:22:30 +0200 Subject: [PATCH 07/10] sbc: fix 3930ca2 - only reply 100 Trying to in-dlg INVITE to be relayed --- apps/sbc/SBC.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sbc/SBC.cpp b/apps/sbc/SBC.cpp index 4b2bc5d6..77098148 100644 --- a/apps/sbc/SBC.cpp +++ b/apps/sbc/SBC.cpp @@ -858,8 +858,8 @@ void SBCDialog::onSipRequest(const AmSipRequest& req) { } } - if (fwd) { - DBG("replying 100 Trying of %s msg to be fwd'ed\n", req.method.c_str()); + if (fwd && req.method == SIP_METH_INVITE) { + DBG("replying 100 Trying of INVITE msg to be fwd'ed\n"); dlg.reply(req, 100, SIP_REPLY_TRYING); } From ff1ef48889ce75be45b199b7af6397a9bc38bec1 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Thu, 1 Aug 2013 17:54:16 +0200 Subject: [PATCH 08/10] uac_auth: added UAS authentication of requests (internal API) Conflicts: core/plug-in/uac_auth/UACAuth.h core/tests/Makefile --- core/AmSipHeaders.h | 1 + core/plug-in/uac_auth/UACAuth.cpp | 255 +++++++++++++++++++++++++++--- core/plug-in/uac_auth/UACAuth.h | 39 +++-- core/tests/Makefile | 9 +- core/tests/sems_tests.cpp | 2 + core/tests/test_auth.cpp | 45 ++++++ core/tests/test_auth.h | 1 + doc/Readme.uac_auth.txt | 60 ++++++- 8 files changed, 376 insertions(+), 36 deletions(-) create mode 100644 core/tests/test_auth.cpp create mode 100644 core/tests/test_auth.h diff --git a/core/AmSipHeaders.h b/core/AmSipHeaders.h index 78b39c39..4abb428f 100644 --- a/core/AmSipHeaders.h +++ b/core/AmSipHeaders.h @@ -11,6 +11,7 @@ #define SIP_METH_ACK "ACK" #define SIP_METH_SUBSCRIBE "SUBSCRIBE" #define SIP_METH_NOTIFY "NOTIFY" +#define SIP_METH_CANCEL "CANCEL" #define SIP_HDR_FROM "From" #define SIP_HDR_TO "To" diff --git a/core/plug-in/uac_auth/UACAuth.cpp b/core/plug-in/uac_auth/UACAuth.cpp index 327b8f5e..54c8df7c 100644 --- a/core/plug-in/uac_auth/UACAuth.cpp +++ b/core/plug-in/uac_auth/UACAuth.cpp @@ -47,6 +47,7 @@ EXPORT_SESSION_EVENT_HANDLER_FACTORY(UACAuthFactory, MOD_NAME); EXPORT_PLUGIN_CLASS_FACTORY(UACAuthFactory, MOD_NAME); UACAuthFactory* UACAuthFactory::_instance=0; +string UACAuth::server_nonce_secret = "CKASLD§$>NLKJSLDKFJ"; // replaced on load UACAuthFactory* UACAuthFactory::instance() { @@ -57,7 +58,7 @@ UACAuthFactory* UACAuthFactory::instance() void UACAuthFactory::invoke(const string& method, const AmArg& args, AmArg& ret) { - if(method == "getHandler"){ + if (method == "getHandler") { CredentialHolder* c = dynamic_cast(args.get(0).asObject()); DialogControl* cc = dynamic_cast(args.get(1).asObject()); @@ -69,14 +70,43 @@ void UACAuthFactory::invoke(const string& method, const AmArg& args, AmArg& ret) ERROR("wrong types in call to getHandler. (c=%ld, cc= %ld)\n", (unsigned long)c, (unsigned long)cc); } - } - else + } else if (method == "checkAuth") { + + // params: Request realm user pwd + if (args.size() < 4) { + ERROR("missing arguments to uac_auth checkAuth function, expected Request realm user pwd\n"); + throw AmArg::TypeMismatchException(); + } + + AmSipRequest* req = dynamic_cast(args.get(0).asObject()); + if (NULL == req) + throw AmArg::TypeMismatchException(); + UACAuth::checkAuthentication(req, args.get(1).asCStr(), + args.get(2).asCStr(), + args.get(3).asCStr(), ret); + } else throw AmDynInvoke::NotImplemented(method); } int UACAuthFactory::onLoad() { + string secret; + AmConfigReader conf; + string cfg_file_path = AmConfig::ModConfigPath + "uac_auth.conf"; + if(conf.loadFile(cfg_file_path)){ + WARN("Could not open '%s', assuming that default values are fine\n", + cfg_file_path.c_str()); + secret = AmSession::getNewId(); // ?? TODO: is this cryptoproof? + } else { + secret = conf.getParameter("server_secret"); + if (secret.size()<5) { + ERROR("server_secret in '%s' too short!\n", cfg_file_path.c_str()); + return -1; + } + } + + UACAuth::setServerSecret(secret); return 0; } @@ -269,21 +299,32 @@ void w_MD5Update(MD5_CTX *ctx, const string& s) { MD5Update(ctx, a, s.length()); } - +// supr gly 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); - } + + while (true) { + if (pos1 == string::npos) + return ""; + + if (!pos1 || header[pos1-1] == ',' || header[pos1-1] == ' ') + break; + + pos1 = header.find(name, pos1+1); + } + + 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) { + return header.substr(pos1, pos2-pos1); + } else { + return header.substr(pos1); // end of hdr } } - return res; + + return ""; } bool UACAuth::parse_header(const string& auth_hdr, UACAuthDigestChallenge& challenge) { @@ -380,9 +421,9 @@ bool UACAuth::do_auth(const UACAuthDigestChallenge& challenge, } /* do authentication */ - uac_calc_HA1( challenge, cnonce, ha1); + uac_calc_HA1( challenge, credential, cnonce, ha1); uac_calc_HA2( method, uri, challenge, qop_auth_int ? hentity : NULL, ha2); - uac_calc_response( ha1, ha2, challenge, cnonce, qop_value, response); + uac_calc_response( ha1, ha2, challenge, cnonce, qop_value, nonce_count, response); DBG("calculated response = %s\n", response); // compile auth response @@ -443,19 +484,23 @@ static inline void cvt_hex(HASH bin, HASHHEX hex) * calculate H(A1) */ void UACAuth::uac_calc_HA1(const UACAuthDigestChallenge& challenge, + const UACAuthCred* _credential, string cnonce, HASHHEX sess_key) { + if (NULL == _credential) + return; + MD5_CTX Md5Ctx; HASH HA1; MD5Init(&Md5Ctx); - w_MD5Update(&Md5Ctx, credential->user); + 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); + w_MD5Update(&Md5Ctx, _credential->pwd); MD5Final(HA1, &Md5Ctx); // MD5sess ...not supported @@ -520,7 +565,7 @@ void UACAuth::uac_calc_hentity( const string& body, HASHHEX hentity ) */ void UACAuth::uac_calc_response(HASHHEX ha1, HASHHEX ha2, const UACAuthDigestChallenge& challenge, const string& cnonce, - const string& qop_value, HASHHEX response) + const string& qop_value, unsigned int nonce_count, HASHHEX response) { unsigned char hc[1]; hc[0]=':'; MD5_CTX Md5Ctx; @@ -532,6 +577,7 @@ void UACAuth::uac_calc_response(HASHHEX ha1, HASHHEX ha2, w_MD5Update(&Md5Ctx, challenge.nonce); MD5Update(&Md5Ctx, hc, 1); + if (!qop_value.empty()) { w_MD5Update(&Md5Ctx, int2hex(nonce_count,true)); @@ -546,3 +592,174 @@ void UACAuth::uac_calc_response(HASHHEX ha1, HASHHEX ha2, MD5Final(RespHash, &Md5Ctx); cvt_hex(RespHash, response); } + +/** calculate nonce: time-stamp H(time-stamp private-key) */ +string UACAuth::calcNonce() { + string result; + HASHHEX hash; + MD5_CTX Md5Ctx; + HASH RespHash; + + time_t now = time(NULL); + result = int2hex(now); + + MD5Init(&Md5Ctx); + w_MD5Update(&Md5Ctx, result); + w_MD5Update(&Md5Ctx, server_nonce_secret); + MD5Final(RespHash, &Md5Ctx); + cvt_hex(RespHash, hash); + + return result+string((const char*)hash); +} + +/** check nonce integrity. @return true if correct */ +bool UACAuth::checkNonce(const string& nonce) { + HASHHEX hash; + MD5_CTX Md5Ctx; + HASH RespHash; + +#define INT_HEX_LEN int(2*sizeof(int)) + + if (nonce.size() != INT_HEX_LEN+HASHHEXLEN) { + DBG("wrong nonce length (expected %u, got %zd)\n", INT_HEX_LEN+HASHHEXLEN, nonce.size()); + return false; + } + + MD5Init(&Md5Ctx); + w_MD5Update(&Md5Ctx, nonce.substr(0,INT_HEX_LEN)); + w_MD5Update(&Md5Ctx, server_nonce_secret); + MD5Final(RespHash, &Md5Ctx); + cvt_hex(RespHash, hash); + + return !strncmp((const char*)hash, &nonce[INT_HEX_LEN], HASHHEXLEN); +} + +void UACAuth::setServerSecret(const string& secret) { + server_nonce_secret = secret; + DBG("Server Nonce secret set\n"); +} + +void UACAuth::checkAuthentication(const AmSipRequest* req, const string& realm, const string& user, + const string& pwd, AmArg& ret) { + if (req->method == SIP_METH_ACK || req->method == SIP_METH_CANCEL) { + DBG("letting pass %s request without authentication\n", req->method.c_str()); + ret.push(200); + ret.push("OK"); + ret.push(""); + return; + } + + string auth_hdr = getHeader(req->hdrs, "Authorization"); + bool authenticated = false; + + if (auth_hdr.size()) { + UACAuthDigestChallenge r_challenge; + + r_challenge.realm = find_attribute("realm", auth_hdr); + r_challenge.nonce = find_attribute("nonce", auth_hdr); + r_challenge.qop = find_attribute("qop", auth_hdr); + string r_response = find_attribute("response", auth_hdr); + string r_username = find_attribute("username", auth_hdr); + string r_uri = find_attribute("uri", auth_hdr); + string r_cnonce = find_attribute("cnonce", auth_hdr); + + DBG("got realm '%s' nonce '%s', qop '%s', response '%s', username '%s' uri '%s' cnonce '%s'\n", + r_challenge.realm.c_str(), r_challenge.nonce.c_str(), r_challenge.qop.c_str(), + r_response.c_str(), r_username.c_str(), r_uri.c_str(), r_cnonce.c_str() ); + + if (r_response.size() != HASHHEXLEN) { + DBG("Auth: response length mismatch (wanted %u hex chars): '%s'\n", HASHHEXLEN, r_response.c_str()); + goto auth_end; + } + + if (realm != r_challenge.realm) { + DBG("Auth: realm mismatch: required '%s' vs '%s'\n", realm.c_str(), r_challenge.realm.c_str()); + goto auth_end; + } + + if (user != r_username) { + DBG("Auth: user mismatch: '%s' vs '%s'\n", user.c_str(), r_username.c_str()); + goto auth_end; + } + + if (!checkNonce(r_challenge.nonce)) { + DBG("Auth: incorrect nonce '%s'\n", r_challenge.nonce.c_str()); + goto auth_end; + } + + // we don't check the URI + // if (r_uri != req->r_uri) { + // DBG("Auth: incorrect URI in request: '%s'\n", r_challenge.nonce.c_str()); + // goto auth_end; + // } + + UACAuthCred credential; + credential.user = user; + credential.pwd = pwd; + + unsigned int client_nonce_count = 1; + + HASHHEX ha1; + HASHHEX ha2; + HASHHEX hentity; + HASHHEX response; + bool qop_auth=false; + bool qop_auth_int=false; + string qop_value; + + if(!r_challenge.qop.empty()){ + + if (r_challenge.qop == "auth") + qop_auth = true; + else if (r_challenge.qop == "auth-int") + qop_auth_int = true; + + if(qop_auth || qop_auth_int) { + + // get nonce count from request + string nonce_count_str = find_attribute("nc", auth_hdr); + if (str2i(nonce_count_str, client_nonce_count)) { + DBG("Error parsing nonce_count '%s'\n", nonce_count_str.c_str()); + goto auth_end; + } + + DBG("got client_nonce_count %u\n", client_nonce_count); + + // auth-int? calculate hentity + if(qop_auth_int){ + uac_calc_hentity(req->body, hentity); + qop_value = "auth-int"; + } else { + qop_value = "auth"; + } + + } + } + + uac_calc_HA1(r_challenge, &credential, r_cnonce, ha1); + uac_calc_HA2(req->method, r_uri, r_challenge, qop_auth_int ? hentity : NULL, ha2); + uac_calc_response( ha1, ha2, r_challenge, r_cnonce, qop_value, client_nonce_count, response); + DBG("calculated our response vs request: '%s' vs '%s'", response, r_response.c_str()); + + if (!strncmp((const char*)response, r_response.c_str(), HASHHEXLEN)) { + DBG("Auth: authentication successfull\n"); + authenticated = true; + } else { + DBG("Auth: authentication NOT successfull\n"); + } + } + + auth_end: + if (authenticated) { + ret.push(200); + ret.push("OK"); + ret.push(""); + } else { + ret.push(401); + ret.push("Unauthorized"); + ret.push(SIP_HDR_COLSP(SIP_HDR_WWW_AUTHENTICATE) "Digest " + "realm=\""+realm+"\", " + "qop=\"auth,auth-int\", " + "nonce=\""+calcNonce()+"\"\r\n"); + } +} diff --git a/core/plug-in/uac_auth/UACAuth.h b/core/plug-in/uac_auth/UACAuth.h index 504c3a34..7088cdc4 100644 --- a/core/plug-in/uac_auth/UACAuth.h +++ b/core/plug-in/uac_auth/UACAuth.h @@ -100,6 +100,8 @@ struct SIPRequestInfo { /** \brief SessionEventHandler for implementing uac authentication */ class UACAuth : public AmSessionEventHandler { + static string server_nonce_secret; + std::map sent_requests; UACAuthCred* credential; @@ -113,24 +115,26 @@ class UACAuth : public AmSessionEventHandler bool nonce_reuse; // reused nonce? - std::string find_attribute(const std::string& name, const std::string& header); - bool parse_header(const std::string& auth_hdr, UACAuthDigestChallenge& challenge); + static std::string find_attribute(const std::string& name, const std::string& header); + static bool parse_header(const std::string& auth_hdr, UACAuthDigestChallenge& challenge); - void uac_calc_HA1(const UACAuthDigestChallenge& challenge, - std::string cnonce, - HASHHEX sess_key); + static void uac_calc_HA1(const UACAuthDigestChallenge& challenge, + const UACAuthCred* _credential, + std::string cnonce, + HASHHEX sess_key); - void uac_calc_HA2( const std::string& method, const std::string& uri, - const UACAuthDigestChallenge& challenge, - HASHHEX hentity, - HASHHEX HA2Hex ); + static void uac_calc_HA2( const std::string& method, const std::string& uri, + const UACAuthDigestChallenge& challenge, + HASHHEX hentity, + HASHHEX HA2Hex ); - void uac_calc_hentity( const std::string& body, HASHHEX hentity ); + static void uac_calc_hentity( const std::string& body, HASHHEX hentity ); - void uac_calc_response( HASHHEX ha1, HASHHEX ha2, - const UACAuthDigestChallenge& challenge, - const std::string& cnonce, const string& qop_value, - HASHHEX response); + static void uac_calc_response( HASHHEX ha1, HASHHEX ha2, + const UACAuthDigestChallenge& challenge, + const std::string& cnonce, const string& qop_value, + unsigned int nonce_count, + HASHHEX response); /** * do auth on cmd with nonce in auth_hdr if possible @@ -174,6 +178,13 @@ class UACAuth : public AmSessionEventHandler const string& body, string& hdrs, int flags); + + static string calcNonce(); + static bool checkNonce(const string& nonce); + static void checkAuthentication(const AmSipRequest* req, const string& realm, + const string& user, const string& pwd, AmArg& ret); + + static void setServerSecret(const string& secret); }; diff --git a/core/tests/Makefile b/core/tests/Makefile index 5b79a9d7..c614e808 100644 --- a/core/tests/Makefile +++ b/core/tests/Makefile @@ -7,6 +7,9 @@ CORE_HDRS=$(CORE_SRCS:.cpp=.h) CORE_OBJS=$(CORE_SRCS:.cpp=.o) CORE_DEPS=$(subst ../,,$(CORE_SRCS:.cpp=.d)) +AUTH_DIR=../plug-in/uac_auth +AUTH_OBJS=$(AUTH_DIR)/UACAuth.o + SRCS=$(wildcard *.cpp) HDRS=$(SRCS:.cpp=.h) OBJS=$(SRCS:.cpp=.o) @@ -40,6 +43,9 @@ deps: $(DEPS) .PHONY: core_deps core_deps: $(CORE_DEPS) +AUTH_OBJS: $(AUTH_DIR)/UACAuth.cpp $(AUTH_DIR)/UACAuth.h + cd $(AUTH_DIR) ; $(MAKE) AUTH_OBJS + COREPATH=.. include ../../Makefile.defs @@ -56,8 +62,7 @@ include ../../Makefile.defs $(NAME): $(OBJS) $(CORE_OBJS) $(SIP_STACK) ../../Makefile.defs -@echo "" -@echo "making $(NAME)" - $(LD) -o $(NAME) $(OBJS) $(CORE_OBJS) $(SIP_STACK) $(LDFLAGS) $(EXTRA_LDFLAGS) - + $(LD) -o $(NAME) $(OBJS) $(CORE_OBJS) $(SIP_STACK) $(LDFLAGS) $(EXTRA_LDFLAGS) $(AUTH_OBJS) ifeq '$(NAME)' '$(MAKECMDGOALS)' include $(DEPS) $(CORE_DEPS) diff --git a/core/tests/sems_tests.cpp b/core/tests/sems_tests.cpp index 109a0b0e..1c3a1bcb 100644 --- a/core/tests/sems_tests.cpp +++ b/core/tests/sems_tests.cpp @@ -21,6 +21,8 @@ FCT_BGN() { init_logging(); log_stderr=true; log_level=3; + + FCTMF_SUITE_CALL(test_auth); FCTMF_SUITE_CALL(test_headers); FCTMF_SUITE_CALL(test_jsonarg); } FCT_END(); diff --git a/core/tests/test_auth.cpp b/core/tests/test_auth.cpp new file mode 100644 index 00000000..450b625a --- /dev/null +++ b/core/tests/test_auth.cpp @@ -0,0 +1,45 @@ +#include "fct.h" + +#include "log.h" + +#include "AmSipHeaders.h" +#include "AmSipMsg.h" +#include "AmUtils.h" +#include "plug-in/uac_auth/UACAuth.h" + +FCTMF_SUITE_BGN(test_auth) { + + FCT_TEST_BGN(nonce_gen) { + + string nonce = UACAuth::calcNonce(); + // DBG("nonce '%s'\n", nonce.c_str()); + fct_chk( UACAuth::checkNonce(nonce)); + } FCT_TEST_END(); + + FCT_TEST_BGN(nonce_wrong_secret) { + UACAuth::setServerSecret("secret1"); + string nonce = UACAuth::calcNonce(); + UACAuth::setServerSecret("secret2"); + fct_chk( !UACAuth::checkNonce(nonce)); + } FCT_TEST_END(); + + FCT_TEST_BGN(nonce_wrong_nonce) { + string nonce = UACAuth::calcNonce(); + nonce[0]=0; + nonce[1]=0; + fct_chk( !UACAuth::checkNonce(nonce)); + } FCT_TEST_END(); + + FCT_TEST_BGN(nonce_wrong_nonce) { + string nonce = UACAuth::calcNonce(); + nonce+="hallo"; + fct_chk( !UACAuth::checkNonce(nonce)); + } FCT_TEST_END(); + + FCT_TEST_BGN(nonce_wrong_nonce2) { + string nonce = UACAuth::calcNonce(); + nonce[nonce.size()-1]=nonce[nonce.size()-2]; + fct_chk( !UACAuth::checkNonce(nonce)); + } FCT_TEST_END(); + +} FCTMF_SUITE_END(); diff --git a/core/tests/test_auth.h b/core/tests/test_auth.h new file mode 100644 index 00000000..8d1c8b69 --- /dev/null +++ b/core/tests/test_auth.h @@ -0,0 +1 @@ + diff --git a/doc/Readme.uac_auth.txt b/doc/Readme.uac_auth.txt index 37e8e90d..0d8a0626 100644 --- a/doc/Readme.uac_auth.txt +++ b/doc/Readme.uac_auth.txt @@ -1,4 +1,4 @@ -uac_auth Client authentication +uac_auth Client / Server authentication how to use uac_auth @@ -41,3 +41,61 @@ How to see announce_auth app for example application + +How to use server auth +---------------------- + +To authenticate a request, use the "checkAuth" DI function. + + Arguments: + args[0] - ArgObject pointing to the Request (AmSipRequest object) + args[1] - realm + args[2] - user + args[3] - password + Return values: + ret[0] - code: 200 for successful auth, 401 for unsuccessful (ie. + ret[1] - reason string + ret[2] - optional auth header + +Limitations +----------- + +- URI in auth hdr is not checked +- multiple Authorization headers are probably not properly processed (only the first one is used) + +code example: + + AmDynInvokeFactory* fact = + AmPlugIn::instance()->getFactory4Di("uac_auth"); + if (NULL != fact) { + AmDynInvoke* di_inst = fact->getInstance(); + if(di_inst) { + AmArg di_args, di_ret; + try { + di_args.push(AmArg((AmObject*)&req)); + di_args.push("myrealm"); + di_args.push("myuser"); + di_args.push("mypwd"); + di_inst->invoke("checkAuth", di_args, di_ret); + + if (di_ret.size() >= 3) { + if (di_ret[0].asInt() != 200) { + DBG("Auth: replying %u %s - hdrs: '%s'\n", + di_ret[0].asInt(), di_ret[1].asCStr(), di_ret[2].asCStr()); + dlg->reply(req, di_ret[0].asInt(), di_ret[1].asCStr(), NULL, di_ret[2].asCStr()); + return; + } else { + DBG("Successfully authenticated request.\n"); + } + } + } catch (const AmDynInvoke::NotImplemented& ni) { + ERROR("not implemented DI function 'checkAuth'\n"); + } catch (const AmArg::OutOfBoundsException& oob) { + ERROR("out of bounds in DI call 'checkAuth'\n"); + } catch (const AmArg::TypeMismatchException& oob) { + ERROR("type mismatch in DI call checkAuth\n"); + } catch (...) { + ERROR("unexpected Exception in DI call checkAuth\n"); + } + } + } From 99dbc30fa75456c18700830ac4b08d4b6049d984 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Thu, 1 Aug 2013 19:03:07 +0200 Subject: [PATCH 09/10] core: AmSipMsg gets ArgObject as base (for passing in DI calls) --- core/AmSipMsg.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/AmSipMsg.h b/core/AmSipMsg.h index a57e0f63..f54e0c79 100644 --- a/core/AmSipMsg.h +++ b/core/AmSipMsg.h @@ -5,9 +5,11 @@ using std::string; #include "sip/trans_layer.h" +#include "AmArg.h" /* enforce common naming in Req&Rpl */ class _AmSipMsgInDlg + : public ArgObject { public: string method; From a0e3c17b36f2f622f39808088f429bebfb5df6b6 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Thu, 1 Aug 2013 19:10:46 +0200 Subject: [PATCH 10/10] sbc: SIP UAS authentication for B leg (uas_auth_bleg_enabled) Notes: - All of those profile options must be set uas_auth_bleg_realm uas_auth_bleg_user uas_auth_bleg_pwd - On realm mismatch, it is not authenticated - URI is not checked against the Authorization URI Conflicts: apps/sbc/SBC.cpp apps/sbc/SBC.h apps/sbc/SBCCallProfile.cpp apps/sbc/SBCCallProfile.h apps/sbc/etc/transparent.sbcprofile.conf --- apps/sbc/SBC.cpp | 57 +++++++++++++++++++++++- apps/sbc/SBC.h | 2 + apps/sbc/SBCCallProfile.cpp | 6 +++ apps/sbc/SBCCallProfile.h | 3 ++ apps/sbc/etc/transparent.sbcprofile.conf | 6 +++ 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/apps/sbc/SBC.cpp b/apps/sbc/SBC.cpp index 77098148..6302cb3f 100644 --- a/apps/sbc/SBC.cpp +++ b/apps/sbc/SBC.cpp @@ -639,6 +639,15 @@ void SBCDialog::onInvite(const AmSipRequest& req) replaceParameters(call_profile.auth_credentials.pwd, "auth_pwd", REPLACE_VALS); } + if (call_profile.uas_auth_bleg_enabled) { + call_profile.uas_auth_bleg_credentials.realm = + replaceParameters(call_profile.uas_auth_bleg_credentials.realm, "uas_auth_bleg_realm", REPLACE_VALS); + call_profile.uas_auth_bleg_credentials.user = + replaceParameters(call_profile.uas_auth_bleg_credentials.user, "uas_auth_bleg_user", REPLACE_VALS); + call_profile.uas_auth_bleg_credentials.pwd = + replaceParameters(call_profile.uas_auth_bleg_credentials.pwd, "uas_auth_bleg_pwd", REPLACE_VALS); + } + if (!call_profile.outbound_interface.empty()) { call_profile.outbound_interface = replaceParameters(call_profile.outbound_interface, "outbound_interface", @@ -1049,6 +1058,18 @@ void SBCDialog::createCalleeSession() } } + if (call_profile.uas_auth_bleg_enabled) { + AmDynInvokeFactory* fact = AmPlugIn::instance()->getFactory4Di("uac_auth"); + if (NULL != fact) { + AmDynInvoke* di_inst = fact->getInstance(); + if(NULL != di_inst) { + callee_session->setAuthDI(di_inst); + } + } else { + ERROR("B-leg UAS auth enabled (uas_auth_bleg_enabled), but uac_auth module not loaded!\n"); + } + } + if (call_profile.sst_enabled) { AmSessionEventHandler* h = SBCFactory::session_timer_fact->getHandler(callee_session); if(!h) { @@ -1133,7 +1154,7 @@ void SBCDialog::createCalleeSession() SBCCalleeSession::SBCCalleeSession(const AmB2BCallerSession* caller, const SBCCallProfile& call_profile) - : auth(NULL), + : auth(NULL),auth_di(NULL), call_profile(call_profile), AmB2BCalleeSession(caller) { @@ -1226,6 +1247,40 @@ void SBCCalleeSession::onSipRequest(const AmSipRequest& req) { } } + if (call_profile.uas_auth_bleg_enabled && NULL != auth_di) { + AmArg di_args, di_ret; + try { + DBG("Auth: checking authentication\n"); + di_args.push((ArgObject*)&req); + di_args.push(call_profile.uas_auth_bleg_credentials.realm); + di_args.push(call_profile.uas_auth_bleg_credentials.user); + di_args.push(call_profile.uas_auth_bleg_credentials.pwd); + auth_di->invoke("checkAuth", di_args, di_ret); + + if (di_ret.size() >= 3) { + if (di_ret[0].asInt() != 200) { + DBG("Auth: replying %u %s - hdrs: '%s'\n", + di_ret[0].asInt(), di_ret[1].asCStr(), di_ret[2].asCStr()); + dlg.reply(req, di_ret[0].asInt(), di_ret[1].asCStr(), "", "", di_ret[2].asCStr()); + return; + } else { + DBG("Successfully authenticated request.\n"); + } + } else { + ERROR("internal: no proper result from checkAuth: '%s'\n", AmArg::print(di_ret).c_str()); + } + + } catch (const AmDynInvoke::NotImplemented& ni) { + ERROR("not implemented DI function 'checkAuth'\n"); + } catch (const AmArg::OutOfBoundsException& oob) { + ERROR("out of bounds in DI call 'checkAuth'\n"); + } catch (const AmArg::TypeMismatchException& oob) { + ERROR("type mismatch in DI call checkAuth\n"); + } catch (...) { + ERROR("unexpected Exception in DI call checkAuth\n"); + } + } + if (fwd) { DBG("replying 100 Trying of %s msg to be fwd'ed\n", req.method.c_str()); dlg.reply(req, 100, SIP_REPLY_TRYING); diff --git a/apps/sbc/SBC.h b/apps/sbc/SBC.h index 01c1ab14..930d7497 100644 --- a/apps/sbc/SBC.h +++ b/apps/sbc/SBC.h @@ -156,6 +156,7 @@ class SBCCalleeSession : public AmB2BCalleeSession, public CredentialHolder { AmSessionEventHandler* auth; + AmDynInvoke* auth_di; SBCCallProfile call_profile; protected: @@ -183,6 +184,7 @@ class SBCCalleeSession inline UACAuthCred* getCredentials(); void setAuthHandler(AmSessionEventHandler* h) { auth = h; } + void setAuthDI(AmDynInvoke* di_inst) { auth_di = di_inst; } }; extern void assertEndCRLF(string& s); diff --git a/apps/sbc/SBCCallProfile.cpp b/apps/sbc/SBCCallProfile.cpp index b727e820..8a2cddb7 100644 --- a/apps/sbc/SBCCallProfile.cpp +++ b/apps/sbc/SBCCallProfile.cpp @@ -118,6 +118,11 @@ bool SBCCallProfile::readFromConfiguration(const string& name, call_timer_enabled = cfg.getParameter("enable_call_timer", "no") == "yes"; call_timer = cfg.getParameter("call_timer"); + uas_auth_bleg_enabled = cfg.getParameter("enable_bleg_uas_auth", "no") == "yes"; + uas_auth_bleg_credentials.realm = cfg.getParameter("uas_auth_bleg_realm"); + uas_auth_bleg_credentials.user = cfg.getParameter("uas_auth_bleg_user"); + uas_auth_bleg_credentials.pwd = cfg.getParameter("uas_auth_bleg_pwd"); + prepaid_enabled = cfg.getParameter("enable_prepaid", "no") == "yes"; prepaid_accmodule = cfg.getParameter("prepaid_accmodule"); prepaid_uuid = cfg.getParameter("prepaid_uuid"); @@ -228,6 +233,7 @@ bool SBCCallProfile::readFromConfiguration(const string& name, INFO("SBC: SST %sabled\n", sst_enabled?"en":"dis"); INFO("SBC: SIP auth %sabled\n", auth_enabled?"en":"dis"); + INFO("SBC: SIP UAS auth for B leg %sabled\n", uas_auth_bleg_enabled?"en":"dis"); INFO("SBC: call timer %sabled\n", call_timer_enabled?"en":"dis"); if (call_timer_enabled) { INFO("SBC: %s seconds\n", call_timer.c_str()); diff --git a/apps/sbc/SBCCallProfile.h b/apps/sbc/SBCCallProfile.h index bec361c6..fe20dfcf 100644 --- a/apps/sbc/SBCCallProfile.h +++ b/apps/sbc/SBCCallProfile.h @@ -74,6 +74,9 @@ struct SBCCallProfile { bool auth_enabled; UACAuthCred auth_credentials; + bool uas_auth_bleg_enabled; + UACAuthCred uas_auth_bleg_credentials; + bool call_timer_enabled; string call_timer; diff --git a/apps/sbc/etc/transparent.sbcprofile.conf b/apps/sbc/etc/transparent.sbcprofile.conf index 3356b07f..f34f5279 100644 --- a/apps/sbc/etc/transparent.sbcprofile.conf +++ b/apps/sbc/etc/transparent.sbcprofile.conf @@ -51,6 +51,12 @@ #auth_user=$P(u) #auth_pwd=$P(p) +## UAS auth for B leg +#uas_auth_bleg_enabled=yes +#uas_auth_bleg_realm=$P(sr) +#uas_auth_bleg_user=$P(su) +#uas_auth_bleg_pwd=$P(sp) + ## call timer #enable_call_timer=yes #call_timer=60