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"
19 // For POST processing from Apache
20 #include <libapreq/apache_request.h>
23 #include <saml/saml.h>
24 #include <shib/shib.h>
25 #include <shib-target/shib-target.h>
33 using namespace shibboleth;
34 using namespace shibtarget;
36 extern "C" module MODULE_VAR_EXPORT shire_module;
39 char* g_szSHIREURL = NULL;
40 char* g_szSHIREConfig = NULL;
41 RPCHandle *rpc_handle = NULL;
42 ShibTargetConfig * g_szConfig = NULL;
45 // per-server configuration structure
46 struct shire_server_config
48 char* serverName; // Name of this server
51 // creates the per-server configuration
52 extern "C" void* create_shire_server_config (pool * p, server_rec * s)
54 shire_server_config* sc=(shire_server_config*)ap_pcalloc(p,sizeof(shire_server_config));
58 // overrides server configuration in virtual servers
59 extern "C" void* merge_shire_server_config (pool* p, void* base, void* sub)
61 shire_server_config* sc=(shire_server_config*)ap_pcalloc(p,sizeof(shire_server_config));
62 shire_server_config* parent=(shire_server_config*)base;
63 shire_server_config* child=(shire_server_config*)sub;
65 if (child->serverName)
66 sc->serverName=ap_pstrdup(p,child->serverName);
67 else if (parent->serverName)
68 sc->serverName=ap_pstrdup(p,parent->serverName);
75 // per-dir module configuration structure
76 struct shire_dir_config
78 int bBasicHijack; // activate for AuthType Basic?
79 int bSSLOnly; // only over SSL?
80 SHIREConfig config; // SHIRE Configuration
83 // creates per-directory config structure
84 extern "C" void* create_shire_dir_config (pool* p, char* d)
86 shire_dir_config* dc=(shire_dir_config*)ap_pcalloc(p,sizeof(shire_dir_config));
87 dc->bBasicHijack = -1;
89 dc->config.lifetime = -1;
90 dc->config.timeout = -1;
94 // overrides server configuration in directories
95 extern "C" void* merge_shire_dir_config (pool* p, void* base, void* sub)
97 shire_dir_config* dc=(shire_dir_config*)ap_pcalloc(p,sizeof(shire_dir_config));
98 shire_dir_config* parent=(shire_dir_config*)base;
99 shire_dir_config* child=(shire_dir_config*)sub;
101 dc->bBasicHijack=((child->bBasicHijack==-1) ? parent->bBasicHijack : child->bBasicHijack);
102 dc->bSSLOnly=((child->bSSLOnly==-1) ? parent->bSSLOnly : child->bSSLOnly);
103 dc->config.lifetime=((child->config.lifetime==-1) ? parent->config.lifetime : child->config.lifetime);
104 dc->config.timeout=((child->config.timeout==-1) ? parent->config.timeout : child->config.timeout);
108 // generic global slot handlers
109 extern "C" const char* ap_set_global_string_slot(cmd_parms* parms, void*, const char* arg)
111 *((char**)(parms->info))=ap_pstrdup(parms->pool,arg);
115 // generic per-server slot handlers
116 extern "C" const char* ap_set_server_string_slot(cmd_parms* parms, void*, const char* arg)
118 char* base=(char*)ap_get_module_config(parms->server->module_config,&shire_module);
119 int offset=(int)parms->info;
120 *((char**)(base + offset))=ap_pstrdup(parms->pool,arg);
124 // some shortcuts for directory config slots
125 extern "C" const char* set_lifetime(cmd_parms* parms, shire_dir_config* dc, const char* arg)
127 dc->config.lifetime=atoi(arg);
131 extern "C" const char* set_timeout(cmd_parms* parms, shire_dir_config* dc, const char* arg)
133 dc->config.timeout=atoi(arg);
137 typedef const char* (*config_fn_t)(void);
139 // SHIRE Module commands
141 static command_rec shire_cmds[] = {
142 {"SHIREConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIREConfig,
143 RSRC_CONF, TAKE1, "Path to SHIRE ini file."},
144 {"SHIREURL", (config_fn_t)ap_set_global_string_slot, &g_szSHIREURL,
145 RSRC_CONF, TAKE1, "SHIRE POST processor URL."},
147 {"ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
148 (void *) XtOffsetOf (shire_dir_config, bBasicHijack),
149 OR_AUTHCFG, FLAG, "Respond to AuthType Basic and convert to shib?"},
150 {"ShibSSLOnly", (config_fn_t)ap_set_flag_slot,
151 (void *) XtOffsetOf (shire_dir_config, bSSLOnly),
152 OR_AUTHCFG, FLAG, "Require SSL when accessing a secured directory?"},
153 {"ShibAuthLifetime", (config_fn_t)set_lifetime, NULL,
154 OR_AUTHCFG, TAKE1, "Lifetime of session in seconds."},
155 {"ShibAuthTimeout", (config_fn_t)set_timeout, NULL,
156 OR_AUTHCFG, TAKE1, "Timeout for session in seconds."},
164 * Things to do when the child process is initialized.
166 extern "C" void shire_child_init(server_rec* s, pool* p)
168 // Initialize runtime components.
171 ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,s,
172 "shire_child_init(): already initialized!");
177 g_szConfig = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, g_szSHIREConfig));
179 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
180 "shire_child_init() failed to initialize SHIB Target");
184 // Create the RPC Handle.. Note: this should be per _thread_
185 // if there is some way to do that reasonably..
186 rpc_handle = new RPCHandle(SHIB_SHAR_SOCKET, SHIBRPC_PROG, SHIBRPC_VERS_1);
188 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,"shire_child_init() done");
196 extern "C" void shire_child_exit(server_rec* s, pool* p)
199 g_szConfig->shutdown();
201 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,"shire_child_exit() done");
204 inline char hexchar(unsigned short s)
206 return (s<=9) ? ('0' + s) : ('A' + s - 10);
209 static char* url_encode(request_rec* r, const char* s)
211 static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
212 char* ret=(char*)ap_palloc(r->pool,sizeof(char)*3*strlen(s)+1);
214 unsigned long count=0;
217 if (strchr(badchars,*s)!=NULL || *s<=0x1F || *s>=0x7F)
220 ret[count++]=hexchar(*s >> 4);
221 ret[count++]=hexchar(*s & 0x0F);
230 // Return the "name" of this server to look up configuration options
231 static const char* get_service_name(request_rec* r)
233 shire_server_config* sc =
234 (shire_server_config*) ap_get_module_config(r->server->module_config,
238 return sc->serverName;
240 return ap_get_server_name(r);
243 // return the "normalized" target URL
244 static const char* get_target(request_rec* r, const char* target)
246 const char* serverName = get_service_name(r);
248 if ((g_szConfig->getINI()).get_tag (serverName, "normalizeRequest", true, &tag))
250 if (ShibINI::boolean (tag))
252 const char* colon=strchr(target,':');
253 const char* slash=strchr(colon+3,'/');
254 const char* second_colon=strchr(colon+3,':');
255 return ap_pstrcat(r->pool,ap_pstrndup(r->pool,target,colon+3-target),
256 ap_get_server_name(r),
257 (second_colon && second_colon < slash) ?
258 second_colon : slash,
265 static const char* get_shire_location(request_rec* r, const char* target, bool encode)
267 ShibINI& ini = g_szConfig->getINI();
268 const char* serverName = get_service_name(r);
269 string shire_location;
272 shire_location = g_szSHIREURL;
273 else if (! ini.get_tag (serverName, "shireURL", true, &shire_location)) {
274 ap_log_rerror(APLOG_MARK,APLOG_ERR,r,
275 "shire_get_location() no shireURL configuration for %s",
280 const char* shire = shire_location.c_str();
284 return url_encode(r,shire);
286 return ap_pstrdup(r->pool,shire);
288 const char* colon=strchr(target,':');
289 const char* slash=strchr(colon+3,'/');
291 return url_encode(r,ap_pstrcat(r->pool,
292 ap_pstrndup(r->pool,target,slash-target),
295 return ap_pstrcat(r->pool, ap_pstrndup(r->pool,target,slash-target),
299 static bool is_shire_location(request_rec* r, const char* target)
301 const char* shire = get_shire_location(r, target, false);
303 if (!shire) return false;
305 if (!strstr(target, shire))
308 return (!strcmp(target,shire));
311 static int shire_error_page(request_rec* r, const char* filename, ShibMLP& mlp)
313 ifstream infile (filename);
315 ap_log_rerror(APLOG_MARK,APLOG_ERR,r,
316 "shire_error_page() cannot open %s", filename);
320 string res = mlp.run(infile);
321 r->content_type = ap_psprintf(r->pool, "text/html");
322 ap_send_http_header(r);
323 ap_rprintf(r, res.c_str());
327 extern "C" int shire_check_user(request_rec* r)
329 ostringstream threadid;
330 threadid << "[" << getpid() << "] shire" << '\0';
331 saml::NDC ndc(threadid.str().c_str());
333 ShibINI& ini = g_szConfig->getINI();
334 ShibMLP markupProcessor;
336 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
337 "shire_check_user: ENTER");
339 shire_dir_config* dc=
340 (shire_dir_config*)ap_get_module_config(r->per_dir_config,&shire_module);
342 const char* targeturl=get_target(r,ap_construct_url(r->pool,r->unparsed_uri,r));
344 const char * shire_location = get_shire_location(r,targeturl,true);
345 if (!shire_location) return SERVER_ERROR;
346 string shire_url = get_shire_location(r,targeturl,false);
348 const char* serverName = get_service_name (r);
350 bool has_tag = ini.get_tag (serverName, "checkIPAddress", true, &tag);
351 dc->config.checkIPAddress = (has_tag ? ShibINI::boolean (tag) : false);
354 if (! ini.get_tag (serverName, "cookieName", true, &shib_cookie)) {
355 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
356 "shire_check_user: no cookieName configuration for %s",
362 if (! ini.get_tag (serverName, "wayfURL", true, &wayfLocation)) {
363 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
364 "shire_check_user: no wayfURL configuration for %s",
370 if (! ini.get_tag (serverName, "shireError", true, &shireError)) {
371 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
372 "shire_check_user: no shireError configuration for %s",
377 ini.get_tag (serverName, "supportContact", true, &tag);
378 markupProcessor.insert ("supportContact", has_tag ? tag : "");
379 has_tag = ini.get_tag (serverName, "logoLocation", true, &tag);
380 markupProcessor.insert ("logoLocation", has_tag ? tag : "");
381 markupProcessor.insert ("requestURL", targeturl);
383 SHIRE shire(rpc_handle, dc->config, shire_url);
385 if (is_shire_location (r, targeturl)) {
386 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
387 "shire_check_user: REQUEST FOR SHIRE! Maybe you did not configure the SHIRE Handler?");
391 // Regular access to arbitrary resource...check AuthType
393 const char *auth_type=ap_auth_type (r);
397 if (strcasecmp(auth_type,"shibboleth"))
399 if (!strcasecmp(auth_type,"basic") && dc->bBasicHijack==1)
401 core_dir_config* conf=
402 (core_dir_config*)ap_get_module_config(r->per_dir_config,
403 ap_find_linked_module("http_core.c"));
404 conf->ap_auth_type="shibboleth";
410 // set the connection authtype
411 if (r->connection) r->connection->ap_auth_type = "shibboleth";
413 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
414 "shire_check_user() Shib check for %s", targeturl);
417 if (dc->bSSLOnly==1 && strcmp(ap_http_method(r),"https"))
419 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
420 "shire_check_user() blocked non-SSL access");
424 // We're in charge, so check for cookie.
425 const char* session_id=NULL;
426 const char* cookies=ap_table_get(r->headers_in,"Cookie");
429 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
430 "shire_check_user() cookies found: %s",
433 if (!cookies || !(session_id=strstr(cookies,shib_cookie.c_str())))
435 // No cookie. Redirect to WAYF.
436 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
437 "shire_check_user() no cookie found -- redirecting to WAYF");
438 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
439 "?shire=",shire_location,
440 "&target=",url_encode(r,targeturl),NULL);
441 ap_table_setn(r->headers_out,"Location",wayf);
445 // Yep, we found a cookie -- pull it out (our session_id)
446 session_id+=strlen(shib_cookie.c_str()) + 1; /* Skip over the '=' */
447 char* cookiebuf = ap_pstrdup(r->pool,session_id);
448 char* cookieend = strchr(cookiebuf,';');
450 *cookieend = '\0'; /* Ignore anyting after a ; */
451 session_id=cookiebuf;
453 // Make sure this session is still valid
454 RPCError* status = NULL;
457 status = shire.sessionIsValid(session_id, r->connection->remote_ip);
459 } catch (ShibTargetException &e) {
460 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
461 "shire_check_user(): %s", e.what());
463 markupProcessor.insert ("errorType", "SHIRE Processing Error");
464 markupProcessor.insert ("errorText", e.what());
465 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
466 return shire_error_page (r, shireError.c_str(), markupProcessor);
470 if (status->isError()) {
472 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,r,
473 "shire_check_user() session invalid: %s",
474 status->error_msg.c_str());
476 // Oops, session is invalid. Redirect to WAYF.
477 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
478 "?shire=",shire_location,
479 "&target=",url_encode(r,targeturl),NULL);
480 ap_table_setn(r->headers_out,"Location",wayf);
487 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
488 "shire_check_user() success");
493 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
494 "shire_check_user() server error");
498 extern "C" int shire_post_handler (request_rec* r)
500 ostringstream threadid;
501 threadid << "[" << getpid() << "] shire" << '\0';
502 saml::NDC ndc(threadid.str().c_str());
504 ShibINI& ini = g_szConfig->getINI();
505 ShibMLP markupProcessor;
507 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
508 "shire_post_handler() ENTER");
510 const char* targeturl=get_target(r,ap_construct_url(r->pool,r->unparsed_uri,r));
512 const char * shire_location = get_shire_location(r,targeturl,true);
513 if (!shire_location) return SERVER_ERROR;
514 string shire_url = get_shire_location(r,targeturl,false);
516 const char* serverName = get_service_name (r);
518 bool has_tag = ini.get_tag (serverName, "checkIPAddress", true, &tag);
520 config.checkIPAddress = (has_tag ? ShibINI::boolean (tag) : false);
523 if (! ini.get_tag (serverName, "cookieName", true, &shib_cookie)) {
524 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
525 "shire_check_user: no cookieName configuration for %s",
531 if (! ini.get_tag (serverName, "wayfURL", true, &wayfLocation)) {
532 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
533 "shire_check_user: no wayfURL configuration for %s",
539 if (! ini.get_tag (serverName, "shireError", true, &shireError)) {
540 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
541 "shire_check_user: no shireError configuration for %s",
546 ini.get_tag (serverName, "supportContact", true, &tag);
547 markupProcessor.insert ("supportContact", has_tag ? tag : "");
548 has_tag = ini.get_tag (serverName, "logoLocation", true, &tag);
549 markupProcessor.insert ("logoLocation", has_tag ? tag : "");
550 markupProcessor.insert ("requestURL", targeturl);
552 SHIRE shire(rpc_handle, config, shire_url);
554 // Process SHIRE POST
556 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
557 "shire_post_handler() Beginning SHIRE POST processing");
561 if (! ini.get_tag (serverName, "shireSSLOnly", true, &sslonly))
562 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
563 "shire_post_handler: no shireSSLOnly configuration");
565 // Make sure this is SSL, if it should be
566 if (ShibINI::boolean(sslonly) && strcmp(ap_http_method(r),"https"))
567 throw ShibTargetException (SHIBRPC_OK,
568 "blocked non-SSL access to SHIRE POST processor");
570 // Make sure this is a POST
571 if (strcasecmp (r->method, "POST"))
572 throw ShibTargetException (SHIBRPC_OK,
573 "blocked non-POST to SHIRE POST processor");
575 // Sure sure this POST is an appropriate content type
576 const char *ct = ap_table_get (r->headers_in, "Content-type");
577 if (!ct || strcasecmp (ct, "application/x-www-form-urlencoded"))
578 throw ShibTargetException (SHIBRPC_OK,
580 "blocked bad content-type to SHIRE POST processor: %s",
583 // Make sure the "bytes sent" is a reasonable number
584 if (r->bytes_sent > 1024*1024) // 1MB?
585 throw ShibTargetException (SHIBRPC_OK,
586 "blocked too-large a post to SHIRE POST processor");
588 // Read the posted data
589 ApacheRequest *ap_req = ApacheRequest_new(r);
590 int err = ApacheRequest_parse(ap_req);
592 throw ShibTargetException (SHIBRPC_OK,
594 "ApacheRequest_parse() failed with %d.", err));
597 // Make sure the target parameter exists
598 const char *target = ApacheRequest_param(ap_req, "TARGET");
599 if (!target || *target == '\0')
601 throw ShibTargetException (SHIBRPC_OK,
602 "SHIRE POST failed to find TARGET");
604 // Make sure the SAML Response parameter exists
605 const char *post = ApacheRequest_param(ap_req, "SAMLResponse");
606 if (!post || *post == '\0')
608 throw ShibTargetException (SHIBRPC_OK,
609 "SHIRE POST failed to find SAMLResponse");
611 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
612 "shire_post_handler() Processing POST for target: %s", target);
616 RPCError* status = shire.sessionCreate(post, r->connection->remote_ip, cookie);
618 if (status->isError()) {
619 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
620 "shire_post_handler() POST process failed (%d): %s",
621 status->status, status->error_msg.c_str());
623 if (status->isRetryable()) {
624 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,r,
625 "shire_post_handler() Retrying POST by redirecting to WAYF");
627 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
628 "?shire=",shire_location,
629 "&target=",url_encode(r,target),NULL);
630 ap_table_setn(r->headers_out,"Location",wayf);
635 // return this error to the user.
636 markupProcessor.insert (*status);
638 return shire_error_page (r, shireError.c_str(), markupProcessor);
642 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
643 "shire_post_handler() POST process succeeded. New cookie: %s",
646 // We've got a good session, set the cookie...
647 char * domain = NULL;
648 char * new_cookie = ap_psprintf(r->pool, "%s=%s; path=/%s%s",
651 (domain ? "; domain=" : ""),
652 (domain ? domain : ""));
654 ap_table_setn(r->err_headers_out, "Set-Cookie", new_cookie);
655 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
656 "shire_post_handler() Set cookie: %s", new_cookie);
658 // ... and redirect to the target
659 char* redir=ap_pstrcat(r->pool,url_encode(r,target),NULL);
660 ap_table_setn(r->headers_out, "Location", target);
663 } catch (ShibTargetException &e) {
664 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
665 "shire_post_handler(): %s", e.what());
667 markupProcessor.insert ("errorType", "SHIRE Processing Error");
668 markupProcessor.insert ("errorText", e.what());
669 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
670 return shire_error_page (r, shireError.c_str(), markupProcessor);
673 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
674 "shire_post_handler() server error");
679 handler_rec shire_handlers[] = {
680 { "shib-shire-post", shire_post_handler },
684 extern "C" void mod_shire_init (server_rec*r, pool* p)
686 ShibTargetConfig::preinit();
689 module MODULE_VAR_EXPORT shire_module = {
690 STANDARD_MODULE_STUFF,
691 mod_shire_init, /* initializer */
692 create_shire_dir_config, /* dir config creater */
693 merge_shire_dir_config, /* dir merger --- default is to override */
694 create_shire_server_config, /* server config */
695 merge_shire_server_config, /* merge server config */
696 shire_cmds, /* command table */
697 shire_handlers, /* handlers */
698 NULL, /* filename translation */
699 shire_check_user, /* check_user_id */
700 NULL, /* check auth */
701 NULL, /* check access */
702 NULL, /* type_checker */
705 NULL, /* header parser */
706 shire_child_init, /* child_init */
707 shire_child_exit, /* child_exit */
708 NULL /* post read-request */