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 2 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, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <ldap_utf8.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 the tag and data */
189 err = (ber_printf(requestBer, "oio}", utf8TagPtr, utf8TagSize, putDataLen, putData, putDataLen) < 0) ? LDAP_ENCODING_ERROR : 0;
193 /* BER Encode the the tag */
194 err = (ber_printf(requestBer, "o}", utf8TagPtr, utf8TagSize) < 0) ? LDAP_ENCODING_ERROR : 0;
202 /* Convert the BER we just built to a berval that we'll send with the extended request. */
203 if(ber_flatten(requestBer, requestBV) == LBER_ERROR)
205 err = LDAP_ENCODING_ERROR;
213 ber_free(requestBer, 1);
219 /**********************************************************************
220 Takes the reply BER Value and decodes the NMAS server version and
221 return code and if a non null retData buffer was supplied, tries to
222 decode the the return data and length
223 **********************************************************************/
225 static int berDecodeLoginData(
226 struct berval *replyBV,
232 BerElement *replyBer = NULL;
233 char *retOctStr = NULL;
234 size_t retOctStrLen = 0;
236 if((replyBer = ber_init(replyBV)) == NULL)
238 err = LDAP_OPERATIONS_ERROR;
244 retOctStrLen = *retDataLen + 1;
245 retOctStr = (char *)malloc(retOctStrLen);
248 err = LDAP_OPERATIONS_ERROR;
252 if( (rc = ber_scanf(replyBer, "{iis}", serverVersion, &err, retOctStr, &retOctStrLen)) != -1)
254 if (*retDataLen >= retOctStrLen)
256 memcpy(retData, retOctStr, retOctStrLen);
260 err = LDAP_NO_MEMORY;
263 *retDataLen = retOctStrLen;
267 err = LDAP_DECODING_ERROR;
272 if( (rc = ber_scanf(replyBer, "{ii}", serverVersion, &err)) == -1)
276 err = LDAP_DECODING_ERROR;
285 ber_free(replyBer, 1);
288 if (retOctStr != NULL)
290 memset(retOctStr, 0, retOctStrLen);
297 /**********************************************************************
298 Retrieves data in the login configuration of the specified object
299 that is tagged with the specified methodID and tag.
300 **********************************************************************/
302 static int getLoginConfig(
305 unsigned int methodIDLen,
306 unsigned int *methodID,
312 struct berval *requestBV = NULL;
313 char *replyOID = NULL;
314 struct berval *replyBV = NULL;
315 int serverVersion = 0;
317 /* Validate unicode parameters. */
318 if((strlen(objectDN) == 0) || ld == NULL)
320 return LDAP_NO_SUCH_ATTRIBUTE;
323 err = berEncodeLoginData(&requestBV, objectDN, methodIDLen, methodID, tag, 0, NULL);
329 /* Call the ldap_extended_operation (synchronously) */
330 if((err = ldap_extended_operation_s(ld, NMASLDAP_GET_LOGIN_CONFIG_REQUEST,
331 requestBV, NULL, NULL, &replyOID, &replyBV)))
336 /* Make sure there is a return OID */
339 err = LDAP_NOT_SUPPORTED;
343 /* Is this what we were expecting to get back. */
344 if(strcmp(replyOID, NMASLDAP_GET_LOGIN_CONFIG_RESPONSE))
346 err = LDAP_NOT_SUPPORTED;
350 /* Do we have a good returned berval? */
353 /* No; returned berval means we experienced a rather drastic error. */
354 /* Return operations error. */
355 err = LDAP_OPERATIONS_ERROR;
359 err = berDecodeLoginData(replyBV, &serverVersion, dataLen, data);
361 if(serverVersion != NMAS_LDAP_EXT_VERSION)
363 err = LDAP_OPERATIONS_ERROR;
374 /* Free the return OID string if one was returned. */
377 ldap_memfree(replyOID);
380 /* Free memory allocated while building the request ber and berval. */
383 ber_bvfree(requestBV);
386 /* Return the appropriate error/success code. */
390 /**********************************************************************
391 Attempts to get the Simple Password
392 **********************************************************************/
394 static int nmasldap_get_simple_pwd(
401 unsigned int methodID = 0;
402 unsigned int methodIDLen = sizeof(methodID);
403 char tag[] = {'P','A','S','S','W','O','R','D',' ','H','A','S','H',0};
405 size_t pwdBufLen, bufferLen;
407 bufferLen = pwdBufLen = pwdLen+2;
408 pwdBuf = (char *)malloc(pwdBufLen); /* digest and null */
411 return LDAP_NO_MEMORY;
414 err = getLoginConfig(ld, objectDN, methodIDLen, &methodID, tag, &pwdBufLen, pwdBuf);
419 pwdBuf[pwdBufLen] = 0; /* null terminate */
423 case 1: /* cleartext password */
425 case 2: /* SHA1 HASH */
427 case 4: /* UNIXCrypt_ID */
428 case 8: /* SSHA_ID */
429 default: /* Unknown digest */
430 err = LDAP_INAPPROPRIATE_AUTH; /* only return clear text */
436 if (pwdLen >= pwdBufLen-1)
438 memcpy(pwd, &pwdBuf[1], pwdBufLen-1); /* skip digest tag and include null */
442 err = LDAP_NO_MEMORY;
450 memset(pwdBuf, 0, bufferLen);
458 /**********************************************************************
459 Attempts to set the Universal Password
460 **********************************************************************/
462 static int nmasldap_set_password(
464 const char *objectDN,
469 struct berval *requestBV = NULL;
470 char *replyOID = NULL;
471 struct berval *replyBV = NULL;
474 /* Validate char parameters. */
475 if(objectDN == NULL || (strlen(objectDN) == 0) || pwd == NULL || ld == NULL)
477 return LDAP_NO_SUCH_ATTRIBUTE;
480 err = berEncodePasswordData(&requestBV, objectDN, pwd, NULL);
486 /* Call the ldap_extended_operation (synchronously) */
487 if((err = ldap_extended_operation_s(ld, NMASLDAP_SET_PASSWORD_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV)))
492 /* Make sure there is a return OID */
495 err = LDAP_NOT_SUPPORTED;
499 /* Is this what we were expecting to get back. */
500 if(strcmp(replyOID, NMASLDAP_SET_PASSWORD_RESPONSE))
502 err = LDAP_NOT_SUPPORTED;
506 /* Do we have a good returned berval? */
509 /* No; returned berval means we experienced a rather drastic error. */
510 /* Return operations error. */
511 err = LDAP_OPERATIONS_ERROR;
515 err = berDecodeLoginData(replyBV, &serverVersion, NULL, NULL);
517 if(serverVersion != NMAS_LDAP_EXT_VERSION)
519 err = LDAP_OPERATIONS_ERROR;
530 /* Free the return OID string if one was returned. */
533 ldap_memfree(replyOID);
536 /* Free memory allocated while building the request ber and berval. */
539 ber_bvfree(requestBV);
542 /* Return the appropriate error/success code. */
546 /**********************************************************************
547 Attempts to get the Universal Password
548 **********************************************************************/
550 static int nmasldap_get_password(
553 size_t *pwdSize, /* in bytes */
558 struct berval *requestBV = NULL;
559 char *replyOID = NULL;
560 struct berval *replyBV = NULL;
563 size_t pwdBufLen, bufferLen;
565 /* Validate char parameters. */
566 if(objectDN == NULL || (strlen(objectDN) == 0) || pwdSize == NULL || ld == NULL)
568 return LDAP_NO_SUCH_ATTRIBUTE;
571 bufferLen = pwdBufLen = *pwdSize;
572 pwdBuf = (char *)malloc(pwdBufLen+2);
575 return LDAP_NO_MEMORY;
578 err = berEncodePasswordData(&requestBV, objectDN, NULL, NULL);
584 /* Call the ldap_extended_operation (synchronously) */
585 if((err = ldap_extended_operation_s(ld, NMASLDAP_GET_PASSWORD_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV)))
590 /* Make sure there is a return OID */
593 err = LDAP_NOT_SUPPORTED;
597 /* Is this what we were expecting to get back. */
598 if(strcmp(replyOID, NMASLDAP_GET_PASSWORD_RESPONSE))
600 err = LDAP_NOT_SUPPORTED;
604 /* Do we have a good returned berval? */
607 /* No; returned berval means we experienced a rather drastic error. */
608 /* Return operations error. */
609 err = LDAP_OPERATIONS_ERROR;
613 err = berDecodeLoginData(replyBV, &serverVersion, &pwdBufLen, pwdBuf);
615 if(serverVersion != NMAS_LDAP_EXT_VERSION)
617 err = LDAP_OPERATIONS_ERROR;
621 if (!err && pwdBufLen != 0)
623 if (*pwdSize >= pwdBufLen+1 && pwd != NULL)
625 memcpy(pwd, pwdBuf, pwdBufLen);
626 pwd[pwdBufLen] = 0; /* add null termination */
628 *pwdSize = pwdBufLen; /* does not include null termination */
638 /* Free the return OID string if one was returned. */
641 ldap_memfree(replyOID);
644 /* Free memory allocated while building the request ber and berval. */
647 ber_bvfree(requestBV);
652 memset(pwdBuf, 0, bufferLen);
656 /* Return the appropriate error/success code. */
660 /**********************************************************************
661 Get the user's password from NDS.
662 *********************************************************************/
664 int pdb_nds_get_password(
665 struct smbldap_state *ldap_state,
670 LDAP *ld = ldap_state->ldap_struct;
673 rc = nmasldap_get_password(ld, object_dn, pwd_len, pwd);
674 if (rc == LDAP_SUCCESS) {
675 #ifdef DEBUG_PASSWORD
676 DEBUG(100,("nmasldap_get_password returned %s for %s\n", pwd, object_dn));
678 DEBUG(5, ("NDS Universal Password retrieved for %s\n", object_dn));
680 DEBUG(3, ("NDS Universal Password NOT retrieved for %s\n", object_dn));
683 if (rc != LDAP_SUCCESS) {
684 rc = nmasldap_get_simple_pwd(ld, object_dn, *pwd_len, pwd);
685 if (rc == LDAP_SUCCESS) {
686 #ifdef DEBUG_PASSWORD
687 DEBUG(100,("nmasldap_get_simple_pwd returned %s for %s\n", pwd, object_dn));
689 DEBUG(5, ("NDS Simple Password retrieved for %s\n", object_dn));
691 /* We couldn't get the password */
692 DEBUG(3, ("NDS Simple Password NOT retrieved for %s\n", object_dn));
693 return LDAP_INVALID_CREDENTIALS;
697 /* We got the password */
701 /**********************************************************************
702 Set the users NDS, Universal and Simple passwords.
703 ********************************************************************/
705 int pdb_nds_set_password(
706 struct smbldap_state *ldap_state,
710 LDAP *ld = ldap_state->ldap_struct;
712 LDAPMod **tmpmods = NULL;
714 rc = nmasldap_set_password(ld, object_dn, pwd);
715 if (rc == LDAP_SUCCESS) {
716 DEBUG(5,("NDS Universal Password changed for user %s\n", object_dn));
718 /* This will fail if Universal Password is not enabled for the user's context */
719 DEBUG(3,("NDS Universal Password could not be changed for user %s: %d\n",
723 /* Set eDirectory Password */
724 smbldap_set_mod(&tmpmods, LDAP_MOD_REPLACE, "userPassword", pwd);
725 rc = smbldap_modify(ldap_state, object_dn, tmpmods);
730 /**********************************************************************
731 Allow ldap server to update internal login attempt counters by
732 performing a simple bind. If the samba authentication failed attempt
733 the bind with a bogus, randomly generated password to count the
734 failed attempt. If the bind fails even though samba authentication
735 succeeded, this would indicate that the user's account is disabled,
736 time restrictions are in place or some other password policy
738 *********************************************************************/
740 static NTSTATUS pdb_nds_update_login_attempts(struct pdb_methods *methods,
741 SAM_ACCOUNT *sam_acct, BOOL success)
743 struct ldapsam_privates *ldap_state;
745 if ((!methods) || (!sam_acct)) {
746 DEBUG(3,("pdb_nds_update_login_attempts: invalid parameter.\n"));
747 return NT_STATUS_MEMORY_NOT_ALLOCATED;
750 ldap_state = (struct ldapsam_privates *)methods->private_data;
753 /* Attempt simple bind with user credentials to update eDirectory
757 LDAPMessage *result = NULL;
758 LDAPMessage *entry = NULL;
759 const char **attr_list;
761 uchar clear_text_pw[512];
762 const char *p = NULL;
766 char ldap_server[256];
767 const char *username = pdb_get_username(sam_acct);
769 DEBUG(5,("pdb_nds_update_login_attempts: %s login for %s\n",
770 success ? "Successful" : "Failed", username));
772 result = pdb_get_backend_private_data(sam_acct, methods);
774 attr_list = get_userattr_list(ldap_state->schema_ver);
775 rc = ldapsam_search_suffix_by_name(ldap_state, username, &result, attr_list );
776 free_attr_list( attr_list );
777 if (rc != LDAP_SUCCESS) {
778 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
780 pdb_set_backend_private_data(sam_acct, result, private_data_free_fn, methods, PDB_CHANGED);
783 if (ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result) == 0) {
784 DEBUG(0, ("pdb_nds_update_login_attempts: No user to modify!\n"));
785 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
788 entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, result);
789 dn = smbldap_get_dn(ldap_state->smbldap_state->ldap_struct, entry);
791 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
794 DEBUG(3, ("pdb_nds_update_login_attempts: username %s found dn '%s'\n", username, dn));
796 pwd_len = sizeof(clear_text_pw);
797 if (success == True) {
798 if (pdb_nds_get_password(ldap_state->smbldap_state, dn, &pwd_len, clear_text_pw) == LDAP_SUCCESS) {
802 generate_random_buffer(clear_text_pw, 24);
803 clear_text_pw[24] = '\0';
804 DEBUG(5,("pdb_nds_update_login_attempts: using random password %s\n", clear_text_pw));
807 /* Parse the location string */
808 p = ldap_state->location;
810 /* skip leading "URL:" (if any) */
811 if ( strnequal( p, "URL:", 4 ) ) {
815 sscanf(p, "%10[^:]://%254[^:/]:%d", protocol, ldap_server, &ldap_port);
817 if (ldap_port == 0) {
818 if (strequal(protocol, "ldap")) {
819 ldap_port = LDAP_PORT;
820 } else if (strequal(protocol, "ldaps")) {
821 ldap_port = LDAPS_PORT;
823 DEBUG(0, ("unrecognised protocol (%s)!\n", protocol));
827 ld = ldap_init(ldap_server, ldap_port);
832 /* LDAP version 3 required for ldap_sasl */
833 if (ldap_get_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version) == LDAP_OPT_SUCCESS) {
834 if (version != LDAP_VERSION3) {
835 version = LDAP_VERSION3;
836 if (ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &version) == LDAP_OPT_SUCCESS) {
837 DEBUG(4, ("pdb_nds_update_login_attempts: Set protocol version to LDAP_VERSION3\n"));
842 /* Turn on ssl if required */
843 if(strequal(protocol, "ldaps")) {
844 int tls = LDAP_OPT_X_TLS_HARD;
845 if (ldap_set_option (ld, LDAP_OPT_X_TLS, &tls) != LDAP_SUCCESS) {
846 DEBUG(1, ("pdb_nds_update_login_attempts: Failed to setup a TLS session\n"));
848 DEBUG(4, ("pdb_nds_update_login_attempts: Activated TLS on session\n"));
853 /* Attempt simple bind with real or bogus password */
854 rc = ldap_simple_bind_s(ld, dn, clear_text_pw);
855 if (rc == LDAP_SUCCESS) {
856 DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Successful for %s\n", username));
857 ldap_unbind_ext(ld, NULL, NULL);
859 NTSTATUS nt_status = NT_STATUS_ACCOUNT_RESTRICTION;
860 DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Failed for %s\n", username));
862 case LDAP_INVALID_CREDENTIALS:
863 nt_status = NT_STATUS_WRONG_PASSWORD;
875 /**********************************************************************
876 Intitalise the parts of the pdb_context that are common to NDS_ldapsam modes
877 *********************************************************************/
879 static NTSTATUS pdb_init_NDS_ldapsam_common(PDB_CONTEXT *pdb_context, PDB_METHODS **pdb_method, const char *location)
881 struct ldapsam_privates *ldap_state = (*pdb_method)->private_data;
883 /* Mark this as eDirectory ldap */
884 ldap_state->is_nds_ldap = True;
886 /* Add pdb_nds specific method for updating login attempts. */
887 (*pdb_method)->update_login_attempts = pdb_nds_update_login_attempts;
889 /* Save location for use in pdb_nds_update_login_attempts */
890 ldap_state->location = strdup(location);
896 /**********************************************************************
897 Initialise the 'nds compat' mode for pdb_ldap
898 *********************************************************************/
900 static NTSTATUS pdb_init_NDS_ldapsam_compat(PDB_CONTEXT *pdb_context, PDB_METHODS **pdb_method, const char *location)
902 NTSTATUS nt_status = pdb_init_ldapsam_compat(pdb_context, pdb_method, location);
904 (*pdb_method)->name = "NDS_ldapsam_compat";
906 pdb_init_NDS_ldapsam_common(pdb_context, pdb_method, location);
912 /**********************************************************************
913 Initialise the 'nds' normal mode for pdb_ldap
914 *********************************************************************/
916 static NTSTATUS pdb_init_NDS_ldapsam(PDB_CONTEXT *pdb_context, PDB_METHODS **pdb_method, const char *location)
918 NTSTATUS nt_status = pdb_init_ldapsam(pdb_context, pdb_method, location);
920 (*pdb_method)->name = "NDS_ldapsam";
922 pdb_init_NDS_ldapsam_common(pdb_context, pdb_method, location);
927 NTSTATUS pdb_nds_init(void)
930 if (!NT_STATUS_IS_OK(nt_status = smb_register_passdb(PASSDB_INTERFACE_VERSION, "NDS_ldapsam", pdb_init_NDS_ldapsam)))
933 if (!NT_STATUS_IS_OK(nt_status = smb_register_passdb(PASSDB_INTERFACE_VERSION, "NDS_ldapsam_compat", pdb_init_NDS_ldapsam_compat)))