Some API refactoring
[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, ShibTarget* st) const;
123         virtual Settings getSettingsFromParsedURL(
124             const char* scheme, const char* hostname, unsigned int port, const char* path, ShibTarget* st
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     auto_ptr<XMLRequestMapper> m(new XMLRequestMapper(e));
136     m->getImplementation();
137     return m.release();
138 }
139
140 short Override::acceptNode(const DOMNode* node) const
141 {
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;
149
150     return FILTER_ACCEPT;
151 }
152
153 void Override::loadACL(const DOMElement* e, Category& log)
154 {
155     IPlugIn* plugin=NULL;
156     const DOMElement* acl=saml::XML::getFirstChildElement(e,ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(htaccess));
157     if (acl) {
158         log.info("building htaccess provider...");
159         plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::htaccessType,acl);
160     }
161     else {
162         acl=saml::XML::getFirstChildElement(e,ShibTargetConfig::SHIBTARGET_NS,SHIBT_L(AccessControlProvider));
163         if (acl) {
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);
167         }
168     }
169     if (plugin) {
170         IAccessControl* acl=dynamic_cast<IAccessControl*>(plugin);
171         if (acl)
172             m_acl=acl;
173         else {
174             delete plugin;
175             log.fatal("plugin was not an Access Control provider");
176             throw UnsupportedExtensionException("plugin was not an Access Control provider");
177         }
178     }
179 }
180
181 Override::Override(const DOMElement* e, Category& log, const Override* base) : m_base(base), m_acl(NULL)
182 {
183     try {
184         // Load the property set.
185         load(e,log,this);
186         
187         // Load any AccessControl provider.
188         loadACL(e,log);
189     
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));
195             if (!n || !*n) {
196                 log.warn("skipping Path element (%d) with empty name attribute",i);
197                 continue;
198             }
199             else if (*n==chForwardSlash && !n[1]) {
200                 log.warn("skipping Path element (%d) with a lone slash in the name attribute",i);
201                 continue;
202             }
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++)
207                 *pch=tolower(*pch);
208             if (m_map.count(dup)) {
209                 log.warn("Skipping duplicate Path element (%s)",dup);
210                 free(dup);
211                 delete o;
212                 continue;
213             }
214             m_map[dup]=o;
215             free(dup);
216         }
217     }
218     catch (...) {
219         this->~Override();
220         throw;
221     }
222 }
223
224 Override::~Override()
225 {
226     delete m_acl;
227     for (map<string,Override*>::iterator i=m_map.begin(); i!=m_map.end(); i++)
228         delete i->second;
229 }
230
231 pair<bool,bool> Override::getBool(const char* name, const char* ns) const
232 {
233     pair<bool,bool> ret=XMLPropertySet::getBool(name,ns);
234     if (ret.first)
235         return ret;
236     return m_base ? m_base->getBool(name,ns) : ret;
237 }
238
239 pair<bool,const char*> Override::getString(const char* name, const char* ns) const
240 {
241     pair<bool,const char*> ret=XMLPropertySet::getString(name,ns);
242     if (ret.first)
243         return ret;
244     return m_base ? m_base->getString(name,ns) : ret;
245 }
246
247 pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const
248 {
249     pair<bool,const XMLCh*> ret=XMLPropertySet::getXMLString(name,ns);
250     if (ret.first)
251         return ret;
252     return m_base ? m_base->getXMLString(name,ns) : ret;
253 }
254
255 pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const
256 {
257     pair<bool,unsigned int> ret=XMLPropertySet::getUnsignedInt(name,ns);
258     if (ret.first)
259         return ret;
260     return m_base ? m_base->getUnsignedInt(name,ns) : ret;
261 }
262
263 pair<bool,int> Override::getInt(const char* name, const char* ns) const
264 {
265     pair<bool,int> ret=XMLPropertySet::getInt(name,ns);
266     if (ret.first)
267         return ret;
268     return m_base ? m_base->getInt(name,ns) : ret;
269 }
270
271 const IPropertySet* Override::getPropertySet(const char* name, const char* ns) const
272 {
273     const IPropertySet* ret=XMLPropertySet::getPropertySet(name,ns);
274     if (ret || !m_base)
275         return ret;
276     return m_base->getPropertySet(name,ns);
277 }
278
279 const Override* Override::locate(const char* path) const
280 {
281     char* dup=strdup(path);
282     char* sep=strchr(dup,'?');
283     if (sep)
284         *sep=0;
285     for (char* pch=dup; *pch; pch++)
286         *pch=tolower(*pch);
287         
288     const Override* o=this;
289     
290 #ifdef HAVE_STRTOK_R
291     char* pos=NULL;
292     const char* token=strtok_r(dup,"/",&pos);
293 #else
294     const char* token=strtok(dup,"/");
295 #endif
296     while (token)
297     {
298         map<string,Override*>::const_iterator i=o->m_map.find(token);
299         if (i==o->m_map.end())
300             break;
301         o=i->second;
302 #ifdef HAVE_STRTOK_R
303         token=strtok_r(NULL,"/",&pos);
304 #else
305         token=strtok(NULL,"/");
306 #endif
307     }
308
309     free(dup);
310     return o;
311 }
312
313 void XMLRequestMapperImpl::init()
314 {
315 #ifdef _DEBUG
316     NDC ndc("init");
317 #endif
318     log=&Category::getInstance("shibtarget.RequestMapper");
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             auto_ptr<char> dupwrap(dup);
351
352             if (!scheme.first && port.first) {
353                 // No scheme, but a port, so assume http.
354                 scheme = pair<bool,const char*>(true,"http");
355             }
356             else if (scheme.first && !port.first) {
357                 // Scheme, no port, so default it.
358                 // XXX Use getservbyname instead?
359                 port.first = true;
360                 if (!strcmp(scheme.second,"http"))
361                     port.second = "80";
362                 else if (!strcmp(scheme.second,"https"))
363                     port.second = "443";
364                 else if (!strcmp(scheme.second,"ftp"))
365                     port.second = "21";
366                 else if (!strcmp(scheme.second,"ldap"))
367                     port.second = "389";
368                 else if (!strcmp(scheme.second,"ldaps"))
369                     port.second = "636";
370             }
371
372             if (scheme.first) {
373                 string url(scheme.second);
374                 url=url + "://" + dup;
375                 
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());
385                         delete o;
386                         continue;
387                     }
388                     m_map[url]=o;
389                     log->debug("Added <Host> mapping for %s",url.c_str());
390                     
391                     // Now append the port. We use the extras vector, to avoid double freeing the object later.
392                     url=url + ':' + port.second;
393                     m_extras[url]=o;
394                     log->debug("Added <Host> mapping for %s",url.c_str());
395                 }
396                 else {
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());
400                         delete o;
401                         continue;
402                     }
403                     m_map[url]=o;
404                     log->debug("Added <Host> mapping for %s",url.c_str());
405                 }
406             }
407             else {
408                 // No scheme or port, so we enter dual hosts on http:80 and https:443
409                 string url("http://");
410                 url = url + dup;
411                 if (m_map.count(url) || m_extras.count(url)) {
412                     log->warn("Skipping duplicate Host element (%s)",url.c_str());
413                     delete o;
414                     continue;
415                 }
416                 m_map[url]=o;
417                 log->debug("Added <Host> mapping for %s",url.c_str());
418                 
419                 url = url + ":80";
420                 if (m_map.count(url) || m_extras.count(url)) {
421                     log->warn("Skipping duplicate Host element (%s)",url.c_str());
422                     continue;
423                 }
424                 m_extras[url]=o;
425                 log->debug("Added <Host> mapping for %s",url.c_str());
426                 
427                 url = "https://";
428                 url = url + dup;
429                 if (m_map.count(url) || m_extras.count(url)) {
430                     log->warn("Skipping duplicate Host element (%s)",url.c_str());
431                     continue;
432                 }
433                 m_extras[url]=o;
434                 log->debug("Added <Host> mapping for %s",url.c_str());
435                 
436                 url = url + ":443";
437                 if (m_map.count(url) || m_extras.count(url)) {
438                     log->warn("Skipping duplicate Host element (%s)",url.c_str());
439                     continue;
440                 }
441                 m_extras[url]=o;
442                 log->debug("Added <Host> mapping for %s",url.c_str());
443             }
444         }
445     }
446     catch (SAMLException& e) {
447         log->errorStream() << "Error while parsing request mapping configuration: " << e.what() << CategoryStream::ENDLINE;
448         throw;
449     }
450 #ifndef _DEBUG
451     catch (...)
452     {
453         log->error("Unexpected error while parsing request mapping configuration");
454         throw;
455     }
456 #endif
457 }
458
459 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char* path) const
460 {
461     const Override* o=NULL;
462     map<string,Override*>::const_iterator i=m_map.find(vhost);
463     if (i!=m_map.end())
464         o=i->second;
465     else {
466         i=m_extras.find(vhost);
467         if (i!=m_extras.end())
468             o=i->second;
469     }
470     
471     return o ? o->locate(path) : this;
472 }
473
474 const char* split_url(const char* url, string& vhost)
475 {
476     const char* path=NULL;
477     const char* slash=strchr(url,'/');
478     if (slash)
479     {
480         slash=strchr(slash,'/');
481         if (slash)
482         {
483             path=strchr(slash,'/');
484             if (path)
485                 vhost.append(url,path-url);
486             else
487                 vhost=url;
488         }
489     }
490     return path;
491 }
492
493 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const char* pathname, bool first) const
494 {
495     return new XMLRequestMapperImpl(pathname);
496 }
497
498 ReloadableXMLFileImpl* XMLRequestMapper::newImplementation(const DOMElement* e, bool first) const
499 {
500     return new XMLRequestMapperImpl(e);
501 }
502
503 IRequestMapper::Settings XMLRequestMapper::getSettingsFromURL(const char* url, ShibTarget* st) const
504 {
505     string vhost;
506     const char* path=split_url(url,vhost);
507
508     XMLRequestMapperImpl* impl=static_cast<XMLRequestMapperImpl*>(getImplementation());
509     const Override* o=impl->findOverride(vhost.c_str(), path);
510
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);
515     }
516
517     return Settings(o,o->m_acl);
518 }
519
520 IRequestMapper::Settings XMLRequestMapper::getSettingsFromParsedURL(
521     const char* scheme, const char* hostname, unsigned int port, const char* path, ShibTarget* st
522     ) const
523 {
524     char buf[21];
525     string vhost(scheme);
526     vhost=vhost + "://" + hostname + ':';
527 #ifdef WIN32
528     _snprintf(buf,20,"%u",port);
529 #else
530     snprintf(buf,20,"%u",port);
531 #endif
532     vhost+=buf;
533
534     XMLRequestMapperImpl* impl=static_cast<XMLRequestMapperImpl*>(getImplementation());
535     const Override* o=impl->findOverride(vhost.c_str(), path);
536
537     if (impl->log->isDebugEnabled())
538     {
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);
542     }
543
544     return Settings(o,o->m_acl);
545 }