476 lines
16 KiB
Diff
476 lines
16 KiB
Diff
From b8a81c06357f0360dbb3b59b1da1108bd1574810 Mon Sep 17 00:00:00 2001
|
|
From: Andrew Bartlett <abartlet@samba.org>
|
|
Date: Fri, 1 Oct 2021 16:14:37 +1300
|
|
Subject: [PATCH] CVE-2020-25718 kdc: Confirm the RODC was allowed to issue a
|
|
particular ticket
|
|
|
|
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14558
|
|
|
|
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
|
|
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
|
|
Conflict: remove selftest/knownfail_heimdal_kdc
|
|
---
|
|
source4/auth/sam.c | 5 +-
|
|
source4/dsdb/common/rodc_helper.c | 47 ++++----
|
|
source4/kdc/mit_samba.c | 6 +-
|
|
source4/kdc/pac-glue.c | 106 +++++++++++++++++-
|
|
source4/kdc/pac-glue.h | 12 +-
|
|
source4/kdc/wdc-samba4.c | 40 ++++++-
|
|
source4/rpc_server/drsuapi/getncchanges.c | 11 +-
|
|
source4/rpc_server/netlogon/dcerpc_netlogon.c | 1 +
|
|
8 files changed, 187 insertions(+), 41 deletions(-)
|
|
|
|
diff --git a/source4/auth/sam.c b/source4/auth/sam.c
|
|
index 39e48c2..93b41be 100644
|
|
--- a/source4/auth/sam.c
|
|
+++ b/source4/auth/sam.c
|
|
@@ -57,7 +57,10 @@
|
|
\
|
|
"pwdLastSet", \
|
|
"msDS-UserPasswordExpiryTimeComputed", \
|
|
- "accountExpires"
|
|
+ "accountExpires", \
|
|
+ \
|
|
+ /* Needed for RODC rule processing */ \
|
|
+ "msDS-KrbTgtLinkBL"
|
|
|
|
const char *krbtgt_attrs[] = {
|
|
KRBTGT_ATTRS, NULL
|
|
diff --git a/source4/dsdb/common/rodc_helper.c b/source4/dsdb/common/rodc_helper.c
|
|
index 3a9636a..39f3b59 100644
|
|
--- a/source4/dsdb/common/rodc_helper.c
|
|
+++ b/source4/dsdb/common/rodc_helper.c
|
|
@@ -47,19 +47,18 @@ bool sid_list_match(uint32_t num_sids1,
|
|
|
|
/*
|
|
* Return an array of SIDs from a ldb_message given an attribute name assumes
|
|
- * the SIDs are in NDR form (with additional sids applied on the end).
|
|
+ * the SIDs are in NDR form (with primary_sid applied on the start).
|
|
*/
|
|
-WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx,
|
|
- struct ldb_message *msg,
|
|
- TALLOC_CTX *mem_ctx,
|
|
- const char *attr,
|
|
- uint32_t *num_sids,
|
|
- struct dom_sid **sids,
|
|
- const struct dom_sid *additional_sids,
|
|
- unsigned int num_additional)
|
|
+static WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx,
|
|
+ struct ldb_message *msg,
|
|
+ TALLOC_CTX *mem_ctx,
|
|
+ const char *attr,
|
|
+ uint32_t *num_sids,
|
|
+ struct dom_sid **sids,
|
|
+ const struct dom_sid *primary_sid)
|
|
{
|
|
struct ldb_message_element *el;
|
|
- unsigned int i, j;
|
|
+ unsigned int i;
|
|
|
|
el = ldb_msg_find_element(msg, attr);
|
|
if (!el) {
|
|
@@ -69,24 +68,25 @@ WERROR samdb_result_sid_array_ndr(struct ldb_context *sam_ctx,
|
|
|
|
/* Make array long enough for NULL and additional SID */
|
|
(*sids) = talloc_array(mem_ctx, struct dom_sid,
|
|
- el->num_values + num_additional);
|
|
+ el->num_values + 1);
|
|
W_ERROR_HAVE_NO_MEMORY(*sids);
|
|
|
|
- for (i=0; i<el->num_values; i++) {
|
|
+ (*sids)[0] = *primary_sid;
|
|
+
|
|
+ for (i = 0; i<el->num_values; i++) {
|
|
enum ndr_err_code ndr_err;
|
|
+ struct dom_sid sid = { 0, };
|
|
|
|
- ndr_err = ndr_pull_struct_blob_all_noalloc(&el->values[i], &(*sids)[i],
|
|
+ ndr_err = ndr_pull_struct_blob_all_noalloc(&el->values[i], &sid,
|
|
(ndr_pull_flags_fn_t)ndr_pull_dom_sid);
|
|
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
|
|
return WERR_INTERNAL_DB_CORRUPTION;
|
|
}
|
|
+ /* Primary SID is already in position zero. */
|
|
+ (*sids)[i+1] = sid;
|
|
}
|
|
|
|
- for (j = 0; j < num_additional; j++) {
|
|
- (*sids)[i++] = additional_sids[j];
|
|
- }
|
|
-
|
|
- *num_sids = i;
|
|
+ *num_sids = i+1;
|
|
|
|
return WERR_OK;
|
|
}
|
|
@@ -131,6 +131,7 @@ WERROR samdb_result_sid_array_dn(struct ldb_context *sam_ctx,
|
|
}
|
|
|
|
WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ctx,
|
|
+ const struct dom_sid *rodc_machine_account_sid,
|
|
struct ldb_message *rodc_msg,
|
|
struct ldb_message *obj_msg,
|
|
uint32_t num_token_sids,
|
|
@@ -190,6 +191,12 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct
|
|
return WERR_DS_DRA_SECRETS_DENIED;
|
|
}
|
|
|
|
+ /* The RODC can replicate and print tickets for itself. */
|
|
+ if (dom_sid_equal(&token_sids[0], rodc_machine_account_sid)) {
|
|
+ TALLOC_FREE(frame);
|
|
+ return WERR_OK;
|
|
+ }
|
|
+
|
|
if (never_reveal_sids &&
|
|
sid_list_match(num_token_sids,
|
|
token_sids,
|
|
@@ -218,6 +225,7 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to_sid_list(struct ldb_context *sam_ct
|
|
* rather than relying on the caller providing those
|
|
*/
|
|
WERROR samdb_confirm_rodc_allowed_to_repl_to(struct ldb_context *sam_ctx,
|
|
+ struct dom_sid *rodc_machine_account_sid,
|
|
struct ldb_message *rodc_msg,
|
|
struct ldb_message *obj_msg)
|
|
{
|
|
@@ -244,12 +252,13 @@ WERROR samdb_confirm_rodc_allowed_to_repl_to(struct ldb_context *sam_ctx,
|
|
frame, "tokenGroups",
|
|
&num_token_sids,
|
|
&token_sids,
|
|
- object_sid, 1);
|
|
+ object_sid);
|
|
if (!W_ERROR_IS_OK(werr) || token_sids==NULL) {
|
|
return WERR_DS_DRA_SECRETS_DENIED;
|
|
}
|
|
|
|
werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(sam_ctx,
|
|
+ rodc_machine_account_sid,
|
|
rodc_msg,
|
|
obj_msg,
|
|
num_token_sids,
|
|
diff --git a/source4/kdc/mit_samba.c b/source4/kdc/mit_samba.c
|
|
index 04be462..c6fa6af 100644
|
|
--- a/source4/kdc/mit_samba.c
|
|
+++ b/source4/kdc/mit_samba.c
|
|
@@ -440,7 +440,8 @@ int mit_samba_get_pac(struct mit_samba_context *smb_ctx,
|
|
&logon_info_blob,
|
|
cred_ndr_ptr,
|
|
&upn_dns_info_blob,
|
|
- NULL, NULL);
|
|
+ NULL, NULL,
|
|
+ NULL);
|
|
if (!NT_STATUS_IS_OK(nt_status)) {
|
|
talloc_free(tmp_ctx);
|
|
return EINVAL;
|
|
@@ -568,7 +569,8 @@ krb5_error_code mit_samba_reget_pac(struct mit_samba_context *ctx,
|
|
&pac_blob,
|
|
NULL,
|
|
&upn_blob,
|
|
- NULL, NULL);
|
|
+ NULL, NULL,
|
|
+ NULL);
|
|
if (!NT_STATUS_IS_OK(nt_status)) {
|
|
code = EINVAL;
|
|
goto done;
|
|
diff --git a/source4/kdc/pac-glue.c b/source4/kdc/pac-glue.c
|
|
index f0d61bb..92171fd 100644
|
|
--- a/source4/kdc/pac-glue.c
|
|
+++ b/source4/kdc/pac-glue.c
|
|
@@ -35,6 +35,7 @@
|
|
#include "libcli/security/security.h"
|
|
#include "dsdb/samdb/samdb.h"
|
|
#include "auth/kerberos/pac_utils.h"
|
|
+#include "source4/dsdb/common/util.h"
|
|
|
|
static
|
|
NTSTATUS samba_get_logon_info_pac_blob(TALLOC_CTX *mem_ctx,
|
|
@@ -752,13 +753,19 @@ int samba_krbtgt_is_in_db(struct samba_kdc_entry *p,
|
|
return 0;
|
|
}
|
|
|
|
+/*
|
|
+ * We return not just the blobs, but also the user_info_dc because we
|
|
+ * will need, in the RODC case, to confirm that the returned user is
|
|
+ * permitted to be replicated to the KDC
|
|
+ */
|
|
NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx,
|
|
struct samba_kdc_entry *p,
|
|
DATA_BLOB **_logon_info_blob,
|
|
DATA_BLOB **_cred_ndr_blob,
|
|
DATA_BLOB **_upn_info_blob,
|
|
DATA_BLOB **_pac_attrs_blob,
|
|
- const krb5_boolean *pac_request)
|
|
+ const krb5_boolean *pac_request,
|
|
+ struct auth_user_info_dc **_user_info_dc)
|
|
{
|
|
struct auth_user_info_dc *user_info_dc;
|
|
DATA_BLOB *logon_blob = NULL;
|
|
@@ -856,7 +863,15 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx,
|
|
}
|
|
}
|
|
|
|
- TALLOC_FREE(user_info_dc);
|
|
+ /*
|
|
+ * Return to the caller to allow a check on the allowed/denied
|
|
+ * RODC replication groups
|
|
+ */
|
|
+ if (_user_info_dc == NULL) {
|
|
+ TALLOC_FREE(user_info_dc);
|
|
+ } else {
|
|
+ *_user_info_dc = user_info_dc;
|
|
+ }
|
|
*_logon_info_blob = logon_blob;
|
|
if (_cred_ndr_blob != NULL) {
|
|
*_cred_ndr_blob = cred_blob;
|
|
@@ -1123,3 +1138,90 @@ out:
|
|
TALLOC_FREE(frame);
|
|
return code;
|
|
}
|
|
+
|
|
+
|
|
+/*
|
|
+ * In the RODC case, to confirm that the returned user is permitted to
|
|
+ * be replicated to the KDC (krbgtgt_xxx user) represented by *rodc
|
|
+ */
|
|
+WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_object_sids,
|
|
+ struct dom_sid *object_sids,
|
|
+ struct samba_kdc_entry *rodc,
|
|
+ struct samba_kdc_entry *object)
|
|
+{
|
|
+ int ret;
|
|
+ WERROR werr;
|
|
+ TALLOC_CTX *frame = talloc_stackframe();
|
|
+ const char *rodc_attrs[] = { "msDS-KrbTgtLink",
|
|
+ "msDS-NeverRevealGroup",
|
|
+ "msDS-RevealOnDemandGroup",
|
|
+ "userAccountControl",
|
|
+ "objectSid",
|
|
+ NULL };
|
|
+ struct ldb_result *rodc_machine_account = NULL;
|
|
+ struct ldb_dn *rodc_machine_account_dn = samdb_result_dn(rodc->kdc_db_ctx->samdb,
|
|
+ frame,
|
|
+ rodc->msg,
|
|
+ "msDS-KrbTgtLinkBL",
|
|
+ NULL);
|
|
+ const struct dom_sid *rodc_machine_account_sid = NULL;
|
|
+
|
|
+ if (rodc_machine_account_dn == NULL) {
|
|
+ DBG_ERR("krbtgt account %s has no msDS-KrbTgtLinkBL to find RODC machine account for allow/deny list\n",
|
|
+ ldb_dn_get_linearized(rodc->msg->dn));
|
|
+ TALLOC_FREE(frame);
|
|
+ return WERR_DS_DRA_BAD_DN;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Follow the link and get the RODC account (the krbtgt
|
|
+ * account is the krbtgt_XXX account, but the
|
|
+ * msDS-NeverRevealGroup and msDS-RevealOnDemandGroup is on
|
|
+ * the RODC$ account)
|
|
+ *
|
|
+ * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID lists
|
|
+ * out of the extended DNs
|
|
+ */
|
|
+
|
|
+ ret = dsdb_search_dn(rodc->kdc_db_ctx->samdb,
|
|
+ frame,
|
|
+ &rodc_machine_account,
|
|
+ rodc_machine_account_dn,
|
|
+ rodc_attrs,
|
|
+ DSDB_SEARCH_SHOW_EXTENDED_DN);
|
|
+ if (ret != LDB_SUCCESS) {
|
|
+ DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: %s\n",
|
|
+ ldb_dn_get_linearized(rodc_machine_account_dn),
|
|
+ ldb_dn_get_linearized(rodc->msg->dn),
|
|
+ ldb_errstring(rodc->kdc_db_ctx->samdb));
|
|
+ TALLOC_FREE(frame);
|
|
+ return WERR_DS_DRA_BAD_DN;
|
|
+ }
|
|
+
|
|
+ if (rodc_machine_account->count != 1) {
|
|
+ DBG_ERR("Failed to fetch RODC machine account %s pointed to by %s to check allow/deny list: (%d)\n",
|
|
+ ldb_dn_get_linearized(rodc_machine_account_dn),
|
|
+ ldb_dn_get_linearized(rodc->msg->dn),
|
|
+ rodc_machine_account->count);
|
|
+ TALLOC_FREE(frame);
|
|
+ return WERR_DS_DRA_BAD_DN;
|
|
+ }
|
|
+
|
|
+ /* if the object SID is equal to the user_sid, allow */
|
|
+ rodc_machine_account_sid = samdb_result_dom_sid(frame,
|
|
+ rodc_machine_account->msgs[0],
|
|
+ "objectSid");
|
|
+ if (rodc_machine_account_sid == NULL) {
|
|
+ return WERR_DS_DRA_BAD_DN;
|
|
+ }
|
|
+
|
|
+ werr = samdb_confirm_rodc_allowed_to_repl_to_sid_list(rodc->kdc_db_ctx->samdb,
|
|
+ rodc_machine_account_sid,
|
|
+ rodc_machine_account->msgs[0],
|
|
+ object->msg,
|
|
+ num_object_sids,
|
|
+ object_sids);
|
|
+
|
|
+ TALLOC_FREE(frame);
|
|
+ return werr;
|
|
+}
|
|
diff --git a/source4/kdc/pac-glue.h b/source4/kdc/pac-glue.h
|
|
index 1b6264c..3b761f4 100644
|
|
--- a/source4/kdc/pac-glue.h
|
|
+++ b/source4/kdc/pac-glue.h
|
|
@@ -52,7 +52,8 @@ NTSTATUS samba_kdc_get_pac_blobs(TALLOC_CTX *mem_ctx,
|
|
DATA_BLOB **_cred_ndr_blob,
|
|
DATA_BLOB **_upn_info_blob,
|
|
DATA_BLOB **_pac_attrs_blob,
|
|
- const krb5_boolean *pac_request);
|
|
+ const krb5_boolean *pac_request,
|
|
+ struct auth_user_info_dc **_user_info_dc);
|
|
NTSTATUS samba_kdc_get_pac_blob(TALLOC_CTX *mem_ctx,
|
|
struct samba_kdc_entry *skdc_entry,
|
|
DATA_BLOB **_logon_info_blob);
|
|
@@ -82,3 +83,12 @@ krb5_error_code samba_kdc_validate_pac_blob(
|
|
krb5_context context,
|
|
struct samba_kdc_entry *client_skdc_entry,
|
|
const krb5_pac pac);
|
|
+
|
|
+/*
|
|
+ * In the RODC case, to confirm that the returned user is permitted to
|
|
+ * be replicated to the KDC (krbgtgt_xxx user) represented by *rodc
|
|
+ */
|
|
+WERROR samba_rodc_confirm_user_is_allowed(uint32_t num_sids,
|
|
+ struct dom_sid *sids,
|
|
+ struct samba_kdc_entry *rodc,
|
|
+ struct samba_kdc_entry *object);
|
|
diff --git a/source4/kdc/wdc-samba4.c b/source4/kdc/wdc-samba4.c
|
|
index b73dc84..51640b1 100644
|
|
--- a/source4/kdc/wdc-samba4.c
|
|
+++ b/source4/kdc/wdc-samba4.c
|
|
@@ -27,6 +27,7 @@
|
|
#include "kdc/pac-glue.h"
|
|
#include "sdb.h"
|
|
#include "sdb_hdb.h"
|
|
+#include "librpc/gen_ndr/auth.h"
|
|
|
|
/*
|
|
* Given the right private pointer from hdb_samba4,
|
|
@@ -68,7 +69,8 @@ static krb5_error_code samba_wdc_get_pac(void *priv, krb5_context context,
|
|
cred_ndr_ptr,
|
|
&upn_blob,
|
|
&pac_attrs_blob,
|
|
- pac_request);
|
|
+ pac_request,
|
|
+ NULL);
|
|
if (!NT_STATUS_IS_OK(nt_status)) {
|
|
talloc_free(mem_ctx);
|
|
return EINVAL;
|
|
@@ -161,9 +163,15 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context,
|
|
}
|
|
}
|
|
|
|
- /* If the krbtgt was generated by an RODC, and we are not that
|
|
+ /*
|
|
+ * If the krbtgt was generated by an RODC, and we are not that
|
|
* RODC, then we need to regenerate the PAC - we can't trust
|
|
- * it */
|
|
+ * it, and confirm that the RODC was permitted to print this ticket
|
|
+ *
|
|
+ * Becasue of the samba_kdc_validate_pac_blob() step we can be
|
|
+ * sure that the record in 'client' matches the SID in the
|
|
+ * original PAC.
|
|
+ */
|
|
ret = samba_krbtgt_is_in_db(krbtgt_skdc_entry, &is_in_db, &is_untrusted);
|
|
if (ret != 0) {
|
|
talloc_free(mem_ctx);
|
|
@@ -237,6 +245,8 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context,
|
|
|
|
if (is_untrusted) {
|
|
struct samba_kdc_entry *client_skdc_entry = NULL;
|
|
+ struct auth_user_info_dc *user_info_dc = NULL;
|
|
+ WERROR werr;
|
|
|
|
if (client == NULL) {
|
|
return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
|
|
@@ -247,12 +257,30 @@ static krb5_error_code samba_wdc_reget_pac2(krb5_context context,
|
|
|
|
nt_status = samba_kdc_get_pac_blobs(mem_ctx, client_skdc_entry,
|
|
&pac_blob, NULL, &upn_blob,
|
|
- NULL, NULL);
|
|
+ NULL, NULL,
|
|
+ &user_info_dc);
|
|
if (!NT_STATUS_IS_OK(nt_status)) {
|
|
talloc_free(mem_ctx);
|
|
- return EINVAL;
|
|
+ return KRB5KDC_ERR_TGT_REVOKED;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Now check if the SID list in the user_info_dc
|
|
+ * intersects correctly with the RODC allow/deny
|
|
+ * lists
|
|
+ */
|
|
+
|
|
+ werr = samba_rodc_confirm_user_is_allowed(user_info_dc->num_sids,
|
|
+ user_info_dc->sids,
|
|
+ krbtgt_skdc_entry,
|
|
+ client_skdc_entry);
|
|
+ if (!W_ERROR_IS_OK(werr)) {
|
|
+ talloc_free(mem_ctx);
|
|
+ return KRB5KDC_ERR_TGT_REVOKED;
|
|
}
|
|
- } else {
|
|
+ }
|
|
+
|
|
+ if (!is_untrusted) {
|
|
pac_blob = talloc_zero(mem_ctx, DATA_BLOB);
|
|
if (!pac_blob) {
|
|
talloc_free(mem_ctx);
|
|
diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c
|
|
index 0d36a94..7084801 100644
|
|
--- a/source4/rpc_server/drsuapi/getncchanges.c
|
|
+++ b/source4/rpc_server/drsuapi/getncchanges.c
|
|
@@ -1177,7 +1177,6 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state,
|
|
NULL };
|
|
const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL };
|
|
struct ldb_result *rodc_res = NULL, *obj_res = NULL;
|
|
- const struct dom_sid *object_sid = NULL;
|
|
WERROR werr;
|
|
|
|
DEBUG(3,(__location__ ": DRSUAPI_EXOP_REPL_SECRET extended op on %s\n",
|
|
@@ -1261,15 +1260,6 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state,
|
|
ret = dsdb_search_dn(b_state->sam_ctx_system, mem_ctx, &obj_res, obj_dn, obj_attrs, 0);
|
|
if (ret != LDB_SUCCESS || obj_res->count != 1) goto failed;
|
|
|
|
- /* if the object SID is equal to the user_sid, allow */
|
|
- object_sid = samdb_result_dom_sid(mem_ctx, obj_res->msgs[0], "objectSid");
|
|
- if (object_sid == NULL) {
|
|
- goto failed;
|
|
- }
|
|
- if (dom_sid_equal(user_sid, object_sid)) {
|
|
- goto allowed;
|
|
- }
|
|
-
|
|
/*
|
|
* Must be an RODC account at this point, verify machine DN matches the
|
|
* SID account
|
|
@@ -1287,6 +1277,7 @@ static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state,
|
|
}
|
|
|
|
werr = samdb_confirm_rodc_allowed_to_repl_to(b_state->sam_ctx_system,
|
|
+ user_sid,
|
|
rodc_res->msgs[0],
|
|
obj_res->msgs[0]);
|
|
|
|
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
|
|
index 11e8280..171b269 100644
|
|
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
|
|
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
|
|
@@ -2810,6 +2810,7 @@ static bool sam_rodc_access_check(struct ldb_context *sam_ctx,
|
|
if (ret != LDB_SUCCESS || obj_res->count != 1) goto denied;
|
|
|
|
werr = samdb_confirm_rodc_allowed_to_repl_to(sam_ctx,
|
|
+ user_sid,
|
|
rodc_res->msgs[0],
|
|
obj_res->msgs[0]);
|
|
|
|
--
|
|
2.27.0
|
|
|