r5928: Use cli_credentials in:
[samba.git] / source4 / libcli / auth / gensec.c
index 24c2c18877010799eb50d0d52f42e5de3138d482..69de016156729ca2aaef0f6f11e65c8bf9d7abbd 100644 (file)
@@ -22,6 +22,7 @@
 */
 
 #include "includes.h"
+#include "auth/auth.h"
 
 /* the list of currently registered GENSEC backends */
 const static struct gensec_security_ops **generic_security_ops;
@@ -93,7 +94,7 @@ const char **gensec_security_oids(TALLOC_CTX *mem_ctx, const char *skip)
        if (!ops) {
                return NULL;
        }
-       oid_list = talloc_array_p(mem_ctx, const char *, num_backends + 1);
+       oid_list = talloc_array(mem_ctx, const char *, num_backends + 1);
        if (!oid_list) {
                return NULL;
        }
@@ -114,26 +115,19 @@ const char **gensec_security_oids(TALLOC_CTX *mem_ctx, const char *skip)
        return oid_list;
 }
 
-static NTSTATUS gensec_start(struct gensec_security **gensec_security) 
+/**
+  Start the GENSEC system, returning a context pointer.
+  @param mem_ctx The parent TALLOC memory context.
+  @param gensec_security Returned GENSEC context pointer.
+  @note  The mem_ctx is only a parent and may be NULL.
+*/
+static NTSTATUS gensec_start(TALLOC_CTX *mem_ctx, struct gensec_security **gensec_security) 
 {
-       TALLOC_CTX *mem_ctx;
-       /* awaiting a correct fix from metze */
-       if (!gensec_init()) {
-               return NT_STATUS_INTERNAL_ERROR;
-       }
-
-       mem_ctx = talloc_init("gensec_security struct");
-       if (!mem_ctx) {
-               return NT_STATUS_NO_MEMORY;
-       }
-
-       (*gensec_security) = talloc_p(mem_ctx, struct gensec_security);
+       (*gensec_security) = talloc(mem_ctx, struct gensec_security);
        if (!(*gensec_security)) {
-               talloc_destroy(mem_ctx);
                return NT_STATUS_NO_MEMORY;
        }
 
-       (*gensec_security)->mem_ctx = mem_ctx;
        (*gensec_security)->ops = NULL;
 
        ZERO_STRUCT((*gensec_security)->user);
@@ -141,23 +135,27 @@ static NTSTATUS gensec_start(struct gensec_security **gensec_security)
        ZERO_STRUCT((*gensec_security)->default_user);
 
        (*gensec_security)->default_user.name = "";
-       (*gensec_security)->default_user.domain = talloc_strdup(mem_ctx, lp_workgroup());
-       (*gensec_security)->default_user.realm = talloc_strdup(mem_ctx, lp_realm());
+       (*gensec_security)->default_user.domain = talloc_strdup(*gensec_security, lp_workgroup());
+       (*gensec_security)->default_user.realm = talloc_strdup(*gensec_security, lp_realm());
 
        (*gensec_security)->subcontext = False;
+       (*gensec_security)->want_features = 0;
        return NT_STATUS_OK;
 }
 
 /** 
  * Start a GENSEC subcontext, with a copy of the properties of the parent
- *
- * @note Used by SPENGO in particular, for the actual implementation mechanism
+ * @param mem_ctx The parent TALLOC memory context.
+ * @param parent The parent GENSEC context 
+ * @param gensec_security Returned GENSEC context pointer.
+ * @note Used by SPNEGO in particular, for the actual implementation mechanism
  */
 
-NTSTATUS gensec_subcontext_start(struct gensec_security *parent, 
+NTSTATUS gensec_subcontext_start(TALLOC_CTX *mem_ctx, 
+                                struct gensec_security *parent, 
                                 struct gensec_security **gensec_security)
 {
-       (*gensec_security) = talloc_p(parent->mem_ctx, struct gensec_security);
+       (*gensec_security) = talloc(mem_ctx, struct gensec_security);
        if (!(*gensec_security)) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -171,10 +169,16 @@ NTSTATUS gensec_subcontext_start(struct gensec_security *parent,
        return NT_STATUS_OK;
 }
 
-NTSTATUS gensec_client_start(struct gensec_security **gensec_security)
+/**
+  Start the GENSEC system, in client mode, returning a context pointer.
+  @param mem_ctx The parent TALLOC memory context.
+  @param gensec_security Returned GENSEC context pointer.
+  @note  The mem_ctx is only a parent and may be NULL.
+*/
+NTSTATUS gensec_client_start(TALLOC_CTX *mem_ctx, struct gensec_security **gensec_security)
 {
        NTSTATUS status;
-       status = gensec_start(gensec_security);
+       status = gensec_start(mem_ctx, gensec_security);
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
@@ -186,10 +190,16 @@ NTSTATUS gensec_client_start(struct gensec_security **gensec_security)
        return status;
 }
 
-NTSTATUS gensec_server_start(struct gensec_security **gensec_security)
+/**
+  Start the GENSEC system, in server mode, returning a context pointer.
+  @param mem_ctx The parent TALLOC memory context.
+  @param gensec_security Returned GENSEC context pointer.
+  @note  The mem_ctx is only a parent and may be NULL.
+*/
+NTSTATUS gensec_server_start(TALLOC_CTX *mem_ctx, struct gensec_security **gensec_security)
 {
        NTSTATUS status;
-       status = gensec_start(gensec_security);
+       status = gensec_start(mem_ctx, gensec_security);
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
@@ -209,7 +219,7 @@ static NTSTATUS gensec_start_mech(struct gensec_security *gensec_security)
                if (gensec_security->ops->client_start) {
                        status = gensec_security->ops->client_start(gensec_security);
                        if (!NT_STATUS_IS_OK(status)) {
-                               DEBUG(1, ("Faild to start GENSEC client mech %s: %s\n",
+                               DEBUG(1, ("Failed to start GENSEC client mech %s: %s\n",
                                          gensec_security->ops->name, nt_errstr(status))); 
                        }
                        return status;
@@ -218,7 +228,7 @@ static NTSTATUS gensec_start_mech(struct gensec_security *gensec_security)
                if (gensec_security->ops->server_start) {
                        status = gensec_security->ops->server_start(gensec_security);
                        if (!NT_STATUS_IS_OK(status)) {
-                               DEBUG(1, ("Faild to start GENSEC server mech %s: %s\n",
+                               DEBUG(1, ("Failed to start GENSEC server mech %s: %s\n",
                                          gensec_security->ops->name, nt_errstr(status))); 
                        }
                        return status;
@@ -229,16 +239,28 @@ static NTSTATUS gensec_start_mech(struct gensec_security *gensec_security)
 
 /** 
  * Start a GENSEC sub-mechanism by DCERPC allocated 'auth type' number 
+ * @param gensec_security GENSEC context pointer.
+ * @param auth_type DCERPC auth type
+ * @param auth_level DCERPC auth level 
  */
 
 NTSTATUS gensec_start_mech_by_authtype(struct gensec_security *gensec_security, 
-                                      uint8_t authtype
+                                      uint8_t auth_type, uint8_t auth_level
 {
-       gensec_security->ops = gensec_security_by_authtype(authtype);
+       gensec_security->ops = gensec_security_by_authtype(auth_type);
        if (!gensec_security->ops) {
-               DEBUG(3, ("Could not find GENSEC backend for authtype=%d\n", (int)authtype));
+               DEBUG(3, ("Could not find GENSEC backend for auth_type=%d\n", (int)auth_type));
                return NT_STATUS_INVALID_PARAMETER;
        }
+       gensec_want_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE);
+       if (auth_level == DCERPC_AUTH_LEVEL_INTEGRITY) {
+               gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN);
+       }
+       if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) {
+               gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN);
+               gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
+       }
+
        return gensec_start_mech(gensec_security);
 }
 
@@ -253,6 +275,17 @@ const char *gensec_get_name_by_authtype(uint8_t authtype)
 }
        
 
+const char *gensec_get_name_by_oid(const char *oid_string) 
+{
+       const struct gensec_security_ops *ops;
+       ops = gensec_security_by_oid(oid_string);
+       if (ops) {
+               return ops->name;
+       }
+       return NULL;
+}
+       
+
 /** 
  * Start a GENSEC sub-mechanism by OID, used in SPNEGO
  *
@@ -292,45 +325,115 @@ NTSTATUS gensec_start_mech_by_sasl_name(struct gensec_security *gensec_security,
 */
 NTSTATUS gensec_unseal_packet(struct gensec_security *gensec_security, 
                              TALLOC_CTX *mem_ctx, 
-                             uint8_t *data, size_t length, DATA_BLOB *sig)
+                             uint8_t *data, size_t length, 
+                             const uint8_t *whole_pdu, size_t pdu_length, 
+                             DATA_BLOB *sig)
 {
        if (!gensec_security->ops->unseal_packet) {
                return NT_STATUS_NOT_IMPLEMENTED;
        }
-       return gensec_security->ops->unseal_packet(gensec_security, mem_ctx, data, length, sig);
+       if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
+               if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) {
+                       return gensec_check_packet(gensec_security, mem_ctx, 
+                                                  data, length, 
+                                                  whole_pdu, pdu_length, 
+                                                  sig);
+               }
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       return gensec_security->ops->unseal_packet(gensec_security, mem_ctx, 
+                                                  data, length, 
+                                                  whole_pdu, pdu_length, 
+                                                  sig);
 }
 
 NTSTATUS gensec_check_packet(struct gensec_security *gensec_security, 
                             TALLOC_CTX *mem_ctx, 
                             const uint8_t *data, size_t length, 
+                            const uint8_t *whole_pdu, size_t pdu_length, 
                             const DATA_BLOB *sig)
 {
        if (!gensec_security->ops->check_packet) {
                return NT_STATUS_NOT_IMPLEMENTED;
        }
-       return gensec_security->ops->check_packet(gensec_security, mem_ctx, data, length, sig);
+       if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+       
+       return gensec_security->ops->check_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig);
 }
 
 NTSTATUS gensec_seal_packet(struct gensec_security *gensec_security, 
                            TALLOC_CTX *mem_ctx, 
                            uint8_t *data, size_t length, 
+                           const uint8_t *whole_pdu, size_t pdu_length, 
                            DATA_BLOB *sig)
 {
        if (!gensec_security->ops->seal_packet) {
                return NT_STATUS_NOT_IMPLEMENTED;
        }
-       return gensec_security->ops->seal_packet(gensec_security, mem_ctx, data, length, sig);
+       if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
+               if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) {
+                       return gensec_sign_packet(gensec_security, mem_ctx, 
+                                                 data, length, 
+                                                 whole_pdu, pdu_length, 
+                                                 sig);
+               }
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       return gensec_security->ops->seal_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig);
 }
 
 NTSTATUS gensec_sign_packet(struct gensec_security *gensec_security, 
                            TALLOC_CTX *mem_ctx, 
                            const uint8_t *data, size_t length, 
+                           const uint8_t *whole_pdu, size_t pdu_length, 
                            DATA_BLOB *sig)
 {
        if (!gensec_security->ops->sign_packet) {
                return NT_STATUS_NOT_IMPLEMENTED;
        }
-       return gensec_security->ops->sign_packet(gensec_security, mem_ctx, data, length, sig);
+       if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+       
+       return gensec_security->ops->sign_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig);
+}
+
+size_t gensec_sig_size(struct gensec_security *gensec_security) 
+{
+       if (!gensec_security->ops->sig_size) {
+               return 0;
+       }
+       if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) {
+               return 0;
+       }
+       
+       return gensec_security->ops->sig_size(gensec_security);
+}
+
+NTSTATUS gensec_wrap(struct gensec_security *gensec_security, 
+                    TALLOC_CTX *mem_ctx, 
+                    const DATA_BLOB *in, 
+                    DATA_BLOB *out) 
+{
+       if (!gensec_security->ops->wrap) {
+               return NT_STATUS_NOT_IMPLEMENTED;
+       }
+       return gensec_security->ops->wrap(gensec_security, mem_ctx, in, out);
+}
+
+NTSTATUS gensec_unwrap(struct gensec_security *gensec_security, 
+                      TALLOC_CTX *mem_ctx, 
+                      const DATA_BLOB *in, 
+                      DATA_BLOB *out) 
+{
+       if (!gensec_security->ops->unwrap) {
+               return NT_STATUS_NOT_IMPLEMENTED;
+       }
+       return gensec_security->ops->unwrap(gensec_security, mem_ctx, in, out);
 }
 
 NTSTATUS gensec_session_key(struct gensec_security *gensec_security, 
@@ -378,73 +481,29 @@ NTSTATUS gensec_update(struct gensec_security *gensec_security, TALLOC_CTX *out_
        return gensec_security->ops->update(gensec_security, out_mem_ctx, in, out);
 }
 
-void gensec_end(struct gensec_security **gensec_security)
-{
-       if ((*gensec_security)->ops) {
-               (*gensec_security)->ops->end(*gensec_security);
-       }
-       (*gensec_security)->private_data = NULL;
+/** 
+ * Set the requirement for a certain feature on the connection
+ *
+ */
 
-       if (!(*gensec_security)->subcontext) {
-               /* don't destory this if this is a subcontext - it belongs to the parent */
-               talloc_destroy((*gensec_security)->mem_ctx);
-       }
-       gensec_security = NULL;
+void gensec_want_feature(struct gensec_security *gensec_security,
+                        uint32_t feature) 
+{
+       gensec_security->want_features |= feature;
 }
 
 /** 
- * Set a username on a GENSEC context - ensures it is talloc()ed 
+ * Check the requirement for a certain feature on the connection
  *
  */
 
-NTSTATUS gensec_set_unparsed_username(struct gensec_security *gensec_security, const char *user) 
+BOOL gensec_have_feature(struct gensec_security *gensec_security,
+                        uint32_t feature) 
 {
-       char *p;
-       char *u = talloc_strdup(gensec_security->mem_ctx, user);
-       if (!u) {
-               return NT_STATUS_NO_MEMORY;
-       }
-
-       p = strchr_m(user, '@');
-       
-       if (p) {
-               *p = '\0';
-               gensec_security->user.name = talloc_strdup(gensec_security->mem_ctx, u);
-               if (!gensec_security->user.name) {
-                       return NT_STATUS_NO_MEMORY;
-               }
-               
-               gensec_security->user.realm = talloc_strdup(gensec_security->mem_ctx, p+1);
-               if (!gensec_security->user.realm) {
-                       return NT_STATUS_NO_MEMORY;
-               }
-               return NT_STATUS_OK;
-       } 
-
-       p = strchr_m(user, '\\');
-       if (!p) {
-               p = strchr_m(user, '/');
-       }
-       
-       if (p) {
-               *p = '\0';
-               gensec_security->user.domain = talloc_strdup(gensec_security->mem_ctx, u);
-               if (!gensec_security->user.domain) {
-                       return NT_STATUS_NO_MEMORY;
-               }
-               gensec_security->user.name = talloc_strdup(gensec_security->mem_ctx, p+1);
-               if (!gensec_security->user.name) {
-                       return NT_STATUS_NO_MEMORY;
-               }
-               
-               return NT_STATUS_OK;
-       } 
-       
-       gensec_security->user.name = u;
-       if (!gensec_security->user.name) {
-               return NT_STATUS_NO_MEMORY;
+       if (!gensec_security->ops->have_feature) {
+               return False;
        }
-       return NT_STATUS_OK;
+       return gensec_security->ops->have_feature(gensec_security, feature);
 }
 
 /** 
@@ -454,8 +513,8 @@ NTSTATUS gensec_set_unparsed_username(struct gensec_security *gensec_security, c
 
 NTSTATUS gensec_set_username(struct gensec_security *gensec_security, const char *user) 
 {
-       gensec_security->user.name = talloc_strdup(gensec_security->mem_ctx, user);
-       if (!gensec_security->user.name) {
+       gensec_security->user.name = talloc_strdup(gensec_security, user);
+       if (user && !gensec_security->user.name) {
                return NT_STATUS_NO_MEMORY;
        }
        return NT_STATUS_OK;
@@ -481,8 +540,8 @@ const char *gensec_get_username(struct gensec_security *gensec_security)
 
 NTSTATUS gensec_set_domain(struct gensec_security *gensec_security, const char *domain) 
 {
-       gensec_security->user.domain = talloc_strdup(gensec_security->mem_ctx, domain);
-       if (!gensec_security->user.domain) {
+       gensec_security->user.domain = talloc_strdup(gensec_security, domain);
+       if (domain && !gensec_security->user.domain) {
                return NT_STATUS_NO_MEMORY;
        }
        return NT_STATUS_OK;
@@ -503,6 +562,34 @@ const char *gensec_get_domain(struct gensec_security *gensec_security)
        return gensec_security->default_user.domain;
 }
 
+/** 
+ * Set the client workstation on a GENSEC context - ensures it is talloc()ed 
+ *
+ */
+
+NTSTATUS gensec_set_workstation(struct gensec_security *gensec_security, const char *workstation) 
+{
+       gensec_security->user.workstation = talloc_strdup(gensec_security, workstation);
+       if (workstation && !gensec_security->user.workstation) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       return NT_STATUS_OK;
+}
+
+/** 
+ * Return the client workstation on a GENSEC context - ensures it is talloc()ed 
+ *
+ */
+
+const char *gensec_get_workstation(struct gensec_security *gensec_security) 
+{
+       if (gensec_security->user.workstation) {
+               return gensec_security->user.workstation;
+       } else {
+               return lp_netbios_name();
+       }
+}
+
 /** 
  * Set a kerberos realm on a GENSEC context - ensures it is talloc()ed 
  *
@@ -510,8 +597,8 @@ const char *gensec_get_domain(struct gensec_security *gensec_security)
 
 NTSTATUS gensec_set_realm(struct gensec_security *gensec_security, const char *realm) 
 {
-       gensec_security->user.realm = talloc_strdup(gensec_security->mem_ctx, realm);
-       if (!gensec_security->user.realm) {
+       gensec_security->user.realm = talloc_strdup(gensec_security, realm);
+       if (realm && !gensec_security->user.realm) {
                return NT_STATUS_NO_MEMORY;
        }
        return NT_STATUS_OK;
@@ -558,8 +645,8 @@ char *gensec_get_client_principal(struct gensec_security *gensec_security, TALLO
 NTSTATUS gensec_set_password(struct gensec_security *gensec_security,
                             const char *password) 
 {
-       gensec_security->user.password = talloc_strdup(gensec_security->mem_ctx, password);
-       if (!gensec_security->user.password) {
+       gensec_security->user.password = talloc_strdup(gensec_security, password);
+       if (password && !gensec_security->user.password) {
                return NT_STATUS_NO_MEMORY;
        }
        return NT_STATUS_OK;
@@ -572,7 +659,7 @@ NTSTATUS gensec_set_password(struct gensec_security *gensec_security,
 
 NTSTATUS gensec_set_target_principal(struct gensec_security *gensec_security, const char *principal) 
 {
-       gensec_security->target.principal = talloc_strdup(gensec_security->mem_ctx, principal);
+       gensec_security->target.principal = talloc_strdup(gensec_security, principal);
        if (!gensec_security->target.principal) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -586,7 +673,7 @@ NTSTATUS gensec_set_target_principal(struct gensec_security *gensec_security, co
 
 NTSTATUS gensec_set_target_service(struct gensec_security *gensec_security, const char *service) 
 {
-       gensec_security->target.service = talloc_strdup(gensec_security->mem_ctx, service);
+       gensec_security->target.service = talloc_strdup(gensec_security, service);
        if (!gensec_security->target.service) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -600,7 +687,7 @@ NTSTATUS gensec_set_target_service(struct gensec_security *gensec_security, cons
 
 NTSTATUS gensec_set_target_hostname(struct gensec_security *gensec_security, const char *hostname) 
 {
-       gensec_security->target.hostname = talloc_strdup(gensec_security->mem_ctx, hostname);
+       gensec_security->target.hostname = talloc_strdup(gensec_security, hostname);
        if (!gensec_security->target.hostname) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -626,6 +713,20 @@ const char *gensec_get_target_service(struct gensec_security *gensec_security)
        return "host";
 }
 
+const char *gensec_get_target_principal(struct gensec_security *gensec_security) 
+{
+       const char *mechListMIC;
+       
+       if (gensec_security->target.principal) {
+               return gensec_security->target.principal;
+       }
+
+       mechListMIC = talloc_asprintf(gensec_security,"%s$@%s",
+                                     lp_netbios_name(),
+                                     lp_realm());
+       return mechListMIC;
+}
+
 /** 
  * Set a password callback, if the gensec module we use demands a password
  */
@@ -654,7 +755,8 @@ NTSTATUS gensec_get_password(struct gensec_security *gensec_security,
                }
        }
        if (!gensec_security->password_callback) {
-               return NT_STATUS_INVALID_PARAMETER;
+               *password = NULL;
+               return NT_STATUS_OK;
        }
        return gensec_security->password_callback(gensec_security, mem_ctx, password);
 }
@@ -665,10 +767,15 @@ NTSTATUS gensec_get_password(struct gensec_security *gensec_security,
   The 'name' can be later used by other backends to find the operations
   structure for this backend.
 */
-static NTSTATUS gensec_register(const void *_ops)
+NTSTATUS gensec_register(const void *_ops)
 {
        const struct gensec_security_ops *ops = _ops;
        
+       if (!lp_parm_bool(-1, "gensec", ops->name, ops->enabled)) {
+               DEBUG(2,("gensec subsystem %s is disabled\n", ops->name));
+               return NT_STATUS_OK;
+       }
+
        if (gensec_security_by_name(ops->name) != NULL) {
                /* its already registered! */
                DEBUG(0,("GENSEC backend '%s' already registered\n", 
@@ -676,7 +783,9 @@ static NTSTATUS gensec_register(const void *_ops)
                return NT_STATUS_OBJECT_NAME_COLLISION;
        }
 
-       generic_security_ops = Realloc(generic_security_ops, sizeof(generic_security_ops[0]) * (gensec_num_backends+1));
+       generic_security_ops = realloc_p(generic_security_ops, 
+                                        const struct gensec_security_ops *, 
+                                        gensec_num_backends+1);
        if (!generic_security_ops) {
                smb_panic("out of memory in gensec_register");
        }
@@ -710,25 +819,8 @@ const struct gensec_critical_sizes *gensec_interface_version(void)
 /*
   initialise the GENSEC subsystem
 */
-BOOL gensec_init(void)
+NTSTATUS gensec_init(void)
 {
-       static BOOL initialised;
-       NTSTATUS status;
-
-       /* this is *completely* the wrong way to do this */
-       if (initialised) {
-               return True;
-       }
-
-       status = register_subsystem("gensec", gensec_register); 
-       if (!NT_STATUS_IS_OK(status)) {
-               return False;
-       }
-
-       static_init_gensec;
        gensec_dcerpc_schannel_init();
-
-       initialised = True;
-       DEBUG(3,("GENSEC subsystem version %d initialised\n", GENSEC_INTERFACE_VERSION));
-       return True;
+       return NT_STATUS_OK;
 }