2 * mod_shire.cpp -- the SHIRE Apache Module
4 * Created by: Derek Atkins <derek@ihtfp.com>
10 #include <saml/saml.h>
11 #include <shib/shib.h>
12 #include <shib/shib-threads.h>
13 #include <shib-target/shib-target.h>
15 // Apache specific header files
17 #include "http_config.h"
18 #include "http_protocol.h"
19 #include "http_main.h"
20 #include "util_script.h"
22 #include "http_core.h"
29 // For POST processing from Apache
30 #undef _XOPEN_SOURCE // bombs on solaris
31 #include <libapreq/apache_request.h>
35 using namespace shibboleth;
36 using namespace shibtarget;
38 extern "C" module MODULE_VAR_EXPORT shire_module;
41 char* g_szSHIREURL = NULL;
42 char* g_szSHIREConfig = NULL;
43 ThreadKey* rpc_handle_key = NULL;
44 ShibTargetConfig* g_Config = NULL;
47 // per-dir module configuration structure
48 struct shire_dir_config
50 int bBasicHijack; // activate for AuthType Basic?
51 int bSSLOnly; // only over SSL?
52 SHIREConfig config; // SHIRE Configuration
55 // creates per-directory config structure
56 extern "C" void* create_shire_dir_config (pool* p, char* d)
58 shire_dir_config* dc=(shire_dir_config*)ap_pcalloc(p,sizeof(shire_dir_config));
59 dc->bBasicHijack = -1;
61 dc->config.lifetime = -1;
62 dc->config.timeout = -1;
66 // overrides server configuration in directories
67 extern "C" void* merge_shire_dir_config (pool* p, void* base, void* sub)
69 shire_dir_config* dc=(shire_dir_config*)ap_pcalloc(p,sizeof(shire_dir_config));
70 shire_dir_config* parent=(shire_dir_config*)base;
71 shire_dir_config* child=(shire_dir_config*)sub;
73 dc->bBasicHijack=((child->bBasicHijack==-1) ? parent->bBasicHijack : child->bBasicHijack);
74 dc->bSSLOnly=((child->bSSLOnly==-1) ? parent->bSSLOnly : child->bSSLOnly);
75 dc->config.lifetime=((child->config.lifetime==-1) ? parent->config.lifetime : child->config.lifetime);
76 dc->config.timeout=((child->config.timeout==-1) ? parent->config.timeout : child->config.timeout);
80 // generic global slot handlers
81 extern "C" const char* ap_set_global_string_slot(cmd_parms* parms, void*, const char* arg)
83 *((char**)(parms->info))=ap_pstrdup(parms->pool,arg);
87 // some shortcuts for directory config slots
88 extern "C" const char* set_lifetime(cmd_parms* parms, shire_dir_config* dc, const char* arg)
90 dc->config.lifetime=atoi(arg);
94 extern "C" const char* set_timeout(cmd_parms* parms, shire_dir_config* dc, const char* arg)
96 dc->config.timeout=atoi(arg);
100 typedef const char* (*config_fn_t)(void);
102 // SHIRE Module commands
104 static command_rec shire_cmds[] = {
105 {"SHIREConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIREConfig,
106 RSRC_CONF, TAKE1, "Path to SHIRE ini file."},
107 {"SHIREURL", (config_fn_t)ap_set_global_string_slot, &g_szSHIREURL,
108 RSRC_CONF, TAKE1, "SHIRE POST processor URL."},
110 {"ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
111 (void *) XtOffsetOf (shire_dir_config, bBasicHijack),
112 OR_AUTHCFG, FLAG, "Respond to AuthType Basic and convert to shib?"},
113 {"ShibSSLOnly", (config_fn_t)ap_set_flag_slot,
114 (void *) XtOffsetOf (shire_dir_config, bSSLOnly),
115 OR_AUTHCFG, FLAG, "Require SSL when accessing a secured directory?"},
116 {"ShibAuthLifetime", (config_fn_t)set_lifetime, NULL,
117 OR_AUTHCFG, TAKE1, "Lifetime of session in seconds."},
118 {"ShibAuthTimeout", (config_fn_t)set_timeout, NULL,
119 OR_AUTHCFG, TAKE1, "Timeout for session in seconds."},
125 void destroy_handle(void* data)
127 delete (RPCHandle*)data;
133 * Things to do when the child process is initialized.
135 extern "C" void shire_child_init(server_rec* s, pool* p)
137 // Initialize runtime components.
139 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
140 "shire_child_init() starting");
143 ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,s,
144 "shire_child_init(): already initialized!");
149 g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, g_szSHIREConfig));
151 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
152 "shire_child_init() failed to initialize SHIB Target");
156 // Create the RPC Handle TLS key.
157 rpc_handle_key=ThreadKey::create(destroy_handle);
159 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,"shire_child_init() done");
167 extern "C" void shire_child_exit(server_rec* s, pool* p)
169 delete rpc_handle_key;
170 g_Config->shutdown();
172 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,"shire_child_exit() done");
175 inline char hexchar(unsigned short s)
177 return (s<=9) ? ('0' + s) : ('A' + s - 10);
180 static char* url_encode(request_rec* r, const char* s)
182 static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
183 char* ret=(char*)ap_palloc(r->pool,sizeof(char)*3*strlen(s)+1);
185 unsigned long count=0;
188 if (strchr(badchars,*s)!=NULL || *s<=0x1F || *s>=0x7F)
191 ret[count++]=hexchar(*s >> 4);
192 ret[count++]=hexchar(*s & 0x0F);
201 static const char* get_shire_location(request_rec* r, const char* target, bool encode)
203 ShibINI& ini = g_Config->getINI();
204 string shire_location;
207 shire_location = g_szSHIREURL;
208 else if (! ini.get_tag (ap_get_server_name(r), "shireURL", true, &shire_location)) {
209 ap_log_rerror(APLOG_MARK,APLOG_ERR,r,
210 "shire_get_location() no shireURL configuration for %s",
211 ap_get_server_name(r));
215 const char* shire = shire_location.c_str();
219 return url_encode(r,shire);
221 return ap_pstrdup(r->pool,shire);
223 const char* colon=strchr(target,':');
224 const char* slash=strchr(colon+3,'/');
226 return url_encode(r,ap_pstrcat(r->pool,
227 ap_pstrndup(r->pool,target,slash-target),
230 return ap_pstrcat(r->pool, ap_pstrndup(r->pool,target,slash-target),
234 static bool is_shire_location(request_rec* r, const char* target)
236 const char* shire = get_shire_location(r, target, false);
238 if (!shire) return false;
240 if (!strstr(target, shire))
243 return (!strcmp(target,shire));
246 static int shire_error_page(request_rec* r, const char* filename, ShibMLP& mlp)
248 ifstream infile (filename);
250 ap_log_rerror(APLOG_MARK,APLOG_ERR,r,
251 "shire_error_page() cannot open %s", filename);
255 string res = mlp.run(infile);
256 r->content_type = ap_psprintf(r->pool, "text/html");
257 ap_send_http_header(r);
258 ap_rprintf(r, res.c_str());
262 extern "C" int shire_check_user(request_rec* r)
264 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_check_user: ENTER");
265 shire_dir_config* dc=(shire_dir_config*)ap_get_module_config(r->per_dir_config,&shire_module);
267 // This will always be normalized, because Apache uses ap_get_server_name in this API call.
268 char* targeturl=ap_construct_url(r->pool,r->unparsed_uri,r);
270 if (is_shire_location (r, targeturl)) {
271 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
272 "shire_check_user: REQUEST FOR SHIRE! Maybe you did not configure the SHIRE Handler?");
276 // Regular access to arbitrary resource...check AuthType
277 const char *auth_type=ap_auth_type (r);
281 if (strcasecmp(auth_type,"shibboleth"))
283 if (!strcasecmp(auth_type,"basic") && dc->bBasicHijack==1)
285 core_dir_config* conf=
286 (core_dir_config*)ap_get_module_config(r->per_dir_config,
287 ap_find_linked_module("http_core.c"));
288 conf->ap_auth_type="shibboleth";
294 // set the connection authtype
296 r->connection->ap_auth_type = "shibboleth";
299 if (dc->bSSLOnly==1 && strcmp(ap_http_method(r),"https"))
301 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
302 "shire_check_user() blocked non-SSL access");
307 ostringstream threadid;
308 threadid << "[" << getpid() << "] shire" << '\0';
309 saml::NDC ndc(threadid.str().c_str());
311 ShibINI& ini = g_Config->getINI();
313 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
314 "shire_check_user() Shib check for %s", targeturl);
317 const char * shire_location = get_shire_location(r,targeturl,true);
320 string shire_url = get_shire_location(r,targeturl,false);
322 const char* serverName = ap_get_server_name(r);
324 bool has_tag = ini.get_tag (serverName, "checkIPAddress", true, &tag);
325 dc->config.checkIPAddress = (has_tag ? ShibINI::boolean (tag) : false);
328 if (! ini.get_tag(serverName, "cookieName", true, &shib_cookie)) {
329 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
330 "shire_check_user: no cookieName configuration for %s",
336 if (! ini.get_tag(serverName, "wayfURL", true, &wayfLocation)) {
337 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
338 "shire_check_user: no wayfURL configuration for %s",
344 if (! ini.get_tag(serverName, "shireError", true, &shireError)) {
345 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
346 "shire_check_user: no shireError configuration for %s",
351 // Get an RPC handle and build the SHIRE object.
352 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
355 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
356 rpc_handle_key->setData(rpc_handle);
358 SHIRE shire(rpc_handle, dc->config, shire_url);
360 // We're in charge, so check for cookie.
361 const char* session_id=NULL;
362 const char* cookies=ap_table_get(r->headers_in,"Cookie");
365 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
366 "shire_check_user() cookies found: %s",cookies);
368 if (!cookies || !(session_id=strstr(cookies,shib_cookie.c_str())))
370 // No cookie. Redirect to WAYF.
371 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
372 "shire_check_user() no cookie found -- redirecting to WAYF");
373 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
374 "?shire=",shire_location,
375 "&target=",url_encode(r,targeturl),NULL);
376 ap_table_setn(r->headers_out,"Location",wayf);
380 // Yep, we found a cookie -- pull it out (our session_id)
381 session_id+=strlen(shib_cookie.c_str()) + 1; /* Skip over the '=' */
382 char* cookiebuf = ap_pstrdup(r->pool,session_id);
383 char* cookieend = strchr(cookiebuf,';');
385 *cookieend = '\0'; /* Ignore anyting after a ; */
386 session_id=cookiebuf;
388 // Make sure this session is still valid
389 RPCError* status = NULL;
390 ShibMLP markupProcessor;
391 has_tag = ini.get_tag(serverName, "supportContact", true, &tag);
392 markupProcessor.insert("supportContact", has_tag ? tag : "");
393 has_tag = ini.get_tag(serverName, "logoLocation", true, &tag);
394 markupProcessor.insert("logoLocation", has_tag ? tag : "");
395 markupProcessor.insert("requestURL", targeturl);
398 status = shire.sessionIsValid(session_id, r->connection->remote_ip,targeturl);
400 catch (ShibTargetException &e) {
401 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_check_user(): %s", e.what());
402 markupProcessor.insert ("errorType", "SHIRE Processing Error");
403 markupProcessor.insert ("errorText", e.what());
404 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
405 return shire_error_page (r, shireError.c_str(), markupProcessor);
408 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_check_user(): caught unexpected error");
409 markupProcessor.insert ("errorType", "SHIRE Processing Error");
410 markupProcessor.insert ("errorText", "Unexpected Exception");
411 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
412 return shire_error_page (r, shireError.c_str(), markupProcessor);
416 if (status->isError()) {
417 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,r,
418 "shire_check_user() session invalid: %s",
421 if (status->isRetryable()) {
422 // Oops, session is invalid. Redirect to WAYF.
423 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
424 "?shire=",shire_location,
425 "&target=",url_encode(r,targeturl),NULL);
426 ap_table_setn(r->headers_out,"Location",wayf);
432 // return the error page to the user
433 markupProcessor.insert (*status);
435 return shire_error_page (r, shireError.c_str(), markupProcessor);
440 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
441 "shire_check_user() success");
445 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,"shire_check_user() server error");
449 extern "C" int shire_post_handler (request_rec* r)
451 ostringstream threadid;
452 threadid << "[" << getpid() << "] shire" << '\0';
453 saml::NDC ndc(threadid.str().c_str());
455 ShibINI& ini = g_Config->getINI();
456 ShibMLP markupProcessor;
458 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_post_handler() ENTER");
460 const char* targeturl=ap_construct_url(r->pool,r->unparsed_uri,r);
462 const char * shire_location = get_shire_location(r,targeturl,true);
465 string shire_url = get_shire_location(r,targeturl,false);
467 const char* serverName = ap_get_server_name(r);
469 bool has_tag = ini.get_tag(serverName, "checkIPAddress", true, &tag);
471 config.checkIPAddress = (has_tag ? ShibINI::boolean(tag) : false);
474 if (! ini.get_tag(serverName, "cookieName", true, &shib_cookie)) {
475 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
476 "shire_check_user: no cookieName configuration for %s",
482 if (! ini.get_tag(serverName, "wayfURL", true, &wayfLocation)) {
483 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
484 "shire_check_user: no wayfURL configuration for %s",
490 if (! ini.get_tag(serverName, "shireError", true, &shireError)) {
491 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
492 "shire_check_user: no shireError configuration for %s",
497 has_tag = ini.get_tag(serverName, "supportContact", true, &tag);
498 markupProcessor.insert("supportContact", has_tag ? tag : "");
499 has_tag = ini.get_tag(serverName, "logoLocation", true, &tag);
500 markupProcessor.insert("logoLocation", has_tag ? tag : "");
501 markupProcessor.insert("requestURL", targeturl);
503 // Get an RPC handle and build the SHIRE object.
504 RPCHandle* rpc_handle = (RPCHandle*)rpc_handle_key->getData();
507 rpc_handle = new RPCHandle(shib_target_sockname(), SHIBRPC_PROG, SHIBRPC_VERS_1);
508 rpc_handle_key->setData(rpc_handle);
510 SHIRE shire(rpc_handle, config, shire_url);
512 // Process SHIRE POST
514 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
515 "shire_post_handler() Beginning SHIRE POST processing");
519 if (!ini.get_tag(serverName, "shireSSLOnly", true, &sslonly))
520 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
521 "shire_post_handler: no shireSSLOnly configuration");
523 // Make sure this is SSL, if it should be
524 if (ShibINI::boolean(sslonly) && strcmp(ap_http_method(r),"https"))
525 throw ShibTargetException (SHIBRPC_OK,
526 "blocked non-SSL access to SHIRE POST processor");
528 // Make sure this is a POST
529 if (strcasecmp (r->method, "POST"))
530 throw ShibTargetException (SHIBRPC_OK,
531 "blocked non-POST to SHIRE POST processor");
533 // Sure sure this POST is an appropriate content type
534 const char *ct = ap_table_get (r->headers_in, "Content-type");
535 if (!ct || strcasecmp (ct, "application/x-www-form-urlencoded"))
536 throw ShibTargetException (SHIBRPC_OK,
538 "blocked bad content-type to SHIRE POST processor: %s",
541 // Make sure the "bytes sent" is a reasonable number
542 if (r->bytes_sent > 1024*1024) // 1MB?
543 throw ShibTargetException (SHIBRPC_OK,
544 "blocked too-large a post to SHIRE POST processor");
546 // Read the posted data
547 ApacheRequest *ap_req = ApacheRequest_new(r);
548 int err = ApacheRequest_parse(ap_req);
550 throw ShibTargetException (SHIBRPC_OK,
552 "ApacheRequest_parse() failed with %d.", err));
555 // Make sure the target parameter exists
556 const char *target = ApacheRequest_param(ap_req, "TARGET");
557 if (!target || *target == '\0')
559 throw ShibTargetException (SHIBRPC_OK,
560 "SHIRE POST failed to find TARGET");
562 // Make sure the SAML Response parameter exists
563 const char *post = ApacheRequest_param(ap_req, "SAMLResponse");
564 if (!post || *post == '\0')
566 throw ShibTargetException (SHIBRPC_OK,
567 "SHIRE POST failed to find SAMLResponse");
569 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
570 "shire_post_handler() Processing POST for target: %s", target);
574 RPCError* status = shire.sessionCreate(post, r->connection->remote_ip, cookie);
576 if (status->isError()) {
577 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
578 "shire_post_handler() POST process failed (%d): %s",
579 status->getCode(), status->getText());
581 if (status->isRetryable()) {
582 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,r,
583 "shire_post_handler() Retrying POST by redirecting to WAYF");
585 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
586 "?shire=",shire_location,
587 "&target=",url_encode(r,target),NULL);
588 ap_table_setn(r->headers_out,"Location",wayf);
593 // return this error to the user.
594 markupProcessor.insert (*status);
596 return shire_error_page (r, shireError.c_str(), markupProcessor);
600 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
601 "shire_post_handler() POST process succeeded. New cookie: %s",
604 // We've got a good session, set the cookie...
605 char * domain = NULL;
606 char * new_cookie = ap_psprintf(r->pool, "%s=%s; path=/%s%s",
609 (domain ? "; domain=" : ""),
610 (domain ? domain : ""));
612 ap_table_setn(r->err_headers_out, "Set-Cookie", new_cookie);
613 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
614 "shire_post_handler() Set cookie: %s", new_cookie);
616 // ... and redirect to the target
617 char* redir=ap_pstrcat(r->pool,url_encode(r,target),NULL);
618 ap_table_setn(r->headers_out, "Location", target);
621 } catch (ShibTargetException &e) {
622 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
623 "shire_post_handler(): %s", e.what());
625 markupProcessor.insert ("errorType", "SHIRE Processing Error");
626 markupProcessor.insert ("errorText", e.what());
627 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
628 return shire_error_page (r, shireError.c_str(), markupProcessor);
631 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_post_handler(): unexpected exception");
633 markupProcessor.insert ("errorType", "SHIRE Processing Error");
634 markupProcessor.insert ("errorText", "Unexpected Exception");
635 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
636 return shire_error_page (r, shireError.c_str(), markupProcessor);
639 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,"shire_post_handler() server error");
644 handler_rec shire_handlers[] = {
645 { "shib-shire-post", shire_post_handler },
649 extern "C" void mod_shire_init (server_rec*r, pool* p)
651 ShibTargetConfig::preinit();
654 module MODULE_VAR_EXPORT shire_module = {
655 STANDARD_MODULE_STUFF,
656 mod_shire_init, /* initializer */
657 create_shire_dir_config, /* dir config creater */
658 merge_shire_dir_config, /* dir merger --- default is to override */
659 NULL, /* server config */
660 NULL, /* merge server config */
661 shire_cmds, /* command table */
662 shire_handlers, /* handlers */
663 NULL, /* filename translation */
664 shire_check_user, /* check_user_id */
665 NULL, /* check auth */
666 NULL, /* check access */
667 NULL, /* type_checker */
670 NULL, /* header parser */
671 shire_child_init, /* child_init */
672 shire_child_exit, /* child_exit */
673 NULL /* post read-request */