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