From fc94c50a9fe0ef344068feafa21d85adb149b6b3 Mon Sep 17 00:00:00 2001 From: Matthieu Patou Date: Sat, 1 May 2010 22:27:31 +0400 Subject: s4:smb_server: Implement GET_DFS_REFERRAL for domain referral requests Signed-off-by: Stefan Metzmacher --- source4/smb_server/smb/trans2.c | 872 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 871 insertions(+), 1 deletion(-) (limited to 'source4') diff --git a/source4/smb_server/smb/trans2.c b/source4/smb_server/smb/trans2.c index eddbf00b28..e7e8edc7a6 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@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; ireferral.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; icount; 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; icount; 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; jcount; 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: -- cgit