Reorganized preprocessor declarations for Windows.
[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 static string get_threadid (const char* proc)
71 {
72   static u_long counter = 0;
73   ostringstream buf;
74   buf << "[" << counter++ << "] " << proc;
75   return buf.str();
76 }
77
78 static Category& get_category (void)
79 {
80   string ctx = "shibtarget.rpc-server";
81   return Category::getInstance(ctx);
82 }
83
84 extern "C" bool_t
85 shibrpc_ping_1_svc(int *argp, int *result, struct svc_req *rqstp)
86 {
87   *result = (*argp)+1;
88   return TRUE;
89 }
90
91 void set_rpc_status(ShibRpcError *error, ShibRpcStatus status,
92                     const char* msg, const char* origin)
93 {
94   error->status = status;
95   if (status) {
96     error->ShibRpcError_u.e.error = strdup(msg);
97     error->ShibRpcError_u.e.origin = strdup(origin);
98   }
99 }
100
101 void set_rpc_status_x(ShibRpcError *error, ShibRpcStatus status,
102                       const char* msg, const XMLCh* origin)
103 {
104   if (!status) {
105     set_rpc_status(error, status, NULL, NULL);
106     return;
107   }
108   auto_ptr<char> orig(XMLString::transcode(origin));
109   set_rpc_status(error, status, msg, orig.get());
110 }
111
112 extern "C" bool_t
113 shibrpc_session_is_valid_1_svc(shibrpc_session_is_valid_args_1 *argp,
114                                shibrpc_session_is_valid_ret_1 *result,
115                                struct svc_req *rqstp)
116 {
117   Category& log = get_category();
118   string ctx = get_threadid("session_is_valid");
119   saml::NDC ndc(ctx);
120
121   if (!argp || !result) {
122     log.error ("RPC Argument Error");
123     return FALSE;
124   }
125
126   memset (result, 0, sizeof (*result));
127   
128   log.debug ("checking: %s@%s (checkAddr=%s)",
129              argp->cookie.cookie, argp->cookie.client_addr,
130              argp->checkIPAddress ? "true" : "false");
131
132   // See if the cookie exists...
133   CCacheEntry *entry = g_shibTargetCCache->find(argp->cookie.cookie);
134
135   // If not, leave now..
136   if (!entry) {
137     log.debug ("Not found");
138     set_rpc_status(&result->status, SHIBRPC_NO_SESSION,
139                    "No session exists for this cookie", "");
140     return TRUE;
141   }
142
143   // TEST the session...
144   try {
145
146     // Grab the origin
147     const XMLCh* origin = entry->getStatement()->getSubject()->getNameQualifier();
148
149     // Verify the address is the same
150     if (argp->checkIPAddress) {
151       log.debug ("Checking address against %s", entry->getClientAddress());
152       if (strcmp (argp->cookie.client_addr, entry->getClientAddress())) {
153         log.debug ("IP Address mismatch");
154
155         throw ShibTargetException(SHIBRPC_IPADDR_MISMATCH,
156   "Your IP address does not match the address in the original authentication.",
157                                   origin);
158       }
159     }
160
161     // and that the session is still valid...
162     if (!entry->isSessionValid(argp->lifetime, argp->timeout)) {
163       log.debug ("Session expired");
164       throw ShibTargetException(SHIBRPC_SESSION_EXPIRED,
165                                 "Your session has expired.  Re-authenticate.",
166                                 origin);
167     }
168
169     // and now try to prefetch the attributes .. this could cause an
170     // "error", which is why we call it here.
171     try {
172       log.debug ("resource: %s", argp->url);
173       Resource r(argp->url);
174       entry->preFetch(r,15);    // give a 15-second window for the RM
175
176     } catch (SAMLException &e) {
177       log.debug ("prefetch failed with a SAML Exception: %s", e.what());
178       ostringstream os;
179       os << e;
180       throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str(), origin);
181
182     } catch (...) {
183       log.error ("prefetch caught an unknown exception");
184       throw ShibTargetException(SHIBRPC_UNKNOWN_ERROR,
185                 "An unknown error occured while pre-fetching attributes.",
186                                 origin);
187     }
188
189   } catch (ShibTargetException &e) {
190     entry->release();
191     g_shibTargetCCache->remove (argp->cookie.cookie);
192     set_rpc_status_x(&result->status, e.which(), e.what(), e.where());
193     return TRUE;
194   }
195
196   // Ok, just release it.
197   entry->release();
198
199   // ok, we've succeeded..
200   set_rpc_status(&result->status, SHIBRPC_OK, NULL, NULL);
201   log.debug ("session ok");
202   return TRUE;
203 }
204
205 extern "C" bool_t
206 shibrpc_new_session_1_svc(shibrpc_new_session_args_1 *argp,
207                           shibrpc_new_session_ret_1 *result, struct svc_req *rqstp)
208 {
209   Category& log = get_category();
210   string ctx = get_threadid("new_session");
211   saml::NDC ndc(ctx);
212
213   if (!argp || !result) {
214     log.error ("Invalid RPC Arguments");
215     return FALSE;
216   }
217
218   // Initialize the result structure
219   memset (result, 0, sizeof(*result));
220   result->cookie = strdup ("");
221
222   log.debug ("creating session for %s", argp->client_addr);
223   log.debug ("shire location: %s", argp->shire_location);
224
225   XMLByte* post=reinterpret_cast<XMLByte*>(argp->saml_post);
226   auto_ptr<XMLCh> location(XMLString::transcode(argp->shire_location));
227
228   // Pull in the Policies
229   Iterator<const XMLCh*> policies=ShibTargetConfig::getConfig().getPolicies();
230
231   // And grab the Profile
232   // XXX: Create a "Global" POSTProfile instance per location...
233   log.debug ("create the POST profile (%d policies)", policies.size());
234   ShibPOSTProfile *profile =
235     ShibPOSTProfileFactory::getInstance(policies,
236                                         location.get(),
237                                         3600);
238
239   SAMLResponse* r = NULL;
240   const SAMLAuthenticationStatement* auth_st = NULL;
241   XMLCh* origin = NULL;
242
243   try
244   {
245     try
246     {
247       // Make sure we've got a profile
248       if (!profile)
249         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,
250                                   "Failed to obtain the profile");
251
252       // Try and accept the response...
253       log.debug ("Trying to accept the post");
254       r = profile->accept(post, &origin);
255
256       // Make sure we got a response
257       if (!r)
258         throw ShibTargetException(SHIBRPC_RESPONSE_MISSING,
259                                   "Failed to accept the response.",
260                                   origin);
261
262       // Find the SSO Assertion
263       log.debug ("Get the SSOAssertion");
264       const SAMLAssertion* ssoAssertion = profile->getSSOAssertion(*r);
265
266       // Check against the replay cache
267       log.debug ("check replay cache");
268       if (profile->checkReplayCache(*ssoAssertion) == false)
269         throw ShibTargetException(SHIBRPC_ASSERTION_REPLAYED,
270                                   "Duplicate assertion found.",
271                                   origin);
272
273       // Get the authentication statement we need.
274       log.debug ("get SSOStatement");
275       auth_st = profile->getSSOStatement(*ssoAssertion);
276
277       // Maybe verify the origin address....
278       if (argp->checkIPAddress) {
279         log.debug ("check IP Address");
280
281         // Verify the client address exists
282         const XMLCh* ip = auth_st->getSubjectIP();
283         if (!ip)
284           throw ShibTargetException(SHIBRPC_IPADDR_MISSING,
285                     "The IP Address provided by your origin site was missing.",
286                                     origin);
287         
288         log.debug ("verify client address");
289         // Verify the client address matches authentication
290         auto_ptr<char> this_ip(XMLString::transcode(ip));
291         if (strcmp (argp->client_addr, this_ip.get()))
292           throw ShibTargetException(SHIBRPC_IPADDR_MISMATCH,
293             "The IP address provided by your origin site did not match "
294             "your current address.  "
295             "To correct this problem you may need to bypass a local proxy server.",
296                                     origin);
297       }
298     }
299     catch (SAMLException &e)    // XXX refine this handler to catch and log different profile exceptions
300     {
301       log.error ("received SAML exception: %s", e.what());
302       ostringstream os;
303       os << e;
304       throw ShibTargetException (SHIBRPC_SAML_EXCEPTION, os.str(), origin);
305     }
306     catch (XMLException &e)
307     {
308       log.error ("received XML exception");
309       auto_ptr<char> msg(XMLString::transcode(e.getMessage()));
310       throw ShibTargetException (SHIBRPC_XML_EXCEPTION, msg.get(), origin);
311     }
312   }
313   catch (ShibTargetException &e)
314   {
315     log.info ("FAILED: %s", e.what());
316     if (r) delete r;
317     if (origin) delete origin;
318     set_rpc_status_x(&result->status, e.which(), e.what(), e.where());
319     return TRUE;
320   }
321 #if 1
322   catch (...)
323   {
324     log.error ("Unknown error");
325     if (r) delete r;
326     if (origin) delete origin;
327     set_rpc_status(&result->status, SHIBRPC_UNKNOWN_ERROR,
328                    "An unknown exception occurred", "");
329     return TRUE;
330   }
331 #endif
332
333   // It passes all our tests -- create a new session.
334   log.info ("Creating new session");
335
336   SAMLAuthenticationStatement* as=static_cast<SAMLAuthenticationStatement*>(auth_st->clone());
337
338   // Create a new cookie
339   SAMLIdentifier id;
340   auto_ptr<char> c(XMLString::transcode(id));
341   char *cookie = c.get();
342
343   // Cache this session with the cookie
344   g_shibTargetCCache->insert(cookie, as, argp->client_addr);
345   
346   // Delete the response...
347   delete r;
348
349   // Delete the origin...
350   XMLString::release(&origin);
351
352   // And let the user know.
353   if (result->cookie) free(result->cookie);
354   result->cookie = strdup(cookie);
355   set_rpc_status(&result->status, SHIBRPC_OK, NULL, NULL);
356
357   log.debug("new session id: %s", cookie);
358   return TRUE;
359 }
360
361 extern "C" bool_t
362 shibrpc_get_assertions_1_svc(shibrpc_get_assertions_args_1 *argp,
363                         shibrpc_get_assertions_ret_1 *result, struct svc_req *rqstp)
364 {
365   Category& log = get_category();
366   string ctx = get_threadid("get_assertions");
367   saml::NDC ndc(ctx);
368
369   if (!argp || !result) {
370     log.error ("Invalid RPC arguments");
371     return FALSE;
372   }
373
374   memset (result, 0, sizeof (*result));
375
376   log.debug ("get attrs for client at %s", argp->cookie.client_addr);
377   log.debug ("cookie: %s", argp->cookie.cookie);
378   log.debug ("resource: %s", argp->url);
379
380   // Find this session
381   CCacheEntry* entry = g_shibTargetCCache->find(argp->cookie.cookie);
382
383   // If it does not exist, leave now..
384   if (!entry) {
385     log.error ("No Session");
386     set_rpc_status(&result->status, SHIBRPC_NO_SESSION,
387                    "getattrs Internal error: no session", "");
388     return TRUE;
389   }
390
391   // Grab the origin
392   const XMLCh* origin = entry->getStatement()->getSubject()->getNameQualifier();
393
394   // Validate the client address (again?)
395   if (argp->checkIPAddress &&
396       strcmp (argp->cookie.client_addr, entry->getClientAddress())) {
397     log.error ("IP Mismatch");
398     set_rpc_status_x(&result->status, SHIBRPC_IPADDR_MISMATCH,
399    "Your IP address does not match the address in the original authentication.",
400                      origin);
401     entry->release();
402     return TRUE;
403   }
404
405   try {
406     // grab the attributes for this resource
407     Resource resource(argp->url);
408     Iterator<SAMLAssertion*> iter = entry->getAssertions(resource);
409     u_int size = iter.size();
410     result->assertions.assertions_len = size;
411
412     // if we have assertions...
413     if (size) {
414
415       // Build the response section
416       ShibRpcXML* av =
417         (ShibRpcXML*) malloc (size * sizeof (ShibRpcXML));
418       result->assertions.assertions_val = av;
419
420       // and then serialize them all...
421       u_int i = 0;
422       while (iter.hasNext()) {
423         SAMLAssertion* as = iter.next();
424         ostringstream os;
425         os << *as;
426         av[i++].xml_string = strdup(os.str().c_str());
427       }
428     }
429   } catch (SAMLException& e) {
430     log.error ("received SAML exception: %s", e.what());
431     ostringstream os;
432     os << e;
433     set_rpc_status_x(&result->status, SHIBRPC_SAML_EXCEPTION,
434                      strdup(os.str().c_str()), origin);
435     entry->release();
436     return TRUE;
437   }
438
439   // Now grab the serialized authentication statement
440   result->auth_statement.xml_string = strdup(entry->getSerializedStatement());
441
442   entry->release();
443
444   // and let it fly
445   set_rpc_status(&result->status, SHIBRPC_OK, NULL, NULL);
446
447   log.debug ("returning");
448   return TRUE;
449 }
450
451 extern "C" int
452 shibrpc_prog_1_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
453 {
454         xdr_free (xdr_result, result);
455
456         /*
457          * Insert additional freeing code here, if needed
458          */
459
460         return 1;
461 }