mirror of
https://github.com/samba-team/samba.git
synced 2024-12-24 21:34:56 +03:00
s4:smb_server: Implement GET_DFS_REFERRAL for domain referral requests
Signed-off-by: Stefan Metzmacher <metze@samba.org>
This commit is contained in:
parent
90d768989c
commit
fc94c50a9f
@ -2,6 +2,7 @@
|
||||
Unix SMB/CIFS implementation.
|
||||
transaction2 handling
|
||||
Copyright (C) Andrew Tridgell 2003
|
||||
Copyright Matthieu Patou 2010 mat@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/tsocket/tsocket.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; \
|
||||
@ -843,6 +860,857 @@ 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_AND_FREE(dc_list, r);
|
||||
|
||||
/* TODO put some random here in the order */
|
||||
list->names = talloc_realloc(list, list->names, const char *, list->count + r->count);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(list->names, r);
|
||||
|
||||
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_AND_FREE(list->names[list->count], r);
|
||||
} else {
|
||||
char *tmp;
|
||||
const char *acct = ldb_msg_find_attr_as_string(r2->msgs[0], "sAMAccountName", NULL);
|
||||
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_AND_FREE(tmp, r);
|
||||
|
||||
/* 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 dc_set **set_list = NULL;
|
||||
uint32_t i;
|
||||
int ret;
|
||||
uint32_t current_pos = 0;
|
||||
NTSTATUS status;
|
||||
TALLOC_CTX *subctx = talloc_new(ctx);
|
||||
|
||||
*pset_list = set_list = NULL;
|
||||
|
||||
subctx = talloc_new(ctx);
|
||||
NT_STATUS_HAVE_NO_MEMORY(subctx);
|
||||
|
||||
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;
|
||||
|
||||
set_list[current_pos+1] = NULL;
|
||||
}
|
||||
|
||||
for (i=0; i<r->count; i++) {
|
||||
const char *site_name = ldb_msg_find_attr_as_string(r->msgs[i], "name", NULL);
|
||||
if (site_name == NULL) {
|
||||
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, site_name) != 0) {
|
||||
DEBUG(2,(__location__ ": Site: %s %s\n",
|
||||
searched_site, site_name));
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
current_pos++;
|
||||
set_list[current_pos] = NULL;
|
||||
|
||||
*pset_list = talloc_move(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 *dns_domain = lp_dnsdomain(lp_ctx);
|
||||
const char *netbios_domain = lp_workgroup(lp_ctx);
|
||||
struct dfs_referral_resp resp;
|
||||
struct dfs_referral_type *tab;
|
||||
struct dfs_referral_type *referral;
|
||||
const char *referral_str;
|
||||
/* In the future this needs to be fetched from the ldb */
|
||||
uint32_t found_domain = 2;
|
||||
uint32_t current_pos = 0;
|
||||
TALLOC_CTX *context;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (dfsreq->max_referral_level < 3) {
|
||||
DEBUG(2,("invalid max_referral_level %u\n",
|
||||
dfsreq->max_referral_level));
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
context = talloc_new(ctx);
|
||||
NT_STATUS_HAVE_NO_MEMORY(context);
|
||||
|
||||
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);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(referral, context);
|
||||
referral_str = talloc_asprintf(referral, "\\%s", dns_domain);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(referral_str, context);
|
||||
status = fill_domain_dfs_referraltype(referral, 3,
|
||||
referral_str,
|
||||
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);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(referral, context);
|
||||
referral_str = talloc_asprintf(referral, "\\%s", netbios_domain);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(referral_str, context);
|
||||
status = fill_domain_dfs_referraltype(referral, 3,
|
||||
referral_str,
|
||||
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, context,
|
||||
&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) {
|
||||
bool ok = false;
|
||||
|
||||
DEBUG(3, ("Blob is too big for the output buffer "
|
||||
"size %u max %u\n",
|
||||
(unsigned int)outblob.length, trans->in.max_data));
|
||||
|
||||
if (trans->in.max_data != MAX_DFS_RESPONSE) {
|
||||
/* As specified in MS-DFSC.pdf 3.3.5.2 */
|
||||
talloc_free(context);
|
||||
return STATUS_BUFFER_OVERFLOW;
|
||||
}
|
||||
|
||||
/*
|
||||
* The answer is too big, so let's remove some answers
|
||||
*/
|
||||
while (!ok && resp.nb_referrals > 2) {
|
||||
data_blob_free(&outblob);
|
||||
|
||||
/*
|
||||
* Let's scrap the first referral (for now)
|
||||
*/
|
||||
resp.nb_referrals -= 1;
|
||||
resp.referral_entries += 1;
|
||||
|
||||
ndr_err = ndr_push_struct_blob(&outblob, context,
|
||||
&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"
|
||||
"number of referral %d, actual count: %d",
|
||||
found_domain, resp.nb_referrals));
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok && resp.nb_referrals == 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;
|
||||
}
|
||||
}
|
||||
|
||||
TRANS2_CHECK(trans2_setup_reply(trans, 0, outblob.length, 0));
|
||||
|
||||
trans->out.data = outblob;
|
||||
talloc_steal(ctx, outblob.data);
|
||||
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;
|
||||
bool found = false;
|
||||
bool need_fqdn = false;
|
||||
bool dc_referral = true;
|
||||
unsigned int i;
|
||||
char *tmp;
|
||||
struct dc_set **set;
|
||||
char const **domain_list;
|
||||
struct tsocket_address *remote_address;
|
||||
char *client_addr = NULL;
|
||||
TALLOC_CTX *context;
|
||||
|
||||
if (lp_server_role(lp_ctx) != ROLE_DOMAIN_CONTROLLER) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (dfsreq.max_referral_level < 3) {
|
||||
DEBUG(2,("invalid max_referral_level %u\n",
|
||||
dfsreq.max_referral_level));
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
context = talloc_new(ctx);
|
||||
NT_STATUS_HAVE_NO_MEMORY(context);
|
||||
|
||||
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 = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
remote_address = req->smb_conn->connection->remote_address;
|
||||
if (tsocket_address_is_inet(remote_address, "ip")) {
|
||||
client_addr = tsocket_address_inet_addr_string(remote_address, context);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(client_addr, context);
|
||||
}
|
||||
|
||||
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) {
|
||||
const char **dc_list = NULL;
|
||||
uint32_t num_dcs = 0;
|
||||
struct dfs_referral_type *referral;
|
||||
const char *referral_str;
|
||||
struct dfs_referral_resp resp;
|
||||
|
||||
for(i=0; set[i]; i++) {
|
||||
uint32_t j;
|
||||
|
||||
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] = talloc_move(context, &set[i]->names[j]);
|
||||
}
|
||||
num_dcs = num_dcs + set[i]->count;
|
||||
TALLOC_FREE(set[i]);
|
||||
dc_list[num_dcs] = NULL;
|
||||
}
|
||||
|
||||
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);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(referral, context);
|
||||
|
||||
referral_str = talloc_asprintf(referral, "\\%s",
|
||||
requestedname);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(referral_str, context);
|
||||
|
||||
status = fill_domain_dfs_referraltype(referral, 3,
|
||||
referral_str,
|
||||
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, context,
|
||||
&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;
|
||||
struct dfs_referral_type *tab;
|
||||
struct dfs_referral_resp resp;
|
||||
|
||||
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++) {
|
||||
uint32_t j;
|
||||
|
||||
for(j=0; j< set[i]->count; j++) {
|
||||
struct dfs_referral_type *referral;
|
||||
const char *referral_str;
|
||||
|
||||
referral = talloc(tab, struct dfs_referral_type);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(referral, context);
|
||||
|
||||
referral_str = talloc_asprintf(referral, "\\%s\\%s",
|
||||
set[i]->names[j], share);
|
||||
NT_STATUS_HAVE_NO_MEMORY_AND_FREE(referral_str, context);
|
||||
|
||||
status = fill_normal_dfs_referraltype(referral,
|
||||
dfsreq.max_referral_level,
|
||||
requestedname, referral_str, 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, context,
|
||||
&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 = outblob;
|
||||
talloc_steal(ctx, outblob.data);
|
||||
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;
|
||||
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);
|
||||
NT_STATUS_HAVE_NO_MEMORY(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(&trans->in.params, op,
|
||||
&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: %u\n",
|
||||
dfsreq.servername, (unsigned int)strlen(dfsreq.servername)));
|
||||
|
||||
/*
|
||||
* If the servername is "" then we are in a case of domain dfs
|
||||
* and the client just searches for the list of local domain
|
||||
* it is attached and also trusted ones.
|
||||
*/
|
||||
requestedname = dfsreq.servername;
|
||||
if (requestedname == NULL || requestedname[0] == '\0') {
|
||||
return dodomain_referral(op, &dfsreq, ldb, trans, lp_ctx);
|
||||
}
|
||||
|
||||
realm = lp_realm(lp_ctx);
|
||||
nbname = lp_netbios_name(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 ||
|
||||
strcasecmp(tmp+1, "sysvol") == 0 ||
|
||||
strcasecmp(tmp+1, "netlogon") == 0) {
|
||||
status = dodc_or_sysvol_referral(op, dfsreq, requestedname,
|
||||
ldb, trans, req, lp_ctx);
|
||||
talloc_free(context);
|
||||
return status;
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
@ -1010,6 +1878,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:
|
||||
|
Loading…
Reference in New Issue
Block a user