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