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 ShibTargetConfig* g_Config = NULL;
46 // per-dir module configuration structure
47 struct shire_dir_config
49 int bBasicHijack; // activate for AuthType Basic?
50 int bSSLOnly; // only over SSL?
51 SHIREConfig config; // SHIRE Configuration
54 // creates per-directory config structure
55 extern "C" void* create_shire_dir_config (pool* p, char* d)
57 shire_dir_config* dc=(shire_dir_config*)ap_pcalloc(p,sizeof(shire_dir_config));
58 dc->bBasicHijack = -1;
60 dc->config.lifetime = -1;
61 dc->config.timeout = -1;
65 // overrides server configuration in directories
66 extern "C" void* merge_shire_dir_config (pool* p, void* base, void* sub)
68 shire_dir_config* dc=(shire_dir_config*)ap_pcalloc(p,sizeof(shire_dir_config));
69 shire_dir_config* parent=(shire_dir_config*)base;
70 shire_dir_config* child=(shire_dir_config*)sub;
72 dc->bBasicHijack=((child->bBasicHijack==-1) ? parent->bBasicHijack : child->bBasicHijack);
73 dc->bSSLOnly=((child->bSSLOnly==-1) ? parent->bSSLOnly : child->bSSLOnly);
74 dc->config.lifetime=((child->config.lifetime==-1) ? parent->config.lifetime : child->config.lifetime);
75 dc->config.timeout=((child->config.timeout==-1) ? parent->config.timeout : child->config.timeout);
79 // generic global slot handlers
80 extern "C" const char* ap_set_global_string_slot(cmd_parms* parms, void*, const char* arg)
82 *((char**)(parms->info))=ap_pstrdup(parms->pool,arg);
86 // some shortcuts for directory config slots
87 extern "C" const char* set_lifetime(cmd_parms* parms, shire_dir_config* dc, const char* arg)
89 dc->config.lifetime=atoi(arg);
93 extern "C" const char* set_timeout(cmd_parms* parms, shire_dir_config* dc, const char* arg)
95 dc->config.timeout=atoi(arg);
99 typedef const char* (*config_fn_t)(void);
101 // SHIRE Module commands
103 static command_rec shire_cmds[] = {
104 {"SHIREConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIREConfig,
105 RSRC_CONF, TAKE1, "Path to SHIRE ini file."},
106 {"SHIREURL", (config_fn_t)ap_set_global_string_slot, &g_szSHIREURL,
107 RSRC_CONF, TAKE1, "SHIRE POST processor URL."},
109 {"ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
110 (void *) XtOffsetOf (shire_dir_config, bBasicHijack),
111 OR_AUTHCFG, FLAG, "Respond to AuthType Basic and convert to shib?"},
112 {"ShibSSLOnly", (config_fn_t)ap_set_flag_slot,
113 (void *) XtOffsetOf (shire_dir_config, bSSLOnly),
114 OR_AUTHCFG, FLAG, "Require SSL when accessing a secured directory?"},
115 {"ShibAuthLifetime", (config_fn_t)set_lifetime, NULL,
116 OR_AUTHCFG, TAKE1, "Lifetime of session in seconds."},
117 {"ShibAuthTimeout", (config_fn_t)set_timeout, NULL,
118 OR_AUTHCFG, TAKE1, "Timeout for session in seconds."},
125 * Things to do when the child process is initialized.
127 extern "C" void shire_child_init(server_rec* s, pool* p)
129 // Initialize runtime components.
131 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
132 "shire_child_init() starting");
135 ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,s,
136 "shire_child_init(): already initialized!");
141 g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, g_szSHIREConfig));
143 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
144 "shire_child_init() failed to initialize SHIB Target");
148 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,"shire_child_init() done");
156 extern "C" void shire_child_exit(server_rec* s, pool* p)
158 g_Config->shutdown();
160 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,"shire_child_exit() done");
163 inline char hexchar(unsigned short s)
165 return (s<=9) ? ('0' + s) : ('A' + s - 10);
168 static char* url_encode(request_rec* r, const char* s)
170 static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
171 char* ret=(char*)ap_palloc(r->pool,sizeof(char)*3*strlen(s)+1);
173 unsigned long count=0;
176 if (strchr(badchars,*s)!=NULL || *s<=0x1F || *s>=0x7F)
179 ret[count++]=hexchar(*s >> 4);
180 ret[count++]=hexchar(*s & 0x0F);
189 static const char* get_application_id(request_rec* r)
191 ApplicationMapper mapper;
192 return ap_pstrdup(r->pool,
193 mapper->getApplicationFromParsedURL(
194 ap_http_method(r), ap_get_server_name(r), ap_get_server_port(r), r->unparsed_uri
199 static const char* get_shire_location(request_rec* r, const char* target, const char* application_id)
201 string shire_location;
202 ShibINI& ini = g_Config->getINI();
204 if (!ini.get_tag (application_id, "shireURL", true, &shire_location)) {
205 ap_log_rerror(APLOG_MARK,APLOG_ERR,r,
206 "shire_get_location() no shireURL configuration for %s", application_id);
210 const char* shire = shire_location.c_str();
213 return ap_pstrdup(r->pool,shire);
215 const char* colon=strchr(target,':');
216 const char* slash=strchr(colon+3,'/');
217 return ap_pstrcat(r->pool, ap_pstrndup(r->pool,target,slash-target), shire, NULL);
220 static int shire_error_page(request_rec* r, const char* filename, ShibMLP& mlp)
222 ifstream infile (filename);
224 ap_log_rerror(APLOG_MARK,APLOG_ERR,r,
225 "shire_error_page() cannot open %s", filename);
229 string res = mlp.run(infile);
230 r->content_type = ap_psprintf(r->pool, "text/html");
231 ap_send_http_header(r);
232 ap_rprintf(r, res.c_str());
236 extern "C" int shire_check_user(request_rec* r)
238 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_check_user: ENTER");
239 shire_dir_config* dc=(shire_dir_config*)ap_get_module_config(r->per_dir_config,&shire_module);
241 // This will always be normalized, because Apache uses ap_get_server_name in this API call.
242 const char* targeturl=ap_construct_url(r->pool,r->unparsed_uri,r);
244 // Map request to application ID, which is the key for config lookup.
245 const char* application_id=get_application_id(r);
247 // Get unescaped location of this application's assertion consumer service.
248 const char* unescaped_shire = get_shire_location(r, targeturl, application_id);
250 if (strstr(targeturl,unescaped_shire)) {
251 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
252 "shire_check_user: REQUEST FOR SHIRE! Maybe you did not configure the SHIRE Handler?");
256 // Regular access to arbitrary resource...check AuthType
257 const char *auth_type=ap_auth_type (r);
261 if (strcasecmp(auth_type,"shibboleth"))
263 if (!strcasecmp(auth_type,"basic") && dc->bBasicHijack==1)
265 core_dir_config* conf=
266 (core_dir_config*)ap_get_module_config(r->per_dir_config,
267 ap_find_linked_module("http_core.c"));
268 conf->ap_auth_type="shibboleth";
274 // set the connection authtype
276 r->connection->ap_auth_type = "shibboleth";
279 if (dc->bSSLOnly==1 && strcmp(ap_http_method(r),"https"))
281 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,"shire_check_user() blocked non-SSL access");
286 ostringstream threadid;
287 threadid << "[" << getpid() << "] shire" << '\0';
288 saml::NDC ndc(threadid.str().c_str());
290 ShibINI& ini = g_Config->getINI();
292 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
293 "shire_check_user() Shib check for %s", targeturl);
297 bool has_tag = ini.get_tag (application_id, "checkIPAddress", true, &tag);
298 dc->config.checkIPAddress = (has_tag ? ShibINI::boolean (tag) : false);
301 if (!ini.get_tag(application_id, "cookieName", true, &shib_cookie)) {
302 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
303 "shire_check_user: no cookieName configuration for %s", application_id);
308 if (!ini.get_tag(application_id, "wayfURL", true, &wayfLocation)) {
309 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
310 "shire_check_user: no wayfURL configuration for %s", application_id);
315 if (!ini.get_tag(application_id, "shireError", true, &shireError)) {
316 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
317 "shire_check_user: no shireError configuration for %s", application_id);
321 SHIRE shire(dc->config, unescaped_shire);
323 // We're in charge, so check for cookie.
324 const char* session_id=NULL;
325 const char* cookies=ap_table_get(r->headers_in,"Cookie");
329 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
330 "shire_check_user() cookies found: %s",cookies);
331 if (session_id=strstr(cookies,shib_cookie.c_str()))
333 // Yep, we found a cookie -- pull it out (our session_id)
334 session_id+=strlen(shib_cookie.c_str()) + 1; /* Skip over the '=' */
335 char* cookiebuf = ap_pstrdup(r->pool,session_id);
336 char* cookieend = strchr(cookiebuf,';');
338 *cookieend = '\0'; /* Ignore anyting after a ; */
339 session_id=cookiebuf;
343 if (!session_id || !*session_id)
345 // No acceptable cookie. Redirect to WAYF.
346 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
347 "shire_check_user() no cookie found -- redirecting to WAYF");
349 sprintf(timebuf,"%u",time(NULL));
350 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
351 "?shire=",url_encode(r,unescaped_shire),
352 "&target=",url_encode(r,targeturl),
354 "&providerId=",application_id,NULL);
355 ap_table_setn(r->headers_out,"Location",wayf);
359 // Make sure this session is still valid
360 RPCError* status = NULL;
361 ShibMLP markupProcessor;
362 has_tag = ini.get_tag(application_id, "supportContact", true, &tag);
363 markupProcessor.insert("supportContact", has_tag ? tag : "");
364 has_tag = ini.get_tag(application_id, "logoLocation", true, &tag);
365 markupProcessor.insert("logoLocation", has_tag ? tag : "");
366 markupProcessor.insert("requestURL", targeturl);
369 status = shire.sessionIsValid(session_id, r->connection->remote_ip,application_id);
371 catch (ShibTargetException &e) {
372 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_check_user(): %s", e.what());
373 markupProcessor.insert ("errorType", "SHIRE Processing Error");
374 markupProcessor.insert ("errorText", e.what());
375 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
376 return shire_error_page (r, shireError.c_str(), markupProcessor);
379 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_check_user(): caught unexpected error");
380 markupProcessor.insert ("errorType", "SHIRE Processing Error");
381 markupProcessor.insert ("errorText", "Unexpected Exception");
382 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
383 return shire_error_page (r, shireError.c_str(), markupProcessor);
387 if (status->isError()) {
388 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,r,
389 "shire_check_user() session invalid: %s",
392 if (status->isRetryable()) {
393 // Oops, session is invalid. Redirect to WAYF.
395 sprintf(timebuf,"%u",time(NULL));
396 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
397 "?shire=",url_encode(r,unescaped_shire),
398 "&target=",url_encode(r,targeturl),
400 "&providerId=",application_id,NULL);
401 ap_table_setn(r->headers_out,"Location",wayf);
407 // return the error page to the user
408 markupProcessor.insert (*status);
410 return shire_error_page (r, shireError.c_str(), markupProcessor);
415 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_check_user() success");
419 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,"shire_check_user() server error");
423 extern "C" int shire_post_handler (request_rec* r)
425 ostringstream threadid;
426 threadid << "[" << getpid() << "] shire" << '\0';
427 saml::NDC ndc(threadid.str().c_str());
429 ShibINI& ini = g_Config->getINI();
430 ShibMLP markupProcessor;
432 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_post_handler() ENTER");
434 // This will always be normalized, because Apache uses ap_get_server_name in this API call.
435 const char* targeturl = ap_construct_url(r->pool,r->unparsed_uri,r);
437 // Map request to application ID, which is the key for config lookup.
438 const char* application_id = get_application_id(r);
440 // The SHIRE URL is the current request URL, by definition...
441 const char* unescaped_shire = targeturl;
444 bool has_tag = ini.get_tag(application_id, "checkIPAddress", true, &tag);
446 config.checkIPAddress = (has_tag ? ShibINI::boolean(tag) : false);
449 if (! ini.get_tag(application_id, "cookieName", true, &shib_cookie)) {
450 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
451 "shire_check_user: no cookieName configuration for %s", application_id);
456 if (! ini.get_tag(application_id, "wayfURL", true, &wayfLocation)) {
457 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
458 "shire_check_user: no wayfURL configuration for %s", application_id);
463 if (! ini.get_tag(application_id, "shireError", true, &shireError)) {
464 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
465 "shire_check_user: no shireError configuration for %s", application_id);
469 has_tag = ini.get_tag(application_id, "supportContact", true, &tag);
470 markupProcessor.insert("supportContact", has_tag ? tag : "");
471 has_tag = ini.get_tag(application_id, "logoLocation", true, &tag);
472 markupProcessor.insert("logoLocation", has_tag ? tag : "");
473 markupProcessor.insert("requestURL", targeturl);
475 SHIRE shire(config, unescaped_shire);
477 // Process SHIRE POST
479 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
480 "shire_post_handler() Beginning SHIRE POST processing");
484 if (!ini.get_tag(application_id, "shireSSLOnly", true, &sslonly))
485 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,r,
486 "shire_post_handler: no shireSSLOnly configuration");
488 // Make sure this is SSL, if it should be
489 if (ShibINI::boolean(sslonly) && strcmp(ap_http_method(r),"https"))
490 throw ShibTargetException(SHIBRPC_OK, "blocked non-SSL access to SHIRE POST processor");
492 // Make sure this is a POST
493 if (strcasecmp (r->method, "POST"))
494 throw ShibTargetException(SHIBRPC_OK, "blocked non-POST to SHIRE POST processor");
496 // Sure sure this POST is an appropriate content type
497 const char *ct = ap_table_get (r->headers_in, "Content-type");
498 if (!ct || strcasecmp (ct, "application/x-www-form-urlencoded"))
499 throw ShibTargetException (SHIBRPC_OK,
501 "blocked bad content-type to SHIRE POST processor: %s",
504 // Make sure the "bytes sent" is a reasonable number
505 if (r->bytes_sent > 1024*1024) // 1MB?
506 throw ShibTargetException (SHIBRPC_OK,
507 "blocked too-large a post to SHIRE POST processor");
509 // Read the posted data
510 ApacheRequest *ap_req = ApacheRequest_new(r);
511 int err = ApacheRequest_parse(ap_req);
513 throw ShibTargetException (SHIBRPC_OK,
515 "ApacheRequest_parse() failed with %d.", err));
518 // Make sure the target parameter exists
519 const char *target = ApacheRequest_param(ap_req, "TARGET");
520 if (!target || *target == '\0')
522 throw ShibTargetException (SHIBRPC_OK,
523 "SHIRE POST failed to find TARGET");
525 // Make sure the SAML Response parameter exists
526 const char *post = ApacheRequest_param(ap_req, "SAMLResponse");
527 if (!post || *post == '\0')
529 throw ShibTargetException (SHIBRPC_OK,
530 "SHIRE POST failed to find SAMLResponse");
532 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
533 "shire_post_handler() Processing POST for target: %s", target);
537 RPCError* status = shire.sessionCreate(post, r->connection->remote_ip, application_id, cookie);
539 if (status->isError()) {
540 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,
541 "shire_post_handler() POST process failed (%d): %s",
542 status->getCode(), status->getText());
544 if (status->isRetryable()) {
545 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,r,
546 "shire_post_handler() Retrying POST by redirecting to WAYF");
549 sprintf(timebuf,"%u",time(NULL));
550 char* wayf=ap_pstrcat(r->pool,wayfLocation.c_str(),
551 "?shire=",url_encode(r,unescaped_shire),
552 "&target=",url_encode(r,targeturl),
554 "&providerId=",application_id,NULL);
555 ap_table_setn(r->headers_out,"Location",wayf);
560 // return this error to the user.
561 markupProcessor.insert (*status);
563 return shire_error_page (r, shireError.c_str(), markupProcessor);
567 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
568 "shire_post_handler() POST process succeeded. New cookie: %s",
571 // We've got a good session, set the cookie...
572 char * domain = NULL;
573 char * new_cookie = ap_psprintf(r->pool, "%s=%s; path=/%s%s",
576 (domain ? "; domain=" : ""),
577 (domain ? domain : ""));
579 ap_table_setn(r->err_headers_out, "Set-Cookie", new_cookie);
580 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
581 "shire_post_handler() Set cookie: %s", new_cookie);
583 // ... and redirect to the target
584 char* redir=ap_pstrcat(r->pool,url_encode(r,target),NULL);
585 ap_table_setn(r->headers_out, "Location", target);
588 } catch (ShibTargetException &e) {
589 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,
590 "shire_post_handler(): %s", e.what());
592 markupProcessor.insert ("errorType", "SHIRE Processing Error");
593 markupProcessor.insert ("errorText", e.what());
594 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
595 return shire_error_page (r, shireError.c_str(), markupProcessor);
598 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r,"shire_post_handler(): unexpected exception");
600 markupProcessor.insert ("errorType", "SHIRE Processing Error");
601 markupProcessor.insert ("errorText", "Unexpected Exception");
602 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
603 return shire_error_page (r, shireError.c_str(), markupProcessor);
606 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r,"shire_post_handler() server error");
611 handler_rec shire_handlers[] = {
612 { "shib-shire-post", shire_post_handler },
616 extern "C" void mod_shire_init (server_rec*r, pool* p)
618 ShibTargetConfig::preinit();
621 module MODULE_VAR_EXPORT shire_module = {
622 STANDARD_MODULE_STUFF,
623 mod_shire_init, /* initializer */
624 create_shire_dir_config, /* dir config creater */
625 merge_shire_dir_config, /* dir merger --- default is to override */
626 NULL, /* server config */
627 NULL, /* merge server config */
628 shire_cmds, /* command table */
629 shire_handlers, /* handlers */
630 NULL, /* filename translation */
631 shire_check_user, /* check_user_id */
632 NULL, /* check auth */
633 NULL, /* check access */
634 NULL, /* type_checker */
637 NULL, /* header parser */
638 shire_child_init, /* child_init */
639 shire_child_exit, /* child_exit */
640 NULL /* post read-request */