Commit a76b3806 authored by Mark Andrews's avatar Mark Andrews
Browse files

2349. [func] Provide incremental re-signing support for secure

                        dynamic zones. [RT #1091]
parent 28b3569d
2349. [func] Provide incremental re-signing support for secure
dynamic zones. [RT #1091]
2348. [func] Use the EVP interface to OpenSSL. Add PKCS#11 support.
Documentation is in the new README.pkcs11 file.
[RT #16844]
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: query.c,v 1.303 2008/01/24 02:00:43 jinmei Exp $ */
/* $Id: query.c,v 1.304 2008/04/01 01:37:24 marka Exp $ */
/*! \file */
......@@ -1087,8 +1087,12 @@ query_addadditional(void *arg, dns_name_t *name, dns_rdatatype_t qtype) {
result = dns_db_find(db, name, version, type, client->query.dboptions,
client->now, &node, fname, rdataset,
sigrdataset);
if (result == ISC_R_SUCCESS)
if (result == ISC_R_SUCCESS) {
if (sigrdataset != NULL && !dns_db_issecure(db) &&
dns_rdataset_isassociated(sigrdataset))
dns_rdataset_disassociate(sigrdataset);
goto found;
}
if (dns_rdataset_isassociated(rdataset))
dns_rdataset_disassociate(rdataset);
......@@ -2025,7 +2029,7 @@ query_addsoa(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version,
eresult = DNS_R_SERVFAIL;
goto cleanup;
}
if (WANTDNSSEC(client)) {
if (WANTDNSSEC(client) && dns_db_issecure(db)) {
sigrdataset = query_newrdataset(client);
if (sigrdataset == NULL) {
eresult = DNS_R_SERVFAIL;
......@@ -2143,7 +2147,7 @@ query_addns(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version) {
eresult = DNS_R_SERVFAIL;
goto cleanup;
}
if (WANTDNSSEC(client)) {
if (WANTDNSSEC(client) && dns_db_issecure(db)) {
sigrdataset = query_newrdataset(client);
if (sigrdataset == NULL) {
CTRACE("query_addns: query_newrdataset failed");
......@@ -3534,7 +3538,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
QUERY_ERROR(DNS_R_SERVFAIL);
goto cleanup;
}
if (WANTDNSSEC(client)) {
if (WANTDNSSEC(client) && (!is_zone || dns_db_issecure(db))) {
sigrdataset = query_newrdataset(client);
if (sigrdataset == NULL) {
QUERY_ERROR(DNS_R_SERVFAIL);
......@@ -4173,7 +4177,16 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
result = dns_rdatasetiter_first(rdsiter);
while (result == ISC_R_SUCCESS) {
dns_rdatasetiter_current(rdsiter, rdataset);
if ((qtype == dns_rdatatype_any ||
if (is_zone && qtype == dns_rdatatype_any &&
!dns_db_issecure(db) &&
dns_rdatatype_isdnssec(rdataset->type)) {
/*
* The zone is transitioning from insecure
* to secure. Hide the dnssec records from
* ANY queries.
*/
dns_rdataset_disassociate(rdataset);
} else if ((qtype == dns_rdatatype_any ||
rdataset->type == qtype) && rdataset->type != 0) {
query_addrrset(client,
fname != NULL ? &fname : &tname,
......
......@@ -15,12 +15,13 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: update.c,v 1.142 2008/02/07 03:12:15 marka Exp $ */
/* $Id: update.c,v 1.143 2008/04/01 01:37:24 marka Exp $ */
#include <config.h>
#include <isc/netaddr.h>
#include <isc/print.h>
#include <isc/serial.h>
#include <isc/string.h>
#include <isc/taskpool.h>
#include <isc/util.h>
......@@ -1063,9 +1064,17 @@ rr_equal_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
*
* RFC2136 does not mention NSEC or DNAME, but multiple NSECs or DNAMEs
* make little sense, so we replace those, too.
*
* Additionally replace RRSIG that have been generated by the same key
* for the same type. This simplifies refreshing a offline KSK by not
* requiring that the old RRSIG be deleted. It also simpifies key
* rollover by only requiring that the new RRSIG be added.
*/
static isc_boolean_t
replaces_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
dns_rdata_rrsig_t updatesig, dbsig;
isc_result_t result;
if (db_rr->type != update_rr->type)
return (ISC_FALSE);
if (db_rr->type == dns_rdatatype_cname)
......@@ -1076,6 +1085,20 @@ replaces_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
return (ISC_TRUE);
if (db_rr->type == dns_rdatatype_nsec)
return (ISC_TRUE);
if (db_rr->type == dns_rdatatype_rrsig) {
/*
* Replace existing RRSIG with the same keyid,
* covered and algorithm.
*/
result = dns_rdata_tostruct(db_rr, &dbsig, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
result = dns_rdata_tostruct(update_rr, &updatesig, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
if (dbsig.keyid == updatesig.keyid &&
dbsig.covered == updatesig.covered &&
dbsig.algorithm == updatesig.algorithm)
return (ISC_TRUE);
}
if (db_rr->type == dns_rdatatype_wks) {
/*
* Compare the address and protocol fields only. These
......@@ -1493,6 +1516,7 @@ next_active(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
dns_dbiterator_t *dbit = NULL;
isc_boolean_t has_nsec;
unsigned int wraps = 0;
isc_boolean_t secure = dns_db_issecure(db);
CHECK(dns_db_createiterator(db, ISC_FALSE, &dbit));
......@@ -1530,9 +1554,29 @@ next_active(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
* we must pause the iterator first.
*/
CHECK(dns_dbiterator_pause(dbit));
CHECK(rrset_exists(db, ver, newname,
dns_rdatatype_nsec, 0, &has_nsec));
if (secure) {
CHECK(rrset_exists(db, ver, newname,
dns_rdatatype_nsec, 0, &has_nsec));
} else {
dns_fixedname_t ffound;
dns_name_t *found;
dns_fixedname_init(&ffound);
found = dns_fixedname_name(&ffound);
result = dns_db_find(db, newname, ver,
dns_rdatatype_soa,
DNS_DBFIND_NOWILD, 0, NULL, found,
NULL, NULL);
if (result == ISC_R_SUCCESS ||
result == DNS_R_EMPTYNAME ||
result == DNS_R_NXRRSET ||
result == DNS_R_CNAME ||
(result == DNS_R_DELEGATION &&
dns_name_equal(newname, found))) {
has_nsec = ISC_TRUE;
result = ISC_R_SUCCESS;
} else if (result != DNS_R_NXDOMAIN)
break;
}
} while (! has_nsec);
failure:
if (dbit != NULL)
......@@ -1541,6 +1585,35 @@ next_active(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
return (result);
}
static isc_boolean_t
has_opt_bit(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node) {
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_t rdataset;
isc_boolean_t has_bit = ISC_FALSE;
dns_rdataset_init(&rdataset);
CHECK(dns_db_findrdataset(db, node, version, dns_rdatatype_nsec,
dns_rdatatype_none, 0, &rdataset, NULL));
CHECK(dns_rdataset_first(&rdataset));
dns_rdataset_current(&rdataset, &rdata);
has_bit = dns_nsec_typepresent(&rdata, dns_rdatatype_opt);
failure:
if (dns_rdataset_isassociated(&rdataset))
dns_rdataset_disassociate(&rdataset);
return (has_bit);
}
static void
set_bit(unsigned char *array, unsigned int index) {
unsigned int shift, mask;
shift = 7 - (index % 8);
mask = 1 << shift;
array[index / 8] |= mask;
}
/*%
* Add a NSEC record for "name", recording the change in "diff".
* The existing NSEC is removed.
......@@ -1572,6 +1645,24 @@ add_nsec(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
CHECK(dns_db_findnode(db, name, ISC_FALSE, &node));
dns_rdata_init(&rdata);
CHECK(dns_nsec_buildrdata(db, ver, node, target, buffer, &rdata));
/*
* Preserve the status of the OPT bit in the origin's NSEC record.
*/
if (dns_name_equal(dns_db_origin(db), name) &&
has_opt_bit(db, ver, node))
{
isc_region_t region;
dns_name_t next;
dns_name_init(&next, NULL);
dns_rdata_toregion(&rdata, &region);
dns_name_fromregion(&next, &region);
isc_region_consume(&region, next.length);
INSIST(region.length > (2 + dns_rdatatype_opt / 8) &&
region.base[0] == 0 &&
region.base[1] > dns_rdatatype_opt / 8);
set_bit(region.base + 2, dns_rdatatype_opt);
}
dns_db_detachnode(db, &node);
/*
......@@ -1715,7 +1806,7 @@ add_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
/* Update the database and journal with the RRSIG. */
/* XXX inefficient - will cause dataset merging */
CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADD, name,
CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADDRESIGN, name,
rdataset.ttl, &sig_rdata));
dns_rdata_reset(&sig_rdata);
added_sig = ISC_TRUE;
......@@ -1735,6 +1826,84 @@ add_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
return (result);
}
/*
* Delete expired RRsigs and any RRsigs we are about to re-sign.
* See also zone.c:del_sigs().
*/
static isc_result_t
del_keysigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys)
{
isc_result_t result;
dns_dbnode_t *node = NULL;
dns_rdataset_t rdataset;
dns_rdata_t rdata = DNS_RDATA_INIT;
unsigned int i;
dns_rdata_rrsig_t rrsig;
isc_boolean_t found;
fprintf(stderr, "del_keysigs\n");
dns_rdataset_init(&rdataset);
result = dns_db_findnode(db, name, ISC_FALSE, &node);
if (result == ISC_R_NOTFOUND)
return (ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS)
goto failure;
result = dns_db_findrdataset(db, node, ver, dns_rdatatype_rrsig,
dns_rdatatype_dnskey, (isc_stdtime_t) 0,
&rdataset, NULL);
dns_db_detachnode(db, &node);
if (result == ISC_R_NOTFOUND)
return (ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS)
goto failure;
for (result = dns_rdataset_first(&rdataset);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(&rdataset)) {
dns_rdataset_current(&rdataset, &rdata);
result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
found = ISC_FALSE;
for (i = 0; i < nkeys; i++) {
if (rrsig.keyid == dst_key_id(keys[i])) {
found = ISC_TRUE;
if (!dst_key_isprivate(keys[i])) {
/*
* The re-signing code in zone.c
* will mark this as offline.
* Just skip the record for now.
*/
break;
}
result = update_one_rr(db, ver, diff,
DNS_DIFFOP_DEL, name,
rdataset.ttl, &rdata);
break;
}
}
/*
* If there is not a matching DNSKEY then delete the RRSIG.
*/
if (!found)
result = update_one_rr(db, ver, diff, DNS_DIFFOP_DEL,
name, rdataset.ttl, &rdata);
dns_rdata_reset(&rdata);
if (result != ISC_R_SUCCESS)
break;
}
dns_rdataset_disassociate(&rdataset);
if (result == ISC_R_NOMORE)
result = ISC_R_SUCCESS;
failure:
if (node != NULL)
dns_db_detachnode(db, &node);
return (result);
}
/*%
* Update RRSIG and NSEC records affected by an update. The original
* update, including the SOA serial update but exluding the RRSIG & NSEC
......@@ -1749,7 +1918,8 @@ add_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
static isc_result_t
update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
dns_dbversion_t *oldver, dns_dbversion_t *newver,
dns_diff_t *diff, isc_uint32_t sigvalidityinterval)
dns_diff_t *diff, isc_uint32_t sigvalidityinterval,
isc_boolean_t *deleted_zsk)
{
isc_result_t result;
dns_difftuple_t *t;
......@@ -1797,8 +1967,27 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
*/
check_ksk = ISC_TF((dns_zone_getoptions(zone) &
DNS_ZONEOPT_UPDATECHECKKSK) != 0);
if (check_ksk)
/*
* If we are not checking the ZSK flag then all DNSKEY's are
* already signing all RRsets so we don't need to trigger special
* changes.
*/
if (*deleted_zsk && (!check_ksk || !ksk_sanity(db, oldver)))
*deleted_zsk = ISC_FALSE;
if (check_ksk) {
check_ksk = ksk_sanity(db, newver);
if (!check_ksk && ksk_sanity(db, oldver))
update_log(client, zone, ISC_LOG_WARNING,
"disabling update-check-ksk");
}
/*
* If we have deleted a ZSK and we we still have some ZSK's
* we don't need to convert the KSK's to a ZSK's.
*/
if (*deleted_zsk && check_ksk)
*deleted_zsk = ISC_FALSE;
/*
* Get the NSEC's TTL from the SOA MINIMUM field.
......@@ -1845,10 +2034,17 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
* Delete all old RRSIGs covering this type, since they
* are all invalid when the signed RRset has changed.
* We may not be able to recreate all of them - tough.
* Special case changes to the zone's DNSKEY records
* to support offline KSKs.
*/
CHECK(delete_if(true_p, db, newver, name,
dns_rdatatype_rrsig, type,
NULL, &sig_diff));
fprintf(stderr, "delete signatures %u\n", type);
if (type == dns_rdatatype_dnskey)
del_keysigs(db, newver, name, &sig_diff,
zone_keys, nkeys);
else
CHECK(delete_if(true_p, db, newver, name,
dns_rdatatype_rrsig, type,
NULL, &sig_diff));
/*
* If this RRset still exists after the update,
......@@ -2349,6 +2545,52 @@ check_mx(ns_client_t *client, dns_zone_t *zone,
return (ok ? ISC_R_SUCCESS : DNS_R_REFUSED);
}
static isc_result_t
add_signing_records(dns_db_t *db, dns_name_t *name, dns_dbversion_t *ver,
dns_diff_t *diff)
{
isc_result_t result = ISC_R_SUCCESS;
dns_difftuple_t *tuple, *newtuple = NULL;
dns_rdata_dnskey_t dnskey;
dns_rdata_t rdata = DNS_RDATA_INIT;
unsigned char buf[4];
isc_region_t r;
isc_uint16_t keyid;
for (tuple = ISC_LIST_HEAD(diff->tuples);
tuple != NULL;
tuple = ISC_LIST_NEXT(tuple, link)) {
if (tuple->rdata.type != dns_rdatatype_dnskey ||
tuple->op != DNS_DIFFOP_ADD)
continue;
dns_rdata_tostruct(&tuple->rdata, &dnskey, NULL);
if ((dnskey.flags &
(DNS_KEYFLAG_OWNERMASK|DNS_KEYTYPE_NOAUTH))
!= DNS_KEYOWNER_ZONE)
continue;
dns_rdata_toregion(&tuple->rdata, &r);
keyid = dst_region_computeid(&r, dnskey.algorithm);
buf[0] = dnskey.algorithm;
buf[1] = (keyid & 0xff00) >> 8;
buf[2] = (keyid & 0xff);
buf[3] = 0;
rdata.data = buf;
rdata.length = sizeof(buf);
rdata.type = 0xFFFF; /* XXXMPA make user settable */
rdata.rdclass = tuple->rdata.rdclass;
CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, name,
0, &rdata, &newtuple));
CHECK(do_one_tuple(&newtuple, db, ver, diff));
INSIST(newtuple == NULL);
}
failure:
return (result);
}
static void
update_action(isc_task_t *task, isc_event_t *event) {
update_event_t *uev = (update_event_t *) event;
......@@ -2371,6 +2613,9 @@ update_action(isc_task_t *task, isc_event_t *event) {
dns_fixedname_t tmpnamefixed;
dns_name_t *tmpname = NULL;
unsigned int options;
isc_boolean_t deleted_zsk;
dns_difftuple_t *tuple;
dns_rdata_dnskey_t dnskey;
INSIST(event->ev_type == DNS_EVENT_UPDATE);
......@@ -2567,16 +2812,18 @@ update_action(isc_task_t *task, isc_event_t *event) {
* "Unlike traditional dynamic update, the client
* is forbidden from updating NSEC records."
*/
if (dns_db_issecure(db)) {
if (dns_db_isdnssec(db)) {
if (rdata.type == dns_rdatatype_nsec) {
FAILC(DNS_R_REFUSED,
"explicit NSEC updates are not allowed "
"in secure zones");
}
else if (rdata.type == dns_rdatatype_rrsig) {
else if (rdata.type == dns_rdatatype_rrsig &&
!dns_name_equal(name, zonename)) {
FAILC(DNS_R_REFUSED,
"explicit RRSIG updates are currently "
"not supported in secure zones");
"not supported in secure zones except "
"at the apex.");
}
}
......@@ -2852,6 +3099,7 @@ update_action(isc_task_t *task, isc_event_t *event) {
if (! ISC_LIST_EMPTY(diff.tuples)) {
char *journalfile;
dns_journal_t *journal;
isc_boolean_t has_dnskey;
/*
* Increment the SOA serial, but only if it was not
......@@ -2865,10 +3113,17 @@ update_action(isc_task_t *task, isc_event_t *event) {
CHECK(remove_orphaned_ds(db, ver, &diff));
if (dns_db_issecure(db)) {
CHECK(add_signing_records(db, zonename, ver, &diff));
CHECK(rrset_exists(db, ver, zonename, dns_rdatatype_dnskey,
0, &has_dnskey));
if (has_dnskey && dns_db_isdnssec(db)) {
isc_uint32_t interval;
interval = dns_zone_getsigvalidityinterval(zone);
result = update_signatures(client, zone, db, oldver,
ver, &diff,
dns_zone_getsigvalidityinterval(zone));
ver, &diff, interval,
&deleted_zsk);
if (result != ISC_R_SUCCESS) {
update_log(client, zone,
ISC_LOG_ERROR,
......@@ -2905,6 +3160,7 @@ update_action(isc_task_t *task, isc_event_t *event) {
*/
update_log(client, zone, LOGLEVEL_DEBUG,
"committing update transaction");
dns_db_closeversion(db, &ver, ISC_TRUE);
/*
......@@ -2916,6 +3172,35 @@ update_action(isc_task_t *task, isc_event_t *event) {
* Notify slaves of the change we just made.
*/
dns_zone_notify(zone);
for (tuple = ISC_LIST_HEAD(diff.tuples);
tuple != NULL;
tuple = ISC_LIST_NEXT(tuple, link)) {
isc_region_t r;
dns_secalg_t algorithm;
isc_uint16_t keyid;
if (tuple->rdata.type != dns_rdatatype_dnskey ||
tuple->op != DNS_DIFFOP_ADD)
continue;
dns_rdata_tostruct(&tuple->rdata, &dnskey, NULL);
if ((dnskey.flags &
(DNS_KEYFLAG_OWNERMASK|DNS_KEYTYPE_NOAUTH))
!= DNS_KEYOWNER_ZONE)
continue;
dns_rdata_toregion(&tuple->rdata, &r);
algorithm = dnskey.algorithm;
keyid = dst_region_computeid(&r, algorithm);
result = dns_zone_signwithkey(zone, algorithm, keyid);
if (result != ISC_R_SUCCESS) {
update_log(client, zone, ISC_LOG_ERROR,
"dns_zone_signwithkey failed: %s",
dns_result_totext(result));
}
}
} else {
update_log(client, zone, LOGLEVEL_DEBUG, "redundant request");
dns_db_closeversion(db, &ver, ISC_TRUE);
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: nsupdate.c,v 1.157 2008/01/18 23:46:57 tbox Exp $ */
/* $Id: nsupdate.c,v 1.158 2008/04/01 01:37:24 marka Exp $ */
/*! \file */
......@@ -1032,7 +1032,7 @@ parse_rdata(char **cmdlinep, dns_rdataclass_t rdataclass,
check_result(result, "isc_lex_openbuffer");
result = isc_buffer_allocate(mctx, &buf, MAXWIRE);
check_result(result, "isc_buffer_allocate");
result = dns_rdata_fromtext(rdata, rdataclass, rdatatype, lex,
result = dns_rdata_fromtext(NULL, rdataclass, rdatatype, lex,
dns_rootname, 0, mctx, buf,
&callbacks);
isc_lex_destroy(&lex);
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: check.c,v 1.89 2008/03/29 23:47:08 tbox Exp $ */
/* $Id: check.c,v 1.90 2008/04/01 01:37:24 marka Exp $ */
/*! \file */
......@@ -654,6 +654,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx) {
if (tresult != ISC_R_SUCCESS &&
result == ISC_R_SUCCESS)
result = tresult;
goto trust_anchor;
}
/*
* XXXMPA to be removed when multiple lookaside
......@@ -666,6 +667,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx) {
if (result == ISC_R_SUCCESS)
result = ISC_R_FAILURE;
}
trust_anchor:
dlv = cfg_obj_asstring(cfg_tuple_get(obj,
"trust-anchor"));
isc_buffer_init(&b, dlv, strlen(dlv));
......
......@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: db.c,v 1.83 2007/06/18 23:47:40 tbox Exp $ */
/* $Id: db.c,v 1.84 2008/04/01 01:37:24 marka Exp $ */
/*! \file */
......@@ -228,6 +228,21 @@ dns_db_isstub(dns_db_t *db) {
return (ISC_FALSE);
}
isc_boolean_t
dns_db_isdnssec(dns_db_t *db) {
/*
* Is 'db' secure or partially secure?
*/
REQUIRE(DNS_DB_VALID(db));
REQUIRE((db->attributes & DNS_DBATTR_CACHE) == 0);
if (db->methods->isdnssec != NULL)
return ((db->methods->isdnssec)(db));
return ((db->methods->issecure)(db));
}
isc_boolean_t
dns_db_issecure(dns_db_t *db) {
......@@ -843,3 +858,27 @@ dns_db_getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) {
return (ISC_R_NOTFOUND);
}
isc_result_t
dns_db_setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset,
isc_stdtime_t resign)
{
if (db->methods->setsigningtime != NULL)
return ((db->methods->setsigningtime)(db, rdataset, resign));
return (ISC_R_NOTIMPLEMENTED);
}