diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /source3/passdb/passdb.c | |
parent | Initial commit. (diff) | |
download | samba-upstream.tar.xz samba-upstream.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/passdb/passdb.c')
-rw-r--r-- | source3/passdb/passdb.c | 2755 |
1 files changed, 2755 insertions, 0 deletions
diff --git a/source3/passdb/passdb.c b/source3/passdb/passdb.c new file mode 100644 index 0000000..526dea5 --- /dev/null +++ b/source3/passdb/passdb.c @@ -0,0 +1,2755 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Jeremy Allison 1996-2001 + Copyright (C) Luke Kenneth Casson Leighton 1996-1998 + Copyright (C) Gerald (Jerry) Carter 2000-2006 + Copyright (C) Andrew Bartlett 2001-2002 + Copyright (C) Simo Sorce 2003 + Copyright (C) Volker Lendecke 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "passdb.h" +#include "system/passwd.h" +#include "../libcli/auth/libcli_auth.h" +#include "secrets.h" +#include "../libcli/security/security.h" +#include "../lib/util/util_pw.h" +#include "util_tdb.h" +#include "auth/credentials/credentials.h" +#include "lib/param/param.h" +#include "lib/util/string_wrappers.h" +#include "source3/lib/substitute.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +/********************************************************************** +***********************************************************************/ + +static int samu_destroy(struct samu *user) +{ + data_blob_clear_free( &user->lm_pw ); + data_blob_clear_free( &user->nt_pw ); + + if ( user->plaintext_pw ) + BURN_PTR_SIZE(user->plaintext_pw, strlen(user->plaintext_pw)); + + return 0; +} + +/********************************************************************** + generate a new struct samuser +***********************************************************************/ + +struct samu *samu_new( TALLOC_CTX *ctx ) +{ + struct samu *user; + + if ( !(user = talloc_zero( ctx, struct samu )) ) { + DEBUG(0,("samuser_new: Talloc failed!\n")); + return NULL; + } + + talloc_set_destructor( user, samu_destroy ); + + /* no initial methods */ + + user->methods = NULL; + + /* Don't change these timestamp settings without a good reason. + They are important for NT member server compatibility. */ + + user->logon_time = (time_t)0; + user->pass_last_set_time = (time_t)0; + user->pass_can_change_time = (time_t)0; + user->logoff_time = get_time_t_max(); + user->kickoff_time = get_time_t_max(); + user->fields_present = 0x00ffffff; + user->logon_divs = 168; /* hours per week */ + user->hours_len = 21; /* 21 times 8 bits = 168 */ + memset(user->hours, 0xff, user->hours_len); /* available at all hours */ + user->bad_password_count = 0; + user->logon_count = 0; + user->unknown_6 = 0x000004ec; /* don't know */ + + /* Some parts of samba strlen their pdb_get...() returns, + so this keeps the interface unchanged for now. */ + + user->username = ""; + user->domain = ""; + user->nt_username = ""; + user->full_name = ""; + user->home_dir = ""; + user->logon_script = ""; + user->profile_path = ""; + user->acct_desc = ""; + user->workstations = ""; + user->comment = ""; + user->munged_dial = ""; + + user->plaintext_pw = NULL; + + /* Unless we know otherwise have a Account Control Bit + value of 'normal user'. This helps User Manager, which + asks for a filtered list of users. */ + + user->acct_ctrl = ACB_NORMAL; + + return user; +} + +static int count_commas(const char *str) +{ + int num_commas = 0; + const char *comma = str; + + while ((comma = strchr(comma, ',')) != NULL) { + comma += 1; + num_commas += 1; + } + return num_commas; +} + +/********************************************************************* + Initialize a struct samu from a struct passwd including the user + and group SIDs. The *user structure is filled out with the Unix + attributes and a user SID. +*********************************************************************/ + +static NTSTATUS samu_set_unix_internal(struct pdb_methods *methods, + struct samu *user, const struct passwd *pwd, bool create) +{ + const char *guest_account = lp_guest_account(); + const char *domain = lp_netbios_name(); + char *fullname; + uint32_t urid; + bool ok; + + if ( !pwd ) { + return NT_STATUS_NO_SUCH_USER; + } + + /* Basic properties based upon the Unix account information */ + + ok = pdb_set_username(user, pwd->pw_name, PDB_SET); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + fullname = NULL; + + if (count_commas(pwd->pw_gecos) == 3) { + /* + * Heuristic: This seems to be a gecos field that has been + * edited by chfn(1). Only use the part before the first + * comma. Fixes bug 5198. + */ + fullname = talloc_strndup( + talloc_tos(), pwd->pw_gecos, + strchr(pwd->pw_gecos, ',') - pwd->pw_gecos); + if (fullname == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (fullname != NULL) { + ok = pdb_set_fullname(user, fullname, PDB_SET); + } else { + ok = pdb_set_fullname(user, pwd->pw_gecos, PDB_SET); + } + TALLOC_FREE(fullname); + + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + ok = pdb_set_domain(user, get_global_sam_name(), PDB_DEFAULT); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } +#if 0 + /* This can lead to a primary group of S-1-22-2-XX which + will be rejected by other parts of the Samba code. + Rely on pdb_get_group_sid() to "Do The Right Thing" (TM) + --jerry */ + + gid_to_sid(&group_sid, pwd->pw_gid); + pdb_set_group_sid(user, &group_sid, PDB_SET); +#endif + + /* save the password structure for later use */ + + user->unix_pw = tcopy_passwd( user, pwd ); + if (user->unix_pw == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* Special case for the guest account which must have a RID of 501 */ + + if ( strequal( pwd->pw_name, guest_account ) ) { + if ( !pdb_set_user_sid_from_rid(user, DOMAIN_RID_GUEST, PDB_DEFAULT)) { + return NT_STATUS_NO_SUCH_USER; + } + return NT_STATUS_OK; + } + + /* Non-guest accounts...Check for a workstation or user account */ + + if (pwd->pw_name[strlen(pwd->pw_name)-1] == '$') { + /* workstation */ + + if (!pdb_set_acct_ctrl(user, ACB_WSTRUST, PDB_DEFAULT)) { + DEBUG(1, ("Failed to set 'workstation account' flags for user %s.\n", + pwd->pw_name)); + return NT_STATUS_INVALID_COMPUTER_NAME; + } + } + else { + /* user */ + + if (!pdb_set_acct_ctrl(user, ACB_NORMAL, PDB_DEFAULT)) { + DEBUG(1, ("Failed to set 'normal account' flags for user %s.\n", + pwd->pw_name)); + return NT_STATUS_INVALID_ACCOUNT_NAME; + } + + /* set some basic attributes */ + + ok = pdb_set_profile_path( + user, + talloc_sub_specified( + user, + lp_logon_path(), + pwd->pw_name, + NULL, + domain, + pwd->pw_uid, + pwd->pw_gid), + PDB_DEFAULT); + ok &= pdb_set_homedir( + user, + talloc_sub_specified( + user, + lp_logon_home(), + pwd->pw_name, + NULL, + domain, + pwd->pw_uid, + pwd->pw_gid), + PDB_DEFAULT); + ok &= pdb_set_dir_drive( + user, + talloc_sub_specified( + user, + lp_logon_drive(), + pwd->pw_name, + NULL, + domain, + pwd->pw_uid, + pwd->pw_gid), + PDB_DEFAULT); + ok &= pdb_set_logon_script( + user, + talloc_sub_specified( + user, + lp_logon_script(), + pwd->pw_name, + NULL, + domain, + pwd->pw_uid, + pwd->pw_gid), + PDB_DEFAULT); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + } + + /* Now deal with the user SID. If we have a backend that can generate + RIDs, then do so. But sometimes the caller just wanted a structure + initialized and will fill in these fields later (such as from a + netr_SamInfo3 structure) */ + + if ( create && (methods->capabilities(methods) & PDB_CAP_STORE_RIDS)) { + uint32_t user_rid; + struct dom_sid user_sid; + + if ( !methods->new_rid(methods, &user_rid) ) { + DEBUG(3, ("Could not allocate a new RID\n")); + return NT_STATUS_ACCESS_DENIED; + } + + sid_compose(&user_sid, get_global_sam_sid(), user_rid); + + if ( !pdb_set_user_sid(user, &user_sid, PDB_SET) ) { + DEBUG(3, ("pdb_set_user_sid failed\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; + } + + /* generate a SID for the user with the RID algorithm */ + + urid = algorithmic_pdb_uid_to_user_rid( user->unix_pw->pw_uid ); + + if ( !pdb_set_user_sid_from_rid( user, urid, PDB_SET) ) { + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; +} + +/******************************************************************** + Set the Unix user attributes +********************************************************************/ + +NTSTATUS samu_set_unix(struct samu *user, const struct passwd *pwd) +{ + return samu_set_unix_internal( NULL, user, pwd, False ); +} + +NTSTATUS samu_alloc_rid_unix(struct pdb_methods *methods, + struct samu *user, const struct passwd *pwd) +{ + return samu_set_unix_internal( methods, user, pwd, True ); +} + +/********************************************************** + Encode the account control bits into a string. + length = length of string to encode into (including terminating + null). length *MUST BE MORE THAN 2* ! + **********************************************************/ + +char *pdb_encode_acct_ctrl(uint32_t acct_ctrl, size_t length) +{ + fstring acct_str; + char *result; + + size_t i = 0; + + SMB_ASSERT(length <= sizeof(acct_str)); + + acct_str[i++] = '['; + + if (acct_ctrl & ACB_PWNOTREQ ) acct_str[i++] = 'N'; + if (acct_ctrl & ACB_DISABLED ) acct_str[i++] = 'D'; + if (acct_ctrl & ACB_HOMDIRREQ) acct_str[i++] = 'H'; + if (acct_ctrl & ACB_TEMPDUP ) acct_str[i++] = 'T'; + if (acct_ctrl & ACB_NORMAL ) acct_str[i++] = 'U'; + if (acct_ctrl & ACB_MNS ) acct_str[i++] = 'M'; + if (acct_ctrl & ACB_WSTRUST ) acct_str[i++] = 'W'; + if (acct_ctrl & ACB_SVRTRUST ) acct_str[i++] = 'S'; + if (acct_ctrl & ACB_AUTOLOCK ) acct_str[i++] = 'L'; + if (acct_ctrl & ACB_PWNOEXP ) acct_str[i++] = 'X'; + if (acct_ctrl & ACB_DOMTRUST ) acct_str[i++] = 'I'; + + for ( ; i < length - 2 ; i++ ) + acct_str[i] = ' '; + + i = length - 2; + acct_str[i++] = ']'; + acct_str[i++] = '\0'; + + result = talloc_strdup(talloc_tos(), acct_str); + SMB_ASSERT(result != NULL); + return result; +} + +/********************************************************** + Decode the account control bits from a string. + **********************************************************/ + +uint32_t pdb_decode_acct_ctrl(const char *p) +{ + uint32_t acct_ctrl = 0; + bool finished = false; + + /* + * Check if the account type bits have been encoded after the + * NT password (in the form [NDHTUWSLXI]). + */ + + if (*p != '[') + return 0; + + for (p++; *p && !finished; p++) { + switch (*p) { + case 'N': { acct_ctrl |= ACB_PWNOTREQ ; break; /* 'N'o password. */ } + case 'D': { acct_ctrl |= ACB_DISABLED ; break; /* 'D'isabled. */ } + case 'H': { acct_ctrl |= ACB_HOMDIRREQ; break; /* 'H'omedir required. */ } + case 'T': { acct_ctrl |= ACB_TEMPDUP ; break; /* 'T'emp account. */ } + case 'U': { acct_ctrl |= ACB_NORMAL ; break; /* 'U'ser account (normal). */ } + case 'M': { acct_ctrl |= ACB_MNS ; break; /* 'M'NS logon user account. What is this ? */ } + case 'W': { acct_ctrl |= ACB_WSTRUST ; break; /* 'W'orkstation account. */ } + case 'S': { acct_ctrl |= ACB_SVRTRUST ; break; /* 'S'erver account. */ } + case 'L': { acct_ctrl |= ACB_AUTOLOCK ; break; /* 'L'ocked account. */ } + case 'X': { acct_ctrl |= ACB_PWNOEXP ; break; /* No 'X'piry on password */ } + case 'I': { acct_ctrl |= ACB_DOMTRUST ; break; /* 'I'nterdomain trust account. */ } + case ' ': { break; } + case ':': + case '\n': + case '\0': + case ']': + default: { finished = true; } + } + } + + return acct_ctrl; +} + +/************************************************************* + Routine to set 32 hex password characters from a 16 byte array. +**************************************************************/ + +void pdb_sethexpwd(char p[33], const unsigned char *pwd, uint32_t acct_ctrl) +{ + if (pwd != NULL) { + hex_encode_buf(p, pwd, 16); + } else { + if (acct_ctrl & ACB_PWNOTREQ) + strlcpy(p, "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX", 33); + else + strlcpy(p, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 33); + } +} + +/************************************************************* + Routine to get the 32 hex characters and turn them + into a 16 byte array. +**************************************************************/ + +bool pdb_gethexpwd(const char *p, unsigned char *pwd) +{ + int i; + unsigned char lonybble, hinybble; + const char *hexchars = "0123456789ABCDEF"; + char *p1, *p2; + + if (!p) + return false; + + for (i = 0; i < 32; i += 2) { + hinybble = toupper_m(p[i]); + lonybble = toupper_m(p[i + 1]); + + p1 = strchr(hexchars, hinybble); + p2 = strchr(hexchars, lonybble); + + if (!p1 || !p2) + return false; + + hinybble = PTR_DIFF(p1, hexchars); + lonybble = PTR_DIFF(p2, hexchars); + + pwd[i / 2] = (hinybble << 4) | lonybble; + } + return true; +} + +/************************************************************* + Routine to set 42 hex hours characters from a 21 byte array. +**************************************************************/ + +void pdb_sethexhours(char *p, const unsigned char *hours) +{ + if (hours != NULL) { + hex_encode_buf(p, hours, 21); + } else { + strlcpy(p, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 44); + } +} + +/************************************************************* + Routine to get the 42 hex characters and turn them + into a 21 byte array. +**************************************************************/ + +bool pdb_gethexhours(const char *p, unsigned char *hours) +{ + int i; + unsigned char lonybble, hinybble; + const char *hexchars = "0123456789ABCDEF"; + char *p1, *p2; + + if (!p) { + return (False); + } + + for (i = 0; i < 42; i += 2) { + hinybble = toupper_m(p[i]); + lonybble = toupper_m(p[i + 1]); + + p1 = strchr(hexchars, hinybble); + p2 = strchr(hexchars, lonybble); + + if (!p1 || !p2) { + return (False); + } + + hinybble = PTR_DIFF(p1, hexchars); + lonybble = PTR_DIFF(p2, hexchars); + + hours[i / 2] = (hinybble << 4) | lonybble; + } + return (True); +} + +/******************************************************************** +********************************************************************/ + +int algorithmic_rid_base(void) +{ + int rid_offset; + + rid_offset = lp_algorithmic_rid_base(); + + if (rid_offset < BASE_RID) { + /* Try to prevent admin foot-shooting, we can't put algorithmic + rids below 1000, that's the 'well known RIDs' on NT */ + DEBUG(0, ("'algorithmic rid base' must be equal to or above %ld\n", BASE_RID)); + rid_offset = BASE_RID; + } + if (rid_offset & 1) { + DEBUG(0, ("algorithmic rid base must be even\n")); + rid_offset += 1; + } + return rid_offset; +} + +/******************************************************************* + Converts NT user RID to a UNIX uid. + ********************************************************************/ + +uid_t algorithmic_pdb_user_rid_to_uid(uint32_t user_rid) +{ + int rid_offset = algorithmic_rid_base(); + return (uid_t)(((user_rid & (~USER_RID_TYPE)) - rid_offset)/RID_MULTIPLIER); +} + +uid_t max_algorithmic_uid(void) +{ + return algorithmic_pdb_user_rid_to_uid(0xfffffffe); +} + +/******************************************************************* + converts UNIX uid to an NT User RID. + ********************************************************************/ + +uint32_t algorithmic_pdb_uid_to_user_rid(uid_t uid) +{ + int rid_offset = algorithmic_rid_base(); + return (((((uint32_t)uid)*RID_MULTIPLIER) + rid_offset) | USER_RID_TYPE); +} + +/******************************************************************* + Converts NT group RID to a UNIX gid. + ********************************************************************/ + +gid_t pdb_group_rid_to_gid(uint32_t group_rid) +{ + int rid_offset = algorithmic_rid_base(); + return (gid_t)(((group_rid & (~GROUP_RID_TYPE))- rid_offset)/RID_MULTIPLIER); +} + +gid_t max_algorithmic_gid(void) +{ + return pdb_group_rid_to_gid(0xffffffff); +} + +/******************************************************************* + converts NT Group RID to a UNIX uid. + + warning: you must not call that function only + you must do a call to the group mapping first. + there is not anymore a direct link between the gid and the rid. + ********************************************************************/ + +uint32_t algorithmic_pdb_gid_to_group_rid(gid_t gid) +{ + int rid_offset = algorithmic_rid_base(); + return (((((uint32_t)gid)*RID_MULTIPLIER) + rid_offset) | GROUP_RID_TYPE); +} + +/******************************************************************* + Decides if a RID is a well known RID. + ********************************************************************/ + +static bool rid_is_well_known(uint32_t rid) +{ + /* Not using rid_offset here, because this is the actual + NT fixed value (1000) */ + + return (rid < BASE_RID); +} + +/******************************************************************* + Decides if a RID is a user or group RID. + ********************************************************************/ + +bool algorithmic_pdb_rid_is_user(uint32_t rid) +{ + if ( rid_is_well_known(rid) ) { + /* + * The only well known user RIDs are DOMAIN_RID_ADMINISTRATOR + * and DOMAIN_RID_GUEST. + */ + if(rid == DOMAIN_RID_ADMINISTRATOR || rid == DOMAIN_RID_GUEST) + return True; + } else if((rid & RID_TYPE_MASK) == USER_RID_TYPE) { + return True; + } + return False; +} + +/******************************************************************* + Convert a name into a SID. Used in the lookup name rpc. + ********************************************************************/ + +bool lookup_global_sam_name(const char *name, int flags, uint32_t *rid, + enum lsa_SidType *type) +{ + GROUP_MAP *map; + bool ret; + + /* Windows treats "MACHINE\None" as a special name for + rid 513 on non-DCs. You cannot create a user or group + name "None" on Windows. You will get an error that + the group already exists. */ + + if ( strequal( name, "None" ) ) { + *rid = DOMAIN_RID_USERS; + *type = SID_NAME_DOM_GRP; + + return True; + } + + /* LOOKUP_NAME_GROUP is a hack to allow valid users = @foo to work + * correctly in the case where foo also exists as a user. If the flag + * is set, don't look for users at all. */ + + if ((flags & LOOKUP_NAME_GROUP) == 0) { + struct samu *sam_account = NULL; + struct dom_sid user_sid; + + if ( !(sam_account = samu_new( NULL )) ) { + return False; + } + + become_root(); + ret = pdb_getsampwnam(sam_account, name); + unbecome_root(); + + if (ret) { + sid_copy(&user_sid, pdb_get_user_sid(sam_account)); + } + + TALLOC_FREE(sam_account); + + if (ret) { + if (!sid_check_is_in_our_sam(&user_sid)) { + struct dom_sid_buf buf; + DBG_ERR("User %s with invalid SID %s" + " in passdb\n", + name, + dom_sid_str_buf(&user_sid, &buf)); + return False; + } + + sid_peek_rid(&user_sid, rid); + *type = SID_NAME_USER; + return True; + } + } + + /* + * Maybe it is a group ? + */ + + map = talloc_zero(NULL, GROUP_MAP); + if (!map) { + return false; + } + + become_root(); + ret = pdb_getgrnam(map, name); + unbecome_root(); + + if (!ret) { + TALLOC_FREE(map); + return False; + } + + /* BUILTIN groups are looked up elsewhere */ + if (!sid_check_is_in_our_sam(&map->sid)) { + struct dom_sid_buf buf; + DEBUG(10, ("Found group %s (%s) not in our domain -- " + "ignoring.\n", + name, + dom_sid_str_buf(&map->sid, &buf))); + TALLOC_FREE(map); + return False; + } + + /* yes it's a mapped group */ + sid_peek_rid(&map->sid, rid); + *type = map->sid_name_use; + TALLOC_FREE(map); + return True; +} + +/************************************************************* + Change a password entry in the local passdb backend. + + Assumptions: + - always called as root + - ignores the account type except when adding a new account + - will create/delete the unix account if the relative + add/delete user script is configured + + *************************************************************/ + +NTSTATUS local_password_change(const char *user_name, + int local_flags, + const char *new_passwd, + char **pp_err_str, + char **pp_msg_str) +{ + TALLOC_CTX *tosctx; + struct samu *sam_pass; + uint32_t acb; + uint32_t rid; + NTSTATUS result; + bool user_exists; + int ret = -1; + + *pp_err_str = NULL; + *pp_msg_str = NULL; + + tosctx = talloc_tos(); + + sam_pass = samu_new(tosctx); + if (!sam_pass) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + /* Get the smb passwd entry for this user */ + user_exists = pdb_getsampwnam(sam_pass, user_name); + + /* Check delete first, we don't need to do anything else if we + * are going to delete the account */ + if (user_exists && (local_flags & LOCAL_DELETE_USER)) { + + result = pdb_delete_user(tosctx, sam_pass); + if (!NT_STATUS_IS_OK(result)) { + ret = asprintf(pp_err_str, + "Failed to delete entry for user %s.\n", + user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_UNSUCCESSFUL; + } else { + ret = asprintf(pp_msg_str, + "Deleted user %s.\n", + user_name); + if (ret < 0) { + *pp_msg_str = NULL; + } + } + goto done; + } + + if (user_exists && (local_flags & LOCAL_ADD_USER)) { + /* the entry already existed */ + local_flags &= ~LOCAL_ADD_USER; + } + + if (!user_exists && !(local_flags & LOCAL_ADD_USER)) { + ret = asprintf(pp_err_str, + "Failed to find entry for user %s.\n", + user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_NO_SUCH_USER; + goto done; + } + + /* First thing add the new user if we are required to do so */ + if (local_flags & LOCAL_ADD_USER) { + + if (local_flags & LOCAL_TRUST_ACCOUNT) { + acb = ACB_WSTRUST; + } else if (local_flags & LOCAL_INTERDOM_ACCOUNT) { + acb = ACB_DOMTRUST; + } else { + acb = ACB_NORMAL; + } + + result = pdb_create_user(tosctx, user_name, acb, &rid); + if (!NT_STATUS_IS_OK(result)) { + ret = asprintf(pp_err_str, + "Failed to add entry for user %s.\n", + user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + sam_pass = samu_new(tosctx); + if (!sam_pass) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + /* Now get back the smb passwd entry for this new user */ + user_exists = pdb_getsampwnam(sam_pass, user_name); + if (!user_exists) { + ret = asprintf(pp_err_str, + "Failed to add entry for user %s.\n", + user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + } + + acb = pdb_get_acct_ctrl(sam_pass); + + /* + * We are root - just write the new password + * and the valid last change time. + */ + if ((local_flags & LOCAL_SET_NO_PASSWORD) && !(acb & ACB_PWNOTREQ)) { + acb |= ACB_PWNOTREQ; + if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) { + ret = asprintf(pp_err_str, + "Failed to set 'no password required' " + "flag for user %s.\n", user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + } + + if (local_flags & LOCAL_SET_PASSWORD) { + /* + * If we're dealing with setting a completely empty user account + * ie. One with a password of 'XXXX', but not set disabled (like + * an account created from scratch) then if the old password was + * 'XX's then getsmbpwent will have set the ACB_DISABLED flag. + * We remove that as we're giving this user their first password + * and the decision hasn't really been made to disable them (ie. + * don't create them disabled). JRA. + */ + if ((pdb_get_lanman_passwd(sam_pass) == NULL) && + (acb & ACB_DISABLED)) { + acb &= (~ACB_DISABLED); + if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) { + ret = asprintf(pp_err_str, + "Failed to unset 'disabled' " + "flag for user %s.\n", + user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + } + + acb &= (~ACB_PWNOTREQ); + if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) { + ret = asprintf(pp_err_str, + "Failed to unset 'no password required'" + " flag for user %s.\n", user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + if (!pdb_set_plaintext_passwd(sam_pass, new_passwd)) { + ret = asprintf(pp_err_str, + "Failed to set password for " + "user %s.\n", user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + } + + if ((local_flags & LOCAL_DISABLE_USER) && !(acb & ACB_DISABLED)) { + acb |= ACB_DISABLED; + if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) { + ret = asprintf(pp_err_str, + "Failed to set 'disabled' flag for " + "user %s.\n", user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + } + + if ((local_flags & LOCAL_ENABLE_USER) && (acb & ACB_DISABLED)) { + acb &= (~ACB_DISABLED); + if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) { + ret = asprintf(pp_err_str, + "Failed to unset 'disabled' flag for " + "user %s.\n", user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + } + + /* now commit changes if any */ + result = pdb_update_sam_account(sam_pass); + if (!NT_STATUS_IS_OK(result)) { + ret = asprintf(pp_err_str, + "Failed to modify entry for user %s.\n", + user_name); + if (ret < 0) { + *pp_err_str = NULL; + } + goto done; + } + + if (local_flags & LOCAL_ADD_USER) { + ret = asprintf(pp_msg_str, "Added user %s.\n", user_name); + } else if (local_flags & LOCAL_DISABLE_USER) { + ret = asprintf(pp_msg_str, "Disabled user %s.\n", user_name); + } else if (local_flags & LOCAL_ENABLE_USER) { + ret = asprintf(pp_msg_str, "Enabled user %s.\n", user_name); + } else if (local_flags & LOCAL_SET_NO_PASSWORD) { + ret = asprintf(pp_msg_str, + "User %s password set to none.\n", user_name); + } + + if (ret < 0) { + *pp_msg_str = NULL; + } + + result = NT_STATUS_OK; + +done: + TALLOC_FREE(sam_pass); + return result; +} + +/********************************************************************** + Marshall/unmarshall struct samu structs. + *********************************************************************/ + +#define SAMU_BUFFER_FORMAT_V0 "ddddddBBBBBBBBBBBBddBBwdwdBwwd" +#define SAMU_BUFFER_FORMAT_V1 "dddddddBBBBBBBBBBBBddBBwdwdBwwd" +#define SAMU_BUFFER_FORMAT_V2 "dddddddBBBBBBBBBBBBddBBBwwdBwwd" +#define SAMU_BUFFER_FORMAT_V3 "dddddddBBBBBBBBBBBBddBBBdwdBwwd" +/* nothing changed between V3 and V4 */ + +/********************************************************************* +*********************************************************************/ + +static bool init_samu_from_buffer_v0(struct samu *sampass, uint8_t *buf, uint32_t buflen) +{ + + /* times are stored as 32bit integer + take care on system with 64bit wide time_t + --SSS */ + uint32_t logon_time, + logoff_time, + kickoff_time, + pass_last_set_time, + pass_can_change_time, + pass_must_change_time; + char *username = NULL; + char *domain = NULL; + char *nt_username = NULL; + char *dir_drive = NULL; + char *unknown_str = NULL; + char *munged_dial = NULL; + char *fullname = NULL; + char *homedir = NULL; + char *logon_script = NULL; + char *profile_path = NULL; + char *acct_desc = NULL; + char *workstations = NULL; + uint32_t username_len, domain_len, nt_username_len, + dir_drive_len, unknown_str_len, munged_dial_len, + fullname_len, homedir_len, logon_script_len, + profile_path_len, acct_desc_len, workstations_len; + + uint32_t user_rid, group_rid, remove_me, hours_len, unknown_6; + uint16_t acct_ctrl, logon_divs; + uint16_t bad_password_count, logon_count; + uint8_t *hours = NULL; + uint8_t *lm_pw_ptr = NULL, *nt_pw_ptr = NULL; + uint32_t len = 0; + uint32_t lm_pw_len, nt_pw_len, hourslen; + bool ret = True; + + if(sampass == NULL || buf == NULL) { + DEBUG(0, ("init_samu_from_buffer_v0: NULL parameters found!\n")); + return False; + } + +/* SAMU_BUFFER_FORMAT_V0 "ddddddBBBBBBBBBBBBddBBwdwdBwwd" */ + + /* unpack the buffer into variables */ + len = tdb_unpack (buf, buflen, SAMU_BUFFER_FORMAT_V0, + &logon_time, /* d */ + &logoff_time, /* d */ + &kickoff_time, /* d */ + &pass_last_set_time, /* d */ + &pass_can_change_time, /* d */ + &pass_must_change_time, /* d */ + &username_len, &username, /* B */ + &domain_len, &domain, /* B */ + &nt_username_len, &nt_username, /* B */ + &fullname_len, &fullname, /* B */ + &homedir_len, &homedir, /* B */ + &dir_drive_len, &dir_drive, /* B */ + &logon_script_len, &logon_script, /* B */ + &profile_path_len, &profile_path, /* B */ + &acct_desc_len, &acct_desc, /* B */ + &workstations_len, &workstations, /* B */ + &unknown_str_len, &unknown_str, /* B */ + &munged_dial_len, &munged_dial, /* B */ + &user_rid, /* d */ + &group_rid, /* d */ + &lm_pw_len, &lm_pw_ptr, /* B */ + &nt_pw_len, &nt_pw_ptr, /* B */ + &acct_ctrl, /* w */ + &remove_me, /* remove on the next TDB_FORMAT upgarde */ /* d */ + &logon_divs, /* w */ + &hours_len, /* d */ + &hourslen, &hours, /* B */ + &bad_password_count, /* w */ + &logon_count, /* w */ + &unknown_6); /* d */ + + if (len == (uint32_t) -1) { + ret = False; + goto done; + } + + pdb_set_logon_time(sampass, logon_time, PDB_SET); + pdb_set_logoff_time(sampass, logoff_time, PDB_SET); + pdb_set_kickoff_time(sampass, kickoff_time, PDB_SET); + pdb_set_pass_can_change_time(sampass, pass_can_change_time, PDB_SET); + pdb_set_pass_last_set_time(sampass, pass_last_set_time, PDB_SET); + + pdb_set_username(sampass, username, PDB_SET); + pdb_set_domain(sampass, domain, PDB_SET); + pdb_set_nt_username(sampass, nt_username, PDB_SET); + pdb_set_fullname(sampass, fullname, PDB_SET); + + if (homedir) { + pdb_set_homedir(sampass, homedir, PDB_SET); + } + else { + pdb_set_homedir(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_home()), + PDB_DEFAULT); + } + + if (dir_drive) + pdb_set_dir_drive(sampass, dir_drive, PDB_SET); + else { + pdb_set_dir_drive(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_drive()), + PDB_DEFAULT); + } + + if (logon_script) + pdb_set_logon_script(sampass, logon_script, PDB_SET); + else { + pdb_set_logon_script(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_script()), + PDB_DEFAULT); + } + + if (profile_path) { + pdb_set_profile_path(sampass, profile_path, PDB_SET); + } else { + pdb_set_profile_path(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_path()), + PDB_DEFAULT); + } + + pdb_set_acct_desc(sampass, acct_desc, PDB_SET); + pdb_set_workstations(sampass, workstations, PDB_SET); + pdb_set_munged_dial(sampass, munged_dial, PDB_SET); + + if (lm_pw_ptr && lm_pw_len == LM_HASH_LEN) { + if (!pdb_set_lanman_passwd(sampass, lm_pw_ptr, PDB_SET)) { + ret = False; + goto done; + } + } + + if (nt_pw_ptr && nt_pw_len == NT_HASH_LEN) { + if (!pdb_set_nt_passwd(sampass, nt_pw_ptr, PDB_SET)) { + ret = False; + goto done; + } + } + + pdb_set_pw_history(sampass, NULL, 0, PDB_SET); + pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET); + pdb_set_group_sid_from_rid(sampass, group_rid, PDB_SET); + pdb_set_hours_len(sampass, hours_len, PDB_SET); + pdb_set_bad_password_count(sampass, bad_password_count, PDB_SET); + pdb_set_logon_count(sampass, logon_count, PDB_SET); + pdb_set_unknown_6(sampass, unknown_6, PDB_SET); + pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET); + pdb_set_logon_divs(sampass, logon_divs, PDB_SET); + pdb_set_hours(sampass, hours, hours_len, PDB_SET); + +done: + + SAFE_FREE(username); + SAFE_FREE(domain); + SAFE_FREE(nt_username); + SAFE_FREE(fullname); + SAFE_FREE(homedir); + SAFE_FREE(dir_drive); + SAFE_FREE(logon_script); + SAFE_FREE(profile_path); + SAFE_FREE(acct_desc); + SAFE_FREE(workstations); + SAFE_FREE(munged_dial); + SAFE_FREE(unknown_str); + SAFE_FREE(lm_pw_ptr); + SAFE_FREE(nt_pw_ptr); + SAFE_FREE(hours); + + return ret; +} + +/********************************************************************* +*********************************************************************/ + +static bool init_samu_from_buffer_v1(struct samu *sampass, uint8_t *buf, uint32_t buflen) +{ + + /* times are stored as 32bit integer + take care on system with 64bit wide time_t + --SSS */ + uint32_t logon_time, + logoff_time, + kickoff_time, + bad_password_time, + pass_last_set_time, + pass_can_change_time, + pass_must_change_time; + char *username = NULL; + char *domain = NULL; + char *nt_username = NULL; + char *dir_drive = NULL; + char *unknown_str = NULL; + char *munged_dial = NULL; + char *fullname = NULL; + char *homedir = NULL; + char *logon_script = NULL; + char *profile_path = NULL; + char *acct_desc = NULL; + char *workstations = NULL; + uint32_t username_len, domain_len, nt_username_len, + dir_drive_len, unknown_str_len, munged_dial_len, + fullname_len, homedir_len, logon_script_len, + profile_path_len, acct_desc_len, workstations_len; + + uint32_t user_rid, group_rid, remove_me, hours_len, unknown_6; + uint16_t acct_ctrl, logon_divs; + uint16_t bad_password_count, logon_count; + uint8_t *hours = NULL; + uint8_t *lm_pw_ptr = NULL, *nt_pw_ptr = NULL; + uint32_t len = 0; + uint32_t lm_pw_len, nt_pw_len, hourslen; + bool ret = True; + + if(sampass == NULL || buf == NULL) { + DEBUG(0, ("init_samu_from_buffer_v1: NULL parameters found!\n")); + return False; + } + +/* SAMU_BUFFER_FORMAT_V1 "dddddddBBBBBBBBBBBBddBBwdwdBwwd" */ + + /* unpack the buffer into variables */ + len = tdb_unpack (buf, buflen, SAMU_BUFFER_FORMAT_V1, + &logon_time, /* d */ + &logoff_time, /* d */ + &kickoff_time, /* d */ + /* Change from V0 is addition of bad_password_time field. */ + &bad_password_time, /* d */ + &pass_last_set_time, /* d */ + &pass_can_change_time, /* d */ + &pass_must_change_time, /* d */ + &username_len, &username, /* B */ + &domain_len, &domain, /* B */ + &nt_username_len, &nt_username, /* B */ + &fullname_len, &fullname, /* B */ + &homedir_len, &homedir, /* B */ + &dir_drive_len, &dir_drive, /* B */ + &logon_script_len, &logon_script, /* B */ + &profile_path_len, &profile_path, /* B */ + &acct_desc_len, &acct_desc, /* B */ + &workstations_len, &workstations, /* B */ + &unknown_str_len, &unknown_str, /* B */ + &munged_dial_len, &munged_dial, /* B */ + &user_rid, /* d */ + &group_rid, /* d */ + &lm_pw_len, &lm_pw_ptr, /* B */ + &nt_pw_len, &nt_pw_ptr, /* B */ + &acct_ctrl, /* w */ + &remove_me, /* d */ + &logon_divs, /* w */ + &hours_len, /* d */ + &hourslen, &hours, /* B */ + &bad_password_count, /* w */ + &logon_count, /* w */ + &unknown_6); /* d */ + + if (len == (uint32_t) -1) { + ret = False; + goto done; + } + + pdb_set_logon_time(sampass, logon_time, PDB_SET); + pdb_set_logoff_time(sampass, logoff_time, PDB_SET); + pdb_set_kickoff_time(sampass, kickoff_time, PDB_SET); + + /* Change from V0 is addition of bad_password_time field. */ + pdb_set_bad_password_time(sampass, bad_password_time, PDB_SET); + pdb_set_pass_can_change_time(sampass, pass_can_change_time, PDB_SET); + pdb_set_pass_last_set_time(sampass, pass_last_set_time, PDB_SET); + + pdb_set_username(sampass, username, PDB_SET); + pdb_set_domain(sampass, domain, PDB_SET); + pdb_set_nt_username(sampass, nt_username, PDB_SET); + pdb_set_fullname(sampass, fullname, PDB_SET); + + if (homedir) { + pdb_set_homedir(sampass, homedir, PDB_SET); + } + else { + pdb_set_homedir(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_home()), + PDB_DEFAULT); + } + + if (dir_drive) + pdb_set_dir_drive(sampass, dir_drive, PDB_SET); + else { + pdb_set_dir_drive(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_drive()), + PDB_DEFAULT); + } + + if (logon_script) + pdb_set_logon_script(sampass, logon_script, PDB_SET); + else { + pdb_set_logon_script(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_script()), + PDB_DEFAULT); + } + + if (profile_path) { + pdb_set_profile_path(sampass, profile_path, PDB_SET); + } else { + pdb_set_profile_path(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_path()), + PDB_DEFAULT); + } + + pdb_set_acct_desc(sampass, acct_desc, PDB_SET); + pdb_set_workstations(sampass, workstations, PDB_SET); + pdb_set_munged_dial(sampass, munged_dial, PDB_SET); + + if (lm_pw_ptr && lm_pw_len == LM_HASH_LEN) { + if (!pdb_set_lanman_passwd(sampass, lm_pw_ptr, PDB_SET)) { + ret = False; + goto done; + } + } + + if (nt_pw_ptr && nt_pw_len == NT_HASH_LEN) { + if (!pdb_set_nt_passwd(sampass, nt_pw_ptr, PDB_SET)) { + ret = False; + goto done; + } + } + + pdb_set_pw_history(sampass, NULL, 0, PDB_SET); + + pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET); + pdb_set_group_sid_from_rid(sampass, group_rid, PDB_SET); + pdb_set_hours_len(sampass, hours_len, PDB_SET); + pdb_set_bad_password_count(sampass, bad_password_count, PDB_SET); + pdb_set_logon_count(sampass, logon_count, PDB_SET); + pdb_set_unknown_6(sampass, unknown_6, PDB_SET); + pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET); + pdb_set_logon_divs(sampass, logon_divs, PDB_SET); + pdb_set_hours(sampass, hours, hours_len, PDB_SET); + +done: + + SAFE_FREE(username); + SAFE_FREE(domain); + SAFE_FREE(nt_username); + SAFE_FREE(fullname); + SAFE_FREE(homedir); + SAFE_FREE(dir_drive); + SAFE_FREE(logon_script); + SAFE_FREE(profile_path); + SAFE_FREE(acct_desc); + SAFE_FREE(workstations); + SAFE_FREE(munged_dial); + SAFE_FREE(unknown_str); + SAFE_FREE(lm_pw_ptr); + SAFE_FREE(nt_pw_ptr); + SAFE_FREE(hours); + + return ret; +} + +static bool init_samu_from_buffer_v2(struct samu *sampass, uint8_t *buf, uint32_t buflen) +{ + + /* times are stored as 32bit integer + take care on system with 64bit wide time_t + --SSS */ + uint32_t logon_time, + logoff_time, + kickoff_time, + bad_password_time, + pass_last_set_time, + pass_can_change_time, + pass_must_change_time; + char *username = NULL; + char *domain = NULL; + char *nt_username = NULL; + char *dir_drive = NULL; + char *unknown_str = NULL; + char *munged_dial = NULL; + char *fullname = NULL; + char *homedir = NULL; + char *logon_script = NULL; + char *profile_path = NULL; + char *acct_desc = NULL; + char *workstations = NULL; + uint32_t username_len, domain_len, nt_username_len, + dir_drive_len, unknown_str_len, munged_dial_len, + fullname_len, homedir_len, logon_script_len, + profile_path_len, acct_desc_len, workstations_len; + + uint32_t user_rid, group_rid, hours_len, unknown_6; + uint16_t acct_ctrl, logon_divs; + uint16_t bad_password_count, logon_count; + uint8_t *hours = NULL; + uint8_t *lm_pw_ptr = NULL, *nt_pw_ptr = NULL, *nt_pw_hist_ptr = NULL; + uint32_t len = 0; + uint32_t lm_pw_len, nt_pw_len, nt_pw_hist_len, hourslen; + uint32_t pwHistLen = 0; + bool ret = True; + fstring tmp_string; + bool expand_explicit = lp_passdb_expand_explicit(); + + if(sampass == NULL || buf == NULL) { + DEBUG(0, ("init_samu_from_buffer_v2: NULL parameters found!\n")); + return False; + } + +/* SAMU_BUFFER_FORMAT_V2 "dddddddBBBBBBBBBBBBddBBBwwdBwwd" */ + + /* unpack the buffer into variables */ + len = tdb_unpack (buf, buflen, SAMU_BUFFER_FORMAT_V2, + &logon_time, /* d */ + &logoff_time, /* d */ + &kickoff_time, /* d */ + &bad_password_time, /* d */ + &pass_last_set_time, /* d */ + &pass_can_change_time, /* d */ + &pass_must_change_time, /* d */ + &username_len, &username, /* B */ + &domain_len, &domain, /* B */ + &nt_username_len, &nt_username, /* B */ + &fullname_len, &fullname, /* B */ + &homedir_len, &homedir, /* B */ + &dir_drive_len, &dir_drive, /* B */ + &logon_script_len, &logon_script, /* B */ + &profile_path_len, &profile_path, /* B */ + &acct_desc_len, &acct_desc, /* B */ + &workstations_len, &workstations, /* B */ + &unknown_str_len, &unknown_str, /* B */ + &munged_dial_len, &munged_dial, /* B */ + &user_rid, /* d */ + &group_rid, /* d */ + &lm_pw_len, &lm_pw_ptr, /* B */ + &nt_pw_len, &nt_pw_ptr, /* B */ + /* Change from V1 is addition of password history field. */ + &nt_pw_hist_len, &nt_pw_hist_ptr, /* B */ + &acct_ctrl, /* w */ + /* Also "remove_me" field was removed. */ + &logon_divs, /* w */ + &hours_len, /* d */ + &hourslen, &hours, /* B */ + &bad_password_count, /* w */ + &logon_count, /* w */ + &unknown_6); /* d */ + + if (len == (uint32_t) -1) { + ret = False; + goto done; + } + + pdb_set_logon_time(sampass, logon_time, PDB_SET); + pdb_set_logoff_time(sampass, logoff_time, PDB_SET); + pdb_set_kickoff_time(sampass, kickoff_time, PDB_SET); + pdb_set_bad_password_time(sampass, bad_password_time, PDB_SET); + pdb_set_pass_can_change_time(sampass, pass_can_change_time, PDB_SET); + pdb_set_pass_last_set_time(sampass, pass_last_set_time, PDB_SET); + + pdb_set_username(sampass, username, PDB_SET); + pdb_set_domain(sampass, domain, PDB_SET); + pdb_set_nt_username(sampass, nt_username, PDB_SET); + pdb_set_fullname(sampass, fullname, PDB_SET); + + if (homedir) { + fstrcpy( tmp_string, homedir ); + if (expand_explicit) { + standard_sub_basic( username, domain, tmp_string, + sizeof(tmp_string) ); + } + pdb_set_homedir(sampass, tmp_string, PDB_SET); + } + else { + pdb_set_homedir(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_home()), + PDB_DEFAULT); + } + + if (dir_drive) + pdb_set_dir_drive(sampass, dir_drive, PDB_SET); + else + pdb_set_dir_drive(sampass, lp_logon_drive(), PDB_DEFAULT ); + + if (logon_script) { + fstrcpy( tmp_string, logon_script ); + if (expand_explicit) { + standard_sub_basic( username, domain, tmp_string, + sizeof(tmp_string) ); + } + pdb_set_logon_script(sampass, tmp_string, PDB_SET); + } + else { + pdb_set_logon_script(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_script()), + PDB_DEFAULT); + } + + if (profile_path) { + fstrcpy( tmp_string, profile_path ); + if (expand_explicit) { + standard_sub_basic( username, domain, tmp_string, + sizeof(tmp_string) ); + } + pdb_set_profile_path(sampass, tmp_string, PDB_SET); + } + else { + pdb_set_profile_path(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_path()), + PDB_DEFAULT); + } + + pdb_set_acct_desc(sampass, acct_desc, PDB_SET); + pdb_set_workstations(sampass, workstations, PDB_SET); + pdb_set_munged_dial(sampass, munged_dial, PDB_SET); + + if (lm_pw_ptr && lm_pw_len == LM_HASH_LEN) { + if (!pdb_set_lanman_passwd(sampass, lm_pw_ptr, PDB_SET)) { + ret = False; + goto done; + } + } + + if (nt_pw_ptr && nt_pw_len == NT_HASH_LEN) { + if (!pdb_set_nt_passwd(sampass, nt_pw_ptr, PDB_SET)) { + ret = False; + goto done; + } + } + + /* Change from V1 is addition of password history field. */ + pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHistLen); + if (pwHistLen) { + uint8_t *pw_hist = SMB_MALLOC_ARRAY(uint8_t, pwHistLen * PW_HISTORY_ENTRY_LEN); + if (!pw_hist) { + ret = False; + goto done; + } + memset(pw_hist, '\0', pwHistLen * PW_HISTORY_ENTRY_LEN); + if (nt_pw_hist_ptr && nt_pw_hist_len) { + int i; + SMB_ASSERT((nt_pw_hist_len % PW_HISTORY_ENTRY_LEN) == 0); + nt_pw_hist_len /= PW_HISTORY_ENTRY_LEN; + for (i = 0; (i < pwHistLen) && (i < nt_pw_hist_len); i++) { + memcpy(&pw_hist[i*PW_HISTORY_ENTRY_LEN], + &nt_pw_hist_ptr[i*PW_HISTORY_ENTRY_LEN], + PW_HISTORY_ENTRY_LEN); + } + } + if (!pdb_set_pw_history(sampass, pw_hist, pwHistLen, PDB_SET)) { + SAFE_FREE(pw_hist); + ret = False; + goto done; + } + SAFE_FREE(pw_hist); + } else { + pdb_set_pw_history(sampass, NULL, 0, PDB_SET); + } + + pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET); + pdb_set_group_sid_from_rid(sampass, group_rid, PDB_SET); + pdb_set_hours_len(sampass, hours_len, PDB_SET); + pdb_set_bad_password_count(sampass, bad_password_count, PDB_SET); + pdb_set_logon_count(sampass, logon_count, PDB_SET); + pdb_set_unknown_6(sampass, unknown_6, PDB_SET); + pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET); + pdb_set_logon_divs(sampass, logon_divs, PDB_SET); + pdb_set_hours(sampass, hours, hours_len, PDB_SET); + +done: + + SAFE_FREE(username); + SAFE_FREE(domain); + SAFE_FREE(nt_username); + SAFE_FREE(fullname); + SAFE_FREE(homedir); + SAFE_FREE(dir_drive); + SAFE_FREE(logon_script); + SAFE_FREE(profile_path); + SAFE_FREE(acct_desc); + SAFE_FREE(workstations); + SAFE_FREE(munged_dial); + SAFE_FREE(unknown_str); + SAFE_FREE(lm_pw_ptr); + SAFE_FREE(nt_pw_ptr); + SAFE_FREE(nt_pw_hist_ptr); + SAFE_FREE(hours); + + return ret; +} + +/********************************************************************* +*********************************************************************/ + +static bool init_samu_from_buffer_v3(struct samu *sampass, uint8_t *buf, uint32_t buflen) +{ + + /* times are stored as 32bit integer + take care on system with 64bit wide time_t + --SSS */ + uint32_t logon_time, + logoff_time, + kickoff_time, + bad_password_time, + pass_last_set_time, + pass_can_change_time, + pass_must_change_time; + char *username = NULL; + char *domain = NULL; + char *nt_username = NULL; + char *dir_drive = NULL; + char *comment = NULL; + char *munged_dial = NULL; + char *fullname = NULL; + char *homedir = NULL; + char *logon_script = NULL; + char *profile_path = NULL; + char *acct_desc = NULL; + char *workstations = NULL; + uint32_t username_len, domain_len, nt_username_len, + dir_drive_len, comment_len, munged_dial_len, + fullname_len, homedir_len, logon_script_len, + profile_path_len, acct_desc_len, workstations_len; + + uint32_t user_rid, group_rid, hours_len, unknown_6, acct_ctrl; + uint16_t logon_divs; + uint16_t bad_password_count, logon_count; + uint8_t *hours = NULL; + uint8_t *lm_pw_ptr = NULL, *nt_pw_ptr = NULL, *nt_pw_hist_ptr = NULL; + uint32_t len = 0; + uint32_t lm_pw_len, nt_pw_len, nt_pw_hist_len, hourslen; + uint32_t pwHistLen = 0; + bool ret = True; + fstring tmp_string; + bool expand_explicit = lp_passdb_expand_explicit(); + + if(sampass == NULL || buf == NULL) { + DEBUG(0, ("init_samu_from_buffer_v3: NULL parameters found!\n")); + return False; + } + +/* SAMU_BUFFER_FORMAT_V3 "dddddddBBBBBBBBBBBBddBBBdwdBwwd" */ + + /* unpack the buffer into variables */ + len = tdb_unpack (buf, buflen, SAMU_BUFFER_FORMAT_V3, + &logon_time, /* d */ + &logoff_time, /* d */ + &kickoff_time, /* d */ + &bad_password_time, /* d */ + &pass_last_set_time, /* d */ + &pass_can_change_time, /* d */ + &pass_must_change_time, /* d */ + &username_len, &username, /* B */ + &domain_len, &domain, /* B */ + &nt_username_len, &nt_username, /* B */ + &fullname_len, &fullname, /* B */ + &homedir_len, &homedir, /* B */ + &dir_drive_len, &dir_drive, /* B */ + &logon_script_len, &logon_script, /* B */ + &profile_path_len, &profile_path, /* B */ + &acct_desc_len, &acct_desc, /* B */ + &workstations_len, &workstations, /* B */ + &comment_len, &comment, /* B */ + &munged_dial_len, &munged_dial, /* B */ + &user_rid, /* d */ + &group_rid, /* d */ + &lm_pw_len, &lm_pw_ptr, /* B */ + &nt_pw_len, &nt_pw_ptr, /* B */ + /* Change from V1 is addition of password history field. */ + &nt_pw_hist_len, &nt_pw_hist_ptr, /* B */ + /* Change from V2 is the uint32_t acb_mask */ + &acct_ctrl, /* d */ + /* Also "remove_me" field was removed. */ + &logon_divs, /* w */ + &hours_len, /* d */ + &hourslen, &hours, /* B */ + &bad_password_count, /* w */ + &logon_count, /* w */ + &unknown_6); /* d */ + + if (len == (uint32_t) -1) { + ret = False; + goto done; + } + + pdb_set_logon_time(sampass, convert_uint32_t_to_time_t(logon_time), PDB_SET); + pdb_set_logoff_time(sampass, convert_uint32_t_to_time_t(logoff_time), PDB_SET); + pdb_set_kickoff_time(sampass, convert_uint32_t_to_time_t(kickoff_time), PDB_SET); + pdb_set_bad_password_time(sampass, convert_uint32_t_to_time_t(bad_password_time), PDB_SET); + pdb_set_pass_can_change_time(sampass, convert_uint32_t_to_time_t(pass_can_change_time), PDB_SET); + pdb_set_pass_last_set_time(sampass, convert_uint32_t_to_time_t(pass_last_set_time), PDB_SET); + + pdb_set_username(sampass, username, PDB_SET); + pdb_set_domain(sampass, domain, PDB_SET); + pdb_set_nt_username(sampass, nt_username, PDB_SET); + pdb_set_fullname(sampass, fullname, PDB_SET); + + if (homedir) { + fstrcpy( tmp_string, homedir ); + if (expand_explicit) { + standard_sub_basic( username, domain, tmp_string, + sizeof(tmp_string) ); + } + pdb_set_homedir(sampass, tmp_string, PDB_SET); + } + else { + pdb_set_homedir(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_home()), + PDB_DEFAULT); + } + + if (dir_drive) + pdb_set_dir_drive(sampass, dir_drive, PDB_SET); + else + pdb_set_dir_drive(sampass, lp_logon_drive(), PDB_DEFAULT ); + + if (logon_script) { + fstrcpy( tmp_string, logon_script ); + if (expand_explicit) { + standard_sub_basic( username, domain, tmp_string, + sizeof(tmp_string) ); + } + pdb_set_logon_script(sampass, tmp_string, PDB_SET); + } + else { + pdb_set_logon_script(sampass, + talloc_sub_basic(sampass, username, domain, + lp_logon_script()), + PDB_DEFAULT); + } + + if (profile_path) { + fstrcpy( tmp_string, profile_path ); + if (expand_explicit) { + standard_sub_basic( username, domain, tmp_string, + sizeof(tmp_string) ); + } + pdb_set_profile_path(sampass, tmp_string, PDB_SET); + } + else { + pdb_set_profile_path(sampass, + talloc_sub_basic(sampass, username, domain, lp_logon_path()), + PDB_DEFAULT); + } + + pdb_set_acct_desc(sampass, acct_desc, PDB_SET); + pdb_set_comment(sampass, comment, PDB_SET); + pdb_set_workstations(sampass, workstations, PDB_SET); + pdb_set_munged_dial(sampass, munged_dial, PDB_SET); + + if (lm_pw_ptr && lm_pw_len == LM_HASH_LEN) { + if (!pdb_set_lanman_passwd(sampass, lm_pw_ptr, PDB_SET)) { + ret = False; + goto done; + } + } + + if (nt_pw_ptr && nt_pw_len == NT_HASH_LEN) { + if (!pdb_set_nt_passwd(sampass, nt_pw_ptr, PDB_SET)) { + ret = False; + goto done; + } + } + + pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHistLen); + if (pwHistLen) { + uint8_t *pw_hist = (uint8_t *)SMB_MALLOC(pwHistLen * PW_HISTORY_ENTRY_LEN); + if (!pw_hist) { + ret = False; + goto done; + } + memset(pw_hist, '\0', pwHistLen * PW_HISTORY_ENTRY_LEN); + if (nt_pw_hist_ptr && nt_pw_hist_len) { + int i; + SMB_ASSERT((nt_pw_hist_len % PW_HISTORY_ENTRY_LEN) == 0); + nt_pw_hist_len /= PW_HISTORY_ENTRY_LEN; + for (i = 0; (i < pwHistLen) && (i < nt_pw_hist_len); i++) { + memcpy(&pw_hist[i*PW_HISTORY_ENTRY_LEN], + &nt_pw_hist_ptr[i*PW_HISTORY_ENTRY_LEN], + PW_HISTORY_ENTRY_LEN); + } + } + if (!pdb_set_pw_history(sampass, pw_hist, pwHistLen, PDB_SET)) { + SAFE_FREE(pw_hist); + ret = False; + goto done; + } + SAFE_FREE(pw_hist); + } else { + pdb_set_pw_history(sampass, NULL, 0, PDB_SET); + } + + pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET); + pdb_set_hours_len(sampass, hours_len, PDB_SET); + pdb_set_bad_password_count(sampass, bad_password_count, PDB_SET); + pdb_set_logon_count(sampass, logon_count, PDB_SET); + pdb_set_unknown_6(sampass, unknown_6, PDB_SET); + /* Change from V2 is the uint32_t acct_ctrl */ + pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET); + pdb_set_logon_divs(sampass, logon_divs, PDB_SET); + pdb_set_hours(sampass, hours, hours_len, PDB_SET); + +done: + + SAFE_FREE(username); + SAFE_FREE(domain); + SAFE_FREE(nt_username); + SAFE_FREE(fullname); + SAFE_FREE(homedir); + SAFE_FREE(dir_drive); + SAFE_FREE(logon_script); + SAFE_FREE(profile_path); + SAFE_FREE(acct_desc); + SAFE_FREE(workstations); + SAFE_FREE(munged_dial); + SAFE_FREE(comment); + SAFE_FREE(lm_pw_ptr); + SAFE_FREE(nt_pw_ptr); + SAFE_FREE(nt_pw_hist_ptr); + SAFE_FREE(hours); + + return ret; +} + +/********************************************************************* +*********************************************************************/ + +static uint32_t init_buffer_from_samu_v3 (uint8_t **buf, struct samu *sampass, bool size_only) +{ + size_t len, buflen; + + /* times are stored as 32bit integer + take care on system with 64bit wide time_t + --SSS */ + uint32_t logon_time, + logoff_time, + kickoff_time, + bad_password_time, + pass_last_set_time, + pass_can_change_time, + pass_must_change_time; + + uint32_t user_rid, group_rid; + + const char *username; + const char *domain; + const char *nt_username; + const char *dir_drive; + const char *comment; + const char *munged_dial; + const char *fullname; + const char *homedir; + const char *logon_script; + const char *profile_path; + const char *acct_desc; + const char *workstations; + uint32_t username_len, domain_len, nt_username_len, + dir_drive_len, comment_len, munged_dial_len, + fullname_len, homedir_len, logon_script_len, + profile_path_len, acct_desc_len, workstations_len; + + const uint8_t *lm_pw; + const uint8_t *nt_pw; + const uint8_t *nt_pw_hist; + uint32_t lm_pw_len = 16; + uint32_t nt_pw_len = 16; + uint32_t nt_pw_hist_len; + uint32_t pwHistLen = 0; + + *buf = NULL; + buflen = 0; + + logon_time = convert_time_t_to_uint32_t(pdb_get_logon_time(sampass)); + logoff_time = convert_time_t_to_uint32_t(pdb_get_logoff_time(sampass)); + kickoff_time = convert_time_t_to_uint32_t(pdb_get_kickoff_time(sampass)); + bad_password_time = convert_time_t_to_uint32_t(pdb_get_bad_password_time(sampass)); + pass_can_change_time = convert_time_t_to_uint32_t(pdb_get_pass_can_change_time_noncalc(sampass)); + pass_must_change_time = convert_time_t_to_uint32_t(pdb_get_pass_must_change_time(sampass)); + pass_last_set_time = convert_time_t_to_uint32_t(pdb_get_pass_last_set_time(sampass)); + + user_rid = pdb_get_user_rid(sampass); + group_rid = pdb_get_group_rid(sampass); + + username = pdb_get_username(sampass); + if (username) { + username_len = strlen(username) +1; + } else { + username_len = 0; + } + + domain = pdb_get_domain(sampass); + if (domain) { + domain_len = strlen(domain) +1; + } else { + domain_len = 0; + } + + nt_username = pdb_get_nt_username(sampass); + if (nt_username) { + nt_username_len = strlen(nt_username) +1; + } else { + nt_username_len = 0; + } + + fullname = pdb_get_fullname(sampass); + if (fullname) { + fullname_len = strlen(fullname) +1; + } else { + fullname_len = 0; + } + + /* + * Only updates fields which have been set (not defaults from smb.conf) + */ + + if (!IS_SAM_DEFAULT(sampass, PDB_DRIVE)) { + dir_drive = pdb_get_dir_drive(sampass); + } else { + dir_drive = NULL; + } + if (dir_drive) { + dir_drive_len = strlen(dir_drive) +1; + } else { + dir_drive_len = 0; + } + + if (!IS_SAM_DEFAULT(sampass, PDB_SMBHOME)) { + homedir = pdb_get_homedir(sampass); + } else { + homedir = NULL; + } + if (homedir) { + homedir_len = strlen(homedir) +1; + } else { + homedir_len = 0; + } + + if (!IS_SAM_DEFAULT(sampass, PDB_LOGONSCRIPT)) { + logon_script = pdb_get_logon_script(sampass); + } else { + logon_script = NULL; + } + if (logon_script) { + logon_script_len = strlen(logon_script) +1; + } else { + logon_script_len = 0; + } + + if (!IS_SAM_DEFAULT(sampass, PDB_PROFILE)) { + profile_path = pdb_get_profile_path(sampass); + } else { + profile_path = NULL; + } + if (profile_path) { + profile_path_len = strlen(profile_path) +1; + } else { + profile_path_len = 0; + } + + lm_pw = pdb_get_lanman_passwd(sampass); + if (!lm_pw) { + lm_pw_len = 0; + } + + nt_pw = pdb_get_nt_passwd(sampass); + if (!nt_pw) { + nt_pw_len = 0; + } + + pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHistLen); + nt_pw_hist = pdb_get_pw_history(sampass, &nt_pw_hist_len); + if (pwHistLen && nt_pw_hist && nt_pw_hist_len) { + nt_pw_hist_len *= PW_HISTORY_ENTRY_LEN; + } else { + nt_pw_hist_len = 0; + } + + acct_desc = pdb_get_acct_desc(sampass); + if (acct_desc) { + acct_desc_len = strlen(acct_desc) +1; + } else { + acct_desc_len = 0; + } + + workstations = pdb_get_workstations(sampass); + if (workstations) { + workstations_len = strlen(workstations) +1; + } else { + workstations_len = 0; + } + + comment = pdb_get_comment(sampass); + if (comment) { + comment_len = strlen(comment) +1; + } else { + comment_len = 0; + } + + munged_dial = pdb_get_munged_dial(sampass); + if (munged_dial) { + munged_dial_len = strlen(munged_dial) +1; + } else { + munged_dial_len = 0; + } + +/* SAMU_BUFFER_FORMAT_V3 "dddddddBBBBBBBBBBBBddBBBdwdBwwd" */ + + /* one time to get the size needed */ + len = tdb_pack(NULL, 0, SAMU_BUFFER_FORMAT_V3, + logon_time, /* d */ + logoff_time, /* d */ + kickoff_time, /* d */ + bad_password_time, /* d */ + pass_last_set_time, /* d */ + pass_can_change_time, /* d */ + pass_must_change_time, /* d */ + username_len, username, /* B */ + domain_len, domain, /* B */ + nt_username_len, nt_username, /* B */ + fullname_len, fullname, /* B */ + homedir_len, homedir, /* B */ + dir_drive_len, dir_drive, /* B */ + logon_script_len, logon_script, /* B */ + profile_path_len, profile_path, /* B */ + acct_desc_len, acct_desc, /* B */ + workstations_len, workstations, /* B */ + comment_len, comment, /* B */ + munged_dial_len, munged_dial, /* B */ + user_rid, /* d */ + group_rid, /* d */ + lm_pw_len, lm_pw, /* B */ + nt_pw_len, nt_pw, /* B */ + nt_pw_hist_len, nt_pw_hist, /* B */ + pdb_get_acct_ctrl(sampass), /* d */ + pdb_get_logon_divs(sampass), /* w */ + pdb_get_hours_len(sampass), /* d */ + MAX_HOURS_LEN, pdb_get_hours(sampass), /* B */ + pdb_get_bad_password_count(sampass), /* w */ + pdb_get_logon_count(sampass), /* w */ + pdb_get_unknown_6(sampass)); /* d */ + + if (size_only) { + return buflen; + } + + /* malloc the space needed */ + if ( (*buf=(uint8_t*)SMB_MALLOC(len)) == NULL) { + DEBUG(0,("init_buffer_from_samu_v3: Unable to malloc() memory for buffer!\n")); + return (-1); + } + + /* now for the real call to tdb_pack() */ + buflen = tdb_pack(*buf, len, SAMU_BUFFER_FORMAT_V3, + logon_time, /* d */ + logoff_time, /* d */ + kickoff_time, /* d */ + bad_password_time, /* d */ + pass_last_set_time, /* d */ + pass_can_change_time, /* d */ + pass_must_change_time, /* d */ + username_len, username, /* B */ + domain_len, domain, /* B */ + nt_username_len, nt_username, /* B */ + fullname_len, fullname, /* B */ + homedir_len, homedir, /* B */ + dir_drive_len, dir_drive, /* B */ + logon_script_len, logon_script, /* B */ + profile_path_len, profile_path, /* B */ + acct_desc_len, acct_desc, /* B */ + workstations_len, workstations, /* B */ + comment_len, comment, /* B */ + munged_dial_len, munged_dial, /* B */ + user_rid, /* d */ + group_rid, /* d */ + lm_pw_len, lm_pw, /* B */ + nt_pw_len, nt_pw, /* B */ + nt_pw_hist_len, nt_pw_hist, /* B */ + pdb_get_acct_ctrl(sampass), /* d */ + pdb_get_logon_divs(sampass), /* w */ + pdb_get_hours_len(sampass), /* d */ + MAX_HOURS_LEN, pdb_get_hours(sampass), /* B */ + pdb_get_bad_password_count(sampass), /* w */ + pdb_get_logon_count(sampass), /* w */ + pdb_get_unknown_6(sampass)); /* d */ + + /* check to make sure we got it correct */ + if (buflen != len) { + DEBUG(0, ("init_buffer_from_samu_v3: something odd is going on here: bufflen (%lu) != len (%lu) in tdb_pack operations!\n", + (unsigned long)buflen, (unsigned long)len)); + /* error */ + SAFE_FREE (*buf); + return (-1); + } + + return (buflen); +} + +static bool init_samu_from_buffer_v4(struct samu *sampass, uint8_t *buf, uint32_t buflen) +{ + /* nothing changed between V3 and V4 */ + return init_samu_from_buffer_v3(sampass, buf, buflen); +} + +static uint32_t init_buffer_from_samu_v4(uint8_t **buf, struct samu *sampass, bool size_only) +{ + /* nothing changed between V3 and V4 */ + return init_buffer_from_samu_v3(buf, sampass, size_only); +} + +/********************************************************************** + Intialize a struct samu struct from a BYTE buffer of size len + *********************************************************************/ + +bool init_samu_from_buffer(struct samu *sampass, uint32_t level, + uint8_t *buf, uint32_t buflen) +{ + switch (level) { + case SAMU_BUFFER_V0: + return init_samu_from_buffer_v0(sampass, buf, buflen); + case SAMU_BUFFER_V1: + return init_samu_from_buffer_v1(sampass, buf, buflen); + case SAMU_BUFFER_V2: + return init_samu_from_buffer_v2(sampass, buf, buflen); + case SAMU_BUFFER_V3: + return init_samu_from_buffer_v3(sampass, buf, buflen); + case SAMU_BUFFER_V4: + return init_samu_from_buffer_v4(sampass, buf, buflen); + } + + return false; +} + +/********************************************************************** + Intialize a BYTE buffer from a struct samu struct + *********************************************************************/ + +uint32_t init_buffer_from_samu (uint8_t **buf, struct samu *sampass, bool size_only) +{ + return init_buffer_from_samu_v4(buf, sampass, size_only); +} + +/********************************************************************* +*********************************************************************/ + +bool pdb_copy_sam_account(struct samu *dst, struct samu *src ) +{ + uint8_t *buf = NULL; + int len; + + len = init_buffer_from_samu(&buf, src, False); + if (len == -1 || !buf) { + SAFE_FREE(buf); + return False; + } + + if (!init_samu_from_buffer( dst, SAMU_BUFFER_LATEST, buf, len )) { + free(buf); + return False; + } + + dst->methods = src->methods; + + if ( src->unix_pw ) { + dst->unix_pw = tcopy_passwd( dst, src->unix_pw ); + if (!dst->unix_pw) { + free(buf); + return False; + } + } + + if (src->group_sid) { + pdb_set_group_sid(dst, src->group_sid, PDB_SET); + } + + free(buf); + return True; +} + +/********************************************************************* + Update the bad password count checking the PDB_POLICY_RESET_COUNT_TIME +*********************************************************************/ + +bool pdb_update_bad_password_count(struct samu *sampass, bool *updated) +{ + time_t LastBadPassword; + uint16_t BadPasswordCount; + uint32_t resettime; + bool res; + + BadPasswordCount = pdb_get_bad_password_count(sampass); + if (!BadPasswordCount) { + DEBUG(9, ("No bad password attempts.\n")); + return True; + } + + become_root(); + res = pdb_get_account_policy(PDB_POLICY_RESET_COUNT_TIME, &resettime); + unbecome_root(); + + if (!res) { + DEBUG(0, ("pdb_update_bad_password_count: pdb_get_account_policy failed.\n")); + return False; + } + + /* First, check if there is a reset time to compare */ + if ((resettime == (uint32_t) -1) || (resettime == 0)) { + DEBUG(9, ("No reset time, can't reset bad pw count\n")); + return True; + } + + LastBadPassword = pdb_get_bad_password_time(sampass); + DEBUG(7, ("LastBadPassword=%d, resettime=%d, current time=%d.\n", + (uint32_t) LastBadPassword, resettime, (uint32_t)time(NULL))); + if (time(NULL) > (LastBadPassword + convert_uint32_t_to_time_t(resettime)*60)){ + pdb_set_bad_password_count(sampass, 0, PDB_CHANGED); + pdb_set_bad_password_time(sampass, 0, PDB_CHANGED); + if (updated) { + *updated = True; + } + } + + return True; +} + +/********************************************************************* + Update the ACB_AUTOLOCK flag checking the PDB_POLICY_LOCK_ACCOUNT_DURATION +*********************************************************************/ + +bool pdb_update_autolock_flag(struct samu *sampass, bool *updated) +{ + uint32_t duration; + time_t LastBadPassword; + bool res; + + if (!(pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK)) { + DEBUG(9, ("pdb_update_autolock_flag: Account %s not autolocked, no check needed\n", + pdb_get_username(sampass))); + return True; + } + + become_root(); + res = pdb_get_account_policy(PDB_POLICY_LOCK_ACCOUNT_DURATION, &duration); + unbecome_root(); + + if (!res) { + DEBUG(0, ("pdb_update_autolock_flag: pdb_get_account_policy failed.\n")); + return False; + } + + /* First, check if there is a duration to compare */ + if ((duration == (uint32_t) -1) || (duration == 0)) { + DEBUG(9, ("pdb_update_autolock_flag: No reset duration, can't reset autolock\n")); + return True; + } + + LastBadPassword = pdb_get_bad_password_time(sampass); + DEBUG(7, ("pdb_update_autolock_flag: Account %s, LastBadPassword=%d, duration=%d, current time =%d.\n", + pdb_get_username(sampass), (uint32_t)LastBadPassword, duration*60, (uint32_t)time(NULL))); + + if (LastBadPassword == (time_t)0) { + DEBUG(1,("pdb_update_autolock_flag: Account %s " + "administratively locked out with no bad password " + "time. Leaving locked out.\n", + pdb_get_username(sampass) )); + return True; + } + + if ((time(NULL) > (LastBadPassword + convert_uint32_t_to_time_t(duration) * 60))) { + pdb_set_acct_ctrl(sampass, + pdb_get_acct_ctrl(sampass) & ~ACB_AUTOLOCK, + PDB_CHANGED); + pdb_set_bad_password_count(sampass, 0, PDB_CHANGED); + pdb_set_bad_password_time(sampass, 0, PDB_CHANGED); + if (updated) { + *updated = True; + } + } + + return True; +} + +/********************************************************************* + Increment the bad_password_count +*********************************************************************/ + +bool pdb_increment_bad_password_count(struct samu *sampass) +{ + uint32_t account_policy_lockout; + bool autolock_updated = False, badpw_updated = False; + bool ret; + + /* Retrieve the account lockout policy */ + become_root(); + ret = pdb_get_account_policy(PDB_POLICY_BAD_ATTEMPT_LOCKOUT, &account_policy_lockout); + unbecome_root(); + if ( !ret ) { + DEBUG(0, ("pdb_increment_bad_password_count: pdb_get_account_policy failed.\n")); + return False; + } + + /* If there is no policy, we don't need to continue checking */ + if (!account_policy_lockout) { + DEBUG(9, ("No lockout policy, don't track bad passwords\n")); + return True; + } + + /* Check if the autolock needs to be cleared */ + if (!pdb_update_autolock_flag(sampass, &autolock_updated)) + return False; + + /* Check if the badpw count needs to be reset */ + if (!pdb_update_bad_password_count(sampass, &badpw_updated)) + return False; + + /* + Ok, now we can assume that any resetting that needs to be + done has been done, and just get on with incrementing + and autolocking if necessary + */ + + pdb_set_bad_password_count(sampass, + pdb_get_bad_password_count(sampass)+1, + PDB_CHANGED); + pdb_set_bad_password_time(sampass, time(NULL), PDB_CHANGED); + + + if (pdb_get_bad_password_count(sampass) < account_policy_lockout) + return True; + + if (!pdb_set_acct_ctrl(sampass, + pdb_get_acct_ctrl(sampass) | ACB_AUTOLOCK, + PDB_CHANGED)) { + DEBUG(1, ("pdb_increment_bad_password_count:failed to set 'autolock' flag. \n")); + return False; + } + + return True; +} + +bool is_dc_trusted_domain_situation(const char *domain_name) +{ + return IS_DC && !strequal(domain_name, lp_workgroup()); +} + +/******************************************************************* + Wrapper around retrieving the clear text trust account password. + appropriate account name is stored in account_name. + Caller must free password, but not account_name. +*******************************************************************/ + +static bool get_trust_pw_clear2(const char *domain, + const char **account_name, + enum netr_SchannelType *channel, + char **cur_pw, + time_t *_last_set_time, + char **prev_pw) +{ + char *pwd; + time_t last_set_time; + + if (cur_pw != NULL) { + *cur_pw = NULL; + } + if (_last_set_time != NULL) { + *_last_set_time = 0; + } + if (prev_pw != NULL) { + *prev_pw = NULL; + } + + /* if we are a DC and this is not our domain, then lookup an account + * for the domain trust */ + + if (is_dc_trusted_domain_situation(domain)) { + if (!lp_allow_trusted_domains()) { + return false; + } + + if (!pdb_get_trusteddom_pw(domain, cur_pw, NULL, + &last_set_time)) + { + DEBUG(0, ("get_trust_pw: could not fetch trust " + "account password for trusted domain %s\n", + domain)); + return false; + } + + if (channel != NULL) { + *channel = SEC_CHAN_DOMAIN; + } + + if (account_name != NULL) { + *account_name = lp_workgroup(); + } + + if (_last_set_time != NULL) { + *_last_set_time = last_set_time; + } + + return true; + } + + /* + * Since we can only be member of one single domain, we are now + * in a member situation: + * + * - Either we are a DC (selfjoined) and the domain is our + * own domain. + * - Or we are on a member and the domain is our own or some + * other (potentially trusted) domain. + * + * In both cases, we can only get the machine account password + * for our own domain to connect to our own dc. (For a member, + * request to trusted domains are performed through our dc.) + * + * So we simply use our own domain name to retrieve the + * machine account passowrd and ignore the request domain here. + */ + + pwd = secrets_fetch_machine_password(lp_workgroup(), &last_set_time, channel); + + if (pwd != NULL) { + struct timeval expire; + + *cur_pw = pwd; + + if (account_name != NULL) { + *account_name = lp_netbios_name(); + } + + if (_last_set_time != NULL) { + *_last_set_time = last_set_time; + } + + if (prev_pw == NULL) { + return true; + } + + ZERO_STRUCT(expire); + expire.tv_sec = lp_machine_password_timeout(); + expire.tv_sec /= 2; + expire.tv_sec += last_set_time; + if (timeval_expired(&expire)) { + return true; + } + + pwd = secrets_fetch_prev_machine_password(lp_workgroup()); + if (pwd != NULL) { + *prev_pw = pwd; + } + + return true; + } + + DEBUG(5, ("get_trust_pw_clear2: could not fetch clear text trust " + "account password for domain %s\n", domain)); + return false; +} + +bool get_trust_pw_clear(const char *domain, char **ret_pwd, + const char **account_name, + enum netr_SchannelType *channel) +{ + return get_trust_pw_clear2(domain, + account_name, + channel, + ret_pwd, + NULL, + NULL); +} + +/******************************************************************* + Wrapper around retrieving the trust account password. + appropriate account name is stored in account_name. +*******************************************************************/ + +static bool get_trust_pw_hash2(const char *domain, + const char **account_name, + enum netr_SchannelType *channel, + struct samr_Password *current_nt_hash, + time_t *last_set_time, + struct samr_Password **_previous_nt_hash) +{ + char *cur_pw = NULL; + char *prev_pw = NULL; + char **_prev_pw = NULL; + bool ok; + + if (_previous_nt_hash != NULL) { + *_previous_nt_hash = NULL; + _prev_pw = &prev_pw; + } + + ok = get_trust_pw_clear2(domain, account_name, channel, + &cur_pw, last_set_time, _prev_pw); + if (ok) { + struct samr_Password *previous_nt_hash = NULL; + + E_md4hash(cur_pw, current_nt_hash->hash); + SAFE_FREE(cur_pw); + + if (prev_pw == NULL) { + return true; + } + + previous_nt_hash = SMB_MALLOC_P(struct samr_Password); + if (previous_nt_hash == NULL) { + return false; + } + + E_md4hash(prev_pw, previous_nt_hash->hash); + SAFE_FREE(prev_pw); + + *_previous_nt_hash = previous_nt_hash; + return true; + } else if (is_dc_trusted_domain_situation(domain)) { + return false; + } + + /* as a fallback, try to get the hashed pwd directly from the tdb... */ + + if (secrets_fetch_trust_account_password_legacy(domain, + current_nt_hash->hash, + last_set_time, + channel)) + { + if (account_name != NULL) { + *account_name = lp_netbios_name(); + } + + return true; + } + + DEBUG(5, ("get_trust_pw_hash: could not fetch trust account " + "password for domain %s\n", domain)); + return False; +} + +bool get_trust_pw_hash(const char *domain, uint8_t ret_pwd[16], + const char **account_name, + enum netr_SchannelType *channel) +{ + struct samr_Password current_nt_hash; + bool ok; + + ok = get_trust_pw_hash2(domain, account_name, channel, + ¤t_nt_hash, NULL, NULL); + if (!ok) { + return false; + } + + memcpy(ret_pwd, current_nt_hash.hash, sizeof(current_nt_hash.hash)); + return true; +} + +NTSTATUS pdb_get_trust_credentials(const char *netbios_domain, + const char *dns_domain, /* optional */ + TALLOC_CTX *mem_ctx, + struct cli_credentials **_creds) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + struct loadparm_context *lp_ctx; + enum netr_SchannelType channel; + time_t last_set_time; + const char *_account_name; + const char *account_name; + char *cur_pw = NULL; + char *prev_pw = NULL; + struct samr_Password cur_nt_hash; + struct cli_credentials *creds = NULL; + bool ok; + + /* + * If this is our primary trust relationship, use the common + * code to read the secrets.ldb or secrets.tdb file. + */ + if (strequal(netbios_domain, lp_workgroup())) { + struct db_context *db_ctx = secrets_db_ctx(); + if (db_ctx == NULL) { + DEBUG(1, ("failed to open secrets.tdb to obtain our trust credentials for %s\n", + netbios_domain)); + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + + lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers()); + if (lp_ctx == NULL) { + DEBUG(1, ("loadparm_init_s3 failed\n")); + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + + creds = cli_credentials_init(mem_ctx); + if (creds == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + ok = cli_credentials_set_conf(creds, lp_ctx); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + + ok = cli_credentials_set_domain(creds, netbios_domain, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + status = cli_credentials_set_machine_account_db_ctx(creds, + lp_ctx, + db_ctx); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + goto done; + } else if (!IS_DC) { + DEBUG(1, ("Refusing to get trust account info for %s, " + "which is not our primary domain %s, " + "as we are not a DC\n", + netbios_domain, lp_workgroup())); + status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + goto fail; + } + + status = pdb_get_trusteddom_creds(netbios_domain, mem_ctx, &creds); + if (NT_STATUS_IS_OK(status)) { + goto done; + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) { + goto fail; + } + + ok = get_trust_pw_clear2(netbios_domain, + &_account_name, + &channel, + &cur_pw, + &last_set_time, + &prev_pw); + if (!ok) { + ok = get_trust_pw_hash2(netbios_domain, + &_account_name, + &channel, + &cur_nt_hash, + &last_set_time, + NULL); + if (!ok) { + DEBUG(1, ("get_trust_pw_*2 failed for domain[%s]\n", + netbios_domain)); + status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + goto fail; + } + } + + account_name = talloc_asprintf(frame, "%s$", _account_name); + if (account_name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers()); + if (lp_ctx == NULL) { + DEBUG(1, ("loadparm_init_s3 failed\n")); + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + + creds = cli_credentials_init(mem_ctx); + if (creds == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + ok = cli_credentials_set_conf(creds, lp_ctx); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + + cli_credentials_set_secure_channel_type(creds, channel); + cli_credentials_set_password_last_changed_time(creds, last_set_time); + + ok = cli_credentials_set_domain(creds, netbios_domain, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (dns_domain != NULL) { + ok = cli_credentials_set_realm(creds, dns_domain, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + /* + * It's not possible to use NTLMSSP with a domain trust account. + */ + cli_credentials_set_kerberos_state(creds, + CRED_USE_KERBEROS_REQUIRED, + CRED_SPECIFIED); + } else { + /* + * We can't use kerberos against an NT4 domain. + * + * We should have a mode that also disallows NTLMSSP here, + * as only NETLOGON SCHANNEL is possible. + */ + cli_credentials_set_kerberos_state(creds, + CRED_USE_KERBEROS_DISABLED, + CRED_SPECIFIED); + } + + ok = cli_credentials_set_username(creds, account_name, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (cur_pw == NULL) { + ok = cli_credentials_set_nt_hash(creds, &cur_nt_hash, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + /* + * We currently can't do kerberos just with an NTHASH. + */ + cli_credentials_set_kerberos_state(creds, + CRED_USE_KERBEROS_DISABLED, + CRED_SPECIFIED); + goto done; + } + + ok = cli_credentials_set_password(creds, cur_pw, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (prev_pw != NULL) { + ok = cli_credentials_set_old_password(creds, prev_pw, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + } + + done: + *_creds = creds; + creds = NULL; + status = NT_STATUS_OK; + fail: + TALLOC_FREE(creds); + SAFE_FREE(cur_pw); + SAFE_FREE(prev_pw); + TALLOC_FREE(frame); + return status; +} |