2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
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.
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
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.
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.
50 /* XMLRequestMapper.cpp - an XML-based map of URLs to application names and settings
60 #include <log4cpp/Category.hh>
63 using namespace log4cpp;
65 using namespace shibboleth;
66 using namespace shibtarget;
68 namespace shibtarget {
70 class Override : public XMLPropertySet, public DOMNodeFilter
73 Override() : m_base(NULL), m_acl(NULL) {}
74 Override(const DOMElement* e, Category& log, const Override* base=NULL);
76 IAccessControl* m_acl;
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;
86 // Provides filter to exclude special config elements.
87 short acceptNode(const DOMNode* node) const;
89 const Override* locate(const char* path) const;
92 void loadACL(const DOMElement* e, Category& log);
94 map<string,Override*> m_map;
97 const Override* m_base;
100 class XMLRequestMapperImpl : public ReloadableXMLFileImpl, public Override
103 XMLRequestMapperImpl(const char* pathname) : ReloadableXMLFileImpl(pathname) { init(); }
104 XMLRequestMapperImpl(const DOMElement* e) : ReloadableXMLFileImpl(e) { init(); }
106 ~XMLRequestMapperImpl() {}
108 const Override* findOverride(const char* vhost, const char* path) const;
112 map<string,Override*> m_extras;
115 // An implementation of the URL->application mapping API using an XML file
116 class XMLRequestMapper : public IRequestMapper, public ReloadableXMLFile
119 XMLRequestMapper(const DOMElement* e) : ReloadableXMLFile(e) {}
120 ~XMLRequestMapper() {}
122 virtual Settings getSettingsFromURL(const char* url, ShibTarget* st) const;
123 virtual Settings getSettingsFromParsedURL(
124 const char* scheme, const char* hostname, unsigned int port, const char* path, ShibTarget* st
128 virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
129 virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
133 IPlugIn* XMLRequestMapFactory(const DOMElement* e)
135 auto_ptr<XMLRequestMapper> m(new XMLRequestMapper(e));
136 m->getImplementation();
140 short Override::acceptNode(const DOMNode* node) const
142 if (XMLString::compareString(node->getNamespaceURI(),ShibTargetConfig::SHIBTARGET_NS))
143 return FILTER_ACCEPT;
144 const XMLCh* name=node->getLocalName();
145 if (XMLString::compareString(name,SHIBT_L(AccessControlProvider)) ||
146 XMLString::compareString(name,SHIBT_L(Host)) ||
147 XMLString::compareString(name,SHIBT_L(Path)))
148 return FILTER_REJECT;
150 return FILTER_ACCEPT;
153 void Override::loadACL(const DOMElement* e, Category& log)
155 IPlugIn* plugin=NULL;
156 const DOMElement* acl=saml::XML::getFirstChildElement(e,ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(htaccess));
158 log.info("building htaccess provider...");
159 plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::htaccessType,acl);
162 acl=saml::XML::getFirstChildElement(e,ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(AccessControlProvider));
164 auto_ptr_char type(acl->getAttributeNS(NULL,SHIBT_L(type)));
165 log.info("building Access Control provider of type %s...",type.get());
166 plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(type.get(),acl);
170 IAccessControl* acl=dynamic_cast<IAccessControl*>(plugin);
175 log.fatal("plugin was not an Access Control provider");
176 throw UnsupportedExtensionException("plugin was not an Access Control provider");
181 Override::Override(const DOMElement* e, Category& log, const Override* base) : m_base(base), m_acl(NULL)
184 // Load the property set.
187 // Load any AccessControl provider.
190 // Handle nested Paths.
191 DOMNodeList* nlist=e->getElementsByTagNameNS(ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(Path));
192 for (int i=0; nlist && i<nlist->getLength(); i++) {
193 DOMElement* path=static_cast<DOMElement*>(nlist->item(i));
194 const XMLCh* n=path->getAttributeNS(NULL,SHIBT_L(name));
196 log.warn("skipping Path element (%d) with empty name attribute",i);
199 else if (*n==chForwardSlash && !n[1]) {
200 log.warn("skipping Path element (%d) with a lone slash in the name attribute",i);
203 Override* o=new Override(path,log,this);
204 pair<bool,const char*> name=o->getString("name");
205 char* dup=strdup(name.second);
206 for (char* pch=dup; *pch; pch++)
208 if (m_map.count(dup)) {
209 log.warn("Skipping duplicate Path element (%s)",dup);
224 Override::~Override()
227 for (map<string,Override*>::iterator i=m_map.begin(); i!=m_map.end(); i++)
231 pair<bool,bool> Override::getBool(const char* name, const char* ns) const
233 pair<bool,bool> ret=XMLPropertySet::getBool(name,ns);
236 return m_base ? m_base->getBool(name,ns) : ret;
239 pair<bool,const char*> Override::getString(const char* name, const char* ns) const
241 pair<bool,const char*> ret=XMLPropertySet::getString(name,ns);
244 return m_base ? m_base->getString(name,ns) : ret;
247 pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const
249 pair<bool,const XMLCh*> ret=XMLPropertySet::getXMLString(name,ns);
252 return m_base ? m_base->getXMLString(name,ns) : ret;
255 pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const
257 pair<bool,unsigned int> ret=XMLPropertySet::getUnsignedInt(name,ns);
260 return m_base ? m_base->getUnsignedInt(name,ns) : ret;
263 pair<bool,int> Override::getInt(const char* name, const char* ns) const
265 pair<bool,int> ret=XMLPropertySet::getInt(name,ns);
268 return m_base ? m_base->getInt(name,ns) : ret;
271 const IPropertySet* Override::getPropertySet(const char* name, const char* ns) const
273 const IPropertySet* ret=XMLPropertySet::getPropertySet(name,ns);
276 return m_base->getPropertySet(name,ns);
279 const Override* Override::locate(const char* path) const
281 char* dup=strdup(path);
282 char* sep=strchr(dup,'?');
285 for (char* pch=dup; *pch; pch++)
288 const Override* o=this;
292 const char* token=strtok_r(dup,"/",&pos);
294 const char* token=strtok(dup,"/");
298 map<string,Override*>::const_iterator i=o->m_map.find(token);
299 if (i==o->m_map.end())
303 token=strtok_r(NULL,"/",&pos);
305 token=strtok(NULL,"/");
313 void XMLRequestMapperImpl::init()
318 log=&Category::getInstance("shibtarget.RequestMapper");
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)");
326 // Load the property set.
327 load(ReloadableXMLFileImpl::m_root,*log,this);
329 // Load any AccessControl provider.
330 loadACL(ReloadableXMLFileImpl::m_root,*log);
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));
338 log->warn("Skipping Host element (%d) with empty name attribute",i);
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");
347 char* dup=strdup(name.second);
348 for (char* pch=dup; *pch; pch++)
350 auto_ptr<char> dupwrap(dup);
352 if (!scheme.first && port.first) {
353 // No scheme, but a port, so assume http.
354 scheme = pair<bool,const char*>(true,"http");
356 else if (scheme.first && !port.first) {
357 // Scheme, no port, so default it.
358 // XXX Use getservbyname instead?
360 if (!strcmp(scheme.second,"http"))
362 else if (!strcmp(scheme.second,"https"))
364 else if (!strcmp(scheme.second,"ftp"))
366 else if (!strcmp(scheme.second,"ldap"))
368 else if (!strcmp(scheme.second,"ldaps"))
373 string url(scheme.second);
374 url=url + "://" + dup;
376 // Is this the default port?
377 if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||
378 (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||
379 (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||
380 (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||
381 (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {
382 // First store a port-less version.
383 if (m_map.count(url) || m_extras.count(url)) {
384 log->warn("Skipping duplicate Host element (%s)",url.c_str());
389 log->debug("Added <Host> mapping for %s",url.c_str());
391 // Now append the port. We use the extras vector, to avoid double freeing the object later.
392 url=url + ':' + port.second;
394 log->debug("Added <Host> mapping for %s",url.c_str());
397 url=url + ':' + port.second;
398 if (m_map.count(url) || m_extras.count(url)) {
399 log->warn("Skipping duplicate Host element (%s)",url.c_str());
404 log->debug("Added <Host> mapping for %s",url.c_str());
408 // No scheme or port, so we enter dual hosts on http:80 and https:443
409 string url("http://");
411 if (m_map.count(url) || m_extras.count(url)) {
412 log->warn("Skipping duplicate Host element (%s)",url.c_str());
417 log->debug("Added <Host> mapping for %s",url.c_str());
420 if (m_map.count(url) || m_extras.count(url)) {
421 log->warn("Skipping duplicate Host element (%s)",url.c_str());
425 log->debug("Added <Host> mapping for %s",url.c_str());
429 if (m_map.count(url) || m_extras.count(url)) {
430 log->warn("Skipping duplicate Host element (%s)",url.c_str());
434 log->debug("Added <Host> mapping for %s",url.c_str());
437 if (m_map.count(url) || m_extras.count(url)) {
438 log->warn("Skipping duplicate Host element (%s)",url.c_str());
442 log->debug("Added <Host> mapping for %s",url.c_str());
446 catch (SAMLException& e) {
447 log->errorStream() << "Error while parsing request mapping configuration: " << e.what() << CategoryStream::ENDLINE;
453 log->error("Unexpected error while parsing request mapping configuration");
459 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char* path) const
461 const Override* o=NULL;
462 map<string,Override*>::const_iterator i=m_map.find(vhost);
466 i=m_extras.find(vhost);
467 if (i!=m_extras.end())
471 return o ? o->locate(path) : this;
474 const char* split_url(const char* url, string& vhost)
476 const char* path=NULL;
477 const char* slash=strchr(url,'/');
480 slash=strchr(slash,'/');
483 path=strchr(slash,'/');
485 vhost.append(url,path-url);
493 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const char* pathname, bool first) const
495 return new XMLRequestMapperImpl(pathname);
498 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const DOMElement* e, bool first) const
500 return new XMLRequestMapperImpl(e);
503 IRequestMapper::Settings XMLRequestMapper::getSettingsFromURL(const char* url, ShibTarget* st) const
506 const char* path=split_url(url,vhost);
508 XMLRequestMapperImpl* impl=static_cast<XMLRequestMapperImpl*>(getImplementation());
509 const Override* o=impl->findOverride(vhost.c_str(), path);
511 if (impl->log->isDebugEnabled()) {
512 saml::NDC ndc("getApplicationFromURL");
513 pair<bool,const char*> ret=o->getString("applicationId");
514 impl->log->debug("mapped %s to %s", url, ret.second);
517 return Settings(o,o->m_acl);
520 IRequestMapper::Settings XMLRequestMapper::getSettingsFromParsedURL(
521 const char* scheme, const char* hostname, unsigned int port, const char* path, ShibTarget* st
525 string vhost(scheme);
526 vhost=vhost + "://" + hostname + ':';
528 _snprintf(buf,20,"%u",port);
530 snprintf(buf,20,"%u",port);
534 XMLRequestMapperImpl* impl=static_cast<XMLRequestMapperImpl*>(getImplementation());
535 const Override* o=impl->findOverride(vhost.c_str(), path);
537 if (impl->log->isDebugEnabled())
539 saml::NDC ndc("getApplicationFromParsedURL");
540 pair<bool,const char*> ret=o->getString("applicationId");
541 impl->log->debug("mapped %s%s to %s", vhost.c_str(), path ? path : "", ret.second);
544 return Settings(o,o->m_acl);