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