ddf57919204659c1f6ef431ad30db81d759c91a6
[shibboleth/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 #ifndef SHIB_APACHE_13
463     // With 2.x, this handler always runs, though last.
464     // We check if shib_check_user ran, because it will detect a SHIRE request
465     // and dispatch it directly.
466     void* data;
467     apr_pool_userdata_get(&data,g_UserDataKey,r->pool);
468     if (data==(const void*)42) {
469         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_post_handler skipped since check_user ran");
470         return DECLINED;
471     }
472 #endif
473     
474     ostringstream threadid;
475     threadid << "[" << getpid() << "] shib_post_handler" << '\0';
476     saml::NDC ndc(threadid.str().c_str());
477
478     // We lock the configuration system for the duration.
479     IConfig* conf=g_Config->getINI();
480     Locker locker(conf);
481     
482     // Map request to application and content settings.
483     IRequestMapper* mapper=conf->getRequestMapper();
484     Locker locker2(mapper);
485     IRequestMapper::Settings settings=mapper->getSettingsFromParsedURL(
486         ap_http_method(r), ap_get_server_name(r), ap_get_server_port(r), r->unparsed_uri
487         );
488     pair<bool,const char*> application_id=settings.first->getString("applicationId");
489     const IApplication* application=conf->getApplication(application_id.second);
490     if (!application) {
491         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
492            "shib_post_handler: unable to map request to application settings, check configuration");
493         return SERVER_ERROR;
494     }
495     
496     // Declare SHIRE object for this request.
497     SHIRE shire(application);
498     
499     return shib_handler(r, application, shire);
500 }
501
502 int shib_handler(request_rec* r, const IApplication* application, SHIRE& shire)
503 {
504     // Prime the pump...
505     const char* targeturl = ap_construct_url(r->pool,r->unparsed_uri,r);
506
507     // Make sure we only process the SHIRE requests.
508     if (!strstr(targeturl,shire.getShireURL(targeturl)))
509         return DECLINED;
510
511     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_handler() running");
512
513     const IPropertySet* sessionProps=application->getPropertySet("Sessions");
514     if (!sessionProps) {
515         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
516            "shib_post_handler: unable to map request to application session settings, check configuration");
517         return SERVER_ERROR;
518     }
519
520     pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();   // always returns something
521
522     ShibMLP markupProcessor;
523     markupProcessor.insert("requestURL", targeturl);
524
525     // Process SHIRE request
526     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_handler() Beginning SHIRE processing");
527       
528     try {
529         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
530       
531         // Make sure this is SSL, if it should be
532         if ((!shireSSL.first || shireSSL.second) && strcmp(ap_http_method(r),"https"))
533             throw ShibTargetException(SHIBRPC_OK, "blocked non-SSL access to session creation service");
534
535         // If this is a GET, we manufacture an AuthnRequest.
536         if (!strcasecmp(r->method,"GET")) {
537             const char* areq=r->args ? shire.getLazyAuthnRequest(r->args) : NULL;
538             if (!areq)
539                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
540             ap_table_setn(r->headers_out, "Location", ap_pstrdup(r->pool,areq));
541             return REDIRECT;
542         }
543         else if (strcasecmp(r->method,"POST")) {
544             throw ShibTargetException(SHIBRPC_OK, "blocked non-POST to SHIRE POST processor");
545         }
546
547         // Sure sure this POST is an appropriate content type
548         const char *ct = ap_table_get(r->headers_in, "Content-type");
549         if (!ct || strcasecmp(ct, "application/x-www-form-urlencoded"))
550             throw ShibTargetException(SHIBRPC_OK,
551                                       ap_psprintf(r->pool, "blocked bad content-type to SHIRE POST processor: %s", (ct ? ct : "")));
552
553         // Read the posted data
554         if (ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))
555             throw ShibTargetException(SHIBRPC_OK, "CGI setup_client_block failed");
556         if (!ap_should_client_block(r))
557             throw ShibTargetException(SHIBRPC_OK, "CGI should_client_block failed");
558         if (r->remaining > 1024*1024)
559             throw ShibTargetException (SHIBRPC_OK, "CGI length too long...");
560
561         string cgistr;
562         char buff[HUGE_STRING_LEN];
563         ap_hard_timeout("[mod_shib] CGI Parser", r);
564         memset(buff, 0, sizeof(buff));
565         while (ap_get_client_block(r, buff, sizeof(buff)-1) > 0) {
566             cgistr += buff;
567             memset(buff, 0, sizeof(buff));
568         }
569         ap_kill_timeout(r);
570
571         // Parse the submission.
572         pair<const char*,const char*> elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
573     
574         // Make sure the SAML Response parameter exists
575         if (!elements.first || !*elements.first)
576             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
577     
578         // Make sure the target parameter exists
579         if (!elements.second || !*elements.second)
580             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
581     
582         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
583             "shib_handler() Processing POST for target: %s", elements.second);
584
585         // process the post
586         string cookie;
587         RPCError* status = shire.sessionCreate(elements.first, r->connection->remote_ip, cookie);
588
589         if (status->isError()) {
590             ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
591                     "shib_handler() POST process failed (%d): %s", status->getCode(), status->getText());
592
593             if (status->isRetryable()) {
594                 delete status;
595                 ap_log_rerror(APLOG_MARK,APLOG_INFO|APLOG_NOERRNO,SH_AP_R(r),
596                         "shib_handler() retryable error, generating new AuthnRequest");
597                 ap_table_setn(r->headers_out,"Location",ap_pstrdup(r->pool,shire.getAuthnRequest(elements.second)));
598                 return REDIRECT;
599             }
600
601             // return this error to the user.
602             markupProcessor.insert(*status);
603             delete status;
604             return shib_error_page(r, application, "shire", markupProcessor);
605         }
606         delete status;
607
608         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
609                   "shib_handler() POST process succeeded.  New session: %s", cookie.c_str());
610
611         // We've got a good session, set the cookie...
612         char* val = ap_psprintf(r->pool,"%s=%s%s",shib_cookie.first,cookie.c_str(),shib_cookie.second);
613         ap_table_setn(r->err_headers_out, "Set-Cookie", val);
614         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_handler() setting cookie: %s", val);
615
616         // ... and redirect to the target
617         ap_table_setn(r->headers_out, "Location", ap_pstrdup(r->pool,elements.second));
618         return REDIRECT;
619     }
620     catch (ShibTargetException &e) {
621         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_handler() caught exception: %s", e.what());
622         markupProcessor.insert("errorType", "Session Creation Service Error");
623         markupProcessor.insert("errorText", e.what());
624         markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
625         return shib_error_page(r, application, "shire", markupProcessor);
626     }
627 #ifndef _DEBUG
628     catch (...) {
629         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_handler(): unexpected exception");
630         markupProcessor.insert("errorType", "Session Creation Service Error");
631         markupProcessor.insert("errorText", "Unknown Exception");
632         markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
633         return shib_error_page(r, application, "shire", markupProcessor);
634     }
635 #endif
636
637     ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),"shib_handler() server error");
638     return SERVER_ERROR;
639 }
640
641 static SH_AP_TABLE* groups_for_user(request_rec* r, const char* user, char* grpfile)
642 {
643     SH_AP_CONFIGFILE* f;
644     SH_AP_TABLE* grps=ap_make_table(r->pool,15);
645     char l[MAX_STRING_LEN];
646     const char *group_name, *ll, *w;
647
648 #ifdef SHIB_APACHE_13
649     if (!(f=ap_pcfg_openfile(r->pool,grpfile))) {
650 #else
651     if (ap_pcfg_openfile(&f,r->pool,grpfile) != APR_SUCCESS) {
652 #endif
653         ap_log_rerror(APLOG_MARK,APLOG_DEBUG,SH_AP_R(r),"groups_for_user() could not open group file: %s\n",grpfile);
654         return NULL;
655     }
656
657     SH_AP_POOL* sp;
658 #ifdef SHIB_APACHE_13
659     sp=ap_make_sub_pool(r->pool);
660 #else
661     if (apr_pool_create(&sp,r->pool) != APR_SUCCESS) {
662         ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
663             "groups_for_user() could not create a subpool");
664         return NULL;
665     }
666 #endif
667
668     while (!(ap_cfg_getline(l,MAX_STRING_LEN,f))) {
669         if ((*l=='#') || (!*l))
670             continue;
671         ll = l;
672         ap_clear_pool(sp);
673
674         group_name=ap_getword(sp,&ll,':');
675
676         while (*ll) {
677             w=ap_getword_conf(sp,&ll);
678             if (!strcmp(w,user)) {
679                 ap_table_setn(grps,ap_pstrdup(r->pool,group_name),"in");
680                 break;
681             }
682         }
683     }
684     ap_cfg_closefile(f);
685     ap_destroy_pool(sp);
686     return grps;
687 }
688
689 /*
690  * shib_auth_checker() -- a simple resource manager to
691  * process the .htaccess settings and copy attributes
692  * into the HTTP headers.
693  */
694 extern "C" int shib_auth_checker(request_rec* r)
695 {
696     shib_dir_config* dc=
697         (shib_dir_config*)ap_get_module_config(r->per_dir_config,&mod_shib);
698
699     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() executing");
700
701     // Regular access to arbitrary resource...check AuthType
702     const char* auth_type=ap_auth_type(r);
703     if (!auth_type || strcasecmp(auth_type,"shibboleth"))
704         return DECLINED;
705
706     ostringstream threadid;
707     threadid << "[" << getpid() << "] shibrm" << '\0';
708     saml::NDC ndc(threadid.str().c_str());
709
710     // We lock the configuration system for the duration.
711     IConfig* conf=g_Config->getINI();
712     Locker locker(conf);
713     
714     const char* application_id=ap_table_get(r->headers_in,"Shib-Application-ID");
715     const IApplication* application=NULL;
716     if (application_id)
717         application = conf->getApplication(application_id);
718
719     // mod_auth clone
720
721     int m=r->method_number;
722     bool method_restricted=false;
723     const char *t, *w;
724     
725     const array_header* reqs_arr=ap_requires(r);
726     if (!reqs_arr)
727         return OK;
728
729     require_line* reqs=(require_line*)reqs_arr->elts;
730
731     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"REQUIRE nelts: %d", reqs_arr->nelts);
732     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"REQUIRE all: %d", dc->bRequireAll);
733
734     vector<bool> auth_OK(reqs_arr->nelts,false);
735
736 #define SHIB_AP_CHECK_IS_OK {       \
737      if (dc->bRequireAll < 1)    \
738          return OK;      \
739      auth_OK[x] = true;      \
740      continue;           \
741 }
742
743     for (int x=0; x<reqs_arr->nelts; x++) {
744         auth_OK[x] = false;
745         if (!(reqs[x].method_mask & (1 << m)))
746             continue;
747         method_restricted=true;
748
749         t = reqs[x].requirement;
750         w = ap_getword_white(r->pool, &t);
751
752         if (!strcasecmp(w,"Shibboleth")) {
753             // This is a dummy rule needed because Apache conflates authn and authz.
754             // Without some require rule, AuthType is ignored and no check_user hooks run.
755             SHIB_AP_CHECK_IS_OK;
756         }
757         else if (!strcmp(w,"valid-user") && application_id) {
758             ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() accepting valid-user");
759             SHIB_AP_CHECK_IS_OK;
760         }
761         else if (!strcmp(w,"user") && SH_AP_USER(r)) {
762             bool regexp=false;
763             while (*t) {
764                 w=ap_getword_conf(r->pool,&t);
765                 if (*w=='~') {
766                     regexp=true;
767                     continue;
768                 }
769                 
770                 if (regexp) {
771                     try {
772                         // To do regex matching, we have to convert from UTF-8.
773                         auto_ptr<XMLCh> trans(fromUTF8(w));
774                         RegularExpression re(trans.get());
775                         auto_ptr<XMLCh> trans2(fromUTF8(SH_AP_USER(r)));
776                         if (re.matches(trans2.get())) {
777                             ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() accepting user: %s",w);
778                             SHIB_AP_CHECK_IS_OK;
779                         }
780                     }
781                     catch (XMLException& ex) {
782                         auto_ptr_char tmp(ex.getMessage());
783                         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
784                                         "shib_auth_checker caught exception while parsing regular expression (%s): %s",w,tmp.get());
785                     }
786                 }
787                 else if (!strcmp(SH_AP_USER(r),w)) {
788                     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() accepting user: %s",w);
789                     SHIB_AP_CHECK_IS_OK;
790                 }
791             }
792         }
793         else if (!strcmp(w,"group")) {
794             SH_AP_TABLE* grpstatus=NULL;
795             if (dc->szAuthGrpFile && SH_AP_USER(r)) {
796                 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() using groups file: %s\n",dc->szAuthGrpFile);
797                 grpstatus=groups_for_user(r,SH_AP_USER(r),dc->szAuthGrpFile);
798             }
799             if (!grpstatus)
800                 return DECLINED;
801     
802             while (*t) {
803                 w=ap_getword_conf(r->pool,&t);
804                 if (ap_table_get(grpstatus,w)) {
805                     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_auth_checker() accepting group: %s",w);
806                     SHIB_AP_CHECK_IS_OK;
807                 }
808             }
809         }
810         else {
811             Iterator<IAAP*> provs=application ? application->getAAPProviders() : EMPTY(IAAP*);
812             AAP wrapper(provs,w);
813             if (wrapper.fail()) {
814                 ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,SH_AP_R(r),
815                     "shib_auth_checker() didn't recognize require rule: %s\n",w);
816                 continue;
817             }
818
819             bool regexp=false;
820             const char* vals=ap_table_get(r->headers_in,wrapper->getHeader());
821             while (*t && vals) {
822                 w=ap_getword_conf(r->pool,&t);
823                 if (*w=='~') {
824                     regexp=true;
825                     continue;
826                 }
827
828                 try {
829                     auto_ptr<RegularExpression> re;
830                     if (regexp) {
831                         delete re.release();
832                         auto_ptr<XMLCh> trans(fromUTF8(w));
833                         auto_ptr<RegularExpression> temp(new RegularExpression(trans.get()));
834                         re=temp;
835                     }
836                     
837                     string vals_str(vals);
838                     int j = 0;
839                     for (int i = 0;  i < vals_str.length();  i++) {
840                         if (vals_str.at(i) == ';') {
841                             if (i == 0) {
842                                 ap_log_rerror(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,SH_AP_R(r),
843                                                 "shib_auth_checker() invalid header encoding %s: starts with semicolon", vals);
844                                 return SERVER_ERROR;
845                             }
846
847                             if (vals_str.at(i-1) == '\\') {
848                                 vals_str.erase(i-1, 1);
849                                 i--;
850                                 continue;
851                             }
852
853                             string val = vals_str.substr(j, i-j);
854                             j = i+1;
855                             if (regexp) {
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,SH_AP_R(r),
859                                                     "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
860                                     SHIB_AP_CHECK_IS_OK;
861                                 }
862                             }
863                             else if (val==w) {
864                                 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
865                                                 "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
866                                 SHIB_AP_CHECK_IS_OK;
867                             }
868                             else {
869                                 ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
870                                                 "shib_auth_checker() expecting %s, got %s: authorization not granted", w, val.c_str());
871                             }
872                         }
873                     }
874     
875                     string val = vals_str.substr(j, vals_str.length()-j);
876                     if (regexp) {
877                         auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
878                         if (re->matches(trans.get())) {
879                             ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
880                                             "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
881                             SHIB_AP_CHECK_IS_OK;
882                         }
883                     }
884                     else if (val==w) {
885                         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
886                                         "shib_auth_checker() expecting %s, got %s: authorization granted", w, val.c_str());
887                         SHIB_AP_CHECK_IS_OK;
888                     }
889                     else {
890                         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),
891                                         "shib_auth_checker() expecting %s, got %s: authorization not granted", w, val.c_str());
892                     }
893                 }
894                 catch (XMLException& ex) {
895                     auto_ptr_char tmp(ex.getMessage());
896                     ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
897                                     "shib_auth_checker caught exception while parsing regular expression (%s): %s",w,tmp.get());
898                 }
899             }
900         }
901     }
902
903     // check if all require directives are true
904     bool auth_all_OK = true;
905     for (int i= 0; i<reqs_arr->nelts; i++) {
906         auth_all_OK &= auth_OK[i];
907     }
908     if (auth_all_OK)
909         return OK;
910
911     if (!method_restricted)
912         return OK;
913
914     if (!application_id) {
915         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
916            "shib_auth_checker: Shib-Application-ID header not found in request");
917         return HTTP_FORBIDDEN;
918     }
919     else if (!application) {
920         ap_log_rerror(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(r),
921            "shib_auth_checker: unable to map request to application settings, check configuration");
922         return HTTP_FORBIDDEN;
923     }
924
925     ShibMLP markupProcessor;
926     markupProcessor.insert("requestURL", ap_construct_url(r->pool,r->unparsed_uri,r));
927     return shib_error_page(r, application, "access", markupProcessor);
928 }
929
930 /*
931  * shib_exit()
932  *  Cleanup the (per-process) pool info.
933  */
934 #ifdef SHIB_APACHE_13
935 extern "C" void shib_exit(server_rec* s, SH_AP_POOL* p)
936 {
937 #else
938 extern "C" apr_status_t shib_exit(void* data)
939 {
940     server_rec* s = NULL;
941 #endif
942     g_Config->shutdown();
943     g_Config = NULL;
944     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_exit() done");
945 #ifndef SHIB_APACHE_13
946     return OK;
947 #endif
948 }
949
950 /* 
951  * shire_child_init()
952  *  Things to do when the child process is initialized.
953  *  (or after the configs are read in apache-2)
954  */
955 #ifdef SHIB_APACHE_13
956 extern "C" void shib_child_init(server_rec* s, SH_AP_POOL* p)
957 #else
958 extern "C" int shib_post_config(apr_pool_t* pconf, apr_pool_t* plog,
959                                 apr_pool_t* ptemp, server_rec* s)
960 #endif
961 {
962     // Initialize runtime components.
963
964     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() starting");
965
966     if (g_Config) {
967         ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() already initialized!");
968 #ifdef SHIB_APACHE_13
969         exit(1);
970 #else
971         return OK;
972 #endif
973     }
974
975     try {
976         g_Config=&ShibTargetConfig::getConfig();
977         g_Config->setFeatures(
978             ShibTargetConfig::Listener |
979             ShibTargetConfig::Metadata |
980             ShibTargetConfig::AAP |
981             ShibTargetConfig::RequestMapper |
982             ShibTargetConfig::SHIREExtensions
983             );
984         if (!g_Config->init(g_szSchemaDir,g_szSHIBConfig)) {
985             ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to initialize SHIB Target");
986             exit(1);
987         }
988     }
989     catch (...) {
990         ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to initialize SHIB Target");
991         exit (1);
992     }
993
994     // Set the cleanup handler
995     apr_pool_cleanup_register(pconf, NULL, shib_exit, NULL);
996
997     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() done");
998
999 #ifndef SHIB_APACHE_13
1000     return OK;
1001 #endif
1002 }
1003
1004 #ifdef SHIB_APACHE_13
1005
1006 // SHIB Module commands
1007
1008 static command_rec shire_cmds[] = {
1009   {"SHIREConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1010    RSRC_CONF, TAKE1, "Path to shibboleth.xml config file."},
1011   {"ShibConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1012    RSRC_CONF, TAKE1, "Path to shibboleth.xml config file."},
1013   {"ShibSchemaDir", (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
1014    RSRC_CONF, TAKE1, "Path to Shibboleth XML schema directory."},
1015
1016   {"ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
1017    (void *) XtOffsetOf (shib_dir_config, bBasicHijack),
1018    OR_AUTHCFG, FLAG, "Respond to AuthType Basic and convert to shib?"},
1019   {"ShibRequireSession", (config_fn_t)ap_set_flag_slot,
1020    (void *) XtOffsetOf (shib_dir_config, bRequireSession),
1021    OR_AUTHCFG, FLAG, "Initiates a new session if one does not exist."},
1022   {"ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
1023    (void *) XtOffsetOf (shib_dir_config, bExportAssertion),
1024    OR_AUTHCFG, FLAG, "Export SAML assertion to Shibboleth-defined header?"},
1025   {"AuthGroupFile", (config_fn_t)ap_set_file_slot,
1026    (void *) XtOffsetOf (shib_dir_config, szAuthGrpFile),
1027    OR_AUTHCFG, TAKE1, "text file containing group names and member user IDs"},
1028   {"ShibRequireAll", (config_fn_t)ap_set_flag_slot,
1029    (void *) XtOffsetOf (shib_dir_config, bRequireAll),
1030    OR_AUTHCFG, FLAG, "All require directives must match!"},
1031
1032   {NULL}
1033 };
1034
1035 extern "C"{
1036 handler_rec shib_handlers[] = {
1037   { "shib-shire-post", shib_post_handler },
1038   { NULL }
1039 };
1040
1041 module MODULE_VAR_EXPORT mod_shib = {
1042     STANDARD_MODULE_STUFF,
1043     NULL,                        /* initializer */
1044     create_shib_dir_config,     /* dir config creater */
1045     merge_shib_dir_config,      /* dir merger --- default is to override */
1046     NULL,                       /* server config */
1047     NULL,                       /* merge server config */
1048     shire_cmds,                 /* command table */
1049     shib_handlers,              /* handlers */
1050     NULL,                       /* filename translation */
1051     shib_check_user,            /* check_user_id */
1052     shib_auth_checker,          /* check auth */
1053     NULL,                       /* check access */
1054     NULL,                       /* type_checker */
1055     NULL,                       /* fixups */
1056     NULL,                       /* logger */
1057     NULL,                       /* header parser */
1058     shib_child_init,            /* child_init */
1059     shib_exit,                  /* child_exit */
1060     NULL                        /* post read-request */
1061 };
1062
1063 #elif defined(SHIB_APACHE_20)
1064
1065 extern "C" void shib_register_hooks (apr_pool_t *p)
1066 {
1067   ap_hook_post_config(shib_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1068   ap_hook_check_user_id(shib_check_user, NULL, NULL, APR_HOOK_MIDDLE);
1069   ap_hook_auth_checker(shib_auth_checker, NULL, NULL, APR_HOOK_FIRST);
1070   ap_hook_handler(shib_post_handler, NULL, NULL, APR_HOOK_LAST);
1071 }
1072
1073 // SHIB Module commands
1074
1075 extern "C" {
1076 static command_rec shib_cmds[] = {
1077   AP_INIT_TAKE1("ShibConfig",
1078                 (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1079                 RSRC_CONF, "Path to shibboleth.xml config file."),
1080   AP_INIT_TAKE1("ShibSchemaDir",
1081      (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
1082       RSRC_CONF, "Path to Shibboleth XML schema directory."),
1083
1084   AP_INIT_FLAG("ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
1085                (void *) offsetof (shib_dir_config, bBasicHijack),
1086                OR_AUTHCFG, "Respond to AuthType Basic and convert to shib?"),
1087   AP_INIT_FLAG("ShibRequireSession", (config_fn_t)ap_set_flag_slot,
1088          (void *) offsetof (shib_dir_config, bRequireSession),
1089         OR_AUTHCFG, "Initiates a new session if one does not exist."),
1090   AP_INIT_FLAG("ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
1091          (void *) offsetof (shib_dir_config, bExportAssertion),
1092         OR_AUTHCFG, "Export SAML assertion to Shibboleth-defined header?"),
1093   AP_INIT_TAKE1("AuthGroupFile", (config_fn_t)ap_set_file_slot,
1094                 (void *) offsetof (shib_dir_config, szAuthGrpFile),
1095                 OR_AUTHCFG, "text file containing group names and member user IDs"),
1096   AP_INIT_FLAG("ShibRequireAll", (config_fn_t)ap_set_flag_slot,
1097                (void *) offsetof (shib_dir_config, bRequireAll),
1098                OR_AUTHCFG, "All require directives must match!"),
1099
1100   {NULL}
1101 };
1102
1103 module AP_MODULE_DECLARE_DATA mod_shib = {
1104     STANDARD20_MODULE_STUFF,
1105     create_shib_dir_config,     /* create dir config */
1106     merge_shib_dir_config,      /* merge dir config --- default is to override */
1107     NULL,                       /* create server config */
1108     NULL,                       /* merge server config */
1109     shib_cmds,                  /* command table */
1110     shib_register_hooks         /* register hooks */
1111 };
1112
1113 #else
1114 #error "undefined APACHE version"
1115 #endif
1116
1117 }