Patch for Forte compiler
[shibboleth/sp.git] / shib-target / XMLRequestMapper.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 /* XMLRequestMapper.cpp - an XML-based map of URLs to application names and settings
51
52    Scott Cantor
53    1/6/04
54
55    $History:$
56 */
57
58 #include "internal.h"
59
60 #include <log4cpp/Category.hh>
61
62 using namespace std;
63 using namespace log4cpp;
64 using namespace saml;
65 using namespace shibboleth;
66 using namespace shibtarget;
67
68 namespace shibtarget {
69
70     class Override : public XMLPropertySet, public DOMNodeFilter
71     {
72     public:
73         Override() : m_base(NULL), m_acl(NULL) {}
74         Override(const DOMElement* e, Category& log, const Override* base=NULL);
75         ~Override();
76         IAccessControl* m_acl;
77
78         // IPropertySet
79         pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
80         pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
81         pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
82         pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;
83         pair<bool,int> getInt(const char* name, const char* ns=NULL) const;
84         const IPropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:target:config:1.0") const;
85         
86         // Provides filter to exclude special config elements.
87         short acceptNode(const DOMNode* node) const;
88
89         const Override* locate(const char* path) const;
90         
91     protected:
92         void loadACL(const DOMElement* e, Category& log);
93         
94         map<string,Override*> m_map;
95     
96     private:
97         const Override* m_base;
98     };
99
100     class XMLRequestMapperImpl : public ReloadableXMLFileImpl, public Override
101     {
102     public:
103         XMLRequestMapperImpl(const char* pathname) : ReloadableXMLFileImpl(pathname) { init(); }
104         XMLRequestMapperImpl(const DOMElement* e) : ReloadableXMLFileImpl(e) { init(); }
105         void init();
106         ~XMLRequestMapperImpl() {}
107     
108         const Override* findOverride(const char* vhost, const char* path) const;
109         Category* log;
110
111     private:    
112         map<string,Override*> m_extras;
113     };
114
115     // An implementation of the URL->application mapping API using an XML file
116     class XMLRequestMapper : public IRequestMapper, public ReloadableXMLFile
117     {
118     public:
119         XMLRequestMapper(const DOMElement* e) : ReloadableXMLFile(e) {}
120         ~XMLRequestMapper() {}
121
122         virtual Settings getSettingsFromURL(const char* url) const;
123         virtual Settings getSettingsFromParsedURL(
124             const char* scheme, const char* hostname, unsigned int port, const char* path=NULL
125             ) const;
126
127     protected:
128         virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
129         virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
130     };
131 }
132
133 IPlugIn* XMLRequestMapFactory(const DOMElement* e)
134 {
135     XMLRequestMapper* m=new XMLRequestMapper(e);
136     try {
137         m->getImplementation();
138     }
139     catch (...) {
140         delete m;
141         throw;
142     }
143     return m;
144 }
145
146 short Override::acceptNode(const DOMNode* node) const
147 {
148     if (XMLString::compareString(node->getNamespaceURI(),ShibTargetConfig::SHIBTARGET_NS))
149         return FILTER_ACCEPT;
150     const XMLCh* name=node->getLocalName();
151     if (XMLString::compareString(name,SHIBT_L(AccessControlProvider)) ||
152         XMLString::compareString(name,SHIBT_L(Host)) ||
153         XMLString::compareString(name,SHIBT_L(Path)))
154         return FILTER_REJECT;
155
156     return FILTER_ACCEPT;
157 }
158
159 void Override::loadACL(const DOMElement* e, Category& log)
160 {
161     IPlugIn* plugin=NULL;
162     const DOMElement* acl=saml::XML::getFirstChildElement(e,ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(htaccess));
163     if (acl) {
164         log.info("building htaccess provider...");
165         plugin=ShibConfig::getConfig().m_plugMgr.newPlugin(shibtarget::XML::htaccessType,acl);
166     }
167     else {
168         acl=saml::XML::getFirstChildElement(e,ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(AccessControlProvider));
169         if (acl) {
170             auto_ptr_char type(acl->getAttributeNS(NULL,SHIBT_L(type)));
171             log.info("building Access Control provider of type %s...",type.get());
172             plugin=ShibConfig::getConfig().m_plugMgr.newPlugin(type.get(),acl);
173         }
174     }
175     if (plugin) {
176         IAccessControl* acl=dynamic_cast<IAccessControl*>(plugin);
177         if (acl)
178             m_acl=acl;
179         else {
180             delete plugin;
181             log.fatal("plugin was not an Access Control provider");
182             throw UnsupportedExtensionException("plugin was not an Access Control provider");
183         }
184     }
185 }
186
187 Override::Override(const DOMElement* e, Category& log, const Override* base) : m_base(base), m_acl(NULL)
188 {
189     try {
190         // Load the property set.
191         load(e,log,this);
192         
193         // Load any AccessControl provider.
194         loadACL(e,log);
195     
196         // Handle nested Paths.
197         DOMNodeList* nlist=e->getElementsByTagNameNS(ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(Path));
198         for (int i=0; nlist && i<nlist->getLength(); i++) {
199             DOMElement* path=static_cast<DOMElement*>(nlist->item(i));
200             const XMLCh* n=path->getAttributeNS(NULL,SHIBT_L(name));
201             if (!n || !*n) {
202                 log.warn("skipping Path element (%d) with empty name attribute",i);
203                 continue;
204             }
205             Override* o=new Override(path,log,this);
206             pair<bool,const char*> name=o->getString("name");
207             char* dup=strdup(name.second);
208             for (char* pch=dup; *pch; pch++)
209                 *pch=tolower(*pch);
210             if (m_map.count(dup)) {
211                 log.warn("Skipping duplicate Path element (%s)",dup);
212                 free(dup);
213                 delete o;
214                 continue;
215             }
216             m_map[dup]=o;
217             free(dup);
218         }
219     }
220     catch (...) {
221         this->~Override();
222         throw;
223     }
224 }
225
226 Override::~Override()
227 {
228     delete m_acl;
229     for (map<string,Override*>::iterator i=m_map.begin(); i!=m_map.end(); i++)
230         delete i->second;
231 }
232
233 pair<bool,bool> Override::getBool(const char* name, const char* ns) const
234 {
235     pair<bool,bool> ret=XMLPropertySet::getBool(name,ns);
236     if (ret.first)
237         return ret;
238     return m_base ? m_base->getBool(name,ns) : ret;
239 }
240
241 pair<bool,const char*> Override::getString(const char* name, const char* ns) const
242 {
243     pair<bool,const char*> ret=XMLPropertySet::getString(name,ns);
244     if (ret.first)
245         return ret;
246     return m_base ? m_base->getString(name,ns) : ret;
247 }
248
249 pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const
250 {
251     pair<bool,const XMLCh*> ret=XMLPropertySet::getXMLString(name,ns);
252     if (ret.first)
253         return ret;
254     return m_base ? m_base->getXMLString(name,ns) : ret;
255 }
256
257 pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const
258 {
259     pair<bool,unsigned int> ret=XMLPropertySet::getUnsignedInt(name,ns);
260     if (ret.first)
261         return ret;
262     return m_base ? m_base->getUnsignedInt(name,ns) : ret;
263 }
264
265 pair<bool,int> Override::getInt(const char* name, const char* ns) const
266 {
267     pair<bool,int> ret=XMLPropertySet::getInt(name,ns);
268     if (ret.first)
269         return ret;
270     return m_base ? m_base->getInt(name,ns) : ret;
271 }
272
273 const IPropertySet* Override::getPropertySet(const char* name, const char* ns) const
274 {
275     const IPropertySet* ret=XMLPropertySet::getPropertySet(name,ns);
276     if (ret || !m_base)
277         return ret;
278     return m_base->getPropertySet(name,ns);
279 }
280
281 const Override* Override::locate(const char* path) const
282 {
283     char* dup=strdup(path);
284     char* sep=strchr(dup,'?');
285     if (sep)
286         *sep=0;
287     for (char* pch=dup; *pch; pch++)
288         *pch=tolower(*pch);
289         
290     const Override* o=this;
291     
292 #ifdef HAVE_STRTOK_R
293     char* pos=NULL;
294     const char* token=strtok_r(dup,"/",&pos);
295 #else
296     const char* token=strtok(dup,"/");
297 #endif
298     while (token)
299     {
300         map<string,Override*>::const_iterator i=o->m_map.find(token);
301         if (i==o->m_map.end())
302             break;
303         o=i->second;
304 #ifdef HAVE_STRTOK_R
305         token=strtok_r(NULL,"/",&pos);
306 #else
307         token=strtok(NULL,"/");
308 #endif
309     }
310
311     free(dup);
312     return o;
313 }
314
315 void XMLRequestMapperImpl::init()
316 {
317     NDC ndc("init");
318     log=&Category::getInstance("shibtarget.XMLRequestMapper");
319
320     try {
321         if (!saml::XML::isElementNamed(ReloadableXMLFileImpl::m_root,ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(RequestMap))) {
322             log->error("Construction requires a valid request mapping file: (conf:RequestMap as root element)");
323             throw MalformedException("Construction requires a valid request mapping file: (conf:RequestMap as root element)");
324         }
325
326         // Load the property set.
327         load(ReloadableXMLFileImpl::m_root,*log,this);
328         
329         // Load any AccessControl provider.
330         loadACL(ReloadableXMLFileImpl::m_root,*log);
331     
332         // Loop over the Host elements.
333         DOMNodeList* nlist = ReloadableXMLFileImpl::m_root->getElementsByTagNameNS(ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(Host));
334         for (int i=0; nlist && i<nlist->getLength(); i++) {
335             DOMElement* host=static_cast<DOMElement*>(nlist->item(i));
336             const XMLCh* n=host->getAttributeNS(NULL,SHIBT_L(name));
337             if (!n || !*n) {
338                 log->warn("Skipping Host element (%d) with empty name attribute",i);
339                 continue;
340             }
341             
342             Override* o=new Override(host,*log,this);
343             pair<bool,const char*> name=o->getString("name");
344             pair<bool,const char*> scheme=o->getString("scheme");
345             pair<bool,const char*> port=o->getString("port");
346             
347             char* dup=strdup(name.second);
348             for (char* pch=dup; *pch; pch++)
349                 *pch=tolower(*pch);
350
351             string url(scheme.first ? scheme.second : "http");
352             url=url + "://" + dup;
353             free(dup);
354             if (!port.first) {
355                 // First store a port-less version.
356                 if (m_map.count(url)) {
357                     log->warn("Skipping duplicate Host element (%s)",url.c_str());
358                     delete o;
359                     continue;
360                 }
361                 m_map[url]=o;
362                 
363                 // Now append the default port.
364                 // XXX Use getservbyname instead?
365                 if (!scheme.first || !strcmp(scheme.second,"http"))
366                     url=url + ":80";
367                 else if (!strcmp(scheme.second,"https"))
368                     url=url + ":443";
369                 else if (!strcmp(scheme.second,"ftp"))
370                     url=url + ":21";
371                 else if (!strcmp(scheme.second,"ldap"))
372                     url=url + ":389";
373                 else if (!strcmp(scheme.second,"ldaps"))
374                     url=url + ":636";
375                 
376                 m_extras[url]=o;
377             }
378             else {
379                 url=url + ':' + port.second;
380                 if (m_map.count(url)) {
381                     log->warn("Skipping duplicate Host element (%s)",url.c_str());
382                     delete o;
383                     continue;
384                 }
385                 m_map[url]=o;
386             }
387             log->debug("Added <Host> mapping for %s",url.c_str());
388         }
389     }
390     catch (SAMLException& e) {
391         log->errorStream() << "Error while parsing request mapping configuration: " << e.what() << CategoryStream::ENDLINE;
392         throw;
393     }
394 #ifndef _DEBUG
395     catch (...)
396     {
397         log->error("Unexpected error while parsing request mapping configuration");
398         throw;
399     }
400 #endif
401 }
402
403 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char* path) const
404 {
405     const Override* o=NULL;
406     map<string,Override*>::const_iterator i=m_map.find(vhost);
407     if (i!=m_map.end())
408         o=i->second;
409     else {
410         i=m_extras.find(vhost);
411         if (i!=m_extras.end())
412             o=i->second;
413     }
414     
415     return o ? o->locate(path) : this;
416 }
417
418 const char* split_url(const char* url, string& vhost)
419 {
420     const char* path=NULL;
421     const char* slash=strchr(url,'/');
422     if (slash)
423     {
424         slash=strchr(slash,'/');
425         if (slash)
426         {
427             path=strchr(slash,'/');
428             if (path)
429                 vhost.append(url,path-url);
430             else
431                 vhost=url;
432         }
433     }
434     return path;
435 }
436
437 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const char* pathname, bool first) const
438 {
439     return new XMLRequestMapperImpl(pathname);
440 }
441
442 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const DOMElement* e, bool first) const
443 {
444     return new XMLRequestMapperImpl(e);
445 }
446
447 IRequestMapper::Settings XMLRequestMapper::getSettingsFromURL(const char* url) const
448 {
449     string vhost;
450     const char* path=split_url(url,vhost);
451
452     XMLRequestMapperImpl* impl=static_cast<XMLRequestMapperImpl*>(getImplementation());
453     const Override* o=impl->findOverride(vhost.c_str(), path);
454
455     if (impl->log->isDebugEnabled()) {
456         saml::NDC ndc("getApplicationFromURL");
457         pair<bool,const char*> ret=o->getString("applicationId");
458         impl->log->debug("mapped %s to %s", url, ret.second);
459     }
460
461     return Settings(o,o->m_acl);
462 }
463
464 IRequestMapper::Settings XMLRequestMapper::getSettingsFromParsedURL(
465     const char* scheme, const char* hostname, unsigned int port, const char* path
466     ) const
467 {
468     char buf[21];
469     string vhost(scheme);
470     vhost=vhost + "://" + hostname + ':';
471 #ifdef WIN32
472     _snprintf(buf,20,"%u",port);
473 #else
474     snprintf(buf,20,"%u",port);
475 #endif
476     vhost+=buf;
477
478     XMLRequestMapperImpl* impl=static_cast<XMLRequestMapperImpl*>(getImplementation());
479     const Override* o=impl->findOverride(vhost.c_str(), path);
480
481     if (impl->log->isDebugEnabled())
482     {
483         saml::NDC ndc("getApplicationFromParsedURL");
484         pair<bool,const char*> ret=o->getString("applicationId");
485         impl->log->debug("mapped %s%s to %s", vhost.c_str(), path ? path : "", ret.second);
486     }
487
488     return Settings(o,o->m_acl);
489 }