Add some debugging info in mod_apache to see where we're called.
[shibboleth/cpp-sp.git] / apache / mod_apache.cpp
1 /*
2  * mod_apache.cpp -- the core Apache Module code
3  *
4  * Created by:  Derek Atkins <derek@ihtfp.com>
5  *
6  * $Id$
7  */
8
9 // SAML Runtime
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>
15
16 #undef _XPG4_2
17
18 // Apache specific header files
19 #include <httpd.h>
20 #include <http_config.h>
21 #include <http_protocol.h>
22 #include <http_main.h>
23 #define CORE_PRIVATE
24 #include <http_core.h>
25 #include <http_log.h>
26
27 #ifndef SHIB_APACHE_13
28 #include <http_request.h>
29 #include <apr_strings.h>
30 #include <apr_pools.h>
31 #endif
32
33 #include <fstream>
34 #include <sstream>
35
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>             // for getpid()
38 #endif
39
40 using namespace std;
41 using namespace saml;
42 using namespace shibboleth;
43 using namespace shibtarget;
44
45 extern "C" module MODULE_VAR_EXPORT mod_shib;
46 int shib_handler(request_rec* r, const IApplication* application, SHIRE& shire);
47
48 namespace {
49     char* g_szSHIBConfig = NULL;
50     char* g_szSchemaDir = NULL;
51     ShibTargetConfig* g_Config = NULL;
52     static const char* g_UserDataKey = "_shib_check_user_";
53 }
54
55 // per-dir module configuration structure
56 struct shib_dir_config
57 {
58     // RM Configuration
59     char* szAuthGrpFile;    // Auth GroupFile name
60     int bRequireAll;        // all require directives must match, otherwise OR logic
61
62     // SHIRE Configuration
63     int bBasicHijack;       // activate for AuthType Basic?
64     int bRequireSession;    // require a session?
65     int bExportAssertion;   // export SAML assertion to the environment?
66 };
67
68 // creates per-directory config structure
69 extern "C" void* create_shib_dir_config (SH_AP_POOL* p, char* d)
70 {
71     shib_dir_config* dc=(shib_dir_config*)ap_pcalloc(p,sizeof(shib_dir_config));
72     dc->bBasicHijack = -1;
73     dc->bRequireSession = -1;
74     dc->bExportAssertion = -1;
75     dc->bRequireAll = -1;
76     dc->szAuthGrpFile = NULL;
77     return dc;
78 }
79
80 // overrides server configuration in directories
81 extern "C" void* merge_shib_dir_config (SH_AP_POOL* p, void* base, void* sub)
82 {
83     shib_dir_config* dc=(shib_dir_config*)ap_pcalloc(p,sizeof(shib_dir_config));
84     shib_dir_config* parent=(shib_dir_config*)base;
85     shib_dir_config* child=(shib_dir_config*)sub;
86
87     if (child->szAuthGrpFile)
88         dc->szAuthGrpFile=ap_pstrdup(p,child->szAuthGrpFile);
89     else if (parent->szAuthGrpFile)
90         dc->szAuthGrpFile=ap_pstrdup(p,parent->szAuthGrpFile);
91     else
92         dc->szAuthGrpFile=NULL;
93
94     dc->bBasicHijack=((child->bBasicHijack==-1) ? parent->bBasicHijack : child->bBasicHijack);
95     dc->bRequireSession=((child->bRequireSession==-1) ? parent->bRequireSession : child->bRequireSession);
96     dc->bExportAssertion=((child->bExportAssertion==-1) ? parent->bExportAssertion : child->bExportAssertion);
97     dc->bRequireAll=((child->bRequireAll==-1) ? parent->bRequireAll : child->bRequireAll);
98     return dc;
99 }
100
101 // generic global slot handlers
102 extern "C" const char* ap_set_global_string_slot(cmd_parms* parms, void*, const char* arg)
103 {
104     *((char**)(parms->info))=ap_pstrdup(parms->pool,arg);
105     return NULL;
106 }
107
108 typedef const char* (*config_fn_t)(void);
109
110 static int shib_error_page(request_rec* r, const IApplication* app, const char* page, ShibMLP& mlp)
111 {
112     const IPropertySet* props=app->getPropertySet("Errors");
113     if (props) {
114         pair<bool,const char*> p=props->getString(page);
115         if (p.first) {
116             ifstream infile(p.second);
117             if (!infile.fail()) {
118                 const char* res = mlp.run(infile,props);
119                 if (res) {
120                     r->content_type = ap_psprintf(r->pool, "text/html");
121                     ap_send_http_header(r);
122                     ap_rprintf(r, res);
123                     return DONE;
124                 }
125             }
126         }
127     }
128
129     ap_log_rerror(APLOG_MARK,APLOG_ERR,SH_AP_R(r),
130         "shib_error_page() could not process shire error template for application %s",app->getId());
131     return SERVER_ERROR;
132 }
133
134 extern "C" int shib_check_user(request_rec* r)
135 {
136     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user: ENTER");
137     shib_dir_config* dc=(shib_dir_config*)ap_get_module_config(r->per_dir_config,&mod_shib);
138
139     ostringstream threadid;
140     threadid << "[" << getpid() << "] shib_check_user" << '\0';
141     saml::NDC ndc(threadid.str().c_str());
142
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);
145
146     // We lock the configuration system for the duration.
147     IConfig* conf=g_Config->getINI();
148     Locker locker(conf);
149     
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
155         );
156     pair<bool,const char*> application_id=settings.first->getString("applicationId");
157     const IApplication* application=conf->getApplication(application_id.second);
158     if (!application) {
159         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
160            "shib_check_user: unable to map request to application settings, check configuration");
161         return SERVER_ERROR;
162     }
163     
164     // Declare SHIRE object for this request.
165     SHIRE shire(application);
166     
167     // Get location of this application's assertion consumer service and see if this is it.
168     if (strstr(targeturl,shire.getShireURL(targeturl))) {
169         return shib_handler(r,application,shire);
170     }
171
172     // We can short circuit the handler if we run this...
173     apr_pool_userdata_setn((const void*)42,g_UserDataKey,NULL,r->pool);
174
175     // Regular access to arbitrary resource...check AuthType
176     const char *auth_type=ap_auth_type(r);
177     if (!auth_type)
178         return DECLINED;
179
180     if (strcasecmp(auth_type,"shibboleth")) {
181         if (!strcasecmp(auth_type,"basic") && dc->bBasicHijack==1) {
182             core_dir_config* conf=
183                 (core_dir_config*)ap_get_module_config(r->per_dir_config,
184                     ap_find_linked_module("http_core.c"));
185             conf->ap_auth_type="shibboleth";
186         }
187         else
188             return DECLINED;
189     }
190
191     pair<bool,bool> requireSession = settings.first->getBool("requireSession");
192     if (!requireSession.first || !requireSession.second)
193         if (dc->bRequireSession==1)
194             requireSession.second=true;
195
196     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user: session check for %s",targeturl);
197
198     pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();   // always returns *something*
199
200     // We're in charge, so check for cookie.
201     const char* session_id=NULL;
202     const char* cookies=ap_table_get(r->headers_in,"Cookie");
203
204     if (cookies) {
205         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user: cookies found: %s",cookies);
206         if (session_id=strstr(cookies,shib_cookie.first)) {
207             // Yep, we found a cookie -- pull it out (our session_id)
208             session_id+=strlen(shib_cookie.first) + 1; /* Skip over the '=' */
209             char* cookiebuf = ap_pstrdup(r->pool,session_id);
210             char* cookieend = strchr(cookiebuf,';');
211             if (cookieend)
212                 *cookieend = '\0';    /* Ignore anyting after a ; */
213             session_id=cookiebuf;
214         }
215     }
216
217     if (!session_id || !*session_id) {
218         // If no session required, bail now.
219         if (!requireSession.second)
220             return OK;
221
222         // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
223         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user: no cookie found -- redirecting to WAYF");
224         ap_table_setn(r->headers_out,"Location",ap_pstrdup(r->pool,shire.getAuthnRequest(targeturl)));
225         return REDIRECT;
226     }
227
228     // Make sure this session is still valid.
229     RPCError* status = NULL;
230     ShibMLP markupProcessor;
231     markupProcessor.insert("requestURL", targeturl);
232
233     try {
234         status = shire.sessionIsValid(session_id, r->connection->remote_ip);
235     }
236     catch (ShibTargetException &e) {
237         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user(): %s", e.what());
238         markupProcessor.insert("errorType", "Session Processing Error");
239         markupProcessor.insert("errorText", e.what());
240         markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
241         return shib_error_page(r, application, "shire", markupProcessor);
242     }
243 #ifndef _DEBUG
244     catch (...) {
245         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user(): caught unexpected error");
246         markupProcessor.insert("errorType", "Session Processing Error");
247         markupProcessor.insert("errorText", "Unexpected Exception");
248         markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
249         return shib_error_page(r, application, "shire", markupProcessor);
250     }
251 #endif
252
253     // Check the status
254     if (status->isError()) {
255         ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,SH_AP_R(r),
256                       "shib_check_user() session invalid: %s", status->getText());
257
258         // If no session required, bail now.
259         if (!requireSession.second)
260             return OK;  // XXX: Or should this be DECLINED?
261                         // Has to be OK because DECLINED will just cause Apache to fail when it can't locate
262                         // anything to process the AuthType. No session plus requireSession false means 
263                         // do not authenticate the user.
264         else if (status->isRetryable()) {
265             // Oops, session is invalid. Generate AuthnRequest.
266             ap_table_setn(r->headers_out,"Location",ap_pstrdup(r->pool,shire.getAuthnRequest(targeturl)));
267             delete status;
268             return REDIRECT;
269         }
270         else {
271             // return the error page to the user
272             markupProcessor.insert(*status);
273             delete status;
274             return shib_error_page(r, application, "shire", markupProcessor);
275         }
276     }
277
278     delete status;
279     // set the authtype
280 #ifdef SHIB_APACHE_13
281     if (r->connection)
282         r->connection->ap_auth_type = "shibboleth";
283 #else
284     r->ap_auth_type = "shibboleth";
285 #endif
286     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user: session successfully verified");
287
288     // This is code transferred in from the auth check to export the attributes.
289     // We could even combine the isSessionValid/getAssertions API...?
290
291     RM rm(application);
292     vector<SAMLAssertion*> assertions;
293     SAMLAuthenticationStatement* sso_statement=NULL;
294
295     try {
296         status = rm.getAssertions(session_id, r->connection->remote_ip, assertions, &sso_statement);
297     }
298     catch (ShibTargetException &e) {
299         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user(): %s", e.what());
300         markupProcessor.insert("errorType", "Attribute Processing Error");
301         markupProcessor.insert("errorText", e.what());
302         markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
303         return shib_error_page(r, application, "rm", markupProcessor);
304     }
305 #ifndef _DEBUG
306     catch (...) {
307         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user(): caught unexpected error");
308         markupProcessor.insert("errorType", "Attribute Processing Error");
309         markupProcessor.insert("errorText", "Unexpected Exception");
310         markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
311         return shib_error_page(r, application, "rm", markupProcessor);
312     }
313 #endif
314
315     if (status->isError()) {
316         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
317             "shib_check_user() getAssertions failed: %s", status->getText());
318
319         markupProcessor.insert(*status);
320         delete status;
321         return shib_error_page(r, application, "rm", markupProcessor);
322     }
323     delete status;
324
325     // Do we have an access control plugin?
326     if (settings.second) {
327         Locker acllock(settings.second);
328         if (!settings.second->authorized(assertions)) {
329             for (int k = 0; k < assertions.size(); k++)
330                 delete assertions[k];
331             delete sso_statement;
332             ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),"shib_check_user(): access control provider denied access");
333             return shib_error_page(r, application, "access", markupProcessor);
334         }
335     }
336
337     // Get the AAP providers, which contain the attribute policy info.
338     Iterator<IAAP*> provs=application->getAAPProviders();
339
340     // Clear out the list of mapped attributes
341     while (provs.hasNext()) {
342         IAAP* aap=provs.next();
343         aap->lock();
344         try {
345             Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
346             while (rules.hasNext()) {
347                 const char* header=rules.next()->getHeader();
348                 if (header)
349                     ap_table_unset(r->headers_in,header);
350             }
351         }
352         catch(...) {
353             aap->unlock();
354             for (int k = 0; k < assertions.size(); k++)
355                 delete assertions[k];
356             delete sso_statement;
357             ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
358                 "shib_check_user(): caught unexpected error while clearing headers");
359             markupProcessor.insert("errorType", "Attribute Processing Error");
360             markupProcessor.insert("errorText", "Unexpected Exception");
361             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
362             return shib_error_page(r, application, "rm", markupProcessor);
363         }
364         aap->unlock();
365     }
366     provs.reset();
367     
368     // Maybe export the first assertion.
369     ap_table_unset(r->headers_in,"Shib-Attributes");
370     pair<bool,bool> exp=settings.first->getBool("exportAssertion");
371     if (!exp.first || !exp.second)
372         if (dc->bExportAssertion==1)
373             exp.second=true;
374     if (exp.second && assertions.size()) {
375         string assertion;
376         RM::serialize(*(assertions[0]), assertion);
377         ap_table_set(r->headers_in,"Shib-Attributes", assertion.c_str());
378     }
379
380     // Export the SAML AuthnMethod and the origin site name, and possibly the NameIdentifier.
381     ap_table_unset(r->headers_in,"Shib-Origin-Site");
382     ap_table_unset(r->headers_in,"Shib-Authentication-Method");
383     ap_table_unset(r->headers_in,"Shib-NameIdentifier-Format");
384     if (sso_statement) {
385         auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
386         auto_ptr_char am(sso_statement->getAuthMethod());
387         ap_table_set(r->headers_in,"Shib-Origin-Site", os.get());
388         ap_table_set(r->headers_in,"Shib-Authentication-Method", am.get());
389         
390         // Export NameID?
391         AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
392         if (!wrapper.fail() && wrapper->getHeader()) {
393             auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
394             auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
395             ap_table_set(r->headers_in,"Shib-NameIdentifier-Format",form.get());
396             if (!strcmp(wrapper->getHeader(),"REMOTE_USER"))
397                 SH_AP_USER(r)=ap_pstrdup(r->pool,nameid.get());
398             else
399                 ap_table_set(r->headers_in,wrapper->getHeader(),nameid.get());
400         }
401     }
402     
403     ap_table_unset(r->headers_in,"Shib-Application-ID");
404     ap_table_set(r->headers_in,"Shib-Application-ID",application_id.second);
405
406     // Export the attributes.
407     Iterator<SAMLAssertion*> a_iter(assertions);
408     while (a_iter.hasNext()) {
409         SAMLAssertion* assert=a_iter.next();
410         Iterator<SAMLStatement*> statements=assert->getStatements();
411         while (statements.hasNext()) {
412             SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
413             if (!astate)
414                 continue;
415             Iterator<SAMLAttribute*> attrs=astate->getAttributes();
416             while (attrs.hasNext()) {
417                 SAMLAttribute* attr=attrs.next();
418         
419                 // Are we supposed to export it?
420                 AAP wrapper(provs,attr->getName(),attr->getNamespace());
421                 if (wrapper.fail() || !wrapper->getHeader())
422                     continue;
423                 
424                 Iterator<string> vals=attr->getSingleByteValues();
425                 if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext())
426                     SH_AP_USER(r)=ap_pstrdup(r->pool,vals.next().c_str());
427                 else {
428                     int it=0;
429                     char* header = (char*)ap_table_get(r->headers_in, wrapper->getHeader());
430                     if (header) {
431                         header=ap_pstrdup(r->pool, header);
432                         it++;
433                     }
434                     else
435                         header = ap_pstrdup(r->pool, "");
436                     for (; vals.hasNext(); it++) {
437                         string value = vals.next();
438                         for (string::size_type pos = value.find_first_of(";", string::size_type(0));
439                                 pos != string::npos;
440                                 pos = value.find_first_of(";", pos)) {
441                             value.insert(pos, "\\");
442                             pos += 2;
443                         }
444                         header=ap_pstrcat(r->pool, header, (it ? ";" : ""), value.c_str(), NULL);
445                     }
446                     ap_table_setn(r->headers_in, wrapper->getHeader(), header);
447                }
448             }
449         }
450     }
451
452     // clean up memory
453     for (int k = 0; k < assertions.size(); k++)
454         delete assertions[k];
455     delete sso_statement;
456
457     return OK;
458 }
459
460 extern "C" int shib_post_handler(request_rec* r)
461 {
462   ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
463                 "shib_post_handler(%d): ENTER", (int)getpid());
464
465 #ifndef SHIB_APACHE_13
466     // With 2.x, this handler always runs, though last.
467     // We check if shib_check_user ran, because it will detect a SHIRE request
468     // and dispatch it directly.
469     void* data;
470     apr_pool_userdata_get(&data,g_UserDataKey,r->pool);
471     if (data==(const void*)42) {
472         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_post_handler skipped since check_user ran");
473         return DECLINED;
474     }
475 #endif
476     
477     ostringstream threadid;
478     threadid << "[" << getpid() << "] shib_post_handler" << '\0';
479     saml::NDC ndc(threadid.str().c_str());
480
481     // We lock the configuration system for the duration.
482     IConfig* conf=g_Config->getINI();
483     Locker locker(conf);
484     
485     // Map request to application and content settings.
486     IRequestMapper* mapper=conf->getRequestMapper();
487     Locker locker2(mapper);
488     IRequestMapper::Settings settings=mapper->getSettingsFromParsedURL(
489         ap_http_method(r), ap_get_server_name(r), ap_get_server_port(r), r->unparsed_uri
490         );
491     pair<bool,const char*> application_id=settings.first->getString("applicationId");
492     const IApplication* application=conf->getApplication(application_id.second);
493     if (!application) {
494         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
495            "shib_post_handler: unable to map request to application settings, check configuration");
496         return SERVER_ERROR;
497     }
498     
499     // Declare SHIRE object for this request.
500     SHIRE shire(application);
501     
502     return shib_handler(r, application, shire);
503 }
504
505 int shib_handler(request_rec* r, const IApplication* application, SHIRE& shire)
506 {
507     // Prime the pump...
508     const char* targeturl = ap_construct_url(r->pool,r->unparsed_uri,r);
509
510     // Make sure we only process the SHIRE requests.
511     if (!strstr(targeturl,shire.getShireURL(targeturl)))
512         return DECLINED;
513
514     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_handler() running");
515
516     const IPropertySet* sessionProps=application->getPropertySet("Sessions");
517     if (!sessionProps) {
518         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
519            "shib_post_handler: unable to map request to application session settings, check configuration");
520         return SERVER_ERROR;
521     }
522
523     pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();   // always returns something
524
525     ShibMLP markupProcessor;
526     markupProcessor.insert("requestURL", targeturl);
527
528     // Process SHIRE request
529     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_handler() Beginning SHIRE processing");
530       
531     try {
532         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
533       
534         // Make sure this is SSL, if it should be
535         if ((!shireSSL.first || shireSSL.second) && strcmp(ap_http_method(r),"https"))
536             throw ShibTargetException(SHIBRPC_OK, "blocked non-SSL access to session creation service");
537
538         // If this is a GET, we manufacture an AuthnRequest.
539         if (!strcasecmp(r->method,"GET")) {
540             const char* areq=r->args ? shire.getLazyAuthnRequest(r->args) : NULL;
541             if (!areq)
542                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
543             ap_table_setn(r->headers_out, "Location", ap_pstrdup(r->pool,areq));
544             return REDIRECT;
545         }
546         else if (strcasecmp(r->method,"POST")) {
547             throw ShibTargetException(SHIBRPC_OK, "blocked non-POST to SHIRE POST processor");
548         }
549
550         // Sure sure this POST is an appropriate content type
551         const char *ct = ap_table_get(r->headers_in, "Content-type");
552         if (!ct || strcasecmp(ct, "application/x-www-form-urlencoded"))
553             throw ShibTargetException(SHIBRPC_OK,
554                                       ap_psprintf(r->pool, "blocked bad content-type to SHIRE POST processor: %s", (ct ? ct : "")));
555
556         // Read the posted data
557         if (ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))
558             throw ShibTargetException(SHIBRPC_OK, "CGI setup_client_block failed");
559         if (!ap_should_client_block(r))
560             throw ShibTargetException(SHIBRPC_OK, "CGI should_client_block failed");
561         if (r->remaining > 1024*1024)
562             throw ShibTargetException (SHIBRPC_OK, "CGI length too long...");
563
564         string cgistr;
565         char buff[HUGE_STRING_LEN];
566         ap_hard_timeout("[mod_shib] CGI Parser", r);
567         memset(buff, 0, sizeof(buff));
568         while (ap_get_client_block(r, buff, sizeof(buff)-1) > 0) {
569             cgistr += buff;
570             memset(buff, 0, sizeof(buff));
571         }
572         ap_kill_timeout(r);
573
574         // Parse the submission.
575         pair<const char*,const char*> elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
576     
577         // Make sure the SAML Response parameter exists
578         if (!elements.first || !*elements.first)
579             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
580     
581         // Make sure the target parameter exists
582         if (!elements.second || !*elements.second)
583             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
584     
585         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
586             "shib_handler() Processing POST for target: %s", elements.second);
587
588         // process the post
589         string cookie;
590         RPCError* status = shire.sessionCreate(elements.first, r->connection->remote_ip, cookie);
591
592         if (status->isError()) {
593             ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
594                     "shib_handler() POST process failed (%d): %s", status->getCode(), status->getText());
595
596             if (status->isRetryable()) {
597                 delete status;
598                 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,SH_AP_R(r),
599                         "shib_handler() retryable error, generating new AuthnRequest");
600                 ap_table_setn(r->headers_out,"Location",ap_pstrdup(r->pool,shire.getAuthnRequest(elements.second)));
601                 return REDIRECT;
602             }
603
604             // return this error to the user.
605             markupProcessor.insert(*status);
606             delete status;
607             return shib_error_page(r, application, "shire", markupProcessor);
608         }
609         delete status;
610
611         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
612                   "shib_handler() POST process succeeded.  New session: %s", cookie.c_str());
613
614         // We've got a good session, set the cookie...
615         char* val = ap_psprintf(r->pool,"%s=%s%s",shib_cookie.first,cookie.c_str(),shib_cookie.second);
616         ap_table_setn(r->err_headers_out, "Set-Cookie", val);
617         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_handler() setting cookie: %s", val);
618
619         // ... and redirect to the target
620         ap_table_setn(r->headers_out, "Location", ap_pstrdup(r->pool,elements.second));
621         return REDIRECT;
622     }
623     catch (ShibTargetException &e) {
624         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_handler() caught exception: %s", e.what());
625         markupProcessor.insert("errorType", "Session Creation Service Error");
626         markupProcessor.insert("errorText", e.what());
627         markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
628         return shib_error_page(r, application, "shire", markupProcessor);
629     }
630 #ifndef _DEBUG
631     catch (...) {
632         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_handler(): unexpected exception");
633         markupProcessor.insert("errorType", "Session Creation Service Error");
634         markupProcessor.insert("errorText", "Unknown Exception");
635         markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
636         return shib_error_page(r, application, "shire", markupProcessor);
637     }
638 #endif
639
640     ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),"shib_handler() server error");
641     return SERVER_ERROR;
642 }
643
644 static SH_AP_TABLE* groups_for_user(request_rec* r, const char* user, char* grpfile)
645 {
646     SH_AP_CONFIGFILE* f;
647     SH_AP_TABLE* grps=ap_make_table(r->pool,15);
648     char l[MAX_STRING_LEN];
649     const char *group_name, *ll, *w;
650
651 #ifdef SHIB_APACHE_13
652     if (!(f=ap_pcfg_openfile(r->pool,grpfile))) {
653 #else
654     if (ap_pcfg_openfile(&f,r->pool,grpfile) != APR_SUCCESS) {
655 #endif
656         ap_log_rerror(APLOG_MARK,APLOG_DEBUG,SH_AP_R(r),"groups_for_user() could not open group file: %s\n",grpfile);
657         return NULL;
658     }
659
660     SH_AP_POOL* sp;
661 #ifdef SHIB_APACHE_13
662     sp=ap_make_sub_pool(r->pool);
663 #else
664     if (apr_pool_create(&sp,r->pool) != APR_SUCCESS) {
665         ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
666             "groups_for_user() could not create a subpool");
667         return NULL;
668     }
669 #endif
670
671     while (!(ap_cfg_getline(l,MAX_STRING_LEN,f))) {
672         if ((*l=='#') || (!*l))
673             continue;
674         ll = l;
675         ap_clear_pool(sp);
676
677         group_name=ap_getword(sp,&ll,':');
678
679         while (*ll) {
680             w=ap_getword_conf(sp,&ll);
681             if (!strcmp(w,user)) {
682                 ap_table_setn(grps,ap_pstrdup(r->pool,group_name),"in");
683                 break;
684             }
685         }
686     }
687     ap_cfg_closefile(f);
688     ap_destroy_pool(sp);
689     return grps;
690 }
691
692 /*
693  * shib_auth_checker() -- a simple resource manager to
694  * process the .htaccess settings and copy attributes
695  * into the HTTP headers.
696  */
697 extern "C" int shib_auth_checker(request_rec* r)
698 {
699     shib_dir_config* dc=
700         (shib_dir_config*)ap_get_module_config(r->per_dir_config,&mod_shib);
701
702     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() executing");
703
704     // Regular access to arbitrary resource...check AuthType
705     const char* auth_type=ap_auth_type(r);
706     if (!auth_type || strcasecmp(auth_type,"shibboleth"))
707         return DECLINED;
708
709     ostringstream threadid;
710     threadid << "[" << getpid() << "] shibrm" << '\0';
711     saml::NDC ndc(threadid.str().c_str());
712
713     // We lock the configuration system for the duration.
714     IConfig* conf=g_Config->getINI();
715     Locker locker(conf);
716     
717     const char* application_id=ap_table_get(r->headers_in,"Shib-Application-ID");
718     const IApplication* application=NULL;
719     if (application_id)
720         application = conf->getApplication(application_id);
721
722     // mod_auth clone
723
724     int m=r->method_number;
725     bool method_restricted=false;
726     const char *t, *w;
727     
728     const array_header* reqs_arr=ap_requires(r);
729     if (!reqs_arr)
730         return OK;
731
732     require_line* reqs=(require_line*)reqs_arr->elts;
733
734     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"REQUIRE nelts: %d", reqs_arr->nelts);
735     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"REQUIRE all: %d", dc->bRequireAll);
736
737     vector<bool> auth_OK(reqs_arr->nelts,false);
738
739 #define SHIB_AP_CHECK_IS_OK {       \
740      if (dc->bRequireAll < 1)    \
741          return OK;      \
742      auth_OK[x] = true;      \
743      continue;           \
744 }
745
746     for (int x=0; x<reqs_arr->nelts; x++) {
747         auth_OK[x] = false;
748         if (!(reqs[x].method_mask & (1 << m)))
749             continue;
750         method_restricted=true;
751
752         t = reqs[x].requirement;
753         w = ap_getword_white(r->pool, &t);
754
755         if (!strcasecmp(w,"Shibboleth")) {
756             // This is a dummy rule needed because Apache conflates authn and authz.
757             // Without some require rule, AuthType is ignored and no check_user hooks run.
758             SHIB_AP_CHECK_IS_OK;
759         }
760         else if (!strcmp(w,"valid-user") && application_id) {
761             ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() accepting valid-user");
762             SHIB_AP_CHECK_IS_OK;
763         }
764         else if (!strcmp(w,"user") && SH_AP_USER(r)) {
765             bool regexp=false;
766             while (*t) {
767                 w=ap_getword_conf(r->pool,&t);
768                 if (*w=='~') {
769                     regexp=true;
770                     continue;
771                 }
772                 
773                 if (regexp) {
774                     try {
775                         // To do regex matching, we have to convert from UTF-8.
776                         auto_ptr<XMLCh> trans(fromUTF8(w));
777                         RegularExpression re(trans.get());
778                         auto_ptr<XMLCh> trans2(fromUTF8(SH_AP_USER(r)));
779                         if (re.matches(trans2.get())) {
780                             ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() accepting user: %s",w);
781                             SHIB_AP_CHECK_IS_OK;
782                         }
783                     }
784                     catch (XMLException& ex) {
785                         auto_ptr_char tmp(ex.getMessage());
786                         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
787                                         "shib_auth_checker caught exception while parsing regular expression (%s): %s",w,tmp.get());
788                     }
789                 }
790                 else if (!strcmp(SH_AP_USER(r),w)) {
791                     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() accepting user: %s",w);
792                     SHIB_AP_CHECK_IS_OK;
793                 }
794             }
795         }
796         else if (!strcmp(w,"group")) {
797             SH_AP_TABLE* grpstatus=NULL;
798             if (dc->szAuthGrpFile && SH_AP_USER(r)) {
799                 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() using groups file: %s\n",dc->szAuthGrpFile);
800                 grpstatus=groups_for_user(r,SH_AP_USER(r),dc->szAuthGrpFile);
801             }
802             if (!grpstatus)
803                 return DECLINED;
804     
805             while (*t) {
806                 w=ap_getword_conf(r->pool,&t);
807                 if (ap_table_get(grpstatus,w)) {
808                     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() accepting group: %s",w);
809                     SHIB_AP_CHECK_IS_OK;
810                 }
811             }
812         }
813         else {
814             Iterator<IAAP*> provs=application ? application->getAAPProviders() : EMPTY(IAAP*);
815             AAP wrapper(provs,w);
816             if (wrapper.fail()) {
817                 ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,SH_AP_R(r),
818                     "shib_auth_checker() didn't recognize require rule: %s\n",w);
819                 continue;
820             }
821
822             bool regexp=false;
823             const char* vals=ap_table_get(r->headers_in,wrapper->getHeader());
824             while (*t && vals) {
825                 w=ap_getword_conf(r->pool,&t);
826                 if (*w=='~') {
827                     regexp=true;
828                     continue;
829                 }
830
831                 try {
832                     auto_ptr<RegularExpression> re;
833                     if (regexp) {
834                         delete re.release();
835                         auto_ptr<XMLCh> trans(fromUTF8(w));
836                         auto_ptr<RegularExpression> temp(new RegularExpression(trans.get()));
837                         re=temp;
838                     }
839                     
840                     string vals_str(vals);
841                     int j = 0;
842                     for (int i = 0;  i < vals_str.length();  i++) {
843                         if (vals_str.at(i) == ';') {
844                             if (i == 0) {
845                                 ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,SH_AP_R(r),
846                                                 "shib_auth_checker() invalid header encoding %s: starts with semicolon", vals);
847                                 return SERVER_ERROR;
848                             }
849
850                             if (vals_str.at(i-1) == '\\') {
851                                 vals_str.erase(i-1, 1);
852                                 i--;
853                                 continue;
854                             }
855
856                             string val = vals_str.substr(j, i-j);
857                             j = i+1;
858                             if (regexp) {
859                                 auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
860                                 if (re->matches(trans.get())) {
861                                     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
862                                                     "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
863                                     SHIB_AP_CHECK_IS_OK;
864                                 }
865                             }
866                             else if (val==w) {
867                                 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
868                                                 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
869                                 SHIB_AP_CHECK_IS_OK;
870                             }
871                             else {
872                                 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
873                                                 "shib_auth_checker() expecting %s, got %s: authorization not granted", w, val.c_str());
874                             }
875                         }
876                     }
877     
878                     string val = vals_str.substr(j, vals_str.length()-j);
879                     if (regexp) {
880                         auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
881                         if (re->matches(trans.get())) {
882                             ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
883                                             "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
884                             SHIB_AP_CHECK_IS_OK;
885                         }
886                     }
887                     else if (val==w) {
888                         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
889                                         "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
890                         SHIB_AP_CHECK_IS_OK;
891                     }
892                     else {
893                         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
894                                         "shib_auth_checker() expecting %s, got %s: authorization not granted", w, val.c_str());
895                     }
896                 }
897                 catch (XMLException& ex) {
898                     auto_ptr_char tmp(ex.getMessage());
899                     ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
900                                     "shib_auth_checker caught exception while parsing regular expression (%s): %s",w,tmp.get());
901                 }
902             }
903         }
904     }
905
906     // check if all require directives are true
907     bool auth_all_OK = true;
908     for (int i= 0; i<reqs_arr->nelts; i++) {
909         auth_all_OK &= auth_OK[i];
910     }
911     if (auth_all_OK)
912         return OK;
913
914     if (!method_restricted)
915         return OK;
916
917     if (!application_id) {
918         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
919            "shib_auth_checker: Shib-Application-ID header not found in request");
920         return HTTP_FORBIDDEN;
921     }
922     else if (!application) {
923         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
924            "shib_auth_checker: unable to map request to application settings, check configuration");
925         return HTTP_FORBIDDEN;
926     }
927
928     ShibMLP markupProcessor;
929     markupProcessor.insert("requestURL", ap_construct_url(r->pool,r->unparsed_uri,r));
930     return shib_error_page(r, application, "access", markupProcessor);
931 }
932
933 /*
934  * shib_exit()
935  *  Cleanup the (per-process) pool info.
936  */
937 #ifdef SHIB_APACHE_13
938 extern "C" void shib_exit(server_rec* s, SH_AP_POOL* p)
939 {
940 #else
941 extern "C" apr_status_t shib_exit(void* data)
942 {
943     server_rec* s = NULL;
944 #endif
945
946     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_exit(%d) dealing with g_Config..", (int)getpid());
947
948     g_Config->shutdown();
949     g_Config = NULL;
950
951     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_exit() done\n");
952 #ifndef SHIB_APACHE_13
953     return OK;
954 #endif
955 }
956
957
958 #ifdef SHIB_APACHE_13
959 extern "C" void shib_child_exit(server_rec* s, SH_AP_POOL* p)
960 #else
961 extern "C" apr_status_t shib_child_exit(void* data)
962 #endif
963 {
964   server_rec* s = NULL;
965
966   ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_exit(%d)",
967                (int)getpid());
968
969 #ifndef SHIB_APACHE_13
970     return OK;
971 #endif
972 }
973
974 /* 
975  * shire_child_init()
976  *  Things to do when the child process is initialized.
977  *  (or after the configs are read in apache-2)
978  */
979 #ifdef SHIB_APACHE_13
980 extern "C" void shib_child_init(server_rec* s, SH_AP_POOL* p)
981 #else
982 extern "C" int shib_post_config(apr_pool_t* pconf, apr_pool_t* plog,
983                                 apr_pool_t* ptemp, server_rec* s)
984 #endif
985 {
986     // Initialize runtime components.
987
988     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init(%d) starting", (int)getpid());
989
990     if (g_Config) {
991         ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() already initialized!");
992 #ifdef SHIB_APACHE_13
993         exit(1);
994 #else
995         return OK;
996 #endif
997     }
998
999     try {
1000         g_Config=&ShibTargetConfig::getConfig();
1001         g_Config->setFeatures(
1002             ShibTargetConfig::Listener |
1003             ShibTargetConfig::Metadata |
1004             ShibTargetConfig::AAP |
1005             ShibTargetConfig::RequestMapper |
1006             ShibTargetConfig::SHIREExtensions
1007             );
1008         if (!g_Config->init(g_szSchemaDir,g_szSHIBConfig)) {
1009             ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to initialize SHIB Target");
1010             exit(1);
1011         }
1012     }
1013     catch (...) {
1014         ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to initialize SHIB Target");
1015         exit (1);
1016     }
1017
1018     // Set the cleanup handler
1019     apr_pool_cleanup_register(pconf, NULL, &shib_exit, &shib_child_exit);
1020
1021     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() done");
1022
1023 #ifndef SHIB_APACHE_13
1024     return OK;
1025 #endif
1026 }
1027
1028 #ifdef SHIB_APACHE_13
1029
1030 // SHIB Module commands
1031
1032 static command_rec shire_cmds[] = {
1033   {"SHIREConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1034    RSRC_CONF, TAKE1, "Path to shibboleth.xml config file."},
1035   {"ShibConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1036    RSRC_CONF, TAKE1, "Path to shibboleth.xml config file."},
1037   {"ShibSchemaDir", (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
1038    RSRC_CONF, TAKE1, "Path to Shibboleth XML schema directory."},
1039
1040   {"ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
1041    (void *) XtOffsetOf (shib_dir_config, bBasicHijack),
1042    OR_AUTHCFG, FLAG, "Respond to AuthType Basic and convert to shib?"},
1043   {"ShibRequireSession", (config_fn_t)ap_set_flag_slot,
1044    (void *) XtOffsetOf (shib_dir_config, bRequireSession),
1045    OR_AUTHCFG, FLAG, "Initiates a new session if one does not exist."},
1046   {"ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
1047    (void *) XtOffsetOf (shib_dir_config, bExportAssertion),
1048    OR_AUTHCFG, FLAG, "Export SAML assertion to Shibboleth-defined header?"},
1049   {"AuthGroupFile", (config_fn_t)ap_set_file_slot,
1050    (void *) XtOffsetOf (shib_dir_config, szAuthGrpFile),
1051    OR_AUTHCFG, TAKE1, "text file containing group names and member user IDs"},
1052   {"ShibRequireAll", (config_fn_t)ap_set_flag_slot,
1053    (void *) XtOffsetOf (shib_dir_config, bRequireAll),
1054    OR_AUTHCFG, FLAG, "All require directives must match!"},
1055
1056   {NULL}
1057 };
1058
1059 extern "C"{
1060 handler_rec shib_handlers[] = {
1061   { "shib-shire-post", shib_post_handler },
1062   { NULL }
1063 };
1064
1065 module MODULE_VAR_EXPORT mod_shib = {
1066     STANDARD_MODULE_STUFF,
1067     NULL,                        /* initializer */
1068     create_shib_dir_config,     /* dir config creater */
1069     merge_shib_dir_config,      /* dir merger --- default is to override */
1070     NULL,                       /* server config */
1071     NULL,                       /* merge server config */
1072     shire_cmds,                 /* command table */
1073     shib_handlers,              /* handlers */
1074     NULL,                       /* filename translation */
1075     shib_check_user,            /* check_user_id */
1076     shib_auth_checker,          /* check auth */
1077     NULL,                       /* check access */
1078     NULL,                       /* type_checker */
1079     NULL,                       /* fixups */
1080     NULL,                       /* logger */
1081     NULL,                       /* header parser */
1082     shib_child_init,            /* child_init */
1083     shib_exit,                  /* child_exit */
1084     NULL                        /* post read-request */
1085 };
1086
1087 #elif defined(SHIB_APACHE_20)
1088
1089 extern "C" void shib_register_hooks (apr_pool_t *p)
1090 {
1091   ap_hook_post_config(shib_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1092   ap_hook_check_user_id(shib_check_user, NULL, NULL, APR_HOOK_MIDDLE);
1093   ap_hook_auth_checker(shib_auth_checker, NULL, NULL, APR_HOOK_FIRST);
1094   ap_hook_handler(shib_post_handler, NULL, NULL, APR_HOOK_LAST);
1095 }
1096
1097 // SHIB Module commands
1098
1099 extern "C" {
1100 static command_rec shib_cmds[] = {
1101   AP_INIT_TAKE1("ShibConfig",
1102                 (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1103                 RSRC_CONF, "Path to shibboleth.xml config file."),
1104   AP_INIT_TAKE1("ShibSchemaDir",
1105      (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
1106       RSRC_CONF, "Path to Shibboleth XML schema directory."),
1107
1108   AP_INIT_FLAG("ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
1109                (void *) offsetof (shib_dir_config, bBasicHijack),
1110                OR_AUTHCFG, "Respond to AuthType Basic and convert to shib?"),
1111   AP_INIT_FLAG("ShibRequireSession", (config_fn_t)ap_set_flag_slot,
1112          (void *) offsetof (shib_dir_config, bRequireSession),
1113         OR_AUTHCFG, "Initiates a new session if one does not exist."),
1114   AP_INIT_FLAG("ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
1115          (void *) offsetof (shib_dir_config, bExportAssertion),
1116         OR_AUTHCFG, "Export SAML assertion to Shibboleth-defined header?"),
1117   AP_INIT_TAKE1("AuthGroupFile", (config_fn_t)ap_set_file_slot,
1118                 (void *) offsetof (shib_dir_config, szAuthGrpFile),
1119                 OR_AUTHCFG, "text file containing group names and member user IDs"),
1120   AP_INIT_FLAG("ShibRequireAll", (config_fn_t)ap_set_flag_slot,
1121                (void *) offsetof (shib_dir_config, bRequireAll),
1122                OR_AUTHCFG, "All require directives must match!"),
1123
1124   {NULL}
1125 };
1126
1127 module AP_MODULE_DECLARE_DATA mod_shib = {
1128     STANDARD20_MODULE_STUFF,
1129     create_shib_dir_config,     /* create dir config */
1130     merge_shib_dir_config,      /* merge dir config --- default is to override */
1131     NULL,                       /* create server config */
1132     NULL,                       /* merge server config */
1133     shib_cmds,                  /* command table */
1134     shib_register_hooks         /* register hooks */
1135 };
1136
1137 #else
1138 #error "undefined APACHE version"
1139 #endif
1140
1141 }