diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /source4/libnet | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
45 files changed, 17356 insertions, 0 deletions
diff --git a/source4/libnet/composite.h b/source4/libnet/composite.h new file mode 100644 index 0000000..50bf1a7 --- /dev/null +++ b/source4/libnet/composite.h @@ -0,0 +1,56 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + 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/>. +*/ + +/* + * Monitor structure and message types definitions. Composite function monitoring + * allows client application to be notified on function progress. This enables + * eg. gui client to display progress bars, status messages, etc. + */ + + +#define mon_SamrCreateUser (0x00000001) +#define mon_SamrOpenUser (0x00000002) +#define mon_SamrQueryUser (0x00000003) +#define mon_SamrCloseUser (0x00000004) +#define mon_SamrLookupName (0x00000005) +#define mon_SamrDeleteUser (0x00000006) +#define mon_SamrSetUser (0x00000007) +#define mon_SamrClose (0x00000008) +#define mon_SamrConnect (0x00000009) +#define mon_SamrLookupDomain (0x0000000A) +#define mon_SamrOpenDomain (0x0000000B) +#define mon_SamrEnumDomains (0x0000000C) +#define mon_LsaOpenPolicy (0x0000000D) +#define mon_LsaQueryPolicy (0x0000000E) +#define mon_LsaClose (0x0000000F) +#define mon_SamrOpenGroup (0x00000010) +#define mon_SamrQueryGroup (0x00000011) + +#define mon_NetLookupDc (0x00000100) +#define mon_NetRpcConnect (0x00000200) + +#define mon_Mask_Rpc (0x000000FF) +#define mon_Mask_Net (0x0000FF00) + + +struct monitor_msg { + uint32_t type; + void *data; + size_t data_size; +}; diff --git a/source4/libnet/groupinfo.c b/source4/libnet/groupinfo.c new file mode 100644 index 0000000..3d2968b --- /dev/null +++ b/source4/libnet/groupinfo.c @@ -0,0 +1,384 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 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/>. +*/ + +/* + a composite function for getting group information via samr pipe +*/ + + +#include "includes.h" +#include "libcli/composite/composite.h" +#include "librpc/gen_ndr/security.h" +#include "libcli/security/security.h" +#include "libnet/libnet.h" +#include "librpc/gen_ndr/ndr_samr_c.h" + + +struct groupinfo_state { + struct dcerpc_binding_handle *binding_handle; + struct policy_handle domain_handle; + struct policy_handle group_handle; + uint16_t level; + struct samr_LookupNames lookup; + struct samr_OpenGroup opengroup; + struct samr_QueryGroupInfo querygroupinfo; + struct samr_Close samrclose; + union samr_GroupInfo *info; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_groupinfo_lookup(struct tevent_req *subreq); +static void continue_groupinfo_opengroup(struct tevent_req *subreq); +static void continue_groupinfo_getgroup(struct tevent_req *subreq); +static void continue_groupinfo_closegroup(struct tevent_req *subreq); + + +/** + * Stage 1 (optional): Look for a group name in SAM server. + */ +static void continue_groupinfo_lookup(struct tevent_req *subreq) +{ + struct composite_context *c; + struct groupinfo_state *s; + struct monitor_msg msg; + struct msg_rpc_lookup_name *msg_lookup; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct groupinfo_state); + + /* receive samr_Lookup reply */ + c->status = dcerpc_samr_LookupNames_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* there could be a problem with name resolving itself */ + if (!NT_STATUS_IS_OK(s->lookup.out.result)) { + composite_error(c, s->lookup.out.result); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + msg.type = mon_SamrLookupName; + msg_lookup = talloc(s, struct msg_rpc_lookup_name); + msg_lookup->rid = s->lookup.out.rids->ids; + msg_lookup->count = s->lookup.out.rids->count; + msg.data = (void*)msg_lookup; + msg.data_size = sizeof(*msg_lookup); + + s->monitor_fn(&msg); + } + + /* have we actually got name resolved + - we're looking for only one at the moment */ + if (s->lookup.out.rids->count != s->lookup.in.num_names) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + if (s->lookup.out.types->count != s->lookup.in.num_names) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* TODO: find proper status code for more than one rid found */ + + /* prepare parameters for LookupNames */ + s->opengroup.in.domain_handle = &s->domain_handle; + s->opengroup.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + s->opengroup.in.rid = s->lookup.out.rids->ids[0]; + s->opengroup.out.group_handle = &s->group_handle; + + /* send request */ + subreq = dcerpc_samr_OpenGroup_r_send(s, c->event_ctx, + s->binding_handle, + &s->opengroup); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_groupinfo_opengroup, c); +} + + +/** + * Stage 2: Open group policy handle. + */ +static void continue_groupinfo_opengroup(struct tevent_req *subreq) +{ + struct composite_context *c; + struct groupinfo_state *s; + struct monitor_msg msg; + struct msg_rpc_open_group *msg_open; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct groupinfo_state); + + /* receive samr_OpenGroup reply */ + c->status = dcerpc_samr_OpenGroup_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!NT_STATUS_IS_OK(s->opengroup.out.result)) { + composite_error(c, s->opengroup.out.result); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + msg.type = mon_SamrOpenGroup; + msg_open = talloc(s, struct msg_rpc_open_group); + msg_open->rid = s->opengroup.in.rid; + msg_open->access_mask = s->opengroup.in.access_mask; + msg.data = (void*)msg_open; + msg.data_size = sizeof(*msg_open); + + s->monitor_fn(&msg); + } + + /* prepare parameters for QueryGroupInfo call */ + s->querygroupinfo.in.group_handle = &s->group_handle; + s->querygroupinfo.in.level = s->level; + s->querygroupinfo.out.info = talloc(s, union samr_GroupInfo *); + if (composite_nomem(s->querygroupinfo.out.info, c)) return; + + /* queue rpc call, set event handling and new state */ + subreq = dcerpc_samr_QueryGroupInfo_r_send(s, + c->event_ctx, + s->binding_handle, + &s->querygroupinfo); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_groupinfo_getgroup, c); +} + + +/** + * Stage 3: Get requested group information. + */ +static void continue_groupinfo_getgroup(struct tevent_req *subreq) +{ + struct composite_context *c; + struct groupinfo_state *s; + struct monitor_msg msg; + struct msg_rpc_query_group *msg_query; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct groupinfo_state); + + /* receive samr_QueryGroupInfo reply */ + c->status = dcerpc_samr_QueryGroupInfo_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* check if querygroup itself went ok */ + if (!NT_STATUS_IS_OK(s->querygroupinfo.out.result)) { + composite_error(c, s->querygroupinfo.out.result); + return; + } + + s->info = talloc_steal(s, *s->querygroupinfo.out.info); + + /* issue a monitor message */ + if (s->monitor_fn) { + msg.type = mon_SamrQueryGroup; + msg_query = talloc(s, struct msg_rpc_query_group); + msg_query->level = s->querygroupinfo.in.level; + msg.data = (void*)msg_query; + msg.data_size = sizeof(*msg_query); + + s->monitor_fn(&msg); + } + + /* prepare arguments for Close call */ + s->samrclose.in.handle = &s->group_handle; + s->samrclose.out.handle = &s->group_handle; + + /* queue rpc call, set event handling and new state */ + subreq = dcerpc_samr_Close_r_send(s, c->event_ctx, + s->binding_handle, + &s->samrclose); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_groupinfo_closegroup, c); +} + + +/** + * Stage 4: Close policy handle associated with opened group. + */ +static void continue_groupinfo_closegroup(struct tevent_req *subreq) +{ + struct composite_context *c; + struct groupinfo_state *s; + struct monitor_msg msg; + struct msg_rpc_close_group *msg_close; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct groupinfo_state); + + /* receive samr_Close reply */ + c->status = dcerpc_samr_Close_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!NT_STATUS_IS_OK(s->samrclose.out.result)) { + composite_error(c, s->samrclose.out.result); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + msg.type = mon_SamrClose; + msg_close = talloc(s, struct msg_rpc_close_group); + msg_close->rid = s->opengroup.in.rid; + msg.data = (void*)msg_close; + msg.data_size = sizeof(*msg_close); + + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +/** + * Sends asynchronous groupinfo request + * + * @param p dce/rpc call pipe + * @param io arguments and results of the call + */ +struct composite_context *libnet_rpc_groupinfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *b, + struct libnet_rpc_groupinfo *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct groupinfo_state *s; + struct dom_sid *sid; + struct tevent_req *subreq; + + if (!b || !io) return NULL; + + c = composite_create(mem_ctx, ev); + if (c == NULL) return c; + + s = talloc_zero(c, struct groupinfo_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + s->level = io->in.level; + s->binding_handle= b; + s->domain_handle = io->in.domain_handle; + s->monitor_fn = monitor; + + if (io->in.sid) { + sid = dom_sid_parse_talloc(s, io->in.sid); + if (composite_nomem(sid, c)) return c; + + s->opengroup.in.domain_handle = &s->domain_handle; + s->opengroup.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + s->opengroup.in.rid = sid->sub_auths[sid->num_auths - 1]; + s->opengroup.out.group_handle = &s->group_handle; + + /* send request */ + subreq = dcerpc_samr_OpenGroup_r_send(s, c->event_ctx, + s->binding_handle, + &s->opengroup); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_groupinfo_opengroup, c); + + } else { + /* preparing parameters to send rpc request */ + s->lookup.in.domain_handle = &s->domain_handle; + s->lookup.in.num_names = 1; + s->lookup.in.names = talloc_array(s, struct lsa_String, 1); + if (composite_nomem(s->lookup.in.names, c)) return c; + + s->lookup.in.names[0].string = talloc_strdup(s, io->in.groupname); + if (composite_nomem(s->lookup.in.names[0].string, c)) return c; + s->lookup.out.rids = talloc_zero(s, struct samr_Ids); + s->lookup.out.types = talloc_zero(s, struct samr_Ids); + if (composite_nomem(s->lookup.out.rids, c)) return c; + if (composite_nomem(s->lookup.out.types, c)) return c; + + /* send request */ + subreq = dcerpc_samr_LookupNames_r_send(s, c->event_ctx, + s->binding_handle, + &s->lookup); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_groupinfo_lookup, c); + } + + return c; +} + + +/** + * Waits for and receives result of asynchronous groupinfo call + * + * @param c composite context returned by asynchronous groupinfo call + * @param mem_ctx memory context of the call + * @param io pointer to results (and arguments) of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_groupinfo_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_rpc_groupinfo *io) +{ + NTSTATUS status; + struct groupinfo_state *s; + + /* wait for results of sending request */ + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status) && io) { + s = talloc_get_type(c->private_data, struct groupinfo_state); + talloc_steal(mem_ctx, s->info); + io->out.info = *s->info; + } + + /* memory context associated to composite context is no longer needed */ + talloc_free(c); + return status; +} + + +/** + * Synchronous version of groupinfo call + * + * @param pipe dce/rpc call pipe + * @param mem_ctx memory context for the call + * @param io arguments and results of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_groupinfo(struct tevent_context *ev, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct libnet_rpc_groupinfo *io) +{ + struct composite_context *c = libnet_rpc_groupinfo_send(mem_ctx, ev, b, + io, NULL); + return libnet_rpc_groupinfo_recv(c, mem_ctx, io); +} diff --git a/source4/libnet/groupinfo.h b/source4/libnet/groupinfo.h new file mode 100644 index 0000000..ad13840 --- /dev/null +++ b/source4/libnet/groupinfo.h @@ -0,0 +1,54 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 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/>. +*/ + + +#include "librpc/gen_ndr/samr.h" + +/* + * IO structures for groupinfo.c functions + */ + +struct libnet_rpc_groupinfo { + struct { + struct policy_handle domain_handle; + const char *groupname; + const char *sid; + uint16_t level; + } in; + struct { + union samr_GroupInfo info; + } out; +}; + + +/* + * Monitor messages sent from groupinfo.c functions + */ + +struct msg_rpc_open_group { + uint32_t rid, access_mask; +}; + +struct msg_rpc_query_group { + uint16_t level; +}; + +struct msg_rpc_close_group { + uint32_t rid; +}; diff --git a/source4/libnet/groupman.c b/source4/libnet/groupman.c new file mode 100644 index 0000000..c91eff3 --- /dev/null +++ b/source4/libnet/groupman.c @@ -0,0 +1,139 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 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/>. +*/ + +/* + a composite function for manipulating (add/edit/del) groups via samr pipe +*/ + +#include "includes.h" +#include "libcli/composite/composite.h" +#include "libnet/libnet.h" +#include "librpc/gen_ndr/ndr_samr_c.h" + + +struct groupadd_state { + struct dcerpc_binding_handle *binding_handle; + struct policy_handle domain_handle; + struct samr_CreateDomainGroup creategroup; + struct policy_handle group_handle; + uint32_t group_rid; + + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_groupadd_created(struct tevent_req *subreq); + + +struct composite_context* libnet_rpc_groupadd_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *b, + struct libnet_rpc_groupadd *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct groupadd_state *s; + struct tevent_req *subreq; + + if (!b || !io) return NULL; + + c = composite_create(mem_ctx, ev); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct groupadd_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + s->domain_handle = io->in.domain_handle; + s->binding_handle= b; + s->monitor_fn = monitor; + + s->creategroup.in.domain_handle = &s->domain_handle; + + s->creategroup.in.name = talloc_zero(c, struct lsa_String); + if (composite_nomem(s->creategroup.in.name, c)) return c; + + s->creategroup.in.name->string = talloc_strdup(c, io->in.groupname); + if (composite_nomem(s->creategroup.in.name->string, c)) return c; + + s->creategroup.in.access_mask = 0; + + s->creategroup.out.group_handle = &s->group_handle; + s->creategroup.out.rid = &s->group_rid; + + subreq = dcerpc_samr_CreateDomainGroup_r_send(s, c->event_ctx, + s->binding_handle, + &s->creategroup); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_groupadd_created, c); + return c; +} + + +NTSTATUS libnet_rpc_groupadd_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_rpc_groupadd *io) +{ + NTSTATUS status; + struct groupadd_state *s; + + status = composite_wait(c); + if (NT_STATUS_IS_OK(status) && io) { + s = talloc_get_type(c->private_data, struct groupadd_state); + io->out.group_handle = s->group_handle; + } + + talloc_free(c); + return status; +} + + +static void continue_groupadd_created(struct tevent_req *subreq) +{ + struct composite_context *c; + struct groupadd_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct groupadd_state); + + c->status = dcerpc_samr_CreateDomainGroup_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + c->status = s->creategroup.out.result; + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + composite_done(c); +} + + +NTSTATUS libnet_rpc_groupadd(struct tevent_context *ev, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct libnet_rpc_groupadd *io) +{ + struct composite_context *c; + + c = libnet_rpc_groupadd_send(mem_ctx, ev, b, io, NULL); + return libnet_rpc_groupadd_recv(c, mem_ctx, io); +} diff --git a/source4/libnet/groupman.h b/source4/libnet/groupman.h new file mode 100644 index 0000000..0acb02d --- /dev/null +++ b/source4/libnet/groupman.h @@ -0,0 +1,35 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 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/>. +*/ + +#include "librpc/gen_ndr/misc.h" + + +/* + * IO structures for groupman.c functions + */ + +struct libnet_rpc_groupadd { + struct { + struct policy_handle domain_handle; + const char *groupname; + } in; + struct { + struct policy_handle group_handle; + } out; +}; diff --git a/source4/libnet/libnet.c b/source4/libnet/libnet.c new file mode 100644 index 0000000..a590893 --- /dev/null +++ b/source4/libnet/libnet.c @@ -0,0 +1,55 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + + 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 "lib/events/events.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" + +struct libnet_context *libnet_context_init(struct tevent_context *ev, + struct loadparm_context *lp_ctx) +{ + struct libnet_context *ctx; + + /* We require an event context here */ + if (!ev) { + return NULL; + } + + /* create brand new libnet context */ + ctx = talloc_zero(ev, struct libnet_context); + if (!ctx) { + return NULL; + } + + ctx->event_ctx = ev; + ctx->lp_ctx = lp_ctx; + + /* make sure dcerpc is initialized */ + dcerpc_init(); + + /* name resolution methods */ + ctx->resolve_ctx = lpcfg_resolve_context(lp_ctx); + + /* default buffer size for various operations requiring specifying a buffer */ + ctx->samr.buf_size = 128; + + return ctx; +} diff --git a/source4/libnet/libnet.h b/source4/libnet/libnet.h new file mode 100644 index 0000000..41ddbea --- /dev/null +++ b/source4/libnet/libnet.h @@ -0,0 +1,86 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Rafal Szczesniak 2005-2006 + + 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 "librpc/gen_ndr/misc.h" + +struct libnet_context { + /* here we need: + * a client env context + * a user env context + */ + struct cli_credentials *cred; + + /* samr connection parameters - opened handles and related properties */ + struct { + struct dcerpc_pipe *pipe; + struct dcerpc_binding_handle *samr_handle; + const char *name; + struct dom_sid *sid; + uint32_t access_mask; + struct policy_handle handle; + struct policy_handle connect_handle; + int buf_size; + } samr; + + /* lsa connection parameters - opened handles and related properties */ + struct { + struct dcerpc_pipe *pipe; + struct dcerpc_binding_handle *lsa_handle; + const char *name; + uint32_t access_mask; + struct policy_handle handle; + } lsa; + + /* name resolution methods */ + struct resolve_context *resolve_ctx; + + struct tevent_context *event_ctx; + + struct loadparm_context *lp_ctx; + + /* if non-null then override the server address */ + const char *server_address; +}; + + +#include <ldb.h> +#include "libnet/composite.h" +#include "libnet/userman.h" +#include "libnet/userinfo.h" +#include "libnet/groupinfo.h" +#include "libnet/groupman.h" +#include "libnet/libnet_passwd.h" +#include "libnet/libnet_time.h" +#include "libnet/libnet_rpc.h" +#include "libnet/libnet_join.h" +#include "libnet/libnet_site.h" +#include "libnet/libnet_become_dc.h" +#include "libnet/libnet_unbecome_dc.h" +#include "libnet/libnet_samsync.h" +#include "libnet/libnet_vampire.h" +#include "libnet/libnet_user.h" +#include "libnet/libnet_group.h" +#include "libnet/libnet_share.h" +#include "libnet/libnet_lookup.h" +#include "libnet/libnet_domain.h" +#include "libnet/libnet_proto.h" +#endif diff --git a/source4/libnet/libnet_become_dc.c b/source4/libnet/libnet_become_dc.c new file mode 100644 index 0000000..e9153a0 --- /dev/null +++ b/source4/libnet/libnet_become_dc.c @@ -0,0 +1,3281 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher <metze@samba.org> 2006 + + 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 "libcli/composite/composite.h" +#include "libcli/cldap/cldap.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "ldb_wrap.h" +#include "dsdb/samdb/samdb.h" +#include "../libds/common/flags.h" +#include "librpc/gen_ndr/ndr_drsuapi_c.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/ndr_nbt.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "auth/gensec/gensec.h" +#include "param/param.h" +#include "lib/tsocket/tsocket.h" + +/***************************************************************************** + * Windows 2003 (w2k3) does the following steps when changing the server role + * from domain member to domain controller + * + * We mostly do the same. + *****************************************************************************/ + +/* + * lookup DC: + * - using nbt name<1C> request and a samlogon mailslot request + * or + * - using a DNS SRV _ldap._tcp.dc._msdcs. request and a CLDAP netlogon request + * + * see: becomeDC_recv_cldap() and becomeDC_send_cldap() + */ + +/* + * Open 1st LDAP connection to the DC using admin credentials + * + * see: becomeDC_connect_ldap1() and becomeDC_ldap_connect() + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_rootdse() + * + * Request: + * basedn: "" + * scope: base + * filter: (objectClass=*) + * attrs: * + * Result: + * "" + * currentTime: 20061202155100.0Z + * subschemaSubentry: CN=Aggregate,CN=Schema,CN=Configuration,<domain_partition> + * dsServiceName: CN=<netbios_name>,CN=Servers,CN=<site_name>,CN=Sites,CN=Configuration,<domain_partition> + * namingContexts: <domain_partition> + * CN=Configuration,<domain_partition> + * CN=Schema,CN=Configuration,<domain_partition> + * defaultNamingContext: <domain_partition> + * schemaNamingContext: CN=Schema,CN=Configuration,<domain_partition> + * configurationNamingContext:CN=Configuration,<domain_partition> + * rootDomainNamingContext:<domain_partition> + * supportedControl: ... + * supportedLDAPVersion: 3 + * 2 + * supportedLDAPPolicies: ... + * highestCommittedUSN: ... + * supportedSASLMechanisms:GSSAPI + * GSS-SPNEGO + * EXTERNAL + * DIGEST-MD5 + * dnsHostName: <dns_host_name> + * ldapServiceName: <domain_dns_name>:<netbios_name>$@<REALM> + * serverName: CN=Servers,CN=<site_name>,CN=Sites,CN=Configuration,<domain_partition> + * supportedCapabilities: ... + * isSyncronized: TRUE + * isGlobalCatalogReady: TRUE + * domainFunctionality: 0 + * forestFunctionality: 0 + * domainControllerFunctionality: 2 + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_crossref_behavior_version() + * + * Request: + * basedn: CN=Configuration,<domain_partition> + * scope: one + * filter: (cn=Partitions) + * attrs: msDS-Behavior-Version + * Result: + * CN=Partitions,CN=Configuration,<domain_partition> + * msDS-Behavior-Version: 0 + */ + +/* + * LDAP search 1st LDAP connection: + * + * NOTE: this seems to be a bug! as the messageID of the LDAP message is corrupted! + * + * not implemented here + * + * Request: + * basedn: CN=Schema,CN=Configuration,<domain_partition> + * scope: one + * filter: (cn=Partitions) + * attrs: msDS-Behavior-Version + * Result: + * <none> + * + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_domain_behavior_version() + * + * Request: + * basedn: <domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: msDS-Behavior-Version + * Result: + * <domain_partition> + * msDS-Behavior-Version: 0 + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_schema_object_version() + * + * Request: + * basedn: CN=Schema,CN=Configuration,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: objectVersion + * Result: + * CN=Schema,CN=Configuration,<domain_partition> + * objectVersion: 30 + */ + +/* + * LDAP search 1st LDAP connection: + * + * not implemented, because the information is already there + * + * Request: + * basedn: "" + * scope: base + * filter: (objectClass=*) + * attrs: defaultNamingContext + * dnsHostName + * Result: + * "" + * defaultNamingContext: <domain_partition> + * dnsHostName: <dns_host_name> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_infrastructure_fsmo() + * + * Request: + * basedn: <WKGUID=2fbac1870ade11d297c400c04fd8d5cd,domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: 1.1 + * Result: + * CN=Infrastructure,<domain_partition> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_w2k3_update_revision() + * + * Request: + * basedn: CN=Windows2003Update,CN=DomainUpdates,CN=System,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: revision + * Result: + * CN=Windows2003Update,CN=DomainUpdates,CN=System,<domain_partition> + * revision: 8 + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_infrastructure_fsmo() + * + * Request: + * basedn: CN=Infrastructure,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: fSMORoleOwner + * Result: + * CN=Infrastructure,<domain_partition> + * fSMORoleOwner: CN=NTDS Settings,<infrastructure_fsmo_server_object> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_infrastructure_fsmo() + * + * Request: + * basedn: <infrastructure_fsmo_server_object> + * scope: base + * filter: (objectClass=*) + * attrs: dnsHostName + * Result: + * <infrastructure_fsmo_server_object> + * dnsHostName: <dns_host_name> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_infrastructure_fsmo() + * + * Request: + * basedn: CN=NTDS Settings,<infrastructure_fsmo_server_object> + * scope: base + * filter: (objectClass=*) + * attrs: objectGUID + * Result: + * CN=NTDS Settings,<infrastructure_fsmo_server_object> + * objectGUID: <object_guid> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_rid_manager_fsmo() + * + * Request: + * basedn: <domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: rIDManagerReference + * Result: + * <domain_partition> + * rIDManagerReference: CN=RID Manager$,CN=System,<domain_partition> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_rid_manager_fsmo() + * + * Request: + * basedn: CN=RID Manager$,CN=System,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: fSMORoleOwner + * Result: + * CN=Infrastructure,<domain_partition> + * fSMORoleOwner: CN=NTDS Settings,<rid_manager_fsmo_server_object> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_rid_manager_fsmo() + * + * Request: + * basedn: <rid_manager_fsmo_server_object> + * scope: base + * filter: (objectClass=*) + * attrs: dnsHostName + * Result: + * <rid_manager_fsmo_server_object> + * dnsHostName: <dns_host_name> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_rid_manager_fsmo() + * + * Request: + * basedn: CN=NTDS Settings,<rid_manager_fsmo_server_object> + * scope: base + * filter: (objectClass=*) + * attrs: msDs-ReplicationEpoch + * Result: + * CN=NTDS Settings,<rid_manager_fsmo_server_object> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_site_object() + * + * Request: + * basedn: CN=<new_dc_site_name>,CN=Sites,CN=Configuration,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: + * Result: + * CN=<new_dc_site_name>,CN=Sites,CN=Configuration,<domain_partition> + * objectClass: top + * site + * cn: <new_dc_site_name> + * distinguishedName:CN=<new_dc_site_name>,CN=Sites,CN=Configuration,<domain_partition> + * instanceType: 4 + * whenCreated: ... + * whenChanged: ... + * uSNCreated: ... + * uSNChanged: ... + * showInAdvancedViewOnly: TRUE + * name: <new_dc_site_name> + * objectGUID: <object_guid> + * systemFlags: 1107296256 <0x42000000> + * objectCategory: CN=Site,CN=Schema,CN=Configuration,<domain_partition> + */ + +/*************************************************************** + * Add this stage we call the check_options() callback function + * of the caller, to see if he wants us to continue + * + * see: becomeDC_check_options() + ***************************************************************/ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_computer_object() + * + * Request: + * basedn: <domain_partition> + * scope: sub + * filter: (&(|(objectClass=user)(objectClass=computer))(sAMAccountName=<new_dc_account_name>)) + * attrs: distinguishedName + * userAccountControl + * Result: + * CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * distinguishedName: CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * userAccoountControl: 4096 <0x1000> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_server_object_1() + * + * Request: + * basedn: CN=<new_dc_netbios_name>,CN=Servers,CN=<new_dc_site_name>,CN=Sites,CN=Configuration,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: + * Result: + * <noSuchObject> + * <matchedDN:CN=Servers,CN=<new_dc_site_name>,CN=Sites,CN=Configuration,<domain_partition>> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: becomeDC_ldap1_server_object_2() + * + * Request: + * basedn: CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: serverReferenceBL + * typesOnly: TRUE!!! + * Result: + * CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + */ + +/* + * LDAP add 1st LDAP connection: + * + * see: becomeDC_ldap1_server_object_add() + * + * Request: + * CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * objectClass: server + * systemFlags: 50000000 <0x2FAF080> + * serverReference:CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * Result: + * <success> + */ + +/* + * LDAP search 1st LDAP connection: + * + * not implemented, maybe we can add that later + * + * Request: + * basedn: CN=NTDS Settings,CN=<new_dc_netbios_name>,CN=Servers,CN=<new_dc_site_name>,CN=Sites,CN=Configuration,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: + * Result: + * <noSuchObject> + * <matchedDN:CN=<new_dc_netbios_name>,CN=Servers,CN=<new_dc_site_name>,CN=Sites,CN=Configuration,<domain_partition>> + */ + +/* + * LDAP search 1st LDAP connection: + * + * not implemented because it gives no new information + * + * Request: + * basedn: CN=Partitions,CN=Configuration,<domain_partition> + * scope: sub + * filter: (nCName=<domain_partition>) + * attrs: nCName + * dnsRoot + * controls: LDAP_SERVER_EXTENDED_DN_OID:critical=false + * Result: + * <GUID=<hex_guid>>;CN=<domain_netbios_name>,CN=Partitions,<domain_partition>> + * nCName: <GUID=<hex_guid>>;<SID=<hex_sid>>;<domain_partition>> + * dnsRoot: <domain_dns_name> + */ + +/* + * LDAP modify 1st LDAP connection: + * + * see: becomeDC_ldap1_server_object_modify() + * + * Request (add): + * CN=<new_dc_netbios_name>,CN=Servers,CN=<new_dc_site_name>,CN=Sites,CN=Configuration,<domain_partition>> + * serverReference:CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * Result: + * <attributeOrValueExist> + */ + +/* + * LDAP modify 1st LDAP connection: + * + * see: becomeDC_ldap1_server_object_modify() + * + * Request (replace): + * CN=<new_dc_netbios_name>,CN=Servers,CN=<new_dc_site_name>,CN=Sites,CN=Configuration,<domain_partition>> + * serverReference:CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * Result: + * <success> + */ + +/* + * Open 1st DRSUAPI connection to the DC using admin credentials + * DsBind with DRSUAPI_DS_BIND_GUID_W2K3 ("6afab99c-6e26-464a-975f-f58f105218bc") + * (w2k3 does 2 DsBind() calls here..., where is first is unused and contains garbage at the end) + * + * see: becomeDC_drsuapi_connect_send(), becomeDC_drsuapi1_connect_recv(), + * becomeDC_drsuapi_bind_send(), becomeDC_drsuapi_bind_recv() and becomeDC_drsuapi1_bind_recv() + */ + +/* + * DsAddEntry to create the CN=NTDS Settings,CN=<machine_name>,CN=Servers,CN=Default-First-Site-Name, ... + * on the 1st DRSUAPI connection + * + * see: becomeDC_drsuapi1_add_entry_send() and becomeDC_drsuapi1_add_entry_recv() + */ + +/*************************************************************** + * Add this stage we call the prepare_db() callback function + * of the caller, to see if he wants us to continue + * + * see: becomeDC_prepare_db() + ***************************************************************/ + +/* + * Open 2nd and 3rd DRSUAPI connection to the DC using admin credentials + * - a DsBind with DRSUAPI_DS_BIND_GUID_W2K3 ("6afab99c-6e26-464a-975f-f58f105218bc") + * on the 2nd connection + * + * see: becomeDC_drsuapi_connect_send(), becomeDC_drsuapi2_connect_recv(), + * becomeDC_drsuapi_bind_send(), becomeDC_drsuapi_bind_recv(), becomeDC_drsuapi2_bind_recv() + * and becomeDC_drsuapi3_connect_recv() + */ + +/* + * replicate CN=Schema,CN=Configuration,... + * on the 3rd DRSUAPI connection and the bind_handle from the 2nd connection + * + * see: becomeDC_drsuapi_pull_partition_send(), becomeDC_drsuapi_pull_partition_recv(), + * becomeDC_drsuapi3_pull_schema_send() and becomeDC_drsuapi3_pull_schema_recv() + * + *************************************************************** + * Add this stage we call the schema_chunk() callback function + * for each replication message + ***************************************************************/ + +/* + * replicate CN=Configuration,... + * on the 3rd DRSUAPI connection and the bind_handle from the 2nd connection + * + * see: becomeDC_drsuapi_pull_partition_send(), becomeDC_drsuapi_pull_partition_recv(), + * becomeDC_drsuapi3_pull_config_send() and becomeDC_drsuapi3_pull_config_recv() + * + *************************************************************** + * Add this stage we call the config_chunk() callback function + * for each replication message + ***************************************************************/ + +/* + * LDAP unbind on the 1st LDAP connection + * + * not implemented, because it's not needed... + */ + +/* + * Open 2nd LDAP connection to the DC using admin credentials + * + * see: becomeDC_connect_ldap2() and becomeDC_ldap_connect() + */ + +/* + * LDAP search 2nd LDAP connection: + * + * not implemented because it gives no new information + * same as becomeDC_ldap1_computer_object() + * + * Request: + * basedn: <domain_partition> + * scope: sub + * filter: (&(|(objectClass=user)(objectClass=computer))(sAMAccountName=<new_dc_account_name>)) + * attrs: distinguishedName + * userAccountControl + * Result: + * CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * distinguishedName: CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * userAccoountControl: 4096 <0x00001000> + */ + +/* + * LDAP search 2nd LDAP connection: + * + * not implemented because it gives no new information + * same as becomeDC_ldap1_computer_object() + * + * Request: + * basedn: CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: userAccountControl + * Result: + * CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * userAccoountControl: 4096 <0x00001000> + */ + +/* + * LDAP modify 2nd LDAP connection: + * + * see: becomeDC_ldap2_modify_computer() + * + * Request (replace): + * CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * userAccoountControl: 532480 <0x82000> + * Result: + * <success> + */ + +/* + * LDAP search 2nd LDAP connection: + * + * see: becomeDC_ldap2_move_computer() + * + * Request: + * basedn: <WKGUID=2fbac1870ade11d297c400c04fd8d5cd,<domain_partition>> + * scope: base + * filter: (objectClass=*) + * attrs: 1.1 + * Result: + * CN=Domain Controllers,<domain_partition> + */ + +/* + * LDAP search 2nd LDAP connection: + * + * not implemented because it gives no new information + * + * Request: + * basedn: CN=Domain Controllers,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: distinguishedName + * Result: + * CN=Domain Controller,<domain_partition> + * distinguishedName: CN=Domain Controllers,<domain_partition> + */ + +/* + * LDAP modifyRDN 2nd LDAP connection: + * + * see: becomeDC_ldap2_move_computer() + * + * Request: + * entry: CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * newrdn: CN=<new_dc_netbios_name> + * deleteoldrdn: TRUE + * newparent: CN=Domain Controllers,<domain_partition> + * Result: + * <success> + */ + +/* + * LDAP unbind on the 2nd LDAP connection + * + * not implemented, because it's not needed... + */ + +/* + * replicate Domain Partition + * on the 3rd DRSUAPI connection and the bind_handle from the 2nd connection + * + * see: becomeDC_drsuapi_pull_partition_send(), becomeDC_drsuapi_pull_partition_recv(), + * becomeDC_drsuapi3_pull_domain_send() and becomeDC_drsuapi3_pull_domain_recv() + * + *************************************************************** + * Add this stage we call the domain_chunk() callback function + * for each replication message + ***************************************************************/ + +/* call DsReplicaUpdateRefs() for all partitions like this: + * req1: struct drsuapi_DsReplicaUpdateRefsRequest1 + * + * naming_context: struct drsuapi_DsReplicaObjectIdentifier + * __ndr_size : 0x000000ae (174) + * __ndr_size_sid : 0x00000000 (0) + * guid : 00000000-0000-0000-0000-000000000000 + * sid : S-0-0 + * dn : 'CN=Schema,CN=Configuration,DC=w2k3,DC=vmnet1,DC=vm,DC=base' + * + * dest_dsa_dns_name : '4a0df188-a0b8-47ea-bbe5-e614723f16dd._msdcs.w2k3.vmnet1.vm.base' + * dest_dsa_guid : 4a0df188-a0b8-47ea-bbe5-e614723f16dd + * options : 0x0000001c (28) + * 0: DRSUAPI_DS_REPLICA_UPDATE_ASYNCHRONOUS_OPERATION + * 0: DRSUAPI_DS_REPLICA_UPDATE_WRITEABLE + * 1: DRSUAPI_DS_REPLICA_UPDATE_ADD_REFERENCE + * 1: DRSUAPI_DS_REPLICA_UPDATE_DELETE_REFERENCE + * 1: DRSUAPI_DS_REPLICA_UPDATE_0x00000010 + * + * 4a0df188-a0b8-47ea-bbe5-e614723f16dd is the objectGUID the DsAddEntry() returned for the + * CN=NTDS Settings,CN=<machine_name>,CN=Servers,CN=Default-First-Site-Name, ... + * on the 2nd!!! DRSUAPI connection + * + * see: becomeDC_drsuapi_update_refs_send(), becomeDC_drsuapi2_update_refs_schema_recv(), + * becomeDC_drsuapi2_update_refs_config_recv() and becomeDC_drsuapi2_update_refs_domain_recv() + */ + +/* + * Windows does opens the 4th and 5th DRSUAPI connection... + * and does a DsBind() with the objectGUID from DsAddEntry() as bind_guid + * on the 4th connection + * + * and then 2 full replications of the domain partition on the 5th connection + * with the bind_handle from the 4th connection + * + * not implemented because it gives no new information + */ + +struct libnet_BecomeDC_state { + struct composite_context *creq; + + struct libnet_context *libnet; + + struct dom_sid zero_sid; + + struct { + struct cldap_socket *sock; + struct cldap_netlogon io; + struct NETLOGON_SAM_LOGON_RESPONSE_EX netlogon; + } cldap; + + struct becomeDC_ldap { + struct ldb_context *ldb; + const struct ldb_message *rootdse; + } ldap1, ldap2; + + struct becomeDC_drsuapi { + struct libnet_BecomeDC_state *s; + struct dcerpc_binding *binding; + struct dcerpc_pipe *pipe; + struct dcerpc_binding_handle *drsuapi_handle; + DATA_BLOB gensec_skey; + struct drsuapi_DsBind bind_r; + struct GUID bind_guid; + struct drsuapi_DsBindInfoCtr bind_info_ctr; + struct drsuapi_DsBindInfo28 local_info28; + struct drsuapi_DsBindInfo28 remote_info28; + struct policy_handle bind_handle; + } drsuapi1, drsuapi2, drsuapi3; + + void *ndr_struct_ptr; + + struct libnet_BecomeDC_Domain domain; + struct libnet_BecomeDC_Forest forest; + struct libnet_BecomeDC_SourceDSA source_dsa; + struct libnet_BecomeDC_DestDSA dest_dsa; + + struct libnet_BecomeDC_Partition schema_part, config_part, domain_part; + + struct becomeDC_fsmo { + const char *dns_name; + const char *server_dn_str; + const char *ntds_dn_str; + struct GUID ntds_guid; + } infrastructure_fsmo; + + struct becomeDC_fsmo rid_manager_fsmo; + + struct libnet_BecomeDC_CheckOptions _co; + struct libnet_BecomeDC_PrepareDB _pp; + struct libnet_BecomeDC_StoreChunk _sc; + struct libnet_BecomeDC_Callbacks callbacks; + + bool rodc_join; + bool critical_only; +}; + +static int32_t get_dc_function_level(struct loadparm_context *lp_ctx) +{ + /* per default we are (Windows) 2008 R2 compatible */ + return lpcfg_parm_int(lp_ctx, NULL, "ads", "dc function level", + DS_DOMAIN_FUNCTION_2008_R2); +} + +static void becomeDC_recv_cldap(struct tevent_req *req); + +static void becomeDC_send_cldap(struct libnet_BecomeDC_state *s) +{ + struct composite_context *c = s->creq; + struct tevent_req *req; + struct tsocket_address *dest_address; + int ret; + + s->cldap.io.in.dest_address = NULL; + s->cldap.io.in.dest_port = 0; + s->cldap.io.in.realm = s->domain.dns_name; + s->cldap.io.in.host = s->dest_dsa.netbios_name; + s->cldap.io.in.user = NULL; + s->cldap.io.in.domain_guid = NULL; + s->cldap.io.in.domain_sid = NULL; + s->cldap.io.in.acct_control = -1; + s->cldap.io.in.version = NETLOGON_NT_VERSION_5 | NETLOGON_NT_VERSION_5EX; + s->cldap.io.in.map_response = true; + + ret = tsocket_address_inet_from_strings(s, "ip", + s->source_dsa.address, + lpcfg_cldap_port(s->libnet->lp_ctx), + &dest_address); + if (ret != 0) { + c->status = map_nt_error_from_unix_common(errno); + if (!composite_is_ok(c)) return; + } + + c->status = cldap_socket_init(s, NULL, dest_address, &s->cldap.sock); + if (!composite_is_ok(c)) return; + + req = cldap_netlogon_send(s, s->libnet->event_ctx, + s->cldap.sock, &s->cldap.io); + if (composite_nomem(req, c)) return; + tevent_req_set_callback(req, becomeDC_recv_cldap, s); +} + +static void becomeDC_connect_ldap1(struct libnet_BecomeDC_state *s); + +static void becomeDC_recv_cldap(struct tevent_req *req) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(req, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + + c->status = cldap_netlogon_recv(req, s, &s->cldap.io); + talloc_free(req); + if (!composite_is_ok(c)) { + DEBUG(0,("Failed to send, receive or parse CLDAP reply from server %s for our host %s: %s\n", + s->cldap.io.in.dest_address, + s->cldap.io.in.host, + nt_errstr(c->status))); + return; + } + s->cldap.netlogon = s->cldap.io.out.netlogon.data.nt5_ex; + + s->domain.dns_name = s->cldap.netlogon.dns_domain; + s->domain.netbios_name = s->cldap.netlogon.domain_name; + s->domain.guid = s->cldap.netlogon.domain_uuid; + + s->forest.dns_name = s->cldap.netlogon.forest; + + s->source_dsa.dns_name = s->cldap.netlogon.pdc_dns_name; + s->source_dsa.netbios_name = s->cldap.netlogon.pdc_name; + s->source_dsa.site_name = s->cldap.netlogon.server_site; + + s->dest_dsa.site_name = s->cldap.netlogon.client_site; + + DEBUG(0,("CLDAP response: forest=%s dns=%s netbios=%s server_site=%s client_site=%s\n", + s->forest.dns_name, s->domain.dns_name, s->domain.netbios_name, + s->source_dsa.site_name, s->dest_dsa.site_name)); + if (!s->dest_dsa.site_name || strcmp(s->dest_dsa.site_name, "") == 0) { + DEBUG(0,("Got empty client site - using server site name %s\n", + s->source_dsa.site_name)); + s->dest_dsa.site_name = s->source_dsa.site_name; + } + + becomeDC_connect_ldap1(s); +} + +static NTSTATUS becomeDC_ldap_connect(struct libnet_BecomeDC_state *s, + struct becomeDC_ldap *ldap) +{ + char *url; + + url = talloc_asprintf(s, "ldap://%s/", s->source_dsa.dns_name); + NT_STATUS_HAVE_NO_MEMORY(url); + + ldap->ldb = ldb_wrap_connect(s, s->libnet->event_ctx, s->libnet->lp_ctx, url, + NULL, + s->libnet->cred, + 0); + talloc_free(url); + if (ldap->ldb == NULL) { + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_rootdse(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + static const char *attrs[] = { + "*", + NULL + }; + + basedn = ldb_dn_new(s, s->ldap1.ldb, NULL); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_BASE, attrs, + "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->ldap1.rootdse = r->msgs[0]; + + s->domain.dn_str = ldb_msg_find_attr_as_string(s->ldap1.rootdse, "defaultNamingContext", NULL); + if (!s->domain.dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + + s->forest.root_dn_str = ldb_msg_find_attr_as_string(s->ldap1.rootdse, "rootDomainNamingContext", NULL); + if (!s->forest.root_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + s->forest.config_dn_str = ldb_msg_find_attr_as_string(s->ldap1.rootdse, "configurationNamingContext", NULL); + if (!s->forest.config_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + s->forest.schema_dn_str = ldb_msg_find_attr_as_string(s->ldap1.rootdse, "schemaNamingContext", NULL); + if (!s->forest.schema_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + + s->source_dsa.server_dn_str = ldb_msg_find_attr_as_string(s->ldap1.rootdse, "serverName", NULL); + if (!s->source_dsa.server_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + s->source_dsa.ntds_dn_str = ldb_msg_find_attr_as_string(s->ldap1.rootdse, "dsServiceName", NULL); + if (!s->source_dsa.ntds_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_crossref_behavior_version(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + static const char *attrs[] = { + "msDs-Behavior-Version", + NULL + }; + + basedn = ldb_dn_new(s, s->ldap1.ldb, s->forest.config_dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_ONELEVEL, attrs, + "(cn=Partitions)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->forest.crossref_behavior_version = ldb_msg_find_attr_as_uint(r->msgs[0], "msDs-Behavior-Version", 0); + if (s->forest.crossref_behavior_version > + get_dc_function_level(s->libnet->lp_ctx)) { + talloc_free(r); + DEBUG(0,("The servers function level %u is above 'ads:dc function level' of %u\n", + s->forest.crossref_behavior_version, + get_dc_function_level(s->libnet->lp_ctx))); + return NT_STATUS_NOT_SUPPORTED; + } + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_domain_behavior_version(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + static const char *attrs[] = { + "msDs-Behavior-Version", + NULL + }; + + basedn = ldb_dn_new(s, s->ldap1.ldb, s->domain.dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_BASE, attrs, + "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->domain.behavior_version = ldb_msg_find_attr_as_uint(r->msgs[0], "msDs-Behavior-Version", 0); + if (s->domain.behavior_version > + get_dc_function_level(s->libnet->lp_ctx)) { + talloc_free(r); + DEBUG(0,("The servers function level %u is above 'ads:dc function level' of %u\n", + s->forest.crossref_behavior_version, + get_dc_function_level(s->libnet->lp_ctx))); + return NT_STATUS_NOT_SUPPORTED; + } + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_schema_object_version(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + static const char *attrs[] = { + "objectVersion", + NULL + }; + + basedn = ldb_dn_new(s, s->ldap1.ldb, s->forest.schema_dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_BASE, attrs, + "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->forest.schema_object_version = ldb_msg_find_attr_as_uint(r->msgs[0], "objectVersion", 0); + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_w2k3_update_revision(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + static const char *attrs[] = { + "revision", + NULL + }; + + basedn = ldb_dn_new_fmt(s, s->ldap1.ldb, "CN=Windows2003Update,CN=DomainUpdates,CN=System,%s", + s->domain.dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_BASE, attrs, + "(objectClass=*)"); + talloc_free(basedn); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + /* w2k doesn't have this object */ + s->domain.w2k3_update_revision = 0; + return NT_STATUS_OK; + } else if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->domain.w2k3_update_revision = ldb_msg_find_attr_as_uint(r->msgs[0], "revision", 0); + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_infrastructure_fsmo(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + struct ldb_dn *ntds_dn; + struct ldb_dn *server_dn; + static const char *dns_attrs[] = { + "dnsHostName", + NULL + }; + static const char *guid_attrs[] = { + "objectGUID", + NULL + }; + + ret = dsdb_wellknown_dn(s->ldap1.ldb, s, + ldb_get_default_basedn(s->ldap1.ldb), + DS_GUID_INFRASTRUCTURE_CONTAINER, + &basedn); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to get well known DN for DS_GUID_INFRASTRUCTURE_CONTAINER on %s: %s\n", + ldb_dn_get_linearized(ldb_get_default_basedn(s->ldap1.ldb)), + ldb_errstring(s->ldap1.ldb))); + return NT_STATUS_LDAP(ret); + } + + ret = samdb_reference_dn(s->ldap1.ldb, s, basedn, "fSMORoleOwner", &ntds_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to get reference DN from fsmoRoleOwner on %s: %s\n", + ldb_dn_get_linearized(basedn), + ldb_errstring(s->ldap1.ldb))); + talloc_free(basedn); + return NT_STATUS_LDAP(ret); + } + + s->infrastructure_fsmo.ntds_dn_str = ldb_dn_get_linearized(ntds_dn); + NT_STATUS_HAVE_NO_MEMORY(s->infrastructure_fsmo.ntds_dn_str); + + server_dn = ldb_dn_get_parent(s, ntds_dn); + NT_STATUS_HAVE_NO_MEMORY(server_dn); + + s->infrastructure_fsmo.server_dn_str = ldb_dn_alloc_linearized(s, server_dn); + NT_STATUS_HAVE_NO_MEMORY(s->infrastructure_fsmo.server_dn_str); + + ret = ldb_search(s->ldap1.ldb, s, &r, server_dn, LDB_SCOPE_BASE, + dns_attrs, "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to get server DN %s: %s\n", + ldb_dn_get_linearized(server_dn), + ldb_errstring(s->ldap1.ldb))); + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->infrastructure_fsmo.dns_name = ldb_msg_find_attr_as_string(r->msgs[0], "dnsHostName", NULL); + if (!s->infrastructure_fsmo.dns_name) return NT_STATUS_INVALID_NETWORK_RESPONSE; + talloc_steal(s, s->infrastructure_fsmo.dns_name); + + talloc_free(r); + + ldb_dn_remove_extended_components(ntds_dn); + ret = ldb_search(s->ldap1.ldb, s, &r, ntds_dn, LDB_SCOPE_BASE, + guid_attrs, "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to get NTDS Settings DN %s: %s\n", + ldb_dn_get_linearized(ntds_dn), + ldb_errstring(s->ldap1.ldb))); + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->infrastructure_fsmo.ntds_guid = samdb_result_guid(r->msgs[0], "objectGUID"); + + talloc_free(r); + + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_rid_manager_fsmo(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + const char *reference_dn_str; + struct ldb_dn *ntds_dn; + struct ldb_dn *server_dn; + static const char *rid_attrs[] = { + "rIDManagerReference", + NULL + }; + static const char *fsmo_attrs[] = { + "fSMORoleOwner", + NULL + }; + static const char *dns_attrs[] = { + "dnsHostName", + NULL + }; + static const char *guid_attrs[] = { + "objectGUID", + NULL + }; + + basedn = ldb_dn_new(s, s->ldap1.ldb, s->domain.dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_BASE, + rid_attrs, "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + reference_dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "rIDManagerReference", NULL); + if (!reference_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + + basedn = ldb_dn_new(s, s->ldap1.ldb, reference_dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + talloc_free(r); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_BASE, + fsmo_attrs, "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->rid_manager_fsmo.ntds_dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "fSMORoleOwner", NULL); + if (!s->rid_manager_fsmo.ntds_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + talloc_steal(s, s->rid_manager_fsmo.ntds_dn_str); + + talloc_free(r); + + ntds_dn = ldb_dn_new(s, s->ldap1.ldb, s->rid_manager_fsmo.ntds_dn_str); + NT_STATUS_HAVE_NO_MEMORY(ntds_dn); + + server_dn = ldb_dn_get_parent(s, ntds_dn); + NT_STATUS_HAVE_NO_MEMORY(server_dn); + + s->rid_manager_fsmo.server_dn_str = ldb_dn_alloc_linearized(s, server_dn); + NT_STATUS_HAVE_NO_MEMORY(s->rid_manager_fsmo.server_dn_str); + + ret = ldb_search(s->ldap1.ldb, s, &r, server_dn, LDB_SCOPE_BASE, + dns_attrs, "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->rid_manager_fsmo.dns_name = ldb_msg_find_attr_as_string(r->msgs[0], "dnsHostName", NULL); + if (!s->rid_manager_fsmo.dns_name) return NT_STATUS_INVALID_NETWORK_RESPONSE; + talloc_steal(s, s->rid_manager_fsmo.dns_name); + + talloc_free(r); + + ret = ldb_search(s->ldap1.ldb, s, &r, ntds_dn, LDB_SCOPE_BASE, + guid_attrs, "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->rid_manager_fsmo.ntds_guid = samdb_result_guid(r->msgs[0], "objectGUID"); + + talloc_free(r); + + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_site_object(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + + basedn = ldb_dn_new_fmt(s, s->ldap1.ldb, "CN=%s,CN=Sites,%s", + s->dest_dsa.site_name, + s->forest.config_dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_BASE, + NULL, "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->dest_dsa.site_guid = samdb_result_guid(r->msgs[0], "objectGUID"); + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_check_options(struct libnet_BecomeDC_state *s) +{ + if (!s->callbacks.check_options) return NT_STATUS_OK; + + s->_co.domain = &s->domain; + s->_co.forest = &s->forest; + s->_co.source_dsa = &s->source_dsa; + + return s->callbacks.check_options(s->callbacks.private_data, &s->_co); +} + +static NTSTATUS becomeDC_ldap1_computer_object(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + static const char *attrs[] = { + "distinguishedName", + "userAccountControl", + NULL + }; + + basedn = ldb_dn_new(s, s->ldap1.ldb, s->domain.dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_SUBTREE, attrs, + "(&(|(objectClass=user)(objectClass=computer))(sAMAccountName=%s$))", + s->dest_dsa.netbios_name); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->dest_dsa.computer_dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "distinguishedName", NULL); + if (!s->dest_dsa.computer_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + talloc_steal(s, s->dest_dsa.computer_dn_str); + + s->dest_dsa.user_account_control = ldb_msg_find_attr_as_uint(r->msgs[0], "userAccountControl", 0); + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_server_object_1(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + const char *server_reference_dn_str; + struct ldb_dn *server_reference_dn; + struct ldb_dn *computer_dn; + + basedn = ldb_dn_new_fmt(s, s->ldap1.ldb, "CN=%s,CN=Servers,CN=%s,CN=Sites,%s", + s->dest_dsa.netbios_name, + s->dest_dsa.site_name, + s->forest.config_dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_BASE, + NULL, "(objectClass=*)"); + talloc_free(basedn); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + /* if the object doesn't exist, we'll create it later */ + return NT_STATUS_OK; + } else if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + server_reference_dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "serverReference", NULL); + if (server_reference_dn_str) { + server_reference_dn = ldb_dn_new(r, s->ldap1.ldb, server_reference_dn_str); + NT_STATUS_HAVE_NO_MEMORY(server_reference_dn); + + computer_dn = ldb_dn_new(r, s->ldap1.ldb, s->dest_dsa.computer_dn_str); + NT_STATUS_HAVE_NO_MEMORY(computer_dn); + + /* + * if the server object belongs to another DC in another domain + * in the forest, we should not touch this object! + */ + if (ldb_dn_compare(computer_dn, server_reference_dn) != 0) { + talloc_free(r); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + } + + /* if the server object is already for the dest_dsa, then we don't need to create it */ + s->dest_dsa.server_dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "distinguishedName", NULL); + if (!s->dest_dsa.server_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + talloc_steal(s, s->dest_dsa.server_dn_str); + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_server_object_2(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + const char *server_reference_bl_dn_str; + static const char *attrs[] = { + "serverReferenceBL", + NULL + }; + + /* if the server_dn_str has a valid value, we skip this lookup */ + if (s->dest_dsa.server_dn_str) return NT_STATUS_OK; + + basedn = ldb_dn_new(s, s->ldap1.ldb, s->dest_dsa.computer_dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap1.ldb, s, &r, basedn, LDB_SCOPE_BASE, + attrs, "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + server_reference_bl_dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "serverReferenceBL", NULL); + if (!server_reference_bl_dn_str) { + /* if no back link is present, we're done for this function */ + talloc_free(r); + return NT_STATUS_OK; + } + + /* if the server object is already for the dest_dsa, then we don't need to create it */ + s->dest_dsa.server_dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "serverReferenceBL", NULL); + if (s->dest_dsa.server_dn_str) { + /* if a back link is present, we know that the server object is present */ + talloc_steal(s, s->dest_dsa.server_dn_str); + } + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_server_object_add(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_message *msg; + char *server_dn_str; + + /* if the server_dn_str has a valid value, we skip this lookup */ + if (s->dest_dsa.server_dn_str) return NT_STATUS_OK; + + msg = ldb_msg_new(s); + NT_STATUS_HAVE_NO_MEMORY(msg); + + msg->dn = ldb_dn_new_fmt(msg, s->ldap1.ldb, "CN=%s,CN=Servers,CN=%s,CN=Sites,%s", + s->dest_dsa.netbios_name, + s->dest_dsa.site_name, + s->forest.config_dn_str); + NT_STATUS_HAVE_NO_MEMORY(msg->dn); + + ret = ldb_msg_add_string(msg, "objectClass", "server"); + if (ret != 0) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + ret = ldb_msg_add_string(msg, "systemFlags", "50000000"); + if (ret != 0) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + ret = ldb_msg_add_string(msg, "serverReference", s->dest_dsa.computer_dn_str); + if (ret != 0) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + + server_dn_str = ldb_dn_alloc_linearized(s, msg->dn); + NT_STATUS_HAVE_NO_MEMORY(server_dn_str); + + ret = ldb_add(s->ldap1.ldb, msg); + talloc_free(msg); + if (ret != LDB_SUCCESS) { + talloc_free(server_dn_str); + return NT_STATUS_LDAP(ret); + } + + s->dest_dsa.server_dn_str = server_dn_str; + + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap1_server_object_modify(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_message *msg; + unsigned int i; + + /* make a 'modify' msg, and only for serverReference */ + msg = ldb_msg_new(s); + NT_STATUS_HAVE_NO_MEMORY(msg); + msg->dn = ldb_dn_new(msg, s->ldap1.ldb, s->dest_dsa.server_dn_str); + NT_STATUS_HAVE_NO_MEMORY(msg->dn); + + ret = ldb_msg_add_string(msg, "serverReference", s->dest_dsa.computer_dn_str); + if (ret != 0) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + + /* mark all the message elements (should be just one) + as LDB_FLAG_MOD_ADD */ + for (i=0;i<msg->num_elements;i++) { + msg->elements[i].flags = LDB_FLAG_MOD_ADD; + } + + ret = ldb_modify(s->ldap1.ldb, msg); + if (ret == LDB_SUCCESS) { + talloc_free(msg); + return NT_STATUS_OK; + } else if (ret == LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS) { + /* retry with LDB_FLAG_MOD_REPLACE */ + } else { + talloc_free(msg); + return NT_STATUS_LDAP(ret); + } + + /* mark all the message elements (should be just one) + as LDB_FLAG_MOD_REPLACE */ + for (i=0;i<msg->num_elements;i++) { + msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + + ret = ldb_modify(s->ldap1.ldb, msg); + talloc_free(msg); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } + + return NT_STATUS_OK; +} + +static void becomeDC_drsuapi_connect_send(struct libnet_BecomeDC_state *s, + struct becomeDC_drsuapi *drsuapi, + void (*recv_fn)(struct composite_context *req)); +static void becomeDC_drsuapi1_connect_recv(struct composite_context *req); +static void becomeDC_connect_ldap2(struct libnet_BecomeDC_state *s); + +static void becomeDC_connect_ldap1(struct libnet_BecomeDC_state *s) +{ + struct composite_context *c = s->creq; + + c->status = becomeDC_ldap_connect(s, &s->ldap1); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_rootdse(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_crossref_behavior_version(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_domain_behavior_version(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_schema_object_version(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_w2k3_update_revision(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_infrastructure_fsmo(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_rid_manager_fsmo(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_site_object(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_check_options(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_computer_object(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_server_object_1(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_server_object_2(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_server_object_add(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap1_server_object_modify(s); + if (!composite_is_ok(c)) return; + + becomeDC_drsuapi_connect_send(s, &s->drsuapi1, becomeDC_drsuapi1_connect_recv); +} + +static void becomeDC_drsuapi_connect_send(struct libnet_BecomeDC_state *s, + struct becomeDC_drsuapi *drsuapi, + void (*recv_fn)(struct composite_context *req)) +{ + struct composite_context *c = s->creq; + struct composite_context *creq; + char *binding_str; + + drsuapi->s = s; + + if (!drsuapi->binding) { + const char *krb5_str = ""; + const char *print_str = ""; + /* + * Note: Replication only works with Windows 2000 when 'krb5' is + * passed as auth_type here. If NTLMSSP is used, Windows + * 2000 returns garbage in the DsGetNCChanges() response + * if encrypted password attributes would be in the + * response. That means the replication of the schema and + * configuration partition works fine, but it fails for + * the domain partition. + */ + if (lpcfg_parm_bool(s->libnet->lp_ctx, NULL, "become_dc", + "force krb5", true)) + { + krb5_str = "krb5,"; + } + if (lpcfg_parm_bool(s->libnet->lp_ctx, NULL, "become_dc", + "print", false)) + { + print_str = "print,"; + } + binding_str = talloc_asprintf(s, "ncacn_ip_tcp:%s[%s%sseal,target_hostname=%s]", + s->source_dsa.address, + krb5_str, print_str, + s->source_dsa.dns_name); + if (composite_nomem(binding_str, c)) return; + c->status = dcerpc_parse_binding(s, binding_str, &drsuapi->binding); + talloc_free(binding_str); + if (!composite_is_ok(c)) return; + } + + if (DEBUGLEVEL >= 10) { + c->status = dcerpc_binding_set_flags(drsuapi->binding, + DCERPC_DEBUG_PRINT_BOTH, + 0); + if (!composite_is_ok(c)) return; + } + + creq = dcerpc_pipe_connect_b_send(s, drsuapi->binding, &ndr_table_drsuapi, + s->libnet->cred, s->libnet->event_ctx, + s->libnet->lp_ctx); + composite_continue(c, creq, recv_fn, s); +} + +static void becomeDC_drsuapi_bind_send(struct libnet_BecomeDC_state *s, + struct becomeDC_drsuapi *drsuapi, + void (*recv_fn)(struct tevent_req *subreq)); +static void becomeDC_drsuapi1_bind_recv(struct tevent_req *subreq); + +static void becomeDC_drsuapi1_connect_recv(struct composite_context *req) +{ + struct libnet_BecomeDC_state *s = talloc_get_type(req->async.private_data, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + + c->status = dcerpc_pipe_connect_b_recv(req, s, &s->drsuapi1.pipe); + if (!composite_is_ok(c)) return; + + s->drsuapi1.drsuapi_handle = s->drsuapi1.pipe->binding_handle; + + c->status = gensec_session_key(s->drsuapi1.pipe->conn->security_state.generic_state, + s, + &s->drsuapi1.gensec_skey); + if (!composite_is_ok(c)) return; + + becomeDC_drsuapi_bind_send(s, &s->drsuapi1, becomeDC_drsuapi1_bind_recv); +} + +static void becomeDC_drsuapi_bind_send(struct libnet_BecomeDC_state *s, + struct becomeDC_drsuapi *drsuapi, + void (*recv_fn)(struct tevent_req *subreq)) +{ + struct composite_context *c = s->creq; + struct drsuapi_DsBindInfo28 *bind_info28; + struct tevent_req *subreq; + + GUID_from_string(DRSUAPI_DS_BIND_GUID_W2K3, &drsuapi->bind_guid); + + bind_info28 = &drsuapi->local_info28; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_BASE; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2; + if (s->domain.behavior_version >= DS_DOMAIN_FUNCTION_2003) { + /* TODO: find out how this is really triggered! */ + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION; + } + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V5; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT; +#if 0 /* we don't support XPRESS compression yet */ + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_XPRESS_COMPRESS; +#endif + bind_info28->site_guid = s->dest_dsa.site_guid; + bind_info28->pid = 0; + bind_info28->repl_epoch = 0; + + drsuapi->bind_info_ctr.length = 28; + drsuapi->bind_info_ctr.info.info28 = *bind_info28; + + drsuapi->bind_r.in.bind_guid = &drsuapi->bind_guid; + drsuapi->bind_r.in.bind_info = &drsuapi->bind_info_ctr; + drsuapi->bind_r.out.bind_handle = &drsuapi->bind_handle; + + subreq = dcerpc_drsuapi_DsBind_r_send(s, c->event_ctx, + drsuapi->drsuapi_handle, + &drsuapi->bind_r); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, recv_fn, s); +} + +static WERROR becomeDC_drsuapi_bind_recv(struct libnet_BecomeDC_state *s, + struct becomeDC_drsuapi *drsuapi) +{ + if (!W_ERROR_IS_OK(drsuapi->bind_r.out.result)) { + return drsuapi->bind_r.out.result; + } + + ZERO_STRUCT(drsuapi->remote_info28); + if (drsuapi->bind_r.out.bind_info) { + switch (drsuapi->bind_r.out.bind_info->length) { + case 24: { + struct drsuapi_DsBindInfo24 *info24; + info24 = &drsuapi->bind_r.out.bind_info->info.info24; + drsuapi->remote_info28.supported_extensions = info24->supported_extensions; + drsuapi->remote_info28.site_guid = info24->site_guid; + drsuapi->remote_info28.pid = info24->pid; + drsuapi->remote_info28.repl_epoch = 0; + break; + } + case 28: { + drsuapi->remote_info28 = drsuapi->bind_r.out.bind_info->info.info28; + break; + } + case 32: { + struct drsuapi_DsBindInfo32 *info32; + info32 = &drsuapi->bind_r.out.bind_info->info.info32; + drsuapi->remote_info28.supported_extensions = info32->supported_extensions; + drsuapi->remote_info28.site_guid = info32->site_guid; + drsuapi->remote_info28.pid = info32->pid; + drsuapi->remote_info28.repl_epoch = info32->repl_epoch; + break; + } + case 48: { + struct drsuapi_DsBindInfo48 *info48; + info48 = &drsuapi->bind_r.out.bind_info->info.info48; + drsuapi->remote_info28.supported_extensions = info48->supported_extensions; + drsuapi->remote_info28.site_guid = info48->site_guid; + drsuapi->remote_info28.pid = info48->pid; + drsuapi->remote_info28.repl_epoch = info48->repl_epoch; + break; + } + case 52: { + struct drsuapi_DsBindInfo52 *info52; + info52 = &drsuapi->bind_r.out.bind_info->info.info52; + drsuapi->remote_info28.supported_extensions = info52->supported_extensions; + drsuapi->remote_info28.site_guid = info52->site_guid; + drsuapi->remote_info28.pid = info52->pid; + drsuapi->remote_info28.repl_epoch = info52->repl_epoch; + break; + } + default: + DEBUG(1, ("Warning: invalid info length in bind info: %d\n", + drsuapi->bind_r.out.bind_info->length)); + break; + } + } + + return WERR_OK; +} + +static void becomeDC_drsuapi1_add_entry_send(struct libnet_BecomeDC_state *s); + +static void becomeDC_drsuapi1_bind_recv(struct tevent_req *subreq) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + WERROR status; + + c->status = dcerpc_drsuapi_DsBind_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + status = becomeDC_drsuapi_bind_recv(s, &s->drsuapi1); + if (!W_ERROR_IS_OK(status)) { + composite_error(c, werror_to_ntstatus(status)); + return; + } + + becomeDC_drsuapi1_add_entry_send(s); +} + +static void becomeDC_drsuapi1_add_entry_recv(struct tevent_req *subreq); + +static void becomeDC_drsuapi1_add_entry_send(struct libnet_BecomeDC_state *s) +{ + struct composite_context *c = s->creq; + struct drsuapi_DsAddEntry *r; + struct drsuapi_DsReplicaObjectIdentifier *identifier; + uint32_t num_attrs, i = 0; + struct drsuapi_DsReplicaAttribute *attrs; + enum ndr_err_code ndr_err; + bool w2k3; + struct tevent_req *subreq; + + /* choose a random invocationId */ + s->dest_dsa.invocation_id = GUID_random(); + + /* + * if the schema version indicates w2k3, then also send some w2k3 + * specific attributes. + */ + if (s->forest.schema_object_version >= 30) { + w2k3 = true; + } else { + w2k3 = false; + } + + r = talloc_zero(s, struct drsuapi_DsAddEntry); + if (composite_nomem(r, c)) return; + + /* setup identifier */ + identifier = talloc(r, struct drsuapi_DsReplicaObjectIdentifier); + if (composite_nomem(identifier, c)) return; + identifier->guid = GUID_zero(); + identifier->sid = s->zero_sid; + identifier->dn = talloc_asprintf(identifier, "CN=NTDS Settings,%s", + s->dest_dsa.server_dn_str); + if (composite_nomem(identifier->dn, c)) return; + + /* allocate attribute array */ + num_attrs = 12; + attrs = talloc_array(r, struct drsuapi_DsReplicaAttribute, num_attrs); + if (composite_nomem(attrs, c)) return; + + /* ntSecurityDescriptor */ + { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + struct security_descriptor *v; + struct dom_sid *domain_admins_sid; + const char *domain_admins_sid_str; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + domain_admins_sid = dom_sid_add_rid(vs, s->domain.sid, DOMAIN_RID_ADMINS); + if (composite_nomem(domain_admins_sid, c)) return; + + domain_admins_sid_str = dom_sid_string(domain_admins_sid, domain_admins_sid); + if (composite_nomem(domain_admins_sid_str, c)) return; + + v = security_descriptor_dacl_create(vd, + 0, + /* owner: domain admins */ + domain_admins_sid_str, + /* owner group: domain admins */ + domain_admins_sid_str, + /* authenticated users */ + SID_NT_AUTHENTICATED_USERS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_STD_READ_CONTROL | + SEC_ADS_LIST | + SEC_ADS_READ_PROP | + SEC_ADS_LIST_OBJECT, + 0, + /* domain admins */ + domain_admins_sid_str, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_STD_REQUIRED | + SEC_ADS_CREATE_CHILD | + SEC_ADS_LIST | + SEC_ADS_SELF_WRITE | + SEC_ADS_READ_PROP | + SEC_ADS_WRITE_PROP | + SEC_ADS_DELETE_TREE | + SEC_ADS_LIST_OBJECT | + SEC_ADS_CONTROL_ACCESS, + 0, + /* system */ + SID_NT_SYSTEM, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_STD_REQUIRED | + SEC_ADS_CREATE_CHILD | + SEC_ADS_DELETE_CHILD | + SEC_ADS_LIST | + SEC_ADS_SELF_WRITE | + SEC_ADS_READ_PROP | + SEC_ADS_WRITE_PROP | + SEC_ADS_DELETE_TREE | + SEC_ADS_LIST_OBJECT | + SEC_ADS_CONTROL_ACCESS, + 0, + /* end */ + NULL); + if (composite_nomem(v, c)) return; + + ndr_err = ndr_push_struct_blob(&vd[0], vd, v, + (ndr_push_flags_fn_t)ndr_push_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_ntSecurityDescriptor; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* objectClass: nTDSDSA */ + { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + vd[0] = data_blob_talloc(vd, NULL, 4); + if (composite_nomem(vd[0].data, c)) return; + + /* value for nTDSDSA */ + SIVAL(vd[0].data, 0, 0x0017002F); + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_objectClass; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* objectCategory: CN=NTDS-DSA,CN=Schema,... or CN=NTDS-DSA-RO,CN=Schema,... */ + { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + struct drsuapi_DsReplicaObjectIdentifier3 v[1]; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + v[0].guid = GUID_zero(); + v[0].sid = s->zero_sid; + + if (s->rodc_join) { + v[0].dn = talloc_asprintf(vd, "CN=NTDS-DSA-RO,%s", + s->forest.schema_dn_str); + } else { + v[0].dn = talloc_asprintf(vd, "CN=NTDS-DSA,%s", + s->forest.schema_dn_str); + } + if (composite_nomem(v[0].dn, c)) return; + + ndr_err = ndr_push_struct_blob(&vd[0], vd, &v[0], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_objectCategory; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* invocationId: random guid */ + { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + const struct GUID *v; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + v = &s->dest_dsa.invocation_id; + + c->status = GUID_to_ndr_blob(v, vd, &vd[0]); + if (!composite_is_ok(c)) return; + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_invocationId; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* hasMasterNCs: ... */ + { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + struct drsuapi_DsReplicaObjectIdentifier3 v[3]; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 3); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 3); + if (composite_nomem(vd, c)) return; + + v[0].guid = GUID_zero(); + v[0].sid = s->zero_sid; + v[0].dn = s->forest.config_dn_str; + + v[1].guid = GUID_zero(); + v[1].sid = s->zero_sid; + v[1].dn = s->domain.dn_str; + + v[2].guid = GUID_zero(); + v[2].sid = s->zero_sid; + v[2].dn = s->forest.schema_dn_str; + + ndr_err = ndr_push_struct_blob(&vd[0], vd, &v[0], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + ndr_err = ndr_push_struct_blob(&vd[1], vd, &v[1], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + ndr_err = ndr_push_struct_blob(&vd[2], vd, &v[2], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + vs[0].blob = &vd[0]; + vs[1].blob = &vd[1]; + vs[2].blob = &vd[2]; + + attrs[i].attid = DRSUAPI_ATTID_hasMasterNCs; + attrs[i].value_ctr.num_values = 3; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* msDS-hasMasterNCs: ... */ + if (w2k3) { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + struct drsuapi_DsReplicaObjectIdentifier3 v[3]; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 3); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 3); + if (composite_nomem(vd, c)) return; + + v[0].guid = GUID_zero(); + v[0].sid = s->zero_sid; + v[0].dn = s->forest.config_dn_str; + + v[1].guid = GUID_zero(); + v[1].sid = s->zero_sid; + v[1].dn = s->domain.dn_str; + + v[2].guid = GUID_zero(); + v[2].sid = s->zero_sid; + v[2].dn = s->forest.schema_dn_str; + + ndr_err = ndr_push_struct_blob(&vd[0], vd, &v[0], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + ndr_err = ndr_push_struct_blob(&vd[1], vd, &v[1], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + ndr_err = ndr_push_struct_blob(&vd[2], vd, &v[2], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + vs[0].blob = &vd[0]; + vs[1].blob = &vd[1]; + vs[2].blob = &vd[2]; + + attrs[i].attid = DRSUAPI_ATTID_msDS_hasMasterNCs; + attrs[i].value_ctr.num_values = 3; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* dMDLocation: CN=Schema,... */ + { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + struct drsuapi_DsReplicaObjectIdentifier3 v[1]; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + v[0].guid = GUID_zero(); + v[0].sid = s->zero_sid; + v[0].dn = s->forest.schema_dn_str; + + ndr_err = ndr_push_struct_blob(&vd[0], vd, &v[0], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_dMDLocation; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* msDS-HasDomainNCs: <domain_partition> */ + if (w2k3) { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + struct drsuapi_DsReplicaObjectIdentifier3 v[1]; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + v[0].guid = GUID_zero(); + v[0].sid = s->zero_sid; + v[0].dn = s->domain.dn_str; + + ndr_err = ndr_push_struct_blob(&vd[0], vd, &v[0], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_msDS_HasDomainNCs; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* msDS-Behavior-Version */ + if (w2k3) { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + vd[0] = data_blob_talloc(vd, NULL, 4); + if (composite_nomem(vd[0].data, c)) return; + + SIVAL(vd[0].data, 0, get_dc_function_level(s->libnet->lp_ctx)); + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_msDS_Behavior_Version; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* systemFlags */ + { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + vd[0] = data_blob_talloc(vd, NULL, 4); + if (composite_nomem(vd[0].data, c)) return; + + if (s->rodc_join) { + SIVAL(vd[0].data, 0, SYSTEM_FLAG_CONFIG_ALLOW_RENAME); + } else { + SIVAL(vd[0].data, 0, SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE); + } + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_systemFlags; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* serverReference: ... */ + { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + struct drsuapi_DsReplicaObjectIdentifier3 v[1]; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + v[0].guid = GUID_zero(); + v[0].sid = s->zero_sid; + v[0].dn = s->dest_dsa.computer_dn_str; + + ndr_err = ndr_push_struct_blob(&vd[0], vd, &v[0], + (ndr_push_flags_fn_t)ndr_push_drsuapi_DsReplicaObjectIdentifier3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + c->status = ndr_map_error2ntstatus(ndr_err); + if (!composite_is_ok(c)) return; + } + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_serverReference; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* options:... */ + if (s->rodc_join) { + struct drsuapi_DsAttributeValue *vs; + DATA_BLOB *vd; + + vs = talloc_array(attrs, struct drsuapi_DsAttributeValue, 1); + if (composite_nomem(vs, c)) return; + + vd = talloc_array(vs, DATA_BLOB, 1); + if (composite_nomem(vd, c)) return; + + vd[0] = data_blob_talloc(vd, NULL, 4); + if (composite_nomem(vd[0].data, c)) return; + + SIVAL(vd[0].data, 0, DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL); + + vs[0].blob = &vd[0]; + + attrs[i].attid = DRSUAPI_ATTID_options; + attrs[i].value_ctr.num_values = 1; + attrs[i].value_ctr.values = vs; + + i++; + } + + /* truncate the attribute list to the attribute count we have filled in */ + num_attrs = i; + + /* setup request structure */ + r->in.bind_handle = &s->drsuapi1.bind_handle; + r->in.level = 2; + r->in.req = talloc(s, union drsuapi_DsAddEntryRequest); + r->in.req->req2.first_object.next_object = NULL; + r->in.req->req2.first_object.object.identifier = identifier; + r->in.req->req2.first_object.object.flags = 0x00000000; + r->in.req->req2.first_object.object.attribute_ctr.num_attributes= num_attrs; + r->in.req->req2.first_object.object.attribute_ctr.attributes = attrs; + + r->out.level_out = talloc(s, uint32_t); + r->out.ctr = talloc(s, union drsuapi_DsAddEntryCtr); + + s->ndr_struct_ptr = r; + subreq = dcerpc_drsuapi_DsAddEntry_r_send(s, c->event_ctx, + s->drsuapi1.drsuapi_handle, r); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, becomeDC_drsuapi1_add_entry_recv, s); +} + +static void becomeDC_drsuapi2_connect_recv(struct composite_context *req); +static NTSTATUS becomeDC_prepare_db(struct libnet_BecomeDC_state *s); + +static void becomeDC_drsuapi1_add_entry_recv(struct tevent_req *subreq) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + struct drsuapi_DsAddEntry *r = talloc_get_type_abort(s->ndr_struct_ptr, + struct drsuapi_DsAddEntry); + char *binding_str; + uint32_t assoc_group_id; + + s->ndr_struct_ptr = NULL; + + c->status = dcerpc_drsuapi_DsAddEntry_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!W_ERROR_IS_OK(r->out.result)) { + composite_error(c, werror_to_ntstatus(r->out.result)); + return; + } + + if (*r->out.level_out == 3) { + WERROR status; + union drsuapi_DsAddEntry_ErrData *err_data = r->out.ctr->ctr3.err_data; + + /* check for errors */ + status = err_data ? err_data->v1.status : WERR_OK; + if (!W_ERROR_IS_OK(status)) { + struct drsuapi_DsAddEntryErrorInfo_Attr_V1 *attr_err; + struct drsuapi_DsAddEntry_AttrErrListItem_V1 *attr_err_li; + struct drsuapi_DsAddEntryErrorInfo_Name_V1 *name_err; + struct drsuapi_DsAddEntryErrorInfo_Referr_V1 *ref_err; + struct drsuapi_DsAddEntry_RefErrListItem_V1 *ref_li; + + if (r->out.ctr->ctr3.err_ver != 1) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + DEBUG(0,("DsAddEntry (R3) of '%s' failed: " + "Errors: dir_err = %d, status = %s;\n", + r->in.req->req3.first_object.object.identifier->dn, + err_data->v1.dir_err, + win_errstr(err_data->v1.status))); + + if (!err_data->v1.info) { + DEBUG(0, ("DsAddEntry (R3): no error info returned!\n")); + composite_error(c, werror_to_ntstatus(status)); + return; + } + + /* dump more detailed error */ + switch (err_data->v1.dir_err) { + case DRSUAPI_DIRERR_ATTRIBUTE: + /* Dump attribute errors */ + attr_err = &err_data->v1.info->attr_err; + DEBUGADD(0,(" Attribute Error: object = %s, count = %d;\n", + attr_err->id->dn, + attr_err->count)); + attr_err_li = &attr_err->first; + for (; attr_err_li; attr_err_li = attr_err_li->next) { + struct drsuapi_DsAddEntry_AttrErr_V1 *err = &attr_err_li->err_data; + DEBUGADD(0,(" Error: err = %s, problem = 0x%08X, attid = 0x%08X;\n", + win_errstr(err->extended_err), + err->problem, + err->attid)); + /* TODO: should we print attribute value here? */ + } + break; + case DRSUAPI_DIRERR_NAME: + /* Dump Name resolution error */ + name_err = &err_data->v1.info->name_err; + DEBUGADD(0,(" Name Error: err = %s, problem = 0x%08X, id_matched = %s;\n", + win_errstr(name_err->extended_err), + name_err->problem, + name_err->id_matched->dn)); + break; + case DRSUAPI_DIRERR_REFERRAL: + /* Dump Referral errors */ + ref_err = &err_data->v1.info->referral_err; + DEBUGADD(0,(" Referral Error: extended_err = %s\n", + win_errstr(ref_err->extended_err))); + ref_li = &ref_err->refer; + for (; ref_li; ref_li = ref_li->next) { + struct drsuapi_DsaAddressListItem_V1 *addr; + DEBUGADD(0,(" Referral: id_target = %s, ref_type = 0x%04X,", + ref_li->id_target->dn, + ref_li->ref_type)); + if (ref_li->is_choice_set) { + DEBUGADD(0,(" choice = 0x%02X, ", + ref_li->choice)); + } + DEBUGADD(0,(" add_list (")); + for (addr = ref_li->addr_list; addr; addr = addr->next) { + DEBUGADD(0,("%s", addr->address->string)); + if (addr->next) { + DEBUGADD(0,(", ")); + } + } + DEBUGADD(0,(");\n")); + } + break; + case DRSUAPI_DIRERR_SECURITY: + /* Dump Security error. */ + DEBUGADD(0,(" Security Error: extended_err = %s, problem = 0x%08X\n", + win_errstr(err_data->v1.info->security_err.extended_err), + err_data->v1.info->security_err.problem)); + break; + case DRSUAPI_DIRERR_SERVICE: + /* Dump Service error. */ + DEBUGADD(0,(" Service Error: extended_err = %s, problem = 0x%08X\n", + win_errstr(err_data->v1.info->service_err.extended_err), + err_data->v1.info->service_err.problem)); + break; + case DRSUAPI_DIRERR_UPDATE: + /* Dump Update error. */ + DEBUGADD(0,(" Update Error: extended_err = %s, problem = 0x%08X\n", + win_errstr(err_data->v1.info->update_err.extended_err), + err_data->v1.info->update_err.problem)); + break; + case DRSUAPI_DIRERR_SYSTEM: + /* System error. */ + DEBUGADD(0,(" System Error: extended_err = %s, problem = 0x%08X\n", + win_errstr(err_data->v1.info->system_err.extended_err), + err_data->v1.info->system_err.problem)); + break; + case DRSUAPI_DIRERR_OK: /* mute compiler warnings */ + default: + DEBUGADD(0,(" Unknown DIRERR error class returned!\n")); + break; + } + + composite_error(c, werror_to_ntstatus(status)); + return; + } + + if (1 != r->out.ctr->ctr3.count) { + DEBUG(0,("DsAddEntry - Ctr3: something very wrong had happened - " + "method succeeded but objects returned are %d (expected 1).\n", + r->out.ctr->ctr3.count)); + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + s->dest_dsa.ntds_guid = r->out.ctr->ctr3.objects[0].guid; + + } else if (*r->out.level_out == 2) { + if (DRSUAPI_DIRERR_OK != r->out.ctr->ctr2.dir_err) { + DEBUG(0,("DsAddEntry failed with: dir_err = %d, extended_err = %s\n", + r->out.ctr->ctr2.dir_err, + win_errstr(r->out.ctr->ctr2.extended_err))); + composite_error(c, werror_to_ntstatus(r->out.ctr->ctr2.extended_err)); + return; + } + + if (1 != r->out.ctr->ctr2.count) { + DEBUG(0,("DsAddEntry: something very wrong had happened - " + "method succeeded but objects returned are %d (expected 1). " + "Errors: dir_err = %d, extended_err = %s\n", + r->out.ctr->ctr2.count, + r->out.ctr->ctr2.dir_err, + win_errstr(r->out.ctr->ctr2.extended_err))); + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + s->dest_dsa.ntds_guid = r->out.ctr->ctr2.objects[0].guid; + } else { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + talloc_free(r); + + s->dest_dsa.ntds_dn_str = talloc_asprintf(s, "CN=NTDS Settings,%s", + s->dest_dsa.server_dn_str); + if (composite_nomem(s->dest_dsa.ntds_dn_str, c)) return; + + c->status = becomeDC_prepare_db(s); + if (!composite_is_ok(c)) return; + + /* this avoids the epmapper lookup on the 2nd connection */ + binding_str = dcerpc_binding_string(s, s->drsuapi1.binding); + if (composite_nomem(binding_str, c)) return; + + c->status = dcerpc_parse_binding(s, binding_str, &s->drsuapi2.binding); + talloc_free(binding_str); + if (!composite_is_ok(c)) return; + + if (DEBUGLEVEL >= 10) { + c->status = dcerpc_binding_set_flags(s->drsuapi2.binding, + DCERPC_DEBUG_PRINT_BOTH, + 0); + if (!composite_is_ok(c)) return; + } + + /* w2k3 uses the same assoc_group_id as on the first connection, so we do */ + assoc_group_id = dcerpc_binding_get_assoc_group_id(s->drsuapi1.pipe->binding); + c->status = dcerpc_binding_set_assoc_group_id(s->drsuapi2.binding, assoc_group_id); + if (!composite_is_ok(c)) return; + + becomeDC_drsuapi_connect_send(s, &s->drsuapi2, becomeDC_drsuapi2_connect_recv); +} + +static NTSTATUS becomeDC_prepare_db(struct libnet_BecomeDC_state *s) +{ + if (!s->callbacks.prepare_db) return NT_STATUS_OK; + + s->_pp.domain = &s->domain; + s->_pp.forest = &s->forest; + s->_pp.source_dsa = &s->source_dsa; + s->_pp.dest_dsa = &s->dest_dsa; + + return s->callbacks.prepare_db(s->callbacks.private_data, &s->_pp); +} + +static void becomeDC_drsuapi2_bind_recv(struct tevent_req *subreq); + +static void becomeDC_drsuapi2_connect_recv(struct composite_context *req) +{ + struct libnet_BecomeDC_state *s = talloc_get_type(req->async.private_data, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + + c->status = dcerpc_pipe_connect_b_recv(req, s, &s->drsuapi2.pipe); + if (!composite_is_ok(c)) return; + + s->drsuapi2.drsuapi_handle = s->drsuapi2.pipe->binding_handle; + + c->status = gensec_session_key(s->drsuapi2.pipe->conn->security_state.generic_state, + s, + &s->drsuapi2.gensec_skey); + if (!composite_is_ok(c)) return; + + becomeDC_drsuapi_bind_send(s, &s->drsuapi2, becomeDC_drsuapi2_bind_recv); +} + +static void becomeDC_drsuapi3_connect_recv(struct composite_context *req); + +static void becomeDC_drsuapi2_bind_recv(struct tevent_req *subreq) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + char *binding_str; + uint32_t assoc_group_id; + WERROR status; + + c->status = dcerpc_drsuapi_DsBind_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + status = becomeDC_drsuapi_bind_recv(s, &s->drsuapi2); + if (!W_ERROR_IS_OK(status)) { + composite_error(c, werror_to_ntstatus(status)); + return; + } + + /* this avoids the epmapper lookup on the 3rd connection */ + binding_str = dcerpc_binding_string(s, s->drsuapi1.binding); + if (composite_nomem(binding_str, c)) return; + + c->status = dcerpc_parse_binding(s, binding_str, &s->drsuapi3.binding); + talloc_free(binding_str); + if (!composite_is_ok(c)) return; + + if (DEBUGLEVEL >= 10) { + c->status = dcerpc_binding_set_flags(s->drsuapi3.binding, + DCERPC_DEBUG_PRINT_BOTH, + 0); + if (!composite_is_ok(c)) return; + } + + /* w2k3 uses the same assoc_group_id as on the first connection, so we do */ + assoc_group_id = dcerpc_binding_get_assoc_group_id(s->drsuapi1.pipe->binding); + c->status = dcerpc_binding_set_assoc_group_id(s->drsuapi3.binding, assoc_group_id); + if (!composite_is_ok(c)) return; + /* w2k3 uses the concurrent multiplex feature on the 3rd connection, so we do */ + c->status = dcerpc_binding_set_flags(s->drsuapi3.binding, + DCERPC_CONCURRENT_MULTIPLEX, + 0); + if (!composite_is_ok(c)) return; + + becomeDC_drsuapi_connect_send(s, &s->drsuapi3, becomeDC_drsuapi3_connect_recv); +} + +static void becomeDC_drsuapi3_pull_schema_send(struct libnet_BecomeDC_state *s); + +static void becomeDC_drsuapi3_connect_recv(struct composite_context *req) +{ + struct libnet_BecomeDC_state *s = talloc_get_type(req->async.private_data, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + + c->status = dcerpc_pipe_connect_b_recv(req, s, &s->drsuapi3.pipe); + if (!composite_is_ok(c)) return; + + s->drsuapi3.drsuapi_handle = s->drsuapi3.pipe->binding_handle; + + c->status = gensec_session_key(s->drsuapi3.pipe->conn->security_state.generic_state, + s, + &s->drsuapi3.gensec_skey); + if (!composite_is_ok(c)) return; + + becomeDC_drsuapi3_pull_schema_send(s); +} + +static void becomeDC_drsuapi_pull_partition_send(struct libnet_BecomeDC_state *s, + struct becomeDC_drsuapi *drsuapi_h, + struct becomeDC_drsuapi *drsuapi_p, + struct libnet_BecomeDC_Partition *partition, + void (*recv_fn)(struct tevent_req *subreq)) +{ + struct composite_context *c = s->creq; + struct drsuapi_DsGetNCChanges *r; + struct tevent_req *subreq; + + r = talloc(s, struct drsuapi_DsGetNCChanges); + if (composite_nomem(r, c)) return; + + r->out.level_out = talloc(r, uint32_t); + if (composite_nomem(r->out.level_out, c)) return; + r->in.req = talloc(r, union drsuapi_DsGetNCChangesRequest); + if (composite_nomem(r->in.req, c)) return; + r->out.ctr = talloc(r, union drsuapi_DsGetNCChangesCtr); + if (composite_nomem(r->out.ctr, c)) return; + + r->in.bind_handle = &drsuapi_h->bind_handle; + if (drsuapi_h->remote_info28.supported_extensions & DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8) { + r->in.level = 8; + r->in.req->req8.destination_dsa_guid = partition->destination_dsa_guid; + r->in.req->req8.source_dsa_invocation_id= partition->source_dsa_invocation_id; + r->in.req->req8.naming_context = &partition->nc; + r->in.req->req8.highwatermark = partition->highwatermark; + r->in.req->req8.uptodateness_vector = NULL; + r->in.req->req8.replica_flags = partition->replica_flags; + r->in.req->req8.max_object_count = 133; + r->in.req->req8.max_ndr_size = 1336811; + r->in.req->req8.extended_op = DRSUAPI_EXOP_NONE; + r->in.req->req8.fsmo_info = 0; + r->in.req->req8.partial_attribute_set = NULL; + r->in.req->req8.partial_attribute_set_ex= NULL; + r->in.req->req8.mapping_ctr.num_mappings= 0; + r->in.req->req8.mapping_ctr.mappings = NULL; + } else { + r->in.level = 5; + r->in.req->req5.destination_dsa_guid = partition->destination_dsa_guid; + r->in.req->req5.source_dsa_invocation_id= partition->source_dsa_invocation_id; + r->in.req->req5.naming_context = &partition->nc; + r->in.req->req5.highwatermark = partition->highwatermark; + r->in.req->req5.uptodateness_vector = NULL; + r->in.req->req5.replica_flags = partition->replica_flags; + r->in.req->req5.max_object_count = 133; + r->in.req->req5.max_ndr_size = 1336770; + r->in.req->req5.extended_op = DRSUAPI_EXOP_NONE; + r->in.req->req5.fsmo_info = 0; + } + + /* + * we should try to use the drsuapi_p->pipe here, as w2k3 does + * but it seems that some extra flags in the DCERPC Bind call + * are needed for it. Or the same KRB5 TGS is needed on both + * connections. + */ + s->ndr_struct_ptr = r; + subreq = dcerpc_drsuapi_DsGetNCChanges_r_send(s, c->event_ctx, + drsuapi_p->drsuapi_handle, + r); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, recv_fn, s); +} + +static WERROR becomeDC_drsuapi_pull_partition_recv(struct libnet_BecomeDC_state *s, + struct becomeDC_drsuapi *drsuapi_h, + struct becomeDC_drsuapi *drsuapi_p, + struct libnet_BecomeDC_Partition *partition, + struct drsuapi_DsGetNCChanges *r) +{ + uint32_t req_level = r->in.level; + struct drsuapi_DsGetNCChangesRequest5 *req5 = NULL; + struct drsuapi_DsGetNCChangesRequest8 *req8 = NULL; + struct drsuapi_DsGetNCChangesRequest10 *req10 = NULL; + uint32_t ctr_level = 0; + struct drsuapi_DsGetNCChangesCtr1 *ctr1 = NULL; + struct drsuapi_DsGetNCChangesCtr6 *ctr6 = NULL; + struct GUID *source_dsa_guid = NULL; + struct GUID *source_dsa_invocation_id = NULL; + struct drsuapi_DsReplicaHighWaterMark *new_highwatermark = NULL; + bool more_data = false; + WERROR werr; + + if (!W_ERROR_IS_OK(r->out.result)) { + return r->out.result; + } + + switch (r->in.level) { + case 0: + /* none */ + break; + case 5: + req5 = &r->in.req->req5; + break; + case 8: + req8 = &r->in.req->req8; + break; + case 10: + req10 = &r->in.req->req10; + break; + default: + return WERR_INVALID_PARAMETER; + } + + if (*r->out.level_out == 1) { + ctr_level = 1; + ctr1 = &r->out.ctr->ctr1; + } else if (*r->out.level_out == 2 && + r->out.ctr->ctr2.mszip1.ts) { + ctr_level = 1; + ctr1 = &r->out.ctr->ctr2.mszip1.ts->ctr1; + } else if (*r->out.level_out == 6) { + ctr_level = 6; + ctr6 = &r->out.ctr->ctr6; + } else if (*r->out.level_out == 7 && + r->out.ctr->ctr7.level == 6 && + r->out.ctr->ctr7.type == DRSUAPI_COMPRESSION_TYPE_MSZIP && + r->out.ctr->ctr7.ctr.mszip6.ts) { + ctr_level = 6; + ctr6 = &r->out.ctr->ctr7.ctr.mszip6.ts->ctr6; + } else if (*r->out.level_out == 7 && + r->out.ctr->ctr7.level == 6 && + r->out.ctr->ctr7.type == DRSUAPI_COMPRESSION_TYPE_XPRESS && + r->out.ctr->ctr7.ctr.xpress6.ts) { + ctr_level = 6; + ctr6 = &r->out.ctr->ctr7.ctr.xpress6.ts->ctr6; + } else { + return WERR_BAD_NET_RESP; + } + + if (!ctr1 && ! ctr6) { + return WERR_BAD_NET_RESP; + } + + if (ctr_level == 6) { + if (!W_ERROR_IS_OK(ctr6->drs_error)) { + return ctr6->drs_error; + } + } + + switch (ctr_level) { + case 1: + source_dsa_guid = &ctr1->source_dsa_guid; + source_dsa_invocation_id = &ctr1->source_dsa_invocation_id; + new_highwatermark = &ctr1->new_highwatermark; + more_data = ctr1->more_data; + break; + case 6: + source_dsa_guid = &ctr6->source_dsa_guid; + source_dsa_invocation_id = &ctr6->source_dsa_invocation_id; + new_highwatermark = &ctr6->new_highwatermark; + more_data = ctr6->more_data; + break; + } + + partition->highwatermark = *new_highwatermark; + partition->source_dsa_guid = *source_dsa_guid; + partition->source_dsa_invocation_id = *source_dsa_invocation_id; + partition->more_data = more_data; + + if (!partition->store_chunk) return WERR_OK; + + s->_sc.domain = &s->domain; + s->_sc.forest = &s->forest; + s->_sc.source_dsa = &s->source_dsa; + s->_sc.dest_dsa = &s->dest_dsa; + s->_sc.partition = partition; + s->_sc.req_level = req_level; + s->_sc.req5 = req5; + s->_sc.req8 = req8; + s->_sc.req10 = req10; + s->_sc.ctr_level = ctr_level; + s->_sc.ctr1 = ctr1; + s->_sc.ctr6 = ctr6; + /* + * we need to use the drsuapi_p->gensec_skey here, + * when we use drsuapi_p->pipe in the for this request + */ + s->_sc.gensec_skey = &drsuapi_p->gensec_skey; + + werr = partition->store_chunk(s->callbacks.private_data, &s->_sc); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + return WERR_OK; +} + +static void becomeDC_drsuapi3_pull_schema_recv(struct tevent_req *subreq); + +static void becomeDC_drsuapi3_pull_schema_send(struct libnet_BecomeDC_state *s) +{ + s->schema_part.nc.guid = GUID_zero(); + s->schema_part.nc.sid = s->zero_sid; + s->schema_part.nc.dn = s->forest.schema_dn_str; + + s->schema_part.destination_dsa_guid = s->drsuapi2.bind_guid; + + s->schema_part.replica_flags = DRSUAPI_DRS_WRIT_REP + | DRSUAPI_DRS_INIT_SYNC + | DRSUAPI_DRS_PER_SYNC + | DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS + | DRSUAPI_DRS_NEVER_SYNCED + | DRSUAPI_DRS_USE_COMPRESSION + | DRSUAPI_DRS_GET_ANC; + if (s->rodc_join) { + s->schema_part.replica_flags &= ~DRSUAPI_DRS_WRIT_REP; + } + + s->schema_part.store_chunk = s->callbacks.schema_chunk; + + becomeDC_drsuapi_pull_partition_send(s, &s->drsuapi2, &s->drsuapi3, &s->schema_part, + becomeDC_drsuapi3_pull_schema_recv); +} + +static void becomeDC_drsuapi3_pull_config_send(struct libnet_BecomeDC_state *s); + +static void becomeDC_drsuapi3_pull_schema_recv(struct tevent_req *subreq) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + struct drsuapi_DsGetNCChanges *r = talloc_get_type_abort(s->ndr_struct_ptr, + struct drsuapi_DsGetNCChanges); + WERROR status; + + s->ndr_struct_ptr = NULL; + + c->status = dcerpc_drsuapi_DsGetNCChanges_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + status = becomeDC_drsuapi_pull_partition_recv(s, &s->drsuapi2, &s->drsuapi3, &s->schema_part, r); + if (!W_ERROR_IS_OK(status)) { + composite_error(c, werror_to_ntstatus(status)); + return; + } + + talloc_free(r); + + if (s->schema_part.more_data) { + becomeDC_drsuapi_pull_partition_send(s, &s->drsuapi2, &s->drsuapi3, &s->schema_part, + becomeDC_drsuapi3_pull_schema_recv); + return; + } + + becomeDC_drsuapi3_pull_config_send(s); +} + +static void becomeDC_drsuapi3_pull_config_recv(struct tevent_req *subreq); + +static void becomeDC_drsuapi3_pull_config_send(struct libnet_BecomeDC_state *s) +{ + s->config_part.nc.guid = GUID_zero(); + s->config_part.nc.sid = s->zero_sid; + s->config_part.nc.dn = s->forest.config_dn_str; + + s->config_part.destination_dsa_guid = s->drsuapi2.bind_guid; + + s->config_part.replica_flags = DRSUAPI_DRS_WRIT_REP + | DRSUAPI_DRS_INIT_SYNC + | DRSUAPI_DRS_PER_SYNC + | DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS + | DRSUAPI_DRS_NEVER_SYNCED + | DRSUAPI_DRS_USE_COMPRESSION + | DRSUAPI_DRS_GET_ANC; + if (s->rodc_join) { + s->schema_part.replica_flags &= ~DRSUAPI_DRS_WRIT_REP; + } + + s->config_part.store_chunk = s->callbacks.config_chunk; + + becomeDC_drsuapi_pull_partition_send(s, &s->drsuapi2, &s->drsuapi3, &s->config_part, + becomeDC_drsuapi3_pull_config_recv); +} + +static void becomeDC_drsuapi3_pull_config_recv(struct tevent_req *subreq) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + struct drsuapi_DsGetNCChanges *r = talloc_get_type_abort(s->ndr_struct_ptr, + struct drsuapi_DsGetNCChanges); + WERROR status; + + s->ndr_struct_ptr = NULL; + + c->status = dcerpc_drsuapi_DsGetNCChanges_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + status = becomeDC_drsuapi_pull_partition_recv(s, &s->drsuapi2, &s->drsuapi3, &s->config_part, r); + if (!W_ERROR_IS_OK(status)) { + composite_error(c, werror_to_ntstatus(status)); + return; + } + + talloc_free(r); + + if (s->config_part.more_data) { + becomeDC_drsuapi_pull_partition_send(s, &s->drsuapi2, &s->drsuapi3, &s->config_part, + becomeDC_drsuapi3_pull_config_recv); + return; + } + + becomeDC_connect_ldap2(s); +} + +static void becomeDC_drsuapi3_pull_domain_recv(struct tevent_req *subreq); + +static void becomeDC_drsuapi3_pull_domain_send(struct libnet_BecomeDC_state *s) +{ + s->domain_part.nc.guid = GUID_zero(); + s->domain_part.nc.sid = s->zero_sid; + s->domain_part.nc.dn = s->domain.dn_str; + + s->domain_part.destination_dsa_guid = s->drsuapi2.bind_guid; + + s->domain_part.replica_flags = DRSUAPI_DRS_WRIT_REP + | DRSUAPI_DRS_INIT_SYNC + | DRSUAPI_DRS_PER_SYNC + | DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS + | DRSUAPI_DRS_NEVER_SYNCED + | DRSUAPI_DRS_USE_COMPRESSION + | DRSUAPI_DRS_GET_ANC; + if (s->critical_only) { + s->domain_part.replica_flags |= DRSUAPI_DRS_CRITICAL_ONLY; + } + if (s->rodc_join) { + s->schema_part.replica_flags &= ~DRSUAPI_DRS_WRIT_REP; + } + + s->domain_part.store_chunk = s->callbacks.domain_chunk; + + becomeDC_drsuapi_pull_partition_send(s, &s->drsuapi2, &s->drsuapi3, &s->domain_part, + becomeDC_drsuapi3_pull_domain_recv); +} + +static void becomeDC_drsuapi_update_refs_send(struct libnet_BecomeDC_state *s, + struct becomeDC_drsuapi *drsuapi, + struct libnet_BecomeDC_Partition *partition, + void (*recv_fn)(struct tevent_req *subreq)); +static void becomeDC_drsuapi2_update_refs_schema_recv(struct tevent_req *subreq); + +static void becomeDC_drsuapi3_pull_domain_recv(struct tevent_req *subreq) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + struct drsuapi_DsGetNCChanges *r = talloc_get_type_abort(s->ndr_struct_ptr, + struct drsuapi_DsGetNCChanges); + WERROR status; + + s->ndr_struct_ptr = NULL; + + c->status = dcerpc_drsuapi_DsGetNCChanges_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + status = becomeDC_drsuapi_pull_partition_recv(s, &s->drsuapi2, &s->drsuapi3, &s->domain_part, r); + if (!W_ERROR_IS_OK(status)) { + composite_error(c, werror_to_ntstatus(status)); + return; + } + + talloc_free(r); + + if (s->domain_part.more_data) { + becomeDC_drsuapi_pull_partition_send(s, &s->drsuapi2, &s->drsuapi3, &s->domain_part, + becomeDC_drsuapi3_pull_domain_recv); + return; + } + + if (s->critical_only) { + /* Remove the critical and ANC */ + s->domain_part.replica_flags ^= DRSUAPI_DRS_CRITICAL_ONLY | DRSUAPI_DRS_GET_ANC; + s->critical_only = false; + becomeDC_drsuapi_pull_partition_send(s, &s->drsuapi2, &s->drsuapi3, &s->domain_part, + becomeDC_drsuapi3_pull_domain_recv); + return; + } + becomeDC_drsuapi_update_refs_send(s, &s->drsuapi2, &s->schema_part, + becomeDC_drsuapi2_update_refs_schema_recv); +} + +static void becomeDC_drsuapi_update_refs_send(struct libnet_BecomeDC_state *s, + struct becomeDC_drsuapi *drsuapi, + struct libnet_BecomeDC_Partition *partition, + void (*recv_fn)(struct tevent_req *subreq)) +{ + struct composite_context *c = s->creq; + struct drsuapi_DsReplicaUpdateRefs *r; + const char *ntds_guid_str; + const char *ntds_dns_name; + struct tevent_req *subreq; + + r = talloc(s, struct drsuapi_DsReplicaUpdateRefs); + if (composite_nomem(r, c)) return; + + ntds_guid_str = GUID_string(r, &s->dest_dsa.ntds_guid); + if (composite_nomem(ntds_guid_str, c)) return; + + ntds_dns_name = talloc_asprintf(r, "%s._msdcs.%s", + ntds_guid_str, + s->forest.dns_name); + if (composite_nomem(ntds_dns_name, c)) return; + + r->in.bind_handle = &drsuapi->bind_handle; + r->in.level = 1; + r->in.req.req1.naming_context = &partition->nc; + r->in.req.req1.dest_dsa_dns_name= ntds_dns_name; + r->in.req.req1.dest_dsa_guid = s->dest_dsa.ntds_guid; + r->in.req.req1.options = DRSUAPI_DRS_ADD_REF | DRSUAPI_DRS_DEL_REF; + + /* I think this is how we mark ourselves as a RODC */ + if (!lpcfg_parm_bool(s->libnet->lp_ctx, NULL, "repl", "RODC", false)) { + r->in.req.req1.options |= DRSUAPI_DRS_WRIT_REP; + } + + s->ndr_struct_ptr = r; + subreq = dcerpc_drsuapi_DsReplicaUpdateRefs_r_send(s, c->event_ctx, + drsuapi->drsuapi_handle, + r); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, recv_fn, s); +} + +static void becomeDC_drsuapi2_update_refs_config_recv(struct tevent_req *subreq); + +static void becomeDC_drsuapi2_update_refs_schema_recv(struct tevent_req *subreq) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + struct drsuapi_DsReplicaUpdateRefs *r = talloc_get_type_abort(s->ndr_struct_ptr, + struct drsuapi_DsReplicaUpdateRefs); + + s->ndr_struct_ptr = NULL; + + c->status = dcerpc_drsuapi_DsReplicaUpdateRefs_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!W_ERROR_IS_OK(r->out.result)) { + composite_error(c, werror_to_ntstatus(r->out.result)); + return; + } + + talloc_free(r); + + becomeDC_drsuapi_update_refs_send(s, &s->drsuapi2, &s->config_part, + becomeDC_drsuapi2_update_refs_config_recv); +} + +static void becomeDC_drsuapi2_update_refs_domain_recv(struct tevent_req *subreq); + +static void becomeDC_drsuapi2_update_refs_config_recv(struct tevent_req *subreq) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + struct drsuapi_DsReplicaUpdateRefs *r = talloc_get_type(s->ndr_struct_ptr, + struct drsuapi_DsReplicaUpdateRefs); + + s->ndr_struct_ptr = NULL; + + c->status = dcerpc_drsuapi_DsReplicaUpdateRefs_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!W_ERROR_IS_OK(r->out.result)) { + composite_error(c, werror_to_ntstatus(r->out.result)); + return; + } + + talloc_free(r); + + becomeDC_drsuapi_update_refs_send(s, &s->drsuapi2, &s->domain_part, + becomeDC_drsuapi2_update_refs_domain_recv); +} + +static void becomeDC_drsuapi2_update_refs_domain_recv(struct tevent_req *subreq) +{ + struct libnet_BecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_BecomeDC_state); + struct composite_context *c = s->creq; + struct drsuapi_DsReplicaUpdateRefs *r = talloc_get_type(s->ndr_struct_ptr, + struct drsuapi_DsReplicaUpdateRefs); + + s->ndr_struct_ptr = NULL; + + c->status = dcerpc_drsuapi_DsReplicaUpdateRefs_r_recv(subreq, r); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!W_ERROR_IS_OK(r->out.result)) { + composite_error(c, werror_to_ntstatus(r->out.result)); + return; + } + + talloc_free(r); + + /* TODO: use DDNS updates and register dns names */ + composite_done(c); +} + +static NTSTATUS becomeDC_ldap2_modify_computer(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_message *msg; + unsigned int i; + uint32_t user_account_control = UF_SERVER_TRUST_ACCOUNT | + UF_TRUSTED_FOR_DELEGATION; + + /* as the value is already as we want it to be, we're done */ + if (s->dest_dsa.user_account_control == user_account_control) { + return NT_STATUS_OK; + } + + /* make a 'modify' msg, and only for serverReference */ + msg = ldb_msg_new(s); + NT_STATUS_HAVE_NO_MEMORY(msg); + msg->dn = ldb_dn_new(msg, s->ldap2.ldb, s->dest_dsa.computer_dn_str); + NT_STATUS_HAVE_NO_MEMORY(msg->dn); + + ret = samdb_msg_add_uint(s->ldap2.ldb, msg, msg, "userAccountControl", + user_account_control); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + + /* mark all the message elements (should be just one) + as LDB_FLAG_MOD_REPLACE */ + for (i=0;i<msg->num_elements;i++) { + msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + + ret = ldb_modify(s->ldap2.ldb, msg); + talloc_free(msg); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } + + s->dest_dsa.user_account_control = user_account_control; + + return NT_STATUS_OK; +} + +static NTSTATUS becomeDC_ldap2_move_computer(struct libnet_BecomeDC_state *s) +{ + int ret; + struct ldb_dn *old_dn; + struct ldb_dn *new_dn; + + ret = dsdb_wellknown_dn(s->ldap2.ldb, s, + ldb_get_default_basedn(s->ldap2.ldb), + DS_GUID_DOMAIN_CONTROLLERS_CONTAINER, + &new_dn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } + + if (!ldb_dn_add_child_fmt(new_dn, "CN=%s", s->dest_dsa.netbios_name)) { + talloc_free(new_dn); + return NT_STATUS_NO_MEMORY; + } + + old_dn = ldb_dn_new(new_dn, s->ldap2.ldb, s->dest_dsa.computer_dn_str); + NT_STATUS_HAVE_NO_MEMORY(old_dn); + + if (ldb_dn_compare(old_dn, new_dn) == 0) { + /* we don't need to rename if the old and new dn match */ + talloc_free(new_dn); + return NT_STATUS_OK; + } + + ret = ldb_rename(s->ldap2.ldb, old_dn, new_dn); + if (ret != LDB_SUCCESS) { + talloc_free(new_dn); + return NT_STATUS_LDAP(ret); + } + + s->dest_dsa.computer_dn_str = ldb_dn_alloc_linearized(s, new_dn); + NT_STATUS_HAVE_NO_MEMORY(s->dest_dsa.computer_dn_str); + + talloc_free(new_dn); + + return NT_STATUS_OK; +} + +static void becomeDC_connect_ldap2(struct libnet_BecomeDC_state *s) +{ + struct composite_context *c = s->creq; + + c->status = becomeDC_ldap_connect(s, &s->ldap2); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap2_modify_computer(s); + if (!composite_is_ok(c)) return; + + c->status = becomeDC_ldap2_move_computer(s); + if (!composite_is_ok(c)) return; + + s->critical_only = true; + becomeDC_drsuapi3_pull_domain_send(s); +} + +struct composite_context *libnet_BecomeDC_send(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_BecomeDC *r) +{ + struct composite_context *c; + struct libnet_BecomeDC_state *s; + char *tmp_name; + + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct libnet_BecomeDC_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + s->creq = c; + s->libnet = ctx; + + /* Domain input */ + s->domain.dns_name = talloc_strdup(s, r->in.domain_dns_name); + if (composite_nomem(s->domain.dns_name, c)) return c; + s->domain.netbios_name = talloc_strdup(s, r->in.domain_netbios_name); + if (composite_nomem(s->domain.netbios_name, c)) return c; + s->domain.sid = dom_sid_dup(s, r->in.domain_sid); + if (composite_nomem(s->domain.sid, c)) return c; + + /* Source DSA input */ + s->source_dsa.address = talloc_strdup(s, r->in.source_dsa_address); + if (composite_nomem(s->source_dsa.address, c)) return c; + + /* Destination DSA input */ + s->dest_dsa.netbios_name= talloc_strdup(s, r->in.dest_dsa_netbios_name); + if (composite_nomem(s->dest_dsa.netbios_name, c)) return c; + + /* Destination DSA dns_name construction */ + tmp_name = strlower_talloc(s, s->dest_dsa.netbios_name); + if (composite_nomem(tmp_name, c)) return c; + tmp_name = talloc_asprintf_append_buffer(tmp_name, ".%s",s->domain.dns_name); + if (composite_nomem(tmp_name, c)) return c; + s->dest_dsa.dns_name = tmp_name; + + /* Callback function pointers */ + s->callbacks = r->in.callbacks; + + /* RODC join*/ + s->rodc_join = r->in.rodc_join; + + becomeDC_send_cldap(s); + return c; +} + +NTSTATUS libnet_BecomeDC_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, struct libnet_BecomeDC *r) +{ + NTSTATUS status; + + status = composite_wait(c); + + ZERO_STRUCT(r->out); + + talloc_free(c); + return status; +} + +NTSTATUS libnet_BecomeDC(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_BecomeDC *r) +{ + NTSTATUS status; + struct composite_context *c; + c = libnet_BecomeDC_send(ctx, mem_ctx, r); + status = libnet_BecomeDC_recv(c, mem_ctx, r); + return status; +} diff --git a/source4/libnet/libnet_become_dc.h b/source4/libnet/libnet_become_dc.h new file mode 100644 index 0000000..f050c22 --- /dev/null +++ b/source4/libnet/libnet_become_dc.h @@ -0,0 +1,152 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher <metze@samba.org> 2006 + + 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_BECOME_DC_H +#define _LIBNET_BECOME_DC_H + +#include "librpc/gen_ndr/drsuapi.h" + +struct libnet_BecomeDC_Domain { + /* input */ + const char *dns_name; + const char *netbios_name; + const struct dom_sid *sid; + + /* constructed */ + struct GUID guid; + const char *dn_str; + uint32_t behavior_version; + uint32_t w2k3_update_revision; +}; + +struct libnet_BecomeDC_Forest { + /* constructed */ + const char *dns_name; + const char *root_dn_str; + const char *config_dn_str; + uint32_t crossref_behavior_version; + const char *schema_dn_str; + uint32_t schema_object_version; +}; + +struct libnet_BecomeDC_SourceDSA { + /* input */ + const char *address; + + /* constructed */ + const char *dns_name; + const char *netbios_name; + const char *site_name; + const char *server_dn_str; + const char *ntds_dn_str; +}; + +struct libnet_BecomeDC_CheckOptions { + const struct libnet_BecomeDC_Domain *domain; + const struct libnet_BecomeDC_Forest *forest; + const struct libnet_BecomeDC_SourceDSA *source_dsa; +}; + +struct libnet_BecomeDC_DestDSA { + /* input */ + const char *netbios_name; + + /* constructed */ + const char *dns_name; + const char *site_name; + struct GUID site_guid; + const char *computer_dn_str; + const char *server_dn_str; + const char *ntds_dn_str; + struct GUID ntds_guid; + struct GUID invocation_id; + uint32_t user_account_control; +}; + +struct libnet_BecomeDC_PrepareDB { + const struct libnet_BecomeDC_Domain *domain; + const struct libnet_BecomeDC_Forest *forest; + const struct libnet_BecomeDC_SourceDSA *source_dsa; + const struct libnet_BecomeDC_DestDSA *dest_dsa; +}; + +struct libnet_BecomeDC_StoreChunk; + +struct libnet_BecomeDC_Partition { + struct drsuapi_DsReplicaObjectIdentifier nc; + struct GUID destination_dsa_guid; + struct GUID source_dsa_guid; + struct GUID source_dsa_invocation_id; + struct drsuapi_DsReplicaHighWaterMark highwatermark; + bool more_data; + uint32_t replica_flags; + + WERROR (*store_chunk)(void *private_data, + const struct libnet_BecomeDC_StoreChunk *info); +}; + +struct libnet_BecomeDC_StoreChunk { + const struct libnet_BecomeDC_Domain *domain; + const struct libnet_BecomeDC_Forest *forest; + const struct libnet_BecomeDC_SourceDSA *source_dsa; + const struct libnet_BecomeDC_DestDSA *dest_dsa; + const struct libnet_BecomeDC_Partition *partition; + uint32_t req_level; + const struct drsuapi_DsGetNCChangesRequest5 *req5; + const struct drsuapi_DsGetNCChangesRequest8 *req8; + const struct drsuapi_DsGetNCChangesRequest10 *req10; + uint32_t ctr_level; + const struct drsuapi_DsGetNCChangesCtr1 *ctr1; + const struct drsuapi_DsGetNCChangesCtr6 *ctr6; + const DATA_BLOB *gensec_skey; +}; + +struct libnet_BecomeDC_Callbacks { + void *private_data; + NTSTATUS (*check_options)(void *private_data, + const struct libnet_BecomeDC_CheckOptions *info); + NTSTATUS (*prepare_db)(void *private_data, + const struct libnet_BecomeDC_PrepareDB *info); + WERROR (*schema_chunk)(void *private_data, + const struct libnet_BecomeDC_StoreChunk *info); + WERROR (*config_chunk)(void *private_data, + const struct libnet_BecomeDC_StoreChunk *info); + WERROR (*domain_chunk)(void *private_data, + const struct libnet_BecomeDC_StoreChunk *info); +}; + +struct libnet_BecomeDC { + struct { + const char *domain_dns_name; + const char *domain_netbios_name; + const struct dom_sid *domain_sid; + const char *source_dsa_address; + const char *dest_dsa_netbios_name; + + struct libnet_BecomeDC_Callbacks callbacks; + + bool rodc_join; + } in; + + struct { + const char *error_string; + } out; +}; + +#endif /* _LIBNET_BECOME_DC_H */ diff --git a/source4/libnet/libnet_domain.c b/source4/libnet/libnet_domain.c new file mode 100644 index 0000000..a7499b8 --- /dev/null +++ b/source4/libnet/libnet_domain.c @@ -0,0 +1,1304 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + 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/>. +*/ + +/* + a composite function for domain handling on samr and lsa pipes +*/ + +#include "includes.h" +#include "libcli/composite/composite.h" +#include "libnet/libnet.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" + + +struct domain_open_samr_state { + struct libnet_context *ctx; + struct dcerpc_pipe *pipe; + struct libnet_RpcConnect rpcconn; + struct samr_Connect connect; + struct samr_LookupDomain lookup; + struct samr_OpenDomain open; + struct samr_Close close; + struct lsa_String domain_name; + uint32_t access_mask; + struct policy_handle connect_handle; + struct policy_handle domain_handle; + struct dom_sid2 *domain_sid; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_domain_open_close(struct tevent_req *subreq); +static void continue_domain_open_connect(struct tevent_req *subreq); +static void continue_domain_open_lookup(struct tevent_req *subreq); +static void continue_domain_open_open(struct tevent_req *subreq); + + +/** + * Stage 0.5 (optional): Connect to samr rpc pipe + */ +static void continue_domain_open_rpc_connect(struct composite_context *ctx) +{ + struct composite_context *c; + struct domain_open_samr_state *s; + struct tevent_req *subreq; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_open_samr_state); + + c->status = libnet_RpcConnect_recv(ctx, s->ctx, c, &s->rpcconn); + if (!composite_is_ok(c)) return; + + s->pipe = s->rpcconn.out.dcerpc_pipe; + + /* preparing parameters for samr_Connect rpc call */ + s->connect.in.system_name = 0; + s->connect.in.access_mask = s->access_mask; + s->connect.out.connect_handle = &s->connect_handle; + + /* send request */ + subreq = dcerpc_samr_Connect_r_send(s, c->event_ctx, + s->pipe->binding_handle, + &s->connect); + if (composite_nomem(subreq, c)) return; + + /* callback handler */ + tevent_req_set_callback(subreq, continue_domain_open_connect, c); +} + + +/** + * Stage 0.5 (optional): Close existing (in libnet context) domain + * handle + */ +static void continue_domain_open_close(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_open_samr_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_open_samr_state); + + /* receive samr_Close reply */ + c->status = dcerpc_samr_Close_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_SamrClose; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + /* reset domain handle and associated data in libnet_context */ + s->ctx->samr.name = NULL; + s->ctx->samr.access_mask = 0; + ZERO_STRUCT(s->ctx->samr.handle); + + /* preparing parameters for samr_Connect rpc call */ + s->connect.in.system_name = 0; + s->connect.in.access_mask = s->access_mask; + s->connect.out.connect_handle = &s->connect_handle; + + /* send request */ + subreq = dcerpc_samr_Connect_r_send(s, c->event_ctx, + s->pipe->binding_handle, + &s->connect); + if (composite_nomem(subreq, c)) return; + + /* callback handler */ + tevent_req_set_callback(subreq, continue_domain_open_connect, c); +} + + +/** + * Stage 1: Connect to SAM server. + */ +static void continue_domain_open_connect(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_open_samr_state *s; + struct samr_LookupDomain *r; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_open_samr_state); + + /* receive samr_Connect reply */ + c->status = dcerpc_samr_Connect_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_SamrConnect; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + r = &s->lookup; + + /* prepare for samr_LookupDomain call */ + r->in.connect_handle = &s->connect_handle; + r->in.domain_name = &s->domain_name; + r->out.sid = talloc(s, struct dom_sid2 *); + if (composite_nomem(r->out.sid, c)) return; + + subreq = dcerpc_samr_LookupDomain_r_send(s, c->event_ctx, + s->pipe->binding_handle, + r); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_domain_open_lookup, c); +} + + +/** + * Stage 2: Lookup domain by name. + */ +static void continue_domain_open_lookup(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_open_samr_state *s; + struct samr_OpenDomain *r; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_open_samr_state); + + /* receive samr_LookupDomain reply */ + c->status = dcerpc_samr_LookupDomain_r_recv(subreq, s); + TALLOC_FREE(subreq); + + if (s->monitor_fn) { + struct monitor_msg msg; + struct msg_rpc_lookup_domain data; + + data.domain_name = s->domain_name.string; + + msg.type = mon_SamrLookupDomain; + msg.data = (void*)&data; + msg.data_size = sizeof(data); + s->monitor_fn(&msg); + } + + r = &s->open; + + /* check the rpc layer status */ + if (!composite_is_ok(c)) return; + + /* check the rpc call itself status */ + if (!NT_STATUS_IS_OK(s->lookup.out.result)) { + composite_error(c, s->lookup.out.result); + return; + } + + /* prepare for samr_OpenDomain call */ + r->in.connect_handle = &s->connect_handle; + r->in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r->in.sid = *s->lookup.out.sid; + r->out.domain_handle = &s->domain_handle; + + subreq = dcerpc_samr_OpenDomain_r_send(s, c->event_ctx, + s->pipe->binding_handle, + r); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_domain_open_open, c); +} + + +/* + * Stage 3: Open domain. + */ +static void continue_domain_open_open(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_open_samr_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_open_samr_state); + + /* receive samr_OpenDomain reply */ + c->status = dcerpc_samr_OpenDomain_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_SamrOpenDomain; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +/** + * Sends asynchronous DomainOpenSamr request + * + * @param ctx initialised libnet context + * @param io arguments and results of the call + * @param monitor pointer to monitor function that is passed monitor message + */ + +struct composite_context *libnet_DomainOpenSamr_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_DomainOpen *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct domain_open_samr_state *s; + struct composite_context *rpcconn_req; + struct tevent_req *subreq; + + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct domain_open_samr_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + s->monitor_fn = monitor; + + s->ctx = ctx; + s->pipe = ctx->samr.pipe; + s->access_mask = io->in.access_mask; + s->domain_name.string = talloc_strdup(c, io->in.domain_name); + + /* check, if there's samr pipe opened already, before opening a domain */ + if (ctx->samr.pipe == NULL) { + + /* attempting to connect a domain controller */ + s->rpcconn.level = LIBNET_RPC_CONNECT_DC; + s->rpcconn.in.name = io->in.domain_name; + s->rpcconn.in.dcerpc_iface = &ndr_table_samr; + + /* send rpc pipe connect request */ + rpcconn_req = libnet_RpcConnect_send(ctx, c, &s->rpcconn, s->monitor_fn); + if (composite_nomem(rpcconn_req, c)) return c; + + composite_continue(c, rpcconn_req, continue_domain_open_rpc_connect, c); + return c; + } + + /* libnet context's domain handle is not empty, so check out what + was opened first, before doing anything */ + if (!ndr_policy_handle_empty(&ctx->samr.handle)) { + if (strequal(ctx->samr.name, io->in.domain_name) && + ctx->samr.access_mask == io->in.access_mask) { + + /* this domain is already opened */ + composite_done(c); + return c; + + } else { + /* another domain or access rights have been + requested - close the existing handle first */ + s->close.in.handle = &ctx->samr.handle; + + /* send request to close domain handle */ + subreq = dcerpc_samr_Close_r_send(s, c->event_ctx, + s->pipe->binding_handle, + &s->close); + if (composite_nomem(subreq, c)) return c; + + /* callback handler */ + tevent_req_set_callback(subreq, continue_domain_open_close, c); + return c; + } + } + + /* preparing parameters for samr_Connect rpc call */ + s->connect.in.system_name = 0; + s->connect.in.access_mask = s->access_mask; + s->connect.out.connect_handle = &s->connect_handle; + + /* send request */ + subreq = dcerpc_samr_Connect_r_send(s, c->event_ctx, + s->pipe->binding_handle, + &s->connect); + if (composite_nomem(subreq, c)) return c; + + /* callback handler */ + tevent_req_set_callback(subreq, continue_domain_open_connect, c); + return c; +} + + +/** + * Waits for and receives result of asynchronous DomainOpenSamr call + * + * @param c composite context returned by asynchronous DomainOpen call + * @param ctx initialised libnet context + * @param mem_ctx memory context of the call + * @param io pointer to results (and arguments) of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_DomainOpenSamr_recv(struct composite_context *c, struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_DomainOpen *io) +{ + NTSTATUS status; + struct domain_open_samr_state *s; + + /* wait for results of sending request */ + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status) && io) { + s = talloc_get_type_abort(c->private_data, struct domain_open_samr_state); + io->out.domain_handle = s->domain_handle; + + /* store the resulting handle and related data for use by other + libnet functions */ + ctx->samr.connect_handle = s->connect_handle; + ctx->samr.handle = s->domain_handle; + ctx->samr.sid = talloc_steal(ctx, *s->lookup.out.sid); + ctx->samr.name = talloc_steal(ctx, s->domain_name.string); + ctx->samr.access_mask = s->access_mask; + } + + talloc_free(c); + return status; +} + + +struct domain_open_lsa_state { + const char *name; + uint32_t access_mask; + struct libnet_context *ctx; + struct libnet_RpcConnect rpcconn; + struct lsa_OpenPolicy2 openpol; + struct policy_handle handle; + struct dcerpc_pipe *pipe; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_rpc_connect_lsa(struct composite_context *ctx); +static void continue_lsa_policy_open(struct tevent_req *subreq); + + +/** + * Sends asynchronous DomainOpenLsa request + * + * @param ctx initialised libnet context + * @param io arguments and results of the call + * @param monitor pointer to monitor function that is passed monitor message + */ + +struct composite_context* libnet_DomainOpenLsa_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_DomainOpen *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct domain_open_lsa_state *s; + struct composite_context *rpcconn_req; + struct tevent_req *subreq; + struct lsa_QosInfo *qos; + + /* create composite context and state */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return c; + + s = talloc_zero(c, struct domain_open_lsa_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* store arguments in the state structure */ + s->name = talloc_strdup(c, io->in.domain_name); + s->access_mask = io->in.access_mask; + s->ctx = ctx; + + /* check, if there's lsa pipe opened already, before opening a handle */ + if (ctx->lsa.pipe == NULL) { + + ZERO_STRUCT(s->rpcconn); + + /* attempting to connect a domain controller */ + s->rpcconn.level = LIBNET_RPC_CONNECT_DC; + s->rpcconn.in.name = talloc_strdup(c, io->in.domain_name); + s->rpcconn.in.dcerpc_iface = &ndr_table_lsarpc; + + /* send rpc pipe connect request */ + rpcconn_req = libnet_RpcConnect_send(ctx, c, &s->rpcconn, s->monitor_fn); + if (composite_nomem(rpcconn_req, c)) return c; + + composite_continue(c, rpcconn_req, continue_rpc_connect_lsa, c); + return c; + } + + s->pipe = ctx->lsa.pipe; + + /* preparing parameters for lsa_OpenPolicy2 rpc call */ + s->openpol.in.system_name = s->name; + s->openpol.in.access_mask = s->access_mask; + s->openpol.in.attr = talloc_zero(c, struct lsa_ObjectAttribute); + + qos = talloc_zero(c, struct lsa_QosInfo); + qos->len = 0; + qos->impersonation_level = 2; + qos->context_mode = 1; + qos->effective_only = 0; + + s->openpol.in.attr->sec_qos = qos; + s->openpol.out.handle = &s->handle; + + /* send rpc request */ + subreq = dcerpc_lsa_OpenPolicy2_r_send(s, c->event_ctx, + s->pipe->binding_handle, + &s->openpol); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_lsa_policy_open, c); + return c; +} + + +/* + Stage 0.5 (optional): Rpc pipe connected, send lsa open policy request + */ +static void continue_rpc_connect_lsa(struct composite_context *ctx) +{ + struct composite_context *c; + struct domain_open_lsa_state *s; + struct lsa_QosInfo *qos; + struct tevent_req *subreq; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_open_lsa_state); + + /* receive rpc connection */ + c->status = libnet_RpcConnect_recv(ctx, s->ctx, c, &s->rpcconn); + if (!composite_is_ok(c)) return; + + /* RpcConnect function leaves the pipe in libnet context, + so get it from there */ + s->pipe = s->ctx->lsa.pipe; + + /* prepare lsa_OpenPolicy2 call */ + s->openpol.in.system_name = s->name; + s->openpol.in.access_mask = s->access_mask; + s->openpol.in.attr = talloc_zero(c, struct lsa_ObjectAttribute); + + qos = talloc_zero(c, struct lsa_QosInfo); + qos->len = 0; + qos->impersonation_level = 2; + qos->context_mode = 1; + qos->effective_only = 0; + + s->openpol.in.attr->sec_qos = qos; + s->openpol.out.handle = &s->handle; + + /* send rpc request */ + subreq = dcerpc_lsa_OpenPolicy2_r_send(s, c->event_ctx, + s->pipe->binding_handle, + &s->openpol); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_lsa_policy_open, c); +} + + +/* + Stage 1: Lsa policy opened - we're done, if successfully + */ +static void continue_lsa_policy_open(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_open_lsa_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_open_lsa_state); + + c->status = dcerpc_lsa_OpenPolicy2_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_LsaOpenPolicy; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +/** + * Receives result of asynchronous DomainOpenLsa call + * + * @param c composite context returned by asynchronous DomainOpenLsa call + * @param ctx initialised libnet context + * @param mem_ctx memory context of the call + * @param io pointer to results (and arguments) of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_DomainOpenLsa_recv(struct composite_context *c, struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_DomainOpen *io) +{ + NTSTATUS status; + struct domain_open_lsa_state *s; + + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status) && io) { + /* everything went fine - get the results and + return the error string */ + s = talloc_get_type_abort(c->private_data, struct domain_open_lsa_state); + io->out.domain_handle = s->handle; + + ctx->lsa.handle = s->handle; + ctx->lsa.name = talloc_steal(ctx, s->name); + ctx->lsa.access_mask = s->access_mask; + + io->out.error_string = talloc_strdup(mem_ctx, "Success"); + + } else if (!NT_STATUS_IS_OK(status)) { + /* there was an error, so provide nt status code description */ + io->out.error_string = talloc_asprintf(mem_ctx, + "Failed to open domain: %s", + nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +/** + * Sends a request to open a domain in desired service + * + * @param ctx initialised libnet context + * @param io arguments and results of the call + * @param monitor pointer to monitor function that is passed monitor message + */ + +struct composite_context* libnet_DomainOpen_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_DomainOpen *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + + switch (io->in.type) { + case DOMAIN_LSA: + /* reques to open a policy handle on \pipe\lsarpc */ + c = libnet_DomainOpenLsa_send(ctx, mem_ctx, io, monitor); + break; + + case DOMAIN_SAMR: + default: + /* request to open a domain policy handle on \pipe\samr */ + c = libnet_DomainOpenSamr_send(ctx, mem_ctx, io, monitor); + break; + } + + return c; +} + + +/** + * Receive result of domain open request + * + * @param c composite context returned by DomainOpen_send function + * @param ctx initialised libnet context + * @param mem_ctx memory context of the call + * @param io results and arguments of the call + */ + +NTSTATUS libnet_DomainOpen_recv(struct composite_context *c, struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_DomainOpen *io) +{ + NTSTATUS status; + + switch (io->in.type) { + case DOMAIN_LSA: + status = libnet_DomainOpenLsa_recv(c, ctx, mem_ctx, io); + break; + + case DOMAIN_SAMR: + default: + status = libnet_DomainOpenSamr_recv(c, ctx, mem_ctx, io); + break; + } + + return status; +} + + +/** + * Synchronous version of DomainOpen call + * + * @param ctx initialised libnet context + * @param mem_ctx memory context for the call + * @param io arguments and results of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_DomainOpen(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_DomainOpen *io) +{ + struct composite_context *c = libnet_DomainOpen_send(ctx, mem_ctx, io, NULL); + return libnet_DomainOpen_recv(c, ctx, mem_ctx, io); +} + + +struct domain_close_lsa_state { + struct dcerpc_pipe *pipe; + struct lsa_Close close; + struct policy_handle handle; + + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_lsa_close(struct tevent_req *subreq); + + +struct composite_context* libnet_DomainCloseLsa_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_DomainClose *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct domain_close_lsa_state *s; + struct tevent_req *subreq; + + /* composite context and state structure allocation */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return c; + + s = talloc_zero(c, struct domain_close_lsa_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + s->monitor_fn = monitor; + + /* TODO: check if lsa pipe pointer is non-null */ + + if (!strequal(ctx->lsa.name, io->in.domain_name)) { + composite_error(c, NT_STATUS_INVALID_PARAMETER); + return c; + } + + /* get opened lsarpc pipe pointer */ + s->pipe = ctx->lsa.pipe; + + /* prepare close handle call arguments */ + s->close.in.handle = &ctx->lsa.handle; + s->close.out.handle = &s->handle; + + /* send the request */ + subreq = dcerpc_lsa_Close_r_send(s, c->event_ctx, + s->pipe->binding_handle, + &s->close); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_lsa_close, c); + return c; +} + + +/* + Stage 1: Receive result of lsa close call +*/ +static void continue_lsa_close(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_close_lsa_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_close_lsa_state); + + c->status = dcerpc_lsa_Close_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_LsaClose; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +NTSTATUS libnet_DomainCloseLsa_recv(struct composite_context *c, struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_DomainClose *io) +{ + NTSTATUS status; + + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status) && io) { + /* policy handle closed successfully */ + + ctx->lsa.name = NULL; + ZERO_STRUCT(ctx->lsa.handle); + + io->out.error_string = talloc_asprintf(mem_ctx, "Success"); + + } else if (!NT_STATUS_IS_OK(status)) { + /* there was an error, so return description of the status code */ + io->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +struct domain_close_samr_state { + struct samr_Close close; + struct policy_handle handle; + + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_samr_close(struct tevent_req *subreq); + + +struct composite_context* libnet_DomainCloseSamr_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_DomainClose *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct domain_close_samr_state *s; + struct tevent_req *subreq; + + /* composite context and state structure allocation */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return c; + + s = talloc_zero(c, struct domain_close_samr_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + s->monitor_fn = monitor; + + /* TODO: check if samr pipe pointer is non-null */ + + if (!strequal(ctx->samr.name, io->in.domain_name)) { + composite_error(c, NT_STATUS_INVALID_PARAMETER); + return c; + } + + /* prepare close domain handle call arguments */ + ZERO_STRUCT(s->close); + s->close.in.handle = &ctx->samr.handle; + s->close.out.handle = &s->handle; + + /* send the request */ + subreq = dcerpc_samr_Close_r_send(s, c->event_ctx, + ctx->samr.pipe->binding_handle, + &s->close); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_samr_close, c); + return c; +} + + +/* + Stage 1: Receive result of samr close call +*/ +static void continue_samr_close(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_close_samr_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_close_samr_state); + + c->status = dcerpc_samr_Close_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_SamrClose; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +NTSTATUS libnet_DomainCloseSamr_recv(struct composite_context *c, struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_DomainClose *io) +{ + NTSTATUS status; + + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status) && io) { + /* domain policy handle closed successfully */ + + ZERO_STRUCT(ctx->samr.handle); + talloc_free(discard_const_p(char, ctx->samr.name)); + talloc_free(ctx->samr.sid); + ctx->samr.name = NULL; + ctx->samr.sid = NULL; + + io->out.error_string = talloc_asprintf(mem_ctx, "Success"); + + } else if (!NT_STATUS_IS_OK(status)) { + /* there was an error, so return description of the status code */ + io->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +struct composite_context* libnet_DomainClose_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_DomainClose *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + + switch (io->in.type) { + case DOMAIN_LSA: + /* request to close policy handle on \pipe\lsarpc */ + c = libnet_DomainCloseLsa_send(ctx, mem_ctx, io, monitor); + break; + + case DOMAIN_SAMR: + default: + /* request to close domain policy handle on \pipe\samr */ + c = libnet_DomainCloseSamr_send(ctx, mem_ctx, io, monitor); + break; + } + + return c; +} + + +NTSTATUS libnet_DomainClose_recv(struct composite_context *c, struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_DomainClose *io) +{ + NTSTATUS status; + + switch (io->in.type) { + case DOMAIN_LSA: + /* receive result of closing lsa policy handle */ + status = libnet_DomainCloseLsa_recv(c, ctx, mem_ctx, io); + break; + + case DOMAIN_SAMR: + default: + /* receive result of closing samr domain policy handle */ + status = libnet_DomainCloseSamr_recv(c, ctx, mem_ctx, io); + break; + } + + return status; +} + + +NTSTATUS libnet_DomainClose(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_DomainClose *io) +{ + struct composite_context *c; + + c = libnet_DomainClose_send(ctx, mem_ctx, io, NULL); + return libnet_DomainClose_recv(c, ctx, mem_ctx, io); +} + + +struct domain_list_state { + struct libnet_context *ctx; + struct libnet_RpcConnect rpcconn; + struct samr_Connect samrconn; + struct samr_EnumDomains enumdom; + struct samr_Close samrclose; + const char *hostname; + struct policy_handle connect_handle; + int buf_size; + struct domainlist *domains; + uint32_t resume_handle; + uint32_t count; + + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_rpc_connect(struct composite_context *c); +static void continue_samr_connect(struct tevent_req *subreq); +static void continue_samr_enum_domains(struct tevent_req *subreq); +static void continue_samr_close_handle(struct tevent_req *subreq); + +static struct domainlist* get_domain_list(TALLOC_CTX *mem_ctx, struct domain_list_state *s); + + +/* + Stage 1: Receive connected rpc pipe and send connection + request to SAMR service +*/ +static void continue_rpc_connect(struct composite_context *ctx) +{ + struct composite_context *c; + struct domain_list_state *s; + struct tevent_req *subreq; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_list_state); + + c->status = libnet_RpcConnect_recv(ctx, s->ctx, c, &s->rpcconn); + if (!composite_is_ok(c)) return; + + s->samrconn.in.system_name = 0; + s->samrconn.in.access_mask = SEC_GENERIC_READ; /* should be enough */ + s->samrconn.out.connect_handle = &s->connect_handle; + + subreq = dcerpc_samr_Connect_r_send(s, c->event_ctx, + s->ctx->samr.pipe->binding_handle, + &s->samrconn); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_samr_connect, c); +} + + +/* + Stage 2: Receive policy handle to the connected SAMR service and issue + a request to enumerate domain databases available +*/ +static void continue_samr_connect(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_list_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_list_state); + + c->status = dcerpc_samr_Connect_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_SamrConnect; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + s->enumdom.in.connect_handle = &s->connect_handle; + s->enumdom.in.resume_handle = &s->resume_handle; + s->enumdom.in.buf_size = s->buf_size; + s->enumdom.out.resume_handle = &s->resume_handle; + s->enumdom.out.num_entries = talloc(s, uint32_t); + if (composite_nomem(s->enumdom.out.num_entries, c)) return; + s->enumdom.out.sam = talloc(s, struct samr_SamArray *); + if (composite_nomem(s->enumdom.out.sam, c)) return; + + subreq = dcerpc_samr_EnumDomains_r_send(s, c->event_ctx, + s->ctx->samr.pipe->binding_handle, + &s->enumdom); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_samr_enum_domains, c); +} + + +/* + Stage 3: Receive domain names available and repeat the request + enumeration is not complete yet. Close samr connection handle + upon completion. +*/ +static void continue_samr_enum_domains(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_list_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_list_state); + + c->status = dcerpc_samr_EnumDomains_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_SamrEnumDomains; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + if (NT_STATUS_IS_OK(s->enumdom.out.result)) { + + s->domains = get_domain_list(c, s); + + } else if (NT_STATUS_EQUAL(s->enumdom.out.result, STATUS_MORE_ENTRIES)) { + + s->domains = get_domain_list(c, s); + + /* prepare next round of enumeration */ + s->enumdom.in.connect_handle = &s->connect_handle; + s->enumdom.in.resume_handle = &s->resume_handle; + s->enumdom.in.buf_size = s->ctx->samr.buf_size; + s->enumdom.out.resume_handle = &s->resume_handle; + + /* send the request */ + subreq = dcerpc_samr_EnumDomains_r_send(s, c->event_ctx, + s->ctx->samr.pipe->binding_handle, + &s->enumdom); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_samr_enum_domains, c); + + } else { + composite_error(c, s->enumdom.out.result); + return; + } + + /* close samr connection handle */ + s->samrclose.in.handle = &s->connect_handle; + s->samrclose.out.handle = &s->connect_handle; + + /* send the request */ + subreq = dcerpc_samr_Close_r_send(s, c->event_ctx, + s->ctx->samr.pipe->binding_handle, + &s->samrclose); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_samr_close_handle, c); +} + + +/* + Stage 4: Receive result of closing samr connection handle. +*/ +static void continue_samr_close_handle(struct tevent_req *subreq) +{ + struct composite_context *c; + struct domain_list_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct domain_list_state); + + c->status = dcerpc_samr_Close_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_SamrClose; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + /* did everything go fine ? */ + if (!NT_STATUS_IS_OK(s->samrclose.out.result)) { + composite_error(c, s->samrclose.out.result); + return; + } + + composite_done(c); +} + + +/* + Utility function to copy domain names from result of samr_EnumDomains call +*/ +static struct domainlist* get_domain_list(TALLOC_CTX *mem_ctx, struct domain_list_state *s) +{ + uint32_t i; + if (mem_ctx == NULL || s == NULL) return NULL; + + /* prepare domains array */ + if (s->domains == NULL) { + s->domains = talloc_array(mem_ctx, struct domainlist, + *s->enumdom.out.num_entries); + } else { + s->domains = talloc_realloc(mem_ctx, s->domains, struct domainlist, + s->count + *s->enumdom.out.num_entries); + } + + /* copy domain names returned from samr_EnumDomains call */ + for (i = s->count; i < s->count + *s->enumdom.out.num_entries; i++) + { + struct lsa_String *domain_name = &(*s->enumdom.out.sam)->entries[i - s->count].name; + + /* strdup name as a child of allocated array to make it follow the array + in case of talloc_steal or talloc_free */ + s->domains[i].name = talloc_strdup(s->domains, domain_name->string); + s->domains[i].sid = NULL; /* this is to be filled out later */ + } + + /* number of entries returned (domains enumerated) */ + s->count += *s->enumdom.out.num_entries; + + return s->domains; +} + + +/** + * Sends a request to list domains on given host + * + * @param ctx initialised libnet context + * @param mem_ctx memory context + * @param io arguments and results of the call + * @param monitor pointer to monitor function that is passed monitor messages + */ + +struct composite_context* libnet_DomainList_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_DomainList *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct domain_list_state *s; + struct composite_context *rpcconn_req; + struct tevent_req *subreq; + + /* composite context and state structure allocation */ + c = composite_create(ctx, ctx->event_ctx); + if (c == NULL) return c; + + s = talloc_zero(c, struct domain_list_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + s->monitor_fn = monitor; + + s->ctx = ctx; + s->hostname = talloc_strdup(c, io->in.hostname); + if (composite_nomem(s->hostname, c)) return c; + + /* check whether samr pipe has already been opened */ + if (ctx->samr.pipe == NULL) { + ZERO_STRUCT(s->rpcconn); + + /* prepare rpc connect call */ + s->rpcconn.level = LIBNET_RPC_CONNECT_SERVER; + s->rpcconn.in.name = s->hostname; + s->rpcconn.in.dcerpc_iface = &ndr_table_samr; + + rpcconn_req = libnet_RpcConnect_send(ctx, c, &s->rpcconn, s->monitor_fn); + if (composite_nomem(rpcconn_req, c)) return c; + + composite_continue(c, rpcconn_req, continue_rpc_connect, c); + + } else { + /* prepare samr_Connect call */ + s->samrconn.in.system_name = 0; + s->samrconn.in.access_mask = SEC_GENERIC_READ; + s->samrconn.out.connect_handle = &s->connect_handle; + + subreq = dcerpc_samr_Connect_r_send(s, c->event_ctx, + s->ctx->samr.pipe->binding_handle, + &s->samrconn); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_samr_connect, c); + } + + return c; +} + + +/** + * Receive result of domain list request + * + * @param c composite context returned by DomainList_send function + * @param ctx initialised libnet context + * @param mem_ctx memory context of the call + * @param io results and arguments of the call + */ + +NTSTATUS libnet_DomainList_recv(struct composite_context *c, struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_DomainList *io) +{ + NTSTATUS status; + struct domain_list_state *s; + + status = composite_wait(c); + + s = talloc_get_type_abort(c->private_data, struct domain_list_state); + + if (NT_STATUS_IS_OK(status) && ctx && mem_ctx && io) { + /* fetch the results to be returned by io structure */ + io->out.count = s->count; + io->out.domains = talloc_steal(mem_ctx, s->domains); + io->out.error_string = talloc_asprintf(mem_ctx, "Success"); + + } else if (!NT_STATUS_IS_OK(status)) { + /* there was an error, so return description of the status code */ + io->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of DomainList call + * + * @param ctx initialised libnet context + * @param mem_ctx memory context for the call + * @param io arguments and results of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_DomainList(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_DomainList *io) +{ + struct composite_context *c; + + c = libnet_DomainList_send(ctx, mem_ctx, io, NULL); + return libnet_DomainList_recv(c, ctx, mem_ctx, io); +} diff --git a/source4/libnet/libnet_domain.h b/source4/libnet/libnet_domain.h new file mode 100644 index 0000000..ae698e3 --- /dev/null +++ b/source4/libnet/libnet_domain.h @@ -0,0 +1,70 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + 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 service_type { DOMAIN_SAMR, DOMAIN_LSA }; + +/* + * struct definition for opening a domain + */ + +struct libnet_DomainOpen { + struct { + enum service_type type; + const char *domain_name; + uint32_t access_mask; + } in; + struct { + struct policy_handle domain_handle; + const char *error_string; + } out; +}; + + +struct libnet_DomainClose { + struct { + enum service_type type; + const char *domain_name; + } in; + struct { + const char *error_string; + } out; +}; + + +struct libnet_DomainList { + struct { + const char *hostname; + } in; + struct { + int count; + + struct domainlist { + const char *sid; + const char *name; + } *domains; + + const char *error_string; + } out; +}; + + +struct msg_rpc_lookup_domain { + const char *domain_name; +}; diff --git a/source4/libnet/libnet_export_keytab.c b/source4/libnet/libnet_export_keytab.c new file mode 100644 index 0000000..4c96546 --- /dev/null +++ b/source4/libnet/libnet_export_keytab.c @@ -0,0 +1,207 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009 + Copyright (C) Andreas Schneider <asn@samba.org> 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "kdc/samba_kdc.h" +#include "libnet/libnet_export_keytab.h" + +#include "kdc/db-glue.h" +#include "kdc/sdb.h" + +static NTSTATUS sdb_kt_copy(TALLOC_CTX *mem_ctx, + krb5_context context, + struct samba_kdc_db_context *db_ctx, + const char *keytab_name, + const char *principal, + const char **error_string) +{ + struct sdb_entry sentry = {}; + krb5_keytab keytab; + krb5_error_code code = 0; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + char *entry_principal = NULL; + bool copy_one_principal = (principal != NULL); + krb5_data password; + + code = smb_krb5_kt_open_relative(context, + keytab_name, + true, /* write_access */ + &keytab); + if (code != 0) { + *error_string = talloc_asprintf(mem_ctx, + "Failed to open keytab: %s", + keytab_name); + status = NT_STATUS_NO_SUCH_FILE; + goto done; + } + + if (copy_one_principal) { + krb5_principal k5_princ; + + code = smb_krb5_parse_name(context, principal, &k5_princ); + if (code != 0) { + *error_string = smb_get_krb5_error_message(context, + code, + mem_ctx); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + code = samba_kdc_fetch(context, db_ctx, k5_princ, + SDB_F_GET_ANY | SDB_F_ADMIN_DATA, + 0, &sentry); + + krb5_free_principal(context, k5_princ); + } else { + code = samba_kdc_firstkey(context, db_ctx, &sentry); + } + + for (; code == 0; code = samba_kdc_nextkey(context, db_ctx, &sentry)) { + int i; + + code = krb5_unparse_name(context, + sentry.principal, + &entry_principal); + if (code != 0) { + *error_string = smb_get_krb5_error_message(context, + code, + mem_ctx); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + if (sentry.keys.len == 0) { + SAFE_FREE(entry_principal); + sdb_entry_free(&sentry); + + continue; + } + + for (i = 0; i < sentry.keys.len; i++) { + struct sdb_key *s = &(sentry.keys.val[i]); + krb5_enctype enctype; + + enctype = KRB5_KEY_TYPE(&(s->key)); + password.length = KRB5_KEY_LENGTH(&s->key); + password.data = (char *)KRB5_KEY_DATA(&s->key); + + DBG_INFO("smb_krb5_kt_add_entry for enctype=0x%04x\n", + (int)enctype); + code = smb_krb5_kt_add_entry(context, + keytab, + sentry.kvno, + entry_principal, + NULL, + enctype, + &password, + true, /* no_salt */ + false); /* keeyp_old_entries */ + if (code != 0) { + status = NT_STATUS_UNSUCCESSFUL; + *error_string = smb_get_krb5_error_message(context, + code, + mem_ctx); + DEBUG(0, ("smb_krb5_kt_add_entry failed code=%d, error = %s\n", + code, *error_string)); + goto done; + } + } + + if (copy_one_principal) { + break; + } + + SAFE_FREE(entry_principal); + sdb_entry_free(&sentry); + } + + if (code != 0 && code != SDB_ERR_NOENTRY) { + *error_string = smb_get_krb5_error_message(context, + code, + mem_ctx); + status = NT_STATUS_NO_SUCH_USER; + goto done; + } + + status = NT_STATUS_OK; +done: + SAFE_FREE(entry_principal); + sdb_entry_free(&sentry); + + return status; +} + +NTSTATUS libnet_export_keytab(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_export_keytab *r) +{ + krb5_error_code ret; + struct smb_krb5_context *smb_krb5_context; + struct samba_kdc_base_context *base_ctx; + struct samba_kdc_db_context *db_ctx = NULL; + const char *error_string = NULL; + NTSTATUS status; + + ret = smb_krb5_init_context(ctx, ctx->lp_ctx, &smb_krb5_context); + if (ret) { + return NT_STATUS_NO_MEMORY; + } + + base_ctx = talloc_zero(mem_ctx, struct samba_kdc_base_context); + if (base_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + base_ctx->ev_ctx = ctx->event_ctx; + base_ctx->lp_ctx = ctx->lp_ctx; + + status = samba_kdc_setup_db_ctx(mem_ctx, base_ctx, &db_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (r->in.principal != NULL) { + DEBUG(0, ("Export one principal to %s\n", r->in.keytab_name)); + status = sdb_kt_copy(mem_ctx, + smb_krb5_context->krb5_context, + db_ctx, + r->in.keytab_name, + r->in.principal, + &error_string); + } else { + unlink(r->in.keytab_name); + DEBUG(0, ("Export complete keytab to %s\n", r->in.keytab_name)); + status = sdb_kt_copy(mem_ctx, + smb_krb5_context->krb5_context, + db_ctx, + r->in.keytab_name, + NULL, + &error_string); + } + + talloc_free(db_ctx); + talloc_free(base_ctx); + + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = error_string; + } + + return status; +} diff --git a/source4/libnet/libnet_export_keytab.h b/source4/libnet/libnet_export_keytab.h new file mode 100644 index 0000000..2b4bdcd --- /dev/null +++ b/source4/libnet/libnet_export_keytab.h @@ -0,0 +1,32 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009 + + 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" + +struct libnet_export_keytab { + struct { + const char *keytab_name; + const char *principal; + } in; + struct { + const char *error_string; + } out; +}; + +NTSTATUS libnet_export_keytab(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_export_keytab *r); diff --git a/source4/libnet/libnet_group.c b/source4/libnet/libnet_group.c new file mode 100644 index 0000000..512d6fb --- /dev/null +++ b/source4/libnet/libnet_group.c @@ -0,0 +1,764 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 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/>. +*/ + + +#include "includes.h" +#include "libnet/libnet.h" +#include "libcli/composite/composite.h" +#include "librpc/gen_ndr/lsa.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/samr.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "libcli/security/security.h" + + +struct create_group_state { + struct libnet_context *ctx; + struct libnet_CreateGroup r; + struct libnet_DomainOpen domain_open; + struct libnet_rpc_groupadd group_add; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_domain_opened(struct composite_context *ctx); +static void continue_rpc_group_added(struct composite_context *ctx); + + +struct composite_context* libnet_CreateGroup_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_CreateGroup *r, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct create_group_state *s; + struct composite_context *create_req; + bool prereq_met = false; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct create_group_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + s->ctx = ctx; + s->r = *r; + ZERO_STRUCT(s->r.out); + + /* prerequisite: make sure we have a valid samr domain handle */ + prereq_met = samr_domain_opened(ctx, c, s->r.in.domain_name, &c, &s->domain_open, + continue_domain_opened, monitor); + if (!prereq_met) return c; + + /* prepare arguments of rpc group add call */ + s->group_add.in.groupname = r->in.group_name; + s->group_add.in.domain_handle = ctx->samr.handle; + + /* send the request */ + create_req = libnet_rpc_groupadd_send(s, s->ctx->event_ctx, + ctx->samr.samr_handle, + &s->group_add, monitor); + if (composite_nomem(create_req, c)) return c; + + composite_continue(c, create_req, continue_rpc_group_added, c); + return c; +} + + +static void continue_domain_opened(struct composite_context *ctx) +{ + struct composite_context *c; + struct create_group_state *s; + struct composite_context *create_req; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct create_group_state); + + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domain_open); + if (!composite_is_ok(c)) return; + + /* prepare arguments of groupadd call */ + s->group_add.in.groupname = s->r.in.group_name; + s->group_add.in.domain_handle = s->ctx->samr.handle; + + /* send the request */ + create_req = libnet_rpc_groupadd_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->group_add, s->monitor_fn); + if (composite_nomem(create_req, c)) return; + + composite_continue(c, create_req, continue_rpc_group_added, c); +} + + +static void continue_rpc_group_added(struct composite_context *ctx) +{ + struct composite_context *c; + struct create_group_state *s; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct create_group_state); + + /* receive result of group add call */ + c->status = libnet_rpc_groupadd_recv(ctx, c, &s->group_add); + if (!composite_is_ok(c)) return; + + /* we're done */ + composite_done(c); +} + + +/** + * Receive result of CreateGroup call + * + * @param c composite context returned by send request routine + * @param mem_ctx memory context of this call + * @param r pointer to a structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_CreateGroup_recv(struct composite_context *c, + TALLOC_CTX *mem_ctx, + struct libnet_CreateGroup *r) +{ + NTSTATUS status; + + status = composite_wait(c); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_strdup(mem_ctx, nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +/** + * Create domain group + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param io pointer to structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_CreateGroup(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_CreateGroup *io) +{ + struct composite_context *c; + + c = libnet_CreateGroup_send(ctx, mem_ctx, io, NULL); + return libnet_CreateGroup_recv(c, mem_ctx, io); +} + + +struct group_info_state { + struct libnet_context *ctx; + const char *domain_name; + enum libnet_GroupInfo_level level; + const char *group_name; + const char *sid_string; + struct libnet_LookupName lookup; + struct libnet_DomainOpen domopen; + struct libnet_rpc_groupinfo info; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_domain_open_info(struct composite_context *ctx); +static void continue_name_found(struct composite_context *ctx); +static void continue_group_info(struct composite_context *ctx); + +/** + * Sends request to get group information + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param io pointer to structure containing arguments the call + * @param monitor function pointer for receiving monitor messages + * @return composite context of this request + */ +struct composite_context* libnet_GroupInfo_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_GroupInfo *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct group_info_state *s; + bool prereq_met = false; + struct composite_context *lookup_req, *info_req; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct group_info_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* store arguments in the state structure */ + s->monitor_fn = monitor; + s->ctx = ctx; + s->domain_name = talloc_strdup(c, io->in.domain_name); + s->level = io->in.level; + switch(s->level) { + case GROUP_INFO_BY_NAME: + s->group_name = talloc_strdup(c, io->in.data.group_name); + s->sid_string = NULL; + break; + case GROUP_INFO_BY_SID: + s->group_name = NULL; + s->sid_string = dom_sid_string(c, io->in.data.group_sid); + break; + } + + /* prerequisite: make sure the domain is opened */ + prereq_met = samr_domain_opened(ctx, c, s->domain_name, &c, &s->domopen, + continue_domain_open_info, monitor); + if (!prereq_met) return c; + + switch(s->level) { + case GROUP_INFO_BY_NAME: + /* prepare arguments for LookupName call */ + s->lookup.in.name = s->group_name; + s->lookup.in.domain_name = s->domain_name; + + /* send the request */ + lookup_req = libnet_LookupName_send(s->ctx, c, &s->lookup, s->monitor_fn); + if (composite_nomem(lookup_req, c)) return c; + + /* set the next stage */ + composite_continue(c, lookup_req, continue_name_found, c); + break; + case GROUP_INFO_BY_SID: + /* prepare arguments for groupinfo call */ + s->info.in.domain_handle = s->ctx->samr.handle; + s->info.in.sid = s->sid_string; + /* we're looking for all information available */ + s->info.in.level = GROUPINFOALL; + + /* send the request */ + info_req = libnet_rpc_groupinfo_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->info, s->monitor_fn); + if (composite_nomem(info_req, c)) return c; + + /* set the next stage */ + composite_continue(c, info_req, continue_group_info, c); + break; + } + + return c; +} + + +/* + * Stage 0.5 (optional): receive opened domain and send lookup name request + */ +static void continue_domain_open_info(struct composite_context *ctx) +{ + struct composite_context *c; + struct group_info_state *s; + struct composite_context *lookup_req, *info_req; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct group_info_state); + + /* receive domain handle */ + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domopen); + if (!composite_is_ok(c)) return; + + switch(s->level) { + case GROUP_INFO_BY_NAME: + /* prepare arguments for LookupName call */ + s->lookup.in.name = s->group_name; + s->lookup.in.domain_name = s->domain_name; + + /* send the request */ + lookup_req = libnet_LookupName_send(s->ctx, c, &s->lookup, s->monitor_fn); + if (composite_nomem(lookup_req, c)) return; + + /* set the next stage */ + composite_continue(c, lookup_req, continue_name_found, c); + break; + case GROUP_INFO_BY_SID: + /* prepare arguments for groupinfo call */ + s->info.in.domain_handle = s->ctx->samr.handle; + s->info.in.sid = s->sid_string; + /* we're looking for all information available */ + s->info.in.level = GROUPINFOALL; + + /* send the request */ + info_req = libnet_rpc_groupinfo_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->info, s->monitor_fn); + if (composite_nomem(info_req, c)) return; + + /* set the next stage */ + composite_continue(c, info_req, continue_group_info, c); + break; + + } +} + + +/* + * Stage 1: Receive SID found and send request for group info + */ +static void continue_name_found(struct composite_context *ctx) +{ + struct composite_context *c; + struct group_info_state *s; + struct composite_context *info_req; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct group_info_state); + + /* receive SID assiociated with name found */ + c->status = libnet_LookupName_recv(ctx, c, &s->lookup); + if (!composite_is_ok(c)) return; + + /* Is is a group SID actually ? */ + if (s->lookup.out.sid_type != SID_NAME_DOM_GRP && + s->lookup.out.sid_type != SID_NAME_ALIAS) { + composite_error(c, NT_STATUS_NO_SUCH_GROUP); + return; + } + + /* prepare arguments for groupinfo call */ + s->info.in.domain_handle = s->ctx->samr.handle; + s->info.in.groupname = s->group_name; + s->info.in.sid = s->lookup.out.sidstr; + /* we're looking for all information available */ + s->info.in.level = GROUPINFOALL; + + /* send the request */ + info_req = libnet_rpc_groupinfo_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->info, s->monitor_fn); + if (composite_nomem(info_req, c)) return; + + /* set the next stage */ + composite_continue(c, info_req, continue_group_info, c); +} + + +/* + * Stage 2: Receive group information + */ +static void continue_group_info(struct composite_context *ctx) +{ + struct composite_context *c; + struct group_info_state *s; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct group_info_state); + + /* receive group information */ + c->status = libnet_rpc_groupinfo_recv(ctx, c, &s->info); + if (!composite_is_ok(c)) return; + + /* we're done */ + composite_done(c); +} + + +/* + * Receive group information + * + * @param c composite context returned by libnet_GroupInfo_send + * @param mem_ctx memory context of this call + * @param io pointer to structure receiving results of the call + * @result nt status + */ +NTSTATUS libnet_GroupInfo_recv(struct composite_context* c, TALLOC_CTX *mem_ctx, + struct libnet_GroupInfo *io) +{ + NTSTATUS status; + struct group_info_state *s; + + status = composite_wait(c); + if (NT_STATUS_IS_OK(status)) { + /* put the results into io structure if everything went fine */ + s = talloc_get_type_abort(c->private_data, struct group_info_state); + + io->out.group_name = talloc_steal(mem_ctx, + s->info.out.info.all.name.string); + io->out.group_sid = talloc_steal(mem_ctx, s->lookup.out.sid); + io->out.num_members = s->info.out.info.all.num_members; + io->out.description = talloc_steal(mem_ctx, s->info.out.info.all.description.string); + + io->out.error_string = talloc_strdup(mem_ctx, "Success"); + + } else { + io->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +/** + * Obtains specified group information + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of the call + * @param io pointer to a structure containing arguments and results of the call + */ +NTSTATUS libnet_GroupInfo(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_GroupInfo *io) +{ + struct composite_context *c = libnet_GroupInfo_send(ctx, mem_ctx, + io, NULL); + return libnet_GroupInfo_recv(c, mem_ctx, io); +} + + +struct grouplist_state { + struct libnet_context *ctx; + const char *domain_name; + struct lsa_DomainInfo dominfo; + int page_size; + uint32_t resume_index; + struct grouplist *groups; + uint32_t count; + + struct libnet_DomainOpen domain_open; + struct lsa_QueryInfoPolicy query_domain; + struct samr_EnumDomainGroups group_list; + + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_lsa_domain_opened(struct composite_context *ctx); +static void continue_domain_queried(struct tevent_req *subreq); +static void continue_samr_domain_opened(struct composite_context *ctx); +static void continue_groups_enumerated(struct tevent_req *subreq); + + +/** + * Sends request to list (enumerate) group accounts + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param io pointer to structure containing arguments and results of this call + * @param monitor function pointer for receiving monitor messages + * @return compostite context of this request + */ +struct composite_context *libnet_GroupList_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_GroupList *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct grouplist_state *s; + struct tevent_req *subreq; + bool prereq_met = false; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct grouplist_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* store the arguments in the state structure */ + s->ctx = ctx; + s->page_size = io->in.page_size; + s->resume_index = io->in.resume_index; + s->domain_name = talloc_strdup(c, io->in.domain_name); + s->monitor_fn = monitor; + + /* make sure we have lsa domain handle before doing anything */ + prereq_met = lsa_domain_opened(ctx, c, s->domain_name, &c, &s->domain_open, + continue_lsa_domain_opened, monitor); + if (!prereq_met) return c; + + /* prepare arguments of QueryDomainInfo call */ + s->query_domain.in.handle = &ctx->lsa.handle; + s->query_domain.in.level = LSA_POLICY_INFO_DOMAIN; + s->query_domain.out.info = talloc_zero(c, union lsa_PolicyInformation *); + if (composite_nomem(s->query_domain.out.info, c)) return c; + + /* send the request */ + subreq = dcerpc_lsa_QueryInfoPolicy_r_send(s, c->event_ctx, + ctx->lsa.pipe->binding_handle, + &s->query_domain); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_domain_queried, c); + return c; +} + + +/* + * Stage 0.5 (optional): receive lsa domain handle and send + * request to query domain info + */ +static void continue_lsa_domain_opened(struct composite_context *ctx) +{ + struct composite_context *c; + struct grouplist_state *s; + struct tevent_req *subreq; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct grouplist_state); + + /* receive lsa domain handle */ + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domain_open); + if (!composite_is_ok(c)) return; + + /* prepare arguments of QueryDomainInfo call */ + s->query_domain.in.handle = &s->ctx->lsa.handle; + s->query_domain.in.level = LSA_POLICY_INFO_DOMAIN; + s->query_domain.out.info = talloc_zero(c, union lsa_PolicyInformation *); + if (composite_nomem(s->query_domain.out.info, c)) return; + + /* send the request */ + subreq = dcerpc_lsa_QueryInfoPolicy_r_send(s, c->event_ctx, + s->ctx->lsa.pipe->binding_handle, + &s->query_domain); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_domain_queried, c); +} + + +/* + * Stage 1: receive domain info and request to enum groups + * provided a valid samr handle is opened + */ +static void continue_domain_queried(struct tevent_req *subreq) +{ + struct composite_context *c; + struct grouplist_state *s; + bool prereq_met = false; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct grouplist_state); + + /* receive result of rpc request */ + c->status = dcerpc_lsa_QueryInfoPolicy_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* get the returned domain info */ + s->dominfo = (*s->query_domain.out.info)->domain; + + /* make sure we have samr domain handle before continuing */ + prereq_met = samr_domain_opened(s->ctx, c, s->domain_name, &c, &s->domain_open, + continue_samr_domain_opened, s->monitor_fn); + if (!prereq_met) return; + + /* prepare arguments od EnumDomainGroups call */ + s->group_list.in.domain_handle = &s->ctx->samr.handle; + s->group_list.in.max_size = s->page_size; + s->group_list.in.resume_handle = &s->resume_index; + s->group_list.out.resume_handle = &s->resume_index; + s->group_list.out.num_entries = talloc(s, uint32_t); + if (composite_nomem(s->group_list.out.num_entries, c)) return; + s->group_list.out.sam = talloc(s, struct samr_SamArray *); + if (composite_nomem(s->group_list.out.sam, c)) return; + + /* send the request */ + subreq = dcerpc_samr_EnumDomainGroups_r_send(s, c->event_ctx, + s->ctx->samr.pipe->binding_handle, + &s->group_list); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_groups_enumerated, c); +} + + +/* + * Stage 1.5 (optional): receive samr domain handle + * and request to enumerate accounts + */ +static void continue_samr_domain_opened(struct composite_context *ctx) +{ + struct composite_context *c; + struct grouplist_state *s; + struct tevent_req *subreq; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct grouplist_state); + + /* receive samr domain handle */ + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domain_open); + if (!composite_is_ok(c)) return; + + /* prepare arguments of EnumDomainGroups call */ + s->group_list.in.domain_handle = &s->ctx->samr.handle; + s->group_list.in.max_size = s->page_size; + s->group_list.in.resume_handle = &s->resume_index; + s->group_list.out.resume_handle = &s->resume_index; + s->group_list.out.num_entries = talloc(s, uint32_t); + if (composite_nomem(s->group_list.out.num_entries, c)) return; + s->group_list.out.sam = talloc(s, struct samr_SamArray *); + if (composite_nomem(s->group_list.out.sam, c)) return; + + /* send the request */ + subreq = dcerpc_samr_EnumDomainGroups_r_send(s, c->event_ctx, + s->ctx->samr.pipe->binding_handle, + &s->group_list); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_groups_enumerated, c); +} + + +/* + * Stage 2: receive enumerated groups and their rids + */ +static void continue_groups_enumerated(struct tevent_req *subreq) +{ + struct composite_context *c; + struct grouplist_state *s; + uint32_t i; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct grouplist_state); + + /* receive result of rpc request */ + c->status = dcerpc_samr_EnumDomainGroups_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* get the actual status of the rpc call result + (instead of rpc layer) */ + c->status = s->group_list.out.result; + + /* we're interested in status "ok" as well as two + enum-specific status codes */ + if (NT_STATUS_IS_OK(c->status) || + NT_STATUS_EQUAL(c->status, STATUS_MORE_ENTRIES) || + NT_STATUS_EQUAL(c->status, NT_STATUS_NO_MORE_ENTRIES)) { + + /* get enumerated accounts counter and resume handle (the latter allows + making subsequent call to continue enumeration) */ + s->resume_index = *s->group_list.out.resume_handle; + s->count = *s->group_list.out.num_entries; + + /* prepare returned group accounts array */ + s->groups = talloc_array(c, struct grouplist, (*s->group_list.out.sam)->count); + if (composite_nomem(s->groups, c)) return; + + for (i = 0; i < (*s->group_list.out.sam)->count; i++) { + struct dom_sid *group_sid; + struct samr_SamEntry *entry = &(*s->group_list.out.sam)->entries[i]; + struct dom_sid *domain_sid = (*s->query_domain.out.info)->domain.sid; + + /* construct group sid from returned rid and queried domain sid */ + group_sid = dom_sid_add_rid(c, domain_sid, entry->idx); + if (composite_nomem(group_sid, c)) return; + + /* groupname */ + s->groups[i].groupname = talloc_strdup(s->groups, entry->name.string); + if (composite_nomem(s->groups[i].groupname, c)) return; + + /* sid string */ + s->groups[i].sid = dom_sid_string(s->groups, group_sid); + if (composite_nomem(s->groups[i].sid, c)) return; + } + + /* that's it */ + composite_done(c); + return; + } else { + /* something went wrong */ + composite_error(c, c->status); + return; + } +} + + +/** + * Receive result of GroupList call + * + * @param c composite context returned by send request routine + * @param mem_ctx memory context of this call + * @param io pointer to structure containing arguments and result of this call + * @param nt status + */ +NTSTATUS libnet_GroupList_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_GroupList *io) +{ + NTSTATUS status; + struct grouplist_state *s; + + if (c == NULL || mem_ctx == NULL || io == NULL) { + talloc_free(c); + return NT_STATUS_INVALID_PARAMETER; + } + + status = composite_wait(c); + if (NT_STATUS_IS_OK(status) || + NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES) || + NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) { + + s = talloc_get_type_abort(c->private_data, struct grouplist_state); + + /* get results from composite context */ + io->out.count = s->count; + io->out.resume_index = s->resume_index; + io->out.groups = talloc_steal(mem_ctx, s->groups); + + if (NT_STATUS_IS_OK(status)) { + io->out.error_string = talloc_asprintf(mem_ctx, "Success"); + } else { + /* success, but we're not done yet */ + io->out.error_string = talloc_asprintf(mem_ctx, "Success (status: %s)", + nt_errstr(status)); + } + + } else { + io->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +/** + * Enumerate domain groups + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param io pointer to structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_GroupList(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_GroupList *io) +{ + struct composite_context *c; + + c = libnet_GroupList_send(ctx, mem_ctx, io, NULL); + return libnet_GroupList_recv(c, mem_ctx, io); +} diff --git a/source4/libnet/libnet_group.h b/source4/libnet/libnet_group.h new file mode 100644 index 0000000..8ac4743 --- /dev/null +++ b/source4/libnet/libnet_group.h @@ -0,0 +1,74 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +struct libnet_CreateGroup { + struct { + const char *group_name; + const char *domain_name; + } in; + struct { + const char *error_string; + } out; +}; + +enum libnet_GroupInfo_level { + GROUP_INFO_BY_NAME=0, + GROUP_INFO_BY_SID +}; + +struct libnet_GroupInfo { + struct { + const char *domain_name; + enum libnet_GroupInfo_level level; + union { + const char *group_name; + const struct dom_sid *group_sid; + } data; + } in; + struct { + const char *group_name; + struct dom_sid *group_sid; + uint32_t num_members; + const char *description; + + const char *error_string; + } out; +}; + + +struct libnet_GroupList { + struct { + const char *domain_name; + int page_size; + uint32_t resume_index; + } in; + struct { + int count; + uint32_t resume_index; + + struct grouplist { + const char *sid; + const char *groupname; + } *groups; + + const char *error_string; + } out; +}; diff --git a/source4/libnet/libnet_join.c b/source4/libnet/libnet_join.c new file mode 100644 index 0000000..d1afb4f --- /dev/null +++ b/source4/libnet/libnet_join.c @@ -0,0 +1,1021 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Brad Henry 2005 + + 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_drsuapi_c.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "dsdb/samdb/samdb.h" +#include "ldb_wrap.h" +#include "libcli/security/security.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "param/param.h" +#include "param/provision.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" + +/* + * complete a domain join, when joining to a AD domain: + * 1.) connect and bind to the DRSUAPI pipe + * 2.) do a DsCrackNames() to find the machine account dn + * 3.) connect to LDAP + * 4.) do an ldap search to find the "msDS-KeyVersionNumber" of the machine account + * 5.) set the servicePrincipalName's of the machine account via LDAP, (maybe we should use DsWriteAccountSpn()...) + * 6.) do a DsCrackNames() to find the domain dn + * 7.) find out Site specific stuff, look at libnet_JoinSite() for details + */ +static NTSTATUS libnet_JoinADSDomain(struct libnet_context *ctx, struct libnet_JoinDomain *r) +{ + NTSTATUS status; + + TALLOC_CTX *tmp_ctx; + + const char *realm = r->out.realm; + + const struct dcerpc_binding *samr_binding = r->out.samr_binding; + + struct dcerpc_pipe *drsuapi_pipe; + struct dcerpc_binding *drsuapi_binding; + enum dcerpc_transport_t transport; + struct drsuapi_DsBind r_drsuapi_bind; + struct drsuapi_DsCrackNames r_crack_names; + struct drsuapi_DsNameString names[1]; + struct policy_handle drsuapi_bind_handle; + struct GUID drsuapi_bind_guid; + + struct ldb_context *remote_ldb; + struct ldb_dn *account_dn; + const char *account_dn_str; + const char *remote_ldb_url; + struct ldb_result *res; + struct ldb_message *msg; + + int ret, rtn; + + const char * const attrs[] = { + "msDS-KeyVersionNumber", + "servicePrincipalName", + "dNSHostName", + "objectGUID", + NULL, + }; + + r->out.error_string = NULL; + + /* We need to convert between a samAccountName and domain to a + * DN in the directory. The correct way to do this is with + * DRSUAPI CrackNames */ + + /* Fiddle with the bindings, so get to DRSUAPI on + * NCACN_IP_TCP, sealed */ + tmp_ctx = talloc_named(r, 0, "libnet_JoinADSDomain temp context"); + if (!tmp_ctx) { + r->out.error_string = NULL; + return NT_STATUS_NO_MEMORY; + } + + drsuapi_binding = dcerpc_binding_dup(tmp_ctx, samr_binding); + if (!drsuapi_binding) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + transport = dcerpc_binding_get_transport(drsuapi_binding); + + /* DRSUAPI is only available on IP_TCP, and locally on NCALRPC */ + if (transport != NCALRPC) { + status = dcerpc_binding_set_transport(drsuapi_binding, NCACN_IP_TCP); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(r, + "dcerpc_binding_set_transport failed: %s", + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + } + + status = dcerpc_binding_set_string_option(drsuapi_binding, "endpoint", NULL); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(r, + "dcerpc_binding_set_string_option failed: %s", + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + status = dcerpc_binding_set_flags(drsuapi_binding, DCERPC_SEAL, 0); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(r, + "dcerpc_binding_set_flags failed: %s", + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + status = dcerpc_pipe_connect_b(tmp_ctx, + &drsuapi_pipe, + drsuapi_binding, + &ndr_table_drsuapi, + ctx->cred, + ctx->event_ctx, + ctx->lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(r, + "Connection to DRSUAPI pipe of PDC of domain '%s' failed: %s", + r->out.domain_name, + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + /* get a DRSUAPI pipe handle */ + GUID_from_string(DRSUAPI_DS_BIND_GUID, &drsuapi_bind_guid); + + r_drsuapi_bind.in.bind_guid = &drsuapi_bind_guid; + r_drsuapi_bind.in.bind_info = NULL; + r_drsuapi_bind.out.bind_handle = &drsuapi_bind_handle; + + status = dcerpc_drsuapi_DsBind_r(drsuapi_pipe->binding_handle, tmp_ctx, &r_drsuapi_bind); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string + = talloc_asprintf(r, + "dcerpc_drsuapi_DsBind failed - %s", + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } else if (!W_ERROR_IS_OK(r_drsuapi_bind.out.result)) { + r->out.error_string + = talloc_asprintf(r, + "DsBind failed - %s", + win_errstr(r_drsuapi_bind.out.result)); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Actually 'crack' the names */ + ZERO_STRUCT(r_crack_names); + r_crack_names.in.bind_handle = &drsuapi_bind_handle; + r_crack_names.in.level = 1; + r_crack_names.in.req = talloc(r, union drsuapi_DsNameRequest); + if (!r_crack_names.in.req) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + r_crack_names.in.req->req1.codepage = 1252; /* western european */ + r_crack_names.in.req->req1.language = 0x00000407; /* german */ + r_crack_names.in.req->req1.count = 1; + r_crack_names.in.req->req1.names = names; + r_crack_names.in.req->req1.format_flags = DRSUAPI_DS_NAME_FLAG_NO_FLAGS; + r_crack_names.in.req->req1.format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY; + r_crack_names.in.req->req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779; + names[0].str = dom_sid_string(tmp_ctx, r->out.account_sid); + if (!names[0].str) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + r_crack_names.out.ctr = talloc(r, union drsuapi_DsNameCtr); + r_crack_names.out.level_out = talloc(r, uint32_t); + if (!r_crack_names.out.ctr || !r_crack_names.out.level_out) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = dcerpc_drsuapi_DsCrackNames_r(drsuapi_pipe->binding_handle, tmp_ctx, &r_crack_names); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string + = talloc_asprintf(r, + "dcerpc_drsuapi_DsCrackNames for [%s] failed - %s", + names[0].str, + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } else if (!W_ERROR_IS_OK(r_crack_names.out.result)) { + r->out.error_string + = talloc_asprintf(r, + "DsCrackNames failed - %s", win_errstr(r_crack_names.out.result)); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } else if (*r_crack_names.out.level_out != 1 + || !r_crack_names.out.ctr->ctr1 + || r_crack_names.out.ctr->ctr1->count != 1) { + r->out.error_string = talloc_asprintf(r, "DsCrackNames failed"); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } else if (r_crack_names.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + r->out.error_string = talloc_asprintf(r, "DsCrackNames failed: %d", r_crack_names.out.ctr->ctr1->array[0].status); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } else if (r_crack_names.out.ctr->ctr1->array[0].result_name == NULL) { + r->out.error_string = talloc_asprintf(r, "DsCrackNames failed: no result name"); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + /* Store the DN of our machine account. */ + account_dn_str = r_crack_names.out.ctr->ctr1->array[0].result_name; + + /* Now we know the user's DN, open with LDAP, read and modify a few things */ + + remote_ldb_url = talloc_asprintf(tmp_ctx, "ldap://%s", + dcerpc_binding_get_string_option(drsuapi_binding, "target_hostname")); + if (!remote_ldb_url) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + remote_ldb = ldb_wrap_connect(tmp_ctx, ctx->event_ctx, ctx->lp_ctx, + remote_ldb_url, + NULL, ctx->cred, 0); + if (!remote_ldb) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + account_dn = ldb_dn_new(tmp_ctx, remote_ldb, account_dn_str); + if (account_dn == NULL) { + r->out.error_string = talloc_asprintf(r, "Invalid account dn: %s", + account_dn_str); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* search for the user's record */ + ret = ldb_search(remote_ldb, tmp_ctx, &res, + account_dn, LDB_SCOPE_BASE, attrs, NULL); + if (ret != LDB_SUCCESS) { + r->out.error_string = talloc_asprintf(r, "ldb_search for %s failed - %s", + account_dn_str, ldb_errstring(remote_ldb)); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + if (res->count != 1) { + r->out.error_string = talloc_asprintf(r, "ldb_search for %s failed - found %d entries", + account_dn_str, res->count); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Prepare a new message, for the modify */ + msg = ldb_msg_new(tmp_ctx); + if (!msg) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + msg->dn = res->msgs[0]->dn; + + { + unsigned int i; + const char *service_principal_name[2]; + const char *dns_host_name = strlower_talloc(msg, + talloc_asprintf(msg, + "%s.%s", + r->in.netbios_name, + realm)); + + if (!dns_host_name) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + service_principal_name[0] = talloc_asprintf(msg, "HOST/%s", + dns_host_name); + service_principal_name[1] = talloc_asprintf(msg, "HOST/%s", + r->in.netbios_name); + + for (i=0; i < ARRAY_SIZE(service_principal_name); i++) { + if (!service_principal_name[i]) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + rtn = ldb_msg_add_string(msg, "servicePrincipalName", + service_principal_name[i]); + if (rtn != LDB_SUCCESS) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + } + + rtn = ldb_msg_add_string(msg, "dNSHostName", dns_host_name); + if (rtn != LDB_SUCCESS) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + rtn = dsdb_replace(remote_ldb, msg, 0); + if (rtn != LDB_SUCCESS) { + r->out.error_string + = talloc_asprintf(r, + "Failed to replace entries on %s", + ldb_dn_get_linearized(msg->dn)); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + msg = ldb_msg_new(tmp_ctx); + if (!msg) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + msg->dn = res->msgs[0]->dn; + + rtn = samdb_msg_add_uint(remote_ldb, msg, msg, + "msDS-SupportedEncryptionTypes", ENC_ALL_TYPES); + if (rtn != LDB_SUCCESS) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + rtn = dsdb_replace(remote_ldb, msg, 0); + /* The remote server may not support this attribute, if it + * isn't a modern schema */ + if (rtn != LDB_SUCCESS && rtn != LDB_ERR_NO_SUCH_ATTRIBUTE) { + r->out.error_string + = talloc_asprintf(r, + "Failed to replace msDS-SupportedEncryptionTypes on %s", + ldb_dn_get_linearized(msg->dn)); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* DsCrackNames to find out the DN of the domain. */ + r_crack_names.in.req->req1.format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT; + r_crack_names.in.req->req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779; + names[0].str = talloc_asprintf(tmp_ctx, "%s\\", r->out.domain_name); + if (!names[0].str) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = dcerpc_drsuapi_DsCrackNames_r(drsuapi_pipe->binding_handle, tmp_ctx, &r_crack_names); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string + = talloc_asprintf(r, + "dcerpc_drsuapi_DsCrackNames for [%s] failed - %s", + r->in.domain_name, + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } else if (!W_ERROR_IS_OK(r_crack_names.out.result)) { + r->out.error_string + = talloc_asprintf(r, + "DsCrackNames failed - %s", win_errstr(r_crack_names.out.result)); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } else if (*r_crack_names.out.level_out != 1 + || !r_crack_names.out.ctr->ctr1 + || r_crack_names.out.ctr->ctr1->count != 1 + || !r_crack_names.out.ctr->ctr1->array[0].result_name + || r_crack_names.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + r->out.error_string = talloc_asprintf(r, "DsCrackNames failed"); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + /* Store the account DN. */ + r->out.account_dn_str = account_dn_str; + talloc_steal(r, account_dn_str); + + /* Store the domain DN. */ + r->out.domain_dn_str = r_crack_names.out.ctr->ctr1->array[0].result_name; + talloc_steal(r, r_crack_names.out.ctr->ctr1->array[0].result_name); + + /* Store the KVNO of the account, critical for some kerberos + * operations */ + r->out.kvno = ldb_msg_find_attr_as_uint(res->msgs[0], "msDS-KeyVersionNumber", 0); + + /* Store the account GUID. */ + r->out.account_guid = samdb_result_guid(res->msgs[0], "objectGUID"); + + if (r->in.acct_type == ACB_SVRTRUST) { + status = libnet_JoinSite(ctx, remote_ldb, r); + } + talloc_free(tmp_ctx); + + return status; +} + +/* + * do a domain join using DCERPC/SAMR calls + * - connect to the LSA pipe, to try and find out information about the domain + * - create a secondary connection to SAMR pipe + * - do a samr_Connect to get a policy handle + * - do a samr_LookupDomain to get the domain sid + * - do a samr_OpenDomain to get a domain handle + * - do a samr_CreateAccount to try and get a new account + * + * If that fails, do: + * - do a samr_LookupNames to get the users rid + * - do a samr_OpenUser to get a user handle + * - potentially delete and recreate the user + * - assert the account is of the right type with samrQueryUserInfo + * + * - call libnet_SetPassword_samr_handle to set the password, + * and pass a samr_UserInfo21 struct to set full_name and the account flags + * + * - do some ADS specific things when we join as Domain Controller, + * look at libnet_joinADSDomain() for the details + */ +NTSTATUS libnet_JoinDomain(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_JoinDomain *r) +{ + TALLOC_CTX *tmp_ctx; + + NTSTATUS status, cu_status; + + struct libnet_RpcConnect *connect_with_info; + struct dcerpc_pipe *samr_pipe; + + struct samr_Connect sc; + struct policy_handle p_handle; + struct samr_OpenDomain od; + struct policy_handle d_handle; + struct samr_LookupNames ln; + struct samr_Ids rids, types; + struct samr_OpenUser ou; + struct samr_CreateUser2 cu; + struct policy_handle *u_handle = NULL; + struct samr_QueryUserInfo qui; + union samr_UserInfo *uinfo; + struct samr_UserInfo21 u_info21; + union libnet_SetPassword r2; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + struct lsa_String samr_account_name; + + uint32_t acct_flags, old_acct_flags; + uint32_t rid, access_granted; + int policy_min_pw_len = 0; + + struct dom_sid *account_sid = NULL; + const char *password_str = NULL; + + r->out.error_string = NULL; + ZERO_STRUCT(r2); + + tmp_ctx = talloc_named(mem_ctx, 0, "libnet_Join temp context"); + if (!tmp_ctx) { + r->out.error_string = NULL; + return NT_STATUS_NO_MEMORY; + } + + u_handle = talloc(tmp_ctx, struct policy_handle); + if (!u_handle) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + connect_with_info = talloc_zero(tmp_ctx, struct libnet_RpcConnect); + if (!connect_with_info) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* prepare connect to the SAMR pipe of PDC */ + if (r->in.level == LIBNET_JOINDOMAIN_AUTOMATIC) { + connect_with_info->in.binding = NULL; + connect_with_info->in.name = r->in.domain_name; + } else { + connect_with_info->in.binding = r->in.binding; + connect_with_info->in.name = NULL; + } + + /* This level makes a connection to the LSA pipe on the way, + * to get some useful bits of information about the domain */ + connect_with_info->level = LIBNET_RPC_CONNECT_DC_INFO; + connect_with_info->in.dcerpc_iface = &ndr_table_samr; + + /* + establish the SAMR connection + */ + status = libnet_RpcConnect(ctx, tmp_ctx, connect_with_info); + if (!NT_STATUS_IS_OK(status)) { + if (r->in.binding) { + r->out.error_string = talloc_asprintf(mem_ctx, + "Connection to SAMR pipe of DC %s failed: %s", + r->in.binding, connect_with_info->out.error_string); + } else { + r->out.error_string = talloc_asprintf(mem_ctx, + "Connection to SAMR pipe of PDC for %s failed: %s", + r->in.domain_name, connect_with_info->out.error_string); + } + talloc_free(tmp_ctx); + return status; + } + + samr_pipe = connect_with_info->out.dcerpc_pipe; + + /* prepare samr_Connect */ + ZERO_STRUCT(p_handle); + sc.in.system_name = NULL; + sc.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + sc.out.connect_handle = &p_handle; + + /* 2. do a samr_Connect to get a policy handle */ + status = dcerpc_samr_Connect_r(samr_pipe->binding_handle, tmp_ctx, &sc); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(sc.out.result)) { + status = sc.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "samr_Connect failed: %s", + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + /* If this is a connection on ncacn_ip_tcp to Win2k3 SP1, we don't get back this useful info */ + if (!connect_with_info->out.domain_name) { + if (r->in.level == LIBNET_JOINDOMAIN_AUTOMATIC) { + connect_with_info->out.domain_name = talloc_strdup(tmp_ctx, r->in.domain_name); + } else { + /* Bugger, we just lost our way to automatically find the domain name */ + connect_with_info->out.domain_name = talloc_strdup(tmp_ctx, lpcfg_workgroup(ctx->lp_ctx)); + connect_with_info->out.realm = talloc_strdup(tmp_ctx, lpcfg_realm(ctx->lp_ctx)); + } + } + + /* Perhaps we didn't get a SID above, because we are against ncacn_ip_tcp */ + if (!connect_with_info->out.domain_sid) { + struct lsa_String name; + struct samr_LookupDomain l; + struct dom_sid2 *sid = NULL; + name.string = connect_with_info->out.domain_name; + l.in.connect_handle = &p_handle; + l.in.domain_name = &name; + l.out.sid = &sid; + + status = dcerpc_samr_LookupDomain_r(samr_pipe->binding_handle, tmp_ctx, &l); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(l.out.result)) { + status = l.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "SAMR LookupDomain failed: %s", + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + connect_with_info->out.domain_sid = *l.out.sid; + } + + /* prepare samr_OpenDomain */ + ZERO_STRUCT(d_handle); + od.in.connect_handle = &p_handle; + od.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + od.in.sid = connect_with_info->out.domain_sid; + od.out.domain_handle = &d_handle; + + /* do a samr_OpenDomain to get a domain handle */ + status = dcerpc_samr_OpenDomain_r(samr_pipe->binding_handle, tmp_ctx, &od); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(od.out.result)) { + status = od.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + struct dom_sid_buf buf; + r->out.error_string = talloc_asprintf( + mem_ctx, + "samr_OpenDomain for [%s] failed: %s", + dom_sid_str_buf(connect_with_info->out.domain_sid, + &buf), + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + /* prepare samr_CreateUser2 */ + ZERO_STRUCTP(u_handle); + cu.in.domain_handle = &d_handle; + cu.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + samr_account_name.string = r->in.account_name; + cu.in.account_name = &samr_account_name; + cu.in.acct_flags = r->in.acct_type; + cu.out.user_handle = u_handle; + cu.out.rid = &rid; + cu.out.access_granted = &access_granted; + + /* do a samr_CreateUser2 to get an account handle, or an error */ + cu_status = dcerpc_samr_CreateUser2_r(samr_pipe->binding_handle, tmp_ctx, &cu); + if (NT_STATUS_IS_OK(cu_status) && !NT_STATUS_IS_OK(cu.out.result)) { + cu_status = cu.out.result; + } + status = cu_status; + if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) { + /* prepare samr_LookupNames */ + ln.in.domain_handle = &d_handle; + ln.in.num_names = 1; + ln.in.names = talloc_array(tmp_ctx, struct lsa_String, 1); + ln.out.rids = &rids; + ln.out.types = &types; + if (!ln.in.names) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + ln.in.names[0].string = r->in.account_name; + + /* 5. do a samr_LookupNames to get the users rid */ + status = dcerpc_samr_LookupNames_r(samr_pipe->binding_handle, tmp_ctx, &ln); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(ln.out.result)) { + status = ln.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "samr_LookupNames for [%s] failed: %s", + r->in.account_name, + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + /* check if we got one RID for the user */ + if (ln.out.rids->count != 1) { + r->out.error_string = talloc_asprintf(mem_ctx, + "samr_LookupNames for [%s] returns %d RIDs", + r->in.account_name, ln.out.rids->count); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + if (ln.out.types->count != 1) { + r->out.error_string = talloc_asprintf(mem_ctx, + "samr_LookupNames for [%s] returns %d RID TYPEs", + r->in.account_name, ln.out.types->count); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + /* prepare samr_OpenUser */ + ZERO_STRUCTP(u_handle); + ou.in.domain_handle = &d_handle; + ou.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + ou.in.rid = ln.out.rids->ids[0]; + rid = ou.in.rid; + ou.out.user_handle = u_handle; + + /* 6. do a samr_OpenUser to get a user handle */ + status = dcerpc_samr_OpenUser_r(samr_pipe->binding_handle, tmp_ctx, &ou); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(ou.out.result)) { + status = ou.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "samr_OpenUser for [%s] failed: %s", + r->in.account_name, + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + if (r->in.recreate_account) { + struct samr_DeleteUser d; + d.in.user_handle = u_handle; + d.out.user_handle = u_handle; + status = dcerpc_samr_DeleteUser_r(samr_pipe->binding_handle, mem_ctx, &d); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(d.out.result)) { + status = d.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "samr_DeleteUser (for recreate) of [%s] failed: %s", + r->in.account_name, + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + /* We want to recreate, so delete and another samr_CreateUser2 */ + + /* &cu filled in above */ + status = dcerpc_samr_CreateUser2_r(samr_pipe->binding_handle, tmp_ctx, &cu); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(cu.out.result)) { + status = cu.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "samr_CreateUser2 (recreate) for [%s] failed: %s", + r->in.account_name, nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + } + } else if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "samr_CreateUser2 for [%s] failed: %s", + r->in.account_name, nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + /* prepare samr_QueryUserInfo (get flags) */ + qui.in.user_handle = u_handle; + qui.in.level = 16; + qui.out.info = &uinfo; + + status = dcerpc_samr_QueryUserInfo_r(samr_pipe->binding_handle, tmp_ctx, &qui); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(qui.out.result)) { + status = qui.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "samr_QueryUserInfo for [%s] failed: %s", + r->in.account_name, + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + if (!uinfo) { + status = NT_STATUS_INVALID_PARAMETER; + r->out.error_string + = talloc_asprintf(mem_ctx, + "samr_QueryUserInfo failed to return qui.out.info for [%s]: %s", + r->in.account_name, nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + old_acct_flags = (uinfo->info16.acct_flags & (ACB_WSTRUST | ACB_SVRTRUST | ACB_DOMTRUST)); + /* Possibly bail if the account is of the wrong type */ + if (old_acct_flags + != r->in.acct_type) { + const char *old_account_type, *new_account_type; + switch (old_acct_flags) { + case ACB_WSTRUST: + old_account_type = "domain member (member)"; + break; + case ACB_SVRTRUST: + old_account_type = "domain controller (bdc)"; + break; + case ACB_DOMTRUST: + old_account_type = "trusted domain"; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + switch (r->in.acct_type) { + case ACB_WSTRUST: + new_account_type = "domain member (member)"; + break; + case ACB_SVRTRUST: + new_account_type = "domain controller (bdc)"; + break; + case ACB_DOMTRUST: + new_account_type = "trusted domain"; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + if (!NT_STATUS_EQUAL(cu_status, NT_STATUS_USER_EXISTS)) { + /* We created a new user, but they didn't come out the right type?!? */ + r->out.error_string + = talloc_asprintf(mem_ctx, + "We asked to create a new machine account (%s) of type %s, " + "but we got an account of type %s. This is unexpected. " + "Perhaps delete the account and try again.", + r->in.account_name, new_account_type, old_account_type); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } else { + /* The account is of the wrong type, so bail */ + + /* TODO: We should allow a --force option to override, and redo this from the top setting r.in.recreate_account */ + r->out.error_string + = talloc_asprintf(mem_ctx, + "The machine account (%s) already exists in the domain %s, " + "but is a %s. You asked to join as a %s. Please delete " + "the account and try again.", + r->in.account_name, connect_with_info->out.domain_name, old_account_type, new_account_type); + talloc_free(tmp_ctx); + return NT_STATUS_USER_EXISTS; + } + } else { + acct_flags = uinfo->info16.acct_flags; + } + + acct_flags = (acct_flags & ~(ACB_DISABLED|ACB_PWNOTREQ)); + + /* Find out what password policy this user has */ + pwp.in.user_handle = u_handle; + pwp.out.info = &info; + + status = dcerpc_samr_GetUserPwInfo_r(samr_pipe->binding_handle, tmp_ctx, &pwp); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(pwp.out.result)) { + status = pwp.out.result; + } + if (NT_STATUS_IS_OK(status)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + + if (r->in.account_pass != NULL) { + password_str = talloc_strdup(tmp_ctx, r->in.account_pass); + } else { + /* Grab a password of that minimum length */ + password_str = generate_random_password(tmp_ctx, + MAX(8, policy_min_pw_len), 255); + } + if (!password_str) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* set full_name and reset flags */ + ZERO_STRUCT(u_info21); + u_info21.full_name.string = r->in.account_name; + u_info21.acct_flags = acct_flags; + u_info21.fields_present = SAMR_FIELD_FULL_NAME | SAMR_FIELD_ACCT_FLAGS; + + r2.samr_handle.level = LIBNET_SET_PASSWORD_SAMR_HANDLE; + r2.samr_handle.in.account_name = r->in.account_name; + r2.samr_handle.in.newpassword = password_str; + r2.samr_handle.in.user_handle = u_handle; + r2.samr_handle.in.dcerpc_pipe = samr_pipe; + r2.samr_handle.in.info21 = &u_info21; + + status = libnet_SetPassword(ctx, tmp_ctx, &r2); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_steal(mem_ctx, r2.samr_handle.out.error_string); + talloc_free(tmp_ctx); + return status; + } + + account_sid = dom_sid_add_rid(mem_ctx, connect_with_info->out.domain_sid, rid); + if (!account_sid) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* Finish out by pushing various bits of status data out for the caller to use */ + r->out.join_password = password_str; + talloc_steal(mem_ctx, r->out.join_password); + + r->out.domain_sid = connect_with_info->out.domain_sid; + talloc_steal(mem_ctx, r->out.domain_sid); + + r->out.account_sid = account_sid; + talloc_steal(mem_ctx, r->out.account_sid); + + r->out.domain_name = connect_with_info->out.domain_name; + talloc_steal(mem_ctx, r->out.domain_name); + r->out.realm = connect_with_info->out.realm; + talloc_steal(mem_ctx, r->out.realm); + r->out.samr_pipe = samr_pipe; + talloc_reparent(tmp_ctx, mem_ctx, samr_pipe); + r->out.samr_binding = samr_pipe->binding; + r->out.user_handle = u_handle; + talloc_steal(mem_ctx, u_handle); + r->out.error_string = r2.samr_handle.out.error_string; + talloc_steal(mem_ctx, r2.samr_handle.out.error_string); + r->out.kvno = 0; + r->out.server_dn_str = NULL; + talloc_free(tmp_ctx); + + /* Now, if it was AD, then we want to start looking changing a + * few more things. Otherwise, we are done. */ + if (r->out.realm) { + status = libnet_JoinADSDomain(ctx, r); + return status; + } + + return status; +} + +NTSTATUS libnet_Join_member(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_Join_member *r) +{ + NTSTATUS status; + TALLOC_CTX *tmp_mem; + struct libnet_JoinDomain *r2; + struct provision_store_self_join_settings *set_secrets; + uint32_t acct_type = 0; + const char *account_name; + const char *netbios_name; + const char *error_string = NULL; + + r->out.error_string = NULL; + + tmp_mem = talloc_new(mem_ctx); + if (!tmp_mem) { + return NT_STATUS_NO_MEMORY; + } + + r2 = talloc_zero(tmp_mem, struct libnet_JoinDomain); + if (!r2) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + acct_type = ACB_WSTRUST; + + if (r->in.netbios_name != NULL) { + netbios_name = r->in.netbios_name; + } else { + netbios_name = talloc_strdup(tmp_mem, lpcfg_netbios_name(ctx->lp_ctx)); + if (!netbios_name) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + + account_name = talloc_asprintf(tmp_mem, "%s$", netbios_name); + if (!account_name) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + /* + * join the domain + */ + r2->in.domain_name = r->in.domain_name; + r2->in.account_name = account_name; + r2->in.netbios_name = netbios_name; + r2->in.level = LIBNET_JOINDOMAIN_AUTOMATIC; + r2->in.acct_type = acct_type; + r2->in.recreate_account = false; + r2->in.account_pass = r->in.account_pass; + status = libnet_JoinDomain(ctx, r2, r2); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_steal(mem_ctx, r2->out.error_string); + goto out; + } + + set_secrets = talloc_zero(tmp_mem, + struct provision_store_self_join_settings); + if (!set_secrets) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + set_secrets->domain_name = r2->out.domain_name; + set_secrets->realm = r2->out.realm; + set_secrets->netbios_name = netbios_name; + set_secrets->secure_channel_type = SEC_CHAN_WKSTA; + set_secrets->machine_password = r2->out.join_password; + set_secrets->key_version_number = r2->out.kvno; + set_secrets->domain_sid = r2->out.domain_sid; + + status = provision_store_self_join(ctx, ctx->lp_ctx, ctx->event_ctx, set_secrets, &error_string); + if (!NT_STATUS_IS_OK(status)) { + if (error_string) { + r->out.error_string = talloc_steal(mem_ctx, error_string); + } else { + r->out.error_string + = talloc_asprintf(mem_ctx, + "provision_store_self_join failed with %s", + nt_errstr(status)); + } + goto out; + } + + /* move all out parameter to the callers TALLOC_CTX */ + r->out.join_password = talloc_move(mem_ctx, &r2->out.join_password); + r->out.domain_sid = talloc_move(mem_ctx, &r2->out.domain_sid); + r->out.domain_name = talloc_move(mem_ctx, &r2->out.domain_name); + status = NT_STATUS_OK; +out: + talloc_free(tmp_mem); + return status; +} + diff --git a/source4/libnet/libnet_join.h b/source4/libnet/libnet_join.h new file mode 100644 index 0000000..c8a4a40 --- /dev/null +++ b/source4/libnet/libnet_join.h @@ -0,0 +1,101 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Brad Henry 2005 + + 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_JOIN_H__ +#define __LIBNET_JOIN_H__ + +#include "librpc/gen_ndr/netlogon.h" + +enum libnet_Join_level { + LIBNET_JOIN_AUTOMATIC, + LIBNET_JOIN_SPECIFIED, +}; + +enum libnet_JoinDomain_level { + LIBNET_JOINDOMAIN_AUTOMATIC, + LIBNET_JOINDOMAIN_SPECIFIED, +}; + +struct libnet_JoinDomain { + struct { + const char *domain_name; + const char *account_name; + const char *netbios_name; + const char *binding; + enum libnet_JoinDomain_level level; + uint32_t acct_type; + bool recreate_account; + const char *account_pass; + } in; + + struct { + const char *error_string; + const char *join_password; + struct dom_sid *domain_sid; + const char *domain_name; + const char *realm; + const char *domain_dn_str; + const char *account_dn_str; + const char *server_dn_str; + uint32_t kvno; /* msDS-KeyVersionNumber */ + struct dcerpc_pipe *samr_pipe; + const struct dcerpc_binding *samr_binding; + struct policy_handle *user_handle; + struct dom_sid *account_sid; + struct GUID account_guid; + } out; +}; + +struct libnet_Join_member { + struct { + const char *domain_name; + const char *netbios_name; + enum libnet_Join_level level; + const char *account_pass; + } in; + + struct { + const char *error_string; + const char *join_password; + struct dom_sid *domain_sid; + const char *domain_name; + } out; +}; + +struct libnet_set_join_secrets { + struct { + const char *domain_name; + const char *realm; + const char *netbios_name; + const char *account_name; + enum netr_SchannelType join_type; + const char *join_password; + int kvno; + struct dom_sid *domain_sid; + } in; + + struct { + const char *error_string; + } out; +}; + + +#endif /* __LIBNET_JOIN_H__ */ diff --git a/source4/libnet/libnet_lookup.c b/source4/libnet/libnet_lookup.c new file mode 100644 index 0000000..63ce601 --- /dev/null +++ b/source4/libnet/libnet_lookup.c @@ -0,0 +1,448 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + 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/>. +*/ + +/* + a composite function for name resolving +*/ + +#include "includes.h" +#include "libnet/libnet.h" +#include "libcli/composite/composite.h" +#include "auth/credentials/credentials.h" +#include "libcli/resolve/resolve.h" +#include "libcli/finddc.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "param/param.h" + +struct lookup_state { + struct nbt_name hostname; + const char *address; +}; + + +static void continue_name_resolved(struct composite_context *ctx); + + +/** + * Sends asynchronous Lookup request + * + * @param io arguments and result of the call + */ + +struct composite_context *libnet_Lookup_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_Lookup *io) +{ + struct composite_context *c; + struct lookup_state *s; + struct composite_context *cresolve_req; + struct resolve_context *resolve_ctx; + + /* allocate context and state structures */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct lookup_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + if (io == NULL || io->in.hostname == NULL) { + composite_error(c, NT_STATUS_INVALID_PARAMETER); + return c; + } + + /* parameters */ + s->hostname.name = talloc_strdup(s, io->in.hostname); + if (composite_nomem(s->hostname.name, c)) return c; + + s->hostname.type = io->in.type; + s->hostname.scope = NULL; + + /* name resolution methods */ + if (io->in.resolve_ctx) { + resolve_ctx = io->in.resolve_ctx; + } else { + resolve_ctx = ctx->resolve_ctx; + } + + /* send resolve request */ + cresolve_req = resolve_name_send(resolve_ctx, s, &s->hostname, c->event_ctx); + if (composite_nomem(cresolve_req, c)) return c; + + composite_continue(c, cresolve_req, continue_name_resolved, c); + return c; +} + + +static void continue_name_resolved(struct composite_context *ctx) +{ + struct composite_context *c; + struct lookup_state *s; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct lookup_state); + + c->status = resolve_name_recv(ctx, s, &s->address); + + composite_done(c); +} + + +/** + * Waits for and receives results of asynchronous Lookup call + * + * @param c composite context returned by asynchronous Lookup call + * @param mem_ctx memory context of the call + * @param io pointer to results (and arguments) of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_Lookup_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_Lookup *io) +{ + NTSTATUS status; + struct lookup_state *s; + + status = composite_wait(c); + if (NT_STATUS_IS_OK(status)) { + char **address; + + s = talloc_get_type(c->private_data, struct lookup_state); + + address = str_list_make_single(mem_ctx, s->address); + NT_STATUS_HAVE_NO_MEMORY(address); + io->out.address = discard_const_p(const char *, address); + } + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of Lookup call + * + * @param mem_ctx memory context for the call + * @param io arguments and results of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_Lookup(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_Lookup *io) +{ + struct composite_context *c = libnet_Lookup_send(ctx, mem_ctx, io); + return libnet_Lookup_recv(c, mem_ctx, io); +} + + +/* + * Shortcut functions to find common types of name + * (and skip nbt name type argument) + */ + + +/** + * Sends asynchronous LookupHost request + */ +struct composite_context* libnet_LookupHost_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_Lookup *io) +{ + io->in.type = NBT_NAME_SERVER; + return libnet_Lookup_send(ctx, mem_ctx, io); +} + + + +/** + * Synchronous version of LookupHost call + */ +NTSTATUS libnet_LookupHost(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_Lookup *io) +{ + struct composite_context *c = libnet_LookupHost_send(ctx, mem_ctx, io); + return libnet_Lookup_recv(c, mem_ctx, io); +} + + +/** + * Sends asynchronous LookupDCs request + */ +struct tevent_req *libnet_LookupDCs_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_LookupDCs *io) +{ + struct tevent_req *req; + struct finddcs finddcs_io; + + ZERO_STRUCT(finddcs_io); + + if (strcasecmp_m(io->in.domain_name, lpcfg_workgroup(ctx->lp_ctx)) == 0) { + finddcs_io.in.domain_name = lpcfg_dnsdomain(ctx->lp_ctx); + } else { + finddcs_io.in.domain_name = io->in.domain_name; + } + finddcs_io.in.minimum_dc_flags = NBT_SERVER_LDAP | NBT_SERVER_DS | NBT_SERVER_WRITABLE; + finddcs_io.in.server_address = ctx->server_address; + + req = finddcs_cldap_send(mem_ctx, &finddcs_io, ctx->resolve_ctx, ctx->event_ctx); + return req; +} + +/** + * Waits for and receives results of asynchronous Lookup call + * + * @param c composite context returned by asynchronous Lookup call + * @param mem_ctx memory context of the call + * @param io pointer to results (and arguments) of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_LookupDCs_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct libnet_LookupDCs *io) +{ + NTSTATUS status; + struct finddcs finddcs_io; + status = finddcs_cldap_recv(req, mem_ctx, &finddcs_io); + talloc_free(req); + io->out.num_dcs = 1; + io->out.dcs = talloc(mem_ctx, struct nbt_dc_name); + NT_STATUS_HAVE_NO_MEMORY(io->out.dcs); + io->out.dcs[0].address = finddcs_io.out.address; + io->out.dcs[0].name = finddcs_io.out.netlogon.data.nt5_ex.pdc_dns_name; + return status; +} + + +/** + * Synchronous version of LookupDCs + */ +NTSTATUS libnet_LookupDCs(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_LookupDCs *io) +{ + struct tevent_req *req = libnet_LookupDCs_send(ctx, mem_ctx, io); + return libnet_LookupDCs_recv(req, mem_ctx, io); +} + + +struct lookup_name_state { + struct libnet_context *ctx; + const char *name; + uint32_t count; + struct libnet_DomainOpen domopen; + struct lsa_LookupNames lookup; + struct lsa_TransSidArray sids; + struct lsa_String *names; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static bool prepare_lookup_params(struct libnet_context *ctx, + struct composite_context *c, + struct lookup_name_state *s); +static void continue_lookup_name(struct composite_context *ctx); +static void continue_name_found(struct tevent_req *subreq); + + +struct composite_context* libnet_LookupName_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_LookupName *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct lookup_name_state *s; + struct tevent_req *subreq; + bool prereq_met = false; + + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct lookup_name_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + s->name = talloc_strdup(c, io->in.name); + s->monitor_fn = monitor; + s->ctx = ctx; + + prereq_met = lsa_domain_opened(ctx, c, io->in.domain_name, &c, &s->domopen, + continue_lookup_name, monitor); + if (!prereq_met) return c; + + if (!prepare_lookup_params(ctx, c, s)) return c; + + subreq = dcerpc_lsa_LookupNames_r_send(s, c->event_ctx, + ctx->lsa.pipe->binding_handle, + &s->lookup); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_name_found, c); + return c; +} + + +static bool prepare_lookup_params(struct libnet_context *ctx, + struct composite_context *c, + struct lookup_name_state *s) +{ + const int single_name = 1; + + s->sids.count = 0; + s->sids.sids = NULL; + + s->names = talloc_array(s, struct lsa_String, single_name); + if (composite_nomem(s->names, c)) return false; + s->names[0].string = s->name; + + s->lookup.in.handle = &ctx->lsa.handle; + s->lookup.in.num_names = single_name; + s->lookup.in.names = s->names; + s->lookup.in.sids = &s->sids; + s->lookup.in.level = 1; + s->lookup.in.count = &s->count; + s->lookup.out.count = &s->count; + s->lookup.out.sids = &s->sids; + s->lookup.out.domains = talloc_zero(s, struct lsa_RefDomainList *); + if (composite_nomem(s->lookup.out.domains, c)) return false; + + return true; +} + + +static void continue_lookup_name(struct composite_context *ctx) +{ + struct composite_context *c; + struct lookup_name_state *s; + struct tevent_req *subreq; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct lookup_name_state); + + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domopen); + if (!composite_is_ok(c)) return; + + if (!prepare_lookup_params(s->ctx, c, s)) return; + + subreq = dcerpc_lsa_LookupNames_r_send(s, c->event_ctx, + s->ctx->lsa.pipe->binding_handle, + &s->lookup); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_name_found, c); +} + + +static void continue_name_found(struct tevent_req *subreq) +{ + struct composite_context *c; + struct lookup_name_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct lookup_name_state); + + c->status = dcerpc_lsa_LookupNames_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + c->status = s->lookup.out.result; + if (!composite_is_ok(c)) return; + + if (s->lookup.out.sids->count != s->lookup.in.num_names) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + composite_done(c); +} + + +NTSTATUS libnet_LookupName_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_LookupName *io) +{ + NTSTATUS status; + struct lookup_name_state *s = NULL; + struct lsa_RefDomainList *domains = NULL; + struct lsa_TransSidArray *sids = NULL; + + status = composite_wait(c); + ZERO_STRUCT(io->out); + + if (!NT_STATUS_IS_OK(status)) { + io->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", + nt_errstr(status)); + goto done; + } + + s = talloc_get_type(c->private_data, struct lookup_name_state); + + if (*s->lookup.out.count == 0) { + goto success; + } + + domains = *s->lookup.out.domains; + sids = s->lookup.out.sids; + + if (domains == NULL || sids == NULL) { + status = NT_STATUS_UNSUCCESSFUL; + io->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", + nt_errstr(status)); + goto done; + } + + if (sids->count == 0) { + goto success; + } + + io->out.rid = sids->sids[0].rid; + io->out.sid_type = sids->sids[0].sid_type; + if (domains->count > 0) { + io->out.sid = dom_sid_add_rid(mem_ctx, domains->domains[0].sid, + io->out.rid); + if (io->out.sid == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + io->out.sidstr = dom_sid_string(mem_ctx, io->out.sid); + if (io->out.sidstr == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + } + +success: + io->out.error_string = talloc_strdup(mem_ctx, "Success"); +done: + talloc_free(c); + return status; +} + + +NTSTATUS libnet_LookupName(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_LookupName *io) +{ + struct composite_context *c; + + c = libnet_LookupName_send(ctx, mem_ctx, io, NULL); + return libnet_LookupName_recv(c, mem_ctx, io); +} diff --git a/source4/libnet/libnet_lookup.h b/source4/libnet/libnet_lookup.h new file mode 100644 index 0000000..189ae58 --- /dev/null +++ b/source4/libnet/libnet_lookup.h @@ -0,0 +1,69 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + 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 libnet_Lookup { + struct { + const char *hostname; + int type; + struct resolve_context *resolve_ctx; + } in; + struct { + const char **address; + } out; +}; + + +struct libnet_LookupDCs { + struct { + const char *domain_name; + int name_type; + } in; + struct { + int num_dcs; + struct nbt_dc_name *dcs; + } out; +}; + + +struct libnet_LookupName { + struct { + const char *name; + const char *domain_name; + } in; + struct { + struct dom_sid *sid; + int rid; + enum lsa_SidType sid_type; + const char *sidstr; + const char *error_string; + } out; +}; + + +/* + * Monitor messages sent from libnet_lookup.c functions + */ + +struct msg_net_lookup_dc { + const char *domain_name; + const char *hostname; + const char *address; +}; + diff --git a/source4/libnet/libnet_passwd.c b/source4/libnet/libnet_passwd.c new file mode 100644 index 0000000..d7e9400 --- /dev/null +++ b/source4/libnet/libnet_passwd.c @@ -0,0 +1,1112 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + + 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 "libcli/auth/libcli_auth.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "source4/librpc/rpc/dcerpc.h" +#include "auth/credentials/credentials.h" +#include "libcli/smb/smb_constants.h" +#include "librpc/rpc/dcerpc_samr.h" +#include "source3/rpc_client/init_samr.h" +#include "lib/param/loadparm.h" +#include "lib/param/param.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +static NTSTATUS libnet_ChangePassword_samr_aes(TALLOC_CTX *mem_ctx, + struct dcerpc_binding_handle *h, + struct lsa_String *server, + struct lsa_String *account, + const char *old_password, + const char *new_password, + const char **error_string) +{ +#ifdef HAVE_GNUTLS_PBKDF2 + struct samr_ChangePasswordUser4 r; + uint8_t old_nt_key_data[16] = {0}; + gnutls_datum_t old_nt_key = { + .data = old_nt_key_data, + .size = sizeof(old_nt_key_data), + }; + uint8_t cek_data[16] = {0}; + DATA_BLOB cek = { + .data = cek_data, + .length = sizeof(cek_data), + }; + struct samr_EncryptedPasswordAES pwd_buf = { + .cipher_len = 0 + }; + DATA_BLOB salt = { + .data = pwd_buf.salt, + .length = sizeof(pwd_buf.salt), + }; + gnutls_datum_t salt_datum = { + .data = pwd_buf.salt, + .size = sizeof(pwd_buf.salt), + }; + uint64_t pbkdf2_iterations = generate_random_u64_range(5000, 1000000); + NTSTATUS status; + int rc; + + E_md4hash(old_password, old_nt_key_data); + + generate_nonce_buffer(salt.data, salt.length); + + rc = gnutls_pbkdf2(GNUTLS_MAC_SHA512, + &old_nt_key, + &salt_datum, + pbkdf2_iterations, + cek.data, + cek.length); + BURN_DATA(old_nt_key_data); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + } + + status = init_samr_CryptPasswordAES(mem_ctx, + new_password, + &salt, + &cek, + &pwd_buf); + data_blob_clear(&cek); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + pwd_buf.PBKDF2Iterations = pbkdf2_iterations; + + r.in.server = server; + r.in.account = account; + r.in.password = &pwd_buf; + + status = dcerpc_samr_ChangePasswordUser4_r(h, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + status = r.out.result; + *error_string = talloc_asprintf(mem_ctx, + "samr_ChangePasswordUser4 for " + "'%s\\%s' failed: %s", + server->string, + account->string, + nt_errstr(status)); + goto done; + } + +done: + BURN_DATA(pwd_buf); + + return status; +#else /* HAVE_GNUTLS_PBKDF2 */ + return NT_STATUS_NOT_IMPLEMENTED; +#endif /* HAVE_GNUTLS_PBKDF2 */ +} + +static NTSTATUS libnet_ChangePassword_samr_rc4(TALLOC_CTX *mem_ctx, + struct dcerpc_binding_handle *h, + struct lsa_String *server, + struct lsa_String *account, + const char *old_password, + const char *new_password, + const char **error_string) +{ + struct samr_OemChangePasswordUser2 oe2; + struct samr_ChangePasswordUser2 pw2; + struct samr_ChangePasswordUser3 pw3; + struct samr_CryptPassword nt_pass, lm_pass; + uint8_t old_nt_hash[16], new_nt_hash[16]; + uint8_t old_lm_hash[16], new_lm_hash[16]; + struct samr_Password nt_verifier, lm_verifier; + struct lsa_AsciiString a_server, a_account; + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t nt_session_key = { + .data = old_nt_hash, + .size = sizeof(old_nt_hash), + }; + gnutls_datum_t lm_session_key = { + .data = old_lm_hash, + .size = sizeof(old_lm_hash), + }; + struct samr_DomInfo1 *dominfo = NULL; + struct userPwdChangeFailureInformation *reject = NULL; + NTSTATUS status; + int rc; + + E_md4hash(old_password, old_nt_hash); + E_md4hash(new_password, new_nt_hash); + + E_deshash(old_password, old_lm_hash); + E_deshash(new_password, new_lm_hash); + + /* prepare samr_ChangePasswordUser3 */ + encode_pw_buffer(lm_pass.data, new_password, STR_UNICODE); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &nt_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + lm_pass.data, + 516); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + + rc = E_old_pw_hash(new_lm_hash, old_lm_hash, lm_verifier.hash); + if (rc != 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + goto done; + } + + encode_pw_buffer(nt_pass.data, new_password, STR_UNICODE); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &nt_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + nt_pass.data, + 516); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + + rc = E_old_pw_hash(new_nt_hash, old_nt_hash, nt_verifier.hash); + if (rc != 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + goto done; + } + + pw3.in.server = server; + pw3.in.account = account; + pw3.in.nt_password = &nt_pass; + pw3.in.nt_verifier = &nt_verifier; + pw3.in.lm_change = 1; + pw3.in.lm_password = &lm_pass; + pw3.in.lm_verifier = &lm_verifier; + pw3.in.password3 = NULL; + pw3.out.dominfo = &dominfo; + pw3.out.reject = &reject; + + /* 2. try samr_ChangePasswordUser3 */ + status = dcerpc_samr_ChangePasswordUser3_r(h, mem_ctx, &pw3); + if (!NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(pw3.out.result)) { + status = pw3.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + *error_string = talloc_asprintf( + mem_ctx, + "samr_ChangePasswordUser3 failed: %s", + nt_errstr(status)); + *error_string = + talloc_asprintf(mem_ctx, + "samr_ChangePasswordUser3 for " + "'%s\\%s' failed: %s", + server->string, + account->string, + nt_errstr(status)); + } + goto done; + } + + /* prepare samr_ChangePasswordUser2 */ + encode_pw_buffer(lm_pass.data, new_password, STR_ASCII | STR_TERMINATE); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &lm_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + lm_pass.data, + 516); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + + rc = E_old_pw_hash(new_lm_hash, old_lm_hash, lm_verifier.hash); + if (rc != 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + goto done; + } + + encode_pw_buffer(nt_pass.data, new_password, STR_UNICODE); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &nt_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + rc = gnutls_cipher_encrypt(cipher_hnd, + nt_pass.data, + 516); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + + rc = E_old_pw_hash(new_nt_hash, old_nt_hash, nt_verifier.hash); + if (rc != 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + goto done; + } + + pw2.in.server = server; + pw2.in.account = account; + pw2.in.nt_password = &nt_pass; + pw2.in.nt_verifier = &nt_verifier; + pw2.in.lm_change = 1; + pw2.in.lm_password = &lm_pass; + pw2.in.lm_verifier = &lm_verifier; + + /* 3. try samr_ChangePasswordUser2 */ + status = dcerpc_samr_ChangePasswordUser2_r(h, mem_ctx, &pw2); + if (!NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(pw2.out.result)) { + status = pw2.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + *error_string = + talloc_asprintf(mem_ctx, + "samr_ChangePasswordUser2 for " + "'%s\\%s' failed: %s", + server->string, + account->string, + nt_errstr(status)); + } + goto done; + } + + + /* prepare samr_OemChangePasswordUser2 */ + a_server.string = server->string; + a_account.string = account->string; + + encode_pw_buffer(lm_pass.data, new_password, STR_ASCII); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &lm_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + lm_pass.data, + 516); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto done; + } + + rc = E_old_pw_hash(new_lm_hash, old_lm_hash, lm_verifier.hash); + if (rc != 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + goto done; + } + + oe2.in.server = &a_server; + oe2.in.account = &a_account; + oe2.in.password = &lm_pass; + oe2.in.hash = &lm_verifier; + + /* 4. try samr_OemChangePasswordUser2 */ + status = dcerpc_samr_OemChangePasswordUser2_r(h, mem_ctx, &oe2); + if (!NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(oe2.out.result)) { + status = oe2.out.result; + } + if (!NT_STATUS_IS_OK(oe2.out.result)) { + *error_string = + talloc_asprintf(mem_ctx, + "samr_OemChangePasswordUser2 " + "for '%s\\%s' failed: %s", + server->string, + account->string, + nt_errstr(status)); + } + goto done; + } + + status = NT_STATUS_OK; +done: + return status; +} + +/* + * do a password change using DCERPC/SAMR calls + * 1. connect to the SAMR pipe of users domain PDC (maybe a standalone server or workstation) + * 2. try samr_ChangePasswordUser3 + * 3. try samr_ChangePasswordUser2 + * 4. try samr_OemChangePasswordUser2 + */ +static NTSTATUS libnet_ChangePassword_samr(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_ChangePassword *r) +{ + NTSTATUS status; + struct libnet_RpcConnect c; + struct lsa_String server, account; + + ZERO_STRUCT(c); + + /* prepare connect to the SAMR pipe of the users domain PDC */ + c.level = LIBNET_RPC_CONNECT_PDC; + c.in.name = r->samr.in.domain_name; + c.in.dcerpc_iface = &ndr_table_samr; + c.in.dcerpc_flags = DCERPC_ANON_FALLBACK; + + /* 1. connect to the SAMR pipe of users domain PDC (maybe a standalone server or workstation) */ + status = libnet_RpcConnect(ctx, mem_ctx, &c); + if (!NT_STATUS_IS_OK(status)) { + r->samr.out.error_string = talloc_asprintf(mem_ctx, + "Connection to SAMR pipe of PDC of domain '%s' failed: %s", + r->samr.in.domain_name, nt_errstr(status)); + return status; + } + + /* prepare password change for account */ + server.string = talloc_asprintf(mem_ctx, "\\\\%s", dcerpc_server_name(c.out.dcerpc_pipe)); + account.string = r->samr.in.account_name; + + status = libnet_ChangePassword_samr_aes( + mem_ctx, + c.out.dcerpc_pipe->binding_handle, + &server, + &account, + r->samr.in.oldpassword, + r->samr.in.newpassword, + &(r->samr.out.error_string)); + if (NT_STATUS_IS_OK(status)) { + goto disconnect; + } else if (NT_STATUS_EQUAL(status, + NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE) || + NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) || + NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) { + /* + * Don't fallback to RC4 based SAMR if weak crypto is not + * allowed. + */ + if (lpcfg_weak_crypto(ctx->lp_ctx) == + SAMBA_WEAK_CRYPTO_DISALLOWED) { + goto disconnect; + } + } else { + /* libnet_ChangePassword_samr_aes is implemented and failed */ + goto disconnect; + } + + status = libnet_ChangePassword_samr_rc4( + mem_ctx, + c.out.dcerpc_pipe->binding_handle, + &server, + &account, + r->samr.in.oldpassword, + r->samr.in.newpassword, + &(r->samr.out.error_string)); + if (!NT_STATUS_IS_OK(status)) { + goto disconnect; + } + +disconnect: + /* close connection */ + talloc_unlink(ctx, c.out.dcerpc_pipe); + + return status; +} + +static NTSTATUS libnet_ChangePassword_generic(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_ChangePassword *r) +{ + NTSTATUS status; + union libnet_ChangePassword r2; + + r2.samr.level = LIBNET_CHANGE_PASSWORD_SAMR; + r2.samr.in.account_name = r->generic.in.account_name; + r2.samr.in.domain_name = r->generic.in.domain_name; + r2.samr.in.oldpassword = r->generic.in.oldpassword; + r2.samr.in.newpassword = r->generic.in.newpassword; + + status = libnet_ChangePassword(ctx, mem_ctx, &r2); + + r->generic.out.error_string = r2.samr.out.error_string; + + return status; +} + +NTSTATUS libnet_ChangePassword(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_ChangePassword *r) +{ + switch (r->generic.level) { + case LIBNET_CHANGE_PASSWORD_GENERIC: + return libnet_ChangePassword_generic(ctx, mem_ctx, r); + case LIBNET_CHANGE_PASSWORD_SAMR: + return libnet_ChangePassword_samr(ctx, mem_ctx, r); + case LIBNET_CHANGE_PASSWORD_KRB5: + return NT_STATUS_NOT_IMPLEMENTED; + case LIBNET_CHANGE_PASSWORD_LDAP: + return NT_STATUS_NOT_IMPLEMENTED; + case LIBNET_CHANGE_PASSWORD_RAP: + return NT_STATUS_NOT_IMPLEMENTED; + } + + return NT_STATUS_INVALID_LEVEL; +} + +static NTSTATUS libnet_SetPassword_samr_handle_26(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_SetPassword *r) +{ + NTSTATUS status; + struct samr_SetUserInfo2 sui; + union samr_UserInfo u_info; + DATA_BLOB session_key; + + if (r->samr_handle.in.info21) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + /* prepare samr_SetUserInfo2 level 26 */ + ZERO_STRUCT(u_info); + u_info.info26.password_expired = 0; + + status = dcerpc_fetch_session_key(r->samr_handle.in.dcerpc_pipe, &session_key); + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string = talloc_asprintf(mem_ctx, + "dcerpc_fetch_session_key failed: %s", + nt_errstr(status)); + return status; + } + + status = encode_rc4_passwd_buffer(r->samr_handle.in.newpassword, + &session_key, + &u_info.info26.password); + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string = + talloc_asprintf(mem_ctx, + "encode_rc4_passwd_buffer failed: %s", + nt_errstr(status)); + return status; + } + + sui.in.user_handle = r->samr_handle.in.user_handle; + sui.in.info = &u_info; + sui.in.level = 26; + + /* 7. try samr_SetUserInfo2 level 26 to set the password */ + status = dcerpc_samr_SetUserInfo2_r(r->samr_handle.in.dcerpc_pipe->binding_handle, mem_ctx, &sui); + /* check result of samr_SetUserInfo2 level 26 */ + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(sui.out.result)) { + status = sui.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string + = talloc_asprintf(mem_ctx, + "SetUserInfo2 level 26 for [%s] failed: %s", + r->samr_handle.in.account_name, nt_errstr(status)); + } + + return status; +} + +static NTSTATUS libnet_SetPassword_samr_handle_25(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_SetPassword *r) +{ + NTSTATUS status; + struct samr_SetUserInfo2 sui; + union samr_UserInfo u_info; + DATA_BLOB session_key; + + if (!r->samr_handle.in.info21) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + /* prepare samr_SetUserInfo2 level 25 */ + ZERO_STRUCT(u_info); + u_info.info25.info = *r->samr_handle.in.info21; + u_info.info25.info.fields_present |= SAMR_FIELD_NT_PASSWORD_PRESENT; + + status = dcerpc_fetch_session_key(r->samr_handle.in.dcerpc_pipe, &session_key); + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string = talloc_asprintf(mem_ctx, + "dcerpc_fetch_session_key failed: %s", + nt_errstr(status)); + return status; + } + + status = encode_rc4_passwd_buffer(r->samr_handle.in.newpassword, + &session_key, + &u_info.info25.password); + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string = + talloc_asprintf(mem_ctx, + "encode_rc4_passwd_buffer failed: %s", + nt_errstr(status)); + return status; + } + + + sui.in.user_handle = r->samr_handle.in.user_handle; + sui.in.info = &u_info; + sui.in.level = 25; + + /* 8. try samr_SetUserInfo2 level 25 to set the password */ + status = dcerpc_samr_SetUserInfo2_r(r->samr_handle.in.dcerpc_pipe->binding_handle, mem_ctx, &sui); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(sui.out.result)) { + status = sui.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string + = talloc_asprintf(mem_ctx, + "SetUserInfo2 level 25 for [%s] failed: %s", + r->samr_handle.in.account_name, nt_errstr(status)); + } + + return status; +} + +static NTSTATUS libnet_SetPassword_samr_handle_24(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_SetPassword *r) +{ + NTSTATUS status; + struct samr_SetUserInfo2 sui; + union samr_UserInfo u_info; + DATA_BLOB session_key; + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t enc_session_key; + int rc; + + if (r->samr_handle.in.info21) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + /* prepare samr_SetUserInfo2 level 24 */ + ZERO_STRUCT(u_info); + encode_pw_buffer(u_info.info24.password.data, r->samr_handle.in.newpassword, STR_UNICODE); + u_info.info24.password_expired = 0; + + status = dcerpc_fetch_session_key(r->samr_handle.in.dcerpc_pipe, &session_key); + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string = talloc_asprintf(mem_ctx, + "dcerpc_fetch_session_key failed: %s", + nt_errstr(status)); + return status; + } + + enc_session_key = (gnutls_datum_t) { + .data = session_key.data, + .size = session_key.length, + }; + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &enc_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + u_info.info24.password.data, + 516); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + sui.in.user_handle = r->samr_handle.in.user_handle; + sui.in.info = &u_info; + sui.in.level = 24; + + /* 9. try samr_SetUserInfo2 level 24 to set the password */ + status = dcerpc_samr_SetUserInfo2_r(r->samr_handle.in.dcerpc_pipe->binding_handle, mem_ctx, &sui); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(sui.out.result)) { + status = sui.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string + = talloc_asprintf(mem_ctx, + "SetUserInfo2 level 24 for [%s] failed: %s", + r->samr_handle.in.account_name, nt_errstr(status)); + } + +out: + data_blob_clear(&session_key); + return status; +} + +static NTSTATUS libnet_SetPassword_samr_handle_23(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_SetPassword *r) +{ + NTSTATUS status; + struct samr_SetUserInfo2 sui; + union samr_UserInfo u_info; + DATA_BLOB session_key; + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t _session_key; + int rc; + + if (!r->samr_handle.in.info21) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + /* prepare samr_SetUserInfo2 level 23 */ + ZERO_STRUCT(u_info); + u_info.info23.info = *r->samr_handle.in.info21; + u_info.info23.info.fields_present |= SAMR_FIELD_NT_PASSWORD_PRESENT; + encode_pw_buffer(u_info.info23.password.data, r->samr_handle.in.newpassword, STR_UNICODE); + + status = dcerpc_fetch_session_key(r->samr_handle.in.dcerpc_pipe, &session_key); + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string + = talloc_asprintf(mem_ctx, + "dcerpc_fetch_session_key failed: %s", + nt_errstr(status)); + return status; + } + + _session_key = (gnutls_datum_t) { + .data = session_key.data, + .size = session_key.length, + }; + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + u_info.info23.password.data, + 516); + data_blob_clear_free(&session_key); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + sui.in.user_handle = r->samr_handle.in.user_handle; + sui.in.info = &u_info; + sui.in.level = 23; + + /* 10. try samr_SetUserInfo2 level 23 to set the password */ + status = dcerpc_samr_SetUserInfo2_r(r->samr_handle.in.dcerpc_pipe->binding_handle, mem_ctx, &sui); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(sui.out.result)) { + status = sui.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string + = talloc_asprintf(mem_ctx, + "SetUserInfo2 level 23 for [%s] failed: %s", + r->samr_handle.in.account_name, nt_errstr(status)); + } + +out: + return status; +} + +static NTSTATUS libnet_SetPassword_samr_handle_18(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_SetPassword *r) +{ + NTSTATUS status; + struct samr_SetUserInfo2 sui; + union samr_UserInfo u_info; + struct samr_Password ntpwd; + DATA_BLOB ntpwd_in; + DATA_BLOB ntpwd_out; + DATA_BLOB session_key; + int rc; + + if (r->samr_handle.in.info21) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + /* prepare samr_SetUserInfo2 level 18 (nt_hash) */ + ZERO_STRUCT(u_info); + E_md4hash(r->samr_handle.in.newpassword, ntpwd.hash); + ntpwd_in = data_blob_const(ntpwd.hash, sizeof(ntpwd.hash)); + ntpwd_out = data_blob_const(u_info.info18.nt_pwd.hash, + sizeof(u_info.info18.nt_pwd.hash)); + u_info.info18.nt_pwd_active = 1; + u_info.info18.password_expired = 0; + + status = dcerpc_fetch_session_key(r->samr_handle.in.dcerpc_pipe, &session_key); + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string = talloc_asprintf(mem_ctx, + "dcerpc_fetch_session_key failed: %s", + nt_errstr(status)); + return status; + } + + rc = sess_crypt_blob(&ntpwd_out, &ntpwd_in, + &session_key, SAMBA_GNUTLS_ENCRYPT); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + sui.in.user_handle = r->samr_handle.in.user_handle; + sui.in.info = &u_info; + sui.in.level = 18; + + /* 9. try samr_SetUserInfo2 level 18 to set the password */ + status = dcerpc_samr_SetUserInfo2_r(r->samr_handle.in.dcerpc_pipe->binding_handle, mem_ctx, &sui); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(sui.out.result)) { + status = sui.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr_handle.out.error_string + = talloc_asprintf(mem_ctx, + "SetUserInfo2 level 18 for [%s] failed: %s", + r->samr_handle.in.account_name, nt_errstr(status)); + } + +out: + data_blob_clear(&session_key); + return status; +} + +/* + * 1. try samr_SetUserInfo2 level 26 to set the password + * 2. try samr_SetUserInfo2 level 25 to set the password + * 3. try samr_SetUserInfo2 level 24 to set the password + * 4. try samr_SetUserInfo2 level 23 to set the password +*/ +static NTSTATUS libnet_SetPassword_samr_handle(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_SetPassword *r) +{ + + NTSTATUS status; + enum libnet_SetPassword_level levels[] = { + LIBNET_SET_PASSWORD_SAMR_HANDLE_26, + LIBNET_SET_PASSWORD_SAMR_HANDLE_25, + LIBNET_SET_PASSWORD_SAMR_HANDLE_24, + LIBNET_SET_PASSWORD_SAMR_HANDLE_23, + }; + unsigned int i; + + if (r->samr_handle.samr_level != 0) { + r->generic.level = r->samr_handle.samr_level; + return libnet_SetPassword(ctx, mem_ctx, r); + } + + for (i=0; i < ARRAY_SIZE(levels); i++) { + r->generic.level = levels[i]; + status = libnet_SetPassword(ctx, mem_ctx, r); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_INFO_CLASS) + || NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER_MIX) + || NT_STATUS_EQUAL(status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) { + /* Try another password set mechanism */ + continue; + } + break; + } + + return status; +} +/* + * set a password with DCERPC/SAMR calls + * 1. connect to the SAMR pipe of users domain PDC (maybe a standalone server or workstation) + * is it correct to contact the the pdc of the domain of the user who's password should be set? + * 2. do a samr_Connect to get a policy handle + * 3. do a samr_LookupDomain to get the domain sid + * 4. do a samr_OpenDomain to get a domain handle + * 5. do a samr_LookupNames to get the users rid + * 6. do a samr_OpenUser to get a user handle + * 7 call libnet_SetPassword_samr_handle to set the password + */ +static NTSTATUS libnet_SetPassword_samr(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_SetPassword *r) +{ + NTSTATUS status; + struct libnet_RpcConnect c; + struct samr_Connect sc; + struct policy_handle p_handle; + struct samr_LookupDomain ld; + struct dom_sid2 *sid = NULL; + struct lsa_String d_name; + struct samr_OpenDomain od; + struct policy_handle d_handle; + struct samr_LookupNames ln; + struct samr_Ids rids, types; + struct samr_OpenUser ou; + struct policy_handle u_handle; + union libnet_SetPassword r2; + + ZERO_STRUCT(c); + /* prepare connect to the SAMR pipe of users domain PDC */ + c.level = LIBNET_RPC_CONNECT_PDC; + c.in.name = r->samr.in.domain_name; + c.in.dcerpc_iface = &ndr_table_samr; + + /* 1. connect to the SAMR pipe of users domain PDC (maybe a standalone server or workstation) */ + status = libnet_RpcConnect(ctx, mem_ctx, &c); + if (!NT_STATUS_IS_OK(status)) { + r->samr.out.error_string = talloc_asprintf(mem_ctx, + "Connection to SAMR pipe of PDC of domain '%s' failed: %s", + r->samr.in.domain_name, nt_errstr(status)); + return status; + } + + /* prepare samr_Connect */ + ZERO_STRUCT(p_handle); + sc.in.system_name = NULL; + sc.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + sc.out.connect_handle = &p_handle; + + /* 2. do a samr_Connect to get a policy handle */ + status = dcerpc_samr_Connect_r(c.out.dcerpc_pipe->binding_handle, mem_ctx, &sc); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(sc.out.result)) { + status = sc.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr.out.error_string = talloc_asprintf(mem_ctx, + "samr_Connect failed: %s", + nt_errstr(status)); + goto disconnect; + } + + /* prepare samr_LookupDomain */ + d_name.string = r->samr.in.domain_name; + ld.in.connect_handle = &p_handle; + ld.in.domain_name = &d_name; + ld.out.sid = &sid; + + /* 3. do a samr_LookupDomain to get the domain sid */ + status = dcerpc_samr_LookupDomain_r(c.out.dcerpc_pipe->binding_handle, mem_ctx, &ld); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(ld.out.result)) { + status = ld.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr.out.error_string = talloc_asprintf(mem_ctx, + "samr_LookupDomain for [%s] failed: %s", + r->samr.in.domain_name, nt_errstr(status)); + goto disconnect; + } + + /* prepare samr_OpenDomain */ + ZERO_STRUCT(d_handle); + od.in.connect_handle = &p_handle; + od.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + od.in.sid = *ld.out.sid; + od.out.domain_handle = &d_handle; + + /* 4. do a samr_OpenDomain to get a domain handle */ + status = dcerpc_samr_OpenDomain_r(c.out.dcerpc_pipe->binding_handle, mem_ctx, &od); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(od.out.result)) { + status = od.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr.out.error_string = talloc_asprintf(mem_ctx, + "samr_OpenDomain for [%s] failed: %s", + r->samr.in.domain_name, nt_errstr(status)); + goto disconnect; + } + + /* prepare samr_LookupNames */ + ln.in.domain_handle = &d_handle; + ln.in.num_names = 1; + ln.in.names = talloc_array(mem_ctx, struct lsa_String, 1); + ln.out.rids = &rids; + ln.out.types = &types; + if (!ln.in.names) { + r->samr.out.error_string = "Out of Memory"; + return NT_STATUS_NO_MEMORY; + } + ln.in.names[0].string = r->samr.in.account_name; + + /* 5. do a samr_LookupNames to get the users rid */ + status = dcerpc_samr_LookupNames_r(c.out.dcerpc_pipe->binding_handle, mem_ctx, &ln); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(ln.out.result)) { + status = ln.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr.out.error_string = talloc_asprintf(mem_ctx, + "samr_LookupNames for [%s] failed: %s", + r->samr.in.account_name, nt_errstr(status)); + goto disconnect; + } + + /* check if we got one RID for the user */ + if (ln.out.rids->count != 1) { + r->samr.out.error_string = talloc_asprintf(mem_ctx, + "samr_LookupNames for [%s] returns %d RIDs", + r->samr.in.account_name, ln.out.rids->count); + status = NT_STATUS_INVALID_NETWORK_RESPONSE; + goto disconnect; + } + + if (ln.out.types->count != 1) { + r->samr.out.error_string = talloc_asprintf(mem_ctx, + "samr_LookupNames for [%s] returns %d RID TYPEs", + r->samr.in.account_name, ln.out.types->count); + status = NT_STATUS_INVALID_NETWORK_RESPONSE; + goto disconnect; + } + + /* prepare samr_OpenUser */ + ZERO_STRUCT(u_handle); + ou.in.domain_handle = &d_handle; + ou.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + ou.in.rid = ln.out.rids->ids[0]; + ou.out.user_handle = &u_handle; + + /* 6. do a samr_OpenUser to get a user handle */ + status = dcerpc_samr_OpenUser_r(c.out.dcerpc_pipe->binding_handle, mem_ctx, &ou); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(ou.out.result)) { + status = ou.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + r->samr.out.error_string = talloc_asprintf(mem_ctx, + "samr_OpenUser for [%s] failed: %s", + r->samr.in.account_name, nt_errstr(status)); + goto disconnect; + } + + ZERO_STRUCT(r2); + r2.samr_handle.level = LIBNET_SET_PASSWORD_SAMR_HANDLE; + r2.samr_handle.samr_level = r->samr.samr_level; + r2.samr_handle.in.account_name = r->samr.in.account_name; + r2.samr_handle.in.newpassword = r->samr.in.newpassword; + r2.samr_handle.in.user_handle = &u_handle; + r2.samr_handle.in.dcerpc_pipe = c.out.dcerpc_pipe; + r2.samr_handle.in.info21 = NULL; + + status = libnet_SetPassword(ctx, mem_ctx, &r2); + + r->generic.out.error_string = r2.samr_handle.out.error_string; + +disconnect: + /* close connection */ + talloc_unlink(ctx, c.out.dcerpc_pipe); + + return status; +} + +static NTSTATUS libnet_SetPassword_generic(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_SetPassword *r) +{ + NTSTATUS status; + union libnet_SetPassword r2; + + ZERO_STRUCT(r2); + r2.samr.level = LIBNET_SET_PASSWORD_SAMR; + r2.samr.samr_level = r->generic.samr_level; + r2.samr.in.account_name = r->generic.in.account_name; + r2.samr.in.domain_name = r->generic.in.domain_name; + r2.samr.in.newpassword = r->generic.in.newpassword; + + r->generic.out.error_string = "Unknown Error"; + status = libnet_SetPassword(ctx, mem_ctx, &r2); + + r->generic.out.error_string = r2.samr.out.error_string; + + return status; +} + +NTSTATUS libnet_SetPassword(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_SetPassword *r) +{ + enum smb_encryption_setting encryption_state = + cli_credentials_get_smb_encryption(ctx->cred); + NTSTATUS status = NT_STATUS_INVALID_LEVEL; + + switch (r->generic.level) { + case LIBNET_SET_PASSWORD_GENERIC: + status = libnet_SetPassword_generic(ctx, mem_ctx, r); + break; + case LIBNET_SET_PASSWORD_SAMR: + status = libnet_SetPassword_samr(ctx, mem_ctx, r); + break; + case LIBNET_SET_PASSWORD_SAMR_HANDLE: + status = libnet_SetPassword_samr_handle(ctx, mem_ctx, r); + break; + case LIBNET_SET_PASSWORD_SAMR_HANDLE_26: + if (encryption_state == SMB_ENCRYPTION_REQUIRED) { + GNUTLS_FIPS140_SET_LAX_MODE(); + } + status = libnet_SetPassword_samr_handle_26(ctx, mem_ctx, r); + break; + case LIBNET_SET_PASSWORD_SAMR_HANDLE_25: + if (encryption_state == SMB_ENCRYPTION_REQUIRED) { + GNUTLS_FIPS140_SET_LAX_MODE(); + } + status = libnet_SetPassword_samr_handle_25(ctx, mem_ctx, r); + break; + case LIBNET_SET_PASSWORD_SAMR_HANDLE_24: + if (encryption_state == SMB_ENCRYPTION_REQUIRED) { + GNUTLS_FIPS140_SET_LAX_MODE(); + } + status = libnet_SetPassword_samr_handle_24(ctx, mem_ctx, r); + break; + case LIBNET_SET_PASSWORD_SAMR_HANDLE_23: + if (encryption_state == SMB_ENCRYPTION_REQUIRED) { + GNUTLS_FIPS140_SET_LAX_MODE(); + } + status = libnet_SetPassword_samr_handle_23(ctx, mem_ctx, r); + break; + case LIBNET_SET_PASSWORD_SAMR_HANDLE_18: + if (encryption_state == SMB_ENCRYPTION_REQUIRED) { + GNUTLS_FIPS140_SET_LAX_MODE(); + } + status = libnet_SetPassword_samr_handle_18(ctx, mem_ctx, r); + break; + case LIBNET_SET_PASSWORD_KRB5: + status = NT_STATUS_NOT_IMPLEMENTED; + break; + case LIBNET_SET_PASSWORD_LDAP: + status = NT_STATUS_NOT_IMPLEMENTED; + break; + case LIBNET_SET_PASSWORD_RAP: + status = NT_STATUS_NOT_IMPLEMENTED; + break; + } + + GNUTLS_FIPS140_SET_STRICT_MODE(); + return status; +} diff --git a/source4/libnet/libnet_passwd.h b/source4/libnet/libnet_passwd.h new file mode 100644 index 0000000..17e6aab --- /dev/null +++ b/source4/libnet/libnet_passwd.h @@ -0,0 +1,144 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + + 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 and enum for doing a remote password change */ +enum libnet_ChangePassword_level { + LIBNET_CHANGE_PASSWORD_GENERIC, + LIBNET_CHANGE_PASSWORD_SAMR, + LIBNET_CHANGE_PASSWORD_KRB5, + LIBNET_CHANGE_PASSWORD_LDAP, + LIBNET_CHANGE_PASSWORD_RAP +}; + +union libnet_ChangePassword { + struct { + enum libnet_ChangePassword_level level; + + struct _libnet_ChangePassword_in { + const char *account_name; + const char *domain_name; + const char *oldpassword; + const char *newpassword; + } in; + + struct _libnet_ChangePassword_out { + const char *error_string; + } out; + } generic; + + struct { + enum libnet_ChangePassword_level level; + struct _libnet_ChangePassword_in in; + struct _libnet_ChangePassword_out out; + } samr; + + struct { + enum libnet_ChangePassword_level level; + struct _libnet_ChangePassword_in in; + struct _libnet_ChangePassword_out out; + } krb5; + + struct { + enum libnet_ChangePassword_level level; + struct _libnet_ChangePassword_in in; + struct _libnet_ChangePassword_out out; + } ldap; + + struct { + enum libnet_ChangePassword_level level; + struct _libnet_ChangePassword_in in; + struct _libnet_ChangePassword_out out; + } rap; +}; + +/* struct and enum for doing a remote password set */ +enum libnet_SetPassword_level { + LIBNET_SET_PASSWORD_GENERIC, + LIBNET_SET_PASSWORD_SAMR, + LIBNET_SET_PASSWORD_SAMR_HANDLE, + LIBNET_SET_PASSWORD_SAMR_HANDLE_26, + LIBNET_SET_PASSWORD_SAMR_HANDLE_25, + LIBNET_SET_PASSWORD_SAMR_HANDLE_24, + LIBNET_SET_PASSWORD_SAMR_HANDLE_23, + LIBNET_SET_PASSWORD_SAMR_HANDLE_18, + LIBNET_SET_PASSWORD_KRB5, + LIBNET_SET_PASSWORD_LDAP, + LIBNET_SET_PASSWORD_RAP +}; + +union libnet_SetPassword { + struct { + enum libnet_SetPassword_level level; + enum libnet_SetPassword_level samr_level; + + struct _libnet_SetPassword_in { + const char *account_name; + const char *domain_name; + const char *newpassword; + } in; + + struct _libnet_SetPassword_out { + const char *error_string; + } out; + } generic; + + struct { + enum libnet_SetPassword_level level; + enum libnet_SetPassword_level samr_level; + struct _libnet_SetPassword_samr_handle_in { + const char *account_name; /* for debug only */ + struct policy_handle *user_handle; + struct dcerpc_pipe *dcerpc_pipe; + const char *newpassword; + struct samr_UserInfo21 *info21; /* can be NULL, + * for level 26,24 it must be NULL + * for level 25,23 it must be non-NULL + */ + } in; + struct _libnet_SetPassword_out out; + } samr_handle; + + struct { + enum libnet_SetPassword_level level; + enum libnet_SetPassword_level samr_level; + struct _libnet_SetPassword_in in; + struct _libnet_SetPassword_out out; + } samr; + + struct { + enum libnet_SetPassword_level level; + enum libnet_SetPassword_level samr_level; + struct _libnet_SetPassword_in in; + struct _libnet_SetPassword_out out; + } krb5; + + struct { + enum libnet_SetPassword_level level; + enum libnet_SetPassword_level samr_level; + struct _libnet_SetPassword_in in; + struct _libnet_SetPassword_out out; + } ldap; + + struct { + enum libnet_ChangePassword_level level; + enum libnet_SetPassword_level samr_level; + struct _libnet_SetPassword_in in; + struct _libnet_SetPassword_out out; + } rap; +}; diff --git a/source4/libnet/libnet_rpc.c b/source4/libnet/libnet_rpc.c new file mode 100644 index 0000000..91c538f --- /dev/null +++ b/source4/libnet/libnet_rpc.c @@ -0,0 +1,1037 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Rafal Szczesniak 2005 + + 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 "libcli/libcli.h" +#include "libcli/composite/composite.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_samr.h" +#include "auth/credentials/credentials.h" + +struct rpc_connect_srv_state { + struct libnet_context *ctx; + struct libnet_RpcConnect r; + const char *binding; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_pipe_connect(struct composite_context *ctx); + + +/** + * Initiates connection to rpc pipe on remote server + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r data structure containing necessary parameters and return values + * @return composite context of this call + **/ + +static struct composite_context* libnet_RpcConnectSrv_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_RpcConnect *r, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct rpc_connect_srv_state *s; + struct dcerpc_binding *b; + struct composite_context *pipe_connect_req; + + /* composite context allocation and setup */ + c = composite_create(ctx, ctx->event_ctx); + if (c == NULL) return c; + + s = talloc_zero(c, struct rpc_connect_srv_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + s->monitor_fn = monitor; + + s->ctx = ctx; + s->r = *r; + ZERO_STRUCT(s->r.out); + + /* prepare binding string */ + switch (r->level) { + case LIBNET_RPC_CONNECT_SERVER: + s->binding = talloc_asprintf(s, "ncacn_np:%s", r->in.name); + break; + case LIBNET_RPC_CONNECT_SERVER_ADDRESS: + s->binding = talloc_asprintf(s, "ncacn_np:%s[target_hostname=%s]", + r->in.address, r->in.name); + break; + + case LIBNET_RPC_CONNECT_BINDING: + s->binding = talloc_strdup(s, r->in.binding); + break; + + case LIBNET_RPC_CONNECT_DC: + case LIBNET_RPC_CONNECT_PDC: + /* this should never happen - DC and PDC level has a separate + composite function */ + case LIBNET_RPC_CONNECT_DC_INFO: + /* this should never happen - DC_INFO level has a separate + composite function */ + composite_error(c, NT_STATUS_INVALID_LEVEL); + return c; + } + + /* parse binding string to the structure */ + c->status = dcerpc_parse_binding(c, s->binding, &b); + if (!NT_STATUS_IS_OK(c->status)) { + DEBUG(0, ("Failed to parse dcerpc binding '%s'\n", s->binding)); + composite_error(c, c->status); + return c; + } + + switch (r->level) { + case LIBNET_RPC_CONNECT_SERVER: + case LIBNET_RPC_CONNECT_SERVER_ADDRESS: + c->status = dcerpc_binding_set_flags(b, r->in.dcerpc_flags, 0); + if (!composite_is_ok(c)) return c; + break; + default: + /* other types have already been checked before */ + break; + } + + if (DEBUGLEVEL >= 10) { + c->status = dcerpc_binding_set_flags(b, DCERPC_DEBUG_PRINT_BOTH, 0); + if (!composite_is_ok(c)) return c; + } + + /* connect to remote dcerpc pipe */ + pipe_connect_req = dcerpc_pipe_connect_b_send(c, b, r->in.dcerpc_iface, + ctx->cred, c->event_ctx, + ctx->lp_ctx); + if (composite_nomem(pipe_connect_req, c)) return c; + + composite_continue(c, pipe_connect_req, continue_pipe_connect, c); + return c; +} + + +/* + Step 2 of RpcConnectSrv - get rpc connection +*/ +static void continue_pipe_connect(struct composite_context *ctx) +{ + struct composite_context *c; + struct rpc_connect_srv_state *s; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct rpc_connect_srv_state); + + /* receive result of rpc pipe connection */ + c->status = dcerpc_pipe_connect_b_recv(ctx, c, &s->r.out.dcerpc_pipe); + + /* post monitor message */ + if (s->monitor_fn) { + struct monitor_msg msg; + struct msg_net_rpc_connect data; + const struct dcerpc_binding *b = s->r.out.dcerpc_pipe->binding; + + /* prepare monitor message and post it */ + data.host = dcerpc_binding_get_string_option(b, "host"); + data.endpoint = dcerpc_binding_get_string_option(b, "endpoint"); + data.transport = dcerpc_binding_get_transport(b); + data.domain_name = dcerpc_binding_get_string_option(b, "target_hostname"); + + msg.type = mon_NetRpcConnect; + msg.data = (void*)&data; + msg.data_size = sizeof(data); + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +/** + * Receives result of connection to rpc pipe on remote server + * + * @param c composite context + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r data structure containing necessary parameters and return values + * @return nt status of rpc connection + **/ + +static NTSTATUS libnet_RpcConnectSrv_recv(struct composite_context *c, + struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_RpcConnect *r) +{ + NTSTATUS status; + + status = composite_wait(c); + if (NT_STATUS_IS_OK(status)) { + struct rpc_connect_srv_state *s; + + /* move the returned rpc pipe between memory contexts */ + s = talloc_get_type(c->private_data, struct rpc_connect_srv_state); + r->out.dcerpc_pipe = talloc_steal(mem_ctx, s->r.out.dcerpc_pipe); + + /* reference created pipe structure to long-term libnet_context + so that it can be used by other api functions even after short-term + mem_ctx is freed */ + if (r->in.dcerpc_iface == &ndr_table_samr) { + ctx->samr.pipe = talloc_reference(ctx, r->out.dcerpc_pipe); + ctx->samr.samr_handle = ctx->samr.pipe->binding_handle; + + } else if (r->in.dcerpc_iface == &ndr_table_lsarpc) { + ctx->lsa.pipe = talloc_reference(ctx, r->out.dcerpc_pipe); + ctx->lsa.lsa_handle = ctx->lsa.pipe->binding_handle; + } + + r->out.error_string = talloc_strdup(mem_ctx, "Success"); + + } else { + r->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +struct rpc_connect_dc_state { + struct libnet_context *ctx; + struct libnet_RpcConnect r; + struct libnet_RpcConnect r2; + struct libnet_LookupDCs f; + const char *connect_name; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_lookup_dc(struct tevent_req *req); +static void continue_rpc_connect(struct composite_context *ctx); + + +/** + * Initiates connection to rpc pipe on domain pdc + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r data structure containing necessary parameters and return values + * @return composite context of this call + **/ + +static struct composite_context* libnet_RpcConnectDC_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_RpcConnect *r, + void (*monitor)(struct monitor_msg *msg)) +{ + struct composite_context *c; + struct rpc_connect_dc_state *s; + struct tevent_req *lookup_dc_req; + + /* composite context allocation and setup */ + c = composite_create(ctx, ctx->event_ctx); + if (c == NULL) return c; + + s = talloc_zero(c, struct rpc_connect_dc_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + s->monitor_fn = monitor; + + s->ctx = ctx; + s->r = *r; + ZERO_STRUCT(s->r.out); + + switch (r->level) { + case LIBNET_RPC_CONNECT_PDC: + s->f.in.name_type = NBT_NAME_PDC; + break; + + case LIBNET_RPC_CONNECT_DC: + s->f.in.name_type = NBT_NAME_LOGON; + break; + + default: + break; + } + + s->f.in.domain_name = r->in.name; + s->f.out.num_dcs = 0; + s->f.out.dcs = NULL; + + /* find the domain pdc first */ + lookup_dc_req = libnet_LookupDCs_send(ctx, c, &s->f); + if (composite_nomem(lookup_dc_req, c)) return c; + + tevent_req_set_callback(lookup_dc_req, continue_lookup_dc, c); + return c; +} + + +/* + Step 2 of RpcConnectDC: get domain controller name and + initiate RpcConnect to it +*/ +static void continue_lookup_dc(struct tevent_req *req) +{ + struct composite_context *c; + struct rpc_connect_dc_state *s; + struct composite_context *rpc_connect_req; + struct monitor_msg msg; + struct msg_net_lookup_dc data; + + c = tevent_req_callback_data(req, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct rpc_connect_dc_state); + + /* receive result of domain controller lookup */ + c->status = libnet_LookupDCs_recv(req, c, &s->f); + if (!composite_is_ok(c)) return; + + /* decide on preferred address type depending on DC type */ + s->connect_name = s->f.out.dcs[0].name; + + /* post monitor message */ + if (s->monitor_fn) { + /* prepare a monitor message and post it */ + data.domain_name = s->f.in.domain_name; + data.hostname = s->f.out.dcs[0].name; + data.address = s->f.out.dcs[0].address; + + msg.type = mon_NetLookupDc; + msg.data = &data; + msg.data_size = sizeof(data); + s->monitor_fn(&msg); + } + + /* ok, pdc has been found so do attempt to rpc connect */ + s->r2.level = LIBNET_RPC_CONNECT_SERVER_ADDRESS; + + /* this will cause yet another name resolution, but at least + * we pass the right name down the stack now */ + s->r2.in.name = talloc_strdup(s, s->connect_name); + s->r2.in.address = talloc_steal(s, s->f.out.dcs[0].address); + s->r2.in.dcerpc_iface = s->r.in.dcerpc_iface; + s->r2.in.dcerpc_flags = s->r.in.dcerpc_flags; + + /* send rpc connect request to the server */ + rpc_connect_req = libnet_RpcConnectSrv_send(s->ctx, c, &s->r2, s->monitor_fn); + if (composite_nomem(rpc_connect_req, c)) return; + + composite_continue(c, rpc_connect_req, continue_rpc_connect, c); +} + + +/* + Step 3 of RpcConnectDC: get rpc connection to the server +*/ +static void continue_rpc_connect(struct composite_context *ctx) +{ + struct composite_context *c; + struct rpc_connect_dc_state *s; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct rpc_connect_dc_state); + + c->status = libnet_RpcConnectSrv_recv(ctx, s->ctx, c, &s->r2); + + /* error string is to be passed anyway */ + s->r.out.error_string = s->r2.out.error_string; + if (!composite_is_ok(c)) return; + + s->r.out.dcerpc_pipe = s->r2.out.dcerpc_pipe; + + /* post monitor message */ + if (s->monitor_fn) { + struct monitor_msg msg; + struct msg_net_rpc_connect data; + const struct dcerpc_binding *b = s->r.out.dcerpc_pipe->binding; + + data.host = dcerpc_binding_get_string_option(b, "host"); + data.endpoint = dcerpc_binding_get_string_option(b, "endpoint"); + data.transport = dcerpc_binding_get_transport(b); + data.domain_name = dcerpc_binding_get_string_option(b, "target_hostname"); + + msg.type = mon_NetRpcConnect; + msg.data = (void*)&data; + msg.data_size = sizeof(data); + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +/** + * Receives result of connection to rpc pipe on domain pdc + * + * @param c composite context + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r data structure containing necessary parameters and return values + * @return nt status of rpc connection + **/ + +static NTSTATUS libnet_RpcConnectDC_recv(struct composite_context *c, + struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_RpcConnect *r) +{ + NTSTATUS status; + struct rpc_connect_dc_state *s = talloc_get_type(c->private_data, + struct rpc_connect_dc_state); + + status = composite_wait(c); + if (NT_STATUS_IS_OK(status)) { + /* move connected rpc pipe between memory contexts + + The use of talloc_reparent(talloc_parent(), ...) is + bizarre, but it is needed because of the absolutely + atrocious use of talloc in this code. We need to + force the original parent to change, but finding + the original parent is well nigh impossible at this + point in the code (yes, I tried). + */ + r->out.dcerpc_pipe = talloc_reparent(talloc_parent(s->r.out.dcerpc_pipe), + mem_ctx, s->r.out.dcerpc_pipe); + + /* reference created pipe structure to long-term libnet_context + so that it can be used by other api functions even after short-term + mem_ctx is freed */ + if (r->in.dcerpc_iface == &ndr_table_samr) { + ctx->samr.pipe = talloc_reference(ctx, r->out.dcerpc_pipe); + ctx->samr.samr_handle = ctx->samr.pipe->binding_handle; + } else if (r->in.dcerpc_iface == &ndr_table_lsarpc) { + ctx->lsa.pipe = talloc_reference(ctx, r->out.dcerpc_pipe); + ctx->lsa.lsa_handle = ctx->lsa.pipe->binding_handle; + } + + } else { + r->out.error_string = talloc_asprintf(mem_ctx, + "Failed to rpc connect: %s", + nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + + +struct rpc_connect_dci_state { + struct libnet_context *ctx; + struct libnet_RpcConnect r; + struct libnet_RpcConnect rpc_conn; + struct policy_handle lsa_handle; + struct lsa_QosInfo qos; + struct lsa_ObjectAttribute attr; + struct lsa_OpenPolicy2 lsa_open_policy; + struct dcerpc_pipe *lsa_pipe; + struct lsa_QueryInfoPolicy2 lsa_query_info2; + struct lsa_QueryInfoPolicy lsa_query_info; + struct dcerpc_binding *final_binding; + struct dcerpc_pipe *final_pipe; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_dci_rpc_connect(struct composite_context *ctx); +static void continue_lsa_policy(struct tevent_req *subreq); +static void continue_lsa_query_info(struct tevent_req *subreq); +static void continue_lsa_query_info2(struct tevent_req *subreq); +static void continue_epm_map_binding(struct composite_context *ctx); +static void continue_secondary_conn(struct composite_context *ctx); +static void continue_epm_map_binding_send(struct composite_context *c); + + +/** + * Initiates connection to rpc pipe on remote server or pdc. Received result + * contains info on the domain name, domain sid and realm. + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r data structure containing necessary parameters and return values. Must be a talloc context + * @return composite context of this call + **/ + +static struct composite_context* libnet_RpcConnectDCInfo_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_RpcConnect *r, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c, *conn_req; + struct rpc_connect_dci_state *s; + + /* composite context allocation and setup */ + c = composite_create(ctx, ctx->event_ctx); + if (c == NULL) return c; + + s = talloc_zero(c, struct rpc_connect_dci_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + s->monitor_fn = monitor; + + s->ctx = ctx; + s->r = *r; + ZERO_STRUCT(s->r.out); + + + /* proceed to pure rpc connection if the binding string is provided, + otherwise try to connect domain controller */ + if (r->in.binding == NULL) { + /* Pass on any binding flags (such as anonymous fallback) that have been set */ + s->rpc_conn.in.dcerpc_flags = r->in.dcerpc_flags; + + s->rpc_conn.in.name = r->in.name; + s->rpc_conn.level = LIBNET_RPC_CONNECT_DC; + } else { + s->rpc_conn.in.binding = r->in.binding; + s->rpc_conn.level = LIBNET_RPC_CONNECT_BINDING; + } + + /* we need to query information on lsarpc interface first */ + s->rpc_conn.in.dcerpc_iface = &ndr_table_lsarpc; + + /* request connection to the lsa pipe on the pdc */ + conn_req = libnet_RpcConnect_send(ctx, c, &s->rpc_conn, s->monitor_fn); + if (composite_nomem(c, conn_req)) return c; + + composite_continue(c, conn_req, continue_dci_rpc_connect, c); + return c; +} + + +/* + Step 2 of RpcConnectDCInfo: receive opened rpc pipe and open + lsa policy handle +*/ +static void continue_dci_rpc_connect(struct composite_context *ctx) +{ + struct composite_context *c; + struct rpc_connect_dci_state *s; + struct tevent_req *subreq; + enum dcerpc_transport_t transport; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct rpc_connect_dci_state); + + c->status = libnet_RpcConnect_recv(ctx, s->ctx, c, &s->rpc_conn); + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + /* post monitor message */ + if (s->monitor_fn) { + struct monitor_msg msg; + struct msg_net_rpc_connect data; + const struct dcerpc_binding *b = s->r.out.dcerpc_pipe->binding; + + data.host = dcerpc_binding_get_string_option(b, "host"); + data.endpoint = dcerpc_binding_get_string_option(b, "endpoint"); + data.transport = dcerpc_binding_get_transport(b); + data.domain_name = dcerpc_binding_get_string_option(b, "target_hostname"); + + msg.type = mon_NetRpcConnect; + msg.data = (void*)&data; + msg.data_size = sizeof(data); + s->monitor_fn(&msg); + } + + /* prepare to open a policy handle on lsa pipe */ + s->lsa_pipe = s->ctx->lsa.pipe; + + s->qos.len = 0; + s->qos.impersonation_level = 2; + s->qos.context_mode = 1; + s->qos.effective_only = 0; + + s->attr.sec_qos = &s->qos; + + transport = dcerpc_binding_get_transport(s->lsa_pipe->binding); + if (transport == NCACN_IP_TCP) { + /* + * Skip to creating the actual connection. We can't open a + * policy handle over tcpip. + */ + continue_epm_map_binding_send(c); + return; + } + + s->lsa_open_policy.in.attr = &s->attr; + s->lsa_open_policy.in.system_name = talloc_asprintf(c, "\\"); + if (composite_nomem(s->lsa_open_policy.in.system_name, c)) return; + + s->lsa_open_policy.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + s->lsa_open_policy.out.handle = &s->lsa_handle; + + subreq = dcerpc_lsa_OpenPolicy2_r_send(s, c->event_ctx, + s->lsa_pipe->binding_handle, + &s->lsa_open_policy); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_lsa_policy, c); +} + + +/* + Step 3 of RpcConnectDCInfo: Get policy handle and query lsa info + for kerberos realm (dns name) and guid. The query may fail. +*/ +static void continue_lsa_policy(struct tevent_req *subreq) +{ + struct composite_context *c; + struct rpc_connect_dci_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct rpc_connect_dci_state); + + c->status = dcerpc_lsa_OpenPolicy2_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + if (NT_STATUS_EQUAL(s->lsa_open_policy.out.result, NT_STATUS_RPC_PROTSEQ_NOT_SUPPORTED)) { + s->r.out.realm = NULL; + s->r.out.guid = NULL; + s->r.out.domain_name = NULL; + s->r.out.domain_sid = NULL; + + /* Skip to the creating the actual connection, no info available on this transport */ + continue_epm_map_binding_send(c); + return; + + } else if (!NT_STATUS_IS_OK(s->lsa_open_policy.out.result)) { + composite_error(c, s->lsa_open_policy.out.result); + return; + } + + /* post monitor message */ + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_LsaOpenPolicy; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + /* query lsa info for dns domain name and guid */ + s->lsa_query_info2.in.handle = &s->lsa_handle; + s->lsa_query_info2.in.level = LSA_POLICY_INFO_DNS; + s->lsa_query_info2.out.info = talloc_zero(c, union lsa_PolicyInformation *); + if (composite_nomem(s->lsa_query_info2.out.info, c)) return; + + subreq = dcerpc_lsa_QueryInfoPolicy2_r_send(s, c->event_ctx, + s->lsa_pipe->binding_handle, + &s->lsa_query_info2); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_lsa_query_info2, c); +} + + +/* + Step 4 of RpcConnectDCInfo: Get realm and guid if provided (rpc call + may result in failure) and query lsa info for domain name and sid. +*/ +static void continue_lsa_query_info2(struct tevent_req *subreq) +{ + struct composite_context *c; + struct rpc_connect_dci_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct rpc_connect_dci_state); + + c->status = dcerpc_lsa_QueryInfoPolicy2_r_recv(subreq, s); + TALLOC_FREE(subreq); + + /* In case of error just null the realm and guid and proceed + to the next step. After all, it doesn't have to be AD domain + controller we talking to - NT-style PDC also counts */ + + if (NT_STATUS_EQUAL(c->status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + s->r.out.realm = NULL; + s->r.out.guid = NULL; + + } else { + if (!NT_STATUS_IS_OK(c->status)) { + s->r.out.error_string = talloc_asprintf(c, + "lsa_QueryInfoPolicy2 failed: %s", + nt_errstr(c->status)); + composite_error(c, c->status); + return; + } + + if (!NT_STATUS_IS_OK(s->lsa_query_info2.out.result)) { + s->r.out.error_string = talloc_asprintf(c, + "lsa_QueryInfoPolicy2 failed: %s", + nt_errstr(s->lsa_query_info2.out.result)); + composite_error(c, s->lsa_query_info2.out.result); + return; + } + + /* Copy the dns domain name and guid from the query result */ + + /* this should actually be a conversion from lsa_StringLarge */ + s->r.out.realm = (*s->lsa_query_info2.out.info)->dns.dns_domain.string; + s->r.out.guid = talloc(c, struct GUID); + if (composite_nomem(s->r.out.guid, c)) { + s->r.out.error_string = NULL; + return; + } + *s->r.out.guid = (*s->lsa_query_info2.out.info)->dns.domain_guid; + } + + /* post monitor message */ + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_LsaQueryPolicy; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + /* query lsa info for domain name and sid */ + s->lsa_query_info.in.handle = &s->lsa_handle; + s->lsa_query_info.in.level = LSA_POLICY_INFO_DOMAIN; + s->lsa_query_info.out.info = talloc_zero(c, union lsa_PolicyInformation *); + if (composite_nomem(s->lsa_query_info.out.info, c)) return; + + subreq = dcerpc_lsa_QueryInfoPolicy_r_send(s, c->event_ctx, + s->lsa_pipe->binding_handle, + &s->lsa_query_info); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_lsa_query_info, c); +} + + +/* + Step 5 of RpcConnectDCInfo: Get domain name and sid +*/ +static void continue_lsa_query_info(struct tevent_req *subreq) +{ + struct composite_context *c; + struct rpc_connect_dci_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct rpc_connect_dci_state); + + c->status = dcerpc_lsa_QueryInfoPolicy_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(c->status)) { + s->r.out.error_string = talloc_asprintf(c, + "lsa_QueryInfoPolicy failed: %s", + nt_errstr(c->status)); + composite_error(c, c->status); + return; + } + + /* post monitor message */ + if (s->monitor_fn) { + struct monitor_msg msg; + + msg.type = mon_LsaQueryPolicy; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + /* Copy the domain name and sid from the query result */ + s->r.out.domain_sid = (*s->lsa_query_info.out.info)->domain.sid; + s->r.out.domain_name = (*s->lsa_query_info.out.info)->domain.name.string; + + continue_epm_map_binding_send(c); +} + +/* + Step 5 (continued) of RpcConnectDCInfo: request endpoint + map binding. + + We may short-cut to this step if we don't support LSA OpenPolicy on this transport +*/ +static void continue_epm_map_binding_send(struct composite_context *c) +{ + struct rpc_connect_dci_state *s; + struct composite_context *epm_map_req; + struct cli_credentials *epm_creds = NULL; + + s = talloc_get_type(c->private_data, struct rpc_connect_dci_state); + + /* prepare to get endpoint mapping for the requested interface */ + s->final_binding = dcerpc_binding_dup(s, s->lsa_pipe->binding); + if (composite_nomem(s->final_binding, c)) return; + + /* + * We don't want to inherit the assoc_group_id from the + * lsa_pipe here! + */ + dcerpc_binding_set_assoc_group_id(s->final_binding, 0); + + epm_creds = cli_credentials_init_anon(s); + if (composite_nomem(epm_creds, c)) return; + + epm_map_req = dcerpc_epm_map_binding_send(c, s->final_binding, s->r.in.dcerpc_iface, + epm_creds, + s->ctx->event_ctx, s->ctx->lp_ctx); + if (composite_nomem(epm_map_req, c)) return; + + composite_continue(c, epm_map_req, continue_epm_map_binding, c); +} + +/* + Step 6 of RpcConnectDCInfo: Receive endpoint mapping and create secondary + rpc connection derived from already used pipe but connected to the requested + one (as specified in libnet_RpcConnect structure) +*/ +static void continue_epm_map_binding(struct composite_context *ctx) +{ + struct composite_context *c, *sec_conn_req; + struct rpc_connect_dci_state *s; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct rpc_connect_dci_state); + + c->status = dcerpc_epm_map_binding_recv(ctx); + if (!NT_STATUS_IS_OK(c->status)) { + s->r.out.error_string = talloc_asprintf(c, + "failed to map pipe with endpoint mapper - %s", + nt_errstr(c->status)); + composite_error(c, c->status); + return; + } + + /* create secondary connection derived from lsa pipe */ + sec_conn_req = dcerpc_secondary_auth_connection_send(s->lsa_pipe, + s->final_binding, + s->r.in.dcerpc_iface, + s->ctx->cred, + s->ctx->lp_ctx); + if (composite_nomem(sec_conn_req, c)) return; + + composite_continue(c, sec_conn_req, continue_secondary_conn, c); +} + + +/* + Step 7 of RpcConnectDCInfo: Get actual pipe to be returned + and complete this composite call +*/ +static void continue_secondary_conn(struct composite_context *ctx) +{ + struct composite_context *c; + struct rpc_connect_dci_state *s; + + c = talloc_get_type(ctx->async.private_data, struct composite_context); + s = talloc_get_type(c->private_data, struct rpc_connect_dci_state); + + c->status = dcerpc_secondary_auth_connection_recv(ctx, s->lsa_pipe, + &s->final_pipe); + if (!NT_STATUS_IS_OK(c->status)) { + s->r.out.error_string = talloc_asprintf(c, + "secondary connection failed: %s", + nt_errstr(c->status)); + + composite_error(c, c->status); + return; + } + + s->r.out.dcerpc_pipe = s->final_pipe; + + /* post monitor message */ + if (s->monitor_fn) { + struct monitor_msg msg; + struct msg_net_rpc_connect data; + const struct dcerpc_binding *b = s->r.out.dcerpc_pipe->binding; + + /* prepare monitor message and post it */ + data.host = dcerpc_binding_get_string_option(b, "host"); + data.endpoint = dcerpc_binding_get_string_option(b, "endpoint"); + data.transport = dcerpc_binding_get_transport(b); + data.domain_name = dcerpc_binding_get_string_option(b, "target_hostname"); + + msg.type = mon_NetRpcConnect; + msg.data = (void*)&data; + msg.data_size = sizeof(data); + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +/** + * Receives result of connection to rpc pipe and gets basic + * domain info (name, sid, realm, guid) + * + * @param c composite context + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r data structure containing return values + * @return nt status of rpc connection + **/ + +static NTSTATUS libnet_RpcConnectDCInfo_recv(struct composite_context *c, struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_RpcConnect *r) +{ + NTSTATUS status; + struct rpc_connect_dci_state *s = talloc_get_type(c->private_data, + struct rpc_connect_dci_state); + + status = composite_wait(c); + if (NT_STATUS_IS_OK(status)) { + r->out.realm = talloc_steal(mem_ctx, s->r.out.realm); + r->out.guid = talloc_steal(mem_ctx, s->r.out.guid); + r->out.domain_name = talloc_steal(mem_ctx, s->r.out.domain_name); + r->out.domain_sid = talloc_steal(mem_ctx, s->r.out.domain_sid); + + r->out.dcerpc_pipe = talloc_steal(mem_ctx, s->r.out.dcerpc_pipe); + + /* reference created pipe structure to long-term libnet_context + so that it can be used by other api functions even after short-term + mem_ctx is freed */ + if (r->in.dcerpc_iface == &ndr_table_samr) { + ctx->samr.pipe = talloc_reference(ctx, r->out.dcerpc_pipe); + ctx->samr.samr_handle = ctx->samr.pipe->binding_handle; + + } else if (r->in.dcerpc_iface == &ndr_table_lsarpc) { + ctx->lsa.pipe = talloc_reference(ctx, r->out.dcerpc_pipe); + ctx->lsa.lsa_handle = ctx->lsa.pipe->binding_handle; + } + + } else { + if (s->r.out.error_string) { + r->out.error_string = talloc_steal(mem_ctx, s->r.out.error_string); + } else if (r->in.binding == NULL) { + r->out.error_string = talloc_asprintf(mem_ctx, "Connection to DC failed: %s", nt_errstr(status)); + } else { + r->out.error_string = talloc_asprintf(mem_ctx, "Connection to DC %s failed: %s", + r->in.binding, nt_errstr(status)); + } + } + + talloc_free(c); + return status; +} + + +/** + * Initiates connection to rpc pipe on remote server or pdc, optionally + * providing domain info + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r data structure containing necessary parameters and return values + * @return composite context of this call + **/ + +struct composite_context* libnet_RpcConnect_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_RpcConnect *r, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + + switch (r->level) { + case LIBNET_RPC_CONNECT_SERVER: + case LIBNET_RPC_CONNECT_SERVER_ADDRESS: + case LIBNET_RPC_CONNECT_BINDING: + c = libnet_RpcConnectSrv_send(ctx, mem_ctx, r, monitor); + break; + + case LIBNET_RPC_CONNECT_PDC: + case LIBNET_RPC_CONNECT_DC: + c = libnet_RpcConnectDC_send(ctx, mem_ctx, r, monitor); + break; + + case LIBNET_RPC_CONNECT_DC_INFO: + c = libnet_RpcConnectDCInfo_send(ctx, mem_ctx, r, monitor); + break; + + default: + c = talloc_zero(mem_ctx, struct composite_context); + composite_error(c, NT_STATUS_INVALID_LEVEL); + } + + return c; +} + + +/** + * Receives result of connection to rpc pipe on remote server or pdc + * + * @param c composite context + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r data structure containing necessary parameters and return values + * @return nt status of rpc connection + **/ + +NTSTATUS libnet_RpcConnect_recv(struct composite_context *c, struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_RpcConnect *r) +{ + switch (r->level) { + case LIBNET_RPC_CONNECT_SERVER: + case LIBNET_RPC_CONNECT_BINDING: + return libnet_RpcConnectSrv_recv(c, ctx, mem_ctx, r); + + case LIBNET_RPC_CONNECT_PDC: + case LIBNET_RPC_CONNECT_DC: + return libnet_RpcConnectDC_recv(c, ctx, mem_ctx, r); + + case LIBNET_RPC_CONNECT_DC_INFO: + return libnet_RpcConnectDCInfo_recv(c, ctx, mem_ctx, r); + + default: + ZERO_STRUCT(r->out); + return NT_STATUS_INVALID_LEVEL; + } +} + + +/** + * Connect to a rpc pipe on a remote server - sync version + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r data structure containing necessary parameters and return values + * @return nt status of rpc connection + **/ + +NTSTATUS libnet_RpcConnect(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_RpcConnect *r) +{ + struct composite_context *c; + + c = libnet_RpcConnect_send(ctx, mem_ctx, r, NULL); + return libnet_RpcConnect_recv(c, ctx, mem_ctx, r); +} diff --git a/source4/libnet/libnet_rpc.h b/source4/libnet/libnet_rpc.h new file mode 100644 index 0000000..fb26284 --- /dev/null +++ b/source4/libnet/libnet_rpc.h @@ -0,0 +1,73 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "librpc/rpc/dcerpc.h" + +/* + * struct definition for connecting to a dcerpc inferface + */ + +enum libnet_RpcConnect_level { + LIBNET_RPC_CONNECT_SERVER, /* connect to a standalone rpc server */ + LIBNET_RPC_CONNECT_SERVER_ADDRESS, /* connect to a standalone rpc server, + knowing both name and address */ + LIBNET_RPC_CONNECT_PDC, /* connect to a domain pdc (resolves domain + name to a pdc address before connecting) */ + LIBNET_RPC_CONNECT_DC, /* connect to any DC (resolves domain + name to a DC address before connecting) */ + LIBNET_RPC_CONNECT_BINDING, /* specified binding string */ + LIBNET_RPC_CONNECT_DC_INFO /* connect to a DC and provide basic domain + information (name, realm, sid, guid) */ +}; + +struct libnet_RpcConnect { + enum libnet_RpcConnect_level level; + + struct { + const char *name; + const char *address; + const char *binding; + const struct ndr_interface_table *dcerpc_iface; + int dcerpc_flags; + } in; + struct { + struct dcerpc_pipe *dcerpc_pipe; + + /* parameters provided in LIBNET_RPC_CONNECT_DC_INFO level, null otherwise */ + const char *domain_name; + struct dom_sid *domain_sid; + const char *realm; /* these parameters are only present if */ + struct GUID *guid; /* the remote server is known to be AD */ + + const char *error_string; + } out; +}; + + +/* + * Monitor messages sent from libnet_rpc.c functions + */ + +struct msg_net_rpc_connect { + const char *host; + const char *domain_name; + const char *endpoint; + enum dcerpc_transport_t transport; +}; diff --git a/source4/libnet/libnet_samsync.h b/source4/libnet/libnet_samsync.h new file mode 100644 index 0000000..fb938d0 --- /dev/null +++ b/source4/libnet/libnet_samsync.h @@ -0,0 +1,32 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "librpc/gen_ndr/netlogon.h" + +struct libnet_SamDump_keytab { + struct { + const char *binding_string; + const char *keytab_name; + struct cli_credentials *machine_account; + } in; + struct { + const char *error_string; + } out; +}; + diff --git a/source4/libnet/libnet_share.c b/source4/libnet/libnet_share.c new file mode 100644 index 0000000..d8e8240 --- /dev/null +++ b/source4/libnet/libnet_share.c @@ -0,0 +1,215 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Grégory LEOCADIE <gleocadie@idealx.com> + + 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_srvsvc_c.h" + + +NTSTATUS libnet_ListShares(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_ListShares *r) +{ + NTSTATUS status; + struct libnet_RpcConnect c; + struct srvsvc_NetShareEnumAll s; + struct srvsvc_NetShareInfoCtr info_ctr; + uint32_t resume_handle = 0; + uint32_t totalentries = 0; + struct srvsvc_NetShareCtr0 ctr0; + struct srvsvc_NetShareCtr1 ctr1; + struct srvsvc_NetShareCtr2 ctr2; + struct srvsvc_NetShareCtr501 ctr501; + struct srvsvc_NetShareCtr502 ctr502; + + ZERO_STRUCT(c); + + c.level = LIBNET_RPC_CONNECT_SERVER; + c.in.name = r->in.server_name; + c.in.dcerpc_iface = &ndr_table_srvsvc; + + s.in.server_unc = talloc_asprintf(mem_ctx, "\\\\%s", c.in.name); + + status = libnet_RpcConnect(ctx, mem_ctx, &c); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "Connection to SRVSVC pipe of server %s " + "failed: %s", + r->in.server_name, + nt_errstr(status)); + return status; + } + + info_ctr.level = r->in.level; + switch (info_ctr.level) { + case 0: + info_ctr.ctr.ctr0 = &ctr0; + ZERO_STRUCT(ctr0); + break; + case 1: + info_ctr.ctr.ctr1 = &ctr1; + ZERO_STRUCT(ctr1); + break; + case 2: + info_ctr.ctr.ctr2 = &ctr2; + ZERO_STRUCT(ctr2); + break; + case 501: + info_ctr.ctr.ctr501 = &ctr501; + ZERO_STRUCT(ctr501); + break; + case 502: + info_ctr.ctr.ctr502 = &ctr502; + ZERO_STRUCT(ctr502); + break; + default: + r->out.error_string = talloc_asprintf(mem_ctx, + "libnet_ListShares: Invalid info level requested: %d", + info_ctr.level); + return NT_STATUS_INVALID_PARAMETER; + } + s.in.max_buffer = ~0; + s.in.resume_handle = &resume_handle; + s.in.info_ctr = &info_ctr; + s.out.info_ctr = &info_ctr; + s.out.totalentries = &totalentries; + + status = dcerpc_srvsvc_NetShareEnumAll_r(c.out.dcerpc_pipe->binding_handle, mem_ctx, &s); + + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "srvsvc_NetShareEnumAll on server '%s' failed" + ": %s", + r->in.server_name, nt_errstr(status)); + goto disconnect; + } + + if (!W_ERROR_IS_OK(s.out.result) && !W_ERROR_EQUAL(s.out.result, WERR_MORE_DATA)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "srvsvc_NetShareEnumAll on server '%s' failed: %s", + r->in.server_name, win_errstr(s.out.result)); + goto disconnect; + } + + r->out.ctr = s.out.info_ctr->ctr; + +disconnect: + talloc_free(c.out.dcerpc_pipe); + + return status; +} + + +NTSTATUS libnet_AddShare(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_AddShare *r) +{ + NTSTATUS status; + struct libnet_RpcConnect c; + struct srvsvc_NetShareAdd s; + union srvsvc_NetShareInfo info; + + ZERO_STRUCT(c); + + c.level = LIBNET_RPC_CONNECT_SERVER; + c.in.name = r->in.server_name; + c.in.dcerpc_iface = &ndr_table_srvsvc; + + status = libnet_RpcConnect(ctx, mem_ctx, &c); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "Connection to SRVSVC pipe of server %s " + "failed: %s", + r->in.server_name, nt_errstr(status)); + return status; + } + + info.info2 = &r->in.share; + + s.in.level = 2; + s.in.info = &info; + s.in.server_unc = talloc_asprintf(mem_ctx, "\\\\%s", r->in.server_name); + + status = dcerpc_srvsvc_NetShareAdd_r(c.out.dcerpc_pipe->binding_handle, mem_ctx, &s); + + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "srvsvc_NetShareAdd '%s' on server '%s' failed" + ": %s", + r->in.share.name, r->in.server_name, + nt_errstr(status)); + } else if (!W_ERROR_IS_OK(s.out.result)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "srvsvc_NetShareAdd '%s' on server '%s' failed" + ": %s", + r->in.share.name, r->in.server_name, + win_errstr(s.out.result)); + status = werror_to_ntstatus(s.out.result); + } + + talloc_free(c.out.dcerpc_pipe); + + return status; +} + + +NTSTATUS libnet_DelShare(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, struct libnet_DelShare *r) +{ + NTSTATUS status; + struct libnet_RpcConnect c; + struct srvsvc_NetShareDel s; + + ZERO_STRUCT(c); + ZERO_STRUCT(s); + + c.level = LIBNET_RPC_CONNECT_SERVER; + c.in.name = r->in.server_name; + c.in.dcerpc_iface = &ndr_table_srvsvc; + + status = libnet_RpcConnect(ctx, mem_ctx, &c); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "Connection to SRVSVC pipe of server %s " + "failed: %s", + r->in.server_name, nt_errstr(status)); + return status; + } + + s.in.server_unc = talloc_asprintf(mem_ctx, "\\\\%s", r->in.server_name); + s.in.share_name = r->in.share_name; + + status = dcerpc_srvsvc_NetShareDel_r(c.out.dcerpc_pipe->binding_handle, mem_ctx, &s); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "srvsvc_NetShareDel '%s' on server '%s' failed" + ": %s", + r->in.share_name, r->in.server_name, + nt_errstr(status)); + } else if (!W_ERROR_IS_OK(s.out.result)) { + r->out.error_string = talloc_asprintf(mem_ctx, + "srvsvc_NetShareDel '%s' on server '%s' failed" + ": %s", + r->in.share_name, r->in.server_name, + win_errstr(s.out.result)); + status = werror_to_ntstatus(s.out.result); + } + + talloc_free(c.out.dcerpc_pipe); + + return status; +} diff --git a/source4/libnet/libnet_share.h b/source4/libnet/libnet_share.h new file mode 100644 index 0000000..3a9bd72 --- /dev/null +++ b/source4/libnet/libnet_share.h @@ -0,0 +1,70 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Grégory LEOCADIE <gleocadie@idealx.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "librpc/gen_ndr/srvsvc.h" + +enum libnet_ListShares_level { + LIBNET_LIST_SHARES_GENERIC, + LIBNET_LIST_SHARES_SRVSVC +}; + +struct libnet_ListShares { + struct { + const char *server_name; + uint32_t *resume_handle; + uint32_t level; + } in; + struct { + const char *error_string; + union srvsvc_NetShareCtr ctr; + uint32_t *resume_handle; + } out; +}; + +enum libnet_AddShare_level { + LIBNET_ADD_SHARE_GENERIC, + LIBNET_ADD_SHARE_SRVSVC +}; + +struct libnet_AddShare { + enum libnet_AddShare_level level; + struct { + const char * server_name; + struct srvsvc_NetShareInfo2 share; + } in; + struct { + const char* error_string; + } out; +}; + +enum libnet_DelShare_level { + LIBNET_DEL_SHARE_GENERIC, + LIBNET_DEL_SHARE_SRVSVC +}; + +struct libnet_DelShare { + enum libnet_DelShare_level level; + struct { + const char *server_name; + const char *share_name; + } in; + struct { + const char *error_string; + } out; +}; diff --git a/source4/libnet/libnet_site.c b/source4/libnet/libnet_site.c new file mode 100644 index 0000000..691e4b2 --- /dev/null +++ b/source4/libnet/libnet_site.c @@ -0,0 +1,292 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Brad Henry 2005 + + 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 "libcli/cldap/cldap.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "libcli/resolve/resolve.h" +#include "param/param.h" +#include "lib/tsocket/tsocket.h" + +/** + * 1. Setup a CLDAP socket. + * 2. Lookup the default Site-Name. + */ +NTSTATUS libnet_FindSite(TALLOC_CTX *ctx, struct libnet_context *lctx, struct libnet_JoinSite *r) +{ + NTSTATUS status; + TALLOC_CTX *tmp_ctx; + + char *site_name_str; + char *config_dn_str; + char *server_dn_str; + + struct cldap_socket *cldap = NULL; + struct cldap_netlogon search; + int ret; + struct tsocket_address *dest_address; + + tmp_ctx = talloc_named(ctx, 0, "libnet_FindSite temp context"); + if (!tmp_ctx) { + r->out.error_string = NULL; + return NT_STATUS_NO_MEMORY; + } + + /* Resolve the site name. */ + ZERO_STRUCT(search); + search.in.dest_address = NULL; + search.in.dest_port = 0; + search.in.acct_control = -1; + search.in.version = NETLOGON_NT_VERSION_5 | NETLOGON_NT_VERSION_5EX; + search.in.map_response = true; + + ret = tsocket_address_inet_from_strings(tmp_ctx, "ip", + r->in.dest_address, + r->in.cldap_port, + &dest_address); + if (ret != 0) { + r->out.error_string = NULL; + status = map_nt_error_from_unix_common(errno); + talloc_free(tmp_ctx); + return status; + } + + /* we want to use non async calls, so we're not passing an event context */ + status = cldap_socket_init(tmp_ctx, NULL, dest_address, &cldap); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + r->out.error_string = NULL; + return status; + } + status = cldap_netlogon(cldap, tmp_ctx, &search); + if (!NT_STATUS_IS_OK(status) + || search.out.netlogon.data.nt5_ex.client_site == NULL + || search.out.netlogon.data.nt5_ex.client_site[0] == '\0') { + /* + If cldap_netlogon() returns in error, + default to using Default-First-Site-Name. + */ + site_name_str = talloc_asprintf(tmp_ctx, "%s", + "Default-First-Site-Name"); + if (!site_name_str) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + } else { + site_name_str = talloc_asprintf(tmp_ctx, "%s", + search.out.netlogon.data.nt5_ex.client_site); + if (!site_name_str) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + } + + /* Generate the CN=Configuration,... DN. */ +/* TODO: look it up! */ + config_dn_str = talloc_asprintf(tmp_ctx, "CN=Configuration,%s", r->in.domain_dn_str); + if (!config_dn_str) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* Generate the CN=Servers,... DN. */ + server_dn_str = talloc_asprintf(tmp_ctx, "CN=%s,CN=Servers,CN=%s,CN=Sites,%s", + r->in.netbios_name, site_name_str, config_dn_str); + if (!server_dn_str) { + r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + r->out.site_name_str = site_name_str; + talloc_steal(r, site_name_str); + + r->out.config_dn_str = config_dn_str; + talloc_steal(r, config_dn_str); + + r->out.server_dn_str = server_dn_str; + talloc_steal(r, server_dn_str); + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +/* + * find out Site specific stuff: + * 1. Lookup the Site name. + * 2. Add entry CN=<netbios name>,CN=Servers,CN=<site name>,CN=Sites,CN=Configuration,<domain dn>. + * TODO: 3.) use DsAddEntry() to create CN=NTDS Settings,CN=<netbios name>,CN=Servers,CN=<site name>,... + */ +NTSTATUS libnet_JoinSite(struct libnet_context *ctx, + struct ldb_context *remote_ldb, + struct libnet_JoinDomain *libnet_r) +{ + NTSTATUS status; + TALLOC_CTX *tmp_ctx; + + struct libnet_JoinSite *r; + + struct ldb_dn *server_dn; + struct ldb_message *msg; + int rtn; + + const char *server_dn_str; + const char *host; + struct nbt_name name; + const char *dest_addr = NULL; + + tmp_ctx = talloc_named(libnet_r, 0, "libnet_JoinSite temp context"); + if (!tmp_ctx) { + libnet_r->out.error_string = NULL; + return NT_STATUS_NO_MEMORY; + } + + r = talloc(tmp_ctx, struct libnet_JoinSite); + if (!r) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + host = dcerpc_binding_get_string_option(libnet_r->out.samr_binding, "host"); + make_nbt_name_client(&name, host); + status = resolve_name_ex(lpcfg_resolve_context(ctx->lp_ctx), + 0, 0, + &name, r, &dest_addr, ctx->event_ctx); + if (!NT_STATUS_IS_OK(status)) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return status; + } + + /* Resolve the site name and AD DN's. */ + r->in.dest_address = dest_addr; + r->in.netbios_name = libnet_r->in.netbios_name; + r->in.domain_dn_str = libnet_r->out.domain_dn_str; + r->in.cldap_port = lpcfg_cldap_port(ctx->lp_ctx); + + status = libnet_FindSite(tmp_ctx, ctx, r); + if (!NT_STATUS_IS_OK(status)) { + libnet_r->out.error_string = + talloc_steal(libnet_r, r->out.error_string); + talloc_free(tmp_ctx); + return status; + } + + server_dn_str = r->out.server_dn_str; + + /* + Add entry CN=<netbios name>,CN=Servers,CN=<site name>,CN=Sites,CN=Configuration,<domain dn>. + */ + msg = ldb_msg_new(tmp_ctx); + if (!msg) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + rtn = ldb_msg_add_string(msg, "objectClass", "server"); + if (rtn != LDB_SUCCESS) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + rtn = ldb_msg_add_string(msg, "systemFlags", "50000000"); + if (rtn != LDB_SUCCESS) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + rtn = ldb_msg_add_string(msg, "serverReference", libnet_r->out.account_dn_str); + if (rtn != LDB_SUCCESS) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + server_dn = ldb_dn_new(tmp_ctx, remote_ldb, server_dn_str); + if ( ! ldb_dn_validate(server_dn)) { + libnet_r->out.error_string = talloc_asprintf(libnet_r, + "Invalid server dn: %s", + server_dn_str); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + msg->dn = server_dn; + + rtn = ldb_add(remote_ldb, msg); + if (rtn == LDB_ERR_ENTRY_ALREADY_EXISTS) { + unsigned int i; + + /* make a 'modify' msg, and only for serverReference */ + msg = ldb_msg_new(tmp_ctx); + if (!msg) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + msg->dn = server_dn; + + rtn = ldb_msg_add_string(msg, "serverReference",libnet_r->out.account_dn_str); + if (rtn != LDB_SUCCESS) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* mark all the message elements (should be just one) + as LDB_FLAG_MOD_REPLACE */ + for (i=0;i<msg->num_elements;i++) { + msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + + rtn = ldb_modify(remote_ldb, msg); + if (rtn != LDB_SUCCESS) { + libnet_r->out.error_string + = talloc_asprintf(libnet_r, + "Failed to modify server entry %s: %s: %d", + server_dn_str, + ldb_errstring(remote_ldb), rtn); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } else if (rtn != LDB_SUCCESS) { + libnet_r->out.error_string + = talloc_asprintf(libnet_r, + "Failed to add server entry %s: %s: %d", + server_dn_str, ldb_errstring(remote_ldb), + rtn); + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + DEBUG(0, ("We still need to perform a DsAddEntry() so that we can create the CN=NTDS Settings container.\n")); + + /* Store the server DN in libnet_r */ + libnet_r->out.server_dn_str = server_dn_str; + talloc_steal(libnet_r, server_dn_str); + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} diff --git a/source4/libnet/libnet_site.h b/source4/libnet/libnet_site.h new file mode 100644 index 0000000..8e607c5 --- /dev/null +++ b/source4/libnet/libnet_site.h @@ -0,0 +1,35 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Brad Henry 2005 + + 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 libnet_JoinSite { + struct { + const char *dest_address; + const char *netbios_name; + const char *domain_dn_str; + uint16_t cldap_port; + } in; + + struct { + const char *error_string; + const char *site_name_str; + const char *config_dn_str; + const char *server_dn_str; + } out; +}; + diff --git a/source4/libnet/libnet_time.c b/source4/libnet/libnet_time.c new file mode 100644 index 0000000..e3b6275 --- /dev/null +++ b/source4/libnet/libnet_time.c @@ -0,0 +1,125 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + + 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 "system/time.h" +#include "librpc/gen_ndr/ndr_srvsvc_c.h" + +/* + * get the remote time of a server via srvsvc_NetRemoteTOD + */ +static NTSTATUS libnet_RemoteTOD_srvsvc(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_RemoteTOD *r) +{ + NTSTATUS status; + struct libnet_RpcConnect c; + struct srvsvc_NetRemoteTOD tod; + struct srvsvc_NetRemoteTODInfo *info = NULL; + struct tm tm; + + ZERO_STRUCT(c); + + /* prepare connect to the SRVSVC pipe of a timeserver */ + c.level = LIBNET_RPC_CONNECT_SERVER; + c.in.name = r->srvsvc.in.server_name; + c.in.dcerpc_iface = &ndr_table_srvsvc; + + /* 1. connect to the SRVSVC pipe of a timeserver */ + status = libnet_RpcConnect(ctx, mem_ctx, &c); + if (!NT_STATUS_IS_OK(status)) { + r->srvsvc.out.error_string = talloc_asprintf(mem_ctx, + "Connection to SRVSVC pipe of server '%s' failed: %s", + r->srvsvc.in.server_name, nt_errstr(status)); + return status; + } + + /* prepare srvsvc_NetrRemoteTOD */ + tod.in.server_unc = talloc_asprintf(mem_ctx, "\\%s", c.in.name); + tod.out.info = &info; + + /* 2. try srvsvc_NetRemoteTOD */ + status = dcerpc_srvsvc_NetRemoteTOD_r(c.out.dcerpc_pipe->binding_handle, mem_ctx, &tod); + if (!NT_STATUS_IS_OK(status)) { + r->srvsvc.out.error_string = talloc_asprintf(mem_ctx, + "srvsvc_NetrRemoteTOD on server '%s' failed: %s", + r->srvsvc.in.server_name, nt_errstr(status)); + goto disconnect; + } + + /* check result of srvsvc_NetrRemoteTOD */ + if (!W_ERROR_IS_OK(tod.out.result)) { + r->srvsvc.out.error_string = talloc_asprintf(mem_ctx, + "srvsvc_NetrRemoteTOD on server '%s' failed: %s", + r->srvsvc.in.server_name, win_errstr(tod.out.result)); + status = werror_to_ntstatus(tod.out.result); + goto disconnect; + } + + /* need to set the out parameters */ + tm.tm_sec = (int)info->secs; + tm.tm_min = (int)info->mins; + tm.tm_hour = (int)info->hours; + tm.tm_mday = (int)info->day; + tm.tm_mon = (int)info->month -1; + tm.tm_year = (int)info->year - 1900; + tm.tm_wday = -1; + tm.tm_yday = -1; + tm.tm_isdst = -1; + + r->srvsvc.out.time = timegm(&tm); + r->srvsvc.out.time_zone = info->timezone * 60; + + goto disconnect; + +disconnect: + /* close connection */ + talloc_free(c.out.dcerpc_pipe); + + return status; +} + +static NTSTATUS libnet_RemoteTOD_generic(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_RemoteTOD *r) +{ + NTSTATUS status; + union libnet_RemoteTOD r2; + + r2.srvsvc.level = LIBNET_REMOTE_TOD_SRVSVC; + r2.srvsvc.in.server_name = r->generic.in.server_name; + + status = libnet_RemoteTOD(ctx, mem_ctx, &r2); + + r->generic.out.time = r2.srvsvc.out.time; + r->generic.out.time_zone = r2.srvsvc.out.time_zone; + + r->generic.out.error_string = r2.srvsvc.out.error_string; + + return status; +} + +NTSTATUS libnet_RemoteTOD(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, union libnet_RemoteTOD *r) +{ + switch (r->generic.level) { + case LIBNET_REMOTE_TOD_GENERIC: + return libnet_RemoteTOD_generic(ctx, mem_ctx, r); + case LIBNET_REMOTE_TOD_SRVSVC: + return libnet_RemoteTOD_srvsvc(ctx, mem_ctx, r); + } + + return NT_STATUS_INVALID_LEVEL; +} diff --git a/source4/libnet/libnet_time.h b/source4/libnet/libnet_time.h new file mode 100644 index 0000000..4382d72 --- /dev/null +++ b/source4/libnet/libnet_time.h @@ -0,0 +1,46 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + + 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 and enum for getting the time of a remote system */ +enum libnet_RemoteTOD_level { + LIBNET_REMOTE_TOD_GENERIC, + LIBNET_REMOTE_TOD_SRVSVC +}; + +union libnet_RemoteTOD { + struct { + enum libnet_RemoteTOD_level level; + + struct _libnet_RemoteTOD_in { + const char *server_name; + } in; + + struct _libnet_RemoteTOD_out { + time_t time; + int time_zone; + const char *error_string; + } out; + } generic; + + struct { + enum libnet_RemoteTOD_level level; + struct _libnet_RemoteTOD_in in; + struct _libnet_RemoteTOD_out out; + } srvsvc; +}; diff --git a/source4/libnet/libnet_unbecome_dc.c b/source4/libnet/libnet_unbecome_dc.c new file mode 100644 index 0000000..38d6a94 --- /dev/null +++ b/source4/libnet/libnet_unbecome_dc.c @@ -0,0 +1,792 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher <metze@samba.org> 2006 + + 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 "libcli/composite/composite.h" +#include "libcli/cldap/cldap.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "ldb_wrap.h" +#include "dsdb/samdb/samdb.h" +#include "../libds/common/flags.h" +#include "librpc/gen_ndr/ndr_drsuapi_c.h" +#include "param/param.h" +#include "lib/tsocket/tsocket.h" + +/***************************************************************************** + * Windows 2003 (w2k3) does the following steps when changing the server role + * from domain controller back to domain member + * + * We mostly do the same. + *****************************************************************************/ + +/* + * lookup DC: + * - using nbt name<1C> request and a samlogon mailslot request + * or + * - using a DNS SRV _ldap._tcp.dc._msdcs. request and a CLDAP netlogon request + * + * see: unbecomeDC_send_cldap() and unbecomeDC_recv_cldap() + */ + +/* + * Open 1st LDAP connection to the DC using admin credentials + * + * see: unbecomeDC_ldap_connect() + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: unbecomeDC_ldap_rootdse() + * + * Request: + * basedn: "" + * scope: base + * filter: (objectClass=*) + * attrs: defaultNamingContext + * configurationNamingContext + * Result: + * "" + * defaultNamingContext: <domain_partition> + * configurationNamingContext:CN=Configuration,<domain_partition> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: unbecomeDC_ldap_computer_object() + * + * Request: + * basedn: <domain_partition> + * scope: sub + * filter: (&(|(objectClass=user)(objectClass=computer))(sAMAccountName=<new_dc_account_name>)) + * attrs: distinguishedName + * userAccountControl + * Result: + * CN=<new_dc_netbios_name>,CN=Domain Controllers,<domain_partition> + * distinguishedName: CN=<new_dc_netbios_name>,CN=Domain Controllers,<domain_partition> + * userAccoountControl: 532480 <0x82000> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: unbecomeDC_ldap_modify_computer() + * + * Request: + * basedn: CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: userAccountControl + * Result: + * CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * userAccoountControl: 532480 <0x82000> + */ + +/* + * LDAP modify 1st LDAP connection: + * + * see: unbecomeDC_ldap_modify_computer() + * + * Request (replace): + * CN=<new_dc_netbios_name>,CN=Computers,<domain_partition> + * userAccoountControl: 4096 <0x1000> + * Result: + * <success> + */ + +/* + * LDAP search 1st LDAP connection: + * + * see: unbecomeDC_ldap_move_computer() + * + * Request: + * basedn: <WKGUID=aa312825768811d1aded00c04fd8d5cd,<domain_partition>> + * scope: base + * filter: (objectClass=*) + * attrs: 1.1 + * Result: + * CN=Computers,<domain_partition> + */ + +/* + * LDAP search 1st LDAP connection: + * + * not implemented because it doesn't give any new information + * + * Request: + * basedn: CN=Computers,<domain_partition> + * scope: base + * filter: (objectClass=*) + * attrs: distinguishedName + * Result: + * CN=Computers,<domain_partition> + * distinguishedName: CN=Computers,<domain_partition> + */ + +/* + * LDAP modifyRDN 1st LDAP connection: + * + * see: unbecomeDC_ldap_move_computer() + * + * Request: + * entry: CN=<new_dc_netbios_name>,CN=Domain Controllers,<domain_partition> + * newrdn: CN=<new_dc_netbios_name> + * deleteoldrdn: TRUE + * newparent: CN=Computers,<domain_partition> + * Result: + * <success> + */ + +/* + * LDAP unbind on the 1st LDAP connection + * + * not implemented, because it's not needed... + */ + +/* + * Open 1st DRSUAPI connection to the DC using admin credentials + * DsBind with DRSUAPI_DS_BIND_GUID ("e24d201a-4fd6-11d1-a3da-0000f875ae0d") + * + * see: unbecomeDC_drsuapi_connect_send(), unbecomeDC_drsuapi_connect_recv(), + * unbecomeDC_drsuapi_bind_send() and unbecomeDC_drsuapi_bind_recv() + */ + +/* + * DsRemoveDsServer to remove the + * CN=<machine_name>,CN=Servers,CN=<site_name>,CN=Configuration,<domain_partition> + * and CN=NTDS Settings,CN=<machine_name>,CN=Servers,CN=<site_name>,CN=Configuration,<domain_partition> + * on the 1st DRSUAPI connection + * + * see: unbecomeDC_drsuapi_remove_ds_server_send() and unbecomeDC_drsuapi_remove_ds_server_recv() + */ + +/* + * DsUnbind on the 1st DRSUAPI connection + * + * not implemented, because it's not needed... + */ + + +struct libnet_UnbecomeDC_state { + struct composite_context *creq; + + struct libnet_context *libnet; + + struct { + struct cldap_socket *sock; + struct cldap_netlogon io; + struct NETLOGON_SAM_LOGON_RESPONSE_EX netlogon; + } cldap; + + struct { + struct ldb_context *ldb; + } ldap; + + struct { + struct dcerpc_binding *binding; + struct dcerpc_pipe *pipe; + struct dcerpc_binding_handle *drsuapi_handle; + struct drsuapi_DsBind bind_r; + struct GUID bind_guid; + struct drsuapi_DsBindInfoCtr bind_info_ctr; + struct drsuapi_DsBindInfo28 local_info28; + struct drsuapi_DsBindInfo28 remote_info28; + struct policy_handle bind_handle; + struct drsuapi_DsRemoveDSServer rm_ds_srv_r; + } drsuapi; + + struct { + /* input */ + const char *dns_name; + const char *netbios_name; + + /* constructed */ + struct GUID guid; + const char *dn_str; + } domain; + + struct { + /* constructed */ + const char *config_dn_str; + } forest; + + struct { + /* input */ + const char *address; + + /* constructed */ + const char *dns_name; + const char *netbios_name; + const char *site_name; + } source_dsa; + + struct { + /* input */ + const char *netbios_name; + + /* constructed */ + const char *dns_name; + const char *site_name; + const char *computer_dn_str; + const char *server_dn_str; + uint32_t user_account_control; + } dest_dsa; +}; + +static void unbecomeDC_recv_cldap(struct tevent_req *req); + +static void unbecomeDC_send_cldap(struct libnet_UnbecomeDC_state *s) +{ + struct composite_context *c = s->creq; + struct tevent_req *req; + struct tsocket_address *dest_address; + int ret; + + s->cldap.io.in.dest_address = NULL; + s->cldap.io.in.dest_port = 0; + s->cldap.io.in.realm = s->domain.dns_name; + s->cldap.io.in.host = s->dest_dsa.netbios_name; + s->cldap.io.in.user = NULL; + s->cldap.io.in.domain_guid = NULL; + s->cldap.io.in.domain_sid = NULL; + s->cldap.io.in.acct_control = -1; + s->cldap.io.in.version = NETLOGON_NT_VERSION_5 | NETLOGON_NT_VERSION_5EX; + s->cldap.io.in.map_response = true; + + ret = tsocket_address_inet_from_strings(s, "ip", + s->source_dsa.address, + lpcfg_cldap_port(s->libnet->lp_ctx), + &dest_address); + if (ret != 0) { + c->status = map_nt_error_from_unix_common(errno); + if (!composite_is_ok(c)) return; + } + + c->status = cldap_socket_init(s, NULL, dest_address, &s->cldap.sock); + if (!composite_is_ok(c)) return; + + req = cldap_netlogon_send(s, s->libnet->event_ctx, + s->cldap.sock, &s->cldap.io); + if (composite_nomem(req, c)) return; + tevent_req_set_callback(req, unbecomeDC_recv_cldap, s); +} + +static void unbecomeDC_connect_ldap(struct libnet_UnbecomeDC_state *s); + +static void unbecomeDC_recv_cldap(struct tevent_req *req) +{ + struct libnet_UnbecomeDC_state *s = tevent_req_callback_data(req, + struct libnet_UnbecomeDC_state); + struct composite_context *c = s->creq; + + c->status = cldap_netlogon_recv(req, s, &s->cldap.io); + talloc_free(req); + if (!composite_is_ok(c)) return; + + s->cldap.netlogon = s->cldap.io.out.netlogon.data.nt5_ex; + + s->domain.dns_name = s->cldap.netlogon.dns_domain; + s->domain.netbios_name = s->cldap.netlogon.domain_name; + s->domain.guid = s->cldap.netlogon.domain_uuid; + + s->source_dsa.dns_name = s->cldap.netlogon.pdc_dns_name; + s->source_dsa.netbios_name = s->cldap.netlogon.pdc_name; + s->source_dsa.site_name = s->cldap.netlogon.server_site; + + s->dest_dsa.site_name = s->cldap.netlogon.client_site; + + unbecomeDC_connect_ldap(s); +} + +static NTSTATUS unbecomeDC_ldap_connect(struct libnet_UnbecomeDC_state *s) +{ + char *url; + + url = talloc_asprintf(s, "ldap://%s/", s->source_dsa.dns_name); + NT_STATUS_HAVE_NO_MEMORY(url); + + s->ldap.ldb = ldb_wrap_connect(s, s->libnet->event_ctx, s->libnet->lp_ctx, url, + NULL, + s->libnet->cred, + 0); + talloc_free(url); + if (s->ldap.ldb == NULL) { + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + + return NT_STATUS_OK; +} + +static NTSTATUS unbecomeDC_ldap_rootdse(struct libnet_UnbecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + static const char *attrs[] = { + "defaultNamingContext", + "configurationNamingContext", + NULL + }; + + basedn = ldb_dn_new(s, s->ldap.ldb, NULL); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap.ldb, s, &r, basedn, LDB_SCOPE_BASE, attrs, + "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->domain.dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "defaultNamingContext", NULL); + if (!s->domain.dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + talloc_steal(s, s->domain.dn_str); + + s->forest.config_dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "configurationNamingContext", NULL); + if (!s->forest.config_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + talloc_steal(s, s->forest.config_dn_str); + + s->dest_dsa.server_dn_str = talloc_asprintf(s, "CN=%s,CN=Servers,CN=%s,CN=Sites,%s", + s->dest_dsa.netbios_name, + s->dest_dsa.site_name, + s->forest.config_dn_str); + NT_STATUS_HAVE_NO_MEMORY(s->dest_dsa.server_dn_str); + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS unbecomeDC_ldap_computer_object(struct libnet_UnbecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + static const char *attrs[] = { + "distinguishedName", + "userAccountControl", + NULL + }; + + basedn = ldb_dn_new(s, s->ldap.ldb, s->domain.dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap.ldb, s, &r, basedn, LDB_SCOPE_SUBTREE, attrs, + "(&(|(objectClass=user)(objectClass=computer))(sAMAccountName=%s$))", + s->dest_dsa.netbios_name); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + s->dest_dsa.computer_dn_str = ldb_msg_find_attr_as_string(r->msgs[0], "distinguishedName", NULL); + if (!s->dest_dsa.computer_dn_str) return NT_STATUS_INVALID_NETWORK_RESPONSE; + talloc_steal(s, s->dest_dsa.computer_dn_str); + + s->dest_dsa.user_account_control = ldb_msg_find_attr_as_uint(r->msgs[0], "userAccountControl", 0); + + talloc_free(r); + return NT_STATUS_OK; +} + +static NTSTATUS unbecomeDC_ldap_modify_computer(struct libnet_UnbecomeDC_state *s) +{ + int ret; + struct ldb_message *msg; + uint32_t user_account_control = UF_WORKSTATION_TRUST_ACCOUNT; + unsigned int i; + + /* as the value is already as we want it to be, we're done */ + if (s->dest_dsa.user_account_control == user_account_control) { + return NT_STATUS_OK; + } + + /* make a 'modify' msg, and only for serverReference */ + msg = ldb_msg_new(s); + NT_STATUS_HAVE_NO_MEMORY(msg); + msg->dn = ldb_dn_new(msg, s->ldap.ldb, s->dest_dsa.computer_dn_str); + NT_STATUS_HAVE_NO_MEMORY(msg->dn); + + ret = samdb_msg_add_uint(s->ldap.ldb, msg, msg, "userAccountControl", + user_account_control); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + + /* mark all the message elements (should be just one) + as LDB_FLAG_MOD_REPLACE */ + for (i=0;i<msg->num_elements;i++) { + msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + + ret = ldb_modify(s->ldap.ldb, msg); + talloc_free(msg); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } + + s->dest_dsa.user_account_control = user_account_control; + + return NT_STATUS_OK; +} + +static NTSTATUS unbecomeDC_ldap_move_computer(struct libnet_UnbecomeDC_state *s) +{ + int ret; + struct ldb_result *r; + struct ldb_dn *basedn; + struct ldb_dn *old_dn; + struct ldb_dn *new_dn; + static const char *_1_1_attrs[] = { + "1.1", + NULL + }; + + basedn = ldb_dn_new_fmt(s, s->ldap.ldb, "<WKGUID=aa312825768811d1aded00c04fd8d5cd,%s>", + s->domain.dn_str); + NT_STATUS_HAVE_NO_MEMORY(basedn); + + ret = ldb_search(s->ldap.ldb, s, &r, basedn, LDB_SCOPE_BASE, + _1_1_attrs, "(objectClass=*)"); + talloc_free(basedn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_LDAP(ret); + } else if (r->count != 1) { + talloc_free(r); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + old_dn = ldb_dn_new(r, s->ldap.ldb, s->dest_dsa.computer_dn_str); + NT_STATUS_HAVE_NO_MEMORY(old_dn); + + new_dn = r->msgs[0]->dn; + + if (!ldb_dn_add_child_fmt(new_dn, "CN=%s", s->dest_dsa.netbios_name)) { + talloc_free(r); + return NT_STATUS_NO_MEMORY; + } + + if (ldb_dn_compare(old_dn, new_dn) == 0) { + /* we don't need to rename if the old and new dn match */ + talloc_free(r); + return NT_STATUS_OK; + } + + ret = ldb_rename(s->ldap.ldb, old_dn, new_dn); + if (ret != LDB_SUCCESS) { + talloc_free(r); + return NT_STATUS_LDAP(ret); + } + + s->dest_dsa.computer_dn_str = ldb_dn_alloc_linearized(s, new_dn); + NT_STATUS_HAVE_NO_MEMORY(s->dest_dsa.computer_dn_str); + + talloc_free(r); + + return NT_STATUS_OK; +} + +static void unbecomeDC_drsuapi_connect_send(struct libnet_UnbecomeDC_state *s); + +static void unbecomeDC_connect_ldap(struct libnet_UnbecomeDC_state *s) +{ + struct composite_context *c = s->creq; + + c->status = unbecomeDC_ldap_connect(s); + if (!composite_is_ok(c)) return; + + c->status = unbecomeDC_ldap_rootdse(s); + if (!composite_is_ok(c)) return; + + c->status = unbecomeDC_ldap_computer_object(s); + if (!composite_is_ok(c)) return; + + c->status = unbecomeDC_ldap_modify_computer(s); + if (!composite_is_ok(c)) return; + + c->status = unbecomeDC_ldap_move_computer(s); + if (!composite_is_ok(c)) return; + + unbecomeDC_drsuapi_connect_send(s); +} + +static void unbecomeDC_drsuapi_connect_recv(struct composite_context *creq); + +static void unbecomeDC_drsuapi_connect_send(struct libnet_UnbecomeDC_state *s) +{ + struct composite_context *c = s->creq; + struct composite_context *creq; + char *binding_str; + + binding_str = talloc_asprintf(s, "ncacn_ip_tcp:%s[seal,target_hostname=%s]", + s->source_dsa.address, + s->source_dsa.dns_name); + if (composite_nomem(binding_str, c)) return; + + c->status = dcerpc_parse_binding(s, binding_str, &s->drsuapi.binding); + talloc_free(binding_str); + if (!composite_is_ok(c)) return; + + if (DEBUGLEVEL >= 10) { + c->status = dcerpc_binding_set_flags(s->drsuapi.binding, + DCERPC_DEBUG_PRINT_BOTH, + 0); + if (!composite_is_ok(c)) return; + } + + creq = dcerpc_pipe_connect_b_send(s, s->drsuapi.binding, &ndr_table_drsuapi, + s->libnet->cred, s->libnet->event_ctx, + s->libnet->lp_ctx); + composite_continue(c, creq, unbecomeDC_drsuapi_connect_recv, s); +} + +static void unbecomeDC_drsuapi_bind_send(struct libnet_UnbecomeDC_state *s); + +static void unbecomeDC_drsuapi_connect_recv(struct composite_context *req) +{ + struct libnet_UnbecomeDC_state *s = talloc_get_type(req->async.private_data, + struct libnet_UnbecomeDC_state); + struct composite_context *c = s->creq; + + c->status = dcerpc_pipe_connect_b_recv(req, s, &s->drsuapi.pipe); + if (!composite_is_ok(c)) return; + + s->drsuapi.drsuapi_handle = s->drsuapi.pipe->binding_handle; + + unbecomeDC_drsuapi_bind_send(s); +} + +static void unbecomeDC_drsuapi_bind_recv(struct tevent_req *subreq); + +static void unbecomeDC_drsuapi_bind_send(struct libnet_UnbecomeDC_state *s) +{ + struct composite_context *c = s->creq; + struct drsuapi_DsBindInfo28 *bind_info28; + struct tevent_req *subreq; + + GUID_from_string(DRSUAPI_DS_BIND_GUID, &s->drsuapi.bind_guid); + + bind_info28 = &s->drsuapi.local_info28; + bind_info28->supported_extensions = 0; + bind_info28->site_guid = GUID_zero(); + bind_info28->pid = 0; + bind_info28->repl_epoch = 0; + + s->drsuapi.bind_info_ctr.length = 28; + s->drsuapi.bind_info_ctr.info.info28 = *bind_info28; + + s->drsuapi.bind_r.in.bind_guid = &s->drsuapi.bind_guid; + s->drsuapi.bind_r.in.bind_info = &s->drsuapi.bind_info_ctr; + s->drsuapi.bind_r.out.bind_handle = &s->drsuapi.bind_handle; + + subreq = dcerpc_drsuapi_DsBind_r_send(s, c->event_ctx, + s->drsuapi.drsuapi_handle, + &s->drsuapi.bind_r); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, unbecomeDC_drsuapi_bind_recv, s); +} + +static void unbecomeDC_drsuapi_remove_ds_server_send(struct libnet_UnbecomeDC_state *s); + +static void unbecomeDC_drsuapi_bind_recv(struct tevent_req *subreq) +{ + struct libnet_UnbecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_UnbecomeDC_state); + struct composite_context *c = s->creq; + + c->status = dcerpc_drsuapi_DsBind_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!W_ERROR_IS_OK(s->drsuapi.bind_r.out.result)) { + composite_error(c, werror_to_ntstatus(s->drsuapi.bind_r.out.result)); + return; + } + + ZERO_STRUCT(s->drsuapi.remote_info28); + if (s->drsuapi.bind_r.out.bind_info) { + switch (s->drsuapi.bind_r.out.bind_info->length) { + case 24: { + struct drsuapi_DsBindInfo24 *info24; + info24 = &s->drsuapi.bind_r.out.bind_info->info.info24; + s->drsuapi.remote_info28.supported_extensions = info24->supported_extensions; + s->drsuapi.remote_info28.site_guid = info24->site_guid; + s->drsuapi.remote_info28.pid = info24->pid; + s->drsuapi.remote_info28.repl_epoch = 0; + break; + } + case 28: { + s->drsuapi.remote_info28 = s->drsuapi.bind_r.out.bind_info->info.info28; + break; + } + case 32: { + struct drsuapi_DsBindInfo32 *info32; + info32 = &s->drsuapi.bind_r.out.bind_info->info.info32; + s->drsuapi.remote_info28.supported_extensions = info32->supported_extensions; + s->drsuapi.remote_info28.site_guid = info32->site_guid; + s->drsuapi.remote_info28.pid = info32->pid; + s->drsuapi.remote_info28.repl_epoch = info32->repl_epoch; + break; + } + case 48: { + struct drsuapi_DsBindInfo48 *info48; + info48 = &s->drsuapi.bind_r.out.bind_info->info.info48; + s->drsuapi.remote_info28.supported_extensions = info48->supported_extensions; + s->drsuapi.remote_info28.site_guid = info48->site_guid; + s->drsuapi.remote_info28.pid = info48->pid; + s->drsuapi.remote_info28.repl_epoch = info48->repl_epoch; + break; + } + case 52: { + struct drsuapi_DsBindInfo52 *info52; + info52 = &s->drsuapi.bind_r.out.bind_info->info.info52; + s->drsuapi.remote_info28.supported_extensions = info52->supported_extensions; + s->drsuapi.remote_info28.site_guid = info52->site_guid; + s->drsuapi.remote_info28.pid = info52->pid; + s->drsuapi.remote_info28.repl_epoch = info52->repl_epoch; + break; + } + default: + DEBUG(1, ("Warning: invalid info length in bind info: %d\n", + s->drsuapi.bind_r.out.bind_info->length)); + break; + } + } + + unbecomeDC_drsuapi_remove_ds_server_send(s); +} + +static void unbecomeDC_drsuapi_remove_ds_server_recv(struct tevent_req *subreq); + +static void unbecomeDC_drsuapi_remove_ds_server_send(struct libnet_UnbecomeDC_state *s) +{ + struct composite_context *c = s->creq; + struct drsuapi_DsRemoveDSServer *r = &s->drsuapi.rm_ds_srv_r; + struct tevent_req *subreq; + + r->in.bind_handle = &s->drsuapi.bind_handle; + r->in.level = 1; + r->in.req = talloc(s, union drsuapi_DsRemoveDSServerRequest); + r->in.req->req1.server_dn = s->dest_dsa.server_dn_str; + r->in.req->req1.domain_dn = s->domain.dn_str; + r->in.req->req1.commit = true; + + r->out.level_out = talloc(s, uint32_t); + r->out.res = talloc(s, union drsuapi_DsRemoveDSServerResult); + + subreq = dcerpc_drsuapi_DsRemoveDSServer_r_send(s, c->event_ctx, + s->drsuapi.drsuapi_handle, + r); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, unbecomeDC_drsuapi_remove_ds_server_recv, s); +} + +static void unbecomeDC_drsuapi_remove_ds_server_recv(struct tevent_req *subreq) +{ + struct libnet_UnbecomeDC_state *s = tevent_req_callback_data(subreq, + struct libnet_UnbecomeDC_state); + struct composite_context *c = s->creq; + struct drsuapi_DsRemoveDSServer *r = &s->drsuapi.rm_ds_srv_r; + + c->status = dcerpc_drsuapi_DsRemoveDSServer_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!W_ERROR_IS_OK(r->out.result)) { + composite_error(c, werror_to_ntstatus(r->out.result)); + return; + } + + if (*r->out.level_out != 1) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + composite_done(c); +} + +struct composite_context *libnet_UnbecomeDC_send(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_UnbecomeDC *r) +{ + struct composite_context *c; + struct libnet_UnbecomeDC_state *s; + char *tmp_name; + + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct libnet_UnbecomeDC_state); + if (composite_nomem(s, c)) return c; + c->private_data = s; + s->creq = c; + s->libnet = ctx; + + /* Domain input */ + s->domain.dns_name = talloc_strdup(s, r->in.domain_dns_name); + if (composite_nomem(s->domain.dns_name, c)) return c; + s->domain.netbios_name = talloc_strdup(s, r->in.domain_netbios_name); + if (composite_nomem(s->domain.netbios_name, c)) return c; + + /* Source DSA input */ + s->source_dsa.address = talloc_strdup(s, r->in.source_dsa_address); + if (composite_nomem(s->source_dsa.address, c)) return c; + + /* Destination DSA input */ + s->dest_dsa.netbios_name= talloc_strdup(s, r->in.dest_dsa_netbios_name); + if (composite_nomem(s->dest_dsa.netbios_name, c)) return c; + + /* Destination DSA dns_name construction */ + tmp_name = strlower_talloc(s, s->dest_dsa.netbios_name); + if (composite_nomem(tmp_name, c)) return c; + s->dest_dsa.dns_name = talloc_asprintf_append_buffer(tmp_name, ".%s", + s->domain.dns_name); + if (composite_nomem(s->dest_dsa.dns_name, c)) return c; + + unbecomeDC_send_cldap(s); + return c; +} + +NTSTATUS libnet_UnbecomeDC_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, struct libnet_UnbecomeDC *r) +{ + NTSTATUS status; + + status = composite_wait(c); + + ZERO_STRUCT(r->out); + + talloc_free(c); + return status; +} + +NTSTATUS libnet_UnbecomeDC(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_UnbecomeDC *r) +{ + NTSTATUS status; + struct composite_context *c; + c = libnet_UnbecomeDC_send(ctx, mem_ctx, r); + status = libnet_UnbecomeDC_recv(c, mem_ctx, r); + return status; +} diff --git a/source4/libnet/libnet_unbecome_dc.h b/source4/libnet/libnet_unbecome_dc.h new file mode 100644 index 0000000..30a412f --- /dev/null +++ b/source4/libnet/libnet_unbecome_dc.h @@ -0,0 +1,31 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher <metze@samba.org> 2006 + + 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 libnet_UnbecomeDC { + struct { + const char *domain_dns_name; + const char *domain_netbios_name; + const char *source_dsa_address; + const char *dest_dsa_netbios_name; + } in; + + struct { + const char *error_string; + } out; +}; diff --git a/source4/libnet/libnet_user.c b/source4/libnet/libnet_user.c new file mode 100644 index 0000000..f4b90d9 --- /dev/null +++ b/source4/libnet/libnet_user.c @@ -0,0 +1,1241 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + 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 "libcli/composite/composite.h" +#include "auth/credentials/credentials.h" +#include "librpc/ndr/libndr.h" +#include "librpc/gen_ndr/samr.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/gen_ndr/lsa.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "libcli/security/security.h" + + +struct create_user_state { + struct libnet_CreateUser r; + struct libnet_DomainOpen domain_open; + struct libnet_rpc_useradd user_add; + struct libnet_context *ctx; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_rpc_useradd(struct composite_context *ctx); +static void continue_domain_open_create(struct composite_context *ctx); + + +/** + * Sends request to create user account + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to a structure containing arguments and results of this call + * @param monitor function pointer for receiving monitor messages + * @return compostite context of this request + */ +struct composite_context* libnet_CreateUser_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_CreateUser *r, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct create_user_state *s; + struct composite_context *create_req; + bool prereq_met = false; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct create_user_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* store arguments in the state structure */ + s->ctx = ctx; + s->r = *r; + ZERO_STRUCT(s->r.out); + + /* prerequisite: make sure the domain is opened */ + prereq_met = samr_domain_opened(ctx, c, s->r.in.domain_name, &c, &s->domain_open, + continue_domain_open_create, monitor); + if (!prereq_met) return c; + + /* prepare arguments for useradd call */ + s->user_add.in.username = r->in.user_name; + s->user_add.in.domain_handle = ctx->samr.handle; + + /* send the request */ + create_req = libnet_rpc_useradd_send(s, s->ctx->event_ctx, + ctx->samr.samr_handle, + &s->user_add, monitor); + if (composite_nomem(create_req, c)) return c; + + /* set the next stage */ + composite_continue(c, create_req, continue_rpc_useradd, c); + return c; +} + + +/* + * Stage 0.5 (optional): receive result of domain open request + * and send useradd request + */ +static void continue_domain_open_create(struct composite_context *ctx) +{ + struct composite_context *c; + struct create_user_state *s; + struct composite_context *create_req; + struct monitor_msg msg; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct create_user_state); + + /* receive result of DomainOpen call */ + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domain_open); + if (!composite_is_ok(c)) return; + + /* send monitor message */ + if (s->monitor_fn) s->monitor_fn(&msg); + + /* prepare arguments for useradd call */ + s->user_add.in.username = s->r.in.user_name; + s->user_add.in.domain_handle = s->ctx->samr.handle; + + /* send the request */ + create_req = libnet_rpc_useradd_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->user_add, s->monitor_fn); + if (composite_nomem(create_req, c)) return; + + /* set the next stage */ + composite_continue(c, create_req, continue_rpc_useradd, c); +} + + +/* + * Stage 1: receive result of useradd call + */ +static void continue_rpc_useradd(struct composite_context *ctx) +{ + struct composite_context *c; + struct create_user_state *s; + struct monitor_msg msg; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct create_user_state); + + /* receive result of the call */ + c->status = libnet_rpc_useradd_recv(ctx, c, &s->user_add); + if (!composite_is_ok(c)) return; + + /* send monitor message */ + if (s->monitor_fn) s->monitor_fn(&msg); + + /* we're done */ + composite_done(c); +} + + +/** + * Receive result of CreateUser call + * + * @param c composite context returned by send request routine + * @param mem_ctx memory context of this call + * @param r pointer to a structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_CreateUser_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_CreateUser *r) +{ + NTSTATUS status; + + r->out.error_string = NULL; + + /* wait for result of async request and check status code */ + status = composite_wait(c); + if (!NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_strdup(mem_ctx, nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of CreateUser call + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to a structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_CreateUser(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_CreateUser *r) +{ + struct composite_context *c; + + c = libnet_CreateUser_send(ctx, mem_ctx, r, NULL); + return libnet_CreateUser_recv(c, mem_ctx, r); +} + + +struct delete_user_state { + struct libnet_DeleteUser r; + struct libnet_context *ctx; + struct libnet_DomainOpen domain_open; + struct libnet_rpc_userdel user_del; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_rpc_userdel(struct composite_context *ctx); +static void continue_domain_open_delete(struct composite_context *ctx); + + +/** + * Sends request to delete user account + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to structure containing arguments and result of this call + * @param monitor function pointer for receiving monitor messages + */ +struct composite_context *libnet_DeleteUser_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_DeleteUser *r, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct delete_user_state *s; + struct composite_context *delete_req; + bool prereq_met = false; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct delete_user_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* store arguments in state structure */ + s->ctx = ctx; + s->r = *r; + ZERO_STRUCT(s->r.out); + + /* prerequisite: make sure the domain is opened before proceeding */ + prereq_met = samr_domain_opened(ctx, c, s->r.in.domain_name, &c, &s->domain_open, + continue_domain_open_delete, monitor); + if (!prereq_met) return c; + + /* prepare arguments for userdel call */ + s->user_del.in.username = r->in.user_name; + s->user_del.in.domain_handle = ctx->samr.handle; + + /* send request */ + delete_req = libnet_rpc_userdel_send(s, s->ctx->event_ctx, + ctx->samr.samr_handle, + &s->user_del, monitor); + if (composite_nomem(delete_req, c)) return c; + + /* set the next stage */ + composite_continue(c, delete_req, continue_rpc_userdel, c); + return c; +} + + +/* + * Stage 0.5 (optional): receive result of domain open request + * and send useradd request + */ +static void continue_domain_open_delete(struct composite_context *ctx) +{ + struct composite_context *c; + struct delete_user_state *s; + struct composite_context *delete_req; + struct monitor_msg msg; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct delete_user_state); + + /* receive result of DomainOpen call */ + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domain_open); + if (!composite_is_ok(c)) return; + + /* send monitor message */ + if (s->monitor_fn) s->monitor_fn(&msg); + + /* prepare arguments for userdel call */ + s->user_del.in.username = s->r.in.user_name; + s->user_del.in.domain_handle = s->ctx->samr.handle; + + /* send request */ + delete_req = libnet_rpc_userdel_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->user_del, s->monitor_fn); + if (composite_nomem(delete_req, c)) return; + + /* set the next stage */ + composite_continue(c, delete_req, continue_rpc_userdel, c); +} + + +/* + * Stage 1: receive result of userdel call and finish the composite function + */ +static void continue_rpc_userdel(struct composite_context *ctx) +{ + struct composite_context *c; + struct delete_user_state *s; + struct monitor_msg msg; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct delete_user_state); + + /* receive result of userdel call */ + c->status = libnet_rpc_userdel_recv(ctx, c, &s->user_del); + if (!composite_is_ok(c)) return; + + /* send monitor message */ + if (s->monitor_fn) s->monitor_fn(&msg); + + /* we're done */ + composite_done(c); +} + + +/** + * Receives result of asynchronous DeleteUser call + * + * @param c composite context returned by async DeleteUser call + * @param mem_ctx memory context of this call + * @param r pointer to structure containing arguments and result + */ +NTSTATUS libnet_DeleteUser_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_DeleteUser *r) +{ + NTSTATUS status; + struct delete_user_state *s; + + r->out.error_string = NULL; + + /* wait for result of async request and check status code */ + status = composite_wait(c); + if (!NT_STATUS_IS_OK(status)) { + s = talloc_get_type_abort(c->private_data, struct delete_user_state); + r->out.error_string = talloc_steal(mem_ctx, s->r.out.error_string); + } + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of DeleteUser call + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to structure containing arguments and result + */ +NTSTATUS libnet_DeleteUser(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_DeleteUser *r) +{ + struct composite_context *c; + + c = libnet_DeleteUser_send(ctx, mem_ctx, r, NULL); + return libnet_DeleteUser_recv(c, mem_ctx, r); +} + + +struct modify_user_state { + struct libnet_ModifyUser r; + struct libnet_context *ctx; + struct libnet_DomainOpen domain_open; + struct libnet_rpc_userinfo user_info; + struct libnet_rpc_usermod user_mod; + + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_rpc_usermod(struct composite_context *ctx); +static void continue_domain_open_modify(struct composite_context *ctx); +static NTSTATUS set_user_changes(TALLOC_CTX *mem_ctx, struct usermod_change *mod, + struct libnet_rpc_userinfo *info, struct libnet_ModifyUser *r); +static void continue_rpc_userinfo(struct composite_context *ctx); + + +/** + * Sends request to modify user account + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to structure containing arguments and result of this call + * @param monitor function pointer for receiving monitor messages + */ +struct composite_context *libnet_ModifyUser_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_ModifyUser *r, + void (*monitor)(struct monitor_msg*)) +{ + const uint16_t level = 21; + struct composite_context *c; + struct modify_user_state *s; + struct composite_context *userinfo_req; + bool prereq_met = false; + + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct modify_user_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + s->ctx = ctx; + s->r = *r; + + prereq_met = samr_domain_opened(ctx, c, s->r.in.domain_name, &c, &s->domain_open, + continue_domain_open_modify, monitor); + if (!prereq_met) return c; + + s->user_info.in.username = r->in.user_name; + s->user_info.in.domain_handle = ctx->samr.handle; + s->user_info.in.level = level; + + userinfo_req = libnet_rpc_userinfo_send(s, s->ctx->event_ctx, + ctx->samr.samr_handle, + &s->user_info, monitor); + if (composite_nomem(userinfo_req, c)) return c; + + composite_continue(c, userinfo_req, continue_rpc_userinfo, c); + return c; +} + + +/* + * Stage 0.5 (optional): receive result of domain open request + * and send userinfo request + */ +static void continue_domain_open_modify(struct composite_context *ctx) +{ + const uint16_t level = 21; + struct composite_context *c; + struct modify_user_state *s; + struct composite_context *userinfo_req; + struct monitor_msg msg; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct modify_user_state); + + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domain_open); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) s->monitor_fn(&msg); + + s->user_info.in.domain_handle = s->ctx->samr.handle; + s->user_info.in.username = s->r.in.user_name; + s->user_info.in.level = level; + + userinfo_req = libnet_rpc_userinfo_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->user_info, s->monitor_fn); + if (composite_nomem(userinfo_req, c)) return; + + composite_continue(c, userinfo_req, continue_rpc_userinfo, c); +} + + +/* + * Stage 1: receive result of userinfo call, prepare user changes + * (set the fields a caller required to change) and send usermod request + */ +static void continue_rpc_userinfo(struct composite_context *ctx) +{ + struct composite_context *c; + struct modify_user_state *s; + struct composite_context *usermod_req; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct modify_user_state); + + c->status = libnet_rpc_userinfo_recv(ctx, c, &s->user_info); + if (!composite_is_ok(c)) return; + + s->user_mod.in.domain_handle = s->ctx->samr.handle; + s->user_mod.in.username = s->r.in.user_name; + + c->status = set_user_changes(c, &s->user_mod.in.change, &s->user_info, &s->r); + + usermod_req = libnet_rpc_usermod_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->user_mod, s->monitor_fn); + if (composite_nomem(usermod_req, c)) return; + + composite_continue(c, usermod_req, continue_rpc_usermod, c); +} + + +/* + * Prepare user changes: compare userinfo result to requested changes and + * set the field values and flags accordingly for user modify call + */ +static NTSTATUS set_user_changes(TALLOC_CTX *mem_ctx, struct usermod_change *mod, + struct libnet_rpc_userinfo *info, struct libnet_ModifyUser *r) +{ + struct samr_UserInfo21 *user; + + if (mod == NULL || info == NULL || r == NULL || info->in.level != 21) { + return NT_STATUS_INVALID_PARAMETER; + } + + user = &info->out.info.info21; + mod->fields = 0; /* reset flag field before setting individual flags */ + + /* account name change */ + SET_FIELD_LSA_STRING(r->in, user, mod, account_name, USERMOD_FIELD_ACCOUNT_NAME); + + /* full name change */ + SET_FIELD_LSA_STRING(r->in, user, mod, full_name, USERMOD_FIELD_FULL_NAME); + + /* description change */ + SET_FIELD_LSA_STRING(r->in, user, mod, description, USERMOD_FIELD_DESCRIPTION); + + /* comment change */ + SET_FIELD_LSA_STRING(r->in, user, mod, comment, USERMOD_FIELD_COMMENT); + + /* home directory change */ + SET_FIELD_LSA_STRING(r->in, user, mod, home_directory, USERMOD_FIELD_HOME_DIRECTORY); + + /* home drive change */ + SET_FIELD_LSA_STRING(r->in, user, mod, home_drive, USERMOD_FIELD_HOME_DRIVE); + + /* logon script change */ + SET_FIELD_LSA_STRING(r->in, user, mod, logon_script, USERMOD_FIELD_LOGON_SCRIPT); + + /* profile path change */ + SET_FIELD_LSA_STRING(r->in, user, mod, profile_path, USERMOD_FIELD_PROFILE_PATH); + + /* account expiry change */ + SET_FIELD_NTTIME(r->in, user, mod, acct_expiry, USERMOD_FIELD_ACCT_EXPIRY); + + /* account flags change */ + SET_FIELD_ACCT_FLAGS(r->in, user, mod, acct_flags, USERMOD_FIELD_ACCT_FLAGS); + + return NT_STATUS_OK; +} + + +/* + * Stage 2: receive result of usermod request and finish the composite function + */ +static void continue_rpc_usermod(struct composite_context *ctx) +{ + struct composite_context *c; + struct modify_user_state *s; + struct monitor_msg msg; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct modify_user_state); + + c->status = libnet_rpc_usermod_recv(ctx, c, &s->user_mod); + if (!composite_is_ok(c)) return; + + if (s->monitor_fn) s->monitor_fn(&msg); + composite_done(c); +} + + +/** + * Receive result of ModifyUser call + * + * @param c composite context returned by send request routine + * @param mem_ctx memory context of this call + * @param r pointer to a structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_ModifyUser_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_ModifyUser *r) +{ + NTSTATUS status = composite_wait(c); + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of ModifyUser call + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to a structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_ModifyUser(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_ModifyUser *r) +{ + struct composite_context *c; + + c = libnet_ModifyUser_send(ctx, mem_ctx, r, NULL); + return libnet_ModifyUser_recv(c, mem_ctx, r); +} + + +struct user_info_state { + struct libnet_context *ctx; + const char *domain_name; + enum libnet_UserInfo_level level; + const char *user_name; + const char *sid_string; + struct libnet_LookupName lookup; + struct libnet_DomainOpen domopen; + struct libnet_rpc_userinfo userinfo; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_name_found(struct composite_context *ctx); +static void continue_domain_open_info(struct composite_context *ctx); +static void continue_info_received(struct composite_context *ctx); + + +/** + * Sends request to get user account information + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to a structure containing arguments and results of this call + * @param monitor function pointer for receiving monitor messages + * @return compostite context of this request + */ +struct composite_context* libnet_UserInfo_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_UserInfo *r, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct user_info_state *s; + struct composite_context *lookup_req, *info_req; + bool prereq_met = false; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct user_info_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* store arguments in the state structure */ + s->monitor_fn = monitor; + s->ctx = ctx; + s->domain_name = talloc_strdup(c, r->in.domain_name); + s->level = r->in.level; + switch (s->level) { + case USER_INFO_BY_NAME: + s->user_name = talloc_strdup(c, r->in.data.user_name); + s->sid_string = NULL; + break; + case USER_INFO_BY_SID: + s->user_name = NULL; + s->sid_string = dom_sid_string(c, r->in.data.user_sid); + break; + } + + /* prerequisite: make sure the domain is opened */ + prereq_met = samr_domain_opened(ctx, c, s->domain_name, &c, &s->domopen, + continue_domain_open_info, monitor); + if (!prereq_met) return c; + + switch (s->level) { + case USER_INFO_BY_NAME: + /* prepare arguments for LookupName call */ + s->lookup.in.domain_name = s->domain_name; + s->lookup.in.name = s->user_name; + + /* send the request */ + lookup_req = libnet_LookupName_send(ctx, c, &s->lookup, + s->monitor_fn); + if (composite_nomem(lookup_req, c)) return c; + + /* set the next stage */ + composite_continue(c, lookup_req, continue_name_found, c); + break; + case USER_INFO_BY_SID: + /* prepare arguments for UserInfo call */ + s->userinfo.in.domain_handle = s->ctx->samr.handle; + s->userinfo.in.sid = s->sid_string; + s->userinfo.in.level = 21; + + /* send the request */ + info_req = libnet_rpc_userinfo_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->userinfo, + s->monitor_fn); + if (composite_nomem(info_req, c)) return c; + + /* set the next stage */ + composite_continue(c, info_req, continue_info_received, c); + break; + } + + return c; +} + + +/* + * Stage 0.5 (optional): receive result of domain open request + * and send LookupName request + */ +static void continue_domain_open_info(struct composite_context *ctx) +{ + struct composite_context *c; + struct user_info_state *s; + struct composite_context *lookup_req, *info_req; + struct monitor_msg msg; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct user_info_state); + + /* receive result of DomainOpen call */ + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domopen); + if (!composite_is_ok(c)) return; + + /* send monitor message */ + if (s->monitor_fn) s->monitor_fn(&msg); + + switch (s->level) { + case USER_INFO_BY_NAME: + /* prepare arguments for LookupName call */ + s->lookup.in.domain_name = s->domain_name; + s->lookup.in.name = s->user_name; + + /* send the request */ + lookup_req = libnet_LookupName_send(s->ctx, c, &s->lookup, s->monitor_fn); + if (composite_nomem(lookup_req, c)) return; + + /* set the next stage */ + composite_continue(c, lookup_req, continue_name_found, c); + break; + + case USER_INFO_BY_SID: + /* prepare arguments for UserInfo call */ + s->userinfo.in.domain_handle = s->ctx->samr.handle; + s->userinfo.in.sid = s->sid_string; + s->userinfo.in.level = 21; + + /* send the request */ + info_req = libnet_rpc_userinfo_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->userinfo, + s->monitor_fn); + if (composite_nomem(info_req, c)) return; + + /* set the next stage */ + composite_continue(c, info_req, continue_info_received, c); + break; + } +} + + +/* + * Stage 1: receive the name (if found) and send userinfo request + */ +static void continue_name_found(struct composite_context *ctx) +{ + struct composite_context *c; + struct user_info_state *s; + struct composite_context *info_req; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct user_info_state); + + /* receive result of LookupName call */ + c->status = libnet_LookupName_recv(ctx, c, &s->lookup); + if (!composite_is_ok(c)) return; + + /* we're only interested in user accounts this time */ + if (s->lookup.out.sid_type != SID_NAME_USER) { + composite_error(c, NT_STATUS_NO_SUCH_USER); + return; + } + + /* prepare arguments for UserInfo call */ + s->userinfo.in.domain_handle = s->ctx->samr.handle; + s->userinfo.in.sid = s->lookup.out.sidstr; + s->userinfo.in.level = 21; + + /* send the request */ + info_req = libnet_rpc_userinfo_send(s, s->ctx->event_ctx, + s->ctx->samr.samr_handle, + &s->userinfo, s->monitor_fn); + if (composite_nomem(info_req, c)) return; + + /* set the next stage */ + composite_continue(c, info_req, continue_info_received, c); +} + + +/* + * Stage 2: receive user account information and finish the composite function + */ +static void continue_info_received(struct composite_context *ctx) +{ + struct composite_context *c; + struct user_info_state *s; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct user_info_state); + + /* receive result of userinfo call */ + c->status = libnet_rpc_userinfo_recv(ctx, c, &s->userinfo); + if (!composite_is_ok(c)) return; + + composite_done(c); +} + + +/** + * Receive result of UserInfo call + * + * @param c composite context returned by send request routine + * @param mem_ctx memory context of this call + * @param r pointer to a structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_UserInfo_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_UserInfo *r) +{ + NTSTATUS status; + struct user_info_state *s; + + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status) && r != NULL) { + struct samr_UserInfo21 *info; + + s = talloc_get_type_abort(c->private_data, struct user_info_state); + info = &s->userinfo.out.info.info21; + + r->out.user_sid = dom_sid_add_rid(mem_ctx, s->ctx->samr.sid, info->rid); + r->out.primary_group_sid = dom_sid_add_rid(mem_ctx, s->ctx->samr.sid, info->primary_gid); + + /* string fields */ + r->out.account_name = talloc_steal(mem_ctx, info->account_name.string); + r->out.full_name = talloc_steal(mem_ctx, info->full_name.string); + r->out.description = talloc_steal(mem_ctx, info->description.string); + r->out.home_directory = talloc_steal(mem_ctx, info->home_directory.string); + r->out.home_drive = talloc_steal(mem_ctx, info->home_drive.string); + r->out.comment = talloc_steal(mem_ctx, info->comment.string); + r->out.logon_script = talloc_steal(mem_ctx, info->logon_script.string); + r->out.profile_path = talloc_steal(mem_ctx, info->profile_path.string); + + /* time fields (allocation) */ + r->out.acct_expiry = talloc(mem_ctx, struct timeval); + r->out.allow_password_change = talloc(mem_ctx, struct timeval); + r->out.force_password_change = talloc(mem_ctx, struct timeval); + r->out.last_logon = talloc(mem_ctx, struct timeval); + r->out.last_logoff = talloc(mem_ctx, struct timeval); + r->out.last_password_change = talloc(mem_ctx, struct timeval); + + /* time fields (converting) */ + nttime_to_timeval(r->out.acct_expiry, info->acct_expiry); + nttime_to_timeval(r->out.allow_password_change, info->allow_password_change); + nttime_to_timeval(r->out.force_password_change, info->force_password_change); + nttime_to_timeval(r->out.last_logon, info->last_logon); + nttime_to_timeval(r->out.last_logoff, info->last_logoff); + nttime_to_timeval(r->out.last_password_change, info->last_password_change); + + /* flag and number fields */ + r->out.acct_flags = info->acct_flags; + + r->out.error_string = talloc_strdup(mem_ctx, "Success"); + + } else { + r->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of UserInfo call + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to a structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_UserInfo(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + struct libnet_UserInfo *r) +{ + struct composite_context *c; + + c = libnet_UserInfo_send(ctx, mem_ctx, r, NULL); + return libnet_UserInfo_recv(c, mem_ctx, r); +} + + +struct userlist_state { + struct libnet_context *ctx; + const char *domain_name; + struct lsa_DomainInfo dominfo; + int page_size; + uint32_t resume_index; + struct userlist *users; + uint32_t count; + + struct libnet_DomainOpen domain_open; + struct lsa_QueryInfoPolicy query_domain; + struct samr_EnumDomainUsers user_list; + + void (*monitor_fn)(struct monitor_msg*); +}; + + +static void continue_lsa_domain_opened(struct composite_context *ctx); +static void continue_domain_queried(struct tevent_req *subreq); +static void continue_samr_domain_opened(struct composite_context *ctx); +static void continue_users_enumerated(struct tevent_req *subreq); + + +/** + * Sends request to list (enumerate) user accounts + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to structure containing arguments and results of this call + * @param monitor function pointer for receiving monitor messages + * @return compostite context of this request + */ +struct composite_context* libnet_UserList_send(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_UserList *r, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct userlist_state *s; + struct tevent_req *subreq; + bool prereq_met = false; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ctx->event_ctx); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct userlist_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* store the arguments in the state structure */ + s->ctx = ctx; + s->page_size = r->in.page_size; + s->resume_index = r->in.resume_index; + s->domain_name = talloc_strdup(c, r->in.domain_name); + s->monitor_fn = monitor; + + /* make sure we have lsa domain handle before doing anything */ + prereq_met = lsa_domain_opened(ctx, c, s->domain_name, &c, &s->domain_open, + continue_lsa_domain_opened, monitor); + if (!prereq_met) return c; + + /* prepare arguments of QueryDomainInfo call */ + s->query_domain.in.handle = &ctx->lsa.handle; + s->query_domain.in.level = LSA_POLICY_INFO_DOMAIN; + s->query_domain.out.info = talloc_zero(c, union lsa_PolicyInformation *); + if (composite_nomem(s->query_domain.out.info, c)) return c; + + /* send the request */ + subreq = dcerpc_lsa_QueryInfoPolicy_r_send(s, c->event_ctx, + ctx->lsa.pipe->binding_handle, + &s->query_domain); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_domain_queried, c); + return c; +} + + +/* + * Stage 0.5 (optional): receive lsa domain handle and send + * request to query domain info + */ +static void continue_lsa_domain_opened(struct composite_context *ctx) +{ + struct composite_context *c; + struct userlist_state *s; + struct tevent_req *subreq; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct userlist_state); + + /* receive lsa domain handle */ + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domain_open); + if (!composite_is_ok(c)) return; + + /* prepare arguments of QueryDomainInfo call */ + s->query_domain.in.handle = &s->ctx->lsa.handle; + s->query_domain.in.level = LSA_POLICY_INFO_DOMAIN; + s->query_domain.out.info = talloc_zero(c, union lsa_PolicyInformation *); + if (composite_nomem(s->query_domain.out.info, c)) return; + + /* send the request */ + subreq = dcerpc_lsa_QueryInfoPolicy_r_send(s, c->event_ctx, + s->ctx->lsa.pipe->binding_handle, + &s->query_domain); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_domain_queried, c); +} + + +/* + * Stage 1: receive domain info and request to enum users, + * provided a valid samr handle is opened + */ +static void continue_domain_queried(struct tevent_req *subreq) +{ + struct composite_context *c; + struct userlist_state *s; + bool prereq_met = false; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct userlist_state); + + /* receive result of rpc request */ + c->status = dcerpc_lsa_QueryInfoPolicy_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* get the returned domain info */ + s->dominfo = (*s->query_domain.out.info)->domain; + + /* make sure we have samr domain handle before continuing */ + prereq_met = samr_domain_opened(s->ctx, c, s->domain_name, &c, &s->domain_open, + continue_samr_domain_opened, s->monitor_fn); + if (!prereq_met) return; + + /* prepare arguments of EnumDomainUsers call */ + s->user_list.in.domain_handle = &s->ctx->samr.handle; + s->user_list.in.max_size = s->page_size; + s->user_list.in.resume_handle = &s->resume_index; + s->user_list.in.acct_flags = ACB_NORMAL; + s->user_list.out.resume_handle = &s->resume_index; + s->user_list.out.num_entries = talloc(s, uint32_t); + if (composite_nomem(s->user_list.out.num_entries, c)) return; + s->user_list.out.sam = talloc(s, struct samr_SamArray *); + if (composite_nomem(s->user_list.out.sam, c)) return; + + /* send the request */ + subreq = dcerpc_samr_EnumDomainUsers_r_send(s, c->event_ctx, + s->ctx->samr.pipe->binding_handle, + &s->user_list); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_users_enumerated, c); +} + + +/* + * Stage 1.5 (optional): receive samr domain handle + * and request to enumerate accounts + */ +static void continue_samr_domain_opened(struct composite_context *ctx) +{ + struct composite_context *c; + struct userlist_state *s; + struct tevent_req *subreq; + + c = talloc_get_type_abort(ctx->async.private_data, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct userlist_state); + + /* receive samr domain handle */ + c->status = libnet_DomainOpen_recv(ctx, s->ctx, c, &s->domain_open); + if (!composite_is_ok(c)) return; + + /* prepare arguments of EnumDomainUsers call */ + s->user_list.in.domain_handle = &s->ctx->samr.handle; + s->user_list.in.max_size = s->page_size; + s->user_list.in.resume_handle = &s->resume_index; + s->user_list.in.acct_flags = ACB_NORMAL; + s->user_list.out.resume_handle = &s->resume_index; + s->user_list.out.sam = talloc(s, struct samr_SamArray *); + if (composite_nomem(s->user_list.out.sam, c)) return; + s->user_list.out.num_entries = talloc(s, uint32_t); + if (composite_nomem(s->user_list.out.num_entries, c)) return; + + /* send the request */ + subreq = dcerpc_samr_EnumDomainUsers_r_send(s, c->event_ctx, + s->ctx->samr.pipe->binding_handle, + &s->user_list); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_users_enumerated, c); +} + + +/* + * Stage 2: receive enumerated users and their rids + */ +static void continue_users_enumerated(struct tevent_req *subreq) +{ + struct composite_context *c; + struct userlist_state *s; + uint32_t i; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct userlist_state); + + /* receive result of rpc request */ + c->status = dcerpc_samr_EnumDomainUsers_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* get the actual status of the rpc call result + (instead of rpc layer status) */ + c->status = s->user_list.out.result; + + /* we're interested in status "ok" as well as two + enum-specific status codes */ + if (NT_STATUS_IS_OK(c->status) || + NT_STATUS_EQUAL(c->status, STATUS_MORE_ENTRIES) || + NT_STATUS_EQUAL(c->status, NT_STATUS_NO_MORE_ENTRIES)) { + + /* get enumerated accounts counter and resume handle (the latter allows + making subsequent call to continue enumeration) */ + s->resume_index = *s->user_list.out.resume_handle; + s->count = *s->user_list.out.num_entries; + + /* prepare returned user accounts array */ + s->users = talloc_array(c, struct userlist, (*s->user_list.out.sam)->count); + if (composite_nomem(s->users, c)) return; + + for (i = 0; i < (*s->user_list.out.sam)->count; i++) { + struct dom_sid *user_sid; + struct samr_SamEntry *entry = &(*s->user_list.out.sam)->entries[i]; + struct dom_sid *domain_sid = (*s->query_domain.out.info)->domain.sid; + + /* construct user sid from returned rid and queried domain sid */ + user_sid = dom_sid_add_rid(c, domain_sid, entry->idx); + if (composite_nomem(user_sid, c)) return; + + /* username */ + s->users[i].username = talloc_strdup(s->users, entry->name.string); + if (composite_nomem(s->users[i].username, c)) return; + + /* sid string */ + s->users[i].sid = dom_sid_string(s->users, user_sid); + if (composite_nomem(s->users[i].sid, c)) return; + } + + /* that's it */ + composite_done(c); + return; + + } else { + /* something went wrong */ + composite_error(c, c->status); + return; + } +} + + +/** + * Receive result of UserList call + * + * @param c composite context returned by send request routine + * @param mem_ctx memory context of this call + * @param r pointer to structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_UserList_recv(struct composite_context* c, TALLOC_CTX *mem_ctx, + struct libnet_UserList *r) +{ + NTSTATUS status; + struct userlist_state *s; + + if (c == NULL || mem_ctx == NULL || r == NULL) { + talloc_free(c); + return NT_STATUS_INVALID_PARAMETER; + } + + status = composite_wait(c); + if (NT_STATUS_IS_OK(status) || + NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES) || + NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) { + + s = talloc_get_type_abort(c->private_data, struct userlist_state); + + /* get results from composite context */ + r->out.count = s->count; + r->out.resume_index = s->resume_index; + r->out.users = talloc_steal(mem_ctx, s->users); + + if (NT_STATUS_IS_OK(status)) { + r->out.error_string = talloc_strdup(mem_ctx, "Success"); + } else { + /* success, but we're not done yet */ + r->out.error_string = talloc_asprintf(mem_ctx, "Success (status: %s)", + nt_errstr(status)); + } + + } else { + r->out.error_string = talloc_asprintf(mem_ctx, "Error: %s", nt_errstr(status)); + } + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of UserList call + * + * @param ctx initialised libnet context + * @param mem_ctx memory context of this call + * @param r pointer to structure containing arguments and result of this call + * @return nt status + */ +NTSTATUS libnet_UserList(struct libnet_context *ctx, + TALLOC_CTX *mem_ctx, + struct libnet_UserList *r) +{ + struct composite_context *c; + + c = libnet_UserList_send(ctx, mem_ctx, r, NULL); + return libnet_UserList_recv(c, mem_ctx, r); +} diff --git a/source4/libnet/libnet_user.h b/source4/libnet/libnet_user.h new file mode 100644 index 0000000..8203d14 --- /dev/null +++ b/source4/libnet/libnet_user.h @@ -0,0 +1,156 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak <mimir@samba.org> 2005 + + 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 libnet_CreateUser { + struct { + const char *user_name; + const char *domain_name; + } in; + struct { + const char *error_string; + } out; +}; + + +struct libnet_DeleteUser { + struct { + const char *user_name; + const char *domain_name; + } in; + struct { + const char *error_string; + } out; +}; + + +struct libnet_ModifyUser { + struct { + const char *user_name; + const char *domain_name; + + const char *account_name; + const char *full_name; + const char *description; + const char *home_directory; + const char *home_drive; + const char *comment; + const char *logon_script; + const char *profile_path; + struct timeval *acct_expiry; + struct timeval *allow_password_change; + struct timeval *force_password_change; + struct timeval *last_password_change; + uint32_t acct_flags; + } in; + struct { + const char *error_string; + } out; +}; + + +#define SET_FIELD_LSA_STRING(new, current, mod, field, flag) \ + if (new.field != NULL && \ + !strequal_m(current->field.string, new.field)) { \ + \ + mod->field = talloc_strdup(mem_ctx, new.field); \ + if (mod->field == NULL) return NT_STATUS_NO_MEMORY; \ + \ + mod->fields |= flag; \ + } + +#define SET_FIELD_NTTIME(new, current, mod, field, flag) \ + if (new.field != 0) { \ + NTTIME newval = timeval_to_nttime(new.field); \ + if (newval != current->field) { \ + mod->field = talloc_memdup(mem_ctx, new.field, sizeof(*new.field)); \ + if (mod->field == NULL) return NT_STATUS_NO_MEMORY; \ + mod->fields |= flag; \ + } \ + } + +#define SET_FIELD_UINT32(new, current, mod, field, flag) \ + if (current->field != new.field) { \ + mod->field = new.field; \ + mod->fields |= flag; \ + } + +#define SET_FIELD_ACCT_FLAGS(new, current, mod, field, flag) \ + if (new.field) { \ + if (current->field != new.field) { \ + mod->field = new.field; \ + mod->fields |= flag; \ + } \ + } + +enum libnet_UserInfo_level { + USER_INFO_BY_NAME=0, + USER_INFO_BY_SID +}; + +struct libnet_UserInfo { + struct { + const char *domain_name; + enum libnet_UserInfo_level level; + union { + const char *user_name; + const struct dom_sid *user_sid; + } data; + } in; + struct { + struct dom_sid *user_sid; + struct dom_sid *primary_group_sid; + const char *account_name; + const char *full_name; + const char *description; + const char *home_directory; + const char *home_drive; + const char *comment; + const char *logon_script; + const char *profile_path; + struct timeval *acct_expiry; + struct timeval *allow_password_change; + struct timeval *force_password_change; + struct timeval *last_logon; + struct timeval *last_logoff; + struct timeval *last_password_change; + uint32_t acct_flags; + const char *error_string; + } out; +}; + + +struct libnet_UserList { + struct { + const char *domain_name; + int page_size; + uint32_t resume_index; + } in; + struct { + int count; + uint32_t resume_index; + + struct userlist { + const char *sid; + const char *username; + } *users; + + const char *error_string; + } out; +}; diff --git a/source4/libnet/libnet_vampire.c b/source4/libnet/libnet_vampire.c new file mode 100644 index 0000000..73adf67 --- /dev/null +++ b/source4/libnet/libnet_vampire.c @@ -0,0 +1,838 @@ +/* + Unix SMB/CIFS implementation. + + Extract the user/system database from a remote server + + Copyright (C) Stefan Metzmacher 2004-2006 + Copyright (C) Brad Henry 2005 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-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 "lib/events/events.h" +#include "dsdb/samdb/samdb.h" +#include "../lib/util/dlinklist.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "librpc/ndr/libndr.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "system/time.h" +#include "ldb_wrap.h" +#include "auth/auth.h" +#include "auth/credentials/credentials.h" +#include "param/param.h" +#include "param/provision.h" +#include "libcli/security/security.h" +#include "dsdb/common/util.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +/* +List of tasks vampire.py must perform: +- Domain Join + - but don't write the secrets.ldb + - results for this should be enough to handle the provision +- if vampire method is samsync + - Provision using these results + - do we still want to support this NT4 technology? +- Start samsync with libnet code + - provision in the callback +- Write out the secrets database, using the code from libnet_Join + +*/ +struct libnet_vampire_cb_state { + const char *netbios_name; + const char *domain_name; + const char *realm; + struct cli_credentials *machine_account; + + /* Schema loaded from local LDIF files */ + struct dsdb_schema *provision_schema; + + /* 1st pass, with some OIDs/attribute names/class names not + * converted, because we may not know them yet */ + struct dsdb_schema *self_made_schema; + + /* prefixMap in LDB format, from the remote DRS server */ + DATA_BLOB prefixmap_blob; + const struct dsdb_schema *schema; + + struct ldb_context *ldb; + + struct { + uint32_t object_count; + struct drsuapi_DsReplicaObjectListItemEx *first_object; + struct drsuapi_DsReplicaObjectListItemEx *last_object; + } schema_part; + + const char *targetdir; + + struct loadparm_context *lp_ctx; + struct tevent_context *event_ctx; + unsigned total_objects; + unsigned total_links; + char *last_partition; + const char *server_dn_str; +}; + +/* initialise a state structure ready for replication of chunks */ +void *libnet_vampire_replicate_init(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct loadparm_context *lp_ctx) +{ + struct libnet_vampire_cb_state *s = talloc_zero(mem_ctx, struct libnet_vampire_cb_state); + if (!s) { + return NULL; + } + + s->ldb = samdb; + s->lp_ctx = lp_ctx; + s->provision_schema = dsdb_get_schema(s->ldb, s); + s->schema = s->provision_schema; + s->netbios_name = lpcfg_netbios_name(lp_ctx); + s->domain_name = lpcfg_workgroup(lp_ctx); + s->realm = lpcfg_realm(lp_ctx); + + return s; +} + +/* Caller is expected to keep supplied pointers around for the lifetime of the structure */ +void *libnet_vampire_cb_state_init(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, struct tevent_context *event_ctx, + const char *netbios_name, const char *domain_name, const char *realm, + const char *targetdir) +{ + struct libnet_vampire_cb_state *s = talloc_zero(mem_ctx, struct libnet_vampire_cb_state); + if (!s) { + return NULL; + } + + s->lp_ctx = lp_ctx; + s->event_ctx = event_ctx; + s->netbios_name = netbios_name; + s->domain_name = domain_name; + s->realm = realm; + s->targetdir = targetdir; + return s; +} + +struct ldb_context *libnet_vampire_cb_ldb(struct libnet_vampire_cb_state *state) +{ + state = talloc_get_type_abort(state, struct libnet_vampire_cb_state); + return state->ldb; +} + +struct loadparm_context *libnet_vampire_cb_lp_ctx(struct libnet_vampire_cb_state *state) +{ + state = talloc_get_type_abort(state, struct libnet_vampire_cb_state); + return state->lp_ctx; +} + +NTSTATUS libnet_vampire_cb_prepare_db(void *private_data, + const struct libnet_BecomeDC_PrepareDB *p) +{ + struct libnet_vampire_cb_state *s = talloc_get_type(private_data, struct libnet_vampire_cb_state); + struct provision_settings settings; + struct provision_result result; + NTSTATUS status; + + ZERO_STRUCT(settings); + settings.site_name = p->dest_dsa->site_name; + settings.root_dn_str = p->forest->root_dn_str; + settings.domain_dn_str = p->domain->dn_str; + settings.config_dn_str = p->forest->config_dn_str; + settings.schema_dn_str = p->forest->schema_dn_str; + settings.netbios_name = p->dest_dsa->netbios_name; + settings.realm = s->realm; + settings.domain = s->domain_name; + settings.server_dn_str = p->dest_dsa->server_dn_str; + settings.machine_password = generate_random_machine_password(s, 120, 120); + settings.targetdir = s->targetdir; + settings.use_ntvfs = true; + status = provision_bare(s, s->lp_ctx, &settings, &result); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + s->ldb = talloc_steal(s, result.samdb); + s->lp_ctx = talloc_reparent(talloc_parent(result.lp_ctx), s, result.lp_ctx); + s->provision_schema = dsdb_get_schema(s->ldb, s); + s->server_dn_str = talloc_steal(s, p->dest_dsa->server_dn_str); + + /* wrap the entire vapire operation in a transaction. This + isn't just cosmetic - we use this to ensure that linked + attribute back links are added at the end by relying on a + transaction commit hook in the linked attributes module. We + need to do this as the order of objects coming from the + server is not sufficiently deterministic to know that the + record that a backlink needs to be created in has itself + been created before the object containing the forward link + has come over the wire */ + if (ldb_transaction_start(s->ldb) != LDB_SUCCESS) { + return NT_STATUS_FOOBAR; + } + + return NT_STATUS_OK; + + +} + +NTSTATUS libnet_vampire_cb_check_options(void *private_data, + const struct libnet_BecomeDC_CheckOptions *o) +{ + struct libnet_vampire_cb_state *s = talloc_get_type(private_data, struct libnet_vampire_cb_state); + + DEBUG(0,("Become DC [%s] of Domain[%s]/[%s]\n", + s->netbios_name, + o->domain->netbios_name, o->domain->dns_name)); + + DEBUG(0,("Promotion Partner is Server[%s] from Site[%s]\n", + o->source_dsa->dns_name, o->source_dsa->site_name)); + + DEBUG(0,("Options:crossRef behavior_version[%u]\n" + "\tschema object_version[%u]\n" + "\tdomain behavior_version[%u]\n" + "\tdomain w2k3_update_revision[%u]\n", + o->forest->crossref_behavior_version, + o->forest->schema_object_version, + o->domain->behavior_version, + o->domain->w2k3_update_revision)); + + return NT_STATUS_OK; +} + +static WERROR libnet_vampire_cb_apply_schema(struct libnet_vampire_cb_state *s, + const struct libnet_BecomeDC_StoreChunk *c) +{ + WERROR status; + struct dsdb_schema_prefixmap *pfm_remote; + const struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr; + struct dsdb_schema *provision_schema; + uint32_t object_count = 0; + struct drsuapi_DsReplicaObjectListItemEx *first_object; + uint32_t linked_attributes_count; + struct drsuapi_DsReplicaLinkedAttribute *linked_attributes; + const struct drsuapi_DsReplicaCursor2CtrEx *uptodateness_vector; + struct dsdb_extended_replicated_objects *schema_objs; + struct repsFromTo1 *s_dsa; + char *tmp_dns_name; + struct ldb_context *schema_ldb; + struct ldb_dn *partition_dn; + struct ldb_message *msg; + struct ldb_message_element *prefixMap_el; + uint32_t i; + int ret; + bool ok; + uint64_t seq_num = 0; + uint32_t cycle_before_switching; + + DEBUG(0,("Analyze and apply schema objects\n")); + + s_dsa = talloc_zero(s, struct repsFromTo1); + if (s_dsa == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + s_dsa->other_info = talloc(s_dsa, struct repsFromTo1OtherInfo); + if (s_dsa->other_info == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + switch (c->ctr_level) { + case 1: + mapping_ctr = &c->ctr1->mapping_ctr; + object_count = s->schema_part.object_count; + first_object = s->schema_part.first_object; + linked_attributes_count = 0; + linked_attributes = NULL; + s_dsa->highwatermark = c->ctr1->new_highwatermark; + s_dsa->source_dsa_obj_guid = c->ctr1->source_dsa_guid; + s_dsa->source_dsa_invocation_id = c->ctr1->source_dsa_invocation_id; + uptodateness_vector = NULL; /* TODO: map it */ + break; + case 6: + mapping_ctr = &c->ctr6->mapping_ctr; + object_count = s->schema_part.object_count; + first_object = s->schema_part.first_object; + linked_attributes_count = c->ctr6->linked_attributes_count; + linked_attributes = c->ctr6->linked_attributes; + s_dsa->highwatermark = c->ctr6->new_highwatermark; + s_dsa->source_dsa_obj_guid = c->ctr6->source_dsa_guid; + s_dsa->source_dsa_invocation_id = c->ctr6->source_dsa_invocation_id; + uptodateness_vector = c->ctr6->uptodateness_vector; + break; + default: + return WERR_INVALID_PARAMETER; + } + /* We must set these up to ensure the replMetaData is written + * correctly, before our NTDS Settings entry is replicated */ + ok = samdb_set_ntds_invocation_id(s->ldb, &c->dest_dsa->invocation_id); + if (!ok) { + DEBUG(0,("Failed to set cached ntds invocationId\n")); + return WERR_INTERNAL_ERROR; + } + ok = samdb_set_ntds_objectGUID(s->ldb, &c->dest_dsa->ntds_guid); + if (!ok) { + DEBUG(0,("Failed to set cached ntds objectGUID\n")); + return WERR_INTERNAL_ERROR; + } + + status = dsdb_schema_pfm_from_drsuapi_pfm(mapping_ctr, true, + s, &pfm_remote, NULL); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0,(__location__ ": Failed to decode remote prefixMap: %s", + win_errstr(status))); + return status; + } + + s_dsa->replica_flags = DRSUAPI_DRS_WRIT_REP + | DRSUAPI_DRS_INIT_SYNC + | DRSUAPI_DRS_PER_SYNC; + memset(s_dsa->schedule, 0x11, sizeof(s_dsa->schedule)); + + tmp_dns_name = GUID_string(s_dsa->other_info, &s_dsa->source_dsa_obj_guid); + if (tmp_dns_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + tmp_dns_name = talloc_asprintf_append_buffer(tmp_dns_name, "._msdcs.%s", c->forest->dns_name); + if (tmp_dns_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + s_dsa->other_info->dns_name = tmp_dns_name; + + if (s->self_made_schema == NULL) { + DEBUG(0,("libnet_vampire_cb_apply_schema: called with out self_made_schema\n")); + return WERR_INTERNAL_ERROR; + } + + schema_ldb = provision_get_schema(s, s->lp_ctx, + c->forest->schema_dn_str, + &s->prefixmap_blob); + if (!schema_ldb) { + DEBUG(0,("Failed to re-load from local provision using remote prefixMap. " + "Will continue with local prefixMap\n")); + provision_schema = dsdb_get_schema(s->ldb, s); + } else { + provision_schema = dsdb_get_schema(schema_ldb, s); + ret = dsdb_reference_schema(s->ldb, provision_schema, SCHEMA_MEMORY_ONLY); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to attach schema from local provision using remote prefixMap.")); + return WERR_INTERNAL_ERROR; + } + talloc_unlink(s, schema_ldb); + } + + cycle_before_switching = lpcfg_parm_long(s->lp_ctx, NULL, + "become dc", + "schema convert retrial", 1); + + provision_schema->resolving_in_progress = true; + s->self_made_schema->resolving_in_progress = true; + + status = dsdb_repl_resolve_working_schema(s->ldb, + pfm_remote, + cycle_before_switching, + provision_schema, + s->self_made_schema, + object_count, + first_object); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0, ("%s: dsdb_repl_resolve_working_schema() failed: %s", + __location__, win_errstr(status))); + return status; + } + + /* free temp objects for 1st conversion phase */ + talloc_unlink(s, provision_schema); + + s->self_made_schema->resolving_in_progress = false; + + /* + * attach the schema we just brought over DRS to the ldb, + * so we can use it in dsdb_convert_object_ex below + */ + ret = dsdb_set_schema(s->ldb, s->self_made_schema, SCHEMA_WRITE); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to attach working schema from DRS.\n")); + return WERR_INTERNAL_ERROR; + } + + /* we don't want to access the self made schema anymore */ + s->schema = s->self_made_schema; + s->self_made_schema = NULL; + + partition_dn = ldb_dn_new(s, s->ldb, c->partition->nc.dn); + if (partition_dn == NULL) { + DEBUG(0,("Failed to parse partition DN from DRS.\n")); + return WERR_INVALID_PARAMETER; + } + + /* Now convert the schema elements again, using the schema we finalised, ready to actually import */ + status = dsdb_replicated_objects_convert(s->ldb, + s->schema, + partition_dn, + mapping_ctr, + object_count, + first_object, + linked_attributes_count, + linked_attributes, + s_dsa, + uptodateness_vector, + c->gensec_skey, + 0, + s, &schema_objs); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0,("Failed to convert objects when trying to import over DRS (2nd pass, to store remote schema): %s\n", win_errstr(status))); + return status; + } + + if (lpcfg_parm_bool(s->lp_ctx, NULL, "become dc", "dump objects", false)) { + for (i=0; i < schema_objs->num_objects; i++) { + struct ldb_ldif ldif; + fprintf(stdout, "#\n"); + ldif.changetype = LDB_CHANGETYPE_NONE; + ldif.msg = schema_objs->objects[i].msg; + ldb_ldif_write_file(s->ldb, stdout, &ldif); + NDR_PRINT_DEBUG(replPropertyMetaDataBlob, schema_objs->objects[i].meta_data); + } + } + + status = dsdb_replicated_objects_commit(s->ldb, NULL, schema_objs, &seq_num); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0,("Failed to commit objects: %s\n", win_errstr(status))); + return status; + } + + msg = ldb_msg_new(schema_objs); + if (msg == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + msg->dn = schema_objs->partition_dn; + + /* We must ensure a prefixMap has been written. Unlike other + * attributes (including schemaInfo), it is not replicated in + * the normal replication stream. We can use the one from + * s->prefixmap_blob because we operate with one, unchanging + * prefixMap for this entire operation. */ + ret = ldb_msg_add_value(msg, "prefixMap", &s->prefixmap_blob, &prefixMap_el); + if (ret != LDB_SUCCESS) { + return WERR_NOT_ENOUGH_MEMORY; + } + /* We want to know if a prefixMap was written already, as it + * would mean that the above comment was not true, and we have + * somehow updated the prefixMap during this transaction */ + prefixMap_el->flags = LDB_FLAG_MOD_ADD; + + ret = dsdb_modify(s->ldb, msg, DSDB_FLAG_AS_SYSTEM); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to add prefixMap: %s\n", ldb_errstring(s->ldb))); + return WERR_INTERNAL_ERROR; + } + + talloc_free(s_dsa); + talloc_free(schema_objs); + + s->schema = dsdb_get_schema(s->ldb, s); + if (!s->schema) { + DEBUG(0,("Failed to get loaded dsdb_schema\n")); + return WERR_INTERNAL_ERROR; + } + + return WERR_OK; +} + +WERROR libnet_vampire_cb_schema_chunk(void *private_data, + const struct libnet_BecomeDC_StoreChunk *c) +{ + struct libnet_vampire_cb_state *s = talloc_get_type(private_data, struct libnet_vampire_cb_state); + WERROR werr; + const struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr; + uint32_t nc_object_count; + uint32_t nc_total_received = 0; + uint32_t object_count; + struct drsuapi_DsReplicaObjectListItemEx *first_object; + struct drsuapi_DsReplicaObjectListItemEx *cur; + uint32_t nc_linked_attributes_count; + uint32_t linked_attributes_count; + + switch (c->ctr_level) { + case 1: + mapping_ctr = &c->ctr1->mapping_ctr; + nc_object_count = c->ctr1->extended_ret; /* maybe w2k send this unexpected? */ + object_count = c->ctr1->object_count; + first_object = c->ctr1->first_object; + nc_linked_attributes_count = 0; + linked_attributes_count = 0; + break; + case 6: + mapping_ctr = &c->ctr6->mapping_ctr; + nc_object_count = c->ctr6->nc_object_count; + object_count = c->ctr6->object_count; + first_object = c->ctr6->first_object; + nc_linked_attributes_count = c->ctr6->nc_linked_attributes_count; + linked_attributes_count = c->ctr6->linked_attributes_count; + break; + default: + return WERR_INVALID_PARAMETER; + } + + if (!s->schema_part.first_object) { + nc_total_received = object_count; + } else { + nc_total_received = s->schema_part.object_count + object_count; + } + if (nc_object_count) { + DEBUG(0,("Schema-DN[%s] objects[%u/%u] linked_values[%u/%u]\n", + c->partition->nc.dn, nc_total_received, nc_object_count, + linked_attributes_count, nc_linked_attributes_count)); + } else { + DEBUG(0,("Schema-DN[%s] objects[%u] linked_values[%u]\n", + c->partition->nc.dn, nc_total_received, linked_attributes_count)); + } + + if (!s->self_made_schema) { + struct drsuapi_DsReplicaOIDMapping_Ctr mapping_ctr_without_schema_info; + /* Put the DRS prefixmap aside for the schema we are + * about to load in the provision, and into the one we + * are making with the help of DRS */ + + mapping_ctr_without_schema_info = *mapping_ctr; + + /* This strips off the 0xFF schema info from the end, + * because we don't want it in the blob */ + if (mapping_ctr_without_schema_info.num_mappings > 0) { + mapping_ctr_without_schema_info.num_mappings--; + } + werr = dsdb_get_drsuapi_prefixmap_as_blob(&mapping_ctr_without_schema_info, s, &s->prefixmap_blob); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + /* Set up two manually-constructed schema - the local + * schema from the provision will be used to build + * one, which will then in turn be used to build the + * other. */ + s->self_made_schema = dsdb_new_schema(s); + if (s->self_made_schema == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + werr = dsdb_load_prefixmap_from_drsuapi(s->self_made_schema, mapping_ctr); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } else { + werr = dsdb_schema_pfm_contains_drsuapi_pfm(s->self_made_schema->prefixmap, mapping_ctr); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } + + if (!s->schema_part.first_object) { + s->schema_part.object_count = object_count; + s->schema_part.first_object = talloc_steal(s, first_object); + } else { + s->schema_part.object_count += object_count; + s->schema_part.last_object->next_object = talloc_steal(s->schema_part.last_object, + first_object); + } + if (first_object != NULL) { + for (cur = first_object; cur->next_object; cur = cur->next_object) {} + } else { + cur = first_object; + } + + s->schema_part.last_object = cur; + + if (!c->partition->more_data) { + return libnet_vampire_cb_apply_schema(s, c); + } + + return WERR_OK; +} + +WERROR libnet_vampire_cb_store_chunk(void *private_data, + const struct libnet_BecomeDC_StoreChunk *c) +{ + struct libnet_vampire_cb_state *s = talloc_get_type(private_data, struct libnet_vampire_cb_state); + WERROR status; + struct dsdb_schema *schema; + const struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr; + uint32_t nc_object_count; + uint32_t object_count; + struct drsuapi_DsReplicaObjectListItemEx *first_object; + uint32_t nc_linked_attributes_count; + uint32_t linked_attributes_count; + struct drsuapi_DsReplicaLinkedAttribute *linked_attributes; + const struct drsuapi_DsReplicaCursor2CtrEx *uptodateness_vector; + struct dsdb_extended_replicated_objects *objs; + uint32_t req_replica_flags; + uint32_t dsdb_repl_flags = 0; + struct repsFromTo1 *s_dsa; + char *tmp_dns_name; + uint32_t i; + uint64_t seq_num = 0; + bool is_exop = false; + struct ldb_dn *partition_dn = NULL; + struct ldb_dn *nc_root = NULL; + + s_dsa = talloc_zero(s, struct repsFromTo1); + if (s_dsa == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + s_dsa->other_info = talloc(s_dsa, struct repsFromTo1OtherInfo); + if (s_dsa->other_info == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + switch (c->ctr_level) { + case 1: + mapping_ctr = &c->ctr1->mapping_ctr; + nc_object_count = c->ctr1->extended_ret; /* maybe w2k send this unexpected? */ + object_count = c->ctr1->object_count; + first_object = c->ctr1->first_object; + nc_linked_attributes_count = 0; + linked_attributes_count = 0; + linked_attributes = NULL; + s_dsa->highwatermark = c->ctr1->new_highwatermark; + s_dsa->source_dsa_obj_guid = c->ctr1->source_dsa_guid; + s_dsa->source_dsa_invocation_id = c->ctr1->source_dsa_invocation_id; + uptodateness_vector = NULL; /* TODO: map it */ + break; + case 6: + mapping_ctr = &c->ctr6->mapping_ctr; + nc_object_count = c->ctr6->nc_object_count; + object_count = c->ctr6->object_count; + first_object = c->ctr6->first_object; + nc_linked_attributes_count = c->ctr6->nc_linked_attributes_count; + linked_attributes_count = c->ctr6->linked_attributes_count; + linked_attributes = c->ctr6->linked_attributes; + s_dsa->highwatermark = c->ctr6->new_highwatermark; + s_dsa->source_dsa_obj_guid = c->ctr6->source_dsa_guid; + s_dsa->source_dsa_invocation_id = c->ctr6->source_dsa_invocation_id; + uptodateness_vector = c->ctr6->uptodateness_vector; + break; + default: + return WERR_INVALID_PARAMETER; + } + + switch (c->req_level) { + case 0: + /* none */ + req_replica_flags = 0; + break; + case 5: + if (c->req5->extended_op != DRSUAPI_EXOP_NONE) { + is_exop = true; + } + req_replica_flags = c->req5->replica_flags; + break; + case 8: + if (c->req8->extended_op != DRSUAPI_EXOP_NONE) { + is_exop = true; + } + req_replica_flags = c->req8->replica_flags; + break; + case 10: + if (c->req10->extended_op != DRSUAPI_EXOP_NONE) { + is_exop = true; + } + req_replica_flags = c->req10->replica_flags; + + if (c->req10->more_flags & DRSUAPI_DRS_GET_TGT) { + dsdb_repl_flags |= DSDB_REPL_FLAG_TARGETS_UPTODATE; + } + break; + default: + return WERR_INVALID_PARAMETER; + } + + /* + * If the peer DC doesn't support GET_TGT (req v10), then the link + * targets are as up-to-date as they're ever gonna be. (Without this, + * cases where we'd normally retry with GET_TGT cause the join to fail) + */ + if (c->req_level < 10) { + dsdb_repl_flags |= DSDB_REPL_FLAG_TARGETS_UPTODATE; + } + + if (req_replica_flags & DRSUAPI_DRS_CRITICAL_ONLY || is_exop) { + /* + * If we only replicate the critical objects, or this + * is an exop we should not remember what we already + * got, as it is incomplete. + */ + ZERO_STRUCT(s_dsa->highwatermark); + uptodateness_vector = NULL; + dsdb_repl_flags |= DSDB_REPL_FLAG_OBJECT_SUBSET; + } + + /* TODO: avoid hardcoded flags */ + s_dsa->replica_flags = DRSUAPI_DRS_WRIT_REP + | DRSUAPI_DRS_INIT_SYNC + | DRSUAPI_DRS_PER_SYNC; + memset(s_dsa->schedule, 0x11, sizeof(s_dsa->schedule)); + + tmp_dns_name = GUID_string(s_dsa->other_info, &s_dsa->source_dsa_obj_guid); + if (tmp_dns_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + tmp_dns_name = talloc_asprintf_append_buffer(tmp_dns_name, "._msdcs.%s", c->forest->dns_name); + if (tmp_dns_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + s_dsa->other_info->dns_name = tmp_dns_name; + + /* we want to show a count per partition */ + if (!s->last_partition || strcmp(s->last_partition, c->partition->nc.dn) != 0) { + s->total_objects = 0; + s->total_links = 0; + talloc_free(s->last_partition); + s->last_partition = talloc_strdup(s, c->partition->nc.dn); + } + s->total_objects += object_count; + s->total_links += linked_attributes_count; + + partition_dn = ldb_dn_new(s_dsa, s->ldb, c->partition->nc.dn); + if (partition_dn == NULL) { + DEBUG(0,("Failed to parse partition DN from DRS.\n")); + return WERR_INVALID_PARAMETER; + } + + if (is_exop) { + int ret; + if (nc_object_count) { + DEBUG(0,("Exop on[%s] objects[%u/%u] linked_values[%u/%u]\n", + c->partition->nc.dn, s->total_objects, nc_object_count, + s->total_links, nc_linked_attributes_count)); + } else { + DEBUG(0,("Exop on[%s] objects[%u] linked_values[%u]\n", + c->partition->nc.dn, s->total_objects, linked_attributes_count)); + } + ret = dsdb_find_nc_root(s->ldb, s_dsa, + partition_dn, &nc_root); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find nc_root for %s\n", + ldb_dn_get_linearized(partition_dn))); + return WERR_INTERNAL_ERROR; + } + } else { + if (nc_object_count) { + DEBUG(0,("Partition[%s] objects[%u/%u] linked_values[%u/%u]\n", + c->partition->nc.dn, s->total_objects, nc_object_count, + s->total_links, nc_linked_attributes_count)); + } else { + DEBUG(0,("Partition[%s] objects[%u] linked_values[%u]\n", + c->partition->nc.dn, s->total_objects, s->total_links)); + } + nc_root = partition_dn; + } + + + schema = dsdb_get_schema(s->ldb, NULL); + if (!schema) { + DEBUG(0,(__location__ ": Schema is not loaded yet!\n")); + return WERR_INTERNAL_ERROR; + } + + if (req_replica_flags & DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS) { + dsdb_repl_flags |= DSDB_REPL_FLAG_PRIORITISE_INCOMING; + } + + if (req_replica_flags & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { + dsdb_repl_flags |= DSDB_REPL_FLAG_EXPECT_NO_SECRETS; + } + + status = dsdb_replicated_objects_convert(s->ldb, + schema, + nc_root, + mapping_ctr, + object_count, + first_object, + linked_attributes_count, + linked_attributes, + s_dsa, + uptodateness_vector, + c->gensec_skey, + dsdb_repl_flags, + s, &objs); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0,("Failed to convert objects: %s\n", win_errstr(status))); + return status; + } + + if (lpcfg_parm_bool(s->lp_ctx, NULL, "become dc", "dump objects", false)) { + for (i=0; i < objs->num_objects; i++) { + struct ldb_ldif ldif; + fprintf(stdout, "#\n"); + ldif.changetype = LDB_CHANGETYPE_NONE; + ldif.msg = objs->objects[i].msg; + ldb_ldif_write_file(s->ldb, stdout, &ldif); + NDR_PRINT_DEBUG(replPropertyMetaDataBlob, objs->objects[i].meta_data); + } + } + status = dsdb_replicated_objects_commit(s->ldb, NULL, objs, &seq_num); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0,("Failed to commit objects: %s\n", win_errstr(status))); + return status; + } + + /* reset debug counters once we've finished replicating the partition */ + if (!c->partition->more_data) { + s->total_objects = 0; + s->total_links = 0; + } + + talloc_free(s_dsa); + talloc_free(objs); + + for (i=0; i < linked_attributes_count; i++) { + const struct dsdb_attribute *sa; + + if (!linked_attributes[i].identifier) { + DEBUG(0, ("No linked attribute identifier\n")); + return WERR_INTERNAL_ERROR; + } + + if (!linked_attributes[i].value.blob) { + DEBUG(0, ("No linked attribute value\n")); + return WERR_INTERNAL_ERROR; + } + + sa = dsdb_attribute_by_attributeID_id(s->schema, + linked_attributes[i].attid); + if (!sa) { + DEBUG(0, ("Unable to find attribute via attribute id %d\n", linked_attributes[i].attid)); + return WERR_INTERNAL_ERROR; + } + + if (lpcfg_parm_bool(s->lp_ctx, NULL, "become dc", "dump objects", false)) { + DEBUG(0,("# %s\n", sa->lDAPDisplayName)); + NDR_PRINT_DEBUG(drsuapi_DsReplicaLinkedAttribute, &linked_attributes[i]); + dump_data(0, + linked_attributes[i].value.blob->data, + linked_attributes[i].value.blob->length); + } + } + + return WERR_OK; +} + diff --git a/source4/libnet/libnet_vampire.h b/source4/libnet/libnet_vampire.h new file mode 100644 index 0000000..ea616ab --- /dev/null +++ b/source4/libnet/libnet_vampire.h @@ -0,0 +1,58 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Brad Henry 2005 + + 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_VAMPIRE_H__ +#define __LIBNET_VAMPIRE_H__ + +struct libnet_Vampire { + struct { + const char *domain_name; + const char *netbios_name; + const char *targetdir; + } in; + + struct { + struct dom_sid *domain_sid; + const char *domain_name; + const char *error_string; + } out; +}; + +struct libnet_Replicate { + struct { + const char *domain_name; + const char *netbios_name; + const char *targetdir; /* optional, may be NULL */ + struct dom_sid *domain_sid; + const char *realm; + const char *server; + const char *join_password; + uint32_t kvno; + } in; + struct { + const char *error_string; + } out; +}; + +/* Private context for the default callbacks */ +struct libnet_vampire_cb_state; + +#endif /* __LIBNET_VAMPIRE_H__ */ diff --git a/source4/libnet/prereq_domain.c b/source4/libnet/prereq_domain.c new file mode 100644 index 0000000..679669e --- /dev/null +++ b/source4/libnet/prereq_domain.c @@ -0,0 +1,144 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2006 + + 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 "libcli/composite/composite.h" +#include "auth/credentials/credentials.h" +#include "librpc/ndr/libndr.h" +#include "librpc/gen_ndr/samr.h" +#include "librpc/gen_ndr/ndr_samr.h" +#include "librpc/gen_ndr/lsa.h" +#include "librpc/gen_ndr/ndr_lsa.h" + + +bool samr_domain_opened(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + const char *domain_name, + struct composite_context **parent_ctx, + struct libnet_DomainOpen *domain_open, + void (*continue_fn)(struct composite_context*), + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *domopen_req; + + if (parent_ctx == NULL || *parent_ctx == NULL) return false; + + if (domain_name == NULL) { + /* + * Try to guess the domain name from credentials, + * if it's not been explicitly specified. + */ + + if (ndr_policy_handle_empty(&ctx->samr.handle)) { + domain_open->in.type = DOMAIN_SAMR; + domain_open->in.domain_name = cli_credentials_get_domain(ctx->cred); + domain_open->in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + + } else { + composite_error(*parent_ctx, NT_STATUS_INVALID_PARAMETER); + return true; + } + + } else { + /* + * The domain name has been specified, so check whether the same + * domain is already opened. If it is - just return NULL. Start + * opening a new domain otherwise. + */ + + if (ndr_policy_handle_empty(&ctx->samr.handle) || + !strequal(domain_name, ctx->samr.name)) { + domain_open->in.type = DOMAIN_SAMR; + domain_open->in.domain_name = domain_name; + domain_open->in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + + } else { + /* domain has already been opened and it's the same domain + as requested */ + return true; + } + } + + /* send request to open the domain */ + domopen_req = libnet_DomainOpen_send(ctx, mem_ctx, domain_open, monitor); + if (composite_nomem(domopen_req, *parent_ctx)) return false; + + composite_continue(*parent_ctx, domopen_req, continue_fn, *parent_ctx); + return false; +} + + +bool lsa_domain_opened(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, + const char *domain_name, + struct composite_context **parent_ctx, + struct libnet_DomainOpen *domain_open, + void (*continue_fn)(struct composite_context*), + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *domopen_req; + + if (parent_ctx == NULL || *parent_ctx == NULL) return false; + + if (domain_name == NULL) { + /* + * Try to guess the domain name from credentials, + * if it's not been explicitly specified. + */ + + if (ndr_policy_handle_empty(&ctx->lsa.handle)) { + domain_open->in.type = DOMAIN_LSA; + domain_open->in.domain_name = cli_credentials_get_domain(ctx->cred); + domain_open->in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + + } else { + composite_error(*parent_ctx, NT_STATUS_INVALID_PARAMETER); + /* this ensures the calling function exits and composite function error + gets noticed quickly */ + return true; + } + + } else { + /* + * The domain name has been specified, so check whether the same + * domain is already opened. If it is - just return NULL. Start + * opening a new domain otherwise. + */ + + if (ndr_policy_handle_empty(&ctx->lsa.handle) || + !strequal(domain_name, ctx->lsa.name)) { + domain_open->in.type = DOMAIN_LSA; + domain_open->in.domain_name = domain_name; + domain_open->in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + + } else { + /* domain has already been opened and it's the same domain + as requested */ + return true; + } + } + + /* send request to open the domain */ + domopen_req = libnet_DomainOpen_send(ctx, mem_ctx, domain_open, monitor); + /* see the comment above to find out why true is returned here */ + if (composite_nomem(domopen_req, *parent_ctx)) return true; + + composite_continue(*parent_ctx, domopen_req, continue_fn, *parent_ctx); + return false; +} diff --git a/source4/libnet/py_net.c b/source4/libnet/py_net.c new file mode 100644 index 0000000..fe5979e --- /dev/null +++ b/source4/libnet/py_net.c @@ -0,0 +1,945 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008-2010 + Copyright (C) Kamen Mazdrashki <kamen.mazdrashki@postpath.com> 2009 + + 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 <Python.h> +#include "python/py3compat.h" +#include "includes.h" +#include "python/modules.h" +#include <pyldb.h> +#include <pytalloc.h> +#include "libnet.h" +#include "auth/credentials/pycredentials.h" +#include "libcli/security/security.h" +#include "lib/events/events.h" +#include "param/pyparam.h" +#include "auth/gensec/gensec.h" +#include "librpc/rpc/pyrpc_util.h" +#include "libcli/resolve/resolve.h" +#include "libcli/finddc.h" +#include "dsdb/samdb/samdb.h" +#include "py_net.h" +#include "librpc/rpc/pyrpc_util.h" +#include "libcli/drsuapi/drsuapi.h" + +static void PyErr_SetDsExtendedError(enum drsuapi_DsExtendedError ext_err, const char *error_description) +{ + PyObject *mod = NULL; + PyObject *error = NULL; + mod = PyImport_ImportModule("samba"); + if (mod) { + error = PyObject_GetAttrString(mod, "DsExtendedError"); + } + if (error_description == NULL) { + switch (ext_err) { + /* Copied out of ndr_drsuapi.c:ndr_print_drsuapi_DsExtendedError() */ + case DRSUAPI_EXOP_ERR_NONE: + error_description = "DRSUAPI_EXOP_ERR_NONE"; + break; + case DRSUAPI_EXOP_ERR_SUCCESS: + error_description = "DRSUAPI_EXOP_ERR_SUCCESS"; + break; + case DRSUAPI_EXOP_ERR_UNKNOWN_OP: + error_description = "DRSUAPI_EXOP_ERR_UNKNOWN_OP"; + break; + case DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER: + error_description = "DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER"; + break; + case DRSUAPI_EXOP_ERR_UPDATE_ERR: + error_description = "DRSUAPI_EXOP_ERR_UPDATE_ERR"; + break; + case DRSUAPI_EXOP_ERR_EXCEPTION: + error_description = "DRSUAPI_EXOP_ERR_EXCEPTION"; + break; + case DRSUAPI_EXOP_ERR_UNKNOWN_CALLER: + error_description = "DRSUAPI_EXOP_ERR_UNKNOWN_CALLER"; + break; + case DRSUAPI_EXOP_ERR_RID_ALLOC: + error_description = "DRSUAPI_EXOP_ERR_RID_ALLOC"; + break; + case DRSUAPI_EXOP_ERR_FSMO_OWNER_DELETED: + error_description = "DRSUAPI_EXOP_ERR_FSMO_OWNER_DELETED"; + break; + case DRSUAPI_EXOP_ERR_FMSO_PENDING_OP: + error_description = "DRSUAPI_EXOP_ERR_FMSO_PENDING_OP"; + break; + case DRSUAPI_EXOP_ERR_MISMATCH: + error_description = "DRSUAPI_EXOP_ERR_MISMATCH"; + break; + case DRSUAPI_EXOP_ERR_COULDNT_CONTACT: + error_description = "DRSUAPI_EXOP_ERR_COULDNT_CONTACT"; + break; + case DRSUAPI_EXOP_ERR_FSMO_REFUSING_ROLES: + error_description = "DRSUAPI_EXOP_ERR_FSMO_REFUSING_ROLES"; + break; + case DRSUAPI_EXOP_ERR_DIR_ERROR: + error_description = "DRSUAPI_EXOP_ERR_DIR_ERROR"; + break; + case DRSUAPI_EXOP_ERR_FSMO_MISSING_SETTINGS: + error_description = "DRSUAPI_EXOP_ERR_FSMO_MISSING_SETTINGS"; + break; + case DRSUAPI_EXOP_ERR_ACCESS_DENIED: + error_description = "DRSUAPI_EXOP_ERR_ACCESS_DENIED"; + break; + case DRSUAPI_EXOP_ERR_PARAM_ERROR: + error_description = "DRSUAPI_EXOP_ERR_PARAM_ERROR"; + break; + } + } + if (error) { + PyObject *value = + Py_BuildValue(discard_const_p(char, "(i,s)"), + ext_err, + error_description); + PyErr_SetObject(error, value); + if (value) { + Py_DECREF(value); + } + Py_DECREF(error); + } +} + +static PyObject *py_net_join_member(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + struct libnet_Join_member r; + int _level = 0; + NTSTATUS status; + PyObject *result; + TALLOC_CTX *mem_ctx; + const char *kwnames[] = { "domain_name", "netbios_name", "level", "machinepass", NULL }; + + ZERO_STRUCT(r); + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ssi|z:Join", discard_const_p(char *, kwnames), + &r.in.domain_name, &r.in.netbios_name, + &_level, + &r.in.account_pass)) { + return NULL; + } + r.in.level = _level; + + mem_ctx = talloc_new(self->mem_ctx); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + status = libnet_Join_member(self->libnet_ctx, mem_ctx, &r); + if (NT_STATUS_IS_ERR(status)) { + PyErr_SetNTSTATUS_and_string(status, + r.out.error_string + ? r.out.error_string + : nt_errstr(status)); + talloc_free(mem_ctx); + return NULL; + } + + result = Py_BuildValue("sss", r.out.join_password, + dom_sid_string(mem_ctx, r.out.domain_sid), + r.out.domain_name); + + talloc_free(mem_ctx); + + return result; +} + +static const char py_net_join_member_doc[] = "join_member(domain_name, netbios_name, level) -> (join_password, domain_sid, domain_name)\n\n" \ +"Join the domain with the specified name."; + +static PyObject *py_net_change_password(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + union libnet_ChangePassword r; + NTSTATUS status; + TALLOC_CTX *mem_ctx = NULL; + struct tevent_context *ev = NULL; + const char *kwnames[] = { "newpassword", "oldpassword", "domain", "username", NULL }; + const char *newpass = NULL; + const char *oldpass = NULL; + ZERO_STRUCT(r); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, PYARG_STR_UNI + "|"PYARG_STR_UNI"ss:change_password", + discard_const_p(char *, kwnames), + "utf8", + &newpass, + "utf8", + &oldpass, + &r.generic.in.domain_name, + &r.generic.in.account_name)) { + return NULL; + } + + r.generic.in.newpassword = newpass; + r.generic.in.oldpassword = oldpass; + + r.generic.level = LIBNET_CHANGE_PASSWORD_GENERIC; + if (r.generic.in.account_name == NULL) { + r.generic.in.account_name + = cli_credentials_get_username(self->libnet_ctx->cred); + } + if (r.generic.in.domain_name == NULL) { + r.generic.in.domain_name + = cli_credentials_get_domain(self->libnet_ctx->cred); + } + if (r.generic.in.oldpassword == NULL) { + r.generic.in.oldpassword + = cli_credentials_get_password(self->libnet_ctx->cred); + } + + /* FIXME: we really need to get a context from the caller or we may end + * up with 2 event contexts */ + ev = s4_event_context_init(NULL); + + mem_ctx = talloc_new(ev); + if (mem_ctx == NULL) { + PyMem_Free(discard_const_p(char, newpass)); + PyMem_Free(discard_const_p(char, oldpass)); + PyErr_NoMemory(); + return NULL; + } + + status = libnet_ChangePassword(self->libnet_ctx, mem_ctx, &r); + + PyMem_Free(discard_const_p(char, newpass)); + PyMem_Free(discard_const_p(char, oldpass)); + + if (NT_STATUS_IS_ERR(status)) { + PyErr_SetNTSTATUS_and_string(status, + r.generic.out.error_string + ? r.generic.out.error_string + : nt_errstr(status)); + talloc_free(mem_ctx); + return NULL; + } + + talloc_free(mem_ctx); + Py_RETURN_NONE; +} + +static const char py_net_change_password_doc[] = "change_password(newpassword) -> True\n\n" \ +"Change password for a user. You must supply credential with enough rights to do this.\n\n" \ +"Sample usage is:\n" \ +"net.change_password(newpassword=<new_password>)\n"; + + +static PyObject *py_net_set_password(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + union libnet_SetPassword r; + NTSTATUS status; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + const char *kwnames[] = { "account_name", "domain_name", "newpassword", "force_samr_18", NULL }; + PyObject *py_force_samr_18 = Py_False; + + ZERO_STRUCT(r); + + r.generic.level = LIBNET_SET_PASSWORD_GENERIC; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sss|O:set_password", + discard_const_p(char *, kwnames), + &r.generic.in.account_name, + &r.generic.in.domain_name, + &r.generic.in.newpassword, + &py_force_samr_18)) { + return NULL; + } + + if (py_force_samr_18) { + if (!PyBool_Check(py_force_samr_18)) { + PyErr_SetString(PyExc_TypeError, "Expected boolean force_samr_18"); + return NULL; + } + if (py_force_samr_18 == Py_True) { + r.generic.samr_level = LIBNET_SET_PASSWORD_SAMR_HANDLE_18; + } + } + + /* FIXME: we really need to get a context from the caller or we may end + * up with 2 event contexts */ + ev = s4_event_context_init(NULL); + + mem_ctx = talloc_new(ev); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + status = libnet_SetPassword(self->libnet_ctx, mem_ctx, &r); + if (NT_STATUS_IS_ERR(status)) { + PyErr_SetNTSTATUS_and_string(status, + r.generic.out.error_string + ? r.generic.out.error_string + : nt_errstr(status)); + talloc_free(mem_ctx); + return NULL; + } + + talloc_free(mem_ctx); + + Py_RETURN_NONE; +} + +static const char py_net_set_password_doc[] = "set_password(account_name, domain_name, newpassword) -> True\n\n" \ +"Set password for a user. You must supply credential with enough rights to do this.\n\n" \ +"Sample usage is:\n" \ +"net.set_password(account_name=account_name, domain_name=domain_name, newpassword=new_pass)\n"; + + +static PyObject *py_net_time(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { "server_name", NULL }; + union libnet_RemoteTOD r; + NTSTATUS status; + TALLOC_CTX *mem_ctx; + char timestr[64]; + PyObject *ret; + struct tm *tm; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", + discard_const_p(char *, kwnames), &r.generic.in.server_name)) + return NULL; + + r.generic.level = LIBNET_REMOTE_TOD_GENERIC; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + status = libnet_RemoteTOD(self->libnet_ctx, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS_and_string(status, + r.generic.out.error_string + ? r.generic.out.error_string + : nt_errstr(status)); + talloc_free(mem_ctx); + return NULL; + } + + ZERO_STRUCT(timestr); + tm = localtime(&r.generic.out.time); + strftime(timestr, sizeof(timestr)-1, "%c %Z",tm); + + ret = PyUnicode_FromString(timestr); + + talloc_free(mem_ctx); + + return ret; +} + +static const char py_net_time_doc[] = "time(server_name) -> timestr\n" +"Retrieve the remote time on a server"; + +static PyObject *py_net_user_create(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { "username", NULL }; + NTSTATUS status; + TALLOC_CTX *mem_ctx; + struct libnet_CreateUser r; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", discard_const_p(char *, kwnames), + &r.in.user_name)) + return NULL; + + r.in.domain_name = cli_credentials_get_domain(self->libnet_ctx->cred); + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + status = libnet_CreateUser(self->libnet_ctx, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS_and_string(status, + r.out.error_string + ? r.out.error_string + : nt_errstr(status)); + talloc_free(mem_ctx); + return NULL; + } + + talloc_free(mem_ctx); + + Py_RETURN_NONE; +} + +static const char py_net_create_user_doc[] = "create_user(username)\n" +"Create a new user."; + +static PyObject *py_net_user_delete(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { "username", NULL }; + NTSTATUS status; + TALLOC_CTX *mem_ctx; + struct libnet_DeleteUser r; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", discard_const_p(char *, kwnames), + &r.in.user_name)) + return NULL; + + r.in.domain_name = cli_credentials_get_domain(self->libnet_ctx->cred); + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + status = libnet_DeleteUser(self->libnet_ctx, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS_and_string(status, + r.out.error_string + ? r.out.error_string + : nt_errstr(status)); + talloc_free(mem_ctx); + return NULL; + } + + talloc_free(mem_ctx); + + Py_RETURN_NONE; +} + +static const char py_net_delete_user_doc[] = "delete_user(username)\n" +"Delete a user."; + +struct replicate_state { + void *vampire_state; + dcerpc_InterfaceObject *drs_pipe; + struct libnet_BecomeDC_StoreChunk chunk; + DATA_BLOB gensec_skey; + struct libnet_BecomeDC_Partition partition; + struct libnet_BecomeDC_Forest forest; + struct libnet_BecomeDC_DestDSA dest_dsa; +}; + +/* + setup for replicate_chunk() calls + */ +static PyObject *py_net_replicate_init(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { "samdb", "lp", "drspipe", "invocation_id", NULL }; + PyObject *py_ldb, *py_lp, *py_drspipe, *py_invocation_id; + struct ldb_context *samdb; + struct loadparm_context *lp; + struct replicate_state *s; + NTSTATUS status; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOOO", + discard_const_p(char *, kwnames), + &py_ldb, &py_lp, &py_drspipe, + &py_invocation_id)) { + return NULL; + } + + s = talloc_zero(NULL, struct replicate_state); + if (!s) return NULL; + + lp = lpcfg_from_py_object(s, py_lp); + if (lp == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected lp object"); + talloc_free(s); + return NULL; + } + + samdb = pyldb_Ldb_AsLdbContext(py_ldb); + if (samdb == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected ldb object"); + talloc_free(s); + return NULL; + } + if (!py_check_dcerpc_type(py_invocation_id, "samba.dcerpc.misc", "GUID")) { + + talloc_free(s); + return NULL; + } + s->dest_dsa.invocation_id = *pytalloc_get_type(py_invocation_id, struct GUID); + + s->drs_pipe = (dcerpc_InterfaceObject *)(py_drspipe); + + s->vampire_state = libnet_vampire_replicate_init(s, samdb, lp); + if (s->vampire_state == NULL) { + PyErr_SetString(PyExc_TypeError, "Failed to initialise vampire_state"); + talloc_free(s); + return NULL; + } + + status = gensec_session_key(s->drs_pipe->pipe->conn->security_state.generic_state, + s, + &s->gensec_skey); + if (!NT_STATUS_IS_OK(status)) { + char *error_string = talloc_asprintf(s, + "Unable to get session key from drspipe: %s", + nt_errstr(status)); + PyErr_SetNTSTATUS_and_string(status, error_string); + talloc_free(s); + return NULL; + } + + s->forest.dns_name = samdb_dn_to_dns_domain(s, ldb_get_root_basedn(samdb)); + s->forest.root_dn_str = ldb_dn_get_linearized(ldb_get_root_basedn(samdb)); + s->forest.config_dn_str = ldb_dn_get_linearized(ldb_get_config_basedn(samdb)); + s->forest.schema_dn_str = ldb_dn_get_linearized(ldb_get_schema_basedn(samdb)); + + s->chunk.gensec_skey = &s->gensec_skey; + s->chunk.partition = &s->partition; + s->chunk.forest = &s->forest; + s->chunk.dest_dsa = &s->dest_dsa; + + return pytalloc_GenericObject_steal(s); +} + + +/* + process one replication chunk + */ +static PyObject *py_net_replicate_chunk(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { "state", "level", "ctr", + "schema", "req_level", "req", + NULL }; + PyObject *py_state, *py_ctr, *py_schema = Py_None, *py_req = Py_None; + struct replicate_state *s; + unsigned level; + unsigned req_level = 0; + WERROR (*chunk_handler)(void *private_data, const struct libnet_BecomeDC_StoreChunk *c); + WERROR werr; + enum drsuapi_DsExtendedError extended_ret = DRSUAPI_EXOP_ERR_NONE; + enum drsuapi_DsExtendedOperation exop = DRSUAPI_EXOP_NONE; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OIO|OIO", + discard_const_p(char *, kwnames), + &py_state, &level, &py_ctr, + &py_schema, &req_level, &py_req)) { + return NULL; + } + + s = pytalloc_get_type(py_state, struct replicate_state); + if (!s) { + return NULL; + } + + switch (level) { + case 1: + if (!py_check_dcerpc_type(py_ctr, "samba.dcerpc.drsuapi", "DsGetNCChangesCtr1")) { + return NULL; + } + s->chunk.ctr1 = pytalloc_get_ptr(py_ctr); + if (s->chunk.ctr1->naming_context != NULL) { + s->partition.nc = *s->chunk.ctr1->naming_context; + } + extended_ret = s->chunk.ctr1->extended_ret; + s->partition.more_data = s->chunk.ctr1->more_data; + s->partition.source_dsa_guid = s->chunk.ctr1->source_dsa_guid; + s->partition.source_dsa_invocation_id = s->chunk.ctr1->source_dsa_invocation_id; + s->partition.highwatermark = s->chunk.ctr1->new_highwatermark; + break; + case 6: + if (!py_check_dcerpc_type(py_ctr, "samba.dcerpc.drsuapi", "DsGetNCChangesCtr6")) { + return NULL; + } + s->chunk.ctr6 = pytalloc_get_ptr(py_ctr); + if (s->chunk.ctr6->naming_context != NULL) { + s->partition.nc = *s->chunk.ctr6->naming_context; + } + extended_ret = s->chunk.ctr6->extended_ret; + s->partition.more_data = s->chunk.ctr6->more_data; + s->partition.source_dsa_guid = s->chunk.ctr6->source_dsa_guid; + s->partition.source_dsa_invocation_id = s->chunk.ctr6->source_dsa_invocation_id; + s->partition.highwatermark = s->chunk.ctr6->new_highwatermark; + break; + default: + PyErr_Format(PyExc_TypeError, "Bad level %u in replicate_chunk", level); + return NULL; + } + + s->chunk.req5 = NULL; + s->chunk.req8 = NULL; + s->chunk.req10 = NULL; + if (py_req != Py_None) { + switch (req_level) { + case 0: + break; + case 5: + if (!py_check_dcerpc_type(py_req, "samba.dcerpc.drsuapi", "DsGetNCChangesRequest5")) { + return NULL; + } + + s->chunk.req5 = pytalloc_get_ptr(py_req); + exop = s->chunk.req5->extended_op; + break; + case 8: + if (!py_check_dcerpc_type(py_req, "samba.dcerpc.drsuapi", "DsGetNCChangesRequest8")) { + return NULL; + } + + s->chunk.req8 = pytalloc_get_ptr(py_req); + exop = s->chunk.req8->extended_op; + break; + case 10: + if (!py_check_dcerpc_type(py_req, "samba.dcerpc.drsuapi", "DsGetNCChangesRequest10")) { + return NULL; + } + + s->chunk.req10 = pytalloc_get_ptr(py_req); + exop = s->chunk.req10->extended_op; + break; + default: + PyErr_Format(PyExc_TypeError, "Bad req_level %u in replicate_chunk", req_level); + return NULL; + } + } + + if (exop != DRSUAPI_EXOP_NONE && extended_ret != DRSUAPI_EXOP_ERR_SUCCESS) { + PyErr_SetDsExtendedError(extended_ret, NULL); + return NULL; + } + + s->chunk.req_level = req_level; + + chunk_handler = libnet_vampire_cb_store_chunk; + if (py_schema) { + if (!PyBool_Check(py_schema)) { + PyErr_SetString(PyExc_TypeError, "Expected boolean schema"); + return NULL; + } + if (py_schema == Py_True) { + chunk_handler = libnet_vampire_cb_schema_chunk; + } + } + + s->chunk.ctr_level = level; + + werr = chunk_handler(s->vampire_state, &s->chunk); + if (!W_ERROR_IS_OK(werr)) { + char *error_string + = talloc_asprintf(NULL, + "Failed to process 'chunk' of DRS replicated objects: %s", + win_errstr(werr)); + PyErr_SetWERROR_and_string(werr, error_string); + TALLOC_FREE(error_string); + return NULL; + } + + Py_RETURN_NONE; +} + + +/* + just do the decryption of a DRS replicated attribute + */ +static PyObject *py_net_replicate_decrypt(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + const char *kwnames[] = { "drspipe", "attribute", "rid", NULL }; + PyObject *py_drspipe, *py_attribute; + NTSTATUS status; + dcerpc_InterfaceObject *drs_pipe; + TALLOC_CTX *frame; + TALLOC_CTX *context; + DATA_BLOB gensec_skey; + unsigned int rid; + struct drsuapi_DsReplicaAttribute *attribute; + WERROR werr; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOI", + discard_const_p(char *, kwnames), + &py_drspipe, + &py_attribute, &rid)) { + return NULL; + } + + frame = talloc_stackframe(); + + if (!py_check_dcerpc_type(py_drspipe, + "samba.dcerpc.base", + "ClientConnection")) { + return NULL; + } + drs_pipe = (dcerpc_InterfaceObject *)(py_drspipe); + + status = gensec_session_key(drs_pipe->pipe->conn->security_state.generic_state, + frame, + &gensec_skey); + if (!NT_STATUS_IS_OK(status)) { + char *error_string + = talloc_asprintf(frame, + "Unable to get session key from drspipe: %s", + nt_errstr(status)); + PyErr_SetNTSTATUS_and_string(status, error_string); + talloc_free(frame); + return NULL; + } + + if (!py_check_dcerpc_type(py_attribute, "samba.dcerpc.drsuapi", + "DsReplicaAttribute")) { + return NULL; + } + + attribute = pytalloc_get_ptr(py_attribute); + context = pytalloc_get_mem_ctx(py_attribute); + werr = drsuapi_decrypt_attribute(context, &gensec_skey, + rid, 0, attribute); + if (!W_ERROR_IS_OK(werr)) { + char *error_string = talloc_asprintf(frame, + "Unable to get decrypt attribute: %s", + win_errstr(werr)); + PyErr_SetWERROR_and_string(werr, error_string); + talloc_free(frame); + return NULL; + } + + talloc_free(frame); + + Py_RETURN_NONE; + +} + +/* + find a DC given a domain name and server type + */ +static PyObject *py_net_finddc(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + const char *domain = NULL, *address = NULL; + unsigned server_type; + NTSTATUS status; + struct finddcs *io; + TALLOC_CTX *mem_ctx; + PyObject *ret; + const char * const kwnames[] = { "flags", "domain", "address", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "I|zz", + discard_const_p(char *, kwnames), + &server_type, &domain, &address)) { + return NULL; + } + + mem_ctx = talloc_new(self->mem_ctx); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + io = talloc_zero(mem_ctx, struct finddcs); + if (io == NULL) { + TALLOC_FREE(mem_ctx); + PyErr_NoMemory(); + return NULL; + } + + if (domain != NULL) { + io->in.domain_name = domain; + } + if (address != NULL) { + io->in.server_address = address; + } + io->in.minimum_dc_flags = server_type; + + status = finddcs_cldap(io, io, + lpcfg_resolve_context(self->libnet_ctx->lp_ctx), self->ev); + if (NT_STATUS_IS_ERR(status)) { + PyErr_SetNTSTATUS(status); + talloc_free(mem_ctx); + return NULL; + } + + ret = py_return_ndr_struct("samba.dcerpc.nbt", "NETLOGON_SAM_LOGON_RESPONSE_EX", + io, &io->out.netlogon.data.nt5_ex); + talloc_free(mem_ctx); + + return ret; +} + + +static const char py_net_replicate_init_doc[] = "replicate_init(samdb, lp, drspipe)\n" + "Setup for replicate_chunk calls."; + +static const char py_net_replicate_chunk_doc[] = "replicate_chunk(state, level, ctr, schema)\n" + "Process replication for one chunk"; + +static const char py_net_replicate_decrypt_doc[] = "replicate_decrypt(drs, attribute, rid)\n" + "Decrypt (in place) a DsReplicaAttribute replicated with drs.GetNCChanges()"; + +static const char py_net_finddc_doc[] = "finddc(flags=server_type, domain=None, address=None)\n" + "Find a DC with the specified 'server_type' bits. The 'domain' and/or 'address' have to be used as additional search criteria. Returns the whole netlogon struct"; + +static PyMethodDef net_obj_methods[] = { + { + .ml_name = "join_member", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_join_member), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_join_member_doc + }, + { + .ml_name = "change_password", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_change_password), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_change_password_doc + }, + { + .ml_name = "set_password", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_set_password), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_set_password_doc + }, + { + .ml_name = "time", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, py_net_time), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_time_doc + }, + { + .ml_name = "create_user", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_user_create), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_create_user_doc + }, + { + .ml_name = "delete_user", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_user_delete), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_delete_user_doc + }, + { + .ml_name = "replicate_init", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_replicate_init), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_replicate_init_doc + }, + { + .ml_name = "replicate_chunk", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_replicate_chunk), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_replicate_chunk_doc + }, + { + .ml_name = "replicate_decrypt", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_replicate_decrypt), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_replicate_decrypt_doc + }, + { + .ml_name = "finddc", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_finddc), + .ml_flags = METH_VARARGS|METH_KEYWORDS, + .ml_doc = py_net_finddc_doc + }, + { .ml_name = NULL } +}; + +static void py_net_dealloc(py_net_Object *self) +{ + talloc_free(self->ev); + PyObject_Del(self); +} + +static PyObject *net_obj_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *py_creds, *py_lp = Py_None; + const char *kwnames[] = { "creds", "lp", "server", NULL }; + py_net_Object *ret; + struct loadparm_context *lp; + const char *server_address = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Oz", + discard_const_p(char *, kwnames), &py_creds, &py_lp, + &server_address)) + return NULL; + + ret = PyObject_New(py_net_Object, type); + if (ret == NULL) { + return NULL; + } + + /* FIXME: we really need to get a context from the caller or we may end + * up with 2 event contexts */ + ret->ev = s4_event_context_init(NULL); + ret->mem_ctx = talloc_new(ret->ev); + + lp = lpcfg_from_py_object(ret->mem_ctx, py_lp); + if (lp == NULL) { + Py_DECREF(ret); + return NULL; + } + + ret->libnet_ctx = libnet_context_init(ret->ev, lp); + if (ret->libnet_ctx == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Unable to initialize net"); + Py_DECREF(ret); + return NULL; + } + + ret->libnet_ctx->server_address = server_address; + + ret->libnet_ctx->cred = cli_credentials_from_py_object(py_creds); + if (ret->libnet_ctx->cred == NULL) { + PyErr_SetString(PyExc_TypeError, "Expected credentials object"); + Py_DECREF(ret); + return NULL; + } + + return (PyObject *)ret; +} + + +PyTypeObject py_net_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "net.Net", + .tp_basicsize = sizeof(py_net_Object), + .tp_dealloc = (destructor)py_net_dealloc, + .tp_methods = net_obj_methods, + .tp_new = net_obj_new, +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "net", + .m_size = -1, +}; + +MODULE_INIT_FUNC(net) +{ + PyObject *m; + + if (PyType_Ready(&py_net_Type) < 0) + return NULL; + + m = PyModule_Create(&moduledef); + if (m == NULL) + return NULL; + + Py_INCREF(&py_net_Type); + PyModule_AddObject(m, "Net", (PyObject *)&py_net_Type); + PyModule_AddIntConstant(m, "LIBNET_JOINDOMAIN_AUTOMATIC", LIBNET_JOINDOMAIN_AUTOMATIC); + PyModule_AddIntConstant(m, "LIBNET_JOINDOMAIN_SPECIFIED", LIBNET_JOINDOMAIN_SPECIFIED); + PyModule_AddIntConstant(m, "LIBNET_JOIN_AUTOMATIC", LIBNET_JOIN_AUTOMATIC); + PyModule_AddIntConstant(m, "LIBNET_JOIN_SPECIFIED", LIBNET_JOIN_SPECIFIED); + + return m; +} diff --git a/source4/libnet/py_net.h b/source4/libnet/py_net.h new file mode 100644 index 0000000..2894d47 --- /dev/null +++ b/source4/libnet/py_net.h @@ -0,0 +1,24 @@ +/* + Unix SMB/CIFS implementation. + Samba python bindings to libnet library + + 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/>. +*/ + +typedef struct { + PyObject_HEAD + TALLOC_CTX *mem_ctx; + struct libnet_context *libnet_ctx; + struct tevent_context *ev; +} py_net_Object; diff --git a/source4/libnet/py_net_dckeytab.c b/source4/libnet/py_net_dckeytab.c new file mode 100644 index 0000000..ad65c7d --- /dev/null +++ b/source4/libnet/py_net_dckeytab.c @@ -0,0 +1,121 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008-2010 + Copyright (C) Kamen Mazdrashki <kamen.mazdrashki@postpath.com> 2009 + Copyright (C) Alexander Bokovoy <ab@samba.org> 2012 + + 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 <Python.h> +#include "includes.h" +#include "python/py3compat.h" +#include "python/modules.h" +#include "py_net.h" +#include "libnet_export_keytab.h" + +void initdckeytab(void); + +static PyObject *py_net_export_keytab(py_net_Object *self, PyObject *args, PyObject *kwargs) +{ + struct libnet_export_keytab r; + TALLOC_CTX *mem_ctx; + const char *kwnames[] = { "keytab", "principal", NULL }; + NTSTATUS status; + r.in.principal = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|z:export_keytab", discard_const_p(char *, kwnames), + &r.in.keytab_name, + &r.in.principal)) { + return NULL; + } + + mem_ctx = talloc_new(self->mem_ctx); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + status = libnet_export_keytab(self->libnet_ctx, mem_ctx, &r); + if (NT_STATUS_IS_ERR(status)) { + PyErr_SetString(PyExc_RuntimeError, + r.out.error_string?r.out.error_string:nt_errstr(status)); + talloc_free(mem_ctx); + return NULL; + } + + talloc_free(mem_ctx); + + Py_RETURN_NONE; +} + +static const char py_net_export_keytab_doc[] = "export_keytab(keytab, name)\n\n" +"Export the DC keytab to a keytab file."; + +static PyMethodDef export_keytab_method_table[] = { + {"export_keytab", PY_DISCARD_FUNC_SIG(PyCFunction, + py_net_export_keytab), + METH_VARARGS|METH_KEYWORDS, py_net_export_keytab_doc}, + { NULL, NULL, 0, NULL } +}; + +/* + * A fake Python module to inject export_keytab() method into existing samba.net.Net class. + * Python enforces that every loaded module actually creates Python module record in + * the global module table even if we don't really need that record. Thus, we initialize + * dckeytab module but never use it. + * */ +void initdckeytab(void); +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "dckeytab", + .m_doc = "dckeytab", + .m_size = -1, + .m_methods = NULL +}; + +MODULE_INIT_FUNC(dckeytab) +{ + PyObject *m = NULL; + PyObject *Net; + PyObject *descr; + int ret; + + m = PyModule_Create(&moduledef); + if (m == NULL) + return m; + + m = PyImport_ImportModule("samba.net"); + if (m == NULL) + return m; + + Net = (PyObject *)PyObject_GetAttrString(m, "Net"); + if (Net == NULL) + return m; + + descr = PyDescr_NewMethod((PyTypeObject*)Net, &export_keytab_method_table[0]); + if (descr == NULL) + return m; + + ret = PyDict_SetItemString(((PyTypeObject*)Net)->tp_dict, + export_keytab_method_table[0].ml_name, + descr); + if (ret != -1) { + Py_DECREF(descr); + } + + return m; +} diff --git a/source4/libnet/userinfo.c b/source4/libnet/userinfo.c new file mode 100644 index 0000000..ec40b81 --- /dev/null +++ b/source4/libnet/userinfo.c @@ -0,0 +1,382 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + 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/>. +*/ + +/* + a composite function for getting user information via samr pipe +*/ + +#include "includes.h" +#include "libcli/composite/composite.h" +#include "librpc/gen_ndr/security.h" +#include "libcli/security/security.h" +#include "libnet/libnet.h" +#include "librpc/gen_ndr/ndr_samr_c.h" + + +struct userinfo_state { + struct dcerpc_binding_handle *binding_handle; + struct policy_handle domain_handle; + struct policy_handle user_handle; + uint16_t level; + struct samr_LookupNames lookup; + struct samr_OpenUser openuser; + struct samr_QueryUserInfo queryuserinfo; + struct samr_Close samrclose; + union samr_UserInfo *info; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_userinfo_lookup(struct tevent_req *subreq); +static void continue_userinfo_openuser(struct tevent_req *subreq); +static void continue_userinfo_getuser(struct tevent_req *subreq); +static void continue_userinfo_closeuser(struct tevent_req *subreq); + + +/** + * Stage 1 (optional): Look for a username in SAM server. + */ +static void continue_userinfo_lookup(struct tevent_req *subreq) +{ + struct composite_context *c; + struct userinfo_state *s; + struct monitor_msg msg; + struct msg_rpc_lookup_name *msg_lookup; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct userinfo_state); + + /* receive samr_Lookup reply */ + c->status = dcerpc_samr_LookupNames_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* there could be a problem with name resolving itself */ + if (!NT_STATUS_IS_OK(s->lookup.out.result)) { + composite_error(c, s->lookup.out.result); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + msg.type = mon_SamrLookupName; + msg_lookup = talloc(s, struct msg_rpc_lookup_name); + msg_lookup->rid = s->lookup.out.rids->ids; + msg_lookup->count = s->lookup.out.rids->count; + msg.data = (void*)msg_lookup; + msg.data_size = sizeof(*msg_lookup); + + s->monitor_fn(&msg); + } + + + /* have we actually got name resolved + - we're looking for only one at the moment */ + if (s->lookup.out.rids->count != s->lookup.in.num_names) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + if (s->lookup.out.types->count != s->lookup.in.num_names) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* TODO: find proper status code for more than one rid found */ + + /* prepare parameters for LookupNames */ + s->openuser.in.domain_handle = &s->domain_handle; + s->openuser.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + s->openuser.in.rid = s->lookup.out.rids->ids[0]; + s->openuser.out.user_handle = &s->user_handle; + + /* send request */ + subreq = dcerpc_samr_OpenUser_r_send(s, c->event_ctx, + s->binding_handle, + &s->openuser); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_userinfo_openuser, c); +} + + +/** + * Stage 2: Open user policy handle. + */ +static void continue_userinfo_openuser(struct tevent_req *subreq) +{ + struct composite_context *c; + struct userinfo_state *s; + struct monitor_msg msg; + struct msg_rpc_open_user *msg_open; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct userinfo_state); + + /* receive samr_OpenUser reply */ + c->status = dcerpc_samr_OpenUser_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!NT_STATUS_IS_OK(s->openuser.out.result)) { + composite_error(c, s->openuser.out.result); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + msg.type = mon_SamrOpenUser; + msg_open = talloc(s, struct msg_rpc_open_user); + msg_open->rid = s->openuser.in.rid; + msg_open->access_mask = s->openuser.in.access_mask; + msg.data = (void*)msg_open; + msg.data_size = sizeof(*msg_open); + + s->monitor_fn(&msg); + } + + /* prepare parameters for QueryUserInfo call */ + s->queryuserinfo.in.user_handle = &s->user_handle; + s->queryuserinfo.in.level = s->level; + s->queryuserinfo.out.info = talloc(s, union samr_UserInfo *); + if (composite_nomem(s->queryuserinfo.out.info, c)) return; + + /* queue rpc call, set event handling and new state */ + subreq = dcerpc_samr_QueryUserInfo_r_send(s, c->event_ctx, + s->binding_handle, + &s->queryuserinfo); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_userinfo_getuser, c); +} + + +/** + * Stage 3: Get requested user information. + */ +static void continue_userinfo_getuser(struct tevent_req *subreq) +{ + struct composite_context *c; + struct userinfo_state *s; + struct monitor_msg msg; + struct msg_rpc_query_user *msg_query; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct userinfo_state); + + /* receive samr_QueryUserInfo reply */ + c->status = dcerpc_samr_QueryUserInfo_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* check if queryuser itself went ok */ + if (!NT_STATUS_IS_OK(s->queryuserinfo.out.result)) { + composite_error(c, s->queryuserinfo.out.result); + return; + } + + s->info = talloc_steal(s, *(s->queryuserinfo.out.info)); + + /* issue a monitor message */ + if (s->monitor_fn) { + msg.type = mon_SamrQueryUser; + msg_query = talloc(s, struct msg_rpc_query_user); + msg_query->level = s->queryuserinfo.in.level; + msg.data = (void*)msg_query; + msg.data_size = sizeof(*msg_query); + + s->monitor_fn(&msg); + } + + /* prepare arguments for Close call */ + s->samrclose.in.handle = &s->user_handle; + s->samrclose.out.handle = &s->user_handle; + + /* queue rpc call, set event handling and new state */ + subreq = dcerpc_samr_Close_r_send(s, c->event_ctx, + s->binding_handle, + &s->samrclose); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_userinfo_closeuser, c); +} + + +/** + * Stage 4: Close policy handle associated with opened user. + */ +static void continue_userinfo_closeuser(struct tevent_req *subreq) +{ + struct composite_context *c; + struct userinfo_state *s; + struct monitor_msg msg; + struct msg_rpc_close_user *msg_close; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type_abort(c->private_data, struct userinfo_state); + + /* receive samr_Close reply */ + c->status = dcerpc_samr_Close_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + if (!NT_STATUS_IS_OK(s->samrclose.out.result)) { + composite_error(c, s->samrclose.out.result); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + msg.type = mon_SamrClose; + msg_close = talloc(s, struct msg_rpc_close_user); + msg_close->rid = s->openuser.in.rid; + msg.data = (void*)msg_close; + msg.data_size = sizeof(*msg_close); + + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +/** + * Sends asynchronous userinfo request + * + * @param p dce/rpc call pipe + * @param io arguments and results of the call + */ +struct composite_context *libnet_rpc_userinfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *b, + struct libnet_rpc_userinfo *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct userinfo_state *s; + struct dom_sid *sid; + struct tevent_req *subreq; + + if (!b || !io) return NULL; + + c = composite_create(mem_ctx, ev); + if (c == NULL) return c; + + s = talloc_zero(c, struct userinfo_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + s->level = io->in.level; + s->binding_handle= b; + s->domain_handle = io->in.domain_handle; + s->monitor_fn = monitor; + + if (io->in.sid) { + sid = dom_sid_parse_talloc(s, io->in.sid); + if (composite_nomem(sid, c)) return c; + + s->openuser.in.domain_handle = &s->domain_handle; + s->openuser.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + s->openuser.in.rid = sid->sub_auths[sid->num_auths - 1]; + s->openuser.out.user_handle = &s->user_handle; + + /* send request */ + subreq = dcerpc_samr_OpenUser_r_send(s, c->event_ctx, + s->binding_handle, + &s->openuser); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_userinfo_openuser, c); + + } else { + /* preparing parameters to send rpc request */ + s->lookup.in.domain_handle = &s->domain_handle; + s->lookup.in.num_names = 1; + s->lookup.in.names = talloc_array(s, struct lsa_String, 1); + if (composite_nomem(s->lookup.in.names, c)) return c; + s->lookup.out.rids = talloc_zero(s, struct samr_Ids); + s->lookup.out.types = talloc_zero(s, struct samr_Ids); + if (composite_nomem(s->lookup.out.rids, c)) return c; + if (composite_nomem(s->lookup.out.types, c)) return c; + + s->lookup.in.names[0].string = talloc_strdup(s, io->in.username); + if (composite_nomem(s->lookup.in.names[0].string, c)) return c; + + /* send request */ + subreq = dcerpc_samr_LookupNames_r_send(s, c->event_ctx, + s->binding_handle, + &s->lookup); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_userinfo_lookup, c); + } + + return c; +} + + +/** + * Waits for and receives result of asynchronous userinfo call + * + * @param c composite context returned by asynchronous userinfo call + * @param mem_ctx memory context of the call + * @param io pointer to results (and arguments) of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_userinfo_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_rpc_userinfo *io) +{ + NTSTATUS status; + struct userinfo_state *s; + + /* wait for results of sending request */ + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status) && io) { + s = talloc_get_type_abort(c->private_data, struct userinfo_state); + talloc_steal(mem_ctx, s->info); + io->out.info = *s->info; + } + + /* memory context associated to composite context is no longer needed */ + talloc_free(c); + return status; +} + + +/** + * Synchronous version of userinfo call + * + * @param pipe dce/rpc call pipe + * @param mem_ctx memory context for the call + * @param io arguments and results of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_userinfo(struct tevent_context *ev, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct libnet_rpc_userinfo *io) +{ + struct composite_context *c = libnet_rpc_userinfo_send(mem_ctx, ev, b, io, NULL); + return libnet_rpc_userinfo_recv(c, mem_ctx, io); +} diff --git a/source4/libnet/userinfo.h b/source4/libnet/userinfo.h new file mode 100644 index 0000000..273ad87 --- /dev/null +++ b/source4/libnet/userinfo.h @@ -0,0 +1,54 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "librpc/gen_ndr/samr.h" + +/* + * IO structures for userinfo.c functions + */ + +struct libnet_rpc_userinfo { + struct { + struct policy_handle domain_handle; + const char *username; + const char *sid; + uint16_t level; + } in; + struct { + union samr_UserInfo info; + } out; +}; + + +/* + * Monitor messages sent from userinfo.c functions + */ + +struct msg_rpc_open_user { + uint32_t rid, access_mask; +}; + +struct msg_rpc_query_user { + uint16_t level; +}; + +struct msg_rpc_close_user { + uint32_t rid; +}; diff --git a/source4/libnet/userman.c b/source4/libnet/userman.c new file mode 100644 index 0000000..9e76364 --- /dev/null +++ b/source4/libnet/userman.c @@ -0,0 +1,922 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + 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/>. +*/ + +/* + a composite functions for user management operations (add/del/chg) +*/ + +#include "includes.h" +#include "libcli/composite/composite.h" +#include "libnet/libnet.h" +#include "librpc/gen_ndr/ndr_samr_c.h" + +/* + * Composite USER ADD functionality + */ + +struct useradd_state { + struct dcerpc_binding_handle *binding_handle; + struct policy_handle domain_handle; + struct samr_CreateUser createuser; + struct policy_handle user_handle; + uint32_t user_rid; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_useradd_create(struct tevent_req *subreq); + + +/** + * Stage 1 (and the only one for now): Create user account. + */ +static void continue_useradd_create(struct tevent_req *subreq) +{ + struct composite_context *c; + struct useradd_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct useradd_state); + + /* check rpc layer status code */ + c->status = dcerpc_samr_CreateUser_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* check create user call status code */ + c->status = s->createuser.out.result; + + /* get created user account data */ + s->user_handle = *s->createuser.out.user_handle; + s->user_rid = *s->createuser.out.rid; + + /* issue a monitor message */ + if (s->monitor_fn) { + struct monitor_msg msg; + struct msg_rpc_create_user rpc_create; + + rpc_create.rid = *s->createuser.out.rid; + + msg.type = mon_SamrCreateUser; + msg.data = (void*)&rpc_create; + msg.data_size = sizeof(rpc_create); + + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +/** + * Sends asynchronous useradd request + * + * @param p dce/rpc call pipe + * @param io arguments and results of the call + * @param monitor monitor function for providing information about the progress + */ + +struct composite_context *libnet_rpc_useradd_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *b, + struct libnet_rpc_useradd *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct useradd_state *s; + struct tevent_req *subreq; + + if (!b || !io) return NULL; + + /* composite allocation and setup */ + c = composite_create(mem_ctx, ev); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct useradd_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* put passed arguments to the state structure */ + s->domain_handle = io->in.domain_handle; + s->binding_handle= b; + s->monitor_fn = monitor; + + /* preparing parameters to send rpc request */ + s->createuser.in.domain_handle = &io->in.domain_handle; + + s->createuser.in.account_name = talloc_zero(c, struct lsa_String); + if (composite_nomem(s->createuser.in.account_name, c)) return c; + + s->createuser.in.account_name->string = talloc_strdup(c, io->in.username); + if (composite_nomem(s->createuser.in.account_name->string, c)) return c; + + s->createuser.out.user_handle = &s->user_handle; + s->createuser.out.rid = &s->user_rid; + + /* send the request */ + subreq = dcerpc_samr_CreateUser_r_send(s, c->event_ctx, + s->binding_handle, + &s->createuser); + if (composite_nomem(subreq, c)) return c; + + tevent_req_set_callback(subreq, continue_useradd_create, c); + return c; +} + + +/** + * Waits for and receives result of asynchronous useradd call + * + * @param c composite context returned by asynchronous useradd call + * @param mem_ctx memory context of the call + * @param io pointer to results (and arguments) of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_useradd_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_rpc_useradd *io) +{ + NTSTATUS status; + struct useradd_state *s; + + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status) && io) { + /* get and return result of the call */ + s = talloc_get_type(c->private_data, struct useradd_state); + io->out.user_handle = s->user_handle; + } + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of useradd call + * + * @param pipe dce/rpc call pipe + * @param mem_ctx memory context for the call + * @param io arguments and results of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_useradd(struct tevent_context *ev, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct libnet_rpc_useradd *io) +{ + struct composite_context *c = libnet_rpc_useradd_send(mem_ctx, ev, b, io, NULL); + return libnet_rpc_useradd_recv(c, mem_ctx, io); +} + + + +/* + * Composite USER DELETE functionality + */ + + +struct userdel_state { + struct dcerpc_binding_handle *binding_handle; + struct policy_handle domain_handle; + struct policy_handle user_handle; + struct samr_LookupNames lookupname; + struct samr_OpenUser openuser; + struct samr_DeleteUser deleteuser; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +static void continue_userdel_name_found(struct tevent_req *subreq); +static void continue_userdel_user_opened(struct tevent_req *subreq); +static void continue_userdel_deleted(struct tevent_req *subreq); + + +/** + * Stage 1: Lookup the user name and resolve it to rid + */ +static void continue_userdel_name_found(struct tevent_req *subreq) +{ + struct composite_context *c; + struct userdel_state *s; + struct monitor_msg msg; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct userdel_state); + + /* receive samr_LookupNames result */ + c->status = dcerpc_samr_LookupNames_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + c->status = s->lookupname.out.result; + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + /* what to do when there's no user account to delete + and what if there's more than one rid resolved */ + if (s->lookupname.out.rids->count != s->lookupname.in.num_names) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + if (s->lookupname.out.types->count != s->lookupname.in.num_names) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + struct msg_rpc_lookup_name msg_lookup; + + msg_lookup.rid = s->lookupname.out.rids->ids; + msg_lookup.count = s->lookupname.out.rids->count; + + msg.type = mon_SamrLookupName; + msg.data = (void*)&msg_lookup; + msg.data_size = sizeof(msg_lookup); + s->monitor_fn(&msg); + } + + /* prepare the arguments for rpc call */ + s->openuser.in.domain_handle = &s->domain_handle; + s->openuser.in.rid = s->lookupname.out.rids->ids[0]; + s->openuser.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + s->openuser.out.user_handle = &s->user_handle; + + /* send rpc request */ + subreq = dcerpc_samr_OpenUser_r_send(s, c->event_ctx, + s->binding_handle, + &s->openuser); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_userdel_user_opened, c); +} + + +/** + * Stage 2: Open user account. + */ +static void continue_userdel_user_opened(struct tevent_req *subreq) +{ + struct composite_context *c; + struct userdel_state *s; + struct monitor_msg msg; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct userdel_state); + + /* receive samr_OpenUser result */ + c->status = dcerpc_samr_OpenUser_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + c->status = s->openuser.out.result; + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + struct msg_rpc_open_user msg_open; + + msg_open.rid = s->openuser.in.rid; + msg_open.access_mask = s->openuser.in.access_mask; + + msg.type = mon_SamrOpenUser; + msg.data = (void*)&msg_open; + msg.data_size = sizeof(msg_open); + s->monitor_fn(&msg); + } + + /* prepare the final rpc call arguments */ + s->deleteuser.in.user_handle = &s->user_handle; + s->deleteuser.out.user_handle = &s->user_handle; + + /* send rpc request */ + subreq = dcerpc_samr_DeleteUser_r_send(s, c->event_ctx, + s->binding_handle, + &s->deleteuser); + if (composite_nomem(subreq, c)) return; + + /* callback handler setup */ + tevent_req_set_callback(subreq, continue_userdel_deleted, c); +} + + +/** + * Stage 3: Delete user account + */ +static void continue_userdel_deleted(struct tevent_req *subreq) +{ + struct composite_context *c; + struct userdel_state *s; + struct monitor_msg msg; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct userdel_state); + + /* receive samr_DeleteUser result */ + c->status = dcerpc_samr_DeleteUser_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* return the actual function call status */ + c->status = s->deleteuser.out.result; + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + msg.type = mon_SamrDeleteUser; + msg.data = NULL; + msg.data_size = 0; + s->monitor_fn(&msg); + } + + composite_done(c); +} + + +/** + * Sends asynchronous userdel request + * + * @param p dce/rpc call pipe + * @param io arguments and results of the call + * @param monitor monitor function for providing information about the progress + */ + +struct composite_context *libnet_rpc_userdel_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *b, + struct libnet_rpc_userdel *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct userdel_state *s; + struct tevent_req *subreq; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ev); + if (c == NULL) return NULL; + + s = talloc_zero(c, struct userdel_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* store function parameters in the state structure */ + s->binding_handle= b; + s->domain_handle = io->in.domain_handle; + s->monitor_fn = monitor; + + /* preparing parameters to send rpc request */ + s->lookupname.in.domain_handle = &io->in.domain_handle; + s->lookupname.in.num_names = 1; + s->lookupname.in.names = talloc_zero(s, struct lsa_String); + s->lookupname.in.names->string = io->in.username; + s->lookupname.out.rids = talloc_zero(s, struct samr_Ids); + s->lookupname.out.types = talloc_zero(s, struct samr_Ids); + if (composite_nomem(s->lookupname.out.rids, c)) return c; + if (composite_nomem(s->lookupname.out.types, c)) return c; + + /* send the request */ + subreq = dcerpc_samr_LookupNames_r_send(s, c->event_ctx, + s->binding_handle, + &s->lookupname); + if (composite_nomem(subreq, c)) return c; + + /* set the next stage */ + tevent_req_set_callback(subreq, continue_userdel_name_found, c); + return c; +} + + +/** + * Waits for and receives results of asynchronous userdel call + * + * @param c composite context returned by asynchronous userdel call + * @param mem_ctx memory context of the call + * @param io pointer to results (and arguments) of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_userdel_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_rpc_userdel *io) +{ + NTSTATUS status; + struct userdel_state *s; + + status = composite_wait(c); + + if (NT_STATUS_IS_OK(status) && io) { + s = talloc_get_type(c->private_data, struct userdel_state); + io->out.user_handle = s->user_handle; + } + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of userdel call + * + * @param pipe dce/rpc call pipe + * @param mem_ctx memory context for the call + * @param io arguments and results of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_userdel(struct tevent_context *ev, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct libnet_rpc_userdel *io) +{ + struct composite_context *c = libnet_rpc_userdel_send(mem_ctx, ev, b, io, NULL); + return libnet_rpc_userdel_recv(c, mem_ctx, io); +} + + +/* + * USER MODIFY functionality + */ + +static void continue_usermod_name_found(struct tevent_req *subreq); +static void continue_usermod_user_opened(struct tevent_req *subreq); +static void continue_usermod_user_queried(struct tevent_req *subreq); +static void continue_usermod_user_changed(struct tevent_req *subreq); + + +struct usermod_state { + struct dcerpc_binding_handle *binding_handle; + struct policy_handle domain_handle; + struct policy_handle user_handle; + struct usermod_change change; + union samr_UserInfo info; + struct samr_LookupNames lookupname; + struct samr_OpenUser openuser; + struct samr_SetUserInfo setuser; + struct samr_QueryUserInfo queryuser; + + /* information about the progress */ + void (*monitor_fn)(struct monitor_msg *); +}; + + +/** + * Step 1: Lookup user name + */ +static void continue_usermod_name_found(struct tevent_req *subreq) +{ + struct composite_context *c; + struct usermod_state *s; + struct monitor_msg msg; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct usermod_state); + + /* receive samr_LookupNames result */ + c->status = dcerpc_samr_LookupNames_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + c->status = s->lookupname.out.result; + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + /* what to do when there's no user account to delete + and what if there's more than one rid resolved */ + if (s->lookupname.out.rids->count != s->lookupname.in.num_names) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + if (s->lookupname.out.types->count != s->lookupname.in.num_names) { + composite_error(c, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + /* issue a monitor message */ + if (s->monitor_fn) { + struct msg_rpc_lookup_name msg_lookup; + + msg_lookup.rid = s->lookupname.out.rids->ids; + msg_lookup.count = s->lookupname.out.rids->count; + + msg.type = mon_SamrLookupName; + msg.data = (void*)&msg_lookup; + msg.data_size = sizeof(msg_lookup); + s->monitor_fn(&msg); + } + + /* prepare the next rpc call */ + s->openuser.in.domain_handle = &s->domain_handle; + s->openuser.in.rid = s->lookupname.out.rids->ids[0]; + s->openuser.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + s->openuser.out.user_handle = &s->user_handle; + + /* send the rpc request */ + subreq = dcerpc_samr_OpenUser_r_send(s, c->event_ctx, + s->binding_handle, + &s->openuser); + if (composite_nomem(subreq, c)) return; + + tevent_req_set_callback(subreq, continue_usermod_user_opened, c); +} + + +/** + * Choose a proper level of samr_UserInfo structure depending on required + * change specified by means of flags field. Subsequent calls of this + * function are made until there's no flags set meaning that all of the + * changes have been made. + */ +static bool usermod_setfields(struct usermod_state *s, uint16_t *level, + union samr_UserInfo *i, bool queried) +{ + if (s->change.fields == 0) return s->change.fields; + + *level = 0; + + if ((s->change.fields & USERMOD_FIELD_ACCOUNT_NAME) && + (*level == 0 || *level == 7)) { + *level = 7; + i->info7.account_name.string = s->change.account_name; + + s->change.fields ^= USERMOD_FIELD_ACCOUNT_NAME; + } + + if ((s->change.fields & USERMOD_FIELD_FULL_NAME) && + (*level == 0 || *level == 8)) { + *level = 8; + i->info8.full_name.string = s->change.full_name; + + s->change.fields ^= USERMOD_FIELD_FULL_NAME; + } + + if ((s->change.fields & USERMOD_FIELD_DESCRIPTION) && + (*level == 0 || *level == 13)) { + *level = 13; + i->info13.description.string = s->change.description; + + s->change.fields ^= USERMOD_FIELD_DESCRIPTION; + } + + if ((s->change.fields & USERMOD_FIELD_COMMENT) && + (*level == 0 || *level == 2)) { + *level = 2; + + if (queried) { + /* the user info is obtained, so now set the required field */ + i->info2.comment.string = s->change.comment; + s->change.fields ^= USERMOD_FIELD_COMMENT; + + } else { + /* we need to query the user info before setting one field in it */ + return false; + } + } + + if ((s->change.fields & USERMOD_FIELD_LOGON_SCRIPT) && + (*level == 0 || *level == 11)) { + *level = 11; + i->info11.logon_script.string = s->change.logon_script; + + s->change.fields ^= USERMOD_FIELD_LOGON_SCRIPT; + } + + if ((s->change.fields & USERMOD_FIELD_PROFILE_PATH) && + (*level == 0 || *level == 12)) { + *level = 12; + i->info12.profile_path.string = s->change.profile_path; + + s->change.fields ^= USERMOD_FIELD_PROFILE_PATH; + } + + if ((s->change.fields & USERMOD_FIELD_HOME_DIRECTORY) && + (*level == 0 || *level == 10)) { + *level = 10; + + if (queried) { + i->info10.home_directory.string = s->change.home_directory; + s->change.fields ^= USERMOD_FIELD_HOME_DIRECTORY; + } else { + return false; + } + } + + if ((s->change.fields & USERMOD_FIELD_HOME_DRIVE) && + (*level == 0 || *level == 10)) { + *level = 10; + + if (queried) { + i->info10.home_drive.string = s->change.home_drive; + s->change.fields ^= USERMOD_FIELD_HOME_DRIVE; + } else { + return false; + } + } + + if ((s->change.fields & USERMOD_FIELD_ACCT_EXPIRY) && + (*level == 0 || *level == 17)) { + *level = 17; + i->info17.acct_expiry = timeval_to_nttime(s->change.acct_expiry); + + s->change.fields ^= USERMOD_FIELD_ACCT_EXPIRY; + } + + if ((s->change.fields & USERMOD_FIELD_ACCT_FLAGS) && + (*level == 0 || *level == 16)) { + *level = 16; + i->info16.acct_flags = s->change.acct_flags; + + s->change.fields ^= USERMOD_FIELD_ACCT_FLAGS; + } + + /* We're going to be here back again soon unless all fields have been set */ + return true; +} + + +static NTSTATUS usermod_change(struct composite_context *c, + struct usermod_state *s) +{ + bool do_set; + union samr_UserInfo *i = &s->info; + struct tevent_req *subreq; + + /* set the level to invalid value, so that unless setfields routine + gives it a valid value we report the error correctly */ + uint16_t level = 27; + + /* prepare UserInfo level and data based on bitmask field */ + do_set = usermod_setfields(s, &level, i, false); + + if (level < 1 || level > 26) { + /* apparently there's a field that the setfields routine + does not know how to set */ + return NT_STATUS_INVALID_PARAMETER; + } + + /* If some specific level is used to set user account data and the change + itself does not cover all fields then we need to query the user info + first, right before changing the data. Otherwise we could set required + fields and accidentally reset the others. + */ + if (!do_set) { + s->queryuser.in.user_handle = &s->user_handle; + s->queryuser.in.level = level; + s->queryuser.out.info = talloc(s, union samr_UserInfo *); + if (composite_nomem(s->queryuser.out.info, c)) return NT_STATUS_NO_MEMORY; + + + /* send query user info request to retrieve complete data of + a particular info level */ + subreq = dcerpc_samr_QueryUserInfo_r_send(s, c->event_ctx, + s->binding_handle, + &s->queryuser); + if (composite_nomem(subreq, c)) return NT_STATUS_NO_MEMORY; + tevent_req_set_callback(subreq, continue_usermod_user_queried, c); + + } else { + s->setuser.in.user_handle = &s->user_handle; + s->setuser.in.level = level; + s->setuser.in.info = i; + + /* send set user info request after making required change */ + subreq = dcerpc_samr_SetUserInfo_r_send(s, c->event_ctx, + s->binding_handle, + &s->setuser); + if (composite_nomem(subreq, c)) return NT_STATUS_NO_MEMORY; + tevent_req_set_callback(subreq, continue_usermod_user_changed, c); + } + + return NT_STATUS_OK; +} + + +/** + * Stage 2: Open user account + */ +static void continue_usermod_user_opened(struct tevent_req *subreq) +{ + struct composite_context *c; + struct usermod_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct usermod_state); + + c->status = dcerpc_samr_OpenUser_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + c->status = s->openuser.out.result; + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + c->status = usermod_change(c, s); +} + + +/** + * Stage 2a (optional): Query the user information + */ +static void continue_usermod_user_queried(struct tevent_req *subreq) +{ + struct composite_context *c; + struct usermod_state *s; + union samr_UserInfo *i; + uint16_t level = 0; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct usermod_state); + + i = &s->info; + + /* receive samr_QueryUserInfo result */ + c->status = dcerpc_samr_QueryUserInfo_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + c->status = s->queryuser.out.result; + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + /* get returned user data and make a change (potentially one + of many) */ + s->info = *(*s->queryuser.out.info); + + usermod_setfields(s, &level, i, true); + + /* prepare rpc call arguments */ + s->setuser.in.user_handle = &s->user_handle; + s->setuser.in.level = level; + s->setuser.in.info = i; + + /* send the rpc request */ + subreq = dcerpc_samr_SetUserInfo_r_send(s, c->event_ctx, + s->binding_handle, + &s->setuser); + if (composite_nomem(subreq, c)) return; + tevent_req_set_callback(subreq, continue_usermod_user_changed, c); +} + + +/** + * Stage 3: Set new user account data + */ +static void continue_usermod_user_changed(struct tevent_req *subreq) +{ + struct composite_context *c; + struct usermod_state *s; + + c = tevent_req_callback_data(subreq, struct composite_context); + s = talloc_get_type(c->private_data, struct usermod_state); + + /* receive samr_SetUserInfo result */ + c->status = dcerpc_samr_SetUserInfo_r_recv(subreq, s); + TALLOC_FREE(subreq); + if (!composite_is_ok(c)) return; + + /* return the actual function call status */ + c->status = s->setuser.out.result; + if (!NT_STATUS_IS_OK(c->status)) { + composite_error(c, c->status); + return; + } + + if (s->change.fields == 0) { + /* all fields have been set - we're done */ + composite_done(c); + + } else { + /* something's still not changed - repeat the procedure */ + c->status = usermod_change(c, s); + } +} + + +/** + * Sends asynchronous usermod request + * + * @param p dce/rpc call pipe + * @param io arguments and results of the call + * @param monitor monitor function for providing information about the progress + */ + +struct composite_context *libnet_rpc_usermod_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *b, + struct libnet_rpc_usermod *io, + void (*monitor)(struct monitor_msg*)) +{ + struct composite_context *c; + struct usermod_state *s; + struct tevent_req *subreq; + + /* composite context allocation and setup */ + c = composite_create(mem_ctx, ev); + if (c == NULL) return NULL; + s = talloc_zero(c, struct usermod_state); + if (composite_nomem(s, c)) return c; + + c->private_data = s; + + /* store parameters in the call structure */ + s->binding_handle= b; + s->domain_handle = io->in.domain_handle; + s->change = io->in.change; + s->monitor_fn = monitor; + + /* prepare rpc call arguments */ + s->lookupname.in.domain_handle = &io->in.domain_handle; + s->lookupname.in.num_names = 1; + s->lookupname.in.names = talloc_zero(s, struct lsa_String); + s->lookupname.in.names->string = io->in.username; + s->lookupname.out.rids = talloc_zero(s, struct samr_Ids); + s->lookupname.out.types = talloc_zero(s, struct samr_Ids); + if (composite_nomem(s->lookupname.out.rids, c)) return c; + if (composite_nomem(s->lookupname.out.types, c)) return c; + + /* send the rpc request */ + subreq = dcerpc_samr_LookupNames_r_send(s, c->event_ctx, + s->binding_handle, + &s->lookupname); + if (composite_nomem(subreq, c)) return c; + + /* callback handler setup */ + tevent_req_set_callback(subreq, continue_usermod_name_found, c); + return c; +} + + +/** + * Waits for and receives results of asynchronous usermod call + * + * @param c composite context returned by asynchronous usermod call + * @param mem_ctx memory context of the call + * @param io pointer to results (and arguments) of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_usermod_recv(struct composite_context *c, TALLOC_CTX *mem_ctx, + struct libnet_rpc_usermod *io) +{ + NTSTATUS status; + + status = composite_wait(c); + + talloc_free(c); + return status; +} + + +/** + * Synchronous version of usermod call + * + * @param pipe dce/rpc call pipe + * @param mem_ctx memory context for the call + * @param io arguments and results of the call + * @return nt status code of execution + */ + +NTSTATUS libnet_rpc_usermod(struct tevent_context *ev, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct libnet_rpc_usermod *io) +{ + struct composite_context *c = libnet_rpc_usermod_send(mem_ctx, ev, b, io, NULL); + return libnet_rpc_usermod_recv(c, mem_ctx, io); +} diff --git a/source4/libnet/userman.h b/source4/libnet/userman.h new file mode 100644 index 0000000..b681c25 --- /dev/null +++ b/source4/libnet/userman.h @@ -0,0 +1,106 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Rafal Szczesniak 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "librpc/gen_ndr/misc.h" + + +/* + * IO structures for userman.c functions + */ + +struct libnet_rpc_useradd { + struct { + struct policy_handle domain_handle; + const char *username; + } in; + struct { + struct policy_handle user_handle; + } out; +}; + + +struct libnet_rpc_userdel { + struct { + struct policy_handle domain_handle; + const char *username; + } in; + struct { + struct policy_handle user_handle; + } out; +}; + + +#define USERMOD_FIELD_ACCOUNT_NAME ( 0x00000001 ) +#define USERMOD_FIELD_FULL_NAME ( 0x00000002 ) +#define USERMOD_FIELD_DESCRIPTION ( 0x00000010 ) +#define USERMOD_FIELD_COMMENT ( 0x00000020 ) +#define USERMOD_FIELD_HOME_DIRECTORY ( 0x00000040 ) +#define USERMOD_FIELD_HOME_DRIVE ( 0x00000080 ) +#define USERMOD_FIELD_LOGON_SCRIPT ( 0x00000100 ) +#define USERMOD_FIELD_PROFILE_PATH ( 0x00000200 ) +#define USERMOD_FIELD_WORKSTATIONS ( 0x00000400 ) +#define USERMOD_FIELD_LOGON_HOURS ( 0x00002000 ) +#define USERMOD_FIELD_ACCT_EXPIRY ( 0x00004000 ) +#define USERMOD_FIELD_ACCT_FLAGS ( 0x00100000 ) +#define USERMOD_FIELD_PARAMETERS ( 0x00200000 ) +#define USERMOD_FIELD_COUNTRY_CODE ( 0x00400000 ) +#define USERMOD_FIELD_CODE_PAGE ( 0x00800000 ) + +struct libnet_rpc_usermod { + struct { + struct policy_handle domain_handle; + const char *username; + + struct usermod_change { + uint32_t fields; /* bitmask field */ + + const char *account_name; + const char *full_name; + const char *description; + const char *comment; + const char *logon_script; + const char *profile_path; + const char *home_directory; + const char *home_drive; + const char *workstations; + struct timeval *acct_expiry; + struct timeval *allow_password_change; + struct timeval *force_password_change; + struct timeval *last_logon; + struct timeval *last_logoff; + struct timeval *last_password_change; + uint32_t acct_flags; + } change; + } in; +}; + + +/* + * Monitor messages sent from userman.c functions + */ + +struct msg_rpc_create_user { + uint32_t rid; +}; + + +struct msg_rpc_lookup_name { + uint32_t *rid; + uint32_t count; +}; diff --git a/source4/libnet/wscript_build b/source4/libnet/wscript_build new file mode 100644 index 0000000..0ec06f2 --- /dev/null +++ b/source4/libnet/wscript_build @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +pytalloc_util = bld.pyembed_libname('pytalloc-util') +pyrpc_util = bld.pyembed_libname('pyrpc_util') +provision = bld.pyembed_libname('PROVISION') +name = bld.pyembed_libname('samba-net') +auto_proto='libnet_proto.h' +bld.SAMBA_LIBRARY(name, + source='libnet.c libnet_passwd.c libnet_time.c libnet_rpc.c libnet_join.c libnet_site.c libnet_become_dc.c libnet_unbecome_dc.c libnet_vampire.c libnet_user.c libnet_group.c libnet_share.c libnet_lookup.c libnet_domain.c userinfo.c groupinfo.c userman.c groupman.c prereq_domain.c', + autoproto=auto_proto, + deps='INIT_SAMR', + public_deps='samba-credentials dcerpc dcerpc-samr RPC_NDR_LSA RPC_NDR_SRVSVC RPC_NDR_DRSUAPI cli_composite LIBCLI_RESOLVE LIBCLI_FINDDCS cli_cldap LIBCLI_FINDDCS gensec_schannel LIBCLI_AUTH ndr smbpasswdparser %s LIBCLI_SAMSYNC LIBTSOCKET GNUTLS_HELPERS' % (provision), + private_library=True, + pyembed=True, + enabled=bld.PYTHON_BUILD_IS_ENABLED() + ) + +bld.SAMBA_PYTHON('python_net', + source='py_net.c', + deps='%s %s %s' % (name, pyrpc_util, pytalloc_util), + realname='samba/net.so' + ) + +bld.SAMBA_PYTHON('python_dckeytab', + source='py_net_dckeytab.c libnet_export_keytab.c', + deps='%s db-glue krb5 com_err' % (pyrpc_util), + realname='samba/dckeytab.so', + enabled=bld.CONFIG_SET('AD_DC_BUILD_IS_ENABLED') + ) |