summaryrefslogtreecommitdiff
path: root/source3/winbindd
diff options
context:
space:
mode:
authorStefan Metzmacher <metze@samba.org>2007-09-14 12:03:58 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 12:30:46 -0500
commit28aa4bff8d6be031c6089fe5c7ab010f1cc48340 (patch)
treeea99511c562dd42b062c0bf70bea1db7c1a93cd0 /source3/winbindd
parent38b140fe70c809ca2f0a51ca2a617a29058b3c02 (diff)
downloadsamba-28aa4bff8d6be031c6089fe5c7ab010f1cc48340.tar.gz
samba-28aa4bff8d6be031c6089fe5c7ab010f1cc48340.tar.bz2
samba-28aa4bff8d6be031c6089fe5c7ab010f1cc48340.zip
r25154: move winbindd code into winbindd/
metze (This used to be commit 3ac7566ae14c48ff9b0f6b232e0ec4b2f73df558)
Diffstat (limited to 'source3/winbindd')
-rw-r--r--source3/winbindd/idmap.c1562
-rw-r--r--source3/winbindd/idmap_ad.c870
-rw-r--r--source3/winbindd/idmap_cache.c531
-rw-r--r--source3/winbindd/idmap_ldap.c1505
-rw-r--r--source3/winbindd/idmap_nss.c223
-rw-r--r--source3/winbindd/idmap_passdb.c139
-rw-r--r--source3/winbindd/idmap_rid.c267
-rw-r--r--source3/winbindd/idmap_tdb.c1232
-rw-r--r--source3/winbindd/idmap_util.c171
-rw-r--r--source3/winbindd/nss_info.c301
-rw-r--r--source3/winbindd/nss_info_template.c83
-rw-r--r--source3/winbindd/winbindd.c1254
-rw-r--r--source3/winbindd/winbindd.h365
-rw-r--r--source3/winbindd/winbindd_ads.c1312
-rw-r--r--source3/winbindd/winbindd_async.c1695
-rw-r--r--source3/winbindd/winbindd_cache.c3892
-rw-r--r--source3/winbindd/winbindd_ccache_access.c283
-rw-r--r--source3/winbindd/winbindd_cm.c2271
-rw-r--r--source3/winbindd/winbindd_cred_cache.c802
-rw-r--r--source3/winbindd/winbindd_creds.c162
-rw-r--r--source3/winbindd/winbindd_dual.c1130
-rw-r--r--source3/winbindd/winbindd_group.c1746
-rw-r--r--source3/winbindd/winbindd_misc.c634
-rw-r--r--source3/winbindd/winbindd_pam.c2382
-rw-r--r--source3/winbindd/winbindd_passdb.c467
-rw-r--r--source3/winbindd/winbindd_reconnect.c316
-rw-r--r--source3/winbindd/winbindd_rpc.c1111
-rw-r--r--source3/winbindd/winbindd_sid.c560
-rw-r--r--source3/winbindd/winbindd_sockinit.c126
-rw-r--r--source3/winbindd/winbindd_user.c875
-rw-r--r--source3/winbindd/winbindd_util.c1460
-rw-r--r--source3/winbindd/winbindd_wins.c234
32 files changed, 29961 insertions, 0 deletions
diff --git a/source3/winbindd/idmap.c b/source3/winbindd/idmap.c
new file mode 100644
index 0000000000..2c7acc185c
--- /dev/null
+++ b/source3/winbindd/idmap.c
@@ -0,0 +1,1562 @@
+/*
+ Unix SMB/CIFS implementation.
+ ID Mapping
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Simo Sorce 2003-2007
+ Copyright (C) Jeremy Allison 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+static_decl_idmap;
+
+struct idmap_backend {
+ const char *name;
+ struct idmap_methods *methods;
+ struct idmap_backend *prev, *next;
+};
+
+struct idmap_alloc_backend {
+ const char *name;
+ struct idmap_alloc_methods *methods;
+ struct idmap_alloc_backend *prev, *next;
+};
+
+struct idmap_cache_ctx;
+
+struct idmap_alloc_context {
+ const char *params;
+ struct idmap_alloc_methods *methods;
+ BOOL initialized;
+};
+
+static TALLOC_CTX *idmap_ctx = NULL;
+static struct idmap_cache_ctx *idmap_cache;
+
+static struct idmap_backend *backends = NULL;
+static struct idmap_domain **idmap_domains = NULL;
+static int num_domains = 0;
+static int pdb_dom_num = -1;
+static int def_dom_num = -1;
+
+static struct idmap_alloc_backend *alloc_backends = NULL;
+static struct idmap_alloc_context *idmap_alloc_ctx = NULL;
+
+#define IDMAP_CHECK_RET(ret) do { \
+ if ( ! NT_STATUS_IS_OK(ret)) { \
+ DEBUG(2, ("ERROR: NTSTATUS = 0x%08x\n", NT_STATUS_V(ret))); \
+ goto done; \
+ } } while(0)
+#define IDMAP_REPORT_RET(ret) do { \
+ if ( ! NT_STATUS_IS_OK(ret)) { \
+ DEBUG(2, ("ERROR: NTSTATUS = 0x%08x\n", NT_STATUS_V(ret))); \
+ } } while(0)
+#define IDMAP_CHECK_ALLOC(mem) do { \
+ if (!mem) { \
+ DEBUG(0, ("Out of memory!\n")); ret = NT_STATUS_NO_MEMORY; \
+ goto done; \
+ } } while(0)
+
+static struct idmap_methods *get_methods(struct idmap_backend *be,
+ const char *name)
+{
+ struct idmap_backend *b;
+
+ for (b = be; b; b = b->next) {
+ if (strequal(b->name, name)) {
+ return b->methods;
+ }
+ }
+
+ return NULL;
+}
+
+static struct idmap_alloc_methods *get_alloc_methods(
+ struct idmap_alloc_backend *be,
+ const char *name)
+{
+ struct idmap_alloc_backend *b;
+
+ for (b = be; b; b = b->next) {
+ if (strequal(b->name, name)) {
+ return b->methods;
+ }
+ }
+
+ return NULL;
+}
+
+BOOL idmap_is_offline(void)
+{
+ return ( lp_winbind_offline_logon() &&
+ get_global_winbindd_state_offline() );
+}
+
+/**********************************************************************
+ Allow a module to register itself as a method.
+**********************************************************************/
+
+NTSTATUS smb_register_idmap(int version, const char *name,
+ struct idmap_methods *methods)
+{
+ struct idmap_methods *test;
+ struct idmap_backend *entry;
+
+ if (!idmap_ctx) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if ((version != SMB_IDMAP_INTERFACE_VERSION)) {
+ DEBUG(0, ("Failed to register idmap module.\n"
+ "The module was compiled against "
+ "SMB_IDMAP_INTERFACE_VERSION %d,\n"
+ "current SMB_IDMAP_INTERFACE_VERSION is %d.\n"
+ "Please recompile against the current version "
+ "of samba!\n",
+ version, SMB_IDMAP_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !name[0] || !methods) {
+ DEBUG(0,("Called with NULL pointer or empty name!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ test = get_methods(backends, name);
+ if (test) {
+ DEBUG(0,("Idmap module %s already registered!\n", name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ entry = talloc(idmap_ctx, struct idmap_backend);
+ if ( ! entry) {
+ DEBUG(0,("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ entry->name = talloc_strdup(idmap_ctx, name);
+ if ( ! entry->name) {
+ DEBUG(0,("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ entry->methods = methods;
+
+ DLIST_ADD(backends, entry);
+ DEBUG(5, ("Successfully added idmap backend '%s'\n", name));
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Allow a module to register itself as a method.
+**********************************************************************/
+
+NTSTATUS smb_register_idmap_alloc(int version, const char *name,
+ struct idmap_alloc_methods *methods)
+{
+ struct idmap_alloc_methods *test;
+ struct idmap_alloc_backend *entry;
+
+ if (!idmap_ctx) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if ((version != SMB_IDMAP_INTERFACE_VERSION)) {
+ DEBUG(0, ("Failed to register idmap alloc module.\n"
+ "The module was compiled against "
+ "SMB_IDMAP_INTERFACE_VERSION %d,\n"
+ "current SMB_IDMAP_INTERFACE_VERSION is %d.\n"
+ "Please recompile against the current version "
+ "of samba!\n",
+ version, SMB_IDMAP_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !name[0] || !methods) {
+ DEBUG(0,("Called with NULL pointer or empty name!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ test = get_alloc_methods(alloc_backends, name);
+ if (test) {
+ DEBUG(0,("idmap_alloc module %s already registered!\n", name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ entry = talloc(idmap_ctx, struct idmap_alloc_backend);
+ if ( ! entry) {
+ DEBUG(0,("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ entry->name = talloc_strdup(idmap_ctx, name);
+ if ( ! entry->name) {
+ DEBUG(0,("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ entry->methods = methods;
+
+ DLIST_ADD(alloc_backends, entry);
+ DEBUG(5, ("Successfully added idmap alloc backend '%s'\n", name));
+ return NT_STATUS_OK;
+}
+
+static int close_domain_destructor(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+
+ ret = dom->methods->close_fn(dom);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(3, ("Failed to close idmap domain [%s]!\n", dom->name));
+ }
+
+ return 0;
+}
+
+/**************************************************************************
+ Shutdown.
+**************************************************************************/
+
+NTSTATUS idmap_close(void)
+{
+ /* close the alloc backend first before freeing idmap_ctx */
+ if (idmap_alloc_ctx) {
+ idmap_alloc_ctx->methods->close_fn();
+ idmap_alloc_ctx->methods = NULL;
+ }
+ alloc_backends = NULL;
+
+ /* this talloc_free call will fire the talloc destructors
+ * that will free all active backends resources */
+ TALLOC_FREE(idmap_ctx);
+ idmap_cache = NULL;
+ idmap_domains = NULL;
+ backends = NULL;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Initialise idmap cache and a remote backend (if configured).
+**********************************************************************/
+
+static const char *idmap_default_domain[] = { "default domain", NULL };
+
+/****************************************************************************
+ ****************************************************************************/
+
+NTSTATUS idmap_init_cache(void)
+{
+ /* Always initialize the cache. We'll have to delay initialization
+ of backends if we are offline */
+
+ if ( idmap_ctx ) {
+ return NT_STATUS_OK;
+ }
+
+ if ( (idmap_ctx = talloc_named_const(NULL, 0, "idmap_ctx")) == NULL ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( (idmap_cache = idmap_cache_init(idmap_ctx)) == NULL ) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+
+NTSTATUS idmap_init(void)
+{
+ NTSTATUS ret;
+ static NTSTATUS idmap_init_status = NT_STATUS_UNSUCCESSFUL;
+ struct idmap_domain *dom;
+ char *compat_backend = NULL;
+ char *compat_params = NULL;
+ const char **dom_list = NULL;
+ char *alloc_backend = NULL;
+ BOOL default_already_defined = False;
+ BOOL pri_dom_is_in_list = False;
+ int compat = 0;
+ int i;
+
+ ret = idmap_init_cache();
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ if (NT_STATUS_IS_OK(idmap_init_status)) {
+ return NT_STATUS_OK;
+ }
+
+ /* We can't reliably call intialization code here unless
+ we are online. But return NT_STATUS_OK so the upper
+ level code doesn't abort idmap lookups. */
+
+ if ( get_global_winbindd_state_offline() ) {
+ idmap_init_status = NT_STATUS_FILE_IS_OFFLINE;
+ return NT_STATUS_OK;
+ }
+
+ static_init_idmap;
+
+ dom_list = lp_idmap_domains();
+
+ if ( lp_idmap_backend() ) {
+ const char **compat_list = lp_idmap_backend();
+ char *p = NULL;
+ const char *q = NULL;
+
+ if ( dom_list ) {
+ DEBUG(0, ("WARNING: idmap backend and idmap domains are"
+ " mutually exclusive!\n"));
+ DEBUGADD(0,("idmap backend option will be IGNORED!\n"));
+ } else {
+ compat = 1;
+
+ compat_backend = talloc_strdup(idmap_ctx, *compat_list);
+ if (compat_backend == NULL ) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* strip any leading idmap_ prefix of */
+ if (strncmp(*compat_list, "idmap_", 6) == 0 ) {
+ q = *compat_list += 6;
+ DEBUG(0, ("WARNING: idmap backend uses obsolete"
+ " and deprecated 'idmap_' prefix.\n"
+ "Please replace 'idmap_%s' by '%s' in"
+ " %s\n", q, q, dyn_CONFIGFILE));
+ compat_backend = talloc_strdup(idmap_ctx, q);
+ } else {
+ compat_backend = talloc_strdup(idmap_ctx,
+ *compat_list);
+ }
+
+ /* separate the backend and module arguements */
+ if ((p = strchr(compat_backend, ':')) != NULL) {
+ *p = '\0';
+ compat_params = p + 1;
+ }
+ }
+ } else if ( !dom_list ) {
+ /* Back compatible: without idmap domains and explicit
+ idmap backend. Taking default idmap backend: tdb */
+
+ compat = 1;
+ compat_backend = talloc_strdup( idmap_ctx, "tdb");
+ compat_params = compat_backend;
+ }
+
+ if ( ! dom_list) {
+ dom_list = idmap_default_domain;
+ }
+
+ /***************************
+ * initialize idmap domains
+ */
+ DEBUG(1, ("Initializing idmap domains\n"));
+
+ for (i = 0; dom_list[i]; i++) {
+ const char *parm_backend;
+ char *config_option;
+
+ /* ignore BUILTIN and local MACHINE domains */
+ if (strequal(dom_list[i], "BUILTIN")
+ || strequal(dom_list[i], get_global_sam_name()))
+ {
+ DEBUG(0,("idmap_init: Ignoring invalid domain %s\n",
+ dom_list[i]));
+ continue;
+ }
+
+ if (strequal(dom_list[i], lp_workgroup())) {
+ pri_dom_is_in_list = True;
+ }
+ /* init domain */
+
+ dom = TALLOC_ZERO_P(idmap_ctx, struct idmap_domain);
+ IDMAP_CHECK_ALLOC(dom);
+
+ dom->name = talloc_strdup(dom, dom_list[i]);
+ IDMAP_CHECK_ALLOC(dom->name);
+
+ config_option = talloc_asprintf(dom, "idmap config %s",
+ dom->name);
+ IDMAP_CHECK_ALLOC(config_option);
+
+ /* default or specific ? */
+
+ dom->default_domain = lp_parm_bool(-1, config_option,
+ "default", False);
+
+ if (dom->default_domain ||
+ strequal(dom_list[i], idmap_default_domain[0])) {
+
+ /* make sure this is set even when we match
+ * idmap_default_domain[0] */
+ dom->default_domain = True;
+
+ if (default_already_defined) {
+ DEBUG(1, ("ERROR: Multiple domains defined as"
+ " default!\n"));
+ ret = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ default_already_defined = True;
+
+ }
+
+ dom->readonly = lp_parm_bool(-1, config_option,
+ "readonly", False);
+
+ /* find associated backend (default: tdb) */
+ if (compat) {
+ parm_backend = talloc_strdup(idmap_ctx, compat_backend);
+ } else {
+ parm_backend = talloc_strdup(idmap_ctx,
+ lp_parm_const_string(
+ -1, config_option,
+ "backend", "tdb"));
+ }
+ IDMAP_CHECK_ALLOC(parm_backend);
+
+ /* get the backend methods for this domain */
+ dom->methods = get_methods(backends, parm_backend);
+
+ if ( ! dom->methods) {
+ ret = smb_probe_module("idmap", parm_backend);
+ if (NT_STATUS_IS_OK(ret)) {
+ dom->methods = get_methods(backends,
+ parm_backend);
+ }
+ }
+ if ( ! dom->methods) {
+ DEBUG(0, ("ERROR: Could not get methods for "
+ "backend %s\n", parm_backend));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* check the set_mapping function exists otherwise mark the
+ * module as readonly */
+ if ( ! dom->methods->set_mapping) {
+ DEBUG(5, ("Forcing to readonly, as this module can't"
+ " store arbitrary mappings.\n"));
+ dom->readonly = True;
+ }
+
+ /* now that we have methods,
+ * set the destructor for this domain */
+ talloc_set_destructor(dom, close_domain_destructor);
+
+ if (compat_params) {
+ dom->params = talloc_strdup(dom, compat_params);
+ IDMAP_CHECK_ALLOC(dom->params);
+ } else {
+ dom->params = NULL;
+ }
+
+ /* Finally instance a backend copy for this domain */
+ ret = dom->methods->init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ DEBUG(0, ("ERROR: Initialization failed for backend "
+ "%s (domain %s), deferred!\n",
+ parm_backend, dom->name));
+ }
+ idmap_domains = talloc_realloc(idmap_ctx, idmap_domains,
+ struct idmap_domain *, i+1);
+ if ( ! idmap_domains) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ idmap_domains[i] = dom;
+
+ /* save default domain position for future uses */
+ if (dom->default_domain) {
+ def_dom_num = i;
+ }
+
+ DEBUG(10, ("Domain %s - Backend %s - %sdefault - %sreadonly\n",
+ dom->name, parm_backend,
+ dom->default_domain?"":"not ",
+ dom->readonly?"":"not "));
+
+ talloc_free(config_option);
+ }
+
+ /* save the number of domains we have */
+ num_domains = i;
+
+ /* automatically add idmap_nss backend if needed */
+ if ((lp_server_role() == ROLE_DOMAIN_MEMBER) &&
+ ( ! pri_dom_is_in_list) &&
+ lp_winbind_trusted_domains_only()) {
+
+ dom = TALLOC_ZERO_P(idmap_ctx, struct idmap_domain);
+ IDMAP_CHECK_ALLOC(dom);
+
+ dom->name = talloc_strdup(dom, lp_workgroup());
+ IDMAP_CHECK_ALLOC(dom->name);
+
+ dom->default_domain = False;
+ dom->readonly = True;
+
+ /* get the backend methods for passdb */
+ dom->methods = get_methods(backends, "nss");
+
+ /* (the nss module is always statically linked) */
+ if ( ! dom->methods) {
+ DEBUG(0, ("ERROR: No methods for idmap_nss ?!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* now that we have methods,
+ * set the destructor for this domain */
+ talloc_set_destructor(dom, close_domain_destructor);
+
+ if (compat_params) {
+ dom->params = talloc_strdup(dom, compat_params);
+ IDMAP_CHECK_ALLOC(dom->params);
+ } else {
+ dom->params = NULL;
+ }
+
+ /* Finally instance a backend copy for this domain */
+ ret = dom->methods->init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ DEBUG(0, ("ERROR: Init. failed for idmap_nss ?!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ idmap_domains = talloc_realloc(idmap_ctx,
+ idmap_domains,
+ struct idmap_domain *,
+ num_domains+1);
+ if ( ! idmap_domains) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ idmap_domains[num_domains] = dom;
+
+ DEBUG(10, ("Domain %s - Backend nss - not default - readonly\n",
+ dom->name ));
+
+ num_domains++;
+ }
+
+ /**** automatically add idmap_passdb backend ****/
+ dom = TALLOC_ZERO_P(idmap_ctx, struct idmap_domain);
+ IDMAP_CHECK_ALLOC(dom);
+
+ dom->name = talloc_strdup(dom, get_global_sam_name());
+ IDMAP_CHECK_ALLOC(dom->name);
+
+ dom->default_domain = False;
+ dom->readonly = True;
+
+ /* get the backend methods for passdb */
+ dom->methods = get_methods(backends, "passdb");
+
+ /* (the passdb module is always statically linked) */
+ if ( ! dom->methods) {
+ DEBUG(0, ("ERROR: No methods for idmap_passdb ?!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* now that we have methods, set the destructor for this domain */
+ talloc_set_destructor(dom, close_domain_destructor);
+
+ if (compat_params) {
+ dom->params = talloc_strdup(dom, compat_params);
+ IDMAP_CHECK_ALLOC(dom->params);
+ } else {
+ dom->params = NULL;
+ }
+
+ /* Finally instance a backend copy for this domain */
+ ret = dom->methods->init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ DEBUG(0, ("ERROR: Init. failed for idmap_passdb ?!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ idmap_domains = talloc_realloc(idmap_ctx,
+ idmap_domains,
+ struct idmap_domain *,
+ num_domains+1);
+ if ( ! idmap_domains) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ idmap_domains[num_domains] = dom;
+
+ /* needed to handle special BUILTIN and wellknown SIDs cases */
+ pdb_dom_num = num_domains;
+
+ DEBUG(10, ("Domain %s - Backend passdb - not default - readonly\n",
+ dom->name));
+
+ num_domains++;
+ /**** finished adding idmap_passdb backend ****/
+
+ /* sort domains so that the default is the last one */
+ /* don't sort if no default domain defined */
+ if (def_dom_num != -1 && def_dom_num != num_domains-1) {
+ /* default is not last, move it */
+ struct idmap_domain *tmp;
+
+ if (pdb_dom_num > def_dom_num) {
+ pdb_dom_num --;
+
+ } else if (pdb_dom_num == def_dom_num) { /* ?? */
+ pdb_dom_num = num_domains - 1;
+ }
+
+ tmp = idmap_domains[def_dom_num];
+
+ for (i = def_dom_num; i < num_domains-1; i++) {
+ idmap_domains[i] = idmap_domains[i+1];
+ }
+ idmap_domains[i] = tmp;
+ def_dom_num = i;
+ }
+
+
+ /* Initialize alloc module */
+
+ DEBUG(3, ("Initializing idmap alloc module\n"));
+
+ alloc_backend = NULL;
+ if (compat) {
+ alloc_backend = talloc_strdup(idmap_ctx, compat_backend);
+ } else {
+ char *ab = lp_idmap_alloc_backend();
+
+ if (ab && (ab[0] != '\0')) {
+ alloc_backend = talloc_strdup(idmap_ctx,
+ lp_idmap_alloc_backend());
+ }
+ }
+
+ if ( alloc_backend ) {
+
+ idmap_alloc_ctx = TALLOC_ZERO_P(idmap_ctx,
+ struct idmap_alloc_context);
+ IDMAP_CHECK_ALLOC(idmap_alloc_ctx);
+
+ idmap_alloc_ctx->methods = get_alloc_methods(alloc_backends,
+ alloc_backend);
+ if ( ! idmap_alloc_ctx->methods) {
+ ret = smb_probe_module("idmap", alloc_backend);
+ if (NT_STATUS_IS_OK(ret)) {
+ idmap_alloc_ctx->methods =
+ get_alloc_methods(alloc_backends,
+ alloc_backend);
+ }
+ }
+ if (idmap_alloc_ctx->methods) {
+
+ if (compat_params) {
+ idmap_alloc_ctx->params =
+ talloc_strdup(idmap_alloc_ctx,
+ compat_params);
+ IDMAP_CHECK_ALLOC(idmap_alloc_ctx->params);
+ } else {
+ idmap_alloc_ctx->params = NULL;
+ }
+
+ ret = idmap_alloc_ctx->methods->init(idmap_alloc_ctx->params);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ DEBUG(0, ("ERROR: Initialization failed for "
+ "alloc backend %s, deferred!\n",
+ alloc_backend));
+ } else {
+ idmap_alloc_ctx->initialized = True;
+ }
+ } else {
+ DEBUG(2, ("idmap_init: Unable to get methods for "
+ "alloc backend %s\n",
+ alloc_backend));
+ /* certain compat backends are just readonly */
+ if ( compat ) {
+ TALLOC_FREE(idmap_alloc_ctx);
+ ret = NT_STATUS_OK;
+ } else {
+ ret = NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+ }
+
+ /* cleanpu temporary strings */
+ TALLOC_FREE( compat_backend );
+
+ idmap_init_status = NT_STATUS_OK;
+
+ return ret;
+
+done:
+ DEBUG(0, ("Aborting IDMAP Initialization ...\n"));
+ idmap_close();
+
+ return ret;
+}
+
+static NTSTATUS idmap_alloc_init(void)
+{
+ NTSTATUS ret;
+
+ if (! NT_STATUS_IS_OK(ret = idmap_init())) {
+ return ret;
+ }
+
+ if ( ! idmap_alloc_ctx) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if ( ! idmap_alloc_ctx->initialized) {
+ ret = idmap_alloc_ctx->methods->init(idmap_alloc_ctx->params);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ DEBUG(0, ("ERROR: Initialization failed for alloc "
+ "backend, deferred!\n"));
+ return ret;
+ } else {
+ idmap_alloc_ctx->initialized = True;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**************************************************************************
+ idmap allocator interface functions
+**************************************************************************/
+
+NTSTATUS idmap_allocate_uid(struct unixid *id)
+{
+ NTSTATUS ret;
+
+ if (! NT_STATUS_IS_OK(ret = idmap_alloc_init())) {
+ return ret;
+ }
+
+ id->type = ID_TYPE_UID;
+ return idmap_alloc_ctx->methods->allocate_id(id);
+}
+
+NTSTATUS idmap_allocate_gid(struct unixid *id)
+{
+ NTSTATUS ret;
+
+ if (! NT_STATUS_IS_OK(ret = idmap_alloc_init())) {
+ return ret;
+ }
+
+ id->type = ID_TYPE_GID;
+ return idmap_alloc_ctx->methods->allocate_id(id);
+}
+
+NTSTATUS idmap_set_uid_hwm(struct unixid *id)
+{
+ NTSTATUS ret;
+
+ if (! NT_STATUS_IS_OK(ret = idmap_alloc_init())) {
+ return ret;
+ }
+
+ id->type = ID_TYPE_UID;
+ return idmap_alloc_ctx->methods->set_id_hwm(id);
+}
+
+NTSTATUS idmap_set_gid_hwm(struct unixid *id)
+{
+ NTSTATUS ret;
+
+ if (! NT_STATUS_IS_OK(ret = idmap_alloc_init())) {
+ return ret;
+ }
+
+ id->type = ID_TYPE_GID;
+ return idmap_alloc_ctx->methods->set_id_hwm(id);
+}
+
+/******************************************************************************
+ Lookup an idmap_domain give a full user or group SID
+ ******************************************************************************/
+
+static struct idmap_domain* find_idmap_domain_from_sid( DOM_SID *account_sid )
+{
+ DOM_SID domain_sid;
+ uint32 rid;
+ struct winbindd_domain *domain = NULL;
+ int i;
+
+ /* 1. Handle BUILTIN or Special SIDs and prevent them from
+ falling into the default domain space (if we have a
+ configured passdb backend. */
+
+ if ( (pdb_dom_num != -1) &&
+ (sid_check_is_in_builtin(account_sid) ||
+ sid_check_is_in_wellknown_domain(account_sid) ||
+ sid_check_is_in_unix_groups(account_sid) ||
+ sid_check_is_in_unix_users(account_sid)) )
+ {
+ return idmap_domains[pdb_dom_num];
+ }
+
+ /* 2. Lookup the winbindd_domain from the account_sid */
+
+ sid_copy( &domain_sid, account_sid );
+ sid_split_rid( &domain_sid, &rid );
+ domain = find_domain_from_sid_noinit( &domain_sid );
+
+ for (i = 0; domain && i < num_domains; i++) {
+ if ( strequal( idmap_domains[i]->name, domain->name ) ) {
+ return idmap_domains[i];
+ }
+ }
+
+ /* 3. Fall back to the default domain */
+
+ if ( def_dom_num != -1 ) {
+ return idmap_domains[def_dom_num];
+ }
+
+ return NULL;
+}
+
+/******************************************************************************
+ Lookup an index given an idmap_domain pointer
+ ******************************************************************************/
+
+static uint32 find_idmap_domain_index( struct idmap_domain *id_domain)
+{
+ int i;
+
+ for (i = 0; i < num_domains; i++) {
+ if ( idmap_domains[i] == id_domain )
+ return i;
+ }
+
+ return -1;
+}
+
+
+/*********************************************************
+ Check if creating a mapping is permitted for the domain
+*********************************************************/
+
+static NTSTATUS idmap_can_map(const struct id_map *map,
+ struct idmap_domain **ret_dom)
+{
+ struct idmap_domain *dom;
+
+ /* Check we do not create mappings for our own local domain,
+ * or BUILTIN or special SIDs */
+ if ((sid_compare_domain(map->sid, get_global_sam_sid()) == 0) ||
+ sid_check_is_in_builtin(map->sid) ||
+ sid_check_is_in_wellknown_domain(map->sid) ||
+ sid_check_is_in_unix_users(map->sid) ||
+ sid_check_is_in_unix_groups(map->sid) )
+ {
+ DEBUG(10, ("We are not supposed to create mappings for our own "
+ "domains (local, builtin, specials)\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* Special check for trusted domain only = Yes */
+ if (lp_winbind_trusted_domains_only()) {
+ struct winbindd_domain *wdom = find_our_domain();
+ if (wdom && (sid_compare_domain(map->sid, &wdom->sid) == 0)) {
+ DEBUG(10, ("We are not supposed to create mappings for "
+ "our primary domain when <trusted domain "
+ "only> is True\n"));
+ DEBUGADD(10, ("Leave [%s] unmapped\n",
+ sid_string_static(map->sid)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ if ( (dom = find_idmap_domain_from_sid( map->sid )) == NULL ) {
+ /* huh, couldn't find a suitable domain,
+ * let's just leave it unmapped */
+ DEBUG(10, ("Could not find idmap backend for SID %s",
+ sid_string_static(map->sid)));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ if (dom->readonly) {
+ /* ouch the domain is read only,
+ * let's just leave it unmapped */
+ DEBUG(10, ("idmap backend for SID %s is READONLY!\n",
+ sid_string_static(map->sid)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *ret_dom = dom;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_new_mapping(TALLOC_CTX *ctx, struct id_map *map)
+{
+ NTSTATUS ret;
+ struct idmap_domain *dom;
+
+ /* If we are offline we cannot lookup SIDs, deny mapping */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ ret = idmap_can_map(map, &dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ /* check if this is a valid SID and then map it */
+ switch (map->xid.type) {
+ case ID_TYPE_UID:
+ ret = idmap_allocate_uid(&map->xid);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ /* can't allocate id, let's just leave it unmapped */
+ DEBUG(2, ("uid allocation failed! "
+ "Can't create mapping\n"));
+ return NT_STATUS_NONE_MAPPED;
+ }
+ break;
+ case ID_TYPE_GID:
+ ret = idmap_allocate_gid(&map->xid);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ /* can't allocate id, let's just leave it unmapped */
+ DEBUG(2, ("gid allocation failed! "
+ "Can't create mapping\n"));
+ return NT_STATUS_NONE_MAPPED;
+ }
+ break;
+ default:
+ /* invalid sid, let's just leave it unmapped */
+ DEBUG(3,("idmap_new_mapping: Refusing to create a "
+ "mapping for an unspecified ID type.\n"));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ /* ok, got a new id, let's set a mapping */
+ map->status = ID_MAPPED;
+
+ DEBUG(10, ("Setting mapping: %s <-> %s %lu\n",
+ sid_string_static(map->sid),
+ (map->xid.type == ID_TYPE_UID) ? "UID" : "GID",
+ (unsigned long)map->xid.id));
+ ret = dom->methods->set_mapping(dom, map);
+
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ /* something wrong here :-( */
+ DEBUG(2, ("Failed to commit mapping\n!"));
+
+ /* TODO: would it make sense to have an "unalloc_id function?" */
+
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_backends_set_mapping(const struct id_map *map)
+{
+ struct idmap_domain *dom;
+ NTSTATUS ret;
+
+ DEBUG(10, ("Setting mapping %s <-> %s %lu\n",
+ sid_string_static(map->sid),
+ (map->xid.type == ID_TYPE_UID) ? "UID" : "GID",
+ (unsigned long)map->xid.id));
+
+ ret = idmap_can_map(map, &dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+
+ DEBUG(10,("set_mapping for domain %s\n", dom->name ));
+
+ return dom->methods->set_mapping(dom, map);
+}
+
+static NTSTATUS idmap_backends_unixids_to_sids(struct id_map **ids)
+{
+ struct idmap_domain *dom;
+ struct id_map **unmapped;
+ struct id_map **_ids;
+ TALLOC_CTX *ctx;
+ NTSTATUS ret;
+ int i, u, n;
+
+ if (!ids || !*ids) {
+ DEBUG(1, ("Invalid list of maps\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ctx = talloc_named_const(NULL, 0, "idmap_backends_unixids_to_sids ctx");
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10, ("Query backends to map ids->sids\n"));
+
+ /* start from the default (the last one) and then if there are still
+ * unmapped entries cycle through the others */
+
+ _ids = ids;
+
+ unmapped = NULL;
+ for (n = num_domains-1; n >= 0; n--) { /* cycle backwards */
+
+ dom = idmap_domains[n];
+
+ DEBUG(10, ("Query sids from domain %s\n", dom->name));
+
+ ret = dom->methods->unixids_to_sids(dom, _ids);
+ IDMAP_REPORT_RET(ret);
+
+ unmapped = NULL;
+
+ for (i = 0, u = 0; _ids[i]; i++) {
+ if (_ids[i]->status != ID_MAPPED) {
+ unmapped = talloc_realloc(ctx, unmapped,
+ struct id_map *, u + 2);
+ IDMAP_CHECK_ALLOC(unmapped);
+ unmapped[u] = _ids[i];
+ u++;
+ }
+ }
+ if (unmapped) {
+ /* terminate the unmapped list */
+ unmapped[u] = NULL;
+ } else { /* no more entries, get out */
+ break;
+ }
+
+ _ids = unmapped;
+
+ }
+
+ if (unmapped) {
+ /* there are still unmapped ids,
+ * map them to the unix users/groups domains */
+ /* except for expired entries,
+ * these will be returned as valid (offline mode) */
+ for (i = 0; unmapped[i]; i++) {
+ if (unmapped[i]->status == ID_EXPIRED) continue;
+ switch (unmapped[i]->xid.type) {
+ case ID_TYPE_UID:
+ uid_to_unix_users_sid(
+ (uid_t)unmapped[i]->xid.id,
+ unmapped[i]->sid);
+ unmapped[i]->status = ID_MAPPED;
+ break;
+ case ID_TYPE_GID:
+ gid_to_unix_groups_sid(
+ (gid_t)unmapped[i]->xid.id,
+ unmapped[i]->sid);
+ unmapped[i]->status = ID_MAPPED;
+ break;
+ default: /* what?! */
+ unmapped[i]->status = ID_UNKNOWN;
+ break;
+ }
+ }
+ }
+
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+static NTSTATUS idmap_backends_sids_to_unixids(struct id_map **ids)
+{
+ struct id_map ***dom_ids;
+ struct idmap_domain *dom;
+ TALLOC_CTX *ctx;
+ NTSTATUS ret;
+ int i, *counters;
+
+ if ( (ctx = talloc_named_const(NULL, 0, "be_sids_to_ids")) == NULL ) {
+ DEBUG(1, ("failed to allocate talloc context, OOM?\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10, ("Query backends to map sids->ids\n"));
+
+ /* split list per domain */
+ if (num_domains == 0) {
+ DEBUG(1, ("No domains available?\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ dom_ids = TALLOC_ZERO_ARRAY(ctx, struct id_map **, num_domains);
+ IDMAP_CHECK_ALLOC(dom_ids);
+ counters = TALLOC_ZERO_ARRAY(ctx, int, num_domains);
+ IDMAP_CHECK_ALLOC(counters);
+
+ /* partition the requests by domain */
+
+ for (i = 0; ids[i]; i++) {
+ uint32 idx;
+
+ if ((dom = find_idmap_domain_from_sid(ids[i]->sid)) == NULL) {
+ /* no available idmap_domain. Move on */
+ continue;
+ }
+
+ DEBUG(10,("SID %s is being handled by %s\n",
+ sid_string_static(ids[i]->sid),
+ dom ? dom->name : "none" ));
+
+ idx = find_idmap_domain_index( dom );
+ SMB_ASSERT( idx != -1 );
+
+ dom_ids[idx] = talloc_realloc(ctx, dom_ids[idx],
+ struct id_map *,
+ counters[idx] + 2);
+ IDMAP_CHECK_ALLOC(dom_ids[idx]);
+
+ dom_ids[idx][counters[idx]] = ids[i];
+ counters[idx]++;
+ dom_ids[idx][counters[idx]] = NULL;
+ }
+
+ /* All the ids have been dispatched in the right queues.
+ Let's cycle through the filled ones */
+
+ for (i = 0; i < num_domains; i++) {
+ if (dom_ids[i]) {
+ dom = idmap_domains[i];
+ DEBUG(10, ("Query ids from domain %s\n", dom->name));
+ ret = dom->methods->sids_to_unixids(dom, dom_ids[i]);
+ IDMAP_REPORT_RET(ret);
+ }
+ }
+
+ /* ok all the backends have been contacted at this point */
+ /* let's see if we have any unmapped SID left and act accordingly */
+
+ for (i = 0; ids[i]; i++) {
+ /* NOTE: this will NOT touch ID_EXPIRED entries that the backend
+ * was not able to confirm/deny (offline mode) */
+ if (ids[i]->status == ID_UNKNOWN ||
+ ids[i]->status == ID_UNMAPPED) {
+ /* ok this is an unmapped one, see if we can map it */
+ ret = idmap_new_mapping(ctx, ids[i]);
+ if (NT_STATUS_IS_OK(ret)) {
+ /* successfully mapped */
+ ids[i]->status = ID_MAPPED;
+ } else
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) {
+ /* could not map it */
+ ids[i]->status = ID_UNMAPPED;
+ } else {
+ /* Something very bad happened down there
+ * OR we are offline */
+ ids[i]->status = ID_UNKNOWN;
+ }
+ }
+ }
+
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+/**************************************************************************
+ idmap interface functions
+**************************************************************************/
+
+NTSTATUS idmap_unixids_to_sids(struct id_map **ids)
+{
+ TALLOC_CTX *ctx;
+ NTSTATUS ret;
+ struct id_map **bids;
+ int i, bi;
+ int bn = 0;
+ struct winbindd_domain *our_domain = find_our_domain();
+
+ if (! NT_STATUS_IS_OK(ret = idmap_init())) {
+ return ret;
+ }
+
+ if (!ids || !*ids) {
+ DEBUG(1, ("Invalid list of maps\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ctx = talloc_named_const(NULL, 0, "idmap_unixids_to_sids ctx");
+ if ( ! ctx) {
+ DEBUG(1, ("failed to allocate talloc context, OOM?\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* no ids to be asked to the backends by default */
+ bids = NULL;
+ bi = 0;
+
+ for (i = 0; ids[i]; i++) {
+
+ if ( ! ids[i]->sid) {
+ DEBUG(1, ("invalid null SID in id_map array"));
+ talloc_free(ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = idmap_cache_map_id(idmap_cache, ids[i]);
+
+ if ( ! NT_STATUS_IS_OK(ret)) {
+
+ if ( ! bids) {
+ /* alloc space for ids to be resolved by
+ * backends (realloc ten by ten) */
+ bids = TALLOC_ARRAY(ctx, struct id_map *, 10);
+ if ( ! bids) {
+ DEBUG(1, ("Out of memory!\n"));
+ talloc_free(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ bn = 10;
+ }
+
+ /* add this id to the ones to be retrieved
+ * from the backends */
+ bids[bi] = ids[i];
+ bi++;
+
+ /* check if we need to allocate new space
+ * on the rids array */
+ if (bi == bn) {
+ bn += 10;
+ bids = talloc_realloc(ctx, bids,
+ struct id_map *, bn);
+ if ( ! bids) {
+ DEBUG(1, ("Out of memory!\n"));
+ talloc_free(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* make sure the last element is NULL */
+ bids[bi] = NULL;
+ }
+ }
+
+ /* let's see if there is any id mapping to be retieved
+ * from the backends */
+ if (bi) {
+ /* Only do query if we are online */
+ if ( IS_DOMAIN_OFFLINE(our_domain) ) {
+ ret = NT_STATUS_FILE_IS_OFFLINE;
+ goto done;
+ }
+
+ ret = idmap_backends_unixids_to_sids(bids);
+ IDMAP_CHECK_RET(ret);
+
+ /* update the cache */
+ for (i = 0; i < bi; i++) {
+ if (bids[i]->status == ID_MAPPED) {
+ ret = idmap_cache_set(idmap_cache, bids[i]);
+ } else if (bids[i]->status == ID_EXPIRED) {
+ /* the cache returned an expired entry and the
+ * backend was not able to clear the situation
+ * (offline). This handles a previous
+ * NT_STATUS_SYNCHRONIZATION_REQUIRED
+ * for disconnected mode, */
+ bids[i]->status = ID_MAPPED;
+ } else if (bids[i]->status == ID_UNKNOWN) {
+ /* something bad here. We were not able to
+ * handle this for some reason, mark it as
+ * unmapped and hope next time things will
+ * settle down. */
+ bids[i]->status = ID_UNMAPPED;
+ } else { /* unmapped */
+ ret = idmap_cache_set_negative_id(idmap_cache,
+ bids[i]);
+ }
+ IDMAP_CHECK_RET(ret);
+ }
+ }
+
+ ret = NT_STATUS_OK;
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+NTSTATUS idmap_sids_to_unixids(struct id_map **ids)
+{
+ TALLOC_CTX *ctx;
+ NTSTATUS ret;
+ struct id_map **bids;
+ int i, bi;
+ int bn = 0;
+ struct winbindd_domain *our_domain = find_our_domain();
+
+ if (! NT_STATUS_IS_OK(ret = idmap_init())) {
+ return ret;
+ }
+
+ if (!ids || !*ids) {
+ DEBUG(1, ("Invalid list of maps\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ctx = talloc_named_const(NULL, 0, "idmap_sids_to_unixids ctx");
+ if ( ! ctx) {
+ DEBUG(1, ("failed to allocate talloc context, OOM?\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* no ids to be asked to the backends by default */
+ bids = NULL;
+ bi = 0;
+
+ for (i = 0; ids[i]; i++) {
+
+ if ( ! ids[i]->sid) {
+ DEBUG(1, ("invalid null SID in id_map array\n"));
+ talloc_free(ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = idmap_cache_map_sid(idmap_cache, ids[i]);
+
+ if ( ! NT_STATUS_IS_OK(ret)) {
+
+ if ( ! bids) {
+ /* alloc space for ids to be resolved
+ by backends (realloc ten by ten) */
+ bids = TALLOC_ARRAY(ctx, struct id_map *, 10);
+ if ( ! bids) {
+ DEBUG(1, ("Out of memory!\n"));
+ talloc_free(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ bn = 10;
+ }
+
+ /* add this id to the ones to be retrieved
+ * from the backends */
+ bids[bi] = ids[i];
+ bi++;
+
+ /* check if we need to allocate new space
+ * on the ids array */
+ if (bi == bn) {
+ bn += 10;
+ bids = talloc_realloc(ctx, bids,
+ struct id_map *, bn);
+ if ( ! bids) {
+ DEBUG(1, ("Out of memory!\n"));
+ talloc_free(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* make sure the last element is NULL */
+ bids[bi] = NULL;
+ }
+ }
+
+ /* let's see if there is any id mapping to be retieved
+ * from the backends */
+ if (bids) {
+ /* Only do query if we are online */
+ if ( IS_DOMAIN_OFFLINE(our_domain) ) {
+ ret = NT_STATUS_FILE_IS_OFFLINE;
+ goto done;
+ }
+
+ ret = idmap_backends_sids_to_unixids(bids);
+ IDMAP_CHECK_RET(ret);
+
+ /* update the cache */
+ for (i = 0; bids[i]; i++) {
+ if (bids[i]->status == ID_MAPPED) {
+ ret = idmap_cache_set(idmap_cache, bids[i]);
+ } else if (bids[i]->status == ID_EXPIRED) {
+ /* the cache returned an expired entry and the
+ * backend was not able to clear the situation
+ * (offline). This handles a previous
+ * NT_STATUS_SYNCHRONIZATION_REQUIRED
+ * for disconnected mode, */
+ bids[i]->status = ID_MAPPED;
+ } else if (bids[i]->status == ID_UNKNOWN) {
+ /* something bad here. We were not able to
+ * handle this for some reason, mark it as
+ * unmapped and hope next time things will
+ * settle down. */
+ bids[i]->status = ID_UNMAPPED;
+ } else { /* unmapped */
+ ret = idmap_cache_set_negative_sid(idmap_cache,
+ bids[i]);
+ }
+ IDMAP_CHECK_RET(ret);
+ }
+ }
+
+ ret = NT_STATUS_OK;
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+NTSTATUS idmap_set_mapping(const struct id_map *id)
+{
+ TALLOC_CTX *ctx;
+ NTSTATUS ret;
+
+ if (! NT_STATUS_IS_OK(ret = idmap_init())) {
+ return ret;
+ }
+
+ /* sanity checks */
+ if ((id->sid == NULL) || (id->status != ID_MAPPED)) {
+ DEBUG(1, ("NULL SID or unmapped entry\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* TODO: check uid/gid range ? */
+
+ ctx = talloc_named_const(NULL, 0, "idmap_set_mapping ctx");
+ if ( ! ctx) {
+ DEBUG(1, ("failed to allocate talloc context, OOM?\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* set the new mapping */
+ ret = idmap_backends_set_mapping(id);
+ IDMAP_CHECK_RET(ret);
+
+ /* set the mapping in the cache */
+ ret = idmap_cache_set(idmap_cache, id);
+ IDMAP_CHECK_RET(ret);
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+/**************************************************************************
+ Dump backend status.
+**************************************************************************/
+
+void idmap_dump_maps(char *logfile)
+{
+ NTSTATUS ret;
+ struct unixid allid;
+ struct id_map *maps;
+ int num_maps;
+ FILE *dump;
+ int i;
+
+ if (! NT_STATUS_IS_OK(ret = idmap_init())) {
+ return;
+ }
+
+ dump = fopen(logfile, "w");
+ if ( ! dump) {
+ DEBUG(0, ("Unable to open open stream for file [%s], "
+ "errno: %d\n", logfile, errno));
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(ret = idmap_alloc_init())) {
+ allid.type = ID_TYPE_UID;
+ allid.id = 0;
+ idmap_alloc_ctx->methods->get_id_hwm(&allid);
+ fprintf(dump, "USER HWM %lu\n", (unsigned long)allid.id);
+
+ allid.type = ID_TYPE_GID;
+ allid.id = 0;
+ idmap_alloc_ctx->methods->get_id_hwm(&allid);
+ fprintf(dump, "GROUP HWM %lu\n", (unsigned long)allid.id);
+ }
+
+ maps = talloc(idmap_ctx, struct id_map);
+ num_maps = 0;
+
+ for (i = 0; i < num_domains; i++) {
+ if (idmap_domains[i]->methods->dump_data) {
+ idmap_domains[i]->methods->dump_data(idmap_domains[i],
+ &maps, &num_maps);
+ }
+ }
+
+ for (i = 0; i < num_maps; i++) {
+ switch (maps[i].xid.type) {
+ case ID_TYPE_UID:
+ fprintf(dump, "UID %lu %s\n",
+ (unsigned long)maps[i].xid.id,
+ sid_string_static(maps[i].sid));
+ break;
+ case ID_TYPE_GID:
+ fprintf(dump, "GID %lu %s\n",
+ (unsigned long)maps[i].xid.id,
+ sid_string_static(maps[i].sid));
+ break;
+ case ID_TYPE_NOT_SPECIFIED:
+ break;
+ }
+ }
+
+ fflush(dump);
+ fclose(dump);
+}
+
+char *idmap_fetch_secret(const char *backend, bool alloc,
+ const char *domain, const char *identity)
+{
+ char *tmp, *ret;
+ int r;
+
+ if (alloc) {
+ r = asprintf(&tmp, "IDMAP_ALLOC_%s", backend);
+ } else {
+ r = asprintf(&tmp, "IDMAP_%s_%s", backend, domain);
+ }
+
+ if (r < 0)
+ return NULL;
+
+ strupper_m(tmp); /* make sure the key is case insensitive */
+ ret = secrets_fetch_generic(tmp, identity);
+
+ SAFE_FREE(tmp);
+
+ return ret;
+}
+
diff --git a/source3/winbindd/idmap_ad.c b/source3/winbindd/idmap_ad.c
new file mode 100644
index 0000000000..41dbb471f9
--- /dev/null
+++ b/source3/winbindd/idmap_ad.c
@@ -0,0 +1,870 @@
+/*
+ * idmap_ad: map between Active Directory and RFC 2307 or "Services for Unix" (SFU) Accounts
+ *
+ * Unix SMB/CIFS implementation.
+ *
+ * Winbind ADS backend functions
+ *
+ * Copyright (C) Andrew Tridgell 2001
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
+ * Copyright (C) Gerald (Jerry) Carter 2004-2007
+ * Copyright (C) Luke Howard 2001-2004
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+#define WINBIND_CCACHE_NAME "MEMORY:winbind_ccache"
+
+#define IDMAP_AD_MAX_IDS 30
+#define CHECK_ALLOC_DONE(mem) do { \
+ if (!mem) { \
+ DEBUG(0, ("Out of memory!\n")); \
+ ret = NT_STATUS_NO_MEMORY; \
+ goto done; \
+ } \
+} while (0)
+
+struct idmap_ad_context {
+ uint32_t filter_low_id;
+ uint32_t filter_high_id;
+};
+
+NTSTATUS init_module(void);
+
+static ADS_STRUCT *ad_idmap_ads = NULL;
+static struct posix_schema *ad_schema = NULL;
+static enum wb_posix_mapping ad_map_type = WB_POSIX_MAP_UNKNOWN;
+
+/************************************************************************
+ ***********************************************************************/
+
+static ADS_STRUCT *ad_idmap_cached_connection_internal(void)
+{
+ ADS_STRUCT *ads;
+ ADS_STATUS status;
+ BOOL local = False;
+ fstring dc_name;
+ struct in_addr dc_ip;
+
+ if (ad_idmap_ads != NULL) {
+
+ time_t expire;
+ time_t now = time(NULL);
+
+ ads = ad_idmap_ads;
+
+ expire = MIN(ads->auth.tgt_expire, ads->auth.tgs_expire);
+
+ /* check for a valid structure */
+ DEBUG(7, ("Current tickets expire in %d seconds (at %d, time is now %d)\n",
+ (uint32)expire-(uint32)now, (uint32) expire, (uint32) now));
+
+ if ( ads->config.realm && (expire > time(NULL))) {
+ return ads;
+ } else {
+ /* we own this ADS_STRUCT so make sure it goes away */
+ DEBUG(7,("Deleting expired krb5 credential cache\n"));
+ ads->is_mine = True;
+ ads_destroy( &ads );
+ ads_kdestroy(WINBIND_CCACHE_NAME);
+ ad_idmap_ads = NULL;
+ TALLOC_FREE( ad_schema );
+ }
+ }
+
+ if (!local) {
+ /* we don't want this to affect the users ccache */
+ setenv("KRB5CCNAME", WINBIND_CCACHE_NAME, 1);
+ }
+
+ if ( (ads = ads_init(lp_realm(), lp_workgroup(), NULL)) == NULL ) {
+ DEBUG(1,("ads_init failed\n"));
+ return NULL;
+ }
+
+ /* the machine acct password might have change - fetch it every time */
+ SAFE_FREE(ads->auth.password);
+ ads->auth.password = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
+
+ SAFE_FREE(ads->auth.realm);
+ ads->auth.realm = SMB_STRDUP(lp_realm());
+
+ /* setup server affinity */
+
+ get_dc_name( NULL, ads->auth.realm, dc_name, &dc_ip );
+
+ status = ads_connect(ads);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1, ("ad_idmap_init: failed to connect to AD\n"));
+ ads_destroy(&ads);
+ return NULL;
+ }
+
+ ads->is_mine = False;
+
+ ad_idmap_ads = ads;
+
+ return ads;
+}
+
+/************************************************************************
+ ***********************************************************************/
+
+static ADS_STRUCT *ad_idmap_cached_connection(void)
+{
+ ADS_STRUCT *ads = ad_idmap_cached_connection_internal();
+
+ if ( !ads )
+ return NULL;
+
+ /* if we have a valid ADS_STRUCT and the schema model is
+ defined, then we can return here. */
+
+ if ( ad_schema )
+ return ads;
+
+ /* Otherwise, set the schema model */
+
+ if ( (ad_map_type == WB_POSIX_MAP_SFU) ||
+ (ad_map_type == WB_POSIX_MAP_SFU20) ||
+ (ad_map_type == WB_POSIX_MAP_RFC2307) )
+ {
+ ADS_STATUS schema_status;
+
+ schema_status = ads_check_posix_schema_mapping( NULL, ads, ad_map_type, &ad_schema);
+ if ( !ADS_ERR_OK(schema_status) ) {
+ DEBUG(2,("ad_idmap_cached_connection: Failed to obtain schema details!\n"));
+ return NULL;
+ }
+ }
+
+ return ads;
+}
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS idmap_ad_initialize(struct idmap_domain *dom)
+{
+ struct idmap_ad_context *ctx;
+ char *config_option;
+ const char *range = NULL;
+ const char *schema_mode = NULL;
+
+ if ( (ctx = TALLOC_ZERO_P(dom, struct idmap_ad_context)) == NULL ) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( (config_option = talloc_asprintf(ctx, "idmap config %s", dom->name)) == NULL ) {
+ DEBUG(0, ("Out of memory!\n"));
+ talloc_free(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* load ranges */
+ range = lp_parm_const_string(-1, config_option, "range", NULL);
+ if (range && range[0]) {
+ if ((sscanf(range, "%u - %u", &ctx->filter_low_id, &ctx->filter_high_id) != 2) ||
+ (ctx->filter_low_id > ctx->filter_high_id)) {
+ DEBUG(1, ("ERROR: invalid filter range [%s]", range));
+ ctx->filter_low_id = 0;
+ ctx->filter_high_id = 0;
+ }
+ }
+
+ /* schema mode */
+ if ( ad_map_type == WB_POSIX_MAP_UNKNOWN )
+ ad_map_type = WB_POSIX_MAP_RFC2307;
+ schema_mode = lp_parm_const_string(-1, config_option, "schema_mode", NULL);
+ if ( schema_mode && schema_mode[0] ) {
+ if ( strequal(schema_mode, "sfu") )
+ ad_map_type = WB_POSIX_MAP_SFU;
+ else if ( strequal(schema_mode, "sfu20" ) )
+ ad_map_type = WB_POSIX_MAP_SFU20;
+ else if ( strequal(schema_mode, "rfc2307" ) )
+ ad_map_type = WB_POSIX_MAP_RFC2307;
+ else
+ DEBUG(0,("idmap_ad_initialize: Unknown schema_mode (%s)\n",
+ schema_mode));
+ }
+
+ dom->private_data = ctx;
+ dom->initialized = True;
+
+ talloc_free(config_option);
+
+ return NT_STATUS_OK;
+}
+
+/************************************************************************
+ Search up to IDMAP_AD_MAX_IDS entries in maps for a match.
+ ***********************************************************************/
+
+static struct id_map *find_map_by_id(struct id_map **maps, enum id_type type, uint32_t id)
+{
+ int i;
+
+ for (i = 0; maps[i] && i<IDMAP_AD_MAX_IDS; i++) {
+ if ((maps[i]->xid.type == type) && (maps[i]->xid.id == id)) {
+ return maps[i];
+ }
+ }
+
+ return NULL;
+}
+
+/************************************************************************
+ Search up to IDMAP_AD_MAX_IDS entries in maps for a match
+ ***********************************************************************/
+
+static struct id_map *find_map_by_sid(struct id_map **maps, DOM_SID *sid)
+{
+ int i;
+
+ for (i = 0; maps[i] && i<IDMAP_AD_MAX_IDS; i++) {
+ if (sid_equal(maps[i]->sid, sid)) {
+ return maps[i];
+ }
+ }
+
+ return NULL;
+}
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS idmap_ad_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *memctx;
+ struct idmap_ad_context *ctx;
+ ADS_STATUS rc;
+ ADS_STRUCT *ads;
+ const char *attrs[] = { "sAMAccountType",
+ "objectSid",
+ NULL, /* uidnumber */
+ NULL, /* gidnumber */
+ NULL };
+ LDAPMessage *res = NULL;
+ LDAPMessage *entry = NULL;
+ char *filter = NULL;
+ int idx = 0;
+ int bidx = 0;
+ int count;
+ int i;
+ char *u_filter = NULL;
+ char *g_filter = NULL;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ /* Initilization my have been deferred because we were offline */
+ if ( ! dom->initialized) {
+ ret = idmap_ad_initialize(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ad_context);
+
+ if ( (memctx = talloc_new(ctx)) == NULL ) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( (ads = ad_idmap_cached_connection()) == NULL ) {
+ DEBUG(1, ("ADS uninitialized\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ attrs[2] = ad_schema->posix_uidnumber_attr;
+ attrs[3] = ad_schema->posix_gidnumber_attr;
+
+again:
+ bidx = idx;
+ for (i = 0; (i < IDMAP_AD_MAX_IDS) && ids[idx]; i++, idx++) {
+ switch (ids[idx]->xid.type) {
+ case ID_TYPE_UID:
+ if ( ! u_filter) {
+ u_filter = talloc_asprintf(memctx, "(&(|"
+ "(sAMAccountType=%d)"
+ "(sAMAccountType=%d)"
+ "(sAMAccountType=%d))(|",
+ ATYPE_NORMAL_ACCOUNT,
+ ATYPE_WORKSTATION_TRUST,
+ ATYPE_INTERDOMAIN_TRUST);
+ }
+ u_filter = talloc_asprintf_append(u_filter, "(%s=%lu)",
+ ad_schema->posix_uidnumber_attr,
+ (unsigned long)ids[idx]->xid.id);
+ CHECK_ALLOC_DONE(u_filter);
+ break;
+
+ case ID_TYPE_GID:
+ if ( ! g_filter) {
+ g_filter = talloc_asprintf(memctx, "(&(|"
+ "(sAMAccountType=%d)"
+ "(sAMAccountType=%d))(|",
+ ATYPE_SECURITY_GLOBAL_GROUP,
+ ATYPE_SECURITY_LOCAL_GROUP);
+ }
+ g_filter = talloc_asprintf_append(g_filter, "(%s=%lu)",
+ ad_schema->posix_gidnumber_attr,
+ (unsigned long)ids[idx]->xid.id);
+ CHECK_ALLOC_DONE(g_filter);
+ break;
+
+ default:
+ DEBUG(3, ("Error: mapping requested but Unknown ID type\n"));
+ ids[idx]->status = ID_UNKNOWN;
+ continue;
+ }
+ }
+ filter = talloc_asprintf(memctx, "(|");
+ CHECK_ALLOC_DONE(filter);
+ if ( u_filter) {
+ filter = talloc_asprintf_append(filter, "%s))", u_filter);
+ CHECK_ALLOC_DONE(filter);
+ TALLOC_FREE(u_filter);
+ }
+ if ( g_filter) {
+ filter = talloc_asprintf_append(filter, "%s))", g_filter);
+ CHECK_ALLOC_DONE(filter);
+ TALLOC_FREE(g_filter);
+ }
+ filter = talloc_asprintf_append(filter, ")");
+ CHECK_ALLOC_DONE(filter);
+
+ rc = ads_search_retry(ads, &res, filter, attrs);
+ if (!ADS_ERR_OK(rc)) {
+ DEBUG(1, ("ERROR: ads search returned: %s\n", ads_errstr(rc)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if ( (count = ads_count_replies(ads, res)) == 0 ) {
+ DEBUG(10, ("No IDs found\n"));
+ }
+
+ entry = res;
+ for (i = 0; (i < count) && entry; i++) {
+ DOM_SID sid;
+ enum id_type type;
+ struct id_map *map;
+ uint32_t id;
+ uint32_t atype;
+
+ if (i == 0) { /* first entry */
+ entry = ads_first_entry(ads, entry);
+ } else { /* following ones */
+ entry = ads_next_entry(ads, entry);
+ }
+
+ if ( !entry ) {
+ DEBUG(2, ("ERROR: Unable to fetch ldap entries from results\n"));
+ break;
+ }
+
+ /* first check if the SID is present */
+ if (!ads_pull_sid(ads, entry, "objectSid", &sid)) {
+ DEBUG(2, ("Could not retrieve SID from entry\n"));
+ continue;
+ }
+
+ /* get type */
+ if (!ads_pull_uint32(ads, entry, "sAMAccountType", &atype)) {
+ DEBUG(1, ("could not get SAM account type\n"));
+ continue;
+ }
+
+ switch (atype & 0xF0000000) {
+ case ATYPE_SECURITY_GLOBAL_GROUP:
+ case ATYPE_SECURITY_LOCAL_GROUP:
+ type = ID_TYPE_GID;
+ break;
+ case ATYPE_NORMAL_ACCOUNT:
+ case ATYPE_WORKSTATION_TRUST:
+ case ATYPE_INTERDOMAIN_TRUST:
+ type = ID_TYPE_UID;
+ break;
+ default:
+ DEBUG(1, ("unrecognized SAM account type %08x\n", atype));
+ continue;
+ }
+
+ if (!ads_pull_uint32(ads, entry, (type==ID_TYPE_UID) ?
+ ad_schema->posix_uidnumber_attr :
+ ad_schema->posix_gidnumber_attr,
+ &id))
+ {
+ DEBUG(1, ("Could not get unix ID\n"));
+ continue;
+ }
+
+ if ((id == 0) ||
+ (ctx->filter_low_id && (id < ctx->filter_low_id)) ||
+ (ctx->filter_high_id && (id > ctx->filter_high_id))) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ id, ctx->filter_low_id, ctx->filter_high_id));
+ continue;
+ }
+
+ map = find_map_by_id(&ids[bidx], type, id);
+ if (!map) {
+ DEBUG(2, ("WARNING: couldn't match result with requested ID\n"));
+ continue;
+ }
+
+ sid_copy(map->sid, &sid);
+
+ /* mapped */
+ map->status = ID_MAPPED;
+
+ DEBUG(10, ("Mapped %s -> %lu (%d)\n",
+ sid_string_static(map->sid),
+ (unsigned long)map->xid.id,
+ map->xid.type));
+ }
+
+ if (res) {
+ ads_msgfree(ads, res);
+ }
+
+ if (ids[idx]) { /* still some values to map */
+ goto again;
+ }
+
+ ret = NT_STATUS_OK;
+
+ /* mark all unknown/expired ones as unmapped */
+ for (i = 0; ids[i]; i++) {
+ if (ids[i]->status != ID_MAPPED)
+ ids[i]->status = ID_UNMAPPED;
+ }
+
+done:
+ talloc_free(memctx);
+ return ret;
+}
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS idmap_ad_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *memctx;
+ struct idmap_ad_context *ctx;
+ ADS_STATUS rc;
+ ADS_STRUCT *ads;
+ const char *attrs[] = { "sAMAccountType",
+ "objectSid",
+ NULL, /* attr_uidnumber */
+ NULL, /* attr_gidnumber */
+ NULL };
+ LDAPMessage *res = NULL;
+ LDAPMessage *entry = NULL;
+ char *filter = NULL;
+ int idx = 0;
+ int bidx = 0;
+ int count;
+ int i;
+ char *sidstr;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ /* Initilization my have been deferred because we were offline */
+ if ( ! dom->initialized) {
+ ret = idmap_ad_initialize(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ad_context);
+
+ if ( (memctx = talloc_new(ctx)) == NULL ) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( (ads = ad_idmap_cached_connection()) == NULL ) {
+ DEBUG(1, ("ADS uninitialized\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ attrs[2] = ad_schema->posix_uidnumber_attr;
+ attrs[3] = ad_schema->posix_gidnumber_attr;
+
+again:
+ filter = talloc_asprintf(memctx, "(&(|"
+ "(sAMAccountType=%d)(sAMAccountType=%d)(sAMAccountType=%d)" /* user account types */
+ "(sAMAccountType=%d)(sAMAccountType=%d)" /* group account types */
+ ")(|",
+ ATYPE_NORMAL_ACCOUNT, ATYPE_WORKSTATION_TRUST, ATYPE_INTERDOMAIN_TRUST,
+ ATYPE_SECURITY_GLOBAL_GROUP, ATYPE_SECURITY_LOCAL_GROUP);
+
+ CHECK_ALLOC_DONE(filter);
+
+ bidx = idx;
+ for (i = 0; (i < IDMAP_AD_MAX_IDS) && ids[idx]; i++, idx++) {
+
+ sidstr = sid_binstring(ids[idx]->sid);
+ filter = talloc_asprintf_append(filter, "(objectSid=%s)", sidstr);
+
+ free(sidstr);
+ CHECK_ALLOC_DONE(filter);
+ }
+ filter = talloc_asprintf_append(filter, "))");
+ CHECK_ALLOC_DONE(filter);
+ DEBUG(10, ("Filter: [%s]\n", filter));
+
+ rc = ads_search_retry(ads, &res, filter, attrs);
+ if (!ADS_ERR_OK(rc)) {
+ DEBUG(1, ("ERROR: ads search returned: %s\n", ads_errstr(rc)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if ( (count = ads_count_replies(ads, res)) == 0 ) {
+ DEBUG(10, ("No IDs found\n"));
+ }
+
+ entry = res;
+ for (i = 0; (i < count) && entry; i++) {
+ DOM_SID sid;
+ enum id_type type;
+ struct id_map *map;
+ uint32_t id;
+ uint32_t atype;
+
+ if (i == 0) { /* first entry */
+ entry = ads_first_entry(ads, entry);
+ } else { /* following ones */
+ entry = ads_next_entry(ads, entry);
+ }
+
+ if ( !entry ) {
+ DEBUG(2, ("ERROR: Unable to fetch ldap entries from results\n"));
+ break;
+ }
+
+ /* first check if the SID is present */
+ if (!ads_pull_sid(ads, entry, "objectSid", &sid)) {
+ DEBUG(2, ("Could not retrieve SID from entry\n"));
+ continue;
+ }
+
+ map = find_map_by_sid(&ids[bidx], &sid);
+ if (!map) {
+ DEBUG(2, ("WARNING: couldn't match result with requested SID\n"));
+ continue;
+ }
+
+ /* get type */
+ if (!ads_pull_uint32(ads, entry, "sAMAccountType", &atype)) {
+ DEBUG(1, ("could not get SAM account type\n"));
+ continue;
+ }
+
+ switch (atype & 0xF0000000) {
+ case ATYPE_SECURITY_GLOBAL_GROUP:
+ case ATYPE_SECURITY_LOCAL_GROUP:
+ type = ID_TYPE_GID;
+ break;
+ case ATYPE_NORMAL_ACCOUNT:
+ case ATYPE_WORKSTATION_TRUST:
+ case ATYPE_INTERDOMAIN_TRUST:
+ type = ID_TYPE_UID;
+ break;
+ default:
+ DEBUG(1, ("unrecognized SAM account type %08x\n", atype));
+ continue;
+ }
+
+ if (!ads_pull_uint32(ads, entry, (type==ID_TYPE_UID) ?
+ ad_schema->posix_uidnumber_attr :
+ ad_schema->posix_gidnumber_attr,
+ &id))
+ {
+ DEBUG(1, ("Could not get unix ID\n"));
+ continue;
+ }
+ if ((id == 0) ||
+ (ctx->filter_low_id && (id < ctx->filter_low_id)) ||
+ (ctx->filter_high_id && (id > ctx->filter_high_id))) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ id, ctx->filter_low_id, ctx->filter_high_id));
+ continue;
+ }
+
+ /* mapped */
+ map->xid.type = type;
+ map->xid.id = id;
+ map->status = ID_MAPPED;
+
+ DEBUG(10, ("Mapped %s -> %lu (%d)\n",
+ sid_string_static(map->sid),
+ (unsigned long)map->xid.id,
+ map->xid.type));
+ }
+
+ if (res) {
+ ads_msgfree(ads, res);
+ }
+
+ if (ids[idx]) { /* still some values to map */
+ goto again;
+ }
+
+ ret = NT_STATUS_OK;
+
+ /* mark all unknwoni/expired ones as unmapped */
+ for (i = 0; ids[i]; i++) {
+ if (ids[i]->status != ID_MAPPED)
+ ids[i]->status = ID_UNMAPPED;
+ }
+
+done:
+ talloc_free(memctx);
+ return ret;
+}
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS idmap_ad_close(struct idmap_domain *dom)
+{
+ ADS_STRUCT *ads = ad_idmap_ads;
+
+ if (ads != NULL) {
+ /* we own this ADS_STRUCT so make sure it goes away */
+ ads->is_mine = True;
+ ads_destroy( &ads );
+ ad_idmap_ads = NULL;
+ }
+
+ TALLOC_FREE( ad_schema );
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * nss_info_{sfu,sfu20,rfc2307}
+ */
+
+/************************************************************************
+ Initialize the {sfu,sfu20,rfc2307} state
+ ***********************************************************************/
+
+static NTSTATUS nss_sfu_init( struct nss_domain_entry *e )
+{
+ /* Sanity check if we have previously been called with a
+ different schema model */
+
+ if ( (ad_map_type != WB_POSIX_MAP_UNKNOWN) &&
+ (ad_map_type != WB_POSIX_MAP_SFU) )
+ {
+ DEBUG(0,("nss_sfu_init: Posix Map type has already been set. "
+ "Mixed schema models not supported!\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ ad_map_type = WB_POSIX_MAP_SFU;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nss_sfu20_init( struct nss_domain_entry *e )
+{
+ /* Sanity check if we have previously been called with a
+ different schema model */
+
+ if ( (ad_map_type != WB_POSIX_MAP_UNKNOWN) &&
+ (ad_map_type != WB_POSIX_MAP_SFU20) )
+ {
+ DEBUG(0,("nss_sfu20_init: Posix Map type has already been set. "
+ "Mixed schema models not supported!\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ ad_map_type = WB_POSIX_MAP_SFU20;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nss_rfc2307_init( struct nss_domain_entry *e )
+{
+ /* Sanity check if we have previously been called with a
+ different schema model */
+
+ if ( (ad_map_type != WB_POSIX_MAP_UNKNOWN) &&
+ (ad_map_type != WB_POSIX_MAP_RFC2307) )
+ {
+ DEBUG(0,("nss_rfc2307_init: Posix Map type has already been set. "
+ "Mixed schema models not supported!\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ ad_map_type = WB_POSIX_MAP_RFC2307;
+
+ return NT_STATUS_OK;
+}
+
+
+/************************************************************************
+ ***********************************************************************/
+static NTSTATUS nss_ad_get_info( struct nss_domain_entry *e,
+ const DOM_SID *sid,
+ TALLOC_CTX *ctx,
+ ADS_STRUCT *ads,
+ LDAPMessage *msg,
+ char **homedir,
+ char **shell,
+ char **gecos,
+ uint32 *gid )
+{
+ ADS_STRUCT *ads_internal = NULL;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ /* We are assuming that the internal ADS_STRUCT is for the
+ same forest as the incoming *ads pointer */
+
+ ads_internal = ad_idmap_cached_connection();
+
+ if ( !ads_internal || !ad_schema )
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+ if ( !homedir || !shell || !gecos )
+ return NT_STATUS_INVALID_PARAMETER;
+
+ *homedir = ads_pull_string( ads, ctx, msg, ad_schema->posix_homedir_attr );
+ *shell = ads_pull_string( ads, ctx, msg, ad_schema->posix_shell_attr );
+ *gecos = ads_pull_string( ads, ctx, msg, ad_schema->posix_gecos_attr );
+
+ if ( gid ) {
+ if ( !ads_pull_uint32(ads, msg, ad_schema->posix_gidnumber_attr, gid ) )
+ *gid = (uint32)-1;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS nss_ad_close( void )
+{
+ /* nothing to do. All memory is free()'d by the idmap close_fn() */
+
+ return NT_STATUS_OK;
+}
+
+/************************************************************************
+ Function dispatch tables for the idmap and nss plugins
+ ***********************************************************************/
+
+static struct idmap_methods ad_methods = {
+ .init = idmap_ad_initialize,
+ .unixids_to_sids = idmap_ad_unixids_to_sids,
+ .sids_to_unixids = idmap_ad_sids_to_unixids,
+ .close_fn = idmap_ad_close
+};
+
+/* The SFU and RFC2307 NSS plugins share everything but the init
+ function which sets the intended schema model to use */
+
+static struct nss_info_methods nss_rfc2307_methods = {
+ .init = nss_rfc2307_init,
+ .get_nss_info = nss_ad_get_info,
+ .close_fn = nss_ad_close
+};
+
+static struct nss_info_methods nss_sfu_methods = {
+ .init = nss_sfu_init,
+ .get_nss_info = nss_ad_get_info,
+ .close_fn = nss_ad_close
+};
+
+static struct nss_info_methods nss_sfu20_methods = {
+ .init = nss_sfu20_init,
+ .get_nss_info = nss_ad_get_info,
+ .close_fn = nss_ad_close
+};
+
+
+
+/************************************************************************
+ Initialize the plugins
+ ***********************************************************************/
+
+NTSTATUS idmap_ad_init(void)
+{
+ static NTSTATUS status_idmap_ad = NT_STATUS_UNSUCCESSFUL;
+ static NTSTATUS status_nss_rfc2307 = NT_STATUS_UNSUCCESSFUL;
+ static NTSTATUS status_nss_sfu = NT_STATUS_UNSUCCESSFUL;
+ static NTSTATUS status_nss_sfu20 = NT_STATUS_UNSUCCESSFUL;
+
+ /* Always register the AD method first in order to get the
+ idmap_domain interface called */
+
+ if ( !NT_STATUS_IS_OK(status_idmap_ad) ) {
+ status_idmap_ad = smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION,
+ "ad", &ad_methods);
+ if ( !NT_STATUS_IS_OK(status_idmap_ad) )
+ return status_idmap_ad;
+ }
+
+ if ( !NT_STATUS_IS_OK( status_nss_rfc2307 ) ) {
+ status_nss_rfc2307 = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
+ "rfc2307", &nss_rfc2307_methods );
+ if ( !NT_STATUS_IS_OK(status_nss_rfc2307) )
+ return status_nss_rfc2307;
+ }
+
+ if ( !NT_STATUS_IS_OK( status_nss_sfu ) ) {
+ status_nss_sfu = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
+ "sfu", &nss_sfu_methods );
+ if ( !NT_STATUS_IS_OK(status_nss_sfu) )
+ return status_nss_sfu;
+ }
+
+ if ( !NT_STATUS_IS_OK( status_nss_sfu20 ) ) {
+ status_nss_sfu20 = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
+ "sfu20", &nss_sfu20_methods );
+ if ( !NT_STATUS_IS_OK(status_nss_sfu20) )
+ return status_nss_sfu20;
+ }
+
+ return NT_STATUS_OK;
+}
+
diff --git a/source3/winbindd/idmap_cache.c b/source3/winbindd/idmap_cache.c
new file mode 100644
index 0000000000..4f01cb1392
--- /dev/null
+++ b/source3/winbindd/idmap_cache.c
@@ -0,0 +1,531 @@
+/*
+ Unix SMB/CIFS implementation.
+ ID Mapping Cache
+
+ based on gencache
+
+ Copyright (C) Simo Sorce 2006
+ Copyright (C) Rafal Szczesniak 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#define TIMEOUT_LEN 12
+#define IDMAP_CACHE_DATA_FMT "%12u/%s"
+#define IDMAP_READ_CACHE_DATA_FMT_TEMPLATE "%%12u/%%%us"
+
+struct idmap_cache_ctx {
+ TDB_CONTEXT *tdb;
+};
+
+static int idmap_cache_destructor(struct idmap_cache_ctx *cache)
+{
+ int ret = 0;
+
+ if (cache && cache->tdb) {
+ ret = tdb_close(cache->tdb);
+ cache->tdb = NULL;
+ }
+
+ return ret;
+}
+
+struct idmap_cache_ctx *idmap_cache_init(TALLOC_CTX *memctx)
+{
+ struct idmap_cache_ctx *cache;
+ char* cache_fname = NULL;
+
+ cache = talloc(memctx, struct idmap_cache_ctx);
+ if ( ! cache) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NULL;
+ }
+
+ cache_fname = lock_path("idmap_cache.tdb");
+
+ DEBUG(10, ("Opening cache file at %s\n", cache_fname));
+
+ cache->tdb = tdb_open_log(cache_fname, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600);
+
+ if (!cache->tdb) {
+ DEBUG(5, ("Attempt to open %s has failed.\n", cache_fname));
+ return NULL;
+ }
+
+ talloc_set_destructor(cache, idmap_cache_destructor);
+
+ return cache;
+}
+
+void idmap_cache_shutdown(struct idmap_cache_ctx *cache)
+{
+ talloc_free(cache);
+}
+
+NTSTATUS idmap_cache_build_sidkey(TALLOC_CTX *ctx, char **sidkey, const struct id_map *id)
+{
+ *sidkey = talloc_asprintf(ctx, "IDMAP/SID/%s", sid_string_static(id->sid));
+ if ( ! *sidkey) {
+ DEBUG(1, ("failed to build sidkey, OOM?\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS idmap_cache_build_idkey(TALLOC_CTX *ctx, char **idkey, const struct id_map *id)
+{
+ *idkey = talloc_asprintf(ctx, "IDMAP/%s/%lu",
+ (id->xid.type==ID_TYPE_UID)?"UID":"GID",
+ (unsigned long)id->xid.id);
+ if ( ! *idkey) {
+ DEBUG(1, ("failed to build idkey, OOM?\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS idmap_cache_set(struct idmap_cache_ctx *cache, const struct id_map *id)
+{
+ NTSTATUS ret;
+ time_t timeout = time(NULL) + lp_idmap_cache_time();
+ TDB_DATA databuf;
+ char *sidkey;
+ char *idkey;
+ char *valstr;
+
+ /* Don't cache lookups in the S-1-22-{1,2} domain */
+ if ( (id->xid.type == ID_TYPE_UID) &&
+ sid_check_is_in_unix_users(id->sid) )
+ {
+ return NT_STATUS_OK;
+ }
+ if ( (id->xid.type == ID_TYPE_GID) &&
+ sid_check_is_in_unix_groups(id->sid) )
+ {
+ return NT_STATUS_OK;
+ }
+
+
+ ret = idmap_cache_build_sidkey(cache, &sidkey, id);
+ if (!NT_STATUS_IS_OK(ret)) return ret;
+
+ /* use sidkey as the local memory ctx */
+ ret = idmap_cache_build_idkey(sidkey, &idkey, id);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto done;
+ }
+
+ /* save SID -> ID */
+
+ /* use sidkey as the local memory ctx */
+ valstr = talloc_asprintf(sidkey, IDMAP_CACHE_DATA_FMT, (int)timeout, idkey);
+ if (!valstr) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ databuf = string_term_tdb_data(valstr);
+ DEBUG(10, ("Adding cache entry with key = %s; value = %s and timeout ="
+ " %s (%d seconds %s)\n", sidkey, valstr , ctime(&timeout),
+ (int)(timeout - time(NULL)),
+ timeout > time(NULL) ? "ahead" : "in the past"));
+
+ if (tdb_store_bystring(cache->tdb, sidkey, databuf, TDB_REPLACE) != 0) {
+ DEBUG(3, ("Failed to store cache entry!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* save ID -> SID */
+
+ /* use sidkey as the local memory ctx */
+ valstr = talloc_asprintf(sidkey, IDMAP_CACHE_DATA_FMT, (int)timeout, sidkey);
+ if (!valstr) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ databuf = string_term_tdb_data(valstr);
+ DEBUG(10, ("Adding cache entry with key = %s; value = %s and timeout ="
+ " %s (%d seconds %s)\n", idkey, valstr, ctime(&timeout),
+ (int)(timeout - time(NULL)),
+ timeout > time(NULL) ? "ahead" : "in the past"));
+
+ if (tdb_store_bystring(cache->tdb, idkey, databuf, TDB_REPLACE) != 0) {
+ DEBUG(3, ("Failed to store cache entry!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(sidkey);
+ return ret;
+}
+
+NTSTATUS idmap_cache_set_negative_sid(struct idmap_cache_ctx *cache, const struct id_map *id)
+{
+ NTSTATUS ret;
+ time_t timeout = time(NULL) + lp_idmap_negative_cache_time();
+ TDB_DATA databuf;
+ char *sidkey;
+ char *valstr;
+
+ ret = idmap_cache_build_sidkey(cache, &sidkey, id);
+ if (!NT_STATUS_IS_OK(ret)) return ret;
+
+ /* use sidkey as the local memory ctx */
+ valstr = talloc_asprintf(sidkey, IDMAP_CACHE_DATA_FMT, (int)timeout, "IDMAP/NEGATIVE");
+ if (!valstr) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ databuf = string_term_tdb_data(valstr);
+ DEBUG(10, ("Adding cache entry with key = %s; value = %s and timeout ="
+ " %s (%d seconds %s)\n", sidkey, valstr, ctime(&timeout),
+ (int)(timeout - time(NULL)),
+ timeout > time(NULL) ? "ahead" : "in the past"));
+
+ if (tdb_store_bystring(cache->tdb, sidkey, databuf, TDB_REPLACE) != 0) {
+ DEBUG(3, ("Failed to store cache entry!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+done:
+ talloc_free(sidkey);
+ return ret;
+}
+
+NTSTATUS idmap_cache_set_negative_id(struct idmap_cache_ctx *cache, const struct id_map *id)
+{
+ NTSTATUS ret;
+ time_t timeout = time(NULL) + lp_idmap_negative_cache_time();
+ TDB_DATA databuf;
+ char *idkey;
+ char *valstr;
+
+ ret = idmap_cache_build_idkey(cache, &idkey, id);
+ if (!NT_STATUS_IS_OK(ret)) return ret;
+
+ /* use idkey as the local memory ctx */
+ valstr = talloc_asprintf(idkey, IDMAP_CACHE_DATA_FMT, (int)timeout, "IDMAP/NEGATIVE");
+ if (!valstr) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ databuf = string_term_tdb_data(valstr);
+ DEBUG(10, ("Adding cache entry with key = %s; value = %s and timeout ="
+ " %s (%d seconds %s)\n", idkey, valstr, ctime(&timeout),
+ (int)(timeout - time(NULL)),
+ timeout > time(NULL) ? "ahead" : "in the past"));
+
+ if (tdb_store_bystring(cache->tdb, idkey, databuf, TDB_REPLACE) != 0) {
+ DEBUG(3, ("Failed to store cache entry!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+done:
+ talloc_free(idkey);
+ return ret;
+}
+
+NTSTATUS idmap_cache_fill_map(struct id_map *id, const char *value)
+{
+ char *rem;
+
+ /* see if it is a sid */
+ if ( ! strncmp("IDMAP/SID/", value, 10)) {
+
+ if ( ! string_to_sid(id->sid, &value[10])) {
+ goto failed;
+ }
+
+ id->status = ID_MAPPED;
+
+ return NT_STATUS_OK;
+ }
+
+ /* not a SID see if it is an UID or a GID */
+ if ( ! strncmp("IDMAP/UID/", value, 10)) {
+
+ /* a uid */
+ id->xid.type = ID_TYPE_UID;
+
+ } else if ( ! strncmp("IDMAP/GID/", value, 10)) {
+
+ /* a gid */
+ id->xid.type = ID_TYPE_GID;
+
+ } else {
+
+ /* a completely bogus value bail out */
+ goto failed;
+ }
+
+ id->xid.id = strtol(&value[10], &rem, 0);
+ if (*rem != '\0') {
+ goto failed;
+ }
+
+ id->status = ID_MAPPED;
+
+ return NT_STATUS_OK;
+
+failed:
+ DEBUG(1, ("invalid value: %s\n", value));
+ id->status = ID_UNKNOWN;
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+}
+
+BOOL idmap_cache_is_negative(const char *val)
+{
+ if ( ! strcmp("IDMAP/NEGATIVE", val)) {
+ return True;
+ }
+ return False;
+}
+
+/* search the cahce for the SID an return a mapping if found *
+ *
+ * 4 cases are possible
+ *
+ * 1 map found
+ * in this case id->status = ID_MAPPED and NT_STATUS_OK is returned
+ * 2 map not found
+ * in this case id->status = ID_UNKNOWN and NT_STATUS_NONE_MAPPED is returned
+ * 3 negative cache found
+ * in this case id->status = ID_UNMAPPED and NT_STATUS_OK is returned
+ * 4 map found but timer expired
+ * in this case id->status = ID_EXPIRED and NT_STATUS_SYNCHRONIZATION_REQUIRED
+ * is returned. In this case revalidation of the cache is needed.
+ */
+
+NTSTATUS idmap_cache_map_sid(struct idmap_cache_ctx *cache, struct id_map *id)
+{
+ NTSTATUS ret;
+ TDB_DATA databuf;
+ time_t t;
+ char *sidkey;
+ char *endptr;
+ struct winbindd_domain *our_domain = find_our_domain();
+ time_t now = time(NULL);
+
+ /* make sure it is marked as not mapped by default */
+ id->status = ID_UNKNOWN;
+
+ ret = idmap_cache_build_sidkey(cache, &sidkey, id);
+ if (!NT_STATUS_IS_OK(ret)) return ret;
+
+ databuf = tdb_fetch_bystring(cache->tdb, sidkey);
+
+ if (databuf.dptr == NULL) {
+ DEBUG(10, ("Cache entry with key = %s couldn't be found\n", sidkey));
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ t = strtol((const char *)databuf.dptr, &endptr, 10);
+
+ if ((endptr == NULL) || (*endptr != '/')) {
+ DEBUG(2, ("Invalid gencache data format: %s\n", (const char *)databuf.dptr));
+ /* remove the entry */
+ tdb_delete_bystring(cache->tdb, sidkey);
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ /* check it is not negative */
+ if (strcmp("IDMAP/NEGATIVE", endptr+1) != 0) {
+
+ DEBUG(10, ("Returning %s cache entry: key = %s, value = %s, "
+ "timeout = %s", t > now ? "valid" :
+ "expired", sidkey, endptr+1, ctime(&t)));
+
+ /* this call if successful will also mark the entry as mapped */
+ ret = idmap_cache_fill_map(id, endptr+1);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ /* if not valid form delete the entry */
+ tdb_delete_bystring(cache->tdb, sidkey);
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ /* here ret == NT_STATUS_OK and id->status = ID_MAPPED */
+
+ if (t <= now) {
+ /* If we've been told to be offline - stay in
+ that state... */
+ if ( IS_DOMAIN_OFFLINE(our_domain) ) {
+ DEBUG(10,("idmap_cache_map_sid: idmap is offline\n"));
+ goto done;
+ }
+
+ /* We're expired, set an error code
+ for upper layer */
+ ret = NT_STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+
+ goto done;
+ }
+
+ /* Was a negative cache hit */
+
+ /* Ignore the negative cache when offline */
+
+ if ( IS_DOMAIN_OFFLINE(our_domain) ) {
+ DEBUG(10,("idmap_cache_map_sid: idmap is offline\n"));
+ goto done;
+ }
+
+
+ /* Check for valid or expired cache hits */
+ if (t <= now) {
+ /* We're expired. Return not mapped */
+ ret = NT_STATUS_NONE_MAPPED;
+ } else {
+ /* this is not mapped as it was a negative cache hit */
+ id->status = ID_UNMAPPED;
+ ret = NT_STATUS_OK;
+ }
+
+done:
+ SAFE_FREE(databuf.dptr);
+ talloc_free(sidkey);
+ return ret;
+}
+
+/* search the cahce for the ID an return a mapping if found *
+ *
+ * 4 cases are possible
+ *
+ * 1 map found
+ * in this case id->status = ID_MAPPED and NT_STATUS_OK is returned
+ * 2 map not found
+ * in this case id->status = ID_UNKNOWN and NT_STATUS_NONE_MAPPED is returned
+ * 3 negative cache found
+ * in this case id->status = ID_UNMAPPED and NT_STATUS_OK is returned
+ * 4 map found but timer expired
+ * in this case id->status = ID_EXPIRED and NT_STATUS_SYNCHRONIZATION_REQUIRED
+ * is returned. In this case revalidation of the cache is needed.
+ */
+
+NTSTATUS idmap_cache_map_id(struct idmap_cache_ctx *cache, struct id_map *id)
+{
+ NTSTATUS ret;
+ TDB_DATA databuf;
+ time_t t;
+ char *idkey;
+ char *endptr;
+ struct winbindd_domain *our_domain = find_our_domain();
+ time_t now = time(NULL);
+
+ /* make sure it is marked as unknown by default */
+ id->status = ID_UNKNOWN;
+
+ ret = idmap_cache_build_idkey(cache, &idkey, id);
+ if (!NT_STATUS_IS_OK(ret)) return ret;
+
+ databuf = tdb_fetch_bystring(cache->tdb, idkey);
+
+ if (databuf.dptr == NULL) {
+ DEBUG(10, ("Cache entry with key = %s couldn't be found\n", idkey));
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ t = strtol((const char *)databuf.dptr, &endptr, 10);
+
+ if ((endptr == NULL) || (*endptr != '/')) {
+ DEBUG(2, ("Invalid gencache data format: %s\n", (const char *)databuf.dptr));
+ /* remove the entry */
+ tdb_delete_bystring(cache->tdb, idkey);
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ /* check it is not negative */
+ if (strcmp("IDMAP/NEGATIVE", endptr+1) != 0) {
+
+ DEBUG(10, ("Returning %s cache entry: key = %s, value = %s, "
+ "timeout = %s", t > now ? "valid" :
+ "expired", idkey, endptr+1, ctime(&t)));
+
+ /* this call if successful will also mark the entry as mapped */
+ ret = idmap_cache_fill_map(id, endptr+1);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ /* if not valid form delete the entry */
+ tdb_delete_bystring(cache->tdb, idkey);
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ /* here ret == NT_STATUS_OK and id->mapped = ID_MAPPED */
+
+ if (t <= now) {
+ /* If we've been told to be offline - stay in
+ that state... */
+ if ( IS_DOMAIN_OFFLINE(our_domain) ) {
+ DEBUG(10,("idmap_cache_map_sid: idmap is offline\n"));
+ goto done;
+ }
+
+ /* We're expired, set an error code
+ for upper layer */
+ ret = NT_STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+
+ goto done;
+ }
+
+ /* Was a negative cache hit */
+
+ /* Ignore the negative cache when offline */
+
+ if ( IS_DOMAIN_OFFLINE(our_domain) ) {
+ DEBUG(10,("idmap_cache_map_sid: idmap is offline\n"));
+ ret = NT_STATUS_NONE_MAPPED;
+
+ goto done;
+ }
+
+ /* Process the negative cache hit */
+
+ if (t <= now) {
+ /* We're expired. Return not mapped */
+ ret = NT_STATUS_NONE_MAPPED;
+ } else {
+ /* this is not mapped is it was a negative cache hit */
+ id->status = ID_UNMAPPED;
+ ret = NT_STATUS_OK;
+ }
+
+done:
+ SAFE_FREE(databuf.dptr);
+ talloc_free(idkey);
+ return ret;
+}
+
diff --git a/source3/winbindd/idmap_ldap.c b/source3/winbindd/idmap_ldap.c
new file mode 100644
index 0000000000..3b63915b05
--- /dev/null
+++ b/source3/winbindd/idmap_ldap.c
@@ -0,0 +1,1505 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap LDAP backend
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Gerald Carter 2003
+ Copyright (C) Simo Sorce 2003-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 "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+#include <lber.h>
+#include <ldap.h>
+
+#include "smbldap.h"
+
+struct idmap_ldap_context {
+ struct smbldap_state *smbldap_state;
+ char *url;
+ char *suffix;
+ char *user_dn;
+ uint32_t filter_low_id, filter_high_id; /* Filter range */
+ BOOL anon;
+};
+
+struct idmap_ldap_alloc_context {
+ struct smbldap_state *smbldap_state;
+ char *url;
+ char *suffix;
+ char *user_dn;
+ uid_t low_uid, high_uid; /* Range of uids */
+ gid_t low_gid, high_gid; /* Range of gids */
+
+};
+
+#define CHECK_ALLOC_DONE(mem) do { \
+ if (!mem) { \
+ DEBUG(0, ("Out of memory!\n")); \
+ ret = NT_STATUS_NO_MEMORY; \
+ goto done; \
+ } } while (0)
+
+/**********************************************************************
+ IDMAP ALLOC TDB BACKEND
+**********************************************************************/
+
+static struct idmap_ldap_alloc_context *idmap_alloc_ldap;
+
+/*********************************************************************
+ ********************************************************************/
+
+static NTSTATUS get_credentials( TALLOC_CTX *mem_ctx,
+ struct smbldap_state *ldap_state,
+ const char *config_option,
+ struct idmap_domain *dom,
+ char **dn )
+{
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ char *secret = NULL;
+ const char *tmp = NULL;
+ char *user_dn = NULL;
+ BOOL anon = False;
+
+ /* assume anonymous if we don't have a specified user */
+
+ tmp = lp_parm_const_string(-1, config_option, "ldap_user_dn", NULL);
+
+ if ( tmp ) {
+ if (!dom) {
+ /* only the alloc backend can pass in a NULL dom */
+ secret = idmap_fetch_secret("ldap", True,
+ NULL, tmp);
+ } else {
+ secret = idmap_fetch_secret("ldap", False,
+ dom->name, tmp);
+ }
+
+ if (!secret) {
+ DEBUG(0, ("get_credentials: Unable to fetch "
+ "auth credentials for %s in %s\n",
+ tmp, (dom==NULL)?"ALLOC":dom->name));
+ ret = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+ *dn = talloc_strdup(mem_ctx, tmp);
+ CHECK_ALLOC_DONE(*dn);
+ } else {
+ if (!fetch_ldap_pw(&user_dn, &secret)) {
+ DEBUG(2, ("get_credentials: Failed to lookup ldap "
+ "bind creds. Using anonymous connection.\n"));
+ anon = True;
+ } else {
+ *dn = talloc_strdup(mem_ctx, user_dn);
+ SAFE_FREE( user_dn );
+ CHECK_ALLOC_DONE(*dn);
+ }
+ }
+
+ smbldap_set_creds(ldap_state, anon, *dn, secret);
+ ret = NT_STATUS_OK;
+
+done:
+ SAFE_FREE(secret);
+
+ return ret;
+}
+
+
+/**********************************************************************
+ Verify the sambaUnixIdPool entry in the directory.
+**********************************************************************/
+
+static NTSTATUS verify_idpool(void)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *ctx;
+ LDAPMessage *result = NULL;
+ LDAPMod **mods = NULL;
+ const char **attr_list;
+ char *filter;
+ int count;
+ int rc;
+
+ if ( ! idmap_alloc_ldap) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ctx = talloc_new(idmap_alloc_ldap);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ filter = talloc_asprintf(ctx, "(objectclass=%s)", LDAP_OBJ_IDPOOL);
+ CHECK_ALLOC_DONE(filter);
+
+ attr_list = get_attr_list(ctx, idpool_attr_list);
+ CHECK_ALLOC_DONE(attr_list);
+
+ rc = smbldap_search(idmap_alloc_ldap->smbldap_state,
+ idmap_alloc_ldap->suffix,
+ LDAP_SCOPE_SUBTREE,
+ filter,
+ attr_list,
+ 0,
+ &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1, ("Unable to verify the idpool, "
+ "cannot continue initialization!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ count = ldap_count_entries(idmap_alloc_ldap->smbldap_state->ldap_struct,
+ result);
+
+ ldap_msgfree(result);
+
+ if ( count > 1 ) {
+ DEBUG(0,("Multiple entries returned from %s (base == %s)\n",
+ filter, idmap_alloc_ldap->suffix));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ else if (count == 0) {
+ char *uid_str, *gid_str;
+
+ uid_str = talloc_asprintf(ctx, "%lu",
+ (unsigned long)idmap_alloc_ldap->low_uid);
+ gid_str = talloc_asprintf(ctx, "%lu",
+ (unsigned long)idmap_alloc_ldap->low_gid);
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ "objectClass", LDAP_OBJ_IDPOOL);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_UIDNUMBER),
+ uid_str);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_GIDNUMBER),
+ gid_str);
+ if (mods) {
+ rc = smbldap_modify(idmap_alloc_ldap->smbldap_state,
+ idmap_alloc_ldap->suffix,
+ mods);
+ ldap_mods_free(mods, True);
+ } else {
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ ret = (rc == LDAP_SUCCESS)?NT_STATUS_OK:NT_STATUS_UNSUCCESSFUL;
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+/*****************************************************************************
+ Initialise idmap database.
+*****************************************************************************/
+
+static NTSTATUS idmap_ldap_alloc_init(const char *params)
+{
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ const char *range;
+ const char *tmp;
+ uid_t low_uid = 0;
+ uid_t high_uid = 0;
+ gid_t low_gid = 0;
+ gid_t high_gid = 0;
+
+ /* Only do init if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ idmap_alloc_ldap = TALLOC_ZERO_P(NULL, struct idmap_ldap_alloc_context);
+ CHECK_ALLOC_DONE( idmap_alloc_ldap );
+
+ /* load ranges */
+
+ idmap_alloc_ldap->low_uid = 0;
+ idmap_alloc_ldap->high_uid = 0;
+ idmap_alloc_ldap->low_gid = 0;
+ idmap_alloc_ldap->high_gid = 0;
+
+ range = lp_parm_const_string(-1, "idmap alloc config", "range", NULL);
+ if (range && range[0]) {
+ unsigned low_id, high_id;
+
+ if (sscanf(range, "%u - %u", &low_id, &high_id) == 2) {
+ if (low_id < high_id) {
+ idmap_alloc_ldap->low_gid = low_id;
+ idmap_alloc_ldap->low_uid = low_id;
+ idmap_alloc_ldap->high_gid = high_id;
+ idmap_alloc_ldap->high_uid = high_id;
+ } else {
+ DEBUG(1, ("ERROR: invalid idmap alloc range "
+ "[%s]", range));
+ }
+ } else {
+ DEBUG(1, ("ERROR: invalid syntax for idmap alloc "
+ "config:range [%s]", range));
+ }
+ }
+
+ if (lp_idmap_uid(&low_uid, &high_uid)) {
+ idmap_alloc_ldap->low_uid = low_uid;
+ idmap_alloc_ldap->high_uid = high_uid;
+ }
+
+ if (lp_idmap_gid(&low_gid, &high_gid)) {
+ idmap_alloc_ldap->low_gid = low_gid;
+ idmap_alloc_ldap->high_gid= high_gid;
+ }
+
+ if (idmap_alloc_ldap->high_uid <= idmap_alloc_ldap->low_uid) {
+ DEBUG(1, ("idmap uid range missing or invalid\n"));
+ DEBUGADD(1, ("idmap will be unable to map foreign SIDs\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (idmap_alloc_ldap->high_gid <= idmap_alloc_ldap->low_gid) {
+ DEBUG(1, ("idmap gid range missing or invalid\n"));
+ DEBUGADD(1, ("idmap will be unable to map foreign SIDs\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (params && *params) {
+ /* assume location is the only parameter */
+ idmap_alloc_ldap->url = talloc_strdup(idmap_alloc_ldap, params);
+ } else {
+ tmp = lp_parm_const_string(-1, "idmap alloc config",
+ "ldap_url", NULL);
+
+ if ( ! tmp) {
+ DEBUG(1, ("ERROR: missing idmap ldap url\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ idmap_alloc_ldap->url = talloc_strdup(idmap_alloc_ldap, tmp);
+ }
+ CHECK_ALLOC_DONE( idmap_alloc_ldap->url );
+
+ tmp = lp_parm_const_string(-1, "idmap alloc config",
+ "ldap_base_dn", NULL);
+ if ( ! tmp || ! *tmp) {
+ tmp = lp_ldap_idmap_suffix();
+ if ( ! tmp) {
+ DEBUG(1, ("ERROR: missing idmap ldap suffix\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ idmap_alloc_ldap->suffix = talloc_strdup(idmap_alloc_ldap, tmp);
+ CHECK_ALLOC_DONE( idmap_alloc_ldap->suffix );
+
+ ret = smbldap_init(idmap_alloc_ldap, winbind_event_context(),
+ idmap_alloc_ldap->url,
+ &idmap_alloc_ldap->smbldap_state);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("ERROR: smbldap_init (%s) failed!\n",
+ idmap_alloc_ldap->url));
+ goto done;
+ }
+
+ ret = get_credentials( idmap_alloc_ldap,
+ idmap_alloc_ldap->smbldap_state,
+ "idmap alloc config", NULL,
+ &idmap_alloc_ldap->user_dn );
+ if ( !NT_STATUS_IS_OK(ret) ) {
+ DEBUG(1,("idmap_ldap_alloc_init: Failed to get connection "
+ "credentials (%s)\n", nt_errstr(ret)));
+ goto done;
+ }
+
+ /* see if the idmap suffix and sub entries exists */
+
+ ret = verify_idpool();
+
+ done:
+ if ( !NT_STATUS_IS_OK( ret ) )
+ TALLOC_FREE( idmap_alloc_ldap );
+
+ return ret;
+}
+
+/********************************
+ Allocate a new uid or gid
+********************************/
+
+static NTSTATUS idmap_ldap_allocate_id(struct unixid *xid)
+{
+ TALLOC_CTX *ctx;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ int rc = LDAP_SERVER_DOWN;
+ int count = 0;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ char *id_str;
+ char *new_id_str;
+ char *filter = NULL;
+ const char *dn = NULL;
+ const char **attr_list;
+ const char *type;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ if ( ! idmap_alloc_ldap) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ctx = talloc_new(idmap_alloc_ldap);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get type */
+ switch (xid->type) {
+
+ case ID_TYPE_UID:
+ type = get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_UIDNUMBER);
+ break;
+
+ case ID_TYPE_GID:
+ type = get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_GIDNUMBER);
+ break;
+
+ default:
+ DEBUG(2, ("Invalid ID type (0x%x)\n", xid->type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ filter = talloc_asprintf(ctx, "(objectClass=%s)", LDAP_OBJ_IDPOOL);
+ CHECK_ALLOC_DONE(filter);
+
+ attr_list = get_attr_list(ctx, idpool_attr_list);
+ CHECK_ALLOC_DONE(attr_list);
+
+ DEBUG(10, ("Search of the id pool (filter: %s)\n", filter));
+
+ rc = smbldap_search(idmap_alloc_ldap->smbldap_state,
+ idmap_alloc_ldap->suffix,
+ LDAP_SCOPE_SUBTREE, filter,
+ attr_list, 0, &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("%s object not found\n", LDAP_OBJ_IDPOOL));
+ goto done;
+ }
+
+ talloc_autofree_ldapmsg(ctx, result);
+
+ count = ldap_count_entries(idmap_alloc_ldap->smbldap_state->ldap_struct,
+ result);
+ if (count != 1) {
+ DEBUG(0,("Single %s object not found\n", LDAP_OBJ_IDPOOL));
+ goto done;
+ }
+
+ entry = ldap_first_entry(idmap_alloc_ldap->smbldap_state->ldap_struct,
+ result);
+
+ dn = smbldap_talloc_dn(ctx,
+ idmap_alloc_ldap->smbldap_state->ldap_struct,
+ entry);
+ if ( ! dn) {
+ goto done;
+ }
+
+ if ( ! (id_str = smbldap_talloc_single_attribute(idmap_alloc_ldap->smbldap_state->ldap_struct,
+ entry, type, ctx))) {
+ DEBUG(0,("%s attribute not found\n", type));
+ goto done;
+ }
+ if ( ! id_str) {
+ DEBUG(0,("Out of memory\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ xid->id = strtoul(id_str, NULL, 10);
+
+ /* make sure we still have room to grow */
+
+ switch (xid->type) {
+ case ID_TYPE_UID:
+ if (xid->id > idmap_alloc_ldap->high_uid) {
+ DEBUG(0,("Cannot allocate uid above %lu!\n",
+ (unsigned long)idmap_alloc_ldap->high_uid));
+ goto done;
+ }
+ break;
+
+ case ID_TYPE_GID:
+ if (xid->id > idmap_alloc_ldap->high_gid) {
+ DEBUG(0,("Cannot allocate gid above %lu!\n",
+ (unsigned long)idmap_alloc_ldap->high_uid));
+ goto done;
+ }
+ break;
+
+ default:
+ /* impossible */
+ goto done;
+ }
+
+ new_id_str = talloc_asprintf(ctx, "%lu", (unsigned long)xid->id + 1);
+ if ( ! new_id_str) {
+ DEBUG(0,("Out of memory\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_DELETE, type, id_str);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, type, new_id_str);
+
+ if (mods == NULL) {
+ DEBUG(0,("smbldap_set_mod() failed.\n"));
+ goto done;
+ }
+
+ DEBUG(10, ("Try to atomically increment the id (%s -> %s)\n",
+ id_str, new_id_str));
+
+ rc = smbldap_modify(idmap_alloc_ldap->smbldap_state, dn, mods);
+
+ ldap_mods_free(mods, True);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1,("Failed to allocate new %s. "
+ "smbldap_modify() failed.\n", type));
+ goto done;
+ }
+
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+/**********************************
+ Get current highest id.
+**********************************/
+
+static NTSTATUS idmap_ldap_get_hwm(struct unixid *xid)
+{
+ TALLOC_CTX *memctx;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ int rc = LDAP_SERVER_DOWN;
+ int count = 0;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ char *id_str;
+ char *filter = NULL;
+ const char **attr_list;
+ const char *type;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ if ( ! idmap_alloc_ldap) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ memctx = talloc_new(idmap_alloc_ldap);
+ if ( ! memctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get type */
+ switch (xid->type) {
+
+ case ID_TYPE_UID:
+ type = get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_UIDNUMBER);
+ break;
+
+ case ID_TYPE_GID:
+ type = get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_GIDNUMBER);
+ break;
+
+ default:
+ DEBUG(2, ("Invalid ID type (0x%x)\n", xid->type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ filter = talloc_asprintf(memctx, "(objectClass=%s)", LDAP_OBJ_IDPOOL);
+ CHECK_ALLOC_DONE(filter);
+
+ attr_list = get_attr_list(memctx, idpool_attr_list);
+ CHECK_ALLOC_DONE(attr_list);
+
+ rc = smbldap_search(idmap_alloc_ldap->smbldap_state,
+ idmap_alloc_ldap->suffix,
+ LDAP_SCOPE_SUBTREE, filter,
+ attr_list, 0, &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("%s object not found\n", LDAP_OBJ_IDPOOL));
+ goto done;
+ }
+
+ talloc_autofree_ldapmsg(memctx, result);
+
+ count = ldap_count_entries(idmap_alloc_ldap->smbldap_state->ldap_struct,
+ result);
+ if (count != 1) {
+ DEBUG(0,("Single %s object not found\n", LDAP_OBJ_IDPOOL));
+ goto done;
+ }
+
+ entry = ldap_first_entry(idmap_alloc_ldap->smbldap_state->ldap_struct,
+ result);
+
+ id_str = smbldap_talloc_single_attribute(idmap_alloc_ldap->smbldap_state->ldap_struct,
+ entry, type, memctx);
+ if ( ! id_str) {
+ DEBUG(0,("%s attribute not found\n", type));
+ goto done;
+ }
+ if ( ! id_str) {
+ DEBUG(0,("Out of memory\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ xid->id = strtoul(id_str, NULL, 10);
+
+ ret = NT_STATUS_OK;
+done:
+ talloc_free(memctx);
+ return ret;
+}
+/**********************************
+ Set highest id.
+**********************************/
+
+static NTSTATUS idmap_ldap_set_hwm(struct unixid *xid)
+{
+ TALLOC_CTX *ctx;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ int rc = LDAP_SERVER_DOWN;
+ int count = 0;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ char *new_id_str;
+ char *filter = NULL;
+ const char *dn = NULL;
+ const char **attr_list;
+ const char *type;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ if ( ! idmap_alloc_ldap) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ctx = talloc_new(idmap_alloc_ldap);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get type */
+ switch (xid->type) {
+
+ case ID_TYPE_UID:
+ type = get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_UIDNUMBER);
+ break;
+
+ case ID_TYPE_GID:
+ type = get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_GIDNUMBER);
+ break;
+
+ default:
+ DEBUG(2, ("Invalid ID type (0x%x)\n", xid->type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ filter = talloc_asprintf(ctx, "(objectClass=%s)", LDAP_OBJ_IDPOOL);
+ CHECK_ALLOC_DONE(filter);
+
+ attr_list = get_attr_list(ctx, idpool_attr_list);
+ CHECK_ALLOC_DONE(attr_list);
+
+ rc = smbldap_search(idmap_alloc_ldap->smbldap_state,
+ idmap_alloc_ldap->suffix,
+ LDAP_SCOPE_SUBTREE, filter,
+ attr_list, 0, &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("%s object not found\n", LDAP_OBJ_IDPOOL));
+ goto done;
+ }
+
+ talloc_autofree_ldapmsg(ctx, result);
+
+ count = ldap_count_entries(idmap_alloc_ldap->smbldap_state->ldap_struct,
+ result);
+ if (count != 1) {
+ DEBUG(0,("Single %s object not found\n", LDAP_OBJ_IDPOOL));
+ goto done;
+ }
+
+ entry = ldap_first_entry(idmap_alloc_ldap->smbldap_state->ldap_struct,
+ result);
+
+ dn = smbldap_talloc_dn(ctx,
+ idmap_alloc_ldap->smbldap_state->ldap_struct,
+ entry);
+ if ( ! dn) {
+ goto done;
+ }
+
+ new_id_str = talloc_asprintf(ctx, "%lu", (unsigned long)xid->id);
+ if ( ! new_id_str) {
+ DEBUG(0,("Out of memory\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_REPLACE, type, new_id_str);
+
+ if (mods == NULL) {
+ DEBUG(0,("smbldap_set_mod() failed.\n"));
+ goto done;
+ }
+
+ rc = smbldap_modify(idmap_alloc_ldap->smbldap_state, dn, mods);
+
+ ldap_mods_free(mods, True);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1,("Failed to allocate new %s. "
+ "smbldap_modify() failed.\n", type));
+ goto done;
+ }
+
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+/**********************************
+ Close idmap ldap alloc
+**********************************/
+
+static NTSTATUS idmap_ldap_alloc_close(void)
+{
+ if (idmap_alloc_ldap) {
+ smbldap_free_struct(&idmap_alloc_ldap->smbldap_state);
+ DEBUG(5,("The connection to the LDAP server was closed\n"));
+ /* maybe free the results here --metze */
+ TALLOC_FREE(idmap_alloc_ldap);
+ }
+ return NT_STATUS_OK;
+}
+
+
+/**********************************************************************
+ IDMAP MAPPING LDAP BACKEND
+**********************************************************************/
+
+static int idmap_ldap_close_destructor(struct idmap_ldap_context *ctx)
+{
+ smbldap_free_struct(&ctx->smbldap_state);
+ DEBUG(5,("The connection to the LDAP server was closed\n"));
+ /* maybe free the results here --metze */
+
+ return 0;
+}
+
+/********************************
+ Initialise idmap database.
+********************************/
+
+static NTSTATUS idmap_ldap_db_init(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+ struct idmap_ldap_context *ctx = NULL;
+ char *config_option = NULL;
+ const char *range = NULL;
+ const char *tmp = NULL;
+
+ /* Only do init if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ ctx = TALLOC_ZERO_P(dom, struct idmap_ldap_context);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ config_option = talloc_asprintf(ctx, "idmap config %s", dom->name);
+ if ( ! config_option) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* load ranges */
+ range = lp_parm_const_string(-1, config_option, "range", NULL);
+ if (range && range[0]) {
+ if ((sscanf(range, "%u - %u", &ctx->filter_low_id,
+ &ctx->filter_high_id) != 2) ||
+ (ctx->filter_low_id > ctx->filter_high_id)) {
+ DEBUG(1, ("ERROR: invalid filter range [%s]", range));
+ ctx->filter_low_id = 0;
+ ctx->filter_high_id = 0;
+ }
+ }
+
+ if (dom->params && *(dom->params)) {
+ /* assume location is the only parameter */
+ ctx->url = talloc_strdup(ctx, dom->params);
+ } else {
+ tmp = lp_parm_const_string(-1, config_option, "ldap_url", NULL);
+
+ if ( ! tmp) {
+ DEBUG(1, ("ERROR: missing idmap ldap url\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ ctx->url = talloc_strdup(ctx, tmp);
+ }
+ CHECK_ALLOC_DONE(ctx->url);
+
+ tmp = lp_parm_const_string(-1, config_option, "ldap_base_dn", NULL);
+ if ( ! tmp || ! *tmp) {
+ tmp = lp_ldap_idmap_suffix();
+ if ( ! tmp) {
+ DEBUG(1, ("ERROR: missing idmap ldap suffix\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ ctx->suffix = talloc_strdup(ctx, tmp);
+ CHECK_ALLOC_DONE(ctx->suffix);
+
+ ret = smbldap_init(ctx, winbind_event_context(), ctx->url,
+ &ctx->smbldap_state);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("ERROR: smbldap_init (%s) failed!\n", ctx->url));
+ goto done;
+ }
+
+ ret = get_credentials( ctx, ctx->smbldap_state, config_option,
+ dom, &ctx->user_dn );
+ if ( !NT_STATUS_IS_OK(ret) ) {
+ DEBUG(1,("idmap_ldap_db_init: Failed to get connection "
+ "credentials (%s)\n", nt_errstr(ret)));
+ goto done;
+ }
+
+ /* set the destructor on the context, so that resource are properly
+ freed if the contexts is released */
+
+ talloc_set_destructor(ctx, idmap_ldap_close_destructor);
+
+ dom->private_data = ctx;
+ dom->initialized = True;
+
+ talloc_free(config_option);
+ return NT_STATUS_OK;
+
+/*failed */
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+/* max number of ids requested per batch query */
+#define IDMAP_LDAP_MAX_IDS 30
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+/* this function searches up to IDMAP_LDAP_MAX_IDS entries
+ * in maps for a match */
+static struct id_map *find_map_by_id(struct id_map **maps,
+ enum id_type type,
+ uint32_t id)
+{
+ int i;
+
+ for (i = 0; i < IDMAP_LDAP_MAX_IDS; i++) {
+ if (maps[i] == NULL) { /* end of the run */
+ return NULL;
+ }
+ if ((maps[i]->xid.type == type) && (maps[i]->xid.id == id)) {
+ return maps[i];
+ }
+ }
+
+ return NULL;
+}
+
+static NTSTATUS idmap_ldap_unixids_to_sids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *memctx;
+ struct idmap_ldap_context *ctx;
+ LDAPMessage *result = NULL;
+ const char *uidNumber;
+ const char *gidNumber;
+ const char **attr_list;
+ char *filter = NULL;
+ BOOL multi = False;
+ int idx = 0;
+ int bidx = 0;
+ int count;
+ int rc;
+ int i;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ /* Initilization my have been deferred because we were offline */
+ if ( ! dom->initialized) {
+ ret = idmap_ldap_db_init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context);
+
+ memctx = talloc_new(ctx);
+ if ( ! memctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ uidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_UIDNUMBER);
+ gidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_GIDNUMBER);
+
+ attr_list = get_attr_list(memctx, sidmap_attr_list);
+
+ if ( ! ids[1]) {
+ /* if we are requested just one mapping use the simple filter */
+
+ filter = talloc_asprintf(memctx, "(&(objectClass=%s)(%s=%lu))",
+ LDAP_OBJ_IDMAP_ENTRY,
+ (ids[0]->xid.type==ID_TYPE_UID)?uidNumber:gidNumber,
+ (unsigned long)ids[0]->xid.id);
+ CHECK_ALLOC_DONE(filter);
+ DEBUG(10, ("Filter: [%s]\n", filter));
+ } else {
+ /* multiple mappings */
+ multi = True;
+ }
+
+again:
+ if (multi) {
+
+ talloc_free(filter);
+ filter = talloc_asprintf(memctx,
+ "(&(objectClass=%s)(|",
+ LDAP_OBJ_IDMAP_ENTRY);
+ CHECK_ALLOC_DONE(filter);
+
+ bidx = idx;
+ for (i = 0; (i < IDMAP_LDAP_MAX_IDS) && ids[idx]; i++, idx++) {
+ filter = talloc_asprintf_append(filter, "(%s=%lu)",
+ (ids[idx]->xid.type==ID_TYPE_UID)?uidNumber:gidNumber,
+ (unsigned long)ids[idx]->xid.id);
+ CHECK_ALLOC_DONE(filter);
+ }
+ filter = talloc_asprintf_append(filter, "))");
+ CHECK_ALLOC_DONE(filter);
+ DEBUG(10, ("Filter: [%s]\n", filter));
+ } else {
+ bidx = 0;
+ idx = 1;
+ }
+
+ rc = smbldap_search(ctx->smbldap_state, ctx->suffix, LDAP_SCOPE_SUBTREE,
+ filter, attr_list, 0, &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(3,("Failure looking up ids (%s)\n", ldap_err2string(rc)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ count = ldap_count_entries(ctx->smbldap_state->ldap_struct, result);
+
+ if (count == 0) {
+ DEBUG(10, ("NO SIDs found\n"));
+ }
+
+ for (i = 0; i < count; i++) {
+ LDAPMessage *entry = NULL;
+ char *sidstr = NULL;
+ char *tmp = NULL;
+ enum id_type type;
+ struct id_map *map;
+ uint32_t id;
+
+ if (i == 0) { /* first entry */
+ entry = ldap_first_entry(ctx->smbldap_state->ldap_struct,
+ result);
+ } else { /* following ones */
+ entry = ldap_next_entry(ctx->smbldap_state->ldap_struct,
+ entry);
+ }
+ if ( ! entry) {
+ DEBUG(2, ("ERROR: Unable to fetch ldap entries "
+ "from results\n"));
+ break;
+ }
+
+ /* first check if the SID is present */
+ sidstr = smbldap_talloc_single_attribute(
+ ctx->smbldap_state->ldap_struct,
+ entry, LDAP_ATTRIBUTE_SID, memctx);
+ if ( ! sidstr) { /* no sid, skip entry */
+ DEBUG(2, ("WARNING SID not found on entry\n"));
+ continue;
+ }
+
+ /* now try to see if it is a uid, if not try with a gid
+ * (gid is more common, but in case both uidNumber and
+ * gidNumber are returned the SID is mapped to the uid
+ *not the gid) */
+ type = ID_TYPE_UID;
+ tmp = smbldap_talloc_single_attribute(
+ ctx->smbldap_state->ldap_struct,
+ entry, uidNumber, memctx);
+ if ( ! tmp) {
+ type = ID_TYPE_GID;
+ tmp = smbldap_talloc_single_attribute(
+ ctx->smbldap_state->ldap_struct,
+ entry, gidNumber, memctx);
+ }
+ if ( ! tmp) { /* wow very strange entry, how did it match ? */
+ DEBUG(5, ("Unprobable match on (%s), no uidNumber, "
+ "nor gidNumber returned\n", sidstr));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ id = strtoul(tmp, NULL, 10);
+ if ((id == 0) ||
+ (ctx->filter_low_id && (id < ctx->filter_low_id)) ||
+ (ctx->filter_high_id && (id > ctx->filter_high_id))) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). "
+ "Filtered!\n", id,
+ ctx->filter_low_id, ctx->filter_high_id));
+ TALLOC_FREE(sidstr);
+ TALLOC_FREE(tmp);
+ continue;
+ }
+ TALLOC_FREE(tmp);
+
+ map = find_map_by_id(&ids[bidx], type, id);
+ if (!map) {
+ DEBUG(2, ("WARNING: couldn't match sid (%s) "
+ "with requested ids\n", sidstr));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ if ( ! string_to_sid(map->sid, sidstr)) {
+ DEBUG(2, ("ERROR: Invalid SID on entry\n"));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+ TALLOC_FREE(sidstr);
+
+ /* mapped */
+ map->status = ID_MAPPED;
+
+ DEBUG(10, ("Mapped %s -> %lu (%d)\n",
+ sid_string_static(map->sid),
+ (unsigned long)map->xid.id, map->xid.type));
+ }
+
+ /* free the ldap results */
+ if (result) {
+ ldap_msgfree(result);
+ result = NULL;
+ }
+
+ if (multi && ids[idx]) { /* still some values to map */
+ goto again;
+ }
+
+ ret = NT_STATUS_OK;
+
+ /* mark all unknwon/expired ones as unmapped */
+ for (i = 0; ids[i]; i++) {
+ if (ids[i]->status != ID_MAPPED)
+ ids[i]->status = ID_UNMAPPED;
+ }
+
+done:
+ talloc_free(memctx);
+ return ret;
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+/* this function searches up to IDMAP_LDAP_MAX_IDS entries
+ * in maps for a match */
+static struct id_map *find_map_by_sid(struct id_map **maps, DOM_SID *sid)
+{
+ int i;
+
+ for (i = 0; i < IDMAP_LDAP_MAX_IDS; i++) {
+ if (maps[i] == NULL) { /* end of the run */
+ return NULL;
+ }
+ if (sid_equal(maps[i]->sid, sid)) {
+ return maps[i];
+ }
+ }
+
+ return NULL;
+}
+
+static NTSTATUS idmap_ldap_sids_to_unixids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ LDAPMessage *entry = NULL;
+ NTSTATUS ret;
+ TALLOC_CTX *memctx;
+ struct idmap_ldap_context *ctx;
+ LDAPMessage *result = NULL;
+ const char *uidNumber;
+ const char *gidNumber;
+ const char **attr_list;
+ char *filter = NULL;
+ BOOL multi = False;
+ int idx = 0;
+ int bidx = 0;
+ int count;
+ int rc;
+ int i;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ /* Initilization my have been deferred because we were offline */
+ if ( ! dom->initialized) {
+ ret = idmap_ldap_db_init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context);
+
+ memctx = talloc_new(ctx);
+ if ( ! memctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ uidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_UIDNUMBER);
+ gidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_GIDNUMBER);
+
+ attr_list = get_attr_list(memctx, sidmap_attr_list);
+
+ if ( ! ids[1]) {
+ /* if we are requested just one mapping use the simple filter */
+
+ filter = talloc_asprintf(memctx, "(&(objectClass=%s)(%s=%s))",
+ LDAP_OBJ_IDMAP_ENTRY,
+ LDAP_ATTRIBUTE_SID,
+ sid_string_static(ids[0]->sid));
+ CHECK_ALLOC_DONE(filter);
+ DEBUG(10, ("Filter: [%s]\n", filter));
+ } else {
+ /* multiple mappings */
+ multi = True;
+ }
+
+again:
+ if (multi) {
+
+ TALLOC_FREE(filter);
+ filter = talloc_asprintf(memctx,
+ "(&(objectClass=%s)(|",
+ LDAP_OBJ_IDMAP_ENTRY);
+ CHECK_ALLOC_DONE(filter);
+
+ bidx = idx;
+ for (i = 0; (i < IDMAP_LDAP_MAX_IDS) && ids[idx]; i++, idx++) {
+ filter = talloc_asprintf_append(filter, "(%s=%s)",
+ LDAP_ATTRIBUTE_SID,
+ sid_string_static(ids[idx]->sid));
+ CHECK_ALLOC_DONE(filter);
+ }
+ filter = talloc_asprintf_append(filter, "))");
+ CHECK_ALLOC_DONE(filter);
+ DEBUG(10, ("Filter: [%s]", filter));
+ } else {
+ bidx = 0;
+ idx = 1;
+ }
+
+ rc = smbldap_search(ctx->smbldap_state, ctx->suffix, LDAP_SCOPE_SUBTREE,
+ filter, attr_list, 0, &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(3,("Failure looking up sids (%s)\n",
+ ldap_err2string(rc)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ count = ldap_count_entries(ctx->smbldap_state->ldap_struct, result);
+
+ if (count == 0) {
+ DEBUG(10, ("NO SIDs found\n"));
+ }
+
+ for (i = 0; i < count; i++) {
+ char *sidstr = NULL;
+ char *tmp = NULL;
+ enum id_type type;
+ struct id_map *map;
+ DOM_SID sid;
+ uint32_t id;
+
+ if (i == 0) { /* first entry */
+ entry = ldap_first_entry(ctx->smbldap_state->ldap_struct,
+ result);
+ } else { /* following ones */
+ entry = ldap_next_entry(ctx->smbldap_state->ldap_struct,
+ entry);
+ }
+ if ( ! entry) {
+ DEBUG(2, ("ERROR: Unable to fetch ldap entries "
+ "from results\n"));
+ break;
+ }
+
+ /* first check if the SID is present */
+ sidstr = smbldap_talloc_single_attribute(
+ ctx->smbldap_state->ldap_struct,
+ entry, LDAP_ATTRIBUTE_SID, memctx);
+ if ( ! sidstr) { /* no sid ??, skip entry */
+ DEBUG(2, ("WARNING SID not found on entry\n"));
+ continue;
+ }
+
+ if ( ! string_to_sid(&sid, sidstr)) {
+ DEBUG(2, ("ERROR: Invalid SID on entry\n"));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ map = find_map_by_sid(&ids[bidx], &sid);
+ if (!map) {
+ DEBUG(2, ("WARNING: couldn't find entry sid (%s) "
+ "in ids", sidstr));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ TALLOC_FREE(sidstr);
+
+ /* now try to see if it is a uid, if not try with a gid
+ * (gid is more common, but in case both uidNumber and
+ * gidNumber are returned the SID is mapped to the uid
+ * not the gid) */
+ type = ID_TYPE_UID;
+ tmp = smbldap_talloc_single_attribute(
+ ctx->smbldap_state->ldap_struct,
+ entry, uidNumber, memctx);
+ if ( ! tmp) {
+ type = ID_TYPE_GID;
+ tmp = smbldap_talloc_single_attribute(
+ ctx->smbldap_state->ldap_struct,
+ entry, gidNumber, memctx);
+ }
+ if ( ! tmp) { /* no ids ?? */
+ DEBUG(5, ("no uidNumber, "
+ "nor gidNumber attributes found\n"));
+ continue;
+ }
+
+ id = strtoul(tmp, NULL, 10);
+ if ((id == 0) ||
+ (ctx->filter_low_id && (id < ctx->filter_low_id)) ||
+ (ctx->filter_high_id && (id > ctx->filter_high_id))) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). "
+ "Filtered!\n", id,
+ ctx->filter_low_id, ctx->filter_high_id));
+ TALLOC_FREE(tmp);
+ continue;
+ }
+ TALLOC_FREE(tmp);
+
+ /* mapped */
+ map->xid.type = type;
+ map->xid.id = id;
+ map->status = ID_MAPPED;
+
+ DEBUG(10, ("Mapped %s -> %lu (%d)\n",
+ sid_string_static(map->sid),
+ (unsigned long)map->xid.id, map->xid.type));
+ }
+
+ /* free the ldap results */
+ if (result) {
+ ldap_msgfree(result);
+ result = NULL;
+ }
+
+ if (multi && ids[idx]) { /* still some values to map */
+ goto again;
+ }
+
+ ret = NT_STATUS_OK;
+
+ /* mark all unknwon/expired ones as unmapped */
+ for (i = 0; ids[i]; i++) {
+ if (ids[i]->status != ID_MAPPED)
+ ids[i]->status = ID_UNMAPPED;
+ }
+
+done:
+ talloc_free(memctx);
+ return ret;
+}
+
+/**********************************
+ set a mapping.
+**********************************/
+
+/* TODO: change this: This function cannot be called to modify a mapping,
+ * only set a new one */
+
+static NTSTATUS idmap_ldap_set_mapping(struct idmap_domain *dom,
+ const struct id_map *map)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *memctx;
+ struct idmap_ldap_context *ctx;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ const char *type;
+ char *id_str;
+ char *sid;
+ char *dn;
+ int rc = -1;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ /* Initilization my have been deferred because we were offline */
+ if ( ! dom->initialized) {
+ ret = idmap_ldap_db_init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context);
+
+ switch(map->xid.type) {
+ case ID_TYPE_UID:
+ type = get_attr_key2string(sidmap_attr_list,
+ LDAP_ATTR_UIDNUMBER);
+ break;
+
+ case ID_TYPE_GID:
+ type = get_attr_key2string(sidmap_attr_list,
+ LDAP_ATTR_GIDNUMBER);
+ break;
+
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ memctx = talloc_new(ctx);
+ if ( ! memctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ id_str = talloc_asprintf(memctx, "%lu", (unsigned long)map->xid.id);
+ CHECK_ALLOC_DONE(id_str);
+
+ sid = talloc_strdup(memctx, sid_string_static(map->sid));
+ CHECK_ALLOC_DONE(sid);
+
+ dn = talloc_asprintf(memctx, "%s=%s,%s",
+ get_attr_key2string(sidmap_attr_list, LDAP_ATTR_SID),
+ sid,
+ ctx->suffix);
+ CHECK_ALLOC_DONE(dn);
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ "objectClass", LDAP_OBJ_IDMAP_ENTRY);
+
+ smbldap_make_mod(ctx->smbldap_state->ldap_struct,
+ entry, &mods, type, id_str);
+
+ smbldap_make_mod(ctx->smbldap_state->ldap_struct, entry, &mods,
+ get_attr_key2string(sidmap_attr_list, LDAP_ATTR_SID),
+ sid);
+
+ if ( ! mods) {
+ DEBUG(2, ("ERROR: No mods?\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* TODO: remove conflicting mappings! */
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_SID_ENTRY);
+
+ DEBUG(10, ("Set DN %s (%s -> %s)\n", dn, sid, id_str));
+
+ rc = smbldap_add(ctx->smbldap_state, dn, mods);
+ ldap_mods_free(mods, True);
+
+ if (rc != LDAP_SUCCESS) {
+ char *ld_error = NULL;
+ ldap_get_option(ctx->smbldap_state->ldap_struct,
+ LDAP_OPT_ERROR_STRING, &ld_error);
+ DEBUG(0,("ldap_set_mapping_internals: Failed to add %s to %lu "
+ "mapping [%s]\n", sid,
+ (unsigned long)map->xid.id, type));
+ DEBUG(0, ("ldap_set_mapping_internals: Error was: %s (%s)\n",
+ ld_error ? ld_error : "(NULL)", ldap_err2string (rc)));
+ if (ld_error) {
+ ldap_memfree(ld_error);
+ }
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ DEBUG(10,("ldap_set_mapping: Successfully created mapping from %s to "
+ "%lu [%s]\n", sid, (unsigned long)map->xid.id, type));
+
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(memctx);
+ return ret;
+}
+
+/**********************************
+ Close the idmap ldap instance
+**********************************/
+
+static NTSTATUS idmap_ldap_close(struct idmap_domain *dom)
+{
+ struct idmap_ldap_context *ctx;
+
+ if (dom->private_data) {
+ ctx = talloc_get_type(dom->private_data,
+ struct idmap_ldap_context);
+
+ talloc_free(ctx);
+ dom->private_data = NULL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static struct idmap_methods idmap_ldap_methods = {
+
+ .init = idmap_ldap_db_init,
+ .unixids_to_sids = idmap_ldap_unixids_to_sids,
+ .sids_to_unixids = idmap_ldap_sids_to_unixids,
+ .set_mapping = idmap_ldap_set_mapping,
+ .close_fn = idmap_ldap_close
+};
+
+static struct idmap_alloc_methods idmap_ldap_alloc_methods = {
+
+ .init = idmap_ldap_alloc_init,
+ .allocate_id = idmap_ldap_allocate_id,
+ .get_id_hwm = idmap_ldap_get_hwm,
+ .set_id_hwm = idmap_ldap_set_hwm,
+ .close_fn = idmap_ldap_alloc_close,
+ /* .dump_data = TODO */
+};
+
+NTSTATUS idmap_alloc_ldap_init(void)
+{
+ return smb_register_idmap_alloc(SMB_IDMAP_INTERFACE_VERSION, "ldap",
+ &idmap_ldap_alloc_methods);
+}
+
+NTSTATUS idmap_ldap_init(void)
+{
+ NTSTATUS ret;
+
+ /* FIXME: bad hack to actually register also the alloc_ldap module
+ * without changining configure.in */
+ ret = idmap_alloc_ldap_init();
+ if (! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "ldap",
+ &idmap_ldap_methods);
+}
+
diff --git a/source3/winbindd/idmap_nss.c b/source3/winbindd/idmap_nss.c
new file mode 100644
index 0000000000..5bb2389c93
--- /dev/null
+++ b/source3/winbindd/idmap_nss.c
@@ -0,0 +1,223 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap PASSDB backend
+
+ Copyright (C) Simo Sorce 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+/*****************************
+ Initialise idmap database.
+*****************************/
+
+static NTSTATUS idmap_nss_int_init(struct idmap_domain *dom)
+{
+ dom->initialized = True;
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+static NTSTATUS idmap_nss_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids)
+{
+ TALLOC_CTX *ctx;
+ int i;
+
+ if (! dom->initialized) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ctx = talloc_new(dom);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; ids[i]; i++) {
+ struct passwd *pw;
+ struct group *gr;
+ const char *name;
+ enum lsa_SidType type;
+ BOOL ret;
+
+ switch (ids[i]->xid.type) {
+ case ID_TYPE_UID:
+ pw = getpwuid((uid_t)ids[i]->xid.id);
+
+ if (!pw) {
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+ name = pw->pw_name;
+ break;
+ case ID_TYPE_GID:
+ gr = getgrgid((gid_t)ids[i]->xid.id);
+
+ if (!gr) {
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+ name = gr->gr_name;
+ break;
+ default: /* ?? */
+ ids[i]->status = ID_UNKNOWN;
+ continue;
+ }
+
+ /* by default calls to winbindd are disabled
+ the following call will not recurse so this is safe */
+ winbind_on();
+ /* Lookup name from PDC using lsa_lookup_names() */
+ ret = winbind_lookup_name(dom->name, name, ids[i]->sid, &type);
+ winbind_off();
+
+ if (!ret) {
+ /* TODO: how do we know if the name is really not mapped,
+ * or something just failed ? */
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+
+ switch (type) {
+ case SID_NAME_USER:
+ if (ids[i]->xid.type == ID_TYPE_UID) {
+ ids[i]->status = ID_MAPPED;
+ }
+ break;
+
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ if (ids[i]->xid.type == ID_TYPE_GID) {
+ ids[i]->status = ID_MAPPED;
+ }
+ break;
+
+ default:
+ ids[i]->status = ID_UNKNOWN;
+ break;
+ }
+ }
+
+
+ talloc_free(ctx);
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+static NTSTATUS idmap_nss_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+{
+ TALLOC_CTX *ctx;
+ int i;
+
+ if (! dom->initialized) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ctx = talloc_new(dom);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; ids[i]; i++) {
+ struct passwd *pw;
+ struct group *gr;
+ enum lsa_SidType type;
+ const char *dom_name = NULL;
+ const char *name = NULL;
+ BOOL ret;
+
+ /* by default calls to winbindd are disabled
+ the following call will not recurse so this is safe */
+ winbind_on();
+ ret = winbind_lookup_sid(ctx, ids[i]->sid, &dom_name, &name, &type);
+ winbind_off();
+
+ if (!ret) {
+ /* TODO: how do we know if the name is really not mapped,
+ * or something just failed ? */
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+
+ switch (type) {
+ case SID_NAME_USER:
+
+ /* this will find also all lower case name and use username level */
+
+ pw = Get_Pwnam(name);
+ if (pw) {
+ ids[i]->xid.id = pw->pw_uid;
+ ids[i]->xid.type = ID_TYPE_UID;
+ ids[i]->status = ID_MAPPED;
+ }
+ break;
+
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+
+ gr = getgrnam(name);
+ if (gr) {
+ ids[i]->xid.id = gr->gr_gid;
+ ids[i]->xid.type = ID_TYPE_GID;
+ ids[i]->status = ID_MAPPED;
+ }
+ break;
+
+ default:
+ ids[i]->status = ID_UNKNOWN;
+ break;
+ }
+ }
+
+ talloc_free(ctx);
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Close the idmap tdb instance
+**********************************/
+
+static NTSTATUS idmap_nss_close(struct idmap_domain *dom)
+{
+ return NT_STATUS_OK;
+}
+
+static struct idmap_methods nss_methods = {
+
+ .init = idmap_nss_int_init,
+ .unixids_to_sids = idmap_nss_unixids_to_sids,
+ .sids_to_unixids = idmap_nss_sids_to_unixids,
+ .close_fn = idmap_nss_close
+};
+
+NTSTATUS idmap_nss_init(void)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "nss", &nss_methods);
+}
diff --git a/source3/winbindd/idmap_passdb.c b/source3/winbindd/idmap_passdb.c
new file mode 100644
index 0000000000..17afd71ab8
--- /dev/null
+++ b/source3/winbindd/idmap_passdb.c
@@ -0,0 +1,139 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap PASSDB backend
+
+ Copyright (C) Simo Sorce 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+/*****************************
+ Initialise idmap database.
+*****************************/
+
+static NTSTATUS idmap_pdb_init(struct idmap_domain *dom)
+{
+ dom->initialized = True;
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+static NTSTATUS idmap_pdb_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids)
+{
+ int i;
+
+ if (! dom->initialized) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ for (i = 0; ids[i]; i++) {
+
+ /* unmapped by default */
+ ids[i]->status = ID_UNMAPPED;
+
+ switch (ids[i]->xid.type) {
+ case ID_TYPE_UID:
+ if (pdb_uid_to_sid((uid_t)ids[i]->xid.id, ids[i]->sid)) {
+ ids[i]->status = ID_MAPPED;
+ }
+ break;
+ case ID_TYPE_GID:
+ if (pdb_gid_to_sid((gid_t)ids[i]->xid.id, ids[i]->sid)) {
+ ids[i]->status = ID_MAPPED;
+ }
+ break;
+ default: /* ?? */
+ ids[i]->status = ID_UNKNOWN;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+static NTSTATUS idmap_pdb_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+{
+ int i;
+
+ if (! dom->initialized) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ for (i = 0; ids[i]; i++) {
+ enum lsa_SidType type;
+ union unid_t id;
+
+ if (pdb_sid_to_id(ids[i]->sid, &id, &type)) {
+ switch (type) {
+ case SID_NAME_USER:
+ ids[i]->xid.id = id.uid;
+ ids[i]->xid.type = ID_TYPE_UID;
+ ids[i]->status = ID_MAPPED;
+ break;
+
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ ids[i]->xid.id = id.gid;
+ ids[i]->xid.type = ID_TYPE_GID;
+ ids[i]->status = ID_MAPPED;
+ break;
+
+ default: /* ?? */
+ /* make sure it is marked as unmapped */
+ ids[i]->status = ID_UNKNOWN;
+ break;
+ }
+ } else {
+ /* Query Failed */
+ ids[i]->status = ID_UNMAPPED;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Close the idmap tdb instance
+**********************************/
+
+static NTSTATUS idmap_pdb_close(struct idmap_domain *dom)
+{
+ return NT_STATUS_OK;
+}
+
+static struct idmap_methods passdb_methods = {
+
+ .init = idmap_pdb_init,
+ .unixids_to_sids = idmap_pdb_unixids_to_sids,
+ .sids_to_unixids = idmap_pdb_sids_to_unixids,
+ .close_fn =idmap_pdb_close
+};
+
+NTSTATUS idmap_passdb_init(void)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "passdb", &passdb_methods);
+}
diff --git a/source3/winbindd/idmap_rid.c b/source3/winbindd/idmap_rid.c
new file mode 100644
index 0000000000..8e5f1302f7
--- /dev/null
+++ b/source3/winbindd/idmap_rid.c
@@ -0,0 +1,267 @@
+/*
+ * idmap_rid: static map between Active Directory/NT RIDs and RFC 2307 accounts
+ * Copyright (C) Guenther Deschner, 2004
+ * Copyright (C) Sumit Bose, 2004
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+struct idmap_rid_context {
+ const char *domain_name;
+ uint32_t low_id;
+ uint32_t high_id;
+ uint32_t base_rid;
+};
+
+/******************************************************************************
+ compat params can't be used because of the completely different way
+ we support multiple domains in the new idmap
+ *****************************************************************************/
+
+static NTSTATUS idmap_rid_initialize(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+ struct idmap_rid_context *ctx;
+ char *config_option = NULL;
+ const char *range;
+ uid_t low_uid = 0;
+ uid_t high_uid = 0;
+ gid_t low_gid = 0;
+ gid_t high_gid = 0;
+
+ if ( (ctx = TALLOC_ZERO_P(dom, struct idmap_rid_context)) == NULL ) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ config_option = talloc_asprintf(ctx, "idmap config %s", dom->name);
+ if ( ! config_option) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+
+ range = lp_parm_const_string(-1, config_option, "range", NULL);
+ if ( !range ||
+ (sscanf(range, "%u - %u", &ctx->low_id, &ctx->high_id) != 2) ||
+ (ctx->low_id > ctx->high_id))
+ {
+ ctx->low_id = 0;
+ ctx->high_id = 0;
+ }
+
+ /* lets see if the range is defined by the old idmap uid/idmap gid */
+ if (!ctx->low_id && !ctx->high_id) {
+ if (lp_idmap_uid(&low_uid, &high_uid)) {
+ ctx->low_id = low_uid;
+ ctx->high_id = high_uid;
+ }
+
+ if (lp_idmap_gid(&low_gid, &high_gid)) {
+ if ((ctx->low_id != low_gid) ||
+ (ctx->high_id != high_uid)) {
+ DEBUG(1, ("ERROR: idmap uid range must match idmap gid range\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto failed;
+ }
+ }
+ }
+
+ if (!ctx->low_id || !ctx->high_id) {
+ DEBUG(1, ("ERROR: Invalid configuration, ID range missing or invalid\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto failed;
+ }
+
+ ctx->base_rid = lp_parm_int(-1, config_option, "base_rid", 0);
+ ctx->domain_name = talloc_strdup( ctx, dom->name );
+
+ dom->private_data = ctx;
+ dom->initialized = True;
+
+ talloc_free(config_option);
+ return NT_STATUS_OK;
+
+failed:
+ talloc_free(ctx);
+ return ret;
+}
+
+static NTSTATUS idmap_rid_id_to_sid(TALLOC_CTX *memctx, struct idmap_rid_context *ctx, struct id_map *map)
+{
+ struct winbindd_domain *domain;
+
+ /* apply filters before checking */
+ if ((map->xid.id < ctx->low_id) || (map->xid.id > ctx->high_id)) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, ctx->low_id, ctx->high_id));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ if ( (domain = find_domain_from_name_noinit(ctx->domain_name)) == NULL ) {
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ sid_compose(map->sid, &domain->sid, map->xid.id - ctx->low_id + ctx->base_rid);
+
+ /* We **really** should have some way of validating
+ the SID exists and is the correct type here. But
+ that is a deficiency in the idmap_rid design. */
+
+ map->status = ID_MAPPED;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Single sid to id lookup function.
+**********************************/
+
+static NTSTATUS idmap_rid_sid_to_id(TALLOC_CTX *memctx, struct idmap_rid_context *ctx, struct id_map *map)
+{
+ uint32_t rid;
+
+ sid_peek_rid(map->sid, &rid);
+ map->xid.id = rid - ctx->base_rid + ctx->low_id;
+
+ /* apply filters before returning result */
+
+ if ((map->xid.id < ctx->low_id) || (map->xid.id > ctx->high_id)) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, ctx->low_id, ctx->high_id));
+ map->status = ID_UNMAPPED;
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ /* We **really** should have some way of validating
+ the SID exists and is the correct type here. But
+ that is a deficiency in the idmap_rid design. */
+
+ map->status = ID_MAPPED;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+static NTSTATUS idmap_rid_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids)
+{
+ struct idmap_rid_context *ridctx;
+ TALLOC_CTX *ctx;
+ NTSTATUS ret;
+ int i;
+
+ /* Initilization my have been deferred because of an error, retry or fail */
+ if ( ! dom->initialized) {
+ ret = idmap_rid_initialize(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ridctx = talloc_get_type(dom->private_data, struct idmap_rid_context);
+
+ ctx = talloc_new(dom);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; ids[i]; i++) {
+
+ ret = idmap_rid_id_to_sid(ctx, ridctx, ids[i]);
+
+ if (( ! NT_STATUS_IS_OK(ret)) &&
+ ( ! NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
+ /* some fatal error occurred, log it */
+ DEBUG(3, ("Unexpected error resolving an ID (%d)\n", ids[i]->xid.id));
+ }
+ }
+
+ talloc_free(ctx);
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+static NTSTATUS idmap_rid_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+{
+ struct idmap_rid_context *ridctx;
+ TALLOC_CTX *ctx;
+ NTSTATUS ret;
+ int i;
+
+ /* Initilization my have been deferred because of an error, retry or fail */
+ if ( ! dom->initialized) {
+ ret = idmap_rid_initialize(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ridctx = talloc_get_type(dom->private_data, struct idmap_rid_context);
+
+ ctx = talloc_new(dom);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; ids[i]; i++) {
+
+ ret = idmap_rid_sid_to_id(ctx, ridctx, ids[i]);
+
+ if (( ! NT_STATUS_IS_OK(ret)) &&
+ ( ! NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
+ /* some fatal error occurred, log it */
+ DEBUG(3, ("Unexpected error resolving a SID (%s)\n",
+ sid_string_static(ids[i]->sid)));
+ }
+ }
+
+ talloc_free(ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_rid_close(struct idmap_domain *dom)
+{
+ if (dom->private_data) {
+ TALLOC_FREE(dom->private_data);
+ }
+ return NT_STATUS_OK;
+}
+
+static struct idmap_methods rid_methods = {
+ .init = idmap_rid_initialize,
+ .unixids_to_sids = idmap_rid_unixids_to_sids,
+ .sids_to_unixids = idmap_rid_sids_to_unixids,
+ .close_fn = idmap_rid_close
+};
+
+NTSTATUS idmap_rid_init(void)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "rid", &rid_methods);
+}
+
diff --git a/source3/winbindd/idmap_tdb.c b/source3/winbindd/idmap_tdb.c
new file mode 100644
index 0000000000..97000689fa
--- /dev/null
+++ b/source3/winbindd/idmap_tdb.c
@@ -0,0 +1,1232 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap TDB backend
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Jeremy Allison 2006
+ Copyright (C) Simo Sorce 2003-2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+/* High water mark keys */
+#define HWM_GROUP "GROUP HWM"
+#define HWM_USER "USER HWM"
+
+static struct idmap_tdb_state {
+
+ /* User and group id pool */
+ uid_t low_uid, high_uid; /* Range of uids to allocate */
+ gid_t low_gid, high_gid; /* Range of gids to allocate */
+
+} idmap_tdb_state;
+
+/*****************************************************************************
+ For idmap conversion: convert one record to new format
+ Ancient versions (eg 2.2.3a) of winbindd_idmap.tdb mapped DOMAINNAME/rid
+ instead of the SID.
+*****************************************************************************/
+static int convert_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA data, void *state)
+{
+ struct winbindd_domain *domain;
+ char *p;
+ DOM_SID sid;
+ uint32 rid;
+ fstring keystr;
+ fstring dom_name;
+ TDB_DATA key2;
+ BOOL *failed = (BOOL *)state;
+
+ DEBUG(10,("Converting %s\n", (const char *)key.dptr));
+
+ p = strchr((const char *)key.dptr, '/');
+ if (!p)
+ return 0;
+
+ *p = 0;
+ fstrcpy(dom_name, (const char *)key.dptr);
+ *p++ = '/';
+
+ domain = find_domain_from_name(dom_name);
+ if (domain == NULL) {
+ /* We must delete the old record. */
+ DEBUG(0,("Unable to find domain %s\n", dom_name ));
+ DEBUG(0,("deleting record %s\n", (const char *)key.dptr ));
+
+ if (tdb_delete(tdb, key) != 0) {
+ DEBUG(0, ("Unable to delete record %s\n", (const char *)key.dptr));
+ *failed = True;
+ return -1;
+ }
+
+ return 0;
+ }
+
+ rid = atoi(p);
+
+ sid_copy(&sid, &domain->sid);
+ sid_append_rid(&sid, rid);
+
+ sid_to_string(keystr, &sid);
+ key2 = string_term_tdb_data(keystr);
+
+ if (tdb_store(tdb, key2, data, TDB_INSERT) != 0) {
+ DEBUG(0,("Unable to add record %s\n", (const char *)key2.dptr ));
+ *failed = True;
+ return -1;
+ }
+
+ if (tdb_store(tdb, data, key2, TDB_REPLACE) != 0) {
+ DEBUG(0,("Unable to update record %s\n", (const char *)data.dptr ));
+ *failed = True;
+ return -1;
+ }
+
+ if (tdb_delete(tdb, key) != 0) {
+ DEBUG(0,("Unable to delete record %s\n", (const char *)key.dptr ));
+ *failed = True;
+ return -1;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ Convert the idmap database from an older version.
+*****************************************************************************/
+
+static BOOL idmap_tdb_upgrade(const char *idmap_name)
+{
+ int32 vers;
+ BOOL bigendianheader;
+ BOOL failed = False;
+ TDB_CONTEXT *idmap_tdb;
+
+ DEBUG(0, ("Upgrading winbindd_idmap.tdb from an old version\n"));
+
+ if (!(idmap_tdb = tdb_open_log(idmap_name, 0,
+ TDB_DEFAULT, O_RDWR,
+ 0600))) {
+ DEBUG(0, ("Unable to open idmap database\n"));
+ return False;
+ }
+
+ bigendianheader = (tdb_get_flags(idmap_tdb) & TDB_BIGENDIAN) ? True : False;
+
+ vers = tdb_fetch_int32(idmap_tdb, "IDMAP_VERSION");
+
+ if (((vers == -1) && bigendianheader) || (IREV(vers) == IDMAP_VERSION)) {
+ /* Arrggghh ! Bytereversed or old big-endian - make order independent ! */
+ /*
+ * high and low records were created on a
+ * big endian machine and will need byte-reversing.
+ */
+
+ int32 wm;
+
+ wm = tdb_fetch_int32(idmap_tdb, HWM_USER);
+
+ if (wm != -1) {
+ wm = IREV(wm);
+ } else {
+ wm = idmap_tdb_state.low_uid;
+ }
+
+ if (tdb_store_int32(idmap_tdb, HWM_USER, wm) == -1) {
+ DEBUG(0, ("Unable to byteswap user hwm in idmap database\n"));
+ tdb_close(idmap_tdb);
+ return False;
+ }
+
+ wm = tdb_fetch_int32(idmap_tdb, HWM_GROUP);
+ if (wm != -1) {
+ wm = IREV(wm);
+ } else {
+ wm = idmap_tdb_state.low_gid;
+ }
+
+ if (tdb_store_int32(idmap_tdb, HWM_GROUP, wm) == -1) {
+ DEBUG(0, ("Unable to byteswap group hwm in idmap database\n"));
+ tdb_close(idmap_tdb);
+ return False;
+ }
+ }
+
+ /* the old format stored as DOMAIN/rid - now we store the SID direct */
+ tdb_traverse(idmap_tdb, convert_fn, &failed);
+
+ if (failed) {
+ DEBUG(0, ("Problem during conversion\n"));
+ tdb_close(idmap_tdb);
+ return False;
+ }
+
+ if (tdb_store_int32(idmap_tdb, "IDMAP_VERSION", IDMAP_VERSION) == -1) {
+ DEBUG(0, ("Unable to dtore idmap version in databse\n"));
+ tdb_close(idmap_tdb);
+ return False;
+ }
+
+ tdb_close(idmap_tdb);
+ return True;
+}
+
+/* WARNING: We can't open a tdb twice inthe same process, for that reason
+ * I'm going to use a hack with open ref counts to open the winbindd_idmap.tdb
+ * only once. We will later decide whether to split the db in multiple files
+ * or come up with a better solution to share them. */
+
+static TDB_CONTEXT *idmap_tdb_common_ctx;
+static int idmap_tdb_open_ref_count = 0;
+
+static NTSTATUS idmap_tdb_open_db(TALLOC_CTX *memctx, TDB_CONTEXT **tdbctx)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *ctx;
+ SMB_STRUCT_STAT stbuf;
+ char *tdbfile = NULL;
+ int32 version;
+ BOOL tdb_is_new = False;
+
+ if (idmap_tdb_open_ref_count) { /* the tdb has already been opened */
+ idmap_tdb_open_ref_count++;
+ *tdbctx = idmap_tdb_common_ctx;
+ return NT_STATUS_OK;
+ }
+
+ /* use our own context here */
+ ctx = talloc_new(memctx);
+ if (!ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* use the old database if present */
+ tdbfile = talloc_strdup(ctx, lock_path("winbindd_idmap.tdb"));
+ if (!tdbfile) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (!file_exist(tdbfile, &stbuf)) {
+ tdb_is_new = True;
+ }
+
+ DEBUG(10,("Opening tdbfile %s\n", tdbfile ));
+
+ /* Open idmap repository */
+ if (!(idmap_tdb_common_ctx = tdb_open_log(tdbfile, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0644))) {
+ DEBUG(0, ("Unable to open idmap database\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (tdb_is_new) {
+ /* the file didn't existed before opening it, let's
+ * store idmap version as nobody else yet opened and
+ * stored it. I do not like this method but didn't
+ * found a way to understand if an opened tdb have
+ * been just created or not --- SSS */
+ tdb_store_int32(idmap_tdb_common_ctx, "IDMAP_VERSION", IDMAP_VERSION);
+ }
+
+ /* check against earlier versions */
+ version = tdb_fetch_int32(idmap_tdb_common_ctx, "IDMAP_VERSION");
+ if (version != IDMAP_VERSION) {
+
+ /* backup_tdb expects the tdb not to be open */
+ tdb_close(idmap_tdb_common_ctx);
+
+ if ( ! idmap_tdb_upgrade(tdbfile)) {
+
+ DEBUG(0, ("Unable to open idmap database, it's in an old formati, and upgrade failed!\n"));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ /* Re-Open idmap repository */
+ if (!(idmap_tdb_common_ctx = tdb_open_log(tdbfile, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0644))) {
+ DEBUG(0, ("Unable to open idmap database\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ *tdbctx = idmap_tdb_common_ctx;
+ idmap_tdb_open_ref_count++;
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+ /* NEVER use tdb_close() except for the conversion routines that are guaranteed
+ * to run only when the database is opened the first time, always use this function. */
+
+BOOL idmap_tdb_tdb_close(TDB_CONTEXT *tdbctx)
+{
+ if (tdbctx != idmap_tdb_common_ctx) {
+ DEBUG(0, ("ERROR: Invalid tdb context!"));
+ return False;
+ }
+
+ idmap_tdb_open_ref_count--;
+ if (idmap_tdb_open_ref_count) {
+ return True;
+ }
+
+ return tdb_close(idmap_tdb_common_ctx);
+}
+
+/**********************************************************************
+ IDMAP ALLOC TDB BACKEND
+**********************************************************************/
+
+static TDB_CONTEXT *idmap_alloc_tdb;
+
+/**********************************
+ Initialise idmap alloc database.
+**********************************/
+
+static NTSTATUS idmap_tdb_alloc_init( const char *params )
+{
+ NTSTATUS ret;
+ TALLOC_CTX *ctx;
+ const char *range;
+ uid_t low_uid = 0;
+ uid_t high_uid = 0;
+ gid_t low_gid = 0;
+ gid_t high_gid = 0;
+
+ /* use our own context here */
+ ctx = talloc_new(NULL);
+ if (!ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = idmap_tdb_open_db(ctx, &idmap_alloc_tdb);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ talloc_free(ctx);
+ return ret;
+ }
+
+ talloc_free(ctx);
+
+ /* load ranges */
+ idmap_tdb_state.low_uid = 0;
+ idmap_tdb_state.high_uid = 0;
+ idmap_tdb_state.low_gid = 0;
+ idmap_tdb_state.high_gid = 0;
+
+ range = lp_parm_const_string(-1, "idmap alloc config", "range", NULL);
+ if (range && range[0]) {
+ unsigned low_id, high_id;
+
+ if (sscanf(range, "%u - %u", &low_id, &high_id) == 2) {
+ if (low_id < high_id) {
+ idmap_tdb_state.low_gid = idmap_tdb_state.low_uid = low_id;
+ idmap_tdb_state.high_gid = idmap_tdb_state.high_uid = high_id;
+ } else {
+ DEBUG(1, ("ERROR: invalid idmap alloc range [%s]", range));
+ }
+ } else {
+ DEBUG(1, ("ERROR: invalid syntax for idmap alloc config:range [%s]", range));
+ }
+ }
+
+ /* Create high water marks for group and user id */
+ if (lp_idmap_uid(&low_uid, &high_uid)) {
+ idmap_tdb_state.low_uid = low_uid;
+ idmap_tdb_state.high_uid = high_uid;
+ }
+
+ if (lp_idmap_gid(&low_gid, &high_gid)) {
+ idmap_tdb_state.low_gid = low_gid;
+ idmap_tdb_state.high_gid = high_gid;
+ }
+
+ if (idmap_tdb_state.high_uid <= idmap_tdb_state.low_uid) {
+ DEBUG(1, ("idmap uid range missing or invalid\n"));
+ DEBUGADD(1, ("idmap will be unable to map foreign SIDs\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ } else {
+ uint32 low_id;
+
+ if (((low_id = tdb_fetch_int32(idmap_alloc_tdb, HWM_USER)) == -1) ||
+ (low_id < idmap_tdb_state.low_uid)) {
+ if (tdb_store_int32(idmap_alloc_tdb, HWM_USER, idmap_tdb_state.low_uid) == -1) {
+ DEBUG(0, ("Unable to initialise user hwm in idmap database\n"));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+ }
+ }
+
+ if (idmap_tdb_state.high_gid <= idmap_tdb_state.low_gid) {
+ DEBUG(1, ("idmap gid range missing or invalid\n"));
+ DEBUGADD(1, ("idmap will be unable to map foreign SIDs\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ } else {
+ uint32 low_id;
+
+ if (((low_id = tdb_fetch_int32(idmap_alloc_tdb, HWM_GROUP)) == -1) ||
+ (low_id < idmap_tdb_state.low_gid)) {
+ if (tdb_store_int32(idmap_alloc_tdb, HWM_GROUP, idmap_tdb_state.low_gid) == -1) {
+ DEBUG(0, ("Unable to initialise group hwm in idmap database\n"));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Allocate a new id.
+**********************************/
+
+static NTSTATUS idmap_tdb_allocate_id(struct unixid *xid)
+{
+ BOOL ret;
+ const char *hwmkey;
+ const char *hwmtype;
+ uint32_t high_hwm;
+ uint32_t hwm;
+
+ /* Get current high water mark */
+ switch (xid->type) {
+
+ case ID_TYPE_UID:
+ hwmkey = HWM_USER;
+ hwmtype = "UID";
+ high_hwm = idmap_tdb_state.high_uid;
+ break;
+
+ case ID_TYPE_GID:
+ hwmkey = HWM_GROUP;
+ hwmtype = "GID";
+ high_hwm = idmap_tdb_state.high_gid;
+ break;
+
+ default:
+ DEBUG(2, ("Invalid ID type (0x%x)\n", xid->type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if ((hwm = tdb_fetch_int32(idmap_alloc_tdb, hwmkey)) == -1) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ /* check it is in the range */
+ if (hwm > high_hwm) {
+ DEBUG(1, ("Fatal Error: %s range full!! (max: %lu)\n",
+ hwmtype, (unsigned long)high_hwm));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* fetch a new id and increment it */
+ ret = tdb_change_uint32_atomic(idmap_alloc_tdb, hwmkey, &hwm, 1);
+ if (!ret) {
+ DEBUG(1, ("Fatal error while fetching a new %s value\n!", hwmtype));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* recheck it is in the range */
+ if (hwm > high_hwm) {
+ DEBUG(1, ("Fatal Error: %s range full!! (max: %lu)\n",
+ hwmtype, (unsigned long)high_hwm));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ xid->id = hwm;
+ DEBUG(10,("New %s = %d\n", hwmtype, hwm));
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Get current highest id.
+**********************************/
+
+static NTSTATUS idmap_tdb_get_hwm(struct unixid *xid)
+{
+ const char *hwmkey;
+ const char *hwmtype;
+ uint32_t hwm;
+ uint32_t high_hwm;
+
+ /* Get current high water mark */
+ switch (xid->type) {
+
+ case ID_TYPE_UID:
+ hwmkey = HWM_USER;
+ hwmtype = "UID";
+ high_hwm = idmap_tdb_state.high_uid;
+ break;
+
+ case ID_TYPE_GID:
+ hwmkey = HWM_GROUP;
+ hwmtype = "GID";
+ high_hwm = idmap_tdb_state.high_gid;
+ break;
+
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if ((hwm = tdb_fetch_int32(idmap_alloc_tdb, hwmkey)) == -1) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ xid->id = hwm;
+
+ /* Warn if it is out of range */
+ if (hwm >= high_hwm) {
+ DEBUG(0, ("Warning: %s range full!! (max: %lu)\n",
+ hwmtype, (unsigned long)high_hwm));
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Set high id.
+**********************************/
+
+static NTSTATUS idmap_tdb_set_hwm(struct unixid *xid)
+{
+ const char *hwmkey;
+ const char *hwmtype;
+ uint32_t hwm;
+ uint32_t high_hwm;
+
+ /* Get current high water mark */
+ switch (xid->type) {
+
+ case ID_TYPE_UID:
+ hwmkey = HWM_USER;
+ hwmtype = "UID";
+ high_hwm = idmap_tdb_state.high_uid;
+ break;
+
+ case ID_TYPE_GID:
+ hwmkey = HWM_GROUP;
+ hwmtype = "GID";
+ high_hwm = idmap_tdb_state.high_gid;
+ break;
+
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ hwm = xid->id;
+
+ if ((hwm = tdb_store_int32(idmap_alloc_tdb, hwmkey, hwm)) == -1) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ /* Warn if it is out of range */
+ if (hwm >= high_hwm) {
+ DEBUG(0, ("Warning: %s range full!! (max: %lu)\n",
+ hwmtype, (unsigned long)high_hwm));
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Close the alloc tdb
+**********************************/
+
+static NTSTATUS idmap_tdb_alloc_close(void)
+{
+ if (idmap_alloc_tdb) {
+ if (idmap_tdb_tdb_close(idmap_alloc_tdb) == 0) {
+ return NT_STATUS_OK;
+ } else {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ IDMAP MAPPING TDB BACKEND
+**********************************************************************/
+
+struct idmap_tdb_context {
+ TDB_CONTEXT *tdb;
+ uint32_t filter_low_id;
+ uint32_t filter_high_id;
+};
+
+/*****************************
+ Initialise idmap database.
+*****************************/
+
+static NTSTATUS idmap_tdb_db_init(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+ struct idmap_tdb_context *ctx;
+ char *config_option = NULL;
+ const char *range;
+
+ ctx = talloc(dom, struct idmap_tdb_context);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ config_option = talloc_asprintf(ctx, "idmap config %s", dom->name);
+ if ( ! config_option) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+
+ ret = idmap_tdb_open_db(ctx, &ctx->tdb);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ goto failed;
+ }
+
+ range = lp_parm_const_string(-1, config_option, "range", NULL);
+ if (( ! range) ||
+ (sscanf(range, "%u - %u", &ctx->filter_low_id, &ctx->filter_high_id) != 2) ||
+ (ctx->filter_low_id > ctx->filter_high_id)) {
+ ctx->filter_low_id = 0;
+ ctx->filter_high_id = 0;
+ }
+
+ dom->private_data = ctx;
+ dom->initialized = True;
+
+ talloc_free(config_option);
+ return NT_STATUS_OK;
+
+failed:
+ talloc_free(ctx);
+ return ret;
+}
+
+/**********************************
+ Single id to sid lookup function.
+**********************************/
+
+static NTSTATUS idmap_tdb_id_to_sid(struct idmap_tdb_context *ctx, struct id_map *map)
+{
+ NTSTATUS ret;
+ TDB_DATA data;
+ char *keystr;
+
+ if (!ctx || !map) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* apply filters before checking */
+ if ((ctx->filter_low_id && (map->xid.id < ctx->filter_low_id)) ||
+ (ctx->filter_high_id && (map->xid.id > ctx->filter_high_id))) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, ctx->filter_low_id, ctx->filter_high_id));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ switch (map->xid.type) {
+
+ case ID_TYPE_UID:
+ keystr = talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id);
+ break;
+
+ case ID_TYPE_GID:
+ keystr = talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id);
+ break;
+
+ default:
+ DEBUG(2, ("INVALID unix ID type: 0x02%x\n", map->xid.type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* final SAFE_FREE safe */
+ data.dptr = NULL;
+
+ if (keystr == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(10,("Fetching record %s\n", keystr));
+
+ /* Check if the mapping exists */
+ data = tdb_fetch_bystring(ctx->tdb, keystr);
+
+ if (!data.dptr) {
+ DEBUG(10,("Record %s not found\n", keystr));
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ if (!string_to_sid(map->sid, (const char *)data.dptr)) {
+ DEBUG(10,("INVALID SID (%s) in record %s\n",
+ (const char *)data.dptr, keystr));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ DEBUG(10,("Found record %s -> %s\n", keystr, (const char *)data.dptr));
+ ret = NT_STATUS_OK;
+
+done:
+ SAFE_FREE(data.dptr);
+ talloc_free(keystr);
+ return ret;
+}
+
+/**********************************
+ Single sid to id lookup function.
+**********************************/
+
+static NTSTATUS idmap_tdb_sid_to_id(struct idmap_tdb_context *ctx, struct id_map *map)
+{
+ NTSTATUS ret;
+ TDB_DATA data;
+ char *keystr;
+ unsigned long rec_id = 0;
+
+ if ((keystr = talloc_asprintf(ctx, "%s", sid_string_static(map->sid))) == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(10,("Fetching record %s\n", keystr));
+
+ /* Check if sid is present in database */
+ data = tdb_fetch_bystring(ctx->tdb, keystr);
+ if (!data.dptr) {
+ DEBUG(10,("Record %s not found\n", keystr));
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ /* What type of record is this ? */
+ if (sscanf((const char *)data.dptr, "UID %lu", &rec_id) == 1) { /* Try a UID record. */
+ map->xid.id = rec_id;
+ map->xid.type = ID_TYPE_UID;
+ DEBUG(10,("Found uid record %s -> %s \n", keystr, (const char *)data.dptr ));
+ ret = NT_STATUS_OK;
+
+ } else if (sscanf((const char *)data.dptr, "GID %lu", &rec_id) == 1) { /* Try a GID record. */
+ map->xid.id = rec_id;
+ map->xid.type = ID_TYPE_GID;
+ DEBUG(10,("Found gid record %s -> %s \n", keystr, (const char *)data.dptr ));
+ ret = NT_STATUS_OK;
+
+ } else { /* Unknown record type ! */
+ DEBUG(2, ("Found INVALID record %s -> %s\n", keystr, (const char *)data.dptr));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ SAFE_FREE(data.dptr);
+
+ /* apply filters before returning result */
+ if ((ctx->filter_low_id && (map->xid.id < ctx->filter_low_id)) ||
+ (ctx->filter_high_id && (map->xid.id > ctx->filter_high_id))) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, ctx->filter_low_id, ctx->filter_high_id));
+ ret = NT_STATUS_NONE_MAPPED;
+ }
+
+done:
+ talloc_free(keystr);
+ return ret;
+}
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+static NTSTATUS idmap_tdb_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids)
+{
+ struct idmap_tdb_context *ctx;
+ NTSTATUS ret;
+ int i;
+
+ /* make sure we initialized */
+ if ( ! dom->initialized) {
+ ret = idmap_tdb_db_init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_tdb_context);
+
+ for (i = 0; ids[i]; i++) {
+ ret = idmap_tdb_id_to_sid(ctx, ids[i]);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+
+ /* if it is just a failed mapping continue */
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) {
+
+ /* make sure it is marked as unmapped */
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+
+ /* some fatal error occurred, return immediately */
+ goto done;
+ }
+
+ /* all ok, id is mapped */
+ ids[i]->status = ID_MAPPED;
+ }
+
+ ret = NT_STATUS_OK;
+
+done:
+ return ret;
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+static NTSTATUS idmap_tdb_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+{
+ struct idmap_tdb_context *ctx;
+ NTSTATUS ret;
+ int i;
+
+ /* make sure we initialized */
+ if ( ! dom->initialized) {
+ ret = idmap_tdb_db_init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_tdb_context);
+
+ for (i = 0; ids[i]; i++) {
+ ret = idmap_tdb_sid_to_id(ctx, ids[i]);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+
+ /* if it is just a failed mapping continue */
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) {
+
+ /* make sure it is marked as unmapped */
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+
+ /* some fatal error occurred, return immediately */
+ goto done;
+ }
+
+ /* all ok, id is mapped */
+ ids[i]->status = ID_MAPPED;
+ }
+
+ ret = NT_STATUS_OK;
+
+done:
+ return ret;
+}
+
+/**********************************
+ set a mapping.
+**********************************/
+
+static NTSTATUS idmap_tdb_set_mapping(struct idmap_domain *dom, const struct id_map *map)
+{
+ struct idmap_tdb_context *ctx;
+ NTSTATUS ret;
+ TDB_DATA ksid, kid, data;
+ char *ksidstr, *kidstr;
+
+ /* make sure we initialized */
+ if ( ! dom->initialized) {
+ ret = idmap_tdb_db_init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ if (!map || !map->sid) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ksidstr = kidstr = NULL;
+ data.dptr = NULL;
+
+ /* TODO: should we filter a set_mapping using low/high filters ? */
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_tdb_context);
+
+ switch (map->xid.type) {
+
+ case ID_TYPE_UID:
+ kidstr = talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id);
+ break;
+
+ case ID_TYPE_GID:
+ kidstr = talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id);
+ break;
+
+ default:
+ DEBUG(2, ("INVALID unix ID type: 0x02%x\n", map->xid.type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (kidstr == NULL) {
+ DEBUG(0, ("ERROR: Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if ((ksidstr = talloc_asprintf(ctx, "%s", sid_string_static(map->sid))) == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(10, ("Storing %s <-> %s map\n", ksidstr, kidstr));
+ kid = string_term_tdb_data(kidstr);
+ ksid = string_term_tdb_data(ksidstr);
+
+ /* *DELETE* previous mappings if any.
+ * This is done both SID and [U|G]ID passed in */
+
+ /* Lock the record for this SID. */
+ if (tdb_chainlock(ctx->tdb, ksid) != 0) {
+ DEBUG(10,("Failed to lock record %s. Error %s\n",
+ ksidstr, tdb_errorstr(ctx->tdb) ));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ data = tdb_fetch(ctx->tdb, ksid);
+ if (data.dptr) {
+ DEBUG(10, ("Deleting existing mapping %s <-> %s\n", (const char *)data.dptr, ksidstr ));
+ tdb_delete(ctx->tdb, data);
+ tdb_delete(ctx->tdb, ksid);
+ SAFE_FREE(data.dptr);
+ }
+
+ data = tdb_fetch(ctx->tdb, kid);
+ if (data.dptr) {
+ DEBUG(10,("Deleting existing mapping %s <-> %s\n", (const char *)data.dptr, kidstr ));
+ tdb_delete(ctx->tdb, data);
+ tdb_delete(ctx->tdb, kid);
+ SAFE_FREE(data.dptr);
+ }
+
+ if (tdb_store(ctx->tdb, ksid, kid, TDB_INSERT) == -1) {
+ DEBUG(0, ("Error storing SID -> ID: %s\n", tdb_errorstr(ctx->tdb)));
+ tdb_chainunlock(ctx->tdb, ksid);
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ if (tdb_store(ctx->tdb, kid, ksid, TDB_INSERT) == -1) {
+ DEBUG(0, ("Error stroing ID -> SID: %s\n", tdb_errorstr(ctx->tdb)));
+ /* try to remove the previous stored SID -> ID map */
+ tdb_delete(ctx->tdb, ksid);
+ tdb_chainunlock(ctx->tdb, ksid);
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ tdb_chainunlock(ctx->tdb, ksid);
+ DEBUG(10,("Stored %s <-> %s\n", ksidstr, kidstr));
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(ksidstr);
+ talloc_free(kidstr);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
+
+/**********************************
+ remove a mapping.
+**********************************/
+
+static NTSTATUS idmap_tdb_remove_mapping(struct idmap_domain *dom, const struct id_map *map)
+{
+ struct idmap_tdb_context *ctx;
+ NTSTATUS ret;
+ TDB_DATA ksid, kid, data;
+ char *ksidstr, *kidstr;
+
+ /* make sure we initialized */
+ if ( ! dom->initialized) {
+ ret = idmap_tdb_db_init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ if (!map || !map->sid) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ksidstr = kidstr = NULL;
+ data.dptr = NULL;
+
+ /* TODO: should we filter a remove_mapping using low/high filters ? */
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_tdb_context);
+
+ switch (map->xid.type) {
+
+ case ID_TYPE_UID:
+ kidstr = talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id);
+ break;
+
+ case ID_TYPE_GID:
+ kidstr = talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id);
+ break;
+
+ default:
+ DEBUG(2, ("INVALID unix ID type: 0x02%x\n", map->xid.type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (kidstr == NULL) {
+ DEBUG(0, ("ERROR: Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if ((ksidstr = talloc_asprintf(ctx, "%s", sid_string_static(map->sid))) == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(10, ("Checking %s <-> %s map\n", ksidstr, kidstr));
+ ksid = string_term_tdb_data(ksidstr);
+ kid = string_term_tdb_data(kidstr);
+
+ /* Lock the record for this SID. */
+ if (tdb_chainlock(ctx->tdb, ksid) != 0) {
+ DEBUG(10,("Failed to lock record %s. Error %s\n",
+ ksidstr, tdb_errorstr(ctx->tdb) ));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* Check if sid is present in database */
+ data = tdb_fetch(ctx->tdb, ksid);
+ if (!data.dptr) {
+ DEBUG(10,("Record %s not found\n", ksidstr));
+ tdb_chainunlock(ctx->tdb, ksid);
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ /* Check if sid is mapped to the specified ID */
+ if ((data.dsize != kid.dsize) ||
+ (memcmp(data.dptr, kid.dptr, data.dsize) != 0)) {
+ DEBUG(10,("Specified SID does not map to specified ID\n"));
+ DEBUGADD(10,("Actual mapping is %s -> %s\n", ksidstr, (const char *)data.dptr));
+ tdb_chainunlock(ctx->tdb, ksid);
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ DEBUG(10, ("Removing %s <-> %s map\n", ksidstr, kidstr));
+
+ /* Delete previous mappings. */
+
+ DEBUG(10, ("Deleting existing mapping %s -> %s\n", ksidstr, kidstr ));
+ tdb_delete(ctx->tdb, ksid);
+
+ DEBUG(10,("Deleting existing mapping %s -> %s\n", kidstr, ksidstr ));
+ tdb_delete(ctx->tdb, kid);
+
+ tdb_chainunlock(ctx->tdb, ksid);
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(ksidstr);
+ talloc_free(kidstr);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
+
+/**********************************
+ Close the idmap tdb instance
+**********************************/
+
+static NTSTATUS idmap_tdb_close(struct idmap_domain *dom)
+{
+ struct idmap_tdb_context *ctx;
+
+ if (dom->private_data) {
+ ctx = talloc_get_type(dom->private_data, struct idmap_tdb_context);
+
+ if (idmap_tdb_tdb_close(ctx->tdb) == 0) {
+ return NT_STATUS_OK;
+ } else {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+struct dump_data {
+ TALLOC_CTX *memctx;
+ struct id_map **maps;
+ int *num_maps;
+ NTSTATUS ret;
+};
+
+static int idmap_tdb_dump_one_entry(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA value, void *pdata)
+{
+ struct dump_data *data = talloc_get_type(pdata, struct dump_data);
+ struct id_map *maps;
+ int num_maps = *data->num_maps;
+
+ /* ignore any record but the ones with a SID as key */
+ if (strncmp((const char *)key.dptr, "S-", 2) == 0) {
+
+ maps = talloc_realloc(NULL, *data->maps, struct id_map, num_maps+1);
+ if ( ! maps) {
+ DEBUG(0, ("Out of memory!\n"));
+ data->ret = NT_STATUS_NO_MEMORY;
+ return -1;
+ }
+ *data->maps = maps;
+ maps[num_maps].sid = talloc(maps, DOM_SID);
+ if ( ! maps[num_maps].sid) {
+ DEBUG(0, ("Out of memory!\n"));
+ data->ret = NT_STATUS_NO_MEMORY;
+ return -1;
+ }
+
+ if (!string_to_sid(maps[num_maps].sid, (const char *)key.dptr)) {
+ DEBUG(10,("INVALID record %s\n", (const char *)key.dptr));
+ /* continue even with errors */
+ return 0;
+ }
+
+ /* Try a UID record. */
+ if (sscanf((const char *)value.dptr, "UID %u", &(maps[num_maps].xid.id)) == 1) {
+ maps[num_maps].xid.type = ID_TYPE_UID;
+ maps[num_maps].status = ID_MAPPED;
+ *data->num_maps = num_maps + 1;
+
+ /* Try a GID record. */
+ } else
+ if (sscanf((const char *)value.dptr, "GID %u", &(maps[num_maps].xid.id)) == 1) {
+ maps[num_maps].xid.type = ID_TYPE_GID;
+ maps[num_maps].status = ID_MAPPED;
+ *data->num_maps = num_maps + 1;
+
+ /* Unknown record type ! */
+ } else {
+ maps[num_maps].status = ID_UNKNOWN;
+ DEBUG(2, ("Found INVALID record %s -> %s\n",
+ (const char *)key.dptr, (const char *)value.dptr));
+ /* do not increment num_maps */
+ }
+ }
+
+ return 0;
+}
+
+/**********************************
+ Dump all mappings out
+**********************************/
+
+static NTSTATUS idmap_tdb_dump_data(struct idmap_domain *dom, struct id_map **maps, int *num_maps)
+{
+ struct idmap_tdb_context *ctx;
+ struct dump_data *data;
+ NTSTATUS ret = NT_STATUS_OK;
+
+ /* make sure we initialized */
+ if ( ! dom->initialized) {
+ ret = idmap_tdb_db_init(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_tdb_context);
+
+ data = TALLOC_ZERO_P(ctx, struct dump_data);
+ if ( ! data) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ data->maps = maps;
+ data->num_maps = num_maps;
+ data->ret = NT_STATUS_OK;
+
+ tdb_traverse(ctx->tdb, idmap_tdb_dump_one_entry, data);
+
+ if ( ! NT_STATUS_IS_OK(data->ret)) {
+ ret = data->ret;
+ }
+
+ talloc_free(data);
+ return ret;
+}
+
+static struct idmap_methods db_methods = {
+
+ .init = idmap_tdb_db_init,
+ .unixids_to_sids = idmap_tdb_unixids_to_sids,
+ .sids_to_unixids = idmap_tdb_sids_to_unixids,
+ .set_mapping = idmap_tdb_set_mapping,
+ .remove_mapping = idmap_tdb_remove_mapping,
+ .dump_data = idmap_tdb_dump_data,
+ .close_fn = idmap_tdb_close
+};
+
+static struct idmap_alloc_methods db_alloc_methods = {
+
+ .init = idmap_tdb_alloc_init,
+ .allocate_id = idmap_tdb_allocate_id,
+ .get_id_hwm = idmap_tdb_get_hwm,
+ .set_id_hwm = idmap_tdb_set_hwm,
+ .close_fn = idmap_tdb_alloc_close
+};
+
+NTSTATUS idmap_alloc_tdb_init(void)
+{
+ return smb_register_idmap_alloc(SMB_IDMAP_INTERFACE_VERSION, "tdb", &db_alloc_methods);
+}
+
+NTSTATUS idmap_tdb_init(void)
+{
+ NTSTATUS ret;
+
+ /* FIXME: bad hack to actually register also the alloc_tdb module without changining configure.in */
+ ret = idmap_alloc_tdb_init();
+ if (! NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "tdb", &db_methods);
+}
diff --git a/source3/winbindd/idmap_util.c b/source3/winbindd/idmap_util.c
new file mode 100644
index 0000000000..077c599739
--- /dev/null
+++ b/source3/winbindd/idmap_util.c
@@ -0,0 +1,171 @@
+/*
+ Unix SMB/CIFS implementation.
+ ID Mapping
+ Copyright (C) Simo Sorce 2003
+ Copyright (C) Jeremy Allison 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.*/
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+/*****************************************************************
+ Returns the SID mapped to the given UID.
+ If mapping is not possible returns an error.
+*****************************************************************/
+
+NTSTATUS idmap_uid_to_sid(DOM_SID *sid, uid_t uid)
+{
+ NTSTATUS ret;
+ struct id_map map;
+ struct id_map *maps[2];
+
+ DEBUG(10,("uid = [%lu]\n", (unsigned long)uid));
+
+ map.sid = sid;
+ map.xid.type = ID_TYPE_UID;
+ map.xid.id = uid;
+
+ maps[0] = &map;
+ maps[1] = NULL;
+
+ ret = idmap_unixids_to_sids(maps);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ DEBUG(10, ("error mapping uid [%lu]\n", (unsigned long)uid));
+ return ret;
+ }
+
+ if (map.status != ID_MAPPED) {
+ DEBUG(10, ("uid [%lu] not mapped\n", (unsigned long)uid));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*****************************************************************
+ Returns SID mapped to the given GID.
+ If mapping is not possible returns an error.
+*****************************************************************/
+
+NTSTATUS idmap_gid_to_sid(DOM_SID *sid, gid_t gid)
+{
+ NTSTATUS ret;
+ struct id_map map;
+ struct id_map *maps[2];
+
+ DEBUG(10,("gid = [%lu]\n", (unsigned long)gid));
+
+ map.sid = sid;
+ map.xid.type = ID_TYPE_GID;
+ map.xid.id = gid;
+
+ maps[0] = &map;
+ maps[1] = NULL;
+
+ ret = idmap_unixids_to_sids(maps);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ DEBUG(10, ("error mapping gid [%lu]\n", (unsigned long)gid));
+ return ret;
+ }
+
+ if (map.status != ID_MAPPED) {
+ DEBUG(10, ("gid [%lu] not mapped\n", (unsigned long)gid));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*****************************************************************
+ Returns the UID mapped to the given SID.
+ If mapping is not possible or SID maps to a GID returns an error.
+*****************************************************************/
+
+NTSTATUS idmap_sid_to_uid(DOM_SID *sid, uid_t *uid)
+{
+ NTSTATUS ret;
+ struct id_map map;
+ struct id_map *maps[2];
+
+ DEBUG(10,("idmap_sid_to_uid: sid = [%s]\n", sid_string_static(sid)));
+
+ map.sid = sid;
+ map.xid.type = ID_TYPE_UID;
+
+ maps[0] = &map;
+ maps[1] = NULL;
+
+ ret = idmap_sids_to_unixids(maps);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ DEBUG(10, ("error mapping sid [%s] to uid\n",
+ sid_string_static(sid)));
+ return ret;
+ }
+
+ if ((map.status != ID_MAPPED) || (map.xid.type != ID_TYPE_UID)) {
+ DEBUG(10, ("sid [%s] not mapped to an uid [%u,%u,%u]\n",
+ sid_string_static(sid),
+ map.status,
+ map.xid.type,
+ map.xid.id));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ *uid = map.xid.id;
+
+ return NT_STATUS_OK;
+}
+
+/*****************************************************************
+ Returns the GID mapped to the given SID.
+ If mapping is not possible or SID maps to a UID returns an error.
+*****************************************************************/
+
+NTSTATUS idmap_sid_to_gid(DOM_SID *sid, gid_t *gid)
+{
+ NTSTATUS ret;
+ struct id_map map;
+ struct id_map *maps[2];
+
+ DEBUG(10,("idmap_sid_to_gid: sid = [%s]\n", sid_string_static(sid)));
+
+ map.sid = sid;
+ map.xid.type = ID_TYPE_GID;
+
+ maps[0] = &map;
+ maps[1] = NULL;
+
+ ret = idmap_sids_to_unixids(maps);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ DEBUG(10, ("error mapping sid [%s] to gid\n",
+ sid_string_static(sid)));
+ return ret;
+ }
+
+ if ((map.status != ID_MAPPED) || (map.xid.type != ID_TYPE_GID)) {
+ DEBUG(10, ("sid [%s] not mapped to a gid [%u,%u,%u]\n",
+ sid_string_static(sid),
+ map.status,
+ map.xid.type,
+ map.xid.id));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ *gid = map.xid.id;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/nss_info.c b/source3/winbindd/nss_info.c
new file mode 100644
index 0000000000..ea51e9dde8
--- /dev/null
+++ b/source3/winbindd/nss_info.c
@@ -0,0 +1,301 @@
+/*
+ Unix SMB/CIFS implementation.
+ Idmap NSS headers
+
+ Copyright (C) Gerald Carter 2006
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "nss_info.h"
+
+static struct nss_function_entry *backends = NULL;
+static struct nss_domain_entry *nss_domain_list = NULL;
+
+/**********************************************************************
+ Get idmap nss methods.
+**********************************************************************/
+
+static struct nss_function_entry *nss_get_backend(const char *name )
+{
+ struct nss_function_entry *entry = backends;
+
+ for(entry = backends; entry; entry = entry->next) {
+ if ( strequal(entry->name, name) )
+ return entry;
+ }
+
+ return NULL;
+}
+
+/*********************************************************************
+ Allow a module to register itself as a backend.
+**********************************************************************/
+
+ NTSTATUS smb_register_idmap_nss(int version, const char *name, struct nss_info_methods *methods)
+{
+ struct nss_function_entry *entry;
+
+ if ((version != SMB_NSS_INFO_INTERFACE_VERSION)) {
+ DEBUG(0, ("smb_register_idmap_nss: Failed to register idmap_nss module.\n"
+ "The module was compiled against SMB_NSS_INFO_INTERFACE_VERSION %d,\n"
+ "current SMB_NSS_INFO_INTERFACE_VERSION is %d.\n"
+ "Please recompile against the current version of samba!\n",
+ version, SMB_NSS_INFO_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !name[0] || !methods) {
+ DEBUG(0,("smb_register_idmap_nss: called with NULL pointer or empty name!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if ( nss_get_backend(name) ) {
+ DEBUG(0,("smb_register_idmap_nss: idmap module %s "
+ "already registered!\n", name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ entry = SMB_XMALLOC_P(struct nss_function_entry);
+ entry->name = smb_xstrdup(name);
+ entry->methods = methods;
+
+ DLIST_ADD(backends, entry);
+ DEBUG(5, ("smb_register_idmap_nss: Successfully added idmap "
+ "nss backend '%s'\n", name));
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ *******************************************************************/
+
+static BOOL parse_nss_parm( const char *config, char **backend, char **domain )
+{
+ char *p;
+ char *q;
+ int len;
+
+ *backend = *domain = NULL;
+
+ if ( !config )
+ return False;
+
+ p = strchr( config, ':' );
+
+ /* if no : then the string must be the backend name only */
+
+ if ( !p ) {
+ *backend = SMB_STRDUP( config );
+ return (*backend != NULL);
+ }
+
+ /* split the string and return the two parts */
+
+ if ( strlen(p+1) > 0 ) {
+ *domain = SMB_STRDUP( p+1 );
+ }
+
+ len = PTR_DIFF(p,config)+1;
+ if ( (q = SMB_MALLOC_ARRAY( char, len )) == NULL ) {
+ SAFE_FREE( *backend );
+ return False;
+ }
+
+ StrnCpy( q, config, len-1);
+ q[len-1] = '\0';
+ *backend = q;
+
+ return True;
+}
+
+/********************************************************************
+ Each nss backend must not store global state, but rather be able
+ to initialize the state on a per domain basis.
+ *******************************************************************/
+
+ NTSTATUS nss_init( const char **nss_list )
+{
+ NTSTATUS status;
+ static NTSTATUS nss_initialized = NT_STATUS_UNSUCCESSFUL;
+ int i;
+ char *backend, *domain;
+ struct nss_function_entry *nss_backend;
+ struct nss_domain_entry *nss_domain;
+
+ /* check for previous successful initializations */
+
+ if ( NT_STATUS_IS_OK(nss_initialized) )
+ return NT_STATUS_OK;
+
+ /* The "template" backend should alqays be registered as it
+ is a static module */
+
+ if ( (nss_backend = nss_get_backend( "template" )) == NULL ) {
+ static_init_nss_info;
+ }
+
+ /* Create the list of nss_domains (loading any shared plugins
+ as necessary) */
+
+ for ( i=0; nss_list && nss_list[i]; i++ ) {
+
+ if ( !parse_nss_parm(nss_list[i], &backend, &domain) ) {
+ DEBUG(0,("nss_init: failed to parse \"%s\"!\n",
+ nss_list[i]));
+ continue;
+ }
+
+ /* validate the backend */
+
+ if ( (nss_backend = nss_get_backend( backend )) == NULL ) {
+ /* attempt to register the backend */
+ status = smb_probe_module( "nss_info", backend );
+ if ( !NT_STATUS_IS_OK(status) ) {
+ continue;
+ }
+
+ /* try again */
+ if ( (nss_backend = nss_get_backend( backend )) == NULL ) {
+ DEBUG(0,("nss_init: unregistered backend %s!. Skipping\n",
+ backend));
+ continue;
+ }
+
+ }
+
+ /* fill in the nss_domain_entry and add it to the
+ list of domains */
+
+ nss_domain = TALLOC_ZERO_P( nss_domain_list, struct nss_domain_entry );
+ if ( !nss_domain ) {
+ DEBUG(0,("nss_init: talloc() failure!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nss_domain->backend = nss_backend;
+ nss_domain->domain = talloc_strdup( nss_domain, domain );
+
+ /* Try to init and ave the result */
+
+ nss_domain->init_status = nss_domain->backend->methods->init( nss_domain );
+ DLIST_ADD( nss_domain_list, nss_domain );
+ if ( !NT_STATUS_IS_OK(nss_domain->init_status) ) {
+ DEBUG(0,("nss_init: Failed to init backend for %s domain!\n",
+ nss_domain->domain));
+ }
+
+ /* cleanup */
+
+ SAFE_FREE( backend );
+ SAFE_FREE( domain );
+ }
+
+ if ( !nss_domain_list ) {
+ DEBUG(3,("nss_init: no nss backends configured. "
+ "Defaulting to \"template\".\n"));
+
+
+ /* we shouild default to use template here */
+ }
+
+
+ nss_initialized = NT_STATUS_OK;
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ *******************************************************************/
+
+static struct nss_domain_entry *find_nss_domain( const char *domain )
+{
+ NTSTATUS status;
+ struct nss_domain_entry *p;
+
+ status = nss_init( lp_winbind_nss_info() );
+ if ( !NT_STATUS_IS_OK(status) ) {
+ DEBUG(4,("nss_get_info: Failed to init nss_info API (%s)!\n",
+ nt_errstr(status)));
+ return NULL;
+ }
+
+ for ( p=nss_domain_list; p; p=p->next ) {
+ if ( strequal( p->domain, domain ) )
+ break;
+ }
+
+ /* If we didn't find a match, then use the default nss info */
+
+ if ( !p ) {
+ if ( !nss_domain_list ) {
+ return NULL;
+ }
+
+ p = nss_domain_list;
+ }
+
+ if ( !NT_STATUS_IS_OK( p->init_status ) ) {
+ p->init_status = p->backend->methods->init( p );
+ }
+
+ return p;
+}
+
+/********************************************************************
+ *******************************************************************/
+
+ NTSTATUS nss_get_info( const char *domain, const DOM_SID *user_sid,
+ TALLOC_CTX *ctx,
+ ADS_STRUCT *ads, LDAPMessage *msg,
+ char **homedir, char **shell, char **gecos,
+ gid_t *p_gid)
+{
+ struct nss_domain_entry *p;
+ struct nss_info_methods *m;
+
+ if ( (p = find_nss_domain( domain )) == NULL ) {
+ DEBUG(4,("nss_get_info: Failed to find nss domain pointer for %s\n",
+ domain ));
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ m = p->backend->methods;
+
+ return m->get_nss_info( p, user_sid, ctx, ads, msg,
+ homedir, shell, gecos, p_gid );
+}
+
+/********************************************************************
+ *******************************************************************/
+
+ NTSTATUS nss_close( const char *parameters )
+{
+ struct nss_domain_entry *p = nss_domain_list;
+ struct nss_domain_entry *q;
+
+ while ( p && p->backend && p->backend->methods ) {
+ /* close the backend */
+ p->backend->methods->close_fn();
+
+ /* free the memory */
+ q = p;
+ p = p->next;
+ TALLOC_FREE( q );
+ }
+
+ return NT_STATUS_OK;
+}
+
diff --git a/source3/winbindd/nss_info_template.c b/source3/winbindd/nss_info_template.c
new file mode 100644
index 0000000000..aaf02e4abe
--- /dev/null
+++ b/source3/winbindd/nss_info_template.c
@@ -0,0 +1,83 @@
+/*
+ Unix SMB/CIFS implementation.
+ idMap nss template plugin
+
+ Copyright (C) Gerald Carter 2006
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "nss_info.h"
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS nss_template_init( struct nss_domain_entry *e )
+{
+ return NT_STATUS_OK;
+}
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS nss_template_get_info( struct nss_domain_entry *e,
+ const DOM_SID *sid,
+ TALLOC_CTX *ctx,
+ ADS_STRUCT *ads,
+ LDAPMessage *msg,
+ char **homedir,
+ char **shell,
+ char **gecos,
+ gid_t *gid )
+{
+ if ( !homedir || !shell || !gecos )
+ return NT_STATUS_INVALID_PARAMETER;
+
+ *homedir = talloc_strdup( ctx, lp_template_homedir() );
+ *shell = talloc_strdup( ctx, lp_template_shell() );
+ *gecos = NULL;
+
+ if ( !*homedir || !*shell ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS nss_template_close( void )
+{
+ return NT_STATUS_OK;
+}
+
+
+/************************************************************************
+ ***********************************************************************/
+
+static struct nss_info_methods nss_template_methods = {
+ .init = nss_template_init,
+ .get_nss_info = nss_template_get_info,
+ .close_fn = nss_template_close
+};
+
+NTSTATUS nss_info_template_init( void )
+{
+ return smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
+ "template",
+ &nss_template_methods);
+}
+
diff --git a/source3/winbindd/winbindd.c b/source3/winbindd/winbindd.c
new file mode 100644
index 0000000000..17915fb01b
--- /dev/null
+++ b/source3/winbindd/winbindd.c
@@ -0,0 +1,1254 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon for ntdom nss module
+
+ Copyright (C) by Tim Potter 2000-2002
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Jelmer Vernooij 2003
+ Copyright (C) Volker Lendecke 2004
+ Copyright (C) James Peach 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 "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+BOOL opt_nocache = False;
+
+extern BOOL override_logfile;
+
+struct event_context *winbind_event_context(void)
+{
+ static struct event_context *ctx;
+
+ if (!ctx && !(ctx = event_context_init(NULL))) {
+ smb_panic("Could not init winbind event context");
+ }
+ return ctx;
+}
+
+struct messaging_context *winbind_messaging_context(void)
+{
+ static struct messaging_context *ctx;
+
+ if (!ctx && !(ctx = messaging_init(NULL, server_id_self(),
+ winbind_event_context()))) {
+ smb_panic("Could not init winbind messaging context");
+ }
+ return ctx;
+}
+
+/* Reload configuration */
+
+static BOOL reload_services_file(void)
+{
+ BOOL ret;
+
+ if (lp_loaded()) {
+ pstring fname;
+
+ pstrcpy(fname,lp_configfile());
+ if (file_exist(fname,NULL) && !strcsequal(fname,dyn_CONFIGFILE)) {
+ pstrcpy(dyn_CONFIGFILE,fname);
+ }
+ }
+
+ reopen_logs();
+ ret = lp_load(dyn_CONFIGFILE,False,False,True,True);
+
+ reopen_logs();
+ load_interfaces();
+
+ return(ret);
+}
+
+
+/**************************************************************************** **
+ Handle a fault..
+ **************************************************************************** */
+
+static void fault_quit(void)
+{
+ dump_core();
+}
+
+static void winbindd_status(void)
+{
+ struct winbindd_cli_state *tmp;
+
+ DEBUG(0, ("winbindd status:\n"));
+
+ /* Print client state information */
+
+ DEBUG(0, ("\t%d clients currently active\n", winbindd_num_clients()));
+
+ if (DEBUGLEVEL >= 2 && winbindd_num_clients()) {
+ DEBUG(2, ("\tclient list:\n"));
+ for(tmp = winbindd_client_list(); tmp; tmp = tmp->next) {
+ DEBUGADD(2, ("\t\tpid %lu, sock %d\n",
+ (unsigned long)tmp->pid, tmp->sock));
+ }
+ }
+}
+
+/* Print winbindd status to log file */
+
+static void print_winbindd_status(void)
+{
+ winbindd_status();
+}
+
+/* Flush client cache */
+
+static void 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. */
+
+ wcache_invalidate_cache();
+}
+
+/* Handle the signal by unlinking socket and exiting */
+
+static void terminate(void)
+{
+
+ winbindd_release_sockets();
+ idmap_close();
+
+ trustdom_cache_shutdown();
+
+#if 0
+ if (interactive) {
+ TALLOC_CTX *mem_ctx = talloc_init("end_description");
+ char *description = talloc_describe_all(mem_ctx);
+
+ DEBUG(3, ("tallocs left:\n%s\n", description));
+ talloc_destroy(mem_ctx);
+ }
+#endif
+
+ exit(0);
+}
+
+static BOOL do_sigterm;
+
+static void termination_handler(int signum)
+{
+ do_sigterm = True;
+ sys_select_signal(signum);
+}
+
+static BOOL do_sigusr2;
+
+static void sigusr2_handler(int signum)
+{
+ do_sigusr2 = True;
+ sys_select_signal(SIGUSR2);
+}
+
+static BOOL do_sighup;
+
+static void sighup_handler(int signum)
+{
+ do_sighup = True;
+ sys_select_signal(SIGHUP);
+}
+
+static BOOL do_sigchld;
+
+static void sigchld_handler(int signum)
+{
+ do_sigchld = True;
+ sys_select_signal(SIGCHLD);
+}
+
+/* React on 'smbcontrol winbindd reload-config' in the same way as on SIGHUP*/
+static void msg_reload_services(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ /* Flush various caches */
+ flush_caches();
+ reload_services_file();
+}
+
+/* React on 'smbcontrol winbindd shutdown' in the same way as on SIGTERM*/
+static void msg_shutdown(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ do_sigterm = True;
+}
+
+
+static void winbind_msg_validate_cache(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ uint8 ret;
+ pid_t child_pid;
+ struct sigaction act;
+ struct sigaction oldact;
+
+ DEBUG(10, ("winbindd_msg_validate_cache: got validate-cache "
+ "message.\n"));
+
+ /*
+ * call the validation code from a child:
+ * so we don't block the main winbindd and the validation
+ * code can safely use fork/waitpid...
+ */
+ CatchChild();
+ child_pid = sys_fork();
+
+ if (child_pid == -1) {
+ DEBUG(1, ("winbind_msg_validate_cache: Could not fork: %s\n",
+ strerror(errno)));
+ return;
+ }
+
+ if (child_pid != 0) {
+ /* parent */
+ DEBUG(5, ("winbind_msg_validate_cache: child created with "
+ "pid %d.\n", child_pid));
+ return;
+ }
+
+ /* child */
+
+ /* install default SIGCHLD handler: validation code uses fork/waitpid */
+ ZERO_STRUCT(act);
+ act.sa_handler = SIG_DFL;
+#ifdef SA_RESTART
+ /* We *want* SIGALRM to interrupt a system call. */
+ act.sa_flags = SA_RESTART;
+#endif
+ sigemptyset(&act.sa_mask);
+ sigaddset(&act.sa_mask,SIGCHLD);
+ sigaction(SIGCHLD,&act,&oldact);
+
+ ret = (uint8)winbindd_validate_cache_nobackup();
+ DEBUG(10, ("winbindd_msg_validata_cache: got return value %d\n", ret));
+ messaging_send_buf(msg_ctx, server_id, MSG_WINBIND_VALIDATE_CACHE, &ret,
+ (size_t)1);
+ _exit(0);
+}
+
+static struct winbindd_dispatch_table {
+ enum winbindd_cmd cmd;
+ void (*fn)(struct winbindd_cli_state *state);
+ const char *winbindd_cmd_name;
+} dispatch_table[] = {
+
+ /* User functions */
+
+ { WINBINDD_GETPWNAM, winbindd_getpwnam, "GETPWNAM" },
+ { WINBINDD_GETPWUID, winbindd_getpwuid, "GETPWUID" },
+
+ { WINBINDD_SETPWENT, winbindd_setpwent, "SETPWENT" },
+ { WINBINDD_ENDPWENT, winbindd_endpwent, "ENDPWENT" },
+ { WINBINDD_GETPWENT, winbindd_getpwent, "GETPWENT" },
+
+ { WINBINDD_GETGROUPS, winbindd_getgroups, "GETGROUPS" },
+ { WINBINDD_GETUSERSIDS, winbindd_getusersids, "GETUSERSIDS" },
+ { WINBINDD_GETUSERDOMGROUPS, winbindd_getuserdomgroups,
+ "GETUSERDOMGROUPS" },
+
+ /* Group functions */
+
+ { WINBINDD_GETGRNAM, winbindd_getgrnam, "GETGRNAM" },
+ { WINBINDD_GETGRGID, winbindd_getgrgid, "GETGRGID" },
+ { WINBINDD_SETGRENT, winbindd_setgrent, "SETGRENT" },
+ { WINBINDD_ENDGRENT, winbindd_endgrent, "ENDGRENT" },
+ { WINBINDD_GETGRENT, winbindd_getgrent, "GETGRENT" },
+ { WINBINDD_GETGRLST, winbindd_getgrent, "GETGRLST" },
+
+ /* PAM auth functions */
+
+ { WINBINDD_PAM_AUTH, winbindd_pam_auth, "PAM_AUTH" },
+ { WINBINDD_PAM_AUTH_CRAP, winbindd_pam_auth_crap, "AUTH_CRAP" },
+ { WINBINDD_PAM_CHAUTHTOK, winbindd_pam_chauthtok, "CHAUTHTOK" },
+ { WINBINDD_PAM_LOGOFF, winbindd_pam_logoff, "PAM_LOGOFF" },
+ { WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP, winbindd_pam_chng_pswd_auth_crap, "CHNG_PSWD_AUTH_CRAP" },
+
+ /* Enumeration functions */
+
+ { WINBINDD_LIST_USERS, winbindd_list_users, "LIST_USERS" },
+ { WINBINDD_LIST_GROUPS, winbindd_list_groups, "LIST_GROUPS" },
+ { WINBINDD_LIST_TRUSTDOM, winbindd_list_trusted_domains,
+ "LIST_TRUSTDOM" },
+ { WINBINDD_SHOW_SEQUENCE, winbindd_show_sequence, "SHOW_SEQUENCE" },
+
+ /* SID related functions */
+
+ { WINBINDD_LOOKUPSID, winbindd_lookupsid, "LOOKUPSID" },
+ { WINBINDD_LOOKUPNAME, winbindd_lookupname, "LOOKUPNAME" },
+ { WINBINDD_LOOKUPRIDS, winbindd_lookuprids, "LOOKUPRIDS" },
+
+ /* Lookup related functions */
+
+ { WINBINDD_SID_TO_UID, winbindd_sid_to_uid, "SID_TO_UID" },
+ { WINBINDD_SID_TO_GID, winbindd_sid_to_gid, "SID_TO_GID" },
+ { WINBINDD_UID_TO_SID, winbindd_uid_to_sid, "UID_TO_SID" },
+ { WINBINDD_GID_TO_SID, winbindd_gid_to_sid, "GID_TO_SID" },
+#if 0 /* DISABLED until we fix the interface in Samba 3.0.26 --jerry */
+ { WINBINDD_SIDS_TO_XIDS, winbindd_sids_to_unixids, "SIDS_TO_XIDS" },
+#endif /* end DISABLED */
+ { WINBINDD_ALLOCATE_UID, winbindd_allocate_uid, "ALLOCATE_UID" },
+ { WINBINDD_ALLOCATE_GID, winbindd_allocate_gid, "ALLOCATE_GID" },
+ { WINBINDD_SET_MAPPING, winbindd_set_mapping, "SET_MAPPING" },
+ { WINBINDD_SET_HWM, winbindd_set_hwm, "SET_HWMS" },
+
+ /* Miscellaneous */
+
+ { WINBINDD_DUMP_MAPS, winbindd_dump_maps, "DUMP_MAPS" },
+
+ { WINBINDD_CHECK_MACHACC, winbindd_check_machine_acct, "CHECK_MACHACC" },
+ { WINBINDD_PING, winbindd_ping, "PING" },
+ { WINBINDD_INFO, winbindd_info, "INFO" },
+ { WINBINDD_INTERFACE_VERSION, winbindd_interface_version,
+ "INTERFACE_VERSION" },
+ { WINBINDD_DOMAIN_NAME, winbindd_domain_name, "DOMAIN_NAME" },
+ { WINBINDD_DOMAIN_INFO, winbindd_domain_info, "DOMAIN_INFO" },
+ { WINBINDD_NETBIOS_NAME, winbindd_netbios_name, "NETBIOS_NAME" },
+ { WINBINDD_PRIV_PIPE_DIR, winbindd_priv_pipe_dir,
+ "WINBINDD_PRIV_PIPE_DIR" },
+ { WINBINDD_GETDCNAME, winbindd_getdcname, "GETDCNAME" },
+ { WINBINDD_DSGETDCNAME, winbindd_dsgetdcname, "DSGETDCNAME" },
+
+ /* Credential cache access */
+ { WINBINDD_CCACHE_NTLMAUTH, winbindd_ccache_ntlm_auth, "NTLMAUTH" },
+
+ /* WINS functions */
+
+ { WINBINDD_WINS_BYNAME, winbindd_wins_byname, "WINS_BYNAME" },
+ { WINBINDD_WINS_BYIP, winbindd_wins_byip, "WINS_BYIP" },
+
+ /* End of list */
+
+ { WINBINDD_NUM_CMDS, NULL, "NONE" }
+};
+
+static void process_request(struct winbindd_cli_state *state)
+{
+ struct winbindd_dispatch_table *table = dispatch_table;
+
+ /* Free response data - we may be interrupted and receive another
+ command before being able to send this data off. */
+
+ SAFE_FREE(state->response.extra_data.data);
+
+ ZERO_STRUCT(state->response);
+
+ state->response.result = WINBINDD_PENDING;
+ state->response.length = sizeof(struct winbindd_response);
+
+ state->mem_ctx = talloc_init("winbind request");
+ if (state->mem_ctx == NULL)
+ return;
+
+ /* Remember who asked us. */
+ state->pid = state->request.pid;
+
+ /* Process command */
+
+ for (table = dispatch_table; table->fn; table++) {
+ if (state->request.cmd == table->cmd) {
+ DEBUG(10,("process_request: request fn %s\n",
+ table->winbindd_cmd_name ));
+ table->fn(state);
+ break;
+ }
+ }
+
+ if (!table->fn) {
+ DEBUG(10,("process_request: unknown request fn number %d\n",
+ (int)state->request.cmd ));
+ request_error(state);
+ }
+}
+
+/*
+ * A list of file descriptors being monitored by select in the main processing
+ * loop. fd_event->handler is called whenever the socket is readable/writable.
+ */
+
+static struct fd_event *fd_events = NULL;
+
+void add_fd_event(struct fd_event *ev)
+{
+ struct fd_event *match;
+
+ /* only add unique fd_event structs */
+
+ for (match=fd_events; match; match=match->next ) {
+#ifdef DEVELOPER
+ SMB_ASSERT( match != ev );
+#else
+ if ( match == ev )
+ return;
+#endif
+ }
+
+ DLIST_ADD(fd_events, ev);
+}
+
+void remove_fd_event(struct fd_event *ev)
+{
+ DLIST_REMOVE(fd_events, ev);
+}
+
+/*
+ * Handler for fd_events to complete a read/write request, set up by
+ * setup_async_read/setup_async_write.
+ */
+
+static void rw_callback(struct fd_event *event, int flags)
+{
+ size_t todo;
+ ssize_t done = 0;
+
+ todo = event->length - event->done;
+
+ if (event->flags & EVENT_FD_WRITE) {
+ SMB_ASSERT(flags == EVENT_FD_WRITE);
+ done = sys_write(event->fd,
+ &((char *)event->data)[event->done],
+ todo);
+
+ if (done <= 0) {
+ event->flags = 0;
+ event->finished(event->private_data, False);
+ return;
+ }
+ }
+
+ if (event->flags & EVENT_FD_READ) {
+ SMB_ASSERT(flags == EVENT_FD_READ);
+ done = sys_read(event->fd, &((char *)event->data)[event->done],
+ todo);
+
+ if (done <= 0) {
+ event->flags = 0;
+ event->finished(event->private_data, False);
+ return;
+ }
+ }
+
+ event->done += done;
+
+ if (event->done == event->length) {
+ event->flags = 0;
+ event->finished(event->private_data, True);
+ }
+}
+
+/*
+ * Request an async read/write on a fd_event structure. (*finished) is called
+ * when the request is completed or an error had occurred.
+ */
+
+void setup_async_read(struct fd_event *event, void *data, size_t length,
+ void (*finished)(void *private_data, BOOL success),
+ void *private_data)
+{
+ SMB_ASSERT(event->flags == 0);
+ event->data = data;
+ event->length = length;
+ event->done = 0;
+ event->handler = rw_callback;
+ event->finished = finished;
+ event->private_data = private_data;
+ event->flags = EVENT_FD_READ;
+}
+
+void setup_async_write(struct fd_event *event, void *data, size_t length,
+ void (*finished)(void *private_data, BOOL success),
+ void *private_data)
+{
+ SMB_ASSERT(event->flags == 0);
+ event->data = data;
+ event->length = length;
+ event->done = 0;
+ event->handler = rw_callback;
+ event->finished = finished;
+ event->private_data = private_data;
+ event->flags = EVENT_FD_WRITE;
+}
+
+/*
+ * This is the main event loop of winbind requests. It goes through a
+ * state-machine of 3 read/write requests, 4 if you have extra data to send.
+ *
+ * An idle winbind client has a read request of 4 bytes outstanding,
+ * finalizing function is request_len_recv, checking the length. request_recv
+ * then processes the packet. The processing function then at some point has
+ * to call request_finished which schedules sending the response.
+ */
+
+static void request_len_recv(void *private_data, BOOL success);
+static void request_recv(void *private_data, BOOL success);
+static void request_main_recv(void *private_data, BOOL success);
+static void request_finished(struct winbindd_cli_state *state);
+void request_finished_cont(void *private_data, BOOL success);
+static void response_main_sent(void *private_data, BOOL success);
+static void response_extra_sent(void *private_data, BOOL success);
+
+static void response_extra_sent(void *private_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (state->mem_ctx != NULL) {
+ talloc_destroy(state->mem_ctx);
+ state->mem_ctx = NULL;
+ }
+
+ if (!success) {
+ state->finished = True;
+ return;
+ }
+
+ SAFE_FREE(state->request.extra_data.data);
+ SAFE_FREE(state->response.extra_data.data);
+
+ setup_async_read(&state->fd_event, &state->request, sizeof(uint32),
+ request_len_recv, state);
+}
+
+static void response_main_sent(void *private_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ state->finished = True;
+ return;
+ }
+
+ if (state->response.length == sizeof(state->response)) {
+ if (state->mem_ctx != NULL) {
+ talloc_destroy(state->mem_ctx);
+ state->mem_ctx = NULL;
+ }
+
+ setup_async_read(&state->fd_event, &state->request,
+ sizeof(uint32), request_len_recv, state);
+ return;
+ }
+
+ setup_async_write(&state->fd_event, state->response.extra_data.data,
+ state->response.length - sizeof(state->response),
+ response_extra_sent, state);
+}
+
+static void request_finished(struct winbindd_cli_state *state)
+{
+ setup_async_write(&state->fd_event, &state->response,
+ sizeof(state->response), response_main_sent, state);
+}
+
+void request_error(struct winbindd_cli_state *state)
+{
+ SMB_ASSERT(state->response.result == WINBINDD_PENDING);
+ state->response.result = WINBINDD_ERROR;
+ request_finished(state);
+}
+
+void request_ok(struct winbindd_cli_state *state)
+{
+ SMB_ASSERT(state->response.result == WINBINDD_PENDING);
+ state->response.result = WINBINDD_OK;
+ request_finished(state);
+}
+
+void request_finished_cont(void *private_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (success)
+ request_ok(state);
+ else
+ request_error(state);
+}
+
+static void request_len_recv(void *private_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ state->finished = True;
+ return;
+ }
+
+ if (*(uint32 *)(&state->request) != sizeof(state->request)) {
+ DEBUG(0,("request_len_recv: Invalid request size received: %d\n",
+ *(uint32 *)(&state->request)));
+ state->finished = True;
+ return;
+ }
+
+ setup_async_read(&state->fd_event, (uint32 *)(&state->request)+1,
+ sizeof(state->request) - sizeof(uint32),
+ request_main_recv, state);
+}
+
+static void request_main_recv(void *private_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ state->finished = True;
+ return;
+ }
+
+ if (state->request.extra_len == 0) {
+ state->request.extra_data.data = NULL;
+ request_recv(state, True);
+ return;
+ }
+
+ if ((!state->privileged) &&
+ (state->request.extra_len > WINBINDD_MAX_EXTRA_DATA)) {
+ DEBUG(3, ("Got request with %d bytes extra data on "
+ "unprivileged socket\n", (int)state->request.extra_len));
+ state->request.extra_data.data = NULL;
+ state->finished = True;
+ return;
+ }
+
+ state->request.extra_data.data =
+ SMB_MALLOC_ARRAY(char, state->request.extra_len + 1);
+
+ if (state->request.extra_data.data == NULL) {
+ DEBUG(0, ("malloc failed\n"));
+ state->finished = True;
+ return;
+ }
+
+ /* Ensure null termination */
+ state->request.extra_data.data[state->request.extra_len] = '\0';
+
+ setup_async_read(&state->fd_event, state->request.extra_data.data,
+ state->request.extra_len, request_recv, state);
+}
+
+static void request_recv(void *private_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ state->finished = True;
+ return;
+ }
+
+ process_request(state);
+}
+
+/* Process a new connection by adding it to the client connection list */
+
+static void new_connection(int listen_sock, BOOL privileged)
+{
+ struct sockaddr_un sunaddr;
+ struct winbindd_cli_state *state;
+ socklen_t len;
+ int sock;
+
+ /* Accept connection */
+
+ len = sizeof(sunaddr);
+
+ do {
+ sock = accept(listen_sock, (struct sockaddr *)&sunaddr, &len);
+ } while (sock == -1 && errno == EINTR);
+
+ if (sock == -1)
+ return;
+
+ DEBUG(6,("accepted socket %d\n", sock));
+
+ /* Create new connection structure */
+
+ if ((state = TALLOC_ZERO_P(NULL, struct winbindd_cli_state)) == NULL) {
+ close(sock);
+ return;
+ }
+
+ state->sock = sock;
+
+ state->last_access = time(NULL);
+
+ state->privileged = privileged;
+
+ state->fd_event.fd = state->sock;
+ state->fd_event.flags = 0;
+ add_fd_event(&state->fd_event);
+
+ setup_async_read(&state->fd_event, &state->request, sizeof(uint32),
+ request_len_recv, state);
+
+ /* Add to connection list */
+
+ winbindd_add_client(state);
+}
+
+/* Remove a client connection from client connection list */
+
+static void remove_client(struct winbindd_cli_state *state)
+{
+ /* It's a dead client - hold a funeral */
+
+ if (state == NULL) {
+ return;
+ }
+
+ /* Close socket */
+
+ close(state->sock);
+
+ /* Free any getent state */
+
+ free_getent_state(state->getpwent_state);
+ free_getent_state(state->getgrent_state);
+
+ /* We may have some extra data that was not freed if the client was
+ killed unexpectedly */
+
+ SAFE_FREE(state->response.extra_data.data);
+
+ if (state->mem_ctx != NULL) {
+ talloc_destroy(state->mem_ctx);
+ state->mem_ctx = NULL;
+ }
+
+ remove_fd_event(&state->fd_event);
+
+ /* Remove from list and free */
+
+ winbindd_remove_client(state);
+ TALLOC_FREE(state);
+}
+
+/* Shutdown client connection which has been idle for the longest time */
+
+static BOOL remove_idle_client(void)
+{
+ struct winbindd_cli_state *state, *remove_state = NULL;
+ time_t last_access = 0;
+ int nidle = 0;
+
+ for (state = winbindd_client_list(); state; state = state->next) {
+ if (state->response.result != WINBINDD_PENDING &&
+ !state->getpwent_state && !state->getgrent_state) {
+ nidle++;
+ if (!last_access || state->last_access < last_access) {
+ last_access = state->last_access;
+ remove_state = state;
+ }
+ }
+ }
+
+ if (remove_state) {
+ DEBUG(5,("Found %d idle client connections, shutting down sock %d, pid %u\n",
+ nidle, remove_state->sock, (unsigned int)remove_state->pid));
+ remove_client(remove_state);
+ return True;
+ }
+
+ return False;
+}
+
+/* Process incoming clients on listen_sock. We use a tricky non-blocking,
+ non-forking, non-threaded model which allows us to handle many
+ simultaneous connections while remaining impervious to many denial of
+ service attacks. */
+
+static int process_loop(int listen_sock, int listen_priv_sock)
+{
+ struct winbindd_cli_state *state;
+ struct fd_event *ev;
+ fd_set r_fds, w_fds;
+ int maxfd, selret;
+ struct timeval timeout, ev_timeout;
+
+ /* We'll be doing this a lot */
+
+ /* Handle messages */
+
+ message_dispatch(winbind_messaging_context());
+
+ run_events(winbind_event_context(), 0, NULL, NULL);
+
+ /* refresh the trusted domain cache */
+
+ rescan_trusted_domains();
+
+ /* Initialise fd lists for select() */
+
+ maxfd = MAX(listen_sock, listen_priv_sock);
+
+ FD_ZERO(&r_fds);
+ FD_ZERO(&w_fds);
+ FD_SET(listen_sock, &r_fds);
+ FD_SET(listen_priv_sock, &r_fds);
+
+ timeout.tv_sec = WINBINDD_ESTABLISH_LOOP;
+ timeout.tv_usec = 0;
+
+ /* Check for any event timeouts. */
+ if (get_timed_events_timeout(winbind_event_context(), &ev_timeout)) {
+ timeout = timeval_min(&timeout, &ev_timeout);
+ }
+
+ /* Set up client readers and writers */
+
+ state = winbindd_client_list();
+
+ while (state) {
+
+ struct winbindd_cli_state *next = state->next;
+
+ /* Dispose of client connection if it is marked as
+ finished */
+
+ if (state->finished)
+ remove_client(state);
+
+ state = next;
+ }
+
+ for (ev = fd_events; ev; ev = ev->next) {
+ if (ev->flags & EVENT_FD_READ) {
+ FD_SET(ev->fd, &r_fds);
+ maxfd = MAX(ev->fd, maxfd);
+ }
+ if (ev->flags & EVENT_FD_WRITE) {
+ FD_SET(ev->fd, &w_fds);
+ maxfd = MAX(ev->fd, maxfd);
+ }
+ }
+
+ /* Call select */
+
+ selret = sys_select(maxfd + 1, &r_fds, &w_fds, NULL, &timeout);
+
+ if (selret == 0) {
+ goto no_fds_ready;
+ }
+
+ if (selret == -1) {
+ if (errno == EINTR) {
+ goto no_fds_ready;
+ }
+
+ /* Select error, something is badly wrong */
+
+ perror("select");
+ exit(1);
+ }
+
+ /* selret > 0 */
+
+ ev = fd_events;
+ while (ev != NULL) {
+ struct fd_event *next = ev->next;
+ int flags = 0;
+ if (FD_ISSET(ev->fd, &r_fds))
+ flags |= EVENT_FD_READ;
+ if (FD_ISSET(ev->fd, &w_fds))
+ flags |= EVENT_FD_WRITE;
+ if (flags)
+ ev->handler(ev, flags);
+ ev = next;
+ }
+
+ if (FD_ISSET(listen_sock, &r_fds)) {
+ while (winbindd_num_clients() >
+ WINBINDD_MAX_SIMULTANEOUS_CLIENTS - 1) {
+ DEBUG(5,("winbindd: Exceeding %d client "
+ "connections, removing idle "
+ "connection.\n",
+ WINBINDD_MAX_SIMULTANEOUS_CLIENTS));
+ if (!remove_idle_client()) {
+ DEBUG(0,("winbindd: Exceeding %d "
+ "client connections, no idle "
+ "connection found\n",
+ WINBINDD_MAX_SIMULTANEOUS_CLIENTS));
+ break;
+ }
+ }
+ /* new, non-privileged connection */
+ new_connection(listen_sock, False);
+ }
+
+ if (FD_ISSET(listen_priv_sock, &r_fds)) {
+ while (winbindd_num_clients() >
+ WINBINDD_MAX_SIMULTANEOUS_CLIENTS - 1) {
+ DEBUG(5,("winbindd: Exceeding %d client "
+ "connections, removing idle "
+ "connection.\n",
+ WINBINDD_MAX_SIMULTANEOUS_CLIENTS));
+ if (!remove_idle_client()) {
+ DEBUG(0,("winbindd: Exceeding %d "
+ "client connections, no idle "
+ "connection found\n",
+ WINBINDD_MAX_SIMULTANEOUS_CLIENTS));
+ break;
+ }
+ }
+ /* new, privileged connection */
+ new_connection(listen_priv_sock, True);
+ }
+
+ no_fds_ready:
+
+#if 0
+ winbindd_check_cache_size(time(NULL));
+#endif
+
+ /* Check signal handling things */
+
+ if (do_sigterm)
+ terminate();
+
+ if (do_sighup) {
+
+ DEBUG(3, ("got SIGHUP\n"));
+
+ flush_caches();
+ reload_services_file();
+
+ do_sighup = False;
+ }
+
+ if (do_sigusr2) {
+ print_winbindd_status();
+ do_sigusr2 = False;
+ }
+
+ if (do_sigchld) {
+ pid_t pid;
+
+ do_sigchld = False;
+
+ while ((pid = sys_waitpid(-1, NULL, WNOHANG)) > 0) {
+ winbind_child_died(pid);
+ }
+ }
+
+
+ return winbindd_num_clients();
+}
+
+static void winbindd_process_loop(enum smb_server_mode server_mode)
+{
+ int idle_timeout_sec;
+ struct timeval starttime;
+ int listen_public, listen_priv;
+
+ errno = 0;
+ if (!winbindd_init_sockets(&listen_public, &listen_priv,
+ &idle_timeout_sec)) {
+ terminate();
+ }
+
+ starttime = timeval_current();
+
+ if (listen_public == -1 || listen_priv == -1) {
+ DEBUG(0, ("failed to open winbindd pipes: %s\n",
+ errno ? strerror(errno) : "unknown error"));
+ terminate();
+ }
+
+ for (;;) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ int clients = process_loop(listen_public, listen_priv);
+
+ /* Don't bother figuring out the idle time if we won't be
+ * timing out anyway.
+ */
+ if (idle_timeout_sec < 0) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ if (clients == 0 && server_mode == SERVER_MODE_FOREGROUND) {
+ struct timeval now;
+
+ now = timeval_current();
+ if (timeval_elapsed2(&starttime, &now) >
+ (double)idle_timeout_sec) {
+ DEBUG(0, ("idle for %d secs, exitting\n",
+ idle_timeout_sec));
+ terminate();
+ }
+ } else {
+ starttime = timeval_current();
+ }
+ TALLOC_FREE(frame);
+ }
+}
+
+/* Main function */
+
+int main(int argc, char **argv, char **envp)
+{
+ pstring logfile;
+ BOOL log_stdout = False;
+ BOOL no_process_group = False;
+
+ enum smb_server_mode server_mode = SERVER_MODE_DAEMON;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "stdout", 'S', POPT_ARG_VAL, &log_stdout, True, "Log to stdout" },
+ { "foreground", 'F', POPT_ARG_VAL, &server_mode, SERVER_MODE_FOREGROUND, "Daemon in foreground mode" },
+ { "no-process-group", 0, POPT_ARG_VAL, &no_process_group, True, "Don't create a new process group" },
+ { "daemon", 'D', POPT_ARG_VAL, &server_mode, SERVER_MODE_DAEMON, "Become a daemon (default)" },
+ { "interactive", 'i', POPT_ARG_VAL, &server_mode, SERVER_MODE_INTERACTIVE, "Interactive mode" },
+ { "no-caching", 'n', POPT_ARG_VAL, &opt_nocache, True, "Disable caching" },
+ POPT_COMMON_SAMBA
+ POPT_TABLEEND
+ };
+ poptContext pc;
+ int opt;
+
+ /* glibc (?) likes to print "User defined signal 1" and exit if a
+ SIGUSR[12] is received before a handler is installed */
+
+ CatchSignal(SIGUSR1, SIG_IGN);
+ CatchSignal(SIGUSR2, SIG_IGN);
+
+ fault_setup((void (*)(void *))fault_quit );
+ dump_core_setup("winbindd");
+
+ load_case_tables();
+
+ /* Initialise for running in non-root mode */
+
+ sec_init();
+
+ set_remote_machine_name("winbindd", False);
+
+ /* Set environment variable so we don't recursively call ourselves.
+ This may also be useful interactively. */
+
+ if ( !winbind_off() ) {
+ DEBUG(0,("Failed to disable recusive winbindd calls. Exiting.\n"));
+ exit(1);
+ }
+
+ /* Initialise samba/rpc client stuff */
+
+ pc = poptGetContext("winbindd", argc, (const char **)argv, long_options, 0);
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ default:
+ d_fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ if (server_mode == SERVER_MODE_INTERACTIVE) {
+ log_stdout = True;
+ if (DEBUGLEVEL >= 9) {
+ talloc_enable_leak_report();
+ }
+ }
+
+ if (log_stdout && server_mode == SERVER_MODE_DAEMON) {
+ printf("Can't log to stdout (-S) unless daemon is in foreground (-F) or interactive (-i)\n");
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+
+ poptFreeContext(pc);
+
+ if (!override_logfile) {
+ pstr_sprintf(logfile, "%s/log.winbindd", dyn_LOGFILEBASE);
+ lp_set_logfile(logfile);
+ }
+ setup_logging("winbindd", log_stdout);
+ reopen_logs();
+
+ DEBUG(1, ("winbindd version %s started.\n%s\n",
+ SAMBA_VERSION_STRING,
+ COPYRIGHT_STARTUP_MESSAGE) );
+
+ if (!reload_services_file()) {
+ DEBUG(0, ("error opening config file\n"));
+ exit(1);
+ }
+
+ if (!directory_exist(lp_lockdir(), NULL)) {
+ mkdir(lp_lockdir(), 0755);
+ }
+
+ /* Setup names. */
+
+ if (!init_names())
+ exit(1);
+
+ load_interfaces();
+
+ if (!secrets_init()) {
+
+ DEBUG(0,("Could not initialize domain trust account secrets. Giving up\n"));
+ return False;
+ }
+
+ /* Enable netbios namecache */
+
+ namecache_enable();
+
+ /* Winbind daemon initialisation */
+
+ if ( ! NT_STATUS_IS_OK(idmap_init_cache()) ) {
+ DEBUG(1, ("Could not init idmap cache!\n"));
+ }
+
+ /* Unblock all signals we are interested in as they may have been
+ blocked by the parent process. */
+
+ BlockSignals(False, SIGINT);
+ BlockSignals(False, SIGQUIT);
+ BlockSignals(False, SIGTERM);
+ BlockSignals(False, SIGUSR1);
+ BlockSignals(False, SIGUSR2);
+ BlockSignals(False, SIGHUP);
+ BlockSignals(False, SIGCHLD);
+
+ /* Setup signal handlers */
+
+ CatchSignal(SIGINT, termination_handler); /* Exit on these sigs */
+ CatchSignal(SIGQUIT, termination_handler);
+ CatchSignal(SIGTERM, termination_handler);
+ CatchSignal(SIGCHLD, sigchld_handler);
+
+ CatchSignal(SIGPIPE, SIG_IGN); /* Ignore sigpipe */
+
+ CatchSignal(SIGUSR2, sigusr2_handler); /* Debugging sigs */
+ CatchSignal(SIGHUP, sighup_handler);
+
+ if (server_mode == SERVER_MODE_DAEMON) {
+ DEBUG( 3, ( "Becoming a daemon.\n" ) );
+ become_daemon(True, no_process_group);
+ } else if (server_mode == SERVER_MODE_FOREGROUND) {
+ become_daemon(False, no_process_group);
+ }
+
+ pidfile_create("winbindd");
+
+ /* Ensure all cache and idmap caches are consistent
+ before we startup. */
+
+ if (winbindd_validate_cache()) {
+ /* We have a bad cache, but luckily we
+ just deleted it. Restart ourselves */
+ int i;
+ /* Ensure we have no open low fd's. */
+ for (i = 3; i < 100; i++) {
+ close(i);
+ }
+ return execve(argv[0], argv, envp);
+ }
+
+#if HAVE_SETPGID
+ /*
+ * If we're interactive we want to set our own process group for
+ * signal management.
+ */
+ if (server_mode == SERVER_MODE_INTERACTIVE && !no_process_group) {
+ setpgid( (pid_t)0, (pid_t)0);
+ }
+#endif
+
+ TimeInit();
+
+ /* Initialise messaging system */
+
+ if (winbind_messaging_context() == NULL) {
+ DEBUG(0, ("unable to initialize messaging system\n"));
+ exit(1);
+ }
+
+ /* Initialize cache (ensure version is correct). */
+ if (!initialize_winbindd_cache()) {
+ exit(1);
+ }
+
+ /* React on 'smbcontrol winbindd reload-config' in the same way
+ as to SIGHUP signal */
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_SMB_CONF_UPDATED, msg_reload_services);
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_SHUTDOWN, msg_shutdown);
+
+ /* Handle online/offline messages. */
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_WINBIND_OFFLINE, winbind_msg_offline);
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_WINBIND_ONLINE, winbind_msg_online);
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_WINBIND_ONLINESTATUS, winbind_msg_onlinestatus);
+
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_DUMP_EVENT_LIST, winbind_msg_dump_event_list);
+
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_WINBIND_VALIDATE_CACHE,
+ winbind_msg_validate_cache);
+
+ netsamlogon_cache_init(); /* Non-critical */
+
+ /* clear the cached list of trusted domains */
+
+ wcache_tdc_clear();
+
+ if (!init_domain_list()) {
+ DEBUG(0,("unable to initalize domain list\n"));
+ exit(1);
+ }
+
+ init_idmap_child();
+ init_locator_child();
+
+ smb_nscd_flush_user_cache();
+ smb_nscd_flush_group_cache();
+
+ /* Loop waiting for requests */
+ winbindd_process_loop(server_mode);
+
+ return 0;
+}
diff --git a/source3/winbindd/winbindd.h b/source3/winbindd/winbindd.h
new file mode 100644
index 0000000000..e7119c332f
--- /dev/null
+++ b/source3/winbindd/winbindd.h
@@ -0,0 +1,365 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon for ntdom nss module
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _WINBINDD_H
+#define _WINBINDD_H
+
+#include "nsswitch/winbind_struct_protocol.h"
+
+#ifdef HAVE_LIBNSCD
+#include <libnscd.h>
+#endif
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+#define WB_REPLACE_CHAR '_'
+
+/* bits for fd_event.flags */
+#define EVENT_FD_READ 1
+#define EVENT_FD_WRITE 2
+
+struct fd_event {
+ struct fd_event *next, *prev;
+ int fd;
+ int flags; /* see EVENT_FD_* flags */
+ void (*handler)(struct fd_event *fde, int flags);
+ void *data;
+ size_t length, done;
+ void (*finished)(void *private_data, BOOL success);
+ void *private_data;
+};
+
+struct sid_ctr {
+ DOM_SID *sid;
+ BOOL finished;
+ const char *domain;
+ const char *name;
+ enum lsa_SidType type;
+};
+
+struct winbindd_cli_state {
+ struct winbindd_cli_state *prev, *next; /* Linked list pointers */
+ int sock; /* Open socket from client */
+ struct fd_event fd_event;
+ pid_t pid; /* pid of client */
+ BOOL finished; /* Can delete from list */
+ BOOL write_extra_data; /* Write extra_data field */
+ time_t last_access; /* Time of last access (read or write) */
+ BOOL privileged; /* Is the client 'privileged' */
+
+ TALLOC_CTX *mem_ctx; /* memory per request */
+ struct winbindd_request request; /* Request from client */
+ struct winbindd_response response; /* Respose to client */
+ BOOL getpwent_initialized; /* Has getpwent_state been
+ * initialized? */
+ BOOL getgrent_initialized; /* Has getgrent_state been
+ * initialized? */
+ struct getent_state *getpwent_state; /* State for getpwent() */
+ struct getent_state *getgrent_state; /* State for getgrent() */
+};
+
+/* State between get{pw,gr}ent() calls */
+
+struct getent_state {
+ struct getent_state *prev, *next;
+ void *sam_entries;
+ uint32 sam_entry_index, num_sam_entries;
+ BOOL got_sam_entries;
+ fstring domain_name;
+};
+
+/* Storage for cached getpwent() user entries */
+
+struct getpwent_user {
+ fstring name; /* Account name */
+ fstring gecos; /* User information */
+ fstring homedir; /* User Home Directory */
+ fstring shell; /* User Login Shell */
+ DOM_SID user_sid; /* NT user and primary group SIDs */
+ DOM_SID group_sid;
+};
+
+/* Server state structure */
+
+typedef struct {
+ char *acct_name;
+ char *full_name;
+ char *homedir;
+ char *shell;
+ gid_t primary_gid; /* allow the nss_info
+ backend to set the primary group */
+ DOM_SID user_sid; /* NT user and primary group SIDs */
+ DOM_SID group_sid;
+} WINBIND_USERINFO;
+
+/* Our connection to the DC */
+
+struct winbindd_cm_conn {
+ struct cli_state *cli;
+
+ struct rpc_pipe_client *samr_pipe;
+ POLICY_HND sam_connect_handle, sam_domain_handle;
+
+ struct rpc_pipe_client *lsa_pipe;
+ POLICY_HND lsa_policy;
+
+ struct rpc_pipe_client *netlogon_pipe;
+};
+
+struct winbindd_async_request;
+
+/* Async child */
+
+struct winbindd_child {
+ struct winbindd_child *next, *prev;
+
+ pid_t pid;
+ struct winbindd_domain *domain;
+ pstring logfilename;
+
+ struct fd_event event;
+ struct timed_event *lockout_policy_event;
+ struct winbindd_async_request *requests;
+};
+
+/* Structures to hold per domain information */
+
+struct winbindd_domain {
+ fstring name; /* Domain name (NetBIOS) */
+ fstring alt_name; /* alt Domain name, if any (FQDN for ADS) */
+ fstring forest_name; /* Name of the AD forest we're in */
+ DOM_SID sid; /* SID for this domain */
+ uint32 domain_flags; /* Domain flags from rpc_ds.h */
+ uint32 domain_type; /* Domain type from rpc_ds.h */
+ uint32 domain_trust_attribs; /* Trust attribs from rpc_ds.h */
+ BOOL initialized; /* Did we already ask for the domain mode? */
+ BOOL native_mode; /* is this a win2k domain in native mode ? */
+ BOOL active_directory; /* is this a win2k active directory ? */
+ BOOL primary; /* is this our primary domain ? */
+ BOOL internal; /* BUILTIN and member SAM */
+ BOOL online; /* is this domain available ? */
+ time_t startup_time; /* When we set "startup" true. */
+ BOOL startup; /* are we in the first 30 seconds after startup_time ? */
+
+ BOOL can_do_samlogon_ex; /* Due to the lack of finer control what type
+ * of DC we have, let us try to do a
+ * credential-chain less samlogon_ex call
+ * with AD and schannel. If this fails with
+ * DCERPC_FAULT_OP_RNG_ERROR, then set this
+ * to False. This variable is around so that
+ * we don't have to try _ex every time. */
+
+ /* Lookup methods for this domain (LDAP or RPC) */
+ struct winbindd_methods *methods;
+
+ /* the backend methods are used by the cache layer to find the right
+ backend */
+ struct winbindd_methods *backend;
+
+ /* Private data for the backends (used for connection cache) */
+
+ void *private_data;
+
+ /* A working DC */
+ fstring dcname;
+ struct sockaddr_in dcaddr;
+
+ /* Sequence number stuff */
+
+ time_t last_seq_check;
+ uint32 sequence_number;
+ NTSTATUS last_status;
+
+ /* The smb connection */
+
+ struct winbindd_cm_conn conn;
+
+ /* The child pid we're talking to */
+
+ struct winbindd_child child;
+
+ /* Callback we use to try put us back online. */
+
+ uint32 check_online_timeout;
+ struct timed_event *check_online_event;
+
+ /* Linked list info */
+
+ struct winbindd_domain *prev, *next;
+};
+
+/* per-domain methods. This is how LDAP vs RPC is selected
+ */
+struct winbindd_methods {
+ /* does this backend provide a consistent view of the data? (ie. is the primary group
+ always correct) */
+ BOOL consistent;
+
+ /* get a list of users, returning a WINBIND_USERINFO for each one */
+ NTSTATUS (*query_user_list)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ WINBIND_USERINFO **info);
+
+ /* get a list of domain groups */
+ NTSTATUS (*enum_dom_groups)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info);
+
+ /* get a list of domain local groups */
+ NTSTATUS (*enum_local_groups)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info);
+
+ /* convert one user or group name to a sid */
+ NTSTATUS (*name_to_sid)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ enum winbindd_cmd orig_cmd,
+ const char *domain_name,
+ const char *name,
+ DOM_SID *sid,
+ enum lsa_SidType *type);
+
+ /* convert a sid to a user or group name */
+ NTSTATUS (*sid_to_name)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type);
+
+ NTSTATUS (*rids_to_names)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *domain_sid,
+ uint32 *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types);
+
+ /* lookup user info for a given SID */
+ NTSTATUS (*query_user)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ WINBIND_USERINFO *user_info);
+
+ /* lookup all groups that a user is a member of. The backend
+ can also choose to lookup by username or rid for this
+ function */
+ NTSTATUS (*lookup_usergroups)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ uint32 *num_groups, DOM_SID **user_gids);
+
+ /* Lookup all aliases that the sids delivered are member of. This is
+ * to implement 'domain local groups' correctly */
+ NTSTATUS (*lookup_useraliases)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 num_sids,
+ const DOM_SID *sids,
+ uint32 *num_aliases,
+ uint32 **alias_rids);
+
+ /* find all members of the group with the specified group_rid */
+ NTSTATUS (*lookup_groupmem)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *group_sid,
+ uint32 *num_names,
+ DOM_SID **sid_mem, char ***names,
+ uint32 **name_types);
+
+ /* return the current global sequence number */
+ NTSTATUS (*sequence_number)(struct winbindd_domain *domain, uint32 *seq);
+
+ /* return the lockout policy */
+ NTSTATUS (*lockout_policy)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_12 *lockout_policy);
+
+ /* return the lockout policy */
+ NTSTATUS (*password_policy)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_1 *password_policy);
+
+ /* enumerate trusted domains */
+ NTSTATUS (*trusted_domains)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_domains,
+ char ***names,
+ char ***alt_names,
+ DOM_SID **dom_sids);
+};
+
+/* Used to glue a policy handle and cli_state together */
+
+typedef struct {
+ struct cli_state *cli;
+ POLICY_HND pol;
+} CLI_POLICY_HND;
+
+/* Filled out by IDMAP backends */
+struct winbindd_idmap_methods {
+ /* Called when backend is first loaded */
+ BOOL (*init)(void);
+
+ BOOL (*get_sid_from_uid)(uid_t uid, DOM_SID *sid);
+ BOOL (*get_sid_from_gid)(gid_t gid, DOM_SID *sid);
+
+ BOOL (*get_uid_from_sid)(DOM_SID *sid, uid_t *uid);
+ BOOL (*get_gid_from_sid)(DOM_SID *sid, gid_t *gid);
+
+ /* Called when backend is unloaded */
+ BOOL (*close)(void);
+ /* Called to dump backend status */
+ void (*status)(void);
+};
+
+/* Data structures for dealing with the trusted domain cache */
+
+struct winbindd_tdc_domain {
+ const char *domain_name;
+ const char *dns_name;
+ DOM_SID sid;
+ uint32 trust_flags;
+ uint32 trust_attribs;
+ uint32 trust_type;
+};
+
+
+#include "winbindd/winbindd_proto.h"
+
+#define WINBINDD_ESTABLISH_LOOP 30
+#define WINBINDD_RESCAN_FREQ lp_winbind_cache_time()
+#define WINBINDD_PAM_AUTH_KRB5_RENEW_TIME 2592000 /* one month */
+#define DOM_SEQUENCE_NONE ((uint32)-1)
+
+#define IS_DOMAIN_OFFLINE(x) ( lp_winbind_offline_logon() && \
+ ( get_global_winbindd_state_offline() \
+ || !(x)->online ) )
+#endif /* _WINBINDD_H */
diff --git a/source3/winbindd/winbindd_ads.c b/source3/winbindd/winbindd_ads.c
new file mode 100644
index 0000000000..65cc00bb97
--- /dev/null
+++ b/source3/winbindd/winbindd_ads.c
@@ -0,0 +1,1312 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind ADS backend functions
+
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
+ Copyright (C) Gerald (Jerry) Carter 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#ifdef HAVE_ADS
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+extern struct winbindd_methods reconnect_methods;
+
+/*
+ return our ads connections structure for a domain. We keep the connection
+ open to make things faster
+*/
+static ADS_STRUCT *ads_cached_connection(struct winbindd_domain *domain)
+{
+ ADS_STRUCT *ads;
+ ADS_STATUS status;
+ fstring dc_name;
+ struct in_addr dc_ip;
+
+ DEBUG(10,("ads_cached_connection\n"));
+
+ if (domain->private_data) {
+
+ time_t expire;
+ time_t now = time(NULL);
+
+ /* check for a valid structure */
+ ads = (ADS_STRUCT *)domain->private_data;
+
+ expire = MIN(ads->auth.tgt_expire, ads->auth.tgs_expire);
+
+ DEBUG(7, ("Current tickets expire in %d seconds (at %d, time is now %d)\n",
+ (uint32)expire-(uint32)now, (uint32) expire, (uint32) now));
+
+ if ( ads->config.realm && (expire > now)) {
+ return ads;
+ } else {
+ /* we own this ADS_STRUCT so make sure it goes away */
+ DEBUG(7,("Deleting expired krb5 credential cache\n"));
+ ads->is_mine = True;
+ ads_destroy( &ads );
+ ads_kdestroy("MEMORY:winbind_ccache");
+ domain->private_data = NULL;
+ }
+ }
+
+ /* we don't want this to affect the users ccache */
+ setenv("KRB5CCNAME", "MEMORY:winbind_ccache", 1);
+
+ ads = ads_init(domain->alt_name, domain->name, NULL);
+ if (!ads) {
+ DEBUG(1,("ads_init for domain %s failed\n", domain->name));
+ return NULL;
+ }
+
+ /* the machine acct password might have change - fetch it every time */
+
+ SAFE_FREE(ads->auth.password);
+ SAFE_FREE(ads->auth.realm);
+
+ if ( IS_DC ) {
+ DOM_SID sid;
+ time_t last_set_time;
+
+ if ( !pdb_get_trusteddom_pw( domain->name, &ads->auth.password, &sid, &last_set_time ) ) {
+ ads_destroy( &ads );
+ return NULL;
+ }
+ ads->auth.realm = SMB_STRDUP( ads->server.realm );
+ strupper_m( ads->auth.realm );
+ }
+ else {
+ struct winbindd_domain *our_domain = domain;
+
+ ads->auth.password = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
+
+ /* always give preference to the alt_name in our
+ primary domain if possible */
+
+ if ( !domain->primary )
+ our_domain = find_our_domain();
+
+ if ( our_domain->alt_name[0] != '\0' ) {
+ ads->auth.realm = SMB_STRDUP( our_domain->alt_name );
+ strupper_m( ads->auth.realm );
+ }
+ else
+ ads->auth.realm = SMB_STRDUP( lp_realm() );
+ }
+
+ ads->auth.renewable = WINBINDD_PAM_AUTH_KRB5_RENEW_TIME;
+
+ /* Setup the server affinity cache. We don't reaally care
+ about the name. Just setup affinity and the KRB5_CONFIG
+ file. */
+
+ get_dc_name( ads->server.workgroup, ads->server.realm, dc_name, &dc_ip );
+
+ status = ads_connect(ads);
+ if (!ADS_ERR_OK(status) || !ads->config.realm) {
+ DEBUG(1,("ads_connect for domain %s failed: %s\n",
+ domain->name, ads_errstr(status)));
+ ads_destroy(&ads);
+
+ /* if we get ECONNREFUSED then it might be a NT4
+ server, fall back to MSRPC */
+ if (status.error_type == ENUM_ADS_ERROR_SYSTEM &&
+ status.err.rc == ECONNREFUSED) {
+ /* 'reconnect_methods' is the MS-RPC backend. */
+ DEBUG(1,("Trying MSRPC methods\n"));
+ domain->backend = &reconnect_methods;
+ }
+ return NULL;
+ }
+
+ /* set the flag that says we don't own the memory even
+ though we do so that ads_destroy() won't destroy the
+ structure we pass back by reference */
+
+ ads->is_mine = False;
+
+ domain->private_data = (void *)ads;
+ return ads;
+}
+
+
+/* Query display info for a realm. This is the basic user list fn */
+static NTSTATUS query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ WINBIND_USERINFO **info)
+{
+ ADS_STRUCT *ads = NULL;
+ const char *attrs[] = { "*", NULL };
+ int i, count;
+ ADS_STATUS rc;
+ LDAPMessage *res = NULL;
+ LDAPMessage *msg = NULL;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+
+ *num_entries = 0;
+
+ DEBUG(3,("ads: query_user_list\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("query_user_list: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ ads = ads_cached_connection(domain);
+
+ if (!ads) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ rc = ads_search_retry(ads, &res, "(objectCategory=user)", attrs);
+ if (!ADS_ERR_OK(rc) || !res) {
+ DEBUG(1,("query_user_list ads_search: %s\n", ads_errstr(rc)));
+ goto done;
+ }
+
+ count = ads_count_replies(ads, res);
+ if (count == 0) {
+ DEBUG(1,("query_user_list: No users found\n"));
+ goto done;
+ }
+
+ (*info) = TALLOC_ZERO_ARRAY(mem_ctx, WINBIND_USERINFO, count);
+ if (!*info) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ i = 0;
+
+ for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
+ char *name, *gecos = NULL;
+ char *homedir = NULL;
+ char *shell = NULL;
+ uint32 group;
+ uint32 atype;
+ DOM_SID user_sid;
+ gid_t primary_gid = (gid_t)-1;
+
+ if (!ads_pull_uint32(ads, msg, "sAMAccountType", &atype) ||
+ ads_atype_map(atype) != SID_NAME_USER) {
+ DEBUG(1,("Not a user account? atype=0x%x\n", atype));
+ continue;
+ }
+
+ name = ads_pull_username(ads, mem_ctx, msg);
+
+ if ( ads_pull_sid( ads, msg, "objectSid", &user_sid ) ) {
+ status = nss_get_info_cached( domain, &user_sid, mem_ctx,
+ ads, msg, &homedir, &shell, &gecos,
+ &primary_gid );
+ }
+
+ if (gecos == NULL) {
+ gecos = ads_pull_string(ads, mem_ctx, msg, "name");
+ }
+
+ if (!ads_pull_sid(ads, msg, "objectSid",
+ &(*info)[i].user_sid)) {
+ DEBUG(1,("No sid for %s !?\n", name));
+ continue;
+ }
+ if (!ads_pull_uint32(ads, msg, "primaryGroupID", &group)) {
+ DEBUG(1,("No primary group for %s !?\n", name));
+ continue;
+ }
+
+ (*info)[i].acct_name = name;
+ (*info)[i].full_name = gecos;
+ (*info)[i].homedir = homedir;
+ (*info)[i].shell = shell;
+ (*info)[i].primary_gid = primary_gid;
+ sid_compose(&(*info)[i].group_sid, &domain->sid, group);
+ i++;
+ }
+
+ (*num_entries) = i;
+ status = NT_STATUS_OK;
+
+ DEBUG(3,("ads query_user_list gave %d entries\n", (*num_entries)));
+
+done:
+ if (res)
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/* list all domain groups */
+static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ ADS_STRUCT *ads = NULL;
+ const char *attrs[] = {"userPrincipalName", "sAMAccountName",
+ "name", "objectSid", NULL};
+ int i, count;
+ ADS_STATUS rc;
+ LDAPMessage *res = NULL;
+ LDAPMessage *msg = NULL;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ const char *filter;
+ BOOL enum_dom_local_groups = False;
+
+ *num_entries = 0;
+
+ DEBUG(3,("ads: enum_dom_groups\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("enum_dom_groups: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ /* only grab domain local groups for our domain */
+ if ( domain->active_directory && strequal(lp_realm(), domain->alt_name) ) {
+ enum_dom_local_groups = True;
+ }
+
+ /* Workaround ADS LDAP bug present in MS W2K3 SP0 and W2K SP4 w/o
+ * rollup-fixes:
+ *
+ * According to Section 5.1(4) of RFC 2251 if a value of a type is it's
+ * default value, it MUST be absent. In case of extensible matching the
+ * "dnattr" boolean defaults to FALSE and so it must be only be present
+ * when set to TRUE.
+ *
+ * When it is set to FALSE and the OpenLDAP lib (correctly) encodes a
+ * filter using bitwise matching rule then the buggy AD fails to decode
+ * the extensible match. As a workaround set it to TRUE and thereby add
+ * the dnAttributes "dn" field to cope with those older AD versions.
+ * It should not harm and won't put any additional load on the AD since
+ * none of the dn components have a bitmask-attribute.
+ *
+ * Thanks to Ralf Haferkamp for input and testing - Guenther */
+
+ filter = talloc_asprintf(mem_ctx, "(&(objectCategory=group)(&(groupType:dn:%s:=%d)(!(groupType:dn:%s:=%d))))",
+ ADS_LDAP_MATCHING_RULE_BIT_AND, GROUP_TYPE_SECURITY_ENABLED,
+ ADS_LDAP_MATCHING_RULE_BIT_AND,
+ enum_dom_local_groups ? GROUP_TYPE_BUILTIN_LOCAL_GROUP : GROUP_TYPE_RESOURCE_GROUP);
+
+ if (filter == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ads = ads_cached_connection(domain);
+
+ if (!ads) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ rc = ads_search_retry(ads, &res, filter, attrs);
+ if (!ADS_ERR_OK(rc) || !res) {
+ DEBUG(1,("enum_dom_groups ads_search: %s\n", ads_errstr(rc)));
+ goto done;
+ }
+
+ count = ads_count_replies(ads, res);
+ if (count == 0) {
+ DEBUG(1,("enum_dom_groups: No groups found\n"));
+ goto done;
+ }
+
+ (*info) = TALLOC_ZERO_ARRAY(mem_ctx, struct acct_info, count);
+ if (!*info) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ i = 0;
+
+ for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
+ char *name, *gecos;
+ DOM_SID sid;
+ uint32 rid;
+
+ name = ads_pull_username(ads, mem_ctx, msg);
+ gecos = ads_pull_string(ads, mem_ctx, msg, "name");
+ if (!ads_pull_sid(ads, msg, "objectSid", &sid)) {
+ DEBUG(1,("No sid for %s !?\n", name));
+ continue;
+ }
+
+ if (!sid_peek_check_rid(&domain->sid, &sid, &rid)) {
+ DEBUG(1,("No rid for %s !?\n", name));
+ continue;
+ }
+
+ fstrcpy((*info)[i].acct_name, name);
+ fstrcpy((*info)[i].acct_desc, gecos);
+ (*info)[i].rid = rid;
+ i++;
+ }
+
+ (*num_entries) = i;
+
+ status = NT_STATUS_OK;
+
+ DEBUG(3,("ads enum_dom_groups gave %d entries\n", (*num_entries)));
+
+done:
+ if (res)
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/* list all domain local groups */
+static NTSTATUS enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ /*
+ * This is a stub function only as we returned the domain
+ * local groups in enum_dom_groups() if the domain->native field
+ * was true. This is a simple performance optimization when
+ * using LDAP.
+ *
+ * if we ever need to enumerate domain local groups separately,
+ * then this the optimization in enum_dom_groups() will need
+ * to be split out
+ */
+ *num_entries = 0;
+
+ return NT_STATUS_OK;
+}
+
+/* If you are looking for "dn_lookup": Yes, it used to be here!
+ * It has gone now since it was a major speed bottleneck in
+ * lookup_groupmem (its only use). It has been replaced by
+ * an rpc lookup sids call... R.I.P. */
+
+/* Lookup user information from a rid */
+static NTSTATUS query_user(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ WINBIND_USERINFO *info)
+{
+ ADS_STRUCT *ads = NULL;
+ const char *attrs[] = { "*", NULL };
+ ADS_STATUS rc;
+ int count;
+ LDAPMessage *msg = NULL;
+ char *ldap_exp;
+ char *sidstr;
+ uint32 group_rid;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ NET_USER_INFO_3 *user;
+
+ DEBUG(3,("ads: query_user\n"));
+
+ info->homedir = NULL;
+ info->shell = NULL;
+ info->primary_gid = (gid_t)-1;
+
+ /* try netsamlogon cache first */
+
+ if ( (user = netsamlogon_cache_get( mem_ctx, sid )) != NULL )
+ {
+
+ DEBUG(5,("query_user: Cache lookup succeeded for %s\n",
+ sid_string_static(sid)));
+
+ sid_compose(&info->user_sid, &domain->sid, user->user_rid);
+ sid_compose(&info->group_sid, &domain->sid, user->group_rid);
+
+ info->acct_name = unistr2_tdup(mem_ctx, &user->uni_user_name);
+ info->full_name = unistr2_tdup(mem_ctx, &user->uni_full_name);
+
+ nss_get_info_cached( domain, sid, mem_ctx, NULL, NULL,
+ &info->homedir, &info->shell, &info->full_name,
+ &info->primary_gid );
+
+ TALLOC_FREE(user);
+
+ return NT_STATUS_OK;
+ }
+
+ if ( !winbindd_can_contact_domain(domain)) {
+ DEBUG(8,("query_user: No incoming trust from domain %s\n",
+ domain->name));
+
+ /* We still need to generate some basic information
+ about the user even if we cannot contact the
+ domain. Most of this stuff we can deduce. */
+
+ sid_copy( &info->user_sid, sid );
+
+ /* Assume "Domain Users" for the primary group */
+
+ sid_compose(&info->group_sid, &domain->sid, DOMAIN_GROUP_RID_USERS );
+
+ /* Try to fill in what the nss_info backend can do */
+
+ nss_get_info_cached( domain, sid, mem_ctx, NULL, NULL,
+ &info->homedir, &info->shell, &info->full_name,
+ &info->primary_gid );
+
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ /* no cache...do the query */
+
+ if ( (ads = ads_cached_connection(domain)) == NULL ) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ sidstr = sid_binstring(sid);
+ asprintf(&ldap_exp, "(objectSid=%s)", sidstr);
+ rc = ads_search_retry(ads, &msg, ldap_exp, attrs);
+ free(ldap_exp);
+ free(sidstr);
+ if (!ADS_ERR_OK(rc) || !msg) {
+ DEBUG(1,("query_user(sid=%s) ads_search: %s\n",
+ sid_string_static(sid), ads_errstr(rc)));
+ goto done;
+ }
+
+ count = ads_count_replies(ads, msg);
+ if (count != 1) {
+ DEBUG(1,("query_user(sid=%s): Not found\n",
+ sid_string_static(sid)));
+ goto done;
+ }
+
+ info->acct_name = ads_pull_username(ads, mem_ctx, msg);
+
+ nss_get_info_cached( domain, sid, mem_ctx, ads, msg,
+ &info->homedir, &info->shell, &info->full_name,
+ &info->primary_gid );
+
+ if (info->full_name == NULL) {
+ info->full_name = ads_pull_string(ads, mem_ctx, msg, "name");
+ }
+
+ if (!ads_pull_uint32(ads, msg, "primaryGroupID", &group_rid)) {
+ DEBUG(1,("No primary group for %s !?\n",
+ sid_string_static(sid)));
+ goto done;
+ }
+
+ sid_copy(&info->user_sid, sid);
+ sid_compose(&info->group_sid, &domain->sid, group_rid);
+
+ status = NT_STATUS_OK;
+
+ DEBUG(3,("ads query_user gave %s\n", info->acct_name));
+done:
+ if (msg)
+ ads_msgfree(ads, msg);
+
+ return status;
+}
+
+/* Lookup groups a user is a member of - alternate method, for when
+ tokenGroups are not available. */
+static NTSTATUS lookup_usergroups_member(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *user_dn,
+ DOM_SID *primary_group,
+ size_t *p_num_groups, DOM_SID **user_sids)
+{
+ ADS_STATUS rc;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ int count;
+ LDAPMessage *res = NULL;
+ LDAPMessage *msg = NULL;
+ char *ldap_exp;
+ ADS_STRUCT *ads;
+ const char *group_attrs[] = {"objectSid", NULL};
+ char *escaped_dn;
+ size_t num_groups = 0;
+
+ DEBUG(3,("ads: lookup_usergroups_member\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_usergroups_members: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ ads = ads_cached_connection(domain);
+
+ if (!ads) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ if (!(escaped_dn = escape_ldap_string_alloc(user_dn))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ldap_exp = talloc_asprintf(mem_ctx,
+ "(&(member=%s)(objectCategory=group)(groupType:dn:%s:=%d))",
+ escaped_dn,
+ ADS_LDAP_MATCHING_RULE_BIT_AND,
+ GROUP_TYPE_SECURITY_ENABLED);
+ if (!ldap_exp) {
+ DEBUG(1,("lookup_usergroups(dn=%s) asprintf failed!\n", user_dn));
+ SAFE_FREE(escaped_dn);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ SAFE_FREE(escaped_dn);
+
+ rc = ads_search_retry(ads, &res, ldap_exp, group_attrs);
+
+ if (!ADS_ERR_OK(rc) || !res) {
+ DEBUG(1,("lookup_usergroups ads_search member=%s: %s\n", user_dn, ads_errstr(rc)));
+ return ads_ntstatus(rc);
+ }
+
+ count = ads_count_replies(ads, res);
+
+ *user_sids = NULL;
+ num_groups = 0;
+
+ /* always add the primary group to the sid array */
+ if (!add_sid_to_array(mem_ctx, primary_group, user_sids, &num_groups)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (count > 0) {
+ for (msg = ads_first_entry(ads, res); msg;
+ msg = ads_next_entry(ads, msg)) {
+ DOM_SID group_sid;
+
+ if (!ads_pull_sid(ads, msg, "objectSid", &group_sid)) {
+ DEBUG(1,("No sid for this group ?!?\n"));
+ continue;
+ }
+
+ /* ignore Builtin groups from ADS - Guenther */
+ if (sid_check_is_in_builtin(&group_sid)) {
+ continue;
+ }
+
+ if (!add_sid_to_array(mem_ctx, &group_sid, user_sids,
+ &num_groups)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ }
+
+ *p_num_groups = num_groups;
+ status = (user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+
+ DEBUG(3,("ads lookup_usergroups (member) succeeded for dn=%s\n", user_dn));
+done:
+ if (res)
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/* Lookup groups a user is a member of - alternate method, for when
+ tokenGroups are not available. */
+static NTSTATUS lookup_usergroups_memberof(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *user_dn,
+ DOM_SID *primary_group,
+ size_t *p_num_groups, DOM_SID **user_sids)
+{
+ ADS_STATUS rc;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ ADS_STRUCT *ads;
+ const char *attrs[] = {"memberOf", NULL};
+ size_t num_groups = 0;
+ DOM_SID *group_sids = NULL;
+ int i;
+ char **strings;
+ size_t num_strings = 0;
+
+
+ DEBUG(3,("ads: lookup_usergroups_memberof\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_usergroups_memberof: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ ads = ads_cached_connection(domain);
+
+ if (!ads) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ rc = ads_search_retry_extended_dn_ranged(ads, mem_ctx, user_dn, attrs,
+ ADS_EXTENDED_DN_HEX_STRING,
+ &strings, &num_strings);
+
+ if (!ADS_ERR_OK(rc)) {
+ DEBUG(1,("lookup_usergroups_memberof ads_search member=%s: %s\n",
+ user_dn, ads_errstr(rc)));
+ return ads_ntstatus(rc);
+ }
+
+ *user_sids = NULL;
+ num_groups = 0;
+
+ /* always add the primary group to the sid array */
+ if (!add_sid_to_array(mem_ctx, primary_group, user_sids, &num_groups)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ group_sids = TALLOC_ZERO_ARRAY(mem_ctx, DOM_SID, num_strings + 1);
+ if (!group_sids) {
+ TALLOC_FREE(strings);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i<num_strings; i++) {
+
+ if (!ads_get_sid_from_extended_dn(mem_ctx, strings[i],
+ ADS_EXTENDED_DN_HEX_STRING,
+ &(group_sids)[i])) {
+ TALLOC_FREE(group_sids);
+ TALLOC_FREE(strings);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ if (i == 0) {
+ DEBUG(1,("No memberOf for this user?!?\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i<num_strings; i++) {
+
+ /* ignore Builtin groups from ADS - Guenther */
+ if (sid_check_is_in_builtin(&group_sids[i])) {
+ continue;
+ }
+
+ if (!add_sid_to_array(mem_ctx, &group_sids[i], user_sids,
+ &num_groups)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ }
+
+ *p_num_groups = num_groups;
+ status = (*user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+
+ DEBUG(3,("ads lookup_usergroups (memberof) succeeded for dn=%s\n", user_dn));
+done:
+ TALLOC_FREE(group_sids);
+
+ return status;
+}
+
+
+/* Lookup groups a user is a member of. */
+static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ uint32 *p_num_groups, DOM_SID **user_sids)
+{
+ ADS_STRUCT *ads = NULL;
+ const char *attrs[] = {"tokenGroups", "primaryGroupID", NULL};
+ ADS_STATUS rc;
+ int count;
+ LDAPMessage *msg = NULL;
+ char *user_dn = NULL;
+ DOM_SID *sids;
+ int i;
+ DOM_SID primary_group;
+ uint32 primary_group_rid;
+ fstring sid_string;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ size_t num_groups = 0;
+
+ DEBUG(3,("ads: lookup_usergroups\n"));
+ *p_num_groups = 0;
+
+ status = lookup_usergroups_cached(domain, mem_ctx, sid,
+ p_num_groups, user_sids);
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OK;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_usergroups: No incoming trust for domain %s\n",
+ domain->name));
+
+ /* Tell the cache manager not to remember this one */
+
+ return NT_STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+
+ ads = ads_cached_connection(domain);
+
+ if (!ads) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ rc = ads_search_retry_sid(ads, &msg, sid, attrs);
+
+ if (!ADS_ERR_OK(rc)) {
+ status = ads_ntstatus(rc);
+ DEBUG(1,("lookup_usergroups(sid=%s) ads_search tokenGroups: %s\n",
+ sid_to_string(sid_string, sid), ads_errstr(rc)));
+ goto done;
+ }
+
+ count = ads_count_replies(ads, msg);
+ if (count != 1) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ DEBUG(1,("lookup_usergroups(sid=%s) ads_search tokenGroups: "
+ "invalid number of results (count=%d)\n",
+ sid_to_string(sid_string, sid), count));
+ goto done;
+ }
+
+ if (!msg) {
+ DEBUG(1,("lookup_usergroups(sid=%s) ads_search tokenGroups: NULL msg\n",
+ sid_to_string(sid_string, sid)));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ user_dn = ads_get_dn(ads, msg);
+ if (user_dn == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (!ads_pull_uint32(ads, msg, "primaryGroupID", &primary_group_rid)) {
+ DEBUG(1,("%s: No primary group for sid=%s !?\n",
+ domain->name, sid_to_string(sid_string, sid)));
+ goto done;
+ }
+
+ sid_copy(&primary_group, &domain->sid);
+ sid_append_rid(&primary_group, primary_group_rid);
+
+ count = ads_pull_sids(ads, mem_ctx, msg, "tokenGroups", &sids);
+
+ /* there must always be at least one group in the token,
+ unless we are talking to a buggy Win2k server */
+
+ /* actually this only happens when the machine account has no read
+ * permissions on the tokenGroup attribute - gd */
+
+ if (count == 0) {
+
+ /* no tokenGroups */
+
+ /* lookup what groups this user is a member of by DN search on
+ * "memberOf" */
+
+ status = lookup_usergroups_memberof(domain, mem_ctx, user_dn,
+ &primary_group,
+ &num_groups, user_sids);
+ *p_num_groups = (uint32)num_groups;
+ if (NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* lookup what groups this user is a member of by DN search on
+ * "member" */
+
+ status = lookup_usergroups_member(domain, mem_ctx, user_dn,
+ &primary_group,
+ &num_groups, user_sids);
+ *p_num_groups = (uint32)num_groups;
+ goto done;
+ }
+
+ *user_sids = NULL;
+ num_groups = 0;
+
+ if (!add_sid_to_array(mem_ctx, &primary_group, user_sids, &num_groups)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i=0;i<count;i++) {
+
+ /* ignore Builtin groups from ADS - Guenther */
+ if (sid_check_is_in_builtin(&sids[i])) {
+ continue;
+ }
+
+ if (!add_sid_to_array_unique(mem_ctx, &sids[i],
+ user_sids, &num_groups)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ *p_num_groups = (uint32)num_groups;
+ status = (*user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+
+ DEBUG(3,("ads lookup_usergroups (tokenGroups) succeeded for sid=%s\n",
+ sid_to_string(sid_string, sid)));
+done:
+ ads_memfree(ads, user_dn);
+ ads_msgfree(ads, msg);
+ return status;
+}
+
+/*
+ find the members of a group, given a group rid and domain
+ */
+static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *group_sid, uint32 *num_names,
+ DOM_SID **sid_mem, char ***names,
+ uint32 **name_types)
+{
+ ADS_STATUS rc;
+ ADS_STRUCT *ads = NULL;
+ char *ldap_exp;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ char *sidbinstr;
+ char **members = NULL;
+ int i;
+ size_t num_members = 0;
+ ads_control args;
+ struct rpc_pipe_client *cli;
+ POLICY_HND lsa_policy;
+ DOM_SID *sid_mem_nocache = NULL;
+ char **names_nocache = NULL;
+ enum lsa_SidType *name_types_nocache = NULL;
+ char **domains_nocache = NULL; /* only needed for rpccli_lsa_lookup_sids */
+ uint32 num_nocache = 0;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ DEBUG(10,("ads: lookup_groupmem %s sid=%s\n", domain->name,
+ sid_string_static(group_sid)));
+
+ *num_names = 0;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ DEBUG(1, ("ads: lookup_groupmem: talloc failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_groupmem: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ ads = ads_cached_connection(domain);
+
+ if (!ads) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ if ((sidbinstr = sid_binstring(group_sid)) == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* search for all members of the group */
+ if (!(ldap_exp = talloc_asprintf(tmp_ctx, "(objectSid=%s)",
+ sidbinstr)))
+ {
+ SAFE_FREE(sidbinstr);
+ DEBUG(1, ("ads: lookup_groupmem: talloc_asprintf for ldap_exp failed!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ SAFE_FREE(sidbinstr);
+
+ args.control = ADS_EXTENDED_DN_OID;
+ args.val = ADS_EXTENDED_DN_HEX_STRING;
+ args.critical = True;
+
+ rc = ads_ranged_search(ads, tmp_ctx, LDAP_SCOPE_SUBTREE, ads->config.bind_path,
+ ldap_exp, &args, "member", &members, &num_members);
+
+ if (!ADS_ERR_OK(rc)) {
+ DEBUG(0,("ads_ranged_search failed with: %s\n", ads_errstr(rc)));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ DEBUG(10, ("ads lookup_groupmem: got %d sids via extended dn call\n", (int)num_members));
+
+ /* Now that we have a list of sids, we need to get the
+ * lists of names and name_types belonging to these sids.
+ * even though conceptually not quite clean, we use the
+ * RPC call lsa_lookup_sids for this since it can handle a
+ * list of sids. ldap calls can just resolve one sid at a time.
+ *
+ * At this stage, the sids are still hidden in the exetended dn
+ * member output format. We actually do a little better than
+ * stated above: In extracting the sids from the member strings,
+ * we try to resolve as many sids as possible from the
+ * cache. Only the rest is passed to the lsa_lookup_sids call. */
+
+ if (num_members) {
+ (*sid_mem) = TALLOC_ZERO_ARRAY(mem_ctx, DOM_SID, num_members);
+ (*names) = TALLOC_ZERO_ARRAY(mem_ctx, char *, num_members);
+ (*name_types) = TALLOC_ZERO_ARRAY(mem_ctx, uint32, num_members);
+ (sid_mem_nocache) = TALLOC_ZERO_ARRAY(tmp_ctx, DOM_SID, num_members);
+
+ if ((members == NULL) || (*sid_mem == NULL) ||
+ (*names == NULL) || (*name_types == NULL) ||
+ (sid_mem_nocache == NULL))
+ {
+ DEBUG(1, ("ads: lookup_groupmem: talloc failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+ else {
+ (*sid_mem) = NULL;
+ (*names) = NULL;
+ (*name_types) = NULL;
+ }
+
+ for (i=0; i<num_members; i++) {
+ enum lsa_SidType name_type;
+ char *name, *domain_name;
+ DOM_SID sid;
+
+ if (!ads_get_sid_from_extended_dn(tmp_ctx, members[i], args.val, &sid)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+ if (lookup_cached_sid(mem_ctx, &sid, &domain_name, &name, &name_type)) {
+ DEBUG(10,("ads: lookup_groupmem: got sid %s from cache\n",
+ sid_string_static(&sid)));
+ sid_copy(&(*sid_mem)[*num_names], &sid);
+ (*names)[*num_names] = talloc_asprintf(*names, "%s%c%s",
+ domain_name,
+ *lp_winbind_separator(),
+ name );
+
+ (*name_types)[*num_names] = name_type;
+ (*num_names)++;
+ }
+ else {
+ DEBUG(10, ("ads: lookup_groupmem: sid %s not found in cache\n",
+ sid_string_static(&sid)));
+ sid_copy(&(sid_mem_nocache)[num_nocache], &sid);
+ num_nocache++;
+ }
+ }
+
+ DEBUG(10, ("ads: lookup_groupmem: %d sids found in cache, "
+ "%d left for lsa_lookupsids\n", *num_names, num_nocache));
+
+ /* handle sids not resolved from cache by lsa_lookup_sids */
+ if (num_nocache > 0) {
+
+ status = cm_connect_lsa(domain, tmp_ctx, &cli, &lsa_policy);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpccli_lsa_lookup_sids_all(cli, tmp_ctx,
+ &lsa_policy,
+ num_nocache,
+ sid_mem_nocache,
+ &domains_nocache,
+ &names_nocache,
+ &name_types_nocache);
+
+ if (NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED))
+ {
+ /* Copy the entries over from the "_nocache" arrays
+ * to the result arrays, skipping the gaps the
+ * lookup_sids call left. */
+ for (i=0; i < num_nocache; i++) {
+ if (((names_nocache)[i] != NULL) &&
+ ((name_types_nocache)[i] != SID_NAME_UNKNOWN))
+ {
+ sid_copy(&(*sid_mem)[*num_names],
+ &sid_mem_nocache[i]);
+ (*names)[*num_names] = talloc_asprintf( *names,
+ "%s%c%s",
+ domains_nocache[i],
+ *lp_winbind_separator(),
+ names_nocache[i] );
+ (*name_types)[*num_names] = name_types_nocache[i];
+ (*num_names)++;
+ }
+ }
+ }
+ else if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ DEBUG(10, ("lookup_groupmem: lsa_lookup_sids could "
+ "not map any SIDs at all.\n"));
+ /* Don't handle this as an error here.
+ * There is nothing left to do with respect to the
+ * overall result... */
+ }
+ else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("lookup_groupmem: Error looking up %d "
+ "sids via rpc_lsa_lookup_sids: %s\n",
+ (int)num_members, nt_errstr(status)));
+ goto done;
+ }
+ }
+
+ status = NT_STATUS_OK;
+ DEBUG(3,("ads lookup_groupmem for sid=%s succeeded\n",
+ sid_string_static(group_sid)));
+
+done:
+
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+/* find the sequence number for a domain */
+static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq)
+{
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS rc;
+
+ DEBUG(3,("ads: fetch sequence_number for %s\n", domain->name));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("sequence: No incoming trust for domain %s\n",
+ domain->name));
+ *seq = time(NULL);
+ return NT_STATUS_OK;
+ }
+
+ *seq = DOM_SEQUENCE_NONE;
+
+ ads = ads_cached_connection(domain);
+
+ if (!ads) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ rc = ads_USN(ads, seq);
+
+ if (!ADS_ERR_OK(rc)) {
+
+ /* its a dead connection, destroy it */
+
+ if (domain->private_data) {
+ ads = (ADS_STRUCT *)domain->private_data;
+ ads->is_mine = True;
+ ads_destroy(&ads);
+ ads_kdestroy("MEMORY:winbind_ccache");
+ domain->private_data = NULL;
+ }
+ }
+ return ads_ntstatus(rc);
+}
+
+/* get a list of trusted domains */
+static NTSTATUS trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_domains,
+ char ***names,
+ char ***alt_names,
+ DOM_SID **dom_sids)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ struct ds_domain_trust *domains = NULL;
+ int count = 0;
+ int i;
+ uint32 flags;
+ struct rpc_pipe_client *cli;
+ uint32 fr_flags = (DS_DOMAIN_IN_FOREST | DS_DOMAIN_TREE_ROOT);
+ int ret_count;
+
+ DEBUG(3,("ads: trusted_domains\n"));
+
+ *num_domains = 0;
+ *alt_names = NULL;
+ *names = NULL;
+ *dom_sids = NULL;
+
+ /* If this is our primary domain or a root in our forest,
+ query for all trusts. If not, then just look for domain
+ trusts in the target forest */
+
+ if ( domain->primary ||
+ ((domain->domain_flags&fr_flags) == fr_flags) )
+ {
+ flags = DS_DOMAIN_DIRECT_OUTBOUND |
+ DS_DOMAIN_DIRECT_INBOUND |
+ DS_DOMAIN_IN_FOREST;
+ } else {
+ flags = DS_DOMAIN_IN_FOREST;
+ }
+
+ result = cm_connect_netlogon(domain, &cli);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(5, ("trusted_domains: Could not open a connection to %s "
+ "for PIPE_NETLOGON (%s)\n",
+ domain->name, nt_errstr(result)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if ( NT_STATUS_IS_OK(result) ) {
+ result = rpccli_ds_enum_domain_trusts(cli, mem_ctx,
+ cli->cli->desthost,
+ flags, &domains,
+ (unsigned int *)&count);
+ }
+
+ if ( NT_STATUS_IS_OK(result) && count) {
+
+ /* Allocate memory for trusted domain names and sids */
+
+ if ( !(*names = TALLOC_ARRAY(mem_ctx, char *, count)) ) {
+ DEBUG(0, ("trusted_domains: out of memory\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !(*alt_names = TALLOC_ARRAY(mem_ctx, char *, count)) ) {
+ DEBUG(0, ("trusted_domains: out of memory\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !(*dom_sids = TALLOC_ARRAY(mem_ctx, DOM_SID, count)) ) {
+ DEBUG(0, ("trusted_domains: out of memory\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Copy across names and sids */
+
+
+ ret_count = 0;
+ for (i = 0; i < count; i++) {
+ struct winbindd_domain d;
+
+ /* drop external trusts if this is not our primary
+ domain. This means that the returned number of
+ domains may be less that the ones actually trusted
+ by the DC. */
+
+ if ( (domains[i].trust_attributes == DS_DOMAIN_TRUST_ATTRIB_QUARANTINED_DOMAIN) &&
+ !domain->primary )
+ {
+ DEBUG(10,("trusted_domains: Skipping external trusted domain "
+ "%s because it is outside of our primary domain\n",
+ domains[i].netbios_domain));
+ continue;
+ }
+
+ (*names)[ret_count] = domains[i].netbios_domain;
+ (*alt_names)[ret_count] = domains[i].dns_domain;
+ sid_copy(&(*dom_sids)[ret_count], &domains[i].sid);
+
+ /* add to the trusted domain cache */
+
+ fstrcpy( d.name, domains[i].netbios_domain );
+ fstrcpy( d.alt_name, domains[i].dns_domain );
+ sid_copy( &d.sid, &domains[i].sid );
+
+ /* This gets a little tricky. If we are
+ following a transitive forest trust, then
+ innerit the flags, type, and attrins from
+ the domain we queried to make sure we don't
+ record the view of the trust from the wrong
+ side. Always view it from the side of our
+ primary domain. --jerry */
+ if ( domain->primary ||
+ ((domain->domain_flags&fr_flags) == fr_flags) )
+ {
+ DEBUG(10,("trusted_domains(ads): Storing trust "
+ "flags for domain %s\n", d.alt_name));
+
+ /* Look this up in cache to make sure
+ we have the current trust flags and
+ attributes */
+
+ d.domain_flags = domains[i].flags;
+ d.domain_type = domains[i].trust_type;
+ d.domain_trust_attribs = domains[i].trust_attributes;
+ } else {
+ DEBUG(10,("trusted_domains(ads): Inheriting trust "
+ "flags for domain %s\n", d.alt_name));
+ d.domain_flags = domain->domain_flags;
+ d.domain_type = domain->domain_type;
+ d.domain_trust_attribs = domain->domain_trust_attribs;
+ }
+
+ wcache_tdc_add_domain( &d );
+
+ ret_count++;
+
+ }
+
+ *num_domains = ret_count;
+ }
+
+ return result;
+}
+
+/* the ADS backend methods are exposed via this structure */
+struct winbindd_methods ads_methods = {
+ True,
+ query_user_list,
+ enum_dom_groups,
+ enum_local_groups,
+ msrpc_name_to_sid,
+ msrpc_sid_to_name,
+ msrpc_rids_to_names,
+ query_user,
+ lookup_usergroups,
+ msrpc_lookup_useraliases,
+ lookup_groupmem,
+ sequence_number,
+ msrpc_lockout_policy,
+ msrpc_password_policy,
+ trusted_domains,
+};
+
+#endif
diff --git a/source3/winbindd/winbindd_async.c b/source3/winbindd/winbindd_async.c
new file mode 100644
index 0000000000..5d31ff0a41
--- /dev/null
+++ b/source3/winbindd/winbindd_async.c
@@ -0,0 +1,1695 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Async helpers for blocking functions
+
+ Copyright (C) Volker Lendecke 2005
+ Copyright (C) Gerald Carter 2006
+
+ The helpers always consist of three functions:
+
+ * A request setup function that takes the necessary parameters together
+ with a continuation function that is to be called upon completion
+
+ * A private continuation function that is internal only. This is to be
+ called by the lower-level functions in do_async(). Its only task is to
+ properly call the continuation function named above.
+
+ * A worker function that is called inside the appropriate child process.
+
+ 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 "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+struct do_async_state {
+ TALLOC_CTX *mem_ctx;
+ struct winbindd_request request;
+ struct winbindd_response response;
+ void (*cont)(TALLOC_CTX *mem_ctx,
+ BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data);
+ void *c, *private_data;
+};
+
+static void do_async_recv(void *private_data, BOOL success)
+{
+ struct do_async_state *state =
+ talloc_get_type_abort(private_data, struct do_async_state);
+
+ state->cont(state->mem_ctx, success, &state->response,
+ state->c, state->private_data);
+}
+
+static void do_async(TALLOC_CTX *mem_ctx, struct winbindd_child *child,
+ const struct winbindd_request *request,
+ void (*cont)(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data),
+ void *c, void *private_data)
+{
+ struct do_async_state *state;
+
+ state = TALLOC_ZERO_P(mem_ctx, struct do_async_state);
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ cont(mem_ctx, False, NULL, c, private_data);
+ return;
+ }
+
+ state->mem_ctx = mem_ctx;
+ state->request = *request;
+ state->request.length = sizeof(state->request);
+ state->cont = cont;
+ state->c = c;
+ state->private_data = private_data;
+
+ async_request(mem_ctx, child, &state->request,
+ &state->response, do_async_recv, state);
+}
+
+void do_async_domain(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
+ const struct winbindd_request *request,
+ void (*cont)(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data),
+ void *c, void *private_data)
+{
+ struct do_async_state *state;
+
+ state = TALLOC_ZERO_P(mem_ctx, struct do_async_state);
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ cont(mem_ctx, False, NULL, c, private_data);
+ return;
+ }
+
+ state->mem_ctx = mem_ctx;
+ state->request = *request;
+ state->request.length = sizeof(state->request);
+ state->cont = cont;
+ state->c = c;
+ state->private_data = private_data;
+
+ async_domain_request(mem_ctx, domain, &state->request,
+ &state->response, do_async_recv, state);
+}
+
+static void winbindd_set_mapping_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ) = (void (*)(void *, BOOL))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger idmap_set_mapping\n"));
+ cont(private_data, False);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("idmap_set_mapping returned an error\n"));
+ cont(private_data, False);
+ return;
+ }
+
+ cont(private_data, True);
+}
+
+void winbindd_set_mapping_async(TALLOC_CTX *mem_ctx, const struct id_map *map,
+ void (*cont)(void *private_data, BOOL success),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_SET_MAPPING;
+ request.data.dual_idmapset.id = map->xid.id;
+ request.data.dual_idmapset.type = map->xid.type;
+ sid_to_string(request.data.dual_idmapset.sid, map->sid);
+
+ do_async(mem_ctx, idmap_child(), &request, winbindd_set_mapping_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_set_mapping(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct id_map map;
+ DOM_SID sid;
+ NTSTATUS result;
+
+ DEBUG(3, ("[%5lu]: dual_idmapset\n", (unsigned long)state->pid));
+
+ if (!string_to_sid(&sid, state->request.data.dual_idmapset.sid))
+ return WINBINDD_ERROR;
+
+ map.sid = &sid;
+ map.xid.id = state->request.data.dual_idmapset.id;
+ map.xid.type = state->request.data.dual_idmapset.type;
+ map.status = ID_MAPPED;
+
+ result = idmap_set_mapping(&map);
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
+
+static void winbindd_set_hwm_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ) = (void (*)(void *, BOOL))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger idmap_set_hwm\n"));
+ cont(private_data, False);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("idmap_set_hwm returned an error\n"));
+ cont(private_data, False);
+ return;
+ }
+
+ cont(private_data, True);
+}
+
+void winbindd_set_hwm_async(TALLOC_CTX *mem_ctx, const struct unixid *xid,
+ void (*cont)(void *private_data, BOOL success),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_SET_HWM;
+ request.data.dual_idmapset.id = xid->id;
+ request.data.dual_idmapset.type = xid->type;
+
+ do_async(mem_ctx, idmap_child(), &request, winbindd_set_hwm_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_set_hwm(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct unixid xid;
+ NTSTATUS result;
+
+ DEBUG(3, ("[%5lu]: dual_set_hwm\n", (unsigned long)state->pid));
+
+ xid.id = state->request.data.dual_idmapset.id;
+ xid.type = state->request.data.dual_idmapset.type;
+
+ switch (xid.type) {
+ case ID_TYPE_UID:
+ result = idmap_set_uid_hwm(&xid);
+ break;
+ case ID_TYPE_GID:
+ result = idmap_set_gid_hwm(&xid);
+ break;
+ default:
+ return WINBINDD_ERROR;
+ }
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
+
+static void winbindd_sids2xids_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, void *, int) =
+ (void (*)(void *, BOOL, void *, int))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger sids2xids\n"));
+ cont(private_data, False, NULL, 0);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("sids2xids returned an error\n"));
+ cont(private_data, False, NULL, 0);
+ return;
+ }
+
+ cont(private_data, True, response->extra_data.data, response->length - sizeof(response));
+}
+
+void winbindd_sids2xids_async(TALLOC_CTX *mem_ctx, void *sids, int size,
+ void (*cont)(void *private_data, BOOL success, void *data, int len),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_SIDS2XIDS;
+ request.extra_data.data = (char *)sids;
+ request.extra_len = size;
+ do_async(mem_ctx, idmap_child(), &request, winbindd_sids2xids_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_sids2xids(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DOM_SID *sids;
+ struct unixid *xids;
+ struct id_map **ids;
+ NTSTATUS result;
+ int num, i;
+
+ DEBUG(3, ("[%5lu]: sids to unix ids\n", (unsigned long)state->pid));
+
+ if (state->request.extra_len == 0) {
+ DEBUG(0, ("Invalid buffer size!\n"));
+ return WINBINDD_ERROR;
+ }
+
+ sids = (DOM_SID *)state->request.extra_data.data;
+ num = state->request.extra_len / sizeof(DOM_SID);
+
+ ids = TALLOC_ZERO_ARRAY(state->mem_ctx, struct id_map *, num + 1);
+ if ( ! ids) {
+ DEBUG(0, ("Out of memory!\n"));
+ return WINBINDD_ERROR;
+ }
+ for (i = 0; i < num; i++) {
+ ids[i] = TALLOC_P(ids, struct id_map);
+ if ( ! ids[i]) {
+ DEBUG(0, ("Out of memory!\n"));
+ talloc_free(ids);
+ return WINBINDD_ERROR;
+ }
+ ids[i]->sid = &sids[i];
+ }
+
+ result = idmap_sids_to_unixids(ids);
+
+ if (NT_STATUS_IS_OK(result)) {
+
+ xids = SMB_MALLOC_ARRAY(struct unixid, num);
+ if ( ! xids) {
+ DEBUG(0, ("Out of memory!\n"));
+ talloc_free(ids);
+ return WINBINDD_ERROR;
+ }
+
+ for (i = 0; i < num; i++) {
+ if (ids[i]->status == ID_MAPPED) {
+ xids[i].type = ids[i]->xid.type;
+ xids[i].id = ids[i]->xid.id;
+ } else {
+ xids[i].type = -1;
+ }
+ }
+
+ state->response.length = sizeof(state->response) + (sizeof(struct unixid) * num);
+ state->response.extra_data.data = xids;
+
+ } else {
+ DEBUG (2, ("idmap_sids_to_unixids returned an error: 0x%08x\n", NT_STATUS_V(result)));
+ talloc_free(ids);
+ return WINBINDD_ERROR;
+ }
+
+ talloc_free(ids);
+ return WINBINDD_OK;
+}
+
+static void winbindd_sid2uid_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, uid_t uid) =
+ (void (*)(void *, BOOL, uid_t))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger sid2uid\n"));
+ cont(private_data, False, 0);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("sid2uid returned an error\n"));
+ cont(private_data, False, 0);
+ return;
+ }
+
+ cont(private_data, True, response->data.uid);
+}
+
+void winbindd_sid2uid_async(TALLOC_CTX *mem_ctx, const DOM_SID *sid,
+ void (*cont)(void *private_data, BOOL success, uid_t uid),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_SID2UID;
+ sid_to_string(request.data.dual_sid2id.sid, sid);
+ do_async(mem_ctx, idmap_child(), &request, winbindd_sid2uid_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_sid2uid(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DOM_SID sid;
+ NTSTATUS result;
+
+ DEBUG(3, ("[%5lu]: sid to uid %s\n", (unsigned long)state->pid,
+ state->request.data.dual_sid2id.sid));
+
+ if (!string_to_sid(&sid, state->request.data.dual_sid2id.sid)) {
+ DEBUG(1, ("Could not get convert sid %s from string\n",
+ state->request.data.dual_sid2id.sid));
+ return WINBINDD_ERROR;
+ }
+
+ /* Find uid for this sid and return it, possibly ask the slow remote idmap */
+
+ result = idmap_sid_to_uid(&sid, &(state->response.data.uid));
+
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
+
+#if 0 /* not used */
+static void uid2name_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data);
+
+void winbindd_uid2name_async(TALLOC_CTX *mem_ctx, uid_t uid,
+ void (*cont)(void *private_data, BOOL success,
+ const char *name),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_UID2NAME;
+ request.data.uid = uid;
+ do_async(mem_ctx, idmap_child(), &request, uid2name_recv,
+ (void *)cont, private_data);
+}
+#endif /* not used */
+
+enum winbindd_result winbindd_dual_uid2name(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct passwd *pw;
+
+ DEBUG(3, ("[%5lu]: uid2name %lu\n", (unsigned long)state->pid,
+ (unsigned long)state->request.data.uid));
+
+ pw = getpwuid(state->request.data.uid);
+ if (pw == NULL) {
+ DEBUG(5, ("User %lu not found\n",
+ (unsigned long)state->request.data.uid));
+ return WINBINDD_ERROR;
+ }
+
+ fstrcpy(state->response.data.name.name, pw->pw_name);
+ return WINBINDD_OK;
+}
+
+#if 0 /* not used */
+static void uid2name_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, const char *name) =
+ (void (*)(void *, BOOL, const char *))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger uid2name\n"));
+ cont(private_data, False, NULL);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("uid2name returned an error\n"));
+ cont(private_data, False, NULL);
+ return;
+ }
+
+ cont(private_data, True, response->data.name.name);
+}
+
+static void name2uid_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data);
+
+static void winbindd_name2uid_async(TALLOC_CTX *mem_ctx, const char *name,
+ void (*cont)(void *private_data, BOOL success,
+ uid_t uid),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_NAME2UID;
+ fstrcpy(request.data.username, name);
+ do_async(mem_ctx, idmap_child(), &request, name2uid_recv,
+ (void *)cont, private_data);
+}
+#endif /* not used */
+
+enum winbindd_result winbindd_dual_name2uid(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct passwd *pw;
+
+ /* Ensure null termination */
+ state->request.data.username
+ [sizeof(state->request.data.username)-1] = '\0';
+
+ DEBUG(3, ("[%5lu]: name2uid %s\n", (unsigned long)state->pid,
+ state->request.data.username));
+
+ pw = getpwnam(state->request.data.username);
+ if (pw == NULL) {
+ return WINBINDD_ERROR;
+ }
+
+ state->response.data.uid = pw->pw_uid;
+ return WINBINDD_OK;
+}
+
+#if 0 /* not used */
+static void name2uid_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, uid_t uid) =
+ (void (*)(void *, BOOL, uid_t))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger name2uid\n"));
+ cont(private_data, False, 0);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("name2uid returned an error\n"));
+ cont(private_data, False, 0);
+ return;
+ }
+
+ cont(private_data, True, response->data.uid);
+}
+#endif /* not used */
+
+static void winbindd_sid2gid_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, gid_t gid) =
+ (void (*)(void *, BOOL, gid_t))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger sid2gid\n"));
+ cont(private_data, False, 0);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("sid2gid returned an error\n"));
+ cont(private_data, False, 0);
+ return;
+ }
+
+ cont(private_data, True, response->data.gid);
+}
+
+void winbindd_sid2gid_async(TALLOC_CTX *mem_ctx, const DOM_SID *sid,
+ void (*cont)(void *private_data, BOOL success, gid_t gid),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_SID2GID;
+ sid_to_string(request.data.dual_sid2id.sid, sid);
+
+ DEBUG(7,("winbindd_sid2gid_async: Resolving %s to a gid\n",
+ request.data.dual_sid2id.sid));
+
+ do_async(mem_ctx, idmap_child(), &request, winbindd_sid2gid_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_sid2gid(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DOM_SID sid;
+ NTSTATUS result;
+
+ DEBUG(3, ("[%5lu]: sid to gid %s\n", (unsigned long)state->pid,
+ state->request.data.dual_sid2id.sid));
+
+ if (!string_to_sid(&sid, state->request.data.dual_sid2id.sid)) {
+ DEBUG(1, ("Could not get convert sid %s from string\n",
+ state->request.data.dual_sid2id.sid));
+ return WINBINDD_ERROR;
+ }
+
+ /* Find gid for this sid and return it, possibly ask the slow remote idmap */
+
+ result = idmap_sid_to_gid(&sid, &state->response.data.gid);
+
+ DEBUG(10, ("winbindd_dual_sid2gid: 0x%08x - %s - %u\n", NT_STATUS_V(result), sid_string_static(&sid), state->response.data.gid));
+
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
+
+static void gid2name_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, const char *name) =
+ (void (*)(void *, BOOL, const char *))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger gid2name\n"));
+ cont(private_data, False, NULL);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("gid2name returned an error\n"));
+ cont(private_data, False, NULL);
+ return;
+ }
+
+ cont(private_data, True, response->data.name.name);
+}
+
+void winbindd_gid2name_async(TALLOC_CTX *mem_ctx, gid_t gid,
+ void (*cont)(void *private_data, BOOL success,
+ const char *name),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_GID2NAME;
+ request.data.gid = gid;
+ do_async(mem_ctx, idmap_child(), &request, gid2name_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_gid2name(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct group *gr;
+
+ DEBUG(3, ("[%5lu]: gid2name %lu\n", (unsigned long)state->pid,
+ (unsigned long)state->request.data.gid));
+
+ gr = getgrgid(state->request.data.gid);
+ if (gr == NULL)
+ return WINBINDD_ERROR;
+
+ fstrcpy(state->response.data.name.name, gr->gr_name);
+ return WINBINDD_OK;
+}
+
+#if 0 /* not used */
+static void name2gid_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data);
+
+static void winbindd_name2gid_async(TALLOC_CTX *mem_ctx, const char *name,
+ void (*cont)(void *private_data, BOOL success,
+ gid_t gid),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_NAME2GID;
+ fstrcpy(request.data.groupname, name);
+ do_async(mem_ctx, idmap_child(), &request, name2gid_recv,
+ (void *)cont, private_data);
+}
+#endif /* not used */
+
+enum winbindd_result winbindd_dual_name2gid(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct group *gr;
+
+ /* Ensure null termination */
+ state->request.data.groupname
+ [sizeof(state->request.data.groupname)-1] = '\0';
+
+ DEBUG(3, ("[%5lu]: name2gid %s\n", (unsigned long)state->pid,
+ state->request.data.groupname));
+
+ gr = getgrnam(state->request.data.groupname);
+ if (gr == NULL) {
+ return WINBINDD_ERROR;
+ }
+
+ state->response.data.gid = gr->gr_gid;
+ return WINBINDD_OK;
+}
+
+#if 0 /* not used */
+static void name2gid_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, gid_t gid) =
+ (void (*)(void *, BOOL, gid_t))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger name2gid\n"));
+ cont(private_data, False, 0);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("name2gid returned an error\n"));
+ cont(private_data, False, 0);
+ return;
+ }
+
+ cont(private_data, True, response->data.gid);
+}
+#endif /* not used */
+
+struct lookupsid_state {
+ DOM_SID sid;
+ void *caller_private_data;
+};
+
+
+static void lookupsid_recv2(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, const char *dom_name,
+ const char *name, enum lsa_SidType type) =
+ (void (*)(void *, BOOL, const char *, const char *,
+ enum lsa_SidType))c;
+ struct lookupsid_state *s = talloc_get_type_abort(private_data,
+ struct lookupsid_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger lookupsid\n"));
+ cont(s->caller_private_data, False, NULL, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("lookupsid (forest root) returned an error\n"));
+ cont(s->caller_private_data, False, NULL, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ cont(s->caller_private_data, True, response->data.name.dom_name,
+ response->data.name.name,
+ (enum lsa_SidType)response->data.name.type);
+}
+
+static void lookupsid_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, const char *dom_name,
+ const char *name, enum lsa_SidType type) =
+ (void (*)(void *, BOOL, const char *, const char *,
+ enum lsa_SidType))c;
+ struct lookupsid_state *s = talloc_get_type_abort(private_data,
+ struct lookupsid_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger lookupsid\n"));
+ cont(s->caller_private_data, False, NULL, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ /* Try again using the forest root */
+ struct winbindd_domain *root_domain = find_root_domain();
+ struct winbindd_request request;
+
+ if ( !root_domain ) {
+ DEBUG(5,("lookupsid_recv: unable to determine forest root\n"));
+ cont(s->caller_private_data, False, NULL, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_LOOKUPSID;
+ fstrcpy(request.data.sid, sid_string_static(&s->sid));
+
+ do_async_domain(mem_ctx, root_domain, &request, lookupsid_recv2,
+ (void *)cont, s);
+
+ return;
+ }
+
+ cont(s->caller_private_data, True, response->data.name.dom_name,
+ response->data.name.name,
+ (enum lsa_SidType)response->data.name.type);
+}
+
+void winbindd_lookupsid_async(TALLOC_CTX *mem_ctx, const DOM_SID *sid,
+ void (*cont)(void *private_data, BOOL success,
+ const char *dom_name,
+ const char *name,
+ enum lsa_SidType type),
+ void *private_data)
+{
+ struct winbindd_domain *domain;
+ struct winbindd_request request;
+ struct lookupsid_state *s;
+
+ domain = find_lookup_domain_from_sid(sid);
+ if (domain == NULL) {
+ DEBUG(5, ("Could not find domain for sid %s\n",
+ sid_string_static(sid)));
+ cont(private_data, False, NULL, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_LOOKUPSID;
+ fstrcpy(request.data.sid, sid_string_static(sid));
+
+ if ( (s = TALLOC_ZERO_P(mem_ctx, struct lookupsid_state)) == NULL ) {
+ DEBUG(0, ("winbindd_lookupsid_async: talloc failed\n"));
+ cont(private_data, False, NULL, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ sid_copy( &s->sid, sid );
+ s->caller_private_data = private_data;
+
+ do_async_domain(mem_ctx, domain, &request, lookupsid_recv,
+ (void *)cont, s);
+}
+
+enum winbindd_result winbindd_dual_lookupsid(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ enum lsa_SidType type;
+ DOM_SID sid;
+ char *name;
+ char *dom_name;
+
+ /* Ensure null termination */
+ state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: lookupsid %s\n", (unsigned long)state->pid,
+ state->request.data.sid));
+
+ /* Lookup sid from PDC using lsa_lookup_sids() */
+
+ if (!string_to_sid(&sid, state->request.data.sid)) {
+ DEBUG(5, ("%s not a SID\n", state->request.data.sid));
+ return WINBINDD_ERROR;
+ }
+
+ /* Lookup the sid */
+
+ if (!winbindd_lookup_name_by_sid(state->mem_ctx, domain, &sid,
+ &dom_name, &name, &type))
+ {
+ TALLOC_FREE(dom_name);
+ TALLOC_FREE(name);
+ return WINBINDD_ERROR;
+ }
+
+ fstrcpy(state->response.data.name.dom_name, dom_name);
+ fstrcpy(state->response.data.name.name, name);
+ state->response.data.name.type = type;
+
+ TALLOC_FREE(dom_name);
+ TALLOC_FREE(name);
+ return WINBINDD_OK;
+}
+
+/********************************************************************
+ This is the second callback after contacting the forest root
+********************************************************************/
+
+struct lookupname_state {
+ char *dom_name;
+ char *name;
+ void *caller_private_data;
+};
+
+
+static void lookupname_recv2(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, const DOM_SID *sid,
+ enum lsa_SidType type) =
+ (void (*)(void *, BOOL, const DOM_SID *, enum lsa_SidType))c;
+ DOM_SID sid;
+ struct lookupname_state *s = talloc_get_type_abort( private_data,
+ struct lookupname_state );
+
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger lookup_name\n"));
+ cont(s->caller_private_data, False, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("lookup_name returned an error\n"));
+ cont(s->caller_private_data, False, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ if (!string_to_sid(&sid, response->data.sid.sid)) {
+ DEBUG(0, ("Could not convert string %s to sid\n",
+ response->data.sid.sid));
+ cont(s->caller_private_data, False, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ cont(s->caller_private_data, True, &sid,
+ (enum lsa_SidType)response->data.sid.type);
+}
+
+/********************************************************************
+ This is the first callback after contacting our own domain
+********************************************************************/
+
+static void lookupname_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, const DOM_SID *sid,
+ enum lsa_SidType type) =
+ (void (*)(void *, BOOL, const DOM_SID *, enum lsa_SidType))c;
+ DOM_SID sid;
+ struct lookupname_state *s = talloc_get_type_abort( private_data,
+ struct lookupname_state );
+
+ if (!success) {
+ DEBUG(5, ("lookupname_recv: lookup_name() failed!\n"));
+ cont(s->caller_private_data, False, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ /* Try again using the forest root */
+ struct winbindd_domain *root_domain = find_root_domain();
+ struct winbindd_request request;
+
+ if ( !root_domain ) {
+ DEBUG(5,("lookupname_recv: unable to determine forest root\n"));
+ cont(s->caller_private_data, False, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_LOOKUPNAME;
+
+ fstrcpy( request.data.name.dom_name, s->dom_name );
+ fstrcpy( request.data.name.name, s->name );
+
+ do_async_domain(mem_ctx, root_domain, &request, lookupname_recv2,
+ (void *)cont, s);
+
+ return;
+ }
+
+ if (!string_to_sid(&sid, response->data.sid.sid)) {
+ DEBUG(0, ("Could not convert string %s to sid\n",
+ response->data.sid.sid));
+ cont(s->caller_private_data, False, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ cont(s->caller_private_data, True, &sid,
+ (enum lsa_SidType)response->data.sid.type);
+}
+
+/********************************************************************
+ The lookup name call first contacts a DC in its own domain
+ and fallbacks to contact a DC in the forest in our domain doesn't
+ know the name.
+********************************************************************/
+
+void winbindd_lookupname_async(TALLOC_CTX *mem_ctx,
+ const char *dom_name, const char *name,
+ void (*cont)(void *private_data, BOOL success,
+ const DOM_SID *sid,
+ enum lsa_SidType type),
+ enum winbindd_cmd orig_cmd,
+ void *private_data)
+{
+ struct winbindd_request request;
+ struct winbindd_domain *domain;
+ struct lookupname_state *s;
+
+ if ( (domain = find_lookup_domain_from_name(dom_name)) == NULL ) {
+ DEBUG(5, ("Could not find domain for name %s\n", dom_name));
+ cont(private_data, False, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_LOOKUPNAME;
+ request.original_cmd = orig_cmd;
+ fstrcpy(request.data.name.dom_name, dom_name);
+ fstrcpy(request.data.name.name, name);
+
+ if ( (s = TALLOC_ZERO_P(mem_ctx, struct lookupname_state)) == NULL ) {
+ DEBUG(0, ("winbindd_lookupname_async: talloc failed\n"));
+ cont(private_data, False, NULL, SID_NAME_UNKNOWN);
+ return;
+ }
+
+ s->dom_name = talloc_strdup( s, dom_name );
+ s->name = talloc_strdup( s, name );
+ s->caller_private_data = private_data;
+
+ do_async_domain(mem_ctx, domain, &request, lookupname_recv,
+ (void *)cont, s);
+}
+
+enum winbindd_result winbindd_dual_lookupname(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ enum lsa_SidType type;
+ char *name_domain, *name_user;
+ DOM_SID sid;
+ char *p;
+
+ /* Ensure null termination */
+ state->request.data.name.dom_name[sizeof(state->request.data.name.dom_name)-1]='\0';
+
+ /* Ensure null termination */
+ state->request.data.name.name[sizeof(state->request.data.name.name)-1]='\0';
+
+ /* cope with the name being a fully qualified name */
+ p = strstr(state->request.data.name.name, lp_winbind_separator());
+ if (p) {
+ *p = 0;
+ name_domain = state->request.data.name.name;
+ name_user = p+1;
+ } else {
+ name_domain = state->request.data.name.dom_name;
+ name_user = state->request.data.name.name;
+ }
+
+ DEBUG(3, ("[%5lu]: lookupname %s%s%s\n", (unsigned long)state->pid,
+ name_domain, lp_winbind_separator(), name_user));
+
+ /* Lookup name from DC using lsa_lookup_names() */
+ if (!winbindd_lookup_sid_by_name(state->mem_ctx, state->request.original_cmd, domain, name_domain,
+ name_user, &sid, &type)) {
+ return WINBINDD_ERROR;
+ }
+
+ sid_to_string(state->response.data.sid.sid, &sid);
+ state->response.data.sid.type = type;
+
+ return WINBINDD_OK;
+}
+
+BOOL print_sidlist(TALLOC_CTX *mem_ctx, const DOM_SID *sids,
+ size_t num_sids, char **result, ssize_t *len)
+{
+ size_t i;
+ size_t buflen = 0;
+
+ *len = 0;
+ *result = NULL;
+ for (i=0; i<num_sids; i++) {
+ sprintf_append(mem_ctx, result, len, &buflen,
+ "%s\n", sid_string_static(&sids[i]));
+ }
+
+ if ((num_sids != 0) && (*result == NULL)) {
+ return False;
+ }
+
+ return True;
+}
+
+static BOOL parse_sidlist(TALLOC_CTX *mem_ctx, char *sidstr,
+ DOM_SID **sids, size_t *num_sids)
+{
+ char *p, *q;
+
+ p = sidstr;
+ if (p == NULL)
+ return False;
+
+ while (p[0] != '\0') {
+ DOM_SID sid;
+ q = strchr(p, '\n');
+ if (q == NULL) {
+ DEBUG(0, ("Got invalid sidstr: %s\n", p));
+ return False;
+ }
+ *q = '\0';
+ q += 1;
+ if (!string_to_sid(&sid, p)) {
+ DEBUG(0, ("Could not parse sid %s\n", p));
+ return False;
+ }
+ if (!add_sid_to_array(mem_ctx, &sid, sids, num_sids)) {
+ return False;
+ }
+ p = q;
+ }
+ return True;
+}
+
+static BOOL parse_ridlist(TALLOC_CTX *mem_ctx, char *ridstr,
+ uint32 **rids, size_t *num_rids)
+{
+ char *p;
+
+ p = ridstr;
+ if (p == NULL)
+ return False;
+
+ while (p[0] != '\0') {
+ uint32 rid;
+ char *q;
+ rid = strtoul(p, &q, 10);
+ if (*q != '\n') {
+ DEBUG(0, ("Got invalid ridstr: %s\n", p));
+ return False;
+ }
+ p = q+1;
+ ADD_TO_ARRAY(mem_ctx, uint32, rid, rids, num_rids);
+ }
+ return True;
+}
+
+enum winbindd_result winbindd_dual_lookuprids(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ uint32 *rids = NULL;
+ size_t i, buflen, num_rids = 0;
+ ssize_t len;
+ DOM_SID domain_sid;
+ char *domain_name;
+ char **names;
+ enum lsa_SidType *types;
+ NTSTATUS status;
+ char *result;
+
+ DEBUG(10, ("Looking up RIDs for domain %s (%s)\n",
+ state->request.domain_name,
+ state->request.data.sid));
+
+ if (!parse_ridlist(state->mem_ctx, state->request.extra_data.data,
+ &rids, &num_rids)) {
+ DEBUG(5, ("Could not parse ridlist\n"));
+ return WINBINDD_ERROR;
+ }
+
+ if (!string_to_sid(&domain_sid, state->request.data.sid)) {
+ DEBUG(5, ("Could not parse domain sid %s\n",
+ state->request.data.sid));
+ return WINBINDD_ERROR;
+ }
+
+ status = domain->methods->rids_to_names(domain, state->mem_ctx,
+ &domain_sid, rids, num_rids,
+ &domain_name,
+ &names, &types);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) {
+ return WINBINDD_ERROR;
+ }
+
+ len = 0;
+ buflen = 0;
+ result = NULL;
+
+ for (i=0; i<num_rids; i++) {
+ sprintf_append(state->mem_ctx, &result, &len, &buflen,
+ "%d %s\n", types[i], names[i]);
+ }
+
+ fstrcpy(state->response.data.domain_name, domain_name);
+
+ if (result != NULL) {
+ state->response.extra_data.data = SMB_STRDUP(result);
+ if (!state->response.extra_data.data) {
+ return WINBINDD_ERROR;
+ }
+ state->response.length += len+1;
+ }
+
+ return WINBINDD_OK;
+}
+
+static void getsidaliases_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ,
+ DOM_SID *aliases, size_t num_aliases) =
+ (void (*)(void *, BOOL, DOM_SID *, size_t))c;
+ char *aliases_str;
+ DOM_SID *sids = NULL;
+ size_t num_sids = 0;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger getsidaliases\n"));
+ cont(private_data, success, NULL, 0);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("getsidaliases returned an error\n"));
+ cont(private_data, False, NULL, 0);
+ return;
+ }
+
+ aliases_str = (char *)response->extra_data.data;
+
+ if (aliases_str == NULL) {
+ DEBUG(10, ("getsidaliases return 0 SIDs\n"));
+ cont(private_data, True, NULL, 0);
+ return;
+ }
+
+ if (!parse_sidlist(mem_ctx, aliases_str, &sids, &num_sids)) {
+ DEBUG(0, ("Could not parse sids\n"));
+ cont(private_data, False, NULL, 0);
+ return;
+ }
+
+ SAFE_FREE(response->extra_data.data);
+
+ cont(private_data, True, sids, num_sids);
+}
+
+void winbindd_getsidaliases_async(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sids, size_t num_sids,
+ void (*cont)(void *private_data,
+ BOOL success,
+ const DOM_SID *aliases,
+ size_t num_aliases),
+ void *private_data)
+{
+ struct winbindd_request request;
+ char *sidstr = NULL;
+ ssize_t len;
+
+ if (num_sids == 0) {
+ cont(private_data, True, NULL, 0);
+ return;
+ }
+
+ if (!print_sidlist(mem_ctx, sids, num_sids, &sidstr, &len)) {
+ cont(private_data, False, NULL, 0);
+ return;
+ }
+
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_GETSIDALIASES;
+ request.extra_len = len;
+ request.extra_data.data = sidstr;
+
+ do_async_domain(mem_ctx, domain, &request, getsidaliases_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_getsidaliases(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DOM_SID *sids = NULL;
+ size_t num_sids = 0;
+ char *sidstr = NULL;
+ ssize_t len;
+ size_t i;
+ uint32 num_aliases;
+ uint32 *alias_rids;
+ NTSTATUS result;
+
+ DEBUG(3, ("[%5lu]: getsidaliases\n", (unsigned long)state->pid));
+
+ sidstr = state->request.extra_data.data;
+ if (sidstr == NULL) {
+ sidstr = talloc_strdup(state->mem_ctx, "\n"); /* No SID */
+ if (!sidstr) {
+ DEBUG(0, ("Out of memory\n"));
+ return WINBINDD_ERROR;
+ }
+ }
+
+ DEBUG(10, ("Sidlist: %s\n", sidstr));
+
+ if (!parse_sidlist(state->mem_ctx, sidstr, &sids, &num_sids)) {
+ DEBUG(0, ("Could not parse SID list: %s\n", sidstr));
+ return WINBINDD_ERROR;
+ }
+
+ num_aliases = 0;
+ alias_rids = NULL;
+
+ result = domain->methods->lookup_useraliases(domain,
+ state->mem_ctx,
+ num_sids, sids,
+ &num_aliases,
+ &alias_rids);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(3, ("Could not lookup_useraliases: %s\n",
+ nt_errstr(result)));
+ return WINBINDD_ERROR;
+ }
+
+ num_sids = 0;
+ sids = NULL;
+ sidstr = NULL;
+
+ DEBUG(10, ("Got %d aliases\n", num_aliases));
+
+ for (i=0; i<num_aliases; i++) {
+ DOM_SID sid;
+ DEBUGADD(10, (" rid %d\n", alias_rids[i]));
+ sid_copy(&sid, &domain->sid);
+ sid_append_rid(&sid, alias_rids[i]);
+ if (!add_sid_to_array(state->mem_ctx, &sid, &sids, &num_sids)) {
+ return WINBINDD_ERROR;
+ }
+ }
+
+
+ if (!print_sidlist(state->mem_ctx, sids, num_sids, &sidstr, &len)) {
+ DEBUG(0, ("Could not print_sidlist\n"));
+ state->response.extra_data.data = NULL;
+ return WINBINDD_ERROR;
+ }
+
+ state->response.extra_data.data = NULL;
+
+ if (sidstr) {
+ state->response.extra_data.data = SMB_STRDUP(sidstr);
+ if (!state->response.extra_data.data) {
+ DEBUG(0, ("Out of memory\n"));
+ return WINBINDD_ERROR;
+ }
+ DEBUG(10, ("aliases_list: %s\n",
+ (char *)state->response.extra_data.data));
+ state->response.length += len+1;
+ }
+
+ return WINBINDD_OK;
+}
+
+struct gettoken_state {
+ TALLOC_CTX *mem_ctx;
+ DOM_SID user_sid;
+ struct winbindd_domain *alias_domain;
+ struct winbindd_domain *local_alias_domain;
+ struct winbindd_domain *builtin_domain;
+ DOM_SID *sids;
+ size_t num_sids;
+ void (*cont)(void *private_data, BOOL success, DOM_SID *sids, size_t num_sids);
+ void *private_data;
+};
+
+static void gettoken_recvdomgroups(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data);
+static void gettoken_recvaliases(void *private_data, BOOL success,
+ const DOM_SID *aliases,
+ size_t num_aliases);
+
+
+void winbindd_gettoken_async(TALLOC_CTX *mem_ctx, const DOM_SID *user_sid,
+ void (*cont)(void *private_data, BOOL success,
+ DOM_SID *sids, size_t num_sids),
+ void *private_data)
+{
+ struct winbindd_domain *domain;
+ struct winbindd_request request;
+ struct gettoken_state *state;
+
+ state = TALLOC_ZERO_P(mem_ctx, struct gettoken_state);
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ cont(private_data, False, NULL, 0);
+ return;
+ }
+
+ state->mem_ctx = mem_ctx;
+ sid_copy(&state->user_sid, user_sid);
+ state->alias_domain = find_our_domain();
+ state->local_alias_domain = find_domain_from_name( get_global_sam_name() );
+ state->builtin_domain = find_builtin_domain();
+ state->cont = cont;
+ state->private_data = private_data;
+
+ domain = find_domain_from_sid_noinit(user_sid);
+ if (domain == NULL) {
+ DEBUG(5, ("Could not find domain from SID %s\n",
+ sid_string_static(user_sid)));
+ cont(private_data, False, NULL, 0);
+ return;
+ }
+
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_GETUSERDOMGROUPS;
+ fstrcpy(request.data.sid, sid_string_static(user_sid));
+
+ do_async_domain(mem_ctx, domain, &request, gettoken_recvdomgroups,
+ NULL, state);
+}
+
+static void gettoken_recvdomgroups(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ struct gettoken_state *state =
+ talloc_get_type_abort(private_data, struct gettoken_state);
+ char *sids_str;
+
+ if (!success) {
+ DEBUG(10, ("Could not get domain groups\n"));
+ state->cont(state->private_data, False, NULL, 0);
+ return;
+ }
+
+ sids_str = (char *)response->extra_data.data;
+
+ if (sids_str == NULL) {
+ /* This could be normal if we are dealing with a
+ local user and local groups */
+
+ if ( !sid_check_is_in_our_domain( &state->user_sid ) ) {
+ DEBUG(10, ("Received no domain groups\n"));
+ state->cont(state->private_data, True, NULL, 0);
+ return;
+ }
+ }
+
+ state->sids = NULL;
+ state->num_sids = 0;
+
+ if (!add_sid_to_array(mem_ctx, &state->user_sid, &state->sids,
+ &state->num_sids)) {
+ DEBUG(0, ("Out of memory\n"));
+ state->cont(state->private_data, False, NULL, 0);
+ return;
+ }
+
+ if (sids_str && !parse_sidlist(mem_ctx, sids_str, &state->sids,
+ &state->num_sids)) {
+ DEBUG(0, ("Could not parse sids\n"));
+ state->cont(state->private_data, False, NULL, 0);
+ return;
+ }
+
+ SAFE_FREE(response->extra_data.data);
+
+ if (state->alias_domain == NULL) {
+ DEBUG(10, ("Don't expand domain local groups\n"));
+ state->cont(state->private_data, True, state->sids,
+ state->num_sids);
+ return;
+ }
+
+ winbindd_getsidaliases_async(state->alias_domain, mem_ctx,
+ state->sids, state->num_sids,
+ gettoken_recvaliases, state);
+}
+
+static void gettoken_recvaliases(void *private_data, BOOL success,
+ const DOM_SID *aliases,
+ size_t num_aliases)
+{
+ struct gettoken_state *state = (struct gettoken_state *)private_data;
+ size_t i;
+
+ if (!success) {
+ DEBUG(10, ("Could not receive domain local groups\n"));
+ state->cont(state->private_data, False, NULL, 0);
+ return;
+ }
+
+ for (i=0; i<num_aliases; i++) {
+ if (!add_sid_to_array(state->mem_ctx, &aliases[i],
+ &state->sids, &state->num_sids)) {
+ DEBUG(0, ("Out of memory\n"));
+ state->cont(state->private_data, False, NULL, 0);
+ return;
+ }
+ }
+
+ if (state->local_alias_domain != NULL) {
+ struct winbindd_domain *local_domain = state->local_alias_domain;
+ DEBUG(10, ("Expanding our own local groups\n"));
+ state->local_alias_domain = NULL;
+ winbindd_getsidaliases_async(local_domain, state->mem_ctx,
+ state->sids, state->num_sids,
+ gettoken_recvaliases, state);
+ return;
+ }
+
+ if (state->builtin_domain != NULL) {
+ struct winbindd_domain *builtin_domain = state->builtin_domain;
+ DEBUG(10, ("Expanding our own BUILTIN groups\n"));
+ state->builtin_domain = NULL;
+ winbindd_getsidaliases_async(builtin_domain, state->mem_ctx,
+ state->sids, state->num_sids,
+ gettoken_recvaliases, state);
+ return;
+ }
+
+ state->cont(state->private_data, True, state->sids, state->num_sids);
+}
+
+static void query_user_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, const char *acct_name,
+ const char *full_name, const char *homedir,
+ const char *shell, uint32 gid, uint32 group_rid) =
+ (void (*)(void *, BOOL, const char *, const char *,
+ const char *, const char *, uint32, uint32))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger query_user\n"));
+ cont(private_data, False, NULL, NULL, NULL, NULL, -1, -1);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("query_user returned an error\n"));
+ cont(private_data, False, NULL, NULL, NULL, NULL, -1, -1);
+ return;
+ }
+
+ cont(private_data, True, response->data.user_info.acct_name,
+ response->data.user_info.full_name,
+ response->data.user_info.homedir,
+ response->data.user_info.shell,
+ response->data.user_info.primary_gid,
+ response->data.user_info.group_rid);
+}
+
+void query_user_async(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
+ const DOM_SID *sid,
+ void (*cont)(void *private_data, BOOL success,
+ const char *acct_name,
+ const char *full_name,
+ const char *homedir,
+ const char *shell,
+ gid_t gid,
+ uint32 group_rid),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_USERINFO;
+ sid_to_string(request.data.sid, sid);
+ do_async_domain(mem_ctx, domain, &request, query_user_recv,
+ (void *)cont, private_data);
+}
+
+/* The following uid2sid/gid2sid functions has been contributed by
+ * Keith Reynolds <Keith.Reynolds@centrify.com> */
+
+static void winbindd_uid2sid_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, const char *sid) =
+ (void (*)(void *, BOOL, const char *))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger uid2sid\n"));
+ cont(private_data, False, NULL);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("uid2sid returned an error\n"));
+ cont(private_data, False, NULL);
+ return;
+ }
+
+ cont(private_data, True, response->data.sid.sid);
+}
+
+void winbindd_uid2sid_async(TALLOC_CTX *mem_ctx, uid_t uid,
+ void (*cont)(void *private_data, BOOL success, const char *sid),
+ void *private_data)
+{
+ struct winbindd_request request;
+
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_UID2SID;
+ request.data.uid = uid;
+ do_async(mem_ctx, idmap_child(), &request, winbindd_uid2sid_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_uid2sid(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DOM_SID sid;
+ NTSTATUS result;
+
+ DEBUG(3,("[%5lu]: uid to sid %lu\n",
+ (unsigned long)state->pid,
+ (unsigned long) state->request.data.uid));
+
+ /* Find sid for this uid and return it, possibly ask the slow remote idmap */
+ result = idmap_uid_to_sid(&sid, state->request.data.uid);
+
+ if (NT_STATUS_IS_OK(result)) {
+ sid_to_string(state->response.data.sid.sid, &sid);
+ state->response.data.sid.type = SID_NAME_USER;
+ return WINBINDD_OK;
+ }
+
+ return WINBINDD_ERROR;
+}
+
+static void winbindd_gid2sid_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ, const char *sid) =
+ (void (*)(void *, BOOL, const char *))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger gid2sid\n"));
+ cont(private_data, False, NULL);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("gid2sid returned an error\n"));
+ cont(private_data, False, NULL);
+ return;
+ }
+
+ cont(private_data, True, response->data.sid.sid);
+}
+
+void winbindd_gid2sid_async(TALLOC_CTX *mem_ctx, gid_t gid,
+ void (*cont)(void *private_data, BOOL success, const char *sid),
+ void *private_data)
+{
+ struct winbindd_request request;
+
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_GID2SID;
+ request.data.gid = gid;
+ do_async(mem_ctx, idmap_child(), &request, winbindd_gid2sid_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_gid2sid(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DOM_SID sid;
+ NTSTATUS result;
+
+ DEBUG(3,("[%5lu]: gid %lu to sid\n",
+ (unsigned long)state->pid,
+ (unsigned long) state->request.data.gid));
+
+ /* Find sid for this gid and return it, possibly ask the slow remote idmap */
+ result = idmap_gid_to_sid(&sid, state->request.data.gid);
+
+ if (NT_STATUS_IS_OK(result)) {
+ sid_to_string(state->response.data.sid.sid, &sid);
+ DEBUG(10, ("[%5lu]: retrieved sid: %s\n",
+ (unsigned long)state->pid,
+ state->response.data.sid.sid));
+ state->response.data.sid.type = SID_NAME_DOM_GRP;
+ return WINBINDD_OK;
+ }
+
+ return WINBINDD_ERROR;
+}
+
+static void winbindd_dump_id_maps_recv(TALLOC_CTX *mem_ctx, BOOL success,
+ struct winbindd_response *response,
+ void *c, void *private_data)
+{
+ void (*cont)(void *priv, BOOL succ) =
+ (void (*)(void *, BOOL))c;
+
+ if (!success) {
+ DEBUG(5, ("Could not trigger a map dump\n"));
+ cont(private_data, False);
+ return;
+ }
+
+ if (response->result != WINBINDD_OK) {
+ DEBUG(5, ("idmap dump maps returned an error\n"));
+ cont(private_data, False);
+ return;
+ }
+
+ cont(private_data, True);
+}
+
+void winbindd_dump_maps_async(TALLOC_CTX *mem_ctx, void *data, int size,
+ void (*cont)(void *private_data, BOOL success),
+ void *private_data)
+{
+ struct winbindd_request request;
+ ZERO_STRUCT(request);
+ request.cmd = WINBINDD_DUAL_DUMP_MAPS;
+ request.extra_data.data = (char *)data;
+ request.extra_len = size;
+ do_async(mem_ctx, idmap_child(), &request, winbindd_dump_id_maps_recv,
+ (void *)cont, private_data);
+}
+
+enum winbindd_result winbindd_dual_dump_maps(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: dual dump maps\n", (unsigned long)state->pid));
+
+ idmap_dump_maps((char *)state->request.extra_data.data);
+
+ return WINBINDD_OK;
+}
+
diff --git a/source3/winbindd/winbindd_cache.c b/source3/winbindd/winbindd_cache.c
new file mode 100644
index 0000000000..e5090dfacf
--- /dev/null
+++ b/source3/winbindd/winbindd_cache.c
@@ -0,0 +1,3892 @@
+/*
+ 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 "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+#define WINBINDD_CACHE_VERSION 1
+#define WINBINDD_CACHE_VERSION_KEYSTR "WINBINDD_CACHE_VERSION"
+
+extern struct winbindd_methods reconnect_methods;
+extern BOOL opt_nocache;
+#ifdef HAVE_ADS
+extern struct winbindd_methods ads_methods;
+#endif
+
+/*
+ * 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/",
+ "DR/",
+ "DE/",
+ "WINBINDD_OFFLINE",
+ WINBINDD_CACHE_VERSION_KEYSTR,
+ NULL
+};
+
+/************************************************************************
+ 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;
+}
+
+/* Global online/offline state - False when online. winbindd starts up online
+ and sets this to true if the first query fails and there's an entry in
+ the cache tdb telling us to stay offline. */
+
+static BOOL global_winbindd_offline_state;
+
+struct winbind_cache {
+ TDB_CONTEXT *tdb;
+};
+
+struct cache_entry {
+ NTSTATUS status;
+ uint32 sequence_number;
+ uint8 *data;
+ uint32 len, ofs;
+};
+
+void (*smb_panic_fn)(const char *const why) = smb_panic;
+
+#define WINBINDD_MAX_CACHE_SIZE (50*1024*1024)
+
+static struct winbind_cache *wcache;
+
+void winbindd_check_cache_size(time_t t)
+{
+ static time_t last_check_time;
+ struct stat st;
+
+ if (last_check_time == (time_t)0)
+ last_check_time = t;
+
+ if (t - last_check_time < 60 && t - last_check_time > 0)
+ return;
+
+ if (wcache == NULL || wcache->tdb == NULL) {
+ DEBUG(0, ("Unable to check size of tdb cache - cache not open !\n"));
+ return;
+ }
+
+ if (fstat(tdb_fd(wcache->tdb), &st) == -1) {
+ DEBUG(0, ("Unable to check size of tdb cache %s!\n", strerror(errno) ));
+ return;
+ }
+
+ if (st.st_size > WINBINDD_MAX_CACHE_SIZE) {
+ DEBUG(10,("flushing cache due to size (%lu) > (%lu)\n",
+ (unsigned long)st.st_size,
+ (unsigned long)WINBINDD_MAX_CACHE_SIZE));
+ wcache_flush_cache();
+ }
+}
+
+/* get the winbind_cache structure */
+static struct winbind_cache *get_cache(struct winbindd_domain *domain)
+{
+ struct winbind_cache *ret = wcache;
+
+ /* We have to know what type of domain we are dealing with first. */
+
+ if ( !domain->initialized ) {
+ init_dc_connection( domain );
+ }
+
+ /*
+ OK. listen up becasue I'm only going to say this once.
+ We have the following scenarios to consider
+ (a) trusted AD domains on a Samba DC,
+ (b) trusted AD domains and we are joined to a non-kerberos domain
+ (c) trusted AD domains and we are joined to a kerberos (AD) domain
+
+ For (a) we can always contact the trusted domain using krb5
+ since we have the domain trust account password
+
+ For (b) we can only use RPC since we have no way of
+ getting a krb5 ticket in our own domain
+
+ For (c) we can always use krb5 since we have a kerberos trust
+
+ --jerry
+ */
+
+ if (!domain->backend) {
+#ifdef HAVE_ADS
+ 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()) {
+ DEBUG(5,("get_cache: Setting ADS methods for domain %s\n", domain->name));
+ domain->backend = &ads_methods;
+ } else {
+#endif /* HAVE_ADS */
+ DEBUG(5,("get_cache: Setting MS-RPC methods for domain %s\n", domain->name));
+ domain->backend = &reconnect_methods;
+#ifdef HAVE_ADS
+ }
+#endif /* HAVE_ADS */
+ }
+
+ if (ret)
+ 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 uint32 from a cache entry
+*/
+static uint32 centry_uint32(struct cache_entry *centry)
+{
+ uint32 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 from a cache entry
+*/
+static uint16 centry_uint16(struct cache_entry *centry)
+{
+ uint16 ret;
+ if (!centry_check_bytes(centry, 2)) {
+ smb_panic_fn("centry_uint16");
+ }
+ ret = CVAL(centry->data, centry->ofs);
+ centry->ofs += 2;
+ return ret;
+}
+
+/*
+ pull a uint8 from a cache entry
+*/
+static uint8 centry_uint8(struct cache_entry *centry)
+{
+ uint8 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 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 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, TALLOC_CTX *mem_ctx, DOM_SID *sid)
+{
+ char *sid_string;
+ sid_string = centry_string(centry, mem_ctx);
+ if ((sid_string == NULL) || (!string_to_sid(sid, sid_string))) {
+ return False;
+ }
+ return True;
+}
+
+
+/*
+ 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;
+}
+
+static NTSTATUS fetch_cache_seqnum( struct winbindd_domain *domain, time_t now )
+{
+ TDB_DATA data;
+ fstring key;
+ uint32 time_diff;
+
+ if (!wcache->tdb) {
+ DEBUG(10,("fetch_cache_seqnum: tdb == NULL\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ fstr_sprintf( key, "SEQNUM/%s", domain->name );
+
+ data = tdb_fetch_bystring( wcache->tdb, key );
+ if ( !data.dptr || data.dsize!=8 ) {
+ DEBUG(10,("fetch_cache_seqnum: invalid data size key [%s]\n", key ));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ domain->sequence_number = IVAL(data.dptr, 0);
+ domain->last_seq_check = IVAL(data.dptr, 4);
+
+ SAFE_FREE(data.dptr);
+
+ /* have we expired? */
+
+ time_diff = now - domain->last_seq_check;
+ if ( time_diff > lp_winbind_cache_time() ) {
+ DEBUG(10,("fetch_cache_seqnum: timeout [%s][%u @ %u]\n",
+ domain->name, domain->sequence_number,
+ (uint32)domain->last_seq_check));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DEBUG(10,("fetch_cache_seqnum: success [%s][%u @ %u]\n",
+ domain->name, domain->sequence_number,
+ (uint32)domain->last_seq_check));
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS store_cache_seqnum( struct winbindd_domain *domain )
+{
+ TDB_DATA data;
+ fstring key_str;
+ uint8 buf[8];
+
+ if (!wcache->tdb) {
+ DEBUG(10,("store_cache_seqnum: tdb == NULL\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ fstr_sprintf( key_str, "SEQNUM/%s", domain->name );
+
+ SIVAL(buf, 0, domain->sequence_number);
+ SIVAL(buf, 4, domain->last_seq_check);
+ data.dptr = buf;
+ data.dsize = 8;
+
+ if ( tdb_store_bystring( wcache->tdb, key_str, data, TDB_REPLACE) == -1 ) {
+ DEBUG(10,("store_cache_seqnum: tdb_store fail key [%s]\n", key_str ));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DEBUG(10,("store_cache_seqnum: success [%s][%u @ %u]\n",
+ domain->name, domain->sequence_number,
+ (uint32)domain->last_seq_check));
+
+ return NT_STATUS_OK;
+}
+
+/*
+ refresh the domain sequence number. If force is True
+ then always refresh it, no matter how recently we fetched it
+*/
+
+static void refresh_sequence_number(struct winbindd_domain *domain, BOOL force)
+{
+ 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 );
+
+#if 0 /* JERRY -- disable as the default cache time is now 5 minutes */
+ /* trying to reconnect is expensive, don't do it too often */
+ if (domain->sequence_number == DOM_SEQUENCE_NONE) {
+ cache_time *= 8;
+ }
+#endif
+
+ time_diff = t - domain->last_seq_check;
+
+ /* see if we have to refetch the domain sequence number */
+ if (!force && (time_diff < cache_time)) {
+ 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) )
+ goto done;
+
+ /* important! make sure that we know if this is a native
+ mode domain or not. And that we can contact it. */
+
+ if ( winbindd_can_contact_domain( domain ) ) {
+ status = domain->backend->sequence_number(domain,
+ &domain->sequence_number);
+ } else {
+ /* just use the current time */
+ status = NT_STATUS_OK;
+ domain->sequence_number = time(NULL);
+ }
+
+
+ /* the above call could have set our domain->backend to NULL when
+ * coming from offline to online mode, make sure to reinitialize the
+ * backend - Guenther */
+ get_cache( domain );
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("refresh_sequence_number: failed with %s\n", nt_errstr(status)));
+ domain->sequence_number = DOM_SEQUENCE_NONE;
+ }
+
+ domain->last_status = status;
+ domain->last_seq_check = time(NULL);
+
+ /* save the new sequence number ni 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() && global_winbindd_offline_state) {
+ 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 then it is OK */
+ if (wcache_server_down(domain) ||
+ centry->sequence_number == domain->sequence_number) {
+ 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 < 8) {
+ /* huh? corrupt cache? */
+ DEBUG(10,("wcache_fetch_raw: Corrupt cache for key %s (len < 8) ?\n", kstr));
+ centry_free(centry);
+ return NULL;
+ }
+
+ centry->status = centry_ntstatus(centry);
+ centry->sequence_number = centry_uint32(centry);
+
+ return centry;
+}
+
+/*
+ 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;
+
+ if (opt_nocache) {
+ return NULL;
+ }
+
+ refresh_sequence_number(domain, False);
+
+ va_start(ap, format);
+ smb_xvasprintf(&kstr, format, ap);
+ va_end(ap);
+
+ 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;
+
+ va_start(ap, format);
+ smb_xvasprintf(&kstr, format, ap);
+ va_end(ap);
+
+ 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 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 uint32 into a centry
+*/
+static void centry_put_uint32(struct cache_entry *centry, uint32 v)
+{
+ centry_expand(centry, 4);
+ SIVAL(centry->data, centry->ofs, v);
+ centry->ofs += 4;
+}
+
+/*
+ push a uint16 into a centry
+*/
+static void centry_put_uint16(struct cache_entry *centry, uint16 v)
+{
+ centry_expand(centry, 2);
+ SIVAL(centry->data, centry->ofs, v);
+ centry->ofs += 2;
+}
+
+/*
+ push a uint8 into a centry
+*/
+static void centry_put_uint8(struct cache_entry *centry, uint8 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 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 DOM_SID *sid)
+{
+ fstring sid_string;
+ centry_put_string(centry, sid_to_string(sid_string, sid));
+}
+
+
+/*
+ put NTSTATUS into a centry
+*/
+static void centry_put_ntstatus(struct cache_entry *centry, NTSTATUS status)
+{
+ uint32 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()
+*/
+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, centry->len);
+ centry->ofs = 0;
+ centry->sequence_number = domain->sequence_number;
+ centry_put_ntstatus(centry, status);
+ centry_put_uint32(centry, centry->sequence_number);
+ 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;
+
+ if (opt_nocache) {
+ return;
+ }
+
+ va_start(ap, format);
+ smb_xvasprintf(&kstr, format, ap);
+ va_end(ap);
+
+ 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 DOM_SID *sid,
+ enum lsa_SidType type)
+{
+ struct cache_entry *centry;
+ fstring uname;
+
+ centry = centry_start(domain, status);
+ if (!centry)
+ return;
+ centry_put_uint32(centry, type);
+ centry_put_sid(centry, sid);
+ fstrcpy(uname, name);
+ strupper_m(uname);
+ centry_end(centry, "NS/%s/%s", domain_name, uname);
+ DEBUG(10,("wcache_save_name_to_sid: %s\\%s -> %s (%s)\n", domain_name, uname,
+ sid_string_static(sid), nt_errstr(status)));
+ centry_free(centry);
+}
+
+static void wcache_save_sid_to_name(struct winbindd_domain *domain, NTSTATUS status,
+ const DOM_SID *sid, const char *domain_name, const char *name, enum lsa_SidType type)
+{
+ struct cache_entry *centry;
+ fstring sid_string;
+
+ centry = centry_start(domain, status);
+ if (!centry)
+ return;
+
+ if (NT_STATUS_IS_OK(status)) {
+ centry_put_uint32(centry, type);
+ centry_put_string(centry, domain_name);
+ centry_put_string(centry, name);
+ }
+
+ centry_end(centry, "SN/%s", sid_to_string(sid_string, sid));
+ DEBUG(10,("wcache_save_sid_to_name: %s -> %s (%s)\n", sid_string,
+ name, nt_errstr(status)));
+ centry_free(centry);
+}
+
+
+static void wcache_save_user(struct winbindd_domain *domain, NTSTATUS status, WINBIND_USERINFO *info)
+{
+ struct cache_entry *centry;
+ fstring sid_string;
+
+ if (is_null_sid(&info->user_sid)) {
+ return;
+ }
+
+ centry = centry_start(domain, status);
+ if (!centry)
+ return;
+ centry_put_string(centry, info->acct_name);
+ centry_put_string(centry, info->full_name);
+ centry_put_string(centry, info->homedir);
+ centry_put_string(centry, info->shell);
+ centry_put_uint32(centry, info->primary_gid);
+ centry_put_sid(centry, &info->user_sid);
+ centry_put_sid(centry, &info->group_sid);
+ centry_end(centry, "U/%s", sid_to_string(sid_string, &info->user_sid));
+ DEBUG(10,("wcache_save_user: %s (acct_name %s)\n", sid_string, info->acct_name));
+ centry_free(centry);
+}
+
+static void wcache_save_lockout_policy(struct winbindd_domain *domain, NTSTATUS status, SAM_UNK_INFO_12 *lockout_policy)
+{
+ struct cache_entry *centry;
+
+ centry = centry_start(domain, status);
+ if (!centry)
+ return;
+
+ centry_put_nttime(centry, lockout_policy->duration);
+ centry_put_nttime(centry, lockout_policy->reset_count);
+ centry_put_uint16(centry, lockout_policy->bad_attempt_lockout);
+
+ 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, SAM_UNK_INFO_1 *policy)
+{
+ struct cache_entry *centry;
+
+ centry = centry_start(domain, status);
+ if (!centry)
+ return;
+
+ centry_put_uint16(centry, policy->min_length_password);
+ centry_put_uint16(centry, policy->password_history);
+ centry_put_uint32(centry, policy->password_properties);
+ centry_put_nttime(centry, policy->expire);
+ centry_put_nttime(centry, policy->min_passwordage);
+
+ centry_end(centry, "PWD_POL/%s", domain->name);
+
+ DEBUG(10,("wcache_save_password_policy: %s\n", domain->name));
+
+ centry_free(centry);
+}
+
+NTSTATUS wcache_cached_creds_exist(struct winbindd_domain *domain, const DOM_SID *sid)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ TDB_DATA data;
+ fstring key_str;
+ uint32 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", sid_string_static(sid));
+
+ data = tdb_fetch(cache->tdb, string_tdb_data(key_str));
+ if (!data.dptr) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ SAFE_FREE(data.dptr);
+ 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 DOM_SID *sid,
+ const uint8 **cached_nt_pass,
+ const uint8 **cached_salt)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ time_t t;
+ uint32 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;
+ }
+
+ /* Try and get a salted cred first. If we can't
+ fall back to an unsalted cred. */
+
+ centry = wcache_fetch(cache, domain, "CRED/%s", sid_string_static(sid));
+ if (!centry) {
+ DEBUG(10,("wcache_get_creds: entry for [CRED/%s] not found\n",
+ sid_string_static(sid)));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ t = 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 *)centry_hash16(centry, mem_ctx);
+ if (*cached_nt_pass == NULL) {
+ const char *sidstr = sid_string_static(sid);
+
+ /* 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));
+ wcache_delete("CRED/%s", sidstr);
+ 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 *)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",
+ sid_string_static(sid), 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,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ const uint8 nt_pass[NT_HASH_LEN])
+{
+ struct cache_entry *centry;
+ fstring sid_string;
+ uint32 rid;
+ uint8 cred_salt[NT_HASH_LEN];
+ uint8 salted_hash[NT_HASH_LEN];
+
+ 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);
+ E_md5hash(cred_salt, nt_pass, salted_hash);
+
+ centry_put_hash16(centry, salted_hash);
+ centry_put_hash16(centry, cred_salt);
+ centry_end(centry, "CRED/%s", sid_to_string(sid_string, sid));
+
+ DEBUG(10,("wcache_save_creds: %s\n", sid_string));
+
+ centry_free(centry);
+
+ return NT_STATUS_OK;
+}
+
+
+/* Query display info. This is the basic user list fn */
+static NTSTATUS query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ WINBIND_USERINFO **info)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i, retry;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "UL/%s", domain->name);
+ if (!centry)
+ goto do_query;
+
+ *num_entries = centry_uint32(centry);
+
+ if (*num_entries == 0)
+ goto do_cached;
+
+ (*info) = TALLOC_ARRAY(mem_ctx, WINBIND_USERINFO, *num_entries);
+ if (! (*info)) {
+ smb_panic_fn("query_user_list out of memory");
+ }
+ for (i=0; i<(*num_entries); i++) {
+ (*info)[i].acct_name = centry_string(centry, mem_ctx);
+ (*info)[i].full_name = centry_string(centry, mem_ctx);
+ (*info)[i].homedir = centry_string(centry, mem_ctx);
+ (*info)[i].shell = centry_string(centry, mem_ctx);
+ centry_sid(centry, mem_ctx, &(*info)[i].user_sid);
+ centry_sid(centry, mem_ctx, &(*info)[i].group_sid);
+ }
+
+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:
+ *num_entries = 0;
+ *info = NULL;
+
+ /* Return status value returned by seq number check */
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ /* 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 ));
+
+ status = domain->backend->query_user_list(domain, mem_ctx, num_entries, info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("query_user_list: returned 0x%08x, "
+ "retrying\n", NT_STATUS_V(status)));
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) {
+ DEBUG(3, ("query_user_list: flushing "
+ "connection cache\n"));
+ invalidate_cm_connection(&domain->conn);
+ }
+
+ } while (NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_UNSUCCESSFUL) &&
+ (retry++ < 5));
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ 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].full_name);
+ centry_put_string(centry, (*info)[i].homedir);
+ centry_put_string(centry, (*info)[i].shell);
+ centry_put_sid(centry, &(*info)[i].user_sid);
+ centry_put_sid(centry, &(*info)[i].group_sid);
+ if (domain->backend && domain->backend->consistent) {
+ /* when the backend is consistent we can pre-prime some mappings */
+ wcache_save_name_to_sid(domain, NT_STATUS_OK,
+ domain->name,
+ (*info)[i].acct_name,
+ &(*info)[i].user_sid,
+ SID_NAME_USER);
+ wcache_save_sid_to_name(domain, NT_STATUS_OK,
+ &(*info)[i].user_sid,
+ domain->name,
+ (*info)[i].acct_name,
+ SID_NAME_USER);
+ wcache_save_user(domain, NT_STATUS_OK, &(*info)[i]);
+ }
+ }
+ centry_end(centry, "UL/%s", domain->name);
+ centry_free(centry);
+
+skip_save:
+ return status;
+}
+
+/* list all domain groups */
+static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "GL/%s/domain", domain->name);
+ if (!centry)
+ goto do_query;
+
+ *num_entries = centry_uint32(centry);
+
+ if (*num_entries == 0)
+ goto do_cached;
+
+ (*info) = TALLOC_ARRAY(mem_ctx, struct acct_info, *num_entries);
+ if (! (*info)) {
+ smb_panic_fn("enum_dom_groups out of memory");
+ }
+ for (i=0; i<(*num_entries); i++) {
+ fstrcpy((*info)[i].acct_name, centry_string(centry, mem_ctx));
+ fstrcpy((*info)[i].acct_desc, centry_string(centry, mem_ctx));
+ (*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;
+
+ /* Return status value returned by seq number check */
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ 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);
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ 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 */
+static NTSTATUS enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "GL/%s/local", domain->name);
+ if (!centry)
+ goto do_query;
+
+ *num_entries = centry_uint32(centry);
+
+ if (*num_entries == 0)
+ goto do_cached;
+
+ (*info) = TALLOC_ARRAY(mem_ctx, struct acct_info, *num_entries);
+ if (! (*info)) {
+ smb_panic_fn("enum_dom_groups out of memory");
+ }
+ for (i=0; i<(*num_entries); i++) {
+ fstrcpy((*info)[i].acct_name, centry_string(centry, mem_ctx));
+ fstrcpy((*info)[i].acct_desc, centry_string(centry, mem_ctx));
+ (*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;
+
+ /* Return status value returned by seq number check */
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ 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);
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ 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;
+}
+
+/* convert a single name to a sid in a domain */
+static NTSTATUS name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ enum winbindd_cmd orig_cmd,
+ const char *domain_name,
+ const char *name,
+ DOM_SID *sid,
+ enum lsa_SidType *type)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ fstring uname;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ fstrcpy(uname, name);
+ strupper_m(uname);
+ centry = wcache_fetch(cache, domain, "NS/%s/%s", domain_name, uname);
+ if (!centry)
+ goto do_query;
+
+ status = centry->status;
+ if (NT_STATUS_IS_OK(status)) {
+ *type = (enum lsa_SidType)centry_uint32(centry);
+ centry_sid(centry, mem_ctx, sid);
+ }
+
+ DEBUG(10,("name_to_sid: [Cached] - cached name for domain %s status: %s\n",
+ domain->name, nt_errstr(status) ));
+
+ centry_free(centry);
+ return status;
+
+do_query:
+ ZERO_STRUCTP(sid);
+
+ /* If the seq number check indicated that there is a problem
+ * with this DC, then return that status... except for
+ * access_denied. This is special because the dc may be in
+ * "restrict anonymous = 1" mode, in which case it will deny
+ * most unauthenticated operations, but *will* allow the LSA
+ * name-to-sid that we try as a fallback. */
+
+ if (!(NT_STATUS_IS_OK(domain->last_status)
+ || NT_STATUS_EQUAL(domain->last_status, NT_STATUS_ACCESS_DENIED)))
+ return domain->last_status;
+
+ DEBUG(10,("name_to_sid: [Cached] - doing backend query for name for domain %s\n",
+ domain->name ));
+
+ status = domain->backend->name_to_sid(domain, mem_ctx, orig_cmd,
+ domain_name, name, sid, type);
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+
+ if (domain->online &&
+ (NT_STATUS_IS_OK(status) || NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))) {
+ wcache_save_name_to_sid(domain, status, domain_name, name, sid, *type);
+
+ /* Only save the reverse mapping if this was not a UPN */
+ if (!strchr(name, '@')) {
+ strupper_m(CONST_DISCARD(char *,domain_name));
+ strlower_m(CONST_DISCARD(char *,name));
+ wcache_save_sid_to_name(domain, status, sid, domain_name, name, *type);
+ }
+ }
+
+ return status;
+}
+
+/* convert a sid to a user or group name. The sid is guaranteed to be in the domain
+ given */
+static NTSTATUS sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ fstring sid_string;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "SN/%s", sid_to_string(sid_string, sid));
+ if (!centry)
+ goto do_query;
+
+ status = centry->status;
+ if (NT_STATUS_IS_OK(status)) {
+ *type = (enum lsa_SidType)centry_uint32(centry);
+ *domain_name = centry_string(centry, mem_ctx);
+ *name = centry_string(centry, mem_ctx);
+ }
+
+ DEBUG(10,("sid_to_name: [Cached] - cached name for domain %s status: %s\n",
+ domain->name, nt_errstr(status) ));
+
+ centry_free(centry);
+ return status;
+
+do_query:
+ *name = NULL;
+ *domain_name = NULL;
+
+ /* If the seq number check indicated that there is a problem
+ * with this DC, then return that status... except for
+ * access_denied. This is special because the dc may be in
+ * "restrict anonymous = 1" mode, in which case it will deny
+ * most unauthenticated operations, but *will* allow the LSA
+ * sid-to-name that we try as a fallback. */
+
+ if (!(NT_STATUS_IS_OK(domain->last_status)
+ || NT_STATUS_EQUAL(domain->last_status, NT_STATUS_ACCESS_DENIED)))
+ return domain->last_status;
+
+ DEBUG(10,("sid_to_name: [Cached] - doing backend query for name for domain %s\n",
+ domain->name ));
+
+ status = domain->backend->sid_to_name(domain, mem_ctx, sid, domain_name, name, type);
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ 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;
+}
+
+static NTSTATUS rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *domain_sid,
+ uint32 *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;
+
+ *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++) {
+ DOM_SID sid;
+ struct cache_entry *centry;
+
+ if (!sid_compose(&sid, domain_sid, rids[i])) {
+ result = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ centry = wcache_fetch(cache, domain, "SN/%s",
+ sid_string_static(&sid));
+ if (!centry) {
+ goto do_query;
+ }
+
+ (*types)[i] = SID_NAME_UNKNOWN;
+ (*names)[i] = talloc_strdup(*names, "");
+
+ if (NT_STATUS_IS_OK(centry->status)) {
+ char *dom;
+ have_mapped = True;
+ (*types)[i] = (enum lsa_SidType)centry_uint32(centry);
+
+ dom = centry_string(centry, mem_ctx);
+ if (*domain_name == NULL) {
+ *domain_name = dom;
+ } else {
+ talloc_free(dom);
+ }
+
+ (*names)[i] = centry_string(centry, *names);
+
+ } else if (NT_STATUS_EQUAL(centry->status, NT_STATUS_NONE_MAPPED)) {
+ have_unmapped = True;
+
+ } else {
+ /* something's definitely wrong */
+ result = centry->status;
+ goto error;
+ }
+
+ centry_free(centry);
+ }
+
+ 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);
+
+ /*
+ 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++) {
+ 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, False);
+
+ for (i=0; i<num_rids; i++) {
+ 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;
+}
+
+/* Lookup user information from a rid */
+static NTSTATUS query_user(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ WINBIND_USERINFO *info)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "U/%s", sid_string_static(user_sid));
+
+ /* If we have an access denied cache entry and a cached info3 in the
+ samlogon cache then do a query. This will force the rpc back end
+ to return the info3 data. */
+
+ if (NT_STATUS_V(domain->last_status) == NT_STATUS_V(NT_STATUS_ACCESS_DENIED) &&
+ netsamlogon_cache_have(user_sid)) {
+ DEBUG(10, ("query_user: cached access denied and have cached info3\n"));
+ domain->last_status = NT_STATUS_OK;
+ centry_free(centry);
+ goto do_query;
+ }
+
+ if (!centry)
+ goto do_query;
+
+ /* 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->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->primary_gid = centry_uint32(centry);
+ centry_sid(centry, mem_ctx, &info->user_sid);
+ centry_sid(centry, mem_ctx, &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;
+
+do_query:
+ ZERO_STRUCTP(info);
+
+ /* Return status value returned by seq number check */
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ DEBUG(10,("query_user: [Cached] - doing backend query for info for domain %s\n",
+ domain->name ));
+
+ status = domain->backend->query_user(domain, mem_ctx, user_sid, info);
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ wcache_save_user(domain, status, info);
+
+ return status;
+}
+
+
+/* Lookup groups a user is a member of. */
+static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ uint32 *num_groups, DOM_SID **user_gids)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ fstring sid_string;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "UG/%s", sid_to_string(sid_string, user_sid));
+
+ /* If we have an access denied cache entry and a cached info3 in the
+ samlogon cache then do a query. This will force the rpc back end
+ to return the info3 data. */
+
+ if (NT_STATUS_V(domain->last_status) == NT_STATUS_V(NT_STATUS_ACCESS_DENIED) &&
+ netsamlogon_cache_have(user_sid)) {
+ DEBUG(10, ("lookup_usergroups: cached access denied and have cached info3\n"));
+ domain->last_status = NT_STATUS_OK;
+ centry_free(centry);
+ goto do_query;
+ }
+
+ if (!centry)
+ goto do_query;
+
+ *num_groups = centry_uint32(centry);
+
+ if (*num_groups == 0)
+ goto do_cached;
+
+ (*user_gids) = TALLOC_ARRAY(mem_ctx, DOM_SID, *num_groups);
+ if (! (*user_gids)) {
+ smb_panic_fn("lookup_usergroups out of memory");
+ }
+ for (i=0; i<(*num_groups); i++) {
+ centry_sid(centry, mem_ctx, &(*user_gids)[i]);
+ }
+
+do_cached:
+ status = centry->status;
+
+ DEBUG(10,("lookup_usergroups: [Cached] - cached info for domain %s status: %s\n",
+ domain->name, nt_errstr(status) ));
+
+ centry_free(centry);
+ return status;
+
+do_query:
+ (*num_groups) = 0;
+ (*user_gids) = NULL;
+
+ /* Return status value returned by seq number check */
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ 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_SYNCHRONIZATION_REQUIRED) )
+ goto skip_save;
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ 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", sid_to_string(sid_string, user_sid));
+ centry_free(centry);
+
+skip_save:
+ return status;
+}
+
+static NTSTATUS lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 num_sids, const DOM_SID *sids,
+ uint32 *num_aliases, uint32 **alias_rids)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ char *sidlist = talloc_strdup(mem_ctx, "");
+ int i;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ if (num_sids == 0) {
+ *num_aliases = 0;
+ *alias_rids = 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. */
+
+ for (i=0; i<num_sids; i++) {
+ sidlist = talloc_asprintf(mem_ctx, "%s/%s", sidlist,
+ sid_string_static(&sids[i]));
+ if (sidlist == NULL)
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ centry = wcache_fetch(cache, domain, "UA%s", sidlist);
+
+ if (!centry)
+ goto do_query;
+
+ *num_aliases = centry_uint32(centry);
+ *alias_rids = NULL;
+
+ if (*num_aliases) {
+ (*alias_rids) = TALLOC_ARRAY(mem_ctx, uint32, *num_aliases);
+
+ if ((*alias_rids) == NULL) {
+ centry_free(centry);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ (*alias_rids) = NULL;
+ }
+
+ for (i=0; i<(*num_aliases); i++)
+ (*alias_rids)[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);
+ return status;
+
+ do_query:
+ (*num_aliases) = 0;
+ (*alias_rids) = NULL;
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ DEBUG(10,("lookup_usergroups: [Cached] - doing backend query for info "
+ "for domain %s\n", domain->name ));
+
+ status = domain->backend->lookup_useraliases(domain, mem_ctx,
+ num_sids, sids,
+ num_aliases, alias_rids);
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ 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 lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *group_sid, uint32 *num_names,
+ DOM_SID **sid_mem, char ***names,
+ uint32 **name_types)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ fstring sid_string;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "GM/%s", sid_to_string(sid_string, group_sid));
+ if (!centry)
+ goto do_query;
+
+ *num_names = centry_uint32(centry);
+
+ if (*num_names == 0)
+ goto do_cached;
+
+ (*sid_mem) = TALLOC_ARRAY(mem_ctx, DOM_SID, *num_names);
+ (*names) = TALLOC_ARRAY(mem_ctx, char *, *num_names);
+ (*name_types) = TALLOC_ARRAY(mem_ctx, uint32, *num_names);
+
+ if (! (*sid_mem) || ! (*names) || ! (*name_types)) {
+ smb_panic_fn("lookup_groupmem out of memory");
+ }
+
+ for (i=0; i<(*num_names); i++) {
+ centry_sid(centry, mem_ctx, &(*sid_mem)[i]);
+ (*names)[i] = centry_string(centry, mem_ctx);
+ (*name_types)[i] = centry_uint32(centry);
+ }
+
+do_cached:
+ 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;
+
+do_query:
+ (*num_names) = 0;
+ (*sid_mem) = NULL;
+ (*names) = NULL;
+ (*name_types) = NULL;
+
+ /* Return status value returned by seq number check */
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ 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, num_names,
+ sid_mem, names, name_types);
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ 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", sid_to_string(sid_string, group_sid));
+ centry_free(centry);
+
+skip_save:
+ return status;
+}
+
+/* find the sequence number for a domain */
+static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq)
+{
+ refresh_sequence_number(domain, False);
+
+ *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 */
+static NTSTATUS trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_domains,
+ char ***names,
+ char ***alt_names,
+ DOM_SID **dom_sids)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ int i;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "TRUSTDOMS/%s", domain->name);
+
+ if (!centry) {
+ goto do_query;
+ }
+
+ *num_domains = centry_uint32(centry);
+
+ if (*num_domains) {
+ (*names) = TALLOC_ARRAY(mem_ctx, char *, *num_domains);
+ (*alt_names) = TALLOC_ARRAY(mem_ctx, char *, *num_domains);
+ (*dom_sids) = TALLOC_ARRAY(mem_ctx, DOM_SID, *num_domains);
+
+ if (! (*dom_sids) || ! (*names) || ! (*alt_names)) {
+ smb_panic_fn("trusted_domains out of memory");
+ }
+ } else {
+ (*names) = NULL;
+ (*alt_names) = NULL;
+ (*dom_sids) = NULL;
+ }
+
+ for (i=0; i<(*num_domains); i++) {
+ (*names)[i] = centry_string(centry, mem_ctx);
+ (*alt_names)[i] = centry_string(centry, mem_ctx);
+ centry_sid(centry, mem_ctx, &(*dom_sids)[i]);
+ }
+
+ status = centry->status;
+
+ DEBUG(10,("trusted_domains: [Cached] - cached info for domain %s (%d trusts) status: %s\n",
+ domain->name, *num_domains, nt_errstr(status) ));
+
+ centry_free(centry);
+ return status;
+
+do_query:
+ (*num_domains) = 0;
+ (*dom_sids) = NULL;
+ (*names) = NULL;
+ (*alt_names) = NULL;
+
+ /* Return status value returned by seq number check */
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ DEBUG(10,("trusted_domains: [Cached] - doing backend query for info for domain %s\n",
+ domain->name ));
+
+ status = domain->backend->trusted_domains(domain, mem_ctx, num_domains,
+ names, alt_names, dom_sids);
+
+ /* 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;
+ }
+
+
+#if 0 /* Disabled as we want the trust dom list to be managed by
+ the main parent and always to make the query. --jerry */
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+
+ centry = centry_start(domain, status);
+ if (!centry)
+ goto skip_save;
+
+ centry_put_uint32(centry, *num_domains);
+
+ for (i=0; i<(*num_domains); i++) {
+ centry_put_string(centry, (*names)[i]);
+ centry_put_string(centry, (*alt_names)[i]);
+ centry_put_sid(centry, &(*dom_sids)[i]);
+ }
+
+ centry_end(centry, "TRUSTDOMS/%s", domain->name);
+
+ centry_free(centry);
+
+skip_save:
+#endif
+
+ return status;
+}
+
+/* get lockout policy */
+static NTSTATUS lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_12 *policy){
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "LOC_POL/%s", domain->name);
+
+ if (!centry)
+ goto do_query;
+
+ policy->duration = centry_nttime(centry);
+ policy->reset_count = centry_nttime(centry);
+ policy->bad_attempt_lockout = 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);
+
+ /* Return status value returned by seq number check */
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ 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);
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ wcache_save_lockout_policy(domain, status, policy);
+
+ return status;
+}
+
+/* get password policy */
+static NTSTATUS password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_1 *policy)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "PWD_POL/%s", domain->name);
+
+ if (!centry)
+ goto do_query;
+
+ policy->min_length_password = centry_uint16(centry);
+ policy->password_history = centry_uint16(centry);
+ policy->password_properties = centry_uint32(centry);
+ policy->expire = centry_nttime(centry);
+ policy->min_passwordage = 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);
+
+ /* Return status value returned by seq number check */
+
+ if (!NT_STATUS_IS_OK(domain->last_status))
+ return domain->last_status;
+
+ 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);
+
+ /* and save it */
+ refresh_sequence_number(domain, False);
+ 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,
+ NET_USER_INFO_3 *info3)
+{
+ struct winbind_cache *cache;
+
+ /* dont 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);
+ netsamlogon_clear_cached_user(cache->tdb, info3);
+}
+
+void 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)
+ tdb_traverse(cache->tdb, traverse_fn, NULL);
+ }
+}
+
+BOOL init_wcache(void)
+{
+ if (wcache == NULL) {
+ wcache = SMB_XMALLOC_P(struct winbind_cache);
+ ZERO_STRUCTP(wcache);
+ }
+
+ if (wcache->tdb != NULL)
+ return True;
+
+ /* when working offline we must not clear the cache on restart */
+ wcache->tdb = tdb_open_log(lock_path("winbindd_cache.tdb"),
+ WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE,
+ lp_winbind_offline_logon() ? TDB_DEFAULT : (TDB_DEFAULT | TDB_CLEAR_IF_FIRST),
+ O_RDWR|O_CREAT, 0600);
+
+ 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 = True;
+ uint32 vers;
+
+ if (!init_wcache()) {
+ DEBUG(0,("initialize_winbindd_cache: init_wcache failed.\n"));
+ return False;
+ }
+
+ /* Check version number. */
+ if (tdb_fetch_uint32(wcache->tdb, WINBINDD_CACHE_VERSION_KEYSTR, &vers) &&
+ vers == WINBINDD_CACHE_VERSION) {
+ cache_bad = False;
+ }
+
+ if (cache_bad) {
+ 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;
+
+ if (unlink(lock_path("winbindd_cache.tdb")) == -1) {
+ DEBUG(0,("initialize_winbindd_cache: unlink %s failed %s ",
+ lock_path("winbindd_cache.tdb"),
+ strerror(errno) ));
+ return False;
+ }
+ 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 cache_store_response(pid_t pid, struct winbindd_response *response)
+{
+ fstring key_str;
+
+ if (!init_wcache())
+ return;
+
+ DEBUG(10, ("Storing response for pid %d, len %d\n",
+ pid, response->length));
+
+ fstr_sprintf(key_str, "DR/%d", pid);
+ if (tdb_store(wcache->tdb, string_tdb_data(key_str),
+ make_tdb_data((uint8 *)response, sizeof(*response)),
+ TDB_REPLACE) == -1)
+ return;
+
+ if (response->length == sizeof(*response))
+ return;
+
+ /* There's extra data */
+
+ DEBUG(10, ("Storing extra data: len=%d\n",
+ (int)(response->length - sizeof(*response))));
+
+ fstr_sprintf(key_str, "DE/%d", pid);
+ if (tdb_store(wcache->tdb, string_tdb_data(key_str),
+ make_tdb_data((uint8 *)response->extra_data.data,
+ response->length - sizeof(*response)),
+ TDB_REPLACE) == 0)
+ return;
+
+ /* We could not store the extra data, make sure the tdb does not
+ * contain a main record with wrong dangling extra data */
+
+ fstr_sprintf(key_str, "DR/%d", pid);
+ tdb_delete(wcache->tdb, string_tdb_data(key_str));
+
+ return;
+}
+
+BOOL cache_retrieve_response(pid_t pid, struct winbindd_response * response)
+{
+ TDB_DATA data;
+ fstring key_str;
+
+ if (!init_wcache())
+ return False;
+
+ DEBUG(10, ("Retrieving response for pid %d\n", pid));
+
+ fstr_sprintf(key_str, "DR/%d", pid);
+ data = tdb_fetch(wcache->tdb, string_tdb_data(key_str));
+
+ if (data.dptr == NULL)
+ return False;
+
+ if (data.dsize != sizeof(*response))
+ return False;
+
+ memcpy(response, data.dptr, data.dsize);
+ SAFE_FREE(data.dptr);
+
+ if (response->length == sizeof(*response)) {
+ response->extra_data.data = NULL;
+ return True;
+ }
+
+ /* There's extra data */
+
+ DEBUG(10, ("Retrieving extra data length=%d\n",
+ (int)(response->length - sizeof(*response))));
+
+ fstr_sprintf(key_str, "DE/%d", pid);
+ data = tdb_fetch(wcache->tdb, string_tdb_data(key_str));
+
+ if (data.dptr == NULL) {
+ DEBUG(0, ("Did not find extra data\n"));
+ return False;
+ }
+
+ if (data.dsize != (response->length - sizeof(*response))) {
+ DEBUG(0, ("Invalid extra data length: %d\n", (int)data.dsize));
+ SAFE_FREE(data.dptr);
+ return False;
+ }
+
+ dump_data(11, (uint8 *)data.dptr, data.dsize);
+
+ response->extra_data.data = data.dptr;
+ return True;
+}
+
+void cache_cleanup_response(pid_t pid)
+{
+ fstring key_str;
+
+ if (!init_wcache())
+ return;
+
+ fstr_sprintf(key_str, "DR/%d", pid);
+ tdb_delete(wcache->tdb, string_tdb_data(key_str));
+
+ fstr_sprintf(key_str, "DE/%d", pid);
+ tdb_delete(wcache->tdb, string_tdb_data(key_str));
+
+ return;
+}
+
+
+BOOL lookup_cached_sid(TALLOC_CTX *mem_ctx, const DOM_SID *sid,
+ char **domain_name, char **name,
+ enum lsa_SidType *type)
+{
+ struct winbindd_domain *domain;
+ struct winbind_cache *cache;
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+
+ domain = find_lookup_domain_from_sid(sid);
+ if (domain == NULL) {
+ return False;
+ }
+
+ cache = get_cache(domain);
+
+ if (cache->tdb == NULL) {
+ return False;
+ }
+
+ centry = wcache_fetch(cache, domain, "SN/%s", sid_string_static(sid));
+ if (centry == NULL) {
+ return False;
+ }
+
+ if (NT_STATUS_IS_OK(centry->status)) {
+ *type = (enum lsa_SidType)centry_uint32(centry);
+ *domain_name = centry_string(centry, mem_ctx);
+ *name = centry_string(centry, mem_ctx);
+ }
+
+ status = centry->status;
+ centry_free(centry);
+ return NT_STATUS_IS_OK(status);
+}
+
+BOOL lookup_cached_name(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ DOM_SID *sid,
+ enum lsa_SidType *type)
+{
+ struct winbindd_domain *domain;
+ struct winbind_cache *cache;
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ fstring uname;
+ BOOL original_online_state;
+
+ domain = find_lookup_domain_from_name(domain_name);
+ if (domain == NULL) {
+ return False;
+ }
+
+ cache = get_cache(domain);
+
+ if (cache->tdb == NULL) {
+ return False;
+ }
+
+ fstrcpy(uname, name);
+ strupper_m(uname);
+
+ /* 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;
+ centry = wcache_fetch(cache, domain, "NS/%s/%s", domain_name, uname);
+ domain->online = original_online_state;
+
+ if (centry == NULL) {
+ return False;
+ }
+
+ if (NT_STATUS_IS_OK(centry->status)) {
+ *type = (enum lsa_SidType)centry_uint32(centry);
+ centry_sid(centry, mem_ctx, sid);
+ }
+
+ status = centry->status;
+ centry_free(centry);
+
+ return NT_STATUS_IS_OK(status);
+}
+
+void cache_name2sid(struct winbindd_domain *domain,
+ const char *domain_name, const char *name,
+ enum lsa_SidType type, const DOM_SID *sid)
+{
+ refresh_sequence_number(domain, False);
+ 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 */
+void wcache_flush_cache(void)
+{
+ if (!wcache)
+ return;
+ if (wcache->tdb) {
+ tdb_close(wcache->tdb);
+ wcache->tdb = NULL;
+ }
+ if (opt_nocache)
+ return;
+
+ /* when working offline we must not clear the cache on restart */
+ wcache->tdb = tdb_open_log(lock_path("winbindd_cache.tdb"),
+ WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE,
+ lp_winbind_offline_logon() ? TDB_DEFAULT : (TDB_DEFAULT | TDB_CLEAR_IF_FIRST),
+ O_RDWR|O_CREAT, 0600);
+
+ 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 DOM_SID *sid)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ NTSTATUS status;
+ int ret;
+ struct cred_list *cred, *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;
+
+ DEBUG(11,("we already have an entry, deleting that\n"));
+
+ fstr_sprintf(key_str, "CRED/%s", sid_string_static(sid));
+
+ 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 == -1) || (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:
+ SAFE_FREE(wcache_cred_list);
+ SAFE_FREE(oldest);
+
+ return status;
+}
+
+/* Change the global online/offline state. */
+BOOL set_global_winbindd_state_offline(void)
+{
+ TDB_DATA data;
+
+ DEBUG(10,("set_global_winbindd_state_offline: offline requested.\n"));
+
+ /* Only go offline if someone has created
+ the key "WINBINDD_OFFLINE" in the cache tdb. */
+
+ if (wcache == NULL || wcache->tdb == NULL) {
+ DEBUG(10,("set_global_winbindd_state_offline: wcache not open yet.\n"));
+ return False;
+ }
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("set_global_winbindd_state_offline: rejecting.\n"));
+ return False;
+ }
+
+ if (global_winbindd_offline_state) {
+ /* Already offline. */
+ return True;
+ }
+
+ data = tdb_fetch_bystring( wcache->tdb, "WINBINDD_OFFLINE" );
+
+ if (!data.dptr || data.dsize != 4) {
+ DEBUG(10,("set_global_winbindd_state_offline: offline state not set.\n"));
+ SAFE_FREE(data.dptr);
+ return False;
+ } else {
+ DEBUG(10,("set_global_winbindd_state_offline: offline state set.\n"));
+ global_winbindd_offline_state = True;
+ SAFE_FREE(data.dptr);
+ return True;
+ }
+}
+
+void set_global_winbindd_state_online(void)
+{
+ DEBUG(10,("set_global_winbindd_state_online: online requested.\n"));
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("set_global_winbindd_state_online: rejecting.\n"));
+ return;
+ }
+
+ if (!global_winbindd_offline_state) {
+ /* Already online. */
+ return;
+ }
+ global_winbindd_offline_state = False;
+
+ 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)
+{
+ return global_winbindd_offline_state;
+}
+
+/***********************************************************************
+ 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 *)memdup(data.dptr, data.dsize);
+ if (!centry->data) {
+ SAFE_FREE(centry);
+ return NULL;
+ }
+ centry->len = data.dsize;
+ centry->ofs = 0;
+
+ if (centry->len < 8) {
+ /* huh? corrupt cache? */
+ DEBUG(0,("create_centry_validate: Corrupt cache for key %s (len < 8) ?\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);
+ 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_ns(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_uint32(centry);
+ if (NT_STATUS_IS_OK(centry->status)) {
+ DOM_SID sid;
+ (void)centry_sid(centry, mem_ctx, &sid);
+ }
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DEBUG(10,("validate_ns: %s ok\n", keystr));
+ return 0;
+}
+
+static int validate_sn(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;
+ }
+
+ if (NT_STATUS_IS_OK(centry->status)) {
+ (void)centry_uint32(centry);
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_string(centry, mem_ctx);
+ }
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DEBUG(10,("validate_sn: %s ok\n", keystr));
+ 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);
+ 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_uint32(centry);
+ (void)centry_sid(centry, mem_ctx, &sid);
+ (void)centry_sid(centry, mem_ctx, &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 num_entries, i;
+
+ if (!centry) {
+ return 1;
+ }
+
+ num_entries = (int32)centry_uint32(centry);
+
+ for (i=0; i< num_entries; i++) {
+ DOM_SID sid;
+ (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_sid(centry, mem_ctx, &sid);
+ (void)centry_sid(centry, mem_ctx, &sid);
+ }
+
+ 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 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 num_groups, i;
+
+ if (!centry) {
+ return 1;
+ }
+
+ num_groups = centry_uint32(centry);
+
+ for (i=0; i< num_groups; i++) {
+ DOM_SID sid;
+ centry_sid(centry, mem_ctx, &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 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 num_names, i;
+
+ if (!centry) {
+ return 1;
+ }
+
+ num_names = centry_uint32(centry);
+
+ for (i=0; i< num_names; i++) {
+ DOM_SID sid;
+ centry_sid(centry, mem_ctx, &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_trustdoms(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 num_domains, i;
+
+ if (!centry) {
+ return 1;
+ }
+
+ num_domains = centry_uint32(centry);
+
+ for (i=0; i< num_domains; i++) {
+ DOM_SID sid;
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_sid(centry, mem_ctx, &sid);
+ }
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DEBUG(10,("validate_trustdoms: %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_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},
+ {"NS/", validate_ns},
+ {"SN/", validate_sn},
+ {"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},
+ {"TRUSTDOMS/", validate_trustdoms},
+ {"TRUSTDOMCACHE/", validate_trustdomcache},
+ {"WINBINDD_OFFLINE", validate_offline},
+ {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;
+ struct tdb_validation_status *v_state = (struct tdb_validation_status *)state;
+
+ /* Paranoia check. */
+ if (kbuf.dsize > 1024) {
+ DEBUG(0,("cache_traverse_validate_fn: key length too large (%u) > 1024\n\n",
+ (unsigned int)kbuf.dsize ));
+ 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 *)kbuf.dptr, kbuf.dsize);
+ DEBUG(0,("data :\n"));
+ dump_data(0, (uint8 *)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);
+}
+
+/***********************************************************************
+ Try and validate every entry in the winbindd cache. If we fail here,
+ delete the cache tdb and return non-zero - the caller (main winbindd
+ function) will restart us as we don't know if we crashed or not.
+***********************************************************************/
+
+int winbindd_validate_cache(void)
+{
+ int ret = -1;
+ const char *tdb_path = lock_path("winbindd_cache.tdb");
+ TDB_CONTEXT *tdb = NULL;
+
+ DEBUG(10, ("winbindd_validate_cache: replacing panic function\n"));
+ smb_panic_fn = validate_panic;
+
+
+ tdb = tdb_open_log(tdb_path,
+ WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE,
+ ( 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;
+ }
+ 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:
+ 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;
+ const char *tdb_path = lock_path("winbindd_cache.tdb");
+
+ DEBUG(10, ("winbindd_validate_cache: replacing panic function\n"));
+ smb_panic_fn = validate_panic;
+
+
+ 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"));
+ }
+
+ DEBUG(10, ("winbindd_validate_cache_nobackup: restoring panic "
+ "function\n"));
+ smb_panic_fn = smb_panic;
+ return ret;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+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 idx;
+ int i;
+ 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_ARRAY( *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 );
+ list[idx].dns_name = talloc_strdup( list, new_dom->alt_name );
+
+ if ( !is_null_sid( &new_dom->sid ) )
+ sid_copy( &list[idx].sid, &new_dom->sid );
+
+ 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;
+ }
+
+
+ asprintf( &keystr, "TRUSTDOMCACHE/%s", domain_name );
+ 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;
+ int 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+len, buflen-len, "d",
+ num_domains );
+
+ /* now pack each domain trust record */
+ for ( i=0; i<num_domains; i++ ) {
+
+ 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+len, buflen-len, "fffddd",
+ domains[i].domain_name,
+ domains[i].dns_name,
+ sid_string_static(&domains[i].sid),
+ 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 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++ ) {
+ len += tdb_unpack( buf+len, buflen-len, "fffddd",
+ domain_name,
+ dns_name,
+ sid_string,
+ &flags,
+ &attribs,
+ &type );
+
+ if ( len == -1 ) {
+ DEBUG(5,("unpack_tdc_domains: Failed to unpack domain array\n"));
+ TALLOC_FREE( list );
+ return 0;
+ }
+
+ 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 = 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 != -1 );
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+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;
+
+ 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,
+ sid_string_static(&domain->sid),
+ 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;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+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;
+ int i;
+ struct winbindd_tdc_domain *d = NULL;
+
+ DEBUG(10,("wcache_tdc_fetch_domain: Searching for domain %s\n", name));
+
+ if ( !init_wcache() ) {
+ return False;
+ }
+
+ /* 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 = TALLOC_P( ctx, struct winbindd_tdc_domain );
+ if ( !d )
+ break;
+
+ d->domain_name = talloc_strdup( d, dom_list[i].domain_name );
+ d->dns_name = talloc_strdup( d, dom_list[i].dns_name );
+ sid_copy( &d->sid, &dom_list[i].sid );
+ d->trust_flags = dom_list[i].trust_flags;
+ d->trust_type = dom_list[i].trust_type;
+ d->trust_attribs = dom_list[i].trust_attribs;
+
+ break;
+ }
+ }
+
+ TALLOC_FREE( dom_list );
+
+ return d;
+}
+
+
+/*********************************************************************
+ ********************************************************************/
+
+void wcache_tdc_clear( void )
+{
+ if ( !init_wcache() )
+ return;
+
+ wcache_tdc_store_list( NULL, 0 );
+
+ return;
+}
+
+
+/*********************************************************************
+ ********************************************************************/
+
+static void wcache_save_user_pwinfo(struct winbindd_domain *domain,
+ NTSTATUS status,
+ const DOM_SID *user_sid,
+ const char *homedir,
+ const char *shell,
+ const char *gecos,
+ uint32 gid)
+{
+ struct cache_entry *centry;
+
+ if ( (centry = centry_start(domain, status)) == NULL )
+ return;
+
+ centry_put_string( centry, homedir );
+ centry_put_string( centry, shell );
+ centry_put_string( centry, gecos );
+ centry_put_uint32( centry, gid );
+
+ centry_end(centry, "NSS/PWINFO/%s", sid_string_static(user_sid) );
+
+ DEBUG(10,("wcache_save_user_pwinfo: %s\n", sid_string_static(user_sid) ));
+
+ centry_free(centry);
+}
+
+NTSTATUS nss_get_info_cached( struct winbindd_domain *domain,
+ const DOM_SID *user_sid,
+ TALLOC_CTX *ctx,
+ ADS_STRUCT *ads, LDAPMessage *msg,
+ char **homedir, char **shell, char **gecos,
+ gid_t *p_gid)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS nt_status;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "NSS/PWINFO/%s", sid_string_static(user_sid));
+
+ if (!centry)
+ goto do_query;
+
+ *homedir = centry_string( centry, ctx );
+ *shell = centry_string( centry, ctx );
+ *gecos = centry_string( centry, ctx );
+ *p_gid = centry_uint32( centry );
+
+ centry_free(centry);
+
+ DEBUG(10,("nss_get_info_cached: [Cached] - user_sid %s\n",
+ sid_string_static(user_sid)));
+
+ return NT_STATUS_OK;
+
+do_query:
+
+ nt_status = nss_get_info( domain->name, user_sid, ctx, ads, msg,
+ homedir, shell, gecos, p_gid );
+
+ if ( NT_STATUS_IS_OK(nt_status) ) {
+ wcache_save_user_pwinfo( domain, nt_status, user_sid,
+ *homedir, *shell, *gecos, *p_gid );
+ }
+
+ if ( NT_STATUS_EQUAL( nt_status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND ) ) {
+ DEBUG(5,("nss_get_info_cached: Setting domain %s offline\n",
+ domain->name ));
+ set_domain_offline( domain );
+ }
+
+ return nt_status;
+}
+
+
+/* the cache backend methods are exposed via this structure */
+struct winbindd_methods cache_methods = {
+ True,
+ query_user_list,
+ enum_dom_groups,
+ enum_local_groups,
+ name_to_sid,
+ sid_to_name,
+ rids_to_names,
+ query_user,
+ lookup_usergroups,
+ lookup_useraliases,
+ lookup_groupmem,
+ sequence_number,
+ lockout_policy,
+ password_policy,
+ trusted_domains
+};
diff --git a/source3/winbindd/winbindd_ccache_access.c b/source3/winbindd/winbindd_ccache_access.c
new file mode 100644
index 0000000000..99c5c0c4d1
--- /dev/null
+++ b/source3/winbindd/winbindd_ccache_access.c
@@ -0,0 +1,283 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - cached credentials funcions
+
+ Copyright (C) Robert O'Callahan 2006
+ Copyright (C) Jeremy Allison 2006 (minor fixes to fit into Samba and
+ protect against integer wrap).
+
+ 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 "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static BOOL client_can_access_ccache_entry(uid_t client_uid,
+ struct WINBINDD_MEMORY_CREDS *entry)
+{
+ if (client_uid == entry->uid || client_uid == 0) {
+ DEBUG(10, ("Access granted to uid %d\n", client_uid));
+ return True;
+ }
+
+ DEBUG(1, ("Access denied to uid %d (expected %d)\n", client_uid, entry->uid));
+ return False;
+}
+
+static NTSTATUS do_ntlm_auth_with_hashes(const char *username,
+ const char *domain,
+ const unsigned char lm_hash[LM_HASH_LEN],
+ const unsigned char nt_hash[NT_HASH_LEN],
+ const DATA_BLOB initial_msg,
+ const DATA_BLOB challenge_msg,
+ DATA_BLOB *auth_msg)
+{
+ NTSTATUS status;
+ NTLMSSP_STATE *ntlmssp_state = NULL;
+ DATA_BLOB dummy_msg, reply;
+
+ status = ntlmssp_client_start(&ntlmssp_state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not start NTLMSSP client: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = ntlmssp_set_username(ntlmssp_state, username);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not set username: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = ntlmssp_set_domain(ntlmssp_state, domain);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not set domain: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = ntlmssp_set_hashes(ntlmssp_state, lm_hash, nt_hash);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not set hashes: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /* We need to get our protocol handler into the right state. So first
+ we ask it to generate the initial message. Actually the client has already
+ sent its own initial message, so we're going to drop this one on the floor.
+ The client might have sent a different message, for example with different
+ negotiation options, but as far as I can tell this won't hurt us. (Unless
+ the client sent a different username or domain, in which case that's their
+ problem for telling us the wrong username or domain.)
+ Since we have a copy of the initial message that the client sent, we could
+ resolve any discrepancies if we had to.
+ */
+ dummy_msg = data_blob_null;
+ reply = data_blob_null;
+ status = ntlmssp_update(ntlmssp_state, dummy_msg, &reply);
+ data_blob_free(&dummy_msg);
+ data_blob_free(&reply);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ DEBUG(1, ("Failed to create initial message! [%s]\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /* Now we are ready to handle the server's actual response. */
+ status = ntlmssp_update(ntlmssp_state, challenge_msg, &reply);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
+ DEBUG(1, ("We didn't get a response to the challenge! [%s]\n",
+ nt_errstr(status)));
+ data_blob_free(&reply);
+ goto done;
+ }
+ *auth_msg = reply;
+ status = NT_STATUS_OK;
+
+done:
+ ntlmssp_end(&ntlmssp_state);
+ return status;
+}
+
+static BOOL check_client_uid(struct winbindd_cli_state *state, uid_t uid)
+{
+ int ret;
+ uid_t ret_uid;
+
+ ret_uid = (uid_t)-1;
+
+ ret = sys_getpeereid(state->sock, &ret_uid);
+ if (ret != 0) {
+ DEBUG(1, ("check_client_uid: Could not get socket peer uid: %s; "
+ "denying access\n", strerror(errno)));
+ return False;
+ }
+
+ if (uid != ret_uid) {
+ DEBUG(1, ("check_client_uid: Client lied about its uid: said %d, "
+ "actually was %d; denying access\n",
+ uid, ret_uid));
+ return False;
+ }
+
+ return True;
+}
+
+void winbindd_ccache_ntlm_auth(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+ fstring name_domain, name_user;
+
+ /* Ensure null termination */
+ state->request.data.ccache_ntlm_auth.user[
+ sizeof(state->request.data.ccache_ntlm_auth.user)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: perform NTLM auth on behalf of user %s\n", (unsigned long)state->pid,
+ state->request.data.ccache_ntlm_auth.user));
+
+ /* Parse domain and username */
+
+ if (!canonicalize_username(state->request.data.ccache_ntlm_auth.user,
+ name_domain, name_user)) {
+ DEBUG(5,("winbindd_ccache_ntlm_auth: cannot parse domain and user from name [%s]\n",
+ state->request.data.ccache_ntlm_auth.user));
+ request_error(state);
+ return;
+ }
+
+ domain = find_auth_domain(state, name_domain);
+
+ if (domain == NULL) {
+ DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n",
+ name_domain));
+ request_error(state);
+ return;
+ }
+
+ if (!check_client_uid(state, state->request.data.ccache_ntlm_auth.uid)) {
+ request_error(state);
+ return;
+ }
+
+ sendto_domain(state, domain);
+}
+
+enum winbindd_result winbindd_dual_ccache_ntlm_auth(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ NTSTATUS result = NT_STATUS_NOT_SUPPORTED;
+ struct WINBINDD_MEMORY_CREDS *entry;
+ DATA_BLOB initial, challenge, auth;
+ fstring name_domain, name_user;
+ uint32 initial_blob_len, challenge_blob_len, extra_len;
+
+ /* Ensure null termination */
+ state->request.data.ccache_ntlm_auth.user[
+ sizeof(state->request.data.ccache_ntlm_auth.user)-1]='\0';
+
+ DEBUG(3, ("winbindd_dual_ccache_ntlm_auth: [%5lu]: perform NTLM auth on "
+ "behalf of user %s (dual)\n", (unsigned long)state->pid,
+ state->request.data.ccache_ntlm_auth.user));
+
+ /* validate blob lengths */
+ initial_blob_len = state->request.data.ccache_ntlm_auth.initial_blob_len;
+ challenge_blob_len = state->request.data.ccache_ntlm_auth.challenge_blob_len;
+ extra_len = state->request.extra_len;
+
+ if (initial_blob_len > extra_len || challenge_blob_len > extra_len ||
+ initial_blob_len + challenge_blob_len > extra_len ||
+ initial_blob_len + challenge_blob_len < initial_blob_len ||
+ initial_blob_len + challenge_blob_len < challenge_blob_len) {
+
+ DEBUG(10,("winbindd_dual_ccache_ntlm_auth: blob lengths overrun "
+ "or wrap. Buffer [%d+%d > %d]\n",
+ initial_blob_len,
+ challenge_blob_len,
+ extra_len));
+ goto process_result;
+ }
+
+ /* Parse domain and username */
+ if (!parse_domain_user(state->request.data.ccache_ntlm_auth.user, name_domain, name_user)) {
+ DEBUG(10,("winbindd_dual_ccache_ntlm_auth: cannot parse "
+ "domain and user from name [%s]\n",
+ state->request.data.ccache_ntlm_auth.user));
+ goto process_result;
+ }
+
+ entry = find_memory_creds_by_name(state->request.data.ccache_ntlm_auth.user);
+ if (entry == NULL || entry->nt_hash == NULL || entry->lm_hash == NULL) {
+ DEBUG(10,("winbindd_dual_ccache_ntlm_auth: could not find "
+ "credentials for user %s\n",
+ state->request.data.ccache_ntlm_auth.user));
+ goto process_result;
+ }
+
+ DEBUG(10,("winbindd_dual_ccache_ntlm_auth: found ccache [%s]\n", entry->username));
+
+ if (!client_can_access_ccache_entry(state->request.data.ccache_ntlm_auth.uid, entry)) {
+ goto process_result;
+ }
+
+ if (initial_blob_len == 0 && challenge_blob_len == 0) {
+ /* this is just a probe to see if credentials are available. */
+ result = NT_STATUS_OK;
+ state->response.data.ccache_ntlm_auth.auth_blob_len = 0;
+ goto process_result;
+ }
+
+ initial = data_blob(state->request.extra_data.data, initial_blob_len);
+ challenge = data_blob(state->request.extra_data.data + initial_blob_len,
+ state->request.data.ccache_ntlm_auth.challenge_blob_len);
+
+ if (!initial.data || !challenge.data) {
+ result = NT_STATUS_NO_MEMORY;
+ } else {
+ result = do_ntlm_auth_with_hashes(name_user, name_domain,
+ entry->lm_hash, entry->nt_hash,
+ initial, challenge, &auth);
+ }
+
+ data_blob_free(&initial);
+ data_blob_free(&challenge);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ goto process_result;
+ }
+
+ state->response.extra_data.data = smb_xmemdup(auth.data, auth.length);
+ if (!state->response.extra_data.data) {
+ result = NT_STATUS_NO_MEMORY;
+ goto process_result;
+ }
+ state->response.length += auth.length;
+ state->response.data.ccache_ntlm_auth.auth_blob_len = auth.length;
+
+ data_blob_free(&auth);
+
+ process_result:
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
new file mode 100644
index 0000000000..9ffb3dfb23
--- /dev/null
+++ b/source3/winbindd/winbindd_cm.c
@@ -0,0 +1,2271 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon connection manager
+
+ Copyright (C) Tim Potter 2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Gerald (Jerry) Carter 2003-2005.
+ Copyright (C) Volker Lendecke 2004-2005
+ Copyright (C) Jeremy Allison 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ We need to manage connections to domain controllers without having to
+ mess up the main winbindd code with other issues. The aim of the
+ connection manager is to:
+
+ - make connections to domain controllers and cache them
+ - re-establish connections when networks or servers go down
+ - centralise the policy on connection timeouts, domain controller
+ selection etc
+ - manage re-entrancy for when winbindd becomes able to handle
+ multiple outstanding rpc requests
+
+ Why not have connection management as part of the rpc layer like tng?
+ Good question. This code may morph into libsmb/rpc_cache.c or something
+ like that but at the moment it's simply staying as part of winbind. I
+ think the TNG architecture of forcing every user of the rpc layer to use
+ the connection caching system is a bad idea. It should be an optional
+ method of using the routines.
+
+ The TNG design is quite good but I disagree with some aspects of the
+ implementation. -tpot
+
+ */
+
+/*
+ TODO:
+
+ - I'm pretty annoyed by all the make_nmb_name() stuff. It should be
+ moved down into another function.
+
+ - Take care when destroying cli_structs as they can be shared between
+ various sam handles.
+
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+struct dc_name_ip {
+ fstring name;
+ struct in_addr ip;
+};
+
+extern struct winbindd_methods reconnect_methods;
+extern BOOL override_logfile;
+
+static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain);
+static void set_dc_type_and_flags( struct winbindd_domain *domain );
+static BOOL get_dcs(TALLOC_CTX *mem_ctx, const struct winbindd_domain *domain,
+ struct dc_name_ip **dcs, int *num_dcs);
+
+/****************************************************************
+ Child failed to find DC's. Reschedule check.
+****************************************************************/
+
+static void msg_failed_to_go_online(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_domain *domain;
+ const char *domainname = (const char *)data->data;
+
+ if (data->data == NULL || data->length == 0) {
+ return;
+ }
+
+ DEBUG(5,("msg_fail_to_go_online: received for domain %s.\n", domainname));
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+
+ if (strequal(domain->name, domainname)) {
+ if (domain->online) {
+ /* We're already online, ignore. */
+ DEBUG(5,("msg_fail_to_go_online: domain %s "
+ "already online.\n", domainname));
+ continue;
+ }
+
+ /* Reschedule the online check. */
+ set_domain_offline(domain);
+ break;
+ }
+ }
+}
+
+/****************************************************************
+ Actually cause a reconnect from a message.
+****************************************************************/
+
+static void msg_try_to_go_online(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_domain *domain;
+ const char *domainname = (const char *)data->data;
+
+ if (data->data == NULL || data->length == 0) {
+ return;
+ }
+
+ DEBUG(5,("msg_try_to_go_online: received for domain %s.\n", domainname));
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+
+ if (strequal(domain->name, domainname)) {
+
+ if (domain->online) {
+ /* We're already online, ignore. */
+ DEBUG(5,("msg_try_to_go_online: domain %s "
+ "already online.\n", domainname));
+ continue;
+ }
+
+ /* This call takes care of setting the online
+ flag to true if we connected, or re-adding
+ the offline handler if false. Bypasses online
+ check so always does network calls. */
+
+ init_dc_connection_network(domain);
+ break;
+ }
+ }
+}
+
+/****************************************************************
+ Fork a child to try and contact a DC. Do this as contacting a
+ DC requires blocking lookups and we don't want to block our
+ parent.
+****************************************************************/
+
+static BOOL fork_child_dc_connect(struct winbindd_domain *domain)
+{
+ struct dc_name_ip *dcs = NULL;
+ int num_dcs = 0;
+ TALLOC_CTX *mem_ctx = NULL;
+ pid_t child_pid;
+ pid_t parent_pid = sys_getpid();
+
+ /* Stop zombies */
+ CatchChild();
+
+ child_pid = sys_fork();
+
+ if (child_pid == -1) {
+ DEBUG(0, ("fork_child_dc_connect: Could not fork: %s\n", strerror(errno)));
+ return False;
+ }
+
+ if (child_pid != 0) {
+ /* Parent */
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_WINBIND_TRY_TO_GO_ONLINE,
+ msg_try_to_go_online);
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_WINBIND_FAILED_TO_GO_ONLINE,
+ msg_failed_to_go_online);
+ return True;
+ }
+
+ /* Child. */
+
+ /* Leave messages blocked - we will never process one. */
+
+ /* tdb needs special fork handling */
+ if (tdb_reopen_all(1) == -1) {
+ DEBUG(0,("tdb_reopen_all failed.\n"));
+ _exit(0);
+ }
+
+ close_conns_after_fork();
+
+ if (!override_logfile) {
+ pstring logfile;
+ pstr_sprintf(logfile, "%s/log.winbindd-dc-connect", dyn_LOGFILEBASE);
+ lp_set_logfile(logfile);
+ reopen_logs();
+ }
+
+ mem_ctx = talloc_init("fork_child_dc_connect");
+ if (!mem_ctx) {
+ DEBUG(0,("talloc_init failed.\n"));
+ _exit(0);
+ }
+
+ if ((!get_dcs(mem_ctx, domain, &dcs, &num_dcs)) || (num_dcs == 0)) {
+ /* Still offline ? Can't find DC's. */
+ messaging_send_buf(winbind_messaging_context(),
+ pid_to_procid(parent_pid),
+ MSG_WINBIND_FAILED_TO_GO_ONLINE,
+ (uint8 *)domain->name,
+ strlen(domain->name)+1);
+ _exit(0);
+ }
+
+ /* We got a DC. Send a message to our parent to get it to
+ try and do the same. */
+
+ messaging_send_buf(winbind_messaging_context(),
+ pid_to_procid(parent_pid),
+ MSG_WINBIND_TRY_TO_GO_ONLINE,
+ (uint8 *)domain->name,
+ strlen(domain->name)+1);
+ _exit(0);
+}
+
+/****************************************************************
+ Handler triggered if we're offline to try and detect a DC.
+****************************************************************/
+
+static void check_domain_online_handler(struct event_context *ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ struct winbindd_domain *domain =
+ (struct winbindd_domain *)private_data;
+
+ DEBUG(10,("check_domain_online_handler: called for domain "
+ "%s (online = %s)\n", domain->name,
+ domain->online ? "True" : "False" ));
+
+ TALLOC_FREE(domain->check_online_event);
+
+ /* Are we still in "startup" mode ? */
+
+ if (domain->startup && (now->tv_sec > domain->startup_time + 30)) {
+ /* No longer in "startup" mode. */
+ DEBUG(10,("check_domain_online_handler: domain %s no longer in 'startup' mode.\n",
+ domain->name ));
+ domain->startup = False;
+ }
+
+ /* We've been told to stay offline, so stay
+ that way. */
+
+ if (get_global_winbindd_state_offline()) {
+ DEBUG(10,("check_domain_online_handler: domain %s remaining globally offline\n",
+ domain->name ));
+ return;
+ }
+
+ /* Fork a child to test if it can contact a DC.
+ If it can then send ourselves a message to
+ cause a reconnect. */
+
+ fork_child_dc_connect(domain);
+}
+
+/****************************************************************
+ If we're still offline setup the timeout check.
+****************************************************************/
+
+static void calc_new_online_timeout_check(struct winbindd_domain *domain)
+{
+ int wbc = lp_winbind_cache_time();
+
+ if (domain->startup) {
+ domain->check_online_timeout = 10;
+ } else if (domain->check_online_timeout < wbc) {
+ domain->check_online_timeout = wbc;
+ }
+}
+
+/****************************************************************
+ Set domain offline and also add handler to put us back online
+ if we detect a DC.
+****************************************************************/
+
+void set_domain_offline(struct winbindd_domain *domain)
+{
+ DEBUG(10,("set_domain_offline: called for domain %s\n",
+ domain->name ));
+
+ TALLOC_FREE(domain->check_online_event);
+
+ if (domain->internal) {
+ DEBUG(3,("set_domain_offline: domain %s is internal - logic error.\n",
+ domain->name ));
+ return;
+ }
+
+ domain->online = False;
+
+ /* Offline domains are always initialized. They're
+ re-initialized when they go back online. */
+
+ domain->initialized = True;
+
+ /* We only add the timeout handler that checks and
+ allows us to go back online when we've not
+ been told to remain offline. */
+
+ if (get_global_winbindd_state_offline()) {
+ DEBUG(10,("set_domain_offline: domain %s remaining globally offline\n",
+ domain->name ));
+ return;
+ }
+
+ /* If we're in statup mode, check again in 10 seconds, not in
+ lp_winbind_cache_time() seconds (which is 5 mins by default). */
+
+ calc_new_online_timeout_check(domain);
+
+ domain->check_online_event = event_add_timed(winbind_event_context(),
+ NULL,
+ timeval_current_ofs(domain->check_online_timeout,0),
+ "check_domain_online_handler",
+ check_domain_online_handler,
+ domain);
+
+ /* The above *has* to succeed for winbindd to work. */
+ if (!domain->check_online_event) {
+ smb_panic("set_domain_offline: failed to add online handler");
+ }
+
+ DEBUG(10,("set_domain_offline: added event handler for domain %s\n",
+ domain->name ));
+
+ /* Send an offline message to the idmap child when our
+ primary domain goes offline */
+
+ if ( domain->primary ) {
+ struct winbindd_child *idmap = idmap_child();
+
+ if ( idmap->pid != 0 ) {
+ messaging_send_buf(winbind_messaging_context(),
+ pid_to_procid(idmap->pid),
+ MSG_WINBIND_OFFLINE,
+ (uint8 *)domain->name,
+ strlen(domain->name)+1);
+ }
+ }
+
+ return;
+}
+
+/****************************************************************
+ Set domain online - if allowed.
+****************************************************************/
+
+static void set_domain_online(struct winbindd_domain *domain)
+{
+ struct timeval now;
+
+ DEBUG(10,("set_domain_online: called for domain %s\n",
+ domain->name ));
+
+ if (domain->internal) {
+ DEBUG(3,("set_domain_online: domain %s is internal - logic error.\n",
+ domain->name ));
+ return;
+ }
+
+ if (get_global_winbindd_state_offline()) {
+ DEBUG(10,("set_domain_online: domain %s remaining globally offline\n",
+ domain->name ));
+ return;
+ }
+
+ winbindd_set_locator_kdc_envs(domain);
+
+ /* If we are waiting to get a krb5 ticket, trigger immediately. */
+ GetTimeOfDay(&now);
+ set_event_dispatch_time(winbind_event_context(),
+ "krb5_ticket_gain_handler", now);
+
+ /* Ok, we're out of any startup mode now... */
+ domain->startup = False;
+
+ if (domain->online == False) {
+ /* We were offline - now we're online. We default to
+ using the MS-RPC backend if we started offline,
+ and if we're going online for the first time we
+ should really re-initialize the backends and the
+ checks to see if we're talking to an AD or NT domain.
+ */
+
+ domain->initialized = False;
+
+ /* 'reconnect_methods' is the MS-RPC backend. */
+ if (domain->backend == &reconnect_methods) {
+ domain->backend = NULL;
+ }
+ }
+
+ /* Ensure we have no online timeout checks. */
+ domain->check_online_timeout = 0;
+ TALLOC_FREE(domain->check_online_event);
+
+ /* Ensure we ignore any pending child messages. */
+ messaging_deregister(winbind_messaging_context(),
+ MSG_WINBIND_TRY_TO_GO_ONLINE, NULL);
+ messaging_deregister(winbind_messaging_context(),
+ MSG_WINBIND_FAILED_TO_GO_ONLINE, NULL);
+
+ domain->online = True;
+
+ /* Send an online message to the idmap child when our
+ primary domain comes online */
+
+ if ( domain->primary ) {
+ struct winbindd_child *idmap = idmap_child();
+
+ if ( idmap->pid != 0 ) {
+ messaging_send_buf(winbind_messaging_context(),
+ pid_to_procid(idmap->pid),
+ MSG_WINBIND_ONLINE,
+ (uint8 *)domain->name,
+ strlen(domain->name)+1);
+ }
+ }
+
+ return;
+}
+
+/****************************************************************
+ Requested to set a domain online.
+****************************************************************/
+
+void set_domain_online_request(struct winbindd_domain *domain)
+{
+ struct timeval tev;
+
+ DEBUG(10,("set_domain_online_request: called for domain %s\n",
+ domain->name ));
+
+ if (get_global_winbindd_state_offline()) {
+ DEBUG(10,("set_domain_online_request: domain %s remaining globally offline\n",
+ domain->name ));
+ return;
+ }
+
+ /* We've been told it's safe to go online and
+ try and connect to a DC. But I don't believe it
+ because network manager seems to lie.
+ Wait at least 5 seconds. Heuristics suck... */
+
+ if (!domain->check_online_event) {
+ /* If we've come from being globally offline we
+ don't have a check online event handler set.
+ We need to add one now we're trying to go
+ back online. */
+
+ DEBUG(10,("set_domain_online_request: domain %s was globally offline.\n",
+ domain->name ));
+
+ domain->check_online_event = event_add_timed(winbind_event_context(),
+ NULL,
+ timeval_current_ofs(5, 0),
+ "check_domain_online_handler",
+ check_domain_online_handler,
+ domain);
+
+ /* The above *has* to succeed for winbindd to work. */
+ if (!domain->check_online_event) {
+ smb_panic("set_domain_online_request: failed to add online handler");
+ }
+ }
+
+ GetTimeOfDay(&tev);
+
+ /* Go into "startup" mode again. */
+ domain->startup_time = tev.tv_sec;
+ domain->startup = True;
+
+ tev.tv_sec += 5;
+
+ set_event_dispatch_time(winbind_event_context(), "check_domain_online_handler", tev);
+}
+
+/****************************************************************
+ Add -ve connection cache entries for domain and realm.
+****************************************************************/
+
+void winbind_add_failed_connection_entry(const struct winbindd_domain *domain,
+ const char *server,
+ NTSTATUS result)
+{
+ add_failed_connection_entry(domain->name, server, result);
+ /* If this was the saf name for the last thing we talked to,
+ remove it. */
+ saf_delete(domain->name);
+ if (*domain->alt_name) {
+ add_failed_connection_entry(domain->alt_name, server, result);
+ saf_delete(domain->alt_name);
+ }
+ winbindd_unset_locator_kdc_env(domain);
+}
+
+/* Choose between anonymous or authenticated connections. We need to use
+ an authenticated connection if DCs have the RestrictAnonymous registry
+ entry set > 0, or the "Additional restrictions for anonymous
+ connections" set in the win2k Local Security Policy.
+
+ Caller to free() result in domain, username, password
+*/
+
+static void cm_get_ipc_userpass(char **username, char **domain, char **password)
+{
+ *username = (char *)secrets_fetch(SECRETS_AUTH_USER, NULL);
+ *domain = (char *)secrets_fetch(SECRETS_AUTH_DOMAIN, NULL);
+ *password = (char *)secrets_fetch(SECRETS_AUTH_PASSWORD, NULL);
+
+ if (*username && **username) {
+
+ if (!*domain || !**domain)
+ *domain = smb_xstrdup(lp_workgroup());
+
+ if (!*password || !**password)
+ *password = smb_xstrdup("");
+
+ DEBUG(3, ("cm_get_ipc_userpass: Retrieved auth-user from secrets.tdb [%s\\%s]\n",
+ *domain, *username));
+
+ } else {
+ DEBUG(3, ("cm_get_ipc_userpass: No auth-user defined\n"));
+ *username = smb_xstrdup("");
+ *domain = smb_xstrdup("");
+ *password = smb_xstrdup("");
+ }
+}
+
+static BOOL get_dc_name_via_netlogon(const struct winbindd_domain *domain,
+ fstring dcname, struct in_addr *dc_ip)
+{
+ struct winbindd_domain *our_domain = NULL;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ NTSTATUS result;
+ WERROR werr;
+ TALLOC_CTX *mem_ctx;
+ unsigned int orig_timeout;
+ fstring tmp;
+ char *p;
+
+ /* Hmmmm. We can only open one connection to the NETLOGON pipe at the
+ * moment.... */
+
+ if (IS_DC) {
+ return False;
+ }
+
+ if (domain->primary) {
+ return False;
+ }
+
+ our_domain = find_our_domain();
+
+ if ((mem_ctx = talloc_init("get_dc_name_via_netlogon")) == NULL) {
+ return False;
+ }
+
+ result = cm_connect_netlogon(our_domain, &netlogon_pipe);
+ if (!NT_STATUS_IS_OK(result)) {
+ talloc_destroy(mem_ctx);
+ return False;
+ }
+
+ /* This call can take a long time - allow the server to time out.
+ 35 seconds should do it. */
+
+ orig_timeout = cli_set_timeout(netlogon_pipe->cli, 35000);
+
+ werr = rpccli_netlogon_getanydcname(netlogon_pipe, mem_ctx, our_domain->dcname,
+ domain->name, tmp);
+
+ /* And restore our original timeout. */
+ cli_set_timeout(netlogon_pipe->cli, orig_timeout);
+
+ talloc_destroy(mem_ctx);
+
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(10, ("rpccli_netlogon_getanydcname failed: %s\n",
+ dos_errstr(werr)));
+ return False;
+ }
+
+ /* cli_netlogon_getanydcname gives us a name with \\ */
+ p = tmp;
+ if (*p == '\\') {
+ p+=1;
+ }
+ if (*p == '\\') {
+ p+=1;
+ }
+
+ fstrcpy(dcname, p);
+
+ DEBUG(10, ("rpccli_netlogon_getanydcname returned %s\n", dcname));
+
+ if (!resolve_name(dcname, dc_ip, 0x20)) {
+ return False;
+ }
+
+ return True;
+}
+
+/************************************************************************
+ Given a fd with a just-connected TCP connection to a DC, open a connection
+ to the pipe.
+************************************************************************/
+
+static NTSTATUS cm_prepare_connection(const struct winbindd_domain *domain,
+ const int sockfd,
+ const char *controller,
+ struct cli_state **cli,
+ BOOL *retry)
+{
+ char *machine_password, *machine_krb5_principal, *machine_account;
+ char *ipc_username, *ipc_domain, *ipc_password;
+
+ BOOL got_mutex;
+
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ struct sockaddr peeraddr;
+ socklen_t peeraddr_len;
+
+ struct sockaddr_in *peeraddr_in = (struct sockaddr_in *)&peeraddr;
+
+ DEBUG(10,("cm_prepare_connection: connecting to DC %s for domain %s\n",
+ controller, domain->name ));
+
+ machine_password = secrets_fetch_machine_password(lp_workgroup(), NULL,
+ NULL);
+
+ if (asprintf(&machine_account, "%s$", global_myname()) == -1) {
+ SAFE_FREE(machine_password);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (asprintf(&machine_krb5_principal, "%s$@%s", global_myname(),
+ lp_realm()) == -1) {
+ SAFE_FREE(machine_account);
+ SAFE_FREE(machine_password);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cm_get_ipc_userpass(&ipc_username, &ipc_domain, &ipc_password);
+
+ *retry = True;
+
+ got_mutex = secrets_named_mutex(controller,
+ WINBIND_SERVER_MUTEX_WAIT_TIME);
+
+ if (!got_mutex) {
+ DEBUG(0,("cm_prepare_connection: mutex grab failed for %s\n",
+ controller));
+ result = NT_STATUS_POSSIBLE_DEADLOCK;
+ goto done;
+ }
+
+ if ((*cli = cli_initialise()) == NULL) {
+ DEBUG(1, ("Could not cli_initialize\n"));
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ (*cli)->timeout = 10000; /* 10 seconds */
+ (*cli)->fd = sockfd;
+ fstrcpy((*cli)->desthost, controller);
+ (*cli)->use_kerberos = True;
+
+ peeraddr_len = sizeof(peeraddr);
+
+ if ((getpeername((*cli)->fd, &peeraddr, &peeraddr_len) != 0) ||
+ (peeraddr_len != sizeof(struct sockaddr_in)) ||
+ (peeraddr_in->sin_family != PF_INET))
+ {
+ DEBUG(0,("cm_prepare_connection: %s\n", strerror(errno)));
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (ntohs(peeraddr_in->sin_port) == 139) {
+ struct nmb_name calling;
+ struct nmb_name called;
+
+ make_nmb_name(&calling, global_myname(), 0x0);
+ make_nmb_name(&called, "*SMBSERVER", 0x20);
+
+ if (!cli_session_request(*cli, &calling, &called)) {
+ DEBUG(8, ("cli_session_request failed for %s\n",
+ controller));
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ cli_setup_signing_state(*cli, Undefined);
+
+ if (!cli_negprot(*cli)) {
+ DEBUG(1, ("cli_negprot failed\n"));
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if ((*cli)->protocol >= PROTOCOL_NT1 && (*cli)->capabilities & CAP_EXTENDED_SECURITY) {
+ ADS_STATUS ads_status;
+
+ if (lp_security() == SEC_ADS) {
+
+ /* Try a krb5 session */
+
+ (*cli)->use_kerberos = True;
+ DEBUG(5, ("connecting to %s from %s with kerberos principal "
+ "[%s]\n", controller, global_myname(),
+ machine_krb5_principal));
+
+ winbindd_set_locator_kdc_envs(domain);
+
+ ads_status = cli_session_setup_spnego(*cli,
+ machine_krb5_principal,
+ machine_password,
+ lp_workgroup());
+
+ if (!ADS_ERR_OK(ads_status)) {
+ DEBUG(4,("failed kerberos session setup with %s\n",
+ ads_errstr(ads_status)));
+ }
+
+ result = ads_ntstatus(ads_status);
+ if (NT_STATUS_IS_OK(result)) {
+ /* Ensure creds are stored for NTLMSSP authenticated pipe access. */
+ cli_init_creds(*cli, machine_account, lp_workgroup(), machine_password);
+ goto session_setup_done;
+ }
+ }
+
+ /* Fall back to non-kerberos session setup using NTLMSSP SPNEGO with the machine account. */
+ (*cli)->use_kerberos = False;
+
+ DEBUG(5, ("connecting to %s from %s with username "
+ "[%s]\\[%s]\n", controller, global_myname(),
+ lp_workgroup(), machine_account));
+
+ ads_status = cli_session_setup_spnego(*cli,
+ machine_account,
+ machine_password,
+ lp_workgroup());
+ if (!ADS_ERR_OK(ads_status)) {
+ DEBUG(4, ("authenticated session setup failed with %s\n",
+ ads_errstr(ads_status)));
+ }
+
+ result = ads_ntstatus(ads_status);
+ if (NT_STATUS_IS_OK(result)) {
+ /* Ensure creds are stored for NTLMSSP authenticated pipe access. */
+ cli_init_creds(*cli, machine_account, lp_workgroup(), machine_password);
+ goto session_setup_done;
+ }
+ }
+
+ /* Fall back to non-kerberos session setup */
+
+ (*cli)->use_kerberos = False;
+
+ if ((((*cli)->sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) != 0) &&
+ (strlen(ipc_username) > 0)) {
+
+ /* Only try authenticated if we have a username */
+
+ DEBUG(5, ("connecting to %s from %s with username "
+ "[%s]\\[%s]\n", controller, global_myname(),
+ ipc_domain, ipc_username));
+
+ if (NT_STATUS_IS_OK(cli_session_setup(
+ *cli, ipc_username,
+ ipc_password, strlen(ipc_password)+1,
+ ipc_password, strlen(ipc_password)+1,
+ ipc_domain))) {
+ /* Successful logon with given username. */
+ cli_init_creds(*cli, ipc_username, ipc_domain, ipc_password);
+ goto session_setup_done;
+ } else {
+ DEBUG(4, ("authenticated session setup with user %s\\%s failed.\n",
+ ipc_domain, ipc_username ));
+ }
+ }
+
+ /* Fall back to anonymous connection, this might fail later */
+
+ if (NT_STATUS_IS_OK(cli_session_setup(*cli, "", NULL, 0,
+ NULL, 0, ""))) {
+ DEBUG(5, ("Connected anonymously\n"));
+ cli_init_creds(*cli, "", "", "");
+ goto session_setup_done;
+ }
+
+ result = cli_nt_error(*cli);
+
+ if (NT_STATUS_IS_OK(result))
+ result = NT_STATUS_UNSUCCESSFUL;
+
+ /* We can't session setup */
+
+ goto done;
+
+ session_setup_done:
+
+ /* cache the server name for later connections */
+
+ saf_store( domain->name, (*cli)->desthost );
+ if (domain->alt_name && (*cli)->use_kerberos) {
+ saf_store( domain->alt_name, (*cli)->desthost );
+ }
+
+ winbindd_set_locator_kdc_envs(domain);
+
+ if (!cli_send_tconX(*cli, "IPC$", "IPC", "", 0)) {
+
+ result = cli_nt_error(*cli);
+
+ DEBUG(1,("failed tcon_X with %s\n", nt_errstr(result)));
+
+ if (NT_STATUS_IS_OK(result))
+ result = NT_STATUS_UNSUCCESSFUL;
+
+ goto done;
+ }
+
+ secrets_named_mutex_release(controller);
+ got_mutex = False;
+ *retry = False;
+
+ /* set the domain if empty; needed for schannel connections */
+ if ( !*(*cli)->domain ) {
+ fstrcpy( (*cli)->domain, domain->name );
+ }
+
+ result = NT_STATUS_OK;
+
+ done:
+ if (got_mutex) {
+ secrets_named_mutex_release(controller);
+ }
+
+ SAFE_FREE(machine_account);
+ SAFE_FREE(machine_password);
+ SAFE_FREE(machine_krb5_principal);
+ SAFE_FREE(ipc_username);
+ SAFE_FREE(ipc_domain);
+ SAFE_FREE(ipc_password);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ winbind_add_failed_connection_entry(domain, controller, result);
+ if ((*cli) != NULL) {
+ cli_shutdown(*cli);
+ *cli = NULL;
+ }
+ }
+
+ return result;
+}
+
+static BOOL add_one_dc_unique(TALLOC_CTX *mem_ctx, const char *domain_name,
+ const char *dcname, struct in_addr ip,
+ struct dc_name_ip **dcs, int *num)
+{
+ if (!NT_STATUS_IS_OK(check_negative_conn_cache(domain_name, dcname))) {
+ DEBUG(10, ("DC %s was in the negative conn cache\n", dcname));
+ return False;
+ }
+
+ *dcs = TALLOC_REALLOC_ARRAY(mem_ctx, *dcs, struct dc_name_ip, (*num)+1);
+
+ if (*dcs == NULL)
+ return False;
+
+ fstrcpy((*dcs)[*num].name, dcname);
+ (*dcs)[*num].ip = ip;
+ *num += 1;
+ return True;
+}
+
+static BOOL add_sockaddr_to_array(TALLOC_CTX *mem_ctx,
+ struct in_addr ip, uint16 port,
+ struct sockaddr_in **addrs, int *num)
+{
+ *addrs = TALLOC_REALLOC_ARRAY(mem_ctx, *addrs, struct sockaddr_in, (*num)+1);
+
+ if (*addrs == NULL) {
+ *num = 0;
+ return False;
+ }
+
+ (*addrs)[*num].sin_family = PF_INET;
+ putip((char *)&((*addrs)[*num].sin_addr), (char *)&ip);
+ (*addrs)[*num].sin_port = htons(port);
+
+ *num += 1;
+ return True;
+}
+
+static void mailslot_name(struct in_addr dc_ip, fstring name)
+{
+ fstr_sprintf(name, "\\MAILSLOT\\NET\\GETDC%X", dc_ip.s_addr);
+}
+
+static BOOL send_getdc_request(struct in_addr dc_ip,
+ const char *domain_name,
+ const DOM_SID *sid)
+{
+ pstring outbuf;
+ char *p;
+ fstring my_acct_name;
+ fstring my_mailslot;
+
+ mailslot_name(dc_ip, my_mailslot);
+
+ memset(outbuf, '\0', sizeof(outbuf));
+
+ p = outbuf;
+
+ SCVAL(p, 0, SAMLOGON);
+ p++;
+
+ SCVAL(p, 0, 0); /* Count pointer ... */
+ p++;
+
+ SIVAL(p, 0, 0); /* The sender's token ... */
+ p += 2;
+
+ p += dos_PutUniCode(p, global_myname(), sizeof(pstring), True);
+ fstr_sprintf(my_acct_name, "%s$", global_myname());
+ p += dos_PutUniCode(p, my_acct_name, sizeof(pstring), True);
+
+ memcpy(p, my_mailslot, strlen(my_mailslot)+1);
+ p += strlen(my_mailslot)+1;
+
+ SIVAL(p, 0, 0x80);
+ p+=4;
+
+ SIVAL(p, 0, sid_size(sid));
+ p+=4;
+
+ p = ALIGN4(p, outbuf);
+
+ sid_linearize(p, sid_size(sid), sid);
+ p += sid_size(sid);
+
+ SIVAL(p, 0, 1);
+ SSVAL(p, 4, 0xffff);
+ SSVAL(p, 6, 0xffff);
+ p+=8;
+
+ return cli_send_mailslot(winbind_messaging_context(),
+ False, "\\MAILSLOT\\NET\\NTLOGON", 0,
+ outbuf, PTR_DIFF(p, outbuf),
+ global_myname(), 0, domain_name, 0x1c,
+ dc_ip);
+}
+
+static BOOL receive_getdc_response(struct in_addr dc_ip,
+ const char *domain_name,
+ fstring dc_name)
+{
+ struct packet_struct *packet;
+ fstring my_mailslot;
+ char *buf, *p;
+ fstring dcname, user, domain;
+ int len;
+
+ mailslot_name(dc_ip, my_mailslot);
+
+ packet = receive_unexpected(DGRAM_PACKET, 0, my_mailslot);
+
+ if (packet == NULL) {
+ DEBUG(5, ("Did not receive packet for %s\n", my_mailslot));
+ return False;
+ }
+
+ DEBUG(5, ("Received packet for %s\n", my_mailslot));
+
+ buf = packet->packet.dgram.data;
+ len = packet->packet.dgram.datasize;
+
+ if (len < 70) {
+ /* 70 is a completely arbitrary value to make sure
+ the SVAL below does not read uninitialized memory */
+ DEBUG(3, ("GetDC got short response\n"));
+ return False;
+ }
+
+ /* This should be (buf-4)+SVAL(buf-4, smb_vwv12)... */
+ p = buf+SVAL(buf, smb_vwv10);
+
+ if (CVAL(p,0) != SAMLOGON_R) {
+ DEBUG(8, ("GetDC got invalid response type %d\n", CVAL(p, 0)));
+ return False;
+ }
+
+ p+=2;
+ pull_ucs2(buf, dcname, p, sizeof(dcname), PTR_DIFF(buf+len, p),
+ STR_TERMINATE|STR_NOALIGN);
+ p = skip_unibuf(p, PTR_DIFF(buf+len, p));
+ pull_ucs2(buf, user, p, sizeof(dcname), PTR_DIFF(buf+len, p),
+ STR_TERMINATE|STR_NOALIGN);
+ p = skip_unibuf(p, PTR_DIFF(buf+len, p));
+ pull_ucs2(buf, domain, p, sizeof(dcname), PTR_DIFF(buf+len, p),
+ STR_TERMINATE|STR_NOALIGN);
+ p = skip_unibuf(p, PTR_DIFF(buf+len, p));
+
+ if (!strequal(domain, domain_name)) {
+ DEBUG(3, ("GetDC: Expected domain %s, got %s\n",
+ domain_name, domain));
+ return False;
+ }
+
+ p = dcname;
+ if (*p == '\\') p += 1;
+ if (*p == '\\') p += 1;
+
+ fstrcpy(dc_name, p);
+
+ DEBUG(10, ("GetDC gave name %s for domain %s\n",
+ dc_name, domain));
+
+ return True;
+}
+
+/*******************************************************************
+ convert an ip to a name
+*******************************************************************/
+
+static BOOL dcip_to_name(const struct winbindd_domain *domain, struct in_addr ip, fstring name )
+{
+ struct ip_service ip_list;
+
+ ip_list.ip = ip;
+ ip_list.port = 0;
+
+#ifdef WITH_ADS
+ /* For active directory servers, try to get the ldap server name.
+ None of these failures should be considered critical for now */
+
+ if (lp_security() == SEC_ADS) {
+ ADS_STRUCT *ads;
+
+ ads = ads_init(domain->alt_name, domain->name, NULL);
+ ads->auth.flags |= ADS_AUTH_NO_BIND;
+
+ if (ads_try_connect( ads, inet_ntoa(ip) ) ) {
+ /* We got a cldap packet. */
+ fstrcpy(name, ads->config.ldap_server_name);
+ namecache_store(name, 0x20, 1, &ip_list);
+
+ DEBUG(10,("dcip_to_name: flags = 0x%x\n", (unsigned int)ads->config.flags));
+
+ if (domain->primary && (ads->config.flags & ADS_KDC)) {
+ if (ads_closest_dc(ads)) {
+ char *sitename = sitename_fetch(ads->config.realm);
+
+ /* We're going to use this KDC for this realm/domain.
+ If we are using sites, then force the krb5 libs
+ to use this KDC. */
+
+ create_local_private_krb5_conf_for_domain(domain->alt_name,
+ domain->name,
+ sitename,
+ ip);
+
+ SAFE_FREE(sitename);
+ } else {
+ /* use an off site KDC */
+ create_local_private_krb5_conf_for_domain(domain->alt_name,
+ domain->name,
+ NULL,
+ ip);
+ }
+ winbindd_set_locator_kdc_envs(domain);
+
+ /* Ensure we contact this DC also. */
+ saf_store( domain->name, name);
+ saf_store( domain->alt_name, name);
+ }
+
+ ads_destroy( &ads );
+ return True;
+ }
+
+ ads_destroy( &ads );
+ }
+#endif
+
+ /* try GETDC requests next */
+
+ if (send_getdc_request(ip, domain->name, &domain->sid)) {
+ int i;
+ smb_msleep(100);
+ for (i=0; i<5; i++) {
+ if (receive_getdc_response(ip, domain->name, name)) {
+ namecache_store(name, 0x20, 1, &ip_list);
+ return True;
+ }
+ smb_msleep(500);
+ }
+ }
+
+ /* try node status request */
+
+ if ( name_status_find(domain->name, 0x1c, 0x20, ip, name) ) {
+ namecache_store(name, 0x20, 1, &ip_list);
+ return True;
+ }
+ return False;
+}
+
+/*******************************************************************
+ Retreive a list of IP address for domain controllers. Fill in
+ the dcs[] with results.
+*******************************************************************/
+
+static BOOL get_dcs(TALLOC_CTX *mem_ctx, const struct winbindd_domain *domain,
+ struct dc_name_ip **dcs, int *num_dcs)
+{
+ fstring dcname;
+ struct in_addr ip;
+ struct ip_service *ip_list = NULL;
+ int iplist_size = 0;
+ int i;
+ BOOL is_our_domain;
+ enum security_types sec = (enum security_types)lp_security();
+
+ is_our_domain = strequal(domain->name, lp_workgroup());
+
+ if ( !is_our_domain
+ && get_dc_name_via_netlogon(domain, dcname, &ip)
+ && add_one_dc_unique(mem_ctx, domain->name, dcname, ip, dcs, num_dcs) )
+ {
+ DEBUG(10, ("Retrieved DC %s at %s via netlogon\n",
+ dcname, inet_ntoa(ip)));
+ return True;
+ }
+
+ if (sec == SEC_ADS) {
+ char *sitename = NULL;
+
+ /* We need to make sure we know the local site before
+ doing any DNS queries, as this will restrict the
+ get_sorted_dc_list() call below to only fetching
+ DNS records for the correct site. */
+
+ /* Find any DC to get the site record.
+ We deliberately don't care about the
+ return here. */
+
+ get_dc_name(domain->name, domain->alt_name, dcname, &ip);
+
+ sitename = sitename_fetch(domain->alt_name);
+ if (sitename) {
+
+ /* Do the site-specific AD dns lookup first. */
+ get_sorted_dc_list(domain->alt_name, sitename, &ip_list, &iplist_size, True);
+
+ for ( i=0; i<iplist_size; i++ ) {
+ add_one_dc_unique(mem_ctx, domain->name, inet_ntoa(ip_list[i].ip),
+ ip_list[i].ip, dcs, num_dcs);
+ }
+
+ SAFE_FREE(ip_list);
+ SAFE_FREE(sitename);
+ iplist_size = 0;
+ }
+
+ /* Now we add DCs from the main AD dns lookup. */
+ get_sorted_dc_list(domain->alt_name, NULL, &ip_list, &iplist_size, True);
+
+ for ( i=0; i<iplist_size; i++ ) {
+ add_one_dc_unique(mem_ctx, domain->name, inet_ntoa(ip_list[i].ip),
+ ip_list[i].ip, dcs, num_dcs);
+ }
+ }
+
+ /* try standard netbios queries if no ADS */
+
+ if (iplist_size==0) {
+ get_sorted_dc_list(domain->name, NULL, &ip_list, &iplist_size, False);
+ }
+
+ /* FIXME!! this is where we should re-insert the GETDC requests --jerry */
+
+ /* now add to the dc array. We'll wait until the last minute
+ to look up the name of the DC. But we fill in the char* for
+ the ip now in to make the failed connection cache work */
+
+ for ( i=0; i<iplist_size; i++ ) {
+ add_one_dc_unique(mem_ctx, domain->name, inet_ntoa(ip_list[i].ip),
+ ip_list[i].ip, dcs, num_dcs);
+ }
+
+ SAFE_FREE( ip_list );
+
+ return True;
+}
+
+static BOOL find_new_dc(TALLOC_CTX *mem_ctx,
+ const struct winbindd_domain *domain,
+ fstring dcname, struct sockaddr_in *addr, int *fd)
+{
+ struct dc_name_ip *dcs = NULL;
+ int num_dcs = 0;
+
+ const char **dcnames = NULL;
+ int num_dcnames = 0;
+
+ struct sockaddr_in *addrs = NULL;
+ int num_addrs = 0;
+
+ int i, fd_index;
+
+ again:
+ if (!get_dcs(mem_ctx, domain, &dcs, &num_dcs) || (num_dcs == 0))
+ return False;
+
+ for (i=0; i<num_dcs; i++) {
+
+ if (!add_string_to_array(mem_ctx, dcs[i].name,
+ &dcnames, &num_dcnames)) {
+ return False;
+ }
+ if (!add_sockaddr_to_array(mem_ctx, dcs[i].ip, 445,
+ &addrs, &num_addrs)) {
+ return False;
+ }
+
+ if (!add_string_to_array(mem_ctx, dcs[i].name,
+ &dcnames, &num_dcnames)) {
+ return False;
+ }
+ if (!add_sockaddr_to_array(mem_ctx, dcs[i].ip, 139,
+ &addrs, &num_addrs)) {
+ return False;
+ }
+ }
+
+ if ((num_dcnames == 0) || (num_dcnames != num_addrs))
+ return False;
+
+ if ((addrs == NULL) || (dcnames == NULL))
+ return False;
+
+ /* 5 second timeout. */
+ if ( !open_any_socket_out(addrs, num_addrs, 5000, &fd_index, fd) )
+ {
+ for (i=0; i<num_dcs; i++) {
+ DEBUG(10, ("find_new_dc: open_any_socket_out failed for "
+ "domain %s address %s. Error was %s\n",
+ domain->name, inet_ntoa(dcs[i].ip), strerror(errno) ));
+ winbind_add_failed_connection_entry(domain,
+ dcs[i].name, NT_STATUS_UNSUCCESSFUL);
+ }
+ return False;
+ }
+
+ *addr = addrs[fd_index];
+
+ if (*dcnames[fd_index] != '\0' && !is_ipaddress(dcnames[fd_index])) {
+ /* Ok, we've got a name for the DC */
+ fstrcpy(dcname, dcnames[fd_index]);
+ return True;
+ }
+
+ /* Try to figure out the name */
+ if (dcip_to_name( domain, addr->sin_addr, dcname )) {
+ return True;
+ }
+
+ /* We can not continue without the DC's name */
+ winbind_add_failed_connection_entry(domain, dcs[fd_index].name,
+ NT_STATUS_UNSUCCESSFUL);
+ goto again;
+}
+
+static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
+ struct winbindd_cm_conn *new_conn)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS result;
+ char *saf_servername = saf_fetch( domain->name );
+ int retries;
+
+ if ((mem_ctx = talloc_init("cm_open_connection")) == NULL) {
+ SAFE_FREE(saf_servername);
+ set_domain_offline(domain);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* we have to check the server affinity cache here since
+ later we selecte a DC based on response time and not preference */
+
+ /* Check the negative connection cache
+ before talking to it. It going down may have
+ triggered the reconnection. */
+
+ if ( saf_servername && NT_STATUS_IS_OK(check_negative_conn_cache( domain->name, saf_servername))) {
+
+ DEBUG(10,("cm_open_connection: saf_servername is '%s' for domain %s\n",
+ saf_servername, domain->name ));
+
+ /* convert an ip address to a name */
+ if ( is_ipaddress( saf_servername ) ) {
+ fstring saf_name;
+ struct in_addr ip;
+
+ ip = *interpret_addr2( saf_servername );
+ if (dcip_to_name( domain, ip, saf_name )) {
+ fstrcpy( domain->dcname, saf_name );
+ } else {
+ winbind_add_failed_connection_entry(
+ domain, saf_servername,
+ NT_STATUS_UNSUCCESSFUL);
+ }
+ } else {
+ fstrcpy( domain->dcname, saf_servername );
+ }
+
+ SAFE_FREE( saf_servername );
+ }
+
+ for (retries = 0; retries < 3; retries++) {
+
+ int fd = -1;
+ BOOL retry = False;
+
+ result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+
+ DEBUG(10,("cm_open_connection: dcname is '%s' for domain %s\n",
+ domain->dcname, domain->name ));
+
+ if (*domain->dcname
+ && NT_STATUS_IS_OK(check_negative_conn_cache( domain->name, domain->dcname))
+ && (resolve_name(domain->dcname, &domain->dcaddr.sin_addr, 0x20)))
+ {
+ struct sockaddr_in *addrs = NULL;
+ int num_addrs = 0;
+ int dummy = 0;
+
+ if (!add_sockaddr_to_array(mem_ctx, domain->dcaddr.sin_addr, 445, &addrs, &num_addrs)) {
+ set_domain_offline(domain);
+ talloc_destroy(mem_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!add_sockaddr_to_array(mem_ctx, domain->dcaddr.sin_addr, 139, &addrs, &num_addrs)) {
+ set_domain_offline(domain);
+ talloc_destroy(mem_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* 5 second timeout. */
+ if (!open_any_socket_out(addrs, num_addrs, 5000, &dummy, &fd)) {
+ fd = -1;
+ }
+ }
+
+ if ((fd == -1)
+ && !find_new_dc(mem_ctx, domain, domain->dcname, &domain->dcaddr, &fd))
+ {
+ /* This is the one place where we will
+ set the global winbindd offline state
+ to true, if a "WINBINDD_OFFLINE" entry
+ is found in the winbindd cache. */
+ set_global_winbindd_state_offline();
+ break;
+ }
+
+ new_conn->cli = NULL;
+
+ result = cm_prepare_connection(domain, fd, domain->dcname,
+ &new_conn->cli, &retry);
+
+ if (!retry)
+ break;
+ }
+
+ if (NT_STATUS_IS_OK(result)) {
+
+ winbindd_set_locator_kdc_envs(domain);
+
+ if (domain->online == False) {
+ /* We're changing state from offline to online. */
+ set_global_winbindd_state_online();
+ }
+ set_domain_online(domain);
+ } else {
+ /* Ensure we setup the retry handler. */
+ set_domain_offline(domain);
+ }
+
+ talloc_destroy(mem_ctx);
+ return result;
+}
+
+/* Close down all open pipes on a connection. */
+
+void invalidate_cm_connection(struct winbindd_cm_conn *conn)
+{
+ /* We're closing down a possibly dead
+ connection. Don't have impossibly long (10s) timeouts. */
+
+ if (conn->cli) {
+ cli_set_timeout(conn->cli, 1000); /* 1 second. */
+ }
+
+ if (conn->samr_pipe != NULL) {
+ if (!cli_rpc_pipe_close(conn->samr_pipe)) {
+ /* Ok, it must be dead. Drop timeout to 0.5 sec. */
+ if (conn->cli) {
+ cli_set_timeout(conn->cli, 500);
+ }
+ }
+ conn->samr_pipe = NULL;
+ }
+
+ if (conn->lsa_pipe != NULL) {
+ if (!cli_rpc_pipe_close(conn->lsa_pipe)) {
+ /* Ok, it must be dead. Drop timeout to 0.5 sec. */
+ if (conn->cli) {
+ cli_set_timeout(conn->cli, 500);
+ }
+ }
+ conn->lsa_pipe = NULL;
+ }
+
+ if (conn->netlogon_pipe != NULL) {
+ if (!cli_rpc_pipe_close(conn->netlogon_pipe)) {
+ /* Ok, it must be dead. Drop timeout to 0.5 sec. */
+ if (conn->cli) {
+ cli_set_timeout(conn->cli, 500);
+ }
+ }
+ conn->netlogon_pipe = NULL;
+ }
+
+ if (conn->cli) {
+ cli_shutdown(conn->cli);
+ }
+
+ conn->cli = NULL;
+}
+
+void close_conns_after_fork(void)
+{
+ struct winbindd_domain *domain;
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->conn.cli == NULL)
+ continue;
+
+ if (domain->conn.cli->fd == -1)
+ continue;
+
+ close(domain->conn.cli->fd);
+ domain->conn.cli->fd = -1;
+ }
+}
+
+static BOOL connection_ok(struct winbindd_domain *domain)
+{
+ if (domain->conn.cli == NULL) {
+ DEBUG(8, ("connection_ok: Connection to %s for domain %s has NULL "
+ "cli!\n", domain->dcname, domain->name));
+ return False;
+ }
+
+ if (!domain->conn.cli->initialised) {
+ DEBUG(3, ("connection_ok: Connection to %s for domain %s was never "
+ "initialised!\n", domain->dcname, domain->name));
+ return False;
+ }
+
+ if (domain->conn.cli->fd == -1) {
+ DEBUG(3, ("connection_ok: Connection to %s for domain %s has died or was "
+ "never started (fd == -1)\n",
+ domain->dcname, domain->name));
+ return False;
+ }
+
+ if (domain->online == False) {
+ DEBUG(3, ("connection_ok: Domain %s is offline\n", domain->name));
+ return False;
+ }
+
+ return True;
+}
+
+/* Initialize a new connection up to the RPC BIND.
+ Bypass online status check so always does network calls. */
+
+static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain)
+{
+ NTSTATUS result;
+
+ /* Internal connections never use the network. */
+ if (domain->internal) {
+ domain->initialized = True;
+ return NT_STATUS_OK;
+ }
+
+ if (connection_ok(domain)) {
+ if (!domain->initialized) {
+ set_dc_type_and_flags(domain);
+ }
+ return NT_STATUS_OK;
+ }
+
+ invalidate_cm_connection(&domain->conn);
+
+ result = cm_open_connection(domain, &domain->conn);
+
+ if (NT_STATUS_IS_OK(result) && !domain->initialized) {
+ set_dc_type_and_flags(domain);
+ }
+
+ return result;
+}
+
+NTSTATUS init_dc_connection(struct winbindd_domain *domain)
+{
+ if (domain->initialized && !domain->online) {
+ /* We check for online status elsewhere. */
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ return init_dc_connection_network(domain);
+}
+
+/******************************************************************************
+ Set the trust flags (direction and forest location) for a domain
+******************************************************************************/
+
+static BOOL set_dc_type_and_flags_trustinfo( struct winbindd_domain *domain )
+{
+ struct winbindd_domain *our_domain;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ struct ds_domain_trust *domains = NULL;
+ int count = 0;
+ int i;
+ uint32 flags = (DS_DOMAIN_IN_FOREST |
+ DS_DOMAIN_DIRECT_OUTBOUND |
+ DS_DOMAIN_DIRECT_INBOUND);
+ struct rpc_pipe_client *cli;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ DEBUG(5, ("set_dc_type_and_flags_trustinfo: domain %s\n", domain->name ));
+
+ /* Our primary domain doesn't need to worry about trust flags.
+ Force it to go through the network setup */
+ if ( domain->primary ) {
+ return False;
+ }
+
+ our_domain = find_our_domain();
+
+ if ( !connection_ok(our_domain) ) {
+ DEBUG(3,("set_dc_type_and_flags_trustinfo: No connection to our domain!\n"));
+ return False;
+ }
+
+ /* This won't work unless our domain is AD */
+
+ if ( !our_domain->active_directory ) {
+ return False;
+ }
+
+ /* Use DsEnumerateDomainTrusts to get us the trust direction
+ and type */
+
+ result = cm_connect_netlogon(our_domain, &cli);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(5, ("set_dc_type_and_flags_trustinfo: Could not open "
+ "a connection to %s for PIPE_NETLOGON (%s)\n",
+ domain->name, nt_errstr(result)));
+ return False;
+ }
+
+ if ( (mem_ctx = talloc_init("set_dc_type_and_flags_trustinfo")) == NULL ) {
+ DEBUG(0,("set_dc_type_and_flags_trustinfo: talloc_init() failed!\n"));
+ return False;
+ }
+
+ result = rpccli_ds_enum_domain_trusts(cli, mem_ctx,
+ cli->cli->desthost,
+ flags, &domains,
+ (unsigned int *)&count);
+
+ /* Now find the domain name and get the flags */
+
+ for ( i=0; i<count; i++ ) {
+ if ( strequal( domain->name, domains[i].netbios_domain ) ) {
+ domain->domain_flags = domains[i].flags;
+ domain->domain_type = domains[i].trust_type;
+ domain->domain_trust_attribs = domains[i].trust_attributes;
+
+ if ( domain->domain_type == DS_DOMAIN_TRUST_TYPE_UPLEVEL )
+ domain->active_directory = True;
+
+ /* This flag is only set if the domain is *our*
+ primary domain and the primary domain is in
+ native mode */
+
+ domain->native_mode = (domain->domain_flags & DS_DOMAIN_NATIVE_MODE);
+
+ DEBUG(5, ("set_dc_type_and_flags_trustinfo: domain %s is %sin "
+ "native mode.\n", domain->name,
+ domain->native_mode ? "" : "NOT "));
+
+ DEBUG(5,("set_dc_type_and_flags_trustinfo: domain %s is %s"
+ "running active directory.\n", domain->name,
+ domain->active_directory ? "" : "NOT "));
+
+
+ domain->initialized = True;
+
+ if ( !winbindd_can_contact_domain( domain) )
+ domain->internal = True;
+
+ break;
+ }
+ }
+
+ talloc_destroy( mem_ctx );
+
+ return domain->initialized;
+}
+
+/******************************************************************************
+ We can 'sense' certain things about the DC by it's replies to certain
+ questions.
+
+ This tells us if this particular remote server is Active Directory, and if it
+ is native mode.
+******************************************************************************/
+
+static void set_dc_type_and_flags_connect( struct winbindd_domain *domain )
+{
+ NTSTATUS result;
+ DS_DOMINFO_CTR ctr;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct rpc_pipe_client *cli;
+ POLICY_HND pol;
+
+ char *domain_name = NULL;
+ char *dns_name = NULL;
+ char *forest_name = NULL;
+ DOM_SID *dom_sid = NULL;
+
+ ZERO_STRUCT( ctr );
+
+ if (!connection_ok(domain)) {
+ return;
+ }
+
+ DEBUG(5, ("set_dc_type_and_flags_connect: domain %s\n", domain->name ));
+
+ cli = cli_rpc_pipe_open_noauth(domain->conn.cli, PI_LSARPC_DS,
+ &result);
+
+ if (cli == NULL) {
+ DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to "
+ "PI_LSARPC_DS on domain %s: (%s)\n",
+ domain->name, nt_errstr(result)));
+
+ /* if this is just a non-AD domain we need to continue
+ * identifying so that we can in the end return with
+ * domain->initialized = True - gd */
+
+ goto no_lsarpc_ds;
+ }
+
+ result = rpccli_ds_getprimarydominfo(cli, cli->cli->mem_ctx,
+ DsRolePrimaryDomainInfoBasic,
+ &ctr);
+ cli_rpc_pipe_close(cli);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(5, ("set_dc_type_and_flags_connect: rpccli_ds_getprimarydominfo "
+ "on domain %s failed: (%s)\n",
+ domain->name, nt_errstr(result)));
+
+ /* older samba3 DCs will return DCERPC_FAULT_OP_RNG_ERROR for
+ * every opcode on the LSARPC_DS pipe, continue with
+ * no_lsarpc_ds mode here as well to get domain->initialized
+ * set - gd */
+
+ if (NT_STATUS_V(result) == DCERPC_FAULT_OP_RNG_ERROR) {
+ goto no_lsarpc_ds;
+ }
+
+ return;
+ }
+
+ if ((ctr.basic->flags & DSROLE_PRIMARY_DS_RUNNING) &&
+ !(ctr.basic->flags & DSROLE_PRIMARY_DS_MIXED_MODE)) {
+ domain->native_mode = True;
+ } else {
+ domain->native_mode = False;
+ }
+
+no_lsarpc_ds:
+ cli = cli_rpc_pipe_open_noauth(domain->conn.cli, PI_LSARPC, &result);
+
+ if (cli == NULL) {
+ DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to "
+ "PI_LSARPC on domain %s: (%s)\n",
+ domain->name, nt_errstr(result)));
+ cli_rpc_pipe_close(cli);
+ return;
+ }
+
+ mem_ctx = talloc_init("set_dc_type_and_flags on domain %s\n",
+ domain->name);
+ if (!mem_ctx) {
+ DEBUG(1, ("set_dc_type_and_flags_connect: talloc_init() failed\n"));
+ cli_rpc_pipe_close(cli);
+ return;
+ }
+
+ result = rpccli_lsa_open_policy2(cli, mem_ctx, True,
+ SEC_RIGHTS_MAXIMUM_ALLOWED, &pol);
+
+ if (NT_STATUS_IS_OK(result)) {
+ /* This particular query is exactly what Win2k clients use
+ to determine that the DC is active directory */
+ result = rpccli_lsa_query_info_policy2(cli, mem_ctx, &pol,
+ 12, &domain_name,
+ &dns_name, &forest_name,
+ NULL, &dom_sid);
+ }
+
+ if (NT_STATUS_IS_OK(result)) {
+ domain->active_directory = True;
+
+ if (domain_name)
+ fstrcpy(domain->name, domain_name);
+
+ if (dns_name)
+ fstrcpy(domain->alt_name, dns_name);
+
+ if ( forest_name )
+ fstrcpy(domain->forest_name, forest_name);
+
+ if (dom_sid)
+ sid_copy(&domain->sid, dom_sid);
+ } else {
+ domain->active_directory = False;
+
+ result = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ SEC_RIGHTS_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(result))
+ goto done;
+
+ result = rpccli_lsa_query_info_policy(cli, mem_ctx,
+ &pol, 5, &domain_name,
+ &dom_sid);
+
+ if (NT_STATUS_IS_OK(result)) {
+ if (domain_name)
+ fstrcpy(domain->name, domain_name);
+
+ if (dom_sid)
+ sid_copy(&domain->sid, dom_sid);
+ }
+ }
+done:
+
+ DEBUG(5, ("set_dc_type_and_flags_connect: domain %s is %sin native mode.\n",
+ domain->name, domain->native_mode ? "" : "NOT "));
+
+ DEBUG(5,("set_dc_type_and_flags_connect: domain %s is %srunning active directory.\n",
+ domain->name, domain->active_directory ? "" : "NOT "));
+
+ cli_rpc_pipe_close(cli);
+
+ talloc_destroy(mem_ctx);
+
+ domain->initialized = True;
+}
+
+/**********************************************************************
+ Set the domain_flags (trust attributes, domain operating modes, etc...
+***********************************************************************/
+
+static void set_dc_type_and_flags( struct winbindd_domain *domain )
+{
+ /* we always have to contact our primary domain */
+
+ if ( domain->primary ) {
+ DEBUG(10,("set_dc_type_and_flags: setting up flags for "
+ "primary domain\n"));
+ set_dc_type_and_flags_connect( domain );
+ return;
+ }
+
+ /* Use our DC to get the information if possible */
+
+ if ( !set_dc_type_and_flags_trustinfo( domain ) ) {
+ /* Otherwise, fallback to contacting the
+ domain directly */
+ set_dc_type_and_flags_connect( domain );
+ }
+
+ return;
+}
+
+
+
+/**********************************************************************
+***********************************************************************/
+
+static BOOL cm_get_schannel_dcinfo(struct winbindd_domain *domain,
+ struct dcinfo **ppdc)
+{
+ NTSTATUS result;
+ struct rpc_pipe_client *netlogon_pipe;
+
+ if (lp_client_schannel() == False) {
+ return False;
+ }
+
+ result = cm_connect_netlogon(domain, &netlogon_pipe);
+ if (!NT_STATUS_IS_OK(result)) {
+ return False;
+ }
+
+ /* Return a pointer to the struct dcinfo from the
+ netlogon pipe. */
+
+ *ppdc = domain->conn.netlogon_pipe->dc;
+ return True;
+}
+
+NTSTATUS cm_connect_sam(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **cli, POLICY_HND *sam_handle)
+{
+ struct winbindd_cm_conn *conn;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ fstring conn_pwd;
+ struct dcinfo *p_dcinfo;
+
+ result = init_dc_connection(domain);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ conn = &domain->conn;
+
+ if (conn->samr_pipe != NULL) {
+ goto done;
+ }
+
+ /*
+ * No SAMR pipe yet. Attempt to get an NTLMSSP SPNEGO authenticated
+ * sign and sealed pipe using the machine account password by
+ * preference. If we can't - try schannel, if that fails, try
+ * anonymous.
+ */
+
+ pwd_get_cleartext(&conn->cli->pwd, conn_pwd);
+ if ((conn->cli->user_name[0] == '\0') ||
+ (conn->cli->domain[0] == '\0') ||
+ (conn_pwd[0] == '\0')) {
+ DEBUG(10, ("cm_connect_sam: No no user available for "
+ "domain %s, trying schannel\n", conn->cli->domain));
+ goto schannel;
+ }
+
+ /* We have an authenticated connection. Use a NTLMSSP SPNEGO
+ authenticated SAMR pipe with sign & seal. */
+ conn->samr_pipe =
+ cli_rpc_pipe_open_spnego_ntlmssp(conn->cli, PI_SAMR,
+ PIPE_AUTH_LEVEL_PRIVACY,
+ conn->cli->domain,
+ conn->cli->user_name,
+ conn_pwd, &result);
+
+ if (conn->samr_pipe == NULL) {
+ DEBUG(10,("cm_connect_sam: failed to connect to SAMR "
+ "pipe for domain %s using NTLMSSP "
+ "authenticated pipe: user %s\\%s. Error was "
+ "%s\n", domain->name, conn->cli->domain,
+ conn->cli->user_name, nt_errstr(result)));
+ goto schannel;
+ }
+
+ DEBUG(10,("cm_connect_sam: connected to SAMR pipe for "
+ "domain %s using NTLMSSP authenticated "
+ "pipe: user %s\\%s\n", domain->name,
+ conn->cli->domain, conn->cli->user_name ));
+
+ result = rpccli_samr_connect(conn->samr_pipe, mem_ctx,
+ SEC_RIGHTS_MAXIMUM_ALLOWED,
+ &conn->sam_connect_handle);
+ if (NT_STATUS_IS_OK(result)) {
+ goto open_domain;
+ }
+ DEBUG(10,("cm_connect_sam: ntlmssp-sealed rpccli_samr_connect "
+ "failed for domain %s, error was %s. Trying schannel\n",
+ domain->name, nt_errstr(result) ));
+ cli_rpc_pipe_close(conn->samr_pipe);
+
+ schannel:
+
+ /* Fall back to schannel if it's a W2K pre-SP1 box. */
+
+ if (!cm_get_schannel_dcinfo(domain, &p_dcinfo)) {
+ /* If this call fails - conn->cli can now be NULL ! */
+ DEBUG(10, ("cm_connect_sam: Could not get schannel auth info "
+ "for domain %s, trying anon\n", domain->name));
+ goto anonymous;
+ }
+ conn->samr_pipe = cli_rpc_pipe_open_schannel_with_key
+ (conn->cli, PI_SAMR, PIPE_AUTH_LEVEL_PRIVACY,
+ domain->name, p_dcinfo, &result);
+
+ if (conn->samr_pipe == NULL) {
+ DEBUG(10,("cm_connect_sam: failed to connect to SAMR pipe for "
+ "domain %s using schannel. Error was %s\n",
+ domain->name, nt_errstr(result) ));
+ goto anonymous;
+ }
+ DEBUG(10,("cm_connect_sam: connected to SAMR pipe for domain %s using "
+ "schannel.\n", domain->name ));
+
+ result = rpccli_samr_connect(conn->samr_pipe, mem_ctx,
+ SEC_RIGHTS_MAXIMUM_ALLOWED,
+ &conn->sam_connect_handle);
+ if (NT_STATUS_IS_OK(result)) {
+ goto open_domain;
+ }
+ DEBUG(10,("cm_connect_sam: schannel-sealed rpccli_samr_connect failed "
+ "for domain %s, error was %s. Trying anonymous\n",
+ domain->name, nt_errstr(result) ));
+ cli_rpc_pipe_close(conn->samr_pipe);
+
+ anonymous:
+
+ /* Finally fall back to anonymous. */
+ conn->samr_pipe = cli_rpc_pipe_open_noauth(conn->cli, PI_SAMR,
+ &result);
+
+ if (conn->samr_pipe == NULL) {
+ result = NT_STATUS_PIPE_NOT_AVAILABLE;
+ goto done;
+ }
+
+ result = rpccli_samr_connect(conn->samr_pipe, mem_ctx,
+ SEC_RIGHTS_MAXIMUM_ALLOWED,
+ &conn->sam_connect_handle);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("cm_connect_sam: rpccli_samr_connect failed "
+ "for domain %s Error was %s\n",
+ domain->name, nt_errstr(result) ));
+ goto done;
+ }
+
+ open_domain:
+ result = rpccli_samr_open_domain(conn->samr_pipe,
+ mem_ctx,
+ &conn->sam_connect_handle,
+ SEC_RIGHTS_MAXIMUM_ALLOWED,
+ &domain->sid,
+ &conn->sam_domain_handle);
+
+ done:
+
+ if (!NT_STATUS_IS_OK(result)) {
+ invalidate_cm_connection(conn);
+ return result;
+ }
+
+ *cli = conn->samr_pipe;
+ *sam_handle = conn->sam_domain_handle;
+ return result;
+}
+
+NTSTATUS cm_connect_lsa(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **cli, POLICY_HND *lsa_policy)
+{
+ struct winbindd_cm_conn *conn;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ fstring conn_pwd;
+ struct dcinfo *p_dcinfo;
+
+ result = init_dc_connection(domain);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ conn = &domain->conn;
+
+ if (conn->lsa_pipe != NULL) {
+ goto done;
+ }
+
+ pwd_get_cleartext(&conn->cli->pwd, conn_pwd);
+ if ((conn->cli->user_name[0] == '\0') ||
+ (conn->cli->domain[0] == '\0') ||
+ (conn_pwd[0] == '\0')) {
+ DEBUG(10, ("cm_connect_lsa: No no user available for "
+ "domain %s, trying schannel\n", conn->cli->domain));
+ goto schannel;
+ }
+
+ /* We have an authenticated connection. Use a NTLMSSP SPNEGO
+ * authenticated LSA pipe with sign & seal. */
+ conn->lsa_pipe = cli_rpc_pipe_open_spnego_ntlmssp
+ (conn->cli, PI_LSARPC, PIPE_AUTH_LEVEL_PRIVACY,
+ conn->cli->domain, conn->cli->user_name, conn_pwd, &result);
+
+ if (conn->lsa_pipe == NULL) {
+ DEBUG(10,("cm_connect_lsa: failed to connect to LSA pipe for "
+ "domain %s using NTLMSSP authenticated pipe: user "
+ "%s\\%s. Error was %s. Trying schannel.\n",
+ domain->name, conn->cli->domain,
+ conn->cli->user_name, nt_errstr(result)));
+ goto schannel;
+ }
+
+ DEBUG(10,("cm_connect_lsa: connected to LSA pipe for domain %s using "
+ "NTLMSSP authenticated pipe: user %s\\%s\n",
+ domain->name, conn->cli->domain, conn->cli->user_name ));
+
+ result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True,
+ SEC_RIGHTS_MAXIMUM_ALLOWED,
+ &conn->lsa_policy);
+ if (NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying "
+ "schannel\n"));
+
+ cli_rpc_pipe_close(conn->lsa_pipe);
+
+ schannel:
+
+ /* Fall back to schannel if it's a W2K pre-SP1 box. */
+
+ if (!cm_get_schannel_dcinfo(domain, &p_dcinfo)) {
+ /* If this call fails - conn->cli can now be NULL ! */
+ DEBUG(10, ("cm_connect_lsa: Could not get schannel auth info "
+ "for domain %s, trying anon\n", domain->name));
+ goto anonymous;
+ }
+ conn->lsa_pipe = cli_rpc_pipe_open_schannel_with_key
+ (conn->cli, PI_LSARPC, PIPE_AUTH_LEVEL_PRIVACY,
+ domain->name, p_dcinfo, &result);
+
+ if (conn->lsa_pipe == NULL) {
+ DEBUG(10,("cm_connect_lsa: failed to connect to LSA pipe for "
+ "domain %s using schannel. Error was %s\n",
+ domain->name, nt_errstr(result) ));
+ goto anonymous;
+ }
+ DEBUG(10,("cm_connect_lsa: connected to LSA pipe for domain %s using "
+ "schannel.\n", domain->name ));
+
+ result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True,
+ SEC_RIGHTS_MAXIMUM_ALLOWED,
+ &conn->lsa_policy);
+ if (NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying "
+ "anonymous\n"));
+
+ cli_rpc_pipe_close(conn->lsa_pipe);
+
+ anonymous:
+
+ conn->lsa_pipe = cli_rpc_pipe_open_noauth(conn->cli, PI_LSARPC,
+ &result);
+ if (conn->lsa_pipe == NULL) {
+ result = NT_STATUS_PIPE_NOT_AVAILABLE;
+ goto done;
+ }
+
+ result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True,
+ SEC_RIGHTS_MAXIMUM_ALLOWED,
+ &conn->lsa_policy);
+ done:
+ if (!NT_STATUS_IS_OK(result)) {
+ invalidate_cm_connection(conn);
+ return result;
+ }
+
+ *cli = conn->lsa_pipe;
+ *lsa_policy = conn->lsa_policy;
+ return result;
+}
+
+/****************************************************************************
+ Open the netlogon pipe to this DC. Use schannel if specified in client conf.
+ session key stored in conn->netlogon_pipe->dc->sess_key.
+****************************************************************************/
+
+NTSTATUS cm_connect_netlogon(struct winbindd_domain *domain,
+ struct rpc_pipe_client **cli)
+{
+ struct winbindd_cm_conn *conn;
+ NTSTATUS result;
+
+ uint32 neg_flags = NETLOGON_NEG_AUTH2_FLAGS;
+ uint8 mach_pwd[16];
+ uint32 sec_chan_type;
+ const char *account_name;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+
+ *cli = NULL;
+
+ result = init_dc_connection(domain);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ conn = &domain->conn;
+
+ if (conn->netlogon_pipe != NULL) {
+ *cli = conn->netlogon_pipe;
+ return NT_STATUS_OK;
+ }
+
+ if ((IS_DC || domain->primary) && !get_trust_pw(domain->name, mach_pwd, &sec_chan_type)) {
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ netlogon_pipe = cli_rpc_pipe_open_noauth(conn->cli, PI_NETLOGON,
+ &result);
+ if (netlogon_pipe == NULL) {
+ return result;
+ }
+
+ if ((!IS_DC) && (!domain->primary)) {
+ /* Clear the schannel request bit and drop down */
+ neg_flags &= ~NETLOGON_NEG_SCHANNEL;
+ goto no_schannel;
+ }
+
+ if (lp_client_schannel() != False) {
+ neg_flags |= NETLOGON_NEG_SCHANNEL;
+ }
+
+ /* if we are a DC and this is a trusted domain, then we need to use our
+ domain name in the net_req_auth2() request */
+
+ if ( IS_DC
+ && !strequal(domain->name, lp_workgroup())
+ && lp_allow_trusted_domains() )
+ {
+ account_name = lp_workgroup();
+ } else {
+ account_name = domain->primary ?
+ global_myname() : domain->name;
+ }
+
+ if (account_name == NULL) {
+ cli_rpc_pipe_close(netlogon_pipe);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ result = rpccli_netlogon_setup_creds(
+ netlogon_pipe,
+ domain->dcname, /* server name. */
+ domain->name, /* domain name */
+ global_myname(), /* client name */
+ account_name, /* machine account */
+ mach_pwd, /* machine password */
+ sec_chan_type, /* from get_trust_pw */
+ &neg_flags);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ cli_rpc_pipe_close(netlogon_pipe);
+ return result;
+ }
+
+ if ((lp_client_schannel() == True) &&
+ ((neg_flags & NETLOGON_NEG_SCHANNEL) == 0)) {
+ DEBUG(3, ("Server did not offer schannel\n"));
+ cli_rpc_pipe_close(netlogon_pipe);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ no_schannel:
+ if ((lp_client_schannel() == False) ||
+ ((neg_flags & NETLOGON_NEG_SCHANNEL) == 0)) {
+
+ /*
+ * NetSamLogonEx only works for schannel
+ */
+ domain->can_do_samlogon_ex = False;
+
+ /* We're done - just keep the existing connection to NETLOGON
+ * open */
+ conn->netlogon_pipe = netlogon_pipe;
+ *cli = conn->netlogon_pipe;
+ return NT_STATUS_OK;
+ }
+
+ /* Using the credentials from the first pipe, open a signed and sealed
+ second netlogon pipe. The session key is stored in the schannel
+ part of the new pipe auth struct.
+ */
+
+ conn->netlogon_pipe =
+ cli_rpc_pipe_open_schannel_with_key(conn->cli,
+ PI_NETLOGON,
+ PIPE_AUTH_LEVEL_PRIVACY,
+ domain->name,
+ netlogon_pipe->dc,
+ &result);
+
+ /* We can now close the initial netlogon pipe. */
+ cli_rpc_pipe_close(netlogon_pipe);
+
+ if (conn->netlogon_pipe == NULL) {
+ DEBUG(3, ("Could not open schannel'ed NETLOGON pipe. Error "
+ "was %s\n", nt_errstr(result)));
+
+ /* make sure we return something besides OK */
+ return !NT_STATUS_IS_OK(result) ? result : NT_STATUS_PIPE_NOT_AVAILABLE;
+ }
+
+ /*
+ * Try NetSamLogonEx for AD domains
+ */
+ domain->can_do_samlogon_ex = domain->active_directory;
+
+ *cli = conn->netlogon_pipe;
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_cred_cache.c b/source3/winbindd/winbindd_cred_cache.c
new file mode 100644
index 0000000000..65dcbe5a00
--- /dev/null
+++ b/source3/winbindd/winbindd_cred_cache.c
@@ -0,0 +1,802 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - krb5 credential cache functions
+ and in-memory cache functions.
+
+ Copyright (C) Guenther Deschner 2005-2006
+ Copyright (C) Jeremy Allison 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/* uncomment this to do fast debugging on the krb5 ticket renewal event */
+#ifdef DEBUG_KRB5_TKT_RENEWAL
+#undef DEBUG_KRB5_TKT_RENEWAL
+#endif
+
+#define MAX_CCACHES 100
+
+static struct WINBINDD_CCACHE_ENTRY *ccache_list;
+
+/* The Krb5 ticket refresh handler should be scheduled
+ at one-half of the period from now till the tkt
+ expiration */
+#define KRB5_EVENT_REFRESH_TIME(x) ((x) - (((x) - time(NULL))/2))
+
+/****************************************************************
+ Find an entry by name.
+****************************************************************/
+
+static struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry;
+
+ for (entry = ccache_list; entry; entry = entry->next) {
+ if (strequal(entry->username, username)) {
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************
+ How many do we have ?
+****************************************************************/
+
+static int ccache_entry_count(void)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry;
+ int i = 0;
+
+ for (entry = ccache_list; entry; entry = entry->next) {
+ i++;
+ }
+ return i;
+}
+
+/****************************************************************
+ Do the work of refreshing the ticket.
+****************************************************************/
+
+static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry =
+ talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
+#ifdef HAVE_KRB5
+ int ret;
+ time_t new_start;
+ struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
+#endif
+
+ DEBUG(10,("krb5_ticket_refresh_handler called\n"));
+ DEBUGADD(10,("event called for: %s, %s\n",
+ entry->ccname, entry->username));
+
+ TALLOC_FREE(entry->event);
+
+#ifdef HAVE_KRB5
+
+ /* Kinit again if we have the user password and we can't renew the old
+ * tgt anymore */
+
+ if ((entry->renew_until < time(NULL)) && cred_ptr && cred_ptr->pass) {
+
+ set_effective_uid(entry->uid);
+
+ ret = kerberos_kinit_password_ext(entry->principal_name,
+ cred_ptr->pass,
+ 0, /* hm, can we do time correction here ? */
+ &entry->refresh_time,
+ &entry->renew_until,
+ entry->ccname,
+ False, /* no PAC required anymore */
+ True,
+ WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+ NULL);
+ gain_root_privilege();
+
+ if (ret) {
+ DEBUG(3,("krb5_ticket_refresh_handler: "
+ "could not re-kinit: %s\n",
+ error_message(ret)));
+ TALLOC_FREE(entry->event);
+ return;
+ }
+
+ DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
+ "for: %s in ccache: %s\n",
+ entry->principal_name, entry->ccname));
+
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ new_start = time(NULL) + 30;
+#else
+ /* The tkt should be refreshed at one-half the period
+ from now to the expiration time */
+ new_start = KRB5_EVENT_REFRESH_TIME(entry->refresh_time);
+#endif
+ goto done;
+ }
+
+ set_effective_uid(entry->uid);
+
+ ret = smb_krb5_renew_ticket(entry->ccname,
+ entry->principal_name,
+ entry->service,
+ &new_start);
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ new_start = time(NULL) + 30;
+#else
+ new_start = KRB5_EVENT_REFRESH_TIME(new_start);
+#endif
+
+ gain_root_privilege();
+
+ if (ret) {
+ DEBUG(3,("krb5_ticket_refresh_handler: "
+ "could not renew tickets: %s\n",
+ error_message(ret)));
+ /* maybe we are beyond the renewing window */
+
+ /* avoid breaking the renewal chain: retry in
+ * lp_winbind_cache_time() seconds when the KDC was not
+ * available right now. */
+
+ if (ret == KRB5_KDC_UNREACH) {
+ new_start = time(NULL) +
+ MAX(30, lp_winbind_cache_time());
+ goto done;
+ }
+
+ return;
+ }
+
+done:
+
+ entry->event = event_add_timed(winbind_event_context(), entry,
+ timeval_set(new_start, 0),
+ "krb5_ticket_refresh_handler",
+ krb5_ticket_refresh_handler,
+ entry);
+
+#endif
+}
+
+/****************************************************************
+ Do the work of regaining a ticket when coming from offline auth.
+****************************************************************/
+
+static void krb5_ticket_gain_handler(struct event_context *event_ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry =
+ talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
+#ifdef HAVE_KRB5
+ int ret;
+ struct timeval t;
+ struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
+ struct winbindd_domain *domain = NULL;
+#endif
+
+ DEBUG(10,("krb5_ticket_gain_handler called\n"));
+ DEBUGADD(10,("event called for: %s, %s\n",
+ entry->ccname, entry->username));
+
+ TALLOC_FREE(entry->event);
+
+#ifdef HAVE_KRB5
+
+ if (!cred_ptr || !cred_ptr->pass) {
+ DEBUG(10,("krb5_ticket_gain_handler: no memory creds\n"));
+ return;
+ }
+
+ if ((domain = find_domain_from_name(entry->realm)) == NULL) {
+ DEBUG(0,("krb5_ticket_gain_handler: unknown domain\n"));
+ return;
+ }
+
+ if (!domain->online) {
+ goto retry_later;
+ }
+
+ set_effective_uid(entry->uid);
+
+ ret = kerberos_kinit_password_ext(entry->principal_name,
+ cred_ptr->pass,
+ 0, /* hm, can we do time correction here ? */
+ &entry->refresh_time,
+ &entry->renew_until,
+ entry->ccname,
+ False, /* no PAC required anymore */
+ True,
+ WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+ NULL);
+ gain_root_privilege();
+
+ if (ret) {
+ DEBUG(3,("krb5_ticket_gain_handler: "
+ "could not kinit: %s\n",
+ error_message(ret)));
+ goto retry_later;
+ }
+
+ DEBUG(10,("krb5_ticket_gain_handler: "
+ "successful kinit for: %s in ccache: %s\n",
+ entry->principal_name, entry->ccname));
+
+ goto got_ticket;
+
+ retry_later:
+
+ t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
+
+ entry->event = event_add_timed(winbind_event_context(),
+ entry,
+ t,
+ "krb5_ticket_gain_handler",
+ krb5_ticket_gain_handler,
+ entry);
+
+ return;
+
+ got_ticket:
+
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ t = timeval_set(time(NULL) + 30, 0);
+#else
+ t = timeval_set(KRB5_EVENT_REFRESH_TIME(entry->refresh_time), 0);
+#endif
+
+ entry->event = event_add_timed(winbind_event_context(),
+ entry,
+ t,
+ "krb5_ticket_refresh_handler",
+ krb5_ticket_refresh_handler,
+ entry);
+
+ return;
+#endif
+}
+
+/****************************************************************
+ Check if an ccache entry exists.
+****************************************************************/
+
+BOOL ccache_entry_exists(const char *username)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
+ return (entry != NULL);
+}
+
+/****************************************************************
+ Ensure we're changing the correct entry.
+****************************************************************/
+
+BOOL ccache_entry_identical(const char *username,
+ uid_t uid,
+ const char *ccname)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
+
+ if (!entry) {
+ return False;
+ }
+
+ if (entry->uid != uid) {
+ DEBUG(0,("cache_entry_identical: uid's differ: %u != %u\n",
+ (unsigned int)entry->uid, (unsigned int)uid));
+ return False;
+ }
+ if (!strcsequal(entry->ccname, ccname)) {
+ DEBUG(0,("cache_entry_identical: "
+ "ccnames differ: (cache) %s != (client) %s\n",
+ entry->ccname, ccname));
+ return False;
+ }
+ return True;
+}
+
+NTSTATUS add_ccache_to_list(const char *princ_name,
+ const char *ccname,
+ const char *service,
+ const char *username,
+ const char *realm,
+ uid_t uid,
+ time_t create_time,
+ time_t ticket_end,
+ time_t renew_until,
+ BOOL postponed_request)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = NULL;
+ struct timeval t;
+
+ if ((username == NULL && princ_name == NULL) ||
+ ccname == NULL || uid < 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (ccache_entry_count() + 1 > MAX_CCACHES) {
+ DEBUG(10,("add_ccache_to_list: "
+ "max number of ccaches reached\n"));
+ return NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ /* Reference count old entries */
+ entry = get_ccache_by_username(username);
+ if (entry) {
+ /* Check cached entries are identical. */
+ if (!ccache_entry_identical(username, uid, ccname)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ entry->ref_count++;
+ DEBUG(10,("add_ccache_to_list: "
+ "ref count on entry %s is now %d\n",
+ username, entry->ref_count));
+ /* FIXME: in this case we still might want to have a krb5 cred
+ * event handler created - gd*/
+ return NT_STATUS_OK;
+ }
+
+ entry = TALLOC_P(NULL, struct WINBINDD_CCACHE_ENTRY);
+ if (!entry) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCTP(entry);
+
+ if (username) {
+ entry->username = talloc_strdup(entry, username);
+ if (!entry->username) {
+ goto no_mem;
+ }
+ }
+ if (princ_name) {
+ entry->principal_name = talloc_strdup(entry, princ_name);
+ if (!entry->principal_name) {
+ goto no_mem;
+ }
+ }
+ if (service) {
+ entry->service = talloc_strdup(entry, service);
+ if (!entry->service) {
+ goto no_mem;
+ }
+ }
+
+ entry->ccname = talloc_strdup(entry, ccname);
+ if (!entry->ccname) {
+ goto no_mem;
+ }
+
+ entry->realm = talloc_strdup(entry, realm);
+ if (!entry->realm) {
+ goto no_mem;
+ }
+
+ entry->create_time = create_time;
+ entry->renew_until = renew_until;
+ entry->uid = uid;
+ entry->ref_count = 1;
+
+ if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
+ goto add_entry;
+ }
+
+ if (postponed_request) {
+ t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
+ entry->event = event_add_timed(winbind_event_context(),
+ entry,
+ t,
+ "krb5_ticket_gain_handler",
+ krb5_ticket_gain_handler,
+ entry);
+ } else {
+ /* Renew at 1/2 the ticket expiration time */
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ t = timeval_set(time(NULL)+30, 0);
+#else
+ t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
+#endif
+ entry->event = event_add_timed(winbind_event_context(),
+ entry,
+ t,
+ "krb5_ticket_refresh_handler",
+ krb5_ticket_refresh_handler,
+ entry);
+ }
+
+ if (!entry->event) {
+ goto no_mem;
+ }
+
+ DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
+
+ add_entry:
+
+ DLIST_ADD(ccache_list, entry);
+
+ DEBUG(10,("add_ccache_to_list: "
+ "added ccache [%s] for user [%s] to the list\n",
+ ccname, username));
+
+ return NT_STATUS_OK;
+
+ no_mem:
+
+ TALLOC_FREE(entry);
+ return NT_STATUS_NO_MEMORY;
+}
+
+/*******************************************************************
+ Remove a WINBINDD_CCACHE_ENTRY entry and the krb5 ccache if no longer
+ referenced.
+ *******************************************************************/
+
+NTSTATUS remove_ccache(const char *username)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
+ NTSTATUS status = NT_STATUS_OK;
+ #ifdef HAVE_KRB5
+ krb5_error_code ret;
+#endif
+
+ if (!entry) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (entry->ref_count <= 0) {
+ DEBUG(0,("remove_ccache: logic error. "
+ "ref count for user %s = %d\n",
+ username, entry->ref_count));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ entry->ref_count--;
+
+ if (entry->ref_count > 0) {
+ DEBUG(10,("remove_ccache: entry %s ref count now %d\n",
+ username, entry->ref_count));
+ return NT_STATUS_OK;
+ }
+
+ /* no references any more */
+
+ DLIST_REMOVE(ccache_list, entry);
+ TALLOC_FREE(entry->event); /* unregisters events */
+
+#ifdef HAVE_KRB5
+ ret = ads_kdestroy(entry->ccname);
+
+ /* we ignore the error when there has been no credential cache */
+ if (ret == KRB5_FCC_NOFILE) {
+ ret = 0;
+ } else if (ret) {
+ DEBUG(0,("remove_ccache: "
+ "failed to destroy user krb5 ccache %s with: %s\n",
+ entry->ccname, error_message(ret)));
+ } else {
+ DEBUG(10,("remove_ccache: "
+ "successfully destroyed krb5 ccache %s for user %s\n",
+ entry->ccname, username));
+ }
+ status = krb5_to_nt_status(ret);
+#endif
+
+ TALLOC_FREE(entry);
+ DEBUG(10,("remove_ccache: removed ccache for user %s\n", username));
+
+ return status;
+}
+
+/*******************************************************************
+ In memory credentials cache code.
+*******************************************************************/
+
+static struct WINBINDD_MEMORY_CREDS *memory_creds_list;
+
+/***********************************************************
+ Find an entry on the list by name.
+***********************************************************/
+
+struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username)
+{
+ struct WINBINDD_MEMORY_CREDS *p;
+
+ for (p = memory_creds_list; p; p = p->next) {
+ if (strequal(p->username, username)) {
+ return p;
+ }
+ }
+ return NULL;
+}
+
+/***********************************************************
+ Store the required creds and mlock them.
+***********************************************************/
+
+static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp,
+ const char *pass)
+{
+#if !defined(HAVE_MLOCK)
+ return NT_STATUS_OK;
+#else
+ /* new_entry->nt_hash is the base pointer for the block
+ of memory pointed into by new_entry->lm_hash and
+ new_entry->pass (if we're storing plaintext). */
+
+ memcredp->len = NT_HASH_LEN + LM_HASH_LEN;
+ if (pass) {
+ memcredp->len += strlen(pass)+1;
+ }
+
+
+#if defined(LINUX)
+ /* aligning the memory on on x86_64 and compiling
+ with gcc 4.1 using -O2 causes a segv in the
+ next memset() --jerry */
+ memcredp->nt_hash = SMB_MALLOC_ARRAY(unsigned char, memcredp->len);
+#else
+ /* On non-linux platforms, mlock()'d memory must be aligned */
+ memcredp->nt_hash = SMB_MEMALIGN_ARRAY(unsigned char,
+ getpagesize(), memcredp->len);
+#endif
+ if (!memcredp->nt_hash) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ memset(memcredp->nt_hash, 0x0, memcredp->len);
+
+ memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN;
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(10,("mlocking memory: %p\n", memcredp->nt_hash));
+#endif
+ if ((mlock(memcredp->nt_hash, memcredp->len)) == -1) {
+ DEBUG(0,("failed to mlock memory: %s (%d)\n",
+ strerror(errno), errno));
+ SAFE_FREE(memcredp->nt_hash);
+ return map_nt_error_from_unix(errno);
+ }
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
+#endif
+
+ /* Create and store the password hashes. */
+ E_md4hash(pass, memcredp->nt_hash);
+ E_deshash(pass, memcredp->lm_hash);
+
+ if (pass) {
+ memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN;
+ memcpy(memcredp->pass, pass,
+ memcredp->len - NT_HASH_LEN - LM_HASH_LEN);
+ }
+
+ return NT_STATUS_OK;
+#endif
+}
+
+/***********************************************************
+ Destroy existing creds.
+***********************************************************/
+
+static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp)
+{
+#if !defined(HAVE_MUNLOCK)
+ return NT_STATUS_OK;
+#else
+ if (munlock(memcredp->nt_hash, memcredp->len) == -1) {
+ DEBUG(0,("failed to munlock memory: %s (%d)\n",
+ strerror(errno), errno));
+ return map_nt_error_from_unix(errno);
+ }
+ memset(memcredp->nt_hash, '\0', memcredp->len);
+ SAFE_FREE(memcredp->nt_hash);
+ memcredp->nt_hash = NULL;
+ memcredp->lm_hash = NULL;
+ memcredp->pass = NULL;
+ memcredp->len = 0;
+ return NT_STATUS_OK;
+#endif
+}
+
+/***********************************************************
+ Replace the required creds with new ones (password change).
+***********************************************************/
+
+static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp,
+ const char *pass)
+{
+ NTSTATUS status = delete_memory_creds(memcredp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return store_memory_creds(memcredp, pass);
+}
+
+/*************************************************************
+ Store credentials in memory in a list.
+*************************************************************/
+
+static NTSTATUS winbindd_add_memory_creds_internal(const char *username,
+ uid_t uid,
+ const char *pass)
+{
+ /* Shortcut to ensure we don't store if no mlock. */
+#if !defined(HAVE_MLOCK) || !defined(HAVE_MUNLOCK)
+ return NT_STATUS_OK;
+#else
+ NTSTATUS status;
+ struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
+
+ memcredp = find_memory_creds_by_name(username);
+ if (uid == (uid_t)-1) {
+ DEBUG(0,("winbindd_add_memory_creds_internal: "
+ "invalid uid for user %s.\n", username));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (memcredp) {
+ /* Already exists. Increment the reference count and replace stored creds. */
+ if (uid != memcredp->uid) {
+ DEBUG(0,("winbindd_add_memory_creds_internal: "
+ "uid %u for user %s doesn't "
+ "match stored uid %u. Replacing.\n",
+ (unsigned int)uid, username,
+ (unsigned int)memcredp->uid));
+ memcredp->uid = uid;
+ }
+ memcredp->ref_count++;
+ DEBUG(10,("winbindd_add_memory_creds_internal: "
+ "ref count for user %s is now %d\n",
+ username, memcredp->ref_count));
+ return winbindd_replace_memory_creds_internal(memcredp, pass);
+ }
+
+ memcredp = TALLOC_ZERO_P(NULL, struct WINBINDD_MEMORY_CREDS);
+ if (!memcredp) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ memcredp->username = talloc_strdup(memcredp, username);
+ if (!memcredp->username) {
+ talloc_destroy(memcredp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = store_memory_creds(memcredp, pass);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_destroy(memcredp);
+ return status;
+ }
+
+ memcredp->uid = uid;
+ memcredp->ref_count = 1;
+ DLIST_ADD(memory_creds_list, memcredp);
+
+ DEBUG(10,("winbindd_add_memory_creds_internal: "
+ "added entry for user %s\n", username));
+
+ return NT_STATUS_OK;
+#endif
+}
+
+/*************************************************************
+ Store users credentials in memory. If we also have a
+ struct WINBINDD_CCACHE_ENTRY for this username with a
+ refresh timer, then store the plaintext of the password
+ and associate the new credentials with the struct WINBINDD_CCACHE_ENTRY.
+*************************************************************/
+
+NTSTATUS winbindd_add_memory_creds(const char *username,
+ uid_t uid,
+ const char *pass)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
+ NTSTATUS status;
+
+ status = winbindd_add_memory_creds_internal(username, uid, pass);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (entry) {
+ struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
+ memcredp = find_memory_creds_by_name(username);
+ if (memcredp) {
+ entry->cred_ptr = memcredp;
+ }
+ }
+
+ return status;
+}
+
+/*************************************************************
+ Decrement the in-memory ref count - delete if zero.
+*************************************************************/
+
+NTSTATUS winbindd_delete_memory_creds(const char *username)
+{
+ struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
+ struct WINBINDD_CCACHE_ENTRY *entry = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ memcredp = find_memory_creds_by_name(username);
+ entry = get_ccache_by_username(username);
+
+ if (!memcredp) {
+ DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n",
+ username));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (memcredp->ref_count <= 0) {
+ DEBUG(0,("winbindd_delete_memory_creds: logic error. "
+ "ref count for user %s = %d\n",
+ username, memcredp->ref_count));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ memcredp->ref_count--;
+ if (memcredp->ref_count <= 0) {
+ delete_memory_creds(memcredp);
+ DLIST_REMOVE(memory_creds_list, memcredp);
+ talloc_destroy(memcredp);
+ DEBUG(10,("winbindd_delete_memory_creds: "
+ "deleted entry for user %s\n",
+ username));
+ } else {
+ DEBUG(10,("winbindd_delete_memory_creds: "
+ "entry for user %s ref_count now %d\n",
+ username, memcredp->ref_count));
+ }
+
+ if (entry) {
+ /* Ensure we have no dangling references to this. */
+ entry->cred_ptr = NULL;
+ }
+
+ return status;
+}
+
+/***********************************************************
+ Replace the required creds with new ones (password change).
+***********************************************************/
+
+NTSTATUS winbindd_replace_memory_creds(const char *username,
+ const char *pass)
+{
+ struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
+
+ memcredp = find_memory_creds_by_name(username);
+ if (!memcredp) {
+ DEBUG(10,("winbindd_replace_memory_creds: unknown user %s\n",
+ username));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ DEBUG(10,("winbindd_replace_memory_creds: replaced creds for user %s\n",
+ username));
+
+ return winbindd_replace_memory_creds_internal(memcredp, pass);
+}
diff --git a/source3/winbindd/winbindd_creds.c b/source3/winbindd/winbindd_creds.c
new file mode 100644
index 0000000000..62facb6769
--- /dev/null
+++ b/source3/winbindd/winbindd_creds.c
@@ -0,0 +1,162 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - cached credentials funcions
+
+ Copyright (C) Guenther Deschner 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+#define MAX_CACHED_LOGINS 10
+
+NTSTATUS winbindd_get_creds(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ NET_USER_INFO_3 **info3,
+ const uint8 *cached_nt_pass[NT_HASH_LEN],
+ const uint8 *cred_salt[NT_HASH_LEN])
+{
+ NET_USER_INFO_3 *info;
+ NTSTATUS status;
+
+ status = wcache_get_creds(domain, mem_ctx, sid, cached_nt_pass, cred_salt);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ info = netsamlogon_cache_get(mem_ctx, sid);
+ if (info == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ *info3 = info;
+
+ return NT_STATUS_OK;
+}
+
+
+NTSTATUS winbindd_store_creds(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *user,
+ const char *pass,
+ NET_USER_INFO_3 *info3,
+ const DOM_SID *user_sid)
+{
+ NTSTATUS status;
+ uchar nt_pass[NT_HASH_LEN];
+ DOM_SID cred_sid;
+
+ if (info3 != NULL) {
+
+ DOM_SID sid;
+ sid_copy(&sid, &(info3->dom_sid.sid));
+ sid_append_rid(&sid, info3->user_rid);
+ sid_copy(&cred_sid, &sid);
+ info3->user_flgs |= LOGON_CACHED_ACCOUNT;
+
+ } else if (user_sid != NULL) {
+
+ sid_copy(&cred_sid, user_sid);
+
+ } else if (user != NULL) {
+
+ /* do lookup ourself */
+
+ enum lsa_SidType type;
+
+ if (!lookup_cached_name(mem_ctx,
+ domain->name,
+ user,
+ &cred_sid,
+ &type)) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ } else {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (pass) {
+
+ int count = 0;
+
+ status = wcache_count_cached_creds(domain, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(11,("we have %d cached creds\n", count));
+
+ if (count + 1 > MAX_CACHED_LOGINS) {
+
+ DEBUG(10,("need to delete the oldest cached login\n"));
+
+ status = wcache_remove_oldest_cached_creds(domain, &cred_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("failed to remove oldest cached cred: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ E_md4hash(pass, nt_pass);
+
+ dump_data_pw("nt_pass", nt_pass, NT_HASH_LEN);
+
+ status = wcache_save_creds(domain, mem_ctx, &cred_sid, nt_pass);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (info3 != NULL && user != NULL) {
+ if (!netsamlogon_cache_store(user, info3)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS winbindd_update_creds_by_info3(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *user,
+ const char *pass,
+ NET_USER_INFO_3 *info3)
+{
+ return winbindd_store_creds(domain, mem_ctx, user, pass, info3, NULL);
+}
+
+NTSTATUS winbindd_update_creds_by_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ const char *pass)
+{
+ return winbindd_store_creds(domain, mem_ctx, NULL, pass, NULL, sid);
+}
+
+NTSTATUS winbindd_update_creds_by_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *user,
+ const char *pass)
+{
+ return winbindd_store_creds(domain, mem_ctx, user, pass, NULL, NULL);
+}
+
+
diff --git a/source3/winbindd/winbindd_dual.c b/source3/winbindd/winbindd_dual.c
new file mode 100644
index 0000000000..67cf6abc2b
--- /dev/null
+++ b/source3/winbindd/winbindd_dual.c
@@ -0,0 +1,1130 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind child daemons
+
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Volker Lendecke 2004,2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * We fork a child per domain to be able to act non-blocking in the main
+ * winbind daemon. A domain controller thousands of miles away being being
+ * slow replying with a 10.000 user list should not hold up netlogon calls
+ * that can be handled locally.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+extern BOOL override_logfile;
+
+/* Read some data from a client connection */
+
+static void child_read_request(struct winbindd_cli_state *state)
+{
+ ssize_t len;
+
+ /* Read data */
+
+ len = read_data(state->sock, (char *)&state->request,
+ sizeof(state->request));
+
+ if (len != sizeof(state->request)) {
+ DEBUG(len > 0 ? 0 : 3, ("Got invalid request length: %d\n", (int)len));
+ state->finished = True;
+ return;
+ }
+
+ if (state->request.extra_len == 0) {
+ state->request.extra_data.data = NULL;
+ return;
+ }
+
+ DEBUG(10, ("Need to read %d extra bytes\n", (int)state->request.extra_len));
+
+ state->request.extra_data.data =
+ SMB_MALLOC_ARRAY(char, state->request.extra_len + 1);
+
+ if (state->request.extra_data.data == NULL) {
+ DEBUG(0, ("malloc failed\n"));
+ state->finished = True;
+ return;
+ }
+
+ /* Ensure null termination */
+ state->request.extra_data.data[state->request.extra_len] = '\0';
+
+ len = read_data(state->sock, state->request.extra_data.data,
+ state->request.extra_len);
+
+ if (len != state->request.extra_len) {
+ DEBUG(0, ("Could not read extra data\n"));
+ state->finished = True;
+ return;
+ }
+}
+
+/*
+ * Machinery for async requests sent to children. You set up a
+ * winbindd_request, select a child to query, and issue a async_request
+ * call. When the request is completed, the callback function you specified is
+ * called back with the private pointer you gave to async_request.
+ */
+
+struct winbindd_async_request {
+ struct winbindd_async_request *next, *prev;
+ TALLOC_CTX *mem_ctx;
+ struct winbindd_child *child;
+ struct winbindd_request *request;
+ struct winbindd_response *response;
+ void (*continuation)(void *private_data, BOOL success);
+ struct timed_event *reply_timeout_event;
+ pid_t child_pid; /* pid of the child we're waiting on. Used to detect
+ a restart of the child (child->pid != child_pid). */
+ void *private_data;
+};
+
+static void async_main_request_sent(void *private_data, BOOL success);
+static void async_request_sent(void *private_data, BOOL success);
+static void async_reply_recv(void *private_data, BOOL success);
+static void schedule_async_request(struct winbindd_child *child);
+
+void async_request(TALLOC_CTX *mem_ctx, struct winbindd_child *child,
+ struct winbindd_request *request,
+ struct winbindd_response *response,
+ void (*continuation)(void *private_data, BOOL success),
+ void *private_data)
+{
+ struct winbindd_async_request *state;
+
+ SMB_ASSERT(continuation != NULL);
+
+ state = TALLOC_P(mem_ctx, struct winbindd_async_request);
+
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ continuation(private_data, False);
+ return;
+ }
+
+ state->mem_ctx = mem_ctx;
+ state->child = child;
+ state->request = request;
+ state->response = response;
+ state->continuation = continuation;
+ state->private_data = private_data;
+
+ DLIST_ADD_END(child->requests, state, struct winbindd_async_request *);
+
+ schedule_async_request(child);
+
+ return;
+}
+
+static void async_main_request_sent(void *private_data, BOOL success)
+{
+ struct winbindd_async_request *state =
+ talloc_get_type_abort(private_data, struct winbindd_async_request);
+
+ if (!success) {
+ DEBUG(5, ("Could not send async request\n"));
+
+ state->response->length = sizeof(struct winbindd_response);
+ state->response->result = WINBINDD_ERROR;
+ state->continuation(state->private_data, False);
+ return;
+ }
+
+ if (state->request->extra_len == 0) {
+ async_request_sent(private_data, True);
+ return;
+ }
+
+ setup_async_write(&state->child->event, state->request->extra_data.data,
+ state->request->extra_len,
+ async_request_sent, state);
+}
+
+/****************************************************************
+ Handler triggered if the child winbindd doesn't respond within
+ a given timeout.
+****************************************************************/
+
+static void async_request_timeout_handler(struct event_context *ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ struct winbindd_async_request *state =
+ talloc_get_type_abort(private_data, struct winbindd_async_request);
+
+ DEBUG(0,("async_request_timeout_handler: child pid %u is not responding. "
+ "Closing connection to it.\n",
+ state->child_pid ));
+
+ /* Deal with the reply - set to error. */
+ async_reply_recv(private_data, False);
+}
+
+/**************************************************************
+ Common function called on both async send and recv fail.
+ Cleans up the child and schedules the next request.
+**************************************************************/
+
+static void async_request_fail(struct winbindd_async_request *state)
+{
+ DLIST_REMOVE(state->child->requests, state);
+
+ TALLOC_FREE(state->reply_timeout_event);
+
+ SMB_ASSERT(state->child_pid != (pid_t)0);
+
+ /* If not already reaped, send kill signal to child. */
+ if (state->child->pid == state->child_pid) {
+ kill(state->child_pid, SIGTERM);
+
+ /*
+ * Close the socket to the child.
+ */
+ winbind_child_died(state->child_pid);
+ }
+
+ state->response->length = sizeof(struct winbindd_response);
+ state->response->result = WINBINDD_ERROR;
+ state->continuation(state->private_data, False);
+}
+
+static void async_request_sent(void *private_data_data, BOOL success)
+{
+ struct winbindd_async_request *state =
+ talloc_get_type_abort(private_data_data, struct winbindd_async_request);
+
+ if (!success) {
+ DEBUG(5, ("Could not send async request to child pid %u\n",
+ (unsigned int)state->child_pid ));
+ async_request_fail(state);
+ return;
+ }
+
+ /* Request successfully sent to the child, setup the wait for reply */
+
+ setup_async_read(&state->child->event,
+ &state->response->result,
+ sizeof(state->response->result),
+ async_reply_recv, state);
+
+ /*
+ * Set up a timeout of 300 seconds for the response.
+ * If we don't get it close the child socket and
+ * report failure.
+ */
+
+ state->reply_timeout_event = event_add_timed(winbind_event_context(),
+ NULL,
+ timeval_current_ofs(300,0),
+ "async_request_timeout",
+ async_request_timeout_handler,
+ state);
+ if (!state->reply_timeout_event) {
+ smb_panic("async_request_sent: failed to add timeout handler.\n");
+ }
+}
+
+static void async_reply_recv(void *private_data, BOOL success)
+{
+ struct winbindd_async_request *state =
+ talloc_get_type_abort(private_data, struct winbindd_async_request);
+ struct winbindd_child *child = state->child;
+
+ TALLOC_FREE(state->reply_timeout_event);
+
+ state->response->length = sizeof(struct winbindd_response);
+
+ if (!success) {
+ DEBUG(5, ("Could not receive async reply from child pid %u\n",
+ (unsigned int)state->child_pid ));
+
+ cache_cleanup_response(state->child_pid);
+ async_request_fail(state);
+ return;
+ }
+
+ SMB_ASSERT(cache_retrieve_response(state->child_pid,
+ state->response));
+
+ cache_cleanup_response(state->child_pid);
+
+ DLIST_REMOVE(child->requests, state);
+
+ schedule_async_request(child);
+
+ state->continuation(state->private_data, True);
+}
+
+static BOOL fork_domain_child(struct winbindd_child *child);
+
+static void schedule_async_request(struct winbindd_child *child)
+{
+ struct winbindd_async_request *request = child->requests;
+
+ if (request == NULL) {
+ return;
+ }
+
+ if (child->event.flags != 0) {
+ return; /* Busy */
+ }
+
+ if ((child->pid == 0) && (!fork_domain_child(child))) {
+ /* Cancel all outstanding requests */
+
+ while (request != NULL) {
+ /* request might be free'd in the continuation */
+ struct winbindd_async_request *next = request->next;
+ request->continuation(request->private_data, False);
+ request = next;
+ }
+ return;
+ }
+
+ /* Now we know who we're sending to - remember the pid. */
+ request->child_pid = child->pid;
+
+ setup_async_write(&child->event, request->request,
+ sizeof(*request->request),
+ async_main_request_sent, request);
+
+ return;
+}
+
+struct domain_request_state {
+ TALLOC_CTX *mem_ctx;
+ struct winbindd_domain *domain;
+ struct winbindd_request *request;
+ struct winbindd_response *response;
+ void (*continuation)(void *private_data_data, BOOL success);
+ void *private_data_data;
+};
+
+static void domain_init_recv(void *private_data_data, BOOL success);
+
+void async_domain_request(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ struct winbindd_request *request,
+ struct winbindd_response *response,
+ void (*continuation)(void *private_data_data, BOOL success),
+ void *private_data_data)
+{
+ struct domain_request_state *state;
+
+ if (domain->initialized) {
+ async_request(mem_ctx, &domain->child, request, response,
+ continuation, private_data_data);
+ return;
+ }
+
+ state = TALLOC_P(mem_ctx, struct domain_request_state);
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ continuation(private_data_data, False);
+ return;
+ }
+
+ state->mem_ctx = mem_ctx;
+ state->domain = domain;
+ state->request = request;
+ state->response = response;
+ state->continuation = continuation;
+ state->private_data_data = private_data_data;
+
+ init_child_connection(domain, domain_init_recv, state);
+}
+
+static void domain_init_recv(void *private_data_data, BOOL success)
+{
+ struct domain_request_state *state =
+ talloc_get_type_abort(private_data_data, struct domain_request_state);
+
+ if (!success) {
+ DEBUG(5, ("Domain init returned an error\n"));
+ state->continuation(state->private_data_data, False);
+ return;
+ }
+
+ async_request(state->mem_ctx, &state->domain->child,
+ state->request, state->response,
+ state->continuation, state->private_data_data);
+}
+
+static void recvfrom_child(void *private_data_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data_data, struct winbindd_cli_state);
+ enum winbindd_result result = state->response.result;
+
+ /* This is an optimization: The child has written directly to the
+ * response buffer. The request itself is still in pending state,
+ * state that in the result code. */
+
+ state->response.result = WINBINDD_PENDING;
+
+ if ((!success) || (result != WINBINDD_OK)) {
+ request_error(state);
+ return;
+ }
+
+ request_ok(state);
+}
+
+void sendto_child(struct winbindd_cli_state *state,
+ struct winbindd_child *child)
+{
+ async_request(state->mem_ctx, child, &state->request,
+ &state->response, recvfrom_child, state);
+}
+
+void sendto_domain(struct winbindd_cli_state *state,
+ struct winbindd_domain *domain)
+{
+ async_domain_request(state->mem_ctx, domain,
+ &state->request, &state->response,
+ recvfrom_child, state);
+}
+
+
+struct winbindd_child_dispatch_table {
+ enum winbindd_cmd cmd;
+ enum winbindd_result (*fn)(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state);
+ const char *winbindd_cmd_name;
+};
+
+static struct winbindd_child_dispatch_table child_dispatch_table[] = {
+
+ { WINBINDD_LOOKUPSID, winbindd_dual_lookupsid, "LOOKUPSID" },
+ { WINBINDD_LOOKUPNAME, winbindd_dual_lookupname, "LOOKUPNAME" },
+ { WINBINDD_LOOKUPRIDS, winbindd_dual_lookuprids, "LOOKUPRIDS" },
+ { WINBINDD_LIST_TRUSTDOM, winbindd_dual_list_trusted_domains, "LIST_TRUSTDOM" },
+ { WINBINDD_INIT_CONNECTION, winbindd_dual_init_connection, "INIT_CONNECTION" },
+ { WINBINDD_GETDCNAME, winbindd_dual_getdcname, "GETDCNAME" },
+ { WINBINDD_DSGETDCNAME, winbindd_dual_dsgetdcname, "DSGETDCNAME" },
+ { WINBINDD_SHOW_SEQUENCE, winbindd_dual_show_sequence, "SHOW_SEQUENCE" },
+ { WINBINDD_PAM_AUTH, winbindd_dual_pam_auth, "PAM_AUTH" },
+ { WINBINDD_PAM_AUTH_CRAP, winbindd_dual_pam_auth_crap, "AUTH_CRAP" },
+ { WINBINDD_PAM_LOGOFF, winbindd_dual_pam_logoff, "PAM_LOGOFF" },
+ { WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP,winbindd_dual_pam_chng_pswd_auth_crap,"CHNG_PSWD_AUTH_CRAP" },
+ { WINBINDD_PAM_CHAUTHTOK, winbindd_dual_pam_chauthtok, "PAM_CHAUTHTOK" },
+ { WINBINDD_CHECK_MACHACC, winbindd_dual_check_machine_acct, "CHECK_MACHACC" },
+ { WINBINDD_DUAL_SID2UID, winbindd_dual_sid2uid, "DUAL_SID2UID" },
+ { WINBINDD_DUAL_SID2GID, winbindd_dual_sid2gid, "DUAL_SID2GID" },
+#if 0 /* DISABLED until we fix the interface in Samba 3.0.26 --jerry */
+ { WINBINDD_DUAL_SIDS2XIDS, winbindd_dual_sids2xids, "DUAL_SIDS2XIDS" },
+#endif /* end DISABLED */
+ { WINBINDD_DUAL_UID2SID, winbindd_dual_uid2sid, "DUAL_UID2SID" },
+ { WINBINDD_DUAL_GID2SID, winbindd_dual_gid2sid, "DUAL_GID2SID" },
+ { WINBINDD_DUAL_UID2NAME, winbindd_dual_uid2name, "DUAL_UID2NAME" },
+ { WINBINDD_DUAL_NAME2UID, winbindd_dual_name2uid, "DUAL_NAME2UID" },
+ { WINBINDD_DUAL_GID2NAME, winbindd_dual_gid2name, "DUAL_GID2NAME" },
+ { WINBINDD_DUAL_NAME2GID, winbindd_dual_name2gid, "DUAL_NAME2GID" },
+ { WINBINDD_DUAL_SET_MAPPING, winbindd_dual_set_mapping, "DUAL_SET_MAPPING" },
+ { WINBINDD_DUAL_SET_HWM, winbindd_dual_set_hwm, "DUAL_SET_HWMS" },
+ { WINBINDD_DUAL_DUMP_MAPS, winbindd_dual_dump_maps, "DUAL_DUMP_MAPS" },
+ { WINBINDD_DUAL_USERINFO, winbindd_dual_userinfo, "DUAL_USERINFO" },
+ { WINBINDD_ALLOCATE_UID, winbindd_dual_allocate_uid, "ALLOCATE_UID" },
+ { WINBINDD_ALLOCATE_GID, winbindd_dual_allocate_gid, "ALLOCATE_GID" },
+ { WINBINDD_GETUSERDOMGROUPS, winbindd_dual_getuserdomgroups, "GETUSERDOMGROUPS" },
+ { WINBINDD_DUAL_GETSIDALIASES, winbindd_dual_getsidaliases, "GETSIDALIASES" },
+ { WINBINDD_CCACHE_NTLMAUTH, winbindd_dual_ccache_ntlm_auth, "CCACHE_NTLM_AUTH" },
+ /* End of list */
+
+ { WINBINDD_NUM_CMDS, NULL, "NONE" }
+};
+
+static void child_process_request(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct winbindd_child_dispatch_table *table;
+
+ /* Free response data - we may be interrupted and receive another
+ command before being able to send this data off. */
+
+ state->response.result = WINBINDD_ERROR;
+ state->response.length = sizeof(struct winbindd_response);
+
+ /* as all requests in the child are sync, we can use talloc_tos() */
+ state->mem_ctx = talloc_tos();
+
+ /* Process command */
+
+ for (table = child_dispatch_table; table->fn; table++) {
+ if (state->request.cmd == table->cmd) {
+ DEBUG(10,("process_request: request fn %s\n",
+ table->winbindd_cmd_name ));
+ state->response.result = table->fn(domain, state);
+ break;
+ }
+ }
+
+ if (!table->fn) {
+ DEBUG(10,("process_request: unknown request fn number %d\n",
+ (int)state->request.cmd ));
+ state->response.result = WINBINDD_ERROR;
+ }
+}
+
+void setup_domain_child(struct winbindd_domain *domain,
+ struct winbindd_child *child,
+ const char *explicit_logfile)
+{
+ if (explicit_logfile != NULL) {
+ pstr_sprintf(child->logfilename, "%s/log.winbindd-%s",
+ dyn_LOGFILEBASE, explicit_logfile);
+ } else if (domain != NULL) {
+ pstr_sprintf(child->logfilename, "%s/log.wb-%s",
+ dyn_LOGFILEBASE, domain->name);
+ } else {
+ smb_panic("Internal error: domain == NULL && "
+ "explicit_logfile == NULL");
+ }
+
+ child->domain = domain;
+}
+
+struct winbindd_child *children = NULL;
+
+void winbind_child_died(pid_t pid)
+{
+ struct winbindd_child *child;
+
+ for (child = children; child != NULL; child = child->next) {
+ if (child->pid == pid) {
+ break;
+ }
+ }
+
+ if (child == NULL) {
+ DEBUG(5, ("Already reaped child %u died\n", (unsigned int)pid));
+ return;
+ }
+
+ remove_fd_event(&child->event);
+ close(child->event.fd);
+ child->event.fd = 0;
+ child->event.flags = 0;
+ child->pid = 0;
+
+ schedule_async_request(child);
+}
+
+/* Ensure any negative cache entries with the netbios or realm names are removed. */
+
+void winbindd_flush_negative_conn_cache(struct winbindd_domain *domain)
+{
+ flush_negative_conn_cache_for_domain(domain->name);
+ if (*domain->alt_name) {
+ flush_negative_conn_cache_for_domain(domain->alt_name);
+ }
+}
+
+/* Set our domains as offline and forward the offline message to our children. */
+
+void winbind_msg_offline(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_child *child;
+ struct winbindd_domain *domain;
+
+ DEBUG(10,("winbind_msg_offline: got offline message.\n"));
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("winbind_msg_offline: rejecting offline message.\n"));
+ return;
+ }
+
+ /* Set our global state as offline. */
+ if (!set_global_winbindd_state_offline()) {
+ DEBUG(10,("winbind_msg_offline: offline request failed.\n"));
+ return;
+ }
+
+ /* Set all our domains as offline. */
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+ DEBUG(5,("winbind_msg_offline: marking %s offline.\n", domain->name));
+ set_domain_offline(domain);
+ }
+
+ for (child = children; child != NULL; child = child->next) {
+ /* Don't send message to internal childs. We've already
+ done so above. */
+ if (!child->domain || winbindd_internal_child(child)) {
+ continue;
+ }
+
+ /* Or internal domains (this should not be possible....) */
+ if (child->domain->internal) {
+ continue;
+ }
+
+ /* Each winbindd child should only process requests for one domain - make sure
+ we only set it online / offline for that domain. */
+
+ DEBUG(10,("winbind_msg_offline: sending message to pid %u for domain %s.\n",
+ (unsigned int)child->pid, domain->name ));
+
+ messaging_send_buf(msg_ctx, pid_to_procid(child->pid),
+ MSG_WINBIND_OFFLINE,
+ (uint8 *)child->domain->name,
+ strlen(child->domain->name)+1);
+ }
+}
+
+/* Set our domains as online and forward the online message to our children. */
+
+void winbind_msg_online(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_child *child;
+ struct winbindd_domain *domain;
+
+ DEBUG(10,("winbind_msg_online: got online message.\n"));
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("winbind_msg_online: rejecting online message.\n"));
+ return;
+ }
+
+ /* Set our global state as online. */
+ set_global_winbindd_state_online();
+
+ smb_nscd_flush_user_cache();
+ smb_nscd_flush_group_cache();
+
+ /* Set all our domains as online. */
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+ DEBUG(5,("winbind_msg_online: requesting %s to go online.\n", domain->name));
+
+ winbindd_flush_negative_conn_cache(domain);
+ set_domain_online_request(domain);
+
+ /* Send an online message to the idmap child when our
+ primary domain comes back online */
+
+ if ( domain->primary ) {
+ struct winbindd_child *idmap = idmap_child();
+
+ if ( idmap->pid != 0 ) {
+ messaging_send_buf(msg_ctx,
+ pid_to_procid(idmap->pid),
+ MSG_WINBIND_ONLINE,
+ (uint8 *)domain->name,
+ strlen(domain->name)+1);
+ }
+
+ }
+ }
+
+ for (child = children; child != NULL; child = child->next) {
+ /* Don't send message to internal childs. */
+ if (!child->domain || winbindd_internal_child(child)) {
+ continue;
+ }
+
+ /* Or internal domains (this should not be possible....) */
+ if (child->domain->internal) {
+ continue;
+ }
+
+ /* Each winbindd child should only process requests for one domain - make sure
+ we only set it online / offline for that domain. */
+
+ DEBUG(10,("winbind_msg_online: sending message to pid %u for domain %s.\n",
+ (unsigned int)child->pid, child->domain->name ));
+
+ messaging_send_buf(msg_ctx, pid_to_procid(child->pid),
+ MSG_WINBIND_ONLINE,
+ (uint8 *)child->domain->name,
+ strlen(child->domain->name)+1);
+ }
+}
+
+/* Forward the online/offline messages to our children. */
+void winbind_msg_onlinestatus(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_child *child;
+
+ DEBUG(10,("winbind_msg_onlinestatus: got onlinestatus message.\n"));
+
+ for (child = children; child != NULL; child = child->next) {
+ if (child->domain && child->domain->primary) {
+ DEBUG(10,("winbind_msg_onlinestatus: "
+ "sending message to pid %u of primary domain.\n",
+ (unsigned int)child->pid));
+ messaging_send_buf(msg_ctx, pid_to_procid(child->pid),
+ MSG_WINBIND_ONLINESTATUS,
+ (uint8 *)data->data,
+ data->length);
+ break;
+ }
+ }
+}
+
+void winbind_msg_dump_event_list(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_child *child;
+
+ DEBUG(10,("winbind_msg_dump_event_list received\n"));
+
+ dump_event_list(winbind_event_context());
+
+ for (child = children; child != NULL; child = child->next) {
+
+ DEBUG(10,("winbind_msg_dump_event_list: sending message to pid %u\n",
+ (unsigned int)child->pid));
+
+ messaging_send_buf(msg_ctx, pid_to_procid(child->pid),
+ MSG_DUMP_EVENT_LIST,
+ NULL, 0);
+ }
+
+}
+
+static void account_lockout_policy_handler(struct event_context *ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data)
+{
+ struct winbindd_child *child =
+ (struct winbindd_child *)private_data;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct winbindd_methods *methods;
+ SAM_UNK_INFO_12 lockout_policy;
+ NTSTATUS result;
+
+ DEBUG(10,("account_lockout_policy_handler called\n"));
+
+ TALLOC_FREE(child->lockout_policy_event);
+
+ if ( !winbindd_can_contact_domain( child->domain ) ) {
+ DEBUG(10,("account_lockout_policy_handler: Removing myself since I "
+ "do not have an incoming trust to domain %s\n",
+ child->domain->name));
+
+ return;
+ }
+
+ methods = child->domain->methods;
+
+ mem_ctx = talloc_init("account_lockout_policy_handler ctx");
+ if (!mem_ctx) {
+ result = NT_STATUS_NO_MEMORY;
+ } else {
+ result = methods->lockout_policy(child->domain, mem_ctx, &lockout_policy);
+ }
+ TALLOC_FREE(mem_ctx);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("account_lockout_policy_handler: lockout_policy failed error %s\n",
+ nt_errstr(result)));
+ }
+
+ child->lockout_policy_event = event_add_timed(winbind_event_context(), NULL,
+ timeval_current_ofs(3600, 0),
+ "account_lockout_policy_handler",
+ account_lockout_policy_handler,
+ child);
+}
+
+/* Deal with a request to go offline. */
+
+static void child_msg_offline(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_domain *domain;
+ const char *domainname = (const char *)data->data;
+
+ if (data->data == NULL || data->length == 0) {
+ return;
+ }
+
+ DEBUG(5,("child_msg_offline received for domain %s.\n", domainname));
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("child_msg_offline: rejecting offline message.\n"));
+ return;
+ }
+
+ /* Mark the requested domain offline. */
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+ if (strequal(domain->name, domainname)) {
+ DEBUG(5,("child_msg_offline: marking %s offline.\n", domain->name));
+ set_domain_offline(domain);
+ }
+ }
+}
+
+/* Deal with a request to go online. */
+
+static void child_msg_online(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_domain *domain;
+ const char *domainname = (const char *)data->data;
+
+ if (data->data == NULL || data->length == 0) {
+ return;
+ }
+
+ DEBUG(5,("child_msg_online received for domain %s.\n", domainname));
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("child_msg_online: rejecting online message.\n"));
+ return;
+ }
+
+ /* Set our global state as online. */
+ set_global_winbindd_state_online();
+
+ /* Try and mark everything online - delete any negative cache entries
+ to force a reconnect now. */
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+ if (strequal(domain->name, domainname)) {
+ DEBUG(5,("child_msg_online: requesting %s to go online.\n", domain->name));
+ winbindd_flush_negative_conn_cache(domain);
+ set_domain_online_request(domain);
+ }
+ }
+}
+
+static const char *collect_onlinestatus(TALLOC_CTX *mem_ctx)
+{
+ struct winbindd_domain *domain;
+ char *buf = NULL;
+
+ if ((buf = talloc_asprintf(mem_ctx, "global:%s ",
+ get_global_winbindd_state_offline() ?
+ "Offline":"Online")) == NULL) {
+ return NULL;
+ }
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if ((buf = talloc_asprintf_append(buf, "%s:%s ",
+ domain->name,
+ domain->online ?
+ "Online":"Offline")) == NULL) {
+ return NULL;
+ }
+ }
+
+ buf = talloc_asprintf_append(buf, "\n");
+
+ DEBUG(5,("collect_onlinestatus: %s", buf));
+
+ return buf;
+}
+
+static void child_msg_onlinestatus(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ TALLOC_CTX *mem_ctx;
+ const char *message;
+ struct server_id *sender;
+
+ DEBUG(5,("winbind_msg_onlinestatus received.\n"));
+
+ if (!data->data) {
+ return;
+ }
+
+ sender = (struct server_id *)data->data;
+
+ mem_ctx = talloc_init("winbind_msg_onlinestatus");
+ if (mem_ctx == NULL) {
+ return;
+ }
+
+ message = collect_onlinestatus(mem_ctx);
+ if (message == NULL) {
+ talloc_destroy(mem_ctx);
+ return;
+ }
+
+ messaging_send_buf(msg_ctx, *sender, MSG_WINBIND_ONLINESTATUS,
+ (uint8 *)message, strlen(message) + 1);
+
+ talloc_destroy(mem_ctx);
+}
+
+static void child_msg_dump_event_list(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ DEBUG(5,("child_msg_dump_event_list received\n"));
+
+ dump_event_list(winbind_event_context());
+}
+
+
+static BOOL fork_domain_child(struct winbindd_child *child)
+{
+ int fdpair[2];
+ struct winbindd_cli_state state;
+ struct winbindd_domain *domain;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) != 0) {
+ DEBUG(0, ("Could not open child pipe: %s\n",
+ strerror(errno)));
+ return False;
+ }
+
+ ZERO_STRUCT(state);
+ state.pid = sys_getpid();
+
+ /* Stop zombies */
+ CatchChild();
+
+ child->pid = sys_fork();
+
+ if (child->pid == -1) {
+ DEBUG(0, ("Could not fork: %s\n", strerror(errno)));
+ return False;
+ }
+
+ if (child->pid != 0) {
+ /* Parent */
+ close(fdpair[0]);
+ child->next = child->prev = NULL;
+ DLIST_ADD(children, child);
+ child->event.fd = fdpair[1];
+ child->event.flags = 0;
+ child->requests = NULL;
+ add_fd_event(&child->event);
+ return True;
+ }
+
+ /* Child */
+
+ state.sock = fdpair[0];
+ close(fdpair[1]);
+
+ /* tdb needs special fork handling */
+ if (tdb_reopen_all(1) == -1) {
+ DEBUG(0,("tdb_reopen_all failed.\n"));
+ _exit(0);
+ }
+
+ close_conns_after_fork();
+
+ if (!override_logfile) {
+ lp_set_logfile(child->logfilename);
+ reopen_logs();
+ }
+
+ /*
+ * For clustering, we need to re-init our ctdbd connection after the
+ * fork
+ */
+ if (!NT_STATUS_IS_OK(messaging_reinit(winbind_messaging_context())))
+ exit(1);
+
+ /* Don't handle the same messages as our parent. */
+ messaging_deregister(winbind_messaging_context(),
+ MSG_SMB_CONF_UPDATED, NULL);
+ messaging_deregister(winbind_messaging_context(),
+ MSG_SHUTDOWN, NULL);
+ messaging_deregister(winbind_messaging_context(),
+ MSG_WINBIND_OFFLINE, NULL);
+ messaging_deregister(winbind_messaging_context(),
+ MSG_WINBIND_ONLINE, NULL);
+ messaging_deregister(winbind_messaging_context(),
+ MSG_WINBIND_ONLINESTATUS, NULL);
+ messaging_deregister(winbind_messaging_context(),
+ MSG_DUMP_EVENT_LIST, NULL);
+
+ /* Handle online/offline messages. */
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_WINBIND_OFFLINE, child_msg_offline);
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_WINBIND_ONLINE, child_msg_online);
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_WINBIND_ONLINESTATUS, child_msg_onlinestatus);
+ messaging_register(winbind_messaging_context(), NULL,
+ MSG_DUMP_EVENT_LIST, child_msg_dump_event_list);
+
+ if ( child->domain ) {
+ child->domain->startup = True;
+ child->domain->startup_time = time(NULL);
+ }
+
+ /* Ensure we have no pending check_online events other
+ than one for this domain. */
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain != child->domain) {
+ TALLOC_FREE(domain->check_online_event);
+ }
+ }
+
+ /* Ensure we're not handling an event inherited from
+ our parent. */
+
+ cancel_named_event(winbind_event_context(),
+ "krb5_ticket_refresh_handler");
+
+ /* We might be in the idmap child...*/
+ if (child->domain && !(child->domain->internal) &&
+ lp_winbind_offline_logon()) {
+
+ set_domain_online_request(child->domain);
+
+ child->lockout_policy_event = event_add_timed(
+ winbind_event_context(), NULL, timeval_zero(),
+ "account_lockout_policy_handler",
+ account_lockout_policy_handler,
+ child);
+ }
+
+ while (1) {
+
+ int ret;
+ fd_set read_fds;
+ struct timeval t;
+ struct timeval *tp;
+ struct timeval now;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ run_events(winbind_event_context(), 0, NULL, NULL);
+
+ GetTimeOfDay(&now);
+
+ if (child->domain && child->domain->startup &&
+ (now.tv_sec > child->domain->startup_time + 30)) {
+ /* No longer in "startup" mode. */
+ DEBUG(10,("fork_domain_child: domain %s no longer in 'startup' mode.\n",
+ child->domain->name ));
+ child->domain->startup = False;
+ }
+
+ tp = get_timed_events_timeout(winbind_event_context(), &t);
+ if (tp) {
+ DEBUG(11,("select will use timeout of %u.%u seconds\n",
+ (unsigned int)tp->tv_sec, (unsigned int)tp->tv_usec ));
+ }
+
+ /* Handle messages */
+
+ message_dispatch(winbind_messaging_context());
+
+ FD_ZERO(&read_fds);
+ FD_SET(state.sock, &read_fds);
+
+ ret = sys_select(state.sock + 1, &read_fds, NULL, NULL, tp);
+
+ if (ret == 0) {
+ DEBUG(11,("nothing is ready yet, continue\n"));
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ if (ret == -1 && errno == EINTR) {
+ /* We got a signal - continue. */
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ if (ret == -1 && errno != EINTR) {
+ DEBUG(0,("select error occured\n"));
+ TALLOC_FREE(frame);
+ perror("select");
+ return False;
+ }
+
+ /* fetch a request from the main daemon */
+ child_read_request(&state);
+
+ if (state.finished) {
+ /* we lost contact with our parent */
+ exit(0);
+ }
+
+ DEBUG(4,("child daemon request %d\n", (int)state.request.cmd));
+
+ ZERO_STRUCT(state.response);
+ state.request.null_term = '\0';
+ child_process_request(child->domain, &state);
+
+ SAFE_FREE(state.request.extra_data.data);
+
+ cache_store_response(sys_getpid(), &state.response);
+
+ SAFE_FREE(state.response.extra_data.data);
+
+ /* We just send the result code back, the result
+ * structure needs to be fetched via the
+ * winbindd_cache. Hmm. That needs fixing... */
+
+ if (write_data(state.sock,
+ (const char *)&state.response.result,
+ sizeof(state.response.result)) !=
+ sizeof(state.response.result)) {
+ DEBUG(0, ("Could not write result\n"));
+ exit(1);
+ }
+ TALLOC_FREE(frame);
+ }
+}
diff --git a/source3/winbindd/winbindd_group.c b/source3/winbindd/winbindd_group.c
new file mode 100644
index 0000000000..9a4b02f734
--- /dev/null
+++ b/source3/winbindd/winbindd_group.c
@@ -0,0 +1,1746 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon for ntdom nss module
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jeremy Allison 2001.
+ Copyright (C) Gerald (Jerry) Carter 2003.
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+extern BOOL opt_nocache;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static void add_member(const char *domain, const char *user,
+ char **pp_members, size_t *p_num_members)
+{
+ fstring name;
+
+ fill_domain_username(name, domain, user, True);
+ safe_strcat(name, ",", sizeof(name)-1);
+ string_append(pp_members, name);
+ *p_num_members += 1;
+}
+
+/**********************************************************************
+ Add member users resulting from sid. Expand if it is a domain group.
+**********************************************************************/
+
+static void add_expanded_sid(const DOM_SID *sid, char **pp_members, size_t *p_num_members)
+{
+ DOM_SID dom_sid;
+ uint32 rid;
+ struct winbindd_domain *domain;
+ size_t i;
+
+ char *domain_name = NULL;
+ char *name = NULL;
+ enum lsa_SidType type;
+
+ uint32 num_names;
+ DOM_SID *sid_mem;
+ char **names;
+ uint32 *types;
+
+ NTSTATUS result;
+
+ TALLOC_CTX *mem_ctx = talloc_init("add_expanded_sid");
+
+ if (mem_ctx == NULL) {
+ DEBUG(1, ("talloc_init failed\n"));
+ return;
+ }
+
+ sid_copy(&dom_sid, sid);
+ sid_split_rid(&dom_sid, &rid);
+
+ domain = find_lookup_domain_from_sid(sid);
+
+ if (domain == NULL) {
+ DEBUG(3, ("Could not find domain for sid %s\n",
+ sid_string_static(sid)));
+ goto done;
+ }
+
+ result = domain->methods->sid_to_name(domain, mem_ctx, sid,
+ &domain_name, &name, &type);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(3, ("sid_to_name failed for sid %s\n",
+ sid_string_static(sid)));
+ goto done;
+ }
+
+ DEBUG(10, ("Found name %s, type %d\n", name, type));
+
+ if (type == SID_NAME_USER) {
+ add_member(domain_name, name, pp_members, p_num_members);
+ goto done;
+ }
+
+ if (type != SID_NAME_DOM_GRP) {
+ DEBUG(10, ("Alias member %s neither user nor group, ignore\n",
+ name));
+ goto done;
+ }
+
+ /* Expand the domain group, this must be done via the target domain */
+
+ domain = find_domain_from_sid(sid);
+
+ if (domain == NULL) {
+ DEBUG(3, ("Could not find domain from SID %s\n",
+ sid_string_static(sid)));
+ goto done;
+ }
+
+ result = domain->methods->lookup_groupmem(domain, mem_ctx,
+ sid, &num_names,
+ &sid_mem, &names,
+ &types);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("Could not lookup group members for %s: %s\n",
+ name, nt_errstr(result)));
+ goto done;
+ }
+
+ for (i=0; i<num_names; i++) {
+ DEBUG(10, ("Adding group member SID %s\n",
+ sid_string_static(&sid_mem[i])));
+
+ if (types[i] != SID_NAME_USER) {
+ DEBUG(1, ("Hmmm. Member %s of group %s is no user. "
+ "Ignoring.\n", names[i], name));
+ continue;
+ }
+
+ add_member(domain->name, names[i], pp_members, p_num_members);
+ }
+
+ done:
+ talloc_destroy(mem_ctx);
+ return;
+}
+
+static BOOL fill_passdb_alias_grmem(struct winbindd_domain *domain,
+ DOM_SID *group_sid,
+ size_t *num_gr_mem, char **gr_mem, size_t *gr_mem_len)
+{
+ DOM_SID *members;
+ size_t i, num_members;
+
+ *num_gr_mem = 0;
+ *gr_mem = NULL;
+ *gr_mem_len = 0;
+
+ if (!NT_STATUS_IS_OK(pdb_enum_aliasmem(group_sid, &members,
+ &num_members)))
+ return True;
+
+ for (i=0; i<num_members; i++) {
+ add_expanded_sid(&members[i], gr_mem, num_gr_mem);
+ }
+
+ TALLOC_FREE(members);
+
+ if (*gr_mem != NULL) {
+ size_t len;
+
+ /* We have at least one member, strip off the last "," */
+ len = strlen(*gr_mem);
+ (*gr_mem)[len-1] = '\0';
+ *gr_mem_len = len;
+ }
+
+ return True;
+}
+
+/* Fill a grent structure from various other information */
+
+static BOOL fill_grent(struct winbindd_gr *gr, const char *dom_name,
+ const char *gr_name, gid_t unix_gid)
+{
+ fstring full_group_name;
+
+ fill_domain_username( full_group_name, dom_name, gr_name, True );
+
+ gr->gr_gid = unix_gid;
+
+ /* Group name and password */
+
+ safe_strcpy(gr->gr_name, full_group_name, sizeof(gr->gr_name) - 1);
+ safe_strcpy(gr->gr_passwd, "x", sizeof(gr->gr_passwd) - 1);
+
+ return True;
+}
+
+/***********************************************************************
+ If "enum users" is set to false, and the group being looked
+ up is the Domain Users SID: S-1-5-domain-513, then for the
+ list of members check if the querying user is in that group,
+ and if so only return that user as the gr_mem array.
+ We can change this to a different parameter than "enum users"
+ if neccessaey, or parameterize the group list we do this for.
+***********************************************************************/
+
+static BOOL fill_grent_mem_domusers( TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ struct winbindd_cli_state *state,
+ DOM_SID *group_sid,
+ enum lsa_SidType group_name_type,
+ size_t *num_gr_mem, char **gr_mem,
+ size_t *gr_mem_len)
+{
+ DOM_SID querying_user_sid;
+ DOM_SID *pquerying_user_sid = NULL;
+ uint32 num_groups = 0;
+ DOM_SID *user_sids = NULL;
+ BOOL u_in_group = False;
+ NTSTATUS status;
+ int i;
+ unsigned int buf_len = 0;
+ char *buf = NULL;
+
+ DEBUG(10,("fill_grent_mem_domain_users: domain %s\n",
+ domain->name ));
+
+ if (state) {
+ uid_t ret_uid = (uid_t)-1;
+ if (sys_getpeereid(state->sock, &ret_uid)==0) {
+ /* We know who's asking - look up their SID if
+ it's one we've mapped before. */
+ status = idmap_uid_to_sid(&querying_user_sid, ret_uid);
+ if (NT_STATUS_IS_OK(status)) {
+ pquerying_user_sid = &querying_user_sid;
+ DEBUG(10,("fill_grent_mem_domain_users: querying uid %u -> %s\n",
+ (unsigned int)ret_uid,
+ sid_string_static(pquerying_user_sid) ));
+ }
+ }
+ }
+
+ /* Only look up if it was a winbindd user in this domain. */
+ if (pquerying_user_sid &&
+ (sid_compare_domain(pquerying_user_sid, &domain->sid) == 0)) {
+
+ DEBUG(10,("fill_grent_mem_domain_users: querying user = %s\n",
+ sid_string_static(pquerying_user_sid) ));
+
+ status = domain->methods->lookup_usergroups(domain,
+ mem_ctx,
+ pquerying_user_sid,
+ &num_groups,
+ &user_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("fill_grent_mem_domain_users: lookup_usergroups failed "
+ "for sid %s in domain %s (error: %s)\n",
+ sid_string_static(pquerying_user_sid),
+ domain->name,
+ nt_errstr(status)));
+ return False;
+ }
+
+ for (i = 0; i < num_groups; i++) {
+ if (sid_equal(group_sid, &user_sids[i])) {
+ /* User is in Domain Users, add their name
+ as the only group member. */
+ u_in_group = True;
+ break;
+ }
+ }
+ }
+
+ if (u_in_group) {
+ size_t len = 0;
+ char *domainname = NULL;
+ char *username = NULL;
+ fstring name;
+ enum lsa_SidType type;
+
+ DEBUG(10,("fill_grent_mem_domain_users: sid %s in 'Domain Users' in domain %s\n",
+ sid_string_static(pquerying_user_sid), domain->name ));
+
+ status = domain->methods->sid_to_name(domain, mem_ctx,
+ pquerying_user_sid,
+ &domainname,
+ &username,
+ &type);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("could not lookup username for user "
+ "sid %s in domain %s (error: %s)\n",
+ sid_string_static(pquerying_user_sid),
+ domain->name,
+ nt_errstr(status)));
+ return False;
+ }
+ fill_domain_username(name, domain->name, username, True);
+ len = strlen(name);
+ buf_len = len + 1;
+ if (!(buf = (char *)SMB_MALLOC(buf_len))) {
+ DEBUG(1, ("out of memory\n"));
+ return False;
+ }
+ memcpy(buf, name, buf_len);
+
+ DEBUG(10,("fill_grent_mem_domain_users: user %s in "
+ "'Domain Users' in domain %s\n",
+ name, domain->name ));
+
+ /* user is the only member */
+ *num_gr_mem = 1;
+ }
+
+ *gr_mem = buf;
+ *gr_mem_len = buf_len;
+
+ DEBUG(10, ("fill_grent_mem_domain_users: num_mem = %u, len = %u, mem = %s\n",
+ (unsigned int)*num_gr_mem,
+ (unsigned int)buf_len, *num_gr_mem ? buf : "NULL"));
+
+ return True;
+}
+
+/***********************************************************************
+ Add names to a list. Assumes a canonical version of the string
+ in DOMAIN\user
+***********************************************************************/
+
+static int namecmp( const void *a, const void *b )
+{
+ return StrCaseCmp( * (char * const *) a, * (char * const *) b);
+}
+
+static NTSTATUS add_names_to_list( TALLOC_CTX *ctx,
+ char ***list, uint32 *n_list,
+ char **names, uint32 n_names )
+{
+ char **new_list = NULL;
+ uint32 n_new_list = 0;
+ int i, j;
+
+ if ( !names || (n_names == 0) )
+ return NT_STATUS_OK;
+
+ /* Alloc the maximum size we'll need */
+
+ if ( *list == NULL ) {
+ if ( (new_list = TALLOC_ARRAY( ctx, char *, n_names )) == NULL )
+ return NT_STATUS_NO_MEMORY;
+ n_new_list = n_names;
+ } else {
+ new_list = TALLOC_REALLOC_ARRAY( ctx, *list, char *,
+ (*n_list) + n_names );
+ if ( !new_list )
+ return NT_STATUS_NO_MEMORY;
+ n_new_list = (*n_list) + n_names;
+ }
+
+ /* Add all names */
+
+ for ( i=*n_list, j=0; i<n_new_list; i++, j++ ) {
+ new_list[i] = talloc_strdup( new_list, names[j] );
+ }
+
+ /* search for duplicates for sorting and looking for matching
+ neighbors */
+
+ qsort( new_list, n_new_list, sizeof(char*), QSORT_CAST namecmp );
+
+ for ( i=1; i<n_new_list; i++ ) {
+ if ( strcmp( new_list[i-1], new_list[i] ) == 0 ) {
+ memmove( &new_list[i-1], &new_list[i],
+ sizeof(char*)*(n_new_list-i) );
+ n_new_list--;
+ }
+ }
+
+ *list = new_list;
+ *n_list = n_new_list;
+
+ return NT_STATUS_OK;
+}
+
+/***********************************************************************
+***********************************************************************/
+
+static NTSTATUS expand_groups( TALLOC_CTX *ctx,
+ struct winbindd_domain *d,
+ DOM_SID *glist, uint32 n_glist,
+ DOM_SID **new_glist, uint32 *n_new_glist,
+ char ***members, uint32 *n_members )
+{
+ int i, j;
+ NTSTATUS status = NT_STATUS_OK;
+ uint32 num_names = 0;
+ uint32 *name_types = NULL;
+ char **names = NULL;
+ DOM_SID *sid_mem = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ DOM_SID *new_groups = NULL;
+ size_t new_groups_size = 0;
+
+ *members = NULL;
+ *n_members = 0;
+ *new_glist = NULL;
+ *n_new_glist = 0;
+
+ for ( i=0; i<n_glist; i++ ) {
+ tmp_ctx = talloc_new( ctx );
+
+ /* Lookup the group membership */
+
+ status = d->methods->lookup_groupmem(d, tmp_ctx,
+ &glist[i], &num_names,
+ &sid_mem, &names,
+ &name_types);
+ if ( !NT_STATUS_IS_OK(status) )
+ goto out;
+
+ /* Separate users and groups into two lists */
+
+ for ( j=0; j<num_names; j++ ) {
+
+ /* Users */
+ if ( name_types[j] == SID_NAME_USER ||
+ name_types[j] == SID_NAME_COMPUTER )
+ {
+ status = add_names_to_list( ctx, members,
+ n_members,
+ names+j, 1 );
+ if ( !NT_STATUS_IS_OK(status) )
+ goto out;
+
+ continue;
+ }
+
+ /* Groups */
+ if ( name_types[j] == SID_NAME_DOM_GRP ||
+ name_types[j] == SID_NAME_ALIAS )
+ {
+ BOOL ret;
+
+ ret = add_sid_to_array_unique( ctx,
+ &sid_mem[j],
+ &new_groups,
+ &new_groups_size );
+ if ( !ret ) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ continue;
+ }
+ }
+
+ TALLOC_FREE( tmp_ctx );
+ }
+
+ *new_glist = new_groups;
+ *n_new_glist = (uint32)new_groups_size;
+
+ out:
+ TALLOC_FREE( tmp_ctx );
+
+ return status;
+}
+
+/***********************************************************************
+ Fill in the group membership field of a NT group given by group_sid
+***********************************************************************/
+
+static BOOL fill_grent_mem(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state,
+ DOM_SID *group_sid,
+ enum lsa_SidType group_name_type,
+ size_t *num_gr_mem, char **gr_mem, size_t *gr_mem_len)
+{
+ uint32 num_names = 0;
+ unsigned int buf_len = 0, buf_ndx = 0, i;
+ char **names = NULL, *buf = NULL;
+ BOOL result = False;
+ TALLOC_CTX *mem_ctx;
+ uint32 group_rid;
+ DOM_SID *glist = NULL;
+ DOM_SID *new_glist = NULL;
+ uint32 n_glist, n_new_glist;
+ int max_depth = lp_winbind_expand_groups();
+
+ if (!(mem_ctx = talloc_init("fill_grent_mem(%s)", domain->name)))
+ return False;
+
+ DEBUG(10, ("group SID %s\n", sid_string_static(group_sid)));
+
+ /* Initialize with no members */
+
+ *num_gr_mem = 0;
+
+ /* HACK ALERT!! This whole routine does not cope with group members
+ * from more than one domain, ie aliases. Thus we have to work it out
+ * ourselves in a special routine. */
+
+ if (domain->internal) {
+ result = fill_passdb_alias_grmem(domain, group_sid,
+ num_gr_mem,
+ gr_mem, gr_mem_len);
+ goto done;
+ }
+
+ /* Verify name type */
+
+ if ( !((group_name_type==SID_NAME_DOM_GRP) ||
+ ((group_name_type==SID_NAME_ALIAS) && domain->primary)) )
+ {
+ DEBUG(1, ("SID %s in domain %s isn't a domain group (%d)\n",
+ sid_string_static(group_sid), domain->name,
+ group_name_type));
+ goto done;
+ }
+
+ /* OPTIMIZATION / HACK. See comment in
+ fill_grent_mem_domusers() */
+
+ sid_peek_rid( group_sid, &group_rid );
+ if (!lp_winbind_enum_users() && group_rid == DOMAIN_GROUP_RID_USERS) {
+ result = fill_grent_mem_domusers( mem_ctx, domain, state,
+ group_sid, group_name_type,
+ num_gr_mem, gr_mem,
+ gr_mem_len );
+ goto done;
+ }
+
+ /* Real work goes here. Create a list of group names to
+ expand startign with the initial one. Pass that to
+ expand_groups() which returns a list of more group names
+ to expand. Do this up to the max search depth. */
+
+ if ( (glist = TALLOC_ARRAY(mem_ctx, DOM_SID, 1 )) == NULL ) {
+ result = False;
+ DEBUG(0,("fill_grent_mem: talloc failure!\n"));
+ goto done;
+ }
+ sid_copy( &glist[0], group_sid );
+ n_glist = 1;
+
+ for ( i=0; i<max_depth && glist; i++ ) {
+ uint32 n_members = 0;
+ char **members = NULL;
+ NTSTATUS nt_status;
+
+ nt_status = expand_groups( mem_ctx, domain,
+ glist, n_glist,
+ &new_glist, &n_new_glist,
+ &members, &n_members);
+ if ( !NT_STATUS_IS_OK(nt_status) ) {
+ result = False;
+ goto done;
+ }
+
+ /* Add new group members to list */
+
+ nt_status = add_names_to_list( mem_ctx, &names, &num_names,
+ members, n_members );
+ if ( !NT_STATUS_IS_OK(nt_status) ) {
+ result = False;
+ goto done;
+ }
+
+ TALLOC_FREE( members );
+
+ /* If we have no more groups to expand, break out
+ early */
+
+ if ( !&new_glist )
+ break;
+
+ /* One more round */
+ TALLOC_FREE(glist);
+ glist = new_glist;
+ n_glist = n_new_glist;
+ }
+ TALLOC_FREE( glist );
+
+ DEBUG(10, ("looked up %d names\n", num_names));
+
+ again:
+ /* Add members to list */
+
+ for (i = 0; i < num_names; i++) {
+ int len;
+
+ DEBUG(10, ("processing name %s\n", names[i]));
+
+ len = strlen(names[i]);
+
+ /* Add to list or calculate buffer length */
+
+ if (!buf) {
+ buf_len += len + 1; /* List is comma separated */
+ (*num_gr_mem)++;
+ DEBUG(10, ("buf_len + %d = %d\n", len + 1, buf_len));
+ } else {
+ DEBUG(10, ("appending %s at ndx %d\n", names[i], buf_ndx));
+ safe_strcpy(&buf[buf_ndx], names[i], len);
+ buf_ndx += len;
+ buf[buf_ndx] = ',';
+ buf_ndx++;
+ }
+ }
+
+ /* Allocate buffer */
+
+ if (!buf && buf_len != 0) {
+ if (!(buf = (char *)SMB_MALLOC(buf_len))) {
+ DEBUG(1, ("out of memory\n"));
+ result = False;
+ goto done;
+ }
+ memset(buf, 0, buf_len);
+ goto again;
+ }
+
+ /* Now we're done */
+
+ if (buf && buf_ndx > 0) {
+ buf[buf_ndx - 1] = '\0';
+ }
+
+ *gr_mem = buf;
+ *gr_mem_len = buf_len;
+
+ DEBUG(10, ("num_mem = %u, len = %u, mem = %s\n", (unsigned int)*num_gr_mem,
+ (unsigned int)buf_len, *num_gr_mem ? buf : "NULL"));
+ result = True;
+
+done:
+
+ talloc_destroy(mem_ctx);
+
+ DEBUG(10, ("fill_grent_mem returning %d\n", result));
+
+ return result;
+}
+
+static void winbindd_getgrsid( struct winbindd_cli_state *state, DOM_SID group_sid );
+
+static void getgrnam_recv( void *private_data, BOOL success, const DOM_SID *sid,
+ enum lsa_SidType type )
+{
+ struct winbindd_cli_state *state = (struct winbindd_cli_state*)private_data;
+
+ if (!success) {
+ DEBUG(5,("getgrnam_recv: lookupname failed!\n"));
+ request_error(state);
+ return;
+ }
+
+ if ( (type != SID_NAME_DOM_GRP) && (type != SID_NAME_ALIAS) ) {
+ DEBUG(5,("getgrnam_recv: not a group!\n"));
+ request_error(state);
+ return;
+ }
+
+ winbindd_getgrsid( state, *sid );
+}
+
+
+/* Return a group structure from a group name */
+
+void winbindd_getgrnam(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+ fstring name_domain, name_group;
+ char *tmp;
+
+ /* Ensure null termination */
+ state->request.data.groupname[sizeof(state->request.data.groupname)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: getgrnam %s\n", (unsigned long)state->pid,
+ state->request.data.groupname));
+
+ /* Parse domain and groupname */
+
+ memset(name_group, 0, sizeof(fstring));
+
+ tmp = state->request.data.groupname;
+
+ name_domain[0] = '\0';
+ name_group[0] = '\0';
+
+ parse_domain_user(tmp, name_domain, name_group);
+
+ /* if no domain or our local domain and no local tdb group, default to
+ * our local domain for aliases */
+
+ if ( !*name_domain || strequal(name_domain, get_global_sam_name()) ) {
+ fstrcpy(name_domain, get_global_sam_name());
+ }
+
+ /* Get info for the domain */
+
+ if ((domain = find_domain_from_name(name_domain)) == NULL) {
+ DEBUG(3, ("could not get domain sid for domain %s\n",
+ name_domain));
+ request_error(state);
+ return;
+ }
+ /* should we deal with users for our domain? */
+
+ if ( lp_winbind_trusted_domains_only() && domain->primary) {
+ DEBUG(7,("winbindd_getgrnam: My domain -- rejecting "
+ "getgrnam() for %s\\%s.\n", name_domain, name_group));
+ request_error(state);
+ return;
+ }
+
+ /* Get rid and name type from name */
+
+ ws_name_replace( name_group, WB_REPLACE_CHAR );
+
+ winbindd_lookupname_async( state->mem_ctx, domain->name, name_group,
+ getgrnam_recv, WINBINDD_GETGRNAM, state );
+}
+
+struct getgrsid_state {
+ struct winbindd_cli_state *state;
+ struct winbindd_domain *domain;
+ char *group_name;
+ enum lsa_SidType group_type;
+ uid_t gid;
+ DOM_SID group_sid;
+};
+
+static void getgrsid_sid2gid_recv(void *private_data, BOOL success, gid_t gid)
+ {
+ struct getgrsid_state *s =
+ (struct getgrsid_state *)private_data;
+ struct winbindd_domain *domain;
+ size_t gr_mem_len;
+ size_t num_gr_mem;
+ char *gr_mem;
+ fstring dom_name, group_name;
+
+ if (!success) {
+ DEBUG(5,("getgrsid_sid2gid_recv: sid2gid failed!\n"));
+ request_error(s->state);
+ return;
+ }
+
+ s->gid = gid;
+
+ if ( !parse_domain_user( s->group_name, dom_name, group_name ) ) {
+ DEBUG(5,("getgrsid_sid2gid_recv: parse_domain_user() failed!\n"));
+ request_error(s->state);
+ return;
+ }
+
+
+ /* Fill in group structure */
+
+ if ( (domain = find_domain_from_name_noinit(dom_name)) == NULL ) {
+ DEBUG(1,("Can't find domain from name (%s)\n", dom_name));
+ request_error(s->state);
+ return;
+ }
+
+ if (!fill_grent(&s->state->response.data.gr, dom_name, group_name, gid) ||
+ !fill_grent_mem(domain, s->state, &s->group_sid, s->group_type,
+ &num_gr_mem, &gr_mem, &gr_mem_len))
+ {
+ request_error(s->state);
+ return;
+ }
+
+ s->state->response.data.gr.num_gr_mem = (uint32)num_gr_mem;
+
+ /* Group membership lives at start of extra data */
+
+ s->state->response.data.gr.gr_mem_ofs = 0;
+
+ s->state->response.length += gr_mem_len;
+ s->state->response.extra_data.data = gr_mem;
+
+ request_ok(s->state);
+ }
+
+static void getgrsid_lookupsid_recv( void *private_data, BOOL success,
+ const char *dom_name, const char *name,
+ enum lsa_SidType name_type )
+{
+ struct getgrsid_state *s = (struct getgrsid_state *)private_data;
+
+ if (!success) {
+ DEBUG(5,("getgrsid_lookupsid_recv: lookupsid failed!\n"));
+ request_error(s->state);
+ return;
+ }
+
+ /* either it's a domain group, a domain local group, or a
+ local group in an internal domain */
+
+ if ( !( (name_type==SID_NAME_DOM_GRP) ||
+ ((name_type==SID_NAME_ALIAS) &&
+ (s->domain->primary || s->domain->internal)) ) )
+ {
+ DEBUG(1, ("name '%s\\%s' is not a local or domain group: %d\n",
+ dom_name, name, name_type));
+ request_error(s->state);
+ return;
+}
+
+ if ( (s->group_name = talloc_asprintf( s->state->mem_ctx,
+ "%s\\%s",
+ dom_name, name )) == NULL )
+{
+ DEBUG(1, ("getgrsid_lookupsid_recv: talloc_asprintf() Failed!\n"));
+ request_error(s->state);
+ return;
+ }
+
+ s->group_type = name_type;
+
+ winbindd_sid2gid_async(s->state->mem_ctx, &s->group_sid,
+ getgrsid_sid2gid_recv, s);
+ }
+
+static void winbindd_getgrsid( struct winbindd_cli_state *state, const DOM_SID group_sid )
+ {
+ struct getgrsid_state *s;
+
+ if ( (s = TALLOC_ZERO_P(state->mem_ctx, struct getgrsid_state)) == NULL ) {
+ DEBUG(0, ("talloc failed\n"));
+ request_error(state);
+ return;
+ }
+
+ s->state = state;
+
+ if ( (s->domain = find_domain_from_sid_noinit(&group_sid)) == NULL ) {
+ DEBUG(3, ("Could not find domain for sid %s\n",
+ sid_string_static(&group_sid)));
+ request_error(state);
+ return;
+ }
+
+ sid_copy(&s->group_sid, &group_sid);
+
+ winbindd_lookupsid_async( s->state->mem_ctx, &group_sid,
+ getgrsid_lookupsid_recv, s );
+}
+
+
+static void getgrgid_recv(void *private_data, BOOL success, const char *sid)
+{
+ struct winbindd_cli_state *state = talloc_get_type_abort(private_data, struct winbindd_cli_state);
+ enum lsa_SidType name_type;
+ DOM_SID group_sid;
+
+ if (success) {
+ DEBUG(10,("getgrgid_recv: gid %lu has sid %s\n",
+ (unsigned long)(state->request.data.gid), sid));
+
+ string_to_sid(&group_sid, sid);
+ winbindd_getgrsid(state, group_sid);
+ return;
+ }
+
+ /* Ok, this might be "ours", i.e. an alias */
+ if (pdb_gid_to_sid(state->request.data.gid, &group_sid) &&
+ lookup_sid(state->mem_ctx, &group_sid, NULL, NULL, &name_type) &&
+ (name_type == SID_NAME_ALIAS)) {
+ /* Hey, got an alias */
+ DEBUG(10,("getgrgid_recv: we have an alias with gid %lu and sid %s\n",
+ (unsigned long)(state->request.data.gid), sid));
+ winbindd_getgrsid(state, group_sid);
+ return;
+ }
+
+ DEBUG(1, ("could not convert gid %lu to sid\n",
+ (unsigned long)state->request.data.gid));
+ request_error(state);
+}
+
+/* Return a group structure from a gid number */
+void winbindd_getgrgid(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: getgrgid %lu\n", (unsigned long)state->pid,
+ (unsigned long)state->request.data.gid));
+
+ /* always use the async interface */
+ winbindd_gid2sid_async(state->mem_ctx, state->request.data.gid, getgrgid_recv, state);
+}
+
+/*
+ * set/get/endgrent functions
+ */
+
+/* "Rewind" file pointer for group database enumeration */
+
+static BOOL winbindd_setgrent_internal(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+
+ DEBUG(3, ("[%5lu]: setgrent\n", (unsigned long)state->pid));
+
+ /* Check user has enabled this */
+
+ if (!lp_winbind_enum_groups()) {
+ return False;
+ }
+
+ /* Free old static data if it exists */
+
+ if (state->getgrent_state != NULL) {
+ free_getent_state(state->getgrent_state);
+ state->getgrent_state = NULL;
+ }
+
+ /* Create sam pipes for each domain we know about */
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ struct getent_state *domain_state;
+
+ /* Create a state record for this domain */
+
+ /* don't add our domaina if we are a PDC or if we
+ are a member of a Samba domain */
+
+ if ( lp_winbind_trusted_domains_only() && domain->primary )
+ {
+ continue;
+ }
+
+
+ if ((domain_state = SMB_MALLOC_P(struct getent_state)) == NULL) {
+ DEBUG(1, ("winbindd_setgrent: malloc failed for domain_state!\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(domain_state);
+
+ fstrcpy(domain_state->domain_name, domain->name);
+
+ /* Add to list of open domains */
+
+ DLIST_ADD(state->getgrent_state, domain_state);
+ }
+
+ state->getgrent_initialized = True;
+ return True;
+}
+
+void winbindd_setgrent(struct winbindd_cli_state *state)
+{
+ if (winbindd_setgrent_internal(state)) {
+ request_ok(state);
+ } else {
+ request_error(state);
+ }
+}
+
+/* Close file pointer to ntdom group database */
+
+void winbindd_endgrent(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: endgrent\n", (unsigned long)state->pid));
+
+ free_getent_state(state->getgrent_state);
+ state->getgrent_initialized = False;
+ state->getgrent_state = NULL;
+ request_ok(state);
+}
+
+/* Get the list of domain groups and domain aliases for a domain. We fill in
+ the sam_entries and num_sam_entries fields with domain group information.
+ The dispinfo_ndx field is incremented to the index of the next group to
+ fetch. Return True if some groups were returned, False otherwise. */
+
+static BOOL get_sam_group_entries(struct getent_state *ent)
+{
+ NTSTATUS status;
+ uint32 num_entries;
+ struct acct_info *name_list = NULL;
+ TALLOC_CTX *mem_ctx;
+ BOOL result = False;
+ struct acct_info *sam_grp_entries = NULL;
+ struct winbindd_domain *domain;
+
+ if (ent->got_sam_entries)
+ return False;
+
+ if (!(mem_ctx = talloc_init("get_sam_group_entries(%s)",
+ ent->domain_name))) {
+ DEBUG(1, ("get_sam_group_entries: could not create talloc context!\n"));
+ return False;
+ }
+
+ /* Free any existing group info */
+
+ SAFE_FREE(ent->sam_entries);
+ ent->num_sam_entries = 0;
+ ent->got_sam_entries = True;
+
+ /* Enumerate domain groups */
+
+ num_entries = 0;
+
+ if (!(domain = find_domain_from_name(ent->domain_name))) {
+ DEBUG(3, ("no such domain %s in get_sam_group_entries\n", ent->domain_name));
+ goto done;
+ }
+
+ /* always get the domain global groups */
+
+ status = domain->methods->enum_dom_groups(domain, mem_ctx, &num_entries, &sam_grp_entries);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("get_sam_group_entries: could not enumerate domain groups! Error: %s\n", nt_errstr(status)));
+ result = False;
+ goto done;
+ }
+
+ /* Copy entries into return buffer */
+
+ if (num_entries) {
+ if ( !(name_list = SMB_MALLOC_ARRAY(struct acct_info, num_entries)) ) {
+ DEBUG(0,("get_sam_group_entries: Failed to malloc memory for %d domain groups!\n",
+ num_entries));
+ result = False;
+ goto done;
+ }
+ memcpy( name_list, sam_grp_entries, num_entries * sizeof(struct acct_info) );
+ }
+
+ ent->num_sam_entries = num_entries;
+
+ /* get the domain local groups if we are a member of a native win2k domain
+ and are not using LDAP to get the groups */
+
+ if ( ( lp_security() != SEC_ADS && domain->native_mode
+ && domain->primary) || domain->internal )
+ {
+ DEBUG(4,("get_sam_group_entries: %s domain; enumerating local groups as well\n",
+ domain->native_mode ? "Native Mode 2k":"BUILTIN or local"));
+
+ status = domain->methods->enum_local_groups(domain, mem_ctx, &num_entries, &sam_grp_entries);
+
+ if ( !NT_STATUS_IS_OK(status) ) {
+ DEBUG(3,("get_sam_group_entries: Failed to enumerate domain local groups!\n"));
+ num_entries = 0;
+ }
+ else
+ DEBUG(4,("get_sam_group_entries: Returned %d local groups\n", num_entries));
+
+ /* Copy entries into return buffer */
+
+ if ( num_entries ) {
+ if ( !(name_list = SMB_REALLOC_ARRAY( name_list, struct acct_info, ent->num_sam_entries+num_entries)) )
+ {
+ DEBUG(0,("get_sam_group_entries: Failed to realloc more memory for %d local groups!\n",
+ num_entries));
+ result = False;
+ goto done;
+ }
+
+ memcpy( &name_list[ent->num_sam_entries], sam_grp_entries,
+ num_entries * sizeof(struct acct_info) );
+ }
+
+ ent->num_sam_entries += num_entries;
+ }
+
+
+ /* Fill in remaining fields */
+
+ ent->sam_entries = name_list;
+ ent->sam_entry_index = 0;
+
+ result = (ent->num_sam_entries > 0);
+
+ done:
+ talloc_destroy(mem_ctx);
+
+ return result;
+}
+
+/* Fetch next group entry from ntdom database */
+
+#define MAX_GETGRENT_GROUPS 500
+
+void winbindd_getgrent(struct winbindd_cli_state *state)
+{
+ struct getent_state *ent;
+ struct winbindd_gr *group_list = NULL;
+ int num_groups, group_list_ndx, gr_mem_list_len = 0;
+ char *gr_mem_list = NULL;
+
+ DEBUG(3, ("[%5lu]: getgrent\n", (unsigned long)state->pid));
+
+ /* Check user has enabled this */
+
+ if (!lp_winbind_enum_groups()) {
+ request_error(state);
+ return;
+ }
+
+ num_groups = MIN(MAX_GETGRENT_GROUPS, state->request.data.num_entries);
+
+ if (num_groups == 0) {
+ request_error(state);
+ return;
+ }
+
+ if ((state->response.extra_data.data = SMB_MALLOC_ARRAY(struct winbindd_gr, num_groups)) == NULL) {
+ request_error(state);
+ return;
+ }
+
+ memset(state->response.extra_data.data, '\0',
+ num_groups * sizeof(struct winbindd_gr) );
+
+ state->response.data.num_entries = 0;
+
+ group_list = (struct winbindd_gr *)state->response.extra_data.data;
+
+ if (!state->getgrent_initialized)
+ winbindd_setgrent_internal(state);
+
+ if (!(ent = state->getgrent_state)) {
+ request_error(state);
+ return;
+ }
+
+ /* Start sending back groups */
+
+ for (group_list_ndx = 0; group_list_ndx < num_groups; ) {
+ struct acct_info *name_list = NULL;
+ fstring domain_group_name;
+ uint32 result;
+ gid_t group_gid;
+ size_t gr_mem_len;
+ char *gr_mem;
+ DOM_SID group_sid;
+ struct winbindd_domain *domain;
+
+ /* Do we need to fetch another chunk of groups? */
+
+ tryagain:
+
+ DEBUG(10, ("entry_index = %d, num_entries = %d\n",
+ ent->sam_entry_index, ent->num_sam_entries));
+
+ if (ent->num_sam_entries == ent->sam_entry_index) {
+
+ while(ent && !get_sam_group_entries(ent)) {
+ struct getent_state *next_ent;
+
+ DEBUG(10, ("freeing state info for domain %s\n", ent->domain_name));
+
+ /* Free state information for this domain */
+
+ SAFE_FREE(ent->sam_entries);
+
+ next_ent = ent->next;
+ DLIST_REMOVE(state->getgrent_state, ent);
+
+ SAFE_FREE(ent);
+ ent = next_ent;
+ }
+
+ /* No more domains */
+
+ if (!ent)
+ break;
+ }
+
+ name_list = (struct acct_info *)ent->sam_entries;
+
+ if (!(domain =
+ find_domain_from_name(ent->domain_name))) {
+ DEBUG(3, ("No such domain %s in winbindd_getgrent\n", ent->domain_name));
+ result = False;
+ goto done;
+ }
+
+ /* Lookup group info */
+
+ sid_copy(&group_sid, &domain->sid);
+ sid_append_rid(&group_sid, name_list[ent->sam_entry_index].rid);
+
+ if (!NT_STATUS_IS_OK(idmap_sid_to_gid(&group_sid, &group_gid))) {
+ union unid_t id;
+ enum lsa_SidType type;
+
+ DEBUG(10, ("SID %s not in idmap\n",
+ sid_string_static(&group_sid)));
+
+ if (!pdb_sid_to_id(&group_sid, &id, &type)) {
+ DEBUG(1, ("could not look up gid for group "
+ "%s\n",
+ name_list[ent->sam_entry_index].acct_name));
+ ent->sam_entry_index++;
+ goto tryagain;
+ }
+
+ if ((type != SID_NAME_DOM_GRP) &&
+ (type != SID_NAME_ALIAS) &&
+ (type != SID_NAME_WKN_GRP)) {
+ DEBUG(1, ("Group %s is a %s, not a group\n",
+ sid_type_lookup(type),
+ name_list[ent->sam_entry_index].acct_name));
+ ent->sam_entry_index++;
+ goto tryagain;
+ }
+ group_gid = id.gid;
+ }
+
+ DEBUG(10, ("got gid %lu for group %lu\n", (unsigned long)group_gid,
+ (unsigned long)name_list[ent->sam_entry_index].rid));
+
+ /* Fill in group entry */
+
+ fill_domain_username(domain_group_name, ent->domain_name,
+ name_list[ent->sam_entry_index].acct_name, True);
+
+ result = fill_grent(&group_list[group_list_ndx],
+ ent->domain_name,
+ name_list[ent->sam_entry_index].acct_name,
+ group_gid);
+
+ /* Fill in group membership entry */
+
+ if (result) {
+ size_t num_gr_mem = 0;
+ DOM_SID member_sid;
+ group_list[group_list_ndx].num_gr_mem = 0;
+ gr_mem = NULL;
+ gr_mem_len = 0;
+
+ /* Get group membership */
+ if (state->request.cmd == WINBINDD_GETGRLST) {
+ result = True;
+ } else {
+ sid_copy(&member_sid, &domain->sid);
+ sid_append_rid(&member_sid, name_list[ent->sam_entry_index].rid);
+ result = fill_grent_mem(
+ domain,
+ NULL,
+ &member_sid,
+ SID_NAME_DOM_GRP,
+ &num_gr_mem,
+ &gr_mem, &gr_mem_len);
+
+ group_list[group_list_ndx].num_gr_mem = (uint32)num_gr_mem;
+ }
+ }
+
+ if (result) {
+ /* Append to group membership list */
+ gr_mem_list = (char *)SMB_REALLOC(
+ gr_mem_list, gr_mem_list_len + gr_mem_len);
+
+ if (!gr_mem_list && (group_list[group_list_ndx].num_gr_mem != 0)) {
+ DEBUG(0, ("out of memory\n"));
+ gr_mem_list_len = 0;
+ break;
+ }
+
+ DEBUG(10, ("list_len = %d, mem_len = %u\n",
+ gr_mem_list_len, (unsigned int)gr_mem_len));
+
+ memcpy(&gr_mem_list[gr_mem_list_len], gr_mem,
+ gr_mem_len);
+
+ SAFE_FREE(gr_mem);
+
+ group_list[group_list_ndx].gr_mem_ofs =
+ gr_mem_list_len;
+
+ gr_mem_list_len += gr_mem_len;
+ }
+
+ ent->sam_entry_index++;
+
+ /* Add group to return list */
+
+ if (result) {
+
+ DEBUG(10, ("adding group num_entries = %d\n",
+ state->response.data.num_entries));
+
+ group_list_ndx++;
+ state->response.data.num_entries++;
+
+ state->response.length +=
+ sizeof(struct winbindd_gr);
+
+ } else {
+ DEBUG(0, ("could not lookup domain group %s\n",
+ domain_group_name));
+ }
+ }
+
+ /* Copy the list of group memberships to the end of the extra data */
+
+ if (group_list_ndx == 0)
+ goto done;
+
+ state->response.extra_data.data = SMB_REALLOC(
+ state->response.extra_data.data,
+ group_list_ndx * sizeof(struct winbindd_gr) + gr_mem_list_len);
+
+ if (!state->response.extra_data.data) {
+ DEBUG(0, ("out of memory\n"));
+ group_list_ndx = 0;
+ SAFE_FREE(gr_mem_list);
+ request_error(state);
+ return;
+ }
+
+ memcpy(&((char *)state->response.extra_data.data)
+ [group_list_ndx * sizeof(struct winbindd_gr)],
+ gr_mem_list, gr_mem_list_len);
+
+ state->response.length += gr_mem_list_len;
+
+ DEBUG(10, ("returning %d groups, length = %d\n",
+ group_list_ndx, gr_mem_list_len));
+
+ /* Out of domains */
+
+ done:
+
+ SAFE_FREE(gr_mem_list);
+
+ if (group_list_ndx > 0)
+ request_ok(state);
+ else
+ request_error(state);
+}
+
+/* List domain groups without mapping to unix ids */
+
+void winbindd_list_groups(struct winbindd_cli_state *state)
+{
+ uint32 total_entries = 0;
+ struct winbindd_domain *domain;
+ const char *which_domain;
+ char *extra_data = NULL;
+ unsigned int extra_data_len = 0, i;
+
+ DEBUG(3, ("[%5lu]: list groups\n", (unsigned long)state->pid));
+
+ /* Ensure null termination */
+ state->request.domain_name[sizeof(state->request.domain_name)-1]='\0';
+ which_domain = state->request.domain_name;
+
+ /* Enumerate over trusted domains */
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ struct getent_state groups;
+
+ /* if we have a domain name restricting the request and this
+ one in the list doesn't match, then just bypass the remainder
+ of the loop */
+
+ if ( *which_domain && !strequal(which_domain, domain->name) )
+ continue;
+
+ ZERO_STRUCT(groups);
+
+ /* Get list of sam groups */
+
+ fstrcpy(groups.domain_name, domain->name);
+
+ get_sam_group_entries(&groups);
+
+ if (groups.num_sam_entries == 0) {
+ /* this domain is empty or in an error state */
+ continue;
+ }
+
+ /* keep track the of the total number of groups seen so
+ far over all domains */
+ total_entries += groups.num_sam_entries;
+
+ /* Allocate some memory for extra data. Note that we limit
+ account names to sizeof(fstring) = 128 characters. */
+ extra_data = (char *)SMB_REALLOC(
+ extra_data, sizeof(fstring) * total_entries);
+
+ if (!extra_data) {
+ DEBUG(0,("failed to enlarge buffer!\n"));
+ request_error(state);
+ return;
+ }
+
+ /* Pack group list into extra data fields */
+ for (i = 0; i < groups.num_sam_entries; i++) {
+ char *group_name = ((struct acct_info *)
+ groups.sam_entries)[i].acct_name;
+ fstring name;
+
+ fill_domain_username(name, domain->name, group_name, True);
+ /* Append to extra data */
+ memcpy(&extra_data[extra_data_len], name,
+ strlen(name));
+ extra_data_len += strlen(name);
+ extra_data[extra_data_len++] = ',';
+ }
+
+ SAFE_FREE(groups.sam_entries);
+ }
+
+ /* Assign extra_data fields in response structure */
+ if (extra_data) {
+ extra_data[extra_data_len - 1] = '\0';
+ state->response.extra_data.data = extra_data;
+ state->response.length += extra_data_len;
+ }
+
+ /* No domains may have responded but that's still OK so don't
+ return an error. */
+
+ request_ok(state);
+}
+
+/* Get user supplementary groups. This is much quicker than trying to
+ invert the groups database. We merge the groups from the gids and
+ other_sids info3 fields as trusted domain, universal group
+ memberships, and nested groups (win2k native mode only) are not
+ returned by the getgroups RPC call but are present in the info3. */
+
+struct getgroups_state {
+ struct winbindd_cli_state *state;
+ struct winbindd_domain *domain;
+ char *domname;
+ char *username;
+ DOM_SID user_sid;
+
+ const DOM_SID *token_sids;
+ size_t i, num_token_sids;
+
+ gid_t *token_gids;
+ size_t num_token_gids;
+};
+
+static void getgroups_usersid_recv(void *private_data, BOOL success,
+ const DOM_SID *sid, enum lsa_SidType type);
+static void getgroups_tokensids_recv(void *private_data, BOOL success,
+ DOM_SID *token_sids, size_t num_token_sids);
+static void getgroups_sid2gid_recv(void *private_data, BOOL success, gid_t gid);
+
+void winbindd_getgroups(struct winbindd_cli_state *state)
+{
+ struct getgroups_state *s;
+
+ /* Ensure null termination */
+ state->request.data.username
+ [sizeof(state->request.data.username)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: getgroups %s\n", (unsigned long)state->pid,
+ state->request.data.username));
+
+ /* Parse domain and username */
+
+ s = TALLOC_P(state->mem_ctx, struct getgroups_state);
+ if (s == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ request_error(state);
+ return;
+ }
+
+ s->state = state;
+
+ ws_name_return( state->request.data.username, WB_REPLACE_CHAR );
+
+ if (!parse_domain_user_talloc(state->mem_ctx,
+ state->request.data.username,
+ &s->domname, &s->username)) {
+ DEBUG(5, ("Could not parse domain user: %s\n",
+ state->request.data.username));
+
+ /* error out if we do not have nested group support */
+
+ if ( !lp_winbind_nested_groups() ) {
+ request_error(state);
+ return;
+ }
+
+ s->domname = talloc_strdup( state->mem_ctx, get_global_sam_name() );
+ s->username = talloc_strdup( state->mem_ctx, state->request.data.username );
+ }
+
+ /* Get info for the domain */
+
+ s->domain = find_domain_from_name_noinit(s->domname);
+
+ if (s->domain == NULL) {
+ DEBUG(7, ("could not find domain entry for domain %s\n",
+ s->domname));
+ request_error(state);
+ return;
+ }
+
+ if ( s->domain->primary && lp_winbind_trusted_domains_only()) {
+ DEBUG(7,("winbindd_getgroups: My domain -- rejecting "
+ "getgroups() for %s\\%s.\n", s->domname,
+ s->username));
+ request_error(state);
+ return;
+ }
+
+ /* Get rid and name type from name. The following costs 1 packet */
+
+ winbindd_lookupname_async(state->mem_ctx, s->domname, s->username,
+ getgroups_usersid_recv, WINBINDD_GETGROUPS, s);
+}
+
+static void getgroups_usersid_recv(void *private_data, BOOL success,
+ const DOM_SID *sid, enum lsa_SidType type)
+{
+ struct getgroups_state *s =
+ (struct getgroups_state *)private_data;
+
+ if ((!success) ||
+ ((type != SID_NAME_USER) && (type != SID_NAME_COMPUTER))) {
+ request_error(s->state);
+ return;
+ }
+
+ sid_copy(&s->user_sid, sid);
+
+ winbindd_gettoken_async(s->state->mem_ctx, &s->user_sid,
+ getgroups_tokensids_recv, s);
+}
+
+static void getgroups_tokensids_recv(void *private_data, BOOL success,
+ DOM_SID *token_sids, size_t num_token_sids)
+{
+ struct getgroups_state *s =
+ (struct getgroups_state *)private_data;
+
+ /* We need at least the user sid and the primary group in the token,
+ * otherwise it's an error */
+
+ if ((!success) || (num_token_sids < 2)) {
+ request_error(s->state);
+ return;
+ }
+
+ s->token_sids = token_sids;
+ s->num_token_sids = num_token_sids;
+ s->i = 0;
+
+ s->token_gids = NULL;
+ s->num_token_gids = 0;
+
+ getgroups_sid2gid_recv(s, False, 0);
+}
+
+static void getgroups_sid2gid_recv(void *private_data, BOOL success, gid_t gid)
+{
+ struct getgroups_state *s =
+ (struct getgroups_state *)private_data;
+
+ if (success) {
+ if (!add_gid_to_array_unique(s->state->mem_ctx, gid,
+ &s->token_gids,
+ &s->num_token_gids)) {
+ return;
+ }
+ }
+
+ if (s->i < s->num_token_sids) {
+ const DOM_SID *sid = &s->token_sids[s->i];
+ s->i += 1;
+
+ if (sid_equal(sid, &s->user_sid)) {
+ getgroups_sid2gid_recv(s, False, 0);
+ return;
+ }
+
+ winbindd_sid2gid_async(s->state->mem_ctx, sid,
+ getgroups_sid2gid_recv, s);
+ return;
+ }
+
+ s->state->response.data.num_entries = s->num_token_gids;
+ /* s->token_gids are talloced */
+ s->state->response.extra_data.data = smb_xmemdup(s->token_gids, s->num_token_gids * sizeof(gid_t));
+ s->state->response.length += s->num_token_gids * sizeof(gid_t);
+ request_ok(s->state);
+}
+
+/* Get user supplementary sids. This is equivalent to the
+ winbindd_getgroups() function but it involves a SID->SIDs mapping
+ rather than a NAME->SID->SIDS->GIDS mapping, which means we avoid
+ idmap. This call is designed to be used with applications that need
+ to do ACL evaluation themselves. Note that the cached info3 data is
+ not used
+
+ this function assumes that the SID that comes in is a user SID. If
+ you pass in another type of SID then you may get unpredictable
+ results.
+*/
+
+static void getusersids_recv(void *private_data, BOOL success, DOM_SID *sids,
+ size_t num_sids);
+
+void winbindd_getusersids(struct winbindd_cli_state *state)
+{
+ DOM_SID *user_sid;
+
+ /* Ensure null termination */
+ state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
+
+ user_sid = TALLOC_P(state->mem_ctx, DOM_SID);
+ if (user_sid == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ request_error(state);
+ return;
+ }
+
+ if (!string_to_sid(user_sid, state->request.data.sid)) {
+ DEBUG(1, ("Could not get convert sid %s from string\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ winbindd_gettoken_async(state->mem_ctx, user_sid, getusersids_recv,
+ state);
+}
+
+static void getusersids_recv(void *private_data, BOOL success, DOM_SID *sids,
+ size_t num_sids)
+{
+ struct winbindd_cli_state *state =
+ (struct winbindd_cli_state *)private_data;
+ char *ret = NULL;
+ unsigned ofs, ret_size = 0;
+ size_t i;
+
+ if (!success) {
+ request_error(state);
+ return;
+ }
+
+ /* work out the response size */
+ for (i = 0; i < num_sids; i++) {
+ const char *s = sid_string_static(&sids[i]);
+ ret_size += strlen(s) + 1;
+ }
+
+ /* build the reply */
+ ret = (char *)SMB_MALLOC(ret_size);
+ if (!ret) {
+ DEBUG(0, ("malloc failed\n"));
+ request_error(state);
+ return;
+ }
+ ofs = 0;
+ for (i = 0; i < num_sids; i++) {
+ const char *s = sid_string_static(&sids[i]);
+ safe_strcpy(ret + ofs, s, ret_size - ofs - 1);
+ ofs += strlen(ret+ofs) + 1;
+ }
+
+ /* Send data back to client */
+ state->response.data.num_entries = num_sids;
+ state->response.extra_data.data = ret;
+ state->response.length += ret_size;
+ request_ok(state);
+}
+
+void winbindd_getuserdomgroups(struct winbindd_cli_state *state)
+{
+ DOM_SID user_sid;
+ struct winbindd_domain *domain;
+
+ /* Ensure null termination */
+ state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
+
+ if (!string_to_sid(&user_sid, state->request.data.sid)) {
+ DEBUG(1, ("Could not get convert sid %s from string\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ /* Get info for the domain */
+ if ((domain = find_domain_from_sid_noinit(&user_sid)) == NULL) {
+ DEBUG(0,("could not find domain entry for sid %s\n",
+ sid_string_static(&user_sid)));
+ request_error(state);
+ return;
+ }
+
+ sendto_domain(state, domain);
+}
+
+enum winbindd_result winbindd_dual_getuserdomgroups(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DOM_SID user_sid;
+ NTSTATUS status;
+
+ char *sidstring;
+ ssize_t len;
+ DOM_SID *groups;
+ uint32 num_groups;
+
+ /* Ensure null termination */
+ state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
+
+ if (!string_to_sid(&user_sid, state->request.data.sid)) {
+ DEBUG(1, ("Could not get convert sid %s from string\n",
+ state->request.data.sid));
+ return WINBINDD_ERROR;
+ }
+
+ status = domain->methods->lookup_usergroups(domain, state->mem_ctx,
+ &user_sid, &num_groups,
+ &groups);
+ if (!NT_STATUS_IS_OK(status))
+ return WINBINDD_ERROR;
+
+ if (num_groups == 0) {
+ state->response.data.num_entries = 0;
+ state->response.extra_data.data = NULL;
+ return WINBINDD_OK;
+ }
+
+ if (!print_sidlist(state->mem_ctx, groups, num_groups, &sidstring, &len)) {
+ DEBUG(0, ("talloc failed\n"));
+ return WINBINDD_ERROR;
+ }
+
+ state->response.extra_data.data = SMB_STRDUP(sidstring);
+ if (!state->response.extra_data.data) {
+ return WINBINDD_ERROR;
+ }
+ state->response.length += len+1;
+ state->response.data.num_entries = num_groups;
+
+ return WINBINDD_OK;
+}
diff --git a/source3/winbindd/winbindd_misc.c b/source3/winbindd/winbindd_misc.c
new file mode 100644
index 0000000000..9c3f634534
--- /dev/null
+++ b/source3/winbindd/winbindd_misc.c
@@ -0,0 +1,634 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - miscellaneous other functions
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Andrew Bartlett 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/* Check the machine account password is valid */
+
+void winbindd_check_machine_acct(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: check machine account\n",
+ (unsigned long)state->pid));
+
+ sendto_domain(state, find_our_domain());
+}
+
+enum winbindd_result winbindd_dual_check_machine_acct(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ int num_retries = 0;
+ struct winbindd_domain *contact_domain;
+
+ DEBUG(3, ("[%5lu]: check machine account\n", (unsigned long)state->pid));
+
+ /* Get trust account password */
+
+ again:
+
+ contact_domain = find_our_domain();
+
+ /* This call does a cli_nt_setup_creds() which implicitly checks
+ the trust account password. */
+
+ invalidate_cm_connection(&contact_domain->conn);
+
+ {
+ struct rpc_pipe_client *netlogon_pipe;
+ result = cm_connect_netlogon(contact_domain, &netlogon_pipe);
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
+ goto done;
+ }
+
+ /* There is a race condition between fetching the trust account
+ password and the periodic machine password change. So it's
+ possible that the trust account password has been changed on us.
+ We are returned NT_STATUS_ACCESS_DENIED if this happens. */
+
+#define MAX_RETRIES 8
+
+ if ((num_retries < MAX_RETRIES) &&
+ NT_STATUS_V(result) == NT_STATUS_V(NT_STATUS_ACCESS_DENIED)) {
+ num_retries++;
+ goto again;
+ }
+
+ /* Pass back result code - zero for success, other values for
+ specific failures. */
+
+ DEBUG(3, ("secret is %s\n", NT_STATUS_IS_OK(result) ?
+ "good" : "bad"));
+
+ done:
+ state->response.data.auth.nt_status = NT_STATUS_V(result);
+ fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
+ fstrcpy(state->response.data.auth.error_string, nt_errstr(result));
+ state->response.data.auth.pam_error = nt_status_to_pam(result);
+
+ DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Checking the trust account password returned %s\n",
+ state->response.data.auth.nt_status_string));
+
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
+
+void winbindd_list_trusted_domains(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *d = NULL;
+ int extra_data_len = 0;
+ char *extra_data = NULL;
+
+ DEBUG(3, ("[%5lu]: list trusted domains\n",
+ (unsigned long)state->pid));
+
+ for ( d=domain_list(); d; d=d->next ) {
+ if ( !extra_data ) {
+ extra_data = talloc_asprintf(state->mem_ctx, "%s\\%s\\%s",
+ d->name,
+ d->alt_name ? d->alt_name : d->name,
+ sid_string_static(&d->sid));
+ } else {
+ extra_data = talloc_asprintf(state->mem_ctx, "%s\n%s\\%s\\%s",
+ extra_data,
+ d->name,
+ d->alt_name ? d->alt_name : d->name,
+ sid_string_static(&d->sid));
+ }
+ }
+
+ extra_data_len = 0;
+ if (extra_data != NULL) {
+ extra_data_len = strlen(extra_data);
+ }
+
+ if (extra_data_len > 0) {
+ state->response.extra_data.data = SMB_STRDUP(extra_data);
+ state->response.length += extra_data_len+1;
+ }
+
+ TALLOC_FREE( extra_data );
+
+ request_ok(state);
+}
+
+enum winbindd_result winbindd_dual_list_trusted_domains(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ uint32 i, num_domains;
+ char **names, **alt_names;
+ DOM_SID *sids;
+ int extra_data_len = 0;
+ char *extra_data;
+ NTSTATUS result;
+ BOOL have_own_domain = False;
+
+ DEBUG(3, ("[%5lu]: list trusted domains\n",
+ (unsigned long)state->pid));
+
+ result = domain->methods->trusted_domains(domain, state->mem_ctx,
+ &num_domains, &names,
+ &alt_names, &sids);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(3, ("winbindd_dual_list_trusted_domains: trusted_domains returned %s\n",
+ nt_errstr(result) ));
+ return WINBINDD_ERROR;
+ }
+
+ extra_data = talloc_strdup(state->mem_ctx, "");
+
+ if (num_domains > 0)
+ extra_data = talloc_asprintf(state->mem_ctx, "%s\\%s\\%s",
+ names[0],
+ alt_names[0] ? alt_names[0] : names[0],
+ sid_string_static(&sids[0]));
+
+ for (i=1; i<num_domains; i++)
+ extra_data = talloc_asprintf(state->mem_ctx, "%s\n%s\\%s\\%s",
+ extra_data,
+ names[i],
+ alt_names[i] ? alt_names[i] : names[i],
+ sid_string_static(&sids[i]));
+ /* add our primary domain */
+
+ for (i=0; i<num_domains; i++) {
+ if (strequal(names[i], domain->name)) {
+ have_own_domain = True;
+ break;
+ }
+ }
+
+ if (state->request.data.list_all_domains && !have_own_domain) {
+ extra_data = talloc_asprintf(state->mem_ctx, "%s\n%s\\%s\\%s",
+ extra_data,
+ domain->name,
+ domain->alt_name ? domain->alt_name : domain->name,
+ sid_string_static(&domain->sid));
+ }
+
+ /* This is a bit excessive, but the extra data sooner or later will be
+ talloc'ed */
+
+ extra_data_len = 0;
+ if (extra_data != NULL) {
+ extra_data_len = strlen(extra_data);
+ }
+
+ if (extra_data_len > 0) {
+ state->response.extra_data.data = SMB_STRDUP(extra_data);
+ state->response.length += extra_data_len+1;
+ }
+
+ return WINBINDD_OK;
+}
+
+void winbindd_getdcname(struct winbindd_cli_state *state)
+{
+ state->request.domain_name
+ [sizeof(state->request.domain_name)-1] = '\0';
+
+ DEBUG(3, ("[%5lu]: Get DC name for %s\n", (unsigned long)state->pid,
+ state->request.domain_name));
+
+ sendto_domain(state, find_our_domain());
+}
+
+enum winbindd_result winbindd_dual_getdcname(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ fstring dcname_slash;
+ char *p;
+ struct rpc_pipe_client *netlogon_pipe;
+ NTSTATUS result;
+ WERROR werr;
+ unsigned int orig_timeout;
+
+ state->request.domain_name
+ [sizeof(state->request.domain_name)-1] = '\0';
+
+ DEBUG(3, ("[%5lu]: Get DC name for %s\n", (unsigned long)state->pid,
+ state->request.domain_name));
+
+ result = cm_connect_netlogon(domain, &netlogon_pipe);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("Can't contact the NETLOGON pipe\n"));
+ return WINBINDD_ERROR;
+ }
+
+ /* This call can take a long time - allow the server to time out.
+ 35 seconds should do it. */
+
+ orig_timeout = cli_set_timeout(netlogon_pipe->cli, 35000);
+
+ werr = rpccli_netlogon_getanydcname(netlogon_pipe, state->mem_ctx, domain->dcname,
+ state->request.domain_name,
+ dcname_slash);
+ /* And restore our original timeout. */
+ cli_set_timeout(netlogon_pipe->cli, orig_timeout);
+
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(5, ("Error requesting DCname for domain %s: %s\n",
+ state->request.domain_name, dos_errstr(werr)));
+ return WINBINDD_ERROR;
+ }
+
+ p = dcname_slash;
+ if (*p == '\\') {
+ p+=1;
+ }
+ if (*p == '\\') {
+ p+=1;
+ }
+
+ fstrcpy(state->response.data.dc_name, p);
+ return WINBINDD_OK;
+}
+
+static struct winbindd_child static_locator_child;
+
+void init_locator_child(void)
+{
+ setup_domain_child(NULL, &static_locator_child, "locator");
+}
+
+struct winbindd_child *locator_child(void)
+{
+ return &static_locator_child;
+}
+
+void winbindd_dsgetdcname(struct winbindd_cli_state *state)
+{
+ state->request.domain_name
+ [sizeof(state->request.domain_name)-1] = '\0';
+
+ DEBUG(3, ("[%5lu]: DsGetDcName for %s\n", (unsigned long)state->pid,
+ state->request.domain_name));
+
+ sendto_child(state, locator_child());
+}
+
+enum winbindd_result winbindd_dual_dsgetdcname(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ NTSTATUS result;
+ struct DS_DOMAIN_CONTROLLER_INFO *info = NULL;
+ const char *dc = NULL;
+
+ state->request.domain_name
+ [sizeof(state->request.domain_name)-1] = '\0';
+
+ DEBUG(3, ("[%5lu]: DsGetDcName for %s\n", (unsigned long)state->pid,
+ state->request.domain_name));
+
+ result = DsGetDcName(state->mem_ctx, NULL, state->request.domain_name,
+ NULL, NULL, state->request.flags, &info);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ return WINBINDD_ERROR;
+ }
+
+ if (info->domain_controller_address) {
+ dc = info->domain_controller_address;
+ if ((dc[0] == '\\') && (dc[1] == '\\')) {
+ dc += 2;
+ }
+ }
+
+ if ((!dc || !is_ipaddress(dc)) && info->domain_controller_name) {
+ dc = info->domain_controller_name;
+ }
+
+ if (!dc || !*dc) {
+ return WINBINDD_ERROR;
+ }
+
+ fstrcpy(state->response.data.dc_name, dc);
+
+ return WINBINDD_OK;
+}
+
+
+struct sequence_state {
+ TALLOC_CTX *mem_ctx;
+ struct winbindd_cli_state *cli_state;
+ struct winbindd_domain *domain;
+ struct winbindd_request *request;
+ struct winbindd_response *response;
+ char *extra_data;
+};
+
+static void sequence_recv(void *private_data, BOOL success);
+
+void winbindd_show_sequence(struct winbindd_cli_state *state)
+{
+ struct sequence_state *seq;
+
+ /* Ensure null termination */
+ state->request.domain_name[sizeof(state->request.domain_name)-1]='\0';
+
+ if (strlen(state->request.domain_name) > 0) {
+ struct winbindd_domain *domain;
+ domain = find_domain_from_name_noinit(
+ state->request.domain_name);
+ if (domain == NULL) {
+ request_error(state);
+ return;
+ }
+ sendto_domain(state, domain);
+ return;
+ }
+
+ /* Ask all domains in sequence, collect the results in sequence_recv */
+
+ seq = TALLOC_P(state->mem_ctx, struct sequence_state);
+ if (seq == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ request_error(state);
+ return;
+ }
+
+ seq->mem_ctx = state->mem_ctx;
+ seq->cli_state = state;
+ seq->domain = domain_list();
+ if (seq->domain == NULL) {
+ DEBUG(0, ("domain list empty\n"));
+ request_error(state);
+ return;
+ }
+ seq->request = TALLOC_ZERO_P(state->mem_ctx,
+ struct winbindd_request);
+ seq->response = TALLOC_ZERO_P(state->mem_ctx,
+ struct winbindd_response);
+ seq->extra_data = talloc_strdup(state->mem_ctx, "");
+
+ if ((seq->request == NULL) || (seq->response == NULL) ||
+ (seq->extra_data == NULL)) {
+ DEBUG(0, ("talloc failed\n"));
+ request_error(state);
+ return;
+ }
+
+ seq->request->length = sizeof(*seq->request);
+ seq->request->cmd = WINBINDD_SHOW_SEQUENCE;
+ fstrcpy(seq->request->domain_name, seq->domain->name);
+
+ async_domain_request(state->mem_ctx, seq->domain,
+ seq->request, seq->response,
+ sequence_recv, seq);
+}
+
+static void sequence_recv(void *private_data, BOOL success)
+{
+ struct sequence_state *state =
+ (struct sequence_state *)private_data;
+ uint32 seq = DOM_SEQUENCE_NONE;
+
+ if ((success) && (state->response->result == WINBINDD_OK))
+ seq = state->response->data.domain_info.sequence_number;
+
+ if (seq == DOM_SEQUENCE_NONE) {
+ state->extra_data = talloc_asprintf(state->mem_ctx,
+ "%s%s : DISCONNECTED\n",
+ state->extra_data,
+ state->domain->name);
+ } else {
+ state->extra_data = talloc_asprintf(state->mem_ctx,
+ "%s%s : %d\n",
+ state->extra_data,
+ state->domain->name, seq);
+ }
+
+ state->domain->sequence_number = seq;
+
+ state->domain = state->domain->next;
+
+ if (state->domain == NULL) {
+ struct winbindd_cli_state *cli_state = state->cli_state;
+ cli_state->response.length =
+ sizeof(cli_state->response) +
+ strlen(state->extra_data) + 1;
+ cli_state->response.extra_data.data =
+ SMB_STRDUP(state->extra_data);
+ request_ok(cli_state);
+ return;
+ }
+
+ /* Ask the next domain */
+ fstrcpy(state->request->domain_name, state->domain->name);
+ async_domain_request(state->mem_ctx, state->domain,
+ state->request, state->response,
+ sequence_recv, state);
+}
+
+/* This is the child-only version of --sequence. It only allows for a single
+ * domain (ie "our" one) to be displayed. */
+
+enum winbindd_result winbindd_dual_show_sequence(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: show sequence\n", (unsigned long)state->pid));
+
+ /* Ensure null termination */
+ state->request.domain_name[sizeof(state->request.domain_name)-1]='\0';
+
+ domain->methods->sequence_number(domain, &domain->sequence_number);
+
+ state->response.data.domain_info.sequence_number =
+ domain->sequence_number;
+
+ return WINBINDD_OK;
+}
+
+struct domain_info_state {
+ struct winbindd_domain *domain;
+ struct winbindd_cli_state *cli_state;
+};
+
+static void domain_info_init_recv(void *private_data, BOOL success);
+
+void winbindd_domain_info(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+
+ DEBUG(3, ("[%5lu]: domain_info [%s]\n", (unsigned long)state->pid,
+ state->request.domain_name));
+
+ domain = find_domain_from_name_noinit(state->request.domain_name);
+
+ if (domain == NULL) {
+ DEBUG(3, ("Did not find domain [%s]\n",
+ state->request.domain_name));
+ request_error(state);
+ return;
+ }
+
+ if (!domain->initialized) {
+ struct domain_info_state *istate;
+
+ istate = TALLOC_P(state->mem_ctx, struct domain_info_state);
+ if (istate == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ request_error(state);
+ return;
+ }
+
+ istate->cli_state = state;
+ istate->domain = domain;
+
+ init_child_connection(domain, domain_info_init_recv, istate);
+
+ return;
+ }
+
+ fstrcpy(state->response.data.domain_info.name,
+ domain->name);
+ fstrcpy(state->response.data.domain_info.alt_name,
+ domain->alt_name);
+ fstrcpy(state->response.data.domain_info.sid,
+ sid_string_static(&domain->sid));
+
+ state->response.data.domain_info.native_mode =
+ domain->native_mode;
+ state->response.data.domain_info.active_directory =
+ domain->active_directory;
+ state->response.data.domain_info.primary =
+ domain->primary;
+ state->response.data.domain_info.sequence_number =
+ domain->sequence_number;
+
+ request_ok(state);
+}
+
+static void domain_info_init_recv(void *private_data, BOOL success)
+{
+ struct domain_info_state *istate =
+ (struct domain_info_state *)private_data;
+ struct winbindd_cli_state *state = istate->cli_state;
+ struct winbindd_domain *domain = istate->domain;
+
+ DEBUG(10, ("Got back from child init: %d\n", success));
+
+ if ((!success) || (!domain->initialized)) {
+ DEBUG(5, ("Could not init child for domain %s\n",
+ domain->name));
+ request_error(state);
+ return;
+ }
+
+ fstrcpy(state->response.data.domain_info.name,
+ domain->name);
+ fstrcpy(state->response.data.domain_info.alt_name,
+ domain->alt_name);
+ fstrcpy(state->response.data.domain_info.sid,
+ sid_string_static(&domain->sid));
+
+ state->response.data.domain_info.native_mode =
+ domain->native_mode;
+ state->response.data.domain_info.active_directory =
+ domain->active_directory;
+ state->response.data.domain_info.primary =
+ domain->primary;
+ state->response.data.domain_info.sequence_number =
+ domain->sequence_number;
+
+ request_ok(state);
+}
+
+void winbindd_ping(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: ping\n", (unsigned long)state->pid));
+ request_ok(state);
+}
+
+/* List various tidbits of information */
+
+void winbindd_info(struct winbindd_cli_state *state)
+{
+
+ DEBUG(3, ("[%5lu]: request misc info\n", (unsigned long)state->pid));
+
+ state->response.data.info.winbind_separator = *lp_winbind_separator();
+ fstrcpy(state->response.data.info.samba_version, SAMBA_VERSION_STRING);
+ request_ok(state);
+}
+
+/* Tell the client the current interface version */
+
+void winbindd_interface_version(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: request interface version\n",
+ (unsigned long)state->pid));
+
+ state->response.data.interface_version = WINBIND_INTERFACE_VERSION;
+ request_ok(state);
+}
+
+/* What domain are we a member of? */
+
+void winbindd_domain_name(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: request domain name\n", (unsigned long)state->pid));
+
+ fstrcpy(state->response.data.domain_name, lp_workgroup());
+ request_ok(state);
+}
+
+/* What's my name again? */
+
+void winbindd_netbios_name(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: request netbios name\n",
+ (unsigned long)state->pid));
+
+ fstrcpy(state->response.data.netbios_name, global_myname());
+ request_ok(state);
+}
+
+/* Where can I find the privilaged pipe? */
+
+void winbindd_priv_pipe_dir(struct winbindd_cli_state *state)
+{
+
+ DEBUG(3, ("[%5lu]: request location of privileged pipe\n",
+ (unsigned long)state->pid));
+
+ state->response.extra_data.data = SMB_STRDUP(get_winbind_priv_pipe_dir());
+ if (!state->response.extra_data.data) {
+ DEBUG(0, ("malloc failed\n"));
+ request_error(state);
+ return;
+ }
+
+ /* must add one to length to copy the 0 for string termination */
+ state->response.length +=
+ strlen((char *)state->response.extra_data.data) + 1;
+
+ request_ok(state);
+}
+
diff --git a/source3/winbindd/winbindd_pam.c b/source3/winbindd/winbindd_pam.c
new file mode 100644
index 0000000000..50b24864b5
--- /dev/null
+++ b/source3/winbindd/winbindd_pam.c
@@ -0,0 +1,2382 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - pam auth funcions
+
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) Tim Potter 2001
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Guenther Deschner 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static NTSTATUS append_info3_as_txt(TALLOC_CTX *mem_ctx,
+ struct winbindd_cli_state *state,
+ NET_USER_INFO_3 *info3)
+{
+ fstring str_sid;
+
+ state->response.data.auth.info3.logon_time =
+ nt_time_to_unix(info3->logon_time);
+ state->response.data.auth.info3.logoff_time =
+ nt_time_to_unix(info3->logoff_time);
+ state->response.data.auth.info3.kickoff_time =
+ nt_time_to_unix(info3->kickoff_time);
+ state->response.data.auth.info3.pass_last_set_time =
+ nt_time_to_unix(info3->pass_last_set_time);
+ state->response.data.auth.info3.pass_can_change_time =
+ nt_time_to_unix(info3->pass_can_change_time);
+ state->response.data.auth.info3.pass_must_change_time =
+ nt_time_to_unix(info3->pass_must_change_time);
+
+ state->response.data.auth.info3.logon_count = info3->logon_count;
+ state->response.data.auth.info3.bad_pw_count = info3->bad_pw_count;
+
+ state->response.data.auth.info3.user_rid = info3->user_rid;
+ state->response.data.auth.info3.group_rid = info3->group_rid;
+ sid_to_string(str_sid, &(info3->dom_sid.sid));
+ fstrcpy(state->response.data.auth.info3.dom_sid, str_sid);
+
+ state->response.data.auth.info3.num_groups = info3->num_groups;
+ state->response.data.auth.info3.user_flgs = info3->user_flgs;
+
+ state->response.data.auth.info3.acct_flags = info3->acct_flags;
+ state->response.data.auth.info3.num_other_sids = info3->num_other_sids;
+
+ unistr2_to_ascii(state->response.data.auth.info3.user_name,
+ &info3->uni_user_name, -1);
+ unistr2_to_ascii(state->response.data.auth.info3.full_name,
+ &info3->uni_full_name, -1);
+ unistr2_to_ascii(state->response.data.auth.info3.logon_script,
+ &info3->uni_logon_script, -1);
+ unistr2_to_ascii(state->response.data.auth.info3.profile_path,
+ &info3->uni_profile_path, -1);
+ unistr2_to_ascii(state->response.data.auth.info3.home_dir,
+ &info3->uni_home_dir, -1);
+ unistr2_to_ascii(state->response.data.auth.info3.dir_drive,
+ &info3->uni_dir_drive, -1);
+
+ unistr2_to_ascii(state->response.data.auth.info3.logon_srv,
+ &info3->uni_logon_srv, -1);
+ unistr2_to_ascii(state->response.data.auth.info3.logon_dom,
+ &info3->uni_logon_dom, -1);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS append_info3_as_ndr(TALLOC_CTX *mem_ctx,
+ struct winbindd_cli_state *state,
+ NET_USER_INFO_3 *info3)
+{
+ prs_struct ps;
+ uint32 size;
+ if (!prs_init(&ps, 256 /* Random, non-zero number */, mem_ctx, MARSHALL)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!net_io_user_info3("", info3, &ps, 1, 3, False)) {
+ prs_mem_free(&ps);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ size = prs_data_size(&ps);
+ SAFE_FREE(state->response.extra_data.data);
+ state->response.extra_data.data = SMB_MALLOC(size);
+ if (!state->response.extra_data.data) {
+ prs_mem_free(&ps);
+ return NT_STATUS_NO_MEMORY;
+ }
+ memset( state->response.extra_data.data, '\0', size );
+ prs_copy_all_data_out((char *)state->response.extra_data.data, &ps);
+ state->response.length += size;
+ prs_mem_free(&ps);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS append_unix_username(TALLOC_CTX *mem_ctx,
+ struct winbindd_cli_state *state,
+ const NET_USER_INFO_3 *info3,
+ const char *name_domain,
+ const char *name_user)
+{
+ /* We've been asked to return the unix username, per
+ 'winbind use default domain' settings and the like */
+
+ fstring username_out;
+ const char *nt_username, *nt_domain;
+
+ if (!(nt_domain = unistr2_tdup(mem_ctx,
+ &info3->uni_logon_dom))) {
+ /* If the server didn't give us one, just use the one
+ * we sent them */
+ nt_domain = name_domain;
+ }
+
+ if (!(nt_username = unistr2_tdup(mem_ctx,
+ &info3->uni_user_name))) {
+ /* If the server didn't give us one, just use the one
+ * we sent them */
+ nt_username = name_user;
+ }
+
+ fill_domain_username(username_out, nt_domain, nt_username,
+ True);
+
+ DEBUG(5,("Setting unix username to [%s]\n", username_out));
+
+ SAFE_FREE(state->response.extra_data.data);
+ state->response.extra_data.data = SMB_STRDUP(username_out);
+ if (!state->response.extra_data.data) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->response.length +=
+ strlen((const char *)state->response.extra_data.data)+1;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS append_afs_token(TALLOC_CTX *mem_ctx,
+ struct winbindd_cli_state *state,
+ const NET_USER_INFO_3 *info3,
+ const char *name_domain,
+ const char *name_user)
+{
+ char *afsname = NULL;
+ char *cell;
+
+ afsname = talloc_strdup(mem_ctx, lp_afs_username_map());
+ if (afsname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ afsname = talloc_string_sub(mem_ctx,
+ lp_afs_username_map(),
+ "%D", name_domain);
+ afsname = talloc_string_sub(mem_ctx, afsname,
+ "%u", name_user);
+ afsname = talloc_string_sub(mem_ctx, afsname,
+ "%U", name_user);
+
+ {
+ DOM_SID user_sid;
+ fstring sidstr;
+
+ sid_copy(&user_sid, &info3->dom_sid.sid);
+ sid_append_rid(&user_sid, info3->user_rid);
+ sid_to_string(sidstr, &user_sid);
+ afsname = talloc_string_sub(mem_ctx, afsname,
+ "%s", sidstr);
+ }
+
+ if (afsname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ strlower_m(afsname);
+
+ DEBUG(10, ("Generating token for user %s\n", afsname));
+
+ cell = strchr(afsname, '@');
+
+ if (cell == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *cell = '\0';
+ cell += 1;
+
+ /* Append an AFS token string */
+ SAFE_FREE(state->response.extra_data.data);
+ state->response.extra_data.data =
+ afs_createtoken_str(afsname, cell);
+
+ if (state->response.extra_data.data != NULL) {
+ state->response.length +=
+ strlen((const char *)state->response.extra_data.data)+1;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS check_info3_in_group(TALLOC_CTX *mem_ctx,
+ NET_USER_INFO_3 *info3,
+ const char *group_sid)
+/**
+ * Check whether a user belongs to a group or list of groups.
+ *
+ * @param mem_ctx talloc memory context.
+ * @param info3 user information, including group membership info.
+ * @param group_sid One or more groups , separated by commas.
+ *
+ * @return NT_STATUS_OK on success,
+ * NT_STATUS_LOGON_FAILURE if the user does not belong,
+ * or other NT_STATUS_IS_ERR(status) for other kinds of failure.
+ */
+{
+ DOM_SID *require_membership_of_sid;
+ size_t num_require_membership_of_sid;
+ fstring req_sid;
+ const char *p;
+ DOM_SID sid;
+ size_t i;
+ struct nt_user_token *token;
+ NTSTATUS status;
+
+ /* Parse the 'required group' SID */
+
+ if (!group_sid || !group_sid[0]) {
+ /* NO sid supplied, all users may access */
+ return NT_STATUS_OK;
+ }
+
+ if (!(token = TALLOC_ZERO_P(mem_ctx, struct nt_user_token))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ num_require_membership_of_sid = 0;
+ require_membership_of_sid = NULL;
+
+ p = group_sid;
+
+ while (next_token(&p, req_sid, ",", sizeof(req_sid))) {
+ if (!string_to_sid(&sid, req_sid)) {
+ DEBUG(0, ("check_info3_in_group: could not parse %s "
+ "as a SID!", req_sid));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!add_sid_to_array(mem_ctx, &sid,
+ &require_membership_of_sid,
+ &num_require_membership_of_sid)) {
+ DEBUG(0, ("add_sid_to_array failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ status = sid_array_from_info3(mem_ctx, info3,
+ &token->user_sids,
+ &token->num_sids,
+ True);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(status = add_aliases(get_global_sam_sid(),
+ token))
+ || !NT_STATUS_IS_OK(status = add_aliases(&global_sid_Builtin,
+ token))) {
+ DEBUG(3, ("could not add aliases: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ debug_nt_user_token(DBGC_CLASS, 10, token);
+
+ for (i=0; i<num_require_membership_of_sid; i++) {
+ DEBUG(10, ("Checking SID %s\n", sid_string_static(
+ &require_membership_of_sid[i])));
+ if (nt_token_check_sid(&require_membership_of_sid[i],
+ token)) {
+ DEBUG(10, ("Access ok\n"));
+ return NT_STATUS_OK;
+ }
+ }
+
+ /* Do not distinguish this error from a wrong username/pw */
+
+ return NT_STATUS_LOGON_FAILURE;
+}
+
+struct winbindd_domain *find_auth_domain(struct winbindd_cli_state *state,
+ const char *domain_name)
+{
+ struct winbindd_domain *domain;
+
+ if (IS_DC) {
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ DEBUG(3, ("Authentication for domain [%s] refused"
+ "as it is not a trusted domain\n",
+ domain_name));
+ }
+ return domain;
+ }
+
+ if (is_myname(domain_name)) {
+ DEBUG(3, ("Authentication for domain %s (local domain "
+ "to this server) not supported at this "
+ "stage\n", domain_name));
+ return NULL;
+ }
+
+ /* we can auth against trusted domains */
+ if (state->request.flags & WBFLAG_PAM_CONTACT_TRUSTDOM) {
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ DEBUG(3, ("Authentication for domain [%s] skipped "
+ "as it is not a trusted domain\n",
+ domain_name));
+ } else {
+ return domain;
+ }
+ }
+
+ return find_our_domain();
+}
+
+static void set_auth_errors(struct winbindd_response *resp, NTSTATUS result)
+{
+ resp->data.auth.nt_status = NT_STATUS_V(result);
+ fstrcpy(resp->data.auth.nt_status_string, nt_errstr(result));
+
+ /* we might have given a more useful error above */
+ if (*resp->data.auth.error_string == '\0')
+ fstrcpy(resp->data.auth.error_string,
+ get_friendly_nt_error_msg(result));
+ resp->data.auth.pam_error = nt_status_to_pam(result);
+}
+
+static NTSTATUS fillup_password_policy(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct winbindd_methods *methods;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ SAM_UNK_INFO_1 password_policy;
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(5,("fillup_password_policy: No inbound trust to "
+ "contact domain %s\n", domain->name));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ methods = domain->methods;
+
+ status = methods->password_policy(domain, state->mem_ctx, &password_policy);
+ if (NT_STATUS_IS_ERR(status)) {
+ return status;
+ }
+
+ state->response.data.auth.policy.min_length_password =
+ password_policy.min_length_password;
+ state->response.data.auth.policy.password_history =
+ password_policy.password_history;
+ state->response.data.auth.policy.password_properties =
+ password_policy.password_properties;
+ state->response.data.auth.policy.expire =
+ nt_time_to_unix_abs(&(password_policy.expire));
+ state->response.data.auth.policy.min_passwordage =
+ nt_time_to_unix_abs(&(password_policy.min_passwordage));
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS get_max_bad_attempts_from_lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint16 *max_allowed_bad_attempts)
+{
+ struct winbindd_methods *methods;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ SAM_UNK_INFO_12 lockout_policy;
+
+ *max_allowed_bad_attempts = 0;
+
+ methods = domain->methods;
+
+ status = methods->lockout_policy(domain, mem_ctx, &lockout_policy);
+ if (NT_STATUS_IS_ERR(status)) {
+ return status;
+ }
+
+ *max_allowed_bad_attempts = lockout_policy.bad_attempt_lockout;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS get_pwd_properties(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *password_properties)
+{
+ struct winbindd_methods *methods;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ SAM_UNK_INFO_1 password_policy;
+
+ *password_properties = 0;
+
+ methods = domain->methods;
+
+ status = methods->password_policy(domain, mem_ctx, &password_policy);
+ if (NT_STATUS_IS_ERR(status)) {
+ return status;
+ }
+
+ *password_properties = password_policy.password_properties;
+
+ return NT_STATUS_OK;
+}
+
+#ifdef HAVE_KRB5
+
+static const char *generate_krb5_ccache(TALLOC_CTX *mem_ctx,
+ const char *type,
+ uid_t uid,
+ BOOL *internal_ccache)
+{
+ /* accept FILE and WRFILE as krb5_cc_type from the client and then
+ * build the full ccname string based on the user's uid here -
+ * Guenther*/
+
+ const char *gen_cc = NULL;
+
+ *internal_ccache = True;
+
+ if (uid == -1) {
+ goto memory_ccache;
+ }
+
+ if (!type || type[0] == '\0') {
+ goto memory_ccache;
+ }
+
+ if (strequal(type, "FILE")) {
+ gen_cc = talloc_asprintf(mem_ctx, "FILE:/tmp/krb5cc_%d", uid);
+ } else if (strequal(type, "WRFILE")) {
+ gen_cc = talloc_asprintf(mem_ctx, "WRFILE:/tmp/krb5cc_%d", uid);
+ } else {
+ DEBUG(10,("we don't allow to set a %s type ccache\n", type));
+ goto memory_ccache;
+ }
+
+ *internal_ccache = False;
+ goto done;
+
+ memory_ccache:
+ gen_cc = talloc_strdup(mem_ctx, "MEMORY:winbindd_pam_ccache");
+
+ done:
+ if (gen_cc == NULL) {
+ DEBUG(0,("out of memory\n"));
+ return NULL;
+ }
+
+ DEBUG(10,("using ccache: %s %s\n", gen_cc, *internal_ccache ? "(internal)":""));
+
+ return gen_cc;
+}
+
+static void setup_return_cc_name(struct winbindd_cli_state *state, const char *cc)
+{
+ const char *type = state->request.data.auth.krb5_cc_type;
+
+ state->response.data.auth.krb5ccname[0] = '\0';
+
+ if (type[0] == '\0') {
+ return;
+ }
+
+ if (!strequal(type, "FILE") &&
+ !strequal(type, "WRFILE")) {
+ DEBUG(10,("won't return krbccname for a %s type ccache\n",
+ type));
+ return;
+ }
+
+ fstrcpy(state->response.data.auth.krb5ccname, cc);
+}
+
+#endif
+
+static uid_t get_uid_from_state(struct winbindd_cli_state *state)
+{
+ uid_t uid = -1;
+
+ uid = state->request.data.auth.uid;
+
+ if (uid < 0) {
+ DEBUG(1,("invalid uid: '%d'\n", uid));
+ return -1;
+ }
+ return uid;
+}
+
+/**********************************************************************
+ Authenticate a user with a clear text password using Kerberos and fill up
+ ccache if required
+ **********************************************************************/
+
+static NTSTATUS winbindd_raw_kerberos_login(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state,
+ NET_USER_INFO_3 **info3)
+{
+#ifdef HAVE_KRB5
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ krb5_error_code krb5_ret;
+ const char *cc = NULL;
+ const char *principal_s = NULL;
+ const char *service = NULL;
+ char *realm = NULL;
+ fstring name_domain, name_user;
+ time_t ticket_lifetime = 0;
+ time_t renewal_until = 0;
+ uid_t uid = -1;
+ ADS_STRUCT *ads;
+ time_t time_offset = 0;
+ BOOL internal_ccache = True;
+
+ ZERO_STRUCTP(info3);
+
+ *info3 = NULL;
+
+ /* 1st step:
+ * prepare a krb5_cc_cache string for the user */
+
+ uid = get_uid_from_state(state);
+ if (uid == -1) {
+ DEBUG(0,("no valid uid\n"));
+ }
+
+ cc = generate_krb5_ccache(state->mem_ctx,
+ state->request.data.auth.krb5_cc_type,
+ state->request.data.auth.uid,
+ &internal_ccache);
+ if (cc == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+
+ /* 2nd step:
+ * get kerberos properties */
+
+ if (domain->private_data) {
+ ads = (ADS_STRUCT *)domain->private_data;
+ time_offset = ads->auth.time_offset;
+ }
+
+
+ /* 3rd step:
+ * do kerberos auth and setup ccache as the user */
+
+ parse_domain_user(state->request.data.auth.user, name_domain, name_user);
+
+ realm = domain->alt_name;
+ strupper_m(realm);
+
+ principal_s = talloc_asprintf(state->mem_ctx, "%s@%s", name_user, realm);
+ if (principal_s == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ service = talloc_asprintf(state->mem_ctx, "%s/%s@%s", KRB5_TGS_NAME, realm, realm);
+ if (service == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* if this is a user ccache, we need to act as the user to let the krb5
+ * library handle the chown, etc. */
+
+ /************************ ENTERING NON-ROOT **********************/
+
+ if (!internal_ccache) {
+ set_effective_uid(uid);
+ DEBUG(10,("winbindd_raw_kerberos_login: uid is %d\n", uid));
+ }
+
+ result = kerberos_return_info3_from_pac(state->mem_ctx,
+ principal_s,
+ state->request.data.auth.pass,
+ time_offset,
+ &ticket_lifetime,
+ &renewal_until,
+ cc,
+ True,
+ True,
+ WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+ info3);
+ if (!internal_ccache) {
+ gain_root_privilege();
+ }
+
+ /************************ RETURNED TO ROOT **********************/
+
+ if (!NT_STATUS_IS_OK(result)) {
+ goto failed;
+ }
+
+ DEBUG(10,("winbindd_raw_kerberos_login: winbindd validated ticket of %s\n",
+ principal_s));
+
+ /* if we had a user's ccache then return that string for the pam
+ * environment */
+
+ if (!internal_ccache) {
+
+ setup_return_cc_name(state, cc);
+
+ result = add_ccache_to_list(principal_s,
+ cc,
+ service,
+ state->request.data.auth.user,
+ realm,
+ uid,
+ time(NULL),
+ ticket_lifetime,
+ renewal_until,
+ False);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_raw_kerberos_login: failed to add ccache to list: %s\n",
+ nt_errstr(result)));
+ }
+ } else {
+
+ /* need to delete the memory cred cache, it is not used anymore */
+
+ krb5_ret = ads_kdestroy(cc);
+ if (krb5_ret) {
+ DEBUG(3,("winbindd_raw_kerberos_login: "
+ "could not destroy krb5 credential cache: "
+ "%s\n", error_message(krb5_ret)));
+ }
+
+ }
+
+ return NT_STATUS_OK;
+
+failed:
+
+ /* we could have created a new credential cache with a valid tgt in it
+ * but we werent able to get or verify the service ticket for this
+ * local host and therefor didn't get the PAC, we need to remove that
+ * cache entirely now */
+
+ krb5_ret = ads_kdestroy(cc);
+ if (krb5_ret) {
+ DEBUG(3,("winbindd_raw_kerberos_login: "
+ "could not destroy krb5 credential cache: "
+ "%s\n", error_message(krb5_ret)));
+ }
+
+ if (!NT_STATUS_IS_OK(remove_ccache(state->request.data.auth.user))) {
+ DEBUG(3,("winbindd_raw_kerberos_login: "
+ "could not remove ccache for user %s\n",
+ state->request.data.auth.user));
+ }
+
+ return result;
+#else
+ return NT_STATUS_NOT_SUPPORTED;
+#endif /* HAVE_KRB5 */
+}
+
+/****************************************************************
+****************************************************************/
+
+static BOOL check_request_flags(uint32_t flags)
+{
+ uint32_t flags_edata = WBFLAG_PAM_AFS_TOKEN |
+ WBFLAG_PAM_UNIX_NAME |
+ WBFLAG_PAM_INFO3_NDR;
+
+ if ( ( (flags & flags_edata) == WBFLAG_PAM_AFS_TOKEN) ||
+ ( (flags & flags_edata) == WBFLAG_PAM_INFO3_NDR) ||
+ ( (flags & flags_edata) == WBFLAG_PAM_UNIX_NAME) ||
+ !(flags & flags_edata) ) {
+ return True;
+ }
+
+ DEBUG(1,("check_request_flags: invalid request flags\n"));
+
+ return False;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS append_data(struct winbindd_cli_state *state,
+ NET_USER_INFO_3 *info3,
+ const char *name_domain,
+ const char *name_user)
+{
+ NTSTATUS result;
+ uint32_t flags = state->request.flags;
+
+ if (flags & WBFLAG_PAM_USER_SESSION_KEY) {
+ memcpy(state->response.data.auth.user_session_key,
+ info3->user_sess_key,
+ sizeof(state->response.data.auth.user_session_key)
+ /* 16 */);
+ }
+
+ if (flags & WBFLAG_PAM_LMKEY) {
+ memcpy(state->response.data.auth.first_8_lm_hash,
+ info3->lm_sess_key,
+ sizeof(state->response.data.auth.first_8_lm_hash)
+ /* 8 */);
+ }
+
+ if (flags & WBFLAG_PAM_INFO3_TEXT) {
+ result = append_info3_as_txt(state->mem_ctx, state, info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to append INFO3 (TXT): %s\n",
+ nt_errstr(result)));
+ return result;
+ }
+ }
+
+ /* currently, anything from here on potentially overwrites extra_data. */
+
+ if (flags & WBFLAG_PAM_INFO3_NDR) {
+ result = append_info3_as_ndr(state->mem_ctx, state, info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to append INFO3 (NDR): %s\n",
+ nt_errstr(result)));
+ return result;
+ }
+ }
+
+ if (flags & WBFLAG_PAM_UNIX_NAME) {
+ result = append_unix_username(state->mem_ctx, state, info3,
+ name_domain, name_user);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to append Unix Username: %s\n",
+ nt_errstr(result)));
+ return result;
+ }
+ }
+
+ if (flags & WBFLAG_PAM_AFS_TOKEN) {
+ result = append_afs_token(state->mem_ctx, state, info3,
+ name_domain, name_user);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to append AFS token: %s\n",
+ nt_errstr(result)));
+ return result;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+void winbindd_pam_auth(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+ fstring name_domain, name_user;
+ NTSTATUS result;
+
+ /* Ensure null termination */
+ state->request.data.auth.user
+ [sizeof(state->request.data.auth.user)-1]='\0';
+
+ /* Ensure null termination */
+ state->request.data.auth.pass
+ [sizeof(state->request.data.auth.pass)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: pam auth %s\n", (unsigned long)state->pid,
+ state->request.data.auth.user));
+
+ if (!check_request_flags(state->request.flags)) {
+ result = NT_STATUS_INVALID_PARAMETER_MIX;
+ goto done;
+ }
+
+ /* Parse domain and username */
+
+ ws_name_return( state->request.data.auth.user, WB_REPLACE_CHAR );
+
+ if (!canonicalize_username(state->request.data.auth.user,
+ name_domain, name_user)) {
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ domain = find_auth_domain(state, name_domain);
+
+ if (domain == NULL) {
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ sendto_domain(state, domain);
+ return;
+ done:
+ set_auth_errors(&state->response, result);
+ DEBUG(5, ("Plain text authentication for %s returned %s "
+ "(PAM: %d)\n",
+ state->request.data.auth.user,
+ state->response.data.auth.nt_status_string,
+ state->response.data.auth.pam_error));
+ request_error(state);
+}
+
+NTSTATUS winbindd_dual_pam_auth_cached(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state,
+ NET_USER_INFO_3 **info3)
+{
+ NTSTATUS result = NT_STATUS_LOGON_FAILURE;
+ uint16 max_allowed_bad_attempts;
+ fstring name_domain, name_user;
+ DOM_SID sid;
+ enum lsa_SidType type;
+ uchar new_nt_pass[NT_HASH_LEN];
+ const uint8 *cached_nt_pass;
+ const uint8 *cached_salt;
+ NET_USER_INFO_3 *my_info3;
+ time_t kickoff_time, must_change_time;
+ BOOL password_good = False;
+#ifdef HAVE_KRB5
+ struct winbindd_tdc_domain *tdc_domain = NULL;
+#endif
+
+ *info3 = NULL;
+
+ ZERO_STRUCTP(info3);
+
+ DEBUG(10,("winbindd_dual_pam_auth_cached\n"));
+
+ /* Parse domain and username */
+
+ parse_domain_user(state->request.data.auth.user, name_domain, name_user);
+
+
+ if (!lookup_cached_name(state->mem_ctx,
+ name_domain,
+ name_user,
+ &sid,
+ &type)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: no such user in the cache\n"));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (type != SID_NAME_USER) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: not a user (%s)\n", sid_type_lookup(type)));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ result = winbindd_get_creds(domain,
+ state->mem_ctx,
+ &sid,
+ &my_info3,
+ &cached_nt_pass,
+ &cached_salt);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get creds: %s\n", nt_errstr(result)));
+ return result;
+ }
+
+ *info3 = my_info3;
+
+ E_md4hash(state->request.data.auth.pass, new_nt_pass);
+
+ dump_data_pw("new_nt_pass", new_nt_pass, NT_HASH_LEN);
+ 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);
+ }
+
+ if (cached_salt) {
+ /* In this case we didn't store the nt_hash itself,
+ but the MD5 combination of salt + nt_hash. */
+ uchar salted_hash[NT_HASH_LEN];
+ E_md5hash(cached_salt, new_nt_pass, salted_hash);
+
+ password_good = (memcmp(cached_nt_pass, salted_hash, NT_HASH_LEN) == 0) ?
+ True : False;
+ } else {
+ /* Old cached cred - direct store of nt_hash (bad bad bad !). */
+ password_good = (memcmp(cached_nt_pass, new_nt_pass, NT_HASH_LEN) == 0) ?
+ True : False;
+ }
+
+ if (password_good) {
+
+ /* User *DOES* know the password, update logon_time and reset
+ * bad_pw_count */
+
+ my_info3->user_flgs |= LOGON_CACHED_ACCOUNT;
+
+ if (my_info3->acct_flags & ACB_AUTOLOCK) {
+ return NT_STATUS_ACCOUNT_LOCKED_OUT;
+ }
+
+ if (my_info3->acct_flags & ACB_DISABLED) {
+ return NT_STATUS_ACCOUNT_DISABLED;
+ }
+
+ if (my_info3->acct_flags & ACB_WSTRUST) {
+ return NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT;
+ }
+
+ if (my_info3->acct_flags & ACB_SVRTRUST) {
+ return NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT;
+ }
+
+ if (my_info3->acct_flags & ACB_DOMTRUST) {
+ return NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT;
+ }
+
+ if (!(my_info3->acct_flags & ACB_NORMAL)) {
+ DEBUG(0,("winbindd_dual_pam_auth_cached: whats wrong with that one?: 0x%08x\n",
+ my_info3->acct_flags));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ kickoff_time = nt_time_to_unix(my_info3->kickoff_time);
+ if (kickoff_time != 0 && time(NULL) > kickoff_time) {
+ return NT_STATUS_ACCOUNT_EXPIRED;
+ }
+
+ must_change_time = nt_time_to_unix(my_info3->pass_must_change_time);
+ if (must_change_time != 0 && must_change_time < time(NULL)) {
+ /* we allow grace logons when the password has expired */
+ my_info3->user_flgs |= LOGON_GRACE_LOGON;
+ /* return NT_STATUS_PASSWORD_EXPIRED; */
+ goto success;
+ }
+
+#ifdef HAVE_KRB5
+ if ((state->request.flags & WBFLAG_PAM_KRB5) &&
+ ((tdc_domain = wcache_tdc_fetch_domain(state->mem_ctx, name_domain)) != NULL) &&
+ (tdc_domain->trust_type & DS_DOMAIN_TRUST_TYPE_UPLEVEL)) {
+
+ uid_t uid = -1;
+ const char *cc = NULL;
+ char *realm = NULL;
+ const char *principal_s = NULL;
+ const char *service = NULL;
+ BOOL internal_ccache = False;
+
+ uid = get_uid_from_state(state);
+ if (uid == -1) {
+ DEBUG(0,("winbindd_dual_pam_auth_cached: invalid uid\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ cc = generate_krb5_ccache(state->mem_ctx,
+ state->request.data.auth.krb5_cc_type,
+ state->request.data.auth.uid,
+ &internal_ccache);
+ if (cc == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ realm = domain->alt_name;
+ strupper_m(realm);
+
+ principal_s = talloc_asprintf(state->mem_ctx, "%s@%s", name_user, realm);
+ if (principal_s == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ service = talloc_asprintf(state->mem_ctx, "%s/%s@%s", KRB5_TGS_NAME, realm, realm);
+ if (service == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!internal_ccache) {
+
+ setup_return_cc_name(state, cc);
+
+ result = add_ccache_to_list(principal_s,
+ cc,
+ service,
+ state->request.data.auth.user,
+ domain->alt_name,
+ uid,
+ time(NULL),
+ time(NULL) + lp_winbind_cache_time(),
+ time(NULL) + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+ True);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: failed "
+ "to add ccache to list: %s\n",
+ nt_errstr(result)));
+ }
+ }
+ }
+#endif /* HAVE_KRB5 */
+ success:
+ /* FIXME: we possibly should handle logon hours as well (does xp when
+ * offline?) see auth/auth_sam.c:sam_account_ok for details */
+
+ unix_to_nt_time(&my_info3->logon_time, time(NULL));
+ my_info3->bad_pw_count = 0;
+
+ result = winbindd_update_creds_by_info3(domain,
+ state->mem_ctx,
+ state->request.data.auth.user,
+ state->request.data.auth.pass,
+ my_info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1,("winbindd_dual_pam_auth_cached: failed to update creds: %s\n",
+ nt_errstr(result)));
+ return result;
+ }
+
+ return NT_STATUS_OK;
+
+ }
+
+ /* User does *NOT* know the correct password, modify info3 accordingly */
+
+ /* failure of this is not critical */
+ result = get_max_bad_attempts_from_lockout_policy(domain, state->mem_ctx, &max_allowed_bad_attempts);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get max_allowed_bad_attempts. "
+ "Won't be able to honour account lockout policies\n"));
+ }
+
+ /* increase counter */
+ my_info3->bad_pw_count++;
+
+ if (max_allowed_bad_attempts == 0) {
+ goto failed;
+ }
+
+ /* lockout user */
+ if (my_info3->bad_pw_count >= max_allowed_bad_attempts) {
+
+ uint32 password_properties;
+
+ result = get_pwd_properties(domain, state->mem_ctx, &password_properties);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get password properties.\n"));
+ }
+
+ if ((my_info3->user_rid != DOMAIN_USER_RID_ADMIN) ||
+ (password_properties & DOMAIN_LOCKOUT_ADMINS)) {
+ my_info3->acct_flags |= ACB_AUTOLOCK;
+ }
+ }
+
+failed:
+ result = winbindd_update_creds_by_info3(domain,
+ state->mem_ctx,
+ state->request.data.auth.user,
+ NULL,
+ my_info3);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("winbindd_dual_pam_auth_cached: failed to update creds %s\n",
+ nt_errstr(result)));
+ }
+
+ return NT_STATUS_LOGON_FAILURE;
+}
+
+NTSTATUS winbindd_dual_pam_auth_kerberos(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state,
+ NET_USER_INFO_3 **info3)
+{
+ struct winbindd_domain *contact_domain;
+ fstring name_domain, name_user;
+ NTSTATUS result;
+
+ DEBUG(10,("winbindd_dual_pam_auth_kerberos\n"));
+
+ /* Parse domain and username */
+
+ parse_domain_user(state->request.data.auth.user, name_domain, name_user);
+
+ /* what domain should we contact? */
+
+ if ( IS_DC ) {
+ if (!(contact_domain = find_domain_from_name(name_domain))) {
+ DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n",
+ state->request.data.auth.user, name_domain, name_user, name_domain));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ } else {
+ if (is_myname(name_domain)) {
+ DEBUG(3, ("Authentication for domain %s (local domain to this server) not supported at this stage\n", name_domain));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ contact_domain = find_domain_from_name(name_domain);
+ if (contact_domain == NULL) {
+ DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n",
+ state->request.data.auth.user, name_domain, name_user, name_domain));
+
+ contact_domain = find_our_domain();
+ }
+ }
+
+ if (contact_domain->initialized &&
+ contact_domain->active_directory) {
+ goto try_login;
+ }
+
+ if (!contact_domain->initialized) {
+ init_dc_connection(contact_domain);
+ }
+
+ if (!contact_domain->active_directory) {
+ DEBUG(3,("krb5 auth requested but domain is not Active Directory\n"));
+ return NT_STATUS_INVALID_LOGON_TYPE;
+ }
+try_login:
+ result = winbindd_raw_kerberos_login(contact_domain, state, info3);
+done:
+ return result;
+}
+
+NTSTATUS winbindd_dual_pam_auth_samlogon(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state,
+ NET_USER_INFO_3 **info3)
+{
+
+ struct rpc_pipe_client *netlogon_pipe;
+ uchar chal[8];
+ DATA_BLOB lm_resp;
+ DATA_BLOB nt_resp;
+ int attempts = 0;
+ unsigned char local_lm_response[24];
+ unsigned char local_nt_response[24];
+ struct winbindd_domain *contact_domain;
+ fstring name_domain, name_user;
+ BOOL retry;
+ NTSTATUS result;
+ NET_USER_INFO_3 *my_info3;
+
+ ZERO_STRUCTP(info3);
+
+ *info3 = NULL;
+
+ my_info3 = TALLOC_ZERO_P(state->mem_ctx, NET_USER_INFO_3);
+ if (my_info3 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+
+ DEBUG(10,("winbindd_dual_pam_auth_samlogon\n"));
+
+ /* Parse domain and username */
+
+ parse_domain_user(state->request.data.auth.user, name_domain, name_user);
+
+ /* do password magic */
+
+
+ generate_random_buffer(chal, 8);
+ if (lp_client_ntlmv2_auth()) {
+ DATA_BLOB server_chal;
+ DATA_BLOB names_blob;
+ DATA_BLOB nt_response;
+ DATA_BLOB lm_response;
+ server_chal = data_blob_talloc(state->mem_ctx, chal, 8);
+
+ /* note that the 'workgroup' here is a best guess - we don't know
+ the server's domain at this point. The 'server name' is also
+ dodgy...
+ */
+ names_blob = NTLMv2_generate_names_blob(global_myname(), lp_workgroup());
+
+ if (!SMBNTLMv2encrypt(name_user, name_domain,
+ state->request.data.auth.pass,
+ &server_chal,
+ &names_blob,
+ &lm_response, &nt_response, NULL)) {
+ data_blob_free(&names_blob);
+ data_blob_free(&server_chal);
+ DEBUG(0, ("winbindd_pam_auth: SMBNTLMv2encrypt() failed!\n"));
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ data_blob_free(&names_blob);
+ data_blob_free(&server_chal);
+ lm_resp = data_blob_talloc(state->mem_ctx, lm_response.data,
+ lm_response.length);
+ nt_resp = data_blob_talloc(state->mem_ctx, nt_response.data,
+ nt_response.length);
+ data_blob_free(&lm_response);
+ data_blob_free(&nt_response);
+
+ } else {
+ if (lp_client_lanman_auth()
+ && SMBencrypt(state->request.data.auth.pass,
+ chal,
+ local_lm_response)) {
+ lm_resp = data_blob_talloc(state->mem_ctx,
+ local_lm_response,
+ sizeof(local_lm_response));
+ } else {
+ lm_resp = data_blob_null;
+ }
+ SMBNTencrypt(state->request.data.auth.pass,
+ chal,
+ local_nt_response);
+
+ nt_resp = data_blob_talloc(state->mem_ctx,
+ local_nt_response,
+ sizeof(local_nt_response));
+ }
+
+ /* what domain should we contact? */
+
+ if ( IS_DC ) {
+ if (!(contact_domain = find_domain_from_name(name_domain))) {
+ DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n",
+ state->request.data.auth.user, name_domain, name_user, name_domain));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ } else {
+ if (is_myname(name_domain)) {
+ DEBUG(3, ("Authentication for domain %s (local domain to this server) not supported at this stage\n", name_domain));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ contact_domain = find_our_domain();
+ }
+
+ /* check authentication loop */
+
+ do {
+ NTSTATUS (*logon_fn)(struct rpc_pipe_client
+ *cli, TALLOC_CTX *mem_ctx,
+ uint32 logon_parameters,
+ const char *server,
+ const char *username,
+ const char *domain,
+ const char *workstation,
+ const uint8 chal[8],
+ DATA_BLOB lm_response,
+ DATA_BLOB nt_response,
+ NET_USER_INFO_3 *info3);
+
+ ZERO_STRUCTP(my_info3);
+ retry = False;
+
+ result = cm_connect_netlogon(contact_domain, &netlogon_pipe);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
+ goto done;
+ }
+
+ logon_fn = contact_domain->can_do_samlogon_ex
+ ? rpccli_netlogon_sam_network_logon_ex
+ : rpccli_netlogon_sam_network_logon;
+
+ result = logon_fn(netlogon_pipe,
+ state->mem_ctx,
+ 0,
+ contact_domain->dcname, /* server name */
+ name_user, /* user name */
+ name_domain, /* target domain */
+ global_myname(), /* workstation */
+ chal,
+ lm_resp,
+ nt_resp,
+ my_info3);
+
+ if ((NT_STATUS_V(result) == DCERPC_FAULT_OP_RNG_ERROR)
+ && contact_domain->can_do_samlogon_ex) {
+ DEBUG(3, ("Got a DC that can not do NetSamLogonEx, "
+ "retrying with NetSamLogon\n"));
+ contact_domain->can_do_samlogon_ex = False;
+ retry = True;
+ continue;
+ }
+
+ attempts += 1;
+
+ /* We have to try a second time as cm_connect_netlogon
+ might not yet have noticed that the DC has killed
+ our connection. */
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)) {
+ retry = True;
+ continue;
+ }
+
+ /* if we get access denied, a possible cause was that we had
+ and open connection to the DC, but someone changed our
+ machine account password out from underneath us using 'net
+ rpc changetrustpw' */
+
+ if ( NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED) ) {
+ DEBUG(3,("winbindd_pam_auth: sam_logon returned "
+ "ACCESS_DENIED. Maybe the trust account "
+ "password was changed and we didn't know it. "
+ "Killing connections to domain %s\n",
+ name_domain));
+ invalidate_cm_connection(&contact_domain->conn);
+ retry = True;
+ }
+
+ } while ( (attempts < 2) && retry );
+
+ /* handle the case where a NT4 DC does not fill in the acct_flags in
+ * the samlogon reply info3. When accurate info3 is required by the
+ * caller, we look up the account flags ourselve - gd */
+
+ if ((state->request.flags & WBFLAG_PAM_INFO3_TEXT) &&
+ (my_info3->acct_flags == 0) && NT_STATUS_IS_OK(result)) {
+
+ struct rpc_pipe_client *samr_pipe;
+ POLICY_HND samr_domain_handle, user_pol;
+ SAM_USERINFO_CTR *user_ctr;
+ NTSTATUS status_tmp;
+ uint32 acct_flags;
+
+ ZERO_STRUCT(user_ctr);
+
+ status_tmp = cm_connect_sam(contact_domain, state->mem_ctx,
+ &samr_pipe, &samr_domain_handle);
+
+ if (!NT_STATUS_IS_OK(status_tmp)) {
+ DEBUG(3, ("could not open handle to SAMR pipe: %s\n",
+ nt_errstr(status_tmp)));
+ goto done;
+ }
+
+ status_tmp = rpccli_samr_open_user(samr_pipe, state->mem_ctx,
+ &samr_domain_handle,
+ MAXIMUM_ALLOWED_ACCESS,
+ my_info3->user_rid, &user_pol);
+
+ if (!NT_STATUS_IS_OK(status_tmp)) {
+ DEBUG(3, ("could not open user handle on SAMR pipe: %s\n",
+ nt_errstr(status_tmp)));
+ goto done;
+ }
+
+ status_tmp = rpccli_samr_query_userinfo(samr_pipe, state->mem_ctx,
+ &user_pol, 16, &user_ctr);
+
+ if (!NT_STATUS_IS_OK(status_tmp)) {
+ DEBUG(3, ("could not query user info on SAMR pipe: %s\n",
+ nt_errstr(status_tmp)));
+ rpccli_samr_close(samr_pipe, state->mem_ctx, &user_pol);
+ goto done;
+ }
+
+ acct_flags = user_ctr->info.id16->acb_info;
+
+ if (acct_flags == 0) {
+ rpccli_samr_close(samr_pipe, state->mem_ctx, &user_pol);
+ goto done;
+ }
+
+ my_info3->acct_flags = acct_flags;
+
+ DEBUG(10,("successfully retrieved acct_flags 0x%x\n", acct_flags));
+
+ rpccli_samr_close(samr_pipe, state->mem_ctx, &user_pol);
+ }
+
+ *info3 = my_info3;
+done:
+ return result;
+}
+
+enum winbindd_result winbindd_dual_pam_auth(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ NTSTATUS result = NT_STATUS_LOGON_FAILURE;
+ NTSTATUS krb5_result = NT_STATUS_OK;
+ fstring name_domain, name_user;
+ NET_USER_INFO_3 *info3 = NULL;
+
+ /* Ensure null termination */
+ state->request.data.auth.user[sizeof(state->request.data.auth.user)-1]='\0';
+
+ /* Ensure null termination */
+ state->request.data.auth.pass[sizeof(state->request.data.auth.pass)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: dual pam auth %s\n", (unsigned long)state->pid,
+ state->request.data.auth.user));
+
+ if (!check_request_flags(state->request.flags)) {
+ result = NT_STATUS_INVALID_PARAMETER_MIX;
+ goto done;
+ }
+
+ /* Parse domain and username */
+
+ ws_name_return( state->request.data.auth.user, WB_REPLACE_CHAR );
+
+ parse_domain_user(state->request.data.auth.user, name_domain, name_user);
+
+ if (domain->online == False) {
+ result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ if (domain->startup) {
+ /* Logons are very important to users. If we're offline and
+ we get a request within the first 30 seconds of startup,
+ try very hard to find a DC and go online. */
+
+ DEBUG(10,("winbindd_dual_pam_auth: domain: %s offline and auth "
+ "request in startup mode.\n", domain->name ));
+
+ winbindd_flush_negative_conn_cache(domain);
+ result = init_dc_connection(domain);
+ }
+ }
+
+ DEBUG(10,("winbindd_dual_pam_auth: domain: %s last was %s\n", domain->name, domain->online ? "online":"offline"));
+
+ /* Check for Kerberos authentication */
+ if (domain->online && (state->request.flags & WBFLAG_PAM_KRB5)) {
+
+ result = winbindd_dual_pam_auth_kerberos(domain, state, &info3);
+ /* save for later */
+ krb5_result = result;
+
+
+ if (NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_kerberos succeeded\n"));
+ goto process_result;
+ } else {
+ DEBUG(10,("winbindd_dual_pam_auth_kerberos failed: %s\n", nt_errstr(result)));
+ }
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ DEBUG(10,("winbindd_dual_pam_auth_kerberos setting domain to offline\n"));
+ set_domain_offline( domain );
+ goto cached_logon;
+ }
+
+ /* there are quite some NT_STATUS errors where there is no
+ * point in retrying with a samlogon, we explictly have to take
+ * care not to increase the bad logon counter on the DC */
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_ACCOUNT_DISABLED) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_ACCOUNT_EXPIRED) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_ACCOUNT_LOCKED_OUT) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_INVALID_LOGON_HOURS) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_INVALID_WORKSTATION) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_LOGON_FAILURE) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_EXPIRED) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_MUST_CHANGE) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_WRONG_PASSWORD)) {
+ goto process_result;
+ }
+
+ if (state->request.flags & WBFLAG_PAM_FALLBACK_AFTER_KRB5) {
+ DEBUG(3,("falling back to samlogon\n"));
+ goto sam_logon;
+ } else {
+ goto cached_logon;
+ }
+ }
+
+sam_logon:
+ /* Check for Samlogon authentication */
+ if (domain->online) {
+ result = winbindd_dual_pam_auth_samlogon(domain, state, &info3);
+
+ if (NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_samlogon succeeded\n"));
+ /* add the Krb5 err if we have one */
+ if ( NT_STATUS_EQUAL(krb5_result, NT_STATUS_TIME_DIFFERENCE_AT_DC ) ) {
+ info3->user_flgs |= LOGON_KRB5_FAIL_CLOCK_SKEW;
+ }
+ goto process_result;
+ }
+
+ DEBUG(10,("winbindd_dual_pam_auth_samlogon failed: %s\n",
+ nt_errstr(result)));
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND))
+ {
+ DEBUG(10,("winbindd_dual_pam_auth_samlogon setting domain to offline\n"));
+ set_domain_offline( domain );
+ goto cached_logon;
+ }
+
+ if (domain->online) {
+ /* We're still online - fail. */
+ goto done;
+ }
+ }
+
+cached_logon:
+ /* Check for Cached logons */
+ if (!domain->online && (state->request.flags & WBFLAG_PAM_CACHED_LOGIN) &&
+ lp_winbind_offline_logon()) {
+
+ result = winbindd_dual_pam_auth_cached(domain, state, &info3);
+
+ if (NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached succeeded\n"));
+ goto process_result;
+ } else {
+ DEBUG(10,("winbindd_dual_pam_auth_cached failed: %s\n", nt_errstr(result)));
+ goto done;
+ }
+ }
+
+process_result:
+
+ if (NT_STATUS_IS_OK(result)) {
+
+ DOM_SID user_sid;
+
+ /* In all codepaths where result == NT_STATUS_OK info3 must have
+ been initialized. */
+ if (!info3) {
+ result = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ netsamlogon_cache_store(name_user, info3);
+ wcache_invalidate_samlogon(find_domain_from_name(name_domain), info3);
+
+ /* save name_to_sid info as early as possible (only if
+ this is our primary domain so we don't invalidate
+ the cache entry by storing the seq_num for the wrong
+ domain). */
+ if ( domain->primary ) {
+ sid_compose(&user_sid, &info3->dom_sid.sid,
+ info3->user_rid);
+ cache_name2sid(domain, name_domain, name_user,
+ SID_NAME_USER, &user_sid);
+ }
+
+ /* Check if the user is in the right group */
+
+ if (!NT_STATUS_IS_OK(result = check_info3_in_group(state->mem_ctx, info3,
+ state->request.data.auth.require_membership_of_sid))) {
+ DEBUG(3, ("User %s is not in the required group (%s), so plaintext authentication is rejected\n",
+ state->request.data.auth.user,
+ state->request.data.auth.require_membership_of_sid));
+ goto done;
+ }
+
+ result = append_data(state, info3, name_domain, name_user);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ if ((state->request.flags & WBFLAG_PAM_CACHED_LOGIN)) {
+
+ /* Store in-memory creds for single-signon using ntlm_auth. */
+ result = winbindd_add_memory_creds(state->request.data.auth.user,
+ get_uid_from_state(state),
+ state->request.data.auth.pass);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to store memory creds: %s\n", nt_errstr(result)));
+ goto done;
+ }
+
+ if (lp_winbind_offline_logon()) {
+ result = winbindd_store_creds(domain,
+ state->mem_ctx,
+ state->request.data.auth.user,
+ state->request.data.auth.pass,
+ info3, NULL);
+ if (!NT_STATUS_IS_OK(result)) {
+
+ /* Release refcount. */
+ winbindd_delete_memory_creds(state->request.data.auth.user);
+
+ DEBUG(10,("Failed to store creds: %s\n", nt_errstr(result)));
+ goto done;
+ }
+ }
+ }
+
+ result = fillup_password_policy(domain, state);
+
+ if (!NT_STATUS_IS_OK(result)
+ && !NT_STATUS_EQUAL(result, NT_STATUS_NOT_SUPPORTED) )
+ {
+ DEBUG(10,("Failed to get password policies: %s\n", nt_errstr(result)));
+ goto done;
+ }
+
+ result = NT_STATUS_OK;
+ }
+
+done:
+ /* give us a more useful (more correct?) error code */
+ if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ||
+ (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
+ result = NT_STATUS_NO_LOGON_SERVERS;
+ }
+
+ state->response.data.auth.nt_status = NT_STATUS_V(result);
+ fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
+
+ /* we might have given a more useful error above */
+ if (!*state->response.data.auth.error_string)
+ fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
+ state->response.data.auth.pam_error = nt_status_to_pam(result);
+
+ DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Plain-text authentication for user %s returned %s (PAM: %d)\n",
+ state->request.data.auth.user,
+ state->response.data.auth.nt_status_string,
+ state->response.data.auth.pam_error));
+
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
+
+
+/**********************************************************************
+ Challenge Response Authentication Protocol
+**********************************************************************/
+
+void winbindd_pam_auth_crap(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain = NULL;
+ const char *domain_name = NULL;
+ NTSTATUS result;
+
+ if (!check_request_flags(state->request.flags)) {
+ result = NT_STATUS_INVALID_PARAMETER_MIX;
+ goto done;
+ }
+
+ if (!state->privileged) {
+ char *error_string = NULL;
+ DEBUG(2, ("winbindd_pam_auth_crap: non-privileged access "
+ "denied. !\n"));
+ DEBUGADD(2, ("winbindd_pam_auth_crap: Ensure permissions "
+ "on %s are set correctly.\n",
+ get_winbind_priv_pipe_dir()));
+ /* send a better message than ACCESS_DENIED */
+ error_string = talloc_asprintf(state->mem_ctx,
+ "winbind client not authorized "
+ "to use winbindd_pam_auth_crap."
+ " Ensure permissions on %s "
+ "are set correctly.",
+ get_winbind_priv_pipe_dir());
+ fstrcpy(state->response.data.auth.error_string, error_string);
+ result = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ /* Ensure null termination */
+ state->request.data.auth_crap.user
+ [sizeof(state->request.data.auth_crap.user)-1]=0;
+ state->request.data.auth_crap.domain
+ [sizeof(state->request.data.auth_crap.domain)-1]=0;
+
+ DEBUG(3, ("[%5lu]: pam auth crap domain: [%s] user: %s\n",
+ (unsigned long)state->pid,
+ state->request.data.auth_crap.domain,
+ state->request.data.auth_crap.user));
+
+ if (*state->request.data.auth_crap.domain != '\0') {
+ domain_name = state->request.data.auth_crap.domain;
+ } else if (lp_winbind_use_default_domain()) {
+ domain_name = lp_workgroup();
+ }
+
+ if (domain_name != NULL)
+ domain = find_auth_domain(state, domain_name);
+
+ if (domain != NULL) {
+ sendto_domain(state, domain);
+ return;
+ }
+
+ result = NT_STATUS_NO_SUCH_USER;
+
+ done:
+ set_auth_errors(&state->response, result);
+ DEBUG(5, ("CRAP authentication for %s\\%s returned %s (PAM: %d)\n",
+ state->request.data.auth_crap.domain,
+ state->request.data.auth_crap.user,
+ state->response.data.auth.nt_status_string,
+ state->response.data.auth.pam_error));
+ request_error(state);
+ return;
+}
+
+
+enum winbindd_result winbindd_dual_pam_auth_crap(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ NTSTATUS result;
+ NET_USER_INFO_3 info3;
+ struct rpc_pipe_client *netlogon_pipe;
+ const char *name_user = NULL;
+ const char *name_domain = NULL;
+ const char *workstation;
+ struct winbindd_domain *contact_domain;
+ int attempts = 0;
+ BOOL retry;
+
+ DATA_BLOB lm_resp, nt_resp;
+
+ /* This is child-only, so no check for privileged access is needed
+ anymore */
+
+ /* Ensure null termination */
+ state->request.data.auth_crap.user[sizeof(state->request.data.auth_crap.user)-1]=0;
+ state->request.data.auth_crap.domain[sizeof(state->request.data.auth_crap.domain)-1]=0;
+
+ if (!check_request_flags(state->request.flags)) {
+ result = NT_STATUS_INVALID_PARAMETER_MIX;
+ goto done;
+ }
+
+ name_user = state->request.data.auth_crap.user;
+
+ if (*state->request.data.auth_crap.domain) {
+ name_domain = state->request.data.auth_crap.domain;
+ } else if (lp_winbind_use_default_domain()) {
+ name_domain = lp_workgroup();
+ } else {
+ DEBUG(5,("no domain specified with username (%s) - failing auth\n",
+ name_user));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ DEBUG(3, ("[%5lu]: pam auth crap domain: %s user: %s\n", (unsigned long)state->pid,
+ name_domain, name_user));
+
+ if (*state->request.data.auth_crap.workstation) {
+ workstation = state->request.data.auth_crap.workstation;
+ } else {
+ workstation = global_myname();
+ }
+
+ if (state->request.data.auth_crap.lm_resp_len > sizeof(state->request.data.auth_crap.lm_resp)
+ || state->request.data.auth_crap.nt_resp_len > sizeof(state->request.data.auth_crap.nt_resp)) {
+ DEBUG(0, ("winbindd_pam_auth_crap: invalid password length %u/%u\n",
+ state->request.data.auth_crap.lm_resp_len,
+ state->request.data.auth_crap.nt_resp_len));
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ lm_resp = data_blob_talloc(state->mem_ctx, state->request.data.auth_crap.lm_resp,
+ state->request.data.auth_crap.lm_resp_len);
+ nt_resp = data_blob_talloc(state->mem_ctx, state->request.data.auth_crap.nt_resp,
+ state->request.data.auth_crap.nt_resp_len);
+
+ /* what domain should we contact? */
+
+ if ( IS_DC ) {
+ if (!(contact_domain = find_domain_from_name(name_domain))) {
+ DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n",
+ state->request.data.auth_crap.user, name_domain, name_user, name_domain));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+ } else {
+ if (is_myname(name_domain)) {
+ DEBUG(3, ("Authentication for domain %s (local domain to this server) not supported at this stage\n", name_domain));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+ contact_domain = find_our_domain();
+ }
+
+ do {
+ NTSTATUS (*logon_fn)(struct rpc_pipe_client
+ *cli, TALLOC_CTX *mem_ctx,
+ uint32 logon_parameters,
+ const char *server,
+ const char *username,
+ const char *domain,
+ const char *workstation,
+ const uint8 chal[8],
+ DATA_BLOB lm_response,
+ DATA_BLOB nt_response,
+ NET_USER_INFO_3 *info3);
+
+ ZERO_STRUCT(info3);
+ retry = False;
+
+ netlogon_pipe = NULL;
+ result = cm_connect_netlogon(contact_domain, &netlogon_pipe);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe (error: %s)\n",
+ nt_errstr(result)));
+ goto done;
+ }
+
+ logon_fn = contact_domain->can_do_samlogon_ex
+ ? rpccli_netlogon_sam_network_logon_ex
+ : rpccli_netlogon_sam_network_logon;
+
+ result = logon_fn(netlogon_pipe,
+ state->mem_ctx,
+ state->request.data.auth_crap.logon_parameters,
+ contact_domain->dcname,
+ name_user,
+ name_domain,
+ /* Bug #3248 - found by Stefan Burkei. */
+ workstation, /* We carefully set this above so use it... */
+ state->request.data.auth_crap.chal,
+ lm_resp,
+ nt_resp,
+ &info3);
+
+ if ((NT_STATUS_V(result) == DCERPC_FAULT_OP_RNG_ERROR)
+ && contact_domain->can_do_samlogon_ex) {
+ DEBUG(3, ("Got a DC that can not do NetSamLogonEx, "
+ "retrying with NetSamLogon\n"));
+ contact_domain->can_do_samlogon_ex = False;
+ retry = True;
+ continue;
+ }
+
+ attempts += 1;
+
+ /* We have to try a second time as cm_connect_netlogon
+ might not yet have noticed that the DC has killed
+ our connection. */
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)) {
+ retry = True;
+ continue;
+ }
+
+ /* if we get access denied, a possible cause was that we had and open
+ connection to the DC, but someone changed our machine account password
+ out from underneath us using 'net rpc changetrustpw' */
+
+ if ( NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED) ) {
+ DEBUG(3,("winbindd_pam_auth: sam_logon returned "
+ "ACCESS_DENIED. Maybe the trust account "
+ "password was changed and we didn't know it. "
+ "Killing connections to domain %s\n",
+ name_domain));
+ invalidate_cm_connection(&contact_domain->conn);
+ retry = True;
+ }
+
+ } while ( (attempts < 2) && retry );
+
+ if (NT_STATUS_IS_OK(result)) {
+
+ netsamlogon_cache_store(name_user, &info3);
+ wcache_invalidate_samlogon(find_domain_from_name(name_domain), &info3);
+
+ /* Check if the user is in the right group */
+
+ if (!NT_STATUS_IS_OK(result = check_info3_in_group(state->mem_ctx, &info3,
+ state->request.data.auth_crap.require_membership_of_sid))) {
+ DEBUG(3, ("User %s is not in the required group (%s), so "
+ "crap authentication is rejected\n",
+ state->request.data.auth_crap.user,
+ state->request.data.auth_crap.require_membership_of_sid));
+ goto done;
+ }
+
+ result = append_data(state, &info3, name_domain, name_user);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+done:
+
+ /* give us a more useful (more correct?) error code */
+ if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ||
+ (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
+ result = NT_STATUS_NO_LOGON_SERVERS;
+ }
+
+ if (state->request.flags & WBFLAG_PAM_NT_STATUS_SQUASH) {
+ result = nt_status_squash(result);
+ }
+
+ state->response.data.auth.nt_status = NT_STATUS_V(result);
+ fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
+
+ /* we might have given a more useful error above */
+ if (!*state->response.data.auth.error_string) {
+ fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
+ }
+ state->response.data.auth.pam_error = nt_status_to_pam(result);
+
+ DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2,
+ ("NTLM CRAP authentication for user [%s]\\[%s] returned %s (PAM: %d)\n",
+ name_domain,
+ name_user,
+ state->response.data.auth.nt_status_string,
+ state->response.data.auth.pam_error));
+
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
+
+/* Change a user password */
+
+void winbindd_pam_chauthtok(struct winbindd_cli_state *state)
+{
+ fstring domain, user;
+ struct winbindd_domain *contact_domain;
+
+ DEBUG(3, ("[%5lu]: pam chauthtok %s\n", (unsigned long)state->pid,
+ state->request.data.chauthtok.user));
+
+ /* Setup crap */
+
+ ws_name_return( state->request.data.auth.user, WB_REPLACE_CHAR );
+
+ if (!canonicalize_username(state->request.data.chauthtok.user, domain, user)) {
+ set_auth_errors(&state->response, NT_STATUS_NO_SUCH_USER);
+ DEBUG(5, ("winbindd_pam_chauthtok: canonicalize_username %s failed with %s"
+ "(PAM: %d)\n",
+ state->request.data.auth.user,
+ state->response.data.auth.nt_status_string,
+ state->response.data.auth.pam_error));
+ request_error(state);
+ return;
+ }
+
+ contact_domain = find_domain_from_name(domain);
+ if (!contact_domain) {
+ set_auth_errors(&state->response, NT_STATUS_NO_SUCH_USER);
+ DEBUG(3, ("Cannot change password for [%s] -> [%s]\\[%s] as %s is not a trusted domain\n",
+ state->request.data.chauthtok.user, domain, user, domain));
+ request_error(state);
+ return;
+ }
+
+ sendto_domain(state, contact_domain);
+}
+
+enum winbindd_result winbindd_dual_pam_chauthtok(struct winbindd_domain *contact_domain,
+ struct winbindd_cli_state *state)
+{
+ char *oldpass;
+ char *newpass = NULL;
+ POLICY_HND dom_pol;
+ struct rpc_pipe_client *cli;
+ BOOL got_info = False;
+ SAM_UNK_INFO_1 info;
+ SAMR_CHANGE_REJECT reject;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ fstring domain, user;
+
+ DEBUG(3, ("[%5lu]: dual pam chauthtok %s\n", (unsigned long)state->pid,
+ state->request.data.auth.user));
+
+ if (!parse_domain_user(state->request.data.chauthtok.user, domain, user)) {
+ goto done;
+ }
+
+ /* Change password */
+
+ oldpass = state->request.data.chauthtok.oldpass;
+ newpass = state->request.data.chauthtok.newpass;
+
+ /* Initialize reject reason */
+ state->response.data.auth.reject_reason = Undefined;
+
+ /* Get sam handle */
+
+ result = cm_connect_sam(contact_domain, state->mem_ctx, &cli,
+ &dom_pol);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("could not get SAM handle on DC for %s\n", domain));
+ goto done;
+ }
+
+ result = rpccli_samr_chgpasswd3(cli, state->mem_ctx, user, newpass, oldpass, &info, &reject);
+
+ /* Windows 2003 returns NT_STATUS_PASSWORD_RESTRICTION */
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_RESTRICTION) ) {
+ state->response.data.auth.policy.min_length_password =
+ info.min_length_password;
+ state->response.data.auth.policy.password_history =
+ info.password_history;
+ state->response.data.auth.policy.password_properties =
+ info.password_properties;
+ state->response.data.auth.policy.expire =
+ nt_time_to_unix_abs(&info.expire);
+ state->response.data.auth.policy.min_passwordage =
+ nt_time_to_unix_abs(&info.min_passwordage);
+
+ state->response.data.auth.reject_reason =
+ reject.reject_reason;
+
+ got_info = True;
+ }
+
+ /* only fallback when the chgpasswd3 call is not supported */
+ if ((NT_STATUS_EQUAL(result, NT_STATUS(DCERPC_FAULT_OP_RNG_ERROR))) ||
+ (NT_STATUS_EQUAL(result, NT_STATUS_NOT_SUPPORTED)) ||
+ (NT_STATUS_EQUAL(result, NT_STATUS_NOT_IMPLEMENTED))) {
+
+ DEBUG(10,("Password change with chgpasswd3 failed with: %s, retrying chgpasswd_user\n",
+ nt_errstr(result)));
+
+ result = rpccli_samr_chgpasswd_user(cli, state->mem_ctx, user, newpass, oldpass);
+
+ /* Windows 2000 returns NT_STATUS_ACCOUNT_RESTRICTION.
+ Map to the same status code as Windows 2003. */
+
+ if ( NT_STATUS_EQUAL(NT_STATUS_ACCOUNT_RESTRICTION, result ) ) {
+ result = NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+
+done:
+
+ if (NT_STATUS_IS_OK(result) && (state->request.flags & WBFLAG_PAM_CACHED_LOGIN)) {
+
+ /* Update the single sign-on memory creds. */
+ result = winbindd_replace_memory_creds(state->request.data.chauthtok.user,
+ newpass);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to replace memory creds: %s\n", nt_errstr(result)));
+ goto process_result;
+ }
+
+ if (lp_winbind_offline_logon()) {
+ result = winbindd_update_creds_by_name(contact_domain,
+ state->mem_ctx, user,
+ newpass);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to store creds: %s\n", nt_errstr(result)));
+ goto process_result;
+ }
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(result) && !got_info && contact_domain) {
+
+ NTSTATUS policy_ret;
+
+ policy_ret = fillup_password_policy(contact_domain, state);
+
+ /* failure of this is non critical, it will just provide no
+ * additional information to the client why the change has
+ * failed - Guenther */
+
+ if (!NT_STATUS_IS_OK(policy_ret)) {
+ DEBUG(10,("Failed to get password policies: %s\n", nt_errstr(policy_ret)));
+ goto process_result;
+ }
+ }
+
+process_result:
+
+ state->response.data.auth.nt_status = NT_STATUS_V(result);
+ fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
+ fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
+ state->response.data.auth.pam_error = nt_status_to_pam(result);
+
+ DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2,
+ ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n",
+ domain,
+ user,
+ state->response.data.auth.nt_status_string,
+ state->response.data.auth.pam_error));
+
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
+
+void winbindd_pam_logoff(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+ fstring name_domain, user;
+ uid_t caller_uid = (uid_t)-1;
+ uid_t request_uid = state->request.data.logoff.uid;
+
+ DEBUG(3, ("[%5lu]: pam logoff %s\n", (unsigned long)state->pid,
+ state->request.data.logoff.user));
+
+ /* Ensure null termination */
+ state->request.data.logoff.user
+ [sizeof(state->request.data.logoff.user)-1]='\0';
+
+ state->request.data.logoff.krb5ccname
+ [sizeof(state->request.data.logoff.krb5ccname)-1]='\0';
+
+ if (request_uid == (gid_t)-1) {
+ goto failed;
+ }
+
+ if (!canonicalize_username(state->request.data.logoff.user, name_domain, user)) {
+ goto failed;
+ }
+
+ if ((domain = find_auth_domain(state, name_domain)) == NULL) {
+ goto failed;
+ }
+
+ if ((sys_getpeereid(state->sock, &caller_uid)) != 0) {
+ DEBUG(1,("winbindd_pam_logoff: failed to check peerid: %s\n",
+ strerror(errno)));
+ goto failed;
+ }
+
+ switch (caller_uid) {
+ case -1:
+ goto failed;
+ case 0:
+ /* root must be able to logoff any user - gd */
+ state->request.data.logoff.uid = request_uid;
+ break;
+ default:
+ if (caller_uid != request_uid) {
+ DEBUG(1,("winbindd_pam_logoff: caller requested invalid uid\n"));
+ goto failed;
+ }
+ state->request.data.logoff.uid = caller_uid;
+ break;
+ }
+
+ sendto_domain(state, domain);
+ return;
+
+ failed:
+ set_auth_errors(&state->response, NT_STATUS_NO_SUCH_USER);
+ DEBUG(5, ("Pam Logoff for %s returned %s "
+ "(PAM: %d)\n",
+ state->request.data.logoff.user,
+ state->response.data.auth.nt_status_string,
+ state->response.data.auth.pam_error));
+ request_error(state);
+ return;
+}
+
+enum winbindd_result winbindd_dual_pam_logoff(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ NTSTATUS result = NT_STATUS_NOT_SUPPORTED;
+
+ DEBUG(3, ("[%5lu]: pam dual logoff %s\n", (unsigned long)state->pid,
+ state->request.data.logoff.user));
+
+ if (!(state->request.flags & WBFLAG_PAM_KRB5)) {
+ result = NT_STATUS_OK;
+ goto process_result;
+ }
+
+ if (state->request.data.logoff.krb5ccname[0] == '\0') {
+ result = NT_STATUS_OK;
+ goto process_result;
+ }
+
+#ifdef HAVE_KRB5
+
+ if (state->request.data.logoff.uid < 0) {
+ DEBUG(0,("winbindd_pam_logoff: invalid uid\n"));
+ goto process_result;
+ }
+
+ /* what we need here is to find the corresponding krb5 ccache name *we*
+ * created for a given username and destroy it */
+
+ if (!ccache_entry_exists(state->request.data.logoff.user)) {
+ result = NT_STATUS_OK;
+ DEBUG(10,("winbindd_pam_logoff: no entry found.\n"));
+ goto process_result;
+ }
+
+ if (!ccache_entry_identical(state->request.data.logoff.user,
+ state->request.data.logoff.uid,
+ state->request.data.logoff.krb5ccname)) {
+ DEBUG(0,("winbindd_pam_logoff: cached entry differs.\n"));
+ goto process_result;
+ }
+
+ result = remove_ccache(state->request.data.logoff.user);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("winbindd_pam_logoff: failed to remove ccache: %s\n",
+ nt_errstr(result)));
+ goto process_result;
+ }
+
+#else
+ result = NT_STATUS_NOT_SUPPORTED;
+#endif
+
+process_result:
+
+ winbindd_delete_memory_creds(state->request.data.logoff.user);
+
+ state->response.data.auth.nt_status = NT_STATUS_V(result);
+ fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
+ fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
+ state->response.data.auth.pam_error = nt_status_to_pam(result);
+
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
+
+/* Change user password with auth crap*/
+
+void winbindd_pam_chng_pswd_auth_crap(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain = NULL;
+ const char *domain_name = NULL;
+
+ /* Ensure null termination */
+ state->request.data.chng_pswd_auth_crap.user[
+ sizeof(state->request.data.chng_pswd_auth_crap.user)-1]=0;
+ state->request.data.chng_pswd_auth_crap.domain[
+ sizeof(state->request.data.chng_pswd_auth_crap.domain)-1]=0;
+
+ DEBUG(3, ("[%5lu]: pam change pswd auth crap domain: %s user: %s\n",
+ (unsigned long)state->pid,
+ state->request.data.chng_pswd_auth_crap.domain,
+ state->request.data.chng_pswd_auth_crap.user));
+
+ if (*state->request.data.chng_pswd_auth_crap.domain != '\0') {
+ domain_name = state->request.data.chng_pswd_auth_crap.domain;
+ } else if (lp_winbind_use_default_domain()) {
+ domain_name = lp_workgroup();
+ }
+
+ if (domain_name != NULL)
+ domain = find_domain_from_name(domain_name);
+
+ if (domain != NULL) {
+ DEBUG(7, ("[%5lu]: pam auth crap changing pswd in domain: "
+ "%s\n", (unsigned long)state->pid,domain->name));
+ sendto_domain(state, domain);
+ return;
+ }
+
+ set_auth_errors(&state->response, NT_STATUS_NO_SUCH_USER);
+ DEBUG(5, ("CRAP change password for %s\\%s returned %s (PAM: %d)\n",
+ state->request.data.chng_pswd_auth_crap.domain,
+ state->request.data.chng_pswd_auth_crap.user,
+ state->response.data.auth.nt_status_string,
+ state->response.data.auth.pam_error));
+ request_error(state);
+ return;
+}
+
+enum winbindd_result winbindd_dual_pam_chng_pswd_auth_crap(struct winbindd_domain *domainSt, struct winbindd_cli_state *state)
+{
+ NTSTATUS result;
+ DATA_BLOB new_nt_password;
+ DATA_BLOB old_nt_hash_enc;
+ DATA_BLOB new_lm_password;
+ DATA_BLOB old_lm_hash_enc;
+ fstring domain,user;
+ POLICY_HND dom_pol;
+ struct winbindd_domain *contact_domain = domainSt;
+ struct rpc_pipe_client *cli;
+
+ /* Ensure null termination */
+ state->request.data.chng_pswd_auth_crap.user[
+ sizeof(state->request.data.chng_pswd_auth_crap.user)-1]=0;
+ state->request.data.chng_pswd_auth_crap.domain[
+ sizeof(state->request.data.chng_pswd_auth_crap.domain)-1]=0;
+ *domain = 0;
+ *user = 0;
+
+ DEBUG(3, ("[%5lu]: pam change pswd auth crap domain: %s user: %s\n",
+ (unsigned long)state->pid,
+ state->request.data.chng_pswd_auth_crap.domain,
+ state->request.data.chng_pswd_auth_crap.user));
+
+ if (lp_winbind_offline_logon()) {
+ DEBUG(0,("Refusing password change as winbind offline logons are enabled. "));
+ DEBUGADD(0,("Changing passwords here would risk inconsistent logons\n"));
+ result = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ if (*state->request.data.chng_pswd_auth_crap.domain) {
+ fstrcpy(domain,state->request.data.chng_pswd_auth_crap.domain);
+ } else {
+ parse_domain_user(state->request.data.chng_pswd_auth_crap.user,
+ domain, user);
+
+ if(!*domain) {
+ DEBUG(3,("no domain specified with username (%s) - "
+ "failing auth\n",
+ state->request.data.chng_pswd_auth_crap.user));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+ }
+
+ if (!*domain && lp_winbind_use_default_domain()) {
+ fstrcpy(domain,(char *)lp_workgroup());
+ }
+
+ if(!*user) {
+ fstrcpy(user, state->request.data.chng_pswd_auth_crap.user);
+ }
+
+ DEBUG(3, ("[%5lu]: pam auth crap domain: %s user: %s\n",
+ (unsigned long)state->pid, domain, user));
+
+ /* Change password */
+ new_nt_password = data_blob_talloc(
+ state->mem_ctx,
+ state->request.data.chng_pswd_auth_crap.new_nt_pswd,
+ state->request.data.chng_pswd_auth_crap.new_nt_pswd_len);
+
+ old_nt_hash_enc = data_blob_talloc(
+ state->mem_ctx,
+ state->request.data.chng_pswd_auth_crap.old_nt_hash_enc,
+ state->request.data.chng_pswd_auth_crap.old_nt_hash_enc_len);
+
+ if(state->request.data.chng_pswd_auth_crap.new_lm_pswd_len > 0) {
+ new_lm_password = data_blob_talloc(
+ state->mem_ctx,
+ state->request.data.chng_pswd_auth_crap.new_lm_pswd,
+ state->request.data.chng_pswd_auth_crap.new_lm_pswd_len);
+
+ old_lm_hash_enc = data_blob_talloc(
+ state->mem_ctx,
+ state->request.data.chng_pswd_auth_crap.old_lm_hash_enc,
+ state->request.data.chng_pswd_auth_crap.old_lm_hash_enc_len);
+ } else {
+ new_lm_password.length = 0;
+ old_lm_hash_enc.length = 0;
+ }
+
+ /* Get sam handle */
+
+ result = cm_connect_sam(contact_domain, state->mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("could not get SAM handle on DC for %s\n", domain));
+ goto done;
+ }
+
+ result = rpccli_samr_chng_pswd_auth_crap(
+ cli, state->mem_ctx, user, new_nt_password, old_nt_hash_enc,
+ new_lm_password, old_lm_hash_enc);
+
+ done:
+ state->response.data.auth.nt_status = NT_STATUS_V(result);
+ fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
+ fstrcpy(state->response.data.auth.error_string,
+ get_friendly_nt_error_msg(result));
+ state->response.data.auth.pam_error = nt_status_to_pam(result);
+
+ DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2,
+ ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n",
+ domain, user,
+ state->response.data.auth.nt_status_string,
+ state->response.data.auth.pam_error));
+
+ return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
diff --git a/source3/winbindd/winbindd_passdb.c b/source3/winbindd/winbindd_passdb.c
new file mode 100644
index 0000000000..5c36c0c327
--- /dev/null
+++ b/source3/winbindd/winbindd_passdb.c
@@ -0,0 +1,467 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind rpc backend functions
+
+ Copyright (C) Tim Potter 2000-2001,2003
+ Copyright (C) Simo Sorce 2003
+ Copyright (C) Volker Lendecke 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/* Query display info for a domain. This returns enough information plus a
+ bit extra to give an overview of domain users for the User Manager
+ application. */
+static NTSTATUS query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ WINBIND_USERINFO **info)
+{
+ /* We don't have users */
+ *num_entries = 0;
+ *info = NULL;
+ return NT_STATUS_OK;
+}
+
+/* list all domain groups */
+static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ /* We don't have domain groups */
+ *num_entries = 0;
+ *info = NULL;
+ return NT_STATUS_OK;
+}
+
+/* List all domain groups */
+
+static NTSTATUS enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ struct pdb_search *search;
+ struct samr_displayentry *aliases;
+ int i;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ search = pdb_search_aliases(&domain->sid);
+ if (search == NULL) goto done;
+
+ *num_entries = pdb_search_entries(search, 0, 0xffffffff, &aliases);
+ if (*num_entries == 0) goto done;
+
+ *info = TALLOC_ARRAY(mem_ctx, struct acct_info, *num_entries);
+ if (*info == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i<*num_entries; i++) {
+ fstrcpy((*info)[i].acct_name, aliases[i].account_name);
+ fstrcpy((*info)[i].acct_desc, aliases[i].description);
+ (*info)[i].rid = aliases[i].rid;
+ }
+
+ result = NT_STATUS_OK;
+ done:
+ pdb_search_destroy(search);
+ return result;
+}
+
+/* convert a single name to a sid in a domain */
+static NTSTATUS name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ enum winbindd_cmd original_cmd,
+ const char *domain_name,
+ const char *name,
+ DOM_SID *sid,
+ enum lsa_SidType *type)
+{
+ uint32 flags = LOOKUP_NAME_ALL;
+
+ switch ( original_cmd ) {
+ case WINBINDD_LOOKUPNAME:
+ /* This call is ok */
+ break;
+ default:
+ /* Avoid any NSS calls in the lookup_name by default */
+ flags |= LOOKUP_NAME_EXPLICIT;
+ DEBUG(10,("winbindd_passdb: limiting name_to_sid() to explicit mappings\n"));
+ break;
+ }
+
+ DEBUG(10, ("Finding name %s\n", name));
+
+ if ( !lookup_name( mem_ctx, name, flags, NULL, NULL, sid, type ) ) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ convert a domain SID to a user or group name
+*/
+static NTSTATUS sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ const char *dom, *nam;
+
+ DEBUG(10, ("Converting SID %s\n", sid_string_static(sid)));
+
+ /* Paranoia check */
+ if (!sid_check_is_in_builtin(sid) &&
+ !sid_check_is_in_our_domain(sid) &&
+ !sid_check_is_in_unix_users(sid) &&
+ !sid_check_is_unix_users(sid) &&
+ !sid_check_is_in_unix_groups(sid) &&
+ !sid_check_is_unix_groups(sid) )
+ {
+ DEBUG(0, ("Possible deadlock: Trying to lookup SID %s with "
+ "passdb backend\n", sid_string_static(sid)));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ if (!lookup_sid(mem_ctx, sid, &dom, &nam, type)) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ *domain_name = talloc_strdup(mem_ctx, dom);
+ *name = talloc_strdup(mem_ctx, nam);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ uint32 *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types)
+{
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+/* Lookup user information from a rid or username. */
+static NTSTATUS query_user(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ WINBIND_USERINFO *user_info)
+{
+ return NT_STATUS_NO_SUCH_USER;
+}
+
+/* Lookup groups a user is a member of. I wish Unix had a call like this! */
+static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ uint32 *num_groups, DOM_SID **user_gids)
+{
+ NTSTATUS result;
+ DOM_SID *groups = NULL;
+ gid_t *gids = NULL;
+ size_t ngroups = 0;
+ struct samu *user;
+
+ if ( (user = samu_new(mem_ctx)) == NULL ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !pdb_getsampwsid( user, user_sid ) ) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ result = pdb_enum_group_memberships( mem_ctx, user, &groups, &gids, &ngroups );
+
+ TALLOC_FREE( user );
+
+ *num_groups = (uint32)ngroups;
+ *user_gids = groups;
+
+ return result;
+}
+
+static NTSTATUS lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 num_sids, const DOM_SID *sids,
+ uint32 *p_num_aliases, uint32 **rids)
+{
+ NTSTATUS result;
+ size_t num_aliases = 0;
+
+ result = pdb_enum_alias_memberships(mem_ctx, &domain->sid,
+ sids, num_sids, rids, &num_aliases);
+
+ *p_num_aliases = num_aliases;
+ return result;
+}
+
+/* Lookup group membership given a rid. */
+static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *group_sid, uint32 *num_names,
+ DOM_SID **sid_mem, char ***names,
+ uint32 **name_types)
+{
+ size_t i, num_members, num_mapped;
+ uint32 *rids;
+ NTSTATUS result;
+ const DOM_SID **sids;
+ struct lsa_dom_info *lsa_domains;
+ struct lsa_name_info *lsa_names;
+ TALLOC_CTX *tmp_ctx;
+
+ if (!sid_check_is_in_our_domain(group_sid)) {
+ /* There's no groups, only aliases in BUILTIN */
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ if (!(tmp_ctx = talloc_init("lookup_groupmem"))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ result = pdb_enum_group_members(tmp_ctx, group_sid, &rids,
+ &num_members);
+ if (!NT_STATUS_IS_OK(result)) {
+ TALLOC_FREE(tmp_ctx);
+ return result;
+ }
+
+ if (num_members == 0) {
+ *num_names = 0;
+ *sid_mem = NULL;
+ *names = NULL;
+ *name_types = NULL;
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ *sid_mem = TALLOC_ARRAY(mem_ctx, DOM_SID, num_members);
+ *names = TALLOC_ARRAY(mem_ctx, char *, num_members);
+ *name_types = TALLOC_ARRAY(mem_ctx, uint32, num_members);
+ sids = TALLOC_ARRAY(tmp_ctx, const DOM_SID *, num_members);
+
+ if (((*sid_mem) == NULL) || ((*names) == NULL) ||
+ ((*name_types) == NULL) || (sids == NULL)) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Prepare an array of sid pointers for the lookup_sids calling
+ * convention.
+ */
+
+ for (i=0; i<num_members; i++) {
+ DOM_SID *sid = &((*sid_mem)[i]);
+ if (!sid_compose(sid, &domain->sid, rids[i])) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ sids[i] = sid;
+ }
+
+ result = lookup_sids(tmp_ctx, num_members, sids, 1,
+ &lsa_domains, &lsa_names);
+ if (!NT_STATUS_IS_OK(result)) {
+ TALLOC_FREE(tmp_ctx);
+ return result;
+ }
+
+ num_mapped = 0;
+ for (i=0; i<num_members; i++) {
+ if (lsa_names[i].type != SID_NAME_USER) {
+ DEBUG(2, ("Got %s as group member -- ignoring\n",
+ sid_type_lookup(lsa_names[i].type)));
+ continue;
+ }
+ if (!((*names)[i] = talloc_strdup((*names),
+ lsa_names[i].name))) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*name_types)[i] = lsa_names[i].type;
+
+ num_mapped += 1;
+ }
+
+ *num_names = num_mapped;
+
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/* find the sequence number for a domain */
+static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq)
+{
+ BOOL result;
+ time_t seq_num;
+
+ result = pdb_get_seq_num(&seq_num);
+ if (!result) {
+ *seq = 1;
+ }
+
+ *seq = (int) seq_num;
+ /* *seq = 1; */
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_12 *policy)
+{
+ /* actually we have that */
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_1 *policy)
+{
+ uint32 min_pass_len,pass_hist,password_properties;
+ time_t u_expire, u_min_age;
+ NTTIME nt_expire, nt_min_age;
+ uint32 account_policy_temp;
+
+ if ((policy = TALLOC_ZERO_P(mem_ctx, SAM_UNK_INFO_1)) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pdb_get_account_policy(AP_MIN_PASSWORD_LEN, &account_policy_temp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ min_pass_len = account_policy_temp;
+
+ if (!pdb_get_account_policy(AP_PASSWORD_HISTORY, &account_policy_temp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ pass_hist = account_policy_temp;
+
+ if (!pdb_get_account_policy(AP_USER_MUST_LOGON_TO_CHG_PASS, &account_policy_temp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ password_properties = account_policy_temp;
+
+ if (!pdb_get_account_policy(AP_MAX_PASSWORD_AGE, &account_policy_temp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ u_expire = account_policy_temp;
+
+ if (!pdb_get_account_policy(AP_MIN_PASSWORD_AGE, &account_policy_temp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ u_min_age = account_policy_temp;
+
+ unix_to_nt_time_abs(&nt_expire, u_expire);
+ unix_to_nt_time_abs(&nt_min_age, u_min_age);
+
+ init_unk_info1(policy, (uint16)min_pass_len, (uint16)pass_hist,
+ password_properties, nt_expire, nt_min_age);
+
+ return NT_STATUS_OK;
+}
+
+/* get a list of trusted domains */
+static NTSTATUS trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_domains,
+ char ***names,
+ char ***alt_names,
+ DOM_SID **dom_sids)
+{
+ NTSTATUS nt_status;
+ struct trustdom_info **domains;
+ int i;
+ TALLOC_CTX *tmp_ctx;
+
+ *num_domains = 0;
+ *names = NULL;
+ *alt_names = NULL;
+ *dom_sids = NULL;
+
+ if (!(tmp_ctx = talloc_init("trusted_domains"))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = pdb_enum_trusteddoms(tmp_ctx, num_domains, &domains);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ if (*num_domains) {
+ *names = TALLOC_ARRAY(mem_ctx, char *, *num_domains);
+ *alt_names = TALLOC_ARRAY(mem_ctx, char *, *num_domains);
+ *dom_sids = TALLOC_ARRAY(mem_ctx, DOM_SID, *num_domains);
+
+ if ((*alt_names == NULL) || (*names == NULL) || (*dom_sids == NULL)) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ *names = NULL;
+ *alt_names = NULL;
+ *dom_sids = NULL;
+ }
+
+ for (i=0; i<*num_domains; i++) {
+ (*alt_names)[i] = NULL;
+ if (!((*names)[i] = talloc_strdup((*names),
+ domains[i]->name))) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ sid_copy(&(*dom_sids)[i], &domains[i]->sid);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/* the rpc backend methods are exposed via this structure */
+struct winbindd_methods passdb_methods = {
+ False,
+ query_user_list,
+ enum_dom_groups,
+ enum_local_groups,
+ name_to_sid,
+ sid_to_name,
+ rids_to_names,
+ query_user,
+ lookup_usergroups,
+ lookup_useraliases,
+ lookup_groupmem,
+ sequence_number,
+ lockout_policy,
+ password_policy,
+ trusted_domains,
+};
diff --git a/source3/winbindd/winbindd_reconnect.c b/source3/winbindd/winbindd_reconnect.c
new file mode 100644
index 0000000000..a1f96a0359
--- /dev/null
+++ b/source3/winbindd/winbindd_reconnect.c
@@ -0,0 +1,316 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Wrapper around winbindd_rpc.c to centralize retry logic.
+
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+extern struct winbindd_methods msrpc_methods;
+
+/* List all users */
+static NTSTATUS query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ WINBIND_USERINFO **info)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.query_user_list(domain, mem_ctx,
+ num_entries, info);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.query_user_list(domain, mem_ctx,
+ num_entries, info);
+ return result;
+}
+
+/* list all domain groups */
+static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.enum_dom_groups(domain, mem_ctx,
+ num_entries, info);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.enum_dom_groups(domain, mem_ctx,
+ num_entries, info);
+ return result;
+}
+
+/* List all domain groups */
+
+static NTSTATUS enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.enum_local_groups(domain, mem_ctx,
+ num_entries, info);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.enum_local_groups(domain, mem_ctx,
+ num_entries, info);
+
+ return result;
+}
+
+/* convert a single name to a sid in a domain */
+static NTSTATUS name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ enum winbindd_cmd orig_cmd,
+ const char *domain_name,
+ const char *name,
+ DOM_SID *sid,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.name_to_sid(domain, mem_ctx, orig_cmd,
+ domain_name, name,
+ sid, type);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.name_to_sid(domain, mem_ctx, orig_cmd,
+ domain_name, name,
+ sid, type);
+
+ return result;
+}
+
+/*
+ convert a domain SID to a user or group name
+*/
+static NTSTATUS sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.sid_to_name(domain, mem_ctx, sid,
+ domain_name, name, type);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.sid_to_name(domain, mem_ctx, sid,
+ domain_name, name, type);
+
+ return result;
+}
+
+static NTSTATUS rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ uint32 *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.rids_to_names(domain, mem_ctx, sid,
+ rids, num_rids,
+ domain_name, names, types);
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)) {
+ result = msrpc_methods.rids_to_names(domain, mem_ctx, sid,
+ rids, num_rids,
+ domain_name, names,
+ types);
+ }
+
+ return result;
+}
+
+/* Lookup user information from a rid or username. */
+static NTSTATUS query_user(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ WINBIND_USERINFO *user_info)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.query_user(domain, mem_ctx, user_sid,
+ user_info);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.query_user(domain, mem_ctx, user_sid,
+ user_info);
+
+ return result;
+}
+
+/* Lookup groups a user is a member of. I wish Unix had a call like this! */
+static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ uint32 *num_groups, DOM_SID **user_gids)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.lookup_usergroups(domain, mem_ctx,
+ user_sid, num_groups,
+ user_gids);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.lookup_usergroups(domain, mem_ctx,
+ user_sid, num_groups,
+ user_gids);
+
+ return result;
+}
+
+static NTSTATUS lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 num_sids, const DOM_SID *sids,
+ uint32 *num_aliases, uint32 **alias_rids)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.lookup_useraliases(domain, mem_ctx,
+ num_sids, sids,
+ num_aliases,
+ alias_rids);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.lookup_useraliases(domain, mem_ctx,
+ num_sids, sids,
+ num_aliases,
+ alias_rids);
+
+ return result;
+}
+
+/* Lookup group membership given a rid. */
+static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *group_sid, uint32 *num_names,
+ DOM_SID **sid_mem, char ***names,
+ uint32 **name_types)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.lookup_groupmem(domain, mem_ctx,
+ group_sid, num_names,
+ sid_mem, names,
+ name_types);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.lookup_groupmem(domain, mem_ctx,
+ group_sid, num_names,
+ sid_mem, names,
+ name_types);
+
+ return result;
+}
+
+/* find the sequence number for a domain */
+static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.sequence_number(domain, seq);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.sequence_number(domain, seq);
+
+ return result;
+}
+
+/* find the lockout policy of a domain */
+static NTSTATUS lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_12 *policy)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.lockout_policy(domain, mem_ctx, policy);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.lockout_policy(domain, mem_ctx, policy);
+
+ return result;
+}
+
+/* find the password policy of a domain */
+static NTSTATUS password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_1 *policy)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.password_policy(domain, mem_ctx, policy);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.password_policy(domain, mem_ctx, policy);
+
+ return result;
+}
+
+/* get a list of trusted domains */
+static NTSTATUS trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_domains,
+ char ***names,
+ char ***alt_names,
+ DOM_SID **dom_sids)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.trusted_domains(domain, mem_ctx,
+ num_domains, names,
+ alt_names, dom_sids);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))
+ result = msrpc_methods.trusted_domains(domain, mem_ctx,
+ num_domains, names,
+ alt_names, dom_sids);
+
+ return result;
+}
+
+/* the rpc backend methods are exposed via this structure */
+struct winbindd_methods reconnect_methods = {
+ False,
+ query_user_list,
+ enum_dom_groups,
+ enum_local_groups,
+ name_to_sid,
+ sid_to_name,
+ rids_to_names,
+ query_user,
+ lookup_usergroups,
+ lookup_useraliases,
+ lookup_groupmem,
+ sequence_number,
+ lockout_policy,
+ password_policy,
+ trusted_domains,
+};
diff --git a/source3/winbindd/winbindd_rpc.c b/source3/winbindd/winbindd_rpc.c
new file mode 100644
index 0000000000..6d2dd35080
--- /dev/null
+++ b/source3/winbindd/winbindd_rpc.c
@@ -0,0 +1,1111 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind rpc backend functions
+
+ Copyright (C) Tim Potter 2000-2001,2003
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+
+/* Query display info for a domain. This returns enough information plus a
+ bit extra to give an overview of domain users for the User Manager
+ application. */
+static NTSTATUS query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ WINBIND_USERINFO **info)
+{
+ NTSTATUS result;
+ POLICY_HND dom_pol;
+ unsigned int i, start_idx;
+ uint32 loop_count;
+ struct rpc_pipe_client *cli;
+
+ DEBUG(3,("rpc: query_user_list\n"));
+
+ *num_entries = 0;
+ *info = NULL;
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("query_user_list: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ i = start_idx = 0;
+ loop_count = 0;
+
+ do {
+ uint32 num_dom_users, j;
+ uint32 max_entries, max_size;
+ SAM_DISPINFO_CTR ctr;
+ SAM_DISPINFO_1 info1;
+
+ ZERO_STRUCT( ctr );
+ ZERO_STRUCT( info1 );
+ ctr.sam.info1 = &info1;
+
+ /* this next bit is copied from net_user_list_internal() */
+
+ get_query_dispinfo_params(loop_count, &max_entries,
+ &max_size);
+
+ result = rpccli_samr_query_dispinfo(cli, mem_ctx, &dom_pol,
+ &start_idx, 1,
+ &num_dom_users,
+ max_entries, max_size,
+ &ctr);
+
+ loop_count++;
+
+ *num_entries += num_dom_users;
+
+ *info = TALLOC_REALLOC_ARRAY(mem_ctx, *info, WINBIND_USERINFO,
+ *num_entries);
+
+ if (!(*info)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (j = 0; j < num_dom_users; i++, j++) {
+ fstring username, fullname;
+ uint32 rid = ctr.sam.info1->sam[j].rid_user;
+
+ unistr2_to_ascii( username, &(&ctr.sam.info1->str[j])->uni_acct_name, sizeof(username)-1);
+ unistr2_to_ascii( fullname, &(&ctr.sam.info1->str[j])->uni_full_name, sizeof(fullname)-1);
+
+ (*info)[i].acct_name = talloc_strdup(mem_ctx, username );
+ (*info)[i].full_name = talloc_strdup(mem_ctx, fullname );
+ (*info)[i].homedir = NULL;
+ (*info)[i].shell = NULL;
+ sid_compose(&(*info)[i].user_sid, &domain->sid, rid);
+
+ /* For the moment we set the primary group for
+ every user to be the Domain Users group.
+ There are serious problems with determining
+ the actual primary group for large domains.
+ This should really be made into a 'winbind
+ force group' smb.conf parameter or
+ something like that. */
+
+ sid_compose(&(*info)[i].group_sid, &domain->sid,
+ DOMAIN_GROUP_RID_USERS);
+ }
+
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ return result;
+}
+
+/* list all domain groups */
+static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ POLICY_HND dom_pol;
+ NTSTATUS status;
+ uint32 start = 0;
+ struct rpc_pipe_client *cli;
+
+ *num_entries = 0;
+ *info = NULL;
+
+ DEBUG(3,("rpc: enum_dom_groups\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("enum_domain_groups: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ status = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(status))
+ return status;
+
+ do {
+ struct acct_info *info2 = NULL;
+ uint32 count = 0;
+ TALLOC_CTX *mem_ctx2;
+
+ mem_ctx2 = talloc_init("enum_dom_groups[rpc]");
+
+ /* start is updated by this call. */
+ status = rpccli_samr_enum_dom_groups(cli, mem_ctx2, &dom_pol,
+ &start,
+ 0xFFFF, /* buffer size? */
+ &info2, &count);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ talloc_destroy(mem_ctx2);
+ break;
+ }
+
+ (*info) = TALLOC_REALLOC_ARRAY(mem_ctx, *info,
+ struct acct_info,
+ (*num_entries) + count);
+ if (! *info) {
+ talloc_destroy(mem_ctx2);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ memcpy(&(*info)[*num_entries], info2, count*sizeof(*info2));
+ (*num_entries) += count;
+ talloc_destroy(mem_ctx2);
+ } while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES));
+
+ return NT_STATUS_OK;
+}
+
+/* List all domain groups */
+
+static NTSTATUS enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_entries,
+ struct acct_info **info)
+{
+ POLICY_HND dom_pol;
+ NTSTATUS result;
+ struct rpc_pipe_client *cli;
+
+ *num_entries = 0;
+ *info = NULL;
+
+ DEBUG(3,("rpc: enum_local_groups\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("enum_local_groups: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ do {
+ struct acct_info *info2 = NULL;
+ uint32 count = 0, start = *num_entries;
+ TALLOC_CTX *mem_ctx2;
+
+ mem_ctx2 = talloc_init("enum_dom_local_groups[rpc]");
+
+ result = rpccli_samr_enum_als_groups( cli, mem_ctx2, &dom_pol,
+ &start, 0xFFFF, &info2,
+ &count);
+
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES) )
+ {
+ talloc_destroy(mem_ctx2);
+ return result;
+ }
+
+ (*info) = TALLOC_REALLOC_ARRAY(mem_ctx, *info,
+ struct acct_info,
+ (*num_entries) + count);
+ if (! *info) {
+ talloc_destroy(mem_ctx2);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ memcpy(&(*info)[*num_entries], info2, count*sizeof(*info2));
+ (*num_entries) += count;
+ talloc_destroy(mem_ctx2);
+
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ return NT_STATUS_OK;
+}
+
+/* convert a single name to a sid in a domain */
+NTSTATUS msrpc_name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ enum winbindd_cmd original_cmd,
+ const char *domain_name,
+ const char *name,
+ DOM_SID *sid,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+ DOM_SID *sids = NULL;
+ enum lsa_SidType *types = NULL;
+ char *full_name = NULL;
+ struct rpc_pipe_client *cli;
+ POLICY_HND lsa_policy;
+
+ if (name == NULL || *name=='\0') {
+ full_name = talloc_asprintf(mem_ctx, "%s", domain_name);
+ } else if (domain_name == NULL || *domain_name == '\0') {
+ full_name = talloc_asprintf(mem_ctx, "%s", name);
+ } else {
+ full_name = talloc_asprintf(mem_ctx, "%s\\%s", domain_name, name);
+ }
+ if (!full_name) {
+ DEBUG(0, ("talloc_asprintf failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(3,("rpc: name_to_sid name=%s\n", full_name));
+
+ ws_name_return( full_name, WB_REPLACE_CHAR );
+
+ DEBUG(3,("name_to_sid [rpc] %s for domain %s\n", full_name?full_name:"", domain_name ));
+
+ result = cm_connect_lsa(domain, mem_ctx, &cli, &lsa_policy);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ result = rpccli_lsa_lookup_names(cli, mem_ctx, &lsa_policy, 1,
+ (const char**) &full_name, NULL, 1, &sids, &types);
+
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ /* Return rid and type if lookup successful */
+
+ sid_copy(sid, &sids[0]);
+ *type = types[0];
+
+ return NT_STATUS_OK;
+}
+
+/*
+ convert a domain SID to a user or group name
+*/
+NTSTATUS msrpc_sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ char **domains;
+ char **names;
+ enum lsa_SidType *types;
+ NTSTATUS result;
+ struct rpc_pipe_client *cli;
+ POLICY_HND lsa_policy;
+
+ DEBUG(3,("sid_to_name [rpc] %s for domain %s\n", sid_string_static(sid),
+ domain->name ));
+
+ result = cm_connect_lsa(domain, mem_ctx, &cli, &lsa_policy);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2,("msrpc_sid_to_name: cm_connect_lsa() failed (%s)\n",
+ nt_errstr(result)));
+ return result;
+ }
+
+
+ result = rpccli_lsa_lookup_sids(cli, mem_ctx, &lsa_policy,
+ 1, sid, &domains, &names, &types);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2,("msrpc_sid_to_name: rpccli_lsa_lookup_sids() failed (%s)\n",
+ nt_errstr(result)));
+ return result;
+ }
+
+ *type = (enum lsa_SidType)types[0];
+ *domain_name = domains[0];
+ *name = names[0];
+
+ ws_name_replace( *name, WB_REPLACE_CHAR );
+
+ DEBUG(5,("Mapped sid to [%s]\\[%s]\n", domains[0], *name));
+ return NT_STATUS_OK;
+}
+
+NTSTATUS msrpc_rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *sid,
+ uint32 *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types)
+{
+ char **domains;
+ NTSTATUS result;
+ struct rpc_pipe_client *cli;
+ POLICY_HND lsa_policy;
+ DOM_SID *sids;
+ size_t i;
+ char **ret_names;
+
+ DEBUG(3, ("rids_to_names [rpc] for domain %s\n", domain->name ));
+
+ if (num_rids) {
+ sids = TALLOC_ARRAY(mem_ctx, DOM_SID, num_rids);
+ if (sids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ sids = NULL;
+ }
+
+ for (i=0; i<num_rids; i++) {
+ if (!sid_compose(&sids[i], sid, rids[i])) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ result = cm_connect_lsa(domain, mem_ctx, &cli, &lsa_policy);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ result = rpccli_lsa_lookup_sids(cli, mem_ctx, &lsa_policy,
+ num_rids, sids, &domains,
+ names, types);
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) {
+ return result;
+ }
+
+ ret_names = *names;
+ for (i=0; i<num_rids; i++) {
+ if ((*types)[i] != SID_NAME_UNKNOWN) {
+ ws_name_replace( ret_names[i], WB_REPLACE_CHAR );
+ *domain_name = domains[i];
+ }
+ }
+
+ return result;
+}
+
+/* Lookup user information from a rid or username. */
+static NTSTATUS query_user(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ WINBIND_USERINFO *user_info)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ POLICY_HND dom_pol, user_pol;
+ SAM_USERINFO_CTR *ctr;
+ fstring sid_string;
+ uint32 user_rid;
+ NET_USER_INFO_3 *user;
+ struct rpc_pipe_client *cli;
+
+ DEBUG(3,("rpc: query_user sid=%s\n",
+ sid_to_string(sid_string, user_sid)));
+
+ if (!sid_peek_check_rid(&domain->sid, user_sid, &user_rid))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ user_info->homedir = NULL;
+ user_info->shell = NULL;
+ user_info->primary_gid = (gid_t)-1;
+
+ /* try netsamlogon cache first */
+
+ if ( (user = netsamlogon_cache_get( mem_ctx, user_sid )) != NULL )
+ {
+
+ DEBUG(5,("query_user: Cache lookup succeeded for %s\n",
+ sid_string_static(user_sid)));
+
+ sid_compose(&user_info->user_sid, &domain->sid, user->user_rid);
+ sid_compose(&user_info->group_sid, &domain->sid,
+ user->group_rid);
+
+ user_info->acct_name = unistr2_tdup(mem_ctx,
+ &user->uni_user_name);
+ user_info->full_name = unistr2_tdup(mem_ctx,
+ &user->uni_full_name);
+
+ TALLOC_FREE(user);
+
+ return NT_STATUS_OK;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("query_user: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("query_user: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ /* no cache; hit the wire */
+
+ result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ /* Get user handle */
+ result = rpccli_samr_open_user(cli, mem_ctx, &dom_pol,
+ SEC_RIGHTS_MAXIMUM_ALLOWED, user_rid,
+ &user_pol);
+
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ /* Get user info */
+ result = rpccli_samr_query_userinfo(cli, mem_ctx, &user_pol,
+ 0x15, &ctr);
+
+ rpccli_samr_close(cli, mem_ctx, &user_pol);
+
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ sid_compose(&user_info->user_sid, &domain->sid, user_rid);
+ sid_compose(&user_info->group_sid, &domain->sid,
+ ctr->info.id21->group_rid);
+ user_info->acct_name = unistr2_tdup(mem_ctx,
+ &ctr->info.id21->uni_user_name);
+ user_info->full_name = unistr2_tdup(mem_ctx,
+ &ctr->info.id21->uni_full_name);
+ user_info->homedir = NULL;
+ user_info->shell = NULL;
+ user_info->primary_gid = (gid_t)-1;
+
+ return NT_STATUS_OK;
+}
+
+/* Lookup groups a user is a member of. I wish Unix had a call like this! */
+static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ uint32 *num_groups, DOM_SID **user_grpsids)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ POLICY_HND dom_pol, user_pol;
+ uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
+ DOM_GID *user_groups;
+ unsigned int i;
+ fstring sid_string;
+ uint32 user_rid;
+ struct rpc_pipe_client *cli;
+
+ DEBUG(3,("rpc: lookup_usergroups sid=%s\n",
+ sid_to_string(sid_string, user_sid)));
+
+ if (!sid_peek_check_rid(&domain->sid, user_sid, &user_rid))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ *num_groups = 0;
+ *user_grpsids = NULL;
+
+ /* so lets see if we have a cached user_info_3 */
+ result = lookup_usergroups_cached(domain, mem_ctx, user_sid,
+ num_groups, user_grpsids);
+
+ if (NT_STATUS_IS_OK(result)) {
+ return NT_STATUS_OK;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_usergroups: No incoming trust for domain %s\n",
+ domain->name));
+
+ /* Tell the cache manager not to remember this one */
+
+ return NT_STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+
+ /* no cache; hit the wire */
+
+ result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ /* Get user handle */
+ result = rpccli_samr_open_user(cli, mem_ctx, &dom_pol,
+ des_access, user_rid, &user_pol);
+
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ /* Query user rids */
+ result = rpccli_samr_query_usergroups(cli, mem_ctx, &user_pol,
+ num_groups, &user_groups);
+
+ rpccli_samr_close(cli, mem_ctx, &user_pol);
+
+ if (!NT_STATUS_IS_OK(result) || (*num_groups) == 0)
+ return result;
+
+ (*user_grpsids) = TALLOC_ARRAY(mem_ctx, DOM_SID, *num_groups);
+ if (!(*user_grpsids))
+ return NT_STATUS_NO_MEMORY;
+
+ for (i=0;i<(*num_groups);i++) {
+ sid_copy(&((*user_grpsids)[i]), &domain->sid);
+ sid_append_rid(&((*user_grpsids)[i]),
+ user_groups[i].g_rid);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS msrpc_lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 num_sids, const DOM_SID *sids,
+ uint32 *num_aliases, uint32 **alias_rids)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ POLICY_HND dom_pol;
+ DOM_SID2 *query_sids;
+ uint32 num_query_sids = 0;
+ int i;
+ struct rpc_pipe_client *cli;
+ uint32 *alias_rids_query, num_aliases_query;
+ int rangesize = MAX_SAM_ENTRIES_W2K;
+ uint32 total_sids = 0;
+ int num_queries = 1;
+
+ *num_aliases = 0;
+ *alias_rids = NULL;
+
+ DEBUG(3,("rpc: lookup_useraliases\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("msrpc_lookup_useraliases: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ do {
+ /* prepare query */
+
+ num_query_sids = MIN(num_sids - total_sids, rangesize);
+
+ DEBUG(10,("rpc: lookup_useraliases: entering query %d for %d sids\n",
+ num_queries, num_query_sids));
+
+ if (num_query_sids) {
+ query_sids = TALLOC_ARRAY(mem_ctx, DOM_SID2, num_query_sids);
+ if (query_sids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ query_sids = NULL;
+ }
+
+ for (i=0; i<num_query_sids; i++) {
+ sid_copy(&query_sids[i].sid, &sids[total_sids++]);
+ query_sids[i].num_auths = query_sids[i].sid.num_auths;
+ }
+
+ /* do request */
+
+ result = rpccli_samr_query_useraliases(cli, mem_ctx, &dom_pol,
+ num_query_sids, query_sids,
+ &num_aliases_query,
+ &alias_rids_query);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ *num_aliases = 0;
+ *alias_rids = NULL;
+ TALLOC_FREE(query_sids);
+ goto done;
+ }
+
+ /* process output */
+
+ for (i=0; i<num_aliases_query; i++) {
+ size_t na = *num_aliases;
+ if (!add_rid_to_array_unique(mem_ctx, alias_rids_query[i],
+ alias_rids, &na)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *num_aliases = na;
+ }
+
+ TALLOC_FREE(query_sids);
+
+ num_queries++;
+
+ } while (total_sids < num_sids);
+
+ done:
+ DEBUG(10,("rpc: lookup_useraliases: got %d aliases in %d queries "
+ "(rangesize: %d)\n", *num_aliases, num_queries, rangesize));
+
+ return result;
+}
+
+
+/* Lookup group membership given a rid. */
+static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *group_sid, uint32 *num_names,
+ DOM_SID **sid_mem, char ***names,
+ uint32 **name_types)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ uint32 i, total_names = 0;
+ POLICY_HND dom_pol, group_pol;
+ uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
+ uint32 *rid_mem = NULL;
+ uint32 group_rid;
+ unsigned int j;
+ fstring sid_string;
+ struct rpc_pipe_client *cli;
+ unsigned int orig_timeout;
+
+ DEBUG(10,("rpc: lookup_groupmem %s sid=%s\n", domain->name,
+ sid_to_string(sid_string, group_sid)));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_groupmem: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ if (!sid_peek_check_rid(&domain->sid, group_sid, &group_rid))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ *num_names = 0;
+
+ result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ result = rpccli_samr_open_group(cli, mem_ctx, &dom_pol,
+ des_access, group_rid, &group_pol);
+
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ /* Step #1: Get a list of user rids that are the members of the
+ group. */
+
+ /* This call can take a long time - allow the server to time out.
+ 35 seconds should do it. */
+
+ orig_timeout = cli_set_timeout(cli->cli, 35000);
+
+ result = rpccli_samr_query_groupmem(cli, mem_ctx,
+ &group_pol, num_names, &rid_mem,
+ name_types);
+
+ /* And restore our original timeout. */
+ cli_set_timeout(cli->cli, orig_timeout);
+
+ rpccli_samr_close(cli, mem_ctx, &group_pol);
+
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ if (!*num_names) {
+ names = NULL;
+ name_types = NULL;
+ sid_mem = NULL;
+ return NT_STATUS_OK;
+ }
+
+ /* Step #2: Convert list of rids into list of usernames. Do this
+ in bunches of ~1000 to avoid crashing NT4. It looks like there
+ is a buffer overflow or something like that lurking around
+ somewhere. */
+
+#define MAX_LOOKUP_RIDS 900
+
+ *names = TALLOC_ZERO_ARRAY(mem_ctx, char *, *num_names);
+ *name_types = TALLOC_ZERO_ARRAY(mem_ctx, uint32, *num_names);
+ *sid_mem = TALLOC_ZERO_ARRAY(mem_ctx, DOM_SID, *num_names);
+
+ for (j=0;j<(*num_names);j++)
+ sid_compose(&(*sid_mem)[j], &domain->sid, rid_mem[j]);
+
+ if (*num_names>0 && (!*names || !*name_types))
+ return NT_STATUS_NO_MEMORY;
+
+ for (i = 0; i < *num_names; i += MAX_LOOKUP_RIDS) {
+ int num_lookup_rids = MIN(*num_names - i, MAX_LOOKUP_RIDS);
+ uint32 tmp_num_names = 0;
+ char **tmp_names = NULL;
+ uint32 *tmp_types = NULL;
+
+ /* Lookup a chunk of rids */
+
+ result = rpccli_samr_lookup_rids(cli, mem_ctx,
+ &dom_pol,
+ num_lookup_rids,
+ &rid_mem[i],
+ &tmp_num_names,
+ &tmp_names, &tmp_types);
+
+ /* see if we have a real error (and yes the
+ STATUS_SOME_UNMAPPED is the one returned from 2k) */
+
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED))
+ return result;
+
+ /* Copy result into array. The talloc system will take
+ care of freeing the temporary arrays later on. */
+
+ memcpy(&(*names)[i], tmp_names, sizeof(char *) *
+ tmp_num_names);
+
+ memcpy(&(*name_types)[i], tmp_types, sizeof(uint32) *
+ tmp_num_names);
+
+ total_names += tmp_num_names;
+ }
+
+ *num_names = total_names;
+
+ return NT_STATUS_OK;
+}
+
+#ifdef HAVE_LDAP
+
+#include <ldap.h>
+
+static int get_ldap_seq(const char *server, int port, uint32 *seq)
+{
+ int ret = -1;
+ struct timeval to;
+ const char *attrs[] = {"highestCommittedUSN", NULL};
+ LDAPMessage *res = NULL;
+ char **values = NULL;
+ LDAP *ldp = NULL;
+
+ *seq = DOM_SEQUENCE_NONE;
+
+ /*
+ * Parameterised (5) second timeout on open. This is needed as the
+ * search timeout doesn't seem to apply to doing an open as well. JRA.
+ */
+
+ ldp = ldap_open_with_timeout(server, port, lp_ldap_timeout());
+ if (ldp == NULL)
+ return -1;
+
+ /* Timeout if no response within 20 seconds. */
+ to.tv_sec = 10;
+ to.tv_usec = 0;
+
+ if (ldap_search_st(ldp, "", LDAP_SCOPE_BASE, "(objectclass=*)",
+ CONST_DISCARD(char **, attrs), 0, &to, &res))
+ goto done;
+
+ if (ldap_count_entries(ldp, res) != 1)
+ goto done;
+
+ values = ldap_get_values(ldp, res, "highestCommittedUSN");
+ if (!values || !values[0])
+ goto done;
+
+ *seq = atoi(values[0]);
+ ret = 0;
+
+ done:
+
+ if (values)
+ ldap_value_free(values);
+ if (res)
+ ldap_msgfree(res);
+ if (ldp)
+ ldap_unbind(ldp);
+ return ret;
+}
+
+/**********************************************************************
+ Get the sequence number for a Windows AD native mode domain using
+ LDAP queries.
+**********************************************************************/
+
+static int get_ldap_sequence_number(struct winbindd_domain *domain, uint32 *seq)
+{
+ int ret = -1;
+ fstring ipstr;
+
+ fstrcpy( ipstr, inet_ntoa(domain->dcaddr.sin_addr));
+ if ((ret = get_ldap_seq( ipstr, LDAP_PORT, seq)) == 0) {
+ DEBUG(3, ("get_ldap_sequence_number: Retrieved sequence "
+ "number for Domain (%s) from DC (%s)\n",
+ domain->name, ipstr));
+ }
+ return ret;
+}
+
+#endif /* HAVE_LDAP */
+
+/* find the sequence number for a domain */
+static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq)
+{
+ TALLOC_CTX *mem_ctx;
+ SAM_UNK_CTR ctr;
+ NTSTATUS result;
+ POLICY_HND dom_pol;
+ BOOL got_seq_num = False;
+ struct rpc_pipe_client *cli;
+
+ DEBUG(10,("rpc: fetch sequence_number for %s\n", domain->name));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("sequence_number: No incoming trust for domain %s\n",
+ domain->name));
+ *seq = time(NULL);
+ return NT_STATUS_OK;
+ }
+
+ *seq = DOM_SEQUENCE_NONE;
+
+ if (!(mem_ctx = talloc_init("sequence_number[rpc]")))
+ return NT_STATUS_NO_MEMORY;
+
+#ifdef HAVE_LDAP
+ if ( domain->active_directory )
+ {
+ int res;
+
+ DEBUG(8,("using get_ldap_seq() to retrieve the "
+ "sequence number\n"));
+
+ res = get_ldap_sequence_number( domain, seq );
+ if (res == 0)
+ {
+ result = NT_STATUS_OK;
+ DEBUG(10,("domain_sequence_number: LDAP for "
+ "domain %s is %u\n",
+ domain->name, *seq));
+ goto done;
+ }
+
+ DEBUG(10,("domain_sequence_number: failed to get LDAP "
+ "sequence number for domain %s\n",
+ domain->name ));
+ }
+#endif /* HAVE_LDAP */
+
+ result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Query domain info */
+
+ result = rpccli_samr_query_dom_info(cli, mem_ctx, &dom_pol, 8, &ctr);
+
+ if (NT_STATUS_IS_OK(result)) {
+ *seq = ctr.info.inf8.seq_num;
+ got_seq_num = True;
+ goto seq_num;
+ }
+
+ /* retry with info-level 2 in case the dc does not support info-level 8
+ * (like all older samba2 and samba3 dc's - Guenther */
+
+ result = rpccli_samr_query_dom_info(cli, mem_ctx, &dom_pol, 2, &ctr);
+
+ if (NT_STATUS_IS_OK(result)) {
+ *seq = ctr.info.inf2.seq_num;
+ got_seq_num = True;
+ }
+
+ seq_num:
+ if (got_seq_num) {
+ DEBUG(10,("domain_sequence_number: for domain %s is %u\n",
+ domain->name, (unsigned)*seq));
+ } else {
+ DEBUG(10,("domain_sequence_number: failed to get sequence "
+ "number (%u) for domain %s\n",
+ (unsigned)*seq, domain->name ));
+ }
+
+ done:
+
+ talloc_destroy(mem_ctx);
+
+ return result;
+}
+
+/* get a list of trusted domains */
+static NTSTATUS trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32 *num_domains,
+ char ***names,
+ char ***alt_names,
+ DOM_SID **dom_sids)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ uint32 enum_ctx = 0;
+ struct rpc_pipe_client *cli;
+ POLICY_HND lsa_policy;
+
+ DEBUG(3,("rpc: trusted_domains\n"));
+
+ *num_domains = 0;
+ *names = NULL;
+ *alt_names = NULL;
+ *dom_sids = NULL;
+
+ result = cm_connect_lsa(domain, mem_ctx, &cli, &lsa_policy);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ result = STATUS_MORE_ENTRIES;
+
+ while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) {
+ uint32 start_idx, num;
+ char **tmp_names;
+ DOM_SID *tmp_sids;
+ int i;
+
+ result = rpccli_lsa_enum_trust_dom(cli, mem_ctx,
+ &lsa_policy, &enum_ctx,
+ &num, &tmp_names,
+ &tmp_sids);
+
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES))
+ break;
+
+ start_idx = *num_domains;
+ *num_domains += num;
+ *names = TALLOC_REALLOC_ARRAY(mem_ctx, *names,
+ char *, *num_domains);
+ *dom_sids = TALLOC_REALLOC_ARRAY(mem_ctx, *dom_sids,
+ DOM_SID, *num_domains);
+ *alt_names = TALLOC_REALLOC_ARRAY(mem_ctx, *alt_names,
+ char *, *num_domains);
+ if ((*names == NULL) || (*dom_sids == NULL) ||
+ (*alt_names == NULL))
+ return NT_STATUS_NO_MEMORY;
+
+ for (i=0; i<num; i++) {
+ (*names)[start_idx+i] = tmp_names[i];
+ (*dom_sids)[start_idx+i] = tmp_sids[i];
+ (*alt_names)[start_idx+i] = talloc_strdup(mem_ctx, "");
+ }
+ }
+ return result;
+}
+
+/* find the lockout policy for a domain */
+NTSTATUS msrpc_lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_12 *lockout_policy)
+{
+ NTSTATUS result;
+ struct rpc_pipe_client *cli;
+ POLICY_HND dom_pol;
+ SAM_UNK_CTR ctr;
+
+ DEBUG(10,("rpc: fetch lockout policy for %s\n", domain->name));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("msrpc_lockout_policy: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ result = rpccli_samr_query_dom_info(cli, mem_ctx, &dom_pol, 12, &ctr);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ *lockout_policy = ctr.info.inf12;
+
+ DEBUG(10,("msrpc_lockout_policy: bad_attempt_lockout %d\n",
+ ctr.info.inf12.bad_attempt_lockout));
+
+ done:
+
+ return result;
+}
+
+/* find the password policy for a domain */
+NTSTATUS msrpc_password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ SAM_UNK_INFO_1 *password_policy)
+{
+ NTSTATUS result;
+ struct rpc_pipe_client *cli;
+ POLICY_HND dom_pol;
+ SAM_UNK_CTR ctr;
+
+ DEBUG(10,("rpc: fetch password policy for %s\n", domain->name));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("msrpc_password_policy: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ result = rpccli_samr_query_dom_info(cli, mem_ctx, &dom_pol, 1, &ctr);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ *password_policy = ctr.info.inf1;
+
+ DEBUG(10,("msrpc_password_policy: min_length_password %d\n",
+ ctr.info.inf1.min_length_password));
+
+ done:
+
+ return result;
+}
+
+
+/* the rpc backend methods are exposed via this structure */
+struct winbindd_methods msrpc_methods = {
+ False,
+ query_user_list,
+ enum_dom_groups,
+ enum_local_groups,
+ msrpc_name_to_sid,
+ msrpc_sid_to_name,
+ msrpc_rids_to_names,
+ query_user,
+ lookup_usergroups,
+ msrpc_lookup_useraliases,
+ lookup_groupmem,
+ sequence_number,
+ msrpc_lockout_policy,
+ msrpc_password_policy,
+ trusted_domains,
+};
diff --git a/source3/winbindd/winbindd_sid.c b/source3/winbindd/winbindd_sid.c
new file mode 100644
index 0000000000..48e84d35e5
--- /dev/null
+++ b/source3/winbindd/winbindd_sid.c
@@ -0,0 +1,560 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - sid related functions
+
+ Copyright (C) Tim Potter 2000
+
+ 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 "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/* Convert a string */
+
+static void lookupsid_recv(void *private_data, BOOL success,
+ const char *dom_name, const char *name,
+ enum lsa_SidType type);
+
+void winbindd_lookupsid(struct winbindd_cli_state *state)
+{
+ DOM_SID sid;
+
+ /* Ensure null termination */
+ state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: lookupsid %s\n", (unsigned long)state->pid,
+ state->request.data.sid));
+
+ if (!string_to_sid(&sid, state->request.data.sid)) {
+ DEBUG(5, ("%s not a SID\n", state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ winbindd_lookupsid_async(state->mem_ctx, &sid, lookupsid_recv, state);
+}
+
+static void lookupsid_recv(void *private_data, BOOL success,
+ const char *dom_name, const char *name,
+ enum lsa_SidType type)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ DEBUG(5, ("lookupsid returned an error\n"));
+ request_error(state);
+ return;
+ }
+
+ fstrcpy(state->response.data.name.dom_name, dom_name);
+ fstrcpy(state->response.data.name.name, name);
+ state->response.data.name.type = type;
+ request_ok(state);
+}
+
+/**
+ * Look up the SID for a qualified name.
+ **/
+
+static void lookupname_recv(void *private_data, BOOL success,
+ const DOM_SID *sid, enum lsa_SidType type);
+
+void winbindd_lookupname(struct winbindd_cli_state *state)
+{
+ char *name_domain, *name_user;
+ char *p;
+
+ /* Ensure null termination */
+ state->request.data.name.dom_name[sizeof(state->request.data.name.dom_name)-1]='\0';
+
+ /* Ensure null termination */
+ state->request.data.name.name[sizeof(state->request.data.name.name)-1]='\0';
+
+ /* cope with the name being a fully qualified name */
+ p = strstr(state->request.data.name.name, lp_winbind_separator());
+ if (p) {
+ *p = 0;
+ name_domain = state->request.data.name.name;
+ name_user = p+1;
+ } else {
+ name_domain = state->request.data.name.dom_name;
+ name_user = state->request.data.name.name;
+ }
+
+ DEBUG(3, ("[%5lu]: lookupname %s%s%s\n", (unsigned long)state->pid,
+ name_domain, lp_winbind_separator(), name_user));
+
+ winbindd_lookupname_async(state->mem_ctx, name_domain, name_user,
+ lookupname_recv, WINBINDD_LOOKUPNAME,
+ state);
+}
+
+static void lookupname_recv(void *private_data, BOOL success,
+ const DOM_SID *sid, enum lsa_SidType type)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ DEBUG(5, ("lookupname returned an error\n"));
+ request_error(state);
+ return;
+ }
+
+ sid_to_string(state->response.data.sid.sid, sid);
+ state->response.data.sid.type = type;
+ request_ok(state);
+ return;
+}
+
+void winbindd_lookuprids(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+ DOM_SID domain_sid;
+
+ /* Ensure null termination */
+ state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
+
+ DEBUG(10, ("lookup_rids: %s\n", state->request.data.sid));
+
+ if (!string_to_sid(&domain_sid, state->request.data.sid)) {
+ DEBUG(5, ("Could not convert %s to SID\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ domain = find_lookup_domain_from_sid(&domain_sid);
+ if (domain == NULL) {
+ DEBUG(10, ("Could not find domain for name %s\n",
+ state->request.domain_name));
+ request_error(state);
+ return;
+ }
+
+ sendto_domain(state, domain);
+}
+
+static struct winbindd_child static_idmap_child;
+
+void init_idmap_child(void)
+{
+ setup_domain_child(NULL, &static_idmap_child, "idmap");
+}
+
+struct winbindd_child *idmap_child(void)
+{
+ return &static_idmap_child;
+}
+
+/* Convert a sid to a uid. We assume we only have one rid attached to the
+ sid. */
+
+static void sid2uid_recv(void *private_data, BOOL success, uid_t uid)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not convert sid %s\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ state->response.data.uid = uid;
+ request_ok(state);
+}
+
+static void sid2uid_lookupsid_recv( void *private_data, BOOL success,
+ const char *domain_name,
+ const char *name,
+ enum lsa_SidType type)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+ DOM_SID sid;
+
+ if (!success) {
+ DEBUG(5, ("sid2uid_lookupsid_recv Could not convert get sid type for %s\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ if ( (type!=SID_NAME_USER) && (type!=SID_NAME_COMPUTER) ) {
+ DEBUG(5,("sid2uid_lookupsid_recv: Sid %s is not a user or a computer.\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ if (!string_to_sid(&sid, state->request.data.sid)) {
+ DEBUG(1, ("sid2uid_lookupsid_recv: Could not get convert sid %s from string\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ /* always use the async interface (may block) */
+ winbindd_sid2uid_async(state->mem_ctx, &sid, sid2uid_recv, state);
+}
+
+void winbindd_sid_to_uid(struct winbindd_cli_state *state)
+{
+ DOM_SID sid;
+
+ /* Ensure null termination */
+ state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: sid to uid %s\n", (unsigned long)state->pid,
+ state->request.data.sid));
+
+ if (!string_to_sid(&sid, state->request.data.sid)) {
+ DEBUG(1, ("Could not get convert sid %s from string\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ /* Validate the SID as a user. Hopefully this will hit cache.
+ Needed to prevent DoS by exhausting the uid allocation
+ range from random SIDs. */
+
+ winbindd_lookupsid_async( state->mem_ctx, &sid, sid2uid_lookupsid_recv, state );
+}
+
+/* Convert a sid to a gid. We assume we only have one rid attached to the
+ sid.*/
+
+static void sid2gid_recv(void *private_data, BOOL success, gid_t gid)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not convert sid %s\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ state->response.data.gid = gid;
+ request_ok(state);
+}
+
+static void sid2gid_lookupsid_recv( void *private_data, BOOL success,
+ const char *domain_name,
+ const char *name,
+ enum lsa_SidType type)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+ DOM_SID sid;
+
+ if (!success) {
+ DEBUG(5, ("sid2gid_lookupsid_recv: Could not get sid type for %s\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ if ( (type!=SID_NAME_DOM_GRP) &&
+ (type!=SID_NAME_ALIAS) &&
+ (type!=SID_NAME_WKN_GRP) )
+ {
+ DEBUG(5,("sid2gid_lookupsid_recv: Sid %s is not a group.\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ if (!string_to_sid(&sid, state->request.data.sid)) {
+ DEBUG(1, ("sid2gid_lookupsid_recv: Could not get convert sid %s from string\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ /* always use the async interface (may block) */
+ winbindd_sid2gid_async(state->mem_ctx, &sid, sid2gid_recv, state);
+}
+
+void winbindd_sid_to_gid(struct winbindd_cli_state *state)
+{
+ DOM_SID sid;
+
+ /* Ensure null termination */
+ state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: sid to gid %s\n", (unsigned long)state->pid,
+ state->request.data.sid));
+
+ if (!string_to_sid(&sid, state->request.data.sid)) {
+ DEBUG(1, ("Could not get convert sid %s from string\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ /* Validate the SID as a group. Hopefully this will hit cache.
+ Needed to prevent DoS by exhausting the uid allocation
+ range from random SIDs. */
+
+ winbindd_lookupsid_async( state->mem_ctx, &sid, sid2gid_lookupsid_recv, state );
+}
+
+static void sids2xids_recv(void *private_data, BOOL success, void *data, int len)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not convert sids to xids\n"));
+ request_error(state);
+ return;
+ }
+
+ state->response.extra_data.data = data;
+ state->response.length = sizeof(state->response) + len;
+ request_ok(state);
+}
+
+void winbindd_sids_to_unixids(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: sids to xids\n", (unsigned long)state->pid));
+
+ winbindd_sids2xids_async(state->mem_ctx,
+ state->request.extra_data.data,
+ state->request.extra_len,
+ sids2xids_recv, state);
+}
+
+static void set_mapping_recv(void *private_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not set sid mapping\n"));
+ request_error(state);
+ return;
+ }
+
+ request_ok(state);
+}
+
+void winbindd_set_mapping(struct winbindd_cli_state *state)
+{
+ struct id_map map;
+ DOM_SID sid;
+
+ DEBUG(3, ("[%5lu]: set id map\n", (unsigned long)state->pid));
+
+ if ( ! state->privileged) {
+ DEBUG(0, ("Only root is allowed to set mappings!\n"));
+ request_error(state);
+ return;
+ }
+
+ if (!string_to_sid(&sid, state->request.data.dual_idmapset.sid)) {
+ DEBUG(1, ("Could not get convert sid %s from string\n",
+ state->request.data.sid));
+ request_error(state);
+ return;
+ }
+
+ map.sid = &sid;
+ map.xid.id = state->request.data.dual_idmapset.id;
+ map.xid.type = state->request.data.dual_idmapset.type;
+
+ winbindd_set_mapping_async(state->mem_ctx, &map,
+ set_mapping_recv, state);
+}
+
+static void set_hwm_recv(void *private_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not set sid mapping\n"));
+ request_error(state);
+ return;
+ }
+
+ request_ok(state);
+}
+
+void winbindd_set_hwm(struct winbindd_cli_state *state)
+{
+ struct unixid xid;
+
+ DEBUG(3, ("[%5lu]: set hwm\n", (unsigned long)state->pid));
+
+ if ( ! state->privileged) {
+ DEBUG(0, ("Only root is allowed to set mappings!\n"));
+ request_error(state);
+ return;
+ }
+
+ xid.id = state->request.data.dual_idmapset.id;
+ xid.type = state->request.data.dual_idmapset.type;
+
+ winbindd_set_hwm_async(state->mem_ctx, &xid, set_hwm_recv, state);
+}
+
+/* Convert a uid to a sid */
+
+static void uid2sid_recv(void *private_data, BOOL success, const char *sid)
+{
+ struct winbindd_cli_state *state =
+ (struct winbindd_cli_state *)private_data;
+
+ if (success) {
+ DEBUG(10,("uid2sid: uid %lu has sid %s\n",
+ (unsigned long)(state->request.data.uid), sid));
+ fstrcpy(state->response.data.sid.sid, sid);
+ state->response.data.sid.type = SID_NAME_USER;
+ request_ok(state);
+ return;
+ }
+
+ request_error(state);
+ return;
+}
+
+void winbindd_uid_to_sid(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: uid to sid %lu\n", (unsigned long)state->pid,
+ (unsigned long)state->request.data.uid));
+
+ /* always go via the async interface (may block) */
+ winbindd_uid2sid_async(state->mem_ctx, state->request.data.uid, uid2sid_recv, state);
+}
+
+/* Convert a gid to a sid */
+
+static void gid2sid_recv(void *private_data, BOOL success, const char *sid)
+{
+ struct winbindd_cli_state *state =
+ (struct winbindd_cli_state *)private_data;
+
+ if (success) {
+ DEBUG(10,("gid2sid: gid %lu has sid %s\n",
+ (unsigned long)(state->request.data.gid), sid));
+ fstrcpy(state->response.data.sid.sid, sid);
+ state->response.data.sid.type = SID_NAME_DOM_GRP;
+ request_ok(state);
+ return;
+ }
+
+ request_error(state);
+ return;
+}
+
+
+void winbindd_gid_to_sid(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: gid to sid %lu\n", (unsigned long)state->pid,
+ (unsigned long)state->request.data.gid));
+
+ /* always use async calls (may block) */
+ winbindd_gid2sid_async(state->mem_ctx, state->request.data.gid, gid2sid_recv, state);
+}
+
+void winbindd_allocate_uid(struct winbindd_cli_state *state)
+{
+ if ( !state->privileged ) {
+ DEBUG(2, ("winbindd_allocate_uid: non-privileged access "
+ "denied!\n"));
+ request_error(state);
+ return;
+ }
+
+ sendto_child(state, idmap_child());
+}
+
+enum winbindd_result winbindd_dual_allocate_uid(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct unixid xid;
+
+ if (!NT_STATUS_IS_OK(idmap_allocate_uid(&xid))) {
+ return WINBINDD_ERROR;
+ }
+ state->response.data.uid = xid.id;
+ return WINBINDD_OK;
+}
+
+void winbindd_allocate_gid(struct winbindd_cli_state *state)
+{
+ if ( !state->privileged ) {
+ DEBUG(2, ("winbindd_allocate_gid: non-privileged access "
+ "denied!\n"));
+ request_error(state);
+ return;
+ }
+
+ sendto_child(state, idmap_child());
+}
+
+enum winbindd_result winbindd_dual_allocate_gid(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct unixid xid;
+
+ if (!NT_STATUS_IS_OK(idmap_allocate_gid(&xid))) {
+ return WINBINDD_ERROR;
+ }
+ state->response.data.gid = xid.id;
+ return WINBINDD_OK;
+}
+
+static void dump_maps_recv(void *private_data, BOOL success)
+{
+ struct winbindd_cli_state *state =
+ talloc_get_type_abort(private_data, struct winbindd_cli_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not dump maps\n"));
+ request_error(state);
+ return;
+ }
+
+ request_ok(state);
+}
+
+void winbindd_dump_maps(struct winbindd_cli_state *state)
+{
+ if ( ! state->privileged) {
+ DEBUG(0, ("Only root is allowed to ask for an idmap dump!\n"));
+ request_error(state);
+ return;
+ }
+
+ DEBUG(3, ("[%5lu]: dump maps\n", (unsigned long)state->pid));
+
+ winbindd_dump_maps_async(state->mem_ctx,
+ state->request.extra_data.data,
+ state->request.extra_len,
+ dump_maps_recv, state);
+}
+
diff --git a/source3/winbindd/winbindd_sockinit.c b/source3/winbindd/winbindd_sockinit.c
new file mode 100644
index 0000000000..50c53a5045
--- /dev/null
+++ b/source3/winbindd/winbindd_sockinit.c
@@ -0,0 +1,126 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Tim Potter 2000-2001
+ Copyright (C) 2001 by Martin Pool <mbp@samba.org>
+ Copyright (C) James Peach 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 "winbindd.h"
+#include "smb_launchd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/* Open the winbindd socket */
+
+static int _winbindd_socket = -1;
+static int _winbindd_priv_socket = -1;
+static BOOL unlink_winbindd_socket = True;
+
+static int open_winbindd_socket(void)
+{
+ if (_winbindd_socket == -1) {
+ _winbindd_socket = create_pipe_sock(
+ WINBINDD_SOCKET_DIR, WINBINDD_SOCKET_NAME, 0755);
+ DEBUG(10, ("open_winbindd_socket: opened socket fd %d\n",
+ _winbindd_socket));
+ }
+
+ return _winbindd_socket;
+}
+
+static int open_winbindd_priv_socket(void)
+{
+ if (_winbindd_priv_socket == -1) {
+ _winbindd_priv_socket = create_pipe_sock(
+ get_winbind_priv_pipe_dir(), WINBINDD_SOCKET_NAME, 0750);
+ DEBUG(10, ("open_winbindd_priv_socket: opened socket fd %d\n",
+ _winbindd_priv_socket));
+ }
+
+ return _winbindd_priv_socket;
+}
+
+/* Close the winbindd socket */
+
+static void close_winbindd_socket(void)
+{
+ if (_winbindd_socket != -1) {
+ DEBUG(10, ("close_winbindd_socket: closing socket fd %d\n",
+ _winbindd_socket));
+ close(_winbindd_socket);
+ _winbindd_socket = -1;
+ }
+ if (_winbindd_priv_socket != -1) {
+ DEBUG(10, ("close_winbindd_socket: closing socket fd %d\n",
+ _winbindd_priv_socket));
+ close(_winbindd_priv_socket);
+ _winbindd_priv_socket = -1;
+ }
+}
+
+BOOL winbindd_init_sockets(int *public_sock, int *priv_sock,
+ int *idle_timeout_sec)
+{
+ struct smb_launch_info linfo;
+
+ if (smb_launchd_checkin_names(&linfo, "WinbindPublicPipe",
+ "WinbindPrivilegedPipe", NULL)) {
+ if (linfo.num_sockets != 2) {
+ DEBUG(0, ("invalid launchd configuration, "
+ "expected 2 sockets but got %d\n",
+ linfo.num_sockets));
+ return False;
+ }
+
+ *public_sock = _winbindd_socket = linfo.socket_list[0];
+ *priv_sock = _winbindd_priv_socket = linfo.socket_list[1];
+ *idle_timeout_sec = linfo.idle_timeout_secs;
+
+ unlink_winbindd_socket = False;
+
+ smb_launchd_checkout(&linfo);
+ return True;
+ } else {
+ *public_sock = open_winbindd_socket();
+ *priv_sock = open_winbindd_priv_socket();
+ *idle_timeout_sec = -1;
+
+ if (*public_sock == -1 || *priv_sock == -1) {
+ DEBUG(0, ("failed to open winbindd pipes: %s\n",
+ errno ? strerror(errno) : "unknown error"));
+ return False;
+ }
+
+ return True;
+ }
+}
+
+void winbindd_release_sockets(void)
+{
+ pstring path;
+
+ close_winbindd_socket();
+
+ /* Remove socket file */
+ if (unlink_winbindd_socket) {
+ pstr_sprintf(path, "%s/%s",
+ WINBINDD_SOCKET_DIR, WINBINDD_SOCKET_NAME);
+ unlink(path);
+ }
+}
+
diff --git a/source3/winbindd/winbindd_user.c b/source3/winbindd/winbindd_user.c
new file mode 100644
index 0000000000..fac2832f56
--- /dev/null
+++ b/source3/winbindd/winbindd_user.c
@@ -0,0 +1,875 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - user related functions
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jeremy Allison 2001.
+ Copyright (C) Gerald (Jerry) Carter 2003.
+
+ 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 "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static BOOL fillup_pw_field(const char *lp_template,
+ const char *username,
+ const char *domname,
+ uid_t uid,
+ gid_t gid,
+ const char *in,
+ fstring out)
+{
+ char *templ;
+
+ if (out == NULL)
+ return False;
+
+ /* The substitution of %U and %D in the 'template
+ homedir' is done by talloc_sub_specified() below.
+ If we have an in string (which means the value has already
+ been set in the nss_info backend), then use that.
+ Otherwise use the template value passed in. */
+
+ if ( in && !strequal(in,"") && lp_security() == SEC_ADS ) {
+ templ = talloc_sub_specified(NULL, in,
+ username, domname,
+ uid, gid);
+ } else {
+ templ = talloc_sub_specified(NULL, lp_template,
+ username, domname,
+ uid, gid);
+ }
+
+ if (!templ)
+ return False;
+
+ safe_strcpy(out, templ, sizeof(fstring) - 1);
+ TALLOC_FREE(templ);
+
+ return True;
+
+}
+/* Fill a pwent structure with information we have obtained */
+
+static BOOL winbindd_fill_pwent(char *dom_name, char *user_name,
+ DOM_SID *user_sid, DOM_SID *group_sid,
+ char *full_name, char *homedir, char *shell,
+ struct winbindd_pw *pw)
+{
+ fstring output_username;
+ fstring sid_string;
+
+ if (!pw || !dom_name || !user_name)
+ return False;
+
+ /* Resolve the uid number */
+
+ if (!NT_STATUS_IS_OK(idmap_sid_to_uid(user_sid, &pw->pw_uid))) {
+ DEBUG(1, ("error getting user id for sid %s\n", sid_to_string(sid_string, user_sid)));
+ return False;
+ }
+
+ /* Resolve the gid number */
+
+ if (!NT_STATUS_IS_OK(idmap_sid_to_gid(group_sid, &pw->pw_gid))) {
+ DEBUG(1, ("error getting group id for sid %s\n", sid_to_string(sid_string, group_sid)));
+ return False;
+ }
+
+ strlower_m(user_name);
+
+ /* Username */
+
+ fill_domain_username(output_username, dom_name, user_name, True);
+
+ safe_strcpy(pw->pw_name, output_username, sizeof(pw->pw_name) - 1);
+
+ /* Full name (gecos) */
+
+ safe_strcpy(pw->pw_gecos, full_name, sizeof(pw->pw_gecos) - 1);
+
+ /* Home directory and shell */
+
+ if (!fillup_pw_field(lp_template_homedir(), user_name, dom_name,
+ pw->pw_uid, pw->pw_gid, homedir, pw->pw_dir))
+ return False;
+
+ if (!fillup_pw_field(lp_template_shell(), user_name, dom_name,
+ pw->pw_uid, pw->pw_gid, shell, pw->pw_shell))
+ return False;
+
+ /* Password - set to "*" as we can't generate anything useful here.
+ Authentication can be done using the pam_winbind module. */
+
+ safe_strcpy(pw->pw_passwd, "*", sizeof(pw->pw_passwd) - 1);
+
+ return True;
+}
+
+/* Wrapper for domain->methods->query_user, only on the parent->child pipe */
+
+enum winbindd_result winbindd_dual_userinfo(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ DOM_SID sid;
+ WINBIND_USERINFO user_info;
+ NTSTATUS status;
+
+ /* Ensure null termination */
+ state->request.data.sid[sizeof(state->request.data.sid)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: lookupsid %s\n", (unsigned long)state->pid,
+ state->request.data.sid));
+
+ if (!string_to_sid(&sid, state->request.data.sid)) {
+ DEBUG(5, ("%s not a SID\n", state->request.data.sid));
+ return WINBINDD_ERROR;
+ }
+
+ status = domain->methods->query_user(domain, state->mem_ctx,
+ &sid, &user_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("error getting user info for sid %s\n",
+ sid_string_static(&sid)));
+ return WINBINDD_ERROR;
+ }
+
+ fstrcpy(state->response.data.user_info.acct_name, user_info.acct_name);
+ fstrcpy(state->response.data.user_info.full_name, user_info.full_name);
+ fstrcpy(state->response.data.user_info.homedir, user_info.homedir);
+ fstrcpy(state->response.data.user_info.shell, user_info.shell);
+ state->response.data.user_info.primary_gid = user_info.primary_gid;
+ if (!sid_peek_check_rid(&domain->sid, &user_info.group_sid,
+ &state->response.data.user_info.group_rid)) {
+ DEBUG(1, ("Could not extract group rid out of %s\n",
+ sid_string_static(&sid)));
+ return WINBINDD_ERROR;
+ }
+
+ return WINBINDD_OK;
+}
+
+struct getpwsid_state {
+ struct winbindd_cli_state *state;
+ struct winbindd_domain *domain;
+ char *username;
+ char *fullname;
+ char *homedir;
+ char *shell;
+ DOM_SID user_sid;
+ uid_t uid;
+ DOM_SID group_sid;
+ gid_t gid;
+};
+
+static void getpwsid_queryuser_recv(void *private_data, BOOL success,
+ const char *acct_name,
+ const char *full_name,
+ const char *homedir,
+ const char *shell,
+ uint32 gid,
+ uint32 group_rid);
+static void getpwsid_sid2uid_recv(void *private_data, BOOL success, uid_t uid);
+static void getpwsid_sid2gid_recv(void *private_data, BOOL success, gid_t gid);
+
+static void winbindd_getpwsid(struct winbindd_cli_state *state,
+ const DOM_SID *sid)
+{
+ struct getpwsid_state *s;
+
+ s = TALLOC_ZERO_P(state->mem_ctx, struct getpwsid_state);
+ if (s == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ goto error;
+ }
+
+ s->state = state;
+ s->domain = find_domain_from_sid_noinit(sid);
+ if (s->domain == NULL) {
+ DEBUG(3, ("Could not find domain for sid %s\n",
+ sid_string_static(sid)));
+ goto error;
+ }
+
+ sid_copy(&s->user_sid, sid);
+
+ query_user_async(s->state->mem_ctx, s->domain, sid,
+ getpwsid_queryuser_recv, s);
+ return;
+
+ error:
+ request_error(state);
+}
+
+static void getpwsid_queryuser_recv(void *private_data, BOOL success,
+ const char *acct_name,
+ const char *full_name,
+ const char *homedir,
+ const char *shell,
+ uint32 gid,
+ uint32 group_rid)
+{
+ fstring username;
+ struct getpwsid_state *s =
+ talloc_get_type_abort(private_data, struct getpwsid_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not query domain %s SID %s\n", s->domain->name,
+ sid_string_static(&s->user_sid)));
+ request_error(s->state);
+ return;
+ }
+
+ if ( acct_name && *acct_name ) {
+ fstrcpy( username, acct_name );
+ } else {
+ char *domain_name = NULL;
+ enum lsa_SidType type;
+ char *user_name = NULL;
+ struct winbindd_domain *domain = NULL;
+
+ domain = find_lookup_domain_from_sid(&s->user_sid);
+ if (domain == NULL) {
+ DEBUG(5, ("find_lookup_domain_from_sid(%s) failed\n",
+ sid_string_static(&s->user_sid)));
+ request_error(s->state);
+ return;
+ }
+ winbindd_lookup_name_by_sid(s->state->mem_ctx, domain,
+ &s->user_sid, &domain_name,
+ &user_name, &type );
+
+ /* If this still fails we ar4e done. Just error out */
+ if ( !user_name ) {
+ DEBUG(5,("Could not obtain a name for SID %s\n",
+ sid_string_static(&s->user_sid)));
+ request_error(s->state);
+ return;
+ }
+
+ fstrcpy( username, user_name );
+ }
+
+ strlower_m( username );
+ s->username = talloc_strdup(s->state->mem_ctx, username);
+
+ ws_name_replace( s->username, WB_REPLACE_CHAR );
+
+ s->fullname = talloc_strdup(s->state->mem_ctx, full_name);
+ s->homedir = talloc_strdup(s->state->mem_ctx, homedir);
+ s->shell = talloc_strdup(s->state->mem_ctx, shell);
+ s->gid = gid;
+ sid_copy(&s->group_sid, &s->domain->sid);
+ sid_append_rid(&s->group_sid, group_rid);
+
+ winbindd_sid2uid_async(s->state->mem_ctx, &s->user_sid,
+ getpwsid_sid2uid_recv, s);
+}
+
+static void getpwsid_sid2uid_recv(void *private_data, BOOL success, uid_t uid)
+{
+ struct getpwsid_state *s =
+ talloc_get_type_abort(private_data, struct getpwsid_state);
+
+ if (!success) {
+ DEBUG(5, ("Could not query uid for user %s\\%s\n",
+ s->domain->name, s->username));
+ request_error(s->state);
+ return;
+ }
+
+ s->uid = uid;
+ winbindd_sid2gid_async(s->state->mem_ctx, &s->group_sid,
+ getpwsid_sid2gid_recv, s);
+}
+
+static void getpwsid_sid2gid_recv(void *private_data, BOOL success, gid_t gid)
+{
+ struct getpwsid_state *s =
+ talloc_get_type_abort(private_data, struct getpwsid_state);
+ struct winbindd_pw *pw;
+ fstring output_username;
+
+ /* allow the nss backend to override the primary group ID.
+ If the gid has already been set, then keep it.
+ This makes me feel dirty. If the nss backend already
+ gave us a gid, we don't really care whether the sid2gid()
+ call worked or not. --jerry */
+
+ if ( s->gid == (gid_t)-1 ) {
+
+ if (!success) {
+ DEBUG(5, ("Could not query gid for user %s\\%s\n",
+ s->domain->name, s->username));
+ goto failed;
+ }
+
+ /* take what the sid2gid() call gave us */
+ s->gid = gid;
+ }
+
+ pw = &s->state->response.data.pw;
+ pw->pw_uid = s->uid;
+ pw->pw_gid = s->gid;
+ fill_domain_username(output_username, s->domain->name, s->username, True);
+ safe_strcpy(pw->pw_name, output_username, sizeof(pw->pw_name) - 1);
+ safe_strcpy(pw->pw_gecos, s->fullname, sizeof(pw->pw_gecos) - 1);
+
+ if (!fillup_pw_field(lp_template_homedir(), s->username, s->domain->name,
+ pw->pw_uid, pw->pw_gid, s->homedir, pw->pw_dir)) {
+ DEBUG(5, ("Could not compose homedir\n"));
+ goto failed;
+ }
+
+ if (!fillup_pw_field(lp_template_shell(), s->username, s->domain->name,
+ pw->pw_uid, pw->pw_gid, s->shell, pw->pw_shell)) {
+ DEBUG(5, ("Could not compose shell\n"));
+ goto failed;
+ }
+
+ /* Password - set to "*" as we can't generate anything useful here.
+ Authentication can be done using the pam_winbind module. */
+
+ safe_strcpy(pw->pw_passwd, "*", sizeof(pw->pw_passwd) - 1);
+
+ request_ok(s->state);
+ return;
+
+ failed:
+ request_error(s->state);
+}
+
+/* Return a password structure from a username. */
+
+static void getpwnam_name2sid_recv(void *private_data, BOOL success,
+ const DOM_SID *sid, enum lsa_SidType type);
+
+void winbindd_getpwnam(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+ fstring domname, username;
+
+ /* Ensure null termination */
+ state->request.data.username[sizeof(state->request.data.username)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: getpwnam %s\n", (unsigned long)state->pid,
+ state->request.data.username));
+
+ ws_name_return( state->request.data.username, WB_REPLACE_CHAR );
+
+ if (!parse_domain_user(state->request.data.username, domname,
+ username)) {
+ DEBUG(5, ("Could not parse domain user: %s\n",
+ state->request.data.username));
+ request_error(state);
+ return;
+ }
+
+ /* Get info for the domain */
+
+ domain = find_domain_from_name(domname);
+
+ if (domain == NULL) {
+ DEBUG(7, ("could not find domain entry for domain %s. "
+ "Using primary domain\n", domname));
+ if ( (domain = find_our_domain()) == NULL ) {
+ DEBUG(0,("Cannot find my primary domain structure!\n"));
+ request_error(state);
+ return;
+ }
+ }
+
+ if ( strequal(domname, lp_workgroup()) && lp_winbind_trusted_domains_only() ) {
+ DEBUG(7,("winbindd_getpwnam: My domain -- rejecting getpwnam() for %s\\%s.\n",
+ domname, username));
+ request_error(state);
+ return;
+ }
+
+ /* Get rid and name type from name. The following costs 1 packet */
+
+ winbindd_lookupname_async(state->mem_ctx, domname, username,
+ getpwnam_name2sid_recv, WINBINDD_GETPWNAM,
+ state);
+}
+
+static void getpwnam_name2sid_recv(void *private_data, BOOL success,
+ const DOM_SID *sid, enum lsa_SidType type)
+{
+ struct winbindd_cli_state *state =
+ (struct winbindd_cli_state *)private_data;
+ fstring domname, username;
+
+ if (!success) {
+ DEBUG(5, ("Could not lookup name for user %s\n",
+ state->request.data.username));
+ request_error(state);
+ return;
+ }
+
+ if ((type != SID_NAME_USER) && (type != SID_NAME_COMPUTER)) {
+ DEBUG(5, ("%s is not a user\n", state->request.data.username));
+ request_error(state);
+ return;
+ }
+
+ if ( parse_domain_user(state->request.data.username, domname, username) ) {
+ check_domain_trusted( domname, sid );
+ }
+
+
+
+ winbindd_getpwsid(state, sid);
+}
+
+static void getpwuid_recv(void *private_data, BOOL success, const char *sid)
+{
+ struct winbindd_cli_state *state =
+ (struct winbindd_cli_state *)private_data;
+ DOM_SID user_sid;
+
+ if (!success) {
+ DEBUG(10,("uid2sid_recv: uid [%lu] to sid mapping failed\n.",
+ (unsigned long)(state->request.data.uid)));
+ request_error(state);
+ return;
+ }
+
+ DEBUG(10,("uid2sid_recv: uid %lu has sid %s\n",
+ (unsigned long)(state->request.data.uid), sid));
+
+ string_to_sid(&user_sid, sid);
+ winbindd_getpwsid(state, &user_sid);
+}
+
+/* Return a password structure given a uid number */
+void winbindd_getpwuid(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: getpwuid %lu\n", (unsigned long)state->pid,
+ (unsigned long)state->request.data.uid));
+
+ /* always query idmap via the async interface */
+ /* if this turns to be too slow we will add here a direct query to the cache */
+ winbindd_uid2sid_async(state->mem_ctx, state->request.data.uid, getpwuid_recv, state);
+}
+
+/*
+ * set/get/endpwent functions
+ */
+
+/* Rewind file pointer for ntdom passwd database */
+
+static BOOL winbindd_setpwent_internal(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+
+ DEBUG(3, ("[%5lu]: setpwent\n", (unsigned long)state->pid));
+
+ /* Check user has enabled this */
+
+ if (!lp_winbind_enum_users()) {
+ return False;
+ }
+
+ /* Free old static data if it exists */
+
+ if (state->getpwent_state != NULL) {
+ free_getent_state(state->getpwent_state);
+ state->getpwent_state = NULL;
+ }
+
+#if 0 /* JERRY */
+ /* add any local users we have */
+
+ if ( (domain_state = (struct getent_state *)malloc(sizeof(struct getent_state))) == NULL )
+ return False;
+
+ ZERO_STRUCTP(domain_state);
+
+ /* Add to list of open domains */
+
+ DLIST_ADD(state->getpwent_state, domain_state);
+#endif
+
+ /* Create sam pipes for each domain we know about */
+
+ for(domain = domain_list(); domain != NULL; domain = domain->next) {
+ struct getent_state *domain_state;
+
+
+ /* don't add our domaina if we are a PDC or if we
+ are a member of a Samba domain */
+
+ if ( (IS_DC || lp_winbind_trusted_domains_only())
+ && strequal(domain->name, lp_workgroup()) )
+ {
+ continue;
+ }
+
+ /* Create a state record for this domain */
+
+ if ((domain_state = SMB_MALLOC_P(struct getent_state)) == NULL) {
+ DEBUG(0, ("malloc failed\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(domain_state);
+
+ fstrcpy(domain_state->domain_name, domain->name);
+
+ /* Add to list of open domains */
+
+ DLIST_ADD(state->getpwent_state, domain_state);
+ }
+
+ state->getpwent_initialized = True;
+ return True;
+}
+
+void winbindd_setpwent(struct winbindd_cli_state *state)
+{
+ if (winbindd_setpwent_internal(state)) {
+ request_ok(state);
+ } else {
+ request_error(state);
+ }
+}
+
+/* Close file pointer to ntdom passwd database */
+
+void winbindd_endpwent(struct winbindd_cli_state *state)
+{
+ DEBUG(3, ("[%5lu]: endpwent\n", (unsigned long)state->pid));
+
+ free_getent_state(state->getpwent_state);
+ state->getpwent_initialized = False;
+ state->getpwent_state = NULL;
+ request_ok(state);
+}
+
+/* Get partial list of domain users for a domain. We fill in the sam_entries,
+ and num_sam_entries fields with domain user information. The dispinfo_ndx
+ field is incremented to the index of the next user to fetch. Return True if
+ some users were returned, False otherwise. */
+
+static BOOL get_sam_user_entries(struct getent_state *ent, TALLOC_CTX *mem_ctx)
+{
+ NTSTATUS status;
+ uint32 num_entries;
+ WINBIND_USERINFO *info;
+ struct getpwent_user *name_list = NULL;
+ struct winbindd_domain *domain;
+ struct winbindd_methods *methods;
+ unsigned int i;
+
+ if (ent->num_sam_entries)
+ return False;
+
+ if (!(domain = find_domain_from_name(ent->domain_name))) {
+ DEBUG(3, ("no such domain %s in get_sam_user_entries\n",
+ ent->domain_name));
+ return False;
+ }
+
+ methods = domain->methods;
+
+ /* Free any existing user info */
+
+ SAFE_FREE(ent->sam_entries);
+ ent->num_sam_entries = 0;
+
+ /* Call query_user_list to get a list of usernames and user rids */
+
+ num_entries = 0;
+
+ status = methods->query_user_list(domain, mem_ctx, &num_entries,
+ &info);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("get_sam_user_entries: query_user_list failed with %s\n",
+ nt_errstr(status) ));
+ return False;
+ }
+
+ if (num_entries) {
+ name_list = SMB_REALLOC_ARRAY(name_list, struct getpwent_user, ent->num_sam_entries + num_entries);
+
+ if (!name_list) {
+ DEBUG(0,("get_sam_user_entries realloc failed.\n"));
+ return False;
+ }
+ }
+
+ for (i = 0; i < num_entries; i++) {
+ /* Store account name and gecos */
+ if (!info[i].acct_name) {
+ fstrcpy(name_list[ent->num_sam_entries + i].name, "");
+ } else {
+ fstrcpy(name_list[ent->num_sam_entries + i].name,
+ info[i].acct_name);
+ }
+ if (!info[i].full_name) {
+ fstrcpy(name_list[ent->num_sam_entries + i].gecos, "");
+ } else {
+ fstrcpy(name_list[ent->num_sam_entries + i].gecos,
+ info[i].full_name);
+ }
+ if (!info[i].homedir) {
+ fstrcpy(name_list[ent->num_sam_entries + i].homedir, "");
+ } else {
+ fstrcpy(name_list[ent->num_sam_entries + i].homedir,
+ info[i].homedir);
+ }
+ if (!info[i].shell) {
+ fstrcpy(name_list[ent->num_sam_entries + i].shell, "");
+ } else {
+ fstrcpy(name_list[ent->num_sam_entries + i].shell,
+ info[i].shell);
+ }
+
+
+ /* User and group ids */
+ sid_copy(&name_list[ent->num_sam_entries+i].user_sid,
+ &info[i].user_sid);
+ sid_copy(&name_list[ent->num_sam_entries+i].group_sid,
+ &info[i].group_sid);
+ }
+
+ ent->num_sam_entries += num_entries;
+
+ /* Fill in remaining fields */
+
+ ent->sam_entries = name_list;
+ ent->sam_entry_index = 0;
+ return ent->num_sam_entries > 0;
+}
+
+/* Fetch next passwd entry from ntdom database */
+
+#define MAX_GETPWENT_USERS 500
+
+void winbindd_getpwent(struct winbindd_cli_state *state)
+{
+ struct getent_state *ent;
+ struct winbindd_pw *user_list;
+ int num_users, user_list_ndx;
+
+ DEBUG(3, ("[%5lu]: getpwent\n", (unsigned long)state->pid));
+
+ /* Check user has enabled this */
+
+ if (!lp_winbind_enum_users()) {
+ request_error(state);
+ return;
+ }
+
+ /* Allocate space for returning a chunk of users */
+
+ num_users = MIN(MAX_GETPWENT_USERS, state->request.data.num_entries);
+
+ if (num_users == 0) {
+ request_error(state);
+ return;
+ }
+
+ if ((state->response.extra_data.data = SMB_MALLOC_ARRAY(struct winbindd_pw, num_users)) == NULL) {
+ request_error(state);
+ return;
+ }
+
+ memset(state->response.extra_data.data, 0, num_users *
+ sizeof(struct winbindd_pw));
+
+ user_list = (struct winbindd_pw *)state->response.extra_data.data;
+
+ if (!state->getpwent_initialized)
+ winbindd_setpwent_internal(state);
+
+ if (!(ent = state->getpwent_state)) {
+ request_error(state);
+ return;
+ }
+
+ /* Start sending back users */
+
+ for (user_list_ndx = 0; user_list_ndx < num_users; ) {
+ struct getpwent_user *name_list = NULL;
+ uint32 result;
+
+ /* Do we need to fetch another chunk of users? */
+
+ if (ent->num_sam_entries == ent->sam_entry_index) {
+
+ while(ent &&
+ !get_sam_user_entries(ent, state->mem_ctx)) {
+ struct getent_state *next_ent;
+
+ /* Free state information for this domain */
+
+ SAFE_FREE(ent->sam_entries);
+
+ next_ent = ent->next;
+ DLIST_REMOVE(state->getpwent_state, ent);
+
+ SAFE_FREE(ent);
+ ent = next_ent;
+ }
+
+ /* No more domains */
+
+ if (!ent)
+ break;
+ }
+
+ name_list = (struct getpwent_user *)ent->sam_entries;
+
+ /* Lookup user info */
+
+ result = winbindd_fill_pwent(
+ ent->domain_name,
+ name_list[ent->sam_entry_index].name,
+ &name_list[ent->sam_entry_index].user_sid,
+ &name_list[ent->sam_entry_index].group_sid,
+ name_list[ent->sam_entry_index].gecos,
+ name_list[ent->sam_entry_index].homedir,
+ name_list[ent->sam_entry_index].shell,
+ &user_list[user_list_ndx]);
+
+ /* Add user to return list */
+
+ if (result) {
+
+ user_list_ndx++;
+ state->response.data.num_entries++;
+ state->response.length +=
+ sizeof(struct winbindd_pw);
+
+ } else
+ DEBUG(1, ("could not lookup domain user %s\n",
+ name_list[ent->sam_entry_index].name));
+
+ ent->sam_entry_index++;
+
+ }
+
+ /* Out of domains */
+
+ if (user_list_ndx > 0)
+ request_ok(state);
+ else
+ request_error(state);
+}
+
+/* List domain users without mapping to unix ids */
+
+void winbindd_list_users(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+ WINBIND_USERINFO *info;
+ const char *which_domain;
+ uint32 num_entries = 0, total_entries = 0;
+ char *extra_data = NULL;
+ int extra_data_len = 0;
+ enum winbindd_result rv = WINBINDD_ERROR;
+
+ DEBUG(3, ("[%5lu]: list users\n", (unsigned long)state->pid));
+
+ /* Ensure null termination */
+ state->request.domain_name[sizeof(state->request.domain_name)-1]='\0';
+ which_domain = state->request.domain_name;
+
+ /* Enumerate over trusted domains */
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ NTSTATUS status;
+ struct winbindd_methods *methods;
+ unsigned int i;
+
+ /* if we have a domain name restricting the request and this
+ one in the list doesn't match, then just bypass the remainder
+ of the loop */
+
+ if ( *which_domain && !strequal(which_domain, domain->name) )
+ continue;
+
+ methods = domain->methods;
+
+ /* Query display info */
+ status = methods->query_user_list(domain, state->mem_ctx,
+ &num_entries, &info);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+
+ if (num_entries == 0)
+ continue;
+
+ /* Allocate some memory for extra data */
+ total_entries += num_entries;
+
+ extra_data = (char *)SMB_REALLOC(
+ extra_data, sizeof(fstring) * total_entries);
+
+ if (!extra_data) {
+ DEBUG(0,("failed to enlarge buffer!\n"));
+ goto done;
+ }
+
+ /* Pack user list into extra data fields */
+
+ for (i = 0; i < num_entries; i++) {
+ fstring acct_name, name;
+
+ if (!info[i].acct_name) {
+ fstrcpy(acct_name, "");
+ } else {
+ fstrcpy(acct_name, info[i].acct_name);
+ }
+
+ fill_domain_username(name, domain->name, acct_name, True);
+
+ /* Append to extra data */
+ memcpy(&extra_data[extra_data_len], name,
+ strlen(name));
+ extra_data_len += strlen(name);
+ extra_data[extra_data_len++] = ',';
+ }
+ }
+
+ /* Assign extra_data fields in response structure */
+
+ if (extra_data) {
+ extra_data[extra_data_len - 1] = '\0';
+ state->response.extra_data.data = extra_data;
+ state->response.length += extra_data_len;
+ }
+
+ /* No domains responded but that's still OK so don't return an
+ error. */
+
+ rv = WINBINDD_OK;
+
+ done:
+
+ if (rv == WINBINDD_OK)
+ request_ok(state);
+ else
+ request_error(state);
+}
diff --git a/source3/winbindd/winbindd_util.c b/source3/winbindd/winbindd_util.c
new file mode 100644
index 0000000000..37d29e1765
--- /dev/null
+++ b/source3/winbindd/winbindd_util.c
@@ -0,0 +1,1460 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon for ntdom nss module
+
+ Copyright (C) Tim Potter 2000-2001
+ Copyright (C) 2001 by Martin Pool <mbp@samba.org>
+
+ 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 "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+extern struct winbindd_methods cache_methods;
+extern struct winbindd_methods passdb_methods;
+
+/**
+ * @file winbindd_util.c
+ *
+ * Winbind daemon for NT domain authentication nss module.
+ **/
+
+/* The list of trusted domains. Note that the list can be deleted and
+ recreated using the init_domain_list() function so pointers to
+ individual winbindd_domain structures cannot be made. Keep a copy of
+ the domain name instead. */
+
+static struct winbindd_domain *_domain_list;
+
+/**
+ When was the last scan of trusted domains done?
+
+ 0 == not ever
+*/
+
+static time_t last_trustdom_scan;
+
+struct winbindd_domain *domain_list(void)
+{
+ /* Initialise list */
+
+ if ((!_domain_list) && (!init_domain_list())) {
+ smb_panic("Init_domain_list failed");
+ }
+
+ return _domain_list;
+}
+
+/* Free all entries in the trusted domain list */
+
+void free_domain_list(void)
+{
+ struct winbindd_domain *domain = _domain_list;
+
+ while(domain) {
+ struct winbindd_domain *next = domain->next;
+
+ DLIST_REMOVE(_domain_list, domain);
+ SAFE_FREE(domain);
+ domain = next;
+ }
+}
+
+static BOOL is_internal_domain(const DOM_SID *sid)
+{
+ if (sid == NULL)
+ return False;
+
+ if ( IS_DC )
+ return sid_check_is_builtin(sid);
+
+ return (sid_check_is_domain(sid) || sid_check_is_builtin(sid));
+}
+
+static BOOL is_in_internal_domain(const DOM_SID *sid)
+{
+ if (sid == NULL)
+ return False;
+
+ if ( IS_DC )
+ return sid_check_is_in_builtin(sid);
+
+ return (sid_check_is_in_our_domain(sid) || sid_check_is_in_builtin(sid));
+}
+
+
+/* Add a trusted domain to our list of domains */
+static struct winbindd_domain *add_trusted_domain(const char *domain_name, const char *alt_name,
+ struct winbindd_methods *methods,
+ const DOM_SID *sid)
+{
+ struct winbindd_domain *domain;
+ const char *alternative_name = NULL;
+
+ /* ignore alt_name if we are not in an AD domain */
+
+ if ( (lp_security() == SEC_ADS) && alt_name && *alt_name) {
+ alternative_name = alt_name;
+ }
+
+ /* We can't call domain_list() as this function is called from
+ init_domain_list() and we'll get stuck in a loop. */
+ for (domain = _domain_list; domain; domain = domain->next) {
+ if (strequal(domain_name, domain->name) ||
+ strequal(domain_name, domain->alt_name))
+ {
+ break;
+ }
+
+ if (alternative_name && *alternative_name)
+ {
+ if (strequal(alternative_name, domain->name) ||
+ strequal(alternative_name, domain->alt_name))
+ {
+ break;
+ }
+ }
+
+ if (sid)
+ {
+ if (is_null_sid(sid)) {
+ continue;
+ }
+
+ if (sid_equal(sid, &domain->sid)) {
+ break;
+ }
+ }
+ }
+
+ /* See if we found a match. Check if we need to update the
+ SID. */
+
+ if ( domain && sid) {
+ if ( sid_equal( &domain->sid, &global_sid_NULL ) )
+ sid_copy( &domain->sid, sid );
+
+ return domain;
+ }
+
+ /* Create new domain entry */
+
+ if ((domain = SMB_MALLOC_P(struct winbindd_domain)) == NULL)
+ return NULL;
+
+ /* Fill in fields */
+
+ ZERO_STRUCTP(domain);
+
+ /* prioritise the short name */
+ if (strchr_m(domain_name, '.') && alternative_name && *alternative_name) {
+ fstrcpy(domain->name, alternative_name);
+ fstrcpy(domain->alt_name, domain_name);
+ } else {
+ fstrcpy(domain->name, domain_name);
+ if (alternative_name) {
+ fstrcpy(domain->alt_name, alternative_name);
+ }
+ }
+
+ domain->methods = methods;
+ domain->backend = NULL;
+ domain->internal = is_internal_domain(sid);
+ domain->sequence_number = DOM_SEQUENCE_NONE;
+ domain->last_seq_check = 0;
+ domain->initialized = False;
+ domain->online = is_internal_domain(sid);
+ domain->check_online_timeout = 0;
+ if (sid) {
+ sid_copy(&domain->sid, sid);
+ }
+
+ /* Link to domain list */
+ DLIST_ADD(_domain_list, domain);
+
+ wcache_tdc_add_domain( domain );
+
+ DEBUG(2,("Added domain %s %s %s\n",
+ domain->name, domain->alt_name,
+ &domain->sid?sid_string_static(&domain->sid):""));
+
+ return domain;
+}
+
+/********************************************************************
+ rescan our domains looking for new trusted domains
+********************************************************************/
+
+struct trustdom_state {
+ TALLOC_CTX *mem_ctx;
+ BOOL primary;
+ BOOL forest_root;
+ struct winbindd_response *response;
+};
+
+static void trustdom_recv(void *private_data, BOOL success);
+static void rescan_forest_root_trusts( void );
+static void rescan_forest_trusts( void );
+
+static void add_trusted_domains( struct winbindd_domain *domain )
+{
+ TALLOC_CTX *mem_ctx;
+ struct winbindd_request *request;
+ struct winbindd_response *response;
+ uint32 fr_flags = (DS_DOMAIN_TREE_ROOT|DS_DOMAIN_IN_FOREST);
+
+ struct trustdom_state *state;
+
+ mem_ctx = talloc_init("add_trusted_domains");
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_init failed\n"));
+ return;
+ }
+
+ request = TALLOC_ZERO_P(mem_ctx, struct winbindd_request);
+ response = TALLOC_P(mem_ctx, struct winbindd_response);
+ state = TALLOC_P(mem_ctx, struct trustdom_state);
+
+ if ((request == NULL) || (response == NULL) || (state == NULL)) {
+ DEBUG(0, ("talloc failed\n"));
+ talloc_destroy(mem_ctx);
+ return;
+ }
+
+ state->mem_ctx = mem_ctx;
+ state->response = response;
+
+ /* Flags used to know how to continue the forest trust search */
+
+ state->primary = domain->primary;
+ state->forest_root = ((domain->domain_flags & fr_flags) == fr_flags );
+
+ request->length = sizeof(*request);
+ request->cmd = WINBINDD_LIST_TRUSTDOM;
+
+ async_domain_request(mem_ctx, domain, request, response,
+ trustdom_recv, state);
+}
+
+static void trustdom_recv(void *private_data, BOOL success)
+{
+ struct trustdom_state *state =
+ talloc_get_type_abort(private_data, struct trustdom_state);
+ struct winbindd_response *response = state->response;
+ char *p;
+
+ if ((!success) || (response->result != WINBINDD_OK)) {
+ DEBUG(1, ("Could not receive trustdoms\n"));
+ talloc_destroy(state->mem_ctx);
+ return;
+ }
+
+ p = (char *)response->extra_data.data;
+
+ while ((p != NULL) && (*p != '\0')) {
+ char *q, *sidstr, *alt_name;
+ DOM_SID sid;
+ struct winbindd_domain *domain;
+ char *alternate_name = NULL;
+
+ alt_name = strchr(p, '\\');
+ if (alt_name == NULL) {
+ DEBUG(0, ("Got invalid trustdom response\n"));
+ break;
+ }
+
+ *alt_name = '\0';
+ alt_name += 1;
+
+ sidstr = strchr(alt_name, '\\');
+ if (sidstr == NULL) {
+ DEBUG(0, ("Got invalid trustdom response\n"));
+ break;
+ }
+
+ *sidstr = '\0';
+ sidstr += 1;
+
+ q = strchr(sidstr, '\n');
+ if (q != NULL)
+ *q = '\0';
+
+ if (!string_to_sid(&sid, sidstr)) {
+ /* Allow NULL sid for sibling domains */
+ if ( strcmp(sidstr,"S-0-0") == 0) {
+ sid_copy( &sid, &global_sid_NULL);
+ } else {
+ DEBUG(0, ("Got invalid trustdom response\n"));
+ break;
+ }
+ }
+
+ /* use the real alt_name if we have one, else pass in NULL */
+
+ if ( !strequal( alt_name, "(null)" ) )
+ alternate_name = alt_name;
+
+ /* If we have an existing domain structure, calling
+ add_trusted_domain() will update the SID if
+ necessary. This is important because we need the
+ SID for sibling domains */
+
+ if ( find_domain_from_name_noinit(p) != NULL ) {
+ domain = add_trusted_domain(p, alternate_name,
+ &cache_methods,
+ &sid);
+ } else {
+ domain = add_trusted_domain(p, alternate_name,
+ &cache_methods,
+ &sid);
+ if (domain) {
+ setup_domain_child(domain, &domain->child, NULL);
+ }
+ }
+ p=q;
+ if (p != NULL)
+ p += 1;
+ }
+
+ SAFE_FREE(response->extra_data.data);
+
+ /*
+ Cases to consider when scanning trusts:
+ (a) we are calling from a child domain (primary && !forest_root)
+ (b) we are calling from the root of the forest (primary && forest_root)
+ (c) we are calling from a trusted forest domain (!primary
+ && !forest_root)
+ */
+
+ if ( state->primary ) {
+ /* If this is our primary domain and we are not the in the
+ forest root, we have to scan the root trusts first */
+
+ if ( !state->forest_root )
+ rescan_forest_root_trusts();
+ else
+ rescan_forest_trusts();
+
+ } else if ( state->forest_root ) {
+ /* Once we have done root forest trust search, we can
+ go on to search thing trusted forests */
+
+ rescan_forest_trusts();
+ }
+
+ talloc_destroy(state->mem_ctx);
+
+ return;
+}
+
+/********************************************************************
+ Scan the trusts of our forest root
+********************************************************************/
+
+static void rescan_forest_root_trusts( void )
+{
+ struct winbindd_tdc_domain *dom_list = NULL;
+ size_t num_trusts = 0;
+ int i;
+
+ /* The only transitive trusts supported by Windows 2003 AD are
+ (a) Parent-Child, (b) Tree-Root, and (c) Forest. The
+ first two are handled in forest and listed by
+ DsEnumerateDomainTrusts(). Forest trusts are not so we
+ have to do that ourselves. */
+
+ if ( !wcache_tdc_fetch_list( &dom_list, &num_trusts ) )
+ return;
+
+ for ( i=0; i<num_trusts; i++ ) {
+ struct winbindd_domain *d = NULL;
+
+ /* Find the forest root. Don't necessarily trust
+ the domain_list() as our primary domain may not
+ have been initialized. */
+
+ if ( !(dom_list[i].trust_flags & DS_DOMAIN_TREE_ROOT) ) {
+ continue;
+ }
+
+ /* Here's the forest root */
+
+ d = find_domain_from_name_noinit( dom_list[i].domain_name );
+
+ if ( !d ) {
+ d = add_trusted_domain( dom_list[i].domain_name,
+ dom_list[i].dns_name,
+ &cache_methods,
+ &dom_list[i].sid );
+ }
+
+ DEBUG(10,("rescan_forest_root_trusts: Following trust path "
+ "for domain tree root %s (%s)\n",
+ d->name, d->alt_name ));
+
+ d->domain_flags = dom_list[i].trust_flags;
+ d->domain_type = dom_list[i].trust_type;
+ d->domain_trust_attribs = dom_list[i].trust_attribs;
+
+ add_trusted_domains( d );
+
+ break;
+ }
+
+ TALLOC_FREE( dom_list );
+
+ return;
+}
+
+/********************************************************************
+ scan the transitive forest trists (not our own)
+********************************************************************/
+
+
+static void rescan_forest_trusts( void )
+{
+ struct winbindd_domain *d = NULL;
+ struct winbindd_tdc_domain *dom_list = NULL;
+ size_t num_trusts = 0;
+ int i;
+
+ /* The only transitive trusts supported by Windows 2003 AD are
+ (a) Parent-Child, (b) Tree-Root, and (c) Forest. The
+ first two are handled in forest and listed by
+ DsEnumerateDomainTrusts(). Forest trusts are not so we
+ have to do that ourselves. */
+
+ if ( !wcache_tdc_fetch_list( &dom_list, &num_trusts ) )
+ return;
+
+ for ( i=0; i<num_trusts; i++ ) {
+ uint32 flags = dom_list[i].trust_flags;
+ uint32 type = dom_list[i].trust_type;
+ uint32 attribs = dom_list[i].trust_attribs;
+
+ d = find_domain_from_name_noinit( dom_list[i].domain_name );
+
+ /* ignore our primary and internal domains */
+
+ if ( d && (d->internal || d->primary ) )
+ continue;
+
+ if ( (flags & DS_DOMAIN_DIRECT_INBOUND) &&
+ (type == DS_DOMAIN_TRUST_TYPE_UPLEVEL) &&
+ (attribs == DS_DOMAIN_TRUST_ATTRIB_FOREST_TRANSITIVE) )
+ {
+ /* add the trusted domain if we don't know
+ about it */
+
+ if ( !d ) {
+ d = add_trusted_domain( dom_list[i].domain_name,
+ dom_list[i].dns_name,
+ &cache_methods,
+ &dom_list[i].sid );
+ }
+
+ DEBUG(10,("Following trust path for domain %s (%s)\n",
+ d->name, d->alt_name ));
+ add_trusted_domains( d );
+ }
+ }
+
+ TALLOC_FREE( dom_list );
+
+ return;
+}
+
+/*********************************************************************
+ The process of updating the trusted domain list is a three step
+ async process:
+ (a) ask our domain
+ (b) ask the root domain in our forest
+ (c) ask the a DC in any Win2003 trusted forests
+*********************************************************************/
+
+void rescan_trusted_domains( void )
+{
+ time_t now = time(NULL);
+
+ /* see if the time has come... */
+
+ if ((now >= last_trustdom_scan) &&
+ ((now-last_trustdom_scan) < WINBINDD_RESCAN_FREQ) )
+ return;
+
+ /* clear the TRUSTDOM cache first */
+
+ wcache_tdc_clear();
+
+ /* this will only add new domains we didn't already know about
+ in the domain_list()*/
+
+ add_trusted_domains( find_our_domain() );
+
+ last_trustdom_scan = now;
+
+ return;
+}
+
+struct init_child_state {
+ TALLOC_CTX *mem_ctx;
+ struct winbindd_domain *domain;
+ struct winbindd_request *request;
+ struct winbindd_response *response;
+ void (*continuation)(void *private_data, BOOL success);
+ void *private_data;
+};
+
+static void init_child_recv(void *private_data, BOOL success);
+static void init_child_getdc_recv(void *private_data, BOOL success);
+
+enum winbindd_result init_child_connection(struct winbindd_domain *domain,
+ void (*continuation)(void *private_data,
+ BOOL success),
+ void *private_data)
+{
+ TALLOC_CTX *mem_ctx;
+ struct winbindd_request *request;
+ struct winbindd_response *response;
+ struct init_child_state *state;
+ struct winbindd_domain *request_domain;
+
+ mem_ctx = talloc_init("init_child_connection");
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_init failed\n"));
+ return WINBINDD_ERROR;
+ }
+
+ request = TALLOC_ZERO_P(mem_ctx, struct winbindd_request);
+ response = TALLOC_P(mem_ctx, struct winbindd_response);
+ state = TALLOC_P(mem_ctx, struct init_child_state);
+
+ if ((request == NULL) || (response == NULL) || (state == NULL)) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(mem_ctx);
+ continuation(private_data, False);
+ return WINBINDD_ERROR;
+ }
+
+ request->length = sizeof(*request);
+
+ state->mem_ctx = mem_ctx;
+ state->domain = domain;
+ state->request = request;
+ state->response = response;
+ state->continuation = continuation;
+ state->private_data = private_data;
+
+ if (IS_DC || domain->primary || domain->internal ) {
+ /* The primary domain has to find the DC name itself */
+ request->cmd = WINBINDD_INIT_CONNECTION;
+ fstrcpy(request->domain_name, domain->name);
+ request->data.init_conn.is_primary = domain->internal ? False : True;
+ fstrcpy(request->data.init_conn.dcname, "");
+ async_request(mem_ctx, &domain->child, request, response,
+ init_child_recv, state);
+ return WINBINDD_PENDING;
+ }
+
+ /* This is *not* the primary domain, let's ask our DC about a DC
+ * name */
+
+ request->cmd = WINBINDD_GETDCNAME;
+ fstrcpy(request->domain_name, domain->name);
+
+ request_domain = find_our_domain();
+ async_domain_request(mem_ctx, request_domain, request, response,
+ init_child_getdc_recv, state);
+ return WINBINDD_PENDING;
+}
+
+static void init_child_getdc_recv(void *private_data, BOOL success)
+{
+ struct init_child_state *state =
+ talloc_get_type_abort(private_data, struct init_child_state);
+ const char *dcname = "";
+
+ DEBUG(10, ("Received getdcname response\n"));
+
+ if (success && (state->response->result == WINBINDD_OK)) {
+ dcname = state->response->data.dc_name;
+ }
+
+ state->request->cmd = WINBINDD_INIT_CONNECTION;
+ fstrcpy(state->request->domain_name, state->domain->name);
+ state->request->data.init_conn.is_primary = False;
+ fstrcpy(state->request->data.init_conn.dcname, dcname);
+
+ async_request(state->mem_ctx, &state->domain->child,
+ state->request, state->response,
+ init_child_recv, state);
+}
+
+static void init_child_recv(void *private_data, BOOL success)
+{
+ struct init_child_state *state =
+ talloc_get_type_abort(private_data, struct init_child_state);
+
+ DEBUG(5, ("Received child initialization response for domain %s\n",
+ state->domain->name));
+
+ if ((!success) || (state->response->result != WINBINDD_OK)) {
+ DEBUG(3, ("Could not init child\n"));
+ state->continuation(state->private_data, False);
+ talloc_destroy(state->mem_ctx);
+ return;
+ }
+
+ fstrcpy(state->domain->name,
+ state->response->data.domain_info.name);
+ fstrcpy(state->domain->alt_name,
+ state->response->data.domain_info.alt_name);
+ string_to_sid(&state->domain->sid,
+ state->response->data.domain_info.sid);
+ state->domain->native_mode =
+ state->response->data.domain_info.native_mode;
+ state->domain->active_directory =
+ state->response->data.domain_info.active_directory;
+ state->domain->sequence_number =
+ state->response->data.domain_info.sequence_number;
+
+ init_dc_connection(state->domain);
+
+ if (state->continuation != NULL)
+ state->continuation(state->private_data, True);
+ talloc_destroy(state->mem_ctx);
+}
+
+enum winbindd_result winbindd_dual_init_connection(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ /* Ensure null termination */
+ state->request.domain_name
+ [sizeof(state->request.domain_name)-1]='\0';
+ state->request.data.init_conn.dcname
+ [sizeof(state->request.data.init_conn.dcname)-1]='\0';
+
+ if (strlen(state->request.data.init_conn.dcname) > 0) {
+ fstrcpy(domain->dcname, state->request.data.init_conn.dcname);
+ }
+
+ init_dc_connection(domain);
+
+ if (!domain->initialized) {
+ /* If we return error here we can't do any cached authentication,
+ but we may be in disconnected mode and can't initialize correctly.
+ Do what the previous code did and just return without initialization,
+ once we go online we'll re-initialize.
+ */
+ DEBUG(5, ("winbindd_dual_init_connection: %s returning without initialization "
+ "online = %d\n", domain->name, (int)domain->online ));
+ }
+
+ fstrcpy(state->response.data.domain_info.name, domain->name);
+ fstrcpy(state->response.data.domain_info.alt_name, domain->alt_name);
+ fstrcpy(state->response.data.domain_info.sid,
+ sid_string_static(&domain->sid));
+
+ state->response.data.domain_info.native_mode
+ = domain->native_mode;
+ state->response.data.domain_info.active_directory
+ = domain->active_directory;
+ state->response.data.domain_info.primary
+ = domain->primary;
+ state->response.data.domain_info.sequence_number =
+ domain->sequence_number;
+
+ return WINBINDD_OK;
+}
+
+/* Look up global info for the winbind daemon */
+BOOL init_domain_list(void)
+{
+ struct winbindd_domain *domain;
+ int role = lp_server_role();
+
+ /* Free existing list */
+ free_domain_list();
+
+ /* Add ourselves as the first entry. */
+
+ if ( role == ROLE_DOMAIN_MEMBER ) {
+ DOM_SID our_sid;
+
+ if (!secrets_fetch_domain_sid(lp_workgroup(), &our_sid)) {
+ DEBUG(0, ("Could not fetch our SID - did we join?\n"));
+ return False;
+ }
+
+ domain = add_trusted_domain( lp_workgroup(), lp_realm(),
+ &cache_methods, &our_sid);
+ if (domain) {
+ domain->primary = True;
+ setup_domain_child(domain, &domain->child, NULL);
+
+ /* Even in the parent winbindd we'll need to
+ talk to the DC, so try and see if we can
+ contact it. Theoretically this isn't neccessary
+ as the init_dc_connection() in init_child_recv()
+ will do this, but we can start detecting the DC
+ early here. */
+ set_domain_online_request(domain);
+ }
+ }
+
+ /* Local SAM */
+
+ domain = add_trusted_domain(get_global_sam_name(), NULL,
+ &passdb_methods, get_global_sam_sid());
+ if (domain) {
+ if ( role != ROLE_DOMAIN_MEMBER ) {
+ domain->primary = True;
+ }
+ setup_domain_child(domain, &domain->child, NULL);
+ }
+
+ /* BUILTIN domain */
+
+ domain = add_trusted_domain("BUILTIN", NULL, &passdb_methods,
+ &global_sid_Builtin);
+ if (domain) {
+ setup_domain_child(domain, &domain->child, NULL);
+ }
+
+ return True;
+}
+
+void check_domain_trusted( const char *name, const DOM_SID *user_sid )
+{
+ struct winbindd_domain *domain;
+ DOM_SID dom_sid;
+ uint32 rid;
+
+ domain = find_domain_from_name_noinit( name );
+ if ( domain )
+ return;
+
+ sid_copy( &dom_sid, user_sid );
+ if ( !sid_split_rid( &dom_sid, &rid ) )
+ return;
+
+ /* add the newly discovered trusted domain */
+
+ domain = add_trusted_domain( name, NULL, &cache_methods,
+ &dom_sid);
+
+ if ( !domain )
+ return;
+
+ /* assume this is a trust from a one-way transitive
+ forest trust */
+
+ domain->active_directory = True;
+ domain->domain_flags = DS_DOMAIN_DIRECT_OUTBOUND;
+ domain->domain_type = DS_DOMAIN_TRUST_TYPE_UPLEVEL;
+ domain->internal = False;
+ domain->online = True;
+
+ setup_domain_child(domain, &domain->child, NULL);
+
+ wcache_tdc_add_domain( domain );
+
+ return;
+}
+
+/**
+ * Given a domain name, return the struct winbindd domain info for it
+ *
+ * @note Do *not* pass lp_workgroup() to this function. domain_list
+ * may modify it's value, and free that pointer. Instead, our local
+ * domain may be found by calling find_our_domain().
+ * directly.
+ *
+ *
+ * @return The domain structure for the named domain, if it is working.
+ */
+
+struct winbindd_domain *find_domain_from_name_noinit(const char *domain_name)
+{
+ struct winbindd_domain *domain;
+
+ /* Search through list */
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ if (strequal(domain_name, domain->name) ||
+ (domain->alt_name[0] &&
+ strequal(domain_name, domain->alt_name))) {
+ return domain;
+ }
+ }
+
+ /* Not found */
+
+ return NULL;
+}
+
+struct winbindd_domain *find_domain_from_name(const char *domain_name)
+{
+ struct winbindd_domain *domain;
+
+ domain = find_domain_from_name_noinit(domain_name);
+
+ if (domain == NULL)
+ return NULL;
+
+ if (!domain->initialized)
+ init_dc_connection(domain);
+
+ return domain;
+}
+
+/* Given a domain sid, return the struct winbindd domain info for it */
+
+struct winbindd_domain *find_domain_from_sid_noinit(const DOM_SID *sid)
+{
+ struct winbindd_domain *domain;
+
+ /* Search through list */
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ if (sid_compare_domain(sid, &domain->sid) == 0)
+ return domain;
+ }
+
+ /* Not found */
+
+ return NULL;
+}
+
+/* Given a domain sid, return the struct winbindd domain info for it */
+
+struct winbindd_domain *find_domain_from_sid(const DOM_SID *sid)
+{
+ struct winbindd_domain *domain;
+
+ domain = find_domain_from_sid_noinit(sid);
+
+ if (domain == NULL)
+ return NULL;
+
+ if (!domain->initialized)
+ init_dc_connection(domain);
+
+ return domain;
+}
+
+struct winbindd_domain *find_our_domain(void)
+{
+ struct winbindd_domain *domain;
+
+ /* Search through list */
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ if (domain->primary)
+ return domain;
+ }
+
+ smb_panic("Could not find our domain");
+ return NULL;
+}
+
+struct winbindd_domain *find_root_domain(void)
+{
+ struct winbindd_domain *ours = find_our_domain();
+
+ if ( !ours )
+ return NULL;
+
+ if ( strlen(ours->forest_name) == 0 )
+ return NULL;
+
+ return find_domain_from_name( ours->forest_name );
+}
+
+struct winbindd_domain *find_builtin_domain(void)
+{
+ DOM_SID sid;
+ struct winbindd_domain *domain;
+
+ string_to_sid(&sid, "S-1-5-32");
+ domain = find_domain_from_sid(&sid);
+
+ if (domain == NULL) {
+ smb_panic("Could not find BUILTIN domain");
+ }
+
+ return domain;
+}
+
+/* Find the appropriate domain to lookup a name or SID */
+
+struct winbindd_domain *find_lookup_domain_from_sid(const DOM_SID *sid)
+{
+ /* SIDs in the S-1-22-{1,2} domain should be handled by our passdb */
+
+ if ( sid_check_is_in_unix_groups(sid) ||
+ sid_check_is_unix_groups(sid) ||
+ sid_check_is_in_unix_users(sid) ||
+ sid_check_is_unix_users(sid) )
+ {
+ return find_domain_from_sid(get_global_sam_sid());
+ }
+
+ /* A DC can't ask the local smbd for remote SIDs, here winbindd is the
+ * one to contact the external DC's. On member servers the internal
+ * domains are different: These are part of the local SAM. */
+
+ DEBUG(10, ("find_lookup_domain_from_sid(%s)\n",
+ sid_string_static(sid)));
+
+ if (IS_DC || is_internal_domain(sid) || is_in_internal_domain(sid)) {
+ DEBUG(10, ("calling find_domain_from_sid\n"));
+ return find_domain_from_sid(sid);
+ }
+
+ /* On a member server a query for SID or name can always go to our
+ * primary DC. */
+
+ DEBUG(10, ("calling find_our_domain\n"));
+ return find_our_domain();
+}
+
+struct winbindd_domain *find_lookup_domain_from_name(const char *domain_name)
+{
+ if ( strequal(domain_name, unix_users_domain_name() ) ||
+ strequal(domain_name, unix_groups_domain_name() ) )
+ {
+ return find_domain_from_name_noinit( get_global_sam_name() );
+ }
+
+ if (IS_DC || strequal(domain_name, "BUILTIN") ||
+ strequal(domain_name, get_global_sam_name()))
+ return find_domain_from_name_noinit(domain_name);
+
+ /* The "Unix User" and "Unix Group" domain our handled by passdb */
+
+ return find_our_domain();
+}
+
+/* Lookup a sid in a domain from a name */
+
+BOOL winbindd_lookup_sid_by_name(TALLOC_CTX *mem_ctx,
+ enum winbindd_cmd orig_cmd,
+ struct winbindd_domain *domain,
+ const char *domain_name,
+ const char *name, DOM_SID *sid,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+
+ /* Lookup name */
+ result = domain->methods->name_to_sid(domain, mem_ctx, orig_cmd,
+ domain_name, name, sid, type);
+
+ /* Return sid and type if lookup successful */
+ if (!NT_STATUS_IS_OK(result)) {
+ *type = SID_NAME_UNKNOWN;
+ }
+
+ return NT_STATUS_IS_OK(result);
+}
+
+/**
+ * @brief Lookup a name in a domain from a sid.
+ *
+ * @param sid Security ID you want to look up.
+ * @param name On success, set to the name corresponding to @p sid.
+ * @param dom_name On success, set to the 'domain name' corresponding to @p sid.
+ * @param type On success, contains the type of name: alias, group or
+ * user.
+ * @retval True if the name exists, in which case @p name and @p type
+ * are set, otherwise False.
+ **/
+BOOL winbindd_lookup_name_by_sid(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ DOM_SID *sid,
+ char **dom_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+
+ *dom_name = NULL;
+ *name = NULL;
+
+ /* Lookup name */
+
+ result = domain->methods->sid_to_name(domain, mem_ctx, sid, dom_name, name, type);
+
+ /* Return name and type if successful */
+
+ if (NT_STATUS_IS_OK(result)) {
+ return True;
+ }
+
+ *type = SID_NAME_UNKNOWN;
+
+ return False;
+}
+
+/* Free state information held for {set,get,end}{pw,gr}ent() functions */
+
+void free_getent_state(struct getent_state *state)
+{
+ struct getent_state *temp;
+
+ /* Iterate over state list */
+
+ temp = state;
+
+ while(temp != NULL) {
+ struct getent_state *next;
+
+ /* Free sam entries then list entry */
+
+ SAFE_FREE(state->sam_entries);
+ DLIST_REMOVE(state, state);
+ next = temp->next;
+
+ SAFE_FREE(temp);
+ temp = next;
+ }
+}
+
+/* Is this a domain which we may assume no DOMAIN\ prefix? */
+
+static BOOL assume_domain(const char *domain)
+{
+ /* never assume the domain on a standalone server */
+
+ if ( lp_server_role() == ROLE_STANDALONE )
+ return False;
+
+ /* domain member servers may possibly assume for the domain name */
+
+ if ( lp_server_role() == ROLE_DOMAIN_MEMBER ) {
+ if ( !strequal(lp_workgroup(), domain) )
+ return False;
+
+ if ( lp_winbind_use_default_domain() || lp_winbind_trusted_domains_only() )
+ return True;
+ }
+
+ /* only left with a domain controller */
+
+ if ( strequal(get_global_sam_name(), domain) ) {
+ return True;
+ }
+
+ return False;
+}
+
+/* Parse a string of the form DOMAIN\user into a domain and a user */
+
+BOOL parse_domain_user(const char *domuser, fstring domain, fstring user)
+{
+ char *p = strchr(domuser,*lp_winbind_separator());
+
+ if ( !p ) {
+ fstrcpy(user, domuser);
+
+ if ( assume_domain(lp_workgroup())) {
+ fstrcpy(domain, lp_workgroup());
+ } else if ((p = strchr(domuser, '@')) != NULL) {
+ fstrcpy(domain, "");
+ } else {
+ return False;
+ }
+ } else {
+ fstrcpy(user, p+1);
+ fstrcpy(domain, domuser);
+ domain[PTR_DIFF(p, domuser)] = 0;
+ }
+
+ strupper_m(domain);
+
+ return True;
+}
+
+BOOL parse_domain_user_talloc(TALLOC_CTX *mem_ctx, const char *domuser,
+ char **domain, char **user)
+{
+ fstring fstr_domain, fstr_user;
+ if (!parse_domain_user(domuser, fstr_domain, fstr_user)) {
+ return False;
+ }
+ *domain = talloc_strdup(mem_ctx, fstr_domain);
+ *user = talloc_strdup(mem_ctx, fstr_user);
+ return ((*domain != NULL) && (*user != NULL));
+}
+
+/* Ensure an incoming username from NSS is fully qualified. Replace the
+ incoming fstring with DOMAIN <separator> user. Returns the same
+ values as parse_domain_user() but also replaces the incoming username.
+ Used to ensure all names are fully qualified within winbindd.
+ Used by the NSS protocols of auth, chauthtok, logoff and ccache_ntlm_auth.
+ The protocol definitions of auth_crap, chng_pswd_auth_crap
+ really should be changed to use this instead of doing things
+ by hand. JRA. */
+
+BOOL canonicalize_username(fstring username_inout, fstring domain, fstring user)
+{
+ if (!parse_domain_user(username_inout, domain, user)) {
+ return False;
+ }
+ slprintf(username_inout, sizeof(fstring) - 1, "%s%c%s",
+ domain, *lp_winbind_separator(),
+ user);
+ return True;
+}
+
+/*
+ Fill DOMAIN\\USERNAME entry accounting 'winbind use default domain' and
+ 'winbind separator' options.
+ This means:
+ - omit DOMAIN when 'winbind use default domain = true' and DOMAIN is
+ lp_workgroup()
+
+ If we are a PDC or BDC, and this is for our domain, do likewise.
+
+ Also, if omit DOMAIN if 'winbind trusted domains only = true', as the
+ username is then unqualified in unix
+
+ We always canonicalize as UPPERCASE DOMAIN, lowercase username.
+*/
+void fill_domain_username(fstring name, const char *domain, const char *user, BOOL can_assume)
+{
+ fstring tmp_user;
+
+ fstrcpy(tmp_user, user);
+ strlower_m(tmp_user);
+
+ if (can_assume && assume_domain(domain)) {
+ strlcpy(name, tmp_user, sizeof(fstring));
+ } else {
+ slprintf(name, sizeof(fstring) - 1, "%s%c%s",
+ domain, *lp_winbind_separator(),
+ tmp_user);
+ }
+}
+
+/*
+ * Winbindd socket accessor functions
+ */
+
+char *get_winbind_priv_pipe_dir(void)
+{
+ return lock_path(WINBINDD_PRIV_SOCKET_SUBDIR);
+}
+
+/*
+ * Client list accessor functions
+ */
+
+static struct winbindd_cli_state *_client_list;
+static int _num_clients;
+
+/* Return list of all connected clients */
+
+struct winbindd_cli_state *winbindd_client_list(void)
+{
+ return _client_list;
+}
+
+/* Add a connection to the list */
+
+void winbindd_add_client(struct winbindd_cli_state *cli)
+{
+ DLIST_ADD(_client_list, cli);
+ _num_clients++;
+}
+
+/* Remove a client from the list */
+
+void winbindd_remove_client(struct winbindd_cli_state *cli)
+{
+ DLIST_REMOVE(_client_list, cli);
+ _num_clients--;
+}
+
+/* Close all open clients */
+
+void winbindd_kill_all_clients(void)
+{
+ struct winbindd_cli_state *cl = winbindd_client_list();
+
+ DEBUG(10, ("winbindd_kill_all_clients: going postal\n"));
+
+ while (cl) {
+ struct winbindd_cli_state *next;
+
+ next = cl->next;
+ winbindd_remove_client(cl);
+ cl = next;
+ }
+}
+
+/* Return number of open clients */
+
+int winbindd_num_clients(void)
+{
+ return _num_clients;
+}
+
+NTSTATUS lookup_usergroups_cached(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const DOM_SID *user_sid,
+ uint32 *p_num_groups, DOM_SID **user_sids)
+{
+ NET_USER_INFO_3 *info3 = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ int i;
+ size_t num_groups = 0;
+ DOM_SID group_sid, primary_group;
+
+ DEBUG(3,(": lookup_usergroups_cached\n"));
+
+ *user_sids = NULL;
+ num_groups = 0;
+ *p_num_groups = 0;
+
+ info3 = netsamlogon_cache_get(mem_ctx, user_sid);
+
+ if (info3 == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (info3->num_groups == 0) {
+ TALLOC_FREE(info3);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* always add the primary group to the sid array */
+ sid_compose(&primary_group, &info3->dom_sid.sid, info3->user_rid);
+
+ if (!add_sid_to_array(mem_ctx, &primary_group, user_sids, &num_groups)) {
+ TALLOC_FREE(info3);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<info3->num_groups; i++) {
+ sid_copy(&group_sid, &info3->dom_sid.sid);
+ sid_append_rid(&group_sid, info3->gids[i].g_rid);
+
+ if (!add_sid_to_array(mem_ctx, &group_sid, user_sids,
+ &num_groups)) {
+ TALLOC_FREE(info3);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* Add any Universal groups in the other_sids list */
+
+ for (i=0; i<info3->num_other_sids; i++) {
+ /* Skip Domain local groups outside our domain.
+ We'll get these from the getsidaliases() RPC call. */
+ if (info3->other_sids_attrib[i] & SE_GROUP_RESOURCE)
+ continue;
+
+ if (!add_sid_to_array(mem_ctx, &info3->other_sids[i].sid,
+ user_sids, &num_groups))
+ {
+ TALLOC_FREE(info3);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+
+ TALLOC_FREE(info3);
+ *p_num_groups = num_groups;
+ status = (user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+
+ DEBUG(3,(": lookup_usergroups_cached succeeded\n"));
+
+ return status;
+}
+
+/*********************************************************************
+ We use this to remove spaces from user and group names
+********************************************************************/
+
+void ws_name_replace( char *name, char replace )
+{
+ char replace_char[2] = { 0x0, 0x0 };
+
+ if ( !lp_winbind_normalize_names() || (replace == '\0') )
+ return;
+
+ replace_char[0] = replace;
+ all_string_sub( name, " ", replace_char, 0 );
+
+ return;
+}
+
+/*********************************************************************
+ We use this to do the inverse of ws_name_replace()
+********************************************************************/
+
+void ws_name_return( char *name, char replace )
+{
+ char replace_char[2] = { 0x0, 0x0 };
+
+ if ( !lp_winbind_normalize_names() || (replace == '\0') )
+ return;
+
+ replace_char[0] = replace;
+ all_string_sub( name, replace_char, " ", 0 );
+
+ return;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+BOOL winbindd_can_contact_domain( struct winbindd_domain *domain )
+{
+ /* We can contact the domain if it is our primary domain */
+
+ if ( domain->primary )
+ return True;
+
+ /* Can always contact a domain that is in out forest */
+
+ if ( domain->domain_flags & DS_DOMAIN_IN_FOREST )
+ return True;
+
+ /* We cannot contact the domain if it is running AD and
+ we have no inbound trust */
+
+ if ( domain->active_directory &&
+ ((domain->domain_flags&DS_DOMAIN_DIRECT_INBOUND) != DS_DOMAIN_DIRECT_INBOUND) )
+ {
+ return False;
+ }
+
+ /* Assume everything else is ok (probably not true but what
+ can you do?) */
+
+ return True;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+BOOL winbindd_internal_child(struct winbindd_child *child)
+{
+ if ((child == idmap_child()) || (child == locator_child())) {
+ return True;
+ }
+
+ return False;
+}
+
+#ifdef HAVE_KRB5_LOCATE_PLUGIN_H
+
+/*********************************************************************
+ ********************************************************************/
+
+static void winbindd_set_locator_kdc_env(const struct winbindd_domain *domain)
+{
+ char *var = NULL;
+ const char *kdc = NULL;
+ int lvl = 11;
+
+ if (!domain || !domain->alt_name || !*domain->alt_name) {
+ return;
+ }
+
+ if (domain->initialized && !domain->active_directory) {
+ DEBUG(lvl,("winbindd_set_locator_kdc_env: %s not AD\n",
+ domain->alt_name));
+ return;
+ }
+
+ kdc = inet_ntoa(domain->dcaddr.sin_addr);
+ if (!kdc) {
+ DEBUG(lvl,("winbindd_set_locator_kdc_env: %s no DC IP\n",
+ domain->alt_name));
+ kdc = domain->dcname;
+ }
+
+ if (!kdc || !*kdc) {
+ DEBUG(lvl,("winbindd_set_locator_kdc_env: %s no DC at all\n",
+ domain->alt_name));
+ return;
+ }
+
+ if (asprintf(&var, "%s_%s", WINBINDD_LOCATOR_KDC_ADDRESS,
+ strupper_static(domain->alt_name)) == -1) {
+ return;
+ }
+
+ DEBUG(lvl,("winbindd_set_locator_kdc_env: setting var: %s to: %s\n",
+ var, kdc));
+
+ setenv(var, kdc, 1);
+ free(var);
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain)
+{
+ struct winbindd_domain *our_dom = find_our_domain();
+
+ winbindd_set_locator_kdc_env(domain);
+
+ if (domain != our_dom) {
+ winbindd_set_locator_kdc_env(our_dom);
+ }
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain)
+{
+ char *var = NULL;
+
+ if (!domain || !domain->alt_name || !*domain->alt_name) {
+ return;
+ }
+
+ if (asprintf(&var, "%s_%s", WINBINDD_LOCATOR_KDC_ADDRESS,
+ strupper_static(domain->alt_name)) == -1) {
+ return;
+ }
+
+ unsetenv(var);
+ free(var);
+}
+#else
+
+void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain)
+{
+ return;
+}
+
+void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain)
+{
+ return;
+}
+
+#endif /* HAVE_KRB5_LOCATE_PLUGIN_H */
diff --git a/source3/winbindd/winbindd_wins.c b/source3/winbindd/winbindd_wins.c
new file mode 100644
index 0000000000..f84dfdf2de
--- /dev/null
+++ b/source3/winbindd/winbindd_wins.c
@@ -0,0 +1,234 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - WINS related functions
+
+ Copyright (C) Andrew Tridgell 1999
+ Copyright (C) Herb Lewis 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/* Use our own create socket code so we don't recurse.... */
+
+static int wins_lookup_open_socket_in(void)
+{
+ struct sockaddr_in sock;
+ int val=1;
+ int res;
+
+ memset((char *)&sock,'\0',sizeof(sock));
+
+#ifdef HAVE_SOCK_SIN_LEN
+ sock.sin_len = sizeof(sock);
+#endif
+ sock.sin_port = 0;
+ sock.sin_family = AF_INET;
+ sock.sin_addr.s_addr = interpret_addr("0.0.0.0");
+ res = socket(AF_INET, SOCK_DGRAM, 0);
+ if (res == -1)
+ return -1;
+
+ setsockopt(res,SOL_SOCKET,SO_REUSEADDR,(char *)&val,sizeof(val));
+#ifdef SO_REUSEPORT
+ setsockopt(res,SOL_SOCKET,SO_REUSEPORT,(char *)&val,sizeof(val));
+#endif /* SO_REUSEPORT */
+
+ /* now we've got a socket - we need to bind it */
+
+ if (bind(res, (struct sockaddr * ) &sock,sizeof(sock)) < 0) {
+ close(res);
+ return(-1);
+ }
+
+ set_socket_options(res,"SO_BROADCAST");
+
+ return res;
+}
+
+
+static NODE_STATUS_STRUCT *lookup_byaddr_backend(char *addr, int *count)
+{
+ int fd;
+ struct in_addr ip;
+ struct nmb_name nname;
+ NODE_STATUS_STRUCT *status;
+
+ fd = wins_lookup_open_socket_in();
+ if (fd == -1)
+ return NULL;
+
+ make_nmb_name(&nname, "*", 0);
+ ip = *interpret_addr2(addr);
+ status = node_status_query(fd,&nname,ip, count, NULL);
+
+ close(fd);
+ return status;
+}
+
+static struct in_addr *lookup_byname_backend(const char *name, int *count)
+{
+ int fd;
+ struct ip_service *ret = NULL;
+ struct in_addr *return_ip = NULL;
+ int j, i, flags = 0;
+
+ *count = 0;
+
+ /* always try with wins first */
+ if (NT_STATUS_IS_OK(resolve_wins(name,0x20,&ret,count))) {
+ if ( *count == 0 )
+ return NULL;
+ if ( (return_ip = SMB_MALLOC_ARRAY(struct in_addr, *count)) == NULL ) {
+ free( ret );
+ return NULL;
+ }
+
+ /* copy the IP addresses */
+ for ( i=0; i<(*count); i++ )
+ return_ip[i] = ret[i].ip;
+
+ free( ret );
+ return return_ip;
+ }
+
+ fd = wins_lookup_open_socket_in();
+ if (fd == -1) {
+ return NULL;
+ }
+
+ /* uggh, we have to broadcast to each interface in turn */
+ for (j=iface_count() - 1;
+ j >= 0;
+ j--) {
+ struct in_addr *bcast = iface_n_bcast(j);
+ return_ip = name_query(fd,name,0x20,True,True,*bcast,count, &flags, NULL);
+ if (return_ip) {
+ break;
+ }
+ }
+
+ close(fd);
+ return return_ip;
+}
+
+/* Get hostname from IP */
+
+void winbindd_wins_byip(struct winbindd_cli_state *state)
+{
+ fstring response;
+ int i, count, maxlen, size;
+ NODE_STATUS_STRUCT *status;
+
+ /* Ensure null termination */
+ state->request.data.winsreq[sizeof(state->request.data.winsreq)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: wins_byip %s\n", (unsigned long)state->pid,
+ state->request.data.winsreq));
+
+ *response = '\0';
+ maxlen = sizeof(response) - 1;
+
+ if ((status = lookup_byaddr_backend(state->request.data.winsreq, &count))){
+ size = strlen(state->request.data.winsreq);
+ if (size > maxlen) {
+ SAFE_FREE(status);
+ request_error(state);
+ return;
+ }
+ fstrcat(response,state->request.data.winsreq);
+ fstrcat(response,"\t");
+ for (i = 0; i < count; i++) {
+ /* ignore group names */
+ if (status[i].flags & 0x80) continue;
+ if (status[i].type == 0x20) {
+ size = sizeof(status[i].name) + strlen(response);
+ if (size > maxlen) {
+ SAFE_FREE(status);
+ request_error(state);
+ return;
+ }
+ fstrcat(response, status[i].name);
+ fstrcat(response, " ");
+ }
+ }
+ /* make last character a newline */
+ response[strlen(response)-1] = '\n';
+ SAFE_FREE(status);
+ }
+ fstrcpy(state->response.data.winsresp,response);
+ request_ok(state);
+}
+
+/* Get IP from hostname */
+
+void winbindd_wins_byname(struct winbindd_cli_state *state)
+{
+ struct in_addr *ip_list;
+ int i, count, maxlen, size;
+ fstring response;
+ char * addr;
+
+ /* Ensure null termination */
+ state->request.data.winsreq[sizeof(state->request.data.winsreq)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: wins_byname %s\n", (unsigned long)state->pid,
+ state->request.data.winsreq));
+
+ *response = '\0';
+ maxlen = sizeof(response) - 1;
+
+ if ((ip_list = lookup_byname_backend(state->request.data.winsreq,&count))){
+ for (i = count; i ; i--) {
+ addr = inet_ntoa(ip_list[i-1]);
+ size = strlen(addr);
+ if (size > maxlen) {
+ SAFE_FREE(ip_list);
+ request_error(state);
+ return;
+ }
+ if (i != 0) {
+ /* Clear out the newline character */
+ /* But only if there is something in there,
+ otherwise we clobber something in the stack */
+ if (strlen(response))
+ response[strlen(response)-1] = ' ';
+ }
+ fstrcat(response,addr);
+ fstrcat(response,"\t");
+ }
+ size = strlen(state->request.data.winsreq) + strlen(response);
+ if (size > maxlen) {
+ SAFE_FREE(ip_list);
+ request_error(state);
+ return;
+ }
+ fstrcat(response,state->request.data.winsreq);
+ fstrcat(response,"\n");
+ SAFE_FREE(ip_list);
+ } else {
+ request_error(state);
+ return;
+ }
+
+ fstrcpy(state->response.data.winsresp,response);
+
+ request_ok(state);
+}