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