Updated to xsec 1.2, removed dead code.
[shibboleth/sp.git] / nsapi_shib / nsapi_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 /* nsapi_shib.cpp - Shibboleth NSAPI filter
51
52    Scott Cantor
53    12/13/04
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 <ctime>
65 #include <fstream>
66 #include <sstream>
67 #include <stdexcept>
68
69 #ifdef WIN32
70 # include <process.h>
71 # define XP_WIN32
72 #else
73 # define XP_UNIX
74 #endif
75
76 #define MCC_HTTPD
77 #define NET_SSL
78
79 extern "C"
80 {
81 #include <nsapi.h>
82 }
83
84 using namespace std;
85 using namespace saml;
86 using namespace shibboleth;
87 using namespace shibtarget;
88
89 // macros to output text to client
90 #define NET_WRITE(str) \
91     if (IO_ERROR==net_write(sn->csd,str,strlen(str))) return REQ_EXIT
92
93 namespace {
94     ShibTargetConfig* g_Config=NULL;
95     string g_ServerName;
96     string g_ServerScheme;
97 }
98
99 PlugManager::Factory SunRequestMapFactory;
100
101 extern "C" NSAPI_PUBLIC void nsapi_shib_exit(void*)
102 {
103     if (g_Config)
104         g_Config->shutdown();
105     g_Config = NULL;
106 }
107
108 extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq)
109 {
110     // Save off a default hostname for this virtual server.
111     char* name=pblock_findval("server-name",pb);
112     if (name)
113         g_ServerName=name;
114     else {
115         name=server_hostname;
116         if (name)
117             g_ServerName=name;
118         else {
119             name=util_hostname();
120             if (name) {
121                 g_ServerName=name;
122                 FREE(name);
123             }
124             else {
125                 pblock_nvinsert("error","unable to determine web server hostname",pb);
126                 return REQ_ABORTED;
127             }
128         }
129     }
130     name=pblock_findval("server-scheme",pb);
131     if (name)
132         g_ServerScheme=name;
133
134     log_error(LOG_INFORM,"nsapi_shib_init",sn,rq,"nsapi_shib loaded for host (%s)",g_ServerName.c_str());
135
136 #ifndef _DEBUG
137     try {
138 #endif
139         const char* schemadir=pblock_findval("shib-schemas",pb);
140         if (!schemadir)
141             schemadir=getenv("SHIBSCHEMAS");
142         if (!schemadir)
143             schemadir=SHIB_SCHEMAS;
144         const char* config=pblock_findval("shib-config",pb);
145         if (!config)
146             config=getenv("SHIBCONFIG");
147         if (!config)
148             config=SHIB_CONFIG;
149         g_Config=&ShibTargetConfig::getConfig();
150         g_Config->setFeatures(
151             ShibTargetConfig::Listener |
152             ShibTargetConfig::Metadata |
153             ShibTargetConfig::AAP |
154             ShibTargetConfig::RequestMapper |
155             ShibTargetConfig::LocalExtensions |
156             ShibTargetConfig::Logging
157             );
158         if (!g_Config->init(schemadir)) {
159             g_Config=NULL;
160             pblock_nvinsert("error","unable to initialize Shibboleth libraries",pb);
161             return REQ_ABORTED;
162         }
163
164         SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::NativeRequestMapType,&SunRequestMapFactory);
165         // We hijack the legacy type so that 1.2 config files will load this plugin
166         SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::LegacyRequestMapType,&SunRequestMapFactory);
167
168         if (!g_Config->load(config)) {
169             g_Config=NULL;
170             pblock_nvinsert("error","unable to initialize load Shibboleth configuration",pb);
171             return REQ_ABORTED;
172         }
173
174         daemon_atrestart(nsapi_shib_exit,NULL);
175 #ifndef _DEBUG
176     }
177     catch (...) {
178         g_Config=NULL;
179         pblock_nvinsert("error","caught exception, unable to initialize Shibboleth libraries",pb);
180         return REQ_ABORTED;
181     }
182 #endif
183     return REQ_PROCEED;
184 }
185
186 /********************************************************************************/
187 // NSAPI Shib Target Subclass
188
189 class ShibTargetNSAPI : public ShibTarget
190 {
191 public:
192   ShibTargetNSAPI(pblock* pb, Session* sn, Request* rq) {
193     m_pb = pb;
194     m_sn = sn;
195     m_rq = rq;
196
197     // Get everything but hostname...
198     const char* uri=pblock_findval("uri", rq->reqpb);
199     const char* qstr=pblock_findval("query", rq->reqpb);
200     int port=server_portnum;
201     const char* scheme=security_active ? "https" : "http";
202     const char* host=NULL;
203
204     string url;
205     if (uri)
206         url=uri;
207     if (qstr)
208         url=url + '?' + qstr;
209     
210 #ifdef vs_is_default_vs
211     // This is 6.0 or later, so we can distinguish requests to name-based vhosts.
212     if (!vs_is_default_vs)
213         // The beauty here is, a non-default vhost can *only* be accessed if the client
214         // specified the exact name in the Host header. So we can trust the Host header.
215         host=pblock_findval("host", rq->headers);
216     else
217 #endif
218     // In other cases, we're going to rely on the initialization process...
219     host=g_ServerName.c_str();
220
221     char* content_type = "";
222     request_header("content-type", &content_type, sn, rq);
223       
224     const char *remote_ip = pblock_findval("ip", sn->client);
225     const char *method = pblock_findval("method", rq->reqpb);
226
227     init(scheme, host, port, url.c_str(), content_type, remote_ip, method);
228   }
229   ~ShibTargetNSAPI() {}
230
231   virtual void log(ShibLogLevel level, const string &msg) {
232     ShibTarget::log(level,msg);
233     if (level==LogLevelError)
234         log_error(LOG_FAILURE, "nsapi_shib", m_sn, m_rq, const_cast<char*>(msg.c_str()));
235   }
236   virtual string getCookies(void) const {
237     char *cookies = NULL;
238     if (request_header("cookie", &cookies, m_sn, m_rq) == REQ_ABORTED)
239       throw("error accessing cookie header");
240     return string(cookies ? cookies : "");
241   }
242   virtual void setCookie(const string &name, const string &value) {
243     string cookie = name + '=' + value;
244     pblock_nvinsert("Set-Cookie", cookie.c_str(), m_rq->srvhdrs);
245   }
246   virtual string getArgs(void) { 
247     const char *q = pblock_findval("query", m_rq->reqpb);
248     return string(q ? q : "");
249   }
250   virtual string getPostData(void) {
251     char* content_length=NULL;
252     if (request_header("content-length", &content_length, m_sn, m_rq)!=REQ_PROCEED ||
253          atoi(content_length) > 1024*1024) // 1MB?
254       throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
255     else {
256       char ch=IO_EOF+1;
257       int cl=atoi(content_length);
258       string cgistr;
259       while (cl && ch != IO_EOF) {
260         ch=netbuf_getc(m_sn->inbuf);
261       
262         // Check for error.
263         if(ch==IO_ERROR)
264           break;
265         cgistr += ch;
266         cl--;
267       }
268       if (cl)
269         throw FatalProfileException("Error reading profile submission from browser.");
270       return cgistr;
271     }
272   }
273   virtual void clearHeader(const string &name) {
274     param_free(pblock_remove(name.c_str(), m_rq->headers));
275   }
276   virtual void setHeader(const string &name, const string &value) {
277     pblock_nvinsert(name.c_str(), value.c_str() ,m_rq->headers);
278   }
279   virtual string getHeader(const string &name) {
280     char *hdr = NULL;
281     if (request_header(const_cast<char*>(name.c_str()), &hdr, m_sn, m_rq) != REQ_PROCEED)
282       hdr = NULL;
283     return string(hdr ? hdr : "");
284   }
285   virtual void setRemoteUser(const string &user) {
286     pblock_nvinsert("remote-user", user.c_str(), m_rq->headers);
287     pblock_nvinsert("auth-user", user.c_str(), m_rq->vars);
288   }
289   virtual string getRemoteUser(void) {
290     return getHeader("remote-user");
291   }
292
293   virtual void* sendPage(
294     const string& msg,
295     int code=200,
296     const string& content_type="text/html",
297     const saml::Iterator<header_t>& headers=EMPTY(header_t)
298     ) {
299     param_free(pblock_remove("content-type", m_rq->srvhdrs));
300     pblock_nvinsert("content-type", content_type.c_str(), m_rq->srvhdrs);
301     pblock_nninsert("content-length", msg.length(), m_rq->srvhdrs);
302     pblock_nvinsert("connection","close",m_rq->srvhdrs);
303     while (headers.hasNext()) {
304         const header_t& h=headers.next();
305         pblock_nvinsert(h.first.c_str(), h.second.c_str(), m_rq->srvhdrs);
306     }
307     protocol_status(m_sn, m_rq, code, NULL);
308     protocol_start_response(m_sn, m_rq);
309     net_write(m_sn->csd,const_cast<char*>(msg.c_str()),msg.length());
310     return (void*)REQ_EXIT;
311   }
312   virtual void* sendRedirect(const string& url) {
313     param_free(pblock_remove("content-type", m_rq->srvhdrs));
314     pblock_nvinsert("content-type", "text/html", m_rq->srvhdrs);
315     pblock_nninsert("content-length", 40, m_rq->srvhdrs);
316     pblock_nvinsert("expires", "01-Jan-1997 12:00:00 GMT", m_rq->srvhdrs);
317     pblock_nvinsert("cache-control", "private,no-store,no-cache", m_rq->srvhdrs);
318     pblock_nvinsert("location", url.c_str(), m_rq->srvhdrs);
319     protocol_status(m_sn, m_rq, PROTOCOL_REDIRECT, "302 Please wait");
320     protocol_start_response(m_sn, m_rq);
321     char* msg="<HTML><BODY>Redirecting...</BODY></HTML>";
322     net_write(m_sn->csd,msg,strlen(msg));
323     return (void*)REQ_EXIT;
324   }
325   virtual void* returnDecline(void) { return (void*)REQ_NOACTION; }
326   virtual void* returnOK(void) { return (void*)REQ_PROCEED; }
327
328   pblock* m_pb;
329   Session* m_sn;
330   Request* m_rq;
331 };
332
333 /********************************************************************************/
334
335 int WriteClientError(Session* sn, Request* rq, char* func, char* msg)
336 {
337     log_error(LOG_FAILURE,func,sn,rq,msg);
338     protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,msg);
339     return REQ_ABORTED;
340 }
341
342 #undef FUNC
343 #define FUNC "shibboleth"
344 extern "C" NSAPI_PUBLIC int nsapi_shib(pblock* pb, Session* sn, Request* rq)
345 {
346   ostringstream threadid;
347   threadid << "[" << getpid() << "] nsapi_shib" << '\0';
348   saml::NDC ndc(threadid.str().c_str());
349
350 #ifndef _DEBUG
351   try {
352 #endif
353     ShibTargetNSAPI stn(pb, sn, rq);
354
355     // Check user authentication
356     pair<bool,void*> res = stn.doCheckAuthN();
357     if (res.first) return (int)res.second;
358
359     // user authN was okay -- export the assertions now
360     param_free(pblock_remove("auth-user",rq->vars));
361     // This seems to be required in order to eventually set
362     // the auth-user var.
363     pblock_nvinsert("auth-type","shibboleth",rq->vars);
364     res = stn.doExportAssertions();
365     if (res.first) return (int)res.second;
366
367     // Check the Authorization
368     res = stn.doCheckAuthZ();
369     if (res.first) return (int)res.second;
370
371     // this user is ok.
372     return REQ_PROCEED;
373
374 #ifndef _DEBUG
375   }
376   catch (...) {
377     return WriteClientError(sn, rq, FUNC, "threw an uncaught exception.");
378   }
379 #endif
380 }
381
382
383 #undef FUNC
384 #define FUNC "shib_handler"
385 extern "C" NSAPI_PUBLIC int shib_handler(pblock* pb, Session* sn, Request* rq)
386 {
387   ostringstream threadid;
388   threadid << "[" << getpid() << "] shib_handler" << '\0';
389   saml::NDC ndc(threadid.str().c_str());
390
391 #ifndef _DEBUG
392   try {
393 #endif
394     ShibTargetNSAPI stn(pb, sn, rq);
395
396     pair<bool,void*> res = stn.doHandler();
397     if (res.first) return (int)res.second;
398
399     return WriteClientError(sn, rq, FUNC, "Shibboleth handler did not do anything.");
400
401 #ifndef _DEBUG
402   }
403   catch (...) {
404     return WriteClientError(sn, rq, FUNC, "Filter threw an unknown exception.");
405   }
406 #endif
407 }
408
409
410 class SunRequestMapper : public virtual IRequestMapper, public virtual IPropertySet
411 {
412 public:
413     SunRequestMapper(const DOMElement* e);
414     ~SunRequestMapper() { delete m_mapper; delete m_stKey; delete m_propsKey; }
415     void lock() { m_mapper->lock(); }
416     void unlock() { m_stKey->setData(NULL); m_propsKey->setData(NULL); m_mapper->unlock(); }
417     Settings getSettings(ShibTarget* st) const;
418     
419     pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
420     pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
421     pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
422     pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;
423     pair<bool,int> getInt(const char* name, const char* ns=NULL) const;
424     const IPropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:target:config:1.0") const;
425     const DOMElement* getElement() const;
426
427 private:
428     IRequestMapper* m_mapper;
429     ThreadKey* m_stKey;
430     ThreadKey* m_propsKey;
431 };
432
433 IPlugIn* SunRequestMapFactory(const DOMElement* e)
434 {
435     return new SunRequestMapper(e);
436 }
437
438 SunRequestMapper::SunRequestMapper(const DOMElement* e) : m_mapper(NULL), m_stKey(NULL), m_propsKey(NULL)
439 {
440     IPlugIn* p=SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::XMLRequestMapType,e);
441     m_mapper=dynamic_cast<IRequestMapper*>(p);
442     if (!m_mapper) {
443         delete p;
444         throw UnsupportedExtensionException("Embedded request mapper plugin was not of correct type.");
445     }
446     m_stKey=ThreadKey::create(NULL);
447     m_propsKey=ThreadKey::create(NULL);
448 }
449
450 IRequestMapper::Settings SunRequestMapper::getSettings(ShibTarget* st) const
451 {
452     Settings s=m_mapper->getSettings(st);
453     m_stKey->setData(dynamic_cast<ShibTargetNSAPI*>(st));
454     m_propsKey->setData((void*)s.first);
455     return pair<const IPropertySet*,IAccessControl*>(this,s.second);
456 }
457
458 pair<bool,bool> SunRequestMapper::getBool(const char* name, const char* ns) const
459 {
460     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
461     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
462     if (stn && !ns && name) {
463         // Override boolean properties.
464         const char* param=pblock_findval(name,stn->m_pb);
465         if (param && (!strcmp(param,"1") || !strcasecmp(param,"true")))
466             return make_pair(true,true);
467     }
468     return s ? s->getBool(name,ns) : make_pair(false,false);
469 }
470
471 pair<bool,const char*> SunRequestMapper::getString(const char* name, const char* ns) const
472 {
473     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
474     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
475     if (stn && !ns && name) {
476         // Override string properties.
477         if (!strcmp(name,"authType"))
478             return pair<bool,const char*>(true,"shibboleth");
479         else {
480             const char* param=pblock_findval(name,stn->m_pb);
481             if (param)
482                 return make_pair(true,param);
483         }
484     }
485     return s ? s->getString(name,ns) : pair<bool,const char*>(false,NULL);
486 }
487
488 pair<bool,const XMLCh*> SunRequestMapper::getXMLString(const char* name, const char* ns) const
489 {
490     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
491     return s ? s->getXMLString(name,ns) : pair<bool,const XMLCh*>(false,NULL);
492 }
493
494 pair<bool,unsigned int> SunRequestMapper::getUnsignedInt(const char* name, const char* ns) const
495 {
496     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
497     return s ? s->getUnsignedInt(name,ns) : pair<bool,unsigned int>(false,0);
498 }
499
500 pair<bool,int> SunRequestMapper::getInt(const char* name, const char* ns) const
501 {
502     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
503     return s ? s->getInt(name,ns) : pair<bool,int>(false,0);
504 }
505
506 const IPropertySet* SunRequestMapper::getPropertySet(const char* name, const char* ns) const
507 {
508     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
509     return s ? s->getPropertySet(name,ns) : NULL;
510 }
511
512 const DOMElement* SunRequestMapper::getElement() const
513 {
514     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
515     return s ? s->getElement() : NULL;
516 }