2 Unix SMB/CIFS Implementation.
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/>.
28 #include "passdb/pdb_ldap.h"
29 #include "passdb/pdb_nds.h"
31 #define NMASLDAP_GET_LOGIN_CONFIG_REQUEST "2.16.840.1.113719.1.39.42.100.3"
32 #define NMASLDAP_GET_LOGIN_CONFIG_RESPONSE "2.16.840.1.113719.1.39.42.100.4"
33 #define NMASLDAP_SET_PASSWORD_REQUEST "2.16.840.1.113719.1.39.42.100.11"
34 #define NMASLDAP_SET_PASSWORD_RESPONSE "2.16.840.1.113719.1.39.42.100.12"
35 #define NMASLDAP_GET_PASSWORD_REQUEST "2.16.840.1.113719.1.39.42.100.13"
36 #define NMASLDAP_GET_PASSWORD_RESPONSE "2.16.840.1.113719.1.39.42.100.14"
38 #define NMAS_LDAP_EXT_VERSION 1
40 /**********************************************************************
41 Take the request BER value and input data items and BER encodes the
42 data into the BER value
43 **********************************************************************/
45 static int berEncodePasswordData(
46 struct berval
**requestBV
,
49 const char *password2
)
52 BerElement
*requestBer
= NULL
;
54 const char * utf8ObjPtr
= NULL
;
56 const char * utf8PwdPtr
= NULL
;
58 const char * utf8Pwd2Ptr
= NULL
;
62 /* Convert objectDN and tag strings from Unicode to UTF-8 */
63 utf8ObjSize
= strlen(objectDN
)+1;
64 utf8ObjPtr
= objectDN
;
68 utf8PwdSize
= strlen(password
)+1;
69 utf8PwdPtr
= password
;
72 if (password2
!= NULL
)
74 utf8Pwd2Size
= strlen(password2
)+1;
75 utf8Pwd2Ptr
= password2
;
78 /* Allocate a BerElement for the request parameters. */
79 if((requestBer
= ber_alloc()) == NULL
)
81 err
= LDAP_ENCODING_ERROR
;
85 if (password
!= NULL
&& password2
!= NULL
)
87 /* BER encode the NMAS Version, the objectDN, and the password */
88 rc
= ber_printf(requestBer
, "{iooo}", NMAS_LDAP_EXT_VERSION
, utf8ObjPtr
, utf8ObjSize
, utf8PwdPtr
, utf8PwdSize
, utf8Pwd2Ptr
, utf8Pwd2Size
);
90 else if (password
!= NULL
)
92 /* BER encode the NMAS Version, the objectDN, and the password */
93 rc
= ber_printf(requestBer
, "{ioo}", NMAS_LDAP_EXT_VERSION
, utf8ObjPtr
, utf8ObjSize
, utf8PwdPtr
, utf8PwdSize
);
97 /* BER encode the NMAS Version and the objectDN */
98 rc
= ber_printf(requestBer
, "{io}", NMAS_LDAP_EXT_VERSION
, utf8ObjPtr
, utf8ObjSize
);
103 err
= LDAP_ENCODING_ERROR
;
111 /* Convert the BER we just built to a berval that we'll send with the extended request. */
112 if(ber_flatten(requestBer
, requestBV
) == LBER_ERROR
)
114 err
= LDAP_ENCODING_ERROR
;
122 ber_free(requestBer
, 1);
128 /**********************************************************************
129 Take the request BER value and input data items and BER encodes the
130 data into the BER value
131 **********************************************************************/
133 static int berEncodeLoginData(
134 struct berval
**requestBV
,
136 unsigned int methodIDLen
,
137 unsigned int *methodID
,
143 BerElement
*requestBer
= NULL
;
146 unsigned int elemCnt
= methodIDLen
/ sizeof(unsigned int);
148 char *utf8ObjPtr
=NULL
;
151 char *utf8TagPtr
= NULL
;
154 utf8ObjPtr
= objectDN
;
155 utf8ObjSize
= strlen(utf8ObjPtr
)+1;
158 utf8TagSize
= strlen(utf8TagPtr
)+1;
160 /* Allocate a BerElement for the request parameters. */
161 if((requestBer
= ber_alloc()) == NULL
)
163 err
= LDAP_ENCODING_ERROR
;
167 /* BER encode the NMAS Version and the objectDN */
168 err
= (ber_printf(requestBer
, "{io", NMAS_LDAP_EXT_VERSION
, utf8ObjPtr
, utf8ObjSize
) < 0) ? LDAP_ENCODING_ERROR
: 0;
170 /* BER encode the MethodID Length and value */
173 err
= (ber_printf(requestBer
, "{i{", methodIDLen
) < 0) ? LDAP_ENCODING_ERROR
: 0;
176 for (i
= 0; !err
&& i
< elemCnt
; i
++)
178 err
= (ber_printf(requestBer
, "i", methodID
[i
]) < 0) ? LDAP_ENCODING_ERROR
: 0;
183 err
= (ber_printf(requestBer
, "}}", 0) < 0) ? LDAP_ENCODING_ERROR
: 0;
188 /* BER Encode the tag and data */
189 err
= (ber_printf(requestBer
, "oio}", utf8TagPtr
,
190 utf8TagSize
, putDataLen
, putData
,
192 ? LDAP_ENCODING_ERROR
: 0;
194 /* BER Encode the tag */
195 err
= (ber_printf(requestBer
, "o}", utf8TagPtr
,
197 ? LDAP_ENCODING_ERROR
: 0;
206 /* Convert the BER we just built to a berval that we'll send with the extended request. */
207 if(ber_flatten(requestBer
, requestBV
) == LBER_ERROR
)
209 err
= LDAP_ENCODING_ERROR
;
217 ber_free(requestBer
, 1);
223 /**********************************************************************
224 Takes the reply BER Value and decodes the NMAS server version and
225 return code and if a non null retData buffer was supplied, tries to
226 decode the return data and length
227 **********************************************************************/
229 static int berDecodeLoginData(
230 struct berval
*replyBV
,
236 BerElement
*replyBer
= NULL
;
237 char *retOctStr
= NULL
;
238 size_t retOctStrLen
= 0;
240 if((replyBer
= ber_init(replyBV
)) == NULL
)
242 err
= LDAP_OPERATIONS_ERROR
;
248 retOctStrLen
= *retDataLen
+ 1;
249 retOctStr
= SMB_MALLOC_ARRAY(char, retOctStrLen
);
252 err
= LDAP_OPERATIONS_ERROR
;
256 if(ber_scanf(replyBer
, "{iis}", serverVersion
, &err
, retOctStr
, &retOctStrLen
) != -1)
258 if (*retDataLen
>= retOctStrLen
)
260 memcpy(retData
, retOctStr
, retOctStrLen
);
264 err
= LDAP_NO_MEMORY
;
267 *retDataLen
= retOctStrLen
;
271 err
= LDAP_DECODING_ERROR
;
276 if(ber_scanf(replyBer
, "{ii}", serverVersion
, &err
) == -1)
280 err
= LDAP_DECODING_ERROR
;
289 ber_free(replyBer
, 1);
292 if (retOctStr
!= NULL
)
294 memset(retOctStr
, 0, retOctStrLen
);
301 /**********************************************************************
302 Retrieves data in the login configuration of the specified object
303 that is tagged with the specified methodID and tag.
304 **********************************************************************/
306 static int getLoginConfig(
309 unsigned int methodIDLen
,
310 unsigned int *methodID
,
316 struct berval
*requestBV
= NULL
;
317 char *replyOID
= NULL
;
318 struct berval
*replyBV
= NULL
;
319 int serverVersion
= 0;
321 /* Validate unicode parameters. */
322 if((strlen(objectDN
) == 0) || ld
== NULL
)
324 return LDAP_NO_SUCH_ATTRIBUTE
;
327 err
= berEncodeLoginData(&requestBV
, objectDN
, methodIDLen
, methodID
, tag
, 0, NULL
);
333 /* Call the ldap_extended_operation (synchronously) */
334 if((err
= ldap_extended_operation_s(ld
, NMASLDAP_GET_LOGIN_CONFIG_REQUEST
,
335 requestBV
, NULL
, NULL
, &replyOID
, &replyBV
)))
340 /* Make sure there is a return OID */
343 err
= LDAP_NOT_SUPPORTED
;
347 /* Is this what we were expecting to get back. */
348 if(strcmp(replyOID
, NMASLDAP_GET_LOGIN_CONFIG_RESPONSE
))
350 err
= LDAP_NOT_SUPPORTED
;
354 /* Do we have a good returned berval? */
357 /* No; returned berval means we experienced a rather drastic error. */
358 /* Return operations error. */
359 err
= LDAP_OPERATIONS_ERROR
;
363 err
= berDecodeLoginData(replyBV
, &serverVersion
, dataLen
, data
);
365 if(serverVersion
!= NMAS_LDAP_EXT_VERSION
)
367 err
= LDAP_OPERATIONS_ERROR
;
378 /* Free the return OID string if one was returned. */
381 ldap_memfree(replyOID
);
384 /* Free memory allocated while building the request ber and berval. */
387 ber_bvfree(requestBV
);
390 /* Return the appropriate error/success code. */
394 /**********************************************************************
395 Attempts to get the Simple Password
396 **********************************************************************/
398 static int nmasldap_get_simple_pwd(
405 unsigned int methodID
= 0;
406 unsigned int methodIDLen
= sizeof(methodID
);
407 char tag
[] = {'P','A','S','S','W','O','R','D',' ','H','A','S','H',0};
409 size_t pwdBufLen
, bufferLen
;
411 bufferLen
= pwdBufLen
= pwdLen
+2;
412 pwdBuf
= SMB_MALLOC_ARRAY(char, pwdBufLen
); /* digest and null */
415 return LDAP_NO_MEMORY
;
418 err
= getLoginConfig(ld
, objectDN
, methodIDLen
, &methodID
, tag
, &pwdBufLen
, pwdBuf
);
423 pwdBuf
[pwdBufLen
] = 0; /* null terminate */
427 case 1: /* cleartext password */
429 case 2: /* SHA1 HASH */
431 case 4: /* UNIXCrypt_ID */
432 case 8: /* SSHA_ID */
433 default: /* Unknown digest */
434 err
= LDAP_INAPPROPRIATE_AUTH
; /* only return clear text */
440 if (pwdLen
>= pwdBufLen
-1)
442 memcpy(pwd
, &pwdBuf
[1], pwdBufLen
-1); /* skip digest tag and include null */
446 err
= LDAP_NO_MEMORY
;
454 memset(pwdBuf
, 0, bufferLen
);
462 /**********************************************************************
463 Attempts to set the Universal Password
464 **********************************************************************/
466 static int nmasldap_set_password(
468 const char *objectDN
,
473 struct berval
*requestBV
= NULL
;
474 char *replyOID
= NULL
;
475 struct berval
*replyBV
= NULL
;
478 /* Validate char parameters. */
479 if(objectDN
== NULL
|| (strlen(objectDN
) == 0) || pwd
== NULL
|| ld
== NULL
)
481 return LDAP_NO_SUCH_ATTRIBUTE
;
484 err
= berEncodePasswordData(&requestBV
, objectDN
, pwd
, NULL
);
490 /* Call the ldap_extended_operation (synchronously) */
491 if((err
= ldap_extended_operation_s(ld
, NMASLDAP_SET_PASSWORD_REQUEST
, requestBV
, NULL
, NULL
, &replyOID
, &replyBV
)))
496 /* Make sure there is a return OID */
499 err
= LDAP_NOT_SUPPORTED
;
503 /* Is this what we were expecting to get back. */
504 if(strcmp(replyOID
, NMASLDAP_SET_PASSWORD_RESPONSE
))
506 err
= LDAP_NOT_SUPPORTED
;
510 /* Do we have a good returned berval? */
513 /* No; returned berval means we experienced a rather drastic error. */
514 /* Return operations error. */
515 err
= LDAP_OPERATIONS_ERROR
;
519 err
= berDecodeLoginData(replyBV
, &serverVersion
, NULL
, NULL
);
521 if(serverVersion
!= NMAS_LDAP_EXT_VERSION
)
523 err
= LDAP_OPERATIONS_ERROR
;
534 /* Free the return OID string if one was returned. */
537 ldap_memfree(replyOID
);
540 /* Free memory allocated while building the request ber and berval. */
543 ber_bvfree(requestBV
);
546 /* Return the appropriate error/success code. */
550 /**********************************************************************
551 Attempts to get the Universal Password
552 **********************************************************************/
554 static int nmasldap_get_password(
557 size_t *pwdSize
, /* in bytes */
562 struct berval
*requestBV
= NULL
;
563 char *replyOID
= NULL
;
564 struct berval
*replyBV
= NULL
;
567 size_t pwdBufLen
, bufferLen
;
569 /* Validate char parameters. */
570 if(objectDN
== NULL
|| (strlen(objectDN
) == 0) || pwdSize
== NULL
|| ld
== NULL
)
572 return LDAP_NO_SUCH_ATTRIBUTE
;
575 bufferLen
= pwdBufLen
= *pwdSize
;
576 pwdBuf
= SMB_MALLOC_ARRAY(char, pwdBufLen
+2);
579 return LDAP_NO_MEMORY
;
582 err
= berEncodePasswordData(&requestBV
, objectDN
, NULL
, NULL
);
588 /* Call the ldap_extended_operation (synchronously) */
589 if((err
= ldap_extended_operation_s(ld
, NMASLDAP_GET_PASSWORD_REQUEST
, requestBV
, NULL
, NULL
, &replyOID
, &replyBV
)))
594 /* Make sure there is a return OID */
597 err
= LDAP_NOT_SUPPORTED
;
601 /* Is this what we were expecting to get back. */
602 if(strcmp(replyOID
, NMASLDAP_GET_PASSWORD_RESPONSE
))
604 err
= LDAP_NOT_SUPPORTED
;
608 /* Do we have a good returned berval? */
611 /* No; returned berval means we experienced a rather drastic error. */
612 /* Return operations error. */
613 err
= LDAP_OPERATIONS_ERROR
;
617 err
= berDecodeLoginData(replyBV
, &serverVersion
, &pwdBufLen
, pwdBuf
);
619 if(serverVersion
!= NMAS_LDAP_EXT_VERSION
)
621 err
= LDAP_OPERATIONS_ERROR
;
625 if (!err
&& pwdBufLen
!= 0)
627 if (*pwdSize
>= pwdBufLen
+1 && pwd
!= NULL
)
629 memcpy(pwd
, pwdBuf
, pwdBufLen
);
630 pwd
[pwdBufLen
] = 0; /* add null termination */
632 *pwdSize
= pwdBufLen
; /* does not include null termination */
642 /* Free the return OID string if one was returned. */
645 ldap_memfree(replyOID
);
648 /* Free memory allocated while building the request ber and berval. */
651 ber_bvfree(requestBV
);
656 memset(pwdBuf
, 0, bufferLen
);
660 /* Return the appropriate error/success code. */
664 /**********************************************************************
665 Get the user's password from NDS.
666 *********************************************************************/
668 int pdb_nds_get_password(
669 struct smbldap_state
*ldap_state
,
674 LDAP
*ld
= smbldap_get_ldap(ldap_state
);
677 rc
= nmasldap_get_password(ld
, object_dn
, pwd_len
, (unsigned char *)pwd
);
678 if (rc
== LDAP_SUCCESS
) {
679 #ifdef DEBUG_PASSWORD
680 DEBUG(100,("nmasldap_get_password returned %s for %s\n", pwd
, object_dn
));
682 DEBUG(5, ("NDS Universal Password retrieved for %s\n", object_dn
));
684 DEBUG(3, ("NDS Universal Password NOT retrieved for %s\n", object_dn
));
687 if (rc
!= LDAP_SUCCESS
) {
688 rc
= nmasldap_get_simple_pwd(ld
, object_dn
, *pwd_len
, pwd
);
689 if (rc
== LDAP_SUCCESS
) {
690 #ifdef DEBUG_PASSWORD
691 DEBUG(100,("nmasldap_get_simple_pwd returned %s for %s\n", pwd
, object_dn
));
693 DEBUG(5, ("NDS Simple Password retrieved for %s\n", object_dn
));
695 /* We couldn't get the password */
696 DEBUG(3, ("NDS Simple Password NOT retrieved for %s\n", object_dn
));
697 return LDAP_INVALID_CREDENTIALS
;
701 /* We got the password */
705 /**********************************************************************
706 Set the users NDS, Universal and Simple passwords.
707 ********************************************************************/
709 int pdb_nds_set_password(
710 struct smbldap_state
*ldap_state
,
714 LDAP
*ld
= smbldap_get_ldap(ldap_state
);
716 LDAPMod
**tmpmods
= NULL
;
718 rc
= nmasldap_set_password(ld
, object_dn
, pwd
);
719 if (rc
== LDAP_SUCCESS
) {
720 DEBUG(5,("NDS Universal Password changed for user %s\n", object_dn
));
722 char *ld_error
= NULL
;
723 ldap_get_option(ld
, LDAP_OPT_ERROR_STRING
, &ld_error
);
725 /* This will fail if Universal Password is not enabled for the user's context */
726 DEBUG(3,("NDS Universal Password could not be changed for user %s: %s (%s)\n",
727 object_dn
, ldap_err2string(rc
), ld_error
?ld_error
:"unknown"));
731 /* Set eDirectory Password */
732 smbldap_set_mod(&tmpmods
, LDAP_MOD_REPLACE
, "userPassword", pwd
);
733 rc
= smbldap_modify(ldap_state
, object_dn
, tmpmods
);
738 /**********************************************************************
739 Allow ldap server to update internal login attempt counters by
740 performing a simple bind. If the samba authentication failed attempt
741 the bind with a bogus, randomly generated password to count the
742 failed attempt. If the bind fails even though samba authentication
743 succeeded, this would indicate that the user's account is disabled,
744 time restrictions are in place or some other password policy
746 *********************************************************************/
748 static NTSTATUS
pdb_nds_update_login_attempts(struct pdb_methods
*methods
,
749 struct samu
*sam_acct
, bool success
)
751 struct ldapsam_privates
*ldap_state
;
753 if ((!methods
) || (!sam_acct
)) {
754 DEBUG(3,("pdb_nds_update_login_attempts: invalid parameter.\n"));
755 return NT_STATUS_MEMORY_NOT_ALLOCATED
;
758 ldap_state
= (struct ldapsam_privates
*)methods
->private_data
;
761 /* Attempt simple bind with user credentials to update eDirectory
765 LDAPMessage
*result
= NULL
;
766 LDAPMessage
*entry
= NULL
;
767 const char **attr_list
;
769 char clear_text_pw
[512];
771 const char *username
= pdb_get_username(sam_acct
);
772 bool got_clear_text_pw
= False
;
774 DEBUG(5,("pdb_nds_update_login_attempts: %s login for %s\n",
775 success
? "Successful" : "Failed", username
));
777 result
= (LDAPMessage
*)pdb_get_backend_private_data(sam_acct
, methods
);
779 attr_list
= get_userattr_list(NULL
,
780 ldap_state
->schema_ver
);
781 rc
= ldapsam_search_suffix_by_name(ldap_state
, username
, &result
, attr_list
);
782 TALLOC_FREE( attr_list
);
783 if (rc
!= LDAP_SUCCESS
) {
784 return NT_STATUS_OBJECT_NAME_NOT_FOUND
;
786 pdb_set_backend_private_data(sam_acct
, result
, NULL
,
787 methods
, PDB_CHANGED
);
788 smbldap_talloc_autofree_ldapmsg(sam_acct
, result
);
791 if (ldap_count_entries(
792 smbldap_get_ldap(ldap_state
->smbldap_state
),
794 DEBUG(0, ("pdb_nds_update_login_attempts: No user to modify!\n"));
795 return NT_STATUS_OBJECT_NAME_NOT_FOUND
;
798 entry
= ldap_first_entry(
799 smbldap_get_ldap(ldap_state
->smbldap_state
), result
);
800 dn
= smbldap_talloc_dn(talloc_tos(),
802 ldap_state
->smbldap_state
),
805 return NT_STATUS_OBJECT_NAME_NOT_FOUND
;
808 DEBUG(3, ("pdb_nds_update_login_attempts: username %s found dn '%s'\n", username
, dn
));
810 pwd_len
= sizeof(clear_text_pw
);
811 if (success
== True
) {
812 if (pdb_nds_get_password(ldap_state
->smbldap_state
, dn
, &pwd_len
, clear_text_pw
) == LDAP_SUCCESS
) {
813 /* Got clear text password. Use simple ldap bind */
814 got_clear_text_pw
= True
;
817 /* This is a long term key */
818 generate_secret_buffer((unsigned char *)clear_text_pw
, 24);
819 clear_text_pw
[24] = '\0';
820 DEBUG(5,("pdb_nds_update_login_attempts: using random password %s\n", clear_text_pw
));
823 if((success
!= True
) || (got_clear_text_pw
== True
)) {
825 rc
= smbldap_setup_full_conn(&ld
, ldap_state
->location
);
828 return NT_STATUS_INVALID_CONNECTION
;
831 /* Attempt simple bind with real or bogus password */
832 rc
= ldap_simple_bind_s(ld
, dn
, clear_text_pw
);
834 if (rc
== LDAP_SUCCESS
) {
835 DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Successful for %s\n", username
));
837 NTSTATUS nt_status
= NT_STATUS_ACCOUNT_RESTRICTION
;
838 DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Failed for %s\n", username
));
840 case LDAP_INVALID_CREDENTIALS
:
841 nt_status
= NT_STATUS_WRONG_PASSWORD
;
843 case LDAP_UNWILLING_TO_PERFORM
:
844 /* eDir returns this if the account was disabled. */
845 /* The problem is we don't know if the given
846 password was correct for this account or
847 not. We have to return more info than we
848 should and tell the client NT_STATUS_ACCOUNT_DISABLED
849 so they don't think the password was bad. JRA. */
850 nt_status
= NT_STATUS_ACCOUNT_DISABLED
;
864 /**********************************************************************
865 Initialise the parts of the pdb_methods structure that are common
867 *********************************************************************/
869 static NTSTATUS
pdb_init_NDS_ldapsam_common(struct pdb_methods
**pdb_method
, const char *location
)
871 struct ldapsam_privates
*ldap_state
=
872 (struct ldapsam_privates
*)((*pdb_method
)->private_data
);
874 /* Mark this as eDirectory ldap */
875 ldap_state
->is_nds_ldap
= True
;
877 /* Add pdb_nds specific method for updating login attempts. */
878 (*pdb_method
)->update_login_attempts
= pdb_nds_update_login_attempts
;
880 /* Save location for use in pdb_nds_update_login_attempts */
881 ldap_state
->location
= SMB_STRDUP(location
);
886 /**********************************************************************
887 Initialise the 'nds' normal mode for pdb_ldap
888 *********************************************************************/
890 static NTSTATUS
pdb_init_NDS_ldapsam(struct pdb_methods
**pdb_method
, const char *location
)
892 NTSTATUS nt_status
= pdb_ldapsam_init_common(pdb_method
, location
);
894 (*pdb_method
)->name
= "NDS_ldapsam";
896 pdb_init_NDS_ldapsam_common(pdb_method
, location
);
901 NTSTATUS
pdb_nds_init(TALLOC_CTX
*ctx
)
904 if (!NT_STATUS_IS_OK(nt_status
= smb_register_passdb(PASSDB_INTERFACE_VERSION
, "NDS_ldapsam", pdb_init_NDS_ldapsam
)))