2 * Copyright 2001-2010 Internet2
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * @file ReloadableXMLFile.cpp
20 * Base class for file-based XML configuration.
24 #include "io/HTTPResponse.h"
25 #ifndef XMLTOOLING_LITE
26 # include "security/Credential.h"
27 # include "security/CredentialCriteria.h"
28 # include "security/CredentialResolver.h"
29 # include "security/SignatureTrustEngine.h"
30 # include "signature/Signature.h"
31 # include "signature/SignatureValidator.h"
34 #include "util/PathResolver.h"
35 #include "util/ReloadableXMLFile.h"
36 #include "util/Threads.h"
37 #include "util/XMLConstants.h"
38 #include "util/XMLHelper.h"
40 #if defined(XMLTOOLING_LOG4SHIB)
41 # include <log4shib/NDC.hh>
42 #elif defined(XMLTOOLING_LOG4CPP)
43 # include <log4cpp/NDC.hh>
48 #include <sys/types.h>
51 #include <xercesc/framework/LocalFileInputSource.hpp>
52 #include <xercesc/framework/Wrapper4InputSource.hpp>
53 #include <xercesc/util/XMLUniDefs.hpp>
55 #ifndef XMLTOOLING_LITE
56 # include <xsec/dsig/DSIGReference.hpp>
57 # include <xsec/dsig/DSIGTransformList.hpp>
58 using namespace xmlsignature;
61 using namespace xmltooling::logging;
62 using namespace xmltooling;
63 using namespace xercesc;
66 #ifndef XMLTOOLING_LITE
68 class XMLTOOL_DLLLOCAL DummyCredentialResolver : public CredentialResolver
71 DummyCredentialResolver() {}
72 ~DummyCredentialResolver() {}
74 Lockable* lock() {return this;}
77 const Credential* resolve(const CredentialCriteria* criteria=nullptr) const {return nullptr;}
78 vector<const Credential*>::size_type resolve(
79 vector<const Credential*>& results, const CredentialCriteria* criteria=nullptr
85 static const XMLCh id[] = UNICODE_LITERAL_2(i,d);
86 static const XMLCh uri[] = UNICODE_LITERAL_3(u,r,i);
87 static const XMLCh url[] = UNICODE_LITERAL_3(u,r,l);
88 static const XMLCh path[] = UNICODE_LITERAL_4(p,a,t,h);
89 static const XMLCh pathname[] = UNICODE_LITERAL_8(p,a,t,h,n,a,m,e);
90 static const XMLCh file[] = UNICODE_LITERAL_4(f,i,l,e);
91 static const XMLCh filename[] = UNICODE_LITERAL_8(f,i,l,e,n,a,m,e);
92 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
93 static const XMLCh reloadChanges[] = UNICODE_LITERAL_13(r,e,l,o,a,d,C,h,a,n,g,e,s);
94 static const XMLCh reloadInterval[] = UNICODE_LITERAL_14(r,e,l,o,a,d,I,n,t,e,r,v,a,l);
95 static const XMLCh backingFilePath[] = UNICODE_LITERAL_15(b,a,c,k,i,n,g,F,i,l,e,P,a,t,h);
96 static const XMLCh type[] = UNICODE_LITERAL_4(t,y,p,e);
97 static const XMLCh certificate[] = UNICODE_LITERAL_11(c,e,r,t,i,f,i,c,a,t,e);
98 static const XMLCh signerName[] = UNICODE_LITERAL_10(s,i,g,n,e,r,N,a,m,e);
99 static const XMLCh _TrustEngine[] = UNICODE_LITERAL_11(T,r,u,s,t,E,n,g,i,n,e);
100 static const XMLCh _CredentialResolver[] = UNICODE_LITERAL_18(C,r,e,d,e,n,t,i,a,l,R,e,s,o,l,v,e,r);
103 ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
104 : m_root(e), m_local(true), m_validate(false), m_backupIndicator(true), m_filestamp(0), m_reloadInterval(0), m_lock(nullptr), m_log(log),
105 #ifndef XMLTOOLING_LITE
106 m_credResolver(nullptr), m_trust(nullptr),
108 m_shutdown(false), m_reload_wait(nullptr), m_reload_thread(nullptr)
111 NDC ndc("ReloadableXMLFile");
114 // Establish source of data...
115 const XMLCh* source=e->getAttributeNS(nullptr,uri);
116 if (!source || !*source) {
117 source=e->getAttributeNS(nullptr,url);
118 if (!source || !*source) {
119 source=e->getAttributeNS(nullptr,path);
120 if (!source || !*source) {
121 source=e->getAttributeNS(nullptr,pathname);
122 if (!source || !*source) {
123 source=e->getAttributeNS(nullptr,file);
124 if (!source || !*source) {
125 source=e->getAttributeNS(nullptr,filename);
138 if (source && *source) {
139 const XMLCh* flag=e->getAttributeNS(nullptr,validate);
140 m_validate=(XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE));
142 auto_ptr_char temp(source);
145 if (!m_local && !strstr(m_source.c_str(),"://")) {
146 log.warn("deprecated usage of uri/url attribute for a local resource, use path instead");
150 #ifndef XMLTOOLING_LITE
151 // Check for signature bits.
152 if (e && e->hasAttributeNS(nullptr, certificate)) {
153 // Use a file-based credential resolver rooted here.
154 m_credResolver = XMLToolingConfig::getConfig().CredentialResolverManager.newPlugin(FILESYSTEM_CREDENTIAL_RESOLVER, e);
157 const DOMElement* sub = e ? XMLHelper::getFirstChildElement(e, _CredentialResolver) : nullptr;
158 auto_ptr_char t(sub ? sub->getAttributeNS(nullptr, type) : nullptr);
160 m_credResolver = XMLToolingConfig::getConfig().CredentialResolverManager.newPlugin(t.get(), sub);
163 sub = e ? XMLHelper::getFirstChildElement(e, _TrustEngine) : nullptr;
164 auto_ptr_char t2(sub ? sub->getAttributeNS(nullptr, type) : nullptr);
166 TrustEngine* trust = XMLToolingConfig::getConfig().TrustEngineManager.newPlugin(t2.get(), sub);
167 if (!(m_trust = dynamic_cast<SignatureTrustEngine*>(trust))) {
169 throw XMLToolingException("TrustEngine-based ReloadableXMLFile requires a SignatureTrustEngine plugin.");
172 flag = e->getAttributeNS(nullptr, signerName);
174 auto_ptr_char sn(flag);
175 m_signerName = sn.get();
183 XMLToolingConfig::getConfig().getPathResolver()->resolve(m_source, PathResolver::XMLTOOLING_CFG_FILE);
185 flag=e->getAttributeNS(nullptr,reloadChanges);
186 if (!XMLString::equals(flag,xmlconstants::XML_FALSE) && !XMLString::equals(flag,xmlconstants::XML_ZERO)) {
188 struct _stat stat_buf;
189 if (_stat(m_source.c_str(), &stat_buf) == 0)
191 struct stat stat_buf;
192 if (stat(m_source.c_str(), &stat_buf) == 0)
194 m_filestamp=stat_buf.st_mtime;
196 throw IOException("Unable to access local file ($1)", params(1,m_source.c_str()));
197 m_lock=RWLock::create();
199 log.debug("using local resource (%s), will %smonitor for changes", m_source.c_str(), m_lock ? "" : "not ");
202 log.debug("using remote resource (%s)", m_source.c_str());
203 source = e->getAttributeNS(nullptr,backingFilePath);
204 if (source && *source) {
205 auto_ptr_char temp2(source);
206 m_backing=temp2.get();
207 XMLToolingConfig::getConfig().getPathResolver()->resolve(m_backing, PathResolver::XMLTOOLING_RUN_FILE);
208 log.debug("backup remote resource to (%s)", m_backing.c_str());
210 source = e->getAttributeNS(nullptr,reloadInterval);
211 if (source && *source) {
212 m_reloadInterval = XMLString::parseInt(source);
213 if (m_reloadInterval > 0) {
214 m_log.debug("will reload remote resource at most every %d seconds", m_reloadInterval);
215 m_lock=RWLock::create();
218 m_filestamp = time(nullptr); // assume it gets loaded initially
222 m_reload_wait = CondWait::create();
223 m_reload_thread = Thread::create(&reload_fn, this);
227 log.debug("no resource uri/path/name supplied, will load inline configuration");
230 source = e->getAttributeNS(nullptr, id);
231 if (source && *source) {
232 auto_ptr_char tempid(source);
237 ReloadableXMLFile::~ReloadableXMLFile()
243 void ReloadableXMLFile::shutdown()
245 if (m_reload_thread) {
246 // Shut down the reload thread and let it know.
248 m_reload_wait->signal();
249 m_reload_thread->join(nullptr);
250 delete m_reload_thread;
251 delete m_reload_wait;
252 m_reload_thread = nullptr;
253 m_reload_wait = nullptr;
257 void* ReloadableXMLFile::reload_fn(void* pv)
259 ReloadableXMLFile* r = reinterpret_cast<ReloadableXMLFile*>(pv);
262 // First, let's block all signals
263 Thread::mask_all_signals();
266 if (!r->m_id.empty()) {
267 string threadid("[");
268 threadid += r->m_id + ']';
269 logging::NDC::push(threadid);
276 auto_ptr<Mutex> mutex(Mutex::create());
280 r->m_log.info("reload thread started...running when signaled");
282 r->m_log.info("reload thread started...running every %d seconds", r->m_reloadInterval);
284 while (!r->m_shutdown) {
286 r->m_reload_wait->wait(mutex.get());
288 r->m_reload_wait->timedwait(mutex.get(), r->m_reloadInterval);
293 r->m_log.info("reloading %s resource...", r->m_local ? "local" : "remote");
294 pair<bool,DOMElement*> ret = r->background_load();
296 ret.second->getOwnerDocument()->release();
299 if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
300 r->m_log.info("remote resource (%s) unchanged from cached version", r->m_source.c_str());
303 // Shouldn't happen, we should only get codes intended to be gracefully handled.
304 r->m_log.crit("maintaining existing configuration, remote resource fetch returned atypical status code (%d)", ex);
307 catch (exception& ex) {
308 r->m_log.crit("maintaining existing configuration, error reloading resource (%s): %s", r->m_source.c_str(), ex.what());
312 r->m_log.info("reload thread finished");
316 if (!r->m_id.empty()) {
323 Lockable* ReloadableXMLFile::lock()
331 // Check if we need to refresh.
333 struct _stat stat_buf;
334 if (_stat(m_source.c_str(), &stat_buf) != 0)
337 struct stat stat_buf;
338 if (stat(m_source.c_str(), &stat_buf) != 0)
341 if (m_filestamp >= stat_buf.st_mtime)
344 // Elevate lock and recheck.
345 m_log.debug("timestamp of local resource changed, elevating to a write lock");
348 if (m_filestamp >= stat_buf.st_mtime) {
349 // Somebody else handled it, just downgrade.
350 m_log.debug("update of local resource handled by another thread, downgrading lock");
356 // Update the timestamp regardless.
357 m_filestamp = stat_buf.st_mtime;
358 m_log.info("change detected, signaling reload thread...");
359 m_reload_wait->signal();
365 void ReloadableXMLFile::unlock()
371 pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
378 if (m_source.empty()) {
379 // Data comes from the DOM we were handed.
380 m_log.debug("loading inline configuration...");
381 return make_pair(false, XMLHelper::getFirstChildElement(m_root));
384 // Data comes from a file we have to parse.
386 m_log.warn("using local backup of remote resource");
388 m_log.debug("loading configuration from external resource...");
390 DOMDocument* doc=nullptr;
391 if (m_local || backup) {
392 auto_ptr_XMLCh widenit(backup ? m_backing.c_str() : m_source.c_str());
393 // Use library-wide lock for now, nothing else is using it anyway.
394 Locker locker(backup ? getBackupLock() : nullptr);
395 LocalFileInputSource src(widenit.get());
396 Wrapper4InputSource dsrc(&src, false);
398 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
400 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
403 URLInputSource src(m_root, nullptr, &m_cacheTag);
404 Wrapper4InputSource dsrc(&src, false);
406 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
408 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
410 // Check for a response code signal.
411 if (XMLHelper::isNodeNamed(doc->getDocumentElement(), xmlconstants::XMLTOOLING_NS, URLInputSource::utf16StatusCodeElementName)) {
412 int responseCode = XMLString::parseInt(doc->getDocumentElement()->getFirstChild()->getNodeValue());
414 if (responseCode == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
415 throw (long)responseCode; // toss out as a "known" case to handle gracefully
418 m_log.warn("remote resource fetch returned atypical status code (%d)", responseCode);
419 throw IOException("remote resource fetch failed, check log for status code of response");
424 m_log.infoStream() << "loaded XML resource (" << (backup ? m_backing : m_source) << ")" << logging::eol;
425 #ifndef XMLTOOLING_LITE
426 if (m_credResolver || m_trust) {
427 m_log.debug("checking signature on XML resource");
429 DOMElement* sigel = XMLHelper::getFirstChildElement(doc->getDocumentElement(), xmlconstants::XMLSIG_NS, Signature::LOCAL_NAME);
431 throw XMLSecurityException("Signature validation required, but no signature found.");
433 // Wrap and unmarshall the signature for the duration of the check.
434 auto_ptr<Signature> sigobj(dynamic_cast<Signature*>(SignatureBuilder::buildOneFromElement(sigel))); // don't bind to document
435 validateSignature(*sigobj.get());
445 if (!backup && !m_backing.empty()) {
446 // If the indicator is true, we're responsible for the backup.
447 if (m_backupIndicator) {
448 m_log.debug("backing up remote resource to (%s)", m_backing.c_str());
450 Locker locker(getBackupLock());
451 ofstream backer(m_backing.c_str());
454 catch (exception& ex) {
455 m_log.crit("exception while backing up resource: %s", ex.what());
459 // If the indicator was false, set true to signal that a backup is needed.
460 // The caller will presumably flip it back to false once that's done.
461 m_backupIndicator = true;
465 return make_pair(true, doc->getDocumentElement());
468 catch (XMLException& e) {
469 auto_ptr_char msg(e.getMessage());
470 m_log.errorStream() << "Xerces error while loading resource (" << (backup ? m_backing : m_source) << "): "
471 << msg.get() << logging::eol;
472 if (!backup && !m_backing.empty())
474 throw XMLParserException(msg.get());
476 catch (exception& e) {
477 m_log.errorStream() << "error while loading resource ("
478 << (m_source.empty() ? "inline" : (backup ? m_backing : m_source)) << "): " << e.what() << logging::eol;
479 if (!backup && !m_backing.empty())
485 #ifndef XMLTOOLING_LITE
487 void ReloadableXMLFile::validateSignature(Signature& sigObj) const
489 DSIGSignature* sig=sigObj.getXMLSignature();
491 throw XMLSecurityException("Signature does not exist yet.");
493 // Make sure the whole document was signed.
495 DSIGReferenceList* refs=sig->getReferenceList();
496 if (refs && refs->getSize()==1) {
497 DSIGReference* ref=refs->item(0);
499 const XMLCh* URI=ref->getURI();
500 if (URI==nullptr || *URI==0) {
501 DSIGTransformList* tlist=ref->getTransforms();
502 if (tlist->getSize() <= 2) {
503 for (unsigned int i=0; tlist && i<tlist->getSize(); i++) {
504 if (tlist->item(i)->getTransformType()==TRANSFORM_ENVELOPED_SIGNATURE)
506 else if (tlist->item(i)->getTransformType()!=TRANSFORM_EXC_C14N &&
507 tlist->item(i)->getTransformType()!=TRANSFORM_C14N &&
508 tlist->item(i)->getTransformType()!=TRANSFORM_C14N11) {
519 throw XMLSecurityException("Invalid signature profile for signed configuration resource.");
522 CredentialCriteria cc;
523 cc.setUsage(Credential::SIGNING_CREDENTIAL);
524 cc.setSignature(sigObj, CredentialCriteria::KEYINFO_EXTRACTION_KEY);
525 if (!m_signerName.empty())
526 cc.setPeerName(m_signerName.c_str());
528 if (m_credResolver) {
529 Locker locker(m_credResolver);
530 vector<const Credential*> creds;
531 if (m_credResolver->resolve(creds, &cc)) {
532 SignatureValidator sigValidator;
533 for (vector<const Credential*>::const_iterator i = creds.begin(); i != creds.end(); ++i) {
535 sigValidator.setCredential(*i);
536 sigValidator.validate(&sigObj);
542 throw XMLSecurityException("Unable to verify signature with supplied key(s).");
545 throw XMLSecurityException("CredentialResolver did not supply any candidate keys.");
549 DummyCredentialResolver dummy;
550 if (m_trust->validate(sigObj, dummy, &cc))
552 throw XMLSecurityException("TrustEngine unable to verify signature.");
555 throw XMLSecurityException("Unable to verify signature.");
560 pair<bool,DOMElement*> ReloadableXMLFile::load()
565 pair<bool,DOMElement*> ReloadableXMLFile::background_load()
567 // If this method isn't overridden, we acquire a write lock
568 // and just call the old override.
571 SharedLock locker(m_lock, false);
575 Lockable* ReloadableXMLFile::getBackupLock()
577 return &XMLToolingConfig::getConfig();