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