Imported Upstream version 2.2.1+dfsg
[shibboleth/sp.git] / shibsp / impl / XMLRequestMapper.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  *
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /** XMLRequestMapper.cpp
18  *
19  * XML-based RequestMapper implementation
20  */
21
22 #include "internal.h"
23 #include "exceptions.h"
24 #include "AccessControl.h"
25 #include "RequestMapper.h"
26 #include "SPRequest.h"
27 #include "util/DOMPropertySet.h"
28 #include "util/SPConstants.h"
29
30 #include <xmltooling/util/NDC.h>
31 #include <xmltooling/util/ReloadableXMLFile.h>
32 #include <xmltooling/util/XMLHelper.h>
33 #include <xercesc/util/XMLUniDefs.hpp>
34 #include <xercesc/util/regx/RegularExpression.hpp>
35
36 using namespace shibsp;
37 using namespace xmltooling;
38 using namespace std;
39
40 namespace shibsp {
41
42     // Blocks access when an ACL plugin fails to load.
43     class AccessControlDummy : public AccessControl
44     {
45     public:
46         Lockable* lock() {
47             return this;
48         }
49
50         void unlock() {}
51
52         aclresult_t authorized(const SPRequest& request, const Session* session) const {
53             return shib_acl_false;
54         }
55     };
56
57     class Override : public DOMPropertySet, public DOMNodeFilter
58     {
59     public:
60         Override() : m_acl(NULL) {}
61         Override(const DOMElement* e, Category& log, const Override* base=NULL);
62         ~Override();
63
64         // Provides filter to exclude special config elements.
65 #ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
66         short
67 #else
68         FilterAction
69 #endif
70         acceptNode(const DOMNode* node) const {
71             return FILTER_REJECT;
72         }
73
74         const Override* locate(const HTTPRequest& request) const;
75         AccessControl* getAC() const { return (m_acl ? m_acl : (getParent() ? dynamic_cast<const Override*>(getParent())->getAC() : NULL)); }
76
77     protected:
78         void loadACL(const DOMElement* e, Category& log);
79
80         map<string,Override*> m_map;
81         vector< pair<RegularExpression*,Override*> > m_regexps;
82         vector< pair< pair<string,RegularExpression*>,Override*> > m_queries;
83
84     private:
85         AccessControl* m_acl;
86     };
87
88     class XMLRequestMapperImpl : public Override
89     {
90     public:
91         XMLRequestMapperImpl(const DOMElement* e, Category& log);
92
93         ~XMLRequestMapperImpl() {
94             if (m_document)
95                 m_document->release();
96         }
97
98         void setDocument(DOMDocument* doc) {
99             m_document = doc;
100         }
101
102         const Override* findOverride(const char* vhost, const HTTPRequest& request) const;
103
104     private:
105         map<string,Override*> m_extras;
106         DOMDocument* m_document;
107     };
108
109 #if defined (_MSC_VER)
110     #pragma warning( push )
111     #pragma warning( disable : 4250 )
112 #endif
113
114     class XMLRequestMapper : public RequestMapper, public ReloadableXMLFile
115     {
116     public:
117         XMLRequestMapper(const DOMElement* e) : ReloadableXMLFile(e,Category::getInstance(SHIBSP_LOGCAT".RequestMapper")), m_impl(NULL) {
118             load();
119         }
120
121         ~XMLRequestMapper() {
122             delete m_impl;
123         }
124
125         Settings getSettings(const HTTPRequest& request) const;
126
127     protected:
128         pair<bool,DOMElement*> load();
129
130     private:
131         XMLRequestMapperImpl* m_impl;
132     };
133
134 #if defined (_MSC_VER)
135     #pragma warning( pop )
136 #endif
137
138     RequestMapper* SHIBSP_DLLLOCAL XMLRequestMapperFactory(const DOMElement* const & e)
139     {
140         return new XMLRequestMapper(e);
141     }
142
143     static const XMLCh _AccessControl[] =           UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);
144     static const XMLCh AccessControlProvider[] =    UNICODE_LITERAL_21(A,c,c,e,s,s,C,o,n,t,r,o,l,P,r,o,v,i,d,e,r);
145     static const XMLCh Host[] =                     UNICODE_LITERAL_4(H,o,s,t);
146     static const XMLCh HostRegex[] =                UNICODE_LITERAL_9(H,o,s,t,R,e,g,e,x);
147     static const XMLCh htaccess[] =                 UNICODE_LITERAL_8(h,t,a,c,c,e,s,s);
148     static const XMLCh ignoreCase[] =               UNICODE_LITERAL_10(i,g,n,o,r,e,C,a,s,e);
149     static const XMLCh ignoreOption[] =             UNICODE_LITERAL_1(i);
150     static const XMLCh Path[] =                     UNICODE_LITERAL_4(P,a,t,h);
151     static const XMLCh PathRegex[] =                UNICODE_LITERAL_9(P,a,t,h,R,e,g,e,x);
152     static const XMLCh Query[] =                    UNICODE_LITERAL_5(Q,u,e,r,y);
153     static const XMLCh name[] =                     UNICODE_LITERAL_4(n,a,m,e);
154     static const XMLCh regex[] =                    UNICODE_LITERAL_5(r,e,g,e,x);
155     static const XMLCh _type[] =                    UNICODE_LITERAL_4(t,y,p,e);
156 }
157
158 void SHIBSP_API shibsp::registerRequestMappers()
159 {
160     SPConfig& conf=SPConfig::getConfig();
161     conf.RequestMapperManager.registerFactory(XML_REQUEST_MAPPER, XMLRequestMapperFactory);
162     conf.RequestMapperManager.registerFactory(NATIVE_REQUEST_MAPPER, XMLRequestMapperFactory);
163 }
164
165 void Override::loadACL(const DOMElement* e, Category& log)
166 {
167     try {
168         const DOMElement* acl=XMLHelper::getFirstChildElement(e,htaccess);
169         if (acl) {
170             log.info("building Apache htaccess AccessControl provider...");
171             m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(HT_ACCESS_CONTROL,acl);
172         }
173         else {
174             acl=XMLHelper::getFirstChildElement(e,_AccessControl);
175             if (acl) {
176                 log.info("building XML-based AccessControl provider...");
177                 m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(XML_ACCESS_CONTROL,acl);
178             }
179             else {
180                 acl=XMLHelper::getFirstChildElement(e,AccessControlProvider);
181                 if (acl) {
182                     auto_ptr_char type(acl->getAttributeNS(NULL,_type));
183                     log.info("building AccessControl provider of type %s...",type.get());
184                     m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(type.get(),acl);
185                 }
186             }
187         }
188     }
189     catch (exception& ex) {
190         log.crit("exception building AccessControl provider: %s", ex.what());
191         m_acl = new AccessControlDummy();
192     }
193 }
194
195 Override::Override(const DOMElement* e, Category& log, const Override* base) : m_acl(NULL)
196 {
197     try {
198         // Load the property set.
199         load(e,NULL,this);
200         setParent(base);
201
202         // Load any AccessControl provider.
203         loadACL(e,log);
204
205         // Handle nested Paths.
206         DOMElement* path = XMLHelper::getFirstChildElement(e,Path);
207         for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Path)) {
208             const XMLCh* n=path->getAttributeNS(NULL,name);
209
210             // Skip any leading slashes.
211             while (n && *n==chForwardSlash)
212                 n++;
213
214             // Check for empty name.
215             if (!n || !*n) {
216                 log.warn("skipping Path element (%d) with empty name attribute", i);
217                 continue;
218             }
219
220             // Check for an embedded slash.
221             int slash=XMLString::indexOf(n,chForwardSlash);
222             if (slash>0) {
223                 // Copy the first path segment.
224                 XMLCh* namebuf=new XMLCh[slash + 1];
225                 for (int pos=0; pos < slash; pos++)
226                     namebuf[pos]=n[pos];
227                 namebuf[slash]=chNull;
228
229                 // Move past the slash in the original pathname.
230                 n=n+slash+1;
231
232                 // Skip any leading slashes again.
233                 while (*n==chForwardSlash)
234                     n++;
235
236                 if (*n) {
237                     // Create a placeholder Path element for the first path segment and replant under it.
238                     DOMElement* newpath=path->getOwnerDocument()->createElementNS(shibspconstants::SHIB2SPCONFIG_NS,Path);
239                     newpath->setAttributeNS(NULL,name,namebuf);
240                     path->setAttributeNS(NULL,name,n);
241                     path->getParentNode()->replaceChild(newpath,path);
242                     newpath->appendChild(path);
243
244                     // Repoint our locals at the new parent.
245                     path=newpath;
246                     n=path->getAttributeNS(NULL,name);
247                 }
248                 else {
249                     // All we had was a pathname with trailing slash(es), so just reset it without them.
250                     path->setAttributeNS(NULL,name,namebuf);
251                     n=path->getAttributeNS(NULL,name);
252                 }
253                 delete[] namebuf;
254             }
255
256             Override* o=new Override(path,log,this);
257             pair<bool,const char*> name=o->getString("name");
258             char* dup=strdup(name.second);
259             for (char* pch=dup; *pch; pch++)
260                 *pch=tolower(*pch);
261             if (m_map.count(dup)) {
262                 log.warn("skipping duplicate Path element (%s)",dup);
263                 free(dup);
264                 delete o;
265                 continue;
266             }
267             m_map[dup]=o;
268             log.debug("added Path mapping (%s)", dup);
269             free(dup);
270         }
271
272         if (!XMLString::equals(e->getLocalName(), PathRegex)) {
273             // Handle nested PathRegexs.
274             path = XMLHelper::getFirstChildElement(e,PathRegex);
275             for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,PathRegex)) {
276                 const XMLCh* n=path->getAttributeNS(NULL,regex);
277                 if (!n || !*n) {
278                     log.warn("skipping PathRegex element (%d) with empty regex attribute",i);
279                     continue;
280                 }
281
282                 auto_ptr<Override> o(new Override(path,log,this));
283
284                 const XMLCh* flag=path->getAttributeNS(NULL,ignoreCase);
285                 try {
286                     auto_ptr<RegularExpression> re(
287                         new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
288                         );
289                     m_regexps.push_back(make_pair(re.release(), o.release()));
290                 }
291                 catch (XMLException& ex) {
292                     auto_ptr_char tmp(ex.getMessage());
293                     log.error("caught exception while parsing PathRegex regular expression (%d): %s", i, tmp.get());
294                     throw ConfigurationException("Invalid regular expression in PathRegex element.");
295                 }
296
297                 if (log.isDebugEnabled())
298                     log.debug("added <PathRegex> mapping (%s)", m_regexps.back().second->getString("regex").second);
299             }
300         }
301
302         // Handle nested Querys.
303         path = XMLHelper::getFirstChildElement(e,Query);
304         for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Query)) {
305             const XMLCh* n=path->getAttributeNS(NULL,name);
306             if (!n || !*n) {
307                 log.warn("skipping Query element (%d) with empty name attribute",i);
308                 continue;
309             }
310             auto_ptr_char ntemp(n);
311             const XMLCh* v=path->getAttributeNS(NULL,regex);
312
313             auto_ptr<Override> o(new Override(path,log,this));
314             try {
315                 RegularExpression* re = NULL;
316                 if (v && *v)
317                     re = new RegularExpression(v);
318                 m_queries.push_back(make_pair(make_pair(string(ntemp.get()),re), o.release()));
319             }
320             catch (XMLException& ex) {
321                 auto_ptr_char tmp(ex.getMessage());
322                 log.error("caught exception while parsing Query regular expression (%d): %s", i, tmp.get());
323                 throw ConfigurationException("Invalid regular expression in Query element.");
324             }
325
326             log.debug("added <Query> mapping (%s)", ntemp.get());
327         }
328     }
329     catch (exception&) {
330         delete m_acl;
331         for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
332         for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {
333             delete i->first;
334             delete i->second;
335         }
336         for (vector< pair< pair<string,RegularExpression*>,Override*> >::iterator j = m_queries.begin(); j != m_queries.end(); ++j) {
337             delete j->first.second;
338             delete j->second;
339         }
340         throw;
341     }
342 }
343
344 Override::~Override()
345 {
346     delete m_acl;
347     for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
348     for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {
349         delete i->first;
350         delete i->second;
351     }
352     for (vector< pair< pair<string,RegularExpression*>,Override*> >::iterator j = m_queries.begin(); j != m_queries.end(); ++j) {
353         delete j->first.second;
354         delete j->second;
355     }
356 }
357
358 const Override* Override::locate(const HTTPRequest& request) const
359 {
360     // This function is confusing because it's *not* recursive.
361     // The whole path is tokenized and mapped in a loop, so the
362     // path parameter starts with the entire request path and
363     // we can skip the leading slash as irrelevant.
364     const char* path = request.getRequestURI();
365     if (*path == '/')
366         path++;
367
368     // Now we copy the path, chop the query string, and lower case it.
369     char* dup=strdup(path);
370     char* sep=strchr(dup,'?');
371     if (sep)
372         *sep=0;
373     for (char* pch=dup; *pch; pch++)
374         *pch=tolower(*pch);
375
376     // Default is for the current object to provide settings.
377     const Override* o=this;
378
379     // Tokenize the path by segment and try and map each segment.
380 #ifdef HAVE_STRTOK_R
381     char* pos=NULL;
382     const char* token=strtok_r(dup,"/",&pos);
383 #else
384     const char* token=strtok(dup,"/");
385 #endif
386     while (token) {
387         map<string,Override*>::const_iterator i=o->m_map.find(token);
388         if (i==o->m_map.end())
389             break;  // Once there's no match, we've consumed as much of the path as possible here.
390         // We found a match, so reset the settings pointer.
391         o=i->second;
392
393         // We descended a step down the path, so we need to advance the original
394         // parameter for the regex step later.
395         path += strlen(token);
396         if (*path == '/')
397             path++;
398
399         // Get the next segment, if any.
400 #ifdef HAVE_STRTOK_R
401         token=strtok_r(NULL,"/",&pos);
402 #else
403         token=strtok(NULL,"/");
404 #endif
405     }
406
407     free(dup);
408
409     // If there's anything left, we try for a regex match on the rest of the path minus the query string.
410     if (*path) {
411         string path2(path);
412         path2 = path2.substr(0,path2.find('?'));
413
414         for (vector< pair<RegularExpression*,Override*> >::const_iterator re = o->m_regexps.begin(); re != o->m_regexps.end(); ++re) {
415             if (re->first->matches(path2.c_str())) {
416                 o = re->second;
417                 break;
418             }
419         }
420     }
421
422     // Finally, check for query string matches. This is another "unrolled" recursive descent in a loop.
423     bool descended;
424     do {
425         descended = false;
426         for (vector< pair< pair<string,RegularExpression*>,Override*> >::const_iterator q = o->m_queries.begin(); !descended && q != o->m_queries.end(); ++q) {
427             vector<const char*> vals;
428             if (request.getParameters(q->first.first.c_str(), vals)) {
429                 if (q->first.second) {
430                     // We have to match one of the values.
431                     for (vector<const char*>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
432                         if (q->first.second->matches(*v)) {
433                             o = q->second;
434                             descended = true;
435                             break;
436                         }
437                     }
438                 }
439                 else {
440                     // The simple presence of the parameter is sufficient to match.
441                     o = q->second;
442                     descended = true;
443                 }
444             }
445         }
446     } while (descended);
447
448     return o;
449 }
450
451 XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(NULL)
452 {
453 #ifdef _DEBUG
454     xmltooling::NDC ndc("XMLRequestMapperImpl");
455 #endif
456
457     // Load the property set.
458     load(e,NULL,this);
459
460     // Load any AccessControl provider.
461     loadACL(e,log);
462
463     // Loop over the HostRegex elements.
464     const DOMElement* host = XMLHelper::getFirstChildElement(e,HostRegex);
465     for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,HostRegex)) {
466         const XMLCh* n=host->getAttributeNS(NULL,regex);
467         if (!n || !*n) {
468             log.warn("Skipping HostRegex element (%d) with empty regex attribute",i);
469             continue;
470         }
471
472         auto_ptr<Override> o(new Override(host,log,this));
473
474         const XMLCh* flag=host->getAttributeNS(NULL,ignoreCase);
475         try {
476             auto_ptr<RegularExpression> re(
477                 new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
478                 );
479             m_regexps.push_back(make_pair(re.release(), o.release()));
480         }
481         catch (XMLException& ex) {
482             auto_ptr_char tmp(ex.getMessage());
483             log.error("caught exception while parsing HostRegex regular expression (%d): %s", i, tmp.get());
484         }
485
486         log.debug("Added <HostRegex> mapping for %s", m_regexps.back().second->getString("regex").second);
487     }
488
489     // Loop over the Host elements.
490     host = XMLHelper::getFirstChildElement(e,Host);
491     for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,Host)) {
492         const XMLCh* n=host->getAttributeNS(NULL,name);
493         if (!n || !*n) {
494             log.warn("Skipping Host element (%d) with empty name attribute",i);
495             continue;
496         }
497
498         Override* o=new Override(host,log,this);
499         pair<bool,const char*> name=o->getString("name");
500         pair<bool,const char*> scheme=o->getString("scheme");
501         pair<bool,const char*> port=o->getString("port");
502
503         char* dup=strdup(name.second);
504         for (char* pch=dup; *pch; pch++)
505             *pch=tolower(*pch);
506         auto_ptr<char> dupwrap(dup);
507
508         if (!scheme.first && port.first) {
509             // No scheme, but a port, so assume http.
510             scheme = pair<bool,const char*>(true,"http");
511         }
512         else if (scheme.first && !port.first) {
513             // Scheme, no port, so default it.
514             // XXX Use getservbyname instead?
515             port.first = true;
516             if (!strcmp(scheme.second,"http"))
517                 port.second = "80";
518             else if (!strcmp(scheme.second,"https"))
519                 port.second = "443";
520             else if (!strcmp(scheme.second,"ftp"))
521                 port.second = "21";
522             else if (!strcmp(scheme.second,"ldap"))
523                 port.second = "389";
524             else if (!strcmp(scheme.second,"ldaps"))
525                 port.second = "636";
526         }
527
528         if (scheme.first) {
529             string url(scheme.second);
530             url=url + "://" + dup;
531
532             // Is this the default port?
533             if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||
534                 (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||
535                 (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||
536                 (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||
537                 (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {
538                 // First store a port-less version.
539                 if (m_map.count(url) || m_extras.count(url)) {
540                     log.warn("Skipping duplicate Host element (%s)",url.c_str());
541                     delete o;
542                     continue;
543                 }
544                 m_map[url]=o;
545                 log.debug("Added <Host> mapping for %s",url.c_str());
546
547                 // Now append the port. We use the extras vector, to avoid double freeing the object later.
548                 url=url + ':' + port.second;
549                 m_extras[url]=o;
550                 log.debug("Added <Host> mapping for %s",url.c_str());
551             }
552             else {
553                 url=url + ':' + port.second;
554                 if (m_map.count(url) || m_extras.count(url)) {
555                     log.warn("Skipping duplicate Host element (%s)",url.c_str());
556                     delete o;
557                     continue;
558                 }
559                 m_map[url]=o;
560                 log.debug("Added <Host> mapping for %s",url.c_str());
561             }
562         }
563         else {
564             // No scheme or port, so we enter dual hosts on http:80 and https:443
565             string url("http://");
566             url = url + dup;
567             if (m_map.count(url) || m_extras.count(url)) {
568                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
569                 delete o;
570                 continue;
571             }
572             m_map[url]=o;
573             log.debug("Added <Host> mapping for %s",url.c_str());
574
575             url = url + ":80";
576             if (m_map.count(url) || m_extras.count(url)) {
577                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
578                 continue;
579             }
580             m_extras[url]=o;
581             log.debug("Added <Host> mapping for %s",url.c_str());
582
583             url = "https://";
584             url = url + dup;
585             if (m_map.count(url) || m_extras.count(url)) {
586                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
587                 continue;
588             }
589             m_extras[url]=o;
590             log.debug("Added <Host> mapping for %s",url.c_str());
591
592             url = url + ":443";
593             if (m_map.count(url) || m_extras.count(url)) {
594                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
595                 continue;
596             }
597             m_extras[url]=o;
598             log.debug("Added <Host> mapping for %s",url.c_str());
599         }
600     }
601 }
602
603 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const HTTPRequest& request) const
604 {
605     const Override* o=NULL;
606     map<string,Override*>::const_iterator i=m_map.find(vhost);
607     if (i!=m_map.end())
608         o=i->second;
609     else {
610         i=m_extras.find(vhost);
611         if (i!=m_extras.end())
612             o=i->second;
613         else {
614             for (vector< pair<RegularExpression*,Override*> >::const_iterator re = m_regexps.begin(); !o && re != m_regexps.end(); ++re) {
615                 if (re->first->matches(vhost))
616                     o=re->second;
617             }
618         }
619     }
620
621     return o ? o->locate(request) : this;
622 }
623
624 pair<bool,DOMElement*> XMLRequestMapper::load()
625 {
626     // Load from source using base class.
627     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
628
629     // If we own it, wrap it.
630     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);
631
632     XMLRequestMapperImpl* impl = new XMLRequestMapperImpl(raw.second,m_log);
633
634     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
635     impl->setDocument(docjanitor.release());
636
637     delete m_impl;
638     m_impl = impl;
639
640     return make_pair(false,(DOMElement*)NULL);
641 }
642
643 RequestMapper::Settings XMLRequestMapper::getSettings(const HTTPRequest& request) const
644 {
645     ostringstream vhost;
646     vhost << request.getScheme() << "://" << request.getHostname() << ':' << request.getPort();
647     const Override* o=m_impl->findOverride(vhost.str().c_str(), request);
648     return Settings(o,o->getAC());
649 }