2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "dnsseckeeper.hh"
26 #include "dnssecinfra.hh"
27 #include "ueberbackend.hh"
32 #include <sys/types.h>
34 #include <boost/algorithm/string.hpp>
35 #include <boost/format.hpp>
36 #include <boost/assign/std/vector.hpp> // for 'operator+=()'
37 #include <boost/assign/list_inserter.hpp>
40 #include "cachecleaner.hh"
41 #include "arguments.hh"
44 using namespace boost::assign
;
45 #include "namespaces.hh"
48 DNSSECKeeper::keycache_t
DNSSECKeeper::s_keycache
;
49 DNSSECKeeper::metacache_t
DNSSECKeeper::s_metacache
;
50 pthread_rwlock_t
DNSSECKeeper::s_metacachelock
= PTHREAD_RWLOCK_INITIALIZER
;
51 pthread_rwlock_t
DNSSECKeeper::s_keycachelock
= PTHREAD_RWLOCK_INITIALIZER
;
52 AtomicCounter
DNSSECKeeper::s_ops
;
53 time_t DNSSECKeeper::s_last_prune
;
55 bool DNSSECKeeper::doesDNSSEC()
57 return d_keymetadb
->doesDNSSEC();
60 bool DNSSECKeeper::isSecuredZone(const DNSName
& zone
)
65 keyset_t keys
= getKeys(zone
); // does the cache
67 for(keyset_t::value_type
& val
: keys
) {
68 if(val
.second
.active
) {
75 bool DNSSECKeeper::isPresigned(const DNSName
& name
)
78 getFromMeta(name
, "PRESIGNED", meta
);
82 bool DNSSECKeeper::addKey(const DNSName
& name
, bool setSEPBit
, int algorithm
, int64_t& id
, int bits
, bool active
)
86 throw runtime_error("Creating an algorithm " +std::to_string(algorithm
)+" ("+algorithm2name(algorithm
)+") key requires the size (in bits) to be passed.");
88 if(algorithm
== 12 || algorithm
== 13 || algorithm
== 15) // GOST, ECDSAP256SHA256, ED25519
90 else if(algorithm
== 14) // ECDSAP384SHA384
92 else if(algorithm
== 16) // ED448
95 throw runtime_error("Can not guess key size for algorithm "+std::to_string(algorithm
));
99 DNSSECPrivateKey dspk
;
100 shared_ptr
<DNSCryptoKeyEngine
> dpk(DNSCryptoKeyEngine::make(algorithm
));
103 } catch (std::runtime_error error
){
104 throw runtime_error("The algorithm does not support the given bit size.");
107 dspk
.d_algorithm
= algorithm
;
108 dspk
.d_flags
= setSEPBit
? 257 : 256;
109 return addKey(name
, dspk
, id
, active
);
112 void DNSSECKeeper::clearAllCaches() {
114 WriteLock
l(&s_keycachelock
);
117 WriteLock
l(&s_metacachelock
);
121 void DNSSECKeeper::clearCaches(const DNSName
& name
)
124 WriteLock
l(&s_keycachelock
);
125 s_keycache
.erase(name
);
127 WriteLock
l(&s_metacachelock
);
128 pair
<metacache_t::iterator
, metacache_t::iterator
> range
= s_metacache
.equal_range(tie(name
));
129 while(range
.first
!= range
.second
)
130 s_metacache
.erase(range
.first
++);
134 bool DNSSECKeeper::addKey(const DNSName
& name
, const DNSSECPrivateKey
& dpk
, int64_t& id
, bool active
)
137 DNSBackend::KeyData kd
;
138 kd
.flags
= dpk
.d_flags
; // the dpk doesn't get stored, only they key part
140 kd
.content
= dpk
.getKey()->convertToISC();
142 return d_keymetadb
->addDomainKey(name
, kd
, id
);
146 static bool keyCompareByKindAndID(const DNSSECKeeper::keyset_t::value_type
& a
, const DNSSECKeeper::keyset_t::value_type
& b
)
148 return make_pair(!a
.second
.keyType
, a
.second
.id
) <
149 make_pair(!b
.second
.keyType
, b
.second
.id
);
152 DNSSECPrivateKey
DNSSECKeeper::getKeyById(const DNSName
& zname
, unsigned int id
)
154 vector
<DNSBackend::KeyData
> keys
;
155 d_keymetadb
->getDomainKeys(zname
, keys
);
156 for(const DNSBackend::KeyData
& kd
: keys
) {
160 DNSSECPrivateKey dpk
;
161 DNSKEYRecordContent dkrc
;
162 dpk
.setKey(shared_ptr
<DNSCryptoKeyEngine
>(DNSCryptoKeyEngine::makeFromISCString(dkrc
, kd
.content
)));
163 dpk
.d_flags
= kd
.flags
;
164 dpk
.d_algorithm
= dkrc
.d_algorithm
;
166 if(dpk
.d_algorithm
== 5 && getNSEC3PARAM(zname
)) {
167 dpk
.d_algorithm
+= 2;
172 throw runtime_error("Can't find a key with id "+std::to_string(id
)+" for zone '"+zname
.toString()+"'");
176 bool DNSSECKeeper::removeKey(const DNSName
& zname
, unsigned int id
)
179 return d_keymetadb
->removeDomainKey(zname
, id
);
182 bool DNSSECKeeper::deactivateKey(const DNSName
& zname
, unsigned int id
)
185 return d_keymetadb
->deactivateDomainKey(zname
, id
);
188 bool DNSSECKeeper::activateKey(const DNSName
& zname
, unsigned int id
)
191 return d_keymetadb
->activateDomainKey(zname
, id
);
195 void DNSSECKeeper::getFromMeta(const DNSName
& zname
, const std::string
& key
, std::string
& value
)
197 static int ttl
= ::arg().asNum("domain-metadata-cache-ttl");
199 unsigned int now
= time(0);
201 if(!((++s_ops
) % 100000)) {
206 ReadLock
l(&s_metacachelock
);
208 metacache_t::const_iterator iter
= s_metacache
.find(tie(zname
, key
));
209 if(iter
!= s_metacache
.end() && iter
->d_ttd
> now
) {
210 value
= iter
->d_value
;
215 d_keymetadb
->getDomainMetadata(zname
, key
, meta
);
222 nce
.d_ttd
= now
+ ttl
;
226 WriteLock
l(&s_metacachelock
);
227 replacing_insert(s_metacache
, nce
);
232 void DNSSECKeeper::getSoaEdit(const DNSName
& zname
, std::string
& value
)
234 static const string
soaEdit(::arg()["default-soa-edit"]);
235 static const string
soaEditSigned(::arg()["default-soa-edit-signed"]);
237 if (isPresigned(zname
)) {
238 // SOA editing on a presigned zone never makes sense
242 getFromMeta(zname
, "SOA-EDIT", value
);
244 if ((!soaEdit
.empty() || !soaEditSigned
.empty()) && value
.empty()) {
245 if (!soaEditSigned
.empty() && isSecuredZone(zname
))
254 uint64_t DNSSECKeeper::dbdnssecCacheSizes(const std::string
& str
)
256 if(str
=="meta-cache-size") {
257 ReadLock
l(&s_metacachelock
);
258 return s_metacache
.size();
260 else if(str
=="key-cache-size") {
261 ReadLock
l(&s_keycachelock
);
262 return s_keycache
.size();
267 bool DNSSECKeeper::getNSEC3PARAM(const DNSName
& zname
, NSEC3PARAMRecordContent
* ns3p
, bool* narrow
)
270 getFromMeta(zname
, "NSEC3PARAM", value
);
271 if(value
.empty()) { // "no NSEC3"
275 static int maxNSEC3Iterations
=::arg().asNum("max-nsec3-iterations");
277 *ns3p
= NSEC3PARAMRecordContent(value
);
278 if (ns3p
->d_iterations
> maxNSEC3Iterations
) {
279 ns3p
->d_iterations
= maxNSEC3Iterations
;
280 L
<<Logger::Error
<<"Number of NSEC3 iterations for zone '"<<zname
<<"' is above 'max-nsec3-iterations'. Value adjusted to: "<<maxNSEC3Iterations
<<endl
;
282 if (ns3p
->d_algorithm
!= 1) {
283 L
<<Logger::Error
<<"Invalid hash algorithm for NSEC3: '"<<std::to_string(ns3p
->d_algorithm
)<<"', setting to 1 for zone '"<<zname
<<"'."<<endl
;
284 ns3p
->d_algorithm
= 1;
288 getFromMeta(zname
, "NSEC3NARROW", value
);
289 *narrow
= (value
=="1");
295 * Check is the provided NSEC3PARAM record is something we can work with
297 * \param ns3p NSEC3PARAMRecordContent to check
298 * \param msg string to fill with an error message
299 * \return true on valid, false otherwise
301 bool DNSSECKeeper::checkNSEC3PARAM(const NSEC3PARAMRecordContent
& ns3p
, string
& msg
)
303 static int maxNSEC3Iterations
=::arg().asNum("max-nsec3-iterations");
305 if (ns3p
.d_iterations
> maxNSEC3Iterations
) {
306 msg
+= "Number of NSEC3 iterations is above 'max-nsec3-iterations'.";
310 if (ns3p
.d_algorithm
!= 1) {
313 msg
+= "Invalid hash algorithm for NSEC3: '"+std::to_string(ns3p
.d_algorithm
)+"', the only valid value is '1'.";
320 bool DNSSECKeeper::setNSEC3PARAM(const DNSName
& zname
, const NSEC3PARAMRecordContent
& ns3p
, const bool& narrow
)
322 string error_msg
= "";
323 if (!checkNSEC3PARAM(ns3p
, error_msg
))
324 throw runtime_error("NSEC3PARAMs provided for zone '"+zname
.toString()+"' are invalid: " + error_msg
);
327 string descr
= ns3p
.getZoneRepresentation();
329 meta
.push_back(descr
);
330 if (d_keymetadb
->setDomainMetadata(zname
, "NSEC3PARAM", meta
)) {
336 return d_keymetadb
->setDomainMetadata(zname
, "NSEC3NARROW", meta
);
341 bool DNSSECKeeper::unsetNSEC3PARAM(const DNSName
& zname
)
344 return (d_keymetadb
->setDomainMetadata(zname
, "NSEC3PARAM", vector
<string
>()) && d_keymetadb
->setDomainMetadata(zname
, "NSEC3NARROW", vector
<string
>()));
348 bool DNSSECKeeper::setPresigned(const DNSName
& zname
)
353 return d_keymetadb
->setDomainMetadata(zname
, "PRESIGNED", meta
);
356 bool DNSSECKeeper::unsetPresigned(const DNSName
& zname
)
359 return d_keymetadb
->setDomainMetadata(zname
, "PRESIGNED", vector
<string
>());
363 * Add domainmetadata to allow publishing CDS records for zone zname
365 * @param zname DNSName of the zone
366 * @param digestAlgos string with comma-separated numbers that describe the
367 * used digest algorithms. This is copied to the database
369 * @return true if the data was inserted, false otherwise
371 bool DNSSECKeeper::setPublishCDS(const DNSName
& zname
, const string
& digestAlgos
)
375 meta
.push_back(digestAlgos
);
376 return d_keymetadb
->setDomainMetadata(zname
, "PUBLISH-CDS", meta
);
380 * Remove domainmetadata to stop publishing CDS records for zone zname
382 * @param zname DNSName of the zone
383 * @return true if the operation was successful, false otherwise
385 bool DNSSECKeeper::unsetPublishCDS(const DNSName
& zname
)
388 return d_keymetadb
->setDomainMetadata(zname
, "PUBLISH-CDS", vector
<string
>());
392 * Add domainmetadata to allow publishing CDNSKEY records.for zone zname
394 * @param zname DNSName of the zone
395 * @return true if the data was inserted, false otherwise
397 bool DNSSECKeeper::setPublishCDNSKEY(const DNSName
& zname
)
402 return d_keymetadb
->setDomainMetadata(zname
, "PUBLISH-CDNSKEY", meta
);
406 * Remove domainmetadata to stop publishing CDNSKEY records for zone zname
408 * @param zname DNSName of the zone
409 * @return true if the operation was successful, false otherwise
411 bool DNSSECKeeper::unsetPublishCDNSKEY(const DNSName
& zname
)
414 return d_keymetadb
->setDomainMetadata(zname
, "PUBLISH-CDNSKEY", vector
<string
>());
418 * Returns all keys that are used to sign the DNSKEY RRSet in a zone
420 * @param zname DNSName of the zone
421 * @return a keyset_t with all keys that are used to sign the DNSKEY
422 * RRSet (these are the entrypoint(s) to the zone)
424 DNSSECKeeper::keyset_t
DNSSECKeeper::getEntryPoints(const DNSName
& zname
)
426 DNSSECKeeper::keyset_t ret
;
427 DNSSECKeeper::keyset_t keys
= getKeys(zname
);
429 for(auto const &keymeta
: keys
)
430 if(keymeta
.second
.keyType
== KSK
|| keymeta
.second
.keyType
== CSK
)
431 ret
.push_back(keymeta
);
435 DNSSECKeeper::keyset_t
DNSSECKeeper::getKeys(const DNSName
& zone
, bool useCache
)
437 static int ttl
= ::arg().asNum("dnssec-key-cache-ttl");
438 unsigned int now
= time(0);
440 if(!((++s_ops
) % 100000)) {
444 if (useCache
&& ttl
> 0) {
445 ReadLock
l(&s_keycachelock
);
446 keycache_t::const_iterator iter
= s_keycache
.find(zone
);
448 if(iter
!= s_keycache
.end() && iter
->d_ttd
> now
) {
450 for(const keyset_t::value_type
& value
: iter
->d_keys
)
451 ret
.push_back(value
);
457 vector
<DNSBackend::KeyData
> dbkeyset
;
459 d_keymetadb
->getDomainKeys(zone
, dbkeyset
);
461 // Determine the algorithms that have a KSK/ZSK split
462 set
<uint8_t> algoSEP
, algoNoSEP
;
463 vector
<uint8_t> algoHasSeparateKSK
;
464 for(const DNSBackend::KeyData
&keydata
: dbkeyset
) {
465 DNSSECPrivateKey dpk
;
466 DNSKEYRecordContent dkrc
;
468 dpk
.setKey(shared_ptr
<DNSCryptoKeyEngine
>(DNSCryptoKeyEngine::makeFromISCString(dkrc
, keydata
.content
)));
471 if(keydata
.flags
== 257)
472 algoSEP
.insert(dkrc
.d_algorithm
);
474 algoNoSEP
.insert(dkrc
.d_algorithm
);
477 set_intersection(algoSEP
.begin(), algoSEP
.end(), algoNoSEP
.begin(), algoNoSEP
.end(), std::back_inserter(algoHasSeparateKSK
));
479 for(DNSBackend::KeyData
& kd
: dbkeyset
)
481 DNSSECPrivateKey dpk
;
482 DNSKEYRecordContent dkrc
;
484 dpk
.setKey(shared_ptr
<DNSCryptoKeyEngine
>(DNSCryptoKeyEngine::makeFromISCString(dkrc
, kd
.content
)));
486 dpk
.d_flags
= kd
.flags
;
487 dpk
.d_algorithm
= dkrc
.d_algorithm
;
488 if(dpk
.d_algorithm
== 5 && getNSEC3PARAM(zone
))
493 kmd
.active
= kd
.active
;
494 kmd
.hasSEPBit
= (kd
.flags
== 257);
497 if (find(algoHasSeparateKSK
.begin(), algoHasSeparateKSK
.end(), dpk
.d_algorithm
) == algoHasSeparateKSK
.end())
499 else if(kmd
.hasSEPBit
)
504 retkeyset
.push_back(make_pair(dpk
, kmd
));
506 sort(retkeyset
.begin(), retkeyset
.end(), keyCompareByKindAndID
);
511 kce
.d_keys
= retkeyset
;
512 kce
.d_ttd
= now
+ ttl
;
514 WriteLock
l(&s_keycachelock
);
515 replacing_insert(s_keycache
, kce
);
522 bool DNSSECKeeper::checkKeys(const DNSName
& zone
)
524 vector
<DNSBackend::KeyData
> dbkeyset
;
525 d_keymetadb
->getDomainKeys(zone
, dbkeyset
);
527 for(const DNSBackend::KeyData
&keydata
: dbkeyset
) {
528 DNSKEYRecordContent dkrc
;
529 shared_ptr
<DNSCryptoKeyEngine
> dke(DNSCryptoKeyEngine::makeFromISCString(dkrc
, keydata
.content
));
530 if (!dke
->checkKey()) {
538 bool DNSSECKeeper::getPreRRSIGs(UeberBackend
& db
, const DNSName
& signer
, const DNSName
& qname
,
539 const DNSName
& wildcardname
, const QType
& qtype
,
540 DNSResourceRecord::Place signPlace
, vector
<DNSZoneRecord
>& rrsigs
, uint32_t signTTL
)
542 // cerr<<"Doing DB lookup for precomputed RRSIGs for '"<<(wildcardname.empty() ? qname : wildcardname)<<"'"<<endl;
544 if(!db
.getSOAUncached(signer
, sd
)) {
545 DLOG(L
<<"Could not get SOA for domain"<<endl
);
548 db
.lookup(QType(QType::RRSIG
), wildcardname
.countLabels() ? wildcardname
: qname
, NULL
, sd
.domain_id
);
551 auto rrsig
= getRR
<RRSIGRecordContent
>(rr
.dr
);
552 if(rrsig
->d_type
== qtype
.getCode() && rrsig
->d_signer
==signer
) {
553 if (wildcardname
.countLabels())
554 rr
.dr
.d_name
= qname
;
555 rr
.dr
.d_place
= signPlace
;
556 rr
.dr
.d_ttl
= signTTL
;
557 rrsigs
.push_back(rr
);
563 bool DNSSECKeeper::TSIGGrantsAccess(const DNSName
& zone
, const DNSName
& keyname
)
565 vector
<string
> allowed
;
567 d_keymetadb
->getDomainMetadata(zone
, "TSIG-ALLOW-AXFR", allowed
);
569 for(const string
& dbkey
: allowed
) {
570 if(DNSName(dbkey
)==keyname
)
576 bool DNSSECKeeper::getTSIGForAccess(const DNSName
& zone
, const string
& master
, DNSName
* keyname
)
578 vector
<string
> keynames
;
579 d_keymetadb
->getDomainMetadata(zone
, "AXFR-MASTER-TSIG", keynames
);
580 keyname
->trimToLabels(0);
582 // XXX FIXME this should check for a specific master!
583 for(const string
& dbkey
: keynames
) {
584 *keyname
=DNSName(dbkey
);
590 /* Rectifies the zone
592 * \param zone The zone to rectify
593 * \param error& A string where error messages are added
594 * \param doTransaction Whether or not to wrap the rectify in a transaction
596 bool DNSSECKeeper::rectifyZone(const DNSName
& zone
, string
& error
, bool doTransaction
) {
597 if (isPresigned(zone
)) {
598 error
= "Rectify presigned zone '"+zone
.toLogString()+"' is not allowed/necessary.";
602 UeberBackend
* B
= d_keymetadb
;
603 std::unique_ptr
<UeberBackend
> b
;
606 // We don't have a *full* Ueberbackend, just a key-only one.
607 // Let's create one and use it
608 b
= std::unique_ptr
<UeberBackend
>(new UeberBackend());
614 if(!B
->getSOAUncached(zone
, sd
)) {
615 error
= "No SOA known for '" + zone
.toLogString() + "', is such a zone in the database?";
619 sd
.db
->list(zone
, sd
.domain_id
);
621 DNSResourceRecord rr
;
622 set
<DNSName
> qnames
, nsset
, dsnames
, insnonterm
, delnonterm
;
623 map
<DNSName
,bool> nonterm
;
624 vector
<DNSResourceRecord
> rrs
;
626 while(sd
.db
->get(rr
)) {
627 rr
.qname
.makeUsLowerCase();
628 if (rr
.qtype
.getCode())
631 qnames
.insert(rr
.qname
);
632 if(rr
.qtype
.getCode() == QType::NS
&& rr
.qname
!= zone
)
633 nsset
.insert(rr
.qname
);
634 if(rr
.qtype
.getCode() == QType::DS
)
635 dsnames
.insert(rr
.qname
);
638 delnonterm
.insert(rr
.qname
);
641 NSEC3PARAMRecordContent ns3pr
;
643 bool haveNSEC3
= getNSEC3PARAM(zone
, &ns3pr
, &narrow
);
644 bool isOptOut
= (haveNSEC3
&& ns3pr
.d_flags
);
646 set
<DNSName
> nsec3set
;
647 if (haveNSEC3
&& !narrow
) {
648 for (auto &loopRR
: rrs
) {
650 DNSName shorter
= loopRR
.qname
;
651 if (shorter
!= zone
&& shorter
.chopOff() && shorter
!= zone
) {
653 if(nsset
.count(shorter
)) {
657 } while(shorter
.chopOff() && shorter
!= zone
);
659 shorter
= loopRR
.qname
;
660 if(!skip
&& (loopRR
.qtype
.getCode() != QType::NS
|| !isOptOut
)) {
663 if(!nsec3set
.count(shorter
)) {
664 nsec3set
.insert(shorter
);
666 } while(shorter
!= zone
&& shorter
.chopOff());
672 sd
.db
->startTransaction(zone
, -1);
676 uint32_t maxent
= ::arg().asNum("max-ent-entries");
679 for (const auto& qname
: qnames
)
687 if(nsset
.count(shorter
)) {
691 } while(shorter
.chopOff());
693 auth
=nonterm
.find(qname
)->second
;
696 if(haveNSEC3
) // NSEC3
698 if(!narrow
&& nsec3set
.count(qname
)) {
699 ordername
=DNSName(toBase32Hex(hashQNameWithSalt(ns3pr
, qname
)));
705 else if (realrr
) // NSEC
706 ordername
=qname
.makeRelative(zone
);
710 cerr<<"'"<<qname<<"' -> '"<< ordername <<"'"<<endl;
712 sd
.db
->updateDNSSECOrderNameAndAuth(sd
.domain_id
, qname
, ordername
, auth
);
716 if (dsnames
.count(qname
))
717 sd
.db
->updateDNSSECOrderNameAndAuth(sd
.domain_id
, qname
, ordername
, true, QType::DS
);
718 if (!auth
|| nsset
.count(qname
)) {
720 if(isOptOut
&& !dsnames
.count(qname
))
721 sd
.db
->updateDNSSECOrderNameAndAuth(sd
.domain_id
, qname
, ordername
, false, QType::NS
);
722 sd
.db
->updateDNSSECOrderNameAndAuth(sd
.domain_id
, qname
, ordername
, false, QType::A
);
723 sd
.db
->updateDNSSECOrderNameAndAuth(sd
.domain_id
, qname
, ordername
, false, QType::AAAA
);
729 while(shorter
!=zone
&& shorter
.chopOff())
731 if(!qnames
.count(shorter
))
735 L
<<Logger::Warning
<<"Zone '"<<zone
<<"' has too many empty non terminals."<<endl
;
742 if (!delnonterm
.count(shorter
) && !nonterm
.count(shorter
))
743 insnonterm
.insert(shorter
);
745 delnonterm
.erase(shorter
);
747 if (!nonterm
.count(shorter
)) {
748 nonterm
.insert(pair
<DNSName
, bool>(shorter
, auth
));
751 nonterm
[shorter
]=true;
760 //cerr<<"Total: "<<nonterm.size()<<" Insert: "<<insnonterm.size()<<" Delete: "<<delnonterm.size()<<endl;
761 if(!insnonterm
.empty() || !delnonterm
.empty() || !doent
)
763 sd
.db
->updateEmptyNonTerminals(sd
.domain_id
, insnonterm
, delnonterm
, !doent
);
769 for(const auto& nt
: nonterm
){
770 qnames
.insert(nt
.first
);
777 sd
.db
->commitTransaction();
782 void DNSSECKeeper::cleanup()
785 Utility::gettimeofday(&now
, 0);
787 if(now
.tv_sec
- s_last_prune
> (time_t)(30)) {
789 WriteLock
l(&s_metacachelock
);
790 pruneCollection(*this, s_metacache
, ::arg().asNum("max-cache-entries"));
793 WriteLock
l(&s_keycachelock
);
794 pruneCollection(*this, s_keycache
, ::arg().asNum("max-cache-entries"));
796 s_last_prune
=time(0);