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