diff options
Diffstat (limited to 'source3/passdb')
-rw-r--r-- | source3/passdb/login_cache.c | 188 | ||||
-rw-r--r-- | source3/passdb/lookup_sid.c | 1501 | ||||
-rw-r--r-- | source3/passdb/machine_sid.c | 248 | ||||
-rw-r--r-- | source3/passdb/passdb.c | 1657 | ||||
-rw-r--r-- | source3/passdb/pdb_compat.c | 103 | ||||
-rw-r--r-- | source3/passdb/pdb_get_set.c | 1081 | ||||
-rw-r--r-- | source3/passdb/pdb_interface.c | 2071 | ||||
-rw-r--r-- | source3/passdb/pdb_ldap.c | 6389 | ||||
-rw-r--r-- | source3/passdb/pdb_nds.c | 913 | ||||
-rw-r--r-- | source3/passdb/pdb_smbpasswd.c | 1718 | ||||
-rw-r--r-- | source3/passdb/pdb_tdb.c | 1639 | ||||
-rw-r--r-- | source3/passdb/secrets.c | 1366 | ||||
-rw-r--r-- | source3/passdb/util_builtin.c | 109 | ||||
-rw-r--r-- | source3/passdb/util_unixsids.c | 105 | ||||
-rw-r--r-- | source3/passdb/util_wellknown.c | 172 |
15 files changed, 19260 insertions, 0 deletions
diff --git a/source3/passdb/login_cache.c b/source3/passdb/login_cache.c new file mode 100644 index 0000000000..8222f77b95 --- /dev/null +++ b/source3/passdb/login_cache.c @@ -0,0 +1,188 @@ +/* + Unix SMB/CIFS implementation. + struct samu local cache for + Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2004. + + 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +#define LOGIN_CACHE_FILE "login_cache.tdb" + +#define SAM_CACHE_FORMAT "dwwd" + +static TDB_CONTEXT *cache; + +bool login_cache_init(void) +{ + char* cache_fname = NULL; + + /* skip file open if it's already opened */ + if (cache) return True; + + asprintf(&cache_fname, "%s/%s", lp_lockdir(), LOGIN_CACHE_FILE); + if (cache_fname) + DEBUG(5, ("Opening cache file at %s\n", cache_fname)); + else { + DEBUG(0, ("Filename allocation failed.\n")); + return False; + } + + cache = tdb_open_log(cache_fname, 0, TDB_DEFAULT, + O_RDWR|O_CREAT, 0644); + + if (!cache) + DEBUG(5, ("Attempt to open %s failed.\n", cache_fname)); + + SAFE_FREE(cache_fname); + + return (cache ? True : False); +} + +bool login_cache_shutdown(void) +{ + /* tdb_close routine returns -1 on error */ + if (!cache) return False; + DEBUG(5, ("Closing cache file\n")); + return tdb_close(cache) != -1; +} + +/* if we can't read the cache, oh well, no need to return anything */ +LOGIN_CACHE * login_cache_read(struct samu *sampass) +{ + char *keystr; + TDB_DATA databuf; + LOGIN_CACHE *entry; + + if (!login_cache_init()) + return NULL; + + if (pdb_get_nt_username(sampass) == NULL) { + return NULL; + } + + keystr = SMB_STRDUP(pdb_get_nt_username(sampass)); + if (!keystr || !keystr[0]) { + SAFE_FREE(keystr); + return NULL; + } + + DEBUG(7, ("Looking up login cache for user %s\n", + keystr)); + databuf = tdb_fetch_bystring(cache, keystr); + SAFE_FREE(keystr); + + if (!(entry = SMB_MALLOC_P(LOGIN_CACHE))) { + DEBUG(1, ("Unable to allocate cache entry buffer!\n")); + SAFE_FREE(databuf.dptr); + return NULL; + } + + if (tdb_unpack (databuf.dptr, databuf.dsize, SAM_CACHE_FORMAT, + &entry->entry_timestamp, &entry->acct_ctrl, + &entry->bad_password_count, + &entry->bad_password_time) == -1) { + DEBUG(7, ("No cache entry found\n")); + SAFE_FREE(entry); + SAFE_FREE(databuf.dptr); + return NULL; + } + + SAFE_FREE(databuf.dptr); + + DEBUG(5, ("Found login cache entry: timestamp %12u, flags 0x%x, count %d, time %12u\n", + (unsigned int)entry->entry_timestamp, entry->acct_ctrl, + entry->bad_password_count, (unsigned int)entry->bad_password_time)); + return entry; +} + +bool login_cache_write(const struct samu *sampass, LOGIN_CACHE entry) +{ + char *keystr; + TDB_DATA databuf; + bool ret; + + if (!login_cache_init()) + return False; + + if (pdb_get_nt_username(sampass) == NULL) { + return False; + } + + keystr = SMB_STRDUP(pdb_get_nt_username(sampass)); + if (!keystr || !keystr[0]) { + SAFE_FREE(keystr); + return False; + } + + entry.entry_timestamp = time(NULL); + + databuf.dsize = + tdb_pack(NULL, 0, SAM_CACHE_FORMAT, + entry.entry_timestamp, + entry.acct_ctrl, + entry.bad_password_count, + entry.bad_password_time); + databuf.dptr = SMB_MALLOC_ARRAY(uint8, databuf.dsize); + if (!databuf.dptr) { + SAFE_FREE(keystr); + return False; + } + + if (tdb_pack(databuf.dptr, databuf.dsize, SAM_CACHE_FORMAT, + entry.entry_timestamp, + entry.acct_ctrl, + entry.bad_password_count, + entry.bad_password_time) + != databuf.dsize) { + SAFE_FREE(keystr); + SAFE_FREE(databuf.dptr); + return False; + } + + ret = tdb_store_bystring(cache, keystr, databuf, 0); + SAFE_FREE(keystr); + SAFE_FREE(databuf.dptr); + return ret == 0; +} + +bool login_cache_delentry(const struct samu *sampass) +{ + int ret; + char *keystr; + + if (!login_cache_init()) + return False; + + if (pdb_get_nt_username(sampass) == NULL) { + return False; + } + + keystr = SMB_STRDUP(pdb_get_nt_username(sampass)); + if (!keystr || !keystr[0]) { + SAFE_FREE(keystr); + return False; + } + + DEBUG(9, ("About to delete entry for %s\n", keystr)); + ret = tdb_delete_bystring(cache, keystr); + DEBUG(9, ("tdb_delete returned %d\n", ret)); + + SAFE_FREE(keystr); + return ret == 0; +} diff --git a/source3/passdb/lookup_sid.c b/source3/passdb/lookup_sid.c new file mode 100644 index 0000000000..3861c8e229 --- /dev/null +++ b/source3/passdb/lookup_sid.c @@ -0,0 +1,1501 @@ +/* + Unix SMB/CIFS implementation. + uid/user handling + Copyright (C) Andrew Tridgell 1992-1998 + Copyright (C) Gerald (Jerry) Carter 2003 + Copyright (C) Volker Lendecke 2005 + + 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" + +/***************************************************************** + Dissect a user-provided name into domain, name, sid and type. + + If an explicit domain name was given in the form domain\user, it + has to try that. If no explicit domain name was given, we have + to do guesswork. +*****************************************************************/ + +bool lookup_name(TALLOC_CTX *mem_ctx, + const char *full_name, int flags, + const char **ret_domain, const char **ret_name, + DOM_SID *ret_sid, enum lsa_SidType *ret_type) +{ + char *p; + const char *tmp; + const char *domain = NULL; + const char *name = NULL; + uint32 rid; + DOM_SID sid; + enum lsa_SidType type; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + if (tmp_ctx == NULL) { + DEBUG(0, ("talloc_new failed\n")); + return false; + } + + p = strchr_m(full_name, '\\'); + + if (p != NULL) { + domain = talloc_strndup(tmp_ctx, full_name, + PTR_DIFF(p, full_name)); + name = talloc_strdup(tmp_ctx, p+1); + } else { + domain = talloc_strdup(tmp_ctx, ""); + name = talloc_strdup(tmp_ctx, full_name); + } + + if ((domain == NULL) || (name == NULL)) { + DEBUG(0, ("talloc failed\n")); + TALLOC_FREE(tmp_ctx); + return false; + } + + DEBUG(10,("lookup_name: %s => %s (domain), %s (name)\n", + full_name, domain, name)); + DEBUG(10, ("lookup_name: flags = 0x0%x\n", flags)); + + if ((flags & LOOKUP_NAME_DOMAIN) && + strequal(domain, get_global_sam_name())) + { + + /* It's our own domain, lookup the name in passdb */ + if (lookup_global_sam_name(name, flags, &rid, &type)) { + sid_copy(&sid, get_global_sam_sid()); + sid_append_rid(&sid, rid); + goto ok; + } + TALLOC_FREE(tmp_ctx); + return false; + } + + if ((flags & LOOKUP_NAME_BUILTIN) && + strequal(domain, builtin_domain_name())) + { + /* Explicit request for a name in BUILTIN */ + if (lookup_builtin_name(name, &rid)) { + sid_copy(&sid, &global_sid_Builtin); + sid_append_rid(&sid, rid); + type = SID_NAME_ALIAS; + goto ok; + } + TALLOC_FREE(tmp_ctx); + return false; + } + + /* Try the explicit winbind lookup first, don't let it guess the + * domain yet at this point yet. This comes later. */ + + if ((domain[0] != '\0') && + (flags & ~(LOOKUP_NAME_DOMAIN|LOOKUP_NAME_ISOLATED)) && + (winbind_lookup_name(domain, name, &sid, &type))) { + goto ok; + } + + if (!(flags & LOOKUP_NAME_EXPLICIT) && strequal(domain, unix_users_domain_name())) { + if (lookup_unix_user_name(name, &sid)) { + type = SID_NAME_USER; + goto ok; + } + TALLOC_FREE(tmp_ctx); + return false; + } + + if (!(flags & LOOKUP_NAME_EXPLICIT) && strequal(domain, unix_groups_domain_name())) { + if (lookup_unix_group_name(name, &sid)) { + type = SID_NAME_DOM_GRP; + goto ok; + } + TALLOC_FREE(tmp_ctx); + return false; + } + + if ((domain[0] == '\0') && (!(flags & LOOKUP_NAME_ISOLATED))) { + TALLOC_FREE(tmp_ctx); + return false; + } + + /* Now the guesswork begins, we haven't been given an explicit + * domain. Try the sequence as documented on + * http://msdn.microsoft.com/library/en-us/secmgmt/security/lsalookupnames.asp + * November 27, 2005 */ + + /* 1. well-known names */ + + if ((flags & LOOKUP_NAME_WKN) && + lookup_wellknown_name(tmp_ctx, name, &sid, &domain)) + { + type = SID_NAME_WKN_GRP; + goto ok; + } + + /* 2. Builtin domain as such */ + + if ((flags & (LOOKUP_NAME_BUILTIN|LOOKUP_NAME_REMOTE)) && + strequal(name, builtin_domain_name())) + { + /* Swap domain and name */ + tmp = name; name = domain; domain = tmp; + sid_copy(&sid, &global_sid_Builtin); + type = SID_NAME_DOMAIN; + goto ok; + } + + /* 3. Account domain */ + + if ((flags & LOOKUP_NAME_DOMAIN) && + strequal(name, get_global_sam_name())) + { + if (!secrets_fetch_domain_sid(name, &sid)) { + DEBUG(3, ("Could not fetch my SID\n")); + TALLOC_FREE(tmp_ctx); + return false; + } + /* Swap domain and name */ + tmp = name; name = domain; domain = tmp; + type = SID_NAME_DOMAIN; + goto ok; + } + + /* 4. Primary domain */ + + if ((flags & LOOKUP_NAME_DOMAIN) && !IS_DC && + strequal(name, lp_workgroup())) + { + if (!secrets_fetch_domain_sid(name, &sid)) { + DEBUG(3, ("Could not fetch the domain SID\n")); + TALLOC_FREE(tmp_ctx); + return false; + } + /* Swap domain and name */ + tmp = name; name = domain; domain = tmp; + type = SID_NAME_DOMAIN; + goto ok; + } + + /* 5. Trusted domains as such, to me it looks as if members don't do + this, tested an XP workstation in a NT domain -- vl */ + + if ((flags & LOOKUP_NAME_REMOTE) && IS_DC && + (pdb_get_trusteddom_pw(name, NULL, &sid, NULL))) + { + /* Swap domain and name */ + tmp = name; name = domain; domain = tmp; + type = SID_NAME_DOMAIN; + goto ok; + } + + /* 6. Builtin aliases */ + + if ((flags & LOOKUP_NAME_BUILTIN) && + lookup_builtin_name(name, &rid)) + { + domain = talloc_strdup(tmp_ctx, builtin_domain_name()); + sid_copy(&sid, &global_sid_Builtin); + sid_append_rid(&sid, rid); + type = SID_NAME_ALIAS; + goto ok; + } + + /* 7. Local systems' SAM (DCs don't have a local SAM) */ + /* 8. Primary SAM (On members, this is the domain) */ + + /* Both cases are done by looking at our passdb */ + + if ((flags & LOOKUP_NAME_DOMAIN) && + lookup_global_sam_name(name, flags, &rid, &type)) + { + domain = talloc_strdup(tmp_ctx, get_global_sam_name()); + sid_copy(&sid, get_global_sam_sid()); + sid_append_rid(&sid, rid); + goto ok; + } + + /* Now our local possibilities are exhausted. */ + + if (!(flags & LOOKUP_NAME_REMOTE)) { + TALLOC_FREE(tmp_ctx); + return false; + } + + /* If we are not a DC, we have to ask in our primary domain. Let + * winbind do that. */ + + if (!IS_DC && + (winbind_lookup_name(lp_workgroup(), name, &sid, &type))) { + domain = talloc_strdup(tmp_ctx, lp_workgroup()); + goto ok; + } + + /* 9. Trusted domains */ + + /* If we're a DC we have to ask all trusted DC's. Winbind does not do + * that (yet), but give it a chance. */ + + if (IS_DC && winbind_lookup_name("", name, &sid, &type)) { + DOM_SID dom_sid; + uint32 tmp_rid; + enum lsa_SidType domain_type; + + if (type == SID_NAME_DOMAIN) { + /* Swap name and type */ + tmp = name; name = domain; domain = tmp; + goto ok; + } + + /* Here we have to cope with a little deficiency in the + * winbind API: We have to ask it again for the name of the + * domain it figured out itself. Maybe fix that later... */ + + sid_copy(&dom_sid, &sid); + sid_split_rid(&dom_sid, &tmp_rid); + + if (!winbind_lookup_sid(tmp_ctx, &dom_sid, &domain, NULL, + &domain_type) || + (domain_type != SID_NAME_DOMAIN)) { + DEBUG(2, ("winbind could not find the domain's name " + "it just looked up for us\n")); + TALLOC_FREE(tmp_ctx); + return false; + } + goto ok; + } + + /* 10. Don't translate */ + + /* 11. Ok, windows would end here. Samba has two more options: + Unmapped users and unmapped groups */ + + if (!(flags & LOOKUP_NAME_EXPLICIT) && lookup_unix_user_name(name, &sid)) { + domain = talloc_strdup(tmp_ctx, unix_users_domain_name()); + type = SID_NAME_USER; + goto ok; + } + + if (!(flags & LOOKUP_NAME_EXPLICIT) && lookup_unix_group_name(name, &sid)) { + domain = talloc_strdup(tmp_ctx, unix_groups_domain_name()); + type = SID_NAME_DOM_GRP; + goto ok; + } + + /* + * Ok, all possibilities tried. Fail. + */ + + TALLOC_FREE(tmp_ctx); + return false; + + ok: + if ((domain == NULL) || (name == NULL)) { + DEBUG(0, ("talloc failed\n")); + TALLOC_FREE(tmp_ctx); + return false; + } + + /* + * Hand over the results to the talloc context we've been given. + */ + + if ((ret_name != NULL) && + !(*ret_name = talloc_strdup(mem_ctx, name))) { + DEBUG(0, ("talloc failed\n")); + TALLOC_FREE(tmp_ctx); + return false; + } + + if (ret_domain != NULL) { + char *tmp_dom; + if (!(tmp_dom = talloc_strdup(mem_ctx, domain))) { + DEBUG(0, ("talloc failed\n")); + TALLOC_FREE(tmp_ctx); + return false; + } + strupper_m(tmp_dom); + *ret_domain = tmp_dom; + } + + if (ret_sid != NULL) { + sid_copy(ret_sid, &sid); + } + + if (ret_type != NULL) { + *ret_type = type; + } + + TALLOC_FREE(tmp_ctx); + return true; +} + +/************************************************************************ + Names from smb.conf can be unqualified. eg. valid users = foo + These names should never map to a remote name. Try global_sam_name()\foo, + and then "Unix Users"\foo (or "Unix Groups"\foo). +************************************************************************/ + +bool lookup_name_smbconf(TALLOC_CTX *mem_ctx, + const char *full_name, int flags, + const char **ret_domain, const char **ret_name, + DOM_SID *ret_sid, enum lsa_SidType *ret_type) +{ + char *qualified_name; + const char *p; + + /* NB. No winbindd_separator here as lookup_name needs \\' */ + if ((p = strchr_m(full_name, *lp_winbind_separator())) != NULL) { + + /* The name is already qualified with a domain. */ + + if (*lp_winbind_separator() != '\\') { + char *tmp; + + /* lookup_name() needs '\\' as a separator */ + + tmp = talloc_strdup(mem_ctx, full_name); + if (!tmp) { + return false; + } + tmp[p - full_name] = '\\'; + full_name = tmp; + } + + return lookup_name(mem_ctx, full_name, flags, + ret_domain, ret_name, + ret_sid, ret_type); + } + + /* Try with our own SAM name. */ + qualified_name = talloc_asprintf(mem_ctx, "%s\\%s", + get_global_sam_name(), + full_name ); + if (!qualified_name) { + return false; + } + + if (lookup_name(mem_ctx, qualified_name, flags, + ret_domain, ret_name, + ret_sid, ret_type)) { + return true; + } + + /* Finally try with "Unix Users" or "Unix Group" */ + qualified_name = talloc_asprintf(mem_ctx, "%s\\%s", + flags & LOOKUP_NAME_GROUP ? + unix_groups_domain_name() : + unix_users_domain_name(), + full_name ); + if (!qualified_name) { + return false; + } + + return lookup_name(mem_ctx, qualified_name, flags, + ret_domain, ret_name, + ret_sid, ret_type); +} + +static bool wb_lookup_rids(TALLOC_CTX *mem_ctx, + const DOM_SID *domain_sid, + int num_rids, uint32 *rids, + const char **domain_name, + const char **names, enum lsa_SidType *types) +{ + int i; + const char **my_names; + enum lsa_SidType *my_types; + TALLOC_CTX *tmp_ctx; + + if (!(tmp_ctx = talloc_init("wb_lookup_rids"))) { + return false; + } + + if (!winbind_lookup_rids(tmp_ctx, domain_sid, num_rids, rids, + domain_name, &my_names, &my_types)) { + *domain_name = ""; + for (i=0; i<num_rids; i++) { + names[i] = ""; + types[i] = SID_NAME_UNKNOWN; + } + TALLOC_FREE(tmp_ctx); + return true; + } + + if (!(*domain_name = talloc_strdup(mem_ctx, *domain_name))) { + TALLOC_FREE(tmp_ctx); + return false; + } + + /* + * winbind_lookup_rids allocates its own array. We've been given the + * array, so copy it over + */ + + for (i=0; i<num_rids; i++) { + if (my_names[i] == NULL) { + TALLOC_FREE(tmp_ctx); + return false; + } + if (!(names[i] = talloc_strdup(names, my_names[i]))) { + TALLOC_FREE(tmp_ctx); + return false; + } + types[i] = my_types[i]; + } + TALLOC_FREE(tmp_ctx); + return true; +} + +static bool lookup_rids(TALLOC_CTX *mem_ctx, const DOM_SID *domain_sid, + int num_rids, uint32_t *rids, + const char **domain_name, + const char ***names, enum lsa_SidType **types) +{ + int i; + + DEBUG(10, ("lookup_rids called for domain sid '%s'\n", + sid_string_dbg(domain_sid))); + + if (num_rids) { + *names = TALLOC_ARRAY(mem_ctx, const char *, num_rids); + *types = TALLOC_ARRAY(mem_ctx, enum lsa_SidType, num_rids); + + if ((*names == NULL) || (*types == NULL)) { + return false; + } + } else { + *names = NULL; + *types = NULL; + } + + if (sid_check_is_domain(domain_sid)) { + NTSTATUS result; + + if (*domain_name == NULL) { + *domain_name = talloc_strdup( + mem_ctx, get_global_sam_name()); + } + + if (*domain_name == NULL) { + return false; + } + + become_root(); + result = pdb_lookup_rids(domain_sid, num_rids, rids, + *names, *types); + unbecome_root(); + + return (NT_STATUS_IS_OK(result) || + NT_STATUS_EQUAL(result, NT_STATUS_NONE_MAPPED) || + NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)); + } + + if (sid_check_is_builtin(domain_sid)) { + + if (*domain_name == NULL) { + *domain_name = talloc_strdup( + mem_ctx, builtin_domain_name()); + } + + if (*domain_name == NULL) { + return false; + } + + for (i=0; i<num_rids; i++) { + if (lookup_builtin_rid(*names, rids[i], + &(*names)[i])) { + if ((*names)[i] == NULL) { + return false; + } + (*types)[i] = SID_NAME_ALIAS; + } else { + (*types)[i] = SID_NAME_UNKNOWN; + } + } + return true; + } + + if (sid_check_is_wellknown_domain(domain_sid, NULL)) { + for (i=0; i<num_rids; i++) { + DOM_SID sid; + sid_copy(&sid, domain_sid); + sid_append_rid(&sid, rids[i]); + if (lookup_wellknown_sid(mem_ctx, &sid, + domain_name, &(*names)[i])) { + if ((*names)[i] == NULL) { + return false; + } + (*types)[i] = SID_NAME_WKN_GRP; + } else { + (*types)[i] = SID_NAME_UNKNOWN; + } + } + return true; + } + + if (sid_check_is_unix_users(domain_sid)) { + if (*domain_name == NULL) { + *domain_name = talloc_strdup( + mem_ctx, unix_users_domain_name()); + if (*domain_name == NULL) { + return false; + } + } + for (i=0; i<num_rids; i++) { + (*names)[i] = talloc_strdup( + (*names), uidtoname(rids[i])); + if ((*names)[i] == NULL) { + return false; + } + (*types)[i] = SID_NAME_USER; + } + return true; + } + + if (sid_check_is_unix_groups(domain_sid)) { + if (*domain_name == NULL) { + *domain_name = talloc_strdup( + mem_ctx, unix_groups_domain_name()); + if (*domain_name == NULL) { + return false; + } + } + for (i=0; i<num_rids; i++) { + (*names)[i] = talloc_strdup( + (*names), gidtoname(rids[i])); + if ((*names)[i] == NULL) { + return false; + } + (*types)[i] = SID_NAME_DOM_GRP; + } + return true; + } + + return wb_lookup_rids(mem_ctx, domain_sid, num_rids, rids, + domain_name, *names, *types); +} + +/* + * Is the SID a domain as such? If yes, lookup its name. + */ + +static bool lookup_as_domain(const DOM_SID *sid, TALLOC_CTX *mem_ctx, + const char **name) +{ + const char *tmp; + enum lsa_SidType type; + + if (sid_check_is_domain(sid)) { + *name = talloc_strdup(mem_ctx, get_global_sam_name()); + return true; + } + + if (sid_check_is_builtin(sid)) { + *name = talloc_strdup(mem_ctx, builtin_domain_name()); + return true; + } + + if (sid_check_is_wellknown_domain(sid, &tmp)) { + *name = talloc_strdup(mem_ctx, tmp); + return true; + } + + if (sid_check_is_unix_users(sid)) { + *name = talloc_strdup(mem_ctx, unix_users_domain_name()); + return true; + } + + if (sid_check_is_unix_groups(sid)) { + *name = talloc_strdup(mem_ctx, unix_groups_domain_name()); + return true; + } + + if (sid->num_auths != 4) { + /* This can't be a domain */ + return false; + } + + if (IS_DC) { + uint32 i, num_domains; + struct trustdom_info **domains; + + /* This is relatively expensive, but it happens only on DCs + * and for SIDs that have 4 sub-authorities and thus look like + * domains */ + + if (!NT_STATUS_IS_OK(pdb_enum_trusteddoms(mem_ctx, + &num_domains, + &domains))) { + return false; + } + + for (i=0; i<num_domains; i++) { + if (sid_equal(sid, &domains[i]->sid)) { + *name = talloc_strdup(mem_ctx, + domains[i]->name); + return true; + } + } + return false; + } + + if (winbind_lookup_sid(mem_ctx, sid, &tmp, NULL, &type) && + (type == SID_NAME_DOMAIN)) { + *name = tmp; + return true; + } + + return false; +} + +/* + * This tries to implement the rather weird rules for the lsa_lookup level + * parameter. + * + * This is as close as we can get to what W2k3 does. With this we survive the + * RPC-LSALOOKUP samba4 test as of 2006-01-08. NT4 as a PDC is a bit more + * different, but I assume that's just being too liberal. For example, W2k3 + * replies to everything else but the levels 1-6 with INVALID_PARAMETER + * whereas NT4 does the same as level 1 (I think). I did not fully test that + * with NT4, this is what w2k3 does. + * + * Level 1: Ask everywhere + * Level 2: Ask domain and trusted domains, no builtin and wkn + * Level 3: Only ask domain + * Level 4: W2k3ad: Only ask AD trusts + * Level 5: Only ask transitive forest trusts + * Level 6: Like 4 + */ + +static bool check_dom_sid_to_level(const DOM_SID *sid, int level) +{ + int ret = false; + + switch(level) { + case 1: + ret = true; + break; + case 2: + ret = (!sid_check_is_builtin(sid) && + !sid_check_is_wellknown_domain(sid, NULL)); + break; + case 3: + case 4: + case 6: + ret = sid_check_is_domain(sid); + break; + case 5: + ret = false; + break; + } + + DEBUG(10, ("%s SID %s in level %d\n", + ret ? "Accepting" : "Rejecting", + sid_string_dbg(sid), level)); + return ret; +} + +/* + * Lookup a bunch of SIDs. This is modeled after lsa_lookup_sids with + * references to domains, it is explicitly made for this. + * + * This attempts to be as efficient as possible: It collects all SIDs + * belonging to a domain and hands them in bulk to the appropriate lookup + * function. In particular pdb_lookup_rids with ldapsam_trusted benefits + * *hugely* from this. Winbind is going to be extended with a lookup_rids + * interface as well, so on a DC we can do a bulk lsa_lookuprids to the + * appropriate DC. + */ + +NTSTATUS lookup_sids(TALLOC_CTX *mem_ctx, int num_sids, + const DOM_SID **sids, int level, + struct lsa_dom_info **ret_domains, + struct lsa_name_info **ret_names) +{ + TALLOC_CTX *tmp_ctx; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + struct lsa_name_info *name_infos; + struct lsa_dom_info *dom_infos = NULL; + + int i, j; + + if (!(tmp_ctx = talloc_new(mem_ctx))) { + DEBUG(0, ("talloc_new failed\n")); + return NT_STATUS_NO_MEMORY; + } + + if (num_sids) { + name_infos = TALLOC_ARRAY(mem_ctx, struct lsa_name_info, num_sids); + if (name_infos == NULL) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + } else { + name_infos = NULL; + } + + dom_infos = TALLOC_ZERO_ARRAY(mem_ctx, struct lsa_dom_info, + MAX_REF_DOMAINS); + if (dom_infos == NULL) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + + /* First build up the data structures: + * + * dom_infos is a list of domains referenced in the list of + * SIDs. Later we will walk the list of domains and look up the RIDs + * in bulk. + * + * name_infos is a shadow-copy of the SIDs array to collect the real + * data. + * + * dom_info->idxs is an index into the name_infos array. The + * difficulty we have here is that we need to keep the SIDs the client + * asked for in the same order for the reply + */ + + for (i=0; i<num_sids; i++) { + DOM_SID sid; + uint32 rid; + const char *domain_name = NULL; + + sid_copy(&sid, sids[i]); + name_infos[i].type = SID_NAME_USE_NONE; + + if (lookup_as_domain(&sid, name_infos, &domain_name)) { + /* We can't push that through the normal lookup + * process, as this would reference illegal + * domains. + * + * For example S-1-5-32 would end up referencing + * domain S-1-5- with RID 32 which is clearly wrong. + */ + if (domain_name == NULL) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + + name_infos[i].rid = 0; + name_infos[i].type = SID_NAME_DOMAIN; + name_infos[i].name = NULL; + + if (sid_check_is_builtin(&sid)) { + /* Yes, W2k3 returns "BUILTIN" both as domain + * and name here */ + name_infos[i].name = talloc_strdup( + name_infos, builtin_domain_name()); + if (name_infos[i].name == NULL) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + } + } else { + /* This is a normal SID with rid component */ + if (!sid_split_rid(&sid, &rid)) { + result = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + } + + if (!check_dom_sid_to_level(&sid, level)) { + name_infos[i].rid = 0; + name_infos[i].type = SID_NAME_UNKNOWN; + name_infos[i].name = NULL; + continue; + } + + for (j=0; j<MAX_REF_DOMAINS; j++) { + if (!dom_infos[j].valid) { + break; + } + if (sid_equal(&sid, &dom_infos[j].sid)) { + break; + } + } + + if (j == MAX_REF_DOMAINS) { + /* TODO: What's the right error message here? */ + result = NT_STATUS_NONE_MAPPED; + goto fail; + } + + if (!dom_infos[j].valid) { + /* We found a domain not yet referenced, create a new + * ref. */ + dom_infos[j].valid = true; + sid_copy(&dom_infos[j].sid, &sid); + + if (domain_name != NULL) { + /* This name was being found above in the case + * when we found a domain SID */ + dom_infos[j].name = + talloc_strdup(dom_infos, domain_name); + if (dom_infos[j].name == NULL) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + } else { + /* lookup_rids will take care of this */ + dom_infos[j].name = NULL; + } + } + + name_infos[i].dom_idx = j; + + if (name_infos[i].type == SID_NAME_USE_NONE) { + name_infos[i].rid = rid; + + ADD_TO_ARRAY(dom_infos, int, i, &dom_infos[j].idxs, + &dom_infos[j].num_idxs); + + if (dom_infos[j].idxs == NULL) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + } + } + + /* Iterate over the domains found */ + + for (i=0; i<MAX_REF_DOMAINS; i++) { + uint32_t *rids; + const char *domain_name = NULL; + const char **names; + enum lsa_SidType *types; + struct lsa_dom_info *dom = &dom_infos[i]; + + if (!dom->valid) { + /* No domains left, we're done */ + break; + } + + if (dom->num_idxs) { + if (!(rids = TALLOC_ARRAY(tmp_ctx, uint32, dom->num_idxs))) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + } else { + rids = NULL; + } + + for (j=0; j<dom->num_idxs; j++) { + rids[j] = name_infos[dom->idxs[j]].rid; + } + + if (!lookup_rids(tmp_ctx, &dom->sid, + dom->num_idxs, rids, &domain_name, + &names, &types)) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!(dom->name = talloc_strdup(dom_infos, domain_name))) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + + for (j=0; j<dom->num_idxs; j++) { + int idx = dom->idxs[j]; + name_infos[idx].type = types[j]; + if (types[j] != SID_NAME_UNKNOWN) { + name_infos[idx].name = + talloc_strdup(name_infos, names[j]); + if (name_infos[idx].name == NULL) { + result = NT_STATUS_NO_MEMORY; + goto fail; + } + } else { + name_infos[idx].name = NULL; + } + } + } + + *ret_domains = dom_infos; + *ret_names = name_infos; + TALLOC_FREE(tmp_ctx); + return NT_STATUS_OK; + + fail: + TALLOC_FREE(dom_infos); + TALLOC_FREE(name_infos); + TALLOC_FREE(tmp_ctx); + return result; +} + +/***************************************************************** + *THE CANONICAL* convert SID to name function. +*****************************************************************/ + +bool lookup_sid(TALLOC_CTX *mem_ctx, const DOM_SID *sid, + const char **ret_domain, const char **ret_name, + enum lsa_SidType *ret_type) +{ + struct lsa_dom_info *domain; + struct lsa_name_info *name; + TALLOC_CTX *tmp_ctx; + bool ret = false; + + DEBUG(10, ("lookup_sid called for SID '%s'\n", sid_string_dbg(sid))); + + if (!(tmp_ctx = talloc_new(mem_ctx))) { + DEBUG(0, ("talloc_new failed\n")); + return false; + } + + if (!NT_STATUS_IS_OK(lookup_sids(tmp_ctx, 1, &sid, 1, + &domain, &name))) { + goto done; + } + + if (name->type == SID_NAME_UNKNOWN) { + goto done; + } + + if ((ret_domain != NULL) && + !(*ret_domain = talloc_strdup(mem_ctx, domain->name))) { + goto done; + } + + if ((ret_name != NULL) && + !(*ret_name = talloc_strdup(mem_ctx, name->name))) { + goto done; + } + + if (ret_type != NULL) { + *ret_type = name->type; + } + + ret = true; + + done: + if (ret) { + DEBUG(10, ("Sid %s -> %s\\%s(%d)\n", sid_string_dbg(sid), + domain->name, name->name, name->type)); + } else { + DEBUG(10, ("failed to lookup sid %s\n", sid_string_dbg(sid))); + } + TALLOC_FREE(tmp_ctx); + return ret; +} + +/***************************************************************** + Id mapping cache. This is to avoid Winbind mappings already + seen by smbd to be queried too frequently, keeping winbindd + busy, and blocking smbd while winbindd is busy with other + stuff. Written by Michael Steffens <michael.steffens@hp.com>, + modified to use linked lists by jra. +*****************************************************************/ + +/***************************************************************** + Find a SID given a uid. +*****************************************************************/ + +static bool fetch_sid_from_uid_cache(DOM_SID *psid, uid_t uid) +{ + DATA_BLOB cache_value; + + if (!memcache_lookup(NULL, UID_SID_CACHE, + data_blob_const(&uid, sizeof(uid)), + &cache_value)) { + return false; + } + + memcpy(psid, cache_value.data, MIN(sizeof(*psid), cache_value.length)); + SMB_ASSERT(cache_value.length >= offsetof(struct dom_sid, id_auth)); + SMB_ASSERT(cache_value.length == ndr_size_dom_sid(psid, 0)); + + return true; +} + +/***************************************************************** + Find a uid given a SID. +*****************************************************************/ + +static bool fetch_uid_from_cache( uid_t *puid, const DOM_SID *psid ) +{ + DATA_BLOB cache_value; + + if (!memcache_lookup(NULL, SID_UID_CACHE, + data_blob_const(psid, ndr_size_dom_sid(psid, 0)), + &cache_value)) { + return false; + } + + SMB_ASSERT(cache_value.length == sizeof(*puid)); + memcpy(puid, cache_value.data, sizeof(*puid)); + + return true; +} + +/***************************************************************** + Store uid to SID mapping in cache. +*****************************************************************/ + +void store_uid_sid_cache(const DOM_SID *psid, uid_t uid) +{ + memcache_add(NULL, SID_UID_CACHE, + data_blob_const(psid, ndr_size_dom_sid(psid, 0)), + data_blob_const(&uid, sizeof(uid))); + memcache_add(NULL, UID_SID_CACHE, + data_blob_const(&uid, sizeof(uid)), + data_blob_const(psid, ndr_size_dom_sid(psid, 0))); +} + +/***************************************************************** + Find a SID given a gid. +*****************************************************************/ + +static bool fetch_sid_from_gid_cache(DOM_SID *psid, gid_t gid) +{ + DATA_BLOB cache_value; + + if (!memcache_lookup(NULL, GID_SID_CACHE, + data_blob_const(&gid, sizeof(gid)), + &cache_value)) { + return false; + } + + memcpy(psid, cache_value.data, MIN(sizeof(*psid), cache_value.length)); + SMB_ASSERT(cache_value.length >= offsetof(struct dom_sid, id_auth)); + SMB_ASSERT(cache_value.length == ndr_size_dom_sid(psid, 0)); + + return true; +} + +/***************************************************************** + Find a gid given a SID. +*****************************************************************/ + +static bool fetch_gid_from_cache(gid_t *pgid, const DOM_SID *psid) +{ + DATA_BLOB cache_value; + + if (!memcache_lookup(NULL, SID_UID_CACHE, + data_blob_const(psid, ndr_size_dom_sid(psid, 0)), + &cache_value)) { + return false; + } + + SMB_ASSERT(cache_value.length == sizeof(*pgid)); + memcpy(pgid, cache_value.data, sizeof(*pgid)); + + return true; +} + +/***************************************************************** + Store gid to SID mapping in cache. +*****************************************************************/ + +void store_gid_sid_cache(const DOM_SID *psid, gid_t gid) +{ + memcache_add(NULL, SID_GID_CACHE, + data_blob_const(psid, ndr_size_dom_sid(psid, 0)), + data_blob_const(&gid, sizeof(gid))); + memcache_add(NULL, GID_SID_CACHE, + data_blob_const(&gid, sizeof(gid)), + data_blob_const(psid, ndr_size_dom_sid(psid, 0))); +} + +/***************************************************************** + *THE LEGACY* convert uid_t to SID function. +*****************************************************************/ + +static void legacy_uid_to_sid(DOM_SID *psid, uid_t uid) +{ + uint32 rid; + bool ret; + + ZERO_STRUCTP(psid); + + become_root(); + ret = pdb_uid_to_rid(uid, &rid); + unbecome_root(); + + if (ret) { + /* This is a mapped user */ + sid_copy(psid, get_global_sam_sid()); + sid_append_rid(psid, rid); + goto done; + } + + /* This is an unmapped user */ + + uid_to_unix_users_sid(uid, psid); + + done: + DEBUG(10,("LEGACY: uid %u -> sid %s\n", (unsigned int)uid, + sid_string_dbg(psid))); + + store_uid_sid_cache(psid, uid); + return; +} + +/***************************************************************** + *THE LEGACY* convert gid_t to SID function. +*****************************************************************/ + +static void legacy_gid_to_sid(DOM_SID *psid, gid_t gid) +{ + bool ret; + + ZERO_STRUCTP(psid); + + become_root(); + ret = pdb_gid_to_sid(gid, psid); + unbecome_root(); + + if (ret) { + /* This is a mapped group */ + goto done; + } + + /* This is an unmapped group */ + + gid_to_unix_groups_sid(gid, psid); + + done: + DEBUG(10,("LEGACY: gid %u -> sid %s\n", (unsigned int)gid, + sid_string_dbg(psid))); + + store_gid_sid_cache(psid, gid); + return; +} + +/***************************************************************** + *THE LEGACY* convert SID to uid function. +*****************************************************************/ + +static bool legacy_sid_to_uid(const DOM_SID *psid, uid_t *puid) +{ + enum lsa_SidType type; + uint32 rid; + + if (sid_peek_check_rid(get_global_sam_sid(), psid, &rid)) { + union unid_t id; + bool ret; + + become_root(); + ret = pdb_sid_to_id(psid, &id, &type); + unbecome_root(); + + if (ret) { + if (type != SID_NAME_USER) { + DEBUG(5, ("sid %s is a %s, expected a user\n", + sid_string_dbg(psid), + sid_type_lookup(type))); + return false; + } + *puid = id.uid; + goto done; + } + + /* This was ours, but it was not mapped. Fail */ + } + + DEBUG(10,("LEGACY: mapping failed for sid %s\n", + sid_string_dbg(psid))); + return false; + +done: + DEBUG(10,("LEGACY: sid %s -> uid %u\n", sid_string_dbg(psid), + (unsigned int)*puid )); + + store_uid_sid_cache(psid, *puid); + return true; +} + +/***************************************************************** + *THE LEGACY* convert SID to gid function. + Group mapping is used for gids that maps to Wellknown SIDs +*****************************************************************/ + +static bool legacy_sid_to_gid(const DOM_SID *psid, gid_t *pgid) +{ + uint32 rid; + GROUP_MAP map; + union unid_t id; + enum lsa_SidType type; + + if ((sid_check_is_in_builtin(psid) || + sid_check_is_in_wellknown_domain(psid))) { + bool ret; + + become_root(); + ret = pdb_getgrsid(&map, *psid); + unbecome_root(); + + if (ret) { + *pgid = map.gid; + goto done; + } + DEBUG(10,("LEGACY: mapping failed for sid %s\n", + sid_string_dbg(psid))); + return false; + } + + if (sid_peek_check_rid(get_global_sam_sid(), psid, &rid)) { + bool ret; + + become_root(); + ret = pdb_sid_to_id(psid, &id, &type); + unbecome_root(); + + if (ret) { + if ((type != SID_NAME_DOM_GRP) && + (type != SID_NAME_ALIAS)) { + DEBUG(5, ("LEGACY: sid %s is a %s, expected " + "a group\n", sid_string_dbg(psid), + sid_type_lookup(type))); + return false; + } + *pgid = id.gid; + goto done; + } + + /* This was ours, but it was not mapped. Fail */ + } + + DEBUG(10,("LEGACY: mapping failed for sid %s\n", + sid_string_dbg(psid))); + return false; + + done: + DEBUG(10,("LEGACY: sid %s -> gid %u\n", sid_string_dbg(psid), + (unsigned int)*pgid )); + + store_gid_sid_cache(psid, *pgid); + + return true; +} + +/***************************************************************** + *THE CANONICAL* convert uid_t to SID function. +*****************************************************************/ + +void uid_to_sid(DOM_SID *psid, uid_t uid) +{ + bool expired = true; + bool ret; + ZERO_STRUCTP(psid); + + if (fetch_sid_from_uid_cache(psid, uid)) + return; + + /* Check the winbindd cache directly. */ + ret = idmap_cache_find_uid2sid(uid, psid, &expired); + + if (ret && !expired && is_null_sid(psid)) { + /* + * Negative cache entry, we already asked. + * do legacy. + */ + legacy_uid_to_sid(psid, uid); + return; + } + + if (!ret || expired) { + /* Not in cache. Ask winbindd. */ + if (!winbind_uid_to_sid(psid, uid)) { + if (!winbind_ping()) { + legacy_uid_to_sid(psid, uid); + return; + } + + DEBUG(5, ("uid_to_sid: winbind failed to find a sid for uid %u\n", + uid)); + return; + } + } + + DEBUG(10,("uid %u -> sid %s\n", (unsigned int)uid, + sid_string_dbg(psid))); + + store_uid_sid_cache(psid, uid); + return; +} + +/***************************************************************** + *THE CANONICAL* convert gid_t to SID function. +*****************************************************************/ + +void gid_to_sid(DOM_SID *psid, gid_t gid) +{ + bool expired = true; + bool ret; + ZERO_STRUCTP(psid); + + if (fetch_sid_from_gid_cache(psid, gid)) + return; + + /* Check the winbindd cache directly. */ + ret = idmap_cache_find_gid2sid(gid, psid, &expired); + + if (ret && !expired && is_null_sid(psid)) { + /* + * Negative cache entry, we already asked. + * do legacy. + */ + legacy_gid_to_sid(psid, gid); + return; + } + + if (!ret || expired) { + /* Not in cache. Ask winbindd. */ + if (!winbind_gid_to_sid(psid, gid)) { + if (!winbind_ping()) { + legacy_gid_to_sid(psid, gid); + return; + } + + DEBUG(5, ("gid_to_sid: winbind failed to find a sid for gid %u\n", + gid)); + return; + } + } + + DEBUG(10,("gid %u -> sid %s\n", (unsigned int)gid, + sid_string_dbg(psid))); + + store_gid_sid_cache(psid, gid); + return; +} + +/***************************************************************** + *THE CANONICAL* convert SID to uid function. +*****************************************************************/ + +bool sid_to_uid(const DOM_SID *psid, uid_t *puid) +{ + bool expired = true; + bool ret; + uint32 rid; + gid_t gid; + + if (fetch_uid_from_cache(puid, psid)) + return true; + + if (fetch_gid_from_cache(&gid, psid)) { + return false; + } + + /* Optimize for the Unix Users Domain + * as the conversion is straightforward */ + if (sid_peek_check_rid(&global_sid_Unix_Users, psid, &rid)) { + uid_t uid = rid; + *puid = uid; + + /* return here, don't cache */ + DEBUG(10,("sid %s -> uid %u\n", sid_string_dbg(psid), + (unsigned int)*puid )); + return true; + } + + /* Check the winbindd cache directly. */ + ret = idmap_cache_find_sid2uid(psid, puid, &expired); + + if (ret && !expired && (*puid == (uid_t)-1)) { + /* + * Negative cache entry, we already asked. + * do legacy. + */ + return legacy_sid_to_uid(psid, puid); + } + + if (!ret || expired) { + /* Not in cache. Ask winbindd. */ + if (!winbind_sid_to_uid(puid, psid)) { + if (!winbind_ping()) { + return legacy_sid_to_uid(psid, puid); + } + + DEBUG(5, ("winbind failed to find a uid for sid %s\n", + sid_string_dbg(psid))); + return false; + } + } + + /* TODO: Here would be the place to allocate both a gid and a uid for + * the SID in question */ + + DEBUG(10,("sid %s -> uid %u\n", sid_string_dbg(psid), + (unsigned int)*puid )); + + store_uid_sid_cache(psid, *puid); + return true; +} + +/***************************************************************** + *THE CANONICAL* convert SID to gid function. + Group mapping is used for gids that maps to Wellknown SIDs +*****************************************************************/ + +bool sid_to_gid(const DOM_SID *psid, gid_t *pgid) +{ + bool expired = true; + bool ret; + uint32 rid; + uid_t uid; + + if (fetch_gid_from_cache(pgid, psid)) + return true; + + if (fetch_uid_from_cache(&uid, psid)) + return false; + + /* Optimize for the Unix Groups Domain + * as the conversion is straightforward */ + if (sid_peek_check_rid(&global_sid_Unix_Groups, psid, &rid)) { + gid_t gid = rid; + *pgid = gid; + + /* return here, don't cache */ + DEBUG(10,("sid %s -> gid %u\n", sid_string_dbg(psid), + (unsigned int)*pgid )); + return true; + } + + /* Check the winbindd cache directly. */ + ret = idmap_cache_find_sid2gid(psid, pgid, &expired); + + if (ret && !expired && (*pgid == (gid_t)-1)) { + /* + * Negative cache entry, we already asked. + * do legacy. + */ + return legacy_sid_to_gid(psid, pgid); + } + + if (!ret || expired) { + /* Not in cache or negative. Ask winbindd. */ + /* Ask winbindd if it can map this sid to a gid. + * (Idmap will check it is a valid SID and of the right type) */ + + if ( !winbind_sid_to_gid(pgid, psid) ) { + if (!winbind_ping()) { + return legacy_sid_to_gid(psid, pgid); + } + + DEBUG(10,("winbind failed to find a gid for sid %s\n", + sid_string_dbg(psid))); + return false; + } + } + + DEBUG(10,("sid %s -> gid %u\n", sid_string_dbg(psid), + (unsigned int)*pgid )); + + store_gid_sid_cache(psid, *pgid); + return true; +} diff --git a/source3/passdb/machine_sid.c b/source3/passdb/machine_sid.c new file mode 100644 index 0000000000..ff2c9bcb0d --- /dev/null +++ b/source3/passdb/machine_sid.c @@ -0,0 +1,248 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Jeremy Allison 1996-2002 + Copyright (C) Andrew Tridgell 2002 + Copyright (C) Gerald (Jerry) Carter 2000 + Copyright (C) Stefan (metze) Metzmacher 2002 + + 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" + +/* NOTE! the global_sam_sid is the SID of our local SAM. This is only + equal to the domain SID when we are a DC, otherwise its our + workstation SID */ +static DOM_SID *global_sam_sid=NULL; + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +/**************************************************************************** + Read a SID from a file. This is for compatibility with the old MACHINE.SID + style of SID storage +****************************************************************************/ + +static bool read_sid_from_file(const char *fname, DOM_SID *sid) +{ + char **lines; + int numlines; + bool ret; + + lines = file_lines_load(fname, &numlines,0); + + if (!lines || numlines < 1) { + if (lines) file_lines_free(lines); + return False; + } + + ret = string_to_sid(sid, lines[0]); + file_lines_free(lines); + return ret; +} + +/* + generate a random sid - used to build our own sid if we don't have one +*/ +static void generate_random_sid(DOM_SID *sid) +{ + int i; + uchar raw_sid_data[12]; + + memset((char *)sid, '\0', sizeof(*sid)); + sid->sid_rev_num = 1; + sid->id_auth[5] = 5; + sid->num_auths = 0; + sid->sub_auths[sid->num_auths++] = 21; + + generate_random_buffer(raw_sid_data, 12); + for (i = 0; i < 3; i++) + sid->sub_auths[sid->num_auths++] = IVAL(raw_sid_data, i*4); +} + +/**************************************************************************** + Generate the global machine sid. +****************************************************************************/ + +static DOM_SID *pdb_generate_sam_sid(void) +{ + DOM_SID domain_sid; + char *fname = NULL; + DOM_SID *sam_sid; + + if(!(sam_sid=SMB_MALLOC_P(DOM_SID))) + return NULL; + + if ( IS_DC ) { + if (secrets_fetch_domain_sid(lp_workgroup(), &domain_sid)) { + sid_copy(sam_sid, &domain_sid); + return sam_sid; + } + } + + if (secrets_fetch_domain_sid(global_myname(), sam_sid)) { + + /* We got our sid. If not a pdc/bdc, we're done. */ + if ( !IS_DC ) + return sam_sid; + + if (!secrets_fetch_domain_sid(lp_workgroup(), &domain_sid)) { + + /* No domain sid and we're a pdc/bdc. Store it */ + + if (!secrets_store_domain_sid(lp_workgroup(), sam_sid)) { + DEBUG(0,("pdb_generate_sam_sid: Can't store domain SID as a pdc/bdc.\n")); + SAFE_FREE(sam_sid); + return NULL; + } + return sam_sid; + } + + if (!sid_equal(&domain_sid, sam_sid)) { + + /* Domain name sid doesn't match global sam sid. Re-store domain sid as 'local' sid. */ + + DEBUG(0,("pdb_generate_sam_sid: Mismatched SIDs as a pdc/bdc.\n")); + if (!secrets_store_domain_sid(global_myname(), &domain_sid)) { + DEBUG(0,("pdb_generate_sam_sid: Can't re-store domain SID for local sid as PDC/BDC.\n")); + SAFE_FREE(sam_sid); + return NULL; + } + return sam_sid; + } + + return sam_sid; + + } + + /* check for an old MACHINE.SID file for backwards compatibility */ + if (asprintf(&fname, "%s/MACHINE.SID", lp_private_dir()) == -1) { + SAFE_FREE(sam_sid); + return NULL; + } + + if (read_sid_from_file(fname, sam_sid)) { + /* remember it for future reference and unlink the old MACHINE.SID */ + if (!secrets_store_domain_sid(global_myname(), sam_sid)) { + DEBUG(0,("pdb_generate_sam_sid: Failed to store SID from file.\n")); + SAFE_FREE(fname); + SAFE_FREE(sam_sid); + return NULL; + } + unlink(fname); + if ( !IS_DC ) { + if (!secrets_store_domain_sid(lp_workgroup(), sam_sid)) { + DEBUG(0,("pdb_generate_sam_sid: Failed to store domain SID from file.\n")); + SAFE_FREE(fname); + SAFE_FREE(sam_sid); + return NULL; + } + } + + /* Stored the old sid from MACHINE.SID successfully.*/ + SAFE_FREE(fname); + return sam_sid; + } + + SAFE_FREE(fname); + + /* we don't have the SID in secrets.tdb, we will need to + generate one and save it */ + generate_random_sid(sam_sid); + + if (!secrets_store_domain_sid(global_myname(), sam_sid)) { + DEBUG(0,("pdb_generate_sam_sid: Failed to store generated machine SID.\n")); + SAFE_FREE(sam_sid); + return NULL; + } + if ( IS_DC ) { + if (!secrets_store_domain_sid(lp_workgroup(), sam_sid)) { + DEBUG(0,("pdb_generate_sam_sid: Failed to store generated domain SID.\n")); + SAFE_FREE(sam_sid); + return NULL; + } + } + + return sam_sid; +} + +/* return our global_sam_sid */ +DOM_SID *get_global_sam_sid(void) +{ + struct db_context *db; + + if (global_sam_sid != NULL) + return global_sam_sid; + + /* + * memory for global_sam_sid is allocated in + * pdb_generate_sam_sid() as needed + * + * Note: this is garded by a transaction + * to prevent races on startup which + * can happen with some dbwrap backends + */ + + db = secrets_db_ctx(); + if (!db) { + smb_panic("could not open secrets db"); + } + + if (db->transaction_start(db) != 0) { + smb_panic("could not start transaction on secrets db"); + } + + if (!(global_sam_sid = pdb_generate_sam_sid())) { + db->transaction_cancel(db); + smb_panic("could not generate a machine SID"); + } + + if (db->transaction_commit(db) != 0) { + smb_panic("could not start commit secrets db"); + } + + return global_sam_sid; +} + +/** + * Force get_global_sam_sid to requery the backends + */ +void reset_global_sam_sid(void) +{ + SAFE_FREE(global_sam_sid); +} + +/***************************************************************** + Check if the SID is our domain SID (S-1-5-21-x-y-z). +*****************************************************************/ + +bool sid_check_is_domain(const DOM_SID *sid) +{ + return sid_equal(sid, get_global_sam_sid()); +} + +/***************************************************************** + Check if the SID is our domain SID (S-1-5-21-x-y-z). +*****************************************************************/ + +bool sid_check_is_in_our_domain(const DOM_SID *sid) +{ + DOM_SID dom_sid; + uint32 rid; + + sid_copy(&dom_sid, sid); + sid_split_rid(&dom_sid, &rid); + return sid_check_is_domain(&dom_sid); +} diff --git a/source3/passdb/passdb.c b/source3/passdb/passdb.c new file mode 100644 index 0000000000..a670b46d69 --- /dev/null +++ b/source3/passdb/passdb.c @@ -0,0 +1,1657 @@ +/* + 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +/****************************************************************** + get the default domain/netbios name to be used when + testing authentication. For example, if you connect + to a Windows member server using a bogus domain name, the + Windows box will map the BOGUS\user to DOMAIN\user. A + standalone box will map to WKS\user. +******************************************************************/ + +const char *my_sam_name(void) +{ + /* standalone servers can only use the local netbios name */ + if ( lp_server_role() == ROLE_STANDALONE ) + return global_myname(); + + /* Windows domain members default to the DOMAIN + name when not specified */ + return lp_workgroup(); +} + +/********************************************************************** +***********************************************************************/ + +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 ) + memset( user->plaintext_pw, 0x0, 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_P( 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->pass_must_change_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; +} + +/********************************************************************* + 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 samu *user, const struct passwd *pwd, bool create) +{ + const char *guest_account = lp_guestaccount(); + const char *domain = global_myname(); + uint32 urid; + + if ( !pwd ) { + return NT_STATUS_NO_SUCH_USER; + } + + /* Basic properties based upon the Unix account information */ + + pdb_set_username(user, pwd->pw_name, PDB_SET); + pdb_set_fullname(user, pwd->pw_gecos, PDB_SET); + pdb_set_domain (user, get_global_sam_name(), PDB_DEFAULT); +#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 ); + + /* 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_USER_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 */ + + pdb_set_profile_path(user, talloc_sub_specified(user, + lp_logon_path(), pwd->pw_name, domain, pwd->pw_uid, pwd->pw_gid), + PDB_DEFAULT); + pdb_set_homedir(user, talloc_sub_specified(user, + lp_logon_home(), pwd->pw_name, domain, pwd->pw_uid, pwd->pw_gid), + PDB_DEFAULT); + pdb_set_dir_drive(user, talloc_sub_specified(user, + lp_logon_drive(), pwd->pw_name, domain, pwd->pw_uid, pwd->pw_gid), + PDB_DEFAULT); + pdb_set_logon_script(user, talloc_sub_specified(user, + lp_logon_script(), pwd->pw_name, domain, pwd->pw_uid, pwd->pw_gid), + PDB_DEFAULT); + } + + /* 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 && !pdb_rid_algorithm() ) { + uint32 user_rid; + DOM_SID user_sid; + + if ( !pdb_new_rid( &user_rid ) ) { + DEBUG(3, ("Could not allocate a new RID\n")); + return NT_STATUS_ACCESS_DENIED; + } + + sid_copy( &user_sid, get_global_sam_sid() ); + sid_append_rid( &user_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( user, pwd, False ); +} + +NTSTATUS samu_alloc_rid_unix(struct samu *user, const struct passwd *pwd) +{ + return samu_set_unix_internal( 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 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 pdb_decode_acct_ctrl(const char *p) +{ + uint32 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 acct_ctrl) +{ + if (pwd != NULL) { + int i; + for (i = 0; i < 16; i++) + slprintf(&p[i*2], 3, "%02X", pwd[i]); + } else { + if (acct_ctrl & ACB_PWNOTREQ) + safe_strcpy(p, "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX", 32); + else + safe_strcpy(p, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 32); + } +} + +/************************************************************* + 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_ascii(p[i]); + lonybble = toupper_ascii(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) { + int i; + for (i = 0; i < 21; i++) { + slprintf(&p[i*2], 3, "%02X", hours[i]); + } + } else { + safe_strcpy(p, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 43); + } +} + +/************************************************************* + 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_ascii(p[i]); + lonybble = toupper_ascii(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 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 algorithmic_pdb_uid_to_user_rid(uid_t uid) +{ + int rid_offset = algorithmic_rid_base(); + return (((((uint32)uid)*RID_MULTIPLIER) + rid_offset) | USER_RID_TYPE); +} + +/******************************************************************* + Converts NT group RID to a UNIX gid. + ********************************************************************/ + +gid_t pdb_group_rid_to_gid(uint32 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 algorithmic_pdb_gid_to_group_rid(gid_t gid) +{ + int rid_offset = algorithmic_rid_base(); + return (((((uint32)gid)*RID_MULTIPLIER) + rid_offset) | GROUP_RID_TYPE); +} + +/******************************************************************* + Decides if a RID is a well known RID. + ********************************************************************/ + +static bool rid_is_well_known(uint32 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 rid) +{ + if ( rid_is_well_known(rid) ) { + /* + * The only well known user RIDs are DOMAIN_USER_RID_ADMIN + * and DOMAIN_USER_RID_GUEST. + */ + if(rid == DOMAIN_USER_RID_ADMIN || rid == DOMAIN_USER_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_GROUP_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; + 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_domain(&user_sid)) { + DEBUG(0, ("User %s with invalid SID %s in passdb\n", + name, sid_string_dbg(&user_sid))); + return False; + } + + sid_peek_rid(&user_sid, rid); + *type = SID_NAME_USER; + return True; + } + } + + /* + * Maybe it is a group ? + */ + + become_root(); + ret = pdb_getgrnam(&map, name); + unbecome_root(); + + if (!ret) { + return False; + } + + /* BUILTIN groups are looked up elsewhere */ + if (!sid_check_is_in_our_domain(&map.sid)) { + DEBUG(10, ("Found group %s (%s) not in our domain -- " + "ignoring.", name, sid_string_dbg(&map.sid))); + return False; + } + + /* yes it's a mapped group */ + sid_peek_rid(&map.sid, rid); + *type = map.sid_name_use; + return True; +} + +/************************************************************* + Change a password entry in the local smbpasswd file. + *************************************************************/ + +NTSTATUS local_password_change(const char *user_name, + int local_flags, + const char *new_passwd, + char **pp_err_str, + char **pp_msg_str) +{ + struct samu *sam_pass=NULL; + uint32 other_acb; + NTSTATUS result; + + *pp_err_str = NULL; + *pp_msg_str = NULL; + + /* Get the smb passwd entry for this user */ + + if ( !(sam_pass = samu_new( NULL )) ) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + if(!pdb_getsampwnam(sam_pass, user_name)) { + unbecome_root(); + TALLOC_FREE(sam_pass); + + if ((local_flags & LOCAL_ADD_USER) || (local_flags & LOCAL_DELETE_USER)) { + int tmp_debug = DEBUGLEVEL; + struct passwd *pwd; + + /* Might not exist in /etc/passwd. */ + + if (tmp_debug < 1) { + DEBUGLEVEL = 1; + } + + if ( !(pwd = getpwnam_alloc( NULL, user_name)) ) { + return NT_STATUS_NO_SUCH_USER; + } + + /* create the struct samu and initialize the basic Unix properties */ + + if ( !(sam_pass = samu_new( NULL )) ) { + return NT_STATUS_NO_MEMORY; + } + + result = samu_set_unix( sam_pass, pwd ); + + DEBUGLEVEL = tmp_debug; + + TALLOC_FREE( pwd ); + + if (NT_STATUS_EQUAL(result, NT_STATUS_INVALID_PRIMARY_GROUP)) { + return result; + } + + if (!NT_STATUS_IS_OK(result)) { + asprintf(pp_err_str, "Failed to " "initialize account for user %s: %s\n", + user_name, nt_errstr(result)); + return result; + } + } else { + asprintf(pp_err_str, "Failed to find entry for user %s.\n", user_name); + return NT_STATUS_NO_SUCH_USER; + } + } else { + unbecome_root(); + /* the entry already existed */ + local_flags &= ~LOCAL_ADD_USER; + } + + /* the 'other' acb bits not being changed here */ + other_acb = (pdb_get_acct_ctrl(sam_pass) & (~(ACB_WSTRUST|ACB_DOMTRUST|ACB_SVRTRUST|ACB_NORMAL))); + if (local_flags & LOCAL_TRUST_ACCOUNT) { + if (!pdb_set_acct_ctrl(sam_pass, ACB_WSTRUST | other_acb, PDB_CHANGED) ) { + asprintf(pp_err_str, "Failed to set 'trusted workstation account' flags for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + } else if (local_flags & LOCAL_INTERDOM_ACCOUNT) { + if (!pdb_set_acct_ctrl(sam_pass, ACB_DOMTRUST | other_acb, PDB_CHANGED)) { + asprintf(pp_err_str, "Failed to set 'domain trust account' flags for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + } else { + if (!pdb_set_acct_ctrl(sam_pass, ACB_NORMAL | other_acb, PDB_CHANGED)) { + asprintf(pp_err_str, "Failed to set 'normal account' flags for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + } + + /* + * We are root - just write the new password + * and the valid last change time. + */ + + if (local_flags & LOCAL_DISABLE_USER) { + if (!pdb_set_acct_ctrl (sam_pass, pdb_get_acct_ctrl(sam_pass)|ACB_DISABLED, PDB_CHANGED)) { + asprintf(pp_err_str, "Failed to set 'disabled' flag for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + } else if (local_flags & LOCAL_ENABLE_USER) { + if (!pdb_set_acct_ctrl (sam_pass, pdb_get_acct_ctrl(sam_pass)&(~ACB_DISABLED), PDB_CHANGED)) { + asprintf(pp_err_str, "Failed to unset 'disabled' flag for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + } + + if (local_flags & LOCAL_SET_NO_PASSWORD) { + if (!pdb_set_acct_ctrl (sam_pass, pdb_get_acct_ctrl(sam_pass)|ACB_PWNOTREQ, PDB_CHANGED)) { + asprintf(pp_err_str, "Failed to set 'no password required' flag for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + } else 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) && (pdb_get_acct_ctrl(sam_pass)&ACB_DISABLED)) { + if (!pdb_set_acct_ctrl (sam_pass, pdb_get_acct_ctrl(sam_pass)&(~ACB_DISABLED), PDB_CHANGED)) { + asprintf(pp_err_str, "Failed to unset 'disabled' flag for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + } + if (!pdb_set_acct_ctrl (sam_pass, pdb_get_acct_ctrl(sam_pass)&(~ACB_PWNOTREQ), PDB_CHANGED)) { + asprintf(pp_err_str, "Failed to unset 'no password required' flag for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + + if (!pdb_set_plaintext_passwd (sam_pass, new_passwd)) { + asprintf(pp_err_str, "Failed to set password for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + } + + if (local_flags & LOCAL_ADD_USER) { + if (NT_STATUS_IS_OK(pdb_add_sam_account(sam_pass))) { + asprintf(pp_msg_str, "Added user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_OK; + } else { + asprintf(pp_err_str, "Failed to add entry for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + } else if (local_flags & LOCAL_DELETE_USER) { + if (!NT_STATUS_IS_OK(pdb_delete_sam_account(sam_pass))) { + asprintf(pp_err_str, "Failed to delete entry for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return NT_STATUS_UNSUCCESSFUL; + } + asprintf(pp_msg_str, "Deleted user %s.\n", user_name); + } else { + result = pdb_update_sam_account(sam_pass); + if(!NT_STATUS_IS_OK(result)) { + asprintf(pp_err_str, "Failed to modify entry for user %s.\n", user_name); + TALLOC_FREE(sam_pass); + return result; + } + if(local_flags & LOCAL_DISABLE_USER) + asprintf(pp_msg_str, "Disabled user %s.\n", user_name); + else if (local_flags & LOCAL_ENABLE_USER) + asprintf(pp_msg_str, "Enabled user %s.\n", user_name); + else if (local_flags & LOCAL_SET_NO_PASSWORD) + asprintf(pp_msg_str, "User %s password set to none.\n", user_name); + } + + TALLOC_FREE(sam_pass); + return NT_STATUS_OK; +} + +/********************************************************************** + Marshall/unmarshall struct samu structs. + *********************************************************************/ + +#define TDB_FORMAT_STRING_V3 "dddddddBBBBBBBBBBBBddBBBdwdBwwd" + +/********************************************************************* +*********************************************************************/ + +bool init_sam_from_buffer_v3(struct samu *sampass, uint8 *buf, uint32 buflen) +{ + + /* times are stored as 32bit integer + take care on system with 64bit wide time_t + --SSS */ + uint32 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 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 user_rid, group_rid, hours_len, unknown_6, acct_ctrl; + uint16 logon_divs; + uint16 bad_password_count, logon_count; + uint8 *hours = NULL; + uint8 *lm_pw_ptr = NULL, *nt_pw_ptr = NULL, *nt_pw_hist_ptr = NULL; + uint32 len = 0; + uint32 lm_pw_len, nt_pw_len, nt_pw_hist_len, hourslen; + uint32 pwHistLen = 0; + bool ret = True; + fstring tmp_string; + bool expand_explicit = lp_passdb_expand_explicit(); + + if(sampass == NULL || buf == NULL) { + DEBUG(0, ("init_sam_from_buffer_v3: NULL parameters found!\n")); + return False; + } + +/* TDB_FORMAT_STRING_V3 "dddddddBBBBBBBBBBBBddBBBdwdBwwd" */ + + /* unpack the buffer into variables */ + len = tdb_unpack (buf, buflen, TDB_FORMAT_STRING_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 */ + &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 */ + /* Change from V2 is the uint32 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) -1) { + ret = False; + goto done; + } + + pdb_set_logon_time(sampass, convert_uint32_to_time_t(logon_time), PDB_SET); + pdb_set_logoff_time(sampass, convert_uint32_to_time_t(logoff_time), PDB_SET); + pdb_set_kickoff_time(sampass, convert_uint32_to_time_t(kickoff_time), PDB_SET); + pdb_set_bad_password_time(sampass, convert_uint32_to_time_t(bad_password_time), PDB_SET); + pdb_set_pass_can_change_time(sampass, convert_uint32_to_time_t(pass_can_change_time), PDB_SET); + pdb_set_pass_must_change_time(sampass, convert_uint32_to_time_t(pass_must_change_time), PDB_SET); + pdb_set_pass_last_set_time(sampass, convert_uint32_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_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(AP_PASSWORD_HISTORY, &pwHistLen); + if (pwHistLen) { + uint8 *pw_hist = (uint8 *)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 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, 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; +} + +/********************************************************************* +*********************************************************************/ + +uint32 init_buffer_from_sam_v3 (uint8 **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 logon_time, + logoff_time, + kickoff_time, + bad_password_time, + pass_last_set_time, + pass_can_change_time, + pass_must_change_time; + + uint32 user_rid, group_rid; + + const char *username; + const char *domain; + const char *nt_username; + const char *dir_drive; + const char *unknown_str; + 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 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; + + const uint8 *lm_pw; + const uint8 *nt_pw; + const uint8 *nt_pw_hist; + uint32 lm_pw_len = 16; + uint32 nt_pw_len = 16; + uint32 nt_pw_hist_len; + uint32 pwHistLen = 0; + + *buf = NULL; + buflen = 0; + + logon_time = convert_time_t_to_uint32(pdb_get_logon_time(sampass)); + logoff_time = convert_time_t_to_uint32(pdb_get_logoff_time(sampass)); + kickoff_time = convert_time_t_to_uint32(pdb_get_kickoff_time(sampass)); + bad_password_time = convert_time_t_to_uint32(pdb_get_bad_password_time(sampass)); + pass_can_change_time = convert_time_t_to_uint32(pdb_get_pass_can_change_time_noncalc(sampass)); + pass_must_change_time = convert_time_t_to_uint32(pdb_get_pass_must_change_time(sampass)); + pass_last_set_time = convert_time_t_to_uint32(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(AP_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; + } + + unknown_str = NULL; + unknown_str_len = 0; + + munged_dial = pdb_get_munged_dial(sampass); + if (munged_dial) { + munged_dial_len = strlen(munged_dial) +1; + } else { + munged_dial_len = 0; + } + +/* TDB_FORMAT_STRING_V3 "dddddddBBBBBBBBBBBBddBBBdwdBwwd" */ + + /* one time to get the size needed */ + len = tdb_pack(NULL, 0, TDB_FORMAT_STRING_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 */ + unknown_str_len, unknown_str, /* 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*)SMB_MALLOC(len)) == NULL) { + DEBUG(0,("init_buffer_from_sam_v3: Unable to malloc() memory for buffer!\n")); + return (-1); + } + + /* now for the real call to tdb_pack() */ + buflen = tdb_pack(*buf, len, TDB_FORMAT_STRING_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 */ + unknown_str_len, unknown_str, /* 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_sam_v3: somthing 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); +} + + +/********************************************************************* +*********************************************************************/ + +bool pdb_copy_sam_account(struct samu *dst, struct samu *src ) +{ + uint8 *buf = NULL; + int len; + + len = init_buffer_from_sam_v3(&buf, src, False); + if (len == -1 || !buf) { + SAFE_FREE(buf); + return False; + } + + if (!init_sam_from_buffer_v3( dst, 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; + } + } + + free(buf); + return True; +} + +/********************************************************************* + Update the bad password count checking the AP_RESET_COUNT_TIME +*********************************************************************/ + +bool pdb_update_bad_password_count(struct samu *sampass, bool *updated) +{ + time_t LastBadPassword; + uint16 BadPasswordCount; + uint32 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(AP_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) -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) LastBadPassword, resettime, (uint32)time(NULL))); + if (time(NULL) > (LastBadPassword + convert_uint32_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 AP_LOCK_ACCOUNT_DURATION +*********************************************************************/ + +bool pdb_update_autolock_flag(struct samu *sampass, bool *updated) +{ + uint32 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(AP_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) -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)LastBadPassword, duration*60, (uint32)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_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 account_policy_lockout; + bool autolock_updated = False, badpw_updated = False; + bool ret; + + /* Retrieve the account lockout policy */ + become_root(); + ret = pdb_get_account_policy(AP_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. +*******************************************************************/ + +bool get_trust_pw_clear(const char *domain, char **ret_pwd, + const char **account_name, uint32 *channel) +{ + char *pwd; + time_t last_set_time; + + /* 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, ret_pwd, 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(); + } + + 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) { + *ret_pwd = pwd; + if (account_name != NULL) { + *account_name = global_myname(); + } + + return true; + } + + DEBUG(5, ("get_trust_pw_clear: could not fetch clear text trust " + "account password for domain %s\n", domain)); + return false; +} + +/******************************************************************* + Wrapper around retrieving the trust account password. + appropriate account name is stored in account_name. +*******************************************************************/ + +bool get_trust_pw_hash(const char *domain, uint8 ret_pwd[16], + const char **account_name, uint32 *channel) +{ + char *pwd = NULL; + time_t last_set_time; + + if (get_trust_pw_clear(domain, &pwd, account_name, channel)) { + E_md4hash(pwd, ret_pwd); + SAFE_FREE(pwd); + 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, ret_pwd, + &last_set_time, + channel)) + { + if (account_name != NULL) { + *account_name = global_myname(); + } + + return true; + } + + DEBUG(5, ("get_trust_pw_hash: could not fetch trust account " + "password for domain %s\n", domain)); + return False; +} + +struct samr_LogonHours get_logon_hours_from_pdb(TALLOC_CTX *mem_ctx, + struct samu *pw) +{ + struct samr_LogonHours hours; + const int units_per_week = 168; + + ZERO_STRUCT(hours); + hours.bits = talloc_array(mem_ctx, uint8_t, units_per_week); + if (!hours.bits) { + return hours; + } + + hours.units_per_week = units_per_week; + memset(hours.bits, 0xFF, units_per_week); + + if (pdb_get_hours(pw)) { + memcpy(hours.bits, pdb_get_hours(pw), + MIN(pdb_get_hours_len(pw), units_per_week)); + } + + return hours; +} + diff --git a/source3/passdb/pdb_compat.c b/source3/passdb/pdb_compat.c new file mode 100644 index 0000000000..9967eb53ad --- /dev/null +++ b/source3/passdb/pdb_compat.c @@ -0,0 +1,103 @@ +/* + Unix SMB/CIFS implementation. + struct samu access routines + Copyright (C) Jeremy Allison 1996-2001 + Copyright (C) Luke Kenneth Casson Leighton 1996-1998 + Copyright (C) Gerald (Jerry) Carter 2000-2001 + Copyright (C) Andrew Bartlett 2001-2002 + Copyright (C) Stefan (metze) Metzmacher 2002 + + 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +uint32 pdb_get_user_rid (const struct samu *sampass) +{ + uint32 u_rid; + + if (sampass) + if (sid_peek_check_rid(get_global_sam_sid(), pdb_get_user_sid(sampass),&u_rid)) + return u_rid; + + return (0); +} + +uint32 pdb_get_group_rid (struct samu *sampass) +{ + uint32 g_rid; + + if (sampass) + if (sid_peek_check_rid(get_global_sam_sid(), pdb_get_group_sid(sampass),&g_rid)) + return g_rid; + return (0); +} + +bool pdb_set_user_sid_from_rid (struct samu *sampass, uint32 rid, enum pdb_value_state flag) +{ + DOM_SID u_sid; + const DOM_SID *global_sam_sid; + + if (!sampass) + return False; + + if (!(global_sam_sid = get_global_sam_sid())) { + DEBUG(1, ("pdb_set_user_sid_from_rid: Could not read global sam sid!\n")); + return False; + } + + sid_copy(&u_sid, global_sam_sid); + + if (!sid_append_rid(&u_sid, rid)) + return False; + + if (!pdb_set_user_sid(sampass, &u_sid, flag)) + return False; + + DEBUG(10, ("pdb_set_user_sid_from_rid:\n\tsetting user sid %s from rid %d\n", + sid_string_dbg(&u_sid),rid)); + + return True; +} + +bool pdb_set_group_sid_from_rid (struct samu *sampass, uint32 grid, enum pdb_value_state flag) +{ + DOM_SID g_sid; + const DOM_SID *global_sam_sid; + + if (!sampass) + return False; + + if (!(global_sam_sid = get_global_sam_sid())) { + DEBUG(1, ("pdb_set_user_sid_from_rid: Could not read global sam sid!\n")); + return False; + } + + sid_copy(&g_sid, global_sam_sid); + + if (!sid_append_rid(&g_sid, grid)) + return False; + + if (!pdb_set_group_sid(sampass, &g_sid, flag)) + return False; + + DEBUG(10, ("pdb_set_group_sid_from_rid:\n\tsetting group sid %s from rid %d\n", + sid_string_dbg(&g_sid), grid)); + + return True; +} + diff --git a/source3/passdb/pdb_get_set.c b/source3/passdb/pdb_get_set.c new file mode 100644 index 0000000000..7a8086c63e --- /dev/null +++ b/source3/passdb/pdb_get_set.c @@ -0,0 +1,1081 @@ +/* + Unix SMB/CIFS implementation. + struct samu access routines + 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) Stefan (metze) Metzmacher 2002 + + 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +/** + * @todo Redefine this to NULL, but this changes the API because + * much of samba assumes that the pdb_get...() funtions + * return strings. (ie not null-pointers). + * See also pdb_fill_default_sam(). + */ + +#define PDB_NOT_QUITE_NULL "" + +/********************************************************************* + Collection of get...() functions for struct samu. + ********************************************************************/ + +uint32 pdb_get_acct_ctrl(const struct samu *sampass) +{ + return sampass->acct_ctrl; +} + +time_t pdb_get_logon_time(const struct samu *sampass) +{ + return sampass->logon_time; +} + +time_t pdb_get_logoff_time(const struct samu *sampass) +{ + return sampass->logoff_time; +} + +time_t pdb_get_kickoff_time(const struct samu *sampass) +{ + return sampass->kickoff_time; +} + +time_t pdb_get_bad_password_time(const struct samu *sampass) +{ + return sampass->bad_password_time; +} + +time_t pdb_get_pass_last_set_time(const struct samu *sampass) +{ + return sampass->pass_last_set_time; +} + +time_t pdb_get_pass_can_change_time(const struct samu *sampass) +{ + uint32 allow; + + /* if the last set time is zero, it means the user cannot + change their password, and this time must be zero. jmcd + */ + if (sampass->pass_last_set_time == 0) + return (time_t) 0; + + /* if the time is max, and the field has been changed, + we're trying to update this real value from the sampass + to indicate that the user cannot change their password. jmcd + */ + if (sampass->pass_can_change_time == get_time_t_max() && + pdb_get_init_flags(sampass, PDB_CANCHANGETIME) == PDB_CHANGED) + return sampass->pass_can_change_time; + + if (!pdb_get_account_policy(AP_MIN_PASSWORD_AGE, &allow)) + allow = 0; + + /* in normal cases, just calculate it from policy */ + return sampass->pass_last_set_time + allow; +} + +/* we need this for loading from the backend, so that we don't overwrite + non-changed max times, otherwise the pass_can_change checking won't work */ +time_t pdb_get_pass_can_change_time_noncalc(const struct samu *sampass) +{ + return sampass->pass_can_change_time; +} + +time_t pdb_get_pass_must_change_time(const struct samu *sampass) +{ + uint32 expire; + + if (sampass->pass_last_set_time == 0) + return (time_t) 0; + + if (sampass->acct_ctrl & ACB_PWNOEXP) + return get_time_t_max(); + + if (!pdb_get_account_policy(AP_MAX_PASSWORD_AGE, &expire) + || expire == (uint32)-1 || expire == 0) + return get_time_t_max(); + + return sampass->pass_last_set_time + expire; +} + +bool pdb_get_pass_can_change(const struct samu *sampass) +{ + if (sampass->pass_can_change_time == get_time_t_max() && + sampass->pass_last_set_time != 0) + return False; + return True; +} + +uint16 pdb_get_logon_divs(const struct samu *sampass) +{ + return sampass->logon_divs; +} + +uint32 pdb_get_hours_len(const struct samu *sampass) +{ + return sampass->hours_len; +} + +const uint8 *pdb_get_hours(const struct samu *sampass) +{ + return (sampass->hours); +} + +const uint8 *pdb_get_nt_passwd(const struct samu *sampass) +{ + SMB_ASSERT((!sampass->nt_pw.data) + || sampass->nt_pw.length == NT_HASH_LEN); + return (uint8 *)sampass->nt_pw.data; +} + +const uint8 *pdb_get_lanman_passwd(const struct samu *sampass) +{ + SMB_ASSERT((!sampass->lm_pw.data) + || sampass->lm_pw.length == LM_HASH_LEN); + return (uint8 *)sampass->lm_pw.data; +} + +const uint8 *pdb_get_pw_history(const struct samu *sampass, uint32 *current_hist_len) +{ + SMB_ASSERT((!sampass->nt_pw_his.data) + || ((sampass->nt_pw_his.length % PW_HISTORY_ENTRY_LEN) == 0)); + *current_hist_len = sampass->nt_pw_his.length / PW_HISTORY_ENTRY_LEN; + return (uint8 *)sampass->nt_pw_his.data; +} + +/* Return the plaintext password if known. Most of the time + it isn't, so don't assume anything magic about this function. + + Used to pass the plaintext to passdb backends that might + want to store more than just the NTLM hashes. +*/ +const char *pdb_get_plaintext_passwd(const struct samu *sampass) +{ + return sampass->plaintext_pw; +} + +const DOM_SID *pdb_get_user_sid(const struct samu *sampass) +{ + return &sampass->user_sid; +} + +const DOM_SID *pdb_get_group_sid(struct samu *sampass) +{ + DOM_SID *gsid; + struct passwd *pwd; + + /* Return the cached group SID if we have that */ + if ( sampass->group_sid ) { + return sampass->group_sid; + } + + /* generate the group SID from the user's primary Unix group */ + + if ( !(gsid = TALLOC_P( sampass, DOM_SID )) ) { + return NULL; + } + + /* No algorithmic mapping, meaning that we have to figure out the + primary group SID according to group mapping and the user SID must + be a newly allocated one. We rely on the user's Unix primary gid. + We have no choice but to fail if we can't find it. */ + + if ( sampass->unix_pw ) { + pwd = sampass->unix_pw; + } else { + pwd = Get_Pwnam_alloc( sampass, pdb_get_username(sampass) ); + } + + if ( !pwd ) { + DEBUG(0,("pdb_get_group_sid: Failed to find Unix account for %s\n", pdb_get_username(sampass) )); + return NULL; + } + + if ( pdb_gid_to_sid(pwd->pw_gid, gsid) ) { + enum lsa_SidType type = SID_NAME_UNKNOWN; + TALLOC_CTX *mem_ctx = talloc_init("pdb_get_group_sid"); + bool lookup_ret; + + if (!mem_ctx) { + return NULL; + } + + /* Now check that it's actually a domain group and not something else */ + + lookup_ret = lookup_sid(mem_ctx, gsid, NULL, NULL, &type); + + TALLOC_FREE( mem_ctx ); + + if ( lookup_ret && (type == SID_NAME_DOM_GRP) ) { + sampass->group_sid = gsid; + return sampass->group_sid; + } + + DEBUG(3, ("Primary group for user %s is a %s and not a domain group\n", + pwd->pw_name, sid_type_lookup(type))); + } + + /* Just set it to the 'Domain Users' RID of 512 which will + always resolve to a name */ + + sid_copy( gsid, get_global_sam_sid() ); + sid_append_rid( gsid, DOMAIN_GROUP_RID_USERS ); + + sampass->group_sid = gsid; + + return sampass->group_sid; +} + +/** + * Get flags showing what is initalised in the struct samu + * @param sampass the struct samu in question + * @return the flags indicating the members initialised in the struct. + **/ + +enum pdb_value_state pdb_get_init_flags(const struct samu *sampass, enum pdb_elements element) +{ + enum pdb_value_state ret = PDB_DEFAULT; + + if (!sampass->change_flags || !sampass->set_flags) + return ret; + + if (bitmap_query(sampass->set_flags, element)) { + DEBUG(11, ("element %d: SET\n", element)); + ret = PDB_SET; + } + + if (bitmap_query(sampass->change_flags, element)) { + DEBUG(11, ("element %d: CHANGED\n", element)); + ret = PDB_CHANGED; + } + + if (ret == PDB_DEFAULT) { + DEBUG(11, ("element %d: DEFAULT\n", element)); + } + + return ret; +} + +const char *pdb_get_username(const struct samu *sampass) +{ + return sampass->username; +} + +const char *pdb_get_domain(const struct samu *sampass) +{ + return sampass->domain; +} + +const char *pdb_get_nt_username(const struct samu *sampass) +{ + return sampass->nt_username; +} + +const char *pdb_get_fullname(const struct samu *sampass) +{ + return sampass->full_name; +} + +const char *pdb_get_homedir(const struct samu *sampass) +{ + return sampass->home_dir; +} + +const char *pdb_get_dir_drive(const struct samu *sampass) +{ + return sampass->dir_drive; +} + +const char *pdb_get_logon_script(const struct samu *sampass) +{ + return sampass->logon_script; +} + +const char *pdb_get_profile_path(const struct samu *sampass) +{ + return sampass->profile_path; +} + +const char *pdb_get_acct_desc(const struct samu *sampass) +{ + return sampass->acct_desc; +} + +const char *pdb_get_workstations(const struct samu *sampass) +{ + return sampass->workstations; +} + +const char *pdb_get_comment(const struct samu *sampass) +{ + return sampass->comment; +} + +const char *pdb_get_munged_dial(const struct samu *sampass) +{ + return sampass->munged_dial; +} + +uint16 pdb_get_bad_password_count(const struct samu *sampass) +{ + return sampass->bad_password_count; +} + +uint16 pdb_get_logon_count(const struct samu *sampass) +{ + return sampass->logon_count; +} + +uint32 pdb_get_unknown_6(const struct samu *sampass) +{ + return sampass->unknown_6; +} + +void *pdb_get_backend_private_data(const struct samu *sampass, const struct pdb_methods *my_methods) +{ + if (my_methods == sampass->backend_private_methods) { + return sampass->backend_private_data; + } else { + return NULL; + } +} + +/********************************************************************* + Collection of set...() functions for struct samu. + ********************************************************************/ + +bool pdb_set_acct_ctrl(struct samu *sampass, uint32 acct_ctrl, enum pdb_value_state flag) +{ + sampass->acct_ctrl = acct_ctrl; + return pdb_set_init_flags(sampass, PDB_ACCTCTRL, flag); +} + +bool pdb_set_logon_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag) +{ + sampass->logon_time = mytime; + return pdb_set_init_flags(sampass, PDB_LOGONTIME, flag); +} + +bool pdb_set_logoff_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag) +{ + sampass->logoff_time = mytime; + return pdb_set_init_flags(sampass, PDB_LOGOFFTIME, flag); +} + +bool pdb_set_kickoff_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag) +{ + sampass->kickoff_time = mytime; + return pdb_set_init_flags(sampass, PDB_KICKOFFTIME, flag); +} + +bool pdb_set_bad_password_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag) +{ + sampass->bad_password_time = mytime; + return pdb_set_init_flags(sampass, PDB_BAD_PASSWORD_TIME, flag); +} + +bool pdb_set_pass_can_change_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag) +{ + sampass->pass_can_change_time = mytime; + return pdb_set_init_flags(sampass, PDB_CANCHANGETIME, flag); +} + +bool pdb_set_pass_must_change_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag) +{ + sampass->pass_must_change_time = mytime; + return pdb_set_init_flags(sampass, PDB_MUSTCHANGETIME, flag); +} + +bool pdb_set_pass_last_set_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag) +{ + sampass->pass_last_set_time = mytime; + return pdb_set_init_flags(sampass, PDB_PASSLASTSET, flag); +} + +bool pdb_set_hours_len(struct samu *sampass, uint32 len, enum pdb_value_state flag) +{ + sampass->hours_len = len; + return pdb_set_init_flags(sampass, PDB_HOURSLEN, flag); +} + +bool pdb_set_logon_divs(struct samu *sampass, uint16 hours, enum pdb_value_state flag) +{ + sampass->logon_divs = hours; + return pdb_set_init_flags(sampass, PDB_LOGONDIVS, flag); +} + +/** + * Set flags showing what is initalised in the struct samu + * @param sampass the struct samu in question + * @param flag The *new* flag to be set. Old flags preserved + * this flag is only added. + **/ + +bool pdb_set_init_flags(struct samu *sampass, enum pdb_elements element, enum pdb_value_state value_flag) +{ + if (!sampass->set_flags) { + if ((sampass->set_flags = + bitmap_talloc(sampass, + PDB_COUNT))==NULL) { + DEBUG(0,("bitmap_talloc failed\n")); + return False; + } + } + if (!sampass->change_flags) { + if ((sampass->change_flags = + bitmap_talloc(sampass, + PDB_COUNT))==NULL) { + DEBUG(0,("bitmap_talloc failed\n")); + return False; + } + } + + switch(value_flag) { + case PDB_CHANGED: + if (!bitmap_set(sampass->change_flags, element)) { + DEBUG(0,("Can't set flag: %d in change_flags.\n",element)); + return False; + } + if (!bitmap_set(sampass->set_flags, element)) { + DEBUG(0,("Can't set flag: %d in set_flags.\n",element)); + return False; + } + DEBUG(11, ("element %d -> now CHANGED\n", element)); + break; + case PDB_SET: + if (!bitmap_clear(sampass->change_flags, element)) { + DEBUG(0,("Can't set flag: %d in change_flags.\n",element)); + return False; + } + if (!bitmap_set(sampass->set_flags, element)) { + DEBUG(0,("Can't set flag: %d in set_flags.\n",element)); + return False; + } + DEBUG(11, ("element %d -> now SET\n", element)); + break; + case PDB_DEFAULT: + default: + if (!bitmap_clear(sampass->change_flags, element)) { + DEBUG(0,("Can't set flag: %d in change_flags.\n",element)); + return False; + } + if (!bitmap_clear(sampass->set_flags, element)) { + DEBUG(0,("Can't set flag: %d in set_flags.\n",element)); + return False; + } + DEBUG(11, ("element %d -> now DEFAULT\n", element)); + break; + } + + return True; +} + +bool pdb_set_user_sid(struct samu *sampass, const DOM_SID *u_sid, enum pdb_value_state flag) +{ + if (!u_sid) + return False; + + sid_copy(&sampass->user_sid, u_sid); + + DEBUG(10, ("pdb_set_user_sid: setting user sid %s\n", + sid_string_dbg(&sampass->user_sid))); + + return pdb_set_init_flags(sampass, PDB_USERSID, flag); +} + +bool pdb_set_user_sid_from_string(struct samu *sampass, fstring u_sid, enum pdb_value_state flag) +{ + DOM_SID new_sid; + + if (!u_sid) + return False; + + DEBUG(10, ("pdb_set_user_sid_from_string: setting user sid %s\n", + u_sid)); + + if (!string_to_sid(&new_sid, u_sid)) { + DEBUG(1, ("pdb_set_user_sid_from_string: %s isn't a valid SID!\n", u_sid)); + return False; + } + + if (!pdb_set_user_sid(sampass, &new_sid, flag)) { + DEBUG(1, ("pdb_set_user_sid_from_string: could not set sid %s on struct samu!\n", u_sid)); + return False; + } + + return True; +} + +/******************************************************************** + We never fill this in from a passdb backend but rather set is + based on the user's primary group membership. However, the + struct samu* is overloaded and reused in domain memship code + as well and built from the netr_SamInfo3 or PAC so we + have to allow the explicitly setting of a group SID here. +********************************************************************/ + +bool pdb_set_group_sid(struct samu *sampass, const DOM_SID *g_sid, enum pdb_value_state flag) +{ + gid_t gid; + + if (!g_sid) + return False; + + if ( !(sampass->group_sid = TALLOC_P( sampass, DOM_SID )) ) { + return False; + } + + /* if we cannot resolve the SID to gid, then just ignore it and + store DOMAIN_USERS as the primary groupSID */ + + if ( sid_to_gid( g_sid, &gid ) ) { + sid_copy(sampass->group_sid, g_sid); + } else { + sid_copy( sampass->group_sid, get_global_sam_sid() ); + sid_append_rid( sampass->group_sid, DOMAIN_GROUP_RID_USERS ); + } + + DEBUG(10, ("pdb_set_group_sid: setting group sid %s\n", + sid_string_dbg(sampass->group_sid))); + + return pdb_set_init_flags(sampass, PDB_GROUPSID, flag); +} + +/********************************************************************* + Set the user's UNIX name. + ********************************************************************/ + +bool pdb_set_username(struct samu *sampass, const char *username, enum pdb_value_state flag) +{ + if (username) { + DEBUG(10, ("pdb_set_username: setting username %s, was %s\n", username, + (sampass->username)?(sampass->username):"NULL")); + + sampass->username = talloc_strdup(sampass, username); + + if (!sampass->username) { + DEBUG(0, ("pdb_set_username: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->username = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_USERNAME, flag); +} + +/********************************************************************* + Set the domain name. + ********************************************************************/ + +bool pdb_set_domain(struct samu *sampass, const char *domain, enum pdb_value_state flag) +{ + if (domain) { + DEBUG(10, ("pdb_set_domain: setting domain %s, was %s\n", domain, + (sampass->domain)?(sampass->domain):"NULL")); + + sampass->domain = talloc_strdup(sampass, domain); + + if (!sampass->domain) { + DEBUG(0, ("pdb_set_domain: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->domain = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_DOMAIN, flag); +} + +/********************************************************************* + Set the user's NT name. + ********************************************************************/ + +bool pdb_set_nt_username(struct samu *sampass, const char *nt_username, enum pdb_value_state flag) +{ + if (nt_username) { + DEBUG(10, ("pdb_set_nt_username: setting nt username %s, was %s\n", nt_username, + (sampass->nt_username)?(sampass->nt_username):"NULL")); + + sampass->nt_username = talloc_strdup(sampass, nt_username); + + if (!sampass->nt_username) { + DEBUG(0, ("pdb_set_nt_username: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->nt_username = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_NTUSERNAME, flag); +} + +/********************************************************************* + Set the user's full name. + ********************************************************************/ + +bool pdb_set_fullname(struct samu *sampass, const char *full_name, enum pdb_value_state flag) +{ + if (full_name) { + DEBUG(10, ("pdb_set_full_name: setting full name %s, was %s\n", full_name, + (sampass->full_name)?(sampass->full_name):"NULL")); + + sampass->full_name = talloc_strdup(sampass, full_name); + + if (!sampass->full_name) { + DEBUG(0, ("pdb_set_fullname: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->full_name = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_FULLNAME, flag); +} + +/********************************************************************* + Set the user's logon script. + ********************************************************************/ + +bool pdb_set_logon_script(struct samu *sampass, const char *logon_script, enum pdb_value_state flag) +{ + if (logon_script) { + DEBUG(10, ("pdb_set_logon_script: setting logon script %s, was %s\n", logon_script, + (sampass->logon_script)?(sampass->logon_script):"NULL")); + + sampass->logon_script = talloc_strdup(sampass, logon_script); + + if (!sampass->logon_script) { + DEBUG(0, ("pdb_set_logon_script: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->logon_script = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_LOGONSCRIPT, flag); +} + +/********************************************************************* + Set the user's profile path. + ********************************************************************/ + +bool pdb_set_profile_path(struct samu *sampass, const char *profile_path, enum pdb_value_state flag) +{ + if (profile_path) { + DEBUG(10, ("pdb_set_profile_path: setting profile path %s, was %s\n", profile_path, + (sampass->profile_path)?(sampass->profile_path):"NULL")); + + sampass->profile_path = talloc_strdup(sampass, profile_path); + + if (!sampass->profile_path) { + DEBUG(0, ("pdb_set_profile_path: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->profile_path = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_PROFILE, flag); +} + +/********************************************************************* + Set the user's directory drive. + ********************************************************************/ + +bool pdb_set_dir_drive(struct samu *sampass, const char *dir_drive, enum pdb_value_state flag) +{ + if (dir_drive) { + DEBUG(10, ("pdb_set_dir_drive: setting dir drive %s, was %s\n", dir_drive, + (sampass->dir_drive)?(sampass->dir_drive):"NULL")); + + sampass->dir_drive = talloc_strdup(sampass, dir_drive); + + if (!sampass->dir_drive) { + DEBUG(0, ("pdb_set_dir_drive: talloc_strdup() failed!\n")); + return False; + } + + } else { + sampass->dir_drive = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_DRIVE, flag); +} + +/********************************************************************* + Set the user's home directory. + ********************************************************************/ + +bool pdb_set_homedir(struct samu *sampass, const char *home_dir, enum pdb_value_state flag) +{ + if (home_dir) { + DEBUG(10, ("pdb_set_homedir: setting home dir %s, was %s\n", home_dir, + (sampass->home_dir)?(sampass->home_dir):"NULL")); + + sampass->home_dir = talloc_strdup(sampass, home_dir); + + if (!sampass->home_dir) { + DEBUG(0, ("pdb_set_home_dir: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->home_dir = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_SMBHOME, flag); +} + +/********************************************************************* + Set the user's account description. + ********************************************************************/ + +bool pdb_set_acct_desc(struct samu *sampass, const char *acct_desc, enum pdb_value_state flag) +{ + if (acct_desc) { + sampass->acct_desc = talloc_strdup(sampass, acct_desc); + + if (!sampass->acct_desc) { + DEBUG(0, ("pdb_set_acct_desc: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->acct_desc = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_ACCTDESC, flag); +} + +/********************************************************************* + Set the user's workstation allowed list. + ********************************************************************/ + +bool pdb_set_workstations(struct samu *sampass, const char *workstations, enum pdb_value_state flag) +{ + if (workstations) { + DEBUG(10, ("pdb_set_workstations: setting workstations %s, was %s\n", workstations, + (sampass->workstations)?(sampass->workstations):"NULL")); + + sampass->workstations = talloc_strdup(sampass, workstations); + + if (!sampass->workstations) { + DEBUG(0, ("pdb_set_workstations: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->workstations = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_WORKSTATIONS, flag); +} + +/********************************************************************* + ********************************************************************/ + +bool pdb_set_comment(struct samu *sampass, const char *comment, enum pdb_value_state flag) +{ + if (comment) { + sampass->comment = talloc_strdup(sampass, comment); + + if (!sampass->comment) { + DEBUG(0, ("pdb_set_comment: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->comment = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_COMMENT, flag); +} + +/********************************************************************* + Set the user's dial string. + ********************************************************************/ + +bool pdb_set_munged_dial(struct samu *sampass, const char *munged_dial, enum pdb_value_state flag) +{ + if (munged_dial) { + sampass->munged_dial = talloc_strdup(sampass, munged_dial); + + if (!sampass->munged_dial) { + DEBUG(0, ("pdb_set_munged_dial: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->munged_dial = PDB_NOT_QUITE_NULL; + } + + return pdb_set_init_flags(sampass, PDB_MUNGEDDIAL, flag); +} + +/********************************************************************* + Set the user's NT hash. + ********************************************************************/ + +bool pdb_set_nt_passwd(struct samu *sampass, const uint8 pwd[NT_HASH_LEN], enum pdb_value_state flag) +{ + data_blob_clear_free(&sampass->nt_pw); + + if (pwd) { + sampass->nt_pw = + data_blob_talloc(sampass, pwd, NT_HASH_LEN); + } else { + sampass->nt_pw = data_blob_null; + } + + return pdb_set_init_flags(sampass, PDB_NTPASSWD, flag); +} + +/********************************************************************* + Set the user's LM hash. + ********************************************************************/ + +bool pdb_set_lanman_passwd(struct samu *sampass, const uint8 pwd[LM_HASH_LEN], enum pdb_value_state flag) +{ + data_blob_clear_free(&sampass->lm_pw); + + /* on keep the password if we are allowing LANMAN authentication */ + + if (pwd && lp_lanman_auth() ) { + sampass->lm_pw = data_blob_talloc(sampass, pwd, LM_HASH_LEN); + } else { + sampass->lm_pw = data_blob_null; + } + + return pdb_set_init_flags(sampass, PDB_LMPASSWD, flag); +} + +/********************************************************************* + Set the user's password history hash. historyLen is the number of + PW_HISTORY_SALT_LEN+SALTED_MD5_HASH_LEN length + entries to store in the history - this must match the size of the uint8 array + in pwd. +********************************************************************/ + +bool pdb_set_pw_history(struct samu *sampass, const uint8 *pwd, uint32 historyLen, enum pdb_value_state flag) +{ + if (historyLen && pwd){ + sampass->nt_pw_his = data_blob_talloc(sampass, + pwd, historyLen*PW_HISTORY_ENTRY_LEN); + if (!sampass->nt_pw_his.length) { + DEBUG(0, ("pdb_set_pw_history: data_blob_talloc() failed!\n")); + return False; + } + } else { + sampass->nt_pw_his = data_blob_talloc(sampass, NULL, 0); + } + + return pdb_set_init_flags(sampass, PDB_PWHISTORY, flag); +} + +/********************************************************************* + Set the user's plaintext password only (base procedure, see helper + below) + ********************************************************************/ + +bool pdb_set_plaintext_pw_only(struct samu *sampass, const char *password, enum pdb_value_state flag) +{ + if (password) { + if (sampass->plaintext_pw!=NULL) + memset(sampass->plaintext_pw,'\0',strlen(sampass->plaintext_pw)+1); + + sampass->plaintext_pw = talloc_strdup(sampass, password); + + if (!sampass->plaintext_pw) { + DEBUG(0, ("pdb_set_unknown_str: talloc_strdup() failed!\n")); + return False; + } + } else { + sampass->plaintext_pw = NULL; + } + + return pdb_set_init_flags(sampass, PDB_PLAINTEXT_PW, flag); +} + +bool pdb_set_bad_password_count(struct samu *sampass, uint16 bad_password_count, enum pdb_value_state flag) +{ + sampass->bad_password_count = bad_password_count; + return pdb_set_init_flags(sampass, PDB_BAD_PASSWORD_COUNT, flag); +} + +bool pdb_set_logon_count(struct samu *sampass, uint16 logon_count, enum pdb_value_state flag) +{ + sampass->logon_count = logon_count; + return pdb_set_init_flags(sampass, PDB_LOGON_COUNT, flag); +} + +bool pdb_set_unknown_6(struct samu *sampass, uint32 unkn, enum pdb_value_state flag) +{ + sampass->unknown_6 = unkn; + return pdb_set_init_flags(sampass, PDB_UNKNOWN6, flag); +} + +bool pdb_set_hours(struct samu *sampass, const uint8 *hours, enum pdb_value_state flag) +{ + if (!hours) { + memset ((char *)sampass->hours, 0, MAX_HOURS_LEN); + } else { + memcpy (sampass->hours, hours, MAX_HOURS_LEN); + } + + return pdb_set_init_flags(sampass, PDB_HOURS, flag); +} + +bool pdb_set_backend_private_data(struct samu *sampass, void *private_data, + void (*free_fn)(void **), + const struct pdb_methods *my_methods, + enum pdb_value_state flag) +{ + if (sampass->backend_private_data && + sampass->backend_private_data_free_fn) { + sampass->backend_private_data_free_fn( + &sampass->backend_private_data); + } + + sampass->backend_private_data = private_data; + sampass->backend_private_data_free_fn = free_fn; + sampass->backend_private_methods = my_methods; + + return pdb_set_init_flags(sampass, PDB_BACKEND_PRIVATE_DATA, flag); +} + + +/* Helpful interfaces to the above */ + +bool pdb_set_pass_can_change(struct samu *sampass, bool canchange) +{ + return pdb_set_pass_can_change_time(sampass, + canchange ? 0 : get_time_t_max(), + PDB_CHANGED); +} + + +/********************************************************************* + Set the user's PLAINTEXT password. Used as an interface to the above. + Also sets the last change time to NOW. + ********************************************************************/ + +bool pdb_set_plaintext_passwd(struct samu *sampass, const char *plaintext) +{ + uchar new_lanman_p16[LM_HASH_LEN]; + uchar new_nt_p16[NT_HASH_LEN]; + + if (!plaintext) + return False; + + /* Calculate the MD4 hash (NT compatible) of the password */ + E_md4hash(plaintext, new_nt_p16); + + if (!pdb_set_nt_passwd (sampass, new_nt_p16, PDB_CHANGED)) + return False; + + if (!E_deshash(plaintext, new_lanman_p16)) { + /* E_deshash returns false for 'long' passwords (> 14 + DOS chars). This allows us to match Win2k, which + does not store a LM hash for these passwords (which + would reduce the effective password length to 14 */ + + if (!pdb_set_lanman_passwd (sampass, NULL, PDB_CHANGED)) + return False; + } else { + if (!pdb_set_lanman_passwd (sampass, new_lanman_p16, PDB_CHANGED)) + return False; + } + + if (!pdb_set_plaintext_pw_only (sampass, plaintext, PDB_CHANGED)) + return False; + + if (!pdb_set_pass_last_set_time (sampass, time(NULL), PDB_CHANGED)) + return False; + + /* Store the password history. */ + if (pdb_get_acct_ctrl(sampass) & ACB_NORMAL) { + uchar *pwhistory; + uint32 pwHistLen; + pdb_get_account_policy(AP_PASSWORD_HISTORY, &pwHistLen); + if (pwHistLen != 0){ + uint32 current_history_len; + /* We need to make sure we don't have a race condition here - the + account policy history length can change between when the pw_history + was first loaded into the struct samu struct and now.... JRA. */ + pwhistory = (uchar *)pdb_get_pw_history(sampass, ¤t_history_len); + + if (current_history_len != pwHistLen) { + /* After closing and reopening struct samu the history + values will sync up. We can't do this here. */ + + /* current_history_len > pwHistLen is not a problem - we + have more history than we need. */ + + if (current_history_len < pwHistLen) { + /* Ensure we have space for the needed history. */ + uchar *new_history = (uchar *)TALLOC(sampass, + pwHistLen*PW_HISTORY_ENTRY_LEN); + if (!new_history) { + return False; + } + + /* And copy it into the new buffer. */ + if (current_history_len) { + memcpy(new_history, pwhistory, + current_history_len*PW_HISTORY_ENTRY_LEN); + } + /* Clearing out any extra space. */ + memset(&new_history[current_history_len*PW_HISTORY_ENTRY_LEN], + '\0', (pwHistLen-current_history_len)*PW_HISTORY_ENTRY_LEN); + /* Finally replace it. */ + pwhistory = new_history; + } + } + if (pwhistory && pwHistLen){ + /* Make room for the new password in the history list. */ + if (pwHistLen > 1) { + memmove(&pwhistory[PW_HISTORY_ENTRY_LEN], + pwhistory, (pwHistLen -1)*PW_HISTORY_ENTRY_LEN ); + } + /* Create the new salt as the first part of the history entry. */ + generate_random_buffer(pwhistory, PW_HISTORY_SALT_LEN); + + /* Generate the md5 hash of the salt+new password as the second + part of the history entry. */ + + E_md5hash(pwhistory, new_nt_p16, &pwhistory[PW_HISTORY_SALT_LEN]); + pdb_set_pw_history(sampass, pwhistory, pwHistLen, PDB_CHANGED); + } else { + DEBUG (10,("pdb_get_set.c: pdb_set_plaintext_passwd: pwhistory was NULL!\n")); + } + } else { + /* Set the history length to zero. */ + pdb_set_pw_history(sampass, NULL, 0, PDB_CHANGED); + } + } + + return True; +} + +/* check for any PDB_SET/CHANGED field and fill the appropriate mask bit */ +uint32 pdb_build_fields_present(struct samu *sampass) +{ + /* value set to all for testing */ + return 0x00ffffff; +} diff --git a/source3/passdb/pdb_interface.c b/source3/passdb/pdb_interface.c new file mode 100644 index 0000000000..2a1024cc56 --- /dev/null +++ b/source3/passdb/pdb_interface.c @@ -0,0 +1,2071 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Andrew Bartlett 2002 + Copyright (C) Jelmer Vernooij 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +static_decl_pdb; + +static struct pdb_init_function_entry *backends = NULL; + +static void lazy_initialize_passdb(void) +{ + static bool initialized = False; + if(initialized) { + return; + } + static_init_pdb; + initialized = True; +} + +static bool lookup_global_sam_rid(TALLOC_CTX *mem_ctx, uint32 rid, + const char **name, + enum lsa_SidType *psid_name_use, + union unid_t *unix_id); + +NTSTATUS smb_register_passdb(int version, const char *name, pdb_init_function init) +{ + struct pdb_init_function_entry *entry = backends; + + if(version != PASSDB_INTERFACE_VERSION) { + DEBUG(0,("Can't register passdb backend!\n" + "You tried to register a passdb module with PASSDB_INTERFACE_VERSION %d, " + "while this version of samba uses version %d\n", + version,PASSDB_INTERFACE_VERSION)); + return NT_STATUS_OBJECT_TYPE_MISMATCH; + } + + if (!name || !init) { + return NT_STATUS_INVALID_PARAMETER; + } + + DEBUG(5,("Attempting to register passdb backend %s\n", name)); + + /* Check for duplicates */ + if (pdb_find_backend_entry(name)) { + DEBUG(0,("There already is a passdb backend registered with the name %s!\n", name)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + entry = SMB_XMALLOC_P(struct pdb_init_function_entry); + entry->name = smb_xstrdup(name); + entry->init = init; + + DLIST_ADD(backends, entry); + DEBUG(5,("Successfully added passdb backend '%s'\n", name)); + return NT_STATUS_OK; +} + +struct pdb_init_function_entry *pdb_find_backend_entry(const char *name) +{ + struct pdb_init_function_entry *entry = backends; + + while(entry) { + if (strcmp(entry->name, name)==0) return entry; + entry = entry->next; + } + + return NULL; +} + +/* + * The event context for the passdb backend. I know this is a bad hack and yet + * another static variable, but our pdb API is a global thing per + * definition. The first use for this is the LDAP idle function, more might be + * added later. + * + * I don't feel too bad about this static variable, it replaces the + * smb_idle_event_list that used to exist in lib/module.c. -- VL + */ + +static struct event_context *pdb_event_ctx; + +struct event_context *pdb_get_event_context(void) +{ + return pdb_event_ctx; +} + +/****************************************************************** + Make a pdb_methods from scratch + *******************************************************************/ + +NTSTATUS make_pdb_method_name(struct pdb_methods **methods, const char *selected) +{ + char *module_name = smb_xstrdup(selected); + char *module_location = NULL, *p; + struct pdb_init_function_entry *entry; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + + lazy_initialize_passdb(); + + p = strchr(module_name, ':'); + + if (p) { + *p = 0; + module_location = p+1; + trim_char(module_location, ' ', ' '); + } + + trim_char(module_name, ' ', ' '); + + + DEBUG(5,("Attempting to find a passdb backend to match %s (%s)\n", selected, module_name)); + + entry = pdb_find_backend_entry(module_name); + + /* Try to find a module that contains this module */ + if (!entry) { + DEBUG(2,("No builtin backend found, trying to load plugin\n")); + if(NT_STATUS_IS_OK(smb_probe_module("pdb", module_name)) && !(entry = pdb_find_backend_entry(module_name))) { + DEBUG(0,("Plugin is available, but doesn't register passdb backend %s\n", module_name)); + SAFE_FREE(module_name); + return NT_STATUS_UNSUCCESSFUL; + } + } + + /* No such backend found */ + if(!entry) { + DEBUG(0,("No builtin nor plugin backend for %s found\n", module_name)); + SAFE_FREE(module_name); + return NT_STATUS_INVALID_PARAMETER; + } + + DEBUG(5,("Found pdb backend %s\n", module_name)); + + if ( !NT_STATUS_IS_OK( nt_status = entry->init(methods, module_location) ) ) { + DEBUG(0,("pdb backend %s did not correctly init (error was %s)\n", + selected, nt_errstr(nt_status))); + SAFE_FREE(module_name); + return nt_status; + } + + SAFE_FREE(module_name); + + DEBUG(5,("pdb backend %s has a valid init\n", selected)); + + return nt_status; +} + +/****************************************************************** + Return an already initialized pdb_methods structure +*******************************************************************/ + +static struct pdb_methods *pdb_get_methods_reload( bool reload ) +{ + static struct pdb_methods *pdb = NULL; + + if ( pdb && reload ) { + pdb->free_private_data( &(pdb->private_data) ); + if ( !NT_STATUS_IS_OK( make_pdb_method_name( &pdb, lp_passdb_backend() ) ) ) { + char *msg = NULL; + asprintf(&msg, "pdb_get_methods_reload: " + "failed to get pdb methods for backend %s\n", + lp_passdb_backend()); + smb_panic(msg); + } + } + + if ( !pdb ) { + if ( !NT_STATUS_IS_OK( make_pdb_method_name( &pdb, lp_passdb_backend() ) ) ) { + char *msg = NULL; + asprintf(&msg, "pdb_get_methods_reload: " + "failed to get pdb methods for backend %s\n", + lp_passdb_backend()); + smb_panic(msg); + } + } + + return pdb; +} + +static struct pdb_methods *pdb_get_methods(void) +{ + return pdb_get_methods_reload(False); +} + +bool pdb_getsampwnam(struct samu *sam_acct, const char *username) +{ + struct pdb_methods *pdb = pdb_get_methods(); + struct samu *cache_copy; + const struct dom_sid *user_sid; + + if (!NT_STATUS_IS_OK(pdb->getsampwnam(pdb, sam_acct, username))) { + return False; + } + + cache_copy = samu_new(NULL); + if (cache_copy == NULL) { + return False; + } + + if (!pdb_copy_sam_account(cache_copy, sam_acct)) { + TALLOC_FREE(cache_copy); + return False; + } + + user_sid = pdb_get_user_sid(cache_copy); + + memcache_add_talloc(NULL, PDB_GETPWSID_CACHE, + data_blob_const(user_sid, sizeof(*user_sid)), + cache_copy); + + return True; +} + +/********************************************************************** +**********************************************************************/ + +bool guest_user_info( struct samu *user ) +{ + struct passwd *pwd; + NTSTATUS result; + const char *guestname = lp_guestaccount(); + + if ( !(pwd = getpwnam_alloc( NULL, guestname ) ) ) { + DEBUG(0,("guest_user_info: Unable to locate guest account [%s]!\n", + guestname)); + return False; + } + + result = samu_set_unix(user, pwd ); + + TALLOC_FREE( pwd ); + + return NT_STATUS_IS_OK( result ); +} + +/********************************************************************** +**********************************************************************/ + +bool pdb_getsampwsid(struct samu *sam_acct, const DOM_SID *sid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + uint32 rid; + void *cache_data; + + /* hard code the Guest RID of 501 */ + + if ( !sid_peek_check_rid( get_global_sam_sid(), sid, &rid ) ) + return False; + + if ( rid == DOMAIN_USER_RID_GUEST ) { + DEBUG(6,("pdb_getsampwsid: Building guest account\n")); + return guest_user_info( sam_acct ); + } + + /* check the cache first */ + + cache_data = memcache_lookup_talloc( + NULL, PDB_GETPWSID_CACHE, data_blob_const(sid, sizeof(*sid))); + + if (cache_data != NULL) { + struct samu *cache_copy = talloc_get_type_abort( + cache_data, struct samu); + + return pdb_copy_sam_account(sam_acct, cache_copy); + } + + return NT_STATUS_IS_OK(pdb->getsampwsid(pdb, sam_acct, sid)); +} + +static NTSTATUS pdb_default_create_user(struct pdb_methods *methods, + TALLOC_CTX *tmp_ctx, const char *name, + uint32 acb_info, uint32 *rid) +{ + struct samu *sam_pass; + NTSTATUS status; + struct passwd *pwd; + + if ((sam_pass = samu_new(tmp_ctx)) == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if ( !(pwd = Get_Pwnam_alloc(tmp_ctx, name)) ) { + char *add_script = NULL; + int add_ret; + fstring name2; + + if ((acb_info & ACB_NORMAL) && name[strlen(name)-1] != '$') { + add_script = talloc_strdup(tmp_ctx, + lp_adduser_script()); + } else { + add_script = talloc_strdup(tmp_ctx, + lp_addmachine_script()); + } + + if (!add_script || add_script[0] == '\0') { + DEBUG(3, ("Could not find user %s and no add script " + "defined\n", name)); + return NT_STATUS_NO_SUCH_USER; + } + + /* lowercase the username before creating the Unix account for + compatibility with previous Samba releases */ + fstrcpy( name2, name ); + strlower_m( name2 ); + add_script = talloc_all_string_sub(tmp_ctx, + add_script, + "%u", + name2); + if (!add_script) { + return NT_STATUS_NO_MEMORY; + } + add_ret = smbrun(add_script,NULL); + DEBUG(add_ret ? 0 : 3, ("_samr_create_user: Running the command `%s' gave %d\n", + add_script, add_ret)); + if (add_ret == 0) { + smb_nscd_flush_user_cache(); + } + + flush_pwnam_cache(); + + pwd = Get_Pwnam_alloc(tmp_ctx, name); + } + + /* we have a valid SID coming out of this call */ + + status = samu_alloc_rid_unix( sam_pass, pwd ); + + TALLOC_FREE( pwd ); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("pdb_default_create_user: failed to create a new user structure: %s\n", nt_errstr(status))); + return status; + } + + if (!sid_peek_check_rid(get_global_sam_sid(), + pdb_get_user_sid(sam_pass), rid)) { + DEBUG(0, ("Could not get RID of fresh user\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + /* Use the username case specified in the original request */ + + pdb_set_username( sam_pass, name, PDB_SET ); + + /* Disable the account on creation, it does not have a reasonable password yet. */ + + acb_info |= ACB_DISABLED; + + pdb_set_acct_ctrl(sam_pass, acb_info, PDB_CHANGED); + + status = pdb_add_sam_account(sam_pass); + + TALLOC_FREE(sam_pass); + + return status; +} + +NTSTATUS pdb_create_user(TALLOC_CTX *mem_ctx, const char *name, uint32 flags, + uint32 *rid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->create_user(pdb, mem_ctx, name, flags, rid); +} + +/**************************************************************************** + Delete a UNIX user on demand. +****************************************************************************/ + +static int smb_delete_user(const char *unix_user) +{ + char *del_script = NULL; + int ret; + + /* safety check */ + + if ( strequal( unix_user, "root" ) ) { + DEBUG(0,("smb_delete_user: Refusing to delete local system root account!\n")); + return -1; + } + + del_script = talloc_strdup(talloc_tos(), lp_deluser_script()); + if (!del_script || !*del_script) { + return -1; + } + del_script = talloc_all_string_sub(talloc_tos(), + del_script, + "%u", + unix_user); + if (!del_script) { + return -1; + } + ret = smbrun(del_script,NULL); + flush_pwnam_cache(); + if (ret == 0) { + smb_nscd_flush_user_cache(); + } + DEBUG(ret ? 0 : 3,("smb_delete_user: Running the command `%s' gave %d\n",del_script,ret)); + + return ret; +} + +static NTSTATUS pdb_default_delete_user(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + struct samu *sam_acct) +{ + NTSTATUS status; + fstring username; + + status = pdb_delete_sam_account(sam_acct); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Now delete the unix side .... + * note: we don't check if the delete really happened as the script is + * not necessary present and maybe the sysadmin doesn't want to delete + * the unix side + */ + + /* always lower case the username before handing it off to + external scripts */ + + fstrcpy( username, pdb_get_username(sam_acct) ); + strlower_m( username ); + + smb_delete_user( username ); + + return status; +} + +NTSTATUS pdb_delete_user(TALLOC_CTX *mem_ctx, struct samu *sam_acct) +{ + struct pdb_methods *pdb = pdb_get_methods(); + uid_t uid = -1; + + /* sanity check to make sure we don't delete root */ + + if ( !sid_to_uid( pdb_get_user_sid(sam_acct), &uid ) ) { + return NT_STATUS_NO_SUCH_USER; + } + + if ( uid == 0 ) { + return NT_STATUS_ACCESS_DENIED; + } + + return pdb->delete_user(pdb, mem_ctx, sam_acct); +} + +NTSTATUS pdb_add_sam_account(struct samu *sam_acct) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->add_sam_account(pdb, sam_acct); +} + +NTSTATUS pdb_update_sam_account(struct samu *sam_acct) +{ + struct pdb_methods *pdb = pdb_get_methods(); + + memcache_flush(NULL, PDB_GETPWSID_CACHE); + + return pdb->update_sam_account(pdb, sam_acct); +} + +NTSTATUS pdb_delete_sam_account(struct samu *sam_acct) +{ + struct pdb_methods *pdb = pdb_get_methods(); + + memcache_flush(NULL, PDB_GETPWSID_CACHE); + + return pdb->delete_sam_account(pdb, sam_acct); +} + +NTSTATUS pdb_rename_sam_account(struct samu *oldname, const char *newname) +{ + struct pdb_methods *pdb = pdb_get_methods(); + uid_t uid; + NTSTATUS status; + + memcache_flush(NULL, PDB_GETPWSID_CACHE); + + /* sanity check to make sure we don't rename root */ + + if ( !sid_to_uid( pdb_get_user_sid(oldname), &uid ) ) { + return NT_STATUS_NO_SUCH_USER; + } + + if ( uid == 0 ) { + return NT_STATUS_ACCESS_DENIED; + } + + status = pdb->rename_sam_account(pdb, oldname, newname); + + /* always flush the cache here just to be safe */ + flush_pwnam_cache(); + + return status; +} + +NTSTATUS pdb_update_login_attempts(struct samu *sam_acct, bool success) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->update_login_attempts(pdb, sam_acct, success); +} + +bool pdb_getgrsid(GROUP_MAP *map, DOM_SID sid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return NT_STATUS_IS_OK(pdb->getgrsid(pdb, map, sid)); +} + +bool pdb_getgrgid(GROUP_MAP *map, gid_t gid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return NT_STATUS_IS_OK(pdb->getgrgid(pdb, map, gid)); +} + +bool pdb_getgrnam(GROUP_MAP *map, const char *name) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return NT_STATUS_IS_OK(pdb->getgrnam(pdb, map, name)); +} + +static NTSTATUS pdb_default_create_dom_group(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + const char *name, + uint32 *rid) +{ + DOM_SID group_sid; + struct group *grp; + fstring tmp; + + grp = getgrnam(name); + + if (grp == NULL) { + gid_t gid; + + if (smb_create_group(name, &gid) != 0) { + return NT_STATUS_ACCESS_DENIED; + } + + grp = getgrgid(gid); + } + + if (grp == NULL) { + return NT_STATUS_ACCESS_DENIED; + } + + if (pdb_rid_algorithm()) { + *rid = algorithmic_pdb_gid_to_group_rid( grp->gr_gid ); + } else { + if (!pdb_new_rid(rid)) { + return NT_STATUS_ACCESS_DENIED; + } + } + + sid_compose(&group_sid, get_global_sam_sid(), *rid); + + return add_initial_entry(grp->gr_gid, sid_to_fstring(tmp, &group_sid), + SID_NAME_DOM_GRP, name, NULL); +} + +NTSTATUS pdb_create_dom_group(TALLOC_CTX *mem_ctx, const char *name, + uint32 *rid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->create_dom_group(pdb, mem_ctx, name, rid); +} + +static NTSTATUS pdb_default_delete_dom_group(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + uint32 rid) +{ + DOM_SID group_sid; + GROUP_MAP map; + NTSTATUS status; + struct group *grp; + const char *grp_name; + + sid_compose(&group_sid, get_global_sam_sid(), rid); + + if (!get_domain_group_from_sid(group_sid, &map)) { + DEBUG(10, ("Could not find group for rid %d\n", rid)); + return NT_STATUS_NO_SUCH_GROUP; + } + + /* We need the group name for the smb_delete_group later on */ + + if (map.gid == (gid_t)-1) { + return NT_STATUS_NO_SUCH_GROUP; + } + + grp = getgrgid(map.gid); + if (grp == NULL) { + return NT_STATUS_NO_SUCH_GROUP; + } + + /* Copy the name, no idea what pdb_delete_group_mapping_entry does.. */ + + grp_name = talloc_strdup(mem_ctx, grp->gr_name); + if (grp_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pdb_delete_group_mapping_entry(group_sid); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Don't check the result of smb_delete_group */ + + smb_delete_group(grp_name); + + return NT_STATUS_OK; +} + +NTSTATUS pdb_delete_dom_group(TALLOC_CTX *mem_ctx, uint32 rid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->delete_dom_group(pdb, mem_ctx, rid); +} + +NTSTATUS pdb_add_group_mapping_entry(GROUP_MAP *map) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->add_group_mapping_entry(pdb, map); +} + +NTSTATUS pdb_update_group_mapping_entry(GROUP_MAP *map) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->update_group_mapping_entry(pdb, map); +} + +NTSTATUS pdb_delete_group_mapping_entry(DOM_SID sid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->delete_group_mapping_entry(pdb, sid); +} + +bool pdb_enum_group_mapping(const DOM_SID *sid, enum lsa_SidType sid_name_use, GROUP_MAP **pp_rmap, + size_t *p_num_entries, bool unix_only) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return NT_STATUS_IS_OK(pdb-> enum_group_mapping(pdb, sid, sid_name_use, + pp_rmap, p_num_entries, unix_only)); +} + +NTSTATUS pdb_enum_group_members(TALLOC_CTX *mem_ctx, + const DOM_SID *sid, + uint32 **pp_member_rids, + size_t *p_num_members) +{ + struct pdb_methods *pdb = pdb_get_methods(); + NTSTATUS result; + + result = pdb->enum_group_members(pdb, mem_ctx, + sid, pp_member_rids, p_num_members); + + /* special check for rid 513 */ + + if ( !NT_STATUS_IS_OK( result ) ) { + uint32 rid; + + sid_peek_rid( sid, &rid ); + + if ( rid == DOMAIN_GROUP_RID_USERS ) { + *p_num_members = 0; + *pp_member_rids = NULL; + + return NT_STATUS_OK; + } + } + + return result; +} + +NTSTATUS pdb_enum_group_memberships(TALLOC_CTX *mem_ctx, struct samu *user, + DOM_SID **pp_sids, gid_t **pp_gids, + size_t *p_num_groups) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->enum_group_memberships( + pdb, mem_ctx, user, + pp_sids, pp_gids, p_num_groups); +} + +static NTSTATUS pdb_default_set_unix_primary_group(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + struct samu *sampass) +{ + struct group *grp; + gid_t gid; + + if (!sid_to_gid(pdb_get_group_sid(sampass), &gid) || + (grp = getgrgid(gid)) == NULL) { + return NT_STATUS_INVALID_PRIMARY_GROUP; + } + + if (smb_set_primary_group(grp->gr_name, + pdb_get_username(sampass)) != 0) { + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +NTSTATUS pdb_set_unix_primary_group(TALLOC_CTX *mem_ctx, struct samu *user) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->set_unix_primary_group(pdb, mem_ctx, user); +} + +/* + * Helper function to see whether a user is in a group. We can't use + * user_in_group_sid here because this creates dependencies only smbd can + * fulfil. + */ + +static bool pdb_user_in_group(TALLOC_CTX *mem_ctx, struct samu *account, + const DOM_SID *group_sid) +{ + DOM_SID *sids; + gid_t *gids; + size_t i, num_groups; + + if (!NT_STATUS_IS_OK(pdb_enum_group_memberships(mem_ctx, account, + &sids, &gids, + &num_groups))) { + return False; + } + + for (i=0; i<num_groups; i++) { + if (sid_equal(group_sid, &sids[i])) { + return True; + } + } + return False; +} + +static NTSTATUS pdb_default_add_groupmem(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + uint32 group_rid, + uint32 member_rid) +{ + DOM_SID group_sid, member_sid; + struct samu *account = NULL; + GROUP_MAP map; + struct group *grp; + struct passwd *pwd; + const char *group_name; + uid_t uid; + + sid_compose(&group_sid, get_global_sam_sid(), group_rid); + sid_compose(&member_sid, get_global_sam_sid(), member_rid); + + if (!get_domain_group_from_sid(group_sid, &map) || + (map.gid == (gid_t)-1) || + ((grp = getgrgid(map.gid)) == NULL)) { + return NT_STATUS_NO_SUCH_GROUP; + } + + group_name = talloc_strdup(mem_ctx, grp->gr_name); + if (group_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if ( !(account = samu_new( NULL )) ) { + return NT_STATUS_NO_MEMORY; + } + + if (!pdb_getsampwsid(account, &member_sid) || + !sid_to_uid(&member_sid, &uid) || + ((pwd = getpwuid_alloc(mem_ctx, uid)) == NULL)) { + return NT_STATUS_NO_SUCH_USER; + } + + if (pdb_user_in_group(mem_ctx, account, &group_sid)) { + return NT_STATUS_MEMBER_IN_GROUP; + } + + /* + * ok, the group exist, the user exist, the user is not in the group, + * we can (finally) add it to the group ! + */ + + smb_add_user_group(group_name, pwd->pw_name); + + if (!pdb_user_in_group(mem_ctx, account, &group_sid)) { + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +NTSTATUS pdb_add_groupmem(TALLOC_CTX *mem_ctx, uint32 group_rid, + uint32 member_rid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->add_groupmem(pdb, mem_ctx, group_rid, member_rid); +} + +static NTSTATUS pdb_default_del_groupmem(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + uint32 group_rid, + uint32 member_rid) +{ + DOM_SID group_sid, member_sid; + struct samu *account = NULL; + GROUP_MAP map; + struct group *grp; + struct passwd *pwd; + const char *group_name; + uid_t uid; + + sid_compose(&group_sid, get_global_sam_sid(), group_rid); + sid_compose(&member_sid, get_global_sam_sid(), member_rid); + + if (!get_domain_group_from_sid(group_sid, &map) || + (map.gid == (gid_t)-1) || + ((grp = getgrgid(map.gid)) == NULL)) { + return NT_STATUS_NO_SUCH_GROUP; + } + + group_name = talloc_strdup(mem_ctx, grp->gr_name); + if (group_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if ( !(account = samu_new( NULL )) ) { + return NT_STATUS_NO_MEMORY; + } + + if (!pdb_getsampwsid(account, &member_sid) || + !sid_to_uid(&member_sid, &uid) || + ((pwd = getpwuid_alloc(mem_ctx, uid)) == NULL)) { + return NT_STATUS_NO_SUCH_USER; + } + + if (!pdb_user_in_group(mem_ctx, account, &group_sid)) { + return NT_STATUS_MEMBER_NOT_IN_GROUP; + } + + /* + * ok, the group exist, the user exist, the user is in the group, + * we can (finally) delete it from the group! + */ + + smb_delete_user_group(group_name, pwd->pw_name); + + if (pdb_user_in_group(mem_ctx, account, &group_sid)) { + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +NTSTATUS pdb_del_groupmem(TALLOC_CTX *mem_ctx, uint32 group_rid, + uint32 member_rid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->del_groupmem(pdb, mem_ctx, group_rid, member_rid); +} + +NTSTATUS pdb_create_alias(const char *name, uint32 *rid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->create_alias(pdb, name, rid); +} + +NTSTATUS pdb_delete_alias(const DOM_SID *sid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->delete_alias(pdb, sid); +} + +NTSTATUS pdb_get_aliasinfo(const DOM_SID *sid, struct acct_info *info) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->get_aliasinfo(pdb, sid, info); +} + +NTSTATUS pdb_set_aliasinfo(const DOM_SID *sid, struct acct_info *info) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->set_aliasinfo(pdb, sid, info); +} + +NTSTATUS pdb_add_aliasmem(const DOM_SID *alias, const DOM_SID *member) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->add_aliasmem(pdb, alias, member); +} + +NTSTATUS pdb_del_aliasmem(const DOM_SID *alias, const DOM_SID *member) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->del_aliasmem(pdb, alias, member); +} + +NTSTATUS pdb_enum_aliasmem(const DOM_SID *alias, + DOM_SID **pp_members, size_t *p_num_members) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->enum_aliasmem(pdb, alias, pp_members, p_num_members); +} + +NTSTATUS pdb_enum_alias_memberships(TALLOC_CTX *mem_ctx, + const DOM_SID *domain_sid, + const DOM_SID *members, size_t num_members, + uint32 **pp_alias_rids, + size_t *p_num_alias_rids) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->enum_alias_memberships(pdb, mem_ctx, + domain_sid, + members, num_members, + pp_alias_rids, + p_num_alias_rids); +} + +NTSTATUS pdb_lookup_rids(const DOM_SID *domain_sid, + int num_rids, + uint32 *rids, + const char **names, + enum lsa_SidType *attrs) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->lookup_rids(pdb, domain_sid, num_rids, rids, names, attrs); +} + +/* + * NOTE: pdb_lookup_names is currently (2007-01-12) not used anywhere + * in the samba code. + * Unlike _lsa_lookup_sids and _samr_lookup_rids, which eventually + * also ask pdb_lookup_rids, thus looking up a bunch of rids at a time, + * the pdb_ calls _lsa_lookup_names and _samr_lookup_names come + * down to are pdb_getsampwnam and pdb_getgrnam instead of + * pdb_lookup_names. + * But in principle, it the call belongs to the API and might get + * used in this context some day. + */ +#if 0 +NTSTATUS pdb_lookup_names(const DOM_SID *domain_sid, + int num_names, + const char **names, + uint32 *rids, + enum lsa_SidType *attrs) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->lookup_names(pdb, domain_sid, num_names, names, rids, attrs); +} +#endif + +bool pdb_get_account_policy(int policy_index, uint32 *value) +{ + struct pdb_methods *pdb = pdb_get_methods(); + NTSTATUS status; + + become_root(); + status = pdb->get_account_policy(pdb, policy_index, value); + unbecome_root(); + + return NT_STATUS_IS_OK(status); +} + +bool pdb_set_account_policy(int policy_index, uint32 value) +{ + struct pdb_methods *pdb = pdb_get_methods(); + NTSTATUS status; + + become_root(); + status = pdb->set_account_policy(pdb, policy_index, value); + unbecome_root(); + + return NT_STATUS_IS_OK(status); +} + +bool pdb_get_seq_num(time_t *seq_num) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return NT_STATUS_IS_OK(pdb->get_seq_num(pdb, seq_num)); +} + +bool pdb_uid_to_rid(uid_t uid, uint32 *rid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->uid_to_rid(pdb, uid, rid); +} + +bool pdb_uid_to_sid(uid_t uid, DOM_SID *sid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->uid_to_sid(pdb, uid, sid); +} + +bool pdb_gid_to_sid(gid_t gid, DOM_SID *sid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->gid_to_sid(pdb, gid, sid); +} + +bool pdb_sid_to_id(const DOM_SID *sid, union unid_t *id, + enum lsa_SidType *type) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->sid_to_id(pdb, sid, id, type); +} + +bool pdb_rid_algorithm(void) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->rid_algorithm(pdb); +} + +/******************************************************************** + Allocate a new RID from the passdb backend. Verify that it is free + by calling lookup_global_sam_rid() to verify that the RID is not + in use. This handles servers that have existing users or groups + with add RIDs (assigned from previous algorithmic mappings) +********************************************************************/ + +bool pdb_new_rid(uint32 *rid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + const char *name = NULL; + enum lsa_SidType type; + uint32 allocated_rid = 0; + int i; + TALLOC_CTX *ctx; + + if (pdb_rid_algorithm()) { + DEBUG(0, ("Trying to allocate a RID when algorithmic RIDs " + "are active\n")); + return False; + } + + if (algorithmic_rid_base() != BASE_RID) { + DEBUG(0, ("'algorithmic rid base' is set but a passdb backend " + "without algorithmic RIDs is chosen.\n")); + DEBUGADD(0, ("Please map all used groups using 'net groupmap " + "add', set the maximum used RID using\n")); + DEBUGADD(0, ("'net setmaxrid' and remove the parameter\n")); + return False; + } + + if ( (ctx = talloc_init("pdb_new_rid")) == NULL ) { + DEBUG(0,("pdb_new_rid: Talloc initialization failure\n")); + return False; + } + + /* Attempt to get an unused RID (max tires is 250...yes that it is + and arbitrary number I pulkled out of my head). -- jerry */ + + for ( i=0; allocated_rid==0 && i<250; i++ ) { + /* get a new RID */ + + if ( !pdb->new_rid(pdb, &allocated_rid) ) { + return False; + } + + /* validate that the RID is not in use */ + + if ( lookup_global_sam_rid( ctx, allocated_rid, &name, &type, NULL ) ) { + allocated_rid = 0; + } + } + + TALLOC_FREE( ctx ); + + if ( allocated_rid == 0 ) { + DEBUG(0,("pdb_new_rid: Failed to find unused RID\n")); + return False; + } + + *rid = allocated_rid; + + return True; +} + +/*************************************************************** + Initialize the static context (at smbd startup etc). + + If uninitialised, context will auto-init on first use. + ***************************************************************/ + +bool initialize_password_db(bool reload, struct event_context *event_ctx) +{ + pdb_event_ctx = event_ctx; + return (pdb_get_methods_reload(reload) != NULL); +} + + +/*************************************************************************** + Default implementations of some functions. + ****************************************************************************/ + +static NTSTATUS pdb_default_getsampwnam (struct pdb_methods *methods, struct samu *user, const char *sname) +{ + return NT_STATUS_NO_SUCH_USER; +} + +static NTSTATUS pdb_default_getsampwsid(struct pdb_methods *my_methods, struct samu * user, const DOM_SID *sid) +{ + return NT_STATUS_NO_SUCH_USER; +} + +static NTSTATUS pdb_default_add_sam_account (struct pdb_methods *methods, struct samu *newpwd) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS pdb_default_update_sam_account (struct pdb_methods *methods, struct samu *newpwd) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS pdb_default_delete_sam_account (struct pdb_methods *methods, struct samu *pwd) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS pdb_default_rename_sam_account (struct pdb_methods *methods, struct samu *pwd, const char *newname) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS pdb_default_update_login_attempts (struct pdb_methods *methods, struct samu *newpwd, bool success) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS pdb_default_get_account_policy(struct pdb_methods *methods, int policy_index, uint32 *value) +{ + return account_policy_get(policy_index, value) ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL; +} + +static NTSTATUS pdb_default_set_account_policy(struct pdb_methods *methods, int policy_index, uint32 value) +{ + return account_policy_set(policy_index, value) ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL; +} + +static NTSTATUS pdb_default_get_seq_num(struct pdb_methods *methods, time_t *seq_num) +{ + *seq_num = time(NULL); + return NT_STATUS_OK; +} + +static bool pdb_default_uid_to_sid(struct pdb_methods *methods, uid_t uid, + DOM_SID *sid) +{ + struct samu *sampw = NULL; + struct passwd *unix_pw; + bool ret; + + unix_pw = sys_getpwuid( uid ); + + if ( !unix_pw ) { + DEBUG(4,("pdb_default_uid_to_rid: host has no idea of uid " + "%lu\n", (unsigned long)uid)); + return False; + } + + if ( !(sampw = samu_new( NULL )) ) { + DEBUG(0,("pdb_default_uid_to_rid: samu_new() failed!\n")); + return False; + } + + become_root(); + ret = NT_STATUS_IS_OK( + methods->getsampwnam(methods, sampw, unix_pw->pw_name )); + unbecome_root(); + + if (!ret) { + DEBUG(5, ("pdb_default_uid_to_rid: Did not find user " + "%s (%d)\n", unix_pw->pw_name, uid)); + TALLOC_FREE(sampw); + return False; + } + + sid_copy(sid, pdb_get_user_sid(sampw)); + + TALLOC_FREE(sampw); + + return True; +} + +static bool pdb_default_uid_to_rid(struct pdb_methods *methods, uid_t uid, + uint32 *rid) +{ + DOM_SID sid; + bool ret; + + ret = pdb_default_uid_to_sid(methods, uid, &sid); + if (!ret) { + return ret; + } + + ret = sid_peek_check_rid(get_global_sam_sid(), &sid, rid); + + if (!ret) { + DEBUG(1, ("Could not peek rid out of sid %s\n", + sid_string_dbg(&sid))); + } + + return ret; +} + +static bool pdb_default_gid_to_sid(struct pdb_methods *methods, gid_t gid, + DOM_SID *sid) +{ + GROUP_MAP map; + + if (!NT_STATUS_IS_OK(methods->getgrgid(methods, &map, gid))) { + return False; + } + + sid_copy(sid, &map.sid); + return True; +} + +static bool pdb_default_sid_to_id(struct pdb_methods *methods, + const DOM_SID *sid, + union unid_t *id, enum lsa_SidType *type) +{ + TALLOC_CTX *mem_ctx; + bool ret = False; + const char *name; + uint32 rid; + + mem_ctx = talloc_new(NULL); + + if (mem_ctx == NULL) { + DEBUG(0, ("talloc_new failed\n")); + return False; + } + + if (sid_peek_check_rid(get_global_sam_sid(), sid, &rid)) { + /* Here we might have users as well as groups and aliases */ + ret = lookup_global_sam_rid(mem_ctx, rid, &name, type, id); + goto done; + } + + /* check for "Unix User" */ + + if ( sid_peek_check_rid(&global_sid_Unix_Users, sid, &rid) ) { + id->uid = rid; + *type = SID_NAME_USER; + ret = True; + goto done; + } + + /* check for "Unix Group" */ + + if ( sid_peek_check_rid(&global_sid_Unix_Groups, sid, &rid) ) { + id->gid = rid; + *type = SID_NAME_ALIAS; + ret = True; + goto done; + } + + /* BUILTIN */ + + if (sid_check_is_in_builtin(sid) || + sid_check_is_in_wellknown_domain(sid)) { + /* Here we only have aliases */ + GROUP_MAP map; + if (!NT_STATUS_IS_OK(methods->getgrsid(methods, &map, *sid))) { + DEBUG(10, ("Could not find map for sid %s\n", + sid_string_dbg(sid))); + goto done; + } + if ((map.sid_name_use != SID_NAME_ALIAS) && + (map.sid_name_use != SID_NAME_WKN_GRP)) { + DEBUG(10, ("Map for sid %s is a %s, expected an " + "alias\n", sid_string_dbg(sid), + sid_type_lookup(map.sid_name_use))); + goto done; + } + + id->gid = map.gid; + *type = SID_NAME_ALIAS; + ret = True; + goto done; + } + + DEBUG(5, ("Sid %s is neither ours, a Unix SID, nor builtin\n", + sid_string_dbg(sid))); + + done: + + TALLOC_FREE(mem_ctx); + return ret; +} + +static bool add_uid_to_array_unique(TALLOC_CTX *mem_ctx, + uid_t uid, uid_t **pp_uids, size_t *p_num) +{ + size_t i; + + for (i=0; i<*p_num; i++) { + if ((*pp_uids)[i] == uid) + return True; + } + + *pp_uids = TALLOC_REALLOC_ARRAY(mem_ctx, *pp_uids, uid_t, *p_num+1); + + if (*pp_uids == NULL) + return False; + + (*pp_uids)[*p_num] = uid; + *p_num += 1; + return True; +} + +static bool get_memberuids(TALLOC_CTX *mem_ctx, gid_t gid, uid_t **pp_uids, size_t *p_num) +{ + struct group *grp; + char **gr; + struct passwd *pwd; + bool winbind_env; + bool ret = False; + + *pp_uids = NULL; + *p_num = 0; + + /* We only look at our own sam, so don't care about imported stuff */ + winbind_env = winbind_env_set(); + (void)winbind_off(); + + if ((grp = getgrgid(gid)) == NULL) { + /* allow winbindd lookups, but only if they weren't already disabled */ + goto done; + } + + /* Primary group members */ + setpwent(); + while ((pwd = getpwent()) != NULL) { + if (pwd->pw_gid == gid) { + if (!add_uid_to_array_unique(mem_ctx, pwd->pw_uid, + pp_uids, p_num)) { + goto done; + } + } + } + endpwent(); + + /* Secondary group members */ + for (gr = grp->gr_mem; (*gr != NULL) && ((*gr)[0] != '\0'); gr += 1) { + struct passwd *pw = getpwnam(*gr); + + if (pw == NULL) + continue; + if (!add_uid_to_array_unique(mem_ctx, pw->pw_uid, pp_uids, p_num)) { + goto done; + } + } + + ret = True; + + done: + + /* allow winbindd lookups, but only if they weren't already disabled */ + if (!winbind_env) { + (void)winbind_on(); + } + + return ret; +} + +static NTSTATUS pdb_default_enum_group_members(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + const DOM_SID *group, + uint32 **pp_member_rids, + size_t *p_num_members) +{ + gid_t gid; + uid_t *uids; + size_t i, num_uids; + + *pp_member_rids = NULL; + *p_num_members = 0; + + if (!sid_to_gid(group, &gid)) + return NT_STATUS_NO_SUCH_GROUP; + + if(!get_memberuids(mem_ctx, gid, &uids, &num_uids)) + return NT_STATUS_NO_SUCH_GROUP; + + if (num_uids == 0) + return NT_STATUS_OK; + + *pp_member_rids = TALLOC_ZERO_ARRAY(mem_ctx, uint32, num_uids); + + for (i=0; i<num_uids; i++) { + DOM_SID sid; + + uid_to_sid(&sid, uids[i]); + + if (!sid_check_is_in_our_domain(&sid)) { + DEBUG(5, ("Inconsistent SAM -- group member uid not " + "in our domain\n")); + continue; + } + + sid_peek_rid(&sid, &(*pp_member_rids)[*p_num_members]); + *p_num_members += 1; + } + + return NT_STATUS_OK; +} + +static NTSTATUS pdb_default_enum_group_memberships(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + struct samu *user, + DOM_SID **pp_sids, + gid_t **pp_gids, + size_t *p_num_groups) +{ + size_t i; + gid_t gid; + struct passwd *pw; + const char *username = pdb_get_username(user); + + + /* Ignore the primary group SID. Honor the real Unix primary group. + The primary group SID is only of real use to Windows clients */ + + if ( !(pw = getpwnam_alloc(mem_ctx, username)) ) { + return NT_STATUS_NO_SUCH_USER; + } + + gid = pw->pw_gid; + + TALLOC_FREE( pw ); + + if (!getgroups_unix_user(mem_ctx, username, gid, pp_gids, p_num_groups)) { + return NT_STATUS_NO_SUCH_USER; + } + + if (*p_num_groups == 0) { + smb_panic("primary group missing"); + } + + *pp_sids = TALLOC_ARRAY(mem_ctx, DOM_SID, *p_num_groups); + + if (*pp_sids == NULL) { + TALLOC_FREE(*pp_gids); + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<*p_num_groups; i++) { + gid_to_sid(&(*pp_sids)[i], (*pp_gids)[i]); + } + + return NT_STATUS_OK; +} + +/******************************************************************* + Look up a rid in the SAM we're responsible for (i.e. passdb) + ********************************************************************/ + +static bool lookup_global_sam_rid(TALLOC_CTX *mem_ctx, uint32 rid, + const char **name, + enum lsa_SidType *psid_name_use, + union unid_t *unix_id) +{ + struct samu *sam_account = NULL; + GROUP_MAP map; + bool ret; + DOM_SID sid; + + *psid_name_use = SID_NAME_UNKNOWN; + + DEBUG(5,("lookup_global_sam_rid: looking up RID %u.\n", + (unsigned int)rid)); + + sid_copy(&sid, get_global_sam_sid()); + sid_append_rid(&sid, rid); + + /* see if the passdb can help us with the name of the user */ + + if ( !(sam_account = samu_new( NULL )) ) { + return False; + } + + /* BEING ROOT BLLOCK */ + become_root(); + if (pdb_getsampwsid(sam_account, &sid)) { + struct passwd *pw; + + unbecome_root(); /* -----> EXIT BECOME_ROOT() */ + *name = talloc_strdup(mem_ctx, pdb_get_username(sam_account)); + if (!*name) { + TALLOC_FREE(sam_account); + return False; + } + + *psid_name_use = SID_NAME_USER; + + TALLOC_FREE(sam_account); + + if (unix_id == NULL) { + return True; + } + + pw = Get_Pwnam_alloc(talloc_tos(), *name); + if (pw == NULL) { + return False; + } + unix_id->uid = pw->pw_uid; + TALLOC_FREE(pw); + return True; + } + TALLOC_FREE(sam_account); + + ret = pdb_getgrsid(&map, sid); + unbecome_root(); + /* END BECOME_ROOT BLOCK */ + + /* do not resolve SIDs to a name unless there is a valid + gid associated with it */ + + if ( ret && (map.gid != (gid_t)-1) ) { + *name = talloc_strdup(mem_ctx, map.nt_name); + *psid_name_use = map.sid_name_use; + + if ( unix_id ) { + unix_id->gid = map.gid; + } + + return True; + } + + /* Windows will always map RID 513 to something. On a non-domain + controller, this gets mapped to SERVER\None. */ + + if ( unix_id ) { + DEBUG(5, ("Can't find a unix id for an unmapped group\n")); + return False; + } + + if ( rid == DOMAIN_GROUP_RID_USERS ) { + *name = talloc_strdup(mem_ctx, "None" ); + *psid_name_use = SID_NAME_DOM_GRP; + + return True; + } + + return False; +} + +static NTSTATUS pdb_default_lookup_rids(struct pdb_methods *methods, + const DOM_SID *domain_sid, + int num_rids, + uint32 *rids, + const char **names, + enum lsa_SidType *attrs) +{ + int i; + NTSTATUS result; + bool have_mapped = False; + bool have_unmapped = False; + + if (sid_check_is_builtin(domain_sid)) { + + for (i=0; i<num_rids; i++) { + const char *name; + + if (lookup_builtin_rid(names, rids[i], &name)) { + attrs[i] = SID_NAME_ALIAS; + names[i] = name; + DEBUG(5,("lookup_rids: %s:%d\n", + names[i], attrs[i])); + have_mapped = True; + } else { + have_unmapped = True; + attrs[i] = SID_NAME_UNKNOWN; + } + } + goto done; + } + + /* Should not happen, but better check once too many */ + if (!sid_check_is_domain(domain_sid)) { + return NT_STATUS_INVALID_HANDLE; + } + + for (i = 0; i < num_rids; i++) { + const char *name; + + if (lookup_global_sam_rid(names, rids[i], &name, &attrs[i], + NULL)) { + if (name == NULL) { + return NT_STATUS_NO_MEMORY; + } + names[i] = name; + DEBUG(5,("lookup_rids: %s:%d\n", names[i], attrs[i])); + have_mapped = True; + } else { + have_unmapped = True; + attrs[i] = SID_NAME_UNKNOWN; + } + } + + done: + + result = NT_STATUS_NONE_MAPPED; + + if (have_mapped) + result = have_unmapped ? STATUS_SOME_UNMAPPED : NT_STATUS_OK; + + return result; +} + +#if 0 +static NTSTATUS pdb_default_lookup_names(struct pdb_methods *methods, + const DOM_SID *domain_sid, + int num_names, + const char **names, + uint32 *rids, + enum lsa_SidType *attrs) +{ + int i; + NTSTATUS result; + bool have_mapped = False; + bool have_unmapped = False; + + if (sid_check_is_builtin(domain_sid)) { + + for (i=0; i<num_names; i++) { + uint32 rid; + + if (lookup_builtin_name(names[i], &rid)) { + attrs[i] = SID_NAME_ALIAS; + rids[i] = rid; + DEBUG(5,("lookup_rids: %s:%d\n", + names[i], attrs[i])); + have_mapped = True; + } else { + have_unmapped = True; + attrs[i] = SID_NAME_UNKNOWN; + } + } + goto done; + } + + /* Should not happen, but better check once too many */ + if (!sid_check_is_domain(domain_sid)) { + return NT_STATUS_INVALID_HANDLE; + } + + for (i = 0; i < num_names; i++) { + if (lookup_global_sam_name(names[i], 0, &rids[i], &attrs[i])) { + DEBUG(5,("lookup_names: %s-> %d:%d\n", names[i], + rids[i], attrs[i])); + have_mapped = True; + } else { + have_unmapped = True; + attrs[i] = SID_NAME_UNKNOWN; + } + } + + done: + + result = NT_STATUS_NONE_MAPPED; + + if (have_mapped) + result = have_unmapped ? STATUS_SOME_UNMAPPED : NT_STATUS_OK; + + return result; +} +#endif + +struct pdb_search *pdb_search_init(enum pdb_search_type type) +{ + TALLOC_CTX *mem_ctx; + struct pdb_search *result; + + mem_ctx = talloc_init("pdb_search"); + if (mem_ctx == NULL) { + DEBUG(0, ("talloc_init failed\n")); + return NULL; + } + + result = TALLOC_P(mem_ctx, struct pdb_search); + if (result == NULL) { + DEBUG(0, ("talloc failed\n")); + return NULL; + } + + result->mem_ctx = mem_ctx; + result->type = type; + result->cache = NULL; + result->num_entries = 0; + result->cache_size = 0; + result->search_ended = False; + + /* Segfault appropriately if not initialized */ + result->next_entry = NULL; + result->search_end = NULL; + + return result; +} + +static void fill_displayentry(TALLOC_CTX *mem_ctx, uint32 rid, + uint16 acct_flags, + const char *account_name, + const char *fullname, + const char *description, + struct samr_displayentry *entry) +{ + entry->rid = rid; + entry->acct_flags = acct_flags; + + if (account_name != NULL) + entry->account_name = talloc_strdup(mem_ctx, account_name); + else + entry->account_name = ""; + + if (fullname != NULL) + entry->fullname = talloc_strdup(mem_ctx, fullname); + else + entry->fullname = ""; + + if (description != NULL) + entry->description = talloc_strdup(mem_ctx, description); + else + entry->description = ""; +} + +struct group_search { + GROUP_MAP *groups; + size_t num_groups, current_group; +}; + +static bool next_entry_groups(struct pdb_search *s, + struct samr_displayentry *entry) +{ + struct group_search *state = (struct group_search *)s->private_data; + uint32 rid; + GROUP_MAP *map = &state->groups[state->current_group]; + + if (state->current_group == state->num_groups) + return False; + + sid_peek_rid(&map->sid, &rid); + + fill_displayentry(s->mem_ctx, rid, 0, map->nt_name, NULL, map->comment, + entry); + + state->current_group += 1; + return True; +} + +static void search_end_groups(struct pdb_search *search) +{ + struct group_search *state = + (struct group_search *)search->private_data; + SAFE_FREE(state->groups); +} + +static bool pdb_search_grouptype(struct pdb_search *search, + const DOM_SID *sid, enum lsa_SidType type) +{ + struct group_search *state; + + state = TALLOC_P(search->mem_ctx, struct group_search); + if (state == NULL) { + DEBUG(0, ("talloc failed\n")); + return False; + } + + if (!pdb_enum_group_mapping(sid, type, &state->groups, &state->num_groups, + True)) { + DEBUG(0, ("Could not enum groups\n")); + return False; + } + + state->current_group = 0; + search->private_data = state; + search->next_entry = next_entry_groups; + search->search_end = search_end_groups; + return True; +} + +static bool pdb_default_search_groups(struct pdb_methods *methods, + struct pdb_search *search) +{ + return pdb_search_grouptype(search, get_global_sam_sid(), SID_NAME_DOM_GRP); +} + +static bool pdb_default_search_aliases(struct pdb_methods *methods, + struct pdb_search *search, + const DOM_SID *sid) +{ + + return pdb_search_grouptype(search, sid, SID_NAME_ALIAS); +} + +static struct samr_displayentry *pdb_search_getentry(struct pdb_search *search, + uint32 idx) +{ + if (idx < search->num_entries) + return &search->cache[idx]; + + if (search->search_ended) + return NULL; + + while (idx >= search->num_entries) { + struct samr_displayentry entry; + + if (!search->next_entry(search, &entry)) { + search->search_end(search); + search->search_ended = True; + break; + } + + ADD_TO_LARGE_ARRAY(search->mem_ctx, struct samr_displayentry, + entry, &search->cache, &search->num_entries, + &search->cache_size); + } + + return (search->num_entries > idx) ? &search->cache[idx] : NULL; +} + +struct pdb_search *pdb_search_users(uint32 acct_flags) +{ + struct pdb_methods *pdb = pdb_get_methods(); + struct pdb_search *result; + + result = pdb_search_init(PDB_USER_SEARCH); + if (result == NULL) { + return NULL; + } + + if (!pdb->search_users(pdb, result, acct_flags)) { + talloc_destroy(result->mem_ctx); + return NULL; + } + return result; +} + +struct pdb_search *pdb_search_groups(void) +{ + struct pdb_methods *pdb = pdb_get_methods(); + struct pdb_search *result; + + result = pdb_search_init(PDB_GROUP_SEARCH); + if (result == NULL) { + return NULL; + } + + if (!pdb->search_groups(pdb, result)) { + talloc_destroy(result->mem_ctx); + return NULL; + } + return result; +} + +struct pdb_search *pdb_search_aliases(const DOM_SID *sid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + struct pdb_search *result; + + if (pdb == NULL) return NULL; + + result = pdb_search_init(PDB_ALIAS_SEARCH); + if (result == NULL) return NULL; + + if (!pdb->search_aliases(pdb, result, sid)) { + talloc_destroy(result->mem_ctx); + return NULL; + } + return result; +} + +uint32 pdb_search_entries(struct pdb_search *search, + uint32 start_idx, uint32 max_entries, + struct samr_displayentry **result) +{ + struct samr_displayentry *end_entry; + uint32 end_idx = start_idx+max_entries-1; + + /* The first entry needs to be searched after the last. Otherwise the + * first entry might have moved due to a realloc during the search for + * the last entry. */ + + end_entry = pdb_search_getentry(search, end_idx); + *result = pdb_search_getentry(search, start_idx); + + if (end_entry != NULL) + return max_entries; + + if (start_idx >= search->num_entries) + return 0; + + return search->num_entries - start_idx; +} + +void pdb_search_destroy(struct pdb_search *search) +{ + if (search == NULL) + return; + + if (!search->search_ended) + search->search_end(search); + + talloc_destroy(search->mem_ctx); +} + +/******************************************************************* + trustodm methods + *******************************************************************/ + +bool pdb_get_trusteddom_pw(const char *domain, char** pwd, DOM_SID *sid, + time_t *pass_last_set_time) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->get_trusteddom_pw(pdb, domain, pwd, sid, + pass_last_set_time); +} + +bool pdb_set_trusteddom_pw(const char* domain, const char* pwd, + const DOM_SID *sid) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->set_trusteddom_pw(pdb, domain, pwd, sid); +} + +bool pdb_del_trusteddom_pw(const char *domain) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->del_trusteddom_pw(pdb, domain); +} + +NTSTATUS pdb_enum_trusteddoms(TALLOC_CTX *mem_ctx, uint32 *num_domains, + struct trustdom_info ***domains) +{ + struct pdb_methods *pdb = pdb_get_methods(); + return pdb->enum_trusteddoms(pdb, mem_ctx, num_domains, domains); +} + +/******************************************************************* + the defaults for trustdom methods: + these simply call the original passdb/secrets.c actions, + to be replaced by pdb_ldap. + *******************************************************************/ + +static bool pdb_default_get_trusteddom_pw(struct pdb_methods *methods, + const char *domain, + char** pwd, + DOM_SID *sid, + time_t *pass_last_set_time) +{ + return secrets_fetch_trusted_domain_password(domain, pwd, + sid, pass_last_set_time); + +} + +static bool pdb_default_set_trusteddom_pw(struct pdb_methods *methods, + const char* domain, + const char* pwd, + const DOM_SID *sid) +{ + return secrets_store_trusted_domain_password(domain, pwd, sid); +} + +static bool pdb_default_del_trusteddom_pw(struct pdb_methods *methods, + const char *domain) +{ + return trusted_domain_password_delete(domain); +} + +static NTSTATUS pdb_default_enum_trusteddoms(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + uint32 *num_domains, + struct trustdom_info ***domains) +{ + return secrets_trusted_domains(mem_ctx, num_domains, domains); +} + +/******************************************************************* + Create a pdb_methods structure and initialize it with the default + operations. In this way a passdb module can simply implement + the functionality it cares about. However, normally this is done + in groups of related functions. +*******************************************************************/ + +NTSTATUS make_pdb_method( struct pdb_methods **methods ) +{ + /* allocate memory for the structure as its own talloc CTX */ + + if ( !(*methods = TALLOC_ZERO_P(NULL, struct pdb_methods) ) ) { + return NT_STATUS_NO_MEMORY; + } + + (*methods)->getsampwnam = pdb_default_getsampwnam; + (*methods)->getsampwsid = pdb_default_getsampwsid; + (*methods)->create_user = pdb_default_create_user; + (*methods)->delete_user = pdb_default_delete_user; + (*methods)->add_sam_account = pdb_default_add_sam_account; + (*methods)->update_sam_account = pdb_default_update_sam_account; + (*methods)->delete_sam_account = pdb_default_delete_sam_account; + (*methods)->rename_sam_account = pdb_default_rename_sam_account; + (*methods)->update_login_attempts = pdb_default_update_login_attempts; + + (*methods)->getgrsid = pdb_default_getgrsid; + (*methods)->getgrgid = pdb_default_getgrgid; + (*methods)->getgrnam = pdb_default_getgrnam; + (*methods)->create_dom_group = pdb_default_create_dom_group; + (*methods)->delete_dom_group = pdb_default_delete_dom_group; + (*methods)->add_group_mapping_entry = pdb_default_add_group_mapping_entry; + (*methods)->update_group_mapping_entry = pdb_default_update_group_mapping_entry; + (*methods)->delete_group_mapping_entry = pdb_default_delete_group_mapping_entry; + (*methods)->enum_group_mapping = pdb_default_enum_group_mapping; + (*methods)->enum_group_members = pdb_default_enum_group_members; + (*methods)->enum_group_memberships = pdb_default_enum_group_memberships; + (*methods)->set_unix_primary_group = pdb_default_set_unix_primary_group; + (*methods)->add_groupmem = pdb_default_add_groupmem; + (*methods)->del_groupmem = pdb_default_del_groupmem; + (*methods)->create_alias = pdb_default_create_alias; + (*methods)->delete_alias = pdb_default_delete_alias; + (*methods)->get_aliasinfo = pdb_default_get_aliasinfo; + (*methods)->set_aliasinfo = pdb_default_set_aliasinfo; + (*methods)->add_aliasmem = pdb_default_add_aliasmem; + (*methods)->del_aliasmem = pdb_default_del_aliasmem; + (*methods)->enum_aliasmem = pdb_default_enum_aliasmem; + (*methods)->enum_alias_memberships = pdb_default_alias_memberships; + (*methods)->lookup_rids = pdb_default_lookup_rids; + (*methods)->get_account_policy = pdb_default_get_account_policy; + (*methods)->set_account_policy = pdb_default_set_account_policy; + (*methods)->get_seq_num = pdb_default_get_seq_num; + (*methods)->uid_to_rid = pdb_default_uid_to_rid; + (*methods)->uid_to_sid = pdb_default_uid_to_sid; + (*methods)->gid_to_sid = pdb_default_gid_to_sid; + (*methods)->sid_to_id = pdb_default_sid_to_id; + + (*methods)->search_groups = pdb_default_search_groups; + (*methods)->search_aliases = pdb_default_search_aliases; + + (*methods)->get_trusteddom_pw = pdb_default_get_trusteddom_pw; + (*methods)->set_trusteddom_pw = pdb_default_set_trusteddom_pw; + (*methods)->del_trusteddom_pw = pdb_default_del_trusteddom_pw; + (*methods)->enum_trusteddoms = pdb_default_enum_trusteddoms; + + return NT_STATUS_OK; +} diff --git a/source3/passdb/pdb_ldap.c b/source3/passdb/pdb_ldap.c new file mode 100644 index 0000000000..ddbb53a9b9 --- /dev/null +++ b/source3/passdb/pdb_ldap.c @@ -0,0 +1,6389 @@ +/* + Unix SMB/CIFS implementation. + LDAP protocol helper functions for SAMBA + Copyright (C) Jean François Micouleau 1998 + Copyright (C) Gerald Carter 2001-2003 + Copyright (C) Shahms King 2001 + Copyright (C) Andrew Bartlett 2002-2003 + Copyright (C) Stefan (metze) Metzmacher 2002-2003 + Copyright (C) Simo Sorce 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/>. + +*/ + +/* TODO: +* persistent connections: if using NSS LDAP, many connections are made +* however, using only one within Samba would be nice +* +* Clean up SSL stuff, compile on OpenLDAP 1.x, 2.x, and Netscape SDK +* +* Other LDAP based login attributes: accountExpires, etc. +* (should be the domain of Samba proper, but the sam_password/struct samu +* structures don't have fields for some of these attributes) +* +* SSL is done, but can't get the certificate based authentication to work +* against on my test platform (Linux 2.4, OpenLDAP 2.x) +*/ + +/* NOTE: this will NOT work against an Active Directory server +* due to the fact that the two password fields cannot be retrieved +* from a server; recommend using security = domain in this situation +* and/or winbind +*/ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +#include <lber.h> +#include <ldap.h> + +/* + * Work around versions of the LDAP client libs that don't have the OIDs + * defined, or have them defined under the old name. + * This functionality is really a factor of the server, not the client + * + */ + +#if defined(LDAP_EXOP_X_MODIFY_PASSWD) && !defined(LDAP_EXOP_MODIFY_PASSWD) +#define LDAP_EXOP_MODIFY_PASSWD LDAP_EXOP_X_MODIFY_PASSWD +#elif !defined(LDAP_EXOP_MODIFY_PASSWD) +#define LDAP_EXOP_MODIFY_PASSWD "1.3.6.1.4.1.4203.1.11.1" +#endif + +#if defined(LDAP_EXOP_X_MODIFY_PASSWD_ID) && !defined(LDAP_EXOP_MODIFY_PASSWD_ID) +#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID LDAP_EXOP_X_MODIFY_PASSWD_ID +#elif !defined(LDAP_EXOP_MODIFY_PASSWD_ID) +#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID ((ber_tag_t) 0x80U) +#endif + +#if defined(LDAP_EXOP_X_MODIFY_PASSWD_NEW) && !defined(LDAP_EXOP_MODIFY_PASSWD_NEW) +#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW LDAP_EXOP_X_MODIFY_PASSWD_NEW +#elif !defined(LDAP_EXOP_MODIFY_PASSWD_NEW) +#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ((ber_tag_t) 0x82U) +#endif + + +#include "smbldap.h" + +/********************************************************************** + Simple helper function to make stuff better readable + **********************************************************************/ + +static LDAP *priv2ld(struct ldapsam_privates *priv) +{ + return priv->smbldap_state->ldap_struct; +} + +/********************************************************************** + Get the attribute name given a user schame version. + **********************************************************************/ + +static const char* get_userattr_key2string( int schema_ver, int key ) +{ + switch ( schema_ver ) { + case SCHEMAVER_SAMBAACCOUNT: + return get_attr_key2string( attrib_map_v22, key ); + + case SCHEMAVER_SAMBASAMACCOUNT: + return get_attr_key2string( attrib_map_v30, key ); + + default: + DEBUG(0,("get_userattr_key2string: unknown schema version specified\n")); + break; + } + return NULL; +} + +/********************************************************************** + Return the list of attribute names given a user schema version. +**********************************************************************/ + +const char** get_userattr_list( TALLOC_CTX *mem_ctx, int schema_ver ) +{ + switch ( schema_ver ) { + case SCHEMAVER_SAMBAACCOUNT: + return get_attr_list( mem_ctx, attrib_map_v22 ); + + case SCHEMAVER_SAMBASAMACCOUNT: + return get_attr_list( mem_ctx, attrib_map_v30 ); + default: + DEBUG(0,("get_userattr_list: unknown schema version specified!\n")); + break; + } + + return NULL; +} + +/************************************************************************** + Return the list of attribute names to delete given a user schema version. +**************************************************************************/ + +static const char** get_userattr_delete_list( TALLOC_CTX *mem_ctx, + int schema_ver ) +{ + switch ( schema_ver ) { + case SCHEMAVER_SAMBAACCOUNT: + return get_attr_list( mem_ctx, + attrib_map_to_delete_v22 ); + + case SCHEMAVER_SAMBASAMACCOUNT: + return get_attr_list( mem_ctx, + attrib_map_to_delete_v30 ); + default: + DEBUG(0,("get_userattr_delete_list: unknown schema version specified!\n")); + break; + } + + return NULL; +} + + +/******************************************************************* + Generate the LDAP search filter for the objectclass based on the + version of the schema we are using. +******************************************************************/ + +static const char* get_objclass_filter( int schema_ver ) +{ + fstring objclass_filter; + char *result; + + switch( schema_ver ) { + case SCHEMAVER_SAMBAACCOUNT: + fstr_sprintf( objclass_filter, "(objectclass=%s)", LDAP_OBJ_SAMBAACCOUNT ); + break; + case SCHEMAVER_SAMBASAMACCOUNT: + fstr_sprintf( objclass_filter, "(objectclass=%s)", LDAP_OBJ_SAMBASAMACCOUNT ); + break; + default: + DEBUG(0,("get_objclass_filter: Invalid schema version specified!\n")); + objclass_filter[0] = '\0'; + break; + } + + result = talloc_strdup(talloc_tos(), objclass_filter); + SMB_ASSERT(result != NULL); + return result; +} + +/***************************************************************** + Scan a sequence number off OpenLDAP's syncrepl contextCSN +******************************************************************/ + +static NTSTATUS ldapsam_get_seq_num(struct pdb_methods *my_methods, time_t *seq_num) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + NTSTATUS ntstatus = NT_STATUS_UNSUCCESSFUL; + LDAPMessage *msg = NULL; + LDAPMessage *entry = NULL; + TALLOC_CTX *mem_ctx; + char **values = NULL; + int rc, num_result, num_values, rid; + char *suffix = NULL; + char *tok; + const char *p; + const char **attrs; + + /* Unfortunatly there is no proper way to detect syncrepl-support in + * smbldap_connect_system(). The syncrepl OIDs are submitted for publication + * but do not show up in the root-DSE yet. Neither we can query the + * subschema-context for the syncProviderSubentry or syncConsumerSubentry + * objectclass. Currently we require lp_ldap_suffix() to show up as + * namingContext. - Guenther + */ + + if (!lp_parm_bool(-1, "ldapsam", "syncrepl_seqnum", False)) { + return ntstatus; + } + + if (!seq_num) { + DEBUG(3,("ldapsam_get_seq_num: no sequence_number\n")); + return ntstatus; + } + + if (!smbldap_has_naming_context(ldap_state->smbldap_state->ldap_struct, lp_ldap_suffix())) { + DEBUG(3,("ldapsam_get_seq_num: DIT not configured to hold %s " + "as top-level namingContext\n", lp_ldap_suffix())); + return ntstatus; + } + + mem_ctx = talloc_init("ldapsam_get_seq_num"); + + if (mem_ctx == NULL) + return NT_STATUS_NO_MEMORY; + + if ((attrs = TALLOC_ARRAY(mem_ctx, const char *, 2)) == NULL) { + ntstatus = NT_STATUS_NO_MEMORY; + goto done; + } + + /* if we got a syncrepl-rid (up to three digits long) we speak with a consumer */ + rid = lp_parm_int(-1, "ldapsam", "syncrepl_rid", -1); + if (rid > 0) { + + /* consumer syncreplCookie: */ + /* csn=20050126161620Z#0000001#00#00000 */ + attrs[0] = talloc_strdup(mem_ctx, "syncreplCookie"); + attrs[1] = NULL; + suffix = talloc_asprintf(mem_ctx, + "cn=syncrepl%d,%s", rid, lp_ldap_suffix()); + if (!suffix) { + ntstatus = NT_STATUS_NO_MEMORY; + goto done; + } + } else { + + /* provider contextCSN */ + /* 20050126161620Z#000009#00#000000 */ + attrs[0] = talloc_strdup(mem_ctx, "contextCSN"); + attrs[1] = NULL; + suffix = talloc_asprintf(mem_ctx, + "cn=ldapsync,%s", lp_ldap_suffix()); + + if (!suffix) { + ntstatus = NT_STATUS_NO_MEMORY; + goto done; + } + } + + rc = smbldap_search(ldap_state->smbldap_state, suffix, + LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0, &msg); + + if (rc != LDAP_SUCCESS) { + goto done; + } + + num_result = ldap_count_entries(ldap_state->smbldap_state->ldap_struct, msg); + if (num_result != 1) { + DEBUG(3,("ldapsam_get_seq_num: Expected one entry, got %d\n", num_result)); + goto done; + } + + entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, msg); + if (entry == NULL) { + DEBUG(3,("ldapsam_get_seq_num: Could not retrieve entry\n")); + goto done; + } + + values = ldap_get_values(ldap_state->smbldap_state->ldap_struct, entry, attrs[0]); + if (values == NULL) { + DEBUG(3,("ldapsam_get_seq_num: no values\n")); + goto done; + } + + num_values = ldap_count_values(values); + if (num_values == 0) { + DEBUG(3,("ldapsam_get_seq_num: not a single value\n")); + goto done; + } + + p = values[0]; + if (!next_token_talloc(mem_ctx, &p, &tok, "#")) { + DEBUG(0,("ldapsam_get_seq_num: failed to parse sequence number\n")); + goto done; + } + + p = tok; + if (!strncmp(p, "csn=", strlen("csn="))) + p += strlen("csn="); + + DEBUG(10,("ldapsam_get_seq_num: got %s: %s\n", attrs[0], p)); + + *seq_num = generalized_to_unix_time(p); + + /* very basic sanity check */ + if (*seq_num <= 0) { + DEBUG(3,("ldapsam_get_seq_num: invalid sequence number: %d\n", + (int)*seq_num)); + goto done; + } + + ntstatus = NT_STATUS_OK; + + done: + if (values != NULL) + ldap_value_free(values); + if (msg != NULL) + ldap_msgfree(msg); + if (mem_ctx) + talloc_destroy(mem_ctx); + + return ntstatus; +} + +/******************************************************************* + Run the search by name. +******************************************************************/ + +int ldapsam_search_suffix_by_name(struct ldapsam_privates *ldap_state, + const char *user, + LDAPMessage ** result, + const char **attr) +{ + char *filter = NULL; + char *escape_user = escape_ldap_string_alloc(user); + int ret = -1; + + if (!escape_user) { + return LDAP_NO_MEMORY; + } + + /* + * in the filter expression, replace %u with the real name + * so in ldap filter, %u MUST exist :-) + */ + filter = talloc_asprintf(talloc_tos(), "(&%s%s)", "(uid=%u)", + get_objclass_filter(ldap_state->schema_ver)); + if (!filter) { + SAFE_FREE(escape_user); + return LDAP_NO_MEMORY; + } + /* + * have to use this here because $ is filtered out + * in string_sub + */ + + filter = talloc_all_string_sub(talloc_tos(), + filter, "%u", escape_user); + SAFE_FREE(escape_user); + if (!filter) { + return LDAP_NO_MEMORY; + } + + ret = smbldap_search_suffix(ldap_state->smbldap_state, + filter, attr, result); + TALLOC_FREE(filter); + return ret; +} + +/******************************************************************* + Run the search by rid. +******************************************************************/ + +static int ldapsam_search_suffix_by_rid (struct ldapsam_privates *ldap_state, + uint32 rid, LDAPMessage ** result, + const char **attr) +{ + char *filter = NULL; + int rc; + + filter = talloc_asprintf(talloc_tos(), "(&(rid=%i)%s)", rid, + get_objclass_filter(ldap_state->schema_ver)); + if (!filter) { + return LDAP_NO_MEMORY; + } + + rc = smbldap_search_suffix(ldap_state->smbldap_state, + filter, attr, result); + TALLOC_FREE(filter); + return rc; +} + +/******************************************************************* + Run the search by SID. +******************************************************************/ + +static int ldapsam_search_suffix_by_sid (struct ldapsam_privates *ldap_state, + const DOM_SID *sid, LDAPMessage ** result, + const char **attr) +{ + char *filter = NULL; + int rc; + fstring sid_string; + + filter = talloc_asprintf(talloc_tos(), "(&(%s=%s)%s)", + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_USER_SID), + sid_to_fstring(sid_string, sid), + get_objclass_filter(ldap_state->schema_ver)); + if (!filter) { + return LDAP_NO_MEMORY; + } + + rc = smbldap_search_suffix(ldap_state->smbldap_state, + filter, attr, result); + + TALLOC_FREE(filter); + return rc; +} + +/******************************************************************* + Delete complete object or objectclass and attrs from + object found in search_result depending on lp_ldap_delete_dn +******************************************************************/ + +static int ldapsam_delete_entry(struct ldapsam_privates *priv, + TALLOC_CTX *mem_ctx, + LDAPMessage *entry, + const char *objectclass, + const char **attrs) +{ + LDAPMod **mods = NULL; + char *name; + const char *dn; + BerElement *ptr = NULL; + + dn = smbldap_talloc_dn(mem_ctx, priv2ld(priv), entry); + if (dn == NULL) { + return LDAP_NO_MEMORY; + } + + if (lp_ldap_delete_dn()) { + return smbldap_delete(priv->smbldap_state, dn); + } + + /* Ok, delete only the SAM attributes */ + + for (name = ldap_first_attribute(priv2ld(priv), entry, &ptr); + name != NULL; + name = ldap_next_attribute(priv2ld(priv), entry, ptr)) { + const char **attrib; + + /* We are only allowed to delete the attributes that + really exist. */ + + for (attrib = attrs; *attrib != NULL; attrib++) { + if (strequal(*attrib, name)) { + DEBUG(10, ("ldapsam_delete_entry: deleting " + "attribute %s\n", name)); + smbldap_set_mod(&mods, LDAP_MOD_DELETE, name, + NULL); + } + } + ldap_memfree(name); + } + + if (ptr != NULL) { + ber_free(ptr, 0); + } + + smbldap_set_mod(&mods, LDAP_MOD_DELETE, "objectClass", objectclass); + talloc_autofree_ldapmod(mem_ctx, mods); + + return smbldap_modify(priv->smbldap_state, dn, mods); +} + +static time_t ldapsam_get_entry_timestamp( struct ldapsam_privates *ldap_state, LDAPMessage * entry) +{ + char *temp; + struct tm tm; + + temp = smbldap_talloc_single_attribute(ldap_state->smbldap_state->ldap_struct, entry, + get_userattr_key2string(ldap_state->schema_ver,LDAP_ATTR_MOD_TIMESTAMP), + talloc_tos()); + if (!temp) { + return (time_t) 0; + } + + if ( !strptime(temp, "%Y%m%d%H%M%SZ", &tm)) { + DEBUG(2,("ldapsam_get_entry_timestamp: strptime failed on: %s\n", + (char*)temp)); + TALLOC_FREE(temp); + return (time_t) 0; + } + TALLOC_FREE(temp); + tzset(); + return timegm(&tm); +} + +/********************************************************************** + Initialize struct samu from an LDAP query. + (Based on init_sam_from_buffer in pdb_tdb.c) +*********************************************************************/ + +static bool init_sam_from_ldap(struct ldapsam_privates *ldap_state, + struct samu * sampass, + LDAPMessage * entry) +{ + time_t logon_time, + logoff_time, + kickoff_time, + pass_last_set_time, + pass_can_change_time, + pass_must_change_time, + ldap_entry_time, + bad_password_time; + char *username = NULL, + *domain = NULL, + *nt_username = NULL, + *fullname = NULL, + *homedir = NULL, + *dir_drive = NULL, + *logon_script = NULL, + *profile_path = NULL, + *acct_desc = NULL, + *workstations = NULL, + *munged_dial = NULL; + uint32 user_rid; + uint8 smblmpwd[LM_HASH_LEN], + smbntpwd[NT_HASH_LEN]; + bool use_samba_attrs = True; + uint32 acct_ctrl = 0; + uint16 logon_divs; + uint16 bad_password_count = 0, + logon_count = 0; + uint32 hours_len; + uint8 hours[MAX_HOURS_LEN]; + char *temp = NULL; + LOGIN_CACHE *cache_entry = NULL; + uint32 pwHistLen; + bool expand_explicit = lp_passdb_expand_explicit(); + bool ret = false; + TALLOC_CTX *ctx = talloc_init("init_sam_from_ldap"); + + if (!ctx) { + return false; + } + if (sampass == NULL || ldap_state == NULL || entry == NULL) { + DEBUG(0, ("init_sam_from_ldap: NULL parameters found!\n")); + goto fn_exit; + } + + if (priv2ld(ldap_state) == NULL) { + DEBUG(0, ("init_sam_from_ldap: ldap_state->smbldap_state->" + "ldap_struct is NULL!\n")); + goto fn_exit; + } + + if (!(username = smbldap_talloc_single_attribute(priv2ld(ldap_state), + entry, + "uid", + ctx))) { + DEBUG(1, ("init_sam_from_ldap: No uid attribute found for " + "this user!\n")); + goto fn_exit; + } + + DEBUG(2, ("init_sam_from_ldap: Entry found for user: %s\n", username)); + + nt_username = talloc_strdup(ctx, username); + if (!nt_username) { + goto fn_exit; + } + + domain = talloc_strdup(ctx, ldap_state->domain_name); + if (!domain) { + goto fn_exit; + } + + pdb_set_username(sampass, username, PDB_SET); + + pdb_set_domain(sampass, domain, PDB_DEFAULT); + pdb_set_nt_username(sampass, nt_username, PDB_SET); + + /* deal with different attributes between the schema first */ + + if ( ldap_state->schema_ver == SCHEMAVER_SAMBASAMACCOUNT ) { + if ((temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_USER_SID), + ctx))!=NULL) { + pdb_set_user_sid_from_string(sampass, temp, PDB_SET); + } + } else { + if ((temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_USER_RID), + ctx))!=NULL) { + user_rid = (uint32)atol(temp); + pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET); + } + } + + if (pdb_get_init_flags(sampass,PDB_USERSID) == PDB_DEFAULT) { + DEBUG(1, ("init_sam_from_ldap: no %s or %s attribute found for this user %s\n", + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_USER_SID), + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_USER_RID), + username)); + return False; + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_PWD_LAST_SET), + ctx); + if (temp) { + pass_last_set_time = (time_t) atol(temp); + pdb_set_pass_last_set_time(sampass, + pass_last_set_time, PDB_SET); + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_LOGON_TIME), + ctx); + if (temp) { + logon_time = (time_t) atol(temp); + pdb_set_logon_time(sampass, logon_time, PDB_SET); + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_LOGOFF_TIME), + ctx); + if (temp) { + logoff_time = (time_t) atol(temp); + pdb_set_logoff_time(sampass, logoff_time, PDB_SET); + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_KICKOFF_TIME), + ctx); + if (temp) { + kickoff_time = (time_t) atol(temp); + pdb_set_kickoff_time(sampass, kickoff_time, PDB_SET); + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_PWD_CAN_CHANGE), + ctx); + if (temp) { + pass_can_change_time = (time_t) atol(temp); + pdb_set_pass_can_change_time(sampass, + pass_can_change_time, PDB_SET); + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_PWD_MUST_CHANGE), + ctx); + if (temp) { + pass_must_change_time = (time_t) atol(temp); + pdb_set_pass_must_change_time(sampass, + pass_must_change_time, PDB_SET); + } + + /* recommend that 'gecos' and 'displayName' should refer to the same + * attribute OID. userFullName depreciated, only used by Samba + * primary rules of LDAP: don't make a new attribute when one is already defined + * that fits your needs; using cn then displayName rather than 'userFullName' + */ + + fullname = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_DISPLAY_NAME), + ctx); + if (fullname) { + pdb_set_fullname(sampass, fullname, PDB_SET); + } else { + fullname = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_CN), + ctx); + if (fullname) { + pdb_set_fullname(sampass, fullname, PDB_SET); + } + } + + dir_drive = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_HOME_DRIVE), + ctx); + if (dir_drive) { + pdb_set_dir_drive(sampass, dir_drive, PDB_SET); + } else { + pdb_set_dir_drive( sampass, lp_logon_drive(), PDB_DEFAULT ); + } + + homedir = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_HOME_PATH), + ctx); + if (homedir) { + if (expand_explicit) { + homedir = talloc_sub_basic(ctx, + username, + domain, + homedir); + if (!homedir) { + goto fn_exit; + } + } + pdb_set_homedir(sampass, homedir, PDB_SET); + } else { + pdb_set_homedir(sampass, + talloc_sub_basic(ctx, username, domain, + lp_logon_home()), + PDB_DEFAULT); + } + + logon_script = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_LOGON_SCRIPT), + ctx); + if (logon_script) { + if (expand_explicit) { + logon_script = talloc_sub_basic(ctx, + username, + domain, + logon_script); + if (!logon_script) { + goto fn_exit; + } + } + pdb_set_logon_script(sampass, logon_script, PDB_SET); + } else { + pdb_set_logon_script(sampass, + talloc_sub_basic(ctx, username, domain, + lp_logon_script()), + PDB_DEFAULT ); + } + + profile_path = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_PROFILE_PATH), + ctx); + if (profile_path) { + if (expand_explicit) { + profile_path = talloc_sub_basic(ctx, + username, + domain, + profile_path); + if (!profile_path) { + goto fn_exit; + } + } + pdb_set_profile_path(sampass, profile_path, PDB_SET); + } else { + pdb_set_profile_path(sampass, + talloc_sub_basic(ctx, username, domain, + lp_logon_path()), + PDB_DEFAULT ); + } + + acct_desc = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_DESC), + ctx); + if (acct_desc) { + pdb_set_acct_desc(sampass, acct_desc, PDB_SET); + } + + workstations = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_USER_WKS), + ctx); + if (workstations) { + pdb_set_workstations(sampass, workstations, PDB_SET); + } + + munged_dial = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_MUNGED_DIAL), + ctx); + if (munged_dial) { + pdb_set_munged_dial(sampass, munged_dial, PDB_SET); + } + + /* FIXME: hours stuff should be cleaner */ + + logon_divs = 168; + hours_len = 21; + memset(hours, 0xff, hours_len); + + if (ldap_state->is_nds_ldap) { + char *user_dn; + size_t pwd_len; + char clear_text_pw[512]; + + /* Make call to Novell eDirectory ldap extension to get clear text password. + NOTE: This will only work if we have an SSL connection to eDirectory. */ + user_dn = smbldap_get_dn(ldap_state->smbldap_state->ldap_struct, entry); + if (user_dn != NULL) { + DEBUG(3, ("init_sam_from_ldap: smbldap_get_dn(%s) returned '%s'\n", username, user_dn)); + + pwd_len = sizeof(clear_text_pw); + if (pdb_nds_get_password(ldap_state->smbldap_state, user_dn, &pwd_len, clear_text_pw) == LDAP_SUCCESS) { + nt_lm_owf_gen(clear_text_pw, smbntpwd, smblmpwd); + if (!pdb_set_lanman_passwd(sampass, smblmpwd, PDB_SET)) { + SAFE_FREE(user_dn); + return False; + } + ZERO_STRUCT(smblmpwd); + if (!pdb_set_nt_passwd(sampass, smbntpwd, PDB_SET)) { + SAFE_FREE(user_dn); + return False; + } + ZERO_STRUCT(smbntpwd); + use_samba_attrs = False; + } + + SAFE_FREE(user_dn); + + } else { + DEBUG(0, ("init_sam_from_ldap: failed to get user_dn for '%s'\n", username)); + } + } + + if (use_samba_attrs) { + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_LMPW), + ctx); + if (temp) { + pdb_gethexpwd(temp, smblmpwd); + memset((char *)temp, '\0', strlen(temp)+1); + if (!pdb_set_lanman_passwd(sampass, smblmpwd, PDB_SET)) { + goto fn_exit; + } + ZERO_STRUCT(smblmpwd); + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_NTPW), + ctx); + if (temp) { + pdb_gethexpwd(temp, smbntpwd); + memset((char *)temp, '\0', strlen(temp)+1); + if (!pdb_set_nt_passwd(sampass, smbntpwd, PDB_SET)) { + goto fn_exit; + } + ZERO_STRUCT(smbntpwd); + } + } + + pwHistLen = 0; + + pdb_get_account_policy(AP_PASSWORD_HISTORY, &pwHistLen); + if (pwHistLen > 0){ + uint8 *pwhist = NULL; + int i; + char *history_string = TALLOC_ARRAY(ctx, char, + MAX_PW_HISTORY_LEN*64); + + if (!history_string) { + goto fn_exit; + } + + pwHistLen = MIN(pwHistLen, MAX_PW_HISTORY_LEN); + + if ((pwhist = TALLOC_ARRAY(ctx, uint8, + pwHistLen * PW_HISTORY_ENTRY_LEN)) == + NULL){ + DEBUG(0, ("init_sam_from_ldap: talloc failed!\n")); + goto fn_exit; + } + memset(pwhist, '\0', pwHistLen * PW_HISTORY_ENTRY_LEN); + + if (smbldap_get_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_PWD_HISTORY), + history_string, + MAX_PW_HISTORY_LEN*64)) { + bool hex_failed = false; + for (i = 0; i < pwHistLen; i++){ + /* Get the 16 byte salt. */ + if (!pdb_gethexpwd(&history_string[i*64], + &pwhist[i*PW_HISTORY_ENTRY_LEN])) { + hex_failed = true; + break; + } + /* Get the 16 byte MD5 hash of salt+passwd. */ + if (!pdb_gethexpwd(&history_string[(i*64)+32], + &pwhist[(i*PW_HISTORY_ENTRY_LEN)+ + PW_HISTORY_SALT_LEN])) { + hex_failed = True; + break; + } + } + if (hex_failed) { + DEBUG(2,("init_sam_from_ldap: Failed to get password history for user %s\n", + username)); + memset(pwhist, '\0', pwHistLen * PW_HISTORY_ENTRY_LEN); + } + } + if (!pdb_set_pw_history(sampass, pwhist, pwHistLen, PDB_SET)){ + goto fn_exit; + } + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_ACB_INFO), + ctx); + if (temp) { + acct_ctrl = pdb_decode_acct_ctrl(temp); + + if (acct_ctrl == 0) { + acct_ctrl |= ACB_NORMAL; + } + + pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET); + } else { + acct_ctrl |= ACB_NORMAL; + } + + pdb_set_hours_len(sampass, hours_len, PDB_SET); + pdb_set_logon_divs(sampass, logon_divs, PDB_SET); + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_BAD_PASSWORD_COUNT), + ctx); + if (temp) { + bad_password_count = (uint32) atol(temp); + pdb_set_bad_password_count(sampass, + bad_password_count, PDB_SET); + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_BAD_PASSWORD_TIME), + ctx); + if (temp) { + bad_password_time = (time_t) atol(temp); + pdb_set_bad_password_time(sampass, bad_password_time, PDB_SET); + } + + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_LOGON_COUNT), + ctx); + if (temp) { + logon_count = (uint32) atol(temp); + pdb_set_logon_count(sampass, logon_count, PDB_SET); + } + + /* pdb_set_unknown_6(sampass, unknown6, PDB_SET); */ + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_LOGON_HOURS), + ctx); + if (temp) { + pdb_gethexhours(temp, hours); + memset((char *)temp, '\0', strlen(temp) +1); + pdb_set_hours(sampass, hours, PDB_SET); + ZERO_STRUCT(hours); + } + + if (lp_parm_bool(-1, "ldapsam", "trusted", False)) { + temp = smbldap_talloc_single_attribute( + priv2ld(ldap_state), + entry, + "uidNumber", + ctx); + if (temp) { + /* We've got a uid, feed the cache */ + uid_t uid = strtoul(temp, NULL, 10); + store_uid_sid_cache(pdb_get_user_sid(sampass), uid); + } + } + + /* check the timestamp of the cache vs ldap entry */ + if (!(ldap_entry_time = ldapsam_get_entry_timestamp(ldap_state, + entry))) { + ret = true; + goto fn_exit; + } + + /* see if we have newer updates */ + if (!(cache_entry = login_cache_read(sampass))) { + DEBUG (9, ("No cache entry, bad count = %u, bad time = %u\n", + (unsigned int)pdb_get_bad_password_count(sampass), + (unsigned int)pdb_get_bad_password_time(sampass))); + ret = true; + goto fn_exit; + } + + DEBUG(7, ("ldap time is %u, cache time is %u, bad time = %u\n", + (unsigned int)ldap_entry_time, + (unsigned int)cache_entry->entry_timestamp, + (unsigned int)cache_entry->bad_password_time)); + + if (ldap_entry_time > cache_entry->entry_timestamp) { + /* cache is older than directory , so + we need to delete the entry but allow the + fields to be written out */ + login_cache_delentry(sampass); + } else { + /* read cache in */ + pdb_set_acct_ctrl(sampass, + pdb_get_acct_ctrl(sampass) | + (cache_entry->acct_ctrl & ACB_AUTOLOCK), + PDB_SET); + pdb_set_bad_password_count(sampass, + cache_entry->bad_password_count, + PDB_SET); + pdb_set_bad_password_time(sampass, + cache_entry->bad_password_time, + PDB_SET); + } + + ret = true; + + fn_exit: + + TALLOC_FREE(ctx); + SAFE_FREE(cache_entry); + return ret; +} + +/********************************************************************** + Initialize the ldap db from a struct samu. Called on update. + (Based on init_buffer_from_sam in pdb_tdb.c) +*********************************************************************/ + +static bool init_ldap_from_sam (struct ldapsam_privates *ldap_state, + LDAPMessage *existing, + LDAPMod *** mods, struct samu * sampass, + bool (*need_update)(const struct samu *, + enum pdb_elements)) +{ + char *temp = NULL; + uint32 rid; + + if (mods == NULL || sampass == NULL) { + DEBUG(0, ("init_ldap_from_sam: NULL parameters found!\n")); + return False; + } + + *mods = NULL; + + /* + * took out adding "objectclass: sambaAccount" + * do this on a per-mod basis + */ + if (need_update(sampass, PDB_USERNAME)) { + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + "uid", pdb_get_username(sampass)); + if (ldap_state->is_nds_ldap) { + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + "cn", pdb_get_username(sampass)); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + "sn", pdb_get_username(sampass)); + } + } + + DEBUG(2, ("init_ldap_from_sam: Setting entry for user: %s\n", pdb_get_username(sampass))); + + /* only update the RID if we actually need to */ + if (need_update(sampass, PDB_USERSID)) { + fstring sid_string; + const DOM_SID *user_sid = pdb_get_user_sid(sampass); + + switch ( ldap_state->schema_ver ) { + case SCHEMAVER_SAMBAACCOUNT: + if (!sid_peek_check_rid(&ldap_state->domain_sid, user_sid, &rid)) { + DEBUG(1, ("init_ldap_from_sam: User's SID (%s) is not for this domain (%s), cannot add to LDAP!\n", + sid_string_dbg(user_sid), + sid_string_dbg( + &ldap_state->domain_sid))); + return False; + } + if (asprintf(&temp, "%i", rid) < 0) { + return false; + } + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_USER_RID), + temp); + SAFE_FREE(temp); + break; + + case SCHEMAVER_SAMBASAMACCOUNT: + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_USER_SID), + sid_to_fstring(sid_string, user_sid)); + break; + + default: + DEBUG(0,("init_ldap_from_sam: unknown schema version specified\n")); + break; + } + } + + /* we don't need to store the primary group RID - so leaving it + 'free' to hang off the unix primary group makes life easier */ + + if (need_update(sampass, PDB_GROUPSID)) { + fstring sid_string; + const DOM_SID *group_sid = pdb_get_group_sid(sampass); + + switch ( ldap_state->schema_ver ) { + case SCHEMAVER_SAMBAACCOUNT: + if (!sid_peek_check_rid(&ldap_state->domain_sid, group_sid, &rid)) { + DEBUG(1, ("init_ldap_from_sam: User's Primary Group SID (%s) is not for this domain (%s), cannot add to LDAP!\n", + sid_string_dbg(group_sid), + sid_string_dbg( + &ldap_state->domain_sid))); + return False; + } + + if (asprintf(&temp, "%i", rid) < 0) { + return false; + } + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_PRIMARY_GROUP_RID), temp); + SAFE_FREE(temp); + break; + + case SCHEMAVER_SAMBASAMACCOUNT: + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_PRIMARY_GROUP_SID), sid_to_fstring(sid_string, group_sid)); + break; + + default: + DEBUG(0,("init_ldap_from_sam: unknown schema version specified\n")); + break; + } + + } + + /* displayName, cn, and gecos should all be the same + * most easily accomplished by giving them the same OID + * gecos isn't set here b/c it should be handled by the + * add-user script + * We change displayName only and fall back to cn if + * it does not exist. + */ + + if (need_update(sampass, PDB_FULLNAME)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_DISPLAY_NAME), + pdb_get_fullname(sampass)); + + if (need_update(sampass, PDB_ACCTDESC)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_DESC), + pdb_get_acct_desc(sampass)); + + if (need_update(sampass, PDB_WORKSTATIONS)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_USER_WKS), + pdb_get_workstations(sampass)); + + if (need_update(sampass, PDB_MUNGEDDIAL)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_MUNGED_DIAL), + pdb_get_munged_dial(sampass)); + + if (need_update(sampass, PDB_SMBHOME)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_HOME_PATH), + pdb_get_homedir(sampass)); + + if (need_update(sampass, PDB_DRIVE)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_HOME_DRIVE), + pdb_get_dir_drive(sampass)); + + if (need_update(sampass, PDB_LOGONSCRIPT)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LOGON_SCRIPT), + pdb_get_logon_script(sampass)); + + if (need_update(sampass, PDB_PROFILE)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PROFILE_PATH), + pdb_get_profile_path(sampass)); + + if (asprintf(&temp, "%li", pdb_get_logon_time(sampass)) < 0) { + return false; + } + if (need_update(sampass, PDB_LOGONTIME)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LOGON_TIME), temp); + SAFE_FREE(temp); + + if (asprintf(&temp, "%li", pdb_get_logoff_time(sampass)) < 0) { + return false; + } + if (need_update(sampass, PDB_LOGOFFTIME)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LOGOFF_TIME), temp); + SAFE_FREE(temp); + + if (asprintf(&temp, "%li", pdb_get_kickoff_time(sampass)) < 0) { + return false; + } + if (need_update(sampass, PDB_KICKOFFTIME)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_KICKOFF_TIME), temp); + SAFE_FREE(temp); + + if (asprintf(&temp, "%li", pdb_get_pass_can_change_time_noncalc(sampass)) < 0) { + return false; + } + if (need_update(sampass, PDB_CANCHANGETIME)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PWD_CAN_CHANGE), temp); + SAFE_FREE(temp); + + if (asprintf(&temp, "%li", pdb_get_pass_must_change_time(sampass)) < 0) { + return false; + } + if (need_update(sampass, PDB_MUSTCHANGETIME)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PWD_MUST_CHANGE), temp); + SAFE_FREE(temp); + + if ((pdb_get_acct_ctrl(sampass)&(ACB_WSTRUST|ACB_SVRTRUST|ACB_DOMTRUST)) + || (lp_ldap_passwd_sync()!=LDAP_PASSWD_SYNC_ONLY)) { + + if (need_update(sampass, PDB_LMPASSWD)) { + const uchar *lm_pw = pdb_get_lanman_passwd(sampass); + if (lm_pw) { + char pwstr[34]; + pdb_sethexpwd(pwstr, lm_pw, + pdb_get_acct_ctrl(sampass)); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LMPW), + pwstr); + } else { + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LMPW), + NULL); + } + } + if (need_update(sampass, PDB_NTPASSWD)) { + const uchar *nt_pw = pdb_get_nt_passwd(sampass); + if (nt_pw) { + char pwstr[34]; + pdb_sethexpwd(pwstr, nt_pw, + pdb_get_acct_ctrl(sampass)); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_NTPW), + pwstr); + } else { + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_NTPW), + NULL); + } + } + + if (need_update(sampass, PDB_PWHISTORY)) { + char *pwstr = NULL; + uint32 pwHistLen = 0; + pdb_get_account_policy(AP_PASSWORD_HISTORY, &pwHistLen); + + pwstr = SMB_MALLOC_ARRAY(char, 1024); + if (!pwstr) { + return false; + } + if (pwHistLen == 0) { + /* Remove any password history from the LDAP store. */ + memset(pwstr, '0', 64); /* NOTE !!!! '0' *NOT '\0' */ + pwstr[64] = '\0'; + } else { + int i; + uint32 currHistLen = 0; + const uint8 *pwhist = pdb_get_pw_history(sampass, &currHistLen); + if (pwhist != NULL) { + /* We can only store (1024-1/64 password history entries. */ + pwHistLen = MIN(pwHistLen, ((1024-1)/64)); + for (i=0; i< pwHistLen && i < currHistLen; i++) { + /* Store the salt. */ + pdb_sethexpwd(&pwstr[i*64], &pwhist[i*PW_HISTORY_ENTRY_LEN], 0); + /* Followed by the md5 hash of salt + md4 hash */ + pdb_sethexpwd(&pwstr[(i*64)+32], + &pwhist[(i*PW_HISTORY_ENTRY_LEN)+PW_HISTORY_SALT_LEN], 0); + DEBUG(100, ("pwstr=%s\n", pwstr)); + } + } + } + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PWD_HISTORY), + pwstr); + SAFE_FREE(pwstr); + } + + if (need_update(sampass, PDB_PASSLASTSET)) { + if (asprintf(&temp, "%li", + pdb_get_pass_last_set_time(sampass)) < 0) { + return false; + } + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PWD_LAST_SET), + temp); + SAFE_FREE(temp); + } + } + + if (need_update(sampass, PDB_HOURS)) { + const uint8 *hours = pdb_get_hours(sampass); + if (hours) { + char hourstr[44]; + pdb_sethexhours(hourstr, hours); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, + existing, + mods, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_LOGON_HOURS), + hourstr); + } + } + + if (need_update(sampass, PDB_ACCTCTRL)) + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_ACB_INFO), + pdb_encode_acct_ctrl (pdb_get_acct_ctrl(sampass), NEW_PW_FORMAT_SPACE_PADDED_LEN)); + + /* password lockout cache: + - If we are now autolocking or clearing, we write to ldap + - If we are clearing, we delete the cache entry + - If the count is > 0, we update the cache + + This even means when autolocking, we cache, just in case the + update doesn't work, and we have to cache the autolock flag */ + + if (need_update(sampass, PDB_BAD_PASSWORD_COUNT)) /* && + need_update(sampass, PDB_BAD_PASSWORD_TIME)) */ { + uint16 badcount = pdb_get_bad_password_count(sampass); + time_t badtime = pdb_get_bad_password_time(sampass); + uint32 pol; + pdb_get_account_policy(AP_BAD_ATTEMPT_LOCKOUT, &pol); + + DEBUG(3, ("updating bad password fields, policy=%u, count=%u, time=%u\n", + (unsigned int)pol, (unsigned int)badcount, (unsigned int)badtime)); + + if ((badcount >= pol) || (badcount == 0)) { + DEBUG(7, ("making mods to update ldap, count=%u, time=%u\n", + (unsigned int)badcount, (unsigned int)badtime)); + if (asprintf(&temp, "%li", (long)badcount) < 0) { + return false; + } + smbldap_make_mod( + ldap_state->smbldap_state->ldap_struct, + existing, mods, + get_userattr_key2string( + ldap_state->schema_ver, + LDAP_ATTR_BAD_PASSWORD_COUNT), + temp); + SAFE_FREE(temp); + + if (asprintf(&temp, "%li", badtime) < 0) { + return false; + } + smbldap_make_mod( + ldap_state->smbldap_state->ldap_struct, + existing, mods, + get_userattr_key2string( + ldap_state->schema_ver, + LDAP_ATTR_BAD_PASSWORD_TIME), + temp); + SAFE_FREE(temp); + } + if (badcount == 0) { + DEBUG(7, ("bad password count is reset, deleting login cache entry for %s\n", pdb_get_nt_username(sampass))); + login_cache_delentry(sampass); + } else { + LOGIN_CACHE cache_entry; + + cache_entry.entry_timestamp = time(NULL); + cache_entry.acct_ctrl = pdb_get_acct_ctrl(sampass); + cache_entry.bad_password_count = badcount; + cache_entry.bad_password_time = badtime; + + DEBUG(7, ("Updating bad password count and time in login cache\n")); + login_cache_write(sampass, cache_entry); + } + } + + return True; +} + +/********************************************************************** + End enumeration of the LDAP password list. +*********************************************************************/ + +static void ldapsam_endsampwent(struct pdb_methods *my_methods) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + if (ldap_state->result) { + ldap_msgfree(ldap_state->result); + ldap_state->result = NULL; + } +} + +static void append_attr(TALLOC_CTX *mem_ctx, const char ***attr_list, + const char *new_attr) +{ + int i; + + if (new_attr == NULL) { + return; + } + + for (i=0; (*attr_list)[i] != NULL; i++) { + ; + } + + (*attr_list) = TALLOC_REALLOC_ARRAY(mem_ctx, (*attr_list), + const char *, i+2); + SMB_ASSERT((*attr_list) != NULL); + (*attr_list)[i] = talloc_strdup((*attr_list), new_attr); + (*attr_list)[i+1] = NULL; +} + +/********************************************************************** +Get struct samu entry from LDAP by username. +*********************************************************************/ + +static NTSTATUS ldapsam_getsampwnam(struct pdb_methods *my_methods, struct samu *user, const char *sname) +{ + NTSTATUS ret = NT_STATUS_UNSUCCESSFUL; + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + int count; + const char ** attr_list; + int rc; + + attr_list = get_userattr_list( user, ldap_state->schema_ver ); + append_attr(user, &attr_list, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_MOD_TIMESTAMP)); + append_attr(user, &attr_list, "uidNumber"); + rc = ldapsam_search_suffix_by_name(ldap_state, sname, &result, + attr_list); + TALLOC_FREE( attr_list ); + + if ( rc != LDAP_SUCCESS ) + return NT_STATUS_NO_SUCH_USER; + + count = ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result); + + if (count < 1) { + DEBUG(4, ("ldapsam_getsampwnam: Unable to locate user [%s] count=%d\n", sname, count)); + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_USER; + } else if (count > 1) { + DEBUG(1, ("ldapsam_getsampwnam: Duplicate entries for this user [%s] Failing. count=%d\n", sname, count)); + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_USER; + } + + entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, result); + if (entry) { + if (!init_sam_from_ldap(ldap_state, user, entry)) { + DEBUG(1,("ldapsam_getsampwnam: init_sam_from_ldap failed for user '%s'!\n", sname)); + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_USER; + } + pdb_set_backend_private_data(user, result, NULL, + my_methods, PDB_CHANGED); + talloc_autofree_ldapmsg(user, result); + ret = NT_STATUS_OK; + } else { + ldap_msgfree(result); + } + return ret; +} + +static int ldapsam_get_ldap_user_by_sid(struct ldapsam_privates *ldap_state, + const DOM_SID *sid, LDAPMessage **result) +{ + int rc = -1; + const char ** attr_list; + uint32 rid; + + switch ( ldap_state->schema_ver ) { + case SCHEMAVER_SAMBASAMACCOUNT: { + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return LDAP_NO_MEMORY; + } + + attr_list = get_userattr_list(tmp_ctx, + ldap_state->schema_ver); + append_attr(tmp_ctx, &attr_list, + get_userattr_key2string( + ldap_state->schema_ver, + LDAP_ATTR_MOD_TIMESTAMP)); + append_attr(tmp_ctx, &attr_list, "uidNumber"); + rc = ldapsam_search_suffix_by_sid(ldap_state, sid, + result, attr_list); + TALLOC_FREE(tmp_ctx); + + if ( rc != LDAP_SUCCESS ) + return rc; + break; + } + + case SCHEMAVER_SAMBAACCOUNT: + if (!sid_peek_check_rid(&ldap_state->domain_sid, sid, &rid)) { + return rc; + } + + attr_list = get_userattr_list(NULL, + ldap_state->schema_ver); + rc = ldapsam_search_suffix_by_rid(ldap_state, rid, result, attr_list ); + TALLOC_FREE( attr_list ); + + if ( rc != LDAP_SUCCESS ) + return rc; + break; + } + return rc; +} + +/********************************************************************** + Get struct samu entry from LDAP by SID. +*********************************************************************/ + +static NTSTATUS ldapsam_getsampwsid(struct pdb_methods *my_methods, struct samu * user, const DOM_SID *sid) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + int count; + int rc; + + rc = ldapsam_get_ldap_user_by_sid(ldap_state, + sid, &result); + if (rc != LDAP_SUCCESS) + return NT_STATUS_NO_SUCH_USER; + + count = ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result); + + if (count < 1) { + DEBUG(4, ("ldapsam_getsampwsid: Unable to locate SID [%s] " + "count=%d\n", sid_string_dbg(sid), count)); + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_USER; + } else if (count > 1) { + DEBUG(1, ("ldapsam_getsampwsid: More than one user with SID " + "[%s]. Failing. count=%d\n", sid_string_dbg(sid), + count)); + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_USER; + } + + entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, result); + if (!entry) { + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_USER; + } + + if (!init_sam_from_ldap(ldap_state, user, entry)) { + DEBUG(1,("ldapsam_getsampwsid: init_sam_from_ldap failed!\n")); + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_USER; + } + + pdb_set_backend_private_data(user, result, NULL, + my_methods, PDB_CHANGED); + talloc_autofree_ldapmsg(user, result); + return NT_STATUS_OK; +} + +/******************************************************************** + Do the actual modification - also change a plaintext passord if + it it set. +**********************************************************************/ + +static NTSTATUS ldapsam_modify_entry(struct pdb_methods *my_methods, + struct samu *newpwd, char *dn, + LDAPMod **mods, int ldap_op, + bool (*need_update)(const struct samu *, enum pdb_elements)) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + int rc; + + if (!newpwd || !dn) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!mods) { + DEBUG(5,("ldapsam_modify_entry: mods is empty: nothing to modify\n")); + /* may be password change below however */ + } else { + switch(ldap_op) { + case LDAP_MOD_ADD: + if (ldap_state->is_nds_ldap) { + smbldap_set_mod(&mods, LDAP_MOD_ADD, + "objectclass", + "inetOrgPerson"); + } else { + smbldap_set_mod(&mods, LDAP_MOD_ADD, + "objectclass", + LDAP_OBJ_ACCOUNT); + } + rc = smbldap_add(ldap_state->smbldap_state, + dn, mods); + break; + case LDAP_MOD_REPLACE: + rc = smbldap_modify(ldap_state->smbldap_state, + dn ,mods); + break; + default: + DEBUG(0,("ldapsam_modify_entry: Wrong LDAP operation type: %d!\n", + ldap_op)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (rc!=LDAP_SUCCESS) { + return NT_STATUS_UNSUCCESSFUL; + } + } + + if (!(pdb_get_acct_ctrl(newpwd)&(ACB_WSTRUST|ACB_SVRTRUST|ACB_DOMTRUST)) && + (lp_ldap_passwd_sync() != LDAP_PASSWD_SYNC_OFF) && + need_update(newpwd, PDB_PLAINTEXT_PW) && + (pdb_get_plaintext_passwd(newpwd)!=NULL)) { + BerElement *ber; + struct berval *bv; + char *retoid = NULL; + struct berval *retdata = NULL; + char *utf8_password; + char *utf8_dn; + size_t converted_size; + + if (!ldap_state->is_nds_ldap) { + + if (!smbldap_has_extension(ldap_state->smbldap_state->ldap_struct, + LDAP_EXOP_MODIFY_PASSWD)) { + DEBUG(2, ("ldap password change requested, but LDAP " + "server does not support it -- ignoring\n")); + return NT_STATUS_OK; + } + } + + if (!push_utf8_allocate(&utf8_password, + pdb_get_plaintext_passwd(newpwd), + &converted_size)) + { + return NT_STATUS_NO_MEMORY; + } + + if (!push_utf8_allocate(&utf8_dn, dn, &converted_size)) { + SAFE_FREE(utf8_password); + return NT_STATUS_NO_MEMORY; + } + + if ((ber = ber_alloc_t(LBER_USE_DER))==NULL) { + DEBUG(0,("ber_alloc_t returns NULL\n")); + SAFE_FREE(utf8_password); + SAFE_FREE(utf8_dn); + return NT_STATUS_UNSUCCESSFUL; + } + + if ((ber_printf (ber, "{") < 0) || + (ber_printf (ber, "ts", LDAP_TAG_EXOP_MODIFY_PASSWD_ID, utf8_dn) < 0) || + (ber_printf (ber, "ts", LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, utf8_password) < 0) || + (ber_printf (ber, "n}") < 0)) { + DEBUG(0,("ldapsam_modify_entry: ber_printf returns a value <0\n")); + ber_free(ber,1); + SAFE_FREE(utf8_dn); + SAFE_FREE(utf8_password); + return NT_STATUS_UNSUCCESSFUL; + } + + if ((rc = ber_flatten (ber, &bv))<0) { + DEBUG(0,("ldapsam_modify_entry: ber_flatten returns a value <0\n")); + ber_free(ber,1); + SAFE_FREE(utf8_dn); + SAFE_FREE(utf8_password); + return NT_STATUS_UNSUCCESSFUL; + } + + SAFE_FREE(utf8_dn); + SAFE_FREE(utf8_password); + ber_free(ber, 1); + + if (!ldap_state->is_nds_ldap) { + rc = smbldap_extended_operation(ldap_state->smbldap_state, + LDAP_EXOP_MODIFY_PASSWD, + bv, NULL, NULL, &retoid, + &retdata); + } else { + rc = pdb_nds_set_password(ldap_state->smbldap_state, dn, + pdb_get_plaintext_passwd(newpwd)); + } + if (rc != LDAP_SUCCESS) { + char *ld_error = NULL; + + if (rc == LDAP_OBJECT_CLASS_VIOLATION) { + DEBUG(3, ("Could not set userPassword " + "attribute due to an objectClass " + "violation -- ignoring\n")); + ber_bvfree(bv); + return NT_STATUS_OK; + } + + ldap_get_option(ldap_state->smbldap_state->ldap_struct, LDAP_OPT_ERROR_STRING, + &ld_error); + DEBUG(0,("ldapsam_modify_entry: LDAP Password could not be changed for user %s: %s\n\t%s\n", + pdb_get_username(newpwd), ldap_err2string(rc), ld_error?ld_error:"unknown")); + SAFE_FREE(ld_error); + ber_bvfree(bv); +#if defined(LDAP_CONSTRAINT_VIOLATION) + if (rc == LDAP_CONSTRAINT_VIOLATION) + return NT_STATUS_PASSWORD_RESTRICTION; +#endif + return NT_STATUS_UNSUCCESSFUL; + } else { + DEBUG(3,("ldapsam_modify_entry: LDAP Password changed for user %s\n",pdb_get_username(newpwd))); +#ifdef DEBUG_PASSWORD + DEBUG(100,("ldapsam_modify_entry: LDAP Password changed to %s\n",pdb_get_plaintext_passwd(newpwd))); +#endif + if (retdata) + ber_bvfree(retdata); + if (retoid) + ldap_memfree(retoid); + } + ber_bvfree(bv); + } + return NT_STATUS_OK; +} + +/********************************************************************** + Delete entry from LDAP for username. +*********************************************************************/ + +static NTSTATUS ldapsam_delete_sam_account(struct pdb_methods *my_methods, + struct samu * sam_acct) +{ + struct ldapsam_privates *priv = + (struct ldapsam_privates *)my_methods->private_data; + const char *sname; + int rc; + LDAPMessage *msg, *entry; + NTSTATUS result = NT_STATUS_NO_MEMORY; + const char **attr_list; + TALLOC_CTX *mem_ctx; + + if (!sam_acct) { + DEBUG(0, ("ldapsam_delete_sam_account: sam_acct was NULL!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + sname = pdb_get_username(sam_acct); + + DEBUG(3, ("ldapsam_delete_sam_account: Deleting user %s from " + "LDAP.\n", sname)); + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + DEBUG(0, ("talloc_new failed\n")); + goto done; + } + + attr_list = get_userattr_delete_list(mem_ctx, priv->schema_ver ); + if (attr_list == NULL) { + goto done; + } + + rc = ldapsam_search_suffix_by_name(priv, sname, &msg, attr_list); + + if ((rc != LDAP_SUCCESS) || + (ldap_count_entries(priv2ld(priv), msg) != 1) || + ((entry = ldap_first_entry(priv2ld(priv), msg)) == NULL)) { + DEBUG(5, ("Could not find user %s\n", sname)); + result = NT_STATUS_NO_SUCH_USER; + goto done; + } + + rc = ldapsam_delete_entry( + priv, mem_ctx, entry, + priv->schema_ver == SCHEMAVER_SAMBASAMACCOUNT ? + LDAP_OBJ_SAMBASAMACCOUNT : LDAP_OBJ_SAMBAACCOUNT, + attr_list); + + result = (rc == LDAP_SUCCESS) ? + NT_STATUS_OK : NT_STATUS_ACCESS_DENIED; + + done: + TALLOC_FREE(mem_ctx); + return result; +} + +/********************************************************************** + Helper function to determine for update_sam_account whether + we need LDAP modification. +*********************************************************************/ + +static bool element_is_changed(const struct samu *sampass, + enum pdb_elements element) +{ + return IS_SAM_CHANGED(sampass, element); +} + +/********************************************************************** + Update struct samu. +*********************************************************************/ + +static NTSTATUS ldapsam_update_sam_account(struct pdb_methods *my_methods, struct samu * newpwd) +{ + NTSTATUS ret = NT_STATUS_UNSUCCESSFUL; + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + int rc = 0; + char *dn; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + LDAPMod **mods = NULL; + const char **attr_list; + + result = (LDAPMessage *)pdb_get_backend_private_data(newpwd, my_methods); + if (!result) { + attr_list = get_userattr_list(NULL, ldap_state->schema_ver); + if (pdb_get_username(newpwd) == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + rc = ldapsam_search_suffix_by_name(ldap_state, pdb_get_username(newpwd), &result, attr_list ); + TALLOC_FREE( attr_list ); + if (rc != LDAP_SUCCESS) { + return NT_STATUS_UNSUCCESSFUL; + } + pdb_set_backend_private_data(newpwd, result, NULL, + my_methods, PDB_CHANGED); + talloc_autofree_ldapmsg(newpwd, result); + } + + if (ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result) == 0) { + DEBUG(0, ("ldapsam_update_sam_account: No user to modify!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, result); + dn = smbldap_get_dn(ldap_state->smbldap_state->ldap_struct, entry); + if (!dn) { + return NT_STATUS_UNSUCCESSFUL; + } + + DEBUG(4, ("ldapsam_update_sam_account: user %s to be modified has dn: %s\n", pdb_get_username(newpwd), dn)); + + if (!init_ldap_from_sam(ldap_state, entry, &mods, newpwd, + element_is_changed)) { + DEBUG(0, ("ldapsam_update_sam_account: init_ldap_from_sam failed!\n")); + SAFE_FREE(dn); + if (mods != NULL) + ldap_mods_free(mods,True); + return NT_STATUS_UNSUCCESSFUL; + } + + if ((lp_ldap_passwd_sync() != LDAP_PASSWD_SYNC_ONLY) + && (mods == NULL)) { + DEBUG(4,("ldapsam_update_sam_account: mods is empty: nothing to update for user: %s\n", + pdb_get_username(newpwd))); + SAFE_FREE(dn); + return NT_STATUS_OK; + } + + ret = ldapsam_modify_entry(my_methods,newpwd,dn,mods,LDAP_MOD_REPLACE, element_is_changed); + + if (mods != NULL) { + ldap_mods_free(mods,True); + } + + SAFE_FREE(dn); + + /* + * We need to set the backend private data to NULL here. For example + * setuserinfo level 25 does a pdb_update_sam_account twice on the + * same one, and with the explicit delete / add logic for attribute + * values the second time we would use the wrong "old" value which + * does not exist in LDAP anymore. Thus the LDAP server would refuse + * the update. + * The existing LDAPMessage is still being auto-freed by the + * destructor. + */ + pdb_set_backend_private_data(newpwd, NULL, NULL, my_methods, + PDB_CHANGED); + + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + + DEBUG(2, ("ldapsam_update_sam_account: successfully modified uid = %s in the LDAP database\n", + pdb_get_username(newpwd))); + return NT_STATUS_OK; +} + +/*************************************************************************** + Renames a struct samu + - The "rename user script" has full responsibility for changing everything +***************************************************************************/ + +static NTSTATUS ldapsam_rename_sam_account(struct pdb_methods *my_methods, + struct samu *old_acct, + const char *newname) +{ + const char *oldname; + int rc; + char *rename_script = NULL; + fstring oldname_lower, newname_lower; + + if (!old_acct) { + DEBUG(0, ("ldapsam_rename_sam_account: old_acct was NULL!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + if (!newname) { + DEBUG(0, ("ldapsam_rename_sam_account: newname was NULL!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + oldname = pdb_get_username(old_acct); + + /* rename the posix user */ + rename_script = SMB_STRDUP(lp_renameuser_script()); + if (rename_script == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (!(*rename_script)) { + SAFE_FREE(rename_script); + return NT_STATUS_ACCESS_DENIED; + } + + DEBUG (3, ("ldapsam_rename_sam_account: Renaming user %s to %s.\n", + oldname, newname)); + + /* We have to allow the account name to end with a '$'. + Also, follow the semantics in _samr_create_user() and lower case the + posix name but preserve the case in passdb */ + + fstrcpy( oldname_lower, oldname ); + strlower_m( oldname_lower ); + fstrcpy( newname_lower, newname ); + strlower_m( newname_lower ); + rename_script = realloc_string_sub2(rename_script, + "%unew", + newname_lower, + true, + true); + if (rename_script) { + return NT_STATUS_NO_MEMORY; + } + rename_script = realloc_string_sub2(rename_script, + "%uold", + oldname_lower, + true, + true); + rc = smbrun(rename_script, NULL); + + DEBUG(rc ? 0 : 3,("Running the command `%s' gave %d\n", + rename_script, rc)); + + SAFE_FREE(rename_script); + + if (rc == 0) { + smb_nscd_flush_user_cache(); + } + + if (rc) + return NT_STATUS_UNSUCCESSFUL; + + return NT_STATUS_OK; +} + +/********************************************************************** + Helper function to determine for update_sam_account whether + we need LDAP modification. + *********************************************************************/ + +static bool element_is_set_or_changed(const struct samu *sampass, + enum pdb_elements element) +{ + return (IS_SAM_SET(sampass, element) || + IS_SAM_CHANGED(sampass, element)); +} + +/********************************************************************** + Add struct samu to LDAP. +*********************************************************************/ + +static NTSTATUS ldapsam_add_sam_account(struct pdb_methods *my_methods, struct samu * newpwd) +{ + NTSTATUS ret = NT_STATUS_UNSUCCESSFUL; + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + int rc; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + LDAPMod **mods = NULL; + int ldap_op = LDAP_MOD_REPLACE; + uint32 num_result; + const char **attr_list; + char *escape_user = NULL; + const char *username = pdb_get_username(newpwd); + const DOM_SID *sid = pdb_get_user_sid(newpwd); + char *filter = NULL; + char *dn = NULL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *ctx = talloc_init("ldapsam_add_sam_account"); + + if (!ctx) { + return NT_STATUS_NO_MEMORY; + } + + if (!username || !*username) { + DEBUG(0, ("ldapsam_add_sam_account: Cannot add user without a username!\n")); + status = NT_STATUS_INVALID_PARAMETER; + goto fn_exit; + } + + /* free this list after the second search or in case we exit on failure */ + attr_list = get_userattr_list(ctx, ldap_state->schema_ver); + + rc = ldapsam_search_suffix_by_name (ldap_state, username, &result, attr_list); + + if (rc != LDAP_SUCCESS) { + goto fn_exit; + } + + if (ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result) != 0) { + DEBUG(0,("ldapsam_add_sam_account: User '%s' already in the base, with samba attributes\n", + username)); + goto fn_exit; + } + ldap_msgfree(result); + result = NULL; + + if (element_is_set_or_changed(newpwd, PDB_USERSID)) { + rc = ldapsam_get_ldap_user_by_sid(ldap_state, + sid, &result); + if (rc == LDAP_SUCCESS) { + if (ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result) != 0) { + DEBUG(0,("ldapsam_add_sam_account: SID '%s' " + "already in the base, with samba " + "attributes\n", sid_string_dbg(sid))); + goto fn_exit; + } + ldap_msgfree(result); + result = NULL; + } + } + + /* does the entry already exist but without a samba attributes? + we need to return the samba attributes here */ + + escape_user = escape_ldap_string_alloc( username ); + filter = talloc_strdup(attr_list, "(uid=%u)"); + if (!filter) { + status = NT_STATUS_NO_MEMORY; + goto fn_exit; + } + filter = talloc_all_string_sub(attr_list, filter, "%u", escape_user); + if (!filter) { + status = NT_STATUS_NO_MEMORY; + goto fn_exit; + } + SAFE_FREE(escape_user); + + rc = smbldap_search_suffix(ldap_state->smbldap_state, + filter, attr_list, &result); + if ( rc != LDAP_SUCCESS ) { + goto fn_exit; + } + + num_result = ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result); + + if (num_result > 1) { + DEBUG (0, ("ldapsam_add_sam_account: More than one user with that uid exists: bailing out!\n")); + goto fn_exit; + } + + /* Check if we need to update an existing entry */ + if (num_result == 1) { + char *tmp; + + DEBUG(3,("ldapsam_add_sam_account: User exists without samba attributes: adding them\n")); + ldap_op = LDAP_MOD_REPLACE; + entry = ldap_first_entry (ldap_state->smbldap_state->ldap_struct, result); + tmp = smbldap_get_dn(ldap_state->smbldap_state->ldap_struct, entry); + if (!tmp) { + goto fn_exit; + } + dn = talloc_asprintf(ctx, "%s", tmp); + SAFE_FREE(tmp); + if (!dn) { + status = NT_STATUS_NO_MEMORY; + goto fn_exit; + } + + } else if (ldap_state->schema_ver == SCHEMAVER_SAMBASAMACCOUNT) { + + /* There might be a SID for this account already - say an idmap entry */ + + filter = talloc_asprintf(ctx, + "(&(%s=%s)(|(objectClass=%s)(objectClass=%s)))", + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_USER_SID), + sid_string_talloc(ctx, sid), + LDAP_OBJ_IDMAP_ENTRY, + LDAP_OBJ_SID_ENTRY); + if (!filter) { + status = NT_STATUS_NO_MEMORY; + goto fn_exit; + } + + /* free old result before doing a new search */ + if (result != NULL) { + ldap_msgfree(result); + result = NULL; + } + rc = smbldap_search_suffix(ldap_state->smbldap_state, + filter, attr_list, &result); + + if ( rc != LDAP_SUCCESS ) { + goto fn_exit; + } + + num_result = ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result); + + if (num_result > 1) { + DEBUG (0, ("ldapsam_add_sam_account: More than one user with specified Sid exists: bailing out!\n")); + goto fn_exit; + } + + /* Check if we need to update an existing entry */ + if (num_result == 1) { + char *tmp; + + DEBUG(3,("ldapsam_add_sam_account: User exists without samba attributes: adding them\n")); + ldap_op = LDAP_MOD_REPLACE; + entry = ldap_first_entry (ldap_state->smbldap_state->ldap_struct, result); + tmp = smbldap_get_dn (ldap_state->smbldap_state->ldap_struct, entry); + if (!tmp) { + goto fn_exit; + } + dn = talloc_asprintf(ctx, "%s", tmp); + SAFE_FREE(tmp); + if (!dn) { + status = NT_STATUS_NO_MEMORY; + goto fn_exit; + } + } + } + + if (num_result == 0) { + char *escape_username; + /* Check if we need to add an entry */ + DEBUG(3,("ldapsam_add_sam_account: Adding new user\n")); + ldap_op = LDAP_MOD_ADD; + + escape_username = escape_rdn_val_string_alloc(username); + if (!escape_username) { + status = NT_STATUS_NO_MEMORY; + goto fn_exit; + } + + if (username[strlen(username)-1] == '$') { + dn = talloc_asprintf(ctx, + "uid=%s,%s", + escape_username, + lp_ldap_machine_suffix()); + } else { + dn = talloc_asprintf(ctx, + "uid=%s,%s", + escape_username, + lp_ldap_user_suffix()); + } + + SAFE_FREE(escape_username); + if (!dn) { + status = NT_STATUS_NO_MEMORY; + goto fn_exit; + } + } + + if (!init_ldap_from_sam(ldap_state, entry, &mods, newpwd, + element_is_set_or_changed)) { + DEBUG(0, ("ldapsam_add_sam_account: init_ldap_from_sam failed!\n")); + if (mods != NULL) { + ldap_mods_free(mods, true); + } + goto fn_exit; + } + + if (mods == NULL) { + DEBUG(0,("ldapsam_add_sam_account: mods is empty: nothing to add for user: %s\n",pdb_get_username(newpwd))); + goto fn_exit; + } + switch ( ldap_state->schema_ver ) { + case SCHEMAVER_SAMBAACCOUNT: + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectclass", LDAP_OBJ_SAMBAACCOUNT); + break; + case SCHEMAVER_SAMBASAMACCOUNT: + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectclass", LDAP_OBJ_SAMBASAMACCOUNT); + break; + default: + DEBUG(0,("ldapsam_add_sam_account: invalid schema version specified\n")); + break; + } + + ret = ldapsam_modify_entry(my_methods,newpwd,dn,mods,ldap_op, element_is_set_or_changed); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("ldapsam_add_sam_account: failed to modify/add user with uid = %s (dn = %s)\n", + pdb_get_username(newpwd),dn)); + ldap_mods_free(mods, true); + goto fn_exit; + } + + DEBUG(2,("ldapsam_add_sam_account: added: uid == %s in the LDAP database\n", pdb_get_username(newpwd))); + ldap_mods_free(mods, true); + + status = NT_STATUS_OK; + + fn_exit: + + TALLOC_FREE(ctx); + SAFE_FREE(escape_user); + if (result) { + ldap_msgfree(result); + } + return status; +} + +/********************************************************************** + *********************************************************************/ + +static int ldapsam_search_one_group (struct ldapsam_privates *ldap_state, + const char *filter, + LDAPMessage ** result) +{ + int scope = LDAP_SCOPE_SUBTREE; + int rc; + const char **attr_list; + + attr_list = get_attr_list(NULL, groupmap_attr_list); + rc = smbldap_search(ldap_state->smbldap_state, + lp_ldap_group_suffix (), scope, + filter, attr_list, 0, result); + TALLOC_FREE(attr_list); + + return rc; +} + +/********************************************************************** + *********************************************************************/ + +static bool init_group_from_ldap(struct ldapsam_privates *ldap_state, + GROUP_MAP *map, LDAPMessage *entry) +{ + char *temp = NULL; + TALLOC_CTX *ctx = talloc_init("init_group_from_ldap"); + + if (ldap_state == NULL || map == NULL || entry == NULL || + ldap_state->smbldap_state->ldap_struct == NULL) { + DEBUG(0, ("init_group_from_ldap: NULL parameters found!\n")); + TALLOC_FREE(ctx); + return false; + } + + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_attr_key2string(groupmap_attr_list, + LDAP_ATTR_GIDNUMBER), + ctx); + if (!temp) { + DEBUG(0, ("init_group_from_ldap: Mandatory attribute %s not found\n", + get_attr_key2string( groupmap_attr_list, LDAP_ATTR_GIDNUMBER))); + TALLOC_FREE(ctx); + return false; + } + DEBUG(2, ("init_group_from_ldap: Entry found for group: %s\n", temp)); + + map->gid = (gid_t)atol(temp); + + TALLOC_FREE(temp); + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_attr_key2string(groupmap_attr_list, + LDAP_ATTR_GROUP_SID), + ctx); + if (!temp) { + DEBUG(0, ("init_group_from_ldap: Mandatory attribute %s not found\n", + get_attr_key2string( groupmap_attr_list, LDAP_ATTR_GROUP_SID))); + TALLOC_FREE(ctx); + return false; + } + + if (!string_to_sid(&map->sid, temp)) { + DEBUG(1, ("SID string [%s] could not be read as a valid SID\n", temp)); + TALLOC_FREE(ctx); + return false; + } + + TALLOC_FREE(temp); + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_attr_key2string(groupmap_attr_list, + LDAP_ATTR_GROUP_TYPE), + ctx); + if (!temp) { + DEBUG(0, ("init_group_from_ldap: Mandatory attribute %s not found\n", + get_attr_key2string( groupmap_attr_list, LDAP_ATTR_GROUP_TYPE))); + TALLOC_FREE(ctx); + return false; + } + map->sid_name_use = (enum lsa_SidType)atol(temp); + + if ((map->sid_name_use < SID_NAME_USER) || + (map->sid_name_use > SID_NAME_UNKNOWN)) { + DEBUG(0, ("init_group_from_ldap: Unknown Group type: %d\n", map->sid_name_use)); + TALLOC_FREE(ctx); + return false; + } + + TALLOC_FREE(temp); + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_attr_key2string(groupmap_attr_list, + LDAP_ATTR_DISPLAY_NAME), + ctx); + if (!temp) { + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_attr_key2string(groupmap_attr_list, + LDAP_ATTR_CN), + ctx); + if (!temp) { + DEBUG(0, ("init_group_from_ldap: Attributes cn not found either \ +for gidNumber(%lu)\n",(unsigned long)map->gid)); + TALLOC_FREE(ctx); + return false; + } + } + fstrcpy(map->nt_name, temp); + + TALLOC_FREE(temp); + temp = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_attr_key2string(groupmap_attr_list, + LDAP_ATTR_DESC), + ctx); + if (!temp) { + temp = talloc_strdup(ctx, ""); + if (!temp) { + TALLOC_FREE(ctx); + return false; + } + } + fstrcpy(map->comment, temp); + + if (lp_parm_bool(-1, "ldapsam", "trusted", false)) { + store_gid_sid_cache(&map->sid, map->gid); + } + + TALLOC_FREE(ctx); + return true; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS ldapsam_getgroup(struct pdb_methods *methods, + const char *filter, + GROUP_MAP *map) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + int count; + + if (ldapsam_search_one_group(ldap_state, filter, &result) + != LDAP_SUCCESS) { + return NT_STATUS_NO_SUCH_GROUP; + } + + count = ldap_count_entries(priv2ld(ldap_state), result); + + if (count < 1) { + DEBUG(4, ("ldapsam_getgroup: Did not find group, filter was " + "%s\n", filter)); + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_GROUP; + } + + if (count > 1) { + DEBUG(1, ("ldapsam_getgroup: Duplicate entries for filter %s: " + "count=%d\n", filter, count)); + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_GROUP; + } + + entry = ldap_first_entry(priv2ld(ldap_state), result); + + if (!entry) { + ldap_msgfree(result); + return NT_STATUS_UNSUCCESSFUL; + } + + if (!init_group_from_ldap(ldap_state, map, entry)) { + DEBUG(1, ("ldapsam_getgroup: init_group_from_ldap failed for " + "group filter %s\n", filter)); + ldap_msgfree(result); + return NT_STATUS_NO_SUCH_GROUP; + } + + ldap_msgfree(result); + return NT_STATUS_OK; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS ldapsam_getgrsid(struct pdb_methods *methods, GROUP_MAP *map, + DOM_SID sid) +{ + char *filter = NULL; + NTSTATUS status; + fstring tmp; + + if (asprintf(&filter, "(&(objectClass=%s)(%s=%s))", + LDAP_OBJ_GROUPMAP, + get_attr_key2string(groupmap_attr_list, LDAP_ATTR_GROUP_SID), + sid_to_fstring(tmp, &sid)) < 0) { + return NT_STATUS_NO_MEMORY; + } + + status = ldapsam_getgroup(methods, filter, map); + SAFE_FREE(filter); + return status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS ldapsam_getgrgid(struct pdb_methods *methods, GROUP_MAP *map, + gid_t gid) +{ + char *filter = NULL; + NTSTATUS status; + + if (asprintf(&filter, "(&(objectClass=%s)(%s=%lu))", + LDAP_OBJ_GROUPMAP, + get_attr_key2string(groupmap_attr_list, LDAP_ATTR_GIDNUMBER), + (unsigned long)gid) < 0) { + return NT_STATUS_NO_MEMORY; + } + + status = ldapsam_getgroup(methods, filter, map); + SAFE_FREE(filter); + return status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS ldapsam_getgrnam(struct pdb_methods *methods, GROUP_MAP *map, + const char *name) +{ + char *filter = NULL; + char *escape_name = escape_ldap_string_alloc(name); + NTSTATUS status; + + if (!escape_name) { + return NT_STATUS_NO_MEMORY; + } + + if (asprintf(&filter, "(&(objectClass=%s)(|(%s=%s)(%s=%s)))", + LDAP_OBJ_GROUPMAP, + get_attr_key2string(groupmap_attr_list, LDAP_ATTR_DISPLAY_NAME), escape_name, + get_attr_key2string(groupmap_attr_list, LDAP_ATTR_CN), + escape_name) < 0) { + SAFE_FREE(escape_name); + return NT_STATUS_NO_MEMORY; + } + + SAFE_FREE(escape_name); + status = ldapsam_getgroup(methods, filter, map); + SAFE_FREE(filter); + return status; +} + +static bool ldapsam_extract_rid_from_entry(LDAP *ldap_struct, + LDAPMessage *entry, + const DOM_SID *domain_sid, + uint32 *rid) +{ + fstring str; + DOM_SID sid; + + if (!smbldap_get_single_attribute(ldap_struct, entry, "sambaSID", + str, sizeof(str)-1)) { + DEBUG(10, ("Could not find sambaSID attribute\n")); + return False; + } + + if (!string_to_sid(&sid, str)) { + DEBUG(10, ("Could not convert string %s to sid\n", str)); + return False; + } + + if (sid_compare_domain(&sid, domain_sid) != 0) { + DEBUG(10, ("SID %s is not in expected domain %s\n", + str, sid_string_dbg(domain_sid))); + return False; + } + + if (!sid_peek_rid(&sid, rid)) { + DEBUG(10, ("Could not peek into RID\n")); + return False; + } + + return True; +} + +static NTSTATUS ldapsam_enum_group_members(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + const DOM_SID *group, + uint32 **pp_member_rids, + size_t *p_num_members) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + struct smbldap_state *conn = ldap_state->smbldap_state; + const char *id_attrs[] = { "memberUid", "gidNumber", NULL }; + const char *sid_attrs[] = { "sambaSID", NULL }; + NTSTATUS ret = NT_STATUS_UNSUCCESSFUL; + LDAPMessage *result = NULL; + LDAPMessage *entry; + char *filter; + char **values = NULL; + char **memberuid; + char *gidstr; + int rc, count; + + *pp_member_rids = NULL; + *p_num_members = 0; + + filter = talloc_asprintf(mem_ctx, + "(&(objectClass=%s)" + "(objectClass=%s)" + "(sambaSID=%s))", + LDAP_OBJ_POSIXGROUP, + LDAP_OBJ_GROUPMAP, + sid_string_talloc(mem_ctx, group)); + if (filter == NULL) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = smbldap_search(conn, lp_ldap_group_suffix(), + LDAP_SCOPE_SUBTREE, filter, id_attrs, 0, + &result); + + if (rc != LDAP_SUCCESS) + goto done; + + talloc_autofree_ldapmsg(mem_ctx, result); + + count = ldap_count_entries(conn->ldap_struct, result); + + if (count > 1) { + DEBUG(1, ("Found more than one groupmap entry for %s\n", + sid_string_dbg(group))); + ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + if (count == 0) { + ret = NT_STATUS_NO_SUCH_GROUP; + goto done; + } + + entry = ldap_first_entry(conn->ldap_struct, result); + if (entry == NULL) + goto done; + + gidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", mem_ctx); + if (!gidstr) { + DEBUG (0, ("ldapsam_enum_group_members: Unable to find the group's gid!\n")); + ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + values = ldap_get_values(conn->ldap_struct, entry, "memberUid"); + + if (values) { + + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(|", LDAP_OBJ_SAMBASAMACCOUNT); + if (filter == NULL) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + for (memberuid = values; *memberuid != NULL; memberuid += 1) { + char *escape_memberuid; + + escape_memberuid = escape_ldap_string_alloc(*memberuid); + if (escape_memberuid == NULL) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + filter = talloc_asprintf_append_buffer(filter, "(uid=%s)", escape_memberuid); + if (filter == NULL) { + SAFE_FREE(escape_memberuid); + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + SAFE_FREE(escape_memberuid); + } + + filter = talloc_asprintf_append_buffer(filter, "))"); + if (filter == NULL) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = smbldap_search(conn, lp_ldap_suffix(), + LDAP_SCOPE_SUBTREE, filter, sid_attrs, 0, + &result); + + if (rc != LDAP_SUCCESS) + goto done; + + count = ldap_count_entries(conn->ldap_struct, result); + DEBUG(10,("ldapsam_enum_group_members: found %d accounts\n", count)); + + talloc_autofree_ldapmsg(mem_ctx, result); + + for (entry = ldap_first_entry(conn->ldap_struct, result); + entry != NULL; + entry = ldap_next_entry(conn->ldap_struct, entry)) + { + char *sidstr; + DOM_SID sid; + uint32 rid; + + sidstr = smbldap_talloc_single_attribute(conn->ldap_struct, + entry, "sambaSID", + mem_ctx); + if (!sidstr) { + DEBUG(0, ("Severe DB error, %s can't miss the sambaSID" + "attribute\n", LDAP_OBJ_SAMBASAMACCOUNT)); + ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + if (!string_to_sid(&sid, sidstr)) + goto done; + + if (!sid_check_is_in_our_domain(&sid)) { + DEBUG(0, ("Inconsistent SAM -- group member uid not " + "in our domain\n")); + ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + sid_peek_rid(&sid, &rid); + + if (!add_rid_to_array_unique(mem_ctx, rid, pp_member_rids, + p_num_members)) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + } + } + + filter = talloc_asprintf(mem_ctx, + "(&(objectClass=%s)" + "(gidNumber=%s))", + LDAP_OBJ_SAMBASAMACCOUNT, + gidstr); + + rc = smbldap_search(conn, lp_ldap_suffix(), + LDAP_SCOPE_SUBTREE, filter, sid_attrs, 0, + &result); + + if (rc != LDAP_SUCCESS) + goto done; + + talloc_autofree_ldapmsg(mem_ctx, result); + + for (entry = ldap_first_entry(conn->ldap_struct, result); + entry != NULL; + entry = ldap_next_entry(conn->ldap_struct, entry)) + { + uint32 rid; + + if (!ldapsam_extract_rid_from_entry(conn->ldap_struct, + entry, + get_global_sam_sid(), + &rid)) { + DEBUG(0, ("Severe DB error, %s can't miss the samba SID" "attribute\n", LDAP_OBJ_SAMBASAMACCOUNT)); + ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + if (!add_rid_to_array_unique(mem_ctx, rid, pp_member_rids, + p_num_members)) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + } + + ret = NT_STATUS_OK; + + done: + + if (values) + ldap_value_free(values); + + return ret; +} + +static NTSTATUS ldapsam_enum_group_memberships(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + struct samu *user, + DOM_SID **pp_sids, + gid_t **pp_gids, + size_t *p_num_groups) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + struct smbldap_state *conn = ldap_state->smbldap_state; + char *filter; + const char *attrs[] = { "gidNumber", "sambaSID", NULL }; + char *escape_name; + int rc, count; + LDAPMessage *result = NULL; + LDAPMessage *entry; + NTSTATUS ret = NT_STATUS_UNSUCCESSFUL; + size_t num_sids, num_gids; + char *gidstr; + gid_t primary_gid = -1; + + *pp_sids = NULL; + num_sids = 0; + + if (pdb_get_username(user) == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + escape_name = escape_ldap_string_alloc(pdb_get_username(user)); + if (escape_name == NULL) + return NT_STATUS_NO_MEMORY; + + /* retrieve the users primary gid */ + filter = talloc_asprintf(mem_ctx, + "(&(objectClass=%s)(uid=%s))", + LDAP_OBJ_SAMBASAMACCOUNT, + escape_name); + if (filter == NULL) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = smbldap_search(conn, lp_ldap_suffix(), + LDAP_SCOPE_SUBTREE, filter, attrs, 0, &result); + + if (rc != LDAP_SUCCESS) + goto done; + + talloc_autofree_ldapmsg(mem_ctx, result); + + count = ldap_count_entries(priv2ld(ldap_state), result); + + switch (count) { + case 0: + DEBUG(1, ("User account [%s] not found!\n", pdb_get_username(user))); + ret = NT_STATUS_NO_SUCH_USER; + goto done; + case 1: + entry = ldap_first_entry(priv2ld(ldap_state), result); + + gidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", mem_ctx); + if (!gidstr) { + DEBUG (1, ("Unable to find the member's gid!\n")); + ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + primary_gid = strtoul(gidstr, NULL, 10); + break; + default: + DEBUG(1, ("found more than one account with the same user name ?!\n")); + ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + filter = talloc_asprintf(mem_ctx, + "(&(objectClass=%s)(|(memberUid=%s)(gidNumber=%d)))", + LDAP_OBJ_POSIXGROUP, escape_name, primary_gid); + if (filter == NULL) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = smbldap_search(conn, lp_ldap_group_suffix(), + LDAP_SCOPE_SUBTREE, filter, attrs, 0, &result); + + if (rc != LDAP_SUCCESS) + goto done; + + talloc_autofree_ldapmsg(mem_ctx, result); + + num_gids = 0; + *pp_gids = NULL; + + num_sids = 0; + *pp_sids = NULL; + + /* We need to add the primary group as the first gid/sid */ + + if (!add_gid_to_array_unique(mem_ctx, primary_gid, pp_gids, &num_gids)) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + /* This sid will be replaced later */ + + ret = add_sid_to_array_unique(mem_ctx, &global_sid_NULL, pp_sids, + &num_sids); + if (!NT_STATUS_IS_OK(ret)) { + goto done; + } + + for (entry = ldap_first_entry(conn->ldap_struct, result); + entry != NULL; + entry = ldap_next_entry(conn->ldap_struct, entry)) + { + fstring str; + DOM_SID sid; + gid_t gid; + char *end; + + if (!smbldap_get_single_attribute(conn->ldap_struct, + entry, "sambaSID", + str, sizeof(str)-1)) + continue; + + if (!string_to_sid(&sid, str)) + goto done; + + if (!smbldap_get_single_attribute(conn->ldap_struct, + entry, "gidNumber", + str, sizeof(str)-1)) + continue; + + gid = strtoul(str, &end, 10); + + if (PTR_DIFF(end, str) != strlen(str)) + goto done; + + if (gid == primary_gid) { + sid_copy(&(*pp_sids)[0], &sid); + } else { + if (!add_gid_to_array_unique(mem_ctx, gid, pp_gids, + &num_gids)) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + ret = add_sid_to_array_unique(mem_ctx, &sid, pp_sids, + &num_sids); + if (!NT_STATUS_IS_OK(ret)) { + goto done; + } + } + } + + if (sid_compare(&global_sid_NULL, &(*pp_sids)[0]) == 0) { + DEBUG(3, ("primary group of [%s] not found\n", + pdb_get_username(user))); + goto done; + } + + *p_num_groups = num_sids; + + ret = NT_STATUS_OK; + + done: + + SAFE_FREE(escape_name); + return ret; +} + +/********************************************************************** + * Augment a posixGroup object with a sambaGroupMapping domgroup + *********************************************************************/ + +static NTSTATUS ldapsam_map_posixgroup(TALLOC_CTX *mem_ctx, + struct ldapsam_privates *ldap_state, + GROUP_MAP *map) +{ + const char *filter, *dn; + LDAPMessage *msg, *entry; + LDAPMod **mods; + int rc; + + filter = talloc_asprintf(mem_ctx, + "(&(objectClass=%s)(gidNumber=%u))", + LDAP_OBJ_POSIXGROUP, map->gid); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, + get_attr_list(mem_ctx, groupmap_attr_list), + &msg); + talloc_autofree_ldapmsg(mem_ctx, msg); + + if ((rc != LDAP_SUCCESS) || + (ldap_count_entries(ldap_state->smbldap_state->ldap_struct, msg) != 1) || + ((entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, msg)) == NULL)) { + return NT_STATUS_NO_SUCH_GROUP; + } + + dn = smbldap_talloc_dn(mem_ctx, ldap_state->smbldap_state->ldap_struct, entry); + if (dn == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mods = NULL; + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", + LDAP_OBJ_GROUPMAP); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, entry, &mods, "sambaSid", + sid_string_talloc(mem_ctx, &map->sid)); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, entry, &mods, "sambaGroupType", + talloc_asprintf(mem_ctx, "%d", map->sid_name_use)); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, entry, &mods, "displayName", + map->nt_name); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, entry, &mods, "description", + map->comment); + talloc_autofree_ldapmod(mem_ctx, mods); + + rc = smbldap_modify(ldap_state->smbldap_state, dn, mods); + if (rc != LDAP_SUCCESS) { + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_add_group_mapping_entry(struct pdb_methods *methods, + GROUP_MAP *map) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + LDAPMessage *msg = NULL; + LDAPMod **mods = NULL; + const char *attrs[] = { NULL }; + char *filter; + + char *dn; + TALLOC_CTX *mem_ctx; + NTSTATUS result; + + DOM_SID sid; + + int rc; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + DEBUG(0, ("talloc_new failed\n")); + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf(mem_ctx, "(sambaSid=%s)", + sid_string_talloc(mem_ctx, &map->sid)); + if (filter == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = smbldap_search(ldap_state->smbldap_state, lp_ldap_suffix(), + LDAP_SCOPE_SUBTREE, filter, attrs, True, &msg); + talloc_autofree_ldapmsg(mem_ctx, msg); + + if ((rc == LDAP_SUCCESS) && + (ldap_count_entries(ldap_state->smbldap_state->ldap_struct, msg) > 0)) { + + DEBUG(3, ("SID %s already present in LDAP, refusing to add " + "group mapping entry\n", sid_string_dbg(&map->sid))); + result = NT_STATUS_GROUP_EXISTS; + goto done; + } + + switch (map->sid_name_use) { + + case SID_NAME_DOM_GRP: + /* To map a domain group we need to have a posix group + to attach to. */ + result = ldapsam_map_posixgroup(mem_ctx, ldap_state, map); + goto done; + break; + + case SID_NAME_ALIAS: + if (!sid_check_is_in_our_domain(&map->sid) + && !sid_check_is_in_builtin(&map->sid) ) + { + DEBUG(3, ("Refusing to map sid %s as an alias, not in our domain\n", + sid_string_dbg(&map->sid))); + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + break; + + default: + DEBUG(3, ("Got invalid use '%s' for mapping\n", + sid_type_lookup(map->sid_name_use))); + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + /* Domain groups have been mapped in a separate routine, we have to + * create an alias now */ + + if (map->gid == -1) { + DEBUG(10, ("Refusing to map gid==-1\n")); + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + if (pdb_gid_to_sid(map->gid, &sid)) { + DEBUG(3, ("Gid %d is already mapped to SID %s, refusing to " + "add\n", map->gid, sid_string_dbg(&sid))); + result = NT_STATUS_GROUP_EXISTS; + goto done; + } + + /* Ok, enough checks done. It's still racy to go ahead now, but that's + * the best we can get out of LDAP. */ + + dn = talloc_asprintf(mem_ctx, "sambaSid=%s,%s", + sid_string_talloc(mem_ctx, &map->sid), + lp_ldap_group_suffix()); + if (dn == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + mods = NULL; + + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, NULL, &mods, "objectClass", + LDAP_OBJ_SID_ENTRY); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, NULL, &mods, "objectClass", + LDAP_OBJ_GROUPMAP); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, NULL, &mods, "sambaSid", + sid_string_talloc(mem_ctx, &map->sid)); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, NULL, &mods, "sambaGroupType", + talloc_asprintf(mem_ctx, "%d", map->sid_name_use)); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, NULL, &mods, "displayName", + map->nt_name); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, NULL, &mods, "description", + map->comment); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, NULL, &mods, "gidNumber", + talloc_asprintf(mem_ctx, "%u", map->gid)); + talloc_autofree_ldapmod(mem_ctx, mods); + + rc = smbldap_add(ldap_state->smbldap_state, dn, mods); + + result = (rc == LDAP_SUCCESS) ? + NT_STATUS_OK : NT_STATUS_ACCESS_DENIED; + + done: + TALLOC_FREE(mem_ctx); + return result; +} + +/********************************************************************** + * Update a group mapping entry. We're quite strict about what can be changed: + * Only the description and displayname may be changed. It simply does not + * make any sense to change the SID, gid or the type in a mapping. + *********************************************************************/ + +static NTSTATUS ldapsam_update_group_mapping_entry(struct pdb_methods *methods, + GROUP_MAP *map) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + int rc; + const char *filter, *dn; + LDAPMessage *msg = NULL; + LDAPMessage *entry = NULL; + LDAPMod **mods = NULL; + TALLOC_CTX *mem_ctx; + NTSTATUS result; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + DEBUG(0, ("talloc_new failed\n")); + return NT_STATUS_NO_MEMORY; + } + + /* Make 100% sure that sid, gid and type are not changed by looking up + * exactly the values we're given in LDAP. */ + + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)" + "(sambaSid=%s)(gidNumber=%u)" + "(sambaGroupType=%d))", + LDAP_OBJ_GROUPMAP, + sid_string_talloc(mem_ctx, &map->sid), + map->gid, map->sid_name_use); + if (filter == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, + get_attr_list(mem_ctx, groupmap_attr_list), + &msg); + talloc_autofree_ldapmsg(mem_ctx, msg); + + if ((rc != LDAP_SUCCESS) || + (ldap_count_entries(ldap_state->smbldap_state->ldap_struct, msg) != 1) || + ((entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, msg)) == NULL)) { + result = NT_STATUS_NO_SUCH_GROUP; + goto done; + } + + dn = smbldap_talloc_dn(mem_ctx, ldap_state->smbldap_state->ldap_struct, entry); + + if (dn == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + mods = NULL; + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, entry, &mods, "displayName", + map->nt_name); + smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, entry, &mods, "description", + map->comment); + talloc_autofree_ldapmod(mem_ctx, mods); + + if (mods == NULL) { + DEBUG(4, ("ldapsam_update_group_mapping_entry: mods is empty: " + "nothing to do\n")); + result = NT_STATUS_OK; + goto done; + } + + rc = smbldap_modify(ldap_state->smbldap_state, dn, mods); + + if (rc != LDAP_SUCCESS) { + result = NT_STATUS_ACCESS_DENIED; + goto done; + } + + DEBUG(2, ("ldapsam_update_group_mapping_entry: successfully modified " + "group %lu in LDAP\n", (unsigned long)map->gid)); + + result = NT_STATUS_OK; + + done: + TALLOC_FREE(mem_ctx); + return result; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS ldapsam_delete_group_mapping_entry(struct pdb_methods *methods, + DOM_SID sid) +{ + struct ldapsam_privates *priv = + (struct ldapsam_privates *)methods->private_data; + LDAPMessage *msg, *entry; + int rc; + NTSTATUS result; + TALLOC_CTX *mem_ctx; + char *filter; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + DEBUG(0, ("talloc_new failed\n")); + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf(mem_ctx, "(&(objectClass=%s)(%s=%s))", + LDAP_OBJ_GROUPMAP, LDAP_ATTRIBUTE_SID, + sid_string_talloc(mem_ctx, &sid)); + if (filter == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + rc = smbldap_search_suffix(priv->smbldap_state, filter, + get_attr_list(mem_ctx, groupmap_attr_list), + &msg); + talloc_autofree_ldapmsg(mem_ctx, msg); + + if ((rc != LDAP_SUCCESS) || + (ldap_count_entries(priv2ld(priv), msg) != 1) || + ((entry = ldap_first_entry(priv2ld(priv), msg)) == NULL)) { + result = NT_STATUS_NO_SUCH_GROUP; + goto done; + } + + rc = ldapsam_delete_entry(priv, mem_ctx, entry, LDAP_OBJ_GROUPMAP, + get_attr_list(mem_ctx, + groupmap_attr_list_to_delete)); + + if ((rc == LDAP_NAMING_VIOLATION) || + (rc == LDAP_OBJECT_CLASS_VIOLATION)) { + const char *attrs[] = { "sambaGroupType", "description", + "displayName", "sambaSIDList", + NULL }; + + /* Second try. Don't delete the sambaSID attribute, this is + for "old" entries that are tacked on a winbind + sambaIdmapEntry. */ + + rc = ldapsam_delete_entry(priv, mem_ctx, entry, + LDAP_OBJ_GROUPMAP, attrs); + } + + if ((rc == LDAP_NAMING_VIOLATION) || + (rc == LDAP_OBJECT_CLASS_VIOLATION)) { + const char *attrs[] = { "sambaGroupType", "description", + "displayName", "sambaSIDList", + "gidNumber", NULL }; + + /* Third try. This is a post-3.0.21 alias (containing only + * sambaSidEntry and sambaGroupMapping classes), we also have + * to delete the gidNumber attribute, only the sambaSidEntry + * remains */ + + rc = ldapsam_delete_entry(priv, mem_ctx, entry, + LDAP_OBJ_GROUPMAP, attrs); + } + + result = (rc == LDAP_SUCCESS) ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL; + + done: + TALLOC_FREE(mem_ctx); + return result; + } + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS ldapsam_setsamgrent(struct pdb_methods *my_methods, + bool update) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)my_methods->private_data; + char *filter = NULL; + int rc; + const char **attr_list; + + filter = talloc_asprintf(NULL, "(objectclass=%s)", LDAP_OBJ_GROUPMAP); + if (!filter) { + return NT_STATUS_NO_MEMORY; + } + attr_list = get_attr_list( NULL, groupmap_attr_list ); + rc = smbldap_search(ldap_state->smbldap_state, lp_ldap_group_suffix(), + LDAP_SCOPE_SUBTREE, filter, + attr_list, 0, &ldap_state->result); + TALLOC_FREE(attr_list); + + if (rc != LDAP_SUCCESS) { + DEBUG(0, ("ldapsam_setsamgrent: LDAP search failed: %s\n", + ldap_err2string(rc))); + DEBUG(3, ("ldapsam_setsamgrent: Query was: %s, %s\n", + lp_ldap_group_suffix(), filter)); + ldap_msgfree(ldap_state->result); + ldap_state->result = NULL; + TALLOC_FREE(filter); + return NT_STATUS_UNSUCCESSFUL; + } + + TALLOC_FREE(filter); + + DEBUG(2, ("ldapsam_setsamgrent: %d entries in the base!\n", + ldap_count_entries(ldap_state->smbldap_state->ldap_struct, + ldap_state->result))); + + ldap_state->entry = + ldap_first_entry(ldap_state->smbldap_state->ldap_struct, + ldap_state->result); + ldap_state->index = 0; + + return NT_STATUS_OK; +} + +/********************************************************************** + *********************************************************************/ + +static void ldapsam_endsamgrent(struct pdb_methods *my_methods) +{ + ldapsam_endsampwent(my_methods); +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS ldapsam_getsamgrent(struct pdb_methods *my_methods, + GROUP_MAP *map) +{ + NTSTATUS ret = NT_STATUS_UNSUCCESSFUL; + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)my_methods->private_data; + bool bret = False; + + while (!bret) { + if (!ldap_state->entry) + return ret; + + ldap_state->index++; + bret = init_group_from_ldap(ldap_state, map, + ldap_state->entry); + + ldap_state->entry = + ldap_next_entry(ldap_state->smbldap_state->ldap_struct, + ldap_state->entry); + } + + return NT_STATUS_OK; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS ldapsam_enum_group_mapping(struct pdb_methods *methods, + const DOM_SID *domsid, enum lsa_SidType sid_name_use, + GROUP_MAP **pp_rmap, + size_t *p_num_entries, + bool unix_only) +{ + GROUP_MAP map; + size_t entries = 0; + + *p_num_entries = 0; + *pp_rmap = NULL; + + if (!NT_STATUS_IS_OK(ldapsam_setsamgrent(methods, False))) { + DEBUG(0, ("ldapsam_enum_group_mapping: Unable to open " + "passdb\n")); + return NT_STATUS_ACCESS_DENIED; + } + + while (NT_STATUS_IS_OK(ldapsam_getsamgrent(methods, &map))) { + if (sid_name_use != SID_NAME_UNKNOWN && + sid_name_use != map.sid_name_use) { + DEBUG(11,("ldapsam_enum_group_mapping: group %s is " + "not of the requested type\n", map.nt_name)); + continue; + } + if (unix_only==ENUM_ONLY_MAPPED && map.gid==-1) { + DEBUG(11,("ldapsam_enum_group_mapping: group %s is " + "non mapped\n", map.nt_name)); + continue; + } + + (*pp_rmap)=SMB_REALLOC_ARRAY((*pp_rmap), GROUP_MAP, entries+1); + if (!(*pp_rmap)) { + DEBUG(0,("ldapsam_enum_group_mapping: Unable to " + "enlarge group map!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + (*pp_rmap)[entries] = map; + + entries += 1; + + } + ldapsam_endsamgrent(methods); + + *p_num_entries = entries; + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_modify_aliasmem(struct pdb_methods *methods, + const DOM_SID *alias, + const DOM_SID *member, + int modop) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + char *dn = NULL; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + int count; + LDAPMod **mods = NULL; + int rc; + enum lsa_SidType type = SID_NAME_USE_NONE; + fstring tmp; + + char *filter = NULL; + + if (sid_check_is_in_builtin(alias)) { + type = SID_NAME_ALIAS; + } + + if (sid_check_is_in_our_domain(alias)) { + type = SID_NAME_ALIAS; + } + + if (type == SID_NAME_USE_NONE) { + DEBUG(5, ("SID %s is neither in builtin nor in our domain!\n", + sid_string_dbg(alias))); + return NT_STATUS_NO_SUCH_ALIAS; + } + + if (asprintf(&filter, + "(&(objectClass=%s)(sambaSid=%s)(sambaGroupType=%d))", + LDAP_OBJ_GROUPMAP, sid_to_fstring(tmp, alias), + type) < 0) { + return NT_STATUS_NO_MEMORY; + } + + if (ldapsam_search_one_group(ldap_state, filter, + &result) != LDAP_SUCCESS) { + SAFE_FREE(filter); + return NT_STATUS_NO_SUCH_ALIAS; + } + + count = ldap_count_entries(ldap_state->smbldap_state->ldap_struct, + result); + + if (count < 1) { + DEBUG(4, ("ldapsam_modify_aliasmem: Did not find alias\n")); + ldap_msgfree(result); + SAFE_FREE(filter); + return NT_STATUS_NO_SUCH_ALIAS; + } + + if (count > 1) { + DEBUG(1, ("ldapsam_modify_aliasmem: Duplicate entries for " + "filter %s: count=%d\n", filter, count)); + ldap_msgfree(result); + SAFE_FREE(filter); + return NT_STATUS_NO_SUCH_ALIAS; + } + + SAFE_FREE(filter); + + entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, + result); + + if (!entry) { + ldap_msgfree(result); + return NT_STATUS_UNSUCCESSFUL; + } + + dn = smbldap_get_dn(ldap_state->smbldap_state->ldap_struct, entry); + if (!dn) { + ldap_msgfree(result); + return NT_STATUS_UNSUCCESSFUL; + } + + smbldap_set_mod(&mods, modop, + get_attr_key2string(groupmap_attr_list, + LDAP_ATTR_SID_LIST), + sid_to_fstring(tmp, member)); + + rc = smbldap_modify(ldap_state->smbldap_state, dn, mods); + + ldap_mods_free(mods, True); + ldap_msgfree(result); + SAFE_FREE(dn); + + if (rc == LDAP_TYPE_OR_VALUE_EXISTS) { + return NT_STATUS_MEMBER_IN_ALIAS; + } + + if (rc == LDAP_NO_SUCH_ATTRIBUTE) { + return NT_STATUS_MEMBER_NOT_IN_ALIAS; + } + + if (rc != LDAP_SUCCESS) { + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_add_aliasmem(struct pdb_methods *methods, + const DOM_SID *alias, + const DOM_SID *member) +{ + return ldapsam_modify_aliasmem(methods, alias, member, LDAP_MOD_ADD); +} + +static NTSTATUS ldapsam_del_aliasmem(struct pdb_methods *methods, + const DOM_SID *alias, + const DOM_SID *member) +{ + return ldapsam_modify_aliasmem(methods, alias, member, + LDAP_MOD_DELETE); +} + +static NTSTATUS ldapsam_enum_aliasmem(struct pdb_methods *methods, + const DOM_SID *alias, + DOM_SID **pp_members, + size_t *p_num_members) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + int count; + char **values = NULL; + int i; + char *filter = NULL; + size_t num_members = 0; + enum lsa_SidType type = SID_NAME_USE_NONE; + fstring tmp; + + *pp_members = NULL; + *p_num_members = 0; + + if (sid_check_is_in_builtin(alias)) { + type = SID_NAME_ALIAS; + } + + if (sid_check_is_in_our_domain(alias)) { + type = SID_NAME_ALIAS; + } + + if (type == SID_NAME_USE_NONE) { + DEBUG(5, ("SID %s is neither in builtin nor in our domain!\n", + sid_string_dbg(alias))); + return NT_STATUS_NO_SUCH_ALIAS; + } + + if (asprintf(&filter, + "(&(objectClass=%s)(sambaSid=%s)(sambaGroupType=%d))", + LDAP_OBJ_GROUPMAP, sid_to_fstring(tmp, alias), + type) < 0) { + return NT_STATUS_NO_MEMORY; + } + + if (ldapsam_search_one_group(ldap_state, filter, + &result) != LDAP_SUCCESS) { + SAFE_FREE(filter); + return NT_STATUS_NO_SUCH_ALIAS; + } + + count = ldap_count_entries(ldap_state->smbldap_state->ldap_struct, + result); + + if (count < 1) { + DEBUG(4, ("ldapsam_enum_aliasmem: Did not find alias\n")); + ldap_msgfree(result); + SAFE_FREE(filter); + return NT_STATUS_NO_SUCH_ALIAS; + } + + if (count > 1) { + DEBUG(1, ("ldapsam_enum_aliasmem: Duplicate entries for " + "filter %s: count=%d\n", filter, count)); + ldap_msgfree(result); + SAFE_FREE(filter); + return NT_STATUS_NO_SUCH_ALIAS; + } + + SAFE_FREE(filter); + + entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, + result); + + if (!entry) { + ldap_msgfree(result); + return NT_STATUS_UNSUCCESSFUL; + } + + values = ldap_get_values(ldap_state->smbldap_state->ldap_struct, + entry, + get_attr_key2string(groupmap_attr_list, + LDAP_ATTR_SID_LIST)); + + if (values == NULL) { + ldap_msgfree(result); + return NT_STATUS_OK; + } + + count = ldap_count_values(values); + + for (i=0; i<count; i++) { + DOM_SID member; + NTSTATUS status; + + if (!string_to_sid(&member, values[i])) + continue; + + status = add_sid_to_array(NULL, &member, pp_members, + &num_members); + if (!NT_STATUS_IS_OK(status)) { + ldap_value_free(values); + ldap_msgfree(result); + return status; + } + } + + *p_num_members = num_members; + ldap_value_free(values); + ldap_msgfree(result); + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_alias_memberships(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + const DOM_SID *domain_sid, + const DOM_SID *members, + size_t num_members, + uint32 **pp_alias_rids, + size_t *p_num_alias_rids) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + LDAP *ldap_struct; + + const char *attrs[] = { LDAP_ATTRIBUTE_SID, NULL }; + + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + int i; + int rc; + char *filter; + enum lsa_SidType type = SID_NAME_USE_NONE; + + if (sid_check_is_builtin(domain_sid)) { + type = SID_NAME_ALIAS; + } + + if (sid_check_is_domain(domain_sid)) { + type = SID_NAME_ALIAS; + } + + if (type == SID_NAME_USE_NONE) { + DEBUG(5, ("SID %s is neither builtin nor domain!\n", + sid_string_dbg(domain_sid))); + return NT_STATUS_UNSUCCESSFUL; + } + + filter = talloc_asprintf(mem_ctx, + "(&(|(objectclass=%s)(sambaGroupType=%d))(|", + LDAP_OBJ_GROUPMAP, type); + + for (i=0; i<num_members; i++) + filter = talloc_asprintf(mem_ctx, "%s(sambaSIDList=%s)", + filter, + sid_string_talloc(mem_ctx, + &members[i])); + + filter = talloc_asprintf(mem_ctx, "%s))", filter); + + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + rc = smbldap_search(ldap_state->smbldap_state, lp_ldap_group_suffix(), + LDAP_SCOPE_SUBTREE, filter, attrs, 0, &result); + + if (rc != LDAP_SUCCESS) + return NT_STATUS_UNSUCCESSFUL; + + ldap_struct = ldap_state->smbldap_state->ldap_struct; + + for (entry = ldap_first_entry(ldap_struct, result); + entry != NULL; + entry = ldap_next_entry(ldap_struct, entry)) + { + fstring sid_str; + DOM_SID sid; + uint32 rid; + + if (!smbldap_get_single_attribute(ldap_struct, entry, + LDAP_ATTRIBUTE_SID, + sid_str, + sizeof(sid_str)-1)) + continue; + + if (!string_to_sid(&sid, sid_str)) + continue; + + if (!sid_peek_check_rid(domain_sid, &sid, &rid)) + continue; + + if (!add_rid_to_array_unique(mem_ctx, rid, pp_alias_rids, + p_num_alias_rids)) { + ldap_msgfree(result); + return NT_STATUS_NO_MEMORY; + } + } + + ldap_msgfree(result); + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_set_account_policy_in_ldap(struct pdb_methods *methods, + int policy_index, + uint32 value) +{ + NTSTATUS ntstatus = NT_STATUS_UNSUCCESSFUL; + int rc; + LDAPMod **mods = NULL; + fstring value_string; + const char *policy_attr = NULL; + + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + + DEBUG(10,("ldapsam_set_account_policy_in_ldap\n")); + + if (!ldap_state->domain_dn) { + return NT_STATUS_INVALID_PARAMETER; + } + + policy_attr = get_account_policy_attr(policy_index); + if (policy_attr == NULL) { + DEBUG(0,("ldapsam_set_account_policy_in_ldap: invalid " + "policy\n")); + return ntstatus; + } + + slprintf(value_string, sizeof(value_string) - 1, "%i", value); + + smbldap_set_mod(&mods, LDAP_MOD_REPLACE, policy_attr, value_string); + + rc = smbldap_modify(ldap_state->smbldap_state, ldap_state->domain_dn, + mods); + + ldap_mods_free(mods, True); + + if (rc != LDAP_SUCCESS) { + return ntstatus; + } + + if (!cache_account_policy_set(policy_index, value)) { + DEBUG(0,("ldapsam_set_account_policy_in_ldap: failed to " + "update local tdb cache\n")); + return ntstatus; + } + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_set_account_policy(struct pdb_methods *methods, + int policy_index, uint32 value) +{ + return ldapsam_set_account_policy_in_ldap(methods, policy_index, + value); +} + +static NTSTATUS ldapsam_get_account_policy_from_ldap(struct pdb_methods *methods, + int policy_index, + uint32 *value) +{ + NTSTATUS ntstatus = NT_STATUS_UNSUCCESSFUL; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + int count; + int rc; + char **vals = NULL; + const char *policy_attr = NULL; + + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + + const char *attrs[2]; + + DEBUG(10,("ldapsam_get_account_policy_from_ldap\n")); + + if (!ldap_state->domain_dn) { + return NT_STATUS_INVALID_PARAMETER; + } + + policy_attr = get_account_policy_attr(policy_index); + if (!policy_attr) { + DEBUG(0,("ldapsam_get_account_policy_from_ldap: invalid " + "policy index: %d\n", policy_index)); + return ntstatus; + } + + attrs[0] = policy_attr; + attrs[1] = NULL; + + rc = smbldap_search(ldap_state->smbldap_state, ldap_state->domain_dn, + LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0, + &result); + + if (rc != LDAP_SUCCESS) { + return ntstatus; + } + + count = ldap_count_entries(priv2ld(ldap_state), result); + if (count < 1) { + goto out; + } + + entry = ldap_first_entry(priv2ld(ldap_state), result); + if (entry == NULL) { + goto out; + } + + vals = ldap_get_values(priv2ld(ldap_state), entry, policy_attr); + if (vals == NULL) { + goto out; + } + + *value = (uint32)atol(vals[0]); + + ntstatus = NT_STATUS_OK; + +out: + if (vals) + ldap_value_free(vals); + ldap_msgfree(result); + + return ntstatus; +} + +/* wrapper around ldapsam_get_account_policy_from_ldap(), handles tdb as cache + + - if user hasn't decided to use account policies inside LDAP just reuse the + old tdb values + + - if there is a valid cache entry, return that + - if there is an LDAP entry, update cache and return + - otherwise set to default, update cache and return + + Guenther +*/ +static NTSTATUS ldapsam_get_account_policy(struct pdb_methods *methods, + int policy_index, uint32 *value) +{ + NTSTATUS ntstatus = NT_STATUS_UNSUCCESSFUL; + + if (cache_account_policy_get(policy_index, value)) { + DEBUG(11,("ldapsam_get_account_policy: got valid value from " + "cache\n")); + return NT_STATUS_OK; + } + + ntstatus = ldapsam_get_account_policy_from_ldap(methods, policy_index, + value); + if (NT_STATUS_IS_OK(ntstatus)) { + goto update_cache; + } + + DEBUG(10,("ldapsam_get_account_policy: failed to retrieve from " + "ldap\n")); + +#if 0 + /* should we automagically migrate old tdb value here ? */ + if (account_policy_get(policy_index, value)) + goto update_ldap; + + DEBUG(10,("ldapsam_get_account_policy: no tdb for %d, trying " + "default\n", policy_index)); +#endif + + if (!account_policy_get_default(policy_index, value)) { + return ntstatus; + } + +/* update_ldap: */ + + ntstatus = ldapsam_set_account_policy(methods, policy_index, *value); + if (!NT_STATUS_IS_OK(ntstatus)) { + return ntstatus; + } + + update_cache: + + if (!cache_account_policy_set(policy_index, *value)) { + DEBUG(0,("ldapsam_get_account_policy: failed to update local " + "tdb as a cache\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_lookup_rids(struct pdb_methods *methods, + const DOM_SID *domain_sid, + int num_rids, + uint32 *rids, + const char **names, + enum lsa_SidType *attrs) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + LDAPMessage *msg = NULL; + LDAPMessage *entry; + char *allsids = NULL; + int i, rc, num_mapped; + NTSTATUS result = NT_STATUS_NO_MEMORY; + TALLOC_CTX *mem_ctx; + LDAP *ld; + bool is_builtin; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + DEBUG(0, ("talloc_new failed\n")); + goto done; + } + + if (!sid_check_is_builtin(domain_sid) && + !sid_check_is_domain(domain_sid)) { + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + for (i=0; i<num_rids; i++) + attrs[i] = SID_NAME_UNKNOWN; + + allsids = talloc_strdup(mem_ctx, ""); + if (allsids == NULL) { + goto done; + } + + for (i=0; i<num_rids; i++) { + DOM_SID sid; + sid_compose(&sid, domain_sid, rids[i]); + allsids = talloc_asprintf_append_buffer( + allsids, "(sambaSid=%s)", + sid_string_talloc(mem_ctx, &sid)); + if (allsids == NULL) { + goto done; + } + } + + /* First look for users */ + + { + char *filter; + const char *ldap_attrs[] = { "uid", "sambaSid", NULL }; + + filter = talloc_asprintf( + mem_ctx, ("(&(objectClass=%s)(|%s))"), + LDAP_OBJ_SAMBASAMACCOUNT, allsids); + + if (filter == NULL) { + goto done; + } + + rc = smbldap_search(ldap_state->smbldap_state, + lp_ldap_user_suffix(), + LDAP_SCOPE_SUBTREE, filter, ldap_attrs, 0, + &msg); + talloc_autofree_ldapmsg(mem_ctx, msg); + } + + if (rc != LDAP_SUCCESS) + goto done; + + ld = ldap_state->smbldap_state->ldap_struct; + num_mapped = 0; + + for (entry = ldap_first_entry(ld, msg); + entry != NULL; + entry = ldap_next_entry(ld, entry)) { + uint32 rid; + int rid_index; + const char *name; + + if (!ldapsam_extract_rid_from_entry(ld, entry, domain_sid, + &rid)) { + DEBUG(2, ("Could not find sid from ldap entry\n")); + continue; + } + + name = smbldap_talloc_single_attribute(ld, entry, "uid", + names); + if (name == NULL) { + DEBUG(2, ("Could not retrieve uid attribute\n")); + continue; + } + + for (rid_index = 0; rid_index < num_rids; rid_index++) { + if (rid == rids[rid_index]) + break; + } + + if (rid_index == num_rids) { + DEBUG(2, ("Got a RID not asked for: %d\n", rid)); + continue; + } + + attrs[rid_index] = SID_NAME_USER; + names[rid_index] = name; + num_mapped += 1; + } + + if (num_mapped == num_rids) { + /* No need to look for groups anymore -- we're done */ + result = NT_STATUS_OK; + goto done; + } + + /* Same game for groups */ + + { + char *filter; + const char *ldap_attrs[] = { "cn", "displayName", "sambaSid", + "sambaGroupType", NULL }; + + filter = talloc_asprintf( + mem_ctx, "(&(objectClass=%s)(|%s))", + LDAP_OBJ_GROUPMAP, allsids); + if (filter == NULL) { + goto done; + } + + rc = smbldap_search(ldap_state->smbldap_state, + lp_ldap_group_suffix(), + LDAP_SCOPE_SUBTREE, filter, ldap_attrs, 0, + &msg); + talloc_autofree_ldapmsg(mem_ctx, msg); + } + + if (rc != LDAP_SUCCESS) + goto done; + + /* ldap_struct might have changed due to a reconnect */ + + ld = ldap_state->smbldap_state->ldap_struct; + + /* For consistency checks, we already checked we're only domain or builtin */ + + is_builtin = sid_check_is_builtin(domain_sid); + + for (entry = ldap_first_entry(ld, msg); + entry != NULL; + entry = ldap_next_entry(ld, entry)) + { + uint32 rid; + int rid_index; + const char *attr; + enum lsa_SidType type; + const char *dn = smbldap_talloc_dn(mem_ctx, ld, entry); + + attr = smbldap_talloc_single_attribute(ld, entry, "sambaGroupType", + mem_ctx); + if (attr == NULL) { + DEBUG(2, ("Could not extract type from ldap entry %s\n", + dn)); + continue; + } + + type = (enum lsa_SidType)atol(attr); + + /* Consistency checks */ + if ((is_builtin && (type != SID_NAME_ALIAS)) || + (!is_builtin && ((type != SID_NAME_ALIAS) && + (type != SID_NAME_DOM_GRP)))) { + DEBUG(2, ("Rejecting invalid group mapping entry %s\n", dn)); + } + + if (!ldapsam_extract_rid_from_entry(ld, entry, domain_sid, + &rid)) { + DEBUG(2, ("Could not find sid from ldap entry %s\n", dn)); + continue; + } + + attr = smbldap_talloc_single_attribute(ld, entry, "displayName", names); + + if (attr == NULL) { + DEBUG(10, ("Could not retrieve 'displayName' attribute from %s\n", + dn)); + attr = smbldap_talloc_single_attribute(ld, entry, "cn", names); + } + + if (attr == NULL) { + DEBUG(2, ("Could not retrieve naming attribute from %s\n", + dn)); + continue; + } + + for (rid_index = 0; rid_index < num_rids; rid_index++) { + if (rid == rids[rid_index]) + break; + } + + if (rid_index == num_rids) { + DEBUG(2, ("Got a RID not asked for: %d\n", rid)); + continue; + } + + attrs[rid_index] = type; + names[rid_index] = attr; + num_mapped += 1; + } + + result = NT_STATUS_NONE_MAPPED; + + if (num_mapped > 0) + result = (num_mapped == num_rids) ? + NT_STATUS_OK : STATUS_SOME_UNMAPPED; + done: + TALLOC_FREE(mem_ctx); + return result; +} + +static char *get_ldap_filter(TALLOC_CTX *mem_ctx, const char *username) +{ + char *filter = NULL; + char *escaped = NULL; + char *result = NULL; + + asprintf(&filter, "(&%s(objectclass=%s))", + "(uid=%u)", LDAP_OBJ_SAMBASAMACCOUNT); + if (filter == NULL) goto done; + + escaped = escape_ldap_string_alloc(username); + if (escaped == NULL) goto done; + + result = talloc_string_sub(mem_ctx, filter, "%u", username); + + done: + SAFE_FREE(filter); + SAFE_FREE(escaped); + + return result; +} + +const char **talloc_attrs(TALLOC_CTX *mem_ctx, ...) +{ + int i, num = 0; + va_list ap; + const char **result; + + va_start(ap, mem_ctx); + while (va_arg(ap, const char *) != NULL) + num += 1; + va_end(ap); + + if ((result = TALLOC_ARRAY(mem_ctx, const char *, num+1)) == NULL) { + return NULL; + } + + va_start(ap, mem_ctx); + for (i=0; i<num; i++) { + result[i] = talloc_strdup(result, va_arg(ap, const char*)); + if (result[i] == NULL) { + talloc_free(result); + return NULL; + } + } + va_end(ap); + + result[num] = NULL; + return result; +} + +struct ldap_search_state { + struct smbldap_state *connection; + + uint32 acct_flags; + uint16 group_type; + + const char *base; + int scope; + const char *filter; + const char **attrs; + int attrsonly; + void *pagedresults_cookie; + + LDAPMessage *entries, *current_entry; + bool (*ldap2displayentry)(struct ldap_search_state *state, + TALLOC_CTX *mem_ctx, + LDAP *ld, LDAPMessage *entry, + struct samr_displayentry *result); +}; + +static bool ldapsam_search_firstpage(struct pdb_search *search) +{ + struct ldap_search_state *state = + (struct ldap_search_state *)search->private_data; + LDAP *ld; + int rc = LDAP_OPERATIONS_ERROR; + + state->entries = NULL; + + if (state->connection->paged_results) { + rc = smbldap_search_paged(state->connection, state->base, + state->scope, state->filter, + state->attrs, state->attrsonly, + lp_ldap_page_size(), &state->entries, + &state->pagedresults_cookie); + } + + if ((rc != LDAP_SUCCESS) || (state->entries == NULL)) { + + if (state->entries != NULL) { + /* Left over from unsuccessful paged attempt */ + ldap_msgfree(state->entries); + state->entries = NULL; + } + + rc = smbldap_search(state->connection, state->base, + state->scope, state->filter, state->attrs, + state->attrsonly, &state->entries); + + if ((rc != LDAP_SUCCESS) || (state->entries == NULL)) + return False; + + /* Ok, the server was lying. It told us it could do paged + * searches when it could not. */ + state->connection->paged_results = False; + } + + ld = state->connection->ldap_struct; + if ( ld == NULL) { + DEBUG(5, ("Don't have an LDAP connection right after a " + "search\n")); + return False; + } + state->current_entry = ldap_first_entry(ld, state->entries); + + if (state->current_entry == NULL) { + ldap_msgfree(state->entries); + state->entries = NULL; + } + + return True; +} + +static bool ldapsam_search_nextpage(struct pdb_search *search) +{ + struct ldap_search_state *state = + (struct ldap_search_state *)search->private_data; + int rc; + + if (!state->connection->paged_results) { + /* There is no next page when there are no paged results */ + return False; + } + + rc = smbldap_search_paged(state->connection, state->base, + state->scope, state->filter, state->attrs, + state->attrsonly, lp_ldap_page_size(), + &state->entries, + &state->pagedresults_cookie); + + if ((rc != LDAP_SUCCESS) || (state->entries == NULL)) + return False; + + state->current_entry = ldap_first_entry(state->connection->ldap_struct, state->entries); + + if (state->current_entry == NULL) { + ldap_msgfree(state->entries); + state->entries = NULL; + } + + return True; +} + +static bool ldapsam_search_next_entry(struct pdb_search *search, + struct samr_displayentry *entry) +{ + struct ldap_search_state *state = + (struct ldap_search_state *)search->private_data; + bool result; + + retry: + if ((state->entries == NULL) && (state->pagedresults_cookie == NULL)) + return False; + + if ((state->entries == NULL) && + !ldapsam_search_nextpage(search)) + return False; + + result = state->ldap2displayentry(state, search->mem_ctx, state->connection->ldap_struct, + state->current_entry, entry); + + if (!result) { + char *dn; + dn = ldap_get_dn(state->connection->ldap_struct, state->current_entry); + DEBUG(5, ("Skipping entry %s\n", dn != NULL ? dn : "<NULL>")); + if (dn != NULL) ldap_memfree(dn); + } + + state->current_entry = ldap_next_entry(state->connection->ldap_struct, state->current_entry); + + if (state->current_entry == NULL) { + ldap_msgfree(state->entries); + state->entries = NULL; + } + + if (!result) goto retry; + + return True; +} + +static void ldapsam_search_end(struct pdb_search *search) +{ + struct ldap_search_state *state = + (struct ldap_search_state *)search->private_data; + int rc; + + if (state->pagedresults_cookie == NULL) + return; + + if (state->entries != NULL) + ldap_msgfree(state->entries); + + state->entries = NULL; + state->current_entry = NULL; + + if (!state->connection->paged_results) + return; + + /* Tell the LDAP server we're not interested in the rest anymore. */ + + rc = smbldap_search_paged(state->connection, state->base, state->scope, + state->filter, state->attrs, + state->attrsonly, 0, &state->entries, + &state->pagedresults_cookie); + + if (rc != LDAP_SUCCESS) + DEBUG(5, ("Could not end search properly\n")); + + return; +} + +static bool ldapuser2displayentry(struct ldap_search_state *state, + TALLOC_CTX *mem_ctx, + LDAP *ld, LDAPMessage *entry, + struct samr_displayentry *result) +{ + char **vals; + size_t converted_size; + DOM_SID sid; + uint32 acct_flags; + + vals = ldap_get_values(ld, entry, "sambaAcctFlags"); + if ((vals == NULL) || (vals[0] == NULL)) { + DEBUG(5, ("\"sambaAcctFlags\" not found\n")); + return False; + } + acct_flags = pdb_decode_acct_ctrl(vals[0]); + ldap_value_free(vals); + + if ((state->acct_flags != 0) && + ((state->acct_flags & acct_flags) == 0)) + return False; + + result->acct_flags = acct_flags; + result->account_name = ""; + result->fullname = ""; + result->description = ""; + + vals = ldap_get_values(ld, entry, "uid"); + if ((vals == NULL) || (vals[0] == NULL)) { + DEBUG(5, ("\"uid\" not found\n")); + return False; + } + if (!pull_utf8_talloc(mem_ctx, + CONST_DISCARD(char **, &result->account_name), + vals[0], &converted_size)) + { + DEBUG(0,("ldapuser2displayentry: pull_utf8_talloc failed: %s", + strerror(errno))); + } + + ldap_value_free(vals); + + vals = ldap_get_values(ld, entry, "displayName"); + if ((vals == NULL) || (vals[0] == NULL)) + DEBUG(8, ("\"displayName\" not found\n")); + else if (!pull_utf8_talloc(mem_ctx, + CONST_DISCARD(char **, &result->fullname), + vals[0], &converted_size)) + { + DEBUG(0,("ldapuser2displayentry: pull_utf8_talloc failed: %s", + strerror(errno))); + } + + ldap_value_free(vals); + + vals = ldap_get_values(ld, entry, "description"); + if ((vals == NULL) || (vals[0] == NULL)) + DEBUG(8, ("\"description\" not found\n")); + else if (!pull_utf8_talloc(mem_ctx, + CONST_DISCARD(char **, &result->description), + vals[0], &converted_size)) + { + DEBUG(0,("ldapuser2displayentry: pull_utf8_talloc failed: %s", + strerror(errno))); + } + + ldap_value_free(vals); + + if ((result->account_name == NULL) || + (result->fullname == NULL) || + (result->description == NULL)) { + DEBUG(0, ("talloc failed\n")); + return False; + } + + vals = ldap_get_values(ld, entry, "sambaSid"); + if ((vals == NULL) || (vals[0] == NULL)) { + DEBUG(0, ("\"objectSid\" not found\n")); + return False; + } + + if (!string_to_sid(&sid, vals[0])) { + DEBUG(0, ("Could not convert %s to SID\n", vals[0])); + ldap_value_free(vals); + return False; + } + ldap_value_free(vals); + + if (!sid_peek_check_rid(get_global_sam_sid(), &sid, &result->rid)) { + DEBUG(0, ("sid %s does not belong to our domain\n", + sid_string_dbg(&sid))); + return False; + } + + return True; +} + + +static bool ldapsam_search_users(struct pdb_methods *methods, + struct pdb_search *search, + uint32 acct_flags) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + struct ldap_search_state *state; + + state = TALLOC_P(search->mem_ctx, struct ldap_search_state); + if (state == NULL) { + DEBUG(0, ("talloc failed\n")); + return False; + } + + state->connection = ldap_state->smbldap_state; + + if ((acct_flags != 0) && ((acct_flags & ACB_NORMAL) != 0)) + state->base = lp_ldap_user_suffix(); + else if ((acct_flags != 0) && + ((acct_flags & (ACB_WSTRUST|ACB_SVRTRUST|ACB_DOMTRUST)) != 0)) + state->base = lp_ldap_machine_suffix(); + else + state->base = lp_ldap_suffix(); + + state->acct_flags = acct_flags; + state->base = talloc_strdup(search->mem_ctx, state->base); + state->scope = LDAP_SCOPE_SUBTREE; + state->filter = get_ldap_filter(search->mem_ctx, "*"); + state->attrs = talloc_attrs(search->mem_ctx, "uid", "sambaSid", + "displayName", "description", + "sambaAcctFlags", NULL); + state->attrsonly = 0; + state->pagedresults_cookie = NULL; + state->entries = NULL; + state->ldap2displayentry = ldapuser2displayentry; + + if ((state->filter == NULL) || (state->attrs == NULL)) { + DEBUG(0, ("talloc failed\n")); + return False; + } + + search->private_data = state; + search->next_entry = ldapsam_search_next_entry; + search->search_end = ldapsam_search_end; + + return ldapsam_search_firstpage(search); +} + +static bool ldapgroup2displayentry(struct ldap_search_state *state, + TALLOC_CTX *mem_ctx, + LDAP *ld, LDAPMessage *entry, + struct samr_displayentry *result) +{ + char **vals; + size_t converted_size; + DOM_SID sid; + uint16 group_type; + + result->account_name = ""; + result->fullname = ""; + result->description = ""; + + + vals = ldap_get_values(ld, entry, "sambaGroupType"); + if ((vals == NULL) || (vals[0] == NULL)) { + DEBUG(5, ("\"sambaGroupType\" not found\n")); + if (vals != NULL) { + ldap_value_free(vals); + } + return False; + } + + group_type = atoi(vals[0]); + + if ((state->group_type != 0) && + ((state->group_type != group_type))) { + ldap_value_free(vals); + return False; + } + + ldap_value_free(vals); + + /* display name is the NT group name */ + + vals = ldap_get_values(ld, entry, "displayName"); + if ((vals == NULL) || (vals[0] == NULL)) { + DEBUG(8, ("\"displayName\" not found\n")); + + /* fallback to the 'cn' attribute */ + vals = ldap_get_values(ld, entry, "cn"); + if ((vals == NULL) || (vals[0] == NULL)) { + DEBUG(5, ("\"cn\" not found\n")); + return False; + } + if (!pull_utf8_talloc(mem_ctx, + CONST_DISCARD(char **, + &result->account_name), + vals[0], &converted_size)) + { + DEBUG(0,("ldapgroup2displayentry: pull_utf8_talloc " + "failed: %s", strerror(errno))); + } + } + else if (!pull_utf8_talloc(mem_ctx, + CONST_DISCARD(char **, + &result->account_name), + vals[0], &converted_size)) + { + DEBUG(0,("ldapgroup2displayentry: pull_utf8_talloc failed: %s", + strerror(errno))); + } + + ldap_value_free(vals); + + vals = ldap_get_values(ld, entry, "description"); + if ((vals == NULL) || (vals[0] == NULL)) + DEBUG(8, ("\"description\" not found\n")); + else if (!pull_utf8_talloc(mem_ctx, + CONST_DISCARD(char **, &result->description), + vals[0], &converted_size)) + { + DEBUG(0,("ldapgroup2displayentry: pull_utf8_talloc failed: %s", + strerror(errno))); + } + ldap_value_free(vals); + + if ((result->account_name == NULL) || + (result->fullname == NULL) || + (result->description == NULL)) { + DEBUG(0, ("talloc failed\n")); + return False; + } + + vals = ldap_get_values(ld, entry, "sambaSid"); + if ((vals == NULL) || (vals[0] == NULL)) { + DEBUG(0, ("\"objectSid\" not found\n")); + if (vals != NULL) { + ldap_value_free(vals); + } + return False; + } + + if (!string_to_sid(&sid, vals[0])) { + DEBUG(0, ("Could not convert %s to SID\n", vals[0])); + return False; + } + + ldap_value_free(vals); + + switch (group_type) { + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + + if (!sid_peek_check_rid(get_global_sam_sid(), &sid, &result->rid) + && !sid_peek_check_rid(&global_sid_Builtin, &sid, &result->rid)) + { + DEBUG(0, ("%s is not in our domain\n", + sid_string_dbg(&sid))); + return False; + } + break; + + default: + DEBUG(0,("unkown group type: %d\n", group_type)); + return False; + } + + result->acct_flags = 0; + + return True; +} + +static bool ldapsam_search_grouptype(struct pdb_methods *methods, + struct pdb_search *search, + const DOM_SID *sid, + enum lsa_SidType type) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + struct ldap_search_state *state; + fstring tmp; + + state = TALLOC_P(search->mem_ctx, struct ldap_search_state); + if (state == NULL) { + DEBUG(0, ("talloc failed\n")); + return False; + } + + state->connection = ldap_state->smbldap_state; + + state->base = talloc_strdup(search->mem_ctx, lp_ldap_group_suffix()); + state->connection = ldap_state->smbldap_state; + state->scope = LDAP_SCOPE_SUBTREE; + state->filter = talloc_asprintf(search->mem_ctx, + "(&(objectclass=%s)" + "(sambaGroupType=%d)(sambaSID=%s*))", + LDAP_OBJ_GROUPMAP, + type, sid_to_fstring(tmp, sid)); + state->attrs = talloc_attrs(search->mem_ctx, "cn", "sambaSid", + "displayName", "description", + "sambaGroupType", NULL); + state->attrsonly = 0; + state->pagedresults_cookie = NULL; + state->entries = NULL; + state->group_type = type; + state->ldap2displayentry = ldapgroup2displayentry; + + if ((state->filter == NULL) || (state->attrs == NULL)) { + DEBUG(0, ("talloc failed\n")); + return False; + } + + search->private_data = state; + search->next_entry = ldapsam_search_next_entry; + search->search_end = ldapsam_search_end; + + return ldapsam_search_firstpage(search); +} + +static bool ldapsam_search_groups(struct pdb_methods *methods, + struct pdb_search *search) +{ + return ldapsam_search_grouptype(methods, search, get_global_sam_sid(), SID_NAME_DOM_GRP); +} + +static bool ldapsam_search_aliases(struct pdb_methods *methods, + struct pdb_search *search, + const DOM_SID *sid) +{ + return ldapsam_search_grouptype(methods, search, sid, SID_NAME_ALIAS); +} + +static bool ldapsam_rid_algorithm(struct pdb_methods *methods) +{ + return False; +} + +static NTSTATUS ldapsam_get_new_rid(struct ldapsam_privates *priv, + uint32 *rid) +{ + struct smbldap_state *smbldap_state = priv->smbldap_state; + + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + LDAPMod **mods = NULL; + NTSTATUS status; + char *value; + int rc; + uint32 nextRid = 0; + const char *dn; + + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + DEBUG(0, ("talloc_new failed\n")); + return NT_STATUS_NO_MEMORY; + } + + status = smbldap_search_domain_info(smbldap_state, &result, + get_global_sam_name(), False); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Could not get domain info: %s\n", + nt_errstr(status))); + goto done; + } + + talloc_autofree_ldapmsg(mem_ctx, result); + + entry = ldap_first_entry(priv2ld(priv), result); + if (entry == NULL) { + DEBUG(0, ("Could not get domain info entry\n")); + status = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + /* Find the largest of the three attributes "sambaNextRid", + "sambaNextGroupRid" and "sambaNextUserRid". I gave up on the + concept of differentiating between user and group rids, and will + use only "sambaNextRid" in the future. But for compatibility + reasons I look if others have chosen different strategies -- VL */ + + value = smbldap_talloc_single_attribute(priv2ld(priv), entry, + "sambaNextRid", mem_ctx); + if (value != NULL) { + uint32 tmp = (uint32)strtoul(value, NULL, 10); + nextRid = MAX(nextRid, tmp); + } + + value = smbldap_talloc_single_attribute(priv2ld(priv), entry, + "sambaNextUserRid", mem_ctx); + if (value != NULL) { + uint32 tmp = (uint32)strtoul(value, NULL, 10); + nextRid = MAX(nextRid, tmp); + } + + value = smbldap_talloc_single_attribute(priv2ld(priv), entry, + "sambaNextGroupRid", mem_ctx); + if (value != NULL) { + uint32 tmp = (uint32)strtoul(value, NULL, 10); + nextRid = MAX(nextRid, tmp); + } + + if (nextRid == 0) { + nextRid = BASE_RID-1; + } + + nextRid += 1; + + smbldap_make_mod(priv2ld(priv), entry, &mods, "sambaNextRid", + talloc_asprintf(mem_ctx, "%d", nextRid)); + talloc_autofree_ldapmod(mem_ctx, mods); + + if ((dn = smbldap_talloc_dn(mem_ctx, priv2ld(priv), entry)) == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = smbldap_modify(smbldap_state, dn, mods); + + /* ACCESS_DENIED is used as a placeholder for "the modify failed, + * please retry" */ + + status = (rc == LDAP_SUCCESS) ? NT_STATUS_OK : NT_STATUS_ACCESS_DENIED; + + done: + if (NT_STATUS_IS_OK(status)) { + *rid = nextRid; + } + + TALLOC_FREE(mem_ctx); + return status; +} + +static NTSTATUS ldapsam_new_rid_internal(struct pdb_methods *methods, uint32 *rid) +{ + int i; + + for (i=0; i<10; i++) { + NTSTATUS result = ldapsam_get_new_rid( + (struct ldapsam_privates *)methods->private_data, rid); + if (NT_STATUS_IS_OK(result)) { + return result; + } + + if (!NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) { + return result; + } + + /* The ldap update failed (maybe a race condition), retry */ + } + + /* Tried 10 times, fail. */ + return NT_STATUS_ACCESS_DENIED; +} + +static bool ldapsam_new_rid(struct pdb_methods *methods, uint32 *rid) +{ + NTSTATUS result = ldapsam_new_rid_internal(methods, rid); + return NT_STATUS_IS_OK(result) ? True : False; +} + +static bool ldapsam_sid_to_id(struct pdb_methods *methods, + const DOM_SID *sid, + union unid_t *id, enum lsa_SidType *type) +{ + struct ldapsam_privates *priv = + (struct ldapsam_privates *)methods->private_data; + char *filter; + const char *attrs[] = { "sambaGroupType", "gidNumber", "uidNumber", + NULL }; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + bool ret = False; + char *value; + int rc; + + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + DEBUG(0, ("talloc_new failed\n")); + return False; + } + + filter = talloc_asprintf(mem_ctx, + "(&(sambaSid=%s)" + "(|(objectClass=%s)(objectClass=%s)))", + sid_string_talloc(mem_ctx, sid), + LDAP_OBJ_GROUPMAP, LDAP_OBJ_SAMBASAMACCOUNT); + if (filter == NULL) { + DEBUG(5, ("talloc_asprintf failed\n")); + goto done; + } + + rc = smbldap_search_suffix(priv->smbldap_state, filter, + attrs, &result); + if (rc != LDAP_SUCCESS) { + goto done; + } + talloc_autofree_ldapmsg(mem_ctx, result); + + if (ldap_count_entries(priv2ld(priv), result) != 1) { + DEBUG(10, ("Got %d entries, expected one\n", + ldap_count_entries(priv2ld(priv), result))); + goto done; + } + + entry = ldap_first_entry(priv2ld(priv), result); + + value = smbldap_talloc_single_attribute(priv2ld(priv), entry, + "sambaGroupType", mem_ctx); + + if (value != NULL) { + const char *gid_str; + /* It's a group */ + + gid_str = smbldap_talloc_single_attribute( + priv2ld(priv), entry, "gidNumber", mem_ctx); + if (gid_str == NULL) { + DEBUG(1, ("%s has sambaGroupType but no gidNumber\n", + smbldap_talloc_dn(mem_ctx, priv2ld(priv), + entry))); + goto done; + } + + id->gid = strtoul(gid_str, NULL, 10); + *type = (enum lsa_SidType)strtoul(value, NULL, 10); + ret = True; + goto done; + } + + /* It must be a user */ + + value = smbldap_talloc_single_attribute(priv2ld(priv), entry, + "uidNumber", mem_ctx); + if (value == NULL) { + DEBUG(1, ("Could not find uidNumber in %s\n", + smbldap_talloc_dn(mem_ctx, priv2ld(priv), entry))); + goto done; + } + + id->uid = strtoul(value, NULL, 10); + *type = SID_NAME_USER; + + ret = True; + done: + TALLOC_FREE(mem_ctx); + return ret; +} + +/* + * The following functions is called only if + * ldapsam:trusted and ldapsam:editposix are + * set to true + */ + +/* + * ldapsam_create_user creates a new + * posixAccount and sambaSamAccount object + * in the ldap users subtree + * + * The uid is allocated by winbindd. + */ + +static NTSTATUS ldapsam_create_user(struct pdb_methods *my_methods, + TALLOC_CTX *tmp_ctx, const char *name, + uint32 acb_info, uint32 *rid) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + LDAPMessage *entry = NULL; + LDAPMessage *result = NULL; + uint32 num_result; + bool is_machine = False; + bool add_posix = False; + LDAPMod **mods = NULL; + struct samu *user; + char *filter; + char *username; + char *homedir; + char *gidstr; + char *uidstr; + char *shell; + const char *dn = NULL; + DOM_SID group_sid; + DOM_SID user_sid; + gid_t gid = -1; + uid_t uid = -1; + NTSTATUS ret; + int rc; + + if (((acb_info & ACB_NORMAL) && name[strlen(name)-1] == '$') || + acb_info & ACB_WSTRUST || + acb_info & ACB_SVRTRUST || + acb_info & ACB_DOMTRUST) { + is_machine = True; + } + + username = escape_ldap_string_alloc(name); + filter = talloc_asprintf(tmp_ctx, "(&(uid=%s)(objectClass=%s))", + username, LDAP_OBJ_POSIXACCOUNT); + SAFE_FREE(username); + + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result); + if (rc != LDAP_SUCCESS) { + DEBUG(0,("ldapsam_create_user: ldap search failed!\n")); + return NT_STATUS_ACCESS_DENIED; + } + talloc_autofree_ldapmsg(tmp_ctx, result); + + num_result = ldap_count_entries(priv2ld(ldap_state), result); + + if (num_result > 1) { + DEBUG (0, ("ldapsam_create_user: More than one user with name [%s] ?!\n", name)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (num_result == 1) { + char *tmp; + /* check if it is just a posix account. + * or if there is a sid attached to this entry + */ + + entry = ldap_first_entry(priv2ld(ldap_state), result); + if (!entry) { + return NT_STATUS_UNSUCCESSFUL; + } + + tmp = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "sambaSID", tmp_ctx); + if (tmp) { + DEBUG (1, ("ldapsam_create_user: The user [%s] already exist!\n", name)); + return NT_STATUS_USER_EXISTS; + } + + /* it is just a posix account, retrieve the dn for later use */ + dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry); + if (!dn) { + DEBUG(0,("ldapsam_create_user: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + } + + if (num_result == 0) { + add_posix = True; + } + + /* Create the basic samu structure and generate the mods for the ldap commit */ + if (!NT_STATUS_IS_OK((ret = ldapsam_new_rid_internal(my_methods, rid)))) { + DEBUG(1, ("ldapsam_create_user: Could not allocate a new RID\n")); + return ret; + } + + sid_compose(&user_sid, get_global_sam_sid(), *rid); + + user = samu_new(tmp_ctx); + if (!user) { + DEBUG(1,("ldapsam_create_user: Unable to allocate user struct\n")); + return NT_STATUS_NO_MEMORY; + } + + if (!pdb_set_username(user, name, PDB_SET)) { + DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n")); + return NT_STATUS_UNSUCCESSFUL; + } + if (!pdb_set_domain(user, get_global_sam_name(), PDB_SET)) { + DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n")); + return NT_STATUS_UNSUCCESSFUL; + } + if (is_machine) { + if (acb_info & ACB_NORMAL) { + if (!pdb_set_acct_ctrl(user, ACB_WSTRUST, PDB_SET)) { + DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n")); + return NT_STATUS_UNSUCCESSFUL; + } + } else { + if (!pdb_set_acct_ctrl(user, acb_info, PDB_SET)) { + DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n")); + return NT_STATUS_UNSUCCESSFUL; + } + } + } else { + if (!pdb_set_acct_ctrl(user, ACB_NORMAL | ACB_DISABLED, PDB_SET)) { + DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n")); + return NT_STATUS_UNSUCCESSFUL; + } + } + + if (!pdb_set_user_sid(user, &user_sid, PDB_SET)) { + DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + if (!init_ldap_from_sam(ldap_state, NULL, &mods, user, element_is_set_or_changed)) { + DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + if (ldap_state->schema_ver != SCHEMAVER_SAMBASAMACCOUNT) { + DEBUG(1,("ldapsam_create_user: Unsupported schema version\n")); + } + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_SAMBASAMACCOUNT); + + if (add_posix) { + char *escape_name; + + DEBUG(3,("ldapsam_create_user: Creating new posix user\n")); + + /* retrieve the Domain Users group gid */ + if (!sid_compose(&group_sid, get_global_sam_sid(), DOMAIN_GROUP_RID_USERS) || + !sid_to_gid(&group_sid, &gid)) { + DEBUG (0, ("ldapsam_create_user: Unable to get the Domain Users gid: bailing out!\n")); + return NT_STATUS_INVALID_PRIMARY_GROUP; + } + + /* lets allocate a new userid for this user */ + if (!winbind_allocate_uid(&uid)) { + DEBUG (0, ("ldapsam_create_user: Unable to allocate a new user id: bailing out!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + + if (is_machine) { + /* TODO: choose a more appropriate default for machines */ + homedir = talloc_sub_specified(tmp_ctx, lp_template_homedir(), "SMB_workstations_home", ldap_state->domain_name, uid, gid); + shell = talloc_strdup(tmp_ctx, "/bin/false"); + } else { + homedir = talloc_sub_specified(tmp_ctx, lp_template_homedir(), name, ldap_state->domain_name, uid, gid); + shell = talloc_sub_specified(tmp_ctx, lp_template_shell(), name, ldap_state->domain_name, uid, gid); + } + uidstr = talloc_asprintf(tmp_ctx, "%d", uid); + gidstr = talloc_asprintf(tmp_ctx, "%d", gid); + + escape_name = escape_rdn_val_string_alloc(name); + if (!escape_name) { + DEBUG (0, ("ldapsam_create_user: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + if (is_machine) { + dn = talloc_asprintf(tmp_ctx, "uid=%s,%s", escape_name, lp_ldap_machine_suffix ()); + } else { + dn = talloc_asprintf(tmp_ctx, "uid=%s,%s", escape_name, lp_ldap_user_suffix ()); + } + + SAFE_FREE(escape_name); + + if (!homedir || !shell || !uidstr || !gidstr || !dn) { + DEBUG (0, ("ldapsam_create_user: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_ACCOUNT); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_POSIXACCOUNT); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "cn", name); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "uidNumber", uidstr); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "gidNumber", gidstr); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "homeDirectory", homedir); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "loginShell", shell); + } + + talloc_autofree_ldapmod(tmp_ctx, mods); + + if (add_posix) { + rc = smbldap_add(ldap_state->smbldap_state, dn, mods); + } else { + rc = smbldap_modify(ldap_state->smbldap_state, dn, mods); + } + + if (rc != LDAP_SUCCESS) { + DEBUG(0,("ldapsam_create_user: failed to create a new user [%s] (dn = %s)\n", name ,dn)); + return NT_STATUS_UNSUCCESSFUL; + } + + DEBUG(2,("ldapsam_create_user: added account [%s] in the LDAP database\n", name)); + + flush_pwnam_cache(); + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_delete_user(struct pdb_methods *my_methods, TALLOC_CTX *tmp_ctx, struct samu *sam_acct) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + int num_result; + const char *dn; + char *filter; + int rc; + + DEBUG(0,("ldapsam_delete_user: Attempt to delete user [%s]\n", pdb_get_username(sam_acct))); + + filter = talloc_asprintf(tmp_ctx, + "(&(uid=%s)" + "(objectClass=%s)" + "(objectClass=%s))", + pdb_get_username(sam_acct), + LDAP_OBJ_POSIXACCOUNT, + LDAP_OBJ_SAMBASAMACCOUNT); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result); + if (rc != LDAP_SUCCESS) { + DEBUG(0,("ldapsam_delete_user: user search failed!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + talloc_autofree_ldapmsg(tmp_ctx, result); + + num_result = ldap_count_entries(priv2ld(ldap_state), result); + + if (num_result == 0) { + DEBUG(0,("ldapsam_delete_user: user not found!\n")); + return NT_STATUS_NO_SUCH_USER; + } + + if (num_result > 1) { + DEBUG (0, ("ldapsam_delete_user: More than one user with name [%s] ?!\n", pdb_get_username(sam_acct))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + entry = ldap_first_entry(priv2ld(ldap_state), result); + if (!entry) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* it is just a posix account, retrieve the dn for later use */ + dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry); + if (!dn) { + DEBUG(0,("ldapsam_delete_user: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + rc = smbldap_delete(ldap_state->smbldap_state, dn); + if (rc != LDAP_SUCCESS) { + return NT_STATUS_UNSUCCESSFUL; + } + + flush_pwnam_cache(); + + return NT_STATUS_OK; +} + +/* + * ldapsam_create_group creates a new + * posixGroup and sambaGroupMapping object + * in the ldap groups subtree + * + * The gid is allocated by winbindd. + */ + +static NTSTATUS ldapsam_create_dom_group(struct pdb_methods *my_methods, + TALLOC_CTX *tmp_ctx, + const char *name, + uint32 *rid) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + NTSTATUS ret; + LDAPMessage *entry = NULL; + LDAPMessage *result = NULL; + uint32 num_result; + bool is_new_entry = False; + LDAPMod **mods = NULL; + char *filter; + char *groupsidstr; + char *groupname; + char *grouptype; + char *gidstr; + const char *dn = NULL; + DOM_SID group_sid; + gid_t gid = -1; + int rc; + + groupname = escape_ldap_string_alloc(name); + filter = talloc_asprintf(tmp_ctx, "(&(cn=%s)(objectClass=%s))", + groupname, LDAP_OBJ_POSIXGROUP); + SAFE_FREE(groupname); + + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result); + if (rc != LDAP_SUCCESS) { + DEBUG(0,("ldapsam_create_group: ldap search failed!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + talloc_autofree_ldapmsg(tmp_ctx, result); + + num_result = ldap_count_entries(priv2ld(ldap_state), result); + + if (num_result > 1) { + DEBUG (0, ("ldapsam_create_group: There exists more than one group with name [%s]: bailing out!\n", name)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (num_result == 1) { + char *tmp; + /* check if it is just a posix group. + * or if there is a sid attached to this entry + */ + + entry = ldap_first_entry(priv2ld(ldap_state), result); + if (!entry) { + return NT_STATUS_UNSUCCESSFUL; + } + + tmp = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "sambaSID", tmp_ctx); + if (tmp) { + DEBUG (1, ("ldapsam_create_group: The group [%s] already exist!\n", name)); + return NT_STATUS_GROUP_EXISTS; + } + + /* it is just a posix group, retrieve the gid and the dn for later use */ + tmp = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", tmp_ctx); + if (!tmp) { + DEBUG (1, ("ldapsam_create_group: Couldn't retrieve the gidNumber for [%s]?!?!\n", name)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + gid = strtoul(tmp, NULL, 10); + + dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry); + if (!dn) { + DEBUG(0,("ldapsam_create_group: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + } + + if (num_result == 0) { + char *escape_name; + + DEBUG(3,("ldapsam_create_user: Creating new posix group\n")); + + is_new_entry = True; + + /* lets allocate a new groupid for this group */ + if (!winbind_allocate_gid(&gid)) { + DEBUG (0, ("ldapsam_create_group: Unable to allocate a new group id: bailing out!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + gidstr = talloc_asprintf(tmp_ctx, "%d", gid); + + escape_name = escape_rdn_val_string_alloc(name); + if (!escape_name) { + DEBUG (0, ("ldapsam_create_group: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + dn = talloc_asprintf(tmp_ctx, "cn=%s,%s", escape_name, lp_ldap_group_suffix()); + + SAFE_FREE(escape_name); + + if (!gidstr || !dn) { + DEBUG (0, ("ldapsam_create_group: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectclass", LDAP_OBJ_POSIXGROUP); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "cn", name); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "gidNumber", gidstr); + } + + if (!NT_STATUS_IS_OK((ret = ldapsam_new_rid_internal(my_methods, rid)))) { + DEBUG(1, ("ldapsam_create_group: Could not allocate a new RID\n")); + return ret; + } + + sid_compose(&group_sid, get_global_sam_sid(), *rid); + + groupsidstr = talloc_strdup(tmp_ctx, sid_string_talloc(tmp_ctx, + &group_sid)); + grouptype = talloc_asprintf(tmp_ctx, "%d", SID_NAME_DOM_GRP); + + if (!groupsidstr || !grouptype) { + DEBUG(0,("ldapsam_create_group: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_GROUPMAP); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaSid", groupsidstr); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaGroupType", grouptype); + smbldap_set_mod(&mods, LDAP_MOD_ADD, "displayName", name); + talloc_autofree_ldapmod(tmp_ctx, mods); + + if (is_new_entry) { + rc = smbldap_add(ldap_state->smbldap_state, dn, mods); +#if 0 + if (rc == LDAP_OBJECT_CLASS_VIOLATION) { + /* This call may fail with rfc2307bis schema */ + /* Retry adding a structural class */ + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "????"); + rc = smbldap_add(ldap_state->smbldap_state, dn, mods); + } +#endif + } else { + rc = smbldap_modify(ldap_state->smbldap_state, dn, mods); + } + + if (rc != LDAP_SUCCESS) { + DEBUG(0,("ldapsam_create_group: failed to create a new group [%s] (dn = %s)\n", name ,dn)); + return NT_STATUS_UNSUCCESSFUL; + } + + DEBUG(2,("ldapsam_create_group: added group [%s] in the LDAP database\n", name)); + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_delete_dom_group(struct pdb_methods *my_methods, TALLOC_CTX *tmp_ctx, uint32 rid) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + int num_result; + const char *dn; + char *gidstr; + char *filter; + DOM_SID group_sid; + int rc; + + /* get the group sid */ + sid_compose(&group_sid, get_global_sam_sid(), rid); + + filter = talloc_asprintf(tmp_ctx, + "(&(sambaSID=%s)" + "(objectClass=%s)" + "(objectClass=%s))", + sid_string_talloc(tmp_ctx, &group_sid), + LDAP_OBJ_POSIXGROUP, + LDAP_OBJ_GROUPMAP); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result); + if (rc != LDAP_SUCCESS) { + DEBUG(1,("ldapsam_delete_dom_group: group search failed!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + talloc_autofree_ldapmsg(tmp_ctx, result); + + num_result = ldap_count_entries(priv2ld(ldap_state), result); + + if (num_result == 0) { + DEBUG(1,("ldapsam_delete_dom_group: group not found!\n")); + return NT_STATUS_NO_SUCH_GROUP; + } + + if (num_result > 1) { + DEBUG (0, ("ldapsam_delete_dom_group: More than one group with the same SID ?!\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + entry = ldap_first_entry(priv2ld(ldap_state), result); + if (!entry) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* here it is, retrieve the dn for later use */ + dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry); + if (!dn) { + DEBUG(0,("ldapsam_delete_dom_group: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + gidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", tmp_ctx); + if (!gidstr) { + DEBUG (0, ("ldapsam_delete_dom_group: Unable to find the group's gid!\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* check no user have this group marked as primary group */ + filter = talloc_asprintf(tmp_ctx, + "(&(gidNumber=%s)" + "(objectClass=%s)" + "(objectClass=%s))", + gidstr, + LDAP_OBJ_POSIXACCOUNT, + LDAP_OBJ_SAMBASAMACCOUNT); + + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result); + if (rc != LDAP_SUCCESS) { + DEBUG(1,("ldapsam_delete_dom_group: accounts search failed!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + talloc_autofree_ldapmsg(tmp_ctx, result); + + num_result = ldap_count_entries(priv2ld(ldap_state), result); + + if (num_result != 0) { + DEBUG(3,("ldapsam_delete_dom_group: Can't delete group, it is a primary group for %d users\n", num_result)); + return NT_STATUS_MEMBERS_PRIMARY_GROUP; + } + + rc = smbldap_delete(ldap_state->smbldap_state, dn); + if (rc != LDAP_SUCCESS) { + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_change_groupmem(struct pdb_methods *my_methods, + TALLOC_CTX *tmp_ctx, + uint32 group_rid, + uint32 member_rid, + int modop) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + LDAPMessage *entry = NULL; + LDAPMessage *result = NULL; + uint32 num_result; + LDAPMod **mods = NULL; + char *filter; + char *uidstr; + const char *dn = NULL; + DOM_SID group_sid; + DOM_SID member_sid; + int rc; + + switch (modop) { + case LDAP_MOD_ADD: + DEBUG(1,("ldapsam_change_groupmem: add new member(rid=%d) to a domain group(rid=%d)", member_rid, group_rid)); + break; + case LDAP_MOD_DELETE: + DEBUG(1,("ldapsam_change_groupmem: delete member(rid=%d) from a domain group(rid=%d)", member_rid, group_rid)); + break; + default: + return NT_STATUS_UNSUCCESSFUL; + } + + /* get member sid */ + sid_compose(&member_sid, get_global_sam_sid(), member_rid); + + /* get the group sid */ + sid_compose(&group_sid, get_global_sam_sid(), group_rid); + + filter = talloc_asprintf(tmp_ctx, + "(&(sambaSID=%s)" + "(objectClass=%s)" + "(objectClass=%s))", + sid_string_talloc(tmp_ctx, &member_sid), + LDAP_OBJ_POSIXACCOUNT, + LDAP_OBJ_SAMBASAMACCOUNT); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* get the member uid */ + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result); + if (rc != LDAP_SUCCESS) { + DEBUG(1,("ldapsam_change_groupmem: member search failed!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + talloc_autofree_ldapmsg(tmp_ctx, result); + + num_result = ldap_count_entries(priv2ld(ldap_state), result); + + if (num_result == 0) { + DEBUG(1,("ldapsam_change_groupmem: member not found!\n")); + return NT_STATUS_NO_SUCH_MEMBER; + } + + if (num_result > 1) { + DEBUG (0, ("ldapsam_change_groupmem: More than one account with the same SID ?!\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + entry = ldap_first_entry(priv2ld(ldap_state), result); + if (!entry) { + return NT_STATUS_UNSUCCESSFUL; + } + + if (modop == LDAP_MOD_DELETE) { + /* check if we are trying to remove the member from his primary group */ + char *gidstr; + gid_t user_gid, group_gid; + + gidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", tmp_ctx); + if (!gidstr) { + DEBUG (0, ("ldapsam_change_groupmem: Unable to find the member's gid!\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + user_gid = strtoul(gidstr, NULL, 10); + + if (!sid_to_gid(&group_sid, &group_gid)) { + DEBUG (0, ("ldapsam_change_groupmem: Unable to get group gid from SID!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + if (user_gid == group_gid) { + DEBUG (3, ("ldapsam_change_groupmem: can't remove user from its own primary group!\n")); + return NT_STATUS_MEMBERS_PRIMARY_GROUP; + } + } + + /* here it is, retrieve the uid for later use */ + uidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "uid", tmp_ctx); + if (!uidstr) { + DEBUG (0, ("ldapsam_change_groupmem: Unable to find the member's name!\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + filter = talloc_asprintf(tmp_ctx, + "(&(sambaSID=%s)" + "(objectClass=%s)" + "(objectClass=%s))", + sid_string_talloc(tmp_ctx, &group_sid), + LDAP_OBJ_POSIXGROUP, + LDAP_OBJ_GROUPMAP); + + /* get the group */ + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result); + if (rc != LDAP_SUCCESS) { + DEBUG(1,("ldapsam_change_groupmem: group search failed!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + talloc_autofree_ldapmsg(tmp_ctx, result); + + num_result = ldap_count_entries(priv2ld(ldap_state), result); + + if (num_result == 0) { + DEBUG(1,("ldapsam_change_groupmem: group not found!\n")); + return NT_STATUS_NO_SUCH_GROUP; + } + + if (num_result > 1) { + DEBUG (0, ("ldapsam_change_groupmem: More than one group with the same SID ?!\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + entry = ldap_first_entry(priv2ld(ldap_state), result); + if (!entry) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* here it is, retrieve the dn for later use */ + dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry); + if (!dn) { + DEBUG(0,("ldapsam_change_groupmem: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + smbldap_set_mod(&mods, modop, "memberUid", uidstr); + + talloc_autofree_ldapmod(tmp_ctx, mods); + + rc = smbldap_modify(ldap_state->smbldap_state, dn, mods); + if (rc != LDAP_SUCCESS) { + if (rc == LDAP_TYPE_OR_VALUE_EXISTS && modop == LDAP_MOD_ADD) { + DEBUG(1,("ldapsam_change_groupmem: member is already in group, add failed!\n")); + return NT_STATUS_MEMBER_IN_GROUP; + } + if (rc == LDAP_NO_SUCH_ATTRIBUTE && modop == LDAP_MOD_DELETE) { + DEBUG(1,("ldapsam_change_groupmem: member is not in group, delete failed!\n")); + return NT_STATUS_MEMBER_NOT_IN_GROUP; + } + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static NTSTATUS ldapsam_add_groupmem(struct pdb_methods *my_methods, + TALLOC_CTX *tmp_ctx, + uint32 group_rid, + uint32 member_rid) +{ + return ldapsam_change_groupmem(my_methods, tmp_ctx, group_rid, member_rid, LDAP_MOD_ADD); +} +static NTSTATUS ldapsam_del_groupmem(struct pdb_methods *my_methods, + TALLOC_CTX *tmp_ctx, + uint32 group_rid, + uint32 member_rid) +{ + return ldapsam_change_groupmem(my_methods, tmp_ctx, group_rid, member_rid, LDAP_MOD_DELETE); +} + +static NTSTATUS ldapsam_set_primary_group(struct pdb_methods *my_methods, + TALLOC_CTX *mem_ctx, + struct samu *sampass) +{ + struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data; + LDAPMessage *entry = NULL; + LDAPMessage *result = NULL; + uint32 num_result; + LDAPMod **mods = NULL; + char *filter; + char *escape_username; + char *gidstr; + const char *dn = NULL; + gid_t gid; + int rc; + + DEBUG(0,("ldapsam_set_primary_group: Attempt to set primary group for user [%s]\n", pdb_get_username(sampass))); + + if (!sid_to_gid(pdb_get_group_sid(sampass), &gid)) { + DEBUG(0,("ldapsam_set_primary_group: failed to retrieve gid from user's group SID!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + gidstr = talloc_asprintf(mem_ctx, "%d", gid); + if (!gidstr) { + DEBUG(0,("ldapsam_set_primary_group: Out of Memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + escape_username = escape_ldap_string_alloc(pdb_get_username(sampass)); + if (escape_username== NULL) { + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf(mem_ctx, + "(&(uid=%s)" + "(objectClass=%s)" + "(objectClass=%s))", + escape_username, + LDAP_OBJ_POSIXACCOUNT, + LDAP_OBJ_SAMBASAMACCOUNT); + + SAFE_FREE(escape_username); + + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result); + if (rc != LDAP_SUCCESS) { + DEBUG(0,("ldapsam_set_primary_group: user search failed!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + talloc_autofree_ldapmsg(mem_ctx, result); + + num_result = ldap_count_entries(priv2ld(ldap_state), result); + + if (num_result == 0) { + DEBUG(0,("ldapsam_set_primary_group: user not found!\n")); + return NT_STATUS_NO_SUCH_USER; + } + + if (num_result > 1) { + DEBUG (0, ("ldapsam_set_primary_group: More than one user with name [%s] ?!\n", pdb_get_username(sampass))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + entry = ldap_first_entry(priv2ld(ldap_state), result); + if (!entry) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* retrieve the dn for later use */ + dn = smbldap_talloc_dn(mem_ctx, priv2ld(ldap_state), entry); + if (!dn) { + DEBUG(0,("ldapsam_set_primary_group: Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + /* remove the old one, and add the new one, this way we do not risk races */ + smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "gidNumber", gidstr); + + if (mods == NULL) { + return NT_STATUS_OK; + } + + rc = smbldap_modify(ldap_state->smbldap_state, dn, mods); + + if (rc != LDAP_SUCCESS) { + DEBUG(0,("ldapsam_set_primary_group: failed to modify [%s] primary group to [%s]\n", + pdb_get_username(sampass), gidstr)); + return NT_STATUS_UNSUCCESSFUL; + } + + flush_pwnam_cache(); + + return NT_STATUS_OK; +} + + +/********************************************************************** + trusted domains functions + *********************************************************************/ + +static char *trusteddom_dn(struct ldapsam_privates *ldap_state, + const char *domain) +{ + return talloc_asprintf(talloc_tos(), "sambaDomainName=%s,%s", domain, + ldap_state->domain_dn); +} + +static bool get_trusteddom_pw_int(struct ldapsam_privates *ldap_state, + TALLOC_CTX *mem_ctx, + const char *domain, LDAPMessage **entry) +{ + int rc; + char *filter; + int scope = LDAP_SCOPE_SUBTREE; + const char **attrs = NULL; /* NULL: get all attrs */ + int attrsonly = 0; /* 0: return values too */ + LDAPMessage *result = NULL; + char *trusted_dn; + uint32 num_result; + + filter = talloc_asprintf(talloc_tos(), + "(&(objectClass=%s)(sambaDomainName=%s))", + LDAP_OBJ_TRUSTDOM_PASSWORD, domain); + + trusted_dn = trusteddom_dn(ldap_state, domain); + if (trusted_dn == NULL) { + return False; + } + rc = smbldap_search(ldap_state->smbldap_state, trusted_dn, scope, + filter, attrs, attrsonly, &result); + + if (result != NULL) { + talloc_autofree_ldapmsg(mem_ctx, result); + } + + if (rc == LDAP_NO_SUCH_OBJECT) { + *entry = NULL; + return True; + } + + if (rc != LDAP_SUCCESS) { + return False; + } + + num_result = ldap_count_entries(priv2ld(ldap_state), result); + + if (num_result > 1) { + DEBUG(1, ("ldapsam_get_trusteddom_pw: more than one " + "%s object for domain '%s'?!\n", + LDAP_OBJ_TRUSTDOM_PASSWORD, domain)); + return False; + } + + if (num_result == 0) { + DEBUG(1, ("ldapsam_get_trusteddom_pw: no " + "%s object for domain %s.\n", + LDAP_OBJ_TRUSTDOM_PASSWORD, domain)); + *entry = NULL; + } else { + *entry = ldap_first_entry(priv2ld(ldap_state), result); + } + + return True; +} + +static bool ldapsam_get_trusteddom_pw(struct pdb_methods *methods, + const char *domain, + char** pwd, + DOM_SID *sid, + time_t *pass_last_set_time) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + LDAPMessage *entry = NULL; + + DEBUG(10, ("ldapsam_get_trusteddom_pw called for domain %s\n", domain)); + + if (!get_trusteddom_pw_int(ldap_state, talloc_tos(), domain, &entry) || + (entry == NULL)) + { + return False; + } + + /* password */ + if (pwd != NULL) { + char *pwd_str; + pwd_str = smbldap_talloc_single_attribute(priv2ld(ldap_state), + entry, "sambaClearTextPassword", talloc_tos()); + if (pwd_str == NULL) { + return False; + } + /* trusteddom_pw routines do not use talloc yet... */ + *pwd = SMB_STRDUP(pwd_str); + if (*pwd == NULL) { + return False; + } + } + + /* last change time */ + if (pass_last_set_time != NULL) { + char *time_str; + time_str = smbldap_talloc_single_attribute(priv2ld(ldap_state), + entry, "sambaPwdLastSet", talloc_tos()); + if (time_str == NULL) { + return False; + } + *pass_last_set_time = (time_t)atol(time_str); + } + + /* domain sid */ + if (sid != NULL) { + char *sid_str; + DOM_SID *dom_sid; + sid_str = smbldap_talloc_single_attribute(priv2ld(ldap_state), + entry, "sambaSID", + talloc_tos()); + if (sid_str == NULL) { + return False; + } + dom_sid = string_sid_talloc(talloc_tos(), sid_str); + if (dom_sid == NULL) { + return False; + } + sid_copy(sid, dom_sid); + } + + return True; +} + +static bool ldapsam_set_trusteddom_pw(struct pdb_methods *methods, + const char* domain, + const char* pwd, + const DOM_SID *sid) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + LDAPMessage *entry = NULL; + LDAPMod **mods = NULL; + char *prev_pwd = NULL; + char *trusted_dn = NULL; + int rc; + + DEBUG(10, ("ldapsam_set_trusteddom_pw called for domain %s\n", domain)); + + /* + * get the current entry (if there is one) in order to put the + * current password into the previous password attribute + */ + if (!get_trusteddom_pw_int(ldap_state, talloc_tos(), domain, &entry)) { + return False; + } + + mods = NULL; + smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "objectClass", + LDAP_OBJ_TRUSTDOM_PASSWORD); + smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "sambaDomainName", + domain); + smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "sambaSID", + sid_string_tos(sid)); + smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "sambaPwdLastSet", + talloc_asprintf(talloc_tos(), "%li", time(NULL))); + smbldap_make_mod(priv2ld(ldap_state), entry, &mods, + "sambaClearTextPassword", pwd); + + talloc_autofree_ldapmod(talloc_tos(), mods); + + if (entry != NULL) { + prev_pwd = smbldap_talloc_single_attribute(priv2ld(ldap_state), + entry, "sambaClearTextPassword", talloc_tos()); + if (prev_pwd != NULL) { + smbldap_make_mod(priv2ld(ldap_state), entry, &mods, + "sambaPreviousClearTextPassword", + prev_pwd); + } + } + + trusted_dn = trusteddom_dn(ldap_state, domain); + if (trusted_dn == NULL) { + return False; + } + if (entry == NULL) { + rc = smbldap_add(ldap_state->smbldap_state, trusted_dn, mods); + } else { + rc = smbldap_modify(ldap_state->smbldap_state, trusted_dn, mods); + } + + if (rc != LDAP_SUCCESS) { + DEBUG(1, ("error writing trusted domain password!\n")); + return False; + } + + return True; +} + +static bool ldapsam_del_trusteddom_pw(struct pdb_methods *methods, + const char *domain) +{ + int rc; + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + LDAPMessage *entry = NULL; + const char *trusted_dn; + + if (!get_trusteddom_pw_int(ldap_state, talloc_tos(), domain, &entry)) { + return False; + } + + if (entry == NULL) { + DEBUG(5, ("ldapsam_del_trusteddom_pw: no such trusted domain: " + "%s\n", domain)); + return True; + } + + trusted_dn = smbldap_talloc_dn(talloc_tos(), priv2ld(ldap_state), + entry); + if (trusted_dn == NULL) { + DEBUG(0,("ldapsam_del_trusteddom_pw: Out of memory!\n")); + return False; + } + + rc = smbldap_delete(ldap_state->smbldap_state, trusted_dn); + if (rc != LDAP_SUCCESS) { + return False; + } + + return True; +} + +static NTSTATUS ldapsam_enum_trusteddoms(struct pdb_methods *methods, + TALLOC_CTX *mem_ctx, + uint32 *num_domains, + struct trustdom_info ***domains) +{ + int rc; + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)methods->private_data; + char *filter; + int scope = LDAP_SCOPE_SUBTREE; + const char *attrs[] = { "sambaDomainName", "sambaSID", NULL }; + int attrsonly = 0; /* 0: return values too */ + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + + filter = talloc_asprintf(talloc_tos(), "(objectClass=%s)", + LDAP_OBJ_TRUSTDOM_PASSWORD); + + rc = smbldap_search(ldap_state->smbldap_state, + ldap_state->domain_dn, + scope, + filter, + attrs, + attrsonly, + &result); + + if (result != NULL) { + talloc_autofree_ldapmsg(mem_ctx, result); + } + + if (rc != LDAP_SUCCESS) { + return NT_STATUS_UNSUCCESSFUL; + } + + *num_domains = 0; + if (!(*domains = TALLOC_ARRAY(mem_ctx, struct trustdom_info *, 1))) { + DEBUG(1, ("talloc failed\n")); + return NT_STATUS_NO_MEMORY; + } + + for (entry = ldap_first_entry(priv2ld(ldap_state), result); + entry != NULL; + entry = ldap_next_entry(priv2ld(ldap_state), entry)) + { + char *dom_name, *dom_sid_str; + struct trustdom_info *dom_info; + + dom_info = TALLOC_P(*domains, struct trustdom_info); + if (dom_info == NULL) { + DEBUG(1, ("talloc failed\n")); + return NT_STATUS_NO_MEMORY; + } + + dom_name = smbldap_talloc_single_attribute(priv2ld(ldap_state), + entry, + "sambaDomainName", + talloc_tos()); + if (dom_name == NULL) { + DEBUG(1, ("talloc failed\n")); + return NT_STATUS_NO_MEMORY; + } + dom_info->name = dom_name; + + dom_sid_str = smbldap_talloc_single_attribute( + priv2ld(ldap_state), entry, "sambaSID", + talloc_tos()); + if (dom_sid_str == NULL) { + DEBUG(1, ("talloc failed\n")); + return NT_STATUS_NO_MEMORY; + } + if (!string_to_sid(&dom_info->sid, dom_sid_str)) { + DEBUG(1, ("Error calling string_to_sid on SID %s\n", + dom_sid_str)); + return NT_STATUS_UNSUCCESSFUL; + } + + ADD_TO_ARRAY(*domains, struct trustdom_info *, dom_info, + domains, num_domains); + + if (*domains == NULL) { + DEBUG(1, ("talloc failed\n")); + return NT_STATUS_NO_MEMORY; + } + } + + DEBUG(5, ("ldapsam_enum_trusteddoms: got %d domains\n", *num_domains)); + return NT_STATUS_OK; +} + + +/********************************************************************** + Housekeeping + *********************************************************************/ + +static void free_private_data(void **vp) +{ + struct ldapsam_privates **ldap_state = (struct ldapsam_privates **)vp; + + smbldap_free_struct(&(*ldap_state)->smbldap_state); + + if ((*ldap_state)->result != NULL) { + ldap_msgfree((*ldap_state)->result); + (*ldap_state)->result = NULL; + } + if ((*ldap_state)->domain_dn != NULL) { + SAFE_FREE((*ldap_state)->domain_dn); + } + + *ldap_state = NULL; + + /* No need to free any further, as it is talloc()ed */ +} + +/********************************************************************* + Intitalise the parts of the pdb_methods structure that are common to + all pdb_ldap modes +*********************************************************************/ + +static NTSTATUS pdb_init_ldapsam_common(struct pdb_methods **pdb_method, const char *location) +{ + NTSTATUS nt_status; + struct ldapsam_privates *ldap_state; + + if (!NT_STATUS_IS_OK(nt_status = make_pdb_method( pdb_method ))) { + return nt_status; + } + + (*pdb_method)->name = "ldapsam"; + + (*pdb_method)->getsampwnam = ldapsam_getsampwnam; + (*pdb_method)->getsampwsid = ldapsam_getsampwsid; + (*pdb_method)->add_sam_account = ldapsam_add_sam_account; + (*pdb_method)->update_sam_account = ldapsam_update_sam_account; + (*pdb_method)->delete_sam_account = ldapsam_delete_sam_account; + (*pdb_method)->rename_sam_account = ldapsam_rename_sam_account; + + (*pdb_method)->getgrsid = ldapsam_getgrsid; + (*pdb_method)->getgrgid = ldapsam_getgrgid; + (*pdb_method)->getgrnam = ldapsam_getgrnam; + (*pdb_method)->add_group_mapping_entry = ldapsam_add_group_mapping_entry; + (*pdb_method)->update_group_mapping_entry = ldapsam_update_group_mapping_entry; + (*pdb_method)->delete_group_mapping_entry = ldapsam_delete_group_mapping_entry; + (*pdb_method)->enum_group_mapping = ldapsam_enum_group_mapping; + + (*pdb_method)->get_account_policy = ldapsam_get_account_policy; + (*pdb_method)->set_account_policy = ldapsam_set_account_policy; + + (*pdb_method)->get_seq_num = ldapsam_get_seq_num; + + (*pdb_method)->rid_algorithm = ldapsam_rid_algorithm; + (*pdb_method)->new_rid = ldapsam_new_rid; + + (*pdb_method)->get_trusteddom_pw = ldapsam_get_trusteddom_pw; + (*pdb_method)->set_trusteddom_pw = ldapsam_set_trusteddom_pw; + (*pdb_method)->del_trusteddom_pw = ldapsam_del_trusteddom_pw; + (*pdb_method)->enum_trusteddoms = ldapsam_enum_trusteddoms; + + /* TODO: Setup private data and free */ + + if ( !(ldap_state = TALLOC_ZERO_P(*pdb_method, struct ldapsam_privates)) ) { + DEBUG(0, ("pdb_init_ldapsam_common: talloc() failed for ldapsam private_data!\n")); + return NT_STATUS_NO_MEMORY; + } + + nt_status = smbldap_init(*pdb_method, pdb_get_event_context(), + location, &ldap_state->smbldap_state); + + if ( !NT_STATUS_IS_OK(nt_status) ) { + return nt_status; + } + + if ( !(ldap_state->domain_name = talloc_strdup(*pdb_method, get_global_sam_name()) ) ) { + return NT_STATUS_NO_MEMORY; + } + + (*pdb_method)->private_data = ldap_state; + + (*pdb_method)->free_private_data = free_private_data; + + return NT_STATUS_OK; +} + +/********************************************************************** + Initialise the 'compat' mode for pdb_ldap + *********************************************************************/ + +NTSTATUS pdb_init_ldapsam_compat(struct pdb_methods **pdb_method, const char *location) +{ + NTSTATUS nt_status; + struct ldapsam_privates *ldap_state; + char *uri = talloc_strdup( NULL, location ); + + trim_char( uri, '\"', '\"' ); + nt_status = pdb_init_ldapsam_common( pdb_method, uri ); + if ( uri ) + TALLOC_FREE( uri ); + + if ( !NT_STATUS_IS_OK(nt_status) ) { + return nt_status; + } + + (*pdb_method)->name = "ldapsam_compat"; + + ldap_state = (struct ldapsam_privates *)((*pdb_method)->private_data); + ldap_state->schema_ver = SCHEMAVER_SAMBAACCOUNT; + + sid_copy(&ldap_state->domain_sid, get_global_sam_sid()); + + return NT_STATUS_OK; +} + +/********************************************************************** + Initialise the normal mode for pdb_ldap + *********************************************************************/ + +NTSTATUS pdb_init_ldapsam(struct pdb_methods **pdb_method, const char *location) +{ + NTSTATUS nt_status; + struct ldapsam_privates *ldap_state = NULL; + uint32 alg_rid_base; + char *alg_rid_base_string = NULL; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + DOM_SID ldap_domain_sid; + DOM_SID secrets_domain_sid; + char *domain_sid_string = NULL; + char *dn = NULL; + char *uri = talloc_strdup( NULL, location ); + + trim_char( uri, '\"', '\"' ); + nt_status = pdb_init_ldapsam_common(pdb_method, uri); + if (uri) { + TALLOC_FREE(uri); + } + + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + (*pdb_method)->name = "ldapsam"; + + (*pdb_method)->add_aliasmem = ldapsam_add_aliasmem; + (*pdb_method)->del_aliasmem = ldapsam_del_aliasmem; + (*pdb_method)->enum_aliasmem = ldapsam_enum_aliasmem; + (*pdb_method)->enum_alias_memberships = ldapsam_alias_memberships; + (*pdb_method)->search_users = ldapsam_search_users; + (*pdb_method)->search_groups = ldapsam_search_groups; + (*pdb_method)->search_aliases = ldapsam_search_aliases; + + if (lp_parm_bool(-1, "ldapsam", "trusted", False)) { + (*pdb_method)->enum_group_members = ldapsam_enum_group_members; + (*pdb_method)->enum_group_memberships = + ldapsam_enum_group_memberships; + (*pdb_method)->lookup_rids = ldapsam_lookup_rids; + (*pdb_method)->sid_to_id = ldapsam_sid_to_id; + + if (lp_parm_bool(-1, "ldapsam", "editposix", False)) { + (*pdb_method)->create_user = ldapsam_create_user; + (*pdb_method)->delete_user = ldapsam_delete_user; + (*pdb_method)->create_dom_group = ldapsam_create_dom_group; + (*pdb_method)->delete_dom_group = ldapsam_delete_dom_group; + (*pdb_method)->add_groupmem = ldapsam_add_groupmem; + (*pdb_method)->del_groupmem = ldapsam_del_groupmem; + (*pdb_method)->set_unix_primary_group = ldapsam_set_primary_group; + } + } + + ldap_state = (struct ldapsam_privates *)((*pdb_method)->private_data); + ldap_state->schema_ver = SCHEMAVER_SAMBASAMACCOUNT; + + /* Try to setup the Domain Name, Domain SID, algorithmic rid base */ + + nt_status = smbldap_search_domain_info(ldap_state->smbldap_state, + &result, + ldap_state->domain_name, True); + + if ( !NT_STATUS_IS_OK(nt_status) ) { + DEBUG(2, ("pdb_init_ldapsam: WARNING: Could not get domain " + "info, nor add one to the domain\n")); + DEBUGADD(2, ("pdb_init_ldapsam: Continuing on regardless, " + "will be unable to allocate new users/groups, " + "and will risk BDCs having inconsistant SIDs\n")); + sid_copy(&ldap_state->domain_sid, get_global_sam_sid()); + return NT_STATUS_OK; + } + + /* Given that the above might fail, everything below this must be + * optional */ + + entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, + result); + if (!entry) { + DEBUG(0, ("pdb_init_ldapsam: Could not get domain info " + "entry\n")); + ldap_msgfree(result); + return NT_STATUS_UNSUCCESSFUL; + } + + dn = smbldap_get_dn(ldap_state->smbldap_state->ldap_struct, entry); + if (!dn) { + ldap_msgfree(result); + return NT_STATUS_UNSUCCESSFUL; + } + + ldap_state->domain_dn = smb_xstrdup(dn); + ldap_memfree(dn); + + domain_sid_string = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_USER_SID), + talloc_tos()); + + if (domain_sid_string) { + bool found_sid; + if (!string_to_sid(&ldap_domain_sid, domain_sid_string)) { + DEBUG(1, ("pdb_init_ldapsam: SID [%s] could not be " + "read as a valid SID\n", domain_sid_string)); + ldap_msgfree(result); + TALLOC_FREE(domain_sid_string); + return NT_STATUS_INVALID_PARAMETER; + } + found_sid = secrets_fetch_domain_sid(ldap_state->domain_name, + &secrets_domain_sid); + if (!found_sid || !sid_equal(&secrets_domain_sid, + &ldap_domain_sid)) { + DEBUG(1, ("pdb_init_ldapsam: Resetting SID for domain " + "%s based on pdb_ldap results %s -> %s\n", + ldap_state->domain_name, + sid_string_dbg(&secrets_domain_sid), + sid_string_dbg(&ldap_domain_sid))); + + /* reset secrets.tdb sid */ + secrets_store_domain_sid(ldap_state->domain_name, + &ldap_domain_sid); + DEBUG(1, ("New global sam SID: %s\n", + sid_string_dbg(get_global_sam_sid()))); + } + sid_copy(&ldap_state->domain_sid, &ldap_domain_sid); + TALLOC_FREE(domain_sid_string); + } + + alg_rid_base_string = smbldap_talloc_single_attribute( + ldap_state->smbldap_state->ldap_struct, + entry, + get_attr_key2string( dominfo_attr_list, + LDAP_ATTR_ALGORITHMIC_RID_BASE ), + talloc_tos()); + if (alg_rid_base_string) { + alg_rid_base = (uint32)atol(alg_rid_base_string); + if (alg_rid_base != algorithmic_rid_base()) { + DEBUG(0, ("The value of 'algorithmic RID base' has " + "changed since the LDAP\n" + "database was initialised. Aborting. \n")); + ldap_msgfree(result); + TALLOC_FREE(alg_rid_base_string); + return NT_STATUS_UNSUCCESSFUL; + } + TALLOC_FREE(alg_rid_base_string); + } + ldap_msgfree(result); + + return NT_STATUS_OK; +} + +NTSTATUS pdb_ldap_init(void) +{ + NTSTATUS nt_status; + if (!NT_STATUS_IS_OK(nt_status = smb_register_passdb(PASSDB_INTERFACE_VERSION, "ldapsam", pdb_init_ldapsam))) + return nt_status; + + if (!NT_STATUS_IS_OK(nt_status = smb_register_passdb(PASSDB_INTERFACE_VERSION, "ldapsam_compat", pdb_init_ldapsam_compat))) + return nt_status; + + /* Let pdb_nds register backends */ + pdb_nds_init(); + + return NT_STATUS_OK; +} diff --git a/source3/passdb/pdb_nds.c b/source3/passdb/pdb_nds.c new file mode 100644 index 0000000000..1edd665d54 --- /dev/null +++ b/source3/passdb/pdb_nds.c @@ -0,0 +1,913 @@ +/* + Unix SMB/CIFS mplementation. + NDS LDAP helper functions for SAMBA + Copyright (C) Vince Brimhall 2004-2005 + + 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 <lber.h> +#include <ldap.h> +#include <wchar.h> + +#include "smbldap.h" + +#define NMASLDAP_GET_LOGIN_CONFIG_REQUEST "2.16.840.1.113719.1.39.42.100.3" +#define NMASLDAP_GET_LOGIN_CONFIG_RESPONSE "2.16.840.1.113719.1.39.42.100.4" +#define NMASLDAP_SET_PASSWORD_REQUEST "2.16.840.1.113719.1.39.42.100.11" +#define NMASLDAP_SET_PASSWORD_RESPONSE "2.16.840.1.113719.1.39.42.100.12" +#define NMASLDAP_GET_PASSWORD_REQUEST "2.16.840.1.113719.1.39.42.100.13" +#define NMASLDAP_GET_PASSWORD_RESPONSE "2.16.840.1.113719.1.39.42.100.14" + +#define NMAS_LDAP_EXT_VERSION 1 + +/********************************************************************** + Take the request BER value and input data items and BER encodes the + data into the BER value +**********************************************************************/ + +static int berEncodePasswordData( + struct berval **requestBV, + const char *objectDN, + const char *password, + const char *password2) +{ + int err = 0, rc=0; + BerElement *requestBer = NULL; + + const char * utf8ObjPtr = NULL; + int utf8ObjSize = 0; + const char * utf8PwdPtr = NULL; + int utf8PwdSize = 0; + const char * utf8Pwd2Ptr = NULL; + int utf8Pwd2Size = 0; + + + /* Convert objectDN and tag strings from Unicode to UTF-8 */ + utf8ObjSize = strlen(objectDN)+1; + utf8ObjPtr = objectDN; + + if (password != NULL) + { + utf8PwdSize = strlen(password)+1; + utf8PwdPtr = password; + } + + if (password2 != NULL) + { + utf8Pwd2Size = strlen(password2)+1; + utf8Pwd2Ptr = password2; + } + + /* Allocate a BerElement for the request parameters. */ + if((requestBer = ber_alloc()) == NULL) + { + err = LDAP_ENCODING_ERROR; + goto Cleanup; + } + + if (password != NULL && password2 != NULL) + { + /* BER encode the NMAS Version, the objectDN, and the password */ + rc = ber_printf(requestBer, "{iooo}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize, utf8PwdPtr, utf8PwdSize, utf8Pwd2Ptr, utf8Pwd2Size); + } + else if (password != NULL) + { + /* BER encode the NMAS Version, the objectDN, and the password */ + rc = ber_printf(requestBer, "{ioo}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize, utf8PwdPtr, utf8PwdSize); + } + else + { + /* BER encode the NMAS Version and the objectDN */ + rc = ber_printf(requestBer, "{io}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize); + } + + if (rc < 0) + { + err = LDAP_ENCODING_ERROR; + goto Cleanup; + } + else + { + err = 0; + } + + /* Convert the BER we just built to a berval that we'll send with the extended request. */ + if(ber_flatten(requestBer, requestBV) == LBER_ERROR) + { + err = LDAP_ENCODING_ERROR; + goto Cleanup; + } + +Cleanup: + + if(requestBer) + { + ber_free(requestBer, 1); + } + + return err; +} + +/********************************************************************** + Take the request BER value and input data items and BER encodes the + data into the BER value +**********************************************************************/ + +static int berEncodeLoginData( + struct berval **requestBV, + char *objectDN, + unsigned int methodIDLen, + unsigned int *methodID, + char *tag, + size_t putDataLen, + void *putData) +{ + int err = 0; + BerElement *requestBer = NULL; + + unsigned int i; + unsigned int elemCnt = methodIDLen / sizeof(unsigned int); + + char *utf8ObjPtr=NULL; + int utf8ObjSize = 0; + + char *utf8TagPtr = NULL; + int utf8TagSize = 0; + + utf8ObjPtr = objectDN; + utf8ObjSize = strlen(utf8ObjPtr)+1; + + utf8TagPtr = tag; + utf8TagSize = strlen(utf8TagPtr)+1; + + /* Allocate a BerElement for the request parameters. */ + if((requestBer = ber_alloc()) == NULL) + { + err = LDAP_ENCODING_ERROR; + goto Cleanup; + } + + /* BER encode the NMAS Version and the objectDN */ + err = (ber_printf(requestBer, "{io", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize) < 0) ? LDAP_ENCODING_ERROR : 0; + + /* BER encode the MethodID Length and value */ + if (!err) + { + err = (ber_printf(requestBer, "{i{", methodIDLen) < 0) ? LDAP_ENCODING_ERROR : 0; + } + + for (i = 0; !err && i < elemCnt; i++) + { + err = (ber_printf(requestBer, "i", methodID[i]) < 0) ? LDAP_ENCODING_ERROR : 0; + } + + if (!err) + { + err = (ber_printf(requestBer, "}}", 0) < 0) ? LDAP_ENCODING_ERROR : 0; + } + + if(putData) + { + /* BER Encode the the tag and data */ + err = (ber_printf(requestBer, "oio}", utf8TagPtr, utf8TagSize, putDataLen, putData, putDataLen) < 0) ? LDAP_ENCODING_ERROR : 0; + } + else + { + /* BER Encode the the tag */ + err = (ber_printf(requestBer, "o}", utf8TagPtr, utf8TagSize) < 0) ? LDAP_ENCODING_ERROR : 0; + } + + if (err) + { + goto Cleanup; + } + + /* Convert the BER we just built to a berval that we'll send with the extended request. */ + if(ber_flatten(requestBer, requestBV) == LBER_ERROR) + { + err = LDAP_ENCODING_ERROR; + goto Cleanup; + } + +Cleanup: + + if(requestBer) + { + ber_free(requestBer, 1); + } + + return err; +} + +/********************************************************************** + Takes the reply BER Value and decodes the NMAS server version and + return code and if a non null retData buffer was supplied, tries to + decode the the return data and length +**********************************************************************/ + +static int berDecodeLoginData( + struct berval *replyBV, + int *serverVersion, + size_t *retDataLen, + void *retData ) +{ + int err = 0; + BerElement *replyBer = NULL; + char *retOctStr = NULL; + size_t retOctStrLen = 0; + + if((replyBer = ber_init(replyBV)) == NULL) + { + err = LDAP_OPERATIONS_ERROR; + goto Cleanup; + } + + if(retData) + { + retOctStrLen = *retDataLen + 1; + retOctStr = SMB_MALLOC_ARRAY(char, retOctStrLen); + if(!retOctStr) + { + err = LDAP_OPERATIONS_ERROR; + goto Cleanup; + } + + if(ber_scanf(replyBer, "{iis}", serverVersion, &err, retOctStr, &retOctStrLen) != -1) + { + if (*retDataLen >= retOctStrLen) + { + memcpy(retData, retOctStr, retOctStrLen); + } + else if (!err) + { + err = LDAP_NO_MEMORY; + } + + *retDataLen = retOctStrLen; + } + else if (!err) + { + err = LDAP_DECODING_ERROR; + } + } + else + { + if(ber_scanf(replyBer, "{ii}", serverVersion, &err) == -1) + { + if (!err) + { + err = LDAP_DECODING_ERROR; + } + } + } + +Cleanup: + + if(replyBer) + { + ber_free(replyBer, 1); + } + + if (retOctStr != NULL) + { + memset(retOctStr, 0, retOctStrLen); + free(retOctStr); + } + + return err; +} + +/********************************************************************** + Retrieves data in the login configuration of the specified object + that is tagged with the specified methodID and tag. +**********************************************************************/ + +static int getLoginConfig( + LDAP *ld, + char *objectDN, + unsigned int methodIDLen, + unsigned int *methodID, + char *tag, + size_t *dataLen, + void *data ) +{ + int err = 0; + struct berval *requestBV = NULL; + char *replyOID = NULL; + struct berval *replyBV = NULL; + int serverVersion = 0; + + /* Validate unicode parameters. */ + if((strlen(objectDN) == 0) || ld == NULL) + { + return LDAP_NO_SUCH_ATTRIBUTE; + } + + err = berEncodeLoginData(&requestBV, objectDN, methodIDLen, methodID, tag, 0, NULL); + if(err) + { + goto Cleanup; + } + + /* Call the ldap_extended_operation (synchronously) */ + if((err = ldap_extended_operation_s(ld, NMASLDAP_GET_LOGIN_CONFIG_REQUEST, + requestBV, NULL, NULL, &replyOID, &replyBV))) + { + goto Cleanup; + } + + /* Make sure there is a return OID */ + if(!replyOID) + { + err = LDAP_NOT_SUPPORTED; + goto Cleanup; + } + + /* Is this what we were expecting to get back. */ + if(strcmp(replyOID, NMASLDAP_GET_LOGIN_CONFIG_RESPONSE)) + { + err = LDAP_NOT_SUPPORTED; + goto Cleanup; + } + + /* Do we have a good returned berval? */ + if(!replyBV) + { + /* No; returned berval means we experienced a rather drastic error. */ + /* Return operations error. */ + err = LDAP_OPERATIONS_ERROR; + goto Cleanup; + } + + err = berDecodeLoginData(replyBV, &serverVersion, dataLen, data); + + if(serverVersion != NMAS_LDAP_EXT_VERSION) + { + err = LDAP_OPERATIONS_ERROR; + goto Cleanup; + } + +Cleanup: + + if(replyBV) + { + ber_bvfree(replyBV); + } + + /* Free the return OID string if one was returned. */ + if(replyOID) + { + ldap_memfree(replyOID); + } + + /* Free memory allocated while building the request ber and berval. */ + if(requestBV) + { + ber_bvfree(requestBV); + } + + /* Return the appropriate error/success code. */ + return err; +} + +/********************************************************************** + Attempts to get the Simple Password +**********************************************************************/ + +static int nmasldap_get_simple_pwd( + LDAP *ld, + char *objectDN, + size_t pwdLen, + char *pwd ) +{ + int err = 0; + unsigned int methodID = 0; + unsigned int methodIDLen = sizeof(methodID); + char tag[] = {'P','A','S','S','W','O','R','D',' ','H','A','S','H',0}; + char *pwdBuf=NULL; + size_t pwdBufLen, bufferLen; + + bufferLen = pwdBufLen = pwdLen+2; + pwdBuf = SMB_MALLOC_ARRAY(char, pwdBufLen); /* digest and null */ + if(pwdBuf == NULL) + { + return LDAP_NO_MEMORY; + } + + err = getLoginConfig(ld, objectDN, methodIDLen, &methodID, tag, &pwdBufLen, pwdBuf); + if (err == 0) + { + if (pwdBufLen !=0) + { + pwdBuf[pwdBufLen] = 0; /* null terminate */ + + switch (pwdBuf[0]) + { + case 1: /* cleartext password */ + break; + case 2: /* SHA1 HASH */ + case 3: /* MD5_ID */ + case 4: /* UNIXCrypt_ID */ + case 8: /* SSHA_ID */ + default: /* Unknown digest */ + err = LDAP_INAPPROPRIATE_AUTH; /* only return clear text */ + break; + } + + if (!err) + { + if (pwdLen >= pwdBufLen-1) + { + memcpy(pwd, &pwdBuf[1], pwdBufLen-1); /* skip digest tag and include null */ + } + else + { + err = LDAP_NO_MEMORY; + } + } + } + } + + if (pwdBuf != NULL) + { + memset(pwdBuf, 0, bufferLen); + free(pwdBuf); + } + + return err; +} + + +/********************************************************************** + Attempts to set the Universal Password +**********************************************************************/ + +static int nmasldap_set_password( + LDAP *ld, + const char *objectDN, + const char *pwd ) +{ + int err = 0; + + struct berval *requestBV = NULL; + char *replyOID = NULL; + struct berval *replyBV = NULL; + int serverVersion; + + /* Validate char parameters. */ + if(objectDN == NULL || (strlen(objectDN) == 0) || pwd == NULL || ld == NULL) + { + return LDAP_NO_SUCH_ATTRIBUTE; + } + + err = berEncodePasswordData(&requestBV, objectDN, pwd, NULL); + if(err) + { + goto Cleanup; + } + + /* Call the ldap_extended_operation (synchronously) */ + if((err = ldap_extended_operation_s(ld, NMASLDAP_SET_PASSWORD_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV))) + { + goto Cleanup; + } + + /* Make sure there is a return OID */ + if(!replyOID) + { + err = LDAP_NOT_SUPPORTED; + goto Cleanup; + } + + /* Is this what we were expecting to get back. */ + if(strcmp(replyOID, NMASLDAP_SET_PASSWORD_RESPONSE)) + { + err = LDAP_NOT_SUPPORTED; + goto Cleanup; + } + + /* Do we have a good returned berval? */ + if(!replyBV) + { + /* No; returned berval means we experienced a rather drastic error. */ + /* Return operations error. */ + err = LDAP_OPERATIONS_ERROR; + goto Cleanup; + } + + err = berDecodeLoginData(replyBV, &serverVersion, NULL, NULL); + + if(serverVersion != NMAS_LDAP_EXT_VERSION) + { + err = LDAP_OPERATIONS_ERROR; + goto Cleanup; + } + +Cleanup: + + if(replyBV) + { + ber_bvfree(replyBV); + } + + /* Free the return OID string if one was returned. */ + if(replyOID) + { + ldap_memfree(replyOID); + } + + /* Free memory allocated while building the request ber and berval. */ + if(requestBV) + { + ber_bvfree(requestBV); + } + + /* Return the appropriate error/success code. */ + return err; +} + +/********************************************************************** + Attempts to get the Universal Password +**********************************************************************/ + +static int nmasldap_get_password( + LDAP *ld, + char *objectDN, + size_t *pwdSize, /* in bytes */ + unsigned char *pwd ) +{ + int err = 0; + + struct berval *requestBV = NULL; + char *replyOID = NULL; + struct berval *replyBV = NULL; + int serverVersion; + char *pwdBuf; + size_t pwdBufLen, bufferLen; + + /* Validate char parameters. */ + if(objectDN == NULL || (strlen(objectDN) == 0) || pwdSize == NULL || ld == NULL) + { + return LDAP_NO_SUCH_ATTRIBUTE; + } + + bufferLen = pwdBufLen = *pwdSize; + pwdBuf = SMB_MALLOC_ARRAY(char, pwdBufLen+2); + if(pwdBuf == NULL) + { + return LDAP_NO_MEMORY; + } + + err = berEncodePasswordData(&requestBV, objectDN, NULL, NULL); + if(err) + { + goto Cleanup; + } + + /* Call the ldap_extended_operation (synchronously) */ + if((err = ldap_extended_operation_s(ld, NMASLDAP_GET_PASSWORD_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV))) + { + goto Cleanup; + } + + /* Make sure there is a return OID */ + if(!replyOID) + { + err = LDAP_NOT_SUPPORTED; + goto Cleanup; + } + + /* Is this what we were expecting to get back. */ + if(strcmp(replyOID, NMASLDAP_GET_PASSWORD_RESPONSE)) + { + err = LDAP_NOT_SUPPORTED; + goto Cleanup; + } + + /* Do we have a good returned berval? */ + if(!replyBV) + { + /* No; returned berval means we experienced a rather drastic error. */ + /* Return operations error. */ + err = LDAP_OPERATIONS_ERROR; + goto Cleanup; + } + + err = berDecodeLoginData(replyBV, &serverVersion, &pwdBufLen, pwdBuf); + + if(serverVersion != NMAS_LDAP_EXT_VERSION) + { + err = LDAP_OPERATIONS_ERROR; + goto Cleanup; + } + + if (!err && pwdBufLen != 0) + { + if (*pwdSize >= pwdBufLen+1 && pwd != NULL) + { + memcpy(pwd, pwdBuf, pwdBufLen); + pwd[pwdBufLen] = 0; /* add null termination */ + } + *pwdSize = pwdBufLen; /* does not include null termination */ + } + +Cleanup: + + if(replyBV) + { + ber_bvfree(replyBV); + } + + /* Free the return OID string if one was returned. */ + if(replyOID) + { + ldap_memfree(replyOID); + } + + /* Free memory allocated while building the request ber and berval. */ + if(requestBV) + { + ber_bvfree(requestBV); + } + + if (pwdBuf != NULL) + { + memset(pwdBuf, 0, bufferLen); + free(pwdBuf); + } + + /* Return the appropriate error/success code. */ + return err; +} + +/********************************************************************** + Get the user's password from NDS. + *********************************************************************/ + +int pdb_nds_get_password( + struct smbldap_state *ldap_state, + char *object_dn, + size_t *pwd_len, + char *pwd ) +{ + LDAP *ld = ldap_state->ldap_struct; + int rc = -1; + + rc = nmasldap_get_password(ld, object_dn, pwd_len, (unsigned char *)pwd); + if (rc == LDAP_SUCCESS) { +#ifdef DEBUG_PASSWORD + DEBUG(100,("nmasldap_get_password returned %s for %s\n", pwd, object_dn)); +#endif + DEBUG(5, ("NDS Universal Password retrieved for %s\n", object_dn)); + } else { + DEBUG(3, ("NDS Universal Password NOT retrieved for %s\n", object_dn)); + } + + if (rc != LDAP_SUCCESS) { + rc = nmasldap_get_simple_pwd(ld, object_dn, *pwd_len, pwd); + if (rc == LDAP_SUCCESS) { +#ifdef DEBUG_PASSWORD + DEBUG(100,("nmasldap_get_simple_pwd returned %s for %s\n", pwd, object_dn)); +#endif + DEBUG(5, ("NDS Simple Password retrieved for %s\n", object_dn)); + } else { + /* We couldn't get the password */ + DEBUG(3, ("NDS Simple Password NOT retrieved for %s\n", object_dn)); + return LDAP_INVALID_CREDENTIALS; + } + } + + /* We got the password */ + return LDAP_SUCCESS; +} + +/********************************************************************** + Set the users NDS, Universal and Simple passwords. + ********************************************************************/ + +int pdb_nds_set_password( + struct smbldap_state *ldap_state, + char *object_dn, + const char *pwd ) +{ + LDAP *ld = ldap_state->ldap_struct; + int rc = -1; + LDAPMod **tmpmods = NULL; + + rc = nmasldap_set_password(ld, object_dn, pwd); + if (rc == LDAP_SUCCESS) { + DEBUG(5,("NDS Universal Password changed for user %s\n", object_dn)); + } else { + char *ld_error = NULL; + ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &ld_error); + + /* This will fail if Universal Password is not enabled for the user's context */ + DEBUG(3,("NDS Universal Password could not be changed for user %s: %s (%s)\n", + object_dn, ldap_err2string(rc), ld_error?ld_error:"unknown")); + SAFE_FREE(ld_error); + } + + /* Set eDirectory Password */ + smbldap_set_mod(&tmpmods, LDAP_MOD_REPLACE, "userPassword", pwd); + rc = smbldap_modify(ldap_state, object_dn, tmpmods); + + return rc; +} + +/********************************************************************** + Allow ldap server to update internal login attempt counters by + performing a simple bind. If the samba authentication failed attempt + the bind with a bogus, randomly generated password to count the + failed attempt. If the bind fails even though samba authentication + succeeded, this would indicate that the user's account is disabled, + time restrictions are in place or some other password policy + violation. +*********************************************************************/ + +static NTSTATUS pdb_nds_update_login_attempts(struct pdb_methods *methods, + struct samu *sam_acct, bool success) +{ + struct ldapsam_privates *ldap_state; + + if ((!methods) || (!sam_acct)) { + DEBUG(3,("pdb_nds_update_login_attempts: invalid parameter.\n")); + return NT_STATUS_MEMORY_NOT_ALLOCATED; + } + + ldap_state = (struct ldapsam_privates *)methods->private_data; + + if (ldap_state) { + /* Attempt simple bind with user credentials to update eDirectory + password policy */ + int rc = 0; + char *dn; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + const char **attr_list; + size_t pwd_len; + char clear_text_pw[512]; + LDAP *ld = NULL; + const char *username = pdb_get_username(sam_acct); + bool got_clear_text_pw = False; + + DEBUG(5,("pdb_nds_update_login_attempts: %s login for %s\n", + success ? "Successful" : "Failed", username)); + + result = (LDAPMessage *)pdb_get_backend_private_data(sam_acct, methods); + if (!result) { + attr_list = get_userattr_list(NULL, + ldap_state->schema_ver); + rc = ldapsam_search_suffix_by_name(ldap_state, username, &result, attr_list ); + TALLOC_FREE( attr_list ); + if (rc != LDAP_SUCCESS) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + pdb_set_backend_private_data(sam_acct, result, NULL, + methods, PDB_CHANGED); + talloc_autofree_ldapmsg(sam_acct, result); + } + + if (ldap_count_entries(ldap_state->smbldap_state->ldap_struct, result) == 0) { + DEBUG(0, ("pdb_nds_update_login_attempts: No user to modify!\n")); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + entry = ldap_first_entry(ldap_state->smbldap_state->ldap_struct, result); + dn = smbldap_get_dn(ldap_state->smbldap_state->ldap_struct, entry); + if (!dn) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + DEBUG(3, ("pdb_nds_update_login_attempts: username %s found dn '%s'\n", username, dn)); + + pwd_len = sizeof(clear_text_pw); + if (success == True) { + if (pdb_nds_get_password(ldap_state->smbldap_state, dn, &pwd_len, clear_text_pw) == LDAP_SUCCESS) { + /* Got clear text password. Use simple ldap bind */ + got_clear_text_pw = True; + } + } else { + generate_random_buffer((unsigned char *)clear_text_pw, 24); + clear_text_pw[24] = '\0'; + DEBUG(5,("pdb_nds_update_login_attempts: using random password %s\n", clear_text_pw)); + } + + if((success != True) || (got_clear_text_pw == True)) { + + rc = smb_ldap_setup_full_conn(&ld, ldap_state->location); + if (rc) { + return NT_STATUS_INVALID_CONNECTION; + } + + /* Attempt simple bind with real or bogus password */ + rc = ldap_simple_bind_s(ld, dn, clear_text_pw); + ldap_unbind(ld); + if (rc == LDAP_SUCCESS) { + DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Successful for %s\n", username)); + } else { + NTSTATUS nt_status = NT_STATUS_ACCOUNT_RESTRICTION; + DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Failed for %s\n", username)); + switch(rc) { + case LDAP_INVALID_CREDENTIALS: + nt_status = NT_STATUS_WRONG_PASSWORD; + break; + case LDAP_UNWILLING_TO_PERFORM: + /* eDir returns this if the account was disabled. */ + /* The problem is we don't know if the given + password was correct for this account or + not. We have to return more info than we + should and tell the client NT_STATUS_ACCOUNT_DISABLED + so they don't think the password was bad. JRA. */ + nt_status = NT_STATUS_ACCOUNT_DISABLED; + break; + default: + break; + } + return nt_status; + } + } + } + + return NT_STATUS_OK; +} + +/********************************************************************** + Intitalise the parts of the pdb_methods structuire that are common + to NDS_ldapsam modes + *********************************************************************/ + +static NTSTATUS pdb_init_NDS_ldapsam_common(struct pdb_methods **pdb_method, const char *location) +{ + struct ldapsam_privates *ldap_state = + (struct ldapsam_privates *)((*pdb_method)->private_data); + + /* Mark this as eDirectory ldap */ + ldap_state->is_nds_ldap = True; + + /* Add pdb_nds specific method for updating login attempts. */ + (*pdb_method)->update_login_attempts = pdb_nds_update_login_attempts; + + /* Save location for use in pdb_nds_update_login_attempts */ + ldap_state->location = SMB_STRDUP(location); + + return NT_STATUS_OK; +} + + +/********************************************************************** + Initialise the 'nds compat' mode for pdb_ldap + *********************************************************************/ + +static NTSTATUS pdb_init_NDS_ldapsam_compat(struct pdb_methods **pdb_method, const char *location) +{ + NTSTATUS nt_status = pdb_init_ldapsam_compat(pdb_method, location); + + (*pdb_method)->name = "NDS_ldapsam_compat"; + + pdb_init_NDS_ldapsam_common(pdb_method, location); + + return nt_status; +} + + +/********************************************************************** + Initialise the 'nds' normal mode for pdb_ldap + *********************************************************************/ + +static NTSTATUS pdb_init_NDS_ldapsam(struct pdb_methods **pdb_method, const char *location) +{ + NTSTATUS nt_status = pdb_init_ldapsam(pdb_method, location); + + (*pdb_method)->name = "NDS_ldapsam"; + + pdb_init_NDS_ldapsam_common(pdb_method, location); + + return nt_status; +} + +NTSTATUS pdb_nds_init(void) +{ + NTSTATUS nt_status; + if (!NT_STATUS_IS_OK(nt_status = smb_register_passdb(PASSDB_INTERFACE_VERSION, "NDS_ldapsam", pdb_init_NDS_ldapsam))) + return nt_status; + + if (!NT_STATUS_IS_OK(nt_status = smb_register_passdb(PASSDB_INTERFACE_VERSION, "NDS_ldapsam_compat", pdb_init_NDS_ldapsam_compat))) + return nt_status; + + return NT_STATUS_OK; +} diff --git a/source3/passdb/pdb_smbpasswd.c b/source3/passdb/pdb_smbpasswd.c new file mode 100644 index 0000000000..f72638bed5 --- /dev/null +++ b/source3/passdb/pdb_smbpasswd.c @@ -0,0 +1,1718 @@ +/* + * Unix SMB/CIFS implementation. + * SMB parameters and setup + * Copyright (C) Andrew Tridgell 1992-1998 + * Modified by Jeremy Allison 1995. + * Modified by Gerald (Jerry) Carter 2000-2001,2003 + * Modified by Andrew Bartlett 2002. + * + * 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +/* + smb_passwd is analogous to sam_passwd used everywhere + else. However, smb_passwd is limited to the information + stored by an smbpasswd entry + */ + +struct smb_passwd +{ + uint32 smb_userid; /* this is actually the unix uid_t */ + const char *smb_name; /* username string */ + + const unsigned char *smb_passwd; /* Null if no password */ + const unsigned char *smb_nt_passwd; /* Null if no password */ + + uint16 acct_ctrl; /* account info (ACB_xxxx bit-mask) */ + time_t pass_last_set_time; /* password last set time */ +}; + +struct smbpasswd_privates +{ + /* used for maintain locks on the smbpasswd file */ + int pw_file_lock_depth; + + /* Global File pointer */ + FILE *pw_file; + + /* formerly static variables */ + struct smb_passwd pw_buf; + fstring user_name; + unsigned char smbpwd[16]; + unsigned char smbntpwd[16]; + + /* retrive-once info */ + const char *smbpasswd_file; +}; + +enum pwf_access_type { PWF_READ, PWF_UPDATE, PWF_CREATE }; + +static SIG_ATOMIC_T gotalarm; + +/*************************************************************** + Signal function to tell us we timed out. +****************************************************************/ + +static void gotalarm_sig(void) +{ + gotalarm = 1; +} + +/*************************************************************** + Lock or unlock a fd for a known lock type. Abandon after waitsecs + seconds. +****************************************************************/ + +static bool do_file_lock(int fd, int waitsecs, int type) +{ + SMB_STRUCT_FLOCK lock; + int ret; + void (*oldsig_handler)(int); + + gotalarm = 0; + oldsig_handler = CatchSignal(SIGALRM, SIGNAL_CAST gotalarm_sig); + + lock.l_type = type; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 1; + lock.l_pid = 0; + + alarm(waitsecs); + /* Note we must *NOT* use sys_fcntl here ! JRA */ + ret = fcntl(fd, SMB_F_SETLKW, &lock); + alarm(0); + CatchSignal(SIGALRM, SIGNAL_CAST oldsig_handler); + + if (gotalarm) { + DEBUG(0, ("do_file_lock: failed to %s file.\n", + type == F_UNLCK ? "unlock" : "lock")); + return False; + } + + return (ret == 0); +} + +/*************************************************************** + Lock an fd. Abandon after waitsecs seconds. +****************************************************************/ + +static bool pw_file_lock(int fd, int type, int secs, int *plock_depth) +{ + if (fd < 0) { + return False; + } + + if(*plock_depth == 0) { + if (!do_file_lock(fd, secs, type)) { + DEBUG(10,("pw_file_lock: locking file failed, error = %s.\n", + strerror(errno))); + return False; + } + } + + (*plock_depth)++; + + return True; +} + +/*************************************************************** + Unlock an fd. Abandon after waitsecs seconds. +****************************************************************/ + +static bool pw_file_unlock(int fd, int *plock_depth) +{ + bool ret=True; + + if (fd == 0 || *plock_depth == 0) { + return True; + } + + if(*plock_depth == 1) { + ret = do_file_lock(fd, 5, F_UNLCK); + } + + if (*plock_depth > 0) { + (*plock_depth)--; + } + + if(!ret) { + DEBUG(10,("pw_file_unlock: unlocking file failed, error = %s.\n", + strerror(errno))); + } + return ret; +} + +/************************************************************** + Intialize a smb_passwd struct + *************************************************************/ + +static void pdb_init_smb(struct smb_passwd *user) +{ + if (user == NULL) + return; + ZERO_STRUCTP (user); + + user->pass_last_set_time = (time_t)0; +} + +/*************************************************************** + Internal fn to enumerate the smbpasswd list. Returns a void pointer + to ensure no modification outside this module. Checks for atomic + rename of smbpasswd file on update or create once the lock has + been granted to prevent race conditions. JRA. +****************************************************************/ + +static FILE *startsmbfilepwent(const char *pfile, enum pwf_access_type type, int *lock_depth) +{ + FILE *fp = NULL; + const char *open_mode = NULL; + int race_loop = 0; + int lock_type = F_RDLCK; + + if (!*pfile) { + DEBUG(0, ("startsmbfilepwent: No SMB password file set\n")); + return (NULL); + } + + switch(type) { + case PWF_READ: + open_mode = "rb"; + lock_type = F_RDLCK; + break; + case PWF_UPDATE: + open_mode = "r+b"; + lock_type = F_WRLCK; + break; + case PWF_CREATE: + /* + * Ensure atomic file creation. + */ + { + int i, fd = -1; + + for(i = 0; i < 5; i++) { + if((fd = sys_open(pfile, O_CREAT|O_TRUNC|O_EXCL|O_RDWR, 0600))!=-1) { + break; + } + sys_usleep(200); /* Spin, spin... */ + } + if(fd == -1) { + DEBUG(0,("startsmbfilepwent_internal: too many race conditions \ +creating file %s\n", pfile)); + return NULL; + } + close(fd); + open_mode = "r+b"; + lock_type = F_WRLCK; + break; + } + } + + for(race_loop = 0; race_loop < 5; race_loop++) { + DEBUG(10, ("startsmbfilepwent_internal: opening file %s\n", pfile)); + + if((fp = sys_fopen(pfile, open_mode)) == NULL) { + + /* + * If smbpasswd file doesn't exist, then create new one. This helps to avoid + * confusing error msg when adding user account first time. + */ + if (errno == ENOENT) { + if ((fp = sys_fopen(pfile, "a+")) != NULL) { + DEBUG(0, ("startsmbfilepwent_internal: file %s did not \ +exist. File successfully created.\n", pfile)); + } else { + DEBUG(0, ("startsmbfilepwent_internal: file %s did not \ +exist. Couldn't create new one. Error was: %s", + pfile, strerror(errno))); + return NULL; + } + } else { + DEBUG(0, ("startsmbfilepwent_internal: unable to open file %s. \ +Error was: %s\n", pfile, strerror(errno))); + return NULL; + } + } + + if (!pw_file_lock(fileno(fp), lock_type, 5, lock_depth)) { + DEBUG(0, ("startsmbfilepwent_internal: unable to lock file %s. \ +Error was %s\n", pfile, strerror(errno) )); + fclose(fp); + return NULL; + } + + /* + * Only check for replacement races on update or create. + * For read we don't mind if the data is one record out of date. + */ + + if(type == PWF_READ) { + break; + } else { + SMB_STRUCT_STAT sbuf1, sbuf2; + + /* + * Avoid the potential race condition between the open and the lock + * by doing a stat on the filename and an fstat on the fd. If the + * two inodes differ then someone did a rename between the open and + * the lock. Back off and try the open again. Only do this 5 times to + * prevent infinate loops. JRA. + */ + + if (sys_stat(pfile,&sbuf1) != 0) { + DEBUG(0, ("startsmbfilepwent_internal: unable to stat file %s. \ +Error was %s\n", pfile, strerror(errno))); + pw_file_unlock(fileno(fp), lock_depth); + fclose(fp); + return NULL; + } + + if (sys_fstat(fileno(fp),&sbuf2) != 0) { + DEBUG(0, ("startsmbfilepwent_internal: unable to fstat file %s. \ +Error was %s\n", pfile, strerror(errno))); + pw_file_unlock(fileno(fp), lock_depth); + fclose(fp); + return NULL; + } + + if( sbuf1.st_ino == sbuf2.st_ino) { + /* No race. */ + break; + } + + /* + * Race occurred - back off and try again... + */ + + pw_file_unlock(fileno(fp), lock_depth); + fclose(fp); + } + } + + if(race_loop == 5) { + DEBUG(0, ("startsmbfilepwent_internal: too many race conditions opening file %s\n", pfile)); + return NULL; + } + + /* Set a buffer to do more efficient reads */ + setvbuf(fp, (char *)NULL, _IOFBF, 1024); + + /* Make sure it is only rw by the owner */ +#ifdef HAVE_FCHMOD + if(fchmod(fileno(fp), S_IRUSR|S_IWUSR) == -1) { +#else + if(chmod(pfile, S_IRUSR|S_IWUSR) == -1) { +#endif + DEBUG(0, ("startsmbfilepwent_internal: failed to set 0600 permissions on password file %s. \ +Error was %s\n.", pfile, strerror(errno) )); + pw_file_unlock(fileno(fp), lock_depth); + fclose(fp); + return NULL; + } + + /* We have a lock on the file. */ + return fp; +} + +/*************************************************************** + End enumeration of the smbpasswd list. +****************************************************************/ + +static void endsmbfilepwent(FILE *fp, int *lock_depth) +{ + if (!fp) { + return; + } + + pw_file_unlock(fileno(fp), lock_depth); + fclose(fp); + DEBUG(7, ("endsmbfilepwent_internal: closed password file.\n")); +} + +/************************************************************************* + Routine to return the next entry in the smbpasswd list. + *************************************************************************/ + +static struct smb_passwd *getsmbfilepwent(struct smbpasswd_privates *smbpasswd_state, FILE *fp) +{ + /* Static buffers we will return. */ + struct smb_passwd *pw_buf = &smbpasswd_state->pw_buf; + char *user_name = smbpasswd_state->user_name; + unsigned char *smbpwd = smbpasswd_state->smbpwd; + unsigned char *smbntpwd = smbpasswd_state->smbntpwd; + char linebuf[256]; + int c; + unsigned char *p; + long uidval; + size_t linebuf_len; + char *status; + + if(fp == NULL) { + DEBUG(0,("getsmbfilepwent: Bad password file pointer.\n")); + return NULL; + } + + pdb_init_smb(pw_buf); + pw_buf->acct_ctrl = ACB_NORMAL; + + /* + * Scan the file, a line at a time and check if the name matches. + */ + status = linebuf; + while (status && !feof(fp)) { + linebuf[0] = '\0'; + + status = fgets(linebuf, 256, fp); + if (status == NULL && ferror(fp)) { + return NULL; + } + + /* + * Check if the string is terminated with a newline - if not + * then we must keep reading and discard until we get one. + */ + if ((linebuf_len = strlen(linebuf)) == 0) { + continue; + } + + if (linebuf[linebuf_len - 1] != '\n') { + c = '\0'; + while (!ferror(fp) && !feof(fp)) { + c = fgetc(fp); + if (c == '\n') { + break; + } + } + } else { + linebuf[linebuf_len - 1] = '\0'; + } + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("getsmbfilepwent: got line |%s|\n", linebuf)); +#endif + if ((linebuf[0] == 0) && feof(fp)) { + DEBUG(4, ("getsmbfilepwent: end of file reached\n")); + break; + } + + /* + * The line we have should be of the form :- + * + * username:uid:32hex bytes:[Account type]:LCT-12345678....other flags presently + * ignored.... + * + * or, + * + * username:uid:32hex bytes:32hex bytes:[Account type]:LCT-12345678....ignored.... + * + * if Windows NT compatible passwords are also present. + * [Account type] is an ascii encoding of the type of account. + * LCT-(8 hex digits) is the time_t value of the last change time. + */ + + if (linebuf[0] == '#' || linebuf[0] == '\0') { + DEBUG(6, ("getsmbfilepwent: skipping comment or blank line\n")); + continue; + } + p = (unsigned char *) strchr_m(linebuf, ':'); + if (p == NULL) { + DEBUG(0, ("getsmbfilepwent: malformed password entry (no :)\n")); + continue; + } + + strncpy(user_name, linebuf, PTR_DIFF(p, linebuf)); + user_name[PTR_DIFF(p, linebuf)] = '\0'; + + /* Get smb uid. */ + + p++; /* Go past ':' */ + + if(*p == '-') { + DEBUG(0, ("getsmbfilepwent: user name %s has a negative uid.\n", user_name)); + continue; + } + + if (!isdigit(*p)) { + DEBUG(0, ("getsmbfilepwent: malformed password entry for user %s (uid not number)\n", + user_name)); + continue; + } + + uidval = atoi((char *) p); + + while (*p && isdigit(*p)) { + p++; + } + + if (*p != ':') { + DEBUG(0, ("getsmbfilepwent: malformed password entry for user %s (no : after uid)\n", + user_name)); + continue; + } + + pw_buf->smb_name = user_name; + pw_buf->smb_userid = uidval; + + /* + * Now get the password value - this should be 32 hex digits + * which are the ascii representations of a 16 byte string. + * Get two at a time and put them into the password. + */ + + /* Skip the ':' */ + p++; + + if (linebuf_len < (PTR_DIFF(p, linebuf) + 33)) { + DEBUG(0, ("getsmbfilepwent: malformed password entry for user %s (passwd too short)\n", + user_name )); + continue; + } + + if (p[32] != ':') { + DEBUG(0, ("getsmbfilepwent: malformed password entry for user %s (no terminating :)\n", + user_name)); + continue; + } + + if (strnequal((char *) p, "NO PASSWORD", 11)) { + pw_buf->smb_passwd = NULL; + pw_buf->acct_ctrl |= ACB_PWNOTREQ; + } else { + if (*p == '*' || *p == 'X') { + /* NULL LM password */ + pw_buf->smb_passwd = NULL; + DEBUG(10, ("getsmbfilepwent: LM password for user %s invalidated\n", user_name)); + } else if (pdb_gethexpwd((char *)p, smbpwd)) { + pw_buf->smb_passwd = smbpwd; + } else { + pw_buf->smb_passwd = NULL; + DEBUG(0, ("getsmbfilepwent: Malformed Lanman password entry for user %s \ +(non hex chars)\n", user_name)); + } + } + + /* + * Now check if the NT compatible password is + * available. + */ + pw_buf->smb_nt_passwd = NULL; + p += 33; /* Move to the first character of the line after the lanman password. */ + if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) { + if (*p != '*' && *p != 'X') { + if(pdb_gethexpwd((char *)p,smbntpwd)) { + pw_buf->smb_nt_passwd = smbntpwd; + } + } + p += 33; /* Move to the first character of the line after the NT password. */ + } + + DEBUG(5,("getsmbfilepwent: returning passwd entry for user %s, uid %ld\n", + user_name, uidval)); + + if (*p == '[') { + unsigned char *end_p = (unsigned char *)strchr_m((char *)p, ']'); + pw_buf->acct_ctrl = pdb_decode_acct_ctrl((char*)p); + + /* Must have some account type set. */ + if(pw_buf->acct_ctrl == 0) { + pw_buf->acct_ctrl = ACB_NORMAL; + } + + /* Now try and get the last change time. */ + if(end_p) { + p = end_p + 1; + } + if(*p == ':') { + p++; + if(*p && (StrnCaseCmp((char *)p, "LCT-", 4)==0)) { + int i; + p += 4; + for(i = 0; i < 8; i++) { + if(p[i] == '\0' || !isxdigit(p[i])) { + break; + } + } + if(i == 8) { + /* + * p points at 8 characters of hex digits - + * read into a time_t as the seconds since + * 1970 that the password was last changed. + */ + pw_buf->pass_last_set_time = (time_t)strtol((char *)p, NULL, 16); + } + } + } + } else { + /* 'Old' style file. Fake up based on user name. */ + /* + * Currently trust accounts are kept in the same + * password file as 'normal accounts'. If this changes + * we will have to fix this code. JRA. + */ + if(pw_buf->smb_name[strlen(pw_buf->smb_name) - 1] == '$') { + pw_buf->acct_ctrl &= ~ACB_NORMAL; + pw_buf->acct_ctrl |= ACB_WSTRUST; + } + } + + return pw_buf; + } + + DEBUG(5,("getsmbfilepwent: end of file reached.\n")); + return NULL; +} + +/************************************************************************ + Create a new smbpasswd entry - malloced space returned. +*************************************************************************/ + +static char *format_new_smbpasswd_entry(const struct smb_passwd *newpwd) +{ + int new_entry_length; + char *new_entry; + char *p; + + new_entry_length = strlen(newpwd->smb_name) + 1 + 15 + 1 + 32 + 1 + 32 + 1 + + NEW_PW_FORMAT_SPACE_PADDED_LEN + 1 + 13 + 2; + + if((new_entry = (char *)SMB_MALLOC( new_entry_length )) == NULL) { + DEBUG(0, ("format_new_smbpasswd_entry: Malloc failed adding entry for user %s.\n", + newpwd->smb_name )); + return NULL; + } + + slprintf(new_entry, new_entry_length - 1, "%s:%u:", newpwd->smb_name, (unsigned)newpwd->smb_userid); + + p = new_entry+strlen(new_entry); + pdb_sethexpwd(p, newpwd->smb_passwd, newpwd->acct_ctrl); + p+=strlen(p); + *p = ':'; + p++; + + pdb_sethexpwd(p, newpwd->smb_nt_passwd, newpwd->acct_ctrl); + p+=strlen(p); + *p = ':'; + p++; + + /* Add the account encoding and the last change time. */ + slprintf((char *)p, new_entry_length - 1 - (p - new_entry), "%s:LCT-%08X:\n", + pdb_encode_acct_ctrl(newpwd->acct_ctrl, NEW_PW_FORMAT_SPACE_PADDED_LEN), + (uint32)newpwd->pass_last_set_time); + + return new_entry; +} + +/************************************************************************ + Routine to add an entry to the smbpasswd file. +*************************************************************************/ + +static NTSTATUS add_smbfilepwd_entry(struct smbpasswd_privates *smbpasswd_state, + struct smb_passwd *newpwd) +{ + const char *pfile = smbpasswd_state->smbpasswd_file; + struct smb_passwd *pwd = NULL; + FILE *fp = NULL; + int wr_len; + int fd; + size_t new_entry_length; + char *new_entry; + SMB_OFF_T offpos; + + /* Open the smbpassword file - for update. */ + fp = startsmbfilepwent(pfile, PWF_UPDATE, &smbpasswd_state->pw_file_lock_depth); + + if (fp == NULL && errno == ENOENT) { + /* Try again - create. */ + fp = startsmbfilepwent(pfile, PWF_CREATE, &smbpasswd_state->pw_file_lock_depth); + } + + if (fp == NULL) { + DEBUG(0, ("add_smbfilepwd_entry: unable to open file.\n")); + return map_nt_error_from_unix(errno); + } + + /* + * Scan the file, a line at a time and check if the name matches. + */ + + while ((pwd = getsmbfilepwent(smbpasswd_state, fp)) != NULL) { + if (strequal(newpwd->smb_name, pwd->smb_name)) { + DEBUG(0, ("add_smbfilepwd_entry: entry with name %s already exists\n", pwd->smb_name)); + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + return NT_STATUS_USER_EXISTS; + } + } + + /* Ok - entry doesn't exist. We can add it */ + + /* Create a new smb passwd entry and set it to the given password. */ + /* + * The add user write needs to be atomic - so get the fd from + * the fp and do a raw write() call. + */ + fd = fileno(fp); + + if((offpos = sys_lseek(fd, 0, SEEK_END)) == -1) { + NTSTATUS result = map_nt_error_from_unix(errno); + DEBUG(0, ("add_smbfilepwd_entry(sys_lseek): Failed to add entry for user %s to file %s. \ +Error was %s\n", newpwd->smb_name, pfile, strerror(errno))); + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + return result; + } + + if((new_entry = format_new_smbpasswd_entry(newpwd)) == NULL) { + DEBUG(0, ("add_smbfilepwd_entry(malloc): Failed to add entry for user %s to file %s. \ +Error was %s\n", newpwd->smb_name, pfile, strerror(errno))); + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + return NT_STATUS_NO_MEMORY; + } + + new_entry_length = strlen(new_entry); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("add_smbfilepwd_entry(%d): new_entry_len %d made line |%s|", + fd, (int)new_entry_length, new_entry)); +#endif + + if ((wr_len = write(fd, new_entry, new_entry_length)) != new_entry_length) { + NTSTATUS result = map_nt_error_from_unix(errno); + DEBUG(0, ("add_smbfilepwd_entry(write): %d Failed to add entry for user %s to file %s. \ +Error was %s\n", wr_len, newpwd->smb_name, pfile, strerror(errno))); + + /* Remove the entry we just wrote. */ + if(sys_ftruncate(fd, offpos) == -1) { + DEBUG(0, ("add_smbfilepwd_entry: ERROR failed to ftruncate file %s. \ +Error was %s. Password file may be corrupt ! Please examine by hand !\n", + newpwd->smb_name, strerror(errno))); + } + + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + free(new_entry); + return result; + } + + free(new_entry); + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + return NT_STATUS_OK; +} + +/************************************************************************ + Routine to search the smbpasswd file for an entry matching the username. + and then modify its password entry. We can't use the startsmbpwent()/ + getsmbpwent()/endsmbpwent() interfaces here as we depend on looking + in the actual file to decide how much room we have to write data. + override = False, normal + override = True, override XXXXXXXX'd out password or NO PASS +************************************************************************/ + +static bool mod_smbfilepwd_entry(struct smbpasswd_privates *smbpasswd_state, const struct smb_passwd* pwd) +{ + /* Static buffers we will return. */ + fstring user_name; + + char *status; + char linebuf[256]; + char readbuf[1024]; + int c; + fstring ascii_p16; + fstring encode_bits; + unsigned char *p = NULL; + size_t linebuf_len = 0; + FILE *fp; + int lockfd; + const char *pfile = smbpasswd_state->smbpasswd_file; + bool found_entry = False; + bool got_pass_last_set_time = False; + + SMB_OFF_T pwd_seekpos = 0; + + int i; + int wr_len; + int fd; + + if (!*pfile) { + DEBUG(0, ("No SMB password file set\n")); + return False; + } + DEBUG(10, ("mod_smbfilepwd_entry: opening file %s\n", pfile)); + + fp = sys_fopen(pfile, "r+"); + + if (fp == NULL) { + DEBUG(0, ("mod_smbfilepwd_entry: unable to open file %s\n", pfile)); + return False; + } + /* Set a buffer to do more efficient reads */ + setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf)); + + lockfd = fileno(fp); + + if (!pw_file_lock(lockfd, F_WRLCK, 5, &smbpasswd_state->pw_file_lock_depth)) { + DEBUG(0, ("mod_smbfilepwd_entry: unable to lock file %s\n", pfile)); + fclose(fp); + return False; + } + + /* Make sure it is only rw by the owner */ + chmod(pfile, 0600); + + /* We have a write lock on the file. */ + /* + * Scan the file, a line at a time and check if the name matches. + */ + status = linebuf; + while (status && !feof(fp)) { + pwd_seekpos = sys_ftell(fp); + + linebuf[0] = '\0'; + + status = fgets(linebuf, sizeof(linebuf), fp); + if (status == NULL && ferror(fp)) { + pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + /* + * Check if the string is terminated with a newline - if not + * then we must keep reading and discard until we get one. + */ + linebuf_len = strlen(linebuf); + if (linebuf[linebuf_len - 1] != '\n') { + c = '\0'; + while (!ferror(fp) && !feof(fp)) { + c = fgetc(fp); + if (c == '\n') { + break; + } + } + } else { + linebuf[linebuf_len - 1] = '\0'; + } + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("mod_smbfilepwd_entry: got line |%s|\n", linebuf)); +#endif + + if ((linebuf[0] == 0) && feof(fp)) { + DEBUG(4, ("mod_smbfilepwd_entry: end of file reached\n")); + break; + } + + /* + * The line we have should be of the form :- + * + * username:uid:[32hex bytes]:....other flags presently + * ignored.... + * + * or, + * + * username:uid:[32hex bytes]:[32hex bytes]:[attributes]:LCT-XXXXXXXX:...ignored. + * + * if Windows NT compatible passwords are also present. + */ + + if (linebuf[0] == '#' || linebuf[0] == '\0') { + DEBUG(6, ("mod_smbfilepwd_entry: skipping comment or blank line\n")); + continue; + } + + p = (unsigned char *) strchr_m(linebuf, ':'); + + if (p == NULL) { + DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry (no :)\n")); + continue; + } + + strncpy(user_name, linebuf, PTR_DIFF(p, linebuf)); + user_name[PTR_DIFF(p, linebuf)] = '\0'; + if (strequal(user_name, pwd->smb_name)) { + found_entry = True; + break; + } + } + + if (!found_entry) { + pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth); + fclose(fp); + + DEBUG(2, ("Cannot update entry for user %s, as they don't exist in the smbpasswd file!\n", + pwd->smb_name)); + return False; + } + + DEBUG(6, ("mod_smbfilepwd_entry: entry exists for user %s\n", pwd->smb_name)); + + /* User name matches - get uid and password */ + p++; /* Go past ':' */ + + if (!isdigit(*p)) { + DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (uid not number)\n", + pwd->smb_name)); + pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + while (*p && isdigit(*p)) { + p++; + } + if (*p != ':') { + DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (no : after uid)\n", + pwd->smb_name)); + pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + /* + * Now get the password value - this should be 32 hex digits + * which are the ascii representations of a 16 byte string. + * Get two at a time and put them into the password. + */ + p++; + + /* Record exact password position */ + pwd_seekpos += PTR_DIFF(p, linebuf); + + if (linebuf_len < (PTR_DIFF(p, linebuf) + 33)) { + DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (passwd too short)\n", + pwd->smb_name)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return (False); + } + + if (p[32] != ':') { + DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (no terminating :)\n", + pwd->smb_name)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + /* Now check if the NT compatible password is available. */ + p += 33; /* Move to the first character of the line after the lanman password. */ + if (linebuf_len < (PTR_DIFF(p, linebuf) + 33)) { + DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (passwd too short)\n", + pwd->smb_name)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return (False); + } + + if (p[32] != ':') { + DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (no terminating :)\n", + pwd->smb_name)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + /* + * Now check if the account info and the password last + * change time is available. + */ + p += 33; /* Move to the first character of the line after the NT password. */ + + if (*p == '[') { + i = 0; + encode_bits[i++] = *p++; + while((linebuf_len > PTR_DIFF(p, linebuf)) && (*p != ']')) { + encode_bits[i++] = *p++; + } + + encode_bits[i++] = ']'; + encode_bits[i++] = '\0'; + + if(i == NEW_PW_FORMAT_SPACE_PADDED_LEN) { + /* + * We are using a new format, space padded + * acct ctrl field. Encode the given acct ctrl + * bits into it. + */ + fstrcpy(encode_bits, pdb_encode_acct_ctrl(pwd->acct_ctrl, NEW_PW_FORMAT_SPACE_PADDED_LEN)); + } else { + DEBUG(0,("mod_smbfilepwd_entry: Using old smbpasswd format for user %s. \ +This is no longer supported.!\n", pwd->smb_name)); + DEBUG(0,("mod_smbfilepwd_entry: No changes made, failing.!\n")); + pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + /* Go past the ']' */ + if(linebuf_len > PTR_DIFF(p, linebuf)) { + p++; + } + + if((linebuf_len > PTR_DIFF(p, linebuf)) && (*p == ':')) { + p++; + + /* We should be pointing at the LCT entry. */ + if((linebuf_len > (PTR_DIFF(p, linebuf) + 13)) && (StrnCaseCmp((char *)p, "LCT-", 4) == 0)) { + p += 4; + for(i = 0; i < 8; i++) { + if(p[i] == '\0' || !isxdigit(p[i])) { + break; + } + } + if(i == 8) { + /* + * p points at 8 characters of hex digits - + * read into a time_t as the seconds since + * 1970 that the password was last changed. + */ + got_pass_last_set_time = True; + } /* i == 8 */ + } /* *p && StrnCaseCmp() */ + } /* p == ':' */ + } /* p == '[' */ + + /* Entry is correctly formed. */ + + /* Create the 32 byte representation of the new p16 */ + pdb_sethexpwd(ascii_p16, pwd->smb_passwd, pwd->acct_ctrl); + + /* Add on the NT md4 hash */ + ascii_p16[32] = ':'; + wr_len = 66; + pdb_sethexpwd(ascii_p16+33, pwd->smb_nt_passwd, pwd->acct_ctrl); + ascii_p16[65] = ':'; + ascii_p16[66] = '\0'; /* null-terminate the string so that strlen works */ + + /* Add on the account info bits and the time of last password change. */ + if(got_pass_last_set_time) { + slprintf(&ascii_p16[strlen(ascii_p16)], + sizeof(ascii_p16)-(strlen(ascii_p16)+1), + "%s:LCT-%08X:", + encode_bits, (uint32)pwd->pass_last_set_time ); + wr_len = strlen(ascii_p16); + } + +#ifdef DEBUG_PASSWORD + DEBUG(100,("mod_smbfilepwd_entry: ")); + dump_data(100, (uint8 *)ascii_p16, wr_len); +#endif + + if(wr_len > sizeof(linebuf)) { + DEBUG(0, ("mod_smbfilepwd_entry: line to write (%d) is too long.\n", wr_len+1)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return (False); + } + + /* + * Do an atomic write into the file at the position defined by + * seekpos. + */ + + /* The mod user write needs to be atomic - so get the fd from + the fp and do a raw write() call. + */ + + fd = fileno(fp); + + if (sys_lseek(fd, pwd_seekpos - 1, SEEK_SET) != pwd_seekpos - 1) { + DEBUG(0, ("mod_smbfilepwd_entry: seek fail on file %s.\n", pfile)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + /* Sanity check - ensure the areas we are writing are framed by ':' */ + if (read(fd, linebuf, wr_len+1) != wr_len+1) { + DEBUG(0, ("mod_smbfilepwd_entry: read fail on file %s.\n", pfile)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + if ((linebuf[0] != ':') || (linebuf[wr_len] != ':')) { + DEBUG(0, ("mod_smbfilepwd_entry: check on passwd file %s failed.\n", pfile)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + if (sys_lseek(fd, pwd_seekpos, SEEK_SET) != pwd_seekpos) { + DEBUG(0, ("mod_smbfilepwd_entry: seek fail on file %s.\n", pfile)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + if (write(fd, ascii_p16, wr_len) != wr_len) { + DEBUG(0, ("mod_smbfilepwd_entry: write failed in passwd file %s\n", pfile)); + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return False; + } + + pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth); + fclose(fp); + return True; +} + +/************************************************************************ + Routine to delete an entry in the smbpasswd file by name. +*************************************************************************/ + +static bool del_smbfilepwd_entry(struct smbpasswd_privates *smbpasswd_state, const char *name) +{ + const char *pfile = smbpasswd_state->smbpasswd_file; + char *pfile2 = NULL; + struct smb_passwd *pwd = NULL; + FILE *fp = NULL; + FILE *fp_write = NULL; + int pfile2_lockdepth = 0; + + pfile2 = talloc_asprintf(talloc_tos(), + "%s.%u", + pfile, (unsigned)sys_getpid()); + if (!pfile2) { + return false; + } + + /* + * Open the smbpassword file - for update. It needs to be update + * as we need any other processes to wait until we have replaced + * it. + */ + + if((fp = startsmbfilepwent(pfile, PWF_UPDATE, &smbpasswd_state->pw_file_lock_depth)) == NULL) { + DEBUG(0, ("del_smbfilepwd_entry: unable to open file %s.\n", pfile)); + return False; + } + + /* + * Create the replacement password file. + */ + if((fp_write = startsmbfilepwent(pfile2, PWF_CREATE, &pfile2_lockdepth)) == NULL) { + DEBUG(0, ("del_smbfilepwd_entry: unable to open file %s.\n", pfile)); + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + return False; + } + + /* + * Scan the file, a line at a time and check if the name matches. + */ + + while ((pwd = getsmbfilepwent(smbpasswd_state, fp)) != NULL) { + char *new_entry; + size_t new_entry_length; + + if (strequal(name, pwd->smb_name)) { + DEBUG(10, ("del_smbfilepwd_entry: found entry with " + "name %s - deleting it.\n", name)); + continue; + } + + /* + * We need to copy the entry out into the second file. + */ + + if((new_entry = format_new_smbpasswd_entry(pwd)) == NULL) { + DEBUG(0, ("del_smbfilepwd_entry(malloc): Failed to copy entry for user %s to file %s. \ +Error was %s\n", pwd->smb_name, pfile2, strerror(errno))); + unlink(pfile2); + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + endsmbfilepwent(fp_write, &pfile2_lockdepth); + return False; + } + + new_entry_length = strlen(new_entry); + + if(fwrite(new_entry, 1, new_entry_length, fp_write) != new_entry_length) { + DEBUG(0, ("del_smbfilepwd_entry(write): Failed to copy entry for user %s to file %s. \ +Error was %s\n", pwd->smb_name, pfile2, strerror(errno))); + unlink(pfile2); + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + endsmbfilepwent(fp_write, &pfile2_lockdepth); + free(new_entry); + return False; + } + + free(new_entry); + } + + /* + * Ensure pfile2 is flushed before rename. + */ + + if(fflush(fp_write) != 0) { + DEBUG(0, ("del_smbfilepwd_entry: Failed to flush file %s. Error was %s\n", pfile2, strerror(errno))); + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + endsmbfilepwent(fp_write,&pfile2_lockdepth); + return False; + } + + /* + * Do an atomic rename - then release the locks. + */ + + if(rename(pfile2,pfile) != 0) { + unlink(pfile2); + } + + endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth); + endsmbfilepwent(fp_write,&pfile2_lockdepth); + return True; +} + +/********************************************************************* + Create a smb_passwd struct from a struct samu. + We will not allocate any new memory. The smb_passwd struct + should only stay around as long as the struct samu does. + ********************************************************************/ + +static bool build_smb_pass (struct smb_passwd *smb_pw, const struct samu *sampass) +{ + uint32 rid; + + if (sampass == NULL) + return False; + ZERO_STRUCTP(smb_pw); + + if (!IS_SAM_DEFAULT(sampass, PDB_USERSID)) { + rid = pdb_get_user_rid(sampass); + + /* If the user specified a RID, make sure its able to be both stored and retreived */ + if (rid == DOMAIN_USER_RID_GUEST) { + struct passwd *passwd = getpwnam_alloc(NULL, lp_guestaccount()); + if (!passwd) { + DEBUG(0, ("Could not find guest account via getpwnam()! (%s)\n", lp_guestaccount())); + return False; + } + smb_pw->smb_userid=passwd->pw_uid; + TALLOC_FREE(passwd); + } else if (algorithmic_pdb_rid_is_user(rid)) { + smb_pw->smb_userid=algorithmic_pdb_user_rid_to_uid(rid); + } else { + DEBUG(0,("build_sam_pass: Failing attempt to store user with non-uid based user RID. \n")); + return False; + } + } + + smb_pw->smb_name=(const char*)pdb_get_username(sampass); + + smb_pw->smb_passwd=pdb_get_lanman_passwd(sampass); + smb_pw->smb_nt_passwd=pdb_get_nt_passwd(sampass); + + smb_pw->acct_ctrl=pdb_get_acct_ctrl(sampass); + smb_pw->pass_last_set_time=pdb_get_pass_last_set_time(sampass); + + return True; +} + +/********************************************************************* + Create a struct samu from a smb_passwd struct + ********************************************************************/ + +static bool build_sam_account(struct smbpasswd_privates *smbpasswd_state, + struct samu *sam_pass, const struct smb_passwd *pw_buf) +{ + struct passwd *pwfile; + + if ( !sam_pass ) { + DEBUG(5,("build_sam_account: struct samu is NULL\n")); + return False; + } + + /* verify the user account exists */ + + if ( !(pwfile = Get_Pwnam_alloc(NULL, pw_buf->smb_name )) ) { + DEBUG(0,("build_sam_account: smbpasswd database is corrupt! username %s with uid " + "%u is not in unix passwd database!\n", pw_buf->smb_name, pw_buf->smb_userid)); + return False; + } + + if ( !NT_STATUS_IS_OK( samu_set_unix(sam_pass, pwfile )) ) + return False; + + TALLOC_FREE(pwfile); + + /* set remaining fields */ + + if (!pdb_set_nt_passwd (sam_pass, pw_buf->smb_nt_passwd, PDB_SET)) + return False; + if (!pdb_set_lanman_passwd (sam_pass, pw_buf->smb_passwd, PDB_SET)) + return False; + pdb_set_acct_ctrl (sam_pass, pw_buf->acct_ctrl, PDB_SET); + pdb_set_pass_last_set_time (sam_pass, pw_buf->pass_last_set_time, PDB_SET); + pdb_set_pass_can_change_time (sam_pass, pw_buf->pass_last_set_time, PDB_SET); + + return True; +} + +/***************************************************************** + Functions to be implemented by the new passdb API + ****************************************************************/ + +/**************************************************************** + Search smbpasswd file by iterating over the entries. Do not + call getpwnam() for unix account information until we have found + the correct entry + ***************************************************************/ + +static NTSTATUS smbpasswd_getsampwnam(struct pdb_methods *my_methods, + struct samu *sam_acct, const char *username) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data; + struct smb_passwd *smb_pw; + FILE *fp = NULL; + + DEBUG(10, ("getsampwnam (smbpasswd): search by name: %s\n", username)); + + /* startsmbfilepwent() is used here as we don't want to lookup + the UNIX account in the local system password file until + we have a match. */ + fp = startsmbfilepwent(smbpasswd_state->smbpasswd_file, PWF_READ, &(smbpasswd_state->pw_file_lock_depth)); + + if (fp == NULL) { + DEBUG(0, ("Unable to open passdb database.\n")); + return nt_status; + } + + while ( ((smb_pw=getsmbfilepwent(smbpasswd_state, fp)) != NULL)&& (!strequal(smb_pw->smb_name, username)) ) + /* do nothing....another loop */ ; + + endsmbfilepwent(fp, &(smbpasswd_state->pw_file_lock_depth)); + + + /* did we locate the username in smbpasswd */ + if (smb_pw == NULL) + return nt_status; + + DEBUG(10, ("getsampwnam (smbpasswd): found by name: %s\n", smb_pw->smb_name)); + + if (!sam_acct) { + DEBUG(10,("getsampwnam (smbpasswd): struct samu is NULL\n")); + return nt_status; + } + + /* now build the struct samu */ + if (!build_sam_account(smbpasswd_state, sam_acct, smb_pw)) + return nt_status; + + /* success */ + return NT_STATUS_OK; +} + +static NTSTATUS smbpasswd_getsampwsid(struct pdb_methods *my_methods, struct samu *sam_acct, const DOM_SID *sid) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data; + struct smb_passwd *smb_pw; + FILE *fp = NULL; + uint32 rid; + + DEBUG(10, ("smbpasswd_getsampwrid: search by sid: %s\n", + sid_string_dbg(sid))); + + if (!sid_peek_check_rid(get_global_sam_sid(), sid, &rid)) + return NT_STATUS_UNSUCCESSFUL; + + /* More special case 'guest account' hacks... */ + if (rid == DOMAIN_USER_RID_GUEST) { + const char *guest_account = lp_guestaccount(); + if (!(guest_account && *guest_account)) { + DEBUG(1, ("Guest account not specfied!\n")); + return nt_status; + } + return smbpasswd_getsampwnam(my_methods, sam_acct, guest_account); + } + + /* Open the sam password file - not for update. */ + fp = startsmbfilepwent(smbpasswd_state->smbpasswd_file, PWF_READ, &(smbpasswd_state->pw_file_lock_depth)); + + if (fp == NULL) { + DEBUG(0, ("Unable to open passdb database.\n")); + return nt_status; + } + + while ( ((smb_pw=getsmbfilepwent(smbpasswd_state, fp)) != NULL) && (algorithmic_pdb_uid_to_user_rid(smb_pw->smb_userid) != rid) ) + /* do nothing */ ; + + endsmbfilepwent(fp, &(smbpasswd_state->pw_file_lock_depth)); + + + /* did we locate the username in smbpasswd */ + if (smb_pw == NULL) + return nt_status; + + DEBUG(10, ("getsampwrid (smbpasswd): found by name: %s\n", smb_pw->smb_name)); + + if (!sam_acct) { + DEBUG(10,("getsampwrid: (smbpasswd) struct samu is NULL\n")); + return nt_status; + } + + /* now build the struct samu */ + if (!build_sam_account (smbpasswd_state, sam_acct, smb_pw)) + return nt_status; + + /* build_sam_account might change the SID on us, if the name was for the guest account */ + if (NT_STATUS_IS_OK(nt_status) && !sid_equal(pdb_get_user_sid(sam_acct), sid)) { + DEBUG(1, ("looking for user with sid %s instead returned %s " + "for account %s!?!\n", sid_string_dbg(sid), + sid_string_dbg(pdb_get_user_sid(sam_acct)), + pdb_get_username(sam_acct))); + return NT_STATUS_NO_SUCH_USER; + } + + /* success */ + return NT_STATUS_OK; +} + +static NTSTATUS smbpasswd_add_sam_account(struct pdb_methods *my_methods, struct samu *sampass) +{ + struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data; + struct smb_passwd smb_pw; + + /* convert the struct samu */ + if (!build_smb_pass(&smb_pw, sampass)) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* add the entry */ + return add_smbfilepwd_entry(smbpasswd_state, &smb_pw); +} + +static NTSTATUS smbpasswd_update_sam_account(struct pdb_methods *my_methods, struct samu *sampass) +{ + struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data; + struct smb_passwd smb_pw; + + /* convert the struct samu */ + if (!build_smb_pass(&smb_pw, sampass)) { + DEBUG(0, ("smbpasswd_update_sam_account: build_smb_pass failed!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + /* update the entry */ + if(!mod_smbfilepwd_entry(smbpasswd_state, &smb_pw)) { + DEBUG(0, ("smbpasswd_update_sam_account: mod_smbfilepwd_entry failed!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static NTSTATUS smbpasswd_delete_sam_account (struct pdb_methods *my_methods, struct samu *sampass) +{ + struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data; + + const char *username = pdb_get_username(sampass); + + if (del_smbfilepwd_entry(smbpasswd_state, username)) + return NT_STATUS_OK; + + return NT_STATUS_UNSUCCESSFUL; +} + +static NTSTATUS smbpasswd_rename_sam_account (struct pdb_methods *my_methods, + struct samu *old_acct, + const char *newname) +{ + char *rename_script = NULL; + struct samu *new_acct = NULL; + bool interim_account = False; + TALLOC_CTX *ctx = talloc_tos(); + NTSTATUS ret = NT_STATUS_UNSUCCESSFUL; + + if (!*(lp_renameuser_script())) + goto done; + + if ( !(new_acct = samu_new( NULL )) ) { + return NT_STATUS_NO_MEMORY; + } + + if ( !pdb_copy_sam_account( new_acct, old_acct ) + || !pdb_set_username(new_acct, newname, PDB_CHANGED)) + { + goto done; + } + + ret = smbpasswd_add_sam_account(my_methods, new_acct); + if (!NT_STATUS_IS_OK(ret)) + goto done; + + interim_account = True; + + /* rename the posix user */ + rename_script = talloc_strdup(ctx, + lp_renameuser_script()); + if (!rename_script) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + if (*rename_script) { + int rename_ret; + + rename_script = talloc_string_sub2(ctx, + rename_script, + "%unew", + newname, + true, + false, + true); + if (!rename_script) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + rename_script = talloc_string_sub2(ctx, + rename_script, + "%uold", + pdb_get_username(old_acct), + true, + false, + true); + if (!rename_script) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + rename_ret = smbrun(rename_script, NULL); + + DEBUG(rename_ret ? 0 : 3,("Running the command `%s' gave %d\n", rename_script, rename_ret)); + + if (rename_ret == 0) { + smb_nscd_flush_user_cache(); + } + + if (rename_ret) + goto done; + } else { + goto done; + } + + smbpasswd_delete_sam_account(my_methods, old_acct); + interim_account = False; + +done: + /* cleanup */ + if (interim_account) + smbpasswd_delete_sam_account(my_methods, new_acct); + + if (new_acct) + TALLOC_FREE(new_acct); + + return (ret); +} + +static bool smbpasswd_rid_algorithm(struct pdb_methods *methods) +{ + return True; +} + +static void free_private_data(void **vp) +{ + struct smbpasswd_privates **privates = (struct smbpasswd_privates**)vp; + + endsmbfilepwent((*privates)->pw_file, &((*privates)->pw_file_lock_depth)); + + *privates = NULL; + /* No need to free any further, as it is talloc()ed */ +} + +struct smbpasswd_search_state { + uint32_t acct_flags; + + struct samr_displayentry *entries; + uint32_t num_entries; + ssize_t array_size; + uint32_t current; +}; + +static void smbpasswd_search_end(struct pdb_search *search) +{ + struct smbpasswd_search_state *state = talloc_get_type_abort( + search->private_data, struct smbpasswd_search_state); + TALLOC_FREE(state); +} + +static bool smbpasswd_search_next_entry(struct pdb_search *search, + struct samr_displayentry *entry) +{ + struct smbpasswd_search_state *state = talloc_get_type_abort( + search->private_data, struct smbpasswd_search_state); + + if (state->current == state->num_entries) { + return false; + } + + entry->idx = state->entries[state->current].idx; + entry->rid = state->entries[state->current].rid; + entry->acct_flags = state->entries[state->current].acct_flags; + + entry->account_name = talloc_strdup( + search->mem_ctx, state->entries[state->current].account_name); + entry->fullname = talloc_strdup( + search->mem_ctx, state->entries[state->current].fullname); + entry->description = talloc_strdup( + search->mem_ctx, state->entries[state->current].description); + + if ((entry->account_name == NULL) || (entry->fullname == NULL) + || (entry->description == NULL)) { + DEBUG(0, ("talloc_strdup failed\n")); + return false; + } + + state->current += 1; + return true; +} + +static bool smbpasswd_search_users(struct pdb_methods *methods, + struct pdb_search *search, + uint32_t acct_flags) +{ + struct smbpasswd_privates *smbpasswd_state = + (struct smbpasswd_privates*)methods->private_data; + + struct smbpasswd_search_state *search_state; + struct smb_passwd *pwd; + FILE *fp; + + search_state = TALLOC_ZERO_P(search->mem_ctx, + struct smbpasswd_search_state); + if (search_state == NULL) { + DEBUG(0, ("talloc failed\n")); + return false; + } + search_state->acct_flags = acct_flags; + + fp = startsmbfilepwent(smbpasswd_state->smbpasswd_file, PWF_READ, + &smbpasswd_state->pw_file_lock_depth); + + if (fp == NULL) { + DEBUG(10, ("Unable to open smbpasswd file.\n")); + TALLOC_FREE(search_state); + return false; + } + + while ((pwd = getsmbfilepwent(smbpasswd_state, fp)) != NULL) { + struct samr_displayentry entry; + struct samu *user; + + if ((acct_flags != 0) + && ((acct_flags & pwd->acct_ctrl) == 0)) { + continue; + } + + user = samu_new(talloc_tos()); + if (user == NULL) { + DEBUG(0, ("samu_new failed\n")); + break; + } + + if (!build_sam_account(smbpasswd_state, user, pwd)) { + /* Already got debug msgs... */ + break; + } + + ZERO_STRUCT(entry); + + entry.acct_flags = pdb_get_acct_ctrl(user); + sid_peek_rid(pdb_get_user_sid(user), &entry.rid); + entry.account_name = talloc_strdup( + search_state, pdb_get_username(user)); + entry.fullname = talloc_strdup( + search_state, pdb_get_fullname(user)); + entry.description = talloc_strdup( + search_state, pdb_get_acct_desc(user)); + + TALLOC_FREE(user); + + if ((entry.account_name == NULL) || (entry.fullname == NULL) + || (entry.description == NULL)) { + DEBUG(0, ("talloc_strdup failed\n")); + break; + } + + ADD_TO_LARGE_ARRAY(search_state, struct samr_displayentry, + entry, &search_state->entries, + &search_state->num_entries, + &search_state->array_size); + } + + endsmbfilepwent(fp, &(smbpasswd_state->pw_file_lock_depth)); + + search->private_data = search_state; + search->next_entry = smbpasswd_search_next_entry; + search->search_end = smbpasswd_search_end; + + return true; +} + +static NTSTATUS pdb_init_smbpasswd( struct pdb_methods **pdb_method, const char *location ) +{ + NTSTATUS nt_status; + struct smbpasswd_privates *privates; + + if ( !NT_STATUS_IS_OK(nt_status = make_pdb_method( pdb_method )) ) { + return nt_status; + } + + (*pdb_method)->name = "smbpasswd"; + + (*pdb_method)->getsampwnam = smbpasswd_getsampwnam; + (*pdb_method)->getsampwsid = smbpasswd_getsampwsid; + (*pdb_method)->add_sam_account = smbpasswd_add_sam_account; + (*pdb_method)->update_sam_account = smbpasswd_update_sam_account; + (*pdb_method)->delete_sam_account = smbpasswd_delete_sam_account; + (*pdb_method)->rename_sam_account = smbpasswd_rename_sam_account; + (*pdb_method)->search_users = smbpasswd_search_users; + + (*pdb_method)->rid_algorithm = smbpasswd_rid_algorithm; + + /* Setup private data and free function */ + + if ( !(privates = TALLOC_ZERO_P( *pdb_method, struct smbpasswd_privates )) ) { + DEBUG(0, ("talloc() failed for smbpasswd private_data!\n")); + return NT_STATUS_NO_MEMORY; + } + + /* Store some config details */ + + if (location) { + privates->smbpasswd_file = talloc_strdup(*pdb_method, location); + } else { + privates->smbpasswd_file = talloc_strdup(*pdb_method, lp_smb_passwd_file()); + } + + if (!privates->smbpasswd_file) { + DEBUG(0, ("talloc_strdp() failed for storing smbpasswd location!\n")); + return NT_STATUS_NO_MEMORY; + } + + (*pdb_method)->private_data = privates; + + (*pdb_method)->free_private_data = free_private_data; + + return NT_STATUS_OK; +} + +NTSTATUS pdb_smbpasswd_init(void) +{ + return smb_register_passdb(PASSDB_INTERFACE_VERSION, "smbpasswd", pdb_init_smbpasswd); +} diff --git a/source3/passdb/pdb_tdb.c b/source3/passdb/pdb_tdb.c new file mode 100644 index 0000000000..e40f4bbab8 --- /dev/null +++ b/source3/passdb/pdb_tdb.c @@ -0,0 +1,1639 @@ +/* + * Unix SMB/CIFS implementation. + * SMB parameters and setup + * Copyright (C) Andrew Tridgell 1992-1998 + * Copyright (C) Simo Sorce 2000-2003 + * Copyright (C) Gerald Carter 2000-2006 + * Copyright (C) Jeremy Allison 2001 + * Copyright (C) Andrew Bartlett 2002 + * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2005 + * + * 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" + +#if 0 /* when made a module use this */ + +static int tdbsam_debug_level = DBGC_ALL; +#undef DBGC_CLASS +#define DBGC_CLASS tdbsam_debug_level + +#else + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +#endif + +#define TDBSAM_VERSION 3 /* Most recent TDBSAM version */ +#define TDBSAM_VERSION_STRING "INFO/version" +#define PASSDB_FILE_NAME "passdb.tdb" +#define USERPREFIX "USER_" +#define USERPREFIX_LEN 5 +#define RIDPREFIX "RID_" +#define PRIVPREFIX "PRIV_" + +/* GLOBAL TDB SAM CONTEXT */ + +static struct db_context *db_sam; +static char *tdbsam_filename; + +/********************************************************************** + Marshall/unmarshall struct samu structs. + *********************************************************************/ + +#define TDB_FORMAT_STRING_V0 "ddddddBBBBBBBBBBBBddBBwdwdBwwd" +#define TDB_FORMAT_STRING_V1 "dddddddBBBBBBBBBBBBddBBwdwdBwwd" +#define TDB_FORMAT_STRING_V2 "dddddddBBBBBBBBBBBBddBBBwwdBwwd" + +/********************************************************************* +*********************************************************************/ + +static bool init_sam_from_buffer_v0(struct samu *sampass, uint8 *buf, uint32 buflen) +{ + + /* times are stored as 32bit integer + take care on system with 64bit wide time_t + --SSS */ + uint32 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 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 user_rid, group_rid, remove_me, hours_len, unknown_6; + uint16 acct_ctrl, logon_divs; + uint16 bad_password_count, logon_count; + uint8 *hours = NULL; + uint8 *lm_pw_ptr = NULL, *nt_pw_ptr = NULL; + uint32 len = 0; + uint32 lm_pw_len, nt_pw_len, hourslen; + bool ret = True; + + if(sampass == NULL || buf == NULL) { + DEBUG(0, ("init_sam_from_buffer_v0: NULL parameters found!\n")); + return False; + } + +/* TDB_FORMAT_STRING_V0 "ddddddBBBBBBBBBBBBddBBwdwdBwwd" */ + + /* unpack the buffer into variables */ + len = tdb_unpack (buf, buflen, TDB_FORMAT_STRING_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) -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_must_change_time(sampass, pass_must_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, 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_sam_from_buffer_v1(struct samu *sampass, uint8 *buf, uint32 buflen) +{ + + /* times are stored as 32bit integer + take care on system with 64bit wide time_t + --SSS */ + uint32 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 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 user_rid, group_rid, remove_me, hours_len, unknown_6; + uint16 acct_ctrl, logon_divs; + uint16 bad_password_count, logon_count; + uint8 *hours = NULL; + uint8 *lm_pw_ptr = NULL, *nt_pw_ptr = NULL; + uint32 len = 0; + uint32 lm_pw_len, nt_pw_len, hourslen; + bool ret = True; + + if(sampass == NULL || buf == NULL) { + DEBUG(0, ("init_sam_from_buffer_v1: NULL parameters found!\n")); + return False; + } + +/* TDB_FORMAT_STRING_V1 "dddddddBBBBBBBBBBBBddBBwdwdBwwd" */ + + /* unpack the buffer into variables */ + len = tdb_unpack (buf, buflen, TDB_FORMAT_STRING_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) -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_must_change_time(sampass, pass_must_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, 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; +} + +bool init_sam_from_buffer_v2(struct samu *sampass, uint8 *buf, uint32 buflen) +{ + + /* times are stored as 32bit integer + take care on system with 64bit wide time_t + --SSS */ + uint32 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 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 user_rid, group_rid, hours_len, unknown_6; + uint16 acct_ctrl, logon_divs; + uint16 bad_password_count, logon_count; + uint8 *hours = NULL; + uint8 *lm_pw_ptr = NULL, *nt_pw_ptr = NULL, *nt_pw_hist_ptr = NULL; + uint32 len = 0; + uint32 lm_pw_len, nt_pw_len, nt_pw_hist_len, hourslen; + uint32 pwHistLen = 0; + bool ret = True; + fstring tmp_string; + bool expand_explicit = lp_passdb_expand_explicit(); + + if(sampass == NULL || buf == NULL) { + DEBUG(0, ("init_sam_from_buffer_v2: NULL parameters found!\n")); + return False; + } + +/* TDB_FORMAT_STRING_V2 "dddddddBBBBBBBBBBBBddBBBwwdBwwd" */ + + /* unpack the buffer into variables */ + len = tdb_unpack (buf, buflen, TDB_FORMAT_STRING_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) -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_must_change_time(sampass, pass_must_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(AP_PASSWORD_HISTORY, &pwHistLen); + if (pwHistLen) { + uint8 *pw_hist = SMB_MALLOC_ARRAY(uint8, 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, 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; +} + + +/********************************************************************** + Intialize a struct samu struct from a BYTE buffer of size len + *********************************************************************/ + +static bool init_sam_from_buffer(struct samu *sampass, uint8 *buf, uint32 buflen) +{ + return init_sam_from_buffer_v3(sampass, buf, buflen); +} + +/********************************************************************** + Intialize a BYTE buffer from a struct samu struct + *********************************************************************/ + +static uint32 init_buffer_from_sam (uint8 **buf, struct samu *sampass, bool size_only) +{ + return init_buffer_from_sam_v3(buf, sampass, size_only); +} + +/********************************************************************** + Intialize a BYTE buffer from a struct samu struct + *********************************************************************/ + +struct tdbsam_convert_state { + int32_t from; + bool success; +}; + +static int tdbsam_convert_one(struct db_record *rec, void *priv) +{ + struct tdbsam_convert_state *state = + (struct tdbsam_convert_state *)priv; + struct samu *user; + TDB_DATA data; + NTSTATUS status; + bool ret; + + if (rec->key.dsize < USERPREFIX_LEN) { + return 0; + } + if (strncmp((char *)rec->key.dptr, USERPREFIX, USERPREFIX_LEN) != 0) { + return 0; + } + + user = samu_new(talloc_tos()); + if (user == NULL) { + DEBUG(0,("tdbsam_convert: samu_new() failed!\n")); + state->success = false; + return -1; + } + + DEBUG(10,("tdbsam_convert: Try unpacking a record with (key:%s) " + "(version:%d)\n", rec->key.dptr, state->from)); + + switch (state->from) { + case 0: + ret = init_sam_from_buffer_v0(user, (uint8 *)rec->value.dptr, + rec->value.dsize); + break; + case 1: + ret = init_sam_from_buffer_v1(user, (uint8 *)rec->value.dptr, + rec->value.dsize); + break; + case 2: + ret = init_sam_from_buffer_v2(user, (uint8 *)rec->value.dptr, + rec->value.dsize); + break; + case 3: + ret = init_sam_from_buffer_v3(user, (uint8 *)rec->value.dptr, + rec->value.dsize); + break; + default: + /* unknown tdbsam version */ + ret = False; + } + if (!ret) { + DEBUG(0,("tdbsam_convert: Bad struct samu entry returned " + "from TDB (key:%s) (version:%d)\n", rec->key.dptr, + state->from)); + TALLOC_FREE(user); + state->success = false; + return -1; + } + + data.dsize = init_buffer_from_sam(&data.dptr, user, false); + TALLOC_FREE(user); + + if (data.dsize == -1) { + DEBUG(0,("tdbsam_convert: cannot pack the struct samu into " + "the new format\n")); + state->success = false; + return -1; + } + + status = rec->store(rec, data, TDB_MODIFY); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Could not store the new record: %s\n", + nt_errstr(status))); + state->success = false; + return -1; + } + + return 0; +} + +static bool tdbsam_convert(struct db_context *db, int32 from) +{ + struct tdbsam_convert_state state; + int ret; + + state.from = from; + state.success = true; + + if (db->transaction_start(db) != 0) { + DEBUG(0, ("Could not start transaction\n")); + return false; + } + + ret = db->traverse(db, tdbsam_convert_one, &state); + if (ret < 0) { + DEBUG(0, ("traverse failed\n")); + goto cancel; + } + + if (!state.success) { + DEBUG(0, ("Converting records failed\n")); + goto cancel; + } + + if (dbwrap_store_int32(db, TDBSAM_VERSION_STRING, + TDBSAM_VERSION) != 0) { + DEBUG(0, ("Could not store tdbsam version\n")); + goto cancel; + } + + if (db->transaction_commit(db) != 0) { + DEBUG(0, ("Could not commit transaction\n")); + return false; + } + + return true; + + cancel: + if (db->transaction_cancel(db) != 0) { + smb_panic("transaction_cancel failed"); + } + + return false; +} + +/********************************************************************* + Open the tdbsam file based on the absolute path specified. + Uses a reference count to allow multiple open calls. +*********************************************************************/ + +static bool tdbsam_open( const char *name ) +{ + int32 version; + + /* check if we are already open */ + + if ( db_sam ) { + return true; + } + + /* Try to open tdb passwd. Create a new one if necessary */ + + db_sam = db_open(NULL, name, 0, TDB_DEFAULT, O_CREAT|O_RDWR, 0600); + if (db_sam == NULL) { + DEBUG(0, ("tdbsam_open: Failed to open/create TDB passwd " + "[%s]\n", name)); + return false; + } + + /* Check the version */ + version = dbwrap_fetch_int32(db_sam, TDBSAM_VERSION_STRING); + if (version == -1) { + version = 0; /* Version not found, assume version 0 */ + } + + /* Compare the version */ + if (version > TDBSAM_VERSION) { + /* Version more recent than the latest known */ + DEBUG(0, ("tdbsam_open: unknown version => %d\n", version)); + TALLOC_FREE(db_sam); + return false; + } + + if ( version < TDBSAM_VERSION ) { + DEBUG(1, ("tdbsam_open: Converting version %d database to " + "version %d.\n", version, TDBSAM_VERSION)); + + if ( !tdbsam_convert(db_sam, version) ) { + DEBUG(0, ("tdbsam_open: Error when trying to convert " + "tdbsam [%s]\n",name)); + TALLOC_FREE(db_sam); + return false; + } + + DEBUG(3, ("TDBSAM converted successfully.\n")); + } + + DEBUG(4,("tdbsam_open: successfully opened %s\n", name )); + + return true; +} + +/****************************************************************** + Lookup a name in the SAM TDB +******************************************************************/ + +static NTSTATUS tdbsam_getsampwnam (struct pdb_methods *my_methods, + struct samu *user, const char *sname) +{ + TDB_DATA data; + fstring keystr; + fstring name; + + if ( !user ) { + DEBUG(0,("pdb_getsampwnam: struct samu is NULL.\n")); + return NT_STATUS_NO_MEMORY; + } + + /* Data is stored in all lower-case */ + fstrcpy(name, sname); + strlower_m(name); + + /* set search key */ + slprintf(keystr, sizeof(keystr)-1, "%s%s", USERPREFIX, name); + + /* open the database */ + + if ( !tdbsam_open( tdbsam_filename ) ) { + DEBUG(0,("tdbsam_getsampwnam: failed to open %s!\n", tdbsam_filename)); + return NT_STATUS_ACCESS_DENIED; + } + + /* get the record */ + + data = dbwrap_fetch_bystring(db_sam, talloc_tos(), keystr); + if (!data.dptr) { + DEBUG(5,("pdb_getsampwnam (TDB): error fetching database.\n")); + DEBUGADD(5, (" Key: %s\n", keystr)); + return NT_STATUS_NO_SUCH_USER; + } + + /* unpack the buffer */ + + if (!init_sam_from_buffer(user, data.dptr, data.dsize)) { + DEBUG(0,("pdb_getsampwent: Bad struct samu entry returned from TDB!\n")); + SAFE_FREE(data.dptr); + return NT_STATUS_NO_MEMORY; + } + + /* success */ + + TALLOC_FREE(data.dptr); + + return NT_STATUS_OK; +} + +/*************************************************************************** + Search by rid + **************************************************************************/ + +static NTSTATUS tdbsam_getsampwrid (struct pdb_methods *my_methods, + struct samu *user, uint32 rid) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + TDB_DATA data; + fstring keystr; + fstring name; + + if ( !user ) { + DEBUG(0,("pdb_getsampwrid: struct samu is NULL.\n")); + return nt_status; + } + + /* set search key */ + + slprintf(keystr, sizeof(keystr)-1, "%s%.8x", RIDPREFIX, rid); + + /* open the database */ + + if ( !tdbsam_open( tdbsam_filename ) ) { + DEBUG(0,("tdbsam_getsampwnam: failed to open %s!\n", tdbsam_filename)); + return NT_STATUS_ACCESS_DENIED; + } + + /* get the record */ + + data = dbwrap_fetch_bystring(db_sam, talloc_tos(), keystr); + if (!data.dptr) { + DEBUG(5,("pdb_getsampwrid (TDB): error looking up RID %d by key %s.\n", rid, keystr)); + return NT_STATUS_UNSUCCESSFUL; + } + + fstrcpy(name, (const char *)data.dptr); + TALLOC_FREE(data.dptr); + + return tdbsam_getsampwnam (my_methods, user, name); +} + +static NTSTATUS tdbsam_getsampwsid(struct pdb_methods *my_methods, + struct samu * user, const DOM_SID *sid) +{ + uint32 rid; + + if ( !sid_peek_check_rid(get_global_sam_sid(), sid, &rid) ) + return NT_STATUS_UNSUCCESSFUL; + + return tdbsam_getsampwrid(my_methods, user, rid); +} + +static bool tdb_delete_samacct_only( struct samu *sam_pass ) +{ + fstring keystr; + fstring name; + NTSTATUS status; + + fstrcpy(name, pdb_get_username(sam_pass)); + strlower_m(name); + + /* set the search key */ + + slprintf(keystr, sizeof(keystr)-1, "%s%s", USERPREFIX, name); + + /* it's outaa here! 8^) */ + + status = dbwrap_delete_bystring(db_sam, keystr); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("Error deleting entry from tdb passwd " + "database: %s!\n", nt_errstr(status))); + return false; + } + + return true; +} + +/*************************************************************************** + Delete a struct samu records for the username and RID key +****************************************************************************/ + +static NTSTATUS tdbsam_delete_sam_account(struct pdb_methods *my_methods, + struct samu *sam_pass) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + fstring keystr; + uint32 rid; + fstring name; + + /* open the database */ + + if ( !tdbsam_open( tdbsam_filename ) ) { + DEBUG(0,("tdbsam_delete_sam_account: failed to open %s!\n", + tdbsam_filename)); + return NT_STATUS_ACCESS_DENIED; + } + + fstrcpy(name, pdb_get_username(sam_pass)); + strlower_m(name); + + /* set the search key */ + + slprintf(keystr, sizeof(keystr)-1, "%s%s", USERPREFIX, name); + + rid = pdb_get_user_rid(sam_pass); + + /* it's outaa here! 8^) */ + + if (db_sam->transaction_start(db_sam) != 0) { + DEBUG(0, ("Could not start transaction\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + nt_status = dbwrap_delete_bystring(db_sam, keystr); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(5, ("Error deleting entry from tdb passwd " + "database: %s!\n", nt_errstr(nt_status))); + goto cancel; + } + + /* set the search key */ + + slprintf(keystr, sizeof(keystr)-1, "%s%.8x", RIDPREFIX, rid); + + /* it's outaa here! 8^) */ + + nt_status = dbwrap_delete_bystring(db_sam, keystr); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(5, ("Error deleting entry from tdb rid " + "database: %s!\n", nt_errstr(nt_status))); + goto cancel; + } + + if (db_sam->transaction_commit(db_sam) != 0) { + DEBUG(0, ("Could not commit transaction\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return NT_STATUS_OK; + + cancel: + if (db_sam->transaction_cancel(db_sam) != 0) { + smb_panic("transaction_cancel failed"); + } + + return nt_status; +} + + +/*************************************************************************** + Update the TDB SAM account record only + Assumes that the tdbsam is already open +****************************************************************************/ +static bool tdb_update_samacct_only( struct samu* newpwd, int flag ) +{ + TDB_DATA data; + uint8 *buf = NULL; + fstring keystr; + fstring name; + bool ret = false; + NTSTATUS status; + + /* copy the struct samu struct into a BYTE buffer for storage */ + + if ( (data.dsize=init_buffer_from_sam (&buf, newpwd, False)) == -1 ) { + DEBUG(0,("tdb_update_sam: ERROR - Unable to copy struct samu info BYTE buffer!\n")); + goto done; + } + data.dptr = buf; + + fstrcpy(name, pdb_get_username(newpwd)); + strlower_m(name); + + DEBUG(5, ("Storing %saccount %s with RID %d\n", + flag == TDB_INSERT ? "(new) " : "", name, + pdb_get_user_rid(newpwd))); + + /* setup the USER index key */ + slprintf(keystr, sizeof(keystr)-1, "%s%s", USERPREFIX, name); + + /* add the account */ + + status = dbwrap_store_bystring(db_sam, keystr, data, flag); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Unable to modify passwd TDB: %s!", + nt_errstr(status))); + goto done; + } + + ret = true; + +done: + /* cleanup */ + SAFE_FREE(buf); + return ret; +} + +/*************************************************************************** + Update the TDB SAM RID record only + Assumes that the tdbsam is already open +****************************************************************************/ +static bool tdb_update_ridrec_only( struct samu* newpwd, int flag ) +{ + TDB_DATA data; + fstring keystr; + fstring name; + NTSTATUS status; + + fstrcpy(name, pdb_get_username(newpwd)); + strlower_m(name); + + /* setup RID data */ + data = string_term_tdb_data(name); + + /* setup the RID index key */ + slprintf(keystr, sizeof(keystr)-1, "%s%.8x", RIDPREFIX, + pdb_get_user_rid(newpwd)); + + /* add the reference */ + status = dbwrap_store_bystring(db_sam, keystr, data, flag); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Unable to modify TDB passwd: %s!\n", + nt_errstr(status))); + return false; + } + + return true; + +} + +/*************************************************************************** + Update the TDB SAM +****************************************************************************/ + +static bool tdb_update_sam(struct pdb_methods *my_methods, struct samu* newpwd, + int flag) +{ + if (!pdb_get_user_rid(newpwd)) { + DEBUG(0,("tdb_update_sam: struct samu (%s) with no RID!\n", + pdb_get_username(newpwd))); + return False; + } + + /* open the database */ + + if ( !tdbsam_open( tdbsam_filename ) ) { + DEBUG(0,("tdbsam_getsampwnam: failed to open %s!\n", tdbsam_filename)); + return False; + } + + if (db_sam->transaction_start(db_sam) != 0) { + DEBUG(0, ("Could not start transaction\n")); + return false; + } + + if (!tdb_update_samacct_only(newpwd, flag) + || !tdb_update_ridrec_only(newpwd, flag)) { + goto cancel; + } + + if (db_sam->transaction_commit(db_sam) != 0) { + DEBUG(0, ("Could not commit transaction\n")); + return false; + } + + return true; + + cancel: + if (db_sam->transaction_cancel(db_sam) != 0) { + smb_panic("transaction_cancel failed"); + } + return false; +} + +/*************************************************************************** + Modifies an existing struct samu +****************************************************************************/ + +static NTSTATUS tdbsam_update_sam_account (struct pdb_methods *my_methods, struct samu *newpwd) +{ + if ( !tdb_update_sam(my_methods, newpwd, TDB_MODIFY) ) + return NT_STATUS_UNSUCCESSFUL; + + return NT_STATUS_OK; +} + +/*************************************************************************** + Adds an existing struct samu +****************************************************************************/ + +static NTSTATUS tdbsam_add_sam_account (struct pdb_methods *my_methods, struct samu *newpwd) +{ + if ( !tdb_update_sam(my_methods, newpwd, TDB_INSERT) ) + return NT_STATUS_UNSUCCESSFUL; + + return NT_STATUS_OK; +} + +/*************************************************************************** + Renames a struct samu + - check for the posix user/rename user script + - Add and lock the new user record + - rename the posix user + - rewrite the rid->username record + - delete the old user + - unlock the new user record +***************************************************************************/ +static NTSTATUS tdbsam_rename_sam_account(struct pdb_methods *my_methods, + struct samu *old_acct, + const char *newname) +{ + struct samu *new_acct = NULL; + char *rename_script = NULL; + int rename_ret; + fstring oldname_lower; + fstring newname_lower; + + /* can't do anything without an external script */ + + if ( !(new_acct = samu_new( talloc_tos() )) ) { + return NT_STATUS_NO_MEMORY; + } + + rename_script = talloc_strdup(new_acct, lp_renameuser_script()); + if (!rename_script) { + TALLOC_FREE(new_acct); + return NT_STATUS_NO_MEMORY; + } + if (!*rename_script) { + TALLOC_FREE(new_acct); + return NT_STATUS_ACCESS_DENIED; + } + + if ( !pdb_copy_sam_account(new_acct, old_acct) + || !pdb_set_username(new_acct, newname, PDB_CHANGED)) + { + TALLOC_FREE(new_acct); + return NT_STATUS_NO_MEMORY; + } + + /* open the database */ + if ( !tdbsam_open( tdbsam_filename ) ) { + DEBUG(0, ("tdbsam_getsampwnam: failed to open %s!\n", + tdbsam_filename)); + TALLOC_FREE(new_acct); + return NT_STATUS_ACCESS_DENIED; + } + + if (db_sam->transaction_start(db_sam) != 0) { + DEBUG(0, ("Could not start transaction\n")); + TALLOC_FREE(new_acct); + return NT_STATUS_ACCESS_DENIED; + + } + + /* add the new account and lock it */ + if ( !tdb_update_samacct_only(new_acct, TDB_INSERT) ) { + goto cancel; + } + + /* Rename the posix user. Follow the semantics of _samr_create_user() + so that we lower case the posix name but preserve the case in passdb */ + + fstrcpy( oldname_lower, pdb_get_username(old_acct) ); + strlower_m( oldname_lower ); + + fstrcpy( newname_lower, newname ); + strlower_m( newname_lower ); + + rename_script = talloc_string_sub2(new_acct, + rename_script, + "%unew", + newname_lower, + true, + false, + true); + if (!rename_script) { + goto cancel; + } + rename_script = talloc_string_sub2(new_acct, + rename_script, + "%uold", + oldname_lower, + true, + false, + true); + if (!rename_script) { + goto cancel; + } + rename_ret = smbrun(rename_script, NULL); + + DEBUG(rename_ret ? 0 : 3,("Running the command `%s' gave %d\n", + rename_script, rename_ret)); + + if (rename_ret != 0) { + goto cancel; + } + + smb_nscd_flush_user_cache(); + + /* rewrite the rid->username record */ + + if ( !tdb_update_ridrec_only( new_acct, TDB_MODIFY) ) { + goto cancel; + } + + tdb_delete_samacct_only( old_acct ); + + if (db_sam->transaction_commit(db_sam) != 0) { + /* + * Ok, we're screwed. We've changed the posix account, but + * could not adapt passdb.tdb. Shall we change the posix + * account back? + */ + DEBUG(0, ("transaction_commit failed\n")); + TALLOC_FREE(new_acct); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + TALLOC_FREE(new_acct ); + return NT_STATUS_OK; + + cancel: + if (db_sam->transaction_cancel(db_sam) != 0) { + smb_panic("transaction_cancel failed"); + } + + TALLOC_FREE(new_acct); + + return NT_STATUS_ACCESS_DENIED; +} + +static bool tdbsam_rid_algorithm(struct pdb_methods *methods) +{ + return False; +} + +/* + * Historically, winbind was responsible for allocating RIDs, so the next RID + * value was stored in winbindd_idmap.tdb. It has been moved to passdb now, + * but for compatibility reasons we still keep the the next RID counter in + * winbindd_idmap.tdb. + */ + +/***************************************************************************** + Initialise idmap database. For now (Dec 2005) this is a copy of the code in + sam/idmap_tdb.c. Maybe at a later stage we can remove that capability from + winbind completely and store the RID counter in passdb.tdb. + + Dont' fully initialize with the HWM values, if it's new, we're only + interested in the RID counter. +*****************************************************************************/ + +static bool init_idmap_tdb(TDB_CONTEXT *tdb) +{ + int32 version; + + if (tdb_lock_bystring(tdb, "IDMAP_VERSION") != 0) { + DEBUG(0, ("Could not lock IDMAP_VERSION\n")); + return False; + } + + version = tdb_fetch_int32(tdb, "IDMAP_VERSION"); + + if (version == -1) { + /* No key found, must be a new db */ + if (tdb_store_int32(tdb, "IDMAP_VERSION", + IDMAP_VERSION) != 0) { + DEBUG(0, ("Could not store IDMAP_VERSION\n")); + tdb_unlock_bystring(tdb, "IDMAP_VERSION"); + return False; + } + version = IDMAP_VERSION; + } + + if (version != IDMAP_VERSION) { + DEBUG(0, ("Expected IDMAP_VERSION=%d, found %d. Please " + "start winbind once\n", IDMAP_VERSION, version)); + tdb_unlock_bystring(tdb, "IDMAP_VERSION"); + return False; + } + + tdb_unlock_bystring(tdb, "IDMAP_VERSION"); + return True; +} + +static bool tdbsam_new_rid(struct pdb_methods *methods, uint32 *prid) +{ + TDB_CONTEXT *tdb; + uint32 rid; + bool ret = False; + + tdb = tdb_open_log(state_path("winbindd_idmap.tdb"), 0, + TDB_DEFAULT, O_RDWR | O_CREAT, 0644); + + if (tdb == NULL) { + DEBUG(1, ("Could not open idmap: %s\n", strerror(errno))); + goto done; + } + + if (!init_idmap_tdb(tdb)) { + DEBUG(1, ("Could not init idmap\n")); + goto done; + } + + rid = BASE_RID; /* Default if not set */ + + if (!tdb_change_uint32_atomic(tdb, "RID_COUNTER", &rid, 1)) { + DEBUG(3, ("tdbsam_new_rid: Failed to increase RID_COUNTER\n")); + goto done; + } + + *prid = rid; + ret = True; + + done: + if ((tdb != NULL) && (tdb_close(tdb) != 0)) { + smb_panic("tdb_close(idmap_tdb) failed"); + } + + return ret; +} + +struct tdbsam_search_state { + struct pdb_methods *methods; + uint32_t acct_flags; + + uint32_t *rids; + uint32_t num_rids; + ssize_t array_size; + uint32_t current; +}; + +static int tdbsam_collect_rids(struct db_record *rec, void *private_data) +{ + struct tdbsam_search_state *state = talloc_get_type_abort( + private_data, struct tdbsam_search_state); + size_t prefixlen = strlen(RIDPREFIX); + uint32 rid; + + if ((rec->key.dsize < prefixlen) + || (strncmp((char *)rec->key.dptr, RIDPREFIX, prefixlen))) { + return 0; + } + + rid = strtoul((char *)rec->key.dptr+prefixlen, NULL, 16); + + ADD_TO_LARGE_ARRAY(state, uint32, rid, &state->rids, &state->num_rids, + &state->array_size); + + return 0; +} + +static void tdbsam_search_end(struct pdb_search *search) +{ + struct tdbsam_search_state *state = talloc_get_type_abort( + search->private_data, struct tdbsam_search_state); + TALLOC_FREE(state); +} + +static bool tdbsam_search_next_entry(struct pdb_search *search, + struct samr_displayentry *entry) +{ + struct tdbsam_search_state *state = talloc_get_type_abort( + search->private_data, struct tdbsam_search_state); + struct samu *user = NULL; + NTSTATUS status; + uint32_t rid; + + again: + TALLOC_FREE(user); + user = samu_new(talloc_tos()); + if (user == NULL) { + DEBUG(0, ("samu_new failed\n")); + return false; + } + + if (state->current == state->num_rids) { + return false; + } + + rid = state->rids[state->current++]; + + status = tdbsam_getsampwrid(state->methods, user, rid); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + /* + * Someone has deleted that user since we listed the RIDs + */ + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("tdbsam_getsampwrid failed: %s\n", + nt_errstr(status))); + TALLOC_FREE(user); + return false; + } + + if ((state->acct_flags != 0) && + ((state->acct_flags & pdb_get_acct_ctrl(user)) == 0)) { + goto again; + } + + entry->acct_flags = pdb_get_acct_ctrl(user); + entry->rid = rid; + entry->account_name = talloc_strdup( + search->mem_ctx, pdb_get_username(user)); + entry->fullname = talloc_strdup( + search->mem_ctx, pdb_get_fullname(user)); + entry->description = talloc_strdup( + search->mem_ctx, pdb_get_acct_desc(user)); + + TALLOC_FREE(user); + + if ((entry->account_name == NULL) || (entry->fullname == NULL) + || (entry->description == NULL)) { + DEBUG(0, ("talloc_strdup failed\n")); + return false; + } + + return true; +} + +static bool tdbsam_search_users(struct pdb_methods *methods, + struct pdb_search *search, + uint32 acct_flags) +{ + struct tdbsam_search_state *state; + + if (!tdbsam_open(tdbsam_filename)) { + DEBUG(0,("tdbsam_getsampwnam: failed to open %s!\n", + tdbsam_filename)); + return false; + } + + state = TALLOC_ZERO_P(search->mem_ctx, struct tdbsam_search_state); + if (state == NULL) { + DEBUG(0, ("talloc failed\n")); + return false; + } + state->acct_flags = acct_flags; + state->methods = methods; + + db_sam->traverse_read(db_sam, tdbsam_collect_rids, state); + + search->private_data = state; + search->next_entry = tdbsam_search_next_entry; + search->search_end = tdbsam_search_end; + + return true; +} + +/********************************************************************* + Initialize the tdb sam backend. Setup the dispath table of methods, + open the tdb, etc... +*********************************************************************/ + +static NTSTATUS pdb_init_tdbsam(struct pdb_methods **pdb_method, const char *location) +{ + NTSTATUS nt_status; + char *tdbfile = NULL; + const char *pfile = location; + + if (!NT_STATUS_IS_OK(nt_status = make_pdb_method( pdb_method ))) { + return nt_status; + } + + (*pdb_method)->name = "tdbsam"; + + (*pdb_method)->getsampwnam = tdbsam_getsampwnam; + (*pdb_method)->getsampwsid = tdbsam_getsampwsid; + (*pdb_method)->add_sam_account = tdbsam_add_sam_account; + (*pdb_method)->update_sam_account = tdbsam_update_sam_account; + (*pdb_method)->delete_sam_account = tdbsam_delete_sam_account; + (*pdb_method)->rename_sam_account = tdbsam_rename_sam_account; + (*pdb_method)->search_users = tdbsam_search_users; + + (*pdb_method)->rid_algorithm = tdbsam_rid_algorithm; + (*pdb_method)->new_rid = tdbsam_new_rid; + + /* save the path for later */ + + if (!location) { + if (asprintf(&tdbfile, "%s/%s", lp_private_dir(), + PASSDB_FILE_NAME) < 0) { + return NT_STATUS_NO_MEMORY; + } + pfile = tdbfile; + } + tdbsam_filename = SMB_STRDUP(pfile); + if (!tdbsam_filename) { + return NT_STATUS_NO_MEMORY; + } + SAFE_FREE(tdbfile); + + /* no private data */ + + (*pdb_method)->private_data = NULL; + (*pdb_method)->free_private_data = NULL; + + return NT_STATUS_OK; +} + +NTSTATUS pdb_tdbsam_init(void) +{ + return smb_register_passdb(PASSDB_INTERFACE_VERSION, "tdbsam", pdb_init_tdbsam); +} diff --git a/source3/passdb/secrets.c b/source3/passdb/secrets.c new file mode 100644 index 0000000000..4527ae7127 --- /dev/null +++ b/source3/passdb/secrets.c @@ -0,0 +1,1366 @@ +/* + Unix SMB/CIFS implementation. + Copyright (C) Andrew Tridgell 1992-2001 + Copyright (C) Andrew Bartlett 2002 + Copyright (C) Rafal Szczesniak 2002 + Copyright (C) Tim Potter 2001 + + 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/>. +*/ + +/* the Samba secrets database stores any generated, private information + such as the local SID and machine trust password */ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +static struct db_context *db_ctx; + +/* Urrrg. global.... */ +bool global_machine_password_needs_changing; + +/** + * Use a TDB to store an incrementing random seed. + * + * Initialised to the current pid, the very first time Samba starts, + * and incremented by one each time it is needed. + * + * @note Not called by systems with a working /dev/urandom. + */ +static void get_rand_seed(int *new_seed) +{ + *new_seed = sys_getpid(); + if (db_ctx) { + dbwrap_change_int32_atomic(db_ctx, "INFO/random_seed", + new_seed, 1); + } +} + +/* open up the secrets database */ +bool secrets_init(void) +{ + char *fname = NULL; + unsigned char dummy; + + if (db_ctx != NULL) + return True; + + fname = talloc_asprintf(talloc_tos(), "%s/secrets.tdb", + lp_private_dir()); + if (fname == NULL) { + return false; + } + + db_ctx = db_open(NULL, fname, 0, + TDB_DEFAULT, O_RDWR|O_CREAT, 0600); + + if (db_ctx == NULL) { + DEBUG(0,("Failed to open %s\n", fname)); + TALLOC_FREE(fname); + return False; + } + + TALLOC_FREE(fname); + + /** + * Set a reseed function for the crypto random generator + * + * This avoids a problem where systems without /dev/urandom + * could send the same challenge to multiple clients + */ + set_rand_reseed_callback(get_rand_seed); + + /* Ensure that the reseed is done now, while we are root, etc */ + generate_random_buffer(&dummy, sizeof(dummy)); + + return True; +} + +struct db_context *secrets_db_ctx(void) +{ + if (!secrets_init()) { + return NULL; + } + + return db_ctx; +} + +/* + * close secrets.tdb + */ +void secrets_shutdown(void) +{ + TALLOC_FREE(db_ctx); +} + +/* read a entry from the secrets database - the caller must free the result + if size is non-null then the size of the entry is put in there + */ +void *secrets_fetch(const char *key, size_t *size) +{ + TDB_DATA dbuf; + void *result; + + if (!secrets_init()) { + return NULL; + } + + if (db_ctx->fetch(db_ctx, talloc_tos(), string_tdb_data(key), + &dbuf) != 0) { + return NULL; + } + + result = memdup(dbuf.dptr, dbuf.dsize); + if (result == NULL) { + return NULL; + } + TALLOC_FREE(dbuf.dptr); + + if (size) { + *size = dbuf.dsize; + } + + return result; +} + +/* store a secrets entry + */ +bool secrets_store(const char *key, const void *data, size_t size) +{ + NTSTATUS status; + + if (!secrets_init()) { + return false; + } + + status = dbwrap_trans_store(db_ctx, string_tdb_data(key), + make_tdb_data((const uint8 *)data, size), + TDB_REPLACE); + return NT_STATUS_IS_OK(status); +} + + +/* delete a secets database entry + */ +bool secrets_delete(const char *key) +{ + NTSTATUS status; + if (!secrets_init()) { + return false; + } + + status = dbwrap_trans_delete(db_ctx, string_tdb_data(key)); + + return NT_STATUS_IS_OK(status); +} + +/** + * Form a key for fetching the domain sid + * + * @param domain domain name + * + * @return keystring + **/ +static const char *domain_sid_keystr(const char *domain) +{ + char *keystr; + + keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", + SECRETS_DOMAIN_SID, domain); + SMB_ASSERT(keystr != NULL); + return keystr; +} + +bool secrets_store_domain_sid(const char *domain, const DOM_SID *sid) +{ + bool ret; + + ret = secrets_store(domain_sid_keystr(domain), sid, sizeof(DOM_SID)); + + /* Force a re-query, in case we modified our domain */ + if (ret) + reset_global_sam_sid(); + return ret; +} + +bool secrets_fetch_domain_sid(const char *domain, DOM_SID *sid) +{ + DOM_SID *dyn_sid; + size_t size = 0; + + dyn_sid = (DOM_SID *)secrets_fetch(domain_sid_keystr(domain), &size); + + if (dyn_sid == NULL) + return False; + + if (size != sizeof(DOM_SID)) { + SAFE_FREE(dyn_sid); + return False; + } + + *sid = *dyn_sid; + SAFE_FREE(dyn_sid); + return True; +} + +bool secrets_store_domain_guid(const char *domain, struct GUID *guid) +{ + fstring key; + + slprintf(key, sizeof(key)-1, "%s/%s", SECRETS_DOMAIN_GUID, domain); + strupper_m(key); + return secrets_store(key, guid, sizeof(struct GUID)); +} + +bool secrets_fetch_domain_guid(const char *domain, struct GUID *guid) +{ + struct GUID *dyn_guid; + fstring key; + size_t size = 0; + struct GUID new_guid; + + slprintf(key, sizeof(key)-1, "%s/%s", SECRETS_DOMAIN_GUID, domain); + strupper_m(key); + dyn_guid = (struct GUID *)secrets_fetch(key, &size); + + if (!dyn_guid) { + if (lp_server_role() == ROLE_DOMAIN_PDC) { + smb_uuid_generate_random(&new_guid); + if (!secrets_store_domain_guid(domain, &new_guid)) + return False; + dyn_guid = (struct GUID *)secrets_fetch(key, &size); + } + if (dyn_guid == NULL) { + return False; + } + } + + if (size != sizeof(struct GUID)) { + DEBUG(1,("UUID size %d is wrong!\n", (int)size)); + SAFE_FREE(dyn_guid); + return False; + } + + *guid = *dyn_guid; + SAFE_FREE(dyn_guid); + return True; +} + +/** + * Form a key for fetching the machine trust account sec channel type + * + * @param domain domain name + * + * @return keystring + **/ +static const char *machine_sec_channel_type_keystr(const char *domain) +{ + char *keystr; + + keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", + SECRETS_MACHINE_SEC_CHANNEL_TYPE, + domain); + SMB_ASSERT(keystr != NULL); + return keystr; +} + +/** + * Form a key for fetching the machine trust account last change time + * + * @param domain domain name + * + * @return keystring + **/ +static const char *machine_last_change_time_keystr(const char *domain) +{ + char *keystr; + + keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", + SECRETS_MACHINE_LAST_CHANGE_TIME, + domain); + SMB_ASSERT(keystr != NULL); + return keystr; +} + + +/** + * Form a key for fetching the machine trust account password + * + * @param domain domain name + * + * @return keystring + **/ +static const char *machine_password_keystr(const char *domain) +{ + char *keystr; + + keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", + SECRETS_MACHINE_PASSWORD, domain); + SMB_ASSERT(keystr != NULL); + return keystr; +} + +/** + * Form a key for fetching the machine trust account password + * + * @param domain domain name + * + * @return stored password's key + **/ +static const char *trust_keystr(const char *domain) +{ + char *keystr; + + keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", + SECRETS_MACHINE_ACCT_PASS, domain); + SMB_ASSERT(keystr != NULL); + return keystr; +} + +/** + * Form a key for fetching a trusted domain password + * + * @param domain trusted domain name + * + * @return stored password's key + **/ +static char *trustdom_keystr(const char *domain) +{ + char *keystr; + + keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s", + SECRETS_DOMTRUST_ACCT_PASS, + domain); + SMB_ASSERT(keystr != NULL); + return keystr; +} + +/************************************************************************ + Lock the trust password entry. +************************************************************************/ + +void *secrets_get_trust_account_lock(TALLOC_CTX *mem_ctx, const char *domain) +{ + if (!secrets_init()) { + return NULL; + } + + return db_ctx->fetch_locked( + db_ctx, mem_ctx, string_term_tdb_data(trust_keystr(domain))); +} + +/************************************************************************ + Routine to get the default secure channel type for trust accounts +************************************************************************/ + +uint32 get_default_sec_channel(void) +{ + if (lp_server_role() == ROLE_DOMAIN_BDC || + lp_server_role() == ROLE_DOMAIN_PDC) { + return SEC_CHAN_BDC; + } else { + return SEC_CHAN_WKSTA; + } +} + +/************************************************************************ + Routine to get the trust account password for a domain. + This only tries to get the legacy hashed version of the password. + The user of this function must have locked the trust password file using + the above secrets_lock_trust_account_password(). +************************************************************************/ + +bool secrets_fetch_trust_account_password_legacy(const char *domain, + uint8 ret_pwd[16], + time_t *pass_last_set_time, + uint32 *channel) +{ + struct machine_acct_pass *pass; + size_t size = 0; + + if (!(pass = (struct machine_acct_pass *)secrets_fetch( + trust_keystr(domain), &size))) { + DEBUG(5, ("secrets_fetch failed!\n")); + return False; + } + + if (size != sizeof(*pass)) { + DEBUG(0, ("secrets were of incorrect size!\n")); + SAFE_FREE(pass); + return False; + } + + if (pass_last_set_time) { + *pass_last_set_time = pass->mod_time; + } + memcpy(ret_pwd, pass->hash, 16); + + if (channel) { + *channel = get_default_sec_channel(); + } + + /* Test if machine password has expired and needs to be changed */ + if (lp_machine_password_timeout()) { + if (pass->mod_time > 0 && time(NULL) > (pass->mod_time + + (time_t)lp_machine_password_timeout())) { + global_machine_password_needs_changing = True; + } + } + + SAFE_FREE(pass); + return True; +} + +/************************************************************************ + Routine to get the trust account password for a domain. + The user of this function must have locked the trust password file using + the above secrets_lock_trust_account_password(). +************************************************************************/ + +bool secrets_fetch_trust_account_password(const char *domain, uint8 ret_pwd[16], + time_t *pass_last_set_time, + uint32 *channel) +{ + char *plaintext; + + plaintext = secrets_fetch_machine_password(domain, pass_last_set_time, + channel); + if (plaintext) { + DEBUG(4,("Using cleartext machine password\n")); + E_md4hash(plaintext, ret_pwd); + SAFE_FREE(plaintext); + return True; + } + + return secrets_fetch_trust_account_password_legacy(domain, ret_pwd, + pass_last_set_time, + channel); +} + +/** + * Pack SID passed by pointer + * + * @param pack_buf pointer to buffer which is to be filled with packed data + * @param bufsize size of packing buffer + * @param sid pointer to sid to be packed + * + * @return length of the packed representation of the whole structure + **/ +static size_t tdb_sid_pack(uint8 *pack_buf, int bufsize, DOM_SID* sid) +{ + int idx; + size_t len = 0; + uint8 *p = pack_buf; + int remaining_space = pack_buf ? bufsize : 0; + + if (!sid) { + return -1; + } + + len += tdb_pack(p, remaining_space, "bb", sid->sid_rev_num, + sid->num_auths); + if (pack_buf) { + p = pack_buf + len; + remaining_space = bufsize - len; + } + + for (idx = 0; idx < 6; idx++) { + len += tdb_pack(p, remaining_space, "b", + sid->id_auth[idx]); + if (pack_buf) { + p = pack_buf + len; + remaining_space = bufsize - len; + } + } + + for (idx = 0; idx < MAXSUBAUTHS; idx++) { + len += tdb_pack(p, remaining_space, "d", + sid->sub_auths[idx]); + if (pack_buf) { + p = pack_buf + len; + remaining_space = bufsize - len; + } + } + + return len; +} + +/** + * Unpack SID into a pointer + * + * @param pack_buf pointer to buffer with packed representation + * @param bufsize size of the buffer + * @param sid pointer to sid structure to be filled with unpacked data + * + * @return size of structure unpacked from buffer + **/ +static size_t tdb_sid_unpack(uint8 *pack_buf, int bufsize, DOM_SID* sid) +{ + int idx, len = 0; + + if (!sid || !pack_buf) return -1; + + len += tdb_unpack(pack_buf + len, bufsize - len, "bb", + &sid->sid_rev_num, &sid->num_auths); + + for (idx = 0; idx < 6; idx++) { + len += tdb_unpack(pack_buf + len, bufsize - len, "b", + &sid->id_auth[idx]); + } + + for (idx = 0; idx < MAXSUBAUTHS; idx++) { + len += tdb_unpack(pack_buf + len, bufsize - len, "d", + &sid->sub_auths[idx]); + } + + return len; +} + +/** + * Pack TRUSTED_DOM_PASS passed by pointer + * + * @param pack_buf pointer to buffer which is to be filled with packed data + * @param bufsize size of the buffer + * @param pass pointer to trusted domain password to be packed + * + * @return length of the packed representation of the whole structure + **/ +static size_t tdb_trusted_dom_pass_pack(uint8 *pack_buf, int bufsize, + TRUSTED_DOM_PASS* pass) +{ + int idx, len = 0; + uint8 *p = pack_buf; + int remaining_space = pack_buf ? bufsize : 0; + + if (!pass) { + return -1; + } + + /* packing unicode domain name and password */ + len += tdb_pack(p, remaining_space, "d", + pass->uni_name_len); + if (pack_buf) { + p = pack_buf + len; + remaining_space = bufsize - len; + } + + for (idx = 0; idx < 32; idx++) { + len += tdb_pack(p, remaining_space, "w", + pass->uni_name[idx]); + if (pack_buf) { + p = pack_buf + len; + remaining_space = bufsize - len; + } + } + + len += tdb_pack(p, remaining_space, "dPd", pass->pass_len, + pass->pass, pass->mod_time); + if (pack_buf) { + p = pack_buf + len; + remaining_space = bufsize - len; + } + + /* packing SID structure */ + len += tdb_sid_pack(p, remaining_space, &pass->domain_sid); + if (pack_buf) { + p = pack_buf + len; + remaining_space = bufsize - len; + } + + return len; +} + + +/** + * Unpack TRUSTED_DOM_PASS passed by pointer + * + * @param pack_buf pointer to buffer with packed representation + * @param bufsize size of the buffer + * @param pass pointer to trusted domain password to be filled with unpacked data + * + * @return size of structure unpacked from buffer + **/ +static size_t tdb_trusted_dom_pass_unpack(uint8 *pack_buf, int bufsize, + TRUSTED_DOM_PASS* pass) +{ + int idx, len = 0; + char *passp = NULL; + + if (!pack_buf || !pass) return -1; + + /* unpack unicode domain name and plaintext password */ + len += tdb_unpack(pack_buf, bufsize - len, "d", &pass->uni_name_len); + + for (idx = 0; idx < 32; idx++) + len += tdb_unpack(pack_buf + len, bufsize - len, "w", + &pass->uni_name[idx]); + + len += tdb_unpack(pack_buf + len, bufsize - len, "dPd", + &pass->pass_len, &passp, &pass->mod_time); + if (passp) { + fstrcpy(pass->pass, passp); + } + SAFE_FREE(passp); + + /* unpack domain sid */ + len += tdb_sid_unpack(pack_buf + len, bufsize - len, + &pass->domain_sid); + + return len; +} + +/************************************************************************ + Routine to get account password to trusted domain +************************************************************************/ + +bool secrets_fetch_trusted_domain_password(const char *domain, char** pwd, + DOM_SID *sid, time_t *pass_last_set_time) +{ + struct trusted_dom_pass pass; + size_t size = 0; + + /* unpacking structures */ + uint8 *pass_buf; + int pass_len = 0; + + ZERO_STRUCT(pass); + + /* fetching trusted domain password structure */ + if (!(pass_buf = (uint8 *)secrets_fetch(trustdom_keystr(domain), + &size))) { + DEBUG(5, ("secrets_fetch failed!\n")); + return False; + } + + /* unpack trusted domain password */ + pass_len = tdb_trusted_dom_pass_unpack(pass_buf, size, &pass); + SAFE_FREE(pass_buf); + + if (pass_len != size) { + DEBUG(5, ("Invalid secrets size. Unpacked data doesn't match trusted_dom_pass structure.\n")); + return False; + } + + /* the trust's password */ + if (pwd) { + *pwd = SMB_STRDUP(pass.pass); + if (!*pwd) { + return False; + } + } + + /* last change time */ + if (pass_last_set_time) *pass_last_set_time = pass.mod_time; + + /* domain sid */ + if (sid != NULL) sid_copy(sid, &pass.domain_sid); + + return True; +} + +/** + * Routine to store the password for trusted domain + * + * @param domain remote domain name + * @param pwd plain text password of trust relationship + * @param sid remote domain sid + * + * @return true if succeeded + **/ + +bool secrets_store_trusted_domain_password(const char* domain, const char* pwd, + const DOM_SID *sid) +{ + smb_ucs2_t *uni_dom_name; + bool ret; + size_t converted_size; + + /* packing structures */ + uint8 *pass_buf = NULL; + int pass_len = 0; + + struct trusted_dom_pass pass; + ZERO_STRUCT(pass); + + if (!push_ucs2_allocate(&uni_dom_name, domain, &converted_size)) { + DEBUG(0, ("Could not convert domain name %s to unicode\n", + domain)); + return False; + } + + strncpy_w(pass.uni_name, uni_dom_name, sizeof(pass.uni_name) - 1); + pass.uni_name_len = strlen_w(uni_dom_name)+1; + SAFE_FREE(uni_dom_name); + + /* last change time */ + pass.mod_time = time(NULL); + + /* password of the trust */ + pass.pass_len = strlen(pwd); + fstrcpy(pass.pass, pwd); + + /* domain sid */ + sid_copy(&pass.domain_sid, sid); + + /* Calculate the length. */ + pass_len = tdb_trusted_dom_pass_pack(NULL, 0, &pass); + pass_buf = SMB_MALLOC_ARRAY(uint8, pass_len); + if (!pass_buf) { + return false; + } + pass_len = tdb_trusted_dom_pass_pack(pass_buf, pass_len, &pass); + ret = secrets_store(trustdom_keystr(domain), (void *)pass_buf, + pass_len); + SAFE_FREE(pass_buf); + return ret; +} + +/************************************************************************ + Routine to delete the plaintext machine account password +************************************************************************/ + +bool secrets_delete_machine_password(const char *domain) +{ + return secrets_delete(machine_password_keystr(domain)); +} + +/************************************************************************ + Routine to delete the plaintext machine account password, sec channel type and + last change time from secrets database +************************************************************************/ + +bool secrets_delete_machine_password_ex(const char *domain) +{ + if (!secrets_delete(machine_password_keystr(domain))) { + return false; + } + if (!secrets_delete(machine_sec_channel_type_keystr(domain))) { + return false; + } + return secrets_delete(machine_last_change_time_keystr(domain)); +} + +/************************************************************************ + Routine to delete the domain sid +************************************************************************/ + +bool secrets_delete_domain_sid(const char *domain) +{ + return secrets_delete(domain_sid_keystr(domain)); +} + +/************************************************************************ + Routine to set the plaintext machine account password for a realm +the password is assumed to be a null terminated ascii string +************************************************************************/ + +bool secrets_store_machine_password(const char *pass, const char *domain, uint32 sec_channel) +{ + bool ret; + uint32 last_change_time; + uint32 sec_channel_type; + + ret = secrets_store(machine_password_keystr(domain), pass, strlen(pass)+1); + if (!ret) + return ret; + + SIVAL(&last_change_time, 0, time(NULL)); + ret = secrets_store(machine_last_change_time_keystr(domain), &last_change_time, sizeof(last_change_time)); + + SIVAL(&sec_channel_type, 0, sec_channel); + ret = secrets_store(machine_sec_channel_type_keystr(domain), &sec_channel_type, sizeof(sec_channel_type)); + + return ret; +} + +/************************************************************************ + Routine to fetch the plaintext machine account password for a realm + the password is assumed to be a null terminated ascii string. +************************************************************************/ + +char *secrets_fetch_machine_password(const char *domain, + time_t *pass_last_set_time, + uint32 *channel) +{ + char *ret; + ret = (char *)secrets_fetch(machine_password_keystr(domain), NULL); + + if (pass_last_set_time) { + size_t size; + uint32 *last_set_time; + last_set_time = (unsigned int *)secrets_fetch(machine_last_change_time_keystr(domain), &size); + if (last_set_time) { + *pass_last_set_time = IVAL(last_set_time,0); + SAFE_FREE(last_set_time); + } else { + *pass_last_set_time = 0; + } + } + + if (channel) { + size_t size; + uint32 *channel_type; + channel_type = (unsigned int *)secrets_fetch(machine_sec_channel_type_keystr(domain), &size); + if (channel_type) { + *channel = IVAL(channel_type,0); + SAFE_FREE(channel_type); + } else { + *channel = get_default_sec_channel(); + } + } + + return ret; +} + +/************************************************************************ + Routine to delete the password for trusted domain +************************************************************************/ + +bool trusted_domain_password_delete(const char *domain) +{ + return secrets_delete(trustdom_keystr(domain)); +} + +bool secrets_store_ldap_pw(const char* dn, char* pw) +{ + char *key = NULL; + bool ret; + + if (asprintf(&key, "%s/%s", SECRETS_LDAP_BIND_PW, dn) < 0) { + DEBUG(0, ("secrets_store_ldap_pw: asprintf failed!\n")); + return False; + } + + ret = secrets_store(key, pw, strlen(pw)+1); + + SAFE_FREE(key); + return ret; +} + +/******************************************************************* + Find the ldap password. +******************************************************************/ + +bool fetch_ldap_pw(char **dn, char** pw) +{ + char *key = NULL; + size_t size = 0; + + *dn = smb_xstrdup(lp_ldap_admin_dn()); + + if (asprintf(&key, "%s/%s", SECRETS_LDAP_BIND_PW, *dn) < 0) { + SAFE_FREE(*dn); + DEBUG(0, ("fetch_ldap_pw: asprintf failed!\n")); + } + + *pw=(char *)secrets_fetch(key, &size); + SAFE_FREE(key); + + if (!size) { + /* Upgrade 2.2 style entry */ + char *p; + char* old_style_key = SMB_STRDUP(*dn); + char *data; + fstring old_style_pw; + + if (!old_style_key) { + DEBUG(0, ("fetch_ldap_pw: strdup failed!\n")); + return False; + } + + for (p=old_style_key; *p; p++) + if (*p == ',') *p = '/'; + + data=(char *)secrets_fetch(old_style_key, &size); + if ((data == NULL) || (size < sizeof(old_style_pw))) { + DEBUG(0,("fetch_ldap_pw: neither ldap secret retrieved!\n")); + SAFE_FREE(old_style_key); + SAFE_FREE(*dn); + SAFE_FREE(data); + return False; + } + + size = MIN(size, sizeof(fstring)-1); + strncpy(old_style_pw, data, size); + old_style_pw[size] = 0; + + SAFE_FREE(data); + + if (!secrets_store_ldap_pw(*dn, old_style_pw)) { + DEBUG(0,("fetch_ldap_pw: ldap secret could not be upgraded!\n")); + SAFE_FREE(old_style_key); + SAFE_FREE(*dn); + return False; + } + if (!secrets_delete(old_style_key)) { + DEBUG(0,("fetch_ldap_pw: old ldap secret could not be deleted!\n")); + } + + SAFE_FREE(old_style_key); + + *pw = smb_xstrdup(old_style_pw); + } + + return True; +} + +/** + * Get trusted domains info from secrets.tdb. + **/ + +struct list_trusted_domains_state { + uint32 num_domains; + struct trustdom_info **domains; +}; + +static int list_trusted_domain(struct db_record *rec, void *private_data) +{ + const size_t prefix_len = strlen(SECRETS_DOMTRUST_ACCT_PASS); + size_t converted_size, packed_size = 0; + struct trusted_dom_pass pass; + struct trustdom_info *dom_info; + + struct list_trusted_domains_state *state = + (struct list_trusted_domains_state *)private_data; + + if ((rec->key.dsize < prefix_len) + || (strncmp((char *)rec->key.dptr, SECRETS_DOMTRUST_ACCT_PASS, + prefix_len) != 0)) { + return 0; + } + + packed_size = tdb_trusted_dom_pass_unpack( + rec->value.dptr, rec->value.dsize, &pass); + + if (rec->value.dsize != packed_size) { + DEBUG(2, ("Secrets record is invalid!\n")); + return 0; + } + + if (pass.domain_sid.num_auths != 4) { + DEBUG(0, ("SID %s is not a domain sid, has %d " + "auths instead of 4\n", + sid_string_dbg(&pass.domain_sid), + pass.domain_sid.num_auths)); + return 0; + } + + if (!(dom_info = TALLOC_P(state->domains, struct trustdom_info))) { + DEBUG(0, ("talloc failed\n")); + return 0; + } + + if (!pull_ucs2_talloc(dom_info, &dom_info->name, pass.uni_name, + &converted_size)) { + DEBUG(2, ("pull_ucs2_talloc failed\n")); + TALLOC_FREE(dom_info); + return 0; + } + + sid_copy(&dom_info->sid, &pass.domain_sid); + + ADD_TO_ARRAY(state->domains, struct trustdom_info *, dom_info, + &state->domains, &state->num_domains); + + if (state->domains == NULL) { + state->num_domains = 0; + return -1; + } + return 0; +} + +NTSTATUS secrets_trusted_domains(TALLOC_CTX *mem_ctx, uint32 *num_domains, + struct trustdom_info ***domains) +{ + struct list_trusted_domains_state state; + + secrets_init(); + + if (db_ctx == NULL) { + return NT_STATUS_ACCESS_DENIED; + } + + state.num_domains = 0; + + /* + * Make sure that a talloc context for the trustdom_info structs + * exists + */ + + if (!(state.domains = TALLOC_ARRAY( + mem_ctx, struct trustdom_info *, 1))) { + return NT_STATUS_NO_MEMORY; + } + + db_ctx->traverse_read(db_ctx, list_trusted_domain, (void *)&state); + + *num_domains = state.num_domains; + *domains = state.domains; + return NT_STATUS_OK; +} + +/******************************************************************************* + Store a complete AFS keyfile into secrets.tdb. +*******************************************************************************/ + +bool secrets_store_afs_keyfile(const char *cell, const struct afs_keyfile *keyfile) +{ + fstring key; + + if ((cell == NULL) || (keyfile == NULL)) + return False; + + if (ntohl(keyfile->nkeys) > SECRETS_AFS_MAXKEYS) + return False; + + slprintf(key, sizeof(key)-1, "%s/%s", SECRETS_AFS_KEYFILE, cell); + return secrets_store(key, keyfile, sizeof(struct afs_keyfile)); +} + +/******************************************************************************* + Fetch the current (highest) AFS key from secrets.tdb +*******************************************************************************/ +bool secrets_fetch_afs_key(const char *cell, struct afs_key *result) +{ + fstring key; + struct afs_keyfile *keyfile; + size_t size = 0; + uint32 i; + + slprintf(key, sizeof(key)-1, "%s/%s", SECRETS_AFS_KEYFILE, cell); + + keyfile = (struct afs_keyfile *)secrets_fetch(key, &size); + + if (keyfile == NULL) + return False; + + if (size != sizeof(struct afs_keyfile)) { + SAFE_FREE(keyfile); + return False; + } + + i = ntohl(keyfile->nkeys); + + if (i > SECRETS_AFS_MAXKEYS) { + SAFE_FREE(keyfile); + return False; + } + + *result = keyfile->entry[i-1]; + + result->kvno = ntohl(result->kvno); + + SAFE_FREE(keyfile); + + return True; +} + +/****************************************************************************** + When kerberos is not available, choose between anonymous or + authenticated connections. + + We need to use an authenticated connection if DCs have the + RestrictAnonymous registry entry set > 0, or the "Additional + restrictions for anonymous connections" set in the win2k Local + Security Policy. + + Caller to free() result in domain, username, password +*******************************************************************************/ +void secrets_fetch_ipc_userpass(char **username, char **domain, char **password) +{ + *username = (char *)secrets_fetch(SECRETS_AUTH_USER, NULL); + *domain = (char *)secrets_fetch(SECRETS_AUTH_DOMAIN, NULL); + *password = (char *)secrets_fetch(SECRETS_AUTH_PASSWORD, NULL); + + if (*username && **username) { + + if (!*domain || !**domain) + *domain = smb_xstrdup(lp_workgroup()); + + if (!*password || !**password) + *password = smb_xstrdup(""); + + DEBUG(3, ("IPC$ connections done by user %s\\%s\n", + *domain, *username)); + + } else { + DEBUG(3, ("IPC$ connections done anonymously\n")); + *username = smb_xstrdup(""); + *domain = smb_xstrdup(""); + *password = smb_xstrdup(""); + } +} + +/****************************************************************************** + Open or create the schannel session store tdb. +*******************************************************************************/ + +static TDB_CONTEXT *open_schannel_session_store(TALLOC_CTX *mem_ctx) +{ + TDB_DATA vers; + uint32 ver; + TDB_CONTEXT *tdb_sc = NULL; + char *fname = talloc_asprintf(mem_ctx, "%s/schannel_store.tdb", lp_private_dir()); + + if (!fname) { + return NULL; + } + + tdb_sc = tdb_open_log(fname, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600); + + if (!tdb_sc) { + DEBUG(0,("open_schannel_session_store: Failed to open %s\n", fname)); + TALLOC_FREE(fname); + return NULL; + } + + vers = tdb_fetch_bystring(tdb_sc, "SCHANNEL_STORE_VERSION"); + if (vers.dptr == NULL) { + /* First opener, no version. */ + SIVAL(&ver,0,1); + vers.dptr = (uint8 *)&ver; + vers.dsize = 4; + tdb_store_bystring(tdb_sc, "SCHANNEL_STORE_VERSION", vers, TDB_REPLACE); + vers.dptr = NULL; + } else if (vers.dsize == 4) { + ver = IVAL(vers.dptr,0); + if (ver != 1) { + tdb_close(tdb_sc); + tdb_sc = NULL; + DEBUG(0,("open_schannel_session_store: wrong version number %d in %s\n", + (int)ver, fname )); + } + } else { + tdb_close(tdb_sc); + tdb_sc = NULL; + DEBUG(0,("open_schannel_session_store: wrong version number size %d in %s\n", + (int)vers.dsize, fname )); + } + + SAFE_FREE(vers.dptr); + TALLOC_FREE(fname); + + return tdb_sc; +} + +/****************************************************************************** + Store the schannel state after an AUTH2 call. + Note we must be root here. +*******************************************************************************/ + +bool secrets_store_schannel_session_info(TALLOC_CTX *mem_ctx, + const char *remote_machine, + const struct dcinfo *pdc) +{ + TDB_CONTEXT *tdb_sc = NULL; + TDB_DATA value; + bool ret; + char *keystr = talloc_asprintf_strupper_m(mem_ctx, "%s/%s", + SECRETS_SCHANNEL_STATE, + remote_machine); + if (!keystr) { + return False; + } + + /* Work out how large the record is. */ + value.dsize = tdb_pack(NULL, 0, "dBBBBBfff", + pdc->sequence, + 8, pdc->seed_chal.data, + 8, pdc->clnt_chal.data, + 8, pdc->srv_chal.data, + 16, pdc->sess_key, + 16, pdc->mach_pw, + pdc->mach_acct, + pdc->remote_machine, + pdc->domain); + + value.dptr = TALLOC_ARRAY(mem_ctx, uint8, value.dsize); + if (!value.dptr) { + TALLOC_FREE(keystr); + return False; + } + + value.dsize = tdb_pack(value.dptr, value.dsize, "dBBBBBfff", + pdc->sequence, + 8, pdc->seed_chal.data, + 8, pdc->clnt_chal.data, + 8, pdc->srv_chal.data, + 16, pdc->sess_key, + 16, pdc->mach_pw, + pdc->mach_acct, + pdc->remote_machine, + pdc->domain); + + tdb_sc = open_schannel_session_store(mem_ctx); + if (!tdb_sc) { + TALLOC_FREE(keystr); + TALLOC_FREE(value.dptr); + return False; + } + + ret = (tdb_store_bystring(tdb_sc, keystr, value, TDB_REPLACE) == 0 ? True : False); + + DEBUG(3,("secrets_store_schannel_session_info: stored schannel info with key %s\n", + keystr )); + + tdb_close(tdb_sc); + TALLOC_FREE(keystr); + TALLOC_FREE(value.dptr); + return ret; +} + +/****************************************************************************** + Restore the schannel state on a client reconnect. + Note we must be root here. +*******************************************************************************/ + +bool secrets_restore_schannel_session_info(TALLOC_CTX *mem_ctx, + const char *remote_machine, + struct dcinfo **ppdc) +{ + TDB_CONTEXT *tdb_sc = NULL; + TDB_DATA value; + unsigned char *pseed_chal = NULL; + unsigned char *pclnt_chal = NULL; + unsigned char *psrv_chal = NULL; + unsigned char *psess_key = NULL; + unsigned char *pmach_pw = NULL; + uint32 l1, l2, l3, l4, l5; + int ret; + struct dcinfo *pdc = NULL; + char *keystr = talloc_asprintf_strupper_m(mem_ctx, "%s/%s", + SECRETS_SCHANNEL_STATE, + remote_machine); + + *ppdc = NULL; + + if (!keystr) { + return False; + } + + tdb_sc = open_schannel_session_store(mem_ctx); + if (!tdb_sc) { + TALLOC_FREE(keystr); + return False; + } + + value = tdb_fetch_bystring(tdb_sc, keystr); + if (!value.dptr) { + DEBUG(0,("secrets_restore_schannel_session_info: Failed to find entry with key %s\n", + keystr )); + tdb_close(tdb_sc); + return False; + } + + pdc = TALLOC_ZERO_P(mem_ctx, struct dcinfo); + + /* Retrieve the record. */ + ret = tdb_unpack(value.dptr, value.dsize, "dBBBBBfff", + &pdc->sequence, + &l1, &pseed_chal, + &l2, &pclnt_chal, + &l3, &psrv_chal, + &l4, &psess_key, + &l5, &pmach_pw, + &pdc->mach_acct, + &pdc->remote_machine, + &pdc->domain); + + if (ret == -1 || l1 != 8 || l2 != 8 || l3 != 8 || l4 != 16 || l5 != 16) { + /* Bad record - delete it. */ + tdb_delete_bystring(tdb_sc, keystr); + tdb_close(tdb_sc); + TALLOC_FREE(keystr); + TALLOC_FREE(pdc); + SAFE_FREE(pseed_chal); + SAFE_FREE(pclnt_chal); + SAFE_FREE(psrv_chal); + SAFE_FREE(psess_key); + SAFE_FREE(pmach_pw); + SAFE_FREE(value.dptr); + return False; + } + + tdb_close(tdb_sc); + + memcpy(pdc->seed_chal.data, pseed_chal, 8); + memcpy(pdc->clnt_chal.data, pclnt_chal, 8); + memcpy(pdc->srv_chal.data, psrv_chal, 8); + memcpy(pdc->sess_key, psess_key, 16); + memcpy(pdc->mach_pw, pmach_pw, 16); + + /* We know these are true so didn't bother to store them. */ + pdc->challenge_sent = True; + pdc->authenticated = True; + + DEBUG(3,("secrets_restore_schannel_session_info: restored schannel info key %s\n", + keystr )); + + SAFE_FREE(pseed_chal); + SAFE_FREE(pclnt_chal); + SAFE_FREE(psrv_chal); + SAFE_FREE(psess_key); + SAFE_FREE(pmach_pw); + + TALLOC_FREE(keystr); + SAFE_FREE(value.dptr); + + *ppdc = pdc; + + return True; +} + +bool secrets_store_generic(const char *owner, const char *key, const char *secret) +{ + char *tdbkey = NULL; + bool ret; + + if (asprintf(&tdbkey, "SECRETS/GENERIC/%s/%s", owner, key) < 0) { + DEBUG(0, ("asprintf failed!\n")); + return False; + } + + ret = secrets_store(tdbkey, secret, strlen(secret)+1); + + SAFE_FREE(tdbkey); + return ret; +} + +/******************************************************************* + Find the ldap password. +******************************************************************/ + +char *secrets_fetch_generic(const char *owner, const char *key) +{ + char *secret = NULL; + char *tdbkey = NULL; + + if (( ! owner) || ( ! key)) { + DEBUG(1, ("Invalid Paramters")); + return NULL; + } + + if (asprintf(&tdbkey, "SECRETS/GENERIC/%s/%s", owner, key) < 0) { + DEBUG(0, ("Out of memory!\n")); + return NULL; + } + + secret = (char *)secrets_fetch(tdbkey, NULL); + SAFE_FREE(tdbkey); + + return secret; +} + diff --git a/source3/passdb/util_builtin.c b/source3/passdb/util_builtin.c new file mode 100644 index 0000000000..dbddddd25d --- /dev/null +++ b/source3/passdb/util_builtin.c @@ -0,0 +1,109 @@ +/* + Unix SMB/CIFS implementation. + Translate BUILTIN names to SIDs and vice versa + Copyright (C) Volker Lendecke 2005 + + 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" + +struct rid_name_map { + uint32 rid; + const char *name; +}; + +static const struct rid_name_map builtin_aliases[] = { + { BUILTIN_ALIAS_RID_ADMINS, "Administrators" }, + { BUILTIN_ALIAS_RID_USERS, "Users" }, + { BUILTIN_ALIAS_RID_GUESTS, "Guests" }, + { BUILTIN_ALIAS_RID_POWER_USERS, "Power Users" }, + { BUILTIN_ALIAS_RID_ACCOUNT_OPS, "Account Operators" }, + { BUILTIN_ALIAS_RID_SYSTEM_OPS, "Server Operators" }, + { BUILTIN_ALIAS_RID_PRINT_OPS, "Print Operators" }, + { BUILTIN_ALIAS_RID_BACKUP_OPS, "Backup Operators" }, + { BUILTIN_ALIAS_RID_REPLICATOR, "Replicator" }, + { BUILTIN_ALIAS_RID_RAS_SERVERS, "RAS Servers" }, + { BUILTIN_ALIAS_RID_PRE_2K_ACCESS, "Pre-Windows 2000 Compatible Access" }, + { 0, NULL}}; + +/******************************************************************* + Look up a rid in the BUILTIN domain + ********************************************************************/ +bool lookup_builtin_rid(TALLOC_CTX *mem_ctx, uint32 rid, const char **name) +{ + const struct rid_name_map *aliases = builtin_aliases; + + while (aliases->name != NULL) { + if (rid == aliases->rid) { + *name = talloc_strdup(mem_ctx, aliases->name); + return True; + } + aliases++; + } + + return False; +} + +/******************************************************************* + Look up a name in the BUILTIN domain + ********************************************************************/ +bool lookup_builtin_name(const char *name, uint32 *rid) +{ + const struct rid_name_map *aliases = builtin_aliases; + + while (aliases->name != NULL) { + if (strequal(name, aliases->name)) { + *rid = aliases->rid; + return True; + } + aliases++; + } + + return False; +} + +/***************************************************************** + Return the name of the BUILTIN domain +*****************************************************************/ + +const char *builtin_domain_name(void) +{ + return "BUILTIN"; +} + +/***************************************************************** + Check if the SID is the builtin SID (S-1-5-32). +*****************************************************************/ + +bool sid_check_is_builtin(const DOM_SID *sid) +{ + return sid_equal(sid, &global_sid_Builtin); +} + +/***************************************************************** + Check if the SID is one of the builtin SIDs (S-1-5-32-a). +*****************************************************************/ + +bool sid_check_is_in_builtin(const DOM_SID *sid) +{ + DOM_SID dom_sid; + uint32 rid; + + sid_copy(&dom_sid, sid); + sid_split_rid(&dom_sid, &rid); + + return sid_check_is_builtin(&dom_sid); +} + diff --git a/source3/passdb/util_unixsids.c b/source3/passdb/util_unixsids.c new file mode 100644 index 0000000000..1b674d02a2 --- /dev/null +++ b/source3/passdb/util_unixsids.c @@ -0,0 +1,105 @@ +/* + Unix SMB/CIFS implementation. + Translate unix-defined names to SIDs and vice versa + Copyright (C) Volker Lendecke 2005 + + 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" + +bool sid_check_is_unix_users(const DOM_SID *sid) +{ + return sid_equal(sid, &global_sid_Unix_Users); +} + +bool sid_check_is_in_unix_users(const DOM_SID *sid) +{ + DOM_SID dom_sid; + uint32 rid; + + sid_copy(&dom_sid, sid); + sid_split_rid(&dom_sid, &rid); + + return sid_check_is_unix_users(&dom_sid); +} + +bool uid_to_unix_users_sid(uid_t uid, DOM_SID *sid) +{ + sid_copy(sid, &global_sid_Unix_Users); + return sid_append_rid(sid, (uint32_t)uid); +} + +bool gid_to_unix_groups_sid(gid_t gid, DOM_SID *sid) +{ + sid_copy(sid, &global_sid_Unix_Groups); + return sid_append_rid(sid, (uint32_t)gid); +} + +const char *unix_users_domain_name(void) +{ + return "Unix User"; +} + +bool lookup_unix_user_name(const char *name, DOM_SID *sid) +{ + struct passwd *pwd; + + pwd = getpwnam_alloc(NULL, name); + if (pwd == NULL) { + return False; + } + + sid_copy(sid, &global_sid_Unix_Users); + sid_append_rid(sid, (uint32_t)pwd->pw_uid); /* For 64-bit uid's we have enough + * space ... */ + TALLOC_FREE(pwd); + return True; +} + +bool sid_check_is_unix_groups(const DOM_SID *sid) +{ + return sid_equal(sid, &global_sid_Unix_Groups); +} + +bool sid_check_is_in_unix_groups(const DOM_SID *sid) +{ + DOM_SID dom_sid; + uint32 rid; + + sid_copy(&dom_sid, sid); + sid_split_rid(&dom_sid, &rid); + + return sid_check_is_unix_groups(&dom_sid); +} + +const char *unix_groups_domain_name(void) +{ + return "Unix Group"; +} + +bool lookup_unix_group_name(const char *name, DOM_SID *sid) +{ + struct group *grp; + + grp = sys_getgrnam(name); + if (grp == NULL) { + return False; + } + + sid_copy(sid, &global_sid_Unix_Groups); + sid_append_rid(sid, (uint32_t)grp->gr_gid); /* For 64-bit uid's we have enough + * space ... */ + return True; +} diff --git a/source3/passdb/util_wellknown.c b/source3/passdb/util_wellknown.c new file mode 100644 index 0000000000..3a30ab0a65 --- /dev/null +++ b/source3/passdb/util_wellknown.c @@ -0,0 +1,172 @@ +/* + Unix SMB/CIFS implementation. + Lookup routines for well-known SIDs + Copyright (C) Andrew Tridgell 1992-1998 + Copyright (C) Luke Kenneth Caseson Leighton 1998-1999 + Copyright (C) Jeremy Allison 1999 + Copyright (C) Volker Lendecke 2005 + + 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" + +struct rid_name_map { + uint32 rid; + const char *name; +}; + +struct sid_name_map_info +{ + const DOM_SID *sid; + const char *name; + const struct rid_name_map *known_users; +}; + +static const struct rid_name_map everyone_users[] = { + { 0, "Everyone" }, + { 0, NULL}}; + +static const struct rid_name_map creator_owner_users[] = { + { 0, "Creator Owner" }, + { 1, "Creator Group" }, + { 0, NULL}}; + +static const struct rid_name_map nt_authority_users[] = { + { 1, "Dialup" }, + { 2, "Network"}, + { 3, "Batch"}, + { 4, "Interactive"}, + { 6, "Service"}, + { 7, "AnonymousLogon"}, + { 8, "Proxy"}, + { 9, "ServerLogon"}, + { 10, "Self"}, + { 11, "Authenticated Users"}, + { 12, "Restricted"}, + { 13, "Terminal Server User"}, + { 14, "Remote Interactive Logon"}, + { 15, "This Organization"}, + { 18, "SYSTEM"}, + { 19, "Local Service"}, + { 20, "Network Service"}, + { 0, NULL}}; + +static struct sid_name_map_info special_domains[] = { + { &global_sid_World_Domain, "", everyone_users }, + { &global_sid_Creator_Owner_Domain, "", creator_owner_users }, + { &global_sid_NT_Authority, "NT Authority", nt_authority_users }, + { NULL, NULL, NULL }}; + +bool sid_check_is_wellknown_domain(const DOM_SID *sid, const char **name) +{ + int i; + + for (i=0; special_domains[i].sid != NULL; i++) { + if (sid_equal(sid, special_domains[i].sid)) { + if (name != NULL) { + *name = special_domains[i].name; + } + return True; + } + } + return False; +} + +bool sid_check_is_in_wellknown_domain(const DOM_SID *sid) +{ + DOM_SID dom_sid; + uint32 rid; + + sid_copy(&dom_sid, sid); + sid_split_rid(&dom_sid, &rid); + + return sid_check_is_wellknown_domain(&dom_sid, NULL); +} + +/************************************************************************** + Looks up a known username from one of the known domains. +***************************************************************************/ + +bool lookup_wellknown_sid(TALLOC_CTX *mem_ctx, const DOM_SID *sid, + const char **domain, const char **name) +{ + int i; + DOM_SID dom_sid; + uint32 rid; + const struct rid_name_map *users = NULL; + + sid_copy(&dom_sid, sid); + if (!sid_split_rid(&dom_sid, &rid)) { + DEBUG(2, ("Could not split rid from SID\n")); + return False; + } + + for (i=0; special_domains[i].sid != NULL; i++) { + if (sid_equal(&dom_sid, special_domains[i].sid)) { + *domain = talloc_strdup(mem_ctx, + special_domains[i].name); + users = special_domains[i].known_users; + break; + } + } + + if (users == NULL) { + DEBUG(10, ("SID %s is no special sid\n", sid_string_dbg(sid))); + return False; + } + + for (i=0; users[i].name != NULL; i++) { + if (rid == users[i].rid) { + *name = talloc_strdup(mem_ctx, users[i].name); + return True; + } + } + + DEBUG(10, ("RID of special SID %s not found\n", sid_string_dbg(sid))); + + return False; +} + +/************************************************************************** + Try and map a name to one of the well known SIDs. +***************************************************************************/ + +bool lookup_wellknown_name(TALLOC_CTX *mem_ctx, const char *name, + DOM_SID *sid, const char **domain) +{ + int i, j; + + DEBUG(10,("map_name_to_wellknown_sid: looking up %s\n", name)); + + for (i=0; special_domains[i].sid != NULL; i++) { + const struct rid_name_map *users = + special_domains[i].known_users; + + if (users == NULL) + continue; + + for (j=0; users[j].name != NULL; j++) { + if ( strequal(users[j].name, name) ) { + sid_copy(sid, special_domains[i].sid); + sid_append_rid(sid, users[j].rid); + *domain = talloc_strdup( + mem_ctx, special_domains[i].name); + return True; + } + } + } + + return False; +} |