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
27 #include <xercesc/util/XMLUniDefs.hpp>
28 #include <xercesc/util/regx/RegularExpression.hpp>
30 using namespace shibtarget::logging;
31 using namespace shibtarget;
32 using namespace shibboleth;
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 {
58 const Override* locate(const char* path) const;
59 IAccessControl* getAC() const { return (m_acl ? m_acl : (m_base ? m_base->getAC() : NULL)); }
62 void loadACL(const DOMElement* e, Category& log);
64 map<string,Override*> m_map;
65 vector< pair<RegularExpression*,Override*> > m_regexps;
68 const Override* m_base;
69 IAccessControl* m_acl;
72 class XMLRequestMapperImpl : public ReloadableXMLFileImpl, public Override
75 XMLRequestMapperImpl(const char* pathname) : ReloadableXMLFileImpl(pathname) { init(); }
76 XMLRequestMapperImpl(const DOMElement* e) : ReloadableXMLFileImpl(e) { init(); }
78 ~XMLRequestMapperImpl() {}
80 const Override* findOverride(const char* vhost, const char* path) const;
84 map<string,Override*> m_extras;
87 // An implementation of the URL->application mapping API using an XML file
88 class XMLRequestMapper : public IRequestMapper, public ReloadableXMLFile
91 XMLRequestMapper(const DOMElement* e) : ReloadableXMLFile(e) {}
92 ~XMLRequestMapper() {}
94 virtual Settings getSettings(ShibTarget* st) const;
97 virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
98 virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
101 static const XMLCh HostRegex[] = { chLatin_H, chLatin_o, chLatin_s, chLatin_t, chLatin_R, chLatin_e, chLatin_g, chLatin_e, chLatin_x, chNull };
102 static const XMLCh ignoreCase[] = { chLatin_i, chLatin_g, chLatin_n, chLatin_o, chLatin_r, chLatin_e, chLatin_C, chLatin_a, chLatin_s, chLatin_e, chNull };
103 static const XMLCh ignoreOption[] = { chLatin_i, chNull };
104 static const XMLCh PathRegex[] = { chLatin_P, chLatin_a, chLatin_t, chLatin_h, chLatin_R, chLatin_e, chLatin_g, chLatin_e, chLatin_x, chNull };
105 static const XMLCh regex[] = { chLatin_r, chLatin_e, chLatin_g, chLatin_e, chLatin_x, chNull };
108 IPlugIn* XMLRequestMapFactory(const DOMElement* e)
110 auto_ptr<XMLRequestMapper> m(new XMLRequestMapper(e));
111 m->getImplementation();
115 void Override::loadACL(const DOMElement* e, Category& log)
117 IPlugIn* plugin=NULL;
118 const DOMElement* acl=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(htaccess));
120 log.info("building Apache htaccess provider...");
121 plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::htAccessControlType,acl);
124 acl=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(AccessControl));
126 log.info("building XML-based AccessControl provider...");
127 plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::XMLAccessControlType,acl);
130 acl=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(AccessControlProvider));
132 auto_ptr_char type(acl->getAttributeNS(NULL,SHIBT_L(type)));
133 log.info("building AccessControl provider of type %s...",type.get());
134 plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(type.get(),acl);
139 IAccessControl* acl=dynamic_cast<IAccessControl*>(plugin);
144 log.fatal("plugin was not an AccessControl provider");
145 throw UnsupportedExtensionException("plugin was not an Access Control provider");
150 Override::Override(const DOMElement* e, Category& log, const Override* base) : m_base(base), m_acl(NULL)
153 // Load the property set.
156 // Load any AccessControl provider.
159 // Handle nested Paths.
160 DOMElement* path=saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
162 const XMLCh* n=path->getAttributeNS(NULL,SHIBT_L(name));
164 // Skip any leading slashes.
165 while (n && *n==chForwardSlash)
168 // Check for empty name.
170 log.warn("skipping Path element with empty name attribute");
171 path=saml::XML::getNextSiblingElement(path,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
175 // Check for an embedded slash.
176 int slash=XMLString::indexOf(n,chForwardSlash);
178 // Copy the first path segment.
179 XMLCh* namebuf=new XMLCh[slash + 1];
180 for (int pos=0; pos < slash; pos++)
182 namebuf[slash]=chNull;
184 // Move past the slash in the original pathname.
187 // Skip any leading slashes again.
188 while (*n==chForwardSlash)
192 // Create a placeholder Path element for the first path segment and replant under it.
193 DOMElement* newpath=path->getOwnerDocument()->createElementNS(shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
194 newpath->setAttributeNS(NULL,SHIBT_L(name),namebuf);
195 path->setAttributeNS(NULL,SHIBT_L(name),n);
196 path->getParentNode()->replaceChild(newpath,path);
197 newpath->appendChild(path);
199 // Repoint our locals at the new parent.
201 n=path->getAttributeNS(NULL,SHIBT_L(name));
204 // All we had was a pathname with trailing slash(es), so just reset it without them.
205 path->setAttributeNS(NULL,SHIBT_L(name),namebuf);
206 n=path->getAttributeNS(NULL,SHIBT_L(name));
211 Override* o=new Override(path,log,this);
212 pair<bool,const char*> name=o->getString("name");
213 char* dup=strdup(name.second);
214 for (char* pch=dup; *pch; pch++)
216 if (m_map.count(dup)) {
217 log.warn("Skipping duplicate Path element (%s)",dup);
220 path=saml::XML::getNextSiblingElement(path,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
226 path=saml::XML::getNextSiblingElement(path,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Path));
229 if (!XMLString::equals(e->getLocalName(), PathRegex)) {
230 // Handle nested PathRegexs.
231 path = saml::XML::getFirstChildElement(e,shibtarget::XML::SHIBTARGET_NS,PathRegex);
232 for (int i=1; path; ++i, path=saml::XML::getNextSiblingElement(path,shibtarget::XML::SHIBTARGET_NS,PathRegex)) {
233 const XMLCh* n=path->getAttributeNS(NULL,regex);
235 log.warn("skipping PathRegex element (%d) with empty regex attribute",i);
239 auto_ptr<Override> o(new Override(path,log,this));
241 const XMLCh* flag=path->getAttributeNS(NULL,ignoreCase);
243 auto_ptr<RegularExpression> re(
244 new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
246 m_regexps.push_back(make_pair(re.release(), o.release()));
248 catch (XMLException& ex) {
249 auto_ptr_char tmp(ex.getMessage());
250 log.error("caught exception while parsing PathRegex regular expression (%d): %s", i, tmp.get());
251 throw ConfigurationException("Invalid regular expression in PathRegex element.");
254 if (log.isDebugEnabled())
255 log.debug("added <PathRegex> mapping (%s)", m_regexps.back().second->getString("regex").second);
262 for (map<string,Override*>::iterator m=m_map.begin(); m!=m_map.end(); m++)
264 for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {
272 Override::~Override()
275 for (map<string,Override*>::iterator m=m_map.begin(); m!=m_map.end(); m++)
277 for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {
283 pair<bool,bool> Override::getBool(const char* name, const char* ns) const
285 pair<bool,bool> ret=XMLPropertySet::getBool(name,ns);
288 return m_base ? m_base->getBool(name,ns) : ret;
291 pair<bool,const char*> Override::getString(const char* name, const char* ns) const
293 pair<bool,const char*> ret=XMLPropertySet::getString(name,ns);
296 return m_base ? m_base->getString(name,ns) : ret;
299 pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const
301 pair<bool,const XMLCh*> ret=XMLPropertySet::getXMLString(name,ns);
304 return m_base ? m_base->getXMLString(name,ns) : ret;
307 pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const
309 pair<bool,unsigned int> ret=XMLPropertySet::getUnsignedInt(name,ns);
312 return m_base ? m_base->getUnsignedInt(name,ns) : ret;
315 pair<bool,int> Override::getInt(const char* name, const char* ns) const
317 pair<bool,int> ret=XMLPropertySet::getInt(name,ns);
320 return m_base ? m_base->getInt(name,ns) : ret;
323 const IPropertySet* Override::getPropertySet(const char* name, const char* ns) const
325 const IPropertySet* ret=XMLPropertySet::getPropertySet(name,ns);
328 return m_base->getPropertySet(name,ns);
331 const Override* Override::locate(const char* path) const
333 // This function is confusing because it's *not* recursive.
334 // The whole path is tokenized and mapped in a loop, so the
335 // path parameter starts with the entire request path and
336 // we can skip the leading slash as irrelevant.
340 // Now we copy the path, chop the query string, and lower case it.
341 char* dup=strdup(path);
342 char* sep=strchr(dup,'?');
345 for (char* pch=dup; *pch; pch++)
348 // Default is for the current object to provide settings.
349 const Override* o=this;
351 // Tokenize the path by segment and try and map each segment.
354 const char* token=strtok_r(dup,"/",&pos);
356 const char* token=strtok(dup,"/");
360 map<string,Override*>::const_iterator i=o->m_map.find(token);
361 if (i==o->m_map.end())
362 break; // Once there's no match, we've consumed as much of the path as possible here.
363 // We found a match, so reset the settings pointer.
366 // We descended a step down the path, so we need to advance the original
367 // parameter for the regex step later.
368 path += strlen(token);
373 token=strtok_r(NULL,"/",&pos);
375 token=strtok(NULL,"/");
381 // If there's anything left, we try for a regex match on the rest of the path minus the query string.
384 path2 = path2.substr(0,path2.find('?'));
386 for (vector< pair<RegularExpression*,Override*> >::const_iterator re = o->m_regexps.begin(); re != o->m_regexps.end(); ++re) {
387 if (re->first->matches(path2.c_str())) {
397 void XMLRequestMapperImpl::init()
402 log=&Category::getInstance("shibtarget.RequestMapper");
405 if (!saml::XML::isElementNamed(ReloadableXMLFileImpl::m_root,shibtarget::XML::SHIBTARGET_NS,SHIBT_L(RequestMap))) {
406 log->error("Construction requires a valid request mapping file: (conf:RequestMap as root element)");
407 throw MalformedException("Construction requires a valid request mapping file: (conf:RequestMap as root element)");
410 // Load the property set.
411 load(ReloadableXMLFileImpl::m_root,*log,this);
413 // Load any AccessControl provider.
414 loadACL(ReloadableXMLFileImpl::m_root,*log);
416 // Loop over the Host elements.
417 DOMNodeList* nlist = ReloadableXMLFileImpl::m_root->getElementsByTagNameNS(shibtarget::XML::SHIBTARGET_NS,SHIBT_L(Host));
418 for (XMLSize_t i=0; nlist && i<nlist->getLength(); i++) {
419 DOMElement* host=static_cast<DOMElement*>(nlist->item(i));
420 const XMLCh* n=host->getAttributeNS(NULL,SHIBT_L(name));
422 log->warn("Skipping Host element (%d) with empty name attribute",i);
426 Override* o=new Override(host,*log,this);
427 pair<bool,const char*> name=o->getString("name");
428 pair<bool,const char*> scheme=o->getString("scheme");
429 pair<bool,const char*> port=o->getString("port");
431 char* dup=strdup(name.second);
432 for (char* pch=dup; *pch; pch++)
434 auto_ptr<char> dupwrap(dup);
436 if (!scheme.first && port.first) {
437 // No scheme, but a port, so assume http.
438 scheme = pair<bool,const char*>(true,"http");
440 else if (scheme.first && !port.first) {
441 // Scheme, no port, so default it.
442 // XXX Use getservbyname instead?
444 if (!strcmp(scheme.second,"http"))
446 else if (!strcmp(scheme.second,"https"))
448 else if (!strcmp(scheme.second,"ftp"))
450 else if (!strcmp(scheme.second,"ldap"))
452 else if (!strcmp(scheme.second,"ldaps"))
457 string url(scheme.second);
458 url=url + "://" + dup;
460 // Is this the default port?
461 if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||
462 (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||
463 (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||
464 (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||
465 (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {
466 // First store a port-less version.
467 if (m_map.count(url) || m_extras.count(url)) {
468 log->warn("Skipping duplicate Host element (%s)",url.c_str());
473 log->debug("Added <Host> mapping for %s",url.c_str());
475 // Now append the port. We use the extras vector, to avoid double freeing the object later.
476 url=url + ':' + port.second;
478 log->debug("Added <Host> mapping for %s",url.c_str());
481 url=url + ':' + port.second;
482 if (m_map.count(url) || m_extras.count(url)) {
483 log->warn("Skipping duplicate Host element (%s)",url.c_str());
488 log->debug("Added <Host> mapping for %s",url.c_str());
492 // No scheme or port, so we enter dual hosts on http:80 and https:443
493 string url("http://");
495 if (m_map.count(url) || m_extras.count(url)) {
496 log->warn("Skipping duplicate Host element (%s)",url.c_str());
501 log->debug("Added <Host> mapping for %s",url.c_str());
504 if (m_map.count(url) || m_extras.count(url)) {
505 log->warn("Skipping duplicate Host element (%s)",url.c_str());
509 log->debug("Added <Host> mapping for %s",url.c_str());
513 if (m_map.count(url) || m_extras.count(url)) {
514 log->warn("Skipping duplicate Host element (%s)",url.c_str());
518 log->debug("Added <Host> mapping for %s",url.c_str());
521 if (m_map.count(url) || m_extras.count(url)) {
522 log->warn("Skipping duplicate Host element (%s)",url.c_str());
526 log->debug("Added <Host> mapping for %s",url.c_str());
530 catch (SAMLException& e) {
531 log->errorStream() << "Error while parsing request mapping configuration: " << e.what() << CategoryStream::ENDLINE;
537 log->error("Unexpected error while parsing request mapping configuration");
543 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char* path) const
545 const Override* o=NULL;
546 map<string,Override*>::const_iterator i=m_map.find(vhost);
550 i=m_extras.find(vhost);
551 if (i!=m_extras.end())
555 return o ? o->locate(path) : this;
558 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const char* pathname, bool first) const
560 return new XMLRequestMapperImpl(pathname);
563 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const DOMElement* e, bool first) const
565 return new XMLRequestMapperImpl(e);
568 IRequestMapper::Settings XMLRequestMapper::getSettings(ShibTarget* st) const
571 vhost << st->getProtocol() << "://" << st->getHostname() << ':' << st->getPort();
573 XMLRequestMapperImpl* impl=static_cast<XMLRequestMapperImpl*>(getImplementation());
574 const Override* o=impl->findOverride(vhost.str().c_str(), st->getRequestURI());
576 if (impl->log->isDebugEnabled()) {
578 saml::NDC ndc("getSettings");
580 pair<bool,const char*> ret=o->getString("applicationId");
581 impl->log->debug("mapped %s%s to %s", vhost.str().c_str(), st->getRequestURI() ? st->getRequestURI() : "", ret.second);
584 return Settings(o,o->getAC());