2 Unix SMB/CIFS implementation.
4 Copyright (C) Stefan Metzmacher 2004
5 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
6 Copyright (C) Brad Henry 2005
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include "libnet/libnet.h"
25 #include "librpc/gen_ndr/ndr_drsuapi.h"
26 #include "librpc/gen_ndr/ndr_drsuapi_c.h"
27 #include "lib/ldb/include/ldb.h"
28 #include "lib/ldb/include/ldb_errors.h"
29 #include "passdb/secrets.h"
30 #include "dsdb/samdb/samdb.h"
32 #include "libcli/security/proto.h"
33 #include "auth/credentials/credentials.h"
34 #include "librpc/gen_ndr/ndr_samr_c.h"
37 * complete a domain join, when joining to a AD domain:
38 * 1.) connect and bind to the DRSUAPI pipe
39 * 2.) do a DsCrackNames() to find the machine account dn
41 * 4.) do an ldap search to find the "msDS-KeyVersionNumber" of the machine account
42 * 5.) set the servicePrincipalName's of the machine account via LDAP, (maybe we should use DsWriteAccountSpn()...)
43 * 6.) do a DsCrackNames() to find the domain dn
44 * 7.) find out Site specific stuff, look at libnet_JoinSite() for details
46 static NTSTATUS libnet_JoinADSDomain(struct libnet_context *ctx, struct libnet_JoinDomain *r)
52 const char *realm = r->out.realm;
54 struct dcerpc_binding *samr_binding = r->out.samr_binding;
56 struct dcerpc_pipe *drsuapi_pipe;
57 struct dcerpc_binding *drsuapi_binding;
58 struct drsuapi_DsBind r_drsuapi_bind;
59 struct drsuapi_DsCrackNames r_crack_names;
60 struct drsuapi_DsNameString names[1];
61 struct policy_handle drsuapi_bind_handle;
62 struct GUID drsuapi_bind_guid;
64 struct ldb_context *remote_ldb;
65 const struct ldb_dn *account_dn;
66 const char *account_dn_str;
67 const char *remote_ldb_url;
68 struct ldb_result *res;
69 struct ldb_message *msg;
75 const char * const attrs[] = {
76 "msDS-KeyVersionNumber",
77 "servicePrincipalName",
82 r->out.error_string = NULL;
84 /* We need to convert between a samAccountName and domain to a
85 * DN in the directory. The correct way to do this is with
86 * DRSUAPI CrackNames */
88 /* Fiddle with the bindings, so get to DRSUAPI on
89 * NCACN_IP_TCP, sealed */
90 tmp_ctx = talloc_named(r, 0, "libnet_JoinADSDomain temp context");
92 r->out.error_string = NULL;
93 return NT_STATUS_NO_MEMORY;
96 drsuapi_binding = talloc(tmp_ctx, struct dcerpc_binding);
97 if (!drsuapi_binding) {
98 r->out.error_string = NULL;
100 return NT_STATUS_NO_MEMORY;
103 *drsuapi_binding = *samr_binding;
105 /* DRSUAPI is only available on IP_TCP, and locally on NCALRPC */
106 if (drsuapi_binding->transport != NCALRPC) {
107 drsuapi_binding->transport = NCACN_IP_TCP;
109 drsuapi_binding->endpoint = NULL;
110 drsuapi_binding->flags |= DCERPC_SEAL;
112 status = dcerpc_pipe_connect_b(tmp_ctx,
115 &dcerpc_table_drsuapi,
118 if (!NT_STATUS_IS_OK(status)) {
119 r->out.error_string = talloc_asprintf(r,
120 "Connection to DRSUAPI pipe of PDC of domain '%s' failed: %s",
123 talloc_free(tmp_ctx);
127 /* get a DRSUAPI pipe handle */
128 GUID_from_string(DRSUAPI_DS_BIND_GUID, &drsuapi_bind_guid);
130 r_drsuapi_bind.in.bind_guid = &drsuapi_bind_guid;
131 r_drsuapi_bind.in.bind_info = NULL;
132 r_drsuapi_bind.out.bind_handle = &drsuapi_bind_handle;
134 status = dcerpc_drsuapi_DsBind(drsuapi_pipe, tmp_ctx, &r_drsuapi_bind);
135 if (!NT_STATUS_IS_OK(status)) {
136 if (NT_STATUS_EQUAL(status, NT_STATUS_NET_WRITE_FAULT)) {
139 "dcerpc_drsuapi_DsBind for [%s\\%s] failed - %s\n",
140 r->in.domain_name, r->in.account_name,
141 dcerpc_errstr(tmp_ctx, drsuapi_pipe->last_fault_code));
142 talloc_free(tmp_ctx);
147 "dcerpc_drsuapi_DsBind for [%s\\%s] failed - %s\n",
148 r->in.domain_name, r->in.account_name,
150 talloc_free(tmp_ctx);
153 } else if (!W_ERROR_IS_OK(r_drsuapi_bind.out.result)) {
156 "DsBind failed - %s\n",
157 win_errstr(r_drsuapi_bind.out.result));
158 talloc_free(tmp_ctx);
159 return NT_STATUS_UNSUCCESSFUL;
162 /* Actually 'crack' the names */
163 ZERO_STRUCT(r_crack_names);
164 r_crack_names.in.bind_handle = &drsuapi_bind_handle;
165 r_crack_names.in.level = 1;
166 r_crack_names.in.req.req1.unknown1 = 0x000004e4;
167 r_crack_names.in.req.req1.unknown2 = 0x00000407;
168 r_crack_names.in.req.req1.count = 1;
169 r_crack_names.in.req.req1.names = names;
170 r_crack_names.in.req.req1.format_flags = DRSUAPI_DS_NAME_FLAG_NO_FLAGS;
171 r_crack_names.in.req.req1.format_offered= DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY;
172 r_crack_names.in.req.req1.format_desired= DRSUAPI_DS_NAME_FORMAT_FQDN_1779;
173 names[0].str = dom_sid_string(tmp_ctx, r->out.account_sid);
175 r->out.error_string = NULL;
176 talloc_free(tmp_ctx);
177 return NT_STATUS_NO_MEMORY;
180 status = dcerpc_drsuapi_DsCrackNames(drsuapi_pipe, tmp_ctx, &r_crack_names);
181 if (!NT_STATUS_IS_OK(status)) {
182 if (NT_STATUS_EQUAL(status, NT_STATUS_NET_WRITE_FAULT)) {
185 "dcerpc_drsuapi_DsCrackNames for [%s] failed - %s\n",
187 dcerpc_errstr(tmp_ctx, drsuapi_pipe->last_fault_code));
188 talloc_free(tmp_ctx);
193 "dcerpc_drsuapi_DsCrackNames for [%s] failed - %s\n",
196 talloc_free(tmp_ctx);
199 } else if (!W_ERROR_IS_OK(r_crack_names.out.result)) {
202 "DsCrackNames failed - %s\n", win_errstr(r_crack_names.out.result));
203 talloc_free(tmp_ctx);
204 return NT_STATUS_UNSUCCESSFUL;
205 } else if (r_crack_names.out.level != 1
206 || !r_crack_names.out.ctr.ctr1
207 || r_crack_names.out.ctr.ctr1->count != 1
208 || !r_crack_names.out.ctr.ctr1->array[0].result_name
209 || r_crack_names.out.ctr.ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) {
210 r->out.error_string = talloc_asprintf(r, "DsCrackNames failed\n");
211 talloc_free(tmp_ctx);
212 return NT_STATUS_UNSUCCESSFUL;
215 /* Store the DN of our machine account. */
216 account_dn_str = r_crack_names.out.ctr.ctr1->array[0].result_name;
218 account_dn = ldb_dn_explode(tmp_ctx, account_dn_str);
220 r->out.error_string = talloc_asprintf(r, "Invalid account dn: %s",
222 talloc_free(tmp_ctx);
223 return NT_STATUS_UNSUCCESSFUL;
226 /* Now we know the user's DN, open with LDAP, read and modify a few things */
228 remote_ldb_url = talloc_asprintf(tmp_ctx, "ldap://%s",
229 drsuapi_binding->host);
230 if (!remote_ldb_url) {
231 r->out.error_string = NULL;
232 talloc_free(tmp_ctx);
233 return NT_STATUS_NO_MEMORY;
236 remote_ldb = ldb_wrap_connect(tmp_ctx, remote_ldb_url,
237 NULL, ctx->cred, 0, NULL);
239 r->out.error_string = NULL;
240 talloc_free(tmp_ctx);
241 return NT_STATUS_UNSUCCESSFUL;
244 /* search for the user's record */
245 ret = ldb_search(remote_ldb, account_dn, LDB_SCOPE_BASE,
247 if (ret != LDB_SUCCESS || res->count != 1) {
248 r->out.error_string = talloc_asprintf(r, "ldb_search for %s failed - %s\n",
249 account_dn_str, ldb_errstring(remote_ldb));
250 talloc_free(tmp_ctx);
251 return NT_STATUS_UNSUCCESSFUL;
254 /* If we have a kvno recorded in AD, we need it locally as well */
255 kvno = ldb_msg_find_uint(res->msgs[0], "msDS-KeyVersionNumber", 0);
257 /* Prepare a new message, for the modify */
258 msg = ldb_msg_new(tmp_ctx);
260 r->out.error_string = NULL;
261 talloc_free(tmp_ctx);
262 return NT_STATUS_NO_MEMORY;
264 msg->dn = res->msgs[0]->dn;
268 const char *service_principal_name[6];
269 const char *dns_host_name = strlower_talloc(tmp_ctx,
270 talloc_asprintf(tmp_ctx,
275 if (!dns_host_name) {
276 r->out.error_string = NULL;
277 talloc_free(tmp_ctx);
278 return NT_STATUS_NO_MEMORY;
281 service_principal_name[0] = talloc_asprintf(tmp_ctx, "host/%s", dns_host_name);
282 service_principal_name[1] = talloc_asprintf(tmp_ctx, "host/%s", strlower_talloc(tmp_ctx, r->in.netbios_name));
283 service_principal_name[2] = talloc_asprintf(tmp_ctx, "host/%s/%s", dns_host_name, realm);
284 service_principal_name[3] = talloc_asprintf(tmp_ctx, "host/%s/%s", strlower_talloc(tmp_ctx, r->in.netbios_name), realm);
285 service_principal_name[4] = talloc_asprintf(tmp_ctx, "host/%s/%s", dns_host_name, r->out.domain_name);
286 service_principal_name[5] = talloc_asprintf(tmp_ctx, "host/%s/%s", strlower_talloc(tmp_ctx, r->in.netbios_name), r->out.domain_name);
288 for (i=0; i < ARRAY_SIZE(service_principal_name); i++) {
289 if (!service_principal_name[i]) {
290 r->out.error_string = NULL;
291 talloc_free(tmp_ctx);
292 return NT_STATUS_NO_MEMORY;
294 rtn = samdb_msg_add_string(remote_ldb, tmp_ctx, msg, "servicePrincipalName", service_principal_name[i]);
296 r->out.error_string = NULL;
297 talloc_free(tmp_ctx);
298 return NT_STATUS_NO_MEMORY;
302 rtn = samdb_msg_add_string(remote_ldb, tmp_ctx, msg, "dNSHostName", dns_host_name);
304 r->out.error_string = NULL;
305 talloc_free(tmp_ctx);
306 return NT_STATUS_NO_MEMORY;
309 rtn = samdb_replace(remote_ldb, tmp_ctx, msg);
313 "Failed to replace entries on %s\n",
314 ldb_dn_linearize(tmp_ctx, msg->dn));
315 talloc_free(tmp_ctx);
316 return NT_STATUS_INTERNAL_DB_CORRUPTION;
320 /* DsCrackNames to find out the DN of the domain. */
321 r_crack_names.in.req.req1.format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT;
322 r_crack_names.in.req.req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779;
323 names[0].str = talloc_asprintf(tmp_ctx, "%s\\", r->out.domain_name);
325 r->out.error_string = NULL;
326 talloc_free(tmp_ctx);
327 return NT_STATUS_NO_MEMORY;
330 status = dcerpc_drsuapi_DsCrackNames(drsuapi_pipe, tmp_ctx, &r_crack_names);
331 if (!NT_STATUS_IS_OK(status)) {
332 if (NT_STATUS_EQUAL(status, NT_STATUS_NET_WRITE_FAULT)) {
335 "dcerpc_drsuapi_DsCrackNames for [%s] failed - %s\n",
337 dcerpc_errstr(tmp_ctx, drsuapi_pipe->last_fault_code));
338 talloc_free(tmp_ctx);
343 "dcerpc_drsuapi_DsCrackNames for [%s] failed - %s\n",
346 talloc_free(tmp_ctx);
349 } else if (!W_ERROR_IS_OK(r_crack_names.out.result)) {
352 "DsCrackNames failed - %s\n", win_errstr(r_crack_names.out.result));
353 talloc_free(tmp_ctx);
354 return NT_STATUS_UNSUCCESSFUL;
355 } else if (r_crack_names.out.level != 1
356 || !r_crack_names.out.ctr.ctr1
357 || r_crack_names.out.ctr.ctr1->count != 1
358 || !r_crack_names.out.ctr.ctr1->array[0].result_name
359 || r_crack_names.out.ctr.ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) {
360 r->out.error_string = talloc_asprintf(r, "DsCrackNames failed\n");
361 talloc_free(tmp_ctx);
362 return NT_STATUS_UNSUCCESSFUL;
365 /* Store the account DN. */
366 r->out.account_dn_str = account_dn_str;
367 talloc_steal(r, account_dn_str);
369 /* Store the domain DN. */
370 r->out.domain_dn_str = r_crack_names.out.ctr.ctr1->array[0].result_name;
371 talloc_steal(r, r_crack_names.out.ctr.ctr1->array[0].result_name);
375 if (r->in.acct_type == ACB_SVRTRUST) {
376 status = libnet_JoinSite(remote_ldb, r);
378 talloc_free(tmp_ctx);
384 * do a domain join using DCERPC/SAMR calls
385 * - connect to the LSA pipe, to try and find out information about the domain
386 * - create a secondary connection to SAMR pipe
387 * - do a samr_Connect to get a policy handle
388 * - do a samr_LookupDomain to get the domain sid
389 * - do a samr_OpenDomain to get a domain handle
390 * - do a samr_CreateAccount to try and get a new account
393 * - do a samr_LookupNames to get the users rid
394 * - do a samr_OpenUser to get a user handle
395 * - potentially delete and recreate the user
396 * - assert the account is of the right type with samrQueryUserInfo
398 * - call libnet_SetPassword_samr_handle to set the password
400 * - do a samrSetUserInfo to set the account flags
401 * - do some ADS specific things when we join as Domain Controller,
402 * look at libnet_joinADSDomain() for the details
404 NTSTATUS libnet_JoinDomain(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_JoinDomain *r)
408 NTSTATUS status, cu_status;
410 struct libnet_RpcConnectDCInfo *connect_with_info;
411 struct dcerpc_pipe *samr_pipe;
413 struct samr_Connect sc;
414 struct policy_handle p_handle;
415 struct samr_OpenDomain od;
416 struct policy_handle d_handle;
417 struct samr_LookupNames ln;
418 struct samr_OpenUser ou;
419 struct samr_CreateUser2 cu;
420 struct policy_handle *u_handle = NULL;
421 struct samr_QueryUserInfo qui;
422 struct samr_SetUserInfo sui;
423 union samr_UserInfo u_info;
424 union libnet_SetPassword r2;
425 struct samr_GetUserPwInfo pwp;
426 struct lsa_String samr_account_name;
428 uint32_t acct_flags, old_acct_flags;
429 uint32_t rid, access_granted;
430 int policy_min_pw_len = 0;
432 struct dom_sid *account_sid = NULL;
433 const char *password_str = NULL;
435 r->out.error_string = NULL;
436 r2.samr_handle.out.error_string = NULL;
438 tmp_ctx = talloc_named(mem_ctx, 0, "libnet_Join temp context");
440 r->out.error_string = NULL;
441 return NT_STATUS_NO_MEMORY;
444 u_handle = talloc(tmp_ctx, struct policy_handle);
446 r->out.error_string = NULL;
447 talloc_free(tmp_ctx);
448 return NT_STATUS_NO_MEMORY;
451 connect_with_info = talloc(tmp_ctx, struct libnet_RpcConnectDCInfo);
452 if (!connect_with_info) {
453 r->out.error_string = NULL;
454 talloc_free(tmp_ctx);
455 return NT_STATUS_NO_MEMORY;
458 /* prepare connect to the LSA pipe of PDC */
459 if (r->in.level == LIBNET_JOINDOMAIN_AUTOMATIC) {
460 connect_with_info->level = LIBNET_RPC_CONNECT_PDC;
461 connect_with_info->in.name = r->in.domain_name;
463 connect_with_info->level = LIBNET_RPC_CONNECT_BINDING;
464 connect_with_info->in.binding = r->in.binding;
467 connect_with_info->in.dcerpc_iface = &dcerpc_table_samr;
469 establish a SAMR connection, on the same CIFS transport
472 status = libnet_RpcConnectDCInfo(ctx, connect_with_info);
473 if (!NT_STATUS_IS_OK(status)) {
475 r->out.error_string = talloc_asprintf(mem_ctx,
476 "Connection to SAMR pipe of DC %s failed: %s",
477 r->in.binding, connect_with_info->out.error_string);
479 r->out.error_string = talloc_asprintf(mem_ctx,
480 "Connection to SAMR pipe of PDC for %s failed: %s",
481 r->in.domain_name, connect_with_info->out.error_string);
483 talloc_free(tmp_ctx);
487 samr_pipe = connect_with_info->out.dcerpc_pipe,
489 status = dcerpc_pipe_auth(samr_pipe,
490 connect_with_info->out.dcerpc_pipe->binding,
491 &dcerpc_table_samr, ctx->cred);
492 if (!NT_STATUS_IS_OK(status)) {
493 r->out.error_string = talloc_asprintf(mem_ctx,
494 "SAMR bind failed: %s",
496 talloc_free(tmp_ctx);
500 /* prepare samr_Connect */
501 ZERO_STRUCT(p_handle);
502 sc.in.system_name = NULL;
503 sc.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
504 sc.out.connect_handle = &p_handle;
506 /* 2. do a samr_Connect to get a policy handle */
507 status = dcerpc_samr_Connect(samr_pipe, tmp_ctx, &sc);
508 if (!NT_STATUS_IS_OK(status)) {
509 r->out.error_string = talloc_asprintf(mem_ctx,
510 "samr_Connect failed: %s",
512 talloc_free(tmp_ctx);
516 /* If this is a connection on ncacn_ip_tcp to Win2k3 SP1, we don't get back this useful info */
517 if (!connect_with_info->out.domain_name) {
518 if (r->in.level == LIBNET_JOINDOMAIN_AUTOMATIC) {
519 connect_with_info->out.domain_name = talloc_strdup(tmp_ctx, r->in.domain_name);
521 /* Bugger, we just lost our way to automaticly find the domain name */
522 connect_with_info->out.domain_name = talloc_strdup(tmp_ctx, lp_workgroup());
526 /* Perhaps we didn't get a SID above, because we are against ncacn_ip_tcp */
527 if (!connect_with_info->out.domain_sid) {
528 struct lsa_String name;
529 struct samr_LookupDomain l;
530 name.string = connect_with_info->out.domain_name;
531 l.in.connect_handle = &p_handle;
532 l.in.domain_name = &name;
534 status = dcerpc_samr_LookupDomain(samr_pipe, tmp_ctx, &l);
535 if (!NT_STATUS_IS_OK(status)) {
536 r->out.error_string = talloc_asprintf(mem_ctx,
537 "SAMR LookupDomain failed: %s",
539 talloc_free(tmp_ctx);
542 connect_with_info->out.domain_sid = l.out.sid;
545 /* prepare samr_OpenDomain */
546 ZERO_STRUCT(d_handle);
547 od.in.connect_handle = &p_handle;
548 od.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
549 od.in.sid = connect_with_info->out.domain_sid;
550 od.out.domain_handle = &d_handle;
552 /* do a samr_OpenDomain to get a domain handle */
553 status = dcerpc_samr_OpenDomain(samr_pipe, tmp_ctx, &od);
554 if (!NT_STATUS_IS_OK(status)) {
555 r->out.error_string = talloc_asprintf(mem_ctx,
556 "samr_OpenDomain for [%s] failed: %s",
557 dom_sid_string(tmp_ctx, connect_with_info->out.domain_sid),
559 talloc_free(tmp_ctx);
563 /* prepare samr_CreateUser2 */
564 ZERO_STRUCTP(u_handle);
565 cu.in.domain_handle = &d_handle;
566 cu.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
567 samr_account_name.string = r->in.account_name;
568 cu.in.account_name = &samr_account_name;
569 cu.in.acct_flags = r->in.acct_type;
570 cu.out.user_handle = u_handle;
572 cu.out.access_granted = &access_granted;
574 /* do a samr_CreateUser2 to get an account handle, or an error */
575 cu_status = dcerpc_samr_CreateUser2(samr_pipe, tmp_ctx, &cu);
577 if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) {
578 /* prepare samr_LookupNames */
579 ln.in.domain_handle = &d_handle;
581 ln.in.names = talloc_array(tmp_ctx, struct lsa_String, 1);
583 r->out.error_string = NULL;
584 talloc_free(tmp_ctx);
585 return NT_STATUS_NO_MEMORY;
587 ln.in.names[0].string = r->in.account_name;
589 /* 5. do a samr_LookupNames to get the users rid */
590 status = dcerpc_samr_LookupNames(samr_pipe, tmp_ctx, &ln);
591 if (!NT_STATUS_IS_OK(status)) {
592 r->out.error_string = talloc_asprintf(mem_ctx,
593 "samr_LookupNames for [%s] failed: %s",
596 talloc_free(tmp_ctx);
600 /* check if we got one RID for the user */
601 if (ln.out.rids.count != 1) {
602 r->out.error_string = talloc_asprintf(mem_ctx,
603 "samr_LookupNames for [%s] returns %d RIDs\n",
604 r->in.account_name, ln.out.rids.count);
605 talloc_free(tmp_ctx);
606 return NT_STATUS_INVALID_PARAMETER;
609 /* prepare samr_OpenUser */
610 ZERO_STRUCTP(u_handle);
611 ou.in.domain_handle = &d_handle;
612 ou.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
613 ou.in.rid = ln.out.rids.ids[0];
615 ou.out.user_handle = u_handle;
617 /* 6. do a samr_OpenUser to get a user handle */
618 status = dcerpc_samr_OpenUser(samr_pipe, tmp_ctx, &ou);
619 if (!NT_STATUS_IS_OK(status)) {
620 r->out.error_string = talloc_asprintf(mem_ctx,
621 "samr_OpenUser for [%s] failed: %s",
624 talloc_free(tmp_ctx);
628 if (r->in.recreate_account) {
629 struct samr_DeleteUser d;
630 d.in.user_handle = u_handle;
631 d.out.user_handle = u_handle;
632 status = dcerpc_samr_DeleteUser(samr_pipe, mem_ctx, &d);
633 if (!NT_STATUS_IS_OK(status)) {
634 r->out.error_string = talloc_asprintf(mem_ctx,
635 "samr_DeleteUser (for recreate) of [%s] failed: %s",
638 talloc_free(tmp_ctx);
642 /* We want to recreate, so delete and another samr_CreateUser2 */
644 /* &cu filled in above */
645 status = dcerpc_samr_CreateUser2(samr_pipe, tmp_ctx, &cu);
646 if (!NT_STATUS_IS_OK(status)) {
647 r->out.error_string = talloc_asprintf(mem_ctx,
648 "samr_CreateUser2 (recreate) for [%s] failed: %s\n",
649 r->in.domain_name, nt_errstr(status));
650 talloc_free(tmp_ctx);
654 } else if (!NT_STATUS_IS_OK(status)) {
655 r->out.error_string = talloc_asprintf(mem_ctx,
656 "samr_CreateUser2 for [%s] failed: %s\n",
657 r->in.domain_name, nt_errstr(status));
658 talloc_free(tmp_ctx);
662 /* prepare samr_QueryUserInfo (get flags) */
663 qui.in.user_handle = u_handle;
666 status = dcerpc_samr_QueryUserInfo(samr_pipe, tmp_ctx, &qui);
667 if (!NT_STATUS_IS_OK(status)) {
668 r->out.error_string = talloc_asprintf(mem_ctx,
669 "samr_QueryUserInfo for [%s] failed: %s",
672 talloc_free(tmp_ctx);
677 status = NT_STATUS_INVALID_PARAMETER;
679 = talloc_asprintf(mem_ctx,
680 "samr_QueryUserInfo failed to return qui.out.info for [%s]: %s\n",
681 r->in.account_name, nt_errstr(status));
682 talloc_free(tmp_ctx);
686 old_acct_flags = (qui.out.info->info16.acct_flags & (ACB_WSTRUST | ACB_SVRTRUST | ACB_DOMTRUST));
687 /* Possibly bail if the account is of the wrong type */
689 != r->in.acct_type) {
690 const char *old_account_type, *new_account_type;
691 switch (old_acct_flags) {
693 old_account_type = "domain member (member)";
696 old_account_type = "domain controller (bdc)";
699 old_account_type = "trusted domain";
702 return NT_STATUS_INVALID_PARAMETER;
704 switch (r->in.acct_type) {
706 new_account_type = "domain member (member)";
709 new_account_type = "domain controller (bdc)";
712 new_account_type = "trusted domain";
715 return NT_STATUS_INVALID_PARAMETER;
718 if (!NT_STATUS_EQUAL(cu_status, NT_STATUS_USER_EXISTS)) {
719 /* We created a new user, but they didn't come out the right type?!? */
721 = talloc_asprintf(mem_ctx,
722 "We asked to create a new machine account (%s) of type %s, "
723 "but we got an account of type %s. This is unexpected. "
724 "Perhaps delete the account and try again.\n",
725 r->in.account_name, new_account_type, old_account_type);
726 talloc_free(tmp_ctx);
727 return NT_STATUS_INVALID_PARAMETER;
729 /* The account is of the wrong type, so bail */
731 /* TODO: We should allow a --force option to override, and redo this from the top setting r.in.recreate_account */
733 = talloc_asprintf(mem_ctx,
734 "The machine account (%s) already exists in the domain %s, "
735 "but is a %s. You asked to join as a %s. Please delete "
736 "the account and try again.\n",
737 r->in.account_name, connect_with_info->out.domain_name, old_account_type, new_account_type);
738 talloc_free(tmp_ctx);
739 return NT_STATUS_USER_EXISTS;
742 acct_flags = qui.out.info->info16.acct_flags;
745 acct_flags = (acct_flags & ~ACB_DISABLED);
747 /* Find out what password policy this user has */
748 pwp.in.user_handle = u_handle;
750 status = dcerpc_samr_GetUserPwInfo(samr_pipe, tmp_ctx, &pwp);
751 if (NT_STATUS_IS_OK(status)) {
752 policy_min_pw_len = pwp.out.info.min_password_length;
755 /* Grab a password of that minimum length */
757 password_str = generate_random_str(tmp_ctx, MAX(8, policy_min_pw_len));
759 r2.samr_handle.level = LIBNET_SET_PASSWORD_SAMR_HANDLE;
760 r2.samr_handle.in.account_name = r->in.account_name;
761 r2.samr_handle.in.newpassword = password_str;
762 r2.samr_handle.in.user_handle = u_handle;
763 r2.samr_handle.in.dcerpc_pipe = samr_pipe;
765 status = libnet_SetPassword(ctx, tmp_ctx, &r2);
766 if (!NT_STATUS_IS_OK(status)) {
767 r->out.error_string = talloc_steal(mem_ctx, r2.samr_handle.out.error_string);
768 talloc_free(tmp_ctx);
772 /* reset flags (if required) */
773 if (acct_flags != qui.out.info->info16.acct_flags) {
775 u_info.info16.acct_flags = acct_flags;
777 sui.in.user_handle = u_handle;
778 sui.in.info = &u_info;
781 dcerpc_samr_SetUserInfo(samr_pipe, tmp_ctx, &sui);
782 if (!NT_STATUS_IS_OK(status)) {
783 r->out.error_string = talloc_asprintf(mem_ctx,
784 "samr_SetUserInfo for [%s] failed to remove ACB_DISABLED flag: %s",
787 talloc_free(tmp_ctx);
792 account_sid = dom_sid_add_rid(mem_ctx, connect_with_info->out.domain_sid, rid);
794 r->out.error_string = NULL;
795 talloc_free(tmp_ctx);
796 return NT_STATUS_NO_MEMORY;
799 /* Finish out by pushing various bits of status data out for the caller to use */
800 r->out.join_password = password_str;
801 talloc_steal(mem_ctx, r->out.join_password);
803 r->out.domain_sid = connect_with_info->out.domain_sid;
804 talloc_steal(mem_ctx, r->out.domain_sid);
806 r->out.account_sid = account_sid;
807 talloc_steal(mem_ctx, r->out.account_sid);
809 r->out.domain_name = connect_with_info->out.domain_name;
810 talloc_steal(mem_ctx, r->out.domain_name);
811 r->out.realm = connect_with_info->out.realm;
812 talloc_steal(mem_ctx, r->out.realm);
813 r->out.samr_pipe = samr_pipe;
814 talloc_steal(mem_ctx, samr_pipe);
815 r->out.samr_binding = samr_pipe->binding;
816 talloc_steal(mem_ctx, r->out.samr_binding);
817 r->out.user_handle = u_handle;
818 talloc_steal(mem_ctx, u_handle);
819 r->out.error_string = r2.samr_handle.out.error_string;
820 talloc_steal(mem_ctx, r2.samr_handle.out.error_string);
822 r->out.server_dn_str = NULL;
823 talloc_free(tmp_ctx);
825 /* Now, if it was AD, then we want to start looking changing a
826 * few more things. Otherwise, we are done. */
828 status = libnet_JoinADSDomain(ctx, r);
835 static NTSTATUS libnet_Join_primary_domain(struct libnet_context *ctx,
837 struct libnet_Join *r)
841 struct libnet_JoinDomain *r2;
843 struct ldb_context *ldb;
844 const struct ldb_dn *base_dn;
845 struct ldb_message **msgs, *msg;
847 const char * const attrs[] = {
856 uint32_t acct_type = 0;
857 const char *account_name;
858 const char *netbios_name;
861 r->out.error_string = NULL;
863 tmp_mem = talloc_new(mem_ctx);
865 return NT_STATUS_NO_MEMORY;
868 r2 = talloc(tmp_mem, struct libnet_JoinDomain);
870 r->out.error_string = NULL;
871 talloc_free(tmp_mem);
872 return NT_STATUS_NO_MEMORY;
875 if (r->in.join_type == SEC_CHAN_BDC) {
876 acct_type = ACB_SVRTRUST;
877 } else if (r->in.join_type == SEC_CHAN_WKSTA) {
878 acct_type = ACB_WSTRUST;
880 r->out.error_string = NULL;
881 talloc_free(tmp_mem);
882 return NT_STATUS_INVALID_PARAMETER;
885 if (r->in.netbios_name != NULL) {
886 netbios_name = r->in.netbios_name;
888 netbios_name = talloc_reference(tmp_mem, lp_netbios_name());
890 r->out.error_string = NULL;
891 talloc_free(tmp_mem);
892 return NT_STATUS_NO_MEMORY;
896 account_name = talloc_asprintf(tmp_mem, "%s$", netbios_name);
898 r->out.error_string = NULL;
899 talloc_free(tmp_mem);
900 return NT_STATUS_NO_MEMORY;
904 * Local secrets are stored in secrets.ldb
905 * open it to make sure we can write the info into it after the join
907 ldb = secrets_db_connect(tmp_mem);
910 = talloc_asprintf(mem_ctx,
911 "Could not open secrets database\n");
912 talloc_free(tmp_mem);
913 return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
920 r2->in.domain_name = r->in.domain_name;
921 r2->in.account_name = account_name;
922 r2->in.netbios_name = netbios_name;
923 r2->in.level = LIBNET_JOINDOMAIN_AUTOMATIC;
924 r2->in.acct_type = acct_type;
925 r2->in.recreate_account = False;
926 status = libnet_JoinDomain(ctx, r2, r2);
927 if (!NT_STATUS_IS_OK(status)) {
928 r->out.error_string = talloc_steal(mem_ctx, r2->out.error_string);
929 talloc_free(tmp_mem);
934 * now prepare the record for secrets.ldb
936 sct = talloc_asprintf(tmp_mem, "%d", r->in.join_type);
938 r->out.error_string = NULL;
939 talloc_free(tmp_mem);
940 return NT_STATUS_NO_MEMORY;
943 msg = ldb_msg_new(tmp_mem);
945 r->out.error_string = NULL;
946 talloc_free(tmp_mem);
947 return NT_STATUS_NO_MEMORY;
950 base_dn = ldb_dn_explode(tmp_mem, "cn=Primary Domains");
952 r->out.error_string = NULL;
953 talloc_free(tmp_mem);
954 return NT_STATUS_NO_MEMORY;
957 msg->dn = ldb_dn_build_child(tmp_mem, "flatname", r2->out.domain_name, base_dn);
959 r->out.error_string = NULL;
960 talloc_free(tmp_mem);
961 return NT_STATUS_NO_MEMORY;
964 rtn = samdb_msg_add_string(ldb, tmp_mem, msg, "flatname", r2->out.domain_name);
966 r->out.error_string = NULL;
967 talloc_free(tmp_mem);
968 return NT_STATUS_NO_MEMORY;
972 rtn = samdb_msg_add_string(ldb, tmp_mem, msg, "realm", r2->out.realm);
974 r->out.error_string = NULL;
975 talloc_free(tmp_mem);
976 return NT_STATUS_NO_MEMORY;
979 rtn = samdb_msg_add_string(ldb, tmp_mem, msg, "objectClass", "primaryDomain");
981 r->out.error_string = NULL;
982 talloc_free(tmp_mem);
983 return NT_STATUS_NO_MEMORY;
987 rtn = samdb_msg_add_string(ldb, tmp_mem, msg, "objectClass", "primaryDomain");
989 r->out.error_string = NULL;
990 talloc_free(tmp_mem);
991 return NT_STATUS_NO_MEMORY;
994 rtn = samdb_msg_add_string(ldb, tmp_mem, msg, "secret", r2->out.join_password);
996 r->out.error_string = NULL;
997 talloc_free(tmp_mem);
998 return NT_STATUS_NO_MEMORY;
1001 rtn = samdb_msg_add_string(ldb, tmp_mem, msg, "samAccountName", r2->in.account_name);
1003 r->out.error_string = NULL;
1004 talloc_free(tmp_mem);
1005 return NT_STATUS_NO_MEMORY;
1008 rtn = samdb_msg_add_string(ldb, tmp_mem, msg, "secureChannelType", sct);
1010 r->out.error_string = NULL;
1011 talloc_free(tmp_mem);
1012 return NT_STATUS_NO_MEMORY;
1016 rtn = samdb_msg_add_uint(ldb, tmp_mem, msg, "msDS-KeyVersionNumber",
1019 r->out.error_string = NULL;
1020 talloc_free(tmp_mem);
1021 return NT_STATUS_NO_MEMORY;
1025 if (r2->out.domain_sid) {
1026 rtn = samdb_msg_add_dom_sid(ldb, tmp_mem, msg, "objectSid",
1027 r2->out.domain_sid);
1029 r->out.error_string = NULL;
1030 talloc_free(tmp_mem);
1031 return NT_STATUS_NO_MEMORY;
1036 * search for the secret record
1037 * - remove the records we find
1038 * - and fetch the old secret and store it under priorSecret
1040 ret = gendb_search(ldb,
1043 "(|" SECRETS_PRIMARY_DOMAIN_FILTER "(realm=%s))",
1044 r2->out.domain_name, r2->out.realm);
1046 rtn = samdb_msg_set_string(ldb, tmp_mem, msg, "secretsKeytab", "secrets.keytab");
1048 r->out.error_string = NULL;
1049 talloc_free(tmp_mem);
1050 return NT_STATUS_NO_MEMORY;
1052 } else if (ret == -1) {
1054 = talloc_asprintf(mem_ctx,
1055 "Search for domain: %s and realm: %s failed: %s",
1056 r2->out.domain_name, r2->out.realm, ldb_errstring(ldb));
1057 talloc_free(tmp_mem);
1058 return NT_STATUS_INTERNAL_DB_CORRUPTION;
1060 const struct ldb_val *private_keytab;
1061 const struct ldb_val *krb5_keytab;
1062 const struct ldb_val *prior_secret;
1063 const struct ldb_val *prior_modified_time;
1066 for (i = 0; i < ret; i++) {
1067 ldb_delete(ldb, msgs[i]->dn);
1070 prior_secret = ldb_msg_find_ldb_val(msgs[0], "secret");
1072 rtn = samdb_msg_set_value(ldb, tmp_mem, msg, "priorSecret", prior_secret);
1074 r->out.error_string = NULL;
1075 talloc_free(tmp_mem);
1076 return NT_STATUS_NO_MEMORY;
1079 rtn = samdb_msg_set_string(ldb, tmp_mem, msg, "secret", r2->out.join_password);
1081 r->out.error_string = NULL;
1082 talloc_free(tmp_mem);
1083 return NT_STATUS_NO_MEMORY;
1086 prior_modified_time = ldb_msg_find_ldb_val(msgs[0],
1088 if (prior_modified_time) {
1089 rtn = samdb_msg_set_value(ldb, tmp_mem, msg, "priorWhenChanged",
1090 prior_modified_time);
1092 r->out.error_string = NULL;
1093 talloc_free(tmp_mem);
1094 return NT_STATUS_NO_MEMORY;
1098 rtn = samdb_msg_set_string(ldb, tmp_mem, msg, "samAccountName", r2->in.account_name);
1100 r->out.error_string = NULL;
1101 talloc_free(tmp_mem);
1102 return NT_STATUS_NO_MEMORY;
1105 rtn = samdb_msg_set_string(ldb, tmp_mem, msg, "secureChannelType", sct);
1107 r->out.error_string = NULL;
1108 talloc_free(tmp_mem);
1109 return NT_STATUS_NO_MEMORY;
1112 /* We will want to keep the keytab names */
1113 private_keytab = ldb_msg_find_ldb_val(msgs[0], "privateKeytab");
1114 if (private_keytab) {
1115 rtn = samdb_msg_set_value(ldb, tmp_mem, msg, "privateKeytab", private_keytab);
1117 r->out.error_string = NULL;
1118 talloc_free(tmp_mem);
1119 return NT_STATUS_NO_MEMORY;
1122 krb5_keytab = ldb_msg_find_ldb_val(msgs[0], "krb5Keytab");
1124 rtn = samdb_msg_set_value(ldb, tmp_mem, msg, "krb5Keytab", krb5_keytab);
1126 r->out.error_string = NULL;
1127 talloc_free(tmp_mem);
1128 return NT_STATUS_NO_MEMORY;
1133 /* create the secret */
1134 ret = samdb_add(ldb, tmp_mem, msg);
1136 r->out.error_string = talloc_asprintf(mem_ctx, "Failed to create secret record %s\n",
1137 ldb_dn_linearize(ldb, msg->dn));
1138 talloc_free(tmp_mem);
1139 return NT_STATUS_INTERNAL_DB_CORRUPTION;
1142 if (r2->out.realm) {
1143 struct cli_credentials *creds;
1144 /* Make a credentials structure from it */
1145 creds = cli_credentials_init(mem_ctx);
1147 r->out.error_string = NULL;
1148 talloc_free(tmp_mem);
1149 return NT_STATUS_NO_MEMORY;
1151 cli_credentials_set_conf(creds);
1152 filter = talloc_asprintf(mem_ctx, "dn=%s", ldb_dn_linearize(mem_ctx, msg->dn));
1153 status = cli_credentials_set_secrets(creds, NULL, filter);
1154 if (!NT_STATUS_IS_OK(status)) {
1155 r->out.error_string = talloc_asprintf(mem_ctx, "Failed to read secrets for keytab update for %s\n",
1157 talloc_free(tmp_mem);
1160 ret = cli_credentials_update_keytab(creds);
1162 r->out.error_string = talloc_asprintf(mem_ctx, "Failed to update keytab for %s\n",
1164 talloc_free(tmp_mem);
1165 return NT_STATUS_UNSUCCESSFUL;
1169 /* move all out parameter to the callers TALLOC_CTX */
1170 r->out.error_string = NULL;
1171 r->out.join_password = r2->out.join_password;
1172 talloc_steal(mem_ctx, r2->out.join_password);
1173 r->out.domain_sid = r2->out.domain_sid;
1174 talloc_steal(mem_ctx, r2->out.domain_sid);
1175 r->out.domain_name = r2->out.domain_name;
1176 talloc_steal(mem_ctx, r2->out.domain_name);
1177 talloc_free(tmp_mem);
1178 return NT_STATUS_OK;
1181 NTSTATUS libnet_Join(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_Join *r)
1183 switch (r->in.join_type) {
1184 case SEC_CHAN_WKSTA:
1185 return libnet_Join_primary_domain(ctx, mem_ctx, r);
1187 return libnet_Join_primary_domain(ctx, mem_ctx, r);
1188 case SEC_CHAN_DOMAIN:
1192 r->out.error_string = talloc_asprintf(mem_ctx,
1193 "Invalid join type specified (%08X) attempting to join domain %s",
1194 r->in.join_type, r->in.domain_name);
1195 return NT_STATUS_INVALID_PARAMETER;