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