reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
ServerAuthManager.cxx
Go to the documentation of this file.
1#include "rutil/ResipAssert.h"
2
9#include "rutil/Logger.hxx"
13
14#include <utility>
15
16#define RESIPROCATE_SUBSYSTEM Subsystem::DUM
17
18using namespace resip;
19using namespace std;
20
22 TargetCommand::Target& target,
23 bool challengeThirdParties,
24 const Data& staticRealm,
25 const std::vector<DigestType>& challengeDigestTypes) :
26 DumFeature(dum, target),
27 mChallengeThirdParties(challengeThirdParties),
28 mStaticRealm(staticRealm),
29 mChallengeDigestTypes(challengeDigestTypes)
30{
31}
32
33
35{
36 InfoLog(<< "~ServerAuthManager: " << mMessages.size() << " messages in memory when destroying.");
37}
38
39// !bwc! We absolutely, positively, MUST NOT throw here. This is because in
40// DialogUsageManager::process(), we do not know if a DumFeature has taken
41// ownership of msg until we get a return. If we throw, the ownership of msg
42// is unknown. This is unacceptable.
45{
46 SipMessage* sipMsg = dynamic_cast<SipMessage*>(msg);
47
48 if (sipMsg)
49 {
51 switch ( handle(sipMsg) )
52 {
54 InfoLog(<< "ServerAuth challenged request " << sipMsg->brief());
57 InfoLog(<< "ServerAuth requested info (requiresChallenge) " << sipMsg->brief());
60 InfoLog(<< "ServerAuth requested credentials " << sipMsg->brief());
63 InfoLog(<< "ServerAuth rejected request " << sipMsg->brief());
65 default: // includes Skipped
67 }
68 }
69
70 ChallengeInfo* challengeInfo = dynamic_cast<ChallengeInfo*>(msg);
71 if(challengeInfo)
72 {
73 InfoLog(<< "ServerAuth got ChallengeInfo " << challengeInfo->brief());
74 MessageMap::iterator it = mMessages.find(challengeInfo->getTransactionId());
75 resip_assert(it != mMessages.end());
76 std::unique_ptr<SipMessage> sipMsg(it->second);
77 mMessages.erase(it);
78
79 if(challengeInfo->isFailed())
80 {
81 // some kind of failure occurred while checking whether a
82 // challenge is required
83 InfoLog(<< "ServerAuth requiresChallenge() async failed");
84 auto response = std::make_shared<SipMessage>();
85 Helper::makeResponse(*response, *sipMsg, 500, "Server Internal Error");
86 mDum.send(response);
88 }
89
90 if(challengeInfo->isChallengeRequired())
91 {
92 issueChallenge(sipMsg.get());
93 InfoLog(<< "ServerAuth challenged request (after async) " << sipMsg->brief());
95 }
96 else
97 {
98 // challenge is not required, re-instate original message
99 postCommand(std::move(sipMsg));
101 }
102 }
103
104 UserAuthInfo* userAuth = dynamic_cast<UserAuthInfo*>(msg);
105 if (userAuth)
106 {
107 //InfoLog(<< "Got UserAuthInfo");
108 UserAuthInfo* userAuth = dynamic_cast<UserAuthInfo*>(msg);
109 if (userAuth)
110 {
111 Message* result = handleUserAuthInfo(userAuth);
112 if (result)
113 {
114 postCommand(unique_ptr<Message>(result));
116 }
117 else
118 {
119 InfoLog(<< "ServerAuth rejected request " << *userAuth);
120 return ChainDoneAndEventDone;
121 }
122 }
123 }
124 return FeatureDone;
125}
126
129{
130 resip_assert(userAuth);
131
132 MessageMap::iterator it = mMessages.find(userAuth->getTransactionId());
133 resip_assert(it != mMessages.end());
134 SipMessage* requestWithAuth = it->second;
135 mMessages.erase(it);
136
137 InfoLog( << "Checking for auth result in realm=" << userAuth->getRealm()
138 << " A1=" << userAuth->getA1());
139
140 if (userAuth->getMode() == UserAuthInfo::UserUnknown ||
141 (userAuth->getMode() == UserAuthInfo::RetrievedA1 && userAuth->getA1().empty()))
142 {
143 InfoLog (<< "User unknown " << userAuth->getUser() << " in " << userAuth->getRealm());
144 auto response = std::make_shared<SipMessage>();
145 Helper::makeResponse(*response, *requestWithAuth, 404, "User unknown.");
146 mDum.send(response);
147 onAuthFailure(BadCredentials, *requestWithAuth);
148 delete requestWithAuth;
149 return nullptr;
150 }
151
152 if (userAuth->getMode() == UserAuthInfo::Error)
153 {
154 InfoLog (<< "Error in auth procedure for " << userAuth->getUser() << " in " << userAuth->getRealm());
155 int32_t code = 0;
156 resip::Data statusText;
157 userAuth->getErrorInfo(code, statusText);
158
159 auto response = std::make_shared<SipMessage>();
160 Helper::makeResponse(*response, *requestWithAuth, code, statusText);
161
162 mDum.send(response);
163 onAuthFailure(Error, *requestWithAuth);
164 delete requestWithAuth;
165 return 0;
166 }
167
168 bool stale = false;
169 bool digestAccepted = (userAuth->getMode() == UserAuthInfo::DigestAccepted);
170 if(userAuth->getMode() == UserAuthInfo::RetrievedA1)
171 {
174 std::pair<Helper::AuthResult,Data> resPair =
175 Helper::advancedAuthenticateRequest(*requestWithAuth,
176 userAuth->getRealm(),
177 userAuth->getA1(),
178 3000,
180
181 switch (resPair.first)
182 {
184 digestAccepted = true;
185 break;
186 case Helper::Failed:
187 // digestAccepted = false; // already false by default
188 break;
190 if(rejectBadNonces())
191 {
192 InfoLog (<< "Authentication nonce badly formed for " << userAuth->getUser());
193
194 auto response = std::make_shared<SipMessage>();
195 Helper::makeResponse(*response, *requestWithAuth, 403, "Invalid nonce");
196 mDum.send(response);
197 onAuthFailure(InvalidRequest, *requestWithAuth);
198 delete requestWithAuth;
199 return 0;
200 }
201 else
202 {
203 stale=true;
204 }
205 break;
206 case Helper::Expired:
207 stale = true;
208 break;
209 default:
210 break;
211 }
212 }
213
214 if(stale || userAuth->getMode() == UserAuthInfo::Stale)
215 {
216 InfoLog (<< "Nonce expired for " << userAuth->getUser());
217
218 issueChallenge(requestWithAuth, true);
219 delete requestWithAuth;
220 return 0;
221 }
222
223 if(digestAccepted)
224 {
225 if (authorizedForThisIdentity(userAuth->getUser(), userAuth->getRealm(),
226 requestWithAuth->header(h_From).uri()))
227 {
228 InfoLog (<< "Authorized request for " << userAuth->getRealm());
229 onAuthSuccess(*requestWithAuth);
230 return requestWithAuth;
231 }
232 else
233 {
234 // !rwm! The user is trying to forge a request. Respond with a 403
235 InfoLog (<< "User: " << userAuth->getUser() << " at realm: " << userAuth->getRealm() <<
236 " trying to forge request from: " << requestWithAuth->header(h_From).uri());
237
238 auto response = std::make_shared<SipMessage>();
239 Helper::makeResponse(*response, *requestWithAuth, 403, "Invalid user name provided");
240 mDum.send(response);
241 onAuthFailure(InvalidRequest, *requestWithAuth);
242 delete requestWithAuth;
243 return 0;
244 }
245 }
246 else
247 {
248 // Handles digestAccepted == false, DigestNotAccepted and any other
249 // case that is not recognised by the foregoing logic
250
251 InfoLog (<< "Invalid password provided for " << userAuth->getUser() << " in " << userAuth->getRealm());
252 InfoLog (<< " a1 hash of password from db was " << userAuth->getA1() );
253
254 auto response = std::make_shared<SipMessage>();
255 Helper::makeResponse(*response, *requestWithAuth, 403, "Invalid password provided");
256 mDum.send(response);
257 onAuthFailure(BadCredentials, *requestWithAuth);
258 delete requestWithAuth;
259 return 0;
260 }
261}
262
263
264bool
266{
267 return false;
268}
269
270
271bool
273{
274 return true;
275}
276
277
278bool
280{
281 return true;
282}
283
284
287{
289 {
290 const Uri& fromUri = msg.header(h_From).uri();
291 if(!mDum.isMyDomain(fromUri.host()))
292 return False;
293 }
294 return True;
295}
296
297
298bool
300 const resip::Data &realm,
301 resip::Uri &fromUri)
302{
303 // !rwm! good enough for now. TODO eventually consult a database to see what
304 // combinations of user/realm combos are authorized for an identity
305
306 // First try the form where the username parameter in the auth
307 // header is just the username component of the fromUri
308 //
309 if ((fromUri.user() == user) && (fromUri.host() == realm))
310 return true;
311
312 // Now try the form where the username parameter in the auth
313 // header is the full fromUri, e.g.
314 // Proxy-Authorization: Digest username="user@domain" ...
315 //
316 if (fromUri.getAorNoPort() == user)
317 return true;
318
319 // catch-all: access denied
320 return false;
321}
322
323
324const Data&
326{
327 // (1) Check if static realm is defined
328 if (!mStaticRealm.empty())
329 {
330 return mStaticRealm;
331 }
332
333 // (2) Check From domain
334 if (mDum.isMyDomain(msg.header(h_From).uri().host()))
335 {
336 return msg.header(h_From).uri().host();
337 }
338
339 // (3) Punt: Use Request URI
340 return msg.header(h_RequestLine).uri().host();
341}
342
343
344bool
346{
347 if(!mStaticRealm.empty())
348 {
349 return mStaticRealm == realm;
350 }
351 return mDum.isMyDomain(realm);
352}
353
354
355// return true if request has been consumed
358{
359 //InfoLog( << "trying to do auth" );
360 if (sipMsg->isRequest())
361 {
362 if(sipMsg->method() == CANCEL)
363 {
364 // If we receive a cancel - check to see if we have the matching INVITE in our message map.
365 // If we do, then we haven't created a DUM dialog for it yet, since we are still waiting
366 // for the credential information to arrive. We need to properly respond to the CANCEL here
367 // since it won't be handled externally.
368 MessageMap::iterator it = mMessages.find(sipMsg->getTransactionId());
369 if(it != mMessages.end())
370 {
371 // Ensure message is an INVITE - if not then something fishy is going on. Either
372 // someone has cancelled a non-INVITE transaction or we have a tid collision.
373 if(it->second->isRequest() && it->second->method() == INVITE)
374 {
375 std::unique_ptr<SipMessage> inviteMsg(it->second);
376 mMessages.erase(it); // Remove the INVITE from the message map and respond to it
377
378 InfoLog (<< "Received a CANCEL for an INVITE request that we are still waiting on auth "
379 << "info for, responding appropriately, tid="
380 << sipMsg->getTransactionId());
381
382 // Send 487/Inv
383 auto inviteResponse = std::make_shared<SipMessage>();
384 Helper::makeResponse(*inviteResponse, *inviteMsg, 487); // Request Cancelled
385 mDum.send(inviteResponse);
386
387 // Send 200/Cancel
388 auto cancelResponse = std::make_shared<SipMessage>();
389 Helper::makeResponse(*cancelResponse, *sipMsg, 200);
390 mDum.send(cancelResponse);
391
392 return Rejected; // Use rejected since handling is what we want - stop DUM from processing the cancel any further
393 }
394 }
395 }
396 else if(sipMsg->method() != ACK) // Do not challenge ACKs or CANCELs (picked off above)
397 {
400 {
401 if(!sipMsg->exists(h_ProxyAuthorizations))
402 {
403 return issueChallengeIfRequired(sipMsg);
404 }
405 auths = &sipMsg->header(h_ProxyAuthorizations);
406 }
407 else
408 {
409 if(!sipMsg->exists(h_Authorizations))
410 {
411 return issueChallengeIfRequired(sipMsg);
412 }
413 auths = &sipMsg->header(h_Authorizations);
414 }
415
416 try
417 {
418 for(Auths::iterator it = auths->begin(); it != auths->end(); it++)
419 {
420 if (isMyRealm(it->param(p_realm)))
421 {
422 InfoLog (<< "Requesting credential for "
423 << it->param(p_username) << " @ " << it->param(p_realm));
424
425 requestCredential(it->param(p_username),
426 it->param(p_realm),
427 *sipMsg,
428 *it,
429 sipMsg->getTransactionId());
430 mMessages[sipMsg->getTransactionId()] = sipMsg;
432 }
433 }
434
435 InfoLog (<< "Didn't find matching realm ");
436 return issueChallengeIfRequired(sipMsg);
437 }
438 catch(BaseException& e)
439 {
440 InfoLog (<< "Invalid auth header provided " << e);
441 auto response = std::make_shared<SipMessage>();
442 Helper::makeResponse(*response, *sipMsg, 400, "Invalid auth header");
443 mDum.send(response);
445 return Rejected;
446 }
447 }
448 }
449 return Skipped;
450}
451
454{
455 // Is challenge required for this message
456 AsyncBool required = requiresChallenge(*sipMsg);
457 switch(required)
458 {
459 case False:
460 return Skipped;
461 case Async:
462 mMessages[sipMsg->getTransactionId()] = sipMsg;
463 return RequestedInfo;
464 case True:
465 default:
466 issueChallenge(sipMsg);
467 return Challenged;
468 }
469}
470
471void
473{
474 //assume TransactionUser has matched/repaired a realm
475 std::shared_ptr<SipMessage> challenge(Helper::makeChallenge(*sipMsg,
476 getChallengeRealm(*sipMsg),
477 useAuthInt(),
478 stale,
481
482 InfoLog(<< "Sending challenge to " << sipMsg->brief());
483 mDum.send(challenge);
484}
485
486void
488{
489 // sub class may want to create a log entry
490}
491
492void
494{
495 // sub class may want to create a log entry
496}
497
498
499/* ====================================================================
500 * The Vovida Software License, Version 1.0
501 *
502 * Copyright (c) 2026 SIP Spectrum, Inc. https://www.sipspectrum.com
503 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
504 *
505 * Redistribution and use in source and binary forms, with or without
506 * modification, are permitted provided that the following conditions
507 * are met:
508 *
509 * 1. Redistributions of source code must retain the above copyright
510 * notice, this list of conditions and the following disclaimer.
511 *
512 * 2. Redistributions in binary form must reproduce the above copyright
513 * notice, this list of conditions and the following disclaimer in
514 * the documentation and/or other materials provided with the
515 * distribution.
516 *
517 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
518 * and "Vovida Open Communication Application Library (VOCAL)" must
519 * not be used to endorse or promote products derived from this
520 * software without prior written permission. For written
521 * permission, please contact vocal@vovida.org.
522 *
523 * 4. Products derived from this software may not be called "VOCAL", nor
524 * may "VOCAL" appear in their name, without prior written
525 * permission of Vovida Networks, Inc.
526 *
527 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
528 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
529 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
530 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
531 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
532 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
533 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
534 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
535 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
536 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
537 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
538 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
539 * DAMAGE.
540 *
541 * ====================================================================
542 *
543 * This software consists of voluntary contributions made by Vovida
544 * Networks, Inc. and many individuals on behalf of Vovida Networks,
545 * Inc. For more information on Vovida Networks, Inc., please see
546 * <http://www.vovida.org/>.
547 *
548 */
#define InfoLog(args_)
Definition Logger.hxx:131
#define resip_assert(x)
This file needs to work for both C and C++ code.
The abstract base-class for all exceptions thrown by resip code.
bool isChallengeRequired() const
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
bool empty() const noexcept
Checks whether the Data is empty.
Definition Data.hxx:591
void send(std::shared_ptr< SipMessage > request)
virtual const Data & getTransactionId() const
DialogUsageManager & mDum
virtual void postCommand(std::unique_ptr< Message > message)
static SipMessage * makeChallenge(const SipMessage &request, const Data &realm, bool useAuth=true, bool stale=false, bool proxy=false, const std::vector< DigestType > &digestTypes=std::vector< DigestType >())
Definition Helper.cxx:1224
static void makeResponse(SipMessage &response, const SipMessage &request, int responseCode, const Data &reason=Data::Empty, const Data &hostname=Data::Empty, const Data &warning=Data::Empty)
Make a response to a provided request.
Definition Helper.cxx:412
static std::pair< AuthResult, Data > advancedAuthenticateRequest(const SipMessage &request, const Data &realm, const Data &resipPasswordHashA1, int expiresDelta=0, bool proxyAuthorization=true)
Definition Helper.cxx:1071
The base-class used for message-passing.
Definition Message.hxx:21
virtual Brief brief() const
return a facet for brief encoding of message
Definition Message.cxx:16
An iterator class (bidirectional)
Container class for ParserCategory, used by SipMessage to represent multi-valued headers (Contact,...
iterator end()
Returns an iterator pointing to the last header field value.
iterator begin()
Returns an iterator pointing to the first header field value.
const Uri & uri() const
virtual bool isMyRealm(const Data &realm)
should return true if realm passed in is ours and we can challenge
Result issueChallengeIfRequired(SipMessage *sipMsg)
virtual SipMessage * handleUserAuthInfo(UserAuthInfo *auth)
std::vector< DigestType > mChallengeDigestTypes
virtual Result handle(SipMessage *sipMsg)
virtual const Data & getChallengeRealm(const SipMessage &msg)
returns the realm to be used for the challenge
virtual bool proxyAuthenticationMode() const
virtual void onAuthSuccess(const SipMessage &msg)
virtual bool useAuthInt() const
virtual AsyncBool requiresChallenge(const SipMessage &msg)
should return true if the request must be challenged The default is to challenge all requests - overr...
virtual bool rejectBadNonces() const
virtual void onAuthFailure(AuthFailureReason reason, const SipMessage &msg)
virtual ProcessingResult process(Message *msg)
void issueChallenge(SipMessage *sipMsg, bool stale=false)
virtual bool authorizedForThisIdentity(const resip::Data &user, const resip::Data &realm, resip::Uri &fromUri)
should return true if the passed in user is authorized for the provided uri
virtual void requestCredential(const Data &user, const Data &realm, const SipMessage &msg, const Auth &auth, const Data &transactionToken)=0
ServerAuthManager(DialogUsageManager &dum, TargetCommand::Target &target, bool challengeThirdParties=true, const resip::Data &staticRealm="", const std::vector< DigestType > &challengeDigestTypes=std::vector< DigestType >())
Represents a SIP message.
bool isRequest() const
Returns true if message is a request, false otherwise.
const RequestLine & header(const RequestLineType &l) const
Returns the RequestLine. This is only valid for request messages.
virtual const Data & getTransactionId() const
Returns the transaction id from the branch or if 2543, the computed hash.
bool exists(const HeaderBase &headerType) const
Returns true if the given header field is present, false otherwise.
resip::MethodTypes method() const
returns the method type of the message
bool isMyDomain(const Data &domain) const
Returns true iff domain matches one of the domains that this TransactionUser is responsible for.
Represents the "SIP-URI" and "SIPS-URI" elements in the RFC 3261 grammar.
Definition Uri.hxx:22
Data getAorNoPort() const
Definition Uri.cxx:1087
Data & user()
Definition Uri.hxx:42
Data & host()
Definition Uri.hxx:40
const void getErrorInfo(int32_t &errorCode, resip::Data &errorStatusText) const
const resip::Data & getUser() const
const resip::Data & getRealm() const
InfoMode getMode() const
const resip::Data & getA1() const
dcm! – add UnusedChecking(_enum) below;
RequestLineType h_RequestLine
Definition Headers.cxx:347
signed int int32_t
Definition stdint.h:82