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>
14 #include <xercesc/util/regx/RegularExpression.hpp>
16 // Apache specific header files
18 #include <http_config.h>
19 #include <http_protocol.h>
20 #include <http_main.h>
21 #include <http_request.h>
22 #include <apr_strings.h>
24 #include <http_core.h>
26 #include <apr_pools.h>
31 //#include <apreq_params.h>
34 #include <unistd.h> // for getpid()
39 using namespace shibboleth;
40 using namespace shibtarget;
42 extern "C" AP_MODULE_DECLARE_DATA module mod_shib;
43 int shib_handler(request_rec* r, const IApplication* application, const IPropertySet* sessionProps, SHIRE& shire);
46 char* g_szSHIBConfig = NULL;
47 char* g_szSchemaDir = NULL;
48 ShibTargetConfig* g_Config = NULL;
49 bool g_bApacheConf = false;
50 static const char* g_UserDataKey = "_shib_check_user_";
53 // per-dir module configuration structure
54 struct shib_dir_config
57 char* szAuthGrpFile; // Auth GroupFile name
58 int bRequireAll; // all require directives must match, otherwise OR logic
60 // SHIRE Configuration
61 int bBasicHijack; // activate for AuthType Basic?
62 int bRequireSession; // require a session?
63 int bExportAssertion; // export SAML assertion to the environment?
66 // creates per-directory config structure
67 extern "C" void* create_shib_dir_config (apr_pool_t* p, char* d)
69 shib_dir_config* dc=(shib_dir_config*)apr_pcalloc(p,sizeof(shib_dir_config));
70 dc->bBasicHijack = -1;
71 dc->bRequireSession = -1;
72 dc->bExportAssertion = -1;
74 dc->szAuthGrpFile = NULL;
78 // overrides server configuration in directories
79 extern "C" void* merge_shib_dir_config (apr_pool_t* p, void* base, void* sub)
81 shib_dir_config* dc=(shib_dir_config*)apr_pcalloc(p,sizeof(shib_dir_config));
82 shib_dir_config* parent=(shib_dir_config*)base;
83 shib_dir_config* child=(shib_dir_config*)sub;
85 if (child->szAuthGrpFile)
86 dc->szAuthGrpFile=apr_pstrdup(p,child->szAuthGrpFile);
87 else if (parent->szAuthGrpFile)
88 dc->szAuthGrpFile=apr_pstrdup(p,parent->szAuthGrpFile);
90 dc->szAuthGrpFile=NULL;
92 dc->bBasicHijack=((child->bBasicHijack==-1) ? parent->bBasicHijack : child->bBasicHijack);
93 dc->bRequireSession=((child->bRequireSession==-1) ? parent->bRequireSession : child->bRequireSession);
94 dc->bExportAssertion=((child->bExportAssertion==-1) ? parent->bExportAssertion : child->bExportAssertion);
95 dc->bRequireAll=((child->bRequireAll==-1) ? parent->bRequireAll : child->bRequireAll);
100 // generic global slot handlers
101 extern "C" const char* ap_set_global_string_slot(cmd_parms* parms, void*,
104 *((char**)(parms->info))=apr_pstrdup(parms->pool,arg);
108 typedef const char* (*config_fn_t)(void);
110 static int shib_error_page(request_rec* r, const IApplication* app, const char* page, ShibMLP& mlp)
112 const IPropertySet* props=app->getPropertySet("Errors");
114 pair<bool,const char*> p=props->getString(page);
116 ifstream infile(p.second);
117 if (!infile.fail()) {
118 const char* res = mlp.run(infile);
120 r->content_type = apr_psprintf(r->pool, "text/html");
121 //ap_send_http_header(r);
129 ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
130 "shire_error_page() could not process shire error template for application %s",app->getId());
131 return HTTP_INTERNAL_SERVER_ERROR;
134 extern "C" int shib_check_user(request_rec* r)
136 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user: ENTER");
137 shib_dir_config* dc=(shib_dir_config*)ap_get_module_config(r->per_dir_config,&mod_shib);
139 ostringstream threadid;
140 threadid << "[" << getpid() << "] shib_check_user" << '\0';
141 saml::NDC ndc(threadid.str().c_str());
143 // This will always be normalized, because Apache uses ap_get_server_name in this API call.
144 const char* targeturl=ap_construct_url(r->pool,r->unparsed_uri,r);
146 // We lock the configuration system for the duration.
147 IConfig* conf=g_Config->getINI();
150 // Map request to application and content settings.
151 IRequestMapper* mapper=conf->getRequestMapper();
152 Locker locker2(mapper);
153 IRequestMapper::Settings settings=mapper->getSettingsFromParsedURL(
154 ap_http_method(r), ap_get_server_name(r), ap_get_server_port(r), r->unparsed_uri
156 pair<bool,const char*> application_id=settings.first->getString("applicationId");
157 const IApplication* application=conf->getApplication(application_id.second);
158 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
159 if (!application || !sessionProps) {
160 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
161 "shib_check_user: unable to map request to application session settings, check configuration");
162 return HTTP_INTERNAL_SERVER_ERROR;
165 // Declare SHIRE object for this request.
166 SHIRE shire(application);
168 // Get location of this application's assertion consumer service and see if this is it.
169 if (strstr(targeturl,shire.getShireURL(targeturl))) {
170 return shib_handler(r,application,sessionProps,shire);
173 // We can short circuit the handler if we run this...
174 apr_pool_userdata_setn((const void*)42,g_UserDataKey,NULL,r->pool);
176 // Regular access to arbitrary resource...check AuthType
177 const char *auth_type=ap_auth_type(r);
181 if (strcasecmp(auth_type,"shibboleth")) {
182 if (!strcasecmp(auth_type,"basic") && dc->bBasicHijack==1) {
183 core_dir_config* conf=
184 (core_dir_config*)ap_get_module_config(r->per_dir_config,
185 ap_find_linked_module("http_core.c"));
186 conf->ap_auth_type="shibboleth";
192 pair<bool,bool> requireSession = pair<bool,bool>(false,false);
194 // By default, we will require a session.
195 if (dc->bRequireSession!=0)
196 requireSession.second=true;
199 requireSession = settings.first->getBool("requireSession");
201 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user: session check for %s",targeturl);
203 pair<bool,const char*> shib_cookie=sessionProps->getString("cookieName");
204 if (!shib_cookie.first) {
205 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
206 "shib_check_user: no cookieName set for %s", application_id.second);
207 return HTTP_INTERNAL_SERVER_ERROR;
210 // We're in charge, so check for cookie.
211 const char* session_id=NULL;
212 const char* cookies=apr_table_get(r->headers_in,"Cookie");
215 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user() cookies found: %s",cookies);
216 if (session_id=strstr(cookies,shib_cookie.second)) {
217 // Yep, we found a cookie -- pull it out (our session_id)
218 session_id+=strlen(shib_cookie.second) + 1; /* Skip over the '=' */
219 char* cookiebuf = apr_pstrdup(r->pool,session_id);
220 char* cookieend = strchr(cookiebuf,';');
222 *cookieend = '\0'; /* Ignore anyting after a ; */
223 session_id=cookiebuf;
227 if (!session_id || !*session_id) {
228 // If no session required, bail now.
229 if (!requireSession.second)
232 // No acceptable cookie, and we require a session. Generate an AuthnRequest.
233 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user: no cookie found -- redirecting to WAYF");
234 apr_table_setn(r->headers_out,"Location",apr_pstrdup(r->pool,shire.getAuthnRequest(targeturl)));
235 return HTTP_MOVED_TEMPORARILY;
238 // Make sure this session is still valid
239 RPCError* status = NULL;
240 ShibMLP markupProcessor(application);
241 markupProcessor.insert("requestURL", targeturl);
244 status = shire.sessionIsValid(session_id, r->connection->remote_ip);
246 catch (ShibTargetException &e) {
247 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user(): %s", e.what());
248 markupProcessor.insert("errorType", "Session Processing Error");
249 markupProcessor.insert("errorText", e.what());
250 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
251 return shib_error_page(r, application, "shire", markupProcessor);
255 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user(): caught unexpected error");
256 markupProcessor.insert("errorType", "Session Processing Error");
257 markupProcessor.insert("errorText", "Unexpected Exception");
258 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
259 return shib_error_page(r, application, "shire", markupProcessor);
264 if (status->isError()) {
265 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,0,r,
266 "shib_check_user() session invalid: %s", status->getText());
268 // If no session required, bail now.
269 if (!requireSession.second)
271 else if (status->isRetryable()) {
272 // Oops, session is invalid. Generate AuthnRequest.
273 apr_table_setn(r->headers_out,"Location",apr_pstrdup(r->pool,shire.getAuthnRequest(targeturl)));
275 return HTTP_MOVED_TEMPORARILY;
278 // return the error page to the user
279 markupProcessor.insert(*status);
281 return shib_error_page(r, application, "shire", markupProcessor);
287 r->ap_auth_type = "shibboleth";
288 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_check_user(): session successfully verified");
290 // This is code transferred in from the auth check to export the attributes.
291 // We could even combine the isSessionValid/getAssertions API...?
294 vector<SAMLAssertion*> assertions;
295 SAMLAuthenticationStatement* sso_statement=NULL;
298 status = rm.getAssertions(session_id, r->connection->remote_ip, assertions, &sso_statement);
300 catch (ShibTargetException &e) {
301 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,"shib_check_user(): %s", e.what());
302 markupProcessor.insert("errorType", "Attribute Processing Error");
303 markupProcessor.insert("errorText", e.what());
304 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
305 return shib_error_page(r, application, "rm", markupProcessor);
309 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,"shib_check_user(): caught unexpected error");
310 markupProcessor.insert("errorType", "Attribute Processing Error");
311 markupProcessor.insert("errorText", "Unexpected Exception");
312 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
313 return shib_error_page(r, application, "rm", markupProcessor);
317 if (status->isError()) {
318 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
319 "shire_check_user() getAssertions failed: %s", status->getText());
321 markupProcessor.insert(*status);
323 return shib_error_page(r, application, "rm", markupProcessor);
327 // Do we have an access control plugin?
328 if (settings.second) {
329 Locker acllock(settings.second);
330 if (!settings.second->authorized(assertions)) {
331 for (int k = 0; k < assertions.size(); k++)
332 delete assertions[k];
333 delete sso_statement;
334 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,"shib_check_user(): access control provider denied access");
335 return shib_error_page(r, application, "access", markupProcessor);
339 // Get the AAP providers, which contain the attribute policy info.
340 Iterator<IAAP*> provs=application->getAAPProviders();
342 // Clear out the list of mapped attributes
343 while (provs.hasNext()) {
344 IAAP* aap=provs.next();
347 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
348 while (rules.hasNext()) {
349 const char* header=rules.next()->getHeader();
351 apr_table_unset(r->headers_in,header);
356 for (int k = 0; k < assertions.size(); k++)
357 delete assertions[k];
358 delete sso_statement;
359 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
360 "shib_check_user(): caught unexpected error while clearing headers");
361 markupProcessor.insert("errorType", "Attribute Processing Error");
362 markupProcessor.insert("errorText", "Unexpected Exception");
363 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
364 return shib_error_page(r, application, "rm", markupProcessor);
370 // Maybe export the first assertion.
371 apr_table_unset(r->headers_in,"Shib-Attributes");
372 pair<bool,bool> exp=pair<bool,bool>(false,false);
373 if (g_bApacheConf && dc->bExportAssertion==1)
374 exp.second=exp.first=true;
375 else if (!g_bApacheConf)
376 exp=settings.first->getBool("exportAssertion");
377 if (exp.first && exp.second && assertions.size()) {
379 RM::serialize(*(assertions[0]), assertion);
380 apr_table_set(r->headers_in,"Shib-Attributes", assertion.c_str());
383 // Export the SAML AuthnMethod and the origin site name.
384 apr_table_unset(r->headers_in,"Shib-Origin-Site");
385 apr_table_unset(r->headers_in,"Shib-Authentication-Method");
387 auto_ptr_char os(sso_statement->getSubject()->getNameQualifier());
388 auto_ptr_char am(sso_statement->getAuthMethod());
389 apr_table_set(r->headers_in,"Shib-Origin-Site", os.get());
390 apr_table_set(r->headers_in,"Shib-Authentication-Method", am.get());
393 apr_table_unset(r->headers_in,"Shib-Application-ID");
394 apr_table_set(r->headers_in,"Shib-Application-ID",application_id.second);
396 // Export the attributes.
397 Iterator<SAMLAssertion*> a_iter(assertions);
398 while (a_iter.hasNext()) {
399 SAMLAssertion* assert=a_iter.next();
400 Iterator<SAMLStatement*> statements=assert->getStatements();
401 while (statements.hasNext()) {
402 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
405 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
406 while (attrs.hasNext()) {
407 SAMLAttribute* attr=attrs.next();
409 // Are we supposed to export it?
410 AAP wrapper(provs,attr->getName(),attr->getNamespace());
414 Iterator<string> vals=attr->getSingleByteValues();
415 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext())
416 r->user=apr_pstrdup(r->pool,vals.next().c_str());
419 char* header = (char*)apr_table_get(r->headers_in, wrapper->getHeader());
421 header=apr_pstrdup(r->pool, header);
425 header = apr_pstrdup(r->pool, "");
426 for (; vals.hasNext(); it++) {
427 string value = vals.next();
428 for (string::size_type pos = value.find_first_of(";", string::size_type(0));
430 pos = value.find_first_of(";", pos)) {
431 value.insert(pos, "\\");
434 header=apr_pstrcat(r->pool, header, (it ? ";" : ""), value.c_str(), NULL);
436 apr_table_setn(r->headers_in, wrapper->getHeader(), header);
443 for (int k = 0; k < assertions.size(); k++)
444 delete assertions[k];
445 delete sso_statement;
450 extern "C" int shib_post_handler(request_rec* r)
452 // With 2.x, this handler always runs, though last. We check if shib_check_user ran,
453 // because it will detect a SHIRE request and dispatch it directly.
455 apr_pool_userdata_get(&data,g_UserDataKey,r->pool);
456 if (data==(const void*)42) {
457 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_post_handler skipped since check_user ran");
461 ostringstream threadid;
462 threadid << "[" << getpid() << "] shib_post_handler" << '\0';
463 saml::NDC ndc(threadid.str().c_str());
465 // We lock the configuration system for the duration.
466 IConfig* conf=g_Config->getINI();
469 // Map request to application and content settings.
470 IRequestMapper* mapper=conf->getRequestMapper();
471 Locker locker2(mapper);
472 IRequestMapper::Settings settings=mapper->getSettingsFromParsedURL(
473 ap_http_method(r), ap_get_server_name(r), ap_get_server_port(r), r->unparsed_uri
475 pair<bool,const char*> application_id=settings.first->getString("applicationId");
476 const IApplication* application=conf->getApplication(application_id.second);
477 const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
478 if (!application || !sessionProps) {
479 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
480 "shib_post_handler: unable to map request to application session settings, check configuration");
481 return HTTP_INTERNAL_SERVER_ERROR;
484 // Declare SHIRE object for this request.
485 SHIRE shire(application);
487 return shib_handler(r, application, sessionProps, shire);
490 int shib_handler(request_rec* r, const IApplication* application, const IPropertySet* sessionProps, SHIRE& shire)
493 const char* targeturl = ap_construct_url(r->pool,r->unparsed_uri,r);
495 // Make sure we only process the SHIRE requests.
496 if (!strstr(targeturl,shire.getShireURL(targeturl)))
499 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_handler() running");
501 pair<bool,const char*> shib_cookie=sessionProps->getString("cookieName");
502 pair<bool,const char*> shib_cookie_props=sessionProps->getString("cookieProps");
503 if (!shib_cookie.first) {
504 ap_log_rerror(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,r,
505 "shire_handler: no cookieName set for %s", application->getId());
506 return HTTP_INTERNAL_SERVER_ERROR;
509 ShibMLP markupProcessor(application);
510 markupProcessor.insert("requestURL", targeturl);
512 // Process SHIRE request
513 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_handler() Beginning SHIRE processing");
516 pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
518 // Make sure this is SSL, if it should be
519 if ((!shireSSL.first || shireSSL.second) && strcmp(ap_http_method(r),"https"))
520 throw ShibTargetException(SHIBRPC_OK, "blocked non-SSL access to session creation service");
522 // If this is a GET, we manufacture an AuthnRequest.
523 if (!strcasecmp(r->method,"GET")) {
524 const char* areq=r->args ? shire.getLazyAuthnRequest(r->args) : NULL;
526 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
527 apr_table_setn(r->headers_out, "Location", apr_pstrdup(r->pool,areq));
528 return HTTP_MOVED_TEMPORARILY;
530 else if (strcasecmp(r->method,"POST")) {
531 throw ShibTargetException(SHIBRPC_OK, "blocked non-POST to SHIRE POST processor");
534 // Sure sure this POST is an appropriate content type
535 const char *ct = apr_table_get(r->headers_in, "Content-type");
536 if (!ct || strcasecmp(ct, "application/x-www-form-urlencoded"))
537 throw ShibTargetException (SHIBRPC_OK,
538 apr_psprintf(r->pool, "blocked bad content-type to SHIRE POST processor: %s", (ct ? ct : "")));
540 // Read the posted data
541 if (ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))
542 throw ShibTargetException (SHIBRPC_OK, "CGI setup_client_block failed");
543 if (!ap_should_client_block(r))
544 throw ShibTargetException (SHIBRPC_OK, "CGI should_client_block failed");
545 if (r->remaining > 1024*1024)
546 throw ShibTargetException (SHIBRPC_OK, "CGI length too long...");
549 char buff[HUGE_STRING_LEN];
550 //apr_hard_timeout("[mod_shib] CGI Parser", r);
551 memset(buff, 0, sizeof(buff));
552 while (ap_get_client_block(r, buff, sizeof(buff)-1) > 0) {
554 memset(buff, 0, sizeof(buff));
556 //ap_kill_timeout(r);
558 // Parse the submission.
559 pair<const char*,const char*> elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
561 // Make sure the SAML Response parameter exists
562 if (!elements.first || !*elements.first)
563 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
565 // Make sure the target parameter exists
566 if (!elements.second || !*elements.second)
567 throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
569 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
570 "shib_handler() Processing POST for target: %s", elements.second);
574 RPCError* status = shire.sessionCreate(elements.first, r->connection->remote_ip, cookie);
576 if (status->isError()) {
577 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
578 "shib_handler() POST process failed (%d): %s", status->getCode(), status->getText());
580 if (status->isRetryable()) {
581 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,0,r,
582 "shib_handler() retryable error, generating new AuthnRequest");
583 apr_table_setn(r->headers_out,"Location",apr_pstrdup(r->pool,shire.getAuthnRequest(elements.second)));
584 return HTTP_MOVED_TEMPORARILY;
587 // return this error to the user.
588 markupProcessor.insert(*status);
590 return shib_error_page(r, application, "shire", markupProcessor);
594 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
595 "shib_handler() POST process succeeded. New cookie: %s", cookie.c_str());
597 // We've got a good session, set the cookie...
598 char* val = apr_psprintf(r->pool,"%s=%s%s",shib_cookie.second,cookie.c_str(),
599 shib_cookie_props.first ? shib_cookie_props.second : "; path=/");
600 apr_table_setn(r->err_headers_out, "Set-Cookie", val);
601 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_handler() setting cookie: %s",val);
603 // ... and redirect to the target
604 apr_table_setn(r->headers_out, "Location", apr_pstrdup(r->pool,elements.second));
605 return HTTP_MOVED_TEMPORARILY;
607 catch (ShibTargetException &e) {
608 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_post_handler(): %s", e.what());
609 markupProcessor.insert("errorType", "Session Creation Service Error");
610 markupProcessor.insert("errorText", e.what());
611 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
612 return shib_error_page(r, application, "shire", markupProcessor);
616 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_handler(): unexpected exception");
617 markupProcessor.insert("errorType", "Session Creation Service Error");
618 markupProcessor.insert("errorText", "Unknown Exception");
619 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
620 return shib_error_page(r, application, "shire", markupProcessor);
624 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,"shib_handler() server error");
625 return HTTP_INTERNAL_SERVER_ERROR;
628 static apr_table_t* groups_for_user(request_rec* r, const char* user, char* grpfile)
631 apr_table_t* grps=apr_table_make(r->pool,15);
632 char l[MAX_STRING_LEN];
633 const char *group_name, *ll, *w;
635 if (ap_pcfg_openfile(&f,r->pool,grpfile) != APR_SUCCESS) {
636 ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
637 "groups_for_user() could not open group file: %s\n", grpfile);
642 if (apr_pool_create(&sp,r->pool) != APR_SUCCESS) {
643 ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
644 "groups_for_user() could not create a subpool");
648 while (!(ap_cfg_getline(l,MAX_STRING_LEN,f))) {
649 if ((*l=='#') || (!*l))
654 group_name=ap_getword(sp,&ll,':');
657 w=ap_getword_conf(sp,&ll);
658 if (!strcmp(w,user)) {
659 apr_table_setn(grps,apr_pstrdup(r->pool,group_name),"in");
665 apr_pool_destroy(sp);
670 * shib_auth_checker() -- a simple resource manager to
671 * process the .htaccess settings and copy attributes
672 * into the HTTP headers.
674 extern "C" int shib_auth_checker(request_rec *r)
677 (shib_dir_config*)ap_get_module_config(r->per_dir_config,&mod_shib);
679 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_auth_checker() executing");
681 // Regular access to arbitrary resource...check AuthType
682 const char* auth_type=ap_auth_type(r);
683 if (!auth_type || strcasecmp(auth_type,"shibboleth"))
686 ostringstream threadid;
687 threadid << "[" << getpid() << "] shibrm" << '\0';
688 saml::NDC ndc(threadid.str().c_str());
690 // We lock the configuration system for the duration.
691 IConfig* conf=g_Config->getINI();
694 const char* application_id=apr_table_get(r->headers_in,"Shib-Application-ID");
695 const IApplication* application=NULL;
697 application = conf->getApplication(application_id);
701 int m=r->method_number;
702 bool method_restricted=false;
705 const apr_array_header_t* reqs_arr=ap_requires(r);
709 require_line* reqs=(require_line*)reqs_arr->elts;
712 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"REQUIRE nelts: %d", reqs_arr->nelts);
713 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"REQUIRE all: %d", dc->bRequireAll);
715 vector<bool> auth_OK(reqs_arr->nelts,false);
717 #define SHIB_AP_CHECK_IS_OK { \
718 if (dc->bRequireAll < 1) \
724 for (int x=0; x<reqs_arr->nelts; x++) {
727 if (!(reqs[x].method_mask & (1 << m)))
729 method_restricted=true;
731 t = reqs[x].requirement;
732 w = ap_getword_white(r->pool, &t);
734 if (!strcmp(w,"valid-user")) {
735 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r, "shib_auth_checker() accepting valid-user");
738 else if (!strcmp(w,"user") && r->user) {
741 w=ap_getword_conf(r->pool,&t);
749 // To do regex matching, we have to convert from UTF-8.
750 auto_ptr<XMLCh> trans(fromUTF8(w));
751 RegularExpression re(trans.get());
752 auto_ptr<XMLCh> trans2(fromUTF8(r->user));
753 if (re.matches(trans2.get())) {
754 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
755 "shib_auth_checker() accepting user: %s",w);
759 catch (XMLException& ex) {
760 auto_ptr_char tmp(ex.getMessage());
761 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
762 "shib_auth_checker caught exception while parsing regular expression (%s): %s",w,tmp.get());
765 else if (!strcmp(r->user,w)) {
766 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_auth_checker() accepting user: %s",w);
771 else if (!strcmp(w,"group")) {
772 apr_table_t* grpstatus=NULL;
773 if (dc->szAuthGrpFile && r->user) {
774 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
775 "shib_auth_checker() using groups file: %s\n", dc->szAuthGrpFile);
776 grpstatus=groups_for_user(r,r->user,dc->szAuthGrpFile);
782 w=ap_getword_conf(r->pool,&t);
783 if (apr_table_get(grpstatus,w)) {
784 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,"shib_auth_checker() accepting group: %s",w);
790 Iterator<IAAP*> provs=application ? application->getAAPProviders() : EMPTY(IAAP*);
791 AAP wrapper(provs,w);
792 if (wrapper.fail()) {
793 ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,
794 "shib_auth_checker() didn't recognize require rule: %s\n",w);
799 const char* vals=apr_table_get(r->headers_in,wrapper->getHeader());
801 w=ap_getword_conf(r->pool,&t);
808 auto_ptr<RegularExpression> re;
811 auto_ptr<XMLCh> trans(fromUTF8(w));
812 auto_ptr<RegularExpression> temp(new RegularExpression(trans.get()));
816 string vals_str(vals);
818 for (int i = 0; i < vals_str.length(); i++) {
819 if (vals_str.at(i) == ';') {
821 ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,0,r,
822 "shib_auth_checker() invalid header encoding %s: starts with semicolon", vals);
823 return HTTP_INTERNAL_SERVER_ERROR;
826 if (vals_str.at(i-1) == '\\') {
827 vals_str.erase(i-1, 1);
832 string val = vals_str.substr(j, i-j);
835 auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
836 if (re->matches(trans.get())) {
837 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
838 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
843 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
844 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
848 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
849 "shib_auth_checker() expecting %s, got %s: authorization not granted", w, val.c_str());
854 string val = vals_str.substr(j, vals_str.length()-j);
856 auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
857 if (re->matches(trans.get())) {
858 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
859 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
864 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
865 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
869 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,r,
870 "shib_auth_checker() expecting %s, got %s: authorization not granted", w, val.c_str());
873 catch (XMLException& ex) {
874 auto_ptr_char tmp(ex.getMessage());
875 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
876 "shib_auth_checker caught exception while parsing regular expression (%s): %s",w,tmp.get());
882 // check if all require directives are true
883 bool auth_all_OK = true;
884 for (int i= 0; i<reqs_arr->nelts; i++) {
885 auth_all_OK &= auth_OK[i];
890 if (!method_restricted)
893 if (!application_id) {
894 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
895 "shib_check_auth: Shib-Application-ID header not found in request");
896 return HTTP_FORBIDDEN;
898 else if (!application) {
899 ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,0,r,
900 "shib_check_auth: unable to map request to application settings, check configuration");
901 return HTTP_FORBIDDEN;
904 ShibMLP markupProcessor(application);
905 markupProcessor.insert("requestURL", ap_construct_url(r->pool,r->unparsed_uri,r));
906 return shib_error_page(r, application, "access", markupProcessor);
911 * Cleanup the (per-process) pool info.
913 extern "C" apr_status_t shib_exit(void* data)
915 g_Config->shutdown();
917 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,NULL,"shib_exit() done");
921 static const XMLCh Apache[] =
922 { chLatin_A, chLatin_p, chLatin_a, chLatin_c, chLatin_h, chLatin_e, chNull };
923 static const XMLCh apacheConfig[] =
924 { chLatin_a, chLatin_p, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
925 chLatin_C, chLatin_o, chLatin_n, chLatin_f, chLatin_i, chLatin_g, chNull
927 static const XMLCh Implementation[] =
928 { chLatin_I, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull };
932 * Things to do at process startup after the configs are read
934 extern "C" int shib_post_config(apr_pool_t* pconf, apr_pool_t* plog,
935 apr_pool_t* ptemp, server_rec* s)
937 // Initialize runtime components.
939 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,s,"shib_post_config() starting");
942 ap_log_error(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,0,s,"shib_post_config(): already initialized");
947 g_Config=&ShibTargetConfig::getConfig();
948 g_Config->setFeatures(
949 ShibTargetConfig::Listener |
950 ShibTargetConfig::Metadata |
951 ShibTargetConfig::AAP |
952 ShibTargetConfig::RequestMapper |
953 ShibTargetConfig::SHIREExtensions
955 if (!g_Config->init(g_szSchemaDir,g_szSHIBConfig)) {
956 ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,0,s,"shib_post_config(): already initialized!");
960 // Access the implementation-specifics for whether to use old Apache config style...
961 IConfig* conf=g_Config->getINI();
963 const IPropertySet* props=conf->getPropertySet("SHIRE");
965 const DOMElement* impl=saml::XML::getFirstChildElement(
966 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
968 if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Apache))) {
969 const XMLCh* flag=impl->getAttributeNS(NULL,apacheConfig);
970 if (flag && (*flag==chDigit_1 || *flag==chLatin_t))
976 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,s,
977 "shib_post_config() failed to initialize SHIB Target");
981 // Set the cleanup handler
982 apr_pool_cleanup_register(pconf, NULL, shib_exit, NULL);
984 ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,s,"shib_post_config() done");
989 extern "C" void shib_register_hooks (apr_pool_t *p)
991 ap_hook_post_config(shib_post_config, NULL, NULL, APR_HOOK_MIDDLE);
992 ap_hook_check_user_id(shib_check_user, NULL, NULL, APR_HOOK_MIDDLE);
993 ap_hook_auth_checker(shib_auth_checker, NULL, NULL, APR_HOOK_FIRST);
994 ap_hook_handler(shib_post_handler, NULL, NULL, APR_HOOK_LAST);
997 // SHIB Module commands
1000 static command_rec shib_cmds[] = {
1001 AP_INIT_TAKE1("ShibConfig",
1002 (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1003 RSRC_CONF, "Path to shibboleth.xml config file."),
1004 AP_INIT_TAKE1("ShibSchemaDir",
1005 (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
1006 RSRC_CONF, "Path to Shibboleth XML schema directory."),
1008 AP_INIT_FLAG("ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
1009 (void *) offsetof (shib_dir_config, bBasicHijack),
1010 OR_AUTHCFG, "Respond to AuthType Basic and convert to shib?"),
1011 AP_INIT_FLAG("ShibRequireSession", (config_fn_t)ap_set_flag_slot,
1012 (void *) offsetof (shib_dir_config, bRequireSession),
1013 OR_AUTHCFG, "Initiates a new session if one does not exist."),
1014 AP_INIT_FLAG("ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
1015 (void *) offsetof (shib_dir_config, bExportAssertion),
1016 OR_AUTHCFG, "Export SAML assertion to Shibboleth-defined header?"),
1017 AP_INIT_TAKE1("AuthGroupFile", (config_fn_t)ap_set_file_slot,
1018 (void *) offsetof (shib_dir_config, szAuthGrpFile),
1019 OR_AUTHCFG, "text file containing group names and member user IDs"),
1020 AP_INIT_FLAG("ShibRequireAll", (config_fn_t)ap_set_flag_slot,
1021 (void *) offsetof (shib_dir_config, bRequireAll),
1022 OR_AUTHCFG, "All require directives must match!"),
1027 module AP_MODULE_DECLARE_DATA mod_shib = {
1028 STANDARD20_MODULE_STUFF,
1029 create_shib_dir_config, /* create dir config */
1030 merge_shib_dir_config, /* merge dir config --- default is to override */
1031 NULL, /* create server config */
1032 NULL, /* merge server config */
1033 shib_cmds, /* command table */
1034 shib_register_hooks /* register hooks */