Adjusted ACL API to take authn statement as well as attrs.
[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(*sso_statement,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         auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
613         auto_ptr_char am(sso_statement->getAuthMethod());
614         pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
615         pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
616
617         // Export NameID?
618         AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
619         if (!wrapper.fail() && wrapper->getHeader()) {
620             auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
621             auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
622             pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
623             if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
624                 char* principal=const_cast<char*>(nameid.get());
625                 pn->SetHeader(pfc,"remote-user:",principal);
626                 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
627                 if (pfc->pFilterContext)
628                     strcpy(static_cast<char*>(pfc->pFilterContext),principal);
629             }
630             else {
631                 string hname=string(wrapper->getHeader()) + ':';
632                 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
633             }
634         }
635
636         pn->SetHeader(pfc,"Shib-Application-ID:","");
637         pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
638
639         // Export the attributes.
640         Iterator<SAMLAssertion*> a_iter(assertions);
641         while (a_iter.hasNext()) {
642             SAMLAssertion* assert=a_iter.next();
643             Iterator<SAMLStatement*> statements=assert->getStatements();
644             while (statements.hasNext()) {
645                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
646                 if (!astate)
647                     continue;
648                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
649                 while (attrs.hasNext()) {
650                     SAMLAttribute* attr=attrs.next();
651         
652                     // Are we supposed to export it?
653                     AAP wrapper(provs,attr->getName(),attr->getNamespace());
654                     if (wrapper.fail() || !wrapper->getHeader())
655                         continue;
656                 
657                     Iterator<string> vals=attr->getSingleByteValues();
658                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
659                         char* principal=const_cast<char*>(vals.next().c_str());
660                         pn->SetHeader(pfc,"remote-user:",principal);
661                         pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
662                         if (pfc->pFilterContext)
663                             strcpy(static_cast<char*>(pfc->pFilterContext),principal);
664                     }
665                     else {
666                         int it=0;
667                         string header;
668                         string hname=string(wrapper->getHeader()) + ':';
669                         GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
670                         if (!buf.empty()) {
671                             header=buf;
672                             it++;
673                         }
674                         for (; vals.hasNext(); it++) {
675                             string value = vals.next();
676                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
677                                     pos != string::npos;
678                                     pos = value.find_first_of(";", pos)) {
679                                 value.insert(pos, "\\");
680                                 pos += 2;
681                             }
682                             if (it == 0)
683                                 header=value;
684                             else
685                                 header=header + ';' + value;
686                         }
687                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
688                         }
689                 }
690             }
691         }
692     
693         // clean up memory
694         for (int k = 0; k < assertions.size(); k++)
695           delete assertions[k];
696         delete sso_statement;
697
698         return SF_STATUS_REQ_NEXT_NOTIFICATION;
699     }
700     catch(bad_alloc) {
701         return WriteClientError(pfc,"Out of Memory");
702     }
703     catch(DWORD e) {
704         if (e==ERROR_NO_DATA)
705             return WriteClientError(pfc,"A required variable or header was empty.");
706         else
707             return WriteClientError(pfc,"Server detected unexpected IIS error.");
708     }
709 #ifndef _DEBUG
710     catch(...) {
711         return WriteClientError(pfc,"Server caught an unknown exception.");
712     }
713 #endif
714
715     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
716 }
717
718 IRequestMapper::Settings map_request(
719     LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const char* hostname, string& target
720     )
721 {
722     dynabuf ssl(5);
723     dynabuf port(10);
724     dynabuf url(256);
725     GetServerVariable(lpECB,"HTTPS",ssl,5);
726     GetServerVariable(lpECB,"SERVER_PORT",port,10);
727     GetServerVariable(lpECB,"URL",url,255);
728     bool SSL=(ssl=="on");
729     
730     if (!url.empty())
731         target=static_cast<char*>(url);
732     if (port!=(SSL ? "443" : "80"))
733         target = ':' + static_cast<char*>(port) + target;
734
735     if (g_bNormalizeRequest) {
736         target = string(SSL ? "https://" : "http://") + hostname + target;
737         return mapper->getSettingsFromParsedURL(lpECB->lpszMethod,hostname,strtoul(port,NULL,10),url);
738     }
739     else {
740         dynabuf name(64);
741         GetServerVariable(lpECB,"SERVER_NAME",name,64);
742         target = string(SSL ? "https://" : "http://") + static_cast<char*>(name) + target;
743         return mapper->getSettingsFromParsedURL((SSL ? "https" : "http"),name,strtoul(port,NULL,10),url);
744     }
745 }
746
747 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
748 {
749     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
750     static const char* ctype="Content-Type: text/html\r\n";
751     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
752     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
753     DWORD resplen=strlen(xmsg);
754     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
755     resplen=strlen(msg);
756     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
757     static const char* xmsg2="</BODY></HTML>";
758     resplen=strlen(xmsg2);
759     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
760     return HSE_STATUS_SUCCESS;
761 }
762
763 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
764 {
765     const IPropertySet* props=app->getPropertySet("Errors");
766     if (props) {
767         pair<bool,const char*> p=props->getString(page);
768         if (p.first) {
769             ifstream infile(p.second);
770             if (!infile.fail()) {
771                 const char* res = mlp.run(infile,props);
772                 if (res) {
773                     static const char* ctype="Content-Type: text/html\r\n";
774                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
775                     DWORD resplen=strlen(res);
776                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
777                     return HSE_STATUS_SUCCESS;
778                 }
779             }
780         }
781     }
782     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
783     return WriteClientError(lpECB,"Unable to open error template, check settings.");
784 }
785
786 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
787 {
788     string targeturl;
789     const IApplication* application=NULL;
790     try
791     {
792         ostringstream threadid;
793         threadid << "[" << getpid() << "] shire_handler" << '\0';
794         saml::NDC ndc(threadid.str().c_str());
795
796         // Determine web site number. This can't really fail, I don't think.
797         dynabuf buf(128);
798         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
799
800         // Match site instance to host name, skip if no match.
801         map<string,string>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
802         if (map_i==g_Sites.end())
803             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
804             
805         const string& site=map_i->second;
806
807         // We lock the configuration system for the duration.
808         IConfig* conf=g_Config->getINI();
809         Locker locker(conf);
810         
811         // Map request to application and content settings.
812         string targeturl;
813         IRequestMapper* mapper=conf->getRequestMapper();
814         Locker locker2(mapper);
815         IRequestMapper::Settings settings=map_request(lpECB,mapper,site.c_str(),targeturl);
816         pair<bool,const char*> application_id=settings.first->getString("applicationId");
817         application=conf->getApplication(application_id.second);
818         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
819         if (!application || !sessionProps)
820             return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
821
822         SHIRE shire(application);
823
824         // Make sure we only process the SHIRE requests.
825         if (!strstr(targeturl.c_str(),shire.getShireURL(targeturl.c_str())))
826             return WriteClientError(lpECB,"The request's application and associated shireURL setting are inconsistent.");;
827
828         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
829
830         // Make sure this is SSL, if it should be
831         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
832         if (!shireSSL.first || shireSSL.second) {
833             GetServerVariable(lpECB,"HTTPS",buf,10);
834             if (buf!="on")
835                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
836         }
837         
838         // If this is a GET, we manufacture an AuthnRequest.
839         if (!stricmp(lpECB->lpszMethod,"GET")) {
840             const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
841             if (!areq)
842                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
843             targeturl = string("Location: ") + areq + "\r\n"
844                 "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
845                 "Cache-Control: private,no-store,no-cache\r\n"
846                 "Connection: close\r\n";
847             HSE_SEND_HEADER_EX_INFO hinfo;
848             hinfo.pszStatus="302 Moved";
849             hinfo.pszHeader=targeturl.c_str();
850             hinfo.cchStatus=9;
851             hinfo.cchHeader=targeturl.length();
852             hinfo.fKeepConn=FALSE;
853             if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
854                 return HSE_STATUS_SUCCESS;
855             return HSE_STATUS_ERROR;
856         }
857         else if (stricmp(lpECB->lpszMethod,"POST"))
858             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
859
860         // Sure sure this POST is an appropriate content type
861         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
862             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
863     
864         // Read the data.
865         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
866         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
867             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
868         else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
869             string cgistr;
870             char buf[8192];
871             DWORD datalen=lpECB->cbTotalBytes;
872             while (datalen) {
873                 DWORD buflen=8192;
874                 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
875                 if (!ret || !buflen)
876                     throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
877                 cgistr.append(buf,buflen);
878                 datalen-=buflen;
879             }
880             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
881         }
882         else
883             elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
884     
885         // Make sure the SAML Response parameter exists
886         if (!elements.first || !*elements.first)
887             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
888     
889         // Make sure the target parameter exists
890         if (!elements.second || !*elements.second)
891             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
892             
893         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
894
895         // Process the post.
896         string cookie;
897         RPCError* status=NULL;
898         ShibMLP markupProcessor;
899         markupProcessor.insert("requestURL", targeturl.c_str());
900         try {
901             status = shire.sessionCreate(elements.first,buf,cookie);
902         }
903         catch (ShibTargetException &e) {
904             markupProcessor.insert("errorType", "Session Creation Service Error");
905             markupProcessor.insert("errorText", e.what());
906             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
907             return WriteClientError(lpECB, application, "shire", markupProcessor);
908         }
909 #ifndef _DEBUG
910         catch (...) {
911             markupProcessor.insert("errorType", "Session Creation Service Error");
912             markupProcessor.insert("errorText", "Unexpected Exception");
913             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
914             return WriteClientError(lpECB, application, "shire", markupProcessor);
915         }
916 #endif
917
918         if (status->isError()) {
919             if (status->isRetryable()) {
920                 delete status;
921                 const char* loc=shire.getAuthnRequest(elements.second);
922                 DWORD len=strlen(loc);
923                 if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_URL_REDIRECT_RESP,(LPVOID)loc,&len,0))
924                     return HSE_STATUS_SUCCESS;
925                 return HSE_STATUS_ERROR;
926             }
927     
928             // Return this error to the user.
929             markupProcessor.insert(*status);
930             delete status;
931             return WriteClientError(lpECB,application,"shire",markupProcessor);
932         }
933         delete status;
934     
935         // We've got a good session, set the cookie and redirect to target.
936         cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
937             "Location: " + elements.second + "\r\n"
938             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
939             "Cache-Control: private,no-store,no-cache\r\n"
940             "Connection: close\r\n";
941         HSE_SEND_HEADER_EX_INFO hinfo;
942         hinfo.pszStatus="302 Moved";
943         hinfo.pszHeader=cookie.c_str();
944         hinfo.cchStatus=9;
945         hinfo.cchHeader=cookie.length();
946         hinfo.fKeepConn=FALSE;
947         if (lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER_EX,&hinfo,0,0))
948             return HSE_STATUS_SUCCESS;
949     }
950     catch (ShibTargetException &e) {
951         if (application) {
952             ShibMLP markupProcessor;
953             markupProcessor.insert("requestURL", targeturl.c_str());
954             markupProcessor.insert("errorType", "Session Creation Service Error");
955             markupProcessor.insert("errorText", e.what());
956             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
957             return WriteClientError(lpECB,application,"shire",markupProcessor);
958         }
959     }
960 #ifndef _DEBUG
961     catch (...) {
962         if (application) {
963             ShibMLP markupProcessor;
964             markupProcessor.insert("requestURL", targeturl.c_str());
965             markupProcessor.insert("errorType", "Session Creation Service Error");
966             markupProcessor.insert("errorText", "Unexpected Exception");
967             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
968             return WriteClientError(lpECB,application,"shire",markupProcessor);
969         }
970     }
971 #endif
972     
973     return HSE_STATUS_ERROR;
974 }