3b0b08cbbfa2ddf9e33f088769959f4822366928
[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_1_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 /*
125 void set_rpc_status_x(ShibRpcError *error, ShibRpcStatus status,
126                         const char* msg=NULL, const XMLCh* origin=NULL)
127 {
128   if (!status) {
129     set_rpc_status(error, status);
130     return;
131   }
132   auto_ptr_char orig(origin);
133   set_rpc_status(error, status, msg, orig.get());
134 }
135 */
136
137 extern "C" bool_t
138 shibrpc_session_is_valid_1_svc(shibrpc_session_is_valid_args_1 *argp,
139                                shibrpc_session_is_valid_ret_1 *result,
140                                struct svc_req *rqstp)
141 {
142   Category& log = get_category();
143   string ctx = get_threadid("session_is_valid");
144   saml::NDC ndc(ctx);
145
146   if (!argp || !result) {
147     log.error ("RPC Argument Error");
148     return FALSE;
149   }
150
151   memset (result, 0, sizeof (*result));
152   
153   log.debug ("checking: %s@%s (checkAddr=%s)",
154              argp->cookie.cookie, argp->cookie.client_addr,
155              argp->checkIPAddress ? "true" : "false");
156
157   // See if the cookie exists...
158   IConfig* conf=ShibTargetConfig::getConfig().getINI();
159   Locker locker(conf);
160   log.debug ("application: %s", argp->application_id);
161   const IApplication* app=conf->getApplication(argp->application_id);
162   if (!app) {
163     // Something's horribly wrong.
164     log.error("couldn't find application for session");
165     set_rpc_status(&result->status, SHIBRPC_UNKNOWN_ERROR, "Unable to locate application for session, deleted?");
166     return TRUE;
167   }
168
169   ISessionCacheEntry* entry = conf->getSessionCache()->find(argp->cookie.cookie,app);
170
171   // If not, leave now..
172   if (!entry) {
173     log.debug ("Not found");
174     set_rpc_status(&result->status, SHIBRPC_NO_SESSION, "No session exists for this cookie");
175     return TRUE;
176   }
177
178   // TEST the session...
179   try {
180     Metadata m(app->getMetadataProviders());
181     const IEntityDescriptor* origin=m.lookup(entry->getStatement()->getSubject()->getNameIdentifier()->getNameQualifier());
182
183     // Verify the address is the same
184     if (argp->checkIPAddress) {
185       log.debug ("Checking address against %s", entry->getClientAddress());
186       if (strcmp (argp->cookie.client_addr, entry->getClientAddress())) {
187         log.debug ("IP Address mismatch");
188         throw ShibTargetException(SHIBRPC_IPADDR_MISMATCH,
189             "Your IP address does not match the address in the original authentication.", origin);
190       }
191     }
192
193     // and that the session is still valid...
194     if (!entry->isValid(argp->lifetime, argp->timeout)) {
195       log.debug ("Session expired");
196       throw ShibTargetException(SHIBRPC_SESSION_EXPIRED, "Your session has expired, must re-authenticate.", origin);
197     }
198
199     // and now try to prefetch the attributes .. this could cause an
200     // "error", which is why we call it here.
201     try {
202       entry->preFetch(15);      // give a 15-second window for the RM
203     }
204     catch (SAMLException &e) {
205       log.debug ("prefetch failed with a SAML Exception: %s", e.what());
206       ostringstream os;
207       os << e;
208       throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str(), origin);
209     }
210     catch (ShibTargetException&) {
211       // These are caught and handled down below.
212       throw;
213     }
214 #ifndef _DEBUG
215     catch (...) {
216       log.error ("prefetch caught an unknown exception");
217       throw ShibTargetException(SHIBRPC_UNKNOWN_ERROR,
218             "An unknown error occured while pre-fetching attributes.", origin);
219     }
220 #endif
221   }
222   catch (ShibTargetException &e) {
223     entry->unlock();
224     conf->getSessionCache()->remove(argp->cookie.cookie);
225     set_rpc_status(&result->status, e);
226     // Transaction Logging
227     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
228     stc.getTransactionLog().infoStream() <<
229         "Destroyed invalid session (ID: " <<
230             argp->cookie.cookie <<
231         ") with (applicationId: " <<
232             argp->application_id <<
233         "), request was from (ClientAddress: " <<
234             argp->cookie.client_addr <<
235         ")";
236     stc.releaseTransactionLog();
237     return TRUE;
238   }
239
240   // Ok, just release it.
241   entry->unlock();
242
243   // ok, we've succeeded..
244   set_rpc_status(&result->status, SHIBRPC_OK);
245   log.debug ("session ok");
246   return TRUE;
247 }
248
249 extern "C" bool_t
250 shibrpc_new_session_1_svc(shibrpc_new_session_args_1 *argp,
251                           shibrpc_new_session_ret_1 *result, struct svc_req *rqstp)
252 {
253   Category& log = get_category();
254   string ctx=get_threadid("new_session");
255   saml::NDC ndc(ctx);
256
257   if (!argp || !result) {
258     log.error ("Invalid RPC Arguments");
259     return FALSE;
260   }
261
262   // Initialize the result structure
263   memset (result, 0, sizeof(*result));
264   result->cookie = strdup ("");
265
266   log.debug ("creating session for %s", argp->client_addr);
267   log.debug ("shire location: %s", argp->shire_location);
268   log.debug ("application: %s", argp->application_id);
269
270   XMLByte* post=reinterpret_cast<XMLByte*>(argp->saml_post);
271   auto_ptr_XMLCh location(argp->shire_location);
272
273   SAMLResponse* r = NULL;
274   const SAMLAuthenticationStatement* auth_st = NULL;
275   XMLCh* origin = NULL;
276  
277   // Access the application config.
278   IConfig* conf=ShibTargetConfig::getConfig().getINI();
279   Locker locker(conf);
280   const IApplication* app=conf->getApplication(argp->application_id);
281   if (!app) {
282       // Something's horribly wrong. Flush the session.
283       log.error ("couldn't find application for session");
284       set_rpc_status(&result->status, SHIBRPC_INTERNAL_ERROR, "Unable to locate application for session, deleted?");
285       return TRUE;
286   }
287
288   pair<bool,bool> checkReplay=pair<bool,bool>(false,false);
289   const IPropertySet* props=app->getPropertySet("Sessions");
290   if (props)
291       checkReplay=props->getBool("checkReplay");
292  
293   const IRoleDescriptor* role=NULL;
294   Metadata m(app->getMetadataProviders());
295   try
296   {
297     if (!app)
298         // Something's horribly wrong.
299         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate application configuration, deleted?");
300       
301     // And build the POST profile wrapper.
302     log.debug("create the POST profile");
303     ShibPOSTProfile profile(app->getMetadataProviders(),app->getRevocationProviders(),app->getTrustProviders());
304     
305     try
306     {
307       // Try and accept the response...
308       log.debug ("Trying to accept the post");
309       r = profile.accept(post,location.get(),300,app->getAudiences(),&origin);
310
311       // Try and map to metadata for support purposes.
312       const IEntityDescriptor* provider=m.lookup(origin);
313       if (provider) {
314           const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
315           role=IDP;
316       }
317       // This can't really happen, since the profile must have found a role.
318       if (!role)
319         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,
320             "Unable to locate role-specific metadata for identity provider", provider);
321     
322       // Make sure we got a response
323       if (!r)
324         throw ShibTargetException(SHIBRPC_RESPONSE_MISSING, "Failed to accept the response.", role);
325
326       // Find the SSO Assertion
327       log.debug ("Get the SSOAssertion");
328       const SAMLAssertion* ssoAssertion = profile.getSSOAssertion(*r,app->getAudiences());
329
330       // Check against the replay cache?
331       if (checkReplay.first && !checkReplay.second)
332         log.warn("replay checking is off, this is a security risk unless you're testing");
333       else {
334         log.debug ("check replay cache");
335         if (!profile.checkReplayCache(*ssoAssertion))
336             throw ShibTargetException(SHIBRPC_ASSERTION_REPLAYED, "Duplicate assertion detected.", role);
337       }
338
339       // Get the authentication statement we need.
340       log.debug ("get SSOStatement");
341       auth_st = profile.getSSOStatement(*ssoAssertion);
342
343       // Maybe verify the origin address....
344       if (argp->checkIPAddress) {
345         log.debug ("check IP Address");
346
347         // Verify the client address exists
348         const XMLCh* ip = auth_st->getSubjectIP();
349         if (ip && *ip) {
350             log.debug ("verify client address");
351
352             // Verify the client address matches authentication
353             auto_ptr_char this_ip(ip);
354             if (strcmp(argp->client_addr, this_ip.get()))
355                 throw ShibTargetException(SHIBRPC_IPADDR_MISMATCH,
356                         "Your client's current IP address differs from the one used when you authenticated "
357                     "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
358                     "Please contact your local support staff or help desk for assistance.",
359                                      role);
360         }
361       }
362     }
363     catch (SAMLException &e)
364     {
365       log.error ("caught SAML exception: %s", e.what());
366       ostringstream os;
367       os << e;
368       throw ShibTargetException (SHIBRPC_SAML_EXCEPTION, os.str().c_str(), role);
369     }
370     catch (XMLException &e)
371     {
372       log.error ("received XML exception");
373       auto_ptr_char msg(e.getMessage());
374       throw ShibTargetException (SHIBRPC_XML_EXCEPTION, msg.get(), role);
375     }
376   }
377   catch (ShibTargetException &e) {
378     log.info ("FAILED: %s", e.what());
379     delete r;
380     if (origin) XMLString::release(&origin);
381     set_rpc_status(&result->status, e);
382     return TRUE;
383   }
384 #ifndef _DEBUG
385   catch (...) {
386     log.error ("Unknown error");
387     delete r;
388     if (origin) XMLString::release(&origin);
389     set_rpc_status(&result->status, SHIBRPC_UNKNOWN_ERROR, "An unknown exception occurred");
390     return TRUE;
391   }
392 #endif
393
394   // It passes all our tests -- create a new session.
395   log.info ("Creating new session");
396
397   SAMLAuthenticationStatement* as=static_cast<SAMLAuthenticationStatement*>(auth_st->clone());
398
399   // Create a new cookie
400   string cookie = conf->getSessionCache()->generateKey();
401
402   // Cache this session, possibly including response if attributes appear present.
403   bool attributesPushed=false;
404   Iterator<SAMLAssertion*> assertions=r->getAssertions();
405   while (!attributesPushed && assertions.hasNext()) {
406       Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
407       while (!attributesPushed && statements.hasNext()) {
408           if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
409             attributesPushed=true;
410       }
411   }
412   conf->getSessionCache()->insert(cookie.c_str(), app, as, argp->client_addr, (attributesPushed ? r : NULL), role);
413   
414   // Maybe delete the response...
415   if (!attributesPushed)
416     delete r;
417
418   // And let the user know.
419   if (result->cookie) free(result->cookie);
420   result->cookie = strdup(cookie.c_str());
421   set_rpc_status(&result->status, SHIBRPC_OK);
422
423   log.debug("new session id: %s", cookie.c_str());
424   
425   // Transaction Logging
426   STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
427   auto_ptr_char oname(origin);
428   auto_ptr_char hname(as->getSubject()->getNameIdentifier()->getName());
429   stc.getTransactionLog().infoStream() <<
430     "New session (ID: " <<
431         result->cookie <<
432     ") with (applicationId: " <<
433         argp->application_id <<
434     ") for principal from (IdP: " <<
435         oname.get() <<
436     ") at (ClientAddress: " <<
437         argp->client_addr <<
438     ") with (NameIdentifier: " <<
439         hname.get() <<
440     ")";
441
442   stc.releaseTransactionLog();
443
444   // Delete the origin...
445   if (origin) XMLString::release(&origin);
446
447   return TRUE;
448 }
449
450 extern "C" bool_t
451 shibrpc_get_assertions_1_svc(shibrpc_get_assertions_args_1 *argp,
452                         shibrpc_get_assertions_ret_1 *result, struct svc_req *rqstp)
453 {
454   Category& log = get_category();
455   string ctx = get_threadid("get_assertions");
456   saml::NDC ndc(ctx);
457
458   if (!argp || !result) {
459     log.error ("Invalid RPC arguments");
460     return FALSE;
461   }
462
463   memset (result, 0, sizeof (*result));
464   result->auth_statement.xml_string = strdup("");
465
466   log.debug ("get attrs for client at %s", argp->cookie.client_addr);
467   log.debug ("cookie: %s", argp->cookie.cookie);
468   log.debug ("application: %s", argp->application_id);
469
470   // Find this session
471   IConfig* conf=ShibTargetConfig::getConfig().getINI();
472   Locker locker(conf);
473
474   // Try and locate support metadata for errors we throw.
475   log.debug ("application: %s", argp->application_id);
476   const IApplication* app=conf->getApplication(argp->application_id);
477   if (!app) {
478       // Something's horribly wrong.
479       log.error("couldn't find application for session");
480       set_rpc_status(&result->status, SHIBRPC_INTERNAL_ERROR, "Unable to locate application for session, deleted?");
481       return TRUE;
482   }
483
484   ISessionCacheEntry* entry = conf->getSessionCache()->find(argp->cookie.cookie,app);
485
486   // If it does not exist, leave now..
487   if (!entry) {
488     log.error ("No Session");
489     set_rpc_status(&result->status, SHIBRPC_NO_SESSION, "getattrs Internal error: no session");
490     return TRUE;
491   }
492
493   Metadata m(app->getMetadataProviders());
494   const IEntityDescriptor* origin=m.lookup(entry->getStatement()->getSubject()->getNameIdentifier()->getNameQualifier());
495
496   try {
497     try {
498       // Validate the client address (again?)
499       if (argp->checkIPAddress && strcmp (argp->cookie.client_addr, entry->getClientAddress())) {
500         entry->unlock();
501         log.error("IP Mismatch");
502         throw ShibTargetException(SHIBRPC_IPADDR_MISMATCH,
503             "Your IP address does not match the address in the original authentication.", origin);
504       }
505
506       // grab the attributes for this resource
507       Iterator<SAMLAssertion*> iter = entry->getAssertions();
508       u_int size = iter.size();
509
510       // if we have assertions...
511       if (size) {
512
513         // Build the response section
514         ShibRpcXML* av = (ShibRpcXML*) malloc (size * sizeof (ShibRpcXML));
515
516         // and then serialize them all...
517         u_int i = 0;
518         while (iter.hasNext()) {
519           SAMLAssertion* as = iter.next();
520           ostringstream os;
521           os << *as;
522           av[i++].xml_string = strdup(os.str().c_str());
523         }
524
525         // Set the results, once we know we've succeeded.
526         result->assertions.assertions_len = size;
527         result->assertions.assertions_val = av;
528       }
529     }
530     catch (SAMLException &e) {
531       entry->unlock();
532       log.error ("caught SAML exception: %s", e.what());
533       ostringstream os;
534       os << e;
535       throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str(), origin);
536     }
537   }
538   catch (ShibTargetException &e) {
539     entry->unlock();
540     set_rpc_status(&result->status, e);
541     return TRUE;
542   }
543 #ifndef _DEBUG
544   catch (...) {
545     entry->unlock();
546     log.error ("caught an unknown exception");
547     throw ShibTargetException(SHIBRPC_UNKNOWN_ERROR,
548           "An unexpected error occured while fetching attributes.", origin);
549   }
550 #endif
551
552
553   // Now grab the serialized authentication statement
554   free(result->auth_statement.xml_string);
555   result->auth_statement.xml_string = strdup(entry->getSerializedStatement());
556  
557   entry->unlock();
558
559   // and let it fly
560   set_rpc_status(&result->status, SHIBRPC_OK);
561
562   log.debug ("returning");
563   return TRUE;
564 }
565
566 extern "C" int
567 shibrpc_prog_1_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
568 {
569         xdr_free (xdr_result, result);
570
571         /*
572          * Insert additional freeing code here, if needed
573          */
574
575         return 1;
576 }