2 Unix SMB/CIFS mplementation.
3 NDS LDAP helper functions for SAMBA
4 Copyright (C) Vince Brimhall 2004-2005
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
29 #include "passdb/pdb_ldap.h"
30 #include "passdb/pdb_nds.h"
32 #define NMASLDAP_GET_LOGIN_CONFIG_REQUEST "2.16.840.1.113719.1.39.42.100.3"
33 #define NMASLDAP_GET_LOGIN_CONFIG_RESPONSE "2.16.840.1.113719.1.39.42.100.4"
34 #define NMASLDAP_SET_PASSWORD_REQUEST "2.16.840.1.113719.1.39.42.100.11"
35 #define NMASLDAP_SET_PASSWORD_RESPONSE "2.16.840.1.113719.1.39.42.100.12"
36 #define NMASLDAP_GET_PASSWORD_REQUEST "2.16.840.1.113719.1.39.42.100.13"
37 #define NMASLDAP_GET_PASSWORD_RESPONSE "2.16.840.1.113719.1.39.42.100.14"
39 #define NMAS_LDAP_EXT_VERSION 1
41 /**********************************************************************
42 Take the request BER value and input data items and BER encodes the
43 data into the BER value
44 **********************************************************************/
46 static int berEncodePasswordData(
47 struct berval
**requestBV
,
50 const char *password2
)
53 BerElement
*requestBer
= NULL
;
55 const char * utf8ObjPtr
= NULL
;
57 const char * utf8PwdPtr
= NULL
;
59 const char * utf8Pwd2Ptr
= NULL
;
63 /* Convert objectDN and tag strings from Unicode to UTF-8 */
64 utf8ObjSize
= strlen(objectDN
)+1;
65 utf8ObjPtr
= objectDN
;
69 utf8PwdSize
= strlen(password
)+1;
70 utf8PwdPtr
= password
;
73 if (password2
!= NULL
)
75 utf8Pwd2Size
= strlen(password2
)+1;
76 utf8Pwd2Ptr
= password2
;
79 /* Allocate a BerElement for the request parameters. */
80 if((requestBer
= ber_alloc()) == NULL
)
82 err
= LDAP_ENCODING_ERROR
;
86 if (password
!= NULL
&& password2
!= NULL
)
88 /* BER encode the NMAS Version, the objectDN, and the password */
89 rc
= ber_printf(requestBer
, "{iooo}", NMAS_LDAP_EXT_VERSION
, utf8ObjPtr
, utf8ObjSize
, utf8PwdPtr
, utf8PwdSize
, utf8Pwd2Ptr
, utf8Pwd2Size
);
91 else if (password
!= NULL
)
93 /* BER encode the NMAS Version, the objectDN, and the password */
94 rc
= ber_printf(requestBer
, "{ioo}", NMAS_LDAP_EXT_VERSION
, utf8ObjPtr
, utf8ObjSize
, utf8PwdPtr
, utf8PwdSize
);
98 /* BER encode the NMAS Version and the objectDN */
99 rc
= ber_printf(requestBer
, "{io}", NMAS_LDAP_EXT_VERSION
, utf8ObjPtr
, utf8ObjSize
);
104 err
= LDAP_ENCODING_ERROR
;
112 /* Convert the BER we just built to a berval that we'll send with the extended request. */
113 if(ber_flatten(requestBer
, requestBV
) == LBER_ERROR
)
115 err
= LDAP_ENCODING_ERROR
;
123 ber_free(requestBer
, 1);
129 /**********************************************************************
130 Take the request BER value and input data items and BER encodes the
131 data into the BER value
132 **********************************************************************/
134 static int berEncodeLoginData(
135 struct berval
**requestBV
,
137 unsigned int methodIDLen
,
138 unsigned int *methodID
,
144 BerElement
*requestBer
= NULL
;
147 unsigned int elemCnt
= methodIDLen
/ sizeof(unsigned int);
149 char *utf8ObjPtr
=NULL
;
152 char *utf8TagPtr
= NULL
;
155 utf8ObjPtr
= objectDN
;
156 utf8ObjSize
= strlen(utf8ObjPtr
)+1;
159 utf8TagSize
= strlen(utf8TagPtr
)+1;
161 /* Allocate a BerElement for the request parameters. */
162 if((requestBer
= ber_alloc()) == NULL
)
164 err
= LDAP_ENCODING_ERROR
;
168 /* BER encode the NMAS Version and the objectDN */
169 err
= (ber_printf(requestBer
, "{io", NMAS_LDAP_EXT_VERSION
, utf8ObjPtr
, utf8ObjSize
) < 0) ? LDAP_ENCODING_ERROR
: 0;
171 /* BER encode the MethodID Length and value */
174 err
= (ber_printf(requestBer
, "{i{", methodIDLen
) < 0) ? LDAP_ENCODING_ERROR
: 0;
177 for (i
= 0; !err
&& i
< elemCnt
; i
++)
179 err
= (ber_printf(requestBer
, "i", methodID
[i
]) < 0) ? LDAP_ENCODING_ERROR
: 0;
184 err
= (ber_printf(requestBer
, "}}", 0) < 0) ? LDAP_ENCODING_ERROR
: 0;
189 /* BER Encode the the tag and data */
190 err
= (ber_printf(requestBer
, "oio}", utf8TagPtr
, utf8TagSize
, putDataLen
, putData
, putDataLen
) < 0) ? LDAP_ENCODING_ERROR
: 0;
194 /* BER Encode the the tag */
195 err
= (ber_printf(requestBer
, "o}", utf8TagPtr
, utf8TagSize
) < 0) ? LDAP_ENCODING_ERROR
: 0;
203 /* Convert the BER we just built to a berval that we'll send with the extended request. */
204 if(ber_flatten(requestBer
, requestBV
) == LBER_ERROR
)
206 err
= LDAP_ENCODING_ERROR
;
214 ber_free(requestBer
, 1);
220 /**********************************************************************
221 Takes the reply BER Value and decodes the NMAS server version and
222 return code and if a non null retData buffer was supplied, tries to
223 decode the the return data and length
224 **********************************************************************/
226 static int berDecodeLoginData(
227 struct berval
*replyBV
,
233 BerElement
*replyBer
= NULL
;
234 char *retOctStr
= NULL
;
235 size_t retOctStrLen
= 0;
237 if((replyBer
= ber_init(replyBV
)) == NULL
)
239 err
= LDAP_OPERATIONS_ERROR
;
245 retOctStrLen
= *retDataLen
+ 1;
246 retOctStr
= SMB_MALLOC_ARRAY(char, retOctStrLen
);
249 err
= LDAP_OPERATIONS_ERROR
;
253 if(ber_scanf(replyBer
, "{iis}", serverVersion
, &err
, retOctStr
, &retOctStrLen
) != -1)
255 if (*retDataLen
>= retOctStrLen
)
257 memcpy(retData
, retOctStr
, retOctStrLen
);
261 err
= LDAP_NO_MEMORY
;
264 *retDataLen
= retOctStrLen
;
268 err
= LDAP_DECODING_ERROR
;
273 if(ber_scanf(replyBer
, "{ii}", serverVersion
, &err
) == -1)
277 err
= LDAP_DECODING_ERROR
;
286 ber_free(replyBer
, 1);
289 if (retOctStr
!= NULL
)
291 memset(retOctStr
, 0, retOctStrLen
);
298 /**********************************************************************
299 Retrieves data in the login configuration of the specified object
300 that is tagged with the specified methodID and tag.
301 **********************************************************************/
303 static int getLoginConfig(
306 unsigned int methodIDLen
,
307 unsigned int *methodID
,
313 struct berval
*requestBV
= NULL
;
314 char *replyOID
= NULL
;
315 struct berval
*replyBV
= NULL
;
316 int serverVersion
= 0;
318 /* Validate unicode parameters. */
319 if((strlen(objectDN
) == 0) || ld
== NULL
)
321 return LDAP_NO_SUCH_ATTRIBUTE
;
324 err
= berEncodeLoginData(&requestBV
, objectDN
, methodIDLen
, methodID
, tag
, 0, NULL
);
330 /* Call the ldap_extended_operation (synchronously) */
331 if((err
= ldap_extended_operation_s(ld
, NMASLDAP_GET_LOGIN_CONFIG_REQUEST
,
332 requestBV
, NULL
, NULL
, &replyOID
, &replyBV
)))
337 /* Make sure there is a return OID */
340 err
= LDAP_NOT_SUPPORTED
;
344 /* Is this what we were expecting to get back. */
345 if(strcmp(replyOID
, NMASLDAP_GET_LOGIN_CONFIG_RESPONSE
))
347 err
= LDAP_NOT_SUPPORTED
;
351 /* Do we have a good returned berval? */
354 /* No; returned berval means we experienced a rather drastic error. */
355 /* Return operations error. */
356 err
= LDAP_OPERATIONS_ERROR
;
360 err
= berDecodeLoginData(replyBV
, &serverVersion
, dataLen
, data
);
362 if(serverVersion
!= NMAS_LDAP_EXT_VERSION
)
364 err
= LDAP_OPERATIONS_ERROR
;
375 /* Free the return OID string if one was returned. */
378 ldap_memfree(replyOID
);
381 /* Free memory allocated while building the request ber and berval. */
384 ber_bvfree(requestBV
);
387 /* Return the appropriate error/success code. */
391 /**********************************************************************
392 Attempts to get the Simple Password
393 **********************************************************************/
395 static int nmasldap_get_simple_pwd(
402 unsigned int methodID
= 0;
403 unsigned int methodIDLen
= sizeof(methodID
);
404 char tag
[] = {'P','A','S','S','W','O','R','D',' ','H','A','S','H',0};
406 size_t pwdBufLen
, bufferLen
;
408 bufferLen
= pwdBufLen
= pwdLen
+2;
409 pwdBuf
= SMB_MALLOC_ARRAY(char, pwdBufLen
); /* digest and null */
412 return LDAP_NO_MEMORY
;
415 err
= getLoginConfig(ld
, objectDN
, methodIDLen
, &methodID
, tag
, &pwdBufLen
, pwdBuf
);
420 pwdBuf
[pwdBufLen
] = 0; /* null terminate */
424 case 1: /* cleartext password */
426 case 2: /* SHA1 HASH */
428 case 4: /* UNIXCrypt_ID */
429 case 8: /* SSHA_ID */
430 default: /* Unknown digest */
431 err
= LDAP_INAPPROPRIATE_AUTH
; /* only return clear text */
437 if (pwdLen
>= pwdBufLen
-1)
439 memcpy(pwd
, &pwdBuf
[1], pwdBufLen
-1); /* skip digest tag and include null */
443 err
= LDAP_NO_MEMORY
;
451 memset(pwdBuf
, 0, bufferLen
);
459 /**********************************************************************
460 Attempts to set the Universal Password
461 **********************************************************************/
463 static int nmasldap_set_password(
465 const char *objectDN
,
470 struct berval
*requestBV
= NULL
;
471 char *replyOID
= NULL
;
472 struct berval
*replyBV
= NULL
;
475 /* Validate char parameters. */
476 if(objectDN
== NULL
|| (strlen(objectDN
) == 0) || pwd
== NULL
|| ld
== NULL
)
478 return LDAP_NO_SUCH_ATTRIBUTE
;
481 err
= berEncodePasswordData(&requestBV
, objectDN
, pwd
, NULL
);
487 /* Call the ldap_extended_operation (synchronously) */
488 if((err
= ldap_extended_operation_s(ld
, NMASLDAP_SET_PASSWORD_REQUEST
, requestBV
, NULL
, NULL
, &replyOID
, &replyBV
)))
493 /* Make sure there is a return OID */
496 err
= LDAP_NOT_SUPPORTED
;
500 /* Is this what we were expecting to get back. */
501 if(strcmp(replyOID
, NMASLDAP_SET_PASSWORD_RESPONSE
))
503 err
= LDAP_NOT_SUPPORTED
;
507 /* Do we have a good returned berval? */
510 /* No; returned berval means we experienced a rather drastic error. */
511 /* Return operations error. */
512 err
= LDAP_OPERATIONS_ERROR
;
516 err
= berDecodeLoginData(replyBV
, &serverVersion
, NULL
, NULL
);
518 if(serverVersion
!= NMAS_LDAP_EXT_VERSION
)
520 err
= LDAP_OPERATIONS_ERROR
;
531 /* Free the return OID string if one was returned. */
534 ldap_memfree(replyOID
);
537 /* Free memory allocated while building the request ber and berval. */
540 ber_bvfree(requestBV
);
543 /* Return the appropriate error/success code. */
547 /**********************************************************************
548 Attempts to get the Universal Password
549 **********************************************************************/
551 static int nmasldap_get_password(
554 size_t *pwdSize
, /* in bytes */
559 struct berval
*requestBV
= NULL
;
560 char *replyOID
= NULL
;
561 struct berval
*replyBV
= NULL
;
564 size_t pwdBufLen
, bufferLen
;
566 /* Validate char parameters. */
567 if(objectDN
== NULL
|| (strlen(objectDN
) == 0) || pwdSize
== NULL
|| ld
== NULL
)
569 return LDAP_NO_SUCH_ATTRIBUTE
;
572 bufferLen
= pwdBufLen
= *pwdSize
;
573 pwdBuf
= SMB_MALLOC_ARRAY(char, pwdBufLen
+2);
576 return LDAP_NO_MEMORY
;
579 err
= berEncodePasswordData(&requestBV
, objectDN
, NULL
, NULL
);
585 /* Call the ldap_extended_operation (synchronously) */
586 if((err
= ldap_extended_operation_s(ld
, NMASLDAP_GET_PASSWORD_REQUEST
, requestBV
, NULL
, NULL
, &replyOID
, &replyBV
)))
591 /* Make sure there is a return OID */
594 err
= LDAP_NOT_SUPPORTED
;
598 /* Is this what we were expecting to get back. */
599 if(strcmp(replyOID
, NMASLDAP_GET_PASSWORD_RESPONSE
))
601 err
= LDAP_NOT_SUPPORTED
;
605 /* Do we have a good returned berval? */
608 /* No; returned berval means we experienced a rather drastic error. */
609 /* Return operations error. */
610 err
= LDAP_OPERATIONS_ERROR
;
614 err
= berDecodeLoginData(replyBV
, &serverVersion
, &pwdBufLen
, pwdBuf
);
616 if(serverVersion
!= NMAS_LDAP_EXT_VERSION
)
618 err
= LDAP_OPERATIONS_ERROR
;
622 if (!err
&& pwdBufLen
!= 0)
624 if (*pwdSize
>= pwdBufLen
+1 && pwd
!= NULL
)
626 memcpy(pwd
, pwdBuf
, pwdBufLen
);
627 pwd
[pwdBufLen
] = 0; /* add null termination */
629 *pwdSize
= pwdBufLen
; /* does not include null termination */
639 /* Free the return OID string if one was returned. */
642 ldap_memfree(replyOID
);
645 /* Free memory allocated while building the request ber and berval. */
648 ber_bvfree(requestBV
);
653 memset(pwdBuf
, 0, bufferLen
);
657 /* Return the appropriate error/success code. */
661 /**********************************************************************
662 Get the user's password from NDS.
663 *********************************************************************/
665 int pdb_nds_get_password(
666 struct smbldap_state
*ldap_state
,
671 LDAP
*ld
= ldap_state
->ldap_struct
;
674 rc
= nmasldap_get_password(ld
, object_dn
, pwd_len
, (unsigned char *)pwd
);
675 if (rc
== LDAP_SUCCESS
) {
676 #ifdef DEBUG_PASSWORD
677 DEBUG(100,("nmasldap_get_password returned %s for %s\n", pwd
, object_dn
));
679 DEBUG(5, ("NDS Universal Password retrieved for %s\n", object_dn
));
681 DEBUG(3, ("NDS Universal Password NOT retrieved for %s\n", object_dn
));
684 if (rc
!= LDAP_SUCCESS
) {
685 rc
= nmasldap_get_simple_pwd(ld
, object_dn
, *pwd_len
, pwd
);
686 if (rc
== LDAP_SUCCESS
) {
687 #ifdef DEBUG_PASSWORD
688 DEBUG(100,("nmasldap_get_simple_pwd returned %s for %s\n", pwd
, object_dn
));
690 DEBUG(5, ("NDS Simple Password retrieved for %s\n", object_dn
));
692 /* We couldn't get the password */
693 DEBUG(3, ("NDS Simple Password NOT retrieved for %s\n", object_dn
));
694 return LDAP_INVALID_CREDENTIALS
;
698 /* We got the password */
702 /**********************************************************************
703 Set the users NDS, Universal and Simple passwords.
704 ********************************************************************/
706 int pdb_nds_set_password(
707 struct smbldap_state
*ldap_state
,
711 LDAP
*ld
= ldap_state
->ldap_struct
;
713 LDAPMod
**tmpmods
= NULL
;
715 rc
= nmasldap_set_password(ld
, object_dn
, pwd
);
716 if (rc
== LDAP_SUCCESS
) {
717 DEBUG(5,("NDS Universal Password changed for user %s\n", object_dn
));
719 char *ld_error
= NULL
;
720 ldap_get_option(ld
, LDAP_OPT_ERROR_STRING
, &ld_error
);
722 /* This will fail if Universal Password is not enabled for the user's context */
723 DEBUG(3,("NDS Universal Password could not be changed for user %s: %s (%s)\n",
724 object_dn
, ldap_err2string(rc
), ld_error
?ld_error
:"unknown"));
728 /* Set eDirectory Password */
729 smbldap_set_mod(&tmpmods
, LDAP_MOD_REPLACE
, "userPassword", pwd
);
730 rc
= smbldap_modify(ldap_state
, object_dn
, tmpmods
);
735 /**********************************************************************
736 Allow ldap server to update internal login attempt counters by
737 performing a simple bind. If the samba authentication failed attempt
738 the bind with a bogus, randomly generated password to count the
739 failed attempt. If the bind fails even though samba authentication
740 succeeded, this would indicate that the user's account is disabled,
741 time restrictions are in place or some other password policy
743 *********************************************************************/
745 static NTSTATUS
pdb_nds_update_login_attempts(struct pdb_methods
*methods
,
746 struct samu
*sam_acct
, bool success
)
748 struct ldapsam_privates
*ldap_state
;
750 if ((!methods
) || (!sam_acct
)) {
751 DEBUG(3,("pdb_nds_update_login_attempts: invalid parameter.\n"));
752 return NT_STATUS_MEMORY_NOT_ALLOCATED
;
755 ldap_state
= (struct ldapsam_privates
*)methods
->private_data
;
758 /* Attempt simple bind with user credentials to update eDirectory
762 LDAPMessage
*result
= NULL
;
763 LDAPMessage
*entry
= NULL
;
764 const char **attr_list
;
766 char clear_text_pw
[512];
768 const char *username
= pdb_get_username(sam_acct
);
769 bool got_clear_text_pw
= False
;
771 DEBUG(5,("pdb_nds_update_login_attempts: %s login for %s\n",
772 success
? "Successful" : "Failed", username
));
774 result
= (LDAPMessage
*)pdb_get_backend_private_data(sam_acct
, methods
);
776 attr_list
= get_userattr_list(NULL
,
777 ldap_state
->schema_ver
);
778 rc
= ldapsam_search_suffix_by_name(ldap_state
, username
, &result
, attr_list
);
779 TALLOC_FREE( attr_list
);
780 if (rc
!= LDAP_SUCCESS
) {
781 return NT_STATUS_OBJECT_NAME_NOT_FOUND
;
783 pdb_set_backend_private_data(sam_acct
, result
, NULL
,
784 methods
, PDB_CHANGED
);
785 talloc_autofree_ldapmsg(sam_acct
, result
);
788 if (ldap_count_entries(ldap_state
->smbldap_state
->ldap_struct
, result
) == 0) {
789 DEBUG(0, ("pdb_nds_update_login_attempts: No user to modify!\n"));
790 return NT_STATUS_OBJECT_NAME_NOT_FOUND
;
793 entry
= ldap_first_entry(ldap_state
->smbldap_state
->ldap_struct
, result
);
794 dn
= smbldap_talloc_dn(talloc_tos(), ldap_state
->smbldap_state
->ldap_struct
, entry
);
796 return NT_STATUS_OBJECT_NAME_NOT_FOUND
;
799 DEBUG(3, ("pdb_nds_update_login_attempts: username %s found dn '%s'\n", username
, dn
));
801 pwd_len
= sizeof(clear_text_pw
);
802 if (success
== True
) {
803 if (pdb_nds_get_password(ldap_state
->smbldap_state
, dn
, &pwd_len
, clear_text_pw
) == LDAP_SUCCESS
) {
804 /* Got clear text password. Use simple ldap bind */
805 got_clear_text_pw
= True
;
808 generate_random_buffer((unsigned char *)clear_text_pw
, 24);
809 clear_text_pw
[24] = '\0';
810 DEBUG(5,("pdb_nds_update_login_attempts: using random password %s\n", clear_text_pw
));
813 if((success
!= True
) || (got_clear_text_pw
== True
)) {
815 rc
= smb_ldap_setup_full_conn(&ld
, ldap_state
->location
);
818 return NT_STATUS_INVALID_CONNECTION
;
821 /* Attempt simple bind with real or bogus password */
822 rc
= ldap_simple_bind_s(ld
, dn
, clear_text_pw
);
824 if (rc
== LDAP_SUCCESS
) {
825 DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Successful for %s\n", username
));
827 NTSTATUS nt_status
= NT_STATUS_ACCOUNT_RESTRICTION
;
828 DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Failed for %s\n", username
));
830 case LDAP_INVALID_CREDENTIALS
:
831 nt_status
= NT_STATUS_WRONG_PASSWORD
;
833 case LDAP_UNWILLING_TO_PERFORM
:
834 /* eDir returns this if the account was disabled. */
835 /* The problem is we don't know if the given
836 password was correct for this account or
837 not. We have to return more info than we
838 should and tell the client NT_STATUS_ACCOUNT_DISABLED
839 so they don't think the password was bad. JRA. */
840 nt_status
= NT_STATUS_ACCOUNT_DISABLED
;
854 /**********************************************************************
855 Intitalise the parts of the pdb_methods structuire that are common
857 *********************************************************************/
859 static NTSTATUS
pdb_init_NDS_ldapsam_common(struct pdb_methods
**pdb_method
, const char *location
)
861 struct ldapsam_privates
*ldap_state
=
862 (struct ldapsam_privates
*)((*pdb_method
)->private_data
);
864 /* Mark this as eDirectory ldap */
865 ldap_state
->is_nds_ldap
= True
;
867 /* Add pdb_nds specific method for updating login attempts. */
868 (*pdb_method
)->update_login_attempts
= pdb_nds_update_login_attempts
;
870 /* Save location for use in pdb_nds_update_login_attempts */
871 ldap_state
->location
= SMB_STRDUP(location
);
877 /**********************************************************************
878 Initialise the 'nds compat' mode for pdb_ldap
879 *********************************************************************/
881 static NTSTATUS
pdb_init_NDS_ldapsam_compat(struct pdb_methods
**pdb_method
, const char *location
)
883 NTSTATUS nt_status
= pdb_init_ldapsam_compat(pdb_method
, location
);
885 (*pdb_method
)->name
= "NDS_ldapsam_compat";
887 pdb_init_NDS_ldapsam_common(pdb_method
, location
);
893 /**********************************************************************
894 Initialise the 'nds' normal mode for pdb_ldap
895 *********************************************************************/
897 static NTSTATUS
pdb_init_NDS_ldapsam(struct pdb_methods
**pdb_method
, const char *location
)
899 NTSTATUS nt_status
= pdb_init_ldapsam(pdb_method
, location
);
901 (*pdb_method
)->name
= "NDS_ldapsam";
903 pdb_init_NDS_ldapsam_common(pdb_method
, location
);
908 NTSTATUS
pdb_nds_init(void)
911 if (!NT_STATUS_IS_OK(nt_status
= smb_register_passdb(PASSDB_INTERFACE_VERSION
, "NDS_ldapsam", pdb_init_NDS_ldapsam
)))
914 if (!NT_STATUS_IS_OK(nt_status
= smb_register_passdb(PASSDB_INTERFACE_VERSION
, "NDS_ldapsam_compat", pdb_init_NDS_ldapsam_compat
)))