Removed ShibTarget members.
[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/SPConfig.h>
29 #include <xmltooling/util/NDC.h>
30
31 // SAML Runtime
32 #include <saml/saml.h>
33 #include <shib/shib.h>
34 #include <shib-target/shib-target.h>
35
36 #include <sstream>
37 #include <fstream>
38 #include <process.h>
39
40 #include <httpfilt.h>
41 #include <httpext.h>
42
43 using namespace shibsp;
44 using namespace shibtarget;
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     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=&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         IConfig* conf=g_Config->getINI();
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 ShibTarget
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     init(m_scheme.c_str(), m_hostname.c_str(), m_port, m_uri.c_str()); 
377   }
378   ~ShibTargetIsapiF() { }
379
380   const char* getScheme() const {
381     return m_scheme.c_str();
382   }
383   const char* getHostname() const {
384     return m_hostname.c_str();
385   }
386   int getPort() const {
387     return m_port;
388   }
389   const char* getRequestURI() const {
390     return m_uri.c_str();
391   }
392   const char* getMethod() const {
393     if (m_method.empty()) {
394         dynabuf var(5);
395         GetServerVariable(m_pfc,"REQUEST_METHOD",var,5,false);
396         if (!var.empty())
397             m_method = var;
398     }
399     return m_method.c_str();
400   }
401   string getContentType() const {
402     if (m_content_type.empty()) {
403         dynabuf var(32);
404         GetServerVariable(m_pfc,"CONTENT_TYPE",var,32,false);
405         if (!var.empty())
406             m_content_type = var;
407     }
408     return m_content_type;
409   }
410   long getContentLength() const {
411       return 0;
412   }
413   string getRemoteAddr() const {
414     if (m_remote_addr.empty()) {
415         dynabuf var(16);
416         GetServerVariable(m_pfc,"REMOTE_ADDR",var,16,false);
417         if (!var.empty())
418             m_remote_addr = var;
419     }
420     return m_remote_addr;
421   }
422   void log(SPLogLevel level, const string& msg) {
423     AbstractSPRequest::log(level,msg);
424     if (level >= SPError)
425         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
426   }
427   void clearHeader(const char* name) {
428     string hdr(!strcmp(name,"REMOTE_USER") ? "remote-user" : name);
429     hdr += ':';
430     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
431   }
432   void setHeader(const char* name, const char* value) {
433     string hdr(name);
434     hdr += ':';
435     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), const_cast<char*>(value));
436   }
437   string getHeader(const char* name) const {
438     string hdr(name);
439     hdr += ':';
440     dynabuf buf(1024);
441     GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
442     return string(buf);
443   }
444   void setRemoteUser(const char* user) {
445     setHeader("remote-user", user);
446   }
447   string getRemoteUser() const {
448     return getHeader("remote-user");
449   }
450   void setResponseHeader(const char* name, const char* value) {
451     // Set for later.
452     if (value)
453         m_headers[name] = value;
454     else
455         m_headers.erase(name);
456   }
457   long sendResponse(istream& in, long status) {
458     string hdr = string("Connection: close\r\n");
459     for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
460         hdr += i->first + ": " + i->second + "\r\n";
461     hdr += "\r\n";
462     const char* codestr="200 OK";
463     switch (status) {
464         case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
465         case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
466         case SAML_HTTP_STATUS_ERROR:    codestr="500 Server Error"; break;
467     }
468     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
469     char buf[1024];
470     while (in) {
471         in.read(buf,1024);
472         DWORD resplen = in.gcount();
473         m_pfc->WriteClient(m_pfc, buf, &resplen, 0);
474     }
475     return SF_STATUS_REQ_FINISHED;
476   }
477   long sendRedirect(const char* url) {
478     // XXX: Don't support the httpRedirect option, yet.
479     string hdr=string("Location: ") + url + "\r\n"
480       "Content-Type: text/html\r\n"
481       "Content-Length: 40\r\n"
482       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
483       "Cache-Control: private,no-store,no-cache\r\n";
484     for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
485         hdr += i->first + ": " + i->second + "\r\n";
486     hdr += "\r\n";
487     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, "302 Please Wait", (DWORD)hdr.c_str(), 0);
488     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
489     DWORD resplen=40;
490     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
491     return SF_STATUS_REQ_FINISHED;
492   }
493   // XXX: We might not ever hit the 'decline' status in this filter.
494   //long returnDecline(void) { }
495   long returnOK(void) {
496     return SF_STATUS_REQ_NEXT_NOTIFICATION;
497   }
498
499   const vector<XSECCryptoX509*>& getClientCertificates() const {
500       return m_certs;
501   }
502   
503   // The filter never processes the POST, so stub these methods.
504   const char* getQueryString() const { throw runtime_error("getQueryString not implemented"); }
505   const char* getRequestBody() const { throw runtime_error("getRequestBody not implemented"); }
506 };
507
508 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
509 {
510     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
511     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
512     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
513     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
514                             "<H1>Shibboleth Filter Error</H1>";
515     DWORD resplen=strlen(xmsg);
516     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
517     resplen=strlen(msg);
518     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
519     static const char* xmsg2="</BODY></HTML>";
520     resplen=strlen(xmsg2);
521     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
522     return SF_STATUS_REQ_FINISHED;
523 }
524
525 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
526 {
527     // Is this a log notification?
528     if (notificationType==SF_NOTIFY_LOG)
529     {
530         if (pfc->pFilterContext)
531             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
532         return SF_STATUS_REQ_NEXT_NOTIFICATION;
533     }
534
535     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
536     try
537     {
538         // Determine web site number. This can't really fail, I don't think.
539         dynabuf buf(128);
540         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
541
542         // Match site instance to host name, skip if no match.
543         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
544         if (map_i==g_Sites.end())
545             return SF_STATUS_REQ_NEXT_NOTIFICATION;
546             
547         ostringstream threadid;
548         threadid << "[" << getpid() << "] isapi_shib" << '\0';
549         xmltooling::NDC ndc(threadid.str().c_str());
550
551         ShibTargetIsapiF stf(pfc, pn, map_i->second);
552
553         // "false" because we don't override the Shib settings
554         pair<bool,long> res = stf.doCheckAuthN();
555         if (res.first) return res.second;
556
557         // "false" because we don't override the Shib settings
558         res = stf.doExportAssertions();
559         if (res.first) return res.second;
560
561         res = stf.doCheckAuthZ();
562         if (res.first) return res.second;
563
564         return SF_STATUS_REQ_NEXT_NOTIFICATION;
565     }
566     catch(bad_alloc) {
567         return WriteClientError(pfc,"Out of Memory");
568     }
569     catch(long e) {
570         if (e==ERROR_NO_DATA)
571             return WriteClientError(pfc,"A required variable or header was empty.");
572         else
573             return WriteClientError(pfc,"Shibboleth Filter detected unexpected IIS error.");
574     }
575     catch (exception& e) {
576         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
577         return WriteClientError(pfc,"Shibboleth Filter caught an exception, check Event Log for details.");
578     }
579 #ifndef _DEBUG
580     catch(...) {
581         return WriteClientError(pfc,"Shibboleth Filter caught an unknown exception.");
582     }
583 #endif
584
585     return WriteClientError(pfc,"Shibboleth Filter reached unreachable code, save my walrus!");
586 }
587         
588
589 /****************************************************************************/
590 // ISAPI Extension
591
592 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
593 {
594     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
595     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
596     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
597     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
598     DWORD resplen=strlen(xmsg);
599     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
600     resplen=strlen(msg);
601     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
602     static const char* xmsg2="</BODY></HTML>";
603     resplen=strlen(xmsg2);
604     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
605     return HSE_STATUS_SUCCESS;
606 }
607
608
609 class ShibTargetIsapiE : public ShibTarget
610 {
611   LPEXTENSION_CONTROL_BLOCK m_lpECB;
612   map<string,string> m_headers;
613   vector<XSECCryptoX509*> m_certs;
614   mutable string m_body;
615   mutable bool m_gotBody;
616   int m_port;
617   string m_scheme,m_hostname,m_uri;
618   mutable string m_remote_addr;
619   
620 public:
621   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site) : m_lpECB(lpECB), m_gotBody(false) {
622     dynabuf ssl(5);
623     GetServerVariable(lpECB,"HTTPS",ssl,5);
624     bool SSL=(ssl=="on" || ssl=="ON");
625
626     // Scheme may come from site def or be derived from IIS.
627     m_scheme=site.m_scheme;
628     if (m_scheme.empty() || !g_bNormalizeRequest)
629         m_scheme = SSL ? "https" : "http";
630
631     // URL path always come from IIS.
632     dynabuf url(256);
633     GetServerVariable(lpECB,"URL",url,255);
634
635     // Port may come from IIS or from site def.
636     dynabuf port(11);
637     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
638         GetServerVariable(lpECB,"SERVER_PORT",port,10);
639     else if (SSL) {
640         strncpy(port,site.m_sslport.c_str(),10);
641         static_cast<char*>(port)[10]=0;
642     }
643     else {
644         strncpy(port,site.m_port.c_str(),10);
645         static_cast<char*>(port)[10]=0;
646     }
647     m_port = atoi(port);
648
649     dynabuf var(32);
650     GetServerVariable(lpECB, "SERVER_NAME", var, 32);
651
652     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
653     m_hostname=var;
654     if (site.m_name!=m_hostname && site.m_aliases.find(m_hostname)==site.m_aliases.end())
655         m_hostname=site.m_name;
656
657     /*
658      * IIS screws us over on PATH_INFO (the hits keep on coming). We need to figure out if
659      * the server is set up for proper PATH_INFO handling, or "IIS sucks rabid weasels mode",
660      * which is the default. No perfect way to tell, but we can take a good guess by checking
661      * whether the URL is a substring of the PATH_INFO:
662      * 
663      * e.g. for /Shibboleth.sso/SAML/POST
664      * 
665      *  Bad mode (default):
666      *      URL:        /Shibboleth.sso
667      *      PathInfo:   /Shibboleth.sso/SAML/POST
668      * 
669      *  Good mode:
670      *      URL:        /Shibboleth.sso
671      *      PathInfo:   /SAML/POST
672      */
673     
674     // Clearly we're only in bad mode if path info exists at all.
675     if (lpECB->lpszPathInfo && *(lpECB->lpszPathInfo)) {
676         if (strstr(lpECB->lpszPathInfo,url))
677             // Pretty good chance we're in bad mode, unless the PathInfo repeats the path itself.
678             m_uri = lpECB->lpszPathInfo;
679         else {
680             m_uri = url;
681             m_uri += lpECB->lpszPathInfo;
682         }
683     }
684     
685     // For consistency with Apache, let's add the query string.
686     if (lpECB->lpszQueryString && *(lpECB->lpszQueryString)) {
687         m_uri += '?';
688         m_uri += lpECB->lpszQueryString;
689     }
690
691     init(
692         m_scheme.c_str(),
693         m_hostname.c_str(),
694         m_port,
695         m_uri.c_str()
696         );
697   }
698   ~ShibTargetIsapiE() { }
699
700   const char* getScheme() const {
701     return m_scheme.c_str();
702   }
703   const char* getHostname() const {
704     return m_hostname.c_str();
705   }
706   int getPort() const {
707     return m_port;
708   }
709   const char* getRequestURI() const {
710     return m_uri.c_str();
711   }
712   const char* getMethod() const {
713     return m_lpECB->lpszMethod;
714   }
715   string getContentType() const {
716     return m_lpECB->lpszContentType ? m_lpECB->lpszContentType : "";
717   }
718   long getContentLength() const {
719       return m_lpECB->cbTotalBytes;
720   }
721   string getRemoteAddr() const {
722     if (m_remote_addr.empty()) {
723         dynabuf var(16);
724         GetServerVariable(m_lpECB, "REMOTE_ADDR", var, 16, false);
725         if (!var.empty())
726             m_remote_addr = var;
727     }
728     return m_remote_addr;
729   }
730   void log(SPLogLevel level, const string& msg) {
731       AbstractSPRequest::log(level,msg);
732       if (level >= SPError)
733           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
734   }
735   string getHeader(const char* name) const {
736     string hdr("HTTP_");
737     for (; *name; ++name) {
738         if (*name=='-')
739             hdr += '_';
740         else
741             hdr += toupper(*name);
742     }
743     dynabuf buf(128);
744     GetServerVariable(m_lpECB, const_cast<char*>(hdr.c_str()), buf, 128, false);
745     return buf.empty() ? "" : buf;
746   }
747   void setResponseHeader(const char* name, const char* value) {
748     // Set for later.
749     if (value)
750         m_headers[name] = value;
751     else
752         m_headers.erase(name);
753   }
754   const char* getQueryString() const {
755     return m_lpECB->lpszQueryString;
756   }
757   const char* getRequestBody() const {
758     if (m_gotBody)
759         return m_body.c_str();
760     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
761         throw opensaml::BindingException("Size of POST request body exceeded limit.");
762     else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
763       m_gotBody=true;
764       char buf[8192];
765       DWORD datalen=m_lpECB->cbTotalBytes;
766       while (datalen) {
767         DWORD buflen=8192;
768         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
769         if (!ret || !buflen)
770             throw saml::SAMLException("Error reading POST request body from browser.");
771         m_body.append(buf, buflen);
772         datalen-=buflen;
773       }
774     }
775     else {
776         m_gotBody=true;
777         m_body.assign(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
778     }
779     return m_body.c_str();
780   }
781   long sendResponse(istream& in, long status) {
782     string hdr = string("Connection: close\r\n");
783     for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
784         hdr += i->first + ": " + i->second + "\r\n";
785     hdr += "\r\n";
786     const char* codestr="200 OK";
787     switch (status) {
788         case SAML_HTTP_STATUS_FORBIDDEN:codestr="403 Forbidden"; break;
789         case SAML_HTTP_STATUS_NOTFOUND: codestr="404 Not Found"; break;
790         case SAML_HTTP_STATUS_ERROR:    codestr="500 Server Error"; break;
791     }
792     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
793     char buf[1024];
794     while (in) {
795         in.read(buf,1024);
796         DWORD resplen = in.gcount();
797         m_lpECB->WriteClient(m_lpECB->ConnID, buf, &resplen, HSE_IO_SYNC);
798     }
799     return HSE_STATUS_SUCCESS;
800   }
801   long sendRedirect(const char* url) {
802     string hdr=string("Location: ") + url + "\r\n"
803       "Content-Type: text/html\r\n"
804       "Content-Length: 40\r\n"
805       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
806       "Cache-Control: private,no-store,no-cache\r\n";
807     for (map<string,string>::const_iterator i=m_headers.begin(); i!=m_headers.end(); ++i)
808         hdr += i->first + ": " + i->second + "\r\n";
809     hdr += "\r\n";
810     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, "302 Moved", 0, (LPDWORD)hdr.c_str());
811     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
812     DWORD resplen=40;
813     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
814     return HSE_STATUS_SUCCESS;
815   }
816   // Decline happens in the POST processor if this isn't the shire url
817   // Note that it can also happen with HTAccess, but we don't support that, yet.
818   long returnDecline() {
819     return WriteClientError(
820         m_lpECB,
821         "ISAPI extension can only be invoked to process Shibboleth protocol requests."
822                 "Make sure the mapped file extension doesn't match actual content."
823         );
824   }
825   long returnOK() {
826       return HSE_STATUS_SUCCESS;
827   }
828
829   const vector<XSECCryptoX509*>& getClientCertificates() const {
830       return m_certs;
831   }
832
833   // Not used in the extension.
834   virtual void clearHeader(const char* name) { throw runtime_error("clearHeader not implemented"); }
835   virtual void setHeader(const char* name, const char* value) { throw runtime_error("setHeader not implemented"); }
836   virtual void setRemoteUser(const char* user) { throw runtime_error("setRemoteUser not implemented"); }
837   virtual string getRemoteUser() const { throw runtime_error("getRemoteUser not implemented"); }
838 };
839
840 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
841 {
842     string targeturl;
843     const IApplication* application=NULL;
844     try {
845         ostringstream threadid;
846         threadid << "[" << getpid() << "] isapi_shib_extension" << '\0';
847         xmltooling::NDC ndc(threadid.str().c_str());
848
849         // Determine web site number. This can't really fail, I don't think.
850         dynabuf buf(128);
851         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
852
853         // Match site instance to host name, skip if no match.
854         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
855         if (map_i==g_Sites.end())
856             return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
857
858         ShibTargetIsapiE ste(lpECB, map_i->second);
859         pair<bool,long> res = ste.doHandler();
860         if (res.first) return res.second;
861         
862         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
863
864     }
865     catch(bad_alloc) {
866         return WriteClientError(lpECB,"Out of Memory");
867     }
868     catch(long e) {
869         if (e==ERROR_NO_DATA)
870             return WriteClientError(lpECB,"A required variable or header was empty.");
871         else
872             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
873     }
874     catch (exception& e) {
875         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, e.what());
876         return WriteClientError(lpECB,"Shibboleth Extension caught an exception, check Event Log for details.");
877     }
878 #ifndef _DEBUG
879     catch(...) {
880         return WriteClientError(lpECB,"Shibboleth Extension caught an unknown exception.");
881     }
882 #endif
883
884     // If we get here we've got an error.
885     return HSE_STATUS_ERROR;
886 }