diff options
Diffstat (limited to 'source3/winbindd/winbindd_cache.c')
-rw-r--r-- | source3/winbindd/winbindd_cache.c | 4805 |
1 files changed, 4805 insertions, 0 deletions
diff --git a/source3/winbindd/winbindd_cache.c b/source3/winbindd/winbindd_cache.c new file mode 100644 index 0000000..de8cab0 --- /dev/null +++ b/source3/winbindd/winbindd_cache.c @@ -0,0 +1,4805 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#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 <gnutls/crypto.h> + +#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()) { + DEBUG(0, ("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) { + DEBUG(0,("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) { + DEBUG(0,("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) + DEBUG(10,("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) { + DEBUG(10, ("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) { + DEBUG(10,("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()) { + DEBUG(10,("fetch_cache_seqnum: timeout [%s][%u @ %u]\n", + domain->name, domain->sequence_number, + (uint32_t)domain->last_seq_check)); + return NT_STATUS_UNSUCCESSFUL; + } + + DEBUG(10,("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) { + DEBUG(10, ("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) { + DEBUG(10, ("tdb_store_bystring failed: %s\n", + tdb_errorstr(wcache->tdb))); + return false; + } + + DEBUG(10, ("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)) { + DEBUG(10, ("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: + DEBUG(10, ("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()) { + DEBUG(10,("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) { + DEBUG(10,("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)) { + DEBUG(10,("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)))) { + DEBUG(10,("centry_expired: Key %s for domain %s is good.\n", + keystr, domain->name )); + return false; + } + + DEBUG(10,("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? */ + DEBUG(10,("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)) { + + DEBUG(10,("wcache_fetch: entry %s expired for domain %s\n", + kstr, domain->name )); + + centry_free(centry); + free(kstr); + return NULL; + } + + DEBUG(10,("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) { + DEBUG(0,("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) { + DEBUG(10,("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); + + DEBUG(10,("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); + + DEBUG(10,("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); + + DEBUG(10,("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); + + DEBUG(10,("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); + + DEBUG(10,("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)) { + DEBUG(8,("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)"); + } + + DEBUG(5,("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); + + DEBUG(10,("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)) { + DEBUG(8,("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, '@')) { + DEBUG(10,("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)"); + } + + DEBUG(5,("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) { + DEBUG(10,("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 + * neet 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. */ + DEBUG(0,("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; + + DEBUG(10,("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)); + + DEBUG(10,("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; i<num_rids; i++) { + rids[i] = centry_uint32(centry); + } + +do_cached: + status = centry->status; + + DEBUG(10,("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 { + + DEBUG(10,("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)) { + DEBUG(3, ("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)) { + DEBUG(3, ("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; i<num_rids; i++) { + centry_put_uint32(centry, rids[i]); + } + centry_end(centry, "UL/%s", domain->name); + 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; + + DEBUG(10,("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; + + DEBUG(10,("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)) { + DEBUG(10, ("enum_local_groups: returning cached user list and server was down\n")); + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } else + status = centry->status; + + DEBUG(10,("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; + + DEBUG(10,("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); + + DEBUG(10,("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; + + DEBUG(10,("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; i<num_rids; i++) { + struct dom_sid sid; + NTSTATUS status; + enum lsa_SidType type; + char *dom, *name; + + if (!sid_compose(&sid, domain_sid, rids[i])) { + result = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + status = wcache_sid_to_name(domain, &sid, *names, &dom, + &name, &type); + + (*types)[i] = SID_NAME_UNKNOWN; + (*names)[i] = talloc_strdup(*names, ""); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + /* not cached */ + goto do_query; + } + + if (NT_STATUS_IS_OK(status)) { + have_mapped = true; + (*types)[i] = type; + + if (*domain_name == NULL) { + *domain_name = dom; + } else { + TALLOC_FREE(dom); + } + + (*names)[i] = name; + + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + have_unmapped = true; + } else { + /* something's definitely wrong */ + result = status; + goto error; + } + } + + if (!have_mapped) { + return NT_STATUS_NONE_MAPPED; + } + if (!have_unmapped) { + return NT_STATUS_OK; + } + return STATUS_SOME_UNMAPPED; + + do_query: + + TALLOC_FREE(*names); + TALLOC_FREE(*types); + + result = domain->backend->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; i<num_rids; i++) { + struct dom_sid sid; + NTSTATUS status; + enum lsa_SidType type; + char *dom, *name; + + if (!sid_compose(&sid, domain_sid, rids[i])) { + result = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + status = wcache_sid_to_name(domain, &sid, + *names, &dom, + &name, &type); + + (*types)[i] = SID_NAME_UNKNOWN; + (*names)[i] = talloc_strdup(*names, ""); + + if (NT_STATUS_IS_OK(status)) { + have_mapped = true; + (*types)[i] = type; + + if (*domain_name == NULL) { + *domain_name = dom; + } else { + TALLOC_FREE(dom); + } + + (*names)[i] = name; + + } else if (NT_STATUS_EQUAL( + status, + NT_STATUS_NONE_MAPPED)) { + have_unmapped = true; + } else { + /* something's definitely wrong */ + result = status; + goto error; + } + } + + if (!have_mapped) { + return NT_STATUS_NONE_MAPPED; + } + if (!have_unmapped) { + return NT_STATUS_OK; + } + return STATUS_SOME_UNMAPPED; + } + } + /* + None of the queried rids has been found so save all negative entries + */ + if (NT_STATUS_EQUAL(result, NT_STATUS_NONE_MAPPED)) { + for (i = 0; i < num_rids; i++) { + struct dom_sid sid; + const char *name = ""; + const enum lsa_SidType type = SID_NAME_UNKNOWN; + NTSTATUS status = NT_STATUS_NONE_MAPPED; + + if (!sid_compose(&sid, domain_sid, rids[i])) { + return NT_STATUS_INTERNAL_ERROR; + } + + wcache_save_sid_to_name(domain, status, &sid, *domain_name, + name, type); + } + + return result; + } + + /* + Some or all of the queried rids have been found. + */ + if (!NT_STATUS_IS_OK(result) && + !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) { + return result; + } + + refresh_sequence_number(domain); + + for (i=0; i<num_rids; i++) { + struct dom_sid sid; + NTSTATUS status; + + if (!sid_compose(&sid, domain_sid, rids[i])) { + result = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + status = (*types)[i] == SID_NAME_UNKNOWN ? + NT_STATUS_NONE_MAPPED : NT_STATUS_OK; + + wcache_save_sid_to_name(domain, status, &sid, *domain_name, + (*names)[i], (*types)[i]); + } + + return result; + + error: + TALLOC_FREE(*names); + TALLOC_FREE(*types); + return result; +} + +static NTSTATUS wcache_query_user(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + struct wbint_userinfo *info) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + struct dom_sid_buf sid_string; + + if (cache->tdb == 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); + } + + DEBUG(10,("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; i<num_sids; i++) { + centry_sid(centry, &sids[i]); + } + + status = centry->status; + + DEBUG(10,("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; + + DEBUG(10,("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; i<num_sids; i++) { + struct dom_sid_buf tmp; + sidlist = talloc_asprintf_append_buffer( + sidlist, + "/%s", + dom_sid_str_buf(&sids[i], &tmp)); + if (sidlist == NULL) { + return NULL; + } + } + return sidlist; +} + +static NTSTATUS wcache_lookup_useraliases(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, + const struct dom_sid *sids, + uint32_t *pnum_aliases, + uint32_t **paliases) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + uint32_t i, num_aliases; + uint32_t *aliases; + NTSTATUS status; + char *sidlist; + + if (cache->tdb == 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; i<num_aliases; i++) { + aliases[i] = centry_uint32(centry); + } + + status = centry->status; + + DEBUG(10,("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; + + DEBUG(10,("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_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; + + DEBUG(10,("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; + + DEBUG(10,("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: + DEBUG(10,("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; + + DEBUG(10,("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); + + DEBUG(10,("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; + + DEBUG(10,("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); + + DEBUG(10,("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)); + DEBUG(10, ("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)); + DEBUG(10, ("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); + + DEBUG(10, ("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); + + DEBUG(10, ("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) { + DEBUG(0,("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()) { + DEBUG(0,("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; + + DEBUG(0,("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) { + DEBUG(0,("initialize_winbindd_cache: unlink %s failed %s ", + db_path, + strerror(errno) )); + TALLOC_FREE(db_path); + return false; + } + TALLOC_FREE(db_path); + if (!init_wcache()) { + DEBUG(0,("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)) { + DEBUG(0,("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)) { + DEBUG(10,("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) { + DEBUG(0,("Failed to open winbindd_cache.tdb!\n")); + return; + } + + tdb_traverse(wcache->tdb, traverse_fn_cleanup, NULL); + + DEBUG(10,("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) { + DEBUG(0,("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; + + DEBUG(11,("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) { + DEBUG(10,("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_ERR("Offline requested\n"); + + if (wcache == NULL || wcache->tdb == NULL) { + DBG_ERR("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) +{ + DEBUG(10,("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? */ + DEBUG(0,("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) { + DEBUG(0,("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; + } + DEBUG(10,("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; + } + DEBUG(10,("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; + } + DEBUG(10,("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; + } + DEBUG(10,("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; + } + DEBUG(10,("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; + } + DEBUG(10,("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; + } + DEBUG(10,("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; + } + DEBUG(10,("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; + } + DEBUG(10,("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) { + DEBUG(0,("validate_dr: Corrupt cache for key %s (len == 0) ?\n", + keystr)); + state->bad_entry = true; + state->success = false; + return 1; + } + + DEBUG(10,("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) { + DEBUG(0,("validate_de: Corrupt cache for key %s (len == 0) ?\n", + keystr)); + state->bad_entry = true; + state->success = false; + return 1; + } + + DEBUG(10,("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; + } + DEBUG(10,("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) { + DEBUG(0, ("validate_trustdomcache: Corrupt cache for " + "key %s (len ==0) ?\n", keystr)); + state->bad_entry = true; + state->success = false; + return 1; + } + + DEBUG(10, ("validate_trustdomcache: %s ok\n", keystr)); + DEBUGADD(10, (" Don't trust me, I am a DUMMY!\n")); + 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) { + DEBUG(0,("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; + } + DEBUG(10,("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) { + DEBUG(0, ("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; + } + + DEBUG(10, ("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) { + DEBUG(0, ("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; + } + } + + DEBUG(0,("cache_traverse_validate_fn: unknown cache entry\nkey :\n")); + dump_data(0, (uint8_t *)kbuf.dptr, kbuf.dsize); + DEBUG(0,("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) +{ + DEBUG(0,("validating cache: would panic %s\n", why )); + DEBUGADD(0, ("exiting instead (cache validation mode)\n")); + 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) { + DEBUG(0, ("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) { + DEBUG(0, ("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; + + DEBUG(1, ("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; + + DEBUG(10, ("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) { + DEBUG(0, ("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)) { + DEBUG(10, ("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) { + DEBUG(10, ("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) { + DEBUG(10, ("winbindd_validate_cache: validation not successful.\n")); + DEBUGADD(10, ("removing tdb %s.\n", tdb_path)); + unlink(tdb_path); + } + +done: + TALLOC_FREE(tdb_path); + DEBUG(10, ("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; + + DEBUG(10, ("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) { + DEBUG(10, ("winbindd_validate_cache_nobackup: validation not " + "successful.\n")); + } + + TALLOC_FREE(tdb_path); +err_panic_restore: + DEBUG(10, ("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) { + DEBUG(0, ("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 ) ) { + DEBUG(10,("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 ) { + DEBUG(5,("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; + + DEBUG(10,("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<num_domains; i++ ) { + + struct dom_sid_buf tmp; + + if ( buflen > 0 ) { + DEBUG(10,("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 ) { + DEBUG(0,("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 ) { + DEBUG(5,("unpack_tdc_domains: Failed to unpack domain array\n")); + return 0; + } + + list = talloc_array( NULL, struct winbindd_tdc_domain, num_domains ); + if ( !list ) { + DEBUG(0,("unpack_tdc_domains: Failed to talloc() domain list!\n")); + return 0; + } + + for ( i=0; i<num_domains; i++ ) { + int this_len; + + this_len = tdb_unpack( buf+len, buflen-len, "fffddd", + domain_name, + dns_name, + sid_string, + &flags, + &attribs, + &type ); + + if ( this_len == -1 ) { + DEBUG(5,("unpack_tdc_domains: Failed to unpack domain array\n")); + TALLOC_FREE( list ); + return 0; + } + len += this_len; + + DEBUG(11,("unpack_tdc_domains: Unpacking domain %s (%s) " + "SID %s, flags = 0x%x, attribs = 0x%x, type = 0x%x\n", + domain_name, dns_name, sid_string, + flags, attribs, type)); + + list[i].domain_name = talloc_strdup( list, domain_name ); + list[i].dns_name = NULL; + if (dns_name[0] != '\0') { + list[i].dns_name = talloc_strdup(list, dns_name); + } + if ( !string_to_sid( &(list[i].sid), sid_string ) ) { + DEBUG(10,("unpack_tdc_domains: no SID for domain %s\n", + domain_name)); + } + list[i].trust_flags = flags; + list[i].trust_attribs = attribs; + list[i].trust_type = type; + } + + *domains = list; + + return num_domains; +} + +/********************************************************************* + ********************************************************************/ + +static bool wcache_tdc_store_list( struct winbindd_tdc_domain *domains, size_t num_domains ) +{ + TDB_DATA key = make_tdc_key( lp_workgroup() ); + TDB_DATA data = { NULL, 0 }; + int ret; + + if ( !key.dptr ) + return false; + + /* See if we were asked to delete the cache entry */ + + if ( !domains ) { + ret = tdb_delete( wcache->tdb, 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; + + DEBUG(10,("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; + + DEBUG(10,("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; i<num_domains; i++ ) { + if ( strequal(name, dom_list[i].domain_name) || + strequal(name, dom_list[i].dns_name) ) + { + DEBUG(10,("wcache_tdc_fetch_domain: Found domain %s\n", + name)); + + d = wcache_tdc_dup_domain(ctx, &dom_list[i]); + break; + } + } + + TALLOC_FREE( dom_list ); + + return d; +} + +/********************************************************************* + ********************************************************************/ + +void wcache_tdc_clear( void ) +{ + if ( !init_wcache() ) + return; + + wcache_tdc_store_list( NULL, 0 ); + + return; +} + +static bool wcache_ndr_key(TALLOC_CTX *mem_ctx, const char *domain_name, + uint32_t opnum, const DATA_BLOB *req, + TDB_DATA *pkey) +{ + char *key; + size_t keylen; + + key = talloc_asprintf(mem_ctx, "NDR/%s/%d/", domain_name, (int)opnum); + if (key == NULL) { + return false; + } + keylen = talloc_get_size(key) - 1; + + key = talloc_realloc(mem_ctx, key, char, keylen + req->length); + 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) { + DEBUG(10, ("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) { + DEBUG(10, ("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) { + DEBUG(10, ("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)) { + DEBUG(10, ("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; +} |