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