Convert to new server name API
[shibboleth/cpp-sp.git] / apache / mod_apache.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * mod_apache.cpp
23  *
24  * Apache module implementation.
25  */
26
27 #define SHIBSP_LITE
28
29 #ifdef SOLARIS2
30 #undef _XOPEN_SOURCE    // causes gethostname conflict in unistd.h
31 #endif
32
33 #ifdef WIN32
34 # define _CRT_NONSTDC_NO_DEPRECATE 1
35 # define _CRT_SECURE_NO_DEPRECATE 1
36 #endif
37
38 #include <shibsp/exceptions.h>
39 #include <shibsp/AbstractSPRequest.h>
40 #include <shibsp/AccessControl.h>
41 #include <shibsp/GSSRequest.h>
42 #include <shibsp/RequestMapper.h>
43 #include <shibsp/SPConfig.h>
44 #include <shibsp/ServiceProvider.h>
45 #include <shibsp/SessionCache.h>
46 #include <shibsp/attribute/Attribute.h>
47
48 #include <xercesc/util/XMLUniDefs.hpp>
49 #include <xercesc/util/regx/RegularExpression.hpp>
50 #include <xmltooling/XMLToolingConfig.h>
51 #include <xmltooling/util/NDC.h>
52 #include <xmltooling/util/ParserPool.h>
53 #include <xmltooling/util/Threads.h>
54 #include <xmltooling/util/XMLConstants.h>
55 #include <xmltooling/util/XMLHelper.h>
56
57 #ifdef WIN32
58 # include <winsock2.h>
59 # include <ws2tcpip.h>
60 #endif
61
62 #undef _XPG4_2
63
64 #include <set>
65 #include <memory>
66 #include <fstream>
67 #include <stdexcept>
68 #include <boost/lexical_cast.hpp>
69
70 // Apache specific header files
71 #include <httpd.h>
72 #include <http_config.h>
73 #include <http_protocol.h>
74 #include <http_main.h>
75 #define CORE_PRIVATE
76 #include <http_core.h>
77 #include <http_log.h>
78 #include <http_request.h>
79
80 #ifndef SHIB_APACHE_13
81 #include <apr_buckets.h>
82 #include <apr_strings.h>
83 #include <apr_pools.h>
84 #endif
85
86 #ifdef SHIB_APACHE_24
87 #include <mod_auth.h>
88 #endif
89
90 #include <cstddef>
91 #ifdef HAVE_UNISTD_H
92 #include <unistd.h>             // for getpid()
93 #endif
94
95 using namespace shibsp;
96 using namespace xmltooling;
97 using namespace boost;
98 using namespace std;
99 using xercesc::RegularExpression;
100 using xercesc::XMLException;
101
102 #ifdef APLOG_USE_MODULE
103     extern "C" module AP_MODULE_DECLARE_DATA mod_shib;
104     static int* const aplog_module_index = &(mod_shib.module_index);
105 #else
106     extern "C" module MODULE_VAR_EXPORT mod_shib;
107 #endif
108
109 namespace {
110     char* g_szSHIBConfig = nullptr;
111     char* g_szSchemaDir = nullptr;
112     char* g_szPrefix = nullptr;
113     SPConfig* g_Config = nullptr;
114     string g_unsetHeaderValue,g_spoofKey;
115     bool g_checkSpoofing = true;
116     bool g_catchAll = false;
117 #ifndef SHIB_APACHE_13
118     char* g_szGSSContextKey = "mod_auth_gssapi:gss_ctx";
119 #endif
120     static const char* g_UserDataKey = "urn:mace:shibboleth:Apache:shib_check_user";
121 }
122
123 /* Apache 2.2.x headers must be accumulated and set in the output filter.
124    Apache 2.0.49+ supports the filter method.
125    Apache 1.3.x and lesser 2.0.x must write the headers directly. */
126
127 #if (defined(SHIB_APACHE_20) || defined(SHIB_APACHE_22) || defined(SHIB_APACHE_24)) && AP_MODULE_MAGIC_AT_LEAST(20020903,6)
128 #define SHIB_DEFERRED_HEADERS
129 #endif
130
131 /********************************************************************************/
132 // Basic Apache Configuration code.
133 //
134
135 // per-server module configuration structure
136 struct shib_server_config
137 {
138     char* szScheme;
139 };
140
141 // creates the per-server configuration
142 extern "C" void* create_shib_server_config(SH_AP_POOL* p, server_rec* s)
143 {
144     shib_server_config* sc=(shib_server_config*)ap_pcalloc(p,sizeof(shib_server_config));
145     sc->szScheme = nullptr;
146     return sc;
147 }
148
149 // overrides server configuration in virtual servers
150 extern "C" void* merge_shib_server_config (SH_AP_POOL* p, void* base, void* sub)
151 {
152     shib_server_config* sc=(shib_server_config*)ap_pcalloc(p,sizeof(shib_server_config));
153     shib_server_config* parent=(shib_server_config*)base;
154     shib_server_config* child=(shib_server_config*)sub;
155
156     if (child->szScheme)
157         sc->szScheme=ap_pstrdup(p,child->szScheme);
158     else if (parent->szScheme)
159         sc->szScheme=ap_pstrdup(p,parent->szScheme);
160     else
161         sc->szScheme=nullptr;
162
163     return sc;
164 }
165
166 // per-dir module configuration structure
167 struct shib_dir_config
168 {
169     SH_AP_TABLE* tSettings; // generic table of extensible settings
170
171     // RM Configuration
172 #ifdef SHIB_APACHE_24
173     int bRequestMapperAuthz;// support RequestMapper AccessControl plugins
174 #else
175     char* szAuthGrpFile;    // Auth GroupFile name
176         char* szAccessControl;  // path to "external" AccessControl plugin file
177     int bRequireAll;        // all "known" require directives must match, otherwise OR logic
178     int bAuthoritative;     // allow htaccess plugin to DECLINE when authz fails
179     int bCompatWith24;      // support 2.4-reserved require logic for compatibility
180 #endif
181
182     // Content Configuration
183     char* szApplicationId;  // Shib applicationId value
184     char* szRequireWith;    // require a session using a specific initiator?
185     char* szRedirectToSSL;  // redirect non-SSL requests to SSL port
186     int bOff;               // flat-out disable all Shib processing
187     int bBasicHijack;       // activate for AuthType Basic?
188     int bRequireSession;    // require a session?
189     int bExportAssertion;   // export SAML assertion to the environment?
190     int bUseEnvVars;        // use environment?
191     int bUseHeaders;        // use headers?
192     int bExpireRedirects;   // expire redirects?
193 };
194
195 // creates per-directory config structure
196 extern "C" void* create_shib_dir_config (SH_AP_POOL* p, char* d)
197 {
198     shib_dir_config* dc=(shib_dir_config*)ap_pcalloc(p,sizeof(shib_dir_config));
199     dc->tSettings = nullptr;
200 #ifdef SHIB_APACHE_24
201     dc->bRequestMapperAuthz = -1;
202 #else
203     dc->szAuthGrpFile = nullptr;
204         dc->szAccessControl = nullptr;
205     dc->bRequireAll = -1;
206     dc->bAuthoritative = -1;
207     dc->bCompatWith24 = -1;
208 #endif
209     dc->szApplicationId = nullptr;
210     dc->szRequireWith = nullptr;
211     dc->szRedirectToSSL = nullptr;
212     dc->bOff = -1;
213     dc->bBasicHijack = -1;
214     dc->bRequireSession = -1;
215     dc->bExportAssertion = -1;
216     dc->bUseEnvVars = -1;
217     dc->bUseHeaders = -1;
218     dc->bExpireRedirects = -1;
219     return dc;
220 }
221
222 // overrides server configuration in directories
223 extern "C" void* merge_shib_dir_config (SH_AP_POOL* p, void* base, void* sub)
224 {
225     shib_dir_config* dc=(shib_dir_config*)ap_pcalloc(p,sizeof(shib_dir_config));
226     shib_dir_config* parent=(shib_dir_config*)base;
227     shib_dir_config* child=(shib_dir_config*)sub;
228
229     // The child supersedes any matching table settings in the parent.
230     dc->tSettings = nullptr;
231     if (parent->tSettings)
232         dc->tSettings = ap_copy_table(p, parent->tSettings);
233     if (child->tSettings) {
234         if (dc->tSettings)
235             ap_overlap_tables(dc->tSettings, child->tSettings, AP_OVERLAP_TABLES_SET);
236         else
237             dc->tSettings = ap_copy_table(p, child->tSettings);
238     }
239
240 #ifdef SHIB_APACHE_24
241     dc->bRequestMapperAuthz = ((child->bRequestMapperAuthz==-1) ? parent->bRequestMapperAuthz : child->bRequestMapperAuthz);
242 #else
243     if (child->szAuthGrpFile)
244         dc->szAuthGrpFile=ap_pstrdup(p,child->szAuthGrpFile);
245     else if (parent->szAuthGrpFile)
246         dc->szAuthGrpFile=ap_pstrdup(p,parent->szAuthGrpFile);
247     else
248         dc->szAuthGrpFile=nullptr;
249
250         if (child->szAccessControl)
251         dc->szAccessControl=ap_pstrdup(p,child->szAccessControl);
252     else if (parent->szAccessControl)
253         dc->szAccessControl=ap_pstrdup(p,parent->szAccessControl);
254     else
255         dc->szAccessControl=nullptr;
256 #endif
257
258     if (child->szApplicationId)
259         dc->szApplicationId=ap_pstrdup(p,child->szApplicationId);
260     else if (parent->szApplicationId)
261         dc->szApplicationId=ap_pstrdup(p,parent->szApplicationId);
262     else
263         dc->szApplicationId=nullptr;
264
265     if (child->szRequireWith)
266         dc->szRequireWith=ap_pstrdup(p,child->szRequireWith);
267     else if (parent->szRequireWith)
268         dc->szRequireWith=ap_pstrdup(p,parent->szRequireWith);
269     else
270         dc->szRequireWith=nullptr;
271
272     if (child->szRedirectToSSL)
273         dc->szRedirectToSSL=ap_pstrdup(p,child->szRedirectToSSL);
274     else if (parent->szRedirectToSSL)
275         dc->szRedirectToSSL=ap_pstrdup(p,parent->szRedirectToSSL);
276     else
277         dc->szRedirectToSSL=nullptr;
278
279     dc->bOff = ((child->bOff==-1) ? parent->bOff : child->bOff);
280     dc->bBasicHijack = ((child->bBasicHijack==-1) ? parent->bBasicHijack : child->bBasicHijack);
281     dc->bRequireSession = ((child->bRequireSession==-1) ? parent->bRequireSession : child->bRequireSession);
282     dc->bExportAssertion = ((child->bExportAssertion==-1) ? parent->bExportAssertion : child->bExportAssertion);
283 #ifndef SHIB_APACHE_24
284     dc->bRequireAll = ((child->bRequireAll==-1) ? parent->bRequireAll : child->bRequireAll);
285     dc->bAuthoritative = ((child->bAuthoritative==-1) ? parent->bAuthoritative : child->bAuthoritative);
286     dc->bCompatWith24 = ((child->bCompatWith24==-1) ? parent->bCompatWith24 : child->bCompatWith24);
287 #endif
288     dc->bUseEnvVars = ((child->bUseEnvVars==-1) ? parent->bUseEnvVars : child->bUseEnvVars);
289     dc->bUseHeaders = ((child->bUseHeaders==-1) ? parent->bUseHeaders : child->bUseHeaders);
290     dc->bExpireRedirects = ((child->bExpireRedirects==-1) ? parent->bExpireRedirects : child->bExpireRedirects);
291     return dc;
292 }
293
294 class ShibTargetApache; // forward decl
295
296 // per-request module structure
297 struct shib_request_config
298 {
299     SH_AP_TABLE* env;        // environment vars
300 #ifdef SHIB_DEFERRED_HEADERS
301     SH_AP_TABLE* hdr_out;    // headers to browser
302 #endif
303 #ifndef SHIB_APACHE_13
304     ShibTargetApache* sta;  // SP per-request structure wrapped around Apache's request
305 #endif
306 };
307
308 // create a request record
309 static shib_request_config* init_request_config(request_rec *r)
310 {
311     shib_request_config* rc = (shib_request_config*)ap_pcalloc(r->pool,sizeof(shib_request_config));
312     memset(rc, 0, sizeof(shib_request_config));
313     ap_set_module_config(r->request_config, &mod_shib, rc);
314     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, SH_AP_R(r), "shib_init_rc");
315     return rc;
316 }
317
318 class ShibTargetApache : public AbstractSPRequest
319 #if defined(SHIBSP_HAVE_GSSAPI) && !defined(SHIB_APACHE_13)
320     , public GSSRequest
321 #endif
322 {
323   mutable string m_body;
324   mutable bool m_gotBody,m_firsttime;
325   mutable vector<string> m_certs;
326   set<string> m_allhttp;
327
328 public:
329   bool m_handler;
330   request_rec* m_req;
331   shib_dir_config* m_dc;
332   shib_server_config* m_sc;
333   shib_request_config* m_rc;
334
335   ShibTargetApache(request_rec* req) : AbstractSPRequest(SHIBSP_LOGCAT".Apache"),
336         m_gotBody(false),m_firsttime(true), m_handler(false), m_req(req), m_dc(nullptr), m_sc(nullptr), m_rc(nullptr) {
337   }
338   virtual ~ShibTargetApache() {}
339
340   bool isInitialized() const {
341       return (m_sc != nullptr);
342   }
343
344   bool init(bool handler, bool check_user) {
345     m_handler = handler;
346     if (m_sc)
347         return !check_user; // only initialize once
348     m_sc = (shib_server_config*)ap_get_module_config(m_req->server->module_config, &mod_shib);
349     m_dc = (shib_dir_config*)ap_get_module_config(m_req->per_dir_config, &mod_shib);
350     m_rc = (shib_request_config*)ap_get_module_config(m_req->request_config, &mod_shib);
351
352     setRequestURI(m_req->unparsed_uri);
353
354     if (check_user && m_dc->bUseHeaders == 1) {
355         // Try and see if this request was already processed, to skip spoof checking.
356         if (!ap_is_initial_req(m_req)) {
357             m_firsttime = false;
358         }
359         else if (!g_spoofKey.empty()) {
360             const char* hdr = ap_table_get(m_req->headers_in, "Shib-Spoof-Check");
361             if (hdr && g_spoofKey == hdr)
362                 m_firsttime = false;
363         }
364         if (!m_firsttime)
365             log(SPDebug, "shib_check_user running more than once");
366     }
367     return true;
368   }
369
370   const char* getScheme() const {
371     return m_sc->szScheme ? m_sc->szScheme : ap_http_method(m_req);
372   }
373   bool isSecure() const {
374       return HTTPRequest::isSecure();
375   }
376   const char* getHostname() const {
377 #ifdef SHIB_APACHE_24
378       return ap_get_server_name_for_url(m_req);
379 #else
380       return ap_get_server_name(m_req);
381 #endif
382   }
383   int getPort() const {
384     return ap_get_server_port(m_req);
385   }
386   const char* getMethod() const {
387     return m_req->method;
388   }
389   string getContentType() const {
390     const char* type = ap_table_get(m_req->headers_in, "Content-Type");
391     return type ? type : "";
392   }
393   long getContentLength() const {
394       return m_gotBody ? m_body.length() : m_req->remaining;
395   }
396   string getRemoteAddr() const {
397     string ret = AbstractSPRequest::getRemoteAddr();
398     if (!ret.empty())
399         return ret;
400 #ifdef SHIB_APACHE_24
401     return m_req->useragent_ip;
402 #else
403     return m_req->connection->remote_ip;
404 #endif
405   }
406   void log(SPLogLevel level, const string& msg) const {
407     AbstractSPRequest::log(level,msg);
408     ap_log_rerror(
409         APLOG_MARK,
410         (level == SPDebug ? APLOG_DEBUG :
411         (level == SPInfo ? APLOG_INFO :
412         (level == SPWarn ? APLOG_WARNING :
413         (level == SPError ? APLOG_ERR : APLOG_CRIT))))|APLOG_NOERRNO,
414         SH_AP_R(m_req),
415         "%s",
416         msg.c_str()
417         );
418   }
419   const char* getQueryString() const { return m_req->args; }
420   const char* getRequestBody() const {
421     if (m_gotBody || m_req->method_number==M_GET)
422         return m_body.c_str();
423 #ifdef SHIB_APACHE_13
424     // Read the posted data
425     if (ap_setup_client_block(m_req, REQUEST_CHUNKED_DECHUNK) != OK) {
426         m_gotBody=true;
427         log(SPError, "Apache function (setup_client_block) failed while reading request body.");
428         return m_body.c_str();
429     }
430     if (!ap_should_client_block(m_req)) {
431         m_gotBody=true;
432         log(SPError, "Apache function (should_client_block) failed while reading request body.");
433         return m_body.c_str();
434     }
435     if (m_req->remaining > 1024*1024)
436         throw opensaml::SecurityPolicyException("Blocked request body larger than 1M size limit.");
437     m_gotBody=true;
438     int len;
439     char buff[HUGE_STRING_LEN];
440     ap_hard_timeout("[mod_shib] getRequestBody", m_req);
441     while ((len=ap_get_client_block(m_req, buff, sizeof(buff))) > 0) {
442       ap_reset_timeout(m_req);
443       m_body.append(buff, len);
444     }
445     ap_kill_timeout(m_req);
446 #else
447     const char *data;
448     apr_size_t len;
449     int seen_eos = 0;
450     apr_bucket_brigade* bb = apr_brigade_create(m_req->pool, m_req->connection->bucket_alloc);
451     do {
452         apr_bucket *bucket;
453         apr_status_t rv = ap_get_brigade(m_req->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN);
454         if (rv != APR_SUCCESS) {
455             log(SPError, "Apache function (ap_get_brigade) failed while reading request body.");
456             break;
457         }
458
459         for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); bucket = APR_BUCKET_NEXT(bucket)) {
460             if (APR_BUCKET_IS_EOS(bucket)) {
461                 seen_eos = 1;
462                 break;
463             }
464
465             /* We can't do much with this. */
466             if (APR_BUCKET_IS_FLUSH(bucket))
467                 continue;
468
469             /* read */
470             apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
471             if (len > 0)
472                 m_body.append(data, len);
473         }
474         apr_brigade_cleanup(bb);
475     } while (!seen_eos);
476     apr_brigade_destroy(bb);
477     m_gotBody=true;
478 #endif
479     return m_body.c_str();
480   }
481   const char* getParameter(const char* name) const {
482       return AbstractSPRequest::getParameter(name);
483   }
484   vector<const char*>::size_type getParameters(const char* name, vector<const char*>& values) const {
485       return AbstractSPRequest::getParameters(name, values);
486   }
487   void clearHeader(const char* rawname, const char* cginame) {
488     if (m_dc->bUseHeaders == 1) {
489        // ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_clear_header: hdr\n");
490         if (g_checkSpoofing && m_firsttime) {
491             if (m_allhttp.empty()) {
492                 // First time, so populate set with "CGI" versions of client-supplied headers.
493 #ifdef SHIB_APACHE_13
494                 array_header *hdrs_arr = ap_table_elts(m_req->headers_in);
495                 table_entry *hdrs = (table_entry *) hdrs_arr->elts;
496 #else
497                 const apr_array_header_t *hdrs_arr = apr_table_elts(m_req->headers_in);
498                 const apr_table_entry_t *hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
499 #endif
500                 for (int i = 0; i < hdrs_arr->nelts; ++i) {
501                     if (!hdrs[i].key)
502                         continue;
503                     string cgiversion("HTTP_");
504                     const char* pch = hdrs[i].key;
505                     while (*pch) {
506                         cgiversion += (isalnum(*pch) ? toupper(*pch) : '_');
507                         pch++;
508                     }
509                     m_allhttp.insert(cgiversion);
510                 }
511             }
512
513             if (m_allhttp.count(cginame) > 0)
514                 throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
515         }
516         ap_table_unset(m_req->headers_in, rawname);
517         ap_table_set(m_req->headers_in, rawname, g_unsetHeaderValue.c_str());
518     }
519   }
520   void setHeader(const char* name, const char* value) {
521     if (m_dc->bUseEnvVars != 0) {
522        if (!m_rc) {
523           // this happens on subrequests
524           // ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_setheader: no_m_rc\n");
525           m_rc = init_request_config(m_req);
526        }
527        if (!m_rc->env)
528            m_rc->env = ap_make_table(m_req->pool, 10);
529        // ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_set_env: %s=%s\n", name, value?value:"Null");
530        ap_table_set(m_rc->env, name, value ? value : "");
531     }
532     if (m_dc->bUseHeaders == 1)
533        ap_table_set(m_req->headers_in, name, value);
534   }
535   string getHeader(const char* name) const {
536     const char* hdr = ap_table_get(m_req->headers_in, name);
537     return string(hdr ? hdr : "");
538   }
539   string getSecureHeader(const char* name) const {
540     if (m_dc->bUseEnvVars != 0) {
541        const char *hdr;
542        if (m_rc && m_rc->env)
543            hdr = ap_table_get(m_rc->env, name);
544        else
545            hdr = nullptr;
546        return string(hdr ? hdr : "");
547     }
548     return getHeader(name);
549   }
550   void setRemoteUser(const char* user) {
551       SH_AP_USER(m_req) = user ? ap_pstrdup(m_req->pool, user) : nullptr;
552       if (m_dc->bUseHeaders == 1) {
553           if (user) {
554               ap_table_set(m_req->headers_in, "REMOTE_USER", user);
555           }
556           else {
557               ap_table_unset(m_req->headers_in, "REMOTE_USER");
558               ap_table_set(m_req->headers_in, "REMOTE_USER", g_unsetHeaderValue.c_str());
559           }
560       }
561   }
562   string getRemoteUser() const {
563     return string(SH_AP_USER(m_req) ? SH_AP_USER(m_req) : "");
564   }
565   void setAuthType(const char* authtype) {
566       if (authtype && m_dc->bBasicHijack == 1)
567           authtype = "Basic";
568       SH_AP_AUTH_TYPE(m_req) = authtype ? ap_pstrdup(m_req->pool, authtype) : nullptr;
569   }
570   string getAuthType() const {
571     return string(SH_AP_AUTH_TYPE(m_req) ? SH_AP_AUTH_TYPE(m_req) : "");
572   }
573   void setContentType(const char* type) {
574       m_req->content_type = ap_psprintf(m_req->pool, "%s", type);
575   }
576   void setResponseHeader(const char* name, const char* value) {
577    HTTPResponse::setResponseHeader(name, value);
578 #ifdef SHIB_DEFERRED_HEADERS
579    if (!m_rc)
580       // this happens on subrequests
581       m_rc = init_request_config(m_req);
582     if (m_handler) {
583         if (!m_rc->hdr_out)
584             m_rc->hdr_out = ap_make_table(m_req->pool, 5);
585         ap_table_add(m_rc->hdr_out, name, value);
586     }
587     else
588 #endif
589     ap_table_add(m_req->err_headers_out, name, value);
590   }
591   long sendResponse(istream& in, long status) {
592     if (status != XMLTOOLING_HTTP_STATUS_OK)
593         m_req->status = status;
594     ap_send_http_header(m_req);
595     char buf[1024];
596     while (in) {
597         in.read(buf,1024);
598         ap_rwrite(buf,in.gcount(),m_req);
599     }
600 #if (defined(SHIB_APACHE_20) || defined(SHIB_APACHE_22) || defined(SHIB_APACHE_24))
601     if (status != XMLTOOLING_HTTP_STATUS_OK && status != XMLTOOLING_HTTP_STATUS_ERROR)
602         return status;
603 #endif
604     return DONE;
605   }
606   long sendRedirect(const char* url) {
607     HTTPResponse::sendRedirect(url);
608     ap_table_set(m_req->headers_out, "Location", url);
609     if (m_dc->bExpireRedirects != 0) {
610         ap_table_set(m_req->err_headers_out, "Expires", "Wed, 01 Jan 1997 12:00:00 GMT");
611         ap_table_set(m_req->err_headers_out, "Cache-Control", "private,no-store,no-cache,max-age=0");
612     }
613     return REDIRECT;
614   }
615   const vector<string>& getClientCertificates() const {
616       if (m_certs.empty()) {
617           const char* cert = ap_table_get(m_req->subprocess_env, "SSL_CLIENT_CERT");
618           if (cert)
619               m_certs.push_back(cert);
620           int i = 0;
621           do {
622               cert = ap_table_get(m_req->subprocess_env, ap_psprintf(m_req->pool, "SSL_CLIENT_CERT_CHAIN_%d", i++));
623               if (cert)
624                   m_certs.push_back(cert);
625           } while (cert);
626       }
627       return m_certs;
628   }
629   long returnDecline(void) { return DECLINED; }
630   long returnOK(void) { return OK; }
631 #if defined(SHIBSP_HAVE_GSSAPI) && !defined(SHIB_APACHE_13)
632   gss_ctx_id_t getGSSContext() const {
633     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
634     apr_pool_userdata_get((void**)&ctx, g_szGSSContextKey, m_req->pool);
635     return ctx;
636   }
637 #endif
638 };
639
640 /********************************************************************************/
641 // Apache hooks
642
643 #ifndef SHIB_APACHE_13
644 extern "C" apr_status_t shib_request_cleanup(void* r)
645 {
646     if (r)
647         delete reinterpret_cast<ShibTargetApache*>(r);
648     return APR_SUCCESS;
649 }
650 #endif
651
652 // Initial look at a request - create the per-request structure
653 static int shib_post_read(request_rec *r)
654 {
655     shib_request_config* rc = init_request_config(r);
656 #ifdef SHIB_APACHE_24
657     rc->sta = new ShibTargetApache(r);
658     apr_pool_cleanup_register(r->pool, rc->sta, shib_request_cleanup, apr_pool_cleanup_null);
659 #endif
660     return DECLINED;
661 }
662
663 // Performs authentication and enforce session requirements.
664 // Also does header/env export from session, and will dispatch
665 // SP handler requests if it detects a handler URL.
666 extern "C" int shib_check_user(request_rec* r)
667 {
668     // Short-circuit entirely?
669     if (((shib_dir_config*)ap_get_module_config(r->per_dir_config, &mod_shib))->bOff == 1)
670         return DECLINED;
671
672     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, SH_AP_R(r), "shib_check_user(%d): ENTER", (int)getpid());
673
674     string threadid("[");
675     threadid += lexical_cast<string>(getpid()) + "] shib_check_user";
676     xmltooling::NDC ndc(threadid.c_str());
677
678     try {
679 #ifndef SHIB_APACHE_24
680         ShibTargetApache sta(r);
681         ShibTargetApache* psta = &sta;
682 #else
683         shib_request_config* rc = (shib_request_config*)ap_get_module_config(r->request_config, &mod_shib);
684         if (!rc || !rc->sta) {
685             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_check_user found no per-request structure");
686             return SERVER_ERROR;
687         }
688         ShibTargetApache* psta = rc->sta;
689 #endif
690         if (!psta->init(false, true)) {
691             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_check_user unable to initialize SP request object");
692             return SERVER_ERROR;
693         }
694
695         // Check user authentication and export information, then set the handler bypass
696         pair<bool,long> res = psta->getServiceProvider().doAuthentication(*psta, true);
697         apr_pool_userdata_setn((const void*)42,g_UserDataKey,nullptr,r->pool);
698         // If directed, install a spoof key to recognize when we've already cleared headers.
699         if (!g_spoofKey.empty() && (((shib_dir_config*)ap_get_module_config(r->per_dir_config, &mod_shib))->bUseHeaders==1))
700             ap_table_set(r->headers_in, "Shib-Spoof-Check", g_spoofKey.c_str());
701         if (res.first) {
702 #ifdef SHIB_APACHE_24
703             // This is insane, but Apache's internal request.c logic insists that an auth module
704             // returning OK MUST set r->user to avoid a failure. But they check for NULL and not
705             // for an empty string. If this turns out to cause trouble, there's no solution except
706             // to set a dummy ID any time it's not set.
707             if (res.second == OK && !r->user)
708                 r->user = "";
709 #endif
710             return res.second;
711         }
712
713         // user auth was okay -- export the session data now
714         res = psta->getServiceProvider().doExport(*psta);
715         if (res.first) {
716 #ifdef SHIB_APACHE_24
717             // See above for explanation of this hack.
718             if (res.second == OK && !r->user)
719                 r->user = "";
720 #endif
721             return res.second;
722         }
723
724 #ifdef SHIB_APACHE_24
725         // See above for explanation of this hack.
726         if (!r->user)
727             r->user = "";
728 #endif
729         return OK;
730     }
731     catch (std::exception& e) {
732         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_check_user threw an exception: %s", e.what());
733         return SERVER_ERROR;
734     }
735     catch (...) {
736         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_check_user threw an unknown exception!");
737         if (g_catchAll)
738             return SERVER_ERROR;
739         throw;
740     }
741 }
742
743 // Runs SP handler requests when invoked directly.
744 extern "C" int shib_handler(request_rec* r)
745 {
746     // Short-circuit entirely?
747     if (((shib_dir_config*)ap_get_module_config(r->per_dir_config, &mod_shib))->bOff == 1)
748         return DECLINED;
749
750     string threadid("[");
751     threadid += lexical_cast<string>(getpid()) + "] shib_handler";
752     xmltooling::NDC ndc(threadid.c_str());
753
754 #ifndef SHIB_APACHE_13
755     // With 2.x, this handler always runs, though last.
756     // We check if shib_check_user ran, because it will detect a handler request
757     // and dispatch it directly.
758     void* data;
759     apr_pool_userdata_get(&data,g_UserDataKey,r->pool);
760     if (data==(const void*)42) {
761         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_handler skipped since check_user ran");
762         return DECLINED;
763     }
764 #endif
765
766     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_handler(%d): ENTER: %s", (int)getpid(), r->handler);
767
768     try {
769 #ifndef SHIB_APACHE_24
770         ShibTargetApache sta(r);
771         ShibTargetApache* psta = &sta;
772 #else
773         shib_request_config* rc = (shib_request_config*)ap_get_module_config(r->request_config, &mod_shib);
774         if (!rc || !rc->sta) {
775             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_handler found no per-request structure");
776             return SERVER_ERROR;
777         }
778         ShibTargetApache* psta = rc->sta;
779 #endif
780         if (!psta->init(true, false)) {
781             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_handler unable to initialize SP request object");
782             return SERVER_ERROR;
783         }
784
785         pair<bool,long> res = psta->getServiceProvider().doHandler(*psta);
786         if (res.first) return res.second;
787
788         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "doHandler() did not do anything.");
789         return SERVER_ERROR;
790     }
791     catch (std::exception& e) {
792         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_handler threw an exception: %s", e.what());
793         return SERVER_ERROR;
794     }
795     catch (...) {
796         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_handler threw an unknown exception!");
797         if (g_catchAll)
798           return SERVER_ERROR;
799         throw;
800     }
801 }
802
803 // This performs authorization functions to limit access.
804 // On all versions, this runs any RequestMap-attached plugins.
805 // For pre-2.4 versions, the RequestMap will always find an htAccess plugin
806 // that runs code to parse and enforce Apache Require rules.
807 // On 2.4, we have to short-circuit that and let Apache run callbacks
808 // for each Require rule we handle.
809 extern "C" int shib_auth_checker(request_rec* r)
810 {
811     // Short-circuit entirely?
812     shib_dir_config* dc = (shib_dir_config*)ap_get_module_config(r->per_dir_config, &mod_shib);
813     if (dc->bOff == 1
814 #ifdef SHIB_APACHE_24
815         || dc->bRequestMapperAuthz == 0     // this allows for bypass of the full auth_checker hook if only htaccess is used
816 #endif
817         ) {
818         return DECLINED;
819     }
820
821     ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, SH_AP_R(r), "shib_auth_checker(%d): ENTER", (int)getpid());
822
823     string threadid("[");
824     threadid += lexical_cast<string>(getpid()) + "] shib_auth_checker";
825     xmltooling::NDC ndc(threadid.c_str());
826
827     try {
828 #ifndef SHIB_APACHE_24
829         ShibTargetApache sta(r);
830         ShibTargetApache* psta = &sta;
831 #else
832         shib_request_config* rc = (shib_request_config*)ap_get_module_config(r->request_config, &mod_shib);
833         if (!rc || !rc->sta) {
834             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_auth_checker found no per-request structure");
835             return SERVER_ERROR;
836         }
837         ShibTargetApache* psta = rc->sta;
838 #endif
839         if (!psta->init(false, false)) {
840             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_auth_checker unable to initialize SP request object");
841             return SERVER_ERROR;
842         }
843
844         pair<bool,long> res = psta->getServiceProvider().doAuthorization(*psta);
845         if (res.first) return res.second;
846
847         // The SP method should always return true, so if we get this far, something unusual happened.
848         // Just let Apache (or some other module) decide what to do.
849         return DECLINED;
850     }
851     catch (std::exception& e) {
852         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_auth_checker threw an exception: %s", e.what());
853         return SERVER_ERROR;
854     }
855     catch (...) {
856         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_auth_checker threw an unknown exception!");
857         if (g_catchAll)
858           return SERVER_ERROR;
859         throw;
860     }
861 }
862
863 // Overlays environment variables on top of subprocess table.
864 extern "C" int shib_fixups(request_rec* r)
865 {
866   shib_dir_config *dc = (shib_dir_config*)ap_get_module_config(r->per_dir_config, &mod_shib);
867   if (dc->bOff==1 || dc->bUseEnvVars==0)
868     return DECLINED;
869
870   ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_fixup(%d): ENTER", (int)getpid());
871
872   shib_request_config *rc = (shib_request_config*)ap_get_module_config(r->request_config, &mod_shib);
873   if (rc==nullptr || rc->env==nullptr || ap_is_empty_table(rc->env))
874         return DECLINED;
875
876   ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_fixup adding %d vars", ap_table_elts(rc->env)->nelts);
877   r->subprocess_env = ap_overlay_tables(r->pool, r->subprocess_env, rc->env);
878
879   return OK;
880 }
881
882
883 // Access control plugin that enforces pre-2.4 htaccess rules.
884 // Post-2.4, we have to register individual methods to respond
885 // to each require rule we want to handle, and have those call
886 // into these methods directly.
887 class htAccessControl : virtual public AccessControl
888 {
889 public:
890     htAccessControl() {}
891     ~htAccessControl() {}
892     Lockable* lock() {return this;}
893     void unlock() {}
894     aclresult_t authorized(const SPRequest& request, const Session* session) const;
895
896     aclresult_t doAccessControl(const ShibTargetApache& sta, const Session* session, const char* plugin) const;
897     aclresult_t doUser(const ShibTargetApache& sta, const char* params) const;
898 #ifndef SHIB_APACHE_24
899     aclresult_t doGroup(const ShibTargetApache& sta, const char* params) const;
900 #endif
901     aclresult_t doAuthnContext(const ShibTargetApache& sta, const char* acRef, const char* params) const;
902     aclresult_t doShibAttr(const ShibTargetApache& sta, const Session* session, const char* rule, const char* params) const;
903
904 private:
905     bool checkAttribute(const SPRequest& request, const Attribute* attr, const char* toMatch, RegularExpression* re) const;
906 };
907
908 AccessControl* htAccessFactory(const xercesc::DOMElement* const & e)
909 {
910     return new htAccessControl();
911 }
912
913 AccessControl::aclresult_t htAccessControl::doAccessControl(const ShibTargetApache& sta, const Session* session, const char* plugin) const
914 {
915         aclresult_t result = shib_acl_false;
916         try {
917         ifstream aclfile(plugin);
918         if (!aclfile)
919             throw ConfigurationException("Unable to open access control file ($1).", params(1, plugin));
920         xercesc::DOMDocument* acldoc = XMLToolingConfig::getConfig().getParser().parse(aclfile);
921                 XercesJanitor<xercesc::DOMDocument> docjanitor(acldoc);
922                 static XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e);
923         string t(XMLHelper::getAttrString(acldoc ? acldoc->getDocumentElement() : nullptr, nullptr, _type));
924         if (t.empty())
925             throw ConfigurationException("Missing type attribute in AccessControl plugin configuration.");
926         scoped_ptr<AccessControl> aclplugin(SPConfig::getConfig().AccessControlManager.newPlugin(t.c_str(), acldoc->getDocumentElement()));
927                 Locker acllock(aclplugin.get());
928                 result = aclplugin->authorized(sta, session);
929         }
930         catch (std::exception& ex) {
931                 sta.log(SPRequest::SPError, ex.what());
932         }
933     return result;
934 }
935
936 AccessControl::aclresult_t htAccessControl::doUser(const ShibTargetApache& sta, const char* params) const
937 {
938     bool regexp = false;
939     bool negated = false;
940     while (*params) {
941         const char* w = ap_getword_conf(sta.m_req->pool, &params);
942         if (*w == '~') {
943             regexp = true;
944             continue;
945         }
946         else if (*w == '!') {
947             // A negated rule presumes success unless a match is found.
948             negated = true;
949             if (*(w+1) == '~')
950                 regexp = true;
951             continue;
952         }
953
954         // Figure out if there's a match.
955         bool match = false;
956         if (regexp) {
957             try {
958                 // To do regex matching, we have to convert from UTF-8.
959                 auto_arrayptr<XMLCh> trans(fromUTF8(w));
960                 RegularExpression re(trans.get());
961                 auto_arrayptr<XMLCh> trans2(fromUTF8(sta.getRemoteUser().c_str()));
962                 match = re.matches(trans2.get());
963             }
964             catch (XMLException& ex) {
965                 auto_ptr_char tmp(ex.getMessage());
966                 sta.log(SPRequest::SPError,
967                     string("htaccess plugin caught exception while parsing regular expression (") + w + "): " + tmp.get());
968             }
969         }
970         else if (sta.getRemoteUser() == w) {
971             match = true;
972         }
973
974         if (match) {
975             if (sta.isPriorityEnabled(SPRequest::SPDebug))
976                 sta.log(SPRequest::SPDebug,
977                     string("htaccess: require user ") + (negated ? "rejecting (" : "accepting (") + sta.getRemoteUser() + ")");
978             return (negated ? shib_acl_false : shib_acl_true);
979         }
980     }
981     return (negated ? shib_acl_true : shib_acl_false);
982 }
983
984 #ifndef SHIB_APACHE_24
985 static SH_AP_TABLE* groups_for_user(request_rec* r, const char* user, char* grpfile)
986 {
987     SH_AP_CONFIGFILE* f;
988     SH_AP_TABLE* grps=ap_make_table(r->pool,15);
989     char l[MAX_STRING_LEN];
990     const char *group_name, *ll, *w;
991
992 #ifdef SHIB_APACHE_13
993     if (!(f=ap_pcfg_openfile(r->pool,grpfile))) {
994 #else
995     if (ap_pcfg_openfile(&f,r->pool,grpfile) != APR_SUCCESS) {
996 #endif
997         ap_log_rerror(APLOG_MARK,APLOG_DEBUG,SH_AP_R(r),"groups_for_user() could not open group file: %s\n",grpfile);
998         return nullptr;
999     }
1000
1001     SH_AP_POOL* sp;
1002 #ifdef SHIB_APACHE_13
1003     sp=ap_make_sub_pool(r->pool);
1004 #else
1005     if (apr_pool_create(&sp,r->pool) != APR_SUCCESS) {
1006         ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
1007             "groups_for_user() could not create a subpool");
1008         return nullptr;
1009     }
1010 #endif
1011
1012     while (!(ap_cfg_getline(l,MAX_STRING_LEN,f))) {
1013         if ((*l=='#') || (!*l))
1014             continue;
1015         ll = l;
1016         ap_clear_pool(sp);
1017         group_name = ap_getword(sp,&ll,':');
1018         while (*ll) {
1019             w=ap_getword_conf(sp,&ll);
1020             if (!strcmp(w,user)) {
1021                 ap_table_setn(grps,ap_pstrdup(r->pool,group_name),"in");
1022                 break;
1023             }
1024         }
1025     }
1026     ap_cfg_closefile(f);
1027     ap_destroy_pool(sp);
1028     return grps;
1029 }
1030
1031 AccessControl::aclresult_t htAccessControl::doGroup(const ShibTargetApache& sta, const char* params) const
1032 {
1033     SH_AP_TABLE* grpstatus = nullptr;
1034     if (sta.m_dc->szAuthGrpFile) {
1035         if (sta.isPriorityEnabled(SPRequest::SPDebug))
1036             sta.log(SPRequest::SPDebug, string("htaccess plugin using groups file: ") + sta.m_dc->szAuthGrpFile);
1037         grpstatus = groups_for_user(sta.m_req, sta.getRemoteUser().c_str(), sta.m_dc->szAuthGrpFile);
1038     }
1039
1040     bool negated = false;
1041     while (*params) {
1042         const char* w = ap_getword_conf(sta.m_req->pool, &params);
1043         if (*w == '!') {
1044             // A negated rule presumes success unless a match is found.
1045             negated = true;
1046             continue;
1047         }
1048
1049         if (grpstatus && ap_table_get(grpstatus, w)) {
1050             // If we matched, then we're done with this rule either way and we flip status to reflect the outcome.
1051             sta.log(SPRequest::SPDebug, string("htaccess: require group ") + (negated ? "rejecting (" : "accepting (") + w + ")");
1052             return (negated ? shib_acl_false : shib_acl_true);
1053         }
1054     }
1055
1056     return (negated ? shib_acl_true : shib_acl_false);
1057 }
1058 #endif
1059
1060 AccessControl::aclresult_t htAccessControl::doAuthnContext(const ShibTargetApache& sta, const char* ref, const char* params) const
1061 {
1062     if (ref && *ref) {
1063         bool regexp = false;
1064         bool negated = false;
1065         while (ref && *params) {
1066             const char* w = ap_getword_conf(sta.m_req->pool, &params);
1067             if (*w == '~') {
1068                 regexp = true;
1069                 continue;
1070             }
1071             else if (*w == '!') {
1072                 // A negated rule presumes success unless a match is found.
1073                 negated = true;
1074                 if (*(w+1) == '~')
1075                     regexp = true;
1076                 continue;
1077             }
1078
1079             // Figure out if there's a match.
1080             bool match = false;
1081             if (regexp) {
1082                 try {
1083                     RegularExpression re(w);
1084                     match = re.matches(ref);
1085                 }
1086                 catch (XMLException& ex) {
1087                     auto_ptr_char tmp(ex.getMessage());
1088                     sta.log(SPRequest::SPError,
1089                         string("htaccess plugin caught exception while parsing regular expression (") + w + "): " + tmp.get());
1090                 }
1091             }
1092             else if (!strcmp(w, ref)) {
1093                 match = true;
1094             }
1095
1096             if (match) {
1097                 if (sta.isPriorityEnabled(SPRequest::SPDebug))
1098                     sta.log(SPRequest::SPDebug,
1099                         string("htaccess: require authnContext ") + (negated ? "rejecting (" : "accepting (") + ref + ")");
1100                 return (negated ? shib_acl_false : shib_acl_true);
1101             }
1102         }
1103         return (negated ? shib_acl_true : shib_acl_false);
1104     }
1105
1106     if (sta.isPriorityEnabled(SPRequest::SPDebug))
1107         sta.log(SPRequest::SPDebug, "htaccess: require authnContext rejecting session with no context associated");
1108     return shib_acl_false;
1109 }
1110
1111 bool htAccessControl::checkAttribute(const SPRequest& request, const Attribute* attr, const char* toMatch, RegularExpression* re) const
1112 {
1113     bool caseSensitive = attr->isCaseSensitive();
1114     const vector<string>& vals = attr->getSerializedValues();
1115     for (vector<string>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
1116         if (re) {
1117             auto_arrayptr<XMLCh> trans(fromUTF8(v->c_str()));
1118             if (re->matches(trans.get())) {
1119                 if (request.isPriorityEnabled(SPRequest::SPDebug))
1120                     request.log(SPRequest::SPDebug, string("htaccess: expecting regexp ") + toMatch + ", got " + *v + ": acccepted");
1121                 return true;
1122             }
1123         }
1124         else if ((caseSensitive && *v == toMatch) || (!caseSensitive && !strcasecmp(v->c_str(), toMatch))) {
1125             if (request.isPriorityEnabled(SPRequest::SPDebug))
1126                 request.log(SPRequest::SPDebug, string("htaccess: expecting ") + toMatch + ", got " + *v + ": accepted");
1127             return true;
1128         }
1129         else if (request.isPriorityEnabled(SPRequest::SPDebug)) {
1130             request.log(SPRequest::SPDebug, string("htaccess: expecting ") + toMatch + ", got " + *v + ": rejected");
1131         }
1132     }
1133     return false;
1134 }
1135
1136 AccessControl::aclresult_t htAccessControl::doShibAttr(const ShibTargetApache& sta, const Session* session, const char* rule, const char* params) const
1137 {
1138 #ifndef SHIB_APACHE_24
1139     // Look for the new shib-attr placeholder and move past it.
1140     if (sta.m_dc->bCompatWith24 == 1 && rule && !strcmp(rule, "shib-attr")) {
1141         if (*params)
1142             rule = ap_getword_conf(sta.m_req->pool, &params);
1143     }
1144 #endif
1145
1146     // Find the attribute(s) matching the require rule.
1147     pair<multimap<string,const Attribute*>::const_iterator,multimap<string,const Attribute*>::const_iterator> attrs =
1148         session->getIndexedAttributes().equal_range(rule ? rule : "");
1149
1150     bool regexp = false;
1151     while (attrs.first != attrs.second && *params) {
1152         const char* w = ap_getword_conf(sta.m_req->pool, &params);
1153         if (*w == '~') {
1154             regexp = true;
1155             continue;
1156         }
1157
1158         try {
1159             scoped_ptr<RegularExpression> re;
1160             if (regexp) {
1161                 auto_arrayptr<XMLCh> trans(fromUTF8(w));
1162                 re.reset(new xercesc::RegularExpression(trans.get()));
1163             }
1164                     
1165             pair<multimap<string,const Attribute*>::const_iterator,multimap<string,const Attribute*>::const_iterator> attrs2(attrs);
1166             for (; attrs2.first != attrs2.second; ++attrs2.first) {
1167                 if (checkAttribute(sta, attrs2.first->second, w, regexp ? re.get() : nullptr)) {
1168                     return shib_acl_true;
1169                 }
1170             }
1171         }
1172         catch (XMLException& ex) {
1173             auto_ptr_char tmp(ex.getMessage());
1174             sta.log(SPRequest::SPError, string("htaccess plugin caught exception while parsing regular expression (") + w + "): " + tmp.get());
1175         }
1176     }
1177     return shib_acl_false;
1178 }
1179
1180 AccessControl::aclresult_t htAccessControl::authorized(const SPRequest& request, const Session* session) const
1181 {
1182 #ifdef SHIB_APACHE_24
1183     // We should never be invoked in 2.4 as an SP plugin.
1184     throw ConfigurationException("Save my walrus!");
1185 #else
1186     // Make sure the object is our type.
1187     const ShibTargetApache* sta=dynamic_cast<const ShibTargetApache*>(&request);
1188     if (!sta)
1189         throw ConfigurationException("Request wrapper object was not of correct type.");
1190
1191     int m = sta->m_req->method_number;
1192     bool method_restricted = false;
1193     const char *t, *w;
1194
1195     const array_header* reqs_arr = ap_requires(sta->m_req);
1196     if (!reqs_arr)
1197         return shib_acl_indeterminate;  // should never happen
1198
1199         // Check for an "embedded" AccessControl plugin.
1200         if (sta->m_dc->szAccessControl) {
1201         aclresult_t result = doAccessControl(*sta, session, sta->m_dc->szAccessControl);
1202         if (result == shib_acl_true && sta->m_dc->bRequireAll != 1) {
1203             // If we're not insisting that all rules be met, then we're done.
1204             request.log(SPRequest::SPDebug, "htaccess: embedded AccessControl plugin was successful, granting access");
1205             return shib_acl_true;
1206         }
1207         else if (result != shib_acl_true && sta->m_dc->bRequireAll == 1) {
1208             // If we're insisting that all rules be met, which is not something Apache really handles well,
1209             // then we either return false or indeterminate based on the authoritative option, which defaults on.
1210             if (sta->m_dc->bAuthoritative != 0) {
1211                 request.log(SPRequest::SPDebug, "htaccess: embedded AccessControl plugin was unsuccessful, denying access");
1212                 return shib_acl_false;
1213             }
1214
1215             request.log(SPRequest::SPDebug, "htaccess: embedded AccessControl plugin was unsuccessful but not authoritative, leaving it up to Apache");
1216             return shib_acl_indeterminate;
1217         }
1218     }
1219
1220     require_line* reqs = (require_line*)reqs_arr->elts;
1221
1222     for (int x = 0; x < reqs_arr->nelts; ++x) {
1223         // This rule should be completely ignored, the method doesn't fit.
1224         // The rule just doesn't exist for our purposes.
1225         if (!(reqs[x].method_mask & (1 << m)))
1226             continue;
1227
1228         method_restricted = true; // this lets us know at the end that at least one rule was potentially enforcable.
1229
1230         // Tracks status of this rule's evaluation.
1231         bool status = false;
1232
1233         string remote_user = request.getRemoteUser();
1234
1235         t = reqs[x].requirement;
1236         w = ap_getword_white(sta->m_req->pool, &t);
1237
1238         if (!strcasecmp(w,"shibboleth")) {
1239             // This is a dummy rule needed because Apache conflates authn and authz.
1240             // Without some require rule, AuthType is ignored and no check_user hooks run.
1241
1242             // We evaluate to false if ShibAccessControl is used and ShibRequireAll is off.
1243             // This allows actual rules to dictate the result, since ShibAccessControl returned
1244             // non-true, and if nothing else is used, access will be denied.
1245             if (!sta->m_dc->szAccessControl || sta->m_dc->bRequireAll == 1) {
1246                 // We evaluate to true, because ShibRequireAll is enabled (so a true is just a no-op)
1247                 // or because there was no other AccessControl rule in place, so this may be the only
1248                 // rule in effect.
1249                 status = true;
1250             }
1251         }
1252         else if (!strcmp(w,"valid-user") && session) {
1253             request.log(SPRequest::SPDebug, "htaccess: accepting valid-user based on active session");
1254             status = true;
1255         }
1256         else if (!strcmp(w,"user") && !remote_user.empty()) {
1257             status = (doUser(*sta, t) == shib_acl_true);
1258         }
1259         else if (!strcmp(w,"group")  && !remote_user.empty()) {
1260             status = (doGroup(*sta, t) == shib_acl_true);
1261         }
1262         else if (!strcmp(w,"authnContextClassRef") || !strcmp(w,"authnContextDeclRef")) {
1263             const char* ref = !strcmp(w, "authnContextClassRef") ? session->getAuthnContextClassRef() : session->getAuthnContextDeclRef();
1264             status = (doAuthnContext(*sta, ref, t) == shib_acl_true);
1265         }
1266         else if (!session) {
1267             request.log(SPRequest::SPError, string("htaccess: require ") + w + " not given a valid session, are you using lazy sessions?");
1268         }
1269         else if (sta->m_dc->bCompatWith24 == 1 && !strcmp(w,"shib-plugin")) {
1270             w = ap_getword_conf(sta->m_req->pool, &t);
1271             if (w) {
1272                 status = (doAccessControl(*sta, session, w) == shib_acl_true);
1273             }
1274         }
1275         else {
1276             status = (doShibAttr(*sta, session, w, t) == shib_acl_true);
1277         }
1278
1279         // If status is false, we found a rule we couldn't satisfy.
1280         // Could be an unknown rule to us, or it just didn't match.
1281
1282         if (status && sta->m_dc->bRequireAll != 1) {
1283             // If we're not insisting that all rules be met, then we're done.
1284             request.log(SPRequest::SPDebug, "htaccess: a rule was successful, granting access");
1285             return shib_acl_true;
1286         }
1287         else if (!status && sta->m_dc->bRequireAll == 1) {
1288             // If we're insisting that all rules be met, which is not something Apache really handles well,
1289             // then we either return false or indeterminate based on the authoritative option, which defaults on.
1290             if (sta->m_dc->bAuthoritative != 0) {
1291                 request.log(SPRequest::SPDebug, "htaccess: a rule was unsuccessful, denying access");
1292                 return shib_acl_false;
1293             }
1294
1295             request.log(SPRequest::SPDebug, "htaccess: a rule was unsuccessful but not authoritative, leaving it up to Apache");
1296             return shib_acl_indeterminate;
1297         }
1298
1299         // Otherwise, we keep going. If we're requring all, then we have to check every rule.
1300         // If not we just didn't find a successful rule yet, so we keep going anyway.
1301     }
1302
1303     // If we get here, we either "failed" or we're in require all mode (but not both).
1304     // If no rules possibly apply or we insisted that all rules check out, then we're good.
1305     if (!method_restricted) {
1306         request.log(SPRequest::SPDebug, "htaccess: no rules applied to this request method, granting access");
1307         return shib_acl_true;
1308     }
1309     else if (sta->m_dc->bRequireAll == 1) {
1310         request.log(SPRequest::SPDebug, "htaccess: all rules successful, granting access");
1311         return shib_acl_true;
1312     }
1313     else if (sta->m_dc->bAuthoritative != 0) {
1314         request.log(SPRequest::SPDebug, "htaccess: no rules were successful, denying access");
1315         return shib_acl_false;
1316     }
1317
1318     request.log(SPRequest::SPDebug, "htaccess: no rules were successful but not authoritative, leaving it up to Apache");
1319     return shib_acl_indeterminate;
1320 #endif
1321 }
1322
1323 class ApacheRequestMapper : public virtual RequestMapper, public virtual PropertySet
1324 {
1325 public:
1326     ApacheRequestMapper(const xercesc::DOMElement* e);
1327     ~ApacheRequestMapper() {}
1328     Lockable* lock() { return m_mapper->lock(); }
1329     void unlock() { m_staKey->setData(nullptr); m_propsKey->setData(nullptr); m_mapper->unlock(); }
1330     Settings getSettings(const HTTPRequest& request) const;
1331
1332     const PropertySet* getParent() const { return nullptr; }
1333     void setParent(const PropertySet*) {}
1334     pair<bool,bool> getBool(const char* name, const char* ns=nullptr) const;
1335     pair<bool,const char*> getString(const char* name, const char* ns=nullptr) const;
1336     pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=nullptr) const;
1337     pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=nullptr) const;
1338     pair<bool,int> getInt(const char* name, const char* ns=nullptr) const;
1339     void getAll(map<string,const char*>& properties) const;
1340     const PropertySet* getPropertySet(const char* name, const char* ns=shibspconstants::ASCII_SHIB2SPCONFIG_NS) const;
1341     const xercesc::DOMElement* getElement() const;
1342
1343     const htAccessControl& getHTAccessControl() const { return m_htaccess; }
1344
1345 private:
1346     scoped_ptr<RequestMapper> m_mapper;
1347     scoped_ptr<ThreadKey> m_staKey,m_propsKey;
1348     mutable htAccessControl m_htaccess;
1349 };
1350
1351 RequestMapper* ApacheRequestMapFactory(const xercesc::DOMElement* const & e)
1352 {
1353     return new ApacheRequestMapper(e);
1354 }
1355
1356 ApacheRequestMapper::ApacheRequestMapper(const xercesc::DOMElement* e)
1357     : m_mapper(SPConfig::getConfig().RequestMapperManager.newPlugin(XML_REQUEST_MAPPER,e)),
1358         m_staKey(ThreadKey::create(nullptr)), m_propsKey(ThreadKey::create(nullptr))
1359 {
1360 }
1361
1362 RequestMapper::Settings ApacheRequestMapper::getSettings(const HTTPRequest& request) const
1363 {
1364     Settings s = m_mapper->getSettings(request);
1365     m_staKey->setData((void*)dynamic_cast<const ShibTargetApache*>(&request));
1366     m_propsKey->setData((void*)s.first);
1367     // Only return the htAccess plugin for pre-2.4 servers.
1368 #ifdef SHIB_APACHE_24
1369     return pair<const PropertySet*,AccessControl*>(this, s.second);
1370 #else
1371     return pair<const PropertySet*,AccessControl*>(this, s.second ? s.second : &m_htaccess);
1372 #endif
1373 }
1374
1375 pair<bool,bool> ApacheRequestMapper::getBool(const char* name, const char* ns) const
1376 {
1377     const ShibTargetApache* sta=reinterpret_cast<const ShibTargetApache*>(m_staKey->getData());
1378     const PropertySet* s=reinterpret_cast<const PropertySet*>(m_propsKey->getData());
1379     if (sta && !ns) {
1380         // Override Apache-settable boolean properties.
1381         if (name && !strcmp(name,"requireSession") && sta->m_dc->bRequireSession != -1)
1382             return make_pair(true, sta->m_dc->bRequireSession==1);
1383         else if (name && !strcmp(name,"exportAssertion") && sta->m_dc->bExportAssertion != -1)
1384             return make_pair(true, sta->m_dc->bExportAssertion==1);
1385         else if (sta->m_dc->tSettings) {
1386             const char* prop = ap_table_get(sta->m_dc->tSettings, name);
1387             if (prop)
1388                 return make_pair(true, !strcmp(prop, "true") || !strcmp(prop, "1") || !strcmp(prop, "On"));
1389         }
1390     }
1391     return s ? s->getBool(name,ns) : make_pair(false,false);
1392 }
1393
1394 pair<bool,const char*> ApacheRequestMapper::getString(const char* name, const char* ns) const
1395 {
1396     const ShibTargetApache* sta=reinterpret_cast<const ShibTargetApache*>(m_staKey->getData());
1397     const PropertySet* s=reinterpret_cast<const PropertySet*>(m_propsKey->getData());
1398     if (sta && !ns) {
1399         // Override Apache-settable string properties.
1400         if (name && !strcmp(name,"authType")) {
1401             const char* auth_type = ap_auth_type(sta->m_req);
1402             if (auth_type) {
1403                 // Check for Basic Hijack
1404                 if (!strcasecmp(auth_type, "basic") && sta->m_dc->bBasicHijack == 1)
1405                     auth_type = "shibboleth";
1406                 return make_pair(true, auth_type);
1407             }
1408         }
1409         else if (name && !strcmp(name,"applicationId") && sta->m_dc->szApplicationId)
1410             return pair<bool,const char*>(true,sta->m_dc->szApplicationId);
1411         else if (name && !strcmp(name,"requireSessionWith") && sta->m_dc->szRequireWith)
1412             return pair<bool,const char*>(true,sta->m_dc->szRequireWith);
1413         else if (name && !strcmp(name,"redirectToSSL") && sta->m_dc->szRedirectToSSL)
1414             return pair<bool,const char*>(true,sta->m_dc->szRedirectToSSL);
1415         else if (sta->m_dc->tSettings) {
1416             const char* prop = ap_table_get(sta->m_dc->tSettings, name);
1417             if (prop)
1418                 return make_pair(true, prop);
1419         }
1420     }
1421     return s ? s->getString(name,ns) : pair<bool,const char*>(false,nullptr);
1422 }
1423
1424 pair<bool,const XMLCh*> ApacheRequestMapper::getXMLString(const char* name, const char* ns) const
1425 {
1426     const PropertySet* s=reinterpret_cast<const PropertySet*>(m_propsKey->getData());
1427     return s ? s->getXMLString(name,ns) : pair<bool,const XMLCh*>(false,nullptr);
1428 }
1429
1430 pair<bool,unsigned int> ApacheRequestMapper::getUnsignedInt(const char* name, const char* ns) const
1431 {
1432     const ShibTargetApache* sta=reinterpret_cast<const ShibTargetApache*>(m_staKey->getData());
1433     const PropertySet* s=reinterpret_cast<const PropertySet*>(m_propsKey->getData());
1434     if (sta && !ns) {
1435         // Override Apache-settable int properties.
1436         if (name && !strcmp(name,"redirectToSSL") && sta->m_dc->szRedirectToSSL)
1437             return pair<bool,unsigned int>(true, strtol(sta->m_dc->szRedirectToSSL, nullptr, 10));
1438         else if (sta->m_dc->tSettings) {
1439             const char* prop = ap_table_get(sta->m_dc->tSettings, name);
1440             if (prop)
1441                 return pair<bool,unsigned int>(true, atoi(prop));
1442         }
1443     }
1444     return s ? s->getUnsignedInt(name,ns) : pair<bool,unsigned int>(false,0);
1445 }
1446
1447 pair<bool,int> ApacheRequestMapper::getInt(const char* name, const char* ns) const
1448 {
1449     const ShibTargetApache* sta=reinterpret_cast<const ShibTargetApache*>(m_staKey->getData());
1450     const PropertySet* s=reinterpret_cast<const PropertySet*>(m_propsKey->getData());
1451     if (sta && !ns) {
1452         // Override Apache-settable int properties.
1453         if (name && !strcmp(name,"redirectToSSL") && sta->m_dc->szRedirectToSSL)
1454             return pair<bool,int>(true,atoi(sta->m_dc->szRedirectToSSL));
1455         else if (sta->m_dc->tSettings) {
1456             const char* prop = ap_table_get(sta->m_dc->tSettings, name);
1457             if (prop)
1458                 return make_pair(true, atoi(prop));
1459         }
1460     }
1461     return s ? s->getInt(name,ns) : pair<bool,int>(false,0);
1462 }
1463
1464 static int _rm_get_all_table_walk(void *v, const char *key, const char *value)
1465 {
1466     reinterpret_cast<map<string,const char*>*>(v)->insert(pair<string,const char*>(key, value));
1467     return 1;
1468 }
1469
1470 void ApacheRequestMapper::getAll(map<string,const char*>& properties) const
1471 {
1472     const ShibTargetApache* sta=reinterpret_cast<const ShibTargetApache*>(m_staKey->getData());
1473     const PropertySet* s=reinterpret_cast<const PropertySet*>(m_propsKey->getData());
1474
1475     if (s)
1476         s->getAll(properties);
1477     if (!sta)
1478         return;
1479
1480     const char* auth_type=ap_auth_type(sta->m_req);
1481     if (auth_type) {
1482         // Check for Basic Hijack
1483         if (!strcasecmp(auth_type, "basic") && sta->m_dc->bBasicHijack == 1)
1484             auth_type = "shibboleth";
1485         properties["authType"] = auth_type;
1486     }
1487
1488     if (sta->m_dc->szApplicationId)
1489         properties["applicationId"] = sta->m_dc->szApplicationId;
1490     if (sta->m_dc->szRequireWith)
1491         properties["requireSessionWith"] = sta->m_dc->szRequireWith;
1492     if (sta->m_dc->szRedirectToSSL)
1493         properties["redirectToSSL"] = sta->m_dc->szRedirectToSSL;
1494     if (sta->m_dc->bRequireSession != 0)
1495         properties["requireSession"] = (sta->m_dc->bRequireSession==1) ? "true" : "false";
1496     if (sta->m_dc->bExportAssertion != 0)
1497         properties["exportAssertion"] = (sta->m_dc->bExportAssertion==1) ? "true" : "false";
1498
1499     if (sta->m_dc->tSettings)
1500         ap_table_do(_rm_get_all_table_walk, &properties, sta->m_dc->tSettings, NULL);
1501 }
1502
1503 const PropertySet* ApacheRequestMapper::getPropertySet(const char* name, const char* ns) const
1504 {
1505     const PropertySet* s=reinterpret_cast<const PropertySet*>(m_propsKey->getData());
1506     return s ? s->getPropertySet(name,ns) : nullptr;
1507 }
1508
1509 const xercesc::DOMElement* ApacheRequestMapper::getElement() const
1510 {
1511     const PropertySet* s=reinterpret_cast<const PropertySet*>(m_propsKey->getData());
1512     return s ? s->getElement() : nullptr;
1513 }
1514
1515 // Authz callbacks for Apache 2.4
1516 #ifdef SHIB_APACHE_24
1517 pair<ShibTargetApache*,authz_status> shib_base_check_authz(request_rec* r)
1518 {
1519     shib_request_config* rc = (shib_request_config*)ap_get_module_config(r->request_config, &mod_shib);
1520     if (!rc || !rc->sta) {
1521         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_base_check_authz found no per-request structure");
1522         return make_pair((ShibTargetApache*)nullptr, AUTHZ_GENERAL_ERROR);
1523     }
1524     else if (!rc->sta->isInitialized()) {
1525         return make_pair((ShibTargetApache*)nullptr, AUTHZ_DENIED_NO_USER);
1526     }
1527     return make_pair(rc->sta, AUTHZ_GRANTED);
1528 }
1529
1530 extern "C" authz_status shib_shibboleth_check_authz(request_rec* r, const char* require_line, const void*)
1531 {
1532     pair<ShibTargetApache*,authz_status> sta = shib_base_check_authz(r);
1533     if (!sta.first)
1534         return sta.second;
1535     return AUTHZ_GRANTED;
1536 }
1537
1538 extern "C" authz_status shib_validuser_check_authz(request_rec* r, const char* require_line, const void*)
1539 {
1540     pair<ShibTargetApache*,authz_status> sta = shib_base_check_authz(r);
1541     if (!sta.first)
1542         return sta.second;
1543
1544     try {
1545         const Session* session = sta.first->getSession(false);
1546         if (session) {
1547             sta.first->log(SPRequest::SPDebug, "htaccess: accepting valid-user based on active session");
1548             return AUTHZ_GRANTED;
1549         }
1550     }
1551     catch (std::exception& e) {
1552         sta.first->log(SPRequest::SPWarn, string("htaccess: unable to obtain session for access control check: ") +  e.what());
1553     }
1554
1555     return AUTHZ_DENIED_NO_USER;
1556 }
1557
1558 extern "C" authz_status shib_user_check_authz(request_rec* r, const char* require_line, const void*)
1559 {
1560     if (!r->user || !*(r->user))
1561         return AUTHZ_DENIED_NO_USER;
1562     pair<ShibTargetApache*,authz_status> sta = shib_base_check_authz(r);
1563     if (!sta.first)
1564         return sta.second;
1565
1566     const htAccessControl& hta = dynamic_cast<const ApacheRequestMapper*>(sta.first->getRequestSettings().first)->getHTAccessControl();
1567     if (hta.doUser(*sta.first, require_line) == AccessControl::shib_acl_true)
1568         return AUTHZ_GRANTED;
1569     return AUTHZ_DENIED;
1570 }
1571
1572 extern "C" authz_status shib_acclass_check_authz(request_rec* r, const char* require_line, const void*)
1573 {
1574     pair<ShibTargetApache*,authz_status> sta = shib_base_check_authz(r);
1575     if (!sta.first)
1576         return sta.second;
1577
1578     const htAccessControl& hta = dynamic_cast<const ApacheRequestMapper*>(sta.first->getRequestSettings().first)->getHTAccessControl();
1579
1580     try {
1581         const Session* session = sta.first->getSession(false);
1582         if (session && hta.doAuthnContext(*sta.first, session->getAuthnContextClassRef(), require_line) == AccessControl::shib_acl_true)
1583             return AUTHZ_GRANTED;
1584         return session ? AUTHZ_DENIED : AUTHZ_DENIED_NO_USER;
1585     }
1586     catch (std::exception& e) {
1587         sta.first->log(SPRequest::SPWarn, string("htaccess: unable to obtain session for access control check: ") +  e.what());
1588     }
1589
1590     return AUTHZ_GENERAL_ERROR;
1591 }
1592
1593 extern "C" authz_status shib_acdecl_check_authz(request_rec* r, const char* require_line, const void*)
1594 {
1595     pair<ShibTargetApache*,authz_status> sta = shib_base_check_authz(r);
1596     if (!sta.first)
1597         return sta.second;
1598
1599     const htAccessControl& hta = dynamic_cast<const ApacheRequestMapper*>(sta.first->getRequestSettings().first)->getHTAccessControl();
1600
1601     try {
1602         const Session* session = sta.first->getSession(false);
1603         if (session && hta.doAuthnContext(*sta.first, session->getAuthnContextDeclRef(), require_line) == AccessControl::shib_acl_true)
1604             return AUTHZ_GRANTED;
1605         return session ? AUTHZ_DENIED : AUTHZ_DENIED_NO_USER;
1606     }
1607     catch (std::exception& e) {
1608         sta.first->log(SPRequest::SPWarn, string("htaccess: unable to obtain session for access control check: ") +  e.what());
1609     }
1610
1611     return AUTHZ_GENERAL_ERROR;
1612 }
1613
1614 extern "C" authz_status shib_attr_check_authz(request_rec* r, const char* require_line, const void*)
1615 {
1616     pair<ShibTargetApache*,authz_status> sta = shib_base_check_authz(r);
1617     if (!sta.first)
1618         return sta.second;
1619
1620     const htAccessControl& hta = dynamic_cast<const ApacheRequestMapper*>(sta.first->getRequestSettings().first)->getHTAccessControl();
1621
1622     try {
1623         const Session* session = sta.first->getSession(false);
1624         if (session) {
1625             const char* rule = ap_getword_conf(r->pool, &require_line);
1626             if (rule && hta.doShibAttr(*sta.first, session, rule, require_line) == AccessControl::shib_acl_true)
1627                 return AUTHZ_GRANTED;
1628         }
1629         return session ? AUTHZ_DENIED : AUTHZ_DENIED_NO_USER;
1630     }
1631     catch (std::exception& e) {
1632         sta.first->log(SPRequest::SPWarn, string("htaccess: unable to obtain session for access control check: ") +  e.what());
1633     }
1634
1635     return AUTHZ_GENERAL_ERROR;
1636 }
1637
1638 extern "C" authz_status shib_plugin_check_authz(request_rec* r, const char* require_line, const void*)
1639 {
1640     pair<ShibTargetApache*,authz_status> sta = shib_base_check_authz(r);
1641     if (!sta.first)
1642         return sta.second;
1643
1644     const htAccessControl& hta = dynamic_cast<const ApacheRequestMapper*>(sta.first->getRequestSettings().first)->getHTAccessControl();
1645
1646     try {
1647         const Session* session = sta.first->getSession(false);
1648         if (session) {
1649             const char* config = ap_getword_conf(r->pool, &require_line);
1650             if (config && hta.doAccessControl(*sta.first, session, config) == AccessControl::shib_acl_true)
1651                 return AUTHZ_GRANTED;
1652         }
1653         return session ? AUTHZ_DENIED : AUTHZ_DENIED_NO_USER;
1654     }
1655     catch (std::exception& e) {
1656         sta.first->log(SPRequest::SPWarn, string("htaccess: unable to obtain session for access control check: ") +  e.what());
1657     }
1658
1659     return AUTHZ_GENERAL_ERROR;
1660 }
1661 #endif
1662
1663 // Command manipulation functions
1664
1665 extern "C" const char* ap_set_global_string_slot(cmd_parms* parms, void*, const char* arg)
1666 {
1667     *((char**)(parms->info))=ap_pstrdup(parms->pool,arg);
1668     return nullptr;
1669 }
1670
1671 extern "C" const char* shib_set_server_string_slot(cmd_parms* parms, void*, const char* arg)
1672 {
1673     char* base=(char*)ap_get_module_config(parms->server->module_config,&mod_shib);
1674     size_t offset=(size_t)parms->info;
1675     *((char**)(base + offset))=ap_pstrdup(parms->pool,arg);
1676     return nullptr;
1677 }
1678
1679 extern "C" const char* shib_ap_set_file_slot(cmd_parms* parms,
1680 #ifdef SHIB_APACHE_13
1681                                              char* arg1, char* arg2
1682 #else
1683                                              void* arg1, const char* arg2
1684 #endif
1685                                              )
1686 {
1687   ap_set_file_slot(parms, arg1, arg2);
1688   return DECLINE_CMD;
1689 }
1690
1691 extern "C" const char* shib_table_set(cmd_parms* parms, shib_dir_config* dc, const char* arg1, const char* arg2)
1692 {
1693     if (!dc->tSettings)
1694         dc->tSettings = ap_make_table(parms->pool, 4);
1695     ap_table_set(dc->tSettings, arg1, arg2);
1696     return nullptr;
1697 }
1698
1699 #ifndef SHIB_APACHE_24
1700 extern "C" const char* shib_set_acl_slot(cmd_parms* params, shib_dir_config* dc, char* arg)
1701 {
1702     bool absolute;
1703     switch (*arg) {
1704         case 0:
1705             absolute = false;
1706             break;
1707         case '/':
1708         case '\\':
1709             absolute = true;
1710             break;
1711         case '.':
1712             absolute = (*(arg+1) == '.' || *(arg+1) == '/' || *(arg+1) == '\\');
1713             break;
1714         default:
1715             absolute = *(arg+1) == ':';
1716     }
1717
1718     if (absolute || !params->path)
1719         dc->szAccessControl = ap_pstrdup(params->pool, arg);
1720     else
1721         dc->szAccessControl = ap_pstrcat(params->pool, params->path, arg);
1722     return nullptr;
1723 }
1724 #endif
1725
1726
1727 #ifdef SHIB_APACHE_13
1728 /*
1729  * shib_child_exit()
1730  *  Cleanup the (per-process) pool info.
1731  */
1732 extern "C" void shib_child_exit(server_rec* s, SH_AP_POOL* p)
1733 {
1734     if (g_Config) {
1735         ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_exit(%d) dealing with g_Config..", (int)getpid());
1736         g_Config->term();
1737         g_Config = nullptr;
1738         ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_exit() done");
1739     }
1740 }
1741 #else
1742 /*
1743  * shib_exit()
1744  *  Apache 2.x doesn't allow for per-child cleanup, causes CGI forks to hang.
1745  */
1746 extern "C" apr_status_t shib_exit(void* data)
1747 {
1748     if (g_Config) {
1749         g_Config->term();
1750         g_Config = nullptr;
1751     }
1752     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,nullptr,"shib_exit() done");
1753     return OK;
1754 }
1755 #endif
1756
1757 /*
1758  * shire_child_init()
1759  *  Things to do when the child process is initialized.
1760  *  (or after the configs are read in apache-2)
1761  */
1762 #ifdef SHIB_APACHE_13
1763 extern "C" void shib_child_init(server_rec* s, SH_AP_POOL* p)
1764 #else
1765 extern "C" void shib_child_init(apr_pool_t* p, server_rec* s)
1766 #endif
1767 {
1768     // Initialize runtime components.
1769
1770     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init(%d) starting", (int)getpid());
1771
1772     if (g_Config) {
1773         ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() already initialized!");
1774         exit(1);
1775     }
1776
1777     g_Config=&SPConfig::getConfig();
1778     g_Config->setFeatures(
1779         SPConfig::Listener |
1780         SPConfig::Caching |
1781         SPConfig::RequestMapping |
1782         SPConfig::InProcess |
1783         SPConfig::Logging |
1784         SPConfig::Handlers
1785         );
1786     if (!g_Config->init(g_szSchemaDir, g_szPrefix)) {
1787         ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to initialize libraries");
1788         exit(1);
1789     }
1790 #ifndef SHIB_APACHE_24
1791     g_Config->AccessControlManager.registerFactory(HT_ACCESS_CONTROL, &htAccessFactory);
1792 #endif
1793     g_Config->RequestMapperManager.registerFactory(NATIVE_REQUEST_MAPPER, &ApacheRequestMapFactory);
1794
1795     try {
1796         if (!g_Config->instantiate(g_szSHIBConfig, true))
1797             throw runtime_error("unknown error");
1798     }
1799     catch (std::exception& ex) {
1800         ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"%s",ex.what());
1801         ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to load configuration");
1802         exit(1);
1803     }
1804
1805     ServiceProvider* sp = g_Config->getServiceProvider();
1806     xmltooling::Locker locker(sp);
1807     const PropertySet* props = sp->getPropertySet("InProcess");
1808     if (props) {
1809         pair<bool,const char*> unsetValue = props->getString("unsetHeaderValue");
1810         if (unsetValue.first)
1811             g_unsetHeaderValue = unsetValue.second;
1812         pair<bool,bool> flag=props->getBool("checkSpoofing");
1813         g_checkSpoofing = !flag.first || flag.second;
1814         if (g_checkSpoofing) {
1815             unsetValue=props->getString("spoofKey");
1816             if (unsetValue.first)
1817                 g_spoofKey = unsetValue.second;
1818         }
1819         flag=props->getBool("catchAll");
1820         g_catchAll = flag.first && flag.second;
1821     }
1822
1823     // Set the cleanup handler
1824     apr_pool_cleanup_register(p, nullptr, &shib_exit, apr_pool_cleanup_null);
1825
1826     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, SH_AP_R(s), "shib_child_init() done");
1827 }
1828
1829 // Output filters
1830 #ifdef SHIB_DEFERRED_HEADERS
1831 static void set_output_filter(request_rec *r)
1832 {
1833    ap_add_output_filter("SHIB_HEADERS_OUT", nullptr, r, r->connection);
1834 }
1835
1836 static void set_error_filter(request_rec *r)
1837 {
1838    ap_add_output_filter("SHIB_HEADERS_ERR", nullptr, r, r->connection);
1839 }
1840
1841 static int _table_add(void *v, const char *key, const char *value)
1842 {
1843     apr_table_addn((apr_table_t*)v, key, value);
1844     return 1;
1845 }
1846
1847 static apr_status_t do_output_filter(ap_filter_t *f, apr_bucket_brigade *in)
1848 {
1849     request_rec *r = f->r;
1850     shib_request_config *rc = (shib_request_config*) ap_get_module_config(r->request_config, &mod_shib);
1851
1852     if (rc && rc->hdr_out) {
1853         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_out_filter: merging %d headers", apr_table_elts(rc->hdr_out)->nelts);
1854         // can't use overlap call because it will collapse Set-Cookie headers
1855         //apr_table_overlap(r->headers_out, rc->hdr_out, APR_OVERLAP_TABLES_MERGE);
1856         apr_table_do(_table_add,r->headers_out, rc->hdr_out,NULL);
1857     }
1858
1859     /* remove ourselves from the filter chain */
1860     ap_remove_output_filter(f);
1861
1862     /* send the data up the stack */
1863     return ap_pass_brigade(f->next,in);
1864 }
1865
1866 static apr_status_t do_error_filter(ap_filter_t *f, apr_bucket_brigade *in)
1867 {
1868     request_rec *r = f->r;
1869     shib_request_config *rc = (shib_request_config*) ap_get_module_config(r->request_config, &mod_shib);
1870
1871     if (rc && rc->hdr_out) {
1872         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_err_filter: merging %d headers", apr_table_elts(rc->hdr_out)->nelts);
1873         // can't use overlap call because it will collapse Set-Cookie headers
1874         //apr_table_overlap(r->err_headers_out, rc->hdr_out, APR_OVERLAP_TABLES_MERGE);
1875         apr_table_do(_table_add,r->err_headers_out, rc->hdr_out,NULL);
1876     }
1877
1878     /* remove ourselves from the filter chain */
1879     ap_remove_output_filter(f);
1880
1881     /* send the data up the stack */
1882     return ap_pass_brigade(f->next,in);
1883 }
1884 #endif // SHIB_DEFERRED_HEADERS
1885
1886 typedef const char* (*config_fn_t)(void);
1887
1888 #ifdef SHIB_APACHE_13
1889
1890 // SHIB Module commands
1891
1892 static command_rec shire_cmds[] = {
1893   {"ShibPrefix", (config_fn_t)ap_set_global_string_slot, &g_szPrefix,
1894    RSRC_CONF, TAKE1, "Shibboleth installation directory"},
1895   {"ShibConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1896    RSRC_CONF, TAKE1, "Path to shibboleth2.xml config file"},
1897   {"ShibCatalogs", (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
1898    RSRC_CONF, TAKE1, "Paths of XML schema catalogs"},
1899
1900   {"ShibURLScheme", (config_fn_t)shib_set_server_string_slot,
1901    (void *) XtOffsetOf (shib_server_config, szScheme),
1902    RSRC_CONF, TAKE1, "URL scheme to force into generated URLs for a vhost"},
1903
1904   {"ShibRequestSetting", (config_fn_t)shib_table_set, nullptr,
1905    OR_AUTHCFG, TAKE2, "Set arbitrary Shibboleth request property for content"},
1906
1907   {"ShibAccessControl", (config_fn_t)shib_set_acl_slot, nullptr,
1908    OR_AUTHCFG, TAKE1, "Set arbitrary Shibboleth access control plugin for content"},
1909
1910   {"ShibDisable", (config_fn_t)ap_set_flag_slot,
1911    (void *) XtOffsetOf (shib_dir_config, bOff),
1912    OR_AUTHCFG, FLAG, "Disable all Shib module activity here to save processing effort"},
1913   {"ShibApplicationId", (config_fn_t)ap_set_string_slot,
1914    (void *) XtOffsetOf (shib_dir_config, szApplicationId),
1915    OR_AUTHCFG, TAKE1, "Set Shibboleth applicationId property for content"},
1916   {"ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
1917    (void *) XtOffsetOf (shib_dir_config, bBasicHijack),
1918    OR_AUTHCFG, FLAG, "(DEPRECATED) Respond to AuthType Basic and convert to shibboleth"},
1919   {"ShibRequireSession", (config_fn_t)ap_set_flag_slot,
1920    (void *) XtOffsetOf (shib_dir_config, bRequireSession),
1921    OR_AUTHCFG, FLAG, "Initiates a new session if one does not exist"},
1922   {"ShibRequireSessionWith", (config_fn_t)ap_set_string_slot,
1923    (void *) XtOffsetOf (shib_dir_config, szRequireWith),
1924    OR_AUTHCFG, TAKE1, "Initiates a new session if one does not exist using a specific SessionInitiator"},
1925   {"ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
1926    (void *) XtOffsetOf (shib_dir_config, bExportAssertion),
1927    OR_AUTHCFG, FLAG, "Export SAML attribute assertion(s) to Shib-Attributes header"},
1928   {"ShibRedirectToSSL", (config_fn_t)ap_set_string_slot,
1929    (void *) XtOffsetOf (shib_dir_config, szRedirectToSSL),
1930    OR_AUTHCFG, TAKE1, "Redirect non-SSL requests to designated port" },
1931   {"AuthGroupFile", (config_fn_t)shib_ap_set_file_slot,
1932    (void *) XtOffsetOf (shib_dir_config, szAuthGrpFile),
1933    OR_AUTHCFG, TAKE1, "text file containing group names and member user IDs"},
1934   {"ShibRequireAll", (config_fn_t)ap_set_flag_slot,
1935    (void *) XtOffsetOf (shib_dir_config, bRequireAll),
1936    OR_AUTHCFG, FLAG, "All require directives must match"},
1937   {"AuthzShibAuthoritative", (config_fn_t)ap_set_flag_slot,
1938    (void *) XtOffsetOf (shib_dir_config, bAuthoritative),
1939    OR_AUTHCFG, FLAG, "Allow failed mod_shib htaccess authorization to fall through to other modules"},
1940   {"ShibCompatWith24", (config_fn_t)ap_set_flag_slot,
1941    (void *) XtOffsetOf (shib_dir_config, bCompatWith24),
1942    OR_AUTHCFG, FLAG, "Support Apache 2.4-style require rules"},
1943   {"ShibUseEnvironment", (config_fn_t)ap_set_flag_slot,
1944    (void *) XtOffsetOf (shib_dir_config, bUseEnvVars),
1945    OR_AUTHCFG, FLAG, "Export attributes using environment variables (default)"},
1946   {"ShibUseHeaders", (config_fn_t)ap_set_flag_slot,
1947    (void *) XtOffsetOf (shib_dir_config, bUseHeaders),
1948    OR_AUTHCFG, FLAG, "Export attributes using custom HTTP headers"},
1949   {"ShibExpireRedirects", (config_fn_t)ap_set_flag_slot,
1950    (void *) XtOffsetOf (shib_dir_config, bExpireRedirects),
1951    OR_AUTHCFG, FLAG, "Expire SP-generated redirects"},
1952
1953   {nullptr}
1954 };
1955
1956 extern "C"{
1957 handler_rec shib_handlers[] = {
1958   { "shib-handler", shib_handler },
1959   { nullptr }
1960 };
1961
1962 module MODULE_VAR_EXPORT mod_shib = {
1963     STANDARD_MODULE_STUFF,
1964     nullptr,                        /* initializer */
1965     create_shib_dir_config,     /* dir config creater */
1966     merge_shib_dir_config,      /* dir merger --- default is to override */
1967     create_shib_server_config, /* server config */
1968     merge_shib_server_config,   /* merge server config */
1969     shire_cmds,                 /* command table */
1970     shib_handlers,              /* handlers */
1971     nullptr,                    /* filename translation */
1972     shib_check_user,            /* check_user_id */
1973     shib_auth_checker,          /* check auth */
1974     nullptr,                    /* check access */
1975     nullptr,                    /* type_checker */
1976     shib_fixups,                /* fixups */
1977     nullptr,                    /* logger */
1978     nullptr,                    /* header parser */
1979     shib_child_init,            /* child_init */
1980     shib_child_exit,            /* child_exit */
1981     shib_post_read              /* post read-request */
1982 };
1983
1984 #else
1985
1986 #ifdef SHIB_APACHE_24
1987 extern "C" const authz_provider shib_authz_shibboleth_provider = { &shib_shibboleth_check_authz, nullptr };
1988 extern "C" const authz_provider shib_authz_validuser_provider = { &shib_validuser_check_authz, nullptr };
1989 extern "C" const authz_provider shib_authz_user_provider = { &shib_user_check_authz, nullptr };
1990 extern "C" const authz_provider shib_authz_acclass_provider = { &shib_acclass_check_authz, nullptr };
1991 extern "C" const authz_provider shib_authz_acdecl_provider = { &shib_acdecl_check_authz, nullptr };
1992 extern "C" const authz_provider shib_authz_attr_provider = { &shib_attr_check_authz, nullptr };
1993 extern "C" const authz_provider shib_authz_plugin_provider = { &shib_plugin_check_authz, nullptr };
1994 #endif
1995
1996 extern "C" void shib_register_hooks (apr_pool_t *p)
1997 {
1998 #ifdef SHIB_DEFERRED_HEADERS
1999     ap_register_output_filter("SHIB_HEADERS_OUT", do_output_filter, nullptr, AP_FTYPE_CONTENT_SET);
2000     ap_hook_insert_filter(set_output_filter, nullptr, nullptr, APR_HOOK_LAST);
2001     ap_register_output_filter("SHIB_HEADERS_ERR", do_error_filter, nullptr, AP_FTYPE_CONTENT_SET);
2002     ap_hook_insert_error_filter(set_error_filter, nullptr, nullptr, APR_HOOK_LAST);
2003     ap_hook_post_read_request(shib_post_read, nullptr, nullptr, APR_HOOK_MIDDLE);
2004 #endif
2005     ap_hook_child_init(shib_child_init, nullptr, nullptr, APR_HOOK_MIDDLE);
2006     const char* prereq = getenv("SHIBSP_APACHE_PREREQ");
2007 #ifdef SHIB_APACHE_24
2008     if (prereq && *prereq) {
2009         const char* const authnPre[] = { prereq, nullptr };
2010         ap_hook_check_authn(shib_check_user, authnPre, nullptr, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_URI);
2011     }
2012     else {
2013         ap_hook_check_authn(shib_check_user, nullptr, nullptr, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_URI);
2014     }
2015     ap_hook_check_authz(shib_auth_checker, nullptr, nullptr, APR_HOOK_FIRST, AP_AUTH_INTERNAL_PER_URI);
2016 #else
2017     if (prereq && *prereq) {
2018         const char* const authnPre[] = { prereq, nullptr };
2019         ap_hook_check_user_id(shib_check_user, authnPre, nullptr, APR_HOOK_MIDDLE);
2020     }
2021     else {
2022         ap_hook_check_user_id(shib_check_user, nullptr, nullptr, APR_HOOK_MIDDLE);
2023     }
2024     ap_hook_auth_checker(shib_auth_checker, nullptr, nullptr, APR_HOOK_FIRST);
2025 #endif
2026     ap_hook_handler(shib_handler, nullptr, nullptr, APR_HOOK_LAST);
2027     ap_hook_fixups(shib_fixups, nullptr, nullptr, APR_HOOK_MIDDLE);
2028
2029 #ifdef SHIB_APACHE_24
2030     ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "shibboleth", AUTHZ_PROVIDER_VERSION, &shib_authz_shibboleth_provider, AP_AUTH_INTERNAL_PER_CONF);
2031     ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "valid-user", AUTHZ_PROVIDER_VERSION, &shib_authz_validuser_provider, AP_AUTH_INTERNAL_PER_CONF);
2032     ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "user", AUTHZ_PROVIDER_VERSION, &shib_authz_user_provider, AP_AUTH_INTERNAL_PER_CONF);
2033     ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "authnContextClassRef", AUTHZ_PROVIDER_VERSION, &shib_authz_acclass_provider, AP_AUTH_INTERNAL_PER_CONF);
2034     ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "authnContextDeclRef", AUTHZ_PROVIDER_VERSION, &shib_authz_acdecl_provider, AP_AUTH_INTERNAL_PER_CONF);
2035     ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "shib-attr", AUTHZ_PROVIDER_VERSION, &shib_authz_attr_provider, AP_AUTH_INTERNAL_PER_CONF);
2036     ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "shib-plugin", AUTHZ_PROVIDER_VERSION, &shib_authz_plugin_provider, AP_AUTH_INTERNAL_PER_CONF);
2037 #endif
2038 }
2039
2040 // SHIB Module commands
2041
2042 extern "C" {
2043 static command_rec shib_cmds[] = {
2044     AP_INIT_TAKE1("ShibPrefix", (config_fn_t)ap_set_global_string_slot, &g_szPrefix,
2045         RSRC_CONF, "Shibboleth installation directory"),
2046     AP_INIT_TAKE1("ShibConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
2047         RSRC_CONF, "Path to shibboleth2.xml config file"),
2048     AP_INIT_TAKE1("ShibCatalogs", (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
2049         RSRC_CONF, "Paths of XML schema catalogs"),
2050     AP_INIT_TAKE1("ShibGSSKey", (config_fn_t)ap_set_global_string_slot, &g_szGSSContextKey,
2051         RSRC_CONF, "Name of user data key containing GSS context established by GSS module"),
2052
2053     AP_INIT_TAKE1("ShibURLScheme", (config_fn_t)shib_set_server_string_slot,
2054         (void *) offsetof (shib_server_config, szScheme),
2055         RSRC_CONF, "URL scheme to force into generated URLs for a vhost"),
2056
2057     AP_INIT_TAKE2("ShibRequestSetting", (config_fn_t)shib_table_set, nullptr,
2058         OR_AUTHCFG, "Set arbitrary Shibboleth request property for content"),
2059
2060     AP_INIT_FLAG("ShibDisable", (config_fn_t)ap_set_flag_slot,
2061         (void *) offsetof (shib_dir_config, bOff),
2062         OR_AUTHCFG, "Disable all Shib module activity here to save processing effort"),
2063     AP_INIT_TAKE1("ShibApplicationId", (config_fn_t)ap_set_string_slot,
2064         (void *) offsetof (shib_dir_config, szApplicationId),
2065         OR_AUTHCFG, "Set Shibboleth applicationId property for content"),
2066     AP_INIT_FLAG("ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
2067         (void *) offsetof (shib_dir_config, bBasicHijack),
2068         OR_AUTHCFG, "(DEPRECATED) Respond to AuthType Basic and convert to shibboleth"),
2069     AP_INIT_FLAG("ShibRequireSession", (config_fn_t)ap_set_flag_slot,
2070         (void *) offsetof (shib_dir_config, bRequireSession),
2071         OR_AUTHCFG, "Initiates a new session if one does not exist"),
2072     AP_INIT_TAKE1("ShibRequireSessionWith", (config_fn_t)ap_set_string_slot,
2073         (void *) offsetof (shib_dir_config, szRequireWith),
2074         OR_AUTHCFG, "Initiates a new session if one does not exist using a specific SessionInitiator"),
2075     AP_INIT_FLAG("ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
2076         (void *) offsetof (shib_dir_config, bExportAssertion),
2077         OR_AUTHCFG, "Export SAML attribute assertion(s) to Shib-Attributes header"),
2078     AP_INIT_TAKE1("ShibRedirectToSSL", (config_fn_t)ap_set_string_slot,
2079         (void *) offsetof (shib_dir_config, szRedirectToSSL),
2080         OR_AUTHCFG, "Redirect non-SSL requests to designated port"),
2081 #ifdef SHIB_APACHE_24
2082     AP_INIT_FLAG("ShibRequestMapperAuthz", (config_fn_t)ap_set_flag_slot,
2083         (void *) offsetof (shib_dir_config, bRequestMapperAuthz),
2084         OR_AUTHCFG, "Support access control via shibboleth2.xml / RequestMapper"),
2085 #else
2086     AP_INIT_TAKE1("AuthGroupFile", (config_fn_t)shib_ap_set_file_slot,
2087         (void *) offsetof (shib_dir_config, szAuthGrpFile),
2088         OR_AUTHCFG, "Text file containing group names and member user IDs"),
2089     AP_INIT_TAKE1("ShibAccessControl", (config_fn_t)shib_set_acl_slot, nullptr,
2090         OR_AUTHCFG, "Set arbitrary Shibboleth access control plugin for content"),
2091     AP_INIT_FLAG("ShibRequireAll", (config_fn_t)ap_set_flag_slot,
2092         (void *) offsetof (shib_dir_config, bRequireAll),
2093         OR_AUTHCFG, "All require directives must match"),
2094     AP_INIT_FLAG("AuthzShibAuthoritative", (config_fn_t)ap_set_flag_slot,
2095         (void *) offsetof (shib_dir_config, bAuthoritative),
2096         OR_AUTHCFG, "Allow failed mod_shib htaccess authorization to fall through to other modules"),
2097     AP_INIT_FLAG("ShibCompatWith24", (config_fn_t)ap_set_flag_slot,
2098         (void *) offsetof (shib_dir_config, bCompatWith24),
2099         OR_AUTHCFG, "Support Apache 2.4-style require rules"),
2100 #endif
2101     AP_INIT_FLAG("ShibUseEnvironment", (config_fn_t)ap_set_flag_slot,
2102         (void *) offsetof (shib_dir_config, bUseEnvVars),
2103         OR_AUTHCFG, "Export attributes using environment variables (default)"),
2104     AP_INIT_FLAG("ShibUseHeaders", (config_fn_t)ap_set_flag_slot,
2105         (void *) offsetof (shib_dir_config, bUseHeaders),
2106         OR_AUTHCFG, "Export attributes using custom HTTP headers"),
2107     AP_INIT_FLAG("ShibExpireRedirects", (config_fn_t)ap_set_flag_slot,
2108         (void *) offsetof (shib_dir_config, bExpireRedirects),
2109         OR_AUTHCFG, "Expire SP-generated redirects"),
2110
2111     {nullptr}
2112 };
2113
2114 module AP_MODULE_DECLARE_DATA mod_shib = {
2115     STANDARD20_MODULE_STUFF,
2116     create_shib_dir_config,     /* create dir config */
2117     merge_shib_dir_config,      /* merge dir config --- default is to override */
2118     create_shib_server_config,  /* create server config */
2119     merge_shib_server_config,   /* merge server config */
2120     shib_cmds,                  /* command table */
2121     shib_register_hooks         /* register hooks */
2122 };
2123
2124 #endif
2125
2126 }