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