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