2 * mod_shib.cpp -- Shibboleth module for Apache-2.0
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 <http_request.h>
21 #include <util_script.h>
22 #include <apr_strings.h>
24 #include <http_core.h>
26 #include <apr_pools.h>
28 #include <xercesc/util/regx/RegularExpression.hpp>
34 // For POST processing from Apache
35 //-- do we still need this? #undef _XOPEN_SOURCE // bombs on solaris
36 #include <apreq_params.h>
40 #include <unistd.h> // for getpid()
44 using namespace shibboleth;
45 using namespace shibtarget;
47 extern "C" AP_MODULE_DECLARE_DATA module mod_shib;
50 char* g_szSHIBConfig = NULL;
51 ShibTargetConfig* g_Config = NULL;
54 // per-dir module configuration structure
55 struct shib_dir_config
58 char* szAuthGrpFile; // Auth GroupFile name
59 int bExportAssertion; // export SAML assertion to the environment?
60 int bRequireAll; // all require directives must match, otherwise OR logic
61 int bDisableRM; // disable the RM functionality?
63 // SHIRE Configuration
64 int bBasicHijack; // activate for AuthType Basic?
65 int bSSLOnly; // only over SSL?
66 SHIREConfig config; // SHIB Configuration
67 RMConfig rm_config; // RM Configuration
70 // creates per-directory config structure
71 extern "C" void* create_shib_dir_config (apr_pool_t* p, char* d)
73 shib_dir_config* dc=(shib_dir_config*)apr_pcalloc(p,sizeof(shib_dir_config));
74 dc->szAuthGrpFile = NULL;
75 dc->bExportAssertion = -1;
79 dc->bBasicHijack = -1;
81 dc->config.lifetime = -1;
82 dc->config.timeout = -1;
86 // overrides server configuration in directories
87 extern "C" void* merge_shib_dir_config (apr_pool_t* p, void* base, void* sub)
89 shib_dir_config* dc=(shib_dir_config*)apr_pcalloc(p,sizeof(shib_dir_config));
90 shib_dir_config* parent=(shib_dir_config*)base;
91 shib_dir_config* child=(shib_dir_config*)sub;
93 if (child->szAuthGrpFile)
94 dc->szAuthGrpFile=apr_pstrdup(p,child->szAuthGrpFile);
95 else if (parent->szAuthGrpFile)
96 dc->szAuthGrpFile=apr_pstrdup(p,parent->szAuthGrpFile);
98 dc->szAuthGrpFile=NULL;
100 dc->bExportAssertion=((child->bExportAssertion==-1) ?
101 parent->bExportAssertion : child->bExportAssertion);
102 dc->bRequireAll=((child->bRequireAll==-1) ?
103 parent->bRequireAll : child->bRequireAll);
104 dc->bDisableRM=((child->bDisableRM==-1) ?
105 parent->bDisableRM : child->bDisableRM);
107 dc->bBasicHijack=((child->bBasicHijack==-1) ?
108 parent->bBasicHijack : child->bBasicHijack);
109 dc->bSSLOnly=((child->bSSLOnly==-1) ? parent->bSSLOnly : child->bSSLOnly);
110 dc->config.lifetime=((child->config.lifetime==-1) ?
111 parent->config.lifetime : child->config.lifetime);
112 dc->config.timeout=((child->config.timeout==-1) ?
113 parent->config.timeout : child->config.timeout);
117 // generic global slot handlers
118 extern "C" const char* ap_set_global_string_slot(cmd_parms* parms, void*,
121 *((char**)(parms->info))=apr_pstrdup(parms->pool,arg);
125 // some shortcuts for directory config slots
126 extern "C" const char* set_lifetime(cmd_parms* parms, shib_dir_config* dc,
129 dc->config.lifetime=atoi(arg);
133 extern "C" const char* set_timeout(cmd_parms* parms, shib_dir_config* dc,
136 dc->config.timeout=atoi(arg);
140 typedef const char* (*config_fn_t)(void);
142 static char* url_encode(request_rec* r, const char* s)
145 char* ret=(char*)apr_palloc(r->pool,sizeof(char)*3*len+1);
147 // apreq_decode takes a string and url-encodes it. Don't ask why
148 // the name is backwards.
149 apreq_decode(ret, s, len);
153 static const char* get_shire_location(request_rec* r, const char* target,
154 const char* application_id)
156 ShibINI& ini = g_Config->getINI();
157 string shire_location;
158 bool shire_ssl_only = false;
160 // Determine if this is supposed to be ssl-only (default == false)
161 if (ini.get_tag (application_id, "shireSSLOnly", true, &shire_location))
162 shire_ssl_only = ShibINI::boolean(shire_location);
164 // Grab the specified shire-location from the config file
165 if (! ini.get_tag (application_id, "shireURL", true, &shire_location)) {
166 ap_log_rerror(APLOG_MARK,APLOG_CRIT,0,r,
167 "shire_get_location() no shireURL configuration for %s",
173 // The "shireURL" can be one of three formats:
175 // 1) a full URI: http://host/foo/bar
176 // 2) a partial URI: http:///foo/bar
177 // 3) a relative path: /foo/bar
179 // # Protocol Host Path
180 // 1 shire shire shire
181 // 2 shire target shire
182 // 3 target target shire
184 // note: if shire_ssl_only is true, make sure the protocol is https
187 const char* shire = shire_location.c_str();
188 const char* path = NULL;
190 // Decide whether to use the shire or the target for the "protocol"
199 // ap_log_rerror(APLOG_MARK,APLOG_DEBUG,0,r,
200 // "get_shire_location: prot=%s, path=%s", prot,
201 // path ? path : "(null)");
203 // break apart the "protocol" string into protocol, host, and "the rest"
204 const char* colon=strchr(prot,':');
206 const char* slash=strchr(colon,'/');
210 // Compute the actual protocol
215 proto = apr_pstrndup(r->pool, prot, colon-prot);
217 // create the "host" from either the colon/slash or from the target string
218 // If prot == shire then we're in either #1 or #2, else #3.
219 // If slash == colon then we're in #2
220 if (prot != shire || slash == colon) {
221 colon = strchr(target, ':');
222 colon += 3; // Get past the ://
223 slash = strchr(colon, '/');
225 const char *host = apr_pstrndup(r->pool, colon, slash-colon);
227 // Build the shire URL
228 return apr_pstrcat(r->pool, proto, host, path, NULL);
231 static int shib_error_page(request_rec* r, const char* filename, ShibMLP& mlp)
233 ifstream infile (filename);
235 ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
236 "shib_error_page() cannot open %s", filename);
237 return HTTP_INTERNAL_SERVER_ERROR;
240 string res = mlp.run(infile);
241 r->content_type = apr_psprintf(r->pool, "text/html");
242 ap_rprintf(r, res.c_str());
246 static const char* get_application_id(request_rec* r)
248 ApplicationMapper mapper;
249 return apr_pstrdup(r->pool,
250 mapper->getApplicationFromParsedURL(
251 ap_http_method(r), ap_get_server_name(r),
252 ap_get_server_port(r), r->unparsed_uri
257 static apr_table_t* groups_for_user(request_rec* r, const char* user, char* grpfile)
260 apr_table_t* grps=apr_table_make(r->pool,15);
261 char l[MAX_STRING_LEN];
262 const char *group_name, *ll, *w;
264 if (ap_pcfg_openfile(&f,r->pool,grpfile) != APR_SUCCESS)
266 ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
267 "groups_for_user() could not open group file: %s\n", grpfile);
272 if (apr_pool_create(&sp,r->pool) != APR_SUCCESS)
274 ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
275 "groups_for_user() could not create a subpool");
279 while (!(ap_cfg_getline(l,MAX_STRING_LEN,f)))
281 if ((*l=='#') || (!*l))
286 group_name=ap_getword(sp,&ll,':');
290 w=ap_getword_conf(sp,&ll);
293 apr_table_setn(grps,apr_pstrdup(r->pool,group_name),"in");
299 apr_pool_destroy(sp);
303 extern "C" int shib_check_user(request_rec* r)
305 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user: ENTER");
306 shib_dir_config* dc=(shib_dir_config*)ap_get_module_config(r->per_dir_config,&mod_shib);
308 // This will always be normalized, because Apache uses ap_get_server_name in this API call.
309 const char* targeturl=ap_construct_url(r->pool,r->unparsed_uri,r);
311 // Map request to application ID, which is the key for config lookup.
312 const char* application_id=get_application_id(r);
314 // Get unescaped location of this application's assertion consumer service.
315 const char* unescaped_shire = get_shire_location(r, targeturl, application_id);
317 if (strstr(targeturl,unescaped_shire)) {
318 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
319 "shib_check_user: REQUEST FOR SHIRE! Maybe you did not configure the SHIRE Handler?");
320 return HTTP_INTERNAL_SERVER_ERROR;
323 // Regular access to arbitrary resource...check AuthType
324 const char *auth_type=ap_auth_type (r);
328 if (strcasecmp(auth_type,"shibboleth"))
330 if (!strcasecmp(auth_type,"basic") && dc->bBasicHijack==1)
332 core_dir_config* conf=
333 (core_dir_config*)ap_get_module_config(r->per_dir_config,
334 ap_find_linked_module("http_core.c"));
335 conf->ap_auth_type="shibboleth";
341 // set the connection authtype
342 r->ap_auth_type = "shibboleth";
345 if (dc->bSSLOnly==1 && strcmp(ap_http_method(r),"https"))
347 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
348 "shib_check_user() blocked non-SSL access");
349 return HTTP_INTERNAL_SERVER_ERROR;
353 ostringstream threadid;
354 threadid << "[" << getpid() << "] shib" << '\0';
355 saml::NDC ndc(threadid.str().c_str());
357 ShibINI& ini = g_Config->getINI();
359 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
360 "shib_check_user() Shib check for %s", targeturl);
363 bool has_tag = ini.get_tag (application_id, "checkIPAddress", true, &tag);
364 dc->config.checkIPAddress = (has_tag ? ShibINI::boolean (tag) : false);
367 if (! ini.get_tag(application_id, "cookieName", true, &shib_cookie)) {
368 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
369 "shib_check_user: no cookieName configuration for %s",
371 return HTTP_INTERNAL_SERVER_ERROR;
375 if (! ini.get_tag(application_id, "wayfURL", true, &wayfLocation)) {
376 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
377 "shib_check_user: no wayfURL configuration for %s",
379 return HTTP_INTERNAL_SERVER_ERROR;
383 if (! ini.get_tag(application_id, "shireError", true, &shireError)) {
384 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
385 "shib_check_user: no shireError configuration for %s",
387 return HTTP_INTERNAL_SERVER_ERROR;
390 SHIRE shire(dc->config, unescaped_shire);
392 // We're in charge, so check for cookie.
393 const char* session_id=NULL;
394 const char* cookies=apr_table_get(r->headers_in,"Cookie");
398 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
399 "shire_check_user() cookies found: %s",cookies);
400 if (session_id=strstr(cookies,shib_cookie.c_str()))
402 // Yep, we found a cookie -- pull it out (our session_id)
403 session_id+=strlen(shib_cookie.c_str()) + 1; /* Skip over the '=' */
404 char* cookiebuf = apr_pstrdup(r->pool,session_id);
405 char* cookieend = strchr(cookiebuf,';');
407 *cookieend = '\0'; /* Ignore anyting after a ; */
408 session_id=cookiebuf;
412 if (!session_id || !*session_id)
414 // No cookie. Redirect to WAYF.
415 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
416 "shib_check_user() no cookie found -- redirecting to WAYF");
418 sprintf(timebuf,"%u",time(NULL));
419 char* wayf=apr_pstrcat(r->pool,wayfLocation.c_str(),
420 "?shire=",url_encode(r,unescaped_shire),
421 "&target=",url_encode(r,targeturl),
423 "&providerId=",application_id,
425 apr_table_setn(r->headers_out,"Location",wayf);
426 return HTTP_MOVED_TEMPORARILY;
429 // Make sure this session is still valid
430 RPCError* status = NULL;
431 ShibMLP markupProcessor;
432 has_tag = ini.get_tag(application_id, "supportContact", true, &tag);
433 markupProcessor.insert("supportContact", has_tag ? tag : "");
434 has_tag = ini.get_tag(application_id, "logoLocation", true, &tag);
435 markupProcessor.insert("logoLocation", has_tag ? tag : "");
436 markupProcessor.insert("requestURL", targeturl);
439 status = shire.sessionIsValid(session_id, r->connection->remote_ip,application_id);
441 catch (ShibTargetException &e) {
442 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user(): %s", e.what());
443 markupProcessor.insert ("errorType", "SHIRE Processing Error");
444 markupProcessor.insert ("errorText", e.what());
445 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
446 return shib_error_page (r, shireError.c_str(), markupProcessor);
449 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user(): caught unexpected error");
450 markupProcessor.insert ("errorType", "SHIRE Processing Error");
451 markupProcessor.insert ("errorText", "Unexpected Exception");
452 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
453 return shib_error_page (r, shireError.c_str(), markupProcessor);
457 if (status->isError()) {
458 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,0,r,
459 "shib_check_user() session invalid: %s",
462 if (status->isRetryable()) {
463 // Oops, session is invalid. Redirect to WAYF.
465 sprintf(timebuf,"%u",time(NULL));
466 char* wayf=apr_pstrcat(r->pool,wayfLocation.c_str(),
467 "?shire=",url_encode(r,unescaped_shire),
468 "&target=",url_encode(r,targeturl),
470 "&providerId=",application_id,
472 apr_table_setn(r->headers_out,"Location",wayf);
475 return HTTP_MOVED_TEMPORARILY;
478 // return the error page to the user
479 markupProcessor.insert (*status);
481 return shib_error_page (r, shireError.c_str(), markupProcessor);
486 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
487 "shib_check_user() success");
491 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,"shib_check_user() server error");
492 return HTTP_INTERNAL_SERVER_ERROR;
495 extern "C" int shib_shire_handler (request_rec* r)
497 ostringstream threadid;
498 threadid << "[" << getpid() << "] shire" << '\0';
499 saml::NDC ndc(threadid.str().c_str());
501 // This will always be normalized, because Apache uses
502 // ap_get_server_name in this API call.
503 const char* targeturl=ap_construct_url(r->pool,r->unparsed_uri,r);
505 // Map request to application ID, which is the key for config lookup.
506 const char* application_id = get_application_id(r);
508 // Find out what SHOULD be the SHIRE URL...
509 const char* unescaped_shire = get_shire_location(r, targeturl, application_id);
511 // Make sure we only process the SHIRE posts.
512 if (!strstr(targeturl,unescaped_shire))
515 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
516 "shire_post_handler() ENTER");
518 ShibINI& ini = g_Config->getINI();
519 ShibMLP markupProcessor;
522 bool has_tag = ini.get_tag(application_id, "checkIPAddress", true, &tag);
524 config.checkIPAddress = (has_tag ? ShibINI::boolean(tag) : false);
527 if (! ini.get_tag(application_id, "cookieName", true, &shib_cookie)) {
528 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
529 "shire_post_handler: no cookieName configuration for %s",
531 return HTTP_INTERNAL_SERVER_ERROR;
535 if (! ini.get_tag(application_id, "wayfURL", true, &wayfLocation)) {
536 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
537 "shire_post_handler: no wayfURL configuration for %s",
539 return HTTP_INTERNAL_SERVER_ERROR;
543 if (! ini.get_tag(application_id, "shireError", true, &shireError)) {
544 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
545 "shire_post_handler: no shireError configuration for %s",
547 return HTTP_INTERNAL_SERVER_ERROR;
550 has_tag = ini.get_tag(application_id, "supportContact", true, &tag);
551 markupProcessor.insert("supportContact", has_tag ? tag : "");
552 has_tag = ini.get_tag(application_id, "logoLocation", true, &tag);
553 markupProcessor.insert("logoLocation", has_tag ? tag : "");
554 markupProcessor.insert("requestURL", targeturl);
556 SHIRE shire(config, unescaped_shire);
558 // Process SHIRE POST
560 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
561 "shire_post_handler() Beginning SHIRE POST processing");
563 CgiParse* cgi = NULL;
567 if (!ini.get_tag(application_id, "shireSSLOnly", true, &sslonly))
568 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
569 "shire_post_handler: no shireSSLOnly configuration");
571 // Make sure this is SSL, if it should be
572 if (ShibINI::boolean(sslonly) && strcmp(ap_http_method(r),"https"))
573 throw ShibTargetException (SHIBRPC_OK,
574 "blocked non-SSL access to SHIRE POST processor");
576 // Make sure this is a POST
577 if (strcasecmp (r->method, "POST"))
578 throw ShibTargetException (SHIBRPC_OK,
579 "blocked non-POST to SHIRE POST processor");
581 // Sure sure this POST is an appropriate content type
582 const char *ct = apr_table_get (r->headers_in, "Content-type");
583 if (!ct || strcasecmp (ct, "application/x-www-form-urlencoded"))
584 throw ShibTargetException (SHIBRPC_OK,
585 apr_psprintf(r->pool,
586 "blocked bad content-type to SHIRE POST processor: %s",
589 // Make sure the "bytes sent" is a reasonable number
590 if (r->bytes_sent > 1024*1024) // 1MB?
591 throw ShibTargetException (SHIBRPC_OK,
592 "blocked too-large a post to SHIRE POST processor");
594 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
595 "shire_post_handler() about to run setup_client_block");
597 // Read the posted data
598 if (ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))
599 throw ShibTargetException (SHIBRPC_OK, "CGI setup_client_block failed");
601 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
602 "shire_post_handler() about to run should_client_block");
604 if (!ap_should_client_block(r))
605 throw ShibTargetException (SHIBRPC_OK, "CGI should_client_block failed");
607 long length = r->remaining;
608 if (length > 1024*1024)
609 throw ShibTargetException (SHIBRPC_OK, "CGI length too long...");
611 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
612 "shire_post_handler() about to read using get_client_block");
615 //ap_hard_timeout("[mod_shib] CGI Parser", r);
617 memset(buff, 0, sizeof(buff));
618 while (ap_get_client_block(r, buff, sizeof(buff)) > 0) {
620 memset(buff, 0, sizeof(buff));
623 //ap_kill_timeout(r);
625 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
626 "shire_post_handler() about to parse cgi...");
628 cgi = CgiParse::ParseCGI(cgistr);
630 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
631 "shire_post_handler() CGI parsed... (%p)", cgi);
634 throw ShibTargetException (SHIBRPC_OK, "CgiParse failed");
636 // Make sure the target parameter exists
637 const char *target = cgi->get_value("TARGET");
639 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
640 "shire_post_handler() obtained target...");
642 if (!target || *target == '\0')
644 throw ShibTargetException (SHIBRPC_OK,
645 "SHIRE POST failed to find TARGET");
647 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
648 "shire_post_handler() obtained target...");
650 // Make sure the SAML Response parameter exists
651 const char *post = cgi->get_value("SAMLResponse");
652 if (!post || *post == '\0')
654 throw ShibTargetException (SHIBRPC_OK,
655 "SHIRE POST failed to find SAMLResponse");
657 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
658 "shire_post_handler() Processing POST for target: %s", target);
660 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
661 "shire_post_handler() POST contents: %s", post);
665 RPCError* status = shire.sessionCreate(post, r->connection->remote_ip, application_id, cookie);
667 if (status->isError()) {
668 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
669 "shire_post_handler() POST process failed (%d): %s",
670 status->getCode(), status->getText());
672 if (status->isRetryable()) {
673 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,0,r,
674 "shire_post_handler() Retrying POST by redirecting to WAYF");
677 sprintf(timebuf,"%u",time(NULL));
678 char* wayf=apr_pstrcat(r->pool,wayfLocation.c_str(),
679 "?shire=",url_encode(r,unescaped_shire),
680 "&target=",url_encode(r,target),
682 "&providerId=",application_id,
684 apr_table_setn(r->headers_out,"Location",wayf);
686 return HTTP_MOVED_TEMPORARILY;
689 // return this error to the user.
690 markupProcessor.insert (*status);
692 return shib_error_page (r, shireError.c_str(), markupProcessor);
696 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
697 "shire_post_handler() POST process succeeded. New cookie: %s",
700 // We've got a good session, set the cookie...
701 char * domain = NULL;
702 char * new_cookie = apr_psprintf(r->pool, "%s=%s; path=/%s%s",
705 (domain ? "; domain=" : ""),
706 (domain ? domain : ""));
708 apr_table_setn(r->err_headers_out, "Set-Cookie", new_cookie);
709 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
710 "shire_post_handler() Set cookie: %s", new_cookie);
712 // ... and redirect to the target
713 char* redir=apr_pstrcat(r->pool,url_encode(r,target),NULL);
714 apr_table_setn(r->headers_out, "Location", target);
716 return HTTP_MOVED_TEMPORARILY;
718 } catch (ShibTargetException &e) {
719 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
720 "shire_post_handler(): %s", e.what());
722 markupProcessor.insert ("errorType", "SHIRE Processing Error");
723 markupProcessor.insert ("errorText", e.what());
724 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
726 return shib_error_page (r, shireError.c_str(), markupProcessor);
729 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shire_post_handler(): unexpected exception");
731 markupProcessor.insert ("errorType", "SHIRE Processing Error");
732 markupProcessor.insert ("errorText", "Unexpected Exception");
733 markupProcessor.insert ("errorDesc", "An error occurred while processing your request.");
735 return shib_error_page (r, shireError.c_str(), markupProcessor);
738 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,"shire_post_handler() server error");
739 return HTTP_INTERNAL_SERVER_ERROR;
743 * shib_auth_checker() -- a simple resource manager to
744 * process the .htaccess settings and copy attributes
745 * into the HTTP headers.
747 extern "C" int shib_auth_checker(request_rec *r)
750 (shib_dir_config*)ap_get_module_config(r->per_dir_config,&mod_shib);
752 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
753 "shib_auth_checker() executing");
755 // Regular access to arbitrary resource...check AuthType
756 const char* auth_type=ap_auth_type(r);
757 if (!auth_type || strcasecmp(auth_type,"shibboleth"))
760 ostringstream threadid;
761 threadid << "[" << getpid() << "] shib" << '\0';
762 saml::NDC ndc(threadid.str().c_str());
764 ShibINI& ini = g_Config->getINI();
766 // This will always be normalized, because Apache uses ap_get_server_name
768 const char* targeturl=ap_construct_url(r->pool,r->unparsed_uri,r);
770 // Map request to application ID, which is the key for config lookup.
771 const char* application_id=get_application_id(r);
773 // Ok, this is a SHIB target; grab the cookie
775 if (!ini.get_tag(application_id, "cookieName", true, &shib_cookie)) {
776 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
777 "shib_check_user: no cookieName configuration for %s",
779 return HTTP_INTERNAL_SERVER_ERROR;
782 const char* session_id=NULL;
783 const char* cookies=apr_table_get(r->headers_in,"Cookie");
784 if (!cookies || !(session_id=strstr(cookies,shib_cookie.c_str())))
786 // No cookie??? Must be a server error!
787 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
788 "shib_auth_checker() no cookie found");
790 return HTTP_INTERNAL_SERVER_ERROR;
793 // Yep, we found a cookie -- pull it out (our session_id)
794 session_id+=strlen(shib_cookie.c_str()) + 1; /* Skip over the '=' */
795 char* cookiebuf = apr_pstrdup(r->pool,session_id);
796 char* cookieend = strchr(cookiebuf,';');
798 *cookieend = '\0'; /* Ignore anyting after a ; */
799 session_id=cookiebuf;
801 ShibMLP markupProcessor;
803 bool has_tag = ini.get_tag(application_id, "supportContact", true, &tag);
804 markupProcessor.insert("supportContact", has_tag ? tag : "");
805 has_tag = ini.get_tag(application_id, "logoLocation", true, &tag);
806 markupProcessor.insert("logoLocation", has_tag ? tag : "");
807 markupProcessor.insert("requestURL", targeturl);
809 // Now grab the attributes...
810 has_tag = ini.get_tag (application_id, "checkIPAddress", true, &tag);
811 dc->rm_config.checkIPAddress = (has_tag ? ShibINI::boolean (tag) : false);
813 RM rm(dc->rm_config);
815 vector<SAMLAssertion*> assertions;
816 SAMLAuthenticationStatement* sso_statement=NULL;
817 RPCError* status = rm.getAssertions(session_id, r->connection->remote_ip, application_id, assertions, &sso_statement);
819 if (status->isError()) {
820 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
821 "shib_auth_checker() getAssertions failed: %s",
825 if (!ini.get_tag(application_id, "rmError", true, &rmError)) {
826 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
827 "shib_auth_checker: no rmError configuration for %s",
830 return HTTP_INTERNAL_SERVER_ERROR;
832 markupProcessor.insert(*status);
834 return shib_error_page (r, rmError.c_str(), markupProcessor);
839 if (!ini.get_tag(application_id, "accessError", true, &rmError)) {
840 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
841 "shib_auth_checker: no accessError configuration for %s",
845 for (int k = 0; k < assertions.size(); k++)
846 delete assertions[k];
847 delete sso_statement;
848 return HTTP_INTERNAL_SERVER_ERROR;
851 // Get the AAP providers, which contain the attribute policy info.
852 Iterator<IAAP*> provs=g_Config->getAAPProviders();
854 // Clear out the list of mapped attributes
855 while (provs.hasNext())
857 IAAP* aap=provs.next();
861 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
862 while (rules.hasNext())
864 const char* header=rules.next()->getHeader();
866 apr_table_unset(r->headers_in,header);
872 for (int k = 0; k < assertions.size(); k++)
873 delete assertions[k];
874 delete sso_statement;
881 // Maybe export the first assertion.
882 apr_table_unset(r->headers_in,"Shib-Attributes");
883 if (dc->bExportAssertion==1 && assertions.size()) {
885 RM::serialize(*(assertions[0]), assertion);
886 apr_table_set(r->headers_in,"Shib-Attributes", assertion.c_str());
889 // Export the SAML AuthnMethod and the origin site name.
890 apr_table_unset(r->headers_in,"Shib-Origin-Site");
891 apr_table_unset(r->headers_in,"Shib-Authentication-Method");
894 auto_ptr_char os(sso_statement->getSubject()->getNameQualifier());
895 auto_ptr_char am(sso_statement->getAuthMethod());
896 apr_table_set(r->headers_in,"Shib-Origin-Site", os.get());
897 apr_table_set(r->headers_in,"Shib-Authentication-Method", am.get());
900 apr_table_unset(r->headers_in,"Shib-Application-ID");
901 apr_table_set(r->headers_in,"Shib-Application-ID",application_id);
903 // Export the attributes.
904 Iterator<SAMLAssertion*> a_iter(assertions);
905 while (a_iter.hasNext()) {
906 SAMLAssertion* assert=a_iter.next();
907 Iterator<SAMLStatement*> statements=assert->getStatements();
908 while (statements.hasNext()) {
909 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
912 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
913 while (attrs.hasNext()) {
914 SAMLAttribute* attr=attrs.next();
916 // Are we supposed to export it?
917 AAP wrapper(provs,attr->getName(),attr->getNamespace());
921 Iterator<string> vals=attr->getSingleByteValues();
922 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext())
923 r->user=apr_pstrdup(r->connection->pool,vals.next().c_str());
925 char* header = apr_pstrdup(r->pool, "");
926 for (int it = 0; vals.hasNext(); it++) {
927 string value = vals.next();
928 for (string::size_type pos = value.find_first_of(";", string::size_type(0)); pos != string::npos; pos = value.find_first_of(";", pos)) {
929 value.insert(pos, "\\");
932 header=apr_pstrcat(r->pool, header, (it ? ";" : ""), value.c_str(), NULL);
934 apr_table_setn(r->headers_in, wrapper->getHeader(), header);
941 for (int k = 0; k < assertions.size(); k++)
942 delete assertions[k];
943 delete sso_statement;
947 int m=r->method_number;
948 bool method_restricted=false;
951 const apr_array_header_t* reqs_arr=ap_requires(r);
955 require_line* reqs=(require_line*)reqs_arr->elts;
958 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
959 "REQUIRE nelts: %d", reqs_arr->nelts);
960 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
961 "REQUIRE all: %d", dc->bRequireAll);
963 bool auth_OK[reqs_arr->nelts];
965 #define SHIB_AP_CHECK_IS_OK { \
966 if (dc->bRequireAll < 1) \
972 for (int x=0; x<reqs_arr->nelts; x++)
976 if (!(reqs[x].method_mask & (1 << m)))
978 method_restricted=true;
980 t = reqs[x].requirement;
981 w = ap_getword_white(r->pool, &t);
983 if (!strcmp(w,"valid-user"))
985 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
986 "shib_auth_checker() accepting valid-user");
989 else if (!strcmp(w,"user") && r->user)
994 w=ap_getword_conf(r->pool,&t);
1005 // To do regex matching, we have to convert from UTF-8.
1006 auto_ptr<XMLCh> trans(fromUTF8(w));
1007 RegularExpression re(trans.get());
1008 auto_ptr<XMLCh> trans2(fromUTF8(r->user));
1009 if (re.matches(trans2.get())) {
1010 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1011 "shib_auth_checker() accepting user: %s",w);
1012 SHIB_AP_CHECK_IS_OK;
1015 catch (XMLException& ex)
1017 auto_ptr_char tmp(ex.getMessage());
1018 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
1019 "shib_auth_checker caught exception while parsing regular expression (%s): %s",w,tmp.get());
1022 else if (!strcmp(r->user,w))
1024 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1025 "shib_auth_checker() accepting user: %s",w);
1026 SHIB_AP_CHECK_IS_OK;
1030 else if (!strcmp(w,"group"))
1032 apr_table_t* grpstatus=NULL;
1033 if (dc->szAuthGrpFile && r->user)
1035 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1036 "shib_auth_checker() using groups file: %s\n",
1038 grpstatus=groups_for_user(r,r->user,dc->szAuthGrpFile);
1045 w=ap_getword_conf(r->pool,&t);
1046 if (apr_table_get(grpstatus,w))
1048 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1049 "shib_auth_checker() accepting group: %s",w);
1050 SHIB_AP_CHECK_IS_OK;
1056 AAP wrapper(provs,w);
1057 if (wrapper.fail()) {
1058 ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,
1059 "shib_auth_checker() didn't recognize require rule: %s\n",w);
1064 const char* vals=apr_table_get(r->headers_in,wrapper->getHeader());
1067 w=ap_getword_conf(r->pool,&t);
1076 auto_ptr<RegularExpression> re;
1079 delete re.release();
1080 auto_ptr<XMLCh> trans(fromUTF8(w));
1081 auto_ptr<RegularExpression> temp(new RegularExpression(trans.get()));
1085 string vals_str(vals);
1087 for (int i = 0; i < vals_str.length(); i++)
1089 if (vals_str.at(i) == ';')
1092 ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,
1093 "shib_auth_checker() invalid header encoding %s: starts with semicolon", vals);
1094 return HTTP_INTERNAL_SERVER_ERROR;
1097 if (vals_str.at(i-1) == '\\') {
1098 vals_str.erase(i-1, 1);
1103 string val = vals_str.substr(j, i-j);
1106 auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
1107 if (re->matches(trans.get())) {
1108 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1109 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
1110 SHIB_AP_CHECK_IS_OK;
1114 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1115 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
1116 SHIB_AP_CHECK_IS_OK;
1119 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1120 "shib_auth_checker() expecting %s, got %s: authorization not granted", w, val.c_str());
1125 string val = vals_str.substr(j, vals_str.length()-j);
1127 auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
1128 if (re->matches(trans.get())) {
1129 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1130 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
1131 SHIB_AP_CHECK_IS_OK;
1135 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1136 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
1137 SHIB_AP_CHECK_IS_OK;
1140 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
1141 "shib_auth_checker() expecting %s, got %s: authorization not granted", w, val.c_str());
1144 catch (XMLException& ex)
1146 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
1147 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
1148 "shib_auth_checker caught exception while parsing regular expression (%s): %s",w,tmp.get());
1154 // check if all require directives are true
1155 bool auth_all_OK = true;
1156 for (int i= 0; i<reqs_arr->nelts; i++) {
1157 auth_all_OK &= auth_OK[i];
1162 if (!method_restricted)
1165 return shib_error_page(r, rmError.c_str(), markupProcessor);
1170 * Cleanup the (per-process) pool info.
1172 extern "C" apr_status_t shib_exit(void* data)
1174 g_Config->shutdown();
1176 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,NULL,"shib_exit() done");
1181 * shib_post_config()
1182 * Things to do at process startup after the configs are read
1184 extern "C" int shib_post_config(apr_pool_t* pconf, apr_pool_t* plog,
1185 apr_pool_t* ptemp, server_rec* s)
1187 // Initialize runtime components.
1189 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,s,
1190 "shib_post_config() starting");
1192 ShibTargetConfig::preinit();
1195 ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,s,
1196 "shib_post_config(): already initialized!");
1201 g_Config = &(ShibTargetConfig::init(SHIBTARGET_SHIRE, g_szSHIBConfig));
1203 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,s,
1204 "shib_post_config() failed to initialize SHIB Target");
1208 // Set the cleanup handler
1209 apr_pool_cleanup_register(pconf, NULL, shib_exit, NULL);
1211 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,s,"shib_post_config() done");
1216 extern "C" void shib_register_hooks (apr_pool_t *p)
1218 ap_hook_post_config(shib_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1219 ap_hook_check_user_id(shib_check_user, NULL, NULL, APR_HOOK_MIDDLE);
1220 ap_hook_auth_checker(shib_auth_checker, NULL, NULL, APR_HOOK_FIRST);
1221 ap_hook_handler(shib_shire_handler, NULL, NULL, APR_HOOK_MIDDLE);
1224 // SHIB Module commands
1227 static command_rec shib_cmds[] = {
1228 AP_INIT_TAKE1("SHIBConfig",
1229 (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1230 RSRC_CONF, "Path to SHIB ini file."),
1232 AP_INIT_FLAG("ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
1233 (void *) offsetof (shib_dir_config, bBasicHijack),
1234 OR_AUTHCFG, "Respond to AuthType Basic and convert to shib?"),
1235 AP_INIT_FLAG("ShibSSLOnly", (config_fn_t)ap_set_flag_slot,
1236 (void *) offsetof (shib_dir_config, bSSLOnly),
1237 OR_AUTHCFG, "Require SSL when accessing a secured directory?"),
1238 AP_INIT_TAKE1("ShibAuthLifetime", (config_fn_t)set_lifetime, NULL,
1239 OR_AUTHCFG, "Lifetime of session in seconds."),
1240 AP_INIT_TAKE1("ShibAuthTimeout", (config_fn_t)set_timeout, NULL,
1241 OR_AUTHCFG, "Timeout for session in seconds."),
1243 AP_INIT_TAKE1("AuthGroupFile", (config_fn_t)ap_set_file_slot,
1244 (void *) offsetof (shib_dir_config, szAuthGrpFile),
1245 OR_AUTHCFG, "text file containing group names and member user IDs"),
1246 AP_INIT_FLAG("ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
1247 (void *) offsetof (shib_dir_config, bExportAssertion),
1248 OR_AUTHCFG, "Export SAML assertion to Shibboleth-defined header?"),
1249 AP_INIT_FLAG("ShibRequireAll", (config_fn_t)ap_set_flag_slot,
1250 (void *) offsetof (shib_dir_config, bRequireAll),
1251 OR_AUTHCFG, "All require directives must match!"),
1252 AP_INIT_FLAG("DisableRM", (config_fn_t)ap_set_flag_slot,
1253 (void *) offsetof (shib_dir_config, bDisableRM),
1254 OR_AUTHCFG, "Disable the Shibboleth Resource Manager?"),
1259 module AP_MODULE_DECLARE_DATA mod_shib = {
1260 STANDARD20_MODULE_STUFF,
1261 create_shib_dir_config, /* create dir config */
1262 merge_shib_dir_config, /* merge dir config --- default is to override */
1263 NULL, /* create server config */
1264 NULL, /* merge server config */
1265 shib_cmds, /* command table */
1266 shib_register_hooks /* register hooks */