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