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