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