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