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