https://issues.shibboleth.net/jira/browse/SSPCPP-255
[shibboleth/cpp-sp.git] / nsapi_shib / nsapi_shib.cpp
1 /*
2  *  Copyright 2001-2009 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 /* nsapi_shib.cpp - Shibboleth NSAPI filter
18
19    Scott Cantor
20    12/13/04
21 */
22
23 #if defined (_MSC_VER) || defined(__BORLANDC__)
24 # include "config_win32.h"
25 #else
26 # include "config.h"
27 #endif
28
29
30 // SAML Runtime
31 #include <saml/saml.h>
32 #include <shib/shib.h>
33 #include <shib/shib-threads.h>
34 #include <shib-target/shib-target.h>
35
36 #include <ctime>
37 #include <fstream>
38 #include <sstream>
39 #include <stdexcept>
40
41 #ifdef WIN32
42 # include <process.h>
43 # define XP_WIN32
44 #else
45 # define XP_UNIX
46 #endif
47
48 #define MCC_HTTPD
49 #define NET_SSL
50
51 extern "C"
52 {
53 #include <nsapi.h>
54 }
55
56 using namespace std;
57 using namespace saml;
58 using namespace shibboleth;
59 using namespace shibtarget;
60
61 // macros to output text to client
62 #define NET_WRITE(str) \
63     if (IO_ERROR==net_write(sn->csd,str,strlen(str))) return REQ_EXIT
64
65 namespace {
66     ShibTargetConfig* g_Config=NULL;
67     string g_ServerName;
68     string g_unsetHeaderValue;
69     set<string> g_allowedSchemes;
70     bool g_checkSpoofing = false;
71     bool g_catchAll = true;
72 }
73
74 PlugManager::Factory SunRequestMapFactory;
75
76 extern "C" NSAPI_PUBLIC void nsapi_shib_exit(void*)
77 {
78     if (g_Config)
79         g_Config->shutdown();
80     g_Config = NULL;
81 }
82
83 extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq)
84 {
85     // Save off a default hostname for this virtual server.
86     char* name=pblock_findval("server-name",pb);
87     if (name)
88         g_ServerName=name;
89     else {
90         name=server_hostname;
91         if (name)
92             g_ServerName=name;
93         else {
94             name=util_hostname();
95             if (name) {
96                 g_ServerName=name;
97                 FREE(name);
98             }
99             else {
100                 pblock_nvinsert("error","unable to determine web server hostname",pb);
101                 return REQ_ABORTED;
102             }
103         }
104     }
105
106     log_error(LOG_INFORM,"nsapi_shib_init",sn,rq,"nsapi_shib loaded for host (%s)",g_ServerName.c_str());
107
108     try {
109         const char* schemadir=pblock_findval("shib-schemas",pb);
110         if (!schemadir)
111             schemadir=getenv("SHIBSCHEMAS");
112         if (!schemadir)
113             schemadir=SHIB_SCHEMAS;
114         const char* config=pblock_findval("shib-config",pb);
115         if (!config)
116             config=getenv("SHIBCONFIG");
117         if (!config)
118             config=SHIB_CONFIG;
119         g_Config=&ShibTargetConfig::getConfig();
120         g_Config->setFeatures(
121             ShibTargetConfig::Listener |
122             ShibTargetConfig::Metadata |
123             ShibTargetConfig::AAP |
124             ShibTargetConfig::RequestMapper |
125             ShibTargetConfig::LocalExtensions |
126             ShibTargetConfig::Logging
127             );
128         if (!g_Config->init(schemadir)) {
129             g_Config=NULL;
130             pblock_nvinsert("error","unable to initialize Shibboleth libraries",pb);
131             return REQ_ABORTED;
132         }
133
134         SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::NativeRequestMapType,&SunRequestMapFactory);
135         // We hijack the legacy type so that 1.2 config files will load this plugin
136         SAMLConfig::getConfig().getPlugMgr().regFactory(shibtarget::XML::LegacyRequestMapType,&SunRequestMapFactory);
137
138         if (!g_Config->load(config)) {
139             g_Config=NULL;
140             pblock_nvinsert("error","unable to initialize load Shibboleth configuration",pb);
141             return REQ_ABORTED;
142         }
143
144         daemon_atrestart(nsapi_shib_exit,NULL);
145
146         IConfig* conf=g_Config->getINI();
147         Locker locker(conf);
148         const IPropertySet* props=conf->getPropertySet("Local");
149         if (props) {
150             pair<bool,const char*> str=props->getString("unsetHeaderValue");
151             if (str.first)
152                 g_unsetHeaderValue = str.second;
153
154             str=props->getString("allowedSchemes");
155             if (str.first) {
156                 string schemes=str.second;
157                 unsigned int j=0;
158                 for (unsigned int i=0;  i < schemes.length();  i++) {
159                     if (schemes.at(i)==' ') {
160                         g_allowedSchemes.insert(schemes.substr(j, i-j));
161                         j = i+1;
162                     }
163                 }
164                 g_allowedSchemes.insert(schemes.substr(j, schemes.length()-j));
165             }
166
167             pair<bool,bool> flag=props->getBool("checkSpoofing");
168             g_checkSpoofing = !flag.first || flag.second;
169             flag=props->getBool("catchAll");
170             g_catchAll = !flag.first || flag.second;
171         }
172         if (g_allowedSchemes.empty()) {
173             g_allowedSchemes.insert("https");
174             g_allowedSchemes.insert("http");
175         }
176     }
177     catch (exception&) {
178         g_Config=NULL;
179         pblock_nvinsert("error","caught exception, unable to initialize Shibboleth libraries",pb);
180         return REQ_ABORTED;
181     }
182     return REQ_PROCEED;
183 }
184
185 /********************************************************************************/
186 // NSAPI Shib Target Subclass
187
188 class ShibTargetNSAPI : public ShibTarget
189 {
190   void checkString(const string& s, const char* msg) {
191     string::const_iterator e = s.end();
192     for (string::const_iterator i=s.begin(); i!=e; ++i) {
193         if (iscntrl(*i))
194             throw FatalProfileException(msg);
195     }
196   }
197
198 public:
199   ShibTargetNSAPI(pblock* pb, Session* sn, Request* rq) : m_pb(pb), m_sn(sn), m_rq(rq), m_firsttime(true) {
200
201       // To determine whether SSL is active or not, we're supposed to rely
202       // on the security_active macro. For iPlanet 4.x, this works.
203       // For Sun 7.x, it's useless and appears to be on or off based
204       // on whether ANY SSL support is enabled for a vhost. Sun 6.x is unknown.
205       // As a fix, there's a conf variable called $security that can be mapped
206       // into a function parameter: security_active="$security"
207       // We check for this parameter, and rely on the macro if it isn't set.
208       // This doubles as a scheme virtualizer for load balanced scenarios
209       // since you can set the parameter to 1 or 0 as needed.
210       const char* scheme;
211       const char* sa = pblock_findval("security_active", pb);
212       if (sa)
213           scheme = (*sa == '1') ? "https" : "http";
214       else if (security_active)
215           scheme = "https";
216       else
217           scheme = "http";
218
219       // A similar issue exists for the port. server_portnum is no longer
220       // working on at least Sun 7.x, and returns the first listener's port
221       // rather than whatever port is actually used for the request. Nice job, Sun.
222       sa = pblock_findval("server_portnum", pb);
223       int port = (sa && *sa) ? atoi(sa) : server_portnum;
224
225     // Get everything else but hostname...
226     const char* uri=pblock_findval("uri", rq->reqpb);
227     const char* qstr=pblock_findval("query", rq->reqpb);
228     const char* host=NULL;
229
230     string url;
231     if (uri)
232         url=uri;
233     if (qstr)
234         url=url + '?' + qstr;
235
236 #ifdef vs_is_default_vs
237     // This is 6.0 or later, so we can distinguish requests to name-based vhosts.
238     if (!vs_is_default_vs(request_get_vs(m_rq)))
239         // The beauty here is, a non-default vhost can *only* be accessed if the client
240         // specified the exact name in the Host header. So we can trust the Host header.
241         host=pblock_findval("host", rq->headers);
242     else
243 #endif
244     // In other cases, we're going to rely on the initialization process...
245     host=g_ServerName.c_str();
246
247     char* content_type = "";
248     request_header("content-type", &content_type, sn, rq);
249
250     const char* remote_ip = pblock_findval("ip", sn->client);
251     const char* method = pblock_findval("method", rq->reqpb);
252
253     init(scheme, host, port, url.c_str(), content_type, remote_ip, method);
254
255     // See if this is the first time we've run.
256     method = pblock_findval("auth-type", rq->vars);
257     if (method && !strcmp(method, "shibboleth"))
258         m_firsttime = false;
259     if (!m_firsttime || rq->orig_rq)
260         log(LogLevelDebug, "nsapi_shib function running more than once");
261   }
262   ~ShibTargetNSAPI() {
263   }
264
265   virtual void log(ShibLogLevel level, const string &msg) {
266     ShibTarget::log(level,msg);
267     if (level==LogLevelError)
268         log_error(LOG_FAILURE, "nsapi_shib", m_sn, m_rq, const_cast<char*>(msg.c_str()));
269   }
270   virtual string getCookies(void) const {
271     char *cookies = NULL;
272     if (request_header("cookie", &cookies, m_sn, m_rq) == REQ_ABORTED)
273       throw("error accessing cookie header");
274     return string(cookies ? cookies : "");
275   }
276   virtual void setCookie(const string &name, const string &value) {
277     string cookie = name + '=' + value;
278     pblock_nvinsert("Set-Cookie", cookie.c_str(), m_rq->srvhdrs);
279   }
280   virtual string getArgs(void) {
281     const char *q = pblock_findval("query", m_rq->reqpb);
282     return string(q ? q : "");
283   }
284   virtual string getPostData(void) {
285     char* content_length=NULL;
286     if (request_header("content-length", &content_length, m_sn, m_rq)!=REQ_PROCEED ||
287          atoi(content_length) > 1024*1024) // 1MB?
288       throw FatalProfileException("Blocked too-large a submission to profile endpoint.");
289     else {
290       char ch=IO_EOF+1;
291       int cl=atoi(content_length);
292       string cgistr;
293       while (cl && ch != IO_EOF) {
294         ch=netbuf_getc(m_sn->inbuf);
295
296         // Check for error.
297         if(ch==IO_ERROR)
298           break;
299         cgistr += ch;
300         cl--;
301       }
302       if (cl)
303         throw FatalProfileException("Error reading profile submission from browser.");
304       return cgistr;
305     }
306   }
307   virtual void clearHeader(const string &name) {
308     if (g_checkSpoofing && m_firsttime && !m_rq->orig_rq && m_allhttp.empty()) {
309       // Populate the set of client-supplied headers for spoof checking.
310       const pb_entry* entry;
311       for (int i=0; i<m_rq->headers->hsize; ++i) {
312           entry = m_rq->headers->ht[i];
313           while (entry) {
314               string cgiversion("HTTP_");
315               const char* pch = entry->param->name;
316               while (*pch) {
317                   cgiversion += (isalnum(*pch) ? toupper(*pch) : '_');
318                   pch++;
319               }
320               m_allhttp.insert(cgiversion);
321               entry = entry->next;
322           }
323       }
324     }
325     if (name=="REMOTE_USER") {
326         if (g_checkSpoofing && m_firsttime && !m_rq->orig_rq && m_allhttp.count("HTTP_REMOTE_USER") > 0)
327             throw SAMLException("Attempt to spoof header ($1) was detected.", params(1, name.c_str()));
328         param_free(pblock_remove("auth-user",m_rq->vars));
329         param_free(pblock_remove("remote-user",m_rq->headers));
330         pblock_nvinsert("remote-user", g_unsetHeaderValue.c_str(), m_rq->headers);
331     }
332     else {
333         if (g_checkSpoofing && m_firsttime && !m_rq->orig_rq) {
334             // Map to the expected CGI variable name.
335             string transformed("HTTP_");
336             const char* pch = name.c_str();
337             while (*pch) {
338                 transformed += (isalnum(*pch) ? toupper(*pch) : '_');
339                 pch++;
340             }
341             if (m_allhttp.count(transformed) > 0)
342                 throw SAMLException("Attempt to spoof header ($1) was detected.", params(1, name.c_str()));
343         }
344         param_free(pblock_remove(name.c_str(), m_rq->headers));
345         pblock_nvinsert(name.c_str(), g_unsetHeaderValue.c_str(), m_rq->headers);
346     }
347   }
348   virtual void setHeader(const string &name, const string &value) {
349     param_free(pblock_remove(name.c_str(), m_rq->headers));
350     pblock_nvinsert(name.c_str(), value.c_str() ,m_rq->headers);
351   }
352   virtual string getHeader(const string &name) {
353     char *hdr = NULL;
354     if (request_header(const_cast<char*>(name.c_str()), &hdr, m_sn, m_rq) != REQ_PROCEED) {
355       string n;
356       const char* pch = name.c_str();
357       while (*pch)
358           n += tolower(*(pch++));
359       if (request_header(const_cast<char*>(n.c_str()), &hdr, m_sn, m_rq) != REQ_PROCEED)
360           return "";
361     }
362     return string(hdr ? hdr : "");
363   }
364   virtual void setRemoteUser(const string &user) {
365     param_free(pblock_remove("remote-user",m_rq->headers));
366     pblock_nvinsert("remote-user", user.c_str(), m_rq->headers);
367     pblock_nvinsert("auth-user", user.c_str(), m_rq->vars);
368   }
369   virtual string getRemoteUser(void) {
370     const char* ru = pblock_findval("auth-user", m_rq->vars);
371     return ru ? ru : "";
372   }
373
374   virtual void* sendPage(
375     const string& msg,
376     int code=200,
377     const string& content_type="text/html",
378     const saml::Iterator<header_t>& headers=EMPTY(header_t)
379     ) {
380     checkString(content_type, "Detected control character in a response header.");
381     param_free(pblock_remove("content-type", m_rq->srvhdrs));
382     pblock_nvinsert("content-type", content_type.c_str(), m_rq->srvhdrs);
383     pblock_nninsert("content-length", msg.length(), m_rq->srvhdrs);
384     pblock_nvinsert("connection","close",m_rq->srvhdrs);
385     while (headers.hasNext()) {
386         const header_t& h=headers.next();
387         checkString(h.first, "Detected control character in a response header.");
388         checkString(h.second, "Detected control character in a response header.");
389         pblock_nvinsert(h.first.c_str(), h.second.c_str(), m_rq->srvhdrs);
390     }
391     protocol_status(m_sn, m_rq, code, NULL);
392     protocol_start_response(m_sn, m_rq);
393     net_write(m_sn->csd,const_cast<char*>(msg.c_str()),msg.length());
394     return (void*)REQ_EXIT;
395   }
396   virtual void* sendRedirect(const string& url) {
397     checkString(url, "Detected control character in an attempted redirect.");
398     if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end())
399         throw FatalProfileException("Invalid scheme in attempted redirect.");
400     param_free(pblock_remove("content-type", m_rq->srvhdrs));
401     pblock_nninsert("content-length", 0, m_rq->srvhdrs);
402     pblock_nvinsert("expires", "01-Jan-1997 12:00:00 GMT", m_rq->srvhdrs);
403     pblock_nvinsert("cache-control", "private,no-store,no-cache", m_rq->srvhdrs);
404     pblock_nvinsert("location", url.c_str(), m_rq->srvhdrs);
405     pblock_nvinsert("connection","close",m_rq->srvhdrs);
406     protocol_status(m_sn, m_rq, PROTOCOL_REDIRECT, NULL);
407     protocol_start_response(m_sn, m_rq);
408     return (void*)REQ_ABORTED;
409   }
410   virtual void* returnDecline(void) { return (void*)REQ_NOACTION; }
411   virtual void* returnOK(void) { return (void*)REQ_PROCEED; }
412
413   pblock* m_pb;
414   Session* m_sn;
415   Request* m_rq;
416   set<string> m_allhttp;
417   bool m_firsttime;
418 };
419
420 /********************************************************************************/
421
422 int WriteClientError(Session* sn, Request* rq, char* func, char* msg)
423 {
424     log_error(LOG_FAILURE,func,sn,rq,msg);
425     protocol_status(sn,rq,PROTOCOL_SERVER_ERROR,msg);
426     return REQ_ABORTED;
427 }
428
429 #undef FUNC
430 #define FUNC "shibboleth"
431 extern "C" NSAPI_PUBLIC int nsapi_shib(pblock* pb, Session* sn, Request* rq)
432 {
433     ostringstream threadid;
434     threadid << "[" << getpid() << "] nsapi_shib" << '\0';
435     saml::NDC ndc(threadid.str().c_str());
436
437     try {
438         ShibTargetNSAPI stn(pb, sn, rq);
439
440         // Check user authentication
441         pair<bool,void*> res = stn.doCheckAuthN();
442         if (res.first) return (int)res.second;
443
444         // user authN was okay -- export the assertions now
445         param_free(pblock_remove("auth-user",rq->vars));
446         // This seems to be required in order to eventually set
447         // the auth-user var.
448         pblock_nvinsert("auth-type","shibboleth",rq->vars);
449         res = stn.doExportAssertions();
450         if (res.first) return (int)res.second;
451
452         // Check the Authorization
453         res = stn.doCheckAuthZ();
454         if (res.first) return (int)res.second;
455
456         // this user is ok.
457         return REQ_PROCEED;
458     }
459     catch (exception& e) {
460         log_error(LOG_FAILURE,FUNC,sn,rq,const_cast<char*>(e.what()));
461         return WriteClientError(sn, rq, FUNC, "Shibboleth filter threw an exception, see web server log for error.");
462     }
463     catch (...) {
464         if (g_catchAll)
465             return WriteClientError(sn, rq, FUNC, "Shibboleth filter threw an uncaught exception.");
466         throw;
467     }
468 }
469
470
471 #undef FUNC
472 #define FUNC "shib_handler"
473 extern "C" NSAPI_PUBLIC int shib_handler(pblock* pb, Session* sn, Request* rq)
474 {
475     ostringstream threadid;
476     threadid << "[" << getpid() << "] shib_handler" << '\0';
477     saml::NDC ndc(threadid.str().c_str());
478
479     try {
480         ShibTargetNSAPI stn(pb, sn, rq);
481
482         pair<bool,void*> res = stn.doHandler();
483         if (res.first) return (int)res.second;
484
485         return WriteClientError(sn, rq, FUNC, "Shibboleth handler did not do anything.");
486     }
487     catch (exception& e) {
488         log_error(LOG_FAILURE,FUNC,sn,rq,const_cast<char*>(e.what()));
489         return WriteClientError(sn, rq, FUNC, "Shibboleth handler threw an exception, see web server log for error.");
490     }
491     catch (...) {
492         if (g_catchAll)
493             return WriteClientError(sn, rq, FUNC, "Shibboleth handler threw an unknown exception.");
494         throw;
495     }
496 }
497
498
499 class SunRequestMapper : public virtual IRequestMapper, public virtual IPropertySet
500 {
501 public:
502     SunRequestMapper(const DOMElement* e);
503     ~SunRequestMapper() { delete m_mapper; delete m_stKey; delete m_propsKey; }
504     void lock() { m_mapper->lock(); }
505     void unlock() { m_stKey->setData(NULL); m_propsKey->setData(NULL); m_mapper->unlock(); }
506     Settings getSettings(ShibTarget* st) const;
507
508     pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
509     pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
510     pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
511     pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;
512     pair<bool,int> getInt(const char* name, const char* ns=NULL) const;
513     const IPropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:target:config:1.0") const;
514     const DOMElement* getElement() const;
515
516 private:
517     IRequestMapper* m_mapper;
518     ThreadKey* m_stKey;
519     ThreadKey* m_propsKey;
520 };
521
522 IPlugIn* SunRequestMapFactory(const DOMElement* e)
523 {
524     return new SunRequestMapper(e);
525 }
526
527 SunRequestMapper::SunRequestMapper(const DOMElement* e) : m_mapper(NULL), m_stKey(NULL), m_propsKey(NULL)
528 {
529     IPlugIn* p=SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::XMLRequestMapType,e);
530     m_mapper=dynamic_cast<IRequestMapper*>(p);
531     if (!m_mapper) {
532         delete p;
533         throw UnsupportedExtensionException("Embedded request mapper plugin was not of correct type.");
534     }
535     m_stKey=ThreadKey::create(NULL);
536     m_propsKey=ThreadKey::create(NULL);
537 }
538
539 IRequestMapper::Settings SunRequestMapper::getSettings(ShibTarget* st) const
540 {
541     Settings s=m_mapper->getSettings(st);
542     m_stKey->setData(dynamic_cast<ShibTargetNSAPI*>(st));
543     m_propsKey->setData((void*)s.first);
544     return pair<const IPropertySet*,IAccessControl*>(this,s.second);
545 }
546
547 pair<bool,bool> SunRequestMapper::getBool(const char* name, const char* ns) const
548 {
549     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
550     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
551     if (stn && !ns && name) {
552         // Override boolean properties.
553         const char* param=pblock_findval(name,stn->m_pb);
554         if (param && (!strcmp(param,"1") || !strcasecmp(param,"true")))
555             return make_pair(true,true);
556     }
557     return s ? s->getBool(name,ns) : make_pair(false,false);
558 }
559
560 pair<bool,const char*> SunRequestMapper::getString(const char* name, const char* ns) const
561 {
562     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
563     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
564     if (stn && !ns && name) {
565         // Override string properties.
566         if (!strcmp(name,"authType"))
567             return pair<bool,const char*>(true,"shibboleth");
568         else {
569             const char* param=pblock_findval(name,stn->m_pb);
570             if (param)
571                 return make_pair(true,param);
572         }
573     }
574     return s ? s->getString(name,ns) : pair<bool,const char*>(false,NULL);
575 }
576
577 pair<bool,const XMLCh*> SunRequestMapper::getXMLString(const char* name, const char* ns) const
578 {
579     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
580     return s ? s->getXMLString(name,ns) : pair<bool,const XMLCh*>(false,NULL);
581 }
582
583 pair<bool,unsigned int> SunRequestMapper::getUnsignedInt(const char* name, const char* ns) const
584 {
585     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
586     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
587     if (stn && !ns && name) {
588         // Override int properties.
589         const char* param=pblock_findval(name,stn->m_pb);
590         if (param)
591             return pair<bool,unsigned int>(true,strtol(param,NULL,10));
592     }
593     return s ? s->getUnsignedInt(name,ns) : pair<bool,unsigned int>(false,0);
594 }
595
596 pair<bool,int> SunRequestMapper::getInt(const char* name, const char* ns) const
597 {
598     ShibTargetNSAPI* stn=reinterpret_cast<ShibTargetNSAPI*>(m_stKey->getData());
599     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
600     if (stn && !ns && name) {
601         // Override int properties.
602         const char* param=pblock_findval(name,stn->m_pb);
603         if (param)
604             return pair<bool,int>(true,atoi(param));
605     }
606     return s ? s->getInt(name,ns) : pair<bool,int>(false,0);
607 }
608
609 const IPropertySet* SunRequestMapper::getPropertySet(const char* name, const char* ns) const
610 {
611     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
612     return s ? s->getPropertySet(name,ns) : NULL;
613 }
614
615 const DOMElement* SunRequestMapper::getElement() const
616 {
617     const IPropertySet* s=reinterpret_cast<const IPropertySet*>(m_propsKey->getData());
618     return s ? s->getElement() : NULL;
619 }