2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 * shibrpc-server.cpp -- SHIBRPC Server implementation. Originally created
52 * as shibrpc-server-stubs.c; make sure that the function
53 * prototypes here match those in shibrpc.x.
55 * Created by: Derek Atkins <derek@ihtfp.com>
66 #ifdef HAVE_LIBDMALLOCXX
70 #include <log4cpp/Category.hh>
73 using namespace log4cpp;
75 using namespace shibboleth;
76 using namespace shibtarget;
78 static string get_threadid (const char* proc)
80 static u_long counter = 0;
82 buf << "[" << counter++ << "] " << proc;
86 static Category& get_category (void)
88 return Category::getInstance("shibtarget.rpc-server");
92 shibrpc_ping_2_svc(int *argp, int *result, struct svc_req *rqstp)
99 shibrpc_get_session_2_svc(
100 shibrpc_get_session_args_2 *argp,
101 shibrpc_get_session_ret_2 *result,
102 struct svc_req *rqstp
105 Category& log = get_category();
106 string ctx = get_threadid("get_session");
109 if (!argp || !result) {
110 log.error ("RPC Argument Error");
114 memset (result, 0, sizeof (*result));
115 result->provider_id = strdup("");
116 result->auth_statement = strdup("");
117 result->attr_response_pre = strdup("");
118 result->attr_response_post = strdup("");
120 log.debug ("checking: %s@%s", argp->cookie, argp->client_addr);
122 // See if the session exists...
124 IConfig* conf=ShibTargetConfig::getConfig().getINI();
126 log.debug ("application: %s", argp->application_id);
127 const IApplication* app=conf->getApplication(argp->application_id);
129 // Something's horribly wrong.
130 log.error("couldn't find application for session");
131 SAMLException ex("Unable to locate application for session, deleted?");
134 result->status=strdup(os.str().c_str());
138 bool checkIPAddress=true;
139 int lifetime=0,timeout=0;
140 const IPropertySet* props=app->getPropertySet("Sessions");
142 pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
145 p=props->getUnsignedInt("timeout");
148 pair<bool,bool> pcheck=props->getBool("checkAddress");
150 checkIPAddress = pcheck.second;
153 ISessionCacheEntry* entry = conf->getSessionCache()->find(argp->cookie,app);
155 // If not, leave now..
157 log.debug("session not found");
158 InvalidSessionException ex("No session exists for key value ($session_id)",namedparams(1,"session_id",argp->cookie));
161 result->status=strdup(os.str().c_str());
165 // TEST the session...
167 // Verify the address is the same
168 if (checkIPAddress) {
169 log.debug("Checking address against %s", entry->getClientAddress());
170 if (strcmp(argp->client_addr, entry->getClientAddress())) {
171 log.debug("client address mismatch");
172 InvalidSessionException ex(
173 SESSION_E_ADDRESSMISMATCH,
174 "Your IP address (%1) does not match the address recorded at the time the session was established.",
175 params(1,argp->client_addr)
177 Metadata m(app->getMetadataProviders());
178 annotateException(ex,m.lookup(entry->getProviderId())); // throws it
182 // and that the session is still valid...
183 if (!entry->isValid(lifetime,timeout)) {
184 log.debug("session expired");
185 InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
186 Metadata m(app->getMetadataProviders());
187 annotateException(ex,m.lookup(entry->getProviderId())); // throws it
190 // Set profile and provider
191 result->profile = entry->getProfile();
192 free(result->provider_id);
193 result->provider_id = strdup(entry->getProviderId());
195 // Now grab the serialized authentication statement and responses
197 os << *(entry->getAuthnStatement());
198 free(result->auth_statement);
199 result->auth_statement = strdup(os.str().c_str());
201 ISessionCacheEntry::CachedResponse responses=entry->getResponse();
202 if (!responses.empty()) {
204 os << *responses.unfiltered;
205 free(result->attr_response_pre);
206 result->attr_response_pre = strdup(os.str().c_str());
209 os << *responses.filtered;
210 free(result->attr_response_post);
211 result->attr_response_post = strdup(os.str().c_str());
214 catch (SAMLException &e) {
216 log.error("caught SAML exception: %s", e.what());
217 conf->getSessionCache()->remove(argp->cookie);
220 result->status = strdup(os.str().c_str());
222 // Transaction Logging
223 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
224 stc.getTransactionLog().infoStream() <<
225 "Destroyed invalid session (ID: " <<
227 ") with (applicationId: " <<
228 argp->application_id <<
229 "), request was from (ClientAddress: " <<
232 stc.releaseTransactionLog();
237 log.error("caught unknown exception");
238 InvalidSessionException ex("An unexpected error occurred while validating your session, and you must re-authenticate.");
239 Metadata m(app->getMetadataProviders());
240 annotateException(ex,m.lookup(entry->getProviderId()),false);
242 conf->getSessionCache()->remove(argp->cookie);
245 result->status = strdup(os.str().c_str());
247 // Transaction Logging
248 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
249 stc.getTransactionLog().infoStream() <<
250 "Destroyed invalid session (ID: " <<
252 ") with (applicationId: " <<
253 argp->application_id <<
254 "), request was from (ClientAddress: " <<
257 stc.releaseTransactionLog();
262 // Ok, just release it.
264 log.debug ("session ok");
265 result->status=strdup("");
270 shibrpc_new_session_2_svc(
271 shibrpc_new_session_args_2 *argp,
272 shibrpc_new_session_ret_2 *result,
273 struct svc_req *rqstp
276 Category& log = get_category();
277 string ctx=get_threadid("new_session");
280 if (!argp || !result) {
281 log.error ("Invalid RPC Arguments");
285 // Initialize the result structure
286 memset (result, 0, sizeof(*result));
287 result->cookie = strdup ("");
288 result->target = strdup ("");
290 log.debug ("creating session for %s", argp->client_addr);
291 log.debug ("recipient: %s", argp->recipient);
292 log.debug ("application: %s", argp->application_id);
294 auto_ptr_XMLCh recipient(argp->recipient);
296 // Access the application config.
297 IConfig* conf=ShibTargetConfig::getConfig().getINI();
299 const IApplication* app=conf->getApplication(argp->application_id);
301 // Something's horribly wrong. Flush the session.
302 log.error ("couldn't find application for session");
303 SAMLException ex("Unable to locate application for session, deleted?");
306 result->status=strdup(os.str().c_str());
310 bool checkIPAddress=true;
311 const IPropertySet* props=app->getPropertySet("Sessions");
313 pair<bool,bool> pcheck=props->getBool("checkAddress");
315 checkIPAddress = pcheck.second;
318 pair<bool,bool> checkReplay=pair<bool,bool>(false,false);
319 props=app->getPropertySet("Sessions");
321 checkReplay=props->getBool("checkReplay");
323 const IRoleDescriptor* role=NULL;
324 Metadata m(app->getMetadataProviders());
325 SAMLBrowserProfile::BrowserProfileResponse bpr;
327 auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
329 // Try and run the profile.
330 log.debug("executing browser profile...");
332 if (argp->supported_profiles & SAML11_POST)
333 allowed |= SAMLBrowserProfile::Post;
334 if (argp->supported_profiles & SAML11_ARTIFACT)
335 allowed |= SAMLBrowserProfile::Artifact;
336 bpr=app->getBrowserProfile()->receive(
340 (!checkReplay.first || checkReplay.second) ? conf->getReplayCache() : NULL,
344 // Blow it away to clear any locks that might be held.
345 delete artifactMapper.release();
347 // Try and map to metadata (again).
348 // Once the metadata layer is in the SAML core, the repetition should be fixed.
349 const IEntityDescriptor* provider=m.lookup(bpr.assertion->getIssuer());
350 if (!provider && bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier())
351 provider=m.lookup(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
353 const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
357 // This isn't likely, since the profile must have found a role.
359 MetadataException ex("Unable to locate role-specific metadata for identity provider.");
360 annotateException(ex,provider); // throws it
363 // Maybe verify the origin address....
364 if (checkIPAddress) {
365 log.debug("verify client address");
366 // Verify the client address exists
367 const XMLCh* ip = bpr.authnStatement->getSubjectIP();
369 // Verify the client address matches authentication
370 auto_ptr_char this_ip(ip);
371 if (strcmp(argp->client_addr, this_ip.get())) {
372 FatalProfileException ex(
373 SESSION_E_ADDRESSMISMATCH,
374 "Your client's current address ($1) differs from the one used when you authenticated "
375 "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
376 "Please contact your local support staff or help desk for assistance.",
377 params(1,argp->client_addr)
379 annotateException(ex,role); // throws it
384 // Verify condition(s) on authentication assertion.
385 // Attribute assertions get filtered later, essentially just like an AAP.
386 Iterator<SAMLCondition*> conditions=bpr.assertion->getConditions();
387 while (conditions.hasNext()) {
388 SAMLCondition* cond=conditions.next();
389 const SAMLAudienceRestrictionCondition* ac=dynamic_cast<const SAMLAudienceRestrictionCondition*>(cond);
393 log.error("Unrecognized Condition in authentication assertion (%s), tossing it.",os.str().c_str());
394 FatalProfileException ex("Unable to create session due to unrecognized condition in authentication assertion.");
395 annotateException(ex,role); // throws it
397 else if (!ac->eval(app->getAudiences())) {
400 log.error("Unacceptable AudienceRestrictionCondition in authentication assertion (%s), tossing it.",os.str().c_str());
401 FatalProfileException ex("Unable to create session due to unacceptable AudienceRestrictionCondition in authentication assertion.");
402 annotateException(ex,role); // throws it
406 catch (SAMLException& e) {
408 log.error("caught SAML exception: %s", e.what());
411 result->status = strdup(os.str().c_str());
416 log.error("unknown error");
418 SAMLException e("An unexpected error occurred while creating your session.");
419 annotateException(e,role,false);
422 result->status = strdup(os.str().c_str());
427 // It passes all our tests -- create a new session.
428 log.info("creating new session");
430 // Create a new session key.
431 string cookie = conf->getSessionCache()->generateKey();
433 // Are attributes present?
434 bool attributesPushed=false;
435 Iterator<SAMLAssertion*> assertions=bpr.response->getAssertions();
436 while (!attributesPushed && assertions.hasNext()) {
437 Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
438 while (!attributesPushed && statements.hasNext()) {
439 if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
440 attributesPushed=true;
444 auto_ptr_char oname(role->getEntityDescriptor()->getId());
445 auto_ptr_char hname(bpr.authnStatement->getSubject()->getNameIdentifier()->getName());
448 // Insert into cache.
449 auto_ptr<SAMLAuthenticationStatement> as(static_cast<SAMLAuthenticationStatement*>(bpr.authnStatement->clone()));
450 conf->getSessionCache()->insert(
454 (bpr.profile==SAMLBrowserProfile::Post) ? SAML11_POST : SAML11_ARTIFACT,
457 (attributesPushed ? bpr.response : NULL),
460 as.release(); // owned by cache now
462 catch (SAMLException& e) {
464 log.error("caught SAML exception: %s", e.what());
467 result->status = strdup(os.str().c_str());
472 log.error("unknown error");
474 SAMLException e("An unexpected error occurred while creating your session.");
475 annotateException(e,role,false);
478 result->status = strdup(os.str().c_str());
483 // And let the user know.
484 if (result->cookie) free(result->cookie);
485 if (result->target) free(result->target);
486 result->cookie = strdup(cookie.c_str());
487 result->target = strdup(bpr.TARGET.c_str());
488 result->status = strdup("");
490 // Maybe delete the response...
491 if (!attributesPushed)
494 log.debug("new session id: %s", cookie.c_str());
496 // Transaction Logging
497 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
498 stc.getTransactionLog().infoStream() <<
499 "New session (ID: " <<
501 ") with (applicationId: " <<
502 argp->application_id <<
503 ") for principal from (IdP: " <<
505 ") at (ClientAddress: " <<
507 ") with (NameIdentifier: " <<
511 stc.releaseTransactionLog();
516 shibrpc_prog_2_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
518 xdr_free (xdr_result, result);
521 * Insert additional freeing code here, if needed