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