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