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