NTSTATUS rpccli_create_netlogon_creds_ctx( struct cli_credentials *creds, const char *server_computer, struct messaging_context *msg_ctx, TALLOC_CTX *mem_ctx, struct netlogon_creds_cli_context **creds_ctx) { enum netr_SchannelType sec_chan_type; const char *server_netbios_domain; const char *server_dns_domain; const char *client_account; sec_chan_type = cli_credentials_get_secure_channel_type(creds); client_account = cli_credentials_get_username(creds); server_netbios_domain = cli_credentials_get_domain(creds); server_dns_domain = cli_credentials_get_realm(creds); return rpccli_create_netlogon_creds(server_computer, server_netbios_domain, server_dns_domain, client_account, sec_chan_type, msg_ctx, mem_ctx, creds_ctx); }
NTSTATUS rpccli_create_netlogon_creds_with_creds(struct cli_credentials *creds, const char *server_computer, struct messaging_context *msg_ctx, TALLOC_CTX *mem_ctx, struct netlogon_creds_cli_context **netlogon_creds) { enum netr_SchannelType sec_chan_type; const char *server_netbios_domain; const char *client_account; sec_chan_type = cli_credentials_get_secure_channel_type(creds); if (sec_chan_type == SEC_CHAN_NULL) { return NT_STATUS_INVALID_PARAMETER_MIX; } client_account = cli_credentials_get_username(creds); server_netbios_domain = cli_credentials_get_domain(creds); return rpccli_create_netlogon_creds(server_computer, server_netbios_domain, client_account, sec_chan_type, msg_ctx, mem_ctx, netlogon_creds); }
NTSTATUS rpccli_connect_netlogon( struct cli_state *cli, enum dcerpc_transport_t transport, struct netlogon_creds_cli_context *creds_ctx, bool force_reauth, struct cli_credentials *trust_creds, struct rpc_pipe_client **_rpccli) { TALLOC_CTX *frame = talloc_stackframe(); struct netlogon_creds_CredentialState *creds = NULL; enum netlogon_creds_cli_lck_type lck_type; enum netr_SchannelType sec_chan_type; struct netlogon_creds_cli_lck *lck = NULL; uint32_t negotiate_flags; uint8_t found_session_key[16] = {0}; bool found_existing_creds = false; bool do_serverauth; struct rpc_pipe_client *rpccli; NTSTATUS status; bool retry = false; sec_chan_type = cli_credentials_get_secure_channel_type(trust_creds); if (sec_chan_type == SEC_CHAN_NULL) { DBG_ERR("secure_channel_type gave SEC_CHAN_NULL\n"); status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; goto fail; } again: /* * See whether we can use existing netlogon_creds or * whether we have to serverauthenticate. */ status = netlogon_creds_cli_get(creds_ctx, frame, &creds); if (NT_STATUS_IS_OK(status)) { int cmp = memcmp(found_session_key, creds->session_key, sizeof(found_session_key)); found_existing_creds = (cmp != 0); memcpy(found_session_key, creds->session_key, sizeof(found_session_key)); TALLOC_FREE(creds); } lck_type = (force_reauth || !found_existing_creds) ? NETLOGON_CREDS_CLI_LCK_EXCLUSIVE : NETLOGON_CREDS_CLI_LCK_SHARED; status = netlogon_creds_cli_lck(creds_ctx, lck_type, frame, &lck); if (!NT_STATUS_IS_OK(status)) { DBG_DEBUG("netlogon_creds_cli_lck failed: %s\n", nt_errstr(status)); goto fail; } if (!found_existing_creds) { /* * Try to find creds under the lock again. Someone * else might have done it for us. */ status = netlogon_creds_cli_get(creds_ctx, frame, &creds); if (NT_STATUS_IS_OK(status)) { int cmp = memcmp(found_session_key, creds->session_key, sizeof(found_session_key)); found_existing_creds = (cmp != 0); memcpy(found_session_key, creds->session_key, sizeof(found_session_key)); TALLOC_FREE(creds); } } do_serverauth = force_reauth || !found_existing_creds; if (!do_serverauth) { /* * Do the quick schannel bind without a reauth */ status = cli_rpc_pipe_open_bind_schannel( cli, &ndr_table_netlogon, transport, creds_ctx, &rpccli); if (!retry && NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) { DBG_DEBUG("Retrying with serverauthenticate\n"); TALLOC_FREE(lck); retry = true; goto again; } if (!NT_STATUS_IS_OK(status)) { DBG_DEBUG("cli_rpc_pipe_open_bind_schannel " "failed: %s\n", nt_errstr(status)); goto fail; } goto done; } if (cli_credentials_is_anonymous(trust_creds)) { DBG_WARNING("get_trust_credential for %s only gave anonymous," "unable to negotiate NETLOGON credentials\n", netlogon_creds_cli_debug_string( creds_ctx, frame)); status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; goto fail; } status = rpccli_setup_netlogon_creds_locked( cli, transport, creds_ctx, true, trust_creds, &negotiate_flags); if (!NT_STATUS_IS_OK(status)) { DBG_DEBUG("rpccli_setup_netlogon_creds failed for %s, " "unable to setup NETLOGON credentials: %s\n", netlogon_creds_cli_debug_string( creds_ctx, frame), nt_errstr(status)); goto fail; } if (!(negotiate_flags & NETLOGON_NEG_AUTHENTICATED_RPC)) { if (lp_winbind_sealed_pipes() || lp_require_strong_key()) { status = NT_STATUS_DOWNGRADE_DETECTED; DBG_WARNING("Unwilling to make connection to %s" "without connection level security, " "must set 'winbind sealed pipes = false'" " and 'require strong key = false' " "to proceed: %s\n", netlogon_creds_cli_debug_string( creds_ctx, frame), nt_errstr(status)); goto fail; } status = cli_rpc_pipe_open_noauth_transport( cli, transport, &ndr_table_netlogon, &rpccli); if (!NT_STATUS_IS_OK(status)) { DBG_DEBUG("cli_rpc_pipe_open_noauth_transport " "failed: %s\n", nt_errstr(status)); goto fail; } goto done; } status = cli_rpc_pipe_open_bind_schannel( cli, &ndr_table_netlogon, transport, creds_ctx, &rpccli); if (!NT_STATUS_IS_OK(status)) { DBG_DEBUG("cli_rpc_pipe_open_bind_schannel " "failed: %s\n", nt_errstr(status)); goto fail; } status = netlogon_creds_cli_check(creds_ctx, rpccli->binding_handle, NULL); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("netlogon_creds_cli_check failed: %s\n", nt_errstr(status)); goto fail; } done: *_rpccli = rpccli; status = NT_STATUS_OK; fail: ZERO_STRUCT(found_session_key); TALLOC_FREE(lck); TALLOC_FREE(frame); return status; }
NTSTATUS libnet_SamSync_netlogon(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_SamSync *r) { NTSTATUS nt_status, dbsync_nt_status; TALLOC_CTX *samsync_ctx, *loop_ctx, *delta_ctx; struct netlogon_creds_CredentialState *creds; struct netr_DatabaseSync dbsync; struct netr_Authenticator credential, return_authenticator; struct netr_DELTA_ENUM_ARRAY *delta_enum_array = NULL; struct cli_credentials *machine_account; struct dcerpc_pipe *p; struct libnet_context *machine_net_ctx; struct libnet_RpcConnect *c; struct libnet_SamSync_state *state; const enum netr_SamDatabaseID database_ids[] = {SAM_DATABASE_DOMAIN, SAM_DATABASE_BUILTIN, SAM_DATABASE_PRIVS}; unsigned int i; samsync_ctx = talloc_named(mem_ctx, 0, "SamSync top context"); if (!r->in.machine_account) { machine_account = cli_credentials_init(samsync_ctx); if (!machine_account) { talloc_free(samsync_ctx); return NT_STATUS_NO_MEMORY; } cli_credentials_set_conf(machine_account, ctx->lp_ctx); nt_status = cli_credentials_set_machine_account(machine_account, ctx->lp_ctx); if (!NT_STATUS_IS_OK(nt_status)) { r->out.error_string = talloc_strdup(mem_ctx, "Could not obtain machine account password - are we joined to the domain?"); talloc_free(samsync_ctx); return nt_status; } } else { machine_account = r->in.machine_account; } /* We cannot do this unless we are a BDC. Check, before we get odd errors later */ if (cli_credentials_get_secure_channel_type(machine_account) != SEC_CHAN_BDC) { r->out.error_string = talloc_asprintf(mem_ctx, "Our join to domain %s is not as a BDC (%d), please rejoin as a BDC", cli_credentials_get_domain(machine_account), cli_credentials_get_secure_channel_type(machine_account)); talloc_free(samsync_ctx); return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; } c = talloc_zero(samsync_ctx, struct libnet_RpcConnect); if (!c) { r->out.error_string = NULL; talloc_free(samsync_ctx); return NT_STATUS_NO_MEMORY; } c->level = LIBNET_RPC_CONNECT_DC_INFO; if (r->in.binding_string) { c->in.binding = r->in.binding_string; c->in.name = NULL; } else { c->in.binding = NULL; c->in.name = cli_credentials_get_domain(machine_account); } /* prepare connect to the NETLOGON pipe of PDC */ c->in.dcerpc_iface = &ndr_table_netlogon; /* We must do this as the machine, not as any command-line * user. So we override the credentials in the * libnet_context */ machine_net_ctx = talloc(samsync_ctx, struct libnet_context); if (!machine_net_ctx) { r->out.error_string = NULL; talloc_free(samsync_ctx); return NT_STATUS_NO_MEMORY; } *machine_net_ctx = *ctx; machine_net_ctx->cred = machine_account; /* connect to the NETLOGON pipe of the PDC */ nt_status = libnet_RpcConnect(machine_net_ctx, samsync_ctx, c); if (!NT_STATUS_IS_OK(nt_status)) { if (r->in.binding_string) { r->out.error_string = talloc_asprintf(mem_ctx, "Connection to NETLOGON pipe of DC %s failed: %s", r->in.binding_string, c->out.error_string); } else { r->out.error_string = talloc_asprintf(mem_ctx, "Connection to NETLOGON pipe of DC for %s failed: %s", c->in.name, c->out.error_string); } talloc_free(samsync_ctx); return nt_status; } /* This makes a new pipe, on which we can do schannel. We * should do this in the RpcConnect code, but the abstaction * layers do not suit yet */ nt_status = dcerpc_secondary_connection(c->out.dcerpc_pipe, &p, c->out.dcerpc_pipe->binding); if (!NT_STATUS_IS_OK(nt_status)) { r->out.error_string = talloc_asprintf(mem_ctx, "Secondary connection to NETLOGON pipe of DC %s failed: %s", dcerpc_server_name(p), nt_errstr(nt_status)); talloc_free(samsync_ctx); return nt_status; } nt_status = dcerpc_bind_auth_schannel(samsync_ctx, p, &ndr_table_netlogon, machine_account, ctx->lp_ctx, DCERPC_AUTH_LEVEL_PRIVACY); if (!NT_STATUS_IS_OK(nt_status)) { r->out.error_string = talloc_asprintf(mem_ctx, "SCHANNEL authentication to NETLOGON pipe of DC %s failed: %s", dcerpc_server_name(p), nt_errstr(nt_status)); talloc_free(samsync_ctx); return nt_status; } state = talloc(samsync_ctx, struct libnet_SamSync_state); if (!state) { r->out.error_string = NULL; talloc_free(samsync_ctx); return nt_status; } state->domain_name = c->out.domain_name; state->domain_sid = c->out.domain_sid; state->realm = c->out.realm; state->domain_guid = c->out.guid; state->machine_net_ctx = machine_net_ctx; state->netlogon_pipe = p; /* initialise the callback layer. It may wish to contact the * server with ldap, now we know the name */ if (r->in.init_fn) { char *error_string; nt_status = r->in.init_fn(samsync_ctx, r->in.fn_ctx, state, &error_string); if (!NT_STATUS_IS_OK(nt_status)) { r->out.error_string = talloc_steal(mem_ctx, error_string); talloc_free(samsync_ctx); return nt_status; } } /* get NETLOGON credentials */ nt_status = dcerpc_schannel_creds(p->conn->security_state.generic_state, samsync_ctx, &creds); if (!NT_STATUS_IS_OK(nt_status)) { r->out.error_string = talloc_strdup(mem_ctx, "Could not obtain NETLOGON credentials from DCERPC/GENSEC layer"); talloc_free(samsync_ctx); return nt_status; } /* Setup details for the synchronisation */ ZERO_STRUCT(return_authenticator); dbsync.in.logon_server = talloc_asprintf(samsync_ctx, "\\\\%s", dcerpc_server_name(p)); dbsync.in.computername = cli_credentials_get_workstation(machine_account); dbsync.in.preferredmaximumlength = (uint32_t)-1; dbsync.in.return_authenticator = &return_authenticator; dbsync.out.return_authenticator = &return_authenticator; dbsync.out.delta_enum_array = &delta_enum_array; for (i=0;i< ARRAY_SIZE(database_ids); i++) { uint32_t sync_context = 0; dbsync.in.database_id = database_ids[i]; dbsync.in.sync_context = &sync_context; dbsync.out.sync_context = &sync_context; do { uint32_t d; loop_ctx = talloc_named(samsync_ctx, 0, "DatabaseSync loop context"); netlogon_creds_client_authenticator(creds, &credential); dbsync.in.credential = &credential; dbsync_nt_status = dcerpc_netr_DatabaseSync_r(p->binding_handle, loop_ctx, &dbsync); if (NT_STATUS_IS_OK(dbsync_nt_status) && !NT_STATUS_IS_OK(dbsync.out.result)) { dbsync_nt_status = dbsync.out.result; } if (!NT_STATUS_IS_OK(dbsync_nt_status) && !NT_STATUS_EQUAL(dbsync_nt_status, STATUS_MORE_ENTRIES)) { r->out.error_string = talloc_asprintf(mem_ctx, "DatabaseSync failed - %s", nt_errstr(nt_status)); talloc_free(samsync_ctx); return nt_status; } if (!netlogon_creds_client_check(creds, &dbsync.out.return_authenticator->cred)) { r->out.error_string = talloc_strdup(mem_ctx, "Credential chaining on incoming DatabaseSync failed"); talloc_free(samsync_ctx); return NT_STATUS_ACCESS_DENIED; } dbsync.in.sync_context = dbsync.out.sync_context; /* For every single remote 'delta' entry: */ for (d=0; d < delta_enum_array->num_deltas; d++) { char *error_string = NULL; delta_ctx = talloc_named(loop_ctx, 0, "DatabaseSync delta context"); /* 'Fix' elements, by decrypting and * de-obfuscating the data */ nt_status = samsync_fix_delta(delta_ctx, creds, dbsync.in.database_id, &delta_enum_array->delta_enum[d]); if (!NT_STATUS_IS_OK(nt_status)) { r->out.error_string = talloc_steal(mem_ctx, error_string); talloc_free(samsync_ctx); return nt_status; } /* Now call the callback. This will * do something like print the data or * write to an ldb */ nt_status = r->in.delta_fn(delta_ctx, r->in.fn_ctx, dbsync.in.database_id, &delta_enum_array->delta_enum[d], &error_string); if (!NT_STATUS_IS_OK(nt_status)) { r->out.error_string = talloc_steal(mem_ctx, error_string); talloc_free(samsync_ctx); return nt_status; } talloc_free(delta_ctx); } talloc_free(loop_ctx); } while (NT_STATUS_EQUAL(dbsync_nt_status, STATUS_MORE_ENTRIES)); if (!NT_STATUS_IS_OK(dbsync_nt_status)) { r->out.error_string = talloc_asprintf(mem_ctx, "libnet_SamSync_netlogon failed: unexpected inconsistancy. Should not get error %s here", nt_errstr(nt_status)); talloc_free(samsync_ctx); return dbsync_nt_status; } nt_status = NT_STATUS_OK; } talloc_free(samsync_ctx); return nt_status; }
NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context, struct messaging_context *msg_ctx, struct dcerpc_binding_handle *b, const char *domain, bool force) { TALLOC_CTX *frame = talloc_stackframe(); struct trust_pw_change_state *state; struct cli_credentials *creds = NULL; const struct samr_Password *current_nt_hash = NULL; const struct samr_Password *previous_nt_hash = NULL; enum netr_SchannelType sec_channel_type = SEC_CHAN_NULL; time_t pass_last_set_time; uint32_t old_version = 0; struct pdb_trusted_domain *td = NULL; struct timeval g_timeout = { 0, }; int timeout = 0; struct timeval tv = { 0, }; size_t new_len = DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH; uint8_t new_password_buffer[256 * 2] = { 0, }; char *new_trust_passwd = NULL; size_t len = 0; uint32_t new_version = 0; uint32_t *new_trust_version = NULL; NTSTATUS status; bool ok; state = talloc_zero(frame, struct trust_pw_change_state); if (state == NULL) { TALLOC_FREE(frame); return NT_STATUS_NO_MEMORY; } state->g_ctx = g_lock_ctx_init(state, msg_ctx); if (state->g_ctx == NULL) { TALLOC_FREE(frame); return NT_STATUS_NO_MEMORY; } state->g_lock_key = talloc_asprintf(state, "trust_password_change_%s", domain); if (state->g_lock_key == NULL) { TALLOC_FREE(frame); return NT_STATUS_NO_MEMORY; } g_timeout = timeval_current_ofs(10, 0); status = g_lock_lock(state->g_ctx, state->g_lock_key, G_LOCK_WRITE, g_timeout); if (!NT_STATUS_IS_OK(status)) { DEBUG(1, ("could not get g_lock on [%s]!\n", state->g_lock_key)); TALLOC_FREE(frame); return status; } talloc_set_destructor(state, trust_pw_change_state_destructor); status = pdb_get_trust_credentials(domain, NULL, frame, &creds); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("could not fetch domain creds for domain %s - %s!\n", domain, nt_errstr(status))); TALLOC_FREE(frame); return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE; } current_nt_hash = cli_credentials_get_nt_hash(creds, frame); if (current_nt_hash == NULL) { DEBUG(0, ("cli_credentials_get_nt_hash failed for domain %s!\n", domain)); TALLOC_FREE(frame); return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE; } old_version = cli_credentials_get_kvno(creds); pass_last_set_time = cli_credentials_get_password_last_changed_time(creds); sec_channel_type = cli_credentials_get_secure_channel_type(creds); new_version = old_version + 1; switch (sec_channel_type) { case SEC_CHAN_WKSTA: case SEC_CHAN_BDC: break; case SEC_CHAN_DNS_DOMAIN: /* * new_len * 2 = 498 bytes is the largest possible length * NL_PASSWORD_VERSION consumes the rest of the possible 512 bytes * and a confounder with at least 2 bytes is required. * * Windows uses new_len = 120 => 240 bytes. */ new_len = 120; /* fall through */ case SEC_CHAN_DOMAIN: status = pdb_get_trusted_domain(frame, domain, &td); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("pdb_get_trusted_domain() failed for domain %s - %s!\n", domain, nt_errstr(status))); TALLOC_FREE(frame); return status; } new_trust_version = &new_version; break; default: TALLOC_FREE(frame); return NT_STATUS_NOT_SUPPORTED; } timeout = lp_machine_password_timeout(); if (timeout == 0) { if (!force) { DEBUG(10,("machine password never expires\n")); TALLOC_FREE(frame); return NT_STATUS_OK; } } tv.tv_sec = pass_last_set_time; DEBUG(10, ("password last changed %s\n", timeval_string(talloc_tos(), &tv, false))); tv.tv_sec += timeout; DEBUGADD(10, ("password valid until %s\n", timeval_string(talloc_tos(), &tv, false))); if (!force && !timeval_expired(&tv)) { TALLOC_FREE(frame); return NT_STATUS_OK; } /* * Create a random machine account password * We create a random buffer and convert that to utf8. * This is similar to what windows is doing. */ generate_secret_buffer(new_password_buffer, new_len * 2); ok = convert_string_talloc(frame, CH_UTF16MUNGED, CH_UTF8, new_password_buffer, new_len * 2, (void *)&new_trust_passwd, &len); ZERO_STRUCT(new_password_buffer); if (!ok) { DEBUG(0, ("convert_string_talloc failed\n")); TALLOC_FREE(frame); return NT_STATUS_NO_MEMORY; } /* * We could use cli_credentials_get_old_nt_hash(creds, frame) to * set previous_nt_hash. * * But we want to check if the dc has our current password and only do * a change if that's the case. So we keep previous_nt_hash = NULL. * * TODO: * If the previous password is the only password in common with the dc, * we better skip the password change, or use something like * ServerTrustPasswordsGet() or netr_ServerGetTrustInfo() to fix our * local secrets before doing the change. */ status = netlogon_creds_cli_auth(context, b, *current_nt_hash, previous_nt_hash); if (!NT_STATUS_IS_OK(status)) { DEBUG(0, ("netlogon_creds_cli_auth for domain %s - %s!\n", domain, nt_errstr(status))); TALLOC_FREE(frame); return status; } /* * Return the result of trying to write the new password * back into the trust account file. */ switch (sec_channel_type) { case SEC_CHAN_WKSTA: case SEC_CHAN_BDC: ok = secrets_store_machine_password(new_trust_passwd, domain, sec_channel_type); if (!ok) { DEBUG(0, ("secrets_store_machine_password failed for domain %s!\n", domain)); TALLOC_FREE(frame); return NT_STATUS_INTERNAL_DB_CORRUPTION; } break; case SEC_CHAN_DNS_DOMAIN: case SEC_CHAN_DOMAIN: /* * we need to get the sid first for the * pdb_set_trusteddom_pw call */ ok = pdb_set_trusteddom_pw(domain, new_trust_passwd, &td->security_identifier); if (!ok) { DEBUG(0, ("pdb_set_trusteddom_pw() failed for domain %s!\n", domain)); TALLOC_FREE(frame); return NT_STATUS_INTERNAL_DB_CORRUPTION; } break; default: smb_panic("Unsupported secure channel type"); break; } DEBUG(1,("%s : %s(%s): Changed password locally\n", current_timestring(talloc_tos(), false), __func__, domain)); status = netlogon_creds_cli_ServerPasswordSet(context, b, new_trust_passwd, new_trust_version); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,("%s : %s(%s) remote password change set failed - %s\n", current_timestring(talloc_tos(), false), __func__, domain, nt_errstr(status))); TALLOC_FREE(frame); return status; } DEBUG(1,("%s : %s(%s): Changed password remotely.\n", current_timestring(talloc_tos(), false), __func__, domain)); TALLOC_FREE(frame); return NT_STATUS_OK; }