Added logging config as a distinct feature
[shibboleth/cpp-sp.git] / isapi_shib / isapi_shib.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution, if any, must include
17  * the following acknowledgment: "This product includes software developed by
18  * the University Corporation for Advanced Internet Development
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20  * may appear in the software itself, if and wherever such third-party
21  * acknowledgments normally appear.
22  *
23  * Neither the name of Shibboleth nor the names of its contributors, nor
24  * Internet2, nor the University Corporation for Advanced Internet Development,
25  * Inc., nor UCAID may be used to endorse or promote products derived from this
26  * software without specific prior written permission. For written permission,
27  * please contact shibboleth@shibboleth.org
28  *
29  * Products derived from this software may not be called Shibboleth, Internet2,
30  * UCAID, or the University Corporation for Advanced Internet Development, nor
31  * may Shibboleth appear in their name, without prior written permission of the
32  * University Corporation for Advanced Internet Development.
33  *
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 /* isapi_shib.cpp - Shibboleth ISAPI filter
51
52    Scott Cantor
53    8/23/02
54 */
55
56 #include "config_win32.h"
57
58 // SAML Runtime
59 #include <saml/saml.h>
60 #include <shib/shib.h>
61 #include <shib/shib-threads.h>
62 #include <shib-target/shib-target.h>
63
64 #include <log4cpp/Category.hh>
65
66 #include <ctime>
67 #include <fstream>
68 #include <sstream>
69 #include <stdexcept>
70
71 #include <httpfilt.h>
72 #include <httpext.h>
73
74 using namespace std;
75 using namespace log4cpp;
76 using namespace saml;
77 using namespace shibboleth;
78 using namespace shibtarget;
79
80 // globals
81 namespace {
82     HINSTANCE g_hinstDLL;
83     ShibTargetConfig* g_Config = NULL;
84     map<string,string> g_Sites;
85     bool g_bNormalizeRequest = true;
86 }
87
88 BOOL LogEvent(
89     LPCSTR  lpUNCServerName,
90     WORD  wType,
91     DWORD  dwEventID,
92     PSID  lpUserSid,
93     LPCSTR  message)
94 {
95     LPCSTR  messages[] = {message, NULL};
96     
97     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
98     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
99     return (DeregisterEventSource(hElog) && res);
100 }
101
102 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
103 {
104     if (fdwReason==DLL_PROCESS_ATTACH)
105         g_hinstDLL=hinstDLL;
106     return TRUE;
107 }
108
109 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
110 {
111     if (!pVer)
112         return FALSE;
113         
114     if (!g_Config)
115     {
116         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
117                 "Extension mode startup not possible, is the DLL loaded as a filter?");
118         return FALSE;
119     }
120
121     pVer->dwExtensionVersion=HSE_VERSION;
122     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
123     return TRUE;
124 }
125
126 extern "C" BOOL WINAPI TerminateExtension(DWORD)
127 {
128     return TRUE;    // cleanup should happen when filter unloads
129 }
130
131 static const XMLCh host[] = { chLatin_h, chLatin_o, chLatin_s, chLatin_t, chNull };
132 static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
133 static const XMLCh Implementation[] =
134 { chLatin_I, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull };
135 static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
136 static const XMLCh normalizeRequest[] =
137 { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
138   chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
139 };
140 static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
141
142 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
143 {
144     if (!pVer)
145         return FALSE;
146
147     try
148     {
149         LPCSTR schemadir=getenv("SHIBSCHEMAS");
150         if (!schemadir)
151             schemadir=SHIB_SCHEMAS;
152         LPCSTR config=getenv("SHIBCONFIG");
153         if (!config)
154             config=SHIB_CONFIG;
155         g_Config=&ShibTargetConfig::getConfig();
156         g_Config->setFeatures(
157             ShibTargetConfig::Listener |
158             ShibTargetConfig::Metadata |
159             ShibTargetConfig::AAP |
160             ShibTargetConfig::RequestMapper |
161             ShibTargetConfig::SHIREExtensions |
162             ShibTargetConfig::Logging
163             );
164         if (!g_Config->init(schemadir,config)) {
165             g_Config=NULL;
166             LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
167                     "Filter startup failed during initialization, check shire log for help.");
168             return FALSE;
169         }
170         
171         // Access the implementation-specifics for site mappings.
172         IConfig* conf=g_Config->getINI();
173         Locker locker(conf);
174         const IPropertySet* props=conf->getPropertySet("SHIRE");
175         if (props) {
176             const DOMElement* impl=saml::XML::getFirstChildElement(
177                 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
178                 );
179             if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
180                 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
181                 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
182                 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
183                 while (impl) {
184                     auto_ptr_char id(impl->getAttributeNS(NULL,id));
185                     auto_ptr_char host(impl->getAttributeNS(NULL,host));
186                     if (id.get() && host.get())
187                         g_Sites[id.get()]=host.get();
188                     impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
189                 }
190             }
191         }
192     }
193     catch (...)
194     {
195         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
196         return FALSE;
197     }
198
199     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
200     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
201     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
202                    SF_NOTIFY_SECURE_PORT |
203                    SF_NOTIFY_NONSECURE_PORT |
204                    SF_NOTIFY_PREPROC_HEADERS |
205                    SF_NOTIFY_LOG);
206     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
207     return TRUE;
208 }
209
210 extern "C" BOOL WINAPI TerminateFilter(DWORD)
211 {
212     if (g_Config)
213         g_Config->shutdown();
214     g_Config = NULL;
215     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
216     return TRUE;
217 }
218
219 /* Next up, some suck-free versions of various APIs.
220
221    You DON'T require people to guess the buffer size and THEN tell them the right size.
222    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
223    constant strings aren't typed as such, making it just that much harder. These versions
224    are now updated to use a special growable buffer object, modeled after the standard
225    string class. The standard string won't work because they left out the option to
226    pre-allocate a non-constant buffer.
227 */
228
229 class dynabuf
230 {
231 public:
232     dynabuf() { bufptr=NULL; buflen=0; }
233     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
234     ~dynabuf() { delete[] bufptr; }
235     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
236     size_t size() const { return buflen; }
237     bool empty() const { return length()==0; }
238     void reserve(size_t s, bool keep=false);
239     void erase() { if (bufptr) memset(bufptr,0,buflen); }
240     operator char*() { return bufptr; }
241     bool operator ==(const char* s) const;
242     bool operator !=(const char* s) const { return !(*this==s); }
243 private:
244     char* bufptr;
245     size_t buflen;
246 };
247
248 void dynabuf::reserve(size_t s, bool keep)
249 {
250     if (s<=buflen)
251         return;
252     char* p=new char[s];
253     if (keep)
254         while (buflen--)
255             p[buflen]=bufptr[buflen];
256     buflen=s;
257     delete[] bufptr;
258     bufptr=p;
259 }
260
261 bool dynabuf::operator==(const char* s) const
262 {
263     if (buflen==NULL || s==NULL)
264         return (buflen==NULL && s==NULL);
265     else
266         return strcmp(bufptr,s)==0;
267 }
268
269 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
270     throw (bad_alloc, DWORD)
271 {
272     s.reserve(size);
273     s.erase();
274     size=s.size();
275
276     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
277     {
278         // Grumble. Check the error.
279         DWORD e=GetLastError();
280         if (e==ERROR_INSUFFICIENT_BUFFER)
281             s.reserve(size);
282         else
283             break;
284     }
285     if (bRequired && s.empty())
286         throw ERROR_NO_DATA;
287 }
288
289 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
290     throw (bad_alloc, DWORD)
291 {
292     s.reserve(size);
293     s.erase();
294     size=s.size();
295
296     while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
297     {
298         // Grumble. Check the error.
299         DWORD e=GetLastError();
300         if (e==ERROR_INSUFFICIENT_BUFFER)
301             s.reserve(size);
302         else
303             break;
304     }
305     if (bRequired && s.empty())
306         throw ERROR_NO_DATA;
307 }
308
309 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
310                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
311     throw (bad_alloc, DWORD)
312 {
313     s.reserve(size);
314     s.erase();
315     size=s.size();
316
317     while (!pn->GetHeader(pfc,lpszName,s,&size))
318     {
319         // Grumble. Check the error.
320         DWORD e=GetLastError();
321         if (e==ERROR_INSUFFICIENT_BUFFER)
322             s.reserve(size);
323         else
324             break;
325     }
326     if (bRequired && s.empty())
327         throw ERROR_NO_DATA;
328 }
329
330 IRequestMapper::Settings map_request(
331     PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const char* hostname, string& target
332     )
333 {
334     dynabuf port(10);
335     dynabuf url(256);
336     GetServerVariable(pfc,"SERVER_PORT",port,10);
337     GetHeader(pn,pfc,"url",url,256,false);
338     
339     if (!url.empty())
340         target=static_cast<char*>(url);
341     if (port!=(pfc->fIsSecurePort ? "443" : "80"))
342         target = ':' + static_cast<char*>(port) + target;
343
344     if (g_bNormalizeRequest) {
345         target = string(pfc->fIsSecurePort ? "https://" : "http://") + hostname + target;
346     }
347     else {
348         dynabuf name(64);
349         GetServerVariable(pfc,"SERVER_NAME",name,64);
350         target = string(pfc->fIsSecurePort ? "https://" : "http://") + static_cast<char*>(name) + target;
351     }
352     return mapper->getSettingsFromParsedURL((pfc->fIsSecurePort ? "https" : "http"),hostname,strtoul(port,NULL,10),url);
353 }
354
355 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
356 {
357     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
358     static const char* ctype="Content-Type: text/html\r\n";
359     pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
360     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
361     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
362                             "<H1>Shibboleth Filter Error</H1>";
363     DWORD resplen=strlen(xmsg);
364     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
365     resplen=strlen(msg);
366     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
367     static const char* xmsg2="</BODY></HTML>";
368     resplen=strlen(xmsg2);
369     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
370     return SF_STATUS_REQ_FINISHED;
371 }
372
373 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
374 {
375     const IPropertySet* props=app->getPropertySet("Errors");
376     if (props) {
377         pair<bool,const char*> p=props->getString(page);
378         if (p.first) {
379             ifstream infile(p.second);
380             if (!infile.fail()) {
381                 const char* res = mlp.run(infile,props);
382                 if (res) {
383                     static const char* ctype="Content-Type: text/html\r\n";
384                     pfc->AddResponseHeaders(pfc,const_cast<char*>(ctype),0);
385                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",0,0);
386                     DWORD resplen=strlen(res);
387                     pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
388                     return SF_STATUS_REQ_FINISHED;
389                 }
390             }
391         }
392     }
393
394     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
395     return WriteClientError(pfc,"Unable to open error template, check settings.");
396 }
397
398 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
399 {
400     // Is this a log notification?
401     if (notificationType==SF_NOTIFY_LOG)
402     {
403         if (pfc->pFilterContext)
404             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
405         return SF_STATUS_REQ_NEXT_NOTIFICATION;
406     }
407
408     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
409     try
410     {
411         // Determine web site number. This can't really fail, I don't think.
412         dynabuf buf(128);
413         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
414
415         // Match site instance to host name, skip if no match.
416         map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
417         if (map_i==g_Sites.end())
418             return SF_STATUS_REQ_NEXT_NOTIFICATION;
419             
420         const string& site=map_i->second;
421
422         ostringstream threadid;
423         threadid << "[" << getpid() << "] isapi_shib" << '\0';
424         saml::NDC ndc(threadid.str().c_str());
425         
426         // We lock the configuration system for the duration.
427         IConfig* conf=g_Config->getINI();
428         Locker locker(conf);
429         
430         // Map request to application and content settings.
431         string targeturl;
432         IRequestMapper* mapper=conf->getRequestMapper();
433         Locker locker2(mapper);
434         IRequestMapper::Settings settings=map_request(pfc,pn,mapper,site.c_str(),targeturl);
435         pair<bool,const char*> application_id=settings.first->getString("applicationId");
436         const IApplication* application=conf->getApplication(application_id.second);
437         if (!application)
438             return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
439         
440         // Declare SHIRE object for this request.
441         SHIRE shire(application);
442
443         // If the user is accessing the SHIRE acceptance point, pass it on.
444         if (targeturl.find(shire.getShireURL(targeturl.c_str()))!=string::npos)
445             return SF_STATUS_REQ_NEXT_NOTIFICATION;
446
447         // Now check the policy for this request.
448         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
449         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
450
451         // Check for session cookie.
452         const char* session_id=NULL;
453         GetHeader(pn,pfc,"Cookie:",buf,128,false);
454         Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
455         if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
456             session_id+=strlen(shib_cookie.first) + 1;   /* Skip over the '=' */
457             char* cookieend=strchr(session_id,';');
458             if (cookieend)
459                 *cookieend = '\0';    /* Ignore anyting after a ; */
460         }
461         
462         if (!session_id || !*session_id) {
463             // If no session required, bail now.
464             if (!requireSession.second)
465                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
466     
467             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
468             string loc("Location: ");
469             loc+=shire.getAuthnRequest(targeturl.c_str());
470             pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
471             pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
472             return SF_STATUS_REQ_FINISHED;
473         }
474
475         // Make sure this session is still valid.
476         RPCError* status = NULL;
477         ShibMLP markupProcessor;
478         markupProcessor.insert("requestURL", targeturl);
479     
480         dynabuf abuf(16);
481         GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
482         try {
483             status = shire.sessionIsValid(session_id, abuf);
484         }
485         catch (ShibTargetException &e) {
486             markupProcessor.insert("errorType", "Session Processing Error");
487             markupProcessor.insert("errorText", e.what());
488             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
489             return WriteClientError(pfc, application, "shire", markupProcessor);
490         }
491 #ifndef _DEBUG
492         catch (...) {
493             markupProcessor.insert("errorType", "Session Processing Error");
494             markupProcessor.insert("errorText", "Unexpected Exception");
495             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
496             return WriteClientError(pfc, application, "shire", markupProcessor);
497         }
498 #endif
499
500         // Check the status
501         if (status->isError()) {
502             if (!requireSession.second)
503                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
504             else if (status->isRetryable()) {
505                 // Oops, session is invalid. Generate AuthnRequest.
506                 delete status;
507                 string loc("Location: ");
508                 loc+=shire.getAuthnRequest(targeturl.c_str());
509                 pfc->AddResponseHeaders(pfc,const_cast<char*>(loc.c_str()),0);
510                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",0,0);
511                 return SF_STATUS_REQ_FINISHED;
512             }
513             else {
514                 // return the error page to the user
515                 markupProcessor.insert(*status);
516                 delete status;
517                 return WriteClientError(pfc, application, "shire", markupProcessor);
518             }
519         }
520         delete status;
521     
522         // Move to RM phase.
523         RM rm(application);
524         vector<SAMLAssertion*> assertions;
525         SAMLAuthenticationStatement* sso_statement=NULL;
526
527         try {
528             status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
529         }
530         catch (ShibTargetException &e) {
531             markupProcessor.insert("errorType", "Attribute Processing Error");
532             markupProcessor.insert("errorText", e.what());
533             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
534             return WriteClientError(pfc, application, "rm", markupProcessor);
535         }
536     #ifndef _DEBUG
537         catch (...) {
538             markupProcessor.insert("errorType", "Attribute Processing Error");
539             markupProcessor.insert("errorText", "Unexpected Exception");
540             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
541             return WriteClientError(pfc, application, "rm", markupProcessor);
542         }
543     #endif
544     
545         if (status->isError()) {
546             markupProcessor.insert(*status);
547             delete status;
548             return WriteClientError(pfc, application, "rm", markupProcessor);
549         }
550         delete status;
551
552         // Do we have an access control plugin?
553         if (settings.second) {
554             Locker acllock(settings.second);
555             if (!settings.second->authorized(assertions)) {
556                 for (int k = 0; k < assertions.size(); k++)
557                     delete assertions[k];
558                 delete sso_statement;
559                 return WriteClientError(pfc, application, "access", markupProcessor);
560             }
561         }
562
563         // Get the AAP providers, which contain the attribute policy info.
564         Iterator<IAAP*> provs=application->getAAPProviders();
565     
566         // Clear out the list of mapped attributes
567         while (provs.hasNext()) {
568             IAAP* aap=provs.next();
569             aap->lock();
570             try {
571                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
572                 while (rules.hasNext()) {
573                     const char* header=rules.next()->getHeader();
574                     if (header) {
575                         string hname=string(header) + ':';
576                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
577                     }
578                 }
579             }
580             catch(...) {
581                 aap->unlock();
582                 for (int k = 0; k < assertions.size(); k++)
583                   delete assertions[k];
584                 delete sso_statement;
585                 markupProcessor.insert("errorType", "Attribute Processing Error");
586                 markupProcessor.insert("errorText", "Unexpected Exception");
587                 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
588                 return WriteClientError(pfc, application, "rm", markupProcessor);
589             }
590             aap->unlock();
591         }
592         provs.reset();
593
594         // Maybe export the first assertion.
595         pn->SetHeader(pfc,"remote-user:","");
596         pn->SetHeader(pfc,"Shib-Attributes:","");
597         pair<bool,bool> exp=settings.first->getBool("exportAssertion");
598         if (exp.first && exp.second && assertions.size()) {
599             string assertion;
600             RM::serialize(*(assertions[0]), assertion);
601             string::size_type lfeed;
602             while ((lfeed=assertion.find('\n'))!=string::npos)
603                 assertion.erase(lfeed,1);
604             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
605         }
606         
607         pn->SetHeader(pfc,"Shib-Origin-Site:","");
608         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
609         pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
610
611         // Export the SAML AuthnMethod and the origin site name.
612         if (sso_statement) {
613             auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
614             auto_ptr_char am(sso_statement->getAuthMethod());
615             pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
616             pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
617
618             // Export NameID?
619             AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
620             if (!wrapper.fail() && wrapper->getHeader()) {
621                 auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
622                 auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
623                 pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
624                 if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
625                     char* principal=const_cast<char*>(nameid.get());
626                     pn->SetHeader(pfc,"remote-user:",principal);
627                     pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
628                     if (pfc->pFilterContext)
629                         strcpy(static_cast<char*>(pfc->pFilterContext),principal);
630                 }
631                 else {
632                     string hname=string(wrapper->getHeader()) + ':';
633                     pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
634                 }
635             }
636         }
637
638         pn->SetHeader(pfc,"Shib-Application-ID:","");
639         pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
640
641         // Export the attributes.
642         Iterator<SAMLAssertion*> a_iter(assertions);
643         while (a_iter.hasNext()) {
644             SAMLAssertion* assert=a_iter.next();
645             Iterator<SAMLStatement*> statements=assert->getStatements();
646             while (statements.hasNext()) {
647                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
648                 if (!astate)
649                     continue;
650                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
651                 while (attrs.hasNext()) {
652                     SAMLAttribute* attr=attrs.next();
653         
654                     // Are we supposed to export it?
655                     AAP wrapper(provs,attr->getName(),attr->getNamespace());
656                     if (wrapper.fail() || !wrapper->getHeader())
657                         continue;
658                 
659                     Iterator<string> vals=attr->getSingleByteValues();
660                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
661                         char* principal=const_cast<char*>(vals.next().c_str());
662                         pn->SetHeader(pfc,"remote-user:",principal);
663                         pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
664                         if (pfc->pFilterContext)
665                             strcpy(static_cast<char*>(pfc->pFilterContext),principal);
666                     }
667                     else {
668                         int it=0;
669                         string header;
670                         string hname=string(wrapper->getHeader()) + ':';
671                         GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
672                         if (!buf.empty()) {
673                             header=buf;
674                             it++;
675                         }
676                         for (; vals.hasNext(); it++) {
677                             string value = vals.next();
678                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
679                                     pos != string::npos;
680                                     pos = value.find_first_of(";", pos)) {
681                                 value.insert(pos, "\\");
682                                 pos += 2;
683                             }
684                             if (it == 0)
685                                 header=value;
686                             else
687                                 header=header + ';' + value;
688                         }
689                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
690                         }
691                 }
692             }
693         }
694     
695         // clean up memory
696         for (int k = 0; k < assertions.size(); k++)
697           delete assertions[k];
698         delete sso_statement;
699
700         return SF_STATUS_REQ_NEXT_NOTIFICATION;
701     }
702     catch(bad_alloc) {
703         return WriteClientError(pfc,"Out of Memory");
704     }
705     catch(DWORD e) {
706         if (e==ERROR_NO_DATA)
707             return WriteClientError(pfc,"A required variable or header was empty.");
708         else
709             return WriteClientError(pfc,"Server detected unexpected IIS error.");
710     }
711 #ifndef _DEBUG
712     catch(...) {
713         return WriteClientError(pfc,"Server caught an unknown exception.");
714     }
715 #endif
716
717     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
718 }
719
720 IRequestMapper::Settings map_request(
721     LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const char* hostname, string& target
722     )
723 {
724     dynabuf ssl(5);
725     dynabuf port(10);
726     dynabuf url(256);
727     GetServerVariable(lpECB,"HTTPS",ssl,5);
728     GetServerVariable(lpECB,"SERVER_PORT",port,10);
729     GetServerVariable(lpECB,"URL",url,255);
730     bool SSL=(ssl=="on");
731     
732     if (!url.empty())
733         target=static_cast<char*>(url);
734     if (port!=(SSL ? "443" : "80"))
735         target = ':' + static_cast<char*>(port) + target;
736
737     if (g_bNormalizeRequest) {
738         target = string(SSL ? "https://" : "http://") + hostname + target;
739         return mapper->getSettingsFromParsedURL(lpECB->lpszMethod,hostname,strtoul(port,NULL,10),url);
740     }
741     else {
742         dynabuf name(64);
743         GetServerVariable(lpECB,"SERVER_NAME",name,64);
744         target = string(SSL ? "https://" : "http://") + static_cast<char*>(name) + target;
745         return mapper->getSettingsFromParsedURL((SSL ? "https" : "http"),name,strtoul(port,NULL,10),url);
746     }
747 }
748
749 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
750 {
751     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
752     static const char* ctype="Content-Type: text/html\r\n";
753     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
754     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
755     DWORD resplen=strlen(xmsg);
756     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
757     resplen=strlen(msg);
758     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
759     static const char* xmsg2="</BODY></HTML>";
760     resplen=strlen(xmsg2);
761     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
762     return HSE_STATUS_SUCCESS;
763 }
764
765 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
766 {
767     const IPropertySet* props=app->getPropertySet("Errors");
768     if (props) {
769         pair<bool,const char*> p=props->getString(page);
770         if (p.first) {
771             ifstream infile(p.second);
772             if (!infile.fail()) {
773                 const char* res = mlp.run(infile,props);
774                 if (res) {
775                     static const char* ctype="Content-Type: text/html\r\n";
776                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
777                     DWORD resplen=strlen(res);
778                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
779                     return HSE_STATUS_SUCCESS;
780                 }
781             }
782         }
783     }
784     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
785     return WriteClientError(lpECB,"Unable to open error template, check settings.");
786 }
787
788 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
789 {
790     string targeturl;
791     const IApplication* application=NULL;
792     try
793     {
794         ostringstream threadid;
795         threadid << "[" << getpid() << "] shire_handler" << '\0';
796         saml::NDC ndc(threadid.str().c_str());
797
798         // Determine web site number. This can't really fail, I don't think.
799         dynabuf buf(128);
800         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
801
802         // Match site instance to host name, skip if no match.
803         map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
804         if (map_i==g_Sites.end())
805             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
806             
807         const string& site=map_i->second;
808
809         // We lock the configuration system for the duration.
810         IConfig* conf=g_Config->getINI();
811         Locker locker(conf);
812         
813         // Map request to application and content settings.
814         string targeturl;
815         IRequestMapper* mapper=conf->getRequestMapper();
816         Locker locker2(mapper);
817         IRequestMapper::Settings settings=map_request(lpECB,mapper,site.c_str(),targeturl);
818         pair<bool,const char*> application_id=settings.first->getString("applicationId");
819         application=conf->getApplication(application_id.second);
820         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
821         if (!application || !sessionProps)
822             return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
823
824         SHIRE shire(application);
825
826         // Make sure we only process the SHIRE requests.
827         if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
828             return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
829
830         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
831
832         // Make sure this is SSL, if it should be
833         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
834         if (!shireSSL.first || shireSSL.second) {
835             GetServerVariable(lpECB,"HTTPS",buf,10);
836             if (buf!="on")
837                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
838         }
839         
840         // If this is a GET, we manufacture an AuthnRequest.
841         if (!stricmp(lpECB->lpszMethod,"GET")) {
842             const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
843             if (!areq)
844                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
845             targeturl = string("Location: ") + areq + "\r\n"
846                 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
847                 "Cache-Control: private,no-store,no-cache\r\n"
848                 "Connection: close\r\n";
849             HSE_SEND_HEADER_EX_INFO hinfo;
850             hinfo.pszStatus="302 Moved";
851             hinfo.pszHeader=targeturl.c_str();
852             hinfo.cchStatus=9;
853             hinfo.cchHeader=targeturl.length();
854             hinfo.fKeepConn=FALSE;
855             if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
856                 return HSE_STATUS_SUCCESS;
857             return HSE_STATUS_ERROR;
858         }
859         else if (stricmp(lpECB->lpszMethod,"POST"))
860             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
861
862         // Sure sure this POST is an appropriate content type
863         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
864             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
865     
866         // Read the data.
867         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
868         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
869             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
870         else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
871             string cgistr;
872             char buf[8192];
873             DWORD datalen=lpECB->cbTotalBytes;
874             while (datalen) {
875                 DWORD buflen=8192;
876                 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
877                 if (!ret || !buflen)
878                     throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
879                 cgistr.append(buf,buflen);
880                 datalen-=buflen;
881             }
882             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
883         }
884         else
885             elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
886     
887         // Make sure the SAML Response parameter exists
888         if (!elements.first || !*elements.first)
889             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
890     
891         // Make sure the target parameter exists
892         if (!elements.second || !*elements.second)
893             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
894             
895         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
896
897         // Process the post.
898         string cookie;
899         RPCError* status=NULL;
900         ShibMLP markupProcessor;
901         markupProcessor.insert("requestURL", targeturl.c_str());
902         try {
903             status = shire.sessionCreate(elements.first,buf,cookie);
904         }
905         catch (ShibTargetException &e) {
906             markupProcessor.insert("errorType", "Session Creation Service Error");
907             markupProcessor.insert("errorText", e.what());
908             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
909             return WriteClientError(lpECB, application, "shire", markupProcessor);
910         }
911 #ifndef _DEBUG
912         catch (...) {
913             markupProcessor.insert("errorType", "Session Creation Service Error");
914             markupProcessor.insert("errorText", "Unexpected Exception");
915             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
916             return WriteClientError(lpECB, application, "shire", markupProcessor);
917         }
918 #endif
919
920         if (status->isError()) {
921             if (status->isRetryable()) {
922                 delete status;
923                 const char* loc=shire.getAuthnRequest(elements.second);
924                 DWORD len=strlen(loc);
925                 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
926                     return HSE_STATUS_SUCCESS;
927                 return HSE_STATUS_ERROR;
928             }
929     
930             // Return this error to the user.
931             markupProcessor.insert(*status);
932             delete status;
933             return WriteClientError(lpECB,application,"shire",markupProcessor);
934         }
935         delete status;
936     
937         // We've got a good session, set the cookie and redirect to target.
938         cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
939             "Location: " + elements.second + "\r\n"
940             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
941             "Cache-Control: private,no-store,no-cache\r\n"
942             "Connection: close\r\n";
943         HSE_SEND_HEADER_EX_INFO hinfo;
944         hinfo.pszStatus="302 Moved";
945         hinfo.pszHeader=cookie.c_str();
946         hinfo.cchStatus=9;
947         hinfo.cchHeader=cookie.length();
948         hinfo.fKeepConn=FALSE;
949         if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
950             return HSE_STATUS_SUCCESS;
951     }
952     catch (ShibTargetException &e) {
953         if (application) {
954             ShibMLP markupProcessor;
955             markupProcessor.insert("requestURL", targeturl.c_str());
956             markupProcessor.insert("errorType", "Session Creation Service Error");
957             markupProcessor.insert("errorText", e.what());
958             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
959             return WriteClientError(lpECB,application,"shire",markupProcessor);
960         }
961     }
962 #ifndef _DEBUG
963     catch (...) {
964         if (application) {
965             ShibMLP markupProcessor;
966             markupProcessor.insert("requestURL", targeturl.c_str());
967             markupProcessor.insert("errorType", "Session Creation Service Error");
968             markupProcessor.insert("errorText", "Unexpected Exception");
969             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
970             return WriteClientError(lpECB,application,"shire",markupProcessor);
971         }
972     }
973 #endif
974     
975     return HSE_STATUS_ERROR;
976 }