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