Redesigned target around URL->application mapping
[shibboleth/sp.git] / shib-target / XMLApplicationMapper.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 /* XMLApplicationMapper.cpp - an XML-based config file for mapping URLs to application names
51
52    Scott Cantor
53    1/6/04
54
55    $History:$
56 */
57
58 #include "internal.h"
59
60 #include <sys/types.h>
61 #include <sys/stat.h>
62
63 #include <log4cpp/Category.hh>
64 #include <xercesc/framework/URLInputSource.hpp>
65
66 namespace shibtarget {
67
68     class XMLApplicationMapperImpl : public ReloadableXMLFileImpl
69     {
70     public:
71         XMLApplicationMapperImpl(const char* pathname);
72         ~XMLApplicationMapperImpl();
73     
74         struct Override
75         {
76             Override(const XMLCh* AppID) : m_XMLChAppID((AppID && *AppID) ? AppID : NULL),
77                 m_AppID((AppID && *AppID) ? AppID : NULL) {}
78             ~Override();
79             const Override* locate(const char* path) const;
80             auto_ptr_char m_AppID;
81             const XMLCh* m_XMLChAppID;
82             map<string,Override*> m_map;
83         };
84         
85         const Override* findOverride(const char* vhost, const char* path) const;
86     
87         map<string,Override*> m_map;
88         map<string,Override*> m_extras;
89         
90         Category* log;
91     
92     private:
93         Override* buildOverride(const XMLCh* appID, DOMElement* root, Category& log);
94     };
95 }
96
97 ReloadableXMLFileImpl* XMLApplicationMapper::newImplementation(const char* pathname) const
98 {
99     return new XMLApplicationMapperImpl(pathname);
100 }
101
102 XMLApplicationMapperImpl::Override::~Override()
103 {
104     for (map<string,Override*>::iterator i=m_map.begin(); i!=m_map.end(); i++)
105         delete i->second;
106 }
107
108 XMLApplicationMapperImpl::XMLApplicationMapperImpl(const char* pathname) : ReloadableXMLFileImpl(pathname)
109 {
110     NDC ndc("XMLApplicationMapperImpl");
111     log=&Category::getInstance("shibtarget.XMLApplicationMapper");
112
113     try
114     {
115         DOMElement* e = m_doc->getDocumentElement();
116         if (XMLString::compareString(shibtarget::XML::APPMAP_NS,e->getNamespaceURI()) ||
117             XMLString::compareString(shibtarget::XML::Literals::ApplicationMap,e->getLocalName()))
118         {
119             log->error("Construction requires a valid app mapping file: (appmap:ApplicationMap as root element)");
120             throw MetadataException("Construction requires a valid app mapping file: (appmap:ApplicationMap as root element)");
121         }
122         
123         // Loop over the Host elements.
124         DOMNodeList* nlist = e->getElementsByTagNameNS(shibtarget::XML::APPMAP_NS,shibtarget::XML::Literals::Host);
125         for (int i=0; nlist && i<nlist->getLength(); i++)
126         {
127             DOMElement* host=static_cast<DOMElement*>(nlist->item(i));
128             const XMLCh* scheme=host->getAttributeNS(NULL,shibtarget::XML::Literals::Scheme);
129             const XMLCh* port=host->getAttributeNS(NULL,shibtarget::XML::Literals::Port);
130             auto_ptr_XMLCh name(host->getAttributeNS(NULL,shibboleth::XML::Literals::Name));
131
132             if (!name.get() || !*(name.get()))
133             {
134                 log->warn("Skipping Host element (%d) with empty Name attribute",i);
135                 continue;
136             }
137             XMLString::lowerCase(const_cast<XMLCh*>(name.get()));
138
139             Override* o=buildOverride(host->getAttributeNS(NULL,shibtarget::XML::Literals::ApplicationID),host,*log);
140
141             auto_ptr_char s(scheme);
142             auto_ptr_char n(name.get());
143             auto_ptr_char p(port);
144             string url((s.get() && *(s.get())) ? s.get() : "http");
145             url=url + "://" + n.get();
146             if (p.get()==NULL || *(p.get())=='\0')
147             {
148                 // First store a port-less version.
149                 if (m_map.count(url))
150                 {
151                     log->warn("Skipping duplicate Host element (%s)",url.c_str());
152                     delete o;
153                     continue;
154                 }
155                 m_map[url]=o;
156                 
157                 // Now append the default port.
158                 // XXX Use getservbyname instead?
159                 if (s.get()==NULL || !strcmp(s.get(),"http"))
160                     url=url + ":80";
161                 else if (!strcmp(s.get(),"https"))
162                     url=url + ":443";
163                 else if (!strcmp(s.get(),"ftp"))
164                     url=url + ":21";
165                 else if (!strcmp(s.get(),"ldap"))
166                     url=url + ":389";
167                 else if (!strcmp(s.get(),"ldaps"))
168                     url=url + ":636";
169                 
170                 m_extras[url]=o;
171             }
172             else
173             {
174                 url=url + ':' + p.get();
175                 if (m_map.count(url))
176                 {
177                     log->warn("Skipping duplicate Host element (%s)",url.c_str());
178                     delete o;
179                     continue;
180                 }
181                 m_map[url]=o;
182             }
183             log->debug("Added <Host> mapping for %s",url.c_str());
184         }
185     }
186     catch (SAMLException& e)
187     {
188         log->errorStream() << "Error while parsing app mapping configuration: " << e.what() << CategoryStream::ENDLINE;
189         for (map<string,Override*>::iterator i=m_map.begin(); i!=m_map.end(); i++)
190             delete i->second;
191         if (m_doc)
192             m_doc->release();
193         throw;
194     }
195 #ifndef _DEBUG
196     catch (...)
197     {
198         log->error("Unexpected error while parsing app mapping configuration");
199         for (map<string,Override*>::iterator i=m_map.begin(); i!=m_map.end(); i++)
200             delete i->second;
201         if (m_doc)
202             m_doc->release();
203         throw;
204     }
205 #endif
206 }
207
208 XMLApplicationMapperImpl::~XMLApplicationMapperImpl()
209 {
210     for (map<string,Override*>::iterator i=m_map.begin(); i!=m_map.end(); i++)
211         delete i->second;
212 }
213
214 XMLApplicationMapperImpl::Override* XMLApplicationMapperImpl::buildOverride(const XMLCh* appID, DOMElement* root, Category& log)
215 {
216     Override* o=new Override(appID);
217     DOMNodeList* nlist = root->getElementsByTagNameNS(shibtarget::XML::APPMAP_NS,shibtarget::XML::Literals::Path);
218     for (int i=0; nlist && i<nlist->getLength(); i++)
219     {
220         DOMElement* path=static_cast<DOMElement*>(nlist->item(i));
221         auto_ptr_XMLCh name(path->getAttributeNS(NULL,shibboleth::XML::Literals::Name));
222         if (!name.get() || !*(name.get()))
223         {
224             log.warn("Skipping Path element (%d) with empty Name attribute",i);
225             continue;
226         }
227         XMLString::lowerCase(const_cast<XMLCh*>(name.get()));
228         
229         auto_ptr_char n(name.get());
230         o->m_map[n.get()]=buildOverride(path->getAttributeNS(NULL,shibtarget::XML::Literals::ApplicationID),path,log);
231     }
232     return o;
233 }
234
235 const XMLApplicationMapperImpl::Override* XMLApplicationMapperImpl::findOverride(const char* vhost, const char* path) const
236 {
237     const Override* o=NULL;
238     map<string,Override*>::const_iterator i=m_map.find(vhost);
239     if (i!=m_map.end())
240         o=i->second;
241     else
242     {
243         i=m_extras.find(vhost);
244         if (i!=m_extras.end())
245             o=i->second;
246     }
247     
248     if (o)
249     {
250         const Override* o2=o->locate(path);
251         if (o2)
252             return o2;
253     }
254     return o;
255 }
256
257 const XMLApplicationMapperImpl::Override* XMLApplicationMapperImpl::Override::locate(const char* path) const
258 {
259     char* dup=strdup(path);
260     char* sep=strchr(path,'?');
261     if (sep)
262         *sep=0;
263     for (char* pch=dup; *pch; pch++)
264         *pch=tolower(*pch);
265         
266     
267     const Override* o=this;
268     const Override* specifier=((m_XMLChAppID && *m_XMLChAppID) ? this : NULL);
269     
270 #ifdef HAVE_STRTOK_R
271     char* pos=NULL;
272     const char* token=strtok_r(dup,"/",&pos);
273 #else
274     const char* token=strtok(dup,"/");
275 #endif
276     while (token)
277     {
278         map<string,Override*>::const_iterator i=o->m_map.find(token);
279         if (i==o->m_map.end())
280             break;
281         o=i->second;
282         if (o->m_XMLChAppID && *(o->m_XMLChAppID))
283             specifier=o;
284 #ifdef HAVE_STRTOK_R
285         token=strtok_r(NULL,"/",&pos);
286 #else
287         token=strtok(NULL,"/");
288 #endif
289     }
290
291     free(dup);
292     return specifier;
293 }
294
295 const char* split_url(const char* url, string& vhost)
296 {
297     const char* path=NULL;
298     char* slash=strchr(url,'/');
299     if (slash)
300     {
301         slash=strchr(slash,'/');
302         if (slash)
303         {
304             path=strchr(slash,'/');
305             if (path)
306                 vhost.append(url,path-url);
307             else
308                 vhost=url;
309         }
310     }
311     return path;
312 }
313
314 const char* XMLApplicationMapper::getApplicationFromURL(const char* url) const
315 {
316     string vhost;
317     const char* path=split_url(url,vhost);
318
319     XMLApplicationMapperImpl* impl=dynamic_cast<XMLApplicationMapperImpl*>(getImplementation());
320     const XMLApplicationMapperImpl::Override* o=impl->findOverride(vhost.c_str(), path);
321
322     if (impl->log->isDebugEnabled())
323     {
324         saml::NDC ndc("getApplicationFromURL");
325         impl->log->debug("mapped %s to %s", url, o ? o->m_AppID.get() : "default application ID");
326     }
327
328     return o ? o->m_AppID.get() : "";
329 }
330
331 const XMLCh* XMLApplicationMapper::getXMLChApplicationFromURL(const char* url) const
332 {
333     string vhost;
334     const char* path=split_url(url,vhost);
335
336     XMLApplicationMapperImpl* impl=dynamic_cast<XMLApplicationMapperImpl*>(getImplementation());
337     const XMLApplicationMapperImpl::Override* o=impl->findOverride(vhost.c_str(), path);
338
339     if (impl->log->isDebugEnabled())
340     {
341         saml::NDC ndc("getXMLChApplicationFromURL");
342         impl->log->debug("mapped %s to %s", url, o ? o->m_AppID.get() : "default application ID");
343     }
344
345     return o ? o->m_XMLChAppID : &chNull;
346 }
347
348 const char* XMLApplicationMapper::getApplicationFromParsedURL(
349     const char* scheme, const char* hostname, unsigned int port, const char* path
350     ) const
351 {
352     char buf[21];
353     string vhost(scheme);
354     vhost=vhost + "://" + hostname + ':';
355 #ifdef WIN32
356     _snprintf(buf,20,"%u",port);
357 #else
358     snprintf(buf,20,"%u",port);
359 #endif
360     vhost+=buf;
361
362     XMLApplicationMapperImpl* impl=dynamic_cast<XMLApplicationMapperImpl*>(getImplementation());
363     const XMLApplicationMapperImpl::Override* o=impl->findOverride(vhost.c_str(), path);
364
365     if (impl->log->isDebugEnabled())
366     {
367         saml::NDC ndc("getApplicationFromParsedURL");
368         impl->log->debug("mapped %s%s to %s", vhost.c_str(), path ? path : "", o ? o->m_AppID.get() : "default application ID");
369     }
370
371     return o ? o->m_AppID.get() : "";
372 }
373
374 const XMLCh* XMLApplicationMapper::getXMLChApplicationFromParsedURL(
375     const char* scheme, const char* hostname, unsigned int port, const char* path
376     ) const
377 {
378     char buf[21];
379     string vhost(scheme);
380     vhost=vhost + "://" + hostname + ':';
381 #ifdef WIN32
382     _snprintf(buf,20,"%u",port);
383 #else
384     snprintf(buf,20,"%u",port);
385 #endif
386     vhost+=buf;
387
388     XMLApplicationMapperImpl* impl=dynamic_cast<XMLApplicationMapperImpl*>(getImplementation());
389     const XMLApplicationMapperImpl::Override* o=impl->findOverride(vhost.c_str(), path);
390
391     if (impl->log->isDebugEnabled())
392     {
393         saml::NDC ndc("getXMLChApplicationFromParsedURL");
394         impl->log->debug("mapped %s%s to %s", vhost.c_str(), path ? path : "", o ? o->m_AppID.get() : "default application ID");
395     }
396
397     return o ? o->m_XMLChAppID : &chNull;
398 }