Add logging when catching unknown errors.
[shibboleth/sp.git] / isapi_shib / isapi_shib.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * isapi_shib.cpp
19  * 
20  * Shibboleth ISAPI filter
21  */
22
23 #define SHIBSP_LITE
24 #include "config_win32.h"
25
26 #define _CRT_NONSTDC_NO_DEPRECATE 1
27 #define _CRT_SECURE_NO_DEPRECATE 1
28
29 #include <shibsp/AbstractSPRequest.h>
30 #include <shibsp/SPConfig.h>
31 #include <shibsp/ServiceProvider.h>
32 #include <xmltooling/unicode.h>
33 #include <xmltooling/XMLToolingConfig.h>
34 #include <xmltooling/util/NDC.h>
35 #include <xmltooling/util/XMLConstants.h>
36 #include <xmltooling/util/XMLHelper.h>
37 #include <xercesc/util/Base64.hpp>
38 #include <xercesc/util/XMLUniDefs.hpp>
39
40 #include <set>
41 #include <sstream>
42 #include <fstream>
43 #include <process.h>
44
45 #include <windows.h>
46 #include <httpfilt.h>
47 #include <httpext.h>
48
49 using namespace shibsp;
50 using namespace xmltooling;
51 using namespace xercesc;
52 using namespace std;
53
54 // globals
55 namespace {
56     static const XMLCh path[] =             UNICODE_LITERAL_4(p,a,t,h);
57     static const XMLCh validate[] =         UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
58     static const XMLCh name[] =             UNICODE_LITERAL_4(n,a,m,e);
59     static const XMLCh port[] =             UNICODE_LITERAL_4(p,o,r,t);
60     static const XMLCh sslport[] =          UNICODE_LITERAL_7(s,s,l,p,o,r,t);
61     static const XMLCh scheme[] =           UNICODE_LITERAL_6(s,c,h,e,m,e);
62     static const XMLCh id[] =               UNICODE_LITERAL_2(i,d);
63     static const XMLCh Implementation[] =   UNICODE_LITERAL_14(I,m,p,l,e,m,e,n,t,a,t,i,o,n);
64     static const XMLCh ISAPI[] =            UNICODE_LITERAL_5(I,S,A,P,I);
65     static const XMLCh Alias[] =            UNICODE_LITERAL_5(A,l,i,a,s);
66     static const XMLCh normalizeRequest[] = UNICODE_LITERAL_16(n,o,r,m,a,l,i,z,e,R,e,q,u,e,s,t);
67     static const XMLCh Site[] =             UNICODE_LITERAL_4(S,i,t,e);
68
69     struct site_t {
70         site_t(const DOMElement* e)
71         {
72             auto_ptr_char n(e->getAttributeNS(NULL,name));
73             auto_ptr_char s(e->getAttributeNS(NULL,scheme));
74             auto_ptr_char p(e->getAttributeNS(NULL,port));
75             auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
76             if (n.get()) m_name=n.get();
77             if (s.get()) m_scheme=s.get();
78             if (p.get()) m_port=p.get();
79             if (p2.get()) m_sslport=p2.get();
80             e = XMLHelper::getFirstChildElement(e, Alias);
81             while (e) {
82                 if (e->hasChildNodes()) {
83                     auto_ptr_char alias(e->getFirstChild()->getNodeValue());
84                     m_aliases.insert(alias.get());
85                 }
86                 e = XMLHelper::getNextSiblingElement(e, Alias);
87             }
88         }
89         string m_scheme,m_port,m_sslport,m_name;
90         set<string> m_aliases;
91     };
92     
93     HINSTANCE g_hinstDLL;
94     SPConfig* g_Config = NULL;
95     map<string,site_t> g_Sites;
96     bool g_bNormalizeRequest = true;
97     string g_unsetHeaderValue;
98     bool g_checkSpoofing = true;
99     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_FORBIDDEN:codestr="403 Forbidden"; break;
488         case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
489         case XMLTOOLING_HTTP_STATUS_ERROR:    codestr="500 Server Error"; break;
490     }
491     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
492     char buf[1024];
493     while (in) {
494         in.read(buf,1024);
495         DWORD resplen = in.gcount();
496         m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
497     }
498     return SF_STATUS_REQ_FINISHED;
499   }
500   long sendRedirect(const char* url) {
501     // XXX: Don't support the httpRedirect option, yet.
502     string hdr=string("Location: ") + url + "\r\n"
503       "Content-Type: text/html\r\n"
504       "Content-Length: 40\r\n"
505       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
506       "Cache-Control: private,no-store,no-cache\r\n";
507     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
508         hdr += i->first + ": " + i->second + "\r\n";
509     hdr += "\r\n";
510     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
511     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
512     DWORD resplen=40;
513     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
514     return SF_STATUS_REQ_FINISHED;
515   }
516   long returnDecline() {
517       return SF_STATUS_REQ_NEXT_NOTIFICATION;
518   }
519   long returnOK() {
520     return SF_STATUS_REQ_NEXT_NOTIFICATION;
521   }
522
523   const vector<string>& getClientCertificates() const {
524       return g_NoCerts;
525   }
526   
527   // The filter never processes the POST, so stub these methods.
528   const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
529   const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
530 };
531
532 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
533 {
534     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
535     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
536     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
537     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
538                             "<H1>Shibboleth Filter Error</H1>";
539     DWORD resplen=strlen(xmsg);
540     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
541     resplen=strlen(msg);
542     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
543     static const char* xmsg2="</BODY></HTML>";
544     resplen=strlen(xmsg2);
545     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
546     return SF_STATUS_REQ_FINISHED;
547 }
548
549 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
550 {
551     // Is this a log notification?
552     if (notificationType==SF_NOTIFY_LOG)
553     {
554         if (pfc->pFilterContext)
555             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
556         return SF_STATUS_REQ_NEXT_NOTIFICATION;
557     }
558
559     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
560     try
561     {
562         // Determine web site number. This can't really fail, I don't think.
563         dynabuf buf(128);
564         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
565
566         // Match site instance to host name, skip if no match.
567         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
568         if (map_i==g_Sites.end())
569             return SF_STATUS_REQ_NEXT_NOTIFICATION;
570             
571         ostringstream threadid;
572         threadid << "[" << getpid() << "] isapi_shib" << '\0';
573         xmltooling::NDC ndc(threadid.str().c_str());
574
575         ShibTargetIsapiF stf(pfc, pn, map_i->second);
576
577         // "false" because we don't override the Shib settings
578         pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
579         if (res.first) return res.second;
580
581         // "false" because we don't override the Shib settings
582         res = stf.getServiceProvider().doExport(stf);
583         if (res.first) return res.second;
584
585         res = stf.getServiceProvider().doAuthorization(stf);
586         if (res.first) return res.second;
587
588         return SF_STATUS_REQ_NEXT_NOTIFICATION;
589     }
590     catch(bad_alloc) {
591         return WriteClientError(pfc,"Out of Memory");
592     }
593     catch(long e) {
594         if (e==ERROR_NO_DATA)
595             return WriteClientError(pfc,"A required variable or header was empty.");
596         else
597             return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
598     }
599     catch (exception& e) {
600         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
601         return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
602     }
603     catch(...) {
604         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
605         if (g_catchAll)
606             return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
607         throw;
608     }
609
610     return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
611 }
612         
613
614 /****************************************************************************/
615 // ISAPI Extension
616
617 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
618 {
619     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
620     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
621     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
622     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
623     DWORD resplen=strlen(xmsg);
624     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
625     resplen=strlen(msg);
626     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
627     static const char* xmsg2="</BODY></HTML>";
628     resplen=strlen(xmsg2);
629     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
630     return HSE_STATUS_SUCCESS;
631 }
632
633
634 class ShibTargetIsapiE : public AbstractSPRequest
635 {
636   LPEXTENSION_CONTROL_BLOCK m_lpECB;
637   multimap<string,string> m_headers;
638   mutable vector<string> m_certs;
639   mutable string m_body;
640   mutable bool m_gotBody;
641   int m_port;
642   string m_scheme,m_hostname,m_uri;
643   mutable string m_remote_addr,m_remote_user;
644   
645 public:
646   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
647     dynabuf ssl(5);
648     GetServerVariable(lpECB,"HTTPS",ssl,5);
649     bool SSL=(ssl=="on" || ssl=="ON");
650
651     // Scheme may come from site def or be derived from IIS.
652     m_scheme=site.m_scheme;
653     if (m_scheme.empty() || !g_bNormalizeRequest)
654         m_scheme = SSL ? "https" : "http";
655
656     // URL path always come from IIS.
657     dynabuf url(256);
658     GetServerVariable(lpECB,"URL",url,255);
659
660     // Port may come from IIS or from site def.
661     dynabuf port(11);
662     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
663         GetServerVariable(lpECB,"SERVER_PORT",port,10);
664     else if (SSL) {
665         strncpy(port,site.m_sslport.c_str(),10);
666         static_cast<char*>(port)[10]=0;
667     }
668     else {
669         strncpy(port,site.m_port.c_str(),10);
670         static_cast<char*>(port)[10]=0;
671     }
672     m_port = atoi(port);
673
674     dynabuf var(32);
675     GetServerVariable(lpECB, "SERVER_NAME", var, 32);
676
677     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
678     m_hostname=var;
679     if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
680         m_hostname=site.m_name;
681
682     /*
683      * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
684      * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
685      * which is the default. No perfect way to tell, but we can take a good guess by checking
686      * whether the URL is a substring of the PATH_INFO:
687      * 
688      * e.g. for /Shibboleth.sso/SAML/POST
689      * 
690      *  Bad mode (default):
691      *      URL:        /Shibboleth.sso
692      *      PathInfo:   /Shibboleth.sso/SAML/POST
693      * 
694      *  Good mode:
695      *      URL:        /Shibboleth.sso
696      *      PathInfo:   /SAML/POST
697      */
698     
699     // Clearly we're only in bad mode if path info exists at all.
700     if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
701         if (strstr(lpECB->lpszPathInfo,url))
702             // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
703             m_uri = lpECB->lpszPathInfo;
704         else {
705             m_uri = url;
706             m_uri += lpECB->lpszPathInfo;
707         }
708     }
709     else {
710         m_uri = url;
711     }
712     
713     // For consistency with Apache, let's add the query string.
714     if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
715         m_uri += '?';
716         m_uri += lpECB->lpszQueryString;
717     }
718   }
719   ~ShibTargetIsapiE() { }
720
721   const char* getScheme() const {
722     return m_scheme.c_str();
723   }
724   const char* getHostname() const {
725     return m_hostname.c_str();
726   }
727   int getPort() const {
728     return m_port;
729   }
730   const char* getRequestURI() const {
731     return m_uri.c_str();
732   }
733   const char* getMethod() const {
734     return m_lpECB->lpszMethod;
735   }
736   string getContentType() const {
737     return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
738   }
739   long getContentLength() const {
740       return m_lpECB->cbTotalBytes;
741   }
742   string getRemoteUser() const {
743     if (m_remote_user.empty()) {
744         dynabuf var(16);
745         GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
746         if (!var.empty())
747             m_remote_user = var;
748     }
749     return m_remote_user;
750   }
751   string getRemoteAddr() const {
752     if (m_remote_addr.empty()) {
753         dynabuf var(16);
754         GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
755         if (!var.empty())
756             m_remote_addr = var;
757     }
758     return m_remote_addr;
759   }
760   void log(SPLogLevel level, const string& msg) const {
761       AbstractSPRequest::log(level,msg);
762       if (level >= SPError)
763           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
764   }
765   string getHeader(const char* name) const {
766     string hdr("HTTP_");
767     for (; *name; ++name) {
768         if (*name=='-')
769             hdr += '_';
770         else
771             hdr += toupper(*name);
772     }
773     dynabuf buf(128);
774     GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
775     return buf.empty() ? "" : buf;
776   }
777   void setResponseHeader(const char* name, const char* value) {
778     // Set for later.
779     if (value)
780         m_headers.insert(make_pair(name,value));
781     else
782         m_headers.erase(name);
783   }
784   const char* getQueryString() const {
785     return m_lpECB->lpszQueryString;
786   }
787   const char* getRequestBody() const {
788     if (m_gotBody)
789         return m_body.c_str();
790     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
791         throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
792     else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
793       m_gotBody=true;
794       char buf[8192];
795       DWORD datalen=m_lpECB->cbTotalBytes;
796       while (datalen) {
797         DWORD buflen=8192;
798         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
799         if (!ret || !buflen)
800             throw IOException("Error reading request body from browser.");
801         m_body.append(buf, buflen);
802         datalen-=buflen;
803       }
804     }
805     else if (m_lpECB->cbAvailable) {
806         m_gotBody=true;
807         m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
808     }
809     return m_body.c_str();
810   }
811   long sendResponse(istream& in, long status) {
812     string hdr = string("Connection: close\r\n");
813     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
814         hdr += i->first + ": " + i->second + "\r\n";
815     hdr += "\r\n";
816     const char* codestr="200 OK";
817     switch (status) {
818         case XMLTOOLING_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
819         case XMLTOOLING_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
820         case XMLTOOLING_HTTP_STATUS_ERROR:    codestr="500 Server Error"; break;
821     }
822     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
823     char buf[1024];
824     while (in) {
825         in.read(buf,1024);
826         DWORD resplen = in.gcount();
827         m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
828     }
829     return HSE_STATUS_SUCCESS;
830   }
831   long sendRedirect(const char* url) {
832     string hdr=string("Location: ") + url + "\r\n"
833       "Content-Type: text/html\r\n"
834       "Content-Length: 40\r\n"
835       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
836       "Cache-Control: private,no-store,no-cache\r\n";
837     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
838         hdr += i->first + ": " + i->second + "\r\n";
839     hdr += "\r\n";
840     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
841     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
842     DWORD resplen=40;
843     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
844     return HSE_STATUS_SUCCESS;
845   }
846   // Decline happens in the POST processor if this isn't the shire url
847   // Note that it can also happen with HTAccess, but we don't support that, yet.
848   long returnDecline() {
849     return WriteClientError(
850         m_lpECB,
851         "ISAPI extension can only be invoked to process Shibboleth protocol requests."
852                 "Make sure the mapped file extension doesn't match actual content."
853         );
854   }
855   long returnOK() {
856       return HSE_STATUS_SUCCESS;
857   }
858
859   const vector<string>& getClientCertificates() const {
860       if (m_certs.empty()) {
861         char CertificateBuf[8192];
862         CERT_CONTEXT_EX ccex;
863         ccex.cbAllocated = sizeof(CertificateBuf);
864         ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
865         DWORD dwSize = sizeof(ccex);
866
867         if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
868             if (ccex.CertContext.cbCertEncoded) {
869                 unsigned int outlen;
870                 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
871                 m_certs.push_back(reinterpret_cast<char*>(serialized));
872                 XMLString::release(&serialized);
873             }
874         }
875       }
876       return m_certs;
877   }
878
879   // Not used in the extension.
880   void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
881   void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
882   void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
883 };
884
885 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
886 {
887     try {
888         ostringstream threadid;
889         threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
890         xmltooling::NDC ndc(threadid.str().c_str());
891
892         // Determine web site number. This can't really fail, I don't think.
893         dynabuf buf(128);
894         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
895
896         // Match site instance to host name, skip if no match.
897         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
898         if (map_i==g_Sites.end())
899             return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
900
901         ShibTargetIsapiE ste(lpECB, map_i->second);
902         pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
903         if (res.first) return res.second;
904         
905         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
906
907     }
908     catch(bad_alloc) {
909         return WriteClientError(lpECB,"Out of Memory");
910     }
911     catch(long e) {
912         if (e==ERROR_NO_DATA)
913             return WriteClientError(lpECB,"A required variable or header was empty.");
914         else
915             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
916     }
917     catch (exception& e) {
918         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
919         return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
920     }
921     catch(...) {
922         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
923         if (g_catchAll)
924             return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
925         throw;
926     }
927
928     // If we get here we've got an error.
929     return HSE_STATUS_ERROR;
930 }