[PATCH 3/6] s4: Implement GET_DFS_REFERRAL for domain referral requests

Matthieu Patou mat at matws.net
Sat May 1 12:27:31 MDT 2010


---
 source4/smb_server/smb/trans2.c |  767 ++++++++++++++++++++++++++++++++++++++-
 1 files changed, 766 insertions(+), 1 deletions(-)

diff --git a/source4/smb_server/smb/trans2.c b/source4/smb_server/smb/trans2.c
index eddbf00..26b261e 100644
--- a/source4/smb_server/smb/trans2.c
+++ b/source4/smb_server/smb/trans2.c
@@ -2,6 +2,7 @@
    Unix SMB/CIFS implementation.
    transaction2 handling
    Copyright (C) Andrew Tridgell 2003
+   Copyright Matthieu Patou 2010 mat at matws.net
 
    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
@@ -21,10 +22,20 @@
 */
 
 #include "includes.h"
+#include "smbd/service_stream.h"
 #include "smb_server/smb_server.h"
 #include "ntvfs/ntvfs.h"
 #include "libcli/raw/libcliraw.h"
 #include "libcli/raw/raw_proto.h"
+#include "librpc/gen_ndr/dfsblobs.h"
+#include "librpc/gen_ndr/ndr_dfsblobs.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/session.h"
+#include "param/param.h"
+#include "lib/socket/socket.h"
+
+#define MAX_DFS_RESPONSE 56*1024 /* 56 Kb */
+#define DEFAULT_SITE_NAME "Default-First-Site-Name"
 
 #define TRANS2_CHECK_ASYNC_STATUS_SIMPLE do { \
 	if (!NT_STATUS_IS_OK(req->ntvfs->async_states->status)) { \
@@ -53,7 +64,13 @@ struct trans_op {
 	NTSTATUS (*send_fn)(struct trans_op *);
 	void *op_info;
 };
-
+/* A DC set is a group of DC, they might have been grouped together
+   because they belong to the same site, or to site with same cost ...
+*/
+struct dc_set {
+	const char **names;
+	uint32_t count;
+};
 #define CHECK_MIN_BLOB_SIZE(blob, size) do { \
 	if ((blob)->length < (size)) { \
 		return NT_STATUS_INFO_LENGTH_MISMATCH; \
@@ -844,6 +861,752 @@ static NTSTATUS trans2_findfirst_send(struct trans_op *op)
 
 
 /*
+  fill a referral type structure
+ */
+static NTSTATUS fill_normal_dfs_referraltype(struct dfs_referral_type *ref,
+					     uint16_t version, 
+					     const char *dfs_path, 
+					     const char *server_path, int isfirstoffset)
+{
+
+	switch (version) {
+	case 3:
+		ZERO_STRUCTP(ref);
+		ref->version = version;
+		ref->referral.v3.data.server_type = DFS_SERVER_NON_ROOT;
+		ref->referral.v3.size = 18;
+
+		ref->referral.v3.data.entry_flags = 0;
+		ref->referral.v3.data.ttl = 600; /* As w2k3 */
+		ref->referral.v3.data.referrals.r1.DFS_path = dfs_path;
+		ref->referral.v3.data.referrals.r1.DFS_alt_path = dfs_path;
+		ref->referral.v3.data.referrals.r1.netw_address = server_path;
+		return NT_STATUS_OK;
+	case 4:
+		ZERO_STRUCTP(ref);
+		ref->version = version;
+		ref->referral.v4.server_type = DFS_SERVER_NON_ROOT;
+		ref->referral.v4.size = 18;
+
+		if (isfirstoffset) {
+			ref->referral.v4.entry_flags =  DFS_HEADER_FLAG_TARGET_BCK;
+		}
+		ref->referral.v4.ttl = 600; /* As w2k3 */
+		ref->referral.v4.r1.DFS_path = dfs_path;
+		ref->referral.v4.r1.DFS_alt_path = dfs_path;
+		ref->referral.v4.r1.netw_address = server_path;
+
+		return NT_STATUS_OK;
+	}
+	return NT_STATUS_INVALID_LEVEL;
+}
+
+/*
+  fill a domain refererral
+ */
+static NTSTATUS fill_domain_dfs_referraltype(struct dfs_referral_type *ref,
+					     uint16_t version, 
+					     const char *domain, 
+					     const char **names, 
+					     uint16_t numnames)
+{
+	switch (version) {
+	case 3:
+		ZERO_STRUCTP(ref);
+		ref->version = version;
+		ref->referral.v3.data.server_type = DFS_SERVER_NON_ROOT;
+		ref->referral.v3.size = 34;
+		ref->referral.v3.data.entry_flags = DFS_FLAG_REFERRAL_DOMAIN_RESP;
+		ref->referral.v3.data.ttl = 600; /* As w2k3 */
+		ref->referral.v3.data.referrals.r2.special_name = domain;
+		ref->referral.v3.data.referrals.r2.nb_expanded_names = numnames;
+		/* Put the final terminator */
+		if (names) {
+			const char **names2 = talloc_array(ref, const char *, numnames+1);
+			NT_STATUS_HAVE_NO_MEMORY(names2);
+			int i;
+			for (i = 0; i<numnames; i++) {
+				names2[i] = talloc_asprintf(names2, "\\%s", names[i]);
+				NT_STATUS_HAVE_NO_MEMORY(names2[i]);
+			}
+			names2[numnames] = 0;
+			ref->referral.v3.data.referrals.r2.expanded_names = names2;
+		}
+		return NT_STATUS_OK;
+	}
+	return NT_STATUS_INVALID_LEVEL;
+}
+
+/*
+  get the DCs list within a site
+ */
+static NTSTATUS get_dcs_insite(TALLOC_CTX *ctx, struct ldb_context *ldb, 
+			       struct ldb_dn *sitedn, struct dc_set *list, 
+			       bool dofqdn)
+{
+	static const char *attrs[] = { "serverReference", NULL };
+	static const char *attrs2[] = { "dNSHostName", "sAMAccountName", NULL };
+	struct ldb_result *r;
+	unsigned int i;
+	int ret;
+	const char **dc_list;
+
+	ret = ldb_search(ldb, ctx, &r, sitedn, LDB_SCOPE_SUBTREE, attrs, 
+			 "(&(objectClass=server)(serverReference=*))");
+
+	if (ret != LDB_SUCCESS) {
+		DEBUG(2,(__location__ ": Failed to get list of servers - %s\n",
+			 ldb_errstring(ldb)));
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	if (r->count == 0) {
+		/* none in this site */
+		talloc_free(r);
+		return NT_STATUS_OK;
+	}
+
+	/* need to search for all server object to know the size of the array */
+	/* Search all the object of class server in this site */
+	dc_list = talloc_array(r, const char *, r->count);
+	NT_STATUS_HAVE_NO_MEMORY(dc_list);
+
+	/* TODO put some random here in the order */
+	list->names = talloc_realloc(list, list->names, const char *, list->count + r->count);
+
+	for (i = 0; i<r->count; i++) {
+		struct ldb_dn  *dn;
+		struct ldb_result *r2;
+
+		dn = ldb_msg_find_attr_as_dn(ldb, ctx, r->msgs[i], "serverReference");
+		if (!dn) {
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+
+		ret = ldb_search(ldb, r, &r2, dn, LDB_SCOPE_BASE, attrs2, "(objectClass=computer)");
+
+		if (ret != LDB_SUCCESS) {
+			DEBUG(2,(__location__ ": Search for computer on %s failed - %s\n",
+				 ldb_dn_get_linearized(dn), ldb_errstring(ldb)));
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+		if (dofqdn) {
+			const char *dns = ldb_msg_find_attr_as_string(r2->msgs[0], "dNSHostName", NULL);
+			if (dns == NULL) {
+				DEBUG(2,(__location__ ": dNSHostName missing on %s\n",
+					 ldb_dn_get_linearized(dn)));
+				talloc_free(r);
+				return NT_STATUS_INTERNAL_ERROR;				
+			}
+			list->names[list->count] = talloc_strdup(list->names, dns);
+			NT_STATUS_HAVE_NO_MEMORY(list->names[list->count]);
+		} else {
+			const char *acct = ldb_msg_find_attr_as_string(r2->msgs[0], "sAMAccountName", NULL);
+			char *tmp;
+			if (acct == NULL) {
+				DEBUG(2,(__location__ ": sAMAccountName missing on %s\n",
+					 ldb_dn_get_linearized(dn)));
+				talloc_free(r);
+				return NT_STATUS_INTERNAL_ERROR;				
+			}
+			tmp = talloc_strdup(list->names, acct);
+			NT_STATUS_HAVE_NO_MEMORY(tmp);
+
+			/* Netbios name is also the sAMAccountName for
+			   computer but without the final $ */
+			tmp[strlen(tmp) - 1] = '\0';
+			list->names[list->count] = tmp;
+		}
+		list->count++;
+		talloc_free(r2);
+	}
+
+	talloc_free(r);
+	return NT_STATUS_OK;
+}
+
+
+/*
+  get all DCs
+ */
+static NTSTATUS get_dcs(TALLOC_CTX *ctx, struct ldb_context *ldb, 
+			const char *searched_site, bool need_fqdn, 
+			struct dc_set ***pset_list, uint32_t flags)
+{
+	/* Flags will be used later to indicate things like least-expensive or same-site options*/
+	const char *attrs_none[] = { NULL };
+	const char *attrs3[] = { "name", NULL };
+	struct ldb_dn *configdn, *sitedn, *dn, *sitescontainerdn;
+	struct ldb_result *r;
+	struct ldb_message_element *el;
+	struct dc_set **set_list;
+	unsigned int i;
+	int ret;
+	int current_pos = 0;
+	TALLOC_CTX *subctx = talloc_new(ctx);
+	NTSTATUS status;
+
+	(*pset_list) = set_list = NULL;
+
+	configdn = ldb_get_config_basedn(ldb);
+
+	/* Let's search for the Site container */
+	ret = ldb_search(ldb, subctx, &r, configdn, LDB_SCOPE_SUBTREE, attrs_none, 
+			 "(objectClass=sitesContainer)");
+
+	if (ret != LDB_SUCCESS) {
+		DEBUG(2,(__location__ ": Failed to find sitesContainer within %s - %s\n",
+			 ldb_dn_get_linearized(configdn), ldb_errstring(ldb)));
+		talloc_free(subctx);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+	if (r->count > 1) {
+		DEBUG(2,(__location__ ": Expected 1 sitesContainer - found %u within %s\n",
+			 r->count, ldb_dn_get_linearized(configdn)));
+		talloc_free(subctx);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	sitescontainerdn = talloc_steal(subctx, r->msgs[0]->dn);
+	talloc_free(r);
+
+	/* TODO Here we should have a more subtle handling for the case "same-site" */
+	ret = ldb_search(ldb, subctx, &r, sitescontainerdn, LDB_SCOPE_SUBTREE, 
+			 attrs_none, "(objectClass=server)");
+
+	if (ret != LDB_SUCCESS) {
+		DEBUG(2,(__location__ ": Failed to find servers within %s - %s\n",
+			 ldb_dn_get_linearized(sitescontainerdn), ldb_errstring(ldb)));
+		talloc_free(subctx);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	talloc_free(r);
+
+	if (searched_site != NULL) {
+		ret = ldb_search(ldb, subctx, &r, configdn, LDB_SCOPE_SUBTREE, 
+				 attrs_none, "(&(name=%s)(objectClass=site))", searched_site);
+
+		if (ret != LDB_SUCCESS) {
+			talloc_free(subctx);
+			return NT_STATUS_FOOBAR;
+		} else if (r->count != 1) {
+			talloc_free(subctx);
+			return NT_STATUS_FOOBAR;
+		}
+
+		/* All of this was to get the DN of the searched_site */
+		sitedn = r->msgs[0]->dn;
+		
+		set_list = talloc_realloc(subctx, set_list, struct dc_set *, current_pos+1);
+		NT_STATUS_HAVE_NO_MEMORY_AND_FREE(set_list, subctx);
+
+		set_list[current_pos] = talloc(set_list, struct dc_set);
+		NT_STATUS_HAVE_NO_MEMORY_AND_FREE(set_list[current_pos], subctx);
+
+		set_list[current_pos]->names = NULL;
+		set_list[current_pos]->count = 0;
+		status = get_dcs_insite(subctx, ldb, sitedn, 
+					set_list[current_pos], need_fqdn);
+		if (!NT_STATUS_IS_OK(status)) {
+			DEBUG(2,(__location__ ": Failed to get DC from site %s - %s\n",
+				 ldb_dn_get_linearized(sitedn), nt_errstr(status)));
+			talloc_free(subctx);
+			return status;
+		}
+		talloc_free(r);
+		current_pos++;
+	}
+
+	/* Let's find all the sites */
+	ret = ldb_search(ldb, subctx, &r, configdn, LDB_SCOPE_SUBTREE, attrs3, "(objectClass=site)");
+	if (ret != LDB_SUCCESS) {
+		DEBUG(2,(__location__ ": Failed to find any site containers in %s\n",
+			 ldb_dn_get_linearized(configdn)));
+		talloc_free(subctx);
+		return NT_STATUS_INTERNAL_DB_CORRUPTION;
+	}
+
+	/* TODO We should randomize the order in the main site, it's mostly needed for sysvol/netlogon referral*/
+	/* Depending of flag we either randomize order of the not "in the same site DCs"
+	   or we randomize by group of site that have the same cost
+	*/
+	/* In the long run we want to manipulate an array of site_set
+	   All the site in one set have the same cost (if least-expansive options is selected)
+	   and we will put all the dc related to 1 site set into 1 DCs set.
+	   Within a site set, site order has to be randomized
+
+	   But for the moment we just return the list of sites
+
+	*/
+	if (r->count) {
+		/* We will realloc + 2 because we will need one additional place for element at 
+		 * current_pos + 1 for the NULL element*/
+		set_list = talloc_realloc(subctx, set_list, struct dc_set *, current_pos+2);
+		NT_STATUS_HAVE_NO_MEMORY_AND_FREE(set_list, subctx);
+
+		set_list[current_pos] = talloc(ctx, struct dc_set);
+		NT_STATUS_HAVE_NO_MEMORY_AND_FREE(set_list[current_pos], subctx);
+
+		set_list[current_pos]->names = NULL;
+		set_list[current_pos]->count = 0;
+	}
+
+	for (i=0; i<r->count;i++) {
+		el = ldb_msg_find_element(r->msgs[i], "name");
+		if (!el) {
+			DEBUG(2,(__location__ ": Failed to find name attribute in %s\n",
+				 ldb_dn_get_linearized(r->msgs[i]->dn)));
+			talloc_free(subctx);
+			return NT_STATUS_INTERNAL_DB_CORRUPTION;
+		}
+		if (searched_site == NULL || strcmp(searched_site, 
+						    (const char *)el->values[0].data)) {
+			DEBUG(2,(__location__ ": Site: %s %s\n",searched_site,el->values[0].data));
+			/* Do all the site but the one of the client
+			 * (because it has already been done ... */
+			dn = r->msgs[i]->dn;
+
+			status = get_dcs_insite(subctx, ldb, dn, 
+						set_list[current_pos], 
+						need_fqdn);
+			if (!NT_STATUS_IS_OK(status)) {
+				talloc_free(subctx);
+				return status;
+			}
+			talloc_free(dn);
+		}
+	}
+	current_pos++;
+	set_list[current_pos] = NULL;
+	(*pset_list) = talloc_steal(ctx, set_list);
+	talloc_free(subctx);
+	return NT_STATUS_OK;
+}
+
+static NTSTATUS dodomain_referral(TALLOC_CTX *ctx, const struct dfs_GetDFSReferral_in dfsreq,
+					struct ldb_context *ldb,
+					struct smb_trans2 *trans,
+					struct loadparm_context *lp_ctx)
+{
+	/* TODO for the moment we just return the local domain */
+	DATA_BLOB *outblob;
+	enum ndr_err_code ndr_err;
+	NTSTATUS status;
+	const char *realm = lp_realm(lp_ctx);
+	const char* domain = lp_workgroup(lp_ctx);
+	struct dfs_referral_resp resp;
+	struct dfs_referral_type *tab;
+	struct dfs_referral_type *referral;
+	/* In the futur this needs to be fetched from the ldb */
+	int found_domain = 2;
+	int current_pos = 0;
+	TALLOC_CTX *context = talloc_new(ctx);
+
+	if (lp_server_role(lp_ctx) != ROLE_DOMAIN_CONTROLLER) {
+		DEBUG(10 ,("Received a domain referral request on a non DC\n"));
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	outblob = talloc(context, DATA_BLOB);
+	NT_STATUS_HAVE_NO_MEMORY_AND_FREE(outblob, context);
+
+	if (dfsreq.max_referral_level < 3) {
+		DEBUG(2,("invalid max_referral_level %u\n",
+			 dfsreq.max_referral_level));
+		return NT_STATUS_UNSUCCESSFUL;
+	}
+
+	resp.path_consumed = 0;
+	resp.header_flags = 0; /* Do like w2k3 */
+	resp.nb_referrals = found_domain; /* the fqdn one + the NT domain */
+
+	tab = talloc_array(context, struct dfs_referral_type, found_domain);
+	NT_STATUS_HAVE_NO_MEMORY_AND_FREE(tab, context);
+
+	referral = talloc(tab, struct dfs_referral_type);
+	status = fill_domain_dfs_referraltype(referral,  3,
+						talloc_asprintf(referral, "\\%s", strlower_talloc(referral, realm)), NULL, 0);
+	if (!NT_STATUS_IS_OK(status)) {
+		DEBUG(2,(__location__ ":Unable to fill domain referral structure\n"));
+		talloc_free(context);
+		return NT_STATUS_UNSUCCESSFUL;
+	}
+	tab[current_pos] = *referral;
+	current_pos++;
+	referral = talloc(tab, struct dfs_referral_type);
+	status = fill_domain_dfs_referraltype(referral,  3,
+						 talloc_asprintf(referral, "\\%s", domain), NULL, 0);
+	if (!NT_STATUS_IS_OK(status)) {
+		DEBUG(2,(__location__ ":Unable to fill domain referral structure\n"));
+		talloc_free(context);
+		return NT_STATUS_UNSUCCESSFUL;
+	}
+	tab[current_pos] = *referral;
+	current_pos++;
+	/* Put here the code from filling the array for trusted domain */
+
+	resp.referral_entries = tab;
+
+	ndr_err =  ndr_push_struct_blob(outblob, outblob,
+				lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), &resp, 
+				(ndr_push_flags_fn_t)ndr_push_dfs_referral_resp);
+
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		DEBUG(2,(__location__ ":NDR marchalling of domain deferral response failed\n"));
+		talloc_free(context);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	if ( outblob->length > trans->in.max_data ) {
+		DEBUG(3, ("Blob is too big for the output buffer size %d max %d\n",outblob->length,trans->in.max_data));
+		if( trans->in.max_data == MAX_DFS_RESPONSE ) {
+			/* The answer is too big, so let's remove some answers*/
+			int ok = 0;
+			int remain = found_domain;
+			talloc_free(outblob);
+			while( ok ==0 && remain >2 ) {
+				/* Let's scrap the first referral*/
+				resp.nb_referrals = resp.nb_referrals - 1;
+				resp.referral_entries++;
+				remain--;
+				ndr_err =  ndr_push_struct_blob(outblob, outblob,
+					lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), &resp, 
+					(ndr_push_flags_fn_t)ndr_push_dfs_referral_resp);
+
+				if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+					talloc_free(context);
+					return NT_STATUS_INTERNAL_ERROR;
+				}
+
+				if (outblob->length <= MAX_DFS_RESPONSE) {
+					DEBUG(10, ("DFS: managed to reduce the size of referral initial # of referral %d, actual #: %d",
+						found_domain,remain));
+					ok = 1;
+				}
+			}
+
+			if (ok == 0 && remain == 2) {
+				DEBUG(0, (__location__ "; Not able to fit the domain and realm in DFS a 56K buffer, something must be broken"));
+				talloc_free(context);
+				return NT_STATUS_INTERNAL_ERROR;
+			}
+
+
+		} else {
+			/* As specified in MS-DFSC.pdf 3.3.5.2 */
+			talloc_free(context);
+			return STATUS_BUFFER_OVERFLOW;
+		}
+	}
+	TRANS2_CHECK(trans2_setup_reply(trans, 0, outblob->length, 0));
+	
+	trans->out.data = *(talloc_steal(ctx, outblob)); 
+	talloc_free(context);
+	return NT_STATUS_OK;
+}
+
+/* Handle the logic for dfs referral request like \\domain 
+ * or \\domain\sysvol or \\fqdn or \\fqdn\netlogon */
+static NTSTATUS dodc_or_sysvol_referral(TALLOC_CTX *ctx, const struct dfs_GetDFSReferral_in dfsreq,
+					const char* requestedname,
+					struct ldb_context *ldb,
+					struct smb_trans2 *trans,
+					struct smbsrv_request *req,
+					struct loadparm_context *lp_ctx)
+{
+
+	/* It's not a "standard" DFS referral but a referral to get the DC list*/
+	/* or sysvol/netlogon */
+	/* Let's check that it's for one of our domain ...*/
+	DATA_BLOB *outblob;
+	NTSTATUS status;
+	
+	unsigned int num_domain = 1;
+	enum ndr_err_code ndr_err;
+	const char* requesteddomain;
+	const char *realm = lp_realm(lp_ctx);
+	const char* domain = lp_workgroup(lp_ctx);
+	const char* site_name = NULL; /* Name of the site where the client is */
+	char *share = NULL;
+	int found = 0;
+	int need_fqdn = 0;
+	int dc_referral = 1;
+	unsigned int i;
+	char *tmp;
+	struct dc_set **set;
+	char const **domain_list;
+	struct dfs_referral_resp resp;
+	struct dfs_referral_type *referral;
+	struct socket_address *client_addr;
+	TALLOC_CTX *context = talloc_new(ctx);
+
+	if (lp_server_role(lp_ctx) != ROLE_DOMAIN_CONTROLLER) {
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	outblob = talloc(context, DATA_BLOB);
+	NT_STATUS_HAVE_NO_MEMORY_AND_FREE(outblob, context);
+
+	if ( dfsreq.max_referral_level < 3 ) {
+		DEBUG(2,("invalid max_referral_level %u\n",
+			 dfsreq.max_referral_level));
+		talloc_free(context);
+		return NT_STATUS_UNSUCCESSFUL;
+	}
+	if (requestedname[0] == '\\' && ! strchr(requestedname+1,'\\')) {
+		requestedname++;
+	}
+	requesteddomain = requestedname;
+
+	if (strchr(requestedname,'\\')) {
+		char *subpart;
+		/* we have a second part */
+		requesteddomain = talloc_strdup(context, requestedname+1);
+		NT_STATUS_HAVE_NO_MEMORY_AND_FREE(requesteddomain, context);
+		subpart = strchr(requesteddomain,'\\');
+		subpart[0] = '\0';
+	}
+	tmp = strchr(requestedname + 1,'\\'); /* To get second \ if any */
+
+	if (tmp != NULL) {
+		/* There was a share */
+		share = tmp+1;
+		dc_referral = 0;
+	}
+	/* We will fetch the trusted domain list soon with something like this: */
+	/* "(&(|(flatname=%s)(cn=%s)(trustPartner=%s)(flatname=%s)(cn=%s)(trustPartner=%s))(objectclass=trustedDomain))",*/
+	/* Allocate for num_domain + 1 so that the last element will be NULL)*/
+	domain_list = talloc_array(context, const char*, num_domain+1);
+	NT_STATUS_HAVE_NO_MEMORY_AND_FREE(domain_list, context);
+
+	domain_list[0] = realm;
+	domain_list[1] = domain;
+	for (i=0; i<=num_domain; i++) {
+		if (strncasecmp(domain_list[i], requesteddomain, strlen(domain_list[i])) == 0) {
+			found = 1;
+			break;
+		}
+	}
+
+	if ( found == 0 ) {
+		/* The requested domain is not one that we support */
+		DEBUG(3, ("Requested referral for domain %s, but we don't handle it", requesteddomain));
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (strchr(requestedname,'.')) {
+		need_fqdn = 1;
+	}  
+	client_addr = socket_get_peer_addr(req->smb_conn->connection->socket, context);
+	if (client_addr) {
+		site_name = samdb_client_site_name(ldb, context, client_addr->addr, NULL);
+	}
+
+	status = get_dcs(context, ldb, site_name, need_fqdn, &set, 0);
+
+	if (!NT_STATUS_IS_OK(status)) {
+		DEBUG(3,("Unable to get list of DCs\n"));
+		talloc_free(context);
+		return status;
+	}
+	if (dc_referral == 1) {
+		const char **dc_list;
+		uint32_t j;
+		int num_dcs = 0;
+		for(i=0; set[i]; i++) {
+			if (i == 0) {
+				dc_list = talloc_array(context, const char*, set[i]->count +1);
+				NT_STATUS_HAVE_NO_MEMORY_AND_FREE(dc_list, context);
+			} else {
+				dc_list = talloc_realloc(context, dc_list, const char*, num_dcs + set[i]->count + 1);
+				NT_STATUS_HAVE_NO_MEMORY_AND_FREE(dc_list, context);
+			}
+			for(j=0; j<set[i]->count; j++) {
+				dc_list[num_dcs + j] = (const char*)talloc_steal(context, set[i]->names[j]);
+			}
+			num_dcs = num_dcs + set[i]->count;
+		}
+		dc_list[num_dcs] = 0;
+		resp.path_consumed = 0;
+		resp.header_flags = 0; /* Do like w2k3 and like in 3.3.5.3 of MS-DFSC*/
+		
+		/* The NumberOfReferrals field MUST be set to 1, independent of the number of DC names
+		   returned. (as stated in 3.3.5.3 of MS-DFSC) */
+		resp.nb_referrals = 1;
+
+		/* Put here the code from filling the array for trusted domain */
+		referral = talloc(context, struct dfs_referral_type);
+		status = fill_domain_dfs_referraltype(referral,  3,
+						talloc_asprintf(referral, "\\%s", 
+						requestedname), (const char**) dc_list, num_dcs);
+
+		if (!NT_STATUS_IS_OK(status)) {
+			DEBUG(2,(__location__ ":Unable to fill domain referral structure\n"));
+			talloc_free(context);
+			return NT_STATUS_UNSUCCESSFUL;
+		}
+		resp.referral_entries = referral;
+
+		ndr_err =  ndr_push_struct_blob(outblob, outblob,
+				lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), &resp, 
+				(ndr_push_flags_fn_t)ndr_push_dfs_referral_resp);
+
+		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+			DEBUG(2,(__location__ ":NDR marshalling of dfs referral response failed\n"));
+			talloc_free(context);
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+	} else {
+		unsigned int nb_entries = 0;
+		unsigned int current = 0;
+		int j;
+		struct dfs_referral_type *tab;
+		for(i=0; set[i]; i++) {
+			nb_entries = nb_entries + set[i]->count;
+		}
+		resp.path_consumed = 2*strlen(requestedname); /* The length is expected in bytes */
+		resp.header_flags = DFS_HEADER_FLAG_STORAGE_SVR; /* Do like w2k3 and like in 3.3.5.3 of MS-DFSC*/
+		/* The NumberOfReferrals field MUST be set to 1, independent of the number of DC names
+		   returned. (as stated in 3.3.5.3 of MS-DFSC) */
+
+		resp.nb_referrals = nb_entries;
+
+		tab = talloc_array(context, struct dfs_referral_type, nb_entries);
+		NT_STATUS_HAVE_NO_MEMORY(tab);
+
+		for(i=0; set[i]; i++) {
+			for(j=0; j< set[i]->count; j++) {
+				referral = talloc(tab, struct dfs_referral_type);
+				status = fill_normal_dfs_referraltype(referral,
+						dfsreq.max_referral_level, requestedname,
+						talloc_asprintf(referral,"\\%s\\%s",set[i]->names[j],share), j==0);
+				if (!NT_STATUS_IS_OK(status)) {
+					DEBUG(2, (__location__ ": Unable to fill a normal dfs referral object"));
+					talloc_free(context);
+					return NT_STATUS_UNSUCCESSFUL;
+				}
+				tab[current] = *referral;
+				current++;
+			}
+		}
+		resp.referral_entries = tab;
+
+		ndr_err =  ndr_push_struct_blob(outblob, outblob,
+					lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")), &resp, 
+					(ndr_push_flags_fn_t)ndr_push_dfs_referral_resp);
+
+		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+			DEBUG(2,(__location__ ":NDR marchalling of domain deferral response failed\n"));
+			talloc_free(context);
+			return NT_STATUS_INTERNAL_ERROR;
+		}
+
+	}
+	TRANS2_CHECK(trans2_setup_reply(trans, 0, outblob->length, 0));
+	/* TODO If the size is too big we should remove some DC from the answer ...*/
+	/* or return STATUS_BUFFER_OVERFLOW */
+
+	trans->out.data = *(talloc_steal(ctx, outblob));
+	talloc_free(context);
+	return NT_STATUS_OK;
+}
+/*
+  trans2 getdfsreferral implementation
+*/
+static NTSTATUS trans2_getdfsreferral(struct smbsrv_request *req, struct trans_op *op)
+{
+	enum ndr_err_code ndr_err;
+	struct smb_trans2 *trans = op->trans;
+	struct dfs_GetDFSReferral_in dfsreq;
+	TALLOC_CTX *context;
+	DATA_BLOB dblob;
+	struct ldb_context *ldb;
+	struct loadparm_context *lp_ctx;
+	const char *realm, *nbname, *requestedname;
+	char *fqdn, *tmp;
+	NTSTATUS status;
+
+	lp_ctx = req->tcon->ntvfs->lp_ctx;
+	if (!lp_host_msdfs(lp_ctx)) {
+		return NT_STATUS_NOT_IMPLEMENTED;
+	}
+
+	context = talloc_new(req);
+	dblob = data_blob_talloc(context, trans->in.params.data,  trans->in.params.length);
+	NT_STATUS_HAVE_NO_MEMORY_AND_FREE(dblob.data, context);
+
+	ldb = samdb_connect(context, req->tcon->ntvfs->event_ctx, lp_ctx, system_session(lp_ctx));
+	if (ldb == NULL) {
+		DEBUG(2,(__location__ ": Failed to open samdb\n"));
+		talloc_free(context);
+		return NT_STATUS_INTERNAL_ERROR;
+	}
+
+	ndr_err = ndr_pull_struct_blob(&dblob, op, 
+				       lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
+				       &dfsreq,
+				       (ndr_pull_flags_fn_t)ndr_pull_dfs_GetDFSReferral_in);
+	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+		status = ndr_map_error2ntstatus(ndr_err);
+		DEBUG(2,(__location__ ": Failed to parse GetDFSReferral_in - %s\n",
+			 nt_errstr(status)));
+		talloc_free(context);
+		return status;
+	}
+	DEBUG(10, ("Requested DFS name: %s length: %d\n",
+		   dfsreq.servername, strlen(dfsreq.servername)));
+
+	/* If the servername is "" then we are in a case of domain dfs and the client "just
+	   search for the list of local domain it is attached and also
+	   trusted one */
+	realm = lp_realm(lp_ctx);
+	nbname = lp_netbios_name(lp_ctx);
+	requestedname = dfsreq.servername;
+
+	if (requestedname[0] == '\0') {
+		return dodomain_referral(op, dfsreq, ldb, trans, lp_ctx);
+	}
+	fqdn = talloc_asprintf(context, "%s.%s", nbname, realm);
+
+	if ( (strncasecmp(requestedname+1, nbname, strlen(nbname)) == 0) || (strncasecmp(requestedname+1, fqdn, strlen(fqdn)) == 0) ) {
+		/* the referral request starts with \NETBIOSNAME or \fqdn */
+		/* it's a standalone referral we do not do it */
+		/* TODO correct this */
+		/* If a DFS link that is a complete prefix of the DFS referral request path is identified, the server MUST
+		return a DFS link referral response; otherwise, if it has a match for the DFS root, it MUST return a
+		root referral response.
+		*/
+		DEBUG(3, ("Received a standalone request for %s, we do not support standalone referral yet",requestedname));
+		talloc_free(context);
+		return NT_STATUS_NOT_FOUND;
+	}
+	talloc_free(fqdn);
+
+	tmp = strchr(requestedname + 1,'\\'); /* To get second \ if any */
+
+	/* If we have no slash at the first position or (foo.bar.domain.net)
+	   a slash at the first position but no other slash (\foo.bar.domain.net)
+	   or a slash at the first position and another slash
+	       and netlogon or sysvol after the second slash ( \foo.bar.domain.net\sysvol )
+	  then we will handle it because it's either a dc referral or a sysvol/netlogon referral */
+	if (requestedname[0] != '\\' || tmp == NULL ||
+		( tmp && (strcasecmp(tmp+1, "sysvol") == 0 || strcasecmp(tmp+1, "netlogon") == 0) )) {
+		return dodc_or_sysvol_referral(op, dfsreq, requestedname, ldb, trans, req, lp_ctx);
+	}
+
+	if (requestedname[0] == '\\' && tmp && strchr(tmp+1, '\\') && ((
+		strncasecmp(tmp+1, "sysvol", 6) == 0 ) ||
+		strncasecmp(tmp+1, "netlogon", 8) == 0 )) {
+		/* We have more than two \ so it something like \domain\sysvol\foobar*/
+		talloc_free(context);
+		return NT_STATUS_NOT_FOUND;
+	}
+	talloc_free(context);
+	/* By default until all the case are handled*/
+	return NT_STATUS_NOT_FOUND;
+}
+/*
   trans2 findfirst implementation
 */
 static NTSTATUS trans2_findfirst(struct smbsrv_request *req, struct trans_op *op)
@@ -1010,6 +1773,8 @@ static NTSTATUS trans2_backend(struct smbsrv_request *req, struct trans_op *op)
 
 	/* the trans2 command is in setup[0] */
 	switch (trans->in.setup[0]) {
+	case TRANSACT2_GET_DFS_REFERRAL:
+		return trans2_getdfsreferral(req, op);
 	case TRANSACT2_FINDFIRST:
 		return trans2_findfirst(req, op);
 	case TRANSACT2_FINDNEXT:
-- 
1.7.0.4


--------------010504080003090902020407
Content-Type: text/x-patch;
 name="0002-s4-fix-trailling-whitespace-in-trans2.c.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0002-s4-fix-trailling-whitespace-in-trans2.c.patch"



More information about the samba-technical mailing list