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