Make init/term reentrant.
[shibboleth/cpp-sp-resolver.git] / src / shibresolver / resolver.cpp
1 /**
2  * See the NOTICE file distributed with this work for information
3  * regarding copyright ownership. Licensed under the Apache License,
4  * Version 2.0 (the "License"); you may not use this file except in
5  * compliance with the License. You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 /**
17  * resolver.cpp
18  *
19  * An embeddable component interface to Shibboleth SP attribute processing.
20  */
21
22 #include "internal.h"
23
24 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
25 # ifdef SHIBRESOLVER_HAVE_GSSMIT
26 #  include <gssapi/gssapi_ext.h>
27 # endif
28 #endif
29
30 #include <shibsp/exceptions.h>
31 #include <shibsp/Application.h>
32 #include <shibsp/GSSRequest.h>
33 #include <shibsp/SPRequest.h>
34 #include <shibsp/ServiceProvider.h>
35 #include <shibsp/attribute/Attribute.h>
36 #include <shibsp/remoting/ListenerService.h>
37 #ifndef SHIBSP_LITE
38 # include <saml/saml2/metadata/Metadata.h>
39 # include <saml/saml2/metadata/MetadataProvider.h>
40 # include <saml/util/SAMLConstants.h>
41 # include <shibsp/attribute/filtering/AttributeFilter.h>
42 # include <shibsp/attribute/filtering/BasicFilteringContext.h>
43 # include <shibsp/attribute/resolver/AttributeExtractor.h>
44 # include <shibsp/attribute/resolver/AttributeResolver.h>
45 # include <shibsp/attribute/resolver/ResolutionContext.h>
46 # include <shibsp/metadata/MetadataProviderCriteria.h>
47 #endif
48 #include <xmltooling/XMLObjectBuilder.h>
49 #include <xmltooling/XMLToolingConfig.h>
50 #include <xmltooling/impl/AnyElement.h>
51 #include <xmltooling/util/ParserPool.h>
52 #include <xmltooling/util/Threads.h>
53 #include <xmltooling/util/XMLHelper.h>
54 #include <xercesc/util/Base64.hpp>
55
56 using namespace shibresolver;
57 using namespace shibsp;
58 #ifndef SHIBSP_LITE
59 using namespace opensaml;
60 using namespace opensaml::saml2md;
61 #endif
62 using namespace xmltooling;
63 using namespace std;
64
65 namespace shibresolver {
66     class SHIBRESOLVER_DLLLOCAL RemotedResolver : public Remoted {
67     public:
68         RemotedResolver() {}
69         ~RemotedResolver() {}
70
71         struct Transaction {
72             ~Transaction() {
73                 for_each(tokens.begin(), tokens.end(), xmltooling::cleanup<XMLObject>());
74                 for_each(inputAttrs.begin(), inputAttrs.end(), xmltooling::cleanup<Attribute>());
75                 for_each(resolvedAttrs.begin(), resolvedAttrs.end(), xmltooling::cleanup<Attribute>());
76             }
77
78             vector<const XMLObject*> tokens;
79             vector<Attribute*> inputAttrs;
80             vector<Attribute*> resolvedAttrs;
81         };
82
83         void receive(DDF& in, ostream& out);
84         void resolve(
85             const Application& app,
86             const char* issuer,
87             const XMLCh* protocol,
88             const vector<const XMLObject*>& tokens,
89             const vector<Attribute*>& inputAttrs,
90             vector<Attribute*>& resolvedAttrs
91             ) const;
92
93     private:
94 #ifndef SHIBSP_LITE
95         void resolve(
96             AttributeExtractor* extractor,
97             const Application& app,
98             const RoleDescriptor* issuer,
99             const XMLObject& token,
100             vector<Attribute*>& resolvedAttrs
101             ) const;
102
103         const RoleDescriptor* lookup(
104             const Application& app,
105             MetadataProvider* m,
106             const char* entityID,
107             const XMLCh* protocol
108             ) const;
109 #endif
110     };
111
112     static RemotedResolver g_Remoted;
113
114     static int g_initCount = 0;
115     static auto_ptr<Mutex> g_lock(Mutex::create());
116 };
117
118 ShibbolethResolver* ShibbolethResolver::create()
119 {
120     return new ShibbolethResolver();
121 }
122
123 ShibbolethResolver::ShibbolethResolver() : m_request(NULL), m_sp(NULL)
124 #ifdef SHIBRESOLVER_HAVE_GSSAPI
125         ,m_gsswrapper(NULL)
126 #endif
127 {
128 }
129
130 ShibbolethResolver::~ShibbolethResolver()
131 {
132 #ifdef SHIBRESOLVER_HAVE_GSSAPI
133     delete m_gsswrapper;
134 #endif
135     for_each(m_resolvedAttributes.begin(), m_resolvedAttributes.end(), xmltooling::cleanup<Attribute>());
136     if (m_sp)
137         m_sp->unlock();
138 }
139
140 void ShibbolethResolver::setRequest(const SPRequest* request)
141 {
142     m_request = request;
143 #if defined(SHIBSP_HAVE_GSSAPI) && defined (SHIBRESOLVER_HAVE_GSSAPI)
144     if (request) {
145         const GSSRequest* gss = dynamic_cast<const GSSRequest*>(request);
146         if (gss) {
147             // TODO: fix API to prevent destruction of contexts
148             gss_ctx_id_t ctx = gss->getGSSContext();
149             addToken(&ctx);
150         }
151     }
152 #endif
153 }
154
155 void ShibbolethResolver::setApplicationID(const char* appID)
156 {
157     m_appID.erase();
158     if (appID)
159         m_appID = appID;
160 }
161
162 void ShibbolethResolver::setIssuer(const char* issuer)
163 {
164     m_issuer.erase();
165     if (issuer)
166         m_issuer = issuer;
167 }
168
169 void ShibbolethResolver::setProtocol(const XMLCh* protocol)
170 {
171     m_protocol.erase();
172     if (protocol)
173         m_protocol = protocol;
174 }
175
176 void ShibbolethResolver::addToken(const XMLObject* token)
177 {
178     if (token)
179         m_tokens.push_back(token);
180 }
181
182 #ifdef SHIBRESOLVER_HAVE_GSSAPI
183 void ShibbolethResolver::addToken(gss_ctx_id_t* ctx)
184 {
185     if (m_gsswrapper) {
186         delete m_gsswrapper;
187         m_gsswrapper = NULL;
188     }
189
190     if (ctx && *ctx != GSS_C_NO_CONTEXT) {
191         OM_uint32 minor;
192         gss_buffer_desc contextbuf = GSS_C_EMPTY_BUFFER;
193         OM_uint32 major = gss_export_sec_context(&minor, ctx, &contextbuf);
194         if (major == GSS_S_COMPLETE) {
195             xsecsize_t len=0;
196             XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf.value), contextbuf.length, &len);
197             if (out) {
198                 string s;
199                 s.append(reinterpret_cast<char*>(out), len);
200                 auto_ptr_XMLCh temp(s.c_str());
201 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
202                 XMLString::release(&out);
203 #else
204                 XMLString::release((char**)&out);
205 #endif
206                 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_13(G,S,S,A,P,I,C,o,n,t,e,x,t);
207                 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
208                 m_gsswrapper->setTextContent(temp.get());
209             }
210             else {
211                 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS context");
212             }
213             gss_release_buffer(&minor, &contextbuf);
214         }
215         else {
216             Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS context");
217         }
218     }
219 }
220
221 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
222 void ShibbolethResolver::addToken(gss_name_t name)
223 {
224     if (m_gsswrapper) {
225         delete m_gsswrapper;
226         m_gsswrapper = NULL;
227     }
228
229     OM_uint32 minor;
230     gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
231     OM_uint32 major = gss_export_name_composite(&minor, name, &namebuf);
232     if (major == GSS_S_COMPLETE) {
233         addToken(&namebuf);
234         gss_release_buffer(&minor, &namebuf);
235     }
236     else {
237         Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS name");
238     }
239 }
240 #endif
241
242 void ShibbolethResolver::addToken(const gss_buffer_t contextbuf)
243 {
244     if (m_gsswrapper) {
245         delete m_gsswrapper;
246         m_gsswrapper = NULL;
247     }
248
249     xsecsize_t len=0;
250     XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf->value), contextbuf->length, &len);
251     if (out) {
252         string s;
253         s.append(reinterpret_cast<char*>(out), len);
254         auto_ptr_XMLCh temp(s.c_str());
255 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
256         XMLString::release(&out);
257 #else
258         XMLString::release((char**)&out);
259 #endif
260         static const XMLCh _GSSAPI[] = UNICODE_LITERAL_10(G,S,S,A,P,I,N,a,m,e);
261         m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
262         m_gsswrapper->setTextContent(temp.get());
263     }
264     else {
265         Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS name");
266     }
267 }
268
269 #endif
270
271 void ShibbolethResolver::addAttribute(Attribute* attr)
272 {
273     if (attr)
274         m_inputAttributes.push_back(attr);
275 }
276
277 vector<Attribute*>& ShibbolethResolver::getResolvedAttributes()
278 {
279     return m_resolvedAttributes;
280 }
281
282 RequestMapper::Settings ShibbolethResolver::getSettings() const
283 {
284     if (!m_request)
285         throw ConfigurationException("Request settings not available without supplying SPRequest instance.");
286     return m_request->getRequestSettings();
287 }
288
289 void ShibbolethResolver::resolve()
290 {
291     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
292     SPConfig& conf = SPConfig::getConfig();
293     if (!m_request) {
294         m_sp = conf.getServiceProvider();
295         m_sp->lock();
296         if (m_appID.empty())
297             m_appID = "default";
298     }
299
300     const Application* app = m_request ? &(m_request->getApplication()) : m_sp->getApplication(m_appID.c_str());
301     if (!app)
302         throw ConfigurationException("Unable to locate application for resolution.");
303
304 #ifdef SHIBRESOLVER_HAVE_GSSAPI
305     if (m_gsswrapper)
306         m_tokens.push_back(m_gsswrapper);
307 #endif
308
309     if (conf.isEnabled(SPConfig::OutOfProcess)) {
310         g_Remoted.resolve(
311             *app,
312             m_issuer.c_str(),
313             m_protocol.c_str(),
314             m_tokens,
315             m_inputAttributes,
316             m_resolvedAttributes
317             );
318     }
319     else {
320         // When not out of process, we remote all the message processing.
321         DDF out,in = DDF("org.project-moonshot.shibresolver");
322         DDFJanitor jin(in), jout(out);
323         in.addmember("application_id").string(app->getId());
324         if (!m_issuer.empty())
325             in.addmember("issuer").string(m_issuer.c_str());
326         if (!m_protocol.empty()) {
327             auto_ptr_char prot(m_protocol.c_str());
328             in.addmember("protocol").string(prot.get());
329         }
330         if (!m_tokens.empty()) {
331             DDF& tokens = in.addmember("tokens").list();
332             for (vector<const XMLObject*>::const_iterator t = m_tokens.begin(); t != m_tokens.end(); ++t) {
333                 ostringstream os;
334                 os << *(*t);
335                 tokens.add(DDF(NULL).string(os.str().c_str()));
336             }
337         }
338         if (!m_inputAttributes.empty()) {
339             DDF attr;
340             DDF& attrs = in.addmember("attributes").list();
341             for (vector<Attribute*>::const_iterator a = m_inputAttributes.begin(); a != m_inputAttributes.end(); ++a) {
342                 attr = (*a)->marshall();
343                 attrs.add(attr);
344             }
345         }
346
347         out = (m_request ? m_request->getServiceProvider() : (*m_sp)).getListenerService()->send(in);
348
349         Attribute* attribute;
350         DDF attr = out.first();
351         while (!attr.isnull()) {
352             try {
353                 attribute = Attribute::unmarshall(attr);
354                 m_resolvedAttributes.push_back(attribute);
355                 if (log.isDebugEnabled())
356                     log.debug("unmarshalled attribute (ID: %s) with %d value%s",
357                         attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
358             }
359             catch (AttributeException& ex) {
360                 const char* id = attr.first().name();
361                 log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
362             }
363             attr = out.next();
364         }
365     }
366 }
367
368 void RemotedResolver::receive(DDF& in, ostream& out)
369 {
370     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
371
372     // Find application.
373     const char* aid = in["application_id"].string();
374     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
375     if (!app) {
376         // Something's horribly wrong.
377         log.error("couldn't find application (%s) for resolution", aid ? aid : "(missing)");
378         throw ConfigurationException("Unable to locate application for resolution, deleted?");
379     }
380
381     DDF ret(NULL);
382     DDFJanitor jout(ret);
383
384     Transaction t;
385
386     DDF tlist = in["tokens"];
387     DDF token = tlist.first();
388     while (token.isstring()) {
389         // Parse and bind the document into an XMLObject.
390         istringstream instr(token.string());
391         DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
392         XercesJanitor<DOMDocument> janitor(doc);
393         XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
394         t.tokens.push_back(xmlObject);
395         janitor.release();
396         token = tlist.next();
397     }
398
399     DDF alist = in["attributes"];
400     Attribute* attribute;
401     DDF attr = alist.first();
402     while (!attr.isnull()) {
403         attribute = Attribute::unmarshall(attr);
404         t.inputAttrs.push_back(attribute);
405         if (log.isDebugEnabled())
406             log.debug("unmarshalled attribute (ID: %s) with %d value%s",
407                 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
408         attr = alist.next();
409     }
410
411     auto_ptr_XMLCh prot(in["protocol"].string());
412
413     resolve(*app, in["issuer"].string(), prot.get(), t.tokens, t.inputAttrs, t.resolvedAttrs);
414
415     if (!t.resolvedAttrs.empty()) {
416         ret.list();
417         for (vector<Attribute*>::const_iterator a = t.resolvedAttrs.begin(); a != t.resolvedAttrs.end(); ++a) {
418             attr = (*a)->marshall();
419             ret.add(attr);
420         }
421     }
422
423     out << ret;
424 }
425
426 void RemotedResolver::resolve(
427     const Application& app,
428     const char* issuer,
429     const XMLCh* protocol,
430     const vector<const XMLObject*>& tokens,
431     const vector<Attribute*>& inputAttrs,
432     vector<Attribute*>& resolvedAttrs
433     ) const
434 {
435 #ifndef SHIBSP_LITE
436     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
437     MetadataProvider* m = app.getMetadataProvider(false);
438     Locker locker(m);
439
440     const RoleDescriptor* role = NULL;
441     if (issuer && *issuer)
442         role = lookup(app, m, issuer, protocol);
443
444     vector<const Assertion*> assertions;
445
446     AttributeExtractor* extractor = app.getAttributeExtractor();
447     if (extractor) {
448         Locker extlocker(extractor);
449         // Support metadata-based attributes for only the "top-level" issuer.
450         if (role) {
451             pair<bool,const char*> mprefix = app.getString("metadataAttributePrefix");
452             if (mprefix.first) {
453                 log.debug("extracting metadata-derived attributes...");
454                 try {
455                     // We pass NULL for "issuer" because the issuer isn't the one asserting metadata-based attributes.
456                     extractor->extractAttributes(app, NULL, *role, resolvedAttrs);
457                     for (vector<Attribute*>::iterator a = resolvedAttrs.begin(); a != resolvedAttrs.end(); ++a) {
458                         vector<string>& ids = (*a)->getAliases();
459                         for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
460                             *id = mprefix.second + *id;
461                     }
462                 }
463                 catch (exception& ex) {
464                     log.error("caught exception extracting attributes: %s", ex.what());
465                 }
466             }
467         }
468
469         log.debug("extracting pushed attributes...");
470         const RoleDescriptor* role2;
471         for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t != tokens.end(); ++t) {
472             // Save off any assertions for later use by resolver.
473             role2 = NULL;
474             const Assertion* assertion = dynamic_cast<const Assertion*>(*t);
475             if (assertion) {
476                 assertions.push_back(assertion);
477                 const saml2::Assertion* saml2token = dynamic_cast<const saml2::Assertion*>(assertion);
478                 if (saml2token && saml2token->getIssuer() && (saml2token->getIssuer()->getFormat() == NULL ||
479                         XMLString::equals(saml2token->getIssuer()->getFormat(), saml2::NameID::ENTITY))) {
480                     auto_ptr_char tokenissuer(saml2token->getIssuer()->getName());
481                     role2 = lookup(app, m, tokenissuer.get(), protocol);
482                 }
483             }
484             resolve(extractor, app, (role2 ? role2 : role), *(*t), resolvedAttrs);
485         }
486     }
487     else {
488         log.warn("no AttributeExtractor plugin installed, check log during startup");
489     }
490
491     try {
492         AttributeResolver* resolver = app.getAttributeResolver();
493         if (resolver) {
494             log.debug("resolving additional attributes...");
495
496             vector<Attribute*> inputs = inputAttrs;
497             inputs.insert(inputs.end(), resolvedAttrs.begin(), resolvedAttrs.end());
498
499             Locker locker(resolver);
500             auto_ptr<ResolutionContext> ctx(
501                 resolver->createResolutionContext(
502                     app,
503                     role ? dynamic_cast<const EntityDescriptor*>(role->getParent()) : NULL,
504                     protocol ? protocol : samlconstants::SAML20P_NS,
505                     NULL,
506                     NULL,
507                     NULL,
508                     &assertions,
509                     &inputs
510                     )
511                 );
512             resolver->resolveAttributes(*ctx.get());
513             if (!ctx->getResolvedAttributes().empty())
514                 resolvedAttrs.insert(resolvedAttrs.end(), ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end());
515         }
516     }
517     catch (exception& ex) {
518         log.error("attribute resolution failed: %s", ex.what());
519     }
520 #else
521     throw ConfigurationException("Cannot process request using lite version of shibsp library.");
522 #endif
523 }
524
525 #ifndef SHIBSP_LITE
526
527 void RemotedResolver::resolve(
528     AttributeExtractor* extractor,
529     const Application& app,
530     const RoleDescriptor* issuer,
531     const XMLObject& token,
532     vector<Attribute*>& resolvedAttrs
533     ) const
534 {
535     vector<Attribute*> extractedAttrs;
536     try {
537         extractor->extractAttributes(app, issuer, token, extractedAttrs);
538     }
539     catch (exception& ex) {
540         Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception extracting attributes: %s", ex.what());
541     }
542
543     AttributeFilter* filter = app.getAttributeFilter();
544     if (filter && !extractedAttrs.empty()) {
545         BasicFilteringContext fc(app, extractedAttrs, issuer);
546         Locker filtlocker(filter);
547         try {
548             filter->filterAttributes(fc, extractedAttrs);
549         }
550         catch (exception& ex) {
551             Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception filtering attributes: %s", ex.what());
552             Category::getInstance(SHIBRESOLVER_LOGCAT).error("dumping extracted attributes due to filtering exception");
553             for_each(extractedAttrs.begin(), extractedAttrs.end(), xmltooling::cleanup<shibsp::Attribute>());
554             extractedAttrs.clear();
555         }
556     }
557
558     resolvedAttrs.insert(resolvedAttrs.end(), extractedAttrs.begin(), extractedAttrs.end());
559 }
560
561 const RoleDescriptor* RemotedResolver::lookup(
562     const Application& app, MetadataProvider* m, const char* entityID, const XMLCh* protocol
563     ) const
564 {
565     if (!m)
566         return NULL;
567
568     MetadataProviderCriteria idpmc(app, entityID, &IDPSSODescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS);
569     if (protocol)
570         idpmc.protocol2 = samlconstants::SAML20P_NS;
571     pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(idpmc);
572     if (!entity.first) {
573         Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate metadata for provider (%s)", entityID);
574     }
575     else if (!entity.second) {
576         MetadataProviderCriteria aamc(
577             app, entityID, &AttributeAuthorityDescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS
578             );
579         if (protocol)
580             aamc.protocol2 = samlconstants::SAML20P_NS;
581         entity = m->getEntityDescriptor(aamc);
582         if (!entity.second) {
583             Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate compatible IdP or AA role for provider (%s)", entityID);
584         }
585     }
586
587     return entity.second;
588 }
589
590 #endif
591
592 bool ShibbolethResolver::init(unsigned long features, const char* config, bool rethrow)
593 {
594     Lock initLock(g_lock.get());
595
596     if (g_initCount == INT_MAX) {
597         Category::getInstance(SHIBRESOLVER_LOGCAT".Config").crit("library initialized too many times");
598         return false;
599     }
600
601     if (g_initCount >= 1) {
602         ++g_initCount;
603         return true;
604     }
605
606     if (features & SPConfig::OutOfProcess) {
607 #ifndef SHIBSP_LITE
608         features = features | SPConfig::AttributeResolution | SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials;
609 #endif
610         if (!(features & SPConfig::InProcess))
611             features |= SPConfig::Listener;
612     }
613     else if (features & SPConfig::InProcess) {
614         features |= SPConfig::Listener;
615     }
616     SPConfig::getConfig().setFeatures(features);
617     if (!SPConfig::getConfig().init())
618         return false;
619     if (!SPConfig::getConfig().instantiate(config, rethrow))
620         return false;
621
622     ++g_initCount;
623     return true;
624 }
625
626 void ShibbolethResolver::term()
627 {
628     Lock initLock(g_lock.get());
629     if (g_initCount == 0) {
630         Category::getInstance(SHIBRESOLVER_LOGCAT".Config").crit("term without corresponding init");
631         return;
632     }
633     else if (--g_initCount > 0) {
634         return;
635     }
636
637     SPConfig::getConfig().term();
638 }
639
640
641 extern "C" int SHIBRESOLVER_EXPORTS xmltooling_extension_init(void*)
642 {
643 #ifdef SHIBRESOLVER_SHIBSP_HAS_REMOTING
644     SPConfig& conf = SPConfig::getConfig();
645     if (conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess) && conf.isEnabled(SPConfig::Listener))
646         conf.getServiceProvider()->regListener("org.project-moonshot.shibresolver", &g_Remoted);
647 #endif
648     return 0;   // signal success
649 }
650
651 extern "C" void SHIBRESOLVER_EXPORTS xmltooling_extension_term()
652 {
653     // Factories normally get unregistered during library shutdown, so no work usually required here.
654 }