25ed8a7028d5575d42c8458451c14d739d81b216
[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 Alias[] =            UNICODE_LITERAL_5(A,l,i,a,s);
64     static const XMLCh Site[] =             UNICODE_LITERAL_4(S,i,t,e);
65
66     struct site_t {
67         site_t(const DOMElement* e)
68         {
69             auto_ptr_char n(e->getAttributeNS(NULL,name));
70             auto_ptr_char s(e->getAttributeNS(NULL,scheme));
71             auto_ptr_char p(e->getAttributeNS(NULL,port));
72             auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
73             if (n.get()) m_name=n.get();
74             if (s.get()) m_scheme=s.get();
75             if (p.get()) m_port=p.get();
76             if (p2.get()) m_sslport=p2.get();
77             e = XMLHelper::getFirstChildElement(e, Alias);
78             while (e) {
79                 if (e->hasChildNodes()) {
80                     auto_ptr_char alias(e->getFirstChild()->getNodeValue());
81                     m_aliases.insert(alias.get());
82                 }
83                 e = XMLHelper::getNextSiblingElement(e, Alias);
84             }
85         }
86         string m_scheme,m_port,m_sslport,m_name;
87         set<string> m_aliases;
88     };
89
90     struct context_t {
91         char* m_user;
92         bool m_checked;
93     };
94
95     HINSTANCE g_hinstDLL;
96     SPConfig* g_Config = NULL;
97     map<string,site_t> g_Sites;
98     bool g_bNormalizeRequest = true;
99     string g_unsetHeaderValue;
100     bool g_checkSpoofing = true;
101     bool g_catchAll = false;
102     vector<string> g_NoCerts;
103 }
104
105 BOOL LogEvent(
106     LPCSTR  lpUNCServerName,
107     WORD  wType,
108     DWORD  dwEventID,
109     PSID  lpUserSid,
110     LPCSTR  message)
111 {
112     LPCSTR  messages[] = {message, NULL};
113
114     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
115     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
116     return (DeregisterEventSource(hElog) && res);
117 }
118
119 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
120 {
121     if (fdwReason==DLL_PROCESS_ATTACH)
122         g_hinstDLL=hinstDLL;
123     return TRUE;
124 }
125
126 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
127 {
128     if (!pVer)
129         return FALSE;
130
131     if (!g_Config) {
132         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
133                 "Extension mode startup not possible, is the DLL loaded as a filter?");
134         return FALSE;
135     }
136
137     pVer->dwExtensionVersion=HSE_VERSION;
138     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
139     return TRUE;
140 }
141
142 extern "C" BOOL WINAPI TerminateExtension(DWORD)
143 {
144     return TRUE;    // cleanup should happen when filter unloads
145 }
146
147 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
148 {
149     if (!pVer)
150         return FALSE;
151     else if (g_Config) {
152         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
153                 "Reentrant filter initialization, ignoring...");
154         return TRUE;
155     }
156
157     g_Config=&SPConfig::getConfig();
158     g_Config->setFeatures(
159         SPConfig::Listener |
160         SPConfig::Caching |
161         SPConfig::RequestMapping |
162         SPConfig::InProcess |
163         SPConfig::Logging |
164         SPConfig::Handlers
165         );
166     if (!g_Config->init()) {
167         g_Config=NULL;
168         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
169                 "Filter startup failed during library initialization, check native log for help.");
170         return FALSE;
171     }
172
173     LPCSTR config=getenv("SHIBSP_CONFIG");
174     if (!config)
175         config=SHIBSP_CONFIG;
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 implementation-specifics and 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         props = props->getPropertySet("ISAPI");
211         if (props) {
212             flag = props->getBool("normalizeRequest");
213             g_bNormalizeRequest = !flag.first || flag.second;
214             const DOMElement* child = XMLHelper::getFirstChildElement(props->getElement(),Site);
215             while (child) {
216                 auto_ptr_char id(child->getAttributeNS(NULL,id));
217                 if (id.get())
218                     g_Sites.insert(pair<string,site_t>(id.get(),site_t(child)));
219                 child=XMLHelper::getNextSiblingElement(child,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;
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       : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), 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     setRequestURI(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     if (!pfc->pFilterContext) {
396         pfc->pFilterContext = pfc->AllocMem(pfc, sizeof(context_t), NULL);
397         if (static_cast<context_t*>(pfc->pFilterContext)) {
398             static_cast<context_t*>(pfc->pFilterContext)->m_user = NULL;
399             static_cast<context_t*>(pfc->pFilterContext)->m_checked = false;
400         }
401     }
402   }
403   ~ShibTargetIsapiF() { }
404
405   const char* getScheme() const {
406     return m_scheme.c_str();
407   }
408   const char* getHostname() const {
409     return m_hostname.c_str();
410   }
411   int getPort() const {
412     return m_port;
413   }
414   const char* getMethod() const {
415     if (m_method.empty()) {
416         dynabuf var(5);
417         GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
418         if (!var.empty())
419             m_method = var;
420     }
421     return m_method.c_str();
422   }
423   string getContentType() const {
424     if (m_content_type.empty()) {
425         dynabuf var(32);
426         GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
427         if (!var.empty())
428             m_content_type = var;
429     }
430     return m_content_type;
431   }
432   long getContentLength() const {
433       return 0;
434   }
435   string getRemoteAddr() const {
436     if (m_remote_addr.empty()) {
437         dynabuf var(16);
438         GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
439         if (!var.empty())
440             m_remote_addr = var;
441     }
442     return m_remote_addr;
443   }
444   void log(SPLogLevel level, const string& msg) {
445     AbstractSPRequest::log(level,msg);
446     if (level >= SPError)
447         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
448   }
449   void clearHeader(const char* rawname, const char* cginame) {
450         if (g_checkSpoofing && m_pfc->pFilterContext && !static_cast<context_t*>(m_pfc->pFilterContext)->m_checked) {
451         if (m_allhttp.empty())
452                 GetServerVariable(m_pfc,"ALL_HTTP",m_allhttp,4096);
453         if (strstr(m_allhttp, cginame))
454             throw opensaml::SecurityPolicyException("Attempt to spoof header ($1) was detected.", params(1, rawname));
455     }
456     string hdr(!strcmp(rawname,"REMOTE_USER") ? "remote-user" : rawname);
457     hdr += ':';
458     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(g_unsetHeaderValue.c_str()));
459   }
460   void setHeader(const char* name, const char* value) {
461     string hdr(name);
462     hdr += ':';
463     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
464   }
465   string getHeader(const char* name) const {
466     string hdr(name);
467     hdr += ':';
468     dynabuf buf(256);
469     GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 256, false);
470     return string(buf);
471   }
472   void setRemoteUser(const char* user) {
473     setHeader("remote-user", user);
474     if (m_pfc->pFilterContext) {
475         if (!user || !*user)
476             static_cast<context_t*>(m_pfc->pFilterContext)->m_user = NULL;
477         else if (static_cast<context_t*>(m_pfc->pFilterContext)->m_user = (char*)m_pfc->AllocMem(m_pfc, sizeof(char) * (strlen(user) + 1), NULL))
478             strcpy(static_cast<context_t*>(m_pfc->pFilterContext)->m_user, user);
479     }
480   }
481   string getRemoteUser() const {
482     return getHeader("remote-user");
483   }
484   void setResponseHeader(const char* name, const char* value) {
485     // Set for later.
486     if (value)
487         m_headers.insert(make_pair(name,value));
488     else
489         m_headers.erase(name);
490   }
491   long sendResponse(istream& in, long status) {
492     string hdr = string("Connection: close\r\n");
493     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
494         hdr += i->first + ": " + i->second + "\r\n";
495     hdr += "\r\n";
496     const char* codestr="200 OK";
497     switch (status) {
498         case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="401 Authorization Required"; break;
499         case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="403 Forbidden"; break;
500         case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="404 Not Found"; break;
501         case XMLTOOLING_HTTP_STATUS_ERROR:          codestr="500 Server Error"; break;
502     }
503     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
504     char buf[1024];
505     while (in) {
506         in.read(buf,1024);
507         DWORD resplen = in.gcount();
508         m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
509     }
510     return SF_STATUS_REQ_FINISHED;
511   }
512   long sendRedirect(const char* url) {
513     // XXX: Don't support the httpRedirect option, yet.
514     string hdr=string("Location: ") + url + "\r\n"
515       "Content-Type: text/html\r\n"
516       "Content-Length: 40\r\n"
517       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
518       "Cache-Control: private,no-store,no-cache\r\n";
519     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
520         hdr += i->first + ": " + i->second + "\r\n";
521     hdr += "\r\n";
522     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
523     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
524     DWORD resplen=40;
525     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
526     return SF_STATUS_REQ_FINISHED;
527   }
528   long returnDecline() {
529       return SF_STATUS_REQ_NEXT_NOTIFICATION;
530   }
531   long returnOK() {
532     return SF_STATUS_REQ_NEXT_NOTIFICATION;
533   }
534
535   const vector<string>& getClientCertificates() const {
536       return g_NoCerts;
537   }
538
539   // The filter never processes the POST, so stub these methods.
540   const char* getQueryString() const { throw IOException("getQueryString not implemented"); }
541   const char* getRequestBody() const { throw IOException("getRequestBody not implemented"); }
542 };
543
544 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
545 {
546     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
547     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
548     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
549     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
550                             "<H1>Shibboleth Filter Error</H1>";
551     DWORD resplen=strlen(xmsg);
552     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
553     resplen=strlen(msg);
554     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
555     static const char* xmsg2="</BODY></HTML>";
556     resplen=strlen(xmsg2);
557     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
558     return SF_STATUS_REQ_FINISHED;
559 }
560
561 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
562 {
563     // Is this a log notification?
564     if (notificationType==SF_NOTIFY_LOG) {
565         if (pfc->pFilterContext && static_cast<context_t*>(pfc->pFilterContext)->m_user)
566                 ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<context_t*>(pfc->pFilterContext)->m_user;
567         return SF_STATUS_REQ_NEXT_NOTIFICATION;
568     }
569
570     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
571     try
572     {
573         // Determine web site number. This can't really fail, I don't think.
574         dynabuf buf(128);
575         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
576
577         // Match site instance to host name, skip if no match.
578         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
579         if (map_i==g_Sites.end())
580             return SF_STATUS_REQ_NEXT_NOTIFICATION;
581
582         ostringstream threadid;
583         threadid << "[" << getpid() << "] isapi_shib" << '\0';
584         xmltooling::NDC ndc(threadid.str().c_str());
585
586         ShibTargetIsapiF stf(pfc, pn, map_i->second);
587
588         // "false" because we don't override the Shib settings
589         pair<bool,long> res = stf.getServiceProvider().doAuthentication(stf);
590         if (pfc->pFilterContext)
591             static_cast<context_t*>(pfc->pFilterContext)->m_checked = true;
592         if (res.first) return res.second;
593
594         // "false" because we don't override the Shib settings
595         res = stf.getServiceProvider().doExport(stf);
596         if (res.first) return res.second;
597
598         res = stf.getServiceProvider().doAuthorization(stf);
599         if (res.first) return res.second;
600
601         return SF_STATUS_REQ_NEXT_NOTIFICATION;
602     }
603     catch(bad_alloc) {
604         return WriteClientError(pfc,"Out of Memory");
605     }
606     catch(long e) {
607         if (e==ERROR_NO_DATA)
608             return WriteClientError(pfc,"A required variable or header was empty.");
609         else
610             return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
611     }
612     catch (exception& e) {
613         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
614         return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
615     }
616     catch(...) {
617         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Filter threw an unknown exception.");
618         if (g_catchAll)
619             return WriteClientError(pfc,"Shibboleth Filter threw an unknown exception.");
620         throw;
621     }
622
623     return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
624 }
625
626
627 /****************************************************************************/
628 // ISAPI Extension
629
630 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
631 {
632     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
633     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
634     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
635     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
636     DWORD resplen=strlen(xmsg);
637     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
638     resplen=strlen(msg);
639     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
640     static const char* xmsg2="</BODY></HTML>";
641     resplen=strlen(xmsg2);
642     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
643     return HSE_STATUS_SUCCESS;
644 }
645
646
647 class ShibTargetIsapiE : public AbstractSPRequest
648 {
649   LPEXTENSION_CONTROL_BLOCK m_lpECB;
650   multimap<string,string> m_headers;
651   mutable vector<string> m_certs;
652   mutable string m_body;
653   mutable bool m_gotBody;
654   int m_port;
655   string m_scheme,m_hostname,m_uri;
656   mutable string m_remote_addr,m_remote_user;
657
658 public:
659   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
660       : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"), m_lpECB(lpECB), m_gotBody(false) {
661     dynabuf ssl(5);
662     GetServerVariable(lpECB,"HTTPS",ssl,5);
663     bool SSL=(ssl=="on" || ssl=="ON");
664
665     // Scheme may come from site def or be derived from IIS.
666     m_scheme=site.m_scheme;
667     if (m_scheme.empty() || !g_bNormalizeRequest)
668         m_scheme = SSL ? "https" : "http";
669
670     // URL path always come from IIS.
671     dynabuf url(256);
672     GetServerVariable(lpECB,"URL",url,255);
673
674     // Port may come from IIS or from site def.
675     dynabuf port(11);
676     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
677         GetServerVariable(lpECB,"SERVER_PORT",port,10);
678     else if (SSL) {
679         strncpy(port,site.m_sslport.c_str(),10);
680         static_cast<char*>(port)[10]=0;
681     }
682     else {
683         strncpy(port,site.m_port.c_str(),10);
684         static_cast<char*>(port)[10]=0;
685     }
686     m_port = atoi(port);
687
688     dynabuf var(32);
689     GetServerVariable(lpECB, "SERVER_NAME", var, 32);
690
691     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
692     m_hostname=var;
693     if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
694         m_hostname=site.m_name;
695
696     /*
697      * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
698      * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
699      * which is the default. No perfect way to tell, but we can take a good guess by checking
700      * whether the URL is a substring of the PATH_INFO:
701      *
702      * e.g. for /Shibboleth.sso/SAML/POST
703      *
704      *  Bad mode (default):
705      *      URL:        /Shibboleth.sso
706      *      PathInfo:   /Shibboleth.sso/SAML/POST
707      *
708      *  Good mode:
709      *      URL:        /Shibboleth.sso
710      *      PathInfo:   /SAML/POST
711      */
712
713     string uri;
714
715     // Clearly we're only in bad mode if path info exists at all.
716     if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
717         if (strstr(lpECB->lpszPathInfo,url))
718             // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
719             uri = lpECB->lpszPathInfo;
720         else {
721             uri = url;
722             uri += lpECB->lpszPathInfo;
723         }
724     }
725     else {
726         uri = url;
727     }
728
729     // For consistency with Apache, let's add the query string.
730     if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
731         uri += '?';
732         uri += lpECB->lpszQueryString;
733     }
734
735     setRequestURI(uri.c_str());
736   }
737   ~ShibTargetIsapiE() { }
738
739   const char* getScheme() const {
740     return m_scheme.c_str();
741   }
742   const char* getHostname() const {
743     return m_hostname.c_str();
744   }
745   int getPort() const {
746     return m_port;
747   }
748   const char* getMethod() const {
749     return m_lpECB->lpszMethod;
750   }
751   string getContentType() const {
752     return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
753   }
754   long getContentLength() const {
755       return m_lpECB->cbTotalBytes;
756   }
757   string getRemoteUser() const {
758     if (m_remote_user.empty()) {
759         dynabuf var(16);
760         GetServerVariable(m_lpECB, "REMOTE_USER", var, 32, false);
761         if (!var.empty())
762             m_remote_user = var;
763     }
764     return m_remote_user;
765   }
766   string getRemoteAddr() const {
767     if (m_remote_addr.empty()) {
768         dynabuf var(16);
769         GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
770         if (!var.empty())
771             m_remote_addr = var;
772     }
773     return m_remote_addr;
774   }
775   void log(SPLogLevel level, const string& msg) const {
776       AbstractSPRequest::log(level,msg);
777       if (level >= SPError)
778           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
779   }
780   string getHeader(const char* name) const {
781     string hdr("HTTP_");
782     for (; *name; ++name) {
783         if (*name=='-')
784             hdr += '_';
785         else
786             hdr += toupper(*name);
787     }
788     dynabuf buf(128);
789     GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
790     return buf.empty() ? "" : buf;
791   }
792   void setResponseHeader(const char* name, const char* value) {
793     // Set for later.
794     if (value)
795         m_headers.insert(make_pair(name,value));
796     else
797         m_headers.erase(name);
798   }
799   const char* getQueryString() const {
800     return m_lpECB->lpszQueryString;
801   }
802   const char* getRequestBody() const {
803     if (m_gotBody)
804         return m_body.c_str();
805     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
806         throw opensaml::SecurityPolicyException("Size of request body exceeded 1M size limit.");
807     else if (m_lpECB->cbTotalBytes > m_lpECB->cbAvailable) {
808       m_gotBody=true;
809       char buf[8192];
810       DWORD datalen=m_lpECB->cbTotalBytes;
811       while (datalen) {
812         DWORD buflen=8192;
813         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
814         if (!ret || !buflen)
815             throw IOException("Error reading request body from browser.");
816         m_body.append(buf, buflen);
817         datalen-=buflen;
818       }
819     }
820     else if (m_lpECB->cbAvailable) {
821         m_gotBody=true;
822         m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
823     }
824     return m_body.c_str();
825   }
826   long sendResponse(istream& in, long status) {
827     string hdr = string("Connection: close\r\n");
828     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
829         hdr += i->first + ": " + i->second + "\r\n";
830     hdr += "\r\n";
831     const char* codestr="200 OK";
832     switch (status) {
833         case XMLTOOLING_HTTP_STATUS_UNAUTHORIZED:   codestr="401 Authorization Required"; break;
834         case XMLTOOLING_HTTP_STATUS_FORBIDDEN:      codestr="403 Forbidden"; break;
835         case XMLTOOLING_HTTP_STATUS_NOTFOUND:       codestr="404 Not Found"; break;
836         case XMLTOOLING_HTTP_STATUS_ERROR:          codestr="500 Server Error"; break;
837     }
838     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
839     char buf[1024];
840     while (in) {
841         in.read(buf,1024);
842         DWORD resplen = in.gcount();
843         m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
844     }
845     return HSE_STATUS_SUCCESS;
846   }
847   long sendRedirect(const char* url) {
848     string hdr=string("Location: ") + url + "\r\n"
849       "Content-Type: text/html\r\n"
850       "Content-Length: 40\r\n"
851       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
852       "Cache-Control: private,no-store,no-cache\r\n";
853     for (multimap<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
854         hdr += i->first + ": " + i->second + "\r\n";
855     hdr += "\r\n";
856     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
857     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
858     DWORD resplen=40;
859     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
860     return HSE_STATUS_SUCCESS;
861   }
862   // Decline happens in the POST processor if this isn't the shire url
863   // Note that it can also happen with HTAccess, but we don't support that, yet.
864   long returnDecline() {
865     return WriteClientError(
866         m_lpECB,
867         "ISAPI extension can only be invoked to process Shibboleth protocol requests."
868                 "Make sure the mapped file extension doesn't match actual content."
869         );
870   }
871   long returnOK() {
872       return HSE_STATUS_SUCCESS;
873   }
874
875   const vector<string>& getClientCertificates() const {
876       if (m_certs.empty()) {
877         char CertificateBuf[8192];
878         CERT_CONTEXT_EX ccex;
879         ccex.cbAllocated = sizeof(CertificateBuf);
880         ccex.CertContext.pbCertEncoded = (BYTE*)CertificateBuf;
881         DWORD dwSize = sizeof(ccex);
882
883         if (m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_GET_CERT_INFO_EX, (LPVOID)&ccex, (LPDWORD)dwSize, NULL)) {
884             if (ccex.CertContext.cbCertEncoded) {
885                 unsigned int outlen;
886                 XMLByte* serialized = Base64::encode(reinterpret_cast<XMLByte*>(CertificateBuf), ccex.CertContext.cbCertEncoded, &outlen);
887                 m_certs.push_back(reinterpret_cast<char*>(serialized));
888                 XMLString::release(&serialized);
889             }
890         }
891       }
892       return m_certs;
893   }
894
895   // Not used in the extension.
896   void clearHeader(const char* rawname, const char* cginame) { throw runtime_error("clearHeader not implemented"); }
897   void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
898   void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
899 };
900
901 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
902 {
903     try {
904         ostringstream threadid;
905         threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
906         xmltooling::NDC ndc(threadid.str().c_str());
907
908         // Determine web site number. This can't really fail, I don't think.
909         dynabuf buf(128);
910         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
911
912         // Match site instance to host name, skip if no match.
913         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
914         if (map_i==g_Sites.end())
915             return WriteClientError(lpECB, "Shibboleth Extension not configured for web site (check <ISAPI> mappings in configuration).");
916
917         ShibTargetIsapiE ste(lpECB, map_i->second);
918         pair<bool,long> res = ste.getServiceProvider().doHandler(ste);
919         if (res.first) return res.second;
920
921         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
922
923     }
924     catch(bad_alloc) {
925         return WriteClientError(lpECB,"Out of Memory");
926     }
927     catch(long e) {
928         if (e==ERROR_NO_DATA)
929             return WriteClientError(lpECB,"A required variable or header was empty.");
930         else
931             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
932     }
933     catch (exception& e) {
934         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
935         return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
936     }
937     catch(...) {
938         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Shibboleth Extension threw an unknown exception.");
939         if (g_catchAll)
940             return WriteClientError(lpECB,"Shibboleth Extension threw an unknown exception.");
941         throw;
942     }
943
944     // If we get here we've got an error.
945     return HSE_STATUS_ERROR;
946 }