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