https://issues.shibboleth.net/jira/browse/SSPCPP-168
[shibboleth/cpp-sp.git] / apache / mod_apache.cpp
1 /*
2  *  Copyright 2001-2005 Internet2
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * mod_apache.cpp -- the core Apache Module code
19  *
20  * Created by:  Derek Atkins <derek@ihtfp.com>
21  *
22  * $Id$
23  */
24
25 #ifdef SOLARIS2
26 #undef _XOPEN_SOURCE    // causes gethostname conflict in unistd.h
27 #endif
28
29 // SAML Runtime
30 #include <saml/saml.h>
31 #include <shib/shib.h>
32 #include <shib/shib-threads.h>
33 #include <shib-target/shib-target.h>
34 #include <xercesc/util/regx/RegularExpression.hpp>
35
36 #undef _XPG4_2
37
38 // Apache specific header files
39 #include <httpd.h>
40 #include <http_config.h>
41 #include <http_protocol.h>
42 #include <http_main.h>
43 #define CORE_PRIVATE
44 #include <http_core.h>
45 #include <http_log.h>
46 #include <http_request.h>
47
48 #ifndef SHIB_APACHE_13
49 #include <apr_strings.h>
50 #include <apr_pools.h>
51 #endif
52
53 #include <fstream>
54 #include <sstream>
55
56 #ifdef HAVE_UNISTD_H
57 #include <unistd.h>             // for getpid()
58 #endif
59
60 using namespace shibtarget;
61 using namespace shibboleth;
62 using namespace saml;
63 using namespace std;
64
65 extern "C" module MODULE_VAR_EXPORT mod_shib;
66
67 namespace {
68     char* g_szSHIBConfig = NULL;
69     char* g_szSchemaDir = NULL;
70     ShibTargetConfig* g_Config = NULL;
71     string g_unsetHeaderValue;
72     bool g_checkSpoofing = true;
73     bool g_catchAll = true;
74     static const char* g_UserDataKey = "_shib_check_user_";
75 }
76
77 /* Apache 2.2.x headers must be accumulated and set in the output filter.
78    Apache 2.0.49+ supports the filter method.
79    Apache 1.3.x and lesser 2.0.x must write the headers directly. */
80
81 #if (defined(SHIB_APACHE_20) || defined(SHIB_APACHE_22)) && AP_MODULE_MAGIC_AT_LEAST(20020903,6)
82 #define SHIB_DEFERRED_HEADERS
83 #endif
84
85 /********************************************************************************/
86 // Basic Apache Configuration code.
87 //
88
89 // per-server module configuration structure
90 struct shib_server_config
91 {
92     char* szScheme;
93 };
94
95 // creates the per-server configuration
96 extern "C" void* create_shib_server_config(SH_AP_POOL* p, server_rec* s)
97 {
98     shib_server_config* sc=(shib_server_config*)ap_pcalloc(p,sizeof(shib_server_config));
99     sc->szScheme = NULL;
100     return sc;
101 }
102
103 // overrides server configuration in virtual servers
104 extern "C" void* merge_shib_server_config (SH_AP_POOL* p, void* base, void* sub)
105 {
106     shib_server_config* sc=(shib_server_config*)ap_pcalloc(p,sizeof(shib_server_config));
107     shib_server_config* parent=(shib_server_config*)base;
108     shib_server_config* child=(shib_server_config*)sub;
109
110     if (child->szScheme)
111         sc->szScheme=ap_pstrdup(p,child->szScheme);
112     else if (parent->szScheme)
113         sc->szScheme=ap_pstrdup(p,parent->szScheme);
114     else
115         sc->szScheme=NULL;
116
117     return sc;
118 }
119
120 // per-dir module configuration structure
121 struct shib_dir_config
122 {
123     // RM Configuration
124     char* szAuthGrpFile;    // Auth GroupFile name
125     int bRequireAll;        // all require directives must match, otherwise OR logic
126
127     // Content Configuration
128     char* szApplicationId;  // Shib applicationId value
129     char* szRequireWith;    // require a session using a specific initiator?
130     char* szRedirectToSSL;  // redirect non-SSL requests to SSL port
131     int bOff;               // flat-out disable all Shib processing
132     int bBasicHijack;       // activate for AuthType Basic?
133     int bRequireSession;    // require a session?
134     int bExportAssertion;   // export SAML assertion to the environment?
135     int bUseEnvVars;        // use environment variables?
136     int bUseHeaders;        // use HTTP headers?
137 };
138
139 // creates per-directory config structure
140 extern "C" void* create_shib_dir_config (SH_AP_POOL* p, char* d)
141 {
142     shib_dir_config* dc=(shib_dir_config*)ap_pcalloc(p,sizeof(shib_dir_config));
143     dc->bOff = -1;
144     dc->bBasicHijack = -1;
145     dc->bRequireSession = -1;
146     dc->bExportAssertion = -1;
147     dc->bRequireAll = -1;
148     dc->szRedirectToSSL = NULL;
149     dc->szAuthGrpFile = NULL;
150     dc->szApplicationId = NULL;
151     dc->szRequireWith = NULL;
152     dc->bUseEnvVars = -1;
153     dc->bUseHeaders = -1;
154     return dc;
155 }
156
157 // overrides server configuration in directories
158 extern "C" void* merge_shib_dir_config (SH_AP_POOL* p, void* base, void* sub)
159 {
160     shib_dir_config* dc=(shib_dir_config*)ap_pcalloc(p,sizeof(shib_dir_config));
161     shib_dir_config* parent=(shib_dir_config*)base;
162     shib_dir_config* child=(shib_dir_config*)sub;
163
164     if (child->szAuthGrpFile)
165         dc->szAuthGrpFile=ap_pstrdup(p,child->szAuthGrpFile);
166     else if (parent->szAuthGrpFile)
167         dc->szAuthGrpFile=ap_pstrdup(p,parent->szAuthGrpFile);
168     else
169         dc->szAuthGrpFile=NULL;
170
171     if (child->szApplicationId)
172         dc->szApplicationId=ap_pstrdup(p,child->szApplicationId);
173     else if (parent->szApplicationId)
174         dc->szApplicationId=ap_pstrdup(p,parent->szApplicationId);
175     else
176         dc->szApplicationId=NULL;
177
178     if (child->szRequireWith)
179         dc->szRequireWith=ap_pstrdup(p,child->szRequireWith);
180     else if (parent->szRequireWith)
181         dc->szRequireWith=ap_pstrdup(p,parent->szRequireWith);
182     else
183         dc->szRequireWith=NULL;
184
185     if (child->szRedirectToSSL)
186         dc->szRedirectToSSL=ap_pstrdup(p,child->szRedirectToSSL);
187     else if (parent->szRedirectToSSL)
188         dc->szRedirectToSSL=ap_pstrdup(p,parent->szRedirectToSSL);
189     else
190         dc->szRedirectToSSL=NULL;
191
192     dc->bOff=((child->bOff==-1) ? parent->bOff : child->bOff);
193     dc->bBasicHijack=((child->bBasicHijack==-1) ? parent->bBasicHijack : child->bBasicHijack);
194     dc->bRequireSession=((child->bRequireSession==-1) ? parent->bRequireSession : child->bRequireSession);
195     dc->bExportAssertion=((child->bExportAssertion==-1) ? parent->bExportAssertion : child->bExportAssertion);
196     dc->bRequireAll=((child->bRequireAll==-1) ? parent->bRequireAll : child->bRequireAll);
197     dc->bUseEnvVars=((child->bUseEnvVars==-1) ? parent->bUseEnvVars : child->bUseEnvVars);
198     dc->bUseHeaders=((child->bUseHeaders==-1) ? parent->bUseHeaders : child->bUseHeaders);
199     return dc;
200 }
201
202 // per-request module structure
203 struct shib_request_config
204 {
205     SH_AP_TABLE *env;        // environment vars
206 #ifdef SHIB_DEFERRED_HEADERS
207     SH_AP_TABLE *hdr_out;    // headers to browser
208 #endif
209 };
210
211 // create a request record
212 static shib_request_config *init_request_config(request_rec *r)
213 {
214     shib_request_config* rc=(shib_request_config*)ap_pcalloc(r->pool,sizeof(shib_request_config));
215     ap_set_module_config (r->request_config, &mod_shib, rc);
216     memset(rc, 0, sizeof(shib_request_config));
217     //ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_init_rc\n");
218     return rc;
219 }
220
221 // generic global slot handlers
222 extern "C" const char* ap_set_global_string_slot(cmd_parms* parms, void*, const char* arg)
223 {
224     *((char**)(parms->info))=ap_pstrdup(parms->pool,arg);
225     return NULL;
226 }
227
228 extern "C" const char* shib_set_server_string_slot(cmd_parms* parms, void*, const char* arg)
229 {
230     char* base=(char*)ap_get_module_config(parms->server->module_config,&mod_shib);
231     long offset=(long)parms->info;
232     *((char**)(base + offset))=ap_pstrdup(parms->pool,arg);
233     return NULL;
234 }
235
236 extern "C" const char* shib_ap_set_file_slot(cmd_parms* parms,
237 #ifdef SHIB_APACHE_13
238                                              char* arg1, char* arg2
239 #else
240                                              void* arg1, const char* arg2
241 #endif
242                                              )
243 {
244   ap_set_file_slot(parms, arg1, arg2);
245   return DECLINE_CMD;
246 }
247
248 /********************************************************************************/
249 // Apache ShibTarget subclass(es) here.
250
251 class ShibTargetApache : public ShibTarget
252 {
253 public:
254   ShibTargetApache(request_rec* req, bool handler) : m_handler(handler) {
255     m_sc = (shib_server_config*)ap_get_module_config(req->server->module_config, &mod_shib);
256     m_dc = (shib_dir_config*)ap_get_module_config(req->per_dir_config, &mod_shib);
257     m_rc = (shib_request_config*)ap_get_module_config(req->request_config, &mod_shib);
258
259     init(
260         m_sc->szScheme ? m_sc->szScheme : ap_http_method(req),
261             ap_get_server_name(req),
262         (int)ap_get_server_port(req),
263             req->unparsed_uri,
264         ap_table_get(req->headers_in, "Content-type"),
265             req->connection->remote_ip,
266         req->method
267         );
268
269     m_req = req;
270   }
271   ~ShibTargetApache() { }
272
273   virtual void log(ShibLogLevel level, const string &msg) {
274     ShibTarget::log(level,msg);
275 #ifdef SHIB_APACHE_13
276     ap_log_rerror(APLOG_MARK,
277         (level == LogLevelDebug ? APLOG_DEBUG :
278             (level == LogLevelInfo ? APLOG_INFO :
279             (level == LogLevelWarn ? APLOG_WARNING : APLOG_ERR)))|APLOG_NOERRNO, SH_AP_R(m_req), msg.c_str());
280 #else
281     if (level == LogLevelError)
282         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(m_req), msg.c_str());
283 #endif
284   }
285   virtual string getCookies(void) const {
286     const char *c = ap_table_get(m_req->headers_in, "Cookie");
287     return string(c ? c : "");
288   }
289   virtual void setCookie(const string &name, const string &value) {
290     char* val = ap_psprintf(m_req->pool, "%s=%s", name.c_str(), value.c_str());
291 #ifdef SHIB_DEFERRED_HEADERS
292     if (!m_rc) {
293       // this happens on subrequests
294       ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_setheader: no_m_rc");
295       m_rc = init_request_config(m_req);
296     }
297     if (m_handler) {
298         if (!m_rc->hdr_out)
299             m_rc->hdr_out = ap_make_table(m_req->pool, 5);
300         ap_table_addn(m_rc->hdr_out, "Set-Cookie", val);
301     }
302     else
303 #endif
304     ap_table_addn(m_req->err_headers_out, "Set-Cookie", val);
305   }
306   virtual string getArgs(void) { return string(m_req->args ? m_req->args : ""); }
307   virtual string getPostData(void) {
308     // Read the posted data
309 #ifdef SHIB_APACHE_13
310     if (ap_setup_client_block(m_req, REQUEST_CHUNKED_DECHUNK))
311         throw FatalProfileException("Apache function (setup_client_block) failed while reading profile submission.");
312     if (!ap_should_client_block(m_req))
313         throw FatalProfileException("Apache function (should_client_block) failed while reading profile submission.");
314     if (m_req->remaining > 1024*1024)
315         throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
316     int len;
317     string cgistr;
318     char buff[HUGE_STRING_LEN];
319     ap_hard_timeout("[mod_shib] getPostData", m_req);
320     while ((len=ap_get_client_block(m_req, buff, sizeof(buff))) > 0) {
321       ap_reset_timeout(m_req);
322       cgistr.append(buff, len);
323     }
324     ap_kill_timeout(m_req);
325     return cgistr;
326 #else
327     string cgistr;
328     const char *data;
329     apr_size_t len;
330     int seen_eos = 0;
331     apr_bucket_brigade* bb = apr_brigade_create(m_req->pool, m_req->connection->bucket_alloc);
332     do {
333         apr_bucket *bucket;
334         apr_status_t rv = ap_get_brigade(m_req->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN);
335         if (rv != APR_SUCCESS)
336             throw FatalProfileException("Apache function (ap_get_brigade) failed while reading profile submission.");
337
338         for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); bucket = APR_BUCKET_NEXT(bucket)) {
339             if (APR_BUCKET_IS_EOS(bucket)) {
340                 seen_eos = 1;
341                 break;
342             }
343
344             /* We can't do much with this. */
345             if (APR_BUCKET_IS_FLUSH(bucket))
346                 continue;
347
348             /* read */
349             apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
350             if (len > 0)
351                 cgistr.append(data, len);
352         }
353         apr_brigade_cleanup(bb);
354     } while (!seen_eos);
355     apr_brigade_destroy(bb);
356     return cgistr;
357 #endif
358   }
359   virtual void clearHeader(const string &name) {
360     if (m_dc->bUseEnvVars == 1) {
361         // ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_clear_header: env");
362         if (m_rc && m_rc->env) ap_table_unset(m_rc->env, name.c_str());
363     }
364     if (m_dc->bUseHeaders != 0) {
365         // ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_clear_header: hdr");
366         if (g_checkSpoofing && ap_is_initial_req(m_req)) {
367             if (m_allhttp.empty()) {
368                 // First time, so populate set with "CGI" versions of client-supplied headers.
369 #ifdef SHIB_APACHE_13
370                 array_header *hdrs_arr = ap_table_elts(m_req->headers_in);
371                 table_entry *hdrs = (table_entry *) hdrs_arr->elts;
372 #else
373                 const apr_array_header_t *hdrs_arr = apr_table_elts(m_req->headers_in);
374                 const apr_table_entry_t *hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
375 #endif
376                 for (int i = 0; i < hdrs_arr->nelts; ++i) {
377                     if (!hdrs[i].key)
378                         continue;
379                     string cgiversion("HTTP_");
380                     const char* pch = hdrs[i].key;
381                     while (*pch) {
382                         cgiversion += (isalnum(*pch) ? toupper(*pch) : '_');
383                         pch++;
384                     }
385                     m_allhttp.insert(cgiversion);
386                 }
387             }
388
389             // Map to the expected CGI variable name.
390             string transformed("HTTP_");
391             const char* pch = name.c_str();
392             while (*pch) {
393                 transformed += (isalnum(*pch) ? toupper(*pch) : '_');
394                 pch++;
395             }
396             if (m_allhttp.count(transformed) > 0)
397                 throw SAMLException("Attempt to spoof header ($1) was detected.", params(1, name.c_str()));
398         }
399
400         ap_table_unset(m_req->headers_in, name.c_str());
401         ap_table_set(m_req->headers_in, name.c_str(), g_unsetHeaderValue.c_str());
402     }
403   }
404   virtual void setHeader(const string &name, const string &value) {
405     if (m_dc->bUseEnvVars == 1) {
406        if (!m_rc) {
407           // this happens on subrequests
408           ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_setheader: no_m_rc");
409           m_rc = init_request_config(m_req);
410        }
411        if (!m_rc->env)
412            m_rc->env = ap_make_table(m_req->pool, 10);
413        //ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_set_env: %s=%s", name.c_str(), value.c_str()?value.c_str():"Null");
414        ap_table_set(m_rc->env, name.c_str(), value.c_str() ? value.c_str() : "");
415     }
416     if (m_dc->bUseHeaders != 0) {
417        //ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_set_hdr: %s=%s", name.c_str(), value.c_str()?value.c_str():"Null");
418        ap_table_set(m_req->headers_in, name.c_str(), value.c_str() ? value.c_str() : "");
419     }
420   }
421   virtual string getHeader(const string &name) {
422     const char *hdr;
423     if (m_dc->bUseEnvVars == 1) {
424        if (m_rc && m_rc->env)
425            hdr = ap_table_get(m_rc->env, name.c_str());
426        else
427            hdr = NULL;
428        //ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_get_hdr_env: %s=%s", name.c_str(), hdr?hdr:"NULL");
429     }
430     else {
431        hdr = ap_table_get(m_req->headers_in, name.c_str());
432        //ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(m_req), "shib_get_hdr: %s=%s", name.c_str(), hdr?hdr:"NULL");
433     }
434     return string(hdr ? hdr : "");
435   }
436   virtual void setRemoteUser(const string &user) {
437     SH_AP_USER(m_req) = ap_pstrdup(m_req->pool, user.c_str());
438   }
439   virtual string getRemoteUser(void) {
440     return string(SH_AP_USER(m_req) ? SH_AP_USER(m_req) : "");
441   }
442   virtual void* sendPage(
443     const string& msg,
444     int code=200,
445     const string& content_type="text/html",
446         const Iterator<header_t>& headers=EMPTY(header_t)
447     ) {
448     m_req->content_type = ap_psprintf(m_req->pool, content_type.c_str());
449     while (headers.hasNext()) {
450         const header_t& h=headers.next();
451         ap_table_set(m_req->headers_out, h.first.c_str(), h.second.c_str());
452     }
453     ap_send_http_header(m_req);
454     ap_rprintf(m_req, msg.c_str());
455     return (void*)((code==200) ? DONE : code);
456   }
457   virtual void* sendRedirect(const string& url) {
458     ap_table_set(m_req->headers_out, "Location", url.c_str());
459     return (void*)REDIRECT;
460   }
461   virtual void* returnDecline(void) { return (void*)DECLINED; }
462   virtual void* returnOK(void) { return (void*)OK; }
463
464   bool m_handler;
465   request_rec* m_req;
466   shib_dir_config* m_dc;
467   shib_server_config* m_sc;
468   shib_request_config* m_rc;
469   set<string> m_allhttp;
470 };
471
472 /********************************************************************************/
473 // Apache handlers
474
475 extern "C" int shib_check_user(request_rec* r)
476 {
477     // Short-circuit entirely?
478     if (((shib_dir_config*)ap_get_module_config(r->per_dir_config, &mod_shib))->bOff==1)
479         return DECLINED;
480
481     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_check_user(%d): ENTER", (int)getpid());
482
483     ostringstream threadid;
484     threadid << "[" << getpid() << "] shib_check_user" << '\0';
485     saml::NDC ndc(threadid.str().c_str());
486
487     try {
488         ShibTargetApache sta(r, false);
489
490         // Check user authentication and export information, then set the handler bypass
491         pair<bool,void*> res = sta.doCheckAuthN(true);
492         apr_pool_userdata_setn((const void*)42,g_UserDataKey,NULL,r->pool);
493         if (res.first) return (int)(long)res.second;
494
495         // user auth was okay -- export the assertions now
496         res = sta.doExportAssertions();
497         if (res.first) return (int)(long)res.second;
498
499         // export happened successfully..  this user is ok.
500         return OK;
501     }
502     catch (exception& e) {
503         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_check_user threw an exception: %s", e.what());
504         return SERVER_ERROR;
505     }
506     catch (...) {
507         if (g_catchAll) {
508             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_check_user threw an uncaught exception!");
509             return SERVER_ERROR;
510         }
511         throw;
512     }
513 }
514
515 extern "C" int shib_handler(request_rec* r)
516 {
517     // Short-circuit entirely?
518     if (((shib_dir_config*)ap_get_module_config(r->per_dir_config, &mod_shib))->bOff==1)
519         return DECLINED;
520
521     ostringstream threadid;
522     threadid << "[" << getpid() << "] shib_handler" << '\0';
523     saml::NDC ndc(threadid.str().c_str());
524
525 #ifndef SHIB_APACHE_13
526     // With 2.x, this handler always runs, though last.
527     // We check if shib_check_user ran, because it will detect a handler request
528     // and dispatch it directly.
529     void* data;
530     apr_pool_userdata_get(&data,g_UserDataKey,r->pool);
531     if (data==(const void*)42) {
532         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_handler skipped since check_user ran");
533         return DECLINED;
534     }
535 #endif
536
537     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_handler(%d): ENTER: %s", (int)getpid(), r->handler);
538
539     try {
540         ShibTargetApache sta(r, true);
541
542         pair<bool,void*> res = sta.doHandler();
543         if (res.first) return (int)(long)res.second;
544
545         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "doHandler() did not do anything.");
546         return SERVER_ERROR;
547     }
548     catch (exception& e) {
549         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_handler threw an exception: %s", e.what());
550         return SERVER_ERROR;
551     }
552     catch (...) {
553         if (g_catchAll) {
554             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_handler threw an uncaught exception!");
555             return SERVER_ERROR;
556         }
557         throw;
558     }
559 }
560
561 /*
562  * shib_auth_checker() -- a simple resource manager to
563  * process the .htaccess settings
564  */
565 extern "C" int shib_auth_checker(request_rec* r)
566 {
567     // Short-circuit entirely?
568     if (((shib_dir_config*)ap_get_module_config(r->per_dir_config, &mod_shib))->bOff==1)
569         return DECLINED;
570
571     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_auth_checker(%d): ENTER", (int)getpid());
572
573     ostringstream threadid;
574     threadid << "[" << getpid() << "] shib_auth_checker" << '\0';
575     saml::NDC ndc(threadid.str().c_str());
576
577     try {
578         ShibTargetApache sta(r, false);
579
580         pair<bool,void*> res = sta.doCheckAuthZ();
581         if (res.first) return (int)(long)res.second;
582
583         // We're all okay.
584         return OK;
585     }
586     catch (exception& e) {
587         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_auth_checker threw an exception: %s", e.what());
588         return SERVER_ERROR;
589     }
590     catch (...) {
591         if (g_catchAll) {
592             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_auth_checker threw an uncaught exception!");
593             return SERVER_ERROR;
594         }
595         throw;
596     }
597 }
598
599 // Access control plugin that enforces htaccess rules
600 class htAccessControl : virtual public IAccessControl
601 {
602 public:
603     htAccessControl() {}
604     ~htAccessControl() {}
605     void lock() {}
606     void unlock() {}
607     bool authorized(
608         ShibTarget* st,
609         ISessionCacheEntry* entry
610     ) const;
611 };
612
613 IPlugIn* htAccessFactory(const DOMElement* e)
614 {
615     return new htAccessControl();
616 }
617
618 class ApacheRequestMapper : public virtual IRequestMapper, public virtual IPropertySet
619 {
620 public:
621     ApacheRequestMapper(const DOMElement* e);
622     ~ApacheRequestMapper() { delete m_mapper; delete m_htaccess; delete m_staKey; delete m_propsKey; }
623     void lock() { m_mapper->lock(); }
624     void unlock() { m_staKey->setData(NULL); m_propsKey->setData(NULL); m_mapper->unlock(); }
625     Settings getSettings(ShibTarget* st) const;
626
627     pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
628     pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
629     pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
630     pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;
631     pair<bool,int> getInt(const char* name, const char* ns=NULL) const;
632     const IPropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:target:config:1.0") const;
633     const DOMElement* getElement() const;
634
635 private:
636     IRequestMapper* m_mapper;
637     ThreadKey* m_staKey;
638     ThreadKey* m_propsKey;
639     IAccessControl* m_htaccess;
640 };
641
642 IPlugIn* ApacheRequestMapFactory(const DOMElement* e)
643 {
644     return new ApacheRequestMapper(e);
645 }
646
647 ApacheRequestMapper::ApacheRequestMapper(const DOMElement* e) : m_mapper(NULL), m_staKey(NULL), m_propsKey(NULL), m_htaccess(NULL)
648 {
649     IPlugIn* p=saml::SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::XMLRequestMapType,e);
650     m_mapper=dynamic_cast<IRequestMapper*>(p);
651     if (!m_mapper) {
652         delete p;
653         throw UnsupportedExtensionException("Embedded request mapper plugin was not of correct type.");
654     }
655     m_htaccess=new htAccessControl();
656     m_staKey=ThreadKey::create(NULL);
657     m_propsKey=ThreadKey::create(NULL);
658 }
659
660 IRequestMapper::Settings ApacheRequestMapper::getSettings(ShibTarget* st) const
661 {
662     Settings s=m_mapper->getSettings(st);
663     m_staKey->setData(dynamic_cast<ShibTargetApache*>(st));
664     m_propsKey->setData((void*)s.first);
665     return pair<const IPropertySet*,IAccessControl*>(this,s.second ? s.second : m_htaccess);
666 }
667
668 pair<bool,bool> ApacheRequestMapper::getBool(const char* name, const char* ns) const
669 {
670     ShibTargetApache* sta=reinterpret_cast<ShibTargetApache*>(m_staKey->getData());
671     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
672     if (sta && !ns) {
673         // Override Apache-settable boolean properties.
674         if (name && !strcmp(name,"requireSession") && sta->m_dc->bRequireSession != -1)
675             return make_pair(true, sta->m_dc->bRequireSession==1);
676         else if (name && !strcmp(name,"exportAssertion") && sta->m_dc->bExportAssertion != -1)
677             return make_pair(true, sta->m_dc->bExportAssertion==1);
678     }
679     return s ? s->getBool(name,ns) : make_pair(false,false);
680 }
681
682 pair<bool,const char*> ApacheRequestMapper::getString(const char* name, const char* ns) const
683 {
684     ShibTargetApache* sta=reinterpret_cast<ShibTargetApache*>(m_staKey->getData());
685     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
686     if (sta && !ns) {
687         // Override Apache-settable string properties.
688         if (name && !strcmp(name,"authType")) {
689             const char *auth_type=ap_auth_type(sta->m_req);
690             if (auth_type) {
691                 // Check for Basic Hijack
692                 if (!strcasecmp(auth_type, "basic") && sta->m_dc->bBasicHijack == 1)
693                     auth_type = "shibboleth";
694                 return make_pair(true,auth_type);
695             }
696         }
697         else if (name && !strcmp(name,"applicationId") && sta->m_dc->szApplicationId)
698             return pair<bool,const char*>(true,sta->m_dc->szApplicationId);
699         else if (name && !strcmp(name,"requireSessionWith") && sta->m_dc->szRequireWith)
700             return pair<bool,const char*>(true,sta->m_dc->szRequireWith);
701         else if (name && !strcmp(name,"redirectToSSL") && sta->m_dc->szRedirectToSSL)
702             return pair<bool,const char*>(true,sta->m_dc->szRedirectToSSL);
703     }
704     return s ? s->getString(name,ns) : pair<bool,const char*>(false,NULL);
705 }
706
707 pair<bool,const XMLCh*> ApacheRequestMapper::getXMLString(const char* name, const char* ns) const
708 {
709     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
710     return s ? s->getXMLString(name,ns) : pair<bool,const XMLCh*>(false,NULL);
711 }
712
713 pair<bool,unsigned int> ApacheRequestMapper::getUnsignedInt(const char* name, const char* ns) const
714 {
715     ShibTargetApache* sta=reinterpret_cast<ShibTargetApache*>(m_staKey->getData());
716     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
717     if (sta && !ns) {
718         // Override Apache-settable int properties.
719         if (name && !strcmp(name,"redirectToSSL") && sta->m_dc->szRedirectToSSL)
720             return pair<bool,unsigned int>(true,strtol(sta->m_dc->szRedirectToSSL,NULL,10));
721     }
722     return s ? s->getUnsignedInt(name,ns) : pair<bool,unsigned int>(false,0);
723 }
724
725 pair<bool,int> ApacheRequestMapper::getInt(const char* name, const char* ns) const
726 {
727     ShibTargetApache* sta=reinterpret_cast<ShibTargetApache*>(m_staKey->getData());
728     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
729     if (sta && !ns) {
730         // Override Apache-settable int properties.
731         if (name && !strcmp(name,"redirectToSSL") && sta->m_dc->szRedirectToSSL)
732             return pair<bool,int>(true,atoi(sta->m_dc->szRedirectToSSL));
733     }
734     return s ? s->getInt(name,ns) : pair<bool,int>(false,0);
735 }
736
737 const IPropertySet* ApacheRequestMapper::getPropertySet(const char* name, const char* ns) const
738 {
739     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
740     return s ? s->getPropertySet(name,ns) : NULL;
741 }
742
743 const DOMElement* ApacheRequestMapper::getElement() const
744 {
745     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
746     return s ? s->getElement() : NULL;
747 }
748
749 static SH_AP_TABLE* groups_for_user(request_rec* r, const char* user, char* grpfile)
750 {
751     SH_AP_CONFIGFILE* f;
752     SH_AP_TABLE* grps=ap_make_table(r->pool,15);
753     char l[MAX_STRING_LEN];
754     const char *group_name, *ll, *w;
755
756 #ifdef SHIB_APACHE_13
757     if (!(f=ap_pcfg_openfile(r->pool,grpfile))) {
758 #else
759     if (ap_pcfg_openfile(&f,r->pool,grpfile) != APR_SUCCESS) {
760 #endif
761         ap_log_rerror(APLOG_MARK,APLOG_DEBUG,SH_AP_R(r),"groups_for_user() could not open group file: %s",grpfile);
762         return NULL;
763     }
764
765     SH_AP_POOL* sp;
766 #ifdef SHIB_APACHE_13
767     sp=ap_make_sub_pool(r->pool);
768 #else
769     if (apr_pool_create(&sp,r->pool) != APR_SUCCESS) {
770         ap_log_rerror(APLOG_MARK,APLOG_ERR,0,r,
771             "groups_for_user() could not create a subpool");
772         return NULL;
773     }
774 #endif
775
776     while (!(ap_cfg_getline(l,MAX_STRING_LEN,f))) {
777         if ((*l=='#') || (!*l))
778             continue;
779         ll = l;
780         ap_clear_pool(sp);
781
782         group_name=ap_getword(sp,&ll,':');
783
784         while (*ll) {
785             w=ap_getword_conf(sp,&ll);
786             if (!strcmp(w,user)) {
787                 ap_table_setn(grps,ap_pstrdup(r->pool,group_name),"in");
788                 break;
789             }
790         }
791     }
792     ap_cfg_closefile(f);
793     ap_destroy_pool(sp);
794     return grps;
795 }
796
797 bool htAccessControl::authorized(
798     ShibTarget* st,
799     ISessionCacheEntry* entry
800 ) const
801 {
802     // Make sure the object is our type.
803     ShibTargetApache* sta=dynamic_cast<ShibTargetApache*>(st);
804     if (!sta)
805         throw ConfigurationException("Request wrapper object was not of correct type.");
806
807     // mod_auth clone
808
809     int m=sta->m_req->method_number;
810     bool method_restricted=false;
811     const char *t, *w;
812
813     const array_header* reqs_arr=ap_requires(sta->m_req);
814     if (!reqs_arr)
815         return true;
816
817     require_line* reqs=(require_line*)reqs_arr->elts;
818
819     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(sta->m_req),"REQUIRE nelts: %d", reqs_arr->nelts);
820     ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(sta->m_req),"REQUIRE all: %d", sta->m_dc->bRequireAll);
821
822     vector<bool> auth_OK(reqs_arr->nelts,false);
823
824 #define SHIB_AP_CHECK_IS_OK {           \
825      if (sta->m_dc->bRequireAll < 1)    \
826          return true;                   \
827      auth_OK[x] = true;                 \
828      continue;                          \
829 }
830
831     for (int x=0; x<reqs_arr->nelts; x++) {
832         auth_OK[x] = false;
833         if (!(reqs[x].method_mask & (1 << m)))
834             continue;
835         method_restricted=true;
836         string remote_user = st->getRemoteUser();
837
838         t = reqs[x].requirement;
839         w = ap_getword_white(sta->m_req->pool, &t);
840
841         if (!strcasecmp(w,"shibboleth")) {
842             // This is a dummy rule needed because Apache conflates authn and authz.
843             // Without some require rule, AuthType is ignored and no check_user hooks run.
844             SHIB_AP_CHECK_IS_OK;
845         }
846         else if (!strcmp(w,"valid-user")) {
847             if (entry) {
848                 st->log(ShibTarget::LogLevelDebug,"htAccessControl plugin accepting valid-user based on active session");
849                 SHIB_AP_CHECK_IS_OK;
850             }
851             else
852                 st->log(ShibTarget::LogLevelError,"htAccessControl plugin rejecting access for valid-user rule, no session is active");
853         }
854         else if (!strcmp(w,"user") && !remote_user.empty()) {
855             bool regexp=false;
856             while (*t) {
857                 w=ap_getword_conf(sta->m_req->pool,&t);
858                 if (*w=='~') {
859                     regexp=true;
860                     continue;
861                 }
862
863                 if (regexp) {
864                     try {
865                         // To do regex matching, we have to convert from UTF-8.
866                         auto_ptr<XMLCh> trans(fromUTF8(w));
867                         RegularExpression re(trans.get());
868                         auto_ptr<XMLCh> trans2(fromUTF8(remote_user.c_str()));
869                         if (re.matches(trans2.get())) {
870                             st->log(ShibTarget::LogLevelDebug, string("htAccessControl plugin accepting user (") + w + ")");
871                             SHIB_AP_CHECK_IS_OK;
872                         }
873                     }
874                     catch (XMLException& ex) {
875                         auto_ptr_char tmp(ex.getMessage());
876                         st->log(ShibTarget::LogLevelError,
877                             string("htAccessControl plugin caught exception while parsing regular expression (") + w + "): " + tmp.get());
878                     }
879                 }
880                 else if (remote_user==w) {
881                     st->log(ShibTarget::LogLevelDebug, string("htAccessControl plugin accepting user (") + w + ")");
882                     SHIB_AP_CHECK_IS_OK;
883                 }
884             }
885         }
886         else if (!strcmp(w,"group")) {
887             SH_AP_TABLE* grpstatus=NULL;
888             if (sta->m_dc->szAuthGrpFile && !remote_user.empty()) {
889                 st->log(ShibTarget::LogLevelDebug,string("htAccessControl plugin using groups file: ") + sta->m_dc->szAuthGrpFile);
890                 grpstatus=groups_for_user(sta->m_req,remote_user.c_str(),sta->m_dc->szAuthGrpFile);
891             }
892             if (!grpstatus)
893                 return false;
894
895             while (*t) {
896                 w=ap_getword_conf(sta->m_req->pool,&t);
897                 if (ap_table_get(grpstatus,w)) {
898                     st->log(ShibTarget::LogLevelDebug, string("htAccessControl plugin accepting group (") + w + ")");
899                     SHIB_AP_CHECK_IS_OK;
900                 }
901             }
902         }
903         else {
904             Iterator<IAAP*> provs=st->getApplication()->getAAPProviders();
905             AAP wrapper(provs,w);
906             if (wrapper.fail()) {
907                 st->log(ShibTarget::LogLevelWarn, string("htAccessControl plugin didn't recognize require rule: ") + w);
908                 continue;
909             }
910
911             bool regexp=false;
912             const char* vals;
913             if (!strcmp(wrapper->getHeader(),"REMOTE_USER"))
914                 vals=remote_user.c_str();
915             else
916                 if (sta->m_dc->bUseEnvVars==1) {
917                    if (sta->m_rc && sta->m_rc->env) vals=ap_table_get(sta->m_rc->env,wrapper->getHeader());
918                    else vals = NULL;
919                 } else {
920                    vals=ap_table_get(sta->m_req->headers_in,wrapper->getHeader());
921                 }
922
923             while (*t && vals && *vals) {
924                 w=ap_getword_conf(sta->m_req->pool,&t);
925                 if (*w=='~') {
926                     regexp=true;
927                     continue;
928                 }
929
930                 try {
931                     auto_ptr<RegularExpression> re;
932                     if (regexp) {
933                         delete re.release();
934                         auto_ptr<XMLCh> trans(fromUTF8(w));
935                         auto_ptr<RegularExpression> temp(new RegularExpression(trans.get()));
936                         re=temp;
937                     }
938
939                     string vals_str(vals);
940                     unsigned int j = 0;
941                     for (unsigned int i = 0;  i < vals_str.length();  i++) {
942                         if (vals_str.at(i) == ';') {
943                             if (i == 0) {
944                                 st->log(ShibTarget::LogLevelError, string("htAccessControl plugin found invalid header encoding (") +
945                                     vals + "): starts with a semicolon");
946                                 throw SAMLException("Invalid information supplied to authorization plugin.");
947                             }
948
949                             if (vals_str.at(i-1) == '\\') {
950                                 vals_str.erase(i-1, 1);
951                                 i--;
952                                 continue;
953                             }
954
955                             string val = vals_str.substr(j, i-j);
956                             j = i+1;
957                             if (regexp) {
958                                 auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
959                                 if (re->matches(trans.get())) {
960                                     st->log(ShibTarget::LogLevelDebug, string("htAccessControl plugin expecting ") + w +
961                                        ", got " + val + ": authorization granted");
962                                     SHIB_AP_CHECK_IS_OK;
963                                 }
964                             }
965                             else if ((wrapper->getCaseSensitive() && val==w) || (!wrapper->getCaseSensitive() && !strcasecmp(val.c_str(),w))) {
966                                 st->log(ShibTarget::LogLevelDebug, string("htAccessControl plugin expecting ") + w +
967                                     ", got " + val + ": authorization granted.");
968                                 SHIB_AP_CHECK_IS_OK;
969                             }
970                             else {
971                                 st->log(ShibTarget::LogLevelDebug, string("htAccessControl plugin expecting ") + w +
972                                     ", got " + val + ": authorization not granted.");
973                             }
974                         }
975                     }
976
977                     string val = vals_str.substr(j, vals_str.length()-j);
978                     if (regexp) {
979                         auto_ptr<XMLCh> trans(fromUTF8(val.c_str()));
980                         if (re->matches(trans.get())) {
981                             st->log(ShibTarget::LogLevelDebug, string("htAccessControl plugin expecting ") + w +
982                                 ", got " + val + ": authorization granted.");
983                             SHIB_AP_CHECK_IS_OK;
984                         }
985                     }
986                     else if ((wrapper->getCaseSensitive() && val==w) || (!wrapper->getCaseSensitive() && !strcasecmp(val.c_str(),w))) {
987                         st->log(ShibTarget::LogLevelDebug, string("htAccessControl plugin expecting ") + w +
988                             ", got " + val + ": authorization granted");
989                         SHIB_AP_CHECK_IS_OK;
990                     }
991                     else {
992                             st->log(ShibTarget::LogLevelDebug, string("htAccessControl plugin expecting ") + w +
993                                 ", got " + val + ": authorization not granted");
994                     }
995                 }
996                 catch (XMLException& ex) {
997                     auto_ptr_char tmp(ex.getMessage());
998                     st->log(ShibTarget::LogLevelError, string("htAccessControl plugin caught exception while parsing regular expression (")
999                         + w + "): " + tmp.get());
1000                 }
1001             }
1002         }
1003     }
1004
1005     // check if all require directives are true
1006     bool auth_all_OK = true;
1007     for (int i= 0; i<reqs_arr->nelts; i++) {
1008         auth_all_OK &= auth_OK[i];
1009     }
1010     if (auth_all_OK || !method_restricted)
1011         return true;
1012
1013     return false;
1014 }
1015
1016 // Initial look at a request - create the per-request structure
1017 static int shib_post_read(request_rec *r)
1018 {
1019     shib_request_config* rc = init_request_config(r);
1020     //ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_post_read");
1021     return DECLINED;
1022 }
1023
1024 // fixups: set environment vars
1025
1026 extern "C" int shib_fixups(request_rec* r)
1027 {
1028   shib_request_config *rc = (shib_request_config*)ap_get_module_config(r->request_config, &mod_shib);
1029   shib_dir_config *dc = (shib_dir_config*)ap_get_module_config(r->per_dir_config, &mod_shib);
1030   if (dc->bOff==1 || dc->bUseEnvVars!=1)
1031     return DECLINED;
1032
1033   //ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_fixup(%d): ENTER", (int)getpid());
1034
1035   if (rc==NULL || rc->env==NULL || ap_is_empty_table(rc->env))
1036         return DECLINED;
1037
1038   ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_fixup adding %d vars", ap_table_elts(rc->env)->nelts);
1039   r->subprocess_env = ap_overlay_tables(r->pool, r->subprocess_env, rc->env);
1040
1041   return OK;
1042 }
1043
1044 #ifdef SHIB_APACHE_13
1045 /*
1046  * shib_child_exit()
1047  *  Cleanup the (per-process) pool info.
1048  */
1049 extern "C" void shib_child_exit(server_rec* s, SH_AP_POOL* p)
1050 {
1051     if (g_Config) {
1052         ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_exit(%d) dealing with g_Config..", (int)getpid());
1053         g_Config->shutdown();
1054         g_Config = NULL;
1055         ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_exit() done");
1056     }
1057 }
1058 #else
1059 /*
1060  * shib_exit()
1061  *  Apache 2.x doesn't allow for per-child cleanup, causes CGI forks to hang.
1062  */
1063 extern "C" apr_status_t shib_exit(void* data)
1064 {
1065     if (g_Config) {
1066         g_Config->shutdown();
1067         g_Config = NULL;
1068     }
1069     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,0,NULL,"shib_exit() done");
1070     return OK;
1071 }
1072 #endif
1073
1074 /*
1075  * shire_child_init()
1076  *  Things to do when the child process is initialized.
1077  *  (or after the configs are read in apache-2)
1078  */
1079 #ifdef SHIB_APACHE_13
1080 extern "C" void shib_child_init(server_rec* s, SH_AP_POOL* p)
1081 #else
1082 extern "C" void shib_child_init(apr_pool_t* p, server_rec* s)
1083 #endif
1084 {
1085     // Initialize runtime components.
1086
1087     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init(%d) starting", (int)getpid());
1088
1089     if (g_Config) {
1090         ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() already initialized!");
1091         exit(1);
1092     }
1093
1094     try {
1095         g_Config=&ShibTargetConfig::getConfig();
1096         g_Config->setFeatures(
1097             ShibTargetConfig::Listener |
1098             ShibTargetConfig::Metadata |
1099             ShibTargetConfig::AAP |
1100             ShibTargetConfig::RequestMapper |
1101             ShibTargetConfig::LocalExtensions |
1102             ShibTargetConfig::Logging
1103             );
1104         if (!g_Config->init(g_szSchemaDir)) {
1105             ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to initialize libraries");
1106             exit(1);
1107         }
1108         PlugManager& mgr = SAMLConfig::getConfig().getPlugMgr();
1109         mgr.regFactory(shibtarget::XML::htAccessControlType,&htAccessFactory);
1110         mgr.regFactory(shibtarget::XML::NativeRequestMapType,&ApacheRequestMapFactory);
1111         // We hijack the legacy type so that 1.2 config files will load this plugin
1112         mgr.regFactory(shibtarget::XML::LegacyRequestMapType,&ApacheRequestMapFactory);
1113
1114         if (!g_Config->load(g_szSHIBConfig)) {
1115             ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to load configuration");
1116             exit(1);
1117         }
1118
1119         IConfig* conf=g_Config->getINI();
1120         saml::Locker locker(conf);
1121         const IPropertySet* props=conf->getPropertySet("Local");
1122         if (props) {
1123             pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
1124             if (unsetValue.first)
1125                 g_unsetHeaderValue = unsetValue.second;
1126             pair<bool,bool> flag=props->getBool("checkSpoofing");
1127             g_checkSpoofing = !flag.first || flag.second;
1128             flag=props->getBool("catchAll");
1129             g_catchAll = !flag.first || flag.second;
1130         }
1131     }
1132     catch (exception&) {
1133         ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to initialize system");
1134         exit(1);
1135     }
1136
1137     // Set the cleanup handler
1138     apr_pool_cleanup_register(p, NULL, &shib_exit, apr_pool_cleanup_null);
1139
1140     ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() done");
1141 }
1142
1143 // Output filters
1144 #ifdef SHIB_DEFERRED_HEADERS
1145 static void set_output_filter(request_rec *r)
1146 {
1147     ap_add_output_filter("SHIB_HEADERS_OUT", NULL, r, r->connection);
1148 }
1149
1150 static void set_error_filter(request_rec *r)
1151 {
1152     ap_add_output_filter("SHIB_HEADERS_ERR", NULL, r, r->connection);
1153 }
1154
1155 static int _table_add(void *v, const char *key, const char *value)
1156 {
1157     apr_table_addn((apr_table_t*)v, key, value);
1158     return 1;
1159 }
1160
1161 static apr_status_t do_output_filter(ap_filter_t *f, apr_bucket_brigade *in)
1162 {
1163     request_rec *r = f->r;
1164     shib_request_config *rc = (shib_request_config*) ap_get_module_config(r->request_config, &mod_shib);
1165
1166     if (rc && rc->hdr_out) {
1167         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_out_filter: merging %d headers", apr_table_elts(rc->hdr_out)->nelts);
1168         // can't use overlap call because it will collapse Set-Cookie headers
1169         // apr_table_overlap(r->headers_out, rc->hdr_out, APR_OVERLAP_TABLES_MERGE);
1170         apr_table_do(_table_add,r->headers_out, rc->hdr_out,NULL);
1171     }
1172
1173     /* remove ourselves from the filter chain */
1174     ap_remove_output_filter(f);
1175
1176     /* send the data up the stack */
1177     return ap_pass_brigade(f->next,in);
1178 }
1179
1180 static apr_status_t do_error_filter(ap_filter_t *f, apr_bucket_brigade *in)
1181 {
1182     request_rec *r = f->r;
1183     shib_request_config *rc = (shib_request_config*) ap_get_module_config(r->request_config, &mod_shib);
1184
1185     if (rc && rc->hdr_out) {
1186         ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r),"shib_err_filter: merging %d headers", apr_table_elts(rc->hdr_out)->nelts);
1187         // can't use overlap call because it will collapse Set-Cookie headers
1188         // apr_table_overlap(r->err_headers_out, rc->hdr_err, APR_OVERLAP_TABLES_MERGE);
1189         apr_table_do(_table_add,r->err_headers_out, rc->hdr_out,NULL);
1190     }
1191
1192     /* remove ourselves from the filter chain */
1193     ap_remove_output_filter(f);
1194
1195     /* send the data up the stack */
1196     return ap_pass_brigade(f->next,in);
1197 }
1198 #endif // SHIB_DEFERRED_HEADERS
1199
1200
1201 typedef const char* (*config_fn_t)(void);
1202
1203 #ifdef SHIB_APACHE_13
1204
1205 // SHIB Module commands
1206
1207 static command_rec shire_cmds[] = {
1208   {"SHIREConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1209    RSRC_CONF, TAKE1, "Path to shibboleth.xml config file"},
1210   {"ShibConfig", (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1211    RSRC_CONF, TAKE1, "Path to shibboleth.xml config file"},
1212   {"ShibSchemaDir", (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
1213    RSRC_CONF, TAKE1, "Path to Shibboleth XML schema directory"},
1214
1215   {"ShibURLScheme", (config_fn_t)shib_set_server_string_slot,
1216    (void *) XtOffsetOf (shib_server_config, szScheme),
1217    RSRC_CONF, TAKE1, "URL scheme to force into generated URLs for a vhost"},
1218
1219   {"ShibDisable", (config_fn_t)ap_set_flag_slot,
1220    (void *) XtOffsetOf (shib_dir_config, bOff),
1221    OR_AUTHCFG, FLAG, "Disable all Shib module activity here to save processing effort"},
1222   {"ShibApplicationId", (config_fn_t)ap_set_string_slot,
1223    (void *) XtOffsetOf (shib_dir_config, szApplicationId),
1224    OR_AUTHCFG, TAKE1, "Set Shibboleth applicationId property for content"},
1225   {"ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
1226    (void *) XtOffsetOf (shib_dir_config, bBasicHijack),
1227    OR_AUTHCFG, FLAG, "Respond to AuthType Basic and convert to shibboleth"},
1228   {"ShibRequireSession", (config_fn_t)ap_set_flag_slot,
1229    (void *) XtOffsetOf (shib_dir_config, bRequireSession),
1230    OR_AUTHCFG, FLAG, "Initiates a new session if one does not exist"},
1231   {"ShibRequireSessionWith", (config_fn_t)ap_set_string_slot,
1232    (void *) XtOffsetOf (shib_dir_config, szRequireWith),
1233    OR_AUTHCFG, TAKE1, "Initiates a new session if one does not exist using a specific SessionInitiator"},
1234   {"ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
1235    (void *) XtOffsetOf (shib_dir_config, bExportAssertion),
1236    OR_AUTHCFG, FLAG, "Export SAML attribute assertion(s) to Shib-Attributes header"},
1237   {"ShibRedirectToSSL", (config_fn_t)ap_set_string_slot,
1238    (void *) XtOffsetOf (shib_dir_config, szRedirectToSSL),
1239    OR_AUTHCFG, TAKE1, "Redirect non-SSL requests to designated port" },
1240   {"AuthGroupFile", (config_fn_t)shib_ap_set_file_slot,
1241    (void *) XtOffsetOf (shib_dir_config, szAuthGrpFile),
1242    OR_AUTHCFG, TAKE1, "text file containing group names and member user IDs"},
1243   {"ShibRequireAll", (config_fn_t)ap_set_flag_slot,
1244    (void *) XtOffsetOf (shib_dir_config, bRequireAll),
1245    OR_AUTHCFG, FLAG, "All require directives must match"},
1246   {"ShibUseEnvironment", (config_fn_t)ap_set_flag_slot,
1247    (void *) XtOffsetOf (shib_dir_config, bUseEnvVars),
1248    OR_AUTHCFG, FLAG, "Export attributes using environment variables"},
1249   {"ShibUseHeaders", (config_fn_t)ap_set_flag_slot,
1250    (void *) XtOffsetOf (shib_dir_config, bUseHeaders),
1251    OR_AUTHCFG, FLAG, "Export attributes using custom HTTP headers (default)"},
1252
1253   {NULL}
1254 };
1255
1256 extern "C"{
1257 handler_rec shib_handlers[] = {
1258   { "shib-handler", shib_handler },
1259   { NULL }
1260 };
1261
1262 module MODULE_VAR_EXPORT mod_shib = {
1263     STANDARD_MODULE_STUFF,
1264     NULL,                        /* initializer */
1265     create_shib_dir_config,     /* dir config creater */
1266     merge_shib_dir_config,      /* dir merger --- default is to override */
1267     create_shib_server_config, /* server config */
1268     merge_shib_server_config,   /* merge server config */
1269     shire_cmds,                 /* command table */
1270     shib_handlers,              /* handlers */
1271     NULL,                       /* filename translation */
1272     shib_check_user,            /* check_user_id */
1273     shib_auth_checker,          /* check auth */
1274     NULL,                       /* check access */
1275     NULL,                       /* type_checker */
1276     shib_fixups,                /* fixups */
1277     NULL,                       /* logger */
1278     NULL,                       /* header parser */
1279     shib_child_init,            /* child_init */
1280     shib_child_exit,            /* child_exit */
1281     shib_post_read              /* post read-request */
1282 };
1283
1284 #elif defined(SHIB_APACHE_20) || defined(SHIB_APACHE_22)
1285
1286 extern "C" void shib_register_hooks (apr_pool_t *p)
1287 {
1288 #ifdef SHIB_DEFERRED_HEADERS
1289   ap_register_output_filter("SHIB_HEADERS_OUT", do_output_filter, NULL, AP_FTYPE_CONTENT_SET);
1290   ap_hook_insert_filter(set_output_filter, NULL, NULL, APR_HOOK_LAST);
1291   ap_register_output_filter("SHIB_HEADERS_ERR", do_error_filter, NULL, AP_FTYPE_CONTENT_SET);
1292   ap_hook_insert_error_filter(set_error_filter, NULL, NULL, APR_HOOK_LAST);
1293   ap_hook_post_read_request(shib_post_read, NULL, NULL, APR_HOOK_MIDDLE);
1294 #endif
1295   ap_hook_child_init(shib_child_init, NULL, NULL, APR_HOOK_MIDDLE);
1296   ap_hook_check_user_id(shib_check_user, NULL, NULL, APR_HOOK_MIDDLE);
1297   ap_hook_auth_checker(shib_auth_checker, NULL, NULL, APR_HOOK_FIRST);
1298   ap_hook_handler(shib_handler, NULL, NULL, APR_HOOK_LAST);
1299   ap_hook_fixups(shib_fixups, NULL, NULL, APR_HOOK_MIDDLE);
1300 }
1301
1302 // SHIB Module commands
1303
1304 extern "C" {
1305 static command_rec shib_cmds[] = {
1306   AP_INIT_TAKE1("ShibConfig",
1307                 (config_fn_t)ap_set_global_string_slot, &g_szSHIBConfig,
1308                 RSRC_CONF, "Path to shibboleth.xml config file"),
1309   AP_INIT_TAKE1("ShibSchemaDir",
1310      (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
1311       RSRC_CONF, "Path to Shibboleth XML schema directory"),
1312
1313   AP_INIT_TAKE1("ShibURLScheme",
1314      (config_fn_t)shib_set_server_string_slot,
1315      (void *) offsetof (shib_server_config, szScheme),
1316       RSRC_CONF, "URL scheme to force into generated URLs for a vhost"),
1317
1318   AP_INIT_FLAG("ShibDisable", (config_fn_t)ap_set_flag_slot,
1319         (void *) offsetof (shib_dir_config, bOff),
1320         OR_AUTHCFG, "Disable all Shib module activity here to save processing effort"),
1321   AP_INIT_TAKE1("ShibApplicationId", (config_fn_t)ap_set_string_slot,
1322         (void *) offsetof (shib_dir_config, szApplicationId),
1323         OR_AUTHCFG, "Set Shibboleth applicationId property for content"),
1324   AP_INIT_FLAG("ShibBasicHijack", (config_fn_t)ap_set_flag_slot,
1325         (void *) offsetof (shib_dir_config, bBasicHijack),
1326         OR_AUTHCFG, "Respond to AuthType Basic and convert to shibboleth"),
1327   AP_INIT_FLAG("ShibRequireSession", (config_fn_t)ap_set_flag_slot,
1328         (void *) offsetof (shib_dir_config, bRequireSession),
1329         OR_AUTHCFG, "Initiates a new session if one does not exist"),
1330   AP_INIT_TAKE1("ShibRequireSessionWith", (config_fn_t)ap_set_string_slot,
1331         (void *) offsetof (shib_dir_config, szRequireWith),
1332         OR_AUTHCFG, "Initiates a new session if one does not exist using a specific SessionInitiator"),
1333   AP_INIT_FLAG("ShibExportAssertion", (config_fn_t)ap_set_flag_slot,
1334         (void *) offsetof (shib_dir_config, bExportAssertion),
1335         OR_AUTHCFG, "Export SAML attribute assertion(s) to Shib-Attributes header"),
1336   AP_INIT_TAKE1("ShibRedirectToSSL", (config_fn_t)ap_set_string_slot,
1337         (void *) offsetof (shib_dir_config, szRedirectToSSL),
1338         OR_AUTHCFG, "Redirect non-SSL requests to designated port"),
1339   AP_INIT_TAKE1("AuthGroupFile", (config_fn_t)shib_ap_set_file_slot,
1340                 (void *) offsetof (shib_dir_config, szAuthGrpFile),
1341                 OR_AUTHCFG, "Text file containing group names and member user IDs"),
1342   AP_INIT_FLAG("ShibRequireAll", (config_fn_t)ap_set_flag_slot,
1343         (void *) offsetof (shib_dir_config, bRequireAll),
1344         OR_AUTHCFG, "All require directives must match"),
1345   AP_INIT_FLAG("ShibUseEnvironment", (config_fn_t)ap_set_flag_slot,
1346         (void *) offsetof (shib_dir_config, bUseEnvVars),
1347         OR_AUTHCFG, "Export attributes using environment variables"),
1348   AP_INIT_FLAG("ShibUseHeaders", (config_fn_t)ap_set_flag_slot,
1349         (void *) offsetof (shib_dir_config, bUseHeaders),
1350         OR_AUTHCFG, "Export attributes using custom HTTP headers (default)"),
1351
1352   {NULL}
1353 };
1354
1355 module AP_MODULE_DECLARE_DATA mod_shib = {
1356     STANDARD20_MODULE_STUFF,
1357     create_shib_dir_config,     /* create dir config */
1358     merge_shib_dir_config,      /* merge dir config --- default is to override */
1359     create_shib_server_config,  /* create server config */
1360     merge_shib_server_config,   /* merge server config */
1361     shib_cmds,                  /* command table */
1362     shib_register_hooks         /* register hooks */
1363 };
1364
1365 #else
1366 #error "undefined APACHE version"
1367 #endif
1368
1369 }