diff options
Diffstat (limited to 'source3/libnet')
-rw-r--r-- | source3/libnet/libnet_dssync.c | 724 | ||||
-rw-r--r-- | source3/libnet/libnet_dssync.h | 73 | ||||
-rw-r--r-- | source3/libnet/libnet_dssync_keytab.c | 609 | ||||
-rw-r--r-- | source3/libnet/libnet_dssync_passdb.c | 1955 | ||||
-rw-r--r-- | source3/libnet/libnet_join.c | 3281 | ||||
-rw-r--r-- | source3/libnet/libnet_join.h | 40 | ||||
-rw-r--r-- | source3/libnet/libnet_join_offline.c | 441 | ||||
-rw-r--r-- | source3/libnet/libnet_join_offline.h | 26 | ||||
-rw-r--r-- | source3/libnet/libnet_keytab.c | 457 | ||||
-rw-r--r-- | source3/libnet/libnet_keytab.h | 61 | ||||
-rw-r--r-- | source3/libnet/netapi.pc.in | 11 |
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/ |