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