diff options
Diffstat (limited to 'source3/libnet')
-rw-r--r-- | source3/libnet/libnet.h | 29 | ||||
-rw-r--r-- | source3/libnet/libnet_dssync.c | 720 | ||||
-rw-r--r-- | source3/libnet/libnet_dssync.h | 57 | ||||
-rw-r--r-- | source3/libnet/libnet_dssync_keytab.c | 641 | ||||
-rw-r--r-- | source3/libnet/libnet_join.c | 2018 | ||||
-rw-r--r-- | source3/libnet/libnet_keytab.c | 404 | ||||
-rw-r--r-- | source3/libnet/libnet_keytab.h | 42 | ||||
-rw-r--r-- | source3/libnet/libnet_proto.h | 78 | ||||
-rw-r--r-- | source3/libnet/libnet_samsync.c | 411 | ||||
-rw-r--r-- | source3/libnet/libnet_samsync.h | 73 | ||||
-rw-r--r-- | source3/libnet/libnet_samsync_display.c | 303 | ||||
-rw-r--r-- | source3/libnet/libnet_samsync_keytab.c | 193 | ||||
-rw-r--r-- | source3/libnet/libnet_samsync_ldif.c | 1229 | ||||
-rw-r--r-- | source3/libnet/libnet_samsync_passdb.c | 789 |
14 files changed, 6987 insertions, 0 deletions
diff --git a/source3/libnet/libnet.h b/source3/libnet/libnet.h new file mode 100644 index 0000000000..570009c6f6 --- /dev/null +++ b/source3/libnet/libnet.h @@ -0,0 +1,29 @@ +/* + * Unix SMB/CIFS implementation. + * libnet Support + * Copyright (C) Guenther Deschner 2007 + * + * 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_H__ +#define __LIBNET_H__ + +#include "libnet/libnet_keytab.h" +#include "libnet/libnet_samsync.h" +#include "libnet/libnet_dssync.h" +#include "librpc/gen_ndr/libnet_join.h" +#include "libnet/libnet_proto.h" + +#endif diff --git a/source3/libnet/libnet_dssync.c b/source3/libnet/libnet_dssync.c new file mode 100644 index 0000000000..bae03effed --- /dev/null +++ b/source3/libnet/libnet_dssync.c @@ -0,0 +1,720 @@ +/* + 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.h" + +/**************************************************************** +****************************************************************/ + +static int libnet_dssync_free_context(struct dssync_context *ctx) +{ + if (!ctx) { + return 0; + } + + if (is_valid_policy_hnd(&ctx->bind_handle) && ctx->cli) { + rpccli_drsuapi_DsUnbind(ctx->cli, ctx, &ctx->bind_handle, NULL); + } + + return 0; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS libnet_dssync_init_context(TALLOC_CTX *mem_ctx, + struct dssync_context **ctx_p) +{ + struct dssync_context *ctx; + + ctx = TALLOC_ZERO_P(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 DATA_BLOB *decrypt_attr_val(TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key, + uint32_t rid, + enum drsuapi_DsAttributeId id, + DATA_BLOB *raw_data) +{ + bool rcrypt = false; + DATA_BLOB out_data; + + ZERO_STRUCT(out_data); + + switch (id) { + case DRSUAPI_ATTRIBUTE_dBCSPwd: + case DRSUAPI_ATTRIBUTE_unicodePwd: + case DRSUAPI_ATTRIBUTE_ntPwdHistory: + case DRSUAPI_ATTRIBUTE_lmPwdHistory: + rcrypt = true; + break; + case DRSUAPI_ATTRIBUTE_supplementalCredentials: + case DRSUAPI_ATTRIBUTE_priorValue: + case DRSUAPI_ATTRIBUTE_currentValue: + case DRSUAPI_ATTRIBUTE_trustAuthOutgoing: + case DRSUAPI_ATTRIBUTE_trustAuthIncoming: + case DRSUAPI_ATTRIBUTE_initialAuthOutgoing: + case DRSUAPI_ATTRIBUTE_initialAuthIncoming: + break; + default: + return raw_data; + } + + out_data = decrypt_drsuapi_blob(mem_ctx, session_key, rcrypt, + rid, raw_data); + + if (out_data.length) { + return (DATA_BLOB *)talloc_memdup(mem_ctx, &out_data, sizeof(DATA_BLOB)); + } + + return raw_data; +} + +/**************************************************************** +****************************************************************/ + +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 parse_obj_attribute(TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key, + uint32_t rid, + struct drsuapi_DsReplicaAttribute *attr) +{ + int i = 0; + + for (i=0; i<attr->value_ctr.num_values; i++) { + + DATA_BLOB *plain_data = NULL; + + plain_data = decrypt_attr_val(mem_ctx, + session_key, + rid, + attr->attid, + attr->value_ctr.values[i].blob); + + attr->value_ctr.values[i].blob = plain_data; + } +} + +/**************************************************************** +****************************************************************/ + +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; + } + + parse_obj_attribute(mem_ctx, + session_key, + rid, + 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; + + 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 = rpccli_drsuapi_DsBind(ctx->cli, 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 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; + } + 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; + int32_t level = 1; + union drsuapi_DsNameRequest req; + int32_t level_out; + struct drsuapi_DsNameString names[1]; + union drsuapi_DsNameCtr ctr; + + 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_UKNOWN; + req.req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779; + + status = rpccli_drsuapi_DsCrackNames(ctx->cli, 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_werror_msg(werr)); + return status; + } + + if (!W_ERROR_IS_OK(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, + int32_t *plevel, + union drsuapi_DsGetNCChangesRequest *preq) +{ + NTSTATUS status; + uint32_t count; + int32_t level; + union drsuapi_DsGetNCChangesRequest req; + struct dom_sid null_sid; + enum drsuapi_DsExtendedOperation extended_op; + struct drsuapi_DsReplicaObjectIdentifier *nc = NULL; + struct drsuapi_DsReplicaCursorCtrEx *cursors = NULL; + + uint32_t replica_flags = DRSUAPI_DS_REPLICA_NEIGHBOUR_WRITEABLE | + DRSUAPI_DS_REPLICA_NEIGHBOUR_SYNC_ON_STARTUP | + DRSUAPI_DS_REPLICA_NEIGHBOUR_DO_SCHEDULED_SYNCS | + DRSUAPI_DS_REPLICA_NEIGHBOUR_RETURN_OBJECT_PARENTS | + DRSUAPI_DS_REPLICA_NEIGHBOUR_NEVER_SYNCED; + + ZERO_STRUCT(null_sid); + ZERO_STRUCT(req); + + if (ctx->remote_info28.supported_extensions + & DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8) + { + level = 8; + } else { + level = 5; + } + + nc = TALLOC_ZERO_P(mem_ctx, struct drsuapi_DsReplicaObjectIdentifier); + if (!nc) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + nc->dn = dn; + nc->guid = GUID_zero(); + nc->sid = null_sid; + + if (!ctx->single_object_replication && + !ctx->force_full_replication && utdv) + { + cursors = TALLOC_ZERO_P(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, + int32_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; + int32_t level_out = 0; + int32_t out_level = 0; + int y; + bool last_query; + + if (!ctx->single_object_replication) { + new_utdv = TALLOC_ZERO_P(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; + + 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 = rpccli_drsuapi_DsGetNCChanges(ctx->cli, 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_werror_msg(werr)); + goto out; + } + + if (!W_ERROR_IS_OK(werr)) { + status = werror_to_ntstatus(werr); + goto out; + } + + if (level_out == 1) { + out_level = 1; + ctr1 = &ctr.ctr1; + } else if (level_out == 2) { + out_level = 1; + ctr1 = ctr.ctr2.ctr.mszip1.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) { + out_level = 6; + ctr6 = ctr.ctr7.ctr.mszip6.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; + + 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; + } + } + } + + *pnew_utdv = new_utdv; + +out: + return status; +} + +static NTSTATUS libnet_dssync_process(TALLOC_CTX *mem_ctx, + struct dssync_context *ctx) +{ + NTSTATUS status; + + int32_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; + + 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; + } + + 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)) { + ctx->error_message = talloc_asprintf(ctx, + "Failed to call DsGetNCCHanges: %s", + nt_errstr(status)); + goto out; + } + } + + 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 0000000000..a5a00742c5 --- /dev/null +++ b/source3/libnet/libnet_dssync.h @@ -0,0 +1,57 @@ +/* + * 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/>. + */ + +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 (*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; diff --git a/source3/libnet/libnet_dssync_keytab.c b/source3/libnet/libnet_dssync_keytab.c new file mode 100644 index 0000000000..6ba2c3aa41 --- /dev/null +++ b/source3/libnet/libnet_dssync_keytab.c @@ -0,0 +1,641 @@ +/* + 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 "libnet/libnet.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" + +#if defined(HAVE_ADS) && defined(ENCTYPE_ARCFOUR_HMAC) + +/** + * Internal helper function to add data to the list + * of keytab entries. It builds the prefix from the input. + */ +static NTSTATUS 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; +} + +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 = 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) + { + 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_P(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_ATTRIBUTE_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_ATTRIBUTE_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_ATTRIBUTE_ntPwdHistory: + pwd_history_len = blob->length / 16; + pwd_history = blob->data; + break; + case DRSUAPI_ATTRIBUTE_userPrincipalName: + pull_string_talloc(mem_ctx, NULL, 0, &upn, + blob->data, blob->length, + STR_UNICODE); + break; + case DRSUAPI_ATTRIBUTE_sAMAccountName: + pull_string_talloc(mem_ctx, NULL, 0, &name, + blob->data, blob->length, + STR_UNICODE); + break; + case DRSUAPI_ATTRIBUTE_sAMAccountType: + sam_type = IVAL(blob->data, 0); + break; + case DRSUAPI_ATTRIBUTE_userAccountControl: + uacc = IVAL(blob->data, 0); + break; + case DRSUAPI_ATTRIBUTE_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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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) && defined(ENCTYPE_ARCFOUR_HMAC) */ + +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_join.c b/source3/libnet/libnet_join.c new file mode 100644 index 0000000000..a39dee676f --- /dev/null +++ b/source3/libnet/libnet_join.c @@ -0,0 +1,2018 @@ +/* + * 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 "libnet/libnet.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) + +#define W_ERROR_NOT_OK_GOTO_DONE(x) do { \ + if (!W_ERROR_IS_OK(x)) {\ + goto done;\ + }\ +} while (0) + +/**************************************************************** +****************************************************************/ + +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, ...) +{ + 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 WITH_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, + ADS_STRUCT **ads) +{ + ADS_STATUS status; + ADS_STRUCT *my_ads = NULL; + + my_ads = ads_init(dns_domain_name, + netbios_domain_name, + dc_name); + if (!my_ads) { + return ADS_ERROR_LDAP(LDAP_NO_MEMORY); + } + + if (user_name) { + SAFE_FREE(my_ads->auth.user_name); + my_ads->auth.user_name = SMB_STRDUP(user_name); + } + + if (password) { + SAFE_FREE(my_ads->auth.password); + my_ads->auth.password = SMB_STRDUP(password); + } + + status = ads_connect_user_creds(my_ads); + if (!ADS_ERR_OK(status)) { + ads_destroy(&my_ads); + return status; + } + + *ads = my_ads; + return ADS_SUCCESS; +} + +/**************************************************************** +****************************************************************/ + +static ADS_STATUS libnet_join_connect_ads(TALLOC_CTX *mem_ctx, + struct libnet_JoinCtx *r) +{ + ADS_STATUS status; + + status = libnet_connect_ads(r->out.dns_domain_name, + r->out.netbios_domain_name, + r->in.dc_name, + r->in.admin_account, + r->in.admin_password, + &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_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, + &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 */ + + status = ads_create_machine_acct(r->in.ads, + r->in.machine_name, + r->in.account_ou); + + if (ADS_ERR_OK(status)) { + DEBUG(1,("machine account creation 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)) { + DEBUG(1,("machine account creation failed\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) { + return libnet_unjoin_connect_ads(mem_ctx, r); + } + + 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; + + 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, 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; + } + + done: + ads_msgfree(r->in.ads, res); + ads_memfree(r->in.ads, dn); + + return status; +} + +/**************************************************************** + Set a machines dNSHostName and servicePrincipalName attributes +****************************************************************/ + +static ADS_STATUS libnet_join_set_machine_spn(TALLOC_CTX *mem_ctx, + struct libnet_JoinCtx *r) +{ + ADS_STATUS status; + ADS_MODLIST mods; + fstring my_fqdn; + const char *spn_array[3] = {NULL, NULL, NULL}; + char *spn = NULL; + + /* Find our DN */ + + status = libnet_join_find_machine_acct(mem_ctx, r); + if (!ADS_ERR_OK(status)) { + return status; + } + + /* Windows only creates HOST/shortname & HOST/fqdn. */ + + spn = talloc_asprintf(mem_ctx, "HOST/%s", r->in.machine_name); + if (!spn) { + return ADS_ERROR_LDAP(LDAP_NO_MEMORY); + } + strupper_m(spn); + spn_array[0] = spn; + + if (name_to_fqdn(my_fqdn, r->in.machine_name) && + !strequal(my_fqdn, r->in.machine_name)) { + + strlower_m(my_fqdn); + spn = talloc_asprintf(mem_ctx, "HOST/%s", my_fqdn); + if (!spn) { + return ADS_ERROR_LDAP(LDAP_NO_MEMORY); + } + spn_array[1] = spn; + } + + 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, "dNSHostName", my_fqdn); + if (!ADS_ERR_OK(status)) { + return ADS_ERROR_LDAP(LDAP_NO_MEMORY); + } + + status = ads_mod_strlist(mem_ctx, &mods, "servicePrincipalName", + spn_array); + 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_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) { + r->in.upn = talloc_asprintf(mem_ctx, + "host/%s@%s", + r->in.machine_name, + r->out.dns_domain_name); + 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); + } + + os_sp = talloc_asprintf(mem_ctx, "Samba %s", SAMBA_VERSION_STRING); + if (!os_sp) { + 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 bool libnet_join_create_keytab(TALLOC_CTX *mem_ctx, + struct libnet_JoinCtx *r) +{ + if (!lp_use_kerberos_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; + } + } + } + + return kerberos_secrets_store_des_salt(salt); +} + +/**************************************************************** +****************************************************************/ + +static ADS_STATUS libnet_join_post_processing_ads(TALLOC_CTX *mem_ctx, + struct libnet_JoinCtx *r) +{ + ADS_STATUS status; + + if (!r->in.ads) { + status = libnet_join_connect_ads(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", + 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; + } + + if (!libnet_join_derive_salting_principal(mem_ctx, r)) { + return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + } + + 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 /* WITH_ADS */ + +/**************************************************************** + Store the machine password and domain SID +****************************************************************/ + +static bool libnet_join_joindomain_store_secrets(TALLOC_CTX *mem_ctx, + struct libnet_JoinCtx *r) +{ + if (!secrets_store_domain_sid(r->out.netbios_domain_name, + r->out.domain_sid)) + { + DEBUG(1,("Failed to save domain sid\n")); + return false; + } + + if (!secrets_store_machine_password(r->in.machine_password, + r->out.netbios_domain_name, + r->in.secure_channel_type)) + { + DEBUG(1,("Failed to save machine password\n")); + return false; + } + + return true; +} + +/**************************************************************** + Connect dc's IPC$ share +****************************************************************/ + +static NTSTATUS libnet_join_connect_dc_ipc(const char *dc, + const char *user, + const char *pass, + bool use_kerberos, + struct cli_state **cli) +{ + int flags = 0; + + if (use_kerberos) { + flags |= CLI_FULL_CONNECTION_USE_KERBEROS; + } + + if (use_kerberos && pass) { + flags |= CLI_FULL_CONNECTION_FALLBACK_AFTER_KERBEROS; + } + + return cli_full_connection(cli, NULL, + dc, + NULL, 0, + "IPC$", "IPC", + user, + NULL, + pass, + flags, + Undefined, NULL); +} + +/**************************************************************** + 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; + POLICY_HND lsa_pol; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + union lsa_PolicyInformation *info = NULL; + + status = libnet_join_connect_dc_ipc(r->in.dc_name, + r->in.admin_account, + r->in.admin_password, + r->in.use_kerberos, + cli); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = cli_rpc_pipe_open_noauth(*cli, &ndr_table_lsarpc.syntax_id, + &pipe_hnd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Error connecting to LSA pipe. Error was %s\n", + nt_errstr(status))); + goto done; + } + + status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true, + SEC_RIGHTS_MAXIMUM_ALLOWED, &lsa_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpccli_lsa_QueryInfoPolicy2(pipe_hnd, mem_ctx, + &lsa_pol, + LSA_POLICY_INFO_DNS, + &info); + if (NT_STATUS_IS_OK(status)) { + 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_sid = sid_dup_talloc(mem_ctx, info->dns.sid); + NT_STATUS_HAVE_NO_MEMORY(r->out.domain_sid); + } + + if (!NT_STATUS_IS_OK(status)) { + status = rpccli_lsa_QueryInfoPolicy(pipe_hnd, mem_ctx, + &lsa_pol, + LSA_POLICY_INFO_ACCOUNT_DOMAIN, + &info); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + r->out.netbios_domain_name = info->account_domain.name.string; + r->out.domain_sid = sid_dup_talloc(mem_ctx, info->account_domain.sid); + NT_STATUS_HAVE_NO_MEMORY(r->out.domain_sid); + } + + rpccli_lsa_Close(pipe_hnd, mem_ctx, &lsa_pol); + TALLOC_FREE(pipe_hnd); + + done: + return status; +} + +/**************************************************************** + 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; + POLICY_HND sam_pol, domain_pol, user_pol; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + char *acct_name; + struct lsa_String lsa_acct_name; + uint32_t user_rid; + uint32_t acct_flags = ACB_WSTRUST; + uchar md4_trust_password[16]; + struct samr_Ids user_rids; + struct samr_Ids name_types; + union samr_UserInfo user_info; + + struct samr_CryptPassword crypt_pwd; + struct samr_CryptPasswordEx crypt_pwd_ex; + + ZERO_STRUCT(sam_pol); + ZERO_STRUCT(domain_pol); + ZERO_STRUCT(user_pol); + + if (!r->in.machine_password) { + r->in.machine_password = talloc_strdup(mem_ctx, generate_random_str(DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH)); + NT_STATUS_HAVE_NO_MEMORY(r->in.machine_password); + } + + /* Open the domain */ + + status = cli_rpc_pipe_open_noauth(cli, &ndr_table_samr.syntax_id, + &pipe_hnd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Error connecting to SAM pipe. Error was %s\n", + nt_errstr(status))); + goto done; + } + + status = rpccli_samr_Connect2(pipe_hnd, mem_ctx, + pipe_hnd->desthost, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &sam_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpccli_samr_OpenDomain(pipe_hnd, mem_ctx, + &sam_pol, + SEC_RIGHTS_MAXIMUM_ALLOWED, + r->out.domain_sid, + &domain_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* Create domain user */ + + acct_name = talloc_asprintf(mem_ctx, "%s$", r->in.machine_name); + strlower_m(acct_name); + + 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; + + /* Don't try to set any acct_flags flags other than ACB_WSTRUST */ + + DEBUG(10,("Creating account with desired access mask: %d\n", + access_desired)); + + status = rpccli_samr_CreateUser2(pipe_hnd, mem_ctx, + &domain_pol, + &lsa_acct_name, + ACB_WSTRUST, + access_desired, + &user_pol, + &access_granted, + &user_rid); + 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)) { + rpccli_samr_Close(pipe_hnd, mem_ctx, &user_pol); + } + } + + status = rpccli_samr_LookupNames(pipe_hnd, mem_ctx, + &domain_pol, + 1, + &lsa_acct_name, + &user_rids, + &name_types); + if (!NT_STATUS_IS_OK(status)) { + 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 = rpccli_samr_OpenUser(pipe_hnd, mem_ctx, + &domain_pol, + SEC_RIGHTS_MAXIMUM_ALLOWED, + user_rid, + &user_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* Create a random machine account password and generate the hash */ + + E_md4hash(r->in.machine_password, md4_trust_password); + + init_samr_CryptPasswordEx(r->in.machine_password, + &cli->user_session_key, + &crypt_pwd_ex); + + /* Fill in the additional account flags now */ + + acct_flags |= ACB_PWNOEXP; + if (r->out.domain_is_ad) { +#if !defined(ENCTYPE_ARCFOUR_HMAC) + acct_flags |= ACB_USE_DES_KEY_ONLY; +#endif + ;; + } + + /* Set password and account flags on machine account */ + + ZERO_STRUCT(user_info.info25); + + user_info.info25.info.fields_present = ACCT_NT_PWD_SET | + ACCT_LM_PWD_SET | + SAMR_FIELD_ACCT_FLAGS; + + user_info.info25.info.acct_flags = acct_flags; + memcpy(&user_info.info25.password.data, crypt_pwd_ex.data, + sizeof(crypt_pwd_ex.data)); + + status = rpccli_samr_SetUserInfo(pipe_hnd, mem_ctx, + &user_pol, + 25, + &user_info); + + if (NT_STATUS_EQUAL(status, NT_STATUS(DCERPC_FAULT_INVALID_TAG))) { + + /* retry with level 24 */ + + init_samr_CryptPassword(r->in.machine_password, + &cli->user_session_key, + &crypt_pwd); + + init_samr_user_info24(&user_info.info24, crypt_pwd.data, 24); + + status = rpccli_samr_SetUserInfo2(pipe_hnd, mem_ctx, + &user_pol, + 24, + &user_info); + } + + if (!NT_STATUS_IS_OK(status)) { + + rpccli_samr_DeleteUser(pipe_hnd, mem_ctx, + &user_pol); + + 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; + } + + if (is_valid_policy_hnd(&sam_pol)) { + rpccli_samr_Close(pipe_hnd, mem_ctx, &sam_pol); + } + if (is_valid_policy_hnd(&domain_pol)) { + rpccli_samr_Close(pipe_hnd, mem_ctx, &domain_pol); + } + if (is_valid_policy_hnd(&user_pol)) { + rpccli_samr_Close(pipe_hnd, mem_ctx, &user_pol); + } + TALLOC_FREE(pipe_hnd); + + return status; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS libnet_join_ok(const char *netbios_domain_name, + const char *machine_name, + const char *dc_name) +{ + uint32_t neg_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; + struct cli_state *cli = NULL; + struct rpc_pipe_client *pipe_hnd = NULL; + struct rpc_pipe_client *netlogon_pipe = NULL; + NTSTATUS status; + char *machine_password = NULL; + char *machine_account = NULL; + + if (!dc_name) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!secrets_init()) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + machine_password = secrets_fetch_machine_password(netbios_domain_name, + NULL, NULL); + if (!machine_password) { + return NT_STATUS_NO_TRUST_LSA_SECRET; + } + + asprintf(&machine_account, "%s$", machine_name); + if (!machine_account) { + SAFE_FREE(machine_password); + return NT_STATUS_NO_MEMORY; + } + + status = cli_full_connection(&cli, NULL, + dc_name, + NULL, 0, + "IPC$", "IPC", + machine_account, + NULL, + machine_password, + 0, + Undefined, NULL); + free(machine_account); + free(machine_password); + + if (!NT_STATUS_IS_OK(status)) { + status = cli_full_connection(&cli, NULL, + dc_name, + NULL, 0, + "IPC$", "IPC", + "", + NULL, + "", + 0, + Undefined, NULL); + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = get_schannel_session_key(cli, netbios_domain_name, + &neg_flags, &netlogon_pipe); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_NETWORK_RESPONSE)) { + cli_shutdown(cli); + return NT_STATUS_OK; + } + + DEBUG(0,("libnet_join_ok: failed to get schannel session " + "key from server %s for domain %s. Error was %s\n", + cli->desthost, netbios_domain_name, nt_errstr(status))); + cli_shutdown(cli); + return status; + } + + if (!lp_client_schannel()) { + cli_shutdown(cli); + return NT_STATUS_OK; + } + + status = cli_rpc_pipe_open_schannel_with_key( + cli, &ndr_table_netlogon.syntax_id, PIPE_AUTH_LEVEL_PRIVACY, + netbios_domain_name, netlogon_pipe->dc, &pipe_hnd); + + cli_shutdown(cli); + + 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", + cli->desthost, netbios_domain_name, nt_errstr(status))); + return status; + } + + 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->out.netbios_domain_name, + r->in.machine_name, + r->in.dc_name); + 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_SETUP_NOT_JOINED; + } + + return WERR_OK; +} + +/**************************************************************** +****************************************************************/ + +static bool libnet_join_unjoindomain_remove_secrets(TALLOC_CTX *mem_ctx, + struct libnet_UnjoinCtx *r) +{ + if (!secrets_delete_machine_password_ex(lp_workgroup())) { + return false; + } + + if (!secrets_delete_domain_sid(lp_workgroup())) { + return false; + } + + return true; +} + +/**************************************************************** +****************************************************************/ + +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; + POLICY_HND sam_pol, domain_pol, user_pol; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + 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; + + 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_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.syntax_id, + &pipe_hnd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Error connecting to SAM pipe. Error was %s\n", + nt_errstr(status))); + goto done; + } + + status = rpccli_samr_Connect2(pipe_hnd, mem_ctx, + pipe_hnd->desthost, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &sam_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpccli_samr_OpenDomain(pipe_hnd, mem_ctx, + &sam_pol, + SEC_RIGHTS_MAXIMUM_ALLOWED, + r->in.domain_sid, + &domain_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* Create domain user */ + + acct_name = talloc_asprintf(mem_ctx, "%s$", r->in.machine_name); + strlower_m(acct_name); + + init_lsa_String(&lsa_acct_name, acct_name); + + status = rpccli_samr_LookupNames(pipe_hnd, mem_ctx, + &domain_pol, + 1, + &lsa_acct_name, + &user_rids, + &name_types); + + if (!NT_STATUS_IS_OK(status)) { + 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 = rpccli_samr_OpenUser(pipe_hnd, mem_ctx, + &domain_pol, + SEC_RIGHTS_MAXIMUM_ALLOWED, + user_rid, + &user_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* Get user info */ + + status = rpccli_samr_QueryUserInfo(pipe_hnd, mem_ctx, + &user_pol, + 16, + &info); + if (!NT_STATUS_IS_OK(status)) { + rpccli_samr_Close(pipe_hnd, mem_ctx, &user_pol); + goto done; + } + + /* now disable and setuser info */ + + info->info16.acct_flags |= ACB_DISABLED; + + status = rpccli_samr_SetUserInfo(pipe_hnd, mem_ctx, + &user_pol, + 16, + info); + + rpccli_samr_Close(pipe_hnd, mem_ctx, &user_pol); + +done: + if (pipe_hnd) { + if (is_valid_policy_hnd(&domain_pol)) { + rpccli_samr_Close(pipe_hnd, mem_ctx, &domain_pol); + } + if (is_valid_policy_hnd(&sam_pol)) { + rpccli_samr_Close(pipe_hnd, mem_ctx, &sam_pol); + } + TALLOC_FREE(pipe_hnd); + } + + if (cli) { + cli_shutdown(cli); + } + + return status; +} + +/**************************************************************** +****************************************************************/ + +static WERROR do_join_modify_vals_config(struct libnet_JoinCtx *r) +{ + WERROR werr; + struct smbconf_ctx *ctx; + + werr = smbconf_init_reg(r, &ctx, NULL); + if (!W_ERROR_IS_OK(werr)) { + goto done; + } + + if (!(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE)) { + + werr = smbconf_set_global_parameter(ctx, "security", "user"); + W_ERROR_NOT_OK_GOTO_DONE(werr); + + werr = smbconf_set_global_parameter(ctx, "workgroup", + r->in.domain_name); + + smbconf_delete_global_parameter(ctx, "realm"); + goto done; + } + + werr = smbconf_set_global_parameter(ctx, "security", "domain"); + W_ERROR_NOT_OK_GOTO_DONE(werr); + + werr = smbconf_set_global_parameter(ctx, "workgroup", + r->out.netbios_domain_name); + W_ERROR_NOT_OK_GOTO_DONE(werr); + + if (r->out.domain_is_ad) { + werr = smbconf_set_global_parameter(ctx, "security", "ads"); + W_ERROR_NOT_OK_GOTO_DONE(werr); + + werr = smbconf_set_global_parameter(ctx, "realm", + r->out.dns_domain_name); + W_ERROR_NOT_OK_GOTO_DONE(werr); + } + + done: + smbconf_shutdown(ctx); + return werr; +} + +/**************************************************************** +****************************************************************/ + +static WERROR do_unjoin_modify_vals_config(struct libnet_UnjoinCtx *r) +{ + WERROR werr = WERR_OK; + struct smbconf_ctx *ctx; + + werr = smbconf_init_reg(r, &ctx, NULL); + if (!W_ERROR_IS_OK(werr)) { + goto done; + } + + if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) { + + werr = smbconf_set_global_parameter(ctx, "security", "user"); + W_ERROR_NOT_OK_GOTO_DONE(werr); + + werr = smbconf_delete_global_parameter(ctx, "workgroup"); + W_ERROR_NOT_OK_GOTO_DONE(werr); + + 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(get_dyn_CONFIGFILE(),true,false,false,true); + + 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(get_dyn_CONFIGFILE(),true,false,false,true); + + 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_PARAM; + } + + 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_PARAM; + } + + if (IS_DC) { + return WERR_SETUP_DOMAIN_CONTROLLER; + } + + 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.")); + } 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.")); + } 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))); + } +} + +/**************************************************************** +****************************************************************/ + +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; + } + + werr = do_JoinConfig(r); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + if (!(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE)) { + return WERR_OK; + } + + saf_store(r->in.domain_name, r->in.dc_name); + +#ifdef WITH_ADS + if (r->out.domain_is_ad) { + ADS_STATUS ads_status; + + ads_status = libnet_join_post_processing_ads(mem_ctx, r); + if (!ADS_ERR_OK(ads_status)) { + return WERR_GENERAL_FAILURE; + } + } +#endif /* WITH_ADS */ + + libnet_join_add_dom_rids_to_builtins(r->out.domain_sid); + + return WERR_OK; +} + +/**************************************************************** +****************************************************************/ + +static int libnet_destroy_JoinCtx(struct libnet_JoinCtx *r) +{ + const char *krb5_cc_env = NULL; + + if (r->in.ads) { + ads_destroy(&r->in.ads); + } + + krb5_cc_env = getenv(KRB5_ENV_CCNAME); + if (krb5_cc_env && StrCaseCmp(krb5_cc_env, "MEMORY:libnetjoin")) { + unsetenv(KRB5_ENV_CCNAME); + } + + return 0; +} + +/**************************************************************** +****************************************************************/ + +static int libnet_destroy_UnjoinCtx(struct libnet_UnjoinCtx *r) +{ + const char *krb5_cc_env = NULL; + + if (r->in.ads) { + ads_destroy(&r->in.ads); + } + + krb5_cc_env = getenv(KRB5_ENV_CCNAME); + if (krb5_cc_env && StrCaseCmp(krb5_cc_env, "MEMORY:libnetjoin")) { + unsetenv(KRB5_ENV_CCNAME); + } + + return 0; +} + +/**************************************************************** +****************************************************************/ + +WERROR libnet_init_JoinCtx(TALLOC_CTX *mem_ctx, + struct libnet_JoinCtx **r) +{ + struct libnet_JoinCtx *ctx; + const char *krb5_cc_env = NULL; + + ctx = talloc_zero(mem_ctx, struct libnet_JoinCtx); + if (!ctx) { + return WERR_NOMEM; + } + + talloc_set_destructor(ctx, libnet_destroy_JoinCtx); + + ctx->in.machine_name = talloc_strdup(mem_ctx, global_myname()); + W_ERROR_HAVE_NO_MEMORY(ctx->in.machine_name); + + krb5_cc_env = getenv(KRB5_ENV_CCNAME); + if (!krb5_cc_env || (strlen(krb5_cc_env) == 0)) { + krb5_cc_env = talloc_strdup(mem_ctx, "MEMORY:libnetjoin"); + W_ERROR_HAVE_NO_MEMORY(krb5_cc_env); + setenv(KRB5_ENV_CCNAME, krb5_cc_env, 1); + } + + ctx->in.secure_channel_type = SEC_CHAN_WKSTA; + + *r = ctx; + + return WERR_OK; +} + +/**************************************************************** +****************************************************************/ + +WERROR libnet_init_UnjoinCtx(TALLOC_CTX *mem_ctx, + struct libnet_UnjoinCtx **r) +{ + struct libnet_UnjoinCtx *ctx; + const char *krb5_cc_env = NULL; + + ctx = talloc_zero(mem_ctx, struct libnet_UnjoinCtx); + if (!ctx) { + return WERR_NOMEM; + } + + talloc_set_destructor(ctx, libnet_destroy_UnjoinCtx); + + ctx->in.machine_name = talloc_strdup(mem_ctx, global_myname()); + W_ERROR_HAVE_NO_MEMORY(ctx->in.machine_name); + + krb5_cc_env = getenv(KRB5_ENV_CCNAME); + if (!krb5_cc_env || (strlen(krb5_cc_env) == 0)) { + krb5_cc_env = talloc_strdup(mem_ctx, "MEMORY:libnetjoin"); + W_ERROR_HAVE_NO_MEMORY(krb5_cc_env); + setenv(KRB5_ENV_CCNAME, krb5_cc_env, 1); + } + + *r = ctx; + + return WERR_OK; +} + +/**************************************************************** +****************************************************************/ + +static WERROR libnet_join_check_config(TALLOC_CTX *mem_ctx, + struct libnet_JoinCtx *r) +{ + /* check if configuration is already set correctly */ + + switch (r->out.domain_is_ad) { + case false: + if ((strequal(lp_workgroup(), + r->out.netbios_domain_name)) && + (lp_security() == SEC_DOMAIN)) { + /* nothing to be done */ + return WERR_OK; + } + break; + case true: + if ((strequal(lp_workgroup(), + r->out.netbios_domain_name)) && + (strequal(lp_realm(), + r->out.dns_domain_name)) && + ((lp_security() == SEC_ADS) || + (lp_security() == SEC_DOMAIN))) { + /* nothing to be done */ + return WERR_OK; + } + break; + } + + /* check if we are supposed to manipulate configuration */ + + if (!r->in.modify_config) { + libnet_join_set_error_string(mem_ctx, r, + "Invalid configuration and configuration modification " + "was not requested"); + 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 WITH_ADS + ADS_STATUS ads_status; +#endif /* WITH_ADS */ + + 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_join_set_error_string(mem_ctx, r, + "failed to find DC for domain %s", + r->in.domain_name, + get_friendly_nt_error_msg(status)); + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; + } + + dc = strip_hostname(info->dc_unc); + r->in.dc_name = talloc_strdup(mem_ctx, dc); + W_ERROR_HAVE_NO_MEMORY(r->in.dc_name); + } + + 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)) { + goto done; + } + +#ifdef WITH_ADS + if (r->out.domain_is_ad && r->in.account_ou) { + + ads_status = libnet_join_connect_ads(mem_ctx, r); + if (!ADS_ERR_OK(ads_status)) { + return WERR_DEFAULT_JOIN_REQUIRED; + } + + ads_status = libnet_join_precreate_machine_acct(mem_ctx, r); + if (!ADS_ERR_OK(ads_status)) { + 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_DEFAULT_JOIN_REQUIRED; + } + + r->in.join_flags &= ~WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE; + } +#endif /* WITH_ADS */ + + 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_SETUP_ALREADY_JOINED; + } + werr = ntstatus_to_werror(status); + goto done; + } + + if (!libnet_join_joindomain_store_secrets(mem_ctx, r)) { + werr = WERR_SETUP_NOT_JOINED; + goto done; + } + + werr = WERR_OK; + + done: + if (cli) { + cli_shutdown(cli); + } + + return werr; +} + +/**************************************************************** +****************************************************************/ + +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.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); + } + + 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) { + 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.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) { + 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_SETUP_NOT_JOINED; + } + r->in.domain_sid = sid_dup_talloc(mem_ctx, &sid); + W_ERROR_HAVE_NO_MEMORY(r->in.domain_sid); + } + + 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", + r->in.domain_name, + get_friendly_nt_error_msg(status)); + return WERR_DOMAIN_CONTROLLER_NOT_FOUND; + } + + dc = strip_hostname(info->dc_unc); + r->in.dc_name = talloc_strdup(mem_ctx, dc); + W_ERROR_HAVE_NO_MEMORY(r->in.dc_name); + } + + 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_SETUP_NOT_JOINED; + } + return ntstatus_to_werror(status); + } + + r->out.disabled_machine_account = true; + +#ifdef WITH_ADS + if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE) { + ADS_STATUS ads_status; + libnet_unjoin_connect_ads(mem_ctx, r); + 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; + /* dirty hack */ + r->out.dns_domain_name = talloc_strdup(mem_ctx, + r->in.ads->server.realm); + W_ERROR_HAVE_NO_MEMORY(r->out.dns_domain_name); + } + } +#endif /* WITH_ADS */ + + 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_PARAM; + } + + 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_PARAM; + } + + if (IS_DC) { + return WERR_SETUP_DOMAIN_CONTROLLER; + } + + 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_keytab.c b/source3/libnet/libnet_keytab.c new file mode 100644 index 0000000000..46c17b219c --- /dev/null +++ b/source3/libnet/libnet_keytab.c @@ -0,0 +1,404 @@ +/* + 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 "libnet/libnet.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); + } + + if (ctx->ads) { + ads_destroy(&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_P(mem_ctx, struct libnet_keytab_context); + if (!r) { + return ENOMEM; + } + + talloc_set_destructor(r, keytab_close); + + initialize_krb5_error_table(); + ret = krb5_init_context(&context); + if (ret) { + DEBUG(1,("keytab_init: could not krb5_init_context: %s\n", + error_message(ret))); + return ret; + } + + ret = smb_krb5_open_keytab(context, keytab_name, true, &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_keytab_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(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); + SAFE_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; + + /* 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); + + if (create_kerberos_key_from_string(context, kt_entry.principal, + &password, keyp, enctype, true)) + { + 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", + 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; + + if (kt_entry.vno != kvno) { + goto cont; + } + + keyp = KRB5_KT_KEY(&kt_entry); + + if (KRB5_KEY_TYPE(keyp) != enctype) { + goto cont; + } + + ret = smb_krb5_unparse_name(ctx->context, kt_entry.principal, + &princ_s); + if (ret) { + goto cont; + } + + if (strcmp(principal, princ_s) != 0) { + goto cont; + } + + entry = talloc_zero(mem_ctx, struct libnet_keytab_entry); + if (!entry) { + DEBUG(3, ("talloc failed\n")); + goto fail; + } + + entry->name = talloc_strdup(entry, princ_s); + if (!entry->name) { + DEBUG(3, ("talloc_strdup_failed\n")); + goto fail; + } + + entry->principal = talloc_strdup(entry, princ_s); + if (!entry->principal) { + DEBUG(3, ("talloc_strdup_failed\n")); + goto fail; + } + + 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); + SAFE_FREE(princ_s); + break; + +fail: + smb_krb5_kt_free_entry(ctx->context, &kt_entry); + SAFE_FREE(princ_s); + TALLOC_FREE(entry); + break; + +cont: + smb_krb5_kt_free_entry(ctx->context, &kt_entry); + SAFE_FREE(princ_s); + continue; + } + + krb5_kt_end_seq_get(ctx->context, ctx->keytab, &cursor); + return entry; +} + +#endif /* HAVE_KRB5 */ diff --git a/source3/libnet/libnet_keytab.h b/source3/libnet/libnet_keytab.h new file mode 100644 index 0000000000..4d311a48e0 --- /dev/null +++ b/source3/libnet/libnet_keytab.h @@ -0,0 +1,42 @@ +/* + * 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 libnet_keytab_context { + krb5_context context; + krb5_keytab keytab; + const char *keytab_name; + ADS_STRUCT *ads; + const char *dns_domain_name; + uint8_t zero_buf[16]; + uint32_t count; + struct libnet_keytab_entry *entries; + bool clean_old_entries; +}; + +#endif /* HAVE_KRB5 */ diff --git a/source3/libnet/libnet_proto.h b/source3/libnet/libnet_proto.h new file mode 100644 index 0000000000..43046a44c0 --- /dev/null +++ b/source3/libnet/libnet_proto.h @@ -0,0 +1,78 @@ +/* + * Unix SMB/CIFS implementation. + * collected prototypes header + * + * frozen from "make proto" in May 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/>. + */ + +#ifndef _LIBNET_PROTO_H_ +#define _LIBNET_PROTO_H_ + + +/* The following definitions come from libnet/libnet_join.c */ + +NTSTATUS libnet_join_ok(const char *netbios_domain_name, + const char *machine_name, + const char *dc_name); +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); + +/* The following definitions come from librpc/gen_ndr/ndr_libnet_join.c */ + +_PUBLIC_ void ndr_print_libnet_JoinCtx(struct ndr_print *ndr, const char *name, int flags, const struct libnet_JoinCtx *r); +_PUBLIC_ void ndr_print_libnet_UnjoinCtx(struct ndr_print *ndr, const char *name, int flags, const struct libnet_UnjoinCtx *r); + +/* The following definitions come from libnet/libnet_keytab.c */ + +#ifdef HAVE_KRB5 +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 const krb5_enctype enctype, + TALLOC_CTX *mem_ctx); +#endif + +/* The following definitions come from libnet/libnet_samsync.c */ + +NTSTATUS libnet_samsync_init_context(TALLOC_CTX *mem_ctx, + const struct dom_sid *domain_sid, + struct samsync_context **ctx_p); +NTSTATUS libnet_samsync(enum netr_SamDatabaseID database_id, + struct samsync_context *ctx); +NTSTATUS pull_netr_AcctLockStr(TALLOC_CTX *mem_ctx, + struct lsa_BinaryString *r, + struct netr_AcctLockStr **str_p); + +/* 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); + +#endif /* _LIBNET_PROTO_H_ */ diff --git a/source3/libnet/libnet_samsync.c b/source3/libnet/libnet_samsync.c new file mode 100644 index 0000000000..daf27ffb51 --- /dev/null +++ b/source3/libnet/libnet_samsync.c @@ -0,0 +1,411 @@ +/* + Unix SMB/CIFS implementation. + + Extract the user/system database from a remote SamSync server + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + 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 "libnet/libnet.h" + +/** + * Decrypt and extract the user's passwords. + * + * The writes decrypted (no longer 'RID encrypted' or arcfour encrypted) + * passwords back into the structure + */ + +static NTSTATUS fix_user(TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key, + bool rid_crypt, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM *delta) +{ + + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_USER *user = delta->delta_union.user; + struct samr_Password lm_hash; + struct samr_Password nt_hash; + + if (rid_crypt) { + if (user->lm_password_present) { + sam_pwd_hash(rid, user->lmpassword.hash, lm_hash.hash, 0); + user->lmpassword = lm_hash; + } + + if (user->nt_password_present) { + sam_pwd_hash(rid, user->ntpassword.hash, nt_hash.hash, 0); + user->ntpassword = nt_hash; + } + } + + if (user->user_private_info.SensitiveData) { + DATA_BLOB data; + struct netr_USER_KEYS keys; + enum ndr_err_code ndr_err; + data.data = user->user_private_info.SensitiveData; + data.length = user->user_private_info.DataLength; + SamOEMhashBlob(data.data, data.length, session_key); + user->user_private_info.SensitiveData = data.data; + user->user_private_info.DataLength = data.length; + + ndr_err = ndr_pull_struct_blob(&data, mem_ctx, &keys, + (ndr_pull_flags_fn_t)ndr_pull_netr_USER_KEYS); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dump_data(10, data.data, data.length); + return ndr_map_error2ntstatus(ndr_err); + } + + if (keys.keys.keys2.lmpassword.length == 16) { + if (rid_crypt) { + sam_pwd_hash(rid, + keys.keys.keys2.lmpassword.pwd.hash, + lm_hash.hash, 0); + user->lmpassword = lm_hash; + } else { + user->lmpassword = keys.keys.keys2.lmpassword.pwd; + } + user->lm_password_present = true; + } + if (keys.keys.keys2.ntpassword.length == 16) { + if (rid_crypt) { + sam_pwd_hash(rid, + keys.keys.keys2.ntpassword.pwd.hash, + nt_hash.hash, 0); + user->ntpassword = nt_hash; + } else { + user->ntpassword = keys.keys.keys2.ntpassword.pwd; + } + user->nt_password_present = true; + } + /* TODO: rid decrypt history fields */ + } + return NT_STATUS_OK; +} + +/** + * Decrypt and extract the secrets + * + * The writes decrypted secrets back into the structure + */ +static NTSTATUS fix_secret(TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM *delta) +{ + struct netr_DELTA_SECRET *secret = delta->delta_union.secret; + + SamOEMhashBlob(secret->current_cipher.cipher_data, + secret->current_cipher.maxlen, + session_key); + + SamOEMhashBlob(secret->old_cipher.cipher_data, + secret->old_cipher.maxlen, + session_key); + + return NT_STATUS_OK; +} + +/** + * Fix up the delta, dealing with encryption issues so that the final + * callback need only do the printing or application logic + */ + +static NTSTATUS samsync_fix_delta(TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key, + bool rid_crypt, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM *delta) +{ + NTSTATUS status = NT_STATUS_OK; + + switch (delta->delta_type) { + case NETR_DELTA_USER: + + status = fix_user(mem_ctx, + session_key, + rid_crypt, + database_id, + delta); + break; + case NETR_DELTA_SECRET: + + status = fix_secret(mem_ctx, + session_key, + database_id, + delta); + break; + default: + break; + } + + return status; +} + +/** + * Fix up the delta, dealing with encryption issues so that the final + * callback need only do the printing or application logic + */ + +static NTSTATUS samsync_fix_delta_array(TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key, + bool rid_crypt, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r) +{ + NTSTATUS status; + int i; + + for (i = 0; i < r->num_deltas; i++) { + + status = samsync_fix_delta(mem_ctx, + session_key, + rid_crypt, + database_id, + &r->delta_enum[i]); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +/** + * libnet_samsync_init_context + */ + +NTSTATUS libnet_samsync_init_context(TALLOC_CTX *mem_ctx, + const struct dom_sid *domain_sid, + struct samsync_context **ctx_p) +{ + struct samsync_context *ctx; + + *ctx_p = NULL; + + ctx = TALLOC_ZERO_P(mem_ctx, struct samsync_context); + NT_STATUS_HAVE_NO_MEMORY(ctx); + + if (domain_sid) { + ctx->domain_sid = sid_dup_talloc(mem_ctx, domain_sid); + NT_STATUS_HAVE_NO_MEMORY(ctx->domain_sid); + + ctx->domain_sid_str = sid_string_talloc(mem_ctx, ctx->domain_sid); + NT_STATUS_HAVE_NO_MEMORY(ctx->domain_sid_str); + } + + *ctx_p = ctx; + + return NT_STATUS_OK; +} + +/** + * samsync_database_str + */ + +static const char *samsync_database_str(enum netr_SamDatabaseID database_id) +{ + + switch (database_id) { + case SAM_DATABASE_DOMAIN: + return "DOMAIN"; + case SAM_DATABASE_BUILTIN: + return "BUILTIN"; + case SAM_DATABASE_PRIVS: + return "PRIVS"; + default: + return "unknown"; + } +} + +/** + * samsync_debug_str + */ + +static const char *samsync_debug_str(TALLOC_CTX *mem_ctx, + enum net_samsync_mode mode, + enum netr_SamDatabaseID database_id) +{ + const char *action = NULL; + + switch (mode) { + case NET_SAMSYNC_MODE_DUMP: + action = "Dumping (to stdout)"; + break; + case NET_SAMSYNC_MODE_FETCH_PASSDB: + action = "Fetching (to passdb)"; + break; + case NET_SAMSYNC_MODE_FETCH_LDIF: + action = "Fetching (to ldif)"; + break; + case NET_SAMSYNC_MODE_FETCH_KEYTAB: + action = "Fetching (to keytab)"; + break; + default: + action = "Unknown"; + break; + } + + return talloc_asprintf(mem_ctx, "%s %s database", + action, samsync_database_str(database_id)); +} + +/** + * libnet_samsync + */ + +NTSTATUS libnet_samsync(enum netr_SamDatabaseID database_id, + struct samsync_context *ctx) +{ + NTSTATUS result; + TALLOC_CTX *mem_ctx; + const char *logon_server = ctx->cli->desthost; + const char *computername = global_myname(); + struct netr_Authenticator credential; + struct netr_Authenticator return_authenticator; + uint16_t restart_state = 0; + uint32_t sync_context = 0; + const char *debug_str; + DATA_BLOB session_key; + + ZERO_STRUCT(return_authenticator); + + if (!(mem_ctx = talloc_init("libnet_samsync"))) { + return NT_STATUS_NO_MEMORY; + } + + debug_str = samsync_debug_str(mem_ctx, ctx->mode, database_id); + if (debug_str) { + d_fprintf(stderr, "%s\n", debug_str); + } + + do { + struct netr_DELTA_ENUM_ARRAY *delta_enum_array = NULL; + NTSTATUS callback_status; + + netlogon_creds_client_step(ctx->cli->dc, &credential); + + result = rpccli_netr_DatabaseSync2(ctx->cli, mem_ctx, + logon_server, + computername, + &credential, + &return_authenticator, + database_id, + restart_state, + &sync_context, + &delta_enum_array, + 0xffff); + if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_SUPPORTED)) { + return result; + } + + /* Check returned credentials. */ + if (!netlogon_creds_client_check(ctx->cli->dc, + &return_authenticator.cred)) { + DEBUG(0,("credentials chain check failed\n")); + return NT_STATUS_ACCESS_DENIED; + } + + if (NT_STATUS_IS_ERR(result)) { + break; + } + + session_key = data_blob_const(ctx->cli->dc->sess_key, 16); + + samsync_fix_delta_array(mem_ctx, + &session_key, + false, + database_id, + delta_enum_array); + + /* Process results */ + callback_status = ctx->delta_fn(mem_ctx, database_id, + delta_enum_array, + NT_STATUS_IS_OK(result), ctx); + if (!NT_STATUS_IS_OK(callback_status)) { + result = callback_status; + goto out; + } + + TALLOC_FREE(delta_enum_array); + + /* Increment sync_context */ + sync_context += 1; + + } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)); + + out: + if (NT_STATUS_IS_ERR(result) && !ctx->error_message) { + + ctx->error_message = talloc_asprintf(ctx, + "Failed to fetch %s database: %s", + samsync_database_str(database_id), + nt_errstr(result)); + + if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_SUPPORTED)) { + + ctx->error_message = + talloc_asprintf_append(ctx->error_message, + "\nPerhaps %s is a Windows native mode domain?", + ctx->domain_name); + } + } + + talloc_destroy(mem_ctx); + + return result; +} + +/** + * pull_netr_AcctLockStr + */ + +NTSTATUS pull_netr_AcctLockStr(TALLOC_CTX *mem_ctx, + struct lsa_BinaryString *r, + struct netr_AcctLockStr **str_p) +{ + struct netr_AcctLockStr *str; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + + if (!mem_ctx || !r || !str_p) { + return NT_STATUS_INVALID_PARAMETER; + } + + *str_p = NULL; + + str = TALLOC_ZERO_P(mem_ctx, struct netr_AcctLockStr); + if (!str) { + return NT_STATUS_NO_MEMORY; + } + + blob = data_blob_const(r->array, r->length); + + ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, str, + (ndr_pull_flags_fn_t)ndr_pull_netr_AcctLockStr); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + *str_p = str; + + return NT_STATUS_OK; +} + diff --git a/source3/libnet/libnet_samsync.h b/source3/libnet/libnet_samsync.h new file mode 100644 index 0000000000..1f10d2c1c0 --- /dev/null +++ b/source3/libnet/libnet_samsync.h @@ -0,0 +1,73 @@ +/* + * 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/>. + */ + + +enum net_samsync_mode { + NET_SAMSYNC_MODE_FETCH_PASSDB = 0, + NET_SAMSYNC_MODE_FETCH_LDIF = 1, + NET_SAMSYNC_MODE_FETCH_KEYTAB = 2, + NET_SAMSYNC_MODE_DUMP = 3 +}; + +struct samsync_context; + +typedef NTSTATUS (*samsync_delta_fn_t)(TALLOC_CTX *, + enum netr_SamDatabaseID, + struct netr_DELTA_ENUM_ARRAY *, + bool, + struct samsync_context *); + +struct samsync_context { + enum net_samsync_mode mode; + const struct dom_sid *domain_sid; + const char *domain_sid_str; + const char *domain_name; + const char *output_filename; + + const char *username; + const char *password; + + char *result_message; + char *error_message; + + struct rpc_pipe_client *cli; + samsync_delta_fn_t delta_fn; + void *private_data; +}; + +NTSTATUS fetch_sam_entries_ldif(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx); +NTSTATUS fetch_sam_entries(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx); +NTSTATUS display_sam_entries(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx); +NTSTATUS fetch_sam_entries_keytab(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx); diff --git a/source3/libnet/libnet_samsync_display.c b/source3/libnet/libnet_samsync_display.c new file mode 100644 index 0000000000..6f7ae4e7aa --- /dev/null +++ b/source3/libnet/libnet_samsync_display.c @@ -0,0 +1,303 @@ +/* + Unix SMB/CIFS implementation. + dump the remote SAM using rpc samsync operations + + Copyright (C) Andrew Tridgell 2002 + Copyright (C) Tim Potter 2001,2002 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2005 + Modified by Volker Lendecke 2002 + Copyright (C) Jeremy Allison 2005. + 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/>. +*/ + +#include "includes.h" +#include "libnet/libnet.h" + +static void display_group_mem_info(uint32_t rid, + struct netr_DELTA_GROUP_MEMBER *r) +{ + int i; + d_printf("Group mem %u: ", rid); + for (i=0; i< r->num_rids; i++) { + d_printf("%u ", r->rids[i]); + } + d_printf("\n"); +} + +static void display_alias_info(uint32_t rid, + struct netr_DELTA_ALIAS *r) +{ + d_printf("Alias '%s' ", r->alias_name.string); + d_printf("desc='%s' rid=%u\n", r->description.string, r->rid); +} + +static void display_alias_mem(uint32_t rid, + struct netr_DELTA_ALIAS_MEMBER *r) +{ + int i; + d_printf("Alias rid %u: ", rid); + for (i=0; i< r->sids.num_sids; i++) { + d_printf("%s ", sid_string_tos(r->sids.sids[i].sid)); + } + d_printf("\n"); +} + +static void display_account_info(uint32_t rid, + struct netr_DELTA_USER *r) +{ + fstring hex_nt_passwd, hex_lm_passwd; + uchar lm_passwd[16], nt_passwd[16]; + static uchar zero_buf[16]; + + /* Decode hashes from password hash (if they are not NULL) */ + + if (memcmp(r->lmpassword.hash, zero_buf, 16) != 0) { + sam_pwd_hash(r->rid, r->lmpassword.hash, lm_passwd, 0); + pdb_sethexpwd(hex_lm_passwd, lm_passwd, r->acct_flags); + } else { + pdb_sethexpwd(hex_lm_passwd, NULL, 0); + } + + if (memcmp(r->ntpassword.hash, zero_buf, 16) != 0) { + sam_pwd_hash(r->rid, r->ntpassword.hash, nt_passwd, 0); + pdb_sethexpwd(hex_nt_passwd, nt_passwd, r->acct_flags); + } else { + pdb_sethexpwd(hex_nt_passwd, NULL, 0); + } + + printf("%s:%d:%s:%s:%s:LCT-0\n", + r->account_name.string, + r->rid, hex_lm_passwd, hex_nt_passwd, + pdb_encode_acct_ctrl(r->acct_flags, NEW_PW_FORMAT_SPACE_PADDED_LEN)); +} + +static void display_domain_info(struct netr_DELTA_DOMAIN *r) +{ + time_t u_logout; + struct netr_AcctLockStr *lockstr = NULL; + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_tos(); + + status = pull_netr_AcctLockStr(mem_ctx, &r->account_lockout, + &lockstr); + if (!NT_STATUS_IS_OK(status)) { + d_printf("failed to pull account lockout string: %s\n", + nt_errstr(status)); + } + + u_logout = uint64s_nt_time_to_unix_abs((const uint64 *)&r->force_logoff_time); + + d_printf("Domain name: %s\n", r->domain_name.string); + + d_printf("Minimal Password Length: %d\n", r->min_password_length); + d_printf("Password History Length: %d\n", r->password_history_length); + + d_printf("Force Logoff: %d\n", (int)u_logout); + + d_printf("Max Password Age: %s\n", display_time(r->max_password_age)); + d_printf("Min Password Age: %s\n", display_time(r->min_password_age)); + + if (lockstr) { + d_printf("Lockout Time: %s\n", display_time((NTTIME)lockstr->lockout_duration)); + d_printf("Lockout Reset Time: %s\n", display_time((NTTIME)lockstr->reset_count)); + d_printf("Bad Attempt Lockout: %d\n", lockstr->bad_attempt_lockout); + } + + d_printf("User must logon to change password: %d\n", r->logon_to_chgpass); +} + +static void display_group_info(uint32_t rid, struct netr_DELTA_GROUP *r) +{ + d_printf("Group '%s' ", r->group_name.string); + d_printf("desc='%s', rid=%u\n", r->description.string, rid); +} + +static void display_delete_group(uint32_t rid) +{ + d_printf("Delete Group '%d' ", rid); +} + +static void display_rename_group(uint32_t rid, struct netr_DELTA_RENAME *r) +{ + d_printf("Rename Group '%d' ", rid); + d_printf("Rename Group: %s -> %s\n", + r->OldName.string, r->NewName.string); +} + +static void display_delete_user(uint32_t rid) +{ + d_printf("Delete User '%d' ", rid); +} + +static void display_rename_user(uint32_t rid, struct netr_DELTA_RENAME *r) +{ + d_printf("Rename User '%d' ", rid); + d_printf("Rename User: %s -> %s\n", + r->OldName.string, r->NewName.string); +} + +static void display_delete_alias(uint32_t rid) +{ + d_printf("Delete Alias '%d' ", rid); +} + +static void display_rename_alias(uint32_t rid, struct netr_DELTA_RENAME *r) +{ + d_printf("Rename Alias '%d' ", rid); + d_printf("Rename Alias: %s -> %s\n", + r->OldName.string, r->NewName.string); +} + +static NTSTATUS display_sam_entry(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM *r, + bool last_query, + struct samsync_context *ctx) +{ + union netr_DELTA_UNION u = r->delta_union; + union netr_DELTA_ID_UNION id = r->delta_id_union; + + switch (r->delta_type) { + case NETR_DELTA_DOMAIN: + display_domain_info(u.domain); + break; + case NETR_DELTA_GROUP: + display_group_info(id.rid, u.group); + break; + case NETR_DELTA_DELETE_GROUP: + display_delete_group(id.rid); + break; + case NETR_DELTA_RENAME_GROUP: + display_rename_group(id.rid, u.rename_group); + break; + case NETR_DELTA_USER: + display_account_info(id.rid, u.user); + break; + case NETR_DELTA_DELETE_USER: + display_delete_user(id.rid); + break; + case NETR_DELTA_RENAME_USER: + display_rename_user(id.rid, u.rename_user); + break; + case NETR_DELTA_GROUP_MEMBER: + display_group_mem_info(id.rid, u.group_member); + break; + case NETR_DELTA_ALIAS: + display_alias_info(id.rid, u.alias); + break; + case NETR_DELTA_DELETE_ALIAS: + display_delete_alias(id.rid); + break; + case NETR_DELTA_RENAME_ALIAS: + display_rename_alias(id.rid, u.rename_alias); + break; + case NETR_DELTA_ALIAS_MEMBER: + display_alias_mem(id.rid, u.alias_member); + break; + case NETR_DELTA_POLICY: + printf("Policy\n"); + break; + case NETR_DELTA_TRUSTED_DOMAIN: + printf("Trusted Domain: %s\n", + u.trusted_domain->domain_name.string); + break; + case NETR_DELTA_DELETE_TRUST: + printf("Delete Trust: %d\n", + u.delete_trust.unknown); + break; + case NETR_DELTA_ACCOUNT: + printf("Account\n"); + break; + case NETR_DELTA_DELETE_ACCOUNT: + printf("Delete Account: %d\n", + u.delete_account.unknown); + break; + case NETR_DELTA_SECRET: + printf("Secret\n"); + break; + case NETR_DELTA_DELETE_SECRET: + printf("Delete Secret: %d\n", + u.delete_secret.unknown); + break; + case NETR_DELTA_DELETE_GROUP2: + printf("Delete Group2: %s\n", + u.delete_group->account_name); + break; + case NETR_DELTA_DELETE_USER2: + printf("Delete User2: %s\n", + u.delete_user->account_name); + break; + case NETR_DELTA_MODIFY_COUNT: + printf("sam sequence update: 0x%016llx\n", + (unsigned long long) *u.modified_count); + break; +#if 0 + /* The following types are recognised but not handled */ + case NETR_DELTA_POLICY: + d_printf("NETR_DELTA_POLICY not handled\n"); + break; + case NETR_DELTA_TRUSTED_DOMAIN: + d_printf("NETR_DELTA_TRUSTED_DOMAIN not handled\n"); + break; + case NETR_DELTA_ACCOUNT: + d_printf("NETR_DELTA_ACCOUNT not handled\n"); + break; + case NETR_DELTA_SECRET: + d_printf("NETR_DELTA_SECRET not handled\n"); + break; + case NETR_DELTA_MODIFY_COUNT: + d_printf("NETR_DELTA_MODIFY_COUNT not handled\n"); + break; + case NETR_DELTA_DELETE_TRUST: + d_printf("NETR_DELTA_DELETE_TRUST not handled\n"); + break; + case NETR_DELTA_DELETE_ACCOUNT: + d_printf("NETR_DELTA_DELETE_ACCOUNT not handled\n"); + break; + case NETR_DELTA_DELETE_SECRET: + d_printf("NETR_DELTA_DELETE_SECRET not handled\n"); + break; + case NETR_DELTA_DELETE_GROUP2: + d_printf("NETR_DELTA_DELETE_GROUP2 not handled\n"); + break; + case NETR_DELTA_DELETE_USER2: + d_printf("NETR_DELTA_DELETE_USER2 not handled\n"); + break; +#endif + default: + printf("unknown delta type 0x%02x\n", + r->delta_type); + break; + } + + return NT_STATUS_OK; +} + +NTSTATUS display_sam_entries(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx) +{ + int i; + + for (i = 0; i < r->num_deltas; i++) { + display_sam_entry(mem_ctx, database_id, &r->delta_enum[i], + last_query, ctx); + } + + return NT_STATUS_OK; +} diff --git a/source3/libnet/libnet_samsync_keytab.c b/source3/libnet/libnet_samsync_keytab.c new file mode 100644 index 0000000000..f284f08ad9 --- /dev/null +++ b/source3/libnet/libnet_samsync_keytab.c @@ -0,0 +1,193 @@ +/* + Unix SMB/CIFS implementation. + dump the remote SAM using rpc samsync operations + + 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/>. +*/ + +#include "includes.h" +#include "libnet/libnet.h" + +#if defined(HAVE_ADS) && defined(ENCTYPE_ARCFOUR_HMAC) + +/**************************************************************** +****************************************************************/ + +static NTSTATUS keytab_ad_connect(TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *username, + const char *password, + struct libnet_keytab_context *ctx) +{ + NTSTATUS status; + ADS_STATUS ad_status; + ADS_STRUCT *ads; + struct netr_DsRGetDCNameInfo *info = NULL; + const char *dc; + + status = dsgetdcname(mem_ctx, NULL, domain_name, NULL, NULL, 0, &info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dc = strip_hostname(info->dc_unc); + + ads = ads_init(NULL, domain_name, dc); + NT_STATUS_HAVE_NO_MEMORY(ads); + + if (getenv(KRB5_ENV_CCNAME) == NULL) { + setenv(KRB5_ENV_CCNAME, "MEMORY:libnet_samsync_keytab", 1); + } + + ads->auth.user_name = SMB_STRDUP(username); + ads->auth.password = SMB_STRDUP(password); + + ad_status = ads_connect_user_creds(ads); + if (!ADS_ERR_OK(ad_status)) { + return NT_STATUS_UNSUCCESSFUL; + } + + ctx->ads = ads; + + ctx->dns_domain_name = talloc_strdup_upper(mem_ctx, ads->config.realm); + NT_STATUS_HAVE_NO_MEMORY(ctx->dns_domain_name); + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS fetch_sam_entry_keytab(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + uint32_t rid, + struct netr_DELTA_USER *r, + bool last_query, + struct libnet_keytab_context *ctx) +{ + uchar nt_passwd[16]; + struct libnet_keytab_entry entry; + + if (memcmp(r->ntpassword.hash, ctx->zero_buf, 16) == 0) { + return NT_STATUS_OK; + } + + sam_pwd_hash(rid, r->ntpassword.hash, nt_passwd, 0); + + entry.name = talloc_strdup(mem_ctx, r->account_name.string); + entry.principal = talloc_asprintf(mem_ctx, "%s@%s", + r->account_name.string, + ctx->dns_domain_name); + entry.password = data_blob_talloc(mem_ctx, nt_passwd, 16); + entry.kvno = ads_get_kvno(ctx->ads, entry.name); + + 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); + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS fetch_sam_entries_keytab(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx) +{ + NTSTATUS status = NT_STATUS_OK; + krb5_error_code ret = 0; + static struct libnet_keytab_context *keytab_ctx = NULL; + int i; + + if (!keytab_ctx) { + ret = libnet_keytab_init(mem_ctx, ctx->output_filename, + &keytab_ctx); + if (ret) { + status = krb5_to_nt_status(ret); + goto out; + } + } + + status = keytab_ad_connect(mem_ctx, + ctx->domain_name, + ctx->username, + ctx->password, + keytab_ctx); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + for (i = 0; i < r->num_deltas; i++) { + + if (r->delta_enum[i].delta_type != NETR_DELTA_USER) { + continue; + } + + status = fetch_sam_entry_keytab(mem_ctx, database_id, + r->delta_enum[i].delta_id_union.rid, + r->delta_enum[i].delta_union.user, + last_query, + keytab_ctx); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + } + + if (last_query) { + + ret = libnet_keytab_add(keytab_ctx); + if (ret) { + status = krb5_to_nt_status(ret); + ctx->error_message = talloc_asprintf(mem_ctx, + "Failed to add entries to keytab %s: %s", + keytab_ctx->keytab_name, error_message(ret)); + goto out; + } + + ctx->result_message = talloc_asprintf(mem_ctx, + "Vampired %d accounts to keytab %s", + keytab_ctx->count, + keytab_ctx->keytab_name); + + TALLOC_FREE(keytab_ctx); + } + + return NT_STATUS_OK; + out: + TALLOC_FREE(keytab_ctx); + + return status; +} + +#else + +NTSTATUS fetch_sam_entries_keytab(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +#endif /* defined(HAVE_ADS) && defined(ENCTYPE_ARCFOUR_HMAC) */ diff --git a/source3/libnet/libnet_samsync_ldif.c b/source3/libnet/libnet_samsync_ldif.c new file mode 100644 index 0000000000..cbae22aad3 --- /dev/null +++ b/source3/libnet/libnet_samsync_ldif.c @@ -0,0 +1,1229 @@ +/* + Unix SMB/CIFS implementation. + dump the remote SAM using rpc samsync operations + + Copyright (C) Andrew Tridgell 2002 + Copyright (C) Tim Potter 2001,2002 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2005 + Modified by Volker Lendecke 2002 + Copyright (C) Jeremy Allison 2005. + 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/>. +*/ + +#include "includes.h" +#include "libnet/libnet_samsync.h" + +#ifdef HAVE_LDAP + +/* uid's and gid's for writing deltas to ldif */ +static uint32 ldif_gid = 999; +static uint32 ldif_uid = 999; + +/* Structure for mapping accounts to groups */ +/* Array element is the group rid */ +typedef struct _groupmap { + uint32_t rid; + uint32_t gidNumber; + const char *sambaSID; + const char *group_dn; +} GROUPMAP; + +typedef struct _accountmap { + uint32_t rid; + const char *cn; +} ACCOUNTMAP; + +struct samsync_ldif_context { + GROUPMAP *groupmap; + ACCOUNTMAP *accountmap; + bool initialized; + const char *add_template; + const char *mod_template; + char *add_name; + char *mod_name; + FILE *add_file; + FILE *mod_file; + FILE *ldif_file; + const char *suffix; + int num_alloced; +}; + +/**************************************************************** +****************************************************************/ + +static NTSTATUS populate_ldap_for_ldif(const char *sid, + const char *suffix, + const char *builtin_sid, + FILE *add_fd) +{ + const char *user_suffix, *group_suffix, *machine_suffix, *idmap_suffix; + char *user_attr=NULL, *group_attr=NULL; + char *suffix_attr; + int len; + + /* Get the suffix attribute */ + suffix_attr = sstring_sub(suffix, '=', ','); + if (suffix_attr == NULL) { + len = strlen(suffix); + suffix_attr = (char*)SMB_MALLOC(len+1); + memcpy(suffix_attr, suffix, len); + suffix_attr[len] = '\0'; + } + + /* Write the base */ + fprintf(add_fd, "# %s\n", suffix); + fprintf(add_fd, "dn: %s\n", suffix); + fprintf(add_fd, "objectClass: dcObject\n"); + fprintf(add_fd, "objectClass: organization\n"); + fprintf(add_fd, "o: %s\n", suffix_attr); + fprintf(add_fd, "dc: %s\n", suffix_attr); + fprintf(add_fd, "\n"); + fflush(add_fd); + + user_suffix = lp_ldap_user_suffix(); + if (user_suffix == NULL) { + SAFE_FREE(suffix_attr); + return NT_STATUS_NO_MEMORY; + } + /* If it exists and is distinct from other containers, + Write the Users entity */ + if (*user_suffix && strcmp(user_suffix, suffix)) { + user_attr = sstring_sub(lp_ldap_user_suffix(), '=', ','); + fprintf(add_fd, "# %s\n", user_suffix); + fprintf(add_fd, "dn: %s\n", user_suffix); + fprintf(add_fd, "objectClass: organizationalUnit\n"); + fprintf(add_fd, "ou: %s\n", user_attr); + fprintf(add_fd, "\n"); + fflush(add_fd); + } + + + group_suffix = lp_ldap_group_suffix(); + if (group_suffix == NULL) { + SAFE_FREE(suffix_attr); + SAFE_FREE(user_attr); + return NT_STATUS_NO_MEMORY; + } + /* If it exists and is distinct from other containers, + Write the Groups entity */ + if (*group_suffix && strcmp(group_suffix, suffix)) { + group_attr = sstring_sub(lp_ldap_group_suffix(), '=', ','); + fprintf(add_fd, "# %s\n", group_suffix); + fprintf(add_fd, "dn: %s\n", group_suffix); + fprintf(add_fd, "objectClass: organizationalUnit\n"); + fprintf(add_fd, "ou: %s\n", group_attr); + fprintf(add_fd, "\n"); + fflush(add_fd); + } + + /* If it exists and is distinct from other containers, + Write the Computers entity */ + machine_suffix = lp_ldap_machine_suffix(); + if (machine_suffix == NULL) { + SAFE_FREE(suffix_attr); + SAFE_FREE(user_attr); + SAFE_FREE(group_attr); + return NT_STATUS_NO_MEMORY; + } + if (*machine_suffix && strcmp(machine_suffix, user_suffix) && + strcmp(machine_suffix, suffix)) { + char *machine_ou = NULL; + fprintf(add_fd, "# %s\n", machine_suffix); + fprintf(add_fd, "dn: %s\n", machine_suffix); + fprintf(add_fd, "objectClass: organizationalUnit\n"); + /* this isn't totally correct as it assumes that + there _must_ be an ou. just fixing memleak now. jmcd */ + machine_ou = sstring_sub(lp_ldap_machine_suffix(), '=', ','); + fprintf(add_fd, "ou: %s\n", machine_ou); + SAFE_FREE(machine_ou); + fprintf(add_fd, "\n"); + fflush(add_fd); + } + + /* If it exists and is distinct from other containers, + Write the IdMap entity */ + idmap_suffix = lp_ldap_idmap_suffix(); + if (idmap_suffix == NULL) { + SAFE_FREE(suffix_attr); + SAFE_FREE(user_attr); + SAFE_FREE(group_attr); + return NT_STATUS_NO_MEMORY; + } + if (*idmap_suffix && + strcmp(idmap_suffix, user_suffix) && + strcmp(idmap_suffix, suffix)) { + char *s; + fprintf(add_fd, "# %s\n", idmap_suffix); + fprintf(add_fd, "dn: %s\n", idmap_suffix); + fprintf(add_fd, "ObjectClass: organizationalUnit\n"); + s = sstring_sub(lp_ldap_idmap_suffix(), '=', ','); + fprintf(add_fd, "ou: %s\n", s); + SAFE_FREE(s); + fprintf(add_fd, "\n"); + fflush(add_fd); + } + + /* Write the domain entity */ + fprintf(add_fd, "# %s, %s\n", lp_workgroup(), suffix); + fprintf(add_fd, "dn: sambaDomainName=%s,%s\n", lp_workgroup(), + suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_DOMINFO); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_IDPOOL); + fprintf(add_fd, "sambaDomainName: %s\n", lp_workgroup()); + fprintf(add_fd, "sambaSID: %s\n", sid); + fprintf(add_fd, "uidNumber: %d\n", ++ldif_uid); + fprintf(add_fd, "gidNumber: %d\n", ++ldif_gid); + fprintf(add_fd, "\n"); + fflush(add_fd); + + /* Write the Domain Admins entity */ + fprintf(add_fd, "# Domain Admins, %s, %s\n", group_attr, + suffix); + fprintf(add_fd, "dn: cn=Domain Admins,ou=%s,%s\n", group_attr, + suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf(add_fd, "cn: Domain Admins\n"); + fprintf(add_fd, "memberUid: Administrator\n"); + fprintf(add_fd, "description: Netbios Domain Administrators\n"); + fprintf(add_fd, "gidNumber: 512\n"); + fprintf(add_fd, "sambaSID: %s-512\n", sid); + fprintf(add_fd, "sambaGroupType: 2\n"); + fprintf(add_fd, "displayName: Domain Admins\n"); + fprintf(add_fd, "\n"); + fflush(add_fd); + + /* Write the Domain Users entity */ + fprintf(add_fd, "# Domain Users, %s, %s\n", group_attr, + suffix); + fprintf(add_fd, "dn: cn=Domain Users,ou=%s,%s\n", group_attr, + suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf(add_fd, "cn: Domain Users\n"); + fprintf(add_fd, "description: Netbios Domain Users\n"); + fprintf(add_fd, "gidNumber: 513\n"); + fprintf(add_fd, "sambaSID: %s-513\n", sid); + fprintf(add_fd, "sambaGroupType: 2\n"); + fprintf(add_fd, "displayName: Domain Users\n"); + fprintf(add_fd, "\n"); + fflush(add_fd); + + /* Write the Domain Guests entity */ + fprintf(add_fd, "# Domain Guests, %s, %s\n", group_attr, + suffix); + fprintf(add_fd, "dn: cn=Domain Guests,ou=%s,%s\n", group_attr, + suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf(add_fd, "cn: Domain Guests\n"); + fprintf(add_fd, "description: Netbios Domain Guests\n"); + fprintf(add_fd, "gidNumber: 514\n"); + fprintf(add_fd, "sambaSID: %s-514\n", sid); + fprintf(add_fd, "sambaGroupType: 2\n"); + fprintf(add_fd, "displayName: Domain Guests\n"); + fprintf(add_fd, "\n"); + fflush(add_fd); + + /* Write the Domain Computers entity */ + fprintf(add_fd, "# Domain Computers, %s, %s\n", group_attr, + suffix); + fprintf(add_fd, "dn: cn=Domain Computers,ou=%s,%s\n", + group_attr, suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf(add_fd, "gidNumber: 515\n"); + fprintf(add_fd, "cn: Domain Computers\n"); + fprintf(add_fd, "description: Netbios Domain Computers accounts\n"); + fprintf(add_fd, "sambaSID: %s-515\n", sid); + fprintf(add_fd, "sambaGroupType: 2\n"); + fprintf(add_fd, "displayName: Domain Computers\n"); + fprintf(add_fd, "\n"); + fflush(add_fd); + + /* Write the Admininistrators Groups entity */ + fprintf(add_fd, "# Administrators, %s, %s\n", group_attr, + suffix); + fprintf(add_fd, "dn: cn=Administrators,ou=%s,%s\n", group_attr, + suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf(add_fd, "gidNumber: 544\n"); + fprintf(add_fd, "cn: Administrators\n"); + fprintf(add_fd, "description: Netbios Domain Members can fully administer the computer/sambaDomainName\n"); + fprintf(add_fd, "sambaSID: %s-544\n", builtin_sid); + fprintf(add_fd, "sambaGroupType: 5\n"); + fprintf(add_fd, "displayName: Administrators\n"); + fprintf(add_fd, "\n"); + + /* Write the Print Operator entity */ + fprintf(add_fd, "# Print Operators, %s, %s\n", group_attr, + suffix); + fprintf(add_fd, "dn: cn=Print Operators,ou=%s,%s\n", + group_attr, suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf(add_fd, "gidNumber: 550\n"); + fprintf(add_fd, "cn: Print Operators\n"); + fprintf(add_fd, "description: Netbios Domain Print Operators\n"); + fprintf(add_fd, "sambaSID: %s-550\n", builtin_sid); + fprintf(add_fd, "sambaGroupType: 5\n"); + fprintf(add_fd, "displayName: Print Operators\n"); + fprintf(add_fd, "\n"); + fflush(add_fd); + + /* Write the Backup Operators entity */ + fprintf(add_fd, "# Backup Operators, %s, %s\n", group_attr, + suffix); + fprintf(add_fd, "dn: cn=Backup Operators,ou=%s,%s\n", + group_attr, suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf(add_fd, "gidNumber: 551\n"); + fprintf(add_fd, "cn: Backup Operators\n"); + fprintf(add_fd, "description: Netbios Domain Members can bypass file security to back up files\n"); + fprintf(add_fd, "sambaSID: %s-551\n", builtin_sid); + fprintf(add_fd, "sambaGroupType: 5\n"); + fprintf(add_fd, "displayName: Backup Operators\n"); + fprintf(add_fd, "\n"); + fflush(add_fd); + + /* Write the Replicators entity */ + fprintf(add_fd, "# Replicators, %s, %s\n", group_attr, suffix); + fprintf(add_fd, "dn: cn=Replicators,ou=%s,%s\n", group_attr, + suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf(add_fd, "gidNumber: 552\n"); + fprintf(add_fd, "cn: Replicators\n"); + fprintf(add_fd, "description: Netbios Domain Supports file replication in a sambaDomainName\n"); + fprintf(add_fd, "sambaSID: %s-552\n", builtin_sid); + fprintf(add_fd, "sambaGroupType: 5\n"); + fprintf(add_fd, "displayName: Replicators\n"); + fprintf(add_fd, "\n"); + fflush(add_fd); + + /* Deallocate memory, and return */ + SAFE_FREE(suffix_attr); + SAFE_FREE(user_attr); + SAFE_FREE(group_attr); + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS map_populate_groups(TALLOC_CTX *mem_ctx, + GROUPMAP *groupmap, + ACCOUNTMAP *accountmap, + const char *sid, + const char *suffix, + const char *builtin_sid) +{ + char *group_attr = sstring_sub(lp_ldap_group_suffix(), '=', ','); + + /* Map the groups created by populate_ldap_for_ldif */ + groupmap[0].rid = 512; + groupmap[0].gidNumber = 512; + groupmap[0].sambaSID = talloc_asprintf(mem_ctx, "%s-512", sid); + groupmap[0].group_dn = talloc_asprintf(mem_ctx, + "cn=Domain Admins,ou=%s,%s", group_attr, suffix); + NT_STATUS_HAVE_NO_MEMORY(groupmap[0].sambaSID); + NT_STATUS_HAVE_NO_MEMORY(groupmap[0].group_dn); + + accountmap[0].rid = 512; + accountmap[0].cn = talloc_strdup(mem_ctx, "Domain Admins"); + NT_STATUS_HAVE_NO_MEMORY(accountmap[0].cn); + + groupmap[1].rid = 513; + groupmap[1].gidNumber = 513; + groupmap[1].sambaSID = talloc_asprintf(mem_ctx, "%s-513", sid); + groupmap[1].group_dn = talloc_asprintf(mem_ctx, + "cn=Domain Users,ou=%s,%s", group_attr, suffix); + NT_STATUS_HAVE_NO_MEMORY(groupmap[1].sambaSID); + NT_STATUS_HAVE_NO_MEMORY(groupmap[1].group_dn); + + accountmap[1].rid = 513; + accountmap[1].cn = talloc_strdup(mem_ctx, "Domain Users"); + NT_STATUS_HAVE_NO_MEMORY(accountmap[1].cn); + + groupmap[2].rid = 514; + groupmap[2].gidNumber = 514; + groupmap[2].sambaSID = talloc_asprintf(mem_ctx, "%s-514", sid); + groupmap[2].group_dn = talloc_asprintf(mem_ctx, + "cn=Domain Guests,ou=%s,%s", group_attr, suffix); + NT_STATUS_HAVE_NO_MEMORY(groupmap[2].sambaSID); + NT_STATUS_HAVE_NO_MEMORY(groupmap[2].group_dn); + + accountmap[2].rid = 514; + accountmap[2].cn = talloc_strdup(mem_ctx, "Domain Guests"); + NT_STATUS_HAVE_NO_MEMORY(accountmap[2].cn); + + groupmap[3].rid = 515; + groupmap[3].gidNumber = 515; + groupmap[3].sambaSID = talloc_asprintf(mem_ctx, "%s-515", sid); + groupmap[3].group_dn = talloc_asprintf(mem_ctx, + "cn=Domain Computers,ou=%s,%s", group_attr, suffix); + NT_STATUS_HAVE_NO_MEMORY(groupmap[3].sambaSID); + NT_STATUS_HAVE_NO_MEMORY(groupmap[3].group_dn); + + accountmap[3].rid = 515; + accountmap[3].cn = talloc_strdup(mem_ctx, "Domain Computers"); + NT_STATUS_HAVE_NO_MEMORY(accountmap[3].cn); + + groupmap[4].rid = 544; + groupmap[4].gidNumber = 544; + groupmap[4].sambaSID = talloc_asprintf(mem_ctx, "%s-544", builtin_sid); + groupmap[4].group_dn = talloc_asprintf(mem_ctx, + "cn=Administrators,ou=%s,%s", group_attr, suffix); + NT_STATUS_HAVE_NO_MEMORY(groupmap[4].sambaSID); + NT_STATUS_HAVE_NO_MEMORY(groupmap[4].group_dn); + + accountmap[4].rid = 515; + accountmap[4].cn = talloc_strdup(mem_ctx, "Administrators"); + NT_STATUS_HAVE_NO_MEMORY(accountmap[4].cn); + + groupmap[5].rid = 550; + groupmap[5].gidNumber = 550; + groupmap[5].sambaSID = talloc_asprintf(mem_ctx, "%s-550", builtin_sid); + groupmap[5].group_dn = talloc_asprintf(mem_ctx, + "cn=Print Operators,ou=%s,%s", group_attr, suffix); + NT_STATUS_HAVE_NO_MEMORY(groupmap[5].sambaSID); + NT_STATUS_HAVE_NO_MEMORY(groupmap[5].group_dn); + + accountmap[5].rid = 550; + accountmap[5].cn = talloc_strdup(mem_ctx, "Print Operators"); + NT_STATUS_HAVE_NO_MEMORY(accountmap[5].cn); + + groupmap[6].rid = 551; + groupmap[6].gidNumber = 551; + groupmap[6].sambaSID = talloc_asprintf(mem_ctx, "%s-551", builtin_sid); + groupmap[6].group_dn = talloc_asprintf(mem_ctx, + "cn=Backup Operators,ou=%s,%s", group_attr, suffix); + NT_STATUS_HAVE_NO_MEMORY(groupmap[6].sambaSID); + NT_STATUS_HAVE_NO_MEMORY(groupmap[6].group_dn); + + accountmap[6].rid = 551; + accountmap[6].cn = talloc_strdup(mem_ctx, "Backup Operators"); + NT_STATUS_HAVE_NO_MEMORY(accountmap[6].cn); + + groupmap[7].rid = 552; + groupmap[7].gidNumber = 552; + groupmap[7].sambaSID = talloc_asprintf(mem_ctx, "%s-552", builtin_sid); + groupmap[7].group_dn = talloc_asprintf(mem_ctx, + "cn=Replicators,ou=%s,%s", group_attr, suffix); + NT_STATUS_HAVE_NO_MEMORY(groupmap[7].sambaSID); + NT_STATUS_HAVE_NO_MEMORY(groupmap[7].group_dn); + + accountmap[7].rid = 551; + accountmap[7].cn = talloc_strdup(mem_ctx, "Replicators"); + NT_STATUS_HAVE_NO_MEMORY(accountmap[7].cn); + + SAFE_FREE(group_attr); + + return NT_STATUS_OK; +} + +/* + * This is a crap routine, but I think it's the quickest way to solve the + * UTF8->base64 problem. + */ + +static int fprintf_attr(FILE *add_fd, const char *attr_name, + const char *fmt, ...) +{ + va_list ap; + char *value, *p, *base64; + DATA_BLOB base64_blob; + bool do_base64 = false; + int res; + + va_start(ap, fmt); + value = talloc_vasprintf(NULL, fmt, ap); + va_end(ap); + + SMB_ASSERT(value != NULL); + + for (p=value; *p; p++) { + if (*p & 0x80) { + do_base64 = true; + break; + } + } + + if (!do_base64) { + bool only_whitespace = true; + for (p=value; *p; p++) { + /* + * I know that this not multibyte safe, but we break + * on the first non-whitespace character anyway. + */ + if (!isspace(*p)) { + only_whitespace = false; + break; + } + } + if (only_whitespace) { + do_base64 = true; + } + } + + if (!do_base64) { + res = fprintf(add_fd, "%s: %s\n", attr_name, value); + TALLOC_FREE(value); + return res; + } + + base64_blob.data = (unsigned char *)value; + base64_blob.length = strlen(value); + + base64 = base64_encode_data_blob(value, base64_blob); + SMB_ASSERT(base64 != NULL); + + res = fprintf(add_fd, "%s:: %s\n", attr_name, base64); + TALLOC_FREE(value); + return res; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS fetch_group_info_to_ldif(TALLOC_CTX *mem_ctx, + struct netr_DELTA_GROUP *r, + GROUPMAP *groupmap, + FILE *add_fd, + const char *sid, + const char *suffix) +{ + const char *groupname = r->group_name.string; + uint32 grouptype = 0, g_rid = 0; + char *group_attr = sstring_sub(lp_ldap_group_suffix(), '=', ','); + + /* Set up the group type (always 2 for group info) */ + grouptype = 2; + + /* These groups are entered by populate_ldap_for_ldif */ + if (strcmp(groupname, "Domain Admins") == 0 || + strcmp(groupname, "Domain Users") == 0 || + strcmp(groupname, "Domain Guests") == 0 || + strcmp(groupname, "Domain Computers") == 0 || + strcmp(groupname, "Administrators") == 0 || + strcmp(groupname, "Print Operators") == 0 || + strcmp(groupname, "Backup Operators") == 0 || + strcmp(groupname, "Replicators") == 0) { + SAFE_FREE(group_attr); + return NT_STATUS_OK; + } else { + /* Increment the gid for the new group */ + ldif_gid++; + } + + /* Map the group rid, gid, and dn */ + g_rid = r->rid; + groupmap->rid = g_rid; + groupmap->gidNumber = ldif_gid; + groupmap->sambaSID = talloc_asprintf(mem_ctx, "%s-%d", sid, g_rid); + groupmap->group_dn = talloc_asprintf(mem_ctx, + "cn=%s,ou=%s,%s", groupname, group_attr, suffix); + NT_STATUS_HAVE_NO_MEMORY(groupmap->sambaSID); + NT_STATUS_HAVE_NO_MEMORY(groupmap->group_dn); + + /* Write the data to the temporary add ldif file */ + fprintf(add_fd, "# %s, %s, %s\n", groupname, group_attr, + suffix); + fprintf_attr(add_fd, "dn", "cn=%s,ou=%s,%s", groupname, group_attr, + suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf_attr(add_fd, "cn", "%s", groupname); + fprintf(add_fd, "gidNumber: %d\n", ldif_gid); + fprintf(add_fd, "sambaSID: %s\n", groupmap->sambaSID); + fprintf(add_fd, "sambaGroupType: %d\n", grouptype); + fprintf_attr(add_fd, "displayName", "%s", groupname); + fprintf(add_fd, "\n"); + fflush(add_fd); + + SAFE_FREE(group_attr); + /* Return */ + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS fetch_account_info_to_ldif(TALLOC_CTX *mem_ctx, + struct netr_DELTA_USER *r, + GROUPMAP *groupmap, + ACCOUNTMAP *accountmap, + FILE *add_fd, + const char *sid, + const char *suffix, + int alloced) +{ + fstring username, logonscript, homedrive, homepath = "", homedir = ""; + fstring hex_nt_passwd, hex_lm_passwd; + fstring description, profilepath, fullname, sambaSID; + uchar lm_passwd[16], nt_passwd[16]; + char *flags, *user_rdn; + const char *ou; + const char* nopasswd = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + static uchar zero_buf[16]; + uint32 rid = 0, group_rid = 0, gidNumber = 0; + time_t unix_time; + int i; + + /* Get the username */ + fstrcpy(username, r->account_name.string); + + /* Get the rid */ + rid = r->rid; + + /* Map the rid and username for group member info later */ + accountmap->rid = rid; + accountmap->cn = talloc_strdup(mem_ctx, username); + NT_STATUS_HAVE_NO_MEMORY(accountmap->cn); + + /* Get the home directory */ + if (r->acct_flags & ACB_NORMAL) { + fstrcpy(homedir, r->home_directory.string); + if (!*homedir) { + snprintf(homedir, sizeof(homedir), "/home/%s", username); + } else { + snprintf(homedir, sizeof(homedir), "/nobodyshomedir"); + } + ou = lp_ldap_user_suffix(); + } else { + ou = lp_ldap_machine_suffix(); + snprintf(homedir, sizeof(homedir), "/machinehomedir"); + } + + /* Get the logon script */ + fstrcpy(logonscript, r->logon_script.string); + + /* Get the home drive */ + fstrcpy(homedrive, r->home_drive.string); + + /* Get the home path */ + fstrcpy(homepath, r->home_directory.string); + + /* Get the description */ + fstrcpy(description, r->description.string); + + /* Get the display name */ + fstrcpy(fullname, r->full_name.string); + + /* Get the profile path */ + fstrcpy(profilepath, r->profile_path.string); + + /* Get lm and nt password data */ + if (memcmp(r->lmpassword.hash, zero_buf, 16) != 0) { + sam_pwd_hash(r->rid, r->lmpassword.hash, lm_passwd, 0); + pdb_sethexpwd(hex_lm_passwd, lm_passwd, r->acct_flags); + } else { + pdb_sethexpwd(hex_lm_passwd, NULL, 0); + } + if (memcmp(r->ntpassword.hash, zero_buf, 16) != 0) { + sam_pwd_hash(r->rid, r->ntpassword.hash, nt_passwd, 0); + pdb_sethexpwd(hex_nt_passwd, nt_passwd, r->acct_flags); + } else { + pdb_sethexpwd(hex_nt_passwd, NULL, 0); + } + unix_time = nt_time_to_unix(r->last_password_change); + + /* Increment the uid for the new user */ + ldif_uid++; + + /* Set up group id and sambaSID for the user */ + group_rid = r->primary_gid; + for (i=0; i<alloced; i++) { + if (groupmap[i].rid == group_rid) break; + } + if (i == alloced){ + DEBUG(1, ("Could not find rid %d in groupmap array\n", + group_rid)); + return NT_STATUS_UNSUCCESSFUL; + } + gidNumber = groupmap[i].gidNumber; + snprintf(sambaSID, sizeof(sambaSID), groupmap[i].sambaSID); + + /* Set up sambaAcctFlags */ + flags = pdb_encode_acct_ctrl(r->acct_flags, + NEW_PW_FORMAT_SPACE_PADDED_LEN); + + /* Add the user to the temporary add ldif file */ + /* this isn't quite right...we can't assume there's just OU=. jmcd */ + user_rdn = sstring_sub(ou, '=', ','); + fprintf(add_fd, "# %s, %s, %s\n", username, user_rdn, suffix); + fprintf_attr(add_fd, "dn", "uid=%s,ou=%s,%s", username, user_rdn, + suffix); + SAFE_FREE(user_rdn); + fprintf(add_fd, "ObjectClass: top\n"); + fprintf(add_fd, "objectClass: inetOrgPerson\n"); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXACCOUNT); + fprintf(add_fd, "objectClass: shadowAccount\n"); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_SAMBASAMACCOUNT); + fprintf_attr(add_fd, "cn", "%s", username); + fprintf_attr(add_fd, "sn", "%s", username); + fprintf_attr(add_fd, "uid", "%s", username); + fprintf(add_fd, "uidNumber: %d\n", ldif_uid); + fprintf(add_fd, "gidNumber: %d\n", gidNumber); + fprintf_attr(add_fd, "homeDirectory", "%s", homedir); + if (*homepath) + fprintf_attr(add_fd, "sambaHomePath", "%s", homepath); + if (*homedrive) + fprintf_attr(add_fd, "sambaHomeDrive", "%s", homedrive); + if (*logonscript) + fprintf_attr(add_fd, "sambaLogonScript", "%s", logonscript); + fprintf(add_fd, "loginShell: %s\n", + ((r->acct_flags & ACB_NORMAL) ? + "/bin/bash" : "/bin/false")); + fprintf(add_fd, "gecos: System User\n"); + if (*description) + fprintf_attr(add_fd, "description", "%s", description); + fprintf(add_fd, "sambaSID: %s-%d\n", sid, rid); + fprintf(add_fd, "sambaPrimaryGroupSID: %s\n", sambaSID); + if(*fullname) + fprintf_attr(add_fd, "displayName", "%s", fullname); + if(*profilepath) + fprintf_attr(add_fd, "sambaProfilePath", "%s", profilepath); + if (strcmp(nopasswd, hex_lm_passwd) != 0) + fprintf(add_fd, "sambaLMPassword: %s\n", hex_lm_passwd); + if (strcmp(nopasswd, hex_nt_passwd) != 0) + fprintf(add_fd, "sambaNTPassword: %s\n", hex_nt_passwd); + fprintf(add_fd, "sambaPwdLastSet: %d\n", (int)unix_time); + fprintf(add_fd, "sambaAcctFlags: %s\n", flags); + fprintf(add_fd, "\n"); + fflush(add_fd); + + /* Return */ + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS fetch_alias_info_to_ldif(TALLOC_CTX *mem_ctx, + struct netr_DELTA_ALIAS *r, + GROUPMAP *groupmap, + FILE *add_fd, + const char *sid, + const char *suffix, + enum netr_SamDatabaseID database_id) +{ + fstring aliasname, description; + uint32 grouptype = 0, g_rid = 0; + char *group_attr = sstring_sub(lp_ldap_group_suffix(), '=', ','); + + /* Get the alias name */ + fstrcpy(aliasname, r->alias_name.string); + + /* Get the alias description */ + fstrcpy(description, r->description.string); + + /* Set up the group type */ + switch (database_id) { + case SAM_DATABASE_DOMAIN: + grouptype = 4; + break; + case SAM_DATABASE_BUILTIN: + grouptype = 5; + break; + default: + grouptype = 4; + break; + } + + /* + These groups are entered by populate_ldap_for_ldif + Note that populate creates a group called Relicators, + but NT returns a group called Replicator + */ + if (strcmp(aliasname, "Domain Admins") == 0 || + strcmp(aliasname, "Domain Users") == 0 || + strcmp(aliasname, "Domain Guests") == 0 || + strcmp(aliasname, "Domain Computers") == 0 || + strcmp(aliasname, "Administrators") == 0 || + strcmp(aliasname, "Print Operators") == 0 || + strcmp(aliasname, "Backup Operators") == 0 || + strcmp(aliasname, "Replicator") == 0) { + SAFE_FREE(group_attr); + return NT_STATUS_OK; + } else { + /* Increment the gid for the new group */ + ldif_gid++; + } + + /* Map the group rid and gid */ + g_rid = r->rid; + groupmap->gidNumber = ldif_gid; + groupmap->sambaSID = talloc_asprintf(mem_ctx, "%s-%d", sid, g_rid); + NT_STATUS_HAVE_NO_MEMORY(groupmap->sambaSID); + + /* Write the data to the temporary add ldif file */ + fprintf(add_fd, "# %s, %s, %s\n", aliasname, group_attr, + suffix); + fprintf_attr(add_fd, "dn", "cn=%s,ou=%s,%s", aliasname, group_attr, + suffix); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_POSIXGROUP); + fprintf(add_fd, "objectClass: %s\n", LDAP_OBJ_GROUPMAP); + fprintf(add_fd, "cn: %s\n", aliasname); + fprintf(add_fd, "gidNumber: %d\n", ldif_gid); + fprintf(add_fd, "sambaSID: %s\n", groupmap->sambaSID); + fprintf(add_fd, "sambaGroupType: %d\n", grouptype); + fprintf_attr(add_fd, "displayName", "%s", aliasname); + if (description[0]) + fprintf_attr(add_fd, "description", "%s", description); + fprintf(add_fd, "\n"); + fflush(add_fd); + + SAFE_FREE(group_attr); + /* Return */ + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS fetch_groupmem_info_to_ldif(struct netr_DELTA_GROUP_MEMBER *r, + uint32_t id_rid, + GROUPMAP *groupmap, + ACCOUNTMAP *accountmap, + FILE *mod_fd, int alloced) +{ + fstring group_dn; + uint32 group_rid = 0, rid = 0; + int i, j, k; + + /* Get the dn for the group */ + if (r->num_rids > 0) { + group_rid = id_rid; + for (j=0; j<alloced; j++) { + if (groupmap[j].rid == group_rid) break; + } + if (j == alloced){ + DEBUG(1, ("Could not find rid %d in groupmap array\n", + group_rid)); + return NT_STATUS_UNSUCCESSFUL; + } + snprintf(group_dn, sizeof(group_dn), "%s", groupmap[j].group_dn); + fprintf(mod_fd, "dn: %s\n", group_dn); + + /* Get the cn for each member */ + for (i=0; i < r->num_rids; i++) { + rid = r->rids[i]; + for (k=0; k<alloced; k++) { + if (accountmap[k].rid == rid) break; + } + if (k == alloced){ + DEBUG(1, ("Could not find rid %d in " + "accountmap array\n", rid)); + return NT_STATUS_UNSUCCESSFUL; + } + fprintf(mod_fd, "memberUid: %s\n", accountmap[k].cn); + } + fprintf(mod_fd, "\n"); + } + fflush(mod_fd); + + /* Return */ + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS ldif_init_context(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + const char *ldif_filename, + const char *domain_sid_str, + struct samsync_ldif_context **ctx) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + struct samsync_ldif_context *r; + const char *add_template = "/tmp/add.ldif.XXXXXX"; + const char *mod_template = "/tmp/mod.ldif.XXXXXX"; + const char *builtin_sid = "S-1-5-32"; + + /* Get other smb.conf data */ + if (!(lp_workgroup()) || !*(lp_workgroup())) { + DEBUG(0,("workgroup missing from smb.conf--exiting\n")); + exit(1); + } + + /* Get the ldap suffix */ + if (!(lp_ldap_suffix()) || !*(lp_ldap_suffix())) { + DEBUG(0,("ldap suffix missing from smb.conf--exiting\n")); + exit(1); + } + + if (*ctx && (*ctx)->initialized) { + return NT_STATUS_OK; + } + + r = TALLOC_ZERO_P(mem_ctx, struct samsync_ldif_context); + NT_STATUS_HAVE_NO_MEMORY(r); + + /* Get the ldap suffix */ + r->suffix = lp_ldap_suffix(); + + /* Ensure we have an output file */ + if (ldif_filename) { + r->ldif_file = fopen(ldif_filename, "a"); + } else { + r->ldif_file = stdout; + } + + if (!r->ldif_file) { + fprintf(stderr, "Could not open %s\n", ldif_filename); + DEBUG(1, ("Could not open %s\n", ldif_filename)); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + r->add_template = talloc_strdup(mem_ctx, add_template); + r->mod_template = talloc_strdup(mem_ctx, mod_template); + if (!r->add_template || !r->mod_template) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + r->add_name = talloc_strdup(mem_ctx, add_template); + r->mod_name = talloc_strdup(mem_ctx, mod_template); + if (!r->add_name || !r->mod_name) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + /* Open the add and mod ldif files */ + if (!(r->add_file = fdopen(smb_mkstemp(r->add_name),"w"))) { + DEBUG(1, ("Could not open %s\n", r->add_name)); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + if (!(r->mod_file = fdopen(smb_mkstemp(r->mod_name),"w"))) { + DEBUG(1, ("Could not open %s\n", r->mod_name)); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + /* Allocate initial memory for groupmap and accountmap arrays */ + r->groupmap = TALLOC_ZERO_ARRAY(mem_ctx, GROUPMAP, 8); + r->accountmap = TALLOC_ZERO_ARRAY(mem_ctx, ACCOUNTMAP, 8); + if (r->groupmap == NULL || r->accountmap == NULL) { + DEBUG(1,("GROUPMAP talloc failed\n")); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + /* Remember how many we malloced */ + r->num_alloced = 8; + + /* Initial database population */ + if (database_id == SAM_DATABASE_DOMAIN) { + + status = populate_ldap_for_ldif(domain_sid_str, + r->suffix, + builtin_sid, + r->add_file); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = map_populate_groups(mem_ctx, + r->groupmap, + r->accountmap, + domain_sid_str, + r->suffix, + builtin_sid); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + } + + r->initialized = true; + + *ctx = r; + + return NT_STATUS_OK; + done: + TALLOC_FREE(r); + return status; +} + +/**************************************************************** +****************************************************************/ + +static void ldif_free_context(struct samsync_ldif_context *r) +{ + if (!r) { + return; + } + + /* Close and delete the ldif files */ + if (r->add_file) { + fclose(r->add_file); + } + + if ((r->add_name != NULL) && + strcmp(r->add_name, r->add_template) && (unlink(r->add_name))) { + DEBUG(1,("unlink(%s) failed, error was (%s)\n", + r->add_name, strerror(errno))); + } + + if (r->mod_file) { + fclose(r->mod_file); + } + + if ((r->mod_name != NULL) && + strcmp(r->mod_name, r->mod_template) && (unlink(r->mod_name))) { + DEBUG(1,("unlink(%s) failed, error was (%s)\n", + r->mod_name, strerror(errno))); + } + + if (r->ldif_file && (r->ldif_file != stdout)) { + fclose(r->ldif_file); + } + + TALLOC_FREE(r); +} + +/**************************************************************** +****************************************************************/ + +static void ldif_write_output(enum netr_SamDatabaseID database_id, + struct samsync_ldif_context *l) +{ + /* Write ldif data to the user's file */ + if (database_id == SAM_DATABASE_DOMAIN) { + fprintf(l->ldif_file, + "# SAM_DATABASE_DOMAIN: ADD ENTITIES\n"); + fprintf(l->ldif_file, + "# =================================\n\n"); + fflush(l->ldif_file); + } else if (database_id == SAM_DATABASE_BUILTIN) { + fprintf(l->ldif_file, + "# SAM_DATABASE_BUILTIN: ADD ENTITIES\n"); + fprintf(l->ldif_file, + "# ==================================\n\n"); + fflush(l->ldif_file); + } + fseek(l->add_file, 0, SEEK_SET); + transfer_file(fileno(l->add_file), fileno(l->ldif_file), (size_t) -1); + + if (database_id == SAM_DATABASE_DOMAIN) { + fprintf(l->ldif_file, + "# SAM_DATABASE_DOMAIN: MODIFY ENTITIES\n"); + fprintf(l->ldif_file, + "# ====================================\n\n"); + fflush(l->ldif_file); + } else if (database_id == SAM_DATABASE_BUILTIN) { + fprintf(l->ldif_file, + "# SAM_DATABASE_BUILTIN: MODIFY ENTITIES\n"); + fprintf(l->ldif_file, + "# =====================================\n\n"); + fflush(l->ldif_file); + } + fseek(l->mod_file, 0, SEEK_SET); + transfer_file(fileno(l->mod_file), fileno(l->ldif_file), (size_t) -1); +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS fetch_sam_entry_ldif(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM *r, + struct samsync_context *ctx, + uint32_t *a_index, + uint32_t *g_index) +{ + union netr_DELTA_UNION u = r->delta_union; + union netr_DELTA_ID_UNION id = r->delta_id_union; + struct samsync_ldif_context *l = + talloc_get_type_abort(ctx->private_data, struct samsync_ldif_context); + + switch (r->delta_type) { + case NETR_DELTA_DOMAIN: + break; + + case NETR_DELTA_GROUP: + fetch_group_info_to_ldif(mem_ctx, + u.group, + &l->groupmap[*g_index], + l->add_file, + ctx->domain_sid_str, + l->suffix); + (*g_index)++; + break; + + case NETR_DELTA_USER: + fetch_account_info_to_ldif(mem_ctx, + u.user, + l->groupmap, + &l->accountmap[*a_index], + l->add_file, + ctx->domain_sid_str, + l->suffix, + l->num_alloced); + (*a_index)++; + break; + + case NETR_DELTA_ALIAS: + fetch_alias_info_to_ldif(mem_ctx, + u.alias, + &l->groupmap[*g_index], + l->add_file, + ctx->domain_sid_str, + l->suffix, + database_id); + (*g_index)++; + break; + + case NETR_DELTA_GROUP_MEMBER: + fetch_groupmem_info_to_ldif(u.group_member, + id.rid, + l->groupmap, + l->accountmap, + l->mod_file, + l->num_alloced); + break; + + case NETR_DELTA_ALIAS_MEMBER: + case NETR_DELTA_POLICY: + case NETR_DELTA_ACCOUNT: + case NETR_DELTA_TRUSTED_DOMAIN: + case NETR_DELTA_SECRET: + case NETR_DELTA_RENAME_GROUP: + case NETR_DELTA_RENAME_USER: + case NETR_DELTA_RENAME_ALIAS: + case NETR_DELTA_DELETE_GROUP: + case NETR_DELTA_DELETE_USER: + case NETR_DELTA_MODIFY_COUNT: + default: + break; + } /* end of switch */ + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS ldif_realloc_maps(TALLOC_CTX *mem_ctx, + struct samsync_ldif_context *l, + uint32_t num_entries) +{ + /* Re-allocate memory for groupmap and accountmap arrays */ + l->groupmap = TALLOC_REALLOC_ARRAY(mem_ctx, + l->groupmap, + GROUPMAP, + num_entries + l->num_alloced); + + l->accountmap = TALLOC_REALLOC_ARRAY(mem_ctx, + l->accountmap, + ACCOUNTMAP, + num_entries + l->num_alloced); + + if (l->groupmap == NULL || l->accountmap == NULL) { + DEBUG(1,("GROUPMAP talloc failed\n")); + return NT_STATUS_NO_MEMORY; + } + + /* Initialize the new records */ + memset(&(l->groupmap[l->num_alloced]), 0, + sizeof(GROUPMAP) * num_entries); + memset(&(l->accountmap[l->num_alloced]), 0, + sizeof(ACCOUNTMAP) * num_entries); + + /* Remember how many we alloced this time */ + l->num_alloced += num_entries; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS fetch_sam_entries_ldif(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx) +{ + NTSTATUS status; + int i; + uint32_t g_index = 0, a_index = 0; + struct samsync_ldif_context *ldif_ctx = + (struct samsync_ldif_context *)ctx->private_data; + + status = ldif_init_context(mem_ctx, + database_id, + ctx->output_filename, + ctx->domain_sid_str, + &ldif_ctx); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + ctx->private_data = ldif_ctx; + + status = ldif_realloc_maps(mem_ctx, ldif_ctx, r->num_deltas); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + for (i = 0; i < r->num_deltas; i++) { + status = fetch_sam_entry_ldif(mem_ctx, database_id, + &r->delta_enum[i], ctx, + &a_index, &g_index); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + } + + /* This was the last query */ + if (last_query) { + ldif_write_output(database_id, ldif_ctx); + if (ldif_ctx->ldif_file != stdout) { + ctx->result_message = talloc_asprintf(mem_ctx, + "Vampired %d accounts and %d groups to %s", + a_index, g_index, ctx->output_filename); + } + ldif_free_context(ldif_ctx); + ctx->private_data = NULL; + } + + return NT_STATUS_OK; + + failed: + ldif_free_context(ldif_ctx); + ctx->private_data = NULL; + + return status; +} + +#else /* HAVE_LDAP */ + +NTSTATUS fetch_sam_entries_ldif(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +#endif diff --git a/source3/libnet/libnet_samsync_passdb.c b/source3/libnet/libnet_samsync_passdb.c new file mode 100644 index 0000000000..7d07bcb791 --- /dev/null +++ b/source3/libnet/libnet_samsync_passdb.c @@ -0,0 +1,789 @@ +/* + Unix SMB/CIFS implementation. + dump the remote SAM using rpc samsync operations + + Copyright (C) Andrew Tridgell 2002 + Copyright (C) Tim Potter 2001,2002 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2005 + Modified by Volker Lendecke 2002 + Copyright (C) Jeremy Allison 2005. + 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/>. +*/ + +#include "includes.h" +#include "libnet/libnet.h" + +/* 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_delta(struct samu *account, + struct netr_DELTA_USER *r) +{ + const char *old_string, *new_string; + time_t unix_time, stored_time; + uchar lm_passwd[16], nt_passwd[16]; + static uchar zero_buf[16]; + + /* Username, fullname, home dir, dir drive, logon script, acct + desc, workstations, profile. */ + + if (r->account_name.string) { + old_string = pdb_get_nt_username(account); + new_string = r->account_name.string; + + 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 (r->full_name.string) { + old_string = pdb_get_fullname(account); + new_string = r->full_name.string; + + if (STRING_CHANGED) + pdb_set_fullname(account, new_string, PDB_CHANGED); + } + + if (r->home_directory.string) { + old_string = pdb_get_homedir(account); + new_string = r->home_directory.string; + + if (STRING_CHANGED) + pdb_set_homedir(account, new_string, PDB_CHANGED); + } + + if (r->home_drive.string) { + old_string = pdb_get_dir_drive(account); + new_string = r->home_drive.string; + + if (STRING_CHANGED) + pdb_set_dir_drive(account, new_string, PDB_CHANGED); + } + + if (r->logon_script.string) { + old_string = pdb_get_logon_script(account); + new_string = r->logon_script.string; + + if (STRING_CHANGED) + pdb_set_logon_script(account, new_string, PDB_CHANGED); + } + + if (r->description.string) { + old_string = pdb_get_acct_desc(account); + new_string = r->description.string; + + if (STRING_CHANGED) + pdb_set_acct_desc(account, new_string, PDB_CHANGED); + } + + if (r->workstations.string) { + old_string = pdb_get_workstations(account); + new_string = r->workstations.string; + + if (STRING_CHANGED) + pdb_set_workstations(account, new_string, PDB_CHANGED); + } + + if (r->profile_path.string) { + old_string = pdb_get_profile_path(account); + new_string = r->profile_path.string; + + if (STRING_CHANGED) + pdb_set_profile_path(account, new_string, PDB_CHANGED); + } + + if (r->parameters.string) { + DATA_BLOB mung; + char *newstr; + old_string = pdb_get_munged_dial(account); + mung.length = r->parameters.length; + mung.data = (uint8 *) r->parameters.string; + newstr = (mung.length == 0) ? NULL : + base64_encode_data_blob(talloc_tos(), mung); + + if (STRING_CHANGED_NC(old_string, newstr)) + pdb_set_munged_dial(account, newstr, PDB_CHANGED); + TALLOC_FREE(newstr); + } + + /* User and group sid */ + if (pdb_get_user_rid(account) != r->rid) + pdb_set_user_sid_from_rid(account, r->rid, PDB_CHANGED); + if (pdb_get_group_rid(account) != r->primary_gid) + pdb_set_group_sid_from_rid(account, r->primary_gid, PDB_CHANGED); + + /* Logon and password information */ + if (!nt_time_is_zero(&r->last_logon)) { + unix_time = nt_time_to_unix(r->last_logon); + 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(&r->last_logoff)) { + unix_time = nt_time_to_unix(r->last_logoff); + stored_time = pdb_get_logoff_time(account); + if (stored_time != unix_time) + pdb_set_logoff_time(account, unix_time,PDB_CHANGED); + } + + /* Logon Divs */ + if (pdb_get_logon_divs(account) != r->logon_hours.units_per_week) + pdb_set_logon_divs(account, r->logon_hours.units_per_week, PDB_CHANGED); + +#if 0 + /* no idea what to do with this one - gd */ + /* Max Logon Hours */ + if (delta->unknown1 != pdb_get_unknown_6(account)) { + pdb_set_unknown_6(account, delta->unknown1, PDB_CHANGED); + } +#endif + /* Logon Hours Len */ + if (r->logon_hours.units_per_week/8 != pdb_get_hours_len(account)) { + pdb_set_hours_len(account, r->logon_hours.units_per_week/8, PDB_CHANGED); + } + + /* Logon Hours */ + if (r->logon_hours.bits) { + char oldstr[44], newstr[44]; + pdb_sethexhours(oldstr, pdb_get_hours(account)); + pdb_sethexhours(newstr, r->logon_hours.bits); + if (!strequal(oldstr, newstr)) + pdb_set_hours(account, r->logon_hours.bits, PDB_CHANGED); + } + + if (pdb_get_bad_password_count(account) != r->bad_password_count) + pdb_set_bad_password_count(account, r->bad_password_count, PDB_CHANGED); + + if (pdb_get_logon_count(account) != r->logon_count) + pdb_set_logon_count(account, r->logon_count, PDB_CHANGED); + + if (!nt_time_is_zero(&r->last_password_change)) { + unix_time = nt_time_to_unix(r->last_password_change); + 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(&r->acct_expiry)) { + unix_time = nt_time_to_unix(r->acct_expiry); + 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 (memcmp(r->ntpassword.hash, zero_buf, 16) != 0) { + sam_pwd_hash(r->rid, r->ntpassword.hash, lm_passwd, 0); + pdb_set_lanman_passwd(account, lm_passwd, PDB_CHANGED); + } + + if (memcmp(r->lmpassword.hash, zero_buf, 16) != 0) { + sam_pwd_hash(r->rid, r->lmpassword.hash, nt_passwd, 0); + pdb_set_nt_passwd(account, nt_passwd, PDB_CHANGED); + } + + /* TODO: account expiry time */ + + pdb_set_acct_ctrl(account, r->acct_flags, PDB_CHANGED); + + pdb_set_domain(account, lp_workgroup(), PDB_CHANGED); + + return NT_STATUS_OK; +} + +static NTSTATUS fetch_account_info(uint32_t rid, + struct netr_DELTA_USER *r) +{ + + NTSTATUS nt_ret = NT_STATUS_UNSUCCESSFUL; + fstring account; + char *add_script = NULL; + struct samu *sam_account=NULL; + GROUP_MAP map; + struct group *grp; + DOM_SID user_sid; + DOM_SID group_sid; + struct passwd *passwd; + fstring sid_string; + + fstrcpy(account, r->account_name.string); + d_printf("Creating account: %s\n", account); + + if ( !(sam_account = samu_new( NULL )) ) { + return NT_STATUS_NO_MEMORY; + } + + if (!(passwd = Get_Pwnam_alloc(sam_account, account))) { + /* Create appropriate user */ + if (r->acct_flags & ACB_NORMAL) { + add_script = talloc_strdup(sam_account, + lp_adduser_script()); + } else if ( (r->acct_flags & ACB_WSTRUST) || + (r->acct_flags & ACB_SVRTRUST) || + (r->acct_flags & ACB_DOMTRUST) ) { + add_script = talloc_strdup(sam_account, + lp_addmachine_script()); + } else { + DEBUG(1, ("Unknown user type: %s\n", + pdb_encode_acct_ctrl(r->acct_flags, NEW_PW_FORMAT_SPACE_PADDED_LEN))); + nt_ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + if (!add_script) { + nt_ret = NT_STATUS_NO_MEMORY; + goto done; + } + if (*add_script) { + int add_ret; + add_script = talloc_all_string_sub(sam_account, + add_script, + "%u", + account); + if (!add_script) { + nt_ret = NT_STATUS_NO_MEMORY; + goto done; + } + add_ret = smbrun(add_script,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 */ + if ( !(passwd = Get_Pwnam_alloc(sam_account, account)) ) { + d_fprintf(stderr, "Could not create posix account info for '%s'\n", account); + nt_ret = NT_STATUS_NO_SUCH_USER; + goto done; + } + } + + sid_copy(&user_sid, get_global_sam_sid()); + sid_append_rid(&user_sid, r->rid); + + DEBUG(3, ("Attempting to find SID %s for user %s in the passdb\n", + sid_to_fstring(sid_string, &user_sid), account)); + if (!pdb_getsampwsid(sam_account, &user_sid)) { + sam_account_from_delta(sam_account, r); + DEBUG(3, ("Attempting to add user SID %s for user %s in the passdb\n", + sid_to_fstring(sid_string, &user_sid), + 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)); + return NT_STATUS_ACCESS_DENIED; + } + } else { + sam_account_from_delta(sam_account, r); + DEBUG(3, ("Attempting to update user SID %s for user %s in the passdb\n", + sid_to_fstring(sid_string, &user_sid), + 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) { + return NT_STATUS_UNSUCCESSFUL; + } + + group_sid = *pdb_get_group_sid(sam_account); + + 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), sid_string_tos(&group_sid))); + } else { + smb_set_primary_group(grp->gr_name, pdb_get_username(sam_account)); + } + } + } + + if ( !passwd ) { + DEBUG(1, ("No unix user for this account (%s), cannot adjust mappings\n", + pdb_get_username(sam_account))); + } + + done: + TALLOC_FREE(sam_account); + return nt_ret; +} + +static NTSTATUS fetch_group_info(uint32_t rid, + struct netr_DELTA_GROUP *r) +{ + fstring name; + fstring comment; + struct group *grp = NULL; + DOM_SID group_sid; + fstring sid_string; + GROUP_MAP map; + bool insert = true; + + fstrcpy(name, r->group_name.string); + fstrcpy(comment, r->description.string); + + /* add the group to the mapping table */ + sid_copy(&group_sid, get_global_sam_sid()); + sid_append_rid(&group_sid, rid); + sid_to_fstring(sid_string, &group_sid); + + 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(name)) == NULL) { + + /* No appropriate group found, create one */ + + d_printf("Creating unix group: '%s'\n", name); + + if (smb_create_group(name, &gid) != 0) + return NT_STATUS_ACCESS_DENIED; + + if ((grp = getgrnam(name)) == NULL) + return NT_STATUS_ACCESS_DENIED; + } + } + + map.gid = grp->gr_gid; + map.sid = group_sid; + map.sid_name_use = SID_NAME_DOM_GRP; + fstrcpy(map.nt_name, name); + if (r->description.string) { + fstrcpy(map.comment, comment); + } else { + fstrcpy(map.comment, ""); + } + + if (insert) + pdb_add_group_mapping_entry(&map); + else + pdb_update_group_mapping_entry(&map); + + return NT_STATUS_OK; +} + +static NTSTATUS fetch_group_mem_info(uint32_t rid, + struct netr_DELTA_GROUP_MEMBER *r) +{ + int i; + TALLOC_CTX *t = NULL; + char **nt_members = NULL; + char **unix_members; + DOM_SID group_sid; + GROUP_MAP map; + struct group *grp; + + if (r->num_rids == 0) { + return NT_STATUS_OK; + } + + sid_copy(&group_sid, get_global_sam_sid()); + sid_append_rid(&group_sid, rid); + + if (!get_domain_group_from_sid(group_sid, &map)) { + DEBUG(0, ("Could not find global group %d\n", rid)); + return NT_STATUS_NO_SUCH_GROUP; + } + + if (!(grp = getgrgid(map.gid))) { + DEBUG(0, ("Could not find unix group %lu\n", (unsigned long)map.gid)); + return NT_STATUS_NO_SUCH_GROUP; + } + + d_printf("Group members of %s: ", grp->gr_name); + + if (!(t = talloc_init("fetch_group_mem_info"))) { + DEBUG(0, ("could not talloc_init\n")); + return NT_STATUS_NO_MEMORY; + } + + if (r->num_rids) { + if ((nt_members = TALLOC_ZERO_ARRAY(t, char *, r->num_rids)) == NULL) { + DEBUG(0, ("talloc failed\n")); + talloc_free(t); + return NT_STATUS_NO_MEMORY; + } + } else { + nt_members = NULL; + } + + for (i=0; i < r->num_rids; i++) { + struct samu *member = NULL; + DOM_SID member_sid; + + if ( !(member = samu_new(t)) ) { + talloc_destroy(t); + return NT_STATUS_NO_MEMORY; + } + + sid_copy(&member_sid, get_global_sam_sid()); + sid_append_rid(&member_sid, r->rids[i]); + + if (!pdb_getsampwsid(member, &member_sid)) { + DEBUG(1, ("Found bogus group member: %d (member_sid=%s group=%s)\n", + r->rids[i], sid_string_tos(&member_sid), grp->gr_name)); + TALLOC_FREE(member); + continue; + } + + if (pdb_get_group_rid(member) == rid) { + d_printf("%s(primary),", pdb_get_username(member)); + TALLOC_FREE(member); + continue; + } + + d_printf("%s,", pdb_get_username(member)); + nt_members[i] = talloc_strdup(t, pdb_get_username(member)); + TALLOC_FREE(member); + } + + d_printf("\n"); + + unix_members = grp->gr_mem; + + while (*unix_members) { + bool is_nt_member = false; + for (i=0; i < r->num_rids; i++) { + if (nt_members[i] == NULL) { + /* This was a primary group */ + continue; + } + + if (strcmp(*unix_members, nt_members[i]) == 0) { + is_nt_member = true; + break; + } + } + if (!is_nt_member) { + /* We look at a unix group member that is not + an nt group member. So, remove it. NT is + boss here. */ + smb_delete_user_group(grp->gr_name, *unix_members); + } + unix_members += 1; + } + + for (i=0; i < r->num_rids; i++) { + bool is_unix_member = false; + + if (nt_members[i] == NULL) { + /* This was the primary group */ + continue; + } + + unix_members = grp->gr_mem; + + while (*unix_members) { + if (strcmp(*unix_members, nt_members[i]) == 0) { + is_unix_member = true; + break; + } + unix_members += 1; + } + + if (!is_unix_member) { + /* We look at a nt group member that is not a + unix group member currently. So, add the nt + group member. */ + smb_add_user_group(grp->gr_name, nt_members[i]); + } + } + + talloc_destroy(t); + return NT_STATUS_OK; +} + +static NTSTATUS fetch_alias_info(uint32_t rid, + struct netr_DELTA_ALIAS *r, + const DOM_SID *dom_sid) +{ + fstring name; + fstring comment; + struct group *grp = NULL; + DOM_SID alias_sid; + fstring sid_string; + GROUP_MAP map; + bool insert = true; + + fstrcpy(name, r->alias_name.string); + fstrcpy(comment, r->description.string); + + /* Find out whether the group is already mapped */ + sid_copy(&alias_sid, dom_sid); + sid_append_rid(&alias_sid, rid); + sid_to_fstring(sid_string, &alias_sid); + + if (pdb_getgrsid(&map, alias_sid)) { + 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(name)) == NULL) { + /* No appropriate group found, create one */ + d_printf("Creating unix group: '%s'\n", name); + if (smb_create_group(name, &gid) != 0) + return NT_STATUS_ACCESS_DENIED; + if ((grp = getgrgid(gid)) == NULL) + return NT_STATUS_ACCESS_DENIED; + } + } + + map.gid = grp->gr_gid; + map.sid = alias_sid; + + if (sid_equal(dom_sid, &global_sid_Builtin)) + map.sid_name_use = SID_NAME_WKN_GRP; + else + map.sid_name_use = SID_NAME_ALIAS; + + fstrcpy(map.nt_name, name); + fstrcpy(map.comment, comment); + + if (insert) + pdb_add_group_mapping_entry(&map); + else + pdb_update_group_mapping_entry(&map); + + return NT_STATUS_OK; +} + +static NTSTATUS fetch_alias_mem(uint32_t rid, + struct netr_DELTA_ALIAS_MEMBER *r, + const DOM_SID *dom_sid) +{ + return NT_STATUS_OK; +} + +static NTSTATUS fetch_domain_info(uint32_t rid, + struct netr_DELTA_DOMAIN *r) +{ + time_t u_max_age, u_min_age, u_logout; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + const char *domname; + struct netr_AcctLockStr *lockstr = NULL; + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_tos(); + + status = pull_netr_AcctLockStr(mem_ctx, &r->account_lockout, + &lockstr); + if (!NT_STATUS_IS_OK(status)) { + d_printf("failed to pull account lockout string: %s\n", + nt_errstr(status)); + } + + u_max_age = uint64s_nt_time_to_unix_abs((uint64 *)&r->max_password_age); + u_min_age = uint64s_nt_time_to_unix_abs((uint64 *)&r->min_password_age); + u_logout = uint64s_nt_time_to_unix_abs((uint64 *)&r->force_logoff_time); + + domname = r->domain_name.string; + if (!domname) { + return NT_STATUS_NO_MEMORY; + } + + /* we don't handle BUILTIN account policies */ + if (!strequal(domname, get_global_sam_name())) { + printf("skipping SAM_DOMAIN_INFO delta for '%s' (is not my domain)\n", domname); + return NT_STATUS_OK; + } + + + if (!pdb_set_account_policy(AP_PASSWORD_HISTORY, + r->password_history_length)) + return nt_status; + + if (!pdb_set_account_policy(AP_MIN_PASSWORD_LEN, + r->min_password_length)) + return nt_status; + + if (!pdb_set_account_policy(AP_MAX_PASSWORD_AGE, (uint32)u_max_age)) + return nt_status; + + if (!pdb_set_account_policy(AP_MIN_PASSWORD_AGE, (uint32)u_min_age)) + return nt_status; + + if (!pdb_set_account_policy(AP_TIME_TO_LOGOUT, (uint32)u_logout)) + return nt_status; + + if (lockstr) { + time_t u_lockoutreset, u_lockouttime; + + u_lockoutreset = uint64s_nt_time_to_unix_abs(&lockstr->reset_count); + u_lockouttime = uint64s_nt_time_to_unix_abs((uint64_t *)&lockstr->lockout_duration); + + if (!pdb_set_account_policy(AP_BAD_ATTEMPT_LOCKOUT, + lockstr->bad_attempt_lockout)) + return nt_status; + + if (!pdb_set_account_policy(AP_RESET_COUNT_TIME, (uint32_t)u_lockoutreset/60)) + return nt_status; + + if (u_lockouttime != -1) + u_lockouttime /= 60; + + if (!pdb_set_account_policy(AP_LOCK_ACCOUNT_DURATION, (uint32_t)u_lockouttime)) + return nt_status; + } + + if (!pdb_set_account_policy(AP_USER_MUST_LOGON_TO_CHG_PASS, + r->logon_to_chgpass)) + return nt_status; + + return NT_STATUS_OK; +} + +static NTSTATUS fetch_sam_entry(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM *r, + struct samsync_context *ctx) +{ + switch(r->delta_type) { + case NETR_DELTA_USER: + fetch_account_info(r->delta_id_union.rid, + r->delta_union.user); + break; + case NETR_DELTA_GROUP: + fetch_group_info(r->delta_id_union.rid, + r->delta_union.group); + break; + case NETR_DELTA_GROUP_MEMBER: + fetch_group_mem_info(r->delta_id_union.rid, + r->delta_union.group_member); + break; + case NETR_DELTA_ALIAS: + fetch_alias_info(r->delta_id_union.rid, + r->delta_union.alias, + ctx->domain_sid); + break; + case NETR_DELTA_ALIAS_MEMBER: + fetch_alias_mem(r->delta_id_union.rid, + r->delta_union.alias_member, + ctx->domain_sid); + break; + case NETR_DELTA_DOMAIN: + fetch_domain_info(r->delta_id_union.rid, + r->delta_union.domain); + break; + /* The following types are recognised but not handled */ + case NETR_DELTA_RENAME_GROUP: + d_printf("NETR_DELTA_RENAME_GROUP not handled\n"); + break; + case NETR_DELTA_RENAME_USER: + d_printf("NETR_DELTA_RENAME_USER not handled\n"); + break; + case NETR_DELTA_RENAME_ALIAS: + d_printf("NETR_DELTA_RENAME_ALIAS not handled\n"); + break; + case NETR_DELTA_POLICY: + d_printf("NETR_DELTA_POLICY not handled\n"); + break; + case NETR_DELTA_TRUSTED_DOMAIN: + d_printf("NETR_DELTA_TRUSTED_DOMAIN not handled\n"); + break; + case NETR_DELTA_ACCOUNT: + d_printf("NETR_DELTA_ACCOUNT not handled\n"); + break; + case NETR_DELTA_SECRET: + d_printf("NETR_DELTA_SECRET not handled\n"); + break; + case NETR_DELTA_DELETE_GROUP: + d_printf("NETR_DELTA_DELETE_GROUP not handled\n"); + break; + case NETR_DELTA_DELETE_USER: + d_printf("NETR_DELTA_DELETE_USER not handled\n"); + break; + case NETR_DELTA_MODIFY_COUNT: + d_printf("NETR_DELTA_MODIFY_COUNT not handled\n"); + break; + case NETR_DELTA_DELETE_ALIAS: + d_printf("NETR_DELTA_DELETE_ALIAS not handled\n"); + break; + case NETR_DELTA_DELETE_TRUST: + d_printf("NETR_DELTA_DELETE_TRUST not handled\n"); + break; + case NETR_DELTA_DELETE_ACCOUNT: + d_printf("NETR_DELTA_DELETE_ACCOUNT not handled\n"); + break; + case NETR_DELTA_DELETE_SECRET: + d_printf("NETR_DELTA_DELETE_SECRET not handled\n"); + break; + case NETR_DELTA_DELETE_GROUP2: + d_printf("NETR_DELTA_DELETE_GROUP2 not handled\n"); + break; + case NETR_DELTA_DELETE_USER2: + d_printf("NETR_DELTA_DELETE_USER2 not handled\n"); + break; + default: + d_printf("Unknown delta record type %d\n", r->delta_type); + break; + } + + return NT_STATUS_OK; +} + +NTSTATUS fetch_sam_entries(TALLOC_CTX *mem_ctx, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM_ARRAY *r, + bool last_query, + struct samsync_context *ctx) +{ + int i; + + for (i = 0; i < r->num_deltas; i++) { + fetch_sam_entry(mem_ctx, database_id, &r->delta_enum[i], ctx); + } + + return NT_STATUS_OK; +} |