summaryrefslogtreecommitdiffstats
path: root/source3/libnet
diff options
context:
space:
mode:
Diffstat (limited to 'source3/libnet')
-rw-r--r--source3/libnet/libnet_dssync.c724
-rw-r--r--source3/libnet/libnet_dssync.h73
-rw-r--r--source3/libnet/libnet_dssync_keytab.c609
-rw-r--r--source3/libnet/libnet_dssync_passdb.c1955
-rw-r--r--source3/libnet/libnet_join.c3281
-rw-r--r--source3/libnet/libnet_join.h40
-rw-r--r--source3/libnet/libnet_join_offline.c441
-rw-r--r--source3/libnet/libnet_join_offline.h26
-rw-r--r--source3/libnet/libnet_keytab.c457
-rw-r--r--source3/libnet/libnet_keytab.h61
-rw-r--r--source3/libnet/netapi.pc.in11
11 files changed, 7678 insertions, 0 deletions
diff --git a/source3/libnet/libnet_dssync.c b/source3/libnet/libnet_dssync.c
new file mode 100644
index 0000000..e593ae8
--- /dev/null
+++ b/source3/libnet/libnet_dssync.c
@@ -0,0 +1,724 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan (metze) Metzmacher 2005
+ Copyright (C) Guenther Deschner 2008
+ Copyright (C) Michael Adam 2008
+
+ 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 "libnet/libnet_dssync.h"
+#include "rpc_client/cli_pipe.h"
+#include "../libcli/drsuapi/drsuapi.h"
+#include "../librpc/gen_ndr/ndr_drsuapi_c.h"
+
+/****************************************************************
+****************************************************************/
+
+static int libnet_dssync_free_context(struct dssync_context *ctx)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+
+ if (!ctx) {
+ return 0;
+ }
+
+ if (is_valid_policy_hnd(&ctx->bind_handle) && ctx->cli) {
+ b = ctx->cli->binding_handle;
+ dcerpc_drsuapi_DsUnbind(b, ctx, &ctx->bind_handle, &result);
+ }
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS libnet_dssync_init_context(TALLOC_CTX *mem_ctx,
+ struct dssync_context **ctx_p)
+{
+ struct dssync_context *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct dssync_context);
+ NT_STATUS_HAVE_NO_MEMORY(ctx);
+
+ talloc_set_destructor(ctx, libnet_dssync_free_context);
+ ctx->clean_old_entries = false;
+
+ *ctx_p = ctx;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static void parse_obj_identifier(struct drsuapi_DsReplicaObjectIdentifier *id,
+ uint32_t *rid)
+{
+ if (!id || !rid) {
+ return;
+ }
+
+ *rid = 0;
+
+ if (id->sid.num_auths > 0) {
+ *rid = id->sid.sub_auths[id->sid.num_auths - 1];
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+static void libnet_dssync_decrypt_attributes(TALLOC_CTX *mem_ctx,
+ DATA_BLOB *session_key,
+ struct drsuapi_DsReplicaObjectListItemEx *cur)
+{
+ for (; cur; cur = cur->next_object) {
+
+ uint32_t i;
+ uint32_t rid = 0;
+
+ parse_obj_identifier(cur->object.identifier, &rid);
+
+ for (i=0; i < cur->object.attribute_ctr.num_attributes; i++) {
+
+ struct drsuapi_DsReplicaAttribute *attr;
+
+ attr = &cur->object.attribute_ctr.attributes[i];
+
+ if (attr->value_ctr.num_values < 1) {
+ continue;
+ }
+
+ if (!attr->value_ctr.values[0].blob) {
+ continue;
+ }
+
+ drsuapi_decrypt_attribute(mem_ctx,
+ session_key,
+ rid,
+ 0,
+ attr);
+ }
+ }
+}
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_dssync_bind(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+ WERROR werr;
+
+ struct GUID bind_guid;
+ struct drsuapi_DsBindInfoCtr bind_info;
+ struct drsuapi_DsBindInfo28 info28;
+ struct dcerpc_binding_handle *b = ctx->cli->binding_handle;
+
+ ZERO_STRUCT(info28);
+
+ GUID_from_string(DRSUAPI_DS_BIND_GUID, &bind_guid);
+
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_BASE;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT;
+ info28.site_guid = GUID_zero();
+ info28.pid = 508;
+ info28.repl_epoch = 0;
+
+ bind_info.length = 28;
+ bind_info.info.info28 = info28;
+
+ status = dcerpc_drsuapi_DsBind(b, mem_ctx,
+ &bind_guid,
+ &bind_info,
+ &ctx->bind_handle,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+
+ ZERO_STRUCT(ctx->remote_info28);
+ switch (bind_info.length) {
+ case 24: {
+ struct drsuapi_DsBindInfo24 *info24;
+ info24 = &bind_info.info.info24;
+ ctx->remote_info28.site_guid = info24->site_guid;
+ ctx->remote_info28.supported_extensions = info24->supported_extensions;
+ ctx->remote_info28.pid = info24->pid;
+ ctx->remote_info28.repl_epoch = 0;
+ break;
+ }
+ case 28: {
+ ctx->remote_info28 = bind_info.info.info28;
+ break;
+ }
+ case 32: {
+ struct drsuapi_DsBindInfo32 *info32;
+ info32 = &bind_info.info.info32;
+ ctx->remote_info28.site_guid = info32->site_guid;
+ ctx->remote_info28.supported_extensions = info32->supported_extensions;
+ ctx->remote_info28.pid = info32->pid;
+ ctx->remote_info28.repl_epoch = info32->repl_epoch;
+ break;
+ }
+ case 48: {
+ struct drsuapi_DsBindInfo48 *info48;
+ info48 = &bind_info.info.info48;
+ ctx->remote_info28.site_guid = info48->site_guid;
+ ctx->remote_info28.supported_extensions = info48->supported_extensions;
+ ctx->remote_info28.pid = info48->pid;
+ ctx->remote_info28.repl_epoch = info48->repl_epoch;
+ break;
+ }
+ case 52: {
+ struct drsuapi_DsBindInfo52 *info52;
+ info52 = &bind_info.info.info52;
+ ctx->remote_info28.site_guid = info52->site_guid;
+ ctx->remote_info28.supported_extensions = info52->supported_extensions;
+ ctx->remote_info28.pid = info52->pid;
+ ctx->remote_info28.repl_epoch = info52->repl_epoch;
+ break;
+ }
+ default:
+ DEBUG(1, ("Warning: invalid info length in bind info: %d\n",
+ bind_info.length));
+ break;
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_dssync_lookup_nc(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t level = 1;
+ union drsuapi_DsNameRequest req;
+ uint32_t level_out;
+ struct drsuapi_DsNameString names[1];
+ union drsuapi_DsNameCtr ctr;
+ struct dcerpc_binding_handle *b = ctx->cli->binding_handle;
+
+ names[0].str = talloc_asprintf(mem_ctx, "%s\\", ctx->domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(names[0].str);
+
+ req.req1.codepage = 1252; /* german */
+ req.req1.language = 0x00000407; /* german */
+ req.req1.count = 1;
+ req.req1.names = names;
+ req.req1.format_flags = DRSUAPI_DS_NAME_FLAG_NO_FLAGS;
+ req.req1.format_offered = DRSUAPI_DS_NAME_FORMAT_UNKNOWN;
+ req.req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779;
+
+ status = dcerpc_drsuapi_DsCrackNames(b, mem_ctx,
+ &ctx->bind_handle,
+ level,
+ &req,
+ &level_out,
+ &ctr,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to lookup DN for domain name: %s",
+ get_friendly_nt_error_msg(status));
+ return status;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to lookup DN for domain name: %s",
+ get_friendly_werror_msg(werr));
+ return werror_to_ntstatus(werr);
+ }
+
+ if (ctr.ctr1->count != 1) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (ctr.ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ctx->nc_dn = talloc_strdup(mem_ctx, ctr.ctr1->array[0].result_name);
+ NT_STATUS_HAVE_NO_MEMORY(ctx->nc_dn);
+
+ if (!ctx->dns_domain_name) {
+ ctx->dns_domain_name = talloc_strdup_upper(mem_ctx,
+ ctr.ctr1->array[0].dns_domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(ctx->dns_domain_name);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_dssync_init(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+
+ status = libnet_dssync_bind(mem_ctx, ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!ctx->nc_dn) {
+ status = libnet_dssync_lookup_nc(mem_ctx, ctx);
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_dssync_build_request(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx,
+ const char *dn,
+ struct replUpToDateVectorBlob *utdv,
+ uint32_t *plevel,
+ union drsuapi_DsGetNCChangesRequest *preq)
+{
+ NTSTATUS status;
+ uint32_t count;
+ uint32_t level;
+ union drsuapi_DsGetNCChangesRequest req;
+ enum drsuapi_DsExtendedOperation extended_op;
+ struct drsuapi_DsReplicaObjectIdentifier *nc = NULL;
+ struct drsuapi_DsReplicaCursorCtrEx *cursors = NULL;
+
+ uint32_t replica_flags = DRSUAPI_DRS_WRIT_REP |
+ DRSUAPI_DRS_INIT_SYNC |
+ DRSUAPI_DRS_PER_SYNC |
+ DRSUAPI_DRS_GET_ANC |
+ DRSUAPI_DRS_NEVER_SYNCED;
+
+ ZERO_STRUCT(req);
+
+ if (ctx->remote_info28.supported_extensions
+ & DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8)
+ {
+ level = 8;
+ } else {
+ level = 5;
+ }
+
+ nc = talloc_zero(mem_ctx, struct drsuapi_DsReplicaObjectIdentifier);
+ if (!nc) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ nc->dn = dn;
+ nc->guid = GUID_zero();
+ nc->sid = (struct dom_sid) {0};
+
+ if (!ctx->single_object_replication &&
+ !ctx->force_full_replication && utdv)
+ {
+ cursors = talloc_zero(mem_ctx,
+ struct drsuapi_DsReplicaCursorCtrEx);
+ if (!cursors) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ switch (utdv->version) {
+ case 1:
+ cursors->count = utdv->ctr.ctr1.count;
+ cursors->cursors = utdv->ctr.ctr1.cursors;
+ break;
+ case 2:
+ cursors->count = utdv->ctr.ctr2.count;
+ cursors->cursors = talloc_array(cursors,
+ struct drsuapi_DsReplicaCursor,
+ cursors->count);
+ if (!cursors->cursors) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ for (count = 0; count < cursors->count; count++) {
+ cursors->cursors[count].source_dsa_invocation_id =
+ utdv->ctr.ctr2.cursors[count].source_dsa_invocation_id;
+ cursors->cursors[count].highest_usn =
+ utdv->ctr.ctr2.cursors[count].highest_usn;
+ }
+ break;
+ }
+ }
+
+ if (ctx->single_object_replication) {
+ extended_op = DRSUAPI_EXOP_REPL_OBJ;
+ } else {
+ extended_op = DRSUAPI_EXOP_NONE;
+ }
+
+ if (level == 8) {
+ req.req8.naming_context = nc;
+ req.req8.replica_flags = replica_flags;
+ req.req8.max_object_count = 402;
+ req.req8.max_ndr_size = 402116;
+ req.req8.uptodateness_vector = cursors;
+ req.req8.extended_op = extended_op;
+ } else if (level == 5) {
+ req.req5.naming_context = nc;
+ req.req5.replica_flags = replica_flags;
+ req.req5.max_object_count = 402;
+ req.req5.max_ndr_size = 402116;
+ req.req5.uptodateness_vector = cursors;
+ req.req5.extended_op = extended_op;
+ } else {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if (plevel) {
+ *plevel = level;
+ }
+
+ if (preq) {
+ *preq = req;
+ }
+
+ return NT_STATUS_OK;
+
+fail:
+ TALLOC_FREE(nc);
+ TALLOC_FREE(cursors);
+ return status;
+}
+
+static NTSTATUS libnet_dssync_getncchanges(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx,
+ uint32_t level,
+ union drsuapi_DsGetNCChangesRequest *req,
+ struct replUpToDateVectorBlob **pnew_utdv)
+{
+ NTSTATUS status;
+ WERROR werr;
+ union drsuapi_DsGetNCChangesCtr ctr;
+ struct drsuapi_DsGetNCChangesCtr1 *ctr1 = NULL;
+ struct drsuapi_DsGetNCChangesCtr6 *ctr6 = NULL;
+ struct replUpToDateVectorBlob *new_utdv = NULL;
+ uint32_t level_out = 0;
+ uint32_t out_level = 0;
+ int y;
+ bool last_query;
+ struct dcerpc_binding_handle *b = ctx->cli->binding_handle;
+
+ if (!ctx->single_object_replication) {
+ new_utdv = talloc_zero(mem_ctx, struct replUpToDateVectorBlob);
+ if (!new_utdv) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ for (y=0, last_query = false; !last_query; y++) {
+ struct drsuapi_DsReplicaObjectListItemEx *first_object = NULL;
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr = NULL;
+ uint32_t linked_attributes_count = 0;
+ struct drsuapi_DsReplicaLinkedAttribute *linked_attributes = NULL;
+
+ if (level == 8) {
+ DEBUG(1,("start[%d] tmp_higest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)req->req8.highwatermark.tmp_highest_usn,
+ (long long)req->req8.highwatermark.highest_usn));
+ } else if (level == 5) {
+ DEBUG(1,("start[%d] tmp_higest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)req->req5.highwatermark.tmp_highest_usn,
+ (long long)req->req5.highwatermark.highest_usn));
+ }
+
+ status = dcerpc_drsuapi_DsGetNCChanges(b, mem_ctx,
+ &ctx->bind_handle,
+ level,
+ req,
+ &level_out,
+ &ctr,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to get NC Changes: %s",
+ get_friendly_nt_error_msg(status));
+ goto out;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to get NC Changes: %s",
+ get_friendly_werror_msg(werr));
+ goto out;
+ }
+
+ if (level_out == 1) {
+ out_level = 1;
+ ctr1 = &ctr.ctr1;
+ } else if (level_out == 2 && ctr.ctr2.mszip1.ts) {
+ out_level = 1;
+ ctr1 = &ctr.ctr2.mszip1.ts->ctr1;
+ } else if (level_out == 6) {
+ out_level = 6;
+ ctr6 = &ctr.ctr6;
+ } else if (level_out == 7
+ && ctr.ctr7.level == 6
+ && ctr.ctr7.type == DRSUAPI_COMPRESSION_TYPE_MSZIP
+ && ctr.ctr7.ctr.mszip6.ts) {
+ out_level = 6;
+ ctr6 = &ctr.ctr7.ctr.mszip6.ts->ctr6;
+ } else if (level_out == 7
+ && ctr.ctr7.level == 6
+ && ctr.ctr7.type == DRSUAPI_COMPRESSION_TYPE_XPRESS
+ && ctr.ctr7.ctr.xpress6.ts) {
+ out_level = 6;
+ ctr6 = &ctr.ctr7.ctr.xpress6.ts->ctr6;
+ }
+
+ if (out_level == 1) {
+ DEBUG(1,("end[%d] tmp_highest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)ctr1->new_highwatermark.tmp_highest_usn,
+ (long long)ctr1->new_highwatermark.highest_usn));
+
+ first_object = ctr1->first_object;
+ mapping_ctr = &ctr1->mapping_ctr;
+
+ if (ctr1->more_data) {
+ req->req5.highwatermark = ctr1->new_highwatermark;
+ } else {
+ last_query = true;
+ if (ctr1->uptodateness_vector &&
+ !ctx->single_object_replication)
+ {
+ new_utdv->version = 1;
+ new_utdv->ctr.ctr1.count =
+ ctr1->uptodateness_vector->count;
+ new_utdv->ctr.ctr1.cursors =
+ ctr1->uptodateness_vector->cursors;
+ }
+ }
+ } else if (out_level == 6) {
+ DEBUG(1,("end[%d] tmp_highest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)ctr6->new_highwatermark.tmp_highest_usn,
+ (long long)ctr6->new_highwatermark.highest_usn));
+
+ first_object = ctr6->first_object;
+ mapping_ctr = &ctr6->mapping_ctr;
+
+ linked_attributes = ctr6->linked_attributes;
+ linked_attributes_count = ctr6->linked_attributes_count;
+
+ if (ctr6->more_data) {
+ req->req8.highwatermark = ctr6->new_highwatermark;
+ } else {
+ last_query = true;
+ if (ctr6->uptodateness_vector &&
+ !ctx->single_object_replication)
+ {
+ new_utdv->version = 2;
+ new_utdv->ctr.ctr2.count =
+ ctr6->uptodateness_vector->count;
+ new_utdv->ctr.ctr2.cursors =
+ ctr6->uptodateness_vector->cursors;
+ }
+ }
+ }
+
+ status = cli_get_session_key(mem_ctx, ctx->cli, &ctx->session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to get Session Key: %s",
+ nt_errstr(status));
+ goto out;
+ }
+
+ libnet_dssync_decrypt_attributes(mem_ctx,
+ &ctx->session_key,
+ first_object);
+
+ if (ctx->ops->process_objects) {
+ status = ctx->ops->process_objects(ctx, mem_ctx,
+ first_object,
+ mapping_ctr);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call processing function: %s",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ if (linked_attributes_count == 0) {
+ continue;
+ }
+
+ if (ctx->ops->process_links) {
+ status = ctx->ops->process_links(ctx, mem_ctx,
+ linked_attributes_count,
+ linked_attributes,
+ mapping_ctr);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call processing function: %s",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+ }
+
+ *pnew_utdv = new_utdv;
+
+out:
+ return status;
+}
+
+static NTSTATUS libnet_dssync_process(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+
+ uint32_t level = 0;
+ union drsuapi_DsGetNCChangesRequest req;
+ struct replUpToDateVectorBlob *old_utdv = NULL;
+ struct replUpToDateVectorBlob *pnew_utdv = NULL;
+ const char **dns;
+ uint32_t dn_count;
+ uint32_t count;
+
+ if (ctx->ops->startup) {
+ status = ctx->ops->startup(ctx, mem_ctx, &old_utdv);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call startup operation: %s",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ if (ctx->single_object_replication && ctx->object_dns) {
+ dns = ctx->object_dns;
+ dn_count = ctx->object_count;
+ } else {
+ dns = &ctx->nc_dn;
+ dn_count = 1;
+ }
+
+ status = NT_STATUS_OK;
+
+ for (count=0; count < dn_count; count++) {
+ status = libnet_dssync_build_request(mem_ctx, ctx,
+ dns[count],
+ old_utdv, &level,
+ &req);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = libnet_dssync_getncchanges(mem_ctx, ctx, level, &req,
+ &pnew_utdv);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!ctx->error_message) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call DsGetNCCHanges: %s",
+ nt_errstr(status));
+ }
+ goto out;
+ }
+ }
+
+ if (ctx->ops->finish) {
+ status = ctx->ops->finish(ctx, mem_ctx, pnew_utdv);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call finishing operation: %s",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ out:
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS libnet_dssync(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = libnet_dssync_init(tmp_ctx, ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = libnet_dssync_process(tmp_ctx, ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ out:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
diff --git a/source3/libnet/libnet_dssync.h b/source3/libnet/libnet_dssync.h
new file mode 100644
index 0000000..d426d8b
--- /dev/null
+++ b/source3/libnet/libnet_dssync.h
@@ -0,0 +1,73 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Support
+ * Copyright (C) Guenther Deschner 2008
+ * Copyright (C) Michael Adam 2008
+ *
+ * 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 "../librpc/gen_ndr/drsuapi.h"
+#include "../librpc/gen_ndr/drsblobs.h"
+
+struct dssync_context;
+
+struct dssync_ops {
+ NTSTATUS (*startup)(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob **pold_utdv);
+ NTSTATUS (*process_objects)(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *objects,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mappings);
+ NTSTATUS (*process_links)(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ uint32_t count,
+ struct drsuapi_DsReplicaLinkedAttribute *links,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mappings);
+ NTSTATUS (*finish)(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob *new_utdv);
+};
+
+struct dssync_context {
+ const char *domain_name;
+ const char *dns_domain_name;
+ struct rpc_pipe_client *cli;
+ const char *nc_dn;
+ bool single_object_replication;
+ bool force_full_replication;
+ bool clean_old_entries;
+ uint32_t object_count;
+ const char **object_dns;
+ struct policy_handle bind_handle;
+ DATA_BLOB session_key;
+ const char *output_filename;
+ struct drsuapi_DsBindInfo28 remote_info28;
+
+ void *private_data;
+
+ const struct dssync_ops *ops;
+
+ char *result_message;
+ char *error_message;
+};
+
+extern const struct dssync_ops libnet_dssync_keytab_ops;
+extern const struct dssync_ops libnet_dssync_passdb_ops;
+
+/* The following definitions come from libnet/libnet_dssync.c */
+
+NTSTATUS libnet_dssync_init_context(TALLOC_CTX *mem_ctx,
+ struct dssync_context **ctx_p);
+NTSTATUS libnet_dssync(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx);
diff --git a/source3/libnet/libnet_dssync_keytab.c b/source3/libnet/libnet_dssync_keytab.c
new file mode 100644
index 0000000..8999a35
--- /dev/null
+++ b/source3/libnet/libnet_dssync_keytab.c
@@ -0,0 +1,609 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Guenther Deschner <gd@samba.org> 2008
+ Copyright (C) Michael Adam 2008
+
+ 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 "smb_krb5.h"
+#include "libnet/libnet_dssync.h"
+#include "libnet/libnet_keytab.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+
+#if defined(HAVE_ADS)
+
+static NTSTATUS keytab_startup(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob **pold_utdv)
+{
+ krb5_error_code ret = 0;
+ struct libnet_keytab_context *keytab_ctx;
+ struct libnet_keytab_entry *entry;
+ struct replUpToDateVectorBlob *old_utdv = NULL;
+ char *principal;
+
+ ret = libnet_keytab_init(mem_ctx, ctx->output_filename, &keytab_ctx);
+ if (ret) {
+ return krb5_to_nt_status(ret);
+ }
+
+ keytab_ctx->dns_domain_name = ctx->dns_domain_name;
+ keytab_ctx->clean_old_entries = ctx->clean_old_entries;
+ ctx->private_data = keytab_ctx;
+
+ principal = talloc_asprintf(mem_ctx, "UTDV/%s@%s",
+ ctx->nc_dn, ctx->dns_domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(principal);
+
+ entry = libnet_keytab_search(keytab_ctx, principal, 0, ENCTYPE_NULL,
+ mem_ctx);
+ if (entry) {
+ enum ndr_err_code ndr_err;
+ old_utdv = talloc(mem_ctx, struct replUpToDateVectorBlob);
+
+ ndr_err = ndr_pull_struct_blob(&entry->password, old_utdv, old_utdv,
+ (ndr_pull_flags_fn_t)ndr_pull_replUpToDateVectorBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to pull UpToDateVector: %s",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(replUpToDateVectorBlob, old_utdv);
+ }
+ }
+
+ if (pold_utdv) {
+ *pold_utdv = old_utdv;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS keytab_finish(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob *new_utdv)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ krb5_error_code ret = 0;
+ struct libnet_keytab_context *keytab_ctx =
+ (struct libnet_keytab_context *)ctx->private_data;
+
+ if (new_utdv) {
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(replUpToDateVectorBlob, new_utdv);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, new_utdv,
+ (ndr_push_flags_fn_t)ndr_push_replUpToDateVectorBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to push UpToDateVector: %s",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, keytab_ctx, 0,
+ ctx->nc_dn, "UTDV",
+ ENCTYPE_NULL,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ ret = libnet_keytab_add(keytab_ctx);
+ if (ret) {
+ status = krb5_to_nt_status(ret);
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to add entries to keytab %s: %s",
+ keytab_ctx->keytab_name, error_message(ret));
+ goto done;
+ }
+
+ ctx->result_message = talloc_asprintf(ctx,
+ "Vampired %d accounts to keytab %s",
+ keytab_ctx->count,
+ keytab_ctx->keytab_name);
+
+done:
+ TALLOC_FREE(keytab_ctx);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS parse_supplemental_credentials(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *blob,
+ struct package_PrimaryKerberosCtr3 **pkb3,
+ struct package_PrimaryKerberosCtr4 **pkb4)
+{
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ struct supplementalCredentialsBlob scb;
+ struct supplementalCredentialsPackage *scpk = NULL;
+ DATA_BLOB scpk_blob;
+ struct package_PrimaryKerberosBlob *pkb;
+ bool newer_keys = false;
+ uint32_t j;
+
+ ndr_err = ndr_pull_struct_blob_all(blob, mem_ctx, &scb,
+ (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+ if ((scb.sub.signature != SUPPLEMENTAL_CREDENTIALS_SIGNATURE)
+ && (scb.sub.num_packages != 0))
+ {
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(supplementalCredentialsBlob, &scb);
+ }
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+ for (j=0; j < scb.sub.num_packages; j++) {
+ if (strcmp("Primary:Kerberos-Newer-Keys",
+ scb.sub.packages[j].name) == 0)
+ {
+ scpk = &scb.sub.packages[j];
+ if (!scpk->data || !scpk->data[0]) {
+ scpk = NULL;
+ continue;
+ }
+ newer_keys = true;
+ break;
+ } else if (strcmp("Primary:Kerberos",
+ scb.sub.packages[j].name) == 0)
+ {
+ /*
+ * grab this but don't break here:
+ * there might still be newer-keys ...
+ */
+ scpk = &scb.sub.packages[j];
+ if (!scpk->data || !scpk->data[0]) {
+ scpk = NULL;
+ }
+ }
+ }
+
+ if (!scpk) {
+ /* no data */
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ scpk_blob = strhex_to_data_blob(mem_ctx, scpk->data);
+ if (!scpk_blob.data) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ pkb = talloc_zero(mem_ctx, struct package_PrimaryKerberosBlob);
+ if (!pkb) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ ndr_err = ndr_pull_struct_blob(&scpk_blob, mem_ctx, pkb,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+
+ if (!newer_keys && pkb->version != 3) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (newer_keys && pkb->version != 4) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (pkb->version == 4 && pkb4) {
+ *pkb4 = &pkb->ctr.ctr4;
+ } else if (pkb->version == 3 && pkb3) {
+ *pkb3 = &pkb->ctr.ctr3;
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+ return status;
+}
+
+static NTSTATUS parse_object(TALLOC_CTX *mem_ctx,
+ struct libnet_keytab_context *ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ uchar nt_passwd[16];
+ DATA_BLOB *blob;
+ int i = 0;
+ struct drsuapi_DsReplicaAttribute *attr;
+ bool got_pwd = false;
+
+ struct package_PrimaryKerberosCtr3 *pkb3 = NULL;
+ struct package_PrimaryKerberosCtr4 *pkb4 = NULL;
+
+ char *object_dn = NULL;
+ char *upn = NULL;
+ char **spn = NULL;
+ uint32_t num_spns = 0;
+ char *name = NULL;
+ uint32_t kvno = 0;
+ uint32_t uacc = 0;
+ uint32_t sam_type = 0;
+
+ uint32_t pwd_history_len = 0;
+ uint8_t *pwd_history = NULL;
+
+ ZERO_STRUCT(nt_passwd);
+
+ object_dn = talloc_strdup(mem_ctx, cur->object.identifier->dn);
+ if (!object_dn) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(3, ("parsing object '%s'\n", object_dn));
+
+ for (i=0; i < cur->object.attribute_ctr.num_attributes; i++) {
+
+ attr = &cur->object.attribute_ctr.attributes[i];
+
+ if (attr->attid == DRSUAPI_ATTID_servicePrincipalName) {
+ uint32_t count;
+ num_spns = attr->value_ctr.num_values;
+ spn = talloc_array(mem_ctx, char *, num_spns);
+ for (count = 0; count < num_spns; count++) {
+ blob = attr->value_ctr.values[count].blob;
+ pull_string_talloc(spn, NULL, 0,
+ &spn[count],
+ blob->data, blob->length,
+ STR_UNICODE);
+ }
+ }
+
+ if (attr->value_ctr.num_values != 1) {
+ continue;
+ }
+
+ if (!attr->value_ctr.values[0].blob) {
+ continue;
+ }
+
+ blob = attr->value_ctr.values[0].blob;
+
+ switch (attr->attid) {
+ case DRSUAPI_ATTID_unicodePwd:
+
+ if (blob->length != 16) {
+ break;
+ }
+
+ memcpy(&nt_passwd, blob->data, 16);
+ got_pwd = true;
+
+ /* pick the kvno from the meta_data version,
+ * thanks, metze, for explaining this */
+
+ if (!cur->meta_data_ctr) {
+ break;
+ }
+ if (cur->meta_data_ctr->count !=
+ cur->object.attribute_ctr.num_attributes) {
+ break;
+ }
+ kvno = cur->meta_data_ctr->meta_data[i].version;
+ break;
+ case DRSUAPI_ATTID_ntPwdHistory:
+ pwd_history_len = blob->length / 16;
+ pwd_history = blob->data;
+ break;
+ case DRSUAPI_ATTID_userPrincipalName:
+ pull_string_talloc(mem_ctx, NULL, 0, &upn,
+ blob->data, blob->length,
+ STR_UNICODE);
+ break;
+ case DRSUAPI_ATTID_sAMAccountName:
+ pull_string_talloc(mem_ctx, NULL, 0, &name,
+ blob->data, blob->length,
+ STR_UNICODE);
+ break;
+ case DRSUAPI_ATTID_sAMAccountType:
+ sam_type = IVAL(blob->data, 0);
+ break;
+ case DRSUAPI_ATTID_userAccountControl:
+ uacc = IVAL(blob->data, 0);
+ break;
+ case DRSUAPI_ATTID_supplementalCredentials:
+ status = parse_supplemental_credentials(mem_ctx,
+ blob,
+ &pkb3,
+ &pkb4);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("parsing of supplemental "
+ "credentials failed: %s\n",
+ nt_errstr(status)));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!got_pwd) {
+ DEBUG(10, ("no password (unicodePwd) found - skipping.\n"));
+ return NT_STATUS_OK;
+ }
+
+ if (name) {
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, 0, object_dn,
+ "SAMACCOUNTNAME",
+ ENCTYPE_NULL,
+ data_blob_talloc(mem_ctx, name,
+ strlen(name) + 1));
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+ /* look into keytab ... */
+ struct libnet_keytab_entry *entry = NULL;
+ char *principal = NULL;
+
+ DEBUG(10, ("looking for SAMACCOUNTNAME/%s@%s in keytayb...\n",
+ object_dn, ctx->dns_domain_name));
+
+ principal = talloc_asprintf(mem_ctx, "%s/%s@%s",
+ "SAMACCOUNTNAME",
+ object_dn,
+ ctx->dns_domain_name);
+ if (!principal) {
+ DEBUG(1, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ entry = libnet_keytab_search(ctx, principal, 0, ENCTYPE_NULL,
+ mem_ctx);
+ if (entry) {
+ name = (char *)talloc_memdup(mem_ctx,
+ entry->password.data,
+ entry->password.length);
+ if (!name) {
+ DEBUG(1, ("talloc failed!"));
+ return NT_STATUS_NO_MEMORY;
+ } else {
+ DEBUG(10, ("found name %s\n", name));
+ }
+ TALLOC_FREE(entry);
+ } else {
+ DEBUG(10, ("entry not found\n"));
+ }
+ TALLOC_FREE(principal);
+ }
+
+ if (!name) {
+ DEBUG(10, ("no name (sAMAccountName) found - skipping.\n"));
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(1,("#%02d: %s:%d, ", ctx->count, name, kvno));
+ DEBUGADD(1,("sAMAccountType: 0x%08x, userAccountControl: 0x%08x",
+ sam_type, uacc));
+ if (upn) {
+ DEBUGADD(1,(", upn: %s", upn));
+ }
+ if (num_spns > 0) {
+ DEBUGADD(1, (", spns: ["));
+ for (i = 0; i < num_spns; i++) {
+ DEBUGADD(1, ("%s%s", spn[i],
+ (i+1 == num_spns)?"]":", "));
+ }
+ }
+ DEBUGADD(1,("\n"));
+
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno, name, NULL,
+ ENCTYPE_ARCFOUR_HMAC,
+ data_blob_talloc(mem_ctx, nt_passwd, 16));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* add kerberos keys (if any) */
+
+ if (pkb4) {
+ for (i=0; i < pkb4->num_keys; i++) {
+ if (!pkb4->keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno,
+ name,
+ NULL,
+ pkb4->keys[i].keytype,
+ *pkb4->keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ for (i=0; i < pkb4->num_old_keys; i++) {
+ if (!pkb4->old_keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno - 1,
+ name,
+ NULL,
+ pkb4->old_keys[i].keytype,
+ *pkb4->old_keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ for (i=0; i < pkb4->num_older_keys; i++) {
+ if (!pkb4->older_keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno - 2,
+ name,
+ NULL,
+ pkb4->older_keys[i].keytype,
+ *pkb4->older_keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ }
+
+ if (pkb3) {
+ for (i=0; i < pkb3->num_keys; i++) {
+ if (!pkb3->keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno, name,
+ NULL,
+ pkb3->keys[i].keytype,
+ *pkb3->keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ for (i=0; i < pkb3->num_old_keys; i++) {
+ if (!pkb3->old_keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno - 1,
+ name,
+ NULL,
+ pkb3->old_keys[i].keytype,
+ *pkb3->old_keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ }
+
+ if ((kvno < 0) && (kvno < pwd_history_len)) {
+ return status;
+ }
+
+ /* add password history */
+
+ /* skip first entry */
+ if (got_pwd) {
+ kvno--;
+ i = 1;
+ } else {
+ i = 0;
+ }
+
+ for (; i<pwd_history_len; i++) {
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno--, name, NULL,
+ ENCTYPE_ARCFOUR_HMAC,
+ data_blob_talloc(mem_ctx, &pwd_history[i*16], 16));
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ }
+
+ return status;
+}
+
+static bool dn_is_in_object_list(struct dssync_context *ctx,
+ const char *dn)
+{
+ uint32_t count;
+
+ if (ctx->object_count == 0) {
+ return true;
+ }
+
+ for (count = 0; count < ctx->object_count; count++) {
+ if (strequal(ctx->object_dns[count], dn)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS keytab_process_objects(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct libnet_keytab_context *keytab_ctx =
+ (struct libnet_keytab_context *)ctx->private_data;
+
+ for (; cur; cur = cur->next_object) {
+ /*
+ * When not in single object replication mode,
+ * the object_dn list is used as a positive write filter.
+ */
+ if (!ctx->single_object_replication &&
+ !dn_is_in_object_list(ctx, cur->object.identifier->dn))
+ {
+ continue;
+ }
+
+ status = parse_object(mem_ctx, keytab_ctx, cur);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ out:
+ return status;
+}
+
+#else
+
+static NTSTATUS keytab_startup(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob **pold_utdv)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static NTSTATUS keytab_finish(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob *new_utdv)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static NTSTATUS keytab_process_objects(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+#endif /* defined(HAVE_ADS) */
+
+const struct dssync_ops libnet_dssync_keytab_ops = {
+ .startup = keytab_startup,
+ .process_objects = keytab_process_objects,
+ .finish = keytab_finish,
+};
diff --git a/source3/libnet/libnet_dssync_passdb.c b/source3/libnet/libnet_dssync_passdb.c
new file mode 100644
index 0000000..7d5ef64
--- /dev/null
+++ b/source3/libnet/libnet_dssync_passdb.c
@@ -0,0 +1,1955 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Guenther Deschner <gd@samba.org> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "libnet/libnet_dssync.h"
+#include "../libcli/security/security.h"
+#include "../libds/common/flags.h"
+#include "../librpc/gen_ndr/ndr_drsuapi.h"
+#include "util_tdb.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "../libds/common/flag_mapping.h"
+#include "passdb.h"
+#include "lib/util/base64.h"
+#include "lib/util/string_wrappers.h"
+
+/****************************************************************
+****************************************************************/
+
+struct dssync_passdb {
+ struct pdb_methods *methods;
+ struct db_context *all;
+ struct db_context *aliases;
+ struct db_context *groups;
+};
+
+struct dssync_passdb_obj {
+ struct dssync_passdb_obj *self;
+ uint32_t type;
+ struct drsuapi_DsReplicaObjectListItemEx *cur;
+ TDB_DATA key;
+ TDB_DATA data;
+ struct db_context *members;
+};
+
+struct dssync_passdb_mem {
+ struct dssync_passdb_mem *self;
+ bool active;
+ struct drsuapi_DsReplicaObjectIdentifier3 *cur;
+ struct dssync_passdb_obj *obj;
+ TDB_DATA key;
+ TDB_DATA data;
+};
+
+static NTSTATUS dssync_insert_obj(struct dssync_passdb *pctx,
+ struct db_context *db,
+ struct dssync_passdb_obj *obj)
+{
+ NTSTATUS status;
+ struct db_record *rec;
+ TDB_DATA value;
+
+ rec = dbwrap_fetch_locked(db, talloc_tos(), obj->key);
+ if (rec == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ value = dbwrap_record_get_value(rec);
+ if (value.dsize != 0) {
+ abort();
+ }
+
+ status = dbwrap_record_store(rec, obj->data, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(rec);
+ return status;
+ }
+ TALLOC_FREE(rec);
+ return NT_STATUS_OK;
+}
+
+static struct dssync_passdb_obj *dssync_parse_obj(const TDB_DATA data)
+{
+ struct dssync_passdb_obj *obj;
+
+ if (data.dsize != sizeof(obj)) {
+ return NULL;
+ }
+
+ /*
+ * we need to copy the pointer to avoid alignment problems
+ * on some systems.
+ */
+ memcpy(&obj, data.dptr, sizeof(obj));
+
+ return talloc_get_type_abort(obj, struct dssync_passdb_obj);
+}
+
+static struct dssync_passdb_obj *dssync_search_obj_by_guid(struct dssync_passdb *pctx,
+ struct db_context *db,
+ const struct GUID *guid)
+{
+ struct dssync_passdb_obj *obj;
+ TDB_DATA key;
+ TDB_DATA data;
+ NTSTATUS status;
+
+ key = make_tdb_data((const uint8_t *)(const void *)guid,
+ sizeof(*guid));
+
+ status = dbwrap_fetch(db, talloc_tos(), key, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ obj = dssync_parse_obj(data);
+ return obj;
+}
+
+static NTSTATUS dssync_create_obj(struct dssync_passdb *pctx,
+ struct db_context *db,
+ uint32_t type,
+ struct drsuapi_DsReplicaObjectListItemEx *cur,
+ struct dssync_passdb_obj **_obj)
+{
+ NTSTATUS status;
+ struct dssync_passdb_obj *obj;
+
+ obj = talloc_zero(pctx, struct dssync_passdb_obj);
+ if (obj == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ obj->self = obj;
+ obj->cur = cur;
+ obj->type = type;
+ obj->key = make_tdb_data((const uint8_t *)(void *)&cur->object.identifier->guid,
+ sizeof(cur->object.identifier->guid));
+ obj->data = make_tdb_data((const uint8_t *)(void *)&obj->self,
+ sizeof(obj->self));
+
+ obj->members = db_open_rbt(obj);
+ if (obj->members == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dssync_insert_obj(pctx, db, obj);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(obj);
+ return status;
+ }
+ *_obj = obj;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS dssync_insert_mem(struct dssync_passdb *pctx,
+ struct dssync_passdb_obj *obj,
+ struct dssync_passdb_mem *mem)
+{
+ NTSTATUS status;
+ struct db_record *rec;
+ TDB_DATA value;
+
+ rec = dbwrap_fetch_locked(obj->members, talloc_tos(), mem->key);
+ if (rec == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ value = dbwrap_record_get_value(rec);
+ if (value.dsize != 0) {
+ abort();
+ }
+
+ status = dbwrap_record_store(rec, mem->data, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(rec);
+ return status;
+ }
+ TALLOC_FREE(rec);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS dssync_create_mem(struct dssync_passdb *pctx,
+ struct dssync_passdb_obj *obj,
+ bool active,
+ struct drsuapi_DsReplicaObjectIdentifier3 *cur,
+ struct dssync_passdb_mem **_mem)
+{
+ NTSTATUS status;
+ struct dssync_passdb_mem *mem;
+
+ mem = talloc_zero(pctx, struct dssync_passdb_mem);
+ if (mem == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ mem->self = mem;
+ mem->cur = cur;
+ mem->active = active;
+ mem->obj = NULL;
+ mem->key = make_tdb_data((const uint8_t *)(void *)&cur->guid,
+ sizeof(cur->guid));
+ mem->data = make_tdb_data((const uint8_t *)(void *)&mem->self,
+ sizeof(mem->self));
+
+ status = dssync_insert_mem(pctx, obj, mem);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(obj);
+ return status;
+ }
+ *_mem = mem;
+ return NT_STATUS_OK;
+}
+
+static struct dssync_passdb_mem *dssync_parse_mem(const TDB_DATA data)
+{
+ struct dssync_passdb_mem *mem;
+
+ if (data.dsize != sizeof(mem)) {
+ return NULL;
+ }
+
+ /*
+ * we need to copy the pointer to avoid alignment problems
+ * on some systems.
+ */
+ memcpy(&mem, data.dptr, sizeof(mem));
+
+ return talloc_get_type_abort(mem, struct dssync_passdb_mem);
+}
+
+static NTSTATUS passdb_startup(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob **pold_utdv)
+{
+ NTSTATUS status;
+ struct dssync_passdb *pctx;
+
+ pctx = talloc_zero(mem_ctx, struct dssync_passdb);
+ if (pctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ctx->output_filename) {
+ status = make_pdb_method_name(&pctx->methods, ctx->output_filename);
+ } else {
+ status = make_pdb_method_name(&pctx->methods, lp_passdb_backend());
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ pctx->all = db_open_rbt(pctx);
+ if (pctx->all == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pctx->aliases = db_open_rbt(pctx);
+ if (pctx->aliases == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pctx->groups = db_open_rbt(pctx);
+ if (pctx->groups == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ctx->private_data = pctx;
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+struct dssync_passdb_traverse_amembers {
+ struct dssync_context *ctx;
+ struct dssync_passdb_obj *obj;
+ const char *name;
+ uint32_t idx;
+};
+
+struct dssync_passdb_traverse_aliases {
+ struct dssync_context *ctx;
+ const char *name;
+ uint32_t idx;
+};
+
+static int dssync_passdb_traverse_amembers(struct db_record *rec,
+ void *private_data)
+{
+ struct dssync_passdb_traverse_amembers *state =
+ (struct dssync_passdb_traverse_amembers *)private_data;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(state->ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_mem *mem;
+ NTSTATUS status;
+ struct dom_sid alias_sid;
+ struct dom_sid member_sid;
+ struct dom_sid_buf buf1, buf2;
+ const char *member_dn;
+ size_t num_members;
+ size_t i;
+ struct dom_sid *members;
+ bool is_member = false;
+ const char *action;
+ TDB_DATA value;
+
+ state->idx++;
+
+ alias_sid = state->obj->cur->object.identifier->sid;
+
+ value = dbwrap_record_get_value(rec);
+ mem = dssync_parse_mem(value);
+ if (mem == NULL) {
+ return -1;
+ }
+
+ member_sid = mem->cur->sid;
+ member_dn = mem->cur->dn;
+
+ mem->obj = dssync_search_obj_by_guid(pctx, pctx->all, &mem->cur->guid);
+ if (mem->obj == NULL) {
+ DEBUG(0,("alias[%s] member[%s] can't resolve member - ignoring\n",
+ dom_sid_str_buf(&alias_sid, &buf1),
+ is_null_sid(&member_sid)?
+ dom_sid_str_buf(&member_sid, &buf2):
+ member_dn));
+ return 0;
+ }
+
+ switch (mem->obj->type) {
+ case ATYPE_DISTRIBUTION_LOCAL_GROUP:
+ case ATYPE_DISTRIBUTION_GLOBAL_GROUP:
+ DEBUG(0, ("alias[%s] ignore distribution group [%s]\n",
+ dom_sid_str_buf(&alias_sid, &buf1),
+ member_dn));
+ return 0;
+ default:
+ break;
+ }
+
+ DEBUG(0,("alias[%s] member[%s]\n",
+ dom_sid_str_buf(&alias_sid, &buf1),
+ dom_sid_str_buf(&member_sid, &buf2)));
+
+ status = pdb_enum_aliasmem(&alias_sid, talloc_tos(),
+ &members, &num_members);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not find current alias members %s - %s\n",
+ dom_sid_str_buf(&alias_sid, &buf1),
+ nt_errstr(status)));
+ return -1;
+ }
+
+ for (i=0; i < num_members; i++) {
+ bool match;
+
+ match = dom_sid_equal(&members[i], &member_sid);
+ if (match) {
+ is_member = true;
+ break;
+ }
+ }
+
+ status = NT_STATUS_OK;
+ action = "none";
+ if (!is_member && mem->active) {
+ action = "add";
+ pdb_add_aliasmem(&alias_sid, &member_sid);
+ } else if (is_member && !mem->active) {
+ action = "delete";
+ pdb_del_aliasmem(&alias_sid, &member_sid);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not %s %s as alias members of %s - %s\n",
+ action,
+ dom_sid_str_buf(&member_sid, &buf1),
+ dom_sid_str_buf(&alias_sid, &buf2),
+ nt_errstr(status)));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dssync_passdb_traverse_aliases(struct db_record *rec,
+ void *private_data)
+{
+ struct dssync_passdb_traverse_aliases *state =
+ (struct dssync_passdb_traverse_aliases *)private_data;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(state->ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_traverse_amembers mstate;
+ struct dssync_passdb_obj *obj;
+ TDB_DATA value;
+ NTSTATUS status;
+
+ state->idx++;
+ if (pctx->methods == NULL) {
+ return -1;
+ }
+
+ value = dbwrap_record_get_value(rec);
+ obj = dssync_parse_obj(value);
+ if (obj == NULL) {
+ return -1;
+ }
+
+ ZERO_STRUCT(mstate);
+ mstate.ctx = state->ctx;
+ mstate.name = "members";
+ mstate.obj = obj;
+ status = dbwrap_traverse_read(obj->members,
+ dssync_passdb_traverse_amembers,
+ &mstate, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+struct dssync_passdb_traverse_gmembers {
+ struct dssync_context *ctx;
+ struct dssync_passdb_obj *obj;
+ const char *name;
+ uint32_t idx;
+};
+
+struct dssync_passdb_traverse_groups {
+ struct dssync_context *ctx;
+ const char *name;
+ uint32_t idx;
+};
+
+static int dssync_passdb_traverse_gmembers(struct db_record *rec,
+ void *private_data)
+{
+ struct dssync_passdb_traverse_gmembers *state =
+ (struct dssync_passdb_traverse_gmembers *)private_data;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(state->ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_mem *mem;
+ NTSTATUS status;
+ char *nt_member = NULL;
+ char **unix_members;
+ struct dom_sid group_sid;
+ struct dom_sid member_sid;
+ struct dom_sid_buf buf1, buf2;
+ struct samu *member = NULL;
+ const char *member_dn = NULL;
+ GROUP_MAP *map;
+ struct group *grp;
+ uint32_t rid;
+ bool is_unix_member = false;
+ TDB_DATA value;
+
+ state->idx++;
+
+ group_sid = state->obj->cur->object.identifier->sid;
+
+ status = dom_sid_split_rid(talloc_tos(), &group_sid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ mem = dssync_parse_mem(value);
+ if (mem == NULL) {
+ return -1;
+ }
+
+ member_sid = mem->cur->sid;
+ member_dn = mem->cur->dn;
+
+ mem->obj = dssync_search_obj_by_guid(pctx, pctx->all, &mem->cur->guid);
+ if (mem->obj == NULL) {
+ DEBUG(0,("group[%s] member[%s] can't resolve member - ignoring\n",
+ dom_sid_str_buf(&group_sid, &buf1),
+ is_null_sid(&member_sid)?
+ dom_sid_str_buf(&member_sid, &buf2):
+ member_dn));
+ return 0;
+ }
+
+ member_sid = mem->obj->cur->object.identifier->sid;
+ member_dn = mem->obj->cur->object.identifier->dn;
+
+ switch (mem->obj->type) {
+ case ATYPE_SECURITY_LOCAL_GROUP:
+ case ATYPE_SECURITY_GLOBAL_GROUP:
+ DEBUG(0, ("Group[%s] ignore member group [%s]\n",
+ dom_sid_str_buf(&group_sid, &buf1),
+ dom_sid_str_buf(&member_sid, &buf2)));
+ return 0;
+
+ case ATYPE_DISTRIBUTION_LOCAL_GROUP:
+ case ATYPE_DISTRIBUTION_GLOBAL_GROUP:
+ DEBUG(0, ("Group[%s] ignore distribution group [%s]\n",
+ dom_sid_str_buf(&group_sid, &buf1),
+ member_dn));
+ return 0;
+ default:
+ break;
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return -1;
+ }
+
+ if (!get_domain_group_from_sid(group_sid, map)) {
+ DEBUG(0, ("Could not find global group %s\n",
+ dom_sid_str_buf(&group_sid, &buf1)));
+ //return NT_STATUS_NO_SUCH_GROUP;
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ if (!(grp = getgrgid(map->gid))) {
+ DEBUG(0, ("Could not find unix group %lu\n",
+ (unsigned long)map->gid));
+ //return NT_STATUS_NO_SUCH_GROUP;
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ TALLOC_FREE(map);
+
+ DEBUG(0,("Group members of %s: ", grp->gr_name));
+
+ if ( !(member = samu_new(talloc_tos())) ) {
+ //return NT_STATUS_NO_MEMORY;
+ return -1;
+ }
+
+ if (!pdb_getsampwsid(member, &member_sid)) {
+ DEBUG(1, ("Found bogus group member: (member_sid=%s group=%s)\n",
+ dom_sid_str_buf(&member_sid, &buf1),
+ grp->gr_name));
+ TALLOC_FREE(member);
+ return -1;
+ }
+
+ if (pdb_get_group_rid(member) == rid) {
+ DEBUGADD(0,("%s(primary),", pdb_get_username(member)));
+ TALLOC_FREE(member);
+ return -1;
+ }
+
+ DEBUGADD(0,("%s,", pdb_get_username(member)));
+ nt_member = talloc_strdup(talloc_tos(), pdb_get_username(member));
+ TALLOC_FREE(member);
+
+ DEBUGADD(0,("\n"));
+
+ unix_members = grp->gr_mem;
+
+ while (*unix_members) {
+ if (strcmp(*unix_members, nt_member) == 0) {
+ is_unix_member = true;
+ break;
+ }
+ unix_members += 1;
+ }
+
+ if (!is_unix_member && mem->active) {
+ smb_add_user_group(grp->gr_name, nt_member);
+ } else if (is_unix_member && !mem->active) {
+ smb_delete_user_group(grp->gr_name, nt_member);
+ }
+
+ return 0;
+}
+
+static int dssync_passdb_traverse_groups(struct db_record *rec,
+ void *private_data)
+{
+ struct dssync_passdb_traverse_groups *state =
+ (struct dssync_passdb_traverse_groups *)private_data;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(state->ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_traverse_gmembers mstate;
+ struct dssync_passdb_obj *obj;
+ TDB_DATA value;
+ NTSTATUS status;
+
+ state->idx++;
+ if (pctx->methods == NULL) {
+ return -1;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ obj = dssync_parse_obj(value);
+ if (obj == NULL) {
+ return -1;
+ }
+
+ ZERO_STRUCT(mstate);
+ mstate.ctx = state->ctx;
+ mstate.name = "members";
+ mstate.obj = obj;
+ status = dbwrap_traverse_read(obj->members,
+ dssync_passdb_traverse_gmembers,
+ &mstate, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static NTSTATUS passdb_finish(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob *new_utdv)
+{
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_traverse_aliases astate;
+ struct dssync_passdb_traverse_groups gstate;
+ NTSTATUS status;
+
+ ZERO_STRUCT(astate);
+ astate.ctx = ctx;
+ astate.name = "aliases";
+ status = dbwrap_traverse_read(pctx->aliases,
+ dssync_passdb_traverse_aliases,
+ &astate, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ZERO_STRUCT(gstate);
+ gstate.ctx = ctx;
+ gstate.name = "groups";
+ status = dbwrap_traverse_read(pctx->groups,
+ dssync_passdb_traverse_groups,
+ &gstate, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ TALLOC_FREE(pctx->methods);
+ TALLOC_FREE(pctx);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS smb_create_user(TALLOC_CTX *mem_ctx,
+ uint32_t acct_flags,
+ const char *account,
+ struct passwd **passwd_p)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct passwd *passwd;
+ char *add_script = NULL;
+
+ passwd = Get_Pwnam_alloc(mem_ctx, account);
+ if (passwd) {
+ *passwd_p = passwd;
+ return NT_STATUS_OK;
+ }
+
+ /* Create appropriate user */
+ if (acct_flags & ACB_NORMAL) {
+ add_script = lp_add_user_script(mem_ctx, lp_sub);
+ } else if ( (acct_flags & ACB_WSTRUST) ||
+ (acct_flags & ACB_SVRTRUST) ||
+ (acct_flags & ACB_DOMTRUST) ) {
+ add_script = lp_add_machine_script(mem_ctx, lp_sub);
+ } else {
+ DEBUG(1, ("Unknown user type: %s\n",
+ pdb_encode_acct_ctrl(acct_flags, NEW_PW_FORMAT_SPACE_PADDED_LEN)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (!add_script) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (*add_script) {
+ int add_ret;
+ add_script = talloc_all_string_sub(mem_ctx, add_script,
+ "%u", account);
+ if (!add_script) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ add_ret = smbrun(add_script, NULL, NULL);
+ DEBUG(add_ret ? 0 : 1,("fetch_account: Running the command `%s' "
+ "gave %d\n", add_script, add_ret));
+ if (add_ret == 0) {
+ smb_nscd_flush_user_cache();
+ }
+ }
+
+ /* try and find the possible unix account again */
+ passwd = Get_Pwnam_alloc(mem_ctx, account);
+ if (!passwd) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ *passwd_p = passwd;
+
+ return NT_STATUS_OK;
+}
+
+static struct drsuapi_DsReplicaAttribute *find_drsuapi_attr(
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid)
+{
+ uint32_t i = 0;
+
+ for (i = 0; i < cur->object.attribute_ctr.num_attributes; i++) {
+ struct drsuapi_DsReplicaAttribute *attr;
+
+ attr = &cur->object.attribute_ctr.attributes[i];
+
+ if (attr->attid == attid) {
+ return attr;
+ }
+ }
+
+ return NULL;
+}
+
+static NTSTATUS find_drsuapi_attr_string(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ char ***_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ char **array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx, char *, attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+ ssize_t ret;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ ret = pull_string_talloc(array, NULL, 0, &array[a],
+ blob->data, blob->length,
+ STR_UNICODE);
+ if (ret == -1) {
+ //TODO
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS find_drsuapi_attr_int32(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ int32_t **_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ int32_t *array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx, int32_t, attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (blob->length != 4) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ array[a] = IVAL(blob->data, 0);
+ }
+
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS find_drsuapi_attr_blob(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ DATA_BLOB **_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ DATA_BLOB *array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx, DATA_BLOB, attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ array[a] = data_blob_talloc(array, blob->data, blob->length);
+ if (array[a].length != blob->length) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS find_drsuapi_attr_int64(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ int64_t **_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ int64_t *array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx, int64_t, attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (blob->length != 8) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ array[a] = BVAL(blob->data, 0);
+ }
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS find_drsuapi_attr_dn(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ struct drsuapi_DsReplicaObjectIdentifier3 **_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ struct drsuapi_DsReplicaObjectIdentifier3 *array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx,
+ struct drsuapi_DsReplicaObjectIdentifier3,
+ attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ /* windows sometimes sends an extra two pad bytes here */
+ ndr_err = ndr_pull_struct_blob(blob, array, &array[a],
+ (ndr_pull_flags_fn_t)ndr_pull_drsuapi_DsReplicaObjectIdentifier3);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+ }
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+#define GET_BLOB_EX(attr, needed) do { \
+ NTSTATUS _status; \
+ uint32_t _cnt; \
+ DATA_BLOB *_vals = NULL; \
+ attr = data_blob_null; \
+ _status = find_drsuapi_attr_blob(mem_ctx, cur, \
+ DRSUAPI_ATTID_ ## attr, \
+ &_cnt, &_vals); \
+ if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
+ if (!needed) { \
+ _status = NT_STATUS_OK; \
+ _cnt = 0; \
+ } \
+ } \
+ if (!NT_STATUS_IS_OK(_status)) { \
+ DEBUG(0,(__location__ "attr[%s] %s\n", \
+ #attr, nt_errstr(_status))); \
+ return _status; \
+ } \
+ if (_cnt == 0) { \
+ if (needed) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
+ } \
+ } else if (_cnt > 1) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_INTERNAL_DB_CORRUPTION; \
+ } else { \
+ attr = _vals[0]; \
+ (void)talloc_steal(mem_ctx, _vals[0].data); \
+ } \
+ talloc_free(_vals); \
+} while(0)
+
+#define GET_STRING_EX(attr, needed) do { \
+ NTSTATUS _status; \
+ uint32_t _cnt; \
+ char **_vals = NULL; \
+ attr = NULL; \
+ _status = find_drsuapi_attr_string(mem_ctx, cur, \
+ DRSUAPI_ATTID_ ## attr, \
+ &_cnt, &_vals); \
+ if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
+ if (!needed) { \
+ _status = NT_STATUS_OK; \
+ _cnt = 0; \
+ } \
+ } \
+ if (!NT_STATUS_IS_OK(_status)) { \
+ DEBUG(0,(__location__ "attr[%s] %s\n", \
+ #attr, nt_errstr(_status))); \
+ return _status; \
+ } \
+ if (_cnt == 0) { \
+ if (needed) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
+ } \
+ } else if (_cnt > 1) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_INTERNAL_DB_CORRUPTION; \
+ } else { \
+ attr = talloc_move(mem_ctx, &_vals[0]); \
+ } \
+ talloc_free(_vals); \
+} while(0)
+
+#define GET_UINT32_EX(attr, needed) do { \
+ NTSTATUS _status; \
+ uint32_t _cnt; \
+ int32_t*_vals = NULL; \
+ attr = 0; \
+ _status = find_drsuapi_attr_int32(mem_ctx, cur, \
+ DRSUAPI_ATTID_ ## attr, \
+ &_cnt, &_vals); \
+ if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
+ if (!needed) { \
+ _status = NT_STATUS_OK; \
+ _cnt = 0; \
+ } \
+ } \
+ if (!NT_STATUS_IS_OK(_status)) { \
+ DEBUG(0,(__location__ "attr[%s] %s\n", \
+ #attr, nt_errstr(_status))); \
+ return _status; \
+ } \
+ if (_cnt == 0) { \
+ if (needed) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
+ } \
+ } else if (_cnt > 1) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_INTERNAL_DB_CORRUPTION; \
+ } else { \
+ attr = (uint32_t)_vals[0]; \
+ } \
+ talloc_free(_vals); \
+} while(0)
+
+#define GET_UINT64_EX(attr, needed) do { \
+ NTSTATUS _status; \
+ uint32_t _cnt; \
+ int64_t *_vals = NULL; \
+ attr = 0; \
+ _status = find_drsuapi_attr_int64(mem_ctx, cur, \
+ DRSUAPI_ATTID_ ## attr, \
+ &_cnt, &_vals); \
+ if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
+ if (!needed) { \
+ _status = NT_STATUS_OK; \
+ _cnt = 0; \
+ } \
+ } \
+ if (!NT_STATUS_IS_OK(_status)) { \
+ DEBUG(0,(__location__ "attr[%s] %s\n", \
+ #attr, nt_errstr(_status))); \
+ return _status; \
+ } \
+ if (_cnt == 0) { \
+ if (needed) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
+ } \
+ } else if (_cnt > 1) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_INTERNAL_DB_CORRUPTION; \
+ } else { \
+ attr = (uint64_t)_vals[0]; \
+ } \
+ talloc_free(_vals); \
+} while(0)
+
+#define GET_BLOB(attr) GET_BLOB_EX(attr, false)
+#define GET_STRING(attr) GET_STRING_EX(attr, false)
+#define GET_UINT32(attr) GET_UINT32_EX(attr, false)
+#define GET_UINT64(attr) GET_UINT64_EX(attr, false)
+
+/* Convert a struct samu_DELTA to a struct samu. */
+#define STRING_CHANGED (old_string && !new_string) ||\
+ (!old_string && new_string) ||\
+ (old_string && new_string && (strcmp(old_string, new_string) != 0))
+
+#define STRING_CHANGED_NC(s1,s2) ((s1) && !(s2)) ||\
+ (!(s1) && (s2)) ||\
+ ((s1) && (s2) && (strcmp((s1), (s2)) != 0))
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS sam_account_from_object(struct samu *account,
+ struct drsuapi_DsReplicaObjectListItemEx *cur)
+{
+ TALLOC_CTX *mem_ctx = account;
+ const char *old_string, *new_string;
+ struct dom_sid_buf buf;
+ time_t unix_time, stored_time;
+ NTSTATUS status;
+
+ NTTIME lastLogon;
+ NTTIME lastLogoff;
+ NTTIME pwdLastSet;
+ NTTIME accountExpires;
+ const char *sAMAccountName;
+ const char *displayName;
+ const char *homeDirectory;
+ const char *homeDrive;
+ const char *scriptPath;
+ const char *profilePath;
+ const char *description;
+ const char *userWorkstations;
+ DATA_BLOB userParameters;
+ struct dom_sid objectSid;
+ uint32_t primaryGroupID;
+ uint32_t userAccountControl;
+ DATA_BLOB logonHours;
+ uint32_t badPwdCount;
+ uint32_t logonCount;
+ DATA_BLOB unicodePwd;
+ DATA_BLOB dBCSPwd;
+
+ uint32_t rid = 0;
+ uint32_t acct_flags;
+ uint32_t units_per_week;
+
+ objectSid = cur->object.identifier->sid;
+ GET_STRING_EX(sAMAccountName, true);
+ DEBUG(0,("sam_account_from_object(%s, %s) start\n",
+ sAMAccountName,
+ dom_sid_str_buf(&objectSid, &buf)));
+ GET_UINT64(lastLogon);
+ GET_UINT64(lastLogoff);
+ GET_UINT64(pwdLastSet);
+ GET_UINT64(accountExpires);
+ GET_STRING(displayName);
+ GET_STRING(homeDirectory);
+ GET_STRING(homeDrive);
+ GET_STRING(scriptPath);
+ GET_STRING(profilePath);
+ GET_STRING(description);
+ GET_STRING(userWorkstations);
+ GET_BLOB(userParameters);
+ GET_UINT32(primaryGroupID);
+ GET_UINT32(userAccountControl);
+ GET_BLOB(logonHours);
+ GET_UINT32(badPwdCount);
+ GET_UINT32(logonCount);
+ GET_BLOB(unicodePwd);
+ GET_BLOB(dBCSPwd);
+
+ status = dom_sid_split_rid(mem_ctx, &objectSid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ acct_flags = ds_uf2acb(userAccountControl);
+
+ /* Username, fullname, home dir, dir drive, logon script, acct
+ desc, workstations, profile. */
+
+ if (sAMAccountName) {
+ old_string = pdb_get_nt_username(account);
+ new_string = sAMAccountName;
+
+ if (STRING_CHANGED) {
+ pdb_set_nt_username(account, new_string, PDB_CHANGED);
+ }
+
+ /* Unix username is the same - for sanity */
+ old_string = pdb_get_username( account );
+ if (STRING_CHANGED) {
+ pdb_set_username(account, new_string, PDB_CHANGED);
+ }
+ }
+
+ if (displayName) {
+ old_string = pdb_get_fullname(account);
+ new_string = displayName;
+
+ if (STRING_CHANGED)
+ pdb_set_fullname(account, new_string, PDB_CHANGED);
+ }
+
+ if (homeDirectory) {
+ old_string = pdb_get_homedir(account);
+ new_string = homeDirectory;
+
+ if (STRING_CHANGED)
+ pdb_set_homedir(account, new_string, PDB_CHANGED);
+ }
+
+ if (homeDrive) {
+ old_string = pdb_get_dir_drive(account);
+ new_string = homeDrive;
+
+ if (STRING_CHANGED)
+ pdb_set_dir_drive(account, new_string, PDB_CHANGED);
+ }
+
+ if (scriptPath) {
+ old_string = pdb_get_logon_script(account);
+ new_string = scriptPath;
+
+ if (STRING_CHANGED)
+ pdb_set_logon_script(account, new_string, PDB_CHANGED);
+ }
+
+ if (description) {
+ old_string = pdb_get_acct_desc(account);
+ new_string = description;
+
+ if (STRING_CHANGED)
+ pdb_set_acct_desc(account, new_string, PDB_CHANGED);
+ }
+
+ if (userWorkstations) {
+ old_string = pdb_get_workstations(account);
+ new_string = userWorkstations;
+
+ if (STRING_CHANGED)
+ pdb_set_workstations(account, new_string, PDB_CHANGED);
+ }
+
+ if (profilePath) {
+ old_string = pdb_get_profile_path(account);
+ new_string = profilePath;
+
+ if (STRING_CHANGED)
+ pdb_set_profile_path(account, new_string, PDB_CHANGED);
+ }
+
+ if (userParameters.data) {
+ char *newstr = NULL;
+ old_string = pdb_get_munged_dial(account);
+
+ if (userParameters.length != 0) {
+ newstr = base64_encode_data_blob(talloc_tos(),
+ userParameters);
+ SMB_ASSERT(newstr != NULL);
+ }
+
+ if (STRING_CHANGED_NC(old_string, newstr))
+ pdb_set_munged_dial(account, newstr, PDB_CHANGED);
+ TALLOC_FREE(newstr);
+ }
+
+ /* User and group sid */
+ if (rid != 0 && pdb_get_user_rid(account) != rid) {
+ pdb_set_user_sid_from_rid(account, rid, PDB_CHANGED);
+ }
+ if (primaryGroupID != 0 && pdb_get_group_rid(account) != primaryGroupID) {
+ pdb_set_group_sid_from_rid(account, primaryGroupID, PDB_CHANGED);
+ }
+
+ /* Logon and password information */
+ if (!nt_time_is_zero(&lastLogon)) {
+ unix_time = nt_time_to_unix(lastLogon);
+ stored_time = pdb_get_logon_time(account);
+ if (stored_time != unix_time)
+ pdb_set_logon_time(account, unix_time, PDB_CHANGED);
+ }
+
+ if (!nt_time_is_zero(&lastLogoff)) {
+ unix_time = nt_time_to_unix(lastLogoff);
+ stored_time = pdb_get_logoff_time(account);
+ if (stored_time != unix_time)
+ pdb_set_logoff_time(account, unix_time,PDB_CHANGED);
+ }
+
+ /* Logon Divs */
+ units_per_week = logonHours.length * 8;
+
+ if (pdb_get_logon_divs(account) != units_per_week) {
+ pdb_set_logon_divs(account, units_per_week, PDB_CHANGED);
+ }
+
+ /* Logon Hours Len */
+ if (units_per_week/8 != pdb_get_hours_len(account)) {
+ pdb_set_hours_len(account, units_per_week/8, PDB_CHANGED);
+ }
+
+ /* Logon Hours */
+ if (logonHours.data) {
+ char oldstr[44], newstr[44];
+ pdb_sethexhours(oldstr, pdb_get_hours(account));
+ pdb_sethexhours(newstr, logonHours.data);
+ if (!strequal(oldstr, newstr)) {
+ pdb_set_hours(account, logonHours.data,
+ logonHours.length, PDB_CHANGED);
+ }
+ }
+
+ if (pdb_get_bad_password_count(account) != badPwdCount)
+ pdb_set_bad_password_count(account, badPwdCount, PDB_CHANGED);
+
+ if (pdb_get_logon_count(account) != logonCount)
+ pdb_set_logon_count(account, logonCount, PDB_CHANGED);
+
+ if (!nt_time_is_zero(&pwdLastSet)) {
+ unix_time = nt_time_to_unix(pwdLastSet);
+ stored_time = pdb_get_pass_last_set_time(account);
+ if (stored_time != unix_time)
+ pdb_set_pass_last_set_time(account, unix_time, PDB_CHANGED);
+ } else {
+ /* no last set time, make it now */
+ pdb_set_pass_last_set_time(account, time(NULL), PDB_CHANGED);
+ }
+
+ if (!nt_time_is_zero(&accountExpires)) {
+ unix_time = nt_time_to_unix(accountExpires);
+ stored_time = pdb_get_kickoff_time(account);
+ if (stored_time != unix_time)
+ pdb_set_kickoff_time(account, unix_time, PDB_CHANGED);
+ }
+
+ /* Decode hashes from password hash
+ Note that win2000 may send us all zeros for the hashes if it doesn't
+ think this channel is secure enough - don't set the passwords at all
+ in that case
+ */
+ if (dBCSPwd.length == 16 && !all_zero(dBCSPwd.data, 16)) {
+ pdb_set_lanman_passwd(account, dBCSPwd.data, PDB_CHANGED);
+ }
+
+ if (unicodePwd.length == 16 && !all_zero(unicodePwd.data, 16)) {
+ pdb_set_nt_passwd(account, unicodePwd.data, PDB_CHANGED);
+ }
+
+ /* TODO: history */
+
+ /* TODO: account expiry time */
+
+ pdb_set_acct_ctrl(account, acct_flags, PDB_CHANGED);
+
+ pdb_set_domain(account, lp_workgroup(), PDB_CHANGED);
+
+ DEBUG(0,("sam_account_from_object(%s, %s) done\n",
+ sAMAccountName,
+ dom_sid_str_buf(&objectSid, &buf)));
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS handle_account_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj)
+{
+ struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
+ NTSTATUS status;
+ fstring account;
+ struct samu *sam_account=NULL;
+ GROUP_MAP *map;
+ struct group *grp;
+ struct dom_sid user_sid;
+ struct dom_sid group_sid;
+ struct dom_sid_buf buf;
+ struct passwd *passwd = NULL;
+ uint32_t acct_flags;
+ uint32_t rid;
+
+ const char *sAMAccountName;
+ uint32_t userAccountControl;
+
+ user_sid = cur->object.identifier->sid;
+ GET_STRING_EX(sAMAccountName, true);
+ GET_UINT32_EX(userAccountControl, true);
+
+ status = dom_sid_split_rid(mem_ctx, &user_sid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fstrcpy(account, sAMAccountName);
+ if (rid == DOMAIN_RID_GUEST) {
+ /*
+ * pdb_getsampwsid() has special handling for DOMAIN_RID_GUEST
+ * that's why we need to ignore it here.
+ *
+ * pdb_smbpasswd.c also has some DOMAIN_RID_GUEST related
+ * code...
+ */
+ DEBUG(0,("Ignore %s - %s\n",
+ account,
+ dom_sid_str_buf(&user_sid, &buf)));
+ return NT_STATUS_OK;
+ }
+ DEBUG(0,("Creating account: %s\n", account));
+
+ if ( !(sam_account = samu_new(mem_ctx)) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ acct_flags = ds_uf2acb(userAccountControl);
+ status = smb_create_user(sam_account, acct_flags, account, &passwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Could not create posix account info for '%s'- %s\n",
+ account, nt_errstr(status)));
+ TALLOC_FREE(sam_account);
+ return status;
+ }
+
+ DEBUG(3, ("Attempting to find SID %s for user %s in the passdb\n",
+ dom_sid_str_buf(&user_sid, &buf),
+ account));
+ if (!pdb_getsampwsid(sam_account, &user_sid)) {
+ sam_account_from_object(sam_account, cur);
+ DEBUG(3, ("Attempting to add user SID %s for user %s in the passdb\n",
+ dom_sid_str_buf(&user_sid, &buf),
+ pdb_get_username(sam_account)));
+ if (!NT_STATUS_IS_OK(pdb_add_sam_account(sam_account))) {
+ DEBUG(1, ("SAM Account for %s failed to be added to the passdb!\n",
+ account));
+ TALLOC_FREE(sam_account);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else {
+ sam_account_from_object(sam_account, cur);
+ DEBUG(3, ("Attempting to update user SID %s for user %s in the passdb\n",
+ dom_sid_str_buf(&user_sid, &buf),
+ pdb_get_username(sam_account)));
+ if (!NT_STATUS_IS_OK(pdb_update_sam_account(sam_account))) {
+ DEBUG(1, ("SAM Account for %s failed to be updated in the passdb!\n",
+ account));
+ TALLOC_FREE(sam_account);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ if (pdb_get_group_sid(sam_account) == NULL) {
+ TALLOC_FREE(sam_account);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ group_sid = *pdb_get_group_sid(sam_account);
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pdb_getgrsid(map, group_sid)) {
+ DEBUG(0, ("Primary group of %s has no mapping!\n",
+ pdb_get_username(sam_account)));
+ } else {
+ if (map->gid != passwd->pw_gid) {
+ if (!(grp = getgrgid(map->gid))) {
+ DEBUG(0, ("Could not find unix group %lu for user %s (group SID=%s)\n",
+ (unsigned long)map->gid, pdb_get_username(sam_account),
+ dom_sid_str_buf(&group_sid, &buf)));
+ } else {
+ smb_set_primary_group(grp->gr_name, pdb_get_username(sam_account));
+ }
+ }
+ }
+
+ TALLOC_FREE(map);
+
+ if ( !passwd ) {
+ DEBUG(1, ("No unix user for this account (%s), cannot adjust mappings\n",
+ pdb_get_username(sam_account)));
+ }
+
+ TALLOC_FREE(sam_account);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS handle_alias_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj)
+{
+ struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
+ NTSTATUS status;
+ struct group *grp = NULL;
+ struct dom_sid group_sid;
+ uint32_t rid = 0;
+ struct dom_sid *dom_sid = NULL;
+ struct dom_sid_buf sid_str;
+ GROUP_MAP *map;
+ bool insert = true;
+
+ const char *sAMAccountName;
+ const char *description;
+ uint32_t i;
+ uint32_t num_members = 0;
+ struct drsuapi_DsReplicaObjectIdentifier3 *members = NULL;
+
+ group_sid = cur->object.identifier->sid;
+ GET_STRING_EX(sAMAccountName, true);
+ GET_STRING(description);
+
+ status = find_drsuapi_attr_dn(obj, cur, DRSUAPI_ATTID_member,
+ &num_members, &members);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROPSET_NOT_FOUND)) {
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dom_sid_split_rid(mem_ctx, &group_sid, &dom_sid, &rid);
+
+ map = talloc_zero(mem_ctx, GROUP_MAP);
+ if (map == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ map->nt_name = talloc_strdup(map, sAMAccountName);
+ if (map->nt_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (description) {
+ map->comment = talloc_strdup(map, description);
+ } else {
+ map->comment = talloc_strdup(map, "");
+ }
+ if (map->comment == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(0,("Creating alias[%s] - %s members[%u]\n",
+ map->nt_name,
+ dom_sid_str_buf(&group_sid, &sid_str),
+ num_members));
+
+ status = dssync_insert_obj(pctx, pctx->aliases, obj);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pdb_getgrsid(map, group_sid)) {
+ if (map->gid != -1) {
+ grp = getgrgid(map->gid);
+ }
+ insert = false;
+ }
+
+ if (grp == NULL) {
+ gid_t gid;
+
+ /* No group found from mapping, find it from its name. */
+ if ((grp = getgrnam(map->nt_name)) == NULL) {
+
+ /* No appropriate group found, create one */
+
+ DEBUG(0, ("Creating unix group: '%s'\n",
+ map->nt_name));
+
+ if (smb_create_group(map->nt_name, &gid) != 0) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ if ((grp = getgrgid(gid)) == NULL) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+ }
+ }
+
+ map->gid = grp->gr_gid;
+ map->sid = group_sid;
+
+ if (dom_sid_equal(dom_sid, &global_sid_Builtin)) {
+ /*
+ * pdb_ldap does not like SID_NAME_WKN_GRP...
+ *
+ * map.sid_name_use = SID_NAME_WKN_GRP;
+ */
+ map->sid_name_use = SID_NAME_ALIAS;
+ } else {
+ map->sid_name_use = SID_NAME_ALIAS;
+ }
+
+ if (insert) {
+ pdb_add_group_mapping_entry(map);
+ } else {
+ pdb_update_group_mapping_entry(map);
+ }
+
+ for (i=0; i < num_members; i++) {
+ struct dssync_passdb_mem *mem;
+
+ status = dssync_create_mem(pctx, obj,
+ true /* active */,
+ &members[i], &mem);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+ TALLOC_FREE(map);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS handle_group_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj)
+{
+ struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
+ NTSTATUS status;
+ struct group *grp = NULL;
+ struct dom_sid group_sid;
+ struct dom_sid_buf sid_str;
+ GROUP_MAP *map;
+ bool insert = true;
+
+ const char *sAMAccountName;
+ const char *description;
+ uint32_t i;
+ uint32_t num_members = 0;
+ struct drsuapi_DsReplicaObjectIdentifier3 *members = NULL;
+
+ group_sid = cur->object.identifier->sid;
+ GET_STRING_EX(sAMAccountName, true);
+ GET_STRING(description);
+
+ status = find_drsuapi_attr_dn(obj, cur, DRSUAPI_ATTID_member,
+ &num_members, &members);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROPSET_NOT_FOUND)) {
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ map->nt_name = talloc_strdup(map, sAMAccountName);
+ if (!map->nt_name) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ if (description) {
+ map->comment = talloc_strdup(map, description);
+ } else {
+ map->comment = talloc_strdup(map, "");
+ }
+ if (!map->comment) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(0,("Creating group[%s] - %s members [%u]\n",
+ map->nt_name,
+ dom_sid_str_buf(&group_sid, &sid_str),
+ num_members));
+
+ status = dssync_insert_obj(pctx, pctx->groups, obj);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pdb_getgrsid(map, group_sid)) {
+ if (map->gid != -1) {
+ grp = getgrgid(map->gid);
+ }
+ insert = false;
+ }
+
+ if (grp == NULL) {
+ gid_t gid;
+
+ /* No group found from mapping, find it from its name. */
+ if ((grp = getgrnam(map->nt_name)) == NULL) {
+
+ /* No appropriate group found, create one */
+
+ DEBUG(0, ("Creating unix group: '%s'\n",
+ map->nt_name));
+
+ if (smb_create_group(map->nt_name, &gid) != 0) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ if ((grp = getgrnam(map->nt_name)) == NULL) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+ }
+ }
+
+ map->gid = grp->gr_gid;
+ map->sid = group_sid;
+ map->sid_name_use = SID_NAME_DOM_GRP;
+
+ if (insert) {
+ pdb_add_group_mapping_entry(map);
+ } else {
+ pdb_update_group_mapping_entry(map);
+ }
+
+ for (i=0; i < num_members; i++) {
+ struct dssync_passdb_mem *mem;
+
+ status = dssync_create_mem(pctx, obj,
+ true /* active */,
+ &members[i], &mem);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+ TALLOC_FREE(map);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS handle_interdomain_trust_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj)
+{
+ struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
+ DEBUG(0,("trust: %s\n", cur->object.identifier->dn));
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+struct dssync_object_table_t {
+ uint32_t type;
+ NTSTATUS (*fn) (struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj);
+};
+
+static const struct dssync_object_table_t dssync_object_table[] = {
+ { ATYPE_NORMAL_ACCOUNT, handle_account_object },
+ { ATYPE_WORKSTATION_TRUST, handle_account_object },
+ { ATYPE_SECURITY_LOCAL_GROUP, handle_alias_object },
+ { ATYPE_SECURITY_GLOBAL_GROUP, handle_group_object },
+ { ATYPE_INTERDOMAIN_TRUST, handle_interdomain_trust_object },
+};
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS parse_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ DATA_BLOB *blob = NULL;
+ uint32_t i = 0;
+ size_t a = 0;
+
+ char *name = NULL;
+ uint32_t sam_type = 0;
+
+ DEBUG(3, ("parsing object '%s'\n", cur->object.identifier->dn));
+
+ for (i=0; i < cur->object.attribute_ctr.num_attributes; i++) {
+ struct drsuapi_DsReplicaAttribute *attr =
+ &cur->object.attribute_ctr.attributes[i];
+
+ if (attr->value_ctr.num_values != 1) {
+ continue;
+ }
+
+ if (!attr->value_ctr.values[0].blob) {
+ continue;
+ }
+
+ blob = attr->value_ctr.values[0].blob;
+
+ switch (attr->attid) {
+ case DRSUAPI_ATTID_sAMAccountName:
+ pull_string_talloc(mem_ctx, NULL, 0, &name,
+ blob->data, blob->length,
+ STR_UNICODE);
+ break;
+ case DRSUAPI_ATTID_sAMAccountType:
+ sam_type = IVAL(blob->data, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ for (a=0; a < ARRAY_SIZE(dssync_object_table); a++) {
+ if (sam_type == dssync_object_table[a].type) {
+ if (dssync_object_table[a].fn) {
+ struct dssync_passdb_obj *obj = NULL;
+ status = dssync_create_obj(pctx, pctx->all,
+ sam_type, cur, &obj);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ status = dssync_object_table[a].fn(pctx,
+ mem_ctx,
+ obj);
+ break;
+ }
+ }
+ }
+
+ return status;
+}
+
+static NTSTATUS parse_link(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaLinkedAttribute *cur)
+{
+ struct drsuapi_DsReplicaObjectIdentifier3 *id3;
+ const DATA_BLOB *blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ bool active = false;
+ struct dssync_passdb_mem *mem;
+ struct dssync_passdb_obj *obj;
+
+ if (cur->attid != DRSUAPI_ATTID_member) {
+ return NT_STATUS_OK;
+ }
+
+ if (cur->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE) {
+ active = true;
+ }
+
+ DEBUG(3, ("parsing link '%s' - %s\n",
+ cur->identifier->dn, active?"adding":"deleting"));
+
+ blob = cur->value.blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ obj = dssync_search_obj_by_guid(pctx, pctx->all, &cur->identifier->guid);
+ if (obj == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ id3 = talloc_zero(obj, struct drsuapi_DsReplicaObjectIdentifier3);
+ if (id3 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* windows sometimes sends an extra two pad bytes here */
+ ndr_err = ndr_pull_struct_blob(blob, id3, id3,
+ (ndr_pull_flags_fn_t)ndr_pull_drsuapi_DsReplicaObjectIdentifier3);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ status = dssync_create_mem(pctx, obj,
+ active,
+ id3, &mem);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS passdb_process_objects(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(ctx->private_data,
+ struct dssync_passdb);
+
+ for (; cur; cur = cur->next_object) {
+ status = parse_object(pctx, mem_ctx, cur);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ out:
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS passdb_process_links(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ uint32_t count,
+ struct drsuapi_DsReplicaLinkedAttribute *links,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(ctx->private_data,
+ struct dssync_passdb);
+ uint32_t i;
+
+ for (i = 0; i < count; i++) {
+ status = parse_link(pctx, mem_ctx, &links[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ out:
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+const struct dssync_ops libnet_dssync_passdb_ops = {
+ .startup = passdb_startup,
+ .process_objects = passdb_process_objects,
+ .process_links = passdb_process_links,
+ .finish = passdb_finish,
+};
diff --git a/source3/libnet/libnet_join.c b/source3/libnet/libnet_join.c
new file mode 100644
index 0000000..d48833d
--- /dev/null
+++ b/source3/libnet/libnet_join.c
@@ -0,0 +1,3281 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Join Support
+ * Copyright (C) Gerald (Jerry) Carter 2006
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * 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 "ads.h"
+#include "libsmb/namequery.h"
+#include "librpc/gen_ndr/ndr_libnet_join.h"
+#include "libnet/libnet_join.h"
+#include "libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/init_samr.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "rpc_client/cli_netlogon.h"
+#include "lib/smbconf/smbconf.h"
+#include "lib/smbconf/smbconf_reg.h"
+#include "../libds/common/flags.h"
+#include "secrets.h"
+#include "rpc_client/init_lsa.h"
+#include "rpc_client/cli_pipe.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "libsmb/libsmb.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/param/loadparm.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+#include "auth/credentials/credentials.h"
+#include "krb5_env.h"
+#include "libsmb/dsgetdcname.h"
+#include "rpc_client/util_netlogon.h"
+#include "libnet/libnet_join_offline.h"
+
+/****************************************************************
+****************************************************************/
+
+#define LIBNET_JOIN_DUMP_CTX(ctx, r, f) \
+ do { \
+ char *str = NULL; \
+ str = NDR_PRINT_FUNCTION_STRING(ctx, libnet_JoinCtx, f, r); \
+ DEBUG(1,("libnet_Join:\n%s", str)); \
+ TALLOC_FREE(str); \
+ } while (0)
+
+#define LIBNET_JOIN_IN_DUMP_CTX(ctx, r) \
+ LIBNET_JOIN_DUMP_CTX(ctx, r, NDR_IN | NDR_SET_VALUES)
+#define LIBNET_JOIN_OUT_DUMP_CTX(ctx, r) \
+ LIBNET_JOIN_DUMP_CTX(ctx, r, NDR_OUT)
+
+#define LIBNET_UNJOIN_DUMP_CTX(ctx, r, f) \
+ do { \
+ char *str = NULL; \
+ str = NDR_PRINT_FUNCTION_STRING(ctx, libnet_UnjoinCtx, f, r); \
+ DEBUG(1,("libnet_Unjoin:\n%s", str)); \
+ TALLOC_FREE(str); \
+ } while (0)
+
+#define LIBNET_UNJOIN_IN_DUMP_CTX(ctx, r) \
+ LIBNET_UNJOIN_DUMP_CTX(ctx, r, NDR_IN | NDR_SET_VALUES)
+#define LIBNET_UNJOIN_OUT_DUMP_CTX(ctx, r) \
+ LIBNET_UNJOIN_DUMP_CTX(ctx, r, NDR_OUT)
+
+/****************************************************************
+****************************************************************/
+
+static void libnet_join_set_error_string(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ const char *format, ...)
+ PRINTF_ATTRIBUTE(3,4);
+
+static void libnet_join_set_error_string(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ const char *format, ...)
+{
+ va_list args;
+
+ if (r->out.error_string) {
+ return;
+ }
+
+ va_start(args, format);
+ r->out.error_string = talloc_vasprintf(mem_ctx, format, args);
+ va_end(args);
+}
+
+/****************************************************************
+****************************************************************/
+
+static void libnet_unjoin_set_error_string(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r,
+ const char *format, ...)
+ PRINTF_ATTRIBUTE(3,4);
+
+static void libnet_unjoin_set_error_string(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r,
+ const char *format, ...)
+{
+ va_list args;
+
+ if (r->out.error_string) {
+ return;
+ }
+
+ va_start(args, format);
+ r->out.error_string = talloc_vasprintf(mem_ctx, format, args);
+ va_end(args);
+}
+
+#ifdef HAVE_ADS
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_connect_ads(const char *dns_domain_name,
+ const char *netbios_domain_name,
+ const char *dc_name,
+ const char *user_name,
+ const char *password,
+ const char *ccname,
+ TALLOC_CTX *mem_ctx,
+ ADS_STRUCT **ads)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STATUS status;
+ ADS_STRUCT *my_ads = NULL;
+ char *cp;
+ enum credentials_use_kerberos krb5_state;
+
+ my_ads = ads_init(tmp_ctx,
+ dns_domain_name,
+ netbios_domain_name,
+ dc_name,
+ ADS_SASL_SEAL);
+ if (!my_ads) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto out;
+ }
+
+ /* In FIPS mode, client use kerberos is forced to required. */
+ krb5_state = lp_client_use_kerberos();
+ switch (krb5_state) {
+ case CRED_USE_KERBEROS_REQUIRED:
+ my_ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS;
+ my_ads->auth.flags &= ~ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ case CRED_USE_KERBEROS_DESIRED:
+ my_ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS;
+ my_ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ case CRED_USE_KERBEROS_DISABLED:
+ my_ads->auth.flags |= ADS_AUTH_DISABLE_KERBEROS;
+ my_ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ }
+
+ if (user_name) {
+ TALLOC_FREE(my_ads->auth.user_name);
+ my_ads->auth.user_name = talloc_strdup(my_ads, user_name);
+ if (my_ads->auth.user_name == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ if ((cp = strchr_m(my_ads->auth.user_name, '@'))!=0) {
+ *cp++ = '\0';
+ TALLOC_FREE(my_ads->auth.realm);
+ my_ads->auth.realm = talloc_asprintf_strupper_m(my_ads, "%s", cp);
+ if (my_ads->auth.realm == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto out;
+ }
+ }
+ }
+
+ if (password) {
+ TALLOC_FREE(my_ads->auth.password);
+ my_ads->auth.password = talloc_strdup(my_ads, password);
+ if (my_ads->auth.password == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (ccname != NULL) {
+ TALLOC_FREE(my_ads->auth.ccache_name);
+ my_ads->auth.ccache_name = talloc_strdup(my_ads, ccname);
+ if (my_ads->auth.ccache_name == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ setenv(KRB5_ENV_CCNAME, my_ads->auth.ccache_name, 1);
+ }
+
+ status = ads_connect_user_creds(my_ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ *ads = talloc_move(mem_ctx, &my_ads);
+
+ status = ADS_SUCCESS;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_connect_ads(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ bool use_machine_creds)
+{
+ ADS_STATUS status;
+ const char *username;
+ const char *password;
+ const char *ccname = NULL;
+
+ if (use_machine_creds) {
+ if (r->in.machine_name == NULL ||
+ r->in.machine_password == NULL) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+ username = talloc_asprintf(mem_ctx, "%s$",
+ r->in.machine_name);
+ if (username == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ password = r->in.machine_password;
+ ccname = "MEMORY:libnet_join_machine_creds";
+ } else {
+ char *p = NULL;
+
+ username = r->in.admin_account;
+
+ p = strchr(r->in.admin_account, '@');
+ if (p == NULL) {
+ username = talloc_asprintf(mem_ctx, "%s@%s",
+ r->in.admin_account,
+ r->in.admin_domain);
+ }
+ if (username == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ password = r->in.admin_password;
+
+ /*
+ * when r->in.use_kerberos is set to allow "net ads join -k" we
+ * may not override the provided credential cache - gd
+ */
+
+ if (!r->in.use_kerberos) {
+ ccname = "MEMORY:libnet_join_user_creds";
+ }
+ }
+
+ status = libnet_connect_ads(r->out.dns_domain_name,
+ r->out.netbios_domain_name,
+ r->in.dc_name,
+ username,
+ password,
+ ccname,
+ r,
+ &r->in.ads);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to connect to AD: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ if (!r->out.netbios_domain_name) {
+ r->out.netbios_domain_name = talloc_strdup(mem_ctx,
+ r->in.ads->server.workgroup);
+ ADS_ERROR_HAVE_NO_MEMORY(r->out.netbios_domain_name);
+ }
+
+ if (!r->out.dns_domain_name) {
+ r->out.dns_domain_name = talloc_strdup(mem_ctx,
+ r->in.ads->config.realm);
+ ADS_ERROR_HAVE_NO_MEMORY(r->out.dns_domain_name);
+ }
+
+ r->out.domain_is_ad = true;
+
+ return ADS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_connect_ads_user(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ return libnet_join_connect_ads(mem_ctx, r, false);
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_connect_ads_machine(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ return libnet_join_connect_ads(mem_ctx, r, true);
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_unjoin_connect_ads(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ ADS_STATUS status;
+
+ status = libnet_connect_ads(r->in.domain_name,
+ r->in.domain_name,
+ r->in.dc_name,
+ r->in.admin_account,
+ r->in.admin_password,
+ NULL,
+ r,
+ &r->in.ads);
+ if (!ADS_ERR_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to connect to AD: %s",
+ ads_errstr(status));
+ }
+
+ return status;
+}
+
+/****************************************************************
+ join a domain using ADS (LDAP mods)
+****************************************************************/
+
+static ADS_STATUS libnet_join_precreate_machine_acct(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ const char *attrs[] = { "dn", NULL };
+ bool moved = false;
+
+ status = ads_check_ou_dn(mem_ctx, r->in.ads, &r->in.account_ou);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ status = ads_search_dn(r->in.ads, &res, r->in.account_ou, attrs);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (ads_count_replies(r->in.ads, res) != 1) {
+ ads_msgfree(r->in.ads, res);
+ return ADS_ERROR_LDAP(LDAP_NO_SUCH_OBJECT);
+ }
+
+ ads_msgfree(r->in.ads, res);
+
+ /* Attempt to create the machine account and bail if this fails.
+ Assume that the admin wants exactly what they requested */
+
+ if (r->in.machine_password == NULL) {
+ r->in.machine_password =
+ trust_pw_new_value(mem_ctx,
+ r->in.secure_channel_type,
+ SEC_ADS);
+ if (r->in.machine_password == NULL) {
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+ }
+
+ status = ads_create_machine_acct(r->in.ads,
+ r->in.machine_name,
+ r->in.machine_password,
+ r->in.account_ou,
+ r->in.desired_encryption_types,
+ r->out.dns_domain_name);
+
+ if (ADS_ERR_OK(status)) {
+ DBG_WARNING("Machine account successfully created\n");
+ return status;
+ } else if ((status.error_type == ENUM_ADS_ERROR_LDAP) &&
+ (status.err.rc == LDAP_ALREADY_EXISTS)) {
+ status = ADS_SUCCESS;
+ }
+
+ if (!ADS_ERR_OK(status)) {
+ DBG_WARNING("Failed to create machine account\n");
+ return status;
+ }
+
+ status = ads_move_machine_acct(r->in.ads,
+ r->in.machine_name,
+ r->in.account_ou,
+ &moved);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("failure to locate/move pre-existing "
+ "machine account\n"));
+ return status;
+ }
+
+ DEBUG(1,("The machine account %s the specified OU.\n",
+ moved ? "was moved into" : "already exists in"));
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_unjoin_remove_machine_acct(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ ADS_STATUS status;
+
+ if (!r->in.ads) {
+ status = libnet_unjoin_connect_ads(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to connect to AD: %s",
+ ads_errstr(status));
+ return status;
+ }
+ }
+
+ status = ads_leave_realm(r->in.ads, r->in.machine_name);
+ if (!ADS_ERR_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to leave realm: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ return ADS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_find_machine_acct(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ char *dn = NULL;
+ struct dom_sid sid;
+
+ if (!r->in.machine_name) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_find_machine_acct(r->in.ads,
+ &res,
+ r->in.machine_name);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (ads_count_replies(r->in.ads, res) != 1) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ dn = ads_get_dn(r->in.ads, mem_ctx, res);
+ if (!dn) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ r->out.dn = talloc_strdup(mem_ctx, dn);
+ if (!r->out.dn) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ if (!ads_pull_uint32(r->in.ads, res, "msDS-SupportedEncryptionTypes",
+ &r->out.set_encryption_types)) {
+ r->out.set_encryption_types = 0;
+ }
+
+ if (!ads_pull_sid(r->in.ads, res, "objectSid", &sid)) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ dom_sid_split_rid(mem_ctx, &sid, NULL, &r->out.account_rid);
+ done:
+ ads_msgfree(r->in.ads, res);
+ TALLOC_FREE(dn);
+
+ return status;
+}
+
+static ADS_STATUS libnet_join_get_machine_spns(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ char ***spn_array,
+ size_t *num_spns)
+{
+ ADS_STATUS status;
+
+ if (r->in.machine_name == NULL) {
+ return ADS_ERROR_SYSTEM(EINVAL);
+ }
+
+ status = ads_get_service_principal_names(mem_ctx,
+ r->in.ads,
+ r->in.machine_name,
+ spn_array,
+ num_spns);
+
+ return status;
+}
+
+static ADS_STATUS add_uniq_spn(TALLOC_CTX *mem_ctx, const char *spn,
+ const char ***array, size_t *num)
+{
+ bool ok = ads_element_in_array(*array, *num, spn);
+ if (!ok) {
+ ok = add_string_to_array(mem_ctx, spn, array, num);
+ if (!ok) {
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+ }
+ return ADS_SUCCESS;
+}
+
+/****************************************************************
+ Set a machines dNSHostName and servicePrincipalName attributes
+****************************************************************/
+
+static ADS_STATUS libnet_join_set_machine_spn(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ ADS_STATUS status;
+ ADS_MODLIST mods;
+ fstring my_fqdn;
+ fstring my_alias;
+ const char **spn_array = NULL;
+ size_t num_spns = 0;
+ char *spn = NULL;
+ const char **netbios_aliases = NULL;
+ const char **addl_hostnames = NULL;
+
+ /* Find our DN */
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ status = libnet_join_get_machine_spns(frame,
+ r,
+ discard_const_p(char **, &spn_array),
+ &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(5, ("Retrieving the servicePrincipalNames failed.\n"));
+ }
+
+ /* Windows only creates HOST/shortname & HOST/fqdn. */
+
+ spn = talloc_asprintf(frame, "HOST/%s", r->in.machine_name);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+ if (!strupper_m(spn)) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ if (r->in.dnshostname != NULL) {
+ fstr_sprintf(my_fqdn, "%s", r->in.dnshostname);
+ } else {
+ fstr_sprintf(my_fqdn, "%s.%s", r->in.machine_name,
+ lp_dnsdomain());
+ }
+
+ if (!strlower_m(my_fqdn)) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ spn = talloc_asprintf(frame, "HOST/%s", my_fqdn);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ for (netbios_aliases = lp_netbios_aliases();
+ netbios_aliases != NULL && *netbios_aliases != NULL;
+ netbios_aliases++) {
+ /*
+ * Add HOST/NETBIOSNAME
+ */
+ spn = talloc_asprintf(frame, "HOST/%s", *netbios_aliases);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+ if (!strupper_m(spn)) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ /*
+ * Add HOST/netbiosname.domainname
+ */
+ fstr_sprintf(my_alias, "%s.%s",
+ *netbios_aliases,
+ lp_dnsdomain());
+
+ spn = talloc_asprintf(frame, "HOST/%s", my_alias);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+ }
+
+ for (addl_hostnames = lp_additional_dns_hostnames();
+ addl_hostnames != NULL && *addl_hostnames != NULL;
+ addl_hostnames++) {
+
+ spn = talloc_asprintf(frame, "HOST/%s", *addl_hostnames);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+ }
+
+ /* make sure to NULL terminate the array */
+ spn_array = talloc_realloc(frame, spn_array, const char *, num_spns + 1);
+ if (spn_array == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+ spn_array[num_spns] = NULL;
+
+ mods = ads_init_mods(mem_ctx);
+ if (!mods) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ /* fields of primary importance */
+
+ status = ads_mod_str(mem_ctx, &mods, "dNSHostName", my_fqdn);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ status = ads_mod_strlist(mem_ctx, &mods, "servicePrincipalName",
+ spn_array);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ addl_hostnames = lp_additional_dns_hostnames();
+ if (addl_hostnames != NULL && *addl_hostnames != NULL) {
+ status = ads_mod_strlist(mem_ctx, &mods,
+ "msDS-AdditionalDnsHostName",
+ addl_hostnames);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+ }
+
+ status = ads_gen_mod(r->in.ads, r->out.dn, mods);
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_set_machine_upn(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ ADS_MODLIST mods;
+
+ if (!r->in.create_upn) {
+ return ADS_SUCCESS;
+ }
+
+ /* Find our DN */
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (!r->in.upn) {
+ const char *realm = r->out.dns_domain_name;
+
+ /* in case we are about to generate a keytab during the join
+ * make sure the default upn we create is usable with kinit -k.
+ * gd */
+
+ if (USE_KERBEROS_KEYTAB) {
+ realm = talloc_strdup_upper(mem_ctx,
+ r->out.dns_domain_name);
+ }
+
+ if (!realm) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ r->in.upn = talloc_asprintf(mem_ctx,
+ "host/%s@%s",
+ r->in.machine_name,
+ realm);
+ if (!r->in.upn) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ }
+
+ /* now do the mods */
+
+ mods = ads_init_mods(mem_ctx);
+ if (!mods) {
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+
+ /* fields of primary importance */
+
+ status = ads_mod_str(mem_ctx, &mods, "userPrincipalName", r->in.upn);
+ if (!ADS_ERR_OK(status)) {
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+
+ return ads_gen_mod(r->in.ads, r->out.dn, mods);
+}
+
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_set_os_attributes(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ ADS_MODLIST mods;
+ char *os_sp = NULL;
+
+ if (!r->in.os_name || !r->in.os_version ) {
+ return ADS_SUCCESS;
+ }
+
+ /* Find our DN */
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ /* now do the mods */
+
+ mods = ads_init_mods(mem_ctx);
+ if (!mods) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (r->in.os_servicepack) {
+ /*
+ * if blank string then leave os_sp equal to NULL to force
+ * attribute delete (LDAP_MOD_DELETE)
+ */
+ if (!strequal(r->in.os_servicepack,"")) {
+ os_sp = talloc_strdup(mem_ctx, r->in.os_servicepack);
+ }
+ } else {
+ os_sp = talloc_asprintf(mem_ctx, "Samba %s",
+ samba_version_string());
+ }
+ if (!os_sp && !strequal(r->in.os_servicepack,"")) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ /* fields of primary importance */
+
+ status = ads_mod_str(mem_ctx, &mods, "operatingSystem",
+ r->in.os_name);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ status = ads_mod_str(mem_ctx, &mods, "operatingSystemVersion",
+ r->in.os_version);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ status = ads_mod_str(mem_ctx, &mods, "operatingSystemServicePack",
+ os_sp);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ return ads_gen_mod(r->in.ads, r->out.dn, mods);
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_set_etypes(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ ADS_MODLIST mods;
+ const char *etype_list_str;
+
+ etype_list_str = talloc_asprintf(mem_ctx, "%d",
+ r->in.desired_encryption_types);
+ if (!etype_list_str) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ /* Find our DN */
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (r->in.desired_encryption_types == r->out.set_encryption_types) {
+ return ADS_SUCCESS;
+ }
+
+ /* now do the mods */
+
+ mods = ads_init_mods(mem_ctx);
+ if (!mods) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_mod_str(mem_ctx, &mods, "msDS-SupportedEncryptionTypes",
+ etype_list_str);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ status = ads_gen_mod(r->in.ads, r->out.dn, mods);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ r->out.set_encryption_types = r->in.desired_encryption_types;
+
+ return ADS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool libnet_join_create_keytab(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ if (!USE_SYSTEM_KEYTAB) {
+ return true;
+ }
+
+ if (ads_keytab_create_default(r->in.ads) != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool libnet_join_derive_salting_principal(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ uint32_t domain_func;
+ ADS_STATUS status;
+ const char *salt = NULL;
+ char *std_salt = NULL;
+
+ status = ads_domain_func_level(r->in.ads, &domain_func);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to determine domain functional level: %s",
+ ads_errstr(status));
+ return false;
+ }
+
+ /* go ahead and setup the default salt */
+
+ std_salt = kerberos_standard_des_salt();
+ if (!std_salt) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to obtain standard DES salt");
+ return false;
+ }
+
+ salt = talloc_strdup(mem_ctx, std_salt);
+ if (!salt) {
+ return false;
+ }
+
+ SAFE_FREE(std_salt);
+
+ /* if it's a Windows functional domain, we have to look for the UPN */
+
+ if (domain_func == DS_DOMAIN_FUNCTION_2000) {
+ char *upn;
+
+ upn = ads_get_upn(r->in.ads, mem_ctx,
+ r->in.machine_name);
+ if (upn) {
+ salt = talloc_strdup(mem_ctx, upn);
+ if (!salt) {
+ return false;
+ }
+ }
+ }
+
+ r->out.krb5_salt = salt;
+ return true;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_post_processing_ads_modify(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ bool need_etype_update = false;
+
+ if (r->in.request_offline_join) {
+ /*
+ * When in the "request offline join" path we can no longer
+ * modify the AD account as we are operating w/o network - gd
+ */
+ return ADS_SUCCESS;
+ }
+
+ if (!r->in.ads) {
+ status = libnet_join_connect_ads_user(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+ }
+
+ status = libnet_join_set_machine_spn(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set machine spn: %s\n"
+ "Do you have sufficient permissions to create machine "
+ "accounts?",
+ ads_errstr(status));
+ return status;
+ }
+
+ status = libnet_join_set_os_attributes(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to set machine os attributes: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ status = libnet_join_set_machine_upn(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to set machine upn: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (r->in.desired_encryption_types != r->out.set_encryption_types) {
+ uint32_t func_level = 0;
+
+ status = ads_domain_func_level(r->in.ads, &func_level);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to query domain controller functional level: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ if (func_level >= DS_DOMAIN_FUNCTION_2008) {
+ need_etype_update = true;
+ }
+ }
+
+ if (need_etype_update) {
+ /*
+ * We need to reconnect as machine account in order
+ * to update msDS-SupportedEncryptionTypes reliable
+ */
+
+ if (r->in.ads->auth.ccache_name != NULL) {
+ ads_kdestroy(r->in.ads->auth.ccache_name);
+ TALLOC_FREE(r->in.ads->auth.ccache_name);
+ }
+
+ TALLOC_FREE(r->in.ads);
+
+ status = libnet_join_connect_ads_machine(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to connect as machine account: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ status = libnet_join_set_etypes(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to set machine kerberos encryption types: %s",
+ ads_errstr(status));
+ return status;
+ }
+ }
+
+ if (!libnet_join_derive_salting_principal(mem_ctx, r)) {
+ return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
+ }
+
+ return ADS_SUCCESS;
+}
+
+static ADS_STATUS libnet_join_post_processing_ads_sync(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ if (!libnet_join_create_keytab(mem_ctx, r)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to create kerberos keytab");
+ return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
+ }
+
+ return ADS_SUCCESS;
+}
+#endif /* HAVE_ADS */
+
+/****************************************************************
+ Store the machine password and domain SID
+****************************************************************/
+
+static bool libnet_join_joindomain_store_secrets(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ NTSTATUS status;
+
+ status = secrets_store_JoinCtx(r);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_store_JoinCtx() failed %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************
+ Connect dc's IPC$ share
+****************************************************************/
+
+static NTSTATUS libnet_join_connect_dc_ipc(const char *dc,
+ const char *user,
+ const char *domain,
+ const char *pass,
+ bool use_kerberos,
+ struct cli_state **cli)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool fallback_after_kerberos = false;
+ bool use_ccache = false;
+ bool pw_nt_hash = false;
+ struct cli_credentials *creds = NULL;
+ int flags = CLI_FULL_CONNECTION_IPC;
+ NTSTATUS status;
+
+ if (use_kerberos && pass) {
+ fallback_after_kerberos = true;
+ }
+
+ creds = cli_session_creds_init(frame,
+ user,
+ domain,
+ NULL, /* realm (use default) */
+ pass,
+ use_kerberos,
+ fallback_after_kerberos,
+ use_ccache,
+ pw_nt_hash);
+ if (creds == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_full_connection_creds(cli,
+ NULL,
+ dc,
+ NULL, 0,
+ "IPC$", "IPC",
+ creds,
+ flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+ Lookup domain dc's info
+****************************************************************/
+
+static NTSTATUS libnet_join_lookup_dc_rpc(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ struct cli_state **cli)
+{
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct policy_handle lsa_pol;
+ NTSTATUS status, result;
+ union lsa_PolicyInformation *info = NULL;
+ struct dcerpc_binding_handle *b;
+ const char *account = r->in.admin_account;
+ const char *domain = r->in.admin_domain;
+ const char *password = r->in.admin_password;
+ bool use_kerberos = r->in.use_kerberos;
+
+ if (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE) {
+ account = "";
+ domain = "";
+ password = NULL;
+ use_kerberos = false;
+ }
+
+ status = libnet_join_connect_dc_ipc(r->in.dc_name,
+ account,
+ domain,
+ password,
+ use_kerberos,
+ cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = cli_rpc_pipe_open_noauth(*cli, &ndr_table_lsarpc,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error connecting to LSA pipe. Error was %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED, &lsa_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy2(b, mem_ctx,
+ &lsa_pol,
+ LSA_POLICY_INFO_DNS,
+ &info,
+ &result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+ r->out.domain_is_ad = true;
+ r->out.netbios_domain_name = info->dns.name.string;
+ r->out.dns_domain_name = info->dns.dns_domain.string;
+ r->out.forest_name = info->dns.dns_forest.string;
+ r->out.domain_guid = info->dns.domain_guid;
+ r->out.domain_sid = dom_sid_dup(mem_ctx, info->dns.sid);
+ NT_STATUS_HAVE_NO_MEMORY(r->out.domain_sid);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &lsa_pol,
+ LSA_POLICY_INFO_ACCOUNT_DOMAIN,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ r->out.netbios_domain_name = info->account_domain.name.string;
+ r->out.domain_sid = dom_sid_dup(mem_ctx, info->account_domain.sid);
+ NT_STATUS_HAVE_NO_MEMORY(r->out.domain_sid);
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &lsa_pol, &result);
+ TALLOC_FREE(pipe_hnd);
+
+ done:
+ return status;
+}
+
+/****************************************************************
+ Do the domain join unsecure
+****************************************************************/
+
+static NTSTATUS libnet_join_joindomain_rpc_unsecure(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ struct cli_state *cli)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct rpc_pipe_client *authenticate_pipe = NULL;
+ struct rpc_pipe_client *passwordset_pipe = NULL;
+ struct cli_credentials *cli_creds;
+ struct netlogon_creds_cli_context *netlogon_creds = NULL;
+ struct netlogon_creds_CredentialState *creds = NULL;
+ uint32_t netlogon_flags = 0;
+ size_t len = 0;
+ bool ok;
+ DATA_BLOB new_trust_blob = data_blob_null;
+ NTSTATUS status;
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_netlogon,
+ &authenticate_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (!r->in.machine_password) {
+ int security = r->in.ads ? SEC_ADS : SEC_DOMAIN;
+
+ r->in.machine_password = trust_pw_new_value(mem_ctx,
+ r->in.secure_channel_type,
+ security);
+ if (r->in.machine_password == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ cli_creds = cli_credentials_init(talloc_tos());
+ if (cli_creds == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cli_credentials_set_username(cli_creds, r->out.account_name,
+ CRED_SPECIFIED);
+ cli_credentials_set_domain(cli_creds, r->in.domain_name,
+ CRED_SPECIFIED);
+ cli_credentials_set_realm(cli_creds, "", CRED_SPECIFIED);
+ cli_credentials_set_secure_channel_type(cli_creds,
+ r->in.secure_channel_type);
+
+ /* according to WKSSVC_JOIN_FLAGS_MACHINE_PWD_PASSED */
+ cli_credentials_set_password(cli_creds, r->in.admin_password,
+ CRED_SPECIFIED);
+
+ status = rpccli_create_netlogon_creds_ctx(
+ cli_creds, authenticate_pipe->desthost, r->in.msg_ctx,
+ frame, &netlogon_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = rpccli_setup_netlogon_creds(
+ cli, NCACN_NP, netlogon_creds, true /* force_reauth */,
+ cli_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = netlogon_creds_cli_get(netlogon_creds, frame, &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ netlogon_flags = creds->negotiate_flags;
+ TALLOC_FREE(creds);
+
+ if (netlogon_flags & NETLOGON_NEG_AUTHENTICATED_RPC) {
+ const char *remote_name = smbXcli_conn_remote_name(cli->conn);
+ const struct sockaddr_storage *remote_sockaddr =
+ smbXcli_conn_remote_sockaddr(cli->conn);
+
+ status = cli_rpc_pipe_open_schannel_with_creds(
+ cli,
+ &ndr_table_netlogon,
+ NCACN_NP,
+ netlogon_creds,
+ remote_name,
+ remote_sockaddr,
+ &passwordset_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ } else {
+ passwordset_pipe = authenticate_pipe;
+ }
+
+ len = strlen(r->in.machine_password);
+ ok = convert_string_talloc(frame, CH_UNIX, CH_UTF16,
+ r->in.machine_password, len,
+ (void **)&new_trust_blob.data,
+ &new_trust_blob.length);
+ if (!ok) {
+ status = NT_STATUS_UNMAPPABLE_CHARACTER;
+ if (errno == ENOMEM) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = netlogon_creds_cli_ServerPasswordSet(netlogon_creds,
+ passwordset_pipe->binding_handle,
+ &new_trust_blob,
+ NULL); /* new_version */
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+ Do the domain join
+****************************************************************/
+
+static NTSTATUS libnet_join_joindomain_rpc(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ struct cli_state *cli)
+{
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct policy_handle sam_pol, domain_pol, user_pol;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL, result;
+ char *acct_name;
+ struct lsa_String lsa_acct_name;
+ uint32_t acct_flags = ACB_WSTRUST;
+ struct samr_Ids user_rids;
+ struct samr_Ids name_types;
+ union samr_UserInfo user_info;
+ struct dcerpc_binding_handle *b = NULL;
+ unsigned int old_timeout = 0;
+
+ DATA_BLOB session_key = data_blob_null;
+ struct samr_CryptPassword crypt_pwd;
+ struct samr_CryptPasswordEx crypt_pwd_ex;
+
+ ZERO_STRUCT(sam_pol);
+ ZERO_STRUCT(domain_pol);
+ ZERO_STRUCT(user_pol);
+
+ switch (r->in.secure_channel_type) {
+ case SEC_CHAN_WKSTA:
+ acct_flags = ACB_WSTRUST;
+ break;
+ case SEC_CHAN_BDC:
+ acct_flags = ACB_SVRTRUST;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!r->in.machine_password) {
+ int security = r->in.ads ? SEC_ADS : SEC_DOMAIN;
+
+ r->in.machine_password = trust_pw_new_value(mem_ctx,
+ r->in.secure_channel_type,
+ security);
+ NT_STATUS_HAVE_NO_MEMORY(r->in.machine_password);
+ }
+
+ /* Open the domain */
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_samr,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error connecting to SAM pipe. Error was %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ status = cli_get_session_key(mem_ctx, pipe_hnd, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error getting session_key of SAM pipe. Error was %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ SAMR_ACCESS_ENUM_DOMAINS
+ | SAMR_ACCESS_LOOKUP_DOMAIN,
+ &sam_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &sam_pol,
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1
+ | SAMR_DOMAIN_ACCESS_CREATE_USER
+ | SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ r->out.domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Create domain user */
+
+ acct_name = talloc_asprintf(mem_ctx, "%s$", r->in.machine_name);
+ if (!strlower_m(acct_name)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ init_lsa_String(&lsa_acct_name, acct_name);
+
+ if (r->in.join_flags & WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE) {
+ uint32_t access_desired =
+ SEC_GENERIC_READ | SEC_GENERIC_WRITE | SEC_GENERIC_EXECUTE |
+ SEC_STD_WRITE_DAC | SEC_STD_DELETE |
+ SAMR_USER_ACCESS_SET_PASSWORD |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES |
+ SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ uint32_t access_granted = 0;
+
+ DEBUG(10,("Creating account with desired access mask: %d\n",
+ access_desired));
+
+ status = dcerpc_samr_CreateUser2(b, mem_ctx,
+ &domain_pol,
+ &lsa_acct_name,
+ acct_flags,
+ access_desired,
+ &user_pol,
+ &access_granted,
+ &r->out.account_rid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = result;
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) {
+
+ DEBUG(10,("Creation of workstation account failed: %s\n",
+ nt_errstr(status)));
+
+ /* If NT_STATUS_ACCESS_DENIED then we have a valid
+ username/password combo but the user does not have
+ administrator access. */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "User specified does not have "
+ "administrator privileges");
+ }
+
+ goto done;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) {
+ if (!(r->in.join_flags &
+ WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED)) {
+ goto done;
+ }
+ }
+
+ /* We *must* do this.... don't ask... */
+
+ if (NT_STATUS_IS_OK(status)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ }
+ }
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ if (name_types.ids[0] != SID_NAME_USER) {
+ DEBUG(0,("%s is not a user account (type=%d)\n",
+ acct_name, name_types.ids[0]));
+ status = NT_STATUS_INVALID_WORKSTATION;
+ goto done;
+ }
+
+ r->out.account_rid = user_rids.ids[0];
+
+ /* Open handle on user */
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ r->out.account_rid,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Fill in the additional account flags now */
+
+ acct_flags |= ACB_PWNOEXP;
+
+ /* Set account flags on machine account */
+ ZERO_STRUCT(user_info.info16);
+ user_info.info16.acct_flags = acct_flags;
+
+ status = dcerpc_samr_SetUserInfo2(b, mem_ctx,
+ &user_pol,
+ UserControlInformation,
+ &user_info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set account flags for machine account (%s)\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+
+ dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set account flags for machine account (%s)\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ /* Set password on machine account - first try level 26 */
+
+ /*
+ * increase the timeout as password filter modules on the DC
+ * might delay the operation for a significant amount of time
+ */
+ old_timeout = rpccli_set_timeout(pipe_hnd, 600000);
+
+ status = init_samr_CryptPasswordEx(r->in.machine_password,
+ &session_key,
+ &crypt_pwd_ex);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+
+ user_info.info26.password = crypt_pwd_ex;
+ user_info.info26.password_expired = PASS_DONT_CHANGE_AT_NEXT_LOGON;
+
+ status = dcerpc_samr_SetUserInfo2(b, mem_ctx,
+ &user_pol,
+ UserInternal5InformationNew,
+ &user_info,
+ &result);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) {
+
+ /* retry with level 24 */
+
+ status = init_samr_CryptPassword(r->in.machine_password,
+ &session_key,
+ &crypt_pwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+
+ user_info.info24.password = crypt_pwd;
+ user_info.info24.password_expired = PASS_DONT_CHANGE_AT_NEXT_LOGON;
+
+ status = dcerpc_samr_SetUserInfo2(b, mem_ctx,
+ &user_pol,
+ UserInternal5Information,
+ &user_info,
+ &result);
+ }
+
+error:
+ old_timeout = rpccli_set_timeout(pipe_hnd, old_timeout);
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set password for machine account (%s)\n",
+ nt_errstr(status));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+
+ dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set password for machine account (%s)\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+
+ done:
+ if (!pipe_hnd) {
+ return status;
+ }
+
+ data_blob_clear_free(&session_key);
+
+ if (is_valid_policy_hnd(&sam_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &sam_pol, &result);
+ }
+ if (is_valid_policy_hnd(&domain_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ }
+ if (is_valid_policy_hnd(&user_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ }
+ TALLOC_FREE(pipe_hnd);
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS libnet_join_ok(struct messaging_context *msg_ctx,
+ const char *netbios_domain_name,
+ const char *dc_name,
+ const bool use_kerberos)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cli_state *cli = NULL;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct cli_credentials *cli_creds = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds = NULL;
+ struct netlogon_creds_CredentialState *creds = NULL;
+ uint32_t netlogon_flags = 0;
+ NTSTATUS status;
+ int flags = CLI_FULL_CONNECTION_IPC;
+ const char *remote_name = NULL;
+ const struct sockaddr_storage *remote_sockaddr = NULL;
+
+ if (!dc_name) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!secrets_init()) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ status = pdb_get_trust_credentials(netbios_domain_name, NULL,
+ frame, &cli_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* we don't want any old password */
+ cli_credentials_set_old_password(cli_creds, NULL, CRED_SPECIFIED);
+
+ if (use_kerberos) {
+ cli_credentials_set_kerberos_state(cli_creds,
+ CRED_USE_KERBEROS_REQUIRED,
+ CRED_SPECIFIED);
+ }
+
+ status = cli_full_connection_creds(&cli, NULL,
+ dc_name,
+ NULL, 0,
+ "IPC$", "IPC",
+ cli_creds,
+ flags);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ struct cli_credentials *anon_creds = NULL;
+
+ anon_creds = cli_credentials_init_anon(frame);
+ if (anon_creds == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_full_connection_creds(&cli,
+ NULL,
+ dc_name,
+ NULL, 0,
+ "IPC$", "IPC",
+ anon_creds,
+ flags);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = rpccli_create_netlogon_creds_ctx(cli_creds,
+ dc_name,
+ msg_ctx,
+ frame,
+ &netlogon_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = rpccli_setup_netlogon_creds(cli, NCACN_NP,
+ netlogon_creds,
+ true, /* force_reauth */
+ cli_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("connect_to_domain_password_server: "
+ "unable to open the domain client session to "
+ "machine %s. Flags[0x%08X] Error was : %s.\n",
+ dc_name, (unsigned)netlogon_flags,
+ nt_errstr(status)));
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = netlogon_creds_cli_get(netlogon_creds,
+ talloc_tos(),
+ &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ netlogon_flags = creds->negotiate_flags;
+ TALLOC_FREE(creds);
+
+ if (!(netlogon_flags & NETLOGON_NEG_AUTHENTICATED_RPC)) {
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ remote_name = smbXcli_conn_remote_name(cli->conn);
+ remote_sockaddr = smbXcli_conn_remote_sockaddr(cli->conn);
+
+ status = cli_rpc_pipe_open_schannel_with_creds(
+ cli, &ndr_table_netlogon, NCACN_NP,
+ netlogon_creds,
+ remote_name,
+ remote_sockaddr,
+ &netlogon_pipe);
+
+ TALLOC_FREE(netlogon_pipe);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("libnet_join_ok: failed to open schannel session "
+ "on netlogon pipe to server %s for domain %s. "
+ "Error was %s\n",
+ remote_name,
+ netbios_domain_name, nt_errstr(status)));
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_post_verify(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ NTSTATUS status;
+
+ status = libnet_join_ok(r->in.msg_ctx,
+ r->out.netbios_domain_name,
+ r->in.dc_name,
+ r->in.use_kerberos);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to verify domain membership after joining: %s",
+ get_friendly_nt_error_msg(status));
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool libnet_join_unjoindomain_remove_secrets(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ /*
+ * TODO: use values from 'struct libnet_UnjoinCtx' ?
+ */
+ return secrets_delete_machine_password_ex(lp_workgroup(), lp_realm());
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_join_unjoindomain_rpc(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ struct cli_state *cli = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct policy_handle sam_pol, domain_pol, user_pol;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL, result;
+ char *acct_name;
+ uint32_t user_rid;
+ struct lsa_String lsa_acct_name;
+ struct samr_Ids user_rids;
+ struct samr_Ids name_types;
+ union samr_UserInfo *info = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ ZERO_STRUCT(sam_pol);
+ ZERO_STRUCT(domain_pol);
+ ZERO_STRUCT(user_pol);
+
+ status = libnet_join_connect_dc_ipc(r->in.dc_name,
+ r->in.admin_account,
+ r->in.admin_domain,
+ r->in.admin_password,
+ r->in.use_kerberos,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Open the domain */
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_samr,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error connecting to SAM pipe. Error was %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &sam_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &sam_pol,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ r->in.domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Create domain user */
+
+ acct_name = talloc_asprintf(mem_ctx, "%s$", r->in.machine_name);
+ if (!strlower_m(acct_name)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ init_lsa_String(&lsa_acct_name, acct_name);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &user_rids,
+ &name_types,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ if (name_types.ids[0] != SID_NAME_USER) {
+ DEBUG(0, ("%s is not a user account (type=%d)\n", acct_name,
+ name_types.ids[0]));
+ status = NT_STATUS_INVALID_WORKSTATION;
+ goto done;
+ }
+
+ user_rid = user_rids.ids[0];
+
+ /* Open handle on user */
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ user_rid,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get user info */
+
+ status = dcerpc_samr_QueryUserInfo(b, mem_ctx,
+ &user_pol,
+ 16,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ goto done;
+ }
+
+ /* now disable and setuser info */
+
+ info->info16.acct_flags |= ACB_DISABLED;
+
+ status = dcerpc_samr_SetUserInfo(b, mem_ctx,
+ &user_pol,
+ 16,
+ info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ goto done;
+ }
+ status = result;
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+
+done:
+ if (pipe_hnd && b) {
+ if (is_valid_policy_hnd(&domain_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ }
+ if (is_valid_policy_hnd(&sam_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &sam_pol, &result);
+ }
+ TALLOC_FREE(pipe_hnd);
+ }
+
+ if (cli) {
+ cli_shutdown(cli);
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR do_join_modify_vals_config(struct libnet_JoinCtx *r)
+{
+ WERROR werr = WERR_OK;
+ sbcErr err;
+ struct smbconf_ctx *ctx;
+
+ err = smbconf_init_reg(r, &ctx, NULL);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "netbios name",
+ r->in.machine_name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ if (!(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE)) {
+
+ err = smbconf_set_global_parameter(ctx, "security", "user");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "workgroup",
+ r->in.domain_name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ smbconf_delete_global_parameter(ctx, "realm");
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "security", "domain");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "workgroup",
+ r->out.netbios_domain_name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ if (r->out.domain_is_ad) {
+ err = smbconf_set_global_parameter(ctx, "security", "ads");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "realm",
+ r->out.dns_domain_name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+ }
+
+ done:
+ smbconf_shutdown(ctx);
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR do_unjoin_modify_vals_config(struct libnet_UnjoinCtx *r)
+{
+ WERROR werr = WERR_OK;
+ sbcErr err;
+ struct smbconf_ctx *ctx;
+
+ err = smbconf_init_reg(r, &ctx, NULL);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) {
+
+ err = smbconf_set_global_parameter(ctx, "security", "user");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_delete_global_parameter(ctx, "workgroup");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ smbconf_delete_global_parameter(ctx, "realm");
+ }
+
+ done:
+ smbconf_shutdown(ctx);
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR do_JoinConfig(struct libnet_JoinCtx *r)
+{
+ WERROR werr;
+
+ if (!W_ERROR_IS_OK(r->out.result)) {
+ return r->out.result;
+ }
+
+ if (!r->in.modify_config) {
+ return WERR_OK;
+ }
+
+ werr = do_join_modify_vals_config(r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ lp_load_global(get_dyn_CONFIGFILE());
+
+ r->out.modified_config = true;
+ r->out.result = werr;
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_unjoin_config(struct libnet_UnjoinCtx *r)
+{
+ WERROR werr;
+
+ if (!W_ERROR_IS_OK(r->out.result)) {
+ return r->out.result;
+ }
+
+ if (!r->in.modify_config) {
+ return WERR_OK;
+ }
+
+ werr = do_unjoin_modify_vals_config(r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ lp_load_global(get_dyn_CONFIGFILE());
+
+ r->out.modified_config = true;
+ r->out.result = werr;
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool libnet_parse_domain_dc(TALLOC_CTX *mem_ctx,
+ const char *domain_str,
+ const char **domain_p,
+ const char **dc_p)
+{
+ char *domain = NULL;
+ char *dc = NULL;
+ const char *p = NULL;
+
+ if (!domain_str || !domain_p || !dc_p) {
+ return false;
+ }
+
+ p = strchr_m(domain_str, '\\');
+
+ if (p != NULL) {
+ domain = talloc_strndup(mem_ctx, domain_str,
+ PTR_DIFF(p, domain_str));
+ dc = talloc_strdup(mem_ctx, p+1);
+ if (!dc) {
+ return false;
+ }
+ } else {
+ domain = talloc_strdup(mem_ctx, domain_str);
+ dc = NULL;
+ }
+ if (!domain) {
+ return false;
+ }
+
+ *domain_p = domain;
+
+ if (!*dc_p && dc) {
+ *dc_p = dc;
+ }
+
+ return true;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_pre_processing(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ if (!r->in.domain_name) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "No domain name defined");
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (strlen(r->in.machine_name) > 15) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Our netbios name can be at most 15 chars long, "
+ "\"%s\" is %u chars long\n",
+ r->in.machine_name,
+ (unsigned int)strlen(r->in.machine_name));
+ return WERR_INVALID_PARAMETER;
+ }
+
+ r->out.account_name = talloc_asprintf(mem_ctx, "%s$",
+ r->in.machine_name);
+ if (r->out.account_name == NULL) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Unable to construct r->out.account_name");
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (!libnet_parse_domain_dc(mem_ctx, r->in.domain_name,
+ &r->in.domain_name,
+ &r->in.dc_name)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to parse domain name");
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (r->in.request_offline_join) {
+ /*
+ * When in the "request offline join" path we do not have admin
+ * credentials available so we can skip the next steps - gd
+ */
+ return WERR_OK;
+ }
+
+ if (!r->in.admin_domain) {
+ char *admin_domain = NULL;
+ char *admin_account = NULL;
+ bool ok;
+
+ ok = split_domain_user(mem_ctx,
+ r->in.admin_account,
+ &admin_domain,
+ &admin_account);
+ if (!ok) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (admin_domain != NULL) {
+ r->in.admin_domain = admin_domain;
+ } else {
+ r->in.admin_domain = r->in.domain_name;
+ }
+ r->in.admin_account = admin_account;
+ }
+
+ if (!secrets_init()) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Unable to open secrets database");
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static void libnet_join_add_dom_rids_to_builtins(struct dom_sid *domain_sid)
+{
+ NTSTATUS status;
+
+ /* Try adding dom admins to builtin\admins. Only log failures. */
+ status = create_builtin_administrators(domain_sid);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROTOCOL_UNREACHABLE)) {
+ DEBUG(10,("Unable to auto-add domain administrators to "
+ "BUILTIN\\Administrators during join because "
+ "winbindd must be running.\n"));
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("Failed to auto-add domain administrators to "
+ "BUILTIN\\Administrators during join: %s\n",
+ nt_errstr(status)));
+ }
+
+ /* Try adding dom users to builtin\users. Only log failures. */
+ status = create_builtin_users(domain_sid);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROTOCOL_UNREACHABLE)) {
+ DEBUG(10,("Unable to auto-add domain users to BUILTIN\\users "
+ "during join because winbindd must be running.\n"));
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("Failed to auto-add domain administrators to "
+ "BUILTIN\\Administrators during join: %s\n",
+ nt_errstr(status)));
+ }
+
+ /* Try adding dom guests to builtin\guests. Only log failures. */
+ status = create_builtin_guests(domain_sid);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROTOCOL_UNREACHABLE)) {
+ DEBUG(10,("Unable to auto-add domain guests to "
+ "BUILTIN\\Guests during join because "
+ "winbindd must be running.\n"));
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("Failed to auto-add domain guests to "
+ "BUILTIN\\Guests during join: %s\n",
+ nt_errstr(status)));
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_post_processing(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ WERROR werr;
+
+ if (!W_ERROR_IS_OK(r->out.result)) {
+ return r->out.result;
+ }
+
+ if (!(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE)) {
+ werr = do_JoinConfig(r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ return WERR_OK;
+ }
+
+#ifdef HAVE_ADS
+ if (r->out.domain_is_ad &&
+ !(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE)) {
+ ADS_STATUS ads_status;
+
+ ads_status = libnet_join_post_processing_ads_modify(mem_ctx, r);
+ if (!ADS_ERR_OK(ads_status)) {
+ return WERR_GEN_FAILURE;
+ }
+ }
+#endif /* HAVE_ADS */
+
+ if (r->in.provision_computer_account_only) {
+ /*
+ * When we only provision a computer account we are done here - gd.
+ */
+ return WERR_OK;
+ }
+
+ saf_join_store(r->out.netbios_domain_name, r->in.dc_name);
+ if (r->out.dns_domain_name) {
+ saf_join_store(r->out.dns_domain_name, r->in.dc_name);
+ }
+
+ if (!libnet_join_joindomain_store_secrets(mem_ctx, r)) {
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+
+ werr = do_JoinConfig(r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+#ifdef HAVE_ADS
+ if (r->out.domain_is_ad &&
+ !(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE)) {
+ ADS_STATUS ads_status;
+
+ ads_status = libnet_join_post_processing_ads_sync(mem_ctx, r);
+ if (!ADS_ERR_OK(ads_status)) {
+ return WERR_GEN_FAILURE;
+ }
+ }
+#endif /* HAVE_ADS */
+
+ libnet_join_add_dom_rids_to_builtins(r->out.domain_sid);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static int libnet_destroy_JoinCtx(struct libnet_JoinCtx *r)
+{
+ TALLOC_FREE(r->in.ads);
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+static int libnet_destroy_UnjoinCtx(struct libnet_UnjoinCtx *r)
+{
+ TALLOC_FREE(r->in.ads);
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnet_init_JoinCtx(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx **r)
+{
+ struct libnet_JoinCtx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct libnet_JoinCtx);
+ if (!ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ talloc_set_destructor(ctx, libnet_destroy_JoinCtx);
+
+ ctx->in.machine_name = talloc_strdup(ctx, lp_netbios_name());
+ W_ERROR_HAVE_NO_MEMORY(ctx->in.machine_name);
+
+ ctx->in.secure_channel_type = SEC_CHAN_WKSTA;
+
+ ctx->in.desired_encryption_types = 0;
+ ctx->in.desired_encryption_types |= ENC_RC4_HMAC_MD5;
+ ctx->in.desired_encryption_types |= ENC_HMAC_SHA1_96_AES128;
+ ctx->in.desired_encryption_types |= ENC_HMAC_SHA1_96_AES256;
+
+ *r = ctx;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnet_init_UnjoinCtx(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx **r)
+{
+ struct libnet_UnjoinCtx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct libnet_UnjoinCtx);
+ if (!ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ talloc_set_destructor(ctx, libnet_destroy_UnjoinCtx);
+
+ ctx->in.machine_name = talloc_strdup(ctx, lp_netbios_name());
+ W_ERROR_HAVE_NO_MEMORY(ctx->in.machine_name);
+
+ *r = ctx;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_check_config(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ bool valid_security = false;
+ bool valid_workgroup = false;
+ bool valid_realm = false;
+ bool valid_hostname = false;
+ bool ignored_realm = false;
+
+ /* check if configuration is already set correctly */
+
+ valid_workgroup = strequal(lp_workgroup(), r->out.netbios_domain_name);
+ valid_hostname = strequal(lp_netbios_name(), r->in.machine_name);
+
+ switch (r->out.domain_is_ad) {
+ case false:
+ valid_security = (lp_security() == SEC_DOMAIN)
+ || (lp_server_role() == ROLE_DOMAIN_PDC)
+ || (lp_server_role() == ROLE_DOMAIN_BDC);
+ if (valid_workgroup && valid_security) {
+ /* nothing to be done */
+ return WERR_OK;
+ }
+ break;
+ case true:
+ valid_realm = strequal(lp_realm(), r->out.dns_domain_name);
+ switch (lp_security()) {
+ case SEC_DOMAIN:
+ if (!valid_realm && lp_winbind_rpc_only()) {
+ valid_realm = true;
+ ignored_realm = true;
+ }
+
+ FALL_THROUGH;
+ case SEC_ADS:
+ valid_security = true;
+ }
+
+ if (valid_workgroup && valid_realm && valid_security &&
+ valid_hostname) {
+ if (ignored_realm && !r->in.modify_config)
+ {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Warning: ignoring realm when "
+ "joining AD domain with "
+ "'security=domain' and "
+ "'winbind rpc only = yes'. "
+ "(realm set to '%s', "
+ "should be '%s').", lp_realm(),
+ r->out.dns_domain_name);
+ }
+ /* nothing to be done */
+ return WERR_OK;
+ }
+ break;
+ }
+
+ /* check if we are supposed to manipulate configuration */
+
+ if (!r->in.modify_config) {
+
+ char *wrong_conf = talloc_strdup(mem_ctx, "");
+
+ if (!valid_hostname) {
+ wrong_conf = talloc_asprintf_append(wrong_conf,
+ "\"netbios name\" set to '%s', should be '%s'",
+ lp_netbios_name(), r->in.machine_name);
+ W_ERROR_HAVE_NO_MEMORY(wrong_conf);
+ }
+
+ if (!valid_workgroup) {
+ wrong_conf = talloc_asprintf_append(wrong_conf,
+ "\"workgroup\" set to '%s', should be '%s'",
+ lp_workgroup(), r->out.netbios_domain_name);
+ W_ERROR_HAVE_NO_MEMORY(wrong_conf);
+ }
+
+ if (!valid_realm) {
+ wrong_conf = talloc_asprintf_append(wrong_conf,
+ "\"realm\" set to '%s', should be '%s'",
+ lp_realm(), r->out.dns_domain_name);
+ W_ERROR_HAVE_NO_MEMORY(wrong_conf);
+ }
+
+ if (!valid_security) {
+ const char *sec = NULL;
+ switch (lp_security()) {
+ case SEC_USER: sec = "user"; break;
+ case SEC_DOMAIN: sec = "domain"; break;
+ case SEC_ADS: sec = "ads"; break;
+ }
+ wrong_conf = talloc_asprintf_append(wrong_conf,
+ "\"security\" set to '%s', should be %s",
+ sec, r->out.domain_is_ad ?
+ "either 'domain' or 'ads'" : "'domain'");
+ W_ERROR_HAVE_NO_MEMORY(wrong_conf);
+ }
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Invalid configuration (%s) and configuration modification "
+ "was not requested", wrong_conf);
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ /* check if we are able to manipulate configuration */
+
+ if (!lp_config_backend_is_registry()) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Configuration manipulation requested but not "
+ "supported by backend");
+ return WERR_NOT_SUPPORTED;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_DomainJoin(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct cli_state *cli = NULL;
+#ifdef HAVE_ADS
+ ADS_STATUS ads_status;
+#endif /* HAVE_ADS */
+ const char *pre_connect_realm = NULL;
+ const char *sitename = NULL;
+ struct netr_DsRGetDCNameInfo *info;
+ const char *dc;
+ uint32_t name_type_flags = 0;
+
+ /* Before contacting a DC, we can securely know
+ * the realm only if the user specifies it.
+ */
+ if (r->in.use_kerberos &&
+ r->in.domain_name_type == JoinDomNameTypeDNS) {
+ pre_connect_realm = r->in.domain_name;
+ }
+
+ if (r->in.domain_name_type == JoinDomNameTypeDNS) {
+ name_type_flags = DS_IS_DNS_NAME;
+ } else if (r->in.domain_name_type == JoinDomNameTypeNBT) {
+ name_type_flags = DS_IS_FLAT_NAME;
+ }
+
+ if (r->in.dc_name) {
+ status = dsgetonedcname(mem_ctx,
+ r->in.msg_ctx,
+ r->in.domain_name,
+ r->in.dc_name,
+ DS_DIRECTORY_SERVICE_REQUIRED |
+ DS_WRITABLE_REQUIRED |
+ DS_RETURN_DNS_NAME |
+ name_type_flags,
+ &info);
+ } else {
+ status = dsgetdcname(mem_ctx,
+ r->in.msg_ctx,
+ r->in.domain_name,
+ NULL,
+ NULL,
+ DS_FORCE_REDISCOVERY |
+ DS_DIRECTORY_SERVICE_REQUIRED |
+ DS_WRITABLE_REQUIRED |
+ DS_RETURN_DNS_NAME |
+ name_type_flags,
+ &info);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to find DC for domain %s - %s",
+ r->in.domain_name,
+ get_friendly_nt_error_msg(status));
+ return WERR_NERR_DCNOTFOUND;
+ }
+
+ dc = strip_hostname(info->dc_unc);
+ r->in.dc_name = talloc_strdup(mem_ctx, dc);
+ W_ERROR_HAVE_NO_MEMORY(r->in.dc_name);
+
+ if (info->dc_address == NULL || info->dc_address[0] != '\\' ||
+ info->dc_address[1] != '\\') {
+ DBG_ERR("ill-formed DC address '%s'\n",
+ info->dc_address);
+ return WERR_NERR_DCNOTFOUND;
+ }
+
+ sitename = info->dc_site_name;
+ /* info goes out of scope but the memory stays
+ allocated on the talloc context */
+
+ /* return the allocated netr_DsRGetDCNameInfo struct */
+ r->out.dcinfo = info;
+
+ if (pre_connect_realm != NULL) {
+ struct sockaddr_storage ss = {0};
+ const char *numeric_dcip = info->dc_address + 2;
+
+ if (numeric_dcip[0] == '\0') {
+ if (!interpret_string_addr(&ss, numeric_dcip,
+ AI_NUMERICHOST)) {
+ DBG_ERR(
+ "cannot parse IP address '%s' of DC '%s'\n",
+ numeric_dcip, r->in.dc_name);
+ return WERR_NERR_DCNOTFOUND;
+ }
+ } else {
+ if (!interpret_string_addr(&ss, r->in.dc_name, 0)) {
+ DBG_WARNING(
+ "cannot resolve IP address of DC '%s'\n",
+ r->in.dc_name);
+ return WERR_NERR_DCNOTFOUND;
+ }
+ }
+
+ /* The domain parameter is only used as modifier
+ * to krb5.conf file name. _JOIN_ is is not a valid
+ * NetBIOS name so it cannot clash with another domain
+ * -- Uri.
+ */
+ create_local_private_krb5_conf_for_domain(pre_connect_realm,
+ "_JOIN_",
+ sitename,
+ &ss);
+ }
+
+ status = libnet_join_lookup_dc_rpc(mem_ctx, r, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to lookup DC info for domain '%s' over rpc: %s",
+ r->in.domain_name, get_friendly_nt_error_msg(status));
+ return ntstatus_to_werror(status);
+ }
+
+ werr = libnet_join_check_config(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ if (!r->in.provision_computer_account_only) {
+ goto done;
+ }
+ /* do not fail when only provisioning */
+ }
+
+#ifdef HAVE_ADS
+
+ if (r->out.domain_is_ad) {
+ create_local_private_krb5_conf_for_domain(
+ r->out.dns_domain_name, r->out.netbios_domain_name,
+ sitename, smbXcli_conn_remote_sockaddr(cli->conn));
+ }
+
+ if (r->out.domain_is_ad &&
+ !(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE)) {
+
+ const char *initial_account_ou = r->in.account_ou;
+
+ /*
+ * we want to create the msDS-SupportedEncryptionTypes attribute
+ * as early as possible so always try an LDAP create as the user
+ * first. We copy r->in.account_ou because it may be changed
+ * during the machine pre-creation.
+ */
+
+ ads_status = libnet_join_connect_ads_user(mem_ctx, r);
+ if (!ADS_ERR_OK(ads_status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to connect to AD: %s",
+ ads_errstr(ads_status));
+ return WERR_NERR_DEFAULTJOINREQUIRED;
+ }
+
+ ads_status = libnet_join_precreate_machine_acct(mem_ctx, r);
+ if (ADS_ERR_OK(ads_status)) {
+
+ /*
+ * LDAP object creation succeeded.
+ */
+ r->in.join_flags &= ~WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE;
+
+ return WERR_OK;
+ }
+
+ if (initial_account_ou != NULL) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to precreate account in ou %s: %s",
+ r->in.account_ou,
+ ads_errstr(ads_status));
+ return WERR_NERR_DEFAULTJOINREQUIRED;
+ }
+
+ DBG_INFO("Failed to pre-create account in OU %s: %s\n",
+ r->in.account_ou, ads_errstr(ads_status));
+ }
+#endif /* HAVE_ADS */
+
+ if ((r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE) &&
+ (r->in.join_flags & WKSSVC_JOIN_FLAGS_MACHINE_PWD_PASSED)) {
+ status = libnet_join_joindomain_rpc_unsecure(mem_ctx, r, cli);
+ } else {
+ status = libnet_join_joindomain_rpc(mem_ctx, r, cli);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to join domain '%s' over rpc: %s",
+ r->in.domain_name, get_friendly_nt_error_msg(status));
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) {
+ return WERR_NERR_SETUPALREADYJOINED;
+ }
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (cli) {
+ cli_shutdown(cli);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_DomainOfflineJoin(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct ODJ_WIN7BLOB win7blob;
+ struct OP_JOINPROV3_PART joinprov3;
+ const char *dc_name;
+
+ if (!r->in.request_offline_join) {
+ return WERR_NERR_DEFAULTJOINREQUIRED;
+ }
+
+ if (r->in.odj_provision_data == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnet_odj_find_win7blob(r->in.odj_provision_data, &win7blob);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ r->out.netbios_domain_name = talloc_strdup(mem_ctx,
+ win7blob.DnsDomainInfo.Name.string);
+ W_ERROR_HAVE_NO_MEMORY(r->out.netbios_domain_name);
+
+ r->out.dns_domain_name = talloc_strdup(mem_ctx,
+ win7blob.DnsDomainInfo.DnsDomainName.string);
+ W_ERROR_HAVE_NO_MEMORY(r->out.dns_domain_name);
+
+ r->out.forest_name = talloc_strdup(mem_ctx,
+ win7blob.DnsDomainInfo.DnsForestName.string);
+ W_ERROR_HAVE_NO_MEMORY(r->out.forest_name);
+
+ r->out.domain_guid = win7blob.DnsDomainInfo.DomainGuid;
+ r->out.domain_sid = dom_sid_dup(mem_ctx,
+ win7blob.DnsDomainInfo.Sid);
+ W_ERROR_HAVE_NO_MEMORY(r->out.domain_sid);
+
+ werr = libnet_odj_find_joinprov3(r->in.odj_provision_data, &joinprov3);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ r->out.account_rid = joinprov3.Rid;
+
+ dc_name = strip_hostname(win7blob.DcInfo.dc_address);
+ if (dc_name == NULL) {
+ return WERR_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+ r->in.dc_name = talloc_strdup(mem_ctx, dc_name);
+ W_ERROR_HAVE_NO_MEMORY(r->in.dc_name);
+
+ r->out.domain_is_ad = true;
+
+ /* we cannot use talloc_steal but have to deep copy the struct here */
+ status = copy_netr_DsRGetDCNameInfo(mem_ctx, &win7blob.DcInfo,
+ &r->out.dcinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ werr = libnet_join_check_config(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ return WERR_OK;
+#if 0
+ /* the following fields are currently not filled in */
+
+ const char * dn;
+ uint32_t set_encryption_types;
+ const char * krb5_salt;
+#endif
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_rollback(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ WERROR werr;
+ struct libnet_UnjoinCtx *u = NULL;
+
+ werr = libnet_init_UnjoinCtx(mem_ctx, &u);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ u->in.debug = r->in.debug;
+ u->in.dc_name = r->in.dc_name;
+ u->in.domain_name = r->in.domain_name;
+ u->in.admin_account = r->in.admin_account;
+ u->in.admin_password = r->in.admin_password;
+ u->in.modify_config = r->in.modify_config;
+ u->in.use_kerberos = r->in.use_kerberos;
+ u->in.unjoin_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE;
+
+ werr = libnet_Unjoin(mem_ctx, u);
+ TALLOC_FREE(u);
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnet_Join(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ WERROR werr;
+
+ if (r->in.debug) {
+ LIBNET_JOIN_IN_DUMP_CTX(mem_ctx, r);
+ }
+
+ ZERO_STRUCT(r->out);
+
+ werr = libnet_join_pre_processing(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) {
+ if (r->in.request_offline_join) {
+ werr = libnet_DomainOfflineJoin(mem_ctx, r);
+ } else {
+ werr = libnet_DomainJoin(mem_ctx, r);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ }
+
+ werr = libnet_join_post_processing(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (r->in.provision_computer_account_only) {
+ /*
+ * When we only provision a computer account we are done here - gd.
+ */
+ goto done;
+ }
+
+ if (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) {
+ if (r->in.request_offline_join) {
+ /*
+ * When we are serving an offline domain join request we
+ * have no network so we are done here - gd.
+ */
+ goto done;
+ }
+
+ werr = libnet_join_post_verify(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ libnet_join_rollback(mem_ctx, r);
+ }
+ }
+
+ done:
+ r->out.result = werr;
+
+ if (r->in.debug) {
+ LIBNET_JOIN_OUT_DUMP_CTX(mem_ctx, r);
+ }
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_DomainUnjoin(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ NTSTATUS status;
+
+ if (!r->in.domain_sid) {
+ struct dom_sid sid;
+ if (!secrets_fetch_domain_sid(lp_workgroup(), &sid)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "Unable to fetch domain sid: are we joined?");
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+ r->in.domain_sid = dom_sid_dup(mem_ctx, &sid);
+ W_ERROR_HAVE_NO_MEMORY(r->in.domain_sid);
+ }
+
+ if (!(r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE) &&
+ !r->in.delete_machine_account) {
+ libnet_join_unjoindomain_remove_secrets(mem_ctx, r);
+ return WERR_OK;
+ }
+
+ if (!r->in.dc_name) {
+ struct netr_DsRGetDCNameInfo *info;
+ const char *dc;
+ status = dsgetdcname(mem_ctx,
+ r->in.msg_ctx,
+ r->in.domain_name,
+ NULL,
+ NULL,
+ DS_DIRECTORY_SERVICE_REQUIRED |
+ DS_WRITABLE_REQUIRED |
+ DS_RETURN_DNS_NAME,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to find DC for domain %s - %s",
+ r->in.domain_name,
+ get_friendly_nt_error_msg(status));
+ return WERR_NERR_DCNOTFOUND;
+ }
+
+ dc = strip_hostname(info->dc_unc);
+ r->in.dc_name = talloc_strdup(mem_ctx, dc);
+ W_ERROR_HAVE_NO_MEMORY(r->in.dc_name);
+ }
+
+#ifdef HAVE_ADS
+ /* for net ads leave, try to delete the account. If it works,
+ no sense in disabling. If it fails, we can still try to
+ disable it. jmcd */
+
+ if (r->in.delete_machine_account) {
+ ADS_STATUS ads_status;
+ ads_status = libnet_unjoin_connect_ads(mem_ctx, r);
+ if (ADS_ERR_OK(ads_status)) {
+ /* dirty hack */
+ r->out.dns_domain_name =
+ talloc_strdup(mem_ctx,
+ r->in.ads->server.realm);
+ ads_status =
+ libnet_unjoin_remove_machine_acct(mem_ctx, r);
+ }
+ if (!ADS_ERR_OK(ads_status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to remove machine account from AD: %s",
+ ads_errstr(ads_status));
+ } else {
+ r->out.deleted_machine_account = true;
+ W_ERROR_HAVE_NO_MEMORY(r->out.dns_domain_name);
+ libnet_join_unjoindomain_remove_secrets(mem_ctx, r);
+ return WERR_OK;
+ }
+ }
+#endif /* HAVE_ADS */
+
+ /* The WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE flag really means
+ "disable". */
+ if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE) {
+ status = libnet_join_unjoindomain_rpc(mem_ctx, r);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to disable machine account via rpc: %s",
+ get_friendly_nt_error_msg(status));
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+ return ntstatus_to_werror(status);
+ }
+
+ r->out.dns_domain_name = talloc_strdup(mem_ctx,
+ r->in.domain_name);
+ r->out.disabled_machine_account = true;
+ }
+
+ /* If disable succeeded or was not requested at all, we
+ should be getting rid of our end of things */
+
+ libnet_join_unjoindomain_remove_secrets(mem_ctx, r);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_unjoin_pre_processing(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ if (!r->in.domain_name) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "No domain name defined");
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!libnet_parse_domain_dc(mem_ctx, r->in.domain_name,
+ &r->in.domain_name,
+ &r->in.dc_name)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "Failed to parse domain name");
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (IS_DC) {
+ return WERR_NERR_SETUPDOMAINCONTROLLER;
+ }
+
+ if (!r->in.admin_domain) {
+ char *admin_domain = NULL;
+ char *admin_account = NULL;
+ bool ok;
+
+ ok = split_domain_user(mem_ctx,
+ r->in.admin_account,
+ &admin_domain,
+ &admin_account);
+ if (!ok) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (admin_domain != NULL) {
+ r->in.admin_domain = admin_domain;
+ } else {
+ r->in.admin_domain = r->in.domain_name;
+ }
+ r->in.admin_account = admin_account;
+ }
+
+ if (!secrets_init()) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "Unable to open secrets database");
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_unjoin_post_processing(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ saf_delete(r->out.netbios_domain_name);
+ saf_delete(r->out.dns_domain_name);
+
+ return libnet_unjoin_config(r);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnet_Unjoin(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ WERROR werr;
+
+ if (r->in.debug) {
+ LIBNET_UNJOIN_IN_DUMP_CTX(mem_ctx, r);
+ }
+
+ werr = libnet_unjoin_pre_processing(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) {
+ werr = libnet_DomainUnjoin(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ libnet_unjoin_config(r);
+ goto done;
+ }
+ }
+
+ werr = libnet_unjoin_post_processing(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ done:
+ r->out.result = werr;
+
+ if (r->in.debug) {
+ LIBNET_UNJOIN_OUT_DUMP_CTX(mem_ctx, r);
+ }
+
+ return werr;
+}
diff --git a/source3/libnet/libnet_join.h b/source3/libnet/libnet_join.h
new file mode 100644
index 0000000..b7e2f0b
--- /dev/null
+++ b/source3/libnet/libnet_join.h
@@ -0,0 +1,40 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Join Support
+ * Copyright (C) Gerald (Jerry) Carter 2006
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * 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/>.
+ */
+
+#ifndef _LIBNET_LIBNET_JOIN_H_
+#define _LIBNET_LIBNET_JOIN_H_
+
+/* The following definitions come from libnet/libnet_join.c */
+
+struct messaging_context;
+NTSTATUS libnet_join_ok(struct messaging_context *msg_ctx,
+ const char *netbios_domain_name,
+ const char *dc_name,
+ const bool use_kerberos);
+WERROR libnet_init_JoinCtx(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx **r);
+WERROR libnet_init_UnjoinCtx(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx **r);
+WERROR libnet_Join(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r);
+WERROR libnet_Unjoin(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r);
+
+#endif /* _LIBNET_LIBNET_JOIN_H_ */
diff --git a/source3/libnet/libnet_join_offline.c b/source3/libnet/libnet_join_offline.c
new file mode 100644
index 0000000..d1317dd
--- /dev/null
+++ b/source3/libnet/libnet_join_offline.c
@@ -0,0 +1,441 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Join offline support
+ * Copyright (C) Guenther Deschner 2021
+ *
+ * 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 "librpc/gen_ndr/ndr_libnet_join.h"
+#include "../librpc/gen_ndr/ndr_ODJ.h"
+#include "libnet/libnet_join_offline.h"
+#include "libcli/security/dom_sid.h"
+#include "rpc_client/util_netlogon.h"
+
+static WERROR libnet_odj_compose_ODJ_WIN7BLOB(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct ODJ_WIN7BLOB *b)
+{
+ char *samaccount;
+ uint32_t len;
+ struct ODJ_POLICY_DNS_DOMAIN_INFO i = {
+ .Sid = NULL,
+ };
+
+ ZERO_STRUCTP(b);
+
+ b->lpDomain = talloc_strdup(mem_ctx, r->out.dns_domain_name);
+ if (b->lpDomain == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ samaccount = talloc_strdup(mem_ctx, r->out.account_name);
+ if (samaccount == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ len = strlen(samaccount);
+ if (samaccount[len-1] == '$') {
+ samaccount[len-1] = '\0';
+ }
+ b->lpMachineName = samaccount;
+
+ b->lpMachinePassword = talloc_strdup(mem_ctx, r->in.machine_password);
+ if (b->lpMachinePassword == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* fill up ODJ_POLICY_DNS_DOMAIN_INFO */
+
+ i.Name.string = talloc_strdup(mem_ctx, r->out.netbios_domain_name);
+ if (i.Name.string == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ i.DnsDomainName.string = talloc_strdup(mem_ctx, r->out.dns_domain_name);
+ if (i.DnsDomainName.string == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ i.DnsForestName.string = talloc_strdup(mem_ctx, r->out.forest_name);
+ if (i.DnsForestName.string == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ i.DomainGuid = r->out.domain_guid;
+ i.Sid = dom_sid_dup(mem_ctx, r->out.domain_sid);
+ if (i.Sid == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->DnsDomainInfo = i;
+
+ if (r->out.dcinfo) {
+ struct netr_DsRGetDCNameInfo *p;
+
+ p = talloc_steal(mem_ctx, r->out.dcinfo);
+ if (p == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->DcInfo = *p;
+ }
+
+ /*
+ * According to
+ * https://docs.microsoft.com/en-us/windows/win32/netmgmt/odj-odj_win7blob
+ * it should be 0 but Windows 2019 always sets 6 - gd.
+ */
+ b->Options = 6;
+
+ return WERR_OK;
+}
+
+static WERROR libnet_odj_compose_OP_JOINPROV2_PART(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct OP_JOINPROV2_PART **p)
+{
+ struct OP_JOINPROV2_PART *b;
+
+ b = talloc_zero(mem_ctx, struct OP_JOINPROV2_PART);
+ if (b == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* TODO */
+
+ *p = b;
+
+ return WERR_INVALID_LEVEL;
+}
+
+static WERROR libnet_odj_compose_OP_JOINPROV3_PART(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct OP_JOINPROV3_PART **p)
+{
+ struct OP_JOINPROV3_PART *b;
+ struct dom_sid *sid;
+
+ b = talloc_zero(mem_ctx, struct OP_JOINPROV3_PART);
+ if (b == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->Rid = r->out.account_rid;
+ sid = dom_sid_add_rid(mem_ctx, r->out.domain_sid, r->out.account_rid);
+ if (sid == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->lpSid = dom_sid_string(mem_ctx, sid);
+ if (b->lpSid == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *p = b;
+
+ return WERR_OK;
+}
+
+static WERROR libnet_odj_compose_OP_PACKAGE_PART(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ const struct ODJ_WIN7BLOB *win7,
+ const char *join_provider_guid,
+ uint32_t flags,
+ struct OP_PACKAGE_PART *p)
+{
+ struct GUID guid;
+ uint32_t level;
+ WERROR werr;
+
+ if (!NT_STATUS_IS_OK(GUID_from_string(join_provider_guid, &guid))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ level = odj_switch_level_from_guid(&guid);
+
+ p->PartType = guid;
+ p->ulFlags = flags;
+ p->part_len = 0; /* autogenerated */
+ p->Part = talloc_zero(mem_ctx, union OP_PACKAGE_PART_u);
+ if (p->Part == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ switch (level) {
+ case 1: /* ODJ_GUID_JOIN_PROVIDER */
+ if (win7 == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+ p->Part->win7blob = *win7;
+ break;
+ case 2: /* ODJ_GUID_JOIN_PROVIDER2 */
+ werr = libnet_odj_compose_OP_JOINPROV2_PART(mem_ctx, r,
+ &p->Part->join_prov2.p);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ break;
+ case 3: /* ODJ_GUID_JOIN_PROVIDER3 */
+ werr = libnet_odj_compose_OP_JOINPROV3_PART(mem_ctx, r,
+ &p->Part->join_prov3.p);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR libnet_odj_compose_OP_PACKAGE_PART_COLLECTION(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ const struct ODJ_WIN7BLOB *win7,
+ struct OP_PACKAGE_PART_COLLECTION **pp)
+{
+ WERROR werr;
+ struct OP_PACKAGE_PART_COLLECTION *p;
+
+ p = talloc_zero(mem_ctx, struct OP_PACKAGE_PART_COLLECTION);
+ if (p == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ p->cParts = 2;
+ p->pParts = talloc_zero_array(p, struct OP_PACKAGE_PART, p->cParts);
+ if (p->pParts == NULL) {
+ talloc_free(p);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werr = libnet_odj_compose_OP_PACKAGE_PART(p, r, win7,
+ ODJ_GUID_JOIN_PROVIDER,
+ OPSPI_PACKAGE_PART_ESSENTIAL,
+ &p->pParts[0]);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(p);
+ return werr;
+ }
+
+ werr = libnet_odj_compose_OP_PACKAGE_PART(p, r, NULL,
+ ODJ_GUID_JOIN_PROVIDER3,
+ 0,
+ &p->pParts[1]);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(p);
+ return werr;
+ }
+
+ *pp = p;
+
+ return WERR_OK;
+}
+
+static WERROR libnet_odj_compose_OP_PACKAGE(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ const struct ODJ_WIN7BLOB *win7,
+ struct OP_PACKAGE **pp)
+{
+ WERROR werr;
+ struct OP_PACKAGE_PART_COLLECTION *c;
+ struct OP_PACKAGE *p;
+
+ p = talloc_zero(mem_ctx, struct OP_PACKAGE);
+ if (p == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werr = libnet_odj_compose_OP_PACKAGE_PART_COLLECTION(p, r, win7, &c);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(p);
+ return werr;
+ }
+
+ p->EncryptionType = GUID_zero();
+
+ p->WrappedPartCollection.cbBlob = 0; /* autogenerated */
+ p->WrappedPartCollection.w = talloc_zero(p,
+ struct OP_PACKAGE_PART_COLLECTION_serialized_ptr);
+ if (p->WrappedPartCollection.w == NULL) {
+ talloc_free(p);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ p->WrappedPartCollection.w->s.p = c;
+
+ *pp = p;
+
+ return WERR_OK;
+}
+
+WERROR libnet_odj_compose_ODJ_PROVISION_DATA(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct ODJ_PROVISION_DATA **b_p)
+{
+ WERROR werr;
+ struct ODJ_PROVISION_DATA *b;
+ struct ODJ_WIN7BLOB win7;
+ struct OP_PACKAGE *package;
+
+ b = talloc_zero(mem_ctx, struct ODJ_PROVISION_DATA);
+ if (b == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->ulVersion = 1;
+ b->ulcBlobs = 2;
+ b->pBlobs = talloc_zero_array(b, struct ODJ_BLOB, b->ulcBlobs);
+ if (b->pBlobs == NULL) {
+ talloc_free(b);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werr = libnet_odj_compose_ODJ_WIN7BLOB(b, r, &win7);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(b);
+ return werr;
+ }
+
+ werr = libnet_odj_compose_OP_PACKAGE(b, r, &win7, &package);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(b);
+ return werr;
+ }
+
+ b->pBlobs[0].ulODJFormat = ODJ_WIN7_FORMAT;
+ b->pBlobs[0].cbBlob = 0; /* autogenerated */
+ b->pBlobs[0].pBlob = talloc_zero(b, union ODJ_BLOB_u);
+ if (b->pBlobs[0].pBlob == NULL) {
+ talloc_free(b);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ b->pBlobs[0].pBlob->odj_win7blob = win7;
+
+ b->pBlobs[1].ulODJFormat = ODJ_WIN8_FORMAT;
+ b->pBlobs[1].cbBlob = 0; /* autogenerated */
+ b->pBlobs[1].pBlob = talloc_zero(b, union ODJ_BLOB_u);
+ if (b->pBlobs[1].pBlob == NULL) {
+ talloc_free(b);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ b->pBlobs[1].pBlob->op_package.p = package;
+
+ *b_p = b;
+
+ return WERR_OK;
+}
+
+WERROR libnet_odj_find_win7blob(const struct ODJ_PROVISION_DATA *r,
+ struct ODJ_WIN7BLOB *win7blob)
+{
+ int i;
+
+ if (r == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ for (i = 0; i < r->ulcBlobs; i++) {
+
+ struct ODJ_BLOB b = r->pBlobs[i];
+
+ switch (b.ulODJFormat) {
+ case ODJ_WIN7_FORMAT:
+ *win7blob = b.pBlob->odj_win7blob;
+ return WERR_OK;
+
+ case ODJ_WIN8_FORMAT: {
+ NTSTATUS status;
+ struct OP_PACKAGE_PART_COLLECTION *col;
+ struct GUID guid;
+ int k;
+
+ if (b.pBlob->op_package.p->WrappedPartCollection.w == NULL) {
+ return WERR_BAD_FORMAT;
+ }
+
+ col = b.pBlob->op_package.p->WrappedPartCollection.w->s.p;
+
+ status = GUID_from_string(ODJ_GUID_JOIN_PROVIDER, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (k = 0; k < col->cParts; k++) {
+ if (GUID_equal(&guid, &col->pParts[k].PartType)) {
+ *win7blob = col->pParts[k].Part->win7blob;
+ return WERR_OK;
+ }
+ }
+ break;
+ }
+ default:
+ return WERR_BAD_FORMAT;
+ }
+ }
+
+ return WERR_BAD_FORMAT;
+}
+
+
+WERROR libnet_odj_find_joinprov3(const struct ODJ_PROVISION_DATA *r,
+ struct OP_JOINPROV3_PART *joinprov3)
+{
+ int i;
+
+ if (r == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ for (i = 0; i < r->ulcBlobs; i++) {
+
+ struct ODJ_BLOB b = r->pBlobs[i];
+
+ switch (b.ulODJFormat) {
+ case ODJ_WIN7_FORMAT:
+ continue;
+
+ case ODJ_WIN8_FORMAT: {
+ NTSTATUS status;
+ struct OP_PACKAGE_PART_COLLECTION *col;
+ struct GUID guid;
+ int k;
+
+ if (b.pBlob->op_package.p->WrappedPartCollection.w == NULL) {
+ return WERR_BAD_FORMAT;
+ }
+
+ col = b.pBlob->op_package.p->WrappedPartCollection.w->s.p;
+
+ status = GUID_from_string(ODJ_GUID_JOIN_PROVIDER3, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (k = 0; k < col->cParts; k++) {
+ if (GUID_equal(&guid, &col->pParts[k].PartType)) {
+ *joinprov3 = *col->pParts[k].Part->join_prov3.p;
+ return WERR_OK;
+ }
+ }
+ break;
+ }
+ default:
+ return WERR_BAD_FORMAT;
+ }
+ }
+
+ return WERR_BAD_FORMAT;
+}
diff --git a/source3/libnet/libnet_join_offline.h b/source3/libnet/libnet_join_offline.h
new file mode 100644
index 0000000..7507c58
--- /dev/null
+++ b/source3/libnet/libnet_join_offline.h
@@ -0,0 +1,26 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Join offline support
+ * Copyright (C) Guenther Deschner 2021
+ *
+ * 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/>.
+ */
+
+WERROR libnet_odj_compose_ODJ_PROVISION_DATA(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct ODJ_PROVISION_DATA **b_p);
+WERROR libnet_odj_find_win7blob(const struct ODJ_PROVISION_DATA *r,
+ struct ODJ_WIN7BLOB *win7blob);
+WERROR libnet_odj_find_joinprov3(const struct ODJ_PROVISION_DATA *r,
+ struct OP_JOINPROV3_PART *joinprov3);
diff --git a/source3/libnet/libnet_keytab.c b/source3/libnet/libnet_keytab.c
new file mode 100644
index 0000000..31d0605
--- /dev/null
+++ b/source3/libnet/libnet_keytab.c
@@ -0,0 +1,457 @@
+/*
+ Unix SMB/CIFS implementation.
+ dump the remote SAM using rpc samsync operations
+
+ Copyright (C) Guenther Deschner 2008.
+ Copyright (C) Michael Adam 2008
+
+ 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 "smb_krb5.h"
+#include "ads.h"
+#include "secrets.h"
+#include "libnet/libnet_keytab.h"
+
+#ifdef HAVE_KRB5
+
+/****************************************************************
+****************************************************************/
+
+static int keytab_close(struct libnet_keytab_context *ctx)
+{
+ if (!ctx) {
+ return 0;
+ }
+
+ if (ctx->keytab && ctx->context) {
+ krb5_kt_close(ctx->context, ctx->keytab);
+ }
+
+ if (ctx->context) {
+ krb5_free_context(ctx->context);
+ }
+
+ TALLOC_FREE(ctx->ads);
+
+ TALLOC_FREE(ctx);
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+krb5_error_code libnet_keytab_init(TALLOC_CTX *mem_ctx,
+ const char *keytab_name,
+ struct libnet_keytab_context **ctx)
+{
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ const char *keytab_string = NULL;
+
+ struct libnet_keytab_context *r;
+
+ r = talloc_zero(mem_ctx, struct libnet_keytab_context);
+ if (!r) {
+ return ENOMEM;
+ }
+
+ talloc_set_destructor(r, keytab_close);
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ return ret;
+ }
+
+ ret = smb_krb5_kt_open_relative(context,
+ keytab_name,
+ true, /* write_access */
+ &keytab);
+ if (ret) {
+ DEBUG(1,("keytab_init: smb_krb5_open_keytab failed (%s)\n",
+ error_message(ret)));
+ krb5_free_context(context);
+ return ret;
+ }
+
+ ret = smb_krb5_kt_get_name(mem_ctx, context, keytab, &keytab_string);
+ if (ret) {
+ krb5_kt_close(context, keytab);
+ krb5_free_context(context);
+ return ret;
+ }
+
+ r->context = context;
+ r->keytab = keytab;
+ r->keytab_name = keytab_string;
+ r->clean_old_entries = false;
+
+ *ctx = r;
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+/**
+ * Remove all entries that have the given principal, kvno and enctype.
+ */
+static krb5_error_code libnet_keytab_remove_entries(krb5_context context,
+ krb5_keytab keytab,
+ const char *principal,
+ int kvno,
+ const krb5_enctype enctype,
+ bool ignore_kvno)
+{
+ krb5_error_code ret;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kt_entry;
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret) {
+ return 0;
+ }
+
+ while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0)
+ {
+ krb5_keyblock *keyp;
+ char *princ_s = NULL;
+
+ if (kt_entry.vno != kvno && !ignore_kvno) {
+ goto cont;
+ }
+
+ keyp = KRB5_KT_KEY(&kt_entry);
+
+ if (KRB5_KEY_TYPE(keyp) != enctype) {
+ goto cont;
+ }
+
+ ret = smb_krb5_unparse_name(talloc_tos(), context, kt_entry.principal,
+ &princ_s);
+ if (ret) {
+ DEBUG(5, ("smb_krb5_unparse_name failed (%s)\n",
+ error_message(ret)));
+ goto cont;
+ }
+
+ if (strcmp(principal, princ_s) != 0) {
+ goto cont;
+ }
+
+ /* match found - remove */
+
+ DEBUG(10, ("found entry for principal %s, kvno %d, "
+ "enctype %d - trying to remove it\n",
+ princ_s, kt_entry.vno, KRB5_KEY_TYPE(keyp)));
+
+ ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+ ZERO_STRUCT(cursor);
+ if (ret) {
+ DEBUG(5, ("krb5_kt_end_seq_get failed (%s)\n",
+ error_message(ret)));
+ goto cont;
+ }
+
+ ret = krb5_kt_remove_entry(context, keytab,
+ &kt_entry);
+ if (ret) {
+ DEBUG(5, ("krb5_kt_remove_entry failed (%s)\n",
+ error_message(ret)));
+ goto cont;
+ }
+ DEBUG(10, ("removed entry for principal %s, kvno %d, "
+ "enctype %d\n", princ_s, kt_entry.vno,
+ KRB5_KEY_TYPE(keyp)));
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret) {
+ DEBUG(5, ("krb5_kt_start_seq_get failed (%s)\n",
+ error_message(ret)));
+ goto cont;
+ }
+
+cont:
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ TALLOC_FREE(princ_s);
+ }
+
+ ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+ if (ret) {
+ DEBUG(5, ("krb5_kt_end_seq_get failed (%s)\n",
+ error_message(ret)));
+ }
+
+ return ret;
+}
+
+static krb5_error_code libnet_keytab_add_entry(krb5_context context,
+ krb5_keytab keytab,
+ krb5_kvno kvno,
+ const char *princ_s,
+ krb5_enctype enctype,
+ krb5_data password)
+{
+ krb5_keyblock *keyp;
+ krb5_keytab_entry kt_entry;
+ krb5_error_code ret;
+ krb5_principal salt_princ = NULL;
+ char *salt_princ_s;
+
+ /* remove duplicates first ... */
+ ret = libnet_keytab_remove_entries(context, keytab, princ_s, kvno,
+ enctype, false);
+ if (ret) {
+ DEBUG(1, ("libnet_keytab_remove_entries failed: %s\n",
+ error_message(ret)));
+ }
+
+ ZERO_STRUCT(kt_entry);
+
+ kt_entry.vno = kvno;
+
+ ret = smb_krb5_parse_name(context, princ_s, &kt_entry.principal);
+ if (ret) {
+ DEBUG(1, ("smb_krb5_parse_name(%s) failed (%s)\n",
+ princ_s, error_message(ret)));
+ return ret;
+ }
+
+ keyp = KRB5_KT_KEY(&kt_entry);
+
+ salt_princ_s = kerberos_secrets_fetch_salt_princ();
+ if (salt_princ_s == NULL) {
+ ret = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ ret = krb5_parse_name(context, salt_princ_s, &salt_princ);
+ SAFE_FREE(salt_princ_s);
+ if (ret != 0) {
+ ret = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ ret = create_kerberos_key_from_string(context,
+ kt_entry.principal,
+ salt_princ,
+ &password,
+ keyp,
+ enctype,
+ true);
+ krb5_free_principal(context, salt_princ);
+ if (ret != 0) {
+ ret = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ ret = krb5_kt_add_entry(context, keytab, &kt_entry);
+ if (ret) {
+ DEBUG(1, ("adding entry to keytab failed (%s)\n",
+ error_message(ret)));
+ }
+
+done:
+ krb5_free_keyblock_contents(context, keyp);
+ krb5_free_principal(context, kt_entry.principal);
+ ZERO_STRUCT(kt_entry);
+ smb_krb5_kt_free_entry(context, &kt_entry);
+
+ return ret;
+}
+
+krb5_error_code libnet_keytab_add(struct libnet_keytab_context *ctx)
+{
+ krb5_error_code ret = 0;
+ uint32_t i;
+
+
+ if (ctx->clean_old_entries) {
+ DEBUG(0, ("cleaning old entries...\n"));
+ for (i=0; i < ctx->count; i++) {
+ struct libnet_keytab_entry *entry = &ctx->entries[i];
+
+ ret = libnet_keytab_remove_entries(ctx->context,
+ ctx->keytab,
+ entry->principal,
+ 0,
+ entry->enctype,
+ true);
+ if (ret) {
+ DEBUG(1,("libnet_keytab_add: Failed to remove "
+ "old entries for %s (enctype %u): %s\n",
+ entry->principal, entry->enctype,
+ error_message(ret)));
+ return ret;
+ }
+ }
+ }
+
+ for (i=0; i<ctx->count; i++) {
+
+ struct libnet_keytab_entry *entry = &ctx->entries[i];
+ krb5_data password;
+
+ ZERO_STRUCT(password);
+ password.data = (char *)entry->password.data;
+ password.length = entry->password.length;
+
+ ret = libnet_keytab_add_entry(ctx->context,
+ ctx->keytab,
+ entry->kvno,
+ entry->principal,
+ entry->enctype,
+ password);
+ if (ret) {
+ DEBUG(1,("libnet_keytab_add: "
+ "Failed to add entry to keytab file\n"));
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+struct libnet_keytab_entry *libnet_keytab_search(struct libnet_keytab_context *ctx,
+ const char *principal,
+ int kvno,
+ const krb5_enctype enctype,
+ TALLOC_CTX *mem_ctx)
+{
+ krb5_error_code ret = 0;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kt_entry;
+ struct libnet_keytab_entry *entry = NULL;
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ ret = krb5_kt_start_seq_get(ctx->context, ctx->keytab, &cursor);
+ if (ret) {
+ DEBUG(10, ("krb5_kt_start_seq_get failed: %s\n",
+ error_message(ret)));
+ return NULL;
+ }
+
+ while (krb5_kt_next_entry(ctx->context, ctx->keytab, &kt_entry, &cursor) == 0)
+ {
+ krb5_keyblock *keyp;
+ char *princ_s = NULL;
+
+ entry = NULL;
+
+ if (kt_entry.vno != kvno) {
+ goto cont;
+ }
+
+ keyp = KRB5_KT_KEY(&kt_entry);
+
+ if (KRB5_KEY_TYPE(keyp) != enctype) {
+ goto cont;
+ }
+
+ entry = talloc_zero(mem_ctx, struct libnet_keytab_entry);
+ if (!entry) {
+ DEBUG(3, ("talloc failed\n"));
+ goto fail;
+ }
+
+ ret = smb_krb5_unparse_name(entry, ctx->context, kt_entry.principal,
+ &princ_s);
+ if (ret) {
+ goto cont;
+ }
+
+ if (strcmp(principal, princ_s) != 0) {
+ goto cont;
+ }
+
+ entry->principal = talloc_strdup(entry, princ_s);
+ if (!entry->principal) {
+ DEBUG(3, ("talloc_strdup_failed\n"));
+ goto fail;
+ }
+
+ entry->name = talloc_move(entry, &princ_s);
+
+ entry->password = data_blob_talloc(entry, KRB5_KEY_DATA(keyp),
+ KRB5_KEY_LENGTH(keyp));
+ if (!entry->password.data) {
+ DEBUG(3, ("data_blob_talloc failed\n"));
+ goto fail;
+ }
+
+ DEBUG(10, ("found entry\n"));
+
+ smb_krb5_kt_free_entry(ctx->context, &kt_entry);
+ break;
+
+fail:
+ smb_krb5_kt_free_entry(ctx->context, &kt_entry);
+ TALLOC_FREE(entry);
+ break;
+
+cont:
+ smb_krb5_kt_free_entry(ctx->context, &kt_entry);
+ TALLOC_FREE(entry);
+ continue;
+ }
+
+ krb5_kt_end_seq_get(ctx->context, ctx->keytab, &cursor);
+ return entry;
+}
+
+/**
+ * Helper function to add data to the list
+ * of keytab entries. It builds the prefix from the input.
+ */
+NTSTATUS libnet_keytab_add_to_keytab_entries(TALLOC_CTX *mem_ctx,
+ struct libnet_keytab_context *ctx,
+ uint32_t kvno,
+ const char *name,
+ const char *prefix,
+ const krb5_enctype enctype,
+ DATA_BLOB blob)
+{
+ struct libnet_keytab_entry entry;
+
+ entry.kvno = kvno;
+ entry.name = talloc_strdup(mem_ctx, name);
+ entry.principal = talloc_asprintf(mem_ctx, "%s%s%s@%s",
+ prefix ? prefix : "",
+ prefix ? "/" : "",
+ name, ctx->dns_domain_name);
+ entry.enctype = enctype;
+ entry.password = blob;
+ NT_STATUS_HAVE_NO_MEMORY(entry.name);
+ NT_STATUS_HAVE_NO_MEMORY(entry.principal);
+ NT_STATUS_HAVE_NO_MEMORY(entry.password.data);
+
+ ADD_TO_ARRAY(mem_ctx, struct libnet_keytab_entry, entry,
+ &ctx->entries, &ctx->count);
+ NT_STATUS_HAVE_NO_MEMORY(ctx->entries);
+
+ return NT_STATUS_OK;
+}
+
+#endif /* HAVE_KRB5 */
diff --git a/source3/libnet/libnet_keytab.h b/source3/libnet/libnet_keytab.h
new file mode 100644
index 0000000..df6e957
--- /dev/null
+++ b/source3/libnet/libnet_keytab.h
@@ -0,0 +1,61 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * 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/>.
+ */
+
+#ifdef HAVE_KRB5
+
+struct libnet_keytab_entry {
+ const char *name;
+ const char *principal;
+ DATA_BLOB password;
+ uint32_t kvno;
+ krb5_enctype enctype;
+};
+
+struct ads_struct;
+
+struct libnet_keytab_context {
+ krb5_context context;
+ krb5_keytab keytab;
+ const char *keytab_name;
+ struct ads_struct *ads;
+ const char *dns_domain_name;
+ uint32_t count;
+ struct libnet_keytab_entry *entries;
+ bool clean_old_entries;
+};
+
+/* The following definitions come from libnet/libnet_keytab.c */
+
+krb5_error_code libnet_keytab_init(TALLOC_CTX *mem_ctx,
+ const char *keytab_name,
+ struct libnet_keytab_context **ctx);
+krb5_error_code libnet_keytab_add(struct libnet_keytab_context *ctx);
+
+struct libnet_keytab_entry *libnet_keytab_search(struct libnet_keytab_context *ctx,
+ const char *principal, int kvno,
+ const krb5_enctype enctype,
+ TALLOC_CTX *mem_ctx);
+NTSTATUS libnet_keytab_add_to_keytab_entries(TALLOC_CTX *mem_ctx,
+ struct libnet_keytab_context *ctx,
+ uint32_t kvno,
+ const char *name,
+ const char *prefix,
+ const krb5_enctype enctype,
+ DATA_BLOB blob);
+#endif /* HAVE_KRB5 */
diff --git a/source3/libnet/netapi.pc.in b/source3/libnet/netapi.pc.in
new file mode 100644
index 0000000..a699027
--- /dev/null
+++ b/source3/libnet/netapi.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Samba libnetapi
+Description: A library to control CIFS servers
+Version: @PACKAGE_VERSION@
+Libs: @LIB_RPATH@ -L${libdir} -lnetapi
+Cflags: -I${includedir}
+URL: http://www.samba.org/