2 * Copyright 2001-2005 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* XMLRequestMapper.cpp - an XML-based map of URLs to application names and settings
28 #include <log4cpp/Category.hh>
31 using namespace log4cpp;
33 using namespace shibboleth;
34 using namespace shibtarget;
36 namespace shibtarget {
38 class Override : public XMLPropertySet, public DOMNodeFilter
41 Override() : m_base(NULL), m_acl(NULL) {}
42 Override(const DOMElement* e, Category& log, const Override* base=NULL);
46 pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
47 pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
48 pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
49 pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;
50 pair<bool,int> getInt(const char* name, const char* ns=NULL) const;
51 const IPropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:target:config:1.0") const;
53 // Provides filter to exclude special config elements.
54 short acceptNode(const DOMNode* node) const;
56 const Override* locate(const char* path) const;
57 IAccessControl* getAC() const { return (m_acl ? m_acl : (m_base ? m_base->getAC() : NULL)); }
60 void loadACL(const DOMElement* e, Category& log);
62 map<string,Override*> m_map;
65 const Override* m_base;
66 IAccessControl* m_acl;
69 class XMLRequestMapperImpl : public ReloadableXMLFileImpl, public Override
72 XMLRequestMapperImpl(const char* pathname) : ReloadableXMLFileImpl(pathname) { init(); }
73 XMLRequestMapperImpl(const DOMElement* e) : ReloadableXMLFileImpl(e) { init(); }
75 ~XMLRequestMapperImpl() {}
77 const Override* findOverride(const char* vhost, const char* path) const;
81 map<string,Override*> m_extras;
84 // An implementation of the URL->application mapping API using an XML file
85 class XMLRequestMapper : public IRequestMapper, public ReloadableXMLFile
88 XMLRequestMapper(const DOMElement* e) : ReloadableXMLFile(e) {}
89 ~XMLRequestMapper() {}
91 virtual Settings getSettings(ShibTarget* st) const;
94 virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
95 virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
99 IPlugIn* XMLRequestMapFactory(const DOMElement* e)
101 auto_ptr<XMLRequestMapper> m(new XMLRequestMapper(e));
102 m->getImplementation();
106 short Override::acceptNode(const DOMNode* node) const
108 if (XMLString::compareString(node->getNamespaceURI(),shibtarget::XML::SHIBTARGET_NS))
109 return FILTER_ACCEPT;
110 const XMLCh* name=node->getLocalName();
111 if (!XMLString::compareString(name,SHIBT_L(Host)) ||
112 !XMLString::compareString(name,SHIBT_L(Path)) ||
113 !XMLString::compareString(name,SHIBT_L(AccessControl)) ||
114 !XMLString::compareString(name,SHIBT_L(htaccess)) ||
115 !XMLString::compareString(name,SHIBT_L(AccessControlProvider)))
116 return FILTER_REJECT;
118 return FILTER_ACCEPT;
121 void Override::loadACL(const DOMElement* e, Category& log)
123 IPlugIn* plugin=NULL;
124 const DOMElement* acl=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(htaccess));
126 log.info("building Apache htaccess provider...");
127 plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::htAccessControlType,acl);
130 acl=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(AccessControl));
132 log.info("building XML-based Access Control provider...");
133 plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::XMLAccessControlType,acl);
136 acl=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(AccessControlProvider));
138 auto_ptr_char type(acl->getAttributeNS(NULL,SHIBT_L(type)));
139 log.info("building Access Control provider of type %s...",type.get());
140 plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(type.get(),acl);
145 IAccessControl* acl=dynamic_cast<IAccessControl*>(plugin);
150 log.fatal("plugin was not an Access Control provider");
151 throw UnsupportedExtensionException("plugin was not an Access Control provider");
156 Override::Override(const DOMElement* e, Category& log, const Override* base) : m_base(base), m_acl(NULL)
159 // Load the property set.
162 // Load any AccessControl provider.
165 // Handle nested Paths.
166 unsigned int count=0;
167 DOMElement* path=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
169 const XMLCh* n=path->getAttributeNS(NULL,SHIBT_L(name));
171 // Skip any leading slashes.
172 while (n && *n==chForwardSlash)
175 // Check for empty name.
177 log.warn("skipping Path element with empty name attribute");
178 path=saml::XML::getNextSiblingElement(path,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
182 // Check for an embedded slash.
183 int slash=XMLString::indexOf(n,chForwardSlash);
185 // Copy the first path segment.
186 XMLCh* namebuf=new XMLCh[slash + 1];
187 for (int pos=0; pos < slash; pos++)
189 namebuf[slash]=chNull;
191 // Move past the slash in the original pathname.
194 // Skip any leading slashes again.
195 while (*n==chForwardSlash)
199 // Create a placeholder Path element for the first path segment and replant under it.
200 DOMElement* newpath=path->getOwnerDocument()->createElementNS(shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
201 newpath->setAttributeNS(NULL,SHIBT_L(name),namebuf);
202 path->setAttributeNS(NULL,SHIBT_L(name),n);
203 path->getParentNode()->replaceChild(newpath,path);
204 newpath->appendChild(path);
206 // Repoint our locals at the new parent.
208 n=path->getAttributeNS(NULL,SHIBT_L(name));
211 // All we had was a pathname with trailing slash(es), so just reset it without them.
212 path->setAttributeNS(NULL,SHIBT_L(name),namebuf);
213 n=path->getAttributeNS(NULL,SHIBT_L(name));
218 Override* o=new Override(path,log,this);
219 pair<bool,const char*> name=o->getString("name");
220 char* dup=strdup(name.second);
221 for (char* pch=dup; *pch; pch++)
223 if (m_map.count(dup)) {
224 log.warn("Skipping duplicate Path element (%s)",dup);
227 path=saml::XML::getNextSiblingElement(path,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
233 path=saml::XML::getNextSiblingElement(path,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
242 Override::~Override()
245 for_each(m_map.begin(),m_map.end(),cleanup<string,Override>);
248 pair<bool,bool> Override::getBool(const char* name, const char* ns) const
250 pair<bool,bool> ret=XMLPropertySet::getBool(name,ns);
253 return m_base ? m_base->getBool(name,ns) : ret;
256 pair<bool,const char*> Override::getString(const char* name, const char* ns) const
258 pair<bool,const char*> ret=XMLPropertySet::getString(name,ns);
261 return m_base ? m_base->getString(name,ns) : ret;
264 pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const
266 pair<bool,const XMLCh*> ret=XMLPropertySet::getXMLString(name,ns);
269 return m_base ? m_base->getXMLString(name,ns) : ret;
272 pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const
274 pair<bool,unsigned int> ret=XMLPropertySet::getUnsignedInt(name,ns);
277 return m_base ? m_base->getUnsignedInt(name,ns) : ret;
280 pair<bool,int> Override::getInt(const char* name, const char* ns) const
282 pair<bool,int> ret=XMLPropertySet::getInt(name,ns);
285 return m_base ? m_base->getInt(name,ns) : ret;
288 const IPropertySet* Override::getPropertySet(const char* name, const char* ns) const
290 const IPropertySet* ret=XMLPropertySet::getPropertySet(name,ns);
293 return m_base->getPropertySet(name,ns);
296 const Override* Override::locate(const char* path) const
298 char* dup=strdup(path);
299 char* sep=strchr(dup,'?');
302 for (char* pch=dup; *pch; pch++)
305 const Override* o=this;
309 const char* token=strtok_r(dup,"/",&pos);
311 const char* token=strtok(dup,"/");
315 map<string,Override*>::const_iterator i=o->m_map.find(token);
316 if (i==o->m_map.end())
320 token=strtok_r(NULL,"/",&pos);
322 token=strtok(NULL,"/");
330 void XMLRequestMapperImpl::init()
335 log=&Category::getInstance("shibtarget.RequestMapper");
338 if (!saml::XML::isElementNamed(ReloadableXMLFileImpl::m_root,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(RequestMap))) {
339 log->error("Construction requires a valid request mapping file: (conf:RequestMap as root element)");
340 throw MalformedException("Construction requires a valid request mapping file: (conf:RequestMap as root element)");
343 // Load the property set.
344 load(ReloadableXMLFileImpl::m_root,*log,this);
346 // Load any AccessControl provider.
347 loadACL(ReloadableXMLFileImpl::m_root,*log);
349 // Loop over the Host elements.
350 DOMNodeList* nlist = ReloadableXMLFileImpl::m_root->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Host));
351 for (unsigned int i=0; nlist && i<nlist->getLength(); i++) {
352 DOMElement* host=static_cast<DOMElement*>(nlist->item(i));
353 const XMLCh* n=host->getAttributeNS(NULL,SHIBT_L(name));
355 log->warn("Skipping Host element (%d) with empty name attribute",i);
359 Override* o=new Override(host,*log,this);
360 pair<bool,const char*> name=o->getString("name");
361 pair<bool,const char*> scheme=o->getString("scheme");
362 pair<bool,const char*> port=o->getString("port");
364 char* dup=strdup(name.second);
365 for (char* pch=dup; *pch; pch++)
367 auto_ptr<char> dupwrap(dup);
369 if (!scheme.first && port.first) {
370 // No scheme, but a port, so assume http.
371 scheme = pair<bool,const char*>(true,"http");
373 else if (scheme.first && !port.first) {
374 // Scheme, no port, so default it.
375 // XXX Use getservbyname instead?
377 if (!strcmp(scheme.second,"http"))
379 else if (!strcmp(scheme.second,"https"))
381 else if (!strcmp(scheme.second,"ftp"))
383 else if (!strcmp(scheme.second,"ldap"))
385 else if (!strcmp(scheme.second,"ldaps"))
390 string url(scheme.second);
391 url=url + "://" + dup;
393 // Is this the default port?
394 if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||
395 (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||
396 (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||
397 (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||
398 (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {
399 // First store a port-less version.
400 if (m_map.count(url) || m_extras.count(url)) {
401 log->warn("Skipping duplicate Host element (%s)",url.c_str());
406 log->debug("Added <Host> mapping for %s",url.c_str());
408 // Now append the port. We use the extras vector, to avoid double freeing the object later.
409 url=url + ':' + port.second;
411 log->debug("Added <Host> mapping for %s",url.c_str());
414 url=url + ':' + port.second;
415 if (m_map.count(url) || m_extras.count(url)) {
416 log->warn("Skipping duplicate Host element (%s)",url.c_str());
421 log->debug("Added <Host> mapping for %s",url.c_str());
425 // No scheme or port, so we enter dual hosts on http:80 and https:443
426 string url("http://");
428 if (m_map.count(url) || m_extras.count(url)) {
429 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 if (m_map.count(url) || m_extras.count(url)) {
447 log->warn("Skipping duplicate Host element (%s)",url.c_str());
451 log->debug("Added <Host> mapping for %s",url.c_str());
454 if (m_map.count(url) || m_extras.count(url)) {
455 log->warn("Skipping duplicate Host element (%s)",url.c_str());
459 log->debug("Added <Host> mapping for %s",url.c_str());
463 catch (SAMLException& e) {
464 log->errorStream() << "Error while parsing request mapping configuration: " << e.what() << CategoryStream::ENDLINE;
470 log->error("Unexpected error while parsing request mapping configuration");
476 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char* path) const
478 const Override* o=NULL;
479 map<string,Override*>::const_iterator i=m_map.find(vhost);
483 i=m_extras.find(vhost);
484 if (i!=m_extras.end())
488 return o ? o->locate(path) : this;
491 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const char* pathname, bool first) const
493 return new XMLRequestMapperImpl(pathname);
496 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const DOMElement* e, bool first) const
498 return new XMLRequestMapperImpl(e);
501 IRequestMapper::Settings XMLRequestMapper::getSettings(ShibTarget* st) const
504 vhost << st->getProtocol() << "://" << st->getHostname() << ':' << st->getPort();
506 XMLRequestMapperImpl* impl=static_cast<XMLRequestMapperImpl*>(getImplementation());
507 const Override* o=impl->findOverride(vhost.str().c_str(), st->getRequestURI());
509 if (impl->log->isDebugEnabled()) {
511 saml::NDC ndc("getSettings");
513 pair<bool,const char*> ret=o->getString("applicationId");
514 impl->log->debug("mapped %s%s to %s", vhost.str().c_str(), st->getRequestURI() ? st->getRequestURI() : "", ret.second);
517 return Settings(o,o->getAC());