4465e4fce126f276f5a9a2e1c2dffc1c2b0a5fd9
[shibboleth/sp.git] / isapi_shib / isapi_shib.cpp
1 /*
2  *  Copyright 2001-2007 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  * isapi_shib.cpp
19  * 
20  * Shibboleth ISAPI filter
21  */
22
23 #define SHIBSP_LITE
24 #include "config_win32.h"
25
26 #define _CRT_NONSTDC_NO_DEPRECATE 1
27 #define _CRT_SECURE_NO_DEPRECATE 1
28
29 #include <shibsp/AbstractSPRequest.h>
30 #include <shibsp/SPConfig.h>
31 #include <shibsp/ServiceProvider.h>
32 #include <xmltooling/unicode.h>
33 #include <xmltooling/XMLToolingConfig.h>
34 #include <xmltooling/util/NDC.h>
35 #include <xmltooling/util/XMLConstants.h>
36 #include <xmltooling/util/XMLHelper.h>
37 #include <xercesc/util/Base64.hpp>
38 #include <xercesc/util/XMLUniDefs.hpp>
39
40 #include <set>
41 #include <sstream>
42 #include <fstream>
43 #include <process.h>
44
45 #include <windows.h>
46 #include <httpfilt.h>
47 #include <httpext.h>
48
49 using namespace shibsp;
50 using namespace xmltooling;
51 using namespace xercesc;
52 using namespace std;
53
54 // globals
55 namespace {
56     static const XMLCh path[] =             UNICODE_LITERAL_4(p,a,t,h);
57     static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
58     static const XMLCh name[] =             UNICODE_LITERAL_4(n,a,m,e);
59     static const XMLCh port[] =             UNICODE_LITERAL_4(p,o,r,t);
60     static const XMLCh sslport[] =          UNICODE_LITERAL_7(s,s,l,p,o,r,t);
61     static const XMLCh scheme[] =           UNICODE_LITERAL_6(s,c,h,e,m,e);
62     static const XMLCh id[] =               UNICODE_LITERAL_2(i,d);
63     static const XMLCh Implementation[] =   UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
64     static const XMLCh ISAPI[] =            UNICODE_LITERAL_5(I,S,A,P,I);
65     static const XMLCh Alias[] =            UNICODE_LITERAL_5(A,l,i,a,s);
66     static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
67     static const XMLCh Site[] =             UNICODE_LITERAL_4(S,i,t,e);
68
69     struct site_t {
70         site_t(const DOMElement* e)
71         {
72             auto_ptr_char n(e->getAttributeNS(NULL,name));
73             auto_ptr_char s(e->getAttributeNS(NULL,scheme));
74             auto_ptr_char p(e->getAttributeNS(NULL,port));
75             auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
76             if (n.get()) m_name=n.get();
77             if (s.get()) m_scheme=s.get();
78             if (p.get()) m_port=p.get();
79             if (p2.get()) m_sslport=p2.get();
80             e = XMLHelper::getFirstChildElement(e, Alias);
81             while (e) {
82                 if (e->hasChildNodes()) {
83                     auto_ptr_char alias(e->getFirstChild()->getNodeValue());
84                     m_aliases.insert(alias.get());
85                 }
86                 e = XMLHelper::getNextSiblingElement(e, Alias);
87             }
88         }
89         string m_scheme,m_port,m_sslport,m_name;
90         set<string> m_aliases;
91     };
92     
93     HINSTANCE g_hinstDLL;
94     SPConfig* g_Config = NULL;
95     map<string,site_t> g_Sites;
96     bool g_bNormalizeRequest = true;
97     string g_unsetHeaderValue;
98     bool g_checkSpoofing = true;
99     vector<string> g_NoCerts;
100 }
101
102 BOOL LogEvent(
103     LPCSTR  lpUNCServerName,
104     WORD  wType,
105     DWORD  dwEventID,
106     PSID  lpUserSid,
107     LPCSTR  message)
108 {
109     LPCSTR  messages[] = {message, NULL};
110     
111     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
112     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
113     return (DeregisterEventSource(hElog) && res);
114 }
115
116 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
117 {
118     if (fdwReason==DLL_PROCESS_ATTACH)
119         g_hinstDLL=hinstDLL;
120     return TRUE;
121 }
122
123 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
124 {
125     if (!pVer)
126         return FALSE;
127         
128     if (!g_Config) {
129         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
130                 "Extension mode startup not possible, is the DLL loaded as a filter?");
131         return FALSE;
132     }
133
134     pVer->dwExtensionVersion=HSE_VERSION;
135     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
136     return TRUE;
137 }
138
139 extern "C" BOOL WINAPI TerminateExtension(DWORD)
140 {
141     return TRUE;    // cleanup should happen when filter unloads
142 }
143
144 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
145 {
146     if (!pVer)
147         return FALSE;
148     else if (g_Config) {
149         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
150                 "Reentrant filter initialization, ignoring...");
151         return TRUE;
152     }
153
154     LPCSTR schemadir=getenv("SHIBSP_SCHEMAS");
155     if (!schemadir)
156         schemadir=SHIBSP_SCHEMAS;
157     LPCSTR config=getenv("SHIBSP_CONFIG");
158     if (!config)
159         config=SHIBSP_CONFIG;
160     g_Config=&SPConfig::getConfig();
161     g_Config->setFeatures(
162         SPConfig::Listener |
163         SPConfig::Caching |
164         SPConfig::RequestMapping |
165         SPConfig::InProcess |
166         SPConfig::Logging |
167         SPConfig::Handlers
168         );
169     if (!g_Config->init(schemadir)) {
170         g_Config=NULL;
171         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
172                 "Filter startup failed during library initialization, check native log for help.");
173         return FALSE;
174     }
175
176     try {
177         DOMDocument* dummydoc=XMLToolingConfig::getConfig().getParser().newDocument();
178         XercesJanitor<DOMDocument> docjanitor(dummydoc);
179         DOMElement* dummy = dummydoc->createElementNS(NULL,path);
180         auto_ptr_XMLCh src(config);
181         dummy->setAttributeNS(NULL,path,src.get());
182         dummy->setAttributeNS(NULL,validate,xmlconstants::XML_ONE);
183
184         g_Config->setServiceProvider(g_Config->ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER,dummy));
185         g_Config->getServiceProvider()->init();
186     }
187     catch (exception& ex) {
188         g_Config->term();
189         g_Config=NULL;
190         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, ex.what());
191         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
192                 "Filter startup failed to load configuration, check native log for details.");
193         return FALSE;
194     }
195     
196     // Access the implementation-specifics for site mappings.
197     ServiceProvider* sp=g_Config->getServiceProvider();
198     Locker locker(sp);
199     const PropertySet* props=sp->getPropertySet("InProcess");
200     if (props) {
201         pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
202         if (unsetValue.first)
203             g_unsetHeaderValue = unsetValue.second;
204         pair<bool,bool> checkSpoofing=props->getBool("checkSpoofing");
205         if (checkSpoofing.first && !checkSpoofing.second)
206             g_checkSpoofing = false;
207         const DOMElement* impl=XMLHelper::getFirstChildElement(props->getElement(),Implementation);
208         if (impl && (impl=XMLHelper::getFirstChildElement(impl,ISAPI))) {
209             const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
210             g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
211             impl=XMLHelper::getFirstChildElement(impl,Site);
212             while (impl) {
213                 auto_ptr_char id(impl->getAttributeNS(NULL,id));
214                 if (id.get())
215                     g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
216                 impl=XMLHelper::getNextSiblingElement(impl,Site);
217             }
218         }
219     }
220
221     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
222     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
223     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
224                    SF_NOTIFY_SECURE_PORT |
225                    SF_NOTIFY_NONSECURE_PORT |
226                    SF_NOTIFY_PREPROC_HEADERS |
227                    SF_NOTIFY_LOG);
228     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
229     return TRUE;
230 }
231
232 extern "C" BOOL WINAPI TerminateFilter(DWORD)
233 {
234     if (g_Config)
235         g_Config->term();
236     g_Config = NULL;
237     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
238     return TRUE;
239 }
240
241 /* Next up, some suck-free versions of various APIs.
242
243    You DON'T require people to guess the buffer size and THEN tell them the right size.
244    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
245    constant strings aren't typed as such, making it just that much harder. These versions
246    are now updated to use a special growable buffer object, modeled after the standard
247    string class. The standard string won't work because they left out the option to
248    pre-allocate a non-constant buffer.
249 */
250
251 class dynabuf
252 {
253 public:
254     dynabuf() { bufptr=NULL; buflen=0; }
255     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
256     ~dynabuf() { delete[] bufptr; }
257     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
258     size_t size() const { return buflen; }
259     bool empty() const { return length()==0; }
260     void reserve(size_t s, bool keep=false);
261     void erase() { if (bufptr) memset(bufptr,0,buflen); }
262     operator char*() { return bufptr; }
263     bool operator ==(const char* s) const;
264     bool operator !=(const char* s) const { return !(*this==s); }
265 private:
266     char* bufptr;
267     size_t buflen;
268 };
269
270 void dynabuf::reserve(size_t s, bool keep)
271 {
272     if (s<=buflen)
273         return;
274     char* p=new char[s];
275     if (keep)
276         while (buflen--)
277             p[buflen]=bufptr[buflen];
278     buflen=s;
279     delete[] bufptr;
280     bufptr=p;
281 }
282
283 bool dynabuf::operator==(const char* s) const
284 {
285     if (buflen==NULL || s==NULL)
286         return (buflen==NULL && s==NULL);
287     else
288         return strcmp(bufptr,s)==0;
289 }
290
291 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
292 {
293     s.reserve(size);
294     s.erase();
295     size=s.size();
296
297     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size)) {
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 GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
310 {
311     s.reserve(size);
312     s.erase();
313     size=s.size();
314
315     while (!lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size)) {
316         // Grumble. Check the error.
317         DWORD e=GetLastError();
318         if (e==ERROR_INSUFFICIENT_BUFFER)
319             s.reserve(size);
320         else
321             break;
322     }
323     if (bRequired && s.empty())
324         throw ERROR_NO_DATA;
325 }
326
327 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
328                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
329 {
330     s.reserve(size);
331     s.erase();
332     size=s.size();
333
334     while (!pn->GetHeader(pfc,lpszName,s,&size)) {
335         // Grumble. Check the error.
336         DWORD e=GetLastError();
337         if (e==ERROR_INSUFFICIENT_BUFFER)
338             s.reserve(size);
339         else
340             break;
341     }
342     if (bRequired && s.empty())
343         throw ERROR_NO_DATA;
344 }
345
346 /****************************************************************************/
347 // ISAPI Filter
348
349 class ShibTargetIsapiF : public AbstractSPRequest
350 {
351   PHTTP_FILTER_CONTEXT m_pfc;
352   PHTTP_FILTER_PREPROC_HEADERS m_pn;
353   multimap<string,string> m_headers;
354   int m_port;
355   string m_scheme,m_hostname,m_uri;
356   mutable string m_remote_addr,m_content_type,m_method;
357   dynabuf m_allhttp;
358
359 public:
360   ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
361       : m_pfc(pfc), m_pn(pn), m_allhttp(4096) {
362
363     // URL path always come from IIS.
364     dynabuf var(256);
365     GetHeader(pn,pfc,"url",var,256,false);
366     m_uri = var;
367
368     // Port may come from IIS or from site def.
369     if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty())) {
370         GetServerVariable(pfc,"SERVER_PORT",var,10);
371         m_port = atoi(var);
372     }
373     else if (pfc->fIsSecurePort) {
374         m_port = atoi(site.m_sslport.c_str());
375     }
376     else {
377         m_port = atoi(site.m_port.c_str());
378     }
379     
380     // Scheme may come from site def or be derived from IIS.
381     m_scheme=site.m_scheme;
382     if (m_scheme.empty() || !g_bNormalizeRequest)
383         m_scheme=pfc->fIsSecurePort ? "https" : "http";
384
385     GetServerVariable(pfc,"SERVER_NAME",var,32);
386
387     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
388     m_hostname = var;
389     if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
390         m_hostname=site.m_name;
391   }
392   ~ShibTargetIsapiF() { }
393
394   const char* getScheme() const {
395     return m_scheme.c_str();
396   }
397   const char* getHostname() const {
398     return m_hostname.c_str();
399   }
400   int getPort() const {
401     return m_port;
402   }
403   const char* getRequestURI() const {
404     return m_uri.c_str();
405   }
406   const char* getMethod() const {
407     if (m_method.empty()) {
408         dynabuf var(5);
409         GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
410         if (!var.empty())
411             m_method = var;
412     }
413     return m_method.c_str();
414   }
415   string getContentType() const {
416     if (m_content_type.empty()) {
417         dynabuf var(32);
418         GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
419         if (!var.empty())
420             m_content_type = var;
421     }
422     return m_content_type;
423   }
424   long getContentLength() const {
425       return 0;
426   }
427   string getRemoteAddr() const {
428     if (m_remote_addr.empty()) {
429         dynabuf var(16);
430         GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
431         if (!var.empty())
432             m_remote_addr = var;
433     }
434     return m_remote_addr;
435   }
436   void log(SPLogLevel level, const string& msg) {
437     AbstractSPRequest::log(level,msg);
438     if (level >= SPError)
439         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
440   }
441   void clearHeader(const char* rawname, const char* cginame) {
442     if (g_checkSpoofing) {
443         if (m_allhttp.empty())
444                 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
445         if (strstr(m_allhttp, cginame))
446             throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
447     }
448     string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
449     hdr += ':';
450     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
451   }
452   void setHeader(const char* name, const char* value) {
453     string hdr(name);
454     hdr += ':';
455     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
456   }
457   string getHeader(const char* name) const {
458     string hdr(name);
459     hdr += ':';
460     dynabuf buf(256);
461     GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
462     return string(buf);
463   }
464   void setRemoteUser(const char* user) {
465     setHeader("remote-user", user);
466   }
467   string getRemoteUser() const {
468     return getHeader("remote-user");
469   }
470   void setResponseHeader(const char* name, const char* value) {
471     // Set for later.
472     if (value)
473         m_headers.insert(make_pair(name,value));
474     else
475         m_headers.erase(name);
476   }
477   long sendResponse(istream& in, long status) {
478     string hdr = string("Connection: close\r\n");
479     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
480         hdr += i->first + ": " + i->second + "\r\n";
481     hdr += "\r\n";
482     const char* codestr="200 OK";
483     switch (status) {
484         case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
485         case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
486         case XMLTOOLING_HTTP_STATUS_ERROR:    codestr="500 Server Error"; break;
487     }
488     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
489     char buf[1024];
490     while (in) {
491         in.read(buf,1024);
492         DWORD resplen = in.gcount();
493         m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
494     }
495     return SF_STATUS_REQ_FINISHED;
496   }
497   long sendRedirect(const char* url) {
498     // XXX: Don't support the httpRedirect option, yet.
499     string hdr=string("Location: ") + url + "\r\n"
500       "Content-Type: text/html\r\n"
501       "Content-Length: 40\r\n"
502       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
503       "Cache-Control: private,no-store,no-cache\r\n";
504     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
505         hdr += i->first + ": " + i->second + "\r\n";
506     hdr += "\r\n";
507     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
508     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
509     DWORD resplen=40;
510     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
511     return SF_STATUS_REQ_FINISHED;
512   }
513   long returnDecline() {
514       return SF_STATUS_REQ_NEXT_NOTIFICATION;
515   }
516   long returnOK() {
517     return SF_STATUS_REQ_NEXT_NOTIFICATION;
518   }
519
520   const vector<string>& getClientCertificates() const {
521       return g_NoCerts;
522   }
523   
524   // The filter never processes the POST, so stub these methods.
525   const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
526   const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
527 };
528
529 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
530 {
531     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
532     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
533     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
534     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
535                             "<H1>Shibboleth Filter Error</H1>";
536     DWORD resplen=strlen(xmsg);
537     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
538     resplen=strlen(msg);
539     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
540     static const char* xmsg2="</BODY></HTML>";
541     resplen=strlen(xmsg2);
542     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
543     return SF_STATUS_REQ_FINISHED;
544 }
545
546 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
547 {
548     // Is this a log notification?
549     if (notificationType==SF_NOTIFY_LOG)
550     {
551         if (pfc->pFilterContext)
552             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
553         return SF_STATUS_REQ_NEXT_NOTIFICATION;
554     }
555
556     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
557     try
558     {
559         // Determine web site number. This can't really fail, I don't think.
560         dynabuf buf(128);
561         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
562
563         // Match site instance to host name, skip if no match.
564         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
565         if (map_i==g_Sites.end())
566             return SF_STATUS_REQ_NEXT_NOTIFICATION;
567             
568         ostringstream threadid;
569         threadid << "[" << getpid() << "] isapi_shib" << '\0';
570         xmltooling::NDC ndc(threadid.str().c_str());
571
572         ShibTargetIsapiF stf(pfc, pn, map_i->second);
573
574         // "false" because we don't override the Shib settings
575         pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
576         if (res.first) return res.second;
577
578         // "false" because we don't override the Shib settings
579         res = stf.getServiceProvider().doExport(stf);
580         if (res.first) return res.second;
581
582         res = stf.getServiceProvider().doAuthorization(stf);
583         if (res.first) return res.second;
584
585         return SF_STATUS_REQ_NEXT_NOTIFICATION;
586     }
587     catch(bad_alloc) {
588         return WriteClientError(pfc,"Out of Memory");
589     }
590     catch(long e) {
591         if (e==ERROR_NO_DATA)
592             return WriteClientError(pfc,"A required variable or header was empty.");
593         else
594             return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
595     }
596     catch (exception& e) {
597         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
598         return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
599     }
600 #ifndef _DEBUG
601     catch(...) {
602         return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
603     }
604 #endif
605
606     return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
607 }
608         
609
610 /****************************************************************************/
611 // ISAPI Extension
612
613 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
614 {
615     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
616     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
617     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
618     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
619     DWORD resplen=strlen(xmsg);
620     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
621     resplen=strlen(msg);
622     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
623     static const char* xmsg2="</BODY></HTML>";
624     resplen=strlen(xmsg2);
625     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
626     return HSE_STATUS_SUCCESS;
627 }
628
629
630 class ShibTargetIsapiE : public AbstractSPRequest
631 {
632   LPEXTENSION_CONTROL_BLOCK m_lpECB;
633   multimap<string,string> m_headers;
634   mutable vector<string> m_certs;
635   mutable string m_body;
636   mutable bool m_gotBody;
637   int m_port;
638   string m_scheme,m_hostname,m_uri;
639   mutable string m_remote_addr,m_remote_user;
640   
641 public:
642   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
643     dynabuf ssl(5);
644     GetServerVariable(lpECB,"HTTPS",ssl,5);
645     bool SSL=(ssl=="on" || ssl=="ON");
646
647     // Scheme may come from site def or be derived from IIS.
648     m_scheme=site.m_scheme;
649     if (m_scheme.empty() || !g_bNormalizeRequest)
650         m_scheme = SSL ? "https" : "http";
651
652     // URL path always come from IIS.
653     dynabuf url(256);
654     GetServerVariable(lpECB,"URL",url,255);
655
656     // Port may come from IIS or from site def.
657     dynabuf port(11);
658     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
659         GetServerVariable(lpECB,"SERVER_PORT",port,10);
660     else if (SSL) {
661         strncpy(port,site.m_sslport.c_str(),10);
662         static_cast<char*>(port)[10]=0;
663     }
664     else {
665         strncpy(port,site.m_port.c_str(),10);
666         static_cast<char*>(port)[10]=0;
667     }
668     m_port = atoi(port);
669
670     dynabuf var(32);
671     GetServerVariable(lpECB, "SERVER_NAME", var, 32);
672
673     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
674     m_hostname=var;
675     if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
676         m_hostname=site.m_name;
677
678     /*
679      * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
680      * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
681      * which is the default. No perfect way to tell, but we can take a good guess by checking
682      * whether the URL is a substring of the PATH_INFO:
683      * 
684      * e.g. for /Shibboleth.sso/SAML/POST
685      * 
686      *  Bad mode (default):
687      *      URL:        /Shibboleth.sso
688      *      PathInfo:   /Shibboleth.sso/SAML/POST
689      * 
690      *  Good mode:
691      *      URL:        /Shibboleth.sso
692      *      PathInfo:   /SAML/POST
693      */
694     
695     // Clearly we're only in bad mode if path info exists at all.
696     if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
697         if (strstr(lpECB->lpszPathInfo,url))
698             // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
699             m_uri = lpECB->lpszPathInfo;
700         else {
701             m_uri = url;
702             m_uri += lpECB->lpszPathInfo;
703         }
704     }
705     else {
706         m_uri = url;
707     }
708     
709     // For consistency with Apache, let's add the query string.
710     if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
711         m_uri += '?';
712         m_uri += lpECB->lpszQueryString;
713     }
714   }
715   ~ShibTargetIsapiE() { }
716
717   const char* getScheme() const {
718     return m_scheme.c_str();
719   }
720   const char* getHostname() const {
721     return m_hostname.c_str();
722   }
723   int getPort() const {
724     return m_port;
725   }
726   const char* getRequestURI() const {
727     return m_uri.c_str();
728   }
729   const char* getMethod() const {
730     return m_lpECB->lpszMethod;
731   }
732   string getContentType() const {
733     return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
734   }
735   long getContentLength() const {
736       return m_lpECB->cbTotalBytes;
737   }
738   string getRemoteUser() const {
739     if (m_remote_user.empty()) {
740         dynabuf var(16);
741         GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
742         if (!var.empty())
743             m_remote_user = var;
744     }
745     return m_remote_user;
746   }
747   string getRemoteAddr() const {
748     if (m_remote_addr.empty()) {
749         dynabuf var(16);
750         GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
751         if (!var.empty())
752             m_remote_addr = var;
753     }
754     return m_remote_addr;
755   }
756   void log(SPLogLevel level, const string& msg) const {
757       AbstractSPRequest::log(level,msg);
758       if (level >= SPError)
759           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
760   }
761   string getHeader(const char* name) const {
762     string hdr("HTTP_");
763     for (; *name; ++name) {
764         if (*name=='-')
765             hdr += '_';
766         else
767             hdr += toupper(*name);
768     }
769     dynabuf buf(128);
770     GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
771     return buf.empty() ? "" : buf;
772   }
773   void setResponseHeader(const char* name, const char* value) {
774     // Set for later.
775     if (value)
776         m_headers.insert(make_pair(name,value));
777     else
778         m_headers.erase(name);
779   }
780   const char* getQueryString() const {
781     return m_lpECB->lpszQueryString;
782   }
783   const char* getRequestBody() const {
784     if (m_gotBody)
785         return m_body.c_str();
786     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
787         throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
788     else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
789       m_gotBody=true;
790       char buf[8192];
791       DWORD datalen=m_lpECB->cbTotalBytes;
792       while (datalen) {
793         DWORD buflen=8192;
794         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
795         if (!ret || !buflen)
796             throw IOException("Error reading request body from browser.");
797         m_body.append(buf, buflen);
798         datalen-=buflen;
799       }
800     }
801     else if (m_lpECB->cbAvailable) {
802         m_gotBody=true;
803         m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
804     }
805     return m_body.c_str();
806   }
807   long sendResponse(istream& in, long status) {
808     string hdr = string("Connection: close\r\n");
809     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
810         hdr += i->first + ": " + i->second + "\r\n";
811     hdr += "\r\n";
812     const char* codestr="200 OK";
813     switch (status) {
814         case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
815         case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
816         case XMLTOOLING_HTTP_STATUS_ERROR:    codestr="500 Server Error"; break;
817     }
818     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
819     char buf[1024];
820     while (in) {
821         in.read(buf,1024);
822         DWORD resplen = in.gcount();
823         m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
824     }
825     return HSE_STATUS_SUCCESS;
826   }
827   long sendRedirect(const char* url) {
828     string hdr=string("Location: ") + url + "\r\n"
829       "Content-Type: text/html\r\n"
830       "Content-Length: 40\r\n"
831       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
832       "Cache-Control: private,no-store,no-cache\r\n";
833     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
834         hdr += i->first + ": " + i->second + "\r\n";
835     hdr += "\r\n";
836     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
837     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
838     DWORD resplen=40;
839     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
840     return HSE_STATUS_SUCCESS;
841   }
842   // Decline happens in the POST processor if this isn't the shire url
843   // Note that it can also happen with HTAccess, but we don't support that, yet.
844   long returnDecline() {
845     return WriteClientError(
846         m_lpECB,
847         "ISAPI extension can only be invoked to process Shibboleth protocol requests."
848                 "Make sure the mapped file extension doesn't match actual content."
849         );
850   }
851   long returnOK() {
852       return HSE_STATUS_SUCCESS;
853   }
854
855   const vector<string>& getClientCertificates() const {
856       if (m_certs.empty()) {
857         char CertificateBuf[8192];
858         CERT_CONTEXT_EX ccex;
859         ccex.cbAllocated = sizeof(CertificateBuf);
860         ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
861         DWORD dwSize = sizeof(ccex);
862
863         if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
864             if (ccex.CertContext.cbCertEncoded) {
865                 unsigned int outlen;
866                 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
867                 m_certs.push_back(reinterpret_cast<char*>(serialized));
868                 XMLString::release(&serialized);
869             }
870         }
871       }
872       return m_certs;
873   }
874
875   // Not used in the extension.
876   void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
877   void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
878   void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
879 };
880
881 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
882 {
883     try {
884         ostringstream threadid;
885         threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
886         xmltooling::NDC ndc(threadid.str().c_str());
887
888         // Determine web site number. This can't really fail, I don't think.
889         dynabuf buf(128);
890         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
891
892         // Match site instance to host name, skip if no match.
893         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
894         if (map_i==g_Sites.end())
895             return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
896
897         ShibTargetIsapiE ste(lpECB, map_i->second);
898         pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
899         if (res.first) return res.second;
900         
901         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
902
903     }
904     catch(bad_alloc) {
905         return WriteClientError(lpECB,"Out of Memory");
906     }
907     catch(long e) {
908         if (e==ERROR_NO_DATA)
909             return WriteClientError(lpECB,"A required variable or header was empty.");
910         else
911             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
912     }
913     catch (exception& e) {
914         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
915         return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
916     }
917 #ifndef _DEBUG
918     catch(...) {
919         return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
920     }
921 #endif
922
923     // If we get here we've got an error.
924     return HSE_STATUS_ERROR;
925 }