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