/* * Unix SMB/CIFS implementation. * libnet Join Support * Copyright (C) Gerald (Jerry) Carter 2006 * Copyright (C) Guenther Deschner 2007-2008 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "includes.h" #include "libnet/libnet.h" /**************************************************************** ****************************************************************/ #define LIBNET_JOIN_DUMP_CTX(ctx, r, f) \ do { \ char *str = NULL; \ str = NDR_PRINT_FUNCTION_STRING(ctx, libnet_JoinCtx, f, r); \ DEBUG(1,("libnet_Join:\n%s", str)); \ TALLOC_FREE(str); \ } while (0) #define LIBNET_JOIN_IN_DUMP_CTX(ctx, r) \ LIBNET_JOIN_DUMP_CTX(ctx, r, NDR_IN | NDR_SET_VALUES) #define LIBNET_JOIN_OUT_DUMP_CTX(ctx, r) \ LIBNET_JOIN_DUMP_CTX(ctx, r, NDR_OUT) #define LIBNET_UNJOIN_DUMP_CTX(ctx, r, f) \ do { \ char *str = NULL; \ str = NDR_PRINT_FUNCTION_STRING(ctx, libnet_UnjoinCtx, f, r); \ DEBUG(1,("libnet_Unjoin:\n%s", str)); \ TALLOC_FREE(str); \ } while (0) #define LIBNET_UNJOIN_IN_DUMP_CTX(ctx, r) \ LIBNET_UNJOIN_DUMP_CTX(ctx, r, NDR_IN | NDR_SET_VALUES) #define LIBNET_UNJOIN_OUT_DUMP_CTX(ctx, r) \ LIBNET_UNJOIN_DUMP_CTX(ctx, r, NDR_OUT) #define W_ERROR_NOT_OK_GOTO_DONE(x) do { \ if (!W_ERROR_IS_OK(x)) {\ goto done;\ }\ } while (0) /**************************************************************** ****************************************************************/ static void libnet_join_set_error_string(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r, const char *format, ...) { va_list args; if (r->out.error_string) { return; } va_start(args, format); r->out.error_string = talloc_vasprintf(mem_ctx, format, args); va_end(args); } /**************************************************************** ****************************************************************/ static void libnet_unjoin_set_error_string(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx *r, const char *format, ...) { va_list args; if (r->out.error_string) { return; } va_start(args, format); r->out.error_string = talloc_vasprintf(mem_ctx, format, args); va_end(args); } #ifdef WITH_ADS /**************************************************************** ****************************************************************/ static ADS_STATUS libnet_connect_ads(const char *dns_domain_name, const char *netbios_domain_name, const char *dc_name, const char *user_name, const char *password, ADS_STRUCT **ads) { ADS_STATUS status; ADS_STRUCT *my_ads = NULL; my_ads = ads_init(dns_domain_name, netbios_domain_name, dc_name); if (!my_ads) { return ADS_ERROR_LDAP(LDAP_NO_MEMORY); } if (user_name) { SAFE_FREE(my_ads->auth.user_name); my_ads->auth.user_name = SMB_STRDUP(user_name); } if (password) { SAFE_FREE(my_ads->auth.password); my_ads->auth.password = SMB_STRDUP(password); } status = ads_connect(my_ads); if (!ADS_ERR_OK(status)) { ads_destroy(&my_ads); return status; } *ads = my_ads; return ADS_SUCCESS; } /**************************************************************** ****************************************************************/ static ADS_STATUS libnet_join_connect_ads(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { ADS_STATUS status; status = libnet_connect_ads(r->in.domain_name, r->in.domain_name, r->in.dc_name, r->in.admin_account, r->in.admin_password, &r->in.ads); if (!ADS_ERR_OK(status)) { libnet_join_set_error_string(mem_ctx, r, "failed to connect to AD: %s", ads_errstr(status)); return status; } if (!r->out.netbios_domain_name) { r->out.netbios_domain_name = talloc_strdup(mem_ctx, r->in.ads->server.workgroup); ADS_ERROR_HAVE_NO_MEMORY(r->out.netbios_domain_name); } if (!r->out.dns_domain_name) { r->out.dns_domain_name = talloc_strdup(mem_ctx, r->in.ads->config.realm); ADS_ERROR_HAVE_NO_MEMORY(r->out.dns_domain_name); } r->out.domain_is_ad = true; return ADS_SUCCESS; } /**************************************************************** ****************************************************************/ static ADS_STATUS libnet_unjoin_connect_ads(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx *r) { ADS_STATUS status; status = libnet_connect_ads(r->in.domain_name, r->in.domain_name, r->in.dc_name, r->in.admin_account, r->in.admin_password, &r->in.ads); if (!ADS_ERR_OK(status)) { libnet_unjoin_set_error_string(mem_ctx, r, "failed to connect to AD: %s", ads_errstr(status)); } return status; } /**************************************************************** join a domain using ADS (LDAP mods) ****************************************************************/ static ADS_STATUS libnet_join_precreate_machine_acct(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { ADS_STATUS status; LDAPMessage *res = NULL; const char *attrs[] = { "dn", NULL }; bool moved = false; status = ads_check_ou_dn(mem_ctx, r->in.ads, r->in.account_ou); if (!ADS_ERR_OK(status)) { return status; } status = ads_search_dn(r->in.ads, &res, r->in.account_ou, attrs); if (!ADS_ERR_OK(status)) { return status; } if (ads_count_replies(r->in.ads, res) != 1) { ads_msgfree(r->in.ads, res); return ADS_ERROR_LDAP(LDAP_NO_SUCH_OBJECT); } ads_msgfree(r->in.ads, res); /* Attempt to create the machine account and bail if this fails. Assume that the admin wants exactly what they requested */ status = ads_create_machine_acct(r->in.ads, r->in.machine_name, r->in.account_ou); if (ADS_ERR_OK(status)) { DEBUG(1,("machine account creation created\n")); return status; } else if ((status.error_type == ENUM_ADS_ERROR_LDAP) && (status.err.rc == LDAP_ALREADY_EXISTS)) { status = ADS_SUCCESS; } if (!ADS_ERR_OK(status)) { DEBUG(1,("machine account creation failed\n")); return status; } status = ads_move_machine_acct(r->in.ads, r->in.machine_name, r->in.account_ou, &moved); if (!ADS_ERR_OK(status)) { DEBUG(1,("failure to locate/move pre-existing " "machine account\n")); return status; } DEBUG(1,("The machine account %s the specified OU.\n", moved ? "was moved into" : "already exists in")); return status; } /**************************************************************** ****************************************************************/ static ADS_STATUS libnet_unjoin_remove_machine_acct(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx *r) { ADS_STATUS status; if (!r->in.ads) { return libnet_unjoin_connect_ads(mem_ctx, r); } status = ads_leave_realm(r->in.ads, r->in.machine_name); if (!ADS_ERR_OK(status)) { libnet_unjoin_set_error_string(mem_ctx, r, "failed to leave realm: %s", ads_errstr(status)); return status; } return ADS_SUCCESS; } /**************************************************************** ****************************************************************/ static ADS_STATUS libnet_join_find_machine_acct(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { ADS_STATUS status; LDAPMessage *res = NULL; char *dn = NULL; if (!r->in.machine_name) { return ADS_ERROR(LDAP_NO_MEMORY); } status = ads_find_machine_acct(r->in.ads, &res, r->in.machine_name); if (!ADS_ERR_OK(status)) { return status; } if (ads_count_replies(r->in.ads, res) != 1) { status = ADS_ERROR_LDAP(LDAP_NO_MEMORY); goto done; } dn = ads_get_dn(r->in.ads, res); if (!dn) { status = ADS_ERROR_LDAP(LDAP_NO_MEMORY); goto done; } r->out.dn = talloc_strdup(mem_ctx, dn); if (!r->out.dn) { status = ADS_ERROR_LDAP(LDAP_NO_MEMORY); goto done; } done: ads_msgfree(r->in.ads, res); ads_memfree(r->in.ads, dn); return status; } /**************************************************************** Set a machines dNSHostName and servicePrincipalName attributes ****************************************************************/ static ADS_STATUS libnet_join_set_machine_spn(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { ADS_STATUS status; ADS_MODLIST mods; fstring my_fqdn; const char *spn_array[3] = {NULL, NULL, NULL}; char *spn = NULL; /* Find our DN */ status = libnet_join_find_machine_acct(mem_ctx, r); if (!ADS_ERR_OK(status)) { return status; } /* Windows only creates HOST/shortname & HOST/fqdn. */ spn = talloc_asprintf(mem_ctx, "HOST/%s", r->in.machine_name); if (!spn) { return ADS_ERROR_LDAP(LDAP_NO_MEMORY); } strupper_m(spn); spn_array[0] = spn; if (name_to_fqdn(my_fqdn, r->in.machine_name) && !strequal(my_fqdn, r->in.machine_name)) { strlower_m(my_fqdn); spn = talloc_asprintf(mem_ctx, "HOST/%s", my_fqdn); if (!spn) { return ADS_ERROR_LDAP(LDAP_NO_MEMORY); } spn_array[1] = spn; } mods = ads_init_mods(mem_ctx); if (!mods) { return ADS_ERROR_LDAP(LDAP_NO_MEMORY); } /* fields of primary importance */ status = ads_mod_str(mem_ctx, &mods, "dNSHostName", my_fqdn); if (!ADS_ERR_OK(status)) { return ADS_ERROR_LDAP(LDAP_NO_MEMORY); } status = ads_mod_strlist(mem_ctx, &mods, "servicePrincipalName", spn_array); if (!ADS_ERR_OK(status)) { return ADS_ERROR_LDAP(LDAP_NO_MEMORY); } return ads_gen_mod(r->in.ads, r->out.dn, mods); } /**************************************************************** ****************************************************************/ static ADS_STATUS libnet_join_set_machine_upn(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { ADS_STATUS status; ADS_MODLIST mods; if (!r->in.create_upn) { return ADS_SUCCESS; } /* Find our DN */ status = libnet_join_find_machine_acct(mem_ctx, r); if (!ADS_ERR_OK(status)) { return status; } if (!r->in.upn) { r->in.upn = talloc_asprintf(mem_ctx, "host/%s@%s", r->in.machine_name, r->out.dns_domain_name); if (!r->in.upn) { return ADS_ERROR(LDAP_NO_MEMORY); } } /* now do the mods */ mods = ads_init_mods(mem_ctx); if (!mods) { return ADS_ERROR_LDAP(LDAP_NO_MEMORY); } /* fields of primary importance */ status = ads_mod_str(mem_ctx, &mods, "userPrincipalName", r->in.upn); if (!ADS_ERR_OK(status)) { return ADS_ERROR_LDAP(LDAP_NO_MEMORY); } return ads_gen_mod(r->in.ads, r->out.dn, mods); } /**************************************************************** ****************************************************************/ static ADS_STATUS libnet_join_set_os_attributes(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { ADS_STATUS status; ADS_MODLIST mods; char *os_sp = NULL; if (!r->in.os_name || !r->in.os_version ) { return ADS_SUCCESS; } /* Find our DN */ status = libnet_join_find_machine_acct(mem_ctx, r); if (!ADS_ERR_OK(status)) { return status; } /* now do the mods */ mods = ads_init_mods(mem_ctx); if (!mods) { return ADS_ERROR(LDAP_NO_MEMORY); } os_sp = talloc_asprintf(mem_ctx, "Samba %s", SAMBA_VERSION_STRING); if (!os_sp) { return ADS_ERROR(LDAP_NO_MEMORY); } /* fields of primary importance */ status = ads_mod_str(mem_ctx, &mods, "operatingSystem", r->in.os_name); if (!ADS_ERR_OK(status)) { return status; } status = ads_mod_str(mem_ctx, &mods, "operatingSystemVersion", r->in.os_version); if (!ADS_ERR_OK(status)) { return status; } status = ads_mod_str(mem_ctx, &mods, "operatingSystemServicePack", os_sp); if (!ADS_ERR_OK(status)) { return status; } return ads_gen_mod(r->in.ads, r->out.dn, mods); } /**************************************************************** ****************************************************************/ static bool libnet_join_create_keytab(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { if (!lp_use_kerberos_keytab()) { return true; } if (!ads_keytab_create_default(r->in.ads)) { return false; } return true; } /**************************************************************** ****************************************************************/ static bool libnet_join_derive_salting_principal(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { uint32_t domain_func; ADS_STATUS status; const char *salt = NULL; char *std_salt = NULL; status = ads_domain_func_level(r->in.ads, &domain_func); if (!ADS_ERR_OK(status)) { libnet_join_set_error_string(mem_ctx, r, "failed to determine domain functional level: %s", ads_errstr(status)); return false; } /* go ahead and setup the default salt */ std_salt = kerberos_standard_des_salt(); if (!std_salt) { libnet_join_set_error_string(mem_ctx, r, "failed to obtain standard DES salt"); return false; } salt = talloc_strdup(mem_ctx, std_salt); if (!salt) { return false; } SAFE_FREE(std_salt); /* if it's a Windows functional domain, we have to look for the UPN */ if (domain_func == DS_DOMAIN_FUNCTION_2000) { char *upn; upn = ads_get_upn(r->in.ads, mem_ctx, r->in.machine_name); if (upn) { salt = talloc_strdup(mem_ctx, upn); if (!salt) { return false; } } } return kerberos_secrets_store_des_salt(salt); } /**************************************************************** ****************************************************************/ static ADS_STATUS libnet_join_post_processing_ads(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { ADS_STATUS status; if (!r->in.ads) { status = libnet_join_connect_ads(mem_ctx, r); if (!ADS_ERR_OK(status)) { return status; } } status = libnet_join_set_machine_spn(mem_ctx, r); if (!ADS_ERR_OK(status)) { libnet_join_set_error_string(mem_ctx, r, "failed to set machine spn: %s", ads_errstr(status)); return status; } status = libnet_join_set_os_attributes(mem_ctx, r); if (!ADS_ERR_OK(status)) { libnet_join_set_error_string(mem_ctx, r, "failed to set machine os attributes: %s", ads_errstr(status)); return status; } status = libnet_join_set_machine_upn(mem_ctx, r); if (!ADS_ERR_OK(status)) { libnet_join_set_error_string(mem_ctx, r, "failed to set machine upn: %s", ads_errstr(status)); return status; } if (!libnet_join_derive_salting_principal(mem_ctx, r)) { return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); } if (!libnet_join_create_keytab(mem_ctx, r)) { libnet_join_set_error_string(mem_ctx, r, "failed to create kerberos keytab"); return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); } return ADS_SUCCESS; } #endif /* WITH_ADS */ /**************************************************************** Store the machine password and domain SID ****************************************************************/ static bool libnet_join_joindomain_store_secrets(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { if (!secrets_store_domain_sid(r->out.netbios_domain_name, r->out.domain_sid)) { DEBUG(1,("Failed to save domain sid\n")); return false; } if (!secrets_store_machine_password(r->in.machine_password, r->out.netbios_domain_name, r->in.secure_channel_type)) { DEBUG(1,("Failed to save machine password\n")); return false; } return true; } /**************************************************************** Do the domain join ****************************************************************/ static NTSTATUS libnet_join_joindomain_rpc(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { struct cli_state *cli = NULL; struct rpc_pipe_client *pipe_hnd = NULL; POLICY_HND sam_pol, domain_pol, user_pol, lsa_pol; NTSTATUS status = NT_STATUS_UNSUCCESSFUL; char *acct_name; struct lsa_String lsa_acct_name; uint32_t user_rid; uint32_t acct_flags = ACB_WSTRUST; uchar pwbuf[532]; struct MD5Context md5ctx; uchar md5buffer[16]; DATA_BLOB digested_session_key; uchar md4_trust_password[16]; union lsa_PolicyInformation *info = NULL; struct samr_Ids user_rids; struct samr_Ids name_types; union samr_UserInfo user_info; if (!r->in.machine_password) { r->in.machine_password = talloc_strdup(mem_ctx, generate_random_str(DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH)); NT_STATUS_HAVE_NO_MEMORY(r->in.machine_password); } status = cli_full_connection(&cli, NULL, r->in.dc_name, NULL, 0, "IPC$", "IPC", r->in.admin_account, NULL, r->in.admin_password, 0, Undefined, NULL); if (!NT_STATUS_IS_OK(status)) { goto done; } pipe_hnd = cli_rpc_pipe_open_noauth(cli, PI_LSARPC, &status); if (!pipe_hnd) { DEBUG(0,("Error connecting to LSA pipe. Error was %s\n", nt_errstr(status))); goto done; } status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true, SEC_RIGHTS_MAXIMUM_ALLOWED, &lsa_pol); if (!NT_STATUS_IS_OK(status)) { goto done; } status = rpccli_lsa_QueryInfoPolicy2(pipe_hnd, mem_ctx, &lsa_pol, LSA_POLICY_INFO_DNS, &info); if (NT_STATUS_IS_OK(status)) { r->out.domain_is_ad = true; r->out.netbios_domain_name = info->dns.name.string; r->out.dns_domain_name = info->dns.dns_domain.string; r->out.domain_sid = info->dns.sid; } if (!NT_STATUS_IS_OK(status)) { status = rpccli_lsa_QueryInfoPolicy(pipe_hnd, mem_ctx, &lsa_pol, LSA_POLICY_INFO_ACCOUNT_DOMAIN, &info); if (!NT_STATUS_IS_OK(status)) { goto done; } r->out.netbios_domain_name = info->account_domain.name.string; r->out.domain_sid = info->account_domain.sid; } rpccli_lsa_Close(pipe_hnd, mem_ctx, &lsa_pol); cli_rpc_pipe_close(pipe_hnd); /* Open the domain */ pipe_hnd = cli_rpc_pipe_open_noauth(cli, PI_SAMR, &status); if (!pipe_hnd) { DEBUG(0,("Error connecting to SAM pipe. Error was %s\n", nt_errstr(status))); goto done; } status = rpccli_samr_Connect2(pipe_hnd, mem_ctx, pipe_hnd->cli->desthost, SEC_RIGHTS_MAXIMUM_ALLOWED, &sam_pol); if (!NT_STATUS_IS_OK(status)) { goto done; } status = rpccli_samr_OpenDomain(pipe_hnd, mem_ctx, &sam_pol, SEC_RIGHTS_MAXIMUM_ALLOWED, r->out.domain_sid, &domain_pol); if (!NT_STATUS_IS_OK(status)) { goto done; } /* Create domain user */ acct_name = talloc_asprintf(mem_ctx, "%s$", r->in.machine_name); strlower_m(acct_name); init_lsa_String(&lsa_acct_name, acct_name); if (r->in.join_flags & WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE) { uint32_t access_desired = SEC_GENERIC_READ | SEC_GENERIC_WRITE | SEC_GENERIC_EXECUTE | SEC_STD_WRITE_DAC | SEC_STD_DELETE | SAMR_USER_ACCESS_SET_PASSWORD | SAMR_USER_ACCESS_GET_ATTRIBUTES | SAMR_USER_ACCESS_SET_ATTRIBUTES; uint32_t access_granted = 0; /* Don't try to set any acct_flags flags other than ACB_WSTRUST */ DEBUG(10,("Creating account with desired access mask: %d\n", access_desired)); status = rpccli_samr_CreateUser2(pipe_hnd, mem_ctx, &domain_pol, &lsa_acct_name, ACB_WSTRUST, access_desired, &user_pol, &access_granted, &user_rid); if (!NT_STATUS_IS_OK(status) && !NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) { DEBUG(10,("Creation of workstation account failed: %s\n", nt_errstr(status))); /* If NT_STATUS_ACCESS_DENIED then we have a valid username/password combo but the user does not have administrator access. */ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { libnet_join_set_error_string(mem_ctx, r, "User specified does not have " "administrator privileges"); } return status; } if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) { if (!(r->in.join_flags & WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED)) { goto done; } } /* We *must* do this.... don't ask... */ if (NT_STATUS_IS_OK(status)) { rpccli_samr_Close(pipe_hnd, mem_ctx, &user_pol); } } status = rpccli_samr_LookupNames(pipe_hnd, mem_ctx, &domain_pol, 1, &lsa_acct_name, &user_rids, &name_types); if (!NT_STATUS_IS_OK(status)) { goto done; } if (name_types.ids[0] != SID_NAME_USER) { DEBUG(0,("%s is not a user account (type=%d)\n", acct_name, name_types.ids[0])); status = NT_STATUS_INVALID_WORKSTATION; goto done; } user_rid = user_rids.ids[0]; /* Open handle on user */ status = rpccli_samr_OpenUser(pipe_hnd, mem_ctx, &domain_pol, SEC_RIGHTS_MAXIMUM_ALLOWED, user_rid, &user_pol); if (!NT_STATUS_IS_OK(status)) { goto done; } /* Create a random machine account password and generate the hash */ E_md4hash(r->in.machine_password, md4_trust_password); encode_pw_buffer(pwbuf, r->in.machine_password, STR_UNICODE); generate_random_buffer((uint8_t*)md5buffer, sizeof(md5buffer)); digested_session_key = data_blob_talloc(mem_ctx, 0, 16); MD5Init(&md5ctx); MD5Update(&md5ctx, md5buffer, sizeof(md5buffer)); MD5Update(&md5ctx, cli->user_session_key.data, cli->user_session_key.length); MD5Final(digested_session_key.data, &md5ctx); SamOEMhashBlob(pwbuf, sizeof(pwbuf), &digested_session_key); memcpy(&pwbuf[516], md5buffer, sizeof(md5buffer)); /* Fill in the additional account flags now */ acct_flags |= ACB_PWNOEXP; if (r->out.domain_is_ad) { #if !defined(ENCTYPE_ARCFOUR_HMAC) acct_flags |= ACB_USE_DES_KEY_ONLY; #endif ;; } /* Set password and account flags on machine account */ ZERO_STRUCT(user_info.info25); user_info.info25.info.fields_present = ACCT_NT_PWD_SET | ACCT_LM_PWD_SET | SAMR_FIELD_ACCT_FLAGS; user_info.info25.info.acct_flags = acct_flags; memcpy(&user_info.info25.password.data, pwbuf, sizeof(pwbuf)); status = rpccli_samr_SetUserInfo(pipe_hnd, mem_ctx, &user_pol, 25, &user_info); if (NT_STATUS_EQUAL(status, NT_STATUS(DCERPC_FAULT_INVALID_TAG))) { uchar pwbuf2[516]; encode_pw_buffer(pwbuf2, r->in.machine_password, STR_UNICODE); /* retry with level 24 */ init_samr_user_info24(&user_info.info24, pwbuf2, 24); SamOEMhashBlob(user_info.info24.password.data, 516, &cli->user_session_key); status = rpccli_samr_SetUserInfo2(pipe_hnd, mem_ctx, &user_pol, 24, &user_info); } if (!NT_STATUS_IS_OK(status)) { libnet_join_set_error_string(mem_ctx, r, "Failed to set password for machine account (%s)\n", nt_errstr(status)); goto done; } rpccli_samr_Close(pipe_hnd, mem_ctx, &user_pol); cli_rpc_pipe_close(pipe_hnd); status = NT_STATUS_OK; done: if (cli) { cli_shutdown(cli); } return status; } /**************************************************************** ****************************************************************/ NTSTATUS libnet_join_ok(const char *netbios_domain_name, const char *machine_name, const char *dc_name) { uint32_t neg_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; struct cli_state *cli = NULL; struct rpc_pipe_client *pipe_hnd = NULL; struct rpc_pipe_client *netlogon_pipe = NULL; NTSTATUS status; char *machine_password = NULL; char *machine_account = NULL; if (!dc_name) { return NT_STATUS_INVALID_PARAMETER; } if (!secrets_init()) { return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } machine_password = secrets_fetch_machine_password(netbios_domain_name, NULL, NULL); if (!machine_password) { return NT_STATUS_NO_TRUST_LSA_SECRET; } asprintf(&machine_account, "%s$", machine_name); if (!machine_account) { SAFE_FREE(machine_password); return NT_STATUS_NO_MEMORY; } status = cli_full_connection(&cli, NULL, dc_name, NULL, 0, "IPC$", "IPC", machine_account, NULL, machine_password, 0, Undefined, NULL); free(machine_account); free(machine_password); if (!NT_STATUS_IS_OK(status)) { status = cli_full_connection(&cli, NULL, dc_name, NULL, 0, "IPC$", "IPC", "", NULL, "", 0, Undefined, NULL); } if (!NT_STATUS_IS_OK(status)) { return status; } netlogon_pipe = get_schannel_session_key(cli, netbios_domain_name, &neg_flags, &status); if (!netlogon_pipe) { if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_NETWORK_RESPONSE)) { cli_shutdown(cli); return NT_STATUS_OK; } DEBUG(0,("libnet_join_ok: failed to get schannel session " "key from server %s for domain %s. Error was %s\n", cli->desthost, netbios_domain_name, nt_errstr(status))); cli_shutdown(cli); return status; } if (!lp_client_schannel()) { cli_shutdown(cli); return NT_STATUS_OK; } pipe_hnd = cli_rpc_pipe_open_schannel_with_key(cli, PI_NETLOGON, PIPE_AUTH_LEVEL_PRIVACY, netbios_domain_name, netlogon_pipe->dc, &status); cli_shutdown(cli); if (!pipe_hnd) { DEBUG(0,("libnet_join_ok: failed to open schannel session " "on netlogon pipe to server %s for domain %s. " "Error was %s\n", cli->desthost, netbios_domain_name, nt_errstr(status))); return status; } return NT_STATUS_OK; } /**************************************************************** ****************************************************************/ static WERROR libnet_join_post_verify(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { NTSTATUS status; status = libnet_join_ok(r->out.netbios_domain_name, r->in.machine_name, r->in.dc_name); if (!NT_STATUS_IS_OK(status)) { libnet_join_set_error_string(mem_ctx, r, "failed to verify domain membership after joining: %s", get_friendly_nt_error_msg(status)); return WERR_SETUP_NOT_JOINED; } return WERR_OK; } /**************************************************************** ****************************************************************/ static bool libnet_join_unjoindomain_remove_secrets(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx *r) { if (!secrets_delete_machine_password_ex(lp_workgroup())) { return false; } if (!secrets_delete_domain_sid(lp_workgroup())) { return false; } return true; } /**************************************************************** ****************************************************************/ static NTSTATUS libnet_join_unjoindomain_rpc(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx *r) { struct cli_state *cli = NULL; struct rpc_pipe_client *pipe_hnd = NULL; POLICY_HND sam_pol, domain_pol, user_pol; NTSTATUS status = NT_STATUS_UNSUCCESSFUL; char *acct_name; uint32_t user_rid; struct lsa_String lsa_acct_name; struct samr_Ids user_rids; struct samr_Ids name_types; union samr_UserInfo *info = NULL; status = cli_full_connection(&cli, NULL, r->in.dc_name, NULL, 0, "IPC$", "IPC", r->in.admin_account, NULL, r->in.admin_password, 0, Undefined, NULL); if (!NT_STATUS_IS_OK(status)) { goto done; } /* Open the domain */ pipe_hnd = cli_rpc_pipe_open_noauth(cli, PI_SAMR, &status); if (!pipe_hnd) { DEBUG(0,("Error connecting to SAM pipe. Error was %s\n", nt_errstr(status))); goto done; } status = rpccli_samr_Connect2(pipe_hnd, mem_ctx, pipe_hnd->cli->desthost, SEC_RIGHTS_MAXIMUM_ALLOWED, &sam_pol); if (!NT_STATUS_IS_OK(status)) { goto done; } status = rpccli_samr_OpenDomain(pipe_hnd, mem_ctx, &sam_pol, SEC_RIGHTS_MAXIMUM_ALLOWED, r->in.domain_sid, &domain_pol); if (!NT_STATUS_IS_OK(status)) { goto done; } /* Create domain user */ acct_name = talloc_asprintf(mem_ctx, "%s$", r->in.machine_name); strlower_m(acct_name); init_lsa_String(&lsa_acct_name, acct_name); status = rpccli_samr_LookupNames(pipe_hnd, mem_ctx, &domain_pol, 1, &lsa_acct_name, &user_rids, &name_types); if (!NT_STATUS_IS_OK(status)) { goto done; } if (name_types.ids[0] != SID_NAME_USER) { DEBUG(0, ("%s is not a user account (type=%d)\n", acct_name, name_types.ids[0])); status = NT_STATUS_INVALID_WORKSTATION; goto done; } user_rid = user_rids.ids[0]; /* Open handle on user */ status = rpccli_samr_OpenUser(pipe_hnd, mem_ctx, &domain_pol, SEC_RIGHTS_MAXIMUM_ALLOWED, user_rid, &user_pol); if (!NT_STATUS_IS_OK(status)) { goto done; } /* Get user info */ status = rpccli_samr_QueryUserInfo(pipe_hnd, mem_ctx, &user_pol, 16, &info); if (!NT_STATUS_IS_OK(status)) { rpccli_samr_Close(pipe_hnd, mem_ctx, &user_pol); goto done; } /* now disable and setuser info */ info->info16.acct_flags |= ACB_DISABLED; status = rpccli_samr_SetUserInfo(pipe_hnd, mem_ctx, &user_pol, 16, info); rpccli_samr_Close(pipe_hnd, mem_ctx, &user_pol); done: if (pipe_hnd) { rpccli_samr_Close(pipe_hnd, mem_ctx, &domain_pol); rpccli_samr_Close(pipe_hnd, mem_ctx, &sam_pol); cli_rpc_pipe_close(pipe_hnd); } if (cli) { cli_shutdown(cli); } return status; } /**************************************************************** ****************************************************************/ static WERROR do_join_modify_vals_config(struct libnet_JoinCtx *r) { WERROR werr; struct smbconf_ctx *ctx; werr = smbconf_init_reg(r, &ctx, NULL); if (!W_ERROR_IS_OK(werr)) { goto done; } if (!(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE)) { werr = smbconf_set_global_parameter(ctx, "security", "user"); W_ERROR_NOT_OK_GOTO_DONE(werr); werr = smbconf_set_global_parameter(ctx, "workgroup", r->in.domain_name); goto done; } werr = smbconf_set_global_parameter(ctx, "security", "domain"); W_ERROR_NOT_OK_GOTO_DONE(werr); werr = smbconf_set_global_parameter(ctx, "workgroup", r->out.netbios_domain_name); W_ERROR_NOT_OK_GOTO_DONE(werr); if (r->out.domain_is_ad) { werr = smbconf_set_global_parameter(ctx, "security", "ads"); W_ERROR_NOT_OK_GOTO_DONE(werr); werr = smbconf_set_global_parameter(ctx, "realm", r->out.dns_domain_name); W_ERROR_NOT_OK_GOTO_DONE(werr); } done: smbconf_shutdown(ctx); return werr; } /**************************************************************** ****************************************************************/ static WERROR do_unjoin_modify_vals_config(struct libnet_UnjoinCtx *r) { WERROR werr = WERR_OK; struct smbconf_ctx *ctx; werr = smbconf_init_reg(r, &ctx, NULL); if (!W_ERROR_IS_OK(werr)) { goto done; } if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) { werr = smbconf_set_global_parameter(ctx, "security", "user"); W_ERROR_NOT_OK_GOTO_DONE(werr); smbconf_delete_global_parameter(ctx, "realm"); } done: smbconf_shutdown(ctx); return werr; } /**************************************************************** ****************************************************************/ static WERROR do_JoinConfig(struct libnet_JoinCtx *r) { WERROR werr; if (!W_ERROR_IS_OK(r->out.result)) { return r->out.result; } if (!r->in.modify_config) { return WERR_OK; } werr = do_join_modify_vals_config(r); if (!W_ERROR_IS_OK(werr)) { return werr; } r->out.modified_config = true; r->out.result = werr; return werr; } /**************************************************************** ****************************************************************/ static WERROR libnet_unjoin_config(struct libnet_UnjoinCtx *r) { WERROR werr; if (!W_ERROR_IS_OK(r->out.result)) { return r->out.result; } if (!r->in.modify_config) { return WERR_OK; } werr = do_unjoin_modify_vals_config(r); if (!W_ERROR_IS_OK(werr)) { return werr; } r->out.modified_config = true; r->out.result = werr; return werr; } /**************************************************************** ****************************************************************/ static bool libnet_parse_domain_dc(TALLOC_CTX *mem_ctx, const char *domain_str, const char **domain_p, const char **dc_p) { char *domain = NULL; char *dc = NULL; const char *p = NULL; if (!domain_str || !domain_p || !dc_p) { return false; } p = strchr_m(domain_str, '\\'); if (p != NULL) { domain = talloc_strndup(mem_ctx, domain_str, PTR_DIFF(p, domain_str)); dc = talloc_strdup(mem_ctx, p+1); if (!dc) { return false; } } else { domain = talloc_strdup(mem_ctx, domain_str); dc = NULL; } if (!domain) { return false; } *domain_p = domain; if (!*dc_p && dc) { *dc_p = dc; } return true; } /**************************************************************** ****************************************************************/ static WERROR libnet_join_pre_processing(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { if (!r->in.domain_name) { libnet_join_set_error_string(mem_ctx, r, "No domain name defined"); return WERR_INVALID_PARAM; } if (!libnet_parse_domain_dc(mem_ctx, r->in.domain_name, &r->in.domain_name, &r->in.dc_name)) { libnet_join_set_error_string(mem_ctx, r, "Failed to parse domain name"); return WERR_INVALID_PARAM; } if (r->in.modify_config && !lp_config_backend_is_registry()) { libnet_join_set_error_string(mem_ctx, r, "Configuration manipulation requested but not " "supported by backend"); return WERR_NOT_SUPPORTED; } if (IS_DC) { return WERR_SETUP_DOMAIN_CONTROLLER; } if (!secrets_init()) { libnet_join_set_error_string(mem_ctx, r, "Unable to open secrets database"); return WERR_CAN_NOT_COMPLETE; } return WERR_OK; } /**************************************************************** ****************************************************************/ static WERROR libnet_join_post_processing(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { WERROR werr; if (!W_ERROR_IS_OK(r->out.result)) { return r->out.result; } werr = do_JoinConfig(r); if (!W_ERROR_IS_OK(werr)) { return werr; } if (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) { saf_store(r->in.domain_name, r->in.dc_name); } return WERR_OK; } /**************************************************************** ****************************************************************/ static int libnet_destroy_JoinCtx(struct libnet_JoinCtx *r) { const char *krb5_cc_env = NULL; if (r->in.ads) { ads_destroy(&r->in.ads); } krb5_cc_env = getenv(KRB5_ENV_CCNAME); if (krb5_cc_env && StrCaseCmp(krb5_cc_env, "MEMORY:libnetjoin")) { unsetenv(KRB5_ENV_CCNAME); } return 0; } /**************************************************************** ****************************************************************/ static int libnet_destroy_UnjoinCtx(struct libnet_UnjoinCtx *r) { if (r->in.ads) { ads_destroy(&r->in.ads); } return 0; } /**************************************************************** ****************************************************************/ WERROR libnet_init_JoinCtx(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx **r) { struct libnet_JoinCtx *ctx; const char *krb5_cc_env = NULL; ctx = talloc_zero(mem_ctx, struct libnet_JoinCtx); if (!ctx) { return WERR_NOMEM; } talloc_set_destructor(ctx, libnet_destroy_JoinCtx); ctx->in.machine_name = talloc_strdup(mem_ctx, global_myname()); W_ERROR_HAVE_NO_MEMORY(ctx->in.machine_name); krb5_cc_env = getenv(KRB5_ENV_CCNAME); if (!krb5_cc_env || (strlen(krb5_cc_env) == 0)) { krb5_cc_env = talloc_strdup(mem_ctx, "MEMORY:libnetjoin"); W_ERROR_HAVE_NO_MEMORY(krb5_cc_env); setenv(KRB5_ENV_CCNAME, krb5_cc_env, 1); } ctx->in.secure_channel_type = SEC_CHAN_WKSTA; *r = ctx; return WERR_OK; } /**************************************************************** ****************************************************************/ WERROR libnet_init_UnjoinCtx(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx **r) { struct libnet_UnjoinCtx *ctx; ctx = talloc_zero(mem_ctx, struct libnet_UnjoinCtx); if (!ctx) { return WERR_NOMEM; } talloc_set_destructor(ctx, libnet_destroy_UnjoinCtx); ctx->in.machine_name = talloc_strdup(mem_ctx, global_myname()); W_ERROR_HAVE_NO_MEMORY(ctx->in.machine_name); *r = ctx; return WERR_OK; } /**************************************************************** ****************************************************************/ static WERROR libnet_DomainJoin(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { NTSTATUS status; #ifdef WITH_ADS ADS_STATUS ads_status; #endif /* WITH_ADS */ if (!r->in.dc_name) { struct netr_DsRGetDCNameInfo *info; status = dsgetdcname(mem_ctx, r->in.domain_name, NULL, NULL, DS_DIRECTORY_SERVICE_REQUIRED | DS_WRITABLE_REQUIRED | DS_RETURN_DNS_NAME, &info); if (!NT_STATUS_IS_OK(status)) { libnet_join_set_error_string(mem_ctx, r, "failed to find DC for domain %s", r->in.domain_name, get_friendly_nt_error_msg(status)); return WERR_DOMAIN_CONTROLLER_NOT_FOUND; } r->in.dc_name = talloc_strdup(mem_ctx, info->dc_unc); W_ERROR_HAVE_NO_MEMORY(r->in.dc_name); } #ifdef WITH_ADS if (r->in.account_ou) { ads_status = libnet_join_connect_ads(mem_ctx, r); if (!ADS_ERR_OK(ads_status)) { return WERR_DEFAULT_JOIN_REQUIRED; } ads_status = libnet_join_precreate_machine_acct(mem_ctx, r); if (!ADS_ERR_OK(ads_status)) { libnet_join_set_error_string(mem_ctx, r, "failed to precreate account in ou %s: %s", r->in.account_ou, ads_errstr(ads_status)); return WERR_DEFAULT_JOIN_REQUIRED; } r->in.join_flags &= ~WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE; } #endif /* WITH_ADS */ status = libnet_join_joindomain_rpc(mem_ctx, r); if (!NT_STATUS_IS_OK(status)) { libnet_join_set_error_string(mem_ctx, r, "failed to join domain over rpc: %s", get_friendly_nt_error_msg(status)); if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) { return WERR_SETUP_ALREADY_JOINED; } return ntstatus_to_werror(status); } if (!libnet_join_joindomain_store_secrets(mem_ctx, r)) { return WERR_SETUP_NOT_JOINED; } #ifdef WITH_ADS if (r->out.domain_is_ad) { ads_status = libnet_join_post_processing_ads(mem_ctx, r); if (!ADS_ERR_OK(ads_status)) { return WERR_GENERAL_FAILURE; } } #endif /* WITH_ADS */ return WERR_OK; } /**************************************************************** ****************************************************************/ WERROR libnet_Join(TALLOC_CTX *mem_ctx, struct libnet_JoinCtx *r) { WERROR werr; if (r->in.debug) { LIBNET_JOIN_IN_DUMP_CTX(mem_ctx, r); } werr = libnet_join_pre_processing(mem_ctx, r); if (!W_ERROR_IS_OK(werr)) { goto done; } if (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) { werr = libnet_DomainJoin(mem_ctx, r); if (!W_ERROR_IS_OK(werr)) { goto done; } werr = libnet_join_post_verify(mem_ctx, r); if (!W_ERROR_IS_OK(werr)) { goto done; } } werr = libnet_join_post_processing(mem_ctx, r); if (!W_ERROR_IS_OK(werr)) { goto done; } done: r->out.result = werr; if (r->in.debug) { LIBNET_JOIN_OUT_DUMP_CTX(mem_ctx, r); } return werr; } /**************************************************************** ****************************************************************/ static WERROR libnet_DomainUnjoin(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx *r) { NTSTATUS status; if (!r->in.domain_sid) { struct dom_sid sid; if (!secrets_fetch_domain_sid(lp_workgroup(), &sid)) { libnet_unjoin_set_error_string(mem_ctx, r, "Unable to fetch domain sid: are we joined?"); return WERR_SETUP_NOT_JOINED; } r->in.domain_sid = sid_dup_talloc(mem_ctx, &sid); W_ERROR_HAVE_NO_MEMORY(r->in.domain_sid); } if (!r->in.dc_name) { struct netr_DsRGetDCNameInfo *info; status = dsgetdcname(mem_ctx, r->in.domain_name, NULL, NULL, DS_DIRECTORY_SERVICE_REQUIRED | DS_WRITABLE_REQUIRED | DS_RETURN_DNS_NAME, &info); if (!NT_STATUS_IS_OK(status)) { libnet_unjoin_set_error_string(mem_ctx, r, "failed to find DC for domain %s", r->in.domain_name, get_friendly_nt_error_msg(status)); return WERR_DOMAIN_CONTROLLER_NOT_FOUND; } r->in.dc_name = talloc_strdup(mem_ctx, info->dc_unc); W_ERROR_HAVE_NO_MEMORY(r->in.dc_name); } status = libnet_join_unjoindomain_rpc(mem_ctx, r); if (!NT_STATUS_IS_OK(status)) { libnet_unjoin_set_error_string(mem_ctx, r, "failed to disable machine account via rpc: %s", get_friendly_nt_error_msg(status)); if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { return WERR_SETUP_NOT_JOINED; } return ntstatus_to_werror(status); } r->out.disabled_machine_account = true; #ifdef WITH_ADS if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE) { ADS_STATUS ads_status; libnet_unjoin_connect_ads(mem_ctx, r); ads_status = libnet_unjoin_remove_machine_acct(mem_ctx, r); if (!ADS_ERR_OK(ads_status)) { libnet_unjoin_set_error_string(mem_ctx, r, "failed to remove machine account from AD: %s", ads_errstr(ads_status)); } else { r->out.deleted_machine_account = true; /* dirty hack */ r->out.dns_domain_name = talloc_strdup(mem_ctx, r->in.ads->server.realm); W_ERROR_HAVE_NO_MEMORY(r->out.dns_domain_name); } } #endif /* WITH_ADS */ libnet_join_unjoindomain_remove_secrets(mem_ctx, r); return WERR_OK; } /**************************************************************** ****************************************************************/ static WERROR libnet_unjoin_pre_processing(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx *r) { if (!r->in.domain_name) { libnet_unjoin_set_error_string(mem_ctx, r, "No domain name defined"); return WERR_INVALID_PARAM; } if (!libnet_parse_domain_dc(mem_ctx, r->in.domain_name, &r->in.domain_name, &r->in.dc_name)) { libnet_unjoin_set_error_string(mem_ctx, r, "Failed to parse domain name"); return WERR_INVALID_PARAM; } if (r->in.modify_config && !lp_config_backend_is_registry()) { libnet_unjoin_set_error_string(mem_ctx, r, "Configuration manipulation requested but not " "supported by backend"); return WERR_NOT_SUPPORTED; } if (IS_DC) { return WERR_SETUP_DOMAIN_CONTROLLER; } if (!secrets_init()) { libnet_unjoin_set_error_string(mem_ctx, r, "Unable to open secrets database"); return WERR_CAN_NOT_COMPLETE; } return WERR_OK; } /**************************************************************** ****************************************************************/ static WERROR libnet_unjoin_post_processing(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx *r) { saf_delete(r->out.netbios_domain_name); saf_delete(r->out.dns_domain_name); return libnet_unjoin_config(r); } /**************************************************************** ****************************************************************/ WERROR libnet_Unjoin(TALLOC_CTX *mem_ctx, struct libnet_UnjoinCtx *r) { WERROR werr; if (r->in.debug) { LIBNET_UNJOIN_IN_DUMP_CTX(mem_ctx, r); } werr = libnet_unjoin_pre_processing(mem_ctx, r); if (!W_ERROR_IS_OK(werr)) { goto done; } if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) { werr = libnet_DomainUnjoin(mem_ctx, r); if (!W_ERROR_IS_OK(werr)) { libnet_unjoin_config(r); goto done; } } werr = libnet_unjoin_post_processing(mem_ctx, r); if (!W_ERROR_IS_OK(werr)) { goto done; } done: r->out.result = werr; if (r->in.debug) { LIBNET_UNJOIN_OUT_DUMP_CTX(mem_ctx, r); } return werr; }