Add name-based API to GSSRequest
[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 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
148             gss_name_t name = gss->getGSSName();
149             if (name != GSS_C_NO_NAME) {
150                 addToken(name);
151                 return;
152             }
153 #endif
154             gss_ctx_id_t ctx = gss->getGSSContext();
155             if (ctx != GSS_C_NO_CONTEXT)
156                 addToken(&ctx);
157         }
158     }
159 #endif
160 }
161
162 void ShibbolethResolver::setApplicationID(const char* appID)
163 {
164     m_appID.erase();
165     if (appID)
166         m_appID = appID;
167 }
168
169 void ShibbolethResolver::setIssuer(const char* issuer)
170 {
171     m_issuer.erase();
172     if (issuer)
173         m_issuer = issuer;
174 }
175
176 void ShibbolethResolver::setProtocol(const XMLCh* protocol)
177 {
178     m_protocol.erase();
179     if (protocol)
180         m_protocol = protocol;
181 }
182
183 void ShibbolethResolver::addToken(const XMLObject* token)
184 {
185     if (token)
186         m_tokens.push_back(token);
187 }
188
189 #ifdef SHIBRESOLVER_HAVE_GSSAPI
190 void ShibbolethResolver::addToken(gss_ctx_id_t* ctx)
191 {
192     if (m_gsswrapper) {
193         delete m_gsswrapper;
194         m_gsswrapper = NULL;
195     }
196
197     if (ctx && *ctx != GSS_C_NO_CONTEXT) {
198         OM_uint32 minor;
199         gss_buffer_desc contextbuf = GSS_C_EMPTY_BUFFER;
200         OM_uint32 major = gss_export_sec_context(&minor, ctx, &contextbuf);
201         if (major == GSS_S_COMPLETE) {
202             xsecsize_t len=0;
203             XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf.value), contextbuf.length, &len);
204             if (out) {
205                 string s;
206                 s.append(reinterpret_cast<char*>(out), len);
207                 auto_ptr_XMLCh temp(s.c_str());
208 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
209                 XMLString::release(&out);
210 #else
211                 XMLString::release((char**)&out);
212 #endif
213                 static const XMLCh _GSSAPI[] = UNICODE_LITERAL_13(G,S,S,A,P,I,C,o,n,t,e,x,t);
214                 m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
215                 m_gsswrapper->setTextContent(temp.get());
216             }
217             else {
218                 Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS context");
219             }
220             gss_release_buffer(&minor, &contextbuf);
221         }
222         else {
223             Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS context");
224         }
225     }
226 }
227
228 #ifdef SHIBRESOLVER_HAVE_GSSAPI_NAMINGEXTS
229 void ShibbolethResolver::addToken(gss_name_t name)
230 {
231     if (m_gsswrapper) {
232         delete m_gsswrapper;
233         m_gsswrapper = NULL;
234     }
235
236     OM_uint32 minor;
237     gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
238     OM_uint32 major = gss_export_name_composite(&minor, name, &namebuf);
239     if (major == GSS_S_COMPLETE) {
240         addToken(&namebuf);
241         gss_release_buffer(&minor, &namebuf);
242     }
243     else {
244         Category::getInstance(SHIBRESOLVER_LOGCAT).error("error exporting GSS name");
245     }
246 }
247 #endif
248
249 void ShibbolethResolver::addToken(const gss_buffer_t contextbuf)
250 {
251     if (m_gsswrapper) {
252         delete m_gsswrapper;
253         m_gsswrapper = NULL;
254     }
255
256     xsecsize_t len=0;
257     XMLByte* out=Base64::encode(reinterpret_cast<const XMLByte*>(contextbuf->value), contextbuf->length, &len);
258     if (out) {
259         string s;
260         s.append(reinterpret_cast<char*>(out), len);
261         auto_ptr_XMLCh temp(s.c_str());
262 #ifdef SHIBSP_XERCESC_HAS_XMLBYTE_RELEASE
263         XMLString::release(&out);
264 #else
265         XMLString::release((char**)&out);
266 #endif
267         static const XMLCh _GSSAPI[] = UNICODE_LITERAL_10(G,S,S,A,P,I,N,a,m,e);
268         m_gsswrapper = new AnyElementImpl(shibspconstants::SHIB2ATTRIBUTEMAP_NS, _GSSAPI);
269         m_gsswrapper->setTextContent(temp.get());
270     }
271     else {
272         Category::getInstance(SHIBRESOLVER_LOGCAT).error("error while base64-encoding GSS name");
273     }
274 }
275
276 #endif
277
278 void ShibbolethResolver::addAttribute(Attribute* attr)
279 {
280     if (attr)
281         m_inputAttributes.push_back(attr);
282 }
283
284 vector<Attribute*>& ShibbolethResolver::getResolvedAttributes()
285 {
286     return m_resolvedAttributes;
287 }
288
289 RequestMapper::Settings ShibbolethResolver::getSettings() const
290 {
291     if (!m_request)
292         throw ConfigurationException("Request settings not available without supplying SPRequest instance.");
293     return m_request->getRequestSettings();
294 }
295
296 void ShibbolethResolver::resolve()
297 {
298     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
299     SPConfig& conf = SPConfig::getConfig();
300     if (!m_request) {
301         m_sp = conf.getServiceProvider();
302         m_sp->lock();
303         if (m_appID.empty())
304             m_appID = "default";
305     }
306
307     const Application* app = m_request ? &(m_request->getApplication()) : m_sp->getApplication(m_appID.c_str());
308     if (!app)
309         throw ConfigurationException("Unable to locate application for resolution.");
310
311 #ifdef SHIBRESOLVER_HAVE_GSSAPI
312     if (m_gsswrapper)
313         m_tokens.push_back(m_gsswrapper);
314 #endif
315
316     if (conf.isEnabled(SPConfig::OutOfProcess)) {
317         g_Remoted.resolve(
318             *app,
319             m_issuer.c_str(),
320             m_protocol.c_str(),
321             m_tokens,
322             m_inputAttributes,
323             m_resolvedAttributes
324             );
325     }
326     else {
327         // When not out of process, we remote all the message processing.
328         DDF out,in = DDF("org.project-moonshot.shibresolver");
329         DDFJanitor jin(in), jout(out);
330         in.addmember("application_id").string(app->getId());
331         if (!m_issuer.empty())
332             in.addmember("issuer").string(m_issuer.c_str());
333         if (!m_protocol.empty()) {
334             auto_ptr_char prot(m_protocol.c_str());
335             in.addmember("protocol").string(prot.get());
336         }
337         if (!m_tokens.empty()) {
338             DDF& tokens = in.addmember("tokens").list();
339             for (vector<const XMLObject*>::const_iterator t = m_tokens.begin(); t != m_tokens.end(); ++t) {
340                 ostringstream os;
341                 os << *(*t);
342                 tokens.add(DDF(NULL).string(os.str().c_str()));
343             }
344         }
345         if (!m_inputAttributes.empty()) {
346             DDF attr;
347             DDF& attrs = in.addmember("attributes").list();
348             for (vector<Attribute*>::const_iterator a = m_inputAttributes.begin(); a != m_inputAttributes.end(); ++a) {
349                 attr = (*a)->marshall();
350                 attrs.add(attr);
351             }
352         }
353
354         out = (m_request ? m_request->getServiceProvider() : (*m_sp)).getListenerService()->send(in);
355
356         Attribute* attribute;
357         DDF attr = out.first();
358         while (!attr.isnull()) {
359             try {
360                 attribute = Attribute::unmarshall(attr);
361                 m_resolvedAttributes.push_back(attribute);
362                 if (log.isDebugEnabled())
363                     log.debug("unmarshalled attribute (ID: %s) with %d value%s",
364                         attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
365             }
366             catch (AttributeException& ex) {
367                 const char* id = attr.first().name();
368                 log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
369             }
370             attr = out.next();
371         }
372     }
373 }
374
375 void RemotedResolver::receive(DDF& in, ostream& out)
376 {
377     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
378
379     // Find application.
380     const char* aid = in["application_id"].string();
381     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
382     if (!app) {
383         // Something's horribly wrong.
384         log.error("couldn't find application (%s) for resolution", aid ? aid : "(missing)");
385         throw ConfigurationException("Unable to locate application for resolution, deleted?");
386     }
387
388     DDF ret(NULL);
389     DDFJanitor jout(ret);
390
391     Transaction t;
392
393     DDF tlist = in["tokens"];
394     DDF token = tlist.first();
395     while (token.isstring()) {
396         // Parse and bind the document into an XMLObject.
397         istringstream instr(token.string());
398         DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
399         XercesJanitor<DOMDocument> janitor(doc);
400         XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
401         t.tokens.push_back(xmlObject);
402         janitor.release();
403         token = tlist.next();
404     }
405
406     DDF alist = in["attributes"];
407     Attribute* attribute;
408     DDF attr = alist.first();
409     while (!attr.isnull()) {
410         attribute = Attribute::unmarshall(attr);
411         t.inputAttrs.push_back(attribute);
412         if (log.isDebugEnabled())
413             log.debug("unmarshalled attribute (ID: %s) with %d value%s",
414                 attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
415         attr = alist.next();
416     }
417
418     auto_ptr_XMLCh prot(in["protocol"].string());
419
420     resolve(*app, in["issuer"].string(), prot.get(), t.tokens, t.inputAttrs, t.resolvedAttrs);
421
422     if (!t.resolvedAttrs.empty()) {
423         ret.list();
424         for (vector<Attribute*>::const_iterator a = t.resolvedAttrs.begin(); a != t.resolvedAttrs.end(); ++a) {
425             attr = (*a)->marshall();
426             ret.add(attr);
427         }
428     }
429
430     out << ret;
431 }
432
433 void RemotedResolver::resolve(
434     const Application& app,
435     const char* issuer,
436     const XMLCh* protocol,
437     const vector<const XMLObject*>& tokens,
438     const vector<Attribute*>& inputAttrs,
439     vector<Attribute*>& resolvedAttrs
440     ) const
441 {
442 #ifndef SHIBSP_LITE
443     Category& log = Category::getInstance(SHIBRESOLVER_LOGCAT);
444     MetadataProvider* m = app.getMetadataProvider(false);
445     Locker locker(m);
446
447     const RoleDescriptor* role = NULL;
448     if (issuer && *issuer)
449         role = lookup(app, m, issuer, protocol);
450
451     vector<const Assertion*> assertions;
452
453     AttributeExtractor* extractor = app.getAttributeExtractor();
454     if (extractor) {
455         Locker extlocker(extractor);
456         // Support metadata-based attributes for only the "top-level" issuer.
457         if (role) {
458             pair<bool,const char*> mprefix = app.getString("metadataAttributePrefix");
459             if (mprefix.first) {
460                 log.debug("extracting metadata-derived attributes...");
461                 try {
462                     // We pass NULL for "issuer" because the issuer isn't the one asserting metadata-based attributes.
463                     extractor->extractAttributes(app, NULL, *role, resolvedAttrs);
464                     for (vector<Attribute*>::iterator a = resolvedAttrs.begin(); a != resolvedAttrs.end(); ++a) {
465                         vector<string>& ids = (*a)->getAliases();
466                         for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
467                             *id = mprefix.second + *id;
468                     }
469                 }
470                 catch (exception& ex) {
471                     log.error("caught exception extracting attributes: %s", ex.what());
472                 }
473             }
474         }
475
476         log.debug("extracting pushed attributes...");
477         const RoleDescriptor* role2;
478         for (vector<const XMLObject*>::const_iterator t = tokens.begin(); t != tokens.end(); ++t) {
479             // Save off any assertions for later use by resolver.
480             role2 = NULL;
481             const Assertion* assertion = dynamic_cast<const Assertion*>(*t);
482             if (assertion) {
483                 assertions.push_back(assertion);
484                 const saml2::Assertion* saml2token = dynamic_cast<const saml2::Assertion*>(assertion);
485                 if (saml2token && saml2token->getIssuer() && (saml2token->getIssuer()->getFormat() == NULL ||
486                         XMLString::equals(saml2token->getIssuer()->getFormat(), saml2::NameID::ENTITY))) {
487                     auto_ptr_char tokenissuer(saml2token->getIssuer()->getName());
488                     role2 = lookup(app, m, tokenissuer.get(), protocol);
489                 }
490             }
491             resolve(extractor, app, (role2 ? role2 : role), *(*t), resolvedAttrs);
492         }
493     }
494     else {
495         log.warn("no AttributeExtractor plugin installed, check log during startup");
496     }
497
498     try {
499         AttributeResolver* resolver = app.getAttributeResolver();
500         if (resolver) {
501             log.debug("resolving additional attributes...");
502
503             vector<Attribute*> inputs = inputAttrs;
504             inputs.insert(inputs.end(), resolvedAttrs.begin(), resolvedAttrs.end());
505
506             Locker locker(resolver);
507             auto_ptr<ResolutionContext> ctx(
508                 resolver->createResolutionContext(
509                     app,
510                     role ? dynamic_cast<const EntityDescriptor*>(role->getParent()) : NULL,
511                     protocol ? protocol : samlconstants::SAML20P_NS,
512                     NULL,
513                     NULL,
514                     NULL,
515                     &assertions,
516                     &inputs
517                     )
518                 );
519             resolver->resolveAttributes(*ctx.get());
520             if (!ctx->getResolvedAttributes().empty())
521                 resolvedAttrs.insert(resolvedAttrs.end(), ctx->getResolvedAttributes().begin(), ctx->getResolvedAttributes().end());
522         }
523     }
524     catch (exception& ex) {
525         log.error("attribute resolution failed: %s", ex.what());
526     }
527 #else
528     throw ConfigurationException("Cannot process request using lite version of shibsp library.");
529 #endif
530 }
531
532 #ifndef SHIBSP_LITE
533
534 void RemotedResolver::resolve(
535     AttributeExtractor* extractor,
536     const Application& app,
537     const RoleDescriptor* issuer,
538     const XMLObject& token,
539     vector<Attribute*>& resolvedAttrs
540     ) const
541 {
542     vector<Attribute*> extractedAttrs;
543     try {
544         extractor->extractAttributes(app, issuer, token, extractedAttrs);
545     }
546     catch (exception& ex) {
547         Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception extracting attributes: %s", ex.what());
548     }
549
550     AttributeFilter* filter = app.getAttributeFilter();
551     if (filter && !extractedAttrs.empty()) {
552         BasicFilteringContext fc(app, extractedAttrs, issuer);
553         Locker filtlocker(filter);
554         try {
555             filter->filterAttributes(fc, extractedAttrs);
556         }
557         catch (exception& ex) {
558             Category::getInstance(SHIBRESOLVER_LOGCAT).error("caught exception filtering attributes: %s", ex.what());
559             Category::getInstance(SHIBRESOLVER_LOGCAT).error("dumping extracted attributes due to filtering exception");
560             for_each(extractedAttrs.begin(), extractedAttrs.end(), xmltooling::cleanup<shibsp::Attribute>());
561             extractedAttrs.clear();
562         }
563     }
564
565     resolvedAttrs.insert(resolvedAttrs.end(), extractedAttrs.begin(), extractedAttrs.end());
566 }
567
568 const RoleDescriptor* RemotedResolver::lookup(
569     const Application& app, MetadataProvider* m, const char* entityID, const XMLCh* protocol
570     ) const
571 {
572     if (!m)
573         return NULL;
574
575     MetadataProviderCriteria idpmc(app, entityID, &IDPSSODescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS);
576     if (protocol)
577         idpmc.protocol2 = samlconstants::SAML20P_NS;
578     pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(idpmc);
579     if (!entity.first) {
580         Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate metadata for provider (%s)", entityID);
581     }
582     else if (!entity.second) {
583         MetadataProviderCriteria aamc(
584             app, entityID, &AttributeAuthorityDescriptor::ELEMENT_QNAME, protocol ? protocol : samlconstants::SAML20P_NS
585             );
586         if (protocol)
587             aamc.protocol2 = samlconstants::SAML20P_NS;
588         entity = m->getEntityDescriptor(aamc);
589         if (!entity.second) {
590             Category::getInstance(SHIBRESOLVER_LOGCAT).warn("unable to locate compatible IdP or AA role for provider (%s)", entityID);
591         }
592     }
593
594     return entity.second;
595 }
596
597 #endif
598
599 bool ShibbolethResolver::init(unsigned long features, const char* config, bool rethrow)
600 {
601     Lock initLock(g_lock.get());
602
603     if (g_initCount == INT_MAX) {
604         Category::getInstance(SHIBRESOLVER_LOGCAT".Config").crit("library initialized too many times");
605         return false;
606     }
607
608     if (g_initCount >= 1) {
609         ++g_initCount;
610         return true;
611     }
612
613     if (features & SPConfig::OutOfProcess) {
614 #ifndef SHIBSP_LITE
615         features = features | SPConfig::AttributeResolution | SPConfig::Metadata | SPConfig::Trust | SPConfig::Credentials;
616 #endif
617         if (!(features & SPConfig::InProcess))
618             features |= SPConfig::Listener;
619     }
620     else if (features & SPConfig::InProcess) {
621         features |= SPConfig::Listener;
622     }
623     SPConfig::getConfig().setFeatures(features);
624     if (!SPConfig::getConfig().init())
625         return false;
626     if (!SPConfig::getConfig().instantiate(config, rethrow))
627         return false;
628
629     ++g_initCount;
630     return true;
631 }
632
633 void ShibbolethResolver::term()
634 {
635     Lock initLock(g_lock.get());
636     if (g_initCount == 0) {
637         Category::getInstance(SHIBRESOLVER_LOGCAT".Config").crit("term without corresponding init");
638         return;
639     }
640     else if (--g_initCount > 0) {
641         return;
642     }
643
644     SPConfig::getConfig().term();
645 }
646
647
648 extern "C" int SHIBRESOLVER_EXPORTS xmltooling_extension_init(void*)
649 {
650 #ifdef SHIBRESOLVER_SHIBSP_HAS_REMOTING
651     SPConfig& conf = SPConfig::getConfig();
652     if (conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess) && conf.isEnabled(SPConfig::Listener))
653         conf.getServiceProvider()->regListener("org.project-moonshot.shibresolver", &g_Remoted);
654 #endif
655     return 0;   // signal success
656 }
657
658 extern "C" void SHIBRESOLVER_EXPORTS xmltooling_extension_term()
659 {
660     // Factories normally get unregistered during library shutdown, so no work usually required here.
661 }