Clean up error handling during cache insertion
[shibboleth/sp.git] / shib-target / shibrpc-server.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
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.
22  *
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
28  *
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.
33  *
34  *
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.
48  */
49
50 /*
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.
54  *
55  * Created by:  Derek Atkins <derek@ihtfp.com>
56  *
57  * $Id$
58  */
59
60 #include "internal.h"
61
62 #include "shibrpc.h"
63
64 #include <sstream>
65
66 #ifdef HAVE_LIBDMALLOCXX
67 #include <dmalloc.h>
68 #endif
69
70 #include <log4cpp/Category.hh>
71
72 using namespace std;
73 using namespace log4cpp;
74 using namespace saml;
75 using namespace shibboleth;
76 using namespace shibtarget;
77
78 static string get_threadid (const char* proc)
79 {
80   static u_long counter = 0;
81   ostringstream buf;
82   buf << "[" << counter++ << "] " << proc;
83   return buf.str();
84 }
85
86 static Category& get_category (void)
87 {
88   return Category::getInstance("shibtarget.rpc-server");
89 }
90
91 extern "C" bool_t
92 shibrpc_ping_2_svc(int *argp, int *result, struct svc_req *rqstp)
93 {
94   *result = (*argp)+1;
95   return TRUE;
96 }
97
98 // Functions to map errors into IDL-defined status structure
99
100 void set_rpc_status(ShibRpcError *error, ShibRpcStatus status, const char* msg=NULL)
101 {
102   error->status = status;
103   if (status) {
104     error->ShibRpcError_u.e.error = strdup(msg ? msg : "");
105     error->ShibRpcError_u.e.provider = strdup("");
106     error->ShibRpcError_u.e.url = strdup("");
107     error->ShibRpcError_u.e.contact = strdup("");
108     error->ShibRpcError_u.e.email = strdup("");
109   }
110 }
111
112 void set_rpc_status(ShibRpcError *error, ShibTargetException& exc)
113 {
114   error->status = exc.which();
115   if (error->status) {
116     error->ShibRpcError_u.e.error = strdup(exc.what() ? exc.what() : "");
117     error->ShibRpcError_u.e.provider = strdup(exc.syswho() ? exc.syswho() : "");
118     error->ShibRpcError_u.e.url = strdup(exc.where() ? exc.where() : "");
119     error->ShibRpcError_u.e.contact = strdup(exc.who() ? exc.who() : "");
120     error->ShibRpcError_u.e.email = strdup(exc.how() ? exc.how() : "");
121   }
122 }
123
124 extern "C" bool_t
125 shibrpc_get_session_2_svc(
126     shibrpc_get_session_args_2 *argp,
127     shibrpc_get_session_ret_2 *result,
128     struct svc_req *rqstp
129     )
130 {
131   Category& log = get_category();
132   string ctx = get_threadid("session_is_valid");
133   saml::NDC ndc(ctx);
134
135   if (!argp || !result) {
136     log.error ("RPC Argument Error");
137     return FALSE;
138   }
139
140   memset (result, 0, sizeof (*result));
141   result->auth_statement.xml_string = strdup("");
142   
143   log.debug ("checking: %s@%s (checkAddr=%s)",
144              argp->cookie, argp->client_addr, argp->checkIPAddress ? "true" : "false");
145
146   // See if the session exists...
147   
148   IConfig* conf=ShibTargetConfig::getConfig().getINI();
149   Locker locker(conf);
150   log.debug ("application: %s", argp->application_id);
151   const IApplication* app=conf->getApplication(argp->application_id);
152   if (!app) {
153     // Something's horribly wrong.
154     log.error("couldn't find application for session");
155     set_rpc_status(&result->status, SHIBRPC_UNKNOWN_ERROR, "Unable to locate application for session, deleted?");
156     return TRUE;
157   }
158
159   ISessionCacheEntry* entry = conf->getSessionCache()->find(argp->cookie,app);
160
161   // If not, leave now..
162   if (!entry) {
163     log.debug ("Not found");
164     set_rpc_status(&result->status, SHIBRPC_NO_SESSION, "No session exists for this key value");
165     return TRUE;
166   }
167
168   // TEST the session...
169   try {
170     Metadata m(app->getMetadataProviders());
171     const IEntityDescriptor* origin=m.lookup(entry->getAuthnStatement()->getSubject()->getNameIdentifier()->getNameQualifier());
172
173     // Verify the address is the same
174     if (argp->checkIPAddress) {
175       log.debug ("Checking address against %s", entry->getClientAddress());
176       if (strcmp (argp->client_addr, entry->getClientAddress())) {
177         log.debug ("IP Address mismatch");
178         throw ShibTargetException(SHIBRPC_IPADDR_MISMATCH,
179             "Your IP address does not match the address recorded at the time the session was established.", origin);
180       }
181     }
182
183     // and that the session is still valid...
184     if (!entry->isValid(argp->lifetime, argp->timeout)) {
185       log.debug ("Session expired");
186       throw ShibTargetException(SHIBRPC_SESSION_EXPIRED, "Your session has expired, and you must re-authenticate.", origin);
187     }
188
189     try {
190       // Now grab the serialized authentication statement
191       ostringstream os;
192       os << *(entry->getAuthnStatement());
193       free(result->auth_statement.xml_string);
194       result->auth_statement.xml_string = strdup(os.str().c_str());
195      
196       // grab the attributes for this session
197       Iterator<SAMLAssertion*> iter = entry->getAssertions();
198       u_int size = iter.size();
199     
200       // if we have assertions...
201       if (size) {
202           // Build the response section
203           ShibRpcXML* av = (ShibRpcXML*) malloc (size * sizeof (ShibRpcXML));
204     
205           // and then serialize them all...
206           u_int i = 0;
207           while (iter.hasNext()) {
208             SAMLAssertion* as = iter.next();
209             ostringstream os2;
210             os2 << *as;
211             av[i++].xml_string = strdup(os2.str().c_str());
212           }
213     
214           // Set the results, once we know we've succeeded.
215           result->assertions.assertions_len = size;
216           result->assertions.assertions_val = av;
217       }
218     }
219     catch (SAMLException &e) {
220       log.error ("caught SAML exception: %s", e.what());
221       ostringstream os;
222       os << e;
223       throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str(), origin);
224     }
225   }
226   catch (ShibTargetException &e) {
227       entry->unlock();
228       log.error ("FAILED: %s", e.what());
229       conf->getSessionCache()->remove(argp->cookie);
230       set_rpc_status(&result->status, e);
231       // Transaction Logging
232       STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
233       stc.getTransactionLog().infoStream() <<
234           "Destroyed invalid session (ID: " <<
235               argp->cookie <<
236           ") with (applicationId: " <<
237               argp->application_id <<
238           "), request was from (ClientAddress: " <<
239               argp->client_addr <<
240           ")";
241       stc.releaseTransactionLog();
242       return TRUE;
243   }
244 #ifndef _DEBUG
245   catch (...) {
246       entry->unlock();
247       log.error ("Unknown exception");
248       conf->getSessionCache()->remove(argp->cookie);
249       set_rpc_status(&result->status, SHIBRPC_UNKNOWN_ERROR, "An unknown exception occurred");
250       // Transaction Logging
251       STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
252       stc.getTransactionLog().infoStream() <<
253           "Destroyed invalid session (ID: " <<
254               argp->cookie <<
255           ") with (applicationId: " <<
256               argp->application_id <<
257           "), request was from (ClientAddress: " <<
258               argp->client_addr <<
259           ")";
260       stc.releaseTransactionLog();
261       return TRUE;
262   }
263 #endif
264
265   // Ok, just release it.
266   entry->unlock();
267
268   // ok, we've succeeded..
269   set_rpc_status(&result->status, SHIBRPC_OK);
270   log.debug ("session ok");
271   return TRUE;
272 }
273
274 extern "C" bool_t
275 shibrpc_new_session_2_svc(
276     shibrpc_new_session_args_2 *argp,
277     shibrpc_new_session_ret_2 *result,
278     struct svc_req *rqstp
279     )
280 {
281   Category& log = get_category();
282   string ctx=get_threadid("new_session");
283   saml::NDC ndc(ctx);
284
285   if (!argp || !result) {
286     log.error ("Invalid RPC Arguments");
287     return FALSE;
288   }
289
290   // Initialize the result structure
291   memset (result, 0, sizeof(*result));
292   result->cookie = strdup ("");
293   result->target = strdup ("");
294
295   log.debug ("creating session for %s", argp->client_addr);
296   log.debug ("recipient: %s", argp->recipient);
297   log.debug ("application: %s", argp->application_id);
298
299   auto_ptr_XMLCh recipient(argp->recipient);
300
301   SAMLResponse* r = NULL;
302   const SAMLAuthenticationStatement* auth_st = NULL;
303   XMLCh* origin = NULL;
304  
305   // Access the application config.
306   IConfig* conf=ShibTargetConfig::getConfig().getINI();
307   Locker locker(conf);
308   const IApplication* app=conf->getApplication(argp->application_id);
309   if (!app) {
310       // Something's horribly wrong. Flush the session.
311       log.error ("couldn't find application for session");
312       set_rpc_status(&result->status, SHIBRPC_INTERNAL_ERROR, "Unable to locate application for session, deleted?");
313       return TRUE;
314   }
315
316   // TODO: Sub in call to getReplayCache() as the determinant.
317   // For now, we always have a cache and use the flag...
318   pair<bool,bool> checkReplay=pair<bool,bool>(false,false);
319   const IPropertySet* props=app->getPropertySet("Sessions");
320   if (props)
321       checkReplay=props->getBool("checkReplay");
322  
323   const IRoleDescriptor* role=NULL;
324   Metadata m(app->getMetadataProviders());
325   SAMLBrowserProfile::BrowserProfileResponse bpr;
326   try
327   {
328     if (!app)
329         // Something's horribly wrong.
330         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate application configuration, deleted?");
331       
332     try
333     {
334       auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
335       
336       // Try and run the profile.
337       log.debug ("Executing browser profile...");
338       bpr=app->getBrowserProfile()->receive(
339         &origin,
340         argp->packet,
341         recipient.get(),
342         SAMLBrowserProfile::Post,   // For now, we only handle POST.
343         (!checkReplay.first || checkReplay.second) ? conf->getReplayCache() : NULL,
344         artifactMapper.get()
345         );
346
347       // Try and map to metadata for support purposes.
348       const IEntityDescriptor* provider=m.lookup(origin);
349       if (provider) {
350           const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
351           role=IDP;
352       }
353       // This can't really happen, since the profile must have found a role.
354       if (!role)
355         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,
356             "Unable to locate role-specific metadata for identity provider", provider);
357     
358       // Maybe verify the origin address....
359       if (argp->checkIPAddress) {
360         log.debug ("verify client address");
361
362         // Verify the client address exists
363         const XMLCh* ip = bpr.authnStatement->getSubjectIP();
364         if (ip && *ip) {
365             // Verify the client address matches authentication
366             auto_ptr_char this_ip(ip);
367             if (strcmp(argp->client_addr, this_ip.get()))
368                 throw ShibTargetException(SHIBRPC_IPADDR_MISMATCH,
369                         "Your client's current IP address differs from the one used when you authenticated "
370                     "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
371                     "Please contact your local support staff or help desk for assistance.",
372                                      role);
373         }
374       }
375       
376       // Verify condition(s) on authentication assertion.
377       // Attribute assertions get filtered later, essentially just like an AAP.
378       Iterator<SAMLCondition*> conditions=bpr.assertion->getConditions();
379       while (conditions.hasNext()) {
380         SAMLCondition* cond=conditions.next();
381         const SAMLAudienceRestrictionCondition* ac=dynamic_cast<const SAMLAudienceRestrictionCondition*>(cond);
382         if (!ac) {
383             ostringstream os;
384             os << *cond;
385             log.error("Unrecognized Condition in authentication assertion (%s), tossing it.",os.str().c_str());
386             throw FatalProfileException("Unable to start session due to unrecognized condition in authentication assertion.");
387         }
388         else if (!ac->eval(app->getAudiences())) {
389             ostringstream os;
390             os << *ac;
391             log.error("Unacceptable AudienceRestrictionCondition in authentication assertion (%s), tossing it.",os.str().c_str());
392             throw FatalProfileException("Unable to start session due to unacceptable AudienceRestrictionCondition in authentication assertion.");
393         }
394       }
395     }
396     catch (ReplayedAssertionException& e) {
397       // Specific case where we have an error code.
398       if (!role) {
399           // Try and map to metadata for support purposes.
400           const IEntityDescriptor* provider=m.lookup(origin);
401           if (provider) {
402               const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
403               role=IDP;
404           }
405       }
406       throw ShibTargetException(SHIBRPC_ASSERTION_REPLAYED, e.what(), role);
407     }
408     catch (SAMLException& e) {
409       log.error ("caught SAML exception: %s", e.what());
410       ostringstream os;
411       os << e;
412       if (!role) {
413           // Try and map to metadata for support purposes.
414           const IEntityDescriptor* provider=m.lookup(origin);
415           if (provider) {
416               const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
417               role=IDP;
418           }
419       }
420       throw ShibTargetException (SHIBRPC_SAML_EXCEPTION, os.str().c_str(), role);
421     }
422   }
423   catch (ShibTargetException& e) {
424     log.error ("FAILED: %s", e.what());
425     bpr.clear();
426     if (origin) XMLString::release(&origin);
427     set_rpc_status(&result->status, e);
428     return TRUE;
429   }
430 #ifndef _DEBUG
431   catch (...) {
432     log.error ("Unknown error");
433     bpr.clear();
434     if (origin) XMLString::release(&origin);
435     set_rpc_status(&result->status, SHIBRPC_UNKNOWN_ERROR, "An unknown exception occurred");
436     return TRUE;
437   }
438 #endif
439
440   // It passes all our tests -- create a new session.
441   log.info ("Creating new session");
442
443   // Create a new session key.
444   string cookie = conf->getSessionCache()->generateKey();
445
446   // Are attributes present?
447   bool attributesPushed=false;
448   Iterator<SAMLAssertion*> assertions=bpr.response->getAssertions();
449   while (!attributesPushed && assertions.hasNext()) {
450       Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
451       while (!attributesPushed && statements.hasNext()) {
452           if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
453             attributesPushed=true;
454       }
455   }
456   
457   // Insertion into cache might fail.
458   SAMLAuthenticationStatement* as=NULL;
459   try {
460       as=static_cast<SAMLAuthenticationStatement*>(bpr.authnStatement->clone());
461       // TODO: we need to extract the Issuer and propagate that around as the origin site along
462       // with the statement and attribute assertions.
463       conf->getSessionCache()->insert(
464         cookie.c_str(),
465         app,
466         as,
467         argp->client_addr,
468         (attributesPushed ? bpr.response : NULL),
469         role
470         );
471   }
472   catch (SAMLException& e) {
473       log.error ("caught SAML exception during cache insertion: %s", e.what());
474       delete as;
475       ostringstream os;
476       os << e;
477       bpr.clear();
478       if (origin) XMLString::release(&origin);
479       ShibTargetException ex(SHIBRPC_SAML_EXCEPTION, os.str().c_str(), role);
480       set_rpc_status(&result->status, ex);
481       return TRUE;
482   }
483 #ifndef _DEBUG
484   catch (...) {
485       log.error ("caught unknown exception during cache insertion");
486       delete as;
487       bpr.clear();
488       if (origin) XMLString::release(&origin);
489       set_rpc_status(&result->status, SHIBRPC_UNKNOWN_ERROR, "An unknown exception occurred");
490       return TRUE;
491   }
492 #endif
493     
494   // And let the user know.
495   if (result->cookie) free(result->cookie);
496   if (result->target) free(result->target);
497   result->cookie = strdup(cookie.c_str());
498   result->target = strdup(bpr.TARGET.c_str());
499   set_rpc_status(&result->status, SHIBRPC_OK);
500
501   // Maybe delete the response...
502   if (!attributesPushed)
503     bpr.clear();
504
505   log.debug("new session id: %s", cookie.c_str());
506   
507   // Transaction Logging
508   STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
509   auto_ptr_char oname(origin);
510   auto_ptr_char hname(as->getSubject()->getNameIdentifier()->getName());
511   stc.getTransactionLog().infoStream() <<
512     "New session (ID: " <<
513         result->cookie <<
514     ") with (applicationId: " <<
515         argp->application_id <<
516     ") for principal from (IdP: " <<
517         oname.get() <<
518     ") at (ClientAddress: " <<
519         argp->client_addr <<
520     ") with (NameIdentifier: " <<
521         hname.get() <<
522     ")";
523
524   stc.releaseTransactionLog();
525
526   // Delete the origin...
527   if (origin) XMLString::release(&origin);
528
529   return TRUE;
530 }
531
532 extern "C" int
533 shibrpc_prog_2_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
534 {
535         xdr_free (xdr_result, result);
536
537         /*
538          * Insert additional freeing code here, if needed
539          */
540
541         return 1;
542 }