Reduce Windows logging.
[shibboleth/sp.git] / isapi_shib / isapi_shib.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution, if any, must include
17  * the following acknowledgment: "This product includes software developed by
18  * the University Corporation for Advanced Internet Development
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20  * may appear in the software itself, if and wherever such third-party
21  * acknowledgments normally appear.
22  *
23  * Neither the name of Shibboleth nor the names of its contributors, nor
24  * Internet2, nor the University Corporation for Advanced Internet Development,
25  * Inc., nor UCAID may be used to endorse or promote products derived from this
26  * software without specific prior written permission. For written permission,
27  * please contact shibboleth@shibboleth.org
28  *
29  * Products derived from this software may not be called Shibboleth, Internet2,
30  * UCAID, or the University Corporation for Advanced Internet Development, nor
31  * may Shibboleth appear in their name, without prior written permission of the
32  * University Corporation for Advanced Internet Development.
33  *
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 /* isapi_shib.cpp - Shibboleth ISAPI filter
51
52    Scott Cantor
53    8/23/02
54 */
55
56 #include "config_win32.h"
57
58 // SAML Runtime
59 #include <saml/saml.h>
60 #include <shib/shib.h>
61 #include <shib/shib-threads.h>
62 #include <shib-target/shib-target.h>
63
64 #include <log4cpp/Category.hh>
65
66 #include <ctime>
67 #include <fstream>
68 #include <sstream>
69 #include <stdexcept>
70
71 #include <httpfilt.h>
72 #include <httpext.h>
73
74 using namespace std;
75 using namespace log4cpp;
76 using namespace saml;
77 using namespace shibboleth;
78 using namespace shibtarget;
79
80 // globals
81 namespace {
82     static const XMLCh name[] = { chLatin_n, chLatin_a, chLatin_m, chLatin_e, chNull };
83     static const XMLCh port[] = { chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
84     static const XMLCh sslport[] = { chLatin_s, chLatin_s, chLatin_l, chLatin_p, chLatin_o, chLatin_r, chLatin_t, chNull };
85     static const XMLCh scheme[] = { chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_e, chNull };
86     static const XMLCh id[] = { chLatin_i, chLatin_d, chNull };
87     static const XMLCh Implementation[] =
88     { chLatin_I, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chLatin_m, chLatin_e, chLatin_n, chLatin_t, chLatin_a, chLatin_t, chLatin_i, chLatin_o, chLatin_n, chNull };
89     static const XMLCh ISAPI[] = { chLatin_I, chLatin_S, chLatin_A, chLatin_P, chLatin_I, chNull };
90     static const XMLCh Alias[] = { chLatin_A, chLatin_l, chLatin_i, chLatin_a, chLatin_s, chNull };
91     static const XMLCh normalizeRequest[] =
92     { chLatin_n, chLatin_o, chLatin_r, chLatin_m, chLatin_a, chLatin_l, chLatin_i, chLatin_z, chLatin_e,
93       chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chNull
94     };
95     static const XMLCh Site[] = { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chNull };
96
97     struct site_t {
98         site_t(const DOMElement* e)
99         {
100             auto_ptr_char n(e->getAttributeNS(NULL,name));
101             auto_ptr_char s(e->getAttributeNS(NULL,scheme));
102             auto_ptr_char p(e->getAttributeNS(NULL,port));
103             auto_ptr_char p2(e->getAttributeNS(NULL,sslport));
104             if (n.get()) m_name=n.get();
105             if (s.get()) m_scheme=s.get();
106             if (p.get()) m_port=p.get();
107             if (p2.get()) m_sslport=p2.get();
108             DOMNodeList* nlist=e->getElementsByTagNameNS(ShibTargetConfig::SHIBTARGET_NS,Alias);
109             for (int i=0; nlist && i<nlist->getLength(); i++) {
110                 if (nlist->item(i)->hasChildNodes()) {
111                     auto_ptr_char alias(nlist->item(i)->getFirstChild()->getNodeValue());
112                     m_aliases.insert(alias.get());
113                 }
114             }
115         }
116         string m_scheme,m_port,m_sslport,m_name;
117         set<string> m_aliases;
118     };
119     
120     HINSTANCE g_hinstDLL;
121     ShibTargetConfig* g_Config = NULL;
122     map<string,site_t> g_Sites;
123     bool g_bNormalizeRequest = true;
124 }
125
126 BOOL LogEvent(
127     LPCSTR  lpUNCServerName,
128     WORD  wType,
129     DWORD  dwEventID,
130     PSID  lpUserSid,
131     LPCSTR  message)
132 {
133     LPCSTR  messages[] = {message, NULL};
134     
135     HANDLE hElog = RegisterEventSource(lpUNCServerName, "Shibboleth ISAPI Filter");
136     BOOL res = ReportEvent(hElog, wType, 0, dwEventID, lpUserSid, 1, 0, messages, NULL);
137     return (DeregisterEventSource(hElog) && res);
138 }
139
140 extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
141 {
142     if (fdwReason==DLL_PROCESS_ATTACH)
143         g_hinstDLL=hinstDLL;
144     return TRUE;
145 }
146
147 extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
148 {
149     if (!pVer)
150         return FALSE;
151         
152     if (!g_Config)
153     {
154         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
155                 "Extension mode startup not possible, is the DLL loaded as a filter?");
156         return FALSE;
157     }
158
159     pVer->dwExtensionVersion=HSE_VERSION;
160     strncpy(pVer->lpszExtensionDesc,"Shibboleth ISAPI Extension",HSE_MAX_EXT_DLL_NAME_LEN-1);
161     return TRUE;
162 }
163
164 extern "C" BOOL WINAPI TerminateExtension(DWORD)
165 {
166     return TRUE;    // cleanup should happen when filter unloads
167 }
168
169 extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
170 {
171     if (!pVer)
172         return FALSE;
173     else if (g_Config) {
174         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
175                 "Reentrant filter initialization, ignoring...");
176         return TRUE;
177     }
178
179 #ifndef _DEBUG
180     try
181     {
182 #endif
183         LPCSTR schemadir=getenv("SHIBSCHEMAS");
184         if (!schemadir)
185             schemadir=SHIB_SCHEMAS;
186         LPCSTR config=getenv("SHIBCONFIG");
187         if (!config)
188             config=SHIB_CONFIG;
189         g_Config=&ShibTargetConfig::getConfig();
190         g_Config->setFeatures(
191             ShibTargetConfig::Listener |
192             ShibTargetConfig::Metadata |
193             ShibTargetConfig::AAP |
194             ShibTargetConfig::RequestMapper |
195             ShibTargetConfig::LocalExtensions |
196             ShibTargetConfig::Logging
197             );
198         if (!g_Config->init(schemadir,config)) {
199             g_Config=NULL;
200             LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL,
201                     "Filter startup failed during initialization, check shire log for help.");
202             return FALSE;
203         }
204         
205         // Access the implementation-specifics for site mappings.
206         IConfig* conf=g_Config->getINI();
207         Locker locker(conf);
208         const IPropertySet* props=conf->getPropertySet("Local");
209         if (props) {
210             const DOMElement* impl=saml::XML::getFirstChildElement(
211                 props->getElement(),ShibTargetConfig::SHIBTARGET_NS,Implementation
212                 );
213             if (impl && (impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,ISAPI))) {
214                 const XMLCh* flag=impl->getAttributeNS(NULL,normalizeRequest);
215                 g_bNormalizeRequest=(!flag || !*flag || *flag==chDigit_1 || *flag==chLatin_t);
216                 impl=saml::XML::getFirstChildElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
217                 while (impl) {
218                     auto_ptr_char id(impl->getAttributeNS(NULL,id));
219                     if (id.get())
220                         g_Sites.insert(pair<string,site_t>(id.get(),site_t(impl)));
221                     impl=saml::XML::getNextSiblingElement(impl,ShibTargetConfig::SHIBTARGET_NS,Site);
222                 }
223             }
224         }
225 #ifndef _DEBUG
226     }
227     catch (...)
228     {
229         LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter startup failed with an exception.");
230         return FALSE;
231     }
232 #endif
233
234     pVer->dwFilterVersion=HTTP_FILTER_REVISION;
235     strncpy(pVer->lpszFilterDesc,"Shibboleth ISAPI Filter",SF_MAX_FILTER_DESC_LEN);
236     pVer->dwFlags=(SF_NOTIFY_ORDER_HIGH |
237                    SF_NOTIFY_SECURE_PORT |
238                    SF_NOTIFY_NONSECURE_PORT |
239                    SF_NOTIFY_PREPROC_HEADERS |
240                    SF_NOTIFY_LOG);
241     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter initialized...");
242     return TRUE;
243 }
244
245 extern "C" BOOL WINAPI TerminateFilter(DWORD)
246 {
247     if (g_Config)
248         g_Config->shutdown();
249     g_Config = NULL;
250     LogEvent(NULL, EVENTLOG_INFORMATION_TYPE, 7701, NULL, "Filter shut down...");
251     return TRUE;
252 }
253
254 /* Next up, some suck-free versions of various APIs.
255
256    You DON'T require people to guess the buffer size and THEN tell them the right size.
257    Returning an LPCSTR is apparently way beyond their ken. Not to mention the fact that
258    constant strings aren't typed as such, making it just that much harder. These versions
259    are now updated to use a special growable buffer object, modeled after the standard
260    string class. The standard string won't work because they left out the option to
261    pre-allocate a non-constant buffer.
262 */
263
264 class dynabuf
265 {
266 public:
267     dynabuf() { bufptr=NULL; buflen=0; }
268     dynabuf(size_t s) { bufptr=new char[buflen=s]; *bufptr=0; }
269     ~dynabuf() { delete[] bufptr; }
270     size_t length() const { return bufptr ? strlen(bufptr) : 0; }
271     size_t size() const { return buflen; }
272     bool empty() const { return length()==0; }
273     void reserve(size_t s, bool keep=false);
274     void erase() { if (bufptr) memset(bufptr,0,buflen); }
275     operator char*() { return bufptr; }
276     bool operator ==(const char* s) const;
277     bool operator !=(const char* s) const { return !(*this==s); }
278 private:
279     char* bufptr;
280     size_t buflen;
281 };
282
283 void dynabuf::reserve(size_t s, bool keep)
284 {
285     if (s<=buflen)
286         return;
287     char* p=new char[s];
288     if (keep)
289         while (buflen--)
290             p[buflen]=bufptr[buflen];
291     buflen=s;
292     delete[] bufptr;
293     bufptr=p;
294 }
295
296 bool dynabuf::operator==(const char* s) const
297 {
298     if (buflen==NULL || s==NULL)
299         return (buflen==NULL && s==NULL);
300     else
301         return strcmp(bufptr,s)==0;
302 }
303
304 void GetServerVariable(PHTTP_FILTER_CONTEXT pfc, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
305     throw (bad_alloc, DWORD)
306 {
307     s.reserve(size);
308     s.erase();
309     size=s.size();
310
311     while (!pfc->GetServerVariable(pfc,lpszVariable,s,&size))
312     {
313         // Grumble. Check the error.
314         DWORD e=GetLastError();
315         if (e==ERROR_INSUFFICIENT_BUFFER)
316             s.reserve(size);
317         else
318             break;
319     }
320     if (bRequired && s.empty())
321         throw ERROR_NO_DATA;
322 }
323
324 void GetServerVariable(LPEXTENSION_CONTROL_BLOCK lpECB, LPSTR lpszVariable, dynabuf& s, DWORD size=80, bool bRequired=true)
325     throw (bad_alloc, DWORD)
326 {
327     s.reserve(size);
328     s.erase();
329     size=s.size();
330
331     while (lpECB->GetServerVariable(lpECB->ConnID,lpszVariable,s,&size))
332     {
333         // Grumble. Check the error.
334         DWORD e=GetLastError();
335         if (e==ERROR_INSUFFICIENT_BUFFER)
336             s.reserve(size);
337         else
338             break;
339     }
340     if (bRequired && s.empty())
341         throw ERROR_NO_DATA;
342 }
343
344 void GetHeader(PHTTP_FILTER_PREPROC_HEADERS pn, PHTTP_FILTER_CONTEXT pfc,
345                LPSTR lpszName, dynabuf& s, DWORD size=80, bool bRequired=true)
346     throw (bad_alloc, DWORD)
347 {
348     s.reserve(size);
349     s.erase();
350     size=s.size();
351
352     while (!pn->GetHeader(pfc,lpszName,s,&size))
353     {
354         // Grumble. Check the error.
355         DWORD e=GetLastError();
356         if (e==ERROR_INSUFFICIENT_BUFFER)
357             s.reserve(size);
358         else
359             break;
360     }
361     if (bRequired && s.empty())
362         throw ERROR_NO_DATA;
363 }
364
365 /****************************************************************************/
366 // ISAPI Filter
367
368 class ShibTargetIsapiF : public ShibTarget
369 {
370   PHTTP_FILTER_CONTEXT m_pfc;
371   PHTTP_FILTER_PREPROC_HEADERS m_pn;
372   string m_cookie;
373   Category* m_log;
374 public:
375   ShibTargetIsapiF(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, const site_t& site)
376     : m_log(&Category::getInstance("isapi_shib"))
377   {
378
379     // URL path always come from IIS.
380     dynabuf url(256);
381     GetHeader(pn,pfc,"url",url,256,false);
382
383     // Port may come from IIS or from site def.
384     dynabuf port(11);
385     if (!g_bNormalizeRequest || (pfc->fIsSecurePort && site.m_sslport.empty()) || (!pfc->fIsSecurePort && site.m_port.empty()))
386         GetServerVariable(pfc,"SERVER_PORT",port,10);
387     else if (pfc->fIsSecurePort) {
388         strncpy(port,site.m_sslport.c_str(),10);
389         static_cast<char*>(port)[10]=0;
390     }
391     else {
392         strncpy(port,site.m_port.c_str(),10);
393         static_cast<char*>(port)[10]=0;
394     }
395     
396     // Scheme may come from site def or be derived from IIS.
397     const char* scheme=site.m_scheme.c_str();
398     if (!scheme || !*scheme || !g_bNormalizeRequest)
399         scheme=pfc->fIsSecurePort ? "https" : "http";
400
401     // Get the rest of the server variables.
402     dynabuf remote_addr(16),method(5),content_type(32),hostname(32);
403     GetServerVariable(pfc,"SERVER_NAME",hostname,32);
404     GetServerVariable(pfc,"REMOTE_ADDR",remote_addr,16);
405     // The last two appear to be unavailable to this filter hook, but we don't need them.
406     GetServerVariable(pfc,"REQUEST_METHOD",method,5,false);
407     GetServerVariable(pfc,"CONTENT_TYPE",content_type,32,false);
408
409     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
410     const char* host=hostname;
411     if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
412         host=site.m_name.c_str();
413
414     init(g_Config, scheme, host, atoi(port), url, content_type, remote_addr, method); 
415
416     m_pfc = pfc;
417     m_pn = pn;
418   }
419   ~ShibTargetIsapiF() { }
420
421   virtual void log(ShibLogLevel level, const string &msg) {
422       if (level == LogLevelError)
423           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
424       m_log->log(
425         (level == LogLevelDebug ? Priority::DEBUG :
426             (level == LogLevelInfo ? Priority::INFO :
427             (level == LogLevelWarn ? Priority::WARN : Priority::ERROR))),
428         msg
429         );
430   }
431   virtual string getCookies() const {
432     dynabuf buf(128);
433     GetHeader(m_pn, m_pfc, "Cookie:", buf, 128, false);
434     return buf.empty() ? "" : buf;
435   }
436   
437   virtual void clearHeader(const string &name) {
438     string hdr = name + ":";
439     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()), "");
440   }
441   virtual void setHeader(const string &name, const string &value) {
442     string hdr = name + ":";
443     m_pn->SetHeader(m_pfc, const_cast<char*>(hdr.c_str()),
444                     const_cast<char*>(value.c_str()));
445   }
446   virtual string getHeader(const string &name) {
447     string hdr = name + ":";
448     dynabuf buf(1024);
449     GetHeader(m_pn, m_pfc, const_cast<char*>(hdr.c_str()), buf, 1024, false);
450     return string(buf);
451   }
452   virtual void setRemoteUser(const string &user) {
453     setHeader(string("remote-user"), user);
454   }
455   virtual string getRemoteUser(void) {
456     return getHeader(string("remote-user"));
457   }
458   virtual void* sendPage(
459     const string& msg,
460     int code=200,
461     const string& content_type="text/html",
462     const Iterator<header_t>& headers=EMPTY(header_t)) {
463     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
464     while (headers.hasNext()) {
465         const header_t& h=headers.next();
466         hdr += h.first + ": " + h.second + "\r\n";
467     }
468     hdr += "\r\n";
469     const char* codestr="200 OK";
470     switch (code) {
471         case 403:   codestr="403 Forbidden"; break;
472         case 404:   codestr="404 Not Found"; break;
473         case 500:   codestr="500 Server Error"; break;
474     }
475     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER, (void*)codestr, (DWORD)hdr.c_str(), 0);
476     DWORD resplen = msg.size();
477     m_pfc->WriteClient(m_pfc, (LPVOID)msg.c_str(), &resplen, 0);
478     return (void*)SF_STATUS_REQ_FINISHED;
479   }
480   virtual void* sendRedirect(const string& url) {
481     // XXX: Don't support the httpRedirect option, yet.
482     string hdrs=m_cookie + string("Location: ") + url + "\r\n"
483       "Content-Type: text/html\r\n"
484       "Content-Length: 40\r\n"
485       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
486       "Cache-Control: private,no-store,no-cache\r\n\r\n";
487     m_pfc->ServerSupportFunction(m_pfc, SF_REQ_SEND_RESPONSE_HEADER,
488                                  "302 Please Wait", (DWORD)hdrs.c_str(), 0);
489     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
490     DWORD resplen=40;
491     m_pfc->WriteClient(m_pfc, (LPVOID)redmsg, &resplen, 0);
492     return reinterpret_cast<void*>(SF_STATUS_REQ_FINISHED);
493   }
494   // XXX: We might not ever hit the 'decline' status in this filter.
495   //virtual void* returnDecline(void) { }
496   virtual void* returnOK(void) { return (void*) SF_STATUS_REQ_NEXT_NOTIFICATION; }
497
498   // The filter never processes the POST, so stub these methods.
499   virtual void setCookie(const string &name, const string &value) {
500     // Set the cookie for later.  Use it during the redirect.
501     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
502   }
503   virtual string getArgs(void) { throw runtime_error("getArgs not implemented"); }
504   virtual string getPostData(void) { throw runtime_error("getPostData not implemented"); }
505 };
506
507 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const char* msg)
508 {
509     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
510     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
511     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
512     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Filter Error</TITLE></HEAD><BODY>"
513                             "<H1>Shibboleth Filter Error</H1>";
514     DWORD resplen=strlen(xmsg);
515     pfc->WriteClient(pfc,(LPVOID)xmsg,&resplen,0);
516     resplen=strlen(msg);
517     pfc->WriteClient(pfc,(LPVOID)msg,&resplen,0);
518     static const char* xmsg2="</BODY></HTML>";
519     resplen=strlen(xmsg2);
520     pfc->WriteClient(pfc,(LPVOID)xmsg2,&resplen,0);
521     return SF_STATUS_REQ_FINISHED;
522 }
523
524 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
525 {
526     // Is this a log notification?
527     if (notificationType==SF_NOTIFY_LOG)
528     {
529         if (pfc->pFilterContext)
530             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
531         return SF_STATUS_REQ_NEXT_NOTIFICATION;
532     }
533
534     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
535     try
536     {
537         // Determine web site number. This can't really fail, I don't think.
538         dynabuf buf(128);
539         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
540
541         // Match site instance to host name, skip if no match.
542         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
543         if (map_i==g_Sites.end())
544             return SF_STATUS_REQ_NEXT_NOTIFICATION;
545             
546         ostringstream threadid;
547         threadid << "[" << getpid() << "] isapi_shib" << '\0';
548         saml::NDC ndc(threadid.str().c_str());
549
550         ShibTargetIsapiF stf(pfc, pn, map_i->second);
551
552         // "false" because we don't override the Shib settings
553         pair<bool,void*> res = stf.doCheckAuthN();
554         if (res.first) return (DWORD)res.second;
555
556         // "false" because we don't override the Shib settings
557         res = stf.doExportAssertions();
558         if (res.first) return (DWORD)res.second;
559
560         res = stf.doCheckAuthZ();
561         if (res.first) return (DWORD)res.second;
562
563         return SF_STATUS_REQ_NEXT_NOTIFICATION;
564     }
565     catch(bad_alloc) {
566         return WriteClientError(pfc,"Out of Memory");
567     }
568     catch(long e) {
569         if (e==ERROR_NO_DATA)
570             return WriteClientError(pfc,"A required variable or header was empty.");
571         else
572             return WriteClientError(pfc,"Server detected unexpected IIS error.");
573     }
574 #ifndef _DEBUG
575     catch(...) {
576         return WriteClientError(pfc,"Server caught an unknown exception.");
577     }
578 #endif
579
580     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
581 }
582         
583
584 #if 0
585 IRequestMapper::Settings map_request(
586     PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pn, IRequestMapper* mapper, const site_t& site, string& target
587     )
588 {
589     // URL path always come from IIS.
590     dynabuf url(256);
591     GetHeader(pn,pfc,"url",url,256,false);
592
593     // Port may come from IIS or from site def.
594     dynabuf port(11);
595     if (site.m_port.empty() || !g_bNormalizeRequest)
596         GetServerVariable(pfc,"SERVER_PORT",port,10);
597     else {
598         strncpy(port,site.m_port.c_str(),10);
599         static_cast<char*>(port)[10]=0;
600     }
601     
602     // Scheme may come from site def or be derived from IIS.
603     const char* scheme=site.m_scheme.c_str();
604     if (!scheme || !*scheme || !g_bNormalizeRequest)
605         scheme=pfc->fIsSecurePort ? "https" : "http";
606
607     // Start with scheme and hostname.
608     if (g_bNormalizeRequest) {
609         target = string(scheme) + "://" + site.m_name;
610     }
611     else {
612         dynabuf name(64);
613         GetServerVariable(pfc,"SERVER_NAME",name,64);
614         target = string(scheme) + "://" + static_cast<char*>(name);
615     }
616     
617     // If port is non-default, append it.
618     if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
619         target = target + ':' + static_cast<char*>(port);
620
621     // Append path.
622     if (!url.empty())
623         target+=static_cast<char*>(url);
624     
625     return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
626 }
627
628 DWORD WriteClientError(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* page, ShibMLP& mlp)
629 {
630     const IPropertySet* props=app->getPropertySet("Errors");
631     if (props) {
632         pair<bool,const char*> p=props->getString(page);
633         if (p.first) {
634             ifstream infile(p.second);
635             if (!infile.fail()) {
636                 const char* res = mlp.run(infile,props);
637                 if (res) {
638                     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
639                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)ctype,0);
640                     DWORD resplen=strlen(res);
641                     pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
642                     return SF_STATUS_REQ_FINISHED;
643                 }
644             }
645         }
646     }
647
648     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Filter unable to open error template.");
649     return WriteClientError(pfc,"Unable to open error template, check settings.");
650 }
651
652 DWORD WriteRedirectPage(PHTTP_FILTER_CONTEXT pfc, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
653 {
654     ifstream infile(file);
655     if (!infile.fail()) {
656         const char* res = mlp.run(infile,app->getPropertySet("Errors"));
657         if (res) {
658             char buf[255];
659             sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
660             if (headers) {
661                 string h(headers);
662                 h+=buf;
663                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)h.c_str(),0);
664             }
665             else
666                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"200 OK",(DWORD)buf,0);
667             DWORD resplen=strlen(res);
668             pfc->WriteClient(pfc,(LPVOID)res,&resplen,0);
669             return SF_STATUS_REQ_FINISHED;
670         }
671     }
672     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
673     return WriteClientError(pfc,"Unable to open redirect template, check settings.");
674 }
675
676 extern "C" DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification)
677 {
678     // Is this a log notification?
679     if (notificationType==SF_NOTIFY_LOG)
680     {
681         if (pfc->pFilterContext)
682             ((PHTTP_FILTER_LOG)pvNotification)->pszClientUserName=static_cast<LPCSTR>(pfc->pFilterContext);
683         return SF_STATUS_REQ_NEXT_NOTIFICATION;
684     }
685
686     PHTTP_FILTER_PREPROC_HEADERS pn=(PHTTP_FILTER_PREPROC_HEADERS)pvNotification;
687     try
688     {
689         // Determine web site number. This can't really fail, I don't think.
690         dynabuf buf(128);
691         GetServerVariable(pfc,"INSTANCE_ID",buf,10);
692
693         // Match site instance to host name, skip if no match.
694         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
695         if (map_i==g_Sites.end())
696             return SF_STATUS_REQ_NEXT_NOTIFICATION;
697             
698         ostringstream threadid;
699         threadid << "[" << getpid() << "] isapi_shib" << '\0';
700         saml::NDC ndc(threadid.str().c_str());
701         
702         // We lock the configuration system for the duration.
703         IConfig* conf=g_Config->getINI();
704         Locker locker(conf);
705         
706         // Map request to application and content settings.
707         string targeturl;
708         IRequestMapper* mapper=conf->getRequestMapper();
709         Locker locker2(mapper);
710         IRequestMapper::Settings settings=map_request(pfc,pn,mapper,map_i->second,targeturl);
711         pair<bool,const char*> application_id=settings.first->getString("applicationId");
712         const IApplication* application=conf->getApplication(application_id.second);
713         if (!application)
714             return WriteClientError(pfc,"Unable to map request to application settings, check configuration.");
715         
716         // Declare SHIRE object for this request.
717         SHIRE shire(application);
718         
719         const char* shireURL=shire.getShireURL(targeturl.c_str());
720         if (!shireURL)
721             return WriteClientError(pfc,"Unable to map request to proper shireURL setting, check configuration.");
722
723         // If the user is accessing the SHIRE acceptance point, pass it on.
724         if (targeturl.find(shireURL)!=string::npos)
725             return SF_STATUS_REQ_NEXT_NOTIFICATION;
726
727         // Now check the policy for this request.
728         pair<bool,bool> requireSession=settings.first->getBool("requireSession");
729         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
730         pair<bool,bool> httpRedirects=application->getPropertySet("Sessions")->getBool("httpRedirects");
731         pair<bool,const char*> redirectPage=application->getPropertySet("Sessions")->getString("redirectPage");
732         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
733             return WriteClientError(pfc,"HTML-based redirection requires a redirectPage property.");
734
735         // Check for session cookie.
736         const char* session_id=NULL;
737         GetHeader(pn,pfc,"Cookie:",buf,128,false);
738         Category::getInstance("isapi_shib.HttpFilterProc").debug("cookie header is {%s}",(const char*)buf);
739         if (!buf.empty() && (session_id=strstr(buf,shib_cookie.first))) {
740             session_id+=strlen(shib_cookie.first) + 1;   /* Skip over the '=' */
741             char* cookieend=strchr(session_id,';');
742             if (cookieend)
743                 *cookieend = '\0';    /* Ignore anyting after a ; */
744         }
745         
746         if (!session_id || !*session_id) {
747             // If no session required, bail now.
748             if (!requireSession.second)
749                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
750     
751             // No acceptable cookie, and we require a session.  Generate an AuthnRequest.
752             const char* areq = shire.getAuthnRequest(targeturl.c_str());
753             if (!httpRedirects.first || httpRedirects.second) {
754                 string hdrs=string("Location: ") + areq + "\r\n"
755                     "Content-Type: text/html\r\n"
756                     "Content-Length: 40\r\n"
757                     "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
758                     "Cache-Control: private,no-store,no-cache\r\n\r\n";
759                 pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
760                 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
761                 DWORD resplen=40;
762                 pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
763                 return SF_STATUS_REQ_FINISHED;
764             }
765             else {
766                 ShibMLP markupProcessor;
767                 markupProcessor.insert("requestURL",areq);
768                 return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
769             }
770         }
771
772         // Make sure this session is still valid.
773         RPCError* status = NULL;
774         ShibMLP markupProcessor;
775         markupProcessor.insert("requestURL", targeturl);
776     
777         dynabuf abuf(16);
778         GetServerVariable(pfc,"REMOTE_ADDR",abuf,16);
779         try {
780             status = shire.sessionIsValid(session_id, abuf);
781         }
782         catch (ShibTargetException &e) {
783             markupProcessor.insert("errorType", "Session Processing Error");
784             markupProcessor.insert("errorText", e.what());
785             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
786             return WriteClientError(pfc, application, "shire", markupProcessor);
787         }
788 #ifndef _DEBUG
789         catch (...) {
790             markupProcessor.insert("errorType", "Session Processing Error");
791             markupProcessor.insert("errorText", "Unexpected Exception");
792             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
793             return WriteClientError(pfc, application, "shire", markupProcessor);
794         }
795 #endif
796
797         // Check the status
798         if (status->isError()) {
799             if (!requireSession.second)
800                 return SF_STATUS_REQ_NEXT_NOTIFICATION;
801             else if (status->isRetryable()) {
802                 // Oops, session is invalid. Generate AuthnRequest.
803                 delete status;
804                 const char* areq = shire.getAuthnRequest(targeturl.c_str());
805                 if (!httpRedirects.first || httpRedirects.second) {
806                     string hdrs=string("Location: ") + areq + "\r\n"
807                         "Content-Type: text/html\r\n"
808                         "Content-Length: 40\r\n"
809                         "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
810                         "Cache-Control: private,no-store,no-cache\r\n\r\n";
811                     pfc->ServerSupportFunction(pfc,SF_REQ_SEND_RESPONSE_HEADER,"302 Please Wait",(DWORD)hdrs.c_str(),0);
812                     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
813                     DWORD resplen=40;
814                     pfc->WriteClient(pfc,(LPVOID)redmsg,&resplen,0);
815                     return SF_STATUS_REQ_FINISHED;
816                 }
817                 else {
818                     markupProcessor.insert("requestURL",areq);
819                     return WriteRedirectPage(pfc, application, redirectPage.second, markupProcessor);
820                 }
821             }
822             else {
823                 // return the error page to the user
824                 markupProcessor.insert(*status);
825                 delete status;
826                 return WriteClientError(pfc, application, "shire", markupProcessor);
827             }
828         }
829         delete status;
830     
831         // Move to RM phase.
832         RM rm(application);
833         vector<SAMLAssertion*> assertions;
834         SAMLAuthenticationStatement* sso_statement=NULL;
835
836         try {
837             status = rm.getAssertions(session_id, abuf, assertions, &sso_statement);
838         }
839         catch (ShibTargetException &e) {
840             markupProcessor.insert("errorType", "Attribute Processing Error");
841             markupProcessor.insert("errorText", e.what());
842             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
843             return WriteClientError(pfc, application, "rm", markupProcessor);
844         }
845     #ifndef _DEBUG
846         catch (...) {
847             markupProcessor.insert("errorType", "Attribute Processing Error");
848             markupProcessor.insert("errorText", "Unexpected Exception");
849             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
850             return WriteClientError(pfc, application, "rm", markupProcessor);
851         }
852     #endif
853     
854         if (status->isError()) {
855             markupProcessor.insert(*status);
856             delete status;
857             return WriteClientError(pfc, application, "rm", markupProcessor);
858         }
859         delete status;
860
861         // Do we have an access control plugin?
862         if (settings.second) {
863             Locker acllock(settings.second);
864             if (!settings.second->authorized(*sso_statement,assertions)) {
865                 for (int k = 0; k < assertions.size(); k++)
866                     delete assertions[k];
867                 delete sso_statement;
868                 return WriteClientError(pfc, application, "access", markupProcessor);
869             }
870         }
871
872         // Get the AAP providers, which contain the attribute policy info.
873         Iterator<IAAP*> provs=application->getAAPProviders();
874     
875         // Clear out the list of mapped attributes
876         while (provs.hasNext()) {
877             IAAP* aap=provs.next();
878             aap->lock();
879             try {
880                 Iterator<const IAttributeRule*> rules=aap->getAttributeRules();
881                 while (rules.hasNext()) {
882                     const char* header=rules.next()->getHeader();
883                     if (header) {
884                         string hname=string(header) + ':';
885                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),"");
886                     }
887                 }
888             }
889             catch(...) {
890                 aap->unlock();
891                 for (int k = 0; k < assertions.size(); k++)
892                   delete assertions[k];
893                 delete sso_statement;
894                 markupProcessor.insert("errorType", "Attribute Processing Error");
895                 markupProcessor.insert("errorText", "Unexpected Exception");
896                 markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
897                 return WriteClientError(pfc, application, "rm", markupProcessor);
898             }
899             aap->unlock();
900         }
901         provs.reset();
902
903         // Maybe export the first assertion.
904         pn->SetHeader(pfc,"remote-user:","");
905         pn->SetHeader(pfc,"Shib-Attributes:","");
906         pair<bool,bool> exp=settings.first->getBool("exportAssertion");
907         if (exp.first && exp.second && assertions.size()) {
908             string assertion;
909             RM::serialize(*(assertions[0]), assertion);
910             string::size_type lfeed;
911             while ((lfeed=assertion.find('\n'))!=string::npos)
912                 assertion.erase(lfeed,1);
913             pn->SetHeader(pfc,"Shib-Attributes:",const_cast<char*>(assertion.c_str()));
914         }
915         
916         pn->SetHeader(pfc,"Shib-Origin-Site:","");
917         pn->SetHeader(pfc,"Shib-Authentication-Method:","");
918         pn->SetHeader(pfc,"Shib-NameIdentifier-Format:","");
919
920         // Export the SAML AuthnMethod and the origin site name.
921         auto_ptr_char os(sso_statement->getSubject()->getNameIdentifier()->getNameQualifier());
922         auto_ptr_char am(sso_statement->getAuthMethod());
923         pn->SetHeader(pfc,"Shib-Origin-Site:", const_cast<char*>(os.get()));
924         pn->SetHeader(pfc,"Shib-Authentication-Method:", const_cast<char*>(am.get()));
925
926         // Export NameID?
927         AAP wrapper(provs,sso_statement->getSubject()->getNameIdentifier()->getFormat(),Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
928         if (!wrapper.fail() && wrapper->getHeader()) {
929             auto_ptr_char form(sso_statement->getSubject()->getNameIdentifier()->getFormat());
930             auto_ptr_char nameid(sso_statement->getSubject()->getNameIdentifier()->getName());
931             pn->SetHeader(pfc,"Shib-NameIdentifier-Format:",const_cast<char*>(form.get()));
932             if (!strcmp(wrapper->getHeader(),"REMOTE_USER")) {
933                 char* principal=const_cast<char*>(nameid.get());
934                 pn->SetHeader(pfc,"remote-user:",principal);
935                 pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
936                 if (pfc->pFilterContext)
937                     strcpy(static_cast<char*>(pfc->pFilterContext),principal);
938             }
939             else {
940                 string hname=string(wrapper->getHeader()) + ':';
941                 pn->SetHeader(pfc,const_cast<char*>(wrapper->getHeader()),const_cast<char*>(nameid.get()));
942             }
943         }
944
945         pn->SetHeader(pfc,"Shib-Application-ID:","");
946         pn->SetHeader(pfc,"Shib-Application-ID:",const_cast<char*>(application_id.second));
947
948         // Export the attributes.
949         Iterator<SAMLAssertion*> a_iter(assertions);
950         while (a_iter.hasNext()) {
951             SAMLAssertion* assert=a_iter.next();
952             Iterator<SAMLStatement*> statements=assert->getStatements();
953             while (statements.hasNext()) {
954                 SAMLAttributeStatement* astate=dynamic_cast<SAMLAttributeStatement*>(statements.next());
955                 if (!astate)
956                     continue;
957                 Iterator<SAMLAttribute*> attrs=astate->getAttributes();
958                 while (attrs.hasNext()) {
959                     SAMLAttribute* attr=attrs.next();
960         
961                     // Are we supposed to export it?
962                     AAP wrapper(provs,attr->getName(),attr->getNamespace());
963                     if (wrapper.fail() || !wrapper->getHeader())
964                         continue;
965                 
966                     Iterator<string> vals=attr->getSingleByteValues();
967                     if (!strcmp(wrapper->getHeader(),"REMOTE_USER") && vals.hasNext()) {
968                         char* principal=const_cast<char*>(vals.next().c_str());
969                         pn->SetHeader(pfc,"remote-user:",principal);
970                         pfc->pFilterContext=pfc->AllocMem(pfc,strlen(principal)+1,0);
971                         if (pfc->pFilterContext)
972                             strcpy(static_cast<char*>(pfc->pFilterContext),principal);
973                     }
974                     else {
975                         int it=0;
976                         string header;
977                         string hname=string(wrapper->getHeader()) + ':';
978                         GetHeader(pn,pfc,const_cast<char*>(hname.c_str()),buf,256,false);
979                         if (!buf.empty()) {
980                             header=buf;
981                             it++;
982                         }
983                         for (; vals.hasNext(); it++) {
984                             string value = vals.next();
985                             for (string::size_type pos = value.find_first_of(";", string::size_type(0));
986                                     pos != string::npos;
987                                     pos = value.find_first_of(";", pos)) {
988                                 value.insert(pos, "\\");
989                                 pos += 2;
990                             }
991                             if (it == 0)
992                                 header=value;
993                             else
994                                 header=header + ';' + value;
995                         }
996                         pn->SetHeader(pfc,const_cast<char*>(hname.c_str()),const_cast<char*>(header.c_str()));
997                         }
998                 }
999             }
1000         }
1001     
1002         // clean up memory
1003         for (int k = 0; k < assertions.size(); k++)
1004           delete assertions[k];
1005         delete sso_statement;
1006
1007         return SF_STATUS_REQ_NEXT_NOTIFICATION;
1008     }
1009     catch(bad_alloc) {
1010         return WriteClientError(pfc,"Out of Memory");
1011     }
1012     catch(DWORD e) {
1013         if (e==ERROR_NO_DATA)
1014             return WriteClientError(pfc,"A required variable or header was empty.");
1015         else
1016             return WriteClientError(pfc,"Server detected unexpected IIS error.");
1017     }
1018 #ifndef _DEBUG
1019     catch(...) {
1020         return WriteClientError(pfc,"Server caught an unknown exception.");
1021     }
1022 #endif
1023
1024     return WriteClientError(pfc,"Server reached unreachable code, save my walrus!");
1025 }
1026 #endif // 0
1027
1028 /****************************************************************************/
1029 // ISAPI Extension
1030
1031 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const char* msg)
1032 {
1033     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg);
1034     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1035     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1036     static const char* xmsg="<HTML><HEAD><TITLE>Shibboleth Error</TITLE></HEAD><BODY><H1>Shibboleth Error</H1>";
1037     DWORD resplen=strlen(xmsg);
1038     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg,&resplen,HSE_IO_SYNC);
1039     resplen=strlen(msg);
1040     lpECB->WriteClient(lpECB->ConnID,(LPVOID)msg,&resplen,HSE_IO_SYNC);
1041     static const char* xmsg2="</BODY></HTML>";
1042     resplen=strlen(xmsg2);
1043     lpECB->WriteClient(lpECB->ConnID,(LPVOID)xmsg2,&resplen,HSE_IO_SYNC);
1044     return HSE_STATUS_SUCCESS;
1045 }
1046
1047
1048 class ShibTargetIsapiE : public ShibTarget
1049 {
1050   LPEXTENSION_CONTROL_BLOCK m_lpECB;
1051   string m_cookie;
1052   Category* m_log;
1053   
1054 public:
1055   ShibTargetIsapiE(LPEXTENSION_CONTROL_BLOCK lpECB, const site_t& site)
1056     : m_log(&Category::getInstance("isapi_shib"))
1057   {
1058     dynabuf ssl(5);
1059     GetServerVariable(lpECB,"HTTPS",ssl,5);
1060     bool SSL=(ssl=="on" || ssl=="ON");
1061
1062     // URL path always come from IIS.
1063     dynabuf url(256);
1064     GetServerVariable(lpECB,"URL",url,255);
1065
1066     // Port may come from IIS or from site def.
1067     dynabuf port(11);
1068     if (!g_bNormalizeRequest || (SSL && site.m_sslport.empty()) || (!SSL && site.m_port.empty()))
1069         GetServerVariable(lpECB,"SERVER_PORT",port,10);
1070     else if (SSL) {
1071         strncpy(port,site.m_sslport.c_str(),10);
1072         static_cast<char*>(port)[10]=0;
1073     }
1074     else {
1075         strncpy(port,site.m_port.c_str(),10);
1076         static_cast<char*>(port)[10]=0;
1077     }
1078
1079     // Scheme may come from site def or be derived from IIS.
1080     const char* scheme=site.m_scheme.c_str();
1081     if (!scheme || !*scheme || !g_bNormalizeRequest) {
1082         scheme = SSL ? "https" : "http";
1083     }
1084
1085     // Get the other server variables.
1086     dynabuf remote_addr(16),hostname(32);
1087     GetServerVariable(lpECB, "REMOTE_ADDR", remote_addr, 16);
1088     GetServerVariable(lpECB, "SERVER_NAME", hostname, 32);
1089
1090     // Make sure SERVER_NAME is "authorized" for use on this site. If not, set to canonical name.
1091     const char* host=hostname;
1092     if (site.m_name!=host && site.m_aliases.find(host)==site.m_aliases.end())
1093         host=site.m_name.c_str();
1094
1095     init(g_Config, scheme, host, atoi(port), url, lpECB->lpszContentType, remote_addr, lpECB->lpszMethod);
1096
1097     m_lpECB = lpECB;
1098   }
1099   ~ShibTargetIsapiE() { }
1100
1101   virtual void log(ShibLogLevel level, const string &msg) {
1102       if (level == LogLevelError)
1103           LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, msg.c_str());
1104       m_log->log(
1105         (level == LogLevelDebug ? Priority::DEBUG :
1106             (level == LogLevelInfo ? Priority::INFO :
1107             (level == LogLevelWarn ? Priority::WARN : Priority::ERROR))),
1108         msg
1109         );
1110   }
1111   virtual string getCookies() const {
1112     dynabuf buf(128);
1113     GetServerVariable(m_lpECB, "HTTP_COOKIE", buf, 128, false);
1114     return buf.empty() ? "" : buf;
1115   }
1116   virtual void setCookie(const string &name, const string &value) {
1117     // Set the cookie for later.  Use it during the redirect.
1118     m_cookie += "Set-Cookie: " + name + "=" + value + "\r\n";
1119   }
1120   virtual string getArgs(void) {
1121     return string(m_lpECB->lpszQueryString ? m_lpECB->lpszQueryString : "");
1122   }
1123   virtual string getPostData(void) {
1124     if (m_lpECB->cbTotalBytes > 1024*1024) // 1MB?
1125       throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
1126     else if (m_lpECB->cbTotalBytes != m_lpECB->cbAvailable) {
1127       string cgistr;
1128       char buf[8192];
1129       DWORD datalen=m_lpECB->cbTotalBytes;
1130       while (datalen) {
1131         DWORD buflen=8192;
1132         BOOL ret = m_lpECB->ReadClient(m_lpECB->ConnID, buf, &buflen);
1133         if (!ret || !buflen)
1134           throw FatalProfileException("Error reading profile submission from browser.");
1135         cgistr.append(buf, buflen);
1136         datalen-=buflen;
1137       }
1138       return cgistr;
1139     }
1140     else
1141       return string(reinterpret_cast<char*>(m_lpECB->lpbData),m_lpECB->cbAvailable);
1142   }
1143   virtual void* sendPage(
1144     const string &msg,
1145     int code=200,
1146     const string& content_type="text/html",
1147     const Iterator<header_t>& headers=EMPTY(header_t)) {
1148     string hdr = string ("Connection: close\r\nContent-type: ") + content_type + "\r\n";
1149     for (int k = 0; k < headers.size(); k++) {
1150       hdr += headers[k].first + ": " + headers[k].second + "\r\n";
1151     }
1152     hdr += "\r\n";
1153     const char* codestr="200 OK";
1154     switch (code) {
1155         case 403:   codestr="403 Forbidden"; break;
1156         case 404:   codestr="404 Not Found"; break;
1157         case 500:   codestr="500 Server Error"; break;
1158     }
1159     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void*)codestr, 0, (LPDWORD)hdr.c_str());
1160     DWORD resplen = msg.size();
1161     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)msg.c_str(), &resplen, HSE_IO_SYNC);
1162     return (void*)HSE_STATUS_SUCCESS;
1163   }
1164   virtual void* sendRedirect(const string& url) {
1165     // XXX: Don't support the httpRedirect option, yet.
1166     string hdrs = m_cookie + "Location: " + url + "\r\n"
1167       "Content-Type: text/html\r\n"
1168       "Content-Length: 40\r\n"
1169       "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1170       "Cache-Control: private,no-store,no-cache\r\n\r\n";
1171     m_lpECB->ServerSupportFunction(m_lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
1172                                  "302 Moved", 0, (LPDWORD)hdrs.c_str());
1173     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1174     DWORD resplen=40;
1175     m_lpECB->WriteClient(m_lpECB->ConnID, (LPVOID)redmsg, &resplen, HSE_IO_SYNC);
1176     return (void*)HSE_STATUS_SUCCESS;
1177   }
1178   // Decline happens in the POST processor if this isn't the shire url
1179   // Note that it can also happen with HTAccess, but we don't support that, yet.
1180   virtual void* returnDecline(void) {
1181     return (void*)
1182       WriteClientError(m_lpECB, "ISAPA extension can only be invoked to process incoming sessions."
1183                        "Make sure the mapped file extension doesn't match actual content.");
1184   }
1185   virtual void* returnOK(void) { return (void*) HSE_STATUS_SUCCESS; }
1186
1187   // Not used in the extension.
1188   virtual void clearHeader(const string &name) { throw runtime_error("clearHeader not implemented"); }
1189   virtual void setHeader(const string &name, const string &value) { throw runtime_error("setHeader not implemented"); }
1190   virtual string getHeader(const string &name) { throw runtime_error("getHeader not implemented"); }
1191   virtual void setRemoteUser(const string &user) { throw runtime_error("setRemoteUser not implemented"); }
1192   virtual string getRemoteUser(void) { throw runtime_error("getRemoteUser not implemented"); }
1193 };
1194
1195 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1196 {
1197     string targeturl;
1198     const IApplication* application=NULL;
1199     try {
1200         ostringstream threadid;
1201         threadid << "[" << getpid() << "] shire_handler" << '\0';
1202         saml::NDC ndc(threadid.str().c_str());
1203
1204         // Determine web site number. This can't really fail, I don't think.
1205         dynabuf buf(128);
1206         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1207
1208         // Match site instance to host name, skip if no match.
1209         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1210         if (map_i==g_Sites.end())
1211             return WriteClientError(lpECB, "Shibboleth Extension not configured for this web site.");
1212
1213         ShibTargetIsapiE ste(lpECB, map_i->second);
1214         pair<bool,void*> res = ste.doHandleProfile();
1215         if (res.first) return (DWORD)res.second;
1216         
1217         return WriteClientError(lpECB, "Shibboleth Extension failed to process request");
1218
1219     }
1220     catch(bad_alloc) {
1221         return WriteClientError(lpECB,"Out of Memory");
1222     }
1223     catch(long e) {
1224         if (e==ERROR_NO_DATA)
1225             return WriteClientError(lpECB,"A required variable or header was empty.");
1226         else
1227             return WriteClientError(lpECB,"Server detected unexpected IIS error.");
1228     }
1229 #ifndef _DEBUG
1230     catch(...) {
1231         return WriteClientError(lpECB,"Server caught an unknown exception.");
1232     }
1233 #endif
1234
1235     // If we get here we've got an error.
1236     return HSE_STATUS_ERROR;
1237 }
1238
1239 #if 0
1240 IRequestMapper::Settings map_request(
1241     LPEXTENSION_CONTROL_BLOCK lpECB, IRequestMapper* mapper, const site_t& site, string& target
1242     )
1243 {
1244     dynabuf ssl(5);
1245     GetServerVariable(lpECB,"HTTPS",ssl,5);
1246     bool SSL=(ssl=="on" || ssl=="ON");
1247
1248     // URL path always come from IIS.
1249     dynabuf url(256);
1250     GetServerVariable(lpECB,"URL",url,255);
1251
1252     // Port may come from IIS or from site def.
1253     dynabuf port(11);
1254     if (site.m_port.empty() || !g_bNormalizeRequest)
1255         GetServerVariable(lpECB,"SERVER_PORT",port,10);
1256     else {
1257         strncpy(port,site.m_port.c_str(),10);
1258         static_cast<char*>(port)[10]=0;
1259     }
1260
1261     // Scheme may come from site def or be derived from IIS.
1262     const char* scheme=site.m_scheme.c_str();
1263     if (!scheme || !*scheme || !g_bNormalizeRequest) {
1264         scheme = SSL ? "https" : "http";
1265     }
1266
1267     // Start with scheme and hostname.
1268     if (g_bNormalizeRequest) {
1269         target = string(scheme) + "://" + site.m_name;
1270     }
1271     else {
1272         dynabuf name(64);
1273         GetServerVariable(lpECB,"SERVER_NAME",name,64);
1274         target = string(scheme) + "://" + static_cast<char*>(name);
1275     }
1276     
1277     // If port is non-default, append it.
1278     if ((!strcmp(scheme,"http") && port!="80") || (!strcmp(scheme,"https") && port!="443"))
1279         target = target + ':' + static_cast<char*>(port);
1280
1281     // Append path.
1282     if (!url.empty())
1283         target+=static_cast<char*>(url);
1284     
1285     return mapper->getSettingsFromParsedURL(scheme,site.m_name.c_str(),strtoul(port,NULL,10),url);
1286 }
1287
1288 DWORD WriteClientError(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* page, ShibMLP& mlp)
1289 {
1290     const IPropertySet* props=app->getPropertySet("Errors");
1291     if (props) {
1292         pair<bool,const char*> p=props->getString(page);
1293         if (p.first) {
1294             ifstream infile(p.second);
1295             if (!infile.fail()) {
1296                 const char* res = mlp.run(infile,props);
1297                 if (res) {
1298                     static const char* ctype="Connection: close\r\nContent-Type: text/html\r\n\r\n";
1299                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)ctype);
1300                     DWORD resplen=strlen(res);
1301                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1302                     return HSE_STATUS_SUCCESS;
1303                 }
1304             }
1305         }
1306     }
1307     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open error template.");
1308     return WriteClientError(lpECB,"Unable to open error template, check settings.");
1309 }
1310
1311 DWORD WriteRedirectPage(LPEXTENSION_CONTROL_BLOCK lpECB, const IApplication* app, const char* file, ShibMLP& mlp, const char* headers=NULL)
1312 {
1313     ifstream infile(file);
1314     if (!infile.fail()) {
1315         const char* res = mlp.run(infile,app->getPropertySet("Errors"));
1316         if (res) {
1317             char buf[255];
1318             sprintf(buf,"Content-Length: %u\r\nContent-Type: text/html\r\n\r\n",strlen(res));
1319             if (headers) {
1320                 string h(headers);
1321                 h+=buf;
1322                 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)h.c_str());
1323             }
1324             else
1325                 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"200 OK",0,(LPDWORD)buf);
1326             DWORD resplen=strlen(res);
1327             lpECB->WriteClient(lpECB->ConnID,(LPVOID)res,&resplen,0);
1328             return HSE_STATUS_SUCCESS;
1329         }
1330     }
1331     LogEvent(NULL, EVENTLOG_ERROR_TYPE, 2100, NULL, "Extension unable to open redirect template.");
1332     return WriteClientError(lpECB,"Unable to open redirect template, check settings.");
1333 }
1334
1335 extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
1336 {
1337     string targeturl;
1338     const IApplication* application=NULL;
1339     try
1340     {
1341         ostringstream threadid;
1342         threadid << "[" << getpid() << "] shire_handler" << '\0';
1343         saml::NDC ndc(threadid.str().c_str());
1344
1345         // Determine web site number. This can't really fail, I don't think.
1346         dynabuf buf(128);
1347         GetServerVariable(lpECB,"INSTANCE_ID",buf,10);
1348
1349         // Match site instance to host name, skip if no match.
1350         map<string,site_t>::const_iterator map_i=g_Sites.find(static_cast<char*>(buf));
1351         if (map_i==g_Sites.end())
1352             return WriteClientError(lpECB,"Shibboleth filter not configured for this web site.");
1353             
1354         // We lock the configuration system for the duration.
1355         IConfig* conf=g_Config->getINI();
1356         Locker locker(conf);
1357         
1358         // Map request to application and content settings.
1359         IRequestMapper* mapper=conf->getRequestMapper();
1360         Locker locker2(mapper);
1361         IRequestMapper::Settings settings=map_request(lpECB,mapper,map_i->second,targeturl);
1362         pair<bool,const char*> application_id=settings.first->getString("applicationId");
1363         application=conf->getApplication(application_id.second);
1364         const IPropertySet* sessionProps=application ? application->getPropertySet("Sessions") : NULL;
1365         if (!application || !sessionProps)
1366             return WriteClientError(lpECB,"Unable to map request to application session settings, check configuration.");
1367
1368         SHIRE shire(application);
1369         
1370         const char* shireURL=shire.getShireURL(targeturl.c_str());
1371         if (!shireURL)
1372             return WriteClientError(lpECB,"Unable to map request to proper shireURL setting, check configuration.");
1373
1374         // Make sure we only process the SHIRE requests.
1375         if (!strstr(targeturl.c_str(),shireURL))
1376             return WriteClientError(lpECB,"ISAPI extension can only be invoked to process incoming sessions."
1377                 "Make sure the mapped file extension doesn't match actual content.");
1378
1379         pair<const char*,const char*> shib_cookie=shire.getCookieNameProps();
1380
1381         // Make sure this is SSL, if it should be
1382         pair<bool,bool> shireSSL=sessionProps->getBool("shireSSL");
1383         if (!shireSSL.first || shireSSL.second) {
1384             GetServerVariable(lpECB,"HTTPS",buf,10);
1385             if (buf!="on")
1386                 throw ShibTargetException(SHIBRPC_OK,"blocked non-SSL access to SHIRE POST processor");
1387         }
1388         
1389         pair<bool,bool> httpRedirects=sessionProps->getBool("httpRedirects");
1390         pair<bool,const char*> redirectPage=sessionProps->getString("redirectPage");
1391         if (httpRedirects.first && !httpRedirects.second && !redirectPage.first)
1392             return WriteClientError(lpECB,"HTML-based redirection requires a redirectPage property.");
1393         
1394         // Check for Mac web browser
1395         /*
1396         bool bSafari=false;
1397         dynabuf agent(64);
1398         GetServerVariable(lpECB,"HTTP_USER_AGENT",agent,64);
1399         if (strstr(agent,"AppleWebKit/"))
1400             bSafari=true;
1401         */
1402         
1403         // If this is a GET, we manufacture an AuthnRequest.
1404         if (!stricmp(lpECB->lpszMethod,"GET")) {
1405             const char* areq=lpECB->lpszQueryString ? shire.getLazyAuthnRequest(lpECB->lpszQueryString) : NULL;
1406             if (!areq)
1407                 throw ShibTargetException(SHIBRPC_OK, "malformed arguments to request a new session");
1408             if (!httpRedirects.first || httpRedirects.second) {
1409                 string hdrs=string("Location: ") + areq + "\r\n"
1410                     "Content-Type: text/html\r\n"
1411                     "Content-Length: 40\r\n"
1412                     "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1413                     "Cache-Control: private,no-store,no-cache\r\n\r\n";
1414                 lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1415                 static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1416                 DWORD resplen=40;
1417                 lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1418                 return HSE_STATUS_SUCCESS;
1419             }
1420             else {
1421                 ShibMLP markupProcessor;
1422                 markupProcessor.insert("requestURL",areq);
1423                 return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1424             }
1425         }
1426         else if (stricmp(lpECB->lpszMethod,"POST"))
1427             throw ShibTargetException(SHIBRPC_OK,"blocked non-POST to SHIRE POST processor");
1428
1429         // Sure sure this POST is an appropriate content type
1430         if (!lpECB->lpszContentType || stricmp(lpECB->lpszContentType,"application/x-www-form-urlencoded"))
1431             throw ShibTargetException(SHIBRPC_OK,"blocked bad content-type to SHIRE POST processor");
1432     
1433         // Read the data.
1434         pair<const char*,const char*> elements=pair<const char*,const char*>(NULL,NULL);
1435         if (lpECB->cbTotalBytes > 1024*1024) // 1MB?
1436             throw ShibTargetException(SHIBRPC_OK,"blocked too-large a post to SHIRE POST processor");
1437         else if (lpECB->cbTotalBytes!=lpECB->cbAvailable) {
1438             string cgistr;
1439             char buf[8192];
1440             DWORD datalen=lpECB->cbTotalBytes;
1441             while (datalen) {
1442                 DWORD buflen=8192;
1443                 BOOL ret=lpECB->ReadClient(lpECB->ConnID,buf,&buflen);
1444                 if (!ret || !buflen)
1445                     throw ShibTargetException(SHIBRPC_OK,"error reading POST data from browser");
1446                 cgistr.append(buf,buflen);
1447                 datalen-=buflen;
1448             }
1449             elements=shire.getFormSubmission(cgistr.c_str(),cgistr.length());
1450         }
1451         else
1452             elements=shire.getFormSubmission(reinterpret_cast<char*>(lpECB->lpbData),lpECB->cbAvailable);
1453     
1454         // Make sure the SAML Response parameter exists
1455         if (!elements.first || !*elements.first)
1456             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find SAMLResponse form element");
1457     
1458         // Make sure the target parameter exists
1459         if (!elements.second || !*elements.second)
1460             throw ShibTargetException(SHIBRPC_OK, "SHIRE POST failed to find TARGET form element");
1461             
1462         GetServerVariable(lpECB,"REMOTE_ADDR",buf,16);
1463
1464         // Process the post.
1465         string cookie;
1466         RPCError* status=NULL;
1467         ShibMLP markupProcessor;
1468         markupProcessor.insert("requestURL", targeturl.c_str());
1469         try {
1470             status = shire.sessionCreate(elements.first,buf,cookie);
1471         }
1472         catch (ShibTargetException &e) {
1473             markupProcessor.insert("errorType", "Session Creation Service Error");
1474             markupProcessor.insert("errorText", e.what());
1475             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1476             return WriteClientError(lpECB, application, "shire", markupProcessor);
1477         }
1478 #ifndef _DEBUG
1479         catch (...) {
1480             markupProcessor.insert("errorType", "Session Creation Service Error");
1481             markupProcessor.insert("errorText", "Unexpected Exception");
1482             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1483             return WriteClientError(lpECB, application, "shire", markupProcessor);
1484         }
1485 #endif
1486
1487         if (status->isError()) {
1488             if (status->isRetryable()) {
1489                 delete status;
1490                 const char* loc=shire.getAuthnRequest(elements.second);
1491                 if (!httpRedirects.first || httpRedirects.second) {
1492                     string hdrs=string("Location: ") + loc + "\r\n"
1493                         "Content-Type: text/html\r\n"
1494                         "Content-Length: 40\r\n"
1495                         "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1496                         "Cache-Control: private,no-store,no-cache\r\n\r\n";
1497                     lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)hdrs.c_str());
1498                     static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1499                     DWORD resplen=40;
1500                     lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1501                     return HSE_STATUS_SUCCESS;
1502                 }
1503                 else {
1504                     markupProcessor.insert("requestURL",loc);
1505                     return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor);
1506                 }
1507             }
1508     
1509             // Return this error to the user.
1510             markupProcessor.insert(*status);
1511             delete status;
1512             return WriteClientError(lpECB,application,"shire",markupProcessor);
1513         }
1514         delete status;
1515     
1516         // We've got a good session, set the cookie and redirect to target.
1517         cookie = string("Set-Cookie: ") + shib_cookie.first + '=' + cookie + shib_cookie.second + "\r\n"
1518             "Expires: 01-Jan-1997 12:00:00 GMT\r\n"
1519             "Cache-Control: private,no-store,no-cache\r\n";
1520         if (!httpRedirects.first || httpRedirects.second) {
1521             cookie=cookie + "Content-Type: text/html\r\nLocation: " + elements.second + "\r\nContent-Length: 40\r\n\r\n";
1522             lpECB->ServerSupportFunction(lpECB->ConnID,HSE_REQ_SEND_RESPONSE_HEADER,"302 Moved",0,(LPDWORD)cookie.c_str());
1523             static const char* redmsg="<HTML><BODY>Redirecting...</BODY></HTML>";
1524             DWORD resplen=40;
1525             lpECB->WriteClient(lpECB->ConnID,(LPVOID)redmsg,&resplen,HSE_IO_SYNC);
1526             return HSE_STATUS_SUCCESS;
1527         }
1528         else {
1529             markupProcessor.insert("requestURL",elements.second);
1530             return WriteRedirectPage(lpECB, application, redirectPage.second, markupProcessor, cookie.c_str());
1531         }
1532     }
1533     catch (ShibTargetException &e) {
1534         if (application) {
1535             ShibMLP markupProcessor;
1536             markupProcessor.insert("requestURL", targeturl.c_str());
1537             markupProcessor.insert("errorType", "Session Creation Service Error");
1538             markupProcessor.insert("errorText", e.what());
1539             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1540             return WriteClientError(lpECB,application,"shire",markupProcessor);
1541         }
1542     }
1543 #ifndef _DEBUG
1544     catch (...) {
1545         if (application) {
1546             ShibMLP markupProcessor;
1547             markupProcessor.insert("requestURL", targeturl.c_str());
1548             markupProcessor.insert("errorType", "Session Creation Service Error");
1549             markupProcessor.insert("errorText", "Unexpected Exception");
1550             markupProcessor.insert("errorDesc", "An error occurred while processing your request.");
1551             return WriteClientError(lpECB,application,"shire",markupProcessor);
1552         }
1553     }
1554 #endif
1555
1556     return HSE_STATUS_ERROR;
1557 }
1558 #endif // 0