/* Unix SMB/CIFS implementation. Winbind cache backend functions Copyright (C) Andrew Tridgell 2001 Copyright (C) Gerald Carter 2003-2007 Copyright (C) Volker Lendecke 2005 Copyright (C) Guenther Deschner 2005 Copyright (C) Michael Adam 2007 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 . */ #include "includes.h" #include "system/filesys.h" #include "winbindd.h" #include "tdb_validate.h" #include "../libcli/auth/libcli_auth.h" #include "../librpc/gen_ndr/ndr_winbind.h" #include "ads.h" #include "nss_info.h" #include "../libcli/security/security.h" #include "passdb/machine_sid.h" #include "util_tdb.h" #include "libsmb/samlogon_cache.h" #include "lib/namemap_cache.h" #include "lib/util/string_wrappers.h" #include "lib/crypto/gnutls_helpers.h" #include #undef DBGC_CLASS #define DBGC_CLASS DBGC_WINBIND #define WINBINDD_CACHE_VER1 1 /* initial db version */ #define WINBINDD_CACHE_VER2 2 /* second version with timeouts for NDR entries */ #define WINBINDD_CACHE_VERSION WINBINDD_CACHE_VER2 #define WINBINDD_CACHE_VERSION_KEYSTR "WINBINDD_CACHE_VERSION" extern struct winbindd_methods reconnect_methods; #ifdef HAVE_ADS extern struct winbindd_methods reconnect_ads_methods; #endif extern struct winbindd_methods builtin_passdb_methods; extern struct winbindd_methods sam_passdb_methods; static void wcache_flush_cache(void); static bool opt_nocache = False; /* * JRA. KEEP THIS LIST UP TO DATE IF YOU ADD CACHE ENTRIES. * Here are the list of entry types that are *not* stored * as form struct cache_entry in the cache. */ static const char *non_centry_keys[] = { "SEQNUM/", "WINBINDD_OFFLINE", WINBINDD_CACHE_VERSION_KEYSTR, NULL }; bool winbindd_use_idmap_cache(void) { return !opt_nocache; } bool winbindd_use_cache(void) { return !opt_nocache; } void winbindd_set_use_cache(bool use_cache) { opt_nocache = !use_cache; } void winbindd_flush_caches(void) { /* We need to invalidate cached user list entries on a SIGHUP otherwise cached access denied errors due to restrict anonymous hang around until the sequence number changes. */ if (!wcache_invalidate_cache()) { DBG_ERR("invalidating the cache failed; revalidate the cache\n"); if (!winbindd_cache_validate_and_initialize()) { exit(1); } } } /************************************************************************ Is this key a non-centry type ? ************************************************************************/ static bool is_non_centry_key(TDB_DATA kbuf) { int i; if (kbuf.dptr == NULL || kbuf.dsize == 0) { return false; } for (i = 0; non_centry_keys[i] != NULL; i++) { size_t namelen = strlen(non_centry_keys[i]); if (kbuf.dsize < namelen) { continue; } if (strncmp(non_centry_keys[i], (const char *)kbuf.dptr, namelen) == 0) { return true; } } return false; } struct winbind_cache { TDB_CONTEXT *tdb; }; struct cache_entry { NTSTATUS status; uint32_t sequence_number; uint64_t timeout; uint8_t *data; uint32_t len, ofs; }; void (*smb_panic_fn)(const char *const why) = smb_panic; static struct winbind_cache *wcache; static char *wcache_path(void) { /* * Data needs to be kept persistent in state directory for * running with "winbindd offline logon". */ return state_path(talloc_tos(), "winbindd_cache.tdb"); } static void winbindd_domain_init_backend(struct winbindd_domain *domain) { if (domain->backend != NULL) { return; } if (domain->internal) { domain->backend = &builtin_passdb_methods; } if (dom_sid_equal(&domain->sid, &global_sid_Builtin)) { domain->initialized = true; } if (strequal(domain->name, get_global_sam_name()) && sid_check_is_our_sam(&domain->sid)) { domain->backend = &sam_passdb_methods; } if (!domain->initialized) { /* We do not need a connection to an RW DC for cache operation */ init_dc_connection(domain, false); } #ifdef HAVE_ADS if (domain->backend == NULL) { struct winbindd_domain *our_domain = domain; /* find our domain first so we can figure out if we are joined to a kerberized domain */ if (!domain->primary) { our_domain = find_our_domain(); } if ((our_domain->active_directory || IS_DC) && domain->active_directory && !lp_winbind_rpc_only()) { DBG_INFO("Setting ADS methods for domain %s\n", domain->name); domain->backend = &reconnect_ads_methods; } } #endif /* HAVE_ADS */ if (domain->backend == NULL) { DBG_INFO("Setting MS-RPC methods for domain %s\n", domain->name); domain->backend = &reconnect_methods; } } /* get the winbind_cache structure */ static struct winbind_cache *get_cache(struct winbindd_domain *domain) { struct winbind_cache *ret = wcache; winbindd_domain_init_backend(domain); if (ret != NULL) { return ret; } ret = SMB_XMALLOC_P(struct winbind_cache); ZERO_STRUCTP(ret); wcache = ret; wcache_flush_cache(); return ret; } /* free a centry structure */ static void centry_free(struct cache_entry *centry) { if (!centry) return; SAFE_FREE(centry->data); free(centry); } static bool centry_check_bytes(struct cache_entry *centry, size_t nbytes) { if (centry->len - centry->ofs < nbytes) { DBG_ERR("centry corruption? needed %u bytes, have %d\n", (unsigned int)nbytes, centry->len - centry->ofs); return false; } return true; } /* pull a uint64_t from a cache entry */ static uint64_t centry_uint64_t(struct cache_entry *centry) { uint64_t ret; if (!centry_check_bytes(centry, 8)) { smb_panic_fn("centry_uint64_t"); } ret = BVAL(centry->data, centry->ofs); centry->ofs += 8; return ret; } /* pull a uint32_t from a cache entry */ static uint32_t centry_uint32(struct cache_entry *centry) { uint32_t ret; if (!centry_check_bytes(centry, 4)) { smb_panic_fn("centry_uint32"); } ret = IVAL(centry->data, centry->ofs); centry->ofs += 4; return ret; } /* pull a uint16_t from a cache entry */ static uint16_t centry_uint16(struct cache_entry *centry) { uint16_t ret; if (!centry_check_bytes(centry, 2)) { smb_panic_fn("centry_uint16"); } ret = SVAL(centry->data, centry->ofs); centry->ofs += 2; return ret; } /* pull a uint8_t from a cache entry */ static uint8_t centry_uint8(struct cache_entry *centry) { uint8_t ret; if (!centry_check_bytes(centry, 1)) { smb_panic_fn("centry_uint8"); } ret = CVAL(centry->data, centry->ofs); centry->ofs += 1; return ret; } /* pull a NTTIME from a cache entry */ static NTTIME centry_nttime(struct cache_entry *centry) { NTTIME ret; if (!centry_check_bytes(centry, 8)) { smb_panic_fn("centry_nttime"); } ret = IVAL(centry->data, centry->ofs); centry->ofs += 4; ret += (uint64_t)IVAL(centry->data, centry->ofs) << 32; centry->ofs += 4; return ret; } /* pull a time_t from a cache entry. time_t stored portably as a 64-bit time. */ static time_t centry_time(struct cache_entry *centry) { return (time_t)centry_nttime(centry); } /* pull a string from a cache entry, using the supplied talloc context */ static char *centry_string(struct cache_entry *centry, TALLOC_CTX *mem_ctx) { uint32_t len; char *ret; len = centry_uint8(centry); if (len == 0xFF) { /* a deliberate NULL string */ return NULL; } if (!centry_check_bytes(centry, (size_t)len)) { smb_panic_fn("centry_string"); } ret = talloc_array(mem_ctx, char, len+1); if (!ret) { smb_panic_fn("centry_string out of memory\n"); } memcpy(ret,centry->data + centry->ofs, len); ret[len] = 0; centry->ofs += len; return ret; } /* pull a hash16 from a cache entry, using the supplied talloc context */ static char *centry_hash16(struct cache_entry *centry, TALLOC_CTX *mem_ctx) { uint32_t len; char *ret; len = centry_uint8(centry); if (len != 16) { DBG_ERR("centry corruption? hash len (%u) != 16\n", len ); return NULL; } if (!centry_check_bytes(centry, 16)) { return NULL; } ret = talloc_array(mem_ctx, char, 16); if (!ret) { smb_panic_fn("centry_hash out of memory\n"); } memcpy(ret,centry->data + centry->ofs, 16); centry->ofs += 16; return ret; } /* pull a sid from a cache entry, using the supplied talloc context */ static bool centry_sid(struct cache_entry *centry, struct dom_sid *sid) { char *sid_string; bool ret; sid_string = centry_string(centry, talloc_tos()); if (sid_string == NULL) { return false; } ret = string_to_sid(sid, sid_string); TALLOC_FREE(sid_string); return ret; } /* pull a NTSTATUS from a cache entry */ static NTSTATUS centry_ntstatus(struct cache_entry *centry) { NTSTATUS status; status = NT_STATUS(centry_uint32(centry)); return status; } /* the server is considered down if it can't give us a sequence number */ static bool wcache_server_down(struct winbindd_domain *domain) { bool ret; if (!wcache->tdb) return false; ret = (domain->sequence_number == DOM_SEQUENCE_NONE); if (ret) DBG_DEBUG("wcache_server_down: server for Domain %s down\n", domain->name ); return ret; } struct wcache_seqnum_state { uint32_t *seqnum; uint32_t *last_seq_check; }; static int wcache_seqnum_parser(TDB_DATA key, TDB_DATA data, void *private_data) { struct wcache_seqnum_state *state = private_data; if (data.dsize != 8) { DBG_DEBUG("wcache_fetch_seqnum: invalid data size %d\n", (int)data.dsize); return -1; } *state->seqnum = IVAL(data.dptr, 0); *state->last_seq_check = IVAL(data.dptr, 4); return 0; } static bool wcache_fetch_seqnum(const char *domain_name, uint32_t *seqnum, uint32_t *last_seq_check) { struct wcache_seqnum_state state = { .seqnum = seqnum, .last_seq_check = last_seq_check }; size_t len = strlen(domain_name); char keystr[len+8]; TDB_DATA key = { .dptr = (uint8_t *)keystr, .dsize = sizeof(keystr) }; int ret; if (wcache->tdb == NULL) { DBG_DEBUG("wcache_fetch_seqnum: tdb == NULL\n"); return false; } snprintf(keystr, sizeof(keystr), "SEQNUM/%s", domain_name); ret = tdb_parse_record(wcache->tdb, key, wcache_seqnum_parser, &state); return (ret == 0); } static NTSTATUS fetch_cache_seqnum( struct winbindd_domain *domain, time_t now ) { uint32_t last_check, time_diff; if (!wcache_fetch_seqnum(domain->name, &domain->sequence_number, &last_check)) { return NT_STATUS_UNSUCCESSFUL; } domain->last_seq_check = last_check; /* have we expired? */ time_diff = now - domain->last_seq_check; if ((int)time_diff > lp_winbind_cache_time()) { DBG_DEBUG("fetch_cache_seqnum: timeout [%s][%u @ %u]\n", domain->name, domain->sequence_number, (uint32_t)domain->last_seq_check); return NT_STATUS_UNSUCCESSFUL; } DBG_DEBUG("fetch_cache_seqnum: success [%s][%u @ %u]\n", domain->name, domain->sequence_number, (uint32_t)domain->last_seq_check); return NT_STATUS_OK; } bool wcache_store_seqnum(const char *domain_name, uint32_t seqnum, time_t last_seq_check) { size_t len = strlen(domain_name); char keystr[len+8]; TDB_DATA key = { .dptr = (uint8_t *)keystr, .dsize = sizeof(keystr) }; uint8_t buf[8]; int ret; if (wcache->tdb == NULL) { DBG_DEBUG("wcache_store_seqnum: wcache->tdb == NULL\n"); return false; } snprintf(keystr, sizeof(keystr), "SEQNUM/%s", domain_name); SIVAL(buf, 0, seqnum); SIVAL(buf, 4, last_seq_check); ret = tdb_store(wcache->tdb, key, make_tdb_data(buf, sizeof(buf)), TDB_REPLACE); if (ret != 0) { DBG_DEBUG("tdb_store_bystring failed: %s\n", tdb_errorstr(wcache->tdb)); return false; } DBG_DEBUG("wcache_store_seqnum: success [%s][%u @ %u]\n", domain_name, seqnum, (unsigned)last_seq_check); return true; } static bool store_cache_seqnum( struct winbindd_domain *domain ) { return wcache_store_seqnum(domain->name, domain->sequence_number, domain->last_seq_check); } /* refresh the domain sequence number on timeout. */ static void refresh_sequence_number(struct winbindd_domain *domain) { NTSTATUS status; unsigned time_diff; time_t t = time(NULL); unsigned cache_time = lp_winbind_cache_time(); if (is_domain_offline(domain)) { return; } get_cache( domain ); time_diff = t - domain->last_seq_check; /* see if we have to refetch the domain sequence number */ if ((time_diff < cache_time) && (domain->sequence_number != DOM_SEQUENCE_NONE) && NT_STATUS_IS_OK(domain->last_status)) { DBG_DEBUG("refresh_sequence_number: %s time ok\n", domain->name); goto done; } /* try to get the sequence number from the tdb cache first */ /* this will update the timestamp as well */ status = fetch_cache_seqnum( domain, t ); if (NT_STATUS_IS_OK(status) && (domain->sequence_number != DOM_SEQUENCE_NONE) && NT_STATUS_IS_OK(domain->last_status)) { goto done; } /* just use the current time */ domain->last_status = NT_STATUS_OK; domain->sequence_number = time(NULL); domain->last_seq_check = time(NULL); /* save the new sequence number in the cache */ store_cache_seqnum( domain ); done: DBG_DEBUG("refresh_sequence_number: %s seq number is now %d\n", domain->name, domain->sequence_number); return; } /* decide if a cache entry has expired */ static bool centry_expired(struct winbindd_domain *domain, const char *keystr, struct cache_entry *centry) { /* If we've been told to be offline - stay in that state... */ if (lp_winbind_offline_logon() && get_global_winbindd_state_offline()) { DBG_DEBUG("centry_expired: Key %s for domain %s valid as winbindd is globally offline.\n", keystr, domain->name ); return false; } /* when the domain is offline return the cached entry. * This deals with transient offline states... */ if (!domain->online) { DBG_DEBUG("centry_expired: Key %s for domain %s valid as domain is offline.\n", keystr, domain->name ); return false; } /* if the server is OK and our cache entry came from when it was down then the entry is invalid */ if ((domain->sequence_number != DOM_SEQUENCE_NONE) && (centry->sequence_number == DOM_SEQUENCE_NONE)) { DBG_DEBUG("centry_expired: Key %s for domain %s invalid sequence.\n", keystr, domain->name ); return true; } /* if the server is down or the cache entry is not older than the current sequence number or it did not timeout then it is OK */ if (wcache_server_down(domain) || ((centry->sequence_number == domain->sequence_number) && ((time_t)centry->timeout > time(NULL)))) { DBG_DEBUG("centry_expired: Key %s for domain %s is good.\n", keystr, domain->name ); return false; } DBG_DEBUG("centry_expired: Key %s for domain %s expired\n", keystr, domain->name ); /* it's expired */ return true; } static struct cache_entry *wcache_fetch_raw(char *kstr) { TDB_DATA data; struct cache_entry *centry; TDB_DATA key; key = string_tdb_data(kstr); data = tdb_fetch(wcache->tdb, key); if (!data.dptr) { /* a cache miss */ return NULL; } centry = SMB_XMALLOC_P(struct cache_entry); centry->data = (unsigned char *)data.dptr; centry->len = data.dsize; centry->ofs = 0; if (centry->len < 16) { /* huh? corrupt cache? */ DBG_DEBUG("wcache_fetch_raw: Corrupt cache for key %s " "(len < 16)?\n", kstr); centry_free(centry); return NULL; } centry->status = centry_ntstatus(centry); centry->sequence_number = centry_uint32(centry); centry->timeout = centry_uint64_t(centry); return centry; } static bool is_my_own_sam_domain(struct winbindd_domain *domain) { if (strequal(domain->name, get_global_sam_name()) && sid_check_is_our_sam(&domain->sid)) { return true; } return false; } static bool is_builtin_domain(struct winbindd_domain *domain) { if (strequal(domain->name, "BUILTIN") && sid_check_is_builtin(&domain->sid)) { return true; } return false; } /* fetch an entry from the cache, with a varargs key. auto-fetch the sequence number and return status */ static struct cache_entry *wcache_fetch(struct winbind_cache *cache, struct winbindd_domain *domain, const char *format, ...) PRINTF_ATTRIBUTE(3,4); static struct cache_entry *wcache_fetch(struct winbind_cache *cache, struct winbindd_domain *domain, const char *format, ...) { va_list ap; char *kstr; struct cache_entry *centry; int ret; if (!winbindd_use_cache() || is_my_own_sam_domain(domain) || is_builtin_domain(domain)) { return NULL; } refresh_sequence_number(domain); va_start(ap, format); ret = vasprintf(&kstr, format, ap); va_end(ap); if (ret == -1) { return NULL; } centry = wcache_fetch_raw(kstr); if (centry == NULL) { free(kstr); return NULL; } if (centry_expired(domain, kstr, centry)) { DBG_DEBUG("wcache_fetch: entry %s expired for domain %s\n", kstr, domain->name ); centry_free(centry); free(kstr); return NULL; } DBG_DEBUG("wcache_fetch: returning entry %s for domain %s\n", kstr, domain->name ); free(kstr); return centry; } static void wcache_delete(const char *format, ...) PRINTF_ATTRIBUTE(1,2); static void wcache_delete(const char *format, ...) { va_list ap; char *kstr; TDB_DATA key; int ret; va_start(ap, format); ret = vasprintf(&kstr, format, ap); va_end(ap); if (ret == -1) { return; } key = string_tdb_data(kstr); tdb_delete(wcache->tdb, key); free(kstr); } /* make sure we have at least len bytes available in a centry */ static void centry_expand(struct cache_entry *centry, uint32_t len) { if (centry->len - centry->ofs >= len) return; centry->len *= 2; centry->data = SMB_REALLOC_ARRAY(centry->data, unsigned char, centry->len); if (!centry->data) { DBG_ERR("out of memory: needed %d bytes in centry_expand\n", centry->len); smb_panic_fn("out of memory in centry_expand"); } } /* push a uint64_t into a centry */ static void centry_put_uint64_t(struct cache_entry *centry, uint64_t v) { centry_expand(centry, 8); SBVAL(centry->data, centry->ofs, v); centry->ofs += 8; } /* push a uint32_t into a centry */ static void centry_put_uint32(struct cache_entry *centry, uint32_t v) { centry_expand(centry, 4); SIVAL(centry->data, centry->ofs, v); centry->ofs += 4; } /* push a uint16_t into a centry */ static void centry_put_uint16(struct cache_entry *centry, uint16_t v) { centry_expand(centry, 2); SSVAL(centry->data, centry->ofs, v); centry->ofs += 2; } /* push a uint8_t into a centry */ static void centry_put_uint8(struct cache_entry *centry, uint8_t v) { centry_expand(centry, 1); SCVAL(centry->data, centry->ofs, v); centry->ofs += 1; } /* push a string into a centry */ static void centry_put_string(struct cache_entry *centry, const char *s) { int len; if (!s) { /* null strings are marked as len 0xFFFF */ centry_put_uint8(centry, 0xFF); return; } len = strlen(s); /* can't handle more than 254 char strings. Truncating is probably best */ if (len > 254) { DBG_DEBUG("centry_put_string: truncating len (%d) to: 254\n", len); len = 254; } centry_put_uint8(centry, len); centry_expand(centry, len); memcpy(centry->data + centry->ofs, s, len); centry->ofs += len; } /* push a 16 byte hash into a centry - treat as 16 byte string. */ static void centry_put_hash16(struct cache_entry *centry, const uint8_t val[16]) { centry_put_uint8(centry, 16); centry_expand(centry, 16); memcpy(centry->data + centry->ofs, val, 16); centry->ofs += 16; } static void centry_put_sid(struct cache_entry *centry, const struct dom_sid *sid) { struct dom_sid_buf sid_string; centry_put_string(centry, dom_sid_str_buf(sid, &sid_string)); } /* put NTSTATUS into a centry */ static void centry_put_ntstatus(struct cache_entry *centry, NTSTATUS status) { uint32_t status_value = NT_STATUS_V(status); centry_put_uint32(centry, status_value); } /* push a NTTIME into a centry */ static void centry_put_nttime(struct cache_entry *centry, NTTIME nt) { centry_expand(centry, 8); SIVAL(centry->data, centry->ofs, nt & 0xFFFFFFFF); centry->ofs += 4; SIVAL(centry->data, centry->ofs, nt >> 32); centry->ofs += 4; } /* push a time_t into a centry - use a 64 bit size. NTTIME here is being used as a convenient 64-bit size. */ static void centry_put_time(struct cache_entry *centry, time_t t) { NTTIME nt = (NTTIME)t; centry_put_nttime(centry, nt); } /* start a centry for output. When finished, call centry_end() */ static struct cache_entry *centry_start(struct winbindd_domain *domain, NTSTATUS status) { struct cache_entry *centry; if (!wcache->tdb) return NULL; centry = SMB_XMALLOC_P(struct cache_entry); centry->len = 8192; /* reasonable default */ centry->data = SMB_XMALLOC_ARRAY(uint8_t, centry->len); centry->ofs = 0; centry->sequence_number = domain->sequence_number; centry->timeout = lp_winbind_cache_time() + time(NULL); centry_put_ntstatus(centry, status); centry_put_uint32(centry, centry->sequence_number); centry_put_uint64_t(centry, centry->timeout); return centry; } /* finish a centry and write it to the tdb */ static void centry_end(struct cache_entry *centry, const char *format, ...) PRINTF_ATTRIBUTE(2,3); static void centry_end(struct cache_entry *centry, const char *format, ...) { va_list ap; char *kstr; TDB_DATA key, data; int ret; if (!winbindd_use_cache()) { return; } va_start(ap, format); ret = vasprintf(&kstr, format, ap); va_end(ap); if (ret == -1) { return; } key = string_tdb_data(kstr); data.dptr = centry->data; data.dsize = centry->ofs; tdb_store(wcache->tdb, key, data, TDB_REPLACE); free(kstr); } static void wcache_save_name_to_sid(struct winbindd_domain *domain, NTSTATUS status, const char *domain_name, const char *name, const struct dom_sid *sid, enum lsa_SidType type) { bool ok; ok = namemap_cache_set_name2sid(domain_name, name, sid, type, time(NULL) + lp_winbind_cache_time()); if (!ok) { DBG_DEBUG("namemap_cache_set_name2sid failed\n"); } /* * Don't store the reverse mapping. The name came from user * input, and we might not have the correct capitalization, * which is important for nsswitch. */ } static void wcache_save_sid_to_name(struct winbindd_domain *domain, NTSTATUS status, const struct dom_sid *sid, const char *domain_name, const char *name, enum lsa_SidType type) { bool ok; ok = namemap_cache_set_sid2name(sid, domain_name, name, type, time(NULL) + lp_winbind_cache_time()); if (!ok) { DBG_DEBUG("namemap_cache_set_sid2name failed\n"); } if (type != SID_NAME_UNKNOWN) { ok = namemap_cache_set_name2sid( domain_name, name, sid, type, time(NULL) + lp_winbind_cache_time()); if (!ok) { DBG_DEBUG("namemap_cache_set_name2sid failed\n"); } } } static void wcache_save_lockout_policy(struct winbindd_domain *domain, NTSTATUS status, struct samr_DomInfo12 *lockout_policy) { struct cache_entry *centry; centry = centry_start(domain, status); if (!centry) return; centry_put_nttime(centry, lockout_policy->lockout_duration); centry_put_nttime(centry, lockout_policy->lockout_window); centry_put_uint16(centry, lockout_policy->lockout_threshold); centry_end(centry, "LOC_POL/%s", domain->name); DBG_DEBUG("wcache_save_lockout_policy: %s\n", domain->name); centry_free(centry); } static void wcache_save_password_policy(struct winbindd_domain *domain, NTSTATUS status, struct samr_DomInfo1 *policy) { struct cache_entry *centry; centry = centry_start(domain, status); if (!centry) return; centry_put_uint16(centry, policy->min_password_length); centry_put_uint16(centry, policy->password_history_length); centry_put_uint32(centry, policy->password_properties); centry_put_nttime(centry, policy->max_password_age); centry_put_nttime(centry, policy->min_password_age); centry_end(centry, "PWD_POL/%s", domain->name); DBG_DEBUG("wcache_save_password_policy: %s\n", domain->name); centry_free(centry); } /*************************************************************************** ***************************************************************************/ static void wcache_save_username_alias(struct winbindd_domain *domain, NTSTATUS status, const char *name, const char *alias) { struct cache_entry *centry; fstring uname; if ( (centry = centry_start(domain, status)) == NULL ) return; centry_put_string( centry, alias ); fstrcpy(uname, name); (void)strupper_m(uname); centry_end(centry, "NSS/NA/%s", uname); DBG_DEBUG("wcache_save_username_alias: %s -> %s\n", name, alias ); centry_free(centry); } static void wcache_save_alias_username(struct winbindd_domain *domain, NTSTATUS status, const char *alias, const char *name) { struct cache_entry *centry; fstring uname; if ( (centry = centry_start(domain, status)) == NULL ) return; centry_put_string( centry, name ); fstrcpy(uname, alias); (void)strupper_m(uname); centry_end(centry, "NSS/AN/%s", uname); DBG_DEBUG("wcache_save_alias_username: %s -> %s\n", alias, name ); centry_free(centry); } /*************************************************************************** ***************************************************************************/ NTSTATUS resolve_username_to_alias( TALLOC_CTX *mem_ctx, struct winbindd_domain *domain, const char *name, char **alias ) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; char *upper_name; if ( domain->internal ) return NT_STATUS_NOT_SUPPORTED; if (!cache->tdb) goto do_query; upper_name = talloc_strdup_upper(mem_ctx, name); if (upper_name == NULL) { return NT_STATUS_NO_MEMORY; } centry = wcache_fetch(cache, domain, "NSS/NA/%s", upper_name); talloc_free(upper_name); if (!centry) goto do_query; status = centry->status; if (!NT_STATUS_IS_OK(status)) { centry_free(centry); return status; } *alias = centry_string( centry, mem_ctx ); centry_free(centry); DBG_DEBUG("resolve_username_to_alias: [Cached] - mapped %s to %s\n", name, *alias ? *alias : "(none)"); return (*alias) ? NT_STATUS_OK : NT_STATUS_OBJECT_NAME_NOT_FOUND; do_query: /* If its not in cache and we are offline, then fail */ if (is_domain_offline(domain)) { DBG_DEBUG("resolve_username_to_alias: rejecting query " "in offline mode\n"); return NT_STATUS_NOT_FOUND; } status = nss_map_to_alias( mem_ctx, domain->name, name, alias ); if ( NT_STATUS_IS_OK( status ) ) { wcache_save_username_alias(domain, status, name, *alias); } if ( NT_STATUS_EQUAL( status, NT_STATUS_NONE_MAPPED ) ) { wcache_save_username_alias(domain, status, name, "(NULL)"); } DBG_INFO("resolve_username_to_alias: backend query returned %s\n", nt_errstr(status)); if ( NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ) { set_domain_offline( domain ); } return status; } /*************************************************************************** ***************************************************************************/ NTSTATUS resolve_alias_to_username( TALLOC_CTX *mem_ctx, struct winbindd_domain *domain, const char *alias, char **name ) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; char *upper_name; if ( domain->internal ) return NT_STATUS_NOT_SUPPORTED; if (!cache->tdb) goto do_query; upper_name = talloc_strdup(mem_ctx, alias); if (upper_name == NULL) { return NT_STATUS_NO_MEMORY; } if (!strupper_m(upper_name)) { talloc_free(upper_name); return NT_STATUS_INVALID_PARAMETER; } centry = wcache_fetch(cache, domain, "NSS/AN/%s", upper_name); talloc_free(upper_name); if (!centry) goto do_query; status = centry->status; if (!NT_STATUS_IS_OK(status)) { centry_free(centry); return status; } *name = centry_string( centry, mem_ctx ); centry_free(centry); DBG_DEBUG("resolve_alias_to_username: [Cached] - mapped %s to %s\n", alias, *name ? *name : "(none)"); return (*name) ? NT_STATUS_OK : NT_STATUS_OBJECT_NAME_NOT_FOUND; do_query: /* If its not in cache and we are offline, then fail */ if (is_domain_offline(domain)) { DBG_DEBUG("resolve_alias_to_username: rejecting query " "in offline mode\n"); return NT_STATUS_NOT_FOUND; } /* an alias cannot contain a domain prefix or '@' */ if (strchr(alias, '\\') || strchr(alias, '@')) { DBG_DEBUG("resolve_alias_to_username: skipping fully " "qualified name %s\n", alias); return NT_STATUS_OBJECT_NAME_INVALID; } status = nss_map_from_alias( mem_ctx, domain->name, alias, name ); if ( NT_STATUS_IS_OK( status ) ) { wcache_save_alias_username( domain, status, alias, *name ); } if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { wcache_save_alias_username(domain, status, alias, "(NULL)"); } DBG_INFO("resolve_alias_to_username: backend query returned %s\n", nt_errstr(status)); if ( NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ) { set_domain_offline( domain ); } return status; } NTSTATUS wcache_cached_creds_exist(struct winbindd_domain *domain, const struct dom_sid *sid) { struct winbind_cache *cache = get_cache(domain); int ret; struct dom_sid_buf tmp; fstring key_str; uint32_t rid; if (!cache->tdb) { return NT_STATUS_INTERNAL_DB_ERROR; } if (is_null_sid(sid)) { return NT_STATUS_INVALID_SID; } if (!(sid_peek_rid(sid, &rid)) || (rid == 0)) { return NT_STATUS_INVALID_SID; } fstr_sprintf(key_str, "CRED/%s", dom_sid_str_buf(sid, &tmp)); ret = tdb_exists(cache->tdb, string_tdb_data(key_str)); if (ret != 1) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } return NT_STATUS_OK; } /* Lookup creds for a SID - copes with old (unsalted) creds as well as new salted ones. */ NTSTATUS wcache_get_creds(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *sid, const uint8_t **cached_nt_pass, const uint8_t **cached_salt) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; uint32_t rid; struct dom_sid_buf sidstr; if (!cache->tdb) { return NT_STATUS_INTERNAL_DB_ERROR; } if (is_null_sid(sid)) { return NT_STATUS_INVALID_SID; } if (!(sid_peek_rid(sid, &rid)) || (rid == 0)) { return NT_STATUS_INVALID_SID; } /* Try and get a salted cred first. If we can't fall back to an unsalted cred. */ centry = wcache_fetch(cache, domain, "CRED/%s", dom_sid_str_buf(sid, &sidstr)); if (!centry) { DBG_DEBUG("wcache_get_creds: entry for [CRED/%s] not found\n", dom_sid_str_buf(sid, &sidstr)); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } /* * We don't use the time element at this moment, * but we have to consume it, so that we don't * need to change the disk format of the cache. */ (void)centry_time(centry); /* In the salted case this isn't actually the nt_hash itself, but the MD5 of the salt + nt_hash. Let the caller sort this out. It can tell as we only return the cached_salt if we are returning a salted cred. */ *cached_nt_pass = (const uint8_t *)centry_hash16(centry, mem_ctx); if (*cached_nt_pass == NULL) { dom_sid_str_buf(sid, &sidstr); /* Bad (old) cred cache. Delete and pretend we don't have it. */ DBG_WARNING("wcache_get_creds: bad entry for [CRED/%s] - deleting\n", sidstr.buf); wcache_delete("CRED/%s", sidstr.buf); centry_free(centry); return NT_STATUS_OBJECT_NAME_NOT_FOUND; } /* We only have 17 bytes more data in the salted cred case. */ if (centry->len - centry->ofs == 17) { *cached_salt = (const uint8_t *)centry_hash16(centry, mem_ctx); } else { *cached_salt = NULL; } dump_data_pw("cached_nt_pass", *cached_nt_pass, NT_HASH_LEN); if (*cached_salt) { dump_data_pw("cached_salt", *cached_salt, NT_HASH_LEN); } status = centry->status; DBG_DEBUG("wcache_get_creds: [Cached] - cached creds for user %s status: %s\n", dom_sid_str_buf(sid, &sidstr), nt_errstr(status) ); centry_free(centry); return status; } /* Store creds for a SID - only writes out new salted ones. */ NTSTATUS wcache_save_creds(struct winbindd_domain *domain, const struct dom_sid *sid, const uint8_t nt_pass[NT_HASH_LEN]) { struct cache_entry *centry; struct dom_sid_buf sid_str; uint32_t rid; uint8_t cred_salt[NT_HASH_LEN]; uint8_t salted_hash[NT_HASH_LEN]; gnutls_hash_hd_t hash_hnd = NULL; int rc; if (is_null_sid(sid)) { return NT_STATUS_INVALID_SID; } if (!(sid_peek_rid(sid, &rid)) || (rid == 0)) { return NT_STATUS_INVALID_SID; } centry = centry_start(domain, NT_STATUS_OK); if (!centry) { return NT_STATUS_INTERNAL_DB_ERROR; } dump_data_pw("nt_pass", nt_pass, NT_HASH_LEN); centry_put_time(centry, time(NULL)); /* Create a salt and then salt the hash. */ generate_random_buffer(cred_salt, NT_HASH_LEN); rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); if (rc < 0) { centry_free(centry); return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); } rc = gnutls_hash(hash_hnd, cred_salt, 16); if (rc < 0) { gnutls_hash_deinit(hash_hnd, NULL); centry_free(centry); return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); } rc = gnutls_hash(hash_hnd, nt_pass, 16); if (rc < 0) { gnutls_hash_deinit(hash_hnd, NULL); centry_free(centry); return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); } gnutls_hash_deinit(hash_hnd, salted_hash); centry_put_hash16(centry, salted_hash); centry_put_hash16(centry, cred_salt); centry_end(centry, "CRED/%s", dom_sid_str_buf(sid, &sid_str)); DBG_DEBUG("wcache_save_creds: %s\n", sid_str.buf); centry_free(centry); return NT_STATUS_OK; } /* Query display info. This is the basic user list fn */ NTSTATUS wb_cache_query_user_list(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, uint32_t **prids) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; uint32_t num_rids = 0; uint32_t *rids = NULL; NTSTATUS status; unsigned int i, retry; bool old_status = domain->online; *prids = NULL; if (!cache->tdb) goto do_query; centry = wcache_fetch(cache, domain, "UL/%s", domain->name); if (!centry) goto do_query; do_fetch_cache: num_rids = centry_uint32(centry); if (num_rids == 0) { goto do_cached; } rids = talloc_array(mem_ctx, uint32_t, num_rids); if (rids == NULL) { centry_free(centry); return NT_STATUS_NO_MEMORY; } for (i=0; istatus; DBG_DEBUG("query_user_list: [Cached] - cached list for domain %s status: %s\n", domain->name, nt_errstr(status) ); centry_free(centry); return status; do_query: /* Put the query_user_list() in a retry loop. There appears to be * some bug either with Windows 2000 or Samba's handling of large * rpc replies. This manifests itself as sudden disconnection * at a random point in the enumeration of a large (60k) user list. * The retry loop simply tries the operation again. )-: It's not * pretty but an acceptable workaround until we work out what the * real problem is. */ retry = 0; do { DBG_DEBUG("query_user_list: [Cached] - doing backend query for list for domain %s\n", domain->name ); rids = NULL; status = domain->backend->query_user_list(domain, mem_ctx, &rids); num_rids = talloc_array_length(rids); if (!NT_STATUS_IS_OK(status)) { DBG_NOTICE("query_user_list: returned 0x%08x, " "retrying\n", NT_STATUS_V(status)); } reset_cm_connection_on_error(domain, NULL, status); if (NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) { DBG_NOTICE("query_user_list: flushing " "connection cache\n"); invalidate_cm_connection(domain); } if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } /* store partial response. */ if (num_rids > 0) { /* * humm, what about the status used for cache? * Should it be NT_STATUS_OK? */ break; } /* * domain is offline now, and there is no user entries, * try to fetch from cache again. */ if (cache->tdb && !domain->online && !domain->internal && old_status) { centry = wcache_fetch(cache, domain, "UL/%s", domain->name); /* partial response... */ if (!centry) { goto skip_save; } else { goto do_fetch_cache; } } else { goto skip_save; } } } while (NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_UNSUCCESSFUL) && (retry++ < 5)); /* and save it */ refresh_sequence_number(domain); if (!NT_STATUS_IS_OK(status)) { return status; } centry = centry_start(domain, status); if (!centry) goto skip_save; centry_put_uint32(centry, num_rids); for (i=0; iname); centry_free(centry); *prids = rids; skip_save: return status; } /* list all domain groups */ NTSTATUS wb_cache_enum_dom_groups(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, uint32_t *num_entries, struct wb_acct_info **info) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; unsigned int i; bool old_status; old_status = domain->online; if (!cache->tdb) goto do_query; centry = wcache_fetch(cache, domain, "GL/%s/domain", domain->name); if (!centry) goto do_query; do_fetch_cache: *num_entries = centry_uint32(centry); if (*num_entries == 0) goto do_cached; (*info) = talloc_array(mem_ctx, struct wb_acct_info, *num_entries); if (! (*info)) { smb_panic_fn("enum_dom_groups out of memory"); } for (i=0; i<(*num_entries); i++) { (*info)[i].acct_name = centry_string(centry, (*info)); (*info)[i].acct_desc = centry_string(centry, (*info)); (*info)[i].rid = centry_uint32(centry); } do_cached: status = centry->status; DBG_DEBUG("enum_dom_groups: [Cached] - cached list for domain %s status: %s\n", domain->name, nt_errstr(status) ); centry_free(centry); return status; do_query: *num_entries = 0; *info = NULL; DBG_DEBUG("enum_dom_groups: [Cached] - doing backend query for list for domain %s\n", domain->name ); status = domain->backend->enum_dom_groups(domain, mem_ctx, num_entries, info); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (cache->tdb && !domain->online && !domain->internal && old_status) { centry = wcache_fetch(cache, domain, "GL/%s/domain", domain->name); if (centry) { goto do_fetch_cache; } } } /* and save it */ refresh_sequence_number(domain); if (!NT_STATUS_IS_OK(status)) { return status; } centry = centry_start(domain, status); if (!centry) goto skip_save; centry_put_uint32(centry, *num_entries); for (i=0; i<(*num_entries); i++) { centry_put_string(centry, (*info)[i].acct_name); centry_put_string(centry, (*info)[i].acct_desc); centry_put_uint32(centry, (*info)[i].rid); } centry_end(centry, "GL/%s/domain", domain->name); centry_free(centry); skip_save: return status; } /* list all domain groups */ NTSTATUS wb_cache_enum_local_groups(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, uint32_t *num_entries, struct wb_acct_info **info) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; unsigned int i; bool old_status; old_status = domain->online; if (!cache->tdb) goto do_query; centry = wcache_fetch(cache, domain, "GL/%s/local", domain->name); if (!centry) goto do_query; do_fetch_cache: *num_entries = centry_uint32(centry); if (*num_entries == 0) goto do_cached; (*info) = talloc_array(mem_ctx, struct wb_acct_info, *num_entries); if (! (*info)) { smb_panic_fn("enum_dom_groups out of memory"); } for (i=0; i<(*num_entries); i++) { (*info)[i].acct_name = centry_string(centry, (*info)); (*info)[i].acct_desc = centry_string(centry, (*info)); (*info)[i].rid = centry_uint32(centry); } do_cached: /* If we are returning cached data and the domain controller is down then we don't know whether the data is up to date or not. Return NT_STATUS_MORE_PROCESSING_REQUIRED to indicate this. */ if (wcache_server_down(domain)) { DBG_DEBUG("enum_local_groups: returning cached user list and server was down\n"); status = NT_STATUS_MORE_PROCESSING_REQUIRED; } else status = centry->status; DBG_DEBUG("enum_local_groups: [Cached] - cached list for domain %s status: %s\n", domain->name, nt_errstr(status) ); centry_free(centry); return status; do_query: *num_entries = 0; *info = NULL; DBG_DEBUG("enum_local_groups: [Cached] - doing backend query for list for domain %s\n", domain->name ); status = domain->backend->enum_local_groups(domain, mem_ctx, num_entries, info); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (cache->tdb && !domain->internal && !domain->online && old_status) { centry = wcache_fetch(cache, domain, "GL/%s/local", domain->name); if (centry) { goto do_fetch_cache; } } } /* and save it */ refresh_sequence_number(domain); if (!NT_STATUS_IS_OK(status)) { return status; } centry = centry_start(domain, status); if (!centry) goto skip_save; centry_put_uint32(centry, *num_entries); for (i=0; i<(*num_entries); i++) { centry_put_string(centry, (*info)[i].acct_name); centry_put_string(centry, (*info)[i].acct_desc); centry_put_uint32(centry, (*info)[i].rid); } centry_end(centry, "GL/%s/local", domain->name); centry_free(centry); skip_save: return status; } struct wcache_name_to_sid_state { struct dom_sid *sid; enum lsa_SidType *type; bool offline; bool found; }; static void wcache_name_to_sid_fn(const struct dom_sid *sid, enum lsa_SidType type, bool expired, void *private_data) { struct wcache_name_to_sid_state *state = private_data; *state->sid = *sid; *state->type = type; state->found = (!expired || state->offline); } static NTSTATUS wcache_name_to_sid(struct winbindd_domain *domain, const char *domain_name, const char *name, struct dom_sid *sid, enum lsa_SidType *type) { struct wcache_name_to_sid_state state = { .sid = sid, .type = type, .found = false, .offline = is_domain_offline(domain), }; bool ok; ok = namemap_cache_find_name(domain_name, name, wcache_name_to_sid_fn, &state); if (!ok) { DBG_DEBUG("namemap_cache_find_name failed\n"); return NT_STATUS_NOT_FOUND; } if (!state.found) { DBG_DEBUG("cache entry not found\n"); return NT_STATUS_NOT_FOUND; } if (*type == SID_NAME_UNKNOWN) { return NT_STATUS_NONE_MAPPED; } return NT_STATUS_OK; } /* convert a single name to a sid in a domain */ NTSTATUS wb_cache_name_to_sid(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const char *domain_name, const char *name, uint32_t flags, struct dom_sid *sid, enum lsa_SidType *type) { NTSTATUS status; bool old_status; const char *dom_name; old_status = domain->online; status = wcache_name_to_sid(domain, domain_name, name, sid, type); if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { return status; } ZERO_STRUCTP(sid); DBG_DEBUG("name_to_sid: [Cached] - doing backend query for name for domain %s\n", domain->name ); winbindd_domain_init_backend(domain); status = domain->backend->name_to_sid(domain, mem_ctx, domain_name, name, flags, &dom_name, sid, type); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (!domain->internal && !domain->online && old_status) { NTSTATUS cache_status; cache_status = wcache_name_to_sid(domain, domain_name, name, sid, type); return cache_status; } } /* and save it */ if (domain->online && (NT_STATUS_IS_OK(status) || NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))) { enum lsa_SidType save_type = *type; if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { save_type = SID_NAME_UNKNOWN; } wcache_save_name_to_sid(domain, status, domain_name, name, sid, save_type); /* Only save the reverse mapping if this was not a UPN */ if (!strchr(name, '@')) { if (!strupper_m(discard_const_p(char, domain_name))) { return NT_STATUS_INVALID_PARAMETER; } (void)strlower_m(discard_const_p(char, name)); wcache_save_sid_to_name(domain, status, sid, dom_name, name, save_type); } } return status; } struct wcache_sid_to_name_state { TALLOC_CTX *mem_ctx; char **domain_name; char **name; enum lsa_SidType *type; bool offline; bool found; }; static void wcache_sid_to_name_fn(const char *domain, const char *name, enum lsa_SidType type, bool expired, void *private_data) { struct wcache_sid_to_name_state *state = private_data; *state->domain_name = talloc_strdup(state->mem_ctx, domain); if (*state->domain_name == NULL) { return; } *state->name = talloc_strdup(state->mem_ctx, name); if (*state->name == NULL) { return; } *state->type = type; state->found = (!expired || state->offline); } static NTSTATUS wcache_sid_to_name(struct winbindd_domain *domain, const struct dom_sid *sid, TALLOC_CTX *mem_ctx, char **domain_name, char **name, enum lsa_SidType *type) { struct wcache_sid_to_name_state state = { .mem_ctx = mem_ctx, .found = false, .domain_name = domain_name, .name = name, .type = type, .offline = is_domain_offline(domain) }; bool ok; ok = namemap_cache_find_sid(sid, wcache_sid_to_name_fn, &state); if (!ok) { DBG_DEBUG("namemap_cache_find_name failed\n"); return NT_STATUS_NOT_FOUND; } if (!state.found) { DBG_DEBUG("cache entry not found\n"); return NT_STATUS_NOT_FOUND; } if (*type == SID_NAME_UNKNOWN) { return NT_STATUS_NONE_MAPPED; } return NT_STATUS_OK; } /* convert a sid to a user or group name. The sid is guaranteed to be in the domain given */ NTSTATUS wb_cache_sid_to_name(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *sid, char **domain_name, char **name, enum lsa_SidType *type) { NTSTATUS status; bool old_status; old_status = domain->online; status = wcache_sid_to_name(domain, sid, mem_ctx, domain_name, name, type); if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { return status; } *name = NULL; *domain_name = NULL; DBG_DEBUG("sid_to_name: [Cached] - doing backend query for name for domain %s\n", domain->name ); winbindd_domain_init_backend(domain); status = domain->backend->sid_to_name(domain, mem_ctx, sid, domain_name, name, type); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (!domain->internal && !domain->online && old_status) { NTSTATUS cache_status; cache_status = wcache_sid_to_name(domain, sid, mem_ctx, domain_name, name, type); return cache_status; } } /* and save it */ if (!NT_STATUS_IS_OK(status)) { return status; } wcache_save_sid_to_name(domain, status, sid, *domain_name, *name, *type); /* We can't save the name to sid mapping here, as with sid history a * later name2sid would give the wrong sid. */ return status; } NTSTATUS wb_cache_rids_to_names(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *domain_sid, uint32_t *rids, size_t num_rids, char **domain_name, char ***names, enum lsa_SidType **types) { struct winbind_cache *cache = get_cache(domain); size_t i; NTSTATUS result = NT_STATUS_UNSUCCESSFUL; bool have_mapped; bool have_unmapped; bool old_status; old_status = domain->online; *domain_name = NULL; *names = NULL; *types = NULL; if (!cache->tdb) { goto do_query; } if (num_rids == 0) { return NT_STATUS_OK; } *names = talloc_array(mem_ctx, char *, num_rids); *types = talloc_array(mem_ctx, enum lsa_SidType, num_rids); if ((*names == NULL) || (*types == NULL)) { result = NT_STATUS_NO_MEMORY; goto error; } have_mapped = have_unmapped = false; for (i=0; ibackend->rids_to_names(domain, mem_ctx, domain_sid, rids, num_rids, domain_name, names, types); if (NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (cache->tdb && !domain->internal && !domain->online && old_status) { have_mapped = have_unmapped = false; *names = talloc_array(mem_ctx, char *, num_rids); if (*names == NULL) { result = NT_STATUS_NO_MEMORY; goto error; } *types = talloc_array(mem_ctx, enum lsa_SidType, num_rids); if (*types == NULL) { result = NT_STATUS_NO_MEMORY; goto error; } for (i=0; itdb == NULL) { return NT_STATUS_NOT_FOUND; } centry = wcache_fetch( cache, domain, "U/%s", dom_sid_str_buf(user_sid, &sid_string)); if (centry == NULL) { return NT_STATUS_NOT_FOUND; } /* if status is not ok then this is a negative hit and the rest of the data doesn't matter */ status = centry->status; if (NT_STATUS_IS_OK(status)) { info->domain_name = centry_string(centry, mem_ctx); info->acct_name = centry_string(centry, mem_ctx); info->full_name = centry_string(centry, mem_ctx); info->homedir = centry_string(centry, mem_ctx); info->shell = centry_string(centry, mem_ctx); info->uid = centry_uint32(centry); info->primary_gid = centry_uint32(centry); info->primary_group_name = centry_string(centry, mem_ctx); centry_sid(centry, &info->user_sid); centry_sid(centry, &info->group_sid); } DBG_DEBUG("query_user: [Cached] - cached info for domain %s status: " "%s\n", domain->name, nt_errstr(status) ); centry_free(centry); return status; } /** * @brief Query a fullname from the username cache (for further gecos processing) * * @param domain A pointer to the winbindd_domain struct. * @param mem_ctx The talloc context. * @param user_sid The user sid. * @param full_name A pointer to the full_name string. * * @return NTSTATUS code */ NTSTATUS wcache_query_user_fullname(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *user_sid, const char **full_name) { NTSTATUS status; struct wbint_userinfo info; status = wcache_query_user(domain, mem_ctx, user_sid, &info); if (!NT_STATUS_IS_OK(status)) { return status; } if (info.full_name != NULL) { *full_name = talloc_strdup(mem_ctx, info.full_name); if (*full_name == NULL) { return NT_STATUS_NO_MEMORY; } } return NT_STATUS_OK; } static NTSTATUS wcache_lookup_usergroups(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *user_sid, uint32_t *pnum_sids, struct dom_sid **psids) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; uint32_t i, num_sids; struct dom_sid *sids; struct dom_sid_buf sid_string; if (cache->tdb == NULL) { return NT_STATUS_NOT_FOUND; } centry = wcache_fetch( cache, domain, "UG/%s", dom_sid_str_buf(user_sid, &sid_string)); if (centry == NULL) { return NT_STATUS_NOT_FOUND; } num_sids = centry_uint32(centry); sids = talloc_array(mem_ctx, struct dom_sid, num_sids); if (sids == NULL) { centry_free(centry); return NT_STATUS_NO_MEMORY; } for (i=0; istatus; DBG_DEBUG("lookup_usergroups: [Cached] - cached info for domain %s " "status: %s\n", domain->name, nt_errstr(status)); centry_free(centry); *pnum_sids = num_sids; *psids = sids; return status; } /* Lookup groups a user is a member of. */ NTSTATUS wb_cache_lookup_usergroups(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *user_sid, uint32_t *num_groups, struct dom_sid **user_gids) { struct cache_entry *centry = NULL; NTSTATUS status; unsigned int i; struct dom_sid_buf sid_string; bool old_status; old_status = domain->online; status = wcache_lookup_usergroups(domain, mem_ctx, user_sid, num_groups, user_gids); if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { return status; } (*num_groups) = 0; (*user_gids) = NULL; DBG_DEBUG("lookup_usergroups: [Cached] - doing backend query for info for domain %s\n", domain->name ); status = domain->backend->lookup_usergroups(domain, mem_ctx, user_sid, num_groups, user_gids); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (!domain->internal && !domain->online && old_status) { NTSTATUS cache_status; cache_status = wcache_lookup_usergroups(domain, mem_ctx, user_sid, num_groups, user_gids); return cache_status; } } if ( NT_STATUS_EQUAL(status, NT_STATUS_SYNCHRONIZATION_REQUIRED) ) goto skip_save; /* and save it */ refresh_sequence_number(domain); if (!NT_STATUS_IS_OK(status)) { return status; } centry = centry_start(domain, status); if (!centry) goto skip_save; centry_put_uint32(centry, *num_groups); for (i=0; i<(*num_groups); i++) { centry_put_sid(centry, &(*user_gids)[i]); } centry_end(centry, "UG/%s", dom_sid_str_buf(user_sid, &sid_string)); centry_free(centry); skip_save: return status; } static char *wcache_make_sidlist(TALLOC_CTX *mem_ctx, uint32_t num_sids, const struct dom_sid *sids) { uint32_t i; char *sidlist; sidlist = talloc_strdup(mem_ctx, ""); if (sidlist == NULL) { return NULL; } for (i=0; itdb == NULL) { return NT_STATUS_NOT_FOUND; } if (num_sids == 0) { *pnum_aliases = 0; *paliases = NULL; return NT_STATUS_OK; } /* We need to cache indexed by the whole list of SIDs, the aliases * resulting might come from any of the SIDs. */ sidlist = wcache_make_sidlist(talloc_tos(), num_sids, sids); if (sidlist == NULL) { return NT_STATUS_NO_MEMORY; } centry = wcache_fetch(cache, domain, "UA%s", sidlist); TALLOC_FREE(sidlist); if (centry == NULL) { return NT_STATUS_NOT_FOUND; } num_aliases = centry_uint32(centry); aliases = talloc_array(mem_ctx, uint32_t, num_aliases); if (aliases == NULL) { centry_free(centry); return NT_STATUS_NO_MEMORY; } for (i=0; istatus; DBG_DEBUG("lookup_useraliases: [Cached] - cached info for domain: %s " "status %s\n", domain->name, nt_errstr(status)); centry_free(centry); *pnum_aliases = num_aliases; *paliases = aliases; return status; } NTSTATUS wb_cache_lookup_useraliases(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, uint32_t num_sids, const struct dom_sid *sids, uint32_t *num_aliases, uint32_t **alias_rids) { struct cache_entry *centry = NULL; NTSTATUS status; char *sidlist; uint32_t i; bool old_status; old_status = domain->online; status = wcache_lookup_useraliases(domain, mem_ctx, num_sids, sids, num_aliases, alias_rids); if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { return status; } (*num_aliases) = 0; (*alias_rids) = NULL; DBG_DEBUG("lookup_usergroups: [Cached] - doing backend query for info " "for domain %s\n", domain->name ); sidlist = wcache_make_sidlist(talloc_tos(), num_sids, sids); if (sidlist == NULL) { return NT_STATUS_NO_MEMORY; } status = domain->backend->lookup_useraliases(domain, mem_ctx, num_sids, sids, num_aliases, alias_rids); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (!domain->internal && !domain->online && old_status) { NTSTATUS cache_status; cache_status = wcache_lookup_useraliases(domain, mem_ctx, num_sids, sids, num_aliases, alias_rids); return cache_status; } } /* and save it */ refresh_sequence_number(domain); if (!NT_STATUS_IS_OK(status)) { return status; } centry = centry_start(domain, status); if (!centry) goto skip_save; centry_put_uint32(centry, *num_aliases); for (i=0; i<(*num_aliases); i++) centry_put_uint32(centry, (*alias_rids)[i]); centry_end(centry, "UA%s", sidlist); centry_free(centry); skip_save: return status; } static NTSTATUS wcache_lookup_aliasmem(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *group_sid, uint32_t *num_names, struct dom_sid **sid_mem) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; unsigned int i; struct dom_sid_buf sid_string; if (cache->tdb == NULL) { return NT_STATUS_NOT_FOUND; } centry = wcache_fetch(cache, domain, "AM/%s", dom_sid_str_buf(group_sid, &sid_string)); if (centry == NULL) { return NT_STATUS_NOT_FOUND; } *sid_mem = NULL; *num_names = centry_uint32(centry); if (*num_names == 0) { centry_free(centry); return NT_STATUS_OK; } *sid_mem = talloc_array(mem_ctx, struct dom_sid, *num_names); if (*sid_mem == NULL) { TALLOC_FREE(*sid_mem); centry_free(centry); return NT_STATUS_NO_MEMORY; } for (i = 0; i < (*num_names); i++) { centry_sid(centry, &(*sid_mem)[i]); } status = centry->status; D_DEBUG("[Cached] - cached info for domain %s " "status: %s\n", domain->name, nt_errstr(status)); centry_free(centry); return status; } NTSTATUS wb_cache_lookup_aliasmem(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *group_sid, enum lsa_SidType type, uint32_t *num_sids, struct dom_sid **sid_mem) { struct cache_entry *centry = NULL; NTSTATUS status; unsigned int i; struct dom_sid_buf sid_string; bool old_status; old_status = domain->online; status = wcache_lookup_aliasmem(domain, mem_ctx, group_sid, num_sids, sid_mem); if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { return status; } (*num_sids) = 0; (*sid_mem) = NULL; D_DEBUG("[Cached] - doing backend query for info for domain %s\n", domain->name); status = domain->backend->lookup_aliasmem(domain, mem_ctx, group_sid, type, num_sids, sid_mem); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (!domain->internal && !domain->online && old_status) { NTSTATUS cache_status; cache_status = wcache_lookup_aliasmem(domain, mem_ctx, group_sid, num_sids, sid_mem); return cache_status; } } /* and save it */ refresh_sequence_number(domain); if (!NT_STATUS_IS_OK(status)) { return status; } centry = centry_start(domain, status); if (!centry) goto skip_save; centry_put_uint32(centry, *num_sids); for (i = 0; i < (*num_sids); i++) { centry_put_sid(centry, &(*sid_mem)[i]); } centry_end(centry, "AM/%s", dom_sid_str_buf(group_sid, &sid_string)); centry_free(centry); skip_save: return status; } static NTSTATUS wcache_lookup_groupmem(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *group_sid, uint32_t *num_names, struct dom_sid **sid_mem, char ***names, uint32_t **name_types) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; unsigned int i; struct dom_sid_buf sid_string; if (cache->tdb == NULL) { return NT_STATUS_NOT_FOUND; } centry = wcache_fetch( cache, domain, "GM/%s", dom_sid_str_buf(group_sid, &sid_string)); if (centry == NULL) { return NT_STATUS_NOT_FOUND; } *sid_mem = NULL; *names = NULL; *name_types = NULL; *num_names = centry_uint32(centry); if (*num_names == 0) { centry_free(centry); return NT_STATUS_OK; } *sid_mem = talloc_array(mem_ctx, struct dom_sid, *num_names); *names = talloc_array(mem_ctx, char *, *num_names); *name_types = talloc_array(mem_ctx, uint32_t, *num_names); if ((*sid_mem == NULL) || (*names == NULL) || (*name_types == NULL)) { TALLOC_FREE(*sid_mem); TALLOC_FREE(*names); TALLOC_FREE(*name_types); centry_free(centry); return NT_STATUS_NO_MEMORY; } for (i=0; i<(*num_names); i++) { centry_sid(centry, &(*sid_mem)[i]); (*names)[i] = centry_string(centry, mem_ctx); (*name_types)[i] = centry_uint32(centry); } status = centry->status; DBG_DEBUG("lookup_groupmem: [Cached] - cached info for domain %s " "status: %s\n", domain->name, nt_errstr(status)); centry_free(centry); return status; } NTSTATUS wb_cache_lookup_groupmem(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const struct dom_sid *group_sid, enum lsa_SidType type, uint32_t *num_names, struct dom_sid **sid_mem, char ***names, uint32_t **name_types) { struct cache_entry *centry = NULL; NTSTATUS status; unsigned int i; struct dom_sid_buf sid_string; bool old_status; old_status = domain->online; status = wcache_lookup_groupmem(domain, mem_ctx, group_sid, num_names, sid_mem, names, name_types); if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { return status; } (*num_names) = 0; (*sid_mem) = NULL; (*names) = NULL; (*name_types) = NULL; DBG_DEBUG("lookup_groupmem: [Cached] - doing backend query for info for domain %s\n", domain->name ); status = domain->backend->lookup_groupmem(domain, mem_ctx, group_sid, type, num_names, sid_mem, names, name_types); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (!domain->internal && !domain->online && old_status) { NTSTATUS cache_status; cache_status = wcache_lookup_groupmem(domain, mem_ctx, group_sid, num_names, sid_mem, names, name_types); return cache_status; } } /* and save it */ refresh_sequence_number(domain); if (!NT_STATUS_IS_OK(status)) { return status; } centry = centry_start(domain, status); if (!centry) goto skip_save; centry_put_uint32(centry, *num_names); for (i=0; i<(*num_names); i++) { centry_put_sid(centry, &(*sid_mem)[i]); centry_put_string(centry, (*names)[i]); centry_put_uint32(centry, (*name_types)[i]); } centry_end(centry, "GM/%s", dom_sid_str_buf(group_sid, &sid_string)); centry_free(centry); skip_save: return status; } /* find the sequence number for a domain */ NTSTATUS wb_cache_sequence_number(struct winbindd_domain *domain, uint32_t *seq) { refresh_sequence_number(domain); *seq = domain->sequence_number; return NT_STATUS_OK; } /* enumerate trusted domains * (we need to have the list of trustdoms in the cache when we go offline) - * Guenther */ NTSTATUS wb_cache_trusted_domains(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, struct netr_DomainTrustList *trusts) { NTSTATUS status; struct winbind_cache *cache; struct winbindd_tdc_domain *dom_list = NULL; size_t num_domains = 0; bool retval = false; size_t i; bool old_status; old_status = domain->online; trusts->count = 0; trusts->array = NULL; cache = get_cache(domain); if (!cache || !cache->tdb) { goto do_query; } if (domain->online) { goto do_query; } retval = wcache_tdc_fetch_list(&dom_list, &num_domains); if (!retval || !num_domains || !dom_list) { TALLOC_FREE(dom_list); goto do_query; } do_fetch_cache: trusts->array = talloc_zero_array(mem_ctx, struct netr_DomainTrust, num_domains); if (!trusts->array) { TALLOC_FREE(dom_list); return NT_STATUS_NO_MEMORY; } for (i = 0; i < num_domains; i++) { struct netr_DomainTrust *trust; struct dom_sid *sid; struct winbindd_domain *dom; dom = find_domain_from_name_noinit(dom_list[i].domain_name); if (dom && dom->internal) { continue; } trust = &trusts->array[trusts->count]; trust->netbios_name = talloc_strdup(trusts->array, dom_list[i].domain_name); trust->dns_name = talloc_strdup(trusts->array, dom_list[i].dns_name); sid = talloc(trusts->array, struct dom_sid); if (!trust->netbios_name || !trust->dns_name || !sid) { TALLOC_FREE(dom_list); TALLOC_FREE(trusts->array); return NT_STATUS_NO_MEMORY; } trust->trust_flags = dom_list[i].trust_flags; trust->trust_attributes = dom_list[i].trust_attribs; trust->trust_type = dom_list[i].trust_type; sid_copy(sid, &dom_list[i].sid); trust->sid = sid; trusts->count++; } TALLOC_FREE(dom_list); return NT_STATUS_OK; do_query: DBG_DEBUG("trusted_domains: [Cached] - doing backend query for info for domain %s\n", domain->name ); status = domain->backend->trusted_domains(domain, mem_ctx, trusts); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (!domain->internal && !domain->online && old_status) { retval = wcache_tdc_fetch_list(&dom_list, &num_domains); if (retval && num_domains && dom_list) { TALLOC_FREE(trusts->array); trusts->count = 0; goto do_fetch_cache; } } } /* no trusts gives NT_STATUS_NO_MORE_ENTRIES resetting to NT_STATUS_OK * so that the generic centry handling still applies correctly - * Guenther*/ if (!NT_STATUS_IS_ERR(status)) { status = NT_STATUS_OK; } return status; } /* get lockout policy */ NTSTATUS wb_cache_lockout_policy(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, struct samr_DomInfo12 *policy) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; bool old_status; old_status = domain->online; if (!cache->tdb) goto do_query; centry = wcache_fetch(cache, domain, "LOC_POL/%s", domain->name); if (!centry) goto do_query; do_fetch_cache: policy->lockout_duration = centry_nttime(centry); policy->lockout_window = centry_nttime(centry); policy->lockout_threshold = centry_uint16(centry); status = centry->status; DBG_DEBUG("lockout_policy: [Cached] - cached info for domain %s status: %s\n", domain->name, nt_errstr(status) ); centry_free(centry); return status; do_query: ZERO_STRUCTP(policy); DBG_DEBUG("lockout_policy: [Cached] - doing backend query for info for domain %s\n", domain->name ); status = domain->backend->lockout_policy(domain, mem_ctx, policy); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (cache->tdb && !domain->internal && !domain->online && old_status) { centry = wcache_fetch(cache, domain, "LOC_POL/%s", domain->name); if (centry) { goto do_fetch_cache; } } } /* and save it */ refresh_sequence_number(domain); if (!NT_STATUS_IS_OK(status)) { return status; } wcache_save_lockout_policy(domain, status, policy); return status; } /* get password policy */ NTSTATUS wb_cache_password_policy(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, struct samr_DomInfo1 *policy) { struct winbind_cache *cache = get_cache(domain); struct cache_entry *centry = NULL; NTSTATUS status; bool old_status; old_status = domain->online; if (!cache->tdb) goto do_query; centry = wcache_fetch(cache, domain, "PWD_POL/%s", domain->name); if (!centry) goto do_query; do_fetch_cache: policy->min_password_length = centry_uint16(centry); policy->password_history_length = centry_uint16(centry); policy->password_properties = centry_uint32(centry); policy->max_password_age = centry_nttime(centry); policy->min_password_age = centry_nttime(centry); status = centry->status; DBG_DEBUG("lockout_policy: [Cached] - cached info for domain %s status: %s\n", domain->name, nt_errstr(status) ); centry_free(centry); return status; do_query: ZERO_STRUCTP(policy); DBG_DEBUG("password_policy: [Cached] - doing backend query for info for domain %s\n", domain->name ); status = domain->backend->password_policy(domain, mem_ctx, policy); if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { if (!domain->internal && old_status) { set_domain_offline(domain); } if (cache->tdb && !domain->internal && !domain->online && old_status) { centry = wcache_fetch(cache, domain, "PWD_POL/%s", domain->name); if (centry) { goto do_fetch_cache; } } } /* and save it */ refresh_sequence_number(domain); if (!NT_STATUS_IS_OK(status)) { return status; } wcache_save_password_policy(domain, status, policy); return status; } /* Invalidate cached user and group lists coherently */ static int traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) { if (strncmp((const char *)kbuf.dptr, "UL/", 3) == 0 || strncmp((const char *)kbuf.dptr, "GL/", 3) == 0) tdb_delete(the_tdb, kbuf); return 0; } /* Invalidate the getpwnam and getgroups entries for a winbindd domain */ void wcache_invalidate_samlogon(struct winbindd_domain *domain, const struct dom_sid *sid) { fstring key_str; struct dom_sid_buf sid_string; struct winbind_cache *cache; /* don't clear cached U/SID and UG/SID entries when we want to logon * offline - gd */ if (lp_winbind_offline_logon()) { return; } if (!domain) return; cache = get_cache(domain); if (!cache->tdb) { return; } /* Clear U/SID cache entry */ fstr_sprintf(key_str, "U/%s", dom_sid_str_buf(sid, &sid_string)); DBG_DEBUG("wcache_invalidate_samlogon: clearing %s\n", key_str); tdb_delete(cache->tdb, string_tdb_data(key_str)); /* Clear UG/SID cache entry */ fstr_sprintf(key_str, "UG/%s", dom_sid_str_buf(sid, &sid_string)); DBG_DEBUG("wcache_invalidate_samlogon: clearing %s\n", key_str); tdb_delete(cache->tdb, string_tdb_data(key_str)); /* Samba/winbindd never needs this. */ netsamlogon_clear_cached_user(sid); } bool wcache_invalidate_cache(void) { struct winbindd_domain *domain; for (domain = domain_list(); domain; domain = domain->next) { struct winbind_cache *cache = get_cache(domain); DBG_DEBUG("wcache_invalidate_cache: invalidating cache " "entries for %s\n", domain->name); if (cache) { if (cache->tdb) { tdb_traverse(cache->tdb, traverse_fn, NULL); } else { return false; } } } return true; } bool wcache_invalidate_cache_noinit(void) { struct winbindd_domain *domain; for (domain = domain_list(); domain; domain = domain->next) { struct winbind_cache *cache; /* Skip uninitialized domains. */ if (!domain->initialized && !domain->internal) { continue; } cache = get_cache(domain); DBG_DEBUG("wcache_invalidate_cache: invalidating cache " "entries for %s\n", domain->name); if (cache) { if (cache->tdb) { tdb_traverse(cache->tdb, traverse_fn, NULL); /* * Flushing cache has nothing to with domains. * return here if we successfully flushed once. * To avoid unnecessary traversing the cache. */ return true; } else { return false; } } } return true; } static bool init_wcache(void) { char *db_path; if (wcache == NULL) { wcache = SMB_XMALLOC_P(struct winbind_cache); ZERO_STRUCTP(wcache); } if (wcache->tdb != NULL) return true; db_path = wcache_path(); if (db_path == NULL) { return false; } /* when working offline we must not clear the cache on restart */ wcache->tdb = tdb_open_log(db_path, WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE, TDB_INCOMPATIBLE_HASH | (lp_winbind_offline_logon() ? TDB_DEFAULT : (TDB_DEFAULT | TDB_CLEAR_IF_FIRST)), O_RDWR|O_CREAT, 0600); TALLOC_FREE(db_path); if (wcache->tdb == NULL) { DBG_ERR("Failed to open winbindd_cache.tdb!\n"); return false; } return true; } /************************************************************************ This is called by the parent to initialize the cache file. We don't need sophisticated locking here as we know we're the only opener. ************************************************************************/ bool initialize_winbindd_cache(void) { bool cache_bad = false; uint32_t vers = 0; bool ok; if (!init_wcache()) { DBG_ERR("initialize_winbindd_cache: init_wcache failed.\n"); return false; } /* Check version number. */ ok = tdb_fetch_uint32(wcache->tdb, WINBINDD_CACHE_VERSION_KEYSTR, &vers); if (!ok) { DBG_DEBUG("Failed to get cache version\n"); cache_bad = true; } if (vers != WINBINDD_CACHE_VERSION) { DBG_DEBUG("Invalid cache version %u != %u\n", vers, WINBINDD_CACHE_VERSION); cache_bad = true; } if (cache_bad) { char *db_path; DBG_NOTICE("initialize_winbindd_cache: clearing cache " "and re-creating with version number %d\n", WINBINDD_CACHE_VERSION); tdb_close(wcache->tdb); wcache->tdb = NULL; db_path = wcache_path(); if (db_path == NULL) { return false; } if (unlink(db_path) == -1) { DBG_ERR("initialize_winbindd_cache: unlink %s failed %s\n", db_path, strerror(errno) ); TALLOC_FREE(db_path); return false; } TALLOC_FREE(db_path); if (!init_wcache()) { DBG_ERR("initialize_winbindd_cache: re-initialization " "init_wcache failed.\n"); return false; } /* Write the version. */ if (!tdb_store_uint32(wcache->tdb, WINBINDD_CACHE_VERSION_KEYSTR, WINBINDD_CACHE_VERSION)) { DBG_ERR("initialize_winbindd_cache: version number store failed %s\n", tdb_errorstr(wcache->tdb) ); return false; } } tdb_close(wcache->tdb); wcache->tdb = NULL; return true; } void close_winbindd_cache(void) { if (!wcache) { return; } if (wcache->tdb) { tdb_close(wcache->tdb); wcache->tdb = NULL; } } bool lookup_cached_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid, char **domain_name, char **name, enum lsa_SidType *type) { struct winbindd_domain *domain; NTSTATUS status; domain = find_lookup_domain_from_sid(sid); if (domain == NULL) { return false; } status = wcache_sid_to_name(domain, sid, mem_ctx, domain_name, name, type); return NT_STATUS_IS_OK(status); } bool lookup_cached_name(const char *namespace, const char *domain_name, const char *name, struct dom_sid *sid, enum lsa_SidType *type) { struct winbindd_domain *domain; NTSTATUS status; bool original_online_state; domain = find_lookup_domain_from_name(namespace); if (domain == NULL) { return false; } /* If we are doing a cached logon, temporarily set the domain offline so the cache won't expire the entry */ original_online_state = domain->online; domain->online = false; status = wcache_name_to_sid(domain, domain_name, name, sid, type); domain->online = original_online_state; return NT_STATUS_IS_OK(status); } /* * Cache a name to sid without checking the sequence number. * Used when caching from a trusted PAC. */ void cache_name2sid_trusted(struct winbindd_domain *domain, const char *domain_name, const char *name, enum lsa_SidType type, const struct dom_sid *sid) { /* * Ensure we store the mapping with the * existing sequence number from the cache. */ get_cache(domain); (void)fetch_cache_seqnum(domain, time(NULL)); wcache_save_name_to_sid(domain, NT_STATUS_OK, domain_name, name, sid, type); } void cache_name2sid(struct winbindd_domain *domain, const char *domain_name, const char *name, enum lsa_SidType type, const struct dom_sid *sid) { refresh_sequence_number(domain); wcache_save_name_to_sid(domain, NT_STATUS_OK, domain_name, name, sid, type); } /* * The original idea that this cache only contains centries has * been blurred - now other stuff gets put in here. Ensure we * ignore these things on cleanup. */ static int traverse_fn_cleanup(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) { struct cache_entry *centry; if (is_non_centry_key(kbuf)) { return 0; } centry = wcache_fetch_raw((char *)kbuf.dptr); if (!centry) { return 0; } if (!NT_STATUS_IS_OK(centry->status)) { DBG_DEBUG("deleting centry %s\n", (const char *)kbuf.dptr); tdb_delete(the_tdb, kbuf); } centry_free(centry); return 0; } /* flush the cache */ static void wcache_flush_cache(void) { char *db_path; if (!wcache) return; if (wcache->tdb) { tdb_close(wcache->tdb); wcache->tdb = NULL; } if (!winbindd_use_cache()) { return; } db_path = wcache_path(); if (db_path == NULL) { return; } /* when working offline we must not clear the cache on restart */ wcache->tdb = tdb_open_log(db_path, WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE, TDB_INCOMPATIBLE_HASH | (lp_winbind_offline_logon() ? TDB_DEFAULT : (TDB_DEFAULT | TDB_CLEAR_IF_FIRST)), O_RDWR|O_CREAT, 0600); TALLOC_FREE(db_path); if (!wcache->tdb) { DBG_ERR("Failed to open winbindd_cache.tdb!\n"); return; } tdb_traverse(wcache->tdb, traverse_fn_cleanup, NULL); DBG_DEBUG("wcache_flush_cache success\n"); } /* Count cached creds */ static int traverse_fn_cached_creds(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) { int *cred_count = (int*)state; if (strncmp((const char *)kbuf.dptr, "CRED/", 5) == 0) { (*cred_count)++; } return 0; } NTSTATUS wcache_count_cached_creds(struct winbindd_domain *domain, int *count) { struct winbind_cache *cache = get_cache(domain); *count = 0; if (!cache->tdb) { return NT_STATUS_INTERNAL_DB_ERROR; } tdb_traverse(cache->tdb, traverse_fn_cached_creds, (void *)count); return NT_STATUS_OK; } struct cred_list { struct cred_list *prev, *next; TDB_DATA key; fstring name; time_t created; }; static struct cred_list *wcache_cred_list; static int traverse_fn_get_credlist(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) { struct cred_list *cred; if (strncmp((const char *)kbuf.dptr, "CRED/", 5) == 0) { cred = SMB_MALLOC_P(struct cred_list); if (cred == NULL) { DBG_ERR("traverse_fn_remove_first_creds: failed to malloc new entry for list\n"); return -1; } ZERO_STRUCTP(cred); /* save a copy of the key */ fstrcpy(cred->name, (const char *)kbuf.dptr); DLIST_ADD(wcache_cred_list, cred); } return 0; } NTSTATUS wcache_remove_oldest_cached_creds(struct winbindd_domain *domain, const struct dom_sid *sid) { struct winbind_cache *cache = get_cache(domain); NTSTATUS status; int ret; struct cred_list *cred, *next, *oldest = NULL; if (!cache->tdb) { return NT_STATUS_INTERNAL_DB_ERROR; } /* we possibly already have an entry */ if (sid && NT_STATUS_IS_OK(wcache_cached_creds_exist(domain, sid))) { fstring key_str; struct dom_sid_buf tmp; DBG_DEBUG("we already have an entry, deleting that\n"); fstr_sprintf(key_str, "CRED/%s", dom_sid_str_buf(sid, &tmp)); tdb_delete(cache->tdb, string_tdb_data(key_str)); return NT_STATUS_OK; } ret = tdb_traverse(cache->tdb, traverse_fn_get_credlist, NULL); if (ret == 0) { return NT_STATUS_OK; } else if ((ret < 0) || (wcache_cred_list == NULL)) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; } ZERO_STRUCTP(oldest); for (cred = wcache_cred_list; cred; cred = cred->next) { TDB_DATA data; time_t t; data = tdb_fetch(cache->tdb, string_tdb_data(cred->name)); if (!data.dptr) { DBG_DEBUG("wcache_remove_oldest_cached_creds: entry for [%s] not found\n", cred->name); status = NT_STATUS_OBJECT_NAME_NOT_FOUND; goto done; } t = IVAL(data.dptr, 0); SAFE_FREE(data.dptr); if (!oldest) { oldest = SMB_MALLOC_P(struct cred_list); if (oldest == NULL) { status = NT_STATUS_NO_MEMORY; goto done; } fstrcpy(oldest->name, cred->name); oldest->created = t; continue; } if (t < oldest->created) { fstrcpy(oldest->name, cred->name); oldest->created = t; } } if (tdb_delete(cache->tdb, string_tdb_data(oldest->name)) == 0) { status = NT_STATUS_OK; } else { status = NT_STATUS_UNSUCCESSFUL; } done: for (cred = wcache_cred_list; cred; cred = next) { next = cred->next; DLIST_REMOVE(wcache_cred_list, cred); SAFE_FREE(cred); } SAFE_FREE(oldest); return status; } /* Change the global online/offline state. */ bool set_global_winbindd_state_offline(void) { bool ok; uint8_t buf[4] = {0}; TDB_DATA data = { .dptr = buf, .dsize = sizeof(buf) }; int rc; DBG_NOTICE("Offline requested\n"); if (wcache == NULL || wcache->tdb == NULL) { DBG_NOTICE("Winbind cache doesn't exist yet\n"); return false; } if (!lp_winbind_offline_logon()) { DBG_DEBUG("Rejecting request to set winbind offline, " "offline logons are disabled in smb.conf\n"); return false; } ok = get_global_winbindd_state_offline(); if (ok) { return true; } PUSH_LE_U32(buf, 0, time(NULL)); rc = tdb_store_bystring(wcache->tdb, "WINBINDD_OFFLINE", data, TDB_INSERT); if (rc != 0) { return false; } return true; } void set_global_winbindd_state_online(void) { DBG_DEBUG("set_global_winbindd_state_online: online requested.\n"); if (!lp_winbind_offline_logon()) { DBG_DEBUG("Rejecting request to set winbind online, " "offline logons are disabled in smb.conf.\n"); return; } if (!wcache->tdb) { return; } /* Ensure there is no key "WINBINDD_OFFLINE" in the cache tdb. */ tdb_delete_bystring(wcache->tdb, "WINBINDD_OFFLINE"); } bool get_global_winbindd_state_offline(void) { TDB_DATA data; data = tdb_fetch_bystring(wcache->tdb, "WINBINDD_OFFLINE"); if (data.dptr == NULL || data.dsize != 4) { DBG_DEBUG("Offline state not set.\n"); SAFE_FREE(data.dptr); return false; } return true; } /*********************************************************************** Validate functions for all possible cache tdb keys. ***********************************************************************/ static struct cache_entry *create_centry_validate(const char *kstr, TDB_DATA data, struct tdb_validation_status *state) { struct cache_entry *centry; centry = SMB_XMALLOC_P(struct cache_entry); centry->data = (unsigned char *)smb_memdup(data.dptr, data.dsize); if (!centry->data) { SAFE_FREE(centry); return NULL; } centry->len = data.dsize; centry->ofs = 0; if (centry->len < 16) { /* huh? corrupt cache? */ DBG_ERR("create_centry_validate: Corrupt cache for key %s " "(len < 16) ?\n", kstr); centry_free(centry); state->bad_entry = true; state->success = false; return NULL; } centry->status = NT_STATUS(centry_uint32(centry)); centry->sequence_number = centry_uint32(centry); centry->timeout = centry_uint64_t(centry); return centry; } static int validate_seqnum(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { if (dbuf.dsize != 8) { DBG_ERR("validate_seqnum: Corrupt cache for key %s (len %u != 8) ?\n", keystr, (unsigned int)dbuf.dsize ); state->bad_entry = true; return 1; } return 0; } static int validate_u(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); struct dom_sid sid; if (!centry) { return 1; } (void)centry_string(centry, mem_ctx); (void)centry_string(centry, mem_ctx); (void)centry_string(centry, mem_ctx); (void)centry_string(centry, mem_ctx); (void)centry_string(centry, mem_ctx); (void)centry_uint32(centry); (void)centry_uint32(centry); (void)centry_string(centry, mem_ctx); (void)centry_sid(centry, &sid); (void)centry_sid(centry, &sid); centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_u: %s ok\n", keystr); return 0; } static int validate_loc_pol(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); if (!centry) { return 1; } (void)centry_nttime(centry); (void)centry_nttime(centry); (void)centry_uint16(centry); centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_loc_pol: %s ok\n", keystr); return 0; } static int validate_pwd_pol(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); if (!centry) { return 1; } (void)centry_uint16(centry); (void)centry_uint16(centry); (void)centry_uint32(centry); (void)centry_nttime(centry); (void)centry_nttime(centry); centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_pwd_pol: %s ok\n", keystr); return 0; } static int validate_cred(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); if (!centry) { return 1; } (void)centry_time(centry); (void)centry_hash16(centry, mem_ctx); /* We only have 17 bytes more data in the salted cred case. */ if (centry->len - centry->ofs == 17) { (void)centry_hash16(centry, mem_ctx); } centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_cred: %s ok\n", keystr); return 0; } static int validate_ul(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); int32_t num_entries, i; if (!centry) { return 1; } num_entries = (int32_t)centry_uint32(centry); for (i=0; i< num_entries; i++) { (void)centry_uint32(centry); } centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_ul: %s ok\n", keystr); return 0; } static int validate_gl(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); int32_t num_entries, i; if (!centry) { return 1; } num_entries = centry_uint32(centry); for (i=0; i< num_entries; i++) { (void)centry_string(centry, mem_ctx); (void)centry_string(centry, mem_ctx); (void)centry_uint32(centry); } centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_gl: %s ok\n", keystr); return 0; } static int validate_ug(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); int32_t num_groups, i; if (!centry) { return 1; } num_groups = centry_uint32(centry); for (i=0; i< num_groups; i++) { struct dom_sid sid; centry_sid(centry, &sid); } centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_ug: %s ok\n", keystr); return 0; } static int validate_ua(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); int32_t num_aliases, i; if (!centry) { return 1; } num_aliases = centry_uint32(centry); for (i=0; i < num_aliases; i++) { (void)centry_uint32(centry); } centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_ua: %s ok\n", keystr); return 0; } static int validate_gm(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); int32_t num_names, i; if (!centry) { return 1; } num_names = centry_uint32(centry); for (i=0; i< num_names; i++) { struct dom_sid sid; centry_sid(centry, &sid); (void)centry_string(centry, mem_ctx); (void)centry_uint32(centry); } centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_gm: %s ok\n", keystr); return 0; } static int validate_dr(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { /* Can't say anything about this other than must be nonzero. */ if (dbuf.dsize == 0) { DBG_ERR("validate_dr: Corrupt cache for key %s (len == 0) ?\n", keystr); state->bad_entry = true; state->success = false; return 1; } DBG_DEBUG("validate_dr: %s ok\n", keystr); return 0; } static int validate_de(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { /* Can't say anything about this other than must be nonzero. */ if (dbuf.dsize == 0) { DBG_ERR("validate_de: Corrupt cache for key %s (len == 0) ?\n", keystr); state->bad_entry = true; state->success = false; return 1; } DBG_DEBUG("validate_de: %s ok\n", keystr); return 0; } static int validate_nss_an(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); if (!centry) { return 1; } (void)centry_string( centry, mem_ctx ); centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("validate_pwinfo: %s ok\n", keystr); return 0; } static int validate_nss_na(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); if (!centry) { return 1; } (void)centry_string( centry, mem_ctx ); centry_free(centry); if (!(state->success)) { return 1; } DBG_DEBUG("%s ok\n", keystr); return 0; } static int validate_trustdomcache(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { if (dbuf.dsize == 0) { DBG_ERR("validate_trustdomcache: Corrupt cache for " "key %s (len ==0) ?\n", keystr); state->bad_entry = true; state->success = false; return 1; } DBG_DEBUG("validate_trustdomcache: %s ok\n" " Don't trust me, I am a DUMMY!\n", keystr); return 0; } static int validate_offline(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { if (dbuf.dsize != 4) { DBG_ERR("validate_offline: Corrupt cache for key %s (len %u != 4) ?\n", keystr, (unsigned int)dbuf.dsize ); state->bad_entry = true; state->success = false; return 1; } DBG_DEBUG("validate_offline: %s ok\n", keystr); return 0; } static int validate_ndr(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { /* * Ignore validation for now. The proper way to do this is with a * checksum. Just pure parsing does not really catch much. */ return 0; } static int validate_cache_version(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { if (dbuf.dsize != 4) { DBG_ERR("validate_cache_version: Corrupt cache for " "key %s (len %u != 4) ?\n", keystr, (unsigned int)dbuf.dsize); state->bad_entry = true; state->success = false; return 1; } DBG_DEBUG("validate_cache_version: %s ok\n", keystr); return 0; } /*********************************************************************** A list of all possible cache tdb keys with associated validation functions. ***********************************************************************/ struct key_val_struct { const char *keyname; int (*validate_data_fn)(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status* state); } key_val[] = { {"SEQNUM/", validate_seqnum}, {"U/", validate_u}, {"LOC_POL/", validate_loc_pol}, {"PWD_POL/", validate_pwd_pol}, {"CRED/", validate_cred}, {"UL/", validate_ul}, {"GL/", validate_gl}, {"UG/", validate_ug}, {"UA", validate_ua}, {"GM/", validate_gm}, {"DR/", validate_dr}, {"DE/", validate_de}, {"TRUSTDOMCACHE/", validate_trustdomcache}, {"NSS/NA/", validate_nss_na}, {"NSS/AN/", validate_nss_an}, {"WINBINDD_OFFLINE", validate_offline}, {"NDR/", validate_ndr}, {WINBINDD_CACHE_VERSION_KEYSTR, validate_cache_version}, {NULL, NULL} }; /*********************************************************************** Function to look at every entry in the tdb and validate it as far as possible. ***********************************************************************/ static int cache_traverse_validate_fn(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) { int i; unsigned int max_key_len = 1024; struct tdb_validation_status *v_state = (struct tdb_validation_status *)state; /* Paranoia check. */ if (strncmp("UA/", (const char *)kbuf.dptr, 3) == 0 || strncmp("NDR/", (const char *)kbuf.dptr, 4) == 0) { max_key_len = 1024 * 1024; } if (kbuf.dsize > max_key_len) { DBG_ERR("cache_traverse_validate_fn: key length too large: " "(%u) > (%u)\n\n", (unsigned int)kbuf.dsize, (unsigned int)max_key_len); return 1; } for (i = 0; key_val[i].keyname; i++) { size_t namelen = strlen(key_val[i].keyname); if (kbuf.dsize >= namelen && ( strncmp(key_val[i].keyname, (const char *)kbuf.dptr, namelen)) == 0) { TALLOC_CTX *mem_ctx; char *keystr; int ret; keystr = SMB_MALLOC_ARRAY(char, kbuf.dsize+1); if (!keystr) { return 1; } memcpy(keystr, kbuf.dptr, kbuf.dsize); keystr[kbuf.dsize] = '\0'; mem_ctx = talloc_init("validate_ctx"); if (!mem_ctx) { SAFE_FREE(keystr); return 1; } ret = key_val[i].validate_data_fn(mem_ctx, keystr, dbuf, v_state); SAFE_FREE(keystr); talloc_destroy(mem_ctx); return ret; } } DBG_ERR("cache_traverse_validate_fn: unknown cache entry\nkey :\n"); dump_data(0, (uint8_t *)kbuf.dptr, kbuf.dsize); DBG_ERR("data :\n"); dump_data(0, (uint8_t *)dbuf.dptr, dbuf.dsize); v_state->unknown_key = true; v_state->success = false; return 1; /* terminate. */ } static void validate_panic(const char *const why) { DBG_ERR("validating cache: would panic %s\n" "exiting instead (cache validation mode)\n", why ); exit(47); } static int wbcache_update_centry_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA data, void *state) { uint64_t ctimeout; TDB_DATA blob; if (is_non_centry_key(key)) { return 0; } if (data.dptr == NULL || data.dsize == 0) { if (tdb_delete(tdb, key) < 0) { DBG_ERR("tdb_delete for [%s] failed!\n", key.dptr); return 1; } } /* add timeout to blob (uint64_t) */ blob.dsize = data.dsize + 8; blob.dptr = SMB_XMALLOC_ARRAY(uint8_t, blob.dsize); if (blob.dptr == NULL) { return 1; } memset(blob.dptr, 0, blob.dsize); /* copy status and seqnum */ memcpy(blob.dptr, data.dptr, 8); /* add timeout */ ctimeout = lp_winbind_cache_time() + time(NULL); SBVAL(blob.dptr, 8, ctimeout); /* copy the rest */ memcpy(blob.dptr + 16, data.dptr + 8, data.dsize - 8); if (tdb_store(tdb, key, blob, TDB_REPLACE) < 0) { DBG_ERR("tdb_store to update [%s] failed!\n", key.dptr); SAFE_FREE(blob.dptr); return 1; } SAFE_FREE(blob.dptr); return 0; } static bool wbcache_upgrade_v1_to_v2(TDB_CONTEXT *tdb) { int rc; DBG_NOTICE("Upgrade to version 2 of the winbindd_cache.tdb\n"); rc = tdb_traverse(tdb, wbcache_update_centry_fn, NULL); if (rc < 0) { return false; } return true; } /*********************************************************************** Try and validate every entry in the winbindd cache. If we fail here, delete the cache tdb and return non-zero. ***********************************************************************/ int winbindd_validate_cache(void) { int ret = -1; char *tdb_path = NULL; TDB_CONTEXT *tdb = NULL; uint32_t vers_id; bool ok; DBG_DEBUG("winbindd_validate_cache: replacing panic function\n"); smb_panic_fn = validate_panic; tdb_path = wcache_path(); if (tdb_path == NULL) { goto done; } tdb = tdb_open_log(tdb_path, WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE, TDB_INCOMPATIBLE_HASH | ( lp_winbind_offline_logon() ? TDB_DEFAULT : TDB_DEFAULT | TDB_CLEAR_IF_FIRST ), O_RDWR|O_CREAT, 0600); if (!tdb) { DBG_ERR("winbindd_validate_cache: " "error opening/initializing tdb\n"); goto done; } /* Version check and upgrade code. */ if (!tdb_fetch_uint32(tdb, WINBINDD_CACHE_VERSION_KEYSTR, &vers_id)) { DBG_DEBUG("Fresh database\n"); tdb_store_uint32(tdb, WINBINDD_CACHE_VERSION_KEYSTR, WINBINDD_CACHE_VERSION); vers_id = WINBINDD_CACHE_VERSION; } if (vers_id != WINBINDD_CACHE_VERSION) { if (vers_id == WINBINDD_CACHE_VER1) { ok = wbcache_upgrade_v1_to_v2(tdb); if (!ok) { DBG_DEBUG("winbindd_validate_cache: upgrade to version 2 failed.\n"); unlink(tdb_path); goto done; } tdb_store_uint32(tdb, WINBINDD_CACHE_VERSION_KEYSTR, WINBINDD_CACHE_VERSION); vers_id = WINBINDD_CACHE_VER2; } } tdb_close(tdb); ret = tdb_validate_and_backup(tdb_path, cache_traverse_validate_fn); if (ret != 0) { DBG_DEBUG("winbindd_validate_cache: validation not successful.\n" "removing tdb %s.\n", tdb_path); unlink(tdb_path); } done: TALLOC_FREE(tdb_path); DBG_DEBUG("winbindd_validate_cache: restoring panic function\n"); smb_panic_fn = smb_panic; return ret; } /*********************************************************************** Try and validate every entry in the winbindd cache. ***********************************************************************/ int winbindd_validate_cache_nobackup(void) { int ret = -1; char *tdb_path; DBG_DEBUG("winbindd_validate_cache: replacing panic function\n"); smb_panic_fn = validate_panic; tdb_path = wcache_path(); if (tdb_path == NULL) { goto err_panic_restore; } if (wcache == NULL || wcache->tdb == NULL) { ret = tdb_validate_open(tdb_path, cache_traverse_validate_fn); } else { ret = tdb_validate(wcache->tdb, cache_traverse_validate_fn); } if (ret != 0) { DBG_DEBUG("winbindd_validate_cache_nobackup: validation not " "successful.\n"); } TALLOC_FREE(tdb_path); err_panic_restore: DBG_DEBUG("winbindd_validate_cache_nobackup: restoring panic " "function\n"); smb_panic_fn = smb_panic; return ret; } bool winbindd_cache_validate_and_initialize(void) { close_winbindd_cache(); if (lp_winbind_offline_logon()) { if (winbindd_validate_cache() < 0) { DBG_ERR("winbindd cache tdb corrupt and no backup " "could be restored.\n"); } } return initialize_winbindd_cache(); } /********************************************************************* ********************************************************************/ static bool add_wbdomain_to_tdc_array( struct winbindd_domain *new_dom, struct winbindd_tdc_domain **domains, size_t *num_domains ) { struct winbindd_tdc_domain *list = NULL; size_t i, idx; bool set_only = false; /* don't allow duplicates */ idx = *num_domains; list = *domains; for ( i=0; i< (*num_domains); i++ ) { if ( strequal( new_dom->name, list[i].domain_name ) ) { DBG_DEBUG("add_wbdomain_to_tdc_array: Found existing record for %s\n", new_dom->name); idx = i; set_only = true; break; } } if ( !set_only ) { if ( !*domains ) { list = talloc_array( NULL, struct winbindd_tdc_domain, 1 ); idx = 0; } else { list = talloc_realloc( *domains, *domains, struct winbindd_tdc_domain, (*num_domains)+1); idx = *num_domains; } ZERO_STRUCT( list[idx] ); } if ( !list ) return false; list[idx].domain_name = talloc_strdup(list, new_dom->name); if (list[idx].domain_name == NULL) { return false; } if (new_dom->alt_name != NULL) { list[idx].dns_name = talloc_strdup(list, new_dom->alt_name); if (list[idx].dns_name == NULL) { return false; } } if ( !is_null_sid( &new_dom->sid ) ) { sid_copy( &list[idx].sid, &new_dom->sid ); } else { sid_copy(&list[idx].sid, &global_sid_NULL); } if ( new_dom->domain_flags != 0x0 ) list[idx].trust_flags = new_dom->domain_flags; if ( new_dom->domain_type != 0x0 ) list[idx].trust_type = new_dom->domain_type; if ( new_dom->domain_trust_attribs != 0x0 ) list[idx].trust_attribs = new_dom->domain_trust_attribs; if ( !set_only ) { *domains = list; *num_domains = idx + 1; } return true; } /********************************************************************* ********************************************************************/ static TDB_DATA make_tdc_key( const char *domain_name ) { char *keystr = NULL; TDB_DATA key = { NULL, 0 }; if ( !domain_name ) { DBG_INFO("make_tdc_key: Keyname workgroup is NULL!\n"); return key; } if (asprintf( &keystr, "TRUSTDOMCACHE/%s", domain_name ) == -1) { return key; } key = string_term_tdb_data(keystr); return key; } /********************************************************************* ********************************************************************/ static int pack_tdc_domains( struct winbindd_tdc_domain *domains, size_t num_domains, unsigned char **buf ) { unsigned char *buffer = NULL; int len = 0; int buflen = 0; size_t i = 0; DBG_DEBUG("pack_tdc_domains: Packing %d trusted domains\n", (int)num_domains); buflen = 0; again: len = 0; /* Store the number of array items first */ len += tdb_pack( buffer ? buffer+len : NULL, buffer ? buflen-len : 0, "d", num_domains ); /* now pack each domain trust record */ for ( i=0; i 0 ) { DBG_DEBUG("pack_tdc_domains: Packing domain %s (%s)\n", domains[i].domain_name, domains[i].dns_name ? domains[i].dns_name : "UNKNOWN" ); } len += tdb_pack( buffer ? buffer+len : NULL, buffer ? buflen-len : 0, "fffddd", domains[i].domain_name, domains[i].dns_name ? domains[i].dns_name : "", dom_sid_str_buf(&domains[i].sid, &tmp), domains[i].trust_flags, domains[i].trust_attribs, domains[i].trust_type ); } if ( buflen < len ) { SAFE_FREE(buffer); if ( (buffer = SMB_MALLOC_ARRAY(unsigned char, len)) == NULL ) { DBG_ERR("pack_tdc_domains: failed to alloc buffer!\n"); buflen = -1; goto done; } buflen = len; goto again; } *buf = buffer; done: return buflen; } /********************************************************************* ********************************************************************/ static size_t unpack_tdc_domains( unsigned char *buf, int buflen, struct winbindd_tdc_domain **domains ) { fstring domain_name, dns_name, sid_string; uint32_t type, attribs, flags; int num_domains; int len = 0; int i; struct winbindd_tdc_domain *list = NULL; /* get the number of domains */ len += tdb_unpack( buf+len, buflen-len, "d", &num_domains); if ( len == -1 ) { DBG_INFO("unpack_tdc_domains: Failed to unpack domain array\n"); return 0; } list = talloc_array( NULL, struct winbindd_tdc_domain, num_domains ); if ( !list ) { DBG_ERR("unpack_tdc_domains: Failed to talloc() domain list!\n"); return 0; } for ( i=0; itdb, key ); goto done; } data.dsize = pack_tdc_domains( domains, num_domains, &data.dptr ); if ( !data.dptr ) { ret = -1; goto done; } ret = tdb_store( wcache->tdb, key, data, 0 ); done: SAFE_FREE( data.dptr ); SAFE_FREE( key.dptr ); return ( ret == 0 ); } /********************************************************************* ********************************************************************/ bool wcache_tdc_fetch_list( struct winbindd_tdc_domain **domains, size_t *num_domains ) { TDB_DATA key = make_tdc_key( lp_workgroup() ); TDB_DATA data = { NULL, 0 }; *domains = NULL; *num_domains = 0; if ( !key.dptr ) return false; data = tdb_fetch( wcache->tdb, key ); SAFE_FREE( key.dptr ); if ( !data.dptr ) return false; *num_domains = unpack_tdc_domains( data.dptr, data.dsize, domains ); SAFE_FREE( data.dptr ); if ( !*domains ) return false; return true; } /********************************************************************* ********************************************************************/ bool wcache_tdc_add_domain( struct winbindd_domain *domain ) { struct winbindd_tdc_domain *dom_list = NULL; size_t num_domains = 0; bool ret = false; struct dom_sid_buf buf; DBG_DEBUG("wcache_tdc_add_domain: Adding domain %s (%s), SID %s, " "flags = 0x%x, attributes = 0x%x, type = 0x%x\n", domain->name, domain->alt_name, dom_sid_str_buf(&domain->sid, &buf), domain->domain_flags, domain->domain_trust_attribs, domain->domain_type); if ( !init_wcache() ) { return false; } /* fetch the list */ wcache_tdc_fetch_list( &dom_list, &num_domains ); /* add the new domain */ if ( !add_wbdomain_to_tdc_array( domain, &dom_list, &num_domains ) ) { goto done; } /* pack the domain */ if ( !wcache_tdc_store_list( dom_list, num_domains ) ) { goto done; } /* Success */ ret = true; done: TALLOC_FREE( dom_list ); return ret; } static struct winbindd_tdc_domain *wcache_tdc_dup_domain( TALLOC_CTX *mem_ctx, const struct winbindd_tdc_domain *src) { struct winbindd_tdc_domain *dst; dst = talloc(mem_ctx, struct winbindd_tdc_domain); if (dst == NULL) { goto fail; } dst->domain_name = talloc_strdup(dst, src->domain_name); if (dst->domain_name == NULL) { goto fail; } dst->dns_name = NULL; if (src->dns_name != NULL) { dst->dns_name = talloc_strdup(dst, src->dns_name); if (dst->dns_name == NULL) { goto fail; } } sid_copy(&dst->sid, &src->sid); dst->trust_flags = src->trust_flags; dst->trust_type = src->trust_type; dst->trust_attribs = src->trust_attribs; return dst; fail: TALLOC_FREE(dst); return NULL; } /********************************************************************* ********************************************************************/ struct winbindd_tdc_domain * wcache_tdc_fetch_domain( TALLOC_CTX *ctx, const char *name ) { struct winbindd_tdc_domain *dom_list = NULL; size_t num_domains = 0; size_t i; struct winbindd_tdc_domain *d = NULL; DBG_DEBUG("wcache_tdc_fetch_domain: Searching for domain %s\n", name); if ( !init_wcache() ) { return NULL; } /* fetch the list */ wcache_tdc_fetch_list( &dom_list, &num_domains ); for ( i=0; ilength); if (key == NULL) { return false; } memcpy(key + keylen, req->data, req->length); pkey->dptr = (uint8_t *)key; pkey->dsize = talloc_get_size(key); return true; } static bool wcache_opnum_cacheable(uint32_t opnum) { switch (opnum) { case NDR_WBINT_LOOKUPSID: case NDR_WBINT_LOOKUPSIDS: case NDR_WBINT_LOOKUPNAME: case NDR_WBINT_SIDS2UNIXIDS: case NDR_WBINT_UNIXIDS2SIDS: case NDR_WBINT_GETNSSINFO: case NDR_WBINT_LOOKUPUSERALIASES: case NDR_WBINT_LOOKUPUSERGROUPS: case NDR_WBINT_LOOKUPGROUPMEMBERS: case NDR_WBINT_QUERYGROUPLIST: case NDR_WBINT_QUERYUSERRIDLIST: case NDR_WBINT_DSGETDCNAME: case NDR_WBINT_LOOKUPRIDS: return true; } return false; } bool wcache_fetch_ndr(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain, uint32_t opnum, const DATA_BLOB *req, DATA_BLOB *resp) { TDB_DATA key, data; bool ret = false; if (!wcache_opnum_cacheable(opnum) || is_my_own_sam_domain(domain) || is_builtin_domain(domain)) { return false; } if (wcache->tdb == NULL) { return false; } if (!wcache_ndr_key(talloc_tos(), domain->name, opnum, req, &key)) { return false; } data = tdb_fetch(wcache->tdb, key); TALLOC_FREE(key.dptr); if (data.dptr == NULL) { return false; } if (data.dsize < 12) { goto fail; } if (is_domain_online(domain)) { uint32_t entry_seqnum, dom_seqnum, last_check; uint64_t entry_timeout; if (!wcache_fetch_seqnum(domain->name, &dom_seqnum, &last_check)) { goto fail; } entry_seqnum = IVAL(data.dptr, 0); if (entry_seqnum != dom_seqnum) { DBG_DEBUG("Entry has wrong sequence number: %d\n", (int)entry_seqnum); goto fail; } entry_timeout = BVAL(data.dptr, 4); if (time(NULL) > (time_t)entry_timeout) { DBG_DEBUG("Entry has timed out\n"); goto fail; } } resp->data = (uint8_t *)talloc_memdup(mem_ctx, data.dptr + 12, data.dsize - 12); if (resp->data == NULL) { DBG_DEBUG("talloc failed\n"); goto fail; } resp->length = data.dsize - 12; ret = true; fail: SAFE_FREE(data.dptr); return ret; } void wcache_store_ndr(struct winbindd_domain *domain, uint32_t opnum, const DATA_BLOB *req, const DATA_BLOB *resp) { TDB_DATA key, data; uint32_t dom_seqnum, last_check; uint64_t timeout; if (!wcache_opnum_cacheable(opnum) || is_my_own_sam_domain(domain) || is_builtin_domain(domain)) { return; } if (wcache->tdb == NULL) { return; } if (!wcache_fetch_seqnum(domain->name, &dom_seqnum, &last_check)) { DBG_DEBUG("could not fetch seqnum for domain %s\n", domain->name); return; } if (!wcache_ndr_key(talloc_tos(), domain->name, opnum, req, &key)) { return; } timeout = time(NULL) + lp_winbind_cache_time(); data.dsize = resp->length + 12; data.dptr = talloc_array(key.dptr, uint8_t, data.dsize); if (data.dptr == NULL) { goto done; } SIVAL(data.dptr, 0, dom_seqnum); SBVAL(data.dptr, 4, timeout); memcpy(data.dptr + 12, resp->data, resp->length); tdb_store(wcache->tdb, key, data, 0); done: TALLOC_FREE(key.dptr); return; }