2 * mod_shire.cpp -- the SHIRE Apache Module
4 * Created by: Derek Atkins <derek@ihtfp.com>
9 // Apache specific header files
11 #include "http_config.h"
12 #include "http_protocol.h"
13 #include "http_main.h"
14 #include "util_script.h"
16 #include "http_core.h"
20 #include <saml/saml.h>
21 #include <shib/shib.h>
22 #include <shib-target/shib-target.h>
28 // For POST processing from Apache
29 #undef _XOPEN_SOURCE // bombs on solaris
30 #include <libapreq/apache_request.h>
34 using namespace shibboleth;
35 using namespace shibtarget;
37 extern "C" module MODULE_VAR_EXPORT shire_module;
40 char* g_szSHIREURL = NULL;
41 char* g_szSHIREConfig = NULL;
42 RPCHandle *rpc_handle = NULL;
43 ShibTargetConfig * g_Config = NULL;
46 // per-server configuration structure
47 struct shire_server_config
49 char* serverName; // Name of this server
52 // creates the per-server configuration
53 extern "C" void* create_shire_server_config (pool * p, server_rec * s)
55 shire_server_config* sc=(shire_server_config*)ap_pcalloc(p,sizeof(shire_server_config));
59 // overrides server configuration in virtual servers
60 extern "C" void* merge_shire_server_config (pool* p, void* base, void* sub)
62 shire_server_config* sc=(shire_server_config*)ap_pcalloc(p,sizeof(shire_server_config));
63 shire_server_config* parent=(shire_server_config*)base;
64 shire_server_config* child=(shire_server_config*)sub;
66 if (child->serverName)
67 sc->serverName=ap_pstrdup(p,child->serverName);
68 else if (parent->serverName)
69 sc->serverName=ap_pstrdup(p,parent->serverName);
76 // per-dir module configuration structure
77 struct shire_dir_config
79 int bBasicHijack; // activate for AuthType Basic?
80 int bSSLOnly; // only over SSL?
81 SHIREConfig config; // SHIRE Configuration
84 // creates per-directory config structure
85 extern "C" void* create_shire_dir_config (pool* p, char* d)
87 shire_dir_config* dc=(shire_dir_config*)ap_pcalloc(p,sizeof(shire_dir_config));
88 dc->bBasicHijack = -1;
90 dc->config.lifetime = -1;
91 dc->config.timeout = -1;
95 // overrides server configuration in directories
96 extern "C" void* merge_shire_dir_config (pool* p, void* base, void* sub)
98 shire_dir_config* dc=(shire_dir_config*)ap_pcalloc(p,sizeof(shire_dir_config));
99 shire_dir_config* parent=(shire_dir_config*)base;
100 shire_dir_config* child=(shire_dir_config*)sub;
102 dc->bBasicHijack=((child->bBasicHijack==-1) ? parent->bBasicHijack : child->bBasicHijack);
103 dc->bSSLOnly=((child->bSSLOnly==-1) ? parent->bSSLOnly : child->bSSLOnly);
104 dc->config.lifetime=((child->config.lifetime==-1) ? parent->config.lifetime : child->config.lifetime);
105 dc->config.timeout=((child->config.timeout==-1) ? parent->config.timeout : child->config.timeout);
109 // generic global slot handlers
110 extern "C" const char* ap_set_global_string_slot(cmd_parms* parms, void*, const char* arg)
112 *((char**)(parms->info))=ap_pstrdup(parms->pool,arg);
116 // generic per-server slot handlers
117 extern "C" const char* ap_set_server_string_slot(cmd_parms* parms, void*, const char* arg)
119 char* base=(char*)ap_get_module_config(parms->server->module_config,&shire_module);
120 int offset=(int)parms->info;
121 *((char**)(base + offset))=ap_pstrdup(parms->pool,arg);
125 // some shortcuts for directory config slots
126 extern "C" const char* set_lifetime(cmd_parms* parms, shire_dir_config* dc, const char* arg)
128 dc->config.lifetime=atoi(arg);
132 extern "C" const char* set_timeout(cmd_parms* parms, shire_dir_config* dc, const char* arg)
134 dc->config.timeout=atoi(arg);
138 typedef const char* (*config_fn_t)(void);
140 // SHIRE Module commands
142 static command_rec shire_cmds[] = {
143 {"SHIREConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIREConfig,
144 RSRC_CONF, TAKE1, "Path to SHIRE ini file."},
145 {"SHIREURL", (config_fn_t)ap_set_global_string_slot, &g_szSHIREURL,
146 RSRC_CONF, TAKE1, "SHIRE POST processor URL."},
148 {"ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
149 (void *) XtOffsetOf (shire_dir_config, bBasicHijack),
150 OR_AUTHCFG, FLAG, "Respond to AuthType Basic and convert to shib?"},
151 {"ShibSSLOnly", (config_fn_t)ap_set_flag_slot,
152 (void *) XtOffsetOf (shire_dir_config, bSSLOnly),
153 OR_AUTHCFG, FLAG, "Require SSL when accessing a secured directory?"},
154 {"ShibAuthLifetime", (config_fn_t)set_lifetime, NULL,
155 OR_AUTHCFG, TAKE1, "Lifetime of session in seconds."},
156 {"ShibAuthTimeout", (config_fn_t)set_timeout, NULL,
157 OR_AUTHCFG, TAKE1, "Timeout for session in seconds."},
165 * Things to do when the child process is initialized.
167 extern "C" void shire_child_init(server_rec* s, pool* p)
169 // Initialize runtime components.
171 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
172 "shire_child_init() starting");
175 ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,s,
176 "shire_child_init(): already initialized!");
181 g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, g_szSHIREConfig));
183 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
184 "shire_child_init() failed to initialize SHIB Target");
188 // Create the RPC Handle.. Note: this should be per _thread_
189 // if there is some way to do that reasonably..
190 rpc_handle = new RPCHandle(g_Config->m_SocketName, SHIBRPC_PROG, SHIBRPC_VERS_1);
192 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,"shire_child_init() done");
200 extern "C" void shire_child_exit(server_rec* s, pool* p)
203 g_Config->shutdown();
205 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,"shire_child_exit() done");
208 inline char hexchar(unsigned short s)
210 return (s<=9) ? ('0' + s) : ('A' + s - 10);
213 static char* url_encode(request_rec* r, const char* s)
215 static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
216 char* ret=(char*)ap_palloc(r->pool,sizeof(char)*3*strlen(s)+1);
218 unsigned long count=0;
221 if (strchr(badchars,*s)!=NULL || *s<=0x1F || *s>=0x7F)
224 ret[count++]=hexchar(*s >> 4);
225 ret[count++]=hexchar(*s & 0x0F);
234 // Return the "name" of this server to look up configuration options
235 static const char* get_service_name(request_rec* r)
237 shire_server_config* sc =
238 (shire_server_config*) ap_get_module_config(r->server->module_config,
242 return sc->serverName;
244 return ap_get_server_name(r);
247 // return the "normalized" target URL
248 static const char* get_target(request_rec* r, const char* target)
250 const char* serverName = get_service_name(r);
252 if ((g_Config->getINI()).get_tag (serverName, "normalizeRequest", true, &tag))
254 if (ShibINI::boolean (tag))
256 const char* colon=strchr(target,':');
257 const char* slash=strchr(colon+3,'/');
258 const char* second_colon=strchr(colon+3,':');
259 return ap_pstrcat(r->pool,ap_pstrndup(r->pool,target,colon+3-target),
260 ap_get_server_name(r),
261 (second_colon && second_colon < slash) ?
262 second_colon : slash,
269 static const char* get_shire_location(request_rec* r, const char* target, bool encode)
271 ShibINI& ini = g_Config->getINI();
272 const char* serverName = get_service_name(r);
273 string shire_location;
276 shire_location = g_szSHIREURL;
277 else if (! ini.get_tag (serverName, "shireURL", true, &shire_location)) {
278 ap_log_rerror(APLOG_MARK,APLOG_ERR,r,
279 "shire_get_location() no shireURL configuration for %s",
284 const char* shire = shire_location.c_str();
288 return url_encode(r,shire);
290 return ap_pstrdup(r->pool,shire);
292 const char* colon=strchr(target,':');
293 const char* slash=strchr(colon+3,'/');
295 return url_encode(r,ap_pstrcat(r->pool,
296 ap_pstrndup(r->pool,target,slash-target),
299 return ap_pstrcat(r->pool, ap_pstrndup(r->pool,target,slash-target),
303 static bool is_shire_location(request_rec* r, const char* target)
305 const char* shire = get_shire_location(r, target, false);
307 if (!shire) return false;
309 if (!strstr(target, shire))
312 return (!strcmp(target,shire));
315 static int shire_error_page(request_rec* r, const char* filename, ShibMLP& mlp)
317 ifstream infile (filename);
319 ap_log_rerror(APLOG_MARK,APLOG_ERR,r,
320 "shire_error_page() cannot open %s", filename);
324 string res = mlp.run(infile);
325 r->content_type = ap_psprintf(r->pool, "text/html");
326 ap_send_http_header(r);
327 ap_rprintf(r, res.c_str());
331 extern "C" int shire_check_user(request_rec* r)
333 ostringstream threadid;
334 threadid << "[" << getpid() << "] shire" << '\0';
335 saml::NDC ndc(threadid.str().c_str());
337 ShibINI& ini = g_Config->getINI();
338 ShibMLP markupProcessor;
340 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
341 "shire_check_user: ENTER");
343 shire_dir_config* dc=
344 (shire_dir_config*)ap_get_module_config(r->per_dir_config,&shire_module);
346 const char* targeturl=get_target(r,ap_construct_url(r->pool,r->unparsed_uri,r));
348 const char * shire_location = get_shire_location(r,targeturl,true);
349 if (!shire_location) return SERVER_ERROR;
350 string shire_url = get_shire_location(r,targeturl,false);
352 const char* serverName = get_service_name (r);
354 bool has_tag = ini.get_tag (serverName, "checkIPAddress", true, &tag);
355 dc->config.checkIPAddress = (has_tag ? ShibINI::boolean (tag) : false);
358 if (! ini.get_tag (serverName, "cookieName", true, &shib_cookie)) {
359 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
360 "shire_check_user: no cookieName configuration for %s",
366 if (! ini.get_tag (serverName, "wayfURL", true, &wayfLocation)) {
367 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
368 "shire_check_user: no wayfURL configuration for %s",
374 if (! ini.get_tag (serverName, "shireError", true, &shireError)) {
375 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
376 "shire_check_user: no shireError configuration for %s",
381 ini.get_tag (serverName, "supportContact", true, &tag);
382 markupProcessor.insert ("supportContact", has_tag ? tag : "");
383 has_tag = ini.get_tag (serverName, "logoLocation", true, &tag);
384 markupProcessor.insert ("logoLocation", has_tag ? tag : "");
385 markupProcessor.insert ("requestURL", targeturl);
387 SHIRE shire(rpc_handle, dc->config, shire_url);
389 if (is_shire_location (r, targeturl)) {
390 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
391 "shire_check_user: REQUEST FOR SHIRE! Maybe you did not configure the SHIRE Handler?");
395 // Regular access to arbitrary resource...check AuthType
397 const char *auth_type=ap_auth_type (r);
401 if (strcasecmp(auth_type,"shibboleth"))
403 if (!strcasecmp(auth_type,"basic") && dc->bBasicHijack==1)
405 core_dir_config* conf=
406 (core_dir_config*)ap_get_module_config(r->per_dir_config,
407 ap_find_linked_module("http_core.c"));
408 conf->ap_auth_type="shibboleth";
414 // set the connection authtype
415 if (r->connection) r->connection->ap_auth_type = "shibboleth";
417 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
418 "shire_check_user() Shib check for %s", targeturl);
421 if (dc->bSSLOnly==1 && strcmp(ap_http_method(r),"https"))
423 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
424 "shire_check_user() blocked non-SSL access");
428 // We're in charge, so check for cookie.
429 const char* session_id=NULL;
430 const char* cookies=ap_table_get(r->headers_in,"Cookie");
433 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
434 "shire_check_user() cookies found: %s",
437 if (!cookies || !(session_id=strstr(cookies,shib_cookie.c_str())))
439 // No cookie. Redirect to WAYF.
440 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
441 "shire_check_user() no cookie found -- redirecting to WAYF");
442 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
443 "?shire=",shire_location,
444 "&target=",url_encode(r,targeturl),NULL);
445 ap_table_setn(r->headers_out,"Location",wayf);
449 // Yep, we found a cookie -- pull it out (our session_id)
450 session_id+=strlen(shib_cookie.c_str()) + 1; /* Skip over the '=' */
451 char* cookiebuf = ap_pstrdup(r->pool,session_id);
452 char* cookieend = strchr(cookiebuf,';');
454 *cookieend = '\0'; /* Ignore anyting after a ; */
455 session_id=cookiebuf;
457 // Make sure this session is still valid
458 RPCError* status = NULL;
461 status = shire.sessionIsValid(session_id, r->connection->remote_ip,
464 } catch (ShibTargetException &e) {
465 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
466 "shire_check_user(): %s", e.what());
468 markupProcessor.insert ("errorType", "SHIRE Processing Error");
469 markupProcessor.insert ("errorText", e.what());
470 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
471 return shire_error_page (r, shireError.c_str(), markupProcessor);
475 if (status->isError()) {
477 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,r,
478 "shire_check_user() session invalid: %s",
479 status->error_msg.c_str());
481 // Oops, session is invalid. Redirect to WAYF.
482 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
483 "?shire=",shire_location,
484 "&target=",url_encode(r,targeturl),NULL);
485 ap_table_setn(r->headers_out,"Location",wayf);
492 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
493 "shire_check_user() success");
498 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
499 "shire_check_user() server error");
503 extern "C" int shire_post_handler (request_rec* r)
505 ostringstream threadid;
506 threadid << "[" << getpid() << "] shire" << '\0';
507 saml::NDC ndc(threadid.str().c_str());
509 ShibINI& ini = g_Config->getINI();
510 ShibMLP markupProcessor;
512 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
513 "shire_post_handler() ENTER");
515 const char* targeturl=get_target(r,ap_construct_url(r->pool,r->unparsed_uri,r));
517 const char * shire_location = get_shire_location(r,targeturl,true);
518 if (!shire_location) return SERVER_ERROR;
519 string shire_url = get_shire_location(r,targeturl,false);
521 const char* serverName = get_service_name (r);
523 bool has_tag = ini.get_tag (serverName, "checkIPAddress", true, &tag);
525 config.checkIPAddress = (has_tag ? ShibINI::boolean (tag) : false);
528 if (! ini.get_tag (serverName, "cookieName", true, &shib_cookie)) {
529 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
530 "shire_check_user: no cookieName configuration for %s",
536 if (! ini.get_tag (serverName, "wayfURL", true, &wayfLocation)) {
537 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
538 "shire_check_user: no wayfURL configuration for %s",
544 if (! ini.get_tag (serverName, "shireError", true, &shireError)) {
545 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
546 "shire_check_user: no shireError configuration for %s",
551 ini.get_tag (serverName, "supportContact", true, &tag);
552 markupProcessor.insert ("supportContact", has_tag ? tag : "");
553 has_tag = ini.get_tag (serverName, "logoLocation", true, &tag);
554 markupProcessor.insert ("logoLocation", has_tag ? tag : "");
555 markupProcessor.insert ("requestURL", targeturl);
557 SHIRE shire(rpc_handle, config, shire_url);
559 // Process SHIRE POST
561 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
562 "shire_post_handler() Beginning SHIRE POST processing");
566 if (! ini.get_tag (serverName, "shireSSLOnly", true, &sslonly))
567 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
568 "shire_post_handler: no shireSSLOnly configuration");
570 // Make sure this is SSL, if it should be
571 if (ShibINI::boolean(sslonly) && strcmp(ap_http_method(r),"https"))
572 throw ShibTargetException (SHIBRPC_OK,
573 "blocked non-SSL access to SHIRE POST processor");
575 // Make sure this is a POST
576 if (strcasecmp (r->method, "POST"))
577 throw ShibTargetException (SHIBRPC_OK,
578 "blocked non-POST to SHIRE POST processor");
580 // Sure sure this POST is an appropriate content type
581 const char *ct = ap_table_get (r->headers_in, "Content-type");
582 if (!ct || strcasecmp (ct, "application/x-www-form-urlencoded"))
583 throw ShibTargetException (SHIBRPC_OK,
585 "blocked bad content-type to SHIRE POST processor: %s",
588 // Make sure the "bytes sent" is a reasonable number
589 if (r->bytes_sent > 1024*1024) // 1MB?
590 throw ShibTargetException (SHIBRPC_OK,
591 "blocked too-large a post to SHIRE POST processor");
593 // Read the posted data
594 ApacheRequest *ap_req = ApacheRequest_new(r);
595 int err = ApacheRequest_parse(ap_req);
597 throw ShibTargetException (SHIBRPC_OK,
599 "ApacheRequest_parse() failed with %d.", err));
602 // Make sure the target parameter exists
603 const char *target = ApacheRequest_param(ap_req, "TARGET");
604 if (!target || *target == '\0')
606 throw ShibTargetException (SHIBRPC_OK,
607 "SHIRE POST failed to find TARGET");
609 // Make sure the SAML Response parameter exists
610 const char *post = ApacheRequest_param(ap_req, "SAMLResponse");
611 if (!post || *post == '\0')
613 throw ShibTargetException (SHIBRPC_OK,
614 "SHIRE POST failed to find SAMLResponse");
616 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
617 "shire_post_handler() Processing POST for target: %s", target);
621 RPCError* status = shire.sessionCreate(post, r->connection->remote_ip, cookie);
623 if (status->isError()) {
624 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
625 "shire_post_handler() POST process failed (%d): %s",
626 status->status, status->error_msg.c_str());
628 if (status->isRetryable()) {
629 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,r,
630 "shire_post_handler() Retrying POST by redirecting to WAYF");
632 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
633 "?shire=",shire_location,
634 "&target=",url_encode(r,target),NULL);
635 ap_table_setn(r->headers_out,"Location",wayf);
640 // return this error to the user.
641 markupProcessor.insert (*status);
643 return shire_error_page (r, shireError.c_str(), markupProcessor);
647 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
648 "shire_post_handler() POST process succeeded. New cookie: %s",
651 // We've got a good session, set the cookie...
652 char * domain = NULL;
653 char * new_cookie = ap_psprintf(r->pool, "%s=%s; path=/%s%s",
656 (domain ? "; domain=" : ""),
657 (domain ? domain : ""));
659 ap_table_setn(r->err_headers_out, "Set-Cookie", new_cookie);
660 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
661 "shire_post_handler() Set cookie: %s", new_cookie);
663 // ... and redirect to the target
664 char* redir=ap_pstrcat(r->pool,url_encode(r,target),NULL);
665 ap_table_setn(r->headers_out, "Location", target);
668 } catch (ShibTargetException &e) {
669 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
670 "shire_post_handler(): %s", e.what());
672 markupProcessor.insert ("errorType", "SHIRE Processing Error");
673 markupProcessor.insert ("errorText", e.what());
674 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
675 return shire_error_page (r, shireError.c_str(), markupProcessor);
678 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
679 "shire_post_handler() server error");
684 handler_rec shire_handlers[] = {
685 { "shib-shire-post", shire_post_handler },
689 extern "C" void mod_shire_init (server_rec*r, pool* p)
691 ShibTargetConfig::preinit();
694 module MODULE_VAR_EXPORT shire_module = {
695 STANDARD_MODULE_STUFF,
696 mod_shire_init, /* initializer */
697 create_shire_dir_config, /* dir config creater */
698 merge_shire_dir_config, /* dir merger --- default is to override */
699 create_shire_server_config, /* server config */
700 merge_shire_server_config, /* merge server config */
701 shire_cmds, /* command table */
702 shire_handlers, /* handlers */
703 NULL, /* filename translation */
704 shire_check_user, /* check_user_id */
705 NULL, /* check auth */
706 NULL, /* check access */
707 NULL, /* type_checker */
710 NULL, /* header parser */
711 shire_child_init, /* child_init */
712 shire_child_exit, /* child_exit */
713 NULL /* post read-request */