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